///|
#valtype
priv struct DaeUniformSyntaxDiscoveryTarget {
def_idx : Int32
callee_abs : Int32
param_count : Int32
local_use : DaeLocalUse
}
///|
/// Syntax-only admission for one fixed-module discovery query. A recursive
/// uniform query follows a direct-caller edge, so include every ancestor.
/// Do not prune at an escape, tail call or cycle: this is a conservative set.
fn dae_uniform_syntax_discovery_target_closure(
targets : Array[DaeUniformSyntaxDiscoveryTarget],
facts : DaeCallBoundaryFacts,
imported : Int32,
defined : Int32,
) -> @hashmap.HashMap[Int32, Bool] {
let admitted : @hashmap.HashMap[Int32, Bool] = @hashmap.new()
let pending : Array[Int32] = []
for target in targets {
if !admitted.contains(target.callee_abs) {
admitted.set(target.callee_abs, true)
pending.push(target.callee_abs)
}
}
let mut next = 0
while next < pending.length() {
let callee_abs = pending[next]
next += 1
for def_idx in dae_call_boundary_direct_callers(facts, callee_abs) {
if def_idx >= 0 && def_idx < defined {
let caller_abs = imported + def_idx
if !admitted.contains(caller_abs) {
admitted.set(caller_abs, true)
pending.push(caller_abs)
}
}
}
}
admitted
}
///|
fn dae_uniform_syntax_target_admitted(
query : DaeUniformSyntaxQuery,
callee_abs : Int32,
) -> Bool {
match query.targets {
Some(targets) => targets.contains(callee_abs)
None => true
}
}
///|
/// Count raw syntax only, saturating before unbounded child traversal. This
/// does not resolve control types, catch payloads or semantic candidates.
fn dae_uniform_syntax_bounded_nodes(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Int32 {
if limit <= 0 {
return 0
}
if instrs.length() >= limit {
return limit
}
let mut nodes = instrs.length()
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
nodes += dae_uniform_syntax_bounded_nodes(body, limit - nodes)
@lib.If(_, then_body, else_body) => {
nodes += dae_uniform_syntax_bounded_nodes(then_body, limit - nodes)
match else_body {
Some(body) =>
nodes += dae_uniform_syntax_bounded_nodes(body, limit - nodes)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
nodes += dae_uniform_syntax_bounded_nodes(body, limit - nodes)
for catch_ in catches {
if nodes >= limit {
break
}
// Empty catch rows still cost traversal; include them in the cap.
nodes += 1
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
nodes += dae_uniform_syntax_bounded_nodes(body, limit - nodes)
}
}
_ => continue
}
if nodes >= limit {
return limit
}
}
nodes
}
///|
/// Targets are emitted in numeric definition order by this pure discovery.
/// Defer an ancestor to its own existing iteration only when its hint tier
/// is at least as permissive. This borrows the target array and allocates
/// no visited set, map, candidate cache, or snapshot.
fn dae_uniform_syntax_ancestor_has_target_owner(
targets : Array[DaeUniformSyntaxDiscoveryTarget],
def_idx : Int32,
wide_readonly : Bool,
) -> Bool {
let mut low = 0
let mut high = targets.length()
while low < high {
let middle = low + (high - low) / 2
let middle_def = targets[middle].def_idx
if middle_def < def_idx {
low = middle + 1
} else if middle_def > def_idx {
high = middle
} else {
if !wide_readonly {
return true
}
let target = targets[middle]
let mut read_only_count = 0
if target.param_count >= 32 {
for p in 0..= 32 {
return true
}
}
}
}
return false
}
}
false
}
///|
/// Return remaining caller visits; -1 retains shared admission. Flat scopes
/// use their borrowed length first. Only shorter scopes need a bounded raw
/// control-size hint. No candidate, catch, operand or visited state is cached.
fn dae_uniform_syntax_caller_work_hint(
targets : Array[DaeUniformSyntaxDiscoveryTarget],
callee_abs : Int32,
wide_readonly : Bool,
funcs : Array[@lib.Func],
facts : DaeCallBoundaryFacts,
imported : Int32,
budget : Int32,
depth : Int32,
) -> Int32 {
if budget <= 0 || depth >= 8 {
// Uncertain/deep graphs retain the previous shared route.
return -1
}
let mut remaining = budget
for caller_def in dae_call_boundary_direct_callers(facts, callee_abs) {
if remaining <= 0 || caller_def < 0 || caller_def >= funcs.length() {
return -1
}
remaining -= 1
let caller_abs = imported + caller_def
let forwards = dae_call_boundary_allows_forwarding(facts, caller_abs)
// Raw51 baseline: pairs128 wins; pairs16/32/64 do not. A separate
// high-readonly tier covers the modest width32 forwarding win; widths
// 2/4/8/16 do not win. These are candidate gates, not optimal cutoffs.
let node_limit = if wide_readonly && forwards { 64 } else { 256 }
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if body.length() >= node_limit ||
dae_uniform_syntax_bounded_nodes(body, node_limit) >= node_limit {
return -1
}
// Semantic forwarding rejects escaped/tail sources before any upstream
// syntax query. An ineligible private forwarder must still be followed.
if forwards &&
!dae_uniform_syntax_ancestor_has_target_owner(
targets, caller_def, wide_readonly,
) {
remaining = dae_uniform_syntax_caller_work_hint(
targets,
caller_abs,
wide_readonly,
funcs,
facts,
imported,
remaining,
depth + 1,
)
if remaining < 0 {
return -1
}
}
}
remaining
}
///|
fn dae_uniform_syntax_shared_work_admitted(
targets : Array[DaeUniformSyntaxDiscoveryTarget],
funcs : Array[@lib.Func],
facts : DaeCallBoundaryFacts,
imported : Int32,
) -> Bool {
if targets.length() < 2 {
return false
}
let mut remaining = 32
for target in targets {
let mut read_only_count = 0
if target.param_count >= 32 {
for p in 0..= 32 {
break
}
}
}
}
remaining = dae_uniform_syntax_caller_work_hint(
targets,
target.callee_abs,
read_only_count >= 32,
funcs,
facts,
imported,
remaining,
0,
)
if remaining <= 0 {
return true
}
}
false
}