Skip to main content

rucc_opt/
objsize.rs

1//! The answer to every `object_size` the front end left for the IR.
2//!
3//! Design: `spec/optimizer/20-idioms-and-libcalls.md` section 20.2, and `spec/13-gnu-compat.md`
4//! section 13.5 for the builtin itself.
5//!
6//! The checker answers `__builtin_object_size` where it can see the object by looking at the
7//! expression, and leaves [`Opcode::ObjectSize`] behind where it cannot. What is left is an address
8//! read out of a variable, and inside a function that variable is usually one of a handful of
9//! addresses a branch or a loop chose between, as in `r = l == 1 ? &a.buf1[5] : &a.buf2[4]`. The
10//! IR as the front end writes it has every one of those in front of it: the variable is a block
11//! parameter and each branch to its block passes one address, which is an `alloca` or a global with
12//! constant offsets on top. This walks that and writes the answer in as a constant.
13//!
14//! # When it runs
15//!
16//! Before every other pass and at every level, because nothing after the front end is allowed to
17//! see the instruction. The `_chk` folds in [`crate::libcall`] run next and read the answer as the
18//! constant they compare a count against, which is the order gcc's own object size pass and its
19//! `_chk` folds run in. At `-O0` nothing is walked and every question gets the answer that says
20//! nothing, which is what gcc 16.2.0 gives at that level for an address in a variable.
21//!
22//! # What the walk believes
23//!
24//! A fixed `alloca` is its size and a dynamic one of a constant count is that count. A global is
25//! its size where `extents::vouched` says the definition in the module is the one that
26//! will run. A `ptr_add` of a constant count takes it off what is left, down to nothing past the
27//! end, and one going backwards is not followed. A block parameter is every argument every branch
28//! to its block passes and a `select` is both of its arms. Anything else is not known.
29//!
30//! The kinds asking for the largest answer take the largest of a choice and the kinds asking for
31//! the smallest take the smallest, and not knowing any part of a choice is not knowing the whole
32//! of it. A loop is the one place that needs thought. A parameter reached again while its own
33//! answer is being worked out is the pointer carried round the loop unchanged or moved forward,
34//! which can only leave less, so for the largest it adds nothing to the choice. Moved backwards it
35//! could leave more, and that is why a backwards `ptr_add` is never followed. For the smallest the
36//! same holds where the pointer comes round unchanged, and one moved forward each time round may
37//! leave as little as anything, so there the smallest is not known.
38//!
39//! The closest member, which is the low bit of the kind, is not something the IR remembers. The
40//! whole object is an answer no smaller than the member for the largest, so the first kind's answer
41//! stands for the second. For the smallest it could be too big, so the fourth kind is not known
42//! here and only the checker ever answers it.
43
44use 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
51/// How far a walk goes before it gives up, which is a chain of block parameters and `ptr_add`
52/// this long.
53const DEPTH: u32 = 16;
54
55/// Answers every `object_size` in the module and says how many there were.
56///
57/// `look` is false at `-O0`, where every question is answered as not known.
58pub 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
94/// Every `object_size` in the function.
95fn 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
102/// Puts the constant in place of the question and takes the question away.
103fn 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
117/// What a walk over one function reads.
118struct Walk<'a> {
119    module: &'a Module,
120    func: &'a Func,
121    cfg: &'a Cfg,
122    pic: Pic,
123}
124
125/// How many bytes are left in front of an address: `Err` where that is not known, `Ok(None)` where
126/// the only way to the address is round a loop back to itself, and otherwise the number.
127type Left = Result<Option<u64>, ()>;
128
129impl Walk<'_> {
130    /// How many bytes there are from this address to the end of its object.
131    ///
132    /// `on` is the block parameters whose answer is being worked out, which is how a loop is seen.
133    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                    // Round a loop to where the walk started, which adds nothing to the choice for
139                    // the reason the module comment gives. The `ptr_add` below is what decides
140                    // whether the smallest can say the same.
141                    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                            // A pointer moved forward each time round a loop may end up anywhere
180                            // further along, so there is no smallest.
181                            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
213/// Two answers for one choice, as the kind asks for them to be put together.
214fn 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    /// What every fixture below starts with, which is the target the widths are of.
230    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    /// The numbers each call to `@use` in the module was given once every question is answered,
238    /// in the order the calls are written.
239    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    /// A pointer chosen by a branch between a local and a global has the larger of what the two
267    /// have left for the first kind and the smaller for the third.
268    #[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        // The second kind is the whole object's answer, which is no smaller than any member's,
305        // and the fourth is not known here at all.
306        assert_eq!(answers(body, true), [28, 28, 15, 0]);
307        // And at `-O0` none of it is looked at.
308        assert_eq!(answers(body, false), [-1, -1, 0, 0]);
309    }
310
311    /// A pointer carried round a loop and replaced on some trips is the choice of every address it
312    /// was given, and the trip that leaves it alone adds nothing to the choice.
313    #[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    /// A pointer moved forward each time round a loop has less and less left, so the largest is
344    /// where it started and there is no smallest. Moved backward there is no largest either, since
345    /// it may have more left than anything the walk saw.
346    #[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    /// An address past the end of its object has nothing left, and a global the linker may take
376    /// from somewhere else is not one whose size this module knows.
377    #[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}