1use rucc_ir::{
45 Def, Extra, Func, FuncId, Imm, Inst, InstData, Module, Opcode, Pic, SymbolRef, Value,
46};
47
48use crate::Cfg;
49use crate::extents::vouched;
50
51const DEPTH: u32 = 16;
54
55pub fn answer(module: &mut Module, pic: Pic, look: bool) -> usize {
59 let mut answered = 0;
60 for id in module.funcs().collect::<Vec<FuncId>>() {
61 if module[id].is_declaration() {
62 continue;
63 }
64 let asked = questions(&module[id]);
65 if asked.is_empty() {
66 continue;
67 }
68 let answers: Vec<(Inst, i128)> = {
69 let func = &module[id];
70 let walk = Walk { module, func, cfg: &Cfg::new(func), pic };
71 asked
72 .iter()
73 .map(|&inst| {
74 let Extra::Question(kind) = func[inst].extra else { return (inst, 0) };
75 let address = func[func[inst].args][0];
76 let largest = kind & 2 == 0;
77 let known = match (look, kind) {
78 (false, _) | (_, 3) => None,
79 _ => walk.left(address, largest, DEPTH, &mut Vec::new()).ok().flatten(),
80 };
81 (inst, known.map_or(if largest { -1 } else { 0 }, i128::from))
82 })
83 .collect()
84 };
85 let func = &mut module[id];
86 for (inst, number) in answers {
87 write(func, inst, number);
88 answered += 1;
89 }
90 }
91 answered
92}
93
94fn questions(func: &Func) -> Vec<Inst> {
96 func.blocks()
97 .flat_map(|block| func.insts(block))
98 .filter(|&inst| func[inst].opcode == Opcode::ObjectSize)
99 .collect()
100}
101
102fn write(func: &mut Func, inst: Inst, number: i128) {
104 let result = func[inst].results().next().expect("an object size is one value");
105 let ty = func[result].ty;
106 let span = func.span(inst);
107 let imm = func.add_imm(Imm::int(number, ty.lane()));
108 let data = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
109 let made = func.create_inst(data, &[ty], span);
110 func.insert_before(made, inst);
111 let value = func[made].results().next().expect("a constant is one value");
112 let forward = [(result, value)].into_iter().collect();
113 crate::uses::substitute(func, &forward);
114 func.remove_inst(inst);
115}
116
117struct Walk<'a> {
119 module: &'a Module,
120 func: &'a Func,
121 cfg: &'a Cfg,
122 pic: Pic,
123}
124
125type Left = Result<Option<u64>, ()>;
128
129impl Walk<'_> {
130 fn left(&self, value: Value, largest: bool, depth: u32, on: &mut Vec<Value>) -> Left {
134 let depth = depth.checked_sub(1).ok_or(())?;
135 match self.func[value].def {
136 Def::Param { block, index } => {
137 if on.contains(&value) {
138 return Ok(None);
142 }
143 let preds = self.cfg.predecessors(block);
144 if preds.is_empty() {
145 return Err(());
146 }
147 on.push(value);
148 let mut all = Ok(None);
149 for &pred in preds {
150 let term = self.func.terminator(pred).ok_or(())?;
151 for call in self.func.successors(term).collect::<Vec<_>>() {
152 if call.block != block {
153 continue;
154 }
155 let arg = *self.func[call.args].get(index as usize).ok_or(())?;
156 all = both(all, self.left(arg, largest, depth, on), largest);
157 }
158 }
159 on.pop();
160 all
161 }
162 Def::Result { inst, .. } => {
163 let data = &self.func[inst];
164 let args = &self.func[data.args];
165 match data.opcode {
166 Opcode::Select => {
167 let (then, other) = (*args.get(1).ok_or(())?, *args.get(2).ok_or(())?);
168 let then = self.left(then, largest, depth, on);
169 both(then, self.left(other, largest, depth, on), largest)
170 }
171 Opcode::PtrAdd => {
172 let base = *args.first().ok_or(())?;
173 let (imm, ty) =
174 crate::fold::evaluated(self.func, *args.get(1).ok_or(())?, 4)
175 .ok_or(())?;
176 let step = u64::try_from(imm.signed(ty)).map_err(|_| ())?;
177 match self.left(base, largest, depth, on)? {
178 Some(left) => Ok(Some(left.saturating_sub(step))),
179 None if !largest && step != 0 => Err(()),
182 None => Ok(None),
183 }
184 }
185 Opcode::Alloca => match args.first() {
186 None => {
187 let Extra::Mem(mem) = data.extra else { return Err(()) };
188 Ok(Some(self.func[mem].size))
189 }
190 Some(&count) => {
191 let (imm, _) = crate::fold::evaluated(self.func, count, 4).ok_or(())?;
192 Ok(Some(u64::try_from(imm.unsigned()).map_err(|_| ())?))
193 }
194 },
195 Opcode::GlobalAddr => {
196 let Extra::Symbol(name) = data.extra else { return Err(()) };
197 let Some(SymbolRef::Global(id)) = self.module.lookup(name) else {
198 return Err(());
199 };
200 let global = &self.module[id];
201 if !vouched(global, self.pic) {
202 return Err(());
203 }
204 Ok(Some(global.size))
205 }
206 _ => Err(()),
207 }
208 }
209 }
210 }
211}
212
213fn both(one: Left, other: Left, largest: bool) -> Left {
215 Ok(match (one?, other?) {
216 (Some(one), Some(other)) if largest => Some(one.max(other)),
217 (Some(one), Some(other)) => Some(one.min(other)),
218 (Some(one), None) | (None, Some(one)) => Some(one),
219 (None, None) => None,
220 })
221}
222
223#[cfg(test)]
224mod tests {
225 use rucc_base::Interner;
226
227 use super::*;
228
229 const HEAD: &str = "\
231; ModuleID = 't.c'
232; format 0
233target triple = \"x86_64-unknown-linux-gnu\"
234target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
235";
236
237 fn answers(body: &str, look: bool) -> Vec<i128> {
240 let mut names = Interner::new();
241 let text = format!("{HEAD}{body}");
242 let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
243 answer(&mut module, Pic::Executable, look);
244 if let Err(errors) = rucc_ir::verify(&module, &names) {
245 panic!("the answers left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
246 }
247 let mut found = Vec::new();
248 for id in module.funcs() {
249 let func = &module[id];
250 for block in func.blocks() {
251 for inst in func.insts(block) {
252 assert_ne!(func[inst].opcode, Opcode::ObjectSize, "a question was left");
253 if func[inst].opcode != Opcode::Call {
254 continue;
255 }
256 let &[value] = &func[func[inst].args] else { continue };
257 let Def::Result { inst: def, .. } = func[value].def else { continue };
258 let Extra::Imm(imm) = func[def].extra else { continue };
259 found.push(func[imm].signed(func[value].ty));
260 }
261 }
262 }
263 found
264 }
265
266 #[test]
269 fn a_choice_of_two_objects_is_the_larger_or_the_smaller_of_what_each_has_left() {
270 let body = "
271global @g : bytes 32 = { zero 32 }, align 1, linkage(external)
272
273func @f(i32), linkage(external) {
274block0(%0: i32):
275 %1 = alloca, size 20, align 16
276 %2 = iconst.i32 0
277 %3 = icmp ne %0, %2
278 br_if %3, block1, block2
279
280block1:
281 %4 = iconst.i32 5
282 %5 = sext.i64 %4
283 %6 = ptr_add %1, %5
284 jump block3(%6)
285
286block2:
287 %7 = global_addr @g
288 %8 = iconst.i64 4
289 %9 = ptr_add %7, %8
290 jump block3(%9)
291
292block3(%10: ptr):
293 %11 = object_size.i64 %10, kind 0
294 call @use(%11) : (i64)
295 %12 = object_size.i64 %10, kind 1
296 call @use(%12) : (i64)
297 %13 = object_size.i64 %10, kind 2
298 call @use(%13) : (i64)
299 %14 = object_size.i64 %10, kind 3
300 call @use(%14) : (i64)
301 return
302}
303";
304 assert_eq!(answers(body, true), [28, 28, 15, 0]);
307 assert_eq!(answers(body, false), [-1, -1, 0, 0]);
309 }
310
311 #[test]
314 fn a_pointer_a_loop_leaves_alone_is_what_it_was_given() {
315 let body = "
316func @f(i32), linkage(external) {
317block0(%0: i32):
318 %1 = alloca, size 20, align 16
319 %2 = iconst.i32 0
320 jump block1(%1, %2)
321
322block1(%3: ptr, %4: i32):
323 %5 = icmp eq %4, %0
324 %6 = iconst.i64 7
325 %7 = ptr_add %1, %6
326 %8 = select.ptr %5, %7, %3
327 %9 = iconst.i32 1
328 %10 = add %4, %9
329 %11 = icmp slt %10, %0
330 br_if %11, block1(%8, %10), block2
331
332block2:
333 %12 = object_size.i64 %8, kind 0
334 call @use(%12) : (i64)
335 %13 = object_size.i64 %8, kind 2
336 call @use(%13) : (i64)
337 return
338}
339";
340 assert_eq!(answers(body, true), [20, 13]);
341 }
342
343 #[test]
347 fn a_pointer_a_loop_moves_is_known_only_where_that_can_only_leave_less() {
348 let forward = "
349func @f(i32), linkage(external) {
350block0(%0: i32):
351 %1 = alloca, size 20, align 16
352 %2 = iconst.i32 0
353 jump block1(%1, %2)
354
355block1(%3: ptr, %4: i32):
356 %5 = iconst.i64 STEP
357 %6 = ptr_add %3, %5
358 %7 = iconst.i32 1
359 %8 = add %4, %7
360 %9 = icmp slt %8, %0
361 br_if %9, block1(%6, %8), block2
362
363block2:
364 %10 = object_size.i64 %6, kind 0
365 call @use(%10) : (i64)
366 %11 = object_size.i64 %6, kind 2
367 call @use(%11) : (i64)
368 return
369}
370";
371 assert_eq!(answers(&forward.replace("STEP", "1"), true), [19, 0]);
372 assert_eq!(answers(&forward.replace("STEP", "-1"), true), [-1, 0]);
373 }
374
375 #[test]
378 fn past_the_end_is_nothing_and_a_weak_global_is_not_known() {
379 let body = "
380global @g : bytes 8 = { zero 8 }, align 1, linkage(external)
381global @w : bytes 8 = { zero 8 }, align 1, linkage(weak)
382
383func @f(), linkage(external) {
384block0:
385 %0 = global_addr @g
386 %1 = iconst.i64 12
387 %2 = ptr_add %0, %1
388 %3 = object_size.i64 %2, kind 0
389 call @use(%3) : (i64)
390 %4 = global_addr @w
391 %5 = object_size.i64 %4, kind 0
392 call @use(%5) : (i64)
393 return
394}
395";
396 assert_eq!(answers(body, true), [0, -1]);
397 }
398}