mod program;
mod engines;
mod gkkernel;
mod scope_coords;
mod manifest;
mod api;
mod api_impl;
mod opt;
pub use program::*;
pub use engines::*;
pub use gkkernel::*;
pub use scope_coords::{ScopeCoord, format_scope_coordinate_path};
pub use manifest::{extract_manifest, ManifestEntry};
pub use api::{Construction, Dataflow, Metadata, WireKey};
pub use opt::KernelOptLevel;
use crate::node::Value;
#[derive(Debug, Clone)]
pub enum WireSource {
Input(usize),
NodeOutput(usize, usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InputKind {
Coordinate,
IterationExtern,
CapturePort,
}
#[derive(Debug, Clone)]
pub struct InputDef {
pub name: String,
pub default: Value,
pub port_type: crate::node::PortType,
pub kind: InputKind,
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use std::sync::Arc;
#[test]
fn capture_inputs_persist_across_set_inputs() {
let program = Arc::new(GkProgram::with_inputs(
vec![], vec![],
vec![
InputDef { name: "cycle".into(), default: Value::U64(0), port_type: crate::node::PortType::U64, kind: InputKind::Coordinate },
InputDef { name: "balance".into(), default: Value::F64(0.0), port_type: crate::node::PortType::F64, kind: InputKind::CapturePort },
InputDef { name: "auth_token".into(), default: Value::Str("anonymous".into()), port_type: crate::node::PortType::Str, kind: InputKind::CapturePort },
],
1, HashMap::new(),
Vec::new(),
"", "(test)",
));
let mut state = program.create_state();
assert_eq!(state.get_input(1), Value::F64(0.0));
assert_eq!(state.get_input(2), Value::Str("anonymous".into()));
state.set_input(1, Value::F64(1234.56));
state.set_input(2, Value::Str("token_abc".into()));
assert_eq!(state.get_input(1), Value::F64(1234.56));
assert_eq!(state.get_input(2), Value::Str("token_abc".into()));
state.set_inputs(&[42]);
assert_eq!(state.get_input(1), Value::F64(1234.56));
assert_eq!(state.get_input(2), Value::Str("token_abc".into()));
}
#[test]
fn reset_inputs_restores_capture_defaults() {
let program = Arc::new(GkProgram::with_inputs(
vec![], vec![],
vec![
InputDef { name: "cycle".into(), default: Value::U64(0), port_type: crate::node::PortType::U64, kind: InputKind::Coordinate },
InputDef { name: "token".into(), default: Value::Str("anon".into()), port_type: crate::node::PortType::Str, kind: InputKind::CapturePort },
],
1,
HashMap::new(),
Vec::new(),
"", "(test)",
));
let mut state = program.create_state();
state.set_input(1, Value::Str("alice".into()));
assert_eq!(state.get_input(1), Value::Str("alice".into()));
state.reset_inputs_from(1);
assert_eq!(state.get_input(1), Value::Str("anon".into()));
}
#[test]
fn invalidate_all_resets_everything() {
let program = Arc::new(GkProgram::with_inputs(
vec![], vec![],
vec![
InputDef { name: "cycle".into(), default: Value::U64(0), port_type: crate::node::PortType::U64, kind: InputKind::Coordinate },
InputDef { name: "token".into(), default: Value::Str("anon".into()), port_type: crate::node::PortType::Str, kind: InputKind::CapturePort },
],
1,
HashMap::new(),
Vec::new(),
"", "(test)",
));
let mut state = program.create_state();
state.set_inputs(&[42]);
state.set_input(1, Value::Str("alice".into()));
state.invalidate_all();
assert_eq!(state.get_input(0), Value::U64(0));
assert_eq!(state.get_input(1), Value::Str("anon".into()));
}
#[test]
fn fold_init_constants_basic() {
use crate::dsl::compile::compile_gk;
let mut k = compile_gk("input cycle: u64\nbase := 42\nseed := hash(base)\nuser_id := hash(cycle)").unwrap();
k.set_inputs(&[0]);
let seed_0 = k.pull("seed").clone();
k.set_inputs(&[1]);
let seed_1 = k.pull("seed").clone();
assert_eq!(seed_0.as_u64(), seed_1.as_u64(), "seed should be constant (folded)");
k.set_inputs(&[0]);
let uid_0 = k.pull("user_id").clone();
k.set_inputs(&[1]);
let uid_1 = k.pull("user_id").clone();
assert_ne!(uid_0.as_u64(), uid_1.as_u64(), "user_id should vary per cycle");
}
#[test]
fn fold_does_not_touch_cycle_dependent() {
use crate::dsl::compile::compile_gk;
let mut k = compile_gk("input cycle: u64\nout := hash(cycle)").unwrap();
k.set_inputs(&[42]);
let v1 = k.pull("out").as_u64();
k.set_inputs(&[43]);
let v2 = k.pull("out").as_u64();
assert_ne!(v1, v2, "cycle-dependent node should not be folded");
}
struct ConfigWireTestNode {
meta: crate::node::NodeMeta,
}
impl ConfigWireTestNode {
fn new() -> Self {
use crate::node::{Port, Slot};
Self {
meta: crate::node::NodeMeta {
name: "config_test".into(),
outs: vec![Port::u64("output")],
ins: vec![
Slot::Wire(Port::u64("config_param").config()),
Slot::Wire(Port::u64("data_input")),
],
},
}
}
}
impl crate::node::GkNode for ConfigWireTestNode {
fn meta(&self) -> &crate::node::NodeMeta { &self.meta }
fn eval(&self, inputs: &[Value], outputs: &mut [Value]) {
let config = inputs[0].as_u64();
let data = inputs[1].as_u64();
outputs[0] = Value::U64(config.wrapping_add(data));
}
}
#[test]
fn wire_cost_no_warning_when_config_is_init_time() {
use crate::assembly::{GkAssembler, WireRef};
use crate::nodes::identity::ConstU64;
use crate::nodes::hash::Hash64;
use crate::dsl::events::CompileEventLog;
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("config_val", Box::new(ConstU64::new(42)), vec![]);
asm.add_node("hashed", Box::new(Hash64::new()), vec![WireRef::input("cycle")]);
asm.add_node("test_node", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("config_val"),
WireRef::node("hashed"),
]);
asm.add_output("result", WireRef::node("test_node"));
let mut log = CompileEventLog::new();
let k = asm.compile_with_log(Some(&mut log)).unwrap();
let _program = k.into_program();
let warnings: Vec<_> = log.events().iter().filter(|e|
matches!(e, crate::dsl::events::CompileEvent::ConfigWireCycleWarning { .. })
).collect();
assert!(warnings.is_empty(), "no warning expected when config wire is init-time: {warnings:?}");
}
#[test]
fn wire_cost_warning_when_config_is_cycle_time() {
use crate::assembly::{GkAssembler, WireRef};
use crate::nodes::hash::Hash64;
use crate::dsl::events::CompileEventLog;
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("hashed", Box::new(Hash64::new()), vec![WireRef::input("cycle")]);
asm.add_node("test_node", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("hashed"), WireRef::input("cycle"), ]);
asm.add_output("result", WireRef::node("test_node"));
let mut log = CompileEventLog::new();
let _k = asm.compile_with_log(Some(&mut log)).unwrap();
let warnings: Vec<_> = log.events().iter().filter(|e|
matches!(e, crate::dsl::events::CompileEvent::ConfigWireCycleWarning { .. })
).collect();
assert_eq!(warnings.len(), 1, "expected exactly one config wire warning: {warnings:?}");
}
#[test]
fn wire_cost_warning_when_config_is_coordinate_direct() {
use crate::assembly::{GkAssembler, WireRef};
use crate::dsl::events::CompileEventLog;
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("test_node", Box::new(ConfigWireTestNode::new()), vec![
WireRef::input("cycle"), WireRef::input("cycle"), ]);
asm.add_output("result", WireRef::node("test_node"));
let mut log = CompileEventLog::new();
let _k = asm.compile_with_log(Some(&mut log)).unwrap();
let warnings: Vec<_> = log.events().iter().filter(|e|
matches!(e, crate::dsl::events::CompileEvent::ConfigWireCycleWarning { .. })
).collect();
assert_eq!(warnings.len(), 1, "config wire from coordinate should warn");
}
#[test]
fn wire_cost_no_warning_data_wire_from_cycle() {
use crate::assembly::{GkAssembler, WireRef};
use crate::nodes::identity::ConstU64;
use crate::dsl::events::CompileEventLog;
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("config_val", Box::new(ConstU64::new(10)), vec![]);
asm.add_node("test_node", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("config_val"), WireRef::input("cycle"), ]);
asm.add_output("result", WireRef::node("test_node"));
let mut log = CompileEventLog::new();
let _k = asm.compile_with_log(Some(&mut log)).unwrap();
let warnings: Vec<_> = log.events().iter().filter(|e|
matches!(e, crate::dsl::events::CompileEvent::ConfigWireCycleWarning { .. })
).collect();
assert!(warnings.is_empty(), "data wire from cycle should not warn");
}
#[test]
fn wire_cost_diamond_config_from_init() {
use crate::assembly::{GkAssembler, WireRef};
use crate::nodes::identity::ConstU64;
use crate::nodes::hash::Hash64;
use crate::dsl::events::CompileEventLog;
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("a", Box::new(ConstU64::new(5)), vec![]);
asm.add_node("b", Box::new(ConstU64::new(3)), vec![]);
asm.add_node("inner", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("a"), WireRef::node("b"),
]);
asm.add_node("hashed", Box::new(Hash64::new()), vec![WireRef::input("cycle")]);
asm.add_node("outer", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("inner"), WireRef::node("hashed"), ]);
asm.add_output("result", WireRef::node("outer"));
let mut log = CompileEventLog::new();
let _k = asm.compile_with_log(Some(&mut log)).unwrap();
let warnings: Vec<_> = log.events().iter().filter(|e|
matches!(e, crate::dsl::events::CompileEvent::ConfigWireCycleWarning { .. })
).collect();
assert!(warnings.is_empty(), "init-time derived config should not warn: {warnings:?}");
}
#[test]
fn wire_cost_diamond_config_from_mixed() {
use crate::assembly::{GkAssembler, WireRef};
use crate::nodes::identity::ConstU64;
use crate::dsl::events::CompileEventLog;
let mut asm = GkAssembler::new(vec!["cycle".into()]);
asm.add_node("five", Box::new(ConstU64::new(5)), vec![]);
asm.add_node("mixer", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("five"), WireRef::input("cycle"), ]);
asm.add_node("outer", Box::new(ConfigWireTestNode::new()), vec![
WireRef::node("mixer"), WireRef::input("cycle"), ]);
asm.add_output("result", WireRef::node("outer"));
let mut log = CompileEventLog::new();
let _k = asm.compile_with_log(Some(&mut log)).unwrap();
let warnings: Vec<_> = log.events().iter().filter(|e|
matches!(e, crate::dsl::events::CompileEvent::ConfigWireCycleWarning { .. })
).collect();
assert_eq!(warnings.len(), 1, "exactly one warning for outer's config: {warnings:?}");
}
#[test]
fn implicit_u64_to_f64_adapter_does_not_crash() {
use crate::dsl::compile::compile_gk;
let mut k = compile_gk("input cycle: u64\nout := sin(cycle)").unwrap();
k.set_inputs(&[1]);
let v = k.pull("out");
let f = v.as_f64();
assert!((f - 0.8414709848078965).abs() < 0.001,
"sin(1) should be ~0.841, got {f}");
}
}