pub struct ScopeTree {
pub nodes: Vec<ScopeNode>,
pub root: ScopeNodeIdx,
}Expand description
A workload’s runtime scope hierarchy. Built once per session.
Stable indices into nodes; parent / child pointers are
ScopeNodeIdx. Use the helpers on this struct for traversal
— direct nodes access is fine for read-only inspection but
the navigation helpers are easier to read.
Fields§
§nodes: Vec<ScopeNode>§root: ScopeNodeIdxImplementations§
Source§impl ScopeTree
impl ScopeTree
Sourcepub fn build(scenario_name: &str, nodes: &[ScenarioNode]) -> Self
pub fn build(scenario_name: &str, nodes: &[ScenarioNode]) -> Self
Build a scope tree from the resolved scenario children.
scenario_name becomes the named ScopeKind::Scenario
that wraps nodes — the user’s authored grouping is
preserved as a real ancestor, restoring “phase P in
scenario default” as a path query.
Sourcepub fn workload_root_idx(&self) -> ScopeNodeIdx
pub fn workload_root_idx(&self) -> ScopeNodeIdx
The workload-root node — the single child of the session root (node 0). One per execution; owns the outer workload kernel. Falls back to the root if (degenerately) there is no workload layer.
Sourcepub fn scenario_root_idx(&self) -> ScopeNodeIdx
pub fn scenario_root_idx(&self) -> ScopeNodeIdx
The scenario-layer node — the single child of the workload root
(node 0). The walker seeds its scope cursor here so the top-level
scenario nodes resolve positionally against this node’s children
(one scope-tree child per scenario node, in order — see
Self::append_subtree). Falls back to the root if (degenerately)
there is no scenario layer.
Sourcepub fn extend_with_op_templates(
&mut self,
phases: &HashMap<String, WorkloadPhase>,
)
pub fn extend_with_op_templates( &mut self, phases: &HashMap<String, WorkloadPhase>, )
SRD-13d Phase 6 — extend every Phase scope node with
OpTemplate children (one per op declared in the
phase). Two-step build: ScopeTree::build produces
the scenario-shaped skeleton (phases as leaves);
this method adds the op-template tier on top by
consulting the workload’s per-phase WorkloadPhase
records.
Idempotent: a phase whose OpTemplate children are
already present is left alone (the post-build pre-walk
can run before or after this without double-adding).
Run before mark_scope_elision so the per-op
classification gets the chance to elide / materialise
each op-template tier.
Sourcepub fn mark_scope_elision<F>(&mut self, is_materialising: F)
pub fn mark_scope_elision<F>(&mut self, is_materialising: F)
SRD-13d §3.3 — pre-walk every scope-tree node and mark
it materialised (own kernel) or elided (descendants
bind through parent). Also assigns the SRD-13d §5.3
logical kernel name, which is the fully-qualified
scope-tree path. Run once at workload-load; premap and
runtime read the marks afterward.
is_materialising is the predicate the pre-walk
applies per node — typically a closure that consults
the AST node’s HasPolydatMatter classification (None /
Readonly ⇒ elide; Definitions ⇒ check program-hash
equivalence with the parent and decide). The walker is
agnostic to the exact predicate; SRD-13d §3.3 fixes
the order.
The workload root is always materialised (see SRD-13d §5.1) so the walk terminates at a materialised ancestor regardless of how aggressively descendants elide.
Sourcepub fn nearest_materialised(&self, idx: ScopeNodeIdx) -> Option<ScopeNodeIdx>
pub fn nearest_materialised(&self, idx: ScopeNodeIdx) -> Option<ScopeNodeIdx>
SRD-13d §5.1 — walk past elided scope tiers to the nearest materialised ancestor (or self, when this node is itself materialised). Every consumer that needs a kernel handle (cache lookups, bind-outer- scope, diagnostics) routes through this — it’s the single point that knows about elision; nothing else does.
The workload root is always materialised, so this
always terminates with Some(idx). Returns None
only if [mark_scope_elision] hasn’t been run.
Sourcepub fn iter_dfs(&self) -> DfsIter<'_> ⓘ
pub fn iter_dfs(&self) -> DfsIter<'_> ⓘ
Iterate every scope node in depth-first pre-order. The scheduler’s default walk and the canonical display linearisation both consume this.
Sourcepub fn ancestors(&self, idx: ScopeNodeIdx) -> AncestorsIter<'_> ⓘ
pub fn ancestors(&self, idx: ScopeNodeIdx) -> AncestorsIter<'_> ⓘ
Walk from idx up through its ancestors to the root,
inclusive of idx itself. Use this to compute effective
pragmas or to render a path label.
Sourcepub fn phase_node_by_name(&self, name: &str) -> Option<ScopeNodeIdx>
pub fn phase_node_by_name(&self, name: &str) -> Option<ScopeNodeIdx>
First scope-tree node whose kind is Phase { name }
matching the given name. Returns None if the scenario
doesn’t reference this phase. When a single phase is
invoked from multiple scenario sites (rare; most workloads
reference a phase exactly once), this returns the first
occurrence in depth-first order — sufficient for current
callers, who use the result to fetch the chain-walked
PragmaSet.
Sourcepub fn op_template_programs_for_phase(
&self,
phase_idx: ScopeNodeIdx,
) -> HashMap<String, Arc<PolydatProgram>>
pub fn op_template_programs_for_phase( &self, phase_idx: ScopeNodeIdx, ) -> HashMap<String, Arc<PolydatProgram>>
Op-template kernel programs for every materialised
op-template that’s a child of phase_idx. Keyed by the
op’s name. Used by the executor to thread per-op-template
programs into the activity so each MetricsDispenser
builds its ScopeFixture against the correct scope
(SRD-13d Phase 9 §“per-dispenser kernel instancing”).
Flattened op-templates (materialised != Some(true)) are
omitted from the map; their dispensers reach the parent
kernel through the standard nearest_materialised
fall-through.
Rule 2 write-through bindings ride on the program itself
(baked in by the SRD-67 builder’s finalize step). Any
kernel built from the program inherits them automatically
when bound (ScopeKernel::bind_under) — no side channel.
Sourcepub fn op_template_modules_for_phase(
&self,
phase_idx: ScopeNodeIdx,
) -> Vec<(String, Arc<OpTemplateModule>)>
pub fn op_template_modules_for_phase( &self, phase_idx: ScopeNodeIdx, ) -> Vec<(String, Arc<OpTemplateModule>)>
The op-template scope modules of phase_idx’s materialised
op-template children, keyed by op name: what each fiber
instantiates its per-op kernels from on the fiber engine
(crate::fiber_engine).
Sourcepub fn phase_leaves(&self) -> Vec<ScopeNodeIdx> ⓘ
pub fn phase_leaves(&self) -> Vec<ScopeNodeIdx> ⓘ
All phase-leaf indices in depth-first order. Equivalent
to filtering iter_dfs() to ScopeKind::Phase — the
helper exists because it’s the most common consumer
query (TUI tree pre-mapping, dryrun=phase).
Sourcepub fn nearest_installed_ancestor_kernel(
&self,
idx: ScopeNodeIdx,
) -> Option<Arc<ScopeKernel>>
pub fn nearest_installed_ancestor_kernel( &self, idx: ScopeNodeIdx, ) -> Option<Arc<ScopeKernel>>
Walk ancestors of idx looking for the nearest scope
node that has a kernel installed. Used at routing time
to find the kernel a for_each scope’s materialize_wiring_from_outer
should chain from. Workload root always has a kernel
installed (per M3.1), so this never returns None for
any descendant of the root.
Sourcepub fn ancestor_kernels(&self, idx: ScopeNodeIdx) -> Vec<Arc<ScopeKernel>>
pub fn ancestor_kernels(&self, idx: ScopeNodeIdx) -> Vec<Arc<ScopeKernel>>
Collect every installed ancestor kernel of idx,
innermost first (immediate parent → workload root).
Skips ancestor levels whose cached_kernel is empty
(intermediate nodes that don’t own their own kernel).
Used by the checkpoint identity path to feed
polydat::kernel::PolydatProgram::instance_hash
(SRD-44 §“Identity matching at resume” + project
memory program_vs_instance_hash).
Sourcepub fn ancestor_kernels_split(
&self,
idx: ScopeNodeIdx,
) -> (Vec<Arc<ScopeKernel>>, Option<Arc<ScopeKernel>>)
pub fn ancestor_kernels_split( &self, idx: ScopeNodeIdx, ) -> (Vec<Arc<ScopeKernel>>, Option<Arc<ScopeKernel>>)
Self::ancestor_kernels split at the session boundary
(SRD-107): (below, session) where below is every
installed ancestor kernel from the immediate parent up
through the workload root, and session is the
session-node kernel (the workload-params module) when one
is installed. The provenance base hash covers below
only; param values are covered per-phase by the
consumed-params digest instead.
Sourcepub fn find_comprehension_scope(
&self,
comprehension: &Comprehension,
) -> Option<ScopeNodeIdx>
pub fn find_comprehension_scope( &self, comprehension: &Comprehension, ) -> Option<ScopeNodeIdx>
Find a Comprehension scope by structural-equality match
against its Comprehension AST. Returns the first
DFS-pre-order match.
Sourcepub fn find_bindings_scope(&self, source: &str) -> Option<ScopeNodeIdx>
pub fn find_bindings_scope(&self, source: &str) -> Option<ScopeNodeIdx>
First scope-tree node whose kind is
ScopeKind::Bindings { source } matching exactly,
searched globally from root in DFS pre-order.
Prefer Self::find_bindings_scope_under when the
executor’s current scope position is known — see that
method’s doc for why a global content-only lookup is
currently unsafe.
Sourcepub fn find_bindings_scope_under(
&self,
parent: ScopeNodeIdx,
source: &str,
) -> Option<ScopeNodeIdx>
pub fn find_bindings_scope_under( &self, parent: ScopeNodeIdx, source: &str, ) -> Option<ScopeNodeIdx>
TRANSITIONAL WORKAROUND — see task #19 for the
canonical end-state plan. Constrains the lookup of a
Bindings scope to descendants of parent so that two
Bindings nodes sharing source text at different scope-
tree positions resolve to the right one based on the
executor’s current position.
The deeper problem: today the AST/source we use as the lookup key is LOSSY — two scope-tree nodes that produce semantically-distinct installed kernels (different cascaded externs from different parent chains) can share AST/source. Per SRD-13d §“Op-template scope synthesis” + SRD-13f §“The read invariant”, installed kernels are determined by their PARENT chain, not by their own content alone. The current scope-aware lookup adds the missing context (parent subtree) at the call site to disambiguate.
Future direction: make the AST/source self-
identifying so that semantically-distinct kernels never
share matter (embed parent-chain signature, encode
scenario-tree path, or some equivalent invariant). Once
that lands, content-only find_bindings_scope is
correct again and this _under variant can be retired.
Sourcepub fn find_comprehension_scope_under(
&self,
parent: ScopeNodeIdx,
comprehension: &Comprehension,
) -> Option<ScopeNodeIdx>
pub fn find_comprehension_scope_under( &self, parent: ScopeNodeIdx, comprehension: &Comprehension, ) -> Option<ScopeNodeIdx>
TRANSITIONAL WORKAROUND — see
Self::find_bindings_scope_under for the underlying
principle and task #19 for the canonical end-state plan.
Retired once the AST becomes self-identifying.
Sourcepub fn validate_iter_var_uniqueness(
&self,
workload_params: &HashSet<String>,
) -> Result<(), String>
pub fn validate_iter_var_uniqueness( &self, workload_params: &HashSet<String>, ) -> Result<(), String>
Validate iteration-variable name uniqueness against the surrounding scope chain.
An iter-var name (for_each: "X in ...",
for_combinations: "X in ..., Y in ...",
for_each_union: ..., do-loop counters) must not
shadow:
- a workload param,
- an iter var declared by an enclosing scope.
Aliasing creates a name that can’t unambiguously resolve
at spec-evaluation time (the iter var is being defined
from a value that uses the same name; the runtime can’t
tell whether {X} means the iter var or the shadowed
outer name). Rather than try to disambiguate, the build
rejects it up-front with a clear error so the user
renames the iter var.
Returns Ok(()) if every iter-var name is unique. Returns
Err(...) with the offending name and which kind of
collision (workload param vs ancestor iter var) the user
has on the first violation found.
Sourcepub fn install_kernel(
&self,
scope_idx: ScopeNodeIdx,
kernel: Arc<ScopeKernel>,
) -> bool
pub fn install_kernel( &self, scope_idx: ScopeNodeIdx, kernel: Arc<ScopeKernel>, ) -> bool
Install the canonical compiled kernel for scope_idx.
Called at pre-map time after compiling the scope’s
PolydatProgram. Once installed, the kernel is the single
authoritative answer for “what is <name> at this
scope?” — every name visible at this scope (own outputs
plus parent-inherited values bound via
[PolydatKernel::materialize_wiring_from_outer]) resolves through the
standard Polydat API on this one kernel. Callers don’t walk
the scope tree to do name resolution; Polydat’s auto-extern +
outer-scope wiring already encapsulates the layering.
Idempotent only by virtue of OnceLock: a second install
silently no-ops, returning false. Returns true on
fresh install. Callers that need to detect a duplicate
install should check the boolean.
Sourcepub fn populate_pragmas(&mut self, phases: &HashMap<String, WorkloadPhase>)
pub fn populate_pragmas(&mut self, phases: &HashMap<String, WorkloadPhase>)
Populate pragmas on every phase-leaf scope by scanning
each phase’s BindingsDef::PolydatSource strings for pragma
statements, then walk the tree so each scope’s set holds its
ancestors’ pragmas followed by its own, as polydat nests a
scope’s pragmas (PragmaSet::nested). After this call,
node.pragmas.strict_values() answers for every pragma in force
at the node.
SRD 18b §“Pragma chain along the scope tree”. Idempotent
per call (replaces any prior pragmas content).
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for ScopeTree
impl !UnwindSafe for ScopeTree
impl Freeze for ScopeTree
impl Send for ScopeTree
impl Sync for ScopeTree
impl Unpin for ScopeTree
impl UnsafeUnpin for ScopeTree
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more