Introduction à WebAssembly pour les développeurs Swift
WebAssembly (Wasm) est un jeu d'instructions binaires de bas niveau qui s'exécute dans une machine virtuelle isolée. Pour un développeur Swift sur iOS/macOS, l'intégration de Wasm ouvre de nouveaux horizons : portage de code depuis d'autres langages, bac à sable pour les bibliothèques tierces et exécution de calculs haute performance sans code natif. Dans cet article, nous explorerons les techniques avancées de travail avec WebAssembly en Swift : de la compilation à la manipulation directe de la mémoire et à l'appel de fonctions.
Contrairement aux tutoriels de base, nous nous concentrerons sur des tâches réelles : transmission de types de données complexes, gestion manuelle de la mémoire et optimisation des performances. Il est supposé que vous êtes déjà familier avec les bases de Swift et le concept fondamental de WebAssembly.
Compilation de code en WebAssembly pour Swift
Outils et configuration
Pour compiler du code tiers (C/C++/Rust) en Wasm, utilisez Emscripten ou wasm-pack (pour Rust). Pour du Swift pur, il existe le projet SwiftWasm, mais il est limité. Le plus souvent, vous intégrerez des modules prêts à l'emploi.
Exemple de compilation d'une simple fonction C en Wasm :
// math.cint add(int a, int b) { return a + b;}Commande :
emcc math.c -o math.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]' -OsIndicateurs clés : EXPORTED_FUNCTIONS — spécifie les fonctions à exporter, -Os — optimisation de la taille. Le fichier math.wasm obtenu est prêt à être chargé dans Swift.
Chargement du module en Swift
Sur iOS/macOS, pour travailler avec Wasm, utilisez WasmKit ou WKWebView. Examinons l'option avancée — WasmKit avec gestion directe de la mémoire.
import WasmKit
guard let wasmData = try? Data(contentsOf: URL(fileURLWithPath: "math.wasm")) else { fatalError("Impossible de charger wasm")}
let module = try WasmModule(data: wasmData)let engine = WasmEngine()let instance = try engine.instantiate(module: module)
// Récupération de la fonction exportéeguard let addFunc = instance.export("add")?.function else { fatalError("Fonction add non trouvée")}Notez que WasmKit nécessite une gestion manuelle des imports et de la mémoire. Cela offre un contrôle total, mais augmente la complexité.
Travail direct avec la mémoire : transmission de tableaux et de chaînes
Mémoire linéaire Wasm
WebAssembly utilise une mémoire linéaire (linear memory) — un tableau plat d'octets. Pour transmettre des structures complexes (tableaux, chaînes), vous devez allouer de la mémoire à l'intérieur de Wasm et copier les données.
Prenons un exemple : transmission d'un tableau d'entiers de Swift à une fonction Wasm qui calcule la somme.
// sum_array.cint sum_array(int* arr, int len) { int sum = 0; for (int i = 0; i < len; i++) { sum += arr[i]; } return sum;}Compilation avec exportation de malloc et free :
emcc sum_array.c -o sum_array.wasm -s WASM=1 -s EXPORTED_FUNCTIONS='["_sum_array", "_malloc", "_free"]' -s ALLOW_MEMORY_GROWTH=1En Swift, nous allouons la mémoire, copions les données et appelons la fonction :
let numbers = [10, 20, 30, 40, 50]let count = numbers.count
// Allocation de mémoire pour le tableau (4 octets par 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 }
// Copie des données dans la mémoire Wasmlet 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]))}
// Appel de sum_arrayguard let sumArrayFunc = instance.export("sum_array")?.function else { return }let result = sumArrayFun