Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Introduction @kateinoigakukun • Intern at Merpay Expert Team • Love Swift ! 2

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Swift on browser? 6

Slide 7

Slide 7 text

SwiftWasm Project https://swiftwasm.org • Supporting WebAssembly for Swift • Fork and apply patches to • apple/swift • apple/llvm-project • etc.. 7

Slide 8

Slide 8 text

Build Pipeline • LLVM supports WebAssembly target 8

Slide 9

Slide 9 text

It's easy to compile Swift into WebAssembly 9

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

What's hard? 1. Swift heavily depends on libc 2. Swift runtime needs some linker supports 3. Swift uses special calling convention 11

Slide 12

Slide 12 text

Swift heavily depends on libc 12

Slide 13

Slide 13 text

Swift heavily depends on libc 13

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

_swift_stdlib_fwrite_stdout #include 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

Slide 17

Slide 17 text

WebAssembly System Interface (WASI) • A system interface for conceptual OS on WebAssembly • Focus on portability and security https://github.com/WebAssembly/WASI 17

Slide 18

Slide 18 text

wasi-libc CraneStation/wasi-libc • A libc implementation using WASI and musl • Can be built as static library 18

Slide 19

Slide 19 text

Runtime dependency on WebAssembly 19

Slide 20

Slide 20 text

What's hard? 1. Swift heavily depends on libc 2. Swift runtime needs some linker supports 3. Swift uses special calling convention 20

Slide 21

Slide 21 text

Relative Pointer 21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

How Relative Pointer generated 1 . Compiler emits LLVM IR using sub instruction on global variable to calculate the difference between two addresses. 23

Slide 24

Slide 24 text

$ swiftc -emit-ir relative_pointer.swift struct Animal {} let type = Animal.self 24

Slide 25

Slide 25 text

@"$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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

How Relative Pointer generated 4 . No need to be relocated by program loader. Make program launch time fast ! 28

Slide 29

Slide 29 text

Relative Pointer doesn't work on WebAssembly 29

Slide 30

Slide 30 text

wasm-ld • A linker project for WebAssembly • A part of LLVM project • Doesn't support RELOC_SUBTRACTOR yet. 30

Slide 31

Slide 31 text

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(offset); #else const Pointee *address = reinterpret_cast(baseAddress + offset); #endif 31

Slide 32

Slide 32 text

What's hard? 1. Swift heavily depends on libc 2. Swift runtime needs some linker supports 3. Swift uses special calling convention 32

Slide 33

Slide 33 text

Swift Calling Convention on WebAssembly • Almost works even on stack-based machine • swifterror attribute breaks calling convention on WebAssembly 33

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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.some(x) // 2. Apply return b(y) } f(thunk) } g { _ in } 35

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

swiftc -emit-ir Experiment.swift 37

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Solutions There are two ways • Add swifterror to all functions • Use thunk function 42

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Still... • Many crashes related with metadata • No debugger for Swift on WebAssembly • Large binary (Hello, world binary is over 5 MB) • etc... 44

Slide 48

Slide 48 text

Conclusion • Supporting new platform is hard but fun • It's a chance to learn Swift compiler techniques 45