ruka_mir/
cfg.rs

1use std::collections::BTreeMap;
2
3use cranelift_entity::PrimaryMap;
4use ruka_types::Ty;
5
6use crate::{
7    LocalInfo, LocalKind, MirBlockId, MirCallArg, MirCallArgBinding, MirEnumDecl, MirFuncId,
8    MirHeapOwnership, MirInstr, MirLocalId, MirLocalRepr, MirOwnershipMode, MirStructDecl,
9};
10
11/// Whole-program MIR container.
12///
13/// `function_names` provides fast lookup from source-level function name
14/// to MIR function id.
15#[derive(Debug, Clone)]
16pub struct MirProgram {
17    /// Functions stored in the MIR program arena.
18    pub functions: PrimaryMap<MirFuncId, MirFunction>,
19    /// Lookup table from canonical function name to function id.
20    pub function_names: BTreeMap<String, MirFuncId>,
21    /// Struct declarations referenced by MIR.
22    pub structs: Vec<MirStructDecl>,
23    /// Enum declarations referenced by MIR.
24    pub enums: Vec<MirEnumDecl>,
25}
26
27impl MirProgram {
28    /// Return the number of functions stored in the MIR program.
29    pub fn function_count(&self) -> usize {
30        self.functions.len()
31    }
32}
33
34/// Function-level MIR in CFG form.
35///
36/// - `params` are function parameter locals.
37/// - `param_modes` mirrors source ownership modes for parameters.
38/// - `entry` is the first block reached for execution.
39#[derive(Debug, Clone)]
40pub struct MirFunction {
41    /// User-visible function or field name.
42    pub name: String,
43    /// Number of source parameters accepted by the function.
44    pub arity: usize,
45    /// Semantic return type of the function.
46    pub return_ty: Ty,
47    /// Local metadata arena for function values.
48    pub locals: PrimaryMap<MirLocalId, LocalInfo>,
49    /// Local ids corresponding to incoming parameters.
50    pub params: Vec<MirLocalId>,
51    /// Ownership modes for each incoming parameter.
52    pub param_modes: Vec<MirOwnershipMode>,
53    /// Entry block id for the function CFG.
54    pub entry: MirBlockId,
55    /// CFG blocks stored for the function.
56    pub blocks: PrimaryMap<MirBlockId, MirBlock>,
57}
58
59/// Shared parameter query object used by lowering and backends.
60#[derive(Debug, Clone, Copy)]
61pub struct MirParamBinding<'a> {
62    /// Positional parameter index in the source signature.
63    pub index: usize,
64    /// MIR local receiving the parameter.
65    pub local_id: MirLocalId,
66    /// Source ownership mode for the parameter.
67    pub mode: MirOwnershipMode,
68    /// Local metadata recorded for the parameter.
69    pub local: &'a LocalInfo,
70}
71
72impl<'a> MirParamBinding<'a> {
73    /// Return whether the parameter expects read-only view access.
74    pub fn expects_view(self) -> bool {
75        matches!(self.mode, MirOwnershipMode::View)
76    }
77
78    /// Return whether the parameter expects mutable borrow access.
79    pub fn expects_mut_borrow(self) -> bool {
80        matches!(self.mode, MirOwnershipMode::MutBorrow)
81    }
82
83    /// Return whether the parameter expects owned value access.
84    pub fn expects_owned(self) -> bool {
85        matches!(self.mode, MirOwnershipMode::Owned)
86    }
87
88    /// Return the semantic item type exposed at the source-language boundary.
89    pub fn semantic_ty(self) -> &'a Ty {
90        match self.mode {
91            MirOwnershipMode::View | MirOwnershipMode::MutBorrow => {
92                self.local.place_item_ty().unwrap_or(&self.local.ty)
93            }
94            MirOwnershipMode::Owned => &self.local.ty,
95        }
96    }
97
98    /// Return the source-language representation expected at the function boundary.
99    pub fn source_repr(self) -> MirLocalRepr {
100        match self.mode {
101            MirOwnershipMode::View => MirLocalRepr::SharedPlace,
102            MirOwnershipMode::MutBorrow => MirLocalRepr::MutablePlace,
103            MirOwnershipMode::Owned => MirLocalRepr::Value,
104        }
105    }
106
107    /// Return the lowered storage/runtime representation of the parameter local.
108    pub fn local_repr(self) -> MirLocalRepr {
109        self.local.repr
110    }
111
112    /// Return whether the lowered local needs materialization from the source boundary shape.
113    pub fn requires_materialization(self) -> bool {
114        self.source_repr() != self.local_repr()
115    }
116
117    /// Return whether this binding is the view-from-owned materialization case.
118    pub fn materializes_view_from_owned(self) -> bool {
119        self.expects_view() && self.requires_materialization()
120    }
121}
122
123impl MirFunction {
124    /// Return metadata for one parameter by index.
125    pub fn param_binding(&self, index: usize) -> MirParamBinding<'_> {
126        let local_id = self.params[index];
127        MirParamBinding {
128            index,
129            local_id,
130            mode: self.param_modes[index],
131            local: &self.locals[local_id],
132        }
133    }
134
135    /// Iterate over source parameters with normalized query helpers.
136    pub fn param_bindings(&self) -> impl Iterator<Item = MirParamBinding<'_>> + '_ {
137        self.params
138            .iter()
139            .enumerate()
140            .map(|(index, _)| self.param_binding(index))
141    }
142
143    /// Return metadata for one local.
144    pub fn local_info(&self, local_id: MirLocalId) -> &LocalInfo {
145        &self.locals[local_id]
146    }
147
148    /// Return normalized metadata for one call argument.
149    pub fn call_arg_binding<'a>(&'a self, arg: &'a MirCallArg) -> MirCallArgBinding<'a> {
150        MirCallArgBinding {
151            arg,
152            local: &self.locals[arg.local],
153        }
154    }
155
156    /// Assert internal MIR invariants needed by downstream backends.
157    pub fn assert_valid(&self) {
158        assert_eq!(
159            self.params.len(),
160            self.param_modes.len(),
161            "function `{}` has mismatched params and param modes",
162            self.name,
163        );
164
165        for binding in self.param_bindings() {
166            let info = binding.local;
167            assert!(
168                matches!(info.kind, LocalKind::Param),
169                "function `{}` has non-param local {:?} in params list",
170                self.name,
171                binding.local_id,
172            );
173            match binding.mode {
174                MirOwnershipMode::View => assert!(
175                    !info.repr.is_mutable_place(),
176                    "function `{}` has mutable view param {:?}",
177                    self.name,
178                    binding.local_id,
179                ),
180                MirOwnershipMode::MutBorrow => assert!(
181                    info.repr.is_mutable_place(),
182                    "function `{}` has non-mutable borrowed param {:?}",
183                    self.name,
184                    binding.local_id,
185                ),
186                MirOwnershipMode::Owned => assert!(
187                    !info.repr.is_place(),
188                    "function `{}` has place-shaped owned param {:?}",
189                    self.name,
190                    binding.local_id,
191                ),
192            }
193        }
194
195        for (local_id, info) in self.locals.iter() {
196            match info.heap_ownership {
197                MirHeapOwnership::None => {}
198                MirHeapOwnership::Owned | MirHeapOwnership::OwnedShallow => assert!(
199                    !info.repr.is_place(),
200                    "function `{}` marks place local {:?} as heap-owned",
201                    self.name,
202                    local_id,
203                ),
204                MirHeapOwnership::BorrowedView => {
205                    assert!(
206                        info.repr.is_place(),
207                        "function `{}` marks non-place local {:?} as BorrowedView",
208                        self.name,
209                        local_id,
210                    );
211                    assert!(
212                        matches!(info.place_item_ty(), Some(Ty::Slice(_))),
213                        "function `{}` marks non-slice place local {:?} as BorrowedView",
214                        self.name,
215                        local_id,
216                    );
217                }
218            }
219        }
220
221        for (_, block) in self.blocks.iter() {
222            for local_id in &block.params {
223                let _ = &self.locals[*local_id];
224            }
225            for instr in &block.instrs {
226                self.assert_instr_valid(instr);
227            }
228            match &block.terminator {
229                MirTerminator::Jump { target, args } => {
230                    self.assert_edge_args_compatible(
231                        self.blocks[*target].params.as_slice(),
232                        args.as_slice(),
233                        "jump",
234                    );
235                }
236                MirTerminator::Branch {
237                    cond,
238                    then_target,
239                    then_args,
240                    else_target,
241                    else_args,
242                    ..
243                } => {
244                    assert!(
245                        !self.locals[*cond].repr.is_place(),
246                        "function `{}` branches on place local {:?}",
247                        self.name,
248                        cond,
249                    );
250                    let then_params = &self.blocks[*then_target].params;
251                    self.assert_edge_args_compatible(
252                        then_params.as_slice(),
253                        then_args.as_slice(),
254                        "then",
255                    );
256                    let else_params = &self.blocks[*else_target].params;
257                    self.assert_edge_args_compatible(
258                        else_params.as_slice(),
259                        else_args.as_slice(),
260                        "else",
261                    );
262                }
263                MirTerminator::Return { value } => {
264                    assert!(
265                        !self.locals[*value].repr.is_place(),
266                        "function `{}` returns place local {:?}",
267                        self.name,
268                        value,
269                    );
270                    assert_eq!(
271                        self.locals[*value].ty, self.return_ty,
272                        "function `{}` returns local {:?} with mismatched type",
273                        self.name, value,
274                    );
275                }
276            }
277        }
278    }
279
280    /// Assert that edge arguments match the target block parameter contract.
281    fn assert_edge_args_compatible(
282        &self,
283        params: &[MirLocalId],
284        args: &[MirLocalId],
285        edge_kind: &'static str,
286    ) {
287        assert_eq!(
288            params.len(),
289            args.len(),
290            "function `{}` has {edge_kind} edge with mismatched block arg count",
291            self.name,
292        );
293        for (param, arg) in params.iter().zip(args.iter()) {
294            let param_info = &self.locals[*param];
295            let arg_info = &self.locals[*arg];
296            assert_eq!(
297                param_info.repr, arg_info.repr,
298                "function `{}` has {edge_kind} edge with repr mismatch into block param {:?}",
299                self.name, param,
300            );
301            assert_eq!(
302                param_info.ty, arg_info.ty,
303                "function `{}` has {edge_kind} edge with type mismatch into block param {:?}",
304                self.name, param,
305            );
306        }
307    }
308
309    /// Assert per-instruction MIR invariants.
310    fn assert_instr_valid(&self, instr: &MirInstr) {
311        match instr {
312            MirInstr::StoreRef { dst_ref, .. } => assert!(
313                self.locals[*dst_ref].repr.is_mutable_place(),
314                "function `{}` stores through non-mutable place {:?}",
315                self.name,
316                dst_ref,
317            ),
318            MirInstr::ReleaseHeap { local } => {
319                assert!(
320                    !self.locals[*local].repr.is_place(),
321                    "function `{}` applies heap ownership op to place local {:?}",
322                    self.name,
323                    local,
324                );
325                assert!(
326                    self.locals[*local].uses_heap_ops(),
327                    "function `{}` applies heap ownership op to non-heap local {:?}",
328                    self.name,
329                    local,
330                );
331            }
332            MirInstr::DerefCopy { src, dst } => {
333                assert!(
334                    self.locals[*src].repr.is_place(),
335                    "function `{}` dereferences non-place local {:?}",
336                    self.name,
337                    src,
338                );
339                assert!(
340                    !self.locals[*dst].repr.is_place(),
341                    "function `{}` deref-copies into place local {:?}",
342                    self.name,
343                    dst,
344                );
345            }
346            MirInstr::Call { args, dst, .. }
347            | MirInstr::CallExtern { args, dst, .. }
348            | MirInstr::CallIntrinsic { args, dst, .. } => {
349                assert!(
350                    !self.locals[*dst].repr.is_place(),
351                    "function `{}` writes call result into place {:?}",
352                    self.name,
353                    dst,
354                );
355                self.assert_call_args_valid(args.as_slice());
356            }
357            MirInstr::EnumIsVariant { value, dst, .. }
358            | MirInstr::EnumGetField { value, dst, .. } => {
359                assert!(
360                    !self.locals[*value].repr.is_place(),
361                    "function `{}` reads enum metadata from place local {:?}",
362                    self.name,
363                    value,
364                );
365                assert!(
366                    !self.locals[*dst].repr.is_place(),
367                    "function `{}` writes enum metadata into place {:?}",
368                    self.name,
369                    dst,
370                );
371            }
372            MirInstr::NumCast { src, dst } | MirInstr::CheckedIntCast { src, dst } => {
373                assert!(
374                    !self.locals[*src].repr.is_place(),
375                    "function `{}` uses place source {:?} in numeric cast",
376                    self.name,
377                    src,
378                );
379                assert!(
380                    !self.locals[*dst].repr.is_place(),
381                    "function `{}` writes numeric cast into place {:?}",
382                    self.name,
383                    dst,
384                );
385            }
386            MirInstr::IntLt { lhs, rhs, dst }
387            | MirInstr::IntGt { lhs, rhs, dst }
388            | MirInstr::IntLtEq { lhs, rhs, dst }
389            | MirInstr::IntGtEq { lhs, rhs, dst }
390            | MirInstr::IntEq { lhs, rhs, dst }
391            | MirInstr::IntNeq { lhs, rhs, dst }
392            | MirInstr::IntAdd { lhs, rhs, dst }
393            | MirInstr::IntSub { lhs, rhs, dst }
394            | MirInstr::IntMul { lhs, rhs, dst }
395            | MirInstr::IntDiv { lhs, rhs, dst }
396            | MirInstr::IntMod { lhs, rhs, dst } => {
397                assert!(
398                    !self.locals[*lhs].repr.is_place(),
399                    "function `{}` uses place lhs {:?} in scalar op",
400                    self.name,
401                    lhs,
402                );
403                assert!(
404                    !self.locals[*rhs].repr.is_place(),
405                    "function `{}` uses place rhs {:?} in scalar op",
406                    self.name,
407                    rhs,
408                );
409                assert!(
410                    !self.locals[*dst].repr.is_place(),
411                    "function `{}` writes scalar op into place {:?}",
412                    self.name,
413                    dst,
414                );
415            }
416            _ => {}
417        }
418    }
419
420    /// Assert call argument invariants that are backend-independent.
421    fn assert_call_args_valid(&self, args: &[MirCallArg]) {
422        for arg in args {
423            let binding = self.call_arg_binding(arg);
424            if binding.is_mutable_borrow() {
425                assert!(
426                    !matches!(binding.local.repr, MirLocalRepr::SharedPlace),
427                    "function `{}` mutably borrows shared place {:?}",
428                    self.name,
429                    binding.local_id(),
430                );
431            }
432        }
433    }
434}
435
436/// Basic block in MIR CFG.
437///
438/// `params` are edge-passed block parameters (phi-like interface).
439#[derive(Debug, Clone)]
440pub struct MirBlock {
441    /// Local ids corresponding to incoming parameters.
442    pub params: Vec<MirLocalId>,
443    /// Instructions executed before the block terminator.
444    pub instrs: Vec<MirInstr>,
445    /// Terminator controlling the outgoing flow edge(s).
446    pub terminator: MirTerminator,
447}
448
449/// Block terminator defining control-flow edges.
450#[derive(Debug, Clone)]
451pub enum MirTerminator {
452    /// Unconditional jump to `target` with block argument values.
453    Jump {
454        /// Block id to jump to.
455        target: MirBlockId,
456        /// Block arguments passed to the target.
457        args: Vec<MirLocalId>,
458    },
459    /// Conditional branch with explicit then/else targets and arguments.
460    Branch {
461        /// Local id holding the branch condition.
462        cond: MirLocalId,
463        /// Target block when the condition is true.
464        then_target: MirBlockId,
465        /// Block arguments passed to the then target.
466        then_args: Vec<MirLocalId>,
467        /// Target block when the condition is false.
468        else_target: MirBlockId,
469        /// Block arguments passed to the else target.
470        else_args: Vec<MirLocalId>,
471    },
472    /// Return from current function with value local.
473    /// Structured return used during CFG construction.
474    Return {
475        /// Local id returned from the function.
476        value: MirLocalId,
477    },
478}