WebAssembly and Swift: Advanced Integration on iOS/macOS

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction to WebAssembly for Swift Developers



WebAssembly (Wasm) is a low-level binary instruction that runs in an isolated virtual machine. For a Swift developer on iOS/macOS, integrating Wasm opens new horizons: porting code from other languages, a sandbox for third-party libraries, and running high-performance computations without native code. In this article, we will explore advanced techniques for working with WebAssembly in Swift: from compilation to direct memory management and function calls.



Unlike basic tutorials, we will focus on real-world tasks: passing complex data types, manual memory management, and performance optimization. It is assumed that you are already familiar with the basics of Swift and the basic concept of WebAssembly.



Compiling Code to WebAssembly for Swift



Tools and Setup



To compile third-party code (C/C++/Rust) into Wasm, use Emscripten or wasm-pack (for Rust). For pure Swift, there is the SwiftWasm project, but it is limited. Most often, you will integrate ready-made modules.



Example of compiling a simple C function into Wasm:



// math.cint add(int a, int b) {    return a + b;}


Command:



emcc math.c -o math.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]' -Os


Key flags: EXPORTED_FUNCTIONS — specifies which functions to export, -Os — optimize for size. The resulting math.wasm is ready to be loaded in Swift.



Loading a Module in Swift



On iOS/macOS, use WasmKit or WKWebView to work with Wasm. Let's consider the advanced option — WasmKit with direct memory management.



import WasmKit

guard let wasmData = try? Data(contentsOf: URL(fileURLWithPath: "math.wasm")) else { fatalError("Failed to load wasm")}

let module = try WasmModule(data: wasmData)let engine = WasmEngine()let instance = try engine.instantiate(module: module)

// Get the exported functionguard let addFunc = instance.export("add")?.function else { fatalError("Function add not found")}


Note: WasmKit requires manual management of imports and memory. This gives full control but increases complexity.



Direct Memory Management: Passing Arrays and Strings



Wasm Linear Memory



WebAssembly uses linear memory — a flat array of bytes. To pass complex structures (arrays, strings), you must allocate memory inside Wasm and copy the data.



Consider an example: passing an array of integers from Swift to a Wasm function that calculates the sum.



// sum_array.cint sum_array(int* arr, int len) {    int sum = 0;    for (int i = 0; i < len; i++) {        sum += arr[i];    }    return sum;}


Compile with malloc and free exported:



emcc sum_array.c -o sum_array.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_sum_array", "_malloc", "_free"]' -s ALLOW_MEMORY_GROWTH=1


In Swift, allocate memory, copy the data, and call the function:



let numbers = [10, 20, 30, 40, 50]let count = numbers.count

// Allocate memory for the array (4 bytes per int)guard let mallocFunc = instance.export("malloc")?.function else { return }let ptrResult = mallocFunc.call(Int32(count * MemoryLayout<Int32>.size))guard let ptr = ptrResult?.i32 else { return }

// Copy data into Wasm memorylet memory = instance.memory!for i in 0..<count { let offset = ptr + Int32(i * MemoryLayout<Int32>.size) memory.store(offset: Int(offset), value: Int32(numbers[i]))}

// Call sum_arrayguard let sumArrayFunc = instance.export("sum_array")?.function else { return }let result = sumArrayFun

Blogs

Book Recommendations