Common Language Infrastructure (CLI )
Partition III
CIL Instruction Set
The participants in ECMA TC39/TG3 are providing these working documents to the public for informational purposes only. The contents are subject to change as often as once a month. To participate in the standardization process, contact your company's ECMA representative. If your company does not currently participate in ECMA and wishes to do so, please contact ECMA (http://www.ecma.ch/) directly.
The following have participated in the work of ECMA TC39/TG3 and their contributions are gratefully acknowledged:
Fujitsu Software, Hewlett-Packard, Intel Corporation, International Business Machines, ISE, Microsoft Corporation, Monash University, Netscape, Phone.Com, Plum Hall, Sun Microsystems
Table of Contents
1.6 Implicit Argument Coercion
1.7 Restrictions on CIL Code Sequences
1.7.5 Backward Branch Constraints
1.7.6 Branch Verification Constraints
1.8.1 Flow Control Restrictions for Verifiable CIL
2.1 tail. (prefix) call terminates current method
2.2 unaligned. (prefix) pointer instruction may be unaligned
2.3 volatile. (prefix) - pointer reference is volatile
3.2 add.ovf.<signed> - add integer values with overflow check
3.4 arglist - get argument list
3.5 beq.<length> branch on equal
3.6 bge.<length> branch on greater than or equal to
3.7 bge.un.<length> branch on greater than or equal to, unsigned or unordered
3.8 bgt.<length> branch on greater than
3.9 bgt.un.<length> branch on greater than, unsigned or unordered
3.10 ble.<length> branch on less than or equal to
3.11 ble.un.<length> branch on less than or equal to, unsigned or unordered
3.12 blt.<length> branch on less than
3.13 blt.un.<length> branch on less than, unsigned or unordered
3.14 bne.un<length> branch on not equal or unordered
3.15 br.<length> unconditional branch
3.16 break breakpoint instruction
3.17 brfalse.<length> - branch on false, null, or zero
3.18 brtrue.<length> - branch on non-false or non-null
3.20 calli indirect method call
3.22 cgt - compare greater than
3.23 cgt.un - compare greater than, unsigned or unordered
3.24 ckfinite check for a finite real number
3.26 clt.un - compare less than, unsigned or unordered
3.27 conv.<to type> - data conversion
3.28 conv.ovf.<to type> - data conversion with overflow detection
3.29 conv.ovf.<to type>.un unsigned data conversion with overflow detection
3.30 cpblk - copy data from memory to memory
3.32 div.un - divide integer values, unsigned
3.33 dup duplicate the top value of the stack
3.34 endfilter end filter clause of SEH
3.35 endfinally end the finally or fault clause of an exception block
3.36 initblk - initialize a block of memory to a value
3.38 ldarg.<length> - load argument onto the stack
3.39 ldarga.<length> - load an argument address
3.40 ldc.<type> - load numeric constant
3.41 ldftn - load method pointer
3.42 ldind.<type> - load value indirect onto the stack
3.43 ldloc - load local variable onto the stack
3.44 ldloca.<length> - load local variable address
3.45 ldnull load a null pointer
3.46 leave.<length> exit a protected region of code
3.47 localloc allocate space in the local dynamic memory pool
3.49 mul.ovf.<type> - multiply integer values with overflow check
3.54 pop remove the top element of the stack
3.56 rem.un - compute integer remainder, unsigned
3.59 shr - shift integer right
3.60 shr.un - shift integer right, unsigned
3.61 starg.<length> - store a value in an argument slot
3.62 stind.<type> - store value indirect from stack
3.63 stloc - pop value from stack to local variable
3.64 sub - subtract numeric values
3.65 sub.ovf.<type> - subtract integer values, checking for overflow
3.66 switch table switch on value
4.1 box convert value type to object reference
4.2 callvirt call a method associated, at runtime, with an object
4.3 castclass cast an object to a class
4.5 initobj - initialize a value type
4.6 isinst test if an object is an instance of a class or interface
4.7 ldelem.<type> load an element of an array
4.8 ldelema load address of an element of an array
4.9 ldfld load field of an object
4.10 ldflda load field address
4.11 ldlen load the length of an array
4.12 ldobj - copy value type to the stack
4.13 ldsfld load static field of a class
4.14 ldsflda load static field address
4.15 ldstr load a literal string
4.16 ldtoken - load the runtime representation of a metadata token
4.17 ldvirtftn - load a virtual method pointer
4.18 mkrefany push a typed reference on the stack
4.19 newarr create a zero-based, one-dimensional array
4.20 newobj create a new object
4.21 refanytype load the type out of a typed reference
4.22 refanyval load the address out of a typed reference
4.23 rethrow rethrow the current exception
4.24 sizeof load the size in bytes of a value type
4.25 stelem.<type> store an element of an array
4.26 stfld store into a field of an object
4.27 stobj - store a value type from the stack into memory
4.28 stsfld store a static field of a class
4.29 throw throw an exception
4.30 unbox Convert boxed value type to its raw form
This specification is a detailed description of the Common Intermediate Language (CIL) instruction set, part of the specification of the Common Language Infrastructure. Partition I_alink_partitionI describes the architecture of the CLI and provides an overview of a large number of issues relating to the CIL instruction set. That overview is essential to an understanding of the instruction set as described here.
Each instruction description describes a set of related CLI machine instructions. Each instruction definition consists of five parts:
· A table describing the binary format, assembly language notation and description of each variant of the instruction. See the Instruction Variant Table section.
· A stack transition diagram that describes the state of the evaluation stack before and after the instruction is executed. See Section 1.3_1.3_StackTransitionDiagram.
· An English description of the instruction. See the English Description section.
· A list of exceptions that might be thrown by the instruction. See Partition I_alink_PartitionI for details. There are three exceptions which may be thrown by any instruction and are not listed with the instruction:
ExecutionEngineException indicates that the internal state of the Execution Engine is corrupted and execution cannot continue. [Note: in a system that executes only verifiable code this exception is not thrown.]
StackOverflowException indicates that the hardware stack size has been exceeded. The precise timing of this exception and the conditions under which it occurs are implementation specific. [Note: this exception is unrelated to the maximum stack size described in clause 1.7.4_1.7.4_MustProvideMaxstack. That size relates to the depth of the evaluation stack that is part of the method state described in Partition I_alink_partitionI, while this exception has to do with the implementation of that method state on physical hardware.]
OutOfMemoryException indicates that the available memory space has been exhausted, either because the instruction inherently allocates memory (newobj, newarr) or for an implementation-specific reason (for example, an implementation based on just-in-time compilation to native code may run out of space to store the translated method while executing the first call or callvirt to a given method).
· A section describing the verifiability conditions associated with the instruction. See Section 1.8_1.8_Verifiability.
In addition, operations that have a numeric operand also specify an operand type table that describes how they operate based on the type of the operand. See Section 1.5_1.5_OperandTypeTable.
Note that not all instructions are included in all CLI Profiles. See Partition IV_alink_partitionIV for details.
While the Common Type System (CTS) defines a rich type system and the Common Language Specification (CLS) specifies a subset that can be used for language interoperability, the CLI itself deals with a much simpler set of types. These types, collectively known as the basic CLI types, are:
· A subset of the full numeric types (int32, int64, native int, and F)
· Object references (O) without distinction between the type of object referenced
· Pointer types (native unsigned int and &) without distinction as to the type pointed to
Note that object references and pointer types may be assigned the value null. This is defined throughout the CLI to be zero (a bit pattern of all bits zero)
· The CLI only operates on the numeric types int32 (4 byte signed integers), int64 (8 byte signed integers), native int (native size integers), and F (native size floating-point numbers). The CIL instruction set, however, allows additional data types to be implemented:
· Short integers. The evaluation stack only holds 4 or 8 byte integers, but other locations (arguments, local variables, statics, array elements, fields) may hold 1 or 2 byte integers. Loading from these locations onto the stack either zero-extends (ldind.u*, ldelem.u*, etc.) or sign-extends (ldind.i*, ldelem.i*, etc.) to a 4 byte value. Storing to integers (stind.u1, stelem.i2, etc.) truncates. Use the conv.ovf.* instructions to detect when this truncation results in a value that doesnt correctly represent the original value.
Note: Short integers are loaded as 4-byte numbers on all architectures and these 4-byte numbers must always be tracked as distinct from 8-byte numbers. This helps portability of code by ensuring that the default arithmetic behavior (i.e when no conv or conv.ovf instruction are executed) will have identical results on all implementations.
Convert instructions that yield short integer values actually leave an int32 (32-bit) value on the stack, but it is guaranteed that only the low bits have meaning (i.e. the more significant bits are all zero for the unsigned conversions or a sign extension for the signed conversions). To correctly simulate the full set of short integer operations a conversion to the short form is required before the div, rem, shr, comparison and conditional branch instructions.
In addition to the explicit conversion instructions there are four cases where the CLI handles short integers in a special way:
1. Assignment to a local (stloc) or argument (starg) whose type is declared to be a short integer type automatically truncates to the size specified for the local or argument.
2. Loading from a local (ldloc) or argument (ldarg) whose type is declared to be a short signed integer type automatically sign extends.
3. Calling a procedure with an argument that is a short integer type is equivalent to assignment to the argument value, so it truncates.
4. Returning a value from a method whose return type is a short integer is modeled as storing into a short integer within the called procedure (i.e. the CLI automatically truncates) and then loading from a short integer within the calling procedure (i.e. the CLI automatically zero- or sign-extends).
In the last two cases it is up to the native calling convention to determine whether values are actually truncated or extended, as well as whether this is done in the called procedure or the calling procedure. The CIL instruction sequence is unaffected and it is as though the CIL sequence included an appropriate conv instruction.
· 4 byte integers. The shortest value actually stored on the stack is a 4-byte integer. These can be converted to 8-byte integers or native-size integers using conv.* instructions. Native-size integers can be converted to 4-byte integers, but doing so is not portable across architectures. The conv.i4 and conv.u4 can be used for this conversion if the excess significant bits should be ignored; the conv.ovf.i4 and conv.ovf.u4 instructions can be used to detect the loss of information. Arithmetic operations allow 4-byte integers to be combined with native size integers, resulting in native size integers. 4-byte integers may not be directly combined with 8-byte integers (they must be converted to 8-byte integers first).
· Native size integers. Native size integers can be combined with 4-byte integers using any of the normal arithmetic instructions, and the result will be a native-size integer. Native size integers must be explicitly converted to 8-byte integers before they can be combined with 8-byte integers.
· 8 byte integers. Supporting 8 byte integers on 32 bit hardware may be expensive, whereas 32 bit arithmetic is available and efficient on current 64 bit hardware. For this reason, numeric instructions allow int32 and I data types to be intermixed (yielding the largest type used as input), but these types cannot be combined with int64s. Instead, an native int or int32 must be explicitly converted to int64 before it can be combined with an int64.
· Unsigned integers. Special instructions are used to interpret integers on the stack as though they were unsigned, rather than tagging the stack locations as being unsigned.
· Floating-point numbers. See also Partition I, Handling of Floating Point Datatypes_alink_PartitionI#FloatingPointDatatypes. Storage locations for floating-point numbers (statics, array elements, and fields of classes) are of fixed size. The supported storage sizes are float32 and float64. Everywhere else (on the evaluation stack, as arguments, as return types, and as local variables) floating-point numbers are represented using an internal floating-point type. In each such instance, the nominal type of the variable or expression is either float32 or float64, but its value may be represented internally with additional range and/or precision. The size of the internal floating-point representation is implementation-dependent, may vary, and shall have precision at least as great as that of the variable or expression being represented. An implicit widening conversion to the internal representation from float32 or float64 is performed when those types are loaded from storage. The internal representation is typically the natural size for the hardware, or as required for efficient implementation of an operation. The internal representation shall have the following characteristics:
o The internal representation shall have precision and range greater than or equal to the nominal type.
o Conversions to and from the internal representation shall preserve value. [Note: This implies that an implicit widening conversion from float32 (or float64) to the internal representation, followed by an explicit conversion from the internal representation to float32 (or float64), will result in a value that is identical to the original float32 (or float64) value.]
Note: The above specification allows a compliant implementation to avoid rounding to the precision of the target type on intermediate computations, and thus permits the use of wider precision hardware registers, as well as the application of optimizing transformations which result in the same or greater precision, such as contractions. Where exactly reproducible behavior is required by a language or application, explicit conversions may be used.
When a floating-point value whose internal representation has greater range and/or precision than its nominal type is put in a storage location, it is automatically coerced to the type of the storage location. This may involve a loss of precision or the creation of an out-of-range value (NaN, +infinity, or ‑infinity). However, the value may be retained in the internal representation for future use, if it is reloaded from the storage location without having been modified. It is the responsibility of the compiler to ensure that the memory location is still valid at the time of a subsequent load, taking into account the effects of aliasing and other execution threads (see memory model section). This freedom to carry extra precision is not permitted, however, following the execution of an explicit conversion (conv.r4 or conv.r8), at which time the internal representation must be exactly representable in the associated type.
Note: To detect values that cannot be converted to a particular storage type, use a conversion instruction (conv.r4, or conv.r8) and then check for an out-of-range value using ckfinite. To detect underflow when converting to a particular storage type, a comparison to zero is required before and after the conversion.
Note: This standard does not specify the behavior of arithmetic operations on denormalized floating point numbers, nor does it specify when or whether such representations should be created. This is in keeping with IEC 60559:1989. In addition, this standard does not specify how to access the exact bit pattern of NaNs that are created, nor the behavior when converting a NaN between 32-bit and 64-bit representation. All of this behavior is deliberately left implementation-specific.
A CLI Boolean type occupies one byte in memory. A bit pattern of all zeroes denotes a value of false. A bit pattern with any bit set (analogous to a non-zero integer) denotes a value of true.
Object references (type O) are completely opaque. There are no arithmetic instructions that allow object references as operands, and the only comparison operations permitted are equality (and inequality) between two object references. There are no conversion operations defined on object references. Object references are created by certain CIL object instructions (notably newobj and newarr). Object references can be passed as arguments, stored as local variables, returned as values, and stored in arrays and as fields of objects.
There are two kinds of pointers: unmanaged pointers and managed pointers. For pointers into the same array or object (see Partition I_alink_partitionI), the following arithmetic operations are defined:
· Adding an integer to a pointer, where the integer is interpreted as a number of bytes, results in a pointer of the same kind.
· Subtracting an integer (number of bytes) from a pointer results in a pointer of the same kind. Note that subtracting a pointer from an integer is not permitted.
· Two pointers, regardless of kind, can be subtracted from one another, producing an integer that specifies the number of bytes between the addresses they reference.
None of these operations is allowed in verifiable code.
It is important to understand the impact on the garbage collector of using arithmetic on the different kinds of pointers. Since unmanaged pointers must never reference memory that is controlled by the garbage collector, performing arithmetic on them can endanger the memory safety of the system (hence it is not verifiable) but since they are not reported to the garbage collector there is no impact on its operation.
Managed pointers, however, are reported to the garbage collector. As part of garbage collection both the contents of the location to which they point and the pointer itself can be modified. The garbage collector will ignore managed pointers if they point into memory that is not under its control (the evaluation stack, the call stack, static memory, or memory under the control of another allocator). If, however, a managed pointer refers to memory controlled by the garbage collector it must point to either a field of an object, an element of an array, or the address of the element just past the end of an array. If address arithmetic is used to create a managed pointer that refers to any other location (an object header or a gap in the allocated memory) the garbage collectors operation is unspecified.
Unmanaged pointers are the traditional pointers used in languages like C and C++. There are no restrictions on their use, although for the most part they result in code that cannot be verified. While it is perfectly legal to mark locations that contain unmanaged pointers as though they were unsigned integers (and this is, in fact, how they are treated by the CLI), it is often better to mark them as unmanaged pointers to a specific type of data. This is done by using ELEMENT_TYPE_PTR in a signature for a return value, local variable or an argument or by using a pointer type for a field or array element.
Unmanaged pointers are not reported to the garbage collector and can be used in any way that an integer can be used.
· Unmanaged pointers should be treated as unsigned (i.e. use conv.ovf.u rather than conv.ovf.i, etc.).
· Verifiable code cannot use unmanaged pointers to reference memory.
· Unverified code can pass an unmanaged pointer to a method that expects a managed pointer. This is safe only if one of the following is true:
a. The unmanaged pointer refers to memory that is not in memory managed by the garbage collector
b. The unmanaged pointer refers to a field within an object
c. The unmanaged pointer refers to an element within an array
d. The unmanaged pointer refers to the location where the element following the last element in an array would be located
Managed pointers (&) may point to a local variable, a method argument, a field of an object, a field of a value type, an element of an array, or the address where an element just past the end of an array would be stored (for pointer indexes into managed arrays). Managed pointers cannot be null. (They must be reported to the garbage collector, even if they do not point to managed memory)
Managed pointers are specified by using ELEMENT_TYPE_BYREF in a signature for a return value, local variable or an argument or by using a by-ref type for a field or array element.
· Managed pointers can be passed as arguments and stored in local variables.
· If you pass a parameter by reference, the corresponding argument is a managed pointer.
· Managed pointers cannot be stored in static variables, array elements, or fields of objects or value types.
· Managed pointers are not interchangeable with object references.
· A managed pointer cannot point to another managed pointer, but it can point to an object reference or a value type.
· Managed pointers that do not point to managed memory can be converted (using conv.u or conv.ovf.u) into unmanaged pointers, but this is not verifiable.
· Unverified code that erroneously converts a managed pointer into an unmanaged pointer can seriously compromise the integrity of the CLI. This conversion is safe if any of the following is known to be true:
a. the managed pointer does not point into the garbage collectors memory area
b. the memory referred to has been pinned for the entire time that the unmanaged pointer is in use
c. a garbage collection cannot occur while the unmanaged pointer is in use
d. the garbage collector for the given implementation of the CLI is known to not move the referenced memory
In Chapter 3_3_BaseInstructions an Instruction Variant Table is presented for each instruction. It describes each variant of the instructions. The Format column of the table lists the opcode for the instruction variant, along with any arguments that follow the instruction in the instruction stream. For example:
|
Format |
Assembly Format |
Description |
|
FE 0A <unsigned int16> |
Ldarga argNum |
fetch the address of argument argNum. |
|
0F <unsigned int8> |
Ldarga.s argNum |
fetch the address of argument argNum, short form |
The first one or two hex numbers in the Format column show how this instruction is encoded (its opcode). So, the ldarga instruction is encoded as a byte holding FE, followed by another holding 0A. Italicized type names represent numbers that should follow in the instruction stream. In this example a 2-byte quantity that is to be treated as an unsigned integer directly follows the FE 0A opcode.
Any of the fixed size built-in types (int8, unsigned int8, int16, unsigned int16, int32, unsigned int32, int64, unsigned in64, float32, and float64) can appear in format descriptions. These types define the number of bytes for the argument and how it should be interpreted (signed, unsigned or floating-point). In addition, a metadata token can appear, indicated as <T>. Tokens are encoded as 4-byte integers. All argument numbers are encoded least-significant-byte-at-smallest-address (a pattern commonly termed little-endian). Bytes for instruction opcodes and arguments are packed as tightly as possible (no alignment padding is done).
The assembly format column defines an assembly code mnemonic for each instruction variant. For those instructions that have instruction stream arguments, this column also assigns names to each of the arguments to the instruction. For each instruction argument, there is a name in the assembly format. These names are used later in the instruction description.
CIL opcodes are one or more bytes long; they may be followed by zero or more operand bytes. All opcodes whose first byte lies in the ranges 0x00 through 0xEF, or 0xFC through 0xFF are reserved for standardization. Opcodes whose first byte lies in the range 0xF0 through 0xFB inclusive, are available for experimental purposes. The use of experimental opcodes in any method renders the method invalid and hence unverifiable.
The currently defined encodings are specified in Table 1: Opcode Encodings_Table1_OpcodeEncodings.
Table 1: Opcode Encodings
|
0x00 |
nop |
|
0x01 |
break |
|
0x02 |
ldarg.0 |
|
0x03 |
ldarg.1 |
|
0x04 |
ldarg.2 |
|
0x05 |
ldarg.3 |
|
0x06 |
ldloc.0 |
|
0x07 |
ldloc.1 |
|
0x08 |
ldloc.2 |
|
0x09 |
ldloc.3 |
|
0x0a |
stloc.0 |
|
0x0b |
stloc.1 |
|
0x0c |
stloc.2 |
|
0x0d |
stloc.3 |
|
0x0e |
ldarg.s |
|
0x0f |
ldarga.s |
|
0x10 |
starg.s |
|
0x11 |
ldloc.s |
|
0x12 |
ldloca.s |
|
0x13 |
stloc.s |
|
0x14 |
ldnull |
|
0x15 |
ldc.i4.m1 |
|
0x16 |
ldc.i4.0 |
|
0x17 |
ldc.i4.1 |
|
0x18 |
ldc.i4.2 |
|
0x19 |
ldc.i4.3 |
|
0x1a |
ldc.i4.4 |
|
0x1b |
ldc.i4.5 |
|
0x1c |
ldc.i4.6 |
|
0x1d |
ldc.i4.7 |
|
0x1e |
ldc.i4.8 |
|
0x1f |
ldc.i4.s |
|
0x20 |
ldc.i4 |
|
0x21 |
ldc.i8 |
|
0x22 |
ldc.r4 |
|
0x23 |
ldc.r8 |
|
0x25 |
dup |
|
0x26 |
pop |
|
0x27 |
jmp |
|
0x28 |
call |
|
0x29 |
calli |
|
0x2a |
ret |
|
0x2b |
br.s |
|
0x2c |
brfalse.s |
|
0x2d |
brtrue.s |
|
0x2e |
beq.s |
|
0x2f |
bge.s |
|
0x30 |
bgt.s |
|
0x31 |
ble.s |
|
0x32 |
blt.s |
|
0x33 |
bne.un.s |
|
0x34 |
bge.un.s |
|
0x35 |
bgt.un.s |
|
0x36 |
ble.un.s |
|
0x37 |
blt.un.s |
|
0x38 |
br |
|
0x39 |
brfalse |
|
0x3a |
brtrue |
|
0x3b |
beq |
|
0x3c |
bge |
|
0x3d |
bgt |
|
0x3e |
ble |
|
0x3f |
blt |
|
0x40 |
bne.un |
|
0x41 |
bge.un |
|
0x42 |
bgt.un |
|
0x43 |
ble.un |
|
0x44 |
blt.un |
|
0x45 |
switch |
|
0x46 |
ldind.i1 |
|
0x47 |
ldind.u1 |
|
0x48 |
ldind.i2 |
|
0x49 |
ldind.u2 |
|
0x4a |
ldind.i4 |
|
0x4b |
ldind.u4 |
|
0x4c |
ldind.i8 |
|
0x4d |
ldind.i |
|
0x4e |
ldind.r4 |
|
0x4f |
ldind.r8 |
|
0x50 |
ldind.ref |
|
0x51 |
stind.ref |
|
0x52 |
stind.i1 |
|
0x53 |
stind.i2 |
|
0x54 |
stind.i4 |
|
0x55 |
stind.i8 |
|
0x56 |
stind.r4 |
|
0x57 |
stind.r8 |
|
0x58 |
add |
|
0x59 |
sub |
|
0x5a |
mul |
|
0x5b |
div |
|
0x5c |
div.un |
|
0x5d |
rem |
|
0x5e |
rem.un |
|
0x5f |
and |
|
0x60 |
or |
|
0x61 |
xor |
|
0x62 |
shl |
|
0x63 |
shr |
|
0x64 |
shr.un |
|
0x65 |
neg |
|
0x66 |
not |
|
0x67 |
conv.i1 |
|
0x68 |
conv.i2 |
|
0x69 |
conv.i4 |
|
0x6a |
conv.i8 |
|
0x6b |
conv.r4 |
|
0x6c |
conv.r8 |
|
0x6d |
conv.u4 |
|
0x6e |
conv.u8 |
|
0x6f |
callvirt |
|
0x70 |
cpobj |
|
0x71 |
ldobj |
|
0x72 |
ldstr |
|
0x73 |
newobj |
|
0x74 |
castclass |
|
0x75 |
isinst |
|
0x76 |
conv.r.un |
|
0x79 |
unbox |
|
0x7a |
throw |
|
0x7b |
ldfld |
|
0x7c |
ldflda |
|
0x7d |
stfld |
|
0x7e |
ldsfld |
|
0x7f |
ldsflda |
|
0x80 |
stsfld |
|
0x81 |
stobj |
|
0x82 |
conv.ovf.i1.un |
|
0x83 |
conv.ovf.i2.un |
|
0x84 |
conv.ovf.i4.un |
|
0x85 |
conv.ovf.i8.un |
|
0x86 |
conv.ovf.u1.un |
|
0x87 |
conv.ovf.u2.un |
|
0x88 |
conv.ovf.u4.un |
|
0x89 |
conv.ovf.u8.un |
|
0x8a |
conv.ovf.i.un |
|
0x8b |
conv.ovf.u.un |
|
0x8c |
box |
|
0x8d |
newarr |
|
0x8e |
ldlen |
|
0x8f |
ldelema |
|
0x90 |
ldelem.i1 |
|
0x91 |
ldelem.u1 |
|
0x92 |
ldelem.i2 |
|
0x93 |
ldelem.u2 |
|
0x94 |
ldelem.i4 |
|
0x95 |
ldelem.u4 |
|
0x96 |
ldelem.i8 |
|
0x97 |
ldelem.i |
|
0x98 |
ldelem.r4 |
|
0x99 |
ldelem.r8 |
|
0x9a |
ldelem.ref |
|
0x9b |
stelem.i |
|
0x9c |
stelem.i1 |
|
0x9d |
stelem.i2 |
|
0x9e |
stelem.i4 |
|
0x9f |
stelem.i8 |
|
0xa0 |
stelem.r4 |
|
0xa1 |
stelem.r8 |
|
0xa2 |
stelem.ref |
|
0xb3 |
conv.ovf.i1 |
|
0xb4 |
conv.ovf.u1 |
|
0xb5 |
conv.ovf.i2 |
|
0xb6 |
conv.ovf.u2 |
|
0xb7 |
conv.ovf.i4 |
|
0xb8 |
conv.ovf.u4 |
|
0xb9 |
conv.ovf.i8 |
|
0xba |
conv.ovf.u8 |
|
0xc2 |
refanyval |
|
0xc3 |
ckfinite |
|
0xc6 |
mkrefany |
|
0xd0 |
ldtoken |
|
0xd1 |
conv.u2 |
|
0xd2 |
conv.u1 |
|
0xd3 |
conv.i |
|
0xd4 |
conv.ovf.i |
|
0xd5 |
conv.ovf.u |
|
0xd6 |
add.ovf |
|
0xd7 |
add.ovf.un |
|
0xd8 |
mul.ovf |
|
0xd9 |
mul.ovf.un |
|
0xda |
sub.ovf |
|
0xdb |
sub.ovf.un |
|
0xdc |
endfinally |
|
0xdd |
leave |
|
0xde |
leave.s |
|
0xdf |
stind.i |
|
0xe0 |
conv.u |
|
0xfe 0x00 |
arglist |
|
0xfe 0x01 |
ceq |
|
0xfe 0x02 |
cgt |
|
0xfe 0x03 |
cgt.un |
|
0xfe 0x04 |
clt |
|
0xfe 0x05 |
clt.un |
|
0xfe 0x06 |
ldftn |
|
0xfe 0x07 |
ldvirtftn |
|
0xfe 0x09 |
ldarg |
|
0xfe 0x0a |
ldarga |
|
0xfe 0x0b |
starg |
|
0xfe 0x0c |
ldloc |
|
0xfe 0x0d |
ldloca |
|
0xfe 0x0e |
stloc |
|
0xfe 0x0f |
localloc |
|
0xfe 0x11 |
endfilter |
|
0xfe 0x12 |
unaligned. |
|
0xfe 0x13 |
volatile. |
|
0xfe 0x14 |
tail. |
|
0xfe 0x15 |
initobj |
|
0xfe 0x17 |
cpblk |
|
0xfe 0x18 |
initblk |
|
0xfe 0x1a |
rethrow |
|
0xfe 0x1c |
sizeof |
|
0xfe 0x1d |
refanytype |
The stack transition diagram displays the state of the evaluation stack before and after the instruction is executed. Below is a typical stack transition diagram.
, value1, value2 ΰ , result
This diagram indicates that the stack must have at least two elements on it, and in the definition the topmost value (top of stack or most recently pushed) will be called value2 and the value underneath (pushed prior to value2) will be called value1. (In diagrams like this, the stack grows to the right, along the page). The instruction removes these values from the stack and replaces them by another value, called result in the description.
The English description describes any details about the instructions that are not immediately apparent once the format and stack transition have been described.
Many CIL operations take numeric operands on the stack. These operations fall into several categories, depending on how they deal with the types of the operands. The following tables summarize the valid types of operand types and the type of the result. Notice that the type referred to here is the type as tracked by the CLI rather than the more detailed types used by tools such as CIL verification. The types tracked by the CLI are: int32, int64, native int, F, O, and &.
A op B (used for add, div, mul, rem, and sub). The table below shows the result type, for each possible combination of oparand types. Boxes holding simply a result type, apply to all five instructions. Boxes marked ϋ indicate an invalid CIL instruction. Shaded boxes indicate a CIL instruction that is not verifiable. Boxes with a list of instructions are valid only for those instructions.
Table 2: Binary Numeric Operations
|
A's Type |
B's Type |
|||||
|
int32 |
int64 |
native int |
F |
& |
O |
|
|
int32 |
int32 |
ϋ |
native int |
ϋ |
& (add) |
ϋ |
|
int64 |
ϋ |
int64 |
ϋ |
ϋ |
ϋ |
ϋ |
|
native int |
native int |
ϋ |
native int |
ϋ |
& (add) |
ϋ |
|
F |
ϋ |
ϋ |
ϋ |
F |
ϋ |
ϋ |
|
& |
& (add, sub) |
ϋ |
& (add, sub) |
ϋ |
native int (sub) |
ϋ |
|
O |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
Used for the neg instruction. Boxes marked ϋ indicate an invalid CIL instruction. All valid uses of this instruction are verifiable.
Table 3: Unary Numeric Operations
|
Operand Type |
int32 |
int64 |
native int |
F |
& |
O |
|
Result Type |
int32 |
int64 |
native int |
F |
ϋ |
ϋ |
These return a boolean value or branch based on the top two values on the stack. Used for beq, beq.s, bge, bge.s, bge.un, bge.un.s, bgt, bgt.s, bgt.un, bgt.un.s, ble, ble.s, ble.un, ble.un.s, blt, blt.s, blt.un, blt.un.s, bne.un, bne.un.s, ceq, cgt, cgt.un, clt, clt.un. Boxes marked ό indicate that all instructions are valid for that combination of operand types. Boxes marked ϋ indicate invalid CIL sequences. Shaded boxes boxes indicate a CIL instruction that is not verifiable. Boxes with a list of instructions are valid only for those instructions.
Table 4: Binary Comparison or Branch Operations
1. Except for beq, bne.un (or short versions) or ceq these combinations make sense if both operands are known to be pointers to elements of the same array. However, there is no security issue for a CLI that does not check this constraint
Note: if the two operands are not pointers into the same array, then the result is simply the distance apart in the garbage-collected heap of two unrelated data items. This distance apart will almost certainly change at the next garbage collection. Essentially, the result cannot be used to compute anything useful
2. cgt.un is allowed and verifiable on ObjectRefs (O). This is commonly used when comparing an ObjectRef with null (there is no compare-not-equal instruction, which would otherwise be a more obvious solution)
These operate only on integer types. Used for and, div.un, not, or, rem.un, xor. The div.un and rem.un instructions treat their arguments as unsigned integers and produce the bit pattern corresponding to the unsigned result. As described in the CLI Specification, however, the CLI makes no distinction between signed and unsigned integers on the stack. The not instruction is unary and returns the same type as the input. The shl and shr instructions return the same type as their first operand and their second operand must be of type native unsigned int. Boxes marked ϋ indicate invalid CIL sequences. All other boxes denote verifiable combinations of operands.
Table 5: Integer Operations
|
|
int32 |
int64 |
native int |
F |
& |
O |
|
int32 |
int32 |
ϋ |
native int |
ϋ |
ϋ |
ϋ |
|
int64 |
ϋ |
int64 |
ϋ |
ϋ |
ϋ |
ϋ |
|
native int |
native int |
ϋ |
native int |
ϋ |
ϋ |
ϋ |
|
F |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
|
& |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
|
O |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
Below are the legal combinations of operands and result for the shift instructions: shl, shr, shr_un. Boxes marked ϋ indicate invalid CIL sequences. All other boxes denote verifiable combinations of operand. If the Shift-By operand is larger than the width of the To-Be-Shifted operand, then the results are implementation-defined. (eg shift an int32 integer left by 37 bits)
Table 6 : Shift Operations
|
|
Shift-By |
||||||
|
int32 |
int64 |
native int |
F |
& |
O |
||
|
To Be Shifted |
int32 |
int32 |
ϋ |
int32 |
ϋ |
ϋ |
ϋ |
|
int64 |
int64 |
ϋ |
int64 |
ϋ |
ϋ |
ϋ |
|
|
native int |
native int |
ϋ |
native int |
ϋ |
ϋ |
ϋ |
|
|
F |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
|
|
& |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
|
|
O |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
|
These operations generate an exception if the result cannot be represented in the target data type. Used for add.ovf, add.ovf.un, mul.ovf, mul.ovf.un, sub.ovf, sub.ovf.un The shaded uses are not verifiable, while boxes marked ϋ indicate invalid CIL sequences.
Table 7: Overflow Arithmetic Operations
|
|
int32 |
int64 |
native int |
F |
& |
O |
|
int32 |
int32 |
ϋ |
native int |
ϋ |
& add.ovf.un |
ϋ |
|
int64 |
ϋ |
int64 |
ϋ |
ϋ |
ϋ |
ϋ |
|
native int |
native int |
ϋ |
native int |
ϋ |
& add.ovf.un |
ϋ |
|
F |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
|
& |
& |
ϋ |
& |
ϋ |
native int sub.ovf.un |
ϋ |
|
O |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
These operations convert the top item on the evaluation stack from one numeric type to another. The result type is guaranteed to be representable as the data type specified as part of the operation (i.e. the conv.u2 instruction returns a value that can be stored in a unsigned int16). The stack, however, can only store values that are a minimum of 4 bytes wide. Used for the conv.<to type>, conv.ovf.<to type>, and conv.ovf.<to type>.un instructions. The shaded uses are not verifiable, while boxes marked ϋ indicate invalid CIL sequences.
Table 8: Conversion Operations
|
Convert-To |
Input (from evaluation stack) |
|||||
|
int32 |
int64 |
native int |
F |
& |
O |
|
|
int8 |
Truncate1 |
Truncate1 |
Truncate1 |
Truncate to zero2 |
ϋ |
ϋ |
|
int32 |
Nop |
Truncate1 |
Truncate1 |
Truncate to zero2 |
ϋ |
ϋ |
|
int64 |
Sign extend |
Nop |
Sign extend |
Truncate to zero2 |
ϋ |
ϋ |
|
unsigned int64 |
Zero extend |
Nop |
Zero extend |
Truncate to zero2 |
Stop GC tracking |
ϋ |
|
native int |
Sign extend |
Truncate1 |
Nop |
Truncate to zero2 |
ϋ |
ϋ |
|
native unsigned int |
Zero extend |
Truncate1 |
Nop |
Truncate to zero2 |
Stop GC tracking |
ϋ |
|
All Float Types |
To Float |
To Float |
To Float |
Change precision3 |
ϋ |
ϋ |
1. Truncate means that the number is truncated to the desired size; ie, the most significant bytes of the input value are simply ignored. If the result is narrower than the minimum stack width of 4 bytes, then this result is zero extended (if the target type is unsigned) or sign-extended (if the target type is signed). Thus, converting the value 0x1234 ABCD from the evaluation stack to an 8-bit datum yields the result 0xCD; if the target type were int8, this is sign-extended to give 0xFFFF FFCD; if, instead, the target type were unsigned int8, this is zero-extended to give 0x0000 00CD.
2. Trunc to 0 means that the floating-point number will be converted to an integer by truncation toward zero. Thus 1.1 is converted to 1 and 1.1 is converted to 1.
3. Converts from the current precision available on the evaluation stack to the precision specified by the instruction. If the stack has more precision than the output size the conversion is performed using the IEC 60559:1989 round to nearest mode to compute the low order bit of the result.
4. Stop GC Tracking means that, following the conversion, the items value will not be reported to subsequent garbage-collection operations (and therefore will not be updated by such operations)
While the CLI operates only on 6 types (int32, native int, int64, F, O, and &) the metadata supplies a much richer model for parameters of methods. When about to call a method, the CLI performs implicit type conversions, detailed in the following table. (Conceptually, it inserts the appropriate conv.* instruction into the CIL stream, which may result in an information loss through truncation or rounding) This implicit conversion occurs for boxes marked ό. Shaded boxes are not verifiable. Boxes marked ϋ indicate invalid CIL sequences. (A compiler is of course free to emit explicit conv.* or conv.*.ovf instructions to achieve any desired effect)
Table 9: Signature Matching
|
Type In Signature |
Stack Parameter |
|||||
|
int32 |
native int |
int64 |
F |
& |
O |
|
|
int8 |
ό |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
unsigned int8 |
ό |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
int16 |
ό |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
unsigned int16 |
ό |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
int32 |
ό |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
unsigned int32 |
ό |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
int64 |
ϋ |
ϋ |
ό |
ϋ |
ϋ |
ϋ |
|
unsigned int64 |
ϋ |
ϋ |
ό |
ϋ |
ϋ |
ϋ |
|
native int |
ό Sign extend |
ό |
ϋ |
ϋ |
ϋ |
ϋ |
|
native unsigned int |
ό Zero extend |
ό Zero extend |
ϋ |
ϋ |
ϋ |
ϋ |
|
float32 |
ϋ |
ϋ |
ϋ |
Note4 |
ϋ |
ϋ |
|
float64 |
ϋ |
ϋ |
ϋ |
Note4 |
ϋ |
ϋ |
|
Class |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ό |
|
Value Type (Note2) |
Note1 |
Note1 |
Note1 |
Note1 |
ϋ |
ϋ |
|
By-Ref |
ϋ |
ό Start GC tracking |
ϋ |
ϋ |
ό |
ϋ |
|
Ref Any |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
ϋ |
1. Passing a built-in type to a parameter that is required to be a value type is not allowed.
2. The CLIs stack can contain a value type. These may only be passed if the particular value type on the stack exactly matches the class required by the corresponding parameter.
3. There are special instructions to construct and pass a Ref Any.
4. The CLI is permitted to pass floating point arguments using its internal F type, see clause 1.1.1_1.1.1_NumericDataTypes. CIL generators may, of course, include an explicit conv.r4, conv.r4.ovf, or similar instruction.
Further notes concerning this table:
· On a 32-bit machine passing a native int argument to a unsigned int32 parameter involves no conversion. On a 64-bit machine it is implicitly converted.
· Start GC Tracking means that, following the implicit conversion, the items value will be reported to any subsequent garbage-collection operations, and perhaps changed as a result of the item pointed-to being relocated in the heap.
As well as detailed restrictions on CIL code sequences to ensure:
· Valid CIL
· Verifiable CIL
there are a few further restrictions, imposed to make it easier to construct a simple CIL-to-native-code compiler. This section specifies the general restrictions that apply in addition to this listed for individual instructions.
The implementation of a method is provided by a contiguous block of CIL instructions, encoded as specified below. The address of the instruction block for a method as well as its length is specified in the file format (see Partition II_alink_partitionII, Common Intermediate Language Physical Layout). The first instruction is at the first byte (lowest address) of the instruction block.
Instructions are variable in size. The size of each instruction can be determined (decoded) from the content of the instruction bytes themselves. The size of and ordering of the bytes within an instruction is specified by each instruction definition. Instructions follow each other without padding in a stream of bytes that is both alignment and byte-order insensitive.
Each instruction occupies an exact number of bytes, and until the end of the instruction block, the next instruction begins immediately at the next byte. It is invalid for the instruction block (as specified by the blocks length) to end without forming a complete last instruction.
Instruction prefixes extend the length of an instruction without introducing a new instruction; an instruction having one or more prefixes introduces only one instruction that begins at the first byte of the first instruction prefix.
Note: Until the end of the instruction block, the instruction following any control transfer instruction is decoded as an instruction and thus participates in locating subsequent instructions even if it is not the target of a branch. Only instructions may appear in the instruction stream, even if unreachable. There are no address-relative data addressing modes and raw data cannot be directly embedded within the instruction stream. Certain instructions allow embedding of immediate data as part of the instruction, however that differs from allowing raw data embedded directly in the instruction stream. Unreachable code may appear as the result of machine-generated code and is allowed, but it must always be in the form of properly formed instruction sequences.
The instruction stream can be translated and the associated instruction block discarded prior to execution of the translation. Thus, even instructions that capture and manipulate code addresses, such as call, ret, etc. can be virtualized to operate on translated addresses instead of addresses in the CIL instruction stream.
The set of addresses composed of the first byte of each instruction identified in the instruction stream defines the only valid instruction targets. Instruction targets include branch targets as specified in branch instructions, targets specified in exception tables such as protected ranges (see Partition I_alink_partitionI and Partition II_alink_partitionII), filter, and handler targets.
Branch instructions specify branch targets as either a one-byte or four-byte signed relative offset; the size of the offset is differentiated by the opcode of the instruction. The offset is defined as being relative to the byte following the branch instruction. [Note: Thus, an offset value of zero targets the immediately following instruction.]
The value of a one-byte offset is computed by interpreting that byte as a signed 8-bit integer. The value of a four-byte offset is can be computed by concatenating the bytes into a signed integer in the following manner: the byte of lowest address forms the least significant byte, and the byte with highest address forms the most significant byte of the integer. [Note: This representation is often called a signed integer in little-endian byte-order.]
Exception tables describe ranges of instructions that are protected by catch, fault, or finally handlers (see Partition I_alink_partitionI and Partition II_alink_partitionII). The starting address of a protected block, filter clause, or handler shall be a valid branch target as specified in clause 1.7.2_1.7.2_ValidBranchTargets. It is invalid for a protected block, filter clause, or handler to end without forming a complete last instruction.
Every method specifies a maximum number of items that can be pushed onto the CIL Evaluation. The value is stored in the IMAGE_COR_ILMETHOD structure that precedes the CIL body of each method. A method that specifies a maximum number of items less than the amount required by a static analysis of the method (using a traditional control flow graph without analysis of the data) is invalid (hence also unverifiable) and need not be supported by a conforming implementation of the CLI.
Note: Maxstack is related to analysis of the program, not to the size of the stack at runtime. It does not specify the maximum size in bytes of a stack frame, but rather the number of items that must be tracked by an analysis tool.
Rationale: By analyzing the CIL stream for any method, it is easy to determine how many items will be pushed on the CIL Evaluation stack. However, specifying that maximum number ahead of time helps a CIL-to-native-code compiler (especially a simple one that does only a single pass through the CIL stream) in allocating internal data structures that model the stack and/or verification algorithm.
It must be possible, with a single forward-pass through the CIL instruction stream for any method, to infer the exact state of the evaluation stack at every instruction (where by state we mean the number and type of each item on the evaluation stack).
In particular, if that single-pass analysis arrives at an instruction, call it location X, that immediately follows an unconditional branch, and where X is not the target of an earlier branch instruction, then the state of the evaluation stack at X, clearly, cannot be derived from existing information. In this case, the CLI demands that the evaluation stack at X be empty.
Following on from this rule, it would clearly be invalid CIL if a later branch instruction to X were to have a non-empty evaluation stack
Rationale: This constraint ensures that CIL code can be processed by a simple CIL-to-native-code compiler. It ensures that the state of the evaluation stack at the beginning of each CIL can be inferred from a single, forward-pass analysis of the instruction stream.
Note: the stack state at location X in the above can be inferred by various means: from a previous forward branch to X; because X marks the start of an exception handler, etc.
See the following sections for further information:
· Exceptions: Partition I_alink_partitionI
· Verification conditions for branch instructions: Chapter 3_3_BaseInstructions
· The tail. prefix: Section 3.19
The target of all branch instruction must be a valid branch target (see clause 1.7.2_1.7.2_ValidBranchTargets) within the method holding that branch instruction.
Memory safety is a property that ensures programs running in the same address space are correctly isolated from one another (see Partition I_alink_partitionI). Thus, it is desirable to test whether programs are memory safe prior to running them. Unfortunately, it is provably impossible to do this with 100% accuracy. Instead, the CLI can test a stronger restriction, called verifiability. Every program that is verified is memory safe, but some programs that are not verifiable are still memory safe.
It is perfectly acceptable to generate CIL code that is not verifiable, but which is known to be memory safe by the compiler writer. Thus, conforming CIL may not be verifiable, even though the producing compiler may know that it is memory safe. Several important uses of CIL instructions are not verifiable, such as the pointer arithmetic versions of add that are required for the faithful and efficient compilation of C programs. For non-verifiable code, memory safety is the responsibility of the application programmer.
CIL contains a verifiable subset. The Verifiability description gives details of the conditions under which a use of an instruction falls within the verifiable subset of CIL. Verification tracks the types of values in much finer detail than is required for the basic functioning of the CLI, because it is checking that a CIL code sequence respects not only the basic rules of the CLI with respect to the safety of garbage collection, but also the typing rules of the CTS. This helps to guarantee the sound operation of the entire CLI.
The verifiability section of each operation description specifies requirements both for correct CIL generation and for verification. Correct CIL generation always requires guaranteeing that the top items on the stack correspond to the types shown in the stack transition diagram. The verifiability section specifies only requirements for correct CIL generation that are not captured in that diagram. Verification tests both the requirements for correct CIL generation and the specific verification conditions that are described with the instruction. The operation of CIL sequences that do not meet the CIL correctness requirements is unspecified. The operation of CIL sequences that meet the correctness requirements but are not verifiable may violate type safety and hence may violate security or memory access constraints.
This section specifies a verification algorithm that, combined with information on individual CIL instructions (see Chapter 3_3_BaseInstructions) and metadata validation (see Partition II_alink_partitionII), guarantees memory integrity.
The algorithm specified here creates a minimum level for all compliant implementations of the CLI in the sense that any program that is considered verifiable by this algorithm shall be considered verifiable and run correctly on all compliant implementations of the CLI.
The CLI provides a security permission (see Partition IV_alink_partitionIV) that controls whether or not the CLI shall run programs that may violate memory safety. Any program that is verifiable according to this specification does not violate memory safety, and a conforming implementation of the CLI shall run such programs. The implementation may also run other programs provided it is able to show they do not violate memory safety (typically because they use a verification algorithm that makes use of specific knowledge about the implementation).
Note: While a compliant implementation is required to accept and run any program this verification algorithm states is verifiable, there may be programs that are accepted as verifiable by a given implementation but which this verification algorithm will fail to consider verifiable. Such programs will run in the given implementation but need not be considered verifiable by other implementations.
For example, an implementation of the CLI may choose to correctly track full signatures on method pointers and permit programs to execute the calli instruction even though this is not permitted by the verification algorithm specified here.
Implementers of the CLI are urged to provide a means for testing whether programs generated on their implementation meet this portable verifiability standard. They are also urged to specify where their verification algorithms are more permissive than this standard.
Implementation Specific (Microsoft)
The various implementations of the CLI produced by Microsoft use slightly different verification algorithms. In all cases, however, the PEVerify program (part of the SDK) implements the portable verification algorithm as specified in this Standard. Programmers are urged to run PEVerify over all code before shipping it for possible use on other implementations of the CLI.
Some implementations of the CLI produced by Microsoft have the following differences from the verification algorithm specified here:
· TBD
Only valid programs shall be verifiable. For ease of explanation, the verification algorithm described here assumes that the program is valid and does not explicitly call for tests of all validity conditions. Validity conditions are specified on a per-CIL instruction basis (see Chapter 3_3_BaseInstructions), and on the overall file format in Partition II_alink_PartitionII.
The verification algorithm shall attempt to associate a valid stack state with every CIL instruction. The stack state specifies the number of slots on the CIL stack at that point in the code and for each slot a required type that must be present in that slot. The initial stack state is empty (there are no items on the stack).
Verification assumes that the CLI zeroes all memory other than the evaluation stack before it is made visible to programs. A conforming implementation of the CLI shall provide this observable behavior. Furthermore, verifiable methods shall have the zero initialize bit set, see Partition II (Flags for Method Headers)_alink_paritionII. If this bit is not set, then a CLI may throw a Verification exception at any point where a local variable is accessed, and where the assembly containing that method has not been granted SecurityPermission.SkipVerification
Rationale: This requirement strongly enhances program portability, and a well-known technique (definite assignment analysis) allows a compiler from CIL to native code to minimize its performance impact. Note that a CLI may optionally choose to perform definite-assignment analysis in such a case, it may confirm that a method, even without the zero initialize bit set, may in fact be verifiable (and therefore not throw a Verification exception)
Note: Definite assignment analysis can be used by the CLI to determine which locations are written before they are read. Such locations neednt be zeroed, since it isnt possible to observe the contents of the memory as it was provided by the EE.
Performance measurements on C++ implementations (which does not require definite assignment analysis) indicate that adding this requirement has almost no impact, even in highly optimized code. Furthermore, customers incorrectly attribute bugs to the compiler when this zeroing is not performed, since such code often fails when small, unrelated changes are made to the program.
The verification algorithm shall simulate all possible control flow paths through the code and ensures that a legal stack state exists for every reachable CIL instruction. The verification algorithm does not take advantage of any data values during its simulation (e.g. it does not perform constant propagation), but uses only type assignments. Details of the type system used for verification and the algorithm used to merge stack states are provided in clause 1.8.1.3. The verification algorithm terminates as follows:
1. Successfully, when all control paths have been simulated.
2. Unsuccessfully when it is not possible to compute a valid stack state for a particular CIL instruction.
3. Unsuccessfully when additional tests specified in this clause fail.
There is a control flow path from every instruction to the subsequent instruction, with the exception of the unconditional branch instructions, throw, rethrow, and ret. Finally, there is a control flow path from each branch instruction (conditional or unconditional) to the branch target (targets, plural, for the switch instruction).
Verification simulates the operation of each CIL instruction to compute the new stack state, and any type mismatch between the specified conditions on the stack state (see Chapter 3_3_BaseInstructions) and the simulated stack state shall cause the verification algorithm to fail. (Note that verification simulates only the effect on the stack state: it does not perform the actual computation). The algorithm shall also fail if there is an existing stack state at the next instruction address (for conditional branches or instructions within a try block there may be more than one such address) that cannot be merged with the stack state just computed. For rules of this merge operation, see clause 1.8.1.3.
The verification algorithm compresses types that are logically equivalent, since they cannot lead to memory safety violations. The types used by the verification algorithm are specified in clause 1.8.1.2.1, the type compatibility rules are specified in clause 1.8.1.2.2, and the rules for merging stack states are in clause 1.8.1.3.
The following table specifies the mapping of types used in the CLI and those used in verification. Notice that verification compresses the CLI types to a smaller set that maintains information about the size of those types in memory, but then compresses these again to represent the fact that the CLI stack expands 1, 2 and 4 byte built-in types into 4-byte types on the stack. Similarly, verification treats floating-point numbers on the stack as 64-bit quantities regardless of the actual representation.
Arrays are objects, but with special compatibility rules.
There is a special encoding for null that represents an object known to be the null value, hence with indeterminate actual type.
In the following table, CLI Type is the type as it is described in metadata. The Verification Type is a corresponding type used for type compatibility rules in verification (see clause 1.8.1.2.2) when considering the types of local variables, incoming arguments, and formal parameters on methods being called. The column Verification Type (in stack state) is used to simulate instructions that load data onto the stack, and shows the types that are actually maintained in the stack state information of the verification algorithm. The column Managed Pointer to Type shows the type tracked for managed pointers.
|
CLI Type |
Verification Type |
Verification Type (in stack state) |
Managed Pointer to Type |
|
Int8, unsigned int8, bool |
int8 |
int32 |
& int8 |
|
Int16, unsigned int16, char |
int16 |
int32 |
& int16 |
|
int32, unsigned int32 |
int32 |
int32 |
& int32 |
|
Int64, unsigned int64 |
int64 |
int64 |
& int64 |
|
native int, native unsigned int |
native int |
native int |
& native int |
|
float32 |
Float32 |
float64 |
& float32 |
|
float64 |
Float64 |
float64 |
& float64 |
|
Any value type |
Same type |
Same type |
& Same type |
|
Any object type |
Same type |
Same type |
& Same type |
|
Method pointer |
Same type |
Same type |
Not valid |
A method can be defined as returning a managed pointer, but calls upon such methods are not verifiable.
Rationale: some uses of returning a managed pointer are perfectly verifiable (eg, returning a reference to a field in an object); but some not (eg, returning a pointer to a local variable of the called method). Tracking this in the general case is a burden, and therefore not included in this standard.
The following rules define type compatibility. We use S and T to denote verification types, and the notation S := T to indicate that the verification type T can be used wherever the verification type S can be used, while S !:= T indicates that T cannot be used where S is expected. These are the verification type compatibility (see Partition I_alink_partitionI) rules. We use T[] to denote an array (of any rank) whose elements are of type T, and T& to denote a managed pointer to type T.
1. [:= is reflexive] For all verification types S, S := S
2. [:= is transitive] For all verification types S, T, and U if S := T and T := U, then S := U.
3. S := T if S is the base class of T or an interface implemented by T and T is not a value type.
4. S := T if S and T are both interfaces and the implementation of T requires the implementation of S
5. S := null if S is an object type or an interface
6. S[] := T[] if S := T and the arrays are either both vectors (zero-based, rank one) or neither is a vector and both have the same rank.
7. If S and T are method pointers, then S := T if the signatures (return types, parameter types, calling convention, and any custom attributes or custom modifiers) are the same.
8. Otherwise S !:= T
As the verification algorithm simulates all control flow paths it shall merge the simulated stack state with any existing stack state at the next CIL instruction in the flow. If there is no existing stack state, the simulated stack state is stored for future use. Otherwise the merge shall be computed as follows and stored to replace the existing stack state for the CIL instruction. If the merge fails, the verification algorithm shall fail.
The merge shall be computed by comparing the number of slots in each stack state. If they differ, the merge shall fail. If they match, then the overall merge shall be computed by merging the states slot-by-slot as follows. Let T be the type from the slot on the newly computed state and S be the type from the corresponding slot on the previously stored state. The merged type, U, shall be computed as follows (recall that S := T is the compatibility function defined in clause 1.8.1.2.2):
1. if S := T then U=S
2. Otherwise if T := S then U=T
3. Otherwise, if S and T are both object types, then let V be the closest common supertype of S and T then U=V.
4. Otherwise, the merge shall fail.
Implementation Specific (Microsoft)
The V1.0 release of the Microsoft CLI will merge interfaces by arbitrarily choosing the first common interface between the two verification types being merged.
The VES ensures that all statics are initially zeroed (i.e. built-in types are 0 or false, object references are null), hence the verification algorithm does not test for definite assignment to statics.
An object constructor shall not return unless a constructor for the base class or a different construct for the objects class has been called on the newly constructed object. The verification algorithm shall treat the this pointer as uninitialized unless the base class constructor has been called. No operations can be performed on an uninitialized this except for storing into and loading from the objects fields.
Note: If the constructor generates an exception the this pointer in the corresponding catch block is still uninitialized.
The verification algorithm shall require that one of the following code sequences is used for constructing delegates; no other code sequence in verifiable code shall contain a newobj instruction for a delegate type. There shall be only one instance constructor method for a Delegate (overloading is not allowed)
The verification algorithm shall fail if a branch target is within these instruction sequences (other than at the start of the sequence).
Note: See Partition II_alink_partitionII for the signature of delegates and a validity requirement regarding the signature of the method used in the constructor and the signature of Invoke and other methods on the delegate class.
The following CIL instruction sequence shall be used or the verification algorithm shall fail. The sequence begins with an object on the stack.
dup
ldvirtftn mthd ; Method shall be on the class of the object,
; or one of its parent classes, or an interface
; implemented by the object
newobj delegateclass::.ctor(object, native int)
Rationale: The dup is required to ensure that it is precisely the same object stored in the delegate as was used to compute the virtual method. If another object of a subtype were used the object and the method wouldnt match and could lead to memory violations.
The following CIL instruction sequence shall be used or the verification algorithm shall fail. The sequence begins with either null or an object on the stack.
ldftn mthd ; Method shall either be a static method or
; a method on the class of the object on the stack or
; one of the objects parent classes
newobj delegateclass::.ctor(object, native int)
Many CIL instructions are followed by a "metadata token". This is a 4-byte value, that specifies a row in a metadata table, or a starting byte offset in the User String heap. The most-significant byte of the token specifies the table or heap. For example, a value of 0x02 specifies the TypeDef table; a value of 0x70 specifies the User String heap. The value corresponds to the number assigned to that metadata table (see Partition II_alink_partitionII for the full list of tables) or to 0x70 for the User String heap. The least-significant 3 bytes specify the target row within that metadata table, or starting byte offset within the User String heap. The rows within metadata tables are numbered one upwards, whilst offsets in the heap are numbered zero upwards. (So, for example, the metadata token with value 0x02000007 specifies row number 7 in the TypeDef table)
A CIL instruction can throw a range of exceptions. The CLI can also throw the general purpose exception called ExecutionEngineException. See Partition I_alink_partitionI for details.
These special values are reserved to precede specific instructions. They do not constitute full instructions in their own right. It is not valid CIL to branch to the instruction following the prefix, but the prefix itself is a valid branch target. It is not valid CIL to have a prefix without immediately following it by one of the instructions it is permitted to precede.
|
Format |
Assembly Format |
Description |
|
FE 14 |
tail. |
Subsequent call terminates current method |
Description:
The tail. instruction must immediately precede a call, calli, or callvirt instruction. It indicates that the current methods stack frame is no longer required and thus can be removed before the call instruction is executed. Because the value returned by the call will be the value returned by this method, the call can be converted into a cross-method jump.
The evaluation stack must be empty except for the arguments being transferred by the following call. The instruction following the call instruction must be a ret. Thus the only legal code sequence is
tail.
call
(or calli
or callvirt)
somewhere
ret
Correct CIL must not branch to the call instruction, but it is permitted to branch to the ret. The only values on the stack must be the arguments for the method being called.
The tail.call (or calli or callvirt) instruction cannot be used to transfer control out of a try, filter, catch, or finally block. See Partition I_alink_partitionI.
The current frame cannot be discarded when control is transferred from untrusted code to trusted code, since this would jeopardize code identity security. Security checks may therefore cause the tail. to be ignored, leaving a standard call instruction.
Similarly, in order to allow the exit of a synchronized region to occur after the call returns, the tail. prefix is ignored when used to exit a method that is marked synchronized.
There may also be implementation-specific restrictions that prevent the tail. prefix from being obeyed in certain cases. While an implementation is free to ignore the tail. prefix under these circumstances, they should be clearly documented as they can affect the behavior of programs.
CLI implementations are required to honor tail. call requests where caller and callee methods can be statically determined to lie in the same assembly; and where the caller is not in a synchronized region; and where caller and callee satisfy all conditions listed in the Verifiability rules below. (To honor the tail. prefix means to remove the callers frame, rather than revert to a regular call sequence). Consequently, a CLI implementation need not honor tail. calli or tail. callvirt sequences.
Rationale: tail. calls allow some linear space algorithms to be converted to constant space algorithms and are required by some languages. In the presence of ldloca and ldarga instructions it isnt always possible for a compiler from CIL to native code to optimally determine when a tail. can be automatically inserted.
Exceptions:
None.
Verifiability:
Correct CIL obeys the control transfer constraints listed above. In addition, no managed pointers can be passed to the method being called if they point into the stack frame that is about to be removed. The return type of the method being called must be compatible with the return type of the current method. Verification requires that no managed pointers are passed to the method being called, since it does not track pointers into the current frame.
|
Format |
Assembly Format |
Description |
|
FE 12 <unsigned int8> |
unaligned. alignment |
Subsequent pointer instruction may be unaligned |
Stack Transition:
..., addr ΰ ..., addr
Description:
Unaligned. specifies that address (an unmanaged pointer (&), or native int) on the stack may not be aligned to the natural size of the immediately following ldind, stind, ldfld, stfld, ldobj, stobj, initblk, or cpblk instruction. That is, for a ldind.i4 instruction the alignment of addr may not be to a 4-byte boundary. For initblk and cpblk the default alignment is architecture dependent (4-byte on 32-bit CPUs, 8-byte on 64-bit CPUs). Code generators that do not restrict their output to a 32-bit word size (see Partition I_alink_partitionI and Partition II_alink_partitionI) must use unaligned. if the alignment is not known at compile time to be 8-byte.
The value of alignment shall be 1, 2, or 4 and means that the generated code should assume that addr is byte, double byte, or quad byte aligned, respectively.
Rationale: While the alignment for a cpblk instruction would logically require two numbers (one for the source and one for the destination), there is no noticeable impact on performance if only the lower number is specified.
The unaligned. and volatile. prefixes may be combined in either order. They must immediately precede a ldind, stind, ldfld, stfld, ldobj, stobj, initblk, or cpblk instruction. Only the volatile. prefix is allowed for the ldsfld and stsfld instructions.
Note: See Partition I, 12.7_alink_partitionI_12.7 for information about atomicity and data alignment.
Exceptions:
None.
Verifiability:
An unaligned. prefix shall be immediately followed by one of the instructions listed above.
|
Format |
Assembly Format |
Description |
|
FE 13 |
volatile. |
Subsequent pointer reference is volatile |
Stack Transition:
..., addr ΰ ..., addr
Description:
volatile. specifies that addr is a volatile address (i.e. it may be referenced externally to the current thread of execution) and the results of reading that location cannot be cached or that multiple stores to that location cannot be suppressed. Marking an access as volatile. affects only that single access; other accesses to the same location must be marked separately. Access to volatile locations need not be performed atomically. [see Partition I_alink_partitionI]
The unaligned. and volatile. prefixes may be combined in either order. They must immediately precede a ldind, stind, ldfld, stfld, ldobj, stobj, initblk, or cpblk instruction. Only the volatile. prefix is allowed for the ldsfld and stsfld instructions.
Exceptions:
None.
Verifiability:
A volatile. prefix should be immediately followed by one of the instructions listed above.
These instructions form a Turing Complete set of basic operations. They are independent of the object model that may be employed. Operations that are specifically related to the CTSs object model are contained in the Object Model Instructions section.
|
Format |
Assembly Format |
Description |
|
58 |
add |
Add two values, returning a new value |
Stack Transition:
, value1, value2 ΰ , result
Description:
The add instruction adds value2 to value1 and pushes the result on the stack. Overflow is not detected for integral operations (but see add.ovf); floating-point overflow returns +inf or -inf.
The acceptable operand types and their corresponding result data type is encapsulated in Table2: Binary Numeric Operations_Table2_BinaryNumericOperations.
Exceptions:
None.
Verifiability:
See Table2: Binary Numeric Operations_Table2_BinaryNumericOperations.
|
Format |
Assembly Format |
Description |
|
D6 |
add.ovf |
Add signed integer values with overflow check. |
|
D7 |
add.ovf.un |
Add unsigned integer values with overflow check. |
Stack Transition:
, value1, value2 ΰ , result
Description:
The add.ovf instruction adds value1 and value2 and pushes the result on the stack. The acceptable operand types and their corresponding result data type is encapsulated in Table 7: Overflow Arithmetic Operations_Table7_OverflowArithmeticOperations.
Exceptions:
OverflowException is thrown if the result can not be represented in the result type.
Verifiability:
See Table 7: Overflow Arithmetic Operations_Table7_OverflowArithmeticOperations.
|
Format |
Instruction |
Description |
|
5F |
And |
Bitwise AND of two integral values, returns an integral value |
Stack Transition:
, value1, value2 ΰ , result
Description:
The and instruction computes the bitwise AND of the top two values on the stack and pushes the result on the stack. The acceptable operand types and their corresponding result data type is encapsulated in Table 5: Integer Operations_Table5_IntegerOperations.
Exceptions:
None.
Verifiability:
See Table 5: Integer Operations_Table5_IntegerOperations.
|
Format |
Assembly Format |
Description |
|
FE 00 |
arglist |
return argument list handle for the current method |
Stack Transition:
ΰ , argListHandle
Description:
The arglist instruction returns an opaque handle (an unmanaged pointer, type native int) representing the argument list of the current method. This handle is valid only during the lifetime of the current method. The handle can, however, be passed to other methods as long as the current method is on the thread of control. The arglist instruction may only be executed within a method that takes a variable number of arguments.
Rationale: This instruction is needed to implement the C va_* macros used to implement procedures like printf. It is intended for use with the class library implementation of System.ArgIterator.
Exceptions:
None.
Verifiability:
It is incorrect CIL generation to emit this instruction except in the body of a method whose signature indicates it accepts a variable number of arguments. Within such a method its use is verifiable, but verification requires that the result is an instance of the System.RuntimeArgumentHandle class.
|
Format |
Assembly Format |
Description |
|
3B <int32> |
beq target |
branch to target if equal |
|
2E <int8> |
beq.s target |
branch to target if equal, short form |
Stack Transition:
, value1, value2 ΰ
Description:
The beq instruction transfers control to target if value1 is equal to value2. The effect is identical to performing a ceq instruction followed by a brtrue target. Target is represented as a signed offset (4 bytes for beq, 1 byte for beq.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch OperationsTable4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTables for more details.
|
Format |
Assembly Format |
Description |
|
3C <int32> |
bge target |
branch to target if greater than or equal to |
|
2F <int8> |
bge.s target |
branch to target if greater than or equal to, short form |
Stack Transition:
, value1, value2 ΰ
Description:
The bge instruction transfers control to target if value1 is greater than or equal to value2. The effect is identical to performing a clt.un instruction followed by a brfalse target. Target is represented as a signed offset (4 bytes for bge, 1 byte for bge.s) from the beginning of the instruction following the current instruction.
The effect of a bge target instruction is identical to:
· If stack operands are integers, then : clt followed by a brfalse target
· If stack operands are floating-point, then : clt.un followed by a brfalse target
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTables for more details.
|
Format |
Assembly Format |
Description |
|
41 <int32> |
bge.un target |
branch to target if greater than or equal to (unsigned or unordered) |
|
34 <int8> |
bge.un.s target |
branch to target if greater than or equal to (unsigned or unordered), short form |
Stack Transition:
, value1, value2 ΰ
Description:
The bge.un instruction transfers control to target if value1 is greater than or equal to value2, when compared unsigned (for integer values) or unordered (for float point values). The effect is identical to performing a clt instruction followed by a brfalse target. Target is represented as a signed offset (4 bytes for bge.un, 1 byte for bge.un.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
3D <int32> |
bgt target |
branch to target if greater than |
|
30 <int8> |
bgt.s target |
branch to target if greater than, short form |
Stack Transition:
, value1, value2 ΰ
Description:
The bgt instruction transfers control to target if value1 is greater than value2. The effect is identical to performing a cgt instruction followed by a brtrue target. Target is represented as a signed offset (4 bytes for bgt, 1 byte for bgt.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
42 <int32> |
bgt.un target |
branch to target if greater than (unsigned or unordered) |
|
35 <int8> |
bgt.un.s target |
branch to target if greater than (unsigned or unordered), short form |
Stack Transition:
, value1, value2 ΰ
Description:
The bgt.un instruction transfers control to target if value1 is greater than value2, when compared unsigned (for integer values) or unordered (for float point values). The effect is identical to performing a cgt.un instruction followed by a brtrue target. Target is represented as a signed offset (4 bytes for bgt.un, 1 byte for bgt.un.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
3E <int32> |
ble target |
branch to target if less than or equal to |
|
31 <int8> |
ble.s target |
branch to target if less than or equal to, short form |
Stack Transition:
, value1, value2 ΰ
Description:
The ble instruction transfers control to target if value1 is less than or equal to value2. Target is represented as a signed offset (4 bytes for ble, 1 byte for ble.s) from the beginning of the instruction following the current instruction.
The effect of a ble target instruction is identical to:
· If stack operands are integers, then : cgt followed by a brfalse target
· If stack operands are floating-point, then : cgt.un followed by a brfalse target
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
43 <int32> |
ble.un target |
branch to target if less than or equal to (unsigned or unordered) |
|
36 <int8> |
ble.un.s target |
branch to target if less than or equal to (unsigned or unordered), short form |
Stack Transition:
, value1, value2 ΰ
Description:
The ble.un instruction transfers control to target if value1 is less than or equal to value2, when compared unsigned (for integer values) or unordered (for float point values). Target is represented as a signed offset (4 bytes for ble.un, 1 byte for ble.un.s) from the beginning of the instruction following the current instruction.
The effect of a ble.un target instruction is identical to:
· If stack operands are integers, then : cgt.un followed by a brfalse target
· If stack operands are floating-point, then : cgt followed by a brfalse target
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
3F <int32> |
blt target |
branch to target if less than |
|
32 <int8> |
blt.s target |
branch to target if less than, short form |
Stack Transition:
, value1, value2 ΰ
Description:
The blt instruction transfers control to target if value1 is less than value2. The effect is identical to performing a clt instruction followed by a brtrue target. Target is represented as a signed offset (4 bytes for blt, 1 byte for blt.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
44 <int32> |
blt.un target |
Branch to target if less than (unsigned or unordered) |
|
37 <int8> |
blt.un.s target |
Branch to target if less than (unsigned or unordered), short form |
Stack Transition:
, value1, value2 ΰ
Description:
The blt.un instruction transfers control to target if value1 is less than value2, when compared unsigned (for integer values) or unordered (for float point values). The effect is identical to performing a clt.un instruction followed by a brtrue target. Target is represented as a signed offset (4 bytes for blt.un, 1 byte for blt.un.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
40 <int32> |
bne.un target |
branch to target if unequal or unordered |
|
33 <int8> |
bne.un.s target |
branch to target if unequal or unordered, short form |
Stack Transition:
, value1, value2 ΰ
Description:
The bne.un instruction transfers control to target if value1 is not equal to value2, when compared unsigned (for integer values) or unordered (for float point values). The effect is identical to performing a ceq instruction followed by a brfalse target. Target is represented as a signed offset (4 bytes for bne.un, 1 byte for bne.un.s) from the beginning of the instruction following the current instruction.
The acceptable operand types are encapsulated in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee that the top two items on the stack correspond to the types shown in Table 4: Binary Comparison or Branch Operations_Table4_BinaryComparisonOrBranchOperations.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
38 <int32> |
br target |
branch to target |
|
2B <int8> |
br.s target |
branch to target, short form |
Stack Transition:
, ΰ
Description:
The br instruction unconditionally transfers control to target. Target is represented as a signed offset (4 bytes for br, 1 byte for br.s) from the beginning of the instruction following the current instruction.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Rationale: While a leave instruction can be used instead of a br instruction when the evaluation stack is empty, doing so may increase the resources required to compile from CIL to native code and/or lead to inferior native code. Therefore CIL generators should use a br instruction in preference to a leave instruction when both are legal.
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
01 |
break |
inform a debugger that a breakpoint has been reached. |
Stack Transition:
, ΰ
Description:
The break instruction is for debugging support. It signals the CLI to inform the debugger that a break point has been tripped. It has no other effect on the interpreter state.
The break instruction has the smallest possible instruction size so that code can be patched with a breakpoint with minimal disturbance to the surrounding code.
The break instruction may trap to a debugger, do nothing, or raise a security exception: the exact behavior is implementation-defined
Exceptions:
None.
Verifiability:
The break instruction is always verifiable.
|
Format |
Assembly Format |
Description |
|
39 <int32> |
brfalse target |
branch to target if value is zero (false) |
|
2C <int8> |
brfalse.s target |
branch to target if value is zero (false), short form |
|
39 <int32> |
brnull target |
branch to target if value is null (alias for brfalse) |
|
2C <int8> |
brnull.s target |
branch to target if value is null (alias for brfalse.s), short form |
|
39 <int32> |
brzero target |
branch to target if value is zero (alias for brfalse) |
|
2C <int8> |
brzero.s target |
branch to target if value is zero (alias for brfalse.s), short form |
Stack Transition:
, value ΰ
Description:
The brfalse instruction transfers control to target if value (of type int32, int64, object reference, managed pointer, unmanaged pointer or native int) is zero (false). If value is non-zero (true) execution continues at the next instruction.
Target is represented as a signed offset (4 bytes for brfalse, 1 byte for brfalse.s) from the beginning of the instruction following the current instruction.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee there is a minimum of one item on the stack.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
3A <int32> |
brtrue target |
branch to target if value is non-zero (true) |
|
2D <int8> |
brtrue.s target |
branch to target if value is non-zero (true), short form |
|
3A <int32> |
brinst target |
branch to target if value is a non-null object reference (alias for brtrue) |
|
2D <int8> |
brinst.s target |
branch to target if value is a non-null object reference, short form (alias for brtrue.s) |
Stack Transition:
, value ΰ
Description:
The brtrue instruction transfers control to target if value (of type native int) is nonzero (true). If value is zero (false) execution continues at the next instruction.
If the value is an object reference (type O) then brinst (an alias for brtrue) transfers control if it represents an instance of an object (i.e. isnt the null object reference, see ldnull).
Target is represented as a signed offset (4 bytes for brtrue, 1 byte for brtrue.s) from the beginning of the instruction following the current instruction.
If the target instruction has one or more prefix codes, control can only be transferred to the first of these prefixes.
Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction. (Such transfers are severely restricted and must use the leave instruction instead; see Partition I_alink_partitionI for details).
Exceptions:
None.
Verifiability:
Correct CIL must observe all of the control transfer rules specified above and must guarantee there is a minimum of one item on the stack.
In addition, verifiable code requires the type-consistency of the stack, locals and arguments for every possible path to the destination instruction. See Section 1.5_1.5_OperandTypeTable for more details.
|
Format |
Assembly Format |
Description |
|
28 <T> |
call method |
Call method described by method |
Stack Transition:
, arg1, arg2 argn ΰ , retVal (not always returned)
Description:
The call instruction calls the method indicated by the descriptor method. Method is a metadata token (either a methodref or methoddef (See Partition II_alink_partitionII) that indicates the method to call and the number, type, and order of the arguments that have been placed on the stack to be passed to that method as well as the calling convention to be used. See Partition I_alink_partitionI for a detailed description of the CIL calling sequence. The call instruction may be immediately preceded by a tail. prefix to specify that the current method state should be released before transferring control (see Section 2.1).
The metadata token carries sufficient information to determine whether the call is to a static method, an instance method, a virtual method, or a global function. In all of these cases the destination address is determined entirely from the metadata token (Contrast with the callvirt instruction for calling virtual methods, where the destination address also depends upon the runtime type of the instance reference pushed before the callvirt; see below).
If the method does not exist in the class specified by the metadata token, the base classes are searched to find the most derived class which defines the method and that method is called.
Rationale: This implementscall superclass behavior.
The arguments are placed on the stack in left-to-right order. That is, the first argument is computed and placed on the stack, then the second argument, etc. There are three important special cases:
1. Calls to an instance (or virtual, see below) method must push that instance referenc