page: the function JIT in a 64-bit memory's process (table64, i64 PIC globals); ?fjit=1 for the page

- gex.mjs: beside a 64-bit memory the function table is a table64 (BigInt indices), and
  __table_base/__memory_base/__stack_pointer are i64;
- gex-xlate fjit: no std::time on wasm64-unknown-unknown either (it panicked in the shim);
- nt-page/runtime: ?fjit=1 (every process), browser-linux.mjs --fjit.

tests/games/src/glx-context in Chromium: 0 failures with ?mem64=1&fjit=1, and with the wasm32
shim with and without the function JIT.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_d2453757b8a84fdbb4ee7c9e093141df
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-07 21:35:18 -04:00
1 parent d442cc5755
commit 1e4ae9d7d9
5 files changed
+24 -14

No files matched your search

+3 -3
View File
@@ -410,15 +410,15 @@ fn stats_line(modules: u64, functions: u64, recover_us: u64, emit_us: u64, bytes
/// Microseconds since the first call, for the translation statistics. 0 on
/// wasm32-unknown-unknown, where `std::time::Instant` panics (the browser's
/// wasm32/wasm64-unknown-unknown, where `std::time::Instant` panics (the browser's
/// function JIT); its stats then report no time.
fn now_us() -> u64 {
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
{
static T0: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
T0.get_or_init(std::time::Instant::now).elapsed().as_micros() as u64
}
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
{
0
}
+15 -9
View File
@@ -16,6 +16,8 @@ const m64 = () => !!globalThis.GEX_MEM64;
const LIMIT64_BYTES = 16 * 2 ** 30;
/** A value for an address-typed global: i64 (BigInt) in a wasm64 module. */
const addrVal = (v) => (m64() ? BigInt(v >>> 0) : v | 0);
/** An index into the function table: a table64 (BigInt indices) beside a 64-bit memory. */
const tIdx = (v) => (m64() ? BigInt(v) : v);
/**
* Decode UTF-8 out of guest memory.
@@ -886,7 +888,7 @@ export class Gex {
call_out: (ctx, target, ret, spWant) => this.#callOut(ctx >>> 0, target, ret, spWant),
// An AOT-only fallback whose target this Worker has just installed
// (cgen.rs `gex_ftab_fallback`): called as a function body.
ftab_call: (idx, ctx, depth, need, ret) => this.#cgen.ftab.get(idx >>> 0)(ctx, depth, need, ret),
ftab_call: (idx, ctx, depth, need, ret) => this.#cgen.ftab.get(tIdx(idx >>> 0))(ctx, depth, need, ret),
// A dispatch miss installs in place (lib.rs `in_place`).
jit_install: (_ctx) => this.#jitInstall(),
// An exception dispatched nested (nt.rs `raise_nested`).
@@ -2466,7 +2468,9 @@ export class Gex {
rm = new WebAssembly.Module(this.#bytes().slice(ptr, ptr + (e.gex_ftab_runtime_len() >>> 0)));
this.#sharedModules.set('ftab-runtime', rm);
}
const ftab = new WebAssembly.Table({ element: 'anyfunc', initial: 0 });
// A table64 beside a 64-bit memory: every module of such a process (units, translations,
// providers) addresses its function table with i64.
const ftab = new WebAssembly.Table(m64() ? { element: 'anyfunc', address: 'i64', initial: 0n } : { element: 'anyfunc', initial: 0 });
const lookup = (ctx, addr) => {
if (e.gex_ftab_behind()) this.#cgenCatchUp();
const idx = e.gex_ftab_lookup(ctx, addr);
@@ -2491,7 +2495,7 @@ export class Gex {
const ri = new WebAssembly.Instance(rm, { gex });
this.#cgen = { ftab, resolve: ri.exports.resolve, bridge: ri.exports.bridge, lookup, have: 0, instances: [ri] };
this.#cgenGrow();
ftab.set(1, e.gex_ftab_fallback);
ftab.set(tIdx(1), e.gex_ftab_fallback);
return this.#cgen;
}
@@ -2501,7 +2505,8 @@ export class Gex {
#cgenGrow() {
const st = this.#cgen;
const want = (this.#shim.exports.gex_ftab_size() >>> 0) + 65536;
if (st.ftab.length < want) st.ftab.grow(want - st.ftab.length, st.resolve);
const have = Number(st.ftab.length);
if (have < want) st.ftab.grow(tIdx(want - have), st.resolve);
}
/** Install unit `u` into this Worker: instantiate it with `__table_base` =
@@ -2538,13 +2543,14 @@ export class Gex {
env: {
memory: this.#memory,
__indirect_function_table: st.ftab,
__table_base: new WebAssembly.Global({ value: 'i32', mutable: false }, base | 0),
__memory_base: new WebAssembly.Global({ value: 'i32', mutable: false }, memBase),
// i64 in a 64-bit memory's process (wasm64 PIC modules).
__table_base: new WebAssembly.Global({ value: m64() ? 'i64' : 'i32', mutable: false }, m64() ? BigInt(base >>> 0) : base | 0),
__memory_base: new WebAssembly.Global({ value: m64() ? 'i64' : 'i32', mutable: false }, m64() ? BigInt(memBase) : memBase),
// A unit's functions never use clang's shadow stack (those are stubs),
// but a position-independent module imports its pointer all the same.
// A stateful provider's is this Worker's (one C stack per thread).
// A wasm32 program sets its own at each thread's entry.
__stack_pointer: memBase && !wasmprog ? this.#providerStack() : new WebAssembly.Global({ value: 'i32', mutable: true }, 0),
__stack_pointer: memBase && !wasmprog ? this.#providerStack() : new WebAssembly.Global(m64() ? { value: 'i64', mutable: true } : { value: 'i32', mutable: true }, m64() ? 0n : 0),
},
gex: {
...this.#imports, lookup: st.lookup, ftab: st.ftab,
@@ -2616,7 +2622,7 @@ export class Gex {
// a C longjmp).
provider_try: (f, arg) => {
try {
ftab.get(f >>> 0)(arg);
ftab.get(m64() ? BigInt(f) : f >>> 0)(arg);
e.gex_provider_note(0);
return 0;
} catch (err) {
@@ -2751,7 +2757,7 @@ export class Gex {
const ev = BigInt.asUintN(64, BigInt(e.gex_ftab_event(st.have)));
const kind = Number(ev >> 32n), v = Number(ev & 0xffffffffn);
if (kind === 1) this.#cgenInstall(e.gex_ftab_install_unit(v) >>> 0, e.gex_ftab_install_base(v) >>> 0);
else if (kind === 2) st.ftab.set(v, st.resolve);
else if (kind === 2) st.ftab.set(tIdx(v), st.resolve);
st.have++;
e.gex_cgen_have(st.have);
}
+2 -2
View File
@@ -8,7 +8,7 @@
// node tests/games/browser-linux.mjs --name NAME --dir GAME_DIR --exe REL
// [--cwd REL] [--args ARGS] [--libs DIR:DIR] [--files LIST]
// [--input SCRIPT.wl] [--size WxH] [--minutes N] [--shot DIR]
// [--gameplay N] [--mem64] [--env K=V,K=V] [--end-on-exit]
// [--gameplay N] [--mem64] [--fjit] [--env K=V,K=V] [--end-on-exit]
//
// --files lists host files the game opens, one per line (collected from a
// native `gexc run --strace`; tests/games/retrocycles-linux-files.txt).
@@ -127,7 +127,7 @@ async function main() {
const t0 = Date.now();
const { evaluate, state, shot, consoleLines } = await openPage(
`tar=/gex/nt/${basename(tar)}&exe=/game/${exe}&cwd=/game/${cwd}&gl=1&glbackend=wgpu&hostx=1`
+ (argv.includes('--mem64') ? '&mem64=1' : '')
+ (argv.includes('--mem64') ? '&mem64=1' : '') + (argv.includes('--fjit') ? '&fjit=1' : '')
+ `&size=${width}x${height}&trapstack=1&env=${encodeURIComponent(genv)}&args=${encodeURIComponent(opt('--args', ''))}`,
{ width: width + 20, height: height + 100, shotDir },
);
+2
View File
@@ -113,6 +113,8 @@ async function main() {
if (q.get('trapstack')) globalThis.GEX_TRAP_STACK = 1;
// `?mem64=1`: a 64-bit guest memory (the wasm64 shim; docs/mem64.md step 6).
if (q.get('mem64') === '1') globalThis.GEX_MEM64 = 1;
// `?fjit=1`: the function JIT (gexc run --fjit's browser half, fjit.rs).
if (q.get('fjit')) globalThis.GEX_FJIT = Number(q.get('fjit')) || 1;
if (q.get('dense') === '0') globalThis.GEX_DENSE = 0;
if (q.get('dense') === '2') globalThis.GEX_DENSE = 2;
// cgen=1|2 (prefer|require units), in the unguarded flavor of i386 units
+2
View File
@@ -669,6 +669,8 @@ export async function runProgram(
denseDebug: !!globalThis.GEX_DENSE_DEBUG,
// `?mem64=1`: every Worker of the process builds or joins a 64-bit memory.
mem64: globalThis.GEX_MEM64 ? 1 : undefined,
// `?fjit=1`: the function JIT in every process.
fjit: globalThis.GEX_FJIT || undefined,
traceMisses: !!globalThis.GEX_TRACE_MISSES,
// `?sharetrace=1`: say how compiled modules are shared between a process's Workers.
shareTrace: flag('sharetrace') ? 1 : undefined,