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