Write WebAssembly in Swift and use it in Swift App

Original link: https://blog.kevinzhow.com/2022/06/11/swift-webassembly/

Backgroud

I’ve been developing a new app for a while, one of the most cool idea is to let user write their own script to extend app’s ability.

But what kind of scripting language should I support? Why not to support them all? So the decision is to adapt WebAssembly which grants user’s flavor.

What’s more, We will also use Swift to write wasm thanks to the great SwiftWasm project.

Communication

WebAssembly was designed to be a 32bit sandbox VM, it’s safe and isolated from our 64bit Swift host app.

The only way to communicate between each other is to copy memory from host app into the VM and read the processed memory from VM back later.

This’s a big challenge, but we will overcome it step by step.

Reference

If you are not quite familiar with Swift pointer & memory layout, check out these talks.

Exploring Swift Memory Layout

Size, Stride, Alignment

Unsafe Swift: Using Pointers and Interacting With C

Setup SwiftWasm

1.Install swiftenv

We will use swiftenv to manage the toolchain, So please install it first.

2.Install swiftwasm

Since swiftwasm have not been merged into the repo, we gonna install it by our own.

Here is the Github release page of toolchain.

3.Set swift env in project

Use swiftenv to check the version of your swiftwasm

 swiftenv versions

Mine is wasm-5.6.0 at the time, so at the root of your project folder, run swiftenv local wasm-5.6.0 to set the project level swift env.

Basic WebAssembly App

Finished project can be found here https://github.com/kevinzhow/write-wasm-in-swift-demo

As we known before, Host app can only communicate with WebAssembly VM through memory copy, with the help of protobuf, we can transport data between 32 bit wasm VM and 64bit host app easily.

But we also need to implement few functions to handle these.

  1. allocate memory with size and return memory pointer
  2. dellocate memory at pointer
  3. function to do the real work with memory address and size.

Quick look

 import Foundation @_cdecl("allocate") func allocate(size: Int) -> UnsafeMutableRawPointer { return UnsafeMutableRawPointer.allocate(byteCount: size, alignment: MemoryLayout<UInt8>.alignment) } @_cdecl("deallocate") func deallocate(pointer: UnsafeMutableRawPointer) { pointer.deallocate() } @_cdecl("change_article_proto") func changeBookProto(protoData: UnsafeMutableRawPointer, size: Int, newAuthor: UnsafeRawPointer, authorSize: Int, newSize: UnsafeMutablePointer<Int>) -> UnsafeRawPointer { // Decode proto binary data let data = Data(bytes: protoData, count: size) var book = try! BookInfo(serializedData: data) // Change author book.author = String(data: Data(bytes: newAuthor, count: authorSize), encoding: .utf8)! let newData = try! book.serializedData() newSize.pointee = newData.count // get the data pointer of the new book proto data let pointer = newData.withUnsafeBytes{ (bufferRawBufferPointer) -> UnsafeRawPointer in let bufferPointer: UnsafePointer<UInt8> = bufferRawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) return UnsafeRawPointer(bufferPointer) } return pointer }

Now we can build our wasm with command

 swift build --triple wasm32-unknown-wasi -c release -Xlinker --allow-undefined

We pass --allow-undefined to make sure all @_cdecl function will be exported.

Then copy it out

 cp .build/release/swiftwasm.wasm ./swiftwasm.wasm

Swift Host App

Finished Project can be found here https://github.com/kevinzhow/swiftwasm-host-app-demo

First we implement a Wasm Module to handle the memory exchange and method call.

 import Foundation import WasmInterpreter import SwiftProtobuf public struct WasmModule { private let _vm: WasmInterpreter init() throws { _vm = try WasmInterpreter(module: Bundle.module.url(forResource: "swiftwasm", withExtension: "wasm")!) } /// Allocate memory on heap /// It returns byteoffset func allocate(size: Int) throws -> Int { return Int(try _vm.call("allocate", Int32(size)) as Int32) } func deallocate(byteOffset: Int) throws { try _vm.call("deallocate", Int32(byteOffset)) } /// Allocate size on heap /// It returns byteoffset func allocateSize() throws -> Int { let length = MemoryLayout<Int32>.size let newSizePointer = try! allocate(size: length) return newSizePointer } /// Write string to heap /// It returns byteoffset func writeString(string: String) throws -> (Int, Int) { let length = Data(string.utf8).count let pointer = try! allocate(size: length) try _vm.writeToHeap(string: string, byteOffset: pointer) return (pointer, length) } /// Write Data to heap /// It returns byteoffset func writeData(data: Data) throws -> Int { let length = data.count let pointer = try! allocate(size: length) try _vm.writeToHeap(data: data, byteOffset: pointer) return pointer } /// Send Protobuf binary into func changeBook(_ book: BookInfo, author: String) throws -> BookInfo { let data = try! book.serializedData() let (newAuthorPtr, newAuthorSize) = try! writeString(string: author) let newSizePointer = try! allocateSize() let dataPointer = try writeData(data: data) let newArticlePointer = Int(try _vm.call("change_article_proto", Int32(dataPointer), Int32(data.count), Int32(newAuthorPtr), Int32(newAuthorSize), Int32(newSizePointer)) as Int32) let newSizeValue = Int(try _vm.valueFromHeap(byteOffset: newSizePointer) as Int32) let newData = try _vm.dataFromHeap(byteOffset: newArticlePointer, length: newSizeValue) let newBook = try! BookInfo(serializedData: newData) try! deallocate(byteOffset: newAuthorPtr) try! deallocate(byteOffset: newSizePointer) try! deallocate(byteOffset: dataPointer) try! deallocate(byteOffset: newArticlePointer) return newBook } }

Finally we can use it.

main.swift

 import WasmInterpreter print("Hello, world!") let module = try! WasmModule() var book = BookInfo() book.id = 1 book.author = "Apple" book.title = "Swift Programming" let newBook = try! module.changeBook(book, author: "Apple Stuff") print(newBook.author)

This article is reprinted from: https://blog.kevinzhow.com/2022/06/11/swift-webassembly/
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment