arena (25.3b): LOCAL sites allocate from the frame's scratch; dispatch is not an allocation; @frame by property

Behind ludicc --arena (or 'arena on' in the program's package.ludic): the escape analysis runs and a
LOCAL site's allocation raises @lp_want for that one call, so it comes from the frame's arena. Two
halves in one mmap reservation (R3D_ARENA_MB each, 256 by default), bump-allocated with a 16-byte
size header, flipped at each frame mark: a frame's scratch is good through the next frame, then its
half is started again (R3D_ARENA_CHECK=1 fills it with 0xDD first). The heap takes over when no frame
is running, off the main thread, or past the half's end; lp_free ignores an arena block and
lp_realloc copies one out. R3D_ARENA=0 turns it off at run time; the census reports each half's
high-water mark. A program that builds text, a list and a record per frame: 29998 heap blocks made
and 18002 freed without it, 8 and 8 with it and 544 bytes of scratch a frame, the same output.

A dispatch's 'new' fills the queue's kept record (E_NEW.b), so 25.2 no longer counts it and 25.3
treats its fields as kept. '@frame' is keyed by property and field: a 'run' field is a root only in
a property that marks it, and 'tick' stays a System's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 16:25:32 +03:00
parent 8dfcccad41
commit bef0d6fbca
11 changed files with 80823 additions and 78428 deletions

View file

@ -1039,7 +1039,18 @@ function emit_call(e: Node) -> Val {
return val(rreg, fn2.ty)
}
# 25.3: which expression is being emitted, when it is a LOCAL site - fence_bind asks for scratch
# for that expression's own allocation, and never for one of its parts that is not one
function emit_expr(e: Node) -> Val {
if (e == null) { return val("0", "int") }
let saved = g_es_node
if e.uns == ES_SCRATCH { g_es_node = e } else { g_es_node = null }
let v = emit_expr_raw(e)
g_es_node = saved
return v
}
function emit_expr_raw(e: Node) -> Val {
if (e == null) { return val("0", "int") }
if e.kind == E_INT {
if e.s != null { return val(e.s, "long") } # a literal past 2^31 - 1

View file

@ -104,6 +104,13 @@ function es_val(e: Node) -> int {
return -1
}
if k == E_UN { es_val(e.a); return -1 }
if k == E_NEW and e.b != null { # a dispatch fills the queue's kept record: kept
if e.a != null and e.a.kids != null {
var i = 0
while i < len(e.a.kids) { es_escape(es_val(e.a.kids[i].a)); i += 1 }
}
return g_es_heap
}
if k == E_NEW {
let s = es_site(e)
if e.a != null and e.a.kids != null {

View file

@ -26,12 +26,17 @@ var g_fence_mode: int = 0 # 0: headless fails, windowed fai
var g_fence_warm: int = 600
var g_fence_census: pointer = null
var g_fence_flagged: bool = false # a flag said so: the manifest does not override it
var g_arena: bool = false # 25.3: --arena / `arena on` - LOCAL sites allocate from the frame's scratch
var g_mmap_declared: bool = false # @mmap: by the fence's arena or by the asset pack
var g_es_node: Node = null # the expression being emitted, when it is a LOCAL site
function fence_reset() -> void {
g_site_fn = new []pointer; g_site_file = new []pointer; g_site_line = new []int; g_site_kind = new []pointer
g_site_kinds = new []pointer; g_site_kindc = new []pointer
g_atexit_declared = false
g_mzs_declared = false
g_mmap_declared = false
g_es_node = null
g_cur_fn_name = "?"; g_site_fnc = null; g_site_filen = null; g_site_filec = null; g_site_next = null
push(g_site_fn, null); push(g_site_file, null); push(g_site_line, 0); push(g_site_kind, null) # site 0, named at the end
}
@ -69,17 +74,25 @@ function emit_site(kind: pointer) -> void {
emit(` store i32 {itoa(id)}, ptr @lp_site\n`)
}
# emit_bind's hook: a call that allocates gets a site first, its kind read off the callee
function fence_bind(rest: pointer) -> void {
if not str_starts(rest, "call ptr @lp_") { return }
if str_starts(rest, "call ptr @lp_malloc(") { emit_site("alloc"); return }
if str_starts(rest, "call ptr @lp_calloc(") { emit_site("slice data"); return }
if str_starts(rest, "call ptr @lp_realloc(") { emit_site("grow"); return }
if str_starts(rest, "call ptr @lp_str_concat(") { emit_site("concat"); return }
if str_starts(rest, "call ptr @lp_int_str(") or str_starts(rest, "call ptr @lp_long_str(") { emit_site("string(n)"); return }
if str_starts(rest, "call ptr @lp_fp_str(") { emit_site("string(x)"); return }
if str_starts(rest, "call ptr @lp_str_slice(") { emit_site("substring"); return }
if str_starts(rest, "call ptr @lp_text_") { emit_site("Text"); return }
# emit_bind's hook: a call that allocates gets a site first, its kind read off the callee; when the
# expression being emitted is a LOCAL site (25.3) it also raises @lp_want for that one call
function fence_bind(rest: pointer) -> bool {
if not str_starts(rest, "call ptr @lp_") { return false }
var alloc = true
if str_starts(rest, "call ptr @lp_malloc(") { emit_site("alloc") }
else if str_starts(rest, "call ptr @lp_calloc(") { emit_site("slice data") }
else if str_starts(rest, "call ptr @lp_realloc(") { emit_site("grow") }
else if str_starts(rest, "call ptr @lp_str_concat(") { emit_site("concat") }
else if str_starts(rest, "call ptr @lp_int_str(") or str_starts(rest, "call ptr @lp_long_str(") { emit_site("string(n)") }
else if str_starts(rest, "call ptr @lp_fp_str(") { emit_site("string(x)") }
else if str_starts(rest, "call ptr @lp_str_slice(") { emit_site("substring") }
else if str_starts(rest, "call ptr @lp_text_") { emit_site("Text"); alloc = false }
else { alloc = false }
if alloc and g_arena and g_es_node != null {
emit(" store i8 1, ptr @lp_want\n")
return true
}
return false
}
function fence_int(s: pointer) -> int {
@ -106,6 +119,7 @@ function fence_flag(a: pointer) -> bool {
if str_starts(a, "--fence=") { g_fence_mode = fence_mode_of(a[8..len(a)]); g_fence_flagged = true; return true }
if str_starts(a, "--fence-warm=") { g_fence_warm = fence_int(a[13..len(a)]); g_fence_flagged = true; return true }
if str_starts(a, "--fence-census=") { g_fence_census = a[15..len(a)]; g_fence_flagged = true; return true }
if (a == "--arena") { g_arena = true; return true }
return false
}
@ -137,6 +151,7 @@ function fence_manifest_read(txt: pointer) -> void {
if (len(ws) == 2) and (ws[0] == "fence") { g_fence_mode = fence_mode_of(ws[1]) }
if (len(ws) == 3) and (ws[0] == "fence") and (ws[1] == "warm") { g_fence_warm = fence_int(ws[2]) }
if (len(ws) == 3) and (ws[0] == "fence") and (ws[1] == "census") { g_fence_census = ws[2] }
if (len(ws) == 2) and (ws[0] == "arena") and (ws[1] == "on") { g_arena = true }
i = e + 1
}
}
@ -171,6 +186,10 @@ function emit_fence_runtime() -> void {
if not g_fprintf_declared { emith("declare i32 @fprintf(ptr, ptr, ...)\n"); g_fprintf_declared = true }
if not g_atexit_declared { emith("declare i32 @atexit(ptr)\n"); g_atexit_declared = true }
if not g_mzs_declared { emith("declare extern_weak void @malloc_zone_statistics(ptr, ptr)\n"); g_mzs_declared = true }
if not g_mmap_declared { emith("declare ptr @mmap(ptr, i64, i32, i32, i32, i64)\n"); g_mmap_declared = true }
var ard = "0"
if g_arena { ard = "1" }
emith(`@lp_ardef = global i8 {ard}\n`)
emit_fence_globals()
emit_fence_entry()
emit_fence_table()

View file

@ -2,6 +2,19 @@
# template (docs/plan 25); the tables sized by the program are emit_fence.ludic's.
function emit_fence_globals() -> void {
emith("@lp_site = global i32 0\n")
emith("@lp_want = global i8 0\n")
emith("@lp_ar_on = global i8 0\n")
emith("@lp_ar_tried = global i8 0\n")
emith("@lp_ar_lo = global i64 0\n")
emith("@lp_ar_hi = global i64 0\n")
emith("@lp_ar_cur = global i64 0\n")
emith("@lp_ar_end = global i64 0\n")
emith("@lp_ar_which = global i64 0\n")
emith("@lp_ar_half = global i64 0\n")
emith("@lp_ar_main = global ptr null\n")
emith("@lp_ar_hw0 = global i64 0\n")
emith("@lp_ar_hw1 = global i64 0\n")
emith("@lp_ar_check = global i8 0\n")
emith("@lp_fdecl = global i32 0\n")
emith("@lp_dlive = global i64 0\n")
emith("@lp_mdlive = global i64 0\n")
@ -47,6 +60,7 @@ function emit_fence_globals() -> void {
emith("declare i64 @_Unwind_GetIP(ptr)\n")
emith("declare i32 @dladdr(ptr, ptr)\n")
emith("declare ptr @dlsym(ptr, ptr)\n")
emith("declare ptr @pthread_self()\n")
emith("@.lpf_env = private unnamed_addr constant [16 x i8] c\"R3D_ALLOC_FENCE\\00\"\n")
emith("@.lpf_warm = private unnamed_addr constant [15 x i8] c\"R3D_ALLOC_WARM\\00\"\n")
emith("@.lpf_play = private unnamed_addr constant [15 x i8] c\"R3D_ALLOC_PLAY\\00\"\n")
@ -60,6 +74,10 @@ function emit_fence_globals() -> void {
emith("@.lpf_row = private unnamed_addr constant [26 x i8] c\" %+lld B %s %s:%d %s\\0A\\00\"\n")
emith("@.lpf_more = private unnamed_addr constant [21 x i8] c\" ... and %lld more\\0A\\00\"\n")
emith("@.lpf_full = private unnamed_addr constant [99 x i8] c\"alloc-fence: the table is full (%lld blocks) - raise R3D_ALLOC_TABLE; from here it judges nothing\\0A\\00\"\n")
emith("@.lpf_arena = private unnamed_addr constant [10 x i8] c\"R3D_ARENA\\00\"\n")
emith("@.lpf_arenamb = private unnamed_addr constant [13 x i8] c\"R3D_ARENA_MB\\00\"\n")
emith("@.lpf_archeck = private unnamed_addr constant [16 x i8] c\"R3D_ARENA_CHECK\\00\"\n")
emith("@.lpf_car = private unnamed_addr constant [61 x i8] c\"arena %lld %lld (each frame arena's high-water mark, bytes)\\0A\\00\"\n")
emith("@.lpf_failing = private unnamed_addr constant [53 x i8] c\"alloc-fence: failing the run (R3D_ALLOC_FENCE=fail)\\0A\\00\"\n")
emith("@.lpf_census = private unnamed_addr constant [17 x i8] c\"R3D_ALLOC_CENSUS\\00\"\n")
emith("@.lpf_warmmax = private unnamed_addr constant [19 x i8] c\"R3D_ALLOC_WARM_MAX\\00\"\n")
@ -80,7 +98,10 @@ function emit_fence_globals() -> void {
emith("@.lpf_lp = private unnamed_addr constant [4 x i8] c\"lp_\\00\"\n")
}
function emit_fence_entry() -> void {
emith("define ptr @lp_malloc(i64 %n) {\n")
emith("; the four entry points. Off: one load of the fence and the scratch request, one branch, then libc.\n")
emith("; @lp_want is raised by the emitter around an allocation of a LOCAL site (25.3): it goes to the\n")
emith("; frame's scratch arena, and lp_free hands an arena block back to nobody\n")
emith("define internal ptr @lp_heap(i64 %n) {\n")
emith("entry:\n")
emith(" %t = load i8, ptr @lp_ft\n")
emith(" %on = icmp ne i8 %t, 0\n")
@ -93,47 +114,254 @@ function emit_fence_entry() -> void {
emith(" call void @lp_fadd(ptr %q, i64 %n)\n")
emith(" ret ptr %q\n")
emith("}\n")
emith("define ptr @lp_malloc(i64 %n) {\n")
emith("entry:\n")
emith(" %t = load i8, ptr @lp_ft\n")
emith(" %w = load i8, ptr @lp_want\n")
emith(" %o = or i8 %t, %w\n")
emith(" %on = icmp ne i8 %o, 0\n")
emith(" br i1 %on, label %slow, label %fast\n")
emith("fast:\n")
emith(" %p = tail call ptr @malloc(i64 %n)\n")
emith(" ret ptr %p\n")
emith("slow:\n")
emith(" %wq = icmp ne i8 %w, 0\n")
emith(" br i1 %wq, label %scratch, label %heap\n")
emith("scratch:\n")
emith(" store i8 0, ptr @lp_want\n")
emith(" %s = call ptr @lp_scratch(i64 %n)\n")
emith(" ret ptr %s\n")
emith("heap:\n")
emith(" %q = call ptr @malloc(i64 %n)\n")
emith(" call void @lp_fadd(ptr %q, i64 %n)\n")
emith(" ret ptr %q\n")
emith("}\n")
emith("define ptr @lp_calloc(i64 %n, i64 %s) {\n")
emith("entry:\n")
emith(" %t = load i8, ptr @lp_ft\n")
emith(" %on = icmp ne i8 %t, 0\n")
emith(" %w = load i8, ptr @lp_want\n")
emith(" %o = or i8 %t, %w\n")
emith(" %on = icmp ne i8 %o, 0\n")
emith(" br i1 %on, label %slow, label %fast\n")
emith("fast:\n")
emith(" %p = tail call ptr @calloc(i64 %n, i64 %s)\n")
emith(" ret ptr %p\n")
emith("slow:\n")
emith(" %q = call ptr @calloc(i64 %n, i64 %s)\n")
emith(" %b = mul i64 %n, %s\n")
emith(" %wq = icmp ne i8 %w, 0\n")
emith(" br i1 %wq, label %scratch, label %heap\n")
emith("scratch:\n")
emith(" store i8 0, ptr @lp_want\n")
emith(" %z = call ptr @lp_scratch(i64 %b)\n")
emith(" call ptr @memset(ptr %z, i32 0, i64 %b)\n")
emith(" ret ptr %z\n")
emith("heap:\n")
emith(" %q = call ptr @calloc(i64 %n, i64 %s)\n")
emith(" call void @lp_fadd(ptr %q, i64 %b)\n")
emith(" ret ptr %q\n")
emith("}\n")
emith("define ptr @lp_realloc(ptr %o, i64 %n) {\n")
emith("entry:\n")
emith(" %t = load i8, ptr @lp_ft\n")
emith(" %on = icmp ne i8 %t, 0\n")
emith(" %w = load i8, ptr @lp_want\n")
emith(" %a = load i8, ptr @lp_ar_on\n")
emith(" %o1 = or i8 %t, %w\n")
emith(" %o2 = or i8 %o1, %a\n")
emith(" %on = icmp ne i8 %o2, 0\n")
emith(" br i1 %on, label %slow, label %fast\n")
emith("fast:\n")
emith(" %p = tail call ptr @realloc(ptr %o, i64 %n)\n")
emith(" ret ptr %p\n")
emith("slow:\n")
emith(" call void @lp_fdel(ptr %o)\n")
emith(" %q = call ptr @realloc(ptr %o, i64 %n)\n")
emith(" call void @lp_fadd(ptr %q, i64 %n)\n")
emith(" %in = call i1 @lp_in_arena(ptr %o)\n")
emith(" %wq = icmp ne i8 %w, 0\n")
emith(" %move = or i1 %in, %wq\n")
emith(" br i1 %move, label %scratch, label %heap\n")
emith("scratch:\n")
emith(" store i8 0, ptr @lp_want\n")
emith(" %q = call ptr @lp_scratch(i64 %n)\n")
emith(" %none = icmp eq ptr %o, null\n")
emith(" br i1 %none, label %done, label %copy\n")
emith("copy:\n")
emith(" br i1 %in, label %oldar, label %oldheap\n")
emith("oldar:\n")
emith(" %hp = getelementptr i8, ptr %o, i64 -16\n")
emith(" %osz = load i64, ptr %hp\n")
emith(" br label %cp\n")
emith("oldheap:\n")
emith(" %has = icmp ne ptr @malloc_size, null\n")
emith(" br i1 %has, label %msz, label %cp\n")
emith("msz:\n")
emith(" %hsz = call i64 @malloc_size(ptr %o)\n")
emith(" br label %cp\n")
emith("cp:\n")
emith(" %old = phi i64 [ %osz, %oldar ], [ 0, %oldheap ], [ %hsz, %msz ]\n")
emith(" %less = icmp ult i64 %old, %n\n")
emith(" %c = select i1 %less, i64 %old, i64 %n\n")
emith(" call ptr @memcpy(ptr %q, ptr %o, i64 %c)\n")
emith(" br i1 %in, label %done, label %givebk\n")
emith("givebk:\n")
emith(" call void @lp_free(ptr %o)\n")
emith(" br label %done\n")
emith("done:\n")
emith(" ret ptr %q\n")
emith("heap:\n")
emith(" %tq = icmp ne i8 %t, 0\n")
emith(" br i1 %tq, label %tracked, label %plain\n")
emith("plain:\n")
emith(" %r = tail call ptr @realloc(ptr %o, i64 %n)\n")
emith(" ret ptr %r\n")
emith("tracked:\n")
emith(" call void @lp_fdel(ptr %o)\n")
emith(" %r2 = call ptr @realloc(ptr %o, i64 %n)\n")
emith(" call void @lp_fadd(ptr %r2, i64 %n)\n")
emith(" ret ptr %r2\n")
emith("}\n")
emith("define void @lp_free(ptr %o) {\n")
emith("entry:\n")
emith(" %t = load i8, ptr @lp_ft\n")
emith(" %on = icmp ne i8 %t, 0\n")
emith(" %a = load i8, ptr @lp_ar_on\n")
emith(" %o1 = or i8 %t, %a\n")
emith(" %on = icmp ne i8 %o1, 0\n")
emith(" br i1 %on, label %slow, label %fast\n")
emith("fast:\n")
emith(" tail call void @free(ptr %o)\n")
emith(" ret void\n")
emith("slow:\n")
emith(" %in = call i1 @lp_in_arena(ptr %o)\n")
emith(" br i1 %in, label %keep, label %give\n")
emith("keep:\n")
emith(" ret void\n")
emith("give:\n")
emith(" %tq = icmp ne i8 %t, 0\n")
emith(" br i1 %tq, label %tracked, label %plain\n")
emith("plain:\n")
emith(" tail call void @free(ptr %o)\n")
emith(" ret void\n")
emith("tracked:\n")
emith(" call void @lp_fdel(ptr %o)\n")
emith(" call void @free(ptr %o)\n")
emith(" ret void\n")
emith("}\n")
emith("define internal i1 @lp_in_arena(ptr %o) {\n")
emith("entry:\n")
emith(" %x = ptrtoint ptr %o to i64\n")
emith(" %lo = load i64, ptr @lp_ar_lo\n")
emith(" %hi = load i64, ptr @lp_ar_hi\n")
emith(" %a = icmp uge i64 %x, %lo\n")
emith(" %b = icmp ult i64 %x, %hi\n")
emith(" %in = and i1 %a, %b\n")
emith(" ret i1 %in\n")
emith("}\n")
emith("; a block from the frame's arena: a 16-byte header (its size) and 16-aligned; the heap when no\n")
emith("; frame is running, off the main thread, or past the arena's end\n")
emith("define internal ptr @lp_scratch(i64 %n) {\n")
emith("entry:\n")
emith(" %on = load i8, ptr @lp_ar_on\n")
emith(" %onq = icmp ne i8 %on, 0\n")
emith(" br i1 %onq, label %thread, label %heap\n")
emith("thread:\n")
emith(" %me = call ptr @pthread_self()\n")
emith(" %main = load ptr, ptr @lp_ar_main\n")
emith(" %mainq = icmp eq ptr %me, %main\n")
emith(" br i1 %mainq, label %bump, label %heap\n")
emith("bump:\n")
emith(" %cur = load i64, ptr @lp_ar_cur\n")
emith(" %n16 = add i64 %n, 31\n")
emith(" %need = and i64 %n16, -16\n")
emith(" %np = add i64 %cur, %need\n")
emith(" %end = load i64, ptr @lp_ar_end\n")
emith(" %fits = icmp ule i64 %np, %end\n")
emith(" br i1 %fits, label %take, label %heap\n")
emith("take:\n")
emith(" store i64 %np, ptr @lp_ar_cur\n")
emith(" %hp = inttoptr i64 %cur to ptr\n")
emith(" store i64 %n, ptr %hp\n")
emith(" %bp = getelementptr i8, ptr %hp, i64 16\n")
emith(" ret ptr %bp\n")
emith("heap:\n")
emith(" %h = call ptr @lp_heap(i64 %n)\n")
emith(" ret ptr %h\n")
emith("}\n")
emith("; the frame's mark for the arenas: the first reserves them (two halves, R3D_ARENA_MB each, 256 by\n")
emith("; default; R3D_ARENA=0 keeps everything on the heap), then each flips to the other half - which\n")
emith("; held the frame before last - and starts it again (R3D_ARENA_CHECK=1 fills it with 0xDD first)\n")
emith("define internal void @lp_ar_frame() {\n")
emith("entry:\n")
emith(" %def = load i8, ptr @lp_ardef\n")
emith(" %defq = icmp ne i8 %def, 0\n")
emith(" br i1 %defq, label %chk, label %out\n")
emith("chk:\n")
emith(" %on = load i8, ptr @lp_ar_on\n")
emith(" %onq = icmp ne i8 %on, 0\n")
emith(" br i1 %onq, label %flip, label %maybe\n")
emith("maybe:\n")
emith(" %tried = load i8, ptr @lp_ar_tried\n")
emith(" %tq = icmp ne i8 %tried, 0\n")
emith(" br i1 %tq, label %out, label %reserve\n")
emith("reserve:\n")
emith(" store i8 1, ptr @lp_ar_tried\n")
emith(" %want = call i64 @lp_fenv(ptr @.lpf_arena, i64 1)\n")
emith(" %wz = icmp eq i64 %want, 0\n")
emith(" br i1 %wz, label %out, label %size\n")
emith("size:\n")
emith(" %mb = call i64 @lp_fenv(ptr @.lpf_arenamb, i64 256)\n")
emith(" %half = shl i64 %mb, 20\n")
emith(" store i64 %half, ptr @lp_ar_half\n")
emith(" %both = shl i64 %half, 1\n")
emith(" %m = call ptr @mmap(ptr null, i64 %both, i32 3, i32 4098, i32 -1, i64 0)\n")
emith(" %mi = ptrtoint ptr %m to i64\n")
emith(" %bad = icmp eq i64 %mi, -1\n")
emith(" br i1 %bad, label %out, label %armed\n")
emith("armed:\n")
emith(" store i64 %mi, ptr @lp_ar_lo\n")
emith(" %hi = add i64 %mi, %both\n")
emith(" store i64 %hi, ptr @lp_ar_hi\n")
emith(" store i64 %mi, ptr @lp_ar_cur\n")
emith(" %e0 = add i64 %mi, %half\n")
emith(" store i64 %e0, ptr @lp_ar_end\n")
emith(" store i64 0, ptr @lp_ar_which\n")
emith(" %me = call ptr @pthread_self()\n")
emith(" store ptr %me, ptr @lp_ar_main\n")
emith(" %ck = call i64 @lp_fenv(ptr @.lpf_archeck, i64 0)\n")
emith(" %ck8 = trunc i64 %ck to i8\n")
emith(" store i8 %ck8, ptr @lp_ar_check\n")
emith(" store i8 1, ptr @lp_ar_on\n")
emith(" br label %out\n")
emith("flip:\n")
emith(" %w = load i64, ptr @lp_ar_which\n")
emith(" %lo = load i64, ptr @lp_ar_lo\n")
emith(" %hf = load i64, ptr @lp_ar_half\n")
emith(" %off = mul i64 %w, %hf\n")
emith(" %start = add i64 %lo, %off\n")
emith(" %cur = load i64, ptr @lp_ar_cur\n")
emith(" %used = sub i64 %cur, %start\n")
emith(" %w0 = icmp eq i64 %w, 0\n")
emith(" %hwq = select i1 %w0, ptr @lp_ar_hw0, ptr @lp_ar_hw1\n")
emith(" %hwv = load i64, ptr %hwq\n")
emith(" %more = icmp ugt i64 %used, %hwv\n")
emith(" %hwn = select i1 %more, i64 %used, i64 %hwv\n")
emith(" store i64 %hwn, ptr %hwq\n")
emith(" %nw = xor i64 %w, 1\n")
emith(" store i64 %nw, ptr @lp_ar_which\n")
emith(" %noff = mul i64 %nw, %hf\n")
emith(" %nstart = add i64 %lo, %noff\n")
emith(" store i64 %nstart, ptr @lp_ar_cur\n")
emith(" %nend = add i64 %nstart, %hf\n")
emith(" store i64 %nend, ptr @lp_ar_end\n")
emith(" %cq = load i8, ptr @lp_ar_check\n")
emith(" %cqq = icmp ne i8 %cq, 0\n")
emith(" br i1 %cqq, label %poison, label %out\n")
emith("poison:\n")
emith(" %n0 = icmp eq i64 %nw, 0\n")
emith(" %nhq = select i1 %n0, ptr @lp_ar_hw0, ptr @lp_ar_hw1\n")
emith(" %nhv = load i64, ptr %nhq\n")
emith(" %np = inttoptr i64 %nstart to ptr\n")
emith(" call ptr @memset(ptr %np, i32 221, i64 %nhv)\n")
emith(" br label %out\n")
emith("out:\n")
emith(" ret void\n")
emith("}\n")
}
function emit_fence_table() -> void {
emith("define internal void @lp_flock() {\n")
@ -836,6 +1064,9 @@ function emit_fence_report() -> void {
emith(" %ddc = sub i64 %dnow, %dset\n")
emith(" %ddc1 = select i1 %jq, i64 %ddc, i64 0\n")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_cdecl, i64 %ddc1)\n")
emith(" %ah0 = load i64, ptr @lp_ar_hw0\n")
emith(" %ah1 = load i64, ptr @lp_ar_hw1\n")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_car, i64 %ah0, i64 %ah1)\n")
emith(" %jd1 = select i1 %jq, i64 %jd, i64 0\n")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_cjolt, i64 %jd1, i64 %jpk)\n")
emith(" %vd1 = select i1 %jq, i64 %vd, i64 0\n")
@ -930,6 +1161,7 @@ function emit_fence_frame() -> void {
emith("; R3D_ALLOC_WARM_MAX frames after tracking (re)started, whichever is first\n")
emith("define void @lp_mem_frame() {\n")
emith("entry:\n")
emith(" call void @lp_ar_frame()\n")
emith(" call void @lp_finit()\n")
emith(" %f = load i64, ptr @lp_frame\n")
emith(" %f1 = add i64 %f, 1\n")
@ -1039,6 +1271,7 @@ function emit_fence_frame() -> void {
}
function emit_fence_win() -> void {
emith("@lp_site = global i32 0\n")
emith("@lp_want = global i8 0\n")
emith("@lp_fdecl = global i32 0\n")
emith("define ptr @lp_malloc(i64 %n) {\n")
emith("entry:\n")

View file

@ -31,10 +31,14 @@ function fr_phase_frame(ph: pointer) -> bool {
return (ph == "Input") or (ph == "FixedUpdate") or (ph == "Update") or (ph == "LateUpdate") or (ph == "Render") or (ph == "Overlay")
}
function fr_is_frame_field(name: pointer) -> bool {
# `tick` is a System's, by any name's record; any other field is a frame root only where its own
# property says `@frame` (Prop.field), so a step list of loads with a `run` field is not one
function fr_is_frame_field(name: pointer) -> bool { return (name == "tick") }
function fr_is_frame_prop_field(prop: pointer, name: pointer) -> bool {
if (name == "tick") { return true }
let key = `{prop}.{name}`
var i = 0
while i < len(g_frame_fields) { if (g_frame_fields[i] == name) { return true }; i += 1 }
while i < len(g_frame_fields) { if (g_frame_fields[i] == key) { return true }; i += 1 }
return false
}
@ -60,11 +64,11 @@ function fr_mark_root(name: pointer) -> void {
function fr_find_roots(n: Node) -> void {
if n == null { return }
if n.kind == S_ASSIGN and n.a != null and n.a.kind == E_MEMBER and n.a.s != null and fr_is_frame_field(n.a.s) and n.b != null and n.b.kind == E_FNREF { fr_mark_root(n.b.s) }
if n.kind == E_REC and n.kids != null {
if n.kind == E_NEW and n.s != null and n.a != null and n.a.kind == E_REC and n.a.kids != null {
var i = 0
while i < len(n.kids) {
let f = n.kids[i]
if f.s != null and fr_is_frame_field(f.s) and f.a != null and f.a.kind == E_FNREF { fr_mark_root(f.a.s) }
while i < len(n.a.kids) {
let f = n.a.kids[i]
if f.s != null and fr_is_frame_prop_field(n.s, f.s) and f.a != null and f.a.kind == E_FNREF { fr_mark_root(f.a.s) }
i += 1
}
}
@ -86,7 +90,7 @@ function fr_is_textish(n: Node) -> bool {
}
function fr_alloc_kind(n: Node, text_above: bool) -> pointer {
if n.kind == E_NEW { return `new:{n.s}` }
if n.kind == E_NEW and n.b == null { return `new:{n.s}` } # a dispatch's `new` fills the queue's kept record (n.b)
if n.kind == E_LIST { return "list" }
if n.kind == E_BIN and (n.s == "+") and not text_above and fr_is_textish(n) { return "text" }
if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and n.a.s != null {

View file

@ -17,7 +17,13 @@ function is_intrinsic(name: pointer) -> bool {
return false
}
# emit " <r> = <rest>\n" and return r
function emit_bind(rest: pointer) -> pointer { fence_bind(rest); let r = nreg(); emit(" "); emit(r); emit(" = "); emit(rest); emit("\n"); return r }
function emit_bind(rest: pointer) -> pointer {
let want = fence_bind(rest) # 25.3: a LOCAL site's allocation asks for the frame's scratch
let r = nreg()
emit(" "); emit(r); emit(" = "); emit(rest); emit("\n")
if want { emit(" store i8 0, ptr @lp_want\n") }
return r
}
function arg_code(e: Node, i: int) -> pointer { let v = emit_expr(e.kids[i]); return v.code }
# a buffer handed to the platform layer: a slice's elements, never its header (as an extern's are)

View file

@ -94,7 +94,7 @@ function emit_pak_prelude() -> void {
emith("declare i32 @open(ptr, i32, ...)\n")
emith("declare i32 @close(i32)\n")
emith("declare i64 @lseek(i32, i64, i32)\n")
emith("declare ptr @mmap(ptr, i64, i32, i32, i32, i64)\n")
if not g_mmap_declared { emith("declare ptr @mmap(ptr, i64, i32, i32, i32, i64)\n"); g_mmap_declared = true }
emith("declare ptr @fmemopen(ptr, i64, ptr)\n")
emith("declare i32 @chdir(ptr)\n")
emith("declare i32 @_NSGetExecutablePath(ptr, ptr)\n")

View file

@ -12,7 +12,7 @@ function parse_component() -> Node {
var is_frame = false # @frame — a fn stored here runs every frame (25.2)
if is_op("@") { pi += 1; let ann = eat_id(); if ann == "Computed" { is_computed = true } else if ann == "Sync" { is_sync = true } else if ann == "frame" { is_frame = true }; skipnl() }
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
if is_frame { push(g_frame_fields, f.s) }
if is_frame { push(g_frame_fields, `{n.s}.{f.s}`) }
if is_op("=") { pi += 1; f.a = expr() }
if is_sync { f.ival = 1 } # mark the field replicable (read by emit_net)
if is_computed { register_computed(n.s, f.s, f.ty, f.a) } # derived: no storage

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -231,6 +231,7 @@ entry {
ix_start() # the lookups by name, as tables (emit_core.ludic)
check_program() # L4: the types agree before anything is emitted
if g_es_report { escape_report() } # 25.3: which allocations never outlive their frame
else if g_arena { escape_analyse() } # 25.3: and they allocate from the frame's scratch
if g_migrate { mg_finish() }
# --check: every check a build makes - the emitter refuses things too (a bind to a function that is
# gone, an unknown name) - and nothing written