pub fn synthesize_phase_scope_bindings(
phase: &WorkloadPhase,
) -> Result<BindingsDef, String>Expand description
Build the per-scope Polydat Kernel for a do_while / do_until
node (SRD 18b). Same composition contract as for_each
(every name visible at this scope resolves through standard
Polydat API on the synthesized kernel) — the difference is the
“scope output” is a counter: u64 rather than tuple
iteration variables, and there’s no value list to pre-eval.
Source shape:
extern <inherited_name>: <type> # one per name
# referenced in
# `condition` text
# that exists in
# the parent
# manifest or
# workload params
final <inherited_param> := <literal> # workload-param
# injection (M3.3
# bridge until M3.6)
extern <counter>: u64 # only when counter
# is `Some`Per iteration the runtime sets counter (if present) via
kernel.state().set_input and evaluates the condition
expression against the kernel via interpolate_via_kernel +
eval_const_expr. Children inherit counter (and any
inherited names) through standard materialize_wiring_from_outer.
Synthesize the Polydat scope kernel for a phase that carries its own
bindings: block.
The phase scope owns this kernel as part of its closure lifetime
(per the Polydat builder/walk/instancing protocol): a phase whose YAML
declared bindings: | produces matter that the parent kernel’s
builds a child scope from (ScopeKernel::build_under). Op-template
scopes that descend from this phase find these bindings as
outputs of their parent kernel and extern them through the
standard manifest cascade.
Phases without bindings AND without for_each: produce no install
spec — their scope-tree node carries an empty cached_kernel and
the parent walker resolves through to the nearest ancestor with a
kernel. The closure invariant (“every scope has a kernel
reference”) still holds; the reference is just the parent’s,
matter-gated as the Polydat APIs prescribe.
Source emitted (in order):
final <workload_param> := <literal> # cascaded params
extern <ancestor_output>: <type> # cascaded outputs/inputs
<phase_bindings_body> # phase-declared bindingsThe phase’s bindings body is appended verbatim so the Polydat compiler
classifies them per the same rules as op-level bindings (init /
shared / final detection, type inference). Iteration coordinate
(cycle) cascades from the parent — we never re-declare it here.
Compose a phase scope’s bindings source with SRD-75
phase-poll augmentation and/or phase-level metrics:
augmentation when applicable. Returns the existing
BindingsDef unchanged when the phase has neither poll:
nor metrics:; otherwise produces a
BindingsDef::PolydatSource that, for the poll case:
- Prepends
shared <name>: u64 := 0for each capture name declared by the phase’s ops. The shared-cell modifier means TraversingDispenser writes viactx.wires.writepropagate to the phase scope kernel (SRD-13c §“Implementation: SharedCell-backed input slots” §4 “Write through”); same cell is observable to other ops’if:reads in the same iteration AND to the__poll_untilpredicate evaluated on the phase kernel. - Appends the original
phase.bindingsbody verbatim (the author’s bindings shadow synthesized captures by ident if any name collides — same shadowing rule as anywhere else in GK; we err out in that case for clarity). - Appends
__poll_until := <until>as a regular cycle binding (dynamic lifecycle per SRD-11 §“Two Evaluation Lifecycles” — re-evaluates per pull as capture cells update).
For the metrics case it appends an extern phase_start: u64 = 0 origin wire (set by the executor at the completion-time
pull) and one volatile __metric_<name> := <value> per
declared phase metric. volatile on the __metric_<name>
binding excludes it from const-fold identity; a value that
reads a clock should declare that read as its own volatile
phase binding (e.g. volatile now_ms := current_epoch_millis(),
metric value: now_ms - phase_start) so the non-deterministic
node is acknowledged directly and the kernel compiles under
--strict. The executor pulls each __metric_<name> once at
phase completion and records it on the phase component.
Type inference is intentionally narrow in the
initial ship: every capture lands as u64 (the
shape the canonical synchronizer-pattern workload
uses — :count reductions and numeric Jolokia
attribute reads). Non-numeric captures aren’t
supported in the phase-poll predicate; the
workload author falls back to a different pattern.
SRD-75 §“Open questions: Capture-type inference
granularity” tracks the refinement.