capacities (25.5a): @max(n) on a list field, and a full table is a failure
'@max(64) boxes: []Box' on a property's or a state's field is a promise: the grow path of a push to
that field (only the grow path, so nothing is paid until it doubles) checks it, and growing past n is
reported by the fence - 'PoolState.boxes grew past its @max(16) (it holds 16)' - counted under
count, said under warn, and under fail (a headless or dev build's default) the run ends with exit 87.
Mem.over("what") is the same for a package's own table: ludic.base's StrTable past its most
(sb_intern) and ludic.ui's memo past three quarters of MM_CAP no longer quietly copy per call. The
census counts overflows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
69d1db06a5
commit
6fb5118afd
8 changed files with 73094 additions and 71750 deletions
|
|
@ -45,7 +45,10 @@ export function sb_intern(sb: StrBuf, tb: StrTable) -> string {
|
|||
slot = (slot + 1) & mask
|
||||
}
|
||||
let s = text_of(sb.b, sb.n)
|
||||
if len(tb.ss) >= tb.most { return s }
|
||||
if len(tb.ss) >= tb.most {
|
||||
Mem.over("a StrTable past its most (sb_intern)") # 25.5a: a full table is a failure, not a copy per call
|
||||
return s
|
||||
}
|
||||
push(tb.ss, s)
|
||||
tb.hs[slot] = h
|
||||
tb.at[slot] = len(tb.ss)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,10 @@ function mm_find(m: UiMemo, tag: int, s: string) -> int {
|
|||
}
|
||||
# a new entry for (tag, s) with its results empty, or -1 when the table is full
|
||||
function mm_add(m: UiMemo, tag: int, s: string) -> int {
|
||||
if len(m.keys) * 4 >= MM_CAP * 3 { return -1 }
|
||||
if len(m.keys) * 4 >= MM_CAP * 3 {
|
||||
Mem.over("ludic.ui's memo, three quarters of MM_CAP") # 25.5a: past it every miss was made and lost
|
||||
return -1
|
||||
}
|
||||
var at = mm_hash(tag, s) & (MM_CAP - 1)
|
||||
while m.slots[at] != 0 { at = (at + 1) & (MM_CAP - 1) }
|
||||
push(m.keys, s)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ 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_cap_keys: []pointer = new []pointer # 25.5a: `Type.field` declared @max(n) ...
|
||||
var g_cap_vals: []int = new []int # ... and its n
|
||||
var g_push_cap: int = 0 # the list being pushed to is capped (emit_push -> emit_push_into)
|
||||
var g_push_capname: pointer = null
|
||||
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
|
||||
|
|
@ -160,13 +164,18 @@ function fence_manifest_read(txt: pointer) -> void {
|
|||
|
||||
# Mem.play() / Mem.rewarm() / Mem.settled() / Mem.frame() / Mem.kept() / Mem.bad_frames()
|
||||
function is_mem_fence_ns(meth: pointer) -> bool {
|
||||
return (meth == "play") or (meth == "rewarm") or (meth == "settled") or (meth == "frame") or (meth == "kept") or (meth == "bad_frames")
|
||||
return (meth == "play") or (meth == "rewarm") or (meth == "settled") or (meth == "over") or (meth == "frame") or (meth == "kept") or (meth == "bad_frames")
|
||||
}
|
||||
|
||||
function emit_mem_fence_ns(meth: pointer, e: Node) -> Val {
|
||||
if (meth == "kept") { return val(emit_bind("call i64 @lp_mem_kept()"), "long") }
|
||||
if (meth == "bad_frames") { return val(emit_bind("call i64 @lp_mem_bad_frames()"), "long") }
|
||||
if (meth == "rewarm") { emit(" call void @lp_mem_play()\n"); return val("0", "void") }
|
||||
if (meth == "over") { # 25.5a: Mem.over("what") - a package's table is full
|
||||
let nm = emit_expr(e.kids[0])
|
||||
emit(` call void @lp_cap_over(ptr {nm.code}, i32 0, i32 0)\n`)
|
||||
return val("0", "void")
|
||||
}
|
||||
emit(` call void @lp_mem_{meth}()\n`)
|
||||
return val("0", "void")
|
||||
}
|
||||
|
|
@ -223,3 +232,21 @@ function emit_fence_runtime() -> void {
|
|||
emith(`@lp_site_base = global [{sn} x i64] zeroinitializer\n`)
|
||||
emith(`@lp_site_ra = global [{itoa(n * 6)} x ptr] zeroinitializer\n`)
|
||||
}
|
||||
|
||||
# @max(n) on the list a push grows: its cap and its name, or 0
|
||||
function fence_cap_of(target: Node) -> int {
|
||||
g_push_capname = null
|
||||
if target == null or target.kind != E_MEMBER or target.a == null or len(g_cap_keys) == 0 { return 0 }
|
||||
let t = static_type(target.a)
|
||||
if t == null { return 0 }
|
||||
let key = `{t}.{target.s}`
|
||||
var i = 0
|
||||
while i < len(g_cap_keys) {
|
||||
if (g_cap_keys[i] == key) {
|
||||
g_push_capname = key
|
||||
return g_cap_vals[i]
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ function emit_fence_globals() -> void {
|
|||
emith("@lp_fkept = global i64 0\n")
|
||||
emith("@lp_fbad = global i64 0\n")
|
||||
emith("@lp_fjudged = global i64 0\n")
|
||||
emith("@lp_capn = global i64 0\n")
|
||||
emith("@lp_zset = global i64 0\n")
|
||||
emith("@lp_jset = global i64 0\n")
|
||||
emith("@lp_vset = global i64 0\n")
|
||||
|
|
@ -73,11 +74,14 @@ function emit_fence_globals() -> void {
|
|||
emith("@.lpf_hdr = private unnamed_addr constant [62 x i8] c\"alloc-fence: frame %lld kept %+lld B (%lld made, %lld freed)\\0A\\00\"\n")
|
||||
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_full = private unnamed_addr constant [71 x i8] c\"alloc-fence: %s is full - it stops keeping and makes a copy each time\\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_capover = private unnamed_addr constant [54 x i8] c\"alloc-fence: %s grew past its @max(%d) (it holds %d)\\0A\\00\"\n")
|
||||
emith("@.lpf_capfail = private unnamed_addr constant [78 x i8] c\"alloc-fence: failing the run (R3D_ALLOC_FENCE=fail): a capacity is a promise\\0A\\00\"\n")
|
||||
emith("@.lpf_ccap = private unnamed_addr constant [56 x i8] c\"overflows %lld (a list past its @max, or a table full)\\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")
|
||||
|
|
@ -1064,6 +1068,8 @@ 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(" %capn = load i64, ptr @lp_capn\n")
|
||||
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_ccap, i64 %capn)\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")
|
||||
|
|
@ -1135,6 +1141,39 @@ function emit_fence_frame() -> void {
|
|||
emith(" %v = call i64 %f()\n")
|
||||
emith(" ret i64 %v\n")
|
||||
emith("}\n")
|
||||
emith("; 25.5a: a list declared @max(n) grew past n, or a package's table filled (Mem.over): said under\n")
|
||||
emith("; warn, and under fail the run ends with exit 87 - silent when the fence is off\n")
|
||||
emith("define void @lp_cap_over(ptr %name, i32 %cap, i32 %len) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" call void @lp_finit()\n")
|
||||
emith(" %m = load i8, ptr @lp_fm\n")
|
||||
emith(" %off = icmp ule i8 %m, 1\n")
|
||||
emith(" br i1 %off, label %out, label %count\n")
|
||||
emith("count:\n")
|
||||
emith(" %c = load i64, ptr @lp_capn\n")
|
||||
emith(" %c1 = add i64 %c, 1\n")
|
||||
emith(" store i64 %c1, ptr @lp_capn\n")
|
||||
emith(" %say = icmp uge i8 %m, 3\n")
|
||||
emith(" br i1 %say, label %which, label %out\n")
|
||||
emith("which:\n")
|
||||
emith(" %isfull = icmp eq i32 %cap, 0\n")
|
||||
emith(" br i1 %isfull, label %full, label %over\n")
|
||||
emith("full:\n")
|
||||
emith(" call i32 (i32, ptr, ...) @dprintf(i32 2, ptr @.lpf_full, ptr %name)\n")
|
||||
emith(" br label %judge\n")
|
||||
emith("over:\n")
|
||||
emith(" call i32 (i32, ptr, ...) @dprintf(i32 2, ptr @.lpf_capover, ptr %name, i32 %cap, i32 %len)\n")
|
||||
emith(" br label %judge\n")
|
||||
emith("judge:\n")
|
||||
emith(" %fail = icmp eq i8 %m, 4\n")
|
||||
emith(" br i1 %fail, label %die, label %out\n")
|
||||
emith("die:\n")
|
||||
emith(" call i32 (i32, ptr, ...) @dprintf(i32 2, ptr @.lpf_capfail)\n")
|
||||
emith(" call void @exit(i32 87)\n")
|
||||
emith(" unreachable\n")
|
||||
emith("out:\n")
|
||||
emith(" ret void\n")
|
||||
emith("}\n")
|
||||
emith("define internal void @lp_fsettle_locked() {\n")
|
||||
emith("entry:\n")
|
||||
emith(" %n = load i32, ptr @lp_site_n\n")
|
||||
|
|
@ -1313,6 +1352,10 @@ function emit_fence_win() -> void {
|
|||
emith("entry:\n")
|
||||
emith(" ret i64 0\n")
|
||||
emith("}\n")
|
||||
emith("define void @lp_cap_over(ptr %name, i32 %cap, i32 %len) {\n")
|
||||
emith("entry:\n")
|
||||
emith(" ret void\n")
|
||||
emith("}\n")
|
||||
}
|
||||
function emit_fence_intern() -> void {
|
||||
emith("@lp_itab = global [65536 x ptr] zeroinitializer\n")
|
||||
|
|
|
|||
|
|
@ -135,7 +135,11 @@ function emit_len(e: Node) -> Val {
|
|||
|
||||
function emit_push(e: Node) -> Val {
|
||||
let s = emit_expr(e.kids[0])
|
||||
emit_push_into(s.code, llty(slice_elem(s.ty)), emit_expr(e.kids[1]))
|
||||
let v = emit_expr(e.kids[1])
|
||||
g_push_cap = fence_cap_of(e.kids[0]) # 25.5a: a list declared @max(n) says when it grows past n
|
||||
let capname = g_push_capname
|
||||
emit_push_into(s.code, llty(slice_elem(s.ty)), v)
|
||||
g_push_capname = capname
|
||||
return val("0", "void")
|
||||
}
|
||||
|
||||
|
|
@ -177,6 +181,17 @@ function emit_push_into(h: pointer, elt: pointer, v: Val) -> void {
|
|||
let grow = lbl("grow"); let put = lbl("put")
|
||||
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(put); emit("\n")
|
||||
emit(grow); emit(":\n")
|
||||
if g_push_cap > 0 { # 25.5a: growth past @max(n) is reported (R3D_ALLOC_FENCE=fail: exit 87)
|
||||
let over = emit_bind(`icmp sge i32 {l}, {itoa(g_push_cap)}`)
|
||||
let nm = emit_str_const(g_push_capname)
|
||||
let sayl = lbl("capover"); let okl = lbl("capok")
|
||||
emit(` br i1 {over}, label %{sayl}, label %{okl}\n`)
|
||||
emit(`{sayl}:\n`)
|
||||
emit(` call void @lp_cap_over(ptr {nm}, i32 {itoa(g_push_cap)}, i32 {l})\n`)
|
||||
emit(` br label %{okl}\n`)
|
||||
emit(`{okl}:\n`)
|
||||
g_push_cap = 0
|
||||
}
|
||||
let dbl = emit_bind(`mul i32 {c}, 2`)
|
||||
let isz = emit_bind(`icmp eq i32 {c}, 0`)
|
||||
let nc = emit_bind(`select i1 {isz}, i32 8, i32 {dbl}`)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,23 @@ function parse_component() -> Node {
|
|||
var is_computed = false
|
||||
var is_sync = false # @Sync — this field replicates (NETWORKING N2)
|
||||
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() }
|
||||
var cap = 0 # @max(n) — this list never holds more than n (25.5a)
|
||||
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 }
|
||||
else if ann == "max" {
|
||||
eat_op("(")
|
||||
let ce = expr()
|
||||
if ce.kind != E_INT or ce.ival <= 0 { perr("@max takes the most the list may hold: @max(64)") }
|
||||
cap = ce.ival
|
||||
eat_op(")")
|
||||
}
|
||||
skipnl()
|
||||
}
|
||||
let f = node(N_FIELD); f.s = eat_id(); eat_op(":"); f.ty = ptype()
|
||||
if is_frame { push(g_frame_fields, `{n.s}.{f.s}`) }
|
||||
if cap > 0 { push(g_cap_keys, `{n.s}.{f.s}`); push(g_cap_vals, cap) }
|
||||
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
|
||||
|
|
|
|||
72393
selfhost/ludicc.seed.ll
72393
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue