Skip to main content

rucc_codegen/
abi.rs

1//! Where a function's arguments already are when it starts running, and where a call puts its own.
2//!
3//! Design: `spec/12-abi-and-runtime.md`.
4//!
5//! This is the one part of the calling convention that is not a lowering rule, and it is worth
6//! saying why, because everything else in this crate is. A rule matches a term and rewrites it,
7//! and which register the third argument arrives in is not a fact about any term: it depends on
8//! the argument's position and on the classification of every argument before it. A pattern has
9//! nowhere to put that. So the arguments are built here, by hand, out of what the convention
10//! says, the same way [`crate::finish`] builds a prologue.
11//!
12//! The classification itself is not here either. `rucc-lower` has already run it by the time a
13//! function reaches this crate, which is why the parameters read here are plain scalars: an
14//! aggregate has been split into the pieces it travels in, and a return through memory is an
15//! ordinary pointer parameter in front of the rest. What is left for this is the step after
16//! classification, from how a value travels to which register it is actually in, which is
17//! [`rucc_target::Places`].
18//!
19//! # What it writes
20//!
21//! One `x64.arg_val_*` per parameter that arrived in a register, at the top of the entry block,
22//! each defining a fresh register constrained to the one the argument arrived in. They encode to
23//! nothing. The point of them is that a parameter has to be defined somewhere for the allocator to
24//! have anything to move, and the entry block cannot define it as a block parameter: there is no
25//! edge into the entry block for the move to go on, which is what `rucc_regalloc::rewrite` asserts.
26//!
27//! What the allocator does with them is the whole of the argument sequence. A parameter that is
28//! read where it arrived costs nothing, and one that is not gets a copy, which is the same
29//! bargain the return already makes and is decided by the same code.
30//!
31//! A parameter past the last register arrived in the caller's memory rather than in a register, so
32//! it is a load and not a pseudo, and it is a real instruction that encodes to real bytes. How far
33//! up the caller's argument area it is is a number [`rucc_target::Places`] answers here, but where
34//! that area is from inside this function is a distance into a frame, and no frame exists until
35//! after allocation. So the load is written with nothing in its displacement, which of the two
36//! registers it reads through is left to be settled too, and both are filled in by [`crate::finish`]
37//! out of [`crate::frame::Frame::incoming`]. That is the same bargain an `alloca` already makes,
38//! for the same reason and in the same two places.
39//!
40//! # A call
41//!
42//! The same reasoning the other way round, and one instruction rather than several. `x64.call`
43//! and `x64.call_reg` are the only opcodes in the description whose operand vector is empty
44//! there, because nothing about a call's operands is the same from one call to the next, so they
45//! are built here: one read per argument constrained to the register the convention passes it in,
46//! one definition for the value that comes back constrained to the register it comes back in, and
47//! one definition per register the convention does not preserve.
48//!
49//! A call through an address has one operand more, which is the address, and it is the one
50//! operand of a call that is a fact about the instruction rather than about the signature. It
51//! goes in front of the arguments, because the assembler has to find it and an index into a
52//! vector whose length depends on the convention is not a way of finding anything.
53//!
54//! Those last ones are the clobbers, and they are the whole of what the allocator has to know
55//! about a call besides where the values go. Each is a definition of the physical register itself
56//! rather than of a value, since there is no value: it says the register is written here, which
57//! is exactly what stops the allocator from leaving something in one across the call. A register
58//! an argument or the result already names is not repeated, because naming it once already blocks
59//! it for the length of the instruction, which is all a clobber does.
60//!
61//! An argument past the last register the convention has for it is a store into the outgoing area
62//! rather than an operand of the call, written in front of the call in the same block. Where that
63//! area is does not have to wait for the frame the way the incoming one does, because the outgoing
64//! area is at the bottom of the frame and the bottom of the frame is where the stack pointer is:
65//! that is the whole reason the frame puts it there, since it is where the callee will look. So the
66//! offset [`rucc_target::Places`] gives back is the offset the store is written with.
67//!
68//! The call still reports how many bytes it needed, because the frame reserves as many as the
69//! widest call in the function asked for and cannot know that until every call has been seen.
70
71use rucc_base::{Interner, Symbol};
72use rucc_ir::Type;
73use rucc_mir as mir;
74use rucc_target::{CallRegs, Constraint, PhysReg, Places, RegClass, Where};
75
76/// Why a parameter could not be brought in.
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum Missing {
79    /// It travels on the x87 stack, which is a `long double` and nothing else. That stack is a
80    /// third register file, it is not one the allocator has, and no instruction in the
81    /// description touches it.
82    OnX87,
83    /// It is a float passed to a callee that takes arguments beyond the ones its signature names,
84    /// on a convention that puts such a float in a vector register and in the general purpose
85    /// register at the same position at once. Which arguments are the ones beyond the signature is
86    /// what decides whether the second copy is needed, and a call does not carry that yet.
87    InBothFiles,
88    /// It is a width no pseudo covers, which is anything a machine register does not hold.
89    Width,
90}
91
92impl Missing {
93    /// What it says when a function could not be compiled because of it.
94    ///
95    /// Worded so that it reads the same about a value arriving and a value being passed, since
96    /// the two are the same fact seen from the two ends of one call.
97    #[must_use]
98    pub fn why(self) -> &'static str {
99        match self {
100            Missing::OnX87 => "is on the x87 stack",
101            Missing::InBothFiles => "is a float passed to a variadic callee on this convention",
102            Missing::Width => "is a width no argument register holds",
103        }
104    }
105}
106
107/// Which register file a value of that type travels in.
108///
109/// The whole of what the two files mean to this module. A float is in the vector one and
110/// everything else is in the general purpose one, which is what both of this machine's conventions
111/// say, and the `long double` that is in neither is turned away by [`refuses`] before this is
112/// asked.
113fn class_of(ty: Type, conv: &CallRegs) -> RegClass {
114    if ty.is_float() { conv.sse_class } else { conv.int_class }
115}
116
117/// Why a value of that type cannot travel at all, or nothing if it can.
118///
119/// The width question and the file question in one place, so that the two ends of a call give the
120/// same answer about the same type.
121fn refuses(ty: Type) -> Option<Missing> {
122    if head_of(ty).is_some() {
123        return None;
124    }
125    // A `long double` is the one type here that is in neither of the two files. Saying so is worth
126    // more than calling it a width, because eighty bits is a width this machine computes in and
127    // the file it computes in is what actually stands in the way.
128    if ty.is_float() && ty.bits() == 80 {
129        return Some(Missing::OnX87);
130    }
131    Some(Missing::Width)
132}
133
134/// What a function's parameters came to.
135#[derive(Debug, Default, Clone, PartialEq, Eq)]
136pub struct Arrived {
137    /// The register each parameter is in, in the order the parameters were given, so the caller can
138    /// bind each IR parameter to the one at its position.
139    pub regs: Vec<mir::Reg>,
140    /// The loads that read a parameter out of the caller's argument area, and how far up that area
141    /// each of them reads.
142    ///
143    /// Empty for almost every function, because almost every function has few enough parameters to
144    /// have been handed all of them in registers. The distance is from the bottom of the caller's
145    /// argument area, which is somewhere [`crate::finish`] works out and this cannot.
146    pub stack: Vec<(mir::Inst, u32)>,
147}
148
149/// Binds a function's parameters to where the convention says they arrive.
150///
151/// # Errors
152///
153/// The first parameter this cannot bring in, and why. A function with one is reported rather
154/// than compiled, because the alternative is a function that reads an argument from wherever the
155/// last one happened to leave a register.
156pub fn entry(
157    out: &mut mir::Func,
158    block: mir::Block,
159    params: &[Type],
160    conv: &CallRegs,
161    names: &mut Interner,
162) -> Result<Arrived, (usize, Missing)> {
163    let mut places = Places::new(conv);
164    let mut arrived = Arrived { regs: Vec::with_capacity(params.len()), stack: Vec::new() };
165    for (index, &ty) in params.iter().enumerate() {
166        // Asking for the place of a parameter that cannot be brought in is still worth doing
167        // before giving up, and it costs nothing, because every place after it depends on it and
168        // a reader stepping through this in a debugger should see the same numbers a working
169        // version would.
170        let at = if ty.is_float() { places.float() } else { places.integer() };
171        if let Some(missing) = refuses(ty) {
172            return Err((index, missing));
173        }
174
175        let class = class_of(ty, conv);
176        let reg = out.new_vreg(class);
177        match at {
178            Where::Reg(arrived_in) => {
179                let head = head_of(ty).ok_or((index, Missing::Width))?;
180                let opcode = mir::Opcode::new(names.intern(head));
181                let operand = mir::Operand::write(reg, class).with(Constraint::Fixed(arrived_in));
182                out.build(block, opcode).operand(operand).finish();
183            }
184            // The stack pointer is written down as the register to read through because it is the
185            // one that reaches the caller's stack in almost every function, and a realigned frame
186            // is the exception that [`crate::finish`] rewrites. Putting something here rather than
187            // nothing keeps the instruction printable and verifiable in between.
188            Where::Stack(up) => {
189                let load = load_of(ty).ok_or((index, Missing::Width))?;
190                let opcode = mir::Opcode::new(names.intern(load));
191                let sp = mir::Operand::read(mir::Reg::physical(conv.stack_pointer), conv.int_class);
192                let made = out.build(block, opcode).def(reg, class).mem(mir::Mem::at(sp)).finish();
193                arrived.stack.push((made, up));
194            }
195        }
196        arrived.regs.push(reg);
197    }
198    Ok(arrived)
199}
200
201/// What the instruction that calls a name is called.
202///
203/// Here rather than in a rule for the same reason the arguments are: a rule pattern sees one term
204/// and a call's operands are whatever the signature made them, so no pattern could name them.
205pub const CALL: &str = "x64.call";
206
207/// What the instruction that calls an address in a register is called.
208///
209/// A different instruction rather than the same one with a different operand, which is what the
210/// machine says too: one carries the distance to somewhere in the program and takes a relocation,
211/// and the other carries the register the address is in and takes none. Sharing an opcode would
212/// mean an instruction whose bytes depend on whether a field beside it happens to be set.
213pub const CALL_REG: &str = "x64.call_reg";
214
215/// What one call came to.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct Made {
218    /// The register the value came back in, or `None` for a call that gives nothing back.
219    pub result: Option<mir::Reg>,
220    /// How many bytes below the stack pointer this call needs for the arguments it passes there.
221    ///
222    /// Not always zero for a call that passes everything in registers: a Windows caller reserves
223    /// thirty two bytes for the callee to spill its register arguments into whether it uses them
224    /// or not, and that reservation is this.
225    pub outgoing: u32,
226}
227
228/// Which of a call's values could not be passed, and why.
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub struct Refused {
231    /// Its position among the arguments, or `None` for the value that comes back.
232    pub argument: Option<usize>,
233    /// What is wrong with where it travels.
234    pub missing: Missing,
235}
236
237/// What a call goes to.
238///
239/// The whole of the difference between the two calls. Everything else about them, which is what
240/// they pass and what comes back and which registers they destroy, is the signature's answer and
241/// is the same answer either way.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum Callee {
244    /// A name, which the linker resolves.
245    Named(Symbol),
246    /// An address in a register, which nothing resolves because there is nothing to resolve: the
247    /// value is not known until the program runs.
248    ///
249    /// The register is unconstrained, and it has to be, because every register the convention
250    /// does not preserve is one this instruction writes and every register an argument travels in
251    /// is spoken for. What is left is the registers the callee has to put back, which is where
252    /// the allocator will put the address, and it is the right answer for the same reason it is
253    /// the only one.
254    Through(mir::Reg),
255}
256
257/// One call, as everything about it that is not the function it is being built into.
258#[derive(Debug, Clone, Copy)]
259pub struct Calling<'a> {
260    /// What it calls.
261    pub callee: Callee,
262    /// What it passes, as the type each value travels as and the register it is in, in the order
263    /// the signature holds them, which is the order the convention places them in.
264    pub args: &'a [(Type, mir::Reg)],
265    /// What comes back, or `None` for a call that gives nothing back.
266    pub returns: Option<Type>,
267    /// Whether the callee takes arguments beyond the ones its signature names, which is what says
268    /// whether it reads the count of vector registers the call passed arguments in.
269    pub variadic: bool,
270}
271
272/// Builds one call: what it passes, what comes back, and what it destroys.
273///
274/// # Errors
275///
276/// The first value this cannot pass, and why, before anything is written. A call with one is
277/// reported rather than compiled, because the alternative is a call that leaves an argument
278/// wherever the last one happened to put a register.
279///
280/// # Panics
281///
282/// If a call passes two gigabytes of arguments on the stack, which is a distance no offset in a
283/// frame can hold and a call no program makes.
284pub fn call(
285    out: &mut mir::Func,
286    block: mir::Block,
287    made: &Calling<'_>,
288    conv: &CallRegs,
289    names: &mut Interner,
290) -> Result<Made, Refused> {
291    let &Calling { callee, args, returns, variadic } = made;
292    // Where everything goes, worked out before anything is built, so that a call this cannot make
293    // leaves no half of one behind.
294    let mut places = Places::new(conv);
295    let mut passed = Vec::with_capacity(args.len());
296    // The ones with no register left for them, as the store each of them becomes and how far up
297    // the outgoing area it writes. Almost always empty.
298    let mut on_stack = Vec::new();
299    // How many of them went in vector registers, which is what a SysV variadic callee is told.
300    let mut vectors = 0u32;
301    for (index, &(ty, reg)) in args.iter().enumerate() {
302        let refused = |missing| Refused { argument: Some(index), missing };
303        let at = if ty.is_float() { places.float() } else { places.integer() };
304        if let Some(missing) = refuses(ty) {
305            return Err(refused(missing));
306        }
307        // Windows passes a float to a variadic callee in the vector register and in the general
308        // purpose register at the same position, both at once, because the callee has no
309        // prototype to tell it which file to look in. Doing that needs to know which arguments are
310        // the ones the signature does not name, and a call carries whether the callee is variadic
311        // rather than how many arguments it names, so this is turned down rather than passed in
312        // one file and read from the other.
313        if ty.is_float() && variadic && conv.shared_positions {
314            return Err(refused(Missing::InBothFiles));
315        }
316        let class = class_of(ty, conv);
317        match at {
318            Where::Reg(at) => {
319                // Only a register counts, because the count is of registers. An argument that went
320                // to memory is one the callee reads from memory whatever this says.
321                if class == conv.sse_class {
322                    vectors += 1;
323                }
324                passed.push((reg, at, class));
325            }
326            Where::Stack(up) => {
327                let store = store_of(ty).ok_or(refused(Missing::Width))?;
328                on_stack.push((reg, class, names.intern(store), up));
329            }
330        }
331    }
332    let comes_back = match returns {
333        None => None,
334        Some(ty) if refuses(ty).is_some() => {
335            return Err(Refused { argument: None, missing: refuses(ty).unwrap_or(Missing::Width) });
336        }
337        // Which register a value comes back in depends on nothing but the value, which is why the
338        // return side of the convention is a rule and this side is not. There is no rule here
339        // because the arguments are in the same instruction.
340        Some(ty) => {
341            let class = class_of(ty, conv);
342            let file = if class == conv.sse_class { conv.sse_returns } else { conv.int_returns };
343            let at = *file.first().ok_or(Refused { argument: None, missing: Missing::Width })?;
344            Some((at, class))
345        }
346    };
347
348    // A variadic callee on SysV reads how many vector registers the call passed arguments in and
349    // skips saving them when the answer is none, which is what makes `printf` with no floating
350    // point argument cheap. It is an obligation rather than an optimization: leaving whatever was
351    // in the register there makes the callee save a register file it was not given, and a count
352    // that is too low makes it read an argument out of a register nothing put one in.
353    let counted = if variadic { conv.vector_count } else { None };
354
355    // The arguments that go to memory go there now, in front of the call and after everything this
356    // could have refused, so that a call it cannot make leaves no store behind either. The offset
357    // is written straight in rather than left for [`crate::finish`]: the outgoing area is at the
358    // bottom of the frame because that is where the callee looks for it, and the bottom of the
359    // frame is where the stack pointer already is.
360    for (reg, class, store, up) in on_stack {
361        let sp = mir::Operand::read(mir::Reg::physical(conv.stack_pointer), conv.int_class);
362        let up = i32::try_from(up).expect("an argument area under two gigabytes");
363        let build = out.build(block, mir::Opcode::new(store));
364        build.uses(reg, class).mem(mir::Mem::at(sp).plus(up)).finish();
365    }
366
367    // The definitions first and the reads after, which is the order every operand vector in the
368    // machine IR is in and the order `rucc_mir::defs` counts.
369    let mut operands = Vec::with_capacity(args.len() + conv.int_order.len() + 2);
370    let result = comes_back.map(|(at, class)| {
371        let reg = out.new_vreg(class);
372        operands.push(mir::Operand::write(reg, class).with(Constraint::Fixed(at)));
373        reg
374    });
375    // One list per file, because a physical register is a number and the class is what says which
376    // file it is a number in. One list would have `xmm0` blocking `rax`.
377    let spoken_for = |class: RegClass| -> Vec<PhysReg> {
378        comes_back
379            .filter(|&(_, at)| at == class)
380            .map(|(reg, _)| reg)
381            .into_iter()
382            .chain(counted.filter(|_| class == conv.int_class))
383            .chain(passed.iter().filter(|&&(_, _, at)| at == class).map(|&(_, reg, _)| reg))
384            .collect()
385    };
386    let named = spoken_for(conv.int_class);
387    for &reg in conv.int_order {
388        if !conv.preserves_int(reg) && !named.contains(&reg) {
389            operands.push(mir::Operand::write(mir::Reg::physical(reg), conv.int_class));
390        }
391    }
392    let named = spoken_for(conv.sse_class);
393    for &reg in conv.sse_order {
394        if !conv.preserves_sse(reg) && !named.contains(&reg) {
395            operands.push(mir::Operand::write(mir::Reg::physical(reg), conv.sse_class));
396        }
397    }
398    // The address in front of the arguments, because a call through one is written with the
399    // register it goes through and nothing in the operand vector is at a place a table could name.
400    // First read is a place that does not depend on the signature, which is what
401    // [`rucc_target::x86_64::Arg::Through`] is written against.
402    if let Callee::Through(reg) = callee {
403        operands.push(mir::Operand::read(reg, conv.int_class));
404    }
405    for (reg, at, class) in passed {
406        operands.push(mir::Operand::read(reg, class).with(Constraint::Fixed(at)));
407    }
408    if let Some(at) = counted {
409        let count = out.new_vreg(conv.int_class);
410        let zero = mir::Opcode::new(names.intern("x64.mov_ri_32"));
411        out.build(block, zero).def(count, conv.int_class).imm(i64::from(vectors)).finish();
412        operands.push(mir::Operand::read(count, conv.int_class).with(Constraint::Fixed(at)));
413    }
414
415    let opcode = mir::Opcode::new(names.intern(match callee {
416        Callee::Named(_) => CALL,
417        Callee::Through(_) => CALL_REG,
418    }));
419    let mut build = out.build(block, opcode);
420    if let Callee::Named(symbol) = callee {
421        build = build.symbol(symbol);
422    }
423    for operand in operands {
424        build = build.operand(operand);
425    }
426    build.finish();
427    Ok(Made { result, outgoing: places.size() })
428}
429
430/// What the pseudo for an argument of that type is called.
431///
432/// The width is in the name for the same reason it is in every other opcode here: it is what the
433/// instruction is about. Nothing encodes it, so nothing depends on it being right, but a listing
434/// that says an argument arrived and does not say how much of it did is a listing worth less.
435///
436/// Which widths there are is the question the rule set asks of a type, and not a list of its own,
437/// because it has to be the same list. An argument brought in at a width the rules have no name
438/// for is a register
439/// nothing downstream could then read, and a width the rules cover that this refuses is a
440/// function turned away for no reason. Asking one question in one place is what keeps the two
441/// answers from drifting, and an address is what they used to disagree about.
442#[must_use]
443pub fn head_of(ty: Type) -> Option<&'static str> {
444    if let Some(at) = crate::term::float_slot(ty) {
445        return Some(["x64.arg_val_f32", "x64.arg_val_f64"][at]);
446    }
447    let names = ["x64.arg_val_8", "x64.arg_val_16", "x64.arg_val_32", "x64.arg_val_64"];
448    Some(names[crate::term::slot(ty)?])
449}
450
451/// What the instruction that reads an argument of that type out of memory is called.
452///
453/// Keyed off the same two questions [`head_of`] asks and answering for the same set of types, so
454/// that a parameter this compiler can bring in from a register is one it can bring in from the
455/// caller's stack as well. A width one of them covered and the other did not would be a function
456/// turned away for where its sixth argument happened to land.
457///
458/// Reading a narrow argument at its own width and not at a word is deliberate. The caller wrote a
459/// whole word, but what it put in the part above the value is not something the convention says, so
460/// the bits this reads are exactly the bits that mean anything. That is the same thing an argument
461/// arriving in a register gets: `x64.arg_val_8` says the low byte of that register is the argument
462/// and says nothing at all about the rest of it.
463#[must_use]
464pub fn load_of(ty: Type) -> Option<&'static str> {
465    if let Some(at) = crate::term::float_slot(ty) {
466        return Some(["x64.movss_rm", "x64.movsd_rm"][at]);
467    }
468    let names = ["x64.mov_rm_8", "x64.mov_rm_16", "x64.mov_rm_32", "x64.mov_rm_64"];
469    Some(names[crate::term::slot(ty)?])
470}
471
472/// What the instruction that writes an argument of that type into memory is called.
473///
474/// The mirror of [`load_of`], keyed off the same two questions and answering for the same set of
475/// types, so that the two ends of one call agree about what travels. A type a callee can read out
476/// of the argument area and a caller cannot write into it would be a call turned away for a reason
477/// the function it calls does not have.
478///
479/// Writing a narrow argument at its own width leaves whatever was already in the rest of the word.
480/// That is allowed, and it is what [`load_of`] is written against: the convention does not say what
481/// is above the value, so the callee reads only the bits that mean anything and neither end has to
482/// agree about the rest.
483#[must_use]
484pub fn store_of(ty: Type) -> Option<&'static str> {
485    if let Some(at) = crate::term::float_slot(ty) {
486        return Some(["x64.movss_mr", "x64.movsd_mr"][at]);
487    }
488    let names = ["x64.mov_mr_8", "x64.mov_mr_16", "x64.mov_mr_32", "x64.mov_mr_64"];
489    Some(names[crate::term::slot(ty)?])
490}
491
492#[cfg(test)]
493mod tests {
494    use rucc_target::x86_64::{REGS, SYSV, WIN64};
495
496    use super::*;
497
498    /// The parameters of a function under a convention, as machine IR text.
499    fn bind(params: &[Type], conv: &CallRegs) -> String {
500        let mut names = Interner::new();
501        let mut out = mir::Func::new(names.intern("f"));
502        let block = out.create_block();
503        entry(&mut out, block, params, conv, &mut names).expect("every parameter arrives");
504        mir::print_func(&out, &names, &REGS)
505    }
506
507    #[test]
508    fn the_first_arguments_arrive_where_the_convention_puts_them() {
509        let i32 = Type::int(32);
510        assert_eq!(
511            bind(&[i32, i32, Type::int(64)], &SYSV),
512            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_32\n    \
513             %1:gpr($rsi) = x64.arg_val_32\n    %2:gpr($rdx) = x64.arg_val_64\n}\n"
514        );
515    }
516
517    #[test]
518    fn the_other_convention_puts_the_same_arguments_somewhere_else() {
519        // The first argument is in `rcx` here and in `rdi` above, which is the difference that
520        // makes a SysV binary calling a Windows one read the wrong value rather than fail.
521        let i64 = Type::int(64);
522        assert_eq!(
523            bind(&[i64, i64], &WIN64),
524            "mfunc @f {\nblock0:\n    %0:gpr($rcx) = x64.arg_val_64\n    \
525             %1:gpr($rdx) = x64.arg_val_64\n}\n"
526        );
527    }
528
529    /// The parameters of a function under a convention, and what each of the ones that arrived in
530    /// memory is waiting on.
531    fn arrive(params: &[Type], conv: &CallRegs) -> (String, Vec<u32>) {
532        let mut names = Interner::new();
533        let mut out = mir::Func::new(names.intern("f"));
534        let block = out.create_block();
535        let arrived = entry(&mut out, block, params, conv, &mut names).expect("every parameter");
536        let up = arrived.stack.iter().map(|&(_, up)| up).collect();
537        (mir::print_func(&out, &names, &REGS), up)
538    }
539
540    #[test]
541    fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
542        let (text, up) = arrive(&[Type::int(64); 7], &SYSV);
543
544        // Six of them got registers and the seventh did not, so the seventh is a load rather than
545        // a pseudo. It reads through the stack pointer with nothing in its displacement, because
546        // where the caller's argument area is from in here is a distance into a frame that does
547        // not exist yet, and it is at the bottom of that area because it is the first one in it.
548        assert_eq!(up, [0]);
549        assert!(text.contains("%6:gpr = x64.mov_rm_64 [$rsp]"), "{text}");
550        assert_eq!(text.matches("x64.arg_val_64").count(), 6, "{text}");
551    }
552
553    #[test]
554    fn the_other_convention_runs_out_of_registers_three_arguments_earlier() {
555        let (text, up) = arrive(&[Type::int(64); 7], &WIN64);
556
557        // Windows passes four integers in registers and reserves thirty two bytes below the call
558        // whether they are used or not, so the fifth argument is not at the bottom of the argument
559        // area but above the shadow space, and the three after it follow it a word at a time.
560        assert_eq!(up, [32, 40, 48]);
561        assert_eq!(text.matches("x64.arg_val_64").count(), 4, "{text}");
562        assert!(text.contains("%4:gpr = x64.mov_rm_64 [$rsp]"), "{text}");
563    }
564
565    /// A parameter narrower than a word is read at its own width rather than at a word, and one in
566    /// the other register file is read with the other file's instruction. Both are the same list
567    /// [`head_of`] answers from, which is what stops a function being turned away for the width of
568    /// its seventh argument alone.
569    #[test]
570    fn what_a_stack_argument_is_read_with_is_its_own_width_and_its_own_file() {
571        let f32 = Type::float(rucc_ir::Float::F32);
572        let params = [Type::int(64), Type::int(64), Type::int(64), Type::int(64), Type::int(8)];
573        let (text, up) = arrive(&params, &WIN64);
574        assert_eq!(up, [32]);
575        assert!(text.contains("x64.mov_rm_8 [$rsp]"), "{text}");
576
577        let floats = [f32; 5];
578        let (text, up) = arrive(&floats, &WIN64);
579        assert_eq!(up, [32]);
580        assert!(text.contains("%4:xmm = x64.movss_rm [$rsp]"), "{text}");
581    }
582
583    /// Every type a parameter can arrive in a register at is one it can be read from memory at.
584    /// The two lists are keyed off the same two questions so that they cannot drift, and this is
585    /// what says so: a width one covered and the other did not would be a function turned away for
586    /// where its arguments happened to land rather than for anything about it.
587    #[test]
588    fn the_two_lists_of_widths_answer_for_the_same_types() {
589        let types = [
590            Type::int(1),
591            Type::int(8),
592            Type::int(16),
593            Type::int(32),
594            Type::int(64),
595            Type::int(128),
596            Type::PTR,
597            Type::float(rucc_ir::Float::F32),
598            Type::float(rucc_ir::Float::F64),
599            Type::float(rucc_ir::Float::F80),
600        ];
601        for ty in types {
602            assert_eq!(head_of(ty).is_some(), load_of(ty).is_some(), "{ty:?}");
603        }
604    }
605
606    /// A float arrives in the other file, and the two files are counted apart on SysV: the
607    /// integer here is the first integer argument and the float is the first float one, so they
608    /// are in `rdi` and `xmm0` rather than in the first and second of anything.
609    #[test]
610    fn a_float_arrives_in_a_vector_register_and_is_counted_apart_from_the_integers() {
611        let f32 = Type::float(rucc_ir::Float::F32);
612        let f64 = Type::float(rucc_ir::Float::F64);
613        assert_eq!(
614            bind(&[Type::int(32), f64, f32], &SYSV),
615            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_32\n    \
616             %1:xmm($xmm0) = x64.arg_val_f64\n    %2:xmm($xmm1) = x64.arg_val_f32\n}\n"
617        );
618    }
619
620    /// Windows counts the two files together, so the same three arguments land in different
621    /// registers: the float is the second argument and takes the second vector register rather
622    /// than the first, which is the difference that makes a mismatched call read the wrong value.
623    #[test]
624    fn the_other_convention_counts_the_two_files_as_one_run_of_positions() {
625        let f64 = Type::float(rucc_ir::Float::F64);
626        assert_eq!(
627            bind(&[Type::int(32), f64, Type::int(64)], &WIN64),
628            "mfunc @f {\nblock0:\n    %0:gpr($rcx) = x64.arg_val_32\n    \
629             %1:xmm($xmm1) = x64.arg_val_f64\n    %2:gpr($r8) = x64.arg_val_64\n}\n"
630        );
631    }
632
633    /// A `long double` is in neither file, and what it is turned away for says so rather than
634    /// calling eighty bits a width no register holds. The x87 stack is a register file this
635    /// compiler does not allocate in and has no instruction for.
636    #[test]
637    fn a_long_double_is_reported_as_the_x87_stack_it_travels_on() {
638        let mut names = Interner::new();
639        let mut out = mir::Func::new(names.intern("f"));
640        let block = out.create_block();
641        let params = [Type::int(32), Type::float(rucc_ir::Float::F80)];
642        assert_eq!(entry(&mut out, block, &params, &SYSV, &mut names), Err((1, Missing::OnX87)));
643        assert_eq!(
644            make(&[], Some(Type::float(rucc_ir::Float::F80)), false, &SYSV).2,
645            Err(Refused { argument: None, missing: Missing::OnX87 })
646        );
647    }
648
649    /// One call to `g`, with a register for each argument arriving in the block that makes it.
650    fn make(
651        args: &[Type],
652        returns: Option<Type>,
653        variadic: bool,
654        conv: &CallRegs,
655    ) -> (Interner, mir::Func, Result<Made, Refused>) {
656        let mut names = Interner::new();
657        let mut out = mir::Func::new(names.intern("f"));
658        let block = out.create_block();
659        let passed: Vec<(Type, mir::Reg)> =
660            args.iter().map(|&ty| (ty, out.append_param(block, class_of(ty, conv)))).collect();
661        let callee = Callee::Named(names.intern("g"));
662        let what = Calling { callee, args: &passed, returns, variadic };
663        let made = call(&mut out, block, &what, conv, &mut names);
664        (names, out, made)
665    }
666
667    /// What the call in that function reads and writes, by register name, in the order the
668    /// operands are in.
669    fn operands(func: &mir::Func) -> (Vec<String>, Vec<String>) {
670        let block = func.entry().expect("a function with a block in it");
671        let call = func.terminator(block).expect("the call is the last thing in the block");
672        let name = |operand: &mir::Operand| match (operand.reg.phys(), operand.constraint) {
673            (Some(reg), _) | (None, Constraint::Fixed(reg)) => {
674                REGS.name(operand.class, reg).expect("a register the file describes").to_string()
675            }
676            _ => format!("{:?}", operand.reg),
677        };
678        let mut written = Vec::new();
679        let mut read = Vec::new();
680        for operand in &func[func[call].operands] {
681            let into = if operand.role == mir::Role::Use { &mut read } else { &mut written };
682            into.push(name(operand));
683        }
684        (written, read)
685    }
686
687    #[test]
688    fn a_call_passes_its_arguments_where_the_convention_puts_them() {
689        let i32 = Type::int(32);
690        let (_, func, made) = make(&[i32, i32, i32], None, false, &SYSV);
691        assert_eq!(made.expect("three integers all fit in registers").result, None);
692        assert_eq!(operands(&func).1, ["rdi", "rsi", "rdx"]);
693    }
694
695    #[test]
696    fn the_other_convention_passes_the_same_arguments_somewhere_else() {
697        let i64 = Type::int(64);
698        let (_, func, made) = make(&[i64, i64], None, false, &WIN64);
699        // Thirty two bytes of stack for a call that passes nothing on the stack, which is what
700        // Windows asks a caller to leave the callee whether the callee uses it or not.
701        assert_eq!(made.expect("two integers fit in registers").outgoing, 32);
702        assert_eq!(operands(&func).1, ["rcx", "rdx"]);
703    }
704
705    #[test]
706    fn what_a_call_gives_back_comes_out_of_the_register_the_convention_returns_in() {
707        let (names, func, made) = make(&[], Some(Type::int(32)), false, &SYSV);
708        let result = made.expect("an integer comes back").result.expect("in a register");
709        // The first thing written is the result, and it is the only thing written that is a value
710        // rather than a register the callee destroyed.
711        assert_eq!(operands(&func).0.first().map(String::as_str), Some("rax"));
712        assert_eq!(func.class_of(result), Some(SYSV.int_class));
713        assert!(mir::print_func(&func, &names, &REGS).contains("x64.call"));
714    }
715
716    #[test]
717    fn every_register_the_callee_may_destroy_is_written_by_the_call() {
718        let (_, func, _) = make(&[Type::int(64)], Some(Type::int(64)), false, &SYSV);
719        let (written, read) = operands(&func);
720        // The callee saved registers are not here, because a value in one of those survives a
721        // call and that is the whole difference between the two halves of the convention.
722        for saved in ["rbx", "rbp", "r12", "r13", "r14", "r15"] {
723            assert!(!written.contains(&saved.to_string()), "{saved} survives a call");
724        }
725        // Every other integer register is, once. The two named ones are named by the result and
726        // by the argument instead, and naming one twice would be blocking it twice.
727        for destroyed in ["rcx", "rdx", "rsi", "r8", "r9", "r10", "r11"] {
728            let count = written.iter().filter(|name| *name == destroyed).count();
729            assert_eq!(count, 1, "{destroyed} is destroyed by a call and is written {count} times");
730        }
731        assert_eq!(written.iter().filter(|name| *name == "rax").count(), 1);
732        assert_eq!(read, ["rdi"]);
733        // The vector registers are all destroyed on SysV, and they are in the other class.
734        assert!(written.contains(&"xmm0".to_string()));
735    }
736
737    #[test]
738    fn a_variadic_call_says_how_many_vector_registers_it_passed_arguments_in() {
739        let (names, func, made) = make(&[Type::int(64)], None, true, &SYSV);
740        made.expect("an integer argument to a variadic callee");
741        let (_, read) = operands(&func);
742        // Zero of them here, and `al` is where a SysV callee looks for it. Leaving whatever was in
743        // the register there would make a callee that saves its vector registers save ones it was
744        // never given.
745        assert_eq!(read, ["rdi", "rax"]);
746        assert_eq!(
747            mir::print_func(&func, &names, &REGS).lines().nth(2),
748            Some("    %1:gpr = x64.mov_ri_32 0")
749        );
750
751        // Two of them here, which is the number that decides how much of the register save area a
752        // callee like `printf` fills in. A count of zero with a float in `xmm0` would be a callee
753        // reading its first `%f` out of a register nothing wrote.
754        let f64 = Type::float(rucc_ir::Float::F64);
755        let (names, func, made) = make(&[Type::int(64), f64, f64], None, true, &SYSV);
756        made.expect("one integer and two floats all fit in registers");
757        assert_eq!(operands(&func).1, ["rdi", "xmm0", "xmm1", "rax"]);
758        assert!(mir::print_func(&func, &names, &REGS).contains("x64.mov_ri_32 2"));
759    }
760
761    /// Windows passes a float to a variadic callee in both files at once, and which arguments are
762    /// the ones the signature does not name is not something a call carries, so it is turned down
763    /// rather than passed in one file and read from the other.
764    #[test]
765    fn a_float_passed_to_a_variadic_callee_on_windows_is_reported() {
766        let f64 = Type::float(rucc_ir::Float::F64);
767        assert_eq!(
768            make(&[Type::int(32), f64], None, true, &WIN64).2,
769            Err(Refused { argument: Some(1), missing: Missing::InBothFiles })
770        );
771        // The same call to a callee whose signature names both arguments is fine, because there is
772        // no second copy to make.
773        assert!(make(&[Type::int(32), f64], None, false, &WIN64).2.is_ok());
774    }
775
776    #[test]
777    fn a_call_through_an_address_reads_it_in_front_of_the_arguments() {
778        let i32 = Type::int(32);
779        let mut names = Interner::new();
780        let mut out = mir::Func::new(names.intern("f"));
781        let block = out.create_block();
782        let address = out.append_param(block, SYSV.int_class);
783        let passed = vec![(i32, out.append_param(block, SYSV.int_class))];
784        let what = Calling {
785            callee: Callee::Through(address),
786            args: &passed,
787            returns: Some(i32),
788            variadic: false,
789        };
790        call(&mut out, block, &what, &SYSV, &mut names).expect("one integer fits in a register");
791
792        // The address is the first thing read and the arguments follow it, which is the order the
793        // assembler counts on, and it is in no particular register because every register a call
794        // could insist on is one the call has already spoken for.
795        let text = mir::print_func(&out, &names, &REGS);
796        assert!(text.contains("= x64.call_reg %0, %1($rdi)\n"), "{text}");
797        assert!(!text.contains("@g"), "a call through an address names nobody: {text}");
798    }
799
800    #[test]
801    fn a_call_with_no_register_left_writes_the_argument_into_the_outgoing_area() {
802        let i64 = Type::int(64);
803        let (names, func, made) = make(&[i64; 7], None, false, &SYSV);
804        let made = made.expect("the seventh goes to memory");
805
806        // At the stack pointer, because the outgoing area is at the bottom of the frame, and in
807        // front of the call rather than as an operand of it.
808        let text = mir::print_func(&func, &names, &REGS);
809        assert!(text.contains("x64.mov_mr_64 %6, [$rsp]\n"), "{text}");
810        let store = text.find("x64.mov_mr_64").expect("the store");
811        assert!(store < text.find("x64.call").expect("the call"), "{text}");
812        // One word of it, which is what the frame has to reserve for this call.
813        assert_eq!(made.outgoing, 8);
814    }
815
816    /// The other convention runs out three arguments earlier and starts its argument area above the
817    /// shadow space it also has to reserve, and both of those are what `Places` already said.
818    #[test]
819    fn where_the_outgoing_area_starts_is_the_convention_s_answer() {
820        let i64 = Type::int(64);
821        let (names, func, made) = make(&[i64; 7], None, false, &WIN64);
822        assert_eq!(made.expect("the last three go to memory").outgoing, 56);
823
824        // Thirty two bytes of shadow space first, which the caller writes nothing into and the
825        // callee owns, and the fifth argument above it.
826        let text = mir::print_func(&func, &names, &REGS);
827        assert!(text.contains("x64.mov_mr_64 %4, [$rsp + 32]\n"), "{text}");
828        assert!(text.contains("x64.mov_mr_64 %5, [$rsp + 40]\n"), "{text}");
829        assert!(text.contains("x64.mov_mr_64 %6, [$rsp + 48]\n"), "{text}");
830    }
831
832    /// What a stack argument is written with is its own width and its own register file, matching
833    /// what the callee reads it back with.
834    #[test]
835    fn a_narrow_or_floating_argument_keeps_its_own_store() {
836        let i64 = Type::int(64);
837        let narrow = [i64, i64, i64, i64, i64, i64, Type::int(8)];
838        let (names, func, made) = make(&narrow, None, false, &SYSV);
839        made.expect("the seventh goes to memory");
840        let text = mir::print_func(&func, &names, &REGS);
841        assert!(text.contains("x64.mov_mr_8 %6, [$rsp]\n"), "{text}");
842
843        let f32 = Type::float(rucc_ir::Float::F32);
844        let (names, func, made) = make(&[f32; 9], None, false, &SYSV);
845        made.expect("the ninth goes to memory");
846        let text = mir::print_func(&func, &names, &REGS);
847        assert!(text.contains("x64.movss_mr %8, [$rsp]\n"), "{text}");
848    }
849
850    /// The count a SysV variadic callee reads is a count of registers, so an argument that went to
851    /// memory instead is not in it.
852    #[test]
853    fn an_argument_in_memory_is_not_counted_as_a_vector_register() {
854        let f64 = Type::float(rucc_ir::Float::F64);
855        let (names, func, made) = make(&[f64; 9], None, true, &SYSV);
856        made.expect("the ninth goes to memory");
857        let text = mir::print_func(&func, &names, &REGS);
858        assert!(text.contains("x64.mov_ri_32 8\n"), "eight registers, not nine: {text}");
859    }
860
861    /// The two lists of widths answer for the same set of types, so that a value the callee can
862    /// read out of the argument area is one the caller can write into it.
863    #[test]
864    fn what_can_be_read_can_be_written() {
865        let types = [
866            Type::int(1),
867            Type::int(8),
868            Type::int(16),
869            Type::int(32),
870            Type::int(64),
871            Type::int(128),
872            Type::PTR,
873            Type::float(rucc_ir::Float::F32),
874            Type::float(rucc_ir::Float::F64),
875            Type::float(rucc_ir::Float::F80),
876        ];
877        for ty in types {
878            assert_eq!(load_of(ty).is_some(), store_of(ty).is_some(), "{ty:?}");
879        }
880    }
881
882    /// A float travels in the other file at both ends of a call, and the register it comes back in
883    /// is the first of that file rather than the first of the other one.
884    #[test]
885    fn a_call_passes_and_returns_a_float_in_a_vector_register() {
886        let f64 = Type::float(rucc_ir::Float::F64);
887        let (_, func, made) = make(&[Type::int(32), f64], Some(f64), false, &SYSV);
888        let result = made.expect("an integer and a float both fit in registers");
889        let (written, read) = operands(&func);
890        assert_eq!(read, ["rdi", "xmm0"]);
891        assert_eq!(written.first().map(String::as_str), Some("xmm0"));
892        assert_eq!(func.class_of(result.result.expect("a float comes back")), Some(SYSV.sse_class));
893        // Written once, because the register the result comes back in is already blocked by being
894        // named and a clobber that repeated it would be blocking it twice. `rax` is a clobber here
895        // rather than the result, which is the same register number in the other file and is the
896        // whole reason the two lists are counted apart.
897        assert_eq!(written.iter().filter(|name| *name == "xmm0").count(), 1);
898        assert!(written.contains(&"rax".to_string()));
899    }
900
901    #[test]
902    fn a_call_at_a_width_no_register_holds_is_reported_on_either_side() {
903        let i128 = Type::int(128);
904        assert_eq!(
905            make(&[i128], None, false, &SYSV).2,
906            Err(Refused { argument: Some(0), missing: Missing::Width })
907        );
908        assert_eq!(
909            make(&[], Some(i128), false, &SYSV).2,
910            Err(Refused { argument: None, missing: Missing::Width })
911        );
912    }
913
914    #[test]
915    fn an_argument_wider_than_a_register_has_no_name() {
916        assert_eq!(head_of(Type::int(128)), None);
917        assert_eq!(head_of(Type::int(8)), Some("x64.arg_val_8"));
918        assert_eq!(head_of(Type::int(64)), Some("x64.arg_val_64"));
919    }
920
921    /// An address arrives in a general purpose register like any other integer of its width, and
922    /// used to be turned away here as a width no register holds, which is what issue 274 is.
923    /// `int g(char *s)` is the smallest program that was.
924    #[test]
925    fn an_address_arrives_in_a_register_like_the_integer_it_is() {
926        assert_eq!(head_of(Type::PTR), Some("x64.arg_val_64"));
927        assert_eq!(
928            bind(&[Type::PTR], &SYSV),
929            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_64\n}\n"
930        );
931        // And it travels the same way at a call, on both sides of one.
932        assert!(make(&[Type::PTR], Some(Type::PTR), false, &SYSV).2.is_ok());
933    }
934}