From 83fb8df514d6296784048688d0a87d1fa155b9d2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Sep 2026 10:47:10 -0700 Subject: [PATCH 1/4] Remove CoreCLR Reflection.Emit's ceefilegen dependency Store emitted IL and FieldRVA data in loader-owned buffers indexed by the existing dynamic token map. Generalize dynamic field resolution, remove PE section and token relocation bookkeeping, and preserve ILAsm's no-host generator. Add emitted-body, field-data, and diagnostic coverage and update the canonical cDAC descriptor descriptions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- docs/design/datacontracts/Loader.md | 6 +- .../design/datacontracts/RuntimeTypeSystem.md | 2 +- .../data-descriptor-meanings.json | 8 +- .../Reflection/Emit/DynamicILGenerator.cs | 5 -- .../Reflection/Emit/RuntimeILGenerator.cs | 45 ---------- .../Reflection/Emit/RuntimeMethodBuilder.cs | 9 -- .../Reflection/Emit/RuntimeModuleBuilder.cs | 2 - .../Reflection/Emit/RuntimeTypeBuilder.cs | 9 +- src/coreclr/debug/daccess/dacdbiimpl.cpp | 3 +- .../dlls/mscoree/coreclr/CMakeLists.txt | 1 - src/coreclr/inc/ceegen.h | 3 - src/coreclr/md/ceefilegen/CMakeLists.txt | 3 - src/coreclr/md/ceefilegen/cceegen.cpp | 50 ----------- src/coreclr/vm/ceeload.cpp | 56 +++--------- src/coreclr/vm/ceeload.h | 14 +-- src/coreclr/vm/comdynamic.cpp | 55 ++---------- src/coreclr/vm/comdynamic.h | 2 - src/coreclr/vm/commodule.cpp | 28 ++---- src/coreclr/vm/field.cpp | 6 +- src/coreclr/vm/field.h | 6 +- src/coreclr/vm/methodtablebuilder.cpp | 6 +- src/coreclr/vm/proftoeeinterfaceimpl.cpp | 1 - src/coreclr/vm/reflectclasswriter.cpp | 11 +-- src/coreclr/vm/reflectclasswriter.h | 10 +-- src/coreclr/vm/reflectioninvocation.cpp | 3 +- .../MethodBuilderGetILGenerator.cs | 90 +++++++++++++++++++ .../ModuleBuilderDefineInitializedData.cs | 83 ++++++++++++++++- .../ModuleBuilderDefineUninitializedData.cs | 51 +++++++++++ .../System.Reflection.Emit/tests/Utilities.cs | 18 ++++ .../cdac/tests/UnitTests/LoaderTests.cs | 81 +++++++++++++++++ .../cdac/tests/UnitTests/MethodTableTests.cs | 53 +++++++++++ .../MockDescriptors/MockDescriptors.Loader.cs | 47 ++++++++++ .../MockDescriptors.RuntimeTypeSystem.cs | 5 +- 33 files changed, 471 insertions(+), 301 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 53f5210f854147..4f32f0a89a70fc 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -169,8 +169,8 @@ enum ClrModifiableAssemblies : uint | `CGrowableSymbolStream` | `Buffer` | `pointer` | Pointer to the in-memory symbol stream buffer | | `CGrowableSymbolStream` | `Size` | `uint32` | Size of the symbol stream buffer in bytes | | `DynamicILBlobTable` | *(type size)* | `uint32` | Size in bytes of each table entry | -| `DynamicILBlobTable` | `EntryIL` | `pointer` | Offset of the IL pointer within each dynamic IL table entry | -| `DynamicILBlobTable` | `EntryMethodToken` | `uint32` | Offset of the method token within each dynamic IL table entry | +| `DynamicILBlobTable` | `EntryIL` | `pointer` | Offset of the IL-body or RVA-field-data pointer within each entry | +| `DynamicILBlobTable` | `EntryMethodToken` | `uint32` | Offset of the full MethodDef or FieldDef token within each entry | | `DynamicILBlobTable` | `Table` | `pointer` | Address of the SHash table | | `DynamicILBlobTable` | `TableSize` | `uint32` | Number of entries in the table | | `EEConfig` | `ModifiableAssemblies` | `uint32` | Edit and Continue configuration represented by `ClrModifiableAssemblies` | @@ -199,7 +199,7 @@ enum ClrModifiableAssemblies : uint | `Module` | `Assembly` | `pointer` | Pointer to the containing assembly | | `Module` | `AvailableTypeParams` | `pointer` | Pointer to the available type-parameter hash table | | `Module` | `Base` | `pointer` | Base address of the module's loaded image | -| `Module` | `DynamicILBlobTable` | `pointer` | Pointer to the table of dynamically supplied IL bodies | +| `Module` | `DynamicILBlobTable` | `pointer` | Pointer to the token-keyed table of dynamically supplied IL bodies and RVA field data | | `Module` | `FieldDefToDescMap` | `pointer` | Pointer to the field-definition-to-field-descriptor lookup map | | `Module` | `FileName` | `pointer` | Pointer to the null-terminated UTF-16 module file name | | `Module` | `Flags` | `uint32` | Module state and capability flags | diff --git a/docs/design/datacontracts/RuntimeTypeSystem.md b/docs/design/datacontracts/RuntimeTypeSystem.md index d153b01d5776a9..598cb467aa3c70 100644 --- a/docs/design/datacontracts/RuntimeTypeSystem.md +++ b/docs/design/datacontracts/RuntimeTypeSystem.md @@ -670,7 +670,7 @@ static class RuntimeTypeSystem_1_Helpers | `CoreLib` | `pointer` | Pointer to the CoreLibBinder data containing well-known core library type handles | | `ExceptionMethodTable` | `pointer` | A pointer to the address of the System.Exception MethodTable (g_pExceptionClass) | | `FieldOffsetBigRVA` | `uint32` | Sentinel value of FieldDesc::DWord2 indicating the field is an RVA static whose offset is too large to encode in the bitfield; the real offset must be read from the field's metadata (FieldDefinition.GetRelativeVirtualAddress). | -| `FieldOffsetDynamicRVA` | `uint32` | Sentinel FieldDesc offset for an EnC-added RVA field whose enclosing type is not yet loaded | +| `FieldOffsetDynamicRVA` | `uint32` | Sentinel FieldDesc offset for token-backed RVA field data, including Reflection.Emit fields and EnC-added fields whose enclosing type was not yet loaded when the field was added | | `FreeObjectMethodTable` | `pointer` | A pointer to the address of a MethodTable used by the GC to indicate reclaimed memory | | `MethodDescAlignment` | `uint64` | MethodDescChunk trailing data is allocated in multiples of this constant. The size (in bytes) of each MethodDesc (or subclass) instance is a multiple of this constant. | | `MethodDescTokenRemainderBitCount` | `uint8` | Number of bits in the token remainder in MethodDesc | diff --git a/docs/design/datacontracts/data-descriptor-meanings.json b/docs/design/datacontracts/data-descriptor-meanings.json index 267bfa9c95794e..d322db96ba051a 100644 --- a/docs/design/datacontracts/data-descriptor-meanings.json +++ b/docs/design/datacontracts/data-descriptor-meanings.json @@ -100,8 +100,8 @@ "DynamicHelperFrame.DynamicHelperFrameFlags": "Flags indicating which argument registers contain GC references", "DynamicFunctionTable.Context": "Tagged pointer to the owning `EEJitManager`; low bits are flags", "DynamicFunctionTable.MinimumAddress": "Module base address covered by the dynamic function table", - "DynamicILBlobTable.EntryIL": "Offset of the IL pointer within each dynamic IL table entry", - "DynamicILBlobTable.EntryMethodToken": "Offset of the method token within each dynamic IL table entry", + "DynamicILBlobTable.EntryIL": "Offset of the IL-body or RVA-field-data pointer within each entry", + "DynamicILBlobTable.EntryMethodToken": "Offset of the full MethodDef or FieldDef token within each entry", "DynamicILBlobTable.Size": "Size in bytes of each table entry", "DynamicILBlobTable.Table": "Address of the SHash table", "DynamicILBlobTable.TableSize": "Number of entries in the table", @@ -401,7 +401,7 @@ "Module.Assembly": "Pointer to the containing assembly", "Module.AvailableTypeParams": "Pointer to the available type-parameter hash table", "Module.Base": "Base address of the module's loaded image", - "Module.DynamicILBlobTable": "Pointer to the table of dynamically supplied IL bodies", + "Module.DynamicILBlobTable": "Pointer to the token-keyed table of dynamically supplied IL bodies and RVA field data", "Module.DynamicMetadata": "Pointer to metadata updated dynamically through Edit and Continue", "Module.EnCClassList": "Pointer to the list of classes added through Edit and Continue", "Module.FieldDefToDescMap": "Pointer to the field-definition-to-field-descriptor lookup map", @@ -769,7 +769,7 @@ "FeaturePortableEntrypoints": "Present (nonzero) when portable entrypoints are enabled", "FeatureWebcil": "Present (nonzero) when Webcil is enabled", "FieldOffsetBigRVA": "Sentinel value of FieldDesc::DWord2 indicating the field is an RVA static whose offset is too large to encode in the bitfield; the real offset must be read from the field's metadata (FieldDefinition.GetRelativeVirtualAddress).", - "FieldOffsetDynamicRVA": "Sentinel FieldDesc offset for an EnC-added RVA field whose enclosing type is not yet loaded", + "FieldOffsetDynamicRVA": "Sentinel FieldDesc offset for token-backed RVA field data, including Reflection.Emit fields and EnC-added fields whose enclosing type was not yet loaded when the field was added", "FieldOffsetNewEnc": "Sentinel offset value stored in FieldDesc::DWord2 for added fields whose storage has not yet been allocated.", "FinalizerThread": "Pointer to the finalizer thread", "FunctionTableIndexRangeList": "Pointer to the head pointer of the registered WASM R2R function-table range list", diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs index 42fad545acf345..032e916373f865 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs @@ -504,11 +504,6 @@ private void AddParameters(SignatureHelper sigHelp, Type[]? parameterTypes, Type } } - internal override void RecordTokenFixup() - { - // DynamicMethod doesn't need fixup. - } - #region GetTokenFor helpers private int GetTokenFor(RuntimeType rtType) { diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs index eaba793d93bb40..f86655664f7cd1 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs @@ -47,9 +47,6 @@ internal static T[] EnlargeArray(T[] incoming, int requiredSize) private int m_fixupCount; - private int[]? m_RelocFixupList; - private int m_RelocFixupCount; - private int m_exceptionCount; private int m_currExcStackCount; private __ExceptionInfo[]? m_exceptions; // This is the list of all of the exceptions in this ILStream. @@ -101,20 +98,6 @@ internal RuntimeILGenerator(MethodInfo methodBuilder, int size) #endregion #region Internal Members - internal virtual void RecordTokenFixup() - { - if (m_RelocFixupList == null) - { - m_RelocFixupList = new int[DefaultFixupArraySize]; - } - else if (m_RelocFixupList.Length <= m_RelocFixupCount) - { - m_RelocFixupList = EnlargeArray(m_RelocFixupList); - } - - m_RelocFixupList[m_RelocFixupCount++] = m_length; - } - internal void InternalEmit(OpCode opcode) { short opcodeValue = opcode.Value; @@ -355,18 +338,6 @@ private static void SortExceptions(__ExceptionInfo[] exceptions) } } - internal int[]? GetTokenFixups() - { - if (m_RelocFixupCount == 0) - { - Debug.Assert(m_RelocFixupList == null); - return null; - } - - int[] narrowTokens = new int[m_RelocFixupCount]; - Array.Copy(m_RelocFixupList!, narrowTokens, m_RelocFixupCount); - return narrowTokens; - } #endregion #region Public Members @@ -506,7 +477,6 @@ public override void Emit(OpCode opcode, MethodInfo meth) InternalEmit(opcode); UpdateStackSize(opcode, 0); - RecordTokenFixup(); PutInteger4(tk); } } @@ -555,7 +525,6 @@ public override void EmitCalli(OpCode opcode, CallingConventions callingConventi UpdateStackSize(OpCodes.Calli, stackchange); - RecordTokenFixup(); PutInteger4(modBuilder.GetSignatureMetadataToken(sig)); } @@ -598,7 +567,6 @@ public override void EmitCalli(OpCode opcode, CallingConvention unmanagedCallCon EnsureCapacity(7); Emit(OpCodes.Calli); - RecordTokenFixup(); PutInteger4(modBuilder.GetSignatureMetadataToken(sig)); } @@ -632,7 +600,6 @@ public override void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? opti stackchange -= optionalParameterTypes.Length; UpdateStackSize(opcode, stackchange); - RecordTokenFixup(); PutInteger4(tk); } @@ -669,7 +636,6 @@ public override void Emit(OpCode opcode, SignatureHelper signature) UpdateStackSize(opcode, stackchange); } - RecordTokenFixup(); PutInteger4(tempVal); } @@ -709,23 +675,17 @@ public override void Emit(OpCode opcode, ConstructorInfo con) } UpdateStackSize(opcode, stackchange); - RecordTokenFixup(); PutInteger4(tk); } public override void Emit(OpCode opcode, Type cls) { - // Puts opcode onto the stream and then the metadata token represented - // by cls. The location of cls is recorded so that the token can be - // patched if necessary when persisting the module to a PE. - RuntimeModuleBuilder modBuilder = (RuntimeModuleBuilder)m_methodBuilder.Module; bool getGenericDefinition = (opcode == OpCodes.Ldtoken && cls != null && cls.IsGenericTypeDefinition); int tempVal = modBuilder.GetTypeTokenInternal(cls!, getGenericDefinition); EnsureCapacity(7); InternalEmit(opcode); - RecordTokenFixup(); PutInteger4(tempVal); } @@ -807,16 +767,11 @@ public override void Emit(OpCode opcode, FieldInfo field) int tempVal = modBuilder.GetFieldMetadataToken(field); EnsureCapacity(7); InternalEmit(opcode); - RecordTokenFixup(); PutInteger4(tempVal); } public override void Emit(OpCode opcode, string str) { - // Puts the opcode onto the IL stream followed by the metadata token - // represented by str. The location of str is recorded for future - // fixups if the module is persisted to a PE. - ModuleBuilder modBuilder = (ModuleBuilder)m_methodBuilder.Module; int tempVal = modBuilder.GetStringMetadataToken(str); EnsureCapacity(7); diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeMethodBuilder.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeMethodBuilder.cs index 6ee68d1843e80b..9365214a60fc82 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeMethodBuilder.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeMethodBuilder.cs @@ -23,7 +23,6 @@ internal sealed class RuntimeMethodBuilder : MethodBuilder internal RuntimeTypeBuilder m_containingType; // IL - private int[]? m_mdMethodFixups; // The location of all of the token fixups. Null means no fixups. private byte[]? m_localSignature; // Local signature if set explicitly via DefineBody. Null otherwise. internal LocalSymInfo? m_localSymInfo; // keep track debugging local information internal RuntimeILGenerator? m_ilGenerator; // Null if not used. @@ -174,8 +173,6 @@ internal void CreateMethodBodyHelper(RuntimeILGenerator il) m_ubBody = il.BakeByteArray(); - m_mdMethodFixups = il.GetTokenFixups(); - // Okay, now the fun part. Calculate all of the exceptions. excp = il.GetExceptions()!; int numExceptions = CalculateNumberOfExceptions(excp); @@ -232,7 +229,6 @@ internal void ReleaseBakedStructures() m_ubBody = null; m_localSymInfo = null; - m_mdMethodFixups = null; m_localSignature = null; m_exceptions = null; } @@ -268,11 +264,6 @@ internal void SetToken(int token) return m_ubBody; } - internal int[]? GetTokenFixups() - { - return m_mdMethodFixups; - } - internal SignatureHelper GetMethodSignature() { m_parameterTypes ??= Type.EmptyTypes; diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs index bc15a724eb3251..b68b480827b38c 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs @@ -850,7 +850,6 @@ protected override FieldBuilder DefineInitializedDataCore(string name, byte[] da { lock (SyncRoot) { - // This method will define an initialized Data in .sdata. // We will create a fake TypeDef to represent the data with size. This TypeDef // will be the signature for the Field. if (_hasGlobalBeenCreated) @@ -866,7 +865,6 @@ protected override FieldBuilder DefineUninitializedDataCore(string name, int siz { lock (SyncRoot) { - // This method will define an uninitialized Data in .sdata. // We will create a fake TypeDef to represent the data with size. This TypeDef // will be the signature for the Field. diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeTypeBuilder.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeTypeBuilder.cs index 9947057a244f98..87ce37ba8b0aaf 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeTypeBuilder.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeTypeBuilder.cs @@ -87,8 +87,7 @@ private static partial void SetMethodIL(QCallModule module, int tk, [MarshalAs(U byte[]? body, int bodyLength, byte[] LocalSig, int sigLength, int maxStackSize, - ExceptionHandler[]? exceptions, int numExceptions, - int[]? tokenFixups, int numTokenFixups); + ExceptionHandler[]? exceptions, int numExceptions); [ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)] [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeBuilder_DefineCustomAttribute")] @@ -1379,7 +1378,6 @@ protected override FieldBuilder DefineInitializedDataCore(string name, byte[] da { lock (SyncRoot) { - // This method will define an initialized Data in .sdata. // We will create a fake TypeDef to represent the data with size. This TypeDef // will be the signature for the Field. @@ -1391,7 +1389,6 @@ protected override FieldBuilder DefineUninitializedDataCore(string name, int siz { lock (SyncRoot) { - // This method will define an uninitialized Data in .sdata. // We will create a fake TypeDef to represent the data with size. This TypeDef // will be the signature for the Field. return DefineDataHelper(name, null, size, attributes); @@ -1636,13 +1633,11 @@ protected override TypeInfo CreateTypeInfoCore() int maxStack = meth.GetMaxStack(); ExceptionHandler[]? exceptions = meth.GetExceptionHandlers(); - int[]? tokenFixups = meth.GetTokenFixups(); SetMethodIL(new QCallModule(ref module), meth.MetadataToken, meth.InitLocals, body, (body != null) ? body.Length : 0, localSig, sigLength, maxStack, - exceptions, (exceptions != null) ? exceptions.Length : 0, - tokenFixups, (tokenFixups != null) ? tokenFixups.Length : 0); + exceptions, (exceptions != null) ? exceptions.Length : 0); if (m_module.ContainingAssemblyBuilder._access == AssemblyBuilderAccess.Run) { diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index e2271ba695b9fd..6185aa778d4734 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -991,8 +991,7 @@ mdSignature DacDbiInterfaceImpl::GetILCodeAndSigHelper(Module * pModule, TADDR pTargetIL = pModule->GetDynamicIL(mdMethodToken); // Method not overridden - get the original copy of the IL by going to the PE file/RVA - // If this is in a dynamic module then don't even attempt this since ReflectionModule::GetIL isn't - // implemented for DAC. + // Dynamic modules have no PE-backed fallback. if (pTargetIL == 0 && !pModule->IsReflectionEmit()) { pTargetIL = (TADDR)pModule->GetIL(methodRVA); diff --git a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt index d510519874a91a..5f652feb0430b8 100644 --- a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt @@ -80,7 +80,6 @@ set(CORECLR_LIBRARIES mdcompiler_wks mdruntime_wks mdruntimerw_wks - ceefgen corguids utilcode v3binder diff --git a/src/coreclr/inc/ceegen.h b/src/coreclr/inc/ceegen.h index 7264c28eca1778..3d7dfdbbdfb680 100644 --- a/src/coreclr/inc/ceegen.h +++ b/src/coreclr/inc/ceegen.h @@ -193,7 +193,6 @@ class CCeeGen : public ICeeGenInternal { HRESULT addSection(CeeSection *section, short *sectionIdx); -// Init process: Call static CreateNewInstance() , not operator new protected: HRESULT Init(); CCeeGen(); @@ -202,8 +201,6 @@ class CCeeGen : public ICeeGenInternal { virtual ~CCeeGen() {} - static HRESULT CreateNewInstance(CCeeGen* & pCeeFileGen); // call this to instantiate - virtual HRESULT Cleanup(); // ICeeGenInternal interfaces diff --git a/src/coreclr/md/ceefilegen/CMakeLists.txt b/src/coreclr/md/ceefilegen/CMakeLists.txt index 708c6ec0426343..12062a4f6f0d65 100644 --- a/src/coreclr/md/ceefilegen/CMakeLists.txt +++ b/src/coreclr/md/ceefilegen/CMakeLists.txt @@ -24,9 +24,6 @@ if (CLR_CMAKE_TARGET_WIN32) list(APPEND CEEFILEGEN_SOURCES ${CEEFILEGEN_HEADERS}) endif (CLR_CMAKE_TARGET_WIN32) -add_library_clr(ceefgen OBJECT ${CEEFILEGEN_SOURCES}) -target_precompile_headers(ceefgen PRIVATE stdafx.h) - # SELF_NO_HOST disables the hosted contract machinery # so these tools do not require the contract implementation at link time. add_library_clr(ceefgen_nohost OBJECT ${CEEFILEGEN_SOURCES}) diff --git a/src/coreclr/md/ceefilegen/cceegen.cpp b/src/coreclr/md/ceefilegen/cceegen.cpp index 42458e64ca25c0..740d154e477899 100644 --- a/src/coreclr/md/ceefilegen/cceegen.cpp +++ b/src/coreclr/md/ceefilegen/cceegen.cpp @@ -10,56 +10,6 @@ #include "memorystreams.h" -//***************************************************************************** -// Creation for new CCeeGen instances -// -// Both allocate and call virtual Init() (Can't call v-func in a ctor, -// but we want to create in 1 call); -//***************************************************************************** - -HRESULT STDMETHODCALLTYPE CreateICeeGen(REFIID riid, void **pCeeGen) -{ - if (riid != IID_ICeeGenInternal) - return E_NOTIMPL; - if (!pCeeGen) - return E_POINTER; - CCeeGen *pCeeFileGen; - HRESULT hr = CCeeGen::CreateNewInstance(pCeeFileGen); - if (FAILED(hr)) - return hr; - pCeeFileGen->AddRef(); - *(CCeeGen**)pCeeGen = pCeeFileGen; - return S_OK; -} - -HRESULT CCeeGen::CreateNewInstance(CCeeGen* & pGen) // static, public -{ - NewHolder pGenHolder(new CCeeGen()); - _ASSERTE(pGenHolder != NULL); - TESTANDRETURNMEMORY(pGenHolder); - - pGenHolder->m_peSectionMan = new PESectionMan; - _ASSERTE(pGenHolder->m_peSectionMan != NULL); - TESTANDRETURNMEMORY(pGenHolder->m_peSectionMan); - - HRESULT hr = pGenHolder->m_peSectionMan->Init(); - if (FAILED(hr)) - { - pGenHolder->Cleanup(); - return hr; - } - - hr = pGenHolder->Init(); - if (FAILED(hr)) - { - // Init() calls Cleanup() on failure - return hr; - } - - pGen = pGenHolder.Extract(); - return hr; -} - STDMETHODIMP CCeeGen::QueryInterface(REFIID riid, void** ppv) { if (!ppv) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 1135769aae8150..b806bd645c1b73 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -81,8 +81,6 @@ #define COR_VTABLE_NOT_PTRSIZED COR_VTABLE_64BIT #endif // !TARGET_64BIT -#define CEE_FILE_GEN_GROWTH_COLLECTIBLE 2048 - #define NGEN_STATICS_ALLCLASSES_WERE_LOADED -1 #ifdef FEATURE_INLINE_TRACKING @@ -912,12 +910,11 @@ void Module::SetDynamicIL(mdToken token, TADDR blobAddress) #endif // !DACCESS_COMPILE -// Get the stored address of the IL blob for reflection/dynamics +// Get the stored address of an IL body or RVA field's data for reflection/dynamics // Arguments: // Input: -// token method token -// fAllowTemporary also check the temporary overrides -// Return Value: starting (target) address of the IL blob corresponding to the input token +// token method token or field token +// Return Value: starting (target) address of the data corresponding to the input token TADDR Module::GetDynamicIL(mdToken token) { @@ -3836,12 +3833,8 @@ ReflectionModule::ReflectionModule(Assembly *pAssembly, PEAssembly *pPEAssembly) CONTRACTL_END m_pInMemoryWriter = NULL; - m_sdataSection = NULL; - m_pCeeFileGen = NULL; } -HRESULT STDMETHODCALLTYPE CreateICeeGen(REFIID riid, void **pCeeGen); - // Module initialization occurs in two phases: the constructor phase and the Initialize phase. // // The Initialize() phase completes the initialization after the constructor has run. @@ -3860,20 +3853,9 @@ void ReflectionModule::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) Module::Initialize(pamTracker); - IfFailThrow(CreateICeeGen(IID_ICeeGenInternal, (void **)&m_pCeeFileGen)); - - // Collectible modules should try to limit the growth of their associate IL section, as common scenarios for collectible - // modules include single type modules - if (IsCollectible()) - { - ReleaseHolder pCeeGenInternal(NULL); - IfFailThrow(m_pCeeFileGen->QueryInterface(IID_ICeeGenInternal, (void **)&pCeeGenInternal)); - IfFailThrow(pCeeGenInternal->SetInitialGrowth(CEE_FILE_GEN_GROWTH_COLLECTIBLE)); - } - m_pInMemoryWriter = new RefClassWriter(); - IfFailThrow(m_pInMemoryWriter->Init(GetCeeGen(), GetEmitter(), szName)); + IfFailThrow(m_pInMemoryWriter->Init(GetEmitter(), szName)); m_CrstLeafLock.Init(CrstLeafLock); } @@ -3890,9 +3872,6 @@ void ReflectionModule::Destruct() delete m_pInMemoryWriter; - if (m_pCeeFileGen) - m_pCeeFileGen->Release(); - Module::Destruct(); delete[] (uint8_t*)m_pDynamicMetadata; @@ -4073,33 +4052,20 @@ TADDR ReflectionModule::GetDynamicMetadataBuffer() const } #endif -TADDR ReflectionModule::GetIL(RVA il) // virtual +TADDR ReflectionModule::GetIL(RVA methodToken) // virtual { #ifndef DACCESS_COMPILE WRAPPER_NO_CONTRACT; - BYTE* pByte = NULL; - if (il != 0) - m_pCeeFileGen->GetMethodBuffer(il, &pByte); - return TADDR(pByte); -#else // DACCESS_COMPILE - SUPPORTS_DAC; - DacNotImpl(); - return (TADDR)NULL; -#endif // DACCESS_COMPILE -} -PTR_VOID ReflectionModule::GetRvaField(RVA field) // virtual -{ -#ifndef DACCESS_COMPILE - WRAPPER_NO_CONTRACT; - // This function should be call only if the target is a field or a field with RVA. - PTR_BYTE pByte = NULL; - m_pCeeFileGen->ComputePointer(m_sdataSection, field, &pByte); - return dac_cast(pByte); + if (methodToken == 0) + return 0; + + _ASSERTE(TypeFromToken(methodToken) == mdtMethodDef); + return GetDynamicIL(methodToken); #else // DACCESS_COMPILE SUPPORTS_DAC; DacNotImpl(); - return NULL; + return 0; #endif // DACCESS_COMPILE } diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 57bcd880d6067b..f2ce79df366220 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1568,12 +1568,10 @@ class Module : public ModuleBase #endif // !DACCESS_COMPILE TADDR GetDynamicIL(mdToken token); -protected: #ifndef DACCESS_COMPILE void SetDynamicRvaField(mdToken token, TADDR blobAddress); #endif // !DACCESS_COMPILE -public: TADDR GetDynamicRvaField(mdToken token); // store and retrieve the instrumented IL offset mapping for a particular method @@ -1756,11 +1754,6 @@ class ReflectionModule : public Module { VPTR_VTABLE_CLASS(ReflectionModule, Module) - public: - HCEESECTION m_sdataSection; - - protected: - ICeeGenInternal * m_pCeeFileGen; private: RefClassWriter *m_pInMemoryWriter; @@ -1785,11 +1778,8 @@ class ReflectionModule : public Module void Destruct(); #endif // !DACCESS_COMPILE - // Overrides functions to access sections - virtual TADDR GetIL(RVA target); - virtual PTR_VOID GetRvaField(RVA rva); - - ICeeGenInternal *GetCeeGen() {LIMITED_METHOD_CONTRACT; return m_pCeeFileGen; } + // Emitted methods use their tokens in the metadata RVA column. + virtual TADDR GetIL(RVA methodToken); RefClassWriter *GetClassWriter() { diff --git a/src/coreclr/vm/comdynamic.cpp b/src/coreclr/vm/comdynamic.cpp index a951046046d49c..19a2d80defa934 100644 --- a/src/coreclr/vm/comdynamic.cpp +++ b/src/coreclr/vm/comdynamic.cpp @@ -307,8 +307,6 @@ extern "C" void QCALLTYPE TypeBuilder_SetMethodIL(QCall::ModuleHandle pModule, UINT16 maxStackSize, ExceptionInstance * pExceptions, INT32 numExceptions, - INT32 * pTokenFixups, - INT32 numTokenFixups, QCallExceptionStatus* qcallError) { QCALL_CONTRACT; @@ -382,14 +380,11 @@ extern "C" void QCALLTYPE TypeBuilder_SetMethodIL(QCall::ModuleHandle pModule, if (totalSizeSafe.IsOverflow()) COMPlusThrowOM(); UINT32 totalSize = totalSizeSafe.Value(); - ICeeGenInternal* pGen = pRCW->GetCeeGen(); - BYTE* buf = NULL; - ULONG methodRVA = 0; - IfFailThrow(pGen->AllocateMethodBuffer(totalSize, &buf, &methodRVA)); + BYTE* buf = static_cast(static_cast( + pModule->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(totalSize)))); _ASSERTE(buf != NULL); _ASSERTE((((size_t) buf) & (sizeof(DWORD) - 1)) == 0); // header is dword aligned - _ASSERTE(methodRVA != 0); // Method RVAs should never be 0, since that is reserved in ECMA-335. INDEBUG(BYTE* endbuf = &buf[totalSize]); BYTE* startBuf = buf; @@ -407,62 +402,26 @@ extern "C" void QCALLTYPE TypeBuilder_SetMethodIL(QCall::ModuleHandle pModule, buf += codeSizeAligned; // Emit the eh - CQuickArray ehTypeOffsets; if (numExceptions > 0) { - // Allocate space for the offsets to the TypeTokens in the Exception headers - // in the IL stream. - ehTypeOffsets.AllocThrows(numExceptions); - - // Emit the eh. This will update the array ehTypeOffsets with offsets - // to Exception type tokens. The offsets are with reference to the - // beginning of eh section. buf += COR_ILMETHOD_SECT_EH::Emit(ehSize, numExceptions, clauses.Ptr(), - false, buf, ehTypeOffsets.Ptr()); + false, buf); } _ASSERTE(buf == endbuf); - //Get the IL Section. - HCEESECTION ilSection; - IfFailThrow(pGen->GetIlSection(&ilSection)); - - // Token Fixup data... - ULONG ilOffset = methodRVA + headerSize; - - //Add all of the relocs based on the info which I saved from ILGenerator. - - //Add the Token Fixups - for (int iTokenFixup=0; iTokenFixupAddSectionReloc(ilSection, pTokenFixups[iTokenFixup] + ilOffset, ilSection, srRelocMapToken)); - } - - // Add token fixups for exception type tokens. - for (int iException=0; iException < numExceptions; iException++) - { - if (ehTypeOffsets[iException] != (ULONG) -1) - { - IfFailThrow(pGen->AddSectionReloc( - ilSection, - ehTypeOffsets[iException] + codeSizeAligned + ilOffset, - ilSection, srRelocMapToken)); - } - } - //nasty interface workaround. What does this mean for abstract methods? if (fatHeader.GetCodeSize() != 0) { - // add the starting address of the il blob to the il blob hash table - // we need to find this information from out of process for debugger inspection - // APIs so we have to store this information where we can get it later + // Publish the body by method token for execution and out-of-process inspection. pModule->SetDynamicIL(mdToken(tk), TADDR(startBuf)); DWORD dwImplFlags; - //Set the RVA of the method. + // Use the method token instead of an image offset. A method with IL must have a nonzero + // metadata RVA (ECMA-335 II.22.26); ReflectionModule::GetIL resolves it through the token map. IfFailThrow(pRCW->GetMDImport()->GetMethodImplProps(tk, NULL, &dwImplFlags)); dwImplFlags |= (miManaged | miIL); - IfFailThrow(pRCW->GetEmitter()->SetMethodProps(tk, (DWORD) -1, methodRVA, dwImplFlags)); + IfFailThrow(pRCW->GetEmitter()->SetMethodProps(tk, (DWORD) -1, static_cast(tk), dwImplFlags)); } END_QCALL; diff --git a/src/coreclr/vm/comdynamic.h b/src/coreclr/vm/comdynamic.h index f94c4df55c7ee9..66cc104f7e528b 100644 --- a/src/coreclr/vm/comdynamic.h +++ b/src/coreclr/vm/comdynamic.h @@ -82,8 +82,6 @@ extern "C" void QCALLTYPE TypeBuilder_SetMethodIL(QCall::ModuleHandle pModule, UINT16 maxStackSize, ExceptionInstance * pExceptions, INT32 numExceptions, - INT32 * pTokenFixups, - INT32 numTokenFixups, QCallExceptionStatus* qcallError); diff --git a/src/coreclr/vm/commodule.cpp b/src/coreclr/vm/commodule.cpp index 61b48f17a6de50..48fae85ac816ef 100644 --- a/src/coreclr/vm/commodule.cpp +++ b/src/coreclr/vm/commodule.cpp @@ -7,7 +7,6 @@ #include "comdynamic.h" #include "reflectclasswriter.h" #include "class.h" -#include "ceesectionstring.h" #include #include "typeparse.h" #include "typekey.h" @@ -481,28 +480,13 @@ extern "C" void QCALLTYPE ModuleBuilder_SetFieldRVAContent(QCall::ModuleHandle p RefClassWriter * pRCW = pModule->GetReflectionModule()->GetClassWriter(); _ASSERTE(pRCW); - ICeeGenInternal * pGen = pRCW->GetCeeGen(); - ReflectionModule * pReflectionModule = pModule->GetReflectionModule(); - // Create the .sdata section if not created - if (pReflectionModule->m_sdataSection == 0) - IfFailThrow( pGen->GetSectionCreate (".sdata", sdReadWrite, &pReflectionModule->m_sdataSection) ); - - // Define the alignment that the rva will be set to. Since the CoreCLR runtime only has hard alignment requirements - // up to 8 bytes, the highest alignment we may need is 8 byte alignment. This hard alignment requirement is only needed - // by Runtime.Helpers.CreateSpan. Since the previous alignment was 4 bytes before CreateSpan was implemented, if the - // data isn't itself of size divisible by 8, just align to 4 to the memory cost of excess alignment. + // CreateSpan requires natural alignment up to 8 bytes. Data whose size is not divisible + // by 8 only needs 4-byte alignment. DWORD alignment = (length % 8 == 0) ? 8 : 4; - // Get the size of current .sdata section. This will be the RVA for this field within the section - DWORD dwRVA = 0; - IfFailThrow( pGen->GetSectionDataLen(pReflectionModule->m_sdataSection, &dwRVA) ); - dwRVA = (dwRVA + alignment-1) & ~(alignment-1); - - // allocate the space in .sdata section - void * pvBlob; - IfFailThrow( pGen->GetSectionBlock(pReflectionModule->m_sdataSection, length, alignment, (void**) &pvBlob) ); + void * pvBlob = pReflectionModule->GetLoaderAllocator()->GetLowFrequencyHeap()->AllocAlignedMem(length, alignment); // copy over the initialized data if specified if (pContent != NULL) @@ -514,8 +498,10 @@ extern "C" void QCALLTYPE ModuleBuilder_SetFieldRVAContent(QCall::ModuleHandle p LoaderAllocator::AssociateMemoryWithLoaderAllocator((BYTE*)pvBlob, ((BYTE*)pvBlob) + length, pReflectionModule->GetLoaderAllocator()); } - // set FieldRVA into metadata. Note that this is not final RVA in the image if save to disk. We will do another round of fix up upon save. - IfFailThrow( pRCW->GetEmitter()->SetFieldRVA(tkField, dwRVA) ); + pReflectionModule->SetDynamicRvaField(tkField, reinterpret_cast(pvBlob)); + + // Dynamic RVA fields are resolved by token, not by an image offset. + IfFailThrow( pRCW->GetEmitter()->SetFieldRVA(tkField, 0) ); END_QCALL; } diff --git a/src/coreclr/vm/field.cpp b/src/coreclr/vm/field.cpp index a5cf04b4820e3f..2d7cfed484f192 100644 --- a/src/coreclr/vm/field.cpp +++ b/src/coreclr/vm/field.cpp @@ -276,12 +276,10 @@ PTR_VOID FieldDesc::GetStaticAddressHandle(PTR_VOID base) Module* pModule = GetModule(); if (offset == FIELD_OFFSET_DYNAMIC_RVA) { -#ifdef FEATURE_METADATA_UPDATER _ASSERTE(!IsEnCNew()); - _ASSERTE(pModule->IsEditAndContinueEnabled()); - LOG((LF_ENC, LL_INFO1000, "FD::GSAH: Dynamic (EnC) - RVA\n")); + _ASSERTE(pModule->IsReflectionEmit() || pModule->IsEditAndContinueEnabled()); + LOG((LF_CORDB, LL_INFO1000, "FD::GSAH: Dynamic RVA\n")); retVal = PTR_VOID(pModule->GetDynamicRvaField(GetMemberDef())); -#endif // FEATURE_METADATA_UPDATER } else { diff --git a/src/coreclr/vm/field.h b/src/coreclr/vm/field.h index 5b42aaff870bb2..b1cdcab15b01a5 100644 --- a/src/coreclr/vm/field.h +++ b/src/coreclr/vm/field.h @@ -23,7 +23,7 @@ // Offset to indicate an EnC added field. They don't have offsets as aren't placed in the object. #define FIELD_OFFSET_NEW_ENC (FIELD_OFFSET_MAX-4) #define FIELD_OFFSET_BIG_RVA (FIELD_OFFSET_MAX-5) -// Offset to indicate a FieldRVA that is added by EnC, but whose enclosing type is not yet loaded. +// Offset to indicate a FieldRVA whose data is stored in the module's dynamic token map. #define FIELD_OFFSET_DYNAMIC_RVA (FIELD_OFFSET_MAX-6) #define FIELD_OFFSET_LAST_REAL_OFFSET (FIELD_OFFSET_MAX-7) // real fields have to be smaller than this @@ -257,8 +257,7 @@ class FieldDesc { LIMITED_METHOD_CONTRACT; - // The FIELD_OFFSET_DYNAMIC_RVA is a special case for EnC added fields when the - // type they are on is not yet loaded. + // The field's address is resolved through the module's dynamic token map. m_dwOffset = FIELD_OFFSET_DYNAMIC_RVA; } @@ -745,4 +744,3 @@ struct cdac_data }; #endif // _FIELD_H_ - diff --git a/src/coreclr/vm/methodtablebuilder.cpp b/src/coreclr/vm/methodtablebuilder.cpp index 9372036a62f67b..c015fcdfd27daa 100644 --- a/src/coreclr/vm/methodtablebuilder.cpp +++ b/src/coreclr/vm/methodtablebuilder.cpp @@ -4622,16 +4622,12 @@ VOID MethodTableBuilder::InitializeFieldDescs(FieldDesc *pFieldDescList, // The PE should be loaded by now. _ASSERT(GetModule()->GetPEAssembly()->IsLoaded()); -#ifdef FEATURE_METADATA_UPDATER - // This is a special case for EnC. The RVA field is not actually in the image, but - // is instead registered in a dynamic map. We need to set the RVA to a special - // value so when the address is looked up, it will be found in the dynamic map. + // Dynamically allocated field data is resolved by token rather than an image RVA. if (GetModule()->GetDynamicRvaField(pFD->GetMemberDef()) != (TADDR)NULL) { pFD->SetDynamicRVA(); } else -#endif // FEATURE_METADATA_UPDATER { // Set the field offset DWORD rva; diff --git a/src/coreclr/vm/proftoeeinterfaceimpl.cpp b/src/coreclr/vm/proftoeeinterfaceimpl.cpp index a44573bbf8bb78..b028b6c2a80f59 100644 --- a/src/coreclr/vm/proftoeeinterfaceimpl.cpp +++ b/src/coreclr/vm/proftoeeinterfaceimpl.cpp @@ -118,7 +118,6 @@ #include "corprof.h" #include "class.h" #include "object.h" -#include "ceegen.h" #include "eeconfig.h" #include "generics.h" #include "gcinfo.h" diff --git a/src/coreclr/vm/reflectclasswriter.cpp b/src/coreclr/vm/reflectclasswriter.cpp index caa8dff09d3db8..e2c7bc59320ab9 100644 --- a/src/coreclr/vm/reflectclasswriter.cpp +++ b/src/coreclr/vm/reflectclasswriter.cpp @@ -12,12 +12,11 @@ //* constructor for RefClassWriter //* //****************************************************** -HRESULT RefClassWriter::Init(ICeeGenInternal *pCeeGen, IUnknown *pUnk, LPCWSTR szName) +HRESULT RefClassWriter::Init(IUnknown *pUnk, LPCWSTR szName) { CONTRACTL { STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pCeeGen)); PRECONDITION(CheckPointer(pUnk)); } CONTRACTL_END; @@ -28,9 +27,6 @@ HRESULT RefClassWriter::Init(ICeeGenInternal *pCeeGen, IUnknown *pUnk, LPCWSTR s m_internalimport = NULL; m_ulResourceSize = 0; - m_pCeeGen = pCeeGen; - pCeeGen->AddRef(); - // Get the interfaces HRESULT hr = pUnk->QueryInterface(IID_IMetaDataEmit2, (void**)&m_emitter); if (FAILED(hr)) @@ -92,9 +88,4 @@ RefClassWriter::~RefClassWriter() if (m_internalimport) { m_internalimport->Release(); } - - if (m_pCeeGen) { - m_pCeeGen->Release(); - m_pCeeGen = NULL; - } } diff --git a/src/coreclr/vm/reflectclasswriter.h b/src/coreclr/vm/reflectclasswriter.h index d2255e0dd1cb69..d52a71a88506f8 100644 --- a/src/coreclr/vm/reflectclasswriter.h +++ b/src/coreclr/vm/reflectclasswriter.h @@ -7,8 +7,6 @@ #ifndef _REFCLASSWRITER_H_ #define _REFCLASSWRITER_H_ -#include "iceefilegen.h" - // RefClassWriter // This will create a Class class RefClassWriter { @@ -17,7 +15,6 @@ class RefClassWriter { IMetaDataEmit2* m_emitter; // Emit interface. IMetaDataImport* m_importer; // Import interface. IMDInternalImport* m_internalimport; // Scopeless internal import interface - ICeeGenInternal* m_pCeeGen; IMetaDataEmitHelper* m_pEmitHelper; ULONG m_ulResourceSize; @@ -26,7 +23,7 @@ class RefClassWriter { LIMITED_METHOD_CONTRACT; } - HRESULT Init(ICeeGenInternal *pCeeGen, IUnknown *pUnk, LPCWSTR szName); + HRESULT Init(IUnknown *pUnk, LPCWSTR szName); IMetaDataEmit2* GetEmitter() { LIMITED_METHOD_CONTRACT; @@ -48,11 +45,6 @@ class RefClassWriter { return m_internalimport; } - ICeeGenInternal* GetCeeGen() { - LIMITED_METHOD_CONTRACT; - return m_pCeeGen; - } - ~RefClassWriter(); }; diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index 408415d006d4b9..48d929a59b8ece 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -1178,8 +1178,7 @@ FCIMPL1(void*, RuntimeFieldHandle::GetStaticFieldAddress, ReflectFieldObject *pF if (pFieldDesc->IsRVA()) { - Module* pModule = pFieldDesc->GetModule(); - return pModule->GetRvaField(pFieldDesc->GetOffset()); + return pFieldDesc->GetStaticAddressHandle(nullptr); } else { diff --git a/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs b/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs index 2ce5cab8152e20..105aff0387c12f 100644 --- a/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs +++ b/src/libraries/System.Reflection.Emit/tests/MethodBuilder/MethodBuilderGetILGenerator.cs @@ -7,6 +7,96 @@ namespace System.Reflection.Emit.Tests { public class MethodBuilderGetILGenerator { + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] + [InlineData(AssemblyBuilderAccess.Run, 0)] + [InlineData(AssemblyBuilderAccess.Run, 512)] + [InlineData(AssemblyBuilderAccess.RunAndCollect, 0)] + [InlineData(AssemblyBuilderAccess.RunAndCollect, 512)] + public void GetILGenerator_BodiesSurviveFurtherEmission(AssemblyBuilderAccess access, int padding) + { + ModuleBuilder module = Helpers.DynamicAssembly(access: access).DefineDynamicModule("MethodModule"); + TypeBuilder type = module.DefineType("Methods", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); + MethodBuilder constant = type.DefineMethod("Constant", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); + constant.InitLocals = false; + ILGenerator constantIL = constant.GetILGenerator(); + constantIL.Emit(OpCodes.Ldc_I4, 37); + constantIL.Emit(OpCodes.Ret); + + MethodBuilder method = type.DefineMethod("WithExceptions", MethodAttributes.Public | MethodAttributes.Static, typeof(int), new[] { typeof(int) }); + ILGenerator il = method.GetILGenerator(); + LocalBuilder result = il.DeclareLocal(typeof(int)); + Label nonNegative = il.DefineLabel(); + il.BeginExceptionBlock(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Bge_S, nonNegative); + il.Emit(OpCodes.Newobj, typeof(InvalidOperationException).GetConstructor(Type.EmptyTypes)); + il.Emit(OpCodes.Throw); + il.MarkLabel(nonNegative); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Call, typeof(Math).GetMethod(nameof(Math.Abs), new[] { typeof(int) })); + il.Emit(OpCodes.Ldstr, "abc"); + il.Emit(OpCodes.Callvirt, typeof(string).GetProperty(nameof(string.Length)).GetMethod); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, result); + il.Emit(OpCodes.Ldtoken, typeof(int)); + il.Emit(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))); + il.Emit(OpCodes.Pop); + for (int i = 0; i < padding; i++) + { + il.Emit(OpCodes.Nop); + } + il.BeginCatchBlock(typeof(InvalidOperationException)); + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ldc_I4, -7); + il.Emit(OpCodes.Stloc, result); + il.BeginFinallyBlock(); + il.Emit(OpCodes.Ldloc, result); + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, result); + il.EndExceptionBlock(); + il.Emit(OpCodes.Ldloc, result); + il.Emit(OpCodes.Ret); + + Type createdType = type.CreateType(); + MethodInfo createdMethod = createdType.GetMethod(method.Name); + MethodInfo createdConstant = createdType.GetMethod(constant.Name); + Assert.Equal(37, createdConstant.Invoke(null, null)); + Assert.Equal(6, createdMethod.Invoke(null, new object[] { 2 })); + Assert.Equal(-6, createdMethod.Invoke(null, new object[] { -2 })); + Assert.Equal(method.MetadataToken, createdMethod.MetadataToken); + + MethodBody body = createdMethod.GetMethodBody(); + Assert.True(body.InitLocals); + Assert.Equal(typeof(int), Assert.Single(body.LocalVariables).LocalType); + Assert.Equal(2, body.ExceptionHandlingClauses.Count); + Assert.Contains(body.ExceptionHandlingClauses, clause => clause.Flags == ExceptionHandlingClauseOptions.Clause && clause.CatchType == typeof(InvalidOperationException)); + Assert.Contains(body.ExceptionHandlingClauses, clause => clause.Flags == ExceptionHandlingClauseOptions.Finally); + byte[] bytes = body.GetILAsByteArray(); + Assert.Equal(il.ILOffset, bytes.Length); + Assert.Empty(createdConstant.GetMethodBody().ExceptionHandlingClauses); + Assert.Empty(createdConstant.GetMethodBody().LocalVariables); + Assert.False(createdConstant.GetMethodBody().InitLocals); + + TypeBuilder moreMethods = module.DefineType("MoreMethods", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); + for (int i = 0; i < 64; i++) + { + MethodBuilder extra = moreMethods.DefineMethod($"Extra{i}", MethodAttributes.Public | MethodAttributes.Static, typeof(int), Type.EmptyTypes); + ILGenerator extraIL = extra.GetILGenerator(); + extraIL.Emit(OpCodes.Ldc_I4, i); + extraIL.Emit(OpCodes.Ret); + } + Type extraType = moreMethods.CreateType(); + Assert.Equal(63, extraType.GetMethod("Extra63").Invoke(null, null)); + + Assert.Equal(bytes, createdMethod.GetMethodBody().GetILAsByteArray()); + Assert.Equal(37, createdConstant.Invoke(null, null)); + Assert.Equal(6, createdMethod.Invoke(null, new object[] { 2 })); + Assert.Equal(-6, createdMethod.Invoke(null, new object[] { -2 })); + Assert.Equal(createdMethod, module.ResolveMethod(method.MetadataToken)); + } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] [InlineData(20)] [InlineData(-10)] diff --git a/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineInitializedData.cs b/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineInitializedData.cs index 4e8297919f65c2..2cc49ac38c4272 100644 --- a/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineInitializedData.cs +++ b/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineInitializedData.cs @@ -1,13 +1,94 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Xunit; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Xunit; namespace System.Reflection.Emit.Tests { public class ModuleBuilderDefineInitializedData { + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported), nameof(PlatformDetection.IsCoreCLR))] + [InlineData(AssemblyBuilderAccess.Run, false)] + [InlineData(AssemblyBuilderAccess.Run, true)] + [InlineData(AssemblyBuilderAccess.RunAndCollect, false)] + [InlineData(AssemblyBuilderAccess.RunAndCollect, true)] + public void DefineInitializedData_ContentsAndAddressesSurviveFurtherEmission(AssemblyBuilderAccess access, bool global) + { + ModuleBuilder module = Helpers.DynamicAssembly(access: access).DefineDynamicModule("DataModule"); + TypeBuilder type = module.DefineType("DataAccessors", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); + int[] sizes = { 1, 4, 8, 13, 16, 4097, 8192 }; + byte[][] expected = new byte[sizes.Length][]; + FieldBuilder[] fields = new FieldBuilder[sizes.Length]; + + for (int i = 0; i < sizes.Length; i++) + { + expected[i] = new byte[sizes[i]]; + Array.Fill(expected[i], (byte)(i + 1)); + string name = $"Data{i}"; + fields[i] = global + ? module.DefineInitializedData(name, expected[i], FieldAttributes.Public) + : type.DefineInitializedData(name, expected[i], FieldAttributes.Public); + + MethodBuilder method = type.DefineMethod($"GetAddress{i}", MethodAttributes.Public | MethodAttributes.Static, typeof(IntPtr), Type.EmptyTypes); + ILGenerator il = method.GetILGenerator(); + il.Emit(OpCodes.Ldsflda, fields[i]); + il.Emit(OpCodes.Conv_I); + il.Emit(OpCodes.Ret); + } + + if (global) + { + module.CreateGlobalFunctions(); + } + Type createdType = type.CreateType(); + IntPtr[] addresses = new IntPtr[sizes.Length]; + for (int i = 0; i < addresses.Length; i++) + { + addresses[i] = (IntPtr)createdType.GetMethod($"GetAddress{i}").Invoke(null, null); + } + + TypeBuilder moreData = module.DefineType("MoreData", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); + for (int i = 0; i < 64; i++) + { + moreData.DefineInitializedData($"Extra{i}", new byte[513], FieldAttributes.Public); + } + moreData.CreateType(); + + for (int i = 0; i < fields.Length; i++) + { + FieldInfo field = global ? module.GetField(fields[i].Name) : createdType.GetField(fields[i].Name); + Assert.Equal(fields[i].MetadataToken, field.MetadataToken); + Assert.True((field.Attributes & FieldAttributes.HasFieldRVA) != 0); + Assert.Equal(addresses[i], (IntPtr)createdType.GetMethod($"GetAddress{i}").Invoke(null, null)); + Assert.Equal(0L, addresses[i].ToInt64() % (sizes[i] % 8 == 0 ? 8 : 4)); + Assert.Equal(expected[i], Helpers.GetFieldValueBytes(field, sizes[i])); + + byte[] initialized = new byte[sizes[i]]; + RuntimeHelpers.InitializeArray(initialized, field.FieldHandle); + Assert.Equal(expected[i], initialized); + + if (BitConverter.IsLittleEndian) + { + Assert.Equal(expected[i], RuntimeHelpers.CreateSpan(field.FieldHandle).ToArray()); + if (sizes[i] % 8 == 0) + { + Assert.Equal(expected[i], MemoryMarshal.AsBytes(RuntimeHelpers.CreateSpan(field.FieldHandle)).ToArray()); + } + } + else + { + Assert.Throws(() => RuntimeHelpers.CreateSpan(field.FieldHandle)); + } + + field.SetValue(null, Activator.CreateInstance(field.FieldType)); + Assert.Equal(new byte[sizes[i]], Helpers.GetFieldValueBytes(field, sizes[i])); + RuntimeHelpers.InitializeArray(initialized, field.FieldHandle); + Assert.Equal(new byte[sizes[i]], initialized); + } + } + [Theory] [InlineData(FieldAttributes.Static | FieldAttributes.Public)] [InlineData(FieldAttributes.Static | FieldAttributes.Private)] diff --git a/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineUninitializedData.cs b/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineUninitializedData.cs index de9dd7764bf0aa..0d526880cc65d8 100644 --- a/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineUninitializedData.cs +++ b/src/libraries/System.Reflection.Emit/tests/ModuleBuilder/ModuleBuilderDefineUninitializedData.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.CompilerServices; using Xunit; namespace System.Reflection.Emit.Tests @@ -10,6 +11,56 @@ public class ModuleBuilderDefineUninitializedData { private const int ReservedMaskFieldAttribute = 0x9500; // This constant maps to FieldAttributes.ReservedMask that is not available in the contract. + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported), nameof(PlatformDetection.IsCoreCLR))] + [InlineData(AssemblyBuilderAccess.Run, false)] + [InlineData(AssemblyBuilderAccess.Run, true)] + [InlineData(AssemblyBuilderAccess.RunAndCollect, false)] + [InlineData(AssemblyBuilderAccess.RunAndCollect, true)] + public void DefineUninitializedData_ZeroFilledAndWritable(AssemblyBuilderAccess access, bool global) + { + ModuleBuilder module = Helpers.DynamicAssembly(access: access).DefineDynamicModule("DataModule"); + TypeBuilder type = module.DefineType("DataOwner", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); + int[] sizes = { 1, 4, 8, 13, 16, 4097 }; + byte[][] expected = new byte[sizes.Length][]; + FieldBuilder[] fields = new FieldBuilder[sizes.Length]; + FieldBuilder[] sources = new FieldBuilder[sizes.Length]; + for (int i = 0; i < sizes.Length; i++) + { + expected[i] = new byte[sizes[i]]; + Array.Fill(expected[i], (byte)(i + 1)); + fields[i] = global + ? module.DefineUninitializedData($"Data{i}", sizes[i], FieldAttributes.Public) + : type.DefineUninitializedData($"Data{i}", sizes[i], FieldAttributes.Public); + sources[i] = global + ? module.DefineInitializedData($"Source{i}", expected[i], FieldAttributes.Public) + : type.DefineInitializedData($"Source{i}", expected[i], FieldAttributes.Public); + } + + if (global) + { + module.CreateGlobalFunctions(); + } + Type createdType = type.CreateType(); + + for (int i = 0; i < fields.Length; i++) + { + FieldInfo field = global ? module.GetField(fields[i].Name) : createdType.GetField(fields[i].Name); + FieldInfo source = global ? module.GetField(sources[i].Name) : createdType.GetField(sources[i].Name); + Assert.Equal(new byte[sizes[i]], Helpers.GetFieldValueBytes(field, sizes[i])); + + byte[] initialized = new byte[sizes[i]]; + Array.Fill(initialized, byte.MaxValue); + RuntimeHelpers.InitializeArray(initialized, field.FieldHandle); + Assert.Equal(new byte[sizes[i]], initialized); + + field.SetValue(null, source.GetValue(null)); + Assert.Equal(expected[i], Helpers.GetFieldValueBytes(field, sizes[i])); + RuntimeHelpers.InitializeArray(initialized, field.FieldHandle); + Assert.Equal(expected[i], initialized); + Assert.Equal(expected[i], Helpers.GetFieldValueBytes(source, sizes[i])); + } + } + public static IEnumerable Attributes_TestData() { yield return new object[] { FieldAttributes.Assembly }; diff --git a/src/libraries/System.Reflection.Emit/tests/Utilities.cs b/src/libraries/System.Reflection.Emit/tests/Utilities.cs index a4f1ba8c39303b..4a33d9a74e9ba0 100644 --- a/src/libraries/System.Reflection.Emit/tests/Utilities.cs +++ b/src/libraries/System.Reflection.Emit/tests/Utilities.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Linq; +using System.Runtime.InteropServices; using Xunit; namespace System.Reflection.Emit.Tests @@ -68,6 +69,23 @@ public static TypeBuilder DynamicType(TypeAttributes attributes, string assembly } } + public static unsafe byte[] GetFieldValueBytes(FieldInfo field, int size) + { + object value = field.GetValue(null); + Assert.NotNull(value); + GCHandle handle = GCHandle.Alloc(value, GCHandleType.Pinned); + try + { + byte[] bytes = new byte[size]; + new ReadOnlySpan(handle.AddrOfPinnedObject().ToPointer(), size).CopyTo(bytes); + return bytes; + } + finally + { + handle.Free(); + } + } + public static EnumBuilder DynamicEnum(TypeAttributes visibility, Type underlyingType, string enumName = "TestEnum", string assemblyName = "TestAssembly", string moduleName = "TestModule") { return DynamicModule(assemblyName, moduleName).DefineEnum(enumName, visibility, underlyingType); diff --git a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs index 85fe8eb83eb4f9..b447acfc7af22b 100644 --- a/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/LoaderTests.cs @@ -29,6 +29,7 @@ public unsafe class LoaderTests [DataType.EEConfig] = TargetTestHelpers.CreateTypeInfo(loader.EEConfigLayout), [DataType.CGrowableSymbolStream] = TargetTestHelpers.CreateTypeInfo(loader.CGrowableSymbolStreamLayout), [DataType.ModuleLookupMap] = TargetTestHelpers.CreateTypeInfo(loader.ModuleLookupMapLayout), + [DataType.DynamicILBlobTable] = TargetTestHelpers.CreateTypeInfo(loader.DynamicILBlobTableLayout), }; private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action configure) @@ -60,6 +61,86 @@ internal static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderCon return (target.Contracts.Loader, target); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetDynamicILAndILHeader_TokenBackedStorageWithoutPEImage(MockTarget.Architecture arch) + { + const uint MethodToken = 0x06000001; + const uint FieldToken = 0x04000001; + const uint MissingMethodToken = 0x06000006; + const uint MissingFieldToken = 0x04000006; + TargetPointer methodIL = new(0x0003_0000); + TargetPointer fieldData = new(0x0004_0000); + TargetPointer moduleAddress = default; + Mock metadata = new(MockBehavior.Strict); + + (ILoader contract, TestPlaceholderTarget target) = CreateLoaderContractWithTarget(arch, (loader, targetBuilder) => + { + MockLoaderModule module = loader.AddModule(flags: (uint)ModuleFlags.ReflectionEmit); + moduleAddress = module.Address; + + // Full tokens hash to buckets 0 and 2; the missing tokens probe past those occupied buckets. + loader.SetDynamicILBlobTable(module, + (FieldToken, fieldData.Value), default, (MethodToken, methodIL.Value), default, default); + loader.Builder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Name = "Dynamic method IL", + Address = methodIL.Value, + Data = [0x06, 0x2a], // Tiny method header followed by ret. + }); + loader.Builder.AddHeapFragment(new MockMemorySpace.HeapFragment + { + Name = "Dynamic RVA field data", + Address = fieldData.Value, + Data = [0x12, 0x34, 0x56, 0x78], + }); + targetBuilder + .AddContract(version: "c1") + .AddMockContract(metadata); + }); + + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddress); + Assert.Equal(TargetPointer.Null, contract.GetPEAssembly(handle)); + Assert.Equal(methodIL, contract.GetDynamicIL(handle, MethodToken)); + Assert.Equal(fieldData, contract.GetDynamicIL(handle, FieldToken)); + Assert.Equal(TargetPointer.Null, contract.GetDynamicIL(handle, MissingMethodToken)); + Assert.Equal(TargetPointer.Null, contract.GetDynamicIL(handle, MissingFieldToken)); + Assert.Equal(methodIL, contract.GetILHeader(handle, MethodToken)); + + IXCLRDataMethodDefinition methodDefinition = new ClrDataMethodDefinition( + target, moduleAddress, MethodToken, legacyImpl: null, new()); + ClrDataAddress entryAddress; + Assert.Equal(System.HResults.S_OK, methodDefinition.GetRepresentativeEntryAddress(&entryAddress)); + Assert.Equal(new TargetPointer(methodIL + 1).ToClrDataAddress(target), entryAddress); + metadata.VerifyNoOtherCalls(); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void GetDynamicIL_NullAndEmptyTablesReturnNull(MockTarget.Architecture arch) + { + const uint MethodToken = 0x06000001; + const uint FieldToken = 0x04000001; + TargetPointer uninitializedModuleAddress = default; + TargetPointer emptyModuleAddress = default; + + (ILoader contract, _) = CreateLoaderContractWithTarget(arch, (loader, targetBuilder) => + { + uninitializedModuleAddress = loader.AddModule(flags: (uint)ModuleFlags.ReflectionEmit).Address; + MockLoaderModule emptyModule = loader.AddModule(flags: (uint)ModuleFlags.ReflectionEmit); + loader.SetDynamicILBlobTable(emptyModule); + emptyModuleAddress = emptyModule.Address; + targetBuilder.AddContract(version: "c1"); + }); + + foreach (TargetPointer moduleAddress in new[] { uninitializedModuleAddress, emptyModuleAddress }) + { + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddress); + Assert.Equal(TargetPointer.Null, contract.GetDynamicIL(handle, MethodToken)); + Assert.Equal(TargetPointer.Null, contract.GetDynamicIL(handle, FieldToken)); + } + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetPath(MockTarget.Architecture arch) diff --git a/src/native/managed/cdac/tests/UnitTests/MethodTableTests.cs b/src/native/managed/cdac/tests/UnitTests/MethodTableTests.cs index 973c5e11794e6b..d4ea03f4d43005 100644 --- a/src/native/managed/cdac/tests/UnitTests/MethodTableTests.cs +++ b/src/native/managed/cdac/tests/UnitTests/MethodTableTests.cs @@ -12,6 +12,7 @@ using Microsoft.Diagnostics.DataContractReader.Legacy; using Microsoft.Diagnostics.DataContractReader.RuntimeTypeSystemHelpers; using Microsoft.Diagnostics.DataContractReader.TestInfrastructure; +using Moq; using Xunit; using static Microsoft.Diagnostics.DataContractReader.TestInfrastructure.TestHelpers; @@ -49,6 +50,7 @@ internal static (string Name, ulong Value)[] CreateContractGlobals(MockRTS rtsBu (nameof(Constants.Globals.MethodDescAlignment), rtsBuilder.MethodDescAlignment), (nameof(Constants.Globals.ArrayBaseSize), rtsBuilder.ArrayBaseSize), (nameof(Constants.Globals.FieldOffsetBigRVA), MockRTS.FieldOffsetBigRVAValue), + (nameof(Constants.Globals.FieldOffsetDynamicRVA), MockRTS.FieldOffsetDynamicRVAValue), ]; public static IEnumerable StdArchBool() @@ -1429,6 +1431,57 @@ public void GetFieldDescOffset_BigRVA_ResolvesFromMetadata(MockTarget.Architectu Assert.Equal((uint)rva, contract.GetFieldDescOffset(fieldDescPtr, fieldDef)); } + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public unsafe void GetFieldDescStaticAddress_DynamicRVA_ResolvesFieldTokenWithoutPEImage(MockTarget.Architecture arch) + { + const uint IsStatic = 0x01000000; + const uint IsRVA = 0x04000000; + byte[] metadata = BuildMetadataWithRvaField(rva: 0, out FieldDefinitionHandle fieldHandle); + using MetadataReaderProvider provider = MetadataReaderProvider.FromMetadataImage(ImmutableArray.Create(metadata)); + MetadataReader reader = provider.GetMetadataReader(); + uint fieldToken = (uint)MetadataTokens.GetToken(fieldHandle); + TargetPointer moduleAddress = new(0x0002_0000); + TargetPointer fieldData = new(0x0003_0000); + Contracts.ModuleHandle moduleHandle = new(moduleAddress); + + Mock loader = new(MockBehavior.Strict); + loader.Setup(l => l.GetModuleHandleFromModulePtr(moduleAddress)).Returns(moduleHandle); + loader.Setup(l => l.GetDynamicIL(moduleHandle, fieldToken)).Returns(fieldData); + Mock ecmaMetadata = new(MockBehavior.Strict); + ecmaMetadata.Setup(m => m.GetMetadata(moduleHandle)).Returns(reader); + + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockRTS rtsBuilder = new(targetBuilder.MemoryBuilder); + rtsBuilder.SystemObjectMethodTable.Module = moduleAddress.Value; + MockFieldDesc fieldDesc = rtsBuilder.AddFieldDesc( + rtsBuilder.SystemObjectMethodTable.Address, CorElementType.I4, MockRTS.FieldOffsetDynamicRVAValue, fieldToken); + fieldDesc.DWord1 |= IsStatic | IsRVA; + + TestPlaceholderTarget target = targetBuilder + .AddTypes(CreateContractTypes(rtsBuilder)) + .AddGlobals(CreateContractGlobals(rtsBuilder)) + .AddContract(version: "c1") + .AddMockContract(loader) + .AddMockContract(ecmaMetadata) + .Build(); + IRuntimeTypeSystem contract = target.Contracts.RuntimeTypeSystem; + + Assert.True(contract.IsFieldDescStatic(fieldDesc.Address)); + Assert.True(contract.IsFieldDescRVA(fieldDesc.Address)); + Assert.Equal(fieldData, contract.GetFieldDescStaticAddress(fieldDesc.Address)); + + DacDbiImpl dacDbi = new(target, legacyObj: null, new()); + ulong staticAddress; + Assert.Equal(System.HResults.S_OK, dacDbi.GetCollectibleTypeStaticAddress(fieldDesc.Address, &staticAddress)); + Assert.Equal(fieldData.Value, staticAddress); + loader.Verify(l => l.GetModuleHandleFromModulePtr(moduleAddress), Times.Exactly(2)); + loader.Verify(l => l.GetDynamicIL(moduleHandle, fieldToken), Times.Exactly(2)); + loader.VerifyNoOtherCalls(); + ecmaMetadata.Verify(m => m.GetMetadata(moduleHandle), Times.Exactly(2)); + ecmaMetadata.VerifyNoOtherCalls(); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void TryGetFieldDescNext_ReturnsNextFieldThenFalseAtEndOfList(MockTarget.Architecture arch) diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs index 8a8ab1f8df0bb5..433f8189311e6f 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.Loader.cs @@ -166,6 +166,12 @@ public ulong GrowableSymbolStream set => WritePointerField(GrowableSymbolStreamFieldName, value); } + public ulong DynamicILBlobTable + { + get => ReadPointerField(DynamicILBlobTableFieldName); + set => WritePointerField(DynamicILBlobTableFieldName, value); + } + public int MemberRefToDescMapOffset => Layout.GetField(MemberRefToDescMapFieldName).Offset; } @@ -288,8 +294,10 @@ internal sealed class MockLoaderBuilder internal Layout LoaderHeapBlockLayout { get; } internal Layout CGrowableSymbolStreamLayout { get; } internal Layout ModuleLookupMapLayout { get; } + internal Layout DynamicILBlobTableLayout { get; } private readonly MockMemorySpace.BumpAllocator _allocator; + private readonly Layout _dynamicILBlobTableHeaderLayout; public MockLoaderBuilder(MockMemorySpace.Builder builder) : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) @@ -310,6 +318,21 @@ public MockLoaderBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong En LoaderHeapBlockLayout = MockLoaderHeapBlock.CreateLayout(builder.TargetTestHelpers.Arch); CGrowableSymbolStreamLayout = MockCGrowableSymbolStream.CreateLayout(builder.TargetTestHelpers.Arch); ModuleLookupMapLayout = MockModuleLookupMap.CreateLayout(builder.TargetTestHelpers.Arch); + + _dynamicILBlobTableHeaderLayout = new SequentialLayoutBuilder("DynamicILBlobTable header", builder.TargetTestHelpers.Arch) + .AddPointerField("Table") + .AddUInt32Field("TableSize") + .Build(); + Layout entryLayout = new SequentialLayoutBuilder("DynamicILBlobTable entry", builder.TargetTestHelpers.Arch) + .AddUInt32Field("EntryMethodToken") + .AddPointerField("EntryIL") + .Build(); + // The descriptor combines container offsets with entry offsets and the entry size. + DynamicILBlobTableLayout = new Layout( + "DynamicILBlobTable", + builder.TargetTestHelpers.Arch, + entryLayout.Size, + [.. _dynamicILBlobTableHeaderLayout.Fields, .. entryLayout.Fields]); } internal MockLoaderHeap AddLoaderHeap(ulong firstBlockAddress = 0) @@ -392,6 +415,30 @@ internal void SetMemberRefToDescMap(MockLoaderModule module, params ulong[] entr map.SupportedFlagsMask = 3; } + // Entries are supplied in SHash bucket order, including empty slots. + internal void SetDynamicILBlobTable(MockLoaderModule module, params (uint Token, ulong IL)[] entries) + { + TargetTestHelpers helpers = Builder.TargetTestHelpers; + MockMemorySpace.HeapFragment header = _allocator.Allocate((ulong)_dynamicILBlobTableHeaderLayout.Size, "DynamicILBlobTable"); + helpers.Write(header.Data.AsSpan(_dynamicILBlobTableHeaderLayout.GetField("TableSize").Offset), (uint)entries.Length); + + if (entries.Length > 0) + { + int entrySize = DynamicILBlobTableLayout.Size; + MockMemorySpace.HeapFragment table = _allocator.Allocate(checked((ulong)entries.Length * (ulong)entrySize), "DynamicILBlobTable entries"); + helpers.WritePointer(header.Data.AsSpan(_dynamicILBlobTableHeaderLayout.GetField("Table").Offset), table.Address); + + for (int i = 0; i < entries.Length; i++) + { + Span entry = table.Data.AsSpan(i * entrySize, entrySize); + helpers.Write(entry.Slice(DynamicILBlobTableLayout.GetField("EntryMethodToken").Offset), entries[i].Token); + helpers.WritePointer(entry.Slice(DynamicILBlobTableLayout.GetField("EntryIL").Offset), entries[i].IL); + } + } + + module.DynamicILBlobTable = header.Address; + } + /// /// Allocates a CGrowableSymbolStream with an associated symbol byte buffer and attaches it /// to via the GrowableSymbolStream field. diff --git a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs index f47e8a3a9a4630..81b04ccfe7c91f 100644 --- a/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs +++ b/src/native/managed/cdac/tests/UnitTests/MockDescriptors/MockDescriptors.RuntimeTypeSystem.cs @@ -616,9 +616,10 @@ internal MockParamTypeDesc AddParamTypeDesc() internal MockTypeVarTypeDesc AddTypeVarTypeDesc() => Add(TypeVarTypeDescLayout, "TypeVarTypeDesc"); - // Value of the native FieldDesc::m_dwOffset sentinel FIELD_OFFSET_BIG_RVA (FIELD_OFFSET_MAX - 5, - // where FIELD_OFFSET_MAX == (1 << 27) - 1). See src/coreclr/vm/field.h. + // Native FieldDesc::m_dwOffset sentinels relative to FIELD_OFFSET_MAX == (1 << 27) - 1. + // See src/coreclr/vm/field.h. internal const uint FieldOffsetBigRVAValue = ((1u << 27) - 1) - 5; + internal const uint FieldOffsetDynamicRVAValue = ((1u << 27) - 1) - 6; // Allocates a FieldDesc whose packed DWord2 stores the given 5-bit field type and 27-bit offset. internal MockFieldDesc AddFieldDesc(ulong mtOfEnclosingClass, CorElementType type, uint offset, uint memberDef = 0) From 88e928d73126eeacaef9ec24df0a9dd7580acab4 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 30 Sep 2026 14:03:54 -0700 Subject: [PATCH 2/4] Tighten non-TLS assertion for RVA fields Remove the Reflection.Emit exception from the image-backed RVA assertion now that emitted fields use the preceding dynamic-token branch. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/field.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/vm/field.cpp b/src/coreclr/vm/field.cpp index 2d7cfed484f192..b8a72e18bb5c9b 100644 --- a/src/coreclr/vm/field.cpp +++ b/src/coreclr/vm/field.cpp @@ -283,7 +283,7 @@ PTR_VOID FieldDesc::GetStaticAddressHandle(PTR_VOID base) } else { - _ASSERTE(pModule->IsReflectionEmit() || !pModule->IsRvaFieldTls(offset)); + _ASSERTE(!pModule->IsRvaFieldTls(offset)); retVal = pModule->GetRvaField(offset); } } From 7d73afcd97506a0a2097d14fb42304545cf3af2f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Thu, 1 Oct 2026 14:43:33 -0700 Subject: [PATCH 3/4] Remove obsolete virtual qualifier from Module::GetRvaField Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/ceeload.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 5e2d70c8040c41..2f680bed66da50 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -1471,10 +1471,10 @@ class Module : public ModuleBase PTR_READYTORUN_IMPORT_SECTION GetImportSectionFromIndex(COUNT_T index); PTR_READYTORUN_IMPORT_SECTION GetImportSectionForRVA(RVA rva); - // These are overridden by reflection modules + // This is overridden by reflection modules virtual TADDR GetIL(RVA il); - virtual PTR_VOID GetRvaField(RVA field); + PTR_VOID GetRvaField(RVA field); CHECK CheckRvaField(RVA field); CHECK CheckRvaField(RVA field, COUNT_T size); From b8dc4ee65fea87ae7f18d2400f328c2bedf1971a Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 2 Oct 2026 11:24:03 -0700 Subject: [PATCH 4/4] Refresh WebAssembly interpreter thunk for emitted method IL Regenerate browser and WASI call-helper tables after shortening the Reflection.Emit SetMethodIL QCall signature. The missing interpreter thunk caused null-function traps in browser CoreCLR library tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../vm/wasm/browser/callhelpers-interp-to-managed.cpp | 8 ++++---- .../vm/wasm/wasi/callhelpers-interp-to-managed.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/wasm/browser/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/browser/callhelpers-interp-to-managed.cpp index c82a08e8fe4c57..f674aec8463e8d 100644 --- a/src/coreclr/vm/wasm/browser/callhelpers-interp-to-managed.cpp +++ b/src/coreclr/vm/wasm/browser/callhelpers-interp-to-managed.cpp @@ -510,10 +510,10 @@ namespace (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7)); } - static void CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10), ARG_I32(11), ARG_I32(12)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10)); } NOINLINE static void CallFunc_This_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) @@ -753,7 +753,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "MvS8iiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_RetVoid }, { "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid }, { "MvS8iiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_RetVoid }, - { "MvS8iiiiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, + { "MvS8iiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, { "MvTp", (void*)&CallFunc_This_RetVoid_PE }, { "Mvdddddddddiii", (void*)&CallFunc_F64_F64_F64_F64_F64_F64_F64_F64_F64_I32_I32_I32_RetVoid }, { "Mvdii", (void*)&CallFunc_F64_I32_I32_RetVoid }, diff --git a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp index bab5632c834b02..527397323aecf7 100644 --- a/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp +++ b/src/coreclr/vm/wasm/wasi/callhelpers-interp-to-managed.cpp @@ -510,10 +510,10 @@ namespace (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7)); } - static void CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) + static void CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid(PCODE pcode, int8_t* pArgs, int8_t* pRet) { - void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; - (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10), ARG_I32(11), ARG_I32(12)); + void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t))pcode; + (*fptr)(ARG_IND(0), ARG_I32(1), ARG_I32(2), ARG_I32(3), ARG_I32(4), ARG_I32(5), ARG_I32(6), ARG_I32(7), ARG_I32(8), ARG_I32(9), ARG_I32(10)); } NOINLINE static void CallFunc_This_RetVoid_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet) @@ -729,7 +729,7 @@ const StringToPortableSigThunk g_portableCallHelperThunks[] = { { "MvS8iiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_RetVoid }, { "MvS8iiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_RetVoid }, { "MvS8iiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_RetVoid }, - { "MvS8iiiiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, + { "MvS8iiiiiiiiii", (void*)&CallFunc_S8_I32_I32_I32_I32_I32_I32_I32_I32_I32_I32_RetVoid }, { "MvTp", (void*)&CallFunc_This_RetVoid_PE }, { "Mvdiip", (void*)&CallFunc_F64_I32_I32_RetVoid_PE }, { "Mvfiip", (void*)&CallFunc_F32_I32_I32_RetVoid_PE },