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

Compile your Swift code into WebAssembly

Yuta Saito
November 16, 2019

Compile your Swift code into WebAssembly

Yuta Saito

November 16, 2019
Tweet

More Decks by Yuta Saito

Other Decks in Programming

Transcript

  1. 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
  2. SwiftWasm Project https://swiftwasm.org • Supporting WebAssembly for Swift • Fork

    and apply patches to • apple/swift • apple/llvm-project • etc.. 7
  3. What's hard? 1. Swift heavily depends on libc 2. Swift

    runtime needs some linker supports 3. Swift uses special calling convention 11
  4. 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
  5. 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
  6. _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
  7. WebAssembly System Interface (WASI) • A system interface for conceptual

    OS on WebAssembly • Focus on portability and security https://github.com/WebAssembly/WASI 17
  8. What's hard? 1. Swift heavily depends on libc 2. Swift

    runtime needs some linker supports 3. Swift uses special calling convention 20
  9. How Relative Pointer generated 1 . Compiler emits LLVM IR

    using sub instruction on global variable to calculate the difference between two addresses. 23
  10. @"$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
  11. 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
  12. 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
  13. How Relative Pointer generated 4 . No need to be

    relocated by program loader. Make program launch time fast ! 28
  14. wasm-ld • A linker project for WebAssembly • A part

    of LLVM project • Doesn't support RELOC_SUBTRACTOR yet. 30
  15. 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
  16. What's hard? 1. Swift heavily depends on libc 2. Swift

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

    stack-based machine • swifterror attribute breaks calling convention on WebAssembly 33
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. Solutions There are two ways • Add swifterror to all

    functions • Use thunk function 42
  29. 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
  30. Still... • Many crashes related with metadata • No debugger

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

    It's a chance to learn Swift compiler techniques 45