Skip to main content

rucc_codegen/varargs/
aapcs.rs

1//! The AAPCS64 walk, which is the SysV one counting down rather than up.
2//!
3//! Design: `spec/12-abi-and-runtime.md`, and the procedure call standard's appendix on `va_arg`,
4//! which is where the layout and every step below come from.
5//!
6//! The callee spills the argument registers the same way a SysV one does, eight general purpose
7//! registers and eight vector ones, and the list says how far into each the walk has got. What is
8//! different is how it says it. There are two pointers to the top of the two halves of the save
9//! area and two offsets that start negative and count up to zero:
10//!
11//! ```text
12//! offset  0  __stack    the next argument that came in the caller's memory
13//! offset  8  __gr_top   the end of the general purpose half of the save area
14//! offset 16  __vr_top   the end of the vector half
15//! offset 24  __gr_offs  minus the bytes of the general purpose half not walked yet
16//! offset 28  __vr_offs  minus the bytes of the vector half not walked yet
17//! ```
18//!
19//! So an argument is in the save area when its offset is still negative after the walk steps past
20//! it, and the address it is at is the top plus the offset before the step. An offset that is zero
21//! or more to start with means that half has run out, and the argument is in the caller's memory,
22//! as is one the step takes past zero. The step is stored either way, which is what the standard
23//! does and what keeps every argument after one that did not fit in memory as well.
24//!
25//! A general purpose slot is eight bytes and a vector one is sixteen, and neither changes with what
26//! is in it: a `float` is the low four bytes of its slot and a `long double` is all sixteen.
27//!
28//! # Objects
29//!
30//! A structure of sixteen bytes or less that is not made of floats arrived in one or two general
31//! purpose registers, and since those are next to each other in the save area the object is too, so
32//! its address is an address in the area. One aligned to sixteen starts on an even register, which
33//! is the offset rounded up to sixteen before the step. An `__int128` is read as one of these.
34//!
35//! A structure made of up to four floats of one kind arrived one member to a vector register, and
36//! the members are sixteen bytes apart in the area, so it is copied out into a buffer the way a
37//! SysV object split between two files is, and the answer is the buffer.
38//!
39//! Anything larger travelled as the address of a copy the caller made, which is one general purpose
40//! slot, and the answer is what is in it.
41//!
42//! In the caller's memory, every argument takes a whole number of eight byte words and one aligned
43//! to sixteen starts on a boundary of sixteen.
44//!
45//! # Apple's platforms
46//!
47//! Apple's AArch64 puts every argument the signature does not name in the caller's memory, so its
48//! list is only the `__stack` pointer, at the same offset of zero, and its walk is the memory half
49//! of this one with no question to ask first.
50
51use rucc_ir::{
52    Block, Builder, Extra, Flags, Float, Func, Inst, IntPred, MemInfo, Opcode, Type, Value,
53};
54use rucc_target::Slot;
55
56use super::{added, buffer, info, is_float, offset, width};
57
58/// Where the pointer to the next argument in the caller's memory is.
59pub const STACK: i64 = 0;
60/// Where the pointer to the end of the general purpose half of the save area is.
61pub const GR_TOP: i64 = 8;
62/// Where the pointer to the end of the vector half is.
63pub const VR_TOP: i64 = 16;
64/// Where the offset into the general purpose half is.
65pub const GR_OFFS: i64 = 24;
66/// Where the offset into the vector half is.
67pub const VR_OFFS: i64 = 28;
68/// How many bytes the list is, which is what a `va_copy` of one moves.
69pub const SIZE: u64 = 32;
70
71/// How far apart two general purpose slots are.
72const WORD: u32 = 8;
73/// How far apart two vector slots are.
74const VECTOR: u32 = 16;
75/// The largest object that travels as itself rather than as the address of a copy.
76const IN_REGISTERS: u64 = 16;
77
78/// Where one argument is, as far as the walk is concerned.
79#[derive(Clone, Copy)]
80struct Wants {
81    /// Whether it is in the vector half.
82    float: bool,
83    /// How many slots of that half it takes.
84    slots: u32,
85    /// Whether it starts on an even general purpose register.
86    even: bool,
87    /// How many bytes it takes in the caller's memory, before rounding up to a word.
88    size: u64,
89    /// Whether it starts on a boundary of sixteen in the caller's memory.
90    wide: bool,
91}
92
93/// One `va_arg` of a scalar, as the walk and then the load the program wrote.
94pub(super) fn next(func: &mut Func, inst: Inst) {
95    let Some(result) = func[inst].first_result else { return };
96    let Some(&list) = func[func[inst].args].first() else { return };
97    let ty = func[result].ty;
98    let Some(block) = func.block_of(inst) else { return };
99    if !ty.is_scalar() || !(ty.is_int() || ty.is_float() || ty.is_ptr()) || ty.bits() > 128 {
100        return;
101    }
102    // An integer of two words is read as an object by the front end, so this one is not a thing.
103    if ty.is_int() && ty.bits() > 64 {
104        return;
105    }
106    let bytes = u64::from(ty.bits().div_ceil(8)).max(1);
107    let float = ty.is_float();
108    let wants = Wants { float, slots: 1, even: false, size: bytes, wide: bytes > 8 };
109
110    let rest = cut(func, block, inst);
111    let (join, address) = walk(func, block, inst, list, wants, |_, found| found);
112
113    let span = func.span(inst);
114    let mut build = Builder::new(func, join).at(span);
115    let from = build.unary(Opcode::IntToPtr, address, Type::PTR);
116    let align = u32::try_from(bytes.next_power_of_two()).unwrap_or(1);
117    let mem = func.add_mem(info(bytes, align));
118    let args = func.push_values(&[from]);
119    let data = &mut func[inst];
120    data.opcode = Opcode::Load;
121    data.args = args;
122    data.extra = Extra::Mem(mem);
123    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Load));
124    func.append_inst(join, inst);
125    for at in rest {
126        func.append_inst(join, at);
127    }
128}
129
130/// One `va_arg` of an aggregate, as the address it can be read from.
131pub(super) fn object(func: &mut Func, inst: Inst) {
132    let Extra::VaObject(at) = func[inst].extra else { return };
133    let object = func[at];
134    let MemInfo { size, align, .. } = func[object.mem];
135    let slots: Vec<Slot> = func[object.slots].to_vec();
136    let Some(&list) = func[func[inst].args].first() else { return };
137    let Some(block) = func.block_of(inst) else { return };
138    if func[inst].first_result.is_none() {
139        return;
140    }
141    let floats = slots.iter().filter(|&&slot| is_float(slot)).count();
142    let by_reference = slots.is_empty() && size > IN_REGISTERS;
143    let wants = if by_reference {
144        Wants { float: false, slots: 1, even: false, size: u64::from(WORD), wide: false }
145    } else if floats == 0 {
146        let slots = u32::try_from(size.div_ceil(u64::from(WORD))).unwrap_or(0);
147        if slots == 0 || size > IN_REGISTERS {
148            return;
149        }
150        Wants { float: false, slots, even: align >= 16, size, wide: align >= 16 }
151    } else if floats == slots.len() && floats <= 4 {
152        let wide = slots.iter().any(|&slot| width(slot) > u64::from(WORD));
153        let slots = u32::try_from(floats).unwrap_or(0);
154        Wants { float: true, slots, even: false, size, wide }
155    } else {
156        return;
157    };
158    // A buffer for the members of a structure of floats, made before anything else because an
159    // alloca of a fixed size belongs in the entry block and the walk is built where the instruction
160    // is. As big as the members reach, which is the object.
161    let room = if wants.float && wants.slots > 1 {
162        let Some(room) = buffer(func, inst, size, align.max(WORD)) else { return };
163        Some(room)
164    } else {
165        None
166    };
167
168    let rest = cut(func, block, inst);
169    let copy = |build: &mut Builder<'_>, found: Value| match room {
170        Some(room) => copied(build, found, &slots, room, align.max(WORD)),
171        None => found,
172    };
173    let (join, mut address) = walk(func, block, inst, list, wants, copy);
174    if by_reference {
175        let span = func.span(inst);
176        let mut build = Builder::new(func, join).at(span);
177        let slot = build.unary(Opcode::IntToPtr, address, Type::PTR);
178        let held = build.load(Type::PTR, slot, info(8, 8), Flags::default());
179        address = build.unary(Opcode::PtrToInt, held, Type::int(64));
180    }
181
182    let args = func.push_values(&[address]);
183    let data = &mut func[inst];
184    data.opcode = Opcode::IntToPtr;
185    data.args = args;
186    data.extra = Extra::None;
187    data.flags = data.flags.intersection(Flags::legal_on(Opcode::IntToPtr));
188    func.append_inst(join, inst);
189    for at in rest {
190        func.append_inst(join, at);
191    }
192}
193
194/// One `va_arg` of an aggregate on Apple's platforms, as the address it can be read from.
195///
196/// An object of sixteen bytes or less, or a structure of up to four floats of one kind, is in the
197/// caller's memory as itself, and anything larger is the address of a copy the caller made. That
198/// is the same split [`object`] makes, and the memory it reads is the same memory its last step
199/// reads.
200pub(super) fn stacked(func: &mut Func, inst: Inst) {
201    let Extra::VaObject(at) = func[inst].extra else { return };
202    let object = func[at];
203    let MemInfo { size, align, .. } = func[object.mem];
204    let by_reference = func[object.slots].is_empty() && size > IN_REGISTERS;
205    let Some(&list) = func[func[inst].args].first() else { return };
206    let Some(block) = func.block_of(inst) else { return };
207    if func[inst].first_result.is_none() {
208        return;
209    }
210    let wants = if by_reference {
211        Wants { float: false, slots: 1, even: false, size: u64::from(WORD), wide: false }
212    } else {
213        Wants { float: false, slots: 1, even: false, size, wide: align >= 16 }
214    };
215
216    let rest = cut(func, block, inst);
217    let span = func.span(inst);
218    let mut build = Builder::new(func, block).at(span);
219    let mut address = in_memory(&mut build, list, wants);
220    if by_reference {
221        let slot = build.unary(Opcode::IntToPtr, address, Type::PTR);
222        let held = build.load(Type::PTR, slot, info(8, 8), Flags::default());
223        address = build.unary(Opcode::PtrToInt, held, Type::int(64));
224    }
225
226    let args = func.push_values(&[address]);
227    let data = &mut func[inst];
228    data.opcode = Opcode::IntToPtr;
229    data.args = args;
230    data.extra = Extra::None;
231    data.flags = data.flags.intersection(Flags::legal_on(Opcode::IntToPtr));
232    func.append_inst(block, inst);
233    for at in rest {
234        func.append_inst(block, at);
235    }
236}
237
238/// Takes the instruction and everything below it out of its block, and gives back what was below
239/// it, because a builder appends to a block and this one has to end at the first branch.
240fn cut(func: &mut Func, block: Block, inst: Inst) -> Vec<Inst> {
241    let rest: Vec<Inst> = func.insts(block).skip_while(|&at| at != inst).skip(1).collect();
242    func.remove_inst(inst);
243    for &at in &rest {
244        func.remove_inst(at);
245    }
246    rest
247}
248
249/// The two questions and the two places, ending in a block that has the address of the argument as
250/// its parameter, an integer.
251///
252/// `found` is handed the address in the save area on the way out of the register path and gives
253/// back the address the path answers with, which is the same address for everything but a
254/// structure of floats, whose members are copied out of the area into a buffer first.
255fn walk(
256    func: &mut Func,
257    block: Block,
258    inst: Inst,
259    list: Value,
260    wants: Wants,
261    found: impl FnOnce(&mut Builder<'_>, Value) -> Value,
262) -> (Block, Value) {
263    let span = func.span(inst);
264    let wide = Type::int(64);
265    let word = Type::int(32);
266    let (field, top, stride) =
267        if wants.float { (VR_OFFS, VR_TOP, VECTOR) } else { (GR_OFFS, GR_TOP, WORD) };
268    let fits = func.create_block();
269    let saved = func.create_block();
270    let stack = func.create_block();
271    let join = func.create_block();
272    let address = func.append_param(join, wide);
273
274    // Whether the half had run out before this argument.
275    let mut build = Builder::new(func, block).at(span);
276    let counter = offset(&mut build, list, field);
277    let mut walked = build.load(word, counter, info(4, 4), Flags::default());
278    let zero = build.iconst(word, 0);
279    let out = build.icmp(IntPred::Sge, walked, zero);
280    build.br_if(out, stack, &[], fits, &[]);
281
282    // Whether this argument takes it past the end. The step is stored whichever way it goes.
283    let mut build = Builder::new(func, fits).at(span);
284    if wants.even {
285        let bump = build.iconst(word, 15);
286        walked = build.binary(Opcode::Add, walked, bump, Flags::default());
287        let mask = build.iconst(word, -16);
288        walked = build.binary(Opcode::And, walked, mask, Flags::default());
289    }
290    let by = build.iconst(word, i128::from(stride * wants.slots));
291    let stepped = build.binary(Opcode::Add, walked, by, Flags::default());
292    let counter = offset(&mut build, list, field);
293    build.store(stepped, counter, info(4, 4), Flags::default());
294    let zero = build.iconst(word, 0);
295    let past = build.icmp(IntPred::Sgt, stepped, zero);
296    build.br_if(past, stack, &[], saved, &[]);
297
298    // In the save area, at the top of the half less what the offset said was left.
299    let mut build = Builder::new(func, saved).at(span);
300    let at = offset(&mut build, list, top);
301    let top = build.load(Type::PTR, at, info(8, 8), Flags::default());
302    let back = build.unary(Opcode::SExt, walked, wide);
303    let here = added(&mut build, top, back);
304    let here = build.unary(Opcode::PtrToInt, here, wide);
305    let here = found(&mut build, here);
306    build.jump(join, &[here]);
307
308    let mut build = Builder::new(func, stack).at(span);
309    let there = in_memory(&mut build, list, wants);
310    build.jump(join, &[there]);
311
312    (join, address)
313}
314
315/// Where the argument is in the caller's memory, as an integer, with `__stack` stepped on past it.
316///
317/// The pointer is rounded up for an argument that wants sixteen and then stepped on by whole words.
318fn in_memory(build: &mut Builder<'_>, list: Value, wants: Wants) -> Value {
319    let wide = Type::int(64);
320    let pointer = offset(build, list, STACK);
321    let there = build.load(Type::PTR, pointer, info(8, 8), Flags::default());
322    let mut there = build.unary(Opcode::PtrToInt, there, wide);
323    if wants.wide {
324        let bump = build.iconst(wide, 15);
325        there = build.binary(Opcode::Add, there, bump, Flags::default());
326        let mask = build.iconst(wide, -16);
327        there = build.binary(Opcode::And, there, mask, Flags::default());
328    }
329    let by = build.iconst(wide, i128::from(wants.size.next_multiple_of(u64::from(WORD))));
330    let onward = build.binary(Opcode::Add, there, by, Flags::default());
331    let onward = build.unary(Opcode::IntToPtr, onward, Type::PTR);
332    build.store(onward, pointer, info(8, 8), Flags::default());
333    there
334}
335
336/// The members of a structure of floats copied out of the vector half into a buffer, as the
337/// address of the buffer.
338fn copied(build: &mut Builder<'_>, from: Value, slots: &[Slot], room: Value, align: u32) -> Value {
339    let from = build.unary(Opcode::IntToPtr, from, Type::PTR);
340    for (index, &slot) in slots.iter().enumerate() {
341        let bytes = width(slot);
342        let ty = if bytes > u64::from(WORD) {
343            Type::float(Float::F128)
344        } else {
345            Type::int(u32::try_from(bytes).unwrap_or(1) * 8)
346        };
347        let step = i64::from(VECTOR) * i64::try_from(index).unwrap_or(0);
348        let at = offset(build, from, step);
349        let aligned = u32::try_from(bytes).unwrap_or(1);
350        let value = build.load(ty, at, info(bytes, aligned), Flags::default());
351        let into = offset(build, room, i64::try_from(slot.offset()).unwrap_or(0));
352        let holds = info(bytes, super::part(align, slot.offset()));
353        build.store(value, into, holds, Flags::default());
354    }
355    build.unary(Opcode::PtrToInt, room, Type::int(64))
356}