WASM Integration Tutorial

Integrate WebAssembly modules with GreyOS

Advanced Time: 45 minutes

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:

WASM Module
(C, C++, Rust, etc.)
Grey WASM Runtime
(Symbolic Optimization Layer)
Grey Application
(Optimized Execution)

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:

WASM 1250.45 ms

Grey-optimized execution:

Grey 395.21 ms

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.