Merge lang/foundations 1e2a6ba into lang/ecs

This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 15:58:12 +03:00
commit a3b2da86b5
8 changed files with 79605 additions and 78050 deletions

View file

@ -32,10 +32,16 @@ function emit_fn(d: Node) -> void {
loc_push(p.s, slot, p.ty)
i += 1
}
let declared = fr_alloc_ok(d.s) # 25.1: what an @alloc_ok function makes, callees too, is declared
if declared { emit(" %decl.in = atomicrmw add ptr @lp_fdecl, i32 1 monotonic\n") }
let keeps = fr_has_aok(d.a) # a statement under @alloc_ok: a return inside it restores the depth
if keeps { emit(" %decl.keep = load i32, ptr @lp_fdecl\n") }
emit_block(d.a)
if not (rl == "void") and not block_ends(d.a) { perr(`function '{d.s}' can reach its end without returning a {ret_ty}`) }
if not g_term { emit(" br label %ret\n") }
emit("ret:\n")
if keeps { emit(" store i32 %decl.keep, ptr @lp_fdecl\n") }
if declared { emit(" %decl.out = atomicrmw sub ptr @lp_fdecl, i32 1 monotonic\n") }
if (rl == "void") { emit(" ret void\n") }
else { let r = emit_bind(`load {rl}, ptr %retval`); emit(" ret "); emit(rl); emit(" "); emit(r); emit("\n") }
code = saved

View file

@ -2,6 +2,10 @@
# 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_fdecl = global i32 0\n")
emith("@lp_dlive = global i64 0\n")
emith("@lp_mdlive = global i64 0\n")
emith("@lp_sdlive = global i64 0\n")
emith("@lp_ft = global i8 0\n")
emith("@lp_fm = global i8 0\n")
emith("@lp_fj = global i8 0\n")
@ -34,12 +38,15 @@ function emit_fence_globals() -> void {
emith("@lp_fbad = global i64 0\n")
emith("@lp_fjudged = 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")
emith("@lp_census = global ptr null\n")
emith("declare extern_weak i64 @malloc_size(ptr)\n")
emith("declare i32 @dprintf(i32, ptr, ...)\n")
emith("declare i32 @_Unwind_Backtrace(ptr, ptr)\n")
emith("declare i64 @_Unwind_GetIP(ptr)\n")
emith("declare i32 @dladdr(ptr, ptr)\n")
emith("declare ptr @dlsym(ptr, ptr)\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")
@ -59,7 +66,14 @@ function emit_fence_globals() -> void {
emith("@.lpf_auto = private unnamed_addr constant [15 x i8] c\"R3D_ALLOC_AUTO\\00\"\n")
emith("@.lpf_w = private unnamed_addr constant [2 x i8] c\"w\\00\"\n")
emith("@.lpf_chdr = private unnamed_addr constant [76 x i8] c\"alloc-census frames %lld bad %lld kept %lld made %lld freed %lld over %lld\\0A\\00\"\n")
emith("@.lpf_cnat = private unnamed_addr constant [91 x i8] c\"native %lld (the heap outside Ludic's blocks, since judging began: libraries and drivers)\\0A\\00\"\n")
emith("@.lpf_cnat = private unnamed_addr constant [116 x i8] c\"native %lld (the heap outside Ludic's blocks and the counted libraries, since judging began: drivers, Metal, libc)\\0A\\00\"\n")
emith("@.lpf_cdecl = private unnamed_addr constant [58 x i8] c\"declared %lld (kept under @alloc_ok since judging began)\\0A\\00\"\n")
emith("@.lpf_cjolt = private unnamed_addr constant [68 x i8] c\"jolt %lld peak %lld (since judging began, and its high-water mark)\\0A\\00\"\n")
emith("@.lpf_cvk = private unnamed_addr constant [83 x i8] c\"vulkan %lld peak %lld (host memory through VkAllocationCallbacks, R3D_ALLOC_VK=1)\\0A\\00\"\n")
emith("@.lpf_jb = private unnamed_addr constant [15 x i8] c\"jph_heap_bytes\\00\"\n")
emith("@.lpf_jp = private unnamed_addr constant [14 x i8] c\"jph_heap_peak\\00\"\n")
emith("@.lpf_vb = private unnamed_addr constant [13 x i8] c\"lvk_ac_bytes\\00\"\n")
emith("@.lpf_vp = private unnamed_addr constant [12 x i8] c\"lvk_ac_peak\\00\"\n")
emith("@.lpf_crow = private unnamed_addr constant [23 x i8] c\"site %lld %s %s:%d %s\\0A\\00\"\n")
emith("@.lpf_from = private unnamed_addr constant [15 x i8] c\" from %s\\0A\\00\"\n")
emith("@.lpf_cfrom = private unnamed_addr constant [11 x i8] c\" from %s\\0A\\00\"\n")
@ -328,10 +342,16 @@ function emit_fence_table() -> void {
emith("dup:\n")
emith(" %dv = getelementptr inbounds i64, ptr %e, i64 1\n")
emith(" %dold = load i64, ptr %dv\n")
emith(" %dn = and i64 %dold, 1099511627775\n")
emith(" %dn = and i64 %dold, 549755813887\n")
emith(" %ds = lshr i64 %dold, 40\n")
emith(" %dneg = sub i64 0, %dn\n")
emith(" call void @lp_fsite_add(i64 %ds, i64 %dneg)\n")
emith(" %dwd = and i64 %dold, 549755813888\n")
emith(" %dwas = icmp ne i64 %dwd, 0\n")
emith(" %ddl = load i64, ptr @lp_dlive\n")
emith(" %ddsub = select i1 %dwas, i64 %dn, i64 0\n")
emith(" %ddl1 = sub i64 %ddl, %ddsub\n")
emith(" store i64 %ddl1, ptr @lp_dlive\n")
emith(" %dtn = load i64, ptr @lp_tabn\n")
emith(" %dtn1 = sub i64 %dtn, 1\n")
emith(" store i64 %dtn1, ptr @lp_tabn\n")
@ -343,8 +363,16 @@ function emit_fence_table() -> void {
emith(" store i64 %k, ptr %e\n")
emith(" %ev = getelementptr inbounds i64, ptr %e, i64 1\n")
emith(" %sh = shl i64 %s64, 40\n")
emith(" %v = or i64 %sh, %n\n")
emith(" %v0 = or i64 %sh, %n\n")
emith(" %dc = load i32, ptr @lp_fdecl\n")
emith(" %isdecl = icmp sgt i32 %dc, 0\n")
emith(" %dbit = select i1 %isdecl, i64 549755813888, i64 0\n")
emith(" %v = or i64 %v0, %dbit\n")
emith(" store i64 %v, ptr %ev\n")
emith(" %dl = load i64, ptr @lp_dlive\n")
emith(" %dadd = select i1 %isdecl, i64 %n, i64 0\n")
emith(" %dl1 = add i64 %dl, %dadd\n")
emith(" store i64 %dl1, ptr @lp_dlive\n")
emith(" %tn = load i64, ptr @lp_tabn\n")
emith(" %tn1 = add i64 %tn, 1\n")
emith(" store i64 %tn1, ptr @lp_tabn\n")
@ -396,10 +424,16 @@ function emit_fence_table() -> void {
emith("found:\n")
emith(" %ev = getelementptr inbounds i64, ptr %e, i64 1\n")
emith(" %v = load i64, ptr %ev\n")
emith(" %n = and i64 %v, 1099511627775\n")
emith(" %n = and i64 %v, 549755813887\n")
emith(" %s = lshr i64 %v, 40\n")
emith(" %neg = sub i64 0, %n\n")
emith(" call void @lp_fsite_add(i64 %s, i64 %neg)\n")
emith(" %wd = and i64 %v, 549755813888\n")
emith(" %wasdecl = icmp ne i64 %wd, 0\n")
emith(" %dl = load i64, ptr @lp_dlive\n")
emith(" %dsub = select i1 %wasdecl, i64 %n, i64 0\n")
emith(" %dl1 = sub i64 %dl, %dsub\n")
emith(" store i64 %dl1, ptr @lp_dlive\n")
emith(" %fr = load i64, ptr @lp_freed\n")
emith(" %fr1 = add i64 %fr, 1\n")
emith(" store i64 %fr1, ptr @lp_freed\n")
@ -526,6 +560,8 @@ function emit_fence_control() -> void {
emith("entry:\n")
emith(" %lv = load i64, ptr @lp_live\n")
emith(" store i64 %lv, ptr @lp_mlive\n")
emith(" %dlm = load i64, ptr @lp_dlive\n")
emith(" store i64 %dlm, ptr @lp_mdlive\n")
emith(" %pr = load i64, ptr @lp_pre\n")
emith(" store i64 %pr, ptr @lp_mpre\n")
emith(" %md = load i64, ptr @lp_made\n")
@ -623,7 +659,11 @@ function emit_fence_control() -> void {
emith(" %sl = load i64, ptr @lp_slive\n")
emith(" %pr = load i64, ptr @lp_pre\n")
emith(" %sp = load i64, ptr @lp_spre\n")
emith(" %a = sub i64 %lv, %sl\n")
emith(" %a0 = sub i64 %lv, %sl\n")
emith(" %dlk = load i64, ptr @lp_dlive\n")
emith(" %sdl = load i64, ptr @lp_sdlive\n")
emith(" %ddk = sub i64 %dlk, %sdl\n")
emith(" %a = sub i64 %a0, %ddk\n")
emith(" %b = sub i64 %pr, %sp\n")
emith(" %k = sub i64 %a, %b\n")
emith(" call void @lp_funlock()\n")
@ -759,6 +799,10 @@ function emit_fence_report() -> void {
emith(" br i1 %none, label %out, label %open\n")
emith("open:\n")
emith(" %zn = call i64 @lp_fzone()\n")
emith(" %jn = call i64 @lp_fnat(ptr @.lpf_jb)\n")
emith(" %jpk = call i64 @lp_fnat(ptr @.lpf_jp)\n")
emith(" %vn = call i64 @lp_fnat(ptr @.lpf_vb)\n")
emith(" %vpk = call i64 @lp_fnat(ptr @.lpf_vp)\n")
emith(" %fp = call ptr @fopen(ptr %path, ptr @.lpf_w)\n")
emith(" %bad = icmp eq ptr %fp, null\n")
emith(" br i1 %bad, label %out, label %write\n")
@ -774,9 +818,28 @@ function emit_fence_report() -> void {
emith(" %jq = icmp ne i64 %j, 0\n")
emith(" %zs = load i64, ptr @lp_zset\n")
emith(" %zd = sub i64 %zn, %zs\n")
emith(" %nat = sub i64 %zd, %k\n")
emith(" %nat1 = select i1 %jq, i64 %nat, i64 0\n")
emith(" %js = load i64, ptr @lp_jset\n")
emith(" %jd = sub i64 %jn, %js\n")
emith(" %vs = load i64, ptr @lp_vset\n")
emith(" %vd = sub i64 %vn, %vs\n")
emith(" %dnw = load i64, ptr @lp_dlive\n")
emith(" %dst = load i64, ptr @lp_sdlive\n")
emith(" %dgr = sub i64 %dnw, %dst\n")
emith(" %nat0 = sub i64 %zd, %k\n")
emith(" %nat = sub i64 %nat0, %dgr\n")
emith(" %nat2 = sub i64 %nat, %jd\n")
emith(" %nat3 = sub i64 %nat2, %vd\n")
emith(" %nat1 = select i1 %jq, i64 %nat3, i64 0\n")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_cnat, i64 %nat1)\n")
emith(" %dnow = load i64, ptr @lp_dlive\n")
emith(" %dset = load i64, ptr @lp_sdlive\n")
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(" %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")
emith(" call i32 (ptr, ptr, ...) @fprintf(ptr %fp, ptr @.lpf_cvk, i64 %vd1, i64 %vpk)\n")
emith(" %sn = load i32, ptr @lp_site_n\n")
emith(" call void @lp_franked(ptr %fp, ptr @lp_site_base, i1 1, i32 %sn, i32 40)\n")
emith(" call i32 @fclose(ptr %fp)\n")
@ -827,6 +890,20 @@ function emit_fence_frame() -> void {
emith(" %n = load i64, ptr %p\n")
emith(" ret i64 %n\n")
emith("}\n")
emith("; a counted native library's number, found by name (ludic.physics' jph_heap_*, the Vulkan runtime's\n")
emith("; lvk_ac_*): 0 when the program does not carry it, and nothing is declared that the package declares\n")
emith("define internal i64 @lp_fnat(ptr %name) {\n")
emith("entry:\n")
emith(" %rtld_default = inttoptr i64 -2 to ptr\n")
emith(" %f = call ptr @dlsym(ptr %rtld_default, ptr %name)\n")
emith(" %none = icmp eq ptr %f, null\n")
emith(" br i1 %none, label %zero, label %call\n")
emith("zero:\n")
emith(" ret i64 0\n")
emith("call:\n")
emith(" %v = call i64 %f()\n")
emith(" ret i64 %v\n")
emith("}\n")
emith("define internal void @lp_fsettle_locked() {\n")
emith("entry:\n")
emith(" %n = load i32, ptr @lp_site_n\n")
@ -835,8 +912,14 @@ function emit_fence_frame() -> void {
emith(" call ptr @memcpy(ptr @lp_site_base, ptr @lp_site_live, i64 %nb)\n")
emith(" %lv = load i64, ptr @lp_live\n")
emith(" store i64 %lv, ptr @lp_slive\n")
emith(" %dls = load i64, ptr @lp_dlive\n")
emith(" store i64 %dls, ptr @lp_sdlive\n")
emith(" %pr = load i64, ptr @lp_pre\n")
emith(" store i64 %pr, ptr @lp_spre\n")
emith(" %jb = call i64 @lp_fnat(ptr @.lpf_jb)\n")
emith(" store i64 %jb, ptr @lp_jset\n")
emith(" %vb = call i64 @lp_fnat(ptr @.lpf_vb)\n")
emith(" store i64 %vb, ptr @lp_vset\n")
emith(" %z = call i64 @lp_fzone()\n")
emith(" store i64 %z, ptr @lp_zset\n")
emith(" store i8 1, ptr @lp_fj\n")
@ -888,7 +971,11 @@ function emit_fence_frame() -> void {
emith(" %ml = load i64, ptr @lp_mlive\n")
emith(" %pr = load i64, ptr @lp_pre\n")
emith(" %mp = load i64, ptr @lp_mpre\n")
emith(" %a = sub i64 %lv, %ml\n")
emith(" %a0 = sub i64 %lv, %ml\n")
emith(" %dln = load i64, ptr @lp_dlive\n")
emith(" %dlmk = load i64, ptr @lp_mdlive\n")
emith(" %dd = sub i64 %dln, %dlmk\n")
emith(" %a = sub i64 %a0, %dd\n")
emith(" %b = sub i64 %pr, %mp\n")
emith(" %kept = sub i64 %a, %b\n")
emith(" %bad = icmp sgt i64 %kept, 0\n")
@ -952,6 +1039,7 @@ function emit_fence_frame() -> void {
}
function emit_fence_win() -> void {
emith("@lp_site = global i32 0\n")
emith("@lp_fdecl = global i32 0\n")
emith("define ptr @lp_malloc(i64 %n) {\n")
emith("entry:\n")
emith(" %p = tail call ptr @malloc(i64 %n)\n")

View file

@ -7,6 +7,26 @@
var g_alloc_ok_names: []pointer = new []pointer # functions and handlers under @alloc_ok("why")
var g_frame_fields: []pointer = new []pointer # record fields declared `@frame`
var g_aok_nodes: []Node = new []Node # statements under @alloc_ok("why")
var g_aok_pending: bool = false # @alloc_ok read, its function or handler not yet
function fr_aok_stmt(n: Node) -> bool {
var i = 0
while i < len(g_aok_nodes) { if g_aok_nodes[i] == n { return true }; i += 1 }
return false
}
# does this body hold a statement under @alloc_ok? (its function then keeps the scope's depth)
function fr_has_aok(n: Node) -> bool {
if n == null or len(g_aok_nodes) == 0 { return false }
if fr_aok_stmt(n) { return true }
if fr_has_aok(n.a) or fr_has_aok(n.b) or fr_has_aok(n.c) { return true }
if n.kids != null {
var i = 0
while i < len(n.kids) { if fr_has_aok(n.kids[i]) { return true }; i += 1 }
}
return false
}
var g_fr_root: []bool = new []bool
var g_fr_from: []int = new []int # the node a reached one was first reached from
@ -76,6 +96,7 @@ function fr_alloc_kind(n: Node, text_above: bool) -> pointer {
# the allocating constructs in one declaration, each a falloc line with its chain
function fr_allocs(f: pointer, n: Node, k: int, text_above: bool) -> int {
if n == null { return 0 }
if fr_aok_stmt(n) { return 0 }
var found = 0
let kind = fr_alloc_kind(n, text_above)
if kind != null {

View file

@ -356,6 +356,24 @@ function emit_emit(st: Node) -> Val {
}
function emit_stmt(st: Node) -> void {
if fr_aok_stmt(st) { # 25.1: @alloc_ok("why") on this statement: its allocations are declared
emit(" %decl.s")
emit(itoa(ll_t))
emit(" = atomicrmw add ptr @lp_fdecl, i32 1 monotonic\n")
ll_t += 1
emit_stmt_body(st)
if not g_term {
emit(" %decl.e")
emit(itoa(ll_t))
emit(" = atomicrmw sub ptr @lp_fdecl, i32 1 monotonic\n")
ll_t += 1
}
return
}
emit_stmt_body(st)
}
function emit_stmt_body(st: Node) -> void {
g_err_file = st.file; g_err_line = st.line # so a lowering error names its statement
emit_cov_hit(st.line) # --coverage: bump this line's hit counter (no-op otherwise)
if st.kind == S_LET {

View file

@ -470,9 +470,20 @@ function block() -> Node {
# unless the specific rule already set one. The line drives --coverage and the
# panic/expect file:line messages.
function stmt() -> Node {
var aok = false
if is_op("@") and (toks[pi + 1].text == "alloc_ok") { # 25.2: @alloc_ok("why") before one statement
pi += 2
eat_op("(")
let why = expr()
if why.kind != E_STR or len(why.s) == 0 { perr("@alloc_ok needs its reason: @alloc_ok(\"a memo miss, bounded by MM_CAP\")") }
eat_op(")")
skipnl()
aok = true
}
let ln = toks[pi].line
let n = stmt_body()
if n.line == 0 { n.line = ln }
if aok { push(g_aok_nodes, n) }
return n
}
@ -925,7 +936,6 @@ function parse_one_decl() -> void {
var esys_phase: pointer = null
var ns_name: pointer = null # @Namespace(Name) — a package-provided Foo.* namespace (#62)
var is_det = false # @deterministic — no floating point inside (emit_float.ludic)
var is_aok = false # @alloc_ok("why") — not counted by 25.2's frame allocations
while is_op("@") {
pi += 1; let a = eat_id() # collect a leading @annotation
if a == "export" { is_export = true }
@ -959,7 +969,7 @@ function parse_one_decl() -> void {
let why = expr()
if why.kind != E_STR or len(why.s) == 0 { perr("@alloc_ok needs its reason: @alloc_ok(\"a memo miss, bounded by MM_CAP\")") }
eat_op(")")
is_aok = true
g_aok_pending = true # a global: `export` parses the declaration one call down
}
else if a == "ClearColor" { # @ClearColor(colour) — Render auto-clear + auto-present (#86)
eat_op("(")
@ -1032,7 +1042,7 @@ function parse_one_decl() -> void {
if is_id("handler") {
let h = parse_system()
if is_det { push(g_det_names, h.s) }
if is_aok { push(g_alloc_ok_names, h.s) }
if g_aok_pending { push(g_alloc_ok_names, h.s); g_aok_pending = false }
if (role != null) { if (role == "server") { h.ival = 1 } else { h.ival = 2 } } # N5: @Server=1 / @Predicted=2
if (on_event != null) { register_onlisten(on_event, h.a); return } # @On(Event) listener
if (onspawn_model != null) {
@ -1103,7 +1113,7 @@ function parse_one_decl() -> void {
let f = parse_fn()
if is_unsafe { f.uns = 1 }
if is_det { push(g_det_names, f.s) }
if is_aok { push(g_alloc_ok_names, f.s) }
if g_aok_pending { push(g_alloc_ok_names, f.s); g_aok_pending = false }
if is_export { f.ival = 1 }
if (sys_phase != null) { push(g_mod_sys_fn, f.s); push(g_mod_sys_phase, sys_phase) } # #64: register at load
if (esys_comp != null) { push(g_esys_comp, esys_comp); push(g_esys_fn, f.s); push(g_esys_phase, esys_phase) } # #62: package engine system

View file

@ -9,8 +9,10 @@ function parse_component() -> Node {
while true { skipnl(); if is_op("}") { break }
var is_computed = false
var is_sync = false # @Sync — this field replicates (NETWORKING N2)
if is_op("@") { pi += 1; let ann = eat_id(); if ann == "Computed" { is_computed = true } else if ann == "Sync" { is_sync = true }; skipnl() }
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_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