Skip to main content

parallax_pcir/
program.rs

1//! PCIR program / function / block containers.
2
3use crate::ops::PcirOp;
4use crate::version::{check_pcir_schema, PCIR_SCHEMA_VERSION};
5use indexmap::IndexMap;
6use parallax_core::{ErrorCode, ParallaxError, Result};
7use serde::{Deserialize, Serialize};
8
9/// A basic block of PCIR ops.
10#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11pub struct PcirBlock {
12    /// Block name (unique within function).
13    pub name: String,
14    /// Ordered operations.
15    pub ops: Vec<PcirOp>,
16}
17
18impl PcirBlock {
19    /// Create an empty named block.
20    pub fn new(name: impl Into<String>) -> Self {
21        Self {
22            name: name.into(),
23            ops: Vec::new(),
24        }
25    }
26}
27
28/// A PCIR function.
29#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
30pub struct PcirFunction {
31    /// Function name.
32    pub name: String,
33    /// Parameter names (bound to ValueIds 0..n-1 by convention).
34    #[serde(default)]
35    pub params: Vec<String>,
36    /// Basic blocks.
37    pub blocks: Vec<PcirBlock>,
38    /// Free-form metadata.
39    #[serde(default)]
40    pub metadata: IndexMap<String, serde_json::Value>,
41}
42
43impl PcirFunction {
44    /// Create an empty function shell.
45    pub fn new(name: impl Into<String>) -> Self {
46        Self {
47            name: name.into(),
48            params: Vec::new(),
49            blocks: Vec::new(),
50            metadata: IndexMap::new(),
51        }
52    }
53}
54
55/// Optional module grouping (for mixed-origin stacks).
56#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
57pub struct PcirModule {
58    /// Module name / path.
59    pub name: String,
60    /// Functions in this module.
61    pub functions: Vec<String>,
62}
63
64/// Top-level PCIR program.
65#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
66pub struct PcirProgram {
67    /// Schema version (independent of PIR).
68    pub schema: u32,
69    /// Functions.
70    pub functions: Vec<PcirFunction>,
71    /// Optional modules.
72    #[serde(default)]
73    pub modules: Vec<PcirModule>,
74    /// Entry function name when applicable.
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub entry: Option<String>,
77    /// Free-form metadata.
78    #[serde(default)]
79    pub metadata: IndexMap<String, serde_json::Value>,
80}
81
82impl PcirProgram {
83    /// Empty program at the current schema.
84    pub fn new() -> Self {
85        Self {
86            schema: PCIR_SCHEMA_VERSION,
87            functions: Vec::new(),
88            modules: Vec::new(),
89            entry: None,
90            metadata: IndexMap::new(),
91        }
92    }
93
94    /// Validate schema and basic structural invariants.
95    pub fn validate(&self) -> Result<()> {
96        check_pcir_schema(self.schema)?;
97        for func in &self.functions {
98            if func.name.is_empty() {
99                return Err(ParallaxError::new(
100                    ErrorCode::InvalidSnapshot,
101                    "PCIR function with empty name",
102                )
103                .with_source("parallax-pcir")
104                .with_operation("validate"));
105            }
106            let mut names = std::collections::HashSet::new();
107            for block in &func.blocks {
108                if !names.insert(block.name.clone()) {
109                    return Err(ParallaxError::new(
110                        ErrorCode::InvalidSnapshot,
111                        format!(
112                            "duplicate PCIR block name '{}' in function '{}'",
113                            block.name, func.name
114                        ),
115                    )
116                    .with_source("parallax-pcir")
117                    .with_operation("validate"));
118                }
119            }
120        }
121        Ok(())
122    }
123
124    /// Build a minimal stub program representing an explicit checkpoint boundary.
125    pub fn checkpoint_stub(label: &str) -> Self {
126        let mut prog = Self::new();
127        let mut func = PcirFunction::new("__parallax_checkpoint_region");
128        let mut block = PcirBlock::new("entry");
129        block.ops.push(PcirOp::Intrinsic {
130            dest: None,
131            name: "parallax.checkpoint".into(),
132            args: vec![crate::ops::Operand::Imm(serde_json::json!({
133                "t": "string",
134                "v": label
135            }))],
136        });
137        func.blocks.push(block);
138        prog.functions.push(func);
139        prog.entry = Some("__parallax_checkpoint_region".into());
140        prog.metadata
141            .insert("kind".into(), serde_json::json!("checkpoint_stub"));
142        prog.metadata
143            .insert("label".into(), serde_json::json!(label));
144        prog
145    }
146}
147
148impl Default for PcirProgram {
149    fn default() -> Self {
150        Self::new()
151    }
152}