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 nearly all plain
14//! scalars: an aggregate has been split into the pieces it travels in, and a return through memory
15//! is an 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//! The one parameter that is not a scalar is an aggregate the classification put in the argument
20//! area whole, which is [`rucc_ir::Abi::ByVal`]. The IR calls it a pointer, because a pointer is
21//! what an instruction reading it has to have, and the convention says the bytes travel and the
22//! pointer does not. So this is the one place that reads what the classification said rather than
23//! only the type, on both sides of the call, and the two sides are the two halves of one copy.
24//!
25//! # What it writes
26//!
27//! One `x64.arg_val_*` per parameter that arrived in a register, at the top of the entry block,
28//! each defining a fresh register constrained to the one the argument arrived in. They encode to
29//! nothing. The point of them is that a parameter has to be defined somewhere for the allocator to
30//! have anything to move, and the entry block cannot define it as a block parameter: there is no
31//! edge into the entry block for the move to go on, which is what `rucc_regalloc::rewrite` asserts.
32//!
33//! What the allocator does with them is the whole of the argument sequence. A parameter that is
34//! read where it arrived costs nothing, and one that is not gets a copy, which is the same
35//! bargain the return already makes and is decided by the same code.
36//!
37//! A parameter whose bytes travelled is the exception to all of that. Its bytes are already in
38//! this function, at a place in the caller's argument area the same walk gives, so nothing is
39//! brought in at all: what the parameter is is where they are, and that is one `lea`. It waits on
40//! the frame the way the loads below it do, and for the same reason.
41//!
42//! A parameter past the last register arrived in the caller's memory rather than in a register, so
43//! it is a load and not a pseudo, and it is a real instruction that encodes to real bytes. How far
44//! up the caller's argument area it is is a number [`rucc_target::Places`] answers here, but where
45//! that area is from inside this function is a distance into a frame, and no frame exists until
46//! after allocation. So the load is written with nothing in its displacement, which of the two
47//! registers it reads through is left to be settled too, and both are filled in by [`crate::finish`]
48//! out of [`crate::frame::Frame::incoming`]. That is the same bargain an `alloca` already makes,
49//! for the same reason and in the same two places.
50//!
51//! # A call
52//!
53//! The same reasoning the other way round, and one instruction rather than several. `x64.call`
54//! and `x64.call_reg` are the only opcodes in the description whose operand vector is empty
55//! there, because nothing about a call's operands is the same from one call to the next, so they
56//! are built here: one read per argument constrained to the register the convention passes it in,
57//! one definition for the value that comes back constrained to the register it comes back in, and
58//! one definition per register the convention does not preserve.
59//!
60//! A call through an address has one operand more, which is the address, and it is the one
61//! operand of a call that is a fact about the instruction rather than about the signature. It
62//! goes in front of the arguments, because the assembler has to find it and an index into a
63//! vector whose length depends on the convention is not a way of finding anything.
64//!
65//! Those last ones are the clobbers, and they are the whole of what the allocator has to know
66//! about a call besides where the values go. Each is a definition of the physical register itself
67//! rather than of a value, since there is no value: it says the register is written here, which
68//! is exactly what stops the allocator from leaving something in one across the call. A register
69//! an argument or the result already names is not repeated, because naming it once already blocks
70//! it for the length of the instruction, which is all a clobber does.
71//!
72//! An argument past the last register the convention has for it is a store into the outgoing area
73//! rather than an operand of the call, written in front of the call in the same block. Where that
74//! area is does not have to wait for the frame the way the incoming one does, because the outgoing
75//! area is at the bottom of the frame and the bottom of the frame is where the stack pointer is:
76//! that is the whole reason the frame puts it there, since it is where the callee will look. So the
77//! offset [`rucc_target::Places`] gives back is the offset the store is written with.
78//!
79//! An object passed by value in memory is the same thing again and a copy rather than a store. The
80//! caller owes the callee a copy it is free to write to, which is what makes a C call by value
81//! different from passing a pointer the callee must not keep, and the argument area is where the
82//! convention says that copy goes. So the bytes are read out of the object and written into the
83//! area a word at a time, in front of the call, with the words chosen by the same function that
84//! chooses them for a `memcpy`. An object with more words than that unrolls to is turned down,
85//! because the copy it wants is a call to the runtime and one call cannot be built inside another.
86//!
87//! The call still reports how many bytes it needed, because the frame reserves as many as the
88//! widest call in the function asked for and cannot know that until every call has been seen.
89
90use rucc_base::{Interner, Symbol};
91use rucc_ir::{Abi, Param, Type};
92use rucc_mir as mir;
93use rucc_target::x86_64;
94use rucc_target::{CallRegs, Constraint, PhysReg, Places, RegClass, Where};
95
96use crate::varargs::Area;
97
98/// Why a parameter could not be brought in.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum Missing {
101    /// It travels on the x87 stack, which is a `long double` and nothing else. That stack is a
102    /// third register file, it is not one the allocator has, and no instruction in the
103    /// description touches it.
104    OnX87,
105    /// It is a float passed to a callee that takes arguments beyond the ones its signature names,
106    /// on a convention that puts such a float in a vector register and in the general purpose
107    /// register at the same position at once. Which arguments are the ones beyond the signature is
108    /// what decides whether the second copy is needed, and a call does not carry that yet.
109    InBothFiles,
110    /// It is a width no pseudo covers, which is anything a machine register does not hold.
111    Width,
112    /// It is a value that comes back in more registers than the convention returns in. A structure
113    /// of at most sixteen bytes comes back in up to two, which is as many as SysV has, and a
114    /// convention with fewer of them returns such a structure through a hidden pointer instead. So
115    /// this is what a signature the classification did not produce would get.
116    NoRoom,
117    /// It is an object whose bytes travel in the argument area and there are more of them than a
118    /// copy a word at a time is worth. Such a copy belongs in a call to the runtime, and the place
119    /// this is decided is in the middle of building a call, where another one cannot go.
120    TooBig,
121}
122
123impl Missing {
124    /// What it says when a function could not be compiled because of it.
125    ///
126    /// Worded so that it reads the same about a value arriving and a value being passed, since
127    /// the two are the same fact seen from the two ends of one call.
128    #[must_use]
129    pub fn why(self) -> &'static str {
130        match self {
131            Missing::OnX87 => "is on the x87 stack",
132            Missing::InBothFiles => "is a float passed to a variadic callee on this convention",
133            Missing::Width => "is a width no argument register holds",
134            Missing::NoRoom => "takes more registers than this convention has for it",
135            Missing::TooBig => "is more bytes than a copy into the argument area unrolls to",
136        }
137    }
138}
139
140/// Which register file a value of that type travels in.
141///
142/// The whole of what the two files mean to this module. A float is in the vector one and
143/// everything else is in the general purpose one, which is what both of this machine's conventions
144/// say. A `long double` is in neither and what its register holds is an address, which is a general
145/// purpose value like every other address, so it answers with the other file rather than with the
146/// one its type would suggest.
147fn class_of(ty: Type, conv: &CallRegs) -> RegClass {
148    if ty.is_float() && !on_the_stack(ty) { conv.sse_class } else { conv.int_class }
149}
150
151/// Whether a value of that type travels as bytes in the argument area because of what it is.
152///
153/// One type does, and it is the `long double`. SysV classifies it X87 and X87UP, which is the
154/// classification that means memory, so it goes where a structure the classification put in memory
155/// goes and what the two ends pass is the address of the bytes. That is not a decision about
156/// registers running out: a `long double` travels in the argument area when it is the only argument
157/// there is.
158///
159/// Sixteen bytes aligned to sixteen, which is what the psABI says the type takes and is the same
160/// number [`crate::lower`] gives one in the frame, so a value being passed and a value being worked
161/// on are the same shape of object in two places.
162#[must_use]
163pub fn on_the_stack(ty: Type) -> bool {
164    ty.is_float() && ty.is_scalar() && ty.bits() == 80
165}
166
167/// How much room one takes in the argument area, as a size and an alignment.
168pub(crate) const X87_AREA: (u32, u32) = (16, 16);
169
170/// Why a value of that type cannot travel at all, or nothing if it can.
171///
172/// The width question and the file question in one place, so that the two ends of a call give the
173/// same answer about the same type, and so that a `return` this cannot make says the same thing
174/// about a type as the call that would have received it.
175///
176/// A `long double` is not one of them any more when it is an argument, since an argument of that
177/// type travels as bytes and [`on_the_stack`] is what says so before this is asked. What is left
178/// here is the value that comes back, because coming back is the one direction where it is not
179/// bytes: it arrives in `st(0)`, which is a register file this cannot name.
180#[must_use]
181pub fn refuses(ty: Type) -> Option<Missing> {
182    if head_of(ty).is_some() {
183        return None;
184    }
185    // A `long double` is the one type here that is in neither of the two files. Saying so is worth
186    // more than calling it a width, because eighty bits is a width this machine computes in and
187    // the file it computes in is what actually stands in the way.
188    if on_the_stack(ty) {
189        return Some(Missing::OnX87);
190    }
191    Some(Missing::Width)
192}
193
194/// What a function's parameters came to.
195#[derive(Debug, Default, Clone, PartialEq, Eq)]
196pub struct Arrived {
197    /// The register each parameter is in, in the order the parameters were given, so the caller can
198    /// bind each IR parameter to the one at its position.
199    pub regs: Vec<mir::Reg>,
200    /// The loads that read a parameter out of the caller's argument area, and how far up that area
201    /// each of them reads.
202    ///
203    /// Empty for almost every function, because almost every function has few enough parameters to
204    /// have been handed all of them in registers. The distance is from the bottom of the caller's
205    /// argument area, which is somewhere [`crate::finish`] works out and this cannot.
206    pub stack: Vec<(mir::Inst, u32)>,
207    /// How many general purpose argument registers the parameters took, and how many vector ones.
208    ///
209    /// Nothing about an ordinary function needs this. A variadic one does: the first argument its
210    /// signature does not name is the one after the last it does, so where each of the two walks
211    /// stopped is where `va_start` has to say the next argument begins.
212    pub took: (usize, usize),
213    /// How many bytes of the caller's argument area the parameters took, which is where the first
214    /// argument the signature does not name begins for the same reason.
215    pub used: u32,
216    /// The argument registers left over for the arguments the signature does not name, as the
217    /// register each was bound into and how far up the save area its slot is.
218    ///
219    /// Empty unless a save area was asked for. The ones a named parameter took are not here,
220    /// because their slots are behind where `va_start` sets the two offsets and nothing ever reads
221    /// them, so writing them would be fourteen stores where six are wanted.
222    pub spare: Vec<(mir::Reg, RegClass, u32)>,
223}
224
225/// Binds a function's parameters to where the convention says they arrive.
226///
227/// # Errors
228///
229/// The first parameter this cannot bring in, and why. A function with one is reported rather
230/// than compiled, because the alternative is a function that reads an argument from wherever the
231/// last one happened to leave a register.
232pub fn entry(
233    out: &mut mir::Func,
234    block: mir::Block,
235    params: &[Param],
236    conv: &CallRegs,
237    names: &mut Interner,
238    save: Option<Area>,
239) -> Result<Arrived, (usize, Missing)> {
240    // Where everything is, worked out before anything is written, both so that a parameter this
241    // cannot bring in stops the function before half of one is built and so that the two loops
242    // below can be two loops. Asking for the place of a parameter that cannot be brought in is
243    // still done, because every place after it depends on it and a reader stepping through this
244    // should see the same numbers a working version would.
245    let mut places = Places::new(conv);
246    let mut where_from = Vec::with_capacity(params.len());
247    for (index, &Param { ty, abi }) in params.iter().enumerate() {
248        // A structure the classification put in the argument area arrived as bytes, and the
249        // parameter the IR sees is a pointer to them. So there is nothing to bring in: the bytes
250        // are already in this function's frame, and what the pointer holds is where they are.
251        if let Abi::ByVal { size, align } = abi {
252            let size = u32::try_from(size).map_err(|_| (index, Missing::TooBig))?;
253            where_from.push((ty, places.on_stack(size, align), abi));
254            continue;
255        }
256        // And an eighty bit float, which arrives the same way for the same reason and is told
257        // apart only by the classification having said nothing about it: the front end passes it
258        // as a value of its own type, and it is this that knows the type is one that travels as
259        // bytes. What arrives is the address of those bytes, which is what an object in the
260        // argument area always hands over.
261        if on_the_stack(ty) {
262            let (size, align) = X87_AREA;
263            where_from.push((
264                ty,
265                places.on_stack(size, align),
266                Abi::ByVal { size: size.into(), align },
267            ));
268            continue;
269        }
270        let at = if ty.is_float() { places.float() } else { places.integer() };
271        if let Some(missing) = refuses(ty) {
272            return Err((index, missing));
273        }
274        where_from.push((ty, at, abi));
275    }
276    let mut arrived = Arrived {
277        regs: Vec::with_capacity(params.len()),
278        took: (places.integers(), places.floats()),
279        used: places.size(),
280        ..Arrived::default()
281    };
282
283    // Every pseudo first and everything else after, which is not a preference. A pseudo says a
284    // register holds an argument and defines nothing before it, so as far as the allocator can see
285    // the register was dead until then and is free to be used as a scratch. That is true of a
286    // register no pseudo has named yet, and it stops being true the moment one does. Anything that
287    // needs a scratch has to come after all of them, and a load out of the caller's stack needs one
288    // for the value it loads.
289    for (index, &(ty, at, _)) in where_from.iter().enumerate() {
290        let class = class_of(ty, conv);
291        let reg = out.new_vreg(class);
292        arrived.regs.push(reg);
293        let Where::Reg(arrived_in) = at else { continue };
294        let head = head_of(ty).ok_or((index, Missing::Width))?;
295        let opcode = mir::Opcode::new(names.intern(head));
296        let operand = mir::Operand::write(reg, class).with(Constraint::Fixed(arrived_in));
297        out.build(block, opcode).operand(operand).finish();
298    }
299    if let Some(area) = save {
300        arrived.spare = spare(out, block, conv, names, area, arrived.took);
301    }
302
303    let lea = format!("{}{}", crate::lower::PREFIX, x86_64::FRAME.lea);
304    // The stack pointer is written down as the register to read through because it is the one that
305    // reaches the caller's stack in almost every function, and a realigned frame is the exception
306    // that [`crate::finish`] rewrites. Putting something here rather than nothing keeps the
307    // instruction printable and verifiable in between.
308    for (index, &(ty, at, abi)) in where_from.iter().enumerate() {
309        let Where::Stack(up) = at else { continue };
310        let class = class_of(ty, conv);
311        // The bytes of an object that travelled as bytes are read by whatever reads the parameter,
312        // and what the parameter is is their address, so this takes the address rather than a
313        // value out of it. Everything else about it is the load's, including the two fields
314        // [`crate::finish`] fills in, because where the caller's argument area is is the same
315        // question for both.
316        let name = match abi {
317            Abi::ByVal { .. } => lea.as_str(),
318            _ => load_of(ty).ok_or((index, Missing::Width))?,
319        };
320        let opcode = mir::Opcode::new(names.intern(name));
321        let sp = mir::Operand::read(mir::Reg::physical(conv.stack_pointer), conv.int_class);
322        let made =
323            out.build(block, opcode).def(arrived.regs[index], class).mem(mir::Mem::at(sp)).finish();
324        arrived.stack.push((made, up));
325    }
326    Ok(arrived)
327}
328
329/// Binds the argument registers no parameter the signature names took, which are the ones the
330/// arguments it does not name arrived in.
331///
332/// One pseudo each and nothing else, for the reason the loop above them gives: what these do is say
333/// the register holds something, and the stores that put it in the save area are written by
334/// [`crate::lower`] once it has an address to store to, which is after every pseudo in the block.
335fn spare(
336    out: &mut mir::Func,
337    block: mir::Block,
338    conv: &CallRegs,
339    names: &mut Interner,
340    area: Area,
341    took: (usize, usize),
342) -> Vec<(mir::Reg, RegClass, u32)> {
343    let word = Type::int(64);
344    let double = Type::float(rucc_ir::Float::F64);
345    let files = [(conv.int_args, took.0, word, false), (conv.sse_args, took.1, double, true)];
346    let mut spare = Vec::new();
347    for (regs, taken, ty, float) in files {
348        let Some(head) = head_of(ty) else { continue };
349        let class = class_of(ty, conv);
350        for (index, &arrived_in) in regs.iter().enumerate().skip(taken) {
351            let reg = out.new_vreg(class);
352            let opcode = mir::Opcode::new(names.intern(head));
353            let operand = mir::Operand::write(reg, class).with(Constraint::Fixed(arrived_in));
354            out.build(block, opcode).operand(operand).finish();
355            let at = area.starts_at(float) + area.stride(float) * u32::try_from(index).unwrap_or(0);
356            spare.push((reg, class, at));
357        }
358    }
359    spare
360}
361
362/// What the instruction that calls a name is called.
363///
364/// Here rather than in a rule for the same reason the arguments are: a rule pattern sees one term
365/// and a call's operands are whatever the signature made them, so no pattern could name them.
366pub const CALL: &str = "x64.call";
367
368/// What the instruction that calls an address in a register is called.
369///
370/// A different instruction rather than the same one with a different operand, which is what the
371/// machine says too: one carries the distance to somewhere in the program and takes a relocation,
372/// and the other carries the register the address is in and takes none. Sharing an opcode would
373/// mean an instruction whose bytes depend on whether a field beside it happens to be set.
374pub const CALL_REG: &str = "x64.call_reg";
375
376/// One value a call passes.
377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
378pub struct Passing {
379    /// The type it travels as, which for an object travelling as bytes is the pointer's rather
380    /// than the object's, because the pointer is what the machine IR has.
381    pub ty: Type,
382    /// The register holding it, or holding its address when the bytes are what travel.
383    pub reg: mir::Reg,
384    /// What the classification asked of it. The one thing read here is whether the object behind
385    /// the pointer is the argument, since everything else it can say is about a value that is
386    /// already in a register in the form it travels in.
387    pub abi: Abi,
388}
389
390/// What one call came to.
391#[derive(Debug, Clone, PartialEq, Eq)]
392pub struct Made {
393    /// The registers the value came back in, in the order the signature returns them, which is
394    /// empty for a call that gives nothing back and holds two for a structure that comes back in a
395    /// pair. Which register each of them is is the classification's answer and is worked out here
396    /// rather than in a table, for the reason the second half of [`Calling::returns`] gives.
397    pub results: Vec<mir::Reg>,
398    /// How many bytes below the stack pointer this call needs for the arguments it passes there.
399    ///
400    /// Not always zero for a call that passes everything in registers: a Windows caller reserves
401    /// thirty two bytes for the callee to spill its register arguments into whether it uses them
402    /// or not, and that reservation is this.
403    pub outgoing: u32,
404}
405
406/// Which of a call's values could not be passed, and why.
407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
408pub struct Refused {
409    /// Its position among the arguments, or `None` for the value that comes back.
410    pub argument: Option<usize>,
411    /// What is wrong with where it travels.
412    pub missing: Missing,
413}
414
415/// What a call goes to.
416///
417/// The whole of the difference between the two calls. Everything else about them, which is what
418/// they pass and what comes back and which registers they destroy, is the signature's answer and
419/// is the same answer either way.
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
421pub enum Callee {
422    /// A name, which the linker resolves.
423    Named(Symbol),
424    /// An address in a register, which nothing resolves because there is nothing to resolve: the
425    /// value is not known until the program runs.
426    ///
427    /// The register is unconstrained, and it has to be, because every register the convention
428    /// does not preserve is one this instruction writes and every register an argument travels in
429    /// is spoken for. What is left is the registers the callee has to put back, which is where
430    /// the allocator will put the address, and it is the right answer for the same reason it is
431    /// the only one.
432    Through(mir::Reg),
433}
434
435/// One call, as everything about it that is not the function it is being built into.
436#[derive(Debug, Clone, Copy)]
437pub struct Calling<'a> {
438    /// What it calls.
439    pub callee: Callee,
440    /// What it passes, in the order the signature holds them, which is the order the convention
441    /// places them in.
442    pub args: &'a [Passing],
443    /// What comes back, which is empty for a call that gives nothing back, one type for a value,
444    /// and two for a structure small enough to come back in a pair of registers.
445    ///
446    /// A pair is placed here rather than named by a rule for the reason the arguments are: which
447    /// register each half goes in depends on the halves before it, since the two files are walked
448    /// separately, and a pattern over a term cannot see them.
449    pub returns: &'a [Type],
450    /// Whether the callee takes arguments beyond the ones its signature names, which is what says
451    /// whether it reads the count of vector registers the call passed arguments in.
452    pub variadic: bool,
453}
454
455/// Builds one call: what it passes, what comes back, and what it destroys.
456///
457/// # Errors
458///
459/// The first value this cannot pass, and why, before anything is written. A call with one is
460/// reported rather than compiled, because the alternative is a call that leaves an argument
461/// wherever the last one happened to put a register.
462///
463/// # Panics
464///
465/// If a call passes two gigabytes of arguments on the stack, which is a distance no offset in a
466/// frame can hold and a call no program makes.
467pub fn call(
468    out: &mut mir::Func,
469    block: mir::Block,
470    made: &Calling<'_>,
471    conv: &CallRegs,
472    names: &mut Interner,
473) -> Result<Made, Refused> {
474    let &Calling { callee, args, returns, variadic } = made;
475    // Where everything goes, worked out before anything is built, so that a call this cannot make
476    // leaves no half of one behind.
477    let mut places = Places::new(conv);
478    let mut passed = Vec::with_capacity(args.len());
479    // The ones with no register left for them, as the store each of them becomes and how far up
480    // the outgoing area it writes. Almost always empty.
481    let mut on_stack = Vec::new();
482    // How many of them went in vector registers, which is what a SysV variadic callee is told.
483    let mut vectors = 0u32;
484    // The ones whose bytes travel rather than their address, as the register that address is in,
485    // how far up the outgoing area they go and which words the copy is made of. Almost always
486    // empty too, and never at the same time as a register: an object in the argument area is in
487    // the argument area whatever is left of the register files.
488    let mut as_bytes = Vec::new();
489    for (index, &Passing { ty, reg, abi }) in args.iter().enumerate() {
490        let refused = |missing| Refused { argument: Some(index), missing };
491        // An eighty bit float is bytes in the argument area whatever the classification said, for
492        // the reason [`on_the_stack`] gives, and the register holding it holds their address. So it
493        // joins the objects below rather than being a case of its own, and the copy it becomes is
494        // the copy any other sixteen byte object gets.
495        let abi = match abi {
496            _ if on_the_stack(ty) => {
497                let (size, align) = X87_AREA;
498                Abi::ByVal { size: size.into(), align }
499            }
500            abi => abi,
501        };
502        if let Abi::ByVal { size, align } = abi {
503            let size = u32::try_from(size).map_err(|_| refused(Missing::TooBig))?;
504            let Where::Stack(up) = places.on_stack(size, align) else {
505                unreachable!("an object in the argument area is in the argument area")
506            };
507            let plan = crate::expand::plan(u64::from(size), align, conv.word)
508                .ok_or(refused(Missing::TooBig))?;
509            as_bytes.push((reg, up, plan));
510            continue;
511        }
512        let at = if ty.is_float() { places.float() } else { places.integer() };
513        if let Some(missing) = refuses(ty) {
514            return Err(refused(missing));
515        }
516        // Windows passes a float to a variadic callee in the vector register and in the general
517        // purpose register at the same position, both at once, because the callee has no
518        // prototype to tell it which file to look in. Doing that needs to know which arguments are
519        // the ones the signature does not name, and a call carries whether the callee is variadic
520        // rather than how many arguments it names, so this is turned down rather than passed in
521        // one file and read from the other.
522        if ty.is_float() && variadic && conv.shared_positions {
523            return Err(refused(Missing::InBothFiles));
524        }
525        let class = class_of(ty, conv);
526        match at {
527            Where::Reg(at) => {
528                // Only a register counts, because the count is of registers. An argument that went
529                // to memory is one the callee reads from memory whatever this says.
530                if class == conv.sse_class {
531                    vectors += 1;
532                }
533                passed.push((reg, at, class));
534            }
535            Where::Stack(up) => {
536                let store = store_of(ty).ok_or(refused(Missing::Width))?;
537                on_stack.push((reg, class, names.intern(store), up));
538            }
539        }
540    }
541    // A `long double` comes back in `st(0)`, which is not a register in either file and not one
542    // this call can be said to write. So nothing is placed for it and nothing is constrained, and
543    // the call gives back no register at all: what takes the value off that stack is the `fstp`
544    // [`crate::lower`] writes straight after the call, which is the same shape every other use of
545    // the x87 stack is written in. Only on its own, because a value that comes back beside another
546    // one comes back in a pair of registers and there is no pair with that stack in it.
547    let comes_back = if matches!(returns, [ty] if on_the_stack(*ty)) {
548        Vec::new()
549    } else {
550        places_back(returns, conv)?
551    };
552
553    // A variadic callee on SysV reads how many vector registers the call passed arguments in and
554    // skips saving them when the answer is none, which is what makes `printf` with no floating
555    // point argument cheap. It is an obligation rather than an optimization: leaving whatever was
556    // in the register there makes the callee save a register file it was not given, and a count
557    // that is too low makes it read an argument out of a register nothing put one in.
558    let counted = if variadic { conv.vector_count } else { None };
559
560    // The arguments that go to memory go there now, in front of the call and after everything this
561    // could have refused, so that a call it cannot make leaves no store behind either. The offset
562    // is written straight in rather than left for [`crate::finish`]: the outgoing area is at the
563    // bottom of the frame because that is where the callee looks for it, and the bottom of the
564    // frame is where the stack pointer already is.
565    for (reg, class, store, up) in on_stack {
566        let sp = mir::Operand::read(mir::Reg::physical(conv.stack_pointer), conv.int_class);
567        let up = i32::try_from(up).expect("an argument area under two gigabytes");
568        let build = out.build(block, mir::Opcode::new(store));
569        build.uses(reg, class).mem(mir::Mem::at(sp).plus(up)).finish();
570    }
571
572    // And the objects whose bytes go there, as a load and a store for each word of each of them.
573    // This is the copy the caller owes a callee that takes a structure by value: the callee is
574    // free to write to what it was handed, so what it was handed cannot be the caller's own copy,
575    // and the argument area is where the convention says the caller's copy goes. The words are the
576    // same words `crate::expand` would have chosen for a `memcpy` of the same block, because they
577    // are chosen by the same function.
578    for (from, up, plan) in as_bytes {
579        let up = i32::try_from(up).expect("an argument area under two gigabytes");
580        for (at, width) in plan {
581            let ty = Type::int(width * 8);
582            let at = i32::try_from(at).expect("an object under two gigabytes");
583            let word = out.new_vreg(conv.int_class);
584            let load = names
585                .intern(load_of(ty).ok_or(Refused { argument: None, missing: Missing::Width })?);
586            let there = mir::Operand::read(from, conv.int_class);
587            let build = out.build(block, mir::Opcode::new(load));
588            build.def(word, conv.int_class).mem(mir::Mem::at(there).plus(at)).finish();
589            let store = names
590                .intern(store_of(ty).ok_or(Refused { argument: None, missing: Missing::Width })?);
591            let sp = mir::Operand::read(mir::Reg::physical(conv.stack_pointer), conv.int_class);
592            let build = out.build(block, mir::Opcode::new(store));
593            build.uses(word, conv.int_class).mem(mir::Mem::at(sp).plus(up + at)).finish();
594        }
595    }
596
597    // The definitions first and the reads after, which is the order every operand vector in the
598    // machine IR is in and the order `rucc_mir::defs` counts.
599    let mut operands = Vec::with_capacity(args.len() + conv.int_order.len() + 2);
600    let results: Vec<mir::Reg> = comes_back
601        .iter()
602        .map(|&(at, class)| {
603            let reg = out.new_vreg(class);
604            operands.push(mir::Operand::write(reg, class).with(Constraint::Fixed(at)));
605            reg
606        })
607        .collect();
608    // One list per file, because a physical register is a number and the class is what says which
609    // file it is a number in. One list would have `xmm0` blocking `rax`.
610    let spoken_for = |class: RegClass| -> Vec<PhysReg> {
611        comes_back
612            .iter()
613            .filter(|&&(_, at)| at == class)
614            .map(|&(reg, _)| reg)
615            .chain(counted.filter(|_| class == conv.int_class))
616            .chain(passed.iter().filter(|&&(_, _, at)| at == class).map(|&(_, reg, _)| reg))
617            .collect()
618    };
619    let named = spoken_for(conv.int_class);
620    for &reg in conv.int_order {
621        if !conv.preserves_int(reg) && !named.contains(&reg) {
622            operands.push(mir::Operand::write(mir::Reg::physical(reg), conv.int_class));
623        }
624    }
625    let named = spoken_for(conv.sse_class);
626    for &reg in conv.sse_order {
627        if !conv.preserves_sse(reg) && !named.contains(&reg) {
628            operands.push(mir::Operand::write(mir::Reg::physical(reg), conv.sse_class));
629        }
630    }
631    // The address in front of the arguments, because a call through one is written with the
632    // register it goes through and nothing in the operand vector is at a place a table could name.
633    // First read is a place that does not depend on the signature, which is what
634    // [`rucc_target::x86_64::Arg::Through`] is written against.
635    if let Callee::Through(reg) = callee {
636        operands.push(mir::Operand::read(reg, conv.int_class));
637    }
638    for (reg, at, class) in passed {
639        operands.push(mir::Operand::read(reg, class).with(Constraint::Fixed(at)));
640    }
641    if let Some(at) = counted {
642        let count = out.new_vreg(conv.int_class);
643        let zero = mir::Opcode::new(names.intern("x64.mov_ri_32"));
644        out.build(block, zero).def(count, conv.int_class).imm(i64::from(vectors)).finish();
645        operands.push(mir::Operand::read(count, conv.int_class).with(Constraint::Fixed(at)));
646    }
647
648    let opcode = mir::Opcode::new(names.intern(match callee {
649        Callee::Named(_) => CALL,
650        Callee::Through(_) => CALL_REG,
651    }));
652    let mut build = out.build(block, opcode);
653    if let Callee::Named(symbol) = callee {
654        build = build.symbol(symbol);
655    }
656    for operand in operands {
657        build = build.operand(operand);
658    }
659    build.finish();
660    Ok(Made { results, outgoing: places.size() })
661}
662
663/// Which register each value comes back in, walked the way the arguments are.
664///
665/// The two files are counted separately, because a structure of a `double` and a `long` comes back
666/// with the `double` in the first vector register and the `long` in the first integer one, and a
667/// single count would put the second half one place further along a list it is not on.
668///
669/// # Errors
670///
671/// The first value that cannot come back at all, and why, so that a call this cannot make leaves
672/// nothing behind. Nothing here reports which value it was, because the caller has one answer for
673/// all of them: the value that comes back is not an argument and has no position among them.
674fn places_back(returns: &[Type], conv: &CallRegs) -> Result<Vec<(PhysReg, RegClass)>, Refused> {
675    let refused = |missing| Refused { argument: None, missing };
676    let mut back = Vec::with_capacity(returns.len());
677    let (mut ints, mut sses) = (0usize, 0usize);
678    for &ty in returns {
679        if let Some(missing) = refuses(ty) {
680            return Err(refused(missing));
681        }
682        let class = class_of(ty, conv);
683        let (file, at) = if class == conv.sse_class {
684            (conv.sse_returns, &mut sses)
685        } else {
686            (conv.int_returns, &mut ints)
687        };
688        let reg = *file.get(*at).ok_or_else(|| refused(Missing::NoRoom))?;
689        *at += 1;
690        back.push((reg, class));
691    }
692    Ok(back)
693}
694
695/// What the pseudo for an argument of that type is called.
696///
697/// The width is in the name for the same reason it is in every other opcode here: it is what the
698/// instruction is about. Nothing encodes it, so nothing depends on it being right, but a listing
699/// that says an argument arrived and does not say how much of it did is a listing worth less.
700///
701/// Which widths there are is the question the rule set asks of a type, and not a list of its own,
702/// because it has to be the same list. An argument brought in at a width the rules have no name
703/// for is a register
704/// nothing downstream could then read, and a width the rules cover that this refuses is a
705/// function turned away for no reason. Asking one question in one place is what keeps the two
706/// answers from drifting, and an address is what they used to disagree about.
707#[must_use]
708pub fn head_of(ty: Type) -> Option<&'static str> {
709    if let Some(at) = crate::term::float_slot(ty) {
710        return Some(["x64.arg_val_f32", "x64.arg_val_f64"][at]);
711    }
712    let names = ["x64.arg_val_8", "x64.arg_val_16", "x64.arg_val_32", "x64.arg_val_64"];
713    Some(names[crate::term::slot(ty)?])
714}
715
716/// What the instruction that reads an argument of that type out of memory is called.
717///
718/// Keyed off the same two questions [`head_of`] asks and answering for the same set of types, so
719/// that a parameter this compiler can bring in from a register is one it can bring in from the
720/// caller's stack as well. A width one of them covered and the other did not would be a function
721/// turned away for where its sixth argument happened to land.
722///
723/// Reading a narrow argument at its own width and not at a word is deliberate. The caller wrote a
724/// whole word, but what it put in the part above the value is not something the convention says, so
725/// the bits this reads are exactly the bits that mean anything. That is the same thing an argument
726/// arriving in a register gets: `x64.arg_val_8` says the low byte of that register is the argument
727/// and says nothing at all about the rest of it.
728#[must_use]
729pub fn load_of(ty: Type) -> Option<&'static str> {
730    if let Some(at) = crate::term::float_slot(ty) {
731        return Some(["x64.movss_rm", "x64.movsd_rm"][at]);
732    }
733    let names = ["x64.mov_rm_8", "x64.mov_rm_16", "x64.mov_rm_32", "x64.mov_rm_64"];
734    Some(names[crate::term::slot(ty)?])
735}
736
737/// What the instruction that writes an argument of that type into memory is called.
738///
739/// The mirror of [`load_of`], keyed off the same two questions and answering for the same set of
740/// types, so that the two ends of one call agree about what travels. A type a callee can read out
741/// of the argument area and a caller cannot write into it would be a call turned away for a reason
742/// the function it calls does not have.
743///
744/// Writing a narrow argument at its own width leaves whatever was already in the rest of the word.
745/// That is allowed, and it is what [`load_of`] is written against: the convention does not say what
746/// is above the value, so the callee reads only the bits that mean anything and neither end has to
747/// agree about the rest.
748#[must_use]
749pub fn store_of(ty: Type) -> Option<&'static str> {
750    if let Some(at) = crate::term::float_slot(ty) {
751        return Some(["x64.movss_mr", "x64.movsd_mr"][at]);
752    }
753    let names = ["x64.mov_mr_8", "x64.mov_mr_16", "x64.mov_mr_32", "x64.mov_mr_64"];
754    Some(names[crate::term::slot(ty)?])
755}
756
757/// What the instruction that leaves a returned value in its register is called, for the value at
758/// that place in its own register file.
759///
760/// Keyed off the same two questions [`head_of`] asks, so a type this can give back is a type it can
761/// take in. The place is the one a call counted to when it laid the return out, which is per file
762/// rather than over the whole list: a structure of a `double` and a `long` gives both of them back
763/// at place zero.
764///
765/// The register itself is not here. It is in the operand table in `rucc_target::x86_64`, which is
766/// where the first one has always been, and the two names below are how a value says which of the
767/// two it is. A convention with more than two registers to come back in would need more names, and
768/// there is none, which is what the `None` at the end is about.
769#[must_use]
770pub fn ret_of(ty: Type, at: usize) -> Option<&'static str> {
771    if let Some(width) = crate::term::float_slot(ty) {
772        let names =
773            [["x64.ret_val_f32", "x64.ret_val_f64"], ["x64.ret_val2_f32", "x64.ret_val2_f64"]];
774        return Some(names.get(at)?[width]);
775    }
776    let names = [
777        ["x64.ret_val_8", "x64.ret_val_16", "x64.ret_val_32", "x64.ret_val_64"],
778        ["x64.ret_val2_8", "x64.ret_val2_16", "x64.ret_val2_32", "x64.ret_val2_64"],
779    ];
780    Some(names.get(at)?[crate::term::slot(ty)?])
781}
782
783#[cfg(test)]
784mod tests {
785    use rucc_target::x86_64::{REGS, SYSV, WIN64};
786
787    use super::*;
788
789    /// Those types as parameters that travel as the values they are, which is every one of them
790    /// that is not a structure the classification put in the argument area.
791    fn plain(params: &[Type]) -> Vec<Param> {
792        params.iter().copied().map(Param::new).collect()
793    }
794
795    /// The parameters of a function under a convention, as machine IR text.
796    fn bind(params: &[Type], conv: &CallRegs) -> String {
797        let mut names = Interner::new();
798        let mut out = mir::Func::new(names.intern("f"));
799        let block = out.create_block();
800        entry(&mut out, block, &plain(params), conv, &mut names, None)
801            .expect("every parameter arrives");
802        mir::print_func(&out, &names, &REGS)
803    }
804
805    #[test]
806    fn the_first_arguments_arrive_where_the_convention_puts_them() {
807        let i32 = Type::int(32);
808        assert_eq!(
809            bind(&[i32, i32, Type::int(64)], &SYSV),
810            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_32\n    \
811             %1:gpr($rsi) = x64.arg_val_32\n    %2:gpr($rdx) = x64.arg_val_64\n}\n"
812        );
813    }
814
815    #[test]
816    fn the_other_convention_puts_the_same_arguments_somewhere_else() {
817        // The first argument is in `rcx` here and in `rdi` above, which is the difference that
818        // makes a SysV binary calling a Windows one read the wrong value rather than fail.
819        let i64 = Type::int(64);
820        assert_eq!(
821            bind(&[i64, i64], &WIN64),
822            "mfunc @f {\nblock0:\n    %0:gpr($rcx) = x64.arg_val_64\n    \
823             %1:gpr($rdx) = x64.arg_val_64\n}\n"
824        );
825    }
826
827    /// The parameters of a function under a convention, and what each of the ones that arrived in
828    /// memory is waiting on.
829    fn arrive(params: &[Type], conv: &CallRegs) -> (String, Vec<u32>) {
830        let mut names = Interner::new();
831        let mut out = mir::Func::new(names.intern("f"));
832        let block = out.create_block();
833        let arrived = entry(&mut out, block, &plain(params), conv, &mut names, None)
834            .expect("every parameter");
835        let up = arrived.stack.iter().map(|&(_, up)| up).collect();
836        (mir::print_func(&out, &names, &REGS), up)
837    }
838
839    #[test]
840    fn an_argument_past_the_last_register_is_read_out_of_the_caller_s_stack() {
841        let (text, up) = arrive(&[Type::int(64); 7], &SYSV);
842
843        // Six of them got registers and the seventh did not, so the seventh is a load rather than
844        // a pseudo. It reads through the stack pointer with nothing in its displacement, because
845        // where the caller's argument area is from in here is a distance into a frame that does
846        // not exist yet, and it is at the bottom of that area because it is the first one in it.
847        assert_eq!(up, [0]);
848        assert!(text.contains("%6:gpr = x64.mov_rm_64 [$rsp]"), "{text}");
849        assert_eq!(text.matches("x64.arg_val_64").count(), 6, "{text}");
850    }
851
852    #[test]
853    fn the_other_convention_runs_out_of_registers_three_arguments_earlier() {
854        let (text, up) = arrive(&[Type::int(64); 7], &WIN64);
855
856        // Windows passes four integers in registers and reserves thirty two bytes below the call
857        // whether they are used or not, so the fifth argument is not at the bottom of the argument
858        // area but above the shadow space, and the three after it follow it a word at a time.
859        assert_eq!(up, [32, 40, 48]);
860        assert_eq!(text.matches("x64.arg_val_64").count(), 4, "{text}");
861        assert!(text.contains("%4:gpr = x64.mov_rm_64 [$rsp]"), "{text}");
862    }
863
864    /// The parameters of a function under a convention, with one of them a structure whose bytes
865    /// travel, and what each of the ones that arrived in memory is waiting on.
866    fn arrive_with(params: &[Param], conv: &CallRegs) -> (String, Vec<u32>) {
867        let mut names = Interner::new();
868        let mut out = mir::Func::new(names.intern("f"));
869        let block = out.create_block();
870        let arrived =
871            entry(&mut out, block, params, conv, &mut names, None).expect("every parameter");
872        let up = arrived.stack.iter().map(|&(_, up)| up).collect();
873        (mir::print_func(&out, &names, &REGS), up)
874    }
875
876    #[test]
877    fn a_structure_that_arrived_as_bytes_is_an_address_and_not_a_load() {
878        let byval = Param::with_abi(Type::PTR, Abi::ByVal { size: 32, align: 8 });
879        let (text, up) =
880            arrive_with(&[Param::new(Type::int(32)), byval, Param::new(Type::int(32))], &SYSV);
881
882        // `int f(int a, struct Big b, int c)`. The bytes of `b` are already in this function, at
883        // the bottom of the caller's argument area, so nothing is read out of them here: what the
884        // parameter is is where they are, which is one address. The two integers still travel in
885        // registers, because an object in the argument area takes no register and the arguments
886        // behind it do not shift along.
887        assert_eq!(up, [0]);
888        assert_eq!(text.matches("x64.arg_val_32").count(), 2, "{text}");
889        assert!(text.contains("%2:gpr = x64.lea_64 [$rsp]"), "{text}");
890        assert!(!text.contains("mov_rm"), "nothing is read out of the bytes: {text}");
891    }
892
893    #[test]
894    fn the_argument_behind_a_structure_that_travelled_as_bytes_is_above_all_of_them() {
895        let byval = Param::with_abi(Type::PTR, Abi::ByVal { size: 24, align: 16 });
896        let params: Vec<Param> = (0..7).map(|_| Param::new(Type::int(64))).collect();
897        let (_, up) = arrive_with(&[&params[..], &[byval], &params[..1]].concat(), &SYSV);
898
899        // Six integers take the six registers, the seventh is at the bottom of the argument area,
900        // and the structure is above it at the alignment its type asks for rather than at a word.
901        // The one behind the structure is above all twenty four of its bytes, rounded up to a
902        // whole number of words, because the area is a run of words.
903        assert_eq!(up, [0, 16, 40]);
904    }
905
906    /// A parameter narrower than a word is read at its own width rather than at a word, and one in
907    /// the other register file is read with the other file's instruction. Both are the same list
908    /// [`head_of`] answers from, which is what stops a function being turned away for the width of
909    /// its seventh argument alone.
910    #[test]
911    fn what_a_stack_argument_is_read_with_is_its_own_width_and_its_own_file() {
912        let f32 = Type::float(rucc_ir::Float::F32);
913        let params = [Type::int(64), Type::int(64), Type::int(64), Type::int(64), Type::int(8)];
914        let (text, up) = arrive(&params, &WIN64);
915        assert_eq!(up, [32]);
916        assert!(text.contains("x64.mov_rm_8 [$rsp]"), "{text}");
917
918        let floats = [f32; 5];
919        let (text, up) = arrive(&floats, &WIN64);
920        assert_eq!(up, [32]);
921        assert!(text.contains("%4:xmm = x64.movss_rm [$rsp]"), "{text}");
922    }
923
924    /// Every type a parameter can arrive in a register at is one it can be read from memory at.
925    /// The two lists are keyed off the same two questions so that they cannot drift, and this is
926    /// what says so: a width one covered and the other did not would be a function turned away for
927    /// where its arguments happened to land rather than for anything about it.
928    #[test]
929    fn the_two_lists_of_widths_answer_for_the_same_types() {
930        let types = [
931            Type::int(1),
932            Type::int(8),
933            Type::int(16),
934            Type::int(32),
935            Type::int(64),
936            Type::int(128),
937            Type::PTR,
938            Type::float(rucc_ir::Float::F32),
939            Type::float(rucc_ir::Float::F64),
940            Type::float(rucc_ir::Float::F80),
941        ];
942        for ty in types {
943            assert_eq!(head_of(ty).is_some(), load_of(ty).is_some(), "{ty:?}");
944        }
945    }
946
947    /// A float arrives in the other file, and the two files are counted apart on SysV: the
948    /// integer here is the first integer argument and the float is the first float one, so they
949    /// are in `rdi` and `xmm0` rather than in the first and second of anything.
950    #[test]
951    fn a_float_arrives_in_a_vector_register_and_is_counted_apart_from_the_integers() {
952        let f32 = Type::float(rucc_ir::Float::F32);
953        let f64 = Type::float(rucc_ir::Float::F64);
954        assert_eq!(
955            bind(&[Type::int(32), f64, f32], &SYSV),
956            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_32\n    \
957             %1:xmm($xmm0) = x64.arg_val_f64\n    %2:xmm($xmm1) = x64.arg_val_f32\n}\n"
958        );
959    }
960
961    /// Windows counts the two files together, so the same three arguments land in different
962    /// registers: the float is the second argument and takes the second vector register rather
963    /// than the first, which is the difference that makes a mismatched call read the wrong value.
964    #[test]
965    fn the_other_convention_counts_the_two_files_as_one_run_of_positions() {
966        let f64 = Type::float(rucc_ir::Float::F64);
967        assert_eq!(
968            bind(&[Type::int(32), f64, Type::int(64)], &WIN64),
969            "mfunc @f {\nblock0:\n    %0:gpr($rcx) = x64.arg_val_32\n    \
970             %1:xmm($xmm1) = x64.arg_val_f64\n    %2:gpr($r8) = x64.arg_val_64\n}\n"
971        );
972    }
973
974    /// A `long double` is in neither file and travels in the argument area, which is what SysV's
975    /// X87 classification comes to. So it arrives the way a structure the classification put in
976    /// memory arrives, as the address of its bytes in a general purpose register, and it does that
977    /// while the vector file is untouched: this one is in the argument area because of what it is
978    /// rather than because the registers ran out.
979    #[test]
980    fn a_long_double_arrives_as_the_address_of_its_bytes_in_the_argument_area() {
981        let params = [Type::int(32), Type::float(rucc_ir::Float::F80)];
982        assert_eq!(
983            bind(&params, &SYSV),
984            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_32\n    \
985             %1:gpr = x64.lea_64 [$rsp]\n}\n"
986        );
987    }
988
989    /// It still cannot come back beside another value, and what it is turned away for says which
990    /// file is in the way rather than calling eighty bits a width no register holds. A pair comes
991    /// back in a pair of registers and there is no pair with the x87 stack in it.
992    #[test]
993    fn a_long_double_in_a_pair_is_reported_as_the_x87_stack_it_travels_on() {
994        let returns = [Type::float(rucc_ir::Float::F80), Type::int(64)];
995        assert_eq!(
996            make(&[], &returns, false, &SYSV).2,
997            Err(Refused { argument: None, missing: Missing::OnX87 })
998        );
999    }
1000
1001    /// One call to `g`, with a register for each argument arriving in the block that makes it.
1002    fn make(
1003        args: &[Type],
1004        returns: &[Type],
1005        variadic: bool,
1006        conv: &CallRegs,
1007    ) -> (Interner, mir::Func, Result<Made, Refused>) {
1008        let mut names = Interner::new();
1009        let mut out = mir::Func::new(names.intern("f"));
1010        let block = out.create_block();
1011        let passed: Vec<Passing> = args
1012            .iter()
1013            .map(|&ty| Passing {
1014                ty,
1015                reg: out.append_param(block, class_of(ty, conv)),
1016                abi: Abi::Plain,
1017            })
1018            .collect();
1019        let callee = Callee::Named(names.intern("g"));
1020        let what = Calling { callee, args: &passed, returns, variadic };
1021        let made = call(&mut out, block, &what, conv, &mut names);
1022        (names, out, made)
1023    }
1024
1025    /// What the call in that function reads and writes, by register name, in the order the
1026    /// operands are in.
1027    fn operands(func: &mir::Func) -> (Vec<String>, Vec<String>) {
1028        let block = func.entry().expect("a function with a block in it");
1029        let call = func.terminator(block).expect("the call is the last thing in the block");
1030        let name = |operand: &mir::Operand| match (operand.reg.phys(), operand.constraint) {
1031            (Some(reg), _) | (None, Constraint::Fixed(reg)) => {
1032                REGS.name(operand.class, reg).expect("a register the file describes").to_string()
1033            }
1034            _ => format!("{:?}", operand.reg),
1035        };
1036        let mut written = Vec::new();
1037        let mut read = Vec::new();
1038        for operand in &func[func[call].operands] {
1039            let into = if operand.role == mir::Role::Use { &mut read } else { &mut written };
1040            into.push(name(operand));
1041        }
1042        (written, read)
1043    }
1044
1045    #[test]
1046    fn a_call_passes_its_arguments_where_the_convention_puts_them() {
1047        let i32 = Type::int(32);
1048        let (_, func, made) = make(&[i32, i32, i32], &[], false, &SYSV);
1049        assert_eq!(made.expect("three integers all fit in registers").results, []);
1050        assert_eq!(operands(&func).1, ["rdi", "rsi", "rdx"]);
1051    }
1052
1053    #[test]
1054    fn the_other_convention_passes_the_same_arguments_somewhere_else() {
1055        let i64 = Type::int(64);
1056        let (_, func, made) = make(&[i64, i64], &[], false, &WIN64);
1057        // Thirty two bytes of stack for a call that passes nothing on the stack, which is what
1058        // Windows asks a caller to leave the callee whether the callee uses it or not.
1059        assert_eq!(made.expect("two integers fit in registers").outgoing, 32);
1060        assert_eq!(operands(&func).1, ["rcx", "rdx"]);
1061    }
1062
1063    #[test]
1064    fn what_a_call_gives_back_comes_out_of_the_register_the_convention_returns_in() {
1065        let (names, func, made) = make(&[], &[Type::int(32)], false, &SYSV);
1066        let made = made.expect("an integer comes back");
1067        let [result] = made.results[..] else { panic!("one register") };
1068        // The first thing written is the result, and it is the only thing written that is a value
1069        // rather than a register the callee destroyed.
1070        assert_eq!(operands(&func).0.first().map(String::as_str), Some("rax"));
1071        assert_eq!(func.class_of(result), Some(SYSV.int_class));
1072        assert!(mir::print_func(&func, &names, &REGS).contains("x64.call"));
1073    }
1074
1075    #[test]
1076    fn every_register_the_callee_may_destroy_is_written_by_the_call() {
1077        let (_, func, _) = make(&[Type::int(64)], &[Type::int(64)], false, &SYSV);
1078        let (written, read) = operands(&func);
1079        // The callee saved registers are not here, because a value in one of those survives a
1080        // call and that is the whole difference between the two halves of the convention.
1081        for saved in ["rbx", "rbp", "r12", "r13", "r14", "r15"] {
1082            assert!(!written.contains(&saved.to_string()), "{saved} survives a call");
1083        }
1084        // Every other integer register is, once. The two named ones are named by the result and
1085        // by the argument instead, and naming one twice would be blocking it twice.
1086        for destroyed in ["rcx", "rdx", "rsi", "r8", "r9", "r10", "r11"] {
1087            let count = written.iter().filter(|name| *name == destroyed).count();
1088            assert_eq!(count, 1, "{destroyed} is destroyed by a call and is written {count} times");
1089        }
1090        assert_eq!(written.iter().filter(|name| *name == "rax").count(), 1);
1091        assert_eq!(read, ["rdi"]);
1092        // The vector registers are all destroyed on SysV, and they are in the other class.
1093        assert!(written.contains(&"xmm0".to_string()));
1094    }
1095
1096    #[test]
1097    fn a_variadic_call_says_how_many_vector_registers_it_passed_arguments_in() {
1098        let (names, func, made) = make(&[Type::int(64)], &[], true, &SYSV);
1099        made.expect("an integer argument to a variadic callee");
1100        let (_, read) = operands(&func);
1101        // Zero of them here, and `al` is where a SysV callee looks for it. Leaving whatever was in
1102        // the register there would make a callee that saves its vector registers save ones it was
1103        // never given.
1104        assert_eq!(read, ["rdi", "rax"]);
1105        assert_eq!(
1106            mir::print_func(&func, &names, &REGS).lines().nth(2),
1107            Some("    %1:gpr = x64.mov_ri_32 0")
1108        );
1109
1110        // Two of them here, which is the number that decides how much of the register save area a
1111        // callee like `printf` fills in. A count of zero with a float in `xmm0` would be a callee
1112        // reading its first `%f` out of a register nothing wrote.
1113        let f64 = Type::float(rucc_ir::Float::F64);
1114        let (names, func, made) = make(&[Type::int(64), f64, f64], &[], true, &SYSV);
1115        made.expect("one integer and two floats all fit in registers");
1116        assert_eq!(operands(&func).1, ["rdi", "xmm0", "xmm1", "rax"]);
1117        assert!(mir::print_func(&func, &names, &REGS).contains("x64.mov_ri_32 2"));
1118    }
1119
1120    /// Windows passes a float to a variadic callee in both files at once, and which arguments are
1121    /// the ones the signature does not name is not something a call carries, so it is turned down
1122    /// rather than passed in one file and read from the other.
1123    #[test]
1124    fn a_float_passed_to_a_variadic_callee_on_windows_is_reported() {
1125        let f64 = Type::float(rucc_ir::Float::F64);
1126        assert_eq!(
1127            make(&[Type::int(32), f64], &[], true, &WIN64).2,
1128            Err(Refused { argument: Some(1), missing: Missing::InBothFiles })
1129        );
1130        // The same call to a callee whose signature names both arguments is fine, because there is
1131        // no second copy to make.
1132        assert!(make(&[Type::int(32), f64], &[], false, &WIN64).2.is_ok());
1133    }
1134
1135    #[test]
1136    fn a_call_through_an_address_reads_it_in_front_of_the_arguments() {
1137        let i32 = Type::int(32);
1138        let mut names = Interner::new();
1139        let mut out = mir::Func::new(names.intern("f"));
1140        let block = out.create_block();
1141        let address = out.append_param(block, SYSV.int_class);
1142        let reg = out.append_param(block, SYSV.int_class);
1143        let passed = vec![Passing { ty: i32, reg, abi: Abi::Plain }];
1144        let what = Calling {
1145            callee: Callee::Through(address),
1146            args: &passed,
1147            returns: &[i32],
1148            variadic: false,
1149        };
1150        call(&mut out, block, &what, &SYSV, &mut names).expect("one integer fits in a register");
1151
1152        // The address is the first thing read and the arguments follow it, which is the order the
1153        // assembler counts on, and it is in no particular register because every register a call
1154        // could insist on is one the call has already spoken for.
1155        let text = mir::print_func(&out, &names, &REGS);
1156        assert!(text.contains("= x64.call_reg %0, %1($rdi)\n"), "{text}");
1157        assert!(!text.contains("@g"), "a call through an address names nobody: {text}");
1158    }
1159
1160    #[test]
1161    fn a_call_with_no_register_left_writes_the_argument_into_the_outgoing_area() {
1162        let i64 = Type::int(64);
1163        let (names, func, made) = make(&[i64; 7], &[], false, &SYSV);
1164        let made = made.expect("the seventh goes to memory");
1165
1166        // At the stack pointer, because the outgoing area is at the bottom of the frame, and in
1167        // front of the call rather than as an operand of it.
1168        let text = mir::print_func(&func, &names, &REGS);
1169        assert!(text.contains("x64.mov_mr_64 %6, [$rsp]\n"), "{text}");
1170        let store = text.find("x64.mov_mr_64").expect("the store");
1171        assert!(store < text.find("x64.call").expect("the call"), "{text}");
1172        // One word of it, which is what the frame has to reserve for this call.
1173        assert_eq!(made.outgoing, 8);
1174    }
1175
1176    /// One call to `g`, passing that many words, then an object of that size and alignment by
1177    /// value, then one more integer, which is `int g(long.., struct Big, int)` after the
1178    /// classification.
1179    fn pass_bytes(
1180        before: usize,
1181        size: u64,
1182        align: u32,
1183        conv: &CallRegs,
1184    ) -> (Interner, mir::Func, Result<Made, Refused>) {
1185        let mut names = Interner::new();
1186        let mut out = mir::Func::new(names.intern("f"));
1187        let block = out.create_block();
1188        let mut args: Vec<Passing> = (0..before)
1189            .map(|_| Passing {
1190                ty: Type::int(64),
1191                reg: out.append_param(block, conv.int_class),
1192                abi: Abi::Plain,
1193            })
1194            .collect();
1195        args.push(Passing {
1196            ty: Type::PTR,
1197            reg: out.append_param(block, conv.int_class),
1198            abi: Abi::ByVal { size, align },
1199        });
1200        args.push(Passing {
1201            ty: Type::int(32),
1202            reg: out.append_param(block, conv.int_class),
1203            abi: Abi::Plain,
1204        });
1205        let callee = Callee::Named(names.intern("g"));
1206        let what = Calling { callee, args: &args, returns: &[], variadic: false };
1207        let made = call(&mut out, block, &what, conv, &mut names);
1208        (names, out, made)
1209    }
1210
1211    #[test]
1212    fn a_structure_passed_by_value_in_memory_is_copied_into_the_outgoing_area() {
1213        let (names, func, made) = pass_bytes(1, 24, 8, &SYSV);
1214        let made = made.expect("an object of three words is copied a word at a time");
1215
1216        // The bytes travel and the address does not, so the copy is a load and a store for each
1217        // word of it, in front of the call, and the callee's copy is at the bottom of the outgoing
1218        // area. The caller owes it this copy: the callee is free to write to what it was handed,
1219        // so what it was handed cannot be the object itself.
1220        let text = mir::print_func(&func, &names, &REGS);
1221        assert!(text.contains("x64.mov_mr_64 %3, [$rsp]\n"), "{text}");
1222        assert!(text.contains("x64.mov_mr_64 %4, [$rsp + 8]\n"), "{text}");
1223        assert!(text.contains("x64.mov_mr_64 %5, [$rsp + 16]\n"), "{text}");
1224        assert_eq!(text.matches("x64.mov_rm_64").count(), 3, "{text}");
1225        assert!(text.find("x64.mov_mr_64") < text.find("x64.call"), "{text}");
1226        assert_eq!(made.outgoing, 24);
1227    }
1228
1229    #[test]
1230    fn the_integers_beside_it_still_travel_in_registers() {
1231        let (_, func, _) = pass_bytes(1, 24, 8, &SYSV);
1232
1233        // An object in the argument area takes no argument register, so the integer behind it is
1234        // in the second one and not the third. Counting it as a register is the mistake that would
1235        // shift every argument after it along by one.
1236        let (clobbered, read) = operands(&func);
1237        assert_eq!(read, ["rdi", "rsi"]);
1238        assert!(clobbered.contains(&"rdx".to_owned()), "the third is free: {clobbered:?}");
1239    }
1240
1241    #[test]
1242    fn an_object_wanting_more_alignment_than_a_word_gets_it() {
1243        let (names, func, made) = pass_bytes(7, 24, 16, &SYSV);
1244        let made = made.expect("an object of three words");
1245
1246        // Six of the integers took the registers and the seventh is at the bottom of the area, so
1247        // the object cannot start where it left off: sixteen byte alignment moves it up to the
1248        // next multiple of sixteen and leaves a word of nothing behind it. The integer after it is
1249        // above all three of its words.
1250        let text = mir::print_func(&func, &names, &REGS);
1251        assert!(text.contains("x64.mov_mr_64 %9, [$rsp + 16]\n"), "{text}");
1252        assert!(text.contains("x64.mov_mr_32 %8, [$rsp + 40]\n"), "{text}");
1253        assert_eq!(made.outgoing, 48);
1254    }
1255
1256    #[test]
1257    fn an_object_too_large_to_copy_a_word_at_a_time_is_reported_rather_than_passed() {
1258        let (_, _, made) = pass_bytes(1, 4096, 8, &SYSV);
1259
1260        // Five hundred and twelve words is past what unrolling is worth, and the copy that size
1261        // wants is a call to the runtime, which cannot be built in the middle of building a call.
1262        // Saying so is the point: the alternative is a call that passes the address of the object
1263        // where the callee is going to read the object.
1264        assert_eq!(made, Err(Refused { argument: Some(1), missing: Missing::TooBig }));
1265        assert_eq!(
1266            Missing::TooBig.why(),
1267            "is more bytes than a copy into the argument area unrolls to"
1268        );
1269    }
1270
1271    /// The other convention runs out three arguments earlier and starts its argument area above the
1272    /// shadow space it also has to reserve, and both of those are what `Places` already said.
1273    #[test]
1274    fn where_the_outgoing_area_starts_is_the_convention_s_answer() {
1275        let i64 = Type::int(64);
1276        let (names, func, made) = make(&[i64; 7], &[], false, &WIN64);
1277        assert_eq!(made.expect("the last three go to memory").outgoing, 56);
1278
1279        // Thirty two bytes of shadow space first, which the caller writes nothing into and the
1280        // callee owns, and the fifth argument above it.
1281        let text = mir::print_func(&func, &names, &REGS);
1282        assert!(text.contains("x64.mov_mr_64 %4, [$rsp + 32]\n"), "{text}");
1283        assert!(text.contains("x64.mov_mr_64 %5, [$rsp + 40]\n"), "{text}");
1284        assert!(text.contains("x64.mov_mr_64 %6, [$rsp + 48]\n"), "{text}");
1285    }
1286
1287    /// What a stack argument is written with is its own width and its own register file, matching
1288    /// what the callee reads it back with.
1289    #[test]
1290    fn a_narrow_or_floating_argument_keeps_its_own_store() {
1291        let i64 = Type::int(64);
1292        let narrow = [i64, i64, i64, i64, i64, i64, Type::int(8)];
1293        let (names, func, made) = make(&narrow, &[], false, &SYSV);
1294        made.expect("the seventh goes to memory");
1295        let text = mir::print_func(&func, &names, &REGS);
1296        assert!(text.contains("x64.mov_mr_8 %6, [$rsp]\n"), "{text}");
1297
1298        let f32 = Type::float(rucc_ir::Float::F32);
1299        let (names, func, made) = make(&[f32; 9], &[], false, &SYSV);
1300        made.expect("the ninth goes to memory");
1301        let text = mir::print_func(&func, &names, &REGS);
1302        assert!(text.contains("x64.movss_mr %8, [$rsp]\n"), "{text}");
1303    }
1304
1305    /// The count a SysV variadic callee reads is a count of registers, so an argument that went to
1306    /// memory instead is not in it.
1307    #[test]
1308    fn an_argument_in_memory_is_not_counted_as_a_vector_register() {
1309        let f64 = Type::float(rucc_ir::Float::F64);
1310        let (names, func, made) = make(&[f64; 9], &[], true, &SYSV);
1311        made.expect("the ninth goes to memory");
1312        let text = mir::print_func(&func, &names, &REGS);
1313        assert!(text.contains("x64.mov_ri_32 8\n"), "eight registers, not nine: {text}");
1314    }
1315
1316    /// The two lists of widths answer for the same set of types, so that a value the callee can
1317    /// read out of the argument area is one the caller can write into it.
1318    #[test]
1319    fn what_can_be_read_can_be_written() {
1320        let types = [
1321            Type::int(1),
1322            Type::int(8),
1323            Type::int(16),
1324            Type::int(32),
1325            Type::int(64),
1326            Type::int(128),
1327            Type::PTR,
1328            Type::float(rucc_ir::Float::F32),
1329            Type::float(rucc_ir::Float::F64),
1330            Type::float(rucc_ir::Float::F80),
1331        ];
1332        for ty in types {
1333            assert_eq!(load_of(ty).is_some(), store_of(ty).is_some(), "{ty:?}");
1334        }
1335    }
1336
1337    /// A float travels in the other file at both ends of a call, and the register it comes back in
1338    /// is the first of that file rather than the first of the other one.
1339    #[test]
1340    fn a_call_passes_and_returns_a_float_in_a_vector_register() {
1341        let f64 = Type::float(rucc_ir::Float::F64);
1342        let (_, func, made) = make(&[Type::int(32), f64], &[f64], false, &SYSV);
1343        let result = made.expect("an integer and a float both fit in registers");
1344        let (written, read) = operands(&func);
1345        assert_eq!(read, ["rdi", "xmm0"]);
1346        assert_eq!(written.first().map(String::as_str), Some("xmm0"));
1347        assert_eq!(func.class_of(result.results[0]), Some(SYSV.sse_class));
1348        // Written once, because the register the result comes back in is already blocked by being
1349        // named and a clobber that repeated it would be blocking it twice. `rax` is a clobber here
1350        // rather than the result, which is the same register number in the other file and is the
1351        // whole reason the two lists are counted apart.
1352        assert_eq!(written.iter().filter(|name| *name == "xmm0").count(), 1);
1353        assert!(written.contains(&"rax".to_string()));
1354    }
1355
1356    #[test]
1357    fn a_call_at_a_width_no_register_holds_is_reported_on_either_side() {
1358        let i128 = Type::int(128);
1359        assert_eq!(
1360            make(&[i128], &[], false, &SYSV).2,
1361            Err(Refused { argument: Some(0), missing: Missing::Width })
1362        );
1363        assert_eq!(
1364            make(&[], &[i128], false, &SYSV).2,
1365            Err(Refused { argument: None, missing: Missing::Width })
1366        );
1367    }
1368
1369    #[test]
1370    fn an_argument_wider_than_a_register_has_no_name() {
1371        assert_eq!(head_of(Type::int(128)), None);
1372        assert_eq!(head_of(Type::int(8)), Some("x64.arg_val_8"));
1373        assert_eq!(head_of(Type::int(64)), Some("x64.arg_val_64"));
1374    }
1375
1376    /// An address arrives in a general purpose register like any other integer of its width, and
1377    /// used to be turned away here as a width no register holds, which is what issue 274 is.
1378    /// `int g(char *s)` is the smallest program that was.
1379    #[test]
1380    fn an_address_arrives_in_a_register_like_the_integer_it_is() {
1381        assert_eq!(head_of(Type::PTR), Some("x64.arg_val_64"));
1382        assert_eq!(
1383            bind(&[Type::PTR], &SYSV),
1384            "mfunc @f {\nblock0:\n    %0:gpr($rdi) = x64.arg_val_64\n}\n"
1385        );
1386        // And it travels the same way at a call, on both sides of one.
1387        assert!(make(&[Type::PTR], &[Type::PTR], false, &SYSV).2.is_ok());
1388    }
1389}