///|
/// Dependencies belong to one checked source in one callback-free core run.
priv struct DaeScalarSyntaxIndex {
imported : Int32
rows : Array[Array[Int32]]
users : Array[Array[Int32]]
declared : Array[Bool]
seen : Array[Int32]
selected : Array[Bool]
}
///|
fn dae_scalar_mark_declaration(
declared : Array[Bool],
target : @lib.FuncIdx,
) -> Unit {
let idx = target.0.reinterpret_as_int()
if idx >= 0 && idx < declared.length() {
declared[idx] = true
}
}
///|
fn dae_scalar_mark_expr_declarations(
instrs : Array[@lib.Instruction],
declared : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.RefFunc(target) => dae_scalar_mark_declaration(declared, target)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_scalar_mark_expr_declarations(body, declared)
@lib.If(_, left, right) => {
dae_scalar_mark_expr_declarations(left, declared)
match right {
Some(body) => dae_scalar_mark_expr_declarations(body, declared)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_scalar_mark_expr_declarations(body, declared)
for catch_ in catches {
let expr = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
dae_scalar_mark_expr_declarations(expr.0, declared)
}
}
_ => ()
}
}
}
///|
/// Match the generic validator's declaration sources, excluding start.
fn dae_scalar_declared_funcs(
source : @lib.Module,
total : Int32,
) -> Array[Bool] {
let declared = Array::make(total, false)
match source.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.FuncExternIdx(target)) =>
dae_scalar_mark_declaration(declared, target)
_ => ()
}
}
None => ()
}
match source.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
let @lib.Global(_, expr) = global
dae_scalar_mark_expr_declarations(expr.0, declared)
}
None => ()
}
match source.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
let @lib.Table(_, init) = table
match init {
Some(expr) => dae_scalar_mark_expr_declarations(expr.0, declared)
None => ()
}
}
None => ()
}
match source.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(_, kind) = elem
match kind {
@lib.FuncsElemKind(targets) =>
for target in targets {
dae_scalar_mark_declaration(declared, target)
}
@lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
for expr in exprs {
dae_scalar_mark_expr_declarations(expr.0, declared)
}
}
}
None => ()
}
declared
}
///|
/// Visit every scope, including inactive suffixes and all legacy catches.
fn dae_scalar_scan_dependencies(
instrs : Array[@lib.Instruction],
index : DaeScalarSyntaxIndex,
has_data_count : Bool,
out : Array[Int32],
) -> Bool {
for instr in instrs {
match instr {
@lib.Call(target) | @lib.ReturnCall(target) | @lib.RefFunc(target) => {
let abs = target.0.reinterpret_as_int()
if abs < 0 || abs >= index.declared.length() {
return false
}
match instr {
@lib.RefFunc(_) if !index.declared[abs] => return false
_ => ()
}
let def = abs - index.imported
if def >= 0 && index.seen[def] == 0 {
index.seen[def] = 1
out.push(def)
}
}
@lib.MemoryInit(_, _) | @lib.DataDrop(_) if !has_data_count =>
return false
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if !dae_scalar_scan_dependencies(body, index, has_data_count, out) {
return false
}
@lib.If(_, left, right) => {
if !dae_scalar_scan_dependencies(left, index, has_data_count, out) {
return false
}
match right {
Some(body) =>
if !dae_scalar_scan_dependencies(body, index, has_data_count, out) {
return false
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if !dae_scalar_scan_dependencies(body, index, has_data_count, out) {
return false
}
for catch_ in catches {
let expr = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if !dae_scalar_scan_dependencies(expr.0, index, has_data_count, out) {
return false
}
}
}
_ => ()
}
}
true
}
///|
fn dae_scalar_dependency_row(
func : @lib.Func,
index : DaeScalarSyntaxIndex,
has_data_count : Bool,
) -> Array[Int32]? {
let @lib.Func(_, @lib.Expr(body)) = func
let out : Array[Int32] = []
let ok = dae_scalar_scan_dependencies(body, index, has_data_count, out)
for target in out {
index.seen[target] = 0
}
if ok {
Some(out)
} else {
None
}
}
///|
/// Empty buckets may be shared. Never push into a shared empty bucket.
fn dae_scalar_add_user(
index : DaeScalarSyntaxIndex,
target : Int32,
caller : Int32,
) -> Unit {
let users = index.users[target]
if users.is_empty() {
index.users[target] = [caller]
} else {
users.push(caller)
}
}
///|
fn dae_scalar_remove_user(
index : DaeScalarSyntaxIndex,
target : Int32,
caller : Int32,
work~ : DaeScalarValidationWork? = None,
) -> Unit {
let users = index.users[target]
for slot in 0.. {
counts.reverse_comparisons += slot + 1
counts.removed_dependencies += 1
}
None => ()
}
let last = users.pop().unwrap()
if slot < users.length() {
users[slot] = last
}
return
}
}
}
///|
/// Update only edge differences; unchanged edges need no reverse-bucket scan.
fn dae_scalar_replace_dependency_row(
index : DaeScalarSyntaxIndex,
caller : Int32,
next : Array[Int32],
work~ : DaeScalarValidationWork? = None,
) -> Unit {
let old = index.rows[caller]
for target in old {
index.seen[target] = 1
}
for target in next {
index.seen[target] += 2
}
for target in old {
if index.seen[target] == 1 {
dae_scalar_remove_user(index, target, caller, work~)
}
}
for target in next {
if index.seen[target] == 2 {
dae_scalar_add_user(index, target, caller)
}
}
for target in old {
index.seen[target] = 0
}
for target in next {
index.seen[target] = 0
}
index.rows[caller] = next
}
///|
/// Called lazily after a second candidate passes the module/type envelope.
fn dae_scalar_syntax_index(source : @lib.Module) -> DaeScalarSyntaxIndex? {
let funcs = source.code_sec.unwrap().0
let defined = funcs.length()
let imported = dae_imported_func_count(source)
let index = {
imported,
rows: Array::make(defined, []),
users: Array::make(defined, []),
declared: dae_scalar_declared_funcs(source, imported + defined),
seen: Array::make(defined, 0),
selected: Array::make(defined, false),
}
for caller in 0.. row
None => return None
}
index.rows[caller] = row
for target in row {
dae_scalar_add_user(index, target, caller)
}
}
Some(index)
}