feat(compiler): the built-in ECS's stores grow - 100 000 entities, where 1024 was the wall

Every per-entity store (@S_ components, @H_ flags, alive, kind, freelist, owners) is a heap block
L_grow doubles from 1024 as L_alloc hands out a slot past it, the new slots zeroed; each site loads
the store's base where it indexes it (ecs_base, its registers %ecsb* so a raw function's t0 labels
cannot collide). Prop.has bounds against @L_cap, Pool.capacity answers it, a mod's registered
stores grow with the rest, every main grows the stores once before anything reads them. A snapshot
records its slot count first and a load grows to it before reading back. The overflow stop of
1c7ce84 is gone with the wall. ludic-dev test 305 passed, selfhost-test 33 passed; 1000 / 5000 /
100000 entities spawn and count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 22:53:24 +03:00
parent 0c6235e55b
commit 8384ad3215
13 changed files with 27441 additions and 26036 deletions

View file

@ -1,7 +0,0 @@
bump: patch
type: fix
**The 1,025th entity stops the program instead of corrupting it.** The built-in ECS keeps every
component in a fixed array of 1024 slots, and `L_alloc` never checked: one entity more was written
past the end of every component array at once. A spawn past the store now stops with `more than
1024 entities: the built-in ECS holds 1024; keep many things in a ludic.base Table`. A game that
keeps thousands of something keeps them as rows of `ludic.base`'s `Table<T>`, which grows.

View file

@ -0,0 +1,9 @@
bump: minor
type: feature
**The built-in ECS grows.** Every component was a fixed array of 1024 slots, so a game past 1024
entities could not have them (and until the last release silently corrupted memory trying). The
per-entity stores are heap blocks now, doubled by `L_grow` as entities outgrow them, the new slots
zero: 100 000 entities spawn and query. `Prop.has` bounds against the live capacity and
`Pool.capacity` answers it. A snapshot (`save`/`load`, `world_save`/`world_load`) records its slot
count first and a load grows to it before reading the stores back, so a snapshot's size follows the
world's instead of a fixed 1024. A mod's registered components grow with the rest.

View file

@ -136,9 +136,11 @@ function emit_component_access(prop: pointer, meth: pointer, e: Node) -> Val {
let lt = emit_bind(`icmp slt i32 {i0}, {ec}`)
emit(" br i1 "); emit(lt); emit(", label %"); emit(body); emit(", label %"); emit(endl); emit("\n")
emit(body); emit(":\n")
let ap = emit_bind(`getelementptr inbounds [{me} x i32], ptr @L_alive, i32 0, i32 {i0}`)
let eb1 = ecs_base("L_alive")
let ap = emit_bind(`getelementptr inbounds i32, ptr {eb1}, i32 {i0}`)
let al = emit_bind(`load i32, ptr {ap}`)
let hp = emit_bind(`getelementptr inbounds [{me} x i8], ptr @H_{prop}, i32 0, i32 {i0}`)
let eb2 = ecs_base(`H_{prop}`)
let hp = emit_bind(`getelementptr inbounds i8, ptr {eb2}, i32 {i0}`)
let hv = emit_bind(`load i8, ptr {hp}`)
let hz = emit_bind(`zext i8 {hv} to i32`)
let both = emit_bind(`and i32 {al}, {hz}`)
@ -154,15 +156,19 @@ function emit_component_access(prop: pointer, meth: pointer, e: Node) -> Val {
if len(e.kids) != 1 { perr(`{prop}.{meth} takes one argument: the entity`) }
let ev = emit_expr(e.kids[0])
if (meth == "of") {
let slot = emit_bind(`getelementptr inbounds [{me} x %Cmp_{prop}], ptr @S_{prop}, i32 0, i32 {ev.code}`)
let eb3 = ecs_base(`S_{prop}`)
let slot = emit_bind(`getelementptr inbounds %Cmp_{prop}, ptr {eb3}, i32 {ev.code}`)
return val(slot, prop)
}
let in_range = emit_bind(`icmp ult i32 {ev.code}, {me}`)
let cap = emit_bind("load i32, ptr @L_cap")
let in_range = emit_bind(`icmp ult i32 {ev.code}, {cap}`)
let idx = emit_bind(`select i1 {in_range}, i32 {ev.code}, i32 0`)
let ap = emit_bind(`getelementptr inbounds [{me} x i32], ptr @L_alive, i32 0, i32 {idx}`)
let eb4 = ecs_base("L_alive")
let ap = emit_bind(`getelementptr inbounds i32, ptr {eb4}, i32 {idx}`)
let alive = emit_bind(`load i32, ptr {ap}`)
let is_alive = emit_bind(`icmp ne i32 {alive}, 0`)
let hp = emit_bind(`getelementptr inbounds [{me} x i8], ptr @H_{prop}, i32 0, i32 {idx}`)
let eb5 = ecs_base(`H_{prop}`)
let hp = emit_bind(`getelementptr inbounds i8, ptr {eb5}, i32 {idx}`)
let hv = emit_bind(`load i8, ptr {hp}`)
let carries = emit_bind(`icmp ne i8 {hv}, 0`)
let ok1 = emit_bind(`and i1 {in_range}, {is_alive}`)
@ -372,7 +378,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
return emit_alias_or_fail("App", meth, e)
}
if (ns == "Pool") {
if (meth == "capacity") { return val(itoa(MAX_ENT), "int") } # max entities
if (meth == "capacity") { return val(emit_bind("load i32, ptr @L_cap"), "int") } # the stores' size now: they grow
if (meth == "reserved") { return val(emit_bind("load i32, ptr @L_entc"), "int") } # slots ever allocated (high-water)
if (meth == "free") { return val(emit_bind("load i32, ptr @L_freen"), "int") } # recycled slots ready for reuse
if (meth == "live") { # currently alive = reserved - free
@ -407,7 +413,8 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
let at = emit_expr(e.kids[1])
let ent = emit_bind(`call i32 @L_spawn_prefab(ptr {nm.code})`)
let me = itoa(MAX_ENT)
let slot = emit_bind(`getelementptr inbounds [{me} x %Cmp_Position], ptr @S_Position, i32 0, i32 {ent}`)
let eb6 = ecs_base(`S_Position`)
let slot = emit_bind(`getelementptr inbounds %Cmp_Position, ptr {eb6}, i32 {ent}`)
let xa = emit_bind(`getelementptr inbounds %Cmp_Position, ptr {slot}, i32 0, i32 {itoa(field_index(pos, "x"))}`)
let ya = emit_bind(`getelementptr inbounds %Cmp_Position, ptr {slot}, i32 0, i32 {itoa(field_index(pos, "y"))}`)
let ax = vec_x(at.code)

View file

@ -61,6 +61,7 @@ function emit_main(d: Node) -> void {
# its framebuffer allocated. Headless it only allocates — no window, no output —
# so a non-rendering entry game is unchanged.
emit(" call void @L_init_runtime()\n")
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
emit(" call void @L_init_globals()\n")
emit_block(d.a)
@ -149,6 +150,7 @@ function emit_test_runner() -> void {
emit(" ret i32 0\n")
emit(`{lrun0}:\n`)
emit(" call void @L_init_runtime()\n")
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
if has_ecs() { emit(" call void @rt_init()\n") }
emit(" call void @L_init_globals()\n")
i = 0

View file

@ -56,9 +56,12 @@ function emit_ecs_storage() -> void {
if len(g_scenes) > 0 { emith("@L_scene = internal global i32 0\n") } # active base scene id
emith("@L_entc = internal global i32 0\n")
let me = itoa(MAX_ENT)
emith(`@L_alive = internal global [{me} x i32] zeroinitializer\n`)
emith(`@L_kind = internal global [{me} x i32] zeroinitializer\n`)
emith(`@L_freelist = internal global [{me} x i32] zeroinitializer\n`)
# the per-entity stores are heap blocks L_grow makes and doubles: @L_cap slots each, from MAX_ENT
emith("@L_cap = internal global i32 0\n")
emith("@L_snapcap = internal global i32 0\n") # a snapshot's slot count, read before its stores
emith("@L_alive = internal global ptr null\n")
emith("@L_kind = internal global ptr null\n")
emith("@L_freelist = internal global ptr null\n")
emith("@L_freen = internal global i32 0\n")
# NETWORKING role registers (N3/N5): a runtime sets these; offline they hold the
# single-player default — @L_role=1 (this peer is the authority), local id 0.
@ -68,7 +71,7 @@ function emit_ecs_storage() -> void {
emith("@L_role = internal global i32 1\n") # 1 = server/authority (offline default)
emith("@L_localid = internal global i32 0\n") # this peer's id
}
if net_has_owned() { emith(`@L_owner_arr = internal global [{me} x i32] zeroinitializer\n`) }
if net_has_owned() { emith("@L_owner_arr = internal global ptr null\n") }
var i = 0
while i < len(prog) {
let c = prog[i]
@ -76,8 +79,8 @@ function emit_ecs_storage() -> void {
# header). Every property in an ECS program is a component today; a property
# used only via `new` would not need these, but no such program mixes the two.
if c.kind == N_COMP {
emith(`@S_{c.s} = internal global [{me} x %Cmp_{c.s}] zeroinitializer\n`)
emith(`@H_{c.s} = internal global [{me} x i8] zeroinitializer\n`)
emith(`@S_{c.s} = internal global ptr null\n`)
emith(`@H_{c.s} = internal global ptr null\n`)
}
# one enabled-flag global per model and per handler (default enabled)
if c.kind == N_ARCH { emith(`@ME_{c.s} = internal global i32 1\n`) }
@ -92,21 +95,25 @@ function emit_ecs_storage() -> void {
# L_reset(e): clear every has-flag and the archetype kind for entity e
function emit_ecs_allocator() -> void {
let me = itoa(MAX_ENT)
emit_ecs_grow()
emit("define void @L_reset(i32 %e) {\nentry:\n")
var i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let hn = `%h{itoa(i)}`
emit(" "); emit(hn); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(prog[i].s); emit(", i32 0, i32 %e\n")
let eb1 = ecs_base(`H_{prog[i].s}`)
emit(" "); emit(hn); emit(" = getelementptr inbounds i8, ptr "); emit(eb1); emit(", i32 %e\n")
emit(" store i8 0, ptr "); emit(hn); emit("\n")
}
i += 1
}
emit(" %k = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
let eb2 = ecs_base("L_kind")
emit(" %k = getelementptr inbounds i32, ptr "); emit(eb2); emit(", i32 %e\n")
emit(" store i32 0, ptr %k\n")
# N3: reset an @Owned entity's network owner to -1 (unowned) on alloc/free
if net_has_owned() {
emit(" %ow = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n")
let eb3 = ecs_base("L_owner_arr")
emit(" %ow = getelementptr inbounds i32, ptr "); emit(eb3); emit(", i32 %e\n")
emit(" store i32 -1, ptr %ow\n")
}
emit(" ret void\n}\n\n")
@ -118,45 +125,100 @@ function emit_ecs_allocator() -> void {
emit("reuse:\n")
emit(" %fn1 = sub i32 %fn, 1\n")
emit(" store i32 %fn1, ptr @L_freen\n")
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn1\n")
let eb4 = ecs_base("L_freelist")
emit(" %fp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %fn1\n")
emit(" %re = load i32, ptr %fp\n")
emit(" br label %done\n")
emit("fresh:\n")
emit(" %ec = load i32, ptr @L_entc\n")
emit(` %full = icmp sge i32 %ec, {me}\n`)
emit(" br i1 %full, label %over, label %room\n")
emit_ecs_full(me)
emit("room:\n")
emit(" %ec1 = add i32 %ec, 1\n")
emit(" call void @L_grow(i32 %ec1)\n") # a slot past the stores doubles them first
emit(" store i32 %ec1, ptr @L_entc\n")
emit(" br label %done\n")
emit("done:\n")
emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %room ]\n")
emit(" %e = phi i32 [ %re, %reuse ], [ %ec, %fresh ]\n")
emit(" call void @L_reset(i32 %e)\n")
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
let eb5 = ecs_base("L_alive")
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n")
emit(" store i32 1, ptr %ap\n")
emit(" ret i32 %e\n}\n\n")
emit("define void @L_free_entity(i32 %e) {\nentry:\n")
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
let eb6 = ecs_base("L_alive")
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb6); emit(", i32 %e\n")
emit(" store i32 0, ptr %ap\n")
emit(" call void @L_reset(i32 %e)\n")
emit(" %fn = load i32, ptr @L_freen\n")
emit(" %fp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_freelist, i32 0, i32 %fn\n")
let eb7 = ecs_base("L_freelist")
emit(" %fp = getelementptr inbounds i32, ptr "); emit(eb7); emit(", i32 %fn\n")
emit(" store i32 %e, ptr %fp\n")
emit(" %fn1 = add i32 %fn, 1\n")
emit(" store i32 %fn1, ptr @L_freen\n")
emit(" ret void\n}\n\n")
}
# the store is fixed at MAX_ENT: one more entity used to be written past the end of every component
# array, silently. It stops the program instead, and says where many things belong.
function emit_ecs_full(me: pointer) -> void {
g_uses_panic = true
emit("over:\n")
let prefix = emit_str_const(`{g_src_name}: panic: `)
let m = emit_str_const(`more than {me} entities: the built-in ECS holds {me}; keep many things in a ludic.base Table`)
let se = emit_bind(stdstream_rhs(2))
emit(` call i32 (ptr, ptr, ...) @fprintf(ptr {se}, ptr @.fmt_panic, ptr {prefix}, ptr {m})\n`)
emit(" call void @exit(i32 1)\n unreachable\n")
# the base of a per-entity store, loaded where it is indexed (a store moves when L_grow doubles it)
function ecs_base(store: pointer) -> pointer {
let r = `%ecsb{itoa(ll_t)}` # its own prefix: raw functions name blocks t0, t1, ...
ll_t += 1
emit(` {r} = load ptr, ptr @{store}\n`)
return r
}
# L_grow(need): every per-entity store holds at least `need` slots - doubled from MAX_ENT as the
# entities outgrow them, the new slots zero, the mods' registered stores with them. Nothing is
# ever moved back down: an entity's slot is its handle for its whole life.
function emit_ecs_grow() -> void {
emit("define void @L_grow(i32 %need) {\nentry:\n %cap = load i32, ptr @L_cap\n")
emit(" %ok = icmp sle i32 %need, %cap\n br i1 %ok, label %done, label %grow\ngrow:\n")
emit(` %c0 = icmp eq i32 %cap, 0\n %start = select i1 %c0, i32 {itoa(MAX_ENT)}, i32 %cap\n br label %dbl\n`)
emit("dbl:\n %nc = phi i32 [ %start, %grow ], [ %nc2, %dbl2 ]\n %enough = icmp sge i32 %nc, %need\n")
emit(" br i1 %enough, label %apply, label %dbl2\ndbl2:\n %nc2 = mul i32 %nc, 2\n br label %dbl\n")
emit("apply:\n %cap64 = sext i32 %cap to i64\n %nc64 = sext i32 %nc to i64\n")
emit_grow_store("L_alive", "4", 0)
emit_grow_store("L_kind", "4", 1)
emit_grow_store("L_freelist", "4", 2)
if net_has_owned() { emit_grow_store("L_owner_arr", "4", 3) }
var k = 4
var i = 0
while i < len(prog) {
if prog[i].kind == N_COMP {
let sz = `%gs{itoa(k)}`
emit(` {sz} = ptrtoint ptr getelementptr (%Cmp_{prog[i].s}, ptr null, i32 1) to i64\n`)
emit_grow_store(`S_{prog[i].s}`, sz, k)
emit_grow_store(`H_{prog[i].s}`, "1", k + 1)
k += 2
}
i += 1
}
emit_grow_dyn()
emit("fin:\n store i32 %nc, ptr @L_cap\n br label %done\ndone:\n ret void\n}\n\n")
}
# one store to nc slots of `esz` bytes: reallocated (8 bytes spare, so a field-less tag is never
# a zero-byte block), its new tail zeroed
function emit_grow_store(store: pointer, esz: pointer, k: int) -> void {
let n = itoa(k)
emit(` %gp{n} = load ptr, ptr @{store}\n %gb{n} = mul i64 %nc64, {esz}\n %gr{n} = add i64 %gb{n}, 8\n`)
emit(` %gn{n} = call ptr @realloc(ptr %gp{n}, i64 %gr{n})\n %go{n} = mul i64 %cap64, {esz}\n`)
emit(` %gt{n} = getelementptr i8, ptr %gn{n}, i64 %go{n}\n %gz{n} = sub i64 %gr{n}, %go{n}\n`)
emit(` %gm{n} = call ptr @memset(ptr %gt{n}, i32 0, i64 %gz{n})\n store ptr %gn{n}, ptr @{store}\n`)
}
# the stores a mod registered at run time (ludic_register_prop), grown the same way
function emit_grow_dyn() -> void {
emit(" br label %dynh\ndynh:\n %di = phi i32 [ 0, %apply ], [ %di1, %dynb ]\n %dcn = load i32, ptr @dyn_count\n")
emit(" %dmore = icmp slt i32 %di, %dcn\n br i1 %dmore, label %dynb, label %fin\ndynb:\n")
emit(" %dfp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %di\n %dnf = load i32, ptr %dfp\n")
emit(" %dnf4 = mul i32 %dnf, 4\n %desz = sext i32 %dnf4 to i64\n")
emit_grow_slot("dynS", "%desz", "s")
emit_grow_slot("dynH", "1", "h")
emit(" %di1 = add i32 %di, 1\n br label %dynh\n")
}
function emit_grow_slot(table: pointer, esz: pointer, t: pointer) -> void {
emit(` %d{t}p = getelementptr inbounds [32 x ptr], ptr @{table}, i32 0, i32 %di\n %d{t}o = load ptr, ptr %d{t}p\n`)
emit(` %d{t}b = mul i64 %nc64, {esz}\n %d{t}r = add i64 %d{t}b, 8\n %d{t}n = call ptr @realloc(ptr %d{t}o, i64 %d{t}r)\n`)
emit(` %d{t}f = mul i64 %cap64, {esz}\n %d{t}t = getelementptr i8, ptr %d{t}n, i64 %d{t}f\n %d{t}z = sub i64 %d{t}r, %d{t}f\n`)
emit(` %d{t}m = call ptr @memset(ptr %d{t}t, i32 0, i64 %d{t}z)\n store ptr %d{t}n, ptr %d{t}p\n`)
}

View file

@ -93,7 +93,8 @@ function emit_countdown_system() -> void {
emit(" %n = load i32, ptr @L_entc\n br label %loop\nloop:\n")
emit(" %i = phi i32 [ 0, %entry ], [ %i1, %next ]\n")
emit(" %go = icmp slt i32 %i, %n\n br i1 %go, label %body, label %done\nbody:\n")
emit(" %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %i\n")
let eb1 = ecs_base("L_alive")
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb1); emit(", i32 %i\n")
emit(" %al = load i32, ptr %ap\n %alive = icmp ne i32 %al, 0\n br i1 %alive, label %c0, label %next\n")
var k = 0
var i = 0
@ -106,12 +107,14 @@ function emit_countdown_system() -> void {
if any {
let sk = itoa(k); let nk = itoa(k + 1)
emit("c"); emit(sk); emit(":\n")
emit(" %h"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(d.s); emit(", i32 0, i32 %i\n")
let eb2 = ecs_base(`H_{d.s}`)
emit(" %h"); emit(sk); emit(" = getelementptr inbounds i8, ptr "); emit(eb2); emit(", i32 %i\n")
emit(" %hv"); emit(sk); emit(" = load i8, ptr %h"); emit(sk); emit("\n")
emit(" %has"); emit(sk); emit(" = icmp ne i8 %hv"); emit(sk); emit(", 0\n")
emit(" br i1 %has"); emit(sk); emit(", label %t"); emit(sk); emit(", label %c"); emit(nk); emit("\n")
emit("t"); emit(sk); emit(":\n")
emit(" %s"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(d.s); emit("], ptr @S_"); emit(d.s); emit(", i32 0, i32 %i\n")
let eb3 = ecs_base(`S_{d.s}`)
emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(d.s); emit(", ptr "); emit(eb3); emit(", i32 %i\n")
f = 0
while f < len(d.kids) {
if (d.kids[f].ty == "countdown") {
@ -474,7 +477,8 @@ function emit_world_despawn_fn() -> void {
emit("define void @fn_world_despawn(i32 %e) {\nentry:\n")
if len(g_ondespawn) > 0 {
let me = itoa(MAX_ENT)
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
let eb4 = ecs_base("L_kind")
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n")
emit(" %k = load i32, ptr %kp\n")
var i = 0
while i < len(g_ondespawn) {
@ -508,10 +512,14 @@ function emit_despawn_all_fn() -> void {
emit("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n")
emit(" %n = load i32, ptr @L_entc\n %go = icmp slt i32 %i, %n\n")
emit(" br i1 %go, label %body, label %fin\n")
emit("body:\n %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %i\n")
emit("body:\n")
let eb5 = ecs_base("L_alive")
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %i\n")
emit(" %al = load i32, ptr %ap\n %isa = icmp ne i32 %al, 0\n")
emit(" br i1 %isa, label %do, label %cont\n")
emit("do:\n %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %i\n")
emit("do:\n")
let eb6 = ecs_base("L_kind")
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb6); emit(", i32 %i\n")
emit(" %k = load i32, ptr %kp\n")
var i = 0
while i < len(g_ondespawn) {
@ -608,7 +616,8 @@ function emit_event_fns() -> void {
emit(" %n = load i32, ptr @evN_"); emit(en); emit("\n")
emit(" %lo = icmp sge i32 %tok, 0\n %hi = icmp slt i32 %tok, %n\n %ok = and i1 %lo, %hi\n")
emit(" br i1 %ok, label %do, label %skip\n")
emit("do:\n %slot = getelementptr inbounds ["); emit(cap); emit(" x ptr], ptr @evL_"); emit(en); emit(", i32 0, i32 %tok\n")
emit("do:\n")
emit(" %slot = getelementptr inbounds ["); emit(cap); emit(" x ptr], ptr @evL_"); emit(en); emit(", i32 0, i32 %tok\n")
emit(" store ptr null, ptr %slot\n br label %skip\n")
emit("skip:\n ret void\n}\n\n")

View file

@ -25,7 +25,8 @@ function emit_query(st: Node) -> void {
emit(body); emit(":\n"); g_term = false
let i1 = emit_bind(`load i32, ptr {ip}`)
# alive?
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 "); emit(i1); emit("\n")
let eb1 = ecs_base("L_alive")
let ap = nreg(); emit(" "); emit(ap); emit(" = getelementptr inbounds i32, ptr "); emit(eb1); emit(", i32 "); emit(i1); emit("\n")
let al = emit_bind(`load i32, ptr {ap}`)
let alc = emit_bind(`icmp ne i32 {al}, 0`)
let ka = lbl("qa")
@ -40,14 +41,16 @@ function emit_query(st: Node) -> void {
let ak = find_arch_id(tm.s)
var ok: pointer = "0"
if ak > 0 {
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(i1); emit("\n")
let eb2 = ecs_base("L_kind")
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds i32, ptr "); emit(eb2); emit(", i32 "); emit(i1); emit("\n")
let kv = emit_bind(`load i32, ptr {kp}`)
let kok = emit_bind(`icmp eq i32 {kv}, {itoa(ak)}`)
let mev = emit_bind(`load i32, ptr @ME_{tm.s}`) # model-enabled flag
let meok = emit_bind(`icmp ne i32 {mev}, 0`)
ok = emit_bind(`and i1 {kok}, {meok}`)
} else {
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
let eb3 = ecs_base(`H_{tm.s}`)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb3); emit(", i32 "); emit(i1); emit("\n")
let hv = emit_bind(`load i8, ptr {hp}`)
ok = emit_bind(`icmp ne i8 {hv}, 0`)
}
@ -66,7 +69,8 @@ function emit_query(st: Node) -> void {
if tm.ival == 0 and find_arch_id(tm.s) == 0 {
if vi < len(st.kids) {
let slot = nreg()
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(tm.s); emit("], ptr @S_"); emit(tm.s); emit(", i32 0, i32 "); emit(i1); emit("\n")
let eb4 = ecs_base(`S_{tm.s}`)
emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(tm.s); emit(", ptr "); emit(eb4); emit(", i32 "); emit(i1); emit("\n")
let vslot = emit_alloca("ptr")
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
loc_push(st.kids[vi].s, vslot, tm.s)

View file

@ -46,16 +46,23 @@ function emit_io(fn2: pointer, p: pointer, bytes: pointer) -> void {
function emit_snapshot_blocks(fn2: pointer) -> void {
g_iok = 0
g_off = "0"
let me = itoa(MAX_ENT)
# the stores grow, so a snapshot says how many slots it holds first; a load grows to it before
# reading them back
let reading = (fn2 == "fread") or (g_snap_mode == "load")
if not reading { emit(" %sncap0 = load i32, ptr @L_cap\n store i32 %sncap0, ptr @L_snapcap\n") }
emit_io(fn2, "@L_snapcap", "4")
emit(" %sncap = load i32, ptr @L_snapcap\n")
if reading { emit(" call void @L_grow(i32 %sncap)\n") }
emit(" %sncap64 = sext i32 %sncap to i64\n")
emit_io(fn2, "@L_entc", "4")
emit_io(fn2, "@L_freen", "4")
emit(" %nalive = mul i64 "); emit(me); emit(", 4\n")
emit_io(fn2, "@L_alive", "%nalive")
emit_io(fn2, "@L_freelist", "%nalive")
emit_io(fn2, "@L_kind", "%nalive")
emit(" %nalive = mul i64 %sncap64, 4\n")
emit_io(fn2, snap_base("L_alive", 0), "%nalive")
emit_io(fn2, snap_base("L_freelist", 1), "%nalive")
emit_io(fn2, snap_base("L_kind", 2), "%nalive")
# N3: an @Owned world snapshots its per-entity owners too, so rollback/replication
# round-trips ownership (like @L_kind). Gated, so non-@Owned snapshots are unchanged.
if net_has_owned() { emit_io(fn2, "@L_owner_arr", "%nalive") }
if net_has_owned() { emit_io(fn2, snap_base("L_owner_arr", 3), "%nalive") }
var i = 0
while i < len(prog) { if prog[i].kind == N_VAR { emit_io(fn2, `@g_{prog[i].s}`, "4") }; i += 1 }
var ci = 0
@ -63,16 +70,25 @@ function emit_snapshot_blocks(fn2: pointer) -> void {
while i < len(prog) {
if prog[i].kind == N_COMP {
let c = prog[i].s
let csz1 = `%csz1_{itoa(ci)}`
let csz = `%csz{itoa(ci)}`
emit(" "); emit(csz); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 "); emit(me); emit(") to i64\n")
emit_io(fn2, `@S_{c}`, csz)
emit_io(fn2, `@H_{c}`, me)
emit(" "); emit(csz1); emit(" = ptrtoint ptr getelementptr (%Cmp_"); emit(c); emit(", ptr null, i32 1) to i64\n")
emit(" "); emit(csz); emit(" = mul i64 %sncap64, "); emit(csz1); emit("\n")
emit_io(fn2, snap_base(`S_{c}`, 4 + ci * 2), csz)
emit_io(fn2, snap_base(`H_{c}`, 5 + ci * 2), "%sncap64")
ci += 1
}
i += 1
}
}
# a store's base, loaded after any grow the snapshot made
function snap_base(store: pointer, k: int) -> pointer {
let r = `%snb{itoa(k)}`
emit(` {r} = load ptr, ptr @{store}\n`)
return r
}
function emit_snapshot() -> void {
g_snap_mode = "file" # seed (module ptr inits are null)
emith("@.sav_path = private unnamed_addr constant [10 x i8] c\"ludic.sav\\00\"\n")

View file

@ -15,10 +15,12 @@ function emit_init_component(e: pointer, comp: pointer, rec: Node) -> void {
let me = itoa(MAX_ENT)
let c = find_comp(comp)
if (c == null) { perr(`spawn: unknown property {comp}`) }
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
let eb1 = ecs_base(`H_{comp}`)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb1); emit(", i32 "); emit(e); emit("\n")
emit(" store i8 1, ptr "); emit(hp); emit("\n")
let slot = nreg()
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(comp); emit("], ptr @S_"); emit(comp); emit(", i32 0, i32 "); emit(e); emit("\n")
let eb2 = ecs_base(`S_{comp}`)
emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(comp); emit(", ptr "); emit(eb2); emit(", i32 "); emit(e); emit("\n")
# defaults
var f = 0
while f < len(c.kids) {
@ -71,7 +73,8 @@ function emit_bind_props(model: Node, e: pointer) -> void {
while c < len(model.kids) {
let pname = model.kids[c].s
let slot = nreg()
emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(pname); emit("], ptr @S_"); emit(pname); emit(", i32 0, i32 "); emit(e); emit("\n")
let eb3 = ecs_base(`S_{pname}`)
emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(pname); emit(", ptr "); emit(eb3); emit(", i32 "); emit(e); emit("\n")
let vslot = emit_alloca("ptr")
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
loc_push(pname, vslot, pname)
@ -146,7 +149,8 @@ function emit_spawn(st: Node) -> pointer {
if (find_prefab(st.s) != null) and (ak == 0) { perr(`prefab {st.s}: unknown model {model}`) }
if ak > 0 {
let me = itoa(MAX_ENT)
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(e); emit("\n")
let eb4 = ecs_base("L_kind")
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 "); emit(e); emit("\n")
emit(" store i32 "); emit(itoa(ak)); emit(", ptr "); emit(kp); emit("\n")
let arch = find_arch(model)
var c = 0
@ -179,7 +183,8 @@ function emit_despawn(st: Node) -> void {
# @OnDespawn: dispatch on the entity's kind and run the matching model's hook
if len(g_ondespawn) > 0 {
let me = itoa(MAX_ENT)
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 "); emit(v.code); emit("\n")
let eb5 = ecs_base("L_kind")
let kp = nreg(); emit(" "); emit(kp); emit(" = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 "); emit(v.code); emit("\n")
let kind = emit_bind(`load i32, ptr {kp}`)
var i = 0
while i < len(g_ondespawn) {
@ -205,7 +210,8 @@ function emit_despawn(st: Node) -> void {
function emit_attach(st: Node) -> void {
let ev = emit_expr(st.a)
let me = itoa(MAX_ENT)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
let eb6 = ecs_base(`H_{st.s}`)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb6); emit(", i32 "); emit(ev.code); emit("\n")
let cur = emit_bind(`load i8, ptr {hp}`)
let isnew = emit_bind(`icmp eq i8 {cur}, 0`)
let doit = lbl("attach"); let done = lbl("attdone")
@ -223,7 +229,8 @@ function emit_attach(st: Node) -> void {
function emit_detach(st: Node) -> void {
let ev = emit_expr(st.a)
let me = itoa(MAX_ENT)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
let eb7 = ecs_base(`H_{st.s}`)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb7); emit(", i32 "); emit(ev.code); emit("\n")
let cur = emit_bind(`load i8, ptr {hp}`)
let here = emit_bind(`icmp ne i8 {cur}, 0`)
let doit = lbl("detach"); let done = lbl("detdone")
@ -233,7 +240,8 @@ function emit_detach(st: Node) -> void {
let hb = ondetach_body(st.s) # @OnDetach reads the outgoing value
if (hb != null) {
let save = nloc
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(st.s); emit("], ptr @S_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
let eb8 = ecs_base(`S_{st.s}`)
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(st.s); emit(", ptr "); emit(eb8); emit(", i32 "); emit(ev.code); emit("\n")
let vslot = emit_alloca("ptr")
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
loc_push(st.s, vslot, st.s)
@ -266,13 +274,15 @@ function emit_toggle(st: Node) -> void {
if (st.a != null) {
let ev = emit_expr(st.a)
let me = itoa(MAX_ENT)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
let eb9 = ecs_base(`H_{st.s}`)
let hp = nreg(); emit(" "); emit(hp); emit(" = getelementptr inbounds i8, ptr "); emit(eb9); emit(", i32 "); emit(ev.code); emit("\n")
emit(" store i8 "); emit(val); emit(", ptr "); emit(hp); emit("\n")
var hb: Node = null
if st.ival == 1 { hb = onenable_body(st.s) } else { hb = ondisable_body(st.s) }
if (hb != null) {
let save = nloc
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(st.s); emit("], ptr @S_"); emit(st.s); emit(", i32 0, i32 "); emit(ev.code); emit("\n")
let eb10 = ecs_base(`S_{st.s}`)
let slot = nreg(); emit(" "); emit(slot); emit(" = getelementptr inbounds %Cmp_"); emit(st.s); emit(", ptr "); emit(eb10); emit(", i32 "); emit(ev.code); emit("\n")
let vslot = emit_alloca("ptr")
emit(" store ptr "); emit(slot); emit(", ptr "); emit(vslot); emit("\n")
loc_push(st.s, vslot, st.s)

View file

@ -96,7 +96,8 @@ function emit_world_table() -> void {
emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n")
emit("g"); emit(sk); emit(":\n")
emit(" %s"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
let eb1 = ecs_base(`S_{cn}`)
emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb1); emit(", i32 %e\n")
var fj = 0
while fj < len(c.kids) {
let ft = llty(c.kids[fj].ty); let fk = `{sk}_{itoa(fj)}`
@ -139,7 +140,8 @@ function emit_world_table() -> void {
emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n")
emit("g"); emit(sk); emit(":\n")
emit(" %s"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
let eb2 = ecs_base(`S_{cn}`)
emit(" %s"); emit(sk); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb2); emit(", i32 %e\n")
var fj = 0
while fj < len(c.kids) {
let ft = llty(c.kids[fj].ty); let fk = `{sk}_{itoa(fj)}`
@ -182,7 +184,8 @@ function emit_world_table() -> void {
emit(" %m"); emit(sk); emit(" = icmp eq i32 %p, "); emit(sk); emit("\n")
emit(" br i1 %m"); emit(sk); emit(", label %g"); emit(sk); emit(", label %gn"); emit(sk); emit("\n")
emit("g"); emit(sk); emit(":\n")
emit(" %hp"); emit(sk); emit(" = getelementptr inbounds ["); emit(me); emit(" x i8], ptr @H_"); emit(cn); emit(", i32 0, i32 %e\n")
let eb3 = ecs_base(`H_{cn}`)
emit(" %hp"); emit(sk); emit(" = getelementptr inbounds i8, ptr "); emit(eb3); emit(", i32 %e\n")
emit(" %hv"); emit(sk); emit(" = load i8, ptr %hp"); emit(sk); emit("\n")
emit(" %hr"); emit(sk); emit(" = zext i8 %hv"); emit(sk); emit(" to i32\n ret i32 %hr"); emit(sk); emit("\n")
emit("gn"); emit(sk); emit(":\n")
@ -201,10 +204,10 @@ function emit_world_table() -> void {
# zeroed. The returned id works with get/set/has/attach exactly like a built-in.
emit("define i32 @ludic_register_prop(ptr %name, i32 %nfields) {\nentry:\n")
emit(" %dc = load i32, ptr @dyn_count\n %full = icmp slt i32 %dc, 32\n br i1 %full, label %do, label %rej\n")
emit("do:\n %nf4 = mul i32 %nfields, 4\n %sz = mul i32 %nf4, "); emit(me); emit("\n %szl = sext i32 %sz to i64\n")
emit("do:\n %rcap = load i32, ptr @L_cap\n %nf4 = mul i32 %nfields, 4\n %sz = mul i32 %nf4, %rcap\n %szl = sext i32 %sz to i64\n")
emit(" %buf = call ptr @malloc(i64 %szl)\n call ptr @memset(ptr %buf, i32 0, i64 %szl)\n")
emit(" %sp = getelementptr inbounds [32 x ptr], ptr @dynS, i32 0, i32 %dc\n store ptr %buf, ptr %sp\n")
emit(" %hbuf = call ptr @malloc(i64 "); emit(me); emit(")\n call ptr @memset(ptr %hbuf, i32 0, i64 "); emit(me); emit(")\n")
emit(" %hcap = sext i32 %rcap to i64\n %hbuf = call ptr @malloc(i64 %hcap)\n call ptr @memset(ptr %hbuf, i32 0, i64 %hcap)\n")
emit(" %hp = getelementptr inbounds [32 x ptr], ptr @dynH, i32 0, i32 %dc\n store ptr %hbuf, ptr %hp\n")
emit(" %fp = getelementptr inbounds [32 x i32], ptr @dynF, i32 0, i32 %dc\n store i32 %nfields, ptr %fp\n")
emit(" %np = getelementptr inbounds [32 x ptr], ptr @dynName, i32 0, i32 %dc\n store ptr %name, ptr %np\n")
@ -222,7 +225,8 @@ function emit_world_table() -> void {
# identifies each entity's model, then reads/writes it with get/set/has above.
emit("define i32 @ludic_entity_count() {\nentry:\n %n = load i32, ptr @L_entc\n ret i32 %n\n}\n\n")
emit("define i32 @ludic_kind(i32 %e) {\nentry:\n")
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n")
let eb4 = ecs_base("L_kind")
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n")
emit(" %k = load i32, ptr %kp\n ret i32 %k\n}\n\n")
emit("define i32 @ludic_model_id(ptr %name) {\nentry:\n")
k = 0; i = 0
@ -287,7 +291,9 @@ function emit_world_table() -> void {
emit(" %n = load i32, ptr @L_entc\n br label %loop\n")
emit("loop:\n %e = phi i32 [ %from, %entry ], [ %e1, %cont ]\n")
emit(" %go = icmp slt i32 %e, %n\n br i1 %go, label %body, label %none\n")
emit("body:\n %ap = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_alive, i32 0, i32 %e\n")
emit("body:\n")
let eb5 = ecs_base("L_alive")
emit(" %ap = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n")
emit(" %al = load i32, ptr %ap\n %isa = icmp ne i32 %al, 0\n br i1 %isa, label %chk, label %cont\n")
emit("chk:\n %h = call i32 @ludic_has(i32 %e, i32 %p)\n %hit = icmp ne i32 %h, 0\n br i1 %hit, label %hitb, label %cont\n")
emit("hitb:\n ret i32 %e\n")
@ -446,6 +452,7 @@ function emit_game_main() -> void {
emit(" store i32 %argc, ptr @L_argc\n")
emit(" store ptr %argv, ptr @L_argv\n")
emit(" call void @L_init_runtime()\n")
if has_ecs() { emit(" call void @L_grow(i32 1)\n") }
if (find_fn("rt_init") != null) { emit(" call void " + fn_sym("rt_init") + "()\n") }
emit(" call void @L_init_globals()\n")
emit_calls_for_phase("Start")

View file

@ -121,7 +121,8 @@ function emit_net_serialize(m: Node) -> void {
let cn = m.kids[ci].s
let c = find_comp(cn)
if (c != null) {
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
let eb1 = ecs_base(`S_{cn}`)
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb1); emit(", i32 %e\n")
var fj = 0
while fj < len(c.kids) {
if c.kids[fj].ival == 1 {
@ -154,7 +155,8 @@ function emit_net_apply(m: Node) -> void {
let cn = m.kids[ci].s
let c = find_comp(cn)
if (c != null) {
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds ["); emit(me); emit(" x %Cmp_"); emit(cn); emit("], ptr @S_"); emit(cn); emit(", i32 0, i32 %e\n")
let eb2 = ecs_base(`S_{cn}`)
let s = nreg(); emit(" "); emit(s); emit(" = getelementptr inbounds %Cmp_"); emit(cn); emit(", ptr "); emit(eb2); emit(", i32 %e\n")
var fj = 0
while fj < len(c.kids) {
if c.kids[fj].ival == 1 {
@ -181,7 +183,8 @@ function emit_net_dispatch() -> void {
let me = itoa(MAX_ENT)
emit("define i32 @ludic_serialize(i32 %e, ptr %buf) {\nentry:\n")
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
let eb3 = ecs_base("L_kind")
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb3); emit(", i32 %e\n %k = load i32, ptr %kp\n")
var k = 0
var i = 0
while i < len(prog) {
@ -198,7 +201,8 @@ function emit_net_dispatch() -> void {
emit(" ret i32 0\n}\n\n")
emit("define void @ludic_apply(i32 %e, ptr %buf, i32 %len) {\nentry:\n")
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
let eb4 = ecs_base("L_kind")
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb4); emit(", i32 %e\n %k = load i32, ptr %kp\n")
k = 0; i = 0
while i < len(prog) {
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
@ -216,7 +220,8 @@ function emit_net_dispatch() -> void {
# ludic_sync_size(e): the replicated byte count for the entity's model — a
# constant per kind, so a runtime can size a buffer before serialize.
emit("define i32 @ludic_sync_size(i32 %e) {\nentry:\n")
emit(" %kp = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_kind, i32 0, i32 %e\n %k = load i32, ptr %kp\n")
let eb5 = ecs_base("L_kind")
emit(" %kp = getelementptr inbounds i32, ptr "); emit(eb5); emit(", i32 %e\n %k = load i32, ptr %kp\n")
k = 0; i = 0
while i < len(prog) {
if prog[i].kind == N_ARCH and net_model_syncs(prog[i]) {
@ -239,12 +244,15 @@ function emit_net_dispatch() -> void {
function emit_net_owner() -> void {
let me = itoa(MAX_ENT)
emit("define i32 @L_owner(i32 %e) {\nentry:\n")
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %v = load i32, ptr %p\n ret i32 %v\n}\n\n")
let eb6 = ecs_base("L_owner_arr")
emit(" %p = getelementptr inbounds i32, ptr "); emit(eb6); emit(", i32 %e\n %v = load i32, ptr %p\n ret i32 %v\n}\n\n")
emit("define void @L_set_owner(i32 %e, i32 %id) {\nentry:\n")
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n store i32 %id, ptr %p\n ret void\n}\n\n")
let eb7 = ecs_base("L_owner_arr")
emit(" %p = getelementptr inbounds i32, ptr "); emit(eb7); emit(", i32 %e\n store i32 %id, ptr %p\n ret void\n}\n\n")
# is_owner(e): does the local peer own e? owner(e) == local_id().
emit("define i32 @L_is_owner(i32 %e) {\nentry:\n")
emit(" %p = getelementptr inbounds ["); emit(me); emit(" x i32], ptr @L_owner_arr, i32 0, i32 %e\n %o = load i32, ptr %p\n")
let eb8 = ecs_base("L_owner_arr")
emit(" %p = getelementptr inbounds i32, ptr "); emit(eb8); emit(", i32 %e\n %o = load i32, ptr %p\n")
emit(" %lid = load i32, ptr @L_localid\n %eq = icmp eq i32 %o, %lid\n %r = zext i1 %eq to i32\n ret i32 %r\n}\n\n")
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff