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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user