Rust · 4262 bytes Raw Blame History
1 //! Call resolution pass.
2 //!
3 //! Rewrites `Call(FuncRef::External(name), args)` to
4 //! `Call(FuncRef::Internal(idx), args)` when `name` matches a
5 //! function defined in the same module. This enables the inliner
6 //! to find the callee's IR body.
7 //!
8 //! Runs as the first pass at O1+ (before Mem2Reg).
9
10 use super::pass::Pass;
11 use crate::ir::inst::*;
12 use std::collections::HashMap;
13
14 pub struct CallResolve;
15
16 impl Pass for CallResolve {
17 fn name(&self) -> &'static str {
18 "call-resolve"
19 }
20
21 fn run(&self, module: &mut Module) -> bool {
22 // Build name → index mapping for all functions in the module.
23 let name_to_idx: HashMap<String, u32> = module
24 .functions
25 .iter()
26 .enumerate()
27 .map(|(i, f)| (f.name.clone(), i as u32))
28 .collect();
29
30 let mut changed = false;
31
32 for func in &mut module.functions {
33 for block in &mut func.blocks {
34 for inst in &mut block.insts {
35 if let InstKind::Call(ref mut func_ref, _) = &mut inst.kind {
36 if let FuncRef::External(name) = func_ref {
37 if let Some(&idx) = name_to_idx.get(name.as_str()) {
38 *func_ref = FuncRef::Internal(idx);
39 changed = true;
40 }
41 }
42 }
43 }
44 }
45 }
46
47 changed
48 }
49 }
50
51 #[cfg(test)]
52 mod tests {
53 use super::*;
54 use crate::ir::inst::*;
55 use crate::ir::types::{IntWidth, IrType};
56 use crate::opt::pass::Pass;
57
58 #[test]
59 fn resolves_internal_calls() {
60 let mut m = Module::new("test".into());
61
62 // Callee: function "double_it"
63 let mut callee = Function::new("double_it".into(), vec![], IrType::Int(IntWidth::I32));
64 callee.block_mut(callee.entry).terminator = Some(Terminator::Return(None));
65 m.add_function(callee);
66
67 // Caller: calls "double_it" via External ref
68 let mut caller = Function::new("main".into(), vec![], IrType::Void);
69 let call_id = caller.next_value_id();
70 caller.register_type(call_id, IrType::Int(IntWidth::I32));
71 caller.block_mut(caller.entry).insts.push(Inst {
72 id: call_id,
73 ty: IrType::Int(IntWidth::I32),
74 span: crate::lexer::Span {
75 file_id: 0,
76 start: crate::lexer::Position { line: 0, col: 0 },
77 end: crate::lexer::Position { line: 0, col: 0 },
78 },
79 kind: InstKind::Call(FuncRef::External("double_it".into()), vec![]),
80 });
81 caller.block_mut(caller.entry).terminator = Some(Terminator::Return(None));
82 m.add_function(caller);
83
84 let pass = CallResolve;
85 let changed = pass.run(&mut m);
86 assert!(changed, "should resolve the internal call");
87
88 // Verify the call was rewritten.
89 let caller_func = &m.functions[1];
90 let call_inst = &caller_func.blocks[0].insts[0];
91 match &call_inst.kind {
92 InstKind::Call(FuncRef::Internal(idx), _) => {
93 assert_eq!(*idx, 0, "should point to function index 0 (double_it)");
94 }
95 other => panic!("expected Internal call, got {:?}", other),
96 }
97 }
98
99 #[test]
100 fn leaves_external_calls_unchanged() {
101 let mut m = Module::new("test".into());
102 let mut f = Function::new("main".into(), vec![], IrType::Void);
103 let call_id = f.next_value_id();
104 f.register_type(call_id, IrType::Void);
105 f.block_mut(f.entry).insts.push(Inst {
106 id: call_id,
107 ty: IrType::Void,
108 span: crate::lexer::Span {
109 file_id: 0,
110 start: crate::lexer::Position { line: 0, col: 0 },
111 end: crate::lexer::Position { line: 0, col: 0 },
112 },
113 kind: InstKind::Call(FuncRef::External("afs_write_int".into()), vec![]),
114 });
115 f.block_mut(f.entry).terminator = Some(Terminator::Return(None));
116 m.add_function(f);
117
118 let pass = CallResolve;
119 let changed = pass.run(&mut m);
120 assert!(!changed, "runtime calls should stay External");
121 }
122 }
123