rucc_codegen/expand.rs
1//! The IR rewrites the machine needs before a rule can be asked anything.
2//!
3//! Design: `spec/10-backend.md` section 10.2, which is where the ordering comes from.
4//!
5//! Everything else in this crate turns an instruction into instructions. There are two things a
6//! rule cannot do, and one of them is dealt with here and one next door.
7//!
8//! The one next door is a new shape of control flow. A rule replaces a term with a term and the
9//! replacement has nowhere to put a block, so a construct that becomes blocks has to be rewritten
10//! before selection rather than during it. There is one such construct and it is `switch`. Every
11//! other terminator leaves a block with one successor or two, which is what the block layout
12//! writes jumps for, and a `switch` leaves it with as many as the program had cases. What it
13//! becomes is a decision large enough to have a document of its own, so it has a module of its
14//! own, which is [`crate::switch`].
15//!
16//! The one here is arithmetic on what a rule matched. A rule may name a constant and pass it along,
17//! and it may not add to one or read it as something else, because the pattern language is a
18//! pattern language and giving it a way to compute would make a rule set a program the solver has
19//! to reason about rather than a table it can check a line of at a time. So an instruction whose
20//! lowering needs a value worked out from another one is rewritten here into instructions whose
21//! lowerings do not. Four of them are floats: a float constant, a negation, and the two conversions
22//! between a float and an unsigned integer. The other two move a block of memory, where the
23//! arithmetic is the offset of each word from the front of it.
24//!
25//! # Why a copy is a run of moves and not a call
26//!
27//! A `memcpy` in the IR is not a call to `memcpy`. It is what the front end writes for a structure
28//! assigned, passed or returned by value, and a `memset` is what it writes for the part of an
29//! object an initialiser left unnamed, so a program with a `struct` in it reaches one almost at
30//! once and the size is a constant every time.
31//!
32//! A constant size is what makes the moves the right answer. A four byte copy written as a call
33//! costs the call and the two arguments and gives back four bytes moved, which is more instructions
34//! than the move it replaced and slower than all of them. Every real compiler writes the moves
35//! under some threshold for that reason, and above the threshold writes the call, which is where
36//! this stops: the call needs a `memcpy` to exist, and a statically linked program has nowhere to
37//! get one from until the compiler runtime in tamnd/rucc#277 exists. So a copy larger than the
38//! threshold is refused by name rather than written wrong.
39
40use std::cmp::Ordering;
41use std::collections::HashMap;
42
43use rucc_base::{Idx, Interner};
44use rucc_ir::{
45 CallInfo, Def, Extra, Flags, FloatPred, Func, Imm, Inst, InstData, IntPred, MemInfo, MemOrder,
46 Opcode, Signature, Type, Value,
47};
48
49use crate::capability;
50
51/// Rewrites every ordered access into the plain access this machine already makes ordered, and
52/// leaves a barrier where the machine needs one.
53///
54/// This is the one pass here whose reason is a memory model rather than a missing instruction, so
55/// it is worth writing down what the model says. x86-64 is total store order. Every load is an
56/// acquire, every store is a release, and an aligned access no wider than a word is indivisible
57/// whether or not anybody asked for one. So an `atomic_load` at any ordering is the same `mov` a
58/// `load` is, and so is an `atomic_store` at every ordering except the strongest, and rewriting
59/// them into the plain access is not an approximation: it is the whole of what the machine does.
60///
61/// The one thing total store order does not give is a store followed by a load of a different
62/// address staying in that order, and that is exactly what sequential consistency is missing. So a
63/// sequentially consistent store is the same `mov` with an `mfence` behind it, which is the pair
64/// gcc 16.2.0 writes. The fence is left in the IR as a `fence` rather than written here, because
65/// what a barrier costs is a target question and [`crate::lower`] is where the target answers are.
66///
67/// A `fence` the program wrote is left alone for the same reason. Every ordering below the
68/// strongest is nothing at all on this machine and the strongest is one instruction, and both of
69/// those are decided by name in [`crate::lower`] where the instruction lives.
70///
71/// # Why the width is checked
72///
73/// An access is only indivisible if the machine can do it in one go, which here means one, two,
74/// four or eight bytes at an address aligned to its own width. Anything else is a run of accesses
75/// and a run of accesses is not atomic at all, so it is left as the opcode it was and no rule
76/// covers it, which is a compile error naming the instruction. That is the right answer: an
77/// atomic access the machine cannot make atomic has no correct lowering, and a wrong one that
78/// looks right is worse than a refusal. C says the same thing through `__atomic_is_lock_free`.
79///
80/// `word` is how many bytes the widest indivisible access carries, which is the same number the
81/// widest move carries and is read from the machine for the reason [`bulk`] reads it.
82///
83/// # A machine that is not total store order
84///
85/// All of the above is x86-64, and `total_store_order` says whether it holds. AArch64 lets a plain
86/// load or store move past the accesses around it, so there only a relaxed access is the plain
87/// one. An acquire, a release and anything stronger are left as the ordered access they are, and
88/// [`crate::lower`] writes each as the `ldar` or `stlr` the machine has for it. A sequentially
89/// consistent store needs no fence behind it there either, because `stlr` already keeps it in
90/// order with a later `ldar`, which is the one pair a fence would be for.
91///
92/// This runs before every other pass here, so that what it produces is an ordinary load or store
93/// that the width legalisation and everything after it get to see. An ordered access at a width the
94/// machine has no register for would otherwise be a shape nothing later understands, since every
95/// pass after this one is written about `load` and `store` by name.
96pub fn orderings(func: &mut Func, word: u32, total_store_order: bool) {
97 let found: Vec<Inst> =
98 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
99 for inst in found {
100 let plain = match func[inst].opcode {
101 Opcode::AtomicLoad => Opcode::Load,
102 Opcode::AtomicStore => Opcode::Store,
103 _ => continue,
104 };
105 if !total_store_order && !unordered(func, inst) {
106 continue;
107 }
108 relaxed(func, inst, plain, word);
109 }
110}
111
112/// Whether an ordered access asks for nothing beyond being done in one go.
113fn unordered(func: &Func, inst: Inst) -> bool {
114 let Extra::Mem(mem) = func[inst].extra else { return false };
115 matches!(func[mem].order, MemOrder::NotAtomic | MemOrder::Relaxed)
116}
117
118/// One ordered access as the plain one, with a barrier behind it when the ordering asked for more
119/// than the machine gives for free.
120///
121/// The ordering is taken off the access rather than left on it, because the IR verifier refuses an
122/// ordering on a plain access, and it refuses one for a good reason: a plain load may be moved,
123/// duplicated and dropped, and an ordering that survived on one would be a claim nothing downstream
124/// honours. What is true after this pass is that the ordering has been discharged, and the way to
125/// say that is to stop carrying it.
126///
127/// A sequentially consistent store becomes the store and then the fence, and it is built that way
128/// round: the plain store is put in front of the instruction and the instruction itself becomes the
129/// fence. Doing it the other way would need somewhere to insert behind an instruction, and there is
130/// nothing to gain from having two ways to insert.
131fn relaxed(func: &mut Func, inst: Inst, plain: Opcode, word: u32) {
132 let Extra::Mem(mem) = func[inst].extra else { return };
133 let info = func[mem];
134 let ty = match plain {
135 Opcode::Store => match func[func[inst].args].first() {
136 Some(&value) => func[value].ty,
137 None => return,
138 },
139 _ => produced(func, inst),
140 };
141 if !indivisible(ty, info, word) {
142 return;
143 }
144 let unordered = MemInfo { order: MemOrder::NotAtomic, ..info };
145
146 if plain == Opcode::Store && info.order == MemOrder::SeqCst {
147 let [value, addr] = func[func[inst].args] else { return };
148 write(func, inst, value, addr, unordered);
149 let none = func.push_values(&[]);
150 let data = &mut func[inst];
151 data.opcode = Opcode::Fence;
152 data.args = none;
153 data.extra = Extra::Order(MemOrder::SeqCst);
154 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Fence));
155 return;
156 }
157
158 let plainly = func.add_mem(unordered);
159 let data = &mut func[inst];
160 data.opcode = plain;
161 data.extra = Extra::Mem(plainly);
162 data.flags = data.flags.intersection(Flags::legal_on(plain));
163}
164
165/// Whether this machine does an access of this type in one go.
166///
167/// One, two, four or eight bytes, at an address aligned to at least that many. The alignment is the
168/// front end's answer for the type being accessed, which for every type C can spell is its own
169/// width, so what this actually refuses is a `long double` and an access the program underaligned
170/// on purpose.
171///
172/// The width is the storage the value takes and not the bits it holds, because that is what the
173/// access moves. A `bool` is one bit of value in one byte of memory and one byte is indivisible, so
174/// rounding up is what makes an ordered access of one work rather than a refusal nobody wanted. An
175/// address is the exception the other way: the IR gives a pointer no width at all, since how wide
176/// one is belongs to the target, so the target's number is used for it.
177fn indivisible(ty: Type, info: MemInfo, word: u32) -> bool {
178 let bytes = if ty.is_ptr() { word } else { ty.bits().div_ceil(8) };
179 ty.is_scalar() && bytes.is_power_of_two() && bytes <= word && info.align >= bytes
180}
181
182/// Rewrites the float instructions no rule can be written for, and leaves the rest alone.
183///
184/// Each of them needs a value worked out from one the pattern matched, which is the one thing the
185/// rule language deliberately cannot do. A float constant is an integer constant read as a float,
186/// and reading it is arithmetic on the immediate. A negation is an exclusive or with a mask that
187/// depends on the format. A conversion between a float and an integer is that conversion at a
188/// width the machine has, which is a width neither the pattern nor the replacement can work out.
189///
190/// What is left after this is a function whose float instructions are each one machine
191/// instruction, so what a rule is asked stays a table. The conversions between a float and an
192/// unsigned sixty four bit integer are the two that are not a widening or a narrowing away from a
193/// signed one, because there is no signed width that holds those values, and each gets a rewrite
194/// of its own below.
195pub fn floats(func: &mut Func) {
196 let found: Vec<Inst> =
197 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
198 for inst in found {
199 match func[inst].opcode {
200 Opcode::FConst => constant(func, inst),
201 Opcode::FNeg => negate(func, inst),
202 Opcode::SIToFP | Opcode::UIToFP => widen_then_convert(func, inst),
203 Opcode::FPToSI | Opcode::FPToUI => convert_then_narrow(func, inst),
204 _ => {}
205 }
206 }
207}
208
209/// A float constant, as the integer that spells it and a reading of those bits as the float.
210///
211/// This is the whole of what a `movsd` from a literal would be if there were a section to put the
212/// literal in, and there is not one yet. Two instructions in a register beats a constant pool that
213/// nothing else needs, and it is exactly what the bits of the immediate already say, since the IR
214/// holds a float constant as its bit pattern rather than as a number.
215///
216/// Not above sixty four bits, for the reason [`negate`] is not: the integer that would spell an
217/// eighty bit constant has no register either, so the exchange gains nothing. A back end with a
218/// float that wide writes the bits where the value lives, which for this one is a stack slot.
219fn constant(func: &mut Func, inst: Inst) {
220 let ty = produced(func, inst);
221 let Extra::Imm(imm) = func[inst].extra else { return };
222 if !ty.is_float() || !ty.is_scalar() || ty.bits() > 64 {
223 return;
224 }
225 let int = Type::int(ty.bits());
226 let bits = func[imm].bits();
227 // The cast is the bits as they are stored, and `Imm::int` keeps the width, so a constant whose
228 // top bit is set stays the negative integer that spells it rather than becoming a wider one.
229 let spelled = ahead_const(func, inst, Imm::int(bits as i128, int), int);
230 becomes(func, inst, Opcode::Bitcast, &[spelled]);
231}
232
233/// A negation, as an exclusive or with the sign bit.
234///
235/// C says negation flips the sign and says nothing else about it, which is not what subtracting
236/// from zero does to a zero or to a not a number, so this is the operation the IR already calls
237/// out as not being `0 - x`. Flipping the bit is the whole of it, and it is right for every value
238/// a float can hold, the payload of a not a number included, because no other bit is touched.
239///
240/// The bit is flipped in a general purpose register rather than in the one the float is in. The
241/// other way is one instruction rather than three and it wants the mask in memory aligned to the
242/// register, which is the same section a constant pool would need.
243///
244/// Not above sixty four bits, where the exchange stops being one. An `i80` is as far from a
245/// register as an `f80` is, so what this would hand the back end is three instructions it cannot
246/// write instead of one it can: a machine with a float that wide has a sign flip for it, because a
247/// machine with no way to flip the sign of its own widest float would be a strange machine.
248fn negate(func: &mut Func, inst: Inst) {
249 let ty = produced(func, inst);
250 let Some(&arg) = func[func[inst].args].first() else { return };
251 if !ty.is_float() || !ty.is_scalar() || ty.bits() > 64 {
252 return;
253 }
254 let int = Type::int(ty.bits());
255 let bits = ahead(func, inst, Opcode::Bitcast, &[arg], int);
256 let mask = ahead_const(func, inst, Imm::int(1i128 << (ty.bits() - 1), int), int);
257 let flipped = ahead(func, inst, Opcode::Xor, &[bits, mask], int);
258 becomes(func, inst, Opcode::Bitcast, &[flipped]);
259}
260
261/// An integer becoming a float, as a widening and the signed conversion at a width there is one at.
262///
263/// The widening is with the sign for a signed integer and with zeroes for an unsigned one, and
264/// after it the value is the same number in a signed integer the machine converts from, so the
265/// conversion is the same value and the same rounding. That is the whole of why the machine needs
266/// no unsigned conversion and none at a width narrower than an `int`.
267fn widen_then_convert(func: &mut Func, inst: Inst) {
268 let signed = func[inst].opcode == Opcode::SIToFP;
269 let Some(&arg) = func[func[inst].args].first() else { return };
270 let from = func[arg].ty;
271 if !from.is_int() || !from.is_scalar() {
272 return;
273 }
274 let Some(width) = holder(from.bits(), signed) else {
275 from_unsigned_word(func, inst, arg, from);
276 return;
277 };
278 if width == from.bits() {
279 return;
280 }
281 let widen = if signed { Opcode::SExt } else { Opcode::ZExt };
282 let wide = ahead(func, inst, widen, &[arg], Type::int(width));
283 becomes(func, inst, Opcode::SIToFP, &[wide]);
284}
285
286/// A float becoming an integer, as the signed conversion at such a width and a narrowing.
287///
288/// The same argument the other way round. A float the program says fits in the integer it asked
289/// for fits in the signed one that holds every value of it, so converting there and keeping the
290/// low bits is that value however it is read, and a float that does not fit is undefined in C and
291/// unspecified in the model at either width.
292fn convert_then_narrow(func: &mut Func, inst: Inst) {
293 let signed = func[inst].opcode == Opcode::FPToSI;
294 let ty = produced(func, inst);
295 let Some(&arg) = func[func[inst].args].first() else { return };
296 if !ty.is_int() || !ty.is_scalar() {
297 return;
298 }
299 let Some(width) = holder(ty.bits(), signed) else {
300 to_unsigned_word(func, inst, arg, ty);
301 return;
302 };
303 if width == ty.bits() {
304 return;
305 }
306 let wide = ahead(func, inst, Opcode::FPToSI, &[arg], Type::int(width));
307 becomes(func, inst, Opcode::Trunc, &[wide]);
308}
309
310/// An unsigned sixty four bit integer becoming a float, without a branch.
311///
312/// This is the one conversion into a float that is not the signed one at some width, because there
313/// is no signed width that holds every value of it. What the machine can do is the signed
314/// conversion, so the value has to be brought under half of its range first and put back after.
315///
316/// Halving it is a shift, and a shift throws away the bit it shifts out, which is the difference
317/// between a number that rounds up and one that rounds down. So the bit is put back as the lowest
318/// bit of the half: a half that was exact stays exact, and one that was not comes out odd, which is
319/// never a value the conversion rounds to and so never a value it rounds the wrong way from. That
320/// is round to odd, and rounding to odd and then to nearest is the same answer as rounding to
321/// nearest once, at every width a float here has. Doubling afterwards is exact, since a float
322/// multiplied by two is the same digits with one more on the exponent and nothing here is near the
323/// top of the range.
324///
325/// A value whose top bit is clear needs none of that and is the signed conversion as it stands, so
326/// there are two answers and the machine has to pick one. gcc writes a branch. This writes the
327/// choice as arithmetic, because a branch here would mean splitting the block this instruction is
328/// in, and every rewrite in this pass stays inside one block. A mask that is every bit or no bit
329/// picks the source, and the same mask over the bits of the result picks between doubling it and
330/// adding a zero to it. That is more instructions than gcc's and no branch to predict wrong.
331fn from_unsigned_word(func: &mut Func, inst: Inst, arg: Value, from: Type) {
332 let ty = produced(func, inst);
333 if !ty.is_float() || !ty.is_scalar() {
334 return;
335 }
336 if ty.bits() > 64 {
337 from_unsigned_word_wide(func, inst, arg, from);
338 return;
339 }
340 let spread = spread_top_bit(func, inst, arg, from);
341
342 // The value halved, with the bit the halving lost put back as the lowest bit of it.
343 let one = ahead_const(func, inst, Imm::int(1, from), from);
344 let lost = ahead(func, inst, Opcode::And, &[arg, one], from);
345 let half = ahead(func, inst, Opcode::LShr, &[arg, one], from);
346 let odd = ahead(func, inst, Opcode::Or, &[half, lost], from);
347
348 // The source, as the value with the difference between the two conditionally taken out of it.
349 let differ = ahead(func, inst, Opcode::Xor, &[arg, odd], from);
350 let taken = ahead(func, inst, Opcode::And, &[differ, spread], from);
351 let source = ahead(func, inst, Opcode::Xor, &[arg, taken], from);
352 let converted = ahead(func, inst, Opcode::SIToFP, &[source], ty);
353
354 // The doubling, as the result added to itself or to a zero. The mask is the same one narrowed
355 // to the width of the float, since the top bit it came from is a fact about the integer.
356 let bits = Type::int(ty.bits());
357 let narrow = same_width(func, inst, spread, from, bits);
358 let raw = ahead(func, inst, Opcode::Bitcast, &[converted], bits);
359 let again = ahead(func, inst, Opcode::And, &[raw, narrow], bits);
360 let addend = ahead(func, inst, Opcode::Bitcast, &[again], ty);
361 becomes(func, inst, Opcode::FAdd, &[converted, addend]);
362}
363
364/// A float becoming an unsigned sixty four bit integer, without a branch.
365///
366/// The same argument the other way round, and the same reason there is no branch. A float below
367/// half the range converts as the signed one and is already the answer. One at or above it has half
368/// the range subtracted first, which is exact because the two have the same exponent or a smaller
369/// one, converts into the signed integer that now holds it, and gets the top bit put back on.
370///
371/// The subtraction is of a constant that is either half the range or a positive zero, which is the
372/// same mask trick as above written over the bits of the float, and subtracting a positive zero
373/// leaves every value alone including a negative zero. A float too big for the answer, or a not a
374/// number, is undefined in C and unspecified in the model, so the comparison being false for a not
375/// a number costs nothing: it takes the path whose answer was never promised either way.
376fn to_unsigned_word(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
377 let from = func[arg].ty;
378 if !from.is_float() || !from.is_scalar() {
379 return;
380 }
381 if from.bits() > 64 {
382 to_unsigned_word_wide(func, inst, arg, ty);
383 return;
384 }
385 // Half the range, as the float that spells it and the bits that spell the float.
386 let bits = Type::int(from.bits());
387 let pattern = Imm::int(half_the_range(from.bits()), bits);
388 let spelled = ahead_const(func, inst, pattern, bits);
389 let half = ahead(func, inst, Opcode::Bitcast, &[spelled], from);
390
391 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
392 let wide = ahead(func, inst, Opcode::ZExt, &[over], bits);
393 let zero = ahead_const(func, inst, Imm::int(0, bits), bits);
394 let spread = ahead(func, inst, Opcode::Sub, &[zero, wide], bits);
395
396 let amount = ahead(func, inst, Opcode::And, &[spread, spelled], bits);
397 let taken = ahead(func, inst, Opcode::Bitcast, &[amount], from);
398 let under = ahead(func, inst, Opcode::FSub, &[arg, taken], from);
399 let low = ahead(func, inst, Opcode::FPToSI, &[under], ty);
400
401 // The top bit back on, from the same comparison at the width of the answer.
402 let again = ahead(func, inst, Opcode::ZExt, &[over], ty);
403 let up = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
404 let top = ahead(func, inst, Opcode::Shl, &[again, up], ty);
405 becomes(func, inst, Opcode::Xor, &[low, top]);
406}
407
408/// An unsigned sixty four bit integer becoming a float wider than that, without a branch.
409///
410/// Neither sequence above can be written at this width, and for the same reason both of them end in
411/// a `bitcast`: the mask that picks between the two answers is laid over the bits of the result, and
412/// a float this wide has no integer holding its bits any more than it has a register holding it. So
413/// the choice has to be made somewhere other than in the bits, and the somewhere is the float
414/// arithmetic itself.
415///
416/// What replaces it is also smaller than what it replaces, because a float this wide has sixty four
417/// bits of significand and so holds every value of a sixty four bit integer exactly. Nothing rounds,
418/// so there is nothing to round to odd first, and the halving and the doubling both go away. The
419/// value is converted as a signed integer, which is the number when the top bit is clear and the
420/// number less two to the sixty fourth when it is set, and that constant is added back in the second
421/// case. Both of those additions are exact, since either operand of one is a value the significand
422/// holds and so is the answer.
423///
424/// The choice is a comparison turned into a one or a zero, converted into a float and multiplied by
425/// the constant, which is where the mask would have been. A float times one is itself and a float
426/// times a positive zero is a positive zero, so what the addition gets is the constant or a zero it
427/// leaves every value alone including a negative zero, and the conversion never produces one of
428/// those anyway.
429fn from_unsigned_word_wide(func: &mut Func, inst: Inst, arg: Value, from: Type) {
430 let ty = produced(func, inst);
431 let zero = ahead_const(func, inst, Imm::int(0, from), from);
432 let over = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
433
434 let signed = ahead(func, inst, Opcode::SIToFP, &[arg], ty);
435 let range = ahead_float(func, inst, two_to_the(64), ty);
436 let flag = flag_as_float(func, inst, over, ty);
437 let addend = ahead(func, inst, Opcode::FMul, &[range, flag], ty);
438 becomes(func, inst, Opcode::FAdd, &[signed, addend]);
439}
440
441/// A float wider than sixty four bits becoming an unsigned sixty four bit integer, without a branch.
442///
443/// The same argument the other way round and the same answer to it. The shape is the sequence above
444/// this one with the two `bitcast`s gone: half the range is a constant of the float's own type
445/// rather than an integer read as one, and the conditional subtraction is that constant multiplied
446/// by a one or a zero rather than masked with one.
447///
448/// Subtracting is exact here for the reason it is at the narrower widths, since the value is at
449/// least as large as what is taken off it. A value too big for the answer, or a not a number, takes
450/// the path whose answer C never promised, which is the same place the comparison being false for a
451/// not a number puts it.
452fn to_unsigned_word_wide(func: &mut Func, inst: Inst, arg: Value, ty: Type) {
453 let from = func[arg].ty;
454 let half = ahead_float(func, inst, two_to_the(63), from);
455 let over = ahead_cmp(func, inst, Opcode::FCmp, Extra::FloatPred(FloatPred::Oge), &[arg, half]);
456
457 let flag = flag_as_float(func, inst, over, from);
458 let taken = ahead(func, inst, Opcode::FMul, &[half, flag], from);
459 let under = ahead(func, inst, Opcode::FSub, &[arg, taken], from);
460 let low = ahead(func, inst, Opcode::FPToSI, &[under], ty);
461
462 // The top bit back on, from the same comparison at the width of the answer.
463 let again = ahead(func, inst, Opcode::ZExt, &[over], ty);
464 let up = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
465 let top = ahead(func, inst, Opcode::Shl, &[again, up], ty);
466 becomes(func, inst, Opcode::Xor, &[low, top]);
467}
468
469/// A condition as a float that is a one or a positive zero, which is what stands in for a mask.
470///
471/// The widening is to sixty four bits rather than to whatever the float came from, since the value
472/// is a one or a zero and the conversion wants an integer the machine converts from. Neither of the
473/// two numbers is anywhere near needing rounding.
474fn flag_as_float(func: &mut Func, inst: Inst, cond: Value, ty: Type) -> Value {
475 let wide = ahead(func, inst, Opcode::ZExt, &[cond], Type::int(64));
476 ahead(func, inst, Opcode::SIToFP, &[wide], ty)
477}
478
479/// The bits of the eighty bit float that is two to this power.
480///
481/// The significand of a power of two is the leading bit and nothing else, which in this format is
482/// written down rather than implied, and the exponent is the power with the bias on it.
483const fn two_to_the(power: u32) -> u128 {
484 ((0x3fff + power as u128) << 64) | 0x8000_0000_0000_0000
485}
486
487/// The top bit of an integer spread over every bit of one, which is every bit or no bit.
488///
489/// A comparison against zero rather than a shift, because the answer wanted is a mask and the
490/// machine writes a mask out of a condition the same way either way, and the comparison says what
491/// the question was.
492fn spread_top_bit(func: &mut Func, inst: Inst, arg: Value, ty: Type) -> Value {
493 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
494 let set = ahead_cmp(func, inst, Opcode::ICmp, Extra::IntPred(IntPred::Slt), &[arg, zero]);
495 let wide = ahead(func, inst, Opcode::ZExt, &[set], ty);
496 ahead(func, inst, Opcode::Sub, &[zero, wide], ty)
497}
498
499/// A value brought to another integer width, and itself when the two are already the same.
500fn same_width(func: &mut Func, inst: Inst, value: Value, from: Type, to: Type) -> Value {
501 match to.bits().cmp(&from.bits()) {
502 Ordering::Equal => value,
503 Ordering::Less => ahead(func, inst, Opcode::Trunc, &[value], to),
504 Ordering::Greater => ahead(func, inst, Opcode::SExt, &[value], to),
505 }
506}
507
508/// The bits of the float of this width that is two to the sixty third.
509///
510/// Half of what an unsigned sixty four bit integer holds, which is the one number both conversions
511/// above are written around. The exponent is biased and the significand is zero in both formats,
512/// so it is the bias plus sixty three shifted up past the significand.
513fn half_the_range(width: u32) -> i128 {
514 match width {
515 32 => 0x5F00_0000,
516 _ => 0x43E0_0000_0000_0000,
517 }
518}
519
520/// Rewrites every byte swap into the shifts and masks that are one, and leaves the rest alone.
521///
522/// A byte swap is a rule on a machine that has the instruction and this everywhere else, and until
523/// `x64.bswap` is a term the model knows about, this is what x86-64 gets too. That is tamnd/rucc#307
524/// and the whole of what is left of it: what is written below is correct at every width and slower
525/// than the one instruction, which is the trade `spec/10-backend.md` section 10.3 says the fast path
526/// makes everywhere.
527///
528/// It is here rather than in the front end because the masks are worked out from the width, and
529/// arithmetic on a value a pattern matched is the one thing the rule language deliberately cannot
530/// do. It is here rather than in the walk to the IR because a byte swap is one instruction in the
531/// IR and should stay one for as long as anything is reading the IR, so that the day the rule
532/// exists nothing above the backend has to change.
533pub fn bytes(func: &mut Func) {
534 let found: Vec<Inst> =
535 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
536 for inst in found {
537 if func[inst].opcode == Opcode::Bswap {
538 swap(func, inst);
539 }
540 }
541}
542
543/// One byte swap, as a halving run of swaps of adjacent groups of bits.
544///
545/// Reversing eight bytes is swapping the two halves, then the two halves of each half, then the two
546/// bytes of each of those, and the three steps commute because each is a permutation of positions
547/// the others do not touch. So the run goes from the widest group down to a byte, and every step is
548/// the same five instructions: keep the even numbered groups, move them up, move the odd numbered
549/// ones down, keep those, and put the two together.
550///
551/// Nine instructions for two bytes, seventeen for four, twenty five for eight, before the constants.
552/// Writing it as a shift and a mask per byte instead is fewer steps to read and more instructions at
553/// every width above two, since the cost there grows with the number of bytes rather than with the
554/// logarithm of it.
555///
556/// A width that is not a whole number of bytes is left alone. The verifier does not allow one, and
557/// silently reversing something else would be worse than the instruction surviving to a selector
558/// that has no rule for it and says so.
559fn swap(func: &mut Func, inst: Inst) {
560 let ty = produced(func, inst);
561 let Some(&arg) = func[func[inst].args].first() else { return };
562 if !ty.is_int() || !ty.is_scalar() || ty.bits() < 16 || ty.bits() % 8 != 0 {
563 return;
564 }
565
566 let mut value = arg;
567 let mut group = ty.bits() / 2;
568 while group >= 8 {
569 // The pattern that keeps every other run of `group` bits, counting the run at the bottom as
570 // the first one kept. It is what says which half of each pair moves up and which moves down.
571 let mask = alternating(ty.bits(), group);
572 let keep = ahead_const(func, inst, Imm::int(mask, ty), ty);
573 let count = ahead_const(func, inst, Imm::int(i128::from(group), ty), ty);
574 let low = ahead(func, inst, Opcode::And, &[value, keep], ty);
575 let up = ahead(func, inst, Opcode::Shl, &[low, count], ty);
576 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
577 let high = ahead(func, inst, Opcode::And, &[down, keep], ty);
578 // The last step of the last round is the instruction itself, so the value everything
579 // downstream already reads is the answer and nothing has to be substituted.
580 if group == 8 {
581 becomes(func, inst, Opcode::Or, &[up, high]);
582 return;
583 }
584 value = ahead(func, inst, Opcode::Or, &[up, high], ty);
585 group /= 2;
586 }
587}
588
589/// The mask that keeps every other run of `group` bits out of `width` of them, starting with the
590/// run at the bottom.
591///
592/// Sixteen bits in groups of eight is `0x00ff`, thirty two in groups of eight is `0x00ff00ff`, and
593/// thirty two in groups of sixteen is `0x0000ffff`. Built rather than written down because there is
594/// one of these per width per group and a table of them is a table to get wrong.
595///
596/// The top group is always one of the dropped ones, since the run at the bottom is kept and the
597/// width is an even number of groups, so the answer never has its sign bit set and reads the same
598/// as a number as it does as a pattern.
599fn alternating(width: u32, group: u32) -> i128 {
600 every(width, group * 2, group)
601}
602
603/// The pattern with the low `run` bits of every `step` bit group set, out of `width` of them.
604///
605/// `every(32, 2, 1)` is `0x55555555` and `every(64, 8, 1)` is `0x0101010101010101`. Built rather
606/// than written down for the reason the byte swap masks are: there is one of these per width per
607/// group and a table of them is a table to get wrong.
608///
609/// The top group is never a full one when `run` is less than `step`, so the answer never has its
610/// sign bit set and reads the same as a number as it does as a pattern.
611fn every(width: u32, step: u32, run: u32) -> i128 {
612 let ones = (1i128 << run) - 1;
613 let mut mask = 0i128;
614 let mut at = 0;
615 while at < width {
616 mask |= ones << at;
617 at += step;
618 }
619 mask
620}
621
622/// Rewrites every bit count into the arithmetic that is one, and leaves the rest alone.
623///
624/// Three instructions and no rules, which is tamnd/rucc#310. `popcnt` is one instruction on a
625/// machine that has it and `bsr` and `bsf` are the two searches, and none of the three is a term the
626/// model knows about yet, so what runs today is what runs everywhere. The trade is the one
627/// `spec/10-backend.md` section 10.3 describes and `expand::bytes` above makes for the same reason:
628/// slower than the instruction, right on every target, and built only out of rules the verifier has
629/// already discharged.
630///
631/// The two searches are rewritten first, into a set bit count and a little arithmetic, and then
632/// every set bit count is rewritten. That is one pass rather than two because the second sweep picks
633/// up what the first one wrote, and it means there is one place that knows how to count bits rather
634/// than three.
635pub fn counts(func: &mut Func) {
636 let found: Vec<Inst> =
637 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
638 for inst in found {
639 match func[inst].opcode {
640 Opcode::Ctlz => searched(func, inst, true),
641 Opcode::Cttz => searched(func, inst, false),
642 _ => {}
643 }
644 }
645 let found: Vec<Inst> =
646 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
647 for inst in found {
648 if func[inst].opcode == Opcode::Ctpop {
649 counted(func, inst);
650 }
651 }
652}
653
654/// A leading or trailing zero count, as the set bit count of a value with those zeroes turned into
655/// the only bits that are set.
656///
657/// For trailing zeroes that is `~x & (x - 1)`, which is exactly the run of zeroes below the lowest
658/// set bit and nothing else, because `x - 1` sets that run and clears the bit above it while `~x`
659/// keeps only positions `x` did not have.
660///
661/// For leading zeroes it is the same idea upside down. Smearing every set bit downwards, by folding
662/// the value into itself shifted right by one, two, four and so on, leaves ones everywhere at or
663/// below the highest set bit, so the complement is exactly the leading zeroes. That is five extra
664/// steps at thirty two bits and six at sixty four, which is why the search instruction is worth
665/// having and why #310 stays open for it.
666///
667/// Both answer the width for a zero argument, which is what they have to answer for `ffs` to be
668/// masked correctly and is more than C asks for: `__builtin_clz(0)` and `__builtin_ctz(0)` are
669/// undefined, so nothing may rely on this, and the point of writing it down is that it is defined
670/// here rather than being whatever a register happened to hold.
671fn searched(func: &mut Func, inst: Inst, leading: bool) {
672 let ty = produced(func, inst);
673 let Some(&arg) = func[func[inst].args].first() else { return };
674 if !countable(ty) {
675 return;
676 }
677 let ones = ahead_const(func, inst, Imm::int(-1, ty), ty);
678 if leading {
679 let mut value = arg;
680 let mut by = 1;
681 while by < ty.bits() {
682 let count = ahead_const(func, inst, Imm::int(i128::from(by), ty), ty);
683 let down = ahead(func, inst, Opcode::LShr, &[value, count], ty);
684 value = ahead(func, inst, Opcode::Or, &[value, down], ty);
685 by *= 2;
686 }
687 let above = ahead(func, inst, Opcode::Xor, &[value, ones], ty);
688 becomes(func, inst, Opcode::Ctpop, &[above]);
689 return;
690 }
691 let missing = ahead(func, inst, Opcode::Xor, &[arg, ones], ty);
692 let less = ahead(func, inst, Opcode::Add, &[arg, ones], ty);
693 let below = ahead(func, inst, Opcode::And, &[missing, less], ty);
694 becomes(func, inst, Opcode::Ctpop, &[below]);
695}
696
697/// One set bit count, as the halving sum every bit counting routine is written as.
698///
699/// Adjacent bits are added into pairs, pairs into nibbles, nibbles into bytes, and then the bytes
700/// are added together at once by a multiply whose top byte is their sum. The first step is written
701/// as a subtraction rather than as two masks and an add, which is the usual form and is one
702/// instruction shorter: a two bit field minus its own high bit is the number of bits set in it.
703///
704/// Twelve instructions and four constants at sixty four bits, against one `popcnt`, which is the
705/// size of what #310 is worth.
706///
707/// The multiply is the last step only because the byte sums are each at most eight and there are at
708/// most eight of them, so the running total in the top byte cannot carry out of it. At eight bits
709/// there are no bytes to add and the third step is already the answer.
710fn counted(func: &mut Func, inst: Inst) {
711 let ty = produced(func, inst);
712 let Some(&arg) = func[func[inst].args].first() else { return };
713 if !countable(ty) {
714 return;
715 }
716 let width = ty.bits();
717 let pairs = ahead_const(func, inst, Imm::int(alternating(width, 1), ty), ty);
718 let two = ahead_const(func, inst, Imm::int(2, ty), ty);
719 let one = ahead_const(func, inst, Imm::int(1, ty), ty);
720 let high = ahead(func, inst, Opcode::LShr, &[arg, one], ty);
721 let odd = ahead(func, inst, Opcode::And, &[high, pairs], ty);
722 let bits = ahead(func, inst, Opcode::Sub, &[arg, odd], ty);
723
724 let quads = ahead_const(func, inst, Imm::int(alternating(width, 2), ty), ty);
725 let low = ahead(func, inst, Opcode::And, &[bits, quads], ty);
726 let up = ahead(func, inst, Opcode::LShr, &[bits, two], ty);
727 let rest = ahead(func, inst, Opcode::And, &[up, quads], ty);
728 let nibbles = ahead(func, inst, Opcode::Add, &[low, rest], ty);
729
730 let four = ahead_const(func, inst, Imm::int(4, ty), ty);
731 let bytes = ahead_const(func, inst, Imm::int(alternating(width, 4), ty), ty);
732 let folded = ahead(func, inst, Opcode::LShr, &[nibbles, four], ty);
733 let summed = ahead(func, inst, Opcode::Add, &[nibbles, folded], ty);
734 if width == 8 {
735 becomes(func, inst, Opcode::And, &[summed, bytes]);
736 return;
737 }
738 let held = ahead(func, inst, Opcode::And, &[summed, bytes], ty);
739
740 let spread = ahead_const(func, inst, Imm::int(every(width, 8, 1), ty), ty);
741 let top = ahead_const(func, inst, Imm::int(i128::from(width - 8), ty), ty);
742 let total = ahead(func, inst, Opcode::Mul, &[held, spread], ty);
743 becomes(func, inst, Opcode::LShr, &[total, top]);
744}
745
746/// Rewrites every overflow checked instruction into the arithmetic and the test that is one.
747///
748/// Six instructions and no rules, which is tamnd/rucc#309. The trade is the one `expand::bytes` and
749/// `expand::counts` above make, with one thing on top of it: these are the only instructions in the
750/// IR whose result is two things, a value and a bit, and the rule language has no way to write a
751/// term that produces two. So even on a machine whose add sets a carry flag, a rule for one of
752/// these could not name both halves of what it answers, and the rewrite would have to happen
753/// somewhere. Here is that somewhere.
754///
755/// Because the instruction goes away rather than becoming another one, the values the rest of the
756/// function read have to be pointed at what replaced them. That is what `substitute` below does,
757/// once, after every instruction has been rewritten.
758pub fn overflows(func: &mut Func) {
759 let found: Vec<Inst> =
760 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
761 let mut forward = HashMap::new();
762 for inst in found {
763 let checked = match func[inst].opcode {
764 Opcode::UAddOverflow => Checked::Add(false),
765 Opcode::SAddOverflow => Checked::Add(true),
766 Opcode::USubOverflow => Checked::Sub(false),
767 Opcode::SSubOverflow => Checked::Sub(true),
768 Opcode::UMulOverflow => Checked::Mul(false),
769 Opcode::SMulOverflow => Checked::Mul(true),
770 _ => continue,
771 };
772 overflowed(func, inst, checked, &mut forward);
773 }
774 if !forward.is_empty() {
775 substitute(func, &forward);
776 }
777}
778
779/// Which of the six an instruction is, as the arithmetic and whether the operands are signed.
780#[derive(Debug, Clone, Copy)]
781enum Checked {
782 /// An add, whose answer wraps when the exact sum needed one more bit at the top.
783 Add(bool),
784 /// A subtract.
785 Sub(bool),
786 /// A multiply, which is the expensive one because the test needs the high half of the product.
787 Mul(bool),
788}
789
790/// One overflow checked instruction, as the ordinary arithmetic and a test on the operands.
791///
792/// The value is always the ordinary instruction, because that is what the wrapped answer is. What
793/// differs between the six is how the bit is worked out.
794///
795/// The two adds and the two subtracts are one comparison each. An unsigned sum wraps exactly when
796/// it came out below either operand, and an unsigned difference wraps exactly when the left operand
797/// was below the right. A signed sum wraps exactly when both operands had the same sign and the
798/// answer had the other one, which `(a ^ v) & (b ^ v)` has the sign bit of, and a signed difference
799/// wraps exactly when the operands had different signs and the answer took the right one's, which
800/// `(a ^ b) & (a ^ v)` has the sign bit of.
801///
802/// The multiplies are the high half of the product against what the low half implies it should be.
803/// For an unsigned multiply the product fits exactly when the high half is zero, and for a signed
804/// one it fits exactly when the high half is the sign extension of the low half, which is the low
805/// half shifted right arithmetically by every bit but one.
806fn overflowed(func: &mut Func, inst: Inst, checked: Checked, forward: &mut HashMap<Value, Value>) {
807 let ty = produced(func, inst);
808 let [a, b] = func[func[inst].args] else { return };
809 if !checkable(ty) {
810 return;
811 }
812 let (value, bit) = match checked {
813 Checked::Add(signed) => {
814 let value = ahead(func, inst, Opcode::Add, &[a, b], ty);
815 let bit = if signed {
816 let left = ahead(func, inst, Opcode::Xor, &[a, value], ty);
817 let right = ahead(func, inst, Opcode::Xor, &[b, value], ty);
818 let both = ahead(func, inst, Opcode::And, &[left, right], ty);
819 negative(func, inst, both, ty)
820 } else {
821 compared(func, inst, IntPred::Ult, value, a)
822 };
823 (value, bit)
824 }
825 Checked::Sub(signed) => {
826 let value = ahead(func, inst, Opcode::Sub, &[a, b], ty);
827 let bit = if signed {
828 let apart = ahead(func, inst, Opcode::Xor, &[a, b], ty);
829 let moved = ahead(func, inst, Opcode::Xor, &[a, value], ty);
830 let both = ahead(func, inst, Opcode::And, &[apart, moved], ty);
831 negative(func, inst, both, ty)
832 } else {
833 compared(func, inst, IntPred::Ult, a, b)
834 };
835 (value, bit)
836 }
837 Checked::Mul(signed) => {
838 let value = ahead(func, inst, Opcode::Mul, &[a, b], ty);
839 let high = high_half(func, inst, a, b, signed, ty);
840 let bit = if signed {
841 let sign = ahead_const(func, inst, Imm::int(i128::from(ty.bits() - 1), ty), ty);
842 let wanted = ahead(func, inst, Opcode::AShr, &[value, sign], ty);
843 compared(func, inst, IntPred::Ne, high, wanted)
844 } else {
845 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
846 compared(func, inst, IntPred::Ne, high, zero)
847 };
848 (value, bit)
849 }
850 };
851 let mut answers = func[inst].results();
852 if let (Some(wrapped), Some(flag)) = (answers.next(), answers.next()) {
853 forward.insert(wrapped, value);
854 forward.insert(flag, bit);
855 }
856 func.remove_inst(inst);
857}
858
859/// The high half of the product of two values, at the width they are.
860///
861/// Both operands are split into halves of half the width and multiplied four ways, which is long
862/// multiplication in base two to the half width. The three partial products that reach the top are
863/// added with the carry out of the bottom ones, and every step of that fits in the width because
864/// the total is the high half of the product and that is what a high half is.
865///
866/// The whole of it is unsigned, and a signed high half is the unsigned one with a correction: a
867/// negative operand contributed the width's worth of sign bits to the unsigned product that it
868/// should not have, so the other operand is subtracted off once for each negative operand. Spreading
869/// the sign bit of each with an arithmetic shift is what turns that into a mask rather than a
870/// branch.
871///
872/// This is the expensive one. Six multiplies and a dozen other instructions at sixty four bits,
873/// against one `mul` on a machine whose multiply writes the high half into a second register. That
874/// is most of what #309 is worth and it is what makes the multiply the one to give a rule to first.
875///
876/// [`crate::wide`] calls it as well, for the carry out of the low halves of a multiply at a hundred
877/// and twenty eight bits, which is the same question asked at the width below. That pass runs
878/// before this one, so what it writes here is already at a width the machine has and nothing in
879/// this module needs to look at it again.
880pub(crate) fn high_half(
881 func: &mut Func,
882 inst: Inst,
883 a: Value,
884 b: Value,
885 signed: bool,
886 ty: Type,
887) -> Value {
888 let width = ty.bits();
889 let half = width / 2;
890 let shift = ahead_const(func, inst, Imm::int(i128::from(half), ty), ty);
891 let mask = ahead_const(func, inst, Imm::int((1i128 << half) - 1, ty), ty);
892
893 let al = ahead(func, inst, Opcode::And, &[a, mask], ty);
894 let ah = ahead(func, inst, Opcode::LShr, &[a, shift], ty);
895 let bl = ahead(func, inst, Opcode::And, &[b, mask], ty);
896 let bh = ahead(func, inst, Opcode::LShr, &[b, shift], ty);
897
898 let ll = ahead(func, inst, Opcode::Mul, &[al, bl], ty);
899 let lh = ahead(func, inst, Opcode::Mul, &[al, bh], ty);
900 let hl = ahead(func, inst, Opcode::Mul, &[ah, bl], ty);
901 let hh = ahead(func, inst, Opcode::Mul, &[ah, bh], ty);
902
903 // The carry out of the low half, which is the top of the smallest partial product plus the
904 // bottoms of the two middle ones.
905 let over = ahead(func, inst, Opcode::LShr, &[ll, shift], ty);
906 let lh_low = ahead(func, inst, Opcode::And, &[lh, mask], ty);
907 let hl_low = ahead(func, inst, Opcode::And, &[hl, mask], ty);
908 let some = ahead(func, inst, Opcode::Add, &[over, lh_low], ty);
909 let carry = ahead(func, inst, Opcode::Add, &[some, hl_low], ty);
910
911 let lh_high = ahead(func, inst, Opcode::LShr, &[lh, shift], ty);
912 let hl_high = ahead(func, inst, Opcode::LShr, &[hl, shift], ty);
913 let up = ahead(func, inst, Opcode::LShr, &[carry, shift], ty);
914 let first = ahead(func, inst, Opcode::Add, &[hh, lh_high], ty);
915 let second = ahead(func, inst, Opcode::Add, &[first, hl_high], ty);
916 let high = ahead(func, inst, Opcode::Add, &[second, up], ty);
917 if !signed {
918 return high;
919 }
920 let top = ahead_const(func, inst, Imm::int(i128::from(width - 1), ty), ty);
921 let a_sign = ahead(func, inst, Opcode::AShr, &[a, top], ty);
922 let b_sign = ahead(func, inst, Opcode::AShr, &[b, top], ty);
923 let a_owes = ahead(func, inst, Opcode::And, &[a_sign, b], ty);
924 let b_owes = ahead(func, inst, Opcode::And, &[b_sign, a], ty);
925 let once = ahead(func, inst, Opcode::Sub, &[high, a_owes], ty);
926 ahead(func, inst, Opcode::Sub, &[once, b_owes], ty)
927}
928
929/// Whether a value's sign bit is set, as a comparison against zero.
930fn negative(func: &mut Func, inst: Inst, value: Value, ty: Type) -> Value {
931 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
932 compared(func, inst, IntPred::Slt, value, zero)
933}
934
935/// A comparison written in front of an instruction, which [`ahead`] cannot write because a
936/// comparison carries its predicate where everything else carries nothing.
937fn compared(func: &mut Func, inst: Inst, pred: IntPred, lhs: Value, rhs: Value) -> Value {
938 let ty = func[lhs].ty.with_lane(Type::I1);
939 let args = func.push_values(&[lhs, rhs]);
940 let extra = Extra::IntPred(pred);
941 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::ICmp) }, ty)
942}
943
944/// Points every reader of a removed instruction's results at what replaced them.
945///
946/// The arguments of each instruction and the arguments of the blocks it branches to, which between
947/// them are everything an instruction can read. Nothing chases here, the way the same walk in
948/// `rucc_opt::simplify` does, because every value this map answers with is one written above and so
949/// is never itself a key.
950fn substitute(func: &mut Func, forward: &HashMap<Value, Value>) {
951 let with = |value: Value| forward.get(&value).copied().unwrap_or(value);
952 for block in func.blocks().collect::<Vec<_>>() {
953 for inst in func.insts(block).collect::<Vec<Inst>>() {
954 let args = func[inst].args;
955 func.rewrite(args, with);
956 for call in func.successors(inst).collect::<Vec<_>>() {
957 func.rewrite(call.args, with);
958 }
959 }
960 }
961}
962
963/// Whether the arithmetic in this file works correctly at this type.
964///
965/// A whole number of bytes and a power of two of them, which every width the front end can ask about
966/// is. Anything else is left as the instruction it was, so a selector with no rule for it says so
967/// rather than the program getting a number that was counted, or checked, in the wrong shape.
968///
969/// The bit counts need it because a halving sum halves, and the overflow checks need it because
970/// splitting a value into two halves of equal width needs the width to be even and the halves to be
971/// what a shift by half of it separates.
972fn countable(ty: Type) -> bool {
973 ty.is_int()
974 && ty.is_scalar()
975 && ty.bits() >= 8
976 && ty.bits() <= 64
977 && ty.bits().is_power_of_two()
978}
979
980/// The same for an overflow check, which reaches one width further up than the bit counts do.
981///
982/// The arithmetic a check becomes is adds, subtracts, multiplies, shifts and comparisons, and
983/// `crate::wide` splits every one of those into the two registers a value that wide travels in. So
984/// the checks run before that pass and it finishes the job, where the bit counts run after it and
985/// have nothing above sixty four bits to finish. The halving argument the function above makes
986/// holds here for the same reason it holds one width down.
987fn checkable(ty: Type) -> bool {
988 countable(ty) || (ty.is_int() && ty.is_scalar() && ty.bits() == 128)
989}
990
991/// Rounds up the bytes every variable length array asks for, so that the stack pointer stays where
992/// a call can be made from.
993///
994/// The size of one is whatever the program wrote in the brackets times the size of an element, so
995/// it is any number at all, and the bytes come off the stack pointer where the declaration stands.
996/// A stack pointer moved by an odd number is one no call can be made through afterwards: the
997/// convention says a call arrives with the stack pointer on a multiple of `to`, and every argument
998/// passed on the stack, every spill of a vector register and the alignment of every local below the
999/// array is counted from there. So the number that comes off is the size rounded up, which is
1000/// `(size + to - 1) & -to` and is three instructions the machine already has.
1001///
1002/// Here rather than in the front end because `to` is the convention's and the front end writes one
1003/// IR for every target. Here rather than in [`crate::lower`] because it is arithmetic, which is the
1004/// one thing a lowering rule deliberately cannot do, and that is what this module is for.
1005///
1006/// An array asking for more alignment than `to` asks for `to` bytes more than it needs and takes
1007/// the address it wanted out of the middle of them, which `aligns` below writes. The stack pointer
1008/// itself is left where a call can be made from, so nothing about a frame like that is different
1009/// from any other frame that grows.
1010pub fn rounds(func: &mut Func, to: u32) {
1011 let found: Vec<Inst> =
1012 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
1013 for inst in found {
1014 if func[inst].opcode != Opcode::Alloca {
1015 continue;
1016 }
1017 let Some(&size) = func[func[inst].args].first() else { continue };
1018 let ty = func[size].ty;
1019 if !ty.is_int() {
1020 continue;
1021 }
1022 let Extra::Mem(mem) = func[inst].extra else { continue };
1023 let align = func[mem].align;
1024 let up = ahead_const(func, inst, Imm::int(i128::from(to) - 1, ty), ty);
1025 let mask = ahead_const(func, inst, Imm::int(-i128::from(to), ty), ty);
1026 let over = ahead(func, inst, Opcode::Add, &[size, up], ty);
1027 let mut rounded = ahead(func, inst, Opcode::And, &[over, mask], ty);
1028 if align > to {
1029 // The whole of the alignment rather than the difference between the two, so that the
1030 // number coming off the stack pointer is still a multiple of `to` and so that the
1031 // room is there wherever the outgoing argument area below the array happens to leave
1032 // the first byte of it.
1033 let slack = ahead_const(func, inst, Imm::int(i128::from(align), ty), ty);
1034 rounded = ahead(func, inst, Opcode::Add, &[rounded, slack], ty);
1035 }
1036 let args = func.push_values(&[rounded]);
1037 func[inst].args = args;
1038 if align > to {
1039 aligns(func, inst, mem, align, to);
1040 }
1041 }
1042}
1043
1044/// Splits an over-aligned variable length array into the bytes it takes and the address it hands
1045/// out, which are not the same address any more.
1046///
1047/// The bytes come off the stack pointer, and a call leaves the stack pointer on a multiple of the
1048/// convention's alignment and no more than that, so an array wanting more has to be given an
1049/// address inside the block rather than the block's own first byte. Rounding the stack pointer
1050/// down again instead would work for the array and for nothing else: the outgoing argument area
1051/// sits below it, the rest of the frame is reached from a register at a constant distance, and a
1052/// register that has been masked is at no constant distance from where it was.
1053///
1054/// So the instruction that was the array becomes `ptr_add` of the offset that rounds the block's
1055/// address up, and a new `alloca` above it takes the block. The value the rest of the function
1056/// reads is the one the `ptr_add` writes, which is the value the `alloca` used to write, so
1057/// nothing that referred to the array has to be pointed anywhere else. The block asked for `to`
1058/// and gets it, which is the truth about it: what wanted the larger alignment is the object, and
1059/// the object is now the offset into the block rather than the block.
1060///
1061/// The offset is `-address & (align - 1)`, which is how far up the next multiple of the alignment
1062/// is, and [`rounds`] took the room for it above. It is computed at the width the size was
1063/// written at, which is `size_t` on the target, and a target whose pointers are wider than its
1064/// `size_t` would be counting the low bits of the address either way: every bit the mask keeps is
1065/// a bit below the alignment, and the alignment is smaller than the narrower of the two.
1066fn aligns(func: &mut Func, inst: Inst, mem: Idx<MemInfo>, align: u32, to: u32) {
1067 let ty = func[func[func[inst].args][0]].ty;
1068 let mut info = func[mem];
1069 info.align = to;
1070 let block = InstData {
1071 args: func[inst].args,
1072 extra: Extra::Mem(func.add_mem(info)),
1073 ..InstData::new(Opcode::Alloca)
1074 };
1075 let raw = written(func, inst, block, Type::PTR);
1076 let address = ahead(func, inst, Opcode::PtrToInt, &[raw], ty);
1077 let zero = ahead_const(func, inst, Imm::int(0, ty), ty);
1078 let below = ahead(func, inst, Opcode::Sub, &[zero, address], ty);
1079 let bits = ahead_const(func, inst, Imm::int(i128::from(align) - 1, ty), ty);
1080 let offset = ahead(func, inst, Opcode::And, &[below, bits], ty);
1081 becomes(func, inst, Opcode::PtrAdd, &[raw, offset]);
1082}
1083
1084/// The most moves a copy or a fill becomes before it is left alone for a call instead.
1085///
1086/// Thirty two, which is two hundred and fifty six bytes at a word a time and is a structure larger
1087/// than almost every one a program writes. What the number is trading is code size against a call,
1088/// and the exchange rate is a machine's rather than a language's, so the number lives here next to
1089/// the code it bounds and not in a target description that would have to be right about it for
1090/// every target at once.
1091///
1092/// It is a count of moves and not a count of bytes because that is what the cost is. A copy of
1093/// sixty four bytes between two addresses aligned to eight is eight moves and a copy of the same
1094/// sixty four bytes between two addresses aligned to one is sixty four, and the second is the
1095/// expensive one whatever the size says.
1096pub const UNROLL: usize = 32;
1097
1098/// Rewrites every bulk copy and bulk fill, into moves when that is worth it and into a call to the
1099/// runtime when it is not.
1100///
1101/// A copy of more than [`UNROLL`] moves becomes a call, and so does a fill whose byte is not a
1102/// constant, which the front end does not write today and which would need the byte spread across
1103/// a word at runtime. A `memmove` is always a call, because the two sides may overlap and a run of
1104/// moves in one direction is only right for one of the two ways they can.
1105///
1106/// `word` is how many bytes the widest move on this machine carries. Nothing here reads a target
1107/// otherwise, and a copy is the same run of loads and stores everywhere.
1108pub fn bulk(func: &mut Func, names: &mut Interner, word: u32) {
1109 let found: Vec<Inst> =
1110 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
1111 for inst in found {
1112 match func[inst].opcode {
1113 Opcode::Memcpy => copy(func, names, inst, word),
1114 Opcode::Memset => fill(func, names, inst, word),
1115 Opcode::Memmove => library(func, names, inst, Opcode::Memmove, word),
1116 _ => {}
1117 }
1118 }
1119}
1120
1121/// One `memcpy`, as a load and a store for each word of it.
1122///
1123/// Each word is read and then written before the next is read, rather than every read being built
1124/// before any write the way [`crate::varargs`] copies a list. A `memcpy` is the copy whose two
1125/// sides the front end promises do not overlap, so what is at the source when the last word is read
1126/// is what was there when the first was, and reading a word at a time costs one register where
1127/// reading all of them first would cost as many registers as the copy has words.
1128fn copy(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1129 let Some(bulk) = func.bulk(inst) else { return };
1130 let (into, from) = (bulk.to, bulk.with);
1131 let Extra::Mem(mem) = func[inst].extra else { return };
1132 let info = func[mem];
1133 // A plan is a list of offsets, so there is none for a copy whose length the program works out.
1134 let Some(plan) = chunks(info, word).filter(|_| bulk.length.is_none()) else {
1135 return library(func, names, inst, Opcode::Memcpy, word);
1136 };
1137 for (at, width) in plan {
1138 let ty = Type::int(width * 8);
1139 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1140 let there = stepped(func, inst, from, at);
1141 let word = read(func, inst, there, access, ty);
1142 let here = stepped(func, inst, into, at);
1143 write(func, inst, word, here, access);
1144 }
1145 func.remove_inst(inst);
1146}
1147
1148/// One `memset`, as a store of the byte spread across each word of it.
1149///
1150/// The byte is a constant, so the word it spreads into is a constant too and the spreading is done
1151/// here rather than by the program. The front end writes a `memset` for the part of an object an
1152/// initialiser did not name, where the byte is always zero, and the general case is written anyway
1153/// because the arithmetic is the same and being right about `0xff` costs nothing.
1154fn fill(func: &mut Func, names: &mut Interner, inst: Inst, word: u32) {
1155 let Some(bulk) = func.bulk(inst) else { return };
1156 let (into, byte) = (bulk.to, bulk.with);
1157 let Extra::Mem(mem) = func[inst].extra else { return };
1158 let info = func[mem];
1159 let Some(spelled) = literal(func, byte) else {
1160 return library(func, names, inst, Opcode::Memset, word);
1161 };
1162 // As in [`copy`]: a fill whose length the program works out has no list of offsets to write.
1163 let Some(plan) = chunks(info, word).filter(|_| bulk.length.is_none()) else {
1164 return library(func, names, inst, Opcode::Memset, word);
1165 };
1166 for (at, width) in plan {
1167 let ty = Type::int(width * 8);
1168 let access = MemInfo { size: u64::from(width), align: width.min(info.align), ..info };
1169 let value = ahead_const(func, inst, Imm::int(spread(spelled, width) as i128, ty), ty);
1170 let here = stepped(func, inst, into, at);
1171 write(func, inst, value, here, access);
1172 }
1173 func.remove_inst(inst);
1174}
1175
1176/// One bulk operation as a call to the routine of that name in the runtime.
1177///
1178/// This is what a copy too large to unroll becomes, and what a `memmove` and a fill with a
1179/// computed byte become whatever their size. The routine is `rucc-builtins`' on a freestanding
1180/// target and the C library's on a hosted one, and the call is the same either way because the two
1181/// have the same names and the same signatures on purpose.
1182///
1183/// The arguments are the C ones and not the IR ones. The IR holds the size beside the instruction
1184/// where C passes it, and holds a fill byte as a byte where C passes an `int`, so the size becomes
1185/// a constant in a register and the byte is widened. The value each returns is its first argument,
1186/// which nothing reads, so the call is built as returning nothing rather than as returning a
1187/// pointer nobody looks at.
1188fn library(func: &mut Func, names: &mut Interner, inst: Inst, opcode: Opcode, word: u32) {
1189 // What each of the three is called is in the capability table, since a call standing in for an
1190 // operation the machine has no instruction for is exactly what that table is a list of. A copy
1191 // and a fill answer at the size this pass gives up at and a move answers at any size, which is
1192 // the mode each is written down under there.
1193 let mode = if opcode == Opcode::Memmove { "any" } else { "big" };
1194 let Some(routine) = capability::libcall(opcode, mode) else { return };
1195 let Some(bulk) = func.bulk(inst) else { return };
1196 let (into, second) = (bulk.to, bulk.with);
1197 let Extra::Mem(mem) = func[inst].extra else { return };
1198 let size = func[mem].size;
1199
1200 // `size_t`, which is as wide as a general purpose register on every target here. Taken from
1201 // the machine rather than written as sixty four so that a thirty two bit target gets the
1202 // argument its own C library declares.
1203 let words = Type::int(word * 8);
1204 // The operand where the program worked the length out, and the payload's number otherwise.
1205 // The operand is fitted to `size_t` here rather than by the front end, because how wide that
1206 // is is a fact about the machine and this is the first pass that has been told which one.
1207 let count = match bulk.length {
1208 Some(length) => fitted(func, inst, length, words),
1209 None => ahead_const(func, inst, Imm::int(i128::from(size), words), words),
1210 };
1211 // A fill passes an `int` where the IR passes the byte itself, and the widening is a zero
1212 // extension because the routine looks at the low eight bits and nothing else.
1213 let second = if opcode == Opcode::Memset { widened(func, inst, second) } else { second };
1214
1215 let sig = func.add_signature(Signature::new().with_params(&[
1216 Type::PTR,
1217 if opcode == Opcode::Memset { Type::int(32) } else { Type::PTR },
1218 words,
1219 ]));
1220 let callee = names.intern(routine);
1221 let varargs = func.push_abis(&[]);
1222 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
1223 let args = func.push_values(&[into, second, count]);
1224 let data = &mut func[inst];
1225 data.opcode = Opcode::Call;
1226 data.args = args;
1227 data.extra = Extra::Call(info);
1228 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
1229}
1230
1231/// A count brought to the width the routine takes it in, whichever side of it the value started.
1232///
1233/// The front end builds a length in whatever `size_t` it decided on, so on every target here the
1234/// two already agree and this does nothing. It is written anyway because narrowing and widening
1235/// are different instructions and picking the wrong one is the kind of mistake that shows up as a
1236/// copy of four gigabytes rather than as a build failure. The extension is unsigned, since a
1237/// length is a count of bytes and there is no negative one.
1238fn fitted(func: &mut Func, inst: Inst, value: Value, want: Type) -> Value {
1239 let ty = func[value].ty;
1240 if ty == want {
1241 return value;
1242 }
1243 let opcode = if ty.bits() < want.bits() { Opcode::ZExt } else { Opcode::Trunc };
1244 ahead(func, inst, opcode, &[value], want)
1245}
1246
1247/// A value widened to an `int`, or the value itself when it is one already.
1248fn widened(func: &mut Func, inst: Inst, value: Value) -> Value {
1249 let int = Type::int(32);
1250 let ty = func[value].ty;
1251 if ty == int {
1252 return value;
1253 }
1254 ahead(func, inst, Opcode::ZExt, &[value], int)
1255}
1256
1257/// Where each word of a block of memory starts and how wide it is, or nothing for a block that is
1258/// more words than [`UNROLL`].
1259///
1260/// The widest word is the smaller of what the machine moves at once and what the block is known to
1261/// be aligned to, because a load wider than the alignment is a fault on a machine that checks and
1262/// this pass does not know whether the one it is compiling for does. That costs a copy of a
1263/// character array a move per byte, which is exactly the copy the threshold sends to a call.
1264///
1265/// The width halves whenever what is left is narrower than it, so a block of thirteen bytes aligned
1266/// to eight is eight, four and one rather than thirteen ones. Every offset is a multiple of the
1267/// width at it, since each width divides the sum of the wider ones in front of it, which is what
1268/// lets the alignment of each access be written down as the width.
1269fn chunks(info: MemInfo, word: u32) -> Option<Vec<(u64, u32)>> {
1270 plan(info.size, info.align, word)
1271}
1272
1273/// The same, as the two numbers rather than as an access, for the one caller that has no access to
1274/// ask about.
1275///
1276/// [`crate::abi`] copies a structure passed by value into the argument area, and that copy is not a
1277/// `memcpy` in the IR: it is written straight into the machine IR, because where it goes is an
1278/// offset the placement walk gives and nothing before this pass knows it. The plan has to be the
1279/// same plan either way, so it is one function.
1280pub(crate) fn plan(size: u64, align: u32, word: u32) -> Option<Vec<(u64, u32)>> {
1281 let widest = word.min(align).max(1);
1282 if !widest.is_power_of_two() {
1283 return None;
1284 }
1285 let mut plan = Vec::new();
1286 let mut at = 0;
1287 let mut width = u64::from(widest);
1288 while at < size {
1289 while width > size - at {
1290 width /= 2;
1291 }
1292 plan.push((at, u32::try_from(width).ok()?));
1293 at += width;
1294 if plan.len() > UNROLL {
1295 return None;
1296 }
1297 }
1298 Some(plan)
1299}
1300
1301/// The byte a fill writes, when the program said which one rather than working it out.
1302fn literal(func: &Func, value: Value) -> Option<u8> {
1303 let Def::Result { inst, .. } = func[value].def else { return None };
1304 if func[inst].opcode != Opcode::IConst {
1305 return None;
1306 }
1307 let Extra::Imm(imm) = func[inst].extra else { return None };
1308 u8::try_from(func[imm].bits() & 0xff).ok()
1309}
1310
1311/// One byte repeated across a word of that many bytes, which is what a fill stores.
1312fn spread(byte: u8, width: u32) -> u64 {
1313 (0..width).fold(0, |word, at| word | u64::from(byte) << (at * 8))
1314}
1315
1316/// The address that far into a block, written in front of an instruction, or the block itself for
1317/// the word at the front of it.
1318fn stepped(func: &mut Func, inst: Inst, block: Value, at: u64) -> Value {
1319 if at == 0 {
1320 return block;
1321 }
1322 let step = ahead_const(func, inst, Imm::int(i128::from(at), Type::int(64)), Type::int(64));
1323 ahead(func, inst, Opcode::PtrAdd, &[block, step], Type::PTR)
1324}
1325
1326/// A load put in front of an instruction, and the value it reads.
1327fn read(func: &mut Func, inst: Inst, from: Value, info: MemInfo, ty: Type) -> Value {
1328 let extra = Extra::Mem(func.add_mem(info));
1329 let args = func.push_values(&[from]);
1330 written(func, inst, InstData { args, extra, ..InstData::new(Opcode::Load) }, ty)
1331}
1332
1333/// A store put in front of an instruction, which produces nothing and is only its effect.
1334fn write(func: &mut Func, inst: Inst, value: Value, into: Value, info: MemInfo) {
1335 let span = func.span(inst);
1336 let extra = Extra::Mem(func.add_mem(info));
1337 let args = func.push_values(&[value, into]);
1338 let data = InstData { args, extra, ..InstData::new(Opcode::Store) };
1339 let made = func.create_inst(data, &[], span);
1340 func.insert_before(made, inst);
1341}
1342
1343/// The width the machine converts at that holds every value of an integer of this one.
1344///
1345/// The machine converts between a float and a signed integer at thirty two bits and at sixty four
1346/// and at no other width, so a conversion anywhere else is one of those two with a widening in
1347/// front of it or a narrowing behind it. Which of the two it is, is the narrower one the values
1348/// fit in, and an unsigned integer of `bits` bits needs one more bit than that to be signed in.
1349///
1350/// `None` is a width no signed integer here holds, which is only an unsigned sixty four bit one.
1351fn holder(bits: u32, signed: bool) -> Option<u32> {
1352 match if signed { bits } else { bits + 1 } {
1353 ..=32 => Some(32),
1354 33..=64 => Some(64),
1355 _ => None,
1356 }
1357}
1358
1359/// The type of the one value an instruction produces.
1360///
1361/// Every opcode this pass touches produces exactly one, so an instruction that produces none is
1362/// one the caller has already gone wrong about and the void type says so without panicking.
1363fn produced(func: &Func, inst: Inst) -> Type {
1364 func[inst].first_result.map_or(Type::VOID, |value| func[value].ty)
1365}
1366
1367/// Puts an instruction over these operands in front of another one, and gives back its value.
1368fn ahead(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value], ty: Type) -> Value {
1369 let args = func.push_values(args);
1370 written(func, inst, InstData { args, ..InstData::new(opcode) }, ty)
1371}
1372
1373/// The same for a comparison, which carries the predicate and produces one bit.
1374fn ahead_cmp(func: &mut Func, inst: Inst, opcode: Opcode, extra: Extra, args: &[Value]) -> Value {
1375 let args = func.push_values(args);
1376 written(func, inst, InstData { args, extra, ..InstData::new(opcode) }, Type::I1)
1377}
1378
1379/// The same for a constant, which carries an immediate rather than operands.
1380fn ahead_const(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
1381 let extra = Extra::Imm(func.add_imm(imm));
1382 written(func, inst, InstData { extra, ..InstData::new(Opcode::IConst) }, ty)
1383}
1384
1385/// The same for a float constant, which carries the bits of its format rather than a number.
1386fn ahead_float(func: &mut Func, inst: Inst, bits: u128, ty: Type) -> Value {
1387 let extra = Extra::Imm(func.add_imm(Imm::from_bits(bits)));
1388 written(func, inst, InstData { extra, ..InstData::new(Opcode::FConst) }, ty)
1389}
1390
1391/// Creates the instruction, puts it where those two asked, and reads its value back out.
1392fn written(func: &mut Func, inst: Inst, data: InstData, ty: Type) -> Value {
1393 let span = func.span(inst);
1394 let made = func.create_inst(data, &[ty], span);
1395 func.insert_before(made, inst);
1396 func[made].first_result.expect("an instruction created with one result has one")
1397}
1398
1399/// Turns an instruction into a different one over different operands, in place.
1400///
1401/// The last instruction of a rewrite is the original rather than a new one, so the value the rest
1402/// of the function reads is the value it already read and nothing has to be substituted anywhere.
1403/// The type of that value does not change either, because every rewrite here ends at the type it
1404/// started at.
1405fn becomes(func: &mut Func, inst: Inst, opcode: Opcode, args: &[Value]) {
1406 let args = func.push_values(args);
1407 let data = &mut func[inst];
1408 data.opcode = opcode;
1409 data.args = args;
1410 data.extra = Extra::None;
1411 // What the program said about rounding and about not a numbers is still true of the
1412 // instructions it became, and what is no longer meaningful is dropped rather than carried.
1413 data.flags = data.flags.intersection(Flags::legal_on(opcode));
1414}
1415
1416#[cfg(test)]
1417mod tests {
1418 use rucc_base::Interner;
1419 use rucc_ir::{Builder, Flags, Float, Func, Module, Opcode, Signature, Type};
1420 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
1421
1422 use rucc_ir::{Extra, InstData, MemInfo, MemOrder, Restrict};
1423
1424 use super::{
1425 UNROLL, alternating, bulk, bytes, chunks, counts, every, floats, orderings, overflows,
1426 rounds, spread,
1427 };
1428
1429 fn target() -> TargetInfo {
1430 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
1431 }
1432
1433 fn printed(func: &Func, names: &mut Interner) -> String {
1434 let module = Module::new(names.intern("sw.c"), &target());
1435 rucc_ir::print_func(&module, func, names)
1436 }
1437
1438 /// A function of one parameter and one result, with a body somebody else writes.
1439 ///
1440 /// The float rewrites are each one instruction becoming several in the middle of a block, so
1441 /// what a test needs is a block with something around the instruction rather than a shape.
1442 fn one(
1443 params: &[Type],
1444 returns: &[Type],
1445 body: impl FnOnce(&mut Builder<'_>, &[rucc_ir::Value]),
1446 ) -> (Interner, Func) {
1447 let mut names = Interner::new();
1448 let mut func = Func::new(
1449 names.intern("f"),
1450 Signature::new().with_params(params).with_returns(returns),
1451 );
1452 let entry = func.create_block();
1453 let args: Vec<_> = params.iter().map(|&ty| func.append_param(entry, ty)).collect();
1454 let mut build = Builder::new(&mut func, entry);
1455 body(&mut build, &args);
1456 (names, func)
1457 }
1458
1459 fn f64() -> Type {
1460 Type::float(Float::F64)
1461 }
1462
1463 fn f32() -> Type {
1464 Type::float(Float::F32)
1465 }
1466
1467 fn f80() -> Type {
1468 Type::float(Float::F80)
1469 }
1470
1471 /// The unsigned words both of the widest conversions are checked over.
1472 ///
1473 /// Every boundary is in the list, and so are the values either side of the ones where the two
1474 /// paths of a conversion meet, and the ones at the last width a `double` counts to.
1475 const CASES: &[u64] = &[
1476 0,
1477 1,
1478 2,
1479 0x7FFF_FFFF,
1480 0x8000_0000,
1481 0xFFFF_FFFF,
1482 0x0020_0000_0000_0000,
1483 0x0020_0000_0000_0001,
1484 0x7FFF_FFFF_FFFF_FFFF,
1485 0x8000_0000_0000_0000,
1486 0x8000_0000_0000_0001,
1487 0x8000_0000_0000_0400,
1488 0xFFFF_FFFF_FFFF_F800,
1489 0xFFFF_FFFF_FFFF_FFFF,
1490 ];
1491
1492 /// The obligation every rewrite here has: nothing after this checks the IR again.
1493 fn valid(func: &Func, names: &mut Interner) {
1494 let module = Module::new(names.intern("f.c"), &target());
1495 rucc_ir::verify_func(&module, func, names).expect("the rewrite builds valid IR");
1496 }
1497
1498 /// `double c(void) { return 1.5; }`, which is the constant nothing in the rule set can name.
1499 #[test]
1500 fn a_float_constant_becomes_the_integer_that_spells_it_and_a_reading_of_those_bits() {
1501 let (mut names, mut func) = one(&[], &[f64()], |build, _| {
1502 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1503 build.ret(&[k]);
1504 });
1505 floats(&mut func);
1506
1507 let text = printed(&func, &mut names);
1508 assert!(!text.contains("fconst"), "the float constant is gone: {text}");
1509 assert!(text.contains("iconst.i64 4609434218613702656"), "the bits, as an integer: {text}");
1510 assert!(text.contains("bitcast"), "read back as the float: {text}");
1511 }
1512
1513 /// The width follows the format rather than being the widest one, so a `float` constant is an
1514 /// `i32` and reaches `movd` rather than `movq`.
1515 #[test]
1516 fn a_constant_at_the_narrow_format_is_an_integer_of_the_narrow_width() {
1517 let (mut names, mut func) = one(&[], &[f32()], |build, _| {
1518 let k = build.fconst(f32(), 0x4020_0000);
1519 build.ret(&[k]);
1520 });
1521 floats(&mut func);
1522 assert!(printed(&func, &mut names).contains("iconst.i32"), "an i32, not an i64");
1523 }
1524
1525 /// `double n(double x) { return -x; }`. Flipping the sign bit is what C means and subtracting
1526 /// from zero is not, so what this asserts is the exclusive or and the mask it is given.
1527 #[test]
1528 fn a_negation_flips_the_sign_bit_and_touches_no_other() {
1529 let (mut names, mut func) = one(&[f64()], &[f64()], |build, args| {
1530 let n = build.unary(Opcode::FNeg, args[0], f64());
1531 build.ret(&[n]);
1532 });
1533 floats(&mut func);
1534
1535 let text = printed(&func, &mut names);
1536 assert!(!text.contains("fneg"), "the negation is gone: {text}");
1537 assert!(!text.contains("fsub"), "and it did not become a subtraction: {text}");
1538 assert!(text.contains("iconst.i64 -9223372036854775808"), "the sign bit alone: {text}");
1539 assert_eq!(text.matches("xor").count(), 1, "one exclusive or: {text}");
1540 assert_eq!(text.matches("bitcast").count(), 2, "there and back: {text}");
1541 }
1542
1543 /// `double u(unsigned x) { return x; }`, which is a widening and the signed conversion.
1544 #[test]
1545 fn an_unsigned_integer_becoming_a_float_widens_first_and_then_converts_as_signed() {
1546 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1547 let d = build.unary(Opcode::UIToFP, args[0], f64());
1548 build.ret(&[d]);
1549 });
1550 floats(&mut func);
1551
1552 let text = printed(&func, &mut names);
1553 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1554 assert!(text.contains("zext.i64"), "widened with zeroes: {text}");
1555 assert!(text.contains("sitofp.f64"), "converted as signed: {text}");
1556 }
1557
1558 /// `unsigned t(double x) { return x; }`, which is the same argument the other way round.
1559 #[test]
1560 fn a_float_becoming_an_unsigned_integer_converts_as_signed_first_and_then_narrows() {
1561 let (mut names, mut func) = one(&[f64()], &[Type::int(32)], |build, args| {
1562 let n = build.unary(Opcode::FPToUI, args[0], Type::int(32));
1563 build.ret(&[n]);
1564 });
1565 floats(&mut func);
1566
1567 let text = printed(&func, &mut names);
1568 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1569 assert!(text.contains("fptosi.i64"), "converted as signed: {text}");
1570 assert!(text.contains("trunc.i32"), "and narrowed to what was asked: {text}");
1571 }
1572
1573 /// `signed char a(double x) { return (signed char)x; }`, which the front end writes as a
1574 /// conversion straight to eight bits and the machine has no instruction for at that width.
1575 #[test]
1576 fn a_conversion_narrower_than_the_machine_has_is_one_it_has_and_a_narrowing() {
1577 let (mut names, mut func) = one(&[f64()], &[Type::int(8)], |build, args| {
1578 let n = build.unary(Opcode::FPToSI, args[0], Type::int(8));
1579 build.ret(&[n]);
1580 });
1581 floats(&mut func);
1582
1583 let text = printed(&func, &mut names);
1584 assert!(text.contains("fptosi.i32"), "converted at a width there is one at: {text}");
1585 assert!(text.contains("trunc.i8"), "and narrowed to what was asked: {text}");
1586 }
1587
1588 /// The same the other way, where the widening carries the sign because the value has one.
1589 #[test]
1590 fn a_signed_integer_narrower_than_the_machine_converts_from_is_widened_with_its_sign() {
1591 let (mut names, mut func) = one(&[Type::int(8)], &[f64()], |build, args| {
1592 let d = build.unary(Opcode::SIToFP, args[0], f64());
1593 build.ret(&[d]);
1594 });
1595 floats(&mut func);
1596
1597 let text = printed(&func, &mut names);
1598 assert!(text.contains("sext.i32"), "widened with the sign and not with zeroes: {text}");
1599 assert!(!text.contains("zext"), "widened with the sign and not with zeroes: {text}");
1600 assert!(text.contains("sitofp.f64"), "converted at a width there is one at: {text}");
1601 }
1602
1603 /// The table the two of them share, which is where the whole argument about widths lives.
1604 #[test]
1605 fn the_width_a_conversion_happens_at_is_the_narrowest_one_that_holds_the_values() {
1606 use super::holder;
1607 for bits in [1, 8, 16, 32] {
1608 assert_eq!(holder(bits, true), Some(32), "a signed {bits} bit value fits in an int");
1609 }
1610 assert_eq!(holder(64, true), Some(64));
1611 for bits in [1, 8, 16, 31] {
1612 assert_eq!(holder(bits, false), Some(32), "an unsigned {bits} bit value does too");
1613 }
1614 // The one more bit an unsigned value needs is what makes these two the wider width.
1615 assert_eq!(holder(32, false), Some(64));
1616 assert_eq!(holder(64, false), None);
1617 }
1618
1619 /// Sixty four bits is where the widening argument runs out, because an unsigned value of that
1620 /// width is not a signed value of any width the IR has. Each of the two gets a rewrite of its
1621 /// own, and what both leave is the signed conversion the machine has with arithmetic around it.
1622 #[test]
1623 fn the_unsigned_conversions_at_the_widest_width_become_the_signed_one_and_a_correction() {
1624 for float in [f32(), f64()] {
1625 let (mut names, mut func) = one(&[Type::int(64)], &[float], |build, args| {
1626 let d = build.unary(Opcode::UIToFP, args[0], float);
1627 build.ret(&[d]);
1628 });
1629 floats(&mut func);
1630 let text = printed(&func, &mut names);
1631 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1632 assert!(text.contains("sitofp"), "the signed one is what is left: {text}");
1633 // The halving that brings the value under the range the signed conversion has, and the
1634 // bit it would have thrown away put back so that the rounding is still the right one.
1635 assert!(text.contains("lshr"), "the value is halved: {text}");
1636 assert!(text.contains("fadd"), "and doubled again afterwards: {text}");
1637 valid(&func, &mut names);
1638 }
1639
1640 for float in [f32(), f64()] {
1641 let (mut names, mut func) = one(&[float], &[Type::int(64)], |build, args| {
1642 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1643 build.ret(&[n]);
1644 });
1645 floats(&mut func);
1646 let text = printed(&func, &mut names);
1647 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1648 assert!(text.contains("fptosi"), "the signed one is what is left: {text}");
1649 // Half the range taken off before the conversion and put back on after it.
1650 assert!(text.contains("fsub"), "the value is brought down: {text}");
1651 assert!(text.contains("shl"), "and the top bit goes back on: {text}");
1652 valid(&func, &mut names);
1653 }
1654 }
1655
1656 /// Neither of those two has a branch in it, which is the thing about them worth a test of its
1657 /// own. Every rewrite in this pass stays inside the block it started in, so a pass that grew a
1658 /// second block would be one whose callers all have to be looked at again.
1659 #[test]
1660 fn the_widest_unsigned_conversions_are_written_without_a_branch() {
1661 let (_, mut func) = one(&[Type::int(64)], &[f64()], |build, args| {
1662 let d = build.unary(Opcode::UIToFP, args[0], f64());
1663 build.ret(&[d]);
1664 });
1665 floats(&mut func);
1666 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1667
1668 let (_, mut func) = one(&[f64()], &[Type::int(64)], |build, args| {
1669 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1670 build.ret(&[n]);
1671 });
1672 floats(&mut func);
1673 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1674 }
1675
1676 /// The arithmetic of those two rewrites, done here in the same order the instructions do it.
1677 ///
1678 /// This is not the compiler running, it is the sequence written out again in a language that
1679 /// can be asked what the answer should have been. What it checks is the part that is easy to
1680 /// get wrong and impossible to see in the assembly, which is whether the halving rounds the way
1681 /// the conversion would have and whether the subtraction is exact.
1682 #[test]
1683 fn the_arithmetic_the_widest_unsigned_conversions_do_is_the_conversion() {
1684 for &x in CASES {
1685 // What `from_unsigned_word` writes, at `f64`.
1686 let mask = if (x as i64) < 0 { u64::MAX } else { 0 };
1687 let odd = (x >> 1) | (x & 1);
1688 let source = x ^ ((x ^ odd) & mask);
1689 let converted = source as i64 as f64;
1690 let addend = f64::from_bits(converted.to_bits() & mask);
1691 assert_eq!(converted + addend, x as f64, "converting {x:#x} into a double");
1692 }
1693
1694 for &x in CASES {
1695 // And what `to_unsigned_word` writes, at `f64`, over the same values read back.
1696 let d = x as f64;
1697 if d >= 18_446_744_073_709_551_616.0 {
1698 continue;
1699 }
1700 let half = f64::from_bits(0x43E0_0000_0000_0000);
1701 let mask = if d >= half { u64::MAX } else { 0 };
1702 let taken = f64::from_bits(half.to_bits() & mask);
1703 let low = (d - taken) as i64;
1704 let top = u64::from(d >= half) << 63;
1705 assert_eq!(low as u64 ^ top, d as u64, "converting {d} into an unsigned word");
1706 }
1707 }
1708
1709 /// At eighty bits both of them are a different sequence, and the thing to check is that it is
1710 /// the shorter one rather than the one above with an impossible instruction in it.
1711 ///
1712 /// What made the pair above long is the mask, and what makes a mask impossible here is that it
1713 /// is laid over the bits of the float. So no `bitcast` is the assertion that matters, and the
1714 /// rest of the list says the correction is still there and is a multiply now.
1715 #[test]
1716 fn the_unsigned_conversions_at_eighty_bits_correct_with_a_multiply_instead_of_a_mask() {
1717 let (mut names, mut func) = one(&[Type::int(64)], &[f80()], |build, args| {
1718 let d = build.unary(Opcode::UIToFP, args[0], f80());
1719 build.ret(&[d]);
1720 });
1721 floats(&mut func);
1722 let text = printed(&func, &mut names);
1723 assert!(!text.contains("uitofp"), "the unsigned conversion is gone: {text}");
1724 assert!(text.contains("sitofp.f80"), "the signed one is what is left: {text}");
1725 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1726 assert!(!text.contains("lshr"), "nor is the value halved, since nothing rounds: {text}");
1727 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1728 assert!(text.contains("fadd "), "and added to what the conversion gave: {text}");
1729 assert_eq!(func.blocks().count(), 1, "the conversion did not split the block");
1730 valid(&func, &mut names);
1731
1732 let (mut names, mut func) = one(&[f80()], &[Type::int(64)], |build, args| {
1733 let n = build.unary(Opcode::FPToUI, args[0], Type::int(64));
1734 build.ret(&[n]);
1735 });
1736 floats(&mut func);
1737 let text = printed(&func, &mut names);
1738 assert!(!text.contains("fptoui"), "the unsigned conversion is gone: {text}");
1739 assert!(text.contains("fptosi.i64"), "the signed one is what is left: {text}");
1740 assert!(!text.contains("bitcast"), "and nothing reads the float as an integer: {text}");
1741 assert!(text.contains("fmul "), "the constant is taken or not by a multiply: {text}");
1742 assert!(text.contains("fsub "), "and subtracted before the conversion: {text}");
1743 assert!(text.contains("shl"), "with the top bit going back on after it: {text}");
1744 assert_eq!(func.blocks().count(), 1, "nor did the other one");
1745 valid(&func, &mut names);
1746 }
1747
1748 /// The arithmetic of those two, where the question is a different one from the question above.
1749 ///
1750 /// At the narrower widths the sequence rounds and the thing worth checking is that it rounds
1751 /// the way the conversion would have. Here nothing rounds, and that is the whole reason the
1752 /// sequence is shorter, so what is worth checking is that nothing does: a float of this format
1753 /// is exactly an integer whose odd part fits in sixty four bits, and every value either
1754 /// sequence makes is one. What would break it is a step whose operands are each a value of the
1755 /// format and whose answer is not, which is the ordinary way an exact looking sequence stops
1756 /// being one.
1757 #[test]
1758 fn nothing_in_either_conversion_at_eighty_bits_rounds() {
1759 /// Whether an integer is a value of a float with a sixty four bit significand.
1760 fn exact(v: i128) -> bool {
1761 let mag = v.unsigned_abs();
1762 mag == 0 || (mag >> mag.trailing_zeros()) < 1 << 64
1763 }
1764
1765 for &x in CASES {
1766 // What `from_unsigned_word_wide` writes, in the order it writes it.
1767 let signed = i128::from(x as i64);
1768 let addend = if (x as i64) < 0 { 1i128 << 64 } else { 0 };
1769 assert!(exact(signed), "the conversion of {x:#x} read as signed is exact");
1770 assert!(exact(addend), "and so is the constant it gets");
1771 assert!(exact(signed + addend), "and so is the sum");
1772 assert_eq!(signed + addend, i128::from(x), "converting {x:#x} into a long double");
1773 }
1774
1775 for &x in CASES {
1776 // And what `to_unsigned_word_wide` writes, over the values that conversion gives back.
1777 let value = i128::from(x);
1778 let taken = if value >= 1 << 63 { 1i128 << 63 } else { 0 };
1779 let under = value - taken;
1780 assert!(exact(under), "the subtraction that brings {x:#x} into range is exact");
1781 let top = u64::from(value >= 1 << 63) << 63;
1782 assert_eq!(under as u64 ^ top, x, "converting {x:#x} back into an unsigned word");
1783 }
1784 }
1785
1786 /// The same obligation the `switch` rewrite has, for the same reason: nothing after this
1787 /// checks the IR again and everything after it assumes what the verifier would have said.
1788 #[test]
1789 fn what_the_float_rewrites_leave_is_valid_ir() {
1790 let (mut names, mut func) = one(&[Type::int(32)], &[f64()], |build, args| {
1791 let k = build.fconst(f64(), 0x3ff8_0000_0000_0000);
1792 let d = build.unary(Opcode::UIToFP, args[0], f64());
1793 let n = build.unary(Opcode::FNeg, d, f64());
1794 let s = build.binary(Opcode::FAdd, n, k, Flags::NONE);
1795 build.ret(&[s]);
1796 });
1797 floats(&mut func);
1798 let module = Module::new(names.intern("f.c"), &target());
1799 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
1800 }
1801
1802 /// Nothing else is touched, for the same reason the `switch` pass has that test: this runs
1803 /// over every function whether or not one has a float in it.
1804 #[test]
1805 fn a_function_with_no_floats_in_it_is_left_exactly_as_it_was() {
1806 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
1807 build.ret(&[args[0]]);
1808 });
1809 let before = printed(&func, &mut names);
1810 floats(&mut func);
1811 assert_eq!(printed(&func, &mut names), before);
1812 }
1813 fn access(size: u64, align: u32) -> MemInfo {
1814 MemInfo {
1815 size,
1816 align,
1817 order: MemOrder::NotAtomic,
1818 tbaa: None,
1819 owns: 0,
1820 restrict: Restrict::NONE,
1821 }
1822 }
1823
1824 /// `void c(void *to, const void *from) { *(T *)to = *(const T *)from; }` for a `T` of that
1825 /// size and alignment, which is what the front end writes for a structure assignment.
1826 fn moving(opcode: Opcode, size: u64, align: u32, byte: Option<i128>) -> (Interner, Func) {
1827 one(&[Type::PTR, Type::PTR], &[], |build, args| {
1828 let second = match byte {
1829 Some(value) => build.iconst(Type::int(8), value),
1830 None => args[1],
1831 };
1832 let mem = build.func().add_mem(access(size, align));
1833 let operands = build.func().push_values(&[args[0], second]);
1834 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1835 build.inst(data, &[]);
1836 build.ret(&[]);
1837 })
1838 }
1839
1840 fn copying(size: u64, align: u32) -> (Interner, Func) {
1841 moving(Opcode::Memcpy, size, align, None)
1842 }
1843
1844 fn filling(size: u64, align: u32, byte: i128) -> (Interner, Func) {
1845 moving(Opcode::Memset, size, align, Some(byte))
1846 }
1847
1848 /// The plan a copy of that size and alignment becomes, as widths, which is what the offsets
1849 /// follow from.
1850 fn widths(size: u64, align: u32) -> Option<Vec<u32>> {
1851 Some(chunks(access(size, align), 8)?.into_iter().map(|(_, width)| width).collect())
1852 }
1853
1854 /// `struct point { int x, y; } a, b; a = b;`, which is sixteen bytes aligned to eight.
1855 #[test]
1856 fn a_copy_becomes_a_load_and_a_store_for_each_word_of_it() {
1857 let (mut names, mut func) = copying(16, 8);
1858 bulk(&mut func, &mut names, 8);
1859
1860 let text = printed(&func, &mut names);
1861 assert!(!text.contains("memcpy"), "the copy is gone: {text}");
1862 assert_eq!(text.matches("load.i64").count(), 2, "a load per word: {text}");
1863 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1864 assert_eq!(
1865 text.matches("ptr_add").count(),
1866 2,
1867 "no offset for the word at the front: {text}"
1868 );
1869 }
1870
1871 /// A word is as wide as the block is known to be aligned to and no wider, because a load
1872 /// wider than that faults on a machine that checks and this does not know whether the one it
1873 /// is compiling for does.
1874 #[test]
1875 fn a_word_is_as_wide_as_the_block_is_aligned_to() {
1876 assert_eq!(widths(16, 8), Some(vec![8, 8]));
1877 assert_eq!(widths(16, 4), Some(vec![4, 4, 4, 4]));
1878 assert_eq!(widths(4, 1), Some(vec![1, 1, 1, 1]));
1879 }
1880
1881 /// What is left over is narrower words rather than a run of bytes, so thirteen bytes aligned
1882 /// to eight is three moves and not six.
1883 #[test]
1884 fn what_is_left_over_is_narrower_words_and_not_a_run_of_bytes() {
1885 assert_eq!(widths(13, 8), Some(vec![8, 4, 1]));
1886 assert_eq!(widths(3, 8), Some(vec![2, 1]));
1887 assert_eq!(widths(1, 8), Some(vec![1]));
1888 }
1889
1890 /// Every offset is a multiple of the width at it, which is what lets the alignment of each
1891 /// access be written down as its width.
1892 #[test]
1893 fn every_word_starts_somewhere_it_is_aligned_for() {
1894 for (at, width) in chunks(access(13, 8), 8).expect("a plan for thirteen bytes") {
1895 assert_eq!(at % u64::from(width), 0, "{at} is a multiple of {width}");
1896 }
1897 }
1898
1899 /// `struct big b = { 0 };`, where the part the initialiser did not name is zeroed.
1900 #[test]
1901 fn a_fill_is_the_byte_spread_across_each_word() {
1902 let (mut names, mut func) = filling(16, 8, 0);
1903 bulk(&mut func, &mut names, 8);
1904
1905 let text = printed(&func, &mut names);
1906 assert!(!text.contains("memset"), "the fill is gone: {text}");
1907 assert_eq!(text.matches("store").count(), 2, "a store per word: {text}");
1908 assert!(!text.contains("load"), "a fill reads nothing: {text}");
1909 }
1910
1911 /// The spreading is arithmetic on the byte, which is the thing a rule cannot do and the
1912 /// reason this pass exists at all.
1913 #[test]
1914 fn the_byte_is_repeated_across_the_word_it_is_stored_as() {
1915 assert_eq!(spread(0, 8), 0);
1916 assert_eq!(spread(0xff, 1), 0xff);
1917 assert_eq!(spread(0xff, 4), 0xffff_ffff);
1918 assert_eq!(spread(0xab, 2), 0xabab);
1919 assert_eq!(spread(0xab, 8), 0xabab_abab_abab_abab);
1920 }
1921
1922 /// A copy larger than the threshold is a call to the runtime rather than a run of moves.
1923 #[test]
1924 fn a_copy_too_large_to_unroll_becomes_a_call_to_the_runtime() {
1925 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1926 let (mut names, mut func) = copying(size, 1);
1927 bulk(&mut func, &mut names, 8);
1928 let text = printed(&func, &mut names);
1929 assert!(text.contains("call @memcpy"), "a call and not a bulk move: {text}");
1930
1931 // And the one word under it is moves, because the threshold counts moves rather than
1932 // bytes and the whole point of the threshold is that a small copy does not pay for a call.
1933 let (mut names, mut func) = copying(size - 1, 1);
1934 bulk(&mut func, &mut names, 8);
1935 assert!(!printed(&func, &mut names).contains("memcpy"), "one word under it is unrolled");
1936 }
1937
1938 /// The call passes what C passes, which is not what the IR holds. The size lives beside the
1939 /// instruction in the IR and travels in a register in the call.
1940 #[test]
1941 fn the_call_passes_the_size_that_the_instruction_carried_beside_it() {
1942 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
1943 let (mut names, mut func) = copying(size, 1);
1944 bulk(&mut func, &mut names, 8);
1945 let text = printed(&func, &mut names);
1946 assert!(text.contains(&format!("{size}")), "the size is an argument now: {text}");
1947 }
1948
1949 /// A `memmove` is a call whatever its size, because the two sides may overlap and a run of
1950 /// moves in one direction is right for only one of the two ways they can.
1951 #[test]
1952 fn a_move_is_a_call_however_small_it_is() {
1953 let (mut names, mut func) = moving(Opcode::Memmove, 8, 8, None);
1954 bulk(&mut func, &mut names, 8);
1955 let text = printed(&func, &mut names);
1956 assert!(text.contains("call @memmove"), "a call and not a run of moves: {text}");
1957 }
1958
1959 /// A fill whose byte the program works out rather than names. Spreading a value across a
1960 /// word at runtime is a multiply, so this is a call rather than moves however small it is.
1961 #[test]
1962 fn a_fill_whose_byte_is_not_a_constant_becomes_a_call() {
1963 let (mut names, mut func) = one(&[Type::PTR, Type::int(8)], &[], |build, args| {
1964 let mem = build.func().add_mem(access(8, 8));
1965 let operands = build.func().push_values(&[args[0], args[1]]);
1966 let data = InstData {
1967 args: operands,
1968 extra: Extra::Mem(mem),
1969 ..InstData::new(Opcode::Memset)
1970 };
1971 build.inst(data, &[]);
1972 build.ret(&[]);
1973 });
1974 bulk(&mut func, &mut names, 8);
1975 let text = printed(&func, &mut names);
1976 assert!(text.contains("call @memset"), "a call and not a run of stores: {text}");
1977 // Widened, because C passes the byte as an `int` and the IR holds it as a byte.
1978 assert!(text.contains("zext.i32"), "the byte is widened to what C passes: {text}");
1979 }
1980
1981 /// A copy or a fill whose length the program works out, which carries the count as a third
1982 /// operand and nothing beside the instruction.
1983 fn computing(opcode: Opcode, byte: Option<i128>) -> (Interner, Func) {
1984 one(&[Type::PTR, Type::PTR, Type::int(64)], &[], |build, args| {
1985 let second = match byte {
1986 Some(value) => build.iconst(Type::int(8), value),
1987 None => args[1],
1988 };
1989 let mem = build.func().add_mem(access(0, 4));
1990 let operands = build.func().push_values(&[args[0], second, args[2]]);
1991 let data = InstData { args: operands, extra: Extra::Mem(mem), ..InstData::new(opcode) };
1992 build.inst(data, &[]);
1993 build.ret(&[]);
1994 })
1995 }
1996
1997 /// However few bytes it turns out to be, because how many there are is not known here and a
1998 /// plan is a list of offsets somebody has to be able to write down.
1999 #[test]
2000 fn a_bulk_move_of_a_length_the_program_works_out_is_a_call_whatever_the_payload_says() {
2001 for (opcode, name) in
2002 [(Opcode::Memcpy, "memcpy"), (Opcode::Memmove, "memmove"), (Opcode::Memset, "memset")]
2003 {
2004 let byte = (opcode == Opcode::Memset).then_some(0);
2005 let (mut names, mut func) = computing(opcode, byte);
2006 bulk(&mut func, &mut names, 8);
2007 let text = printed(&func, &mut names);
2008 assert!(text.contains(&format!("call @{name}")), "a call and not a plan: {text}");
2009 // The count is the operand it came in with rather than a constant made here, which is
2010 // the whole difference between this and a copy whose size the payload holds.
2011 assert!(!text.contains("iconst.i64"), "no size was invented: {text}");
2012 }
2013 }
2014
2015 #[test]
2016 fn what_a_bulk_move_of_a_length_the_program_works_out_becomes_is_ir_that_verifies() {
2017 for opcode in [Opcode::Memcpy, Opcode::Memmove, Opcode::Memset] {
2018 let byte = (opcode == Opcode::Memset).then_some(0);
2019 let (mut names, mut func) = computing(opcode, byte);
2020 bulk(&mut func, &mut names, 8);
2021 valid(&func, &mut names);
2022 }
2023 }
2024
2025 /// A machine whose widest move is four bytes gets four byte words out of an eight byte block,
2026 /// however well aligned the block is.
2027 #[test]
2028 fn no_word_is_wider_than_the_machine_moves_at_once() {
2029 assert_eq!(chunks(access(8, 8), 4).map(|plan| plan.len()), Some(2));
2030 assert_eq!(chunks(access(8, 8), 8).map(|plan| plan.len()), Some(1));
2031 }
2032
2033 #[test]
2034 fn what_a_copy_becomes_is_ir_that_verifies() {
2035 let (mut names, mut func) = copying(13, 8);
2036 bulk(&mut func, &mut names, 8);
2037 let module = Module::new(names.intern("c.c"), &target());
2038 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
2039 }
2040
2041 #[test]
2042 fn what_a_fill_becomes_is_ir_that_verifies() {
2043 let (mut names, mut func) = filling(13, 8, 0xff);
2044 bulk(&mut func, &mut names, 8);
2045 let module = Module::new(names.intern("f.c"), &target());
2046 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
2047 }
2048
2049 #[test]
2050 fn what_a_copy_too_large_to_unroll_becomes_is_ir_that_verifies() {
2051 let size = u64::try_from(UNROLL).expect("a small threshold") + 1;
2052 let (mut names, mut func) = copying(size, 1);
2053 bulk(&mut func, &mut names, 8);
2054 let module = Module::new(names.intern("c.c"), &target());
2055 rucc_ir::verify_func(&module, &func, &names).expect("the call is valid IR");
2056 }
2057
2058 /// Nothing else is touched, for the same reason the other two passes have that test.
2059 #[test]
2060 fn a_function_with_no_bulk_move_in_it_is_left_exactly_as_it_was() {
2061 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2062 build.ret(&[args[0]]);
2063 });
2064 let before = printed(&func, &mut names);
2065 bulk(&mut func, &mut names, 8);
2066 assert_eq!(printed(&func, &mut names), before);
2067 }
2068
2069 /// A function whose body is one byte swap of the given width, which is what a call to
2070 /// `__builtin_bswap16` and its neighbours has become by the time this pass runs.
2071 fn swapping(width: u32) -> (Interner, Func) {
2072 let ty = Type::int(width);
2073 one(&[ty], &[ty], |build, args| {
2074 let s = build.unary(Opcode::Bswap, args[0], ty);
2075 build.ret(&[s]);
2076 })
2077 }
2078
2079 /// The masks are the alternating runs the halving needs, and they are the constants a reader
2080 /// checking this against a byte swap written by hand would expect to see.
2081 ///
2082 /// At thirty two bits the first step swaps sixteen bit halves and so keeps the low half of each
2083 /// pair, which is `0x0000ffff`, and the second swaps bytes within those halves and keeps
2084 /// `0x00ff00ff`. Written as signed because that is what the IR holds an immediate as.
2085 #[test]
2086 fn the_masks_are_the_alternating_runs_of_the_group_being_swapped() {
2087 assert_eq!(alternating(32, 16), 0x0000_ffff);
2088 assert_eq!(alternating(32, 8), 0x00ff_00ff);
2089 assert_eq!(alternating(16, 8), 0x00ff);
2090 assert_eq!(alternating(64, 32), 0x0000_0000_ffff_ffff);
2091 assert_eq!(alternating(64, 16), 0x0000_ffff_0000_ffff);
2092 assert_eq!(alternating(64, 8), 0x00ff_00ff_00ff_00ff);
2093 }
2094
2095 /// The two byte swap is the one step there is, so it is one mask and one pair of shifts.
2096 #[test]
2097 fn a_two_byte_swap_is_one_exchange_of_neighbouring_bytes() {
2098 let (mut names, mut func) = swapping(16);
2099 bytes(&mut func);
2100
2101 let text = printed(&func, &mut names);
2102 assert!(!text.contains("bswap"), "the instruction is gone: {text}");
2103 assert!(text.contains("iconst.i16 255"), "the low byte of the pair: {text}");
2104 assert_eq!(text.matches("shl").count(), 1, "one shift up: {text}");
2105 assert_eq!(text.matches("lshr").count(), 1, "one shift down: {text}");
2106 assert_eq!(text.matches(" or ").count(), 1, "and the two put together: {text}");
2107 }
2108
2109 /// The wider two are the same step done again at half the group, which is what makes the count
2110 /// grow by a fixed amount per doubling rather than per byte.
2111 #[test]
2112 fn a_wider_swap_is_the_same_exchange_once_per_halving() {
2113 for (width, steps) in [(16u32, 1usize), (32, 2), (64, 3)] {
2114 let (mut names, mut func) = swapping(width);
2115 bytes(&mut func);
2116 let text = printed(&func, &mut names);
2117 assert_eq!(text.matches("shl").count(), steps, "at {width}: {text}");
2118 assert_eq!(text.matches("lshr").count(), steps, "at {width}: {text}");
2119 assert_eq!(text.matches(" and ").count(), steps * 2, "at {width}: {text}");
2120 assert_eq!(text.matches(" or ").count(), steps, "at {width}: {text}");
2121 }
2122 }
2123
2124 /// The shift counts are the group being exchanged and nothing else, so a reader can read the
2125 /// halving straight off the constants.
2126 #[test]
2127 fn the_shift_counts_are_the_group_width_halving_as_it_goes() {
2128 let (mut names, mut func) = swapping(64);
2129 bytes(&mut func);
2130 let text = printed(&func, &mut names);
2131 for count in ["iconst.i64 32", "iconst.i64 16", "iconst.i64 8"] {
2132 assert!(text.contains(count), "{count} is a step: {text}");
2133 }
2134 }
2135
2136 /// The rewrite has to leave a function the verifier still accepts, for the reason the switch
2137 /// rewrite has the same test: nothing rechecks it.
2138 #[test]
2139 fn what_a_byte_swap_becomes_is_ir_that_verifies() {
2140 let (mut names, mut func) = swapping(32);
2141 bytes(&mut func);
2142 let module = Module::new(names.intern("b.c"), &target());
2143 rucc_ir::verify_func(&module, &func, &names).expect("the rewrite builds valid IR");
2144 }
2145
2146 /// Nothing else is touched, which matters because this runs over every function in the program
2147 /// and nearly none of them reverses any bytes.
2148 #[test]
2149 fn a_function_with_no_byte_swap_in_it_is_left_exactly_as_it_was() {
2150 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2151 build.ret(&[args[0]]);
2152 });
2153 let before = printed(&func, &mut names);
2154 bytes(&mut func);
2155 assert_eq!(printed(&func, &mut names), before);
2156 }
2157
2158 /// A function whose body is one bit count of the given opcode and width.
2159 fn counting(op: Opcode, width: u32) -> (Interner, Func) {
2160 let ty = Type::int(width);
2161 one(&[ty], &[ty], |build, args| {
2162 let c = build.unary(op, args[0], ty);
2163 build.ret(&[c]);
2164 })
2165 }
2166
2167 /// The masks the halving sum needs, which are the ones any bit counting routine is written with
2168 /// and are worth being able to read off against one.
2169 #[test]
2170 fn the_counting_masks_are_the_ones_the_halving_sum_is_written_with() {
2171 assert_eq!(alternating(32, 1), 0x5555_5555);
2172 assert_eq!(alternating(32, 2), 0x3333_3333);
2173 assert_eq!(alternating(32, 4), 0x0f0f_0f0f);
2174 assert_eq!(every(32, 8, 1), 0x0101_0101);
2175 assert_eq!(every(64, 8, 1), 0x0101_0101_0101_0101);
2176 }
2177
2178 /// The set bit count is arithmetic and the multiply is what adds the bytes together, which is
2179 /// the step a reader is most likely to want to check.
2180 #[test]
2181 fn a_set_bit_count_is_the_halving_sum_and_a_multiply_that_adds_the_bytes() {
2182 let (mut names, mut func) = counting(Opcode::Ctpop, 32);
2183 counts(&mut func);
2184
2185 let text = printed(&func, &mut names);
2186 assert!(!text.contains("ctpop"), "the instruction is gone: {text}");
2187 assert!(text.contains("iconst.i32 1431655765"), "the pairs mask: {text}");
2188 assert!(text.contains("iconst.i32 858993459"), "the nibbles mask: {text}");
2189 assert!(text.contains("iconst.i32 252645135"), "the bytes mask: {text}");
2190 assert_eq!(text.matches(" mul ").count(), 1, "one multiply: {text}");
2191 assert!(text.contains("iconst.i32 24"), "and the top byte is the answer: {text}");
2192 }
2193
2194 /// At eight bits there are no bytes left to add, so the multiply is not written at all.
2195 #[test]
2196 fn a_count_of_one_byte_stops_before_the_multiply() {
2197 let (mut names, mut func) = counting(Opcode::Ctpop, 8);
2198 counts(&mut func);
2199 let text = printed(&func, &mut names);
2200 assert!(!text.contains("ctpop"), "{text}");
2201 assert!(!text.contains(" mul "), "nothing to add together: {text}");
2202 }
2203
2204 /// A leading zero count smears every set bit downwards and counts what is left unset above it,
2205 /// which is one shift and one or per doubling and then the count.
2206 #[test]
2207 fn a_leading_zero_count_smears_the_value_down_and_counts_the_complement() {
2208 let (mut names, mut func) = counting(Opcode::Ctlz, 32);
2209 counts(&mut func);
2210
2211 let text = printed(&func, &mut names);
2212 assert!(!text.contains("ctlz"), "the instruction is gone: {text}");
2213 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2214 for by in ["iconst.i32 1", "iconst.i32 2", "iconst.i32 4", "iconst.i32 8", "iconst.i32 16"]
2215 {
2216 assert!(text.contains(by), "{by} is a smearing step: {text}");
2217 }
2218 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2219 }
2220
2221 /// A trailing zero count is the bits below the lowest set one, which is a mask and no smearing.
2222 #[test]
2223 fn a_trailing_zero_count_masks_the_bits_below_the_lowest_set_one() {
2224 let (mut names, mut func) = counting(Opcode::Cttz, 32);
2225 counts(&mut func);
2226
2227 let text = printed(&func, &mut names);
2228 assert!(!text.contains("cttz"), "the instruction is gone: {text}");
2229 assert!(!text.contains("ctpop"), "and so is the count it became: {text}");
2230 assert!(text.contains("iconst.i32 -1"), "the complement and the decrement: {text}");
2231 assert_eq!(text.matches(" xor ").count(), 1, "one complement: {text}");
2232 // Far fewer instructions than the leading count, because there is no smearing to do.
2233 assert!(text.matches(" or ").count() <= 1, "no smearing run: {text}");
2234 }
2235
2236 /// The rewrites have to leave a function the verifier still accepts, at every width and for all
2237 /// three, because nothing rechecks what comes out of here.
2238 #[test]
2239 fn what_a_bit_count_becomes_is_ir_that_verifies() {
2240 for op in [Opcode::Ctpop, Opcode::Ctlz, Opcode::Cttz] {
2241 for width in [8u32, 16, 32, 64] {
2242 let (mut names, mut func) = counting(op, width);
2243 counts(&mut func);
2244 let module = Module::new(names.intern("c.c"), &target());
2245 rucc_ir::verify_func(&module, &func, &names)
2246 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2247 }
2248 }
2249 }
2250
2251 /// A width the arithmetic is not written for is left as the instruction it was, so a selector
2252 /// with no rule for it says so rather than the program getting a number counted in the wrong
2253 /// shape.
2254 #[test]
2255 fn a_width_the_halving_sum_is_not_written_for_is_left_alone() {
2256 let (mut names, mut func) = counting(Opcode::Ctpop, 24);
2257 counts(&mut func);
2258 assert!(printed(&func, &mut names).contains("ctpop"), "left as it was");
2259 }
2260
2261 /// Nothing else is touched, for the same reason the other passes have that test.
2262 #[test]
2263 fn a_function_with_no_bit_count_in_it_is_left_exactly_as_it_was() {
2264 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2265 build.ret(&[args[0]]);
2266 });
2267 let before = printed(&func, &mut names);
2268 counts(&mut func);
2269 assert_eq!(printed(&func, &mut names), before);
2270 }
2271
2272 /// One overflow checked instruction whose value and whose flag are both returned, so that the
2273 /// substitution has two readers to find rather than none.
2274 fn checking(op: Opcode, width: u32) -> (Interner, Func) {
2275 let ty = Type::int(width);
2276 let bit = ty.with_lane(Type::I1);
2277 one(&[ty, ty], &[ty, bit], |build, args| {
2278 let (value, flag) = build.checked(op, args[0], args[1]);
2279 build.ret(&[value, flag]);
2280 })
2281 }
2282
2283 /// An unsigned add wraps exactly when the sum came out below an operand, which is one
2284 /// comparison and no arithmetic on the sign bits.
2285 #[test]
2286 fn a_checked_unsigned_add_becomes_an_add_and_one_comparison() {
2287 let (mut names, mut func) = checking(Opcode::UAddOverflow, 32);
2288 overflows(&mut func);
2289
2290 let text = printed(&func, &mut names);
2291 assert!(!text.contains("uadd_overflow"), "the instruction is gone: {text}");
2292 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2293 assert_eq!(text.matches("icmp ult").count(), 1, "and one comparison: {text}");
2294 assert!(!text.contains(" xor "), "nothing about sign bits: {text}");
2295 }
2296
2297 /// A signed add wraps exactly when the operands agreed in sign and the answer did not, which is
2298 /// the sign bit of `(a ^ v) & (b ^ v)`.
2299 #[test]
2300 fn a_checked_signed_add_becomes_an_add_and_the_sign_bit_of_two_exclusive_ors() {
2301 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2302 overflows(&mut func);
2303
2304 let text = printed(&func, &mut names);
2305 assert!(!text.contains("sadd_overflow"), "the instruction is gone: {text}");
2306 assert_eq!(text.matches(" add ").count(), 1, "one add: {text}");
2307 assert_eq!(text.matches(" xor ").count(), 2, "the answer against each operand: {text}");
2308 assert_eq!(text.matches(" and ").count(), 1, "both at once: {text}");
2309 assert!(text.contains("icmp slt"), "and its sign bit: {text}");
2310 }
2311
2312 /// An unsigned subtract wraps exactly when the left operand was below the right, which does not
2313 /// need the answer at all.
2314 #[test]
2315 fn a_checked_unsigned_subtract_compares_the_operands_and_not_the_answer() {
2316 let (mut names, mut func) = checking(Opcode::USubOverflow, 64);
2317 overflows(&mut func);
2318
2319 let text = printed(&func, &mut names);
2320 assert!(!text.contains("usub_overflow"), "the instruction is gone: {text}");
2321 assert_eq!(text.matches(" sub ").count(), 1, "one subtract: {text}");
2322 assert!(text.contains("icmp ult %0, %1"), "the operands, in order: {text}");
2323 }
2324
2325 /// A checked multiply is the ordinary multiply and the high half of the product, which is four
2326 /// multiplies of the halves and the carry between them.
2327 ///
2328 /// This is the expensive one and it is what tamnd/rucc#309 is mostly worth: a machine whose
2329 /// multiply writes the high half into a second register does the whole of it in one
2330 /// instruction.
2331 #[test]
2332 fn a_checked_multiply_becomes_a_multiply_and_the_high_half_of_the_product() {
2333 let (mut names, mut func) = checking(Opcode::UMulOverflow, 64);
2334 overflows(&mut func);
2335
2336 let text = printed(&func, &mut names);
2337 assert!(!text.contains("umul_overflow"), "the instruction is gone: {text}");
2338 assert_eq!(text.matches(" mul ").count(), 5, "the answer and the four halves: {text}");
2339 assert!(text.contains("iconst.i64 32"), "split at half the width: {text}");
2340 assert!(text.contains("iconst.i64 4294967295"), "and masked to it: {text}");
2341 assert!(text.contains("icmp ne"), "the high half against zero: {text}");
2342 assert!(!text.contains("ashr"), "and nothing corrected for sign: {text}");
2343 }
2344
2345 /// The signed multiply is the unsigned one with the sign correction on top, and the test is
2346 /// against the sign extension of the low half rather than against zero.
2347 #[test]
2348 fn a_checked_signed_multiply_corrects_the_high_half_for_each_negative_operand() {
2349 let (mut names, mut func) = checking(Opcode::SMulOverflow, 64);
2350 overflows(&mut func);
2351
2352 let text = printed(&func, &mut names);
2353 assert!(!text.contains("smul_overflow"), "the instruction is gone: {text}");
2354 assert_eq!(
2355 text.matches(" ashr ").count(),
2356 3,
2357 "each operand's sign, and the answer: {text}"
2358 );
2359 assert!(text.contains("iconst.i64 63"), "spread from the top bit: {text}");
2360 assert_eq!(text.matches(" sub ").count(), 2, "one correction per operand: {text}");
2361 }
2362
2363 /// Both results have to reach their readers, which is the one thing this pass has to do that
2364 /// the others do not: the instruction goes away rather than becoming another one, so nothing is
2365 /// left holding the values the rest of the function was reading.
2366 #[test]
2367 fn both_results_are_substituted_into_whoever_was_reading_them() {
2368 let (mut names, mut func) = checking(Opcode::SAddOverflow, 32);
2369 overflows(&mut func);
2370
2371 // The whole of it, because what this is checking is that nothing is left pointing at the
2372 // two values the removed instruction used to define. The return names the add and the
2373 // comparison, which are what replaced them.
2374 let text = printed(&func, &mut names);
2375 assert_eq!(
2376 text,
2377 concat!(
2378 "func @f(i32, i32) -> (i32, i1), linkage(external) {\n",
2379 "block0(%0: i32, %1: i32):\n",
2380 " %2 = add %0, %1\n",
2381 " %3 = xor %0, %2\n",
2382 " %4 = xor %1, %2\n",
2383 " %5 = and %3, %4\n",
2384 " %6 = iconst.i32 0\n",
2385 " %7 = icmp slt %5, %6\n",
2386 " return %2, %7\n",
2387 "}\n",
2388 ),
2389 );
2390 }
2391
2392 /// The rewrites have to leave a function the verifier still accepts, for all six and at every
2393 /// width, because nothing rechecks what comes out of here.
2394 #[test]
2395 fn what_an_overflow_check_becomes_is_ir_that_verifies() {
2396 let all = [
2397 Opcode::UAddOverflow,
2398 Opcode::SAddOverflow,
2399 Opcode::USubOverflow,
2400 Opcode::SSubOverflow,
2401 Opcode::UMulOverflow,
2402 Opcode::SMulOverflow,
2403 ];
2404 for op in all {
2405 for width in [8u32, 16, 32, 64, 128] {
2406 let (mut names, mut func) = checking(op, width);
2407 overflows(&mut func);
2408 let module = Module::new(names.intern("c.c"), &target());
2409 rucc_ir::verify_func(&module, &func, &names)
2410 .unwrap_or_else(|e| panic!("{op:?} at {width}: {e:?}"));
2411 }
2412 }
2413 }
2414
2415 /// A width the arithmetic is not written for is left as the instruction it was, for the same
2416 /// reason the bit counts leave one: a selector with no rule for it says so, which is better
2417 /// than an answer checked in the wrong shape.
2418 #[test]
2419 fn a_width_the_split_is_not_written_for_is_left_alone() {
2420 let (mut names, mut func) = checking(Opcode::UMulOverflow, 24);
2421 overflows(&mut func);
2422 assert!(printed(&func, &mut names).contains("umul_overflow"), "left as it was");
2423 }
2424
2425 /// A check at the width no register holds is rewritten here and split into halves after.
2426 ///
2427 /// This pass runs above `crate::wide` for exactly this, because an overflow check is the one
2428 /// instruction whose result is two things and that pass has no answer for one. What it leaves
2429 /// behind is arithmetic and a comparison, which are both things the splitting understands, so
2430 /// the check reaches the machine as instructions the machine has.
2431 #[test]
2432 fn a_check_at_the_width_no_register_holds_is_rewritten_here() {
2433 let (mut names, mut func) = checking(Opcode::UAddOverflow, 128);
2434 overflows(&mut func);
2435 let text = printed(&func, &mut names);
2436 assert!(!text.contains("uadd_overflow"), "the check is gone: {text}");
2437 assert!(text.contains(" = add "), "into the arithmetic it is: {text}");
2438 assert!(text.contains("icmp ult"), "and the test that says it wrapped: {text}");
2439 }
2440
2441 /// Nothing else is touched, for the same reason the other passes have that test.
2442 #[test]
2443 fn a_function_with_no_overflow_check_in_it_is_left_exactly_as_it_was() {
2444 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2445 build.ret(&[args[0]]);
2446 });
2447 let before = printed(&func, &mut names);
2448 overflows(&mut func);
2449 assert_eq!(printed(&func, &mut names), before);
2450 }
2451
2452 /// `T x = *p;` with an ordering on it, which is what `__atomic_load_n` becomes.
2453 fn reading(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2454 one(&[Type::PTR], &[ty], |build, args| {
2455 let info = MemInfo { order, ..access(0, align) };
2456 let value = build.atomic_load(ty, args[0], info, Flags::NONE);
2457 build.ret(&[value]);
2458 })
2459 }
2460
2461 /// `*p = x;` with an ordering on it, which is what `__atomic_store_n` becomes.
2462 fn writing(ty: Type, align: u32, order: MemOrder) -> (Interner, Func) {
2463 one(&[Type::PTR, ty], &[], |build, args| {
2464 let info = MemInfo { order, ..access(0, align) };
2465 build.atomic_store(args[1], args[0], info, Flags::NONE);
2466 build.ret(&[]);
2467 })
2468 }
2469
2470 /// Every ordered access below the strongest store is the plain instruction on this machine,
2471 /// and the ordering comes off it when it is.
2472 ///
2473 /// The ordering coming off is not cosmetic: the verifier refuses an ordering on a plain access,
2474 /// because a plain load may be moved, duplicated and dropped and an ordering left on one would
2475 /// be a claim nothing downstream honours.
2476 #[test]
2477 fn an_ordered_access_becomes_the_plain_one_this_machine_already_orders() {
2478 for order in [MemOrder::Relaxed, MemOrder::Acquire, MemOrder::SeqCst] {
2479 let (mut names, mut func) = reading(Type::int(32), 4, order);
2480 orderings(&mut func, 8, true);
2481 let text = printed(&func, &mut names);
2482 assert!(text.contains("load.i32"), "{order:?}: {text}");
2483 assert!(!text.contains("atomic_load"), "{order:?}: {text}");
2484 assert!(!text.contains(order.name()), "the ordering came off: {text}");
2485 }
2486
2487 for order in [MemOrder::Relaxed, MemOrder::Release] {
2488 let (mut names, mut func) = writing(Type::int(32), 4, order);
2489 orderings(&mut func, 8, true);
2490 let text = printed(&func, &mut names);
2491 assert!(text.contains("store %1 -> %0"), "{order:?}: {text}");
2492 assert!(!text.contains("atomic_store"), "{order:?}: {text}");
2493 assert!(!text.contains("fence"), "{order:?} costs nothing here: {text}");
2494 }
2495 }
2496
2497 /// The strongest store is the plain store and a barrier behind it, in that order.
2498 ///
2499 /// It is the one thing total store order does not give away: a store followed by a load of
2500 /// another address may be seen the other way round, and sequential consistency is exactly the
2501 /// ordering that forbids it.
2502 #[test]
2503 fn the_strongest_store_keeps_a_barrier_behind_it() {
2504 let (mut names, mut func) = writing(Type::int(32), 4, MemOrder::SeqCst);
2505 orderings(&mut func, 8, true);
2506 let text = printed(&func, &mut names);
2507 let (before, after) = text.split_once("fence seq_cst").expect("a barrier");
2508 assert!(before.contains("store %1 -> %0"), "the store comes first: {text}");
2509 assert!(!after.contains("store"), "and nothing is between them: {text}");
2510 assert!(!text.contains("atomic_store"), "{text}");
2511 }
2512
2513 /// On a machine that is not total store order only the relaxed access is the plain one. The
2514 /// rest stay ordered for `crate::lower` to write as the acquiring load and the releasing store,
2515 /// and the strongest store gets no fence, since `stlr` is already enough.
2516 #[test]
2517 fn a_weakly_ordered_machine_keeps_every_ordering_above_relaxed() {
2518 let (mut names, mut func) = reading(Type::int(32), 4, MemOrder::Relaxed);
2519 orderings(&mut func, 8, false);
2520 assert!(!printed(&func, &mut names).contains("atomic_load"), "relaxed is a plain load");
2521
2522 for order in [MemOrder::Acquire, MemOrder::SeqCst] {
2523 let (mut names, mut func) = reading(Type::int(32), 4, order);
2524 let before = printed(&func, &mut names);
2525 orderings(&mut func, 8, false);
2526 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2527 }
2528 for order in [MemOrder::Release, MemOrder::SeqCst] {
2529 let (mut names, mut func) = writing(Type::int(32), 4, order);
2530 let before = printed(&func, &mut names);
2531 orderings(&mut func, 8, false);
2532 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2533 }
2534 }
2535
2536 /// A barrier the program wrote is left for `crate::lower`, which is where a target says what
2537 /// an ordering costs.
2538 #[test]
2539 fn a_barrier_is_left_for_the_place_that_knows_what_one_costs() {
2540 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2541 let (mut names, mut func) = one(&[], &[], |build, _| {
2542 build.fence(order);
2543 build.ret(&[]);
2544 });
2545 let before = printed(&func, &mut names);
2546 orderings(&mut func, 8, true);
2547 assert_eq!(printed(&func, &mut names), before, "{order:?}");
2548 }
2549 }
2550
2551 /// An access the machine cannot do in one go is left as the opcode it was, which is a refusal
2552 /// naming the instruction rather than an answer that is not atomic at all.
2553 ///
2554 /// Two ways it happens: wider than a word, and narrower than a word but at an address the
2555 /// program said less about than the width. Both are `__atomic_is_lock_free` answering no.
2556 #[test]
2557 fn an_access_this_machine_cannot_do_in_one_go_is_left_alone() {
2558 for (ty, align) in [(Type::int(128), 16), (Type::int(64), 4)] {
2559 let (mut names, mut func) = reading(ty, align, MemOrder::SeqCst);
2560 orderings(&mut func, 8, true);
2561 assert!(printed(&func, &mut names).contains("atomic_load"), "left as it was");
2562 }
2563 }
2564
2565 /// What comes out is IR the verifier takes, which is the check that matters most here: the
2566 /// ordering has to be gone from a plain access or this pass has built something illegal.
2567 #[test]
2568 fn what_the_ordered_accesses_become_verifies() {
2569 for order in MemOrder::all().filter(|&order| order != MemOrder::NotAtomic) {
2570 for (mut names, mut func) in
2571 [reading(Type::int(32), 4, order), writing(Type::int(32), 4, order)]
2572 {
2573 if !order.is_valid_for_load() && !order.is_valid_for_store() {
2574 continue;
2575 }
2576 orderings(&mut func, 8, true);
2577 let module = Module::new(names.intern("a.c"), &target());
2578 rucc_ir::verify_func(&module, &func, &names)
2579 .unwrap_or_else(|e| panic!("{order:?}: {e:?}"));
2580 }
2581 }
2582 }
2583
2584 /// Nothing else is touched, for the same reason the other passes have that test.
2585 #[test]
2586 fn a_function_with_no_ordered_access_in_it_is_left_exactly_as_it_was() {
2587 let (mut names, mut func) = one(&[Type::int(32)], &[Type::int(32)], |build, args| {
2588 build.ret(&[args[0]]);
2589 });
2590 let before = printed(&func, &mut names);
2591 orderings(&mut func, 8, true);
2592 assert_eq!(printed(&func, &mut names), before);
2593 }
2594
2595 /// A variable length array, built the way the front end builds one.
2596 fn growing(build: &mut Builder<'_>, size: rucc_ir::Value, align: u32) -> rucc_ir::Value {
2597 let info = MemInfo {
2598 size: 0,
2599 align,
2600 order: MemOrder::NotAtomic,
2601 tbaa: None,
2602 owns: 0,
2603 restrict: Restrict::default(),
2604 };
2605 let mem = build.func().add_mem(info);
2606 let args = build.func().push_values(&[size]);
2607 build.value(
2608 InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Alloca) },
2609 Type::PTR,
2610 )
2611 }
2612
2613 /// The bytes an array asks for are rounded up to what a call leaves the stack pointer on.
2614 #[test]
2615 fn the_bytes_a_variable_length_array_takes_are_a_multiple_of_the_stack_alignment() {
2616 let (mut names, mut func) = one(&[Type::int(64)], &[Type::PTR], |build, args| {
2617 let slot = growing(build, args[0], 8);
2618 build.ret(&[slot]);
2619 });
2620 rounds(&mut func, 16);
2621 let text = printed(&func, &mut names);
2622 assert!(text.contains("%3 = add %0, %1"), "{text}");
2623 assert!(text.contains("%4 = and %3, %2"), "{text}");
2624 assert!(text.contains("%5 = alloca %4, align 8"), "{text}");
2625 // One array and one allocation of it, since nothing here wanted an address the stack
2626 // pointer does not already land on.
2627 assert_eq!(text.matches("alloca").count(), 1, "{text}");
2628 assert!(!text.contains("ptr_add"), "{text}");
2629 }
2630
2631 /// One that wants more alignment than that takes the room for it and lands inside it.
2632 ///
2633 /// The instruction the rest of the function reads is the `ptr_add`, which is the instruction
2634 /// the array was, so the value it writes is the value everything already held. What the
2635 /// `alloca` above it asks for is the convention's alignment, which is the truth about the
2636 /// block: the object wanted thirty two and the object is the offset into the block.
2637 #[test]
2638 fn a_variable_length_array_wanting_more_alignment_is_placed_inside_the_bytes_it_took() {
2639 let (mut names, mut func) = one(&[Type::int(64)], &[Type::PTR], |build, args| {
2640 let slot = growing(build, args[0], 32);
2641 build.ret(&[slot]);
2642 });
2643 rounds(&mut func, 16);
2644 let text = printed(&func, &mut names);
2645 // The size, rounded up and then given the whole of the alignment as room to move in.
2646 assert!(text.contains("%5 = iconst.i64 32"), "{text}");
2647 assert!(text.contains("%6 = add %4, %5"), "{text}");
2648 // The offset, which is how far above the block the next multiple of thirty two is.
2649 assert!(text.contains("ptrtoint"), "{text}");
2650 assert!(text.contains("%11 = iconst.i64 31"), "{text}");
2651 assert!(text.contains("%10 = sub %9, %8"), "{text}");
2652 assert!(text.contains("%12 = and %10, %11"), "{text}");
2653 // The block itself asks for what a call leaves the stack pointer on and no more.
2654 assert!(text.contains("%7 = alloca %6, align 16"), "{text}");
2655 // The last instruction of the rewrite is the array itself, so the value the function
2656 // returns is the value it already returned.
2657 assert!(text.contains("%13 = ptr_add %7, %12"), "{text}");
2658 assert!(text.contains("return %13"), "{text}");
2659 }
2660}