wip(0.S3): ui blocks, scene on enter/exit, hooks, machines over a state's field, namespace and engine-system injection; a reference out of a read-only state or a module let is read-only; deps counts writes into another module's state; the rejected examples hold states
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4919c9c2fc
commit
e7f8ee6b91
31 changed files with 100137 additions and 72476 deletions
|
|
@ -358,6 +358,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
let al = ns_alias_find(ns, meth)
|
||||
if al >= 0 { return emit_alias_call(al, e) }
|
||||
var bare: pointer = null
|
||||
var nsfn: Node = null # a namespace function behind Ns.meth
|
||||
let labels = new []pointer
|
||||
# Sprite.* / Assets.* — the namespaced spritesheet / atlas API (#81), a runtime
|
||||
# in atlas.ludic over the variable-size image loader. A cell (or multi-cell span)
|
||||
|
|
@ -519,15 +520,16 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if is_ns_block(ns) and (not ns_export_has(bare)) { perr(`{ns}.{meth} is internal to namespace {ns}`) }
|
||||
# a namespace function takes named arguments like any other function: its
|
||||
# labels are its parameter names, so `Weapon.def(name: "pistol", ...)` works.
|
||||
let nsfn = find_fn(bare)
|
||||
nsfn = find_fn(bare)
|
||||
if (nsfn != null) {
|
||||
let names = param_labels(nsfn)
|
||||
var pi2 = 0
|
||||
var pi2 = state_lead(nsfn) # 0.S: its states are the runtime's to give
|
||||
while pi2 < len(names) { push(labels, names[pi2]); pi2 += 1 }
|
||||
}
|
||||
}
|
||||
if (bare == null) { perr(`unknown builtin {ns}.{meth}`) }
|
||||
reorder_named(e, labels)
|
||||
state_inject(e, nsfn)
|
||||
let id = node(E_ID); id.s = bare; e.a = id
|
||||
return emit_call(e)
|
||||
}
|
||||
|
|
@ -955,6 +957,7 @@ function emit_call(e: Node) -> Val {
|
|||
cname = rtname
|
||||
}
|
||||
vis_check(fn2, name)
|
||||
state_inject_generated(e, fn2) # 0.S: a call the compiler wrote gets its states
|
||||
call_fill_defaults(e, fn2) # L11 - for code the checker does not walk
|
||||
reorder_named(e, param_labels(fn2))
|
||||
# evaluate args first (their IR is emitted before the call instruction), coercing
|
||||
|
|
|
|||
|
|
@ -154,6 +154,36 @@ function deps_chain_root(t: Node) -> Node {
|
|||
if n == null or n.kind != E_ID { return null }
|
||||
return n
|
||||
}
|
||||
# 0.S: a chain rooted in a state parameter (`b_st.b_count`): the state's module and the field, when
|
||||
# the state is another module's - else null
|
||||
var g_dp_st_field: pointer = null
|
||||
function deps_state_owner(t: Node) -> pointer {
|
||||
let r = deps_chain_root(t)
|
||||
if r == null { return null }
|
||||
let li = loc_find(r.s)
|
||||
if li < 0 or not is_state_ty(loc_ty[li]) { return null }
|
||||
let g = find_global(state_global(loc_ty[li]))
|
||||
if g == null or g.file == null { return null }
|
||||
let owner = module_for_uses(g.file)
|
||||
let from = module_of(g_err_file)
|
||||
if (from == "") or (owner == "") or (from == owner) { return null }
|
||||
var n = t
|
||||
g_dp_st_field = null
|
||||
while n != null and n != r {
|
||||
if n.kind == E_MEMBER and n.a == r { g_dp_st_field = n.s }
|
||||
n = n.a
|
||||
}
|
||||
if g_dp_st_field == null { g_dp_st_field = loc_ty[li] }
|
||||
return owner
|
||||
}
|
||||
# an assignment into another module's state, through the parameter that holds it
|
||||
function deps_state_write(t: Node) -> bool {
|
||||
if not deps_on() or g_err_file == null { return false }
|
||||
let owner = deps_state_owner(t)
|
||||
if owner == null { return false }
|
||||
push(g_dp_writes, `write {owner} {g_dp_st_field} {module_of(g_err_file)} {g_err_file}:{itoa(g_err_line)}`)
|
||||
return true
|
||||
}
|
||||
# `let local = <expr>`: an alias when the expression is another module's global, or an alias
|
||||
function deps_alias_let(local: pointer, e: Node) -> void {
|
||||
if not deps_on() or g_err_file == null { return }
|
||||
|
|
@ -162,6 +192,13 @@ function deps_alias_let(local: pointer, e: Node) -> void {
|
|||
let r = deps_chain_root(e)
|
||||
if r == null { return }
|
||||
if loc_find(r.s) >= 0 {
|
||||
let so = deps_state_owner(e) # a field of another module's state
|
||||
if so != null {
|
||||
push(g_dp_al_local, local)
|
||||
push(g_dp_al_owner, so)
|
||||
push(g_dp_al_global, g_dp_st_field)
|
||||
return
|
||||
}
|
||||
let k = deps_alias_find(r.s)
|
||||
if k < 0 { return }
|
||||
push(g_dp_al_local, local)
|
||||
|
|
|
|||
|
|
@ -113,6 +113,13 @@ function emit_inj_thunk(d: Node) -> pointer {
|
|||
emith(`define {rt} {name}({params}) {{\nentry:\n{body}{call}}}\n\n`)
|
||||
return name
|
||||
}
|
||||
# the symbol a call the compiler writes by name reaches: the function, or its thunk when it takes
|
||||
# states (an engine system, a runtime hook) - the runtime's side of an entry point
|
||||
function fn_sym(name: pointer) -> pointer {
|
||||
let d = find_fn(name)
|
||||
if d != null and state_lead(d) > 0 { return emit_inj_thunk(d) }
|
||||
return `@fn_{name}`
|
||||
}
|
||||
# a call through a value of a function type
|
||||
function emit_indirect_call(fv: Val, e: Node) -> Val {
|
||||
let ptys = fn_ty_params(fv.ty)
|
||||
|
|
@ -174,7 +181,8 @@ function check_worker_ref(e: Node) -> void {
|
|||
let d = find_fn(e.s)
|
||||
if d == null { return }
|
||||
# the thread calls it as void(i32, ptr): any pointer-sized context will do (words, a record)
|
||||
var ok = len(d.kids) == 2 and llty(d.ty) == "void"
|
||||
if ok { ok = llty(d.kids[0].ty) == "i32" and llty(d.kids[1].ty) == "ptr" }
|
||||
let s = state_lead(d) # its states come from the thunk
|
||||
var ok = len(d.kids) == s + 2 and llty(d.ty) == "void"
|
||||
if ok { ok = llty(d.kids[s].ty) == "i32" and llty(d.kids[s + 1].ty) == "ptr" }
|
||||
if not ok { perr(`fn {e.s}: a worker function takes (i: int, ctx: pointer) and returns nothing`) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ function emit_assign(st: Node) -> void {
|
|||
} else {
|
||||
if deps_on() {
|
||||
let root = deps_target_root(t)
|
||||
if root != null { deps_write(root, root.s) } else { deps_alias_write(t) }
|
||||
if root != null { deps_write(root, root.s) } else if not deps_state_write(t) { deps_alias_write(t) }
|
||||
}
|
||||
if t.kind == E_MEMBER { addr = emit_member_addr(t); ty = g_addr_ty }
|
||||
else { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ function emit_call_one(d: Node) -> void {
|
|||
function emit_one_engine_system(comp: pointer, fn: pointer) -> void {
|
||||
if is_system_disabled(fn) { return } # lever 5 (#57): `disable system <fn>` drops the tick
|
||||
if (find_comp(comp) != null) and (find_fn(fn) != null) {
|
||||
emit(" call void @fn_"); emit(fn); emit("()\n")
|
||||
emit(` call void {fn_sym(fn)}()\n`)
|
||||
}
|
||||
}
|
||||
function emit_engine_systems_for_phase(phase: pointer) -> void {
|
||||
|
|
|
|||
|
|
@ -11,27 +11,49 @@ function machine_var(a: Node) -> Node {
|
|||
}
|
||||
return null
|
||||
}
|
||||
# 0.S: a machine over a state's field (`machine hero.mode`) or a local: its address, else null
|
||||
var g_mach_ty: pointer = null
|
||||
function machine_addr(a: Node) -> pointer {
|
||||
if a.kind == E_MEMBER {
|
||||
let p = emit_member_addr(a)
|
||||
g_mach_ty = g_addr_ty
|
||||
return p
|
||||
}
|
||||
if a.kind == E_ID {
|
||||
let li = loc_find(a.s)
|
||||
if li >= 0 {
|
||||
g_mach_ty = loc_ty[li]
|
||||
return loc_reg[li]
|
||||
}
|
||||
let gv = machine_var(a)
|
||||
if gv != null {
|
||||
g_mach_ty = gv.ty
|
||||
return `@g_{a.s}`
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
# the value a state dispatches on: its explicit `= expr`; else, when the machine's
|
||||
# store is a `var` declared with an enum type, the variant named like the state
|
||||
# (`var mode: HeroState` + `state Rolling` -> HeroState.Rolling); else its ordinal.
|
||||
function machine_state_value(m: Node, state: Node) -> pointer {
|
||||
if (state.b != null) { return emit_expr(state.b).code }
|
||||
let gv = machine_var(m.a)
|
||||
if (gv != null) {
|
||||
if (gv.ty != null) {
|
||||
let ord = enum_ordinal(gv.ty, state.s)
|
||||
if ord >= 0 { return itoa(ord) }
|
||||
if (find_enum(gv.ty) != null) { perr(`machine state {state.s} is not a variant of enum {gv.ty}`) }
|
||||
}
|
||||
g_mach_ty = null
|
||||
let addr = machine_addr(m.a)
|
||||
let ty = g_mach_ty
|
||||
if addr != null and ty != null {
|
||||
let ord = enum_ordinal(ty, state.s)
|
||||
if ord >= 0 { return itoa(ord) }
|
||||
if (find_enum(ty) != null) { perr(`machine state {state.s} is not a variant of enum {ty}`) }
|
||||
}
|
||||
return itoa(state.ival)
|
||||
}
|
||||
|
||||
function emit_machine(st: Node) -> void {
|
||||
let gv = machine_var(st.a)
|
||||
let addr = machine_addr(st.a)
|
||||
var s: pointer = ""
|
||||
if (gv != null) { s = emit_bind(`load i32, ptr @g_{st.a.s}`) }
|
||||
if (addr != null) { s = emit_bind(`load i32, ptr {addr}`) }
|
||||
else {
|
||||
let regv = emit_expr(st.a)
|
||||
s = emit_bind(`call i32 @fn_rt_reg(i32 {regv.code})`)
|
||||
|
|
@ -68,10 +90,10 @@ function emit_become(st: Node) -> void {
|
|||
var i = 0
|
||||
while i < len(m.kids) { if (m.kids[i].s == st.s) { target = m.kids[i] }; i += 1 }
|
||||
if (target != null) {
|
||||
let gv = machine_var(m.a)
|
||||
let sv = machine_state_value(m, target)
|
||||
if (gv != null) {
|
||||
emit(" store i32 "); emit(sv); emit(", ptr @g_"); emit(m.a.s); emit("\n")
|
||||
let addr = machine_addr(m.a)
|
||||
if (addr != null) {
|
||||
emit(` store i32 {sv}, ptr {addr}\n`)
|
||||
} else {
|
||||
let regv = emit_expr(m.a)
|
||||
emit(" call void @fn_rt_set_reg(i32 "); emit(regv.code); emit(", i32 "); emit(sv); emit(")\n")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue