This page follows the lexer of ilasm (yylex in grammar_after.cpp of the .NET runtime) and its keyword table il_kywd.h, which ildasm shares. The standard (ECMA-335 II.5.2 to II.5.6) describes the same tokens in fewer details. The lexer reads bytes; the parser then decides what a token means, and a few tokens (the hex bytes) only exist when the parser asks for them.
Blanks and comments
Space, tab, carriage return, line feed and form feed separate tokens and are otherwise ignored. A comment is // up to the end of the line, or /* … */, which does not nest (the first */ ends it). An unterminated /* is the error “bad comment”. Comments are skipped everywhere, also between the hex bytes of a list and inside a string’s + chain.
// a line comment
/* a block comment
over two lines */
.maxstack 8 /* between tokens */ nop
Identifiers, dotted names and quoted names
An ID starts with a letter or one of _ # $ @ (a ? starts one only if a name character follows it) and continues with letters, digits and _ ? $ @ and the grave accent `, which generic type names use (List`1). A dot inside an ID is allowed and makes it a dotted name (System.Collections.Generic.List`1); the lexer cuts a name before a dot that is followed by a digit (Foo.123 is Foo and then the number .123) and drops trailing dots. The grammar also joins Id . Id into one name when the pieces are separate tokens (dottedName).
The whole token, dots included, is looked up in the keyword table, case-sensitively. That is why ldarg.0, ldc.i4.1 and unaligned. (with its dot) are single keywords, and why Foo.nop is just a name. A keyword cannot be used as a name unless it is quoted.
A quoted name is any text between single quotes ('Weird Name', '<Module>', 'my method'). It is an Id like any other, so it can name a class, method, field, label or assembly, and it is the way to use a keyword or characters outside the ID set as a name. To put a single quote inside, use the octal escape \047. The grammar also lets these keywords be used as names without quotes: native cil optil managed unmanaged forwardref preservesig runtime internalcall synchronized noinlining aggressiveinlining nooptimization aggressiveoptimization async extended (the method implementation words, so that a method can be called runtime).
System.Console List`1 $Test @Foo? _X_ 'Weird Name' 'Odd\102Char'
ldarg.0 unaligned. Foo.nop 'ldarg.0'
Numbers
A number starts with a digit, or with . or - followed by a digit (a lone - is a plain character: -.5 is a minus and .5).
| Form | Example | Rule |
|---|---|---|
| decimal | 12, -12 | digits, with an optional leading - |
| hexadecimal | 0x1F, 0X1f | 0x or 0X and hex digits of either case |
| octal | 017 | a leading 0 that is not followed by a dot; the digits are read in base 8 |
| real | 1.5, .5, 1E10, 1.5e-3, 2. | read by the C library’s strtod when a decimal number is followed by . (not by a second .) or by E or e; there are no inf or nan words in ilasm of .NET |
An integer token is an int32 when its magnitude fits in 32 bits (a positive value up to 0xFFFFFFFF is kept as the 32 bits, so it can read as negative; a negative one may go down to −231) and an int64 otherwise. Where the grammar wants an Int64 an int32 is accepted too. There are no 8-bit or 16-bit constants: int8(…) and int16(…) take an int32 and keep its low bytes. A real number can also be written as the bits of the value: float32(int32) or float64(int64), so float64(0x7FF0000000000000) is positive infinity. The operand of ldc.r4 and ldc.r8 can also be a list of hex bytes in brackets: ildasm writes a decimal number when it reads back to the same bits and the list of bytes (the four or eight bytes of the value, low byte first) for infinities, NaNs and any value that does not round-trip.
Strings
A QSTRING is text between double quotes. A backslash starts an escape; the single-quoted names above follow the same rules.
| Escape | Meaning |
|---|---|
\t \n \r \b \f \v \a \? | tab, line feed, carriage return, backspace, form feed, vertical tab, bell, question mark |
\ooo | a byte given as three octal digits, when the first is 0 to 3 (\012 is a line feed, \040 a space); \0 alone is a zero byte |
\ then a line end | continues the string on the next line; the line end and the blanks that follow it are dropped |
\\, \", \' and any other character after a backslash | that character itself |
A string that reaches a line end without a backslash, or the end of the file, before its closing quote is the error “bad literal”. Adjacent strings are joined with +: "Hello " + "World" (the grammar’s compQstring), which is how a long string is split over lines. A string in an ldstr is stored as UTF-16 (it may hold any Unicode character, written as UTF-8 in the file); ldstr ansi("…") stores it as 8-bit text and ldstr bytearray (…) as the given bytes. A string used as the value of a field or a parameter is stored as UTF-16 too; in the text form of a custom attribute blob, string('…') is stored as UTF-8 with its length in front.
ldstr "Hello " + "World " +
"from CIL!"
ldstr "Hello World\
\040from CIL!"
ldstr ansi("plain bytes")
ldstr bytearray (48 00 69 00)
ldstr (the first two give the same string as in ECMA-335 II.5.2).Hex byte lists
Hexadecimal bytes appear in a few places: the contents of a custom attribute, public keys and hashes, security blobs, data items, ldstr bytearray, real-number operands and marshalling blobs. The parser switches the lexer into a special mode after an opening bracket that follows =, bytearray or ldc.r4/ldc.r8, and after marshal ({. In that mode each token is one or two hex digits with no 0x (0102 is two bytes), blanks and comments are skipped, and the closing ) (or } for the marshalling blob) ends the mode. Anything else is a syntax error.
.publickeytoken = (B7 7A 5C 56 19 34 E0 89)
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.data D_00002050 = bytearray (48 00 65 00 6C 00 6C 00 6F 00)
Punctuation
The characters { } ( ) [ ] < > , ; : = * & + - / ! are tokens of their own; :: separates a type from a member, and ... is the ellipsis (the vararg sentinel, and the bounds of an array dimension). ! followed by a number or a name is a type parameter (!0, !!0, !T); / separates an outer type from a nested one (Outer/Inner); ; by itself is an empty declaration; : follows a label and separates the numbers of .ver 1:0:0:0 and .line 12:5.
Labels and line information
A label is an Id followed by a colon at the start of a method body item: IL_0000:, loop:, 'my label':. Data labels (for .data) have no colon. .line and #line tell a debugger which source line, column and file the following instructions come from. The forms are .line line, .line line:col, .line line:col,endcol and .line line,endline:col,endcol, each with an optional single-quoted file name at the end; .line n "file" with a double-quoted name is accepted too. #line takes the same forms and, unlike .line, increases the line number by one for every source line it passes.
The keyword table
ilasm knows 260 words in addition to the instruction names: il_kywd.h lists them. All are lower case. The viewer colours the types, the literals and the directives as such and the rest as keywords; the words in the marshalling row are types inside marshal( … ) and keywords elsewhere.
| Group | Words |
|---|---|
| Types (23) | void bool char wchar int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float float32 float64 refany typedref object string native unsigned |
| Literals (4) | true false null nullref |
| Method names (2) | .ctor .cctor |
| Accessibility (8) | public private family assembly famandassem famorassem privatescope nested |
| Type kinds and layout (21) | class value valuetype byreflike interface enum extends implements sealed abstract auto sequential explicit extended ansi unicode autochar import serializable beforefieldinit windowsruntime |
| Member attributes (13) | static virtual strict final newslot hidebysig specialname rtspecialname initonly literal notserialized unmanagedexp reqsecobj |
| Method implementation (14) | il cil optil managed runtime forwardref preservesig internalcall synchronized noinlining aggressiveinlining nooptimization aggressiveoptimization async |
| Calling conventions and signatures (15) | instance default vararg unmanaged cdecl stdcall thiscall fastcall method callconv flags mdtoken pinned modreq modopt |
| Fields, properties and data (6) | field property init at tls bytearray |
| Parameters (3) | in out opt |
| Platform invoke (8) | pinvokeimpl nomangle lasterr winapi bestfit charmaperror on off |
| Exception handling (6) | catch filter finally fault handler to |
| Security actions (15) | request demand assert deny permitonly linkcheck inheritcheck reqmin reqopt reqrefuse prejitgrant prejitdeny noncasdemand noncaslinkdemand noncasinheritance |
| Manifest (13) | extern forwarder nometadata retargetable noplatform legacy library x86 amd64 arm arm64 algorithm alignment |
| Marshalling, always keywords (5) | marshal custom sysstring fixed iidparam |
| Marshalling and variant types (35) | variant currency syschar decimal date bstr tbstr lpstr lpwstr lptstr objectref iunknown idispatch struct safearray byvalstr lpvoid any array lpstruct error hresult carray userdefined record filetime blob stream storage streamed_object stored_object blob_object cf clsid vector |
| Vtable fixups (3) | fromunmanaged retainappdomain callmostderived |
| Others (4) | as with type constraint |
| Directives (62) | Words |
|---|---|
| Assembly, module and manifest | .assembly .module .file .hash .publickey .publickeytoken .ver .locale .mresource .manifestres .export .vtfixup .vtentry .vtable |
| Types and members | .class .namespace .method .field .property .event .get .set .addon .removeon .fire .other .override .interfaceimpl .size .pack .this .base .nester .param |
| Method body | .maxstack .locals .entrypoint .zeroinit .emitbyte .try |
| Data | .data |
| Attributes and security | .custom .permission .permissionset |
| Debug information | .line .language #line |
| PE and CLR headers | .subsystem .corflags .imagebase .stackreserve |
| Conveniences | .typedef .template .typelist .mscorlib |
| Preprocessor | #define #undef #ifdef #ifndef #else #endif #include |
(alignment is the second word of .file alignment. The list in il_kywd.h ends with a sentinel, ^THE_END^, which is not a word.) The instruction names (the table of the instruction set) are keywords in the same table. .algorithm and similar dotted words of Mono’s ilasm are not in this table (see the dialects).
How the viewer colours each token
| Token | Colour class |
|---|---|
comments (// and /* */) | comment |
strings in double quotes; single-quoted text that is a value (string('…'), file names of .line) | string |
| numbers, hex bytes | number |
keywords; branches, ret, throw and the other control-flow instructions | keyword |
the other instructions; method names (Main, WriteLine, .ctor) | function |
built-in types, class names, type parameters (!0, !!T, the generic parameters of a header) | type |
true, false, null, nullref, and names defined by #define | literal |
| directives and preprocessor words | directive |
| field, parameter, local, assembly and module names, data labels | variable |
| labels (definitions and branch targets) | tag |
What a plain name is depends on its place: after :: it is a method (if a ( follows) or a field; after class, valuetype, extends, implements, catch or an instruction that takes a type it is a class; after a complete type it is a variable or, before (, a method; before : or after a branch it is a label; after .assembly, .module, as or at it is a declared name.
Not described
Known, not yet covered
- Reference: how the parser of Mono’s ilasm differs in numbers and strings (its tokenizer was read for the keyword differences only); see the dialects.