A method is a header followed by a body in braces. The header (methodHead in the grammar of ilasm, MethodHeader in ECMA-335 II.15.4) is: .method, the method attributes, the calling convention, the return type (with optional parameter attributes and a marshalling clause), the name, optional generic parameters, the parameters in parentheses, and the implementation attributes. The body holds directives, labels and instructions.
.method public hidebysig static int32 Max(int32 a, int32 b) cil managed
{
.maxstack 2
ldarg.0
ldarg.1
ble.s second
ldarg.0
ret
second:
ldarg.1
ret
}
.maxstack, instructions and a label.Method attributes
The attributes before the calling convention are the words of the grammar rule methAttr; accessibility words replace one another (the last one wins), the others add up.
| Words | Meaning |
|---|---|
public, private, family, assembly, famandassem, famorassem, privatescope | accessibility: everyone; this type; this type and derived types; this assembly; both of the last two; either; and compiler-controlled (the standard calls it compilercontrolled; ilasm of .NET spells it privatescope, see the dialects) |
static | no this; not with virtual, final or newslot |
virtual, final, newslot, abstract, strict | a virtual method; one that cannot be overridden; one that gets a new slot in the table instead of overriding; one without a body that a derived class must provide (not with final or pinvokeimpl); one that can be overridden only where it is accessible |
hidebysig | hides base methods of the same signature (a hint for tools; the runtime ignores it) |
specialname, rtspecialname | the name means something to tools (get_X, op_Addition) or to the runtime (.ctor, .cctor); the grammar of ilasm accepts rtspecialname and sets no bit for it |
unmanagedexp, reqsecobj | exported to unmanaged code; requires a security object |
pinvokeimpl(…) | the body is native code in a library (see Marshalling and platform invoke) |
flags(n) | all attribute bits as a number |
Calling conventions
| Words | Meaning |
|---|---|
instance | a this pointer is passed first: for instance and virtual methods, and in the reference of every call to one |
instance explicit | the first type in the parameter list is the type of this |
default (or nothing) | the managed convention |
vararg | a variable number of arguments (below) |
unmanaged cdecl, unmanaged stdcall, unmanaged thiscall, unmanaged fastcall, unmanaged | unmanaged conventions, used by calli and function pointers; unmanaged alone is the platform convention |
callconv(n) | the calling-convention byte as a number |
In a call, ilasm adds instance to the reference by itself for callvirt and newobj (and warns that it did).
Names, parameters and generic methods
The name is a dotted name or one of .ctor (an instance constructor) and .cctor (the type initializer). Each parameter is [attributes] type [marshal(…)] [name], where an attribute is [in], [out], [opt] or a number; the attributes are metadata of the parameter, not part of the signature. A name lets the body say ldarg x, but the instruction encodes the index. Parameters are numbered from 0 and this is not counted; .param [n] counts from 1 and .param [0] is the return value. Generic methods list their parameters in angle brackets after the name, written like those of a type (Classes); inside, !!0 or !!T refers to them. A method of a generic class refers to the class parameters with !0.
.method public hidebysig static !!0 First<T>(!!0[] items) cil managed
{
.maxstack 8
ldarg.0
ldc.i4.0
ldelem !!0
ret
}
.method public hidebysig instance void Set([in] string s, [opt] int32 n) cil managed
{
.param [2] = int32(5)
ret
}
.param.Implementation attributes
After the closing parenthesis come the implementation attributes. The default is cil managed.
| Words | Meaning |
|---|---|
cil, native, optil, runtime | what the body is: CIL; native code (no IL body: used with platform invoke); optimised IL (ilasm accepts the word, the standard does not list it); supplied by the runtime (the methods of a delegate) |
managed, unmanaged | the method is managed or unmanaged (il is accepted as a synonym of cil) |
forwardref, internalcall | the body is provided by another module in a link step; the body is provided by the runtime itself (only for library code) |
preservesig | the signature is kept as written when the method is called through platform invoke or COM interop (an HRESULT result is not turned into an exception) |
synchronized | the whole body runs under a lock on this (or on the type, for a static method) |
noinlining, aggressiveinlining, nooptimization, aggressiveoptimization | advice to the compiler of native code: never inline; inline when possible; do not optimise; optimise at once |
async | sets the miAsync implementation flag (a recent addition to ilasm; the standard does not have it) |
flags(n) | all implementation bits as a number |
An abstract method, a method with runtime, native, forwardref or internalcall, and a pinvokeimpl method have no instructions; their braces are empty.
What a method body holds
| Item | Meaning |
|---|---|
| instruction | an opcode name and its operand (the instruction set) |
label: | a name for the address of the next instruction (Control flow) |
.maxstack n | the largest number of values on the evaluation stack at any point; 8 is the default when it is left out |
.locals [init] (type [name], …) | the local variables; with init they start at zero or null. A local may have an index in brackets before its type ([1] int32 V_1) |
.zeroinit | the method’s locals are initialised (the same flag as init) |
.entrypoint | the method that starts the program |
.param [n] [= value], .param type … | a default value for parameter n; attributes of a generic parameter |
.override Type::Name, .override method … | this method implements that one |
.custom …, .permission …, .permissionset … | attributes and security of the method; a .custom straight after an instruction that names a member or type belongs to that member or type (see Custom attributes) |
.data … | data |
.line, #line, .language | source information for the instructions that follow |
.export [n] [as name], .vtentry n : m | export number and vtable slot for unmanaged callers (Marshalling and platform invoke) |
.emitbyte n | puts one raw byte into the code (used for generating tests, not by compilers) |
.try …, { … } | exception blocks and scope blocks |
.entrypoint marks a static method, once per executable, that takes no arguments or an array of strings and returns void, int32 or unsigned int32; it cannot be in a generic class. A { … } block inside a body is a scope block: the lexical scope of the variables declared by a .locals inside it, for debuggers. .line and the rest are described in the lexical structure.
A vararg method is declared with the vararg convention and lists only its fixed parameters (no ... in the definition). Each call spells out the fixed parameters, then ..., then the types of the extra arguments, so a call is call vararg void Foo::M(int32, ..., string). The body reads the extra arguments through arglist and System.ArgIterator.
Properties and events
A property and an event are lists of methods that already exist; the directives only group them. Both can only be in a type.
.property instance string Name()
{
.custom instance void [mscorlib]System.ObsoleteAttribute::.ctor() = ( 01 00 00 00 )
.get instance string Foo::get_Name()
.set instance void Foo::set_Name(string)
}
.event [mscorlib]System.EventHandler Changed
{
.addon instance void Foo::add_Changed(class [mscorlib]System.EventHandler)
.removeon instance void Foo::remove_Changed(class [mscorlib]System.EventHandler)
}
| Declaration | Header | Body items |
|---|---|---|
.property | [specialname] [rtspecialname] callconv type name(parameters) [= default value]; the parameters are those of the getter | .get, .set, .other followed by a method reference; .custom, .line, .language, preprocessor lines |
.event | [specialname] [rtspecialname] [type] name | .addon, .removeon, .fire, .other followed by a method reference; .custom, .line, .language, preprocessor lines |
A method reference in these items may leave out the class (it then names a method of the current class); one getter and one setter at most per property.
Not described
Known, not yet covered
- Reference: the rules of the standard about what combinations are valid (II.15, II.17, II.18) are only partly summarised; the grammar is the subject. What the
asyncflag means to the runtime is not described: only the grammar rule that sets it was read, and no documentation of it.