EXPERIMENT: invocation control resolves at runtime

ISHIKAWA: why did wraps_body need compile-time knowledge? Because the wrapper
called the target directly. If the wrapper calls through the seam instead, the
seam can call the body itself, and a construct bound after the build decides
how and whether to invoke it.

PREDICTIONS AND RESULTS
  P1 wrap becomes runtime-bindable                  TRUE   body x3 -> 21,
                                                           never invoked -> 111
  P2 codegen shrinks                                TRUE   5042 -> 4977
  P3 cost 5-10% from an indirect call on every fn   TRUE   0.36s -> 0.39s, ~8%
  P4 zero-param fns break on the empty struct       TRUE   empty struct is a GNU
                                                           extension, empty init
                                                           is C23. Fixed with a
                                                           char field.
  P5 fixpoint holds                                 TRUE

PROCESS FAILURE worth recording: my first patch silently did not apply because
I dropped the assert on the string replacement. The build then failed with
"undeclared identifier __thunk_noargs", which I nearly attributed to the
empty-struct prediction. The guard that would have caught it existed and I
removed it -- the same shape as every other defect found tonight.

Removed: declare_wrap, decorator_wrap, cg_wrap_target, cg_wrap_construct,
params_to_call_args, and the wraps_body scanner branch.

prohibits_outside is now the ONLY construct kind left at compile time, and it
cannot move: a #error has no runtime.
This commit is contained in:
bigmerge
2026-08-17 09:06:05 -05:00
parent b40754f07b
commit bc2f26ddfc
5 changed files with 70 additions and 89 deletions
+6 -1
View File
@@ -83,10 +83,15 @@ check "two constructs compose on one crossing" \
SEEN work/b
7" "$(cd "$WORK" && EL_CONSTRUCTS=two.txt ./prog 2>&1)"
cat >> "$WORK/targets.c" <<'TGT'
el_val_t thrice(el_val_t fn, el_val_t con, el_val_t (*b)(void*), void* e){
(void)fn; (void)con; return b(e) + b(e) + b(e); /* wrap: invoke N times */
}
TGT
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 " 7 assertions, $((6-FAILS)) passed, $FAILS failed"
echo " 7 assertions, $((7-FAILS)) passed, $FAILS failed"
exit $FAILS
+24 -7
View File
@@ -805,13 +805,6 @@ test "undeclared-guard-emits-nothing" {
// Declared constructs: wraps and prohibitions
test "declared-wrap-emits-closure-and-convention" {
let src: String = "@decorator(\"wraps_body\", \"with_timeout\")\nfn timed() {}\n@timed\nfn slow(k: Int) -> Int { return 9 }"
let out: String = compile_capture(src)
assert str_contains(out, "struct __env_slow"), "captured environment is emitted"
assert str_contains(out, "__thunk_slow(void* __v)"), "a thunk taking void* is emitted"
assert str_contains(out, "extern el_val_t with_timeout(el_val_t, el_val_t, el_val_t(*)(void*), void*);"), "codegen emits the calling convention — El's single type cannot describe a callable"
}
test "declared-prohibition-fires-outside-the-boundary" {
let src: String = "@decorator(\"prohibits_outside\", \"raw_sql\")\nfn repository() {}\nfn sneaky() -> Int { raw_sql(\"DROP\") return 1 }"
@@ -908,3 +901,27 @@ test "early-returns-route-through-the-exit-seam" {
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"
}
// Invocation control resolves at runtime
//
// Every fn gets an env struct and a thunk, because codegen cannot know which
// fns a wrap construct will be bound to after the binary exists. That the bound
// construct can invoke the body zero or N times is behaviour, so it lives in
// tests/integration/seam_binding.sh.
test "every-fn-gets-a-closure" {
let src: String = "fn f(k: Int) -> Int { return k }"
let out: String = compile_capture(src)
assert str_contains(out, "struct __env_f { el_val_t k; };"), "captured environment"
assert str_contains(out, "static el_val_t __thunk_f(void* __v)"), "thunk over that environment"
assert str_contains(out, "el_seam_wrap(EL_STR(\"f\"), __thunk_f, &__env)"), "invocation goes through the seam"
}
test "zero-param-fn-emits-valid-c" {
// An empty struct is a GNU extension and an empty initialiser is C23.
let src: String = "fn noargs() -> Int { return 3 }"
let out: String = compile_capture(src)
assert str_contains(out, "struct __env_noargs { char __e0; };"), "zero-param env has a field"
assert !str_contains(out, "__env = { }"), "and no empty initialiser"
}