fix(lang): the action drain is the program's; --check runs every check a build makes; a registry key named count is refused; name lookups are tables
- the function that calls every reducer (and the action queue) is written in the program's own file: in the first action's file it belonged to that module, depended on every module with a reducer, and joined a game's modules into one 69-module cycle (examples/actions/modules and a ludic deps case hold it); the state instances, the queue and the reducers make no deps edges - ludicc --check / ludic build --check lower the program too and write nothing, so the code writer's refusals are in it: a bind to a function that is gone, an unknown name (and the checker now refuses fn <missing> itself); rejects bind_missing_fn, unknown_name, registry_count_key - def R count is refused: its constant would be PREFIX_COUNT, the registry's size - a file's module, package, trust and numbers-float are tables, and from the check on the lookups of functions, enums, records, globals and externs are too (tagged enums kept as a list): Maroon Lake's check-only build went from about 20 s to 7 s including lowering, its IR from about 2 minutes to under 10 s; duplicate declarations are found by table, not a pair of loops - threads.ludic's pool check gives each call a little work, so a busy machine cannot run them all on the caller before a worker wakes (it failed one run in three under load) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2fdefa6040
commit
8cf4b3fed6
24 changed files with 67169 additions and 64398 deletions
|
|
@ -151,7 +151,94 @@ function find_arch(name: pointer) -> Node {
|
|||
while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and (d.s == name) { return d }; i += 1 }
|
||||
return null
|
||||
}
|
||||
# ---- the emitter's lookups by name: tables, once the program is whole ---------------------
|
||||
# find_fn / find_enum / find_comp / find_global / find_extern are asked for every name the emitter
|
||||
# lowers; walking prog for each was most of a big program's build. From emit_program on, each kind
|
||||
# is a table, kept up with what is appended to prog; a hit whose node has since been renamed falls
|
||||
# back to the walk.
|
||||
const IX_SLOTS: int = 131072
|
||||
var g_ix_on: bool = false
|
||||
var g_ix_tagged: []Node = new []Node
|
||||
var g_ix_upto: int = 0
|
||||
var g_ix_keys: [][]pointer = new [][]pointer # per kind: 0 fn, 1 enum, 2 comp, 3 global, 4 extern
|
||||
var g_ix_vals: [][]pointer = new [][]pointer
|
||||
function ix_start() -> void {
|
||||
g_ix_keys = new [][]pointer
|
||||
g_ix_vals = new [][]pointer
|
||||
var t = 0
|
||||
while t < 5 {
|
||||
let k = new []pointer
|
||||
let v = new []pointer
|
||||
var i = 0
|
||||
while i < IX_SLOTS {
|
||||
push(k, null)
|
||||
push(v, null)
|
||||
i += 1
|
||||
}
|
||||
push(g_ix_keys, k)
|
||||
push(g_ix_vals, v)
|
||||
t += 1
|
||||
}
|
||||
g_ix_upto = 0
|
||||
g_ix_tagged = new []Node
|
||||
g_ix_on = true
|
||||
}
|
||||
function ix_kind(d: Node) -> int {
|
||||
if d.kind == N_FN { return 0 }
|
||||
if d.kind == N_ENUM { return 1 }
|
||||
if d.kind == N_COMP { return 2 }
|
||||
if d.kind == N_VAR or d.kind == N_CONST { return 3 }
|
||||
if d.kind == N_EXTERN { return 4 }
|
||||
return -1
|
||||
}
|
||||
function ix_slot(t: int, name: pointer) -> int {
|
||||
var h = 5381
|
||||
var i = 0
|
||||
let n = len(name)
|
||||
while i < n {
|
||||
h = (h * 33 + name[i]) % 1000003
|
||||
i += 1
|
||||
}
|
||||
h = h % IX_SLOTS
|
||||
let ks = g_ix_keys[t]
|
||||
while ks[h] != null and not (ks[h] == name) { h = (h + 1) % IX_SLOTS }
|
||||
return h
|
||||
}
|
||||
function ix_sync() -> void {
|
||||
while g_ix_upto < len(prog) {
|
||||
let d = prog[g_ix_upto]
|
||||
let t = ix_kind(d)
|
||||
if d.kind == N_ENUM and enum_is_tagged(d) { push(g_ix_tagged, d) }
|
||||
if t >= 0 and d.s != null {
|
||||
let h = ix_slot(t, d.s)
|
||||
if g_ix_keys[t][h] == null { # the first of a name wins, as the walk had it
|
||||
g_ix_keys[t][h] = d.s
|
||||
g_ix_vals[t][h] = d
|
||||
}
|
||||
}
|
||||
g_ix_upto += 1
|
||||
}
|
||||
}
|
||||
# the node of kind t called name, or null; -1 when the table cannot say (off, or a stale hit)
|
||||
var g_ix_miss: bool = false
|
||||
function ix_find(t: int, name: pointer) -> Node {
|
||||
g_ix_miss = false
|
||||
if not g_ix_on or name == null {
|
||||
g_ix_miss = true
|
||||
return null
|
||||
}
|
||||
ix_sync()
|
||||
let h = ix_slot(t, name)
|
||||
let p = g_ix_vals[t][h]
|
||||
if p == null { return null }
|
||||
let d: Node = p
|
||||
if (d.s == name) and ix_kind(d) == t { return d }
|
||||
g_ix_miss = true
|
||||
return null
|
||||
}
|
||||
function find_comp(name: pointer) -> Node {
|
||||
let x = ix_find(2, name)
|
||||
if not g_ix_miss { return x }
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and (d.s == name) { return d }; i += 1 }
|
||||
return null
|
||||
|
|
@ -174,6 +261,8 @@ function field_type(s: Node, fname: pointer) -> pointer {
|
|||
|
||||
# find a global var/const by name
|
||||
function find_global(name: pointer) -> Node {
|
||||
let x = ix_find(3, name)
|
||||
if not g_ix_miss { return x }
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
|
|
@ -188,6 +277,14 @@ function find_global(name: pointer) -> Node {
|
|||
# enum with that variant. Enum names live in `prog` like any other declaration.
|
||||
function enum_ordinal(ename: pointer, vname: pointer) -> int {
|
||||
var i = 0
|
||||
if g_ix_on { # the table knows the one enum of that name
|
||||
let d = find_enum(ename)
|
||||
if d != null {
|
||||
var j = 0
|
||||
while j < len(d.kids) { if (d.kids[j].s == vname) { return j }; j += 1 }
|
||||
}
|
||||
i = len(prog)
|
||||
}
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_ENUM and (d.s == ename) {
|
||||
|
|
@ -234,6 +331,8 @@ function has_countdowns() -> bool {
|
|||
return false
|
||||
}
|
||||
function find_fn(name: pointer) -> Node {
|
||||
let x = ix_find(0, name)
|
||||
if not g_ix_miss { return x }
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_FN and (d.s == name) { return d }; i += 1 }
|
||||
return null
|
||||
|
|
@ -248,6 +347,8 @@ function find_fn(name: pointer) -> Node {
|
|||
var g_var_ord: int = 0
|
||||
|
||||
function find_enum(name: pointer) -> Node {
|
||||
let x = ix_find(1, name)
|
||||
if not g_ix_miss { return x }
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_ENUM and (d.s == name) { return d }; i += 1 }
|
||||
return null
|
||||
|
|
@ -276,6 +377,16 @@ function variant_ordinal(en: Node, vname: pointer) -> int {
|
|||
# Only tagged enums participate, so a plain enum's `Enum.Variant` member form and
|
||||
# any like-named function are left alone.
|
||||
function variant_enum(vname: pointer) -> Node {
|
||||
if g_ix_on { # the tagged enums, kept as prog grows
|
||||
ix_sync()
|
||||
var t = 0
|
||||
while t < len(g_ix_tagged) {
|
||||
let ord = variant_ordinal(g_ix_tagged[t], vname)
|
||||
if ord >= 0 { g_var_ord = ord; return g_ix_tagged[t] }
|
||||
t += 1
|
||||
}
|
||||
return null
|
||||
}
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
|
|
@ -306,6 +417,8 @@ function enum_box_size(en: Node) -> int { return 8 * (1 + enum_max_arity(en)) }
|
|||
# is the transport seam (net_send/net_poll), the windowing/socket FFI, and any
|
||||
# C/Rust/Zig library binding — the same seam NETWORKING-DESIGN §5 names.
|
||||
function find_extern(name: pointer) -> Node {
|
||||
let x = ix_find(4, name)
|
||||
if not g_ix_miss { return x }
|
||||
var i = 0
|
||||
while i < len(prog) { let d = prog[i]; if d.kind == N_EXTERN and (d.s == name) { return d }; i += 1 }
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -213,19 +213,19 @@ function emit_test_runner() -> void {
|
|||
# two `function`s with one name would collide in the object file; say so in
|
||||
# source terms (and name both files) instead of leaving it to the IR assembler.
|
||||
function check_duplicate_fns() -> void {
|
||||
let k = new []pointer # by name, in a table: a pair of loops was quadratic
|
||||
let v = new []Node
|
||||
ck_tab_init(k, v)
|
||||
var i = 0
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_FN {
|
||||
var j = i + 1
|
||||
while j < len(prog) {
|
||||
let o = prog[j]
|
||||
if o.kind == N_FN and (o.s == d.s) {
|
||||
g_err_file = o.file; g_err_line = o.line
|
||||
perr(`function '{d.s}' is defined twice (first in {d.file}:{itoa(d.line)})`)
|
||||
}
|
||||
j += 1
|
||||
let first = ck_tab_get(k, v, d.s)
|
||||
if first != null {
|
||||
g_err_file = d.file; g_err_line = d.line
|
||||
perr(`function '{d.s}' is defined twice (first in {first.file}:{itoa(first.line)})`)
|
||||
}
|
||||
ck_tab_put(k, v, d.s, d)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
|
@ -240,22 +240,23 @@ function decl_group(k: int) -> int {
|
|||
return 0
|
||||
}
|
||||
function check_duplicate_decls() -> void {
|
||||
let k = new []pointer
|
||||
let v = new []Node
|
||||
ck_tab_init(k, v)
|
||||
let n = g_prog_user_end
|
||||
var i = 0
|
||||
while i < n and i < len(prog) {
|
||||
let d = prog[i]
|
||||
let g = decl_group(d.kind)
|
||||
if g > 0 {
|
||||
var j = i + 1
|
||||
while j < n and j < len(prog) {
|
||||
let o = prog[j]
|
||||
if decl_group(o.kind) == g and (o.s == d.s) {
|
||||
g_err_file = o.file
|
||||
g_err_line = o.line
|
||||
perr(`'{d.s}' is defined twice (first in {d.file}:{itoa(d.line)})`)
|
||||
}
|
||||
j += 1
|
||||
let key = `{itoa(g)}:{d.s}`
|
||||
let first = ck_tab_get(k, v, key)
|
||||
if first != null {
|
||||
g_err_file = d.file
|
||||
g_err_line = d.line
|
||||
perr(`'{d.s}' is defined twice (first in {first.file}:{itoa(first.line)})`)
|
||||
}
|
||||
ck_tab_put(k, v, key, d)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
|
@ -264,6 +265,7 @@ function check_duplicate_decls() -> void {
|
|||
function emit_program() -> void {
|
||||
check_duplicate_fns()
|
||||
check_duplicate_decls()
|
||||
ix_start() # the lookups by name, as tables from here on
|
||||
head = buf_new()
|
||||
code = buf_new()
|
||||
g_uses_str = false
|
||||
|
|
|
|||
|
|
@ -29,17 +29,29 @@ function vis_plain(what: pointer) -> pointer {
|
|||
function vis_say(m: pointer) -> void {
|
||||
file_write(file_stderr(), m, len(m))
|
||||
}
|
||||
# does s start with p - without making the slice a comparison of s[0 .. n] would (asked for every
|
||||
# reference)
|
||||
function str_prefix(s: pointer, p: pointer) -> bool {
|
||||
if s == null { return false }
|
||||
let n = len(p)
|
||||
if len(s) < n { return false }
|
||||
var i = 0
|
||||
while i < n {
|
||||
if s[i] != p[i] { return false }
|
||||
i += 1
|
||||
}
|
||||
return true
|
||||
}
|
||||
function vis_check(d: Node, what0: pointer) -> void {
|
||||
if d == null { return }
|
||||
port_check_bound(d)
|
||||
# 0.S/0.R: a state's instance, the action queue and a reducer are the runtime's to supply and to
|
||||
# call - the code that names them depends on no module for it
|
||||
if d.kind == N_VAR and d.uns == 1 and str_prefix(d.s, "state$") and is_state_ty(d.ty) { return }
|
||||
if d.kind == N_FN and (str_prefix(d.s, "ludic_reduce__") or is_action_builtin(d.s)) { return }
|
||||
deps_edge(d, what0) # LUDIC_DEPS: the graph ludic deps reads (emit_deps.ludic)
|
||||
if g_vis_off { return }
|
||||
if d.file == null { return }
|
||||
# 0.S: a state's instance, supplied by the runtime (a component's or a view's glue, an entry's
|
||||
# parameters): the code that names it only passes it on
|
||||
if d.kind == N_VAR and d.uns == 1 and is_state_ty(d.ty) and len(d.s) > 6 and (d.s[0 .. 6] == "state$") { return }
|
||||
# 0.R: the action queue and the reducers are the runtime's to call, from any module
|
||||
if d.kind == N_FN and (is_action_builtin(d.s) or (len(d.s) > 14 and (d.s[0 .. 14] == "ludic_reduce__"))) { return }
|
||||
let what = vis_plain(what0)
|
||||
var here = g_err_file
|
||||
if here == null { return }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue