308 lines
11 KiB
Rust
308 lines
11 KiB
Rust
|
|
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());
|
||
|
|
}
|
||
|
|
}
|