diff --git a/eng/Subsets.props b/eng/Subsets.props
index 64079deecc48cb..2a0aee52b8e152 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -254,6 +254,7 @@
+
@@ -510,7 +511,7 @@
-
+
+
+
+
+
+
diff --git a/eng/Versions.props b/eng/Versions.props
index ad0283439a0745..93a6e474f80463 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -121,6 +121,8 @@
17.11.48
7.0.412701
6.0
+ 4.6.6
+ 4.6.6
1.6.0
17.4.0-preview-20220707-01
diff --git a/eng/pipelines/coreclr/ilasm.yml b/eng/pipelines/coreclr/ilasm.yml
index 47477272843934..dc375ecff9b0c4 100644
--- a/eng/pipelines/coreclr/ilasm.yml
+++ b/eng/pipelines/coreclr/ilasm.yml
@@ -8,6 +8,7 @@ pr:
include:
- src/coreclr/ilasm/*
- src/coreclr/ildasm/*
+ - src/tools/ilasm/*
schedules:
- cron: "0 19 * * 6"
@@ -32,3 +33,20 @@ extends:
- windows_x86
- windows_arm64
testGroup: ilasm
+
+ stages:
+ - stage: Managed_ILAsm
+ displayName: Managed IL Assembler Tests
+ dependsOn: []
+ jobs:
+ - template: /eng/pipelines/common/platform-matrix.yml
+ parameters:
+ jobTemplate: /eng/pipelines/common/global-build-job.yml
+ buildConfig: Debug
+ platforms:
+ - windows_x64
+ jobParameters:
+ nameSuffix: ILAsm
+ buildArgs: -s tools.ilasm -c $(_BuildConfig) -test
+ timeoutInMinutes: 120
+ condition: and(succeeded(), ne(variables['Build.Reason'], 'Schedule'))
diff --git a/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml b/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml
index 1e9c70114c5614..41bea22cbb7f11 100644
--- a/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml
+++ b/eng/pipelines/coreclr/templates/jit-outerloop-pipeline.yml
@@ -4,6 +4,9 @@ parameters:
- name: testGroup
type: string
default: outerloop
+ - name: stages
+ type: stageList
+ default: []
extends:
template: /eng/pipelines/common/templates/pipeline-with-resources.yml
@@ -55,3 +58,5 @@ extends:
testGroup: ${{ parameters.testGroup }}
liveLibrariesBuildConfig: Release
unifiedArtifactsName: BuildArtifacts_$(osGroup)$(osSubgroup)_$(archType)_$(_BuildConfig)
+ - ${{ each stage in parameters.stages }}:
+ - ${{ stage }}
diff --git a/src/tools/ilasm/ilasm.slnx b/src/tools/ilasm/ilasm.slnx
new file mode 100644
index 00000000000000..38a4c5d535964e
--- /dev/null
+++ b/src/tools/ilasm/ilasm.slnx
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/tools/ilasm/src/ILAssembler/BlobBuilderExtensions.cs b/src/tools/ilasm/src/ILAssembler/BlobBuilderExtensions.cs
new file mode 100644
index 00000000000000..6519c50ab2dabd
--- /dev/null
+++ b/src/tools/ilasm/src/ILAssembler/BlobBuilderExtensions.cs
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Text;
+
+namespace ILAssembler
+{
+ internal static class BlobBuilderExtensions
+ {
+ public static BlobBuilder SerializeSequence(this ImmutableArray sequence)
+ {
+ BlobBuilder builder = new BlobBuilder();
+ builder.WriteSerializedSequence(sequence);
+ return builder;
+ }
+
+ public static void WriteSerializedSequence(this BlobBuilder writer, ImmutableArray sequence)
+ {
+ foreach (T value in sequence)
+ {
+ WriteSerializedValue(writer, value);
+ }
+ }
+
+ public static void WriteSerializedValue(this BlobBuilder writer, T value)
+ {
+ if (typeof(T) == typeof(bool))
+ {
+ writer.WriteBoolean((bool)(object)value!);
+ }
+ else if (typeof(T) == typeof(int))
+ {
+ writer.WriteInt32((int)(object)value!);
+ }
+ else if (typeof(T) == typeof(byte))
+ {
+ writer.WriteByte((byte)(object)value!);
+ }
+ else if (typeof(T) == typeof(char))
+ {
+ writer.WriteUInt16((char)(object)value!);
+ }
+ else if (typeof(T) == typeof(double))
+ {
+ writer.WriteDouble((double)(object)value!);
+ }
+ else if (typeof(T) == typeof(short))
+ {
+ writer.WriteInt16((short)(object)value!);
+ }
+ else if (typeof(T) == typeof(long))
+ {
+ writer.WriteInt64((long)(object)value!);
+ }
+ else if (typeof(T) == typeof(sbyte))
+ {
+ writer.WriteSByte((sbyte)(object)value!);
+ }
+ else if (typeof(T) == typeof(float))
+ {
+ writer.WriteSingle((float)(object)value!);
+ }
+ else if (typeof(T) == typeof(ushort))
+ {
+ writer.WriteUInt16((ushort)(object)value!);
+ }
+ else if (typeof(T) == typeof(uint))
+ {
+ writer.WriteUInt32((uint)(object)value!);
+ }
+ else if (typeof(T) == typeof(ulong))
+ {
+ writer.WriteUInt64((ulong)(object)value!);
+ }
+ else if (typeof(T) == typeof(string))
+ {
+ writer.WriteSerializedString((string?)(object?)value);
+ }
+ }
+
+ public static void WriteTypeEntity(this BlobBuilder builder, EntityRegistry.TypeEntity entity)
+ {
+ if (entity is EntityRegistry.FakeTypeEntity fakeEntity)
+ {
+ builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(fakeEntity.TypeSignatureHandle));
+ }
+ else
+ {
+ builder.WriteCompressedInteger(CodedIndex.TypeDefOrRefOrSpec(entity.Handle));
+ }
+ }
+ }
+}
diff --git a/src/tools/ilasm/src/ILAssembler/CIL.g4 b/src/tools/ilasm/src/ILAssembler/CIL.g4
new file mode 100644
index 00000000000000..0ed37a1baee599
--- /dev/null
+++ b/src/tools/ilasm/src/ILAssembler/CIL.g4
@@ -0,0 +1,1152 @@
+/*
+Licensed to the .NET Foundation under one or more agreements.
+The .NET Foundation licenses this file to you under the MIT license.
+*/
+
+grammar CIL;
+
+import Instructions;
+
+tokens { IncludedFileEof, SyntheticIncludedFileEof }
+
+INT32: '-'? ('0x' [0-9A-Fa-f]+ | [0-9]+);
+INT64: '-'? ('0x' [0-9A-Fa-f]+ | [0-9]+);
+FLOAT64: '-'? [0-9]+ ('.' [0-9]+ | [eE] '-'? [0-9]+);
+HEXBYTE: [0-9A-Fa-f][0-9A-Fa-f];
+DCOLON: '::';
+ELLIPSIS: '..';
+NULL: 'null';
+NULLREF: 'nullref';
+HASH: '.hash';
+CHAR: 'char';
+STRING: 'string';
+BOOL: 'bool';
+INT8: 'int8';
+INT16: 'int16';
+INT32_: 'int32';
+INT64_: 'int64';
+FLOAT32: 'float32';
+FLOAT64_: 'float64';
+fragment UNSIGNED: 'unsigned';
+UINT8: 'uint8' | (UNSIGNED INT8);
+UINT16: 'uint16' | (UNSIGNED INT16);
+UINT32: 'uint32' | (UNSIGNED INT32_);
+UINT64: 'uint64' | (UNSIGNED INT64_);
+INT: 'int';
+UINT: 'uint' | (UNSIGNED 'int');
+TYPE: 'type';
+OBJECT: 'object';
+MODULE: '.module';
+VALUE: 'value';
+VALUETYPE: 'valuetype';
+VOID: 'void';
+ENUM: 'enum';
+CUSTOM: 'custom';
+FIXED: 'fixed';
+SYSSTRING: 'systring';
+ARRAY: 'array';
+VARIANT: 'variant';
+CURRENCY: 'currency';
+SYSCHAR: 'syschar';
+ERROR: 'error';
+DECIMAL: 'decimal';
+DATE: 'date';
+BSTR: 'bstr';
+LPSTR: 'lpstr';
+LPWSTR: 'lpwstr';
+LPTSTR: 'lptstr';
+OBJECTREF: 'objectref';
+IUNKNOWN: 'iunknown';
+IDISPATCH: 'idispatch';
+STRUCT: 'struct';
+INTERFACE: 'interface';
+SAFEARRAY: 'safearray';
+NESTEDSTRUCT: 'nested' STRUCT;
+VARIANTBOOL: VARIANT BOOL;
+BYVALSTR: 'byvalstr';
+ANSI: 'ansi';
+ANSIBSTR: ANSI BSTR;
+TBSTR: 'tbstr';
+METHOD: 'method';
+ANY: 'any';
+LPSTRUCT: 'lpstruct';
+VECTOR: 'vector';
+HRESULT: 'hresult';
+CARRAY: 'carray';
+USERDEFINED: 'userdefined';
+RECORD: 'record';
+FILETIME: 'filetime';
+BLOB: 'blob';
+STREAM: 'stream';
+STORAGE: 'storage';
+STREAMED_OBJECT: 'streamed_object';
+STORED_OBJECT: 'stored_object';
+BLOB_OBJECT: 'blob_object';
+CF: 'cf';
+CLSID: 'clsid';
+INSTANCE: 'instance';
+EXPLICIT: 'explicit';
+DEFAULT: 'default';
+VARARG: 'vararg';
+UNMANAGED: 'unmanaged';
+CDECL: 'cdecl';
+STDCALL: 'stdcall';
+THISCALL: 'thiscall';
+FASTCALL: 'fastcall';
+TYPE_PARAMETER: '!';
+METHOD_TYPE_PARAMETER: '!' '!';
+TYPEDREF: 'typedref';
+NATIVE_INT: 'native' 'int';
+NATIVE_UINT: ('native' 'unsigned' 'int') | ('native' 'uint');
+PARAM: '.param';
+CONSTRAINT: 'constraint';
+
+THIS: '.this';
+BASE: '.base';
+NESTER: '.nester';
+REF: '&';
+ARRAY_TYPE_NO_BOUNDS: '[' ']';
+PTR: '*';
+
+QSTRING: '"' (~('"' | '\\') | '\\' ('"' | '\\'))* '"';
+SQSTRING: '\'' (~('\'' | '\\') | '\\' ('\'' | '\\'))* '\'';
+DOT: '.';
+PLUS: '+';
+
+PP_DEFINE: '#define';
+PP_UNDEF: '#undef';
+PP_IFDEF: '#ifdef';
+PP_IFNDEF: '#ifndef';
+PP_ELSE: '#else';
+PP_ENDIF: '#endif';
+PP_INCLUDE: '#include';
+MRESOURCE: '.mresource';
+
+// ID needs to be last to ensure it doesn't take priority over other token types
+fragment IDSTART: [A-Za-z_#$@];
+fragment IDCONT: [A-Za-z0-9_#?$@`];
+DOTTEDNAME: (ID DOT)+ ID;
+ID: IDSTART IDCONT*;
+
+id:
+ ID
+ | 'native'
+ | 'cil'
+ | 'optil'
+ | 'managed'
+ | 'unmanaged'
+ | 'forwardref'
+ | 'preservesig'
+ | 'runtime'
+ | 'internalcall'
+ | 'synchronized'
+ | 'noinlining'
+ | 'aggressiveinlining'
+ | 'nooptimization'
+ | 'aggressiveoptimization'
+ | 'async'
+ | 'extended'
+ | SQSTRING;
+dottedName: DOTTEDNAME | ((ID '.')* ID);
+compQstring: (QSTRING PLUS)* QSTRING;
+
+
+WS: [ \t\r\n] -> skip;
+SINGLE_LINE_COMMENT: '//' ~[\r\n]* -> skip;
+COMMENT: '/*' .*? '*/' -> skip;
+
+decls: decl+;
+
+decl:
+ classHead '{' classDecls '}'
+ | nameSpaceHead '{' decls '}'
+ | methodHead '{' methodDecls '}'
+ | fieldDecl
+ | dataDecl
+ | vtableDecl
+ | vtfixupDecl
+ | extSourceSpec
+ | fileDecl
+ | assemblyBlock
+ | assemblyRefHead '{' assemblyRefDecls '}'
+ | exptypeHead '{' exptypeDecls '}'
+ | manifestResHead '{' manifestResDecls '}'
+ | moduleHead
+ | secDecl
+ | customAttrDecl
+ | subsystem
+ | corflags
+ | alignment
+ | imagebase
+ | stackreserve
+ | languageDecl
+ | typedefDecl
+ | compControl
+ | typelist
+ | mscorlib;
+
+subsystem: '.subsystem' int32;
+
+corflags: '.corflags' int32;
+
+alignment: '.file' 'alignment' int32;
+
+imagebase: '.imagebase' int64;
+
+stackreserve: '.stackreserve' int64;
+
+assemblyBlock:
+ '.assembly' asmAttr dottedName '{' assemblyDecls '}';
+
+mscorlib: '.mscorlib';
+
+languageDecl:
+ '.language' SQSTRING
+ | '.language' SQSTRING ',' SQSTRING
+ | '.language' SQSTRING ',' SQSTRING ',' SQSTRING;
+
+typelist: '.typelist' '{' (className)* '}';
+
+int32: INT32;
+int64: INT64 | INT32;
+
+float64:
+ FLOAT64
+ | FLOAT32 '(' int32 ')'
+ | FLOAT64_ '(' int64 ')';
+
+intOrWildcard: int32 | PTR;
+
+/* This is handled in the PreprocessedTokenSource lexer. We have this in the grammar just for completeness */
+compControl:
+ PP_DEFINE ID
+ | PP_DEFINE ID QSTRING
+ | PP_UNDEF ID
+ | PP_IFDEF ID
+ | PP_IFNDEF ID
+ | PP_ELSE
+ | PP_ENDIF
+ | PP_INCLUDE QSTRING
+ | ';';
+
+
+/* Aliasing of types, type specs, methods, fields and custom attributes */
+typedefDecl:
+ '.typedef' type 'as' dottedName
+ | '.typedef' className 'as' dottedName
+ | '.typedef' memberRef 'as' dottedName
+ | '.typedef' customDescr 'as' dottedName
+ | '.typedef' customDescrWithOwner 'as' dottedName;
+
+/* Custom attribute declarations */
+customDescr:
+ '.custom' customType
+ | '.custom' customType '=' compQstring
+ | '.custom' customType '=' '{' customBlobDescr '}'
+ | '.custom' customType '=' '(' bytes ')';
+
+customDescrWithOwner:
+ '.custom' '(' ownerType ')' customType
+ | '.custom' '(' ownerType ')' customType '=' compQstring
+ | '.custom' '(' ownerType ')' customType '=' '{' customBlobDescr '}'
+ | '.custom' '(' ownerType ')' customType '=' '(' bytes ')';
+
+customType: methodRef;
+
+ownerType: typeSpec | memberRef;
+
+/* Verbal description of custom attribute initialization blob */
+customBlobDescr: customBlobArgs customBlobNVPairs;
+
+customBlobArgs: (serInit | compControl)*;
+
+customBlobNVPairs: (
+ fieldOrProp serializType dottedName '=' serInit
+ | compControl
+ )*;
+
+fieldOrProp: 'field' | 'property';
+
+serializType: serializTypeElement (ARRAY_TYPE_NO_BOUNDS)?;
+
+serializTypeElement:
+ simpleType
+ | dottedName /* typedef */
+ | TYPE
+ | OBJECT
+ | ENUM 'class' SQSTRING
+ | ENUM className;
+
+/* Module declaration */
+moduleHead:
+ MODULE
+ | MODULE dottedName
+ | MODULE 'extern' dottedName;
+
+/* VTable Fixup table declaration */
+vtfixupDecl: '.vtfixup' '[' int32 ']' vtfixupAttr 'at' id;
+
+vtfixupAttr:
+ /* EMPTY */
+ | vtfixupAttr INT32_
+ | vtfixupAttr INT64_
+ | vtfixupAttr 'fromunmanaged'
+ | vtfixupAttr 'callmostderived'
+ | vtfixupAttr 'retainappdomain';
+
+vtableDecl: '.vtable' '=' '(' bytes ')' /* deprecated */;
+
+/* Namespace and class declaration */
+nameSpaceHead: '.namespace' dottedName;
+
+classHead:
+ '.class' classAttr* dottedName typarsClause extendsClause implClause;
+
+
+classAttr:
+ 'public'
+ | 'private'
+ | VALUE
+ | ENUM
+ | 'interface'
+ | 'sealed'
+ | 'abstract'
+ | 'auto'
+ | 'sequential'
+ | 'explicit'
+ | 'extended'
+ | ANSI
+ | 'unicode'
+ | 'autochar'
+ | 'import'
+ | 'serializable'
+ | 'windowsruntime'
+ | 'nested' 'public'
+ | 'nested' 'private'
+ | 'nested' 'family'
+ | 'nested' 'assembly'
+ | 'nested' 'famandassem'
+ | 'nested' 'famorassem'
+ | 'beforefieldinit'
+ | 'specialname'
+ | 'rtspecialname'
+ | 'flags' '(' int32 ')';
+
+extendsClause: /* EMPTY */ | 'extends' typeSpec;
+
+implClause: /* EMPTY */ | 'implements' implList;
+
+classDecls: classDecl*;
+
+implList: (typeSpec ',')* typeSpec;
+
+/* External source declarations */
+esHead: '.line' | '#line';
+
+extSourceSpec:
+ esHead int32 SQSTRING
+ | esHead int32
+ | esHead int32 ':' int32 SQSTRING
+ | esHead int32 ':' int32
+ | esHead int32 ':' int32 ',' int32 SQSTRING
+ | esHead int32 ':' int32 ',' int32
+ | esHead int32 ',' int32 ':' int32 SQSTRING
+ | esHead int32 ',' int32 ':' int32
+ | esHead int32 ',' int32 ':' int32 ',' int32 SQSTRING
+ | esHead int32 ',' int32 ':' int32 ',' int32
+ | esHead int32 QSTRING;
+
+/* Manifest declarations */
+fileDecl:
+ '.file' fileAttr* dottedName fileEntry HASH '=' '(' bytes ')' fileEntry
+ | '.file' fileAttr* dottedName fileEntry;
+
+fileAttr: 'nometadata';
+
+fileEntry: /* EMPTY */ | '.entrypoint';
+
+asmAttrAny:
+ 'retargetable'
+ | 'windowsruntime'
+ | 'noplatform'
+ | 'legacy library'
+ | 'cil'
+ | 'x86'
+ | 'amd64'
+ | 'arm'
+ | 'arm64';
+
+asmAttr: asmAttrAny*;
+
+/* IL instructions and associated definitions */
+instr_none: INSTR_NONE;
+
+instr_var: INSTR_VAR;
+
+instr_i: INSTR_I;
+
+instr_i8: INSTR_I8;
+
+instr_r: INSTR_R;
+
+instr_brtarget: INSTR_BRTARGET;
+
+instr_method: INSTR_METHOD;
+
+instr_field: INSTR_FIELD;
+
+instr_type: INSTR_TYPE;
+
+instr_string: INSTR_STRING;
+
+instr_sig: INSTR_SIG;
+
+instr_tok: INSTR_TOK;
+
+instr_switch: INSTR_SWITCH;
+
+instr:
+ instr_none
+ | instr_var int32
+ | instr_var id
+ | instr_i int32
+ | instr_i8 int64
+ | instr_r float64
+ | instr_r int64
+ | instr_r '(' bytes ')'
+ | instr_brtarget int32
+ | instr_brtarget id
+ | instr_method methodRef
+ | instr_field fieldRef
+ | instr_field mdtoken
+ | instr_type typeSpec
+ | instr_string compQstring
+ | instr_string ANSI '(' compQstring ')'
+ | instr_string 'bytearray' '(' bytes ')'
+ | instr_sig callConv type sigArgs
+ | instr_tok ownerType /* ownerType ::= memberRef | typeSpec */
+ | instr_switch '(' labels ')';
+
+labels:
+ /* empty */
+ | (id | int32 ',')* (id | int32);
+
+typeArgs: '<' (type ',')* type '>';
+
+bounds: '[' (bound ',')* bound ']';
+
+sigArgs: '(' (sigArg ',')* sigArg ')' | '()';
+
+sigArg:
+ ELLIPSIS
+ | paramAttr type marshalClause
+ | paramAttr type marshalClause id;
+
+/* Class referencing */
+
+className:
+ '[' dottedName ']' slashedName
+ | '[' mdtoken ']' slashedName
+ | '[' PTR ']' slashedName
+ | '[' MODULE dottedName ']' slashedName
+ | slashedName
+ | mdtoken
+ | THIS
+ | BASE
+ | NESTER;
+
+slashedName: (dottedName '/')* dottedName;
+
+assemblyDecls: assemblyDecl*;
+
+assemblyDecl: (HASH 'algorithm' int32) | secDecl | asmOrRefDecl;
+
+typeSpec:
+ className
+ | '[' dottedName ']'
+ | '[' MODULE dottedName ']'
+ | type;
+
+/* Native types for marshaling signatures */
+nativeType:
+ /* EMPTY */
+ | nativeTypeElement nativeTypeArrayPointerInfo*;
+
+nativeTypeArrayPointerInfo:
+ PTR # PointerNativeType
+ | ARRAY_TYPE_NO_BOUNDS # PointerArrayTypeNoSizeData
+ | '[' int32 ']' # PointerArrayTypeSize
+ | '[' int32 PLUS int32 ']' # PointerArrayTypeSizeParamIndex
+ | '[' PLUS int32 ']' # PointerArrayTypeParamIndex
+ ;
+
+nativeTypeElement:
+ /* EMPTY */
+ | marshalType=CUSTOM '(' compQstring ',' compQstring ',' compQstring ',' compQstring ')'
+ | marshalType=CUSTOM '(' compQstring ',' compQstring ')'
+ | FIXED marshalType=SYSSTRING '[' int32 ']'
+ | FIXED marshalType=ARRAY '[' int32 ']' nativeType
+ | marshalType=VARIANT
+ | marshalType=CURRENCY
+ | marshalType=SYSCHAR
+ | marshalType=VOID
+ | marshalType=BOOL
+ | marshalType=INT8
+ | marshalType=INT16
+ | marshalType=INT32_
+ | marshalType=INT64_
+ | marshalType=FLOAT32
+ | marshalType=FLOAT64_
+ | marshalType=ERROR
+ | marshalType=UINT8
+ | marshalType=UINT16
+ | marshalType=UINT32
+ | marshalType=UINT64
+ | marshalType=DECIMAL
+ | marshalType=DATE
+ | marshalType=BSTR
+ | marshalType=LPSTR
+ | marshalType=LPWSTR
+ | marshalType=LPTSTR
+ | marshalType=OBJECTREF
+ | marshalType=IUNKNOWN iidParamIndex
+ | marshalType=IDISPATCH iidParamIndex
+ | marshalType=STRUCT
+ | marshalType=INTERFACE iidParamIndex
+ | marshalType=SAFEARRAY variantType
+ | marshalType=SAFEARRAY variantType ',' compQstring
+ | marshalType=INT
+ | marshalType=UINT
+ | marshalType=NESTEDSTRUCT
+ | marshalType=BYVALSTR
+ | marshalType=ANSIBSTR
+ | marshalType=TBSTR
+ | marshalType=VARIANTBOOL
+ | marshalType=METHOD
+ | marshalType=LPSTRUCT
+ | 'as' marshalType=ANY
+ | dottedName /* typedef */;
+
+iidParamIndex: /* EMPTY */ | '(' 'iidparam' '=' int32 ')';
+
+variantType:
+ /*EMPTY */
+ | variantTypeElement (ARRAY_TYPE_NO_BOUNDS | VECTOR | REF)*;
+
+variantTypeElement:
+ NULL
+ | VARIANT
+ | CURRENCY
+ | VOID
+ | BOOL
+ | INT8
+ | INT16
+ | INT32_
+ | INT64_
+ | FLOAT32
+ | FLOAT64_
+ | UINT8
+ | UINT16
+ | UINT32
+ | UINT64
+ | PTR
+ | DECIMAL
+ | DATE
+ | BSTR
+ | LPSTR
+ | LPWSTR
+ | IUNKNOWN
+ | IDISPATCH
+ | SAFEARRAY
+ | INT
+ | UINT
+ | ERROR
+ | HRESULT
+ | CARRAY
+ | USERDEFINED
+ | RECORD
+ | FILETIME
+ | BLOB
+ | STREAM
+ | STORAGE
+ | STREAMED_OBJECT
+ | STORED_OBJECT
+ | BLOB_OBJECT
+ | CF
+ | CLSID;
+
+/* Managed types for signatures */
+type: elementType typeModifiers*;
+
+typeModifiers:
+ '[' ']' # SZArrayModifier
+ | bounds # ArrayModifier
+ | REF # ByRefModifier
+ | PTR # PtrModifier
+ | 'pinned' # PinnedModifier
+ | 'modreq' '(' typeSpec ')' # RequiredModifier
+ | 'modopt' '(' typeSpec ')' # OptionalModifier
+ | typeArgs # GenericArgumentsModifier;
+
+elementType:
+ 'class' className
+ | OBJECT
+ | VALUE 'class' className
+ | VALUETYPE className
+ | 'method' callConv type PTR sigArgs
+ | METHOD_TYPE_PARAMETER int32
+ | TYPE_PARAMETER int32
+ | METHOD_TYPE_PARAMETER dottedName
+ | TYPE_PARAMETER dottedName
+ | TYPEDREF
+ | VOID
+ | NATIVE_INT
+ | NATIVE_UINT
+ | simpleType
+ | dottedName /* typedef */
+ | ELLIPSIS type;
+
+simpleType:
+ CHAR
+ | STRING
+ | BOOL
+ | INT8
+ | INT16
+ | INT32_
+ | INT64_
+ | FLOAT32
+ | FLOAT64_
+ | UINT8
+ | UINT16
+ | UINT32
+ | UINT64;
+
+bound:
+ | ELLIPSIS
+ | int32
+ | int32 ELLIPSIS int32
+ | int32 ELLIPSIS;
+
+/* Security declarations */
+PERMISSION: '.permission';
+PERMISSIONSET: '.permissionset';
+
+secDecl:
+ PERMISSION secAction typeSpec '(' nameValPairs ')'
+ | PERMISSION secAction typeSpec '=' '{' customBlobDescr '}'
+ | PERMISSION secAction typeSpec
+ | PERMISSIONSET secAction '=' 'bytearray'? '(' bytes ')'
+ | PERMISSIONSET secAction compQstring
+ | PERMISSIONSET secAction '=' '{' secAttrSetBlob '}';
+
+secAttrSetBlob: | (secAttrBlob ',')* secAttrBlob;
+
+secAttrBlob:
+ typeSpec '=' '{' customBlobNVPairs '}'
+ | 'class' SQSTRING '=' '{' customBlobNVPairs '}';
+
+nameValPairs: (nameValPair ',')* nameValPair;
+
+nameValPair: compQstring '=' caValue;
+
+truefalse: 'true' | 'false';
+
+caValue:
+ truefalse
+ | int32
+ | INT32_ '(' int32 ')'
+ | compQstring
+ | className '(' INT8 ':' int32 ')'
+ | className '(' INT16 ':' int32 ')'
+ | className '(' INT32_ ':' int32 ')'
+ | className '(' int32 ')';
+
+secAction:
+ 'request'
+ | 'demand'
+ | 'assert'
+ | 'deny'
+ | 'permitonly'
+ | 'linkcheck'
+ | 'inheritcheck'
+ | 'reqmin'
+ | 'reqopt'
+ | 'reqrefuse'
+ | 'prejitgrant'
+ | 'prejitdeny'
+ | 'noncasdemand'
+ | 'noncaslinkdemand'
+ | 'noncasinheritance';
+
+/* Method referencing */
+methodRef:
+ callConv type typeSpec '::' methodName typeArgs? sigArgs
+ | callConv type typeSpec '::' methodName genArityNotEmpty sigArgs
+ | callConv type methodName typeArgs? sigArgs
+ | callConv type methodName genArityNotEmpty sigArgs
+ | mdtoken
+ | dottedName /* typeDef */;
+
+callConv:
+ INSTANCE callConv
+ | EXPLICIT callConv
+ | callKind
+ | 'callconv' '(' int32 ')';
+
+callKind:
+ /* EMPTY */
+ | DEFAULT
+ | VARARG
+ | UNMANAGED CDECL
+ | UNMANAGED STDCALL
+ | UNMANAGED THISCALL
+ | UNMANAGED FASTCALL
+ | UNMANAGED;
+
+mdtoken: 'mdtoken' '(' int32 ')';
+
+memberRef:
+ 'method' methodRef
+ | 'field' fieldRef
+ | mdtoken;
+
+fieldRef:
+ type typeSpec '::' dottedName
+ | type dottedName
+ | dottedName // typedef
+ ;
+
+/* Generic type parameters declaration */
+typeList: (typeSpec ',')* typeSpec;
+
+typarsClause: /* EMPTY */ | '<' typars '>';
+
+typarAttrib:
+ covariant = PLUS
+ | contravariant = '-'
+ | class = 'class'
+ | valuetype = VALUETYPE
+ | byrefLike = 'byreflike'
+ | ctor = '.ctor'
+ | 'flags' '(' flags = int32 ')';
+
+typarAttribs: typarAttrib*;
+
+typar: typarAttribs tyBound? dottedName;
+
+typars: (typar ',')* typar;
+
+tyBound: '(' typeList ')';
+
+genArity: /* EMPTY */ | genArityNotEmpty;
+
+genArityNotEmpty: '<' '[' int32 ']' '>';
+
+/* Class body declarations */
+classDecl:
+ methodHead '{' methodDecls '}'
+ | classHead '{' classDecls '}'
+ | eventHead '{' eventDecls '}'
+ | propHead '{' propDecls '}'
+ | fieldDecl
+ | dataDecl
+ | secDecl
+ | extSourceSpec
+ | customAttrDecl
+ | '.size' int32
+ | '.pack' int32
+ | exportHead '{' exptypeDecls '}'
+ | OVERRIDE typeSpec '::' methodName 'with' callConv type typeSpec '::' methodName sigArgs
+ | OVERRIDE 'method' callConv type typeSpec '::' methodName genArity sigArgs 'with' 'method'
+ callConv type typeSpec '::' methodName genArity sigArgs
+ | languageDecl
+ | compControl
+ | PARAM TYPE '[' int32 ']' customAttrDecl*
+ | PARAM TYPE dottedName customAttrDecl*
+ | PARAM CONSTRAINT '[' int32 ']' ',' typeSpec customAttrDecl*
+ | PARAM CONSTRAINT dottedName ',' typeSpec customAttrDecl*
+ | '.interfaceimpl' TYPE typeSpec customDescr;
+
+/* Field declaration */
+fieldDecl:
+ '.field' repeatOpt (fieldAttr | 'marshal' '(' marshalBlob ')')* type dottedName atOpt initOpt;
+
+fieldAttr:
+ 'static'
+ | 'public'
+ | 'private'
+ | 'family'
+ | 'initonly'
+ | 'rtspecialname'
+ | 'specialname'
+ | 'assembly'
+ | 'famandassem'
+ | 'famorassem'
+ | 'privatescope'
+ | 'literal'
+ | 'notserialized'
+ | 'flags' '(' int32 ')';
+
+atOpt: /* EMPTY */ | 'at' id;
+
+initOpt: /* EMPTY */ | '=' fieldInit;
+
+repeatOpt: /* EMPTY */ | '[' int32 ']';
+
+/* Event declaration */
+eventHead:
+ '.event' eventAttr* typeSpec dottedName
+ | '.event' eventAttr* dottedName;
+
+eventAttr:
+ 'rtspecialname'
+ | 'specialname';
+
+eventDecls: eventDecl*;
+
+eventDecl:
+ '.addon' methodRef
+ | '.removeon' methodRef
+ | '.fire' methodRef
+ | '.other' methodRef
+ | extSourceSpec
+ | customAttrDecl
+ | languageDecl
+ | compControl;
+
+/* Property declaration */
+propHead:
+ '.property' propAttr* callConv type dottedName sigArgs initOpt;
+
+propAttr:
+ 'rtspecialname'
+ | 'specialname';
+
+propDecls: propDecl*;
+
+propDecl:
+ '.set' methodRef
+ | '.get' methodRef
+ | '.other' methodRef
+ | customAttrDecl
+ | extSourceSpec
+ | languageDecl
+ | compControl;
+
+/* Method declaration */
+
+marshalClause: /* EMPTY */ | 'marshal' '(' marshalBlob ')';
+
+marshalBlob: nativeType | '{' hexbytes '}';
+
+paramAttr: paramAttrElement*;
+
+paramAttrElement:
+ '[' in = 'in' ']'
+ | '[' out = 'out' ']'
+ | '[' opt = 'opt' ']'
+ | '[' int32 ']';
+
+methodHead:
+ '.method' (methAttr | pinvImpl)* callConv paramAttr type marshalClause methodName typarsClause sigArgs
+ implAttr*;
+
+methAttr: 'static'
+ | 'public'
+ | 'private'
+ | 'family'
+ | 'final'
+ | 'specialname'
+ | 'virtual'
+ | 'strict'
+ | 'abstract'
+ | 'assembly'
+ | 'famandassem'
+ | 'famorassem'
+ | 'privatescope'
+ | 'hidebysig'
+ | 'newslot'
+ | 'rtspecialname'
+ | 'unmanagedexp'
+ | 'reqsecobj'
+ | 'flags' '(' int32 ')';
+
+pinvImpl: 'pinvokeimpl' '(' (compQstring ('as' compQstring)?)? pinvAttr* ')';
+
+pinvAttr:
+ 'nomangle'
+ | 'ansi'
+ | 'unicode'
+ | 'autochar'
+ | 'lasterr'
+ | 'winapi'
+ | 'cdecl'
+ | 'stdcall'
+ | 'thiscall'
+ | 'fastcall'
+ | 'bestfit' ':' 'on'
+ | 'bestfit' ':' 'off'
+ | 'charmaperror' ':' 'on'
+ | 'charmaperror' ':' 'off'
+ | 'flags' '(' int32 ')';
+
+methodName: '.ctor' | '.cctor' | dottedName;
+
+implAttr:
+ 'native'
+ | 'cil'
+ | 'optil'
+ | 'managed'
+ | 'unmanaged'
+ | 'forwardref'
+ | 'preservesig'
+ | 'runtime'
+ | 'internalcall'
+ | 'synchronized'
+ | 'noinlining'
+ | 'aggressiveinlining'
+ | 'nooptimization'
+ | 'aggressiveoptimization'
+ | 'async'
+ | 'flags' '(' int32 ')';
+
+EMITBYTE: '.emitbyte';
+MAXSTACK: '.maxstack';
+ENTRYPOINT: '.entrypoint';
+ZEROINIT: '.zeroinit';
+LOCALS: '.locals';
+EXPORT: '.export';
+OVERRIDE: '.override';
+VTENTRY: '.vtentry';
+
+methodDecls: methodDecl*;
+
+methodDecl:
+ EMITBYTE int32
+ | sehBlock
+ | MAXSTACK int32
+ | LOCALS sigArgs
+ | LOCALS 'init' sigArgs
+ | ENTRYPOINT
+ | ZEROINIT
+ | dataDecl
+ | instr
+ | id ':'
+ | secDecl
+ | extSourceSpec // Leave for later when I get to generating symbols.
+ | languageDecl // Leave for later when I get to generating symbols.
+ | customAttrDecl
+ | compControl
+ | EXPORT '[' int32 ']'
+ | EXPORT '[' int32 ']' 'as' id
+ | VTENTRY int32 ':' int32
+ | OVERRIDE typeSpec '::' methodName
+ | OVERRIDE 'method' callConv type typeSpec '::' methodName genArity sigArgs
+ | scopeBlock
+ | PARAM TYPE '[' int32 ']' customAttrDecl*
+ | PARAM TYPE dottedName customAttrDecl*
+ | PARAM CONSTRAINT '[' int32 ']' ',' typeSpec customAttrDecl*
+ | PARAM CONSTRAINT dottedName ',' typeSpec customAttrDecl*
+ | PARAM '[' int32 ']' initOpt customAttrDecl*;
+
+scopeBlock: '{' methodDecls '}';
+
+/* Structured exception handling directives */
+sehBlock: tryBlock sehClauses;
+
+sehClauses: sehClause+;
+
+tryBlock:
+ '.try' scopeBlock
+ | '.try' id 'to' id
+ | '.try' int32 'to' int32;
+
+sehClause:
+ catchClause handlerBlock
+ | filterClause handlerBlock
+ | finallyClause handlerBlock
+ | faultClause handlerBlock;
+
+filterClause:
+ 'filter' scopeBlock
+ | 'filter' id
+ | 'filter' int32;
+
+catchClause: 'catch' typeSpec;
+
+finallyClause: 'finally';
+
+faultClause: 'fault';
+
+handlerBlock:
+ scopeBlock
+ | 'handler' id 'to' id
+ | 'handler' int32 'to' int32;
+
+/* Data declaration */
+dataDecl: ddHead ddBody;
+
+ddHead: '.data' tls id '=' | '.data' tls;
+
+tls: /* EMPTY */ | 'tls' | 'cil';
+
+ddBody: '{' ddItemList '}' | ddItem;
+
+ddItemList: (ddItem ',')* ddItem;
+
+ddItemCount: /* EMPTY */ | '[' int32 ']';
+
+ddItem:
+ CHAR PTR '(' compQstring ')'
+ | REF '(' id ')'
+ | 'bytearray' '(' bytes ')'
+ | FLOAT32 '(' float64 ')' ddItemCount
+ | FLOAT64_ '(' float64 ')' ddItemCount
+ | INT64_ '(' int64 ')' ddItemCount
+ | INT32_ '(' int32 ')' ddItemCount
+ | INT16 '(' int32 ')' ddItemCount
+ | INT8 '(' int32 ')' ddItemCount
+ | FLOAT32 ddItemCount
+ | FLOAT64_ ddItemCount
+ | INT64_ ddItemCount
+ | INT32_ ddItemCount
+ | INT16 ddItemCount
+ | INT8 ddItemCount;
+
+/* Default values declaration for fields, parameters and verbal form of CA blob description */
+fieldSerInit:
+ FLOAT32 '(' float64 ')'
+ | FLOAT64_ '(' float64 ')'
+ | FLOAT32 '(' int32 ')'
+ | FLOAT64_ '(' int64 ')'
+ | INT64_ '(' int64 ')'
+ | INT32_ '(' int32 ')'
+ | INT16 '(' int32 ')'
+ | INT8 '(' int32 ')'
+ | UINT64 '(' int64 ')'
+ | UINT32 '(' int32 ')'
+ | UINT16 '(' int32 ')'
+ | UINT8 '(' int32 ')'
+ | CHAR '(' int32 ')'
+ | BOOL '(' truefalse ')'
+ | 'bytearray' '(' bytes ')';
+
+bytes: hexbytes*;
+
+hexbytes: HEXBYTE+;
+/* Field/parameter initialization */
+fieldInit: fieldSerInit | compQstring | NULLREF;
+
+/* Values for verbal form of CA blob description */
+serInit:
+ fieldSerInit
+ | STRING '(' NULLREF ')'
+ | STRING '(' SQSTRING ')'
+ | TYPE '(' 'class' SQSTRING ')'
+ | TYPE '(' className ')'
+ | TYPE '(' NULLREF ')'
+ | OBJECT '(' serInit ')'
+ | FLOAT32 '[' int32 ']' '(' f32seq ')'
+ | FLOAT64_ '[' int32 ']' '(' f64seq ')'
+ | INT64_ '[' int32 ']' '(' i64seq ')'
+ | INT32_ '[' int32 ']' '(' i32seq ')'
+ | INT16 '[' int32 ']' '(' i16seq ')'
+ | INT8 '[' int32 ']' '(' i8seq ')'
+ | UINT64 '[' int32 ']' '(' i64seq ')'
+ | UINT32 '[' int32 ']' '(' i32seq ')'
+ | UINT16 '[' int32 ']' '(' i16seq ')'
+ | UINT8 '[' int32 ']' '(' i8seq ')'
+ | CHAR '[' int32 ']' '(' i16seq ')'
+ | BOOL '[' int32 ']' '(' boolSeq ')'
+ | STRING '[' int32 ']' '(' sqstringSeq ')'
+ | TYPE '[' int32 ']' '(' classSeq ')'
+ | OBJECT '[' int32 ']' '(' objSeq ')';
+
+f32seq: (float64 | int32)*;
+
+f64seq: (float64 | int64)*;
+
+i64seq: int64*;
+
+i32seq: int32*;
+
+i16seq: int32*;
+
+i8seq: int32*;
+
+boolSeq: truefalse*;
+
+sqstringSeq: (NULLREF | SQSTRING)*;
+
+classSeq: classSeqElement*;
+
+classSeqElement: NULLREF | 'class' SQSTRING | className;
+
+objSeq: serInit*;
+
+customAttrDecl:
+ customDescr
+ | customDescrWithOwner
+ | dottedName /* typedef */;
+
+/* Assembly References */
+asmOrRefDecl:
+ '.publicKey' '=' '(' bytes ')'
+ | '.ver' intOrWildcard ':' intOrWildcard ':' intOrWildcard ':' intOrWildcard
+ | '.locale' compQstring
+ | '.locale' '=' '(' bytes ')'
+ | customAttrDecl
+ | compControl;
+
+assemblyRefHead:
+ '.assembly' 'extern' asmAttr dottedName
+ | '.assembly' 'extern' asmAttr dottedName 'as' dottedName;
+
+assemblyRefDecls: assemblyRefDecl*;
+
+assemblyRefDecl:
+ '.hash' '=' '(' bytes ')'
+ | asmOrRefDecl
+ | '.publickeytoken' '=' '(' bytes ')'
+ | 'auto';
+
+exptypeHead: '.class' 'extern' exptAttr* dottedName;
+
+exportHead: '.export' exptAttr* dottedName;
+
+exptAttr:
+ 'private'
+ | 'public'
+ | 'forwarder'
+ | 'nested' 'public'
+ | 'nested' 'private'
+ | 'nested' 'family'
+ | 'nested' 'assembly'
+ | 'nested' 'famandassem'
+ | 'nested' 'famorassem';
+
+exptypeDecls: exptypeDecl*;
+
+exptypeDecl:
+ '.file' dottedName
+ | '.class' 'extern' slashedName
+ | '.assembly' 'extern' dottedName
+ | mdtoken
+ | '.class' int32
+ | customAttrDecl
+ | compControl;
+
+manifestResHead:
+ MRESOURCE manresAttr* dottedName
+ | MRESOURCE manresAttr* dottedName 'as' dottedName;
+
+manresAttr: 'public' | 'private';
+
+manifestResDecls: manifestResDecl*;
+
+manifestResDecl:
+ '.file' dottedName 'at' int32
+ | '.assembly' 'extern' dottedName
+ | customAttrDecl
+ | compControl;
+
diff --git a/src/tools/ilasm/src/ILAssembler/Diagnostic.cs b/src/tools/ilasm/src/ILAssembler/Diagnostic.cs
new file mode 100644
index 00000000000000..b5d6971cbab403
--- /dev/null
+++ b/src/tools/ilasm/src/ILAssembler/Diagnostic.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Immutable;
+
+namespace ILAssembler;
+
+public enum DiagnosticSeverity
+{
+ Error,
+ Warning,
+ Info,
+ Hidden
+}
+
+public record Diagnostic(string Id, DiagnosticSeverity Severity, string Message, Location Location);
+
+internal static class DiagnosticIds
+{
+ public const string LiteralOutOfRange = "ILA0001";
+ public const string UnsealedValueType = "ILA0002";
+}
+
+internal static class DiagnosticMessageTemplates
+{
+ public const string LiteralOutOfRange = "The value '{0}' is out of range";
+ public const string UnsealedValueType = "The value type '{0}' is unsealed; implicitly sealed.";
+}
diff --git a/src/tools/ilasm/src/ILAssembler/DocumentCompiler.cs b/src/tools/ilasm/src/ILAssembler/DocumentCompiler.cs
new file mode 100644
index 00000000000000..3b239357bfb5a3
--- /dev/null
+++ b/src/tools/ilasm/src/ILAssembler/DocumentCompiler.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using Antlr4.Runtime;
+
+namespace ILAssembler;
+public sealed class DocumentCompiler
+{
+ public (ImmutableArray, PEBuilder?) Compile(SourceText document, Func includedDocumentLoader, Func resourceLocator, Options options)
+ {
+ var inputSource = new AntlrInputStream(document.Text)
+ {
+ name = document.Path
+ };
+ CILLexer lexer = new(inputSource);
+ Dictionary loadedDocuments = new()
+ {
+ {document.Path!, document }
+ };
+ PreprocessedTokenSource preprocessor = new(lexer, path =>
+ {
+ var includedDocument = includedDocumentLoader(path);
+
+ var includedSource = new AntlrInputStream(includedDocument.Text)
+ {
+ name = includedDocument.Path
+ };
+ loadedDocuments.Add(includedDocument.Path, includedDocument);
+ return new CILLexer(includedSource);
+ });
+
+ ImmutableArray.Builder diagnostics = ImmutableArray.CreateBuilder();
+ preprocessor.OnPreprocessorSyntaxError += (source, start, length, msg) =>
+ {
+ diagnostics.Add(new Diagnostic("Preprocessor", DiagnosticSeverity.Error, msg, new Location(new(start, length), loadedDocuments[source])));
+ };
+
+ CILParser parser = new(new CommonTokenStream(lexer));
+ var result = parser.decls();
+ GrammarVisitor visitor = new GrammarVisitor(loadedDocuments, options, resourceLocator);
+ _ = result.Accept(visitor);
+
+ var image = visitor.BuildImage();
+
+ bool anyErrors = diagnostics.Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error);
+
+ diagnostics.AddRange(image.Diagnostics);
+
+ return (diagnostics.ToImmutable(), anyErrors ? null : image.Image);
+ }
+}
diff --git a/src/tools/ilasm/src/ILAssembler/EntityRegistry.cs b/src/tools/ilasm/src/ILAssembler/EntityRegistry.cs
new file mode 100644
index 00000000000000..1755fcc15920bf
--- /dev/null
+++ b/src/tools/ilasm/src/ILAssembler/EntityRegistry.cs
@@ -0,0 +1,1337 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Text;
+
+namespace ILAssembler
+{
+ internal sealed class EntityRegistry
+ {
+ private readonly Dictionary> _seenEntities = new();
+ private readonly Dictionary<(TypeDefinitionEntity? ContainingType, string Namespace, string Name), TypeDefinitionEntity> _seenTypeDefs = new();
+ private readonly Dictionary<(EntityBase ResolutionScope, string Namespace, string Name), TypeReferenceEntity> _seenTypeRefs = new();
+ private readonly Dictionary _seenAssemblyRefs = new();
+ private readonly Dictionary _seenModuleRefs = new();
+ private readonly Dictionary _seenTypeSpecs = new(new BlobBuilderContentEqualityComparer());
+ private readonly Dictionary _seenStandaloneSignatures = new(new BlobBuilderContentEqualityComparer());
+ private readonly Dictionary _seenFiles = new();
+ private readonly List _manifestResourceEntities = new();
+ private readonly Dictionary<(ExportedTypeEntity? ContainingType, string Namespace, string Name), ExportedTypeEntity> _seenExportedTypes = new();
+ private readonly List _memberReferences = new();
+ private readonly Dictionary<(EntityBase, BlobBuilder), MethodSpecificationEntity> _seenMethodSpecs = new(new MethodSpecEqualityComparer());
+
+ private sealed class BlobBuilderContentEqualityComparer : IEqualityComparer
+ {
+ public bool Equals(BlobBuilder? x, BlobBuilder? y)
+ {
+ if (x is null && y is null)
+ {
+ return true;
+ }
+
+ if (x is null || y is null)
+ {
+ return false;
+ }
+
+ return x.ContentEquals(y!);
+ }
+
+ public int GetHashCode(BlobBuilder obj)
+ {
+ HashCode hash = default;
+ foreach (var b in obj.GetBlobs())
+ {
+ hash.AddBytes(b.GetBytes());
+ }
+ return hash.ToHashCode();
+ }
+ }
+
+ private sealed class MethodSpecEqualityComparer : IEqualityComparer<(EntityBase, BlobBuilder)>
+ {
+ public bool Equals((EntityBase, BlobBuilder) x, (EntityBase, BlobBuilder) y)
+ {
+ return x.Item1 == y.Item1 && x.Item2.ContentEquals(y.Item2);
+ }
+
+ public int GetHashCode((EntityBase, BlobBuilder) obj)
+ {
+ return (obj.Item1.GetHashCode(), obj.Item2.Count).GetHashCode();
+ }
+ }
+
+ public enum WellKnownBaseType
+ {
+ System_Object,
+ System_ValueType,
+ System_Enum
+ }
+
+ public EntityRegistry()
+ {
+ ModuleType = GetOrCreateTypeDefinition(null, "", "", moduleType =>
+ {
+ moduleType.BaseType = null;
+ });
+ }
+
+ private IReadOnlyList GetSeenEntities(TableIndex table)
+ {
+ if (_seenEntities.TryGetValue(table, out var entities))
+ {
+ return entities;
+ }
+ return Array.Empty();
+ }
+
+ public void WriteContentTo(MetadataBuilder builder, BlobBuilder ilStream)
+ {
+ // Now that we've seen all of the entities, we can write them out in the correct order.
+ // Record the entities in the correct order so they are assigned handles.
+ // After this, we'll write out the content of the entities in the correct order.
+ foreach (TypeDefinitionEntity type in GetSeenEntities(TableIndex.TypeDef))
+ {
+ // Record entries for members defined in list columns
+ foreach (var method in type.Methods)
+ {
+ RecordEntityInTable(TableIndex.MethodDef, method);
+ foreach (var param in method.Parameters)
+ {
+ // COMPAT: Only record param entries for parameters that have names
+ // or other rows that would refer to it.
+ if (param.Name is not null
+ || param.MarshallingDescriptor.Count != 0
+ || param.HasCustomAttributes)
+ {
+ RecordEntityInTable(TableIndex.Param, param);
+ }
+ }
+ }
+ foreach (var field in type.Fields)
+ {
+ RecordEntityInTable(TableIndex.Field, field);
+ }
+ foreach (var property in type.Properties)
+ {
+ RecordEntityInTable(TableIndex.Property, property);
+ }
+ foreach (var @event in type.Events)
+ {
+ RecordEntityInTable(TableIndex.Event, @event);
+ }
+
+ // Record entries in tables that are sorted based on their containing/associated class
+ foreach (var impl in type.InterfaceImplementations)
+ {
+ RecordEntityInTable(TableIndex.InterfaceImpl, impl);
+ }
+
+ foreach (var impl in type.MethodImplementations)
+ {
+ RecordEntityInTable(TableIndex.MethodImpl, impl);
+ }
+
+ foreach (var genericParam in type.GenericParameters)
+ {
+ RecordEntityInTable(TableIndex.GenericParam, genericParam);
+ }
+
+ // COMPAT: Record the generic parameter constraints based on the order saved in the TypeDef
+ foreach (var constraint in type.GenericParameterConstraints)
+ {
+ RecordEntityInTable(TableIndex.GenericParamConstraint, constraint);
+ }
+ }
+
+ foreach (MemberReferenceEntity memberReferenceEntity in _memberReferences)
+ {
+ ResolveAndRecordMemberReference(memberReferenceEntity);
+ }
+
+ // Now that we've recorded all of the entities that wouldn't have had handles before,
+ // we can start writing out the content of the entities.
+ builder.AddModule(0, Module.Name is null ? default : builder.GetOrAddString(Module.Name), builder.GetOrAddGuid(Guid.NewGuid()), default, default);
+
+ foreach (TypeReferenceEntity type in GetSeenEntities(TableIndex.TypeRef))
+ {
+ EntityBase resolutionScope = type.ResolutionScope;
+ builder.AddTypeReference(
+ resolutionScope is FakeTypeEntity fakeScope ? fakeScope.ResolutionScopeColumnHandle : resolutionScope.Handle,
+ builder.GetOrAddString(type.Namespace),
+ builder.GetOrAddString(type.Name));
+ }
+
+ for (int i = 0; i < GetSeenEntities(TableIndex.TypeDef).Count; i++)
+ {
+ TypeDefinitionEntity type = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[i];
+ builder.AddTypeDefinition(
+ type.Attributes,
+ builder.GetOrAddString(type.Namespace),
+ builder.GetOrAddString(type.Name),
+ type.BaseType is null ? default : type.BaseType.Handle,
+ (FieldDefinitionHandle)GetHandleForList(type.Fields, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Fields, i, TableIndex.Field),
+ (MethodDefinitionHandle)GetHandleForList(type.Methods, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Methods, i, TableIndex.MethodDef));
+
+ builder.AddEventMap(
+ (TypeDefinitionHandle)type.Handle,
+ (EventDefinitionHandle)GetHandleForList(type.Events, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Events, i, TableIndex.Event));
+ builder.AddPropertyMap(
+ (TypeDefinitionHandle)type.Handle,
+ (PropertyDefinitionHandle)GetHandleForList(type.Properties, GetSeenEntities(TableIndex.TypeDef), type => ((TypeDefinitionEntity)type).Properties, i, TableIndex.Property));
+
+ // TODO: ClassLayout
+ if (type.ContainingType is not null)
+ {
+ builder.AddNestedType((TypeDefinitionHandle)type.Handle, (TypeDefinitionHandle)type.ContainingType.Handle);
+ }
+ }
+
+ foreach (FieldDefinitionEntity fieldDef in GetSeenEntities(TableIndex.Field))
+ {
+ builder.AddFieldDefinition(
+ fieldDef.Attributes,
+ builder.GetOrAddString(fieldDef.Name),
+ fieldDef.Signature!.Count == 0 ? default : builder.GetOrAddBlob(fieldDef.Signature));
+
+ // TODO: FieldLayout, FieldRVA
+ if (fieldDef.MarshallingDescriptor is not null)
+ {
+ builder.AddMarshallingDescriptor(fieldDef.Handle, builder.GetOrAddBlob(fieldDef.MarshallingDescriptor));
+ }
+ }
+
+ for (int i = 0; i < GetSeenEntities(TableIndex.MethodDef).Count; i++)
+ {
+ MethodDefinitionEntity methodDef = (MethodDefinitionEntity)GetSeenEntities(TableIndex.MethodDef)[i];
+
+ int rva = 0;
+ if (methodDef.MethodBody.CodeBuilder.Count != 0)
+ {
+ rva = ilStream.Count;
+ methodDef.MethodBody.CodeBuilder.WriteContentTo(ilStream);
+ }
+
+ builder.AddMethodDefinition(
+ methodDef.MethodAttributes,
+ methodDef.ImplementationAttributes,
+ builder.GetOrAddString(methodDef.Name),
+ builder.GetOrAddBlob(methodDef.MethodSignature!),
+ rva,
+ (ParameterHandle)GetHandleForList(methodDef.Parameters, GetSeenEntities(TableIndex.MethodDef), method => ((MethodDefinitionEntity)method).Parameters, i, TableIndex.Param));
+
+ if (methodDef.MethodImportInformation is not null)
+ {
+ builder.AddMethodImport(
+ (MethodDefinitionHandle)methodDef.Handle,
+ methodDef.MethodImportInformation.Value.Attributes,
+ methodDef.MethodImportInformation.Value.EntryPointName is null ? default : builder.GetOrAddString(methodDef.MethodImportInformation.Value.EntryPointName),
+ (ModuleReferenceHandle)methodDef.MethodImportInformation.Value.ModuleName.Handle);
+ }
+ }
+
+ foreach (ParameterEntity param in GetSeenEntities(TableIndex.Param))
+ {
+ builder.AddParameter(
+ param.Attributes,
+ param.Name is null ? default : builder.GetOrAddString(param.Name),
+ param.Sequence);
+
+ if (param.MarshallingDescriptor is not null)
+ {
+ builder.AddMarshallingDescriptor(param.Handle, builder.GetOrAddBlob(param.MarshallingDescriptor));
+ }
+ }
+
+ foreach (InterfaceImplementationEntity impl in GetSeenEntities(TableIndex.InterfaceImpl))
+ {
+ builder.AddInterfaceImplementation(
+ (TypeDefinitionHandle)impl.Type.Handle,
+ impl.InterfaceType is FakeTypeEntity fakeType ? fakeType.TypeColumnHandle : impl.InterfaceType.Handle);
+ }
+
+ foreach (MemberReferenceEntity memberRef in _memberReferences)
+ {
+ builder.AddMemberReference(
+ memberRef.Parent.Handle,
+ builder.GetOrAddString(memberRef.Name),
+ builder.GetOrAddBlob(memberRef.Signature));
+ }
+
+ foreach (DeclarativeSecurityAttributeEntity declSecurity in GetSeenEntities(TableIndex.DeclSecurity))
+ {
+ builder.AddDeclarativeSecurityAttribute(
+ declSecurity.Parent?.Handle ?? default,
+ declSecurity.Action,
+ builder.GetOrAddBlob(declSecurity.PermissionSet));
+ }
+
+ foreach (StandaloneSignatureEntity standaloneSig in GetSeenEntities(TableIndex.StandAloneSig))
+ {
+ builder.AddStandaloneSignature(
+ builder.GetOrAddBlob(standaloneSig.Signature));
+ }
+
+ foreach (EventEntity evt in GetSeenEntities(TableIndex.Event))
+ {
+ builder.AddEvent(
+ evt.Attributes,
+ builder.GetOrAddString(evt.Name),
+ evt.Type.Handle);
+
+ foreach (var accessor in evt.Accessors)
+ {
+ builder.AddMethodSemantics(evt.Handle, accessor.Semantic, (MethodDefinitionHandle)accessor.Method.Handle);
+ }
+ }
+
+ foreach (PropertyEntity prop in GetSeenEntities(TableIndex.Property))
+ {
+ builder.AddProperty(
+ prop.Attributes,
+ builder.GetOrAddString(prop.Name),
+ builder.GetOrAddBlob(prop.Type));
+
+ foreach (var accessor in prop.Accessors)
+ {
+ builder.AddMethodSemantics(prop.Handle, accessor.Semantic, (MethodDefinitionHandle)accessor.Method.Handle);
+ }
+ }
+
+ foreach (ModuleReferenceEntity moduleRef in GetSeenEntities(TableIndex.ModuleRef))
+ {
+ builder.AddModuleReference(builder.GetOrAddString(moduleRef.Name));
+ }
+
+ foreach (TypeSpecificationEntity typeSpec in GetSeenEntities(TableIndex.TypeSpec))
+ {
+ builder.AddTypeSpecification(builder.GetOrAddBlob(typeSpec.Signature));
+ }
+
+ if (Assembly is not null)
+ {
+ builder.AddAssembly(
+ builder.GetOrAddString(Assembly.Name),
+ Assembly.Version ?? new Version(),
+ Assembly.Culture is null ? default : builder.GetOrAddString(Assembly.Culture),
+ Assembly.PublicKeyOrToken is null ? default : builder.GetOrAddBlob(Assembly.PublicKeyOrToken),
+ Assembly.Flags,
+ Assembly.HashAlgorithm);
+ }
+
+ foreach (FileEntity file in GetSeenEntities(TableIndex.File))
+ {
+ builder.AddAssemblyFile(
+ builder.GetOrAddString(file.Name),
+ file.Hash is not null ? builder.GetOrAddBlob(file.Hash) : default,
+ file.HasMetadata);
+ }
+
+ foreach (ExportedTypeEntity exportedType in GetSeenEntities(TableIndex.ExportedType))
+ {
+ builder.AddExportedType(
+ exportedType.Attributes,
+ builder.GetOrAddString(exportedType.Name),
+ builder.GetOrAddString(exportedType.Namespace),
+ exportedType.Implementation?.Handle ?? default,
+ exportedType.TypeDefinitionId);
+ }
+
+ foreach (ManifestResourceEntity resource in GetSeenEntities(TableIndex.ManifestResource))
+ {
+ builder.AddManifestResource(
+ resource.Attributes,
+ builder.GetOrAddString(resource.Name),
+ resource.Implementation?.Handle ?? default,
+ resource.Offset);
+ }
+
+ foreach (MethodSpecificationEntity methodSpec in GetSeenEntities(TableIndex.MethodSpec))
+ {
+ builder.AddMethodSpecification(methodSpec.Parent.Handle, builder.GetOrAddBlob(methodSpec.Signature));
+ }
+
+ static EntityHandle GetHandleForList(IReadOnlyList list, IReadOnlyList listOwner, Func> getList, int ownerIndex, TableIndex tokenType)
+ {
+ // Return the first entry in the list.
+ // If the list is empty, return the start of the next list.
+ // If there is no next list, return one past the end of the previous list.
+ if (list.Count != 0)
+ {
+ return list[0].Handle;
+ }
+
+ for (int i = 0; i < listOwner.Count; i++)
+ {
+ var otherList = getList(listOwner[i]);
+ if (otherList.Count != 0)
+ {
+ return otherList[0].Handle;
+ }
+ }
+
+ for (int i = ownerIndex - 1; i >= 0; i--)
+ {
+ var otherList = getList(listOwner[i]);
+ if (otherList.Count != 0)
+ {
+ return MetadataTokens.EntityHandle(tokenType, MetadataTokens.GetRowNumber(otherList[otherList.Count - 1].Handle) + 1);
+ }
+ }
+ return MetadataTokens.EntityHandle(tokenType, 0);
+ }
+ }
+
+ public TypeEntity? ResolveImplicitBaseType(WellKnownBaseType? type)
+ {
+ if (type is null)
+ {
+ return null;
+ }
+ return type switch
+ {
+ WellKnownBaseType.System_Object => SystemObjectType,
+ WellKnownBaseType.System_ValueType => SystemValueTypeType,
+ WellKnownBaseType.System_Enum => SystemEnumType,
+ _ => throw new ArgumentOutOfRangeException(nameof(type))
+ };
+ }
+
+ private TypeEntity? _systemObject;
+ public TypeEntity SystemObjectType
+ {
+ get
+ {
+ return _systemObject ??= ResolveFromCoreAssembly("System.Object");
+ }
+ }
+
+ private TypeEntity? _systemValueType;
+ public TypeEntity SystemValueTypeType
+ {
+ get
+ {
+ return _systemValueType ??= ResolveFromCoreAssembly("System.ValueType");
+ }
+ }
+
+ private TypeEntity? _systemEnum;
+ public TypeEntity SystemEnumType
+ {
+ get
+ {
+ return _systemEnum ??= ResolveFromCoreAssembly("System.Enum");
+ }
+ }
+
+ public TypeDefinitionEntity ModuleType { get; }
+
+ public ModuleEntity Module { get; } = new ModuleEntity();
+
+ public AssemblyEntity? Assembly { get; set; }
+
+ private TypeReferenceEntity ResolveFromCoreAssembly(string typeName)
+ {
+ // TODO: System.Private.CoreLib as the core assembly?
+ var coreAsmRef = GetOrCreateAssemblyReference("mscorlib", new Version(4, 0), culture: null, publicKeyOrToken: null, 0, ProcessorArchitecture.None);
+ return GetOrCreateTypeReference(coreAsmRef, new TypeName(null, typeName));
+ }
+
+ public interface IHasHandle
+ {
+ EntityHandle Handle { get; }
+ void SetHandle(EntityHandle token);
+ }
+
+ public void RecordEntityInTable(TableIndex table, EntityBase entity)
+ {
+ if (!_seenEntities.TryGetValue(table, out List? entities))
+ {
+ _seenEntities[table] = entities = new List();
+ }
+ entities.Add(entity);
+ ((IHasHandle)entity).SetHandle(MetadataTokens.EntityHandle(table, entities.Count));
+ }
+
+ private TEntity GetOrCreateEntity(TKey key, TableIndex table, Dictionary cache, Func constructor, Action onCreate)
+ where TKey : notnull
+ where TEntity : EntityBase
+ {
+ if (cache.TryGetValue(key, out TEntity? entity))
+ {
+ return entity;
+ }
+ entity = constructor(key);
+ RecordEntityInTable(table, entity);
+ cache.Add(key, entity);
+ onCreate(entity);
+ return entity;
+ }
+
+ private TEntity CreateEntity(TableIndex table, List cache, Func constructor)
+ where TEntity : EntityBase
+ {
+ TEntity entity = constructor();
+ RecordEntityInTable(table, entity);
+ cache.Add(entity);
+ return entity;
+ }
+
+ public TypeDefinitionEntity GetOrCreateTypeDefinition(TypeDefinitionEntity? containingType, string @namespace, string name, Action onCreateType)
+ {
+ return GetOrCreateEntity((containingType, @namespace, name), TableIndex.TypeDef, _seenTypeDefs, (key) => new(key.Item1, key.Item2, key.Item3), onCreateType);
+ }
+
+ public TypeDefinitionEntity? FindTypeDefinition(TypeDefinitionEntity? containingType, string @namespace, string @name)
+ {
+ if (_seenTypeDefs.TryGetValue((containingType, @namespace, name), out var typeDef))
+ {
+ return typeDef;
+ }
+ return null;
+ }
+
+ public AssemblyReferenceEntity GetOrCreateAssemblyReference(string name, Action onCreateAssemblyReference)
+ {
+ return GetOrCreateEntity(new(name), TableIndex.AssemblyRef, _seenAssemblyRefs, _ => new(name), onCreateAssemblyReference);
+ }
+
+ public ModuleReferenceEntity GetOrCreateModuleReference(string name, Action onCreateModuleReference)
+ {
+ return GetOrCreateEntity(name, TableIndex.ModuleRef, _seenModuleRefs, name => new(name), onCreateModuleReference);
+ }
+
+ public ModuleReferenceEntity? FindModuleReference(string name)
+ {
+ if (_seenModuleRefs.TryGetValue(name, out var moduleRef))
+ {
+ return moduleRef;
+ }
+ return null;
+ }
+
+ public static GenericParameterEntity CreateGenericParameter(GenericParameterAttributes attributes, string name)
+ {
+ GenericParameterEntity param = new(attributes, name);
+ return param;
+ }
+
+ public static GenericParameterConstraintEntity CreateGenericConstraint(TypeEntity baseType)
+ {
+ GenericParameterConstraintEntity constraint = new(baseType);
+ return constraint;
+ }
+
+ public TypeSpecificationEntity GetOrCreateTypeSpec(BlobBuilder signature)
+ {
+ return GetOrCreateEntity(signature, TableIndex.TypeSpec, _seenTypeSpecs, signature => new(signature), _ => { });
+ }
+
+ public EntityBase ResolveHandleToEntity(EntityHandle entityHandle)
+ {
+ _ = MetadataTokens.TryGetTableIndex(entityHandle.Kind, out var tableIndex);
+ if (_seenEntities.TryGetValue(tableIndex, out var entity))
+ {
+ int rowNumber = MetadataTokens.GetRowNumber(entityHandle);
+ if (entity.Count < rowNumber - 1)
+ {
+ return entity[rowNumber - 1];
+ }
+ }
+ // Row entry does not exist. Use our FakeTypeEntity type to record the invalid handle.
+ return new FakeTypeEntity(entityHandle);
+ }
+
+ public TypeReferenceEntity GetOrCreateTypeReference(EntityBase resolutionContext, TypeName name)
+ {
+ Stack<(string Namespace, string Name)> allTypeNames = new();
+ // Record all of the containing type names
+ for (TypeName? containingType = name; containingType is not null; containingType = containingType.ContainingTypeName)
+ {
+ allTypeNames.Push(NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName));
+ }
+
+ EntityBase scope = resolutionContext;
+ while (scope is TypeReferenceEntity typeRef)
+ {
+ allTypeNames.Push((typeRef.Namespace, typeRef.Name));
+ scope = typeRef.ResolutionScope;
+ }
+ while (allTypeNames.Count > 0)
+ {
+ var typeName = allTypeNames.Pop();
+ scope = GetOrCreateEntity((scope, typeName.Namespace, typeName.Name), TableIndex.TypeRef, _seenTypeRefs, value => new TypeReferenceEntity(scope, value.Namespace, value.Name), typeRef =>
+ {
+ StringBuilder builder = new(typeRef.Namespace.Length + typeRef.Name.Length + 1);
+ builder.AppendFormat("{0}.{1}", typeRef.Namespace, typeRef.Name);
+ if (resolutionContext is AssemblyReferenceEntity asmRef)
+ {
+ // TODO: Do full assembly name here
+ builder.Append(asmRef.Name);
+ }
+ typeRef.ReflectionNotation = builder.ToString();
+ });
+ }
+ return (TypeReferenceEntity)scope;
+ }
+
+ public static MethodDefinitionEntity CreateUnrecordedMethodDefinition(TypeDefinitionEntity containingType, string name)
+ {
+ return new MethodDefinitionEntity(containingType, name);
+ }
+
+ public static bool TryAddMethodDefinitionToContainingType(MethodDefinitionEntity methodDef)
+ {
+ if (methodDef.MethodSignature is null)
+ {
+ throw new ArgumentException("The method signature must be defined before recording the method definition, to enable detecting duplicate methods.");
+ }
+ bool allowDuplicate = (methodDef.MethodAttributes & MethodAttributes.MemberAccessMask) == MethodAttributes.PrivateScope;
+ if (!allowDuplicate)
+ {
+ foreach (var method in methodDef.ContainingType.Methods)
+ {
+ if (methodDef.Name == method.Name
+ && methodDef.MethodSignature.ContentEquals(method.MethodSignature!)
+ && (method.MethodAttributes & MethodAttributes.MemberAccessMask) != MethodAttributes.PrivateScope)
+ {
+ return false;
+ }
+ }
+ }
+ methodDef.ContainingType.Methods.Add(methodDef);
+ return true;
+ }
+
+ public static FieldDefinitionEntity? CreateUnrecordedFieldDefinition(FieldAttributes attributes, TypeDefinitionEntity containingType, string name, BlobBuilder signature)
+ {
+ var field = new FieldDefinitionEntity(attributes, containingType, name, signature);
+ bool allowDuplicate = (field.Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.PrivateScope;
+ if (!allowDuplicate)
+ {
+ foreach (var fieldDef in field.ContainingType.Fields)
+ {
+ if (fieldDef.Name == field.Name
+ && fieldDef.Signature.ContentEquals(field.Signature!)
+ && (fieldDef.Attributes & FieldAttributes.FieldAccessMask) != FieldAttributes.PrivateScope)
+ {
+ return null;
+ }
+ }
+ }
+ field.ContainingType.Fields.Add(field);
+ return field;
+ }
+
+ public static InterfaceImplementationEntity CreateUnrecordedInterfaceImplementation(TypeDefinitionEntity implementingType, TypeEntity interfaceType)
+ {
+ return new InterfaceImplementationEntity(implementingType, interfaceType);
+ }
+
+ public static ParameterEntity CreateParameter(ParameterAttributes attributes, string? name, BlobBuilder marshallingDescriptor, int sequence)
+ {
+ return new ParameterEntity(attributes, name, marshallingDescriptor, sequence);
+ }
+
+ public MemberReferenceEntity CreateLazilyRecordedMemberReference(TypeEntity containingType, string name, BlobBuilder signature)
+ {
+ var entity = new MemberReferenceEntity(containingType, name, signature);
+ _memberReferences.Add(entity);
+ return entity;
+ }
+
+ private sealed class SignatureRewriter : ISignatureTypeProvider
+ {
+ public readonly struct BlobOrHandle
+ {
+ public BlobOrHandle(BlobBuilder? blob)
+ {
+ Blob = blob;
+ Handle = default;
+ HandleIsValueType = false;
+ }
+
+ public BlobOrHandle(EntityHandle handle, bool handleIsValueType)
+ {
+ Blob = default;
+ Handle = handle;
+ HandleIsValueType = handleIsValueType;
+ }
+
+ private BlobBuilder? Blob { get; }
+ public EntityHandle Handle { get; }
+ public bool HandleIsValueType { get; }
+
+ public static implicit operator BlobOrHandle(BlobBuilder blob) => new(blob);
+ public static implicit operator BlobBuilder(BlobOrHandle blobOrHandle)
+ {
+ if (blobOrHandle.Blob is not null)
+ {
+ return blobOrHandle.Blob;
+ }
+ var signatureTypeEncoder = new SignatureTypeEncoder(new BlobBuilder());
+ signatureTypeEncoder.Type(blobOrHandle.Handle, blobOrHandle.HandleIsValueType);
+ return signatureTypeEncoder.Builder;
+ }
+
+ public void WriteBlobTo(BlobBuilder builder)
+ {
+ ((BlobBuilder)this).WriteContentTo(builder);
+ }
+ }
+
+ public BlobOrHandle GetArrayType(BlobOrHandle elementType, ArrayShape shape)
+ {
+ var encoder = new ArrayShapeEncoder(elementType);
+ encoder.Shape(shape.Rank, shape.Sizes, shape.LowerBounds);
+ return encoder.Builder;
+ }
+
+ public BlobOrHandle GetByReferenceType(BlobOrHandle elementType)
+ {
+ var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
+ paramEncoder.Type(isByRef: true);
+ elementType.WriteBlobTo(paramEncoder.Builder);
+ return paramEncoder.Builder;
+ }
+
+ public BlobOrHandle GetFunctionPointerType(MethodSignature signature)
+ {
+ var sig = new SignatureTypeEncoder(new BlobBuilder());
+ sig.FunctionPointer(signature.Header.CallingConvention, (FunctionPointerAttributes)signature.Header.Attributes, signature.GenericParameterCount)
+ .Parameters(signature.ParameterTypes.Length, out var retTypeBuilder, out var parametersEncoder);
+ signature.ReturnType.WriteBlobTo(retTypeBuilder.Builder);
+ for (int i = 0; i < signature.ParameterTypes.Length; i++)
+ {
+ if (i == signature.RequiredParameterCount)
+ {
+ parametersEncoder.StartVarArgs();
+ }
+ BlobBuilder paramType = signature.ParameterTypes[i];
+ paramType.WriteContentTo(parametersEncoder.AddParameter().Builder);
+ }
+ return sig.Builder;
+ }
+
+ public BlobOrHandle GetGenericInstantiation(BlobOrHandle genericType, ImmutableArray typeArguments)
+ {
+ var encoder = new SignatureTypeEncoder(new BlobBuilder());
+ var parameterEncoder = encoder.GenericInstantiation(genericType.Handle, typeArguments.Length, genericType.HandleIsValueType);
+ foreach (var typeArg in typeArguments)
+ {
+ typeArg.WriteBlobTo(parameterEncoder.AddArgument().Builder);
+ }
+ return encoder.Builder;
+ }
+
+ public BlobOrHandle GetGenericMethodParameter(EmptyGenericContext genericContext, int index)
+ {
+ var encoder = new SignatureTypeEncoder(new BlobBuilder());
+ encoder.GenericMethodTypeParameter(index);
+ return encoder.Builder;
+ }
+ public BlobOrHandle GetGenericTypeParameter(EmptyGenericContext genericContext, int index)
+ {
+ var encoder = new SignatureTypeEncoder(new BlobBuilder());
+ encoder.GenericTypeParameter(index);
+ return encoder.Builder;
+ }
+ public BlobOrHandle GetModifiedType(BlobOrHandle modifier, BlobOrHandle unmodifiedType, bool isRequired)
+ {
+ var builder = new BlobBuilder();
+ if (isRequired)
+ {
+ builder.WriteByte((byte)SignatureTypeCode.RequiredModifier);
+ }
+ else
+ {
+ builder.WriteByte((byte)SignatureTypeCode.OptionalModifier);
+ }
+ unmodifiedType.WriteBlobTo(builder);
+ return builder;
+ }
+ public BlobOrHandle GetPinnedType(BlobOrHandle elementType) => throw new NotImplementedException();
+ public BlobOrHandle GetPointerType(BlobOrHandle elementType)
+ {
+ var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
+ paramEncoder.Type().Pointer();
+ elementType.WriteBlobTo(paramEncoder.Builder);
+ return paramEncoder.Builder;
+ }
+ public BlobOrHandle GetPrimitiveType(PrimitiveTypeCode typeCode)
+ {
+ var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
+ paramEncoder.Type().PrimitiveType(typeCode);
+ return paramEncoder.Builder;
+ }
+ public BlobOrHandle GetSZArrayType(BlobOrHandle elementType)
+ {
+ var paramEncoder = new ParameterTypeEncoder(new BlobBuilder());
+ paramEncoder.Type().SZArray();
+ elementType.WriteBlobTo(paramEncoder.Builder);
+ return paramEncoder.Builder;
+ }
+ public BlobOrHandle GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
+ {
+ return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType);
+ }
+ public BlobOrHandle GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
+ {
+ return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType);
+ }
+
+ public BlobOrHandle GetTypeFromSpecification(MetadataReader reader, EmptyGenericContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
+ {
+ return new BlobOrHandle(handle, rawTypeKind == (byte)SignatureTypeKind.ValueType);
+ }
+
+ public struct EmptyGenericContext { }
+
+ }
+
+ private void ResolveAndRecordMemberReference(MemberReferenceEntity memberRef)
+ {
+ // We need to resolve a MemberReference in a few scenarios:
+ // 1. The MemberReference references a local MethodDefinition
+ // - This case may occur when a method is referenced by a property or event, which can only reference MethodDefinition entities
+ // TODO-COMPAT: The following scenarios are required for compat with the existing ILASM, but are not required to produce valid metadata:
+ // 2. The MemberReference refers to a local FieldDefinition
+
+ var signature = memberRef.Signature.ToArray();
+ SignatureHeader header = new(signature[0]);
+ if (header.Kind == SignatureKind.Method)
+ {
+ if (header.CallingConvention == SignatureCallingConvention.VarArgs)
+ {
+ UpdateMemberRefForVarargSignatures(memberRef, signature);
+ }
+ switch (memberRef.Parent)
+ {
+ // Use this weird construction to look up TypeDefs as we may change TypeRef resolution to use a similar model to MemberReference
+ // where we always return a TypeReference type, but it might just point to a TypeDef handle.
+ case TypeEntity { Handle.Kind: HandleKind.TypeDefinition } type:
+ {
+ var typeDef = (TypeDefinitionEntity)GetSeenEntities(TableIndex.TypeDef)[MetadataTokens.GetRowNumber(type.Handle) - 1];
+ // Look on this type for methods with the same name and signature
+ foreach (var method in typeDef.Methods)
+ {
+ if (method.Name == memberRef.Name
+ && method.MethodSignature!.ContentEquals(memberRef.Signature))
+ {
+ ((IHasHandle)memberRef).SetHandle(method.Handle);
+ return;
+ }
+ }
+ }
+ break;
+ }
+ }
+ RecordEntityInTable(TableIndex.MemberRef, memberRef);
+ }
+
+ private void UpdateMemberRefForVarargSignatures(MemberReferenceEntity memberRef, byte[] signature)
+ {
+ var decoder = new SignatureDecoder(new SignatureRewriter(), null!, default);
+ BlobEncoder methodDefSig = new(new BlobBuilder());
+ bool hasVarargParameters = false;
+ // TODO-SRM: Propose a public API to construct a blob reader over a byte array or ReadOnlyMemory
+ // to avoid the unsafe block.
+ // Alternatively, propose an API to get the corresponding MethodDefSig for a MethodRefSig and move all of this logic into SRM.
+ unsafe
+ {
+ fixed (byte* ptr = &signature[0])
+ {
+ var reader = new BlobReader(ptr, signature.Length);
+ var methodSignature = decoder.DecodeMethodSignature(ref reader);
+
+ if (methodSignature.RequiredParameterCount != methodSignature.ParameterTypes.Length)
+ {
+ hasVarargParameters = true;
+
+ methodDefSig.MethodSignature(methodSignature.Header.CallingConvention, methodSignature.GenericParameterCount, methodSignature.Header.Attributes.HasFlag(SignatureAttributes.Instance))
+ .Parameters(methodSignature.RequiredParameterCount, out var retTypeBuilder, out var parametersEncoder);
+ methodSignature.ReturnType.WriteBlobTo(retTypeBuilder.Builder);
+ for (int i = 0; i < methodSignature.RequiredParameterCount; i++)
+ {
+ methodSignature.ParameterTypes[i].WriteBlobTo(parametersEncoder.AddParameter().Builder);
+ }
+ }
+ }
+ }
+
+ // If the method has vararg parameters, then this needs to be a MemberRef whose parent is a reference to the method with the signature without any vararg parameters.
+ if (hasVarargParameters)
+ {
+ var methodRef = new MemberReferenceEntity(memberRef.Parent, memberRef.Name, methodDefSig.Builder);
+ ResolveAndRecordMemberReference(methodRef);
+ memberRef.SetMemberRefParent(methodRef);
+ }
+ }
+
+ public MethodSpecificationEntity GetOrCreateMethodSpecification(EntityBase method, BlobBuilder signature)
+ {
+ return GetOrCreateEntity(
+ (method, signature),
+ TableIndex.MethodSpec,
+ _seenMethodSpecs,
+ ((EntityBase method, BlobBuilder signature) value) => new(method, signature),
+ _ => { });
+ }
+
+ public StandaloneSignatureEntity GetOrCreateStandaloneSignature(BlobBuilder signature)
+ {
+ return GetOrCreateEntity(signature, TableIndex.StandAloneSig, _seenStandaloneSignatures, (sig) => new(sig), _ => { });
+ }
+
+ public DeclarativeSecurityAttributeEntity CreateDeclarativeSecurityAttribute(DeclarativeSecurityAction action, BlobBuilder permissionSet)
+ {
+ var entity = new DeclarativeSecurityAttributeEntity(action, permissionSet);
+ RecordEntityInTable(TableIndex.DeclSecurity, entity);
+ return entity;
+ }
+
+ public CustomAttributeEntity CreateCustomAttribute(EntityBase constructor, BlobBuilder value)
+ {
+ var entity = new CustomAttributeEntity(constructor, value);
+ RecordEntityInTable(TableIndex.CustomAttribute, entity);
+ return entity;
+ }
+
+ public static MethodImplementationEntity CreateUnrecordedMethodImplementation(MethodDefinitionEntity methodBody, MemberReferenceEntity methodDeclaration)
+ {
+ return new MethodImplementationEntity(methodBody, methodDeclaration);
+ }
+
+ public FileEntity GetOrCreateFile(string name, bool hasMetadata, BlobBuilder? hash)
+ {
+ return GetOrCreateEntity(name, TableIndex.File, _seenFiles, (name) => new FileEntity(name), entity =>
+ {
+ entity.HasMetadata = hasMetadata;
+ entity.Hash = hash;
+ });
+ }
+
+ public FileEntity? FindFile(string name)
+ {
+ if (_seenFiles.TryGetValue(name, out var file))
+ {
+ return file;
+ }
+ return null;
+ }
+
+ public AssemblyReferenceEntity? FindAssemblyReference(string name)
+ {
+ if (_seenAssemblyRefs.TryGetValue(new AssemblyName(name), out var file))
+ {
+ return file;
+ }
+ return null;
+ }
+
+ public AssemblyReferenceEntity GetOrCreateAssemblyReference(string name, Version version, string? culture, BlobBuilder? publicKeyOrToken, AssemblyFlags flags, ProcessorArchitecture architecture)
+ {
+ AssemblyName key = new AssemblyName(name)
+ {
+ Version = version,
+ CultureName = culture,
+ Flags = (AssemblyNameFlags)flags,
+#pragma warning disable SYSLIB0037 // ProcessorArchitecture is obsolete
+ ProcessorArchitecture = architecture
+#pragma warning restore SYSLIB0037 // ProcessorArchitecture is obsolete
+ };
+ return GetOrCreateEntity(key, TableIndex.AssemblyRef, _seenAssemblyRefs, (value) => new AssemblyReferenceEntity(name), entity =>
+ {
+ entity.Version = version;
+ entity.Culture = culture;
+ entity.PublicKeyOrToken = publicKeyOrToken;
+ entity.Flags = flags;
+ entity.ProcessorArchitecture = architecture;
+ });
+ }
+
+ public ManifestResourceEntity CreateManifestResource(string name, uint offset)
+ {
+ return CreateEntity(TableIndex.ManifestResource, _manifestResourceEntities, () => new ManifestResourceEntity(name, offset));
+ }
+
+ public ExportedTypeEntity GetOrCreateExportedType(EntityBase? implementation, string @namespace, string name, Action onCreateType)
+ {
+ // We only key on the implementation if the type is nested.
+ return GetOrCreateEntity((implementation as ExportedTypeEntity, @namespace, name), TableIndex.ExportedType, _seenExportedTypes, (key) => new(key.Item3, key.Item2, key.Item1), onCreateType);
+ }
+
+ public ExportedTypeEntity? FindExportedType(ExportedTypeEntity? containingType, string @namespace, string @name)
+ {
+ if (_seenExportedTypes.TryGetValue((containingType, @namespace, name), out var typeDef))
+ {
+ return typeDef;
+ }
+ return null;
+ }
+
+ public IHasHandle? EntryPoint { get; set; }
+
+ public abstract class EntityBase : IHasHandle
+ {
+ public EntityHandle Handle { get; private set; }
+
+ protected virtual void SetHandle(EntityHandle token)
+ {
+ Handle = token;
+ }
+
+ void IHasHandle.SetHandle(EntityHandle token) => SetHandle(token);
+ }
+
+ public abstract class TypeEntity : EntityBase
+ {
+ }
+
+ public interface IHasReflectionNotation
+ {
+ string ReflectionNotation { get; }
+ }
+
+ // COMPAT: The ilasm grammar allows ModuleRefs and AssemblyRefs to be returned in addition to types in typeSpec rules and arbitrary tokens to be returned
+ // by the mdtoken grammar, which can replace a type amongst other things.
+ // We'll record the actual handle as callers might need to retrieve it,
+ // but for emit we'll use the specialized properties depending on the case where the invalid handle is used.
+ public sealed class FakeTypeEntity : TypeEntity
+ {
+ public FakeTypeEntity(EntityHandle realEntity)
+ {
+ ((IHasHandle)this).SetHandle(realEntity);
+ TypeColumnHandle = default(TypeDefinitionHandle);
+ ResolutionScopeColumnHandle = default(ModuleDefinitionHandle);
+ TypeSignatureHandle = MetadataTokens.TypeDefinitionHandle(MetadataTokens.GetRowNumber(realEntity));
+ ImplementationHandle = default(AssemblyReferenceHandle);
+ }
+
+ ///
+ /// For cases where an arbitrary (non-TypeDefOrRefOrSpec) token is referenced by a column in a metadata table that uses coded indices, the encoding fails,
+ /// ilasm asserts, and a nil token for 0 option (TypeDef) of the coded index is emitted.
+ ///
+ public EntityHandle TypeColumnHandle { get; }
+
+ ///
+ /// For cases where an arbitrary (non-ResolutionScope) token is referenced by a column in a metadata table that uses coded indices, the encoding fails,
+ /// ilasm asserts, and a nil token for 0 option (ModuleDefinition) of the coded index is emitted.
+ ///
+ public EntityHandle ResolutionScopeColumnHandle { get; }
+
+ ///
+ /// In cases where an arbitrary (non-TypeDefOrRefOrSpec) token is referenced in a metadata blob (like a signature blob),
+ /// the token is emitted as a compressed integer of the row entry, shifted to account for encoding the table type.
+ ///
+ public EntityHandle TypeSignatureHandle { get; }
+
+ ///
+ /// In cases where an arbitrary (non-Implementation) token is referenced in a column in a metadata table,
+ /// the the token is emitted as the nil token.
+ ///
+ public EntityHandle ImplementationHandle { get; }
+ }
+
+ public sealed class TypeDefinitionEntity : TypeEntity, IHasReflectionNotation
+ {
+ public TypeDefinitionEntity(TypeDefinitionEntity? containingType, string @namespace, string name)
+ {
+ ContainingType = containingType;
+ Namespace = @namespace;
+ Name = name;
+
+ ReflectionNotation = CreateReflectionNotation(this);
+
+ static string CreateReflectionNotation(TypeDefinitionEntity typeDefinition)
+ {
+ StringBuilder builder = new();
+ Stack containingTypes = new();
+ for (TypeDefinitionEntity? containingType = typeDefinition; containingType is not null; containingType = containingType.ContainingType)
+ {
+ containingTypes.Push(containingType);
+ }
+ while (containingTypes.Count != 0)
+ {
+ TypeDefinitionEntity containingType = containingTypes.Pop();
+ builder.Append(containingType.Namespace);
+ builder.Append('.');
+ builder.Append(containingType.Name);
+ if (containingTypes.Count > 0)
+ {
+ builder.Append('+');
+ }
+ }
+ return builder.ToString();
+ }
+ }
+ public TypeDefinitionEntity? ContainingType { get; }
+ public string Namespace { get; }
+ public string Name { get; }
+ public TypeAttributes Attributes { get; set; }
+ public TypeEntity? BaseType { get; set; }
+
+ public NamedElementList GenericParameters { get; } = new();
+
+ // COMPAT: Save the list of generic parameter constraints here to ensure we can match ILASM's emit order for generic parameter constraints exactly.
+ public List GenericParameterConstraints { get; } = new();
+
+ public List Methods { get; } = new();
+
+ public List MethodImplementations { get; } = new();
+
+ public List Fields { get; } = new();
+
+ public List Properties { get; } = new();
+
+ public List Events { get; } = new();
+
+ public List InterfaceImplementations { get; } = new();
+
+ public string ReflectionNotation { get; }
+ }
+
+ public sealed class TypeReferenceEntity(EntityBase resolutionScope, string @namespace, string name) : TypeEntity, IHasReflectionNotation
+ {
+ public EntityBase ResolutionScope { get; } = resolutionScope;
+
+ public string Namespace { get; } = @namespace;
+
+ public string Name { get; } = name;
+
+ public string ReflectionNotation { get; set; } = string.Empty;
+ }
+
+ public sealed class TypeSpecificationEntity(BlobBuilder signature) : TypeEntity
+ {
+ public BlobBuilder Signature { get; } = signature;
+ }
+
+ public sealed class GenericParameterEntity(GenericParameterAttributes attributes, string name) : EntityBase, INamed
+ {
+ public GenericParameterAttributes Attributes { get; } = attributes;
+
+ public EntityBase? Owner { get; set; }
+
+ public int Index { get; set; }
+
+ public List Constraints { get; } = new();
+
+ public string Name { get; } = name;
+ }
+
+ public sealed class GenericParameterConstraintEntity(TypeEntity baseType) : EntityBase
+ {
+ public GenericParameterEntity? Owner { get; set; }
+
+ public TypeEntity BaseType { get; } = baseType;
+ }
+
+ public sealed class ModuleReferenceEntity(string name) : EntityBase
+ {
+ public string Name { get; } = name;
+ }
+
+ public sealed class MethodDefinitionEntity(TypeDefinitionEntity containingType, string name) : EntityBase, IHasHandle
+ {
+ public TypeDefinitionEntity ContainingType { get; } = containingType;
+ public string Name { get; } = name;
+
+ public MethodAttributes MethodAttributes { get; set; }
+
+ public List Parameters { get; } = new();
+
+ public NamedElementList GenericParameters { get; } = new();
+
+ // COMPAT: Save the list of generic parameter constraints here to ensure we can match ILASM's emit order for generic parameter constraints exactly.
+ public List GenericParameterConstraints { get; } = new();
+
+ public SignatureHeader SignatureHeader { get; set; }
+
+ public BlobBuilder? MethodSignature { get; set; }
+
+ public StandaloneSignatureEntity? LocalsSignature { get; set; }
+
+ public InstructionEncoder MethodBody { get; } = new(new BlobBuilder(), new ControlFlowBuilder());
+
+ public MethodBodyAttributes BodyAttributes { get; set; }
+
+ public int MaxStack { get; set; }
+
+ public (ModuleReferenceEntity ModuleName, string? EntryPointName, MethodImportAttributes Attributes)? MethodImportInformation { get; set; }
+ public MethodImplAttributes ImplementationAttributes { get; set; }
+ }
+
+ public sealed class ParameterEntity(ParameterAttributes attributes, string? name, BlobBuilder marshallingDescriptor, int sequence) : EntityBase
+ {
+ public ParameterAttributes Attributes { get; } = attributes;
+ public string? Name { get; } = name;
+ public BlobBuilder MarshallingDescriptor { get; set; } = marshallingDescriptor;
+ public bool HasCustomAttributes { get; set; }
+ public int Sequence { get; } = sequence;
+ }
+
+ public sealed class MemberReferenceEntity(EntityBase parent, string name, BlobBuilder signature) : EntityBase
+ {
+ // In the case of a MemberRef to a specific instantiation of a vararg method, we need to update the owner at emit time.
+ public EntityBase Parent { get; private set; } = parent;
+ public string Name { get; } = name;
+ public BlobBuilder Signature { get; } = signature;
+
+ private readonly List _placesToWriteResolvedHandle = new();
+
+ public void RecordBlobToWriteResolvedHandle(Blob blob)
+ {
+ _placesToWriteResolvedHandle.Add(blob);
+ }
+
+ protected override void SetHandle(EntityHandle token)
+ {
+ base.SetHandle(token);
+ // Now that we've set the handle, backpatch all the blobs that need to be updated.
+ // This way we can determine the right token to use for the member reference
+ // after we've processed all of the source code.
+ foreach (var blob in _placesToWriteResolvedHandle)
+ {
+ var writer = new BlobWriter(blob);
+ writer.WriteInt32(MetadataTokens.GetToken(token));
+ }
+ }
+
+ internal void SetMemberRefParent(MemberReferenceEntity parent)
+ {
+ Parent = parent;
+ }
+ }
+
+ public sealed class MethodSpecificationEntity(EntityBase parent, BlobBuilder signature) : EntityBase
+ {
+ public EntityBase Parent { get; } = parent;
+ public BlobBuilder Signature { get; } = signature;
+ }
+
+ public sealed class StandaloneSignatureEntity(BlobBuilder signature) : EntityBase
+ {
+ public BlobBuilder Signature { get; } = signature;
+ }
+
+ public sealed class DeclarativeSecurityAttributeEntity(DeclarativeSecurityAction action, BlobBuilder permissionSet) : EntityBase
+ {
+ public EntityBase? Parent { get; set; }
+ public DeclarativeSecurityAction Action { get; } = action;
+ public BlobBuilder PermissionSet { get; } = permissionSet;
+ }
+
+ public sealed class CustomAttributeEntity(EntityBase constructor, BlobBuilder value) : EntityBase
+ {
+ public EntityBase? Owner { get; set; }
+ public EntityBase Constructor { get; } = constructor;
+ public BlobBuilder Value { get; } = value;
+ }
+
+ public sealed class MethodImplementationEntity(MethodDefinitionEntity methodBody, MemberReferenceEntity methodDeclaration) : EntityBase
+ {
+ public MethodDefinitionEntity MethodBody { get; } = methodBody;
+ public MemberReferenceEntity MethodDeclaration { get; } = methodDeclaration;
+ }
+
+ public sealed class FieldDefinitionEntity(FieldAttributes attributes, TypeDefinitionEntity type, string name, BlobBuilder signature) : EntityBase
+ {
+ public FieldAttributes Attributes { get; } = attributes;
+ public TypeDefinitionEntity ContainingType { get; } = type;
+ public string Name { get; } = name;
+ public BlobBuilder Signature { get; } = signature;
+
+ public BlobBuilder? MarshallingDescriptor { get; set; }
+ public string? DataDeclarationName { get; set; }
+ }
+
+ public sealed class InterfaceImplementationEntity(TypeDefinitionEntity type, TypeEntity interfaceType) : EntityBase
+ {
+ public TypeDefinitionEntity Type { get; } = type;
+ public TypeEntity InterfaceType { get; } = interfaceType;
+ }
+
+ public sealed class EventEntity(EventAttributes attributes, TypeEntity type, string name) : EntityBase
+ {
+ public EventAttributes Attributes { get; } = attributes;
+ public TypeEntity Type { get; } = type;
+ public string Name { get; } = name;
+
+ public List<(MethodSemanticsAttributes Semantic, EntityBase Method)> Accessors { get; } = new();
+ }
+
+ public sealed class PropertyEntity(PropertyAttributes attributes, BlobBuilder type, string name) : EntityBase
+ {
+ public PropertyAttributes Attributes { get; } = attributes;
+ public BlobBuilder Type { get; } = type;
+ public string Name { get; } = name;
+
+ public List<(MethodSemanticsAttributes Semantic, EntityBase Method)> Accessors { get; } = new();
+ }
+
+ public sealed class FileEntity(string name) : EntityBase
+ {
+ public string Name { get; } = name;
+ public bool HasMetadata { get; set; }
+ public BlobBuilder? Hash { get; set; }
+ }
+
+ public sealed class ModuleEntity : EntityBase
+ {
+ public string? Name { get; set; }
+ }
+
+ public abstract class AssemblyOrRefEntity(string name) : EntityBase
+ {
+ public string Name { get; set; } = name;
+ public Version? Version { get; set; }
+ public string? Culture { get; set; }
+ public BlobBuilder? PublicKeyOrToken { get; set; }
+ public AssemblyFlags Flags { get; set; }
+ public ProcessorArchitecture ProcessorArchitecture { get; set; }
+ }
+
+ public sealed class AssemblyEntity(string name) : AssemblyOrRefEntity(name)
+ {
+ public AssemblyHashAlgorithm HashAlgorithm { get; set; }
+ }
+
+ public sealed class AssemblyReferenceEntity(string name) : AssemblyOrRefEntity(name)
+ {
+ public BlobBuilder? Hash { get; set; }
+ }
+
+ public sealed class ManifestResourceEntity(string name, uint offset) : EntityBase
+ {
+ public string Name { get; } = name;
+ public uint Offset { get; } = offset;
+ public ManifestResourceAttributes Attributes { get; set; }
+ public EntityBase? Implementation { get; set; }
+ }
+
+ public sealed class ExportedTypeEntity : EntityBase
+ {
+ public ExportedTypeEntity(string name, string @namespace, EntityBase? implementation)
+ {
+ Name = name;
+ Namespace = @namespace;
+ Implementation = implementation;
+ }
+
+ public string Name { get; }
+ public string Namespace { get; }
+ public TypeAttributes Attributes { get; set; }
+ public EntityBase? Implementation { get; }
+
+ public int TypeDefinitionId { get; set; }
+ }
+ }
+}
diff --git a/src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs b/src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs
new file mode 100644
index 00000000000000..d7db285d256924
--- /dev/null
+++ b/src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs
@@ -0,0 +1,4348 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Buffers.Binary;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text;
+using System.Xml.XPath;
+using Antlr4.Runtime;
+using Antlr4.Runtime.Misc;
+using Antlr4.Runtime.Tree;
+
+namespace ILAssembler
+{
+ internal abstract record GrammarResult
+ {
+ protected GrammarResult() { }
+
+ public sealed record String(string Value) : GrammarResult;
+
+ public sealed record Literal(T Value) : GrammarResult;
+
+ public sealed record Sequence(ImmutableArray Value) : GrammarResult;
+
+ ///
+ /// A formatted blob of bytes.
+ ///
+ /// The bytes of the blob.
+ public sealed record FormattedBlob(BlobBuilder Value) : GrammarResult;
+
+ public sealed record SentinelValue
+ {
+ public static SentinelValue Instance { get; } = new();
+
+ public static Literal Result { get; } = new(Instance);
+ }
+
+ public sealed record Flag(T Value, bool ShouldAppend = true) : GrammarResult
+ where T : struct, Enum
+ {
+ private readonly T _groupMask;
+ public Flag(T value, bool shouldAppend, T groupMask)
+ : this(value, shouldAppend)
+ {
+ _groupMask = groupMask;
+ }
+ public Flag(T value, T groupMask)
+ : this(value)
+ {
+ _groupMask = groupMask;
+ }
+
+ public static T operator |(T lhs, Flag rhs)
+ {
+ if (!rhs.ShouldAppend)
+ {
+ return rhs.Value;
+ }
+ return (T)(object)(((int)(object)lhs & (~(int)(object)rhs._groupMask)) | (int)(object)rhs.Value);
+ }
+ }
+ }
+
+#pragma warning disable CA1822 // Mark members as static
+ internal sealed class GrammarVisitor : ICILVisitor
+ {
+ private const string NodeShouldNeverBeDirectlyVisited = "This node should never be directly visited. It should be directly processed by its parent node.";
+ private readonly ImmutableArray.Builder _diagnostics = ImmutableArray.CreateBuilder();
+ private readonly EntityRegistry _entityRegistry = new();
+ private readonly IReadOnlyDictionary _documents;
+ private readonly Options _options;
+ private readonly MetadataBuilder _metadataBuilder = new();
+ private readonly Func _resourceLocator;
+
+ // Record the mapped field data directly into the blob to ensure we preserve ordering
+ private readonly BlobBuilder _mappedFieldData = new();
+ private readonly Dictionary _mappedFieldDataNames = new();
+ private readonly Dictionary> _mappedFieldDataReferenceFixups = new();
+ private readonly BlobBuilder _manifestResources = new();
+
+ public GrammarVisitor(IReadOnlyDictionary documents, Options options, Func resourceLocator)
+ {
+ _documents = documents;
+ _options = options;
+ _resourceLocator = resourceLocator;
+ }
+
+ public (ImmutableArray Diagnostics, PEBuilder? Image) BuildImage()
+ {
+ if (_diagnostics.Any(diag => diag.Severity == DiagnosticSeverity.Error))
+ {
+ return (_diagnostics.ToImmutable(), null);
+ }
+
+ BlobBuilder ilStream = new();
+ _entityRegistry.WriteContentTo(_metadataBuilder, ilStream);
+ MetadataRootBuilder rootBuilder = new(_metadataBuilder);
+ PEHeaderBuilder header = new(
+ fileAlignment: _alignment,
+ imageBase: (ulong)_imageBase,
+ subsystem: _subsystem);
+
+ MethodDefinitionHandle entryPoint = default;
+ if (_entityRegistry.EntryPoint is not null)
+ {
+ entryPoint = (MethodDefinitionHandle)_entityRegistry.EntryPoint.Handle;
+ }
+
+ ManagedPEBuilder peBuilder = new(
+ header,
+ rootBuilder,
+ ilStream,
+ _mappedFieldData,
+ _manifestResources,
+ flags: CorFlags.ILOnly, entryPoint: entryPoint);
+
+ return (_diagnostics.ToImmutable(), peBuilder);
+ }
+
+ public GrammarResult Visit(IParseTree tree) => tree.Accept(this);
+
+ GrammarResult ICILVisitor.VisitAlignment(CILParser.AlignmentContext context) => VisitAlignment(context);
+ public GrammarResult.Literal VisitAlignment(CILParser.AlignmentContext context)
+ {
+ return VisitInt32(context.int32());
+ }
+
+ GrammarResult ICILVisitor.VisitAsmAttr(CILParser.AsmAttrContext context) => VisitAsmAttr(context);
+ public GrammarResult.Literal VisitAsmAttr(CILParser.AsmAttrContext context)
+ => new(context.asmAttrAny().Select(VisitAsmAttrAny).Aggregate((AssemblyFlags)0, (lhs, rhs) => lhs | rhs));
+ GrammarResult ICILVisitor.VisitAsmAttrAny(CILParser.AsmAttrAnyContext context) => VisitAsmAttrAny(context);
+ public GrammarResult.Flag VisitAsmAttrAny(CILParser.AsmAttrAnyContext context)
+ {
+ return context.GetText() switch
+ {
+ "retargetable" => new(AssemblyFlags.Retargetable),
+ "windowsruntime" => new(AssemblyFlags.WindowsRuntime),
+ "noplatform" => new(AssemblyFlags.NoPlatform),
+ "legacy library" => new(0),
+ "cil" => new(GetFlagForArch(ProcessorArchitecture.MSIL), AssemblyFlags.ArchitectureMask),
+ "x86" => new(GetFlagForArch(ProcessorArchitecture.X86), AssemblyFlags.ArchitectureMask),
+ "amd64" => new(GetFlagForArch(ProcessorArchitecture.Amd64), AssemblyFlags.ArchitectureMask),
+ "arm" => new(GetFlagForArch(ProcessorArchitecture.Arm), AssemblyFlags.ArchitectureMask),
+ "arm64" => new(GetFlagForArch((ProcessorArchitecture)6), AssemblyFlags.ArchitectureMask),
+ _ => throw new UnreachableException()
+ };
+ }
+
+ private static AssemblyFlags GetFlagForArch(ProcessorArchitecture arch)
+ {
+ return (AssemblyFlags)((int)arch << 4);
+ }
+
+ private static (ProcessorArchitecture, AssemblyFlags) GetArchAndFlags(AssemblyFlags flags)
+ {
+ var arch = (ProcessorArchitecture)(((int)flags & 0xF0) >> 4);
+ var newFlags = flags & ~((AssemblyFlags)((int)arch << 4));
+ return (arch, newFlags);
+ }
+
+ private EntityRegistry.AssemblyOrRefEntity? _currentAssemblyOrRef;
+ public GrammarResult VisitAsmOrRefDecl(CILParser.AsmOrRefDeclContext context)
+ {
+ Debug.Assert(_currentAssemblyOrRef is not null);
+
+ if (context.customAttrDecl() is { } attr)
+ {
+ var customAttr = VisitCustomAttrDecl(attr).Value;
+ customAttr?.Owner = _currentAssemblyOrRef;
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ string decl = context.GetChild(0).GetText();
+ if (decl == ".publicKey")
+ {
+ BlobBuilder blob = new();
+ blob.WriteBytes(VisitBytes(context.bytes()).Value);
+ _currentAssemblyOrRef!.PublicKeyOrToken = blob;
+ }
+ else if (decl == ".ver")
+ {
+ var versionComponents = context.intOrWildcard();
+ _currentAssemblyOrRef!.Version = new Version(
+ VisitIntOrWildcard(versionComponents[0]).Value ?? 0,
+ VisitIntOrWildcard(versionComponents[1]).Value ?? 0,
+ VisitIntOrWildcard(versionComponents[2]).Value ?? 0,
+ VisitIntOrWildcard(versionComponents[3]).Value ?? 0);
+ }
+ else if (decl == ".locale")
+ {
+ _currentAssemblyOrRef!.Culture = context.compQstring() is { } compQstring
+ ? VisitCompQstring(compQstring).Value
+ : Encoding.Unicode.GetString([.. VisitBytes(context.bytes()).Value]);
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ GrammarResult ICILVisitor.VisitAssemblyBlock(CILParser.AssemblyBlockContext context) => VisitAssemblyBlock(context);
+ public GrammarResult VisitAssemblyBlock(CILParser.AssemblyBlockContext context)
+ {
+ _entityRegistry.Assembly ??= new EntityRegistry.AssemblyEntity(VisitDottedName(context.dottedName()).Value);
+ var attr = VisitAsmAttr(context.asmAttr()).Value;
+ (_entityRegistry.Assembly.ProcessorArchitecture, _entityRegistry.Assembly.Flags) = GetArchAndFlags(attr);
+ foreach (var decl in context.assemblyDecls().assemblyDecl())
+ {
+ VisitAssemblyDecl(decl);
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ GrammarResult ICILVisitor.VisitAssemblyDecl(CILParser.AssemblyDeclContext context) => VisitAssemblyDecl(context);
+ public GrammarResult VisitAssemblyDecl(CILParser.AssemblyDeclContext context)
+ {
+ if (context.secDecl() is { } secDecl)
+ {
+ var declarativeSecurity = VisitSecDecl(secDecl);
+ if (declarativeSecurity.Value is { } sec)
+ {
+ sec.Parent = _entityRegistry.Assembly;
+ }
+ }
+ else if (context.int32() is { } hashAlg)
+ {
+ _entityRegistry.Assembly!.HashAlgorithm = (AssemblyHashAlgorithm)VisitInt32(hashAlg).Value;
+ }
+ else if (context.asmOrRefDecl() is { } asmOrRef)
+ {
+ _currentAssemblyOrRef = _entityRegistry.Assembly;
+ VisitAsmOrRefDecl(asmOrRef);
+ _currentAssemblyOrRef = null;
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitAssemblyDecls(CILParser.AssemblyDeclsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+ public GrammarResult VisitAssemblyRefDecl(CILParser.AssemblyRefDeclContext context)
+ {
+ if (context.asmOrRefDecl() is { } asmOrRef)
+ {
+ VisitAsmOrRefDecl(asmOrRef);
+ }
+ string decl = context.GetChild(0).GetText();
+ if (decl == ".hash")
+ {
+ var blob = new BlobBuilder();
+ blob.WriteBytes(VisitBytes(context.bytes()).Value);
+ ((EntityRegistry.AssemblyReferenceEntity)_currentAssemblyOrRef!).Hash = blob;
+ }
+ if (decl == ".publickeytoken")
+ {
+ var blob = new BlobBuilder();
+ blob.WriteBytes(VisitBytes(context.bytes()).Value);
+ _currentAssemblyOrRef!.PublicKeyOrToken = blob;
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitAssemblyRefDecls(CILParser.AssemblyRefDeclsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+ GrammarResult ICILVisitor.VisitAssemblyRefHead(CILParser.AssemblyRefHeadContext context) => VisitAssemblyRefHead(context);
+ public GrammarResult.Literal VisitAssemblyRefHead(CILParser.AssemblyRefHeadContext context)
+ {
+ var (arch, flags) = GetArchAndFlags(VisitAsmAttr(context.asmAttr()).Value);
+ var dottedNames = context.dottedName();
+ string name = VisitDottedName(dottedNames[0]).Value;
+ string alias = name;
+ if (dottedNames.Length > 1)
+ {
+ alias = VisitDottedName(dottedNames[1]).Value;
+ }
+ return new(_entityRegistry.GetOrCreateAssemblyReference(alias, asmref =>
+ {
+ asmref.Name = name;
+ asmref.Flags = flags;
+ asmref.ProcessorArchitecture = arch;
+ }));
+ }
+
+ GrammarResult ICILVisitor.VisitAtOpt(CILParser.AtOptContext context) => VisitAtOpt(context);
+ public static GrammarResult.Literal VisitAtOpt(CILParser.AtOptContext context) => context.id() is {} id ? new(VisitId(id).Value) : new(null);
+
+ GrammarResult ICILVisitor.VisitBoolSeq(CILParser.BoolSeqContext context) => VisitBoolSeq(context);
+ public static GrammarResult.FormattedBlob VisitBoolSeq(CILParser.BoolSeqContext context)
+ {
+ var builder = ImmutableArray.CreateBuilder();
+
+ foreach (var item in context.truefalse())
+ {
+ builder.AddRange(VisitTruefalse(item).Value);
+ }
+
+ return new(builder.ToImmutable().SerializeSequence());
+ }
+ GrammarResult ICILVisitor.VisitBound(CILParser.BoundContext context) => VisitBound(context);
+ public GrammarResult.Literal<(int? Lower, int? Upper)> VisitBound(CILParser.BoundContext context)
+ {
+ bool hasEllipsis = context.ELLIPSIS() is not null;
+ if (context.ChildCount == 0 || (context.ChildCount == 1 && hasEllipsis))
+ {
+ return new((null, null));
+ }
+
+ var indicies = context.int32();
+
+ int firstValue = VisitInt32(indicies[0]).Value;
+
+ return (indicies.Length, hasEllipsis) switch
+ {
+ (1, false) => new((0, firstValue)),
+ (1, true) => new((firstValue, null)),
+ (2, false) => new((firstValue, VisitInt32(indicies[1]).Value - firstValue + 1)),
+ _ => throw new UnreachableException()
+ };
+ }
+
+ GrammarResult ICILVisitor.VisitBounds(CILParser.BoundsContext context) => VisitBounds(context);
+ public GrammarResult.Sequence<(int? Lower, int? Upper)> VisitBounds(CILParser.BoundsContext context)
+ {
+ return new(context.bound().Select(bound => VisitBound(bound).Value).ToImmutableArray());
+ }
+
+ GrammarResult ICILVisitor.VisitBytes(CILParser.BytesContext context) => VisitBytes(context);
+ public static GrammarResult.Sequence VisitBytes(CILParser.BytesContext context)
+ {
+ var builder = ImmutableArray.CreateBuilder();
+
+ foreach (var item in context.hexbytes())
+ {
+ builder.AddRange(VisitHexbytes(item).Value);
+ }
+
+ return new(builder.ToImmutable());
+ }
+ GrammarResult ICILVisitor.VisitCallConv(CILParser.CallConvContext context) => VisitCallConv(context);
+ public GrammarResult.Literal VisitCallConv(CILParser.CallConvContext context)
+ {
+ if (context.callKind() is CILParser.CallKindContext callKind)
+ {
+ return new((byte)VisitCallKind(callKind).Value);
+ }
+ else if (context.int32() is CILParser.Int32Context int32)
+ {
+ return new((byte)VisitInt32(int32).Value);
+ }
+ else if (context.INSTANCE() is not null)
+ {
+ return new((byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureAttributes.Instance));
+ }
+ else if (context.EXPLICIT() is not null)
+ {
+ return new((byte)(VisitCallConv(context.callConv()).Value | (byte)SignatureAttributes.ExplicitThis));
+ }
+ return new(0);
+ }
+ GrammarResult ICILVisitor.VisitCallKind(CILParser.CallKindContext context) => VisitCallKind(context);
+ public GrammarResult.Literal VisitCallKind(CILParser.CallKindContext context)
+ {
+ int childType = context.GetChild(context.ChildCount - 1).Symbol.Type;
+ return new(childType switch
+ {
+ CILParser.DEFAULT => SignatureCallingConvention.Default,
+ CILParser.VARARG => SignatureCallingConvention.VarArgs,
+ CILParser.CDECL => SignatureCallingConvention.CDecl,
+ CILParser.STDCALL => SignatureCallingConvention.StdCall,
+ CILParser.THISCALL => SignatureCallingConvention.ThisCall,
+ CILParser.FASTCALL => SignatureCallingConvention.FastCall,
+ CILParser.UNMANAGED => SignatureCallingConvention.Unmanaged,
+ _ => throw new UnreachableException()
+ });
+ }
+
+ GrammarResult ICILVisitor.VisitCatchClause(CILParser.CatchClauseContext context) => VisitCatchClause(context);
+ public GrammarResult.Literal VisitCatchClause(CILParser.CatchClauseContext context) => VisitTypeSpec(context.typeSpec());
+
+ GrammarResult ICILVisitor.VisitCaValue(CILParser.CaValueContext context) => VisitCaValue(context);
+ public GrammarResult.FormattedBlob VisitCaValue(CILParser.CaValueContext context)
+ {
+ BlobBuilder blob = new();
+ if (context.truefalse() is CILParser.TruefalseContext truefalse)
+ {
+ blob.WriteByte((byte)SerializationTypeCode.Boolean);
+ blob.WriteBoolean(VisitTruefalse(truefalse).Value);
+ }
+ else if (context.compQstring() is CILParser.CompQstringContext str)
+ {
+ blob.WriteUTF8(VisitCompQstring(str).Value);
+ blob.WriteByte(0);
+ }
+ else if (context.className() is CILParser.ClassNameContext className)
+ {
+ var name = VisitClassName(className).Value;
+ blob.WriteByte((byte)SerializationTypeCode.Enum);
+ blob.WriteUTF8((name as EntityRegistry.IHasReflectionNotation)?.ReflectionNotation ?? "");
+ blob.WriteByte(0);
+ byte size = 4;
+ if (context.INT8() is not null)
+ {
+ size = 1;
+ }
+ else if (context.INT16() is not null)
+ {
+ size = 2;
+ }
+ blob.WriteByte(size);
+ blob.WriteInt32(VisitInt32(context.int32()).Value);
+ }
+ else
+ {
+ blob.WriteByte((byte)SerializationTypeCode.Int32);
+ blob.WriteInt32(VisitInt32(context.int32()).Value);
+ }
+ return new(blob);
+ }
+
+ public GrammarResult VisitChildren(IRuleNode node)
+ {
+ for (int i = 0; i < node.ChildCount; i++)
+ {
+ node.GetChild(i).Accept(this);
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ GrammarResult ICILVisitor.VisitClassAttr(CILParser.ClassAttrContext context) => VisitClassAttr(context);
+
+ public GrammarResult.Literal<(GrammarResult.Flag Attribute, EntityRegistry.WellKnownBaseType? FallbackBase, bool RequireSealed)> VisitClassAttr(CILParser.ClassAttrContext context)
+ {
+ if (context.int32() is CILParser.Int32Context int32)
+ {
+ int value = VisitInt32(int32).Value;
+ // COMPAT: The VALUE and ENUM keywords use sentinel values to pass through the fallback base type
+ // in ILASM. These sentinel values can be provided through the "pass the value of the flag" feature,
+ // so we detect those old flags here and provide the correct fallback type.
+ bool requireSealed = false;
+ EntityRegistry.WellKnownBaseType? fallbackBase = null;
+ if ((value & 0x80000000) != 0)
+ {
+ requireSealed = true;
+ fallbackBase = EntityRegistry.WellKnownBaseType.System_ValueType;
+ }
+ if ((value & 0x40000000) != 0)
+ {
+ fallbackBase = EntityRegistry.WellKnownBaseType.System_Enum;
+ }
+ // Mask off the sentinel bits
+ value &= unchecked((int)~0xC0000000);
+ // COMPAT: When explicit flags are provided they always supercede previously set flags
+ // (other than the sentinel values)
+ return new((new((TypeAttributes)value, ShouldAppend: false), fallbackBase, requireSealed));
+ }
+
+ if (context.ENUM() is not null)
+ {
+ // COMPAT: ilasm implies the Sealed flag when using the 'value' keyword in a type declaration
+ // even when the 'enum' keyword is used.
+ return new((new(context.VALUE() is not null ? TypeAttributes.Sealed : 0), EntityRegistry.WellKnownBaseType.System_Enum, false));
+ }
+ else if (context.VALUE() is not null)
+ {
+ // COMPAT: ilasm implies the Sealed flag when using the 'value' keyword in a type declaration
+ return new((new(TypeAttributes.Sealed), EntityRegistry.WellKnownBaseType.System_ValueType, true));
+ }
+
+ switch (context.GetText())
+ {
+ case "private":
+ return new((new(TypeAttributes.NotPublic), null, false));
+ case "ansi":
+ return new((new(TypeAttributes.AnsiClass), null, false));
+ case "autochar":
+ return new((new(TypeAttributes.AutoClass), null, false));
+ case "auto":
+ return new((new(TypeAttributes.AutoLayout), null, false));
+ case "sequential":
+ return new((new(TypeAttributes.SequentialLayout), null, false));
+ case "explicit":
+ return new((new(TypeAttributes.ExplicitLayout), null, false));
+ case "extended":
+ return new((new(TypeAttributes.ExtendedLayout), null, false));
+ default:
+ return new((new((TypeAttributes)Enum.Parse(typeof(TypeAttributes), context.GetText(), true)), null, false));
+ }
+ }
+
+ private sealed class CurrentMethodContext
+ {
+ public CurrentMethodContext(EntityRegistry.MethodDefinitionEntity definition)
+ {
+ Definition = definition;
+ }
+
+ public EntityRegistry.MethodDefinitionEntity Definition { get; }
+
+ public Dictionary Labels { get; } = new();
+
+ public Dictionary ArgumentNames { get; } = new();
+
+ public List> LocalsScopes { get; } = new();
+
+ public List AllLocals { get; } = new();
+ }
+
+ private CurrentMethodContext? _currentMethod;
+
+ public GrammarResult VisitClassDecl(CILParser.ClassDeclContext context)
+ {
+ if (context.classHead() is CILParser.ClassHeadContext classHead)
+ {
+ _currentTypeDefinition.Push(VisitClassHead(classHead).Value);
+ VisitClassDecls(context.classDecls());
+ _currentTypeDefinition.Pop();
+ }
+ else if (context.methodHead() is CILParser.MethodHeadContext methodHead)
+ {
+ _currentMethod = new(VisitMethodHead(methodHead).Value);
+ VisitMethodDecls(context.methodDecls());
+ _currentMethod = null;
+ }
+ else if (context.secDecl() is {} secDecl)
+ {
+ var declarativeSecurity = VisitSecDecl(secDecl).Value;
+ declarativeSecurity?.Parent = _currentTypeDefinition.PeekOrDefault();
+ }
+ else if (context.fieldDecl() is {} fieldDecl)
+ {
+ _ = VisitFieldDecl(fieldDecl);
+ }
+
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitClassDecls(CILParser.ClassDeclsContext context) => VisitChildren(context);
+
+
+ GrammarResult ICILVisitor.VisitClassHead(CILParser.ClassHeadContext context) => VisitClassHead(context);
+ public GrammarResult.Literal VisitClassHead(CILParser.ClassHeadContext context)
+ {
+ string typeFullName = VisitDottedName(context.dottedName()).Value;
+ int typeFullNameLastDot = typeFullName.LastIndexOf('.');
+ string typeNS;
+ if (_currentTypeDefinition.Count != 0)
+ {
+ if (typeFullNameLastDot == -1)
+ {
+ typeNS = string.Empty;
+ }
+ else
+ {
+ typeNS = typeFullName.Substring(0, typeFullNameLastDot);
+ }
+ }
+ else
+ {
+ if (typeFullNameLastDot == -1)
+ {
+ typeNS = _currentNamespace.PeekOrDefault() ?? string.Empty;
+ }
+ else
+ {
+ typeNS = $"{_currentNamespace.PeekOrDefault()}{typeFullName.Substring(0, typeFullNameLastDot)}";
+ }
+ }
+
+ bool isNewType = false;
+
+ var typeDefinition = _entityRegistry.GetOrCreateTypeDefinition(
+ _currentTypeDefinition.PeekOrDefault(),
+ typeNS,
+ typeFullNameLastDot != -1
+ ? typeFullName.Substring(typeFullNameLastDot)
+ : typeFullName,
+ (newTypeDef) =>
+ {
+ isNewType = true;
+ EntityRegistry.WellKnownBaseType? fallbackBase = _options.NoAutoInherit ? null : EntityRegistry.WellKnownBaseType.System_Object;
+ bool requireSealed = false;
+ newTypeDef.Attributes = context.classAttr().Select(VisitClassAttr).Aggregate(
+ (TypeAttributes)0,
+ (acc, result) =>
+ {
+ var (attribute, implicitBase, attrRequireSealed) = result.Value;
+ if (implicitBase is not null)
+ {
+ // COMPAT: Any base type specified by an attribute is ignored if
+ // the user specified an explicit base type in an 'extends' clause.
+ fallbackBase = implicitBase;
+ }
+ // COMPAT: When a flags value is specified as an integer, it overrides
+ // all of the provided flags, including any compat sentinel flags that will require
+ // the sealed modifier to be provided.
+ if (!attribute.ShouldAppend)
+ {
+ requireSealed = attrRequireSealed;
+ return attribute.Value;
+ }
+ requireSealed |= attrRequireSealed;
+ if (TypeAttributes.LayoutMask.HasFlag(attribute.Value))
+ {
+ return (acc & ~TypeAttributes.LayoutMask) | attribute.Value;
+ }
+ if (TypeAttributes.StringFormatMask.HasFlag(attribute.Value))
+ {
+ return (acc & ~TypeAttributes.StringFormatMask) | attribute.Value;
+ }
+ if (TypeAttributes.VisibilityMask.HasFlag(attribute.Value))
+ {
+ return (acc & ~TypeAttributes.VisibilityMask) | attribute.Value;
+ }
+ if (attribute.Value == TypeAttributes.RTSpecialName)
+ {
+ // COMPAT: ILASM ignores the rtspecialname directive on a type.
+ return acc;
+ }
+ if (attribute.Value == TypeAttributes.Interface)
+ {
+ // COMPAT: interface implies abstract
+ return acc | TypeAttributes.Interface | TypeAttributes.Abstract;
+ }
+
+ return acc | attribute.Value;
+ });
+
+
+ for (int i = 0; i < VisitTyparsClause(context.typarsClause()).Value.Length; i++)
+ {
+ EntityRegistry.GenericParameterEntity? param = VisitTyparsClause(context.typarsClause()).Value[i];
+ param.Owner = newTypeDef;
+ param.Index = i;
+ newTypeDef.GenericParameters.Add(param);
+ foreach (var constraint in param.Constraints)
+ {
+ constraint.Owner = param;
+ newTypeDef.GenericParameterConstraints.Add(constraint);
+ }
+ }
+
+ // Temporarily push the new type as the current type definition so we can resolve type parameters
+ // that are used in the base type and interface types.
+ _currentTypeDefinition.Push(newTypeDef);
+
+ if (context.extendsClause() is CILParser.ExtendsClauseContext extends)
+ {
+ newTypeDef.BaseType = VisitExtendsClause(context.extendsClause()).Value;
+ }
+
+ if (context.implClause() is CILParser.ImplClauseContext impl)
+ {
+ newTypeDef.InterfaceImplementations.AddRange(VisitImplClause(context.implClause()).Value);
+ }
+
+ _currentTypeDefinition.Pop();
+
+ newTypeDef.BaseType ??= _entityRegistry.ResolveImplicitBaseType(fallbackBase);
+
+ // When the user has provided a type definition for a type that directly inherits
+ // System.ValueType but has not sealed it, emit a warning and add the 'sealed' modifier.
+ if (!newTypeDef.Attributes.HasFlag(TypeAttributes.Sealed) &&
+ (requireSealed // COMPAT: when both the sentinel values for 'value' and 'enum' are explicitly
+ // specified, the sealed modifier is required even though
+ // the base type isn't System.ValueType.
+ || _entityRegistry.SystemValueTypeType.Equals(newTypeDef.BaseType)))
+ {
+ _diagnostics.Add(
+ new Diagnostic(
+ DiagnosticIds.UnsealedValueType,
+ DiagnosticSeverity.Error,
+ string.Format(DiagnosticMessageTemplates.UnsealedValueType, newTypeDef.Name),
+ Location.From(context.dottedName().Stop, _documents)));
+ newTypeDef.Attributes |= TypeAttributes.Sealed;
+ }
+ });
+
+ if (!isNewType)
+ {
+ // COMPAT: Still visit some of the clauses to ensure the provided types are still imported,
+ // even if unused.
+ _ = context.extendsClause()?.Accept(this);
+ _ = context.typarsClause().Accept(this);
+ }
+
+ return new(typeDefinition);
+ }
+
+ GrammarResult ICILVisitor.VisitClassName(CILParser.ClassNameContext context) => VisitClassName(context);
+ public GrammarResult.Literal VisitClassName(CILParser.ClassNameContext context)
+ {
+ if (context.THIS() is not null)
+ {
+ if (_currentTypeDefinition.Count == 0)
+ {
+ // TODO: Report diagnostic.
+ return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
+ }
+ var thisType = _currentTypeDefinition.Peek();
+ return new(thisType);
+ }
+ else if (context.BASE() is not null)
+ {
+ if (_currentTypeDefinition.Count == 0)
+ {
+ // TODO: Report diagnostic.
+ return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
+ }
+ var baseType = _currentTypeDefinition.Peek().BaseType;
+ if (baseType is null)
+ {
+ // TODO: Report diagnostic.
+ return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
+ }
+ return new(baseType);
+ }
+ else if (context.NESTER() is not null)
+ {
+ if (_currentTypeDefinition.Count < 2)
+ {
+ // TODO: Report diagnostic.
+ return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
+ }
+ var nesterType = _currentTypeDefinition.Peek().ContainingType!;
+ return new(nesterType);
+ }
+ else if (context.slashedName() is CILParser.SlashedNameContext slashedName)
+ {
+ EntityRegistry.EntityBase? resolutionContext = null;
+ if (context.dottedName() is CILParser.DottedNameContext dottedAssemblyOrModuleName)
+ {
+ if (context.MODULE() is not null)
+ {
+ resolutionContext = _entityRegistry.FindModuleReference(VisitDottedName(dottedAssemblyOrModuleName).Value);
+ if (resolutionContext is null)
+ {
+ // TODO: Report diagnostic
+ return new(new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle)));
+ }
+ }
+ else
+ {
+ resolutionContext = _entityRegistry.GetOrCreateAssemblyReference(VisitDottedName(dottedAssemblyOrModuleName).Value, newRef => { });
+ }
+ }
+ else if (context.mdtoken() is CILParser.MdtokenContext typeRefScope)
+ {
+ resolutionContext = VisitMdtoken(typeRefScope).Value;
+ }
+ else if (context.PTR() is not null)
+ {
+ resolutionContext = new EntityRegistry.FakeTypeEntity(default(ModuleDefinitionHandle));
+ }
+
+ if (resolutionContext is not null)
+ {
+ EntityRegistry.TypeReferenceEntity typeRef = _entityRegistry.GetOrCreateTypeReference(resolutionContext, VisitSlashedName(slashedName).Value);
+ return new(typeRef);
+ }
+
+ Debug.Assert(resolutionContext is null);
+
+ return new(ResolveTypeDef());
+
+ // Resolve typedef references
+ EntityRegistry.TypeEntity ResolveTypeDef()
+ {
+ TypeName typeName = VisitSlashedName(slashedName).Value;
+ if (typeName.ContainingTypeName is null)
+ {
+ // TODO: Check for typedef.
+ }
+ Stack containingTypes = new();
+ for (TypeName? containingType = typeName; containingType is not null; containingType = containingType.ContainingTypeName)
+ {
+ containingTypes.Push(containingType);
+ }
+ EntityRegistry.TypeDefinitionEntity? typeDef = null;
+ while (containingTypes.Count != 0)
+ {
+ TypeName containingType = containingTypes.Pop();
+
+ (string ns, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName);
+
+ typeDef = _entityRegistry.FindTypeDefinition(
+ typeDef,
+ ns,
+ name);
+
+ if (typeDef is null)
+ {
+ // TODO: Report diagnostic for missing type name
+ return new EntityRegistry.FakeTypeEntity(default(TypeDefinitionHandle));
+ }
+ }
+
+ return typeDef!;
+ }
+ }
+ else if (context.mdtoken() is CILParser.MdtokenContext typeToken)
+ {
+ EntityRegistry.EntityBase resolvedToken = VisitMdtoken(typeToken).Value;
+
+ if (resolvedToken is not EntityRegistry.TypeEntity type)
+ {
+ return new(new EntityRegistry.FakeTypeEntity(resolvedToken.Handle));
+ }
+ return new(type);
+ }
+
+ throw new UnreachableException();
+ }
+
+ GrammarResult ICILVisitor.VisitClassSeq(CILParser.ClassSeqContext context) => VisitClassSeq(context);
+ public GrammarResult.FormattedBlob VisitClassSeq(CILParser.ClassSeqContext context)
+ {
+ // We're going to add all of the elements in the sequence as prefix blobs to this blob.
+ BlobBuilder objSeqBlob = new(0);
+ foreach (var item in context.classSeqElement())
+ {
+ objSeqBlob.LinkPrefix(VisitClassSeqElement(item).Value);
+ }
+ return new(objSeqBlob);
+ }
+
+ GrammarResult ICILVisitor.VisitClassSeqElement(CILParser.ClassSeqElementContext context) => VisitClassSeqElement(context);
+
+ public GrammarResult.FormattedBlob VisitClassSeqElement(CILParser.ClassSeqElementContext context)
+ {
+ BlobBuilder blob = new();
+ if (context.className() is CILParser.ClassNameContext className)
+ {
+ if (VisitClassName(className).Value is EntityRegistry.IHasReflectionNotation notation)
+ {
+ blob.WriteSerializedString(notation.ReflectionNotation);
+ }
+ else
+ {
+ blob.WriteSerializedString("");
+ }
+ return new(blob);
+ }
+
+ blob.WriteSerializedString(context.SQSTRING()?.Symbol.Text);
+ return new(blob);
+ }
+ public GrammarResult VisitCompControl(CILParser.CompControlContext context)
+ {
+ // All compilation control directives that need special handling will be handled
+ // directly in the token stream before parsing.
+ // Any that reach here can be ignored.
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ GrammarResult ICILVisitor.VisitCompQstring(CILParser.CompQstringContext context)
+ {
+ return VisitCompQstring(context);
+ }
+
+ private static GrammarResult.String VisitCompQstring(CILParser.CompQstringContext context)
+ {
+ StringBuilder builder = new();
+ foreach (var item in context.QSTRING())
+ {
+ builder.Append(item.Symbol.Text);
+ }
+ return new(builder.ToString());
+ }
+
+ GrammarResult ICILVisitor.VisitCorflags(CILParser.CorflagsContext context) => VisitCorflags(context);
+ public GrammarResult.Literal VisitCorflags(CILParser.CorflagsContext context) => VisitInt32(context.int32());
+
+ GrammarResult ICILVisitor.VisitCustomAttrDecl(CILParser.CustomAttrDeclContext context) => VisitCustomAttrDecl(context);
+ public GrammarResult.Literal VisitCustomAttrDecl(CILParser.CustomAttrDeclContext context)
+ {
+ if (context.dottedName() is {})
+ {
+ // TODO: typedef
+ return new(null);
+ }
+ if (context.customDescrWithOwner() is {} descrWithOwner)
+ {
+ // Visit the custom attribute descriptor to record it,
+ // but don't return it as it will already have its owner recorded.
+ _ = VisitCustomDescrWithOwner(descrWithOwner);
+ return new(null);
+ }
+ if (context.customDescr() is {} descr)
+ {
+#nullable disable // Disable nullability to work around lack of variance.
+ return VisitCustomDescr(descr);
+#nullable restore
+ }
+ throw new UnreachableException();
+ }
+
+ GrammarResult ICILVisitor.VisitCustomBlobArgs(CILParser.CustomBlobArgsContext context) => VisitCustomBlobArgs(context);
+ public GrammarResult.FormattedBlob VisitCustomBlobArgs(CILParser.CustomBlobArgsContext context)
+ {
+ BlobBuilder blob = new();
+ foreach (var item in context.serInit())
+ {
+ VisitSerInit(item).Value.WriteContentTo(blob);
+ }
+ return new(blob);
+ }
+
+ private const int CustomAttributeBlobFormatVersion = 1;
+
+ GrammarResult ICILVisitor.VisitCustomBlobDescr(CILParser.CustomBlobDescrContext context) => VisitCustomBlobDescr(context);
+ public GrammarResult.FormattedBlob VisitCustomBlobDescr(CILParser.CustomBlobDescrContext context)
+ {
+ var blob = new BlobBuilder();
+ blob.WriteInt32(CustomAttributeBlobFormatVersion);
+ VisitCustomBlobArgs(context.customBlobArgs()).Value.WriteContentTo(blob);
+ VisitCustomBlobNVPairs(context.customBlobNVPairs()).Value.WriteContentTo(blob);
+ return new(blob);
+ }
+
+ GrammarResult ICILVisitor.VisitCustomBlobNVPairs(CILParser.CustomBlobNVPairsContext context) => VisitCustomBlobNVPairs(context);
+ public GrammarResult.FormattedBlob VisitCustomBlobNVPairs(CILParser.CustomBlobNVPairsContext context)
+ {
+ var blob = new BlobBuilder();
+ var fieldOrProps = context.fieldOrProp();
+ var types = context.serializType();
+ var names = context.dottedName();
+ var values = context.serInit();
+
+ blob.WriteInt16((short)fieldOrProps.Length);
+
+ for (int i = 0; i < fieldOrProps.Length; i++)
+ {
+ var fieldOrProp = fieldOrProps[i].GetText() == "field" ? CustomAttributeNamedArgumentKind.Field : CustomAttributeNamedArgumentKind.Property;
+ var type = VisitSerializType(types[i]).Value;
+ var name = VisitDottedName(names[i]).Value;
+ var value = VisitSerInit(values[i]).Value;
+ blob.WriteByte((byte)fieldOrProp);
+ type.WriteContentTo(blob);
+ blob.WriteSerializedString(name);
+ value.WriteContentTo(blob);
+ }
+ return new(blob);
+ }
+
+ GrammarResult ICILVisitor.VisitCustomDescr(CILParser.CustomDescrContext context) => VisitCustomDescr(context);
+ public GrammarResult.Literal VisitCustomDescr(CILParser.CustomDescrContext context)
+ {
+ var ctor = VisitCustomType(context.customType()).Value;
+ BlobBuilder value;
+ if (context.customBlobDescr() is {} customBlobDescr)
+ {
+ value = VisitCustomBlobDescr(customBlobDescr).Value;
+ }
+ else if (context.bytes() is {} bytes)
+ {
+ value = new();
+ value.WriteBytes(VisitBytes(bytes).Value);
+ }
+ else if (context.compQstring() is {} str)
+ {
+ value = new();
+ value.WriteUTF8(VisitCompQstring(str).Value);
+ // COMPAT: We treat this string as a string-reprensentation of a blob,
+ // so we don't emit the null terminator.
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
+
+ return new(_entityRegistry.CreateCustomAttribute(ctor, value));
+ }
+
+ GrammarResult ICILVisitor.VisitCustomDescrWithOwner(CILParser.CustomDescrWithOwnerContext context) => VisitCustomDescrWithOwner(context);
+ public GrammarResult.Literal VisitCustomDescrWithOwner(CILParser.CustomDescrWithOwnerContext context)
+ {
+ var ctor = VisitCustomType(context.customType()).Value;
+ BlobBuilder value;
+ if (context.customBlobDescr() is {} customBlobDescr)
+ {
+ value = VisitCustomBlobDescr(customBlobDescr).Value;
+ }
+ else if (context.bytes() is {} bytes)
+ {
+ value = new();
+ value.WriteBytes(VisitBytes(bytes).Value);
+ }
+ else if (context.compQstring() is {} str)
+ {
+ value = new();
+ value.WriteUTF8(VisitCompQstring(str).Value);
+ // COMPAT: We treat this string as a string-reprensentation of a blob,
+ // so we don't emit the null terminator.
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
+
+ var attr = _entityRegistry.CreateCustomAttribute(ctor, value);
+
+ attr.Owner = VisitOwnerType(context.ownerType()).Value;
+
+ return new(attr);
+ }
+
+ GrammarResult ICILVisitor.VisitCustomType(CILParser.CustomTypeContext context) => VisitCustomType(context);
+ public GrammarResult.Literal VisitCustomType(CILParser.CustomTypeContext context) => VisitMethodRef(context.methodRef());
+
+ public GrammarResult VisitDataDecl(CILParser.DataDeclContext context)
+ {
+ _ = VisitDdHead(context.ddHead());
+ _ = VisitDdBody(context.ddBody());
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitDdBody(CILParser.DdBodyContext context)
+ {
+ if (context.ddItemList() is CILParser.DdItemListContext ddItemList)
+ {
+ _ = VisitDdItemList(ddItemList);
+ }
+ else
+ {
+ _ = VisitDdItem(context.ddItem());
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitDdHead(CILParser.DdHeadContext context)
+ {
+ if (context.id() is CILParser.IdContext id)
+ {
+ string name = VisitId(id).Value;
+ if (!_mappedFieldDataNames.ContainsKey(name))
+ {
+ _mappedFieldDataNames.Add(name, _mappedFieldData.Count);
+ }
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitDdItem(CILParser.DdItemContext context)
+ {
+ if (context.compQstring() is CILParser.CompQstringContext str)
+ {
+ var value = VisitCompQstring(str).Value;
+ _mappedFieldData.WriteUTF16(value);
+ return GrammarResult.SentinelValue.Result;
+ }
+ else if (context.id() is CILParser.IdContext id)
+ {
+ string name = VisitId(id).Value;
+ if (!_mappedFieldDataReferenceFixups.TryGetValue(name, out var fixups))
+ {
+ _mappedFieldDataReferenceFixups[name] = fixups = new();
+ }
+
+ // TODO: Figure out how to handle relocs correctly
+ fixups.Add(_mappedFieldData.ReserveBytes(4));
+ return GrammarResult.SentinelValue.Result;
+ }
+ else if (context.bytes() is CILParser.BytesContext bytes)
+ {
+ _mappedFieldData.WriteBytes(VisitBytes(bytes).Value);
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ int itemCount = VisitDdItemCount(context.ddItemCount()).Value;
+
+ if (context.INT8() is not null)
+ {
+ _mappedFieldData.WriteBytes(context.int32() is CILParser.Int32Context int32 ? (byte)VisitInt32(int32).Value : (byte)0, itemCount);
+ }
+ else if (context.INT16() is not null)
+ {
+ for (int i = 0; i < itemCount; i++)
+ {
+ _mappedFieldData.WriteInt16(context.int32() is CILParser.Int32Context int32 ? (short)VisitInt32(int32).Value : (short)0);
+ }
+ }
+ else if (context.INT32_() is not null)
+ {
+ for (int i = 0; i < itemCount; i++)
+ {
+ _mappedFieldData.WriteInt32(context.int32() is CILParser.Int32Context int32 ? VisitInt32(int32).Value : 0);
+ }
+ }
+ else if (context.INT64_() is not null)
+ {
+ for (int i = 0; i < itemCount; i++)
+ {
+ _mappedFieldData.WriteInt64(context.int64() is CILParser.Int64Context int64 ? VisitInt64(int64).Value : 0);
+ }
+ }
+ else if (context.FLOAT32() is not null)
+ {
+ for (int i = 0; i < itemCount; i++)
+ {
+ _mappedFieldData.WriteSingle(context.float64() is CILParser.Float64Context float64 ? (float)VisitFloat64(float64).Value : 0);
+ }
+ }
+ else if (context.FLOAT64_() is not null)
+ {
+ for (int i = 0; i < itemCount; i++)
+ {
+ _mappedFieldData.WriteDouble(context.float64() is CILParser.Float64Context float64 ? VisitFloat64(float64).Value : 0);
+ }
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ GrammarResult ICILVisitor.VisitDdItemCount(CILParser.DdItemCountContext context) => VisitDdItemCount(context);
+ public GrammarResult.Literal VisitDdItemCount(CILParser.DdItemCountContext context) => new(context.int32() is CILParser.Int32Context ? VisitInt32(context.int32()).Value : 1);
+ public GrammarResult VisitDdItemList(CILParser.DdItemListContext context)
+ {
+ foreach (var item in context.ddItem())
+ {
+ VisitDdItem(item);
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ private readonly Stack _currentNamespace = new();
+
+ private readonly Stack _currentTypeDefinition = new();
+
+ public GrammarResult VisitDecl(CILParser.DeclContext context)
+ {
+ if (context.nameSpaceHead() is CILParser.NameSpaceHeadContext ns)
+ {
+ string namespaceName = VisitNameSpaceHead(ns).Value;
+ _currentNamespace.Push($"{_currentNamespace.PeekOrDefault()}.{namespaceName}");
+ VisitDecls(context.decls());
+ _currentNamespace.Pop();
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.classHead() is CILParser.ClassHeadContext classHead)
+ {
+ _currentTypeDefinition.Push(VisitClassHead(classHead).Value);
+ VisitClassDecls(context.classDecls());
+ _currentTypeDefinition.Pop();
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.methodHead() is CILParser.MethodHeadContext methodHead)
+ {
+ _currentMethod = new(VisitMethodHead(methodHead).Value);
+ VisitMethodDecls(context.methodDecls());
+ _currentMethod = null;
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.fieldDecl() is { } fieldDecl)
+ {
+ _ = VisitFieldDecl(fieldDecl);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.dataDecl() is { } dataDecl)
+ {
+ _ = VisitDataDecl(dataDecl);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.vtableDecl() is { } vtable)
+ {
+ _ = VisitVtableDecl(vtable);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.vtfixupDecl() is { } vtFixup)
+ {
+ _ = VisitVtfixupDecl(vtFixup);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.extSourceSpec() is { } extSourceSpec)
+ {
+ _ = VisitExtSourceSpec(extSourceSpec);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.fileDecl() is { } fileDecl)
+ {
+ _ = VisitFileDecl(fileDecl);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.assemblyBlock() is { } assemblyBlock)
+ {
+ _ = VisitAssemblyBlock(assemblyBlock);
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.assemblyRefHead() is { } assemblyRef)
+ {
+ var asmRef = VisitAssemblyRefHead(assemblyRef).Value;
+ _currentAssemblyOrRef = asmRef;
+ foreach (var decl in context.assemblyRefDecls().assemblyRefDecl())
+ {
+ _ = VisitAssemblyRefDecl(decl);
+ }
+ _currentAssemblyOrRef = null;
+ }
+ if (context.exptypeHead() is { } exptypeHead)
+ {
+ var (attrs, dottedName) = VisitExptypeHead(exptypeHead).Value;
+ (string typeNamespace, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(dottedName);
+ var (impl, typeDefId, customAttrs) = VisitExptypeDecls(context.exptypeDecls()).Value;
+ var exp = _entityRegistry.GetOrCreateExportedType(impl, typeNamespace, name, exp =>
+ {
+ exp.Attributes = attrs;
+ exp.TypeDefinitionId = typeDefId;
+ });
+ foreach (var attr in customAttrs)
+ {
+ attr.Owner = exp;
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.manifestResHead() is { } manifestResHead)
+ {
+ var (name, alias, flags) = VisitManifestResHead(manifestResHead).Value;
+ var (implementation, offset, attrs) = VisitManifestResDecls(context.manifestResDecls()).Value;
+ if (implementation is null)
+ {
+ offset = (uint)_manifestResources.Count;
+ _manifestResources.WriteBytes(_resourceLocator(alias));
+ }
+ var res = _entityRegistry.CreateManifestResource(name, offset);
+ res.Attributes = flags;
+ res.Implementation = implementation;
+ foreach (var attr in attrs)
+ {
+ attr.Owner = res;
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.moduleHead() is { } moduleHead)
+ {
+ if (moduleHead.dottedName() is null)
+ {
+ _entityRegistry.Module.Name = null;
+ }
+ else if (moduleHead.ChildCount == 2)
+ {
+ _entityRegistry.Module.Name = VisitDottedName(moduleHead.dottedName()).Value;
+ }
+ else
+ {
+ var name = VisitDottedName(moduleHead.dottedName()).Value;
+ _entityRegistry.GetOrCreateModuleReference(name, _ => { });
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ if (context.subsystem() is { } subsystem)
+ {
+ _subsystem = (Subsystem)VisitSubsystem(subsystem).Value;
+ }
+ if (context.corflags() is { } corflags)
+ {
+ _corflags = (CorFlags)VisitCorflags(corflags).Value;
+ }
+ if (context.alignment() is { } alignment)
+ {
+ _alignment = VisitAlignment(alignment).Value;
+ }
+ if (context.imagebase() is { } imagebase)
+ {
+ _imageBase = VisitImagebase(imagebase).Value;
+ }
+ if (context.stackreserve() is { } stackreserve)
+ {
+ _stackReserve = VisitStackreserve(stackreserve).Value;
+ }
+ if (context.languageDecl() is { } languageDecl)
+ {
+ VisitLanguageDecl(languageDecl);
+ }
+ if (context.typedefDecl() is { } typedefDecl)
+ {
+ VisitTypedefDecl(typedefDecl);
+ }
+ if (context.typelist() is { } typelist)
+ {
+ foreach (var name in typelist.className())
+ {
+ _ = VisitClassName(name);
+ }
+ }
+ if (context.mscorlib() is { } mscorlib)
+ {
+ VisitMscorlib(mscorlib);
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ public GrammarResult VisitDecls(CILParser.DeclsContext context)
+ {
+ foreach (var decl in context.decl())
+ {
+ _ = VisitDecl(decl);
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ GrammarResult ICILVisitor.VisitDottedName(CILParser.DottedNameContext context)
+ {
+ return VisitDottedName(context);
+ }
+
+ public static GrammarResult.String VisitDottedName(CILParser.DottedNameContext context)
+ {
+ return new(context.GetText());
+ }
+ GrammarResult ICILVisitor.VisitElementType(CILParser.ElementTypeContext context) => VisitElementType(context);
+ public GrammarResult.FormattedBlob VisitElementType(CILParser.ElementTypeContext context)
+ {
+ BlobBuilder blob = new(5);
+ if (context.OBJECT() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeCode.Object);
+ }
+ else if (context.className() is CILParser.ClassNameContext className)
+ {
+ EntityRegistry.TypeEntity typeEntity = VisitClassName(className).Value;
+ if (context.VALUE() is not null || context.VALUETYPE() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeKind.ValueType);
+ blob.WriteTypeEntity(typeEntity);
+ }
+ else
+ {
+ blob.WriteByte((byte)SignatureTypeKind.Class);
+ blob.WriteTypeEntity(typeEntity);
+ }
+ }
+ else if (context.callConv() is CILParser.CallConvContext callConv)
+ {
+ // Emit function pointer signature.
+ blob.WriteByte((byte)SignatureTypeCode.FunctionPointer);
+ byte sigCallConv = VisitCallConv(callConv).Value;
+ blob.WriteByte(sigCallConv);
+ var signatureArgs = VisitSigArgs(context.sigArgs()).Value;
+ int numArgs = signatureArgs.Count(arg => !arg.IsSentinel);
+ blob.WriteCompressedInteger(numArgs);
+ blob.LinkSuffix(VisitType(context.type()).Value);
+ foreach (var arg in signatureArgs)
+ {
+ blob.LinkSuffix(arg.SignatureBlob);
+ }
+ }
+ else if (context.ELLIPSIS() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeCode.Sentinel);
+ blob.LinkSuffix(VisitType(context.type()).Value);
+ }
+ else if (context.METHOD_TYPE_PARAMETER() is not null)
+ {
+ if (context.int32() is CILParser.Int32Context int32)
+ {
+ // COMPAT: Always write a reference to a generic method parameter by index
+ // even if we aren't in a method or the index is out of range. We want to be able to write invalid IL like this.
+ blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter);
+ blob.WriteCompressedInteger(VisitInt32(int32).Value);
+ }
+ else
+ {
+ string dottedName = VisitDottedName(context.dottedName()).Value;
+ if (_currentMethod is null)
+ {
+ // TODO: Report diagnostic
+ blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter);
+ blob.WriteCompressedInteger(0);
+ }
+ else
+ {
+ blob.WriteByte((byte)SignatureTypeCode.GenericMethodParameter);
+ bool foundParameter = false;
+ for (int i = 0; i < _currentMethod.Definition.GenericParameters.Count; i++)
+ {
+ EntityRegistry.GenericParameterEntity? genericParameter = _currentMethod.Definition.GenericParameters[i];
+ if (genericParameter.Name == dottedName)
+ {
+ foundParameter = true;
+ blob.WriteCompressedInteger(i);
+ break;
+ }
+ }
+ if (!foundParameter)
+ {
+ // BREAK-COMPAT: ILASM would silently emit an invalid signature when a method uses an invalid method type parameter but doesn't have method type parameters.
+ // The signature used completely invalid undocumented codes (that were really sentinel values for how ilasm later detected errors due to how the parsing model worked with a YACC-based parser)
+ // and when a method had no type parameters, it didn't run the code to process out these values and emit errors.
+ // This seems like a scenario that doesn't need to be brought forward.
+ // Instead, we'll just emit a reference to "generic method parameter" 0 and report an error.
+
+ // TODO: Report diagnostic
+ blob.WriteCompressedInteger(0);
+ }
+ }
+ }
+ }
+ else if (context.TYPE_PARAMETER() is not null)
+ {
+ if (context.int32() is CILParser.Int32Context int32)
+ {
+ // COMPAT: Always write a reference to a generic type parameter by index
+ // even if we aren't in a type or the index is out of range. We want to be able to write invalid IL like this.
+ blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter);
+ blob.WriteCompressedInteger(VisitInt32(int32).Value);
+ }
+ else
+ {
+ string dottedName = VisitDottedName(context.dottedName()).Value;
+ if (_currentTypeDefinition.Count == 0)
+ {
+ // TODO: Report diagnostic
+ blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter);
+ blob.WriteCompressedInteger(0);
+ }
+ else
+ {
+ blob.WriteByte((byte)SignatureTypeCode.GenericTypeParameter);
+ bool foundParameter = false;
+ for (int i = 0; i < _currentTypeDefinition.Peek().GenericParameters.Count; i++)
+ {
+ EntityRegistry.GenericParameterEntity? genericParameter = _currentTypeDefinition.Peek().GenericParameters[i];
+ if (genericParameter.Name == dottedName)
+ {
+ foundParameter = true;
+ blob.WriteCompressedInteger(i);
+ break;
+ }
+ }
+ if (!foundParameter)
+ {
+ // BREAK-COMPAT: ILASM would silently emit an invalid signature when a type uses an invalid method type parameter but doesn't have any type parameters.
+ // The signature used completely invalid undocumented codes (that were really sentinel values for how ilasm later detected errors due to how the parsing model worked with a YACC-based parser)
+ // and when a method had no type parameters, it didn't run the code to process out these values and emit errors.
+ // This seems like a scenario that doesn't need to be brought forward.
+ // Instead, we'll just emit a reference to "generic method parameter" 0 and report an error.
+
+ // TODO: Report diagnostic
+ blob.WriteCompressedInteger(0);
+ }
+ }
+ }
+ }
+ else if (context.TYPEDREF() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeCode.TypedReference);
+ }
+ else if (context.VOID() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeCode.Void);
+ }
+ else if (context.NATIVE_INT() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeCode.IntPtr);
+ }
+ else if (context.NATIVE_UINT() is not null)
+ {
+ blob.WriteByte((byte)SignatureTypeCode.UIntPtr);
+ }
+ else if (context.simpleType() is CILParser.SimpleTypeContext simpleType)
+ {
+ blob.WriteByte((byte)VisitSimpleType(simpleType).Value);
+ }
+ else if (context.dottedName() is CILParser.DottedNameContext dottedName)
+ {
+ // TODO: typedef
+ throw new NotImplementedException();
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
+ return new(blob);
+ }
+
+ public GrammarResult VisitErrorNode(IErrorNode node) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+ public GrammarResult VisitEsHead(CILParser.EsHeadContext context) => throw new NotImplementedException("TODO: Symbols");
+
+ GrammarResult ICILVisitor.VisitEventAttr(CILParser.EventAttrContext context) => VisitEventAttr(context);
+ public GrammarResult.Flag VisitEventAttr(CILParser.EventAttrContext context)
+ {
+ return context.GetText() switch
+ {
+ "specialname" => new(EventAttributes.SpecialName),
+ "rtspecialname" => new(0), // COMPAT: Ignore
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ GrammarResult ICILVisitor.VisitEventDecl(CILParser.EventDeclContext context) => VisitEventDecl(context);
+ public GrammarResult.Literal<(MethodSemanticsAttributes, EntityRegistry.EntityBase)?> VisitEventDecl(CILParser.EventDeclContext context)
+ {
+ if (context.ChildCount != 2)
+ {
+ return new(null);
+ }
+ string accessor = context.GetChild(0).GetText();
+ EntityRegistry.EntityBase memberReference = VisitMethodRef(context.methodRef()).Value;
+ MethodSemanticsAttributes methodSemanticsAttributes = accessor switch
+ {
+ ".addon" => MethodSemanticsAttributes.Adder,
+ ".removeon" => MethodSemanticsAttributes.Remover,
+ ".fire" => MethodSemanticsAttributes.Raiser,
+ ".other" => MethodSemanticsAttributes.Other,
+ _ => throw new UnreachableException(),
+ };
+ return new((methodSemanticsAttributes, memberReference));
+ }
+
+ GrammarResult ICILVisitor.VisitEventDecls(CILParser.EventDeclsContext context) => VisitEventDecls(context);
+ public GrammarResult.Sequence<(MethodSemanticsAttributes, EntityRegistry.EntityBase)> VisitEventDecls(CILParser.EventDeclsContext context)
+ => new(
+ context.eventDecl()
+ .Select(decl => VisitEventDecl(decl).Value)
+ .Where(decl => decl is not null)
+ .Select(decl => decl!.Value).ToImmutableArray());
+
+ GrammarResult ICILVisitor.VisitEventHead(CILParser.EventHeadContext context) => VisitEventHead(context);
+ public GrammarResult.Literal VisitEventHead(CILParser.EventHeadContext context)
+ {
+ string name = VisitDottedName(context.dottedName()).Value;
+ EventAttributes eventAttributes = context.eventAttr().Select(attr => VisitEventAttr(attr).Value).Aggregate((a, b) => a | b);
+ return new(new EntityRegistry.EventEntity(eventAttributes, VisitTypeSpec(context.typeSpec()).Value, name));
+ }
+
+ public GrammarResult VisitExportHead(CILParser.ExportHeadContext context) => throw new NotImplementedException("Obsolete syntax");
+
+ GrammarResult ICILVisitor.VisitExptAttr(CILParser.ExptAttrContext context) => VisitExptAttr(context);
+ public static GrammarResult.Flag VisitExptAttr(CILParser.ExptAttrContext context)
+ {
+ return context.GetText() switch
+ {
+ "private" => new(TypeAttributes.NotPublic, TypeAttributes.VisibilityMask),
+ "public" => new(TypeAttributes.Public, TypeAttributes.VisibilityMask),
+ "forwarder" => new(TypeAttributes.Forwarder),
+ "nestedpublic" => new(TypeAttributes.NestedPublic, TypeAttributes.VisibilityMask),
+ "nestedprivate" => new(TypeAttributes.NestedPrivate, TypeAttributes.VisibilityMask),
+ "nestedfamily" => new(TypeAttributes.NestedFamily, TypeAttributes.VisibilityMask),
+ "nestedassembly" => new(TypeAttributes.NestedAssembly, TypeAttributes.VisibilityMask),
+ "nestedfamandassem" => new(TypeAttributes.NestedFamANDAssem, TypeAttributes.VisibilityMask),
+ "nestedfamorassem" => new(TypeAttributes.NestedFamORAssem, TypeAttributes.VisibilityMask),
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ // TODO: Implement multimodule type exports and fowarders
+ public GrammarResult VisitExptypeDecl(CILParser.ExptypeDeclContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+
+ GrammarResult ICILVisitor.VisitExptypeDecls(CILParser.ExptypeDeclsContext context) => VisitExptypeDecls(context);
+ public GrammarResult.Literal<(EntityRegistry.EntityBase? implementation, int typedefId, ImmutableArray attrs)> VisitExptypeDecls(CILParser.ExptypeDeclsContext context)
+ {
+ // COMPAT: The following order specifies the precedence of the various export kinds.
+ // File, Assembly, Class (enclosing type), invalid token.
+ // We'll process through all of the options here and then return the one that is valid.
+ // We'll also record custom attributes here.
+ EntityRegistry.EntityBase? implementationEntity = null;
+ int typedefId = 0;
+ var attrs = ImmutableArray.CreateBuilder();
+ var declarations = context.exptypeDecl();
+ for (int i = 0; i < declarations.Length; i++)
+ {
+ if (declarations[i].customAttrDecl() is { } attr)
+ {
+ if (VisitCustomAttrDecl(attr).Value is EntityRegistry.CustomAttributeEntity customAttribute)
+ {
+ attrs.Add(customAttribute);
+ }
+ continue;
+ }
+ if (declarations[i].mdtoken() is { } mdToken)
+ {
+ var entity = VisitMdtoken(mdToken).Value;
+ if (entity is null)
+ {
+ // TODO: Report diagnostic
+ }
+ implementationEntity = ResolveBetterEntity(entity);
+ continue;
+ }
+ string kind = declarations[i].GetText();
+ if (kind == ".file")
+ {
+ implementationEntity = _entityRegistry.FindFile(VisitDottedName(declarations[i].dottedName()).Value);
+ if (implementationEntity is null)
+ {
+ // TODO: Report diagnostic
+ }
+ }
+ else if (kind == ".assembly")
+ {
+ implementationEntity = _entityRegistry.FindAssemblyReference(VisitDottedName(declarations[i].dottedName()).Value);
+ if (implementationEntity is null)
+ {
+ // TODO: Report diagnostic
+ }
+ }
+ else if (kind == ".class")
+ {
+ if (declarations[i].int32() is CILParser.Int32Context int32)
+ {
+ typedefId = VisitInt32(int32).Value;
+ }
+ else
+ {
+ _ = VisitSlashedName(declarations[i].slashedName());
+ var containing = ResolveExportedType(declarations[i].slashedName());
+ if (containing is null)
+ {
+ // TODO: Report diagnostic
+ }
+ else
+ {
+ implementationEntity = ResolveBetterEntity(containing);
+ }
+ }
+ }
+ }
+
+ return new((implementationEntity, typedefId, attrs.ToImmutable()));
+
+ EntityRegistry.EntityBase? ResolveBetterEntity(EntityRegistry.EntityBase? newImplementation)
+ {
+ return (implementationEntity, newImplementation) switch
+ {
+ (null, _) => newImplementation,
+ (_, null) => implementationEntity,
+ (_, EntityRegistry.FileEntity) => newImplementation,
+ (EntityRegistry.FileEntity, _) => implementationEntity,
+ (_, EntityRegistry.AssemblyEntity) => newImplementation,
+ (EntityRegistry.AssemblyEntity, _) => implementationEntity,
+ (_, EntityRegistry.TypeEntity) => newImplementation,
+ (EntityRegistry.TypeEntity, _) => implementationEntity,
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ // Resolve ExportedType reference
+ EntityRegistry.ExportedTypeEntity? ResolveExportedType(CILParser.SlashedNameContext slashedName)
+ {
+ TypeName typeName = VisitSlashedName(slashedName).Value;
+ if (typeName.ContainingTypeName is null)
+ {
+ // TODO: Check for typedef.
+ }
+ Stack containingTypes = new();
+ for (TypeName? containingType = typeName; containingType is not null; containingType = containingType.ContainingTypeName)
+ {
+ containingTypes.Push(containingType);
+ }
+ EntityRegistry.ExportedTypeEntity? exportedType = null;
+ while (containingTypes.Count != 0)
+ {
+ TypeName containingType = containingTypes.Pop();
+
+ (string ns, string name) = NameHelpers.SplitDottedNameToNamespaceAndName(containingType.DottedName);
+
+ exportedType = _entityRegistry.FindExportedType(
+ exportedType,
+ ns,
+ name);
+
+ if (exportedType is null)
+ {
+ // TODO: Report diagnostic for missing type name
+ return null;
+ }
+ }
+
+ return exportedType!;
+ }
+ }
+ GrammarResult ICILVisitor.VisitExptypeHead(CILParser.ExptypeHeadContext context) => VisitExptypeHead(context);
+ public GrammarResult.Literal<(TypeAttributes attrs, string dottedName)> VisitExptypeHead(CILParser.ExptypeHeadContext context)
+ {
+ var attrs = context.exptAttr().Select(VisitExptAttr).Aggregate((TypeAttributes)0, (a, b) => a | b);
+ return new((attrs, VisitDottedName(context.dottedName()).Value));
+ }
+ GrammarResult ICILVisitor.VisitExtendsClause(CILParser.ExtendsClauseContext context) => VisitExtendsClause(context);
+
+ public GrammarResult.Literal VisitExtendsClause(CILParser.ExtendsClauseContext context)
+ {
+ if (context.typeSpec() is CILParser.TypeSpecContext typeSpec)
+ {
+ return new(VisitTypeSpec(typeSpec).Value);
+ }
+ else
+ {
+ return new(null);
+ }
+ }
+
+ public GrammarResult VisitExtSourceSpec(CILParser.ExtSourceSpecContext context) => throw new NotImplementedException("TODO: Symbols");
+
+ GrammarResult ICILVisitor.VisitF32seq(CILParser.F32seqContext context) => VisitF32seq(context);
+ public GrammarResult.FormattedBlob VisitF32seq(CILParser.F32seqContext context)
+ {
+ var builder = ImmutableArray.CreateBuilder();
+
+ foreach (var item in context.children)
+ {
+ builder.Add((float)(item switch
+ {
+ CILParser.Int32Context int32 => VisitInt32(int32).Value,
+ CILParser.Float64Context float64 => VisitFloat64(float64).Value,
+ _ => throw new UnreachableException()
+ }));
+ }
+ return new(builder.MoveToImmutable().SerializeSequence());
+ }
+ GrammarResult ICILVisitor.VisitF64seq(CILParser.F64seqContext context) => VisitF64seq(context);
+ public GrammarResult.FormattedBlob VisitF64seq(CILParser.F64seqContext context)
+ {
+ var builder = ImmutableArray.CreateBuilder();
+
+ foreach (var item in context.children)
+ {
+ builder.Add((double)(item switch
+ {
+ CILParser.Int64Context int64 => VisitInt64(int64).Value,
+ CILParser.Float64Context float64 => VisitFloat64(float64).Value,
+ _ => throw new UnreachableException()
+ }));
+ }
+ return new(builder.MoveToImmutable().SerializeSequence());
+ }
+
+ public GrammarResult VisitFaultClause(CILParser.FaultClauseContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+
+ GrammarResult ICILVisitor.VisitFieldAttr(CILParser.FieldAttrContext context) => VisitFieldAttr(context);
+ public GrammarResult.Flag VisitFieldAttr(CILParser.FieldAttrContext context)
+ {
+ if (context.int32() is { } int32)
+ {
+ return new((FieldAttributes)VisitInt32(int32).Value, ShouldAppend: false);
+ }
+
+ return context.GetText() switch
+ {
+ "static" => new(FieldAttributes.Static),
+ "public" => new(FieldAttributes.Public, FieldAttributes.FieldAccessMask),
+ "private" => new(FieldAttributes.Private, FieldAttributes.FieldAccessMask),
+ "family" => new(FieldAttributes.Family, FieldAttributes.FieldAccessMask),
+ "initonly" => new(FieldAttributes.InitOnly),
+ "rtspecialname" => new(0), // COMPAT: Don't emit rtspecialname
+ "specialname" => new(FieldAttributes.SpecialName),
+ "assembly" => new(FieldAttributes.Assembly, FieldAttributes.FieldAccessMask),
+ "famandassem" => new(FieldAttributes.FamANDAssem, FieldAttributes.FieldAccessMask),
+ "famorassem" => new(FieldAttributes.FamORAssem, FieldAttributes.FieldAccessMask),
+ "privatescope" => new(FieldAttributes.PrivateScope, FieldAttributes.FieldAccessMask),
+ "literal" => new(FieldAttributes.Literal),
+#pragma warning disable SYSLIB0050 // FieldAttributes.NotSeralized is obsolete
+ "notserialized" => new(FieldAttributes.NotSerialized),
+#pragma warning restore SYSLIB0050 // FieldAttributes.NotSeralized is obsolete
+ _ => throw new UnreachableException()
+ };
+ }
+ GrammarResult ICILVisitor.VisitFieldDecl(CILParser.FieldDeclContext context) => VisitFieldDecl(context);
+ public GrammarResult VisitFieldDecl(CILParser.FieldDeclContext context)
+ {
+ var fieldAttrs = context.fieldAttr().Select(VisitFieldAttr).Aggregate((FieldAttributes)0, (a, b) => a | b);
+ var fieldType = VisitType(context.type()).Value;
+ var marshalBlobs = context.marshalBlob();
+ var marshalBlob = VisitMarshalBlob(marshalBlobs[marshalBlobs.Length - 1]).Value;
+ var name = VisitDottedName(context.dottedName()).Value;
+ var rvaOffset = VisitAtOpt(context.atOpt()).Value;
+ _ = VisitInitOpt(context.initOpt());
+
+ var signature = new BlobEncoder(new BlobBuilder());
+ _ = signature.Field();
+ fieldType.WriteContentTo(signature.Builder);
+
+ var field = EntityRegistry.CreateUnrecordedFieldDefinition(fieldAttrs, _currentTypeDefinition.PeekOrDefault()!, name, signature.Builder);
+
+ if (field is not null)
+ {
+ field.MarshallingDescriptor = marshalBlob;
+ field.DataDeclarationName = rvaOffset;
+ }
+
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ public GrammarResult VisitFieldInit(CILParser.FieldInitContext context) => throw new NotImplementedException("TODO-SRM: Need support for an arbitrary byte blob as a constant value");
+
+ public GrammarResult VisitFieldOrProp(CILParser.FieldOrPropContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+
+ GrammarResult ICILVisitor.VisitFieldRef(CILParser.FieldRefContext context) => VisitFieldRef(context);
+ public GrammarResult.Literal VisitFieldRef(CILParser.FieldRefContext context)
+ {
+ if (context.type() is not CILParser.TypeContext type)
+ {
+ // TODO: typedef
+ throw new NotImplementedException();
+ }
+
+ var fieldTypeSig = VisitType(type).Value;
+ EntityRegistry.TypeEntity definingType = _currentTypeDefinition.PeekOrDefault() ?? _entityRegistry.ModuleType;
+ if (context.typeSpec() is CILParser.TypeSpecContext typeSpec)
+ {
+ definingType = VisitTypeSpec(typeSpec).Value;
+ }
+
+ var name = VisitDottedName(context.dottedName()).Value;
+
+ var fieldSig = new BlobBuilder(fieldTypeSig.Count + 1);
+ fieldSig.WriteByte((byte)SignatureKind.Field);
+ fieldTypeSig.WriteContentTo(fieldSig);
+ return new(_entityRegistry.CreateLazilyRecordedMemberReference(definingType, name, fieldSig));
+ }
+
+ GrammarResult ICILVisitor.VisitFieldSerInit(CILParser.FieldSerInitContext context) => VisitFieldSerInit(context);
+ public GrammarResult.FormattedBlob VisitFieldSerInit(CILParser.FieldSerInitContext context)
+ {
+ // The max length for the majority of the blobs is 9 bytes. 1 for the type of blob, 8 for the max 64-bit value.
+ // Byte arrays can be larger, so we handle that case separately.
+ const int CommonMaxBlobLength = 9;
+ BlobBuilder builder;
+ var bytesNode = context.bytes();
+ if (bytesNode is not null)
+ {
+ var bytesResult = VisitBytes(bytesNode);
+ // Our blob length is the number of bytes in the byte array + the code for the byte array.
+ builder = new BlobBuilder(bytesResult.Value.Length + 1);
+ builder.WriteByte((byte)SerializationTypeCode.String);
+ builder.WriteBytes(bytesResult.Value);
+ return new(builder);
+ }
+ builder = new BlobBuilder(CommonMaxBlobLength);
+
+ int tokenType = ((ITerminalNode)context.GetChild(0)).Symbol.Type;
+
+ builder.WriteByte((byte)GetTypeCodeForToken(tokenType));
+
+ switch (tokenType)
+ {
+ case CILParser.BOOL:
+ builder.WriteBoolean(VisitTruefalse(context.truefalse()).Value);
+ break;
+ case CILParser.INT8:
+ case CILParser.UINT8:
+ builder.WriteByte((byte)VisitInt32(context.int32()).Value);
+ break;
+ case CILParser.CHAR:
+ case CILParser.INT16:
+ case CILParser.UINT16:
+ builder.WriteInt16((short)VisitInt32(context.int32()).Value);
+ break;
+ case CILParser.INT32:
+ case CILParser.UINT32:
+ builder.WriteInt32(VisitInt32(context.int32()).Value);
+ break;
+ case CILParser.INT64:
+ case CILParser.UINT64:
+ builder.WriteInt64(VisitInt64(context.int64()).Value);
+ break;
+ case CILParser.FLOAT32:
+ {
+ if (context.float64() is CILParser.Float64Context float64)
+ {
+ builder.WriteSingle((float)VisitFloat64(float64).Value);
+ }
+ if (context.int32() is CILParser.Int32Context int32)
+ {
+ int value = VisitInt32(int32).Value;
+ builder.WriteSingle(BitConverter.Int32BitsToSingle(value));
+ }
+ break;
+ }
+ case CILParser.FLOAT64:
+ {
+ if (context.float64() is CILParser.Float64Context float64)
+ {
+ builder.WriteDouble(VisitFloat64(float64).Value);
+ }
+ if (context.int64() is CILParser.Int64Context int64)
+ {
+ long value = VisitInt64(int64).Value;
+ builder.WriteDouble(BitConverter.Int64BitsToDouble(value));
+ }
+ break;
+ }
+ }
+
+ return new(builder);
+ }
+
+ GrammarResult ICILVisitor.VisitFileAttr(CILParser.FileAttrContext context) => VisitFileAttr(context);
+ public GrammarResult.Literal VisitFileAttr(CILParser.FileAttrContext context)
+ => context.ChildCount != 0 ? new(false) : new(true);
+ GrammarResult ICILVisitor.VisitFileDecl(CILParser.FileDeclContext context) => VisitFileDecl(context);
+ public GrammarResult.Literal VisitFileDecl(CILParser.FileDeclContext context)
+ {
+ string dottedName = VisitDottedName(context.dottedName()).Value;
+ ImmutableArray? hash = context.HASH() is not null ? VisitBytes(context.bytes()).Value : null;
+ var hashBlob = hash is not null ? new BlobBuilder() : null;
+ hashBlob?.WriteBytes(hash!.Value);
+
+ bool hasMetadata = context.fileAttr().Aggregate(true, (acc, attr) => acc || VisitFileAttr(attr).Value);
+ bool isEntrypoint = context.fileEntry().Aggregate(true, (acc, attr) => acc || VisitFileEntry(attr).Value);
+ var entity = _entityRegistry.GetOrCreateFile(dottedName, hasMetadata, hashBlob);
+ if (isEntrypoint)
+ {
+ _entityRegistry.EntryPoint = entity;
+ }
+ return new(entity);
+ }
+ GrammarResult ICILVisitor.VisitFileEntry(CILParser.FileEntryContext context) => VisitFileEntry(context);
+ public GrammarResult.Literal VisitFileEntry(CILParser.FileEntryContext context)
+ => context.ChildCount != 0 ? new(true) : new(false);
+
+ GrammarResult ICILVisitor.VisitFilterClause(CILParser.FilterClauseContext context) => VisitFilterClause(context);
+ public GrammarResult.Literal VisitFilterClause(CILParser.FilterClauseContext context)
+ {
+ if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock)
+ {
+ LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel();
+ _currentMethod.Definition.MethodBody.MarkLabel(start);
+ _ = VisitScopeBlock(scopeBlock);
+ return new(start);
+ }
+ if (context.id() is CILParser.IdContext id)
+ {
+ var start = _currentMethod!.Labels.TryGetValue(VisitId(id).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(id).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
+ return new(start);
+ }
+ if (context.int32() is CILParser.Int32Context offset)
+ {
+ var start = _currentMethod!.Definition.MethodBody.DefineLabel();
+ _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offset).Value);
+ return new(start);
+ }
+ throw new UnreachableException();
+ }
+
+ public GrammarResult VisitFinallyClause(CILParser.FinallyClauseContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+
+ GrammarResult ICILVisitor.VisitFloat64(CILParser.Float64Context context) => VisitFloat64(context);
+ public GrammarResult.Literal VisitFloat64(CILParser.Float64Context context)
+ {
+ if (context.FLOAT64() is ITerminalNode float64)
+ {
+ string text = float64.Symbol.Text;
+ bool neg = text.StartsWith('-');
+ if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out double result))
+ {
+ result = neg ? double.MaxValue : double.MinValue;
+ }
+ return new(result);
+ }
+ else if (context.int32() is CILParser.Int32Context int32)
+ {
+ int value = VisitInt32(int32).Value;
+ return new(BitConverter.Int32BitsToSingle(value));
+ }
+ else if (context.int64() is CILParser.Int64Context int64)
+ {
+ long value = VisitInt64(int64).Value;
+ return new(BitConverter.Int64BitsToDouble(value));
+ }
+ throw new UnreachableException();
+ }
+ GrammarResult ICILVisitor.VisitGenArity(CILParser.GenArityContext context) => VisitGenArity(context);
+ public GrammarResult.Literal VisitGenArity(CILParser.GenArityContext context)
+ => context.genArityNotEmpty() is CILParser.GenArityNotEmptyContext genArity ? VisitGenArityNotEmpty(genArity) : new(0);
+
+ GrammarResult ICILVisitor.VisitGenArityNotEmpty(CILParser.GenArityNotEmptyContext context) => VisitGenArityNotEmpty(context);
+ public GrammarResult.Literal VisitGenArityNotEmpty(CILParser.GenArityNotEmptyContext context) => VisitInt32(context.int32());
+
+ GrammarResult ICILVisitor.VisitHandlerBlock(CILParser.HandlerBlockContext context) => VisitHandlerBlock(context);
+
+ public GrammarResult.Literal<(LabelHandle Start, LabelHandle End)> VisitHandlerBlock(CILParser.HandlerBlockContext context)
+ {
+ if (context.scopeBlock() is CILParser.ScopeBlockContext scopeBlock)
+ {
+ LabelHandle start = _currentMethod!.Definition.MethodBody.DefineLabel();
+ _currentMethod.Definition.MethodBody.MarkLabel(start);
+ _ = VisitScopeBlock(scopeBlock);
+ LabelHandle end = _currentMethod.Definition.MethodBody.DefineLabel();
+ _currentMethod.Definition.MethodBody.MarkLabel(end);
+ return new((start, end));
+ }
+ if (context.id() is CILParser.IdContext[] ids)
+ {
+ var start = _currentMethod!.Labels.TryGetValue(VisitId(ids[0]).Value, out LabelHandle startLabel) ? startLabel : _currentMethod.Labels[VisitId(ids[0]).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
+ var end = _currentMethod!.Labels.TryGetValue(VisitId(ids[1]).Value, out LabelHandle endLabel) ? endLabel : _currentMethod.Labels[VisitId(ids[1]).Value] = _currentMethod.Definition.MethodBody.DefineLabel();
+ return new((start, end));
+ }
+ if (context.int32() is CILParser.Int32Context[] offsets)
+ {
+ var start = _currentMethod!.Definition.MethodBody.DefineLabel();
+ var end = _currentMethod.Definition.MethodBody.DefineLabel();
+ _currentMethod.Definition.MethodBody.MarkLabel(start, VisitInt32(offsets[0]).Value);
+ _currentMethod.Definition.MethodBody.MarkLabel(end, VisitInt32(offsets[1]).Value);
+ return new((start, end));
+ }
+ throw new UnreachableException();
+ }
+
+ GrammarResult ICILVisitor.VisitHexbytes(CILParser.HexbytesContext context)
+ {
+ return VisitHexbytes(context);
+ }
+
+ public static GrammarResult.Sequence VisitHexbytes(CILParser.HexbytesContext context)
+ {
+ ITerminalNode[] bytes = context.HEXBYTE();
+ var builder = ImmutableArray.CreateBuilder(bytes.Length);
+ foreach (var @byte in bytes)
+ {
+ builder.Add(byte.Parse(@byte.Symbol.Text, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture));
+ }
+ return new(builder.MoveToImmutable());
+ }
+ GrammarResult ICILVisitor.VisitI16seq(CILParser.I16seqContext context) => VisitI16seq(context);
+ public GrammarResult.FormattedBlob VisitI16seq(CILParser.I16seqContext context)
+ {
+ var values = context.int32();
+ var builder = ImmutableArray.CreateBuilder(values.Length);
+ foreach (var value in values)
+ {
+ builder.Add((short)VisitInt32(value).Value);
+ }
+ return new(builder.MoveToImmutable().SerializeSequence());
+ }
+ GrammarResult ICILVisitor.VisitI32seq(CILParser.I32seqContext context) => VisitI32seq(context);
+ public GrammarResult.FormattedBlob VisitI32seq(CILParser.I32seqContext context)
+ {
+ var values = context.int32();
+ var builder = ImmutableArray.CreateBuilder(values.Length);
+ foreach (var value in values)
+ {
+ builder.Add(VisitInt32(value).Value);
+ }
+ return new(builder.MoveToImmutable().SerializeSequence());
+ }
+
+ GrammarResult ICILVisitor.VisitI8seq(CILParser.I8seqContext context) => VisitI8seq(context);
+ public GrammarResult.FormattedBlob VisitI8seq(CILParser.I8seqContext context)
+ {
+ var values = context.int32();
+ var builder = ImmutableArray.CreateBuilder(values.Length);
+ foreach (var value in values)
+ {
+ builder.Add((byte)VisitInt32(value).Value);
+ }
+ return new(builder.MoveToImmutable().SerializeSequence());
+ }
+
+ GrammarResult ICILVisitor.VisitI64seq(CILParser.I64seqContext context) => VisitI64seq(context);
+ public GrammarResult.FormattedBlob VisitI64seq(CILParser.I64seqContext context)
+ {
+ var values = context.int64();
+ var builder = ImmutableArray.CreateBuilder(values.Length);
+ foreach (var value in values)
+ {
+ builder.Add(VisitInt64(value).Value);
+ }
+ return new(builder.MoveToImmutable().SerializeSequence());
+ }
+ GrammarResult ICILVisitor.VisitId(CILParser.IdContext context) => VisitId(context);
+ public static GrammarResult.String VisitId(CILParser.IdContext context)
+ {
+ return new GrammarResult.String(context.GetText());
+ }
+
+ GrammarResult ICILVisitor.VisitIidParamIndex(CILParser.IidParamIndexContext context) => VisitIidParamIndex(context);
+ public GrammarResult.Literal VisitIidParamIndex(CILParser.IidParamIndexContext context)
+ => context.int32() is CILParser.Int32Context int32 ? new(VisitInt32(int32).Value) : new(null);
+
+ GrammarResult ICILVisitor.VisitImagebase(CILParser.ImagebaseContext context) => VisitImagebase(context);
+ public GrammarResult.Literal VisitImagebase(CILParser.ImagebaseContext context) => VisitInt64(context.int64());
+
+ GrammarResult ICILVisitor.VisitImplAttr(ILAssembler.CILParser.ImplAttrContext context) => VisitImplAttr(context);
+ public GrammarResult.Flag VisitImplAttr(CILParser.ImplAttrContext context)
+ {
+ if (context.int32() is CILParser.Int32Context int32)
+ {
+ return new((MethodImplAttributes)VisitInt32(int32).Value, ShouldAppend: false);
+ }
+ string attribute = context.GetText();
+ return attribute switch
+ {
+ "native" => new(MethodImplAttributes.Native, MethodImplAttributes.CodeTypeMask),
+ "cil" => new(MethodImplAttributes.IL, MethodImplAttributes.CodeTypeMask),
+ "optil" => new(MethodImplAttributes.OPTIL, MethodImplAttributes.CodeTypeMask),
+ "managed" => new(MethodImplAttributes.Managed, MethodImplAttributes.ManagedMask),
+ "unmanaged" => new(MethodImplAttributes.Unmanaged, MethodImplAttributes.ManagedMask),
+ "forwardref" => new(MethodImplAttributes.ForwardRef),
+ "preservesig" => new(MethodImplAttributes.PreserveSig),
+ "runtime" => new(MethodImplAttributes.Runtime, MethodImplAttributes.CodeTypeMask),
+ "internalcall" => new(MethodImplAttributes.InternalCall),
+ "synchronized" => new(MethodImplAttributes.Synchronized),
+ "noinlining" => new(MethodImplAttributes.NoInlining),
+ "aggressiveinlining" => new(MethodImplAttributes.AggressiveInlining),
+ "nooptimization" => new(MethodImplAttributes.NoOptimization),
+ "aggressiveoptimization" => new(MethodImplAttributes.AggressiveOptimization),
+ "async" => new(MethodImplAttributes.Async),
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ GrammarResult ICILVisitor.VisitImplClause(CILParser.ImplClauseContext context) => VisitImplClause(context);
+ public GrammarResult.Sequence VisitImplClause(CILParser.ImplClauseContext context) => context.implList() is {} implList ? VisitImplList(implList) : new(ImmutableArray.Empty);
+
+ GrammarResult ICILVisitor.VisitImplList(CILParser.ImplListContext context) => VisitImplList(context);
+ public GrammarResult.Sequence VisitImplList(CILParser.ImplListContext context)
+ {
+ var builder = ImmutableArray.CreateBuilder();
+ foreach (var impl in context.typeSpec())
+ {
+ builder.Add(EntityRegistry.CreateUnrecordedInterfaceImplementation(_currentTypeDefinition.PeekOrDefault()!, VisitTypeSpec(impl).Value));
+ }
+ return new(builder.ToImmutable());
+ }
+
+ public GrammarResult VisitInitOpt(CILParser.InitOptContext context)
+ {
+ if (context.fieldInit() is {})
+ {
+ // TODO: Change fieldSerInit to return a parsed System.Object value to construct the constant row entry.
+ // TODO-SRM: AddConstant does not support providing an arbitrary byte array as a constant value.
+ // Propose MetadataBuilder.AddConstant(EntityHandle parent, PrimitiveTypeCode type, BlobBuilder value) overload?
+ throw new NotImplementedException();
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+ public GrammarResult VisitInstr(CILParser.InstrContext context)
+ {
+ var instrContext = context.GetRuleContext(0);
+ ILOpCode opcode = ((GrammarResult.Literal)instrContext.Accept(this)).Value;
+ switch (instrContext.RuleIndex)
+ {
+ case CILParser.RULE_instr_brtarget:
+ {
+ ParserRuleContext argument = context.GetRuleContext(1);
+ if (argument is CILParser.IdContext id)
+ {
+ string label = VisitId(id).Value;
+ if (!_currentMethod!.Labels.TryGetValue(label, out var handle))
+ {
+ _currentMethod.Labels.Add(label, handle = _currentMethod.Definition.MethodBody.DefineLabel());
+ }
+ _currentMethod.Definition.MethodBody.Branch(opcode, handle);
+ }
+ if (argument is CILParser.Int32Context int32)
+ {
+ int offset = VisitInt32(int32).Value;
+ LabelHandle label = _currentMethod!.Definition.MethodBody.DefineLabel();
+ _currentMethod.Definition.MethodBody.Branch(opcode, label);
+ _currentMethod.Definition.MethodBody.MarkLabel(label, _currentMethod.Definition.MethodBody.Offset + offset);
+ }
+ }
+ break;
+ case CILParser.RULE_instr_field:
+ {
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ if (context.mdtoken() is CILParser.MdtokenContext mdtoken)
+ {
+ _currentMethod.Definition.MethodBody.Token(VisitMdtoken(mdtoken).Value.Handle);
+ }
+ else
+ {
+ var fieldRef = VisitFieldRef(context.fieldRef()).Value;
+ if (fieldRef is EntityRegistry.MemberReferenceEntity memberRef)
+ {
+ memberRef.RecordBlobToWriteResolvedHandle(_currentMethod.Definition.MethodBody.CodeBuilder.ReserveBytes(4));
+ }
+ else
+ {
+ _currentMethod.Definition.MethodBody.Token(fieldRef.Handle);
+ }
+ }
+ }
+ break;
+ case CILParser.RULE_instr_i:
+ {
+ int arg = VisitInt32(context.int32()).Value;
+ if (opcode == ILOpCode.Ldc_i4 || opcode == ILOpCode.Ldc_i4_s)
+ {
+ _currentMethod!.Definition.MethodBody.LoadConstantI4(arg);
+ }
+ else
+ {
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)arg);
+ }
+ }
+ break;
+ case CILParser.RULE_instr_i8:
+ Debug.Assert(opcode == ILOpCode.Ldc_i8);
+ _currentMethod!.Definition.MethodBody.LoadConstantI8(VisitInt64(context.int64()).Value);
+ break;
+ case CILParser.RULE_instr_method:
+ {
+ if (opcode == ILOpCode.Callvirt || opcode == ILOpCode.Newobj)
+ {
+ _expectInstance = true;
+ }
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ var methodRef = VisitMethodRef(context.methodRef()).Value;
+ if (methodRef is EntityRegistry.MemberReferenceEntity memberRef)
+ {
+ memberRef.RecordBlobToWriteResolvedHandle(_currentMethod.Definition.MethodBody.CodeBuilder.ReserveBytes(4));
+ }
+ else
+ {
+ _currentMethod.Definition.MethodBody.Token(methodRef.Handle);
+ }
+ // Reset the instance flag for the next instruction.
+ if (opcode == ILOpCode.Callvirt || opcode == ILOpCode.Newobj)
+ {
+ _expectInstance = false;
+ }
+ }
+ break;
+ case CILParser.RULE_instr_none:
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ break;
+ case CILParser.RULE_instr_r:
+ {
+ double value;
+ ParserRuleContext argument = context.GetRuleContext(1);
+ if (argument is CILParser.Float64Context float64)
+ {
+ value = VisitFloat64(float64).Value;
+ }
+ else if (argument is CILParser.Int64Context int64)
+ {
+ long intValue = VisitInt64(int64).Value;
+ value = BitConverter.Int64BitsToDouble(intValue);
+ }
+ else if (argument is CILParser.BytesContext bytesContext)
+ {
+ var bytes = VisitBytes(bytesContext).Value.ToArray();
+ value = bytes switch
+ {
+ { Length: >= 8 } => BitConverter.ToDouble(bytes, 0),
+ { Length: >= 4 } => BitConverter.ToSingle(bytes, 0),
+ // TODO: Report diagnostic on too-short byte array
+ _ => 0.0d
+ };
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
+ if (opcode == ILOpCode.Ldc_r4)
+ {
+ _currentMethod!.Definition.MethodBody.LoadConstantR4((float)value);
+ }
+ else
+ {
+ _currentMethod!.Definition.MethodBody.LoadConstantR8(value);
+ }
+ }
+ break;
+ case CILParser.RULE_instr_sig:
+ {
+ BlobBuilder signature = new();
+ byte callConv = VisitCallConv(context.callConv()).Value;
+ signature.WriteByte(callConv);
+ var args = VisitSigArgs(context.sigArgs()).Value;
+ signature.WriteCompressedInteger(args.Length);
+ // Write return type
+ VisitType(context.type()).Value.WriteContentTo(signature);
+ // Write arg signatures
+ foreach (var arg in args)
+ {
+ arg.SignatureBlob.WriteContentTo(signature);
+ }
+ _currentMethod!.Definition.MethodBody.Token(_entityRegistry.GetOrCreateStandaloneSignature(signature).Handle);
+ }
+ break;
+ case CILParser.RULE_instr_string:
+ Debug.Assert(opcode == ILOpCode.Ldstr);
+ string str;
+ if (context.bytes() is CILParser.BytesContext rawBytes)
+ {
+ ReadOnlySpan bytes = VisitBytes(rawBytes).Value.AsSpan();
+ ReadOnlySpan bytesAsChars = MemoryMarshal.Cast(bytes);
+ if (!BitConverter.IsLittleEndian)
+ {
+ for (int i = 0; i < bytesAsChars.Length; i++)
+ {
+ BinaryPrimitives.ReverseEndianness(bytesAsChars[i]);
+ }
+ }
+ str = bytesAsChars.ToString();
+ }
+ else
+ {
+ var userString = context.compQstring();
+ Debug.Assert(userString is not null);
+ str = VisitCompQstring(userString!).Value;
+ if (context.ANSI() is not null)
+ {
+ // Emit the string not as a UTF-16 string (as per the spec), but directly as an ANSI string.
+ // Although the string is marked as ANSI, this always used the UTF-8 code page
+ // so we can emit this as UTF-8 bytes.
+ int byteCount = Encoding.UTF8.GetByteCount(str);
+ // Ensure we have an even number of bytes.
+ if ((byteCount % 1) != 0)
+ {
+ byteCount++;
+ }
+
+ Span utf8Bytes = new byte[byteCount];
+ Encoding.UTF8.GetBytes(str, utf8Bytes);
+
+ str = new string(MemoryMarshal.Cast(utf8Bytes));
+ }
+ }
+ _currentMethod!.Definition.MethodBody.LoadString(_metadataBuilder.GetOrAddUserString(str));
+ break;
+ case CILParser.RULE_instr_switch:
+ {
+ var labels = new List<(LabelHandle Label, int? Offset)>();
+ foreach (var label in context.labels().children)
+ {
+ if (label is CILParser.IdContext id)
+ {
+ string labelName = VisitId(id).Value;
+ if (!_currentMethod!.Labels.TryGetValue(labelName, out var handle))
+ {
+ _currentMethod.Labels.Add(labelName, handle = _currentMethod.Definition.MethodBody.DefineLabel());
+ }
+ labels.Add((handle, null));
+ }
+ else if (label is CILParser.Int32Context int32)
+ {
+ int offset = VisitInt32(int32).Value;
+ LabelHandle labelHandle = _currentMethod!.Definition.MethodBody.DefineLabel();
+ labels.Add((labelHandle, offset));
+ }
+ }
+ var switchEncoder = _currentMethod!.Definition.MethodBody.Switch(labels.Count);
+ foreach (var label in labels)
+ {
+ switchEncoder.Branch(label.Label);
+ }
+ // Now that we've emitted the switch instruction, we can go back and mark the offset-based target labels
+ foreach (var label in labels)
+ {
+ if (label.Offset is int offset)
+ {
+ _currentMethod.Definition.MethodBody.MarkLabel(label.Label, _currentMethod.Definition.MethodBody.Offset + offset);
+ }
+ }
+ }
+ break;
+ case CILParser.RULE_instr_tok:
+ var tok = VisitOwnerType(context.ownerType()).Value;
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ _currentMethod.Definition.MethodBody.Token(tok.Handle);
+ break;
+ case CILParser.RULE_instr_type:
+ {
+ var arg = VisitTypeSpec(context.typeSpec()).Value;
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ _currentMethod.Definition.MethodBody.Token(arg.Handle);
+ }
+ break;
+ case CILParser.RULE_instr_var:
+ {
+ string instrName = opcode.ToString();
+ bool isShortForm = instrName.EndsWith("_s");
+ _currentMethod!.Definition.MethodBody.OpCode(opcode);
+ if (context.int32() is CILParser.Int32Context int32)
+ {
+ int value = VisitInt32(int32).Value;
+ if (isShortForm)
+ {
+ // Emit a byte instead of the int for the short form
+ _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)value);
+ }
+ else
+ {
+ _currentMethod.Definition.MethodBody.CodeBuilder.WriteInt32(value);
+ }
+ }
+ else
+ {
+ Debug.Assert(context.id() is not null);
+ string varName = VisitId(context.id()!).Value;
+ int? index = null;
+ if (instrName.Contains("arg"))
+ {
+ if (_currentMethod!.ArgumentNames.TryGetValue(varName, out var argIndex))
+ {
+ index = argIndex;
+
+ if (_currentMethod.Definition.SignatureHeader.IsInstance)
+ {
+ index++;
+ }
+ }
+ else
+ {
+ // TODO: Report diagnostic
+ }
+ }
+ else
+ {
+ for (int i = _currentMethod!.LocalsScopes.Count - 1; i >= 0 ; i--)
+ {
+ if (_currentMethod.LocalsScopes[i].TryGetValue(varName, out var localIndex))
+ {
+ index = localIndex;
+ break;
+ }
+ }
+ if (index is null)
+ {
+ // TODO: diagnostic
+ }
+ }
+
+ index ??= -1;
+
+ if (isShortForm)
+ {
+ // Emit a byte instead of the int for the short form
+ _currentMethod.Definition.MethodBody.CodeBuilder.WriteByte((byte)index.Value);
+ }
+ else
+ {
+ _currentMethod.Definition.MethodBody.CodeBuilder.WriteInt32(index.Value);
+ }
+ }
+ }
+ break;
+ }
+ return GrammarResult.SentinelValue.Result;
+ }
+
+ public GrammarResult.Literal VisitInstr_brtarget(CILParser.Instr_brtargetContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_field(CILParser.Instr_fieldContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_i(CILParser.Instr_iContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_i8(CILParser.Instr_i8Context context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_method(CILParser.Instr_methodContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_none(CILParser.Instr_noneContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_r(CILParser.Instr_rContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_sig(CILParser.Instr_sigContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_string(CILParser.Instr_stringContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_switch(CILParser.Instr_switchContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_tok(CILParser.Instr_tokContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_type(CILParser.Instr_typeContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ public GrammarResult.Literal VisitInstr_var(CILParser.Instr_varContext context) => new(ParseOpCodeFromToken(((ITerminalNode)context.children[0]).Symbol));
+ private static ILOpCode ParseOpCodeFromToken(IToken token)
+ {
+ return (ILOpCode)Enum.Parse(typeof(ILOpCode), token.Text.Replace('.', '_'), ignoreCase: true);
+ }
+
+ GrammarResult ICILVisitor.VisitInstr(CILParser.InstrContext context) => VisitInstr(context);
+ GrammarResult ICILVisitor.VisitInstr_brtarget(CILParser.Instr_brtargetContext context) => VisitInstr_brtarget(context);
+ GrammarResult ICILVisitor.VisitInstr_field(CILParser.Instr_fieldContext context) => VisitInstr_field(context);
+ GrammarResult ICILVisitor.VisitInstr_i(CILParser.Instr_iContext context) => VisitInstr_i(context);
+ GrammarResult ICILVisitor.VisitInstr_i8(CILParser.Instr_i8Context context) => VisitInstr_i8(context);
+ GrammarResult ICILVisitor.VisitInstr_method(CILParser.Instr_methodContext context) => VisitInstr_method(context);
+ GrammarResult ICILVisitor.VisitInstr_none(CILParser.Instr_noneContext context) => VisitInstr_none(context);
+ GrammarResult ICILVisitor.VisitInstr_r(CILParser.Instr_rContext context) => VisitInstr_r(context);
+ GrammarResult ICILVisitor.VisitInstr_sig(CILParser.Instr_sigContext context) => VisitInstr_sig(context);
+ GrammarResult ICILVisitor.VisitInstr_string(CILParser.Instr_stringContext context) => VisitInstr_string(context);
+ GrammarResult ICILVisitor.VisitInstr_switch(CILParser.Instr_switchContext context) => VisitInstr_switch(context);
+ GrammarResult ICILVisitor.VisitInstr_tok(CILParser.Instr_tokContext context) => VisitInstr_tok(context);
+ GrammarResult ICILVisitor.VisitInstr_type(CILParser.Instr_typeContext context) => VisitInstr_type(context);
+ GrammarResult ICILVisitor.VisitInstr_var(CILParser.Instr_varContext context) => VisitInstr_var(context);
+
+ private static bool ParseIntegerValue(ReadOnlySpan value, out long result)
+ {
+ NumberStyles parseStyle = NumberStyles.None;
+ bool negate = false;
+ if (value.StartsWith("-".AsSpan()))
+ {
+ negate = true;
+ }
+
+ if (value.StartsWith("0x".AsSpan()))
+ {
+ parseStyle = NumberStyles.AllowHexSpecifier;
+ value = value.Slice(2);
+ }
+ else if (value.StartsWith("0".AsSpan()))
+ {
+ // Octal support isn't built-in, so we'll do it manually.
+ result = 0;
+ for (int i = 0; i < value.Length; i++, result *= 8)
+ {
+ int digitValue = value[i] - '0';
+ if (digitValue < 0 || digitValue > 7)
+ {
+ // COMPAT: native ilasm skips invalid digits silently
+ continue;
+ }
+ result += digitValue;
+ }
+ return true;
+ }
+
+ bool success = long.TryParse(value.ToString(), parseStyle, CultureInfo.InvariantCulture, out result);
+ if (!success)
+ {
+ return false;
+ }
+
+ result *= negate ? -1 : 1;
+ return true;
+ }
+
+ GrammarResult ICILVisitor.VisitInt32(CILParser.Int32Context context)
+ {
+ return VisitInt32(context);
+ }
+
+ public GrammarResult.Literal VisitInt32(CILParser.Int32Context context)
+ {
+ IToken node = context.INT32().Symbol;
+
+ ReadOnlySpan value = node.Text.AsSpan();
+
+ if (!ParseIntegerValue(value, out long num))
+ {
+ _diagnostics.Add(new Diagnostic(
+ DiagnosticIds.LiteralOutOfRange,
+ DiagnosticSeverity.Error,
+ string.Format(DiagnosticMessageTemplates.LiteralOutOfRange, node.Text),
+ Location.From(node, _documents)));
+ return new GrammarResult.Literal(0);
+ }
+
+ return new GrammarResult.Literal((int)num);
+ }
+
+
+ GrammarResult ICILVisitor.VisitInt64(CILParser.Int64Context context)
+ {
+ return VisitInt64(context);
+ }
+
+ public GrammarResult.Literal VisitInt64(CILParser.Int64Context context)
+ {
+ IToken node = context.GetChild(0).Symbol;
+
+ ReadOnlySpan value = node.Text.AsSpan();
+
+ if (!ParseIntegerValue(value, out long num))
+ {
+ _diagnostics.Add(new Diagnostic(
+ DiagnosticIds.LiteralOutOfRange,
+ DiagnosticSeverity.Error,
+ string.Format(DiagnosticMessageTemplates.LiteralOutOfRange, node.Text),
+ Location.From(node, _documents)));
+ return new GrammarResult.Literal(0);
+ }
+
+ return new GrammarResult.Literal(num);
+ }
+
+ GrammarResult ICILVisitor.VisitIntOrWildcard(CILParser.IntOrWildcardContext context) => VisitIntOrWildcard(context);
+ public GrammarResult.Literal VisitIntOrWildcard(CILParser.IntOrWildcardContext context) => context.int32() is {} int32 ? new(VisitInt32(int32).Value) : new(null);
+ public GrammarResult VisitLabels(CILParser.LabelsContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+ public GrammarResult VisitLanguageDecl(CILParser.LanguageDeclContext context) => throw new NotImplementedException("TODO: Symbols");
+
+ public GrammarResult VisitManifestResDecl(CILParser.ManifestResDeclContext context) => throw new UnreachableException(NodeShouldNeverBeDirectlyVisited);
+ GrammarResult ICILVisitor.VisitManifestResDecls(CILParser.ManifestResDeclsContext context) => VisitManifestResDecls(context);
+ public GrammarResult.Literal<(EntityRegistry.EntityBase? implementation, uint offset, ImmutableArray attributes)> VisitManifestResDecls(CILParser.ManifestResDeclsContext context)
+ {
+ EntityRegistry.EntityBase? implementation = null;
+ uint offset = 0;
+ var attributes = ImmutableArray.CreateBuilder();
+ // COMPAT: Priority order for implementation is the following
+ // AssemblyRef, File, nil
+ foreach (var decl in context.manifestResDecl())
+ {
+ if (decl.customAttrDecl() is CILParser.CustomAttrDeclContext customAttrDecl)
+ {
+ if (VisitCustomAttrDecl(customAttrDecl).Value is { } attr)
+ {
+ attributes.Add(attr);
+ }
+ }
+ string kind = decl.GetChild(0).GetText();
+ if (kind == ".file" && implementation is not EntityRegistry.AssemblyReferenceEntity)
+ {
+ var file = _entityRegistry.FindFile(VisitDottedName(decl.dottedName()).Value);
+ if (file is null)
+ {
+ // TODO: Report diagnostic
+ }
+ else
+ {
+ implementation = file;
+ offset = (uint)VisitInt32(decl.int32()).Value;
+ }
+ }
+ else if (kind == ".assembly")
+ {
+ var asm = _entityRegistry.FindAssemblyReference(VisitDottedName(decl.dottedName()).Value);
+ if (asm is null)
+ {
+ // TODO: Report diagnostic
+ }
+ else
+ {
+ implementation = asm;
+ }
+ }
+ }
+
+ return new((implementation, offset, attributes.ToImmutable()));
+ }
+ GrammarResult ICILVisitor.VisitManifestResHead(CILParser.ManifestResHeadContext context) => VisitManifestResHead(context);
+ public GrammarResult.Literal<(string name, string alias, ManifestResourceAttributes attr)> VisitManifestResHead(CILParser.ManifestResHeadContext context)
+ {
+ var dottedNames = context.dottedName();
+ string name = VisitDottedName(dottedNames[0]).Value;
+ string alias = dottedNames.Length == 2 ? VisitDottedName(dottedNames[1]).Value : name;
+ ManifestResourceAttributes attr = 0;
+ foreach (var attrContext in context.manresAttr())
+ {
+ attr |= VisitManresAttr(attrContext).Value;
+ }
+
+ return new((name, alias, attr));
+ }
+
+ GrammarResult ICILVisitor.VisitManresAttr(CILParser.ManresAttrContext context) => VisitManresAttr(context);
+ public GrammarResult.Flag