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
Tweet

More Decks by Yuta Saito

Other Decks in Technology

Transcript

  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