Skip to content

[stapledons_godot] VM silently skips rest of block after deep non-tail recursion in a service handler (v0.52.0) #1576

Closed

Description

@sunholo-voight-kampff

AILANG v0.52.0 bytecode VM: a request handler silently drops its work (no output, no error, exit 0) when it builds a list of ~1100+ elements by non-tail recursion; the interpreter answers.

Repro: sunholo-data/stapledons-godot at commit 2b54a08 (branch sprint/ai10a-style-frame). ai/voice.ail stubPcm then used concatList(rep(n, period(p))) with rep(n,x) = if n<=0 then [] else x :: rep(n-1,x). Run the stub service
ailang run --quiet --bytecode --package-dir ai --caps IO,FS --entry main --args-file cfg.json ai/service.ail < in.ndjson
(cfg: {"provider":"stub","keys_present":["gemini","openrouter"],"text_only":false,"cache_dir":"/tmp/x","routing":"data/ai/routing.json","fixtures":"ai/fixtures"}) with a hello line then a voice request whose single segment is {"emotion":"grieving","text":"I keep thinking of the ones we left behind."} (n ~ 1170). Instrumenting stubLoop's Ask arm (println ASK; let s = handle(c,l,r); println HANDLED) prints ASK, never HANDLED; the loop then continues reading and exits 0, nothing on stderr. Without --bytecode it answers. n=984 works; n>=~1100 fails. A standalone module with rep(5000) in an effectful recursive loop does not reproduce, so the trigger depends on call context (handler reached through bindF closures from start()). Expected: work, or RT_REC_003 as the interpreter reports at depth 10000. Workaround in-tree: repeat by doubling (depth log2 n).


Reported by: stapledons_godot via ailang messages

Activity

  1. sunholo-voight-kampff commented on Oct 16, 2026

    @sunholo-voight-kampff
    CollaboratorAuthor

    Both halves of this are now fixed on dev, shipping in v0.54.12.

    1. Depth limit (v0.53.1, PR #1729). The VM frame cap was 1,000 vs the evaluator's 10,000 and --max-recursion-depth never reached the VM. Re-verified on v0.54.11 with the reported service shape: depths 1,170 and 9,000 answer identically on both engines; 12,000 fails loudly on both (RT_REC_003 / vm: stack overflow, exit 1).

    2. Silent evaluator replay — B4 (acc23c5). After a VM failure, non-strict --bytecode re-ran the entry on the evaluator unless the VM itself had dispatched a native effect. IO done by an evaluator-only callee reached through the bridge was invisible to that guard, so the re-run duplicated output or read consumed stdin. Reproduced on v0.54.11: a @limit helper printing START before an over-limit recursion printed START twice. Fix: EffContext counts every capability check (shared across budget views) and the runner refuses the fallback when it moved during the VM run — the VM error surfaces instead. Regression test: TestBridgedEffectIsNotReplayed (mutation-checked).

    Decision recorded in m-bytecode-vm-parity-bugs.md (B4 = option b, pre-effect-only fallback). Known consequence: stream_sse/stream_websocket examples now fail loudly under non-strict --bytecode — their previous parity 'match' was a second network connect (bridge can't convert the stream builtins' untyped values; M-BYTECODE-2E).

    For stapledons-godot: the ai/voice.ail doubling workaround (W4) can be removed once on v0.54.12.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions