1use crate::{const_prop::ConstProp, pass::ModulePass, value_rewrite::rewrite_values_in_kind};
4use llvm_ir::{
5 ArgId, Context, Function, FunctionId, GlobalId, InstrKind, Module, ValueRef,
6};
7
8pub struct Ipcp;
13
14impl ModulePass for Ipcp {
15 fn name(&self) -> &'static str {
16 "ipcp"
17 }
18
19 fn run_on_module(&mut self, ctx: &mut Context, module: &mut Module) -> bool {
20 let mut changed = false;
21
22 for callee_idx in 0..module.functions.len() {
23 let callee_id = FunctionId(callee_idx as u32);
24 if module.functions[callee_idx].is_declaration || module.functions[callee_idx].args.is_empty() {
25 continue;
26 }
27
28 let callsites = collect_direct_calls(module, callee_id);
29 if callsites.is_empty() {
30 continue;
31 }
32 let Some((arg_idx, const_val)) = find_constant_arg(&callsites) else {
33 continue;
34 };
35
36 let spec_name = format!(
37 "{}.ipcp.a{}.c{}",
38 module.functions[callee_idx].name, arg_idx, const_val.0
39 );
40 let spec_id = if let Some(fid) = module.get_function_id(&spec_name) {
41 fid
42 } else {
43 let mut spec = clone_specialized_function(
44 &module.functions[callee_idx],
45 &spec_name,
46 ArgId(arg_idx as u32),
47 ValueRef::Constant(const_val),
48 );
49 let mut cp = ConstProp;
50 let _ = crate::pass::FunctionPass::run_on_function(&mut cp, ctx, &mut spec);
51 module.add_function(spec)
52 };
53
54 let spec_ty = module.functions[spec_id.0 as usize].ty;
55 for cs in callsites {
56 if cs.const_args.get(arg_idx).copied() == Some(Some(const_val)) {
57 let instr = &mut module.functions[cs.caller.0 as usize].instructions[cs.iid.0 as usize];
58 if let InstrKind::Call { callee, callee_ty, .. } = &mut instr.kind {
59 *callee = ValueRef::Global(GlobalId(spec_id.0));
60 *callee_ty = spec_ty;
61 changed = true;
62 }
63 }
64 }
65 }
66
67 changed
68 }
69}
70
71#[derive(Clone)]
72struct DirectCallSite {
73 caller: FunctionId,
74 iid: llvm_ir::InstrId,
75 const_args: Vec<Option<llvm_ir::ConstId>>,
76}
77
78fn collect_direct_calls(module: &Module, callee_id: FunctionId) -> Vec<DirectCallSite> {
79 let mut out = Vec::new();
80 for (caller_idx, f) in module.functions.iter().enumerate() {
81 if f.is_declaration {
82 continue;
83 }
84 for (iid_idx, instr) in f.instructions.iter().enumerate() {
85 let InstrKind::Call { callee, args, .. } = &instr.kind else {
86 continue;
87 };
88 if *callee != ValueRef::Global(GlobalId(callee_id.0)) {
89 continue;
90 }
91 let const_args = args
92 .iter()
93 .map(|a| match a {
94 ValueRef::Constant(c) => Some(*c),
95 _ => None,
96 })
97 .collect();
98 out.push(DirectCallSite {
99 caller: FunctionId(caller_idx as u32),
100 iid: llvm_ir::InstrId(iid_idx as u32),
101 const_args,
102 });
103 }
104 }
105 out
106}
107
108fn find_constant_arg(callsites: &[DirectCallSite]) -> Option<(usize, llvm_ir::ConstId)> {
109 let argc = callsites.first()?.const_args.len();
110 for ai in 0..argc {
111 let mut cst: Option<llvm_ir::ConstId> = None;
112 let mut ok = true;
113 for cs in callsites {
114 match cs.const_args.get(ai).copied().flatten() {
115 Some(c) => {
116 if let Some(prev) = cst {
117 if prev != c {
118 ok = false;
119 break;
120 }
121 } else {
122 cst = Some(c);
123 }
124 }
125 None => {
126 ok = false;
127 break;
128 }
129 }
130 }
131 if ok {
132 return cst.map(|c| (ai, c));
133 }
134 }
135 None
136}
137
138fn clone_specialized_function(
139 src: &Function,
140 new_name: &str,
141 arg_id: ArgId,
142 const_val: ValueRef,
143) -> Function {
144 let mut dst = Function::new(new_name.to_string(), src.ty, src.args.clone(), src.linkage);
145 dst.blocks = src.blocks.clone();
146 dst.instructions = src.instructions.clone();
147 dst.value_names = src.value_names.clone();
148 dst.arg_names = src.arg_names.clone();
149 dst.is_declaration = false;
150
151 for instr in &mut dst.instructions {
152 let old = instr.kind.clone();
153 instr.kind = rewrite_values_in_kind(old, |v| {
154 if v == ValueRef::Argument(arg_id) {
155 const_val
156 } else {
157 v
158 }
159 });
160 }
161 dst
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use llvm_ir::{Builder, Linkage};
168
169 #[test]
170 fn ipcp_specializes_constant_argument_and_rewrites_calls() {
171 let mut ctx = Context::new();
172 let mut module = Module::new("m");
173 let mut b = Builder::new(&mut ctx, &mut module);
174
175 b.add_function(
176 "addk",
177 b.ctx.i64_ty,
178 vec![b.ctx.i64_ty, b.ctx.i64_ty],
179 vec!["x".into(), "k".into()],
180 false,
181 Linkage::External,
182 );
183 let addk_entry = b.add_block("addk.entry");
184 b.position_at_end(addk_entry);
185 let x = b.get_arg(0);
186 let k = b.get_arg(1);
187 let s = b.build_add("s", x, k);
188 b.build_ret(s);
189
190 b.add_function(
191 "caller",
192 b.ctx.i64_ty,
193 vec![b.ctx.i64_ty],
194 vec!["x".into()],
195 false,
196 Linkage::External,
197 );
198 let caller_entry = b.add_block("caller.entry");
199 b.position_at_end(caller_entry);
200 let x0 = b.get_arg(0);
201 let c7 = b.const_int(b.ctx.i64_ty, 7);
202 let c7b = b.const_int(b.ctx.i64_ty, 7);
203 let call_ty = b.ctx.mk_fn_type(b.ctx.i64_ty, vec![b.ctx.i64_ty, b.ctx.i64_ty], false);
204 let t1 = b.build_call(
205 "t1",
206 b.ctx.i64_ty,
207 call_ty,
208 ValueRef::Global(GlobalId(0)),
209 vec![x0, c7],
210 );
211 let t2 = b.build_call(
212 "t2",
213 b.ctx.i64_ty,
214 call_ty,
215 ValueRef::Global(GlobalId(0)),
216 vec![t1, c7],
217 );
218 let _t3 = b.build_call(
219 "t3",
220 b.ctx.i64_ty,
221 call_ty,
222 ValueRef::Global(GlobalId(0)),
223 vec![t2, c7b],
224 );
225 b.build_ret(t2);
226
227 let mut pass = Ipcp;
228 let changed = pass.run_on_module(&mut ctx, &mut module);
229 assert!(changed);
230 assert!(
231 module.functions.iter().any(|f| f.name.starts_with("addk.ipcp")),
232 "expected specialized clone"
233 );
234 }
235}