page: the function JIT on by default for Wine's own loader (W3); ?fjit=0 opts out; its cost reported every 60 s

- nt-page.js: exe=.../lib/wine/<arch>-unix/wine runs with the fjit unless
  ?fjit=0. Undertale on units-ut2 (worldmachine, 600 s, fps over the last
  120 s): 23.1 without, a flat 30.0 with; code no unit holds otherwise
  runs on tier-1 behind the units' fallback. ?fjit=0 used to turn the JIT
  on (Number('0') || 1).
- fjit.rs: the process's translation time and wasm bytes
  (gex_fjit_translate_ms, gex_fjit_wasm_kib, gex_fjit_modules); gex.mjs
  prints '[gex] fjit so far: ...' every 60 s while modules are being made,
  with this Worker's compile time, since the census at exit never comes
  for a game.

W3 page echo (gooner, correctness): default ok with 20 JIT modules, and
?fjit=0 ok with none. The gate legs gate-w3page/run-fjit.sh and
run-nofjit.sh and gate-utpage-fj run each mode explicitly.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_ea9f71488b6e448da93d6c2dabf4879c
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-11 19:09:45 -04:00
1 parent fc9a7394b6
commit f6e6c7ef15
3 files changed
+54 -4

No files matched your search

+15 -1
View File
@@ -727,6 +727,9 @@ export class Gex {
#tableRetries = 0;
#iCopied = 0;
#compileMs = 0;
/** `#fjitModule`'s compile time, and when it next reports (`[gex] fjit so far`). */
#fjitCompileMs = 0;
#fjitNextReport = 0;
#instantiateMs = 0;
#growMs = 0;
/// What every Worker pays instantiating the shim and guest before the guest
@@ -3584,7 +3587,18 @@ export class Gex {
if (dump) globalThis.process.getBuiltinModule('fs').writeFileSync(`${dump}/fjit-${n}.wasm`, bytes);
const t0 = performance.now();
mod = new WebAssembly.Module(bytes);
this.#compileMs += performance.now() - t0;
const dt = performance.now() - t0;
this.#compileMs += dt;
this.#fjitCompileMs += dt;
// Every 60 s while the JIT makes modules: what it has cost so far (the
// census at exit comes too late for a game that never exits).
if (t0 >= this.#fjitNextReport && e.gex_fjit_translate_ms) {
if (this.#fjitNextReport) {
console.error(`[gex] fjit so far: ${e.gex_fjit_modules()} modules, ${e.gex_fjit_wasm_kib()} KiB of wasm, `
+ `${e.gex_fjit_translate_ms()} ms translating (the process), ${this.#fjitCompileMs.toFixed(0)} ms compiling (this Worker)`);
}
this.#fjitNextReport = t0 + 60000;
}
this.#sharedModules.set(name, mod);
this.#env.shareModule?.(name, mod);
return mod;
+29 -1
View File
@@ -45,6 +45,28 @@ fn trace(s: &str) {
}
static JIT: OnceLock<gex_xlate::fjit::Jit> = OnceLock::new();
/// What translating has cost this process: shim time in `translate_root`
/// (recovery and emission, declined ones included) and the wasm it made.
static TRANSLATE_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static WASM_BYTES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
/// Milliseconds this process has spent translating (`TRANSLATE_NS`).
#[no_mangle]
pub extern "C" fn gex_fjit_translate_ms() -> u32 {
(TRANSLATE_NS.load(Ordering::Relaxed) / 1_000_000) as u32
}
/// KiB of wasm the JIT has made in this process, and how many modules.
#[no_mangle]
pub extern "C" fn gex_fjit_wasm_kib() -> u32 {
(WASM_BYTES.load(Ordering::Relaxed) / 1024) as u32
}
#[no_mangle]
pub extern "C" fn gex_fjit_modules() -> u32 {
lock(&MODULES).len() as u32
}
/// One translation: its bytes (named `gex-fjit` for profiles), the bridges
/// every Worker writes (cgen.rs `bridges`), the pages it numbered and what
/// the dispatch tables must hold.
@@ -486,7 +508,13 @@ fn translate_root(addr: u64) -> Option<u32> {
}
}
};
let Some(wasm) = jit.translate_skipping(0, crate::guest_bytes(), addr, &executable, &served, &read_only, &volatile, &pg, class32) else {
let t0 = crate::now_ns();
let wasm = jit.translate_skipping(0, crate::guest_bytes(), addr, &executable, &served, &read_only, &volatile, &pg, class32);
TRANSLATE_NS.fetch_add(crate::now_ns().saturating_sub(t0), Ordering::Relaxed);
if let Some(w) = &wasm {
WASM_BYTES.fetch_add(w.len() as u64, Ordering::Relaxed);
}
let Some(wasm) = wasm else {
trace(&format!("declined {addr:#x}"));
declined(fresh.into_inner());
return None;
+10 -2
View File
@@ -115,8 +115,16 @@ async function main() {
if (q.get('heartbeat')) globalThis.GEX_HEARTBEAT = Number(q.get('heartbeat'));
// `?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;
// `?fjit=1`: the function JIT (gexc run --fjit's browser half, fjit.rs);
// `?fjit=2` traces it, `?fjit=0` turns it off. On by default for Wine's
// own loader (W3, `exe=.../lib/wine/<arch>-unix/wine`): code no
// function-tier unit holds otherwise runs on tier-1 behind the units'
// fallback, and Undertale drops from its 30 fps cap to ~15 in some
// scenes (worldmachine, units-ut2: 23.1 fps over 120 s without, a flat
// 30.0 with).
const fjitQ = q.get('fjit');
const w3Loader = /\/lib\/wine\/[^/]+-unix\/wine$/.test(q.get('exe') || '');
if (fjitQ !== null ? fjitQ !== '0' : w3Loader) globalThis.GEX_FJIT = Number(fjitQ) || 1;
// `?wasmdlls=soname=path,...`: shared objects replaced by wasm64 providers (cgen/libprov.rs).
if (q.get('wasmdlls')) globalThis.GEX_WASM_DLLS = q.get('wasmdlls');
// `?elfunits=name,...`: shared objects that get function-tier units (with `?cgen=1`).