pub enum Form {
Show 37 variants
LoadImm,
AluRr,
AluRi,
UnaryR,
ShiftRi,
ShiftCl,
CmpSet,
Convert,
DivQuo,
DivRem,
Lea,
Load,
Store,
RetVal,
RetVal2,
ArgVal,
BrCond,
Test,
Jcc,
Jmp,
Call,
Move,
Push,
Pop,
Ret,
MoveVec,
LoadVec,
StoreVec,
AluVec,
RetValVec,
RetVal2Vec,
ArgValVec,
ConvertVec,
ConvertToVec,
ConvertFromVec,
CmpSetVec,
CmpSetVecBoth,
}Expand description
The operand vector one machine instruction has.
A form rather than a list per opcode, because a hundred and fifty six opcodes have eleven answers between them and writing the eleven once is what makes a mistake in one of them a mistake a test can find.
Variants§
LoadImm
A destination and an immediate, which is mov r, imm.
AluRr
Two-address arithmetic on two registers: the destination is the first source, which the allocator is the one that has to arrange.
AluRi
Two-address arithmetic on a register and an immediate.
UnaryR
Two-address arithmetic on one register, which is negation and complement.
ShiftRi
A two-address shift by a constant.
ShiftCl
A two-address shift by a count, which this machine reads from cl and nowhere else.
CmpSet
A comparison and the byte it sets, which writes a destination unrelated to either source rather than destroying one of them.
Convert
A move between widths, which reads one register and writes another.
DivQuo
The quotient of a division, which comes back in rax and destroys rdx on the way.
DivRem
The remainder of a division, which comes back in rdx and destroys rax on the way.
Lea
An address computation, whose registers are in an addressing mode rather than in the operand vector, and which the builder puts there.
Load
A load: a destination register, and an addressing mode the value comes from.
Store
A store: an addressing mode the value goes to, and the register it comes out of. It writes no register at all, which makes it the first form here with no definition in it.
RetVal
The value a function gives back, in the register it is given back in.
It is not the ret instruction and it encodes to nothing. What the selector can do about
a return is put the value where the caller will look for it, and what it cannot do is
leave, because the epilogue has to give the frame back first and the epilogue is written
long after selection has finished. So this is the whole of the return that a lowering rule
gets to decide, and rucc_codegen::finish appends the rest to the same block.
The point of it surviving as an instruction rather than being nothing at all is the operand: a read constrained to the return register is how the allocator is told to get the value there, and it is what keeps the value alive that far.
RetVal2
The second register a value comes back in, when it takes two of them.
Form::RetVal one place further along the convention’s list of return registers. A
structure of at most sixteen bytes comes back in up to two registers, and which register
each half goes in is the classification’s answer, so a return of two values is built from
the convention the way a call is rather than matched by a rule. There is no third of these
because no convention this target has returns in three registers.
ArgVal
A value the caller already passed, in the register it arrived in.
The mirror of Form::RetVal and the same kind of thing: it encodes to nothing, and what
it is for is telling the allocator where a value already is. A function’s arguments are
there before its first instruction runs, so something has to define them, and a block
parameter cannot, because there is no edge into the entry block for a move to go on.
Which register is not written here, unlike the return, because the answer depends on the
argument’s position and on every argument before it. rucc_codegen::abi works that out
from the convention and puts it on the operand.
BrCond
The condition a block leaves on, in a register.
The third form here that encodes to nothing, and the smallest. Where the two arms go is on
the block rather than on the instruction, so this says nothing about either of them: it
reads the condition, which keeps the value alive to the end of the block and gets it into
a register. What turns it into a test and a jump is the block layout, which is the only
thing that knows which of the two arms falls through and therefore which way round the
jump goes. What takes the test back out again, where the condition came from a comparison
that already set the flags, is the peephole pass spec/10-backend.md section 10.9
describes, and it is a rule like any other rule.
An unconditional jump is not a form at all, because there is nothing left of one once the edge is on the block.
Test
A comparison of a register against itself, which is what asks whether it is zero.
The first instruction here that sets the flags and says nothing about them, which is the same arrangement every instruction here has: the flags are not an operand and the allocator never sees one. What makes that sound is that this and the jump that reads it are put in by the block layout, next to each other, after allocation has finished, so there is nothing left that could put an instruction between them.
Jcc
A jump taken when the flags say so, whose target is on the block.
Where it goes is the block’s first successor, for the reason every other arm is on the block: an instruction is twenty four bytes and a block reference would not fit in one, and the successors of a block are the thing every pass over the CFG already reads. The second successor is where the block goes when the jump is not taken, and after the layout has run that is always the block laid out next, which is why nothing is written for it.
Jmp
A jump always taken, whose target is on the block.
The one this becomes when the block it goes to is not the next block in the layout. A block that falls into the next one has no jump at all, which is what laying blocks out in a good order is worth.
Call
A call, whose operand vector is not a fact about the instruction.
Empty for a different reason than the jumps are. A jump has no operands because there is
nothing for it to read, and this has none because there is nothing true of
every call: how many values it passes, which registers they are in, whether anything comes
back and where, are all facts about the signature and the convention. So the operands of a
call are built where it is built, by rucc_codegen::abi, the same way an argument’s
register is.
What is the same about every call is the rest of it, and none of that is an operand either. The registers the convention does not preserve are gone across it, which is said with a definition per register that nothing reads, and that is what stops the allocator from leaving a value in one. The bytes below the stack pointer the arguments that did not fit in registers occupy are the frame’s, which is why the selector reports how many a function’s widest call needs rather than writing anything about them here.
A call through an address is the same form. The address is an operand and is a fact about
the instruction rather than about the signature, so it is the one operand of a call that
could have been written here, and it is not: an index into the operand vector is what a
row of this table names an operand by, and how many registers a call writes before it
reads anything is a different number for every call. What names it instead is
Arg::Through, which is the first operand read rather than
the operand at a place.
Move
A copy from one general purpose register to another.
The first form here no rule reaches. A copy is what the allocator writes when the two ends of a value could not be given the same register, and what a prologue writes when it puts the stack pointer in the frame pointer, and neither of those is a term a pattern could match. It is a whole register at a time whatever the value in it is worth, because a copy of half a register is a copy that has to know what the other half was for.
Push
A register put on the stack, which is how a prologue saves one the convention preserves.
Pop
A register taken off it, which is how the epilogue gives it back.
Ret
Leaving, which is the instruction a lowering rule cannot select for the reason
Form::RetVal gives: the frame has to be given back first and the frame is worked out
long after selection has finished.
MoveVec
A copy from one vector register to another.
The same thing as Form::Move and a separate form rather than the same one, because a
form is the class each of its operands is drawn from and these two are drawn from
different classes. That is also why there are three of these rather than one: a spill and
a reload of a vector register are a different instruction from a spill and a reload of a
general purpose one, and the allocator picks between them by asking the register file
which class the value is in.
LoadVec
A vector register read back from the stack.
StoreVec
A vector register written to it.
AluVec
Two-address arithmetic on two vector registers, which is every scalar floating point operation this machine has.
Form::AluRr in the other class and a separate form for the same reason the three moves
above are separate: a form is which class each of its operands comes from, and an allocator
handed the wrong one would put a float in a register that cannot hold one. The destination
reuses the first source here too, because addsd writes its answer over one of the two it
was given, exactly as addq does.
RetValVec
The value a function gives back, when it goes back in a vector register.
Form::RetVal in the other class. It encodes to nothing for the same reason and exists
for the same reason: a read constrained to the register the convention returns in is how
the allocator is told where the value has to end up.
RetVal2Vec
Form::RetVal2 in the other file.
ArgValVec
A value the caller already passed, when it arrived in a vector register.
Form::ArgVal in the other class, unconstrained here and constrained where it is built,
for the reason that one gives.
ConvertVec
A conversion from one float format to the other, which reads a vector register and writes one.
Form::Convert in the other class, and the reason there are three of these is the reason
there are two of that: a form is which file each of its operands is drawn from, and a
conversion is the one kind of instruction here whose answer is not the same for both of
them. What the destination is not is a reuse of the source, which every other vector
instruction here is: cvtss2sd writes a register it did not read.
ConvertToVec
A conversion that reads a general purpose register and writes a vector one, which is an integer becoming a float.
ConvertFromVec
A conversion that reads a vector register and writes a general purpose one, which is a float becoming an integer.
CmpSetVec
A comparison of two floats and the byte it sets, which reads two vector registers and writes a general purpose one.
Form::CmpSet with the two sources in the other file. The destination is in this one
because a truth value is a byte and a byte is not a thing the vector registers hold: what
ucomisd writes is the flags, and reading the flags is setcc and nothing else.
CmpSetVecBoth
The same, when the condition takes two of those bytes and a boolean operation to spell.
Two of the sixteen float comparisons are not one condition on this machine. ucomisd says
less, greater, equal or unordered in three flag bits, and every predicate but two is one of
those bits: equal on its own is the flag that means equal or unordered, so an ordered
equality is that flag and the one that says the operands were ordered, put together with an
and. Its negation is the other one, with an or.
So the instruction writes a second byte it then reads back, and that byte is written here
as a second definition, the way idiv writes down the register it destroys on the way. It
is a register the allocator picks and nothing else can be in it, because a definition that
is live where the first one is live is a definition that cannot share with it.
Implementations§
Source§impl Form
impl Form
Sourcepub fn operands(self) -> &'static [OperandDesc]
pub fn operands(self) -> &'static [OperandDesc]
The operands of an instruction of this form, the ones it writes before the ones it reads.
The registers an addressing mode names are not here. They are operands and the allocator
rewrites them like any other, and rucc_mir::InstBuilder::mem is what puts them in the
vector, because the addressing mode holds their positions and a caller that had to keep
those positions right by hand would eventually not.
Examples found in repository?
56fn main() {
57 let banks = [[RAX, RCX, RDX, RSI], [R8, R9, R10, R11]];
58 let immediates: [i64; 4] = [1, -1, 1000, 0x1_2345_6789];
59 let mut lines = Vec::new();
60
61 for &(opcode, form) in INSTS {
62 let operands = form.operands();
63 for inst in written(opcode).expect("every opcode in the table is written") {
64 if inst.args.iter().any(|arg| matches!(arg, Arg::Symbol | Arg::Label)) {
65 continue;
66 }
67 let has = |kind: fn(&Arg) -> bool| inst.args.iter().any(kind);
68 let mems = if has(|arg| matches!(arg, Arg::Mem)) {
69 addresses()
70 } else {
71 vec![(Addr::default(), String::new())]
72 };
73 let imms =
74 if has(|arg| matches!(arg, Arg::Imm)) { immediates.to_vec() } else { vec![0] };
75
76 for bank in banks {
77 for (addr, addr_text) in &mems {
78 for &imm in &imms {
79 let mut values = Vec::new();
80 let mut text = Vec::new();
81 let mut high = false;
82 for arg in inst.args {
83 match *arg {
84 Arg::Reg(at, width) => {
85 // An operand pinned to a register is that register and
86 // nothing else, which is what makes every shift count %cl.
87 let desc = operands[usize::from(at)];
88 let reg = match desc.constraint {
89 Constraint::Fixed(fixed) => fixed,
90 _ => bank[usize::from(at) % bank.len()],
91 };
92 values.push(Value::Reg(reg, width));
93 text.push(name(reg, width, desc.class));
94 }
95 // A vector register, which is a whole register and has no
96 // constraint on this machine: the one operand anything pins to a
97 // vector register is the value a function gives back, and that is
98 // written as nothing at all.
99 Arg::Xmm(at) => {
100 let desc = operands[usize::from(at)];
101 let reg = match desc.constraint {
102 Constraint::Fixed(fixed) => fixed,
103 _ => bank[usize::from(at) % bank.len()],
104 };
105 values.push(Value::Xmm(reg));
106 text.push(name(reg, Width::Quad, desc.class));
107 }
108 // A call names no operand in the table, so there is no constraint
109 // to read and any register at all is one it could go through.
110 Arg::Through => {
111 let reg = bank[0];
112 values.push(Value::Reg(reg, Width::Quad));
113 text.push(format!("*{}", name(reg, Width::Quad, GPR)));
114 }
115 Arg::Named(named) => {
116 high = true;
117 values.push(Value::High(RAX));
118 text.push(format!("%{named}"));
119 }
120 Arg::Imm => {
121 values.push(Value::Imm(imm));
122 text.push(format!("${imm}"));
123 }
124 Arg::Mem => {
125 values.push(Value::Mem(*addr));
126 text.push(addr_text.clone());
127 }
128 Arg::Symbol | Arg::Label => unreachable!("filtered above"),
129 }
130 }
131 // The high half of a register cannot share an instruction with one of the
132 // registers the machine gained later, so the second bank has nothing to
133 // say about an instruction naming it.
134 if high && bank[0] != RAX {
135 continue;
136 }
137 let mut bytes = Vec::new();
138 match encode(inst.mnemonic, &values, &mut bytes) {
139 Ok(_) => {}
140 Err(e) => {
141 eprintln!("{}: {e}", inst.mnemonic);
142 continue;
143 }
144 }
145 let hex: Vec<String> =
146 bytes.iter().map(|byte| format!("{byte:02x}")).collect();
147 let written = match text.is_empty() {
148 true => inst.mnemonic.to_owned(),
149 false => format!("{} {}", inst.mnemonic, text.join(", ")),
150 };
151 lines.push(format!("{}|{written}", hex.join(" ")));
152 }
153 }
154 }
155 }
156 }
157
158 println!("{}", lines.join("\n"));
159 eprintln!("{} instructions", lines.len());
160}