Skip to main content

Form

Enum Form 

Source
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

Source

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?
examples/listing.rs (line 62)
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}
Source

pub fn takes_imm(self) -> bool

Whether an instruction of this form carries an immediate.

Source

pub fn takes_mem(self) -> bool

Whether an instruction of this form carries an addressing mode.

Trait Implementations§

Source§

impl Clone for Form

Source§

fn clone(&self) -> Form

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Form

Source§

impl Debug for Form

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Form

Source§

impl PartialEq for Form

Source§

fn eq(&self, other: &Form) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Form

Auto Trait Implementations§

§

impl Freeze for Form

§

impl RefUnwindSafe for Form

§

impl Send for Form

§

impl Sync for Form

§

impl Unpin for Form

§

impl UnsafeUnpin for Form

§

impl UnwindSafe for Form

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.