region rule (25.3c): keep() and intern(), frame_keeps, and --arena-strict

keep(x) copies a string, a slice (header and elements) or a record (shallow) onto the heap; intern(s)
hands back one heap string per distinct text from a fixed table in the runtime (FNV-1a, 65536 slots,
copied the first time; past 49152 only copied). Both are how frame code keeps what it made on purpose:
the escape analysis takes the copy as the heap's and leaves the argument LOCAL.

The analysis now records why a class escapes (the store, the event, the global it reached) and
ludic deps lists every allocation frame code makes and keeps - fkeep lines, 'ludic deps --keeps',
the frame_keeps number --check ratchets - leaving out what is under @alloc_ok and a push's growth
(25.5's capacities). --arena-strict (or 'arena strict') makes each an error naming the store, before
anything is emitted. A test: a template stored into a state is the one error; keep and intern of the
next two, an @alloc_ok push and a scratch temporary are not; 195 frames of arena resets under
R3D_ARENA_CHECK=1 later the kept and interned texts read as made, and intern gives the same string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 16:33:53 +03:00
parent cd71da9330
commit a0b030290b
10 changed files with 80528 additions and 76677 deletions

View file

@ -1314,3 +1314,67 @@ function emit_fence_win() -> void {
emith(" ret i64 0\n")
emith("}\n")
}
function emit_fence_intern() -> void {
emith("@lp_itab = global [65536 x ptr] zeroinitializer\n")
emith("@lp_icount = global i64 0\n")
emith("define internal ptr @lp_copystr(ptr %s, i64 %n) {\n")
emith("entry:\n")
emith(" %n1 = add i64 %n, 1\n")
emith(" %p = call ptr @lp_malloc(i64 %n1)\n")
emith(" call ptr @memcpy(ptr %p, ptr %s, i64 %n1)\n")
emith(" ret ptr %p\n")
emith("}\n")
emith("define ptr @lp_intern(ptr %s) {\n")
emith("entry:\n")
emith(" %null = icmp eq ptr %s, null\n")
emith(" br i1 %null, label %none, label %hl\n")
emith("none:\n")
emith(" ret ptr null\n")
emith("hl:\n")
emith(" %i = phi i64 [ 0, %entry ], [ %i1, %hb ]\n")
emith(" %h = phi i64 [ -3750763034362895579, %entry ], [ %h2, %hb ]\n")
emith(" %cp = getelementptr i8, ptr %s, i64 %i\n")
emith(" %c = load i8, ptr %cp\n")
emith(" %end = icmp eq i8 %c, 0\n")
emith(" br i1 %end, label %probe, label %hb\n")
emith("hb:\n")
emith(" %cz = zext i8 %c to i64\n")
emith(" %h1 = xor i64 %h, %cz\n")
emith(" %h2 = mul i64 %h1, 1099511628211\n")
emith(" %i1 = add i64 %i, 1\n")
emith(" br label %hl\n")
emith("probe:\n")
emith(" %k = phi i64 [ 0, %hl ], [ %k1, %next ]\n")
emith(" %far = icmp uge i64 %k, 64\n")
emith(" br i1 %far, label %copy_only, label %look\n")
emith("look:\n")
emith(" %slot0 = add i64 %h, %k\n")
emith(" %slot = and i64 %slot0, 65535\n")
emith(" %sp = getelementptr [65536 x ptr], ptr @lp_itab, i64 0, i64 %slot\n")
emith(" %v = load ptr, ptr %sp\n")
emith(" %empty = icmp eq ptr %v, null\n")
emith(" br i1 %empty, label %store, label %cmp\n")
emith("cmp:\n")
emith(" %r = call i32 @strcmp(ptr %v, ptr %s)\n")
emith(" %same = icmp eq i32 %r, 0\n")
emith(" br i1 %same, label %found, label %next\n")
emith("next:\n")
emith(" %k1 = add i64 %k, 1\n")
emith(" br label %probe\n")
emith("found:\n")
emith(" ret ptr %v\n")
emith("store:\n")
emith(" %cnt = load i64, ptr @lp_icount\n")
emith(" %full = icmp uge i64 %cnt, 49152\n")
emith(" br i1 %full, label %copy_only, label %keep\n")
emith("keep:\n")
emith(" %c2 = call ptr @lp_copystr(ptr %s, i64 %i)\n")
emith(" store ptr %c2, ptr %sp\n")
emith(" %cnt1 = add i64 %cnt, 1\n")
emith(" store i64 %cnt1, ptr @lp_icount\n")
emith(" ret ptr %c2\n")
emith("copy_only:\n")
emith(" %c3 = call ptr @lp_copystr(ptr %s, i64 %i)\n")
emith(" ret ptr %c3\n")
emith("}\n")
}