use std::collections::{HashMap, HashSet};
use crate::scope_synth::{
collect_leaf_placeholders, value_to_param_string, workload_param_type_name,
};
use nmbrs_workload::model::{BindingsDef, ParsedOp};
use polydat::kernel::interp::collect_string_interp_refs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BindingOrigin {
Inherited,
Phase,
Op(String),
IterationVar,
AutoExtern,
ParamExpansion,
InlineExpr,
}
impl std::fmt::Display for BindingOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Inherited => write!(f, "inherited"),
Self::Phase => write!(f, "phase"),
Self::Op(name) => write!(f, "op '{name}'"),
Self::IterationVar => write!(f, "iteration variable"),
Self::AutoExtern => write!(f, "auto-extern"),
Self::ParamExpansion => write!(f, "param expansion"),
Self::InlineExpr => write!(f, "inline expression"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeModifier {
None,
Init,
Shared,
Final,
Volatile,
Cursor,
}
#[derive(Debug, Clone)]
pub struct ScopedBinding {
pub name: String,
pub line: String,
pub origin: BindingOrigin,
pub modifier: ScopeModifier,
}
#[derive(Debug, Clone)]
pub struct ExternDecl {
pub name: String,
pub type_name: String,
}
pub struct BindingScope {
coordinates: Option<String>,
bindings: Vec<ScopedBinding>,
externs: Vec<ExternDecl>,
required_outputs: Vec<String>,
config_refs: Vec<String>,
}
impl Default for BindingScope {
fn default() -> Self {
Self::new()
}
}
impl BindingScope {
pub fn new() -> Self {
Self {
coordinates: None,
bindings: Vec::new(),
externs: Vec::new(),
required_outputs: Vec::new(),
config_refs: Vec::new(),
}
}
pub fn ingest_polydat_source(&mut self, source: &str, origin: BindingOrigin) {
for line in logical_lines(source) {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with("//") || trimmed.starts_with('#') {
continue;
}
if trimmed.starts_with("input ") {
self.coordinates = Some(trimmed.to_string());
continue;
}
if (trimmed.starts_with("cursor ") || trimmed.starts_with("init "))
&& let Some(eq_pos) = trimmed.find('=')
{
let before_eq = &trimmed[..eq_pos];
if !before_eq.ends_with(':') {
let lhs = before_eq.trim();
let (modifier, name) = parse_modifier_and_name(lhs);
self.bindings.push(ScopedBinding {
name: name.to_string(),
line: trimmed.to_string(),
origin: origin.clone(),
modifier,
});
continue;
}
}
if let Some(pos) = trimmed.find(":=") {
let lhs = trimmed[..pos].trim();
if lhs.starts_with("extern") {
continue;
}
let (modifier, name) = parse_modifier_and_name(lhs);
self.bindings.push(ScopedBinding {
name: name.to_string(),
line: trimmed.to_string(),
origin: origin.clone(),
modifier,
});
} else if trimmed.starts_with("extern ") {
if let Some(colon_pos) = trimmed.find(':') {
let name = trimmed["extern ".len()..colon_pos].trim();
let type_name = trimmed[colon_pos + 1..].trim();
self.externs.push(ExternDecl {
name: name.to_string(),
type_name: type_name.to_string(),
});
}
}
}
}
pub fn add_iteration_var(&mut self, name: &str, value: &str) {
let type_name = if value.parse::<u64>().is_ok() {
"u64"
} else if value.parse::<f64>().is_ok() {
"f64"
} else {
"String"
};
self.externs.push(ExternDecl {
name: name.to_string(),
type_name: type_name.to_string(),
});
}
pub fn add_extern(&mut self, name: &str, type_name: &str) {
self.externs.push(ExternDecl {
name: name.to_string(),
type_name: type_name.to_string(),
});
}
pub fn add_param_binding(&mut self, name: &str, value: &str) {
let trimmed = value.trim();
let literal = if trimmed.parse::<u64>().is_ok()
|| trimmed.parse::<f64>().is_ok()
|| trimmed == "true"
|| trimmed == "false"
|| is_polydat_quoted_string(trimmed)
|| is_polydat_array_literal(trimmed)
{
trimmed.to_string()
} else {
let escaped = value.replace('\\', "\\\\").replace('"', "\\\"");
format!("\"{escaped}\"")
};
self.bindings.push(ScopedBinding {
name: name.to_string(),
line: format!("const {name} := {literal}"),
origin: BindingOrigin::ParamExpansion,
modifier: ScopeModifier::Final,
});
}
pub fn add_inline_expr(&mut self, name: &str, expr: &str) {
self.bindings.push(ScopedBinding {
name: name.to_string(),
line: format!("{name} := {expr}"),
origin: BindingOrigin::InlineExpr,
modifier: ScopeModifier::None,
});
}
pub fn add_required_output(&mut self, name: &str) {
if !self.required_outputs.contains(&name.to_string()) {
self.required_outputs.push(name.to_string());
}
}
pub fn add_config_ref(&mut self, name: &str) {
if !self.config_refs.contains(&name.to_string()) {
self.config_refs.push(name.to_string());
}
}
pub fn defined_names(&self) -> HashSet<String> {
self.bindings.iter().map(|b| b.name.clone()).collect()
}
pub fn extern_names(&self) -> HashSet<String> {
self.externs.iter().map(|e| e.name.clone()).collect()
}
pub fn required_outputs(&self) -> Vec<String> {
let mut all = self.required_outputs.clone();
for name in &self.config_refs {
if !all.contains(name) {
all.push(name.clone());
}
}
all
}
pub fn validate(&self) -> Result<(), String> {
let mut owned: HashMap<String, &ScopedBinding> = HashMap::new();
for binding in &self.bindings {
if let Some(prior) = owned.get(&binding.name) {
match (&prior.origin, &binding.origin) {
(BindingOrigin::Inherited, BindingOrigin::Inherited) => {
if prior.line != binding.line {
return Err(format!(
"binding '{}' has conflicting inherited definitions:\n \
first: {}\n second: {}",
binding.name, prior.line, binding.line
));
}
}
(BindingOrigin::Phase, BindingOrigin::Phase) => {
if prior.line != binding.line {
return Err(format!(
"binding '{}' has conflicting phase-level definitions:\n \
first: {}\n second: {}",
binding.name, prior.line, binding.line
));
}
}
(
BindingOrigin::Inherited
| BindingOrigin::Phase
| BindingOrigin::IterationVar,
BindingOrigin::Op(op_name),
) => {
if prior.line != binding.line {
return Err(format!(
"op '{}' binding '{}' shadows a name from {} origin \
with a different definition.\n \
scope: {}\n op: {}\n\
Ops augment the scope DAG but cannot override it. \
Use a separate phase for different bindings.",
op_name, binding.name, prior.origin, prior.line, binding.line
));
}
}
(BindingOrigin::Op(prior_op), BindingOrigin::Op(this_op)) => {
if prior_op != this_op {
return Err(format!(
"op '{}' binding '{}' is already defined by op '{}'. \
Each ride-along binding name must be unique across \
all ops in the scope.",
this_op, binding.name, prior_op
));
}
}
(
BindingOrigin::IterationVar,
BindingOrigin::Inherited | BindingOrigin::Phase,
)
| (
BindingOrigin::Inherited | BindingOrigin::Phase,
BindingOrigin::IterationVar,
) => {
}
(_, BindingOrigin::ParamExpansion) | (BindingOrigin::ParamExpansion, _) => {
}
(_, BindingOrigin::InlineExpr) | (BindingOrigin::InlineExpr, _) => {
}
(_, BindingOrigin::AutoExtern) | (BindingOrigin::AutoExtern, _) => {}
_ => {
if prior.line != binding.line {
return Err(format!(
"binding '{}' conflicts: {} origin ({}) vs {} origin ({})",
binding.name,
prior.origin,
prior.line,
binding.origin,
binding.line
));
}
}
}
} else {
owned.insert(binding.name.clone(), binding);
}
}
let final_names: HashSet<String> = self
.bindings
.iter()
.filter(|b| b.modifier == ScopeModifier::Final)
.map(|b| b.name.clone())
.collect();
for binding in &self.bindings {
if final_names.contains(&binding.name)
&& binding.modifier != ScopeModifier::Final
&& binding.origin != BindingOrigin::Inherited
{
return Err(format!(
"cannot shadow 'final' binding '{}' from outer scope",
binding.name
));
}
}
Ok(())
}
pub fn emit(&self) -> String {
let mut lines: Vec<String> = Vec::new();
let mut emitted_names: HashSet<String> = HashSet::new();
if let Some(ref coords) = self.coordinates {
lines.push(coords.clone());
scan_input_decl_names(coords.trim(), &mut emitted_names);
}
for ext in &self.externs {
if !emitted_names.contains(&ext.name) {
lines.push(format!("extern {}: {}", ext.name, ext.type_name));
emitted_names.insert(ext.name.clone());
}
}
let origin_order: &[fn(&BindingOrigin) -> bool] = &[
|o| matches!(o, BindingOrigin::IterationVar),
|o| matches!(o, BindingOrigin::Inherited),
|o| matches!(o, BindingOrigin::Phase),
|o| matches!(o, BindingOrigin::ParamExpansion),
|o| matches!(o, BindingOrigin::InlineExpr),
|o| matches!(o, BindingOrigin::Op(_)),
];
for predicate in origin_order {
for binding in &self.bindings {
if predicate(&binding.origin) && !emitted_names.contains(&binding.name) {
lines.push(binding.line.clone());
emitted_names.insert(binding.name.clone());
}
}
}
lines.join("\n")
}
}
fn logical_lines(source: &str) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
let mut buf = String::new();
let mut depth: i32 = 0;
let mut in_str = false;
let mut in_line_comment = false;
let mut chars = source.chars().peekable();
while let Some(ch) = chars.next() {
if in_line_comment {
buf.push(ch);
if ch == '\n' {
in_line_comment = false;
if !buf.is_empty() {
out.push(std::mem::take(&mut buf));
}
}
continue;
}
if in_str {
buf.push(ch);
if ch == '\\' {
if let Some(nx) = chars.next() {
buf.push(nx);
}
} else if ch == '"' {
in_str = false;
}
continue;
}
match ch {
'"' => {
in_str = true;
buf.push(ch);
}
'#' => {
in_line_comment = true;
buf.push(ch);
}
'/' if matches!(chars.peek(), Some('/')) => {
in_line_comment = true;
buf.push(ch);
}
'(' | '[' | '{' => {
depth += 1;
buf.push(ch);
}
')' | ']' | '}' => {
if depth > 0 {
depth -= 1;
}
buf.push(ch);
}
'\n' => {
if depth > 0 {
buf.push(' ');
} else {
out.push(std::mem::take(&mut buf));
}
}
_ => buf.push(ch),
}
}
if !buf.is_empty() {
out.push(buf);
}
out
}
pub(crate) fn scan_input_decl_names(line: &str, out: &mut HashSet<String>) {
let body = line.trim().strip_prefix("input ").unwrap_or(line.trim());
let body = body.trim();
if let Some(inner) = body.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
for part in inner.split(',') {
let name = part.trim().split(':').next().unwrap_or("").trim();
if !name.is_empty() {
out.insert(name.to_string());
}
}
return;
}
let name = body.split(':').next().unwrap_or("").trim();
if !name.is_empty() {
out.insert(name.to_string());
}
}
fn format_input_decl_line(names: &[String]) -> String {
match names {
[] => String::new(),
[single] => format!("input {single}: u64\n"),
many => {
let typed: Vec<String> = many.iter().map(|n| format!("{n}: u64")).collect();
format!("input ({})\n", typed.join(", "))
}
}
}
fn parse_modifier_and_name(lhs: &str) -> (ScopeModifier, &str) {
let mut rest = lhs;
let mut tag = ScopeModifier::None;
loop {
let prev = rest;
if let Some(r) = rest.strip_prefix("shared ") {
rest = r.trim();
if matches!(tag, ScopeModifier::None | ScopeModifier::Volatile) {
tag = ScopeModifier::Shared;
}
} else if let Some(r) = rest.strip_prefix("const ") {
rest = r.trim();
tag = ScopeModifier::Final;
} else if let Some(r) = rest.strip_prefix("final ") {
rest = r.trim();
tag = ScopeModifier::Final;
} else if let Some(r) = rest.strip_prefix("volatile ") {
rest = r.trim();
if matches!(tag, ScopeModifier::None) {
tag = ScopeModifier::Volatile;
}
} else if let Some(r) = rest.strip_prefix("init ") {
rest = r.trim();
tag = ScopeModifier::Init;
break; } else if let Some(r) = rest.strip_prefix("cursor ") {
rest = r.trim();
tag = ScopeModifier::Cursor;
break;
}
if rest == prev {
break;
}
}
(tag, rest)
}
pub fn synthesize_phase_scope_bindings(
phase: &nmbrs_workload::model::WorkloadPhase,
) -> Result<nmbrs_workload::model::BindingsDef, String> {
use nmbrs_workload::model::BindingsDef;
let has_poll = phase.poll.is_some();
let has_metrics = !phase.metrics.is_empty();
let has_objective_expr = phase
.optimize
.as_ref()
.is_some_and(|o| !objective_is_bare_wire(&o.objective));
if !has_poll && !has_metrics && !has_objective_expr {
return Ok(phase.bindings.clone());
}
let mut capture_names: Vec<String> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
if has_poll {
for op in &phase.ops {
for cap in &op.captures {
if seen.insert(cap.as_name.clone()) {
capture_names.push(cap.as_name.clone());
}
}
}
}
let mut source = String::new();
if has_poll {
source.push_str(
"# SRD-75 phase-poll augmentation — synthesized.\n\
# Captures land here as shared cells; ops write through via\n\
# `ctx.wires.write` on their op-template kernel's import\n\
# slots (Rule 1 shared import → cell attached at spawn).\n",
);
for name in &capture_names {
source.push_str(&format!("shared {name} := 0\n"));
}
}
let original_body: String = match &phase.bindings {
BindingsDef::PolydatSource(s) => s.clone(),
BindingsDef::Map(m) => {
let mut out = String::new();
for (n, e) in m {
out.push_str(&format!("{n} := {e}\n"));
}
out
}
};
if !original_body.trim().is_empty() {
let locally_declared = scan_locally_declared_idents(&original_body);
for name in &capture_names {
if locally_declared.contains(name) {
return Err(format!(
"phase-poll synthesis: capture name '{name}' is also \
declared by the phase's `bindings:` block — pick one. \
SRD-75 synthesizes captures as `shared <name>: u64`; \
re-declaring the same name in bindings is a collision."
));
}
}
source.push_str(&original_body);
if !source.ends_with('\n') {
source.push('\n');
}
}
if has_poll {
let poll = phase.poll.as_ref().expect("has_poll");
source.push_str(&format!("__poll_until := {}\n", poll.until));
}
if has_metrics {
source.push_str(
"# Phase-level metrics — synthesized.\n\
# `phase_start` is the epoch millis at which THIS phase started,\n\
# read from the runtime's phase-scoped clock.\n\
volatile phase_start := phase_start_millis()\n",
);
let mut entries: Vec<_> = phase.metrics.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (name, spec) in entries {
let binding = synthesize_metric_binding_name(name);
source.push_str(&format!(
"volatile {binding} := {expr}\n",
expr = spec.value
));
for (dim, expr) in &spec.cell {
let cell_binding = synthesize_cell_binding_name(name, dim);
source.push_str(&format!("volatile {cell_binding} := {expr}\n"));
}
}
}
if has_objective_expr {
let objective = &phase
.optimize
.as_ref()
.expect("has_objective_expr")
.objective;
source.push_str(&format!(
"# SRD-86 inline objective — synthesized.\n\
volatile {OBJECTIVE_WIRE} := {objective}\n",
));
}
Ok(BindingsDef::PolydatSource(source))
}
#[allow(clippy::too_many_arguments)]
pub fn build_phase_scope_kernel(
bindings: &nmbrs_workload::model::BindingsDef,
parent_manifest: &[crate::runner::ManifestEntry],
parent_kernel: &crate::scope_kernel::ScopeKernel,
workload_params: &HashMap<String, String>,
polydat_lib_paths: Vec<std::path::PathBuf>,
workload_dir: Option<&std::path::Path>,
strict: bool,
context: &str,
) -> Result<crate::scope_kernel::ScopeKernel, String> {
use nmbrs_workload::model::BindingsDef;
let body_text: String = match bindings {
BindingsDef::PolydatSource(s) => s.clone(),
BindingsDef::Map(m) => {
let mut out = String::new();
for (name, expr) in m {
out.push_str(&format!("{name} := {expr}\n"));
}
out
}
};
let mut source = String::new();
let mut emitted: HashSet<String> = HashSet::new();
let mut inherited_names: Vec<String> = Vec::new();
let body_locally_declared = scan_locally_declared_idents(&body_text);
let mut referenced: HashSet<String> = HashSet::new();
collect_string_interp_refs(&body_text, &mut referenced);
for ident in scan_idents_in_polydat_source(&body_text) {
if !body_locally_declared.contains(&ident) {
referenced.insert(ident);
}
}
crate::scope_synth::cascade_parent_into_source(
crate::scope_synth::CascadeInputs {
parent_kernel,
workload_params,
parent_manifest,
referenced: &referenced,
pre_emitted: &body_locally_declared,
shadow_names: &body_locally_declared,
include_referenced_cascade: false,
},
crate::scope_synth::CascadeOutputs {
source: &mut source,
emitted: &mut emitted,
inherited_names: &mut inherited_names,
},
);
if !source.ends_with('\n') && !source.is_empty() {
source.push('\n');
}
source.push_str(&body_text);
if !source.ends_with('\n') {
source.push('\n');
}
let compile_options = polydat::kernel::subcontext::CompileOptions {
workload_dir: workload_dir.map(|p| p.to_path_buf()),
polydat_lib_paths,
strict,
required_outputs: Vec::new(),
context_label: Some(context.to_string()),
cursor_limit: None,
..Default::default()
};
if std::env::var("NMBRS_DEBUG_SCOPE_SYNTH")
.map(|v| v == "1")
.unwrap_or(false)
{
eprintln!(
"=== SCOPE SYNTH [{context}] inherited={inherited_names:?} ===\n{source}\n=== END ==="
);
}
crate::scope_kernel::ScopeKernel::synthesize_under(
parent_kernel,
crate::scope_kernel::SourceMatter::source(context, source, compile_options)
.inherited(inherited_names),
)
.map_err(|e| format!("{context}: phase scope synthesis: {e}"))
}
#[allow(clippy::too_many_arguments)]
pub fn build_do_loop_scope_kernel(
counter: Option<&str>,
condition: &str,
parent_manifest: &[crate::runner::ManifestEntry],
parent_kernel: &crate::scope_kernel::ScopeKernel,
workload_params: &HashMap<String, String>,
polydat_lib_paths: Vec<std::path::PathBuf>,
workload_dir: Option<&std::path::Path>,
strict: bool,
context: &str,
) -> Result<crate::scope_kernel::ScopeKernel, String> {
let mut source = String::new();
let mut emitted: HashSet<String> = HashSet::new();
let mut inherited_names: Vec<String> = Vec::new();
if let Some(c) = counter {
source.push_str(&format!("extern {c}: u64\n"));
emitted.insert(c.to_string());
}
let referenced = collect_leaf_placeholders(&[condition.to_string()]);
let pre_emitted: HashSet<String> = counter.iter().map(|c| c.to_string()).collect();
let shadow_names: HashSet<String> = pre_emitted.clone();
crate::scope_synth::cascade_parent_into_source(
crate::scope_synth::CascadeInputs {
parent_kernel,
workload_params,
parent_manifest,
referenced: &referenced,
pre_emitted: &pre_emitted,
shadow_names: &shadow_names,
include_referenced_cascade: true,
},
crate::scope_synth::CascadeOutputs {
source: &mut source,
emitted: &mut emitted,
inherited_names: &mut inherited_names,
},
);
if source.is_empty() {
source.push_str("const __empty := 0\n");
}
let compile_options = polydat::kernel::subcontext::CompileOptions {
workload_dir: workload_dir.map(|p| p.to_path_buf()),
polydat_lib_paths,
strict,
required_outputs: Vec::new(),
context_label: Some(context.to_string()),
cursor_limit: None,
..Default::default()
};
crate::scope_kernel::ScopeKernel::synthesize_under(
parent_kernel,
crate::scope_kernel::SourceMatter::source(context, source, compile_options)
.inherited(inherited_names),
)
.map_err(|e| format!("{context}: do-loop scope synthesis: {e}"))
}
pub(crate) fn scan_idents_in_polydat_source(src: &str) -> HashSet<String> {
const KEYWORDS: &[&str] = &[
"input", "extern", "const", "final", "init", "shared", "volatile", "cursor", "pragma",
"true", "false", "as", "in", "for",
"if", "else",
];
let mut out = HashSet::new();
let mut chars = src.chars().peekable();
let mut current = String::new();
let mut in_string = false;
let mut in_line_comment = false;
let mut in_block_comment = false;
let mut suppress_next_ident = false;
while let Some(c) = chars.next() {
if in_line_comment {
if c == '\n' {
in_line_comment = false;
}
continue;
}
if in_block_comment {
if c == '*' && chars.peek() == Some(&'/') {
chars.next();
in_block_comment = false;
}
continue;
}
if in_string {
if c == '\\' {
chars.next();
continue;
}
if c == '"' {
in_string = false;
}
continue;
}
if c == '"' {
in_string = true;
continue;
}
if c == '#' {
in_line_comment = true;
continue;
}
if c == '/' {
if chars.peek() == Some(&'/') {
chars.next();
in_line_comment = true;
continue;
}
if chars.peek() == Some(&'*') {
chars.next();
in_block_comment = true;
continue;
}
}
if c.is_alphanumeric() || c == '_' {
current.push(c);
} else if !current.is_empty() {
let is_ident = !current
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(true)
&& !KEYWORDS.contains(¤t.as_str());
if is_ident && !suppress_next_ident {
out.insert(current.clone());
}
current.clear();
suppress_next_ident = false;
}
if c == ':' && chars.peek() != Some(&'=') {
suppress_next_ident = true;
}
}
if !current.is_empty()
&& !current
.chars()
.next()
.map(|c| c.is_ascii_digit())
.unwrap_or(true)
&& !KEYWORDS.contains(¤t.as_str())
&& !suppress_next_ident
{
out.insert(current);
}
out
}
pub(crate) fn scan_locally_declared_idents(src: &str) -> HashSet<String> {
let mut out = HashSet::new();
for line in src.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with("//") || line.starts_with('#') {
continue;
}
let prefixes = [
"shared const ",
"const ",
"init ",
"shared ",
"final ",
"volatile ",
"extern ",
];
let mut rest = line;
loop {
let mut stripped = false;
for p in &prefixes {
if let Some(r) = rest.strip_prefix(p) {
rest = r.trim_start();
stripped = true;
break;
}
}
if !stripped {
break;
}
}
let assign_idx = rest.find(":=").or_else(|| {
rest.find('=')
.filter(|&i| rest.as_bytes().get(i + 1) != Some(&b'='))
});
let Some(idx) = assign_idx else { continue };
let lhs = rest[..idx].trim();
let name = lhs.split(':').next().unwrap_or(lhs).trim();
if !name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_') {
out.insert(name.to_string());
}
}
out
}
#[allow(clippy::too_many_arguments)]
pub fn build_op_template_scope_kernel(
op: &nmbrs_workload::model::ParsedOp,
parent_manifest: &[crate::runner::ManifestEntry],
parent_kernel: &crate::scope_kernel::ScopeKernel,
workload_params: &HashMap<String, String>,
polydat_lib_paths: Vec<std::path::PathBuf>,
workload_dir: Option<&std::path::Path>,
strict: bool,
kernel_opt: polydat::kernel::KernelOptLevel,
context: &str,
) -> Result<crate::scope_kernel::ScopeKernel, String> {
use nmbrs_workload::model::BindingsDef;
let manifest_by_name: HashMap<&str, &crate::runner::ManifestEntry> = parent_manifest
.iter()
.map(|e| (e.name.as_str(), e))
.collect();
let mut source = String::new();
let mut emitted: HashSet<String> = HashSet::new();
let mut inherited_names: Vec<String> = Vec::new();
let body_text: String = match &op.bindings {
BindingsDef::PolydatSource(s) => s.clone(),
BindingsDef::Map(m) => {
let mut out = String::new();
for (name, expr) in m {
out.push_str(&format!("{name} := {expr}\n"));
}
out
}
};
let body_has_inputs_decl = body_text
.lines()
.any(|line| line.trim_start().starts_with("input "));
if !body_has_inputs_decl {
let parent_coord_names: Vec<String> = parent_kernel
.program()
.input_names()
.into_iter()
.take(parent_kernel.program().coord_count())
.collect();
if !parent_coord_names.is_empty() {
source.push_str(&format_input_decl_line(&parent_coord_names));
for name in &parent_coord_names {
emitted.insert(name.clone());
}
}
}
if let Some(iface) = op.abstract_interface.as_ref() {
for (rname, kw) in &iface.results {
if polydat::ast::PortType::from_keyword(kw).is_none() {
return Err(format!(
"{context}: interface results wire '{rname}' declares \
unknown type '{kw}'"
));
}
if !emitted.contains(rname) {
source.push_str(&format!("extern {rname}: {kw}\n"));
emitted.insert(rname.clone());
}
}
}
let body_idents = scan_idents_in_polydat_source(&body_text);
let body_locally_declared = scan_locally_declared_idents(&body_text);
let mut referenced: Vec<String> = Vec::new();
for value in op.op.values() {
if let Some(s) = value.as_str() {
for n in nmbrs_workload::bindpoints::referenced_bindings(s) {
if !body_locally_declared.contains(&n) && !referenced.iter().any(|r| r == &n) {
referenced.push(n);
}
}
}
}
for ident in &body_idents {
if body_locally_declared.contains(ident) {
continue;
}
if !referenced.iter().any(|r| r == ident) {
referenced.push(ident.clone());
}
}
let mut interp_refs: HashSet<String> = HashSet::new();
collect_string_interp_refs(&body_text, &mut interp_refs);
for ident in interp_refs {
if body_locally_declared.contains(&ident) {
continue;
}
if !referenced.iter().any(|r| r == &ident) {
referenced.push(ident);
}
}
if let Some(ref s) = op.condition {
let n = s.trim().trim_start_matches('{').trim_end_matches('}');
if !n.is_empty() && !body_locally_declared.contains(n) && !referenced.iter().any(|r| r == n)
{
referenced.push(n.to_string());
}
}
for cap in &op.captures {
let n = cap.as_name.as_str();
if !n.is_empty() && !body_locally_declared.contains(n) && !referenced.iter().any(|r| r == n)
{
referenced.push(n.to_string());
}
}
if let Some(ref delay_spec) = op.delay {
for raw in delay_spec.names() {
let n = raw.trim().trim_start_matches('{').trim_end_matches('}');
if !n.is_empty()
&& !body_locally_declared.contains(n)
&& !referenced.iter().any(|r| r == n)
{
referenced.push(n.to_string());
}
}
}
for spec in op.metrics.values() {
let trimmed = spec.value.trim();
let bare = !trimmed.is_empty() && trimmed.chars().all(|c| c.is_alphanumeric() || c == '_');
if bare
&& !body_locally_declared.contains(trimmed)
&& !referenced.iter().any(|r| r == trimmed)
{
referenced.push(trimmed.to_string());
}
}
if let Some(rel) = op.params.get("relevancy").and_then(|v| v.as_object()) {
for key in &["actual", "expected", "k", "r"] {
let Some(val) = rel.get(*key).and_then(|v| v.as_str()) else {
continue;
};
let trimmed = val
.trim()
.trim_start_matches('{')
.trim_end_matches('}')
.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.parse::<i64>().is_ok() {
continue;
}
let bare = trimmed
.chars()
.next()
.map(|c| c.is_ascii_alphabetic() || c == '_')
.unwrap_or(false)
&& trimmed
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_');
if !bare {
continue;
}
if !body_locally_declared.contains(trimmed) && !referenced.iter().any(|r| r == trimmed)
{
referenced.push(trimmed.to_string());
}
}
}
if let Some(rb) = op.result.as_ref() {
rb.walk_fragments(|frag| match frag {
nmbrs_workload::model::ResultFragment::Source(s) => {
for n in scan_locally_declared_idents(s) {
if !body_locally_declared.contains(&n) && !referenced.iter().any(|r| r == &n) {
referenced.push(n);
}
}
}
nmbrs_workload::model::ResultFragment::Named { name, .. } => {
let n = name.to_string();
if !body_locally_declared.contains(&n) && !referenced.iter().any(|r| r == &n) {
referenced.push(n);
}
}
});
}
for name in &referenced {
if emitted.contains(name) {
continue;
}
if body_locally_declared.contains(name) {
continue;
}
let is_parent_coord = parent_kernel
.program()
.find_input(name)
.and_then(|idx| parent_kernel.program().input_kind(idx))
.is_some_and(|k| matches!(k, polydat::kernel::InputKind::Coordinate));
if is_parent_coord {
inherited_names.push(name.clone());
continue;
}
if let Some(value) = workload_params.get(name) {
crate::scope_synth::emit_workload_param_chain_aware(
name,
value,
parent_kernel,
&mut source,
&mut emitted,
None,
);
} else if let Some(entry) = manifest_by_name.get(name.as_str()) {
let type_name = entry.port_type.to_keyword();
source.push_str(&format!("extern {name}: {type_name}\n"));
emitted.insert(name.clone());
inherited_names.push(name.clone());
} else if let Some(parent_idx) = parent_kernel.program().find_input(name) {
let kind = parent_kernel.program().input_kind(parent_idx);
if !matches!(kind, Some(polydat::kernel::InputKind::Coordinate)) {
let port_type = parent_kernel
.program()
.input_port_type(name)
.ok_or_else(|| {
format!(
"scope synthesis: parent input '{name}' has no \
declared PortType (find_input returned index {parent_idx} \
but input_port_type returned None — kernel program shape \
broken)"
)
})
.expect("input index just resolved must have a port type");
let type_name = port_type.to_keyword();
source.push_str(&format!("extern {name}: {type_name}\n"));
emitted.insert(name.clone());
inherited_names.push(name.clone());
}
}
}
for (name, value) in workload_params {
crate::scope_synth::emit_workload_param_chain_aware(
name,
value,
parent_kernel,
&mut source,
&mut emitted,
None,
);
}
if body_text.trim().is_empty() {
if source.is_empty() {
source.push_str("const __empty := 0\n");
}
} else {
if !source.ends_with('\n') && !source.is_empty() {
source.push('\n');
}
source.push_str(&body_text);
if !source.ends_with('\n') {
source.push('\n');
}
}
let compile_options = polydat::kernel::subcontext::CompileOptions {
workload_dir: workload_dir.map(|p| p.to_path_buf()),
polydat_lib_paths,
strict,
required_outputs: Vec::new(),
context_label: Some(context.to_string()),
cursor_limit: None,
kernel_opt,
..Default::default()
};
let mut result_source: String = op
.result
.as_ref()
.map(collect_result_bindings_source)
.unwrap_or_default();
if !op.metrics.is_empty() {
let mut entries: Vec<_> = op.metrics.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (name, spec) in entries {
let binding = synthesize_metric_binding_name(name);
result_source.push_str(&format!("{binding} := {expr}\n", expr = spec.value));
for (dim, expr) in &spec.cell {
let cell_binding = synthesize_cell_binding_name(name, dim);
result_source.push_str(&format!("{cell_binding} := {expr}\n"));
}
}
}
if let Some(until) = template_poll_until(op) {
result_source.push_str(&format!(
"{} := {until}\n",
crate::wrappers::condition::UNTIL_BINDING,
));
}
if let Some(while_expr) = op.while_cond.as_ref() {
result_source.push_str(&format!(
"{} := {expr}\n",
crate::wrappers::r#while::BINDING_NAME,
expr = while_expr,
));
}
let result_source: Option<String> = Some(result_source).filter(|s| !s.trim().is_empty());
if std::env::var("NMBRS_DEBUG_SCOPE_SYNTH")
.map(|v| v == "1")
.unwrap_or(false)
{
eprintln!(
"=== OP SYNTH [{context}] inherited={inherited_names:?} parent_has_xval_input={:?} parent_manifest_has_xval={} ===\n{source}\n=== END ===",
parent_kernel.program().find_input("xval"),
manifest_by_name.contains_key("xval")
);
}
let synthesis_error = |e: String| format!("{context}: op-template scope synthesis: {e}");
let mut matter = crate::scope_kernel::SourceMatter::source(context, source, compile_options)
.inherited(inherited_names);
if let Some(rb) = result_source {
matter = matter.results(rb);
}
let kernel = crate::scope_kernel::ScopeKernel::synthesize_under(parent_kernel, matter)
.map_err(synthesis_error)?;
if let Some(iface) = op.abstract_interface.as_ref() {
verify_op_interface(iface, kernel.program().as_ref(), context)?;
}
Ok(kernel)
}
fn verify_op_interface(
iface: &nmbrs_workload::model::OpInterface,
program: &polydat::kernel::PolydatProgram,
context: &str,
) -> Result<(), String> {
let type_of = |name: &str| -> Option<polydat::ast::PortType> {
program
.input_port_type(name)
.or_else(|| program.output_port_type(name))
};
for (name, declared) in &iface.yields {
let Some(expected) = polydat::ast::PortType::from_keyword(declared) else {
return Err(format!(
"{context}: interface yield '{name}' declares unknown \
type '{declared}'"
));
};
if let Some(actual) = type_of(name)
&& actual != expected
{
return Err(format!(
"{context}: interface yield '{name}' declares type \
{declared}, but the compiled op template carries \
{actual:?}"
));
}
}
for (name, declared) in &iface.needs {
let Some(expected) = polydat::ast::PortType::from_keyword(declared) else {
return Err(format!(
"{context}: interface need '{name}' declares unknown \
type '{declared}'"
));
};
if let Some(actual) = type_of(name)
&& actual != expected
{
return Err(format!(
"{context}: interface need '{name}' declares type \
{declared}, but the compiled op template carries \
{actual:?}"
));
}
}
for (name, declared) in &iface.results {
let Some(expected) = polydat::ast::PortType::from_keyword(declared) else {
return Err(format!(
"{context}: interface results wire '{name}' declares \
unknown type '{declared}'"
));
};
if let Some(actual) = type_of(name)
&& actual != expected
{
return Err(format!(
"{context}: interface results wire '{name}' declares \
type {declared}, but the compiled op template carries \
{actual:?}"
));
}
}
Ok(())
}
fn template_poll_until(op: &nmbrs_workload::model::ParsedOp) -> Option<String> {
op.params
.get("poll")?
.as_object()?
.get("until")?
.as_str()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
}
pub fn synthesize_metric_binding_name(metric_name: &str) -> String {
format!("__metric_{metric_name}")
}
pub fn synthesize_cell_binding_name(metric_name: &str, dimension: &str) -> String {
format!("__cell_{metric_name}__{dimension}")
}
pub const OBJECTIVE_WIRE: &str = "__objective";
pub fn objective_is_bare_wire(objective: &str) -> bool {
let s = objective.trim();
!s.is_empty()
&& s.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
pub fn objective_wire(objective: &str) -> &str {
if objective_is_bare_wire(objective) {
objective.trim()
} else {
OBJECTIVE_WIRE
}
}
fn collect_result_bindings_source(spec: &nmbrs_workload::model::ResultSpec) -> String {
let mut out = String::new();
spec.walk_fragments(|frag| match frag {
nmbrs_workload::model::ResultFragment::Source(src) => {
out.push_str(src);
if !src.ends_with('\n') {
out.push('\n');
}
}
nmbrs_workload::model::ResultFragment::Named { name, source } => {
let s = source.trim();
if s == "count" || s == "ok" || s.contains('(') {
out.push_str(&format!("{name} := {source}\n"));
}
}
});
out
}
#[allow(clippy::too_many_arguments)]
pub fn build_scope(
ops: &[ParsedOp],
iteration_vars: &HashMap<String, String>,
outer_manifest: &[crate::runner::ManifestEntry],
workload_params: &HashMap<String, String>,
phases: &HashMap<String, nmbrs_workload::model::WorkloadPhase>,
phase_cycles: Option<&str>,
exclude: &[String],
parent_kernel: Option<&crate::scope_kernel::ScopeKernel>,
) -> Result<BindingScope, String> {
let mut scope = BindingScope::new();
let mut base_source: Option<String> = None;
for op in ops {
if let BindingsDef::PolydatSource(src) = &op.bindings {
let src = src.trim();
if src.is_empty() {
continue;
}
match &base_source {
None => {
base_source = Some(src.to_string());
scope.ingest_polydat_source(src, BindingOrigin::Inherited);
}
Some(base) => {
if src == base.as_str() {
} else {
let base_logical: Vec<String> = logical_lines(base)
.into_iter()
.map(|l| l.trim().to_string())
.filter(|l| !l.is_empty())
.collect();
for line in logical_lines(src) {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if base_logical.iter().any(|b| b == trimmed) {
} else {
scope.ingest_polydat_source(
trimmed,
BindingOrigin::Op(op.name.clone()),
);
}
}
}
}
}
}
}
for (var, val) in iteration_vars {
scope.add_iteration_var(var, val);
scope.add_required_output(var);
}
let defined = scope.defined_names();
let extern_names = scope.extern_names();
let mut referenced: HashSet<String> = HashSet::new();
for op in ops {
for value in op.op.values() {
if let Some(s) = value.as_str() {
for name in nmbrs_workload::bindpoints::referenced_bindings(s) {
referenced.insert(name);
}
}
}
if let Some(ref cond) = op.condition {
let bare = cond
.trim()
.strip_prefix('{')
.and_then(|s| s.strip_suffix('}'))
.unwrap_or(cond.trim());
referenced.insert(bare.to_string());
}
if let Some(ref delay_spec) = op.delay {
for raw in delay_spec.names() {
let bare = raw
.trim()
.strip_prefix('{')
.and_then(|s| s.strip_suffix('}'))
.unwrap_or(raw.trim());
referenced.insert(bare.to_string());
}
}
if let BindingsDef::PolydatSource(src) = &op.bindings {
collect_string_interp_refs(src, &mut referenced);
let body_locally_declared = scan_locally_declared_idents(src);
for ident in scan_idents_in_polydat_source(src) {
if !body_locally_declared.contains(&ident) {
referenced.insert(ident);
}
}
}
let mut param_refs: Vec<String> = Vec::new();
crate::bindings::collect_param_bindings_into(&op.params, &[], &mut param_refs);
for name in param_refs {
referenced.insert(name);
}
}
for entry in outer_manifest {
if entry.modifier == polydat::dsl::ast::BindingModifier::CONST
&& defined.contains(&entry.name)
{
return Err(format!(
"cannot shadow 'final' binding '{}' from outer scope",
entry.name
));
}
}
if let Some(parent_kernel_ref) = parent_kernel {
let parent_prog = parent_kernel_ref.program();
if scope.coordinates.is_none() {
let coord_count = parent_prog.coord_count();
if coord_count > 0 {
let input_names = parent_prog.input_names();
let coords: Vec<String> = input_names.into_iter().take(coord_count).collect();
scope.coordinates = Some(format_input_decl_line(&coords).trim_end().to_string());
}
}
let mut already_satisfied: HashSet<String> = HashSet::new();
already_satisfied.extend(defined.iter().cloned());
already_satisfied.extend(extern_names.iter().cloned());
for var in iteration_vars.keys() {
already_satisfied.insert(var.clone());
}
let mut refs_sorted: Vec<String> = referenced.iter().cloned().collect();
refs_sorted.sort();
for name in &refs_sorted {
let name = name.as_str();
if already_satisfied.contains(name) {
continue;
}
let manifest_modifier = outer_manifest
.iter()
.find(|e| e.name == name)
.map(|e| e.modifier);
let is_workload_param = workload_params.contains_key(name);
if let Some(m) = manifest_modifier {
use polydat::dsl::ast::BindingModifier;
if m == BindingModifier::SHARED {
continue;
}
if m == BindingModifier::CONST {
let statically_known = parent_kernel_ref
.program()
.output_index(name)
.map(|out_idx| {
let (node_idx, _) =
parent_kernel_ref.program().resolve_output_by_index(out_idx);
parent_kernel_ref
.program()
.input_provenance_for(node_idx)
.is_none_or(|p| p.is_zero())
})
.unwrap_or(true);
if statically_known
&& let Some(value) = parent_kernel_ref.lookup(name)
&& let Some(natural) = value_to_param_string(&value)
{
scope.add_param_binding(name, &natural);
already_satisfied.insert(name.to_string());
continue;
}
continue;
}
}
if is_workload_param
&& let Some(value) = parent_kernel_ref.lookup(name)
&& let Some(natural) = value_to_param_string(&value)
{
scope.add_param_binding(name, &natural);
already_satisfied.insert(name.to_string());
continue;
}
let chain = parent_prog.local_inclusion_chain(name, &already_satisfied);
if chain.is_empty() {
continue;
}
for stmt in chain {
let line = polydat::dsl::pprint::pp_statement(stmt);
scope.ingest_polydat_source(&line, BindingOrigin::Inherited);
let body = match stmt {
polydat::dsl::ast::Statement::Binding(b) => Some(&b.value),
_ => None,
};
if let Some(expr) = body {
polydat::dsl::collect_expr_references(expr, &mut referenced);
}
if let polydat::dsl::ast::Statement::Binding(b) = stmt {
for t in &b.targets {
already_satisfied.insert(t.clone());
}
}
}
}
}
let defined = scope.defined_names();
for entry in outer_manifest {
let is_iter_var = iteration_vars.contains_key(&entry.name);
if referenced.contains(&entry.name)
&& !defined.contains(&entry.name)
&& !extern_names.contains(&entry.name)
&& !is_iter_var
{
let type_name = entry.port_type.to_keyword();
scope.add_extern(&entry.name, type_name);
}
}
if parent_kernel.is_some() {
let defined_now = scope.defined_names();
let extern_now = scope.extern_names();
let mut satisfied: HashSet<String> = HashSet::new();
satisfied.extend(defined_now);
satisfied.extend(extern_now);
for var in iteration_vars.keys() {
satisfied.insert(var.clone());
}
for name in workload_params.keys() {
satisfied.insert(name.clone());
}
for entry in outer_manifest {
satisfied.insert(entry.name.clone());
}
if let Some(coords_line) = &scope.coordinates
&& let Some(rhs) = coords_line.split(":=").nth(1)
{
let inner = rhs.trim().trim_start_matches('(').trim_end_matches(')');
for n in inner.split(',') {
let n = n.trim();
if !n.is_empty() {
satisfied.insert(n.to_string());
}
}
}
let mut unresolved: Vec<&String> = referenced
.iter()
.filter(|n| !satisfied.contains(n.as_str()))
.filter(|n| !n.contains('.') || !satisfied.contains(n.replace('.', "__").as_str()))
.filter(|n| !n.starts_with("__"))
.filter(|n| polydat::dsl::registry::lookup(n).is_none())
.collect();
unresolved.sort();
if !unresolved.is_empty() {
let names: Vec<&str> = unresolved.iter().map(|s| s.as_str()).collect();
let mut visible: Vec<&str> = satisfied.iter().map(|s| s.as_str()).collect();
visible.sort();
return Err(format!(
"unresolved wire reference(s) {names:?}: not declared locally, \
not in parent manifest, not a workload param. \
Visible names in this scope: {visible:?}"
));
}
}
let defined = scope.defined_names(); let mut params_sorted: Vec<(&String, &String)> = workload_params.iter().collect();
params_sorted.sort_by(|a, b| a.0.cmp(b.0));
for (name, value) in params_sorted {
if defined.contains(name) {
continue;
}
let type_name = workload_param_type_name(value);
scope.add_extern(name, type_name);
scope.add_required_output(name);
}
for phase in phases.values() {
if let Some(ref c) = phase.cycles
&& c.starts_with('{')
&& c.ends_with('}')
{
let name = &c[1..c.len() - 1];
if workload_params.contains_key(name) && !scope.defined_names().contains(name) {
scope.add_param_binding(name, &workload_params[name]);
}
}
}
let mut inline_idx = 0usize;
let mut expr_to_name: HashMap<String, String> = HashMap::new();
let mut collect = |s: &str| {
for bp in nmbrs_workload::bindpoints::extract_bind_points(s) {
if let nmbrs_workload::bindpoints::BindPoint::InlineDefinition(ref expr) = bp
&& !expr_to_name.contains_key(expr)
{
let name = format!("__expr_{inline_idx}");
inline_idx += 1;
expr_to_name.insert(expr.clone(), name);
}
}
};
for op in ops {
for value in op.op.values() {
if let Some(s) = value.as_str() {
collect(s);
}
}
if let Some(s) = &op.condition {
collect(s);
}
if let Some(spec) = &op.delay {
for name in spec.names() {
collect(name);
}
}
}
for (expr, name) in &expr_to_name {
scope.add_inline_expr(name, expr);
}
for op in ops {
for value in op.op.values() {
if let Some(s) = value.as_str() {
for name in nmbrs_workload::bindpoints::referenced_bindings(s) {
if !exclude.contains(&name) {
scope.add_required_output(&name);
}
}
}
}
let mut collect_required = |s: &str| {
let trimmed = s.trim();
let bps = nmbrs_workload::bindpoints::extract_bind_points(trimmed);
if !bps.is_empty() {
for bp in bps {
match bp {
nmbrs_workload::bindpoints::BindPoint::InlineDefinition(expr) => {
if let Some(name) = expr_to_name.get(&expr)
&& !exclude.contains(name)
{
scope.add_required_output(name);
}
}
nmbrs_workload::bindpoints::BindPoint::Reference { name, .. } => {
if !exclude.contains(&name) {
scope.add_required_output(&name);
}
}
}
}
return;
}
if !trimmed.is_empty() && !exclude.contains(&trimmed.to_string()) {
scope.add_required_output(trimmed);
}
};
if let Some(ref cond) = op.condition {
collect_required(cond);
}
if let Some(ref delay_spec) = op.delay {
for name in delay_spec.names() {
collect_required(name);
}
}
for spec in op.metrics.values() {
let trimmed = spec.value.trim();
let bare =
!trimmed.is_empty() && trimmed.chars().all(|c| c.is_alphanumeric() || c == '_');
if bare && !exclude.contains(&trimmed.to_string()) {
scope.add_required_output(trimmed);
}
}
let _ = op.result.as_ref();
crate::bindings::collect_param_bindings_into(
&op.params,
exclude,
&mut scope.required_outputs,
);
}
if let Some(cycles_spec) = phase_cycles
&& cycles_spec.starts_with('{')
&& cycles_spec.ends_with('}')
{
let mut inner = cycles_spec[1..cycles_spec.len() - 1].to_string();
for (v, val) in iteration_vars {
inner = inner.replace(&format!("{{{v}}}"), val);
}
inner = crate::runner::expand_workload_params(&inner, workload_params);
scope.add_config_ref(&inner);
}
Ok(scope)
}
pub fn validate_placeholders_via_kernel(
ops: &[ParsedOp],
kernel: &dyn polydat::Kernel,
enclosing: &[&polydat::kernel::PolydatProgram],
) -> Result<(), String> {
let mut per_cycle_names = collect_phase_binding_lhs_names(ops);
let nothing_excluded = HashSet::new();
for name in collect_op_placeholder_names(ops) {
if !per_cycle_names.contains(&name)
&& enclosing.iter().any(|program| {
!program
.local_inclusion_chain(&name, ¬hing_excluded)
.is_empty()
})
{
per_cycle_names.push(name);
}
}
let mut errors: Vec<String> = Vec::new();
let in_scope = || -> Vec<String> {
let mut names: Vec<String> = kernel.output_names();
for n in kernel.input_names() {
if !names.contains(&n) {
names.push(n);
}
}
names.sort();
names
};
for op in ops.iter() {
let op_name = op.name.clone();
for (key, value) in op.op.iter() {
let path = format!("op '{op_name}' field '{key}'");
let mut throwaway = value.clone();
resolve_placeholders_in_json(
&mut throwaway,
kernel,
&per_cycle_names,
&path,
&mut errors,
);
}
for (key, value) in op.params.iter() {
if key == "gutter" {
continue; }
let path = format!("op '{op_name}' param '{key}'");
let mut throwaway = value.clone();
resolve_placeholders_in_json(
&mut throwaway,
kernel,
&per_cycle_names,
&path,
&mut errors,
);
}
}
if errors.is_empty() {
return Ok(());
}
let in_scope_str = in_scope().join(", ");
let mut out =
String::from("placeholder resolution failed (single read path: Polydat Kernel lookup):\n");
for e in &errors {
out.push_str(" - ");
out.push_str(e);
out.push('\n');
}
out.push_str(&format!(
" in-scope names at this kernel: [{in_scope_str}]"
));
Err(out)
}
pub fn resolve_placeholders_in_op_params(
op: &mut ParsedOp,
kernel: &dyn polydat::Kernel,
) -> Result<(), String> {
let per_cycle_names = collect_phase_binding_lhs_names(std::slice::from_ref(op));
let mut errors: Vec<String> = Vec::new();
let in_scope = || -> Vec<String> {
let mut names: Vec<String> = kernel.output_names();
for n in kernel.input_names() {
if !names.contains(&n) {
names.push(n);
}
}
names.sort();
names
};
let op_name = op.name.clone();
for (key, value) in op.params.iter_mut() {
if key == "gutter" {
continue;
}
let path = format!("op '{op_name}' param '{key}'");
resolve_placeholders_in_json(value, kernel, &per_cycle_names, &path, &mut errors);
}
if errors.is_empty() {
return Ok(());
}
let in_scope_str = in_scope().join(", ");
let mut out = String::from("param-placeholder resolution failed:\n");
for e in &errors {
out.push_str(" - ");
out.push_str(e);
out.push('\n');
}
out.push_str(&format!(
" in-scope names at this kernel: [{in_scope_str}]"
));
Err(out)
}
fn collect_op_placeholder_names(ops: &[ParsedOp]) -> Vec<String> {
fn walk(value: &serde_json::Value, out: &mut Vec<String>) {
match value {
serde_json::Value::String(s) => {
for name in nmbrs_workload::bindpoints::referenced_bindings(s) {
if !out.contains(&name) {
out.push(name);
}
}
}
serde_json::Value::Array(items) => items.iter().for_each(|v| walk(v, out)),
serde_json::Value::Object(map) => map.values().for_each(|v| walk(v, out)),
_ => {}
}
}
let mut out = Vec::new();
for op in ops {
op.op.values().for_each(|v| walk(v, &mut out));
op.params.values().for_each(|v| walk(v, &mut out));
}
out
}
fn collect_phase_binding_lhs_names(ops: &[ParsedOp]) -> Vec<String> {
let mut out: Vec<String> = Vec::new();
for op in ops {
if let BindingsDef::PolydatSource(src) = &op.bindings {
for line in logical_lines(src) {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
if let Some(rest) = trimmed.strip_prefix("input ") {
let rest = rest.trim();
if let Some(inner) = rest.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
for piece in inner.split(',') {
let n = piece.trim().split(':').next().unwrap_or("").trim();
if is_bare_ident(n) && !out.contains(&n.to_string()) {
out.push(n.to_string());
}
}
} else {
let n = rest.split(':').next().unwrap_or("").trim();
if is_bare_ident(n) && !out.contains(&n.to_string()) {
out.push(n.to_string());
}
}
continue;
}
let lhs_end = trimmed
.find(":=")
.or_else(|| trimmed.find('='))
.unwrap_or(trimmed.len());
let mut lhs = &trimmed[..lhs_end];
loop {
let mut matched = false;
for prefix in [
"cursor ",
"init ",
"extern ",
"const ",
"final ",
"shared ",
"volatile ",
"private ",
] {
if let Some(stripped) = lhs.strip_prefix(prefix) {
lhs = stripped.trim();
matched = true;
break;
}
}
if !matched {
break;
}
}
let lhs = lhs.trim();
if let Some(inner) = lhs.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
for piece in inner.split(',') {
let n = piece.trim().trim_end_matches(':').trim();
if is_bare_ident(n) && !out.contains(&n.to_string()) {
out.push(n.to_string());
}
}
continue;
}
let bare = lhs.split(':').next().unwrap_or(lhs).trim();
if is_bare_ident(bare) && !out.contains(&bare.to_string()) {
out.push(bare.to_string());
}
}
}
}
out
}
fn is_bare_ident(s: &str) -> bool {
let mut chars = s.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
fn is_polydat_quoted_string(s: &str) -> bool {
if s.len() < 2 {
return false;
}
if !s.starts_with('"') || !s.ends_with('"') {
return false;
}
let bytes = s.as_bytes();
let mut i = 1;
let last = bytes.len() - 1;
while i < last {
if bytes[i] == b'\\' {
i += 2;
continue;
}
if bytes[i] == b'"' {
return false;
}
i += 1;
}
true
}
fn is_polydat_array_literal(s: &str) -> bool {
if !s.starts_with('[') || !s.ends_with(']') {
return false;
}
let mut depth: i32 = 0;
let mut in_string = false;
let mut escape = false;
for c in s.chars() {
if escape {
escape = false;
continue;
}
if in_string {
if c == '\\' {
escape = true;
} else if c == '"' {
in_string = false;
}
continue;
}
match c {
'"' => in_string = true,
'[' => depth += 1,
']' => {
depth -= 1;
if depth < 0 {
return false;
}
}
_ => {}
}
}
depth == 0
}
#[cfg(test)]
mod polydat_param_classifier_tests {
use super::*;
#[test]
fn scan_idents_skips_hash_comments() {
let src = "# Forwarding bindings: allow-list = bindings + params\n\
pct := mul(active, 2) # trailing comment with words\n";
let idents = scan_idents_in_polydat_source(src);
assert!(
idents.contains("active"),
"real ident must be found: {idents:?}"
);
for word in [
"Forwarding",
"bindings",
"allow",
"list",
"params",
"trailing",
"comment",
"words",
] {
assert!(
!idents.contains(word),
"comment word '{word}' must not be scanned as a wire ref: {idents:?}"
);
}
}
#[test]
fn scan_idents_skips_block_conditional_keywords() {
let src = "seg_mib_mean := if segments > 0 { total / max(segments, 1) } else { 0 }\n";
let idents = scan_idents_in_polydat_source(src);
assert!(
idents.contains("segments"),
"real wire must still be found: {idents:?}"
);
assert!(
idents.contains("total"),
"real wire must still be found: {idents:?}"
);
for kw in ["if", "else"] {
assert!(
!idents.contains(kw),
"conditional keyword '{kw}' must not scan as a wire ref: {idents:?}"
);
}
}
#[test]
fn scan_locally_declared_skips_hash_comment_lines() {
let src = "# total = sum of parts\n\
shared sstables := 0\n";
let decls = scan_locally_declared_idents(src);
assert!(decls.contains("sstables"));
assert!(
!decls.contains("total"),
"comment LHS must not declare: {decls:?}"
);
}
#[test]
fn bare_identifier_classifier_accepts_idents_and_rejects_other_shapes() {
assert!(is_bare_ident("sm"));
assert!(is_bare_ident("source_model"));
assert!(is_bare_ident("k_values"));
assert!(is_bare_ident("_underscore"));
assert!(is_bare_ident("a1"));
assert!(!is_bare_ident(""));
assert!(!is_bare_ident("1abc"), "ident can't start with digit");
assert!(!is_bare_ident("foo bar"), "no spaces");
assert!(!is_bare_ident("[a, b]"));
assert!(!is_bare_ident("\"quoted\""));
assert!(!is_bare_ident("foo+bar"));
assert!(is_bare_ident("true"));
assert!(is_bare_ident("false"));
}
#[test]
fn polydat_quoted_string_accepts_paired_quotes_only() {
assert!(is_polydat_quoted_string("\"hello\""));
assert!(is_polydat_quoted_string("\"\""));
assert!(is_polydat_quoted_string("\"with \\\"escaped\\\" inner\""));
assert!(!is_polydat_quoted_string("hello"));
assert!(!is_polydat_quoted_string("\"open-only"));
assert!(!is_polydat_quoted_string("close-only\""));
assert!(!is_polydat_quoted_string(""));
assert!(!is_polydat_quoted_string("\""));
}
#[test]
fn polydat_array_literal_balances_brackets() {
assert!(is_polydat_array_literal("[1, 2, 3]"));
assert!(is_polydat_array_literal("[]"));
assert!(
is_polydat_array_literal("[[1, 2], [3, 4]]"),
"nested arrays balance"
);
assert!(is_polydat_array_literal("[\"a\", \"b\"]"));
assert!(is_polydat_array_literal("[\"a]b\", \"c\"]"));
assert!(!is_polydat_array_literal("[1, 2"));
assert!(!is_polydat_array_literal("1, 2]"));
}
}
fn resolve_placeholders_in_json(
value: &mut serde_json::Value,
kernel: &dyn polydat::Kernel,
per_cycle_names: &[String],
field_path: &str,
errors: &mut Vec<String>,
) {
match value {
serde_json::Value::String(s) => {
match resolve_placeholders_in_string(s, kernel, per_cycle_names, field_path) {
Ok(out) => *value = serde_json::Value::String(out),
Err(es) => errors.extend(es),
}
}
serde_json::Value::Array(arr) => {
for (i, v) in arr.iter_mut().enumerate() {
let p = format!("{field_path}[{i}]");
resolve_placeholders_in_json(v, kernel, per_cycle_names, &p, errors);
}
}
serde_json::Value::Object(map) => {
for (k, v) in map.iter_mut() {
let p = format!("{field_path}.{k}");
resolve_placeholders_in_json(v, kernel, per_cycle_names, &p, errors);
}
}
_ => {}
}
}
fn resolve_placeholders_in_string(
s: &str,
kernel: &dyn polydat::Kernel,
per_cycle_names: &[String],
field_path: &str,
) -> Result<String, Vec<String>> {
let bytes = s.as_bytes();
let n = bytes.len();
let mut out = String::with_capacity(n);
let mut errors: Vec<String> = Vec::new();
let mut i = 0;
while i < n {
if bytes[i] == b'\\' && i + 1 < n && (bytes[i + 1] == b'{' || bytes[i + 1] == b'}') {
out.push(bytes[i] as char);
out.push(bytes[i + 1] as char);
i += 2;
continue;
}
if i + 1 < n && bytes[i] == b'{' && bytes[i + 1] == b'{' {
let start = i;
let mut j = i + 2;
while j + 1 < n && !(bytes[j] == b'}' && bytes[j + 1] == b'}') {
j += 1;
}
let end = (j + 2).min(n);
out.push_str(&s[start..end]);
i = end;
continue;
}
if bytes[i] != b'{' {
out.push(bytes[i] as char);
i += 1;
continue;
}
let body_start = i + 1;
let mut j = body_start;
while j < n && bytes[j] != b'}' {
j += 1;
}
if j >= n {
out.push('{');
i += 1;
continue;
}
let body = &s[body_start..j];
let after = j + 1;
if body.contains(':') {
out.push('{');
out.push_str(body);
out.push('}');
i = after;
continue;
}
if body.is_empty() {
out.push('{');
out.push_str(body);
out.push('}');
i = after;
continue;
}
if !is_bare_ident(body) {
out.push('{');
out.push_str(body);
out.push('}');
i = after;
continue;
}
if per_cycle_names.iter().any(|n| n == body) {
out.push('{');
out.push_str(body);
out.push('}');
i = after;
continue;
}
match polydat::kernel::interp::Lookup::lookup(
&polydat::kernel::interp::KernelLookup::new(kernel),
body,
) {
Some(v) => out.push_str(&v.to_display_string()),
None if kernel.output_index(body).is_some() => {
out.push('{');
out.push_str(body);
out.push('}');
}
None => {
errors.push(format!(
"{field_path}: '{{{body}}}' did not resolve in scope and is \
not a per-cycle binding declared by this phase"
));
out.push('{');
out.push_str(body);
out.push('}');
}
}
i = after;
}
if errors.is_empty() {
Ok(out)
} else {
Err(errors)
}
}
pub fn rewrite_inline_exprs(ops: &mut [ParsedOp]) -> HashMap<String, String> {
let mut inline_idx = 0usize;
let mut per_op_expr_to_name: Vec<HashMap<String, String>> =
(0..ops.len()).map(|_| HashMap::new()).collect();
let collect_from = |s: &str, idx: &mut usize, op_map: &mut HashMap<String, String>| {
for bp in nmbrs_workload::bindpoints::extract_bind_points(s) {
if let nmbrs_workload::bindpoints::BindPoint::InlineDefinition(ref expr) = bp {
op_map.entry(expr.clone()).or_insert_with(|| {
let n = format!("__expr_{idx}");
*idx += 1;
n
});
}
}
};
for (op_index, op) in ops.iter().enumerate() {
let op_map = &mut per_op_expr_to_name[op_index];
for value in op.op.values() {
if let Some(s) = value.as_str() {
collect_from(s, &mut inline_idx, op_map);
}
}
if let Some(s) = &op.condition {
collect_from(s, &mut inline_idx, op_map);
}
if let Some(spec) = &op.delay {
for name in spec.names() {
collect_from(name, &mut inline_idx, op_map);
}
}
}
let expr_to_name: HashMap<String, String> = per_op_expr_to_name
.iter()
.flat_map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())))
.collect();
if expr_to_name.is_empty() {
return expr_to_name;
}
use nmbrs_workload::model::BindingsDef;
for (op_index, op) in ops.iter_mut().enumerate() {
let op_map = &per_op_expr_to_name[op_index];
if op_map.is_empty() {
continue;
}
let mut entries: Vec<(&String, &String)> = op_map.iter().collect();
entries.sort_by(|a, b| a.1.cmp(b.1));
let mut synth_lines = String::new();
for (expr, name) in &entries {
synth_lines.push_str(&format!("\n{name} := {expr}"));
}
match &mut op.bindings {
BindingsDef::PolydatSource(s) => {
if s.trim().is_empty() {
*s = synth_lines.trim_start_matches('\n').to_string();
} else {
s.push_str(&synth_lines);
}
}
BindingsDef::Map(_) => {
if let BindingsDef::Map(map) = &op.bindings {
let mut existing = String::new();
for (k, v) in map.iter() {
existing.push_str(&format!("{k} := {v}\n"));
}
op.bindings = BindingsDef::PolydatSource(format!("{existing}{synth_lines}"));
}
}
}
let rewrite = |s: &str| -> String {
let mut rewritten = s.to_string();
for (expr, name) in op_map {
rewritten = rewritten.replace(&format!("{{{{{expr}}}}}"), &format!("{{{name}}}"));
rewritten = rewritten.replace(&format!("{{:={expr}:=}}"), &format!("{{{name}}}"));
rewritten = rewritten.replace(&format!("{{:={expr}}}"), &format!("{{{name}}}"));
rewritten = rewritten.replace(&format!("{{{expr}}}"), &format!("{{{name}}}"));
}
rewritten
};
for value in op.op.values_mut() {
if let Some(s) = value.as_str() {
*value = serde_json::Value::String(rewrite(s));
}
}
if let Some(s) = &op.condition {
op.condition = Some(rewrite(s));
}
if let Some(spec) = &op.delay {
op.delay = Some(match spec {
nmbrs_workload::model::DelaySpec::Before(name) => {
nmbrs_workload::model::DelaySpec::Before(rewrite(name))
}
nmbrs_workload::model::DelaySpec::BeforeAfter { before, after } => {
nmbrs_workload::model::DelaySpec::BeforeAfter {
before: before.as_deref().map(rewrite),
after: after.as_deref().map(rewrite),
}
}
});
}
}
expr_to_name
}
#[cfg(test)]
mod tests {
use super::*;
fn make_polydat_op(name: &str, stmt: &str, bindings: &str) -> ParsedOp {
let mut op = ParsedOp::simple(name, stmt);
op.bindings = BindingsDef::PolydatSource(bindings.to_string());
op
}
#[test]
fn objective_bare_wire_vs_inline_expression() {
assert!(objective_is_bare_wire("score"));
assert!(objective_is_bare_wire("err_rate"));
assert!(objective_is_bare_wire("_objective"));
assert!(objective_is_bare_wire(" recall99 ")); assert_eq!(objective_wire("score"), "score");
assert_eq!(objective_wire(" recall99 "), "recall99");
assert!(!objective_is_bare_wire("0 - err_rate"));
assert!(!objective_is_bare_wire("metricsql_scalar(\"x\")"));
assert!(!objective_is_bare_wire("a - b"));
assert!(!objective_is_bare_wire("q.cursor.depth"));
assert!(!objective_is_bare_wire(""));
assert_eq!(objective_wire("0 - err_rate"), OBJECTIVE_WIRE);
assert_eq!(objective_wire("metricsql_scalar(\"x\")"), OBJECTIVE_WIRE);
}
#[test]
fn inline_objective_synthesizes_volatile_objective_binding() {
use nmbrs_workload::model::{BindingsDef, OptimizeBlock, WorkloadPhase};
let phase = WorkloadPhase {
for_each: Some("rate in 1000, 2000".to_string()),
bindings: BindingsDef::PolydatSource("input cycle: u64\n".to_string()),
optimize: Some(OptimizeBlock {
method: "sweep".to_string(),
objective: "0 - metricsql_scalar(\"sum(rate(errors_total[3s]))\")".to_string(),
servo: vec!["rate".to_string()],
max_evals: 10,
seed: 0,
params: Default::default(),
}),
..Default::default()
};
let out = synthesize_phase_scope_bindings(&phase).expect("synthesis ok");
let src = match out {
BindingsDef::PolydatSource(s) => s,
other => panic!("expected PolydatSource, got {other:?}"),
};
assert!(
src.contains("volatile __objective := 0 - metricsql_scalar("),
"inline objective must be lowered to a `__objective` binding:\n{src}"
);
let mut bare = phase.clone();
bare.optimize.as_mut().unwrap().objective = "score".to_string();
let bare_out = synthesize_phase_scope_bindings(&bare).expect("synthesis ok");
if let BindingsDef::PolydatSource(s) = bare_out {
assert!(
!s.contains("__objective"),
"bare objective must not synthesize:\n{s}"
);
}
}
#[test]
fn inherited_bindings_dedup_across_ops() {
let bindings = "input cycle: u64\nprofiles := matching_profiles(\"example\", \"label\")";
let ops = vec![
make_polydat_op("op_a", "{profiles}", bindings),
make_polydat_op("op_b", "{profiles}", bindings),
];
let scope = build_scope(
&ops,
&HashMap::new(),
&[],
&HashMap::new(),
&HashMap::new(),
None,
&[],
None,
)
.unwrap();
scope.validate().unwrap();
let emitted = scope.emit();
let count = emitted.matches("profiles :=").count();
assert_eq!(
count, 1,
"expected exactly 1 'profiles :=' in emitted scope, got {count}:\n{emitted}"
);
}
#[test]
fn iteration_vars_dont_conflict_with_inherited() {
let bindings = "input cycle: u64\nprofiles := matching_profiles(\"example\", \"label\")";
let ops = vec![
make_polydat_op("op_a", "{profiles} {table}", bindings),
make_polydat_op("op_b", "{profiles} {table}", bindings),
];
let mut iter_vars = HashMap::new();
iter_vars.insert("table".to_string(), "vec_default".to_string());
let scope = build_scope(
&ops,
&iter_vars,
&[],
&HashMap::new(),
&HashMap::new(),
None,
&[],
None,
)
.unwrap();
scope.validate().unwrap();
let emitted = scope.emit();
assert!(
emitted.contains("extern table: String"),
"expected extern table declaration in:\n{emitted}"
);
assert!(
emitted.contains("profiles :="),
"expected profiles in:\n{emitted}"
);
}
#[test]
fn op_augmentation_adds_new_names() {
let base = "input cycle: u64\nfoo := hash(cycle)";
let augmented = "input cycle: u64\nfoo := hash(cycle)\nbar := mod(cycle, 100)";
let ops = vec![
make_polydat_op("op_a", "{foo}", base),
make_polydat_op("op_b", "{foo} {bar}", augmented),
];
let scope = build_scope(
&ops,
&HashMap::new(),
&[],
&HashMap::new(),
&HashMap::new(),
None,
&[],
None,
)
.unwrap();
scope.validate().unwrap();
let emitted = scope.emit();
assert!(emitted.contains("foo := hash(cycle)"), "missing foo");
assert!(emitted.contains("bar := mod(cycle, 100)"), "missing bar");
}
#[test]
fn real_shadow_is_caught() {
let base = "input cycle: u64\nfoo := hash(cycle)";
let shadow = "input cycle: u64\nfoo := mod(cycle, 100)";
let ops = vec![
make_polydat_op("op_a", "{foo}", base),
make_polydat_op("op_b", "{foo}", shadow),
];
let scope = build_scope(
&ops,
&HashMap::new(),
&[],
&HashMap::new(),
&HashMap::new(),
None,
&[],
None,
)
.unwrap();
let result = scope.validate();
assert!(result.is_err(), "expected shadow error");
let err = result.unwrap_err();
assert!(
err.contains("shadows"),
"expected 'shadows' in error: {err}"
);
assert!(err.contains("op_b"), "expected op name in error: {err}");
}
#[test]
fn original_bug_repro_no_false_shadow() {
let bindings = "input cycle: u64\nprofiles := matching_profiles(\"example\", \"label\")";
let ops = vec![
make_polydat_op(
"drop_metadata_index",
"DROP INDEX {table}_meta_idx",
bindings,
),
make_polydat_op("drop_vector_index", "DROP INDEX {table}_idx", bindings),
make_polydat_op("drop_table", "DROP TABLE {table}", bindings),
];
let mut iter_vars = HashMap::new();
iter_vars.insert("table".to_string(), "fknn_default".to_string());
iter_vars.insert("spec".to_string(), "example:default".to_string());
iter_vars.insert("optimize_for".to_string(), "RECALL".to_string());
let scope = build_scope(
&ops,
&iter_vars,
&[],
&HashMap::new(),
&HashMap::new(),
None,
&[],
None,
)
.unwrap();
scope.validate().unwrap();
let emitted = scope.emit();
assert!(
emitted.contains("extern table: String"),
"missing extern table in:\n{emitted}"
);
assert!(emitted.contains("profiles :="), "missing profiles");
let count = emitted.matches("profiles :=").count();
assert_eq!(count, 1, "profiles duplicated in:\n{emitted}");
}
fn parent_kernel_with_load() -> crate::scope_kernel::ScopeKernel {
crate::bindings::compile_scope_kernel(
"input cycle: u64\n\
const dim := 128\n\
shared budget := 100\n\
load := add(cycle, 1)\n",
&Default::default(),
)
.expect("compile parent")
}
fn op_with_body(name: &str, body: &str) -> ParsedOp {
let mut op = ParsedOp::simple(name, "noop");
op.bindings = BindingsDef::PolydatSource(body.into());
op
}
#[test]
fn op_template_referencing_cycle_input_is_accepted() {
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let op = op_with_body("step_op", "step := add(cycle, 1)\n");
let result = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"test",
);
assert!(
result.is_ok(),
"cycle is a parent input — should be accepted. err: {:?}",
result.err()
);
}
#[test]
fn op_template_kernel_exposes_synthesised_metric_bindings() {
use nmbrs_workload::model::MetricSpec;
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let mut op = op_with_body("m_op", "measured := add(cycle, 1)\n");
op.metrics.insert(
"bytes_out".to_string(),
MetricSpec {
value: "measured".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
let kernel = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"test",
)
.expect("op-template kernel builds");
let outs = kernel.program().output_names();
assert!(
outs.contains(&"__metric_bytes_out"),
"the synthesised metric binding must be an output of the \
op-template kernel; outputs: {outs:?}"
);
}
#[test]
fn op_template_referencing_constant_output_is_accepted() {
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let op = op_with_body("calc_op", "scaled := mul(dim, 2)\n");
let result = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"test",
);
assert!(
result.is_ok(),
"final/folded output should be accepted. err: {:?}",
result.err()
);
}
#[test]
fn op_template_referencing_shared_output_is_accepted() {
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let op = op_with_body("budget_op", "remaining := add(budget, 1)\n");
let result = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"test",
);
assert!(
result.is_ok(),
"shared output should be accepted. err: {:?}",
result.err()
);
}
#[test]
fn op_template_referencing_dynamic_output_accepted_per_srd_13f() {
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let op = op_with_body("forecast_op", "forecast := mul(load, 2)\n");
let kernel = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"test",
)
.expect("op-template kernel synth should accept dynamic parent ref");
assert!(
kernel.program().find_input("load").is_some(),
"extern load slot should land on op-template kernel"
);
assert!(
kernel.program().output_names().contains(&"forecast"),
"op-local binding should be an output"
);
}
#[test]
fn op_template_pvs_query_full_shape_with_workload_params() {
use nmbrs_workload::model::MetricSpec;
let parent_src = r#"
extern k: u64
extern limit: u64
extern optimize_for: String
extern table: String
"#;
let parent = crate::bindings::compile_scope_kernel(
parent_src,
&polydat::dsl::compile::CompileOptions {
context: "parent".to_string(),
..Default::default()
},
)
.expect("parent compile");
let manifest: Vec<crate::runner::ManifestEntry> =
polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let body = "const prebuffered := dataset_prebuffer(\"{dataset}:{profile}\")\n\
const query_counts := query_count(prebuffered)\n\
cursor q = range(0, query_counts * 10)\n\
query_vector := query_vector_at(prebuffered, q % query_counts)\n\
predicate := predicate_value_at(prebuffered, q % query_counts)\n\
ground_truth := filtered_neighbor_indices_at(prebuffered, q % query_counts)\n\
latency_factor := 0.979 + 4.021 * pow(limit, -0.761)\n\
recall_factor := 0.509 + 9.491 * pow(limit, -0.402)\n\
overscan := if(optimize_for == \"LATENCY\", latency_factor, recall_factor)\n";
let mut op = op_with_body("select_ann", body);
op.op.insert(
"prepared".into(),
serde_json::json!(
"SELECT key,value FROM {keyspace}.{table} \
WHERE metadata = {predicate} \
ORDER BY value ANN OF {query_vector} LIMIT {limit}"
),
);
op.metrics.insert(
"overscan".into(),
MetricSpec {
value: "overscan".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
let mut workload_params = HashMap::new();
workload_params.insert("dataset".into(), "example".into());
workload_params.insert("profile".into(), "label_00".into());
workload_params.insert("keyspace".into(), "baselines".into());
let kernel = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&workload_params,
vec![],
None,
false,
polydat::kernel::KernelOptLevel::Release,
"pvs_query.select_ann",
)
.expect("op-template kernel synth");
let outs: Vec<String> = kernel
.program()
.output_names()
.iter()
.map(|s| s.to_string())
.collect();
for required in &["overscan", "latency_factor", "recall_factor"] {
assert!(
outs.iter().any(|o| o == required),
"op-template kernel missing '{required}'; outputs: {outs:?}"
);
}
for param in &["dataset", "profile", "keyspace"] {
assert!(
kernel.program().find_input(param).is_none(),
"workload param '{param}' must NOT be an extern input \
on the op-template kernel — cascade should emit \
it as `final` so init bindings fold. Inputs: {:?}",
kernel.program().input_names(),
);
}
}
#[test]
fn op_template_with_pow_and_if_keeps_all_outputs() {
let parent_src = r#"
extern k: u64
extern limit: u64
extern optimize_for: String
extern table: String
extern dataset: String
extern profile: String
extern keyspace: String
"#;
let parent = crate::bindings::compile_scope_kernel(
parent_src,
&polydat::dsl::compile::CompileOptions {
context: "parent".to_string(),
..Default::default()
},
)
.expect("parent compile");
let manifest: Vec<crate::runner::ManifestEntry> =
polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let body = "const prebuffered := dataset_prebuffer(\"dummy:default\")\n\
const query_counts := query_count(prebuffered)\n\
cursor q = range(0, query_counts * 10)\n\
query_vector := query_vector_at(prebuffered, q % query_counts)\n\
predicate := predicate_value_at(prebuffered, q % query_counts)\n\
ground_truth := filtered_neighbor_indices_at(prebuffered, q % query_counts)\n\
latency_factor := 0.979 + 4.021 * pow(limit, -0.761)\n\
recall_factor := 0.509 + 9.491 * pow(limit, -0.402)\n\
overscan := if(optimize_for == \"LATENCY\", latency_factor, recall_factor)\n";
let op = op_with_body("select_ann", body);
let kernel = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"pvs_query.select_ann",
)
.expect("op-template kernel synth");
let outs: Vec<String> = kernel
.program()
.output_names()
.iter()
.map(|s| s.to_string())
.collect();
for required in &[
"query_vector",
"predicate",
"ground_truth",
"latency_factor",
"recall_factor",
"overscan",
] {
assert!(
outs.iter().any(|o| o == required),
"op-template kernel missing '{required}'; outputs: {outs:?}"
);
}
}
#[test]
fn op_template_relevancy_k_r_bare_wire_names_cascade() {
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let mut op = ParsedOp::simple("read", "noop");
op.bindings = BindingsDef::PolydatSource("".into());
op.params.insert(
"relevancy".into(),
serde_json::json!({
"actual": "rows",
"expected": "ground_truth",
"k": "k_value",
"r": "limit_value",
"functions": ["recall"],
}),
);
let _ = manifest;
let kernel_src = "\
input cycle: u64\n\
const rows := 10\n\
const ground_truth := \"1,2,3\"\n\
const k_value := 5\n\
const limit_value := 100\n";
let real_parent =
crate::scope_kernel::ScopeKernel::compile(kernel_src).expect("parent compile");
let real_manifest = polydat::kernel::extract_manifest(real_parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let kernel = build_op_template_scope_kernel(
&op,
&real_manifest,
&real_parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"relevancy-cascade-test",
)
.expect("op-template kernel synth");
for name in &["rows", "ground_truth", "k_value", "limit_value"] {
assert!(
kernel.lookup(name).is_some(),
"relevancy wire '{name}' should be visible on the \
op-template kernel (cascaded extern); kernel had \
outputs: {outs:?}",
outs = kernel.program().output_names()
);
}
}
#[test]
fn op_template_metric_value_count_allocates_magic_extern_slot() {
let parent = parent_kernel_with_load();
let manifest = polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect::<Vec<_>>();
let mut op = ParsedOp::simple("read", "noop");
op.bindings = BindingsDef::PolydatSource("".into());
op.metrics.insert(
"rows_per_op".into(),
nmbrs_workload::model::MetricSpec {
value: "count".into(),
family: None,
kind: Some(nmbrs_workload::model::MetricKind::Gauge),
unit: None,
format: None,
cell: Default::default(),
},
);
let kernel = build_op_template_scope_kernel(
&op,
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
false,
polydat::kernel::KernelOptLevel::Release,
"metric-walker-test",
)
.expect("op-template kernel synth");
let inputs = kernel.program().input_names();
assert!(
inputs.iter().any(|i| i == "count"),
"metric `value: count` should force the `count` magic-extern \
input slot to be allocated under Release opt level; \
inputs were: {inputs:?}"
);
let outs = kernel.program().output_names();
let synth = synthesize_metric_binding_name("rows_per_op");
assert!(
outs.iter().any(|o| o == &synth),
"synthesised `{synth}` binding should be a kernel output; \
outputs were: {outs:?}"
);
}
#[test]
fn promoted_final_emits_inline_literal_for_str() {
let parent = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\nconst dataset := \"example\"\n",
)
.expect("compile parent");
let manifest: Vec<crate::runner::ManifestEntry> =
polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let ops = vec![make_polydat_op("step", "x={dataset}", "input cycle: u64")];
let scope = build_scope(
&ops,
&HashMap::new(),
&manifest,
&HashMap::new(),
&HashMap::new(),
None,
&[],
Some(&parent),
)
.expect("build_scope");
let emitted = scope.emit();
assert!(
emitted.contains("const dataset := \"example\""),
"expected promoted-final emission, got:\n{emitted}"
);
assert!(
!emitted.contains("extern dataset"),
"expected no extern for promoted-final dataset, got:\n{emitted}"
);
}
#[test]
fn promoted_final_emits_inline_literal_for_u64() {
let parent =
crate::scope_kernel::ScopeKernel::compile("input cycle: u64\nconst count := 42\n")
.expect("compile parent");
let manifest: Vec<crate::runner::ManifestEntry> =
polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let ops = vec![make_polydat_op("step", "n={count}", "input cycle: u64")];
let scope = build_scope(
&ops,
&HashMap::new(),
&manifest,
&HashMap::new(),
&HashMap::new(),
None,
&[],
Some(&parent),
)
.expect("build_scope");
let emitted = scope.emit();
assert!(
emitted.contains("const count := 42"),
"expected promoted-final u64 emission, got:\n{emitted}"
);
}
#[test]
fn unresolved_wire_reference_surfaces_validation_error() {
let parent = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\nconst dataset := \"example\"\n",
)
.expect("compile parent");
let manifest: Vec<crate::runner::ManifestEntry> =
polydat::kernel::extract_manifest(parent.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let ops = vec![make_polydat_op("step", "x={tirp}", "input cycle: u64")];
let err = match build_scope(
&ops,
&HashMap::new(),
&manifest,
&HashMap::new(),
&HashMap::new(),
None,
&[],
Some(&parent),
) {
Ok(_) => panic!("expected unresolved-wire error, got Ok"),
Err(e) => e,
};
assert!(err.contains("unresolved wire"), "wrong error: {err}");
assert!(
err.contains("tirp"),
"error should mention the typoed name: {err}"
);
assert!(
err.contains("Visible names"),
"error should list visible names: {err}"
);
}
#[test]
fn ingest_preserves_bindings_when_comment_contains_apostrophe() {
let mut scope = BindingScope::new();
let src_with_apostrophe_comment = "# full_cql_vector's bindings block\n\
shared has_a := true\n\
shared has_b := false\n";
scope.ingest_polydat_source(src_with_apostrophe_comment, BindingOrigin::Inherited);
let defined = scope.defined_names();
assert!(
defined.contains("has_a"),
"comment with apostrophe must NOT consume subsequent bindings; \
expected has_a in defined names, got {defined:?}"
);
assert!(
defined.contains("has_b"),
"expected has_b in defined names, got {defined:?}"
);
let emitted = scope.emit();
assert!(
emitted.contains("shared has_a := true"),
"scope.emit() must include the shared bindings; got:\n{emitted}"
);
assert!(
emitted.contains("shared has_b := false"),
"scope.emit() must include the shared bindings; got:\n{emitted}"
);
}
#[test]
fn ingest_then_compile_preserves_shared_modifier() {
let mut scope = BindingScope::new();
let workload_polydat = "shared has_sai_column_indexes := false\n\
shared has_indexes := false\n";
scope.ingest_polydat_source(workload_polydat, BindingOrigin::Inherited);
let source = scope.emit();
let kernel = crate::scope_kernel::ScopeKernel::compile(&source)
.unwrap_or_else(|e| panic!("compile failed for source:\n{source}\nerror: {e}"));
let shared = kernel.program().shared_outputs();
assert!(
shared.contains(&"has_sai_column_indexes"),
"expected `has_sai_column_indexes` in shared_outputs after scope-ingest/emit/compile;\n\
got shared_outputs={shared:?}\nemitted source:\n{source}",
);
assert!(
shared.contains(&"has_indexes"),
"expected `has_indexes` in shared_outputs after scope-ingest/emit/compile;\n\
got shared_outputs={shared:?}\nemitted source:\n{source}",
);
}
#[test]
fn synthesize_phase_scope_bindings_passthrough_when_no_poll() {
use nmbrs_workload::model::{BindingsDef, WorkloadPhase};
let phase = WorkloadPhase {
bindings: BindingsDef::PolydatSource("k := 5\n".into()),
poll: None,
..Default::default()
};
let out = synthesize_phase_scope_bindings(&phase).expect("no-poll synthesis is a no-op");
match out {
BindingsDef::PolydatSource(s) => assert_eq!(
s, "k := 5\n",
"no-poll should return the original bindings unchanged"
),
other => panic!("expected PolydatSource, got {other:?}"),
}
}
#[test]
fn phase_metrics_reify_cell_coordinates_as_kernel_bindings() {
use nmbrs_workload::model::{BindingsDef, MetricSpec, WorkloadPhase};
let mut metrics = std::collections::HashMap::new();
let mut cell = std::collections::BTreeMap::new();
cell.insert("tier".to_string(), "tier_name".to_string());
metrics.insert(
"bytes_out".to_string(),
MetricSpec {
value: "history_bytes_out".into(),
family: None,
kind: None,
unit: None,
format: None,
cell,
},
);
let phase = WorkloadPhase {
bindings: BindingsDef::PolydatSource(
"extern tier_name: str = \"\"\n\
extern history_bytes_out: u64 = 0\n"
.into(),
),
metrics,
poll: None,
..Default::default()
};
let out = synthesize_phase_scope_bindings(&phase).expect("synthesis");
let src = match out {
BindingsDef::PolydatSource(s) => s,
other => panic!("expected PolydatSource, got {other:?}"),
};
assert!(
src.contains("volatile __cell_bytes_out__tier := tier_name"),
"coordinate must be emitted as a volatile binding; got:\n{src}"
);
assert!(
src.contains("volatile __metric_bytes_out := history_bytes_out"),
"the value binding must still be emitted; got:\n{src}"
);
}
#[test]
fn two_metrics_in_one_dimension_get_distinct_coordinate_wires() {
assert_eq!(
synthesize_cell_binding_name("bytes_out", "tier"),
"__cell_bytes_out__tier"
);
assert_ne!(
synthesize_cell_binding_name("bytes_out", "tier"),
synthesize_cell_binding_name("bytes_in", "tier")
);
}
#[test]
fn synthesize_phase_scope_bindings_emits_metric_bindings() {
use nmbrs_workload::model::{BindingsDef, MetricSpec, WorkloadPhase};
let mut metrics = std::collections::HashMap::new();
metrics.insert(
"time_to_index".to_string(),
MetricSpec {
value: "current_epoch_millis() - phase_start".into(),
family: None,
kind: None,
unit: None,
format: None,
cell: Default::default(),
},
);
let phase = WorkloadPhase {
bindings: BindingsDef::default(),
metrics,
poll: None,
..Default::default()
};
let out = synthesize_phase_scope_bindings(&phase).expect("metrics synthesis");
let src = match out {
BindingsDef::PolydatSource(s) => s,
other => panic!("expected PolydatSource, got {other:?}"),
};
assert!(
src.contains("volatile phase_start := phase_start_millis()"),
"must bind phase_start to the phase-scoped clock; got:\n{src}"
);
assert!(
!src.contains("extern phase_start"),
"the fill-me-in extern must be gone; got:\n{src}"
);
assert!(
src.contains("volatile __metric_time_to_index := current_epoch_millis() - phase_start"),
"must emit the volatile metric binding; got:\n{src}"
);
let kernel = crate::scope_kernel::ScopeKernel::compile(&src)
.unwrap_or_else(|e| panic!("compile failed:\n{src}\nerror: {e}"));
assert!(
kernel
.program()
.output_names()
.contains(&"__metric_time_to_index"),
"metric binding must be a kernel output; outputs: {:?}",
kernel.program().output_names()
);
}
#[test]
fn synthesize_phase_scope_bindings_emits_shared_captures_and_predicate() {
use nmbrs_workload::bindpoints::CapturePoint;
use nmbrs_workload::model::{BindingsDef, ParsedOp, PhasePollSpec, WorkloadPhase};
let mut op = ParsedOp::simple("read_state", "noop");
op.captures = vec![
CapturePoint {
row_filter: None,
source_name: "sstables".into(),
as_name: "sstables".into(),
cast_type: None,
slurp: false,
path: Some("/0/value".into()),
count: false,
agg: None,
},
CapturePoint {
row_filter: None,
source_name: "active_for_cf".into(),
as_name: "active_for_cf".into(),
cast_type: None,
slurp: false,
path: Some("/1/value".into()),
count: true,
agg: None,
},
];
let phase = WorkloadPhase {
ops: vec![op],
bindings: BindingsDef::PolydatSource("dummy := 1\n".into()),
poll: Some(PhasePollSpec {
until: "sstables == 1 && active_for_cf == 0".into(),
..Default::default()
}),
..Default::default()
};
let out = synthesize_phase_scope_bindings(&phase).expect("poll synthesis should succeed");
let src = match out {
BindingsDef::PolydatSource(s) => s,
other => panic!("expected PolydatSource, got {other:?}"),
};
assert!(
src.contains("shared sstables := 0"),
"missing shared declaration for sstables; source:\n{src}"
);
assert!(
src.contains("shared active_for_cf := 0"),
"missing shared declaration for active_for_cf; source:\n{src}"
);
assert!(
src.contains("dummy := 1"),
"original phase bindings should appear verbatim; source:\n{src}"
);
assert!(
src.contains("__poll_until := sstables == 1 && active_for_cf == 0"),
"missing __poll_until predicate binding; source:\n{src}"
);
let shared_pos = src
.find("shared sstables")
.expect("shared sstables present");
let until_binding = crate::wrappers::condition::UNTIL_BINDING;
let until_pos = src
.find(until_binding)
.expect("poll predicate binding present");
assert!(
shared_pos < until_pos,
"shared captures must precede __poll_until in the synthesized source"
);
}
#[test]
fn synthesize_phase_scope_bindings_rejects_capture_name_collision() {
use nmbrs_workload::bindpoints::CapturePoint;
use nmbrs_workload::model::{BindingsDef, ParsedOp, PhasePollSpec, WorkloadPhase};
let mut op = ParsedOp::simple("read_state", "noop");
op.captures = vec![CapturePoint {
row_filter: None,
source_name: "sstables".into(),
as_name: "sstables".into(),
cast_type: None,
slurp: false,
path: Some("/0/value".into()),
count: false,
agg: None,
}];
let phase = WorkloadPhase {
ops: vec![op],
bindings: BindingsDef::PolydatSource("sstables := 7\n".into()),
poll: Some(PhasePollSpec {
until: "sstables == 1".into(),
..Default::default()
}),
..Default::default()
};
let err =
synthesize_phase_scope_bindings(&phase).expect_err("colliding capture name must error");
assert!(
err.contains("sstables") && err.contains("collision"),
"expected error to name 'sstables' and 'collision'; got: {err}"
);
}
#[test]
fn build_phase_scope_kernel_with_mod_in_binding_over_partition_iter_var() {
let parent_kernel = crate::scope_synth::build_for_each_scope_kernel(
&[("p".to_string(), "partitions(\"linear:3\")".to_string())],
&[],
&crate::scope_kernel::ScopeKernel::compile("\n").unwrap(),
&HashMap::new(),
Vec::new(),
None,
false,
"test_for_each",
None,
)
.expect("for-each scope synthesis");
let phase_bindings = nmbrs_workload::model::BindingsDef::PolydatSource(
"n := mod_in(cycle, p)\n".to_string(),
);
let phase_kernel = build_phase_scope_kernel(
&phase_bindings,
&[],
&parent_kernel,
&HashMap::new(),
Vec::new(),
None,
false,
"test_phase",
)
.expect("phase kernel build with mod_in(cycle, p) binding");
assert_eq!(
phase_kernel.program().input_port_type("p"),
Some(polydat::ast::PortType::Ext),
"phase kernel's `p` must be Ext-typed",
);
assert!(
phase_kernel.program().output_names().contains(&"n"),
"phase kernel should expose `n` as an output"
);
}
#[test]
fn build_phase_scope_kernel_cascades_partition_iter_var_as_extern_ext() {
let parent_kernel = crate::scope_synth::build_for_each_scope_kernel(
&[("p".to_string(), "partitions(\"linear:3\")".to_string())],
&[], &crate::scope_kernel::ScopeKernel::compile("\n").unwrap(),
&HashMap::new(),
Vec::new(),
None,
false,
"test_for_each",
None,
)
.expect("for-each scope synthesis");
assert_eq!(
parent_kernel.program().input_port_type("p"),
Some(polydat::ast::PortType::Ext),
"for-each scope's iter-var `p` must declare as Ext",
);
let phase_bindings = nmbrs_workload::model::BindingsDef::PolydatSource(
"cursor row = range(0, 1000) over p\n".to_string(),
);
let phase_kernel = build_phase_scope_kernel(
&phase_bindings,
&[],
&parent_kernel,
&HashMap::new(),
Vec::new(),
None,
false,
"test_phase",
)
.expect("phase kernel build");
let port_type = phase_kernel.program().input_port_type("p");
assert_eq!(
port_type,
Some(polydat::ast::PortType::Ext),
"phase kernel's `p` slot must be Ext (preserved through cascade), got {port_type:?}"
);
}
#[test]
fn build_phase_scope_kernel_cascades_shared_bool_as_extern_bool() {
let parent = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\nshared has_sai_column_indexes := false\n\
shared has_indexes := false\n",
)
.expect("parent compile");
let phase_bindings = nmbrs_workload::model::BindingsDef::PolydatSource(
"target_index_table := pick(has_sai_column_indexes, has_indexes, \
\"a\", \"b\")\n"
.to_string(),
);
let phase_kernel = build_phase_scope_kernel(
&phase_bindings,
&[], &parent,
&HashMap::new(),
Vec::new(),
None,
false,
"test_phase",
)
.expect("phase kernel build");
let idx_sai = phase_kernel.program().find_input("has_sai_column_indexes");
assert!(
idx_sai.is_some(),
"phase kernel must have `has_sai_column_indexes` input slot"
);
let idx = idx_sai.unwrap();
let port_type = phase_kernel
.program()
.input_port_type("has_sai_column_indexes");
assert_eq!(
port_type,
Some(polydat::ast::PortType::Bool),
"phase kernel's has_sai_column_indexes slot must be Bool, got {port_type:?}"
);
let kind = phase_kernel.program().input_kind(idx);
assert_ne!(
kind,
Some(polydat::kernel::InputKind::Coordinate),
"phase kernel's has_sai_column_indexes slot must NOT be Coordinate; got {kind:?}"
);
}
#[test]
fn executor_build_scope_emits_extern_bool_for_cell_backed_shared_wire() {
use polydat::kernel::extract_manifest;
let root = crate::scope_kernel::ScopeKernel::compile(
"shared has_a := true\n\
shared has_b := false\n\
selector := mod(cycle, 1)\n",
)
.expect("workload root compile");
let for_each = crate::scope_synth::build_for_each_scope_kernel(
&[("outer".to_string(), "p1,p2".to_string())],
&extract_manifest(root.program()),
&root,
&HashMap::new(),
Vec::new(),
None,
false,
"test_for_each",
None,
)
.expect("for_each synth");
let phase_bindings = nmbrs_workload::model::BindingsDef::PolydatSource(
"chosen := pick(has_a, has_b, \"alpha\", \"beta\")\n".to_string(),
);
let phase_scope = build_phase_scope_kernel(
&phase_bindings,
&[],
&for_each,
&HashMap::new(),
Vec::new(),
None,
false,
"test_phase",
)
.expect("phase scope synth");
let op = ParsedOp::simple("report", "consume chosen={chosen}");
let ops = vec![op];
let effective_manifest: Vec<crate::runner::ManifestEntry> =
extract_manifest(phase_scope.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let scope = build_scope(
&ops,
&HashMap::new(),
&effective_manifest,
&HashMap::new(),
&HashMap::new(),
None,
&[],
Some(&phase_scope),
)
.expect("executor build_scope");
let emitted = scope.emit();
let required = scope.required_outputs();
let executor_kernel = polydat::dsl::compile::compile_polydat_interpreter_with_options(
&emitted,
&polydat::dsl::compile::CompileOptions {
required_outputs: required.clone(),
context: "test".to_string(),
..Default::default()
},
None,
)
.unwrap_or_else(|e| {
panic!("compile failed: {e}\nemitted source:\n{emitted}\nrequired: {required:?}")
});
if let Some(idx) = executor_kernel.program().find_input("has_a") {
let typ = executor_kernel.program().input_port_type("has_a");
assert_eq!(
typ,
Some(polydat::ast::PortType::Bool),
"executor kernel's has_a slot must be Bool;\n\
got {typ:?}\n\
emitted source:\n{emitted}\n\
input names: {:?}\n\
coord_count: {}",
executor_kernel.program().input_names(),
executor_kernel.program().coord_count()
);
let kind = executor_kernel.program().input_kind(idx);
assert_ne!(
kind,
Some(polydat::kernel::InputKind::Coordinate),
"executor kernel's has_a slot must NOT be Coordinate;\n\
got {kind:?}\n\
emitted source:\n{emitted}\n\
input names: {:?}\n\
coord_count: {}",
executor_kernel.program().input_names(),
executor_kernel.program().coord_count()
);
}
}
#[test]
fn full_chain_workload_to_executor_scope_preserves_shared_bool() {
use nmbrs_workload::model::ParsedOp;
use polydat::kernel::extract_manifest;
let mut workload_params: HashMap<String, String> = HashMap::new();
workload_params.insert("dataset".to_string(), "example_dataset".to_string());
workload_params.insert("prefix".to_string(), "px_".to_string());
let mut sorted: Vec<&String> = workload_params.keys().collect();
sorted.sort();
let mut params_source = String::new();
for k in sorted {
params_source.push_str(&format!("const {k} := \"{}\"\n", workload_params[k]));
}
let params_kernel =
crate::scope_kernel::ScopeKernel::compile(¶ms_source).expect("params compile");
let mut scope = build_scope(
&[] as &[ParsedOp],
&HashMap::new(),
&[],
&workload_params,
&HashMap::new(),
None,
&[],
None,
)
.expect("workload root build_scope");
let workload_level_polydat = "combo_label := str_concat(\"{dataset}\", \"{prefix}\")\n\
shared has_a := true\n\
shared has_b := false\n";
scope.ingest_polydat_source(workload_level_polydat, BindingOrigin::Inherited);
let root_source = scope.emit();
let root = crate::scope_kernel::ScopeKernel::build_under(
params_kernel.kernel(),
crate::scope_kernel::SourceMatter::source(
"test_root",
root_source.clone(),
polydat::kernel::subcontext::CompileOptions {
workload_dir: None,
polydat_lib_paths: Vec::new(),
strict: false,
required_outputs: scope.required_outputs(),
context_label: Some("test_root".to_string()),
cursor_limit: None,
..Default::default()
},
),
)
.expect("root build");
let shared = root.program().shared_outputs();
assert!(
shared.contains(&"has_a"),
"root should have has_a as SHARED output; got {shared:?}"
);
let outer_fe = crate::scope_synth::build_for_each_scope_kernel(
&[("outer".to_string(), "p1,p2".to_string())],
&extract_manifest(root.program()),
&root,
&workload_params,
Vec::new(),
None,
false,
"test_outer_fe",
None,
)
.expect("outer for_each synth");
let inner_fe = crate::scope_synth::build_for_each_scope_kernel(
&[
("inner".to_string(), "lo,hi".to_string()),
("label".to_string(), "tag_p1_lo,tag_p1_hi".to_string()),
],
&extract_manifest(outer_fe.program()),
&outer_fe,
&workload_params,
Vec::new(),
None,
false,
"test_inner_fe",
None,
)
.expect("inner for_each synth");
let phase_bindings = BindingsDef::PolydatSource(
"chosen := pick(has_a, has_b, \"alpha\", \"beta\")\n".to_string(),
);
let phase_scope = build_phase_scope_kernel(
&phase_bindings,
&[],
&inner_fe,
&workload_params,
Vec::new(),
None,
false,
"test_consume_phase",
)
.expect("consume phase synth");
let op = ParsedOp::simple("report", "spc/consume chosen={chosen}");
let ops = vec![op];
let effective_manifest: Vec<crate::runner::ManifestEntry> =
extract_manifest(phase_scope.program())
.into_iter()
.map(|e| crate::runner::ManifestEntry {
name: e.name,
port_type: e.port_type,
modifier: e.modifier,
})
.collect();
let exec_scope = build_scope(
&ops,
&HashMap::new(),
&effective_manifest,
&HashMap::new(),
&HashMap::new(),
None,
&[],
Some(&phase_scope),
)
.expect("executor build_scope");
let exec_source = exec_scope.emit();
let exec_kernel = polydat::dsl::compile::compile_polydat_interpreter_with_options(
&exec_source,
&polydat::dsl::compile::CompileOptions {
required_outputs: exec_scope.required_outputs(),
context: "test_executor".to_string(),
..Default::default()
},
None,
)
.unwrap_or_else(|e| panic!("exec compile failed: {e}\nexec source:\n{exec_source}"));
if let Some(idx) = exec_kernel.program().find_input("has_a") {
let typ = exec_kernel.program().input_port_type("has_a");
assert_eq!(
typ,
Some(polydat::ast::PortType::Bool),
"FINAL kernel has_a must be Bool, got {typ:?}\n\
exec source:\n{exec_source}\n\
exec input_names: {:?}\n\
exec coord_count: {}",
exec_kernel.program().input_names(),
exec_kernel.program().coord_count()
);
let kind = exec_kernel.program().input_kind(idx);
assert_ne!(
kind,
Some(polydat::kernel::InputKind::Coordinate),
"FINAL kernel has_a must NOT be Coordinate, got {kind:?}\n\
exec source:\n{exec_source}\n\
exec input_names: {:?}\n\
exec coord_count: {}",
exec_kernel.program().input_names(),
exec_kernel.program().coord_count()
);
}
}
#[test]
fn workload_root_via_compile_bindings_preserves_shared_bool_type() {
use nmbrs_workload::model::ParsedOp;
let mut workload_params: HashMap<String, String> = HashMap::new();
workload_params.insert("dataset".to_string(), "example_dataset".to_string());
workload_params.insert("prefix".to_string(), "px_".to_string());
workload_params.insert("keyspace".to_string(), "ks1".to_string());
workload_params.insert("inner_options".to_string(), "lo,hi".to_string());
let mut params_source = String::new();
let mut sorted: Vec<&String> = workload_params.keys().collect();
sorted.sort();
for k in sorted {
let v = &workload_params[k];
params_source.push_str(&format!("const {k} := \"{v}\"\n"));
}
let params_kernel =
crate::scope_kernel::ScopeKernel::compile(¶ms_source).expect("params compile");
let workload_level_polydat = "selector := mod(cycle, 1)\n\
shared has_a := true\n\
shared has_b := false\n";
let mut scope = build_scope(
&[] as &[ParsedOp],
&HashMap::new(),
&[],
&workload_params,
&HashMap::new(),
None,
&[],
None,
)
.expect("build_scope");
scope.ingest_polydat_source(workload_level_polydat, BindingOrigin::Inherited);
let source = scope.emit();
let opts = polydat::kernel::subcontext::CompileOptions {
workload_dir: None,
polydat_lib_paths: Vec::new(),
strict: false,
required_outputs: scope.required_outputs(),
context_label: Some("test_workload_root".to_string()),
cursor_limit: None,
..Default::default()
};
let root = crate::scope_kernel::ScopeKernel::build_under(
params_kernel.kernel(),
crate::scope_kernel::SourceMatter::source("test_workload_root", source.clone(), opts),
)
.expect("workload root build");
let has_a_idx = root.program().find_input("has_a").unwrap_or_else(|| {
panic!(
"workload root missing has_a input;\n\
emitted scope source:\n{source}\n\
input names: {:?}",
root.program().input_names()
)
});
let typ = root.program().input_port_type("has_a");
assert_eq!(
typ,
Some(polydat::ast::PortType::Bool),
"workload root has_a must be Bool;\n\
got {typ:?}\n\
emitted source:\n{source}\n\
input names: {:?}\n\
coord_count: {}",
root.program().input_names(),
root.program().coord_count()
);
let kind = root.program().input_kind(has_a_idx);
assert_ne!(
kind,
Some(polydat::kernel::InputKind::Coordinate),
"workload root has_a must NOT be Coordinate;\n\
got {kind:?}\n\
emitted source:\n{source}\n\
input names: {:?}\n\
coord_count: {}",
root.program().input_names(),
root.program().coord_count()
);
let modifier = root.program().output_modifier("has_a");
assert_eq!(
modifier,
polydat::dsl::ast::BindingModifier::SHARED,
"workload root has_a output must have SHARED modifier;\n\
got {modifier:?}"
);
let shared = root.program().shared_outputs();
assert!(
shared.contains(&"has_a"),
"workload root must have has_a in shared_outputs (so seed_shared_cells creates a cell);\n\
got shared_outputs={shared:?}"
);
}
#[test]
fn shared_bool_survives_when_workload_root_also_has_cycle_binding() {
use polydat::kernel::extract_manifest;
let root = crate::scope_kernel::ScopeKernel::compile(
"shared has_a := true\n\
shared has_b := false\n\
selector := mod(cycle, 1)\n",
)
.expect("workload root compile");
let root_has_a_idx = root
.program()
.find_input("has_a")
.expect("workload root has_a slot");
let root_has_a_type = root.program().input_port_type("has_a");
assert_eq!(
root_has_a_type,
Some(polydat::ast::PortType::Bool),
"workload root has_a must be Bool"
);
let root_has_a_kind = root.program().input_kind(root_has_a_idx);
assert_ne!(
root_has_a_kind,
Some(polydat::kernel::InputKind::Coordinate),
"workload root has_a must NOT be Coordinate; got {root_has_a_kind:?}"
);
let for_each = crate::scope_synth::build_for_each_scope_kernel(
&[("outer".to_string(), "p1,p2".to_string())],
&extract_manifest(root.program()),
&root,
&HashMap::new(),
Vec::new(),
None,
false,
"test_for_each",
None,
)
.expect("for_each synth");
let fe_has_a_idx = for_each
.program()
.find_input("has_a")
.expect("for_each has_a slot");
let fe_has_a_type = for_each.program().input_port_type("has_a");
assert_eq!(
fe_has_a_type,
Some(polydat::ast::PortType::Bool),
"for_each has_a must be Bool, got {fe_has_a_type:?}"
);
let fe_has_a_kind = for_each.program().input_kind(fe_has_a_idx);
assert_ne!(
fe_has_a_kind,
Some(polydat::kernel::InputKind::Coordinate),
"for_each has_a must NOT be Coordinate; got {fe_has_a_kind:?}"
);
let phase_bindings = nmbrs_workload::model::BindingsDef::PolydatSource(
"chosen := pick(has_a, has_b, \"alpha\", \"beta\")\n".to_string(),
);
let phase = build_phase_scope_kernel(
&phase_bindings,
&[],
&for_each,
&HashMap::new(),
Vec::new(),
None,
false,
"test_phase",
)
.expect("phase scope synth");
let phase_has_a_idx = phase
.program()
.find_input("has_a")
.expect("phase has_a slot");
let phase_has_a_type = phase.program().input_port_type("has_a");
assert_eq!(
phase_has_a_type,
Some(polydat::ast::PortType::Bool),
"phase has_a must be Bool; got {phase_has_a_type:?}\n\
phase input names: {:?}",
phase.program().input_names()
);
let phase_has_a_kind = phase.program().input_kind(phase_has_a_idx);
assert_ne!(
phase_has_a_kind,
Some(polydat::kernel::InputKind::Coordinate),
"phase has_a must NOT be Coordinate; got {phase_has_a_kind:?}\n\
phase input names: {:?}\n\
coord_count: {}",
phase.program().input_names(),
phase.program().coord_count()
);
}
#[test]
fn for_each_then_phase_preserves_shared_bool_through_chain() {
use polydat::kernel::extract_manifest;
let root = crate::scope_kernel::ScopeKernel::compile(
"input cycle: u64\nshared has_sai_column_indexes := false\n\
shared has_indexes := false\n",
)
.expect("root compile");
let for_each_kernel = crate::scope_synth::build_for_each_scope_kernel(
&[("dummy_var".to_string(), "1,2".to_string())],
&extract_manifest(root.program()),
&root,
&HashMap::new(), Vec::new(), None, false, "test_for_each",
None, )
.expect("for_each kernel synth");
let fe_type = for_each_kernel
.program()
.input_port_type("has_sai_column_indexes");
assert_eq!(
fe_type,
Some(polydat::ast::PortType::Bool),
"for_each scope's has_sai_column_indexes must be Bool, got {fe_type:?}"
);
let fe_idx = for_each_kernel
.program()
.find_input("has_sai_column_indexes")
.expect("for_each has has_sai_column_indexes input");
let fe_kind = for_each_kernel.program().input_kind(fe_idx);
assert_ne!(
fe_kind,
Some(polydat::kernel::InputKind::Coordinate),
"for_each scope's has_sai_column_indexes must NOT be Coordinate; got {fe_kind:?}"
);
let phase_bindings = nmbrs_workload::model::BindingsDef::PolydatSource(
"target_index_table := pick(has_sai_column_indexes, has_indexes, \
\"a\", \"b\")\n"
.to_string(),
);
let phase_kernel = build_phase_scope_kernel(
&phase_bindings,
&[],
&for_each_kernel,
&HashMap::new(),
Vec::new(),
None,
false,
"test_await_index",
)
.expect("phase kernel synth");
let phase_type = phase_kernel
.program()
.input_port_type("has_sai_column_indexes");
assert_eq!(
phase_type,
Some(polydat::ast::PortType::Bool),
"phase scope's has_sai_column_indexes must be Bool, got {phase_type:?}"
);
let phase_idx = phase_kernel
.program()
.find_input("has_sai_column_indexes")
.expect("phase has has_sai_column_indexes input");
let phase_kind = phase_kernel.program().input_kind(phase_idx);
assert_ne!(
phase_kind,
Some(polydat::kernel::InputKind::Coordinate),
"phase scope's has_sai_column_indexes must NOT be Coordinate; got {phase_kind:?}"
);
}
}
#[cfg(test)]
mod strict_synthesis_tests {
use super::*;
fn parent() -> crate::scope_kernel::ScopeKernel {
crate::scope_kernel::ScopeKernel::compile("input cycle: u64\nconst k_values := \"1, 10\"\n")
.expect("parent scope")
}
#[test]
fn a_phase_scope_compiles_under_strict() {
let parent = parent();
let manifest = crate::runner::extract_manifest(parent.program());
let kernel = build_phase_scope_kernel(
&nmbrs_workload::model::BindingsDef::PolydatSource("x := cycle + 1\n".into()),
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
true,
"strict phase",
)
.expect("a phase scope compiles under strict");
assert!(kernel.input_names().iter().any(|n| n == "cycle"));
}
#[test]
fn a_do_loop_scope_compiles_under_strict() {
let parent = parent();
let manifest = crate::runner::extract_manifest(parent.program());
build_do_loop_scope_kernel(
Some("i"),
"{i} < 3",
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
true,
"strict do-loop",
)
.expect("a do-loop scope compiles under strict");
}
#[test]
fn a_for_each_scope_compiles_under_strict() {
let parent = parent();
let manifest = crate::runner::extract_manifest(parent.program());
crate::scope_synth::build_for_each_scope_kernel(
&[("k".to_string(), "{k_values}".to_string())],
&manifest,
&parent,
&HashMap::new(),
Vec::new(),
None,
true,
"strict for_each",
None,
)
.expect("a for_each scope compiles under strict");
}
}