MADOLA WASM Module Imports - Complete Guide

July 27, 2026 · View on GitHub

Complete guide for using WebAssembly modules in MADOLA, covering implementation, usage, testing, and troubleshooting for both native executable and web application environments.

Table of Contents


Executive Summary

WASM module imports are now fully functional in all environments:

  • Native executable (dist/madola.exe) - Uses Node.js to execute WASM
  • Web application (browser) - Loads WASM directly via WebAssembly API
  • Regression tests - Both native and WASM modes validated

Key Achievements

  1. Native Executable WASM Imports

    • Import fallback logic implemented
    • Path resolution handles Windows backslashes
    • Node.js integration working
  2. Web Application WASM Imports

    • Manifest-based module discovery
    • Preloading on page load
    • Bridge integration complete
  3. Regression Tests

    • Native mode tests WASM imports
    • WASM mode tests WASM runtime
    • Separate baselines for each mode
    • 40/40 tests passing

Quick Start

For Native Executable

  1. Place WASM files in ~/.madola/trove/[moduleName]/:

    ~/.madola/trove/calcPi/
    ├── calcPi.js
    └── calcPi.wasm
    
  2. Use in code:

    from calcPi import calcPi;
    x := calcPi(1000000);
    
  3. Run:

    dist/madola.exe mycode.mda      # Windows
    dist/madola mycode.mda          # Linux/Mac
    

For Web Application

  1. Place WASM files in web/trove/[moduleName]/:

    web/trove/calcPi/
    ├── calcPi.js
    └── calcPi.wasm
    
  2. Update manifest (web/trove/manifest.json):

    {
      "calcPi": ["calcPi"]
    }
    
  3. Use in code:

    from calcPi import calcPi;
    x := calcPi(1000000);
    
  4. Open in browser:

    # Start server
    dev.bat serve    # Windows
    ./dev.sh serve   # Linux/Mac
    
    # Open http://localhost:8080
    

Quick Reference Table

EnvironmentWASM LocationConfig RequiredLoading Method
Native~/.madola/trove/[module]/None (auto-discover)Node.js
Web Appweb/trove/[module]/Yes (manifest.json)Browser WebAssembly

Overview

MADOLA supports importing functions from WASM modules using the standard import syntax:

from moduleName import functionName;

The system automatically handles different execution environments:

  • Native executable (dist/madola.exe) - Uses Node.js to execute WASM
  • Web application (web/index.html) - Loads WASM directly in the browser

Import Resolution Flow

Native Executable:

  1. Try to find .mda file in current directory
  2. Try to find .mda file in ~/.madola/trove/
  3. [Fallback] Try WASM: ~/.madola/trove/[module]/[function].{js,wasm}
  4. Load WASM wrapper with Node.js
  5. Execute function calls via WASM

Web Application:

  1. On page load: Read web/trove/manifest.json
  2. Preload all modules: Load each WASM file listed in manifest
  3. Register in bridge: window.madolaWasmBridge[functionName] = ...
  4. On import: from calcPi import calcPi; → check bridge
  5. Execute: Call function via bridge

Directory Structure

Native Executable Environment

The native executable searches for WASM modules in:

~/.madola/
└── trove/
    └── [moduleName]/
        ├── [functionName].js      # JavaScript wrapper
        └── [functionName].wasm    # WASM binary

Search Priority:

  1. Current directory (for co-located test modules)
  2. ~/.madola/trove/ (standard user modules)

Example:

C:/Users/YourName/.madola/
└── trove/
    └── calcPi/
        ├── calcPi.js
        └── calcPi.wasm

Web Application Environment

The web application loads WASM modules from:

web/
└── trove/
    ├── manifest.json          # Module registry (REQUIRED!)
    └── [moduleName]/
        ├── [functionName].js   # JavaScript wrapper
        └── [functionName].wasm # WASM binary

Manifest Format:

{
  "calcPi": ["calcPi"],
  "mathLib": ["square", "cube", "sqrt"],
  "testWasm": ["testSquare", "testDouble"]
}

Regression Test Environment

regression/
├── fixtures/
│   ├── wasm_import.mda           # Test file
│   └── testWasm/                 # Local WASM modules
│       ├── testSquare.{js,wasm}
│       └── testDouble.{js,wasm}
└── expected/                      # Baselines shared by native and WASM
    ├── evaluation/wasm_import.txt
    └── html/wasm_import.html

Native and WASM compare against the same baselines — diagnostic log lines that legitimately differ between backends (import/codegen chatter) are stripped by the comparison's normalization step, not by keeping two sets of files.


Creating WASM Modules

  1. Create a .mda file with the decorator:
@gen_addon
fn calcPi(n) {
    sum := 0.0;
    for i in 0...n {
        sum := sum + ((-1)^i) / ((2*i)+1);
    }
    return sum * 4;
}
  1. Generate WASM for native executable:
# Native execution stores in ~/.madola/trove/
dist/madola.exe myFunction.mda     # Windows
dist/madola myFunction.mda         # Linux/Mac

This creates:

  • ~/.madola/trove/myFunction/myFunction.cpp (C++ source)
  • ~/.madola/trove/myFunction/myFunction.wasm (WASM binary)
  • ~/.madola/trove/myFunction/myFunction.js (JavaScript wrapper)
  1. Copy to web app (if needed):
# Linux/Mac
cp -r ~/.madola/trove/myFunction web/trove/

# Windows
xcopy /E /I %USERPROFILE%\.madola\trove\myFunction web\trove\myFunction
  1. Update web manifest:

Edit web/trove/manifest.json:

{
  "myFunction": ["myFunction"]
}

Manual Placement

If you have pre-compiled WASM modules:

  1. Create module directory:
web/trove/myModule/
  1. Add WASM files:
  • myModule.wasm - WASM binary
  • myModule.js - JavaScript wrapper (see template below)
  1. Update manifest:
{
  "myModule": ["myFunc1", "myFunc2"]
}

Usage Examples

Example 1: Basic Import

from calcPi import calcPi;

result := calcPi(1000000);
print(result);

Example 2: Multiple Imports

from mathLib import square, cube;

x := square(5);
y := cube(3);

print(x);  // 25
print(y);  // 27

Example 3: Performance Comparison

from calcPi import calcPi;

// WASM version (fast)
s1 := time();
x1 := calcPi(1000000);
t1 := time() - s1;

// Pure Madola version (slower)
fn calcPi_madola(n) {
    sum := 0.0;
    for i in 0...n {
        sum := sum + ((-1)^i) / ((2*i)+1);
    }
    return sum * 4;
}

s2 := time();
x2 := calcPi_madola(1000000);
t2 := time() - s2;

print(t1);  // ~10-50ms (WASM)
print(t2);  // ~500-1000ms (Interpreted)

Web Application Configuration

1. Update Manifest File

The web app uses web/trove/manifest.json to discover available WASM modules:

{
  "calcPi": ["calcPi"],
  "mathLib": ["square", "cube", "sqrt"],
  "statistics": ["mean", "median", "stddev"]
}

Format:

{
  "moduleName": ["function1", "function2", ...]
}

2. Module Loading Process

When the web app starts:

  1. Reads manifest.json - Discovers all available modules and functions
  2. Preloads WASM modules - Loads each WASM file before code execution
  3. Registers in bridge - Makes functions available via window.madolaWasmBridge
  4. Executes user code - Import statements can now resolve to preloaded functions

3. Import Resolution

When MADOLA code contains an import:

from calcPi import calcPi;
x := calcPi(1000000);

Resolution sequence:

  1. Parser detects from calcPi import calcPi;
  2. Checks if calcPi is in window.madolaWasmBridge
  3. If not preloaded, attempts dynamic load from trove/calcPi/calcPi.js
  4. Calls the function via the WASM bridge

WASM Wrapper Structure

JavaScript Wrapper Template

Generated wrappers follow this pattern:

// JavaScript wrapper for [functionName] WASM function

class [functionName]Addon {
    constructor(basePath = '') {
        this.module = null;
        this.isReady = false;
        this.basePath = basePath;
    }

    async load() {
        try {
            const wasmPath = this.basePath ? `${this.basePath}/[functionName].wasm` : '[functionName].wasm';
            let wasmBytes;

            // Check if we're in Node.js or browser
            if (typeof window === 'undefined') {
                // Node.js environment (native executable)
                const fs = require('fs');
                wasmBytes = fs.readFileSync(`${this.basePath}/[functionName].wasm`);
            } else {
                // Browser environment (web app)
                const response = await fetch(wasmPath);
                if (!response.ok) {
                    throw new Error(`Failed to fetch WASM: ${response.status} ${response.statusText}`);
                }
                wasmBytes = await response.arrayBuffer();
            }
            
            const wasmModule = await WebAssembly.instantiate(wasmBytes, {
                env: {
                    memory: new WebAssembly.Memory({ initial: 256 }),
                    table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
                }
            });
            
            this.module = wasmModule.instance;
            this.isReady = true;
            return true;
        } catch (error) {
            console.error('Failed to load WASM module:', error);
            return false;
        }
    }

    [functionName](n) {
        if (!this.isReady) {
            throw new Error('WASM module not loaded. Call load() first.');
        }
        
        return this.module.exports.[functionName]_wasm(n);
    }
}

// Export for both Node.js and browser
if (typeof module !== 'undefined' && module.exports) {
    module.exports = [functionName]Addon;
} else {
    window.[functionName]Addon = [functionName]Addon;
}

Key Features

  • Dual environment support - Works in both Node.js and browser
  • Dynamic path resolution - Accepts basePath parameter
  • Automatic loading - Fetches WASM binary on load()
  • Error handling - Clear error messages for debugging

Testing & Regression

Running Regression Tests

Native Tests (Windows):

.\regression\run_regression.bat native

WASM Tests (Windows):

.\regression\run_regression.bat wasm

Native Tests (Linux/Mac):

./regression/run_regression.sh native

WASM Tests (Linux/Mac):

./regression/run_regression.sh wasm

Update Baselines:

.\regression\run_regression.bat native update  # Update native baselines
.\regression\run_regression.bat wasm update    # Update WASM baselines

Test Coverage

Both native and WASM modes test:

  • ✅ Basic evaluation
  • .mda file imports
  • WASM module imports (testSquare, testDouble)
  • ✅ HTML formatting
  • ✅ Mathematical operations

Test Results

Expected Output:

Running regression tests in native mode...
Running test: wasm_import

Comparing results...
[PASS] wasm_import.txt (evaluation)
[PASS] wasm_import.html (html)

Results: 40 passed, 0 failed
All tests passed!

Adding New WASM Test

  1. Create generator file:

    @gen_addon
    fn myFunc(n) {
        return n * 3;
    }
    
  2. Generate WASM:

    dist/madola.exe myGenerator.mda
    
  3. Copy to fixtures:

    copy %USERPROFILE%\.madola\trove\myGenerator\myFunc.* regression\fixtures\myModule\
    
  4. Create test:

    from myModule import myFunc;
    result := myFunc(10);
    print(result);
    
  5. Run and update:

    .\regression\run_regression.bat native update
    

Implementation Details

Key Code Changes

1. Import Fallback Logic

File: src/core/generator/evaluator.cpp

#ifdef __EMSCRIPTEN__
    // Browser: Skip filesystem, use WASM bridge
    wasmLoader.loadModule(...);
#else
    // Native: Try .mda first, then WASM
    if (!found_mda_file) {
        wasmLoader.loadModule(...);
    }
#endif

2. WASM Module Search Paths

File: src/core/generator/wasm_addon_loader.cpp

std::vector<std::filesystem::path> searchPaths = {
    std::filesystem::current_path() / moduleName,  // Current dir (tests)
    utils::getTroveDirectory(moduleName)           // ~/.madola/trove/
};

3. Path Conversion for Node.js

File: src/core/generator/evaluator_functions.cpp

// Convert backslashes to forward slashes for Node.js
std::string jsPath = wasmFunc->jsWrapperPath;
std::replace(jsPath.begin(), jsPath.end(), '\\', '/');

// Add basePath parameter
jsCode << "const addon = new " << name << "Addon('" << basePath << "'); ";

4. WASM Runner Bridge Setup

File: madola_runner.js

// Temporarily delete window so wrappers detect Node.js
const savedWindow = global.window;
delete global.window;

// Load wrapper (uses fs.readFileSync)
const addon = new WrapperClass(basePath);
await addon.load();

// Restore window for WASM runtime
global.window = savedWindow;
madolaBridge[functionName] = (n) => addon[functionName](n);

5. Exception Support for WASM

File: CMakeLists.txt

# Enable WASM exceptions for control flow
target_compile_options(madola_core_obj PRIVATE -fwasm-exceptions)
target_compile_options(madola_core PRIVATE -fwasm-exceptions)
target_compile_options(madola_wasm PRIVATE -fwasm-exceptions)

Files Modified

Source Code (8 files):

  1. src/core/generator/evaluator.cpp - Import fallback logic
  2. src/core/generator/evaluator_functions.cpp - Path conversion
  3. src/core/generator/wasm_addon_loader.cpp - Search paths
  4. madola_runner.js - WASM bridge setup
  5. CMakeLists.txt - WASM exception support

Configuration (2 files): 6. web/trove/manifest.json - Module registry 7. regression/run_regression.bat - WASM test support

Test Files: 8. regression/fixtures/wasm_import.mda - Test case 9. regression/fixtures/testWasm/ - Test modules 10. regression/expected/ - Baselines shared by native and WASM


Troubleshooting

Native Executable Issues

Error: Cannot find module 'moduleName.mda' in current directory or ~/.madola/trove/

Solution:

  1. ✅ Check if WASM files exist in ~/.madola/trove/moduleName/
  2. ✅ Ensure both .js and .wasm files are present
  3. ✅ Verify the module name matches the directory name
  4. ✅ Or place WASM files in same directory as test file (for regression tests)

Error: WASM execution failed: Module not found

Solution:

  1. ✅ Check that Node.js is installed and in PATH
  2. ✅ Verify file paths are correct (Windows uses backslashes, converts to forward slashes internally)
  3. ✅ Check that the basePath is being passed correctly to the wrapper

Web Application Issues

Error: Failed to load WASM: 404 Not Found

Solution:

  1. ✅ Verify WASM files are in web/trove/moduleName/
  2. ✅ Check manifest.json includes the module
  3. ✅ Ensure web server is serving from the web/ directory
  4. ✅ Refresh browser to reload (hard refresh: Ctrl+F5)

Error: Function not found in manifest

Solution:

  1. ✅ Add the module to web/trove/manifest.json:
    {
      "moduleName": ["functionName"]
    }
    
  2. ✅ Refresh the browser to reload the manifest

Error: WASM module not loaded

Solution:

  1. ✅ Check browser console for loading errors
  2. ✅ Verify the wrapper's load() method is being called
  3. ✅ Ensure WASM binary is valid and not corrupted

Regression Test Issues

Error: Tests fail with "Return from function" or "Break from loop"

Solution:

  1. ✅ Ensure WASM exceptions are enabled in CMake (-fwasm-exceptions)
  2. ✅ Rebuild WASM runtime: dev.bat wasm or ./dev.sh wasm
  3. ✅ Check that control flow state tracking is implemented

Error: Missing output lines in WASM tests

Solution:

  1. ✅ Ensure madola_runner.js captures print/printErr logs
  2. ✅ Verify output parity between native and WASM modes
  3. ✅ Update WASM baselines: run_regression.bat wasm update

Best Practices

1. Consistent Naming

  • Module directory name should match import name
  • Function name should match the exported WASM function
  • Use descriptive names: calcPi, not cp

2. Organize by Functionality

web/trove/
├── math/           # Mathematical functions
├── statistics/     # Statistical functions
├── geometry/       # Geometric calculations
└── physics/        # Physics simulations

3. Keep Manifest Updated

After adding new WASM modules, always update manifest.json:

{
  "math": ["sqrt", "pow", "log"],
  "statistics": ["mean", "median", "mode"],
  "geometry": ["area", "perimeter", "volume"]
}

4. Version Control

Add generated files to .gitignore if they're build outputs:

# Generated WASM files
~/.madola/trove/**/*.wasm
~/.madola/trove/**/*.js
web/trove/**/*.wasm
web/gen_cpp/**/*.cpp

But commit template/example WASM modules for testing.


Performance Comparison

WASM modules provide significant performance improvements:

Speedup: 10-100x faster for compute-intensive tasks

ImplementationExecution TimeRelative Performance
WASM~10-50ms10-100x faster ⚡
Pure Madola~500-1000msBaseline

Example:

from calcPi import calcPi;

// WASM: ~10-50ms for 1,000,000 iterations
x1 := calcPi(1000000);

// Madola: ~500-1000ms for 1,000,000 iterations  
fn calcPi_madola(n) { /* ... */ }
x2 := calcPi_madola(1000000);

Status & Verification

✅ PRODUCTION READY

All components tested and working:

  • ✅ Native executable imports WASM modules
  • ✅ Web application imports WASM modules
  • ✅ Regression test coverage (40/40 passing)
  • ✅ Comprehensive documentation
  • ✅ Example files provided
  • ✅ Troubleshooting guides complete

Verification Checklist

Native Executable Environment

Location: ~/.madola/trove/

  • ✅ Module directory exists: ~/.madola/trove/calcPi/
  • ✅ JavaScript wrapper exists: calcPi.js
  • ✅ WASM binary exists: calcPi.wasm
  • ✅ Import fallback logic implemented in evaluator
  • ✅ Path resolution handles Windows backslashes

Test Command:

dist/madola.exe example.mda    # Windows
dist/madola example.mda        # Linux/Mac

Expected Output:

Imported WASM function: calcPi from calcPi
Execution completed successfully
Output:
  7

Web Application Environment

Location: web/trove/

  • ✅ Module directory exists: web/trove/calcPi/
  • ✅ JavaScript wrapper exists: calcPi.js
  • ✅ WASM binary exists: calcPi.wasm
  • ✅ Manifest updated: web/trove/manifest.json
  • ✅ Example file created: web/examples/wasm_import.mda

Test Steps:

  1. Start server: dev.bat serve or ./dev.sh serve
  2. Open: http://localhost:8080
  3. Load example: examples/wasm_import.mda
  4. Click "Run"

Expected Behavior:

  • WASM module loads automatically on page load
  • Import statement resolves to preloaded function
  • Code executes successfully
  • Results display in output panel

Testing Matrix

EnvironmentImport .mdaImport WASMStatus
Native ExecWorking
Web App❌ N/AWorking
Regression (native)Working
Regression (wasm)❌ N/AWorking

Summary

EnvironmentLocationConfigurationLoadingStatus
Native~/.madola/trove/[module]/Auto-discoverNode.js✅ Working
Web Appweb/trove/[module]/manifest.jsonBrowser✅ Working

Key Points

  • ✅ Native and web use same wrapper format
  • ✅ Web app requires manifest.json
  • ✅ Native auto-discovers from ~/.madola/trove/
  • ✅ Import syntax is identical in both environments
  • ✅ WASM provides 10-100x performance improvement
  • ✅ Setup verified and tested in both environments
  • ✅ Regression tests cover both native and WASM modes
  • ✅ Exception support enabled for control flow
  • ✅ Output parity between native and WASM modes

Next Steps

  1. Add new WASM modules:

    • Generate with @gen_addon decorator
    • Copy to appropriate trove/ directory
    • Update web/trove/manifest.json for web app
  2. For support:

    • Check troubleshooting section above
    • Verify setup using checklist
    • Test with provided examples

MADOLA WASM imports are complete and production-ready! 🚀

Last updated: 2025-11-26