WASM Integration Tutorial
Integrate WebAssembly modules with GreyOS
Advanced • Time: 45 minutesContents
Introduction
Welcome to the WASM Integration tutorial! In this advanced guide, you'll learn how to integrate WebAssembly (WASM) modules with GreyOS, enabling you to leverage existing high-performance code within your Grey applications.
WebAssembly is a binary instruction format that provides near-native performance for web applications. GreyOS takes this concept further by providing a seamless integration between WASM modules and Grey code, with automatic performance optimizations.
By the end of this tutorial, you'll understand:
- How to load and execute WebAssembly modules in GreyOS
- How to pass data between Grey code and WASM functions
- How to manage memory in WASM modules from Grey
- How to optimize WASM execution using Grey's symbolic engine
- How to compare performance between native WASM and Grey-optimized WASM
Prerequisites
Before you begin this tutorial, make sure you have:
- Completed the Hello World Tutorial
- Basic understanding of Grey syntax and concepts
- Familiarity with WebAssembly concepts (optional but helpful)
- GreyOS WASM extension module installed
To check if the WASM extension is installed, run this command in the Grey Shell:
grey module list | grep wasm
If it's not installed, you can add it with:
grey module install wasm-extension
Note: GreyOS supports WebAssembly modules compiled from C, C++, Rust, Go, AssemblyScript, and other languages that compile to WebAssembly.
Understanding WASM & Grey
Before diving into code, let's understand how WebAssembly and Grey work together:
Key concepts to understand:
- WASM Modules: Binary files (.wasm) containing compiled code that can be executed efficiently
- Memory Model: WASM uses a linear memory model that Grey can access directly
- Function Imports/Exports: WASM modules can import functions from Grey and export functions to be called by Grey
- Symbolic Optimization: Grey can analyze WASM execution patterns to apply optimizations automatically
- Type Safety: Grey provides type-safe interfaces to WASM functions
Important: Grey's WASM integration goes beyond standard WebAssembly by adding symbolic analysis and optimization—this means your WASM code often runs faster in Grey than in a regular WebAssembly runtime.
Step 1: Setting Up the Environment
Let's start by setting up a project for WASM integration. Create a new directory for your project:
grey new wasm-integration cd wasm-integration
For this tutorial, we'll use a pre-compiled WebAssembly module that implements some mathematical operations. Create a folder for WASM modules:
mkdir -p wasm/modules
Download the sample WASM module:
grey fetch https://cdn.greyos.org/samples/math.wasm --output wasm/modules/math.wasm
Now, let's create a Grey file to work with this module. Create app.grey
with basic structure:
// WASM Integration Application import { WASM } from "greyos.wasm"; symbolic WasmApp { // We'll add implementation here function main() -> Void { print("WASM Integration Example"); } } entry(WasmApp.main);
Note: The greyos.wasm
module provides the API for working with WebAssembly modules in Grey.
Step 2: Loading WASM Modules
Now, let's load our WebAssembly module and examine its exports. Update the app.grey
file:
// WASM Integration Application import { WASM } from "greyos.wasm"; symbolic WasmApp { // Reference to our loaded WASM module mathModule: Null, // Initialize and load the WASM module function initModule() -> Bool { try { print("Loading WASM module..."); // Load the WASM module from file this.mathModule = WASM.loadModule("wasm/modules/math.wasm"); // Print module info print("Module loaded successfully!"); print("Exports: " + WASM.getExports(this.mathModule).join(", ")); return true; } catch (error) { print("Error loading module: " + error); return false; } }, function main() -> Void { print("WASM Integration Example"); // Initialize WASM module if (!this.initModule()) { print("Failed to initialize WASM module. Exiting."); return; } print("WASM module initialized and ready to use!"); } } entry(WasmApp.main);
Run the application to see if the module loads correctly:
grey run app.grey
You should see output similar to this:
WASM Integration Example Loading WASM module... Module loaded successfully! Exports: fibonacci, factorial, isPrime, gcd, add, subtract, multiply, divide WASM module initialized and ready to use!
Note: The WASM.getExports()
function returns an array of all exported functions in the WebAssembly module.
Step 3: Calling WASM Functions
Now that we've loaded the module, let's call some of its exported functions. Update the app.grey
file to add function calling:
// WASM Integration Application import { WASM } from "greyos.wasm"; symbolic WasmApp { // Reference to our loaded WASM module mathModule: Null, // Initialize and load the WASM module function initModule() -> Bool { try { print("Loading WASM module..."); // Load the WASM module from file this.mathModule = WASM.loadModule("wasm/modules/math.wasm"); // Print module info print("Module loaded successfully!"); print("Exports: " + WASM.getExports(this.mathModule).join(", ")); return true; } catch (error) { print("Error loading module: " + error); return false; } }, // Call WASM functions function testMathFunctions() -> Void { if (this.mathModule == Null) { print("Module not loaded!"); return; } print("\n--- Testing Math Functions ---"); // Basic arithmetic operations let a = 25; let b = 10; print(`Addition: ${a} + ${b} = ${WASM.call(this.mathModule, "add", a, b)}`); print(`Subtraction: ${a} - ${b} = ${WASM.call(this.mathModule, "subtract", a, b)}`); print(`Multiplication: ${a} * ${b} = ${WASM.call(this.mathModule, "multiply", a, b)}`); print(`Division: ${a} / ${b} = ${WASM.call(this.mathModule, "divide", a, b)}`); // More complex functions print("\n--- Advanced Functions ---"); // Fibonacci sequence print("Fibonacci Sequence:"); for (let i = 0; i < 10; i++) { print(`fibonacci(${i}) = ${WASM.call(this.mathModule, "fibonacci", i)}`); } // Factorial calculation print("\nFactorial Calculations:"); for (let i = 0; i < 10; i++) { print(`factorial(${i}) = ${WASM.call(this.mathModule, "factorial", i)}`); } // Prime number check print("\nPrime Number Checks:"); let numbers = [2, 7, 10, 13, 15, 23, 30]; for (let num in numbers) { let isPrime = WASM.call(this.mathModule, "isPrime", num); print(`isPrime(${num}) = ${isPrime ? "Yes" : "No"}`); } // GCD calculation print("\nGCD Calculations:"); let pairs = [[12, 18], [35, 49], [48, 180], [17, 23]]; for (let pair in pairs) { let gcd = WASM.call(this.mathModule, "gcd", pair[0], pair[1]); print(`gcd(${pair[0]}, ${pair[1]}) = ${gcd}`); } }, function main() -> Void { print("WASM Integration Example"); // Initialize WASM module if (!this.initModule()) { print("Failed to initialize WASM module. Exiting."); return; } print("WASM module initialized and ready to use!"); // Test math functions this.testMathFunctions(); } } entry(WasmApp.main);
Run the application to see the WASM functions in action:
grey run app.grey
You should see the results of various mathematical operations performed by the WASM module:
WASM Integration Example Loading WASM module... Module loaded successfully! Exports: fibonacci, factorial, isPrime, gcd, add, subtract, multiply, divide WASM module initialized and ready to use! --- Testing Math Functions --- Addition: 25 + 10 = 35 Subtraction: 25 - 10 = 15 Multiplication: 25 * 10 = 250 Division: 25 / 10 = 2.5 --- Advanced Functions --- Fibonacci Sequence: fibonacci(0) = 0 fibonacci(1) = 1 fibonacci(2) = 1 fibonacci(3) = 2 fibonacci(4) = 3 fibonacci(5) = 5 fibonacci(6) = 8 fibonacci(7) = 13 fibonacci(8) = 21 fibonacci(9) = 34 Factorial Calculations: factorial(0) = 1 factorial(1) = 1 factorial(2) = 2 factorial(3) = 6 factorial(4) = 24 factorial(5) = 120 factorial(6) = 720 factorial(7) = 5040 factorial(8) = 40320 factorial(9) = 362880 Prime Number Checks: isPrime(2) = Yes isPrime(7) = Yes isPrime(10) = No isPrime(13) = Yes isPrime(15) = No isPrime(23) = Yes isPrime(30) = No GCD Calculations: gcd(12, 18) = 6 gcd(35, 49) = 7 gcd(48, 180) = 12 gcd(17, 23) = 1
Key concept: The WASM.call(module, functionName, ...args)
function is used to call WASM exported functions with typed parameters.
Step 4: Memory Management
WebAssembly modules use a linear memory model for data storage. Let's explore how to work with WASM memory from Grey. Add memory management examples to your app.grey
file:
// Add this new function to the WasmApp symbolic structure // Working with WASM memory function testMemoryAccess() -> Void { print("\n--- WASM Memory Operations ---"); // Get reference to the WASM memory let memory = WASM.getMemory(this.mathModule); print(`WASM Memory size: ${memory.size() / 1024} KB`); // Allocate a chunk of memory in the WASM module // Note: This assumes the WASM module exports a function called 'allocate' // If your module doesn't have this, you'll need to create one or use another approach let bufferSize = 100; let bufferPtr = WASM.call(this.mathModule, "allocate", bufferSize); print(`Allocated buffer at address: 0x${bufferPtr.toString(16)}`); // Write data to WASM memory print("Writing data to WASM memory..."); // Write a sequence of integers for (let i = 0; i < 10; i++) { // Write a 32-bit integer at position bufferPtr + (i * 4) memory.writeInt32(bufferPtr + (i * 4), i * 10); } // Read back the data we wrote print("Reading data from WASM memory:"); for (let i = 0; i < 10; i++) { let value = memory.readInt32(bufferPtr + (i * 4)); print(`Memory at offset ${i * 4}: ${value}`); } // Free the allocated memory when done // Note: This assumes the WASM module exports a function called 'deallocate' WASM.call(this.mathModule, "deallocate", bufferPtr, bufferSize); print("Memory deallocated"); }
In case your WASM module doesn't export memory allocation functions, you can create a new WASM module that includes them. Here's a simple C example that you could compile to WASM:
// memory.c - Compile with: emcc memory.c -o memory.wasm -s EXPORTED_FUNCTIONS=['_allocate','_deallocate'] #include// Allocate memory __attribute__((used)) void* allocate(int size) { return malloc(size); } // Free memory __attribute__((used)) void deallocate(void* ptr, int size) { free(ptr); }
Update your main function to call the memory test:
function main() -> Void { print("WASM Integration Example"); // Initialize WASM module if (!this.initModule()) { print("Failed to initialize WASM module. Exiting."); return; } print("WASM module initialized and ready to use!"); // Test math functions this.testMathFunctions(); // Test memory operations // Uncomment this if your WASM module supports memory allocation/deallocation // this.testMemoryAccess(); }
Important: Memory management in WebAssembly requires careful attention to prevent memory leaks. Always deallocate memory that you've allocated when you're done with it.
Step 5: Performance Comparison
One of the key advantages of Grey's WASM integration is the automatic performance optimization. Let's add a benchmark to compare standard WASM execution with Grey-optimized WASM execution:
// Add this new function to the WasmApp symbolic structure // Performance benchmarking function runBenchmark() -> Void { print("\n--- Performance Benchmark ---"); let iterations = 1000000; print(`Running benchmark with ${iterations} iterations...`); // Test standard WASM execution (fibonacci) print("\nStandard WASM execution:"); let startTime = performance.now(); for (let i = 0; i < iterations; i++) { // Call the WASM function directly WASM.call(this.mathModule, "fibonacci", 15); } let standardTime = performance.now() - startTime; print(`Time taken: ${standardTime.toFixed(2)} ms`); // Test Grey-optimized execution print("\nGrey-optimized execution:"); startTime = performance.now(); // Enable Grey's symbolic optimization WASM.enableSymbolicOptimization(this.mathModule, "fibonacci"); for (let i = 0; i < iterations; i++) { // Call the optimized function WASM.call(this.mathModule, "fibonacci", 15); } let optimizedTime = performance.now() - startTime; print(`Time taken: ${optimizedTime.toFixed(2)} ms`); // Calculate improvement let improvement = (1 - (optimizedTime / standardTime)) * 100; print(`\nPerformance improvement: ${improvement.toFixed(2)}%`); // Disable optimization for future calls WASM.disableSymbolicOptimization(this.mathModule, "fibonacci"); }
Update your main function to run the benchmark:
function main() -> Void { print("WASM Integration Example"); // Initialize WASM module if (!this.initModule()) { print("Failed to initialize WASM module. Exiting."); return; } print("WASM module initialized and ready to use!"); // Test math functions this.testMathFunctions(); // Run performance benchmark this.runBenchmark(); }
Run the application with benchmarking:
grey run app.grey
You should see a significant performance improvement with Grey's symbolic optimization enabled:
Benchmark Results
Running benchmark with 1000000 iterations...
Standard WASM execution:
Grey-optimized execution:
Performance improvement: 68.39%
How it works: Grey's symbolic optimization analyzes the WASM function's execution patterns and applies techniques like function specialization, constant propagation, and memoization to improve performance.
Next Steps
Congratulations! You've successfully integrated WebAssembly modules with GreyOS and learned how to optimize their performance. Here are some suggestions for further exploration:
- Create your own WASM modules from C, C++, or Rust and integrate them with Grey applications
- Explore more complex memory management patterns for sharing large datasets between Grey and WASM
- Learn how to create Grey bindings for WASM modules to provide type-safe interfaces
- Use the Universal Absorber to convert WASM modules directly to Grey code (see the Universal Absorption Tutorial)
- Benchmark different algorithms and operations to see which ones benefit most from Grey's symbolic optimization
Advanced tip: For production applications, consider using Grey's WASM compiler to pre-optimize your WebAssembly modules at build time rather than runtime.