From ca151d7ed5515726bceda821ce95a8573276b03e Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 7 May 2026 09:48:21 +0000 Subject: [PATCH] ocaml: VM OP_CLOSURE upvalue-count handles Integer values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the Integer/Number numeric tower split (c70bbdeb), the bytecode compiler emits :upvalue-count as Integer, but the VM and SXBC loader only matched Number. The fallback `_ -> 0` made the VM skip reading upvalue descriptors entirely, so the IP advanced into raw upvalue bytes which were then misread as opcodes. Symptom: JIT runs of nested closures (curried functions, Y combinator, component bodies that close over outer let-bindings) produced "VM: CONST index N out of bounds (pool size M)" with N values like 256, 4096, 5120, 12800, 13056 — all of the form `byte | (opcode << 8)`, i.e. an upvalue descriptor (lo) followed by the next instruction's opcode (hi) being read as a u16 operand. Fix all five sites that decode upvalue-count to also accept Integer: - hosts/ocaml/lib/sx_vm.ml: OP_CLOSURE handler, trace_run, disassemble - hosts/ocaml/lib/sx_vm_ref.ml + hosts/ocaml/sx_vm_ref.ml + bootstrap_vm.py: vm_create_closure preamble (the bootstrap source-of-truth and both generated copies) - hosts/ocaml/browser/sx_browser.ml: SXBC loader's parse_kv Test impact: JIT 3848 -> 4538 passing (+690). No-JIT unchanged at 4550. The previously-failing curried/Y/higher-order tests in spec/tests/test-cek-advanced.sx now pass under --jit and serve as regression coverage. This fixes a real current bug. The 28-day-old memory file describing parser-combinator JIT bugs predates the numeric tower split and described a different problem; with this fix the parser-combinator broken-name list (`_jit_is_broken_name` in sx_vm.ml) is no longer strictly required for correctness, but keeping it avoids a TIMEOUT regression in one hyperscript test, so it remains in place. --- hosts/ocaml/bootstrap_vm.py | 4 +++- hosts/ocaml/browser/sx_browser.ml | 6 ++++-- hosts/ocaml/lib/sx_vm.ml | 12 +++++++++--- hosts/ocaml/lib/sx_vm_ref.ml | 4 +++- hosts/ocaml/sx_vm_ref.ml | 4 +++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/hosts/ocaml/bootstrap_vm.py b/hosts/ocaml/bootstrap_vm.py index 4b4c8c34..5d2f461b 100644 --- a/hosts/ocaml/bootstrap_vm.py +++ b/hosts/ocaml/bootstrap_vm.py @@ -355,7 +355,9 @@ let vm_create_closure vm_val frame_val code_val = let f = unwrap_frame frame_val in let uv_count = match code_val with | Dict d -> (match Hashtbl.find_opt d "upvalue-count" with - | Some (Number n) -> int_of_float n | _ -> 0) + | Some (Integer n) -> n + | Some (Number n) -> int_of_float n + | _ -> 0) | _ -> 0 in let upvalues = Array.init uv_count (fun _ -> diff --git a/hosts/ocaml/browser/sx_browser.ml b/hosts/ocaml/browser/sx_browser.ml index c61cac7f..2abbbf17 100644 --- a/hosts/ocaml/browser/sx_browser.ml +++ b/hosts/ocaml/browser/sx_browser.ml @@ -704,8 +704,10 @@ let () = | List (Symbol "code" :: rest) -> let d = Hashtbl.create 8 in let rec parse_kv = function - | Keyword "arity" :: Number n :: rest -> Hashtbl.replace d "arity" (Number n); parse_kv rest - | Keyword "upvalue-count" :: Number n :: rest -> Hashtbl.replace d "upvalue-count" (Number n); parse_kv rest + | Keyword "arity" :: (Number _ as n) :: rest -> Hashtbl.replace d "arity" n; parse_kv rest + | Keyword "arity" :: (Integer _ as n) :: rest -> Hashtbl.replace d "arity" n; parse_kv rest + | Keyword "upvalue-count" :: (Number _ as n) :: rest -> Hashtbl.replace d "upvalue-count" n; parse_kv rest + | Keyword "upvalue-count" :: (Integer _ as n) :: rest -> Hashtbl.replace d "upvalue-count" n; parse_kv rest | Keyword "bytecode" :: List nums :: rest -> Hashtbl.replace d "bytecode" (List nums); parse_kv rest | Keyword "constants" :: List consts :: rest -> diff --git a/hosts/ocaml/lib/sx_vm.ml b/hosts/ocaml/lib/sx_vm.ml index 4fd09eac..330316e3 100644 --- a/hosts/ocaml/lib/sx_vm.ml +++ b/hosts/ocaml/lib/sx_vm.ml @@ -642,7 +642,9 @@ and run vm = (* Read upvalue descriptors from bytecode *) let uv_count = match code_val with | Dict d -> (match Hashtbl.find_opt d "upvalue-count" with - | Some (Number n) -> int_of_float n | _ -> 0) + | Some (Integer n) -> n + | Some (Number n) -> int_of_float n + | _ -> 0) | _ -> 0 in let upvalues = Array.init uv_count (fun _ -> @@ -1307,7 +1309,9 @@ let trace_run src globals = let code_val2 = frame.closure.vm_code.vc_constants.(idx) in let uv_count = match code_val2 with | Dict d -> (match Hashtbl.find_opt d "upvalue-count" with - | Some (Number n) -> int_of_float n | _ -> 0) + | Some (Integer n) -> n + | Some (Number n) -> int_of_float n + | _ -> 0) | _ -> 0 in let upvalues = Array.init uv_count (fun _ -> let is_local = read_u8 frame in @@ -1428,7 +1432,9 @@ let disassemble (code : vm_code) = if op = 51 && idx < Array.length consts then begin let uv_count = match consts.(idx) with | Dict d -> (match Hashtbl.find_opt d "upvalue-count" with - | Some (Number n) -> int_of_float n | _ -> 0) + | Some (Integer n) -> n + | Some (Number n) -> int_of_float n + | _ -> 0) | _ -> 0 in ip := !ip + uv_count * 2 end diff --git a/hosts/ocaml/lib/sx_vm_ref.ml b/hosts/ocaml/lib/sx_vm_ref.ml index 62e754f1..d8d9b465 100644 --- a/hosts/ocaml/lib/sx_vm_ref.ml +++ b/hosts/ocaml/lib/sx_vm_ref.ml @@ -270,7 +270,9 @@ let vm_create_closure vm_val frame_val code_val = let f = unwrap_frame frame_val in let uv_count = match code_val with | Dict d -> (match Hashtbl.find_opt d "upvalue-count" with - | Some (Number n) -> int_of_float n | _ -> 0) + | Some (Integer n) -> n + | Some (Number n) -> int_of_float n + | _ -> 0) | _ -> 0 in let upvalues = Array.init uv_count (fun _ -> diff --git a/hosts/ocaml/sx_vm_ref.ml b/hosts/ocaml/sx_vm_ref.ml index d7cfd35d..92eedb8f 100644 --- a/hosts/ocaml/sx_vm_ref.ml +++ b/hosts/ocaml/sx_vm_ref.ml @@ -265,7 +265,9 @@ let vm_create_closure vm_val frame_val code_val = let f = unwrap_frame frame_val in let uv_count = match code_val with | Dict d -> (match Hashtbl.find_opt d "upvalue-count" with - | Some (Number n) -> int_of_float n | _ -> 0) + | Some (Integer n) -> n + | Some (Number n) -> int_of_float n + | _ -> 0) | _ -> 0 in let upvalues = Array.init uv_count (fun _ ->