code wiki / _hdl_build / nx_js_vm.nx
nx_js_vm.nx source
↩ module page · 4436 lines · 214931 B
1// nx_js_compile.nx -- BYTECODE-VM rungs 2+3+4a+4b: the AST->BYTECODE COMPILER + a TAGGED-VALUE stack VM.
2// rung2: walk nx_js_parse's AST once, emit bytecode (number/ident/assign/binary/var/while/if/block).
3// rung3: the VM stack + vars carry the engine's 2-slot [tag,payload] value model (VAL_NUM/STR/BOOL/...).
4// rung4a: logical short-circuit && || + unary ! - + typeof.
5// rung4b (FUNCTIONS + OBJECTS -- the real-bundle rung): function decls/exprs/arrows compile to a
6// FUNCTION TABLE + inline bodies (JMP over); calls run on CALL FRAMES; CLOSURES are arena-allocated
7// env records chained by lexical parent (variables resolve at COMPILE time to depth+slot -- no name
8// lookup at runtime); objects/arrays/member/index reuse the engine's obj_*/arr_*/ev_get_prop model;
9// `this` rides an env-header binding (env-chain walk == the tree-walker's THIS_NS lookup); `new`
10// allocates + binds this + keeps the object unless the ctor returns one; natives dispatch through
11// js_native_apply (the value-based core split out of js_invoke_native); for/do-while/ternary/
12// break/continue/switch/for-of/for-in/??/compound-assign/destructuring/spread all compile.
13// The VM has an ERROR CHANNEL (BC_ERROR + honest halts) so rc parity with js_run_source is gated.
14// NAMED OPENS (rung-4c/5): template ${} interpolation (no-interp templates work) -- BC_ERROR;
15// VM closures passed to callback natives (forEach/map/setTimeout/then) need the rung-5 VM re-entry
16// (js_call_core expects tree closures; VM closure records carry VMF_MAGIC so the wire can detect);
17// per-iteration for-of capture + for-scope vars are FUNCTION-scoped here (real-JS var semantics;
18// the tree-walker gives for-loops their own child env -- divergence only for post-loop var reads).
19// license_tier: ORIGINAL
20import "nx_x86emit.nx" // P5 baseline-JIT: the sovereign x86-64 emitter (verified by nx_x86emit_gate)
21import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc)
22import "nx_js_eval.nx" // jp_* AST + ND_*/OP_*/VAL_*/NS_* consts + ev_* value helpers + obj_*/arr_* +
23const BC_MAGIC_262144: i64 = 262144
24const BC_MAGIC_65536: i64 = 65536
25const BC_MAGIC_1000000: i64 = 1000000
26const BC_MAGIC_3000: i64 = 3000
27const BC_MAGIC_2147483647: i64 = 2147483647
28const BC_MAGIC_2147483648: i64 = 2147483648
29const BC_MAGIC_1099511627776: i64 = 1099511627776
30const BC_MAGIC_1000000007: i64 = 1000000007
31const BC_MAGIC_1234567891234567: i64 = 1234567891234567
32const BC_MAGIC_4000: i64 = 4000
33 // ev_get_prop/ev_set_prop/ev_global_prop/ev_native_* + js_native_apply +
34 // js_construct_native + js_dom_write_hook + js_run_source (the parity oracle)
35
36func bw(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
37// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
38// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
39// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
40// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
41func bn(v: i64) -> i64 { nxi_out(v); return 0 }
42func bsl(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
43
44// ---------------- opcodes ----------------
45const BC_PUSH: i64 = 1 // push [VAL_NUM, arg]
46const BC_PUSHK: i64 = 2 // push pool[arg] (typed constant: string/bool/null/float/globalns/native)
47const BC_LOAD: i64 = 3 // push CURRENT env slot arg
48const BC_STORE: i64 = 4 // current env slot arg = pop
49const BC_POP: i64 = 5
50const BC_JMPF: i64 = 6 // if !truthy(pop) pc=arg else +2
51const BC_JMP: i64 = 7
52const BC_RET: i64 = 8 // program end: out = top, halt rc 0
53const BC_BINOP: i64 = 9 // arg = OP_*; b=pop a=pop push vm_binary(op,a,b)
54const BC_DUP: i64 = 10 // duplicate top value
55const BC_JMPT: i64 = 11 // if truthy(pop) pc=arg else +2 (for || short-circuit)
56const BC_UNOP: i64 = 12 // arg = OP_NOT/OP_NEG/OP_POS/OP_TYPEOF; push unary(op, pop)
57// rung 4b (dispatched via vm_ext -- the cold half of the interpreter):
58const BC_PUSHU: i64 = 13 // push undefined
59const BC_LOADU: i64 = 14 // arg = depth*VM_UPK+slot; walk parent chain, push
60const BC_STOREU: i64 = 15 // arg = depth*VM_UPK+slot; walk parent chain, store pop
61const BC_JMPNN: i64 = 16 // v=pop; if v is NOT null/undefined pc=arg else +2 (?? short-circuit)
62const BC_CLOSURE: i64 = 17 // arg = ftab idx; push VAL_FUNC closure [VMF_MAGIC, fidx, current env]
63const BC_CALL: i64 = 18 // arg = argc; stack [.. fn a0..]; VM fn -> frame push; native -> js_native_apply
64const BC_RETV: i64 = 19 // return from function: pop result, pop frame, restore, push result
65const BC_CALLM: i64 = 20 // arg = kidx*CM_PACK+argc; stack [.. recv a0..]; method resolve like js_eval_call
66const BC_GETPROP: i64 = 21 // arg = key pool idx; pop base, push base.key (mirrors js_eval_member)
67const BC_GETPROPQ:i64 = 22 // optional-chain variant: null/undefined base -> undefined
68const BC_SETPROP: i64 = 23 // arg = key pool idx; stack [base val] -> set; push val
69const BC_GETIDX: i64 = 24 // stack [base key] -> push base[key] (mirrors js_eval_index)
70const BC_SETIDX: i64 = 25 // stack [base key val] -> set; push val
71const BC_ARR: i64 = 26 // push a fresh empty VAL_ARRAY
72const BC_APUSH: i64 = 27 // stack [arr v] -> append v; keep arr
73const BC_SPREAD: i64 = 28 // stack [arr src] -> append all of src's elements (non-array: skip); keep arr
74const BC_OBJ: i64 = 29 // push a fresh empty VAL_OBJECT
75const BC_OPROP: i64 = 30 // arg = key pool idx; stack [obj v] -> obj[key]=v; keep obj
76const BC_THIS: i64 = 31 // push the nearest env-chain `this` binding (absent -> undefined)
77const BC_NEW: i64 = 32 // arg = argc; stack [.. ctor a0..]; native -> js_construct_native; VM fn -> new-frame
78const BC_ERROR: i64 = 33 // halt rc=1 (ReferenceError/unsupported-node parity with the tree-walker)
79const BC_KEYS: i64 = 34 // pop obj (MUST be VAL_OBJECT else halt) -> push array of its key strings
80const BC_ITERCHK: i64 = 35 // top MUST be VAL_ARRAY else halt (for-of parity with the tree-walker)
81const BC_SWAP: i64 = 36 // swap top two values
82const BC_ROT: i64 = 37 // [a b c] -> [b c a] (third-from-top moves to top)
83const BC_IDXSOFT: i64 = 38 // arg = int index; pop v; push v[arg] if array in-range else undefined (destructuring)
84const BC_PROPSOFT:i64 = 39 // arg = key pool idx; pop v; push v.key if object+present else undefined
85const BC_TRY: i64 = 40 // arg = catch-handler pc; push [catchpc, sp, env, fp] onto the handler stack
86const BC_TRYPOP: i64 = 41 // leave a try block normally: pop the top handler
87const BC_THROW: i64 = 42 // pop value; unwind to the nearest handler (push value at its entry) or halt rc=1
88const BC_CLASSMETHOD: i64 = 43 // arg=method-name key pool idx; stack [ctor method] -> obj_set(func_prototype(ctor), key, method); leave [ctor]
89const BC_DUP2: i64 = 44 // duplicate the top TWO values: [.. a b] -> [.. a b a b] (single-eval compound index assign)
90const BC_DELIDX: i64 = 45 // delete: [.. base keyval] -> [.. true]; obj_delete(base, ToString(key)) when base is an object
91
92const VM_UPK: i64 = 65536 // LOADU/STOREU packing: arg = depth*VM_UPK + slot
93const CM_PACK: i64 = 1024 // CALLM packing: arg = keypoolidx*CM_PACK + argc (argc < 1024; argbuf caps 256)
94// VMF_MAGIC (VM-closure marker) now lives in nx_js_eval.nx beside the re-entry hook seam.
95
96// ---------------- capacities (honest caps: overflow halts rc=1 / compile emits BC_ERROR, never silent) ----------------
97const VMC_CODEI: i64 = 262144 // max instructions
98const VMC_POOLN: i64 = 32768 // max pool constants
99const VMC_MAXF: i64 = 2048 // max compiled functions
100const VMC_MAXSC: i64 = 128 // max lexical function-nesting depth (compile-time)
101const VMC_MAXV: i64 = 4096 // max vars per function scope
102const VMC_STACKN: i64 = 16384 // operand stack (values)
103const VMC_MAXFR: i64 = 8192 // max call depth
104const VMC_ARENA: i64 = 33554432 // 32MB env/closure arena (bump; envs never freed -- matches the tree-walker)
105const VMC_PATCH: i64 = 2048 // max pending break/continue patches
106const VMC_MAXCASE: i64 = 256 // max switch cases
107const VMC_MAXH: i64 = 1024 // max active try handlers (depth)
108const VMC_MAXFE: i64 = 128 // max compile-time nested try (finally-stack) depth
109
110// env record (arena): [0]=parent(*i64 as i64), [1]=this-flag, [2]=this tag, [3]=this pay, slots at 4+i*2
111const ENVR_HDR: i64 = 4
112// frame record: [0]=return pc, [1]=saved env, [2]=base sp (fn cell position -- result lands here), [3]=new-obj ptr (0 = plain call)
113const FR_ENT: i64 = 6 // [ret_pc, saved_env, base, newobj, arena_hp0, esc0] -- last 2 = escape-aware arena reclaim
114// ftab entry: [0]=entry pc, [1]=nparams (excl rest), [2]=nvars, [3]=flags (1=has rest param)
115const FT_ENT: i64 = 4
116
117// ---------------- unary/binary op cores (EXACT replicas of js_eval's ND_UNARY/ND_BINARY bodies) ----------------
118func vm_unary(op: i64, v: *i64, out: *i64) -> i64 {
119 if op == OP_NOT { if ev_truthy(v) == 1 { ev_set(out, VAL_BOOL, 0) } else { ev_set(out, VAL_BOOL, 1) } return 0 }
120 // -x / +x on a FLOAT stay float (was int-truncating: -0.5 -> 0, which zeroed NavierStokes' h).
121 if op == OP_NEG { if v[0] == VAL_FLOAT { ev_set(out, VAL_FLOAT, nx_f64_neg(v[1])); return 0 } ev_set(out, VAL_NUM, 0 - ev_tonum(v)); return 0 }
122 if op == OP_POS { if v[0] == VAL_FLOAT { ev_set(out, VAL_FLOAT, v[1]); return 0 } ev_set(out, VAL_NUM, ev_tonum(v)); return 0 }
123 if op == OP_TYPEOF { ev_set(out, VAL_STR, (ev_typeof_str(v[0])) as i64); return 0 }
124 if op == OP_BNOT { ev_set(out, VAL_NUM, 0 - js_toint32(ev_tonum(v)) - 1); return 0 } // ~x
125 ev_copy(out, v); return 0
126}
127func vm_binary(op: i64, lb: *i64, rb: *i64, out: *i64) -> i64 {
128 if op == OP_INSTANCEOF { return ev_instanceof(lb, rb, out) }
129 if op == OP_IN { return ev_in(lb, rb, out) }
130 if op == OP_EQ { ev_set(out, VAL_BOOL, ev_loose_eq(lb, rb)); return 0 }
131 if op == OP_NE { if ev_loose_eq(lb, rb) == 1 { ev_set(out, VAL_BOOL, 0) } else { ev_set(out, VAL_BOOL, 1) } return 0 }
132 if op == OP_SEQ { ev_set(out, VAL_BOOL, ev_strict_eq(lb, rb)); return 0 }
133 if op == OP_SNE { if ev_strict_eq(lb, rb) == 1 { ev_set(out, VAL_BOOL, 0) } else { ev_set(out, VAL_BOOL, 1) } return 0 }
134 if op == OP_ADD { if lb[0] == VAL_STR { return ev_str_concat(ev_coerce_str(lb), ev_coerce_str(rb), out) } if rb[0] == VAL_STR { return ev_str_concat(ev_coerce_str(lb), ev_coerce_str(rb), out) } }
135 if lb[0] == VAL_STR { if rb[0] == VAL_STR {
136 let c: i64 = ev_str_cmp((lb[1]) as *i64, (rb[1]) as *i64)
137 if op == OP_LT { ev_set(out, VAL_BOOL, ev_b2(c < 0)); return 0 }
138 if op == OP_LE { ev_set(out, VAL_BOOL, ev_b2(c <= 0)); return 0 }
139 if op == OP_GT { ev_set(out, VAL_BOOL, ev_b2(c > 0)); return 0 }
140 if op == OP_GE { ev_set(out, VAL_BOOL, ev_b2(c >= 0)); return 0 }
141 } }
142 if ev_is_relop(op) == 1 { if ev_one_str_one_num(lb, rb) == 1 {
143 let mb: *i64 = sys_mmap(16) as *i64
144 if ev_mixed_relnum(lb, mb) == 0 { ev_set(out, VAL_UNDEF, 0); return 1 }
145 let nb: *i64 = sys_mmap(16) as *i64
146 if ev_mixed_relnum(rb, nb) == 0 { ev_set(out, VAL_UNDEF, 0); return 1 }
147 let x: i64 = mb[0]; let y: i64 = nb[0]
148 if op == OP_LT { ev_set(out, VAL_BOOL, ev_b2(x < y)); return 0 }
149 if op == OP_LE { ev_set(out, VAL_BOOL, ev_b2(x <= y)); return 0 }
150 if op == OP_GT { ev_set(out, VAL_BOOL, ev_b2(x > y)); return 0 }
151 if op == OP_GE { ev_set(out, VAL_BOOL, ev_b2(x >= y)); return 0 }
152 } }
153 // ToNumber coercion for non-`+` ops: string/bool/null operand -> number (JS ToNumber). `100/"5"`=20.
154 var lc: *i64 = lb
155 var rc: *i64 = rb
156 if lb[0] == VAL_STR { let lt: *i64 = sys_mmap(16) as *i64; ev_coerce_num(lb, lt); lc = lt }
157 if rb[0] == VAL_STR { let rt: *i64 = sys_mmap(16) as *i64; ev_coerce_num(rb, rt); rc = rt }
158 // BITWISE (&|^ << >> >>>) -- real JS ToInt32 both sides (never float-promote); 32-bit int result.
159 if op == OP_BAND { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) & js_toint32(ev_tonum(rc))); return 0 }
160 if op == OP_BOR { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) | js_toint32(ev_tonum(rc))); return 0 }
161 if op == OP_BXOR { ev_set(out, VAL_NUM, js_toint32(ev_tonum(lc)) ^ js_toint32(ev_tonum(rc))); return 0 }
162 if op == OP_SHL { let sa: i64 = js_toint32(ev_tonum(lc)); let sh: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, js_toint32(sa << sh)); return 0 }
163 if op == OP_SHR { let sa: i64 = js_toint32(ev_tonum(lc)); let sh: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, sa >> sh); return 0 }
164 if op == OP_USHR { let ua: i64 = ev_tonum(lc) & 0xffffffff; let sh: i64 = ev_tonum(rc) & 31; ev_set(out, VAL_NUM, ua >> sh); return 0 }
165 if lc[0] == VAL_FLOAT { return ev_binop_f64(op, ev_tof64(lc), ev_tof64(rc), out) }
166 if rc[0] == VAL_FLOAT { return ev_binop_f64(op, ev_tof64(lc), ev_tof64(rc), out) }
167 let a: i64 = ev_tonum(lc); let b: i64 = ev_tonum(rc)
168 if op == OP_ADD { ev_set(out, VAL_NUM, a + b); return 0 }
169 if op == OP_SUB { ev_set(out, VAL_NUM, a - b); return 0 }
170 if op == OP_MUL { ev_set(out, VAL_NUM, a * b); return 0 }
171 if op == OP_DIV { return ev_num_div(a, b, out) }
172 if op == OP_MOD { return ev_num_mod(a, b, out) }
173 if op == OP_LT { ev_set(out, VAL_BOOL, ev_b2(a < b)); return 0 }
174 if op == OP_LE { ev_set(out, VAL_BOOL, ev_b2(a <= b)); return 0 }
175 if op == OP_GT { ev_set(out, VAL_BOOL, ev_b2(a > b)); return 0 }
176 if op == OP_GE { ev_set(out, VAL_BOOL, ev_b2(a >= b)); return 0 }
177 ev_set(out, VAL_UNDEF, 0); return 0
178}
179
180// ---------------- VM state block (vs) ----------------
181const VS_PC: i64 = 0
182const VS_SP: i64 = 1
183const VS_ENV: i64 = 2
184const VS_FP: i64 = 3
185const VS_HP: i64 = 4 // arena bump offset (bytes)
186const VS_RC: i64 = 5
187const VS_RUN: i64 = 6
188const VS_ARENA: i64 = 7 // arena base (i64)
189const VS_STACK: i64 = 8
190const VS_CODE: i64 = 9
191const VS_POOL: i64 = 10
192const VS_FTAB: i64 = 11
193const VS_CTX: i64 = 12
194const VS_GENV: i64 = 13
195const VS_FRAMES: i64 = 14
196const VS_ARGBUF: i64 = 15 // reusable 256-arg buffer for native calls (values COPIED out by natives)
197const VS_T1: i64 = 16 // scratch cell (thisv)
198const VS_T2: i64 = 17 // scratch cell (results)
199const VS_OUT: i64 = 18 // final result cell
200const VS_HSTK: i64 = 19 // handler stack ptr (HS_ENT per entry)
201const VS_HN: i64 = 20 // active handler count
202const VS_HFLOOR: i64 = 21 // handler floor: a NESTED (re-entry) run may not unwind past handlers below this
203const VS_SLB: i64 = 22 // pre-allocated scratch cell for vm_loop's left operand (sota-bench finding:
204const VS_SRB: i64 = 23 // mmap-per-vm_loop-entry cost 3 syscalls PER CALLBACK re-entry; cells are
205const VS_SOB: i64 = 24 // transient within one instruction, so outer/nested loops share safely)
206const VS_ICACHE: i64 = 25 // INLINE-CACHE array (P4): 2 i64 per instruction site, indexed by pc; shape-keyed.
207const VS_CBBASE: i64 = 26 // native code-buffer base (for resolving a callee's absolute native entry)
208const VS_FNADDR: i64 = 27 // ptr to fnaddr[fidx] = native OFFSET of that JIT'd function's entry (-1 = none)
209const VS_ENTRYSP: i64 = 28 // saved native RSP at blob entry -> a JIT error longjmps here (depth-independent unwind)
210const VS_POOLN: i64 = 29 // # const-pool entries (cc[CC_NP]) -> GC scans pool[0..POOLN*2] as a root (string literals)
211const VS_SZ: i64 = 32
212const HS_ENT: i64 = 4 // [catch pc, sp at BC_TRY, env, fp at BC_TRY]
213
214// P3/P4 property-IC key mode: 0 = SHAPE (default, hits across all objects of a layout), 1 = OBJECT
215// POINTER (legacy per-object; the gate flips this to A/B-measure the shape win). Each compile_run
216// allocates a cold icache, so flipping between runs is clean.
217static jsic_ptr_mode: i64
218func js_ic_ptr_mode(v: i64) -> i64 { jsic_ptr_mode = v; return 0 }
219
220// ESCAPE-AWARE ARENA RECLAMATION: the VM env/closure arena is a bump allocator that historically NEVER
221// freed (call-heavy code like fib(28)+ exhausted 32MB; Octane loops OOM'd). Fix: a call's arena
222// allocations (its activation env + any nested-call envs) are DEAD on return UNLESS a closure captured an
223// env during the call (the only way an arena env escapes = BC_CLOSURE). `vmx_esc` is a monotonic counter
224// bumped on every closure creation; each frame snapshots (arena hp0, esc0) at call entry; on return, if the
225// counter is UNCHANGED (no closure escaped anywhere in the call subtree) we rewind vs[VS_HP] to hp0 -- a
226// conservative-correct stack-discipline reclaim (never frees anything a live closure references). Covers
227// BOTH tiers: interpreter (BC_RETV) + JIT (jit_retv_prep); closures counted in vm_ext BC_CLOSURE (the JIT
228// routes closure creation through vm_ext via jit_ct). [[project-nishi-bytecode-vm-2026-07-06]]
229static vmx_esc: i64
230
231// ===== P6 FOUNDATION: TYPE FEEDBACK (Maglev's "runtime metadata collected during unoptimized execution")
232// The optimizing tier reads this per-bytecode-PC classification to emit type-specialized/unboxed nodes.
233// OPT-IN (fb_enabled off by default) so it adds ZERO baseline overhead until the opt tier consumes it.
234// Class per BINOP PC: 0=cold(unseen) · 1=INT-monomorphic (every observation was VAL_NUM+VAL_NUM = safe to
235// speculate unboxed int) · 2=polymorphic/other (must stay generic). [[reference-js-optimizing-jit-sota-2026-07-08]]
236static fb_enabled: i64
237static fb_table: i64 // *u8 indexed by bytecode PC
238static fb_cap: i64
239func js_fb_enable(on: i64) -> i64 {
240 fb_enabled = on
241 if on == 1 {
242 if fb_cap == 0 { fb_cap = BC_MAGIC_262144; fb_table = (sys_mmap(fb_cap as i64)) as i64 }
243 let t: *u8 = fb_table as *u8
244 var i: i64 = 0
245 while i < fb_cap { t[i] = 0 as u8; i = i + 1 }
246 }
247 return 0
248}
249func js_fb_get(pc: i64) -> i64 { if fb_table == 0 { return 0 } if pc < 0 { return 0 } if pc >= fb_cap { return 0 } let t: *u8 = fb_table as *u8; return t[pc] & 0xff }
250// count PCs classified `cls` -- the gate uses this to prove int-mono vs poly separation without needing PCs.
251func js_fb_count(cls: i64) -> i64 {
252 if fb_table == 0 { return 0 }
253 let t: *u8 = fb_table as *u8
254 var n: i64 = 0
255 var i: i64 = 0
256 while i < fb_cap { if (t[i] & 0xff) == cls { n = n + 1 } i = i + 1 }
257 return n
258}
259// record one BINOP observation at `pc` (operand tags lt,rt). int+int keeps/sets INT-mono; anything else -> poly.
260func fb_record_binop(pc: i64, lt: i64, rt: i64) -> i64 {
261 if fb_enabled == 0 { return 0 }
262 if fb_table == 0 { return 0 }
263 if pc < 0 { return 0 }
264 if pc >= fb_cap { return 0 }
265 let t: *u8 = fb_table as *u8
266 var cls: i64 = 1
267 if lt != VAL_NUM { cls = 2 }
268 if rt != VAL_NUM { cls = 2 }
269 let cur: i64 = t[pc] & 0xff
270 if cur == 0 { t[pc] = cls as u8; return 0 }
271 if cur != cls { t[pc] = 2 as u8 }
272 return 0
273}
274
275// NATIVE FUNCTION CALLS (P5 call rung): default ON. Flip OFF to make CALL/RETV/CLOSURE decline again
276// (whole function-bearing programs run on the interpreter) -- the safety switch + A/B toggle.
277static jit_calls_off: i64
278func js_jit_calls_off(v: i64) -> i64 { jit_calls_off = v; return 0 }
279
280// P4 POLYMORPHIC INLINE CACHES (the V8/JSC mono->poly->megamorphic model): each property site holds a
281// K-WAY set of (shape,offset) entries with MOVE-TO-FRONT (LRU). A monomorphic site (one shape) sits in
282// way 0 -- what the JIT's inline GETPROP reads -- so nothing regresses; a POLYMORPHIC site (a loop over
283// objects of 2..K shapes) hits every way instead of thrashing the slow obj_find scan every iteration.
284// IC layout: POLY_WAYS*2 i64 per instruction; base for pc = pc*POLY_WAYS (pc=instr*2 -> base=instr*8).
285const POLY_WAYS: i64 = 4
286static jsic_poly_ways: i64 // 0/unset -> POLY_WAYS (default); the gate sets 1 (mono) for the A/B measurement
287func js_ic_poly_ways(v: i64) -> i64 { jsic_poly_ways = v; return 0 }
288func ic_ways() -> i64 { if jsic_poly_ways <= 0 { return POLY_WAYS } if jsic_poly_ways > POLY_WAYS { return POLY_WAYS } return jsic_poly_ways }
289// search the K-way set at instruction base `b` for shape `ckey`; on HIT move-to-front + return the cached
290// offset; on MISS return -1. Front (way 0) is the JIT's inline monomorphic guard.
291func ic_poly_get(ic: *i64, b: i64, ckey: i64) -> i64 {
292 let ways: i64 = ic_ways()
293 var w: i64 = 0
294 while w < ways {
295 let e: i64 = b + w * 2
296 if ic[e] == ckey {
297 let off: i64 = ic[e + 1]
298 var k: i64 = w
299 while k > 0 { let d: i64 = b + k * 2; let s: i64 = b + (k - 1) * 2; ic[d] = ic[s]; ic[d + 1] = ic[s + 1]; k = k - 1 }
300 ic[b] = ckey
301 ic[b + 1] = off
302 return off
303 }
304 w = w + 1
305 }
306 return 0 - 1
307}
308// insert (ckey,off) at the front of the K-way set, shifting the rest down (evict the last way).
309func ic_poly_put(ic: *i64, b: i64, ckey: i64, off: i64) -> i64 {
310 let ways: i64 = ic_ways()
311 var k: i64 = ways - 1
312 while k > 0 { let d: i64 = b + k * 2; let s: i64 = b + (k - 1) * 2; ic[d] = ic[s]; ic[d + 1] = ic[s + 1]; k = k - 1 }
313 ic[b] = ckey
314 ic[b + 1] = off
315 return 0
316}
317
318// FATAL = VM-internal capacity/invariant blowout: hard rc=1 halt, NEVER catchable
319// (arena/frame/handler overflow, closure-record corruption). JS-semantic errors go
320// through vmx_halt below, which a try handler CAN catch (tree-walker parity).
321func vmx_fatal(vs: *i64) -> i64 { vs[VS_RC] = 1; vs[VS_RUN] = 0; return 0 }
322// unwind to the top handler with a thrown value: restore its sp/env/fp, push the value,
323// jump to its catch pc. Caller must return immediately after.
324func vmx_unwind(vs: *i64, tt: i64, tp: i64) -> i64 {
325 let hn: i64 = vs[VS_HN]
326 let h: *i64 = (vs[VS_HSTK]) as *i64
327 let b: i64 = (hn - 1) * HS_ENT
328 vs[VS_HN] = hn - 1
329 let sp: i64 = h[b + 1]
330 let stack: *i64 = (vs[VS_STACK]) as *i64
331 let d: i64 = sp * 2
332 stack[d] = tt
333 stack[d + 1] = tp
334 vs[VS_SP] = sp + 1
335 vs[VS_PC] = h[b]
336 vs[VS_ENV] = h[b + 2]
337 vs[VS_FP] = h[b + 3]
338 return 0
339}
340// raise a JS runtime error: if the genv pending-throw channel holds a value (a throw that
341// crossed the VM/native re-entry boundary), unwind with THAT value; else with undefined
342// (== the tree-walker, where a plain CS_ERROR leaves the pending channel empty). A nested
343// re-entry run may only unwind to handlers it registered (above VS_HFLOOR); crossing the
344// floor stops the nested run rc=1 and the error propagates the native-return way instead.
345func vmx_halt(vs: *i64) -> i64 {
346 if js_rt_dbg == 1 { // gated VM-tier raise-site trace: window disassembly around the halt pc,
347 let dcode: *i64 = (vs[VS_CODE]) as *i64 // pool keys resolved for PUSHK/GETPROP/SETPROP/CALLM
348 let dpool: *i64 = (vs[VS_POOL]) as *i64
349 let hpc: i64 = vs[VS_PC]
350 sys_write(2, "VMX-HALT pc=" as *u8, 12)
351 nx_dbg_num(hpc)
352 sys_write(2, "\n" as *u8, 1)
353 var wp: i64 = hpc - 8
354 if wp < 0 { wp = 0 }
355 while wp <= hpc + 2 {
356 sys_write(2, " [" as *u8, 3)
357 nx_dbg_num(wp)
358 sys_write(2, "] op=" as *u8, 5)
359 let wop: i64 = dcode[wp * 2]
360 let warg: i64 = dcode[wp * 2 + 1]
361 nx_dbg_num(wop)
362 sys_write(2, " arg=" as *u8, 5)
363 nx_dbg_num(warg)
364 var kidx: i64 = 0 - 1
365 if wop == BC_PUSHK { kidx = warg }
366 if wop == BC_GETPROP { kidx = warg }
367 if wop == BC_GETPROPQ { kidx = warg }
368 if wop == BC_SETPROP { kidx = warg }
369 if wop == BC_OPROP { kidx = warg }
370 if wop == BC_CALLM { kidx = warg / CM_PACK }
371 if kidx >= 0 {
372 if dpool[kidx * 2] == VAL_STR {
373 sys_write(2, " '" as *u8, 2)
374 let krec: *i64 = (dpool[kidx * 2 + 1]) as *i64
375 sys_write(2, ev_str_bytes(krec), ev_str_len(krec))
376 sys_write(2, "'" as *u8, 1)
377 }
378 }
379 if wp == hpc { sys_write(2, " <-- HALT" as *u8, 11) }
380 sys_write(2, "\n" as *u8, 1)
381 wp = wp + 1
382 }
383 }
384 if vs[VS_HN] > vs[VS_HFLOOR] {
385 let genv: *i64 = (vs[VS_GENV]) as *i64
386 if genv[6] == 1 { genv[6] = 0; return vmx_unwind(vs, genv[7], genv[8]) }
387 return vmx_unwind(vs, VAL_UNDEF, 0)
388 }
389 vs[VS_RC] = 1
390 vs[VS_RUN] = 0
391 return 0
392}
393// bump-allocate `bytes` (8-aligned) from the env arena; 0 = exhausted (caller halts). Fresh = zero-filled.
394func vmx_alloc(vs: *i64, bytes: i64) -> i64 {
395 let hp: i64 = vs[VS_HP]
396 let nb: i64 = (bytes + 7) / 8 * 8
397 if (hp + nb) > VMC_ARENA { return 0 }
398 vs[VS_HP] = hp + nb
399 return vs[VS_ARENA] + hp
400}
401// walk `depth` lexical parents up from the current env.
402func vm_envwalk(env: *i64, depth: i64) -> *i64 {
403 var e: *i64 = env
404 var d: i64 = depth
405 while d > 0 { e = (e[0]) as *i64; d = d - 1 }
406 return e
407}
408// CALL a VM closure: build the activation env from the arena, copy args, push a frame, jump.
409// rec = [VMF_MAGIC, fidx, defenv]; args live at stack[(base+1+i)*2]; base = the fn/recv cell (result lands there).
410func vmx_callfn(vs: *i64, rec: *i64, argc: i64, base: i64, thisflag: i64, ttag: i64, tpay: i64, newobj: i64) -> i64 {
411 let ftab: *i64 = (vs[VS_FTAB]) as *i64
412 let fidx: i64 = rec[1]
413 let fb: i64 = fidx * FT_ENT
414 let entry: i64 = ftab[fb]
415 let np: i64 = ftab[fb + 1]
416 let nv: i64 = ftab[fb + 2]
417 let fl: i64 = ftab[fb + 3]
418 // Rewind mark BEFORE the callee env alloc: on a clean (non-escaping) return the env itself reclaims.
419 // (Was captured AFTER -> every top-level call LEAKED its env record: 80B/call killed a 3-arg 600k-call
420 // loop at the 32MB arena while deep recursion hid it, since a parent's rewind swallows child envs.)
421 let hp0: i64 = vs[VS_HP]
422 let bytes: i64 = (ENVR_HDR + nv * 2) * 8
423 let nep: i64 = vmx_alloc(vs, bytes)
424 if nep == 0 { return vmx_fatal(vs) }
425 let ne: *i64 = nep as *i64
426 ne[0] = rec[2]
427 ne[1] = thisflag
428 ne[2] = ttag
429 ne[3] = tpay
430 let stack: *i64 = (vs[VS_STACK]) as *i64
431 var i: i64 = 0
432 // MISSING args MUST bind undefined explicitly: the arena REUSES rewound bytes, so an unwritten param
433 // slot holds a PREVIOUS call's env garbage (bit jsbn: `BigInteger(a)` left b,c as stale values after the
434 // RNG-init call storm -> `b == null` false -> wrong ctor branch). Fresh-arena runs masked it (zero pages).
435 while i < np {
436 let d: i64 = ENVR_HDR + i * 2
437 if i < argc { let s: i64 = (base + 1 + i) * 2; ne[d] = stack[s]; ne[d + 1] = stack[s + 1] }
438 else { ne[d] = VAL_UNDEF; ne[d + 1] = 0 }
439 i = i + 1
440 }
441 if (fl % 2) == 1 {
442 let ra: *i64 = arr_new()
443 var k: i64 = 0
444 var j: i64 = np
445 while j < argc { let s2: i64 = (base + 1 + j) * 2; let t2: i64 = stack[s2]; let p2: i64 = stack[s2 + 1]; arr_set(ra, k, t2, p2); k = k + 1; j = j + 1 }
446 let dr: i64 = ENVR_HDR + np * 2
447 ne[dr] = VAL_ARRAY
448 ne[dr + 1] = ra as i64
449 }
450 if ((fl / 2) % 2) == 1 { // body references `arguments` -> materialize ALL actual args (array-like)
451 let aslot: i64 = fl / 4
452 let aa: *i64 = arr_new()
453 var k2: i64 = 0
454 while k2 < argc { let s3: i64 = (base + 1 + k2) * 2; arr_set(aa, k2, stack[s3], stack[s3 + 1]); k2 = k2 + 1 }
455 let da: i64 = ENVR_HDR + aslot * 2
456 ne[da] = VAL_ARRAY
457 ne[da + 1] = aa as i64
458 }
459 let fp: i64 = vs[VS_FP]
460 if fp >= VMC_MAXFR { return vmx_fatal(vs) }
461 let fr: *i64 = (vs[VS_FRAMES]) as *i64
462 let b: i64 = fp * FR_ENT
463 fr[b] = vs[VS_PC] + 2
464 fr[b + 1] = vs[VS_ENV]
465 fr[b + 2] = base
466 fr[b + 3] = newobj
467 fr[b + 4] = hp0 // arena mark BEFORE the callee env (rewind target if nothing escapes)
468 fr[b + 5] = vmx_esc // escape counter at call entry
469 vs[VS_FP] = fp + 1
470 vs[VS_ENV] = nep
471 vs[VS_PC] = entry
472 vs[VS_SP] = base
473 return 0
474}
475// Function.prototype.call (mode 0) / .apply (mode 1) on a VM-function receiver at stack[base].
476// Args are stack[base+1 .. base+argc] = [thisArg, ...]. Rearrange in place so the callee's args sit at
477// base+1.. with `this`=arg0, then re-dispatch via vmx_callfn. `arr` (apply's array) is saved before the
478// overwrite, so clobbering stack[base+2] is safe. na args after rearrange.
479func vmx_call_apply(vs: *i64, rec: *i64, base: i64, argc: i64, mode: i64) -> i64 {
480 if rec[0] != VMF_MAGIC { return vmx_halt(vs) }
481 let stack: *i64 = (vs[VS_STACK]) as *i64
482 var ntt: i64 = VAL_UNDEF
483 var ntp: i64 = 0
484 if argc >= 1 { ntt = stack[(base + 1) * 2]; ntp = stack[(base + 1) * 2 + 1] }
485 var na: i64 = 0
486 if mode == 0 {
487 if argc >= 1 { na = argc - 1 }
488 var i: i64 = 0
489 while i < na {
490 let s: i64 = (base + 2 + i) * 2
491 let d: i64 = (base + 1 + i) * 2
492 stack[d] = stack[s]; stack[d + 1] = stack[s + 1]
493 i = i + 1
494 }
495 } else {
496 if argc >= 2 { if stack[(base + 2) * 2] == VAL_ARRAY {
497 let arr: *i64 = (stack[(base + 2) * 2 + 1]) as *i64
498 let al: i64 = arr_len(arr)
499 let eb: *i64 = (vs[VS_T2]) as *i64
500 var i: i64 = 0
501 while i < al {
502 if arr_get(arr, i, eb) == 0 { eb[0] = VAL_UNDEF; eb[1] = 0 }
503 stack[(base + 1 + i) * 2] = eb[0]; stack[(base + 1 + i) * 2 + 1] = eb[1]
504 i = i + 1
505 }
506 na = al
507 } }
508 }
509 return vmx_callfn(vs, rec, na, base, 1, ntt, ntp, 0)
510}
511// invoke a NATIVE builtin with stack args [base+1 .. base+argc]; receiver = stack[base] when hasrecv=1.
512// Result replaces the base cell; sp = base+1. A native rc=1 halts (matches the tree-walker's method-error rc).
513// arg/this/out cells are BUMP-ALLOCATED per call (NOT a shared buffer): a callback native re-enters the
514// VM, whose code can invoke ANOTHER native -- a shared argbuf/scratch would clobber the outer native's
515// callback ptr + receiver mid-iteration. Arena blocks are never reused, so nesting is safe by construction.
516func vmx_native(vs: *i64, bid: i64, base: i64, argc: i64, hasrecv: i64) -> i64 {
517 let stack: *i64 = (vs[VS_STACK]) as *i64
518 if argc > 255 { return vmx_fatal(vs) }
519 let need: i64 = (argc * 2 + 6) * 8
520 let abaddr: i64 = vmx_alloc(vs, need)
521 if abaddr == 0 { return vmx_fatal(vs) }
522 let argbuf: *i64 = abaddr as *i64
523 var i: i64 = 0
524 while i < argc { let s: i64 = (base + 1 + i) * 2; argbuf[i * 2] = stack[s]; argbuf[i * 2 + 1] = stack[s + 1]; i = i + 1 }
525 let t1: *i64 = (abaddr + argc * 16) as *i64
526 let t2: *i64 = (abaddr + argc * 16 + 16) as *i64
527 if hasrecv == 1 { let sb: i64 = base * 2; t1[0] = stack[sb]; t1[1] = stack[sb + 1] } else { t1[0] = VAL_UNDEF; t1[1] = 0 }
528 let ctx: *i64 = (vs[VS_CTX]) as *i64
529 let genv: *i64 = (vs[VS_GENV]) as *i64
530 let rc: i64 = js_native_apply(ctx, genv, bid, t1, argbuf, argc, t2)
531 if rc == 1 { if js_rt_dbg == 1 { sys_write(2, "NATIVE-ERR bid=" as *u8, 15); nx_dbg_num(bid); sys_write(2, " recv-tag=" as *u8, 10); nx_dbg_num(t1[0]); sys_write(2, " argc=" as *u8, 6); nx_dbg_num(argc); sys_write(2, "\n" as *u8, 1) } return vmx_halt(vs) }
532 let db: i64 = base * 2
533 stack[db] = t2[0]
534 stack[db + 1] = t2[1]
535 vs[VS_SP] = base + 1
536 vs[VS_PC] = vs[VS_PC] + 2
537 return 0
538}
539// COLD-HALF dispatcher: every rung-4b opcode. Each op fully updates vs (pc/sp/env/...) before returning.
540func vm_ext(vs: *i64, op: i64, arg: i64) -> i64 {
541 let stack: *i64 = (vs[VS_STACK]) as *i64
542 let pool: *i64 = (vs[VS_POOL]) as *i64
543 let sp: i64 = vs[VS_SP]
544 let pc: i64 = vs[VS_PC]
545 if op == BC_PUSHU { let d: i64 = sp * 2; stack[d] = VAL_UNDEF; stack[d + 1] = 0; vs[VS_SP] = sp + 1; vs[VS_PC] = pc + 2; return 0 }
546 if op == BC_LOADU {
547 let depth: i64 = arg / VM_UPK
548 let slot: i64 = arg % VM_UPK
549 let e: *i64 = vm_envwalk((vs[VS_ENV]) as *i64, depth)
550 let b: i64 = ENVR_HDR + slot * 2
551 let d: i64 = sp * 2
552 stack[d] = e[b]
553 stack[d + 1] = e[b + 1]
554 vs[VS_SP] = sp + 1
555 vs[VS_PC] = pc + 2
556 return 0
557 }
558 if op == BC_STOREU {
559 let depth: i64 = arg / VM_UPK
560 let slot: i64 = arg % VM_UPK
561 let e: *i64 = vm_envwalk((vs[VS_ENV]) as *i64, depth)
562 let b: i64 = ENVR_HDR + slot * 2
563 let s: i64 = (sp - 1) * 2
564 e[b] = stack[s]
565 e[b + 1] = stack[s + 1]
566 vs[VS_SP] = sp - 1
567 vs[VS_PC] = pc + 2
568 return 0
569 }
570 if op == BC_JMPNN {
571 let s: i64 = (sp - 1) * 2
572 let t: i64 = stack[s]
573 vs[VS_SP] = sp - 1
574 var nul: i64 = 0
575 if t == VAL_NULL { nul = 1 }
576 if t == VAL_UNDEF { nul = 1 }
577 if nul == 0 { vs[VS_PC] = arg } else { vs[VS_PC] = pc + 2 }
578 return 0
579 }
580 if op == BC_CLOSURE {
581 vmx_esc = vmx_esc + 1 // a closure captures vs[VS_ENV] -> that env (and its parent
582 // chain) ESCAPES; enclosing calls must NOT rewind past it.
583 let cp: i64 = vmx_alloc(vs, 24)
584 if cp == 0 { return vmx_fatal(vs) }
585 let c: *i64 = cp as *i64
586 c[0] = VMF_MAGIC
587 c[1] = arg
588 c[2] = vs[VS_ENV]
589 let d: i64 = sp * 2
590 stack[d] = VAL_FUNC
591 stack[d + 1] = cp
592 vs[VS_SP] = sp + 1
593 vs[VS_PC] = pc + 2
594 return 0
595 }
596 if op == BC_CLASSMETHOD {
597 // stack [ctor, method] -> install `method` on the ctor's .prototype under `key`; pop method, leave ctor.
598 let key: *i64 = (pool[arg * 2 + 1]) as *i64
599 let vsl: i64 = (sp - 1) * 2 // method (top)
600 let bsl2: i64 = (sp - 2) * 2 // ctor (below)
601 let ctorpay: i64 = stack[bsl2 + 1]
602 let mtag: i64 = stack[vsl]
603 let mpay: i64 = stack[vsl + 1]
604 let proto: i64 = func_prototype(ctorpay) // keyed by closure ptr -> uniform for VM closures
605 if obj_set(proto as *i64, key, mtag, mpay) == 1 { return vmx_halt(vs) }
606 vs[VS_SP] = sp - 1
607 vs[VS_PC] = pc + 2
608 return 0
609 }
610 if op == BC_CALL {
611 let base: i64 = sp - arg - 1
612 let fb: i64 = base * 2
613 let ft: i64 = stack[fb]
614 let fpay: i64 = stack[fb + 1]
615 if ft == VAL_FUNC {
616 let rec: *i64 = fpay as *i64
617 if rec[0] != VMF_MAGIC { return vmx_fatal(vs) }
618 return vmx_callfn(vs, rec, arg, base, 0, 0, 0, 0)
619 }
620 if ft == VAL_NATIVE { return vmx_native(vs, fpay as i64, base, arg, 0) }
621 return vmx_halt(vs)
622 }
623 if op == BC_RETV {
624 let fp: i64 = vs[VS_FP]
625 if fp == 0 {
626 let s: i64 = (sp - 1) * 2
627 let outc: *i64 = (vs[VS_OUT]) as *i64
628 outc[0] = stack[s]
629 outc[1] = stack[s + 1]
630 vs[VS_RUN] = 0
631 return 0
632 }
633 let fr: *i64 = (vs[VS_FRAMES]) as *i64
634 let b: i64 = (fp - 1) * FR_ENT
635 vs[VS_FP] = fp - 1
636 let rs: i64 = (sp - 1) * 2
637 var rt: i64 = stack[rs]
638 var rp: i64 = stack[rs + 1]
639 let nobj: i64 = fr[b + 3]
640 if nobj != 0 { if rt != VAL_OBJECT { rt = VAL_OBJECT; rp = nobj } }
641 let base: i64 = fr[b + 2]
642 let db: i64 = base * 2
643 stack[db] = rt
644 stack[db + 1] = rp
645 vs[VS_SP] = base + 1
646 vs[VS_PC] = fr[b]
647 vs[VS_ENV] = fr[b + 1]
648 if vmx_esc == fr[b + 5] { vs[VS_HP] = fr[b + 4] } // no closure escaped this call -> reclaim its arena
649 // prune try handlers registered INSIDE the frame we just left (a return inside
650 // try{} must not leave a stale handler pointing into a dead frame).
651 var hn: i64 = vs[VS_HN]
652 let hs: *i64 = (vs[VS_HSTK]) as *i64
653 var going: i64 = 1
654 while going == 1 {
655 if hn == 0 { going = 0 }
656 else { if hs[(hn - 1) * HS_ENT + 3] > vs[VS_FP] { hn = hn - 1 } else { going = 0 } }
657 }
658 vs[VS_HN] = hn
659 return 0
660 }
661 if op == BC_CALLM {
662 let kidx: i64 = arg / CM_PACK
663 let argc: i64 = arg % CM_PACK
664 let key: *i64 = (pool[kidx * 2 + 1]) as *i64
665 let base: i64 = sp - argc - 1
666 let rb: i64 = base * 2
667 let rt: i64 = stack[rb]
668 let rp: i64 = stack[rb + 1]
669 // P4 METHOD-NAME IC: at a fixed-key site, bid is a pure function of (receiver tag[, ns]).
670 // Composite tag*65536(+ns) is injective + nonzero for every native-bearing tag; VAL_OBJECT
671 // receivers never store (bid stays 0), so user-method dispatch is unaffected.
672 let icm: *i64 = (vs[VS_ICACHE]) as *i64
673 let icmb: i64 = pc * POLY_WAYS // method sites stay monomorphic (way 0) -- receiver tags are few
674 var ckey: i64 = rt * BC_MAGIC_65536
675 if rt == VAL_GLOBALNS { ckey = ckey + rp }
676 if icm[icmb] == ckey { let cbid: i64 = icm[icmb + 1]; return vmx_native(vs, cbid as i64, base, argc, 1) }
677 var bid: i64 = 0
678 if rt == VAL_STR { bid = ev_native_str(key) }
679 if rt == VAL_ARRAY { bid = ev_native_arr(key) }
680 if rt == VAL_NUM { bid = ev_native_num(key) }
681 if rt == VAL_FLOAT { bid = ev_native_num(key) }
682 if rt == VAL_BOOL { bid = ev_native_num(key) }
683 if rt == VAL_GLOBALNS { bid = ev_native_global(rp, key) }
684 if rt == VAL_PROMISE { bid = ev_native_promise(key) }
685 if rt == VAL_RESPONSE { bid = ev_native_response(key) }
686 if rt == VAL_NATIVE { if rp == BI_STRING_CTOR { if ev_key_is(key, "fromCharCode\x00" as *u8) == 1 { bid = BI_STR_FROMCHARCODE } } } // String.fromCharCode (mirrors js_eval_call)
687 if rt == VAL_GLOBALNS { // user statics (Object.extend) WIN over natives; not IC-cached
688 let t2g: *i64 = (vs[VS_T2]) as *i64
689 if obj_get(js_ns_statics(rp), key, t2g) == 1 {
690 if t2g[0] == VAL_FUNC {
691 let recg: *i64 = (t2g[1]) as *i64
692 if recg[0] != VMF_MAGIC { return vmx_fatal(vs) }
693 return vmx_callfn(vs, recg, argc, base, 1, rt, rp, 0)
694 }
695 if t2g[0] == VAL_NATIVE { return vmx_native(vs, t2g[1], base, argc, 1) }
696 }
697 }
698 if bid != 0 { icm[icmb] = ckey; icm[icmb + 1] = bid; return vmx_native(vs, bid as i64, base, argc, 1) }
699 // USER String/Array.prototype method (`String.prototype.m = fn`) -- native builtins already won
700 // above; this is the tree-tier js_eval_call fallback mirrored onto the fast tier. Not IC-cached
701 // (the composite ckey only encodes native bids). VM-tier run => the stored fn is a VMF closure.
702 if rt == VAL_STR {
703 let tps: *i64 = (vs[VS_T2]) as *i64
704 let fps: i64 = js_userproto_method(VAL_STR, key, tps)
705 if fps != 0 {
706 let recs: *i64 = fps as *i64
707 if recs[0] != VMF_MAGIC { return vmx_halt(vs) }
708 return vmx_callfn(vs, recs, argc, base, 1, rt, rp, 0)
709 }
710 }
711 if rt == VAL_ARRAY {
712 let tpa: *i64 = (vs[VS_T2]) as *i64
713 let fpa: i64 = js_userproto_method(VAL_ARRAY, key, tpa)
714 if fpa != 0 {
715 let reca: *i64 = fpa as *i64
716 if reca[0] != VMF_MAGIC { return vmx_halt(vs) }
717 return vmx_callfn(vs, reca, argc, base, 1, rt, rp, 0)
718 }
719 }
720 if rt == VAL_FUNC { // Function.prototype.call/.apply (NOT IC-cached; receiver payload varies)
721 if ev_key_is(key, "call\x00" as *u8) == 1 { return vmx_call_apply(vs, rp as *i64, base, argc, 0) }
722 if ev_key_is(key, "apply\x00" as *u8) == 1 { return vmx_call_apply(vs, rp as *i64, base, argc, 1) }
723 // own (func_own) or inherited (Object.prototype) method on the function, e.g. Ctor.inheritsFrom
724 let t1f: *i64 = (vs[VS_T1]) as *i64
725 t1f[0] = rt; t1f[1] = rp
726 let t2f: *i64 = (vs[VS_T2]) as *i64
727 if ev_get_prop(t1f, key, t2f) == 0 {
728 if t2f[0] == VAL_FUNC {
729 let recf: *i64 = (t2f[1]) as *i64
730 if recf[0] != VMF_MAGIC { return vmx_halt(vs) }
731 return vmx_callfn(vs, recf, argc, base, 1, rt, rp, 0)
732 }
733 if t2f[0] == VAL_NATIVE { return vmx_native(vs, t2f[1], base, argc, 1) }
734 }
735 }
736 if rt == VAL_OBJECT {
737 let t1: *i64 = (vs[VS_T1]) as *i64
738 t1[0] = rt
739 t1[1] = rp
740 let t2: *i64 = (vs[VS_T2]) as *i64
741 let got: i64 = ev_get_prop(t1, key, t2)
742 if got == 0 {
743 if t2[0] == VAL_FUNC {
744 let rec: *i64 = (t2[1]) as *i64
745 if rec[0] != VMF_MAGIC { return vmx_fatal(vs) }
746 return vmx_callfn(vs, rec, argc, base, 1, rt, rp, 0)
747 }
748 if t2[0] == VAL_NATIVE { return vmx_native(vs, t2[1], base, argc, 1) }
749 }
750 }
751 // VM twin of the tree's CALL-ERR missing-method (gated): which method name on which receiver tag.
752 if js_rt_dbg == 1 { sys_write(2, "CALLM-MISS key='" as *u8, 16); sys_write(2, ev_str_bytes(key), ev_str_len(key)); sys_write(2, "' recv-tag=" as *u8, 11); nx_dbg_num(rt); sys_write(2, " argc=" as *u8, 6); nx_dbg_num(argc); sys_write(2, "\n" as *u8, 1) }
753 return vmx_halt(vs)
754 }
755 if op == BC_GETPROP {
756 let key: *i64 = (pool[arg * 2 + 1]) as *i64
757 let s: i64 = (sp - 1) * 2
758 // P3 SHAPE-KEYED PROPERTY IC: object receiver -> O(1) cached-slot read, validated by the
759 // object's SHAPE so the cache hits across EVERY object of that layout (not just one pointer).
760 if stack[s] == VAL_OBJECT {
761 let ic: *i64 = (vs[VS_ICACHE]) as *i64
762 let opay: i64 = stack[s + 1]
763 let om: *i64 = opay as *i64
764 var ckey: i64 = obj_shape(om)
765 if jsic_ptr_mode == 1 { ckey = opay }
766 let icb: i64 = pc * POLY_WAYS
767 let hw: i64 = ic_poly_get(ic, icb, ckey)
768 if hw >= 0 {
769 let db: *i64 = (om[OBJ_BACK]) as *i64
770 let bh: i64 = hw * OBJ_ENT
771 stack[s] = db[bh + 1]
772 stack[s + 1] = db[bh + 2]
773 vs[VS_PC] = pc + 2
774 return 0
775 }
776 let fi: i64 = obj_find(om, key)
777 if fi >= 0 {
778 ic_poly_put(ic, icb, ckey, fi)
779 let db: *i64 = (om[OBJ_BACK]) as *i64
780 let bm: i64 = fi * OBJ_ENT
781 stack[s] = db[bm + 1]
782 stack[s + 1] = db[bm + 2]
783 vs[VS_PC] = pc + 2
784 return 0
785 }
786 // own miss -> walk the [[Prototype]] chain (inherited data + methods) before undefined
787 let t2p: *i64 = (vs[VS_T2]) as *i64
788 if obj_proto_lookup(om, key, t2p) == 1 {
789 stack[s] = t2p[0]
790 stack[s + 1] = t2p[1]
791 vs[VS_PC] = pc + 2
792 return 0
793 }
794 if obj_get((js_object_prototype()) as *i64, key, t2p) == 1 { // implicit Object.prototype
795 stack[s] = t2p[0]
796 stack[s + 1] = t2p[1]
797 vs[VS_PC] = pc + 2
798 return 0
799 }
800 stack[s] = VAL_UNDEF
801 stack[s + 1] = 0
802 vs[VS_PC] = pc + 2
803 return 0
804 }
805 let t1: *i64 = (vs[VS_T1]) as *i64
806 t1[0] = stack[s]
807 t1[1] = stack[s + 1]
808 let t2: *i64 = (vs[VS_T2]) as *i64
809 if t1[0] == VAL_GLOBALNS { if ev_global_prop(t1[1], key, t2) == 0 { stack[s] = t2[0]; stack[s + 1] = t2[1]; vs[VS_PC] = pc + 2; return 0 } }
810 if t1[0] == VAL_RESPONSE {
811 let r: *i64 = (t1[1]) as *i64
812 if ev_key_is(key, "status\x00" as *u8) == 1 { stack[s] = VAL_NUM; stack[s + 1] = r[0]; vs[VS_PC] = pc + 2; return 0 }
813 if ev_key_is(key, "ok\x00" as *u8) == 1 { var okv: i64 = 0; if r[0] >= 200 { if r[0] < 300 { okv = 1 } } stack[s] = VAL_BOOL; stack[s + 1] = okv; vs[VS_PC] = pc + 2; return 0 }
814 }
815 if ev_get_prop(t1, key, t2) == 1 { return vmx_halt(vs) }
816 stack[s] = t2[0]
817 stack[s + 1] = t2[1]
818 vs[VS_PC] = pc + 2
819 return 0
820 }
821 if op == BC_GETPROPQ {
822 let s: i64 = (sp - 1) * 2
823 let bt: i64 = stack[s]
824 var nul: i64 = 0
825 if bt == VAL_NULL { nul = 1 }
826 if bt == VAL_UNDEF { nul = 1 }
827 if nul == 1 { stack[s] = VAL_UNDEF; stack[s + 1] = 0; vs[VS_PC] = pc + 2; return 0 }
828 return vm_ext(vs, BC_GETPROP, arg)
829 }
830 if op == BC_SETPROP {
831 let key: *i64 = (pool[arg * 2 + 1]) as *i64
832 let vsl: i64 = (sp - 1) * 2
833 let bsl2: i64 = (sp - 2) * 2
834 let t1: *i64 = (vs[VS_T1]) as *i64
835 t1[0] = stack[bsl2]
836 t1[1] = stack[bsl2 + 1]
837 let t2: *i64 = (vs[VS_T2]) as *i64
838 t2[0] = stack[vsl]
839 t2[1] = stack[vsl + 1]
840 // P3 SHAPE-KEYED PROPERTY IC (write side): shape-hit -> in-place O(1) slot write; miss on an
841 // EXISTING prop -> find + cache [shape, offset]; a NEW prop -> obj_set (transitions the shape,
842 // NOT cached: a future same-shape object arrives PRE-transition, so the site re-fills). Mirrors
843 // ev_set_prop's object branch (incl. the cap halt).
844 if t1[0] == VAL_OBJECT {
845 let ic: *i64 = (vs[VS_ICACHE]) as *i64
846 let opay: i64 = t1[1]
847 let om: *i64 = opay as *i64
848 var ckey: i64 = obj_shape(om)
849 if jsic_ptr_mode == 1 { ckey = opay }
850 let icb: i64 = pc * POLY_WAYS
851 var done: i64 = 0
852 let hw: i64 = ic_poly_get(ic, icb, ckey)
853 if hw >= 0 {
854 let db: *i64 = (om[OBJ_BACK]) as *i64
855 let bh: i64 = hw * OBJ_ENT
856 db[bh + 1] = t2[0]
857 db[bh + 2] = t2[1]
858 done = 1
859 }
860 if done == 0 {
861 let fi: i64 = obj_find(om, key)
862 if fi >= 0 {
863 let db: *i64 = (om[OBJ_BACK]) as *i64
864 let bm: i64 = fi * OBJ_ENT
865 db[bm + 1] = t2[0]
866 db[bm + 2] = t2[1]
867 ic_poly_put(ic, icb, ckey, fi)
868 done = 1
869 }
870 }
871 if done == 0 {
872 if obj_set(om, key, t2[0], t2[1]) == 1 { return vmx_halt(vs) }
873 }
874 js_dom_write_hook(t1, key, t2)
875 stack[bsl2] = t2[0]
876 stack[bsl2 + 1] = t2[1]
877 vs[VS_SP] = sp - 1
878 vs[VS_PC] = pc + 2
879 return 0
880 }
881 if ev_set_prop(t1, key, t2[0], t2[1]) == 1 { return vmx_halt(vs) }
882 js_dom_write_hook(t1, key, t2)
883 stack[bsl2] = t2[0]
884 stack[bsl2 + 1] = t2[1]
885 vs[VS_SP] = sp - 1
886 vs[VS_PC] = pc + 2
887 return 0
888 }
889 if op == BC_GETIDX {
890 let ksl: i64 = (sp - 1) * 2
891 let bsl2: i64 = (sp - 2) * 2
892 let t1: *i64 = (vs[VS_T1]) as *i64
893 t1[0] = stack[bsl2]
894 t1[1] = stack[bsl2 + 1]
895 let t2: *i64 = (vs[VS_T2]) as *i64
896 t2[0] = stack[ksl]
897 t2[1] = stack[ksl + 1]
898 if t1[0] == VAL_ARRAY { if t2[0] == VAL_NUM {
899 let a: *i64 = (t1[1]) as *i64
900 let t3: *i64 = (vs[VS_T2]) as *i64
901 if arr_get(a, t2[1], t3) == 1 { stack[bsl2] = t3[0]; stack[bsl2 + 1] = t3[1]; vs[VS_SP] = sp - 1; vs[VS_PC] = pc + 2; return 0 }
902 stack[bsl2] = VAL_UNDEF
903 stack[bsl2 + 1] = 0
904 vs[VS_SP] = sp - 1
905 vs[VS_PC] = pc + 2
906 return 0
907 } }
908 let key: *i64 = ev_key_of_val(t2)
909 let t4: *i64 = (vs[VS_T2]) as *i64
910 if ev_get_prop(t1, key, t4) == 1 { return vmx_halt(vs) }
911 stack[bsl2] = t4[0]
912 stack[bsl2 + 1] = t4[1]
913 vs[VS_SP] = sp - 1
914 vs[VS_PC] = pc + 2
915 return 0
916 }
917 if op == BC_SETIDX {
918 let vsl: i64 = (sp - 1) * 2
919 let ksl: i64 = (sp - 2) * 2
920 let bsl2: i64 = (sp - 3) * 2
921 let t1: *i64 = (vs[VS_T1]) as *i64
922 t1[0] = stack[bsl2]
923 t1[1] = stack[bsl2 + 1]
924 let vt: i64 = stack[vsl]
925 let vp: i64 = stack[vsl + 1]
926 var done: i64 = 0
927 if t1[0] == VAL_ARRAY { if stack[ksl] == VAL_NUM {
928 let a: *i64 = (t1[1]) as *i64
929 let ki: i64 = stack[ksl + 1]
930 if arr_set(a, ki, vt, vp) == 1 { return vmx_halt(vs) }
931 done = 1
932 } }
933 if done == 0 {
934 let t2: *i64 = (vs[VS_T2]) as *i64
935 t2[0] = stack[ksl]
936 t2[1] = stack[ksl + 1]
937 let key: *i64 = ev_key_of_val(t2)
938 if ev_set_prop(t1, key, vt, vp) == 1 { return vmx_halt(vs) }
939 }
940 stack[bsl2] = vt
941 stack[bsl2 + 1] = vp
942 vs[VS_SP] = sp - 2
943 vs[VS_PC] = pc + 2
944 return 0
945 }
946 if op == BC_DELIDX { // delete o.k / o[k]: [.. base key] -> [.. true] (mirrors the tree OP_DELETE)
947 let ksl: i64 = (sp - 1) * 2
948 let bsl2: i64 = (sp - 2) * 2
949 if stack[bsl2] == VAL_OBJECT {
950 let t2: *i64 = (vs[VS_T2]) as *i64
951 t2[0] = stack[ksl]
952 t2[1] = stack[ksl + 1]
953 let key: *i64 = ev_key_of_val(t2)
954 obj_delete((stack[bsl2 + 1]) as *i64, key)
955 }
956 stack[bsl2] = VAL_BOOL
957 stack[bsl2 + 1] = 1
958 vs[VS_SP] = sp - 1
959 vs[VS_PC] = pc + 2
960 return 0
961 }
962 if op == BC_ARR {
963 let a: *i64 = arr_new()
964 let d: i64 = sp * 2
965 stack[d] = VAL_ARRAY
966 stack[d + 1] = a as i64
967 vs[VS_SP] = sp + 1
968 vs[VS_PC] = pc + 2
969 return 0
970 }
971 if op == BC_APUSH {
972 let vsl: i64 = (sp - 1) * 2
973 let asl: i64 = (sp - 2) * 2
974 let a: *i64 = (stack[asl + 1]) as *i64
975 let ln: i64 = arr_len(a)
976 let vt: i64 = stack[vsl]
977 let vp: i64 = stack[vsl + 1]
978 if arr_set(a, ln, vt, vp) == 1 { return vmx_halt(vs) }
979 vs[VS_SP] = sp - 1
980 vs[VS_PC] = pc + 2
981 return 0
982 }
983 if op == BC_SPREAD {
984 let vsl: i64 = (sp - 1) * 2
985 let asl: i64 = (sp - 2) * 2
986 if stack[vsl] == VAL_ARRAY {
987 let a: *i64 = (stack[asl + 1]) as *i64
988 let srca: *i64 = (stack[vsl + 1]) as *i64
989 let sl: i64 = arr_len(srca)
990 let eb: *i64 = (vs[VS_T2]) as *i64
991 var i: i64 = 0
992 while i < sl { arr_get(srca, i, eb); let ln: i64 = arr_len(a); let et: i64 = eb[0]; let ep: i64 = eb[1]; arr_set(a, ln, et, ep); i = i + 1 }
993 }
994 vs[VS_SP] = sp - 1
995 vs[VS_PC] = pc + 2
996 return 0
997 }
998 if op == BC_OBJ {
999 let o: *i64 = obj_new()
1000 let d: i64 = sp * 2
1001 stack[d] = VAL_OBJECT
1002 stack[d + 1] = o as i64
1003 vs[VS_SP] = sp + 1
1004 vs[VS_PC] = pc + 2
1005 return 0
1006 }
1007 if op == BC_OPROP {
1008 let key: *i64 = (pool[arg * 2 + 1]) as *i64
1009 let vsl: i64 = (sp - 1) * 2
1010 let osl: i64 = (sp - 2) * 2
1011 let o: *i64 = (stack[osl + 1]) as *i64
1012 let vt: i64 = stack[vsl]
1013 let vp: i64 = stack[vsl + 1]
1014 if obj_set(o, key, vt, vp) == 1 { return vmx_halt(vs) }
1015 vs[VS_SP] = sp - 1
1016 vs[VS_PC] = pc + 2
1017 return 0
1018 }
1019 if op == BC_THIS {
1020 var e: *i64 = (vs[VS_ENV]) as *i64
1021 var tt: i64 = VAL_OBJECT
1022 var tp: i64 = js_globalthis() // non-strict: unbound this -> globalThis (matches tree-walker)
1023 var going: i64 = 1
1024 while going == 1 {
1025 if (e as i64) == 0 { going = 0 }
1026 else { if e[1] == 1 { tt = e[2]; tp = e[3]; going = 0 } else { e = (e[0]) as *i64 } }
1027 }
1028 let d: i64 = sp * 2
1029 stack[d] = tt
1030 stack[d + 1] = tp
1031 vs[VS_SP] = sp + 1
1032 vs[VS_PC] = pc + 2
1033 return 0
1034 }
1035 if op == BC_NEW {
1036 let base: i64 = sp - arg - 1
1037 let fb: i64 = base * 2
1038 let ft: i64 = stack[fb]
1039 let fpay: i64 = stack[fb + 1]
1040 if ft == VAL_NATIVE {
1041 let t2: *i64 = (vs[VS_T2]) as *i64
1042 let ab: *i64 = (vs[VS_ARGBUF]) as *i64
1043 var ci: i64 = 0
1044 while ci < arg { let sj: i64 = (base + 1 + ci) * 2; ab[ci * 2] = stack[sj]; ab[ci * 2 + 1] = stack[sj + 1]; ci = ci + 1 }
1045 if js_construct_native_args(fpay, ab, arg, t2) == 1 { if js_rt_dbg == 1 { sys_write(2, "NEW-NATIVE-ERR bid=" as *u8, 19); nx_dbg_num(fpay); sys_write(2, " argc=" as *u8, 6); nx_dbg_num(arg); sys_write(2, "\n" as *u8, 1) } return vmx_halt(vs) }
1046 stack[fb] = t2[0]
1047 stack[fb + 1] = t2[1]
1048 vs[VS_SP] = base + 1
1049 vs[VS_PC] = pc + 2
1050 return 0
1051 }
1052 if ft == VAL_FUNC {
1053 let rec: *i64 = fpay as *i64
1054 if rec[0] != VMF_MAGIC { return vmx_fatal(vs) }
1055 let o: *i64 = obj_new()
1056 obj_set_proto(o, func_prototype(fpay)) // link instance -> Foo.prototype BEFORE the ctor runs
1057 return vmx_callfn(vs, rec, arg, base, 1, VAL_OBJECT, o as i64, o as i64)
1058 }
1059 if ft == VAL_GLOBALNS { if fpay == NS_OBJECT { // new Object() -> {}
1060 let o: *i64 = obj_new()
1061 stack[fb] = VAL_OBJECT; stack[fb + 1] = o as i64
1062 vs[VS_SP] = base + 1; vs[VS_PC] = pc + 2
1063 return 0
1064 } }
1065 if js_rt_dbg == 1 { sys_write(2, "NEW-ERR ctor-tag=" as *u8, 17); nx_dbg_num(ft); sys_write(2, " pay=" as *u8, 5); nx_dbg_num(fpay); sys_write(2, " argc=" as *u8, 6); nx_dbg_num(arg); sys_write(2, "\n" as *u8, 1) }
1066 return vmx_halt(vs)
1067 }
1068 if op == BC_ERROR { return vmx_halt(vs) }
1069 if op == BC_KEYS {
1070 let s: i64 = (sp - 1) * 2
1071 if stack[s] != VAL_OBJECT { return vmx_halt(vs) }
1072 let o: *i64 = (stack[s + 1]) as *i64
1073 let kc: i64 = obj_count(o)
1074 let a: *i64 = arr_new()
1075 var i: i64 = 0
1076 var w3: i64 = 0
1077 while i < kc { // skip DELETE tombstones (key==0) -- mirrors the tree's for-in (obj_delete leaves them)
1078 let kr: *i64 = obj_key(o, i)
1079 if (kr as i64) != 0 { arr_set(a, w3, VAL_STR, kr as i64); w3 = w3 + 1 }
1080 i = i + 1
1081 }
1082 stack[s] = VAL_ARRAY
1083 stack[s + 1] = a as i64
1084 vs[VS_PC] = pc + 2
1085 return 0
1086 }
1087 if op == BC_ITERCHK {
1088 let s: i64 = (sp - 1) * 2
1089 if stack[s] != VAL_ARRAY { return vmx_halt(vs) }
1090 vs[VS_PC] = pc + 2
1091 return 0
1092 }
1093 if op == BC_SWAP {
1094 let a: i64 = (sp - 1) * 2
1095 let b: i64 = (sp - 2) * 2
1096 let t0: i64 = stack[a]
1097 let t1v: i64 = stack[a + 1]
1098 stack[a] = stack[b]
1099 stack[a + 1] = stack[b + 1]
1100 stack[b] = t0
1101 stack[b + 1] = t1v
1102 vs[VS_PC] = pc + 2
1103 return 0
1104 }
1105 if op == BC_ROT {
1106 let a: i64 = (sp - 3) * 2
1107 let b: i64 = (sp - 2) * 2
1108 let c: i64 = (sp - 1) * 2
1109 let t0: i64 = stack[a]
1110 let t1v: i64 = stack[a + 1]
1111 stack[a] = stack[b]
1112 stack[a + 1] = stack[b + 1]
1113 stack[b] = stack[c]
1114 stack[b + 1] = stack[c + 1]
1115 stack[c] = t0
1116 stack[c + 1] = t1v
1117 vs[VS_PC] = pc + 2
1118 return 0
1119 }
1120 if op == BC_DUP2 {
1121 let a: i64 = (sp - 2) * 2
1122 let d: i64 = sp * 2
1123 stack[d] = stack[a]
1124 stack[d + 1] = stack[a + 1]
1125 stack[d + 2] = stack[a + 2]
1126 stack[d + 3] = stack[a + 3]
1127 vs[VS_SP] = sp + 2
1128 vs[VS_PC] = pc + 2
1129 return 0
1130 }
1131 if op == BC_IDXSOFT {
1132 let s: i64 = (sp - 1) * 2
1133 var done: i64 = 0
1134 if stack[s] == VAL_ARRAY {
1135 let a: *i64 = (stack[s + 1]) as *i64
1136 let t2: *i64 = (vs[VS_T2]) as *i64
1137 if arr_get(a, arg, t2) == 1 { stack[s] = t2[0]; stack[s + 1] = t2[1]; done = 1 }
1138 }
1139 if done == 0 { stack[s] = VAL_UNDEF; stack[s + 1] = 0 }
1140 vs[VS_PC] = pc + 2
1141 return 0
1142 }
1143 if op == BC_PROPSOFT {
1144 let key: *i64 = (pool[arg * 2 + 1]) as *i64
1145 let s: i64 = (sp - 1) * 2
1146 var done: i64 = 0
1147 if stack[s] == VAL_OBJECT {
1148 let o: *i64 = (stack[s + 1]) as *i64
1149 let t2: *i64 = (vs[VS_T2]) as *i64
1150 if obj_get(o, key, t2) == 1 { stack[s] = t2[0]; stack[s + 1] = t2[1]; done = 1 }
1151 }
1152 if done == 0 { stack[s] = VAL_UNDEF; stack[s + 1] = 0 }
1153 vs[VS_PC] = pc + 2
1154 return 0
1155 }
1156 if op == BC_TRY {
1157 let hn: i64 = vs[VS_HN]
1158 if hn >= VMC_MAXH { return vmx_fatal(vs) }
1159 let h: *i64 = (vs[VS_HSTK]) as *i64
1160 let b: i64 = hn * HS_ENT
1161 h[b] = arg
1162 h[b + 1] = sp
1163 h[b + 2] = vs[VS_ENV]
1164 h[b + 3] = vs[VS_FP]
1165 vs[VS_HN] = hn + 1
1166 vs[VS_PC] = pc + 2
1167 return 0
1168 }
1169 if op == BC_TRYPOP {
1170 let hn: i64 = vs[VS_HN]
1171 if hn > 0 { vs[VS_HN] = hn - 1 }
1172 vs[VS_PC] = pc + 2
1173 return 0
1174 }
1175 if op == BC_THROW {
1176 let s: i64 = (sp - 1) * 2
1177 let tt: i64 = stack[s]
1178 let tp: i64 = stack[s + 1]
1179 vs[VS_SP] = sp - 1
1180 if vs[VS_HN] > vs[VS_HFLOOR] { return vmx_unwind(vs, tt, tp) }
1181 // no handler within this run: park the value on the genv pending channel (an OUTER
1182 // catch -- across a native re-entry boundary, or the tree-walker's -- takes it) + rc 1.
1183 let genv: *i64 = (vs[VS_GENV]) as *i64
1184 ev_throw_set(genv, tt, tp)
1185 vs[VS_RC] = 1
1186 vs[VS_RUN] = 0
1187 return 0
1188 }
1189 return vmx_fatal(vs)
1190}
1191
1192// TAGGED-VALUE stack VM dispatch loop. HOT ladder = the proven rung-2/3/4a ops (loops/arith
1193// stay 100x+); everything else syncs vs and runs through vm_ext. fpfloor: run until the frame
1194// depth returns to the floor (nested re-entry runs pass their entry depth; the TOP run passes
1195// -1 so only BC_RET/halt ends it). Returns rc: 0 ok / 1 error.
1196// GARBAGE COLLECT the object/array/string pool from the VM's PRECISE roots. Called ONLY at a vm_loop
1197// instruction boundary (pc/sp/env committed back to vs), so no C-local holds an untraceable pool pointer.
1198// Non-moving: nothing relocates, so the caller's cached pc/sp/env stay valid across this call. The scan is
1199// conservative (every word treated as a candidate) but made EXACT by the block-start bitmap in gc_is_start:
1200// a false/interior pointer is rejected before any write -> no corruption, only (bounded) over-retention.
1201static gc_dbg_on: i64
1202func gc_set_dbg(v: i64) -> i64 { gc_dbg_on = v; return 0 }
1203func gc_dbg(s: *u8) -> i64 { if gc_dbg_on == 1 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); sys_write(1, "\n" as *u8, 1) } return 0 }
1204// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
1205// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
1206// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
1207// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
1208func gc_dbg_n(v: i64) -> i64 { nxi_out(v); return 0 }
1209func gc_collect_vm(vs: *i64) -> i64 {
1210 if gc_dbg_on == 1 { sys_write(1, "[gc#" as *u8, 4); gc_dbg_n(gc_collection_count()); sys_write(1, " poolhp=" as *u8, 8); gc_dbg_n(nx_pool_hp_bytes()); sys_write(1, " chunks=" as *u8, 8); gc_dbg_n(gc_chunk_count()); sys_write(1, "]\n" as *u8, 2) }
1211 gc_scan_region(vs[VS_STACK], vs[VS_SP] * 2) // operand stack (live values mid-computation)
1212 gc_scan_region(vs[VS_ARENA], vs[VS_HP] / 8) // ALL activation envs + VM closures (dead ones over-retain=safe)
1213 gc_scan_env(vs[VS_GENV]) // global env (env_new): global vars + their name strings
1214 gc_scan_region(vs[VS_POOL], vs[VS_POOLN] * 2) // const pool: string/float literals (BC_PUSHK sources)
1215 gc_scan_region(vs[VS_FRAMES], vs[VS_FP] * FR_ENT) // call frames (newobj = object under construction in `new`)
1216 gc_scan_region(vs[VS_T1], 2) // scratch cells (thisv / results / final out / binop operands)
1217 gc_scan_region(vs[VS_T2], 2)
1218 gc_scan_region(vs[VS_OUT], 2)
1219 gc_scan_region(vs[VS_SLB], 2)
1220 gc_scan_region(vs[VS_SRB], 2)
1221 gc_scan_region(vs[VS_SOB], 2)
1222 gc_scan_region(vs[VS_ARGBUF], 256 * 2) // native-call arg marshalling buffer
1223 gc_scan_region(vs[VS_HSTK], vs[VS_HN] * HS_ENT) // try-handler stack (saved env; ints elsewhere)
1224 gc_mark_globals() // eval-side persistent roots (protos, ns-statics, shape keys)
1225 gc_trace_all() // transitively mark (obj backings, array data, cons ropes)
1226 gc_sweep_all() // reclaim unmarked -> size-class free-lists; clear marks
1227 return 0
1228}
1229// JIT SAFEPOINT: called from native JIT'd code at a loop back-edge (sp/env already synced to vs by the emit).
1230// Runs a collection iff one is due. Non-moving GC => the caller's R15(sp)/R14(env) reload from vs stays valid.
1231// This is what lets a fully-JIT'd allocating loop collect (JIT-to-JIT calls never reach vm_loop's poll).
1232func gc_safepoint(vs: *i64) -> i64 { if gc_poll() == 1 { gc_collect_vm(vs) } return 0 }
1233func vm_loop(vs: *i64, fpfloor: i64) -> i64 {
1234 let code: *i64 = (vs[VS_CODE]) as *i64
1235 let pool: *i64 = (vs[VS_POOL]) as *i64
1236 let stack: *i64 = (vs[VS_STACK]) as *i64
1237 var env: *i64 = (vs[VS_ENV]) as *i64
1238 var pc: i64 = vs[VS_PC]
1239 var sp: i64 = vs[VS_SP]
1240 var running: i64 = 1
1241 let lb: *i64 = (vs[VS_SLB]) as *i64
1242 let rb: *i64 = (vs[VS_SRB]) as *i64
1243 let ob: *i64 = (vs[VS_SOB]) as *i64
1244 let outc: *i64 = (vs[VS_OUT]) as *i64
1245 while running == 1 {
1246 // SAFE-POINT: a GC may run here (all state committed). Write back the register-cached pc/sp/env so the
1247 // collector sees precise roots; non-moving GC leaves them valid, so no reload afterward.
1248 if gc_poll() == 1 { vs[VS_SP] = sp; vs[VS_PC] = pc; vs[VS_ENV] = env as i64; gc_collect_vm(vs) }
1249 let op: i64 = code[pc]
1250 let arg: i64 = code[pc + 1]
1251 if op == BC_PUSH { let d: i64 = sp * 2; stack[d] = VAL_NUM; stack[d + 1] = arg; sp = sp + 1; pc = pc + 2 }
1252 else { if op == BC_PUSHK { let d: i64 = sp * 2; let k: i64 = arg * 2; stack[d] = pool[k]; stack[d + 1] = pool[k + 1]; sp = sp + 1; pc = pc + 2 }
1253 else { if op == BC_LOAD { let d: i64 = sp * 2; let k: i64 = ENVR_HDR + arg * 2; stack[d] = env[k]; stack[d + 1] = env[k + 1]; sp = sp + 1; pc = pc + 2 }
1254 else { if op == BC_STORE { sp = sp - 1; let d: i64 = sp * 2; let k: i64 = ENVR_HDR + arg * 2; env[k] = stack[d]; env[k + 1] = stack[d + 1]; pc = pc + 2 }
1255 else { if op == BC_POP { sp = sp - 1; pc = pc + 2 }
1256 else { if op == BC_BINOP { sp = sp - 1; let ia: i64 = (sp - 1) * 2; let ib: i64 = sp * 2; lb[0] = stack[ia]; lb[1] = stack[ia + 1]; rb[0] = stack[ib]; rb[1] = stack[ib + 1]; if fb_enabled == 1 { fb_record_binop(pc, lb[0], rb[0]) } let brc: i64 = vm_binary(arg, lb, rb, ob); if brc == 1 { vs[VS_PC] = pc; vs[VS_SP] = sp - 1; vmx_halt(vs); pc = vs[VS_PC]; sp = vs[VS_SP]; env = (vs[VS_ENV]) as *i64; if vs[VS_RUN] == 0 { running = 0 } if vs[VS_FP] <= fpfloor { running = 0 } } else { stack[ia] = ob[0]; stack[ia + 1] = ob[1]; pc = pc + 2 } }
1257 else { if op == BC_JMPF { sp = sp - 1; let ic: i64 = sp * 2; lb[0] = stack[ic]; lb[1] = stack[ic + 1]; if ev_truthy(lb) == 0 { pc = arg } else { pc = pc + 2 } }
1258 else { if op == BC_JMP { pc = arg }
1259 else { if op == BC_DUP { let s0: i64 = (sp - 1) * 2; let d: i64 = sp * 2; stack[d] = stack[s0]; stack[d + 1] = stack[s0 + 1]; sp = sp + 1; pc = pc + 2 }
1260 else { if op == BC_JMPT { sp = sp - 1; let ic: i64 = sp * 2; lb[0] = stack[ic]; lb[1] = stack[ic + 1]; if ev_truthy(lb) == 1 { pc = arg } else { pc = pc + 2 } }
1261 else { if op == BC_UNOP { let iu: i64 = (sp - 1) * 2; lb[0] = stack[iu]; lb[1] = stack[iu + 1]; vm_unary(arg, lb, ob); stack[iu] = ob[0]; stack[iu + 1] = ob[1]; pc = pc + 2 }
1262 else { if op == BC_RET { let ir: i64 = (sp - 1) * 2; outc[0] = stack[ir]; outc[1] = stack[ir + 1]; running = 0 }
1263 else {
1264 vs[VS_PC] = pc
1265 vs[VS_SP] = sp
1266 vm_ext(vs, op, arg)
1267 pc = vs[VS_PC]
1268 sp = vs[VS_SP]
1269 env = (vs[VS_ENV]) as *i64
1270 if vs[VS_RUN] == 0 { running = 0 }
1271 if vs[VS_FP] <= fpfloor { running = 0 }
1272 } } } } } } } } } } } }
1273 }
1274 return vs[VS_RC]
1275}
1276// TOP-LEVEL run: floor -1 (frame depth 0 never trips it; BC_RET/halt ends the program).
1277func vm_run(vs: *i64) -> i64 { return vm_loop(vs, 0 - 1) }
1278// RE-ENTRY HOOK (bound via js_vm_bind): invoke a VM closure FROM NATIVE code -- callback
1279// builtins (forEach/map/...), the event-loop drain, promise reactions. Stages fn+args on the
1280// operand stack ABOVE the saved sp, pushes a call frame, runs a NESTED dispatch loop until
1281// that frame returns (fp floor). The nested run may not unwind past its own try handlers
1282// (VS_HFLOOR); an uncaught throw parks its value on the genv pending channel and returns rc=1,
1283// so the OUTER engine (tree-walker or VM) rethrows it natively. State restored on every exit.
1284func vm_call_closure(vsaddr: i64, closaddr: i64, argbufaddr: i64, argc: i64, thisaddr: i64, outaddr: i64) -> i64 {
1285 let vs: *i64 = vsaddr as *i64
1286 let clos: *i64 = closaddr as *i64
1287 let argbuf: *i64 = argbufaddr as *i64
1288 let out: *i64 = outaddr as *i64
1289 let pc0: i64 = vs[VS_PC]
1290 let sp0: i64 = vs[VS_SP]
1291 let env0: i64 = vs[VS_ENV]
1292 let fp0: i64 = vs[VS_FP]
1293 let hf0: i64 = vs[VS_HFLOOR]
1294 let base: i64 = sp0
1295 if (base + 2 + argc) >= VMC_STACKN { ev_set(out, VAL_UNDEF, 0); return 1 }
1296 let stack: *i64 = (vs[VS_STACK]) as *i64
1297 stack[base * 2] = VAL_FUNC
1298 stack[base * 2 + 1] = closaddr
1299 var i: i64 = 0
1300 while i < argc { let d: i64 = (base + 1 + i) * 2; stack[d] = argbuf[i * 2]; stack[d + 1] = argbuf[i * 2 + 1]; i = i + 1 }
1301 vs[VS_SP] = base + 1 + argc
1302 var tf: i64 = 0
1303 var tt: i64 = 0
1304 var tp: i64 = 0
1305 if thisaddr != 0 { let tv: *i64 = thisaddr as *i64; tf = 1; tt = tv[0]; tp = tv[1] }
1306 vs[VS_HFLOOR] = vs[VS_HN]
1307 vmx_callfn(vs, clos, argc, base, tf, tt, tp, 0)
1308 var rc: i64 = 0
1309 if vs[VS_RUN] == 0 { rc = 1 }
1310 if rc == 0 { vm_loop(vs, fp0); if vs[VS_RUN] == 0 { rc = 1 } }
1311 if rc == 0 { out[0] = stack[base * 2]; out[1] = stack[base * 2 + 1] } else { ev_set(out, VAL_UNDEF, 0) }
1312 vs[VS_PC] = pc0
1313 vs[VS_SP] = sp0
1314 vs[VS_ENV] = env0
1315 vs[VS_FP] = fp0
1316 vs[VS_HFLOOR] = hf0
1317 vs[VS_RUN] = 1
1318 vs[VS_RC] = 0
1319 return rc
1320}
1321
1322// ---------------- compiler state (cc) ----------------
1323const CC_CODE: i64 = 0
1324const CC_NP: i64 = 1
1325const CC_POOL: i64 = 2
1326const CC_PCNT: i64 = 3
1327const CC_FTAB: i64 = 4
1328const CC_FCNT: i64 = 5
1329const CC_SCT: i64 = 6 // scope table ptr: per scope [vtab ptr, count, parent idx]
1330const CC_SCUR: i64 = 7 // current scope index
1331const CC_CTX: i64 = 8
1332const CC_BRK: i64 = 9 // break patch list ptr
1333const CC_BRKN: i64 = 10
1334const CC_CON: i64 = 11 // continue patch list ptr
1335const CC_CONN: i64 = 12
1336const CC_BRKD: i64 = 13 // active break-target depth (loops + switches)
1337const CC_COND: i64 = 14 // active continue-target depth (loops)
1338const CC_TMPID: i64 = 15 // hidden-temp uniquifier
1339const CC_INFN: i64 = 16 // >0 = compiling inside a function body (return -> BC_RETV)
1340const CC_FINS: i64 = 17 // finally-stack ptr: one entry PER ACTIVE TRY being compiled (FE_ENT each)
1341const CC_FINN: i64 = 18 // active try count (compile-time nesting)
1342const CC_FINFLOOR: i64 = 19 // function-local floor into the finally stack (returns stop here)
1343const CC_ARGSLOT: i64 = 21 // `arguments` slot in the CURRENT function (-1 = unused); saved/restored per c_function
1344const CC_UNSUP: i64 = 20 // set when the compiler DECLINES a FEATURE it can't compile (template ${},
1345 // unknown node) -- distinct from a runtime BC_ERROR (unresolved name). The
1346 // consumer-swap doc path reads this to FALL BACK to the tree-walker.
1347const CC_SZ: i64 = 24
1348const SC_ENT: i64 = 3
1349const VT_ENT: i64 = 3 // vtab entry [tok start, tok len, name-src ptr]; hidden temps: start=-1000000-id, len 0, src 0.
1350 // per-name src ptr => resolution is CTX-INDEPENDENT: a sub-parsed expr (template ${}, later
1351 // eval/class) in ctx2 resolves enclosing vars stored under the original ctx by comparing BYTES.
1352const FE_ENT: i64 = 3 // finally-stack entry: [finally BLOCK node (-1 none), brkd at entry, cond at entry]
1353
1354func c_emit(cc: *i64, op: i64, arg: i64) -> i64 {
1355 let code: *i64 = (cc[CC_CODE]) as *i64
1356 let i: i64 = cc[CC_NP]
1357 if i >= VMC_CODEI { return i }
1358 code[i * 2] = op
1359 code[i * 2 + 1] = arg
1360 cc[CC_NP] = i + 1
1361 return i
1362}
1363func c_kconst(cc: *i64, tag: i64, pay: i64) -> i64 {
1364 let pool: *i64 = (cc[CC_POOL]) as *i64
1365 let i: i64 = cc[CC_PCNT]
1366 if i >= VMC_POOLN { return 0 }
1367 pool[i * 2] = tag
1368 pool[i * 2 + 1] = pay
1369 cc[CC_PCNT] = i + 1
1370 return i
1371}
1372// pool a property-name STRING RECORD (for GETPROP/SETPROP/CALLM/OPROP/PROPSOFT key operands).
1373func c_keyidx(cc: *i64, keyrec: *i64) -> i64 { return c_kconst(cc, VAL_STR, keyrec as i64) }
1374func c_scope_push(cc: *i64) -> i64 {
1375 let sct: *i64 = (cc[CC_SCT]) as *i64
1376 let cur: i64 = cc[CC_SCUR]
1377 let ni: i64 = cur + 1
1378 if ni >= VMC_MAXSC { return cur }
1379 let vt: *i64 = sys_mmap(VMC_MAXV * VT_ENT * 8) as *i64
1380 let b: i64 = ni * SC_ENT
1381 sct[b] = vt as i64
1382 sct[b + 1] = 0
1383 sct[b + 2] = cur
1384 cc[CC_SCUR] = ni
1385 return ni
1386}
1387func c_scope_pop(cc: *i64) -> i64 {
1388 let sct: *i64 = (cc[CC_SCT]) as *i64
1389 let cur: i64 = cc[CC_SCUR]
1390 let b: i64 = cur * SC_ENT
1391 cc[CC_SCUR] = sct[b + 2]
1392 return 0
1393}
1394// find a name in ONE scope's vtab; -1 absent. Hidden temps (len 0) never match a real name.
1395func c_findslot(cc: *i64, si: i64, ns: i64, nl: i64) -> i64 {
1396 let sct: *i64 = (cc[CC_SCT]) as *i64
1397 let qsrc: *u8 = jp_src((cc[CC_CTX]) as *i64) // the QUERY name lives in the CURRENT ctx's source
1398 let b: i64 = si * SC_ENT
1399 let vt: *i64 = (sct[b]) as *i64
1400 let cnt: i64 = sct[b + 1]
1401 var i: i64 = 0
1402 while i < cnt {
1403 if vt[i * VT_ENT + 1] == nl {
1404 let os: i64 = vt[i * VT_ENT]
1405 let ssrc: *u8 = (vt[i * VT_ENT + 2]) as *u8 // the STORED name lives in ITS OWN ctx's source
1406 var m: i64 = 1
1407 var j: i64 = 0
1408 while j < nl { if (ssrc[os + j] & 0xff) != (qsrc[ns + j] & 0xff) { m = 0; j = nl } else { j = j + 1 } }
1409 if m == 1 { return i }
1410 }
1411 i = i + 1
1412 }
1413 return 0 - 1
1414}
1415// resolve a name up the LEXICAL scope chain; rbox[0]=depth (env hops), rbox[1]=slot. 1 found / 0 not.
1416func c_resolve(cc: *i64, ns: i64, nl: i64, rbox: *i64) -> i64 {
1417 let sct: *i64 = (cc[CC_SCT]) as *i64
1418 var si: i64 = cc[CC_SCUR]
1419 var depth: i64 = 0
1420 while si >= 0 {
1421 let f: i64 = c_findslot(cc, si, ns, nl)
1422 if f >= 0 { rbox[0] = depth; rbox[1] = f; return 1 }
1423 si = sct[si * SC_ENT + 2]
1424 depth = depth + 1
1425 }
1426 return 0
1427}
1428// find-or-create a slot in the CURRENT scope (var semantics: redeclaration = same slot).
1429func c_newslot(cc: *i64, ns: i64, nl: i64) -> i64 {
1430 let cur: i64 = cc[CC_SCUR]
1431 let f: i64 = c_findslot(cc, cur, ns, nl)
1432 if f >= 0 { return f }
1433 let sct: *i64 = (cc[CC_SCT]) as *i64
1434 let b: i64 = cur * SC_ENT
1435 let vt: *i64 = (sct[b]) as *i64
1436 let cnt: i64 = sct[b + 1]
1437 if cnt >= VMC_MAXV { return cnt - 1 }
1438 vt[cnt * VT_ENT] = ns
1439 vt[cnt * VT_ENT + 1] = nl
1440 vt[cnt * VT_ENT + 2] = jp_src((cc[CC_CTX]) as *i64) as i64 // remember which source THIS name lives in
1441 sct[b + 1] = cnt + 1
1442 return cnt
1443}
1444// always-create a hidden compiler temp slot in the current scope (never name-matched).
1445func c_newtmp(cc: *i64) -> i64 {
1446 let id: i64 = cc[CC_TMPID]
1447 cc[CC_TMPID] = id + 1
1448 let cur: i64 = cc[CC_SCUR]
1449 let sct: *i64 = (cc[CC_SCT]) as *i64
1450 let b: i64 = cur * SC_ENT
1451 let vt: *i64 = (sct[b]) as *i64
1452 let cnt: i64 = sct[b + 1]
1453 if cnt >= VMC_MAXV { return cnt - 1 }
1454 vt[cnt * VT_ENT] = 0 - BC_MAGIC_1000000 - id
1455 vt[cnt * VT_ENT + 1] = 0
1456 vt[cnt * VT_ENT + 2] = 0
1457 sct[b + 1] = cnt + 1
1458 return cnt
1459}
1460func c_brkadd(cc: *i64, j: i64) -> i64 { let l: *i64 = (cc[CC_BRK]) as *i64; let n: i64 = cc[CC_BRKN]; if n < VMC_PATCH { l[n] = j; cc[CC_BRKN] = n + 1 } return 0 }
1461func c_conadd(cc: *i64, j: i64) -> i64 { let l: *i64 = (cc[CC_CON]) as *i64; let n: i64 = cc[CC_CONN]; if n < VMC_PATCH { l[n] = j; cc[CC_CONN] = n + 1 } return 0 }
1462// patch every break-list entry added since `from` to jump to `target`; truncate back to `from`.
1463func c_brkpatch(cc: *i64, from: i64, target: i64) -> i64 {
1464 let l: *i64 = (cc[CC_BRK]) as *i64
1465 let code: *i64 = (cc[CC_CODE]) as *i64
1466 var i: i64 = from
1467 let n: i64 = cc[CC_BRKN]
1468 while i < n { let j: i64 = l[i]; code[j * 2 + 1] = target; i = i + 1 }
1469 cc[CC_BRKN] = from
1470 return 0
1471}
1472func c_conpatch(cc: *i64, from: i64, target: i64) -> i64 {
1473 let l: *i64 = (cc[CC_CON]) as *i64
1474 let code: *i64 = (cc[CC_CODE]) as *i64
1475 var i: i64 = from
1476 let n: i64 = cc[CC_CONN]
1477 while i < n { let j: i64 = l[i]; code[j * 2 + 1] = target; i = i + 1 }
1478 cc[CC_CONN] = from
1479 return 0
1480}
1481// EXIT-PATH try unwinding (compile-time): a return/break/continue that jumps OUT of enclosing
1482// try blocks must pop their handlers (BC_TRYPOP) and run their finally bodies, innermost-first.
1483// mode 0 = return (all entries down to the function's floor), 1 = break / 2 = continue (only
1484// entries entered INSIDE the current breakable/continuable -- their recorded depth matches).
1485// Each finally body compiles with the stack MASKED to its own level, so a return inside a
1486// finally re-runs only OUTER finallys (no self-recursion).
1487func c_exit_trys(cc: *i64, mode: i64) -> i64 {
1488 let fins: *i64 = (cc[CC_FINS]) as *i64
1489 var i: i64 = cc[CC_FINN] - 1
1490 var stop: i64 = 0 - 1
1491 if mode == 0 { stop = cc[CC_FINFLOOR] - 1 }
1492 var going: i64 = 1
1493 while going == 1 {
1494 if i <= stop { going = 0 }
1495 else {
1496 let b: i64 = i * FE_ENT
1497 var take: i64 = 1
1498 if mode == 1 { if fins[b + 1] != cc[CC_BRKD] { take = 0 } }
1499 if mode == 2 { if fins[b + 2] != cc[CC_COND] { take = 0 } }
1500 if take == 0 { going = 0 }
1501 else {
1502 c_emit(cc, BC_TRYPOP, 0)
1503 let fnode: i64 = fins[b]
1504 if fnode >= 0 {
1505 let savedn: i64 = cc[CC_FINN]
1506 cc[CC_FINN] = i
1507 c_stmt(cc, fnode)
1508 cc[CC_FINN] = savedn
1509 }
1510 i = i - 1
1511 }
1512 }
1513 }
1514 return 0
1515}
1516// emit a load of resolved (depth,slot): depth 0 -> hot BC_LOAD, else BC_LOADU packed.
1517func c_emitload(cc: *i64, depth: i64, slot: i64) -> i64 {
1518 if depth == 0 { c_emit(cc, BC_LOAD, slot); return 0 }
1519 let packed: i64 = depth * VM_UPK + slot
1520 c_emit(cc, BC_LOADU, packed)
1521 return 0
1522}
1523func c_emitstore(cc: *i64, depth: i64, slot: i64) -> i64 {
1524 if depth == 0 { c_emit(cc, BC_STORE, slot); return 0 }
1525 let packed: i64 = depth * VM_UPK + slot
1526 c_emit(cc, BC_STOREU, packed)
1527 return 0
1528}
1529
1530// NON-STRICT AUTO-GLOBAL (VM-native): find-or-create `name` in the ROOT scope and return
1531// rbox[0]=depth (hops from the CURRENT scope to the root -- the SAME static chain c_resolve walks, so the
1532// runtime env walk lands on genv), rbox[1]=slot. genv is sized AFTER c_program from the root scope's count,
1533// so slots created mid-compile are allocated automatically. Mirrors c_newslot but pinned to the root.
1534func c_rootslot(cc: *i64, ns: i64, nl: i64, rbox: *i64) -> i64 {
1535 let sct: *i64 = (cc[CC_SCT]) as *i64
1536 var si: i64 = cc[CC_SCUR]
1537 var depth: i64 = 0
1538 while sct[si * SC_ENT + 2] >= 0 { si = sct[si * SC_ENT + 2]; depth = depth + 1 }
1539 var f: i64 = c_findslot(cc, si, ns, nl)
1540 if f < 0 {
1541 let b: i64 = si * SC_ENT
1542 let vt: *i64 = (sct[b]) as *i64
1543 let cnt: i64 = sct[b + 1]
1544 if cnt >= VMC_MAXV { f = cnt - 1 } else {
1545 vt[cnt * VT_ENT] = ns
1546 vt[cnt * VT_ENT + 1] = nl
1547 vt[cnt * VT_ENT + 2] = jp_src((cc[CC_CTX]) as *i64) as i64
1548 sct[b + 1] = cnt + 1
1549 f = cnt
1550 }
1551 }
1552 rbox[0] = depth
1553 rbox[1] = f
1554 return 1
1555}
1556
1557// compile the CALL node's argument expressions in order; returns argc (>255 -> emits BC_ERROR).
1558func c_args(cc: *i64, callnode: i64) -> i64 {
1559 let ctx: *i64 = (cc[CC_CTX]) as *i64
1560 let acount: i64 = jp_nc(ctx, callnode)
1561 let argstart: i64 = jp_nb(ctx, callnode)
1562 let children: *i64 = jp_children(ctx)
1563 if acount > 255 { c_emit(cc, BC_ERROR, 0); return 0 }
1564 var i: i64 = 0
1565 while i < acount { let anode: i64 = children[argstart + i]; c_expr(cc, anode); i = i + 1 }
1566 return acount
1567}
1568
1569// flush the current literal run buf[0..bpb[0]) as a PUSHK string const; concat onto the accumulator if one
1570// exists (stb[0]==1). Then mark started + reset the run. Guarantees the whole template is string-coerced.
1571func c_tmpl_flush(cc: *i64, buf: *u8, bpb: *i64, stb: *i64) -> i64 {
1572 let rec: *i64 = je_buf_to_str(buf, bpb[0])
1573 let ki: i64 = c_kconst(cc, VAL_STR, rec as i64)
1574 c_emit(cc, BC_PUSHK, ki)
1575 if stb[0] == 1 { c_emit(cc, BC_BINOP, OP_ADD) }
1576 stb[0] = 1
1577 bpb[0] = 0
1578 return 0
1579}
1580// compile a ${..} interior: SUB-PARSE the bytes into a fresh ctx + compile the expr against the CURRENT scope.
1581// The per-name src ptr (VT_ENT+2) makes cross-ctx variable resolution sound. 0=code emitted / 1=decline.
1582func c_tmpl_expr(cc: *i64, bytes: *u8, blen: i64) -> i64 {
1583 if blen <= 0 { cc[CC_UNSUP] = 1; c_emit(cc, BC_ERROR, 0); return 1 }
1584 let ctxbox: *i64 = sys_mmap(16) as *i64
1585 let prog: i64 = jp_parse_source(bytes, blen, ctxbox)
1586 let ctx2: *i64 = (ctxbox[0]) as *i64
1587 let pst: *i64 = jp_pst(ctx2)
1588 if pst[PST_ERR] == 1 { cc[CC_UNSUP] = 1; c_emit(cc, BC_ERROR, 0); return 1 }
1589 if jp_nkind(ctx2, prog) != ND_PROGRAM { cc[CC_UNSUP] = 1; c_emit(cc, BC_ERROR, 0); return 1 }
1590 if jp_nb(ctx2, prog) < 1 { cc[CC_UNSUP] = 1; c_emit(cc, BC_ERROR, 0); return 1 }
1591 let child: i64 = jp_child_at(ctx2, prog, 0)
1592 var enode: i64 = child
1593 if jp_nkind(ctx2, child) == ND_EXPR_STMT { enode = jp_na(ctx2, child) }
1594 let old: i64 = cc[CC_CTX]
1595 cc[CC_CTX] = ctx2 as i64
1596 c_expr(cc, enode)
1597 cc[CC_CTX] = old
1598 return 0
1599}
1600// template literal. No-${} = a single decoded pooled string const. ${expr} = SUB-PARSE + compile each interior,
1601// concatenated onto a string accumulator (BC_BINOP OP_ADD with a string left uses ev_coerce_str = the SAME
1602// stringification js_eval_template does -> byte-parity with the tree-walker). Rung 4c open CLOSED 2026-07-08.
1603func c_template(cc: *i64, node: i64) -> i64 {
1604 let ctx: *i64 = (cc[CC_CTX]) as *i64
1605 let src: *u8 = jp_src(ctx)
1606 let tstart: i64 = ev_tok_start(ctx, node)
1607 let tlen: i64 = ev_tok_len(ctx, node)
1608 let iend: i64 = tstart + tlen - 1
1609 let cap: i64 = tlen + 16
1610 let buf: *u8 = sys_mmap(cap as i64)
1611 let bpb: *i64 = sys_mmap(8) as *i64; bpb[0] = 0
1612 let stb: *i64 = sys_mmap(8) as *i64; stb[0] = 0
1613 var p: i64 = tstart + 1
1614 while p < iend {
1615 let ch: i64 = src[p] & 0xff
1616 if ch == 92 {
1617 if (p + 1) < iend {
1618 let nx: i64 = src[p + 1] & 0xff
1619 var ec: i64 = nx
1620 if nx == 110 { ec = 10 }
1621 if nx == 116 { ec = 9 }
1622 if nx == 114 { ec = 13 }
1623 buf[bpb[0]] = ec as u8; bpb[0] = bpb[0] + 1; p = p + 2
1624 } else { p = p + 1 }
1625 } else {
1626 var did: i64 = 0
1627 if ch == 36 { if (p + 1) < iend { if (src[p + 1] & 0xff) == 123 {
1628 c_tmpl_flush(cc, buf, bpb, stb)
1629 let estart: i64 = p + 2
1630 var q: i64 = p + 2
1631 var depth: i64 = 1
1632 var g2: i64 = 1
1633 while g2 == 1 {
1634 if q >= iend { g2 = 0 } else {
1635 let c2: i64 = src[q] & 0xff
1636 if c2 == 123 { depth = depth + 1; q = q + 1 } else { if c2 == 125 { depth = depth - 1; q = q + 1; if depth == 0 { g2 = 0 } } else { q = q + 1 } }
1637 }
1638 }
1639 let eend: i64 = q - 1
1640 if c_tmpl_expr(cc, ((src as i64) + estart) as *u8, eend - estart) == 0 { c_emit(cc, BC_BINOP, OP_ADD) }
1641 p = q; did = 1
1642 } } }
1643 if did == 0 { buf[bpb[0]] = ch as u8; bpb[0] = bpb[0] + 1; p = p + 1 }
1644 }
1645 }
1646 c_tmpl_flush(cc, buf, bpb, stb)
1647 return 0
1648}
1649
1650// compile a FUNCTION (decl/expr/arrow): allocate an ftab entry, JMP over the inline body,
1651// compile params (defaults/rest) + hoisted inner decls + body in a FRESH scope, patch sizes.
1652func c_function(cc: *i64, fnode: i64) -> i64 {
1653 let ctx: *i64 = (cc[CC_CTX]) as *i64
1654 let ftab: *i64 = (cc[CC_FTAB]) as *i64
1655 let fidx: i64 = cc[CC_FCNT]
1656 if fidx >= VMC_MAXF { c_emit(cc, BC_ERROR, 0); return 0 }
1657 cc[CC_FCNT] = fidx + 1
1658 let jover: i64 = c_emit(cc, BC_JMP, 0)
1659 let entry: i64 = cc[CC_NP] * 2
1660 c_scope_push(cc)
1661 cc[CC_INFN] = cc[CC_INFN] + 1
1662 let argslot0: i64 = cc[CC_ARGSLOT] // reentrant: each function tracks ITS OWN `arguments` slot
1663 cc[CC_ARGSLOT] = 0 - 1
1664 let finfloor0: i64 = cc[CC_FINFLOOR]
1665 cc[CC_FINFLOOR] = cc[CC_FINN]
1666 let params: i64 = jp_nb(ctx, fnode)
1667 let body: i64 = jp_nc(ctx, fnode)
1668 let pcount: i64 = jp_nb(ctx, params)
1669 var np: i64 = 0
1670 var flags: i64 = 0
1671 var i: i64 = 0
1672 while i < pcount {
1673 let pnode: i64 = jp_child_at(ctx, params, i)
1674 let pns: i64 = ev_tok_start(ctx, pnode)
1675 let pnl: i64 = ev_tok_len(ctx, pnode)
1676 c_newslot(cc, pns, pnl)
1677 if jp_nextra(ctx, pnode) == 1 { flags = 1 } else { np = np + 1 }
1678 i = i + 1
1679 }
1680 // pre-declare every var/function name in the body (var hoisting) so bodies compiled below
1681 // -- including hoisted inner decls -- resolve names declared textually later.
1682 c_predecl(cc, body)
1683 // param DEFAULTS: slot === undefined -> eval default into the slot (mirrors js_call_core order).
1684 var di: i64 = 0
1685 while di < pcount {
1686 let pnode: i64 = jp_child_at(ctx, params, di)
1687 if jp_nextra(ctx, pnode) != 1 {
1688 let defx: i64 = jp_na(ctx, pnode)
1689 if defx >= 0 {
1690 c_emit(cc, BC_LOAD, di)
1691 let ku: i64 = c_kconst(cc, VAL_UNDEF, 0)
1692 c_emit(cc, BC_PUSHK, ku)
1693 c_emit(cc, BC_BINOP, OP_SEQ)
1694 let jf: i64 = c_emit(cc, BC_JMPF, 0)
1695 c_expr(cc, defx)
1696 c_emit(cc, BC_STORE, di)
1697 let code: *i64 = (cc[CC_CODE]) as *i64
1698 code[jf * 2 + 1] = cc[CC_NP] * 2
1699 }
1700 }
1701 di = di + 1
1702 }
1703 // intra-body hoist: DIRECT-child function decls bind before any statement (mirrors js_hoist_body).
1704 c_body(cc, body, 0)
1705 c_emit(cc, BC_PUSHU, 0)
1706 c_emit(cc, BC_RETV, 0)
1707 let sct: *i64 = (cc[CC_SCT]) as *i64
1708 let nvars: i64 = sct[cc[CC_SCUR] * SC_ENT + 1]
1709 // pack `arguments` info: bit0=rest-param, bit1=uses-arguments, bits2+ = the arguments slot index.
1710 if cc[CC_ARGSLOT] >= 0 { flags = flags + 2 + cc[CC_ARGSLOT] * 4 }
1711 let fb: i64 = fidx * FT_ENT
1712 ftab[fb] = entry
1713 ftab[fb + 1] = np
1714 ftab[fb + 2] = nvars
1715 ftab[fb + 3] = flags
1716 cc[CC_ARGSLOT] = argslot0
1717 cc[CC_FINFLOOR] = finfloor0
1718 cc[CC_INFN] = cc[CC_INFN] - 1
1719 c_scope_pop(cc)
1720 let code2: *i64 = (cc[CC_CODE]) as *i64
1721 code2[jover * 2 + 1] = cc[CC_NP] * 2
1722 return fidx
1723}
1724
1725// PRE-DECLARATION pass (real-JS var hoisting): walk a function body/program's STATEMENT structure
1726// and create scope slots for every `var` name + named function decl BEFORE any body compiles, so a
1727// hoisted function's body can reference names declared textually later (fib self-recursion, an inner
1728// fn closing over a later `var`). Does NOT descend into nested function bodies (their own scopes).
1729// (Divergence note: a READ before its `var` line is undefined here (real JS) where the tree-walker
1730// raises ReferenceError -- the VM is the real-JS-correct side.)
1731func c_predecl(cc: *i64, node: i64) -> i64 {
1732 let ctx: *i64 = (cc[CC_CTX]) as *i64
1733 if node < 0 { return 0 }
1734 let k: i64 = jp_nkind(ctx, node)
1735 if k == ND_VAR_DECL {
1736 let name: i64 = jp_na(ctx, node)
1737 let nk: i64 = jp_nkind(ctx, name)
1738 if nk == ND_ARRAY_PAT { let pc2: i64 = jp_nb(ctx, name); var pi: i64 = 0; while pi < pc2 { let nm: i64 = jp_child_at(ctx, name, pi); c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm)); pi = pi + 1 } return 0 }
1739 if nk == ND_OBJ_PAT { let pc2: i64 = jp_nb(ctx, name); var pi: i64 = 0; while pi < pc2 { let nm: i64 = jp_child_at(ctx, name, pi); c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm)); pi = pi + 1 } return 0 }
1740 c_newslot(cc, ev_tok_start(ctx, name), ev_tok_len(ctx, name))
1741 return 0
1742 }
1743 if k == ND_VAR_LIST { let cnt: i64 = jp_nb(ctx, node); var i: i64 = 0; while i < cnt { c_predecl(cc, jp_child_at(ctx, node, i)); i = i + 1 } return 0 }
1744 if k == ND_FUNC_DECL { let nm: i64 = jp_na(ctx, node); if nm >= 0 { c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm)) } return 0 }
1745 if k == ND_EXPR_STMT {
1746 // NON-STRICT AUTO-GLOBAL HOISTING: a plain `x = v` on a bare ident (no `var`) creates a GLOBAL.
1747 // Function bodies are compiled during the hoist pass, so a global assigned LATER in top-level source
1748 // (jsbn: `nValue = "..."`) must be pre-declared into the ROOT scope NOW, else a function referencing
1749 // it fails c_resolve -> BC_ERROR. c_rootslot is idempotent; a name that's also a local just gets a
1750 // harmless spare root slot (the nearer local wins at resolve time).
1751 let e: i64 = jp_na(ctx, node)
1752 if e >= 0 { if jp_nkind(ctx, e) == ND_ASSIGN { if jp_nextra(ctx, e) == 0 {
1753 let lhs: i64 = jp_na(ctx, e)
1754 if jp_nkind(ctx, lhs) == ND_IDENT {
1755 let rbx: *i64 = sys_mmap(16) as *i64
1756 c_rootslot(cc, ev_tok_start(ctx, lhs), ev_tok_len(ctx, lhs), rbx)
1757 }
1758 } } }
1759 return 0
1760 }
1761 if k == ND_CLASS { let nm: i64 = jp_na(ctx, node); if nm >= 0 { c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm)) } return 0 }
1762 if k == ND_BLOCK { let cnt: i64 = jp_nb(ctx, node); var i: i64 = 0; while i < cnt { let ch: i64 = jp_child_at(ctx, node, i); c_predecl(cc, ch); i = i + 1 } return 0 }
1763 if k == ND_PROGRAM { let cnt: i64 = jp_nb(ctx, node); var i: i64 = 0; while i < cnt { let ch: i64 = jp_child_at(ctx, node, i); c_predecl(cc, ch); i = i + 1 } return 0 }
1764 if k == ND_IF { c_predecl(cc, jp_nb(ctx, node)); let e: i64 = jp_nc(ctx, node); if e >= 0 { c_predecl(cc, e) } return 0 }
1765 if k == ND_WHILE { c_predecl(cc, jp_nb(ctx, node)); return 0 }
1766 if k == ND_DOWHILE { c_predecl(cc, jp_nb(ctx, node)); return 0 }
1767 if k == ND_FOR { let init: i64 = jp_na(ctx, node); if init >= 0 { c_predecl(cc, init) } let body: i64 = ev_for_body(ctx, node); c_predecl(cc, body); return 0 }
1768 if k == ND_FOR_OF { c_predecl(cc, jp_na(ctx, node)); c_predecl(cc, jp_nc(ctx, node)); return 0 }
1769 if k == ND_FOR_IN { c_predecl(cc, jp_na(ctx, node)); c_predecl(cc, jp_nc(ctx, node)); return 0 }
1770 if k == ND_SWITCH {
1771 let ccount: i64 = jp_nb(ctx, node)
1772 var i: i64 = 0
1773 while i < ccount {
1774 let cnode: i64 = jp_child_at(ctx, node, i)
1775 let scount: i64 = jp_nb(ctx, cnode)
1776 var si: i64 = 0
1777 while si < scount { let stn: i64 = jp_child_at(ctx, cnode, si); c_predecl(cc, stn); si = si + 1 }
1778 i = i + 1
1779 }
1780 return 0
1781 }
1782 if k == ND_TRY {
1783 c_predecl(cc, jp_na(ctx, node))
1784 let cb: i64 = jp_nc(ctx, node)
1785 if cb >= 0 { c_predecl(cc, cb) }
1786 let fb: i64 = ev_try_finally(ctx, node)
1787 if fb >= 0 { c_predecl(cc, fb) }
1788 return 0
1789 }
1790 return 0
1791}
1792
1793// compile a BLOCK-shaped list node: hoist direct-child named function decls first (when hoistmode=0
1794// this is a FUNCTION BODY or PROGRAM -- hoist applies), then the statements (hoisted decls skipped).
1795// hoistmode=1 -> plain block (no hoist; decls bind at their site, mirroring js_exec_stmt).
1796func c_body(cc: *i64, blk: i64, hoistmode: i64) -> i64 {
1797 let ctx: *i64 = (cc[CC_CTX]) as *i64
1798 let cnt: i64 = jp_nb(ctx, blk)
1799 if hoistmode == 0 {
1800 var h: i64 = 0
1801 while h < cnt {
1802 let ch: i64 = jp_child_at(ctx, blk, h)
1803 if jp_nkind(ctx, ch) == ND_FUNC_DECL {
1804 let nm: i64 = jp_na(ctx, ch)
1805 if nm >= 0 {
1806 let fidx: i64 = c_function(cc, ch)
1807 c_emit(cc, BC_CLOSURE, fidx)
1808 let s: i64 = c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm))
1809 c_emit(cc, BC_STORE, s)
1810 }
1811 }
1812 h = h + 1
1813 }
1814 }
1815 var i: i64 = 0
1816 while i < cnt {
1817 let ch: i64 = jp_child_at(ctx, blk, i)
1818 var skip: i64 = 0
1819 if hoistmode == 0 { if jp_nkind(ctx, ch) == ND_FUNC_DECL { let nm2: i64 = jp_na(ctx, ch); if nm2 >= 0 { skip = 1 } } }
1820 if skip == 0 { c_stmt(cc, ch) }
1821 i = i + 1
1822 }
1823 return 0
1824}
1825
1826// ---------------- expression compiler ----------------
1827func c_expr(cc: *i64, node: i64) -> i64 {
1828 let ctx: *i64 = (cc[CC_CTX]) as *i64
1829 let k: i64 = jp_nkind(ctx, node)
1830 if k == ND_NUMBER { let o: *i64 = sys_mmap(16) as *i64; ev_num_node(ctx, node, o); if o[0] == VAL_NUM { c_emit(cc, BC_PUSH, o[1]) } else { let ki: i64 = c_kconst(cc, o[0], o[1]); c_emit(cc, BC_PUSHK, ki) } return 0 }
1831 if k == ND_STRING { let o: *i64 = sys_mmap(16) as *i64; ev_str_from_lit(ctx, node, o); let ki: i64 = c_kconst(cc, o[0], o[1]); c_emit(cc, BC_PUSHK, ki); return 0 }
1832 if k == ND_BOOL { let bv: i64 = ev_b2(jp_nextra(ctx, node)); let ki: i64 = c_kconst(cc, VAL_BOOL, bv); c_emit(cc, BC_PUSHK, ki); return 0 }
1833 if k == ND_NULL { let ki: i64 = c_kconst(cc, VAL_NULL, 0); c_emit(cc, BC_PUSHK, ki); return 0 }
1834 if k == ND_TEMPLATE { return c_template(cc, node) }
1835 if k == ND_THIS { c_emit(cc, BC_THIS, 0); return 0 }
1836 if k == ND_IDENT { return c_ident(cc, node, 0) }
1837 if k == ND_SEQ { // comma operator: eval each child, POP all but the last (which is the value)
1838 let sn: i64 = jp_nb(ctx, node)
1839 var si: i64 = 0
1840 while si < sn {
1841 c_expr(cc, jp_child_at(ctx, node, si))
1842 if si < sn - 1 { c_emit(cc, BC_POP, 0) }
1843 si = si + 1
1844 }
1845 return 0
1846 }
1847 if k == ND_UNARY {
1848 let uop: i64 = jp_nextra(ctx, node)
1849 let opnd: i64 = jp_na(ctx, node)
1850 // `delete o.k` / `delete o[k]` -> base + key on the stack, BC_DELIDX removes and leaves true
1851 // (VM-NATIVE now: this was the LAST decline forcing GC-heavy bundles like EarleyBoyer/Splay onto
1852 // the tree tier, which cannot collect). Non-reference operand -> true, NO eval (tree parity).
1853 if uop == OP_DELETE {
1854 let dk2: i64 = jp_nkind(ctx, opnd)
1855 if dk2 == ND_MEMBER {
1856 c_expr(cc, jp_na(ctx, opnd)) // [base]
1857 let dkey: *i64 = ev_prop_key(ctx, jp_nb(ctx, opnd))
1858 let dki: i64 = c_kconst(cc, VAL_STR, dkey as i64)
1859 c_emit(cc, BC_PUSHK, dki) // [base key]
1860 c_emit(cc, BC_DELIDX, 0) // [true]
1861 return 0
1862 }
1863 if dk2 == ND_INDEX {
1864 c_expr(cc, jp_na(ctx, opnd)) // [base]
1865 c_expr(cc, jp_nb(ctx, opnd)) // [base key]
1866 c_emit(cc, BC_DELIDX, 0) // [true]
1867 return 0
1868 }
1869 let dti: i64 = c_kconst(cc, VAL_BOOL, 1)
1870 c_emit(cc, BC_PUSHK, dti)
1871 return 0
1872 }
1873 // typeof UNDECLARED-ident is "undefined", not a ReferenceError (mirror js_eval's special case).
1874 if uop == OP_TYPEOF { if jp_nkind(ctx, opnd) == ND_IDENT { if c_ident_resolves(cc, opnd) == 0 { c_emit(cc, BC_PUSHU, 0); c_emit(cc, BC_UNOP, OP_TYPEOF); return 0 } } }
1875 c_expr(cc, opnd)
1876 c_emit(cc, BC_UNOP, uop)
1877 return 0
1878 }
1879 if k == ND_BINARY {
1880 let bop: i64 = jp_nextra(ctx, node)
1881 if bop == OP_AND {
1882 c_expr(cc, jp_na(ctx, node))
1883 c_emit(cc, BC_DUP, 0)
1884 let jf: i64 = c_emit(cc, BC_JMPF, 0)
1885 c_emit(cc, BC_POP, 0)
1886 c_expr(cc, jp_nb(ctx, node))
1887 let code: *i64 = (cc[CC_CODE]) as *i64
1888 code[jf * 2 + 1] = cc[CC_NP] * 2
1889 return 0
1890 }
1891 if bop == OP_OR {
1892 c_expr(cc, jp_na(ctx, node))
1893 c_emit(cc, BC_DUP, 0)
1894 let jt: i64 = c_emit(cc, BC_JMPT, 0)
1895 c_emit(cc, BC_POP, 0)
1896 c_expr(cc, jp_nb(ctx, node))
1897 let code: *i64 = (cc[CC_CODE]) as *i64
1898 code[jt * 2 + 1] = cc[CC_NP] * 2
1899 return 0
1900 }
1901 if bop == OP_NULLISH {
1902 c_expr(cc, jp_na(ctx, node))
1903 c_emit(cc, BC_DUP, 0)
1904 let jn: i64 = c_emit(cc, BC_JMPNN, 0)
1905 c_emit(cc, BC_POP, 0)
1906 c_expr(cc, jp_nb(ctx, node))
1907 let code: *i64 = (cc[CC_CODE]) as *i64
1908 code[jn * 2 + 1] = cc[CC_NP] * 2
1909 return 0
1910 }
1911 c_expr(cc, jp_na(ctx, node))
1912 c_expr(cc, jp_nb(ctx, node))
1913 c_emit(cc, BC_BINOP, bop)
1914 return 0
1915 }
1916 if k == ND_TERNARY {
1917 c_expr(cc, jp_na(ctx, node))
1918 let jf: i64 = c_emit(cc, BC_JMPF, 0)
1919 c_expr(cc, jp_nb(ctx, node))
1920 let je: i64 = c_emit(cc, BC_JMP, 0)
1921 let code: *i64 = (cc[CC_CODE]) as *i64
1922 code[jf * 2 + 1] = cc[CC_NP] * 2
1923 c_expr(cc, jp_nc(ctx, node))
1924 code[je * 2 + 1] = cc[CC_NP] * 2
1925 return 0
1926 }
1927 if k == ND_ASSIGN { return c_assign(cc, node) }
1928 if k == ND_UPDATE { return c_update(cc, node) }
1929 if k == ND_MEMBER {
1930 c_expr(cc, jp_na(ctx, node))
1931 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, node))
1932 let ki: i64 = c_keyidx(cc, key)
1933 if jp_nextra(ctx, node) == 1 { c_emit(cc, BC_GETPROPQ, ki) } else { c_emit(cc, BC_GETPROP, ki) }
1934 return 0
1935 }
1936 if k == ND_INDEX {
1937 c_expr(cc, jp_na(ctx, node))
1938 c_expr(cc, jp_nb(ctx, node))
1939 c_emit(cc, BC_GETIDX, 0)
1940 return 0
1941 }
1942 if k == ND_CALL {
1943 let callee: i64 = jp_na(ctx, node)
1944 if jp_nkind(ctx, callee) == ND_MEMBER {
1945 c_expr(cc, jp_na(ctx, callee))
1946 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, callee))
1947 let ki: i64 = c_keyidx(cc, key)
1948 let argc: i64 = c_args(cc, node)
1949 let packed: i64 = ki * CM_PACK + argc
1950 c_emit(cc, BC_CALLM, packed)
1951 return 0
1952 }
1953 c_expr(cc, callee)
1954 let argc2: i64 = c_args(cc, node)
1955 c_emit(cc, BC_CALL, argc2)
1956 return 0
1957 }
1958 if k == ND_NEW {
1959 if jp_nextra(ctx, node) == 1 {
1960 c_expr(cc, jp_na(ctx, node))
1961 c_emit(cc, BC_NEW, 0)
1962 return 0
1963 }
1964 let cn: i64 = jp_na(ctx, node)
1965 c_expr(cc, jp_na(ctx, cn))
1966 let argc: i64 = c_args(cc, cn)
1967 c_emit(cc, BC_NEW, argc)
1968 return 0
1969 }
1970 if k == ND_FUNC_DECL {
1971 let fidx: i64 = c_function(cc, node)
1972 c_emit(cc, BC_CLOSURE, fidx)
1973 return 0
1974 }
1975 if k == ND_CLASS {
1976 // `extends` (superclass in the extra slot) needs prototype-chaining + `@super`/super() semantics
1977 // the VM doesn't implement -> DECLINE so the consumer-swap falls back to the tree-walker (which
1978 // does). A plain class (no extends) compiles natively below. (`super` nodes decline via the
1979 // unrecognized-kind fallthrough at the end of c_expr.)
1980 if jp_nextra(ctx, node) >= 0 { cc[CC_UNSUP] = 1; c_emit(cc, BC_ERROR, 0); return 1 }
1981 // ctor closure -> DUP -> store Name; then per method: closure -> BC_CLASSMETHOD (install on prototype).
1982 // leaves [ctor] as the expression value (a class STATEMENT pops it in c_stmt).
1983 let cfidx: i64 = c_function(cc, jp_nb(ctx, node))
1984 c_emit(cc, BC_CLOSURE, cfidx)
1985 let nameid: i64 = jp_na(ctx, node)
1986 let rbox: *i64 = sys_mmap(16) as *i64
1987 if c_resolve(cc, ev_tok_start(ctx, nameid), ev_tok_len(ctx, nameid), rbox) == 0 { c_emit(cc, BC_ERROR, 0); return 1 }
1988 c_emit(cc, BC_DUP, 0)
1989 c_emitstore(cc, rbox[0], rbox[1])
1990 let methods: i64 = jp_nc(ctx, node)
1991 let mcount: i64 = jp_nb(ctx, methods)
1992 var mi: i64 = 0
1993 while mi < mcount {
1994 let mnode: i64 = jp_child_at(ctx, methods, mi)
1995 let mki: i64 = c_keyidx(cc, ev_prop_key(ctx, jp_na(ctx, mnode)))
1996 let mfidx: i64 = c_function(cc, mnode)
1997 c_emit(cc, BC_CLOSURE, mfidx)
1998 c_emit(cc, BC_CLASSMETHOD, mki)
1999 mi = mi + 1
2000 }
2001 return 0
2002 }
2003 if k == ND_OBJECT {
2004 c_emit(cc, BC_OBJ, 0)
2005 let cnt: i64 = jp_nb(ctx, node)
2006 var i: i64 = 0
2007 while i < cnt {
2008 let prop: i64 = jp_child_at(ctx, node, i)
2009 let key: *i64 = ev_prop_key(ctx, prop)
2010 let ki: i64 = c_keyidx(cc, key)
2011 c_expr(cc, jp_na(ctx, prop))
2012 c_emit(cc, BC_OPROP, ki)
2013 i = i + 1
2014 }
2015 return 0
2016 }
2017 if k == ND_ARRAY {
2018 c_emit(cc, BC_ARR, 0)
2019 let cnt: i64 = jp_nb(ctx, node)
2020 var i: i64 = 0
2021 while i < cnt {
2022 let el: i64 = jp_child_at(ctx, node, i)
2023 if jp_nkind(ctx, el) == ND_SPREAD { c_expr(cc, jp_na(ctx, el)); c_emit(cc, BC_SPREAD, 0) }
2024 else { c_expr(cc, el); c_emit(cc, BC_APUSH, 0) }
2025 i = i + 1
2026 }
2027 return 0
2028 }
2029 // unrecognized expression kind -> FEATURE decline (a node kind the VM compiler doesn't handle but the
2030 // tree-walker might; the consumer-swap falls back to it rather than mis-erroring).
2031 cc[CC_UNSUP] = 1
2032 c_emit(cc, BC_ERROR, 0)
2033 return 1
2034}
2035
2036// does this IDENT resolve to ANYTHING (scope slot / `undefined` / global ns / global native)?
2037func c_ident_resolves(cc: *i64, node: i64) -> i64 {
2038 let ctx: *i64 = (cc[CC_CTX]) as *i64
2039 let src: *u8 = jp_src(ctx)
2040 let ns: i64 = ev_tok_start(ctx, node)
2041 let nl: i64 = ev_tok_len(ctx, node)
2042 let rbox: *i64 = sys_mmap(16) as *i64
2043 if c_resolve(cc, ns, nl, rbox) == 1 { return 1 }
2044 if js_lexeme_eq(src, ns, nl, "undefined\x00" as *u8) == 1 { return 1 }
2045 if ev_global_ns(src, ns, nl) != 0 { return 1 }
2046 if ev_global_native(src, ns, nl) != 0 { return 1 }
2047 return 0
2048}
2049// IDENT in LOAD position (mirrors js_eval's ND_IDENT ladder: scope -> undefined -> ns -> native -> ReferenceError).
2050func c_ident(cc: *i64, node: i64, unused: i64) -> i64 {
2051 let ctx: *i64 = (cc[CC_CTX]) as *i64
2052 let src: *u8 = jp_src(ctx)
2053 let ns: i64 = ev_tok_start(ctx, node)
2054 let nl: i64 = ev_tok_len(ctx, node)
2055 let rbox: *i64 = sys_mmap(16) as *i64
2056 if c_resolve(cc, ns, nl, rbox) == 1 { let d: i64 = rbox[0]; let s: i64 = rbox[1]; c_emitload(cc, d, s); return 0 }
2057 // `arguments` inside a function: LAZILY create the slot on first reference (zero per-call cost when a
2058 // function never uses it) + record it in CC_ARGSLOT so c_function packs it into the FT flags and
2059 // vmx_callfn materializes the args array. Blocks don't push scopes, so depth is always 0 here.
2060 if cc[CC_INFN] > 0 { if js_lexeme_eq(src, ns, nl, "arguments\x00" as *u8) == 1 {
2061 if cc[CC_ARGSLOT] < 0 { cc[CC_ARGSLOT] = c_newslot(cc, ns, nl) }
2062 c_emitload(cc, 0, cc[CC_ARGSLOT])
2063 return 0
2064 } }
2065 if js_lexeme_eq(src, ns, nl, "undefined\x00" as *u8) == 1 { c_emit(cc, BC_PUSHU, 0); return 0 }
2066 let gns: i64 = ev_global_ns(src, ns, nl)
2067 if gns != 0 { let ki: i64 = c_kconst(cc, VAL_GLOBALNS, gns); c_emit(cc, BC_PUSHK, ki); return 0 }
2068 let gnat: i64 = ev_global_native(src, ns, nl)
2069 if gnat != 0 { let ki: i64 = c_kconst(cc, VAL_NATIVE, gnat); c_emit(cc, BC_PUSHK, ki); return 0 }
2070 c_emit(cc, BC_ERROR, 0)
2071 return 1
2072}
2073
2074// ++x/--x/x++/x-- (ND_UPDATE extra: 1=++pre 2=--pre 3=++post 4=--post). Same fold as the
2075// tree-walker (cur op 1 through the numeric/float ladder); member/index re-evaluate the base
2076// for the write (compound-assign order). Prefix leaves the NEW value, postfix the OLD.
2077func c_update(cc: *i64, node: i64) -> i64 {
2078 let ctx: *i64 = (cc[CC_CTX]) as *i64
2079 let tgt: i64 = jp_na(ctx, node)
2080 let mode: i64 = jp_nextra(ctx, node)
2081 var uop: i64 = OP_ADD
2082 if mode == 2 { uop = OP_SUB }
2083 if mode == 4 { uop = OP_SUB }
2084 var post: i64 = 0
2085 if mode >= 3 { post = 1 }
2086 let lk: i64 = jp_nkind(ctx, tgt)
2087 if lk == ND_IDENT {
2088 let ns: i64 = ev_tok_start(ctx, tgt)
2089 let nl: i64 = ev_tok_len(ctx, tgt)
2090 let rbox: *i64 = sys_mmap(16) as *i64
2091 if c_resolve(cc, ns, nl, rbox) == 0 { c_emit(cc, BC_ERROR, 0); return 1 }
2092 let d: i64 = rbox[0]
2093 let s: i64 = rbox[1]
2094 c_emitload(cc, d, s)
2095 if post == 1 { c_emit(cc, BC_DUP, 0) }
2096 c_emit(cc, BC_PUSH, 1)
2097 c_emit(cc, BC_BINOP, uop)
2098 if post == 0 { c_emit(cc, BC_DUP, 0) }
2099 c_emitstore(cc, d, s)
2100 return 0
2101 }
2102 if lk == ND_MEMBER {
2103 // SINGLE-EVAL: base evaluated ONCE, read+write both go through the DUP'd base (was: base re-eval'd
2104 // for the write -> `f().x++` ran f() twice). Prefix leaves NEW, postfix leaves OLD.
2105 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, tgt))
2106 let ki: i64 = c_keyidx(cc, key)
2107 c_expr(cc, jp_na(ctx, tgt)) // [b] base ONCE
2108 c_emit(cc, BC_DUP, 0) // [b b]
2109 c_emit(cc, BC_GETPROP, ki) // [b cur]
2110 if post == 1 {
2111 c_emit(cc, BC_DUP, 0) // [b cur cur]
2112 c_emit(cc, BC_PUSH, 1) // [b cur cur 1]
2113 c_emit(cc, BC_BINOP, uop) // [b cur nw]
2114 c_emit(cc, BC_ROT, 0) // [cur nw b]
2115 c_emit(cc, BC_SWAP, 0) // [cur b nw]
2116 c_emit(cc, BC_SETPROP, ki) // [cur nw]
2117 c_emit(cc, BC_POP, 0) // [cur] result = OLD
2118 } else {
2119 c_emit(cc, BC_PUSH, 1) // [b cur 1]
2120 c_emit(cc, BC_BINOP, uop) // [b nw]
2121 c_emit(cc, BC_SETPROP, ki) // [nw] result = NEW
2122 }
2123 return 0
2124 }
2125 if lk == ND_INDEX {
2126 // SINGLE-EVAL: base+index evaluated ONCE (DUP2 preserves them across the read). Postfix re-READS the
2127 // element (a pure array/obj read of already-evaluated VALUES -- NOT an expr re-eval) to keep OLD while
2128 // storing NEW. Was: base+index re-eval'd for the write -> `a[i++]++` ran i++ twice.
2129 c_expr(cc, jp_na(ctx, tgt)) // [b] base ONCE
2130 c_expr(cc, jp_nb(ctx, tgt)) // [b k] index ONCE
2131 if post == 1 {
2132 c_emit(cc, BC_DUP2, 0) // [b k b k]
2133 c_emit(cc, BC_GETIDX, 0) // [b k old]
2134 c_emit(cc, BC_ROT, 0) // [k old b]
2135 c_emit(cc, BC_ROT, 0) // [old b k]
2136 c_emit(cc, BC_DUP2, 0) // [old b k b k]
2137 c_emit(cc, BC_GETIDX, 0) // [old b k old2]
2138 c_emit(cc, BC_PUSH, 1) // [old b k old2 1]
2139 c_emit(cc, BC_BINOP, uop) // [old b k nw]
2140 c_emit(cc, BC_SETIDX, 0) // [old nw]
2141 c_emit(cc, BC_POP, 0) // [old] result = OLD
2142 } else {
2143 c_emit(cc, BC_DUP2, 0) // [b k b k]
2144 c_emit(cc, BC_GETIDX, 0) // [b k cur]
2145 c_emit(cc, BC_PUSH, 1) // [b k cur 1]
2146 c_emit(cc, BC_BINOP, uop) // [b k nw]
2147 c_emit(cc, BC_SETIDX, 0) // [nw] result = NEW
2148 }
2149 return 0
2150 }
2151 c_emit(cc, BC_ERROR, 0)
2152 return 1
2153}
2154
2155// assignment (plain + compound, ident/member/index targets). Tree-walker ORDER preserved:
2156// JS-SPEC ORDER (= V8; was rhs-first, which broke Octane NavierStokes' `x[cur] = (... x[++cur] ...)`
2157// Gauss-Seidel idiom): the target's base (and index) evaluate ONCE, BEFORE the rhs; a compound reads
2158// the current value through that same base/index pair (DUP / DUP2), folds AFTER the rhs, and stores
2159// back to the SAME slot (so `a[++i] -= e` increments i exactly once).
2160func c_assign(cc: *i64, node: i64) -> i64 {
2161 let ctx: *i64 = (cc[CC_CTX]) as *i64
2162 let lhs: i64 = jp_na(ctx, node)
2163 let aop: i64 = jp_nextra(ctx, node)
2164 let lk: i64 = jp_nkind(ctx, lhs)
2165 if lk == ND_IDENT {
2166 let ns: i64 = ev_tok_start(ctx, lhs)
2167 let nl: i64 = ev_tok_len(ctx, lhs)
2168 let rbox: *i64 = sys_mmap(16) as *i64
2169 if c_resolve(cc, ns, nl, rbox) == 0 {
2170 // Undeclared IDENT target. Plain `x = v` (aop==0) is a NON-STRICT auto-global (real JS creates a
2171 // global binding): VM-NATIVE via c_rootslot (root-scope slot + depth; genv sized post-compile).
2172 // Compound `x += v` on an undeclared name genuinely throws ReferenceError -> hard BC_ERROR.
2173 if aop != 0 { c_emit(cc, BC_ERROR, 0); return 1 }
2174 c_rootslot(cc, ns, nl, rbox)
2175 }
2176 let d: i64 = rbox[0]
2177 let s: i64 = rbox[1]
2178 if aop != 0 { c_emitload(cc, d, s) } // read current BEFORE rhs (spec)
2179 c_expr(cc, jp_nb(ctx, node))
2180 if aop != 0 { c_emit(cc, BC_BINOP, aop) } // [cur rhs] -> folded
2181 c_emit(cc, BC_DUP, 0)
2182 c_emitstore(cc, d, s)
2183 return 0
2184 }
2185 if lk == ND_MEMBER {
2186 let key: *i64 = ev_prop_key(ctx, jp_nb(ctx, lhs))
2187 let ki: i64 = c_keyidx(cc, key)
2188 c_expr(cc, jp_na(ctx, lhs)) // base ONCE, before rhs
2189 if aop != 0 {
2190 c_emit(cc, BC_DUP, 0) // [b b]
2191 c_emit(cc, BC_GETPROP, ki) // [b cur]
2192 c_expr(cc, jp_nb(ctx, node)) // [b cur rhs]
2193 c_emit(cc, BC_BINOP, aop) // [b folded]
2194 } else {
2195 c_expr(cc, jp_nb(ctx, node)) // [b rhs]
2196 }
2197 c_emit(cc, BC_SETPROP, ki) // [val]
2198 return 0
2199 }
2200 if lk == ND_INDEX {
2201 c_expr(cc, jp_na(ctx, lhs)) // base ONCE
2202 c_expr(cc, jp_nb(ctx, lhs)) // index ONCE, before rhs
2203 if aop != 0 {
2204 c_emit(cc, BC_DUP2, 0) // [b k b k]
2205 c_emit(cc, BC_GETIDX, 0) // [b k cur]
2206 c_expr(cc, jp_nb(ctx, node)) // [b k cur rhs]
2207 c_emit(cc, BC_BINOP, aop) // [b k folded]
2208 } else {
2209 c_expr(cc, jp_nb(ctx, node)) // [b k rhs]
2210 }
2211 c_emit(cc, BC_SETIDX, 0) // [val]
2212 return 0
2213 }
2214 c_emit(cc, BC_ERROR, 0)
2215 return 1
2216}
2217
2218// ---------------- statement compiler ----------------
2219func c_stmt(cc: *i64, node: i64) -> i64 {
2220 let ctx: *i64 = (cc[CC_CTX]) as *i64
2221 let k: i64 = jp_nkind(ctx, node)
2222 if k == ND_VAR_DECL { return c_vardecl(cc, node) }
2223 if k == ND_VAR_LIST { // multi-declarator: compile each child ND_VAR_DECL
2224 let cnt: i64 = jp_nb(ctx, node)
2225 var i: i64 = 0
2226 while i < cnt { if c_vardecl(cc, jp_child_at(ctx, node, i)) == 1 { return 1 } i = i + 1 }
2227 return 0
2228 }
2229 if k == ND_FUNC_DECL {
2230 let nm: i64 = jp_na(ctx, node)
2231 let fidx: i64 = c_function(cc, node)
2232 c_emit(cc, BC_CLOSURE, fidx)
2233 if nm >= 0 { let s: i64 = c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm)); c_emit(cc, BC_STORE, s) } else { c_emit(cc, BC_POP, 0) }
2234 return 0
2235 }
2236 if k == ND_EXPR_STMT { c_expr(cc, jp_na(ctx, node)); c_emit(cc, BC_POP, 0); return 0 }
2237 if k == ND_CLASS { if c_expr(cc, node) == 1 { return 1 } c_emit(cc, BC_POP, 0); return 0 }
2238 if k == ND_BLOCK { return c_body(cc, node, 1) }
2239 if k == ND_IF {
2240 c_expr(cc, jp_na(ctx, node))
2241 let jf: i64 = c_emit(cc, BC_JMPF, 0)
2242 c_stmt(cc, jp_nb(ctx, node))
2243 let els: i64 = jp_nc(ctx, node)
2244 let code: *i64 = (cc[CC_CODE]) as *i64
2245 if els >= 0 {
2246 let je: i64 = c_emit(cc, BC_JMP, 0)
2247 code[jf * 2 + 1] = cc[CC_NP] * 2
2248 c_stmt(cc, els)
2249 code[je * 2 + 1] = cc[CC_NP] * 2
2250 } else { code[jf * 2 + 1] = cc[CC_NP] * 2 }
2251 return 0
2252 }
2253 if k == ND_WHILE {
2254 let brk0: i64 = cc[CC_BRKN]
2255 let con0: i64 = cc[CC_CONN]
2256 cc[CC_BRKD] = cc[CC_BRKD] + 1
2257 cc[CC_COND] = cc[CC_COND] + 1
2258 let l1: i64 = cc[CC_NP] * 2
2259 c_expr(cc, jp_na(ctx, node))
2260 let jf: i64 = c_emit(cc, BC_JMPF, 0)
2261 c_stmt(cc, jp_nb(ctx, node))
2262 c_emit(cc, BC_JMP, l1)
2263 let endpc: i64 = cc[CC_NP] * 2
2264 let code: *i64 = (cc[CC_CODE]) as *i64
2265 code[jf * 2 + 1] = endpc
2266 c_brkpatch(cc, brk0, endpc)
2267 c_conpatch(cc, con0, l1)
2268 cc[CC_BRKD] = cc[CC_BRKD] - 1
2269 cc[CC_COND] = cc[CC_COND] - 1
2270 return 0
2271 }
2272 if k == ND_FOR {
2273 let brk0: i64 = cc[CC_BRKN]
2274 let con0: i64 = cc[CC_CONN]
2275 cc[CC_BRKD] = cc[CC_BRKD] + 1
2276 cc[CC_COND] = cc[CC_COND] + 1
2277 let init: i64 = jp_na(ctx, node)
2278 let cond: i64 = jp_nb(ctx, node)
2279 let update: i64 = jp_nc(ctx, node)
2280 let body: i64 = ev_for_body(ctx, node)
2281 if init >= 0 { c_stmt(cc, init) }
2282 let l1: i64 = cc[CC_NP] * 2
2283 var jf: i64 = 0 - 1
2284 if cond >= 0 { c_expr(cc, cond); jf = c_emit(cc, BC_JMPF, 0) }
2285 c_stmt(cc, body)
2286 let lcont: i64 = cc[CC_NP] * 2
2287 if update >= 0 { c_expr(cc, update); c_emit(cc, BC_POP, 0) }
2288 c_emit(cc, BC_JMP, l1)
2289 let endpc: i64 = cc[CC_NP] * 2
2290 if jf >= 0 { let code: *i64 = (cc[CC_CODE]) as *i64; code[jf * 2 + 1] = endpc }
2291 c_brkpatch(cc, brk0, endpc)
2292 c_conpatch(cc, con0, lcont)
2293 cc[CC_BRKD] = cc[CC_BRKD] - 1
2294 cc[CC_COND] = cc[CC_COND] - 1
2295 return 0
2296 }
2297 if k == ND_DOWHILE {
2298 let brk0: i64 = cc[CC_BRKN]
2299 let con0: i64 = cc[CC_CONN]
2300 cc[CC_BRKD] = cc[CC_BRKD] + 1
2301 cc[CC_COND] = cc[CC_COND] + 1
2302 let lbody: i64 = cc[CC_NP] * 2
2303 c_stmt(cc, jp_nb(ctx, node))
2304 let lcont: i64 = cc[CC_NP] * 2
2305 c_expr(cc, jp_na(ctx, node))
2306 c_emit(cc, BC_JMPT, lbody)
2307 let endpc: i64 = cc[CC_NP] * 2
2308 c_brkpatch(cc, brk0, endpc)
2309 c_conpatch(cc, con0, lcont)
2310 cc[CC_BRKD] = cc[CC_BRKD] - 1
2311 cc[CC_COND] = cc[CC_COND] - 1
2312 return 0
2313 }
2314 if k == ND_FOR_OF { return c_forx(cc, node, 0) }
2315 if k == ND_FOR_IN { return c_forx(cc, node, 1) }
2316 if k == ND_SWITCH { return c_switch(cc, node) }
2317 if k == ND_BREAK {
2318 if cc[CC_BRKD] == 0 { c_emit(cc, BC_ERROR, 0); return 0 }
2319 c_exit_trys(cc, 1)
2320 let j: i64 = c_emit(cc, BC_JMP, 0)
2321 c_brkadd(cc, j)
2322 return 0
2323 }
2324 if k == ND_CONTINUE {
2325 if cc[CC_COND] == 0 { c_emit(cc, BC_ERROR, 0); return 0 }
2326 c_exit_trys(cc, 2)
2327 let j: i64 = c_emit(cc, BC_JMP, 0)
2328 c_conadd(cc, j)
2329 return 0
2330 }
2331 if k == ND_RETURN {
2332 let e: i64 = jp_na(ctx, node)
2333 if e >= 0 { c_expr(cc, e) } else { c_emit(cc, BC_PUSHU, 0) }
2334 c_exit_trys(cc, 0)
2335 if cc[CC_INFN] > 0 { c_emit(cc, BC_RETV, 0) } else { c_emit(cc, BC_RET, 0) }
2336 return 0
2337 }
2338 // try { a } catch (b) { c } finally { slot4 } -- BC_TRY registers a handler pointing at the
2339 // catch code; the try body pops it (BC_TRYPOP) on the normal path. The handler entry receives
2340 // the THROWN VALUE on the stack (catch param stores it, `catch {` pops it). finally code is
2341 // DUPLICATED on the normal + caught paths (and on return/break/continue exits via c_exit_trys);
2342 // finally-only tries stash the value, run finally, rethrow. NAMED divergence (matches scope
2343 // choice): a throw INSIDE the catch block skips this try's finally (outer handlers still run).
2344 if k == ND_TRY {
2345 let tryb: i64 = jp_na(ctx, node)
2346 let param: i64 = jp_nb(ctx, node)
2347 let catchb: i64 = jp_nc(ctx, node)
2348 let finb: i64 = ev_try_finally(ctx, node)
2349 let j1: i64 = c_emit(cc, BC_TRY, 0)
2350 let fins: *i64 = (cc[CC_FINS]) as *i64
2351 let fi: i64 = cc[CC_FINN]
2352 if fi >= VMC_MAXFE { c_emit(cc, BC_ERROR, 0); return 1 }
2353 fins[fi * FE_ENT] = finb
2354 fins[fi * FE_ENT + 1] = cc[CC_BRKD]
2355 fins[fi * FE_ENT + 2] = cc[CC_COND]
2356 cc[CC_FINN] = fi + 1
2357 c_stmt(cc, tryb)
2358 cc[CC_FINN] = fi
2359 c_emit(cc, BC_TRYPOP, 0)
2360 if finb >= 0 { c_stmt(cc, finb) }
2361 let je: i64 = c_emit(cc, BC_JMP, 0)
2362 let code: *i64 = (cc[CC_CODE]) as *i64
2363 code[j1 * 2 + 1] = cc[CC_NP] * 2
2364 if catchb >= 0 {
2365 if param >= 0 { let s: i64 = c_newslot(cc, ev_tok_start(ctx, param), ev_tok_len(ctx, param)); c_emit(cc, BC_STORE, s) } else { c_emit(cc, BC_POP, 0) }
2366 c_stmt(cc, catchb)
2367 if finb >= 0 { c_stmt(cc, finb) }
2368 } else {
2369 let t: i64 = c_newtmp(cc)
2370 c_emit(cc, BC_STORE, t)
2371 c_stmt(cc, finb)
2372 c_emit(cc, BC_LOAD, t)
2373 c_emit(cc, BC_THROW, 0)
2374 }
2375 code[je * 2 + 1] = cc[CC_NP] * 2
2376 return 0
2377 }
2378 if k == ND_THROW {
2379 c_expr(cc, jp_na(ctx, node))
2380 c_emit(cc, BC_THROW, 0)
2381 return 0
2382 }
2383 // bare expression used as a statement (for-init clauses etc.) -- mirror js_eval_to_status.
2384 c_expr(cc, node)
2385 c_emit(cc, BC_POP, 0)
2386 return 0
2387}
2388
2389// var declaration: IDENT / [a,b] array-pattern / {a,b} object-pattern (soft reads mirror the tree-walker).
2390func c_vardecl(cc: *i64, node: i64) -> i64 {
2391 let ctx: *i64 = (cc[CC_CTX]) as *i64
2392 let name: i64 = jp_na(ctx, node)
2393 let init: i64 = jp_nb(ctx, node)
2394 let nk: i64 = jp_nkind(ctx, name)
2395 if nk == ND_ARRAY_PAT {
2396 let t: i64 = c_newtmp(cc)
2397 if init >= 0 { c_expr(cc, init) } else { c_emit(cc, BC_PUSHU, 0) }
2398 c_emit(cc, BC_STORE, t)
2399 let pc2: i64 = jp_nb(ctx, name)
2400 var pi: i64 = 0
2401 while pi < pc2 {
2402 let nm: i64 = jp_child_at(ctx, name, pi)
2403 c_emit(cc, BC_LOAD, t)
2404 c_emit(cc, BC_IDXSOFT, pi)
2405 let s: i64 = c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm))
2406 c_emit(cc, BC_STORE, s)
2407 pi = pi + 1
2408 }
2409 return 0
2410 }
2411 if nk == ND_OBJ_PAT {
2412 let t: i64 = c_newtmp(cc)
2413 if init >= 0 { c_expr(cc, init) } else { c_emit(cc, BC_PUSHU, 0) }
2414 c_emit(cc, BC_STORE, t)
2415 let pc2: i64 = jp_nb(ctx, name)
2416 var pi: i64 = 0
2417 while pi < pc2 {
2418 let nm: i64 = jp_child_at(ctx, name, pi)
2419 let key: *i64 = ev_prop_key(ctx, nm)
2420 let ki: i64 = c_keyidx(cc, key)
2421 c_emit(cc, BC_LOAD, t)
2422 c_emit(cc, BC_PROPSOFT, ki)
2423 let s: i64 = c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm))
2424 c_emit(cc, BC_STORE, s)
2425 pi = pi + 1
2426 }
2427 return 0
2428 }
2429 // JS: a bare re-declaration (`var x;` where x already has a slot in THIS scope -- param or prior
2430 // var) is a runtime NO-OP: emit NOTHING (storing undefined would clobber the live value; scheme2js
2431 // re-declares params as vars pervasively -- the EarleyBoyer silent-wrong-result root cause). A
2432 // first declaration still creates the slot and initializes it to undefined.
2433 if init < 0 { if c_findslot(cc, cc[CC_SCUR], ev_tok_start(ctx, name), ev_tok_len(ctx, name)) >= 0 { return 0 } }
2434 let s: i64 = c_newslot(cc, ev_tok_start(ctx, name), ev_tok_len(ctx, name))
2435 if init >= 0 { c_expr(cc, init) } else { c_emit(cc, BC_PUSHU, 0) }
2436 c_emit(cc, BC_STORE, s)
2437 return 0
2438}
2439
2440// for-of (mode 0: iterable MUST be an array) / for-in (mode 1: keys of an object) -- desugared to an
2441// index loop over the array / the KEYS array, loop var bound each iteration (function-scoped slot).
2442func c_forx(cc: *i64, node: i64, mode: i64) -> i64 {
2443 let ctx: *i64 = (cc[CC_CTX]) as *i64
2444 let target: i64 = jp_na(ctx, node)
2445 let body: i64 = jp_nc(ctx, node)
2446 var namenode: i64 = target
2447 if jp_nkind(ctx, target) == ND_VAR_DECL { namenode = jp_na(ctx, target) }
2448 if jp_nkind(ctx, namenode) != ND_IDENT { c_emit(cc, BC_ERROR, 0); return 1 }
2449 let vslot: i64 = c_newslot(cc, ev_tok_start(ctx, namenode), ev_tok_len(ctx, namenode))
2450 let brk0: i64 = cc[CC_BRKN]
2451 let con0: i64 = cc[CC_CONN]
2452 cc[CC_BRKD] = cc[CC_BRKD] + 1
2453 cc[CC_COND] = cc[CC_COND] + 1
2454 c_expr(cc, jp_nb(ctx, node))
2455 if mode == 1 { c_emit(cc, BC_KEYS, 0) } else { c_emit(cc, BC_ITERCHK, 0) }
2456 let tit: i64 = c_newtmp(cc)
2457 c_emit(cc, BC_STORE, tit)
2458 let ti: i64 = c_newtmp(cc)
2459 c_emit(cc, BC_PUSH, 0)
2460 c_emit(cc, BC_STORE, ti)
2461 let lenrec: *i64 = ev_cstr("length\x00" as *u8)
2462 let lenki: i64 = c_keyidx(cc, lenrec)
2463 let l1: i64 = cc[CC_NP] * 2
2464 c_emit(cc, BC_LOAD, ti)
2465 c_emit(cc, BC_LOAD, tit)
2466 c_emit(cc, BC_GETPROP, lenki)
2467 c_emit(cc, BC_BINOP, OP_LT)
2468 let jf: i64 = c_emit(cc, BC_JMPF, 0)
2469 c_emit(cc, BC_LOAD, tit)
2470 c_emit(cc, BC_LOAD, ti)
2471 c_emit(cc, BC_GETIDX, 0)
2472 c_emit(cc, BC_STORE, vslot)
2473 c_stmt(cc, body)
2474 let lcont: i64 = cc[CC_NP] * 2
2475 c_emit(cc, BC_LOAD, ti)
2476 c_emit(cc, BC_PUSH, 1)
2477 c_emit(cc, BC_BINOP, OP_ADD)
2478 c_emit(cc, BC_STORE, ti)
2479 c_emit(cc, BC_JMP, l1)
2480 let endpc: i64 = cc[CC_NP] * 2
2481 let code: *i64 = (cc[CC_CODE]) as *i64
2482 code[jf * 2 + 1] = endpc
2483 c_brkpatch(cc, brk0, endpc)
2484 c_conpatch(cc, con0, lcont)
2485 cc[CC_BRKD] = cc[CC_BRKD] - 1
2486 cc[CC_COND] = cc[CC_COND] - 1
2487 return 0
2488}
2489
2490// switch: disc into a temp; sequential === tests (case exprs eval in order until a match, mirroring
2491// js_exec_switch); matched test jumps into the CONTIGUOUS body run (natural fall-through); no match ->
2492// default's body or end. break patches to end; continue passes through to the enclosing loop.
2493func c_switch(cc: *i64, node: i64) -> i64 {
2494 let ctx: *i64 = (cc[CC_CTX]) as *i64
2495 let ccount: i64 = jp_nb(ctx, node)
2496 if ccount > VMC_MAXCASE { c_emit(cc, BC_ERROR, 0); return 1 }
2497 let brk0: i64 = cc[CC_BRKN]
2498 cc[CC_BRKD] = cc[CC_BRKD] + 1
2499 let td: i64 = c_newtmp(cc)
2500 c_expr(cc, jp_nc(ctx, node))
2501 c_emit(cc, BC_STORE, td)
2502 let casejmp: *i64 = sys_mmap(VMC_MAXCASE * 8) as *i64
2503 var defidx: i64 = 0 - 1
2504 var i: i64 = 0
2505 while i < ccount {
2506 let cnode: i64 = jp_child_at(ctx, node, i)
2507 let cexpr: i64 = jp_nc(ctx, cnode)
2508 casejmp[i] = 0 - 1
2509 if cexpr < 0 { if defidx < 0 { defidx = i } }
2510 else {
2511 c_emit(cc, BC_LOAD, td)
2512 c_expr(cc, cexpr)
2513 c_emit(cc, BC_BINOP, OP_SEQ)
2514 let jt: i64 = c_emit(cc, BC_JMPT, 0)
2515 casejmp[i] = jt
2516 }
2517 i = i + 1
2518 }
2519 let tailjmp: i64 = c_emit(cc, BC_JMP, 0)
2520 var defpc: i64 = 0 - 1
2521 let code: *i64 = (cc[CC_CODE]) as *i64
2522 var j: i64 = 0
2523 while j < ccount {
2524 let cnode: i64 = jp_child_at(ctx, node, j)
2525 let bodypc: i64 = cc[CC_NP] * 2
2526 let cj: i64 = casejmp[j]
2527 if cj >= 0 { code[cj * 2 + 1] = bodypc }
2528 if j == defidx { defpc = bodypc }
2529 let scount: i64 = jp_nb(ctx, cnode)
2530 var si: i64 = 0
2531 while si < scount { let stn: i64 = jp_child_at(ctx, cnode, si); c_stmt(cc, stn); si = si + 1 }
2532 j = j + 1
2533 }
2534 let endpc: i64 = cc[CC_NP] * 2
2535 if defpc >= 0 { code[tailjmp * 2 + 1] = defpc } else { code[tailjmp * 2 + 1] = endpc }
2536 c_brkpatch(cc, brk0, endpc)
2537 cc[CC_BRKD] = cc[CC_BRKD] - 1
2538 return 0
2539}
2540
2541// program: hoist top-level function decls (mirrors js_run_source), then statements; the LAST
2542// expression-statement's value is the program result (else undefined), ended by BC_RET.
2543func c_program(cc: *i64, prog: i64) -> i64 {
2544 let ctx: *i64 = (cc[CC_CTX]) as *i64
2545 c_predecl(cc, prog)
2546 let cnt: i64 = jp_nb(ctx, prog)
2547 var h: i64 = 0
2548 while h < cnt {
2549 let ch: i64 = jp_child_at(ctx, prog, h)
2550 if jp_nkind(ctx, ch) == ND_FUNC_DECL {
2551 let nm: i64 = jp_na(ctx, ch)
2552 if nm >= 0 {
2553 let fidx: i64 = c_function(cc, ch)
2554 c_emit(cc, BC_CLOSURE, fidx)
2555 let s: i64 = c_newslot(cc, ev_tok_start(ctx, nm), ev_tok_len(ctx, nm))
2556 c_emit(cc, BC_STORE, s)
2557 }
2558 }
2559 h = h + 1
2560 }
2561 var i: i64 = 0
2562 while i < cnt {
2563 let ch: i64 = jp_child_at(ctx, prog, i)
2564 var skip: i64 = 0
2565 if jp_nkind(ctx, ch) == ND_FUNC_DECL { let nm2: i64 = jp_na(ctx, ch); if nm2 >= 0 { skip = 1 } }
2566 if skip == 0 {
2567 if i == (cnt - 1) {
2568 if jp_nkind(ctx, ch) == ND_EXPR_STMT { c_expr(cc, jp_na(ctx, ch)); c_emit(cc, BC_RET, 0); return 0 }
2569 c_stmt(cc, ch)
2570 } else { c_stmt(cc, ch) }
2571 }
2572 i = i + 1
2573 }
2574 c_emit(cc, BC_PUSHU, 0)
2575 c_emit(cc, BC_RET, 0)
2576 return 0
2577}
2578
2579// ============================================================================================
2580// P5 BASELINE JIT (Sparkplug-tier): templated x86-64 off the bytecode. The interpreter (vm_loop)
2581// stays the correctness oracle + the fallback for everything this template compiler declines.
2582// SUBSET compiled to native: integer/local/control-flow programs -- PUSH/PUSHK/PUSHU/LOAD/STORE/POP/
2583// DUP/SWAP/UNOP/BINOP/JMP/JMPF/JMPT/RET. ANY other op (CALL/CLOSURE/GETPROP/TRY/LOADU/...) -> DECLINE
2584// (jit_compile returns 0) -> vm_loop runs it, fully correct. The operand stack lives in the SAME
2585// vs[VS_STACK] memory the VM uses (so semantics + the [tag,pay] model are identical); registers:
2586// RBX=stack base, R14=env base (depth 0), R15=sp as a BYTE offset, R12=vs. 5 callee-saved pushes keep
2587// the frame 16-aligned so CALLs into vm_binary/vm_unary/ev_truthy are ABI-correct + those regs survive.
2588// BINOP has a monomorphic INT fast path (both tags==VAL_NUM -> inline add/sub/imul/cmp+setcc) with a
2589// slow path calling vm_binary (strings/floats/mixed/div0 -> identical semantics + error rc). Proven by
2590// the 108/108 parity gate (cr_core now JITs every templatable snippet) + nx_js_jit_gate (A/B + decline).
2591// static i64 zero-inits: jit_disabled==0 => ENABLED by default; the gate flips it to force the VM path.
2592static jit_disabled: i64
2593func js_jit_disable(v: i64) -> i64 { jit_disabled = v; return 0 }
2594static jit_ran: i64 // 1 iff the LAST compile_run tiered up to native (P5 JIT)
2595func jit_ran_get() -> i64 { return jit_ran }
2596
2597// data ops routed through the call-threading stub (run via vm_ext with synced sp -- correct by
2598// construction, still native dispatch). GETPROP is handled INLINE (shape-guarded) instead, so it is
2599// NOT in this set. Control-flow / frame ops (CALL/CALLM/NEW/RETV/CLOSURE/JMPNN/TRY/THROW/ERROR) are
2600// NOT here -- they still DECLINE the whole program to the interpreter.
2601func jit_is_ct(op: i64) -> i64 {
2602 if op == BC_GETPROPQ { return 1 }
2603 if op == BC_SETPROP { return 1 }
2604 if op == BC_GETIDX { return 1 }
2605 if op == BC_SETIDX { return 1 }
2606 if op == BC_OBJ { return 1 }
2607 if op == BC_OPROP { return 1 }
2608 if op == BC_ARR { return 1 }
2609 if op == BC_APUSH { return 1 }
2610 if op == BC_SPREAD { return 1 }
2611 if op == BC_THIS { return 1 }
2612 if op == BC_KEYS { return 1 }
2613 if op == BC_ITERCHK { return 1 }
2614 if op == BC_IDXSOFT { return 1 }
2615 if op == BC_PROPSOFT { return 1 }
2616 if op == BC_DELIDX { return 1 }
2617 if op == BC_ROT { return 1 }
2618 if op == BC_LOADU { return 1 }
2619 if op == BC_STOREU { return 1 }
2620 return 0
2621}
2622func jit_templatable(op: i64) -> i64 {
2623 if op == BC_PUSH { return 1 }
2624 if op == BC_PUSHK { return 1 }
2625 if op == BC_PUSHU { return 1 }
2626 if op == BC_LOAD { return 1 }
2627 if op == BC_STORE { return 1 }
2628 if op == BC_POP { return 1 }
2629 if op == BC_BINOP { return 1 }
2630 if op == BC_JMPF { return 1 }
2631 if op == BC_JMP { return 1 }
2632 if op == BC_JMPT { return 1 }
2633 if op == BC_DUP { return 1 }
2634 if op == BC_DUP2 { return 1 }
2635 if op == BC_SWAP { return 1 }
2636 if op == BC_UNOP { return 1 }
2637 if op == BC_RET { return 1 }
2638 if op == BC_GETPROP { return 1 }
2639 if jit_is_ct(op) == 1 { return 1 }
2640 if jit_calls_off == 0 {
2641 if op == BC_CLOSURE { return 1 }
2642 if op == BC_CALL { return 1 }
2643 if op == BC_RETV { return 1 }
2644 if op == BC_CALLM { return 1 }
2645 if op == BC_NEW { return 1 }
2646 }
2647 return 0
2648}
2649// reg = RBX + R15 + disp (address of a stack cell relative to the top-of-stack byte offset)
2650func jit_addr(cb: *u8, pb: *i64, reg: i64, disp: i64) -> i64 {
2651 xe_mov_rr(cb, pb, reg, RBX)
2652 xe_add_rr(cb, pb, reg, R15)
2653 if disp < 0 { xe_sub_ri32(cb, pb, reg, 0 - disp) }
2654 if disp > 0 { xe_add_ri32(cb, pb, reg, disp) }
2655 return 0
2656}
2657// slow BINOP: call vm_binary(op, &a, &b, &a); a=[RBX+R15-32], b=[RBX+R15-16]; bail (rc1) if it errors; pop one.
2658func jit_slow_binop(cb: *u8, pb: *i64, op: i64, bsite: *i64, bcnt: *i64) -> i64 {
2659 xe_mov_ri32(cb, pb, RDI, op)
2660 jit_addr(cb, pb, RSI, 0 - 32)
2661 jit_addr(cb, pb, RDX, 0 - 16)
2662 xe_mov_rr(cb, pb, RCX, RSI)
2663 xe_mov_ri64(cb, pb, R10, (&vm_binary) as i64)
2664 xe_call_reg(cb, pb, R10)
2665 xe_cmp_ri32(cb, pb, RAX, 1)
2666 let b: i64 = xe_jcc(cb, pb, CC_E)
2667 bsite[bcnt[0]] = b
2668 bcnt[0] = bcnt[0] + 1
2669 xe_sub_ri32(cb, pb, R15, 16)
2670 return 0
2671}
2672// load one JIT operand at (tagdisp,paydisp on the operand stack) into `xmm` as a hardware double: VAL_FLOAT
2673// -> movq the raw f64 bits; VAL_NUM -> cvtsi2sd (int->double, so MIXED int/float arithmetic works). Records a
2674// "non-numeric -> soft" jcc into ssite for the caller to patch to the soft path. Scratch: RDX (tag), RAX (pay).
2675func jit_float_operand(cb: *u8, pb: *i64, xmm: i64, tagdisp: i64, paydisp: i64, ssite: *i64, scnt: *i64) -> i64 {
2676 xe_load_idx(cb, pb, RDX, RBX, R15, tagdisp)
2677 xe_cmp_ri32(cb, pb, RDX, VAL_FLOAT)
2678 let jf: i64 = xe_jcc(cb, pb, CC_E)
2679 xe_cmp_ri32(cb, pb, RDX, VAL_NUM)
2680 let jns: i64 = xe_jcc(cb, pb, CC_NE) // neither float nor int -> soft path
2681 ssite[scnt[0]] = jns
2682 scnt[0] = scnt[0] + 1
2683 xe_load_idx(cb, pb, RAX, RBX, R15, paydisp) // int operand: cvtsi2sd
2684 xe_cvtsi2sd(cb, pb, xmm, RAX)
2685 let jdn: i64 = xe_jmp(cb, pb)
2686 xe_patch(cb, jf, pb[0])
2687 xe_load_idx(cb, pb, RAX, RBX, R15, paydisp) // float operand: movq raw bits
2688 xe_movq_xr(cb, pb, xmm, RAX)
2689 xe_patch(cb, jdn, pb[0])
2690 return 0
2691}
2692// BINOP: monomorphic int fast path for add/sub/mul/lt/le/gt/ge/eq/ne; else (or non-num operands) slow.
2693func jit_binop(cb: *u8, pb: *i64, op: i64, bsite: *i64, bcnt: *i64) -> i64 {
2694 var inl: i64 = 0
2695 if op == OP_ADD { inl = 1 }
2696 if op == OP_SUB { inl = 1 }
2697 if op == OP_MUL { inl = 1 }
2698 if op == OP_LT { inl = 1 }
2699 if op == OP_LE { inl = 1 }
2700 if op == OP_GT { inl = 1 }
2701 if op == OP_GE { inl = 1 }
2702 if op == OP_EQ { inl = 1 }
2703 if op == OP_NE { inl = 1 }
2704 if op == OP_MOD { inl = 1 } // integer remainder via hardware idiv (a%0 -> soft NaN)
2705 if inl == 0 { jit_slow_binop(cb, pb, op, bsite, bcnt); return 0 }
2706 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 32)
2707 xe_cmp_ri32(cb, pb, RAX, VAL_NUM)
2708 let s1: i64 = xe_jcc(cb, pb, CC_NE)
2709 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
2710 xe_cmp_ri32(cb, pb, RAX, VAL_NUM)
2711 let s2: i64 = xe_jcc(cb, pb, CC_NE)
2712 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 24)
2713 xe_load_idx(cb, pb, RCX, RBX, R15, 0 - 8)
2714 var restag: i64 = VAL_NUM
2715 var modz: i64 = 0
2716 if op == OP_ADD { xe_add_rr(cb, pb, RAX, RCX) }
2717 if op == OP_SUB { xe_sub_rr(cb, pb, RAX, RCX) }
2718 if op == OP_MUL { xe_imul_rr(cb, pb, RAX, RCX) }
2719 if op == OP_MOD { // RAX=a, RCX=b -> RAX=a%b via signed idiv (rem in RDX)
2720 xe_cmp_ri32(cb, pb, RCX, 0)
2721 modz = xe_jcc(cb, pb, CC_E) // b==0 -> soft (a%0 = NaN)
2722 xe_cqo(cb, pb) // sign-extend RAX into RDX:RAX
2723 xe_idiv(cb, pb, RCX) // RAX=quotient, RDX=remainder
2724 xe_mov_rr(cb, pb, RAX, RDX) // result = remainder
2725 }
2726 if op == OP_LT { xe_cmp_rr(cb, pb, RAX, RCX); xe_setcc(cb, pb, SET_L, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2727 if op == OP_LE { xe_cmp_rr(cb, pb, RAX, RCX); xe_setcc(cb, pb, SET_LE, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2728 if op == OP_GT { xe_cmp_rr(cb, pb, RAX, RCX); xe_setcc(cb, pb, SET_G, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2729 if op == OP_GE { xe_cmp_rr(cb, pb, RAX, RCX); xe_setcc(cb, pb, SET_GE, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2730 if op == OP_EQ { xe_cmp_rr(cb, pb, RAX, RCX); xe_setcc(cb, pb, SET_E, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2731 if op == OP_NE { xe_cmp_rr(cb, pb, RAX, RCX); xe_setcc(cb, pb, SET_NE, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2732 xe_mov_ri32(cb, pb, RDX, restag)
2733 xe_store_idx(cb, pb, RBX, R15, 0 - 32, RDX)
2734 xe_store_idx(cb, pb, RBX, R15, 0 - 24, RAX)
2735 xe_sub_ri32(cb, pb, R15, 16)
2736 let jd: i64 = xe_jmp(cb, pb)
2737 // int-path fail -> HARDWARE-FLOAT fast path: both operands VAL_FLOAT & op in {+,-,*} -> SSE scalar-double
2738 // (nx_f64 is correctly-rounded RNE so hardware==soft-float bit-for-bit; parity holds). Else -> soft.
2739 let floatoff: i64 = pb[0]
2740 xe_patch(cb, s1, floatoff)
2741 xe_patch(cb, s2, floatoff)
2742 var is_arith: i64 = 0
2743 if op == OP_ADD { is_arith = 1 }
2744 if op == OP_SUB { is_arith = 1 }
2745 if op == OP_MUL { is_arith = 1 }
2746 var jd2: i64 = 0
2747 let ssite: *i64 = (sys_mmap(8 * 8)) as *i64
2748 let scnt: *i64 = (sys_mmap(8)) as *i64
2749 scnt[0] = 0
2750 if is_arith == 1 {
2751 // MIXED-capable: each operand loaded as a double (float->movq, int->cvtsi2sd). Both-int never
2752 // reaches here (handled by the int fast path), so a float result here is always JS-correct.
2753 jit_float_operand(cb, pb, 0, 0 - 32, 0 - 24, ssite, scnt) // A -> xmm0
2754 jit_float_operand(cb, pb, 1, 0 - 16, 0 - 8, ssite, scnt) // B -> xmm1
2755 var opc: i64 = 0x58 // addsd
2756 if op == OP_SUB { opc = 0x5C } // subsd
2757 if op == OP_MUL { opc = 0x59 } // mulsd
2758 xe_sse_arith(cb, pb, opc, 0, 1) // xmm0 = xmm0 <op> xmm1
2759 xe_movq_rx(cb, pb, RAX, 0) // rax = result f64 bits
2760 xe_mov_ri32(cb, pb, RDX, VAL_FLOAT)
2761 xe_store_idx(cb, pb, RBX, R15, 0 - 32, RDX)
2762 xe_store_idx(cb, pb, RBX, R15, 0 - 24, RAX)
2763 xe_sub_ri32(cb, pb, R15, 16)
2764 jd2 = xe_jmp(cb, pb)
2765 }
2766 let slowoff: i64 = pb[0]
2767 if is_arith == 1 { var si: i64 = 0; while si < scnt[0] { xe_patch(cb, ssite[si], slowoff); si = si + 1 } }
2768 if op == OP_MOD { xe_patch(cb, modz, slowoff) } // a%0 -> soft (NaN)
2769 jit_slow_binop(cb, pb, op, bsite, bcnt)
2770 let doneoff: i64 = pb[0]
2771 xe_patch(cb, jd, doneoff)
2772 if is_arith == 1 { xe_patch(cb, jd2, doneoff) }
2773 return 0
2774}
2775// Fusable-op predicate: exactly the ops jit_binop inlines on the int fast path, so a compile-time int const
2776// RHS can be folded into an x86 IMMEDIATE. DIV / bitwise are NOT inlined by jit_binop -> not fusable here.
2777func jit_binop_fusable(op: i64) -> i64 {
2778 if op == OP_ADD { return 1 }
2779 if op == OP_SUB { return 1 }
2780 if op == OP_MUL { return 1 }
2781 if op == OP_MOD { return 1 }
2782 if op == OP_LT { return 1 }
2783 if op == OP_LE { return 1 }
2784 if op == OP_GT { return 1 }
2785 if op == OP_GE { return 1 }
2786 if op == OP_EQ { return 1 }
2787 if op == OP_NE { return 1 }
2788 return 0
2789}
2790// CONST-FOLDED BINOP (peephole for `BC_PUSH k ; BC_BINOP op`): the RHS is a compile-time int constant, so fold
2791// k into an x86 IMMEDIATE and skip BOTH the const's stack push AND its tag-check. Only ONE operand (A = the
2792// LHS) is live on the stack at [R15-16]/[R15-8]; k arrives as an argument. The emitter GUARANTEES 0<=k<=2^31-1
2793// (imm32 sign-extends correctly; `mov ecx,k` zero-extend == value) and k!=0 for MOD. Fast path (A is int): op
2794// A,k in place -> result REPLACES A, sp unchanged (net-0, exactly matching the PUSH(+16)+BINOP(-16) it stands
2795// in for). Slow path (A is float/string): MATERIALIZE the k push so the stack is byte-identical to the
2796// un-fused 2-operand state, then run jit_slow_binop -> the EXACT same cold tail jit_binop uses (mixed int/float
2797// + string-concat all handled there; correct by construction). jit_binop itself is left untouched.
2798func jit_binop_const(cb: *u8, pb: *i64, op: i64, k: i64, bsite: *i64, bcnt: *i64) -> i64 {
2799 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16) // A tag
2800 xe_cmp_ri32(cb, pb, RAX, VAL_NUM)
2801 let sfall: i64 = xe_jcc(cb, pb, CC_NE) // A not int -> fallback (materialize + slow)
2802 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 8) // RAX = A pay
2803 var restag: i64 = VAL_NUM
2804 if op == OP_ADD { xe_add_ri32(cb, pb, RAX, k) }
2805 if op == OP_SUB { xe_sub_ri32(cb, pb, RAX, k) }
2806 if op == OP_MUL { xe_mov_ri32(cb, pb, RCX, k); xe_imul_rr(cb, pb, RAX, RCX) }
2807 if op == OP_MOD { // k guaranteed != 0 -> no b==0 check (cf jit_binop MOD)
2808 xe_mov_ri32(cb, pb, RCX, k)
2809 xe_cqo(cb, pb)
2810 xe_idiv(cb, pb, RCX)
2811 xe_mov_rr(cb, pb, RAX, RDX)
2812 }
2813 if op == OP_LT { xe_cmp_ri32(cb, pb, RAX, k); xe_setcc(cb, pb, SET_L, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2814 if op == OP_LE { xe_cmp_ri32(cb, pb, RAX, k); xe_setcc(cb, pb, SET_LE, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2815 if op == OP_GT { xe_cmp_ri32(cb, pb, RAX, k); xe_setcc(cb, pb, SET_G, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2816 if op == OP_GE { xe_cmp_ri32(cb, pb, RAX, k); xe_setcc(cb, pb, SET_GE, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2817 if op == OP_EQ { xe_cmp_ri32(cb, pb, RAX, k); xe_setcc(cb, pb, SET_E, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2818 if op == OP_NE { xe_cmp_ri32(cb, pb, RAX, k); xe_setcc(cb, pb, SET_NE, RAX); xe_movzx_rb(cb, pb, RAX, RAX); restag = VAL_BOOL }
2819 xe_mov_ri32(cb, pb, RDX, restag)
2820 xe_store_idx(cb, pb, RBX, R15, 0 - 16, RDX) // result replaces A in place -- NO sp change
2821 xe_store_idx(cb, pb, RBX, R15, 0 - 8, RAX)
2822 let jd: i64 = xe_jmp(cb, pb)
2823 // FALLBACK: A is float/string -> push k (stack now byte-identical to [.., A, k]) + normal slow binop.
2824 let falloff: i64 = pb[0]
2825 xe_patch(cb, sfall, falloff)
2826 xe_mov_ri32(cb, pb, RAX, VAL_NUM)
2827 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
2828 xe_mov_ri64(cb, pb, RAX, k)
2829 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
2830 xe_add_ri32(cb, pb, R15, 16) // stack now [.., A, k] -- byte-identical to un-fused
2831 // FLOAT/mixed tail: MIRROR jit_binop's tail so `float +/- int-const` uses INLINE SSE. Falling straight to
2832 // jit_slow_binop here was a MEASURED ~10x regression (nx_js_floatfuse_probe: float s=s+1 17.5ms->173ms).
2833 // A at [-32]/[-24], k at [-16]/[-8]. ADD/SUB/MUL -> SSE scalar-double; string/other -> jit_slow_binop
2834 // (jit_float_operand bails non-numbers to ssite, so `'ab'+3` still concats correctly).
2835 var is_arith: i64 = 0
2836 if op == OP_ADD { is_arith = 1 }
2837 if op == OP_SUB { is_arith = 1 }
2838 if op == OP_MUL { is_arith = 1 }
2839 var jd2: i64 = 0
2840 let ssite: *i64 = (sys_mmap(8 * 8)) as *i64
2841 let scnt: *i64 = (sys_mmap(8)) as *i64
2842 scnt[0] = 0
2843 if is_arith == 1 {
2844 jit_float_operand(cb, pb, 0, 0 - 32, 0 - 24, ssite, scnt) // A -> xmm0
2845 jit_float_operand(cb, pb, 1, 0 - 16, 0 - 8, ssite, scnt) // k -> xmm1
2846 var opc: i64 = 0x58 // addsd
2847 if op == OP_SUB { opc = 0x5C } // subsd
2848 if op == OP_MUL { opc = 0x59 } // mulsd
2849 xe_sse_arith(cb, pb, opc, 0, 1)
2850 xe_movq_rx(cb, pb, RAX, 0)
2851 xe_mov_ri32(cb, pb, RDX, VAL_FLOAT)
2852 xe_store_idx(cb, pb, RBX, R15, 0 - 32, RDX)
2853 xe_store_idx(cb, pb, RBX, R15, 0 - 24, RAX)
2854 xe_sub_ri32(cb, pb, R15, 16)
2855 jd2 = xe_jmp(cb, pb)
2856 }
2857 let slowoff: i64 = pb[0]
2858 if is_arith == 1 { var si: i64 = 0; while si < scnt[0] { xe_patch(cb, ssite[si], slowoff); si = si + 1 } }
2859 jit_slow_binop(cb, pb, op, bsite, bcnt)
2860 let doneoff2: i64 = pb[0]
2861 xe_patch(cb, jd, doneoff2)
2862 if is_arith == 1 { xe_patch(cb, jd2, doneoff2) }
2863 return 0
2864}
2865// (A MEM-OPERAND BINOP fusion `BC_LOAD v ; BC_BINOP op` -- read the RHS local straight from its env slot
2866// instead of pushing it -- was built + differential-verified 23/23 here on 2026-07-14 and REVERTED: MEASURED
2867// NEUTRAL. Controlled best-of-3 A/B, K3 sieve 563us(off) vs 555us(on) = ~1.4% = within the noise floor (K2, a
2868// zero-memrhs control, "moved" 0.6%); a latency-bound `s=s+a+b` microbench 16918us vs 17544us. WHY, vs const
2869// fusion's real +11%: const fusion folds k into an x86 IMMEDIATE so the constant leaves memory entirely;
2870// mem-operand still LOADS v from memory (env vs stack = same traffic) and the one push it removes is off the
2871// critical path -> out-of-order execution hides it. Per measured-exceed doctrine [cf. the reverted method IC],
2872// correct-but-unmeasured complexity is not shipped. To retry: needs a throughput-bound var-var workload where
2873// the removed push is on the critical path -- none found in the h2h kernels.)
2874// JMPF/JMPT: truthy(top) (inline for num/bool, else call ev_truthy), pop, conditional jump to target instr.
2875func jit_jmpcond(cb: *u8, pb: *i64, jumpIfTrue: i64, fsite: *i64, ftgt: *i64, fcnt: *i64, targetInstr: i64) -> i64 {
2876 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
2877 xe_cmp_ri32(cb, pb, RAX, VAL_BOOL)
2878 let a1: i64 = xe_jcc(cb, pb, CC_E)
2879 xe_cmp_ri32(cb, pb, RAX, VAL_NUM)
2880 let a2: i64 = xe_jcc(cb, pb, CC_E)
2881 jit_addr(cb, pb, RDI, 0 - 16)
2882 xe_mov_ri64(cb, pb, R10, (&ev_truthy) as i64)
2883 xe_call_reg(cb, pb, R10)
2884 let jc: i64 = xe_jmp(cb, pb)
2885 let nb: i64 = pb[0]
2886 xe_patch(cb, a1, nb)
2887 xe_patch(cb, a2, nb)
2888 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 8)
2889 let have: i64 = pb[0]
2890 xe_patch(cb, jc, have)
2891 xe_sub_ri32(cb, pb, R15, 16)
2892 xe_cmp_ri32(cb, pb, RAX, 0)
2893 var cc: i64 = CC_E
2894 if jumpIfTrue == 1 { cc = CC_NE }
2895 let s: i64 = xe_jcc(cb, pb, cc)
2896 fsite[fcnt[0]] = s
2897 ftgt[fcnt[0]] = targetInstr
2898 fcnt[0] = fcnt[0] + 1
2899 return 0
2900}
2901// CALL-THREADING stub: run ONE non-control-flow op through the interpreter's cold half (vm_ext) with
2902// the operand stack synced from the JIT's byte-offset sp. Returns the new sp byte-offset; the caller
2903// checks vs[VS_RUN] afterward (0 => a halt/error -> bail rc1). Lets the JIT run object/array DATA ops
2904// natively (the hot arithmetic/loop ops stay fully inline; only these heavy ops pay a call) with the
2905// exact interpreter semantics -- correct by construction.
2906func jit_ct(vs: *i64, op: i64, arg: i64, pc: i64, spbytes: i64) -> i64 {
2907 vs[VS_SP] = spbytes / 16
2908 vs[VS_PC] = pc
2909 vm_ext(vs, op, arg)
2910 return vs[VS_SP] * 16
2911}
2912// emit the native call-threading sequence for op/arg at instruction pc: sync sp+env -> call jit_ct ->
2913// reload sp+env -> bail if vs[VS_RUN]==0. RBX/R12/R13 are callee-saved (survive the call); env is
2914// synced through vs[VS_ENV] because closure/up-level-var/this ops read it (function bodies use them).
2915func jit_emit_ct(cb: *u8, pb: *i64, op: i64, arg: i64, pc: i64, bsite: *i64, bcnt: *i64) -> i64 {
2916 xe_store(cb, pb, R12, VS_ENV * 8, R14)
2917 xe_mov_rr(cb, pb, RDI, R12)
2918 xe_mov_ri32(cb, pb, RSI, op)
2919 xe_mov_ri32(cb, pb, RDX, arg)
2920 xe_mov_ri32(cb, pb, RCX, pc)
2921 xe_mov_rr(cb, pb, R8, R15)
2922 xe_mov_ri64(cb, pb, R10, (&jit_ct) as i64)
2923 xe_call_reg(cb, pb, R10)
2924 xe_mov_rr(cb, pb, R15, RAX)
2925 xe_load(cb, pb, R14, R12, VS_ENV * 8)
2926 xe_load(cb, pb, RAX, R12, VS_RUN * 8)
2927 xe_cmp_ri32(cb, pb, RAX, 0)
2928 let b: i64 = xe_jcc(cb, pb, CC_E)
2929 bsite[bcnt[0]] = b
2930 bcnt[0] = bcnt[0] + 1
2931 return 0
2932}
2933// (A method inline cache keyed by (key,shape,proto)->(holder,slot) was prototyped here 2026-07-13 and REVERTED:
2934// correct (16/16) but MEASURED slower than the ev_get_prop path on the small-object/shallow-proto method call
2935// -- the common case (Point{x,y}.dist()) -- because the cache validate+hash exceeded a 1-2 slot obj_find. It
2936// would only help wide objects / deep chains, unmeasured. measured-exceed doctrine: not kept. See coordination.)
2937// ---- NATIVE FUNCTION CALLS (P5 call rung) ----
2938// prep: resolve the callee at stack[base] (base = sp-argc-1). VM closure -> set up the activation frame
2939// via the shared vmx_callfn (env alloc + arg copy + defaults/rest) and RETURN its absolute native entry
2940// address (cbbase + fnaddr[fidx]); the JIT then hardware-CALLs it. Native builtin -> run it here (returns
2941// 0, no native jump). Non-function / fatal -> vmx_halt + return 0. (vs[VS_SP]/[VS_ENV] must be synced.)
2942func jit_call_prep(vs: *i64, argc: i64) -> i64 {
2943 let sp: i64 = vs[VS_SP]
2944 let base: i64 = sp - argc - 1
2945 let stack: *i64 = (vs[VS_STACK]) as *i64
2946 let ft: i64 = stack[base * 2]
2947 let fpay: i64 = stack[base * 2 + 1]
2948 if ft == VAL_FUNC {
2949 let rec: *i64 = fpay as *i64
2950 if rec[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
2951 let fidx: i64 = rec[1]
2952 let ftab: *i64 = (vs[VS_FTAB]) as *i64
2953 let fb: i64 = fidx * FT_ENT
2954 let fl: i64 = ftab[fb + 3]
2955 if fl == 0 {
2956 // FAST PATH (P6 rung-2): fl==0 = no rest param / no `arguments` object = the common function shape.
2957 // Inline vmx_callfn's body natively here (env bump-alloc + param bind + frame push) so a plain JS
2958 // call is ONE sovereign helper-call instead of jit_call_prep -> vmx_callfn -> vmx_alloc (3 nested sovereign calls).
2959 // BYTE-IDENTICAL to vmx_callfn(vs,rec,argc,base, 0,0,0, 0). Edge shapes fall through to vmx_callfn.
2960 let entry: i64 = ftab[fb]
2961 let nv: i64 = ftab[fb + 2]
2962 let np: i64 = ftab[fb + 1]
2963 let hp0: i64 = vs[VS_HP]
2964 let nb: i64 = ((ENVR_HDR + nv * 2) * 8 + 7) / 8 * 8
2965 if (hp0 + nb) > VMC_ARENA { return vmx_fatal(vs) }
2966 vs[VS_HP] = hp0 + nb
2967 let nep: i64 = vs[VS_ARENA] + hp0
2968 let ne: *i64 = nep as *i64
2969 ne[0] = rec[2]; ne[1] = 0; ne[2] = 0; ne[3] = 0
2970 var i: i64 = 0
2971 while i < np {
2972 let d: i64 = ENVR_HDR + i * 2
2973 if i < argc { let s: i64 = (base + 1 + i) * 2; ne[d] = stack[s]; ne[d + 1] = stack[s + 1] }
2974 else { ne[d] = VAL_UNDEF; ne[d + 1] = 0 }
2975 i = i + 1
2976 }
2977 let fp: i64 = vs[VS_FP]
2978 if fp >= VMC_MAXFR { return vmx_fatal(vs) }
2979 let fr: *i64 = (vs[VS_FRAMES]) as *i64
2980 let b: i64 = fp * FR_ENT
2981 fr[b] = vs[VS_PC] + 2
2982 fr[b + 1] = vs[VS_ENV]
2983 fr[b + 2] = base
2984 fr[b + 3] = 0
2985 fr[b + 4] = hp0
2986 fr[b + 5] = vmx_esc
2987 vs[VS_FP] = fp + 1
2988 vs[VS_ENV] = nep
2989 vs[VS_PC] = entry
2990 vs[VS_SP] = base
2991 } else {
2992 vmx_callfn(vs, rec, argc, base, 0, 0, 0, 0)
2993 }
2994 if vs[VS_RUN] == 0 { return 0 }
2995 let fnaddr: *i64 = (vs[VS_FNADDR]) as *i64
2996 let off: i64 = fnaddr[fidx]
2997 if off < 0 { vmx_halt(vs); return 0 }
2998 return vs[VS_CBBASE] + off
2999 }
3000 if ft == VAL_NATIVE {
3001 vmx_native(vs, fpay as i64, base, argc, 0)
3002 return 0
3003 }
3004 vmx_halt(vs)
3005 return 0
3006}
3007// retv: mirror the interpreter BC_RETV (result move to the frame base + frame pop + set vs[VS_SP]); the
3008// native `ret` after this returns to the CALL site (which restores the caller env + reloads sp).
3009func jit_retv_prep(vs: *i64) -> i64 {
3010 let sp: i64 = vs[VS_SP]
3011 let stack: *i64 = (vs[VS_STACK]) as *i64
3012 let rs: i64 = (sp - 1) * 2
3013 var rt: i64 = stack[rs]
3014 var rp: i64 = stack[rs + 1]
3015 let fp: i64 = vs[VS_FP]
3016 let fr: *i64 = (vs[VS_FRAMES]) as *i64
3017 let b: i64 = (fp - 1) * FR_ENT
3018 vs[VS_FP] = fp - 1
3019 let nobj: i64 = fr[b + 3]
3020 if nobj != 0 { if rt != VAL_OBJECT { rt = VAL_OBJECT; rp = nobj } }
3021 let base: i64 = fr[b + 2]
3022 stack[base * 2] = rt
3023 stack[base * 2 + 1] = rp
3024 vs[VS_SP] = base + 1
3025 if vmx_esc == fr[b + 5] { vs[VS_HP] = fr[b + 4] } // no closure escaped this call -> reclaim its arena
3026 return 0
3027}
3028// prep for BC_CALLM (method call): mirror the interpreter -- native builtin (str/arr/globalns/promise/
3029// response method OR an object-prop native) runs inline (return 0); a VM-closure method sets up the frame
3030// (this = receiver) and returns its native entry. Missing method / non-func -> halt. `packed`=kidx*CM_PACK+argc.
3031func jit_callm_prep(vs: *i64, packed: i64) -> i64 {
3032 let kidx: i64 = packed / CM_PACK
3033 let argc: i64 = packed % CM_PACK
3034 let pool: *i64 = (vs[VS_POOL]) as *i64
3035 let key: *i64 = (pool[kidx * 2 + 1]) as *i64
3036 let sp: i64 = vs[VS_SP]
3037 let base: i64 = sp - argc - 1
3038 let stack: *i64 = (vs[VS_STACK]) as *i64
3039 let rt: i64 = stack[base * 2]
3040 let rp: i64 = stack[base * 2 + 1]
3041 var bid: i64 = 0
3042 if rt == VAL_STR { bid = ev_native_str(key) }
3043 if rt == VAL_ARRAY { bid = ev_native_arr(key) }
3044 if rt == VAL_NUM { bid = ev_native_num(key) }
3045 if rt == VAL_FLOAT { bid = ev_native_num(key) }
3046 if rt == VAL_BOOL { bid = ev_native_num(key) }
3047 if rt == VAL_GLOBALNS { bid = ev_native_global(rp, key) }
3048 if rt == VAL_PROMISE { bid = ev_native_promise(key) }
3049 if rt == VAL_RESPONSE { bid = ev_native_response(key) }
3050 if rt == VAL_NATIVE { if rp == BI_STRING_CTOR { if ev_key_is(key, "fromCharCode\x00" as *u8) == 1 { bid = BI_STR_FROMCHARCODE } } } // String.fromCharCode (mirrors js_eval_call)
3051 if rt == VAL_GLOBALNS { // user statics (Object.extend) WIN over natives (mirrors BC_CALLM)
3052 let t2g: *i64 = (vs[VS_T2]) as *i64
3053 if obj_get(js_ns_statics(rp), key, t2g) == 1 {
3054 if t2g[0] == VAL_FUNC {
3055 let recg: *i64 = (t2g[1]) as *i64
3056 if recg[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
3057 vmx_callfn(vs, recg, argc, base, 1, rt, rp, 0)
3058 if vs[VS_RUN] == 0 { return 0 }
3059 let fnaddrg: *i64 = (vs[VS_FNADDR]) as *i64
3060 let offg: i64 = fnaddrg[recg[1]]
3061 if offg < 0 { vmx_halt(vs); return 0 }
3062 return vs[VS_CBBASE] + offg
3063 }
3064 if t2g[0] == VAL_NATIVE { vmx_native(vs, t2g[1], base, argc, 1); return 0 }
3065 }
3066 }
3067 if bid != 0 { vmx_native(vs, bid as i64, base, argc, 1); return 0 }
3068 // USER String/Array.prototype method -- native builtins already won; mirrors the interpreter BC_CALLM
3069 // fallback and the tree-tier js_eval_call. VM-tier run => the stored fn is a VMF closure; jump to its
3070 // JIT entry (or halt if this callee wasn't compiled, same as every other closure path here).
3071 if rt == VAL_STR {
3072 let tps: *i64 = (vs[VS_T2]) as *i64
3073 let fps: i64 = js_userproto_method(VAL_STR, key, tps)
3074 if fps != 0 {
3075 let recs: *i64 = fps as *i64
3076 if recs[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
3077 vmx_callfn(vs, recs, argc, base, 1, rt, rp, 0)
3078 if vs[VS_RUN] == 0 { return 0 }
3079 let fnaddrs: *i64 = (vs[VS_FNADDR]) as *i64
3080 let offs: i64 = fnaddrs[recs[1]]
3081 if offs < 0 { vmx_halt(vs); return 0 }
3082 return vs[VS_CBBASE] + offs
3083 }
3084 }
3085 if rt == VAL_ARRAY {
3086 let tpa: *i64 = (vs[VS_T2]) as *i64
3087 let fpa: i64 = js_userproto_method(VAL_ARRAY, key, tpa)
3088 if fpa != 0 {
3089 let reca: *i64 = fpa as *i64
3090 if reca[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
3091 vmx_callfn(vs, reca, argc, base, 1, rt, rp, 0)
3092 if vs[VS_RUN] == 0 { return 0 }
3093 let fnaddra: *i64 = (vs[VS_FNADDR]) as *i64
3094 let offa: i64 = fnaddra[reca[1]]
3095 if offa < 0 { vmx_halt(vs); return 0 }
3096 return vs[VS_CBBASE] + offa
3097 }
3098 }
3099 if rt == VAL_FUNC { // Function.prototype.call/.apply (mirrors the interpreter BC_CALLM)
3100 var camode: i64 = 0 - 1
3101 if ev_key_is(key, "call\x00" as *u8) == 1 { camode = 0 }
3102 if ev_key_is(key, "apply\x00" as *u8) == 1 { camode = 1 }
3103 if camode >= 0 {
3104 let rec: *i64 = rp as *i64
3105 if rec[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
3106 vmx_call_apply(vs, rec, base, argc, camode)
3107 if vs[VS_RUN] == 0 { return 0 }
3108 let fnaddr: *i64 = (vs[VS_FNADDR]) as *i64
3109 let off: i64 = fnaddr[rec[1]]
3110 if off < 0 { vmx_halt(vs); return 0 }
3111 return vs[VS_CBBASE] + off
3112 }
3113 // own/inherited method on the function object (Ctor.inheritsFrom via Object.prototype)
3114 let t1f: *i64 = (vs[VS_T1]) as *i64
3115 t1f[0] = rt; t1f[1] = rp
3116 let t2f: *i64 = (vs[VS_T2]) as *i64
3117 if ev_get_prop(t1f, key, t2f) == 0 {
3118 if t2f[0] == VAL_FUNC {
3119 let recf: *i64 = (t2f[1]) as *i64
3120 if recf[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
3121 vmx_callfn(vs, recf, argc, base, 1, rt, rp, 0)
3122 if vs[VS_RUN] == 0 { return 0 }
3123 let fnaddr2: *i64 = (vs[VS_FNADDR]) as *i64
3124 let off2: i64 = fnaddr2[recf[1]]
3125 if off2 < 0 { vmx_halt(vs); return 0 }
3126 return vs[VS_CBBASE] + off2
3127 }
3128 if t2f[0] == VAL_NATIVE { vmx_native(vs, t2f[1], base, argc, 1); return 0 }
3129 }
3130 }
3131 if rt == VAL_OBJECT {
3132 let t1: *i64 = (vs[VS_T1]) as *i64
3133 t1[0] = rt
3134 t1[1] = rp
3135 let t2: *i64 = (vs[VS_T2]) as *i64
3136 if ev_get_prop(t1, key, t2) == 0 {
3137 if t2[0] == VAL_FUNC {
3138 let rec: *i64 = (t2[1]) as *i64
3139 if rec[0] != VMF_MAGIC { vmx_halt(vs); return 0 }
3140 let mfidx: i64 = rec[1]
3141 let mftab: *i64 = (vs[VS_FTAB]) as *i64
3142 let mfb: i64 = mfidx * FT_ENT
3143 if mftab[mfb + 3] == 0 {
3144 // FAST PATH (P6 rung-2): inline vmx_callfn(...,thisflag=1, ttag=rt, tpay=rp) for the common
3145 // method shape -- object methods are Octane's hottest call. fl!=0 (rest/arguments) falls
3146 // through to vmx_callfn. BYTE-IDENTICAL to it; mirrors jit_call_prep's inline.
3147 let mnv: i64 = mftab[mfb + 2]
3148 let mnp: i64 = mftab[mfb + 1]
3149 let mhp0: i64 = vs[VS_HP]
3150 let mnb: i64 = ((ENVR_HDR + mnv * 2) * 8 + 7) / 8 * 8
3151 if (mhp0 + mnb) > VMC_ARENA { return vmx_fatal(vs) }
3152 vs[VS_HP] = mhp0 + mnb
3153 let mnep: i64 = vs[VS_ARENA] + mhp0
3154 let mne: *i64 = mnep as *i64
3155 mne[0] = rec[2]; mne[1] = 1; mne[2] = rt; mne[3] = rp
3156 var mi: i64 = 0
3157 while mi < mnp {
3158 let md: i64 = ENVR_HDR + mi * 2
3159 if mi < argc { let ms: i64 = (base + 1 + mi) * 2; mne[md] = stack[ms]; mne[md + 1] = stack[ms + 1] }
3160 else { mne[md] = VAL_UNDEF; mne[md + 1] = 0 }
3161 mi = mi + 1
3162 }
3163 let mfp: i64 = vs[VS_FP]
3164 if mfp >= VMC_MAXFR { return vmx_fatal(vs) }
3165 let mfr: *i64 = (vs[VS_FRAMES]) as *i64
3166 let mbb: i64 = mfp * FR_ENT
3167 mfr[mbb] = vs[VS_PC] + 2
3168 mfr[mbb + 1] = vs[VS_ENV]
3169 mfr[mbb + 2] = base
3170 mfr[mbb + 3] = 0
3171 mfr[mbb + 4] = mhp0
3172 mfr[mbb + 5] = vmx_esc
3173 vs[VS_FP] = mfp + 1
3174 vs[VS_ENV] = mnep
3175 vs[VS_PC] = mftab[mfb]
3176 vs[VS_SP] = base
3177 } else {
3178 vmx_callfn(vs, rec, argc, base, 1, rt, rp, 0)
3179 }
3180 if vs[VS_RUN] == 0 { return 0 }
3181 let fnaddr: *i64 = (vs[VS_FNADDR]) as *i64
3182 let off: i64 = fnaddr[mfidx]
3183 if off < 0 { vmx_halt(vs); return 0 }
3184 return vs[VS_CBBASE] + off
3185 }
3186 if t2[0] == VAL_NATIVE { vmx_native(vs, t2[1], base, argc, 1); return 0 }
3187 }
3188 }
3189 vmx_halt(vs)
3190 return 0
3191}
3192// prep for BC_NEW: native ctor (XMLHttpRequest) constructs inline (return 0); a VM-closure ctor gets a
3193// fresh `this` object + frame (newobj kept unless the ctor returns an object -- jit_retv_prep handles it)
3194// and returns its native entry. `arg`=argc.
3195func jit_new_prep(vs: *i64, argc: i64) -> i64 {
3196 let sp: i64 = vs[VS_SP]
3197 let base: i64 = sp - argc - 1
3198 let stack: *i64 = (vs[VS_STACK]) as *i64
3199 let ft: i64 = stack[base * 2]
3200 let fpay: i64 = stack[base * 2 + 1]
3201 if ft == VAL_NATIVE {
3202 let t2: *i64 = (vs[VS_T2]) as *i64
3203 let ab: *i64 = (vs[VS_ARGBUF]) as *i64
3204 var ci: i64 = 0
3205 while ci < argc { let sj: i64 = (base + 1 + ci) * 2; ab[ci * 2] = stack[sj]; ab[ci * 2 + 1] = stack[sj + 1]; ci = ci + 1 }
3206 if js_construct_native_args(fpay, ab, argc, t2) == 1 { vmx_halt(vs); return 0 }
3207 stack[base * 2] = t2[0]
3208 stack[base * 2 + 1] = t2[1]
3209 vs[VS_SP] = base + 1
3210 return 0
3211 }
3212 if ft == VAL_FUNC {
3213 let rec: *i64 = fpay as *i64
3214 if rec[0] != VMF_MAGIC { vmx_fatal(vs); return 0 }
3215 let o: *i64 = obj_new()
3216 obj_set_proto(o, func_prototype(fpay)) // link instance -> Foo.prototype BEFORE the ctor runs
3217 let nfidx: i64 = rec[1]
3218 let nftab: *i64 = (vs[VS_FTAB]) as *i64
3219 let nfb: i64 = nfidx * FT_ENT
3220 if nftab[nfb + 3] == 0 {
3221 // FAST PATH (P6 rung-2): inline vmx_callfn(...,thisflag=1, ttag=VAL_OBJECT, tpay=o, newobj=o) for the
3222 // common ctor shape -- `new P()` object churn (K4) was the last un-inlined call path. newobj=o so a
3223 // ctor that doesn't return an object yields `this` (RETV handles it). BYTE-IDENTICAL to vmx_callfn.
3224 let nnv: i64 = nftab[nfb + 2]
3225 let nnp: i64 = nftab[nfb + 1]
3226 let nhp0: i64 = vs[VS_HP]
3227 let nnb: i64 = ((ENVR_HDR + nnv * 2) * 8 + 7) / 8 * 8
3228 if (nhp0 + nnb) > VMC_ARENA { return vmx_fatal(vs) }
3229 vs[VS_HP] = nhp0 + nnb
3230 let nnep: i64 = vs[VS_ARENA] + nhp0
3231 let nne: *i64 = nnep as *i64
3232 nne[0] = rec[2]; nne[1] = 1; nne[2] = VAL_OBJECT; nne[3] = o as i64
3233 var ni: i64 = 0
3234 while ni < nnp {
3235 let nd: i64 = ENVR_HDR + ni * 2
3236 if ni < argc { let ns: i64 = (base + 1 + ni) * 2; nne[nd] = stack[ns]; nne[nd + 1] = stack[ns + 1] }
3237 else { nne[nd] = VAL_UNDEF; nne[nd + 1] = 0 }
3238 ni = ni + 1
3239 }
3240 let nfp: i64 = vs[VS_FP]
3241 if nfp >= VMC_MAXFR { return vmx_fatal(vs) }
3242 let nfr: *i64 = (vs[VS_FRAMES]) as *i64
3243 let nbb: i64 = nfp * FR_ENT
3244 nfr[nbb] = vs[VS_PC] + 2
3245 nfr[nbb + 1] = vs[VS_ENV]
3246 nfr[nbb + 2] = base
3247 nfr[nbb + 3] = o as i64
3248 nfr[nbb + 4] = nhp0
3249 nfr[nbb + 5] = vmx_esc
3250 vs[VS_FP] = nfp + 1
3251 vs[VS_ENV] = nnep
3252 vs[VS_PC] = nftab[nfb]
3253 vs[VS_SP] = base
3254 } else {
3255 vmx_callfn(vs, rec, argc, base, 1, VAL_OBJECT, o as i64, o as i64)
3256 }
3257 if vs[VS_RUN] == 0 { return 0 }
3258 let fnaddr: *i64 = (vs[VS_FNADDR]) as *i64
3259 let off: i64 = fnaddr[nfidx]
3260 if off < 0 { vmx_halt(vs); return 0 }
3261 return vs[VS_CBBASE] + off
3262 }
3263 if ft == VAL_GLOBALNS { if fpay == NS_OBJECT { // new Object() -> {}
3264 let o: *i64 = obj_new()
3265 stack[base * 2] = VAL_OBJECT; stack[base * 2 + 1] = o as i64
3266 vs[VS_SP] = base + 1
3267 return 0
3268 } }
3269 vmx_halt(vs)
3270 return 0
3271}
3272// emit a CALL-family op: sync sp+env, call `prepaddr`(vs,arg); bail if it halted; if it returned a native
3273// target, save the caller env (2 pushes = 16-align), switch env/sp to the callee, hardware-CALL the target,
3274// restore, reload sp. A native-builtin/inline case (prep returns 0, run!=0) just reloads. Shared by
3275// CALL (jit_call_prep) / CALLM (jit_callm_prep) / NEW (jit_new_prep).
3276func jit_emit_callseq(cb: *u8, pb: *i64, prepaddr: i64, arg: i64, pc: i64, bsite: *i64, bcnt: *i64) -> i64 {
3277 xe_mov_rr(cb, pb, RAX, R15)
3278 xe_shr_imm(cb, pb, RAX, 4)
3279 xe_store(cb, pb, R12, VS_SP * 8, RAX)
3280 xe_store(cb, pb, R12, VS_ENV * 8, R14)
3281 xe_mov_rr(cb, pb, RDI, R12)
3282 xe_mov_ri32(cb, pb, RSI, arg)
3283 xe_mov_ri64(cb, pb, R10, prepaddr)
3284 xe_call_reg(cb, pb, R10) // RAX = native target | 0
3285 xe_load(cb, pb, RCX, R12, VS_RUN * 8)
3286 xe_cmp_ri32(cb, pb, RCX, 0)
3287 let bail1: i64 = xe_jcc(cb, pb, CC_E) // prep halted -> bail
3288 bsite[bcnt[0]] = bail1
3289 bcnt[0] = bcnt[0] + 1
3290 xe_cmp_ri32(cb, pb, RAX, 0)
3291 let jnd: i64 = xe_jcc(cb, pb, CC_E) // RAX==0 -> native builtin done -> reload
3292 xe_push(cb, pb, R14) // save caller env (+ pad below for 16-align)
3293 xe_push(cb, pb, R14)
3294 xe_load(cb, pb, R14, R12, VS_ENV * 8) // callee env
3295 xe_load(cb, pb, R15, R12, VS_SP * 8)
3296 xe_shl_imm(cb, pb, R15, 4) // callee sp bytes = base*16
3297 xe_call_reg(cb, pb, RAX) // hardware-call the callee native entry
3298 xe_pop(cb, pb, R14) // discard pad
3299 xe_pop(cb, pb, R14) // restore caller env
3300 xe_load(cb, pb, RCX, R12, VS_RUN * 8)
3301 xe_cmp_ri32(cb, pb, RCX, 0)
3302 let bail2: i64 = xe_jcc(cb, pb, CC_E) // callee bailed -> bail
3303 bsite[bcnt[0]] = bail2
3304 bcnt[0] = bcnt[0] + 1
3305 let reloadoff: i64 = pb[0]
3306 xe_patch(cb, jnd, reloadoff)
3307 xe_load(cb, pb, R15, R12, VS_SP * 8)
3308 xe_shl_imm(cb, pb, R15, 4) // R15 = vs[VS_SP]*16 (result at frame base)
3309 return 0
3310}
3311// emit a GC SAFEPOINT (only at JIT loop back-edges, only when GC is enabled). The poll is INLINED: bake the
3312// addresses of gc_since/gc_threshold and compare in-place -- the common no-collect case is 2 loads + a cmp +
3313// a NOT-TAKEN branch (no call). Only when a collection is DUE do we sync R15(sp)/R14(env) to vs, call
3314// gc_collect_vm, and reload (non-moving => identical). RAX/RDX/RCX dead between ops; RBX/R12/R13 callee-saved.
3315func jit_emit_safepoint(cb: *u8, pb: *i64) -> i64 {
3316 xe_mov_ri64(cb, pb, RAX, gc_since_addr())
3317 xe_load(cb, pb, RDX, RAX, 0) // RDX = gc_since
3318 xe_mov_ri64(cb, pb, RAX, gc_threshold_addr())
3319 xe_load(cb, pb, RCX, RAX, 0) // RCX = gc_threshold
3320 xe_cmp_rr(cb, pb, RDX, RCX) // gc_since - gc_threshold
3321 let skip: i64 = xe_jcc(cb, pb, CC_L) // gc_since < threshold (signed; both >=0) -> skip collect
3322 xe_mov_rr(cb, pb, RAX, R15)
3323 xe_shr_imm(cb, pb, RAX, 4)
3324 xe_store(cb, pb, R12, VS_SP * 8, RAX)
3325 xe_store(cb, pb, R12, VS_ENV * 8, R14)
3326 xe_mov_rr(cb, pb, RDI, R12)
3327 xe_mov_ri64(cb, pb, R10, (&gc_collect_vm) as i64)
3328 xe_call_reg(cb, pb, R10)
3329 xe_load(cb, pb, R14, R12, VS_ENV * 8)
3330 xe_load(cb, pb, R15, R12, VS_SP * 8)
3331 xe_shl_imm(cb, pb, R15, 4)
3332 xe_patch(cb, skip, pb[0]) // skip: -> just past the collect call
3333 return 0
3334}
3335// emit BC_RETV: sync sp, call retv_prep (result move + frame pop), undo the body's align sub, `ret`.
3336func jit_emit_retv(cb: *u8, pb: *i64) -> i64 {
3337 xe_mov_rr(cb, pb, RAX, R15)
3338 xe_shr_imm(cb, pb, RAX, 4)
3339 xe_store(cb, pb, R12, VS_SP * 8, RAX)
3340 xe_mov_rr(cb, pb, RDI, R12)
3341 xe_mov_ri64(cb, pb, R10, (&jit_retv_prep) as i64)
3342 xe_call_reg(cb, pb, R10)
3343 xe_add_ri32(cb, pb, RSP, 8) // undo the body-entry align sub
3344 xe_ret(cb, pb)
3345 return 0
3346}
3347// INLINE SHAPE-GUARDED GETPROP (the native Sparkplug property IC -- the payoff of P3 shapes). Fast path:
3348// base is a VAL_OBJECT whose shape == the per-site cached shape -> O(1) slot load, ZERO calls. Any miss
3349// (non-object receiver / shape mismatch / first touch) -> the call-threading slow path, which runs the
3350// full vm_ext GETPROP (handles strings/arrays/globalns + FILLS the IC so the next hit inlines).
3351func jit_getprop_inline(cb: *u8, pb: *i64, i: i64, arg: i64, bsite: *i64, bcnt: *i64) -> i64 {
3352 let pc: i64 = i * 2
3353 let icd: i64 = i * POLY_WAYS * 16 // byte offset of this site's WAY-0 (pc*POLY_WAYS i64)
3354 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16) // base tag
3355 xe_cmp_ri32(cb, pb, RAX, VAL_OBJECT)
3356 let sne: i64 = xe_jcc(cb, pb, CC_NE) // not object -> slow
3357 xe_load_idx(cb, pb, RCX, RBX, R15, 0 - 8) // obj ptr
3358 xe_load(cb, pb, RDX, RCX, 8) // obj shape = o[1]
3359 xe_load(cb, pb, RSI, R13, icd) // cached shape
3360 xe_cmp_rr(cb, pb, RDX, RSI)
3361 let sne2: i64 = xe_jcc(cb, pb, CC_NE) // shape miss -> slow
3362 xe_load(cb, pb, RDI, R13, icd + 8) // cached slot offset
3363 xe_mov_ri32(cb, pb, R9, OBJ_ENT * 8)
3364 xe_imul_rr(cb, pb, RDI, R9) // off * (OBJ_ENT*8)
3365 xe_load(cb, pb, RCX, RCX, OBJ_BACK * 8) // RCX = backing ptr (growable objects)
3366 xe_load_idx(cb, pb, R8, RCX, RDI, 1 * 8) // value tag (backing[slot*ENT + 1])
3367 xe_load_idx(cb, pb, R9, RCX, RDI, 2 * 8) // value pay
3368 xe_store_idx(cb, pb, RBX, R15, 0 - 16, R8) // overwrite base cell with the value (sp unchanged)
3369 xe_store_idx(cb, pb, RBX, R15, 0 - 8, R9)
3370 let jd: i64 = xe_jmp(cb, pb)
3371 let slowoff: i64 = pb[0]
3372 xe_patch(cb, sne, slowoff)
3373 xe_patch(cb, sne2, slowoff)
3374 jit_emit_ct(cb, pb, BC_GETPROP, arg, pc, bsite, bcnt)
3375 let doneoff: i64 = pb[0]
3376 xe_patch(cb, jd, doneoff)
3377 return 0
3378}
3379// SETPROP inline (UPDATE case): stack [base(sp-2), val(sp-1)] -> obj[key]=val, result val at base cell, sp--.
3380// Shape-guarded like getprop: if base is an object at the cached shape, overwrite the cached slot's VALUE
3381// in place (no count/shape/key change) -- reuses the poly-IC that vm_ext BC_SETPROP fills on the found path.
3382// Miss (new-prop ADD, wrong shape, non-object) -> jit_ct -> vm_ext (does add/update + DOM hook + repopulate).
3383// Only emitted for NON-DOM keys (caller excludes innerHTML/textContent), so skipping js_dom_write_hook (a
3384// no-op for other keys) is safe. Scratch: RAX(base tag), R8/R9(val), RCX(obj), RDX/RSI(shape), RDI/R10(slot).
3385func jit_setprop_inline(cb: *u8, pb: *i64, i: i64, arg: i64, bsite: *i64, bcnt: *i64) -> i64 {
3386 let pc: i64 = i * 2
3387 let icd: i64 = i * POLY_WAYS * 16
3388 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 32) // base tag
3389 xe_cmp_ri32(cb, pb, RAX, VAL_OBJECT)
3390 let sne: i64 = xe_jcc(cb, pb, CC_NE) // not object -> slow
3391 xe_load_idx(cb, pb, R8, RBX, R15, 0 - 16) // val tag (saved)
3392 xe_load_idx(cb, pb, R9, RBX, R15, 0 - 8) // val pay (saved)
3393 xe_load_idx(cb, pb, RCX, RBX, R15, 0 - 24) // obj ptr
3394 xe_load(cb, pb, RDX, RCX, 8) // obj shape = o[1]
3395 xe_load(cb, pb, RSI, R13, icd) // cached shape
3396 xe_cmp_rr(cb, pb, RDX, RSI)
3397 let sne2: i64 = xe_jcc(cb, pb, CC_NE) // shape miss -> slow
3398 xe_load(cb, pb, RDI, R13, icd + 8) // cached slot
3399 xe_mov_ri32(cb, pb, R10, OBJ_ENT * 8)
3400 xe_imul_rr(cb, pb, RDI, R10) // slot * (OBJ_ENT*8)
3401 xe_load(cb, pb, RCX, RCX, OBJ_BACK * 8) // RCX = backing ptr (growable objects)
3402 xe_store_idx(cb, pb, RCX, RDI, 1 * 8, R8) // backing[slot*ENT + 1] value tag = val
3403 xe_store_idx(cb, pb, RCX, RDI, 2 * 8, R9) // backing[slot*ENT + 2] value pay = val
3404 xe_store_idx(cb, pb, RBX, R15, 0 - 32, R8) // result: base cell (sp-2) = val
3405 xe_store_idx(cb, pb, RBX, R15, 0 - 24, R9)
3406 xe_sub_ri32(cb, pb, R15, 16) // sp--
3407 let jd: i64 = xe_jmp(cb, pb)
3408 let slowoff: i64 = pb[0]
3409 xe_patch(cb, sne, slowoff)
3410 xe_patch(cb, sne2, slowoff)
3411 jit_emit_ct(cb, pb, BC_SETPROP, arg, pc, bsite, bcnt)
3412 let doneoff: i64 = pb[0]
3413 xe_patch(cb, jd, doneoff)
3414 return 0
3415}
3416// Compile `code`/`np` (+ `pool` for PUSHK constants, `ftab`/`ftabn` for function entries, `vs` to record
3417// the code base + function-address table) to native x86. Returns the code address, or 0 to DECLINE (any
3418// untemplatable op / too big / buffer overflow) -> the caller runs vm_loop instead.
3419// REGISTER-ALLOCATION eligibility detector (the optimizing tier, step 1). Decides whether a top-level program
3420// is a "hot int loop" that can be recompiled with locals + operand-stack all in REGISTERS (boxed [tag,pay], no
3421// type assumption -> correct by construction), eliminating the per-iteration env/operand-stack MEMORY round-trips
3422// that are the true critical-path cost (the hardware law: only cutting real memory dependencies -- not
3423// instruction count -- reduces wall-clock). Conservative + DECLINE-safe: returns 1 ONLY for a program whose every
3424// op is register-safe (PUSH/LOAD/STORE/POP/DUP/BINOP[the 10 int ops]/JMP/JMPF/JMPT/RET -- NO PUSHK float/string
3425// const, NO calls/objects/arrays/closures/try, NO float-producing DIV), that HAS a loop (back-edge), and whose
3426// register pressure (locals + max operand-stack depth, boxed => 2 regs each) fits the budget. Anything else -> 0
3427// (jit_compile falls through to the existing stack-based codegen -> zero risk). Returns: 1 eligible, 0 unsafe-op,
3428// 2 no-loop, 3 pressure. (Codegen consuming this lands next; today this is pure analysis, wired to a probe.)
3429const RA_REGBUDGET: i64 = 6 // max boxed values (locals + peak stack) that fit x86-64 GP regs (2 regs each, minus RSP+scratch)
3430func jit_intloop_eligible(code: *i64, np: i64) -> i64 {
3431 if np <= 0 { return 0 }
3432 var has_backedge: i64 = 0
3433 var maxslot: i64 = 0 - 1
3434 var depth: i64 = 0
3435 var maxdepth: i64 = 0
3436 var i: i64 = 0
3437 while i < np {
3438 let op: i64 = code[i * 2]
3439 let arg: i64 = code[i * 2 + 1]
3440 var ok: i64 = 0
3441 if op == BC_PUSH { ok = 1; depth = depth + 1 }
3442 if op == BC_LOAD { ok = 1; depth = depth + 1; if arg > maxslot { maxslot = arg } }
3443 if op == BC_STORE { ok = 1; depth = depth - 1; if arg > maxslot { maxslot = arg } }
3444 if op == BC_POP { ok = 1; depth = depth - 1 }
3445 if op == BC_DUP { ok = 1; depth = depth + 1 }
3446 if op == BC_BINOP {
3447 if jit_binop_fusable(arg) == 1 {
3448 ok = 1; depth = depth - 1 // the 10 int-safe ops (NO DIV/bitwise/strict-eq)
3449 if arg >= OP_LT { if arg <= OP_NE { // a COMPARISON (OP_LT..OP_NE): its BOOL result must
3450 var nb: i64 = 0 // feed a branch DIRECTLY -- the unboxed-int codegen
3451 if i + 1 < np { // emits cmp+jcc and never materializes a BOOL value,
3452 let nop2: i64 = code[(i + 1) * 2] // so a stored/returned comparison result (would need
3453 if nop2 == BC_JMPF { nb = 1 } // a VAL_BOOL tag) is unrepresentable -> decline.
3454 if nop2 == BC_JMPT { nb = 1 }
3455 }
3456 if nb == 0 { return 0 }
3457 } }
3458 }
3459 }
3460 if op == BC_JMP { ok = 1; if arg / 2 <= i { has_backedge = 1 } }
3461 if op == BC_JMPF { ok = 1; depth = depth - 1; if arg / 2 <= i { has_backedge = 1 } }
3462 if op == BC_JMPT { ok = 1; depth = depth - 1; if arg / 2 <= i { has_backedge = 1 } }
3463 if op == BC_RET { ok = 1 }
3464 if ok == 0 { return 0 } // any non-int-safe opcode (PUSHK/CALL/GETPROP/CLOSURE/...) -> decline
3465 if depth < 0 { return 0 } // malformed stack -> decline
3466 if depth > maxdepth { maxdepth = depth }
3467 i = i + 1
3468 }
3469 if has_backedge == 0 { return 2 } // no loop -> register allocation not worth it
3470 let nlocals: i64 = maxslot + 1
3471 if nlocals + maxdepth > RA_REGBUDGET { return 3 } // too much register pressure (v1 declines rather than spill)
3472 if nlocals > 256 { return 0 }
3473 // PASS 2 (branch-target + MOD soundness): compute istgt[] (jump targets). (a) a fused comparison's branch
3474 // (the JMPF/JMPT right after it) must NOT itself be a jump target -- a jump landing ON the branch would
3475 // expect a popped bool that the cmp+jcc fusion never materializes. (b) MOD only with a NONZERO CONSTANT
3476 // RHS (prev instr = BC_PUSH k, k!=0): `a % var` could divide by 0 at runtime -> JS NaN, which an unboxed
3477 // int register cannot represent (and v1 has no deopt).
3478 let istgt2: *i64 = sys_mmap(np * 8 + 8) as *i64
3479 var j: i64 = 0
3480 while j < np {
3481 let jop: i64 = code[j * 2]
3482 var isj: i64 = 0
3483 if jop == BC_JMP { isj = 1 }
3484 if jop == BC_JMPF { isj = 1 }
3485 if jop == BC_JMPT { isj = 1 }
3486 if isj == 1 { let tg: i64 = code[j * 2 + 1] / 2; if tg >= 0 { if tg < np { istgt2[tg] = 1 } } }
3487 j = j + 1
3488 }
3489 j = 0
3490 while j < np {
3491 if code[j * 2] == BC_BINOP {
3492 let bop: i64 = code[j * 2 + 1]
3493 if bop >= OP_LT { if bop <= OP_NE {
3494 if j + 1 < np { if istgt2[j + 1] == 1 { return 0 } } // jump lands ON the fused branch -> decline
3495 } }
3496 if bop == OP_MOD {
3497 if j < 1 { return 0 }
3498 if code[(j - 1) * 2] != BC_PUSH { return 0 } // MOD RHS must be a const...
3499 if code[(j - 1) * 2 + 1] == 0 { return 0 } // ...and nonzero (no NaN in unboxed regs)
3500 }
3501 }
3502 j = j + 1
3503 }
3504 // PASS 3 (PROLOGUE-DOMINANCE init, unboxed soundness): every slot that is LOADed or STOREd anywhere must be
3505 // FIRST-STOREd in the straight-line PROLOGUE (instructions before the first branch or jump target), which
3506 // executes unconditionally exactly once. A merely-linear earlier STORE is NOT enough -- a conditionally-
3507 // skipped STORE (e.g. inside an if) leaves the register holding garbage on the not-taken path, and a slot
3508 // never stored reads `undefined` (not an int). Conservative: body-temps (`var t=...` inside the loop)
3509 // decline -> fall back to the stack JIT (correct, just not register-fast).
3510 var proend: i64 = 0
3511 while proend < np {
3512 let pop2: i64 = code[proend * 2]
3513 var stop: i64 = 0
3514 if pop2 == BC_JMP { stop = 1 }
3515 if pop2 == BC_JMPF { stop = 1 }
3516 if pop2 == BC_JMPT { stop = 1 }
3517 if istgt2[proend] == 1 { stop = 1 }
3518 if stop == 1 { break }
3519 proend = proend + 1
3520 }
3521 let written: *i64 = sys_mmap(256 * 8) as *i64 // zeroed by mmap
3522 j = 0
3523 while j < np {
3524 let op2: i64 = code[j * 2]
3525 let arg2: i64 = code[j * 2 + 1]
3526 if op2 == BC_LOAD { if written[arg2] == 0 { return 0 } } // read of a non-prologue-init slot
3527 if op2 == BC_STORE {
3528 if written[arg2] == 0 {
3529 if j < proend { written[arg2] = 1 } else { return 0 } // first STORE outside the prologue
3530 }
3531 }
3532 j = j + 1
3533 }
3534 return 1
3535}
3536// value-register map for the int-loop tier: slot k (locals first, then operand-stack) -> a CALLER-SAVED GP reg
3537// (no push/pop needed; the blob makes NO calls). RSI/RDI are free after the prologue copies vs(RDI) into RBP.
3538func ra_reg(k: i64) -> i64 {
3539 if k == 0 { return RSI }
3540 if k == 1 { return RDI }
3541 if k == 2 { return R8 }
3542 if k == 3 { return R9 }
3543 if k == 4 { return R10 }
3544 return R11
3545}
3546// comparison op -> jcc condition code: direct (jump when TRUE, for JMPT) / inverted (jump when FALSE, for JMPF).
3547func ra_ccdir(op: i64) -> i64 {
3548 if op == OP_LT { return CC_L }
3549 if op == OP_LE { return CC_LE }
3550 if op == OP_GT { return CC_G }
3551 if op == OP_GE { return CC_GE }
3552 if op == OP_EQ { return CC_E }
3553 return CC_NE
3554}
3555func ra_ccinv(op: i64) -> i64 {
3556 if op == OP_LT { return CC_GE }
3557 if op == OP_LE { return CC_G }
3558 if op == OP_GT { return CC_LE }
3559 if op == OP_GE { return CC_L }
3560 if op == OP_EQ { return CC_NE }
3561 return CC_E
3562}
3563// ---- const-divisor STRENGTH REDUCTION (sovereign magic-number generator, Granlund-Montgomery/HD 10-4) ----
3564// `a % k` (k a nonzero compile-time const) emits multiply-shift (~10cy) instead of 64-bit idiv (~40-90cy):
3565// q = sar( mulhi_s(M, a) (+ a if M wrapped negative), s ) - (a >> 63) ; r = a - q*|k|
3566// (sign of the divisor is irrelevant: a % k == a % |k|, remainder sign follows the DIVIDEND -- C99/x86/JS-int.)
3567// M,s are COMPUTED for any divisor (a generator, no hardcoded table) and then COMPILE-TIME LIAR-KILLED:
3568// ra_mod_emu re-implements the exact emitted sequence with an INDEPENDENT NishiLang mulhi (32-bit-split, no
3569// imul1) and is swept vs native `%` over a dense range + 64-bit edges; ANY mismatch -> the caller emits the
3570// proven idiv sequence instead. A wrong magic can therefore never reach emitted code.
3571func ra_ult(a: i64, b: i64) -> i64 { // unsigned a < b on raw i64 bits (no unsigned type needed)
3572 if a >= 0 {
3573 if b < 0 { return 1 }
3574 if a < b { return 1 }
3575 return 0
3576 }
3577 if b >= 0 { return 0 }
3578 if a < b { return 1 }
3579 return 0
3580}
3581func ra_mulhi_u(a: i64, b: i64) -> i64 { // high 64 bits of the UNSIGNED 128-bit product (raw bits in/out)
3582 let mask: i64 = 0xFFFFFFFF
3583 let a0: i64 = a & mask
3584 let a1: i64 = (a >> 32) & mask
3585 let b0: i64 = b & mask
3586 let b1: i64 = (b >> 32) & mask
3587 let p00: i64 = a0 * b0
3588 let p10: i64 = a1 * b0
3589 let p01: i64 = a0 * b1
3590 let p11: i64 = a1 * b1
3591 let mid: i64 = p10 + ((p00 >> 32) & mask) // bounded < 2^64: no wrap
3592 let mid2: i64 = p01 + (mid & mask)
3593 return p11 + ((mid >> 32) & mask) + ((mid2 >> 32) & mask)
3594}
3595func ra_mulhi_s(a: i64, b: i64) -> i64 { // signed mulhi from unsigned: identity hi_s = hi_u - (a<0?b:0) - (b<0?a:0)
3596 var h: i64 = ra_mulhi_u(a, b)
3597 if a < 0 { h = h - b }
3598 if b < 0 { h = h - a }
3599 return h
3600}
3601func ra_ashr1(x: i64) -> i64 { // arithmetic shift right 1 == floor(x/2) (div truncates -> fix odd negatives)
3602 if x < 0 { return (x - 1) / 2 }
3603 return x / 2
3604}
3605// Hacker's Delight 10-4 signed-magic for divisor ad (2 <= ad < 2^31). out[0]=M (raw bits, may wrap negative =
3606// the add-a correction case), out[1]=s. Unsigned 64-bit intermediates are carried as RAW BITS with ra_ult
3607// compares (q1/r1 range up to ~2^63). Returns 1 ok / 0 give-up (caller emits idiv).
3608func ra_magic(ad: i64, out: *i64) -> i64 {
3609 if ad < 2 { return 0 }
3610 var h62: i64 = 1
3611 var kk: i64 = 0
3612 while kk < 62 { h62 = h62 + h62; kk = kk + 1 } // 2^62 without shift ops
3613 let intmax: i64 = (h62 - 1) + h62 // 2^63 - 1
3614 let tm: i64 = ((intmax % ad) + 1) % ad // 2^63 mod ad
3615 let anc: i64 = intmax - tm // t-1-(t%ad) for t=2^63 (HD's |nc|); > 2^62 since tm < ad < 2^31
3616 var p: i64 = 63
3617 var q1: i64 = 1 // floor(2^63/anc) -- anc > 2^62 => exactly 1
3618 var r1: i64 = tm + 1 // 2^63 - anc
3619 var q2v: i64 = (intmax - tm + 1) / ad // floor(2^63/ad): 2^63-tm is an exact multiple of ad
3620 var r2: i64 = tm
3621 var guard: i64 = 0
3622 var done: i64 = 0
3623 while done == 0 {
3624 guard = guard + 1
3625 if guard > 100 { return 0 }
3626 p = p + 1
3627 q1 = q1 + q1
3628 r1 = r1 + r1 // raw bits (2*r1_true < 2^64: exact)
3629 if ra_ult(r1, anc) == 0 { q1 = q1 + 1; r1 = r1 - anc }
3630 q2v = q2v + q2v // raw bits
3631 r2 = r2 + r2 // < 2^32: plain arithmetic safe
3632 if r2 >= ad { q2v = q2v + 1; r2 = r2 - ad }
3633 let delta: i64 = ad - r2
3634 var cont: i64 = 0
3635 if ra_ult(q1, delta) == 1 { cont = 1 }
3636 if q1 == delta { if r1 == 0 { cont = 1 } }
3637 if cont == 0 { done = 1 }
3638 }
3639 out[0] = q2v + 1
3640 out[1] = p - 64
3641 return 1
3642}
3643// emulate the EXACT emitted x86 sequence (independent mulhi; explicit floor-shift; wrap-faithful) -> a % ad.
3644func ra_mod_emu(v: i64, ad: i64, M: i64, s: i64) -> i64 {
3645 var q: i64 = ra_mulhi_s(M, v)
3646 if M < 0 { q = q + v } // the `add RDX, a` correction (wraps like hardware)
3647 var k3: i64 = 0
3648 while k3 < s { q = ra_ashr1(q); k3 = k3 + 1 } // sar RDX, s
3649 if v < 0 { q = q + 1 } // sub RDX, (a sar 63) == q - (-1)
3650 return v - q * ad
3651}
3652// generate + LIAR-KILL: dense -3000..3000 sweep + 64-bit edges (INT_MAX/INT_MIN, 2^62, 2^40, 2^31 rims,
3653// multiples of ad near the rails) vs native %. 1 = verified-safe to emit; 0 = caller emits idiv.
3654func ra_magic_verified(ad: i64, out: *i64) -> i64 {
3655 if ra_magic(ad, out) == 0 { return 0 }
3656 let M: i64 = out[0]
3657 let s: i64 = out[1]
3658 var v: i64 = 0 - BC_MAGIC_3000
3659 while v <= BC_MAGIC_3000 {
3660 if ra_mod_emu(v, ad, M, s) != v % ad { return 0 }
3661 v = v + 1
3662 }
3663 var h62: i64 = 1
3664 var kk: i64 = 0
3665 while kk < 62 { h62 = h62 + h62; kk = kk + 1 }
3666 let intmax: i64 = (h62 - 1) + h62
3667 let intmin: i64 = (0 - intmax) - 1
3668 let ev: *i64 = sys_mmap(40 * 8) as *i64
3669 var n: i64 = 0
3670 ev[n] = intmax; n = n + 1
3671 ev[n] = intmax - 1; n = n + 1
3672 ev[n] = intmin; n = n + 1
3673 ev[n] = intmin + 1; n = n + 1
3674 ev[n] = h62; n = n + 1
3675 ev[n] = h62 - 1; n = n + 1
3676 ev[n] = 0 - h62; n = n + 1
3677 ev[n] = (0 - h62) + 1; n = n + 1
3678 ev[n] = BC_MAGIC_2147483647; n = n + 1
3679 ev[n] = 0 - BC_MAGIC_2147483647; n = n + 1
3680 ev[n] = BC_MAGIC_2147483648; n = n + 1
3681 ev[n] = 0 - BC_MAGIC_2147483648; n = n + 1
3682 ev[n] = BC_MAGIC_1099511627776 + 123; n = n + 1 // 2^40 + 123
3683 ev[n] = 0 - (BC_MAGIC_1099511627776 + 123); n = n + 1
3684 ev[n] = BC_MAGIC_1000000007; n = n + 1
3685 ev[n] = 0 - BC_MAGIC_1000000007; n = n + 1
3686 ev[n] = BC_MAGIC_1234567891234567; n = n + 1
3687 ev[n] = 0 - BC_MAGIC_1234567891234567; n = n + 1
3688 ev[n] = intmax - (intmax % ad); n = n + 1 // multiple of ad at the +rail
3689 ev[n] = (intmax - (intmax % ad)) - ad; n = n + 1
3690 ev[n] = ad * 3; n = n + 1
3691 ev[n] = 0 - (ad * 3); n = n + 1
3692 ev[n] = ad * 3 + 1; n = n + 1
3693 ev[n] = 0 - (ad * 3 + 1); n = n + 1
3694 var e: i64 = 0
3695 while e < n {
3696 let x: i64 = ev[e]
3697 if ra_mod_emu(x, ad, M, s) != x % ad { return 0 }
3698 e = e + 1
3699 }
3700 return 1
3701}
3702// THE INT-LOOP REGISTER TIER (optimizing-tier v1): recompile a detector-approved program with locals AND the
3703// operand stack held UNBOXED in x86 GP registers -- zero env/operand-stack memory traffic in the loop body (the
3704// true critical-path cost per the hardware law). SOUND BY CONSTRUCTION, not speculation: jit_intloop_eligible
3705// guarantees every value is an int (int consts only, no PUSHK/call/object/float-op, comparisons feed branches
3706// directly so no bool is ever materialized, MOD only by nonzero consts so no NaN, every touched slot prologue-
3707// initialized so no undefined) => the VAL_NUM tag is implicit and only payloads live in registers. NO deopt
3708// needed. NO GC safepoint needed: an eligible loop ALLOCATES NOTHING, so gc_since cannot advance during the
3709// blob (the stack JIT's safepoint compare would never fire either -- exactly equivalent). Depth at every
3710// instruction is tracked statically; any inconsistency at a join -> return 0 (caller falls through to the
3711// stack JIT, so a decline is always safe). At RET: sync prologue-initialized locals back to the env as boxed
3712// [VAL_NUM,pay] pairs (parity for the read-a-global-after-run path; non-initialized slots keep their arena-
3713// zero = VAL_UNDEF) + write [VAL_NUM,result] to vs[VS_OUT] + rc=0. Blob ABI: arg RDI = vs (copied to RBP,
3714// callee-saved, pushed/popped); value regs are all caller-saved.
3715func jit_intloop_compile(code: *i64, np: i64, vs: *i64) -> i64 {
3716 var maxslot: i64 = 0 - 1
3717 var i: i64 = 0
3718 while i < np {
3719 let op0: i64 = code[i * 2]
3720 if op0 == BC_LOAD { if code[i * 2 + 1] > maxslot { maxslot = code[i * 2 + 1] } }
3721 if op0 == BC_STORE { if code[i * 2 + 1] > maxslot { maxslot = code[i * 2 + 1] } }
3722 i = i + 1
3723 }
3724 let nl: i64 = maxslot + 1
3725 // recompute istgt + the prologue-initialized set (mirrors jit_intloop_eligible pass 2/3: proend = first
3726 // branch-or-target; written[] = slots first-STOREd before it) -- the RET local-sync writes ONLY these.
3727 let istgt: *i64 = sys_mmap(np * 8 + 8) as *i64
3728 var j: i64 = 0
3729 while j < np {
3730 let jop: i64 = code[j * 2]
3731 var isj: i64 = 0
3732 if jop == BC_JMP { isj = 1 }
3733 if jop == BC_JMPF { isj = 1 }
3734 if jop == BC_JMPT { isj = 1 }
3735 if isj == 1 { let tg: i64 = code[j * 2 + 1] / 2; if tg >= 0 { if tg < np { istgt[tg] = 1 } } }
3736 j = j + 1
3737 }
3738 var proend: i64 = 0
3739 while proend < np {
3740 let pop2: i64 = code[proend * 2]
3741 var stop: i64 = 0
3742 if pop2 == BC_JMP { stop = 1 }
3743 if pop2 == BC_JMPF { stop = 1 }
3744 if pop2 == BC_JMPT { stop = 1 }
3745 if istgt[proend] == 1 { stop = 1 }
3746 if stop == 1 { break }
3747 proend = proend + 1
3748 }
3749 let written: *i64 = sys_mmap(256 * 8) as *i64
3750 j = 0
3751 while j < proend { if code[j * 2] == BC_STORE { written[code[j * 2 + 1]] = 1 } j = j + 1 }
3752 let CBSZ: i64 = BC_MAGIC_65536
3753 let cb: *u8 = xe_mmap_rwx(CBSZ)
3754 if (cb as i64) <= 0 { return 0 }
3755 let pb: *i64 = sys_mmap(8) as *i64
3756 pb[0] = 0
3757 let noff: *i64 = sys_mmap(np * 8 + 8) as *i64
3758 let dat: *i64 = sys_mmap(np * 8 + 8) as *i64 // static stack depth at instr i (-1 = not yet known)
3759 var z: i64 = 0
3760 while z < np { dat[z] = 0 - 1; z = z + 1 }
3761 let fsite: *i64 = sys_mmap(np * 8 + 64) as *i64
3762 let ftgt: *i64 = sys_mmap(np * 8 + 64) as *i64
3763 let fcnt: *i64 = sys_mmap(8) as *i64
3764 fcnt[0] = 0
3765 xe_push(cb, pb, RBP) // prologue: RBP (callee-saved) = vs
3766 xe_mov_rr(cb, pb, RBP, RDI)
3767 var depth: i64 = 0
3768 var unreach: i64 = 0 // 1 = just after an unconditional JMP/RET
3769 var pendcc: i64 = 0 // comparison op awaiting its JMPF/JMPT (cmp already emitted)
3770 i = 0
3771 while i < np {
3772 if pb[0] > (CBSZ - 256) { return 0 }
3773 noff[i] = pb[0]
3774 if dat[i] >= 0 {
3775 if unreach == 1 { depth = dat[i]; unreach = 0 }
3776 else { if dat[i] != depth { return 0 } } // depth mismatch at a join -> decline (stack JIT runs it)
3777 } else {
3778 if unreach == 1 { return 0 } // unreachable + untargeted -> unexpected shape
3779 dat[i] = depth
3780 }
3781 let op: i64 = code[i * 2]
3782 let arg: i64 = code[i * 2 + 1]
3783 if op == BC_PUSH { xe_mov_ri64(cb, pb, ra_reg(nl + depth), arg); depth = depth + 1 }
3784 if op == BC_LOAD { xe_mov_rr(cb, pb, ra_reg(nl + depth), ra_reg(arg)); depth = depth + 1 }
3785 if op == BC_STORE { xe_mov_rr(cb, pb, ra_reg(arg), ra_reg(nl + depth - 1)); depth = depth - 1 }
3786 if op == BC_POP { depth = depth - 1 }
3787 if op == BC_DUP { xe_mov_rr(cb, pb, ra_reg(nl + depth), ra_reg(nl + depth - 1)); depth = depth + 1 }
3788 if op == BC_BINOP {
3789 let ra: i64 = ra_reg(nl + depth - 2)
3790 let rb: i64 = ra_reg(nl + depth - 1)
3791 if arg == OP_ADD { xe_add_rr(cb, pb, ra, rb); depth = depth - 1 }
3792 if arg == OP_SUB { xe_sub_rr(cb, pb, ra, rb); depth = depth - 1 }
3793 if arg == OP_MUL { xe_imul_rr(cb, pb, ra, rb); depth = depth - 1 }
3794 if arg == OP_MOD { // RHS = nonzero const (detector) -> no zero check
3795 // STRENGTH-REDUCE `a % k`: multiply-shift (~10cy) instead of 64-bit idiv (~40-90cy) whenever
3796 // the sovereign magic generator passes its compile-time liar-killer sweep; else the proven
3797 // idiv. |k|==1 -> the remainder is always 0. (rb holds k but is dead in the reduced paths --
3798 // one dead mov, off the critical path.)
3799 let kc: i64 = code[(i - 1) * 2 + 1]
3800 var ad2: i64 = kc
3801 if ad2 < 0 { ad2 = 0 - ad2 } // a % k == a % |k| (sign follows the dividend)
3802 var sr: i64 = 0
3803 let mg: *i64 = sys_mmap(16) as *i64
3804 if ad2 >= 2 { if ad2 < BC_MAGIC_2147483648 { sr = ra_magic_verified(ad2, mg) } }
3805 if sr == 1 {
3806 xe_mov_ri64(cb, pb, RAX, mg[0]) // M
3807 xe_imul1(cb, pb, ra) // RDX = mulhi_s(M, a)
3808 if mg[0] < 0 { xe_add_rr(cb, pb, RDX, ra) }
3809 if mg[1] > 0 { xe_sar_imm(cb, pb, RDX, mg[1]) }
3810 xe_mov_rr(cb, pb, RCX, ra)
3811 xe_sar_imm(cb, pb, RCX, 63)
3812 xe_sub_rr(cb, pb, RDX, RCX) // q = a / ad (truncated)
3813 xe_mov_ri64(cb, pb, RCX, ad2)
3814 xe_imul_rr(cb, pb, RDX, RCX) // q*ad
3815 xe_sub_rr(cb, pb, ra, RDX) // r = a - q*ad
3816 } else { if ad2 == 1 {
3817 xe_mov_ri64(cb, pb, ra, 0) // a % 1 == 0 (and a % -1 == 0)
3818 } else {
3819 xe_mov_rr(cb, pb, RAX, ra)
3820 xe_mov_rr(cb, pb, RCX, rb)
3821 xe_cqo(cb, pb)
3822 xe_idiv(cb, pb, RCX)
3823 xe_mov_rr(cb, pb, ra, RDX)
3824 } }
3825 depth = depth - 1
3826 }
3827 if arg >= OP_LT { if arg <= OP_NE {
3828 xe_cmp_rr(cb, pb, ra, rb) // flags live to the NEXT instr (the branch; nothing between)
3829 pendcc = arg
3830 depth = depth - 2 // both operands consumed; no bool is materialized
3831 } }
3832 }
3833 if op == BC_JMP {
3834 let s: i64 = xe_jmp(cb, pb)
3835 fsite[fcnt[0]] = s; ftgt[fcnt[0]] = arg / 2; fcnt[0] = fcnt[0] + 1
3836 let t: i64 = arg / 2
3837 if t < 0 { return 0 }
3838 if t >= np { return 0 }
3839 if dat[t] >= 0 { if dat[t] != depth { return 0 } } else { dat[t] = depth }
3840 unreach = 1
3841 }
3842 if op == BC_JMPF {
3843 var cc2: i64 = 0
3844 if pendcc != 0 { cc2 = ra_ccinv(pendcc); pendcc = 0 }
3845 else { xe_cmp_ri32(cb, pb, ra_reg(nl + depth - 1), 0); depth = depth - 1; cc2 = CC_E }
3846 let s: i64 = xe_jcc(cb, pb, cc2)
3847 fsite[fcnt[0]] = s; ftgt[fcnt[0]] = arg / 2; fcnt[0] = fcnt[0] + 1
3848 let t: i64 = arg / 2
3849 if t < 0 { return 0 }
3850 if t >= np { return 0 }
3851 if dat[t] >= 0 { if dat[t] != depth { return 0 } } else { dat[t] = depth }
3852 }
3853 if op == BC_JMPT {
3854 var cc2: i64 = 0
3855 if pendcc != 0 { cc2 = ra_ccdir(pendcc); pendcc = 0 }
3856 else { xe_cmp_ri32(cb, pb, ra_reg(nl + depth - 1), 0); depth = depth - 1; cc2 = CC_NE }
3857 let s: i64 = xe_jcc(cb, pb, cc2)
3858 fsite[fcnt[0]] = s; ftgt[fcnt[0]] = arg / 2; fcnt[0] = fcnt[0] + 1
3859 let t: i64 = arg / 2
3860 if t < 0 { return 0 }
3861 if t >= np { return 0 }
3862 if dat[t] >= 0 { if dat[t] != depth { return 0 } } else { dat[t] = depth }
3863 }
3864 if op == BC_RET {
3865 if depth < 1 { return 0 }
3866 xe_load(cb, pb, RCX, RBP, VS_ENV * 8) // sync prologue-init locals -> env as boxed [VAL_NUM,pay]
3867 var L: i64 = 0
3868 while L < nl {
3869 if written[L] == 1 {
3870 xe_mov_ri32(cb, pb, RAX, VAL_NUM)
3871 xe_store(cb, pb, RCX, (ENVR_HDR + L * 2) * 8, RAX)
3872 xe_store(cb, pb, RCX, (ENVR_HDR + L * 2) * 8 + 8, ra_reg(L))
3873 }
3874 L = L + 1
3875 }
3876 xe_load(cb, pb, RDX, RBP, VS_OUT * 8) // out = [VAL_NUM, top]
3877 xe_mov_ri32(cb, pb, RAX, VAL_NUM)
3878 xe_store(cb, pb, RDX, 0, RAX)
3879 xe_store(cb, pb, RDX, 8, ra_reg(nl + depth - 1))
3880 xe_mov_ri32(cb, pb, RAX, 0) // rc = 0
3881 xe_pop(cb, pb, RBP)
3882 xe_ret(cb, pb)
3883 unreach = 1
3884 }
3885 i = i + 1
3886 }
3887 var f: i64 = 0
3888 while f < fcnt[0] { xe_patch(cb, fsite[f], noff[ftgt[f]]); f = f + 1 }
3889 return cb as i64
3890}
3891// untemplatable op / too big / buffer overflow) -> the caller runs vm_loop instead.
3892func jit_compile(code: *i64, np: i64, pool: *i64, ftab: *i64, ftabn: i64, vs: *i64) -> i64 {
3893 if np <= 0 { return 0 }
3894 // OPTIMIZING TIER v1 (register allocation): a detector-approved hot int loop compiles to the UNBOXED
3895 // REGISTER blob instead -- locals + operand stack in GP registers, zero env/stack memory traffic in the
3896 // body. DECLINE-SAFE: any shape the register tier can't prove (detector 0/2/3 or a mid-emit decline)
3897 // falls through to the proven stack codegen below, so this hook can never make a wrong program.
3898 if jit_intloop_eligible(code, np) == 1 {
3899 let ra: i64 = jit_intloop_compile(code, np, vs)
3900 if ra != 0 { return ra }
3901 }
3902 if np > BC_MAGIC_4000 { return 0 }
3903 var i: i64 = 0
3904 while i < np { if jit_templatable(code[i * 2]) == 0 { return 0 } i = i + 1 }
3905 let sp_on: i64 = gc_is_enabled() // emit GC safepoints at loop back-edges ONLY when GC is on (zero JIT cost off)
3906 let CBSZ: i64 = BC_MAGIC_262144
3907 let cb: *u8 = xe_mmap_rwx(CBSZ)
3908 if (cb as i64) <= 0 { return 0 }
3909 let pb: *i64 = sys_mmap(8) as *i64
3910 pb[0] = 0
3911 let noff: *i64 = sys_mmap(np * 8) as *i64
3912 let fsite: *i64 = sys_mmap(np * 8 + 64) as *i64
3913 let ftgt: *i64 = sys_mmap(np * 8 + 64) as *i64
3914 let fcnt: *i64 = sys_mmap(8) as *i64
3915 fcnt[0] = 0
3916 let bsite: *i64 = sys_mmap(np * 8 + 64) as *i64
3917 let bcnt: *i64 = sys_mmap(8) as *i64
3918 bcnt[0] = 0
3919 // entryset[i] = 1 iff instruction i is a JIT'd function's entry (gets a body-align prologue); fnaddr
3920 // maps fidx -> native offset (filled after emit) so a runtime CALL resolves the callee's native entry.
3921 let entryset: *i64 = sys_mmap(np * 8) as *i64
3922 let fnaddr: *i64 = sys_mmap((ftabn + 1) * 8) as *i64
3923 var fk: i64 = 0
3924 while fk < ftabn { fnaddr[fk] = 0 - 1; let ei: i64 = ftab[fk * FT_ENT] / 2; if ei >= 0 { if ei < np { entryset[ei] = 1 } } fk = fk + 1 }
3925 // BRANCH-TARGET pre-pass for the const-BINOP fusion peephole: istgt[i]=1 iff any JMP/JMPF/JMPT targets
3926 // instruction i. Fusion (`BC_PUSH k ; BC_BINOP op` -> skip the push) is only legal when neither the PUSH
3927 // nor the BINOP is a jump target (else a jump into them would see the un-fused stack shape) -- within a
3928 // single expression neither ever is, so this fuses all real arithmetic while staying correct by construction.
3929 let istgt: *i64 = sys_mmap(np * 8) as *i64
3930 var jt: i64 = 0
3931 while jt < np {
3932 let jop: i64 = code[jt * 2]
3933 var isj: i64 = 0
3934 if jop == BC_JMP { isj = 1 }
3935 if jop == BC_JMPF { isj = 1 }
3936 if jop == BC_JMPT { isj = 1 }
3937 if isj == 1 { let tg: i64 = code[jt * 2 + 1] / 2; if tg >= 0 { if tg < np { istgt[tg] = 1 } } }
3938 jt = jt + 1
3939 }
3940 // prologue: save 5 callee-saved (16-align), record entry RSP (bail longjmps here), load bases, sp = 0
3941 xe_push(cb, pb, RBX)
3942 xe_push(cb, pb, R12)
3943 xe_push(cb, pb, R13)
3944 xe_push(cb, pb, R14)
3945 xe_push(cb, pb, R15)
3946 xe_mov_rr(cb, pb, R12, RDI)
3947 xe_store(cb, pb, R12, VS_ENTRYSP * 8, RSP)
3948 xe_load(cb, pb, RBX, R12, VS_STACK * 8)
3949 xe_load(cb, pb, R14, R12, VS_ENV * 8)
3950 xe_load(cb, pb, R13, R12, VS_ICACHE * 8) // inline-cache base (for the native shape-guarded GETPROP)
3951 xe_mov_ri32(cb, pb, R15, 0)
3952 var pend_have: i64 = 0 // fusion state: a BC_PUSH k folded into the next BC_BINOP
3953 var pend_const: i64 = 0
3954 i = 0
3955 while i < np {
3956 if pb[0] > (CBSZ - 512) { return 0 }
3957 noff[i] = pb[0] // native entry of instr i (call RAX lands here)
3958 if entryset[i] == 1 { xe_sub_ri32(cb, pb, RSP, 8) } // body-entry: align (entered via call, RSP+8)
3959 let op: i64 = code[i * 2]
3960 let arg: i64 = code[i * 2 + 1]
3961 if op == BC_PUSH {
3962 // FUSION peek: `BC_PUSH k ; BC_BINOP op` with an int const k -> emit NOTHING here; the BINOP folds
3963 // k into an immediate (jit_binop_const). Guards: op fusable, 0<=k<=2^31-1 (imm32 range), k!=0 for
3964 // MOD, and neither this PUSH nor the BINOP is a jump target / function entry (stack-shape safety).
3965 var fuse: i64 = 0
3966 if i + 1 < np {
3967 if code[(i + 1) * 2] == BC_BINOP {
3968 let nop: i64 = code[(i + 1) * 2 + 1]
3969 if jit_binop_fusable(nop) == 1 {
3970 if arg >= 0 {
3971 if arg <= BC_MAGIC_2147483647 {
3972 if entryset[i] == 0 {
3973 if istgt[i] == 0 {
3974 if istgt[i + 1] == 0 {
3975 fuse = 1
3976 if nop == OP_MOD { if arg == 0 { fuse = 0 } }
3977 }
3978 }
3979 }
3980 }
3981 }
3982 }
3983 }
3984 }
3985 if fuse == 1 {
3986 pend_have = 1
3987 pend_const = arg
3988 } else {
3989 xe_mov_ri32(cb, pb, RAX, VAL_NUM)
3990 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
3991 xe_mov_ri64(cb, pb, RAX, arg)
3992 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
3993 xe_add_ri32(cb, pb, R15, 16)
3994 }
3995 }
3996 if op == BC_PUSHU {
3997 xe_mov_ri32(cb, pb, RAX, VAL_UNDEF)
3998 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
3999 xe_mov_ri32(cb, pb, RAX, 0)
4000 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
4001 xe_add_ri32(cb, pb, R15, 16)
4002 }
4003 if op == BC_PUSHK {
4004 xe_mov_ri32(cb, pb, RAX, pool[arg * 2])
4005 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
4006 xe_mov_ri64(cb, pb, RAX, pool[arg * 2 + 1])
4007 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
4008 xe_add_ri32(cb, pb, R15, 16)
4009 }
4010 if op == BC_LOAD {
4011 let d: i64 = (ENVR_HDR + arg * 2) * 8
4012 xe_load(cb, pb, RAX, R14, d)
4013 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
4014 xe_load(cb, pb, RAX, R14, d + 8)
4015 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
4016 xe_add_ri32(cb, pb, R15, 16)
4017 }
4018 if op == BC_STORE {
4019 let d: i64 = (ENVR_HDR + arg * 2) * 8
4020 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
4021 xe_store(cb, pb, R14, d, RAX)
4022 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 8)
4023 xe_store(cb, pb, R14, d + 8, RAX)
4024 xe_sub_ri32(cb, pb, R15, 16)
4025 }
4026 if op == BC_POP { xe_sub_ri32(cb, pb, R15, 16) }
4027 if op == BC_DUP {
4028 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
4029 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
4030 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 8)
4031 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
4032 xe_add_ri32(cb, pb, R15, 16)
4033 }
4034 if op == BC_SWAP {
4035 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
4036 xe_load_idx(cb, pb, RCX, RBX, R15, 0 - 8)
4037 xe_load_idx(cb, pb, RDX, RBX, R15, 0 - 32)
4038 xe_load_idx(cb, pb, RSI, RBX, R15, 0 - 24)
4039 xe_store_idx(cb, pb, RBX, R15, 0 - 32, RAX)
4040 xe_store_idx(cb, pb, RBX, R15, 0 - 24, RCX)
4041 xe_store_idx(cb, pb, RBX, R15, 0 - 16, RDX)
4042 xe_store_idx(cb, pb, RBX, R15, 0 - 8, RSI)
4043 }
4044 if op == BC_DUP2 {
4045 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 32)
4046 xe_store_idx(cb, pb, RBX, R15, 0, RAX)
4047 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 24)
4048 xe_store_idx(cb, pb, RBX, R15, 8, RAX)
4049 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
4050 xe_store_idx(cb, pb, RBX, R15, 16, RAX)
4051 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 8)
4052 xe_store_idx(cb, pb, RBX, R15, 24, RAX)
4053 xe_add_ri32(cb, pb, R15, 32)
4054 }
4055 if op == BC_UNOP {
4056 xe_mov_ri32(cb, pb, RDI, arg)
4057 jit_addr(cb, pb, RSI, 0 - 16)
4058 xe_mov_rr(cb, pb, RDX, RSI)
4059 xe_mov_ri64(cb, pb, R10, (&vm_unary) as i64)
4060 xe_call_reg(cb, pb, R10)
4061 }
4062 if op == BC_BINOP {
4063 if pend_have == 1 { jit_binop_const(cb, pb, arg, pend_const, bsite, bcnt); pend_have = 0 }
4064 else { jit_binop(cb, pb, arg, bsite, bcnt) }
4065 }
4066 if op == BC_GETPROP { jit_getprop_inline(cb, pb, i, arg, bsite, bcnt) }
4067 if op == BC_SETPROP {
4068 let sk: *i64 = (pool[arg * 2 + 1]) as *i64
4069 var domkey: i64 = 0
4070 if ev_key_is(sk, "innerHTML\x00" as *u8) == 1 { domkey = 1 }
4071 if ev_key_is(sk, "textContent\x00" as *u8) == 1 { domkey = 1 }
4072 if domkey == 1 { jit_emit_ct(cb, pb, op, arg, i * 2, bsite, bcnt) } else { jit_setprop_inline(cb, pb, i, arg, bsite, bcnt) }
4073 }
4074 if jit_is_ct(op) == 1 { if op != BC_SETPROP { jit_emit_ct(cb, pb, op, arg, i * 2, bsite, bcnt) } }
4075 if op == BC_CLOSURE { jit_emit_ct(cb, pb, op, arg, i * 2, bsite, bcnt) }
4076 if op == BC_CALL { jit_emit_callseq(cb, pb, (&jit_call_prep) as i64, arg, i * 2, bsite, bcnt) }
4077 if op == BC_CALLM { jit_emit_callseq(cb, pb, (&jit_callm_prep) as i64, arg, i * 2, bsite, bcnt) }
4078 if op == BC_NEW { jit_emit_callseq(cb, pb, (&jit_new_prep) as i64, arg, i * 2, bsite, bcnt) }
4079 if op == BC_RETV { jit_emit_retv(cb, pb) }
4080 if op == BC_JMP {
4081 if sp_on == 1 { if arg / 2 <= i { jit_emit_safepoint(cb, pb) } } // back-edge = loop continuation
4082 let s: i64 = xe_jmp(cb, pb)
4083 fsite[fcnt[0]] = s
4084 ftgt[fcnt[0]] = arg / 2
4085 fcnt[0] = fcnt[0] + 1
4086 }
4087 if op == BC_JMPF { if sp_on == 1 { if arg / 2 <= i { jit_emit_safepoint(cb, pb) } } jit_jmpcond(cb, pb, 0, fsite, ftgt, fcnt, arg / 2) }
4088 if op == BC_JMPT { if sp_on == 1 { if arg / 2 <= i { jit_emit_safepoint(cb, pb) } } jit_jmpcond(cb, pb, 1, fsite, ftgt, fcnt, arg / 2) }
4089 if op == BC_RET {
4090 xe_load_idx(cb, pb, RAX, RBX, R15, 0 - 16)
4091 xe_load_idx(cb, pb, RCX, RBX, R15, 0 - 8)
4092 xe_load(cb, pb, RDX, R12, VS_OUT * 8)
4093 xe_store(cb, pb, RDX, 0, RAX)
4094 xe_store(cb, pb, RDX, 8, RCX)
4095 xe_mov_ri32(cb, pb, RAX, 0)
4096 xe_pop(cb, pb, R15)
4097 xe_pop(cb, pb, R14)
4098 xe_pop(cb, pb, R13)
4099 xe_pop(cb, pb, R12)
4100 xe_pop(cb, pb, RBX)
4101 xe_ret(cb, pb)
4102 }
4103 i = i + 1
4104 }
4105 // shared bail epilogue (rc=1): LONGJMP to blob entry -- restore RSP to VS_ENTRYSP so a bail from ANY
4106 // native call depth unwinds the whole native stack in one shot, then pop the 5 saved regs + rc=1.
4107 let bailoff: i64 = pb[0]
4108 xe_load(cb, pb, RSP, R12, VS_ENTRYSP * 8)
4109 xe_pop(cb, pb, R15)
4110 xe_pop(cb, pb, R14)
4111 xe_pop(cb, pb, R13)
4112 xe_pop(cb, pb, R12)
4113 xe_pop(cb, pb, RBX)
4114 xe_mov_ri32(cb, pb, RAX, 1)
4115 xe_ret(cb, pb)
4116 // resolve jump + bail fixups to native offsets
4117 var f: i64 = 0
4118 while f < fcnt[0] { xe_patch(cb, fsite[f], noff[ftgt[f]]); f = f + 1 }
4119 var b: i64 = 0
4120 while b < bcnt[0] { xe_patch(cb, bsite[b], bailoff); b = b + 1 }
4121 // fill fnaddr[fidx] = native offset of each function's entry, and hand the code base + table to vs.
4122 var g: i64 = 0
4123 while g < ftabn { let ei: i64 = ftab[g * FT_ENT] / 2; if ei >= 0 { if ei < np { fnaddr[g] = noff[ei] } } g = g + 1 }
4124 if (vs as i64) != 0 { vs[VS_CBBASE] = cb as i64; vs[VS_FNADDR] = fnaddr as i64 }
4125 return cb as i64
4126}
4127// probe (for the gate): would the JIT compile `src`'s top-level program? (parse+compile, ask jit_compile).
4128func js_jit_probe(src: *u8) -> i64 {
4129 let slen: i64 = bsl(src)
4130 let ctxbox: *i64 = sys_mmap(16) as *i64
4131 let prog: i64 = jp_parse_source(src, slen, ctxbox)
4132 let ctx: *i64 = (ctxbox[0]) as *i64
4133 let pst: *i64 = jp_pst(ctx)
4134 if pst[PST_ERR] == 1 { return 0 }
4135 let cc: *i64 = sys_mmap(CC_SZ * 8) as *i64
4136 let code: *i64 = sys_mmap(VMC_CODEI * 2 * 8) as *i64
4137 let pool: *i64 = sys_mmap(VMC_POOLN * 2 * 8) as *i64
4138 let ftab: *i64 = sys_mmap(VMC_MAXF * FT_ENT * 8) as *i64
4139 let sct: *i64 = sys_mmap(VMC_MAXSC * SC_ENT * 8) as *i64
4140 let brkl: *i64 = sys_mmap(VMC_PATCH * 8) as *i64
4141 let conl: *i64 = sys_mmap(VMC_PATCH * 8) as *i64
4142 let fins: *i64 = sys_mmap(VMC_MAXFE * FE_ENT * 8) as *i64
4143 cc[CC_FINS] = fins as i64
4144 cc[CC_CODE] = code as i64
4145 cc[CC_POOL] = pool as i64
4146 cc[CC_FTAB] = ftab as i64
4147 cc[CC_SCT] = sct as i64
4148 cc[CC_CTX] = ctx as i64
4149 cc[CC_BRK] = brkl as i64
4150 cc[CC_CON] = conl as i64
4151 cc[CC_SCUR] = 0 - 1
4152 c_scope_push(cc)
4153 c_program(cc, prog)
4154 let a: i64 = jit_compile(code, cc[CC_NP], pool, ftab, cc[CC_FCNT], 0 as *i64)
4155 if a != 0 { return 1 }
4156 return 0
4157}
4158// probe (for the regalloc gate): compile `src` to bytecode, ask the register-allocation eligibility detector.
4159// Returns jit_intloop_eligible's verdict (1 eligible / 0 unsafe-op / 2 no-loop / 3 pressure), or -1 on parse error.
4160func js_regalloc_probe(src: *u8) -> i64 {
4161 let slen: i64 = bsl(src)
4162 let ctxbox: *i64 = sys_mmap(16) as *i64
4163 let prog: i64 = jp_parse_source(src, slen, ctxbox)
4164 let ctx: *i64 = (ctxbox[0]) as *i64
4165 let pst: *i64 = jp_pst(ctx)
4166 if pst[PST_ERR] == 1 { return 0 - 1 }
4167 let cc: *i64 = sys_mmap(CC_SZ * 8) as *i64
4168 let code: *i64 = sys_mmap(VMC_CODEI * 2 * 8) as *i64
4169 let pool: *i64 = sys_mmap(VMC_POOLN * 2 * 8) as *i64
4170 let ftab: *i64 = sys_mmap(VMC_MAXF * FT_ENT * 8) as *i64
4171 let sct: *i64 = sys_mmap(VMC_MAXSC * SC_ENT * 8) as *i64
4172 let brkl: *i64 = sys_mmap(VMC_PATCH * 8) as *i64
4173 let conl: *i64 = sys_mmap(VMC_PATCH * 8) as *i64
4174 let fins: *i64 = sys_mmap(VMC_MAXFE * FE_ENT * 8) as *i64
4175 cc[CC_FINS] = fins as i64
4176 cc[CC_CODE] = code as i64
4177 cc[CC_POOL] = pool as i64
4178 cc[CC_FTAB] = ftab as i64
4179 cc[CC_SCT] = sct as i64
4180 cc[CC_CTX] = ctx as i64
4181 cc[CC_BRK] = brkl as i64
4182 cc[CC_CON] = conl as i64
4183 cc[CC_SCUR] = 0 - 1
4184 c_scope_push(cc)
4185 c_program(cc, prog)
4186 if cc[CC_UNSUP] == 1 { return 0 } // declined a feature at compile -> not a pure int loop anyway
4187 return jit_intloop_eligible(code, cc[CC_NP])
4188}
4189
4190// compile + run `src`; result value in out. Returns 0 ok / 1 error (parse OR runtime -- rc parity
4191// with js_run_source is part of the gate). Binds the VM re-entry hook, then DRAINS the event
4192// loop after a clean run (setTimeout/queueMicrotask/promise callbacks -- VM closures -- execute
4193// through js_call_core -> vm_call_closure). readname != 0 -> after the drain, out = the named
4194// TOP-LEVEL global's final value (the async-observation seam, mirroring js_run_source_read).
4195func cr_core(src: *u8, srclen: i64, out: *i64, readname: *u8, dochtml: i64, doclen: i64, decl: i64) -> i64 {
4196 let slen: i64 = srclen // caller-supplied length: page-script slices are NOT null-terminated (they
4197 // point into the HTML buffer, followed by </script>), so bsl() would over-read.
4198 let ctxbox: *i64 = sys_mmap(16) as *i64
4199 let prog: i64 = jp_parse_source(src, slen, ctxbox)
4200 let ctx: *i64 = (ctxbox[0]) as *i64
4201 let pst: *i64 = jp_pst(ctx)
4202 if pst[PST_ERR] == 1 { ev_set(out, VAL_UNDEF, 0); return 1 }
4203 let cc: *i64 = sys_mmap(CC_SZ * 8) as *i64
4204 let code: *i64 = sys_mmap(VMC_CODEI * 2 * 8) as *i64
4205 let pool: *i64 = sys_mmap(VMC_POOLN * 2 * 8) as *i64
4206 let ftab: *i64 = sys_mmap(VMC_MAXF * FT_ENT * 8) as *i64
4207 let sct: *i64 = sys_mmap(VMC_MAXSC * SC_ENT * 8) as *i64
4208 let brkl: *i64 = sys_mmap(VMC_PATCH * 8) as *i64
4209 let conl: *i64 = sys_mmap(VMC_PATCH * 8) as *i64
4210 let fins: *i64 = sys_mmap(VMC_MAXFE * FE_ENT * 8) as *i64
4211 cc[CC_FINS] = fins as i64
4212 cc[CC_CODE] = code as i64
4213 cc[CC_POOL] = pool as i64
4214 cc[CC_FTAB] = ftab as i64
4215 cc[CC_SCT] = sct as i64
4216 cc[CC_CTX] = ctx as i64
4217 cc[CC_BRK] = brkl as i64
4218 cc[CC_CON] = conl as i64
4219 cc[CC_SCUR] = 0 - 1
4220 c_scope_push(cc)
4221 c_program(cc, prog)
4222 // consumer-swap: if the VM compiler DECLINED a feature (template ${}, unknown node) and the caller asked
4223 // to defer (decl=1), return the DECLINE sentinel so the tree-walker handles this script instead.
4224 if decl == 1 { if cc[CC_UNSUP] == 1 { return 0 - 2 } }
4225 // NON-DOM path (decl=0, = compile_run / js_vm_run_read): a DECLINE means a feature only the tree-walker
4226 // implements (e.g. non-strict auto-global `x=5`) -- run the WHOLE program on the tree so it still executes
4227 // correctly (this is why compile_run stays a total function; the VM is an accelerator, not a gate).
4228 if cc[CC_UNSUP] == 1 { return js_run_source(src, slen, out) }
4229 let nvars0: i64 = sct[cc[CC_SCUR] * SC_ENT + 1]
4230 let vs: *i64 = sys_mmap(VS_SZ * 8) as *i64
4231 let arena: i64 = sys_mmap(VMC_ARENA as i64) as i64
4232 let stack: *i64 = sys_mmap(VMC_STACKN * 2 * 8) as *i64
4233 let frames: *i64 = sys_mmap(VMC_MAXFR * FR_ENT * 8) as *i64
4234 let hstk: *i64 = sys_mmap(VMC_MAXH * HS_ENT * 8) as *i64
4235 let icache: *i64 = sys_mmap((cc[CC_NP] + 2) * POLY_WAYS * 2 * 8) as *i64 // K-way poly IC, sized to the program
4236 let argbuf: *i64 = sys_mmap(8 * 2 * 256) as *i64
4237 let t1: *i64 = sys_mmap(16) as *i64
4238 let t2: *i64 = sys_mmap(16) as *i64
4239 let slb: *i64 = sys_mmap(16) as *i64
4240 let srb: *i64 = sys_mmap(16) as *i64
4241 let sob: *i64 = sys_mmap(16) as *i64
4242 let genv: *i64 = env_new()
4243 genv[2] = dochtml // DOM-mode: document tree/bytes ptr (0 = no document); doclen = len or tree sentinel
4244 genv[3] = doclen
4245 vs[VS_ARENA] = arena
4246 vs[VS_STACK] = stack as i64
4247 vs[VS_CODE] = code as i64
4248 vs[VS_POOL] = pool as i64
4249 vs[VS_POOLN] = cc[CC_NP] // const-pool entry count -> GC root extent for the string/float literals
4250 vs[VS_FTAB] = ftab as i64
4251 vs[VS_CTX] = ctx as i64
4252 vs[VS_GENV] = genv as i64
4253 vs[VS_FRAMES] = frames as i64
4254 vs[VS_HSTK] = hstk as i64
4255 vs[VS_ICACHE] = icache as i64
4256 vs[VS_ARGBUF] = argbuf as i64
4257 vs[VS_T1] = t1 as i64
4258 vs[VS_T2] = t2 as i64
4259 vs[VS_SLB] = slb as i64
4260 vs[VS_SRB] = srb as i64
4261 vs[VS_SOB] = sob as i64
4262 vs[VS_OUT] = out as i64
4263 vs[VS_RUN] = 1
4264 let ebytes: i64 = (ENVR_HDR + nvars0 * 2) * 8
4265 let e0: i64 = vmx_alloc(vs, ebytes)
4266 if e0 == 0 { ev_set(out, VAL_UNDEF, 0); return 1 }
4267 vs[VS_ENV] = e0
4268 ev_set(out, VAL_UNDEF, 0)
4269 js_vm_bind((&vm_call_closure) as i64, vs as i64)
4270 // P5: tier up to native if the whole program is templatable; else run the interpreter. Both write
4271 // vs[VS_OUT] + return the same rc, so the drain/read tail below is identical either way.
4272 var rc: i64 = 0
4273 var jaddr: i64 = 0
4274 // JIT+GC COEXISTENCE: the P5 JIT keeps every JS value on the vs-stack (RBX+R15) and syncs vs[VS_SP]/vs[VS_ENV]
4275 // at every call boundary (jit_emit_callseq) + emits a gc_safepoint at every loop back-edge (jit_emit_safepoint,
4276 // gated on gc_is_enabled). So a collection fired from JIT'd code finds all roots on the vs-stack -> SAFE. JIT
4277 // now runs WITH GC on (was: interpreter-only while GC on).
4278 if jit_disabled == 0 { jaddr = jit_compile(code, cc[CC_NP], pool, ftab, cc[CC_FCNT], vs) }
4279 if jaddr != 0 { jit_ran = 1 } else { jit_ran = 0 }
4280 if jaddr != 0 {
4281 let jf: func(i64) -> i64 = jaddr as func(i64) -> i64
4282 rc = jf(vs as i64)
4283 } else {
4284 rc = vm_run(vs)
4285 }
4286 if rc != 0 { return rc }
4287 el_drain(ctx, genv)
4288 if (readname as i64) != 0 {
4289 let vt0: *i64 = (sct[0]) as *i64
4290 let cnt0: i64 = sct[1]
4291 let nl: i64 = bsl(readname)
4292 let src2: *u8 = jp_src(ctx)
4293 var slot: i64 = 0 - 1
4294 var i2: i64 = 0
4295 while i2 < cnt0 {
4296 if vt0[i2 * VT_ENT + 1] == nl {
4297 let os: i64 = vt0[i2 * VT_ENT]
4298 if os >= 0 {
4299 var m: i64 = 1
4300 var j2: i64 = 0
4301 while j2 < nl { if (src2[os + j2] & 0xff) != (readname[j2] & 0xff) { m = 0; j2 = nl } else { j2 = j2 + 1 } }
4302 if m == 1 { slot = i2 }
4303 }
4304 }
4305 i2 = i2 + 1
4306 }
4307 ev_set(out, VAL_UNDEF, 0)
4308 if slot >= 0 { let e0p: *i64 = e0 as *i64; let b2: i64 = ENVR_HDR + slot * 2; ev_set(out, e0p[b2], e0p[b2 + 1]) }
4309 }
4310 return 0
4311}
4312func compile_run(src: *u8, out: *i64) -> i64 { return cr_core(src, bsl(src), out, 0 as *u8, 0, 0, 0) }
4313// VM twin of js_run_source_read: run + drain + read a named global (async gates compare both).
4314func js_vm_run_read(src: *u8, readname: *u8, out: *i64) -> i64 { return cr_core(src, bsl(src), out, readname, 0, 0, 0) }
4315// CONSUMER-SWAP doc runner (the hook nx_js_eval's js_run_source_doc calls): run one page script through
4316// the VM/JIT in DOM mode (genv[2]/[3]=doc), FALLING BACK (return -2) if the compiler declined a feature.
4317// Signatures use i64 addresses so the fn-ptr hook type is uniform across the module boundary.
4318func compile_run_doc(srcaddr: i64, srclen: i64, dochtml: i64, doclen: i64, outaddr: i64) -> i64 {
4319 return cr_core(srcaddr as *u8, srclen, outaddr as *i64, 0 as *u8, dochtml, doclen, 1)
4320}
4321// register compile_run_doc as the DOM-script hook so js_run_source_doc routes page scripts through the
4322// VM/JIT (with tree-walker fallback). Consumers (renderer/gate) call this once before rendering.
4323func js_vm_doc_enable() -> i64 { js_vm_doc_bind((&compile_run_doc) as i64); return 0 }
4324
4325// deep value equality for the parity gate: primitives by tag+payload, strings by bytes,
4326// floats by bit pattern, arrays elementwise, objects by count+keys+values (insertion order).
4327func cells_eq(a: *i64, b: *i64) -> i64 {
4328 if a[0] != b[0] { return 0 }
4329 if a[0] == VAL_NUM { if a[1] == b[1] { return 1 } return 0 }
4330 if a[0] == VAL_BOOL { if a[1] == b[1] { return 1 } return 0 }
4331 if a[0] == VAL_FLOAT { if a[1] == b[1] { return 1 } return 0 }
4332 if a[0] == VAL_STR { let ra: *i64 = (a[1]) as *i64; let rb: *i64 = (b[1]) as *i64; if ev_str_eq(ra, rb) == 1 { return 1 } return 0 }
4333 if a[0] == VAL_UNDEF { return 1 }
4334 if a[0] == VAL_NULL { return 1 }
4335 if a[0] == VAL_ARRAY {
4336 let ra: *i64 = (a[1]) as *i64
4337 let rb: *i64 = (b[1]) as *i64
4338 let la: i64 = arr_len(ra)
4339 if la != arr_len(rb) { return 0 }
4340 let ea: *i64 = sys_mmap(16) as *i64
4341 let eb: *i64 = sys_mmap(16) as *i64
4342 var i: i64 = 0
4343 while i < la {
4344 arr_get(ra, i, ea)
4345 arr_get(rb, i, eb)
4346 if cells_eq(ea, eb) == 0 { return 0 }
4347 i = i + 1
4348 }
4349 return 1
4350 }
4351 if a[0] == VAL_OBJECT {
4352 let oa: *i64 = (a[1]) as *i64
4353 let obb: *i64 = (b[1]) as *i64
4354 let ca: i64 = obj_count(oa)
4355 if ca != obj_count(obb) { return 0 }
4356 let va: *i64 = sys_mmap(16) as *i64
4357 let vb: *i64 = sys_mmap(16) as *i64
4358 var i: i64 = 0
4359 while i < ca {
4360 let ka: *i64 = obj_key(oa, i)
4361 let kb: *i64 = obj_key(obb, i)
4362 if ev_str_eq(ka, kb) == 0 { return 0 }
4363 if obj_get(oa, ka, va) == 0 { return 0 }
4364 if obj_get(obb, kb, vb) == 0 { return 0 }
4365 if cells_eq(va, vb) == 0 { return 0 }
4366 i = i + 1
4367 }
4368 return 1
4369 }
4370 if a[0] == VAL_GLOBALNS { if a[1] == b[1] { return 1 } return 0 }
4371 if a[0] == VAL_NATIVE { if a[1] == b[1] { return 1 } return 0 }
4372 if a[0] == VAL_FUNC { return 1 } // both engines produced A function -- identity differs by design
4373 return 0
4374}
4375// the VM must MATCH the tree-walker on `src`: same rc AND (on rc 0) deep-equal values.
4376func chk(src: *u8, label: *u8, pp: *i64, tp: *i64) -> i64 {
4377 tp[0] = tp[0] + 1
4378 let tw: *i64 = sys_mmap(16) as *i64
4379 let trc: i64 = js_run_source(src, bsl(src), tw)
4380 let vm: *i64 = sys_mmap(16) as *i64
4381 let vrc: i64 = compile_run(src, vm)
4382 bw(" ")
4383 bw(label)
4384 bw(": " as *u8)
4385 var ok: i64 = 0
4386 if trc == 1 { if vrc == 1 { ok = 1 } }
4387 if trc == 0 { if vrc == 0 { ok = cells_eq(tw, vm) } }
4388 if ok == 1 { pp[0] = pp[0] + 1; bw("ok\n" as *u8); return 0 }
4389 bw("FAIL (tw rc=")
4390 bn(trc)
4391 bw(" tag=")
4392 bn(tw[0])
4393 bw(" pay=")
4394 bn(tw[1])
4395 bw(" vs vm rc=")
4396 bn(vrc)
4397 bw(" tag=")
4398 bn(vm[0])
4399 bw(" pay=")
4400 bn(vm[1])
4401 bw(")\n" as *u8)
4402 return 0
4403}
4404
4405// ASYNC parity: run + DRAIN on both engines, then compare the named global's final value
4406// (tree = js_run_source_read, VM = js_vm_run_read). Observes effects landing after the last
4407// top-level statement -- setTimeout/microtask/promise callbacks executed as VM closures.
4408func chk_read(src: *u8, name: *u8, label: *u8, pp: *i64, tp: *i64) -> i64 {
4409 tp[0] = tp[0] + 1
4410 let tw: *i64 = sys_mmap(16) as *i64
4411 let trc: i64 = js_run_source_read(src, bsl(src), name, tw)
4412 let vm: *i64 = sys_mmap(16) as *i64
4413 let vrc: i64 = js_vm_run_read(src, name, vm)
4414 bw(" ")
4415 bw(label)
4416 bw(": " as *u8)
4417 var ok: i64 = 0
4418 if trc == 1 { if vrc == 1 { ok = 1 } }
4419 if trc == 0 { if vrc == 0 { ok = cells_eq(tw, vm) } }
4420 if ok == 1 { pp[0] = pp[0] + 1; bw("ok\n" as *u8); return 0 }
4421 bw("FAIL (tw rc=")
4422 bn(trc)
4423 bw(" tag=")
4424 bn(tw[0])
4425 bw(" pay=")
4426 bn(tw[1])
4427 bw(" vs vm rc=")
4428 bn(vrc)
4429 bw(" tag=")
4430 bn(vm[0])
4431 bw(" pay=")
4432 bn(vm[1])
4433 bw(")\n" as *u8)
4434 return 0
4435}
4436