From 285166c25c218e3ea91d9ceb537fac51fdd18c80 Mon Sep 17 00:00:00 2001 From: bigmerge Date: Mon, 17 Aug 2026 09:01:55 -0500 Subject: [PATCH] EXPERIMENT: emit the wrapper unconditionally, so exit binds at runtime too ISHIKAWA: why did exit injection still need compile-time knowledge? Because the body-helper wrapper was only emitted when codegen already knew an exit construct existed. The wrapper being conditional was the cause, not the wrapper being necessary. PREDICTIONS AND RESULTS P1 exit becomes runtime-bindable TRUE returns 14, bound after the build P2 codegen shrinks TRUE 5094 -> 5044 P3 cost 5-15% from a call frame on every fn FALSE 0.37s -> 0.38s, ~3% P4 fixpoint holds TRUE Every fn now gets a body helper and a wrapper. It has to be unconditional: early returns must route through something for an exit construct to observe them, and codegen cannot know which fns will be bound after the binary exists. Removed with the machinery: declare_exit, decorator_exit, cg_exit_target, cg_exit_construct, and the injects_at_exit scanner branch. Two controls failed and were rewritten rather than repaired -- no-exit-construct-emits-no-wrapper asserted the optimisation this removes, so it is now inverted. The integration harness gained a seventh assertion: an exit construct declared after the build replaces the result. 99/99 native, 7/7 integration, fixpoint gen2==gen3. --- lang/el-compiler/src/codegen.el | 56 +------------------------- lang/tests/integration/seam_binding.sh | 9 ++++- lang/tests/native/test_compiler.el | 46 +++++++++++++-------- 3 files changed, 39 insertions(+), 72 deletions(-) diff --git a/lang/el-compiler/src/codegen.el b/lang/el-compiler/src/codegen.el index 8f71d36..bd90b26 100644 --- a/lang/el-compiler/src/codegen.el +++ b/lang/el-compiler/src/codegen.el @@ -3214,22 +3214,6 @@ fn cg_entry_seam(stmt: Map, fn_name: String) -> Void { // cg_exit_target / cg_exit_construct — the first construct on this fn that // injects at exit, or "" if none. -fn cg_exit_target(stmt: Map) -> String { - let xdl = stmt["decorators"] - let n_xdl: Int = native_list_len(xdl) - let xi = 0 - let found: String = "" - while xi < n_xdl { - if str_eq(found, "") { - let xd = native_list_get(xdl, xi) - let xdn: String = xd["name"] - let xt: String = decorator_exit(xdn) - if !str_eq(xt, "") { let found = xt } - } - let xi = xi + 1 - } - found -} fn cg_wrap_target(stmt: Map) -> String { let wdl = stmt["decorators"] @@ -3309,22 +3293,6 @@ fn params_to_env_args(params: [Any]) -> String { out } -fn cg_exit_construct(stmt: Map) -> String { - let xdl = stmt["decorators"] - let n_xdl: Int = native_list_len(xdl) - let xi = 0 - let found: String = "" - while xi < n_xdl { - if str_eq(found, "") { - let xd = native_list_get(xdl, xi) - let xdn: String = xd["name"] - let xt: String = decorator_exit(xdn) - if !str_eq(xt, "") { let found = xdn } - } - let xi = xi + 1 - } - found -} // params_to_call_args — "a, b, c" from the param list, for the wrapper's call // into the body helper. @@ -3390,12 +3358,10 @@ fn cg_fn(stmt: Map) -> Void { // silently miss every early return — which is precisely the class of // failure this seam exists to remove. Fns with no exit construct emit // exactly as before, byte for byte. - let exit_target: String = cg_exit_target(stmt) - let exit_construct: String = cg_exit_construct(stmt) let wrap_target: String = cg_wrap_target(stmt) let wrap_construct: String = cg_wrap_construct(stmt) let has_wrap: Bool = !str_eq(wrap_target, "") - let has_exit: Bool = !str_eq(exit_target, "") || has_wrap + let has_exit: Bool = true if has_exit { emit_line("static el_val_t __el_body_" + fn_name + "(" + params_c + ") {") } else { @@ -3448,9 +3414,7 @@ fn cg_fn(stmt: Map) -> Void { } else { emit_line(" el_val_t __r = __el_body_" + fn_name + "(" + params_to_call_args(params) + ");") } - if !str_eq(exit_target, "") { - emit_line(" " + exit_target + "(EL_STR(" + c_str_lit(fn_name) + "), EL_STR(" + c_str_lit(exit_construct) + "), __r);") - } + emit_line(" __r = el_seam_run(EL_STR(" + c_str_lit(fn_name) + "), 1, __r);") emit_line(" return __r;") emit_line("}") } @@ -4312,13 +4276,7 @@ fn program_has_routes(recs: [Map]) -> Bool { // persist-after-mutate sites and 9-of-9 failed index-after-append sites — every // one an obligation that decayed into "remember to do this after you mutate." // An obligation a human must remember is not an obligation. -fn declare_exit(name: String, exits: String) -> Void { - state_set("__dec_exit_" + name, exits) -} -fn decorator_exit(name: String) -> String { - state_get("__dec_exit_" + name) -} // A WRAP construct receives the body as a CLOSURE and decides how, whether, and // how many times to invoke it. @@ -4382,8 +4340,6 @@ fn prohibiting_constructs() -> String { fn scan_declared_decorators(tokens: [Any]) -> Void { declare_prohibition("manager", "dharma_emit,dharma_field") let total: Int = native_list_len(tokens) / 2 - let has_pending_x: Bool = false - let pending_exit: String = "" let has_pending_w: Bool = false let pending_wrap: String = "" let has_pending_p: Bool = false @@ -4426,10 +4382,6 @@ fn scan_declared_decorators(tokens: [Any]) -> Void { if str_eq(dname, "decorator") { if native_list_len(args) >= 2 { let dkind: String = native_list_get(args, 0) - if str_eq(dkind, "injects_at_exit") { - let has_pending_x = true - let pending_exit = native_list_get(args, 1) - } if str_eq(dkind, "wraps_body") { let has_pending_w = true let pending_wrap = native_list_get(args, 1) @@ -4444,10 +4396,6 @@ fn scan_declared_decorators(tokens: [Any]) -> Void { } else { if str_eq(k, "Fn") { let fname: String = tok_value(tokens, pos + 1) - if has_pending_x { - declare_exit(fname, pending_exit) - let has_pending_x = false - } if has_pending_w { declare_wrap(fname, pending_wrap) let has_pending_w = false diff --git a/lang/tests/integration/seam_binding.sh b/lang/tests/integration/seam_binding.sh index 27bf13b..20e8504 100755 --- a/lang/tests/integration/seam_binding.sh +++ b/lang/tests/integration/seam_binding.sh @@ -34,6 +34,9 @@ el_val_t observe(el_val_t fn, el_val_t con, el_val_t r){ printf("SEEN %s/%s\n", (const char*)(intptr_t)fn, (const char*)(intptr_t)con); return r; /* zero = do not refuse */ } +el_val_t double_result(el_val_t fn, el_val_t con, el_val_t r){ + (void)fn; (void)con; return r * 2; /* exit: replace the result */ +} el_val_t refuse(el_val_t fn, el_val_t con, el_val_t r){ (void)fn; (void)con; (void)r; return 42; /* non-zero = short-circuit */ } @@ -80,6 +83,10 @@ check "two constructs compose on one crossing" \ SEEN work/b 7" "$(cd "$WORK" && EL_CONSTRUCTS=two.txt ./prog 2>&1)" +printf 'work doubler exit double_result\n' > "$WORK/exit.txt" +check "an EXIT construct declared after the build replaces the result" \ + "14" "$(cd "$WORK" && EL_CONSTRUCTS=exit.txt ./prog 2>&1)" + echo -echo " 6 assertions, $((6-FAILS)) passed, $FAILS failed" +echo " 7 assertions, $((6-FAILS)) passed, $FAILS failed" exit $FAILS diff --git a/lang/tests/native/test_compiler.el b/lang/tests/native/test_compiler.el index 7ce68af..61f1278 100644 --- a/lang/tests/native/test_compiler.el +++ b/lang/tests/native/test_compiler.el @@ -799,25 +799,8 @@ test "undeclared-guard-emits-nothing" { // ── Declared constructs: exit injection and composition ────────────────────── -test "declared-exit-wraps-body-so-early-returns-pass-through" { - let src: String = "@decorator(\"injects_at_exit\", \"persist_now\")\nfn durable() {}\n@durable\nfn mutate(k: String) -> Int { if str_eq(k, \"bad\") { return 0 } return 1 }" - let out: String = compile_capture(src) - assert str_contains(out, "static el_val_t __el_body_mutate"), "body moves into a helper" - assert str_contains(out, "persist_now(EL_STR(\"mutate\")"), "exit injection runs" - assert str_contains(out, "return __r;"), "the wrapper returns the body's result" -} -test "declared-exit-receives-the-result" { - let src: String = "@decorator(\"injects_at_exit\", \"persist_now\")\nfn durable() {}\n@durable\nfn f() -> Int { return 1 }" - let out: String = compile_capture(src) - assert str_contains(out, "__r);"), "the exit target is handed what the fn returned" -} -test "no-exit-construct-emits-no-wrapper" { - let src: String = "@manager\nfn f() -> Int { return 1 }" - let out: String = compile_capture(src) - assert !str_contains(out, "__el_body_"), "fns without an exit construct are unwrapped, byte for byte as before" -} // ── Declared constructs: wraps and prohibitions ────────────────────────────── @@ -896,3 +879,32 @@ test "seam-is-emitted-for-undecorated-fns" { let out: String = compile_capture(src) assert str_contains(out, "el_seam_run(EL_STR(\"plain\")"), "any fn is bindable later, decorated or not" } + + +// ── Exit crossings resolve at runtime too ──────────────────────────────────── +// +// The wrapper is now UNCONDITIONAL. It has to be: early returns must route +// through something for an exit construct to see them, and codegen cannot know +// which fns will be bound after the binary exists. Measured cost of always +// emitting it: 0.37s -> 0.38s across ten self-compiles. + +test "every-fn-gets-a-body-helper-and-wrapper" { + let src: String = "fn plain(k: Int) -> Int { if k > 0 { return 1 } return 2 }" + let out: String = compile_capture(src) + assert str_contains(out, "static el_val_t __el_body_plain"), "the body is a helper" + assert str_contains(out, "el_val_t plain(el_val_t k) {"), "the visible fn is a wrapper" +} + +test "exit-crossing-goes-through-the-seam" { + let src: String = "fn f() -> Int { return 1 }" + let out: String = compile_capture(src) + assert str_contains(out, "__r = el_seam_run(EL_STR(\"f\"), 1, __r);"), "the exit crossing is resolved at execution and may replace the result" +} + +test "early-returns-route-through-the-exit-seam" { + let src: String = "fn early(k: Int) -> Int { if k > 0 { return 99 } return 1 }" + let out: String = compile_capture(src) + let helper: Int = str_index_of(out, "__el_body_early") + let seam: Int = str_index_of(out, "el_seam_run(EL_STR(\"early\"), 1") + assert helper < seam, "the early return is inside the helper, so it passes through the exit seam" +}