frame allocs (25.2): @frame on a step list's field, @alloc_ok on a statement, and on an exported function
A field declared '@frame run: fn(...)' makes every function stored in it a frame root, as a System's
tick is. @alloc_ok("why") before a statement takes that statement out of frame_allocs and makes what
it allocates declared at run time; a function holding one keeps the fence's scope depth and puts it
back at its return, so a return inside the statement cannot leave the scope open. @alloc_ok above
'export function' was lost - export parses the declaration one call down - and is now carried to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b35409254e
commit
1e2a6bab5b
7 changed files with 75754 additions and 74774 deletions
|
|
@ -34,10 +34,13 @@ function emit_fn(d: Node) -> void {
|
|||
}
|
||||
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") }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
75243
selfhost/ludicc.seed.ll
75243
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