$30 off During Our Annual Pro Sale. View Details »

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
Tweet

More Decks by Yuta Saito

Other Decks in Technology

Transcript

  1. Porting Swift to WebAssembly
    WebAssembly Live! Extended
    @kateinoigakukun
    1

    View Slide

  2. Who am I
    Yuta Saito (a.k.a @kateinoigakukun)
    • Co-maintainer of SwiftWasm
    • Working at Merpay, Inc.
    • Love Swift
    !
    2

    View Slide

  3. Motivation: Why Swift?
    • Safe
    • Flexible
    • Not only for iOS application
    • Multi-paradigm
    • Compiled Language
    • Seamless C-interoperability
    3

    View Slide

  4. Motivation: Why porting to WebAssembly?
    • Portable
    • Access to Web developer community
    4

    View Slide

  5. The all story started with an issue
    5

    View Slide

  6. 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

    View Slide

  7. The story of SwiftWasm
    • Sep, 2019: Started hunting Wasm specific bugs around compiler
    • Relative Pointer
    • Swift Calling Convention
    • etc...
    7

    View Slide

  8. Relative Pointer
    • Offset is 32-bit, saving lots of space on 64-bit platforms.
    • Calculated by PC-relative relocation designed for jumping.
    8

    View Slide

  9. 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

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

  12. The story of SwiftWasm
    • Jan, 2020: Develop WebAssembly debugger
    https://github.com/kateinoigakukun/wasminspect 12

    View Slide

  13. The story of SwiftWasm
    • Mar, 2020: Passing all standard library test suites
    13

    View Slide

  14. The story of SwiftWasm
    • Mar, 2020: Continuous
    Integration and Delivery
    14

    View Slide

  15. The story of SwiftWasm
    • Apr, 2020: Published JavaScriptKit: JavaScript interoperation
    • Jul, 2020: Published Tokamak: SwiftUI compatible UI framework
    for Wasm
    15

    View Slide

  16. 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

    View Slide

  17. The story of SwiftWasm
    • Nov, 2020: Swift cross-module DCE to reduce binary size
    • Jan, 2021: Experimental concurrency support with JS event loop
    17

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. 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

    View Slide

  24. Future Works
    • WASI-less "bare metal" target
    • Performance and Binary size improvement
    24

    View Slide

  25. Contributions are welcome!
    • Website: https://swiftwasm.org
    • GitHub: https://github.com/swiftwasm
    25

    View Slide

  26. Thanks!
    • All SwiftWasm contributors and sponsors
    • The Swift community
    26

    View Slide