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