Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Compile your Swift code into WebAssembly

7a4968fbcd56e81f95a4f3c186141b52?s=47 Yuta Saito
November 16, 2019

Compile your Swift code into WebAssembly

7a4968fbcd56e81f95a4f3c186141b52?s=128

Yuta Saito

November 16, 2019
Tweet

Transcript

  1. Compile your Swift code into WebAssembly SwiftcKaigi #1 @kateinoigakukun 1

  2. Introduction @kateinoigakukun • Intern at Merpay Expert Team • Love

    Swift ! 2
  3. What is WebAssembly? WebAssembly (abbreviated Wasm) is a binary instruction

    format for a stack-based virtual machine. Wasm is designed as a portable target for compilation of high-level languages like C/ C++/Rust, enabling deployment on the web for client and server applications1. 1 https://webassembly.org/ 3
  4. https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/ 4

  5. https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/ 5

  6. Swift on browser? 6

  7. SwiftWasm Project https://swiftwasm.org • Supporting WebAssembly for Swift • Fork

    and apply patches to • apple/swift • apple/llvm-project • etc.. 7
  8. Build Pipeline • LLVM supports WebAssembly target 8

  9. It's easy to compile Swift into WebAssembly 9

  10. It's easy very hard to compile Swift into WebAssembly 10

  11. What's hard? 1. Swift heavily depends on libc 2. Swift

    runtime needs some linker supports 3. Swift uses special calling convention 11
  12. Swift heavily depends on libc 12

  13. Swift heavily depends on libc 13

  14. print public func print( _ items: Any..., separator: String =

    " ", terminator: String = "\n" ) { var output = _Stdout() _print(items, separator: separator, terminator: terminator, to: &output) } https://github.com/apple/swift/blob/master/stdlib/public/core/Print.swift#L52-L66 14
  15. print internal struct _Stdout: TextOutputStream { ... internal mutating func

    write(_ string: String) { if string.isEmpty { return } var string = string _ = string.withUTF8 { utf8 in _swift_stdlib_fwrite_stdout(utf8.baseAddress!, 1, utf8.count) } } } https://github.com/apple/swift/blob/master/stdlib/public/stubs/LibcShims.cpp#L50-L55 15
  16. _swift_stdlib_fwrite_stdout #include <stdio.h> SWIFT_RUNTIME_STDLIB_INTERNAL __swift_size_t swift::_swift_stdlib_fwrite_stdout(const void *ptr, __swift_size_t size,

    __swift_size_t nitems) { return fwrite(ptr, size, nitems, stdout); } https://github.com/apple/swift/blob/master/stdlib/public/stubs/LibcShims.cpp#L50-L55 16
  17. WebAssembly System Interface (WASI) • A system interface for conceptual

    OS on WebAssembly • Focus on portability and security https://github.com/WebAssembly/WASI 17
  18. wasi-libc CraneStation/wasi-libc • A libc implementation using WASI and musl

    • Can be built as static library 18
  19. Runtime dependency on WebAssembly 19

  20. What's hard? 1. Swift heavily depends on libc 2. Swift

    runtime needs some linker supports 3. Swift uses special calling convention 20
  21. Relative Pointer 21

  22. Relative Pointer • Reduce binary size • Minimize load time

    relocation 22
  23. How Relative Pointer generated 1 . Compiler emits LLVM IR

    using sub instruction on global variable to calculate the difference between two addresses. 23
  24. $ swiftc -emit-ir relative_pointer.swift struct Animal {} let type =

    Animal.self 24
  25. @"$s16relative_pointer6AnimalVMF" = internal constant { i32, i32, i16, i16, i32

    } { i32 trunc ( i64 sub ( i64 ptrtoint (<{ i8, i32, i8 }>* @"symbolic _____ 16relative_pointer6AnimalV" to i64), i64 ptrtoint ({ i32, i32, i16, i16, i32 }* @"$s16relative_pointer6AnimalVMF" to i64) ) to i32 ), i32 0, i16 0, i16 12, i32 0 }, section "__TEXT,__swift5_fieldmd, regular, no_dead_strip", align 4 25
  26. How Relative Pointer generated 2 . LLVM transforms the sub

    instruction into RELOC_SUBTRACTOR2 linker command. $ swiftc -c relative_pointer.swift $ objdump -x relative_pointer.o ... RELOCATION RECORDS FOR [__swift5_fieldmd]: 0000000000000000 X86_64_RELOC_SUBTRACTOR _symbolic _____ 16relative_pointer6AnimalV-_$s16relative_pointer6AnimalVMF 0000000000000000 X86_64_RELOC_UNSIGNED _symbolic _____ 16relative_pointer6AnimalV ... 2 for MachO format. 26
  27. How Relative Pointer generated 3 . Linker links the object

    files and handle RELOC_SUBTRACTOR. Only linker can calculate the difference because compiler can't know where the global variable will be located. $ swiftc relative_pointer.o $ objdump -x relative_pointer No RELOCATION RECORDS !! 27
  28. How Relative Pointer generated 4 . No need to be

    relocated by program loader. Make program launch time fast ! 28
  29. Relative Pointer doesn't work on WebAssembly 29

  30. wasm-ld • A linker project for WebAssembly • A part

    of LLVM project • Doesn't support RELOC_SUBTRACTOR yet. 30
  31. Use absolute pointer instead @zhuowei tried to use absolute pointer

    instead of relative pointer. const uint32_t offset = ...; #ifdef __wasm__ const Pointee *address = reinterpret_cast<Pointee *>(offset); #else const Pointee *address = reinterpret_cast<Pointee *>(baseAddress + offset); #endif 31
  32. What's hard? 1. Swift heavily depends on libc 2. Swift

    runtime needs some linker supports 3. Swift uses special calling convention 32
  33. Swift Calling Convention on WebAssembly • Almost works even on

    stack-based machine • swifterror attribute breaks calling convention on WebAssembly 33
  34. Function conversion (Int?) -> Void to (Int) -> Void func

    f(_ a: (Int) -> Void) -> Void { a(1) } func g(_ b: (Int?) -> Void) -> Void { f(b) } g { _ in } 34
  35. Function conversion (Int?) -> Void to (Int) -> Void func

    f(_ a: (Int) -> Void) -> Void { a(1) } func g(_ b: (Int?) -> Void) -> Void { func thunk(_ x: Int) -> Void { // 0. Capture `b` // 1. Transform arguments let y = Optional<Int>.some(x) // 2. Apply return b(y) } f(thunk) } g { _ in } 35
  36. Function conversion (Int) -> Void to (Int) throws -> Void

    func f(_ a: (Int) throws -> Void) -> Void { try! a(1) } func g(_ b: (Int) -> Void) -> Void { f(b) } g { _ in } 36
  37. swiftc -emit-ir Experiment.swift 37

  38. define i32 @main(i32, i8**) #0 { entry: call swiftcc void

    @"g"( i8* bitcast (void (i64)* @"closure" to i8*), %swift.opaque* null ) ret i32 0 } define hidden swiftcc void @"f"(i8*, %swift.opaque*) #0 { entry: ... %err = alloca swifterror %swift.error*, align 8 store %swift.error* null, %swift.error** %err, align 8 %2 = bitcast i8* %0 to void (i64, %swift.refcounted*, %swift.error**)* call swiftcc void %2(i64 1, %swift.error** swifterror %err) } define hidden swiftcc void @"g"(i8*, %swift.opaque*) #0 { entry: call swiftcc void @"f"(i8* %0, %swift.opaque* %1) ret void } define internal swiftcc void @"closure"(i64) #0 { entry: ret void } 38
  39. define i32 @main(i32, i8**) #0 { entry: call swiftcc void

    @"g"( i8* bitcast (void (i64)* @"closure" to i8*), %swift.opaque* null ) ret i32 0 } define hidden swiftcc void @"f"(i8*, %swift.opaque*) #0 { entry: ... %err = alloca swifterror %swift.error*, align 8 store %swift.error* null, %swift.error** %err, align 8 %2 = bitcast i8* %0 to void (i64, %swift.refcounted*, %swift.error**)* call swiftcc void %2(i64 1, %swift.error** swifterror %err) } define hidden swiftcc void @"g"(i8*, %swift.opaque*) #0 { entry: call swiftcc void @"f"(i8* %0, %swift.opaque* %1) ret void } define internal swiftcc void @"closure"(i64) #0 { entry: ret void } 38
  40. define i32 @main(i32, i8**) #0 { entry: call swiftcc void

    @"g"( i8* bitcast (void (i64)* @"closure" to i8*), %swift.opaque* null ) ret i32 0 } define hidden swiftcc void @"f"(i8*, %swift.opaque*) #0 { entry: ... %err = alloca swifterror %swift.error*, align 8 store %swift.error* null, %swift.error** %err, align 8 %2 = bitcast i8* %0 to void (i64, %swift.refcounted*, %swift.error**)* call swiftcc void %2(i64 1, %swift.error** swifterror %err) } define hidden swiftcc void @"g"(i8*, %swift.opaque*) #0 { entry: call swiftcc void @"f"(i8* %0, %swift.opaque* %1) ret void } define internal swiftcc void @"closure"(i64) #0 { entry: ret void } 38
  41. define i32 @main(i32, i8**) #0 { entry: call swiftcc void

    @"g"( i8* bitcast (void (i64)* @"closure" to i8*), %swift.opaque* null ) ret i32 0 } define hidden swiftcc void @"f"(i8*, %swift.opaque*) #0 { entry: ... %err = alloca swifterror %swift.error*, align 8 store %swift.error* null, %swift.error** %err, align 8 %2 = bitcast i8* %0 to void (i64, %swift.refcounted*, %swift.error**)* call swiftcc void %2(i64 1, %swift.error** swifterror %err) } define hidden swiftcc void @"g"(i8*, %swift.opaque*) #0 { entry: call swiftcc void @"f"(i8* %0, %swift.opaque* %1) ret void } define internal swiftcc void @"closure"(i64) #0 { entry: ret void } 38
  42. Runtime (Swift like language) func f(_ a: (Int, inout Error)

    throws -> Void) -> Void { var error: Error = nullptr a(1, &error) if error != nullptr { fatalError() } } func g(_ b: (Int) -> Void) -> Void { f(b) } func closure(_: Int) -> Void {} g(closure) 39
  43. swifterror This attribute is motivated to model and optimize Swift

    error handling3. • Place the argument in a specific register • No effect to stack • ABI compatible with one which doesn't have swifterror 3 https://llvm.org/docs/LangRef.html#parameter-attributes 40
  44. swifterror on WebAssembly WebAssembly runtime checks number of arguments for

    indirect call for security purpose. But swifterror needs a variadic error parameter https://github.com/WebAssembly/design/blob/master/Rationale.md#indirect-calls 41
  45. Solutions There are two ways • Add swifterror to all

    functions • Use thunk function 42
  46. Use thunk function func f(_ a: (Int) throws -> Void)

    -> Void { try! a(1) } func g(_ b: (Int) -> Void) -> Void { func thunk(_ x: Int) throws -> Void { return b(x) } f(thunk) } g { _ in } 43
  47. Still... • Many crashes related with metadata • No debugger

    for Swift on WebAssembly • Large binary (Hello, world binary is over 5 MB) • etc... 44
  48. Conclusion • Supporting new platform is hard but fun •

    It's a chance to learn Swift compiler techniques 45