Compare commits

..

4 Commits

Author SHA1 Message Date
f480eb943c merge: bugs/resume-letrec — cek_run propagates IO suspension via hook
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
2026-05-07 11:27:04 +00:00
edc7e865b4 merge: bugs/jit-bytecode-loop — OP_CLOSURE Integer/Number fix (+690 JIT tests) 2026-05-07 11:26:57 +00:00
ca151d7ed5 ocaml: VM OP_CLOSURE upvalue-count handles Integer values
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m44s
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.
2026-05-07 09:48:21 +00:00
322eb1d034 plans: tick Phase 5 channel I/O — 349/349 green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 2m18s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:29:14 +00:00
6 changed files with 50 additions and 10 deletions

View File

@@ -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 _ ->

View File

@@ -715,8 +715,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 ->

View File

@@ -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

View File

@@ -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 _ ->

View File

@@ -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 _ ->

View File

@@ -132,6 +132,31 @@ architectural improvement worth doing when the moment is right.
---
## Phase 5 — Channel I/O (random access + non-blocking) ✓
Real Tcl channel commands replacing the previous stubs. SX gained 11 channel
primitives in `sx_primitives.ml` (using `Unix.openfile` + `Unix.read`/`write`/
`lseek`/`set_nonblock`). Tcl `open`/`close`/`read`/`gets`/`puts`/`seek`/`tell`/
`eof`/`flush`/`fconfigure` now wrap them.
| Status | Work | Unlocks in Tcl |
|---|---|---|
| [x] | `channel-open`, `channel-close` | `open` returns "fileN", `close` actually closes |
| [x] | `channel-read`, `channel-read-line`, `channel-write` | `read`/`gets`/`puts` to/from real files |
| [x] | `channel-seek`, `channel-tell` | random access — `seek $c offset start\|current\|end`, `tell` |
| [x] | `channel-eof?`, `channel-flush` | proper EOF detection, no-op flush |
| [x] | `channel-blocking?`, `channel-set-blocking!` | `fconfigure $c -blocking 0\|1` |
Modes supported: `r`, `w`, `a`, `r+`, `w+`, `a+`. Whence: `start`, `current`, `end`.
`puts` now detects channel argument (string starting with "file") and dispatches
to `channel-write`; otherwise writes to `interp :output` as before.
**Total: ~half day. 7 new idiom tests covering write+read, gets-loop, seek/tell,
eof-after-read, append mode, seek-to-end, fconfigure-blocking.**
---
## Suggested order
1. **Phase 1** — immediate Tcl wins, zero risk, proves the approach
@@ -148,6 +173,7 @@ becomes a lasting SX contribution used by every future hosted language.
_Newest first._
- 2026-05-07: Phase 5 channel I/O — 11 SX primitives (channel-open/close/read/read-line/write/flush/seek/tell/eof?/blocking?/set-blocking!) wrapping Unix.openfile/read/write/lseek/set_nonblock; tcl-cmd-open/close/read/gets-chan/seek/tell/flush rewritten + new tcl-cmd-fconfigure; tcl-cmd-puts dispatches on "fileN" arg; gets registration fixed; +7 idiom tests; 349/349 green
- 2026-05-06: Phase 4 env-as-value — current-env (special form via Sx_ref.register_special_form), eval-in-env (primitive in setup_evaluator_bridge), env-lookup + env-extend (in setup_env_operations); 5 idiom tests; 342/342 green
- 2026-05-06: Phase 3 OCaml primitives — file-read/write/append/exists?/glob + clock-seconds/milliseconds/format in sx_primitives.ml + unix dep; tcl-cmd-clock/file wired up; 337/337 green
- 2026-05-06: Phase 2 coroutine rewrite — `tcl-cmd-coroutine` now creates a `make-fiber`; `tcl-cmd-yield` calls `:coro-yield-fn` (threaded through interp); true suspension; 337/337 green
@@ -162,7 +188,7 @@ _Newest first._
## What stays out of scope
- `package require` of binary loadables
- Full `clock format` locale support
- Full `clock format` locale support
- Tk / GUI
- Threads (mapped to coroutines only, as planned)
- Full POSIX file I/O (seek/tell/async) — stubs are fine
- `chan event` / `fileevent` — event-driven I/O callbacks (Phase 5 covers blocking + non-blocking flag, but no event loop dispatch)