Introduction
A prefix is an instruction of its own, with its own opcode and (for some) an operand, that changes how the next instruction behaves. It does nothing on its own, so it
changes nothing about the stack. All six are two-byte opcodes starting with FE. More than one prefix may stand before one instruction, and a prefix must always be followed by an
instruction it may modify; a prefix followed by the end of the code, or by an instruction it cannot modify, is invalid. The explorer shows a prefix together with the instruction it modifies as
one unit, and the two are listed as separate instructions in the bytes.
Exceptions and verifiability, for the whole page. A prefix throws nothing itself. What the next instruction can throw is described with it.
The prefixes
| Opcode | Instruction | May precede | What it does | Exceptions · verifiability |
|---|---|---|---|---|
FE 12 u1 | unaligned. alignment | ldind, stind, ldfld, stfld, ldobj, stobj, initblk, cpblk | Says that the address the next instruction uses may not be aligned for the size of the data. The operand is the alignment that can be relied on: 1, 2 or 4 bytes. Without it the address must be naturally aligned. | Not verifiable on its own. |
FE 13 | volatile. | ldind, stind, ldfld, stfld, ldsfld, stsfld, ldobj, stobj, initblk, cpblk | Marks the next load or store as volatile: the runtime may not cache the value or move other memory accesses past it, so another thread sees it in order. A volatile read behaves as an acquire, a volatile write as a release. C# volatile fields are read and written this way, and the field itself carries a modreq(IsVolatile) modifier in its signature. | None. |
FE 14 | tail. | call, callvirt, calli | Asks the runtime to make the next call a tail call: the caller’s frame is removed before the callee starts, so the stack does not grow. The call must be followed immediately by ret, the stack must hold only the arguments, and the instruction cannot be inside a try block. F# uses it for tail recursion. | Verifiable when the return type of the callee fits the caller’s. |
FE 16 token | constrained. type | callvirt | Used by generic code. The token names the type of the this value, and the stack holds a managed pointer to it instead of an object. If the type is a reference type, the pointer is dereferenced and the call is an ordinary callvirt. If it is a value type that implements the method, that implementation is called directly with the pointer. If it is a value type that does not (the method comes from Object or ValueType), the value is boxed first. It lets one body of IL call a method on a type argument whether it is a class or a struct, without boxing a struct that has its own method. | Verifiable. |
FE 1E | readonly. | ldelema | The address that ldelema pushes is a controlled-mutability pointer, which the code does not write through, so the instruction does not check that the array’s element type is exactly the one named (no ArrayTypeMismatchException). C# ref readonly element access uses it. | Verifiable. |
FE 19 u1 | no. flags | instructions that check a type, a range or a null reference | The specification defines this prefix to say that the following instruction need not perform some of its checks; the operand byte selects which. The runtime library’s opcode table (System.Reflection.Emit.OpCodes) has no entry for it, and the explorer decodes it (opcode and operand) without interpreting the flags. | Not verifiable. |
The reserved single bytes F8 to FF (prefix7 to prefix1 and prefixref in the runtime library’s table) are described on the
other instructions page: they are not prefixes any runtime uses.
Not described
Known, not yet covered
- The flag byte of
no.: which checks each bit names is not decoded. - Memory model: what acquire and release mean for each processor, and for
unaligned.accesses, is outside these pages.
Unknown
- Whether any runtime acts on
no.; none was checked.
Links
Reference
- ECMA-335 – partition III, section 2: the prefix instructions
- System.Reflection.Emit.OpCodes – the runtime library’s table of opcodes (.NET)