Lesson 04-WebAssembly Application Basics

WebAssembly in Web Applications

Graphics Rendering (Combining Canvas, WebGL, and WASM)

Canvas 2D Rendering Optimization:

// Using WASM to accelerate Canvas 2D drawing operations
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Load WASM module
WebAssembly.instantiateStreaming(fetch('canvas_render.wasm'))
  .then(obj => {
    const render = obj.instance.exports;

    // Prepare pixel data
    const width = canvas.width;
    const height = canvas.height;
    const pixelData = new Uint8ClampedArray(width * height * 4);

    // Call WASM render function
    render.render_scene(
      pixelData.byteOffset,
      width,
      height
    );

    // Create ImageData and draw to Canvas
    const imageData = new ImageData(pixelData, width, height);
    ctx.putImageData(imageData, 0, 0);
  });

Combining WebGL with WASM:

// Using WASM to process WebGL vertex and texture data
const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');

// Load WASM module
WebAssembly.instantiateStreaming(fetch('webgl_render.wasm'))
  .then(obj => {
    const render = obj.instance.exports;
    const memory = obj.instance.exports.memory;

    // Create WebGL buffer
    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Get vertex data from WASM memory
    const vertexCount = render.get_vertex_count();
    const vertexSize = 3 * 4; // 3 float32 (x,y,z)
    const vertexData = new Float32Array(
      memory.buffer,
      render.get_vertex_data_offset(),
      vertexCount * 3
    );

    // Upload data to WebGL
    gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);

    // Similar processing for texture data...
  });

Audio and Video Processing (FFmpeg Compiled to WASM)

Basic FFmpeg WASM Usage:

// Load FFmpeg WASM module
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });

async function processVideo(inputFile) {
  await ffmpeg.load();

  // Write input file to WASM virtual file system
  ffmpeg.FS('writeFile', 'input.mp4', await fetchFile(inputFile));

  // Execute FFmpeg command
  await ffmpeg.run('-i', 'input.mp4', '-vf', 'scale=640:360', '-c:v', 'libx264', '-preset', 'fast', 'output.mp4');

  // Read output file from WASM file system
  const data = ffmpeg.FS('readFile', 'output.mp4');

  // Create download link
  const blob = new Blob([data.buffer], { type: 'video/mp4' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = 'processed.mp4';
  a.click();
}

Real-Time Audio/Video Processing:

// Using WASM for real-time audio processing
const audioContext = new AudioContext();
const processorNode = audioContext.createScriptProcessor(4096, 1, 1);

processorNode.onaudioprocess = (e) => {
  const inputData = e.inputBuffer.getChannelData(0);
  const outputData = e.outputBuffer.getChannelData(0);

  // Write input data to WASM memory
  const wasmMemory = wasmInstance.exports.memory;
  const inputDataPtr = wasmInstance.exports.alloc(inputData.length * 4);
  new Float32Array(wasmMemory.buffer, inputDataPtr, inputData.length).set(inputData);

  // Call WASM processing function
  wasmInstance.exports.process_audio(
    inputDataPtr,
    outputDataPtr,
    inputData.length
  );

  // Read processed data from WASM memory
  new Float32Array(wasmMemory.buffer, outputDataPtr, outputData.length).set(outputData);

  // Free memory
  wasmInstance.exports.free(inputDataPtr);
  wasmInstance.exports.free(outputDataPtr);
};

// Connect audio nodes
sourceNode.connect(processorNode);
processorNode.connect(audioContext.destination);

Game Development (Unity, Godot Engine Export to WASM)

Unity WebGL Export Configuration:

  1. In Unity Editor:
    • File > Build Settings
    • Select WebGL platform
    • In Player Settings:
      • Resolution and Presentation > Compression Format: Disable
      • Publishing Settings > Enable Exceptions: Full
  2. Build output directory structure: Build/ WebGl.data WebGl.loader.js WebGl.wasm WebGl.framework.js index.html

Godot Engine Export to WASM:

  1. Export Settings:
    • Project > Export
    • Select Web platform
    • In Options:
      • Export With: WebAssembly
      • Threads: Enable (if multithreading is needed)
  2. Exported files: export/ web/ godot.js godot.wasm godot.data index.html

Cryptographic Algorithms (RSA, AES Implementations in WASM)

RSA Encryption Example:

// RSA encryption implemented in WASM
WebAssembly.instantiateStreaming(fetch('crypto_wasm.wasm'))
  .then(obj => {
    const crypto = obj.instance.exports;

    // Generate key pair
    const keySize = 2048;
    crypto.rsa_generate_keypair(keySize);

    // Get public and private keys
    const publicKeyPtr = crypto.rsa_get_public_key();
    const privateKeyPtr = crypto.rsa_get_private_key();

    // Encrypt data
    const message = new TextEncoder().encode("Secret Message");
    const encryptedPtr = crypto.rsa_encrypt(
      publicKeyPtr,
      message.byteOffset,
      message.length
    );

    // Decrypt data
    const decryptedPtr = crypto.rsa_decrypt(
      privateKeyPtr,
      encryptedPtr,
      /* encrypted length */ 
    );

    // Read decrypted result
    const decryptedLength = crypto.rsa_get_decrypted_length(decryptedPtr);
    const decryptedData = new Uint8Array(
      crypto.memory.buffer,
      decryptedPtr,
      decryptedLength
    );
    console.log(new TextDecoder().decode(decryptedData));

    // Free memory
    crypto.rsa_free_keypair();
    crypto.rsa_free_buffer(publicKeyPtr);
    crypto.rsa_free_buffer(privateKeyPtr);
    crypto.rsa_free_buffer(encryptedPtr);
    crypto.rsa_free_buffer(decryptedPtr);
  });

AES Encryption Example:

// AES encryption implemented in WASM
WebAssembly.instantiateStreaming(fetch('aes_wasm.wasm'))
  .then(obj => {
    const aes = obj.instance.exports;

    // Set key
    const key = new Uint8Array(32); // AES-256
    crypto.getRandomValues(key);
    const keyPtr = aes.aes_set_key(key.byteOffset, key.length);

    // Encrypt data
    const plaintext = new Uint8Array([/*...*/]);
    const ciphertextPtr = aes.aes_encrypt(
      keyPtr,
      plaintext.byteOffset,
      plaintext.length
    );

    // Decrypt data
    const decryptedPtr = aes.aes_decrypt(
      keyPtr,
      ciphertextPtr,
      /* ciphertext length */
    );

    // Read decrypted result
    const decryptedLength = aes.aes_get_decrypted_length(decryptedPtr);
    const decryptedData = new Uint8Array(
      aes.memory.buffer,
      decryptedPtr,
      decryptedLength
    );
    console.log(decryptedData);

    // Free memory
    aes.aes_free_key(keyPtr);
    aes.aes_free_buffer(ciphertextPtr);
    aes.aes_free_buffer(decryptedPtr);
  });

Scientific Computing (Numerical Simulations, Matrix Operations)

Matrix Multiplication Example:

// High-performance matrix multiplication using WASM
WebAssembly.instantiateStreaming(fetch('matrix_wasm'))
  .then(obj => {
    const matrix = obj.instance.exports;
    const memory = obj.instance.exports.memory;

    // Matrix dimensions
    const rowsA = 1024;
    const colsA = 1024;
    const colsB = 1024;

    // Allocate matrix space in WASM memory
    const matrixAPtr = matrix.matrix_multiplyllocate(
      rowsA * colsA);

    const matrixBPtr = matrix.matrix_multiply(colsA * rowsB);
    const resultMatrixPtr = matrix.matrix_multiply(rowsA * colsB);

    // Fill matrix data (example)
    const matrixA = new Float32Array(memory.bufferData, matrixAPtr, rowsA * colsA);
    const matrixB = new Float32Array(memory.bufferData, matrixBPtr, colsA * colsB);

    // Initialize matrices (in real applications, data would come from other sources)
    for (let i = 0; i < rowsA; i++) {
      for (let j = 0; j < colsA; j++) {
        matrixA[i * colsA + j] = Math.random();
      }
    }

    for (let i = 0; i < colsA; i++) {
      for (let j = 0; j < colsB; j++) {
        matrixB[i * colsB + j] = Math.random();
      }
    }

    // Perform matrix multiplication
    matrix.matrix_multiply(
      matrixAPtr,
      matrixBPtr,
      resultMatrixPtr,
      rowsA,
      colsA,
      colsB
    );

    // Read result matrix
    const resultMatrix = new Float32Array(memory.bufferData, resultMatrixPtr, rowsA * colsB);

    // Process result...

    // Free memory
    matrix.matrix_free(matrixAPtr);
    matrix.matrix_free(matrixBPtr);
    matrix.matrix_free(resultMatrixPtr);
  });

Numerical Integration Example:

// Numerical integration calculation using WASM
WebAssembly.instantiateStreaming(fetch('numerical_wasm.wasm'))
  .then(obj => {
    const numerical = obj.instance.exports;

    // Define integrand function (via callback)
    function integrand(x) {
      return Math.sin(x) / x; // Example function
    }

    // Set integration parameters
    const a = 0.0001; // Lower bound (avoid x=0)
    const b = 10.0;   // Upper bound
    const tolerance = 1e-6; // Tolerance

    // Call WASM integration function
    const resultPtr = numerical.numerical_integrate(
      /* Callback function pointer - requires special handling */
      a,
      b,
      tolerance
    );

    // Read result
    const result = numerical.get_double_result(resultPtr);
    console.log(`Integration result: ${result}`);

    // Free memory
    numerical.free_result(resultPtr);
  });

WebAssembly in Non-Web Environments

Node.js WASM Applications

High-Performance Computing:

// Using WASM to accelerate compute-intensive tasks in Node.js
const fs = require('fs');
const wasmBuffer = fs.readFileSync('compute_wasm.wasm');

WebAssembly.instantiate(wasmBuffer).then(obj => {
  const compute = obj.instance.exports;

  // Prepare large dataset
  const dataSize = 1000000;
  const inputData = new Float64Array(dataSize);
  for (let i = 0; i < dataSize; i++) {
    inputData[i] = Math.random();
  }

  // Allocate space in WASM memory
  const inputPtr = compute.alloc(dataSize * 8); // 8 bytes per double
  const outputPtr = compute.alloc(dataSize * 8);

  // Copy data to WASM memory
  new Float64Array(compute.memory.buffer, inputPtr, dataSize).set(inputData);

  // Perform computation
  compute.process_data(inputPtr, outputPtr, dataSize);

  // Read result
  const outputData = new Float64Array(compute.memory.buffer, outputPtr, dataSize);
  console.log('Computation result:', outputData.slice(0, 10)); // Print first 10 results

  // Free memory
  compute.free(inputPtr);
  compute.free(outputPtr);
});

Plugin System:

// Implementing a Node.js plugin system using WASM
const { Worker } = require('worker_threads');
const path = require('path');

class WasmPlugin {
  constructor(pluginPath) {
    this.pluginPath = pluginPath;
    this.instance = null;
  }

  async load() {
    // Load WASM in Worker thread to avoid blocking main thread
    return new Promise((resolve, reject) => {
      const worker = new Worker(`
        const { parentPort } = require('worker_threads');
        const fs = require('fs');
        const wasmPath = '${this.pluginPath}';

        (async () => {
          try {
            const wasmBuffer = fs.readFileSync(wasmPath);
            const obj = await WebAssembly.instantiate(wasmBuffer);
            parentPort.postMessage({ success: true, instance: obj.instance });
          } catch (err) {
            parentPort.postMessage({ success: false, error: err.message });
          }
        })();
      `, { eval: true });

      worker.on('message', (msg) => {
        if (msg.success) {
          this.instance = msg.instance;
          resolve();
        } else {
          reject(new Error(msg.error));
        }
      });

      worker.on('error', reject);
      worker.on('exit', (code) => {
        if (code !== 0) {
          reject(new Error(`Worker stopped with exit code ${code}`));
        }
      });
    });
  }

  execute(methodName, ...args) {
    if (!this.instance) {
      throw new Error('Plugin not loaded');
    }

    // Find exported method
    if (!this.instance.exports[methodName]) {
      throw new Error(`Method ${methodName} not found in plugin`);
    }

    // Call method
    return this.instance.exports[methodName](...args);
  }
}

// Usage example
(async () => {
  const plugin = new WasmPlugin(path.join(__dirname, 'plugin.wasm'));
  await plugin.load();

  const result = plugin.execute('process_data', inputData);
  console.log('Plugin processing result:', result);
})();

Server-Side WASM

WASM in Cloudflare Workers:

// Using WASM to process requests in Cloudflare Workers
export default {
  async fetch(request) {
    // Load WASM module
    const wasmBuffer = await fetch('https://example.com/filter.wasm').then(res => res.arrayBuffer());
    const wasmModule = await WebAssembly.compile(wasmBuffer);
    const wasmInstance = await WebAssembly.instantiate(wasmModule);

    // Read request body
    const { headers, body } = request;
    const contentType = headers.get('content-type') || '';

    if (contentType.includes('application/json')) {
      const text = await body.text();
      const data = JSON.parse(text);

      // Process data in WASM memory
      const inputPtr = wasmInstance.exports.alloc(JSON.stringify(data).length);
      const memory = new Uint8Array(wasmInstance.exports.memory.buffer);
      for (let i = 0; i < text.length; i++) {
        memory[inputPtr + i] = text.charCodeAt(i);
      }

      // Call WASM processing function
      const outputPtr = wasmInstance.exports.process_data(inputPtr, text.length);
      const outputLength = wasmInstance.exports.get_output_length(outputPtr);
      const result = new TextDecoder().decode(
        memory.slice(outputPtr, outputPtr + outputLength)
      );

      // Free memory
      wasmInstance.exports.free(inputPtr);
      wasmInstance.exports.free(outputPtr);

      // Return processed result
      return new Response(result, {
        headers: { 'content-type': 'application/json' }
      });
    }

    return new Response('Unsupported content type', { status: 400 });
  }
};

WASM in Fastly Compute@Edge:

// Using Rust and WASM to process requests in Fastly Compute@Edge
// Note: Actual code requires Rust; this shows the concept

// Pseudo-code example:
#[fastly::main]
async fn main(req: Request) -> Result<Response> {
    // Load WASM module
    let wasm_module = WASM::from_file("filter.wasm")?;

    // Get request body
    let body = req.get_body()?.as_bytes()?;

    // Process data in WASM memory
    let input_ptr = wasm_module.alloc(body.len())?;
    wasm_module.write_memory(input_ptr, body)?;

    // Call WASM processing function
    let output_ptr = wasm_module.call("process_data", input_ptr, body.len())?;
    let output_len = wasm_module.get_output_length(output_ptr)?;
    let result = wasm_module.read_memory(output_ptr, output_len)?;

    // Free memory
    wasm_module.free(input_ptr)?;
    wasm_module.free(output_ptr)?;

    // Return processed result
    Ok(Response::from_body(result))
}

WASM in Embedded Devices

WASM in IoT Devices:

// Example of running WASM on embedded devices (conceptual code)
#include "wasm3.h"

void run_wasm_on_device() {
    // Initialize WASM runtime
    M3Environment *env = m3_NewEnvironment();
    M3Runtime *runtime = m3_NewRuntime(env, 1024, NULL);

    // Load WASM module (from flash or network)
    uint8_t wasm_buffer[] = { /* WASM binary data */ };
    M3Module *module = m3_ParseModule(env, wasm_buffer, sizeof(wasm_buffer));
    M3Result result = m3_LoadModule(runtime, module);

    if (result) {
        printf("Failed to load module: %s\n", result);
        return;
    }

    // Get exported function
    IM3Function func = m3_FindFunction(runtime, "sensor_processing");

    if (!func) {
        printf("Function not found\n");
        return;
    }

    // Prepare input data (read from sensors)
    float sensor_data[10];
    read_sensors(sensor_data, 10);

    // Call WASM function
    M3Value args[1] = { m3_MakeFloat(0) }; // Simplified example
    M3Value rets[1];

    result = m3_CallV(func, 1, args, 1, rets);

    if (result) {
        printf("Function call failed: %s\n", result);
        return;
    }

    // Process result
    float processed_data = m3_GetFloat(rets[0]);
    send_processed_data(processed_data);

    // Cleanup
    m3_FreeRuntime(runtime);
    m3_FreeEnvironment(env);
}

WASM on Microcontrollers:

// Running WASM on microcontrollers using Rust (conceptual code)
#![no_std]
#![no_main]

use panic_halt as _;
use wasm3_rt as wasm3;

#[rtic::app(device = stm32f4xx_hal::stm32, peripherals = true)]
mod app {
    use super::*;

    #[shared]
    struct Shared {}

    #[local]
    struct Local {
        wasm_runtime: wasm3::Runtime,
        wasm_module: wasm3::Module,
    }

    #[init]
    fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
        // Initialize WASM runtime
        let env = wasm3::Environment::new();
        let runtime = env.new_runtime(1024).unwrap();

        // Load WASM module (from Flash)
        let wasm_bytes = include_bytes!("app.wasm");
        let module = env.parse_module(wasm_bytes).unwrap();
        let module = runtime.load_module(module).unwrap();

        (Shared {}, Local { wasm_runtime: runtime, wasm_module: module }, init::Monotonics())
    }

    #[task(binds = TIM2, shared = [], local = [wasm_runtime, wasm_module])]
    fn timer_tick(cx: timer_tick::Context) {
        let locals = cx.local;

        // Prepare input data (read from sensors)
        let sensor_data = read_sensor_data();

        // Call WASM function
        let func = locals.wasm_module.find_function("process_sensor_data").unwrap();
        let args = [wasm3::Value::I32(sensor_data as i32)];
        let rets = [wasm3::Value::I32(0)];

        func.call(&locals.wasm_runtime, &args, &rets).unwrap();

        // Process result
        let result = rets[0].as_i32();
        control_actuator(result);
    }
}

WASM in Mobile Applications

WASM in React Native:

// Using WASM in React Native
import { useEffect, useState } from 'react';
import { View, Text } from 'react-native';

export default function App() {
  const [result, setResult] = useState(null);

  useEffect(() => {
    async function loadAndRunWasm() {
      try {
        // Load WASM module
        const wasm = await WebAssembly.instantiateStreaming(
          fetch('mobile_wasm.wasm'),
          { env: { /* Possible imports */ } }
        );

        // Call WASM function
        const processedData = wasm.instance.exports.process_data(
          /* Input parameters */
        );

        setResult(processedData);
      } catch (err) {
        console.error('WASM loading failed:', err);
      }
    }

    loadAndRunWasm();
  }, []);

  return (
    <View>
      <Text>WASM processing result: {result !== null ? result : 'Loading...'}</Text>
    </View>
  );
}

WASM in Flutter Plugins:

// Dart-side Flutter plugin code
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';

class WasmPlugin {
  static const MethodChannel _channel = MethodChannel('wasm_plugin');

  static Future<dynamic> runWasm(String wasmPath, Map<String, dynamic> input) async {
    // Load and run WASM in platform-specific code
    final result = await _channel.invokeMethod('run_wasm', {
      'wasm_path': wasmPath,
      'input': input,
    });

    return result;
  }
}

// Platform-specific implementation (Android example)
// AndroidManifest.xml requires network permissions
// Implement platform channel in Kotlin
/*
class WasmPlugin(private val messenger: BinaryMessenger) : MethodCallHandler {
    private val channel = MethodChannel(messenger, "wasm_plugin")

    init {
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(call: MethodCall, result: Result) {
        when (call.method) {
            "run_wasm" -> {
                val wasmPath = call.argument<String>("wasm_path")!!
                val input = call.argument<Map<String, Any>>("input")!!

                // Run WASM on Android
                val wasmResult = runWasmOnAndroid(wasmPath, input)

                result.success(wasmResult)
            }
            else -> result.notImplemented()
        }
    }

    private fun runWasmOnAndroid(wasmPath: String, input: Map<String, Any>): Any {
        // Use Android's WASM runtime (e.g., Wasmer Android)
        // Implementation details...
    }
}
*/

WASM in Desktop Applications

WASM in Electron:

// Using WASM in Electron main process
const { app, BrowserWindow } = require('electron');
const path = require('path');
const fs = require('fs');

let mainWindow;

app.whenReady().then(() => {
  mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    }
  });

  mainWindow.loadFile('index.html');

  // Optionally use WASM in main process
  loadWasmInMainProcess();
});

async function loadWasmInMainProcess() {
  try {
    const wasmBuffer = fs.readFileSync(path.join(__dirname, 'desktop_wasm.wasm'));
    const wasmModule = await WebAssembly.compile(wasmBuffer);
    const wasmInstance = await WebAssembly.instantiate(wasmModule);

    // Call WASM function
    const result = wasmInstance.exports.process_data(
      /* Input parameters */
    );

    console.log('WASM processing result:', result);
  } catch (err) {
    console.error('WASM loading failed:', err);
  }
}

WASM in Tauri:

// Using WASM in Tauri (Rust) backend
use tauri::command;
use wasmer::{imports, Instance, Module, Store, Value};

#[command]
fn process_data_with_wasm(input: Vec<f64>) -> Vec<f64> {
    // Create WASM runtime
    let store = Store::default();

    // Load WASM module (from file or embedded)
    let module = Module::from_file(&store, "data_processor.wasm")
        .expect("Failed to load WASM module");

    // Create import object (if needed)
    let import_object = imports! {};

    // Instantiate module
    let instance = Instance::new(&module, &import_object)
        .expect("Failed to instantiate WASM module");

    // Get exported function
    let process_func = instance.exports.get_function("process_data")
        .expect("Failed to find process_data function");

    // Prepare input memory
    let input_ptr = process_func.call(&[Value::I32(input.len() as i32)])
        .expect("Function call failed")
        .get(0)
        .unwrap()
        .unwrap_i32() as *mut f64;

    // Write input data
    unsafe {
        std::ptr::copy_nonoverlapping(input.as_ptr(), input_ptr, input.len());
    }

    // Call processing function
    let output_ptr = process_func.call(&[Value::I32(input_ptr as i32)])
        .expect("Function call failed")
        .get(0)
        .unwrap()
        .unwrap_i32() as *mut f64;

    // Read output data (assuming known output length)
    let output_len = input.len(); // Simplified example
    let mut output = vec![0.0; output_len];
    unsafe {
        std::ptr::copy_nonoverlapping(output_ptr, output.as_mut_ptr(), output_len);
    }

    output
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![process_data_with_wasm])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

WebAssembly Toolchain and Ecosystem

Emscripten Toolchain

Compiling C/C++ to WASM:

# Basic compilation command
emcc hello.c -o hello.html

# Advanced options example
emcc \
  -O3 \                          # Optimization level
  -s WASM=1 \                    # Enable WASM output
  -s SIDE_MODULE=0 \             # Generate full module (not just library)
  -s EXPORTED_FUNCTIONS='["_main", "_add"]' \  # Export functions
  -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap"]' \  # Export runtime methods
  -s ALLOW_MEMORY_GROWTH=1 \     # Allow memory growth
  -s ENVIRONMENT='web,worker' \  # Supported environments
  input.c -o output.js           # Output file

Emscripten API Usage:

#include <emscripten.h>

// Use EMSCRIPTEN_KEEPALIVE to prevent function optimization
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
    return a + b;
}

// File system operation example
EMSCRIPTEN_KEEPALIVE
void save_to_file(const char* filename, const char* data) {
    EM_ASM_({
        var filename = UTF8ToString($0);
        var data = UTF8ToString($1);
        FS.writeFile(filename, data);
    }, filename, data);
}

// Main function (if generating executable)
int main() {
    printf("Hello from C!\n");
    return 0;
}

Rust and WASM

wasm-pack Toolchain:

# Initialize WASM project
wasm-pack init my-wasm-project

# Build WASM package
wasm-pack build --target web

# Development mode build (with hot reload)
wasm-pack build --target web --dev

# Release build (optimized)
wasm-pack build --target web --release

wasm-bindgen Example:

// src/lib.rs
use wasm_bindgen::prelude::*;

// Export function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

// Import JavaScript function
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

// Use imported function
#[wasm_bindgen]
pub fn log_message(msg: &str) {
    log(msg);
}

// Handle complex data types
#[wasm_bindgen]
pub struct Person {
    name: String,
    age: u32,
}

#[wasm_bindgen]
impl Person {
    #[wasm_bindgen(constructor)]
    pub fn new(name: &str, age: u32) -> Person {
        Person {
            name: name.to_string(),
            age,
        }
    }

    #[wasm_bindgen(getter)]
    pub fn name(&self) -> String {
        self.name.clone()
    }

    #[wasm_bindgen(setter)]
    pub fn set_name(&mut self, name: &str) {
        self.name = name.to_string();
    }

    #[wasm_bindgen(getter)]
    pub fn age(&self) -> u32 {
        self.age
    }

    #[wasm_bindgen(setter)]
    pub fn set_age(&mut self, age: u32) {
        self.age = age;
    }
}

Go and WASM

TinyGo Toolchain:

# Install TinyGo
brew install tinygo  # macOS
# Or download binary for your platform from the official site

# Compile to WASM
tinygo build -o main.wasm -target wasm ./main.go

# Compile with smaller memory configuration
tinygo build -o main.wasm -target wasm -opt=2 -no-debug ./main.go

Go WASM Example:

// main.go
package main

import (
    "syscall/js"
)

// Export function to JavaScript
func add(this js.Value, args []js.Value) interface{} {
    a := args[0].Int()
    b := args[1].Int()
    return a + b
}

// Export string processing function
func greet(this js.Value, args []js.Value) interface{} {
    name := args[0].String()
    return "Hello, " + name + "!"
}

func main() {
    // Register exported functions
    js.Global().Set("add", js.FuncOf(add))
    js.Global().Set("greet", js.FuncOf(greet))

    // Keep program running
    select {}
}

Go WASM with DOM Interaction:

// dom_example.go
package main

import (
    "syscall/js"
)

func main() {
    // Wait for DOM to load
    document := js.Global().Get("document")
    body := document.Call("getElementsByTagName", "body").Index(0)

    // Create button
    button := document.Call("createElement", "button")
    button.Set("innerHTML", "Click me!")

    // Set click event handler
    clickHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        js.Global().Call("alert", "Button clicked from Go!")
        return nil
    })
    button.Call("addEventListener", "click", clickHandler)

    // Add button to page
    body.Call("appendChild", button)

    // Keep program running
    select {}
}

WebAssembly Component Model

Component Model Proposal Overview:

  • Goal: Enable WASM modules to be composed like components
  • Key Features:
    • Clearly defined interfaces
    • Type-safe imports/exports
    • Interoperability between components written in multiple languages

Component Model Example (Conceptual):

;; Define a component interface
(component
  (import "math" "add" (func $add (param i32 i32) (result i32)))
  (import "env" "log" (func $log (param string)))

  ;; Export component functionality
  (export "calculate" (func $calculate))

  ;; Component internal function
  (func $calculate (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    call $add
  )
)

;; Implement component
(instance
  (instantiate
    (component
      ;; Component definition...
    )
    ;; Provide import implementations
    (import "math" "add" (func $add (param i32 i32) (result i32))
      ;; Implement add function
      (func (param $a i32) (param $b i32) (result i32)
        local.get $a
        local.get $b
        i32.add
      )
    )
    (import "env" "log" (func $log (param string))
      ;; Implement log function
      (func (param $msg string)
        ;; In practice, may call host logging functionality
      )
    )
  )
)

Current Tool Support:

  • Partial component model support in Wasmtime and Wasmer
  • Rust’s wasm-component crate
  • Experimental toolchain support

WASM Ecosystem

WASI (WebAssembly System Interface):

# Run WASM module with WASI
wasmer run --enable-wasi my_wasi_app.wasm

# Example with file system access
wasmer run --dir=/tmp --env=MY_ENV_VAR=value my_wasi_app.wasm

WASI Example Code (C):

#include <wasi/api.h>
#include <stdio.h>

int main() {
    // Use WASI API to access file system
    __wasi_fd_t fd;
    __wasi_errno_t err = __wasi_fd_prestat_get(0, &fd); // Example call

    // More practical file operation example
    char buf[1024];
    size_t bytes_read;
    err = __wasi_fd_read(0, buf, sizeof(buf), &bytes_read);

    if (err == 0) {
        // Process read data
    }

    return 0;
}

Wasmtime Runtime:

# Install Wasmtime
curl https://wasmtime.dev/install.sh -sSf | bash

# Run WASM module
wasmtime hello.wasm

# Run with configuration
wasmtime --enable-multi-memory --max-wasm-stack=1MB hello.wasm

# Run as HTTP service on server
wasmtime serve --port 8080 ./wasm_modules/

Wasmer Runtime:

# Install Wasmer
curl https://get.wasmer.io -sSfL | sh

# Run WASM module
wasmer run hello.wasm

# Run with WASI
wasmer run --enable-wasi my_wasi_app.wasm

# Use Wasmer in Python
python3 -c "from wasmer import Instance; instance = Instance('hello.wasm'); print(instance.exports.hello())"
Share your love