Crafter.Build/wasi-runtime/runtime.js

1001 lines
44 KiB
JavaScript
Raw Normal View History

2026-07-23 01:24:42 +02:00
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
// Minimal browser WASI shim. Loaded by index.html, which sets
// window.CRAFTER_WASM_URL before this script runs so a single runtime.js
// can serve any output name. Most syscalls return 0 — enough to host a
// hello-world that uses fd_write (stdout) and the args/environ probes
// libc invokes during startup. Extend as needed.
const textEncoder = new TextEncoder();
2026-05-19 17:20:20 +02:00
// ─── Two VFS roots ─────────────────────────────────────────────────────
// fd=3 ⟶ read-only baked layer at "/". Populated from files.json at
// startup, keyed by basename — the bin-dir layout `cfg.assets`
// produces is flat enough that basename collapse doesn't
// collide for legitimate assets. Writes are rejected.
// fd=4 ⟶ read/write persistent layer at "/persistent". Backed by OPFS
// (Origin Private File System), keyed by relative path so
// nested directories work. The wasm sees an in-memory map
// (writes are synchronous from its POV); a background task
// mirrors dirty entries back to OPFS so they survive reload.
//
// wasi-libc walks preopens starting at fd=3 and resolves every open()
// against the longest matching preopen. "/persistent/foo.json" hits fd=4
// with relpath "foo.json"; "/Inter.ttf" hits fd=3 with relpath "Inter.ttf".
const readonlyVfs = new Map(); // basename → Uint8Array (immutable)
const persistentVfs = new Map(); // relpath → Uint8Array (mutable)
// Paths the wasm has mkdir'd. Our backing store is flat-keyed so dirs
// don't otherwise exist on their own — but libstdc++'s
// `create_directories` mkdir's, then re-stats each component, and
// expects the stat to report "directory". Tracking explicit mkdir'd
// paths here keeps that loop convergent.
const persistentDirs = new Set();
const ROOTS = {
3: { kind: "ro", map: readonlyVfs, name: "/" },
4: { kind: "rw", map: persistentVfs, name: "/persistent" },
};
2026-05-26 22:50:08 +02:00
function resolveKey(_root, raw) {
// Both VFS layers key by relative path now. We did keep the read-
// only side basename-collapsed up to Phase 1, but that broke as
// soon as two bundled dirs shipped a file with the same basename
// (e.g. maps/A/map.json vs maps/B/map.json — only one survives the
// preload). Full-path keying matches what files.json actually
// serves and what cfg.assets actually deploys.
2026-05-19 17:20:20 +02:00
let key = raw.replace(/\\/g, "/");
while (key.startsWith("./")) key = key.slice(2);
while (key.startsWith("/")) key = key.slice(1);
return key;
}
2026-05-26 22:50:08 +02:00
// Directory tree for the read-only baked layer. Built after files.json
// preload completes — each path like "maps/A/map.json" registers as:
// "" → { "maps": "dir" }
// "maps" → { "A": "dir" }
// "maps/A" → { "map.json":"file" }
// Used by path_open (recognize directory paths) and fd_readdir
// (iterate entries).
const readonlyDirs = new Map();
function roBuildDirTree() {
readonlyDirs.clear();
const addChild = (parent, name, kind) => {
let entries = readonlyDirs.get(parent);
if (!entries) {
entries = new Map();
readonlyDirs.set(parent, entries);
}
if (!entries.has(name)) entries.set(name, kind);
};
for (const path of readonlyVfs.keys()) {
const parts = path.split("/").filter(Boolean);
for (let i = 0; i < parts.length; ++i) {
const parent = parts.slice(0, i).join("/");
const name = parts[i];
const kind = (i === parts.length - 1) ? "file" : "dir";
addChild(parent, name, kind);
}
}
}
2026-05-19 17:20:20 +02:00
// ─── OPFS mirror for the persistent layer ──────────────────────────────
let opfsRoot = null;
const dirtyPaths = new Set();
let flushTimer = null;
const FLUSH_DEBOUNCE_MS = 750;
async function opfsResolveDir(parts, create) {
if (!opfsRoot) return null;
let dir = opfsRoot;
for (const part of parts) {
if (!part) continue;
dir = await dir.getDirectoryHandle(part, { create });
}
return dir;
}
async function opfsHydrate() {
if (!navigator.storage || !navigator.storage.getDirectory) {
console.warn("[wasi] OPFS unavailable — persistent writes will not survive reload");
return;
}
try {
opfsRoot = await navigator.storage.getDirectory();
} catch (e) {
console.warn("[wasi] OPFS getDirectory failed:", e?.message);
return;
}
async function walk(dir, prefix) {
for await (const [name, handle] of dir.entries()) {
const full = prefix ? `${prefix}/${name}` : name;
if (handle.kind === "file") {
try {
const file = await handle.getFile();
persistentVfs.set(full, new Uint8Array(await file.arrayBuffer()));
} catch (e) {
console.warn(`[wasi] OPFS read failed for ${full}:`, e?.message);
}
} else if (handle.kind === "directory") {
await walk(handle, full);
}
}
}
await walk(opfsRoot, "");
}
async function opfsFlushOne(path) {
if (!opfsRoot) return;
const parts = path.split("/").filter(Boolean);
if (parts.length === 0) return;
const data = persistentVfs.get(path);
if (data === undefined) {
// Removed entry — delete from OPFS, walking parents.
try {
const parent = await opfsResolveDir(parts.slice(0, -1), false);
await parent.removeEntry(parts[parts.length - 1]);
} catch (e) {
// Missing entry is fine; other errors get reported.
if (e && e.name !== "NotFoundError") {
console.warn(`[wasi] OPFS remove failed for ${path}:`, e?.message);
}
}
return;
}
try {
const dir = await opfsResolveDir(parts.slice(0, -1), true);
const fh = await dir.getFileHandle(parts[parts.length - 1], { create: true });
const w = await fh.createWritable();
await w.write(data);
await w.close();
} catch (e) {
console.warn(`[wasi] OPFS write failed for ${path}:`, e?.message);
}
}
async function opfsFlushAll() {
if (!opfsRoot) return;
const batch = Array.from(dirtyPaths);
dirtyPaths.clear();
for (const path of batch) {
await opfsFlushOne(path);
}
}
function scheduleFlush() {
if (flushTimer !== null) return;
flushTimer = setTimeout(() => {
flushTimer = null;
opfsFlushAll();
}, FLUSH_DEBOUNCE_MS);
}
function markDirty(path) {
dirtyPaths.add(path);
scheduleFlush();
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
class Wasi {
#encodedStdin;
#envEncodedStrings;
#argEncodedStrings;
instance;
2026-05-18 05:23:11 +02:00
#fdTable;
#nextFd;
#decoder;
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
2026-05-19 17:20:20 +02:00
constructor({ env, stdin, args }) {
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
this.#encodedStdin = textEncoder.encode(stdin);
const envStrings = Object.entries(env).map(([k, v]) => `${k}=${v}`);
this.#envEncodedStrings = envStrings.map(s => textEncoder.encode(s + "\0"));
this.#argEncodedStrings = args.map(s => textEncoder.encode(s + "\0"));
2026-05-18 05:23:11 +02:00
this.#fdTable = new Map();
2026-05-19 17:20:20 +02:00
this.#nextFd = 100; // stay clear of stdio + preopens
2026-05-18 05:23:11 +02:00
this.#decoder = new TextDecoder();
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
this.bind();
}
bind() {
2026-05-18 05:23:11 +02:00
// wasi imports are looked up as plain function references at
// instantiate time, so any method that touches `this` MUST be
// explicitly bound here. Anything purely no-op (returning 0) can
// stay unbound.
const m = [
"args_get", "args_sizes_get",
"environ_get", "environ_sizes_get",
2026-05-26 22:50:08 +02:00
"fd_read", "fd_write", "fd_pwrite", "fd_readdir",
2026-05-19 17:20:20 +02:00
"fd_close", "fd_seek", "fd_tell",
2026-05-18 05:23:11 +02:00
"fd_filestat_get", "fd_fdstat_get",
"fd_prestat_get", "fd_prestat_dir_name",
2026-05-19 17:20:20 +02:00
"path_open", "path_create_directory", "path_remove_directory",
"path_unlink_file", "path_rename", "path_filestat_get",
2026-05-26 22:50:08 +02:00
// clock_res_get + clock_time_get touch this.instance.exports
// .memory; the old stubs were no-ops and didn't need binding.
"clock_res_get", "clock_time_get",
// random_get now writes into this.instance.exports.memory too.
"random_get",
2026-05-18 05:23:11 +02:00
];
for (const name of m) this[name] = this[name].bind(this);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
}
args_sizes_get(argCountPtr, argBufferSizePtr) {
const argByteLength = this.#argEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0);
const countPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, argCountPtr, 1);
const sizePointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, argBufferSizePtr, 1);
countPointerBuffer[0] = this.#argEncodedStrings.length;
sizePointerBuffer[0] = argByteLength;
return 0;
}
args_get(argsPtr, argBufferPtr) {
const argsByteLength = this.#argEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0);
const argsPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, argsPtr, this.#argEncodedStrings.length);
const argsBuffer = new Uint8Array(this.instance.exports.memory.buffer, argBufferPtr, argsByteLength);
let pointerOffset = 0;
for (let i = 0; i < this.#argEncodedStrings.length; i++) {
argsPointerBuffer[i] = argBufferPtr + pointerOffset;
argsBuffer.set(this.#argEncodedStrings[i], pointerOffset);
pointerOffset += this.#argEncodedStrings[i].byteLength;
}
return 0;
}
fd_write(fd, iovsPtr, iovsLength, bytesWrittenPtr) {
2026-05-19 17:20:20 +02:00
const dv = new DataView(this.instance.exports.memory.buffer);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
const iovs = new Uint32Array(this.instance.exports.memory.buffer, iovsPtr, iovsLength * 2);
if (fd === 1 || fd === 2) {
let text = "";
let totalBytesWritten = 0;
const decoder = new TextDecoder();
for (let i = 0; i < iovsLength * 2; i += 2) {
const offset = iovs[i];
const length = iovs[i + 1];
text += decoder.decode(new Int8Array(this.instance.exports.memory.buffer, offset, length));
totalBytesWritten += length;
}
2026-05-19 17:20:20 +02:00
dv.setInt32(bytesWrittenPtr, totalBytesWritten, true);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
(fd === 2 ? console.error : console.log)(text);
2026-05-19 17:20:20 +02:00
return 0;
}
const entry = this.#fdTable.get(fd);
if (!entry) {
dv.setInt32(bytesWrittenPtr, 0, true);
return 8; // EBADF
}
const root = ROOTS[entry.rootFd];
if (!root || root.kind !== "rw") {
dv.setInt32(bytesWrittenPtr, 0, true);
return 76; // EROFS — close enough; libc surfaces this as "no permission"
}
// Sum iov lengths so we can grow the entry once.
let total = 0;
for (let i = 0; i < iovsLength * 2; i += 2) total += iovs[i + 1];
let data = root.map.get(entry.name) ?? new Uint8Array(0);
const end = entry.offset + total;
if (data.byteLength < end) {
const grown = new Uint8Array(end);
grown.set(data, 0);
data = grown;
root.map.set(entry.name, data);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
}
2026-05-19 17:20:20 +02:00
const memory = new Uint8Array(this.instance.exports.memory.buffer);
let written = 0;
for (let i = 0; i < iovsLength * 2; i += 2) {
const offset = iovs[i];
const length = iovs[i + 1];
data.set(memory.subarray(offset, offset + length), entry.offset + written);
written += length;
}
entry.offset += written;
dv.setInt32(bytesWrittenPtr, written, true);
markDirty(entry.name);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
return 0;
}
2026-05-19 17:20:20 +02:00
fd_pwrite(fd, iovsPtr, iovsLength, offsetArg, bytesWrittenPtr) {
// pwrite is positioned write — same as fd_write but the seek pos
// isn't advanced. We just temporarily override entry.offset.
const entry = this.#fdTable.get(fd);
if (!entry) {
new DataView(this.instance.exports.memory.buffer)
.setInt32(bytesWrittenPtr, 0, true);
return 8;
}
const saved = entry.offset;
entry.offset = typeof offsetArg === "bigint" ? Number(offsetArg) : Number(offsetArg);
const rc = this.fd_write(fd, iovsPtr, iovsLength, bytesWrittenPtr);
entry.offset = saved;
return rc;
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
fd_read(fd, iovsPtr, iovsLength, bytesReadPtr) {
const memory = new Uint8Array(this.instance.exports.memory.buffer);
const iovs = new Uint32Array(this.instance.exports.memory.buffer, iovsPtr, iovsLength * 2);
2026-05-18 05:23:11 +02:00
const dataView = new DataView(this.instance.exports.memory.buffer);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
let totalBytesRead = 0;
if (fd === 0) {
for (let i = 0; i < iovsLength * 2; i += 2) {
const offset = iovs[i];
const length = iovs[i + 1];
const chunk = this.#encodedStdin.slice(0, length);
this.#encodedStdin = this.#encodedStdin.slice(length);
memory.set(chunk, offset);
totalBytesRead += chunk.byteLength;
if (this.#encodedStdin.length === 0) break;
}
dataView.setInt32(bytesReadPtr, totalBytesRead, true);
2026-05-18 05:23:11 +02:00
return 0;
}
const entry = this.#fdTable.get(fd);
if (!entry) {
dataView.setInt32(bytesReadPtr, 0, true);
return 8; // EBADF
}
2026-05-19 17:20:20 +02:00
const root = ROOTS[entry.rootFd];
const file = root ? root.map.get(entry.name) : undefined;
if (!file) {
dataView.setInt32(bytesReadPtr, 0, true);
return 0; // EOF
}
2026-05-18 05:23:11 +02:00
for (let i = 0; i < iovsLength * 2; i += 2) {
const offset = iovs[i];
const length = iovs[i + 1];
const remaining = file.byteLength - entry.offset;
if (remaining <= 0) break;
const n = Math.min(length, remaining);
memory.set(file.subarray(entry.offset, entry.offset + n), offset);
entry.offset += n;
totalBytesRead += n;
if (n < length) break;
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
}
2026-05-18 05:23:11 +02:00
dataView.setInt32(bytesReadPtr, totalBytesRead, true);
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
return 0;
}
fd_advise() { return 0; }
2026-05-18 05:23:11 +02:00
fd_close(fd) {
this.#fdTable.delete(fd);
return 0;
}
fd_fdstat_get(fd, statPtr) {
// 24 bytes: filetype(1) + flags(2) + padding + rights_base(8) + rights_inheriting(8).
const dv = new DataView(this.instance.exports.memory.buffer);
2026-05-19 17:20:20 +02:00
let filetype;
2026-05-26 22:50:08 +02:00
if (ROOTS[fd]) {
filetype = 3; // directory — preopen mount
} else {
const entry = this.#fdTable.get(fd);
if (entry) filetype = entry.isDir ? 3 : 4;
else filetype = 0;
}
2026-05-18 05:23:11 +02:00
dv.setUint8(statPtr + 0, filetype);
dv.setUint16(statPtr + 2, 0, true);
dv.setBigUint64(statPtr + 8, 0xFFFFFFFFFFFFFFFFn, true);
dv.setBigUint64(statPtr + 16, 0xFFFFFFFFFFFFFFFFn, true);
return 0;
}
// wasi-libc walks preopens starting at fd=3 until fd_prestat_get returns
// EBADF, then resolves every relative open against one of the discovered
2026-05-19 17:20:20 +02:00
// dirs. Two preopens: fd=3 ("/") for baked assets, fd=4 ("/persistent")
// for OPFS-backed mutable state.
2026-05-18 05:23:11 +02:00
fd_prestat_get(fd, prestatPtr) {
const dv = new DataView(this.instance.exports.memory.buffer);
2026-05-19 17:20:20 +02:00
const root = ROOTS[fd];
if (!root) return 8; // EBADF — terminates the walk
// prestat_t: tag(u8) + padding to 4 + u.dir.pr_name_len(u32). 8 bytes.
dv.setUint8(prestatPtr + 0, 0); // tag = preopentype_dir
dv.setUint32(prestatPtr + 4, root.name.length, true);
return 0;
2026-05-18 05:23:11 +02:00
}
fd_prestat_dir_name(fd, pathPtr, pathLen) {
2026-05-19 17:20:20 +02:00
const root = ROOTS[fd];
if (!root) return 8;
2026-05-18 05:23:11 +02:00
const memory = new Uint8Array(this.instance.exports.memory.buffer);
2026-05-19 17:20:20 +02:00
const name = textEncoder.encode(root.name);
2026-05-18 05:23:11 +02:00
memory.set(name.subarray(0, Math.min(name.byteLength, pathLen)), pathPtr);
return 0;
}
2026-05-26 22:50:08 +02:00
// WASI clock_res_get(clock_id, resPtr): writes a u64 nanosecond
// resolution to *resPtr. We back every clock with performance.now()
// which the platform documents as ≥5µs resolution on modern browsers
// — round-trip via Math.round() loses sub-µs precision but anything
// more aggressive would mis-represent the actual host resolution.
clock_res_get(_clockId, resPtr) {
const dv = new DataView(this.instance.exports.memory.buffer);
dv.setBigUint64(resPtr, 1000n, /*littleEndian*/ true); // 1µs in ns
return 0;
}
// WASI clock_time_get(clock_id, precision, timePtr): writes a u64
// nanosecond timestamp. The stub used to return 0 unconditionally,
// which made every std::chrono::high_resolution_clock::now() in wasm
// return the same value — Physics::Update read delta=0 each frame
// and never advanced. Use performance.now() for MONOTONIC and a
// Date.now()-relative reference for REALTIME so std::chrono actually
// works.
clock_time_get(clockId, _precision, timePtr) {
const dv = new DataView(this.instance.exports.memory.buffer);
// clock IDs per wasi-snapshot-preview1:
// 0 = realtime, 1 = monotonic, 2 = process_cputime_id, 3 = thread_cputime_id
let nanos;
if (clockId === 0) {
// Date.now() returns ms since the Unix epoch.
nanos = BigInt(Date.now()) * 1000000n;
} else {
// performance.now() returns ms since the page navigation
// start with sub-ms precision. Anchor against
// performance.timeOrigin so caller sees a clean monotonic
// walltime equivalent.
const ms = performance.now();
nanos = BigInt(Math.round(ms * 1e6));
}
dv.setBigUint64(timePtr, nanos, /*littleEndian*/ true);
return 0;
}
2026-05-18 05:23:11 +02:00
fd_seek(fd, offsetLow, whence, newOffsetPtr) {
// offsetLow is a BigInt under wasi-snapshot-preview1's two-i32 ABI?
// No — wasi-snapshot-preview1 fd_seek's signature is (fd, filedelta:i64,
// whence:u8, newoffset:ptr<filesize>). JS receives the i64 as BigInt.
const entry = this.#fdTable.get(fd);
const dv = new DataView(this.instance.exports.memory.buffer);
if (!entry) {
dv.setBigUint64(newOffsetPtr, 0n, true);
return 8;
}
const delta = typeof offsetLow === "bigint" ? Number(offsetLow) : Number(offsetLow);
2026-05-19 17:20:20 +02:00
const root = ROOTS[entry.rootFd];
const file = root ? root.map.get(entry.name) : undefined;
const size = file ? file.byteLength : 0;
2026-05-18 05:23:11 +02:00
let newOff;
switch (whence) {
case 0: newOff = delta; break; // SEEK_SET
case 1: newOff = entry.offset + delta; break; // SEEK_CUR
2026-05-19 17:20:20 +02:00
case 2: newOff = size + delta; break; // SEEK_END
2026-05-18 05:23:11 +02:00
default: return 28; // EINVAL
}
if (newOff < 0) newOff = 0;
2026-05-19 17:20:20 +02:00
// Writeable fds may seek past EOF (the next write extends the
// backing buffer); read-only fds clamp to EOF.
if (!root || root.kind !== "rw") {
if (newOff > size) newOff = size;
}
2026-05-18 05:23:11 +02:00
entry.offset = newOff;
dv.setBigUint64(newOffsetPtr, BigInt(newOff), true);
return 0;
}
fd_tell(fd, offsetPtr) {
const entry = this.#fdTable.get(fd);
const dv = new DataView(this.instance.exports.memory.buffer);
if (!entry) { dv.setBigUint64(offsetPtr, 0n, true); return 8; }
dv.setBigUint64(offsetPtr, BigInt(entry.offset), true);
return 0;
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
fd_allocate() { return 0; }
fd_datasync() { return 0; }
fd_fdstat_set_flags() { return 0; }
fd_fdstat_set_rights() { return 0; }
2026-05-18 05:23:11 +02:00
fd_filestat_get(fd, statPtr) {
// Layout: dev(8) ino(8) filetype(1) +pad nlink(8) size(8) atim(8) mtim(8) ctim(8) = 64 bytes.
const dv = new DataView(this.instance.exports.memory.buffer);
2026-05-19 17:20:20 +02:00
let filetype = 4; // regular_file (default)
let size = 0;
if (ROOTS[fd]) {
filetype = 3; // directory — preopen mount itself
} else {
const entry = this.#fdTable.get(fd);
if (!entry) return 8;
2026-05-26 22:50:08 +02:00
if (entry.isDir) {
filetype = 3;
} else {
const root = ROOTS[entry.rootFd];
const file = root ? root.map.get(entry.name) : undefined;
size = file ? file.byteLength : 0;
}
2026-05-19 17:20:20 +02:00
}
2026-05-18 05:23:11 +02:00
dv.setBigUint64(statPtr + 0, 0n, true);
dv.setBigUint64(statPtr + 8, 0n, true);
2026-05-19 17:20:20 +02:00
dv.setUint8(statPtr + 16, filetype);
2026-05-18 05:23:11 +02:00
dv.setBigUint64(statPtr + 24, 1n, true);
2026-05-19 17:20:20 +02:00
dv.setBigUint64(statPtr + 32, BigInt(size), true);
2026-05-18 05:23:11 +02:00
dv.setBigUint64(statPtr + 40, 0n, true);
dv.setBigUint64(statPtr + 48, 0n, true);
dv.setBigUint64(statPtr + 56, 0n, true);
return 0;
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
fd_filestat_set_size() { return 0; }
fd_filestat_set_times() { return 0; }
fd_pread() { return 0; }
2026-05-26 22:50:08 +02:00
fd_readdir(fd, bufPtr, bufLen, cookieArg, bytesUsedPtr) {
// wasi dirent: dircookie(8) + inode(8) + namlen(4) + filetype(1)
// = 24 bytes header (the 3-byte tail is implicit padding before
// the variable-length name). The cookie returned in each entry
// is the value the caller passes in to read the *next* entry.
// We return at most bufLen bytes; libc reads, processes, calls
// back with the last entry's cookie, repeats until short read.
const dv = new DataView(this.instance.exports.memory.buffer);
const mem = new Uint8Array(this.instance.exports.memory.buffer);
const entry = this.#fdTable.get(fd);
if (!entry || !entry.isDir) {
dv.setInt32(bytesUsedPtr, 0, true);
return 8; // EBADF
}
const root = ROOTS[entry.rootFd];
if (!root) {
dv.setInt32(bytesUsedPtr, 0, true);
return 8;
}
// Gather the immediate children of `entry.name`.
let children;
if (root.kind === "ro") {
const dirMap = readonlyDirs.get(entry.name);
children = dirMap ? Array.from(dirMap.entries()) : [];
} else {
// Persistent: derive children from the keys themselves —
// strip the prefix, take the first remaining component,
// dedupe (a subdir contributes one entry per child file).
const prefix = entry.name === "" ? "" : entry.name + "/";
const seen = new Map();
for (const k of root.map.keys()) {
if (!k.startsWith(prefix)) continue;
const rest = k.slice(prefix.length);
if (rest === "") continue;
const slash = rest.indexOf("/");
if (slash < 0) {
if (!seen.has(rest)) seen.set(rest, "file");
} else {
const name = rest.slice(0, slash);
if (!seen.has(name)) seen.set(name, "dir");
}
}
// Surface mkdir'd dirs that have no files yet too.
const dirPrefix = entry.name === "" ? "" : entry.name + "/";
for (const d of persistentDirs) {
if (!d.startsWith(dirPrefix)) continue;
const rest = d.slice(dirPrefix.length);
const slash = rest.indexOf("/");
const name = slash < 0 ? rest : rest.slice(0, slash);
if (name && !seen.has(name)) seen.set(name, "dir");
}
children = Array.from(seen.entries());
}
const startCookie = typeof cookieArg === "bigint" ? Number(cookieArg) : Number(cookieArg);
let cursor = bufPtr;
let used = 0;
const textEnc = new TextEncoder();
for (let i = startCookie; i < children.length; ++i) {
const [name, kind] = children[i];
const nameBytes = textEnc.encode(name);
const recSize = 24 + nameBytes.byteLength;
if (used + 24 > bufLen) break;
const nextCookie = BigInt(i + 1);
dv.setBigUint64(cursor + 0, nextCookie, true); // d_next
dv.setBigUint64(cursor + 8, BigInt(i + 1), true); // d_ino (synthetic)
dv.setUint32(cursor + 16, nameBytes.byteLength, true); // d_namlen
dv.setUint8(cursor + 20, kind === "dir" ? 3 : 4); // d_type
mem.fill(0, cursor + 21, cursor + 24); // padding
const writeName = Math.min(nameBytes.byteLength, bufLen - used - 24);
mem.set(nameBytes.subarray(0, writeName), cursor + 24);
used += 24 + writeName;
cursor += 24 + writeName;
if (writeName < nameBytes.byteLength) break; // truncated name; libc will retry
}
dv.setInt32(bytesUsedPtr, used, true);
return 0;
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
fd_renumber() { return 0; }
fd_sync() { return 0; }
2026-05-19 17:20:20 +02:00
path_filestat_get(dirfd, _dirflags, pathPtr, pathLen, statPtr) {
const root = ROOTS[dirfd];
if (!root) return 8;
2026-05-18 05:23:11 +02:00
const memory = new Uint8Array(this.instance.exports.memory.buffer);
2026-05-19 17:20:20 +02:00
const raw = this.#decoder.decode(memory.subarray(pathPtr, pathPtr + pathLen));
const key = resolveKey(root, raw);
const file = root.map.get(key);
2026-05-18 05:23:11 +02:00
const dv = new DataView(this.instance.exports.memory.buffer);
2026-05-19 17:20:20 +02:00
// Resolve filetype: 4 = regular_file, 3 = directory. The
2026-05-26 22:50:08 +02:00
// preopen mount itself (empty key) is always a directory. On
// the read-only side we built an explicit directory tree at
// startup (readonlyDirs) — anything that key-matches a node is
// a directory. On the rw side, dirs are remembered explicitly
// via persistentDirs (path_create_directory) OR implicitly by
// being a prefix of any persistent file key (so libstdc++'s
// create_directories stat→mkdir→stat loop converges).
2026-05-19 17:20:20 +02:00
let filetype;
let size = 0n;
if (file !== undefined) {
filetype = 4;
size = BigInt(file.byteLength);
2026-05-26 22:50:08 +02:00
} else if (key === "") {
2026-05-19 17:20:20 +02:00
filetype = 3;
2026-05-26 22:50:08 +02:00
} else if (root.kind === "ro" && readonlyDirs.has(key)) {
filetype = 3;
} else if (root.kind === "rw" && persistentDirs.has(key)) {
filetype = 3;
} else if (root.kind === "rw") {
2026-05-19 17:20:20 +02:00
const prefix = key + "/";
let isDir = false;
for (const k of root.map.keys()) {
if (k.startsWith(prefix)) { isDir = true; break; }
}
2026-05-26 22:50:08 +02:00
if (!isDir) return 44;
2026-05-19 17:20:20 +02:00
filetype = 3;
2026-05-26 22:50:08 +02:00
} else {
return 44; // ENOENT
2026-05-19 17:20:20 +02:00
}
dv.setBigUint64(statPtr + 0, 0n, true);
dv.setBigUint64(statPtr + 8, 0n, true);
dv.setUint8(statPtr + 16, filetype);
dv.setBigUint64(statPtr + 24, 1n, true);
dv.setBigUint64(statPtr + 32, size, true);
dv.setBigUint64(statPtr + 40, 0n, true);
dv.setBigUint64(statPtr + 48, 0n, true);
dv.setBigUint64(statPtr + 56, 0n, true);
return 0;
}
path_create_directory(dirfd, pathPtr, pathLen) {
const root = ROOTS[dirfd];
if (!root) return 8;
if (root.kind !== "rw") return 76;
// Flat-keyed Map: directories don't need to be "created" for
// files to exist under them. Track the path in persistentDirs
// so a subsequent path_filestat_get reports it as a directory
// — that's what libstdc++'s create_directories looks for to
// confirm the mkdir landed.
const memory = new Uint8Array(this.instance.exports.memory.buffer);
const raw = this.#decoder.decode(memory.subarray(pathPtr, pathPtr + pathLen));
const key = resolveKey(root, raw);
if (key) persistentDirs.add(key);
return 0;
}
path_remove_directory(dirfd, pathPtr, pathLen) {
const root = ROOTS[dirfd];
if (!root || root.kind !== "rw") return 8;
const memory = new Uint8Array(this.instance.exports.memory.buffer);
2026-05-18 05:23:11 +02:00
const raw = this.#decoder.decode(memory.subarray(pathPtr, pathPtr + pathLen));
2026-05-19 17:20:20 +02:00
const key = resolveKey(root, raw);
// Delete every entry under this prefix and mark them for OPFS removal.
const prefix = key.endsWith("/") ? key : key + "/";
for (const k of [...root.map.keys()]) {
if (k === key || k.startsWith(prefix)) {
root.map.delete(k);
markDirty(k);
}
}
return 0;
}
path_unlink_file(dirfd, pathPtr, pathLen) {
const root = ROOTS[dirfd];
if (!root || root.kind !== "rw") return 8;
const memory = new Uint8Array(this.instance.exports.memory.buffer);
const raw = this.#decoder.decode(memory.subarray(pathPtr, pathPtr + pathLen));
const key = resolveKey(root, raw);
if (!root.map.has(key)) return 44; // ENOENT
root.map.delete(key);
markDirty(key);
return 0;
}
path_rename(oldDirfd, oldPathPtr, oldPathLen,
newDirfd, newPathPtr, newPathLen) {
const oldRoot = ROOTS[oldDirfd];
const newRoot = ROOTS[newDirfd];
if (!oldRoot || !newRoot) return 8;
if (oldRoot.kind !== "rw" || newRoot.kind !== "rw") return 76;
const memory = new Uint8Array(this.instance.exports.memory.buffer);
const oldRaw = this.#decoder.decode(memory.subarray(oldPathPtr, oldPathPtr + oldPathLen));
const newRaw = this.#decoder.decode(memory.subarray(newPathPtr, newPathPtr + newPathLen));
const oldKey = resolveKey(oldRoot, oldRaw);
const newKey = resolveKey(newRoot, newRaw);
const data = oldRoot.map.get(oldKey);
if (data === undefined) return 44;
oldRoot.map.delete(oldKey);
newRoot.map.set(newKey, data);
markDirty(oldKey);
markDirty(newKey);
return 0;
}
path_open(dirfd, _dirflags, pathPtr, pathLen, oflags, _rightsBase, _rightsInh, _fdflags, openedFdPtr) {
const memory = new Uint8Array(this.instance.exports.memory.buffer);
const dv = new DataView(this.instance.exports.memory.buffer);
const root = ROOTS[dirfd];
if (!root) {
2026-05-18 05:23:11 +02:00
dv.setInt32(openedFdPtr, -1, true);
2026-05-19 17:20:20 +02:00
return 8; // EBADF
2026-05-18 05:23:11 +02:00
}
2026-05-19 17:20:20 +02:00
const raw = this.#decoder.decode(memory.subarray(pathPtr, pathPtr + pathLen));
const key = resolveKey(root, raw);
2026-05-26 22:50:08 +02:00
const O_CREAT = 0x1;
const O_DIRECTORY = 0x2;
const O_TRUNC = 0x8;
2026-05-19 17:20:20 +02:00
const create = (oflags & O_CREAT) !== 0;
2026-05-26 22:50:08 +02:00
const wantDir = (oflags & O_DIRECTORY) !== 0;
2026-05-19 17:20:20 +02:00
const trunc = (oflags & O_TRUNC) !== 0;
2026-05-26 22:50:08 +02:00
// Directory open path. wasi-libc passes O_DIRECTORY for
// `fs::directory_iterator`; we also treat "empty key + ro" as
// the preopen root.
const isRoDir = root.kind === "ro" && (key === "" || readonlyDirs.has(key));
const isRwDir = root.kind === "rw" && (key === "" || persistentDirs.has(key) ||
(() => {
const prefix = key + "/";
for (const k of root.map.keys()) if (k.startsWith(prefix)) return true;
return false;
})());
if (wantDir || (isRoDir && !root.map.has(key)) || (isRwDir && !root.map.has(key))) {
if (!isRoDir && !isRwDir) {
dv.setInt32(openedFdPtr, -1, true);
return 54; // ENOTDIR
}
const fd = this.#nextFd++;
this.#fdTable.set(fd, { rootFd: dirfd, name: key, offset: 0, isDir: true });
dv.setInt32(openedFdPtr, fd, true);
return 0;
}
2026-05-19 17:20:20 +02:00
let present = root.map.has(key);
if (!present) {
if (!create || root.kind !== "rw") {
dv.setInt32(openedFdPtr, -1, true);
return 44; // ENOENT
}
root.map.set(key, new Uint8Array(0));
markDirty(key);
present = true;
} else if (trunc && root.kind === "rw") {
root.map.set(key, new Uint8Array(0));
markDirty(key);
}
2026-05-18 05:23:11 +02:00
const fd = this.#nextFd++;
2026-05-26 22:50:08 +02:00
this.#fdTable.set(fd, { rootFd: dirfd, name: key, offset: 0, isDir: false });
2026-05-18 05:23:11 +02:00
dv.setInt32(openedFdPtr, fd, true);
return 0;
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
path_readlink() { return 0; }
path_symlink() { return 0; }
2026-05-19 17:20:20 +02:00
path_link() { return 0; }
path_filestat_set_times() { return 0; }
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
poll_oneoff() { return 0; }
sched_yield() { return 0; }
// WASI random_get(buf, buf_len): fill buf_len bytes at *buf with
// high-quality randomness. The old stub returned success without
// writing anything, so the destination kept its prior contents (zero
// in practice) — every std::random_device user got all-zero
// "randomness". That collided WebRTC peer ids across tabs in 3DForts
// (Catcrafts/3DForts#50). Back it with crypto.getRandomValues, which
// is a CSPRNG. crypto.getRandomValues rejects views longer than 65536
// bytes, so chunk for buffers above that bound.
random_get(ptr, len) {
const MAX = 65536; // QuotaExceededError above this per the WebCrypto spec
for (let off = 0; off < len; off += MAX) {
const chunk = Math.min(MAX, len - off);
// Re-view per chunk: the buffer can detach/grow between calls,
// and a fresh view also keeps each slice within the 65536 cap.
const view = new Uint8Array(this.instance.exports.memory.buffer, ptr + off, chunk);
crypto.getRandomValues(view);
}
return 0;
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
sock_accept() { return 0; }
sock_recv() { return 0; }
sock_send() { return 0; }
sock_shutdown() { return 0; }
environ_get(environPtr, environBufferPtr) {
const envByteLength = this.#envEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0);
const environsPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, environPtr, this.#envEncodedStrings.length);
const environsBuffer = new Uint8Array(this.instance.exports.memory.buffer, environBufferPtr, envByteLength);
let pointerOffset = 0;
for (let i = 0; i < this.#envEncodedStrings.length; i++) {
environsPointerBuffer[i] = environBufferPtr + pointerOffset;
environsBuffer.set(this.#envEncodedStrings[i], pointerOffset);
pointerOffset += this.#envEncodedStrings[i].byteLength;
}
return 0;
}
environ_sizes_get(environCountPtr, environBufferSizePtr) {
const envByteLength = this.#envEncodedStrings.reduce((sum, val) => sum + val.byteLength, 0);
const countPointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, environCountPtr, 1);
const sizePointerBuffer = new Uint32Array(this.instance.exports.memory.buffer, environBufferSizePtr, 1);
countPointerBuffer[0] = this.#envEncodedStrings.length;
sizePointerBuffer[0] = envByteLength;
return 0;
}
proc_exit(code) {
2026-05-18 05:23:11 +02:00
// Throw a sentinel so the wasm stack unwinds back to runtime.js
// WITHOUT executing the `unreachable` instruction wasi-libc emits
// after __wasi_proc_exit (which is declared noreturn). Trapping
// there would mark the wasm call as crashed and may interrupt any
// browser-side render loop that called _Exit on purpose (e.g.
// Crafter::Window::StartSync on DOM, which hands the loop to rAF
// and then exits main without running static destructors).
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
console.log(`[wasi] proc_exit(${code})`);
2026-05-19 17:20:20 +02:00
// Trigger a final flush so anything written this session has the
// best chance of reaching OPFS before the page is torn down.
opfsFlushAll();
2026-05-18 05:23:11 +02:00
const e = new Error(`wasi proc_exit(${code})`);
e.crafterWasiExit = code;
throw e;
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
}
}
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and baked a single wasm URL into index.html, so newer codegen features that aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD revisions later) couldn't be adopted without dropping the browsers that lack them. Add a general, feature-parameterized mechanism owned entirely by Crafter.Build: - Configuration::wasmVariants declares N codegen variants (label, extra -m flags, runtime probes). Build() compiles the baseline plus one outputName.<label>.wasm per variant, recompiling the whole graph (incl. dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU codegen, not a link switch. wasmVariantFlags folds into VariantId so each variant's objects/PCMs land in their own build+bin dir. - EnableWasiBrowserRuntime emits a variants.json manifest (label -> url + probes), preferred-first with the baseline as the universal fallback. - The shipped runtime.js runs inlined wasm-feature-detect probes (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads), picks the first variant whose probes all pass, and falls back to the single baked CRAFTER_WASM_URL when no manifest is present (backward compatible). - EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari still flag-gates it as of mid-2026). Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the relaxed-simd variant and runs it. Resolves #24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:14:18 +00:00
// ─── wasm feature-detect probes ────────────────────────────────────────
// Each probe validates a ~dozen-byte module that only validates on an engine
// implementing the named feature — the same technique as
// GoogleChromeLabs/wasm-feature-detect, inlined so we ship no dependency. A
// variant in variants.json lists the probe names that must all pass for that
// variant to be served; an unknown name is treated as unsupported (we fall
// through to a less demanding variant, ultimately the baseline). Keep the keys
feat(wasm): compile C++ with WebAssembly exception handling wasm builds were compiled with -fno-exceptions. A throw did not compile, libc++'s own throws (vector::at, std::format, filesystem) aborted the program, and code that relies on exceptions could not be built for the browser at all. Every wasm unit and the std module now get -fwasm-exceptions, and the link adds -lunwind, which code built that way calls into and the driver does not add. The flag also selects the libc++ under the sysroot's lib/<triple>/eh, which wasi-sdk 33 and later ship next to the noeh one. Those libraries use the standardized encoding (try_table, exnref) while LLVM 23 still emits the legacy one by default, which wasmtime refuses to load and which would put both in one binary, so the flags ask for the standard form with -wasm-use-legacy-eh=false. That makes the setjmp lowering loadable under wasmtime too. The flags live in one constant, WasmBaselineFlags, which the compile command and the std module both take through WasmCompileFlags: a BMI is refused by a unit whose exception setting differs from its own. VariantId and StdPcmDir hash it, so the first build after the update starts fresh instead of importing PCMs built without exceptions, and two releases sharing the Flatpak's cache don't take turns rebuilding one std PCM. A sysroot without the exception-enabled libraries stops the build with a line saying so, rather than at the link with "unable to find library -lunwind". The result needs an engine with exnref: Chrome 137, Firefox 131, Safari 18.4, the bundled wasmtime. The runtime's exception-handling probe tested the legacy encoding; it now tests try_table, every variants.json entry carries it, and a browser without it is told which feature it lacks instead of failing with a CompileError. There is no switch back to -fno-exceptions: engines without exnref are old enough not to keep a second build for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:54:39 +01:00
// in sync with the `probes` a WasmVariant declares in Crafter.Build, and with
// WasmBaselineProbes, which every entry carries.
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and baked a single wasm URL into index.html, so newer codegen features that aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD revisions later) couldn't be adopted without dropping the browsers that lack them. Add a general, feature-parameterized mechanism owned entirely by Crafter.Build: - Configuration::wasmVariants declares N codegen variants (label, extra -m flags, runtime probes). Build() compiles the baseline plus one outputName.<label>.wasm per variant, recompiling the whole graph (incl. dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU codegen, not a link switch. wasmVariantFlags folds into VariantId so each variant's objects/PCMs land in their own build+bin dir. - EnableWasiBrowserRuntime emits a variants.json manifest (label -> url + probes), preferred-first with the baseline as the universal fallback. - The shipped runtime.js runs inlined wasm-feature-detect probes (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads), picks the first variant whose probes all pass, and falls back to the single baked CRAFTER_WASM_URL when no manifest is present (backward compatible). - EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari still flag-gates it as of mid-2026). Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the relaxed-simd variant and runs it. Resolves #24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:14:18 +00:00
const WASM_PROBES = {
// relaxed SIMD: i8x16.relaxed_swizzle (Chrome 114+, Firefox 120+; Safari
// still flag-gated as of mid-2026).
"relaxed-simd": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,15,1,13,0,65,1,253,15,65,2,253,15,253,128,2,11])),
// fixed-width SIMD128 (v128.const + i8x16.popcnt) — the current baseline,
// here for completeness / future baseline shifts.
"simd": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11])),
// tail calls (return_call).
"tail-call": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,6,1,4,0,18,0,11])),
// bulk memory ops (memory.init).
"bulk-memory": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,3,1,0,1,10,14,1,12,0,65,0,65,0,65,0,252,10,0,0,11])),
feat(wasm): compile C++ with WebAssembly exception handling wasm builds were compiled with -fno-exceptions. A throw did not compile, libc++'s own throws (vector::at, std::format, filesystem) aborted the program, and code that relies on exceptions could not be built for the browser at all. Every wasm unit and the std module now get -fwasm-exceptions, and the link adds -lunwind, which code built that way calls into and the driver does not add. The flag also selects the libc++ under the sysroot's lib/<triple>/eh, which wasi-sdk 33 and later ship next to the noeh one. Those libraries use the standardized encoding (try_table, exnref) while LLVM 23 still emits the legacy one by default, which wasmtime refuses to load and which would put both in one binary, so the flags ask for the standard form with -wasm-use-legacy-eh=false. That makes the setjmp lowering loadable under wasmtime too. The flags live in one constant, WasmBaselineFlags, which the compile command and the std module both take through WasmCompileFlags: a BMI is refused by a unit whose exception setting differs from its own. VariantId and StdPcmDir hash it, so the first build after the update starts fresh instead of importing PCMs built without exceptions, and two releases sharing the Flatpak's cache don't take turns rebuilding one std PCM. A sysroot without the exception-enabled libraries stops the build with a line saying so, rather than at the link with "unable to find library -lunwind". The result needs an engine with exnref: Chrome 137, Firefox 131, Safari 18.4, the bundled wasmtime. The runtime's exception-handling probe tested the legacy encoding; it now tests try_table, every variants.json entry carries it, and a browser without it is told which feature it lacks instead of failing with a CompileError. There is no switch back to -fno-exceptions: engines without exnref are old enough not to keep a second build for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:54:39 +01:00
// exception handling in its standardized form (try_table, exnref; Chrome
// 137, Firefox 131, Safari 18.4), which every crafter-build wasm uses for
// C++ exceptions. Probing the legacy try/catch encoding instead says
// nothing about this one: V8 accepts it with exnref switched off.
"exception-handling": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,8,1,6,0,31,64,0,11,11])),
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and baked a single wasm URL into index.html, so newer codegen features that aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD revisions later) couldn't be adopted without dropping the browsers that lack them. Add a general, feature-parameterized mechanism owned entirely by Crafter.Build: - Configuration::wasmVariants declares N codegen variants (label, extra -m flags, runtime probes). Build() compiles the baseline plus one outputName.<label>.wasm per variant, recompiling the whole graph (incl. dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU codegen, not a link switch. wasmVariantFlags folds into VariantId so each variant's objects/PCMs land in their own build+bin dir. - EnableWasiBrowserRuntime emits a variants.json manifest (label -> url + probes), preferred-first with the baseline as the universal fallback. - The shipped runtime.js runs inlined wasm-feature-detect probes (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads), picks the first variant whose probes all pass, and falls back to the single baked CRAFTER_WASM_URL when no manifest is present (backward compatible). - EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari still flag-gates it as of mid-2026). Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the relaxed-simd variant and runs it. Resolves #24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:14:18 +00:00
// threads/atomics — also requires SharedArrayBuffer (cross-origin isolated).
"threads": () => {
try {
if (typeof SharedArrayBuffer === "undefined") return false;
return WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,4,1,3,1,1,10,11,1,9,0,65,0,254,16,2,0,26,11]));
} catch (e) { return false; }
},
};
function variantSupported(probes) {
return (probes || []).every((name) => {
const probe = WASM_PROBES[name];
if (!probe) {
console.warn(`[wasi] unknown wasm probe '${name}' — treating variant as unsupported`);
return false;
}
try { return !!probe(); } catch (e) { return false; }
});
}
// Pick the wasm URL: prefer the variants.json manifest (first entry whose
// probes all pass), else fall back to the single baked window.CRAFTER_WASM_URL.
feat(wasm): compile C++ with WebAssembly exception handling wasm builds were compiled with -fno-exceptions. A throw did not compile, libc++'s own throws (vector::at, std::format, filesystem) aborted the program, and code that relies on exceptions could not be built for the browser at all. Every wasm unit and the std module now get -fwasm-exceptions, and the link adds -lunwind, which code built that way calls into and the driver does not add. The flag also selects the libc++ under the sysroot's lib/<triple>/eh, which wasi-sdk 33 and later ship next to the noeh one. Those libraries use the standardized encoding (try_table, exnref) while LLVM 23 still emits the legacy one by default, which wasmtime refuses to load and which would put both in one binary, so the flags ask for the standard form with -wasm-use-legacy-eh=false. That makes the setjmp lowering loadable under wasmtime too. The flags live in one constant, WasmBaselineFlags, which the compile command and the std module both take through WasmCompileFlags: a BMI is refused by a unit whose exception setting differs from its own. VariantId and StdPcmDir hash it, so the first build after the update starts fresh instead of importing PCMs built without exceptions, and two releases sharing the Flatpak's cache don't take turns rebuilding one std PCM. A sysroot without the exception-enabled libraries stops the build with a line saying so, rather than at the link with "unable to find library -lunwind". The result needs an engine with exnref: Chrome 137, Firefox 131, Safari 18.4, the bundled wasmtime. The runtime's exception-handling probe tested the legacy encoding; it now tests try_table, every variants.json entry carries it, and a browser without it is told which feature it lacks instead of failing with a CompileError. There is no switch back to -fno-exceptions: engines without exnref are old enough not to keep a second build for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:54:39 +01:00
// When the manifest has no entry this engine can run, not even the baseline,
// say which features it lacks: loading the baseline anyway would only fail
// with a CompileError about an unknown opcode.
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and baked a single wasm URL into index.html, so newer codegen features that aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD revisions later) couldn't be adopted without dropping the browsers that lack them. Add a general, feature-parameterized mechanism owned entirely by Crafter.Build: - Configuration::wasmVariants declares N codegen variants (label, extra -m flags, runtime probes). Build() compiles the baseline plus one outputName.<label>.wasm per variant, recompiling the whole graph (incl. dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU codegen, not a link switch. wasmVariantFlags folds into VariantId so each variant's objects/PCMs land in their own build+bin dir. - EnableWasiBrowserRuntime emits a variants.json manifest (label -> url + probes), preferred-first with the baseline as the universal fallback. - The shipped runtime.js runs inlined wasm-feature-detect probes (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads), picks the first variant whose probes all pass, and falls back to the single baked CRAFTER_WASM_URL when no manifest is present (backward compatible). - EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari still flag-gates it as of mid-2026). Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the relaxed-simd variant and runs it. Resolves #24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:14:18 +00:00
async function pickWasmUrl() {
let variants = null;
try {
const r = await fetch("variants.json");
if (r.ok) variants = await r.json();
} catch (e) {
// No manifest (older build) — fall back below.
}
if (Array.isArray(variants) && variants.length) {
for (const v of variants) {
if (variantSupported(v.probes)) {
if (v.label) console.log(`[wasi] selected wasm variant '${v.label}' (${(v.probes || []).join(", ")})`);
return v.url;
}
}
feat(wasm): compile C++ with WebAssembly exception handling wasm builds were compiled with -fno-exceptions. A throw did not compile, libc++'s own throws (vector::at, std::format, filesystem) aborted the program, and code that relies on exceptions could not be built for the browser at all. Every wasm unit and the std module now get -fwasm-exceptions, and the link adds -lunwind, which code built that way calls into and the driver does not add. The flag also selects the libc++ under the sysroot's lib/<triple>/eh, which wasi-sdk 33 and later ship next to the noeh one. Those libraries use the standardized encoding (try_table, exnref) while LLVM 23 still emits the legacy one by default, which wasmtime refuses to load and which would put both in one binary, so the flags ask for the standard form with -wasm-use-legacy-eh=false. That makes the setjmp lowering loadable under wasmtime too. The flags live in one constant, WasmBaselineFlags, which the compile command and the std module both take through WasmCompileFlags: a BMI is refused by a unit whose exception setting differs from its own. VariantId and StdPcmDir hash it, so the first build after the update starts fresh instead of importing PCMs built without exceptions, and two releases sharing the Flatpak's cache don't take turns rebuilding one std PCM. A sysroot without the exception-enabled libraries stops the build with a line saying so, rather than at the link with "unable to find library -lunwind". The result needs an engine with exnref: Chrome 137, Firefox 131, Safari 18.4, the bundled wasmtime. The runtime's exception-handling probe tested the legacy encoding; it now tests try_table, every variants.json entry carries it, and a browser without it is told which feature it lacks instead of failing with a CompileError. There is no switch back to -fno-exceptions: engines without exnref are old enough not to keep a second build for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:54:39 +01:00
const baseline = variants[variants.length - 1];
const missing = (baseline.probes || []).filter((name) => !variantSupported([name]));
throw new Error(`[wasi] ${baseline.url} needs WebAssembly ${missing.join(", ")}, which this browser does not support; a current Chrome, Firefox or Safari does`);
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and baked a single wasm URL into index.html, so newer codegen features that aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD revisions later) couldn't be adopted without dropping the browsers that lack them. Add a general, feature-parameterized mechanism owned entirely by Crafter.Build: - Configuration::wasmVariants declares N codegen variants (label, extra -m flags, runtime probes). Build() compiles the baseline plus one outputName.<label>.wasm per variant, recompiling the whole graph (incl. dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU codegen, not a link switch. wasmVariantFlags folds into VariantId so each variant's objects/PCMs land in their own build+bin dir. - EnableWasiBrowserRuntime emits a variants.json manifest (label -> url + probes), preferred-first with the baseline as the universal fallback. - The shipped runtime.js runs inlined wasm-feature-detect probes (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads), picks the first variant whose probes all pass, and falls back to the single baked CRAFTER_WASM_URL when no manifest is present (backward compatible). - EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari still flag-gates it as of mid-2026). Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the relaxed-simd variant and runs it. Resolves #24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:14:18 +00:00
}
return window.CRAFTER_WASM_URL;
}
const wasmUrl = await pickWasmUrl();
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
if (!wasmUrl) {
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and baked a single wasm URL into index.html, so newer codegen features that aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD revisions later) couldn't be adopted without dropping the browsers that lack them. Add a general, feature-parameterized mechanism owned entirely by Crafter.Build: - Configuration::wasmVariants declares N codegen variants (label, extra -m flags, runtime probes). Build() compiles the baseline plus one outputName.<label>.wasm per variant, recompiling the whole graph (incl. dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU codegen, not a link switch. wasmVariantFlags folds into VariantId so each variant's objects/PCMs land in their own build+bin dir. - EnableWasiBrowserRuntime emits a variants.json manifest (label -> url + probes), preferred-first with the baseline as the universal fallback. - The shipped runtime.js runs inlined wasm-feature-detect probes (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads), picks the first variant whose probes all pass, and falls back to the single baked CRAFTER_WASM_URL when no manifest is present (backward compatible). - EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari still flag-gates it as of mid-2026). Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the relaxed-simd variant and runs it. Resolves #24 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:14:18 +00:00
throw new Error("runtime.js: no wasm variant selected and window.CRAFTER_WASM_URL is not set");
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
}
2026-05-18 05:23:11 +02:00
// Preload asset files listed in files.json (emitted by
2026-05-19 17:20:20 +02:00
// EnableWasiBrowserRuntime) into the read-only baked layer so wasi-libc's
// file syscalls work against them. Browser builds otherwise can't open
// assets shipped alongside the .wasm — sync XHR is too deprecated to rely
// on.
2026-05-19 03:28:27 +02:00
//
// Manifest entries are relative paths under the bin dir (the layout
// `cfg.assets` produces — e.g. "assets/Inter.ttf",
2026-05-26 22:50:08 +02:00
// "mods/3DForts_Base/foo.cmesh"). We fetch each at its full path and
// key the read-only map by that same relative path so directory
// iteration + nested dirs work; path_open's resolveKey normalises
// caller-side variations (leading "./" or "/").
2026-05-18 05:23:11 +02:00
try {
const manifestResp = await fetch("files.json");
if (manifestResp.ok) {
const names = await manifestResp.json();
await Promise.all(names.map(async (name) => {
const r = await fetch(name);
2026-05-19 03:28:27 +02:00
if (!r.ok) {
console.warn(`[wasi] failed to preload ${name}: HTTP ${r.status}`);
return;
}
2026-05-26 22:50:08 +02:00
readonlyVfs.set(name, new Uint8Array(await r.arrayBuffer()));
2026-05-18 05:23:11 +02:00
}));
}
} catch (e) {
console.warn("[wasi] no files.json manifest (or fetch failed); file I/O syscalls will return ENOENT:", e.message);
}
2026-05-26 22:50:08 +02:00
roBuildDirTree();
2026-05-18 05:23:11 +02:00
2026-05-19 17:20:20 +02:00
// Hydrate the persistent layer from OPFS. Must complete before main()
// runs so any std::ifstream on /persistent/* sees the on-disk bytes.
await opfsHydrate();
// Best-effort final flush as the page is torn down. `pagehide` is the
// modern equivalent of `unload`; it fires on tab close + bfcache eviction
// + cross-document navigation. The browser MAY kill the page before the
// async writes complete — the periodic FLUSH_DEBOUNCE_MS timer is the
// real durability guarantee for writes that happen close to shutdown.
window.addEventListener("pagehide", () => { opfsFlushAll(); });
const wasi = new Wasi({ stdin: "", env: {}, args: [] });
2026-05-18 05:23:11 +02:00
// Modules that need env imports (Crafter.Graphics DOM mode, etc.) ship a
// co-located env.js that sets `window.crafter_webbuild_env`. The
// `EnableWasiBrowserRuntime` injects each one as a regular <script> tag
// before this module script, so by the time we instantiate they've all
// populated the global. Pure-WASI builds leave the global undefined and
// we pass an empty object — extra imports on the JS side are harmless
// (WebAssembly only complains about *missing* declared imports).
if (!window.crafter_webbuild_env) {
window.crafter_webbuild_env = {};
}
// Some env.js bridges (notably the Crafter.CppDOM one) expect an indirect
// function table they can push function references onto. Set it up so we
// don't break compatibility with that pattern.
window.crafter_webbuild_env.table = new WebAssembly.Table({ initial: 4, element: "anyfunc" });
// Async env bridges (notably the WebGPU one in Crafter.Graphics) set
// `window.crafter_webbuild_env_ready` to a Promise that resolves once
// their init (adapter/device requests, pipeline compilation, etc.) is
// finished AND env.* has been swapped from sync stubs to real impls.
// We MUST await this BEFORE WebAssembly.instantiateStreaming — the wasm
// snapshots its import functions at instantiate time, so stubs captured
// then are baked in. Sibling <script type="module"> top-level awaits
// aren't reliably serialized cross-browser, so we can't rely on dom-*
// scripts' own TLAs to block us here.
if (window.crafter_webbuild_env_ready) {
try {
await window.crafter_webbuild_env_ready;
} catch (e) {
console.error("[crafter] env bridge init failed:", e);
// Continue to instantiation anyway — the bridge's stubs throw a
// clearer error from their own call sites.
}
}
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
const { instance } = await WebAssembly.instantiateStreaming(fetch(wasmUrl), {
wasi_snapshot_preview1: wasi,
2026-05-18 05:23:11 +02:00
env: window.crafter_webbuild_env,
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
});
wasi.instance = instance;
2026-05-18 05:23:11 +02:00
// Alias under both names — `crafter_wasi` is the canonical, but the older
// CppDOM env.js reads from `crafter_webbuild_wasi`. Keep both wired so
// either generation of env.js works against this runtime.
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
window.crafter_wasi = wasi;
2026-05-18 05:23:11 +02:00
window.crafter_webbuild_wasi = wasi;
V2: WASI, -r flag, CI pipeline, examples & tests cleanup WASI / wasm32 target support - Auto-detect /usr/share/wasi-sysroot on Linux when target starts_with("wasm32") - Skip -march/-mtune for wasm (clang rejects them) - Apply -fno-exceptions -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL to wasm builds (compile + std PCM, kept in sync) - .wasm output extension in expectedOutputFor and link command - EnableWasiBrowserRuntime(cfg): opt-in helper that drops index.html + runtime.js next to the .wasm; runtime.js reads window.CRAFTER_WASM_URL set in the templated index.html so a single shim handles any output name -r run flag in the CLI: build then exec the artifact (host targets only; rejects libraries; auto .exe/.wasm extension handling) CI pipeline (.forgejo/workflows/ci.yaml) - Triggers: PR/push to master + manual dispatch - Single arch-latest container job: install deps, bootstrap, self-rebuild, run tests, cross-compile mingw, package both archives, upload artifacts - Rolling 'latest' release published only on push/dispatch to master mingw cross-compile from Linux now works end-to-end: - ExternalDependency cache key includes target so per-target glslang builds don't collide; CMAKE_BUILD_TYPE=Release pinned (otherwise glslang appends 'd' to lib names and breaks linking); cross-compile cmake flags (CMAKE_SYSTEM_NAME=Windows, CMAKE_*_COMPILER_TARGET=...) - project.cpp accepts --target=<triple>; Linux-only -Wl,--export-dynamic and -ldl are gated; mingw glslang skips the standalone exe (its libgcc_eh link pulls pthread which mingw doesn't link by default) - mingw compile uses -femulated-tls so std::__once_callable etc reference the same emutls symbols libstdc++ provides - mingw link auto-adds -lstdc++exp -lpthread GetCrafterBuildHome() exposed from the Platform module; LoadProject (Linux + Windows) now both use it instead of duplicating the resolution. Examples reorg: hello-world, library, with-module, wasi, tests — each with its own README. Tests reorg: per-test directory with inner/ fixture, no shared tests/fixtures/ tree. New Wasi test verifies .wasm magic bytes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 23:24:46 +02:00
2026-05-18 05:23:11 +02:00
try {
instance.exports._start();
} catch (e) {
if (e && typeof e.crafterWasiExit === "number") {
// Clean exit from std::_Exit / __wasi_proc_exit. The wasm instance
// remains valid; any rAF callback registered before exit keeps
// running. (See Crafter::Window::StartSync DOM impl.)
} else {
throw e;
}
}