pub enum Opcode {
Show 108 variants
IConst,
FConst,
Splat,
GlobalAddr,
BlockAddr,
Add,
Sub,
Mul,
SDiv,
UDiv,
SRem,
URem,
And,
Or,
Xor,
Shl,
LShr,
AShr,
FAdd,
FSub,
FMul,
FDiv,
FRem,
FNeg,
Fma,
ICmp,
FCmp,
Select,
Trunc,
SExt,
ZExt,
FPTrunc,
FPExt,
FPToSI,
FPToUI,
SIToFP,
UIToFP,
PtrToInt,
IntToPtr,
Bitcast,
MemEntry,
Alloca,
Load,
Store,
PtrAdd,
Memcpy,
Memmove,
Memset,
AtomicLoad,
AtomicStore,
AtomicRmw,
Cmpxchg,
Fence,
CapOf,
CapLoad,
CapStore,
CapNull,
CapNarrow,
CapRecover,
CheckBounds,
CheckLive,
CheckType,
CheckInit,
CheckDeriv,
CheckRace,
MetaBegin,
MetaEnd,
MetaType,
MetaInit,
MetaTransfer,
SafeRegionBegin,
SafeRegionEnd,
Jump,
BrIf,
Switch,
IndirectBr,
Return,
Unreachable,
Call,
CallIndirect,
TailCall,
Ctlz,
Cttz,
Ctpop,
Bswap,
Bitreverse,
SAddOverflow,
UAddOverflow,
SSubOverflow,
USubOverflow,
SMulOverflow,
UMulOverflow,
Expect,
UnreachableHint,
Prefetch,
FrameAddress,
ReturnAddress,
VaStart,
VaArg,
VaObject,
VaEnd,
VaCopy,
StackSave,
StackRestore,
SetjmpMarker,
LongjmpMarker,
TargetIntrinsic,
InlineAsm,
}Expand description
One instruction of the IR.
The names are the textual form exactly, so Opcode::name and Opcode::from_name are
what the printer and the parser use, and neither carries a table of its own that could
drift from this one.
The enum is not non_exhaustive, deliberately. The set is closed, so a pass that matches
on every opcode should stop compiling when one is added rather than fall into a wildcard
arm that quietly does the wrong thing.
Variants§
IConst
An integer constant, iconst.i32 7.
FConst
A floating point constant, fconst.f64 0x1.8p+1.
Splat
A vector constant with every lane the same, splat.i8x16 0.
GlobalAddr
The address of a global or a function, global_addr @counter.
BlockAddr
The address of a block in this function, block_addr block3.
The one instruction that names a block without being a branch, which is what GNU’s
&&label is. Where it goes is Opcode::IndirectBr, and the two are only useful
together: an address on its own is a number that nothing can do anything with.
Add
Integer addition.
Sub
Integer subtraction.
Mul
Integer multiplication.
SDiv
Signed division.
UDiv
Unsigned division.
SRem
Signed remainder, with the sign of the dividend.
URem
Unsigned remainder.
And
Bitwise and.
Or
Bitwise or.
Xor
Bitwise exclusive or.
Shl
Shift left.
LShr
Logical shift right, shifting in zeroes.
AShr
Arithmetic shift right, shifting in the sign bit.
FAdd
Floating point addition.
FSub
Floating point subtraction.
FMul
Floating point multiplication.
FDiv
Floating point division.
FRem
Floating point remainder.
FNeg
Floating point negation, which flips the sign bit and is not 0 - x.
Fma
Fused multiply-add, rounded once.
ICmp
Integer comparison, producing i1 or a vector of i1.
FCmp
Floating point comparison, producing i1 or a vector of i1.
Select
One of two values, chosen by a bit. select c, a, b is a when c is one.
This is what control flow becomes when it stops being control flow.
spec/optimizer/22-phiopt-and-if-conversion.md section 22.2 makes it the lowering target
for a diamond whose two arms compute a value, and the reason it is an opcode rather than a
pattern is that it is the form the rule set is written against: select(c, a, a) -> a and
select(c, 1, 0) -> zext(c) are ordinary rules once the shape has a name.
Both arms are evaluated, which is the whole point and also the whole danger. Whatever produces one of these owes the argument that evaluating the arm that is not chosen is harmless, and section 22.6 is the list of ways that argument goes wrong.
Trunc
Narrows an integer, discarding the high bits.
SExt
Widens an integer, copying the sign bit.
ZExt
Widens an integer, filling with zeroes.
FPTrunc
Narrows a floating point value.
FPExt
Widens a floating point value.
FPToSI
Floating point to signed integer.
FPToUI
Floating point to unsigned integer.
SIToFP
Signed integer to floating point.
UIToFP
Unsigned integer to floating point.
PtrToInt
An address to an integer of the same width.
IntToPtr
An integer to an address.
Bitcast
A reinterpretation of the same bits at the same width.
MemEntry
Memory as the function found it, which is where a memory SSA chain starts.
It produces one mem and takes nothing, and it belongs at the top of the entry block.
GCC calls the same thing the default definition of .MEM and LLVM calls it
liveOnEntry. It exists as an instruction rather than as a parameter of the entry block
because the entry block’s parameters are the function’s parameters and the verifier
checks them against the signature, and memory is not an argument anybody passed.
Alloca
A stack slot. In the entry block, or marked dynamic for a variable length array.
Load
A read.
Store
A write, producing no value.
PtrAdd
Address arithmetic: an address and a byte offset.
Memcpy
A copy of a known size between addresses that do not overlap.
Memmove
A copy of a known size between addresses that may overlap.
Memset
A fill of a known size with one byte.
AtomicLoad
An atomic read.
AtomicStore
An atomic write.
AtomicRmw
An atomic read-modify-write, carrying which operation in RmwOp.
Cmpxchg
An atomic compare and exchange, producing the old value and whether it succeeded.
Fence
A memory barrier.
CapOf
The capability of a pointer value, taken from the pointer’s provenance.
CapLoad
The capability in the auxiliary slot beside a stored pointer, read back.
A pointer written to memory and read again has to bring its capability with it, and where the capability lives is document 05’s question rather than this one’s. What this says is that a capability comes back from an address, which is enough for every pass above.
CapStore
The other half of Opcode::CapLoad, writing one into the slot beside a pointer.
CapNull
The capability that permits nothing, which is what a null pointer has.
CapNarrow
A capability narrowed to a sub-object of what it covered.
Only under -fsafety-subobject. Narrowing is what catches an overflow from one member of
a struct into the next, and it is separate because C code that walks off the end of a
member on purpose exists and a project has to be able to say so.
CapRecover
The capability for an address that arrived from outside, recovered from the planes.
CheckBounds
An access is within its capability’s bounds, aligned, and permitted.
The size and the alignment are the access’s, and they are in the memory payload rather than in operands because they are what the front end knew and not what the program computed.
CheckLive
The capability’s provenance is still live.
CheckType
The access agrees with the type plane, which is the effective type rule of C 6.5.
CheckInit
The bytes the access reads have been written.
CheckDeriv
A pointer derived from another stays inside the capability the first one had.
Three operands, because the answer is about the new pointer and the question is about the old one’s capability.
CheckRace
The metadata this access is about to consult has not been changed under it.
MetaBegin
A storage instance begins here, over a range, with a class.
Judgement J4. This is the alloca for an automatic instance and the allocator’s report
for an allocated one, and the range is a pointer and a length in registers rather than a
payload, because the length of a variable length array is not known when the instruction
is written down.
MetaEnd
A storage instance ends here, which is judgement J5.
Every capability for it fails from this point on and keeps failing after the address is handed out again, which is what makes the check a use after free check rather than a use after reallocation one.
MetaType
The effective type of a range is now this one.
MetaInit
The bytes of a range are now initialized.
MetaTransfer
A range leaves the monitor’s authority, or comes back, which is judgement J7.
SafeRegionBegin
A declared exemption starts here, with the reason it was declared.
Not an optimization hint. Everything between this and its safe_region_end is code the
monitor is told not to judge, so the reason it carries is a trust set entry, and
spec/safe-memory/10-boundaries.md section 10.2 counts them per build precisely so that
a reviewer can read what a binary’s guarantee rests on.
SafeRegionEnd
The end of the region the last safe_region_begin opened.
Jump
An unconditional branch, jump block1(%a, %b).
BrIf
A two-way branch on an i1.
Switch
A multi-way branch on an integer, with a default.
IndirectBr
A branch to an address, indirect_br %0, block1, block2.
The targets are every block control can arrive at, which is what makes the edges of a
computed goto ordinary edges: nothing else in the compiler has to know that the
address decides which one it is. A target that is not listed is a branch that does not
happen, so a frontend that leaves one out has made a promise on the program’s behalf.
Return
A return, with the values the signature says.
Unreachable
A place control cannot reach, which the frontend emits after a noreturn call.
Call
A call to a named function.
CallIndirect
A call through an address, carrying the signature it is called with.
TailCall
A call in tail position that reuses the frame, which is a terminator.
Ctlz
Count leading zeroes.
Cttz
Count trailing zeroes.
Ctpop
Count set bits.
Bswap
Reverse the bytes.
Bitreverse
Reverse the bits.
SAddOverflow
Signed addition, producing the result and whether it overflowed.
UAddOverflow
Unsigned addition, producing the result and whether it overflowed.
SSubOverflow
Signed subtraction, producing the result and whether it overflowed.
USubOverflow
Unsigned subtraction, producing the result and whether it overflowed.
SMulOverflow
Signed multiplication, producing the result and whether it overflowed.
UMulOverflow
Unsigned multiplication, producing the result and whether it overflowed.
Expect
__builtin_expect, which is the value with a hint attached.
UnreachableHint
__builtin_unreachable as a hint on a path, distinct from the terminator.
Prefetch
__builtin_prefetch.
FrameAddress
__builtin_frame_address.
ReturnAddress
__builtin_return_address.
VaStart
The start of a variable argument list.
VaArg
One argument off a variable argument list, which moves the list on as it reads it. Two of these on one list are two arguments and never one argument read twice, so whatever decides which instructions may be folded together has to leave these alone.
VaObject
One argument off a variable argument list, when that argument is an object rather than a
value, which is what a struct or a union read out of one is.
It answers the address of the object rather than the object, because an aggregate is not
a value and there is nothing for one result to be. Where the object arrives in registers
there is no address until something makes one, so what this asks of a target is a place
to put the registers and the address of that place, which is the copy every psABI’s own
description of the algorithm makes. It moves the list on for the reason Opcode::VaArg
does.
VaEnd
The end of a variable argument list.
VaCopy
A copy of a variable argument list.
StackSave
The stack pointer, saved before a variable length array.
StackRestore
The stack pointer, restored after one.
SetjmpMarker
The marker a setjmp leaves, which pins everything live across it.
LongjmpMarker
The marker a longjmp leaves.
TargetIntrinsic
A target-specific intrinsic, named rather than enumerated, for the vector builtins.
InlineAsm
Inline assembly. A terminator when it has labels, which is asm goto.
Implementations§
Source§impl Opcode
impl Opcode
Sourcepub fn all() -> impl Iterator<Item = Self>
pub fn all() -> impl Iterator<Item = Self>
Every opcode, in the order they are declared.
The parser walks this rather than holding a second table, because a second table is a table that can disagree with the first one.
Sourcepub const fn is_terminator(self) -> bool
pub const fn is_terminator(self) -> bool
Whether this ends a block.
Opcode::InlineAsm is not here and is the one instruction whose answer depends on
the instruction rather than on the opcode: asm goto has successors and everything
else does not. Ask the instruction, not the opcode.
Sourcepub const fn is_commutative(self) -> bool
pub const fn is_commutative(self) -> bool
Whether the operands can be swapped without changing the result.
The floating point cases are commutative even under the strictest rounding, because swapping the operands of an addition does not change which of them is a NaN, and the sign of a NaN result is not something we promise anything about either way.
Sourcepub const fn has_effects(self) -> bool
pub const fn has_effects(self) -> bool
Whether this reads or writes memory, or has an effect the optimizer has to preserve.
An instruction that answers no can be deleted when nothing uses its result, moved across a call, and merged with another one computing the same thing. Everything else has to be argued about individually, so the conservative answer is the true one here and the list of exceptions is the part that is checked.
Sourcepub const fn touches_memory(self) -> bool
pub const fn touches_memory(self) -> bool
Whether an instruction with this opcode touches memory.
This is what decides whether it takes a memory operand once memory SSA is built, per
document 09 of spec/optimizer. It is written as the exceptions to touching memory
rather than as a list of what does, for the reason document 08.6 gives about the escape
analysis: an opcode added later has to end up on the conservative side by default, and a
list of what touches memory would silently leave a new one out.
mem_entry answers no. It produces memory rather than touching it, which is the whole
of what it is for.
Sourcepub const fn writes_memory(self) -> bool
pub const fn writes_memory(self) -> bool
Whether an instruction with this opcode writes memory, and so produces a new version of it rather than only reading the version it was given.
Everything that touches memory writes it except the ones that plainly do not. A fence
writes nothing and is still a write here, because document 09.5 says an atomic or a
barrier is a definition nothing walks past, and giving it one is how that is expressed
in a representation whose only ordering is the memory chain.
The checks read the planes and change nothing, which
spec/safe-memory/06-instrumentation.md section 6.2.4 states as the word readonly. A
check that trapped is a program that stopped and there is no version of memory after it
for anything to observe, so the trap costs nothing here. What it does cost is that a
check may not be moved across a plane write, and that is the memory chain saying so
rather than this.
Sourcepub const fn results(self) -> Option<u8>
pub const fn results(self) -> Option<u8>
How many values this produces, for the opcodes where the count is fixed.
None means the count comes from somewhere else: a call takes it from its signature,
and inline assembly takes it from its output constraints. A tail call is not one of
them, because whatever it returns goes straight out of the function and there is no
instruction after it to use anything.
Sourcepub const fn makes_capability(self) -> bool
pub const fn makes_capability(self) -> bool
Whether an instruction with this opcode produces a capability.
Five of the six cap instructions, cap_store being the one that consumes one instead.
The reason this is a question about the opcode rather than about the
result type is that the verifier asks it the other way round: it walks the results looking
for a cap and needs to know whether the instruction under it was entitled to make one.
Sourcepub const fn extra_kind(self) -> ExtraKind
pub const fn extra_kind(self) -> ExtraKind
Which payload an instruction with this opcode carries.
The printer reads the payload it finds and does not need this. The parser has only the
opcode when it reaches the operands, so this is where the two of them agree on what
comes after them. An instruction carrying a payload of some other kind prints as text
the parser cannot read back, which is why the verifier checks it against
Extra::kind rather than leaving it to be found later.