land runtime invocation control: only prohibition remains compiled
This commit is contained in:
@@ -3215,44 +3215,15 @@ fn cg_entry_seam(stmt: Map<String, Any>, fn_name: String) -> Void {
|
|||||||
// cg_exit_target / cg_exit_construct — the first construct on this fn that
|
// cg_exit_target / cg_exit_construct — the first construct on this fn that
|
||||||
// injects at exit, or "" if none.
|
// injects at exit, or "" if none.
|
||||||
|
|
||||||
fn cg_wrap_target(stmt: Map<String, Any>) -> String {
|
|
||||||
let wdl = stmt["decorators"]
|
|
||||||
let n_wdl: Int = native_list_len(wdl)
|
|
||||||
let wi = 0
|
|
||||||
let found: String = ""
|
|
||||||
while wi < n_wdl {
|
|
||||||
if str_eq(found, "") {
|
|
||||||
let wd = native_list_get(wdl, wi)
|
|
||||||
let wdn: String = wd["name"]
|
|
||||||
let wt: String = decorator_wrap(wdn)
|
|
||||||
if !str_eq(wt, "") { let found = wt }
|
|
||||||
}
|
|
||||||
let wi = wi + 1
|
|
||||||
}
|
|
||||||
found
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cg_wrap_construct(stmt: Map<String, Any>) -> String {
|
|
||||||
let wdl = stmt["decorators"]
|
|
||||||
let n_wdl: Int = native_list_len(wdl)
|
|
||||||
let wi = 0
|
|
||||||
let found: String = ""
|
|
||||||
while wi < n_wdl {
|
|
||||||
if str_eq(found, "") {
|
|
||||||
let wd = native_list_get(wdl, wi)
|
|
||||||
let wdn: String = wd["name"]
|
|
||||||
let wt: String = decorator_wrap(wdn)
|
|
||||||
if !str_eq(wt, "") { let found = wdn }
|
|
||||||
}
|
|
||||||
let wi = wi + 1
|
|
||||||
}
|
|
||||||
found
|
|
||||||
}
|
|
||||||
|
|
||||||
// params_to_env_fields / params_to_env_init / params_to_env_args — the captured
|
// params_to_env_fields / params_to_env_init / params_to_env_args — the captured
|
||||||
// environment. This IS the closure: a struct of captured values, a function
|
// environment. This IS the closure: a struct of captured values, a function
|
||||||
// pointer that takes it, and the pair handed to the wrap target.
|
// pointer that takes it, and the pair handed to the wrap target.
|
||||||
fn params_to_env_fields(params: [Any]) -> String {
|
fn params_to_env_fields(params: [Any]) -> String {
|
||||||
|
// A zero-param fn would emit `struct __env_f { };` -- an empty struct is a
|
||||||
|
// GNU extension, not C99, and an empty initialiser is C23.
|
||||||
|
if native_list_len(params) == 0 { return " char __e0;" }
|
||||||
let out: String = ""
|
let out: String = ""
|
||||||
let n: Int = native_list_len(params)
|
let n: Int = native_list_len(params)
|
||||||
let i = 0
|
let i = 0
|
||||||
@@ -3266,6 +3237,7 @@ fn params_to_env_fields(params: [Any]) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn params_to_env_init(params: [Any]) -> String {
|
fn params_to_env_init(params: [Any]) -> String {
|
||||||
|
if native_list_len(params) == 0 { return "0" }
|
||||||
let out: String = ""
|
let out: String = ""
|
||||||
let n: Int = native_list_len(params)
|
let n: Int = native_list_len(params)
|
||||||
let i = 0
|
let i = 0
|
||||||
@@ -3296,19 +3268,6 @@ fn params_to_env_args(params: [Any]) -> String {
|
|||||||
|
|
||||||
// params_to_call_args — "a, b, c" from the param list, for the wrapper's call
|
// params_to_call_args — "a, b, c" from the param list, for the wrapper's call
|
||||||
// into the body helper.
|
// into the body helper.
|
||||||
fn params_to_call_args(params: [Any]) -> String {
|
|
||||||
let out: String = ""
|
|
||||||
let n: Int = native_list_len(params)
|
|
||||||
let i = 0
|
|
||||||
while i < n {
|
|
||||||
let p = native_list_get(params, i)
|
|
||||||
let pn: String = p["name"]
|
|
||||||
if i > 0 { let out = out + ", " }
|
|
||||||
let out = out + pn
|
|
||||||
let i = i + 1
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cg_fn(stmt: Map<String, Any>) -> Void {
|
fn cg_fn(stmt: Map<String, Any>) -> Void {
|
||||||
let fn_name: String = stmt["name"]
|
let fn_name: String = stmt["name"]
|
||||||
@@ -3358,11 +3317,7 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
|
|||||||
// silently miss every early return — which is precisely the class of
|
// silently miss every early return — which is precisely the class of
|
||||||
// failure this seam exists to remove. Fns with no exit construct emit
|
// failure this seam exists to remove. Fns with no exit construct emit
|
||||||
// exactly as before, byte for byte.
|
// exactly as before, byte for byte.
|
||||||
let wrap_target: String = cg_wrap_target(stmt)
|
if true {
|
||||||
let wrap_construct: String = cg_wrap_construct(stmt)
|
|
||||||
let has_wrap: Bool = !str_eq(wrap_target, "")
|
|
||||||
let has_exit: Bool = true
|
|
||||||
if has_exit {
|
|
||||||
emit_line("static el_val_t __el_body_" + fn_name + "(" + params_c + ") {")
|
emit_line("static el_val_t __el_body_" + fn_name + "(" + params_c + ") {")
|
||||||
} else {
|
} else {
|
||||||
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
||||||
@@ -3393,27 +3348,21 @@ fn cg_fn(stmt: Map<String, Any>) -> Void {
|
|||||||
// The wrapper: guards, entry injection, the body call, then the exit
|
// The wrapper: guards, entry injection, the body call, then the exit
|
||||||
// injection, which receives the result so it can observe what the fn
|
// injection, which receives the result so it can observe what the fn
|
||||||
// actually returned.
|
// actually returned.
|
||||||
if has_wrap {
|
if true {
|
||||||
// Codegen defines the wrap calling convention, so codegen declares it.
|
// EVERY fn gets an env struct and a thunk. Codegen cannot know which fns
|
||||||
// El has ONE type -- el_val_t = int64_t -- so El's own `extern fn` cannot
|
// a wrap construct will be bound to after the binary exists, and the
|
||||||
// describe a callable, and asking it to produces an int/pointer
|
// wrapper unconditionally calls through el_seam_wrap.
|
||||||
// mismatch. The convention is not El-expressible; it is emitted.
|
|
||||||
emit_line("extern el_val_t " + wrap_target + "(el_val_t, el_val_t, el_val_t(*)(void*), void*);")
|
|
||||||
emit_line("struct __env_" + fn_name + " { " + params_to_env_fields(params) + " };")
|
emit_line("struct __env_" + fn_name + " { " + params_to_env_fields(params) + " };")
|
||||||
emit_line("static el_val_t __thunk_" + fn_name + "(void* __v) {")
|
emit_line("static el_val_t __thunk_" + fn_name + "(void* __v) {")
|
||||||
emit_line(" struct __env_" + fn_name + "* __e = (struct __env_" + fn_name + "*)__v; (void)__e;")
|
emit_line(" struct __env_" + fn_name + "* __e = (struct __env_" + fn_name + "*)__v; (void)__e;")
|
||||||
emit_line(" return __el_body_" + fn_name + "(" + params_to_env_args(params) + ");")
|
emit_line(" return __el_body_" + fn_name + "(" + params_to_env_args(params) + ");")
|
||||||
emit_line("}")
|
emit_line("}")
|
||||||
}
|
}
|
||||||
if has_exit {
|
if true {
|
||||||
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
emit_line("el_val_t " + fn_name + "(" + params_c + ") {")
|
||||||
cg_entry_seam(stmt, fn_name)
|
cg_entry_seam(stmt, fn_name)
|
||||||
if has_wrap {
|
emit_line(" struct __env_" + fn_name + " __env = { " + params_to_env_init(params) + " };")
|
||||||
emit_line(" struct __env_" + fn_name + " __env = { " + params_to_env_init(params) + " };")
|
emit_line(" el_val_t __r = el_seam_wrap(EL_STR(" + c_str_lit(fn_name) + "), __thunk_" + fn_name + ", &__env);")
|
||||||
emit_line(" el_val_t __r = " + wrap_target + "(EL_STR(" + c_str_lit(fn_name) + "), EL_STR(" + c_str_lit(wrap_construct) + "), __thunk_" + fn_name + ", &__env);")
|
|
||||||
} else {
|
|
||||||
emit_line(" el_val_t __r = __el_body_" + fn_name + "(" + params_to_call_args(params) + ");")
|
|
||||||
}
|
|
||||||
emit_line(" __r = el_seam_run(EL_STR(" + c_str_lit(fn_name) + "), 1, __r);")
|
emit_line(" __r = el_seam_run(EL_STR(" + c_str_lit(fn_name) + "), 1, __r);")
|
||||||
emit_line(" return __r;")
|
emit_line(" return __r;")
|
||||||
emit_line("}")
|
emit_line("}")
|
||||||
@@ -4291,13 +4240,7 @@ fn program_has_routes(recs: [Map<String, Any>]) -> Bool {
|
|||||||
// only decide whether to repeat — no timeout, no rollback-and-retry, no
|
// only decide whether to repeat — no timeout, no rollback-and-retry, no
|
||||||
// parallel, no memoize-on-arguments. It would also have crippled the JS backend,
|
// parallel, no memoize-on-arguments. It would also have crippled the JS backend,
|
||||||
// which has closures natively, for a limit in the C one.
|
// which has closures natively, for a limit in the C one.
|
||||||
fn declare_wrap(name: String, wraps: String) -> Void {
|
|
||||||
state_set("__dec_wrap_" + name, wraps)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decorator_wrap(name: String) -> String {
|
|
||||||
state_get("__dec_wrap_" + name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A PROHIBITION is the other half of a boundary: not what runs when something
|
// A PROHIBITION is the other half of a boundary: not what runs when something
|
||||||
// crosses, but what may not cross at all.
|
// crosses, but what may not cross at all.
|
||||||
@@ -4340,8 +4283,6 @@ fn prohibiting_constructs() -> String {
|
|||||||
fn scan_declared_decorators(tokens: [Any]) -> Void {
|
fn scan_declared_decorators(tokens: [Any]) -> Void {
|
||||||
declare_prohibition("manager", "dharma_emit,dharma_field")
|
declare_prohibition("manager", "dharma_emit,dharma_field")
|
||||||
let total: Int = native_list_len(tokens) / 2
|
let total: Int = native_list_len(tokens) / 2
|
||||||
let has_pending_w: Bool = false
|
|
||||||
let pending_wrap: String = ""
|
|
||||||
let has_pending_p: Bool = false
|
let has_pending_p: Bool = false
|
||||||
let pending_prohibit: String = ""
|
let pending_prohibit: String = ""
|
||||||
let pos: Int = 0
|
let pos: Int = 0
|
||||||
@@ -4382,10 +4323,6 @@ fn scan_declared_decorators(tokens: [Any]) -> Void {
|
|||||||
if str_eq(dname, "decorator") {
|
if str_eq(dname, "decorator") {
|
||||||
if native_list_len(args) >= 2 {
|
if native_list_len(args) >= 2 {
|
||||||
let dkind: String = native_list_get(args, 0)
|
let dkind: String = native_list_get(args, 0)
|
||||||
if str_eq(dkind, "wraps_body") {
|
|
||||||
let has_pending_w = true
|
|
||||||
let pending_wrap = native_list_get(args, 1)
|
|
||||||
}
|
|
||||||
if str_eq(dkind, "prohibits_outside") {
|
if str_eq(dkind, "prohibits_outside") {
|
||||||
let has_pending_p = true
|
let has_pending_p = true
|
||||||
let pending_prohibit = native_list_get(args, 1)
|
let pending_prohibit = native_list_get(args, 1)
|
||||||
@@ -4396,10 +4333,6 @@ fn scan_declared_decorators(tokens: [Any]) -> Void {
|
|||||||
} else {
|
} else {
|
||||||
if str_eq(k, "Fn") {
|
if str_eq(k, "Fn") {
|
||||||
let fname: String = tok_value(tokens, pos + 1)
|
let fname: String = tok_value(tokens, pos + 1)
|
||||||
if has_pending_w {
|
|
||||||
declare_wrap(fname, pending_wrap)
|
|
||||||
let has_pending_w = false
|
|
||||||
}
|
|
||||||
if has_pending_p {
|
if has_pending_p {
|
||||||
declare_prohibition(fname, pending_prohibit)
|
declare_prohibition(fname, pending_prohibit)
|
||||||
let has_pending_p = false
|
let has_pending_p = false
|
||||||
|
|||||||
@@ -17116,7 +17116,8 @@ static void el_seam_load(void) {
|
|||||||
if (fn[0] == '#') continue;
|
if (fn[0] == '#') continue;
|
||||||
_el_seam[_el_seam_n].fn = el_strdup(fn);
|
_el_seam[_el_seam_n].fn = el_strdup(fn);
|
||||||
_el_seam[_el_seam_n].construct = el_strdup(con);
|
_el_seam[_el_seam_n].construct = el_strdup(con);
|
||||||
_el_seam[_el_seam_n].phase = (strcmp(ph, "exit") == 0) ? EL_PHASE_EXIT : EL_PHASE_ENTRY;
|
_el_seam[_el_seam_n].phase = (strcmp(ph, "exit") == 0) ? EL_PHASE_EXIT :
|
||||||
|
(strcmp(ph, "wrap") == 0) ? 2 : EL_PHASE_ENTRY;
|
||||||
_el_seam[_el_seam_n].target = el_strdup(tgt);
|
_el_seam[_el_seam_n].target = el_strdup(tgt);
|
||||||
_el_seam_n++;
|
_el_seam_n++;
|
||||||
}
|
}
|
||||||
@@ -17124,6 +17125,30 @@ static void el_seam_load(void) {
|
|||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* el_seam_wrap — the seam calls the body itself, so a bound construct can
|
||||||
|
* control invocation: run it zero times, N times, or around a transaction.
|
||||||
|
* With no binding it is a direct call through the thunk, which is what the
|
||||||
|
* unwrapped code did anyway. */
|
||||||
|
el_val_t el_seam_wrap(el_val_t fn_v, el_val_t (*body)(void*), void* env) {
|
||||||
|
if (!_el_seam_loaded) el_seam_load();
|
||||||
|
if (_el_seam_n == 0) return body(env);
|
||||||
|
const char* fn = EL_CSTR(fn_v);
|
||||||
|
if (!fn) return body(env);
|
||||||
|
for (int i = 0; i < _el_seam_n; i++) {
|
||||||
|
if (_el_seam[i].phase != 2) continue; /* 2 = wrap */
|
||||||
|
if (strcmp(_el_seam[i].fn, fn) != 0) continue;
|
||||||
|
if (!_el_seam[i].resolve_tried) {
|
||||||
|
_el_seam[i].resolved = dlsym(RTLD_DEFAULT, _el_seam[i].target);
|
||||||
|
_el_seam[i].resolve_tried = 1;
|
||||||
|
}
|
||||||
|
if (!_el_seam[i].resolved) continue;
|
||||||
|
el_val_t (*fp)(el_val_t, el_val_t, el_val_t (*)(void*), void*) =
|
||||||
|
(el_val_t (*)(el_val_t, el_val_t, el_val_t (*)(void*), void*))_el_seam[i].resolved;
|
||||||
|
return fp(fn_v, el_wrap_str(el_strdup(_el_seam[i].construct)), body, env);
|
||||||
|
}
|
||||||
|
return body(env);
|
||||||
|
}
|
||||||
|
|
||||||
el_val_t el_seam_run(el_val_t fn_v, el_val_t phase_v, el_val_t result) {
|
el_val_t el_seam_run(el_val_t fn_v, el_val_t phase_v, el_val_t result) {
|
||||||
if (!_el_seam_loaded) el_seam_load();
|
if (!_el_seam_loaded) el_seam_load();
|
||||||
if (_el_seam_n == 0) return result; /* the common path: no bindings */
|
if (_el_seam_n == 0) return result; /* the common path: no bindings */
|
||||||
|
|||||||
@@ -888,7 +888,8 @@ el_val_t engram_age_field_catchup(void);
|
|||||||
el_val_t engram_chrono_persist_tick(void);
|
el_val_t engram_chrono_persist_tick(void);
|
||||||
el_val_t engram_chrono_tick(void);
|
el_val_t engram_chrono_tick(void);
|
||||||
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct);
|
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct);
|
||||||
el_val_t el_seam_run(el_val_t fn_name, el_val_t phase, el_val_t result); /* runtime construct seam */ /* API-reshape decorator-seam auto-emit; construct = the decorator that caused the beat */
|
el_val_t el_seam_run(el_val_t fn_name, el_val_t phase, el_val_t result); /* runtime construct seam */
|
||||||
|
el_val_t el_seam_wrap(el_val_t fn_name, el_val_t (*body)(void*), void* env); /* runtime invocation control */ /* API-reshape decorator-seam auto-emit; construct = the decorator that caused the beat */
|
||||||
el_val_t engram_self_anchor_capture(void);
|
el_val_t engram_self_anchor_capture(void);
|
||||||
el_val_t engram_self_drift_json(void);
|
el_val_t engram_self_drift_json(void);
|
||||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||||
|
|||||||
@@ -83,10 +83,15 @@ check "two constructs compose on one crossing" \
|
|||||||
SEEN work/b
|
SEEN work/b
|
||||||
7" "$(cd "$WORK" && EL_CONSTRUCTS=two.txt ./prog 2>&1)"
|
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"
|
printf 'work doubler exit double_result\n' > "$WORK/exit.txt"
|
||||||
check "an EXIT construct declared after the build replaces the result" \
|
check "an EXIT construct declared after the build replaces the result" \
|
||||||
"14" "$(cd "$WORK" && EL_CONSTRUCTS=exit.txt ./prog 2>&1)"
|
"14" "$(cd "$WORK" && EL_CONSTRUCTS=exit.txt ./prog 2>&1)"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo " 7 assertions, $((6-FAILS)) passed, $FAILS failed"
|
echo " 7 assertions, $((7-FAILS)) passed, $FAILS failed"
|
||||||
exit $FAILS
|
exit $FAILS
|
||||||
|
|||||||
@@ -805,13 +805,6 @@ test "undeclared-guard-emits-nothing" {
|
|||||||
|
|
||||||
// ── Declared constructs: wraps and prohibitions ──────────────────────────────
|
// ── 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" {
|
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 }"
|
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")
|
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"
|
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