Skip to main content

tidepool_codegen/emit/
mod.rs

1pub mod case;
2pub mod expr;
3pub mod join;
4pub mod primop;
5
6use cranelift_codegen::ir::{FuncRef, SigRef, Value};
7use std::collections::HashMap;
8use tidepool_repr::{CoreExpr, JoinId, PrimOpKind, VarId};
9
10pub use crate::layout::*;
11
12/// Per-function compilation context bundling common parameters.
13pub struct EmitSession<'a> {
14    pub pipeline: &'a mut crate::pipeline::CodegenPipeline,
15    pub vmctx: Value,
16    pub gc_sig: SigRef,
17    pub oom_func: FuncRef,
18    pub tree: &'a CoreExpr,
19}
20
21/// SSA value with boxed/unboxed tracking.
22#[derive(Debug, Clone, Copy)]
23pub enum SsaVal {
24    /// Unboxed raw value (i64 or f64 bits) with its literal tag.
25    Raw(Value, i64),
26    /// Heap pointer. Already declared via `declare_value_needs_stack_map`.
27    HeapPtr(Value),
28}
29
30impl SsaVal {
31    pub fn value(self) -> Value {
32        match self {
33            SsaVal::Raw(v, _) | SsaVal::HeapPtr(v) => v,
34        }
35    }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum TailCtx {
40    Tail,
41    NonTail,
42}
43
44impl TailCtx {
45    pub fn is_tail(self) -> bool {
46        matches!(self, TailCtx::Tail)
47    }
48}
49
50/// A scoped environment mapping variables to SSA values.
51pub struct ScopedEnv {
52    inner: HashMap<VarId, SsaVal>,
53}
54
55#[allow(clippy::new_without_default)]
56impl ScopedEnv {
57    pub fn new() -> Self {
58        Self {
59            inner: HashMap::new(),
60        }
61    }
62
63    pub fn get(&self, var: &VarId) -> Option<&SsaVal> {
64        self.inner.get(var)
65    }
66
67    pub fn contains_key(&self, var: &VarId) -> bool {
68        self.inner.contains_key(var)
69    }
70
71    pub fn insert(&mut self, var: VarId, val: SsaVal) -> Option<SsaVal> {
72        self.inner.insert(var, val)
73    }
74
75    /// Restores a variable to its previous state.
76    pub fn restore(&mut self, var: VarId, old: Option<SsaVal>) {
77        if let Some(val) = old {
78            self.inner.insert(var, val);
79        } else {
80            self.inner.remove(&var);
81        }
82    }
83
84    pub fn iter(&self) -> std::collections::hash_map::Iter<'_, VarId, SsaVal> {
85        self.inner.iter()
86    }
87
88    pub fn keys(&self) -> std::collections::hash_map::Keys<'_, VarId, SsaVal> {
89        self.inner.keys()
90    }
91
92    pub fn len(&self) -> usize {
93        self.inner.len()
94    }
95
96    pub fn is_empty(&self) -> bool {
97        self.inner.is_empty()
98    }
99
100    /// Inserts a variable into the environment and records the old value in the scope.
101    pub fn insert_scoped(&mut self, scope: &mut EnvScope, var: VarId, val: SsaVal) {
102        let old = self.insert(var, val);
103        scope.saved.push((var, old));
104    }
105
106    /// Restores all variables saved in the scope in reverse order.
107    pub fn restore_scope(&mut self, scope: EnvScope) {
108        for (var, old) in scope.saved.into_iter().rev() {
109            self.restore(var, old);
110        }
111    }
112}
113
114/// A set of saved environment bindings to be restored.
115pub struct EnvScope {
116    pub(crate) saved: Vec<(VarId, Option<SsaVal>)>,
117}
118
119impl EnvScope {
120    pub fn new() -> Self {
121        Self { saved: Vec::new() }
122    }
123}
124
125impl Default for EnvScope {
126    fn default() -> Self {
127        Self::new()
128    }
129}
130
131/// RAII guard that automatically restores environment bindings when dropped.
132///
133/// Note: Due to borrow checker constraints, this cannot be used where the
134/// parent `EmitContext` needs to be used (e.g., in `emit_node` calls) since it
135/// mutably borrows `ctx.env`. It is primarily for use in future refactorings
136/// that split `EmitContext` or in simple leaf functions.
137#[allow(dead_code)]
138pub(crate) struct EnvGuard<'a> {
139    env: &'a mut ScopedEnv,
140    scope: EnvScope,
141}
142
143#[allow(dead_code)]
144impl<'a> EnvGuard<'a> {
145    pub fn new(env: &'a mut ScopedEnv) -> Self {
146        Self {
147            env,
148            scope: EnvScope::new(),
149        }
150    }
151
152    pub fn insert(&mut self, var: VarId, val: SsaVal) {
153        self.env.insert_scoped(&mut self.scope, var, val);
154    }
155}
156
157impl<'a> Drop for EnvGuard<'a> {
158    fn drop(&mut self) {
159        let scope = std::mem::take(&mut self.scope);
160        self.env.restore_scope(scope);
161    }
162}
163
164/// Emission context — bundles state during IR generation for one function.
165pub struct EmitContext {
166    pub env: ScopedEnv,
167    pub(crate) join_blocks: JoinPointRegistry,
168    pub lambda_counter: u32,
169    pub prefix: String,
170    /// Storage for LetRec deferred state, indexed by work items.
171    pub(crate) letrec_states: Vec<crate::emit::expr::LetRecDeferredState>,
172}
173
174pub(crate) struct JoinPointRegistry {
175    blocks: HashMap<JoinId, JoinInfo>,
176}
177
178impl JoinPointRegistry {
179    pub(crate) fn new() -> Self {
180        Self {
181            blocks: HashMap::new(),
182        }
183    }
184
185    pub(crate) fn register(&mut self, label: JoinId, info: JoinInfo) {
186        self.blocks.insert(label, info);
187    }
188
189    pub(crate) fn get(&self, label: &JoinId) -> Result<&JoinInfo, EmitError> {
190        self.blocks.get(label).ok_or_else(|| {
191            EmitError::NotYetImplemented(format!("Jump to unregistered join {:?}", label))
192        })
193    }
194
195    pub(crate) fn remove(&mut self, label: &JoinId) -> Option<JoinInfo> {
196        self.blocks.remove(label)
197    }
198}
199
200/// Placeholder for join point info (used by case/join leaf later).
201pub struct JoinInfo {
202    pub block: cranelift_codegen::ir::Block,
203    pub param_types: Vec<SsaVal>,
204}
205
206/// Errors during IR emission.
207#[derive(Debug)]
208pub enum EmitError {
209    UnboundVariable(VarId),
210    NotYetImplemented(String),
211    CraneliftError(String),
212    Pipeline(crate::pipeline::PipelineError),
213    InvalidArity(PrimOpKind, usize, usize),
214    /// A variable needed for closure capture was not found in the environment.
215    MissingCaptureVar(VarId, String),
216    /// Internal invariant violation (should never happen).
217    InternalError(String),
218}
219
220impl std::fmt::Display for EmitError {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        match self {
223            EmitError::UnboundVariable(v) => write!(f, "unbound variable: {:?}", v),
224            EmitError::NotYetImplemented(s) => write!(f, "not yet implemented: {}", s),
225            EmitError::CraneliftError(s) => write!(f, "cranelift error: {}", s),
226            EmitError::Pipeline(e) => write!(f, "pipeline error: {}", e),
227            EmitError::InvalidArity(op, expected, got) => {
228                write!(
229                    f,
230                    "invalid arity for {:?}: expected {}, got {}",
231                    op, expected, got
232                )
233            }
234            EmitError::MissingCaptureVar(v, ctx) => {
235                write!(f, "missing capture variable VarId({:#x}): {}", v.0, ctx)
236            }
237            EmitError::InternalError(msg) => write!(f, "internal error: {}", msg),
238        }
239    }
240}
241
242impl std::error::Error for EmitError {}
243
244impl From<crate::pipeline::PipelineError> for EmitError {
245    fn from(e: crate::pipeline::PipelineError) -> Self {
246        EmitError::Pipeline(e)
247    }
248}
249
250impl EmitContext {
251    pub fn new(prefix: String) -> Self {
252        Self {
253            env: ScopedEnv::new(),
254            join_blocks: JoinPointRegistry::new(),
255            lambda_counter: 0,
256            prefix,
257            letrec_states: Vec::new(),
258        }
259    }
260
261    /// Re-declare all heap pointers currently in the environment as needing
262    /// stack map entries. Should be called after switching to a new block
263    /// (e.g., merge blocks, join points, case alternatives) to ensure
264    /// liveness is tracked correctly across block boundaries.
265    pub fn declare_env(&self, builder: &mut cranelift_frontend::FunctionBuilder) {
266        // Collect and sort keys for deterministic IR output (useful for debugging/tests)
267        let mut keys: Vec<_> = self.env.keys().collect();
268        keys.sort_by_key(|v| v.0);
269        for &k in keys {
270            if let Some(SsaVal::HeapPtr(v)) = self.env.get(&k) {
271                builder.declare_value_needs_stack_map(*v);
272            }
273        }
274    }
275
276    pub fn trace_scope(&self, msg: &str) {
277        if crate::debug::trace_level() >= crate::debug::TraceLevel::Scope {
278            eprintln!("[scope:{}] {}", self.prefix, msg);
279        }
280    }
281
282    pub fn next_lambda_name(&mut self) -> String {
283        let n = self.lambda_counter;
284        self.lambda_counter += 1;
285        format!("{}_lambda_{}", self.prefix, n)
286    }
287
288    pub fn next_thunk_name(&mut self) -> String {
289        let n = self.lambda_counter;
290        self.lambda_counter += 1;
291        format!("{}_thunk_{}", self.prefix, n)
292    }
293}