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

Porting Swift to WebAssembly

Porting Swift to WebAssembly

Event: WebAssembly Live! Extended https://www.webassembly.live/

WebAssembly is now a compilation target of Swift. SwiftWasm project compiles Swift into Wasm using LLVM. It was an interesting challenge because Swift uses some architecture-dependent techniques. This talk covers the story of bringing Swift to WebAssembly: Porting the Swift compiler, interacting with JavaScript, interoperation of async API using JavaScript event loop.

Yuta Saito

March 30, 2021

Transcript

26 slides
  1. Who am I Yuta Saito (a.k.a @kateinoigakukun) • Co-maintainer of

    SwiftWasm • Working at Merpay, Inc. • Love Swift ! 2
  2. Motivation: Why Swift? • Safe • Flexible • Not only

    for iOS application • Multi-paradigm • Compiled Language • Seamless C-interoperability 3
  3. The story of SwiftWasm • Apr, 2019: Initial efforts made

    by @patcheng, @maxdesiatov and @zhuowei • Setting up basic build system for cross-compiling for Wasm • Build stdlib using WASI and wasi-libc for POSIX APIs. 6
  4. The story of SwiftWasm • Sep, 2019: Started hunting Wasm

    specific bugs around compiler • Relative Pointer • Swift Calling Convention • etc... 7
  5. Relative Pointer • Offset is 32-bit, saving lots of space

    on 64-bit platforms. • Calculated by PC-relative relocation designed for jumping. 8
  6. Issue with Relative Pointer No PC-relative relocation like R_X86_64_PC32 at

    that time WASM_RELOC(R_WASM_TABLE_NUMBER_LEB, 20) WASM_RELOC(R_WASM_MEMORY_ADDR_TLS_SLEB, 21) WASM_RELOC(R_WASM_FUNCTION_OFFSET_I64, 22) +WASM_RELOC(R_WASM_MEMORY_ADDR_LOCREL_I32, 23) // S + A - P https://reviews.llvm.org/D96659 9
  7. Swift Calling Convention declare swiftcc void @foo(i32, i32) @data =

    global i8* bitcast (void (i32, i32)* @foo to i8*) define swiftcc void @bar() { %1 = load i8*, i8** @data %2 = bitcast i8* %1 to void (i32, i32, i32, i32)* ; Crash on WebAssembly! ; >> call_indirect to a signature that does not match call swiftcc void %2(i32 1, i32 2, i32 swiftself 0, swifterror 0) %3 = bitcast i8* %1 to void (i32, i32, i32)* ; Crash on WebAssembly! ; >> call_indirect to a signature that does not match call swiftcc void %3(i32 1, i32 2, i32 swiftself 0) ret void } 10
  8. Swift Calling Convention ; lowered as: ; declare swiftcc void

    @foo(i32, i32, swiftself i32, swifterror i32); declare swiftcc void @foo(i32, i32) @data = global i8* bitcast (void (i32, i32)* @foo to i8*) define void @main() { %1 = load i8*, i8** @data %2 = bitcast i8* %1 to void (i32, i32, i32, i32)* call swiftcc void %2(i32 1, i32 2, i32 swiftself 0, swifterror 0) %3 = bitcast i8* %1 to void (i32, i32, i32)* ; lowered as: ; call swiftcc void %2(i32 1, i32 2, i32 swiftself 0, i32 swifterror 0) call swiftcc void %3(i32 1, i32 2, i32 swiftself 0) ret void } https://reviews.llvm.org/D76049 11
  9. The story of SwiftWasm • Jan, 2020: Develop WebAssembly debugger

    https://github.com/kateinoigakukun/wasminspect 12
  10. The story of SwiftWasm • Apr, 2020: Published JavaScriptKit: JavaScript

    interoperation • Jul, 2020: Published Tokamak: SwiftUI compatible UI framework for Wasm 15
  11. Tokamak: SwiftUI compatible UI framework for Wasm import TokamakDOM struct

    Counter: View { @State var count: Int = 0 var body: some View { VStack { Text("\(count)") HStack { Button("Reset") { count = 0 } Button("Increment") { count += 1 } } } } } Try it out!: https://pad.swiftwasm.org/ 16
  12. The story of SwiftWasm • Nov, 2020: Swift cross-module DCE

    to reduce binary size • Jan, 2021: Experimental concurrency support with JS event loop 17
  13. import JavaScriptKit let fetch = JSObject.global.fetch.function!.async func printZen() async throws

    { let result = try await fetch("https://api.github.com/zen").bject! let text = try await result.asyncing.text!() print(text) } JavaScriptEventLoop.runAsync { try! await printZen() } 18
  14. import JavaScriptKit let fetch = JSObject.global.fetch.function!.async func printZen() async throws

    { let result = try await fetch("https://api.github.com/zen").object! let text = try await result.asyncing.text!() print(text) } JavaScriptEventLoop.runAsync { try! await printZen() } 19
  15. import JavaScriptKit let fetch = JSObject.global.fetch.function!.async func printZen() async throws

    { let result = try await fetch("https://api.github.com/zen").object! let text = try await result.asyncing.text!() print(text) } JavaScriptEventLoop.runAsync { try! await printZen() } 20
  16. import JavaScriptKit let fetch = JSObject.global.fetch.function!.async func printZen() async throws

    { let result = try await fetch("https://api.github.com/zen").object! let text = try await result.asyncing.text!() print(text) } JavaScriptEventLoop.runAsync { try! await printZen() } 21
  17. import JavaScriptKit let fetch = JSObject.global.fetch.function!.async func printZen() async throws

    { let result = try await fetch("https://api.github.com/zen").object! let text = try await result.asyncing.text!() print(text) } JavaScriptEventLoop.runAsync { try! await printZen() } 22
  18. import JavaScriptKit let fetch = JSObject.global.fetch.function!.async func printZen() async throws

    { let result = try await fetch("https://api.github.com/zen").object! let text = try await result.asyncing.text!() print(text) } JavaScriptEventLoop.runAsync { try! await printZen() } 23

More Decks by Yuta Saito

Other Decks in Technology