structs & refs

Layout & Performance

Where struct globals live, the sizeof/alignof/offsetof builtins, and where copying actually costs.

Examples on this page import this shared module — it is live too:

vec2.as
export struct Vec2 {
    x: f32 = 0;
    y: f32 = 0;
    constructor(x: f32, y: f32) {
        this.x = x;
        this.y = y;
    }
}

Value globals

A module-level let or const of struct type lives in static memory, not in a wasm global — the compiler reserves the flat bytes at a fixed address and initializes them in the start function. Every place operation works on it: field writes, compound assignment, methods, and binding a ref into it. Statics never move, so the ref fence that guards growable arrays does not apply.

globals.as
import { Vec2 } from "./vec2";

export const gravity = Vec2(0, -10);
let counter = Vec2(0, 0);

function bump(v: ref Vec2): void { v.x += 1; }

export function demo(): f32 {
    counter.x = 5;
    counter.y += 2;                   // compound leaf write
    bump(counter);                    // ref into the static place
    return counter.x + counter.y + gravity.y;   // 6 + 2 - 10
}

Layout

sizeof<T>() and alignof<T>() report payload size and alignment for structs; offsetof<T>("field") works through the flat layout. Flattened locals have no address of their own — taking a ref to one spills it to a per-frame scratch zone, so aliasing works and costs only where used.

Performance notes