Introduction à WebAssembly et Swift au niveau avancé
\n\nWebAssembly (Wasm) est un format d'instructions binaires de bas niveau conçu pour s'exécuter dans le navigateur et sur le serveur avec des performances proches du natif. Pour les développeurs Swift travaillant avec iOS et macOS, l'intégration de Wasm ouvre des possibilités de transfert de calculs haute performance (cryptographie, traitement d'images, simulations) de l'environnement web vers les applications natives. Dans cet article, nous aborderons des techniques avancées : appel direct de fonctions Wasm depuis Swift, gestion de la mémoire via la mémoire linéaire Wasm, optimisation de l'ABI (Application Binary Interface) et traitement de types de données complexes.
\n\nIl est supposé que vous êtes déjà familiarisé avec les bases de Swift et que vous avez de l'expérience dans la compilation de code C/C++ en Wasm (par exemple, via Emscripten ou wasi-sdk). Nous nous concentrerons sur la liaison entre Swift (iOS/macOS) et un module Wasm compilé, en utilisant des dépendances minimales — uniquement les bibliothèques standard Foundation et les capacités de base de Swift pour la gestion de la mémoire.
\n\n1. Compilation de code C/C++ en WebAssembly et préparation de l'ABI
\n\nLa première étape consiste à préparer un module Wasm avec un ABI clairement défini. Pour Swift, il est important que les fonctions exportées aient des signatures plates (uniquement des nombres : i32, i64, f32, f64) et n'utilisent pas de structures complexes. Voici un exemple de fonction C pour calculer la somme d'un tableau :
\n\n// sum.c\n#include <stdint.h>\n\nint32_t sum_array(int32_t* arr, int32_t len) {\n int32_t sum = 0;\n for (int32_t i = 0; i < len; i++) {\n sum += arr[i];\n }\n return sum;\n}\n\n// Fonction pour allouer de la mémoire dans Wasm\nint32_t* allocate_array(int32_t size) {\n return (int32_t*)malloc(size * sizeof(int32_t));\n}\n\n\nCompilation avec wasi-sdk (CLang pour Wasm) :
\n\nclang \\\n --target=wasm32-wasi \\\n -O3 \\\n -nostartfiles \\\n -Wl,--export=sum_array \\\n -Wl,--export=allocate_array \\\n -Wl,--export=__heap_base \\\n -o sum.wasm sum.c\n\n\nLe drapeau --export spécifie explicitement les fonctions exportées. __heap_base est un symbole standard de Wasm indiquant le début du tas, nécessaire pour la gestion de la mémoire depuis Swift.
2. Chargement du module Wasm dans Swift et exécution des fonctions
\n\nPour travailler avec Wasm dans Swift sur iOS/macOS, nous utilisons le framework WebKit (WKWebView) ou l'API C de bas niveau via le module JavaScriptCore. Cependant, pour une solution Swift pure sans interface utilisateur, il est plus pratique d'utiliser la bibliothèque WasmInterpreter de SwiftWasm (disponible via SPM). Considérons une variante utilisant JavaScriptCore — intégré dans iOS/macOS.
2.1. Initialisation et chargement du fichier binaire
\n\nimport JavaScriptCore\n\nclass WasmEngine {\n private let context: JSContext\n\n init?(wasmData: Data) {\n guard let ctx = JSContext() else { return nil }\n self.context = ctx\n\n // Créer l'objet WebAssembly\n let wasmObj = ctx.evaluateScript("WebAssembly")\n guard let wasm = wasmObj else { return nil }\n\n // Compiler le module\n let compileFunc = wasm.objectForKeyedSubscript("compile")\n guard let compile = compileFunc else { return nil }\n\n let promise = compile.call(withArguments: [wasmData])\n // Traiter la promesse (simplifié pour la démonstration)\n context.evaluateScript("""\n var wasmModule = null;\n \\(promise?.toString() ?? "").then(m => { wasmModule = m; });\n """)\n }\n\n func callFunction(name: String, args: [JSValue]) -> JSValue? {\n let instance = context.objectForKeyedSubscript("wasmModule")\n let exports = instance?.objectForKeyedSubscript("exports")\n let funcToCall = exports?.objectForKeyedSubscript(name)\n return funcToCall?.call(withArguments: args)\n }\n}\n\n\nImportant : Ce code est simplifié. Dans un projet réel, utilisez JSVirtualMachin