Skip to main content

synthesize_phase_scope_bindings

Function synthesize_phase_scope_bindings 

Source
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 bindings

The 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 := 0 for each capture name declared by the phase’s ops. The shared-cell modifier means TraversingDispenser writes via ctx.wires.write propagate 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_until predicate evaluated on the phase kernel.
  • Appends the original phase.bindings body 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.