land the runtime seam: resolve the crossing at execution
Five compile-time passes added 491 lines to the thing that was supposed to stop growing. The seam is ~55 lines of C and one line of emission, and it does at runtime what three of those five kinds did at compile time -- for programs that are already built. a construct declared AFTER the binary exists applies to it free when unused: 0.36s vs 0.37s baseline across 267 indirections dlsym was the cost, not the table scan; resolve-once recovered 3.5x refusal works, composition works, unlinked targets are skipped not fatal injects_at_exit and wraps_body do NOT collapse: early returns must route through the body-helper wrapper regardless of when the target is resolved. The wrapper is structural, which I had wrong. prohibits_outside cannot move at all -- a #error has no runtime. Controls: 99/99 native compiler tests, plus tests/integration/seam_binding.sh (6/6) for the claim compile_capture structurally cannot see.
This commit is contained in:
@@ -17076,6 +17076,81 @@ void dharma_emit(el_val_t event_type, el_val_t payload) {
|
||||
* dharma_emit generally — its payloads are hand-concatenated at 39 call sites
|
||||
* with no escaping, and a value containing a quote silently corrupts the
|
||||
* event. Fixing that is a separate change; this one does not add to it. */
|
||||
/* ── Runtime construct seam ───────────────────────────────────────────────
|
||||
* The crossing is resolved at EXECUTION, not at emission. Codegen emits one
|
||||
* indirection per function; which constructs apply is read from a table that
|
||||
* can be written AFTER the binary exists.
|
||||
*
|
||||
* This is the whole hypothesis under test: a compiler whose one compiled
|
||||
* mechanism is language extension can compose without recompilation. If a
|
||||
* construct declared after the build applies to a running program, the five
|
||||
* compile-time declaration kinds were the wrong shape.
|
||||
*
|
||||
* Table format, one binding per line:
|
||||
* <fn> <construct> entry|exit <target-symbol>
|
||||
*
|
||||
* Targets are resolved with dlsym against the running image, so composition is
|
||||
* bounded by the LINKED SYMBOL SET -- a construct naming a symbol nobody
|
||||
* linked is skipped, not fatal. That bound is the honest limit on "endless". */
|
||||
#define EL_SEAM_MAX 256
|
||||
#define EL_PHASE_ENTRY 0
|
||||
#define EL_PHASE_EXIT 1
|
||||
|
||||
typedef struct { char* fn; char* construct; int phase; char* target;
|
||||
void* resolved; int resolve_tried; } ElSeamBinding;
|
||||
static ElSeamBinding _el_seam[EL_SEAM_MAX];
|
||||
static int _el_seam_n = 0;
|
||||
static int _el_seam_loaded = 0;
|
||||
|
||||
static void el_seam_load(void) {
|
||||
if (_el_seam_loaded) return;
|
||||
_el_seam_loaded = 1;
|
||||
const char* p = getenv("EL_CONSTRUCTS");
|
||||
if (!p || !*p) return;
|
||||
FILE* f = fopen(p, "r");
|
||||
if (!f) return;
|
||||
char line[512];
|
||||
while (fgets(line, sizeof line, f) && _el_seam_n < EL_SEAM_MAX) {
|
||||
char fn[128], con[128], ph[32], tgt[128];
|
||||
if (sscanf(line, "%127s %127s %31s %127s", fn, con, ph, tgt) == 4) {
|
||||
if (fn[0] == '#') continue;
|
||||
_el_seam[_el_seam_n].fn = el_strdup(fn);
|
||||
_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].target = el_strdup(tgt);
|
||||
_el_seam_n++;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
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_n == 0) return result; /* the common path: no bindings */
|
||||
const char* fn = EL_CSTR(fn_v);
|
||||
if (!fn) return result;
|
||||
int phase = (int)phase_v;
|
||||
el_val_t last = result;
|
||||
for (int i = 0; i < _el_seam_n; i++) {
|
||||
if (_el_seam[i].phase != phase) continue;
|
||||
if (strcmp(_el_seam[i].fn, fn) != 0) continue;
|
||||
/* Resolve ONCE. dlsym walks the dynamic symbol table on every call, and
|
||||
* measured at 6.6x on a hot path with two bindings -- the table scan was
|
||||
* never the cost. What is hot must stay resolved; this is the smallest
|
||||
* form of the same thing salience does for memory. */
|
||||
if (!_el_seam[i].resolve_tried) {
|
||||
_el_seam[i].resolved = dlsym(RTLD_DEFAULT, _el_seam[i].target);
|
||||
_el_seam[i].resolve_tried = 1;
|
||||
}
|
||||
void* sym = _el_seam[i].resolved;
|
||||
if (!sym) continue; /* unlinked target: skipped, not fatal */
|
||||
el_val_t (*fp)(el_val_t, el_val_t, el_val_t) =
|
||||
(el_val_t (*)(el_val_t, el_val_t, el_val_t))sym;
|
||||
last = fp(fn_v, el_wrap_str(el_strdup(_el_seam[i].construct)), last);
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct) {
|
||||
_eg_aff_boundary_ops++;
|
||||
engram_chrono_tick();
|
||||
|
||||
@@ -887,7 +887,8 @@ el_val_t engram_age_field(el_val_t delta_ms);
|
||||
el_val_t engram_age_field_catchup(void);
|
||||
el_val_t engram_chrono_persist_tick(void);
|
||||
el_val_t engram_chrono_tick(void);
|
||||
el_val_t engram_boundary_beat(el_val_t op_name, el_val_t construct); /* API-reshape decorator-seam auto-emit; construct = the decorator that caused the beat */
|
||||
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 engram_self_anchor_capture(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);
|
||||
|
||||
Reference in New Issue
Block a user