synplexum/src/pipeline.rs

308 lines
11 KiB
Rust
Raw Normal View History

2026-03-02 15:46:50 +00:00
use crate::codegen::{self, CodegenError, ExecutionResult, MachineCode};
use crate::emit::{self, EmitError};
use crate::l0_ir::L0Program;
use crate::l1_ir::L1Program;
use crate::parser::{self, ParseError};
use crate::translate_validate::{self, VerificationReport};
use crate::verify_l0;
use crate::verify_l1::{self, VerifyError};
use std::fmt;
/// Errors from any stage of the pipeline.
#[derive(Debug)]
pub enum PipelineError {
Parse(ParseError),
VerifyL1(VerifyError),
Emit(EmitError),
VerifyL0(Vec<verify_l0::L0VerifyError>),
Codegen(CodegenError),
TranslationValidation(String),
}
impl fmt::Display for PipelineError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PipelineError::Parse(e) => write!(f, "parse: {}", e),
PipelineError::VerifyL1(e) => write!(f, "L1 verify: {}", e),
PipelineError::Emit(e) => write!(f, "emit: {}", e),
PipelineError::VerifyL0(errs) => {
write!(f, "L0 verify:")?;
for e in errs {
write!(f, " {}", e)?;
}
Ok(())
}
PipelineError::Codegen(e) => write!(f, "codegen: {}", e),
PipelineError::TranslationValidation(msg) => write!(f, "translation validation: {}", msg),
}
}
}
impl std::error::Error for PipelineError {}
/// All intermediate representations captured during pipeline execution.
pub struct PipelineIR {
pub l1: L1Program,
pub l0: L0Program,
pub machine_code: MachineCode,
}
/// Run the full pipeline: DSL text -> parse -> L1 IR -> verify -> emit L0 IR
/// -> verify -> codegen -> execute.
///
/// Returns both the execution result and captured intermediate representations.
pub fn run(source: &str) -> Result<(ExecutionResult, PipelineIR), PipelineError> {
// Parse
let l1 = parser::parse(source).map_err(PipelineError::Parse)?;
// Verify L1
verify_l1::verify(&l1).map_err(PipelineError::VerifyL1)?;
// Emit L0 IR
let l0 = emit::emit(&l1).map_err(PipelineError::Emit)?;
// Verify L0
verify_l0::verify(&l0).map_err(PipelineError::VerifyL0)?;
// Codegen
let mc = codegen::codegen(&l0).map_err(PipelineError::Codegen)?;
// Execute
let result = codegen::execute(&mc).map_err(PipelineError::Codegen)?;
let ir = PipelineIR {
l1,
l0,
machine_code: mc,
};
Ok((result, ir))
}
/// Dump all intermediate representations for a given DSL input.
/// Returns a human-readable string showing L1 IR, L0 IR, and x86-64 hex.
pub fn dump_ir(source: &str) -> Result<String, PipelineError> {
let l1 = parser::parse(source).map_err(PipelineError::Parse)?;
verify_l1::verify(&l1).map_err(PipelineError::VerifyL1)?;
let l0 = emit::emit(&l1).map_err(PipelineError::Emit)?;
verify_l0::verify(&l0).map_err(PipelineError::VerifyL0)?;
let mc = codegen::codegen(&l0).map_err(PipelineError::Codegen)?;
let mut out = String::new();
out.push_str("========== L1 IR ==========\n");
out.push_str(&format!("{}", l1));
out.push('\n');
out.push_str("========== L0 IR ==========\n");
out.push_str(&format!("{}", l0));
out.push('\n');
out.push_str("========== x86-64 Machine Code ==========\n");
out.push_str(&mc.hex_dump());
out.push('\n');
Ok(out)
}
/// Run the full verification pipeline: parse → L1 verify → emit → L0 verify → translate validate → report.
///
/// Returns the verification report containing results from all three validation tools.
pub fn verify_translation(source: &str) -> Result<VerificationReport, PipelineError> {
// Parse
let l1 = parser::parse(source).map_err(PipelineError::Parse)?;
// Verify L1
verify_l1::verify(&l1).map_err(PipelineError::VerifyL1)?;
// Emit L0 IR
let l0 = emit::emit(&l1).map_err(PipelineError::Emit)?;
// Verify L0
verify_l0::verify(&l0).map_err(PipelineError::VerifyL0)?;
// Translation validation
let report = translate_validate::validate(&l1, &l0);
if !report.all_passed() {
return Err(PipelineError::TranslationValidation(format!("{}", report)));
}
Ok(report)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counter_end_to_end() {
let source = include_str!("../examples/counter.l1");
let (result, _ir) = run(source).expect("counter pipeline should succeed");
let count = result.read_u64("counter_state", 0).expect("should read counter_state");
assert_eq!(count, 5, "counter should be 5 after 5 steps");
}
#[test]
fn window_end_to_end() {
let source = include_str!("../examples/window.l1");
let (result, _ir) = run(source).expect("window pipeline should succeed");
let total = result.read_u64("accumulator_state", 0).expect("should read accumulator_state");
assert_eq!(total, 30, "total should be 30 after 3 steps of Add(10)");
let observed = result.read_output_values();
assert_eq!(observed, vec![10, 20, 30], "observed values should be [10, 20, 30]");
}
#[test]
fn counter_ir_dump() {
let source = include_str!("../examples/counter.l1");
let dump = dump_ir(source).expect("dump should succeed");
assert!(dump.contains("========== L1 IR =========="));
assert!(dump.contains("========== L0 IR =========="));
assert!(dump.contains("========== x86-64 Machine Code =========="));
assert!(dump.contains("actor counter"));
assert!(dump.contains("=== Regions ==="));
assert!(dump.contains("counter_state"));
}
#[test]
fn window_ir_dump() {
let source = include_str!("../examples/window.l1");
let dump = dump_ir(source).expect("dump should succeed");
assert!(dump.contains("========== L1 IR =========="));
assert!(dump.contains("accumulator"));
assert!(dump.contains("output"));
}
#[test]
fn counter_test_vector_full_pipeline() {
// Full test vector: DSL source -> L1 IR -> L0 IR -> x86-64 -> result
let source = include_str!("../examples/counter.l1");
let (result, ir) = run(source).expect("pipeline should succeed");
// 1. DSL source is the counter.l1 file (inlined via include_str!)
assert!(source.contains("actor counter"));
assert!(source.contains("on Increment(amount: u64)"));
assert!(source.contains("steps: 5"));
// 2. Expected L1 IR text
let l1_text = format!("{}", ir.l1);
assert!(l1_text.contains("actor counter {"));
assert!(l1_text.contains("count: u64 = 0"));
assert!(l1_text.contains("window count_view : (count) readers(display)"));
assert!(l1_text.contains("on Increment(amount: u64) { count = (count + amount) }"));
assert!(l1_text.contains("leaf ticker {"));
assert!(l1_text.contains("forward(counter, Increment(1))"));
assert!(l1_text.contains("leaf display {"));
assert!(l1_text.contains("pipeline main { ticker -> counter -> display }"));
assert!(l1_text.contains("steps: 5"));
// 3. Expected L0 IR text
let l0_text = format!("{}", ir.l0);
assert!(l0_text.contains("=== Regions ==="));
assert!(l0_text.contains("region counter_state : 8 bytes, rw, state"));
assert!(l0_text.contains("queue"));
assert!(l0_text.contains("region step_counter : 8 bytes, rw, control"));
assert!(l0_text.contains("=== Blocks ==="));
assert!(l0_text.contains("entry:"));
assert!(l0_text.contains("loop_check:"));
assert!(l0_text.contains("step:"));
assert!(l0_text.contains("exit:"));
assert!(l0_text.contains("load.64"));
assert!(l0_text.contains("store.64"));
assert!(l0_text.contains("cmp.lt"));
assert!(l0_text.contains("branch"));
assert!(l0_text.contains("queue_push"));
assert!(l0_text.contains("queue_pop"));
assert!(l0_text.contains("terminate"));
// 4. Expected x86-64 bytes (non-empty hex string)
let hex = ir.machine_code.hex_dump();
assert!(!hex.is_empty(), "machine code should not be empty");
// Verify it ends with ret (c3) preceded by the epilogue pops
assert!(hex.contains("c3"), "machine code should contain ret instruction");
// 5. Expected execution result
let count = result.read_u64("counter_state", 0).unwrap();
assert_eq!(count, 5, "counter should equal step count (5)");
}
#[test]
fn window_test_vector_full_pipeline() {
// Full test vector: DSL source -> L1 IR -> L0 IR -> x86-64 -> result
let source = include_str!("../examples/window.l1");
let (result, ir) = run(source).expect("pipeline should succeed");
// 1. DSL source
assert!(source.contains("actor accumulator"));
assert!(source.contains("on Add(value: u64)"));
assert!(source.contains("steps: 3"));
// 2. Expected L1 IR text
let l1_text = format!("{}", ir.l1);
assert!(l1_text.contains("actor accumulator {"));
assert!(l1_text.contains("total: u64 = 0"));
assert!(l1_text.contains("window total_view : (total) readers(observe)"));
assert!(l1_text.contains("on Add(value: u64) { total = (total + value) }"));
assert!(l1_text.contains("leaf source {"));
assert!(l1_text.contains("forward(accumulator, Add(10))"));
assert!(l1_text.contains("leaf observe {"));
assert!(l1_text.contains("emit(total)"));
assert!(l1_text.contains("pipeline main { source -> accumulator -> observe }"));
assert!(l1_text.contains("steps: 3"));
// 3. Expected L0 IR text
let l0_text = format!("{}", ir.l0);
assert!(l0_text.contains("region accumulator_state : 8 bytes, rw, state"));
assert!(l0_text.contains("region output :"));
assert!(l0_text.contains("output"));
assert!(l0_text.contains("entry:"));
assert!(l0_text.contains("queue_push output"));
// 4. x86-64 bytes
let hex = ir.machine_code.hex_dump();
assert!(!hex.is_empty());
assert!(hex.contains("c3"));
// 5. Expected execution result
let total = result.read_u64("accumulator_state", 0).unwrap();
assert_eq!(total, 30, "total should be 30");
let observed = result.read_output_values();
assert_eq!(observed, vec![10, 20, 30], "observed values should be [10, 20, 30]");
}
#[test]
fn pipeline_error_on_invalid_input() {
let result = run("invalid garbage input");
assert!(result.is_err());
}
#[test]
fn verify_translation_counter() {
let source = include_str!("../examples/counter.l1");
let report = verify_translation(source).expect("counter verification should pass");
assert!(report.all_passed());
assert_eq!(report.results.len(), 3);
}
#[test]
fn verify_translation_window() {
let source = include_str!("../examples/window.l1");
let report = verify_translation(source).expect("window verification should pass");
assert!(report.all_passed());
}
#[test]
fn verify_translation_product() {
let source = include_str!("../examples/product.l1");
let report = verify_translation(source).expect("product verification should pass");
assert!(report.all_passed());
}
#[test]
fn verify_translation_invalid_input() {
let result = verify_translation("invalid garbage");
assert!(result.is_err());
}
}