Skip to main content

rucc_codegen/
varargs.rs

1//! What a function does to read the arguments its own signature does not name.
2//!
3//! Design: `spec/12-abi-and-runtime.md`, which is where the layout below comes from.
4//!
5//! A variadic callee has a problem an ordinary one does not. Six of its arguments arrived in
6//! general purpose registers and eight more in vector ones, and it cannot know which of those hold
7//! anything, because what it was passed is a thing only the caller knew. Registers are also not
8//! addressable, and `va_arg` walks arguments one after another at run time, which is walking
9//! addresses. So the convention says the callee spills all fourteen of them into a block of its own
10//! frame on the way in, and from then on every argument it was passed is somewhere in memory: the
11//! ones that came in registers are in that block, and the ones that did not are in the caller's
12//! argument area where they were left.
13//!
14//! That block is the register save area, and a `va_list` is four fields saying how far into the
15//! arguments the walk has got:
16//!
17//! ```text
18//! offset  0  gp_offset          bytes into the save area of the next argument from a gpr
19//! offset  4  fp_offset          bytes into the save area of the next argument from an xmm
20//! offset  8  overflow_arg_area  the next argument that came in the caller's memory
21//! offset 16  reg_save_area      the bottom of the save area
22//! ```
23//!
24//! `va_start` fills all four in. The two offsets do not start at zero: the arguments the signature
25//! does name took registers too, and they took the first ones, so each offset starts past them.
26//! `va_arg` is then one question asked at run time, which is whether the offset for its file has run
27//! off the end of the save area. If it has not, the argument is in the save area and the offset
28//! steps on by a slot. If it has, the argument is in the caller's memory and the overflow pointer
29//! steps on by a word instead.
30//!
31//! # Why the layout is exactly the psABI's and not a convenient one
32//!
33//! Nothing outside the function can see the save area, so its shape looks like a private decision.
34//! It is not one, because a `va_list` is a thing a program hands to another function, and the
35//! function it usually hands it to is `vfprintf` in the C library, which somebody else compiled and
36//! which walks the list by the rules in the psABI document. So the offsets are the document's
37//! offsets, the area is the document's one hundred and seventy six bytes, and the eight bytes
38//! between two general purpose slots and the sixteen between two vector ones are the document's too.
39//!
40//! The upper half of a vector slot is the document's too, and what is in it is the top of a
41//! `_Float128`. A slot is sixteen bytes wide because the register is, and a quad is the one type
42//! here that fills one, so the spill writes all sixteen bytes of every vector register and a
43//! `va_arg` of a quad reads all sixteen back. gcc writes the same sixteen with the same instruction,
44//! which is what makes a list built here readable by a walk somebody else compiled. Anything wider
45//! than a register would be a vector type, which is issue #200 and is not a thing yet.
46//!
47//! # What is here and what is next door
48//!
49//! `va_arg` becomes a compare and a branch, and this is where, because a rewrite that needs new
50//! blocks has to happen before selection for the reason [`crate::expand`] gives. Everything it needs
51//! is in the list it was handed, so it needs nothing from the frame and can run here.
52//!
53//! An aggregate read off a list is the same instruction under another name, because an aggregate is
54//! not a value and there is nothing for one result to be, so that one answers where the object is
55//! instead. Over two eightbytes it is class MEMORY whatever its members are, which means it is in
56//! the caller's argument area and there is no question to ask about which half of the walk it is
57//! in: the overflow pointer says where it is and steps on past it. Sixteen bytes and under arrived
58//! in registers, and then the question is the one a scalar asks, with two differences. The object
59//! takes a register of each file for each of its eightbytes, so the room in the save area has to be
60//! there for all of them at once and the offsets step on by all of them at once. And the halves of
61//! it in the save area are not next to each other, so the answer cannot be an address in the area:
62//! the eightbytes are copied out into a buffer of the function's own and the answer is that.
63//!
64//! Which file each eightbyte came from is the classification, which is an answer about a C type and
65//! not one the size and the alignment give. It arrives on the instruction, worked out by the front
66//! end, which is the last thing to hold a type. An object with no slots on it is one the
67//! classification sent to the argument area, and that is what tells the two halves below apart.
68//!
69//! `va_start` is the other way round. Three of the four fields it writes are distances into a frame
70//! that does not exist yet, so it stays an instruction as far as [`crate::lower`], which builds it
71//! out of the frame the way it builds an `alloca`. The spill that fills the save area is written
72//! there for the same reason.
73//!
74//! # The other kind of list
75//!
76//! Windows has none of that. Its convention counts the two register files as one run of positions,
77//! so an argument's position says which register of either file it is in and the two walks above
78//! are one walk. It also gives every argument exactly one eight byte slot whatever it is: anything
79//! that is not one, two, four or eight bytes travels as the address of a copy the caller owns, and
80//! a float beyond the ones the signature names travels in the general purpose register at its
81//! position as well as in the vector one, because a callee with no prototype has no way to know
82//! which file to look in.
83//!
84//! What that comes to is that every argument a variadic callee was passed is already one contiguous
85//! run of words in the caller's argument area, since the first four of them are homed in the thirty
86//! two bytes of shadow space the caller reserved above the return address and the rest follow.
87//! There is nothing to gather and nowhere to gather it to. So a `va_list` is a `char *` pointing at
88//! the next of those words, `va_start` is one `lea` and one store, and `va_arg` is a load and an
89//! eight byte step with no compare, no branch and no second file. The register save area of the
90//! four field list is, on this convention, the caller's shadow space, and the callee's prologue
91//! writes its leftover argument registers into it rather than into a block of its own.
92//!
93//! An argument that travelled by reference costs one more load and that is the whole of the
94//! difference: the slot holds the address of the copy rather than the copy. Which arguments those
95//! are is a question about the size and nothing else, so the classification the front end put on a
96//! `va_object` is not read here at all.
97
98pub mod aapcs;
99
100use rucc_base::float::Format;
101use rucc_ir::{
102    Block, Builder, Extra, Flags, Float, Func, Imm, Inst, InstData, IntPred, MemInfo, MemOrder,
103    Opcode, Restrict, Type, Value,
104};
105use rucc_target::{CallRegs, Slot, VaList, Variadic};
106
107/// Where the count of general purpose register bytes already walked is.
108pub const GP_OFFSET: i64 = 0;
109/// Where the count of vector register bytes already walked is.
110pub const FP_OFFSET: i64 = 4;
111/// Where the pointer to the next argument in the caller's memory is.
112pub const OVERFLOW: i64 = 8;
113/// Where the pointer to the bottom of the register save area is.
114pub const SAVE_AREA: i64 = 16;
115/// How many bytes the four field `va_list` is, which is what a `va_copy` of one moves.
116pub const SIZE: u64 = 24;
117/// How wide the slot one vector register is saved in is, which is how wide the register is whatever
118/// this actually writes into it.
119pub const VECTOR_SLOT: u32 = 16;
120
121/// How big a callee's register save area is and where its two halves are.
122///
123/// Worked out from the convention rather than written down, so that a convention with a different
124/// number of argument registers gets an area the right size for it without anything here changing.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126pub struct Area {
127    /// How many bytes of it the general purpose registers take, which is also where the vector half
128    /// begins, since the general purpose half is first and starts at nothing.
129    pub floats_at: u32,
130    /// How many bytes the whole of it is.
131    pub size: u32,
132    /// How many registers of each file it holds, general purpose first.
133    counts: (u32, u32),
134    /// How far apart two general purpose slots are, which is a word.
135    word: u32,
136}
137
138impl Area {
139    /// The save area a variadic callee under that convention needs.
140    ///
141    /// Two shapes, and what tells them apart is the convention's own answer about how it counts
142    /// argument positions. One that counts the two files apart spills all of both into a block of
143    /// the callee's own frame, which is the psABI's register save area and is what the four field
144    /// list walks. One that counts them as one run homes each register argument in the word of the
145    /// caller's argument area that belongs to its position, and that run of words is the area. The
146    /// vector file has nothing in it there: a float beyond the ones the signature names travels in
147    /// the general purpose register at its position as well, so the copy a walk reads is that one.
148    #[must_use]
149    pub fn of(conv: &CallRegs) -> Self {
150        let ints = u32::try_from(conv.int_args.len()).unwrap_or(0);
151        let floats = u32::try_from(conv.sse_args.len()).unwrap_or(0);
152        if conv.shared_positions {
153            let size = conv.word * ints;
154            return Self { floats_at: size, size, counts: (ints, 0), word: conv.word };
155        }
156        let floats_at = conv.word * ints;
157        Self {
158            floats_at,
159            size: floats_at + VECTOR_SLOT * floats,
160            counts: (ints, floats),
161            word: conv.word,
162        }
163    }
164
165    /// How far apart two of a file's slots are.
166    #[must_use]
167    pub fn stride(self, float: bool) -> u32 {
168        if float { VECTOR_SLOT } else { self.word }
169    }
170
171    /// Where a file's first slot is, which is what `va_start` writes into that file's field when
172    /// the signature named no argument that file carried.
173    #[must_use]
174    pub fn starts_at(self, float: bool) -> u32 {
175        if float { self.floats_at } else { 0 }
176    }
177
178    /// Where a file's slots end, which is where the vector half begins for the general purpose
179    /// file and the end of the whole area for the vector one.
180    ///
181    /// This is what an object taking more than one register of a file is measured against: the
182    /// psABI asks whether the offset is at or below the end less a slot for each register the
183    /// object wants, and one register of it is the same question [`Area::last`] asks.
184    #[must_use]
185    pub fn ends_at(self, float: bool) -> u32 {
186        if float { self.size } else { self.floats_at }
187    }
188
189    /// How many registers of a file the area holds.
190    #[must_use]
191    pub fn holds(self, float: bool) -> u32 {
192        if float { self.counts.1 } else { self.counts.0 }
193    }
194
195    /// The offset of a file's last slot, which is the threshold `va_arg` compares against.
196    ///
197    /// The last slot's own offset and not the end of the area, because an offset equal to the end
198    /// is one slot past the last argument while an offset a slot below the end is the last argument
199    /// itself. An empty file has no such offset and nothing here has one.
200    #[must_use]
201    pub fn last(self, float: bool) -> Option<u32> {
202        let last = self.holds(float).checked_sub(1)?;
203        Some(self.starts_at(float) + self.stride(float) * last)
204    }
205}
206
207/// Rewrites every `va_arg`, `va_copy` and `va_end` in the function, and leaves `va_start` alone.
208///
209/// Those three are the ones made only of reads and writes of a list some pointer already reaches,
210/// so none of them needs to know anything about the frame and all three can be done here.
211/// `va_start` is the one that does need the frame, and [`crate::lower`] has it.
212///
213/// A convention whose list is a plain pointer gets the walk the module doc's last section
214/// describes instead, which is the same three rewrites over a list of one field, and AAPCS64 gets
215/// the walk [`aapcs`] describes. Apple's AArch64 has the plain pointer, and its scalars take the
216/// same walk, but an object there is as many words as it needs rather than one, which is the
217/// memory half of the AAPCS64 walk and is `aapcs::stacked`. Windows on AArch64 walks objects the
218/// same way, since its homed x registers and the caller's memory above them are one run of words
219/// and an object of up to sixteen bytes takes as many of them as it needs.
220pub fn lists(func: &mut Func, conv: &CallRegs) {
221    let area = Area::of(conv);
222    let word = u64::from(conv.word);
223    let found: Vec<Inst> =
224        func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
225    for inst in found {
226        match (func[inst].opcode, conv.list) {
227            (Opcode::VaArg, VaList::SysV) => next(func, inst, area),
228            (Opcode::VaArg, VaList::Aapcs) => aapcs::next(func, inst),
229            (Opcode::VaArg, VaList::CharPointer | VaList::VoidPointer) => value(func, inst, word),
230            (Opcode::VaObject, VaList::SysV) => object(func, inst, area),
231            (Opcode::VaObject, VaList::Aapcs) => aapcs::object(func, inst),
232            (Opcode::VaObject, VaList::CharPointer)
233                if matches!(conv.abi.variadic, Variadic::AlwaysMemory | Variadic::IntegersOnly) =>
234            {
235                aapcs::stacked(func, inst);
236            }
237            (Opcode::VaObject, VaList::CharPointer | VaList::VoidPointer) => {
238                held(func, inst, word);
239            }
240            (Opcode::VaCopy, VaList::SysV) => copy(func, inst, SIZE),
241            (Opcode::VaCopy, VaList::Aapcs) => copy(func, inst, aapcs::SIZE),
242            (Opcode::VaCopy, VaList::CharPointer | VaList::VoidPointer) => copy(func, inst, word),
243            // Nothing at all, which is what the psABI says it is. The instruction was still worth
244            // emitting, because it says the list stops being read here, and here is where that
245            // stops being worth saying.
246            (Opcode::VaEnd, _) => func.remove_inst(inst),
247            _ => {}
248        }
249    }
250}
251
252/// One `va_arg`, as the branch on whether the argument it wants is still in the save area.
253///
254/// The block the instruction was in is cut in two at the instruction. What was above it stays where
255/// it is and gets the compare and the branch, what was below it moves into a new block that takes
256/// the address as a parameter, and the `va_arg` itself becomes the load at the top of that block.
257/// Turning it into the load rather than replacing it keeps the value the rest of the function reads
258/// the value it already read, so nothing has to be substituted anywhere, and the two paths meet at a
259/// block parameter because the IR has no variables for them to meet at.
260fn next(func: &mut Func, inst: Inst, area: Area) {
261    let Some(result) = func[inst].first_result else { return };
262    let Some(&list) = func[func[inst].args].first() else { return };
263    let ty = func[result].ty;
264    let Some(block) = func.block_of(inst) else { return };
265    let span = func.span(inst);
266    // A `long double` is class X87, which is a class with no register in the save area, so it is
267    // always in the caller's argument area and there is no question to ask about it. That is this
268    // walk with the register half deleted, which is little enough to be written out separately
269    // rather than folded in as a special case of a branch that is never taken.
270    if ty.is_float() && ty.bits() == 80 {
271        x87(func, inst, area);
272        return;
273    }
274    // A `_Float128` is the one value wider than a general purpose register that a single register
275    // still holds. It is class SSE followed by SSEUP, which name one vector register between them,
276    // so it walks the vector half the way a `double` does and takes the whole of a slot instead of
277    // the low half of one. Where it stops being a wider `double` is the caller's argument area,
278    // which gives it two words aligned to two rather than the one word every value the machine
279    // computes in gets.
280    let quad = ty.is_float() && ty.bits() == 128;
281    // A scalar of a width a register holds, which is every type the algorithm below is right about.
282    // An `__int128` takes two slots with an alignment rule of its own, which is the walk `object`
283    // makes for a small structure, so `rucc-lower` reads one as that and it never arrives here.
284    // Anything else wider is left alone and refused by name further down.
285    if !quad
286        && (!ty.is_scalar() || ty.bits() > 64 || !(ty.is_int() || ty.is_float() || ty.is_ptr()))
287    {
288        return;
289    }
290    let float = ty.is_float();
291    let Some(last) = area.last(float) else { return };
292    let field = if float { FP_OFFSET } else { GP_OFFSET };
293
294    // Everything below the instruction, taken out before anything is built, because the builder
295    // appends to a block and this block has to end at the branch.
296    let rest: Vec<Inst> = func.insts(block).skip_while(|&at| at != inst).skip(1).collect();
297    let taken = func.create_block();
298    let overflowed = func.create_block();
299    let join = func.create_block();
300    let addr = func.append_param(join, Type::PTR);
301    func.remove_inst(inst);
302    for &at in &rest {
303        func.remove_inst(at);
304    }
305
306    // The question, in the block the `va_arg` used to be in. Unsigned, because an offset into the
307    // save area counts bytes and is never negative, and because what the field holds once the
308    // register arguments have all been walked is a number past the end rather than a small one.
309    let mut build = Builder::new(func, block).at(span);
310    let counter = offset(&mut build, list, field);
311    let walked = build.load(Type::int(32), counter, info(4, 4), Flags::default());
312    let end = build.iconst(Type::int(32), i128::from(last));
313    let inside = build.icmp(IntPred::Ule, walked, end);
314    build.br_if(inside, taken, &[], overflowed, &[]);
315
316    // The register path: the argument is in the save area at the offset the field holds, and the
317    // field steps on by one slot of its file.
318    let mut build = Builder::new(func, taken).at(span);
319    let base = offset(&mut build, list, SAVE_AREA);
320    let save = build.load(Type::PTR, base, info(8, 8), Flags::default());
321    let wide = build.unary(Opcode::ZExt, walked, Type::int(64));
322    let found = added(&mut build, save, wide);
323    let stride = build.iconst(Type::int(32), i128::from(area.stride(float)));
324    let stepped = build.binary(Opcode::Add, walked, stride, Flags::default());
325    let counter = offset(&mut build, list, field);
326    build.store(stepped, counter, info(4, 4), Flags::default());
327    build.jump(join, &[found]);
328
329    // The memory path: the argument is where the caller left it, and the pointer steps on past it.
330    // By a word for everything the machine computes in, because the caller's argument area is a run
331    // of whole words whatever is in them, and by two words rounded up to two for a quad, which is
332    // the slot the class gets from a function that names it as well as from one that does not.
333    let mut build = Builder::new(func, overflowed).at(span);
334    let (slot, want) = if quad {
335        (u64::from(VECTOR_SLOT), VECTOR_SLOT)
336    } else {
337        (u64::from(area.word), area.word)
338    };
339    let at = overflow(&mut build, list, area, slot, want);
340    let here = build.unary(Opcode::IntToPtr, at, Type::PTR);
341    build.jump(join, &[here]);
342
343    // And the load the program actually wrote, over the address the two paths agreed on, with
344    // everything that used to follow it behind it in the order it was written.
345    let bytes = ty.bits() / 8;
346    let mem = func.add_mem(info(u64::from(bytes), bytes));
347    let args = func.push_values(&[addr]);
348    let data = &mut func[inst];
349    data.opcode = Opcode::Load;
350    data.args = args;
351    data.extra = Extra::Mem(mem);
352    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Load));
353    func.append_inst(join, inst);
354    for at in rest {
355        func.append_inst(join, at);
356    }
357}
358
359/// One `va_arg` of a `long double`, which is the memory half of the walk and nothing else.
360///
361/// The psABI gives a `long double` class X87 and there is no x87 register among the ones a variadic
362/// callee spills, so a `long double` passed to one is in the caller's argument area whatever else
363/// the call passed and however few arguments came before it. Nothing is asked, no block is made,
364/// and the overflow pointer is rounded up, read and stepped on.
365///
366/// Sixteen bytes and sixteen byte alignment are the psABI's numbers for the class rather than the
367/// type's own: the value is ten bytes of x87 and the argument slot it sits in is padded out to two
368/// words, which is why the load below is ten bytes wide and the step is sixteen.
369fn x87(func: &mut Func, inst: Inst, area: Area) {
370    /// What one of these takes in the argument area.
371    const SLOT: u64 = 16;
372    /// What the argument area aligns one to.
373    const ALIGN: u32 = 16;
374
375    let Some(result) = func[inst].first_result else { return };
376    let Some(&list) = func[func[inst].args].first() else { return };
377    let Some(block) = func.block_of(inst) else { return };
378    let bytes = func[result].ty.bits() / 8;
379    let span = func.span(inst);
380
381    // Everything below the instruction, taken out before anything is built, for the reason the
382    // branching walk takes it out: a builder appends to a block, and the instruction has to end up
383    // behind what is built and in front of what followed it.
384    let rest: Vec<Inst> = func.insts(block).skip_while(|&at| at != inst).skip(1).collect();
385    func.remove_inst(inst);
386    for &at in &rest {
387        func.remove_inst(at);
388    }
389
390    let mut build = Builder::new(func, block).at(span);
391    let at = overflow(&mut build, list, area, SLOT, ALIGN);
392    let addr = build.unary(Opcode::IntToPtr, at, Type::PTR);
393
394    let mem = func.add_mem(info(u64::from(bytes), ALIGN));
395    let args = func.push_values(&[addr]);
396    let data = &mut func[inst];
397    data.opcode = Opcode::Load;
398    data.args = args;
399    data.extra = Extra::Mem(mem);
400    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Load));
401    func.append_inst(block, inst);
402    for at in rest {
403        func.append_inst(block, at);
404    }
405}
406
407/// One `va_object`, as the address the object can be read from.
408///
409/// Two shapes, and the slots on the instruction are what say which. An object with none is one the
410/// classification sent to the caller's argument area, which is what everything over two eightbytes
411/// is whatever its members are. There is no question to ask about that one: the overflow pointer
412/// says where it is and steps on past it, and no block is needed.
413///
414/// An object with slots arrived in registers, and that is the branch [`next`] builds for a scalar
415/// with the object's own two differences: the room has to be there for every one of its slots at
416/// once, and what is answered is a buffer the slots were copied into rather than an address in the
417/// save area, because two eightbytes of one object are not next to each other in there.
418///
419/// The address is answered rather than a copy of the object, which is what the instruction is for
420/// and what gcc does with the same argument. An object in the caller's memory is already somewhere
421/// addressable, and the copy the C standard describes is the assignment the caller of `va_arg`
422/// wrote, which the front end has already built around this.
423fn object(func: &mut Func, inst: Inst, area: Area) {
424    let Extra::VaObject(at) = func[inst].extra else { return };
425    let object = func[at];
426    let MemInfo { size, align, .. } = func[object.mem];
427    let slots: Vec<Slot> = func[object.slots].to_vec();
428    let Some(&list) = func[func[inst].args].first() else { return };
429    let Some(block) = func.block_of(inst) else { return };
430    if func[inst].first_result.is_none() || !fits(&slots, area) {
431        return;
432    }
433    let span = func.span(inst);
434
435    // The buffer the register form copies into, made before anything else, because an alloca of
436    // a fixed size belongs in the entry block and the walk below is built where the instruction
437    // is. It is as big as the slots reach rather than as big as the object, which is more for an
438    // object whose last eightbyte is a part of one: five bytes travel in a whole register and
439    // come out of the area as a whole register, so the buffer has eight bytes for them to land
440    // in and the three past the object are never read.
441    // It is also aligned to whatever the widest slot has to be stored at rather than to whatever
442    // the object asked for, which is the same number for every object a C program can write and is
443    // not the same statement. A slot holding a whole vector register moves as a `movaps`, and a
444    // `movaps` faults on an address that is not a multiple of sixteen, so the buffer says sixteen
445    // because the copy needs it and not because the type happened to ask.
446    let reach = slots.iter().map(|&slot| slot.offset() + width(slot)).max().unwrap_or(0);
447    let wants = slots
448        .iter()
449        .map(|&slot| slot_align(area, is_float(slot), width(slot)))
450        .max()
451        .unwrap_or(1)
452        .max(align);
453    let room = buffer(func, inst, reach.max(size), wants);
454
455    // Everything below the instruction, taken out before anything is built, because a builder
456    // appends to a block and the register form ends this one at a branch.
457    let rest: Vec<Inst> = func.insts(block).skip_while(|&at| at != inst).skip(1).collect();
458    func.remove_inst(inst);
459    for &at in &rest {
460        func.remove_inst(at);
461    }
462
463    let (ends, address) = match room {
464        Some(room) if !slots.is_empty() => {
465            let read = Read { list, area, slots: &slots, size, align, room, wants };
466            registers(func, block, inst, read)
467        }
468        _ => {
469            let mut build = Builder::new(func, block).at(span);
470            (block, overflow(&mut build, list, area, size, align))
471        }
472    };
473
474    // And the instruction itself is that address, so that everything reading it goes on reading
475    // the value it already read and nothing has to be substituted anywhere.
476    let args = func.push_values(&[address]);
477    let data = &mut func[inst];
478    data.opcode = Opcode::IntToPtr;
479    data.args = args;
480    data.extra = Extra::None;
481    data.flags = data.flags.intersection(Flags::legal_on(Opcode::IntToPtr));
482    func.append_inst(ends, inst);
483    for at in rest {
484        func.append_inst(ends, at);
485    }
486}
487
488/// One object read off one list, which is what both halves of the walk are about.
489#[derive(Clone, Copy)]
490struct Read<'a> {
491    /// The list it is read from.
492    list: Value,
493    /// The save area of the function doing the reading.
494    area: Area,
495    /// Which register each of the object's eightbytes arrived in, and empty for an object that
496    /// arrived in the caller's memory.
497    slots: &'a [Slot],
498    /// How many bytes the object is.
499    size: u64,
500    /// What it is aligned to, which is what the caller's argument area put it at.
501    align: u32,
502    /// The buffer of the function's own the register form copies the object into.
503    room: Value,
504    /// What that buffer is aligned to, which is the object's alignment or what the widest slot
505    /// needs, whichever is the larger.
506    wants: u32,
507}
508
509/// Whether the classification is one this knows how to read out of the save area.
510///
511/// A slot wider than a register or more of them than the area holds is a classification from some
512/// other machine or from a rule this has not been taught. Turning it down here leaves the
513/// instruction alone, and an instruction left alone is refused by name further down, which is a
514/// message about `va_arg` rather than whatever a half built walk would do at run time.
515fn fits(slots: &[Slot], area: Area) -> bool {
516    let mut counts = [0, 0];
517    for &slot in slots {
518        let float = is_float(slot);
519        if !in_a_register(slot) || width(slot) > u64::from(area.stride(float)) {
520            return false;
521        }
522        counts[usize::from(float)] += 1;
523    }
524    counts[0] <= area.holds(false) && counts[1] <= area.holds(true)
525}
526
527/// Whether a slot is one of the registers a variadic callee spills.
528///
529/// The width does not say on its own. A `long double` is ten bytes and would sit inside a vector
530/// slot with room to spare, and it is class X87, which has no register among the fourteen, so a
531/// classification carrying one is a classification this walk cannot read. The formats a vector
532/// register does hold are named rather than the ones it does not, so a format added later is one
533/// this leaves alone until somebody says where it travels.
534fn in_a_register(slot: Slot) -> bool {
535    match slot {
536        Slot::Integer { .. } => true,
537        Slot::Float { format, .. } => matches!(
538            format,
539            Format::Half | Format::BFloat16 | Format::Single | Format::Double | Format::Quad
540        ),
541    }
542}
543
544/// The register form: the room in the save area is asked about once per file, and the object is
545/// copied out of the area into a buffer when it is there and read from the caller's memory when it
546/// is not.
547///
548/// The question is asked once per file the object takes a register of, and both have to say yes,
549/// because the psABI puts the whole object in the caller's memory when there is not room in the
550/// area for all of it. A file the object takes nothing of has room by definition and is not asked
551/// about, which is every object of one class and is most of them.
552///
553/// Gives back the block the walk ends in and the address, as an integer, that the two paths agreed
554/// on.
555fn registers(func: &mut Func, block: Block, inst: Inst, read: Read<'_>) -> (Block, Value) {
556    let span = func.span(inst);
557    let area = read.area;
558    let counts = [taken_of(read.slots, false), taken_of(read.slots, true)];
559    let saved = func.create_block();
560    let overflowed = func.create_block();
561    let join = func.create_block();
562    let address = func.append_param(join, Type::int(64));
563
564    // The questions, each in its own block, because two of them are two branches and the second
565    // is only asked when the first said yes.
566    let asked: Vec<bool> =
567        [false, true].into_iter().filter(|&float| counts[usize::from(float)] > 0).collect();
568    let mut at = block;
569    for (index, &float) in asked.iter().enumerate() {
570        let next = if index + 1 == asked.len() { saved } else { func.create_block() };
571        // The psABI's own threshold: the end of the file's half of the area, less a slot for each
572        // register the object wants, so that an offset at it leaves room for all of them.
573        let room =
574            area.ends_at(float).saturating_sub(area.stride(float) * counts[usize::from(float)]);
575        let mut build = Builder::new(func, at).at(span);
576        let counter = offset(&mut build, read.list, field_of(float));
577        let walked = build.load(Type::int(32), counter, info(4, 4), Flags::default());
578        let end = build.iconst(Type::int(32), i128::from(room));
579        let inside = build.icmp(IntPred::Ule, walked, end);
580        build.br_if(inside, next, &[], overflowed, &[]);
581        at = next;
582    }
583
584    let mut build = Builder::new(func, saved).at(span);
585    let found = copied(&mut build, read, counts);
586    build.jump(join, &[found]);
587
588    let mut build = Builder::new(func, overflowed).at(span);
589    let here = overflow(&mut build, read.list, area, read.size, read.align);
590    build.jump(join, &[here]);
591
592    (join, address)
593}
594
595/// The object copied out of the save area into the buffer, as the address of the buffer.
596///
597/// A buffer and not an address in the area because the eightbytes of one object are not next to
598/// each other in there: two integer eightbytes are eight bytes apart and two vector ones are
599/// sixteen, and an object of one of each has them in different halves of the area entirely. So
600/// there is nowhere in the area the object is, and the one place it can be made to be is somewhere
601/// else.
602fn copied(build: &mut Builder<'_>, read: Read<'_>, counts: [u32; 2]) -> Value {
603    let area = read.area;
604    let base = offset(build, read.list, SAVE_AREA);
605    let save = build.load(Type::PTR, base, info(8, 8), Flags::default());
606
607    // Where each file's next slot is, which is the one thing the offsets in the list say, and the
608    // counter itself, which is what steps on by every slot the object took of that file.
609    let mut walked = [None, None];
610    let mut nexts = [None, None];
611    for float in [false, true] {
612        let file = usize::from(float);
613        if counts[file] == 0 {
614            continue;
615        }
616        let counter = offset(build, read.list, field_of(float));
617        let read = build.load(Type::int(32), counter, info(4, 4), Flags::default());
618        let wide = build.unary(Opcode::ZExt, read, Type::int(64));
619        walked[file] = Some(read);
620        nexts[file] = Some(added(build, save, wide));
621    }
622
623    let mut seen = [0, 0];
624    for &slot in read.slots {
625        let float = is_float(slot);
626        let file = usize::from(float);
627        let Some(from) = nexts[file] else { continue };
628        let step = i64::from(area.stride(float) * seen[file]);
629        seen[file] += 1;
630        let bytes = width(slot);
631        let ty = moved_as(area, float, bytes);
632        let aligned = slot_align(area, float, bytes);
633        let at = offset(build, from, step);
634        let value = build.load(ty, at, info(bytes, aligned), Flags::default());
635        let into = offset(build, read.room, i64::try_from(slot.offset()).unwrap_or(0));
636        let holds = info(bytes, part(read.wants, slot.offset()));
637        build.store(value, into, holds, Flags::default());
638    }
639
640    // And the counters step on by every slot the object took, since the whole of it came out of
641    // the area and the argument behind it starts past all of it.
642    for float in [false, true] {
643        let file = usize::from(float);
644        let Some(counter) = walked[file] else { continue };
645        let by = build.iconst(Type::int(32), i128::from(area.stride(float) * counts[file]));
646        let stepped = build.binary(Opcode::Add, counter, by, Flags::default());
647        let at = offset(build, read.list, field_of(float));
648        build.store(stepped, at, info(4, 4), Flags::default());
649    }
650    build.unary(Opcode::PtrToInt, read.room, Type::int(64))
651}
652
653/// A buffer at the front of the entry block, which is where an alloca of a fixed size belongs.
654///
655/// Not where the walk is, because a walk inside a loop would then be an alloca inside a loop,
656/// which is a frame that grows every time round. One buffer per `va_arg` of an object, made once
657/// and written every time the object is read, which is what the front end would have written if
658/// the temporary had a name.
659fn buffer(func: &mut Func, inst: Inst, size: u64, align: u32) -> Option<Value> {
660    let entry = func.entry()?;
661    let span = func.span(inst);
662    let mem = func.add_mem(info(size, align.max(1)));
663    let data = InstData { extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) };
664    let made = func.create_inst(data, &[Type::PTR], span);
665    let first = func.insts(entry).next();
666    match first {
667        Some(first) => func.insert_before(made, first),
668        None => func.append_inst(entry, made),
669    }
670    func[made].first_result
671}
672
673/// Where the argument the caller left in memory is, with the overflow pointer stepped on past it,
674/// as an integer address.
675///
676/// The pointer is rounded up first for an object that wants more alignment than a word. The
677/// argument area is a run of words, so anything asking for eight or less is where it is already,
678/// and anything asking for more was put at the next multiple of what it asked for by whoever
679/// passed it.
680fn overflow(build: &mut Builder<'_>, list: Value, area: Area, size: u64, align: u32) -> Value {
681    let word = u64::from(area.word);
682    let wide = Type::int(64);
683    let pointer = offset(build, list, OVERFLOW);
684    let here = build.load(Type::PTR, pointer, info(word, area.word), Flags::default());
685
686    // As an integer, because rounding up is an add and a mask and neither is a thing to do to a
687    // pointer. Both casts are free: the two are the same bits on this machine and nothing is
688    // written for either.
689    let mut at = build.unary(Opcode::PtrToInt, here, wide);
690    if u64::from(align) > word {
691        // Up to the next multiple of a power of two, which is the round up every alignment is.
692        // The mask is the negative of the alignment because that is what the complement of one
693        // less than it comes to, and writing it that way keeps it inside a signed sixty four bit
694        // constant.
695        let bump = build.iconst(wide, i128::from(align) - 1);
696        at = build.binary(Opcode::Add, at, bump, Flags::default());
697        let mask = build.iconst(wide, -i128::from(align));
698        at = build.binary(Opcode::And, at, mask, Flags::default());
699    }
700
701    // Past it, rounded up to a whole number of words, because the argument area holds words and
702    // the argument behind this one starts at one of them.
703    let by = build.iconst(wide, i128::from(size.next_multiple_of(word)));
704    let onward = build.binary(Opcode::Add, at, by, Flags::default());
705    let onward = build.unary(Opcode::IntToPtr, onward, Type::PTR);
706    build.store(onward, pointer, info(word, area.word), Flags::default());
707    at
708}
709
710/// Which of the two counters a file's slots are walked with.
711fn field_of(float: bool) -> i64 {
712    if float { FP_OFFSET } else { GP_OFFSET }
713}
714
715/// Whether a slot is one of the vector file's.
716fn is_float(slot: Slot) -> bool {
717    matches!(slot, Slot::Float { .. })
718}
719
720/// How many registers of a file an object takes.
721fn taken_of(slots: &[Slot], float: bool) -> u32 {
722    u32::try_from(slots.iter().filter(|&&slot| is_float(slot) == float).count()).unwrap_or(0)
723}
724
725/// What one slot's bytes are moved as.
726///
727/// An integer of the slot's width whatever file it came from, because what this is is a copy of the
728/// object's bytes and nothing here reads them as anything. A slot the whole width of a vector
729/// register is the exception and has to be: there is no integer that wide on this machine, and the
730/// file the bytes are already in is the one that moves all sixteen of them at once.
731fn moved_as(area: Area, float: bool, bytes: u64) -> Type {
732    if float && bytes > u64::from(area.word) {
733        return Type::float(Float::F128);
734    }
735    Type::int(u32::try_from(bytes).unwrap_or(1) * 8)
736}
737
738/// What the address of a slot in the save area is known to be aligned to.
739///
740/// The area begins on a vector slot boundary and every slot in it is a whole number of its file's
741/// strides along from there, so a value as wide as its file's stride sits at a multiple of the
742/// stride and everything narrower sits at a multiple of a word. The wide case is the one that has
743/// to be right, since what moves a whole vector register is a `movaps` and a `movaps` faults on an
744/// address that is not a multiple of sixteen rather than being slow about it.
745fn slot_align(area: Area, float: bool, bytes: u64) -> u32 {
746    if bytes > u64::from(area.word) { area.stride(float) } else { area.word }
747}
748
749/// How many bytes one slot moves, which is its own width rounded up to one the machine has a load
750/// for.
751fn width(slot: Slot) -> u64 {
752    match slot {
753        Slot::Integer { size, .. } => u64::from(size.next_power_of_two().clamp(1, 8)),
754        Slot::Float { format, .. } => u64::from(format.width()).div_ceil(8),
755    }
756}
757
758/// What a part of an object at that offset is aligned to, which is what the object is aligned to
759/// for the part at the front of it and how far into the object the part sits for every other.
760fn part(align: u32, offset: u64) -> u32 {
761    let align = align.max(1);
762    if offset == 0 {
763        return align;
764    }
765    u32::try_from(1_u64 << offset.trailing_zeros()).unwrap_or(align).min(align)
766}
767
768/// Whether an argument of that size travelled as the address of a copy rather than as itself.
769///
770/// The platform's rule stated as a size and nothing else: an argument that is not one, two, four or
771/// eight bytes is passed as a pointer to a copy the caller made, whatever the argument is made of.
772/// A three byte structure is one and so is a sixteen byte float, and no classification is asked
773/// about either, which is why the slots the front end put on a `va_object` go unread on this side.
774fn by_reference(size: u64) -> bool {
775    !matches!(size, 1 | 2 | 4 | 8)
776}
777
778/// The slot the walk is at, with the list stepped on past it, written in front of an instruction.
779///
780/// One word whatever is in the slot, because this convention gives every argument exactly one and
781/// pays for the ones that do not fit by passing their address instead. So there is nothing to round
782/// up, nothing to ask and nothing to branch on.
783fn slot(func: &mut Func, inst: Inst, list: Value, word: u64) -> Value {
784    let here = read(func, inst, list, Type::PTR, word);
785    let step = field(func, inst, here, i64::try_from(word).unwrap_or(0));
786    let mem = func.add_mem(info(word, u32::try_from(word).unwrap_or(1)));
787    let args = func.push_values(&[step, list]);
788    let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Store) };
789    let span = func.span(inst);
790    let made = func.create_inst(data, &[], span);
791    func.insert_before(made, inst);
792    here
793}
794
795/// A load written in front of an instruction, at the alignment its own width gives it.
796fn read(func: &mut Func, inst: Inst, from: Value, ty: Type, size: u64) -> Value {
797    let mem = func.add_mem(info(size, u32::try_from(size).unwrap_or(1)));
798    let args = func.push_values(&[from]);
799    let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Load) };
800    ahead(func, inst, data, ty)
801}
802
803/// How many bytes of a scalar the one field walk moves, or nothing for a type it is not right
804/// about.
805///
806/// A pointer has no width of its own here and is as wide as the convention's word, which is the one
807/// question this has to ask the target rather than the type.
808///
809/// A `long double`, a `_Float128` and an `__int128` are answered for as well, and what the slot
810/// holds for one of those is the address of the copy the caller made rather than the value. That is
811/// [`by_reference`] of the width, the same question the object walk below asks, and the load at the
812/// end of it is the same load either way.
813fn travels(ty: Type, word: u64) -> Option<u64> {
814    if !ty.is_scalar() || !(ty.is_int() || ty.is_float() || ty.is_ptr()) {
815        return None;
816    }
817    if ty.is_ptr() {
818        return Some(word);
819    }
820    Some(u64::from(ty.bits().div_ceil(8)))
821}
822
823/// One `va_arg` on a convention whose list is a plain pointer, as the load at the slot the walk is
824/// at.
825///
826/// The instruction becomes that load rather than being replaced by one, for the reason the
827/// branching walk gives: the value the rest of the function reads stays the value it already read,
828/// so nothing has to be substituted anywhere. Everything the load needs is written in front of it,
829/// and since nothing here branches the instruction does not move and the block is not cut.
830///
831/// A scalar of a width the convention passes whole is in the slot, and the load is the whole of it.
832/// One of the three the convention passes as an address is not, and the slot holds where the caller
833/// put its copy, so the value is one load further on. That is the same question the object walk
834/// below asks and it is asked of the width alone, which is what keeps the two answers together.
835fn value(func: &mut Func, inst: Inst, word: u64) {
836    let Some(result) = func[inst].first_result else { return };
837    let Some(&list) = func[func[inst].args].first() else { return };
838    let ty = func[result].ty;
839    let Some(bytes) = travels(ty, word) else { return };
840
841    let here = slot(func, inst, list, word);
842    let from = if by_reference(bytes) { read(func, inst, here, Type::PTR, word) } else { here };
843    // The next power of two up from the width, which is the width itself for everything the slot
844    // holds and is sixteen for the ten bytes of an x87 value, since the copy the caller made is an
845    // object of the type and the type is sixteen bytes here.
846    let align = u32::try_from(bytes.next_power_of_two()).unwrap_or(1);
847    let mem = func.add_mem(info(bytes, align));
848    let args = func.push_values(&[from]);
849    let data = &mut func[inst];
850    data.opcode = Opcode::Load;
851    data.args = args;
852    data.extra = Extra::Mem(mem);
853    data.flags = data.flags.intersection(Flags::legal_on(Opcode::Load));
854}
855
856/// One `va_object` on the same convention, as the address the object can be read from.
857///
858/// The slot itself for an object of a width the convention passes whole, and what the slot holds
859/// for every other one, which is the address of the copy the caller made. That is the same question
860/// [`by_reference`] answers for a scalar and it is asked of the size alone, so an object of three
861/// bytes and an object of a hundred take the two different paths for the one reason.
862///
863/// The address is answered rather than a copy of the object, which is what the instruction is for:
864/// the object is already somewhere addressable either way, and the copy the C standard describes is
865/// the assignment the caller of `va_arg` wrote.
866fn held(func: &mut Func, inst: Inst, word: u64) {
867    let Extra::VaObject(at) = func[inst].extra else { return };
868    let MemInfo { size, .. } = func[func[at].mem];
869    let Some(&list) = func[func[inst].args].first() else { return };
870    if func[inst].first_result.is_none() {
871        return;
872    }
873
874    let here = slot(func, inst, list, word);
875    let from = if by_reference(size) { read(func, inst, here, Type::PTR, word) } else { here };
876    // Through an integer and back, which is what the branching walk's answer is too and is free
877    // either way: the two are the same bits on this machine and nothing is written for the pair.
878    let args = func.push_values(&[from]);
879    let data = InstData { args, ..InstData::new(Opcode::PtrToInt) };
880    let address = ahead(func, inst, data, Type::int(64));
881    let args = func.push_values(&[address]);
882    let data = &mut func[inst];
883    data.opcode = Opcode::IntToPtr;
884    data.args = args;
885    data.extra = Extra::None;
886    data.flags = data.flags.intersection(Flags::legal_on(Opcode::IntToPtr));
887}
888
889/// One `va_copy`, as the fields of one list moved into another.
890///
891/// A list is those fields and holds nothing anywhere else, so copying it is copying them, and a
892/// handful of words move as a handful of words rather than as a call to `memcpy`, which is a name
893/// this compiler cannot emit yet and would be the wrong answer for three words in any case. How
894/// many words there are is the convention's answer: three for the four field list, since the two
895/// offsets share one, and one for the list that is a pointer.
896///
897/// Every read is built before any write, so that a list copied onto itself, which is legal and
898/// useless, moves what it held rather than what it has just been given.
899fn copy(func: &mut Func, inst: Inst, bytes: u64) {
900    let [into, from] = func[func[inst].args] else { return };
901    let mut moved = Vec::new();
902    for word in 0..bytes / 8 {
903        let step = i64::try_from(word * 8).unwrap_or(0);
904        let there = field(func, inst, from, step);
905        let mem = func.add_mem(info(8, 8));
906        let args = func.push_values(&[there]);
907        let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Load) };
908        moved.push((ahead(func, inst, data, Type::int(64)), step));
909    }
910    for (read, step) in moved {
911        let here = field(func, inst, into, step);
912        let mem = func.add_mem(info(8, 8));
913        let args = func.push_values(&[read, here]);
914        let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Store) };
915        let span = func.span(inst);
916        let made = func.create_inst(data, &[], span);
917        func.insert_before(made, inst);
918    }
919    func.remove_inst(inst);
920}
921
922/// The address that far past a pointer, written in front of an instruction, or the pointer itself
923/// for no distance at all.
924///
925/// A field of a list for the walk that has four of them, and the slot behind this one for the walk
926/// whose list is a pointer.
927fn field(func: &mut Func, inst: Inst, list: Value, at: i64) -> Value {
928    if at == 0 {
929        return list;
930    }
931    let extra = Extra::Imm(func.add_imm(Imm::int(i128::from(at), Type::int(64))));
932    let step =
933        ahead(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, Type::int(64));
934    let args = func.push_values(&[list, step]);
935    ahead(func, inst, InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
936}
937
938/// Puts an instruction in front of another one and gives back the value it produces.
939fn ahead(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
940    let span = func.span(inst);
941    let made = func.create_inst(data, &[ty], span);
942    func.insert_before(made, inst);
943    func[made].first_result.expect("an instruction created with one result has one")
944}
945
946/// The address of a field of a list in a block being filled, or the list itself for the field at
947/// the front of it.
948fn offset(build: &mut Builder<'_>, list: Value, at: i64) -> Value {
949    if at == 0 {
950        return list;
951    }
952    let step = build.iconst(Type::int(64), i128::from(at));
953    added(build, list, step)
954}
955
956/// A pointer with an integer added to it.
957fn added(build: &mut Builder<'_>, pointer: Value, by: Value) -> Value {
958    let args = build.func().push_values(&[pointer, by]);
959    build.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR)
960}
961
962/// An ordinary read or write of that many bytes, aligned that far.
963///
964/// Every access this pass makes is to a field of a list or to an argument, and none of them is
965/// atomic or has anything to say about aliasing.
966fn info(size: u64, align: u32) -> MemInfo {
967    MemInfo {
968        size,
969        align,
970        order: MemOrder::NotAtomic,
971        tbaa: None,
972        owns: 0,
973        restrict: Restrict::NONE,
974    }
975}
976
977#[cfg(test)]
978mod tests {
979    use rucc_base::Interner;
980    use rucc_base::float::Format;
981    use rucc_ir::{Builder, Extra, Func, InstData, Module, Opcode, Signature, Type, VaInfo};
982    use rucc_target::x86_64::{SYSV, WIN64};
983    use rucc_target::{Arch, Env, Os, Slot, TargetInfo, Triple};
984
985    use super::{Area, FP_OFFSET, GP_OFFSET, OVERFLOW, SAVE_AREA, SIZE, VECTOR_SLOT, lists};
986
987    fn target() -> TargetInfo {
988        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
989    }
990
991    /// `T f(va_list *ap) { return va_arg(*ap, T); }`, or the same shape over whichever of the
992    /// family is asked for, with the list arriving as the pointer it has decayed to by the time
993    /// anything reads it.
994    fn built(opcode: Opcode, ty: Type, lists: usize) -> (Interner, Func) {
995        let mut names = Interner::new();
996        let params = vec![Type::PTR; lists];
997        let mut signature = Signature::new().with_params(&params);
998        if !ty.is_void() {
999            signature = signature.with_returns(&[ty]);
1000        }
1001        let mut func = Func::new(names.intern("f"), signature);
1002        let entry = func.create_block();
1003        let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
1004
1005        let mut build = Builder::new(&mut func, entry);
1006        let list = build.func().push_values(&args);
1007        if ty.is_void() {
1008            build.inst(InstData { args: list, ..InstData::new(opcode) }, &[]);
1009            build.ret(&[]);
1010        } else {
1011            let got = build.value(InstData { args: list, ..InstData::new(opcode) }, ty);
1012            build.ret(&[got]);
1013        }
1014        (names, func)
1015    }
1016
1017    fn printed(func: &Func, names: &mut Interner) -> String {
1018        let module = Module::new(names.intern("va.c"), &target());
1019        rucc_ir::print_func(&module, func, names)
1020    }
1021
1022    fn valid(func: &Func, names: &mut Interner) {
1023        let module = Module::new(names.intern("va.c"), &target());
1024        rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
1025    }
1026
1027    /// The numbers in this test are the psABI's own, written out rather than computed, because the
1028    /// whole point of the layout is that it is the document's and not a convenient one. A version
1029    /// of [`Area`] that worked them out differently would agree with itself and disagree with the C
1030    /// library, and this is what would notice.
1031    #[test]
1032    fn the_save_area_is_the_one_the_document_describes() {
1033        let area = Area::of(&SYSV);
1034        assert_eq!(area.floats_at, 48, "six general purpose registers of eight bytes");
1035        assert_eq!(area.size, 176, "and eight vector ones of sixteen");
1036        assert_eq!(area.stride(false), 8);
1037        assert_eq!(area.stride(true), VECTOR_SLOT);
1038        assert_eq!(area.starts_at(false), 0);
1039        assert_eq!(area.starts_at(true), 48);
1040        // The last slot's own offset and not the end of the area, which is what `va_arg` compares
1041        // against: an offset equal to the end is one slot past the last argument.
1042        assert_eq!(area.last(false), Some(40));
1043        assert_eq!(area.last(true), Some(160));
1044    }
1045
1046    /// And the four fields, for the same reason.
1047    #[test]
1048    fn a_list_is_the_four_fields_the_document_describes() {
1049        assert_eq!((GP_OFFSET, FP_OFFSET, OVERFLOW, SAVE_AREA), (0, 4, 8, 16));
1050        assert_eq!(SIZE, 24);
1051    }
1052
1053    #[test]
1054    fn a_va_arg_becomes_the_branch_on_whether_the_argument_is_still_in_the_save_area() {
1055        let (mut names, mut func) = built(Opcode::VaArg, Type::int(32), 1);
1056        let before = func.blocks().count();
1057        lists(&mut func, &SYSV);
1058        assert_eq!(func.blocks().count(), before + 3, "one for each path and one they meet at");
1059
1060        let text = printed(&func, &mut names);
1061        assert!(!text.contains("va_arg"), "the va_arg is gone: {text}");
1062        assert!(text.contains("icmp ule"), "the threshold is a comparison: {text}");
1063        assert!(text.contains("br_if"), "and it is branched on: {text}");
1064        valid(&func, &mut names);
1065    }
1066
1067    /// Which field it walks is the whole of the difference between the two files, and getting it
1068    /// backwards is a program that reads its integers out of the vector half.
1069    #[test]
1070    fn which_half_of_the_area_is_walked_is_the_type_s_answer() {
1071        for (ty, last, stride) in
1072            [(Type::int(64), 40, 8), (Type::float(rucc_ir::Float::F64), 160, 16)]
1073        {
1074            let (mut names, mut func) = built(Opcode::VaArg, ty, 1);
1075            lists(&mut func, &SYSV);
1076            let text = printed(&func, &mut names);
1077            assert!(text.contains(&format!("iconst.i32 {last}")), "{ty:?} stops at {last}: {text}");
1078            assert!(text.contains(&format!("iconst.i32 {stride}")), "and steps by it: {text}");
1079        }
1080    }
1081
1082    /// The value the rest of the function reads has to stay the value it already read, since the
1083    /// rewrite substitutes nothing anywhere. It stays it by the `va_arg` becoming the load rather
1084    /// than being replaced by one, so the instruction is the same instruction under a new opcode
1085    /// and in a new block.
1086    #[test]
1087    fn what_reads_the_argument_reads_the_same_value_it_did_before() {
1088        let (mut names, mut func) = built(Opcode::VaArg, Type::int(32), 1);
1089        let entry = func.entry().expect("an entry block");
1090        let inst = func.insts(entry).next().expect("the va_arg is first");
1091        let read = func[inst].first_result.expect("it produces the argument");
1092
1093        lists(&mut func, &SYSV);
1094        assert_eq!(func[inst].opcode, Opcode::Load, "the same instruction, lowered");
1095        assert_eq!(func[inst].first_result, Some(read), "producing the same value");
1096        assert_ne!(func.block_of(inst), Some(entry), "in the block the two paths meet at");
1097        valid(&func, &mut names);
1098    }
1099
1100    #[test]
1101    fn a_va_end_is_nothing_at_all() {
1102        let (mut names, mut func) = built(Opcode::VaEnd, Type::VOID, 1);
1103        lists(&mut func, &SYSV);
1104        let text = printed(&func, &mut names);
1105        assert!(!text.contains("va_end"), "{text}");
1106        assert_eq!(func.blocks().count(), 1, "and needs no block: {text}");
1107        valid(&func, &mut names);
1108    }
1109
1110    /// Three words and no branch, because a list is three words and holds nothing anywhere else.
1111    #[test]
1112    fn a_va_copy_is_the_list_moved_a_word_at_a_time() {
1113        let (mut names, mut func) = built(Opcode::VaCopy, Type::VOID, 2);
1114        lists(&mut func, &SYSV);
1115        let text = printed(&func, &mut names);
1116        assert!(!text.contains("va_copy"), "{text}");
1117        assert_eq!(text.matches("load.i64").count(), 3, "{text}");
1118        assert_eq!(text.matches("store").count(), 3, "{text}");
1119        assert_eq!(func.blocks().count(), 1, "and needs no block: {text}");
1120        valid(&func, &mut names);
1121    }
1122
1123    /// Every read before every write, so that `va_copy(ap, ap)` moves what the list held rather
1124    /// than what it has just been given. Useless and legal, which is exactly the combination that
1125    /// gets written once and never tested anywhere else.
1126    #[test]
1127    fn a_list_copied_onto_itself_moves_what_it_held() {
1128        let (mut names, mut func) = built(Opcode::VaCopy, Type::VOID, 1);
1129        // One parameter, so both operands of the copy are the same list. The builder above pushes
1130        // as many operands as there are parameters, so the second is added here.
1131        let entry = func.entry().expect("an entry block");
1132        let inst = func.insts(entry).next().expect("the copy is first");
1133        let list = func[func[inst].args][0];
1134        let args = func.push_values(&[list, list]);
1135        func[inst].args = args;
1136
1137        lists(&mut func, &SYSV);
1138        let text = printed(&func, &mut names);
1139        let first = text.find("store").expect("a write");
1140        let last = text.rfind("load.i64").expect("a read");
1141        assert!(last < first, "every read is above every write: {text}");
1142        valid(&func, &mut names);
1143    }
1144
1145    /// `struct s f(va_list *ap) { return va_arg(*ap, struct s); }`, where the structure is that
1146    /// many bytes wanting that much alignment and arrived in those registers. The object form of
1147    /// the instruction rather than the value one, because an aggregate is not a value and answers
1148    /// where it is instead.
1149    ///
1150    /// No slots is the object the classification sent to the caller's argument area, which is what
1151    /// everything over two eightbytes is.
1152    fn object(size: u64, align: u32, slots: &[Slot]) -> (Interner, Func) {
1153        let mut names = Interner::new();
1154        let signature = Signature::new().with_params(&[Type::PTR]).with_returns(&[Type::PTR]);
1155        let mut func = Func::new(names.intern("f"), signature);
1156        let entry = func.create_block();
1157        let list = func.append_param(entry, Type::PTR);
1158        let mem = func.add_mem(super::info(size, align));
1159        let slots = func.push_slots(slots);
1160        let at = func.add_va_object(VaInfo { mem, slots });
1161        let mut build = Builder::new(&mut func, entry);
1162        let args = build.func().push_values(&[list]);
1163        let data = InstData { args, extra: Extra::VaObject(at), ..InstData::new(Opcode::VaObject) };
1164        let got = build.value(data, Type::PTR);
1165        build.ret(&[got]);
1166        (names, func)
1167    }
1168
1169    /// One eightbyte of an object in the general purpose file, at that offset.
1170    fn gpr(offset: u64, size: u32) -> Slot {
1171        Slot::Integer { offset, size }
1172    }
1173
1174    /// One in the vector file, holding a `double`, which is what a whole eightbyte of floating
1175    /// point data is read as whichever way the members divide it up.
1176    fn sse(offset: u64) -> Slot {
1177        Slot::Float { offset, format: Format::Double }
1178    }
1179
1180    /// Over two eightbytes is class MEMORY whatever the members are, so there is one place it can
1181    /// be and no question to ask about which.
1182    #[test]
1183    fn an_object_too_big_for_the_registers_is_read_out_of_the_caller_s_memory() {
1184        let (mut names, mut func) = object(24, 8, &[]);
1185        lists(&mut func, &SYSV);
1186        let text = printed(&func, &mut names);
1187        assert!(!text.contains("va_object"), "{text}");
1188        assert_eq!(func.blocks().count(), 1, "no branch, so no new block: {text}");
1189        assert!(text.contains("iconst.i64 8"), "the overflow field is at eight: {text}");
1190        assert!(text.contains("iconst.i64 24"), "and the pointer steps past the object: {text}");
1191        assert!(!text.contains("gp_offset"), "{text}");
1192        valid(&func, &mut names);
1193    }
1194
1195    /// The size the pointer steps on by is the size rounded up to a word, because the argument
1196    /// area holds words and the argument behind this one starts at one of them.
1197    #[test]
1198    fn a_size_that_is_not_a_whole_number_of_words_steps_on_by_the_next_one() {
1199        let (mut names, mut func) = object(28, 4, &[]);
1200        lists(&mut func, &SYSV);
1201        let text = printed(&func, &mut names);
1202        assert!(text.contains("iconst.i64 32"), "twenty eight bytes step on by thirty two: {text}");
1203        valid(&func, &mut names);
1204    }
1205
1206    /// An object wanting more than a word is at the next multiple of what it wants, and one
1207    /// wanting a word or less is where the pointer already is, since the area is a run of words.
1208    #[test]
1209    fn an_object_wanting_more_alignment_than_a_word_is_rounded_up_to_it() {
1210        let (mut names, mut func) = object(32, 16, &[]);
1211        lists(&mut func, &SYSV);
1212        let text = printed(&func, &mut names);
1213        assert!(text.contains("iconst.i64 15"), "up to the next sixteen: {text}");
1214        assert!(text.contains("iconst.i64 -16"), "and down to a multiple of it: {text}");
1215        assert!(text.contains(" = and "), "which is an add and a mask: {text}");
1216        valid(&func, &mut names);
1217
1218        let (mut names, mut func) = object(24, 8, &[]);
1219        lists(&mut func, &SYSV);
1220        assert!(!printed(&func, &mut names).contains(" = and "), "a word wants no rounding");
1221    }
1222
1223    /// An object that arrived in registers is in the save area, and reading it is the branch a
1224    /// scalar asks with the object's own threshold: two eightbytes want two slots, so an offset
1225    /// that leaves room for one is not room enough.
1226    #[test]
1227    fn an_object_that_arrived_in_registers_is_copied_out_of_the_save_area() {
1228        let (mut names, mut func) = object(16, 8, &[gpr(0, 8), gpr(8, 8)]);
1229        let before = func.blocks().count();
1230        lists(&mut func, &SYSV);
1231        assert_eq!(func.blocks().count(), before + 3, "one for each path and one they meet at");
1232
1233        let text = printed(&func, &mut names);
1234        assert!(!text.contains("va_object"), "{text}");
1235        assert!(text.contains("iconst.i32 32"), "forty eight less two slots: {text}");
1236        assert!(text.contains("icmp ule"), "which is the threshold: {text}");
1237        assert!(text.contains("alloca, size 16"), "the object lands in a buffer: {text}");
1238        assert!(text.contains("iconst.i32 16"), "and the counter steps by both slots: {text}");
1239        valid(&func, &mut names);
1240    }
1241
1242    /// An object of one eightbyte of each file has to have room in both halves of the area, and
1243    /// the psABI puts the whole of it in the caller's memory when either of them is out. So there
1244    /// are two questions, and the second is only asked when the first said yes.
1245    #[test]
1246    fn an_object_in_both_files_asks_about_both_of_them() {
1247        let (mut names, mut func) = object(16, 8, &[gpr(0, 8), sse(8)]);
1248        lists(&mut func, &SYSV);
1249        let text = printed(&func, &mut names);
1250        assert_eq!(text.matches("br_if").count(), 2, "one question per file: {text}");
1251        assert!(text.contains("iconst.i32 40"), "forty eight less one slot: {text}");
1252        assert!(text.contains("iconst.i32 160"), "and a hundred and seventy six less one: {text}");
1253        assert!(text.contains("iconst.i32 8"), "each counter steps by its own slot: {text}");
1254        valid(&func, &mut names);
1255    }
1256
1257    /// An object whose last eightbyte is a part of one still comes out of the area as a whole
1258    /// register, so the buffer has room for the whole register and the bytes past the object are
1259    /// never read.
1260    #[test]
1261    fn the_buffer_is_as_big_as_the_registers_reach() {
1262        let (mut names, mut func) = object(5, 1, &[gpr(0, 5)]);
1263        lists(&mut func, &SYSV);
1264        let text = printed(&func, &mut names);
1265        assert!(text.contains("alloca, size 8"), "five bytes travel in a whole register: {text}");
1266        valid(&func, &mut names);
1267    }
1268
1269    /// A classification this cannot read out of the area is left alone, which is what makes the
1270    /// function refused by name further down rather than compiled into half a walk.
1271    ///
1272    /// Class X87 is the one to ask about, because the width alone would say yes: ten bytes sit
1273    /// inside a vector slot with room to spare, and there is no x87 register among the fourteen a
1274    /// variadic callee spills, so there is nothing in the area for this to read.
1275    #[test]
1276    fn a_classification_that_does_not_fit_the_area_is_left_alone() {
1277        let x87 = [Slot::Float { offset: 0, format: Format::X87Extended }];
1278        let (mut names, mut func) = object(16, 16, &x87);
1279        let before = printed(&func, &mut names);
1280        lists(&mut func, &SYSV);
1281        assert_eq!(printed(&func, &mut names), before);
1282    }
1283
1284    /// A `_Float128` walks the vector half with a slot of the whole register.
1285    ///
1286    /// One register and not two, which is the same answer the classification gives a quad passed to
1287    /// a function that names it: the offset stops at the last slot rather than the last but one,
1288    /// and it steps on by sixteen. What is read is sixteen bytes of float, which is a `movaps`
1289    /// further down and is the instruction gcc reads the same slot with.
1290    #[test]
1291    fn a_quad_takes_a_whole_vector_slot_of_the_save_area() {
1292        let (mut names, mut func) = built(Opcode::VaArg, Type::float(rucc_ir::Float::F128), 1);
1293        lists(&mut func, &SYSV);
1294        valid(&func, &mut names);
1295        let text = printed(&func, &mut names);
1296        assert!(!text.contains("va_arg"), "the va_arg is gone: {text}");
1297        assert!(text.contains("iconst.i32 160"), "a hundred and seventy six less one slot: {text}");
1298        assert!(text.contains("iconst.i32 16"), "and the counter steps by a whole one: {text}");
1299        assert!(text.contains("load.f128"), "read as the sixteen bytes it is: {text}");
1300    }
1301
1302    /// And the argument area gives it two words aligned to two, which is where it stops being a
1303    /// wider `double`. Every other value the machine computes in is where the pointer already is
1304    /// and steps it on by a word.
1305    #[test]
1306    fn a_quad_the_registers_ran_out_before_is_rounded_up_to_sixteen() {
1307        let (mut names, mut func) = built(Opcode::VaArg, Type::float(rucc_ir::Float::F128), 1);
1308        lists(&mut func, &SYSV);
1309        let text = printed(&func, &mut names);
1310        assert!(text.contains("iconst.i64 15"), "up to the next sixteen: {text}");
1311        assert!(text.contains("iconst.i64 -16"), "and down to a multiple of it: {text}");
1312        assert!(text.contains(" = and "), "which is an add and a mask: {text}");
1313
1314        let (mut names, mut func) = built(Opcode::VaArg, Type::float(rucc_ir::Float::F64), 1);
1315        lists(&mut func, &SYSV);
1316        let text = printed(&func, &mut names);
1317        assert!(!text.contains(" = and "), "a double is where the pointer already is: {text}");
1318        assert!(text.contains("iconst.i64 8"), "and steps it on by a word: {text}");
1319    }
1320
1321    /// An object holding a quad is one slot of the vector file, so the question is a single one
1322    /// and the copy moves all sixteen bytes at once.
1323    ///
1324    /// The buffer it lands in is sixteen byte aligned, which is what the store needs rather than
1325    /// what the object asked for, although for this object the two are the same number.
1326    #[test]
1327    fn an_object_holding_a_quad_is_copied_out_as_one_whole_register() {
1328        let quad = [Slot::Float { offset: 0, format: Format::Quad }];
1329        let (mut names, mut func) = object(16, 16, &quad);
1330        lists(&mut func, &SYSV);
1331        valid(&func, &mut names);
1332        let text = printed(&func, &mut names);
1333        assert!(!text.contains("va_object"), "{text}");
1334        assert_eq!(text.matches("br_if").count(), 1, "one file, so one question: {text}");
1335        assert!(text.contains("iconst.i32 160"), "a hundred and seventy six less one slot: {text}");
1336        assert!(text.contains("load.f128"), "moved as the register it is in: {text}");
1337        assert!(text.contains("alloca, size 16, align 16"), "a buffer a movaps accepts: {text}");
1338    }
1339
1340    /// What reads the object goes on reading the value it already read, the same way it does for a
1341    /// value, and for the same reason: the instruction becomes the address rather than being
1342    /// replaced by one, so nothing has to be substituted anywhere.
1343    #[test]
1344    fn what_reads_the_object_reads_the_same_value_it_did_before() {
1345        for slots in [&[][..], &[gpr(0, 8), gpr(8, 8)][..]] {
1346            let (mut names, mut func) = object(if slots.is_empty() { 24 } else { 16 }, 8, slots);
1347            let entry = func.entry().expect("an entry block");
1348            let inst = func.insts(entry).next().expect("the va_object is first");
1349            let read = func[inst].first_result.expect("it answers an address");
1350
1351            lists(&mut func, &SYSV);
1352            assert_eq!(func[inst].opcode, Opcode::IntToPtr, "the same instruction, lowered");
1353            assert_eq!(func[inst].first_result, Some(read), "producing the same value");
1354            valid(&func, &mut names);
1355        }
1356    }
1357
1358    /// Windows describes a list as one pointer, and its walk is that pointer stepped on, so there is
1359    /// nothing to compare and nowhere else to look: the argument is at the pointer, the pointer
1360    /// moves on by a word, and all of it is straight line.
1361    #[test]
1362    fn a_windows_va_arg_is_the_word_at_the_pointer_and_a_step() {
1363        let (mut names, mut func) = built(Opcode::VaArg, Type::int(32), 1);
1364        lists(&mut func, &WIN64);
1365        valid(&func, &mut names);
1366        let text = printed(&func, &mut names);
1367        assert!(!text.contains("va_arg"), "the va_arg is gone: {text}");
1368        assert!(!text.contains("br_if"), "and nothing was asked: {text}");
1369        assert_eq!(func.blocks().count(), 1, "so no block was made: {text}");
1370        assert!(text.contains("iconst.i64 8"), "the step is one word: {text}");
1371        assert_eq!(text.matches("= load").count(), 2, "the list and the argument: {text}");
1372        assert_eq!(text.matches("store").count(), 1, "and the list is written back: {text}");
1373    }
1374
1375    /// A pointer is as wide as the convention says a word is, since a type carries no width for
1376    /// one. Reading it as no bytes at all would be every string a `printf` was handed.
1377    #[test]
1378    fn a_windows_pointer_argument_is_the_whole_word() {
1379        let (mut names, mut func) = built(Opcode::VaArg, Type::PTR, 1);
1380        lists(&mut func, &WIN64);
1381        valid(&func, &mut names);
1382        let text = printed(&func, &mut names);
1383        assert_eq!(text.matches("= load").count(), 2, "the list and the argument: {text}");
1384        assert_eq!(text.matches("size 8").count(), 3, "and all three are words: {text}");
1385    }
1386
1387    /// The size is the whole of what says where a Windows argument is, so an object of eight bytes
1388    /// is in the slot and the answer is the slot's own address, and one of twenty four is elsewhere
1389    /// and the answer is what the slot holds. Neither of them asks about the classification.
1390    #[test]
1391    fn a_windows_object_is_in_the_slot_or_behind_it_according_to_its_size() {
1392        for (size, loads) in [(8, 1), (24, 2)] {
1393            let (mut names, mut func) = object(size, 8, &[]);
1394            lists(&mut func, &WIN64);
1395            valid(&func, &mut names);
1396            let text = printed(&func, &mut names);
1397            assert!(!text.contains("va_object"), "{text}");
1398            assert_eq!(func.blocks().count(), 1, "no branch, so no new block: {text}");
1399            assert_eq!(text.matches("= load").count(), loads, "{size} bytes: {text}");
1400            assert!(!text.contains("iconst.i64 24"), "the step is a word either way: {text}");
1401        }
1402    }
1403
1404    /// A list that is one pointer is copied by moving one pointer, and a copy moving three words
1405    /// would read two the caller never wrote and write them somewhere it does not own.
1406    #[test]
1407    fn a_windows_va_copy_moves_the_one_word_a_list_is() {
1408        let (mut names, mut func) = built(Opcode::VaCopy, Type::VOID, 2);
1409        lists(&mut func, &WIN64);
1410        valid(&func, &mut names);
1411        let text = printed(&func, &mut names);
1412        assert!(!text.contains("va_copy"), "{text}");
1413        assert_eq!(text.matches("load.i64").count(), 1, "{text}");
1414        assert_eq!(text.matches("store").count(), 1, "{text}");
1415    }
1416
1417    /// A scalar wider than a general purpose register is read through the slot rather than out of
1418    /// it, because the convention travels one as the address of a copy the caller made.
1419    ///
1420    /// Two loads and not one: the slot holds the address and the value is behind it. Reading the
1421    /// slot as the value would be reading the low eight bytes of a `long double`, which is the
1422    /// bottom of its significand and no number anybody wrote.
1423    #[test]
1424    fn a_wide_scalar_is_read_through_the_slot_on_windows() {
1425        let wide =
1426            [Type::int(128), Type::float(rucc_ir::Float::F80), Type::float(rucc_ir::Float::F128)];
1427        for ty in wide {
1428            let (mut names, mut func) = built(Opcode::VaArg, ty, 1);
1429            lists(&mut func, &WIN64);
1430            valid(&func, &mut names);
1431            let text = printed(&func, &mut names);
1432            assert!(!text.contains("va_arg"), "{ty:?}: {text}");
1433            assert_eq!(text.matches("= load").count(), 3, "{ty:?}: {text}");
1434            // Two of the three are addresses, the list's own and the copy's, and the value is
1435            // the third. A dump writes a pointer load without a type on it.
1436            assert_eq!(text.matches("= load %").count(), 2, "{ty:?}: {text}");
1437        }
1438    }
1439
1440    /// A width the algorithm is not right about is left alone for the same reason. An `__int128`
1441    /// takes two slots under an alignment rule of its own, which is a second algorithm and not a
1442    /// wider reading of this one, so it stays exactly as it was and is refused by name later.
1443    #[test]
1444    fn a_type_that_does_not_travel_in_one_slot_is_left_alone() {
1445        let ty = Type::int(128);
1446        let (mut names, mut func) = built(Opcode::VaArg, ty, 1);
1447        let before = printed(&func, &mut names);
1448        lists(&mut func, &SYSV);
1449        assert_eq!(printed(&func, &mut names), before, "{ty:?}");
1450    }
1451
1452    /// A `long double` is class X87, and the class has no register among the fourteen a variadic
1453    /// callee spills, so one is in the caller's argument area whether or not anything came before
1454    /// it. What that means for the rewrite is that the question `va_arg` usually asks has a known
1455    /// answer, so there is no compare, no branch and no join: one block, the overflow pointer
1456    /// rounded up to sixteen and stepped on by sixteen, and the load.
1457    #[test]
1458    fn a_long_double_is_read_straight_out_of_the_callers_argument_area() {
1459        let (mut names, mut func) = built(Opcode::VaArg, Type::float(rucc_ir::Float::F80), 1);
1460        lists(&mut func, &SYSV);
1461        valid(&func, &mut names);
1462        let text = printed(&func, &mut names);
1463        assert!(!text.contains("va_arg"), "the va_arg is gone: {text}");
1464        assert!(!text.contains("br_if"), "and nothing was asked: {text}");
1465        assert_eq!(func.blocks().count(), 1, "so no block was made: {text}");
1466        // The two numbers the psABI gives the class, in the rounding up and in the step.
1467        assert!(text.contains(" 15"), "rounded up to sixteen: {text}");
1468        assert!(text.contains(" 16"), "and stepped on by sixteen: {text}");
1469    }
1470
1471    /// Nothing else is touched, which matters because this runs over every function whether or not
1472    /// one reads a variable argument.
1473    #[test]
1474    fn a_function_with_no_list_in_it_is_left_exactly_as_it_was() {
1475        let mut names = Interner::new();
1476        let int = Type::int(32);
1477        let mut func =
1478            Func::new(names.intern("f"), Signature::new().with_params(&[int]).with_returns(&[int]));
1479        let entry = func.create_block();
1480        let x = func.append_param(entry, int);
1481        Builder::new(&mut func, entry).ret(&[x]);
1482
1483        let before = printed(&func, &mut names);
1484        lists(&mut func, &SYSV);
1485        assert_eq!(printed(&func, &mut names), before);
1486    }
1487}