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