Introduction
Arithmetic instructions pop their operands from the evaluation stack and push the result. Each instruction is the same for every operand type; the type decides what it does.
The operands of a binary instruction must be of the same kind (int32 with int32, int64 with int64, floating point with floating point),
except that int32 and native int mix freely and give a native int, and a managed pointer plus or minus an integer is a pointer. The result is of
the operand kind; there is no 8-bit or 16-bit arithmetic on the stack (smaller values were widened to int32 when loaded).
Exceptions and verifiability, for the whole page. Integer add, sub and mul wrap around on overflow and throw nothing; the
.ovf forms throw OverflowException. Integer div and rem throw DivideByZeroException when the divisor is 0, and may throw
ArithmeticException (an OverflowException is one) when the smallest integer is divided by −1; floating-point division by zero gives an infinity or NaN and throws
nothing. Mixed or mismatched operand kinds are not verifiable. Pointer arithmetic on native int or unmanaged pointers is not verifiable.
Arithmetic, logic and shifts
| Opcode | Instruction | Stack | What it does | Exceptions · verifiability |
|---|---|---|---|---|
58 | add | …, a, b → …, a + b | Adds two numbers. Integers wrap; floats follow IEEE 754. | None. |
59 | sub | …, a, b → …, a − b | Subtracts the second from the first. | None. |
5A | mul | …, a, b → …, a × b | Multiplies, keeping the low bits for integers. | None. |
5B | div | …, a, b → …, a ÷ b | Signed division; integers are truncated toward zero. | DivideByZeroException for integer b = 0; ArithmeticException for min ÷ −1. |
5C | div.un | …, a, b → …, a ÷ b | Division of the operands as unsigned integers. Not defined for floats. | DivideByZeroException for b = 0. |
5D | rem | …, a, b → …, a mod b | Remainder, with the sign of a. For floats, the remainder of the division (as the C function fmod). | DivideByZeroException for integer b = 0; ArithmeticException for min rem −1. |
5E | rem.un | …, a, b → …, a mod b | Remainder of unsigned integers. | DivideByZeroException for b = 0. |
65 | neg | …, a → …, −a | Negates (two’s complement for integers; the smallest integer stays itself). | None. |
5F | and | …, a, b → …, a & b | Bitwise AND. Integers only. | None. Not verifiable on floats. |
60 | or | …, a, b → …, a | b | Bitwise OR. Integers only. | None. |
61 | xor | …, a, b → …, a ^ b | Bitwise exclusive OR. Integers only. | None. |
66 | not | …, a → …, ~a | Bitwise complement. Integers only. | None. |
62 | shl | …, a, count → …, a << count | Shifts left, filling with zeros. The count is an int32 or native int; the result has the kind of a. | None. The result is unspecified if the count is negative or not less than the width of a. |
63 | shr | …, a, count → …, a >> count | Shifts right, copying the sign bit in (arithmetic shift). | As for shl. |
64 | shr.un | …, a, count → …, a >>> count | Shifts right, filling with zeros (logical shift). | As for shl. |
Overflow-checked arithmetic
These work on integers only and throw instead of wrapping. The .un forms treat both operands as unsigned. C# emits them inside checked contexts.
| Opcode | Instruction | Stack | What it does | Exceptions · verifiability |
|---|---|---|---|---|
D6 | add.ovf | …, a, b → …, a + b | Signed addition that throws if the result does not fit. | OverflowException. |
D7 | add.ovf.un | …, a, b → …, a + b | Unsigned addition that throws on overflow. | OverflowException. |
DA | sub.ovf | …, a, b → …, a − b | Signed subtraction that throws on overflow. | OverflowException. |
DB | sub.ovf.un | …, a, b → …, a − b | Unsigned subtraction that throws on overflow (including a result below 0). | OverflowException. |
D8 | mul.ovf | …, a, b → …, a × b | Signed multiplication that throws on overflow. | OverflowException. |
D9 | mul.ovf.un | …, a, b → …, a × b | Unsigned multiplication that throws on overflow. | OverflowException. |
Comparison
These compare two values and push 1 (true) or 0 (false) as an int32; a conditional branch does the same and jumps. For floats, any comparison with a NaN
is false, except that the .un forms (“unordered”) are true when either operand is a NaN. For integers the .un forms compare as unsigned. Object references can be
compared with ceq; cgt.un against null is how code tests that a reference is not null.
| Opcode | Instruction | Stack | What it does | Exceptions · verifiability |
|---|---|---|---|---|
FE 01 | ceq | …, a, b → …, int32 | 1 if a equals b, otherwise 0. | None. |
FE 02 | cgt | …, a, b → …, int32 | 1 if a is greater than b (signed or floating point), otherwise 0. | None. |
FE 03 | cgt.un | …, a, b → …, int32 | 1 if a is greater than b as unsigned integers, or if a float comparison is unordered; also for object references. | None. |
FE 04 | clt | …, a, b → …, int32 | 1 if a is less than b (signed or floating point), otherwise 0. | None. |
FE 05 | clt.un | …, a, b → …, int32 | 1 if a is less than b as unsigned integers, or if a float comparison is unordered. | None. |
C3 | ckfinite | …, F → …, F | Leaves the float on the stack unchanged, if it is a finite number. | ArithmeticException if it is NaN or an infinity. |
Conversion
A conv instruction pops a number and pushes it converted to the named type: i1, i2, i4, i8 are signed integers of 8 to 64 bits, u1 to
u8 unsigned, i and u native-sized, r4 and r8 floating point. The result of a conversion to an integer of 32 bits or fewer is an
int32 on the stack; i8 and u8 give an int64; i and u give a native int. Converting an integer to a narrower
one keeps the low bits. Converting a float to an integer truncates toward zero, and the result is unspecified if the value is out of range. The .ovf forms throw
OverflowException instead of truncating or leaving the result unspecified; the .un forms take the input as unsigned.
| Opcode | Instruction | Stack | What it does | Exceptions · verifiability |
|---|---|---|---|---|
67 | conv.i1 | …, a → …, int32 | Truncates to a signed 8-bit value, then sign-extends it. | None. |
68 | conv.i2 | …, a → …, int32 | Truncates to a signed 16-bit value, then sign-extends it. | None. |
69 | conv.i4 | …, a → …, int32 | Converts to a signed 32-bit integer. | None. |
6A | conv.i8 | …, a → …, int64 | Converts to a signed 64-bit integer; an int32 is sign-extended. | None. |
6B | conv.r4 | …, a → …, F | Converts to single precision. | None. |
6C | conv.r8 | …, a → …, F | Converts to double precision. | None. |
6D | conv.u4 | …, a → …, int32 | Converts to an unsigned 32-bit integer (kept as an int32 with the same bits). | None. |
6E | conv.u8 | …, a → …, int64 | Converts to an unsigned 64-bit integer; an int32 is zero-extended. | None. |
D1 | conv.u2 | …, a → …, int32 | Truncates to an unsigned 16-bit value, then zero-extends it. | None. |
D2 | conv.u1 | …, a → …, int32 | Truncates to an unsigned 8-bit value, then zero-extends it. | None. |
D3 | conv.i | …, a → …, native int | Converts to a native-sized signed integer. | None. |
E0 | conv.u | …, a → …, native int | Converts to a native-sized unsigned integer. Also how a managed pointer becomes an unmanaged one. | None. Not verifiable on a pointer. |
76 | conv.r.un | …, a → …, F | Treats an integer as unsigned and converts it to floating point. | None. |
B3 | conv.ovf.i1 | …, a → …, int32 | Converts a signed value to int8. | OverflowException if it does not fit. |
B4 | conv.ovf.u1 | …, a → …, int32 | Converts a signed value to uint8. | OverflowException if it does not fit. |
B5 | conv.ovf.i2 | …, a → …, int32 | Converts a signed value to int16. | OverflowException if it does not fit. |
B6 | conv.ovf.u2 | …, a → …, int32 | Converts a signed value to uint16. | OverflowException if it does not fit. |
B7 | conv.ovf.i4 | …, a → …, int32 | Converts a signed value to int32. | OverflowException if it does not fit. |
B8 | conv.ovf.u4 | …, a → …, int32 | Converts a signed value to uint32. | OverflowException if it does not fit. |
B9 | conv.ovf.i8 | …, a → …, int64 | Converts a signed value to int64. | OverflowException if it does not fit. |
BA | conv.ovf.u8 | …, a → …, int64 | Converts a signed value to uint64. | OverflowException if it does not fit. |
D4 | conv.ovf.i | …, a → …, native int | Converts a signed value to a native-sized signed integer. | OverflowException if it does not fit. |
D5 | conv.ovf.u | …, a → …, native int | Converts a signed value to a native-sized unsigned integer. | OverflowException if it does not fit. |
82 | conv.ovf.i1.un | …, a → …, int32 | Converts an unsigned value to int8. | OverflowException if it does not fit. |
83 | conv.ovf.i2.un | …, a → …, int32 | Converts an unsigned value to int16. | OverflowException if it does not fit. |
84 | conv.ovf.i4.un | …, a → …, int32 | Converts an unsigned value to int32. | OverflowException if it does not fit. |
85 | conv.ovf.i8.un | …, a → …, int64 | Converts an unsigned value to int64. | OverflowException if it does not fit. |
86 | conv.ovf.u1.un | …, a → …, int32 | Converts an unsigned value to uint8. | OverflowException if it does not fit. |
87 | conv.ovf.u2.un | …, a → …, int32 | Converts an unsigned value to uint16. | OverflowException if it does not fit. |
88 | conv.ovf.u4.un | …, a → …, int32 | Converts an unsigned value to uint32. | OverflowException if it does not fit. |
89 | conv.ovf.u8.un | …, a → …, int64 | Converts an unsigned value to uint64. | OverflowException if it does not fit. |
8A | conv.ovf.i.un | …, a → …, native int | Converts an unsigned value to a native-sized signed integer. | OverflowException if it does not fit. |
8B | conv.ovf.u.un | …, a → …, native int | Converts an unsigned value to a native-sized unsigned integer. | OverflowException if it does not fit. |
Not described
Known, not yet covered
- Floating-point precision: the specification lets the stack hold floats at a precision above double; how much each runtime keeps, and when it narrows, is not described.
Unknown
- Which exception each runtime throws for the smallest integer divided by −1 (the specification allows
ArithmeticException); this page gives the specification’s wording.
Links
Reference
- ECMA-335 – partition III, section 3: the base instructions, and partition I, section 12.1 on the stack
- System.Reflection.Emit.OpCodes – the runtime library’s table of opcodes (.NET)