Skip to main content

shape_vm/mir/
types.rs

1//! Core MIR types: Place, Statement, Terminator, BasicBlock.
2//!
3//! These represent the mid-level IR that the borrow solver operates on.
4//! Places track what can be borrowed (locals, fields, indices).
5//! Statements and terminators form basic blocks in a control flow graph.
6
7use shape_ast::ast::Span;
8use std::fmt;
9
10// ── Identifiers ──────────────────────────────────────────────────────
11
12/// Index of a local variable slot.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
14pub struct SlotId(pub u16);
15
16/// Index of a struct/object field.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct FieldIdx(pub u16);
19
20/// Index of a basic block within a MIR function.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct BasicBlockId(pub u32);
23
24/// A program point (statement index within the function's linearized MIR).
25/// Used as the "point" dimension in Datafrog relations.
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
27pub struct Point(pub u32);
28
29/// Unique identifier for a loan (borrow).
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
31pub struct LoanId(pub u32);
32
33/// A normalized step in a place projection chain.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub enum ProjectionStep {
36    Field(FieldIdx),
37    /// Index projections are intentionally summarized without their concrete
38    /// operand. The borrow solver only needs to know that an index boundary
39    /// exists for provenance and diagnostics.
40    Index,
41}
42
43impl fmt::Display for SlotId {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "_{}", self.0)
46    }
47}
48
49impl fmt::Display for BasicBlockId {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        write!(f, "bb{}", self.0)
52    }
53}
54
55impl fmt::Display for Point {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "p{}", self.0)
58    }
59}
60
61impl fmt::Display for LoanId {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "L{}", self.0)
64    }
65}
66
67// ── Places ───────────────────────────────────────────────────────────
68
69/// A place is something that can be borrowed or assigned to.
70/// Tracks granular access paths for disjoint borrow analysis.
71#[derive(Debug, Clone, PartialEq, Eq, Hash)]
72pub enum Place {
73    /// A local variable: `x`
74    Local(SlotId),
75    /// A field of a place: `x.field_name`
76    Field(Box<Place>, FieldIdx),
77    /// An index into a place: `x[i]` — index analysis is conservative in v1.
78    /// The index operand is boxed to break the recursive type cycle (Place → Operand → Place).
79    Index(Box<Place>, Box<Operand>),
80    /// Dereferencing a reference: `*r`
81    Deref(Box<Place>),
82}
83
84impl Place {
85    /// Get the root local of this place (e.g., `x.a.b` → `x`).
86    pub fn root_local(&self) -> SlotId {
87        match self {
88            Place::Local(slot) => *slot,
89            Place::Field(base, _) | Place::Index(base, _) | Place::Deref(base) => base.root_local(),
90        }
91    }
92
93    /// Check if this place is a prefix of another (for conflict detection).
94    /// `x` is a prefix of `x.a`, `x` is a prefix of `x[i]`, etc.
95    pub fn is_prefix_of(&self, other: &Place) -> bool {
96        if self == other {
97            return true;
98        }
99        match other {
100            Place::Local(_) => false,
101            Place::Field(base, _) | Place::Index(base, _) | Place::Deref(base) => {
102                self.is_prefix_of(base)
103            }
104        }
105    }
106
107    /// Check whether two places conflict (one borrows/writes something the other uses).
108    /// Two places conflict if one is a prefix of the other, or they're the same.
109    /// In v1, disjoint field borrows are tracked (x.a and x.b don't conflict),
110    /// but index borrows are conservative (x[i] and x[j] always conflict).
111    pub fn conflicts_with(&self, other: &Place) -> bool {
112        // Same root?
113        if self.root_local() != other.root_local() {
114            return false;
115        }
116        // Walk both paths to check overlap
117        self.is_prefix_of(other) || other.is_prefix_of(self) || self.overlaps(other)
118    }
119
120    fn overlaps(&self, other: &Place) -> bool {
121        match (self, other) {
122            (Place::Local(a), Place::Local(b)) => a == b,
123            // Disjoint fields: x.a and x.b do NOT conflict
124            (Place::Field(base_a, field_a), Place::Field(base_b, field_b)) => {
125                if base_a == base_b {
126                    field_a == field_b
127                } else {
128                    base_a.overlaps(base_b)
129                }
130            }
131            // Conservative: x[i] and x[j] always conflict
132            (Place::Index(base_a, _), Place::Index(base_b, _)) => base_a.overlaps(base_b),
133            _ => self.is_prefix_of(other) || other.is_prefix_of(self),
134        }
135    }
136
137    /// Return a normalized projection summary from the root local to this place.
138    pub fn projection_steps(&self) -> Vec<ProjectionStep> {
139        let mut steps = Vec::new();
140        self.collect_projection_steps(&mut steps);
141        steps
142    }
143
144    fn collect_projection_steps(&self, steps: &mut Vec<ProjectionStep>) {
145        match self {
146            Place::Local(_) => {}
147            Place::Field(base, field) => {
148                base.collect_projection_steps(steps);
149                steps.push(ProjectionStep::Field(*field));
150            }
151            Place::Index(base, _) => {
152                base.collect_projection_steps(steps);
153                steps.push(ProjectionStep::Index);
154            }
155            Place::Deref(base) => base.collect_projection_steps(steps),
156        }
157    }
158}
159
160impl fmt::Display for Place {
161    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162        match self {
163            Place::Local(slot) => write!(f, "{}", slot),
164            Place::Field(base, field) => write!(f, "{}.{}", base, field.0),
165            Place::Index(base, idx) => write!(f, "{}[{}]", base, idx),
166            Place::Deref(base) => write!(f, "*{}", base),
167        }
168    }
169}
170
171// ── Operands ─────────────────────────────────────────────────────────
172
173/// An operand in an Rvalue or terminator.
174#[derive(Debug, Clone, PartialEq, Eq, Hash)]
175pub enum Operand {
176    /// Copy the value from a place (for Copy types).
177    Copy(Place),
178    /// Move the value from a place (invalidates the source).
179    Move(Place),
180    /// Explicit source-level move (`move x`) that must not be rewritten into a clone.
181    MoveExplicit(Place),
182    /// A constant value.
183    Constant(MirConstant),
184}
185
186impl fmt::Display for Operand {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        match self {
189            Operand::Copy(p) => write!(f, "copy {}", p),
190            Operand::Move(p) => write!(f, "move {}", p),
191            Operand::MoveExplicit(p) => write!(f, "move! {}", p),
192            Operand::Constant(c) => write!(f, "{}", c),
193        }
194    }
195}
196
197/// A constant value in MIR.
198#[derive(Debug, Clone, PartialEq, Eq, Hash)]
199pub enum MirConstant {
200    Int(i64),
201    Bool(bool),
202    None,
203    /// String interned index
204    StringId(u32),
205    /// Float (stored as bits for Eq/Hash)
206    Float(u64),
207    /// Function reference by name
208    Function(String),
209    /// Method name for dispatch
210    Method(String),
211}
212
213impl fmt::Display for MirConstant {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            MirConstant::Int(v) => write!(f, "{}", v),
217            MirConstant::Bool(v) => write!(f, "{}", v),
218            MirConstant::None => write!(f, "none"),
219            MirConstant::StringId(id) => write!(f, "str#{}", id),
220            MirConstant::Float(bits) => write!(f, "{}", f64::from_bits(*bits)),
221            MirConstant::Function(name) => write!(f, "fn:{}", name),
222            MirConstant::Method(name) => write!(f, "method:{}", name),
223        }
224    }
225}
226
227// ── Rvalues ──────────────────────────────────────────────────────────
228
229/// The kind of borrow.
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231pub enum BorrowKind {
232    /// Shared (immutable) borrow: `&x`
233    Shared,
234    /// Exclusive (mutable) borrow: `&mut x`
235    Exclusive,
236}
237
238impl fmt::Display for BorrowKind {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        match self {
241            BorrowKind::Shared => write!(f, "&"),
242            BorrowKind::Exclusive => write!(f, "&mut"),
243        }
244    }
245}
246
247/// Right-hand side of an assignment.
248#[derive(Debug, Clone, PartialEq)]
249pub enum Rvalue {
250    /// Use an operand directly.
251    Use(Operand),
252    /// Create a borrow: `&place` or `&mut place`
253    Borrow(BorrowKind, Place),
254    /// Binary operation.
255    BinaryOp(BinOp, Operand, Operand),
256    /// Unary operation.
257    UnaryOp(UnOp, Operand),
258    /// Function call result (arguments passed via terminator).
259    /// This is a placeholder — actual calls use Call terminators.
260    Aggregate(Vec<Operand>),
261    /// Clone of a value (explicit or auto-inferred).
262    Clone(Operand),
263}
264
265/// Binary operations in MIR.
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum BinOp {
268    Add,
269    Sub,
270    Mul,
271    Div,
272    Mod,
273    Eq,
274    Ne,
275    Lt,
276    Le,
277    Gt,
278    Ge,
279    And,
280    Or,
281}
282
283/// Unary operations in MIR.
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum UnOp {
286    Neg,
287    Not,
288}
289
290// ── Task Boundary Kind ───────────────────────────────────────────────
291
292/// Distinguishes detached vs structured async task boundaries.
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
294pub enum TaskBoundaryKind {
295    /// Detached async task (not joined in declaring scope).
296    Detached,
297    /// Structured child task (joined before parent scope exits).
298    Structured,
299}
300
301// ── Statements ───────────────────────────────────────────────────────
302
303/// A statement within a basic block (doesn't affect control flow).
304#[derive(Debug, Clone, PartialEq)]
305pub struct MirStatement {
306    pub kind: StatementKind,
307    pub span: Span,
308    /// The program point of this statement (assigned during linearization).
309    pub point: Point,
310}
311
312#[derive(Debug, Clone, PartialEq)]
313pub enum StatementKind {
314    /// Assign a value to a place: `place = rvalue`
315    Assign(Place, Rvalue),
316    /// Drop a place (scope exit, explicit drop).
317    /// Generates invalidation facts for any loans on this place.
318    Drop(Place),
319    /// Cross a task boundary (spawn/join branch capture).
320    /// Operands are the values flowing into the spawned task.
321    /// The kind distinguishes detached vs structured tasks.
322    TaskBoundary(Vec<Operand>, TaskBoundaryKind),
323    /// Capture values into a closure environment.
324    /// Operands are the outer values flowing into the closure.
325    ClosureCapture {
326        closure_slot: SlotId,
327        operands: Vec<Operand>,
328    },
329    /// Store values into an array literal.
330    /// Operands are the array elements being stored.
331    ArrayStore {
332        container_slot: SlotId,
333        operands: Vec<Operand>,
334    },
335    /// Store values into an object or struct literal.
336    /// Operands are the fields/spreads being stored.
337    ObjectStore {
338        container_slot: SlotId,
339        operands: Vec<Operand>,
340    },
341    /// Store values into an enum payload.
342    /// Operands are the tuple/struct payload values being stored.
343    EnumStore {
344        container_slot: SlotId,
345        operands: Vec<Operand>,
346    },
347    /// No-op (placeholder, padding).
348    Nop,
349}
350
351// ── Terminators ──────────────────────────────────────────────────────
352
353/// A block terminator (controls flow between basic blocks).
354#[derive(Debug, Clone, PartialEq)]
355pub struct Terminator {
356    pub kind: TerminatorKind,
357    pub span: Span,
358}
359
360#[derive(Debug, Clone, PartialEq)]
361pub enum TerminatorKind {
362    /// Unconditional jump.
363    Goto(BasicBlockId),
364    /// Conditional branch.
365    SwitchBool {
366        operand: Operand,
367        true_bb: BasicBlockId,
368        false_bb: BasicBlockId,
369    },
370    /// Function call.
371    Call {
372        func: Operand,
373        args: Vec<Operand>,
374        /// Where to store the return value.
375        destination: Place,
376        /// Block to jump to after the call returns.
377        next: BasicBlockId,
378    },
379    /// Return from function.
380    Return,
381    /// Unreachable (after diverging calls, infinite loops).
382    Unreachable,
383}
384
385// ── Basic Blocks ─────────────────────────────────────────────────────
386
387/// A basic block: a sequence of statements ending in a terminator.
388#[derive(Debug, Clone)]
389pub struct BasicBlock {
390    pub id: BasicBlockId,
391    pub statements: Vec<MirStatement>,
392    pub terminator: Terminator,
393}
394
395// ── MIR Function ─────────────────────────────────────────────────────
396
397/// The MIR representation of a single function.
398#[derive(Debug, Clone)]
399pub struct MirFunction {
400    pub name: String,
401    /// The basic blocks forming the CFG.
402    pub blocks: Vec<BasicBlock>,
403    /// Number of local variable slots.
404    pub num_locals: u16,
405    /// Which locals are function parameters.
406    pub param_slots: Vec<SlotId>,
407    /// Per-parameter reference kind, aligned with `param_slots`.
408    pub param_reference_kinds: Vec<Option<BorrowKind>>,
409    /// Type information for locals (for Copy/Clone inference).
410    pub local_types: Vec<LocalTypeInfo>,
411    /// Source span of the function.
412    pub span: Span,
413}
414
415/// Type information for a local variable, used for Copy/Clone inference.
416#[derive(Debug, Clone, PartialEq, Eq)]
417pub enum LocalTypeInfo {
418    /// Primitive (int, number, bool, none) — implicitly Copy, no borrow tracking.
419    Copy,
420    /// Heap type (String, Array, TypedObject, etc.) — requires borrow/move/clone tracking.
421    NonCopy,
422    /// Unknown type (will be resolved during analysis).
423    Unknown,
424}
425
426impl MirFunction {
427    /// Get the entry block (always block 0).
428    pub fn entry_block(&self) -> BasicBlockId {
429        BasicBlockId(0)
430    }
431
432    /// Iterate over all blocks.
433    pub fn iter_blocks(&self) -> impl Iterator<Item = &BasicBlock> {
434        self.blocks.iter()
435    }
436
437    /// Get a block by ID.
438    pub fn block(&self, id: BasicBlockId) -> &BasicBlock {
439        &self.blocks[id.0 as usize]
440    }
441
442    /// Linearize all statements into a flat list of points.
443    /// Returns (point, block_id, statement_index) triples.
444    pub fn all_points(&self) -> Vec<(Point, BasicBlockId, usize)> {
445        let mut points = Vec::new();
446        for block in &self.blocks {
447            for (i, stmt) in block.statements.iter().enumerate() {
448                points.push((stmt.point, block.id, i));
449            }
450        }
451        points
452    }
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn test_place_root_local() {
461        let p = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(1));
462        assert_eq!(p.root_local(), SlotId(0));
463    }
464
465    #[test]
466    fn test_place_prefix() {
467        let x = Place::Local(SlotId(0));
468        let xa = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0));
469        assert!(x.is_prefix_of(&xa));
470        assert!(!xa.is_prefix_of(&x));
471    }
472
473    #[test]
474    fn test_disjoint_fields_no_conflict() {
475        let xa = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0));
476        let xb = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(1));
477        // Disjoint fields should not overlap
478        assert!(!xa.overlaps(&xb));
479    }
480
481    #[test]
482    fn test_same_field_conflicts() {
483        let xa1 = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0));
484        let xa2 = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0));
485        assert!(xa1.conflicts_with(&xa2));
486    }
487
488    #[test]
489    fn test_different_locals_no_conflict() {
490        let x = Place::Local(SlotId(0));
491        let y = Place::Local(SlotId(1));
492        assert!(!x.conflicts_with(&y));
493    }
494
495    #[test]
496    fn test_parent_child_conflict() {
497        let x = Place::Local(SlotId(0));
498        let xa = Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0));
499        assert!(x.conflicts_with(&xa));
500        assert!(xa.conflicts_with(&x));
501    }
502}