diff --git a/src/coreclr/interpreter/CMakeLists.txt b/src/coreclr/interpreter/CMakeLists.txt index f6c4884a744f2a..656a6dc2252714 100644 --- a/src/coreclr/interpreter/CMakeLists.txt +++ b/src/coreclr/interpreter/CMakeLists.txt @@ -5,6 +5,7 @@ set(INTERPRETER_SOURCES compileropt.cpp intops.cpp interpconfig.cpp + interpmethoddata.cpp eeinterp.cpp stackmap.cpp naming.cpp diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index cbd7d7d6393712..e7986826fa2db7 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -193,7 +193,10 @@ void* MemPoolAllocator::Alloc(size_t sz) const { sz = sizeof(void*); // Arena allocator does not support zero-length allocations, so allocate something of minimum size instead. } - return m_compiler->getAllocator(m_memKind).allocate(sz); + + // allocate is non-const; cast away constness of m_allocator to avoid an extra copy per allocation. + auto* allocator = const_cast(&m_allocator); + return allocator->allocate(sz); } void MemPoolAllocator::Free(void* ptr) const { /* no-op */ } @@ -277,12 +280,11 @@ void InterpCompiler::CopyToInterpGenericLookup(InterpGenericLookup* dst, const C assert(!src->indirectSecondOffset); } -// Interpreter-FIXME Use specific allocators for their intended purpose -// Allocator for data that is kept alive throughout application execution, -// being freed only if the associated method gets freed. +// Temporary allocator for method data during compilation. +// Data allocated here is copied to the final unified allocation during FinalizeMethodData. void* InterpCompiler::AllocMethodData(size_t numBytes) { - return malloc(numBytes); + return getAllocator(IMK_MethodData).allocate(numBytes); } static int GetDataLen(int opcode) @@ -1850,23 +1852,46 @@ void InterpCompiler::BuildEHInfo() } } -InterpMethod* InterpCompiler::CreateInterpMethod() +void InterpCompiler::PrepareInterpMethod() { - int numDataItems = m_dataItems.GetSize(); - void **pDataItems = (void**)AllocMethodData(numDataItems * sizeof(void*)); + // Store method data for later finalization + m_initLocals = (m_methodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0; + m_unmanagedCallersOnly = m_corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE); + m_publishSecretStubParam = m_corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_PUBLISH_SECRET_PARAM); - for (int i = 0; i < numDataItems; i++) - pDataItems[i] = m_dataItems.Get(i); + // Reserve space in the builder for each section + // Bytecode section + m_methodDataBuilder.SetBytecodeSize(m_methodCodeSize * sizeof(int32_t)); - bool initLocals = (m_methodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0; + // InterpMethod section + m_methodDataBuilder.AllocateInterpMethod(); - bool unmanagedCallersOnly = m_corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE); - bool publishSecretStubParam = m_corJitFlags.IsSet(CORJIT_FLAGS::CORJIT_FLAG_PUBLISH_SECRET_PARAM); + // DataItems section + int numDataItems = m_dataItems.GetSize(); + if (numDataItems > 0) + { + m_methodDataBuilder.AllocateDataItems(numDataItems); + } - void* pMethodData = AllocMethodData(sizeof(InterpMethod)); - InterpMethod *pMethod = new (pMethodData) InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, initLocals, unmanagedCallersOnly, publishSecretStubParam); + // AsyncSuspendData section - reserve space for all async suspend data + for (int32_t i = 0; i < m_asyncSuspendDataItems.GetSize(); i++) + { + m_methodDataBuilder.AllocateAsyncSuspendData(); + } - return pMethod; + // IntervalMaps section - reserve space tracked via m_varIntervalMaps + for (int32_t i = 0; i < m_varIntervalMaps.GetSize(); i++) + { + // Count entries in this interval map (terminated by entry with countBytes == 0) + InterpIntervalMapEntry* pMap = *m_varIntervalMaps.Get(i); + int32_t count = 0; + while (pMap[count].countBytes != 0) + { + count++; + } + count++; // Include the terminator entry + m_methodDataBuilder.AllocateIntervalMap(count); + } } int32_t* InterpCompiler::GetCode(int32_t *pCodeSize) @@ -1875,6 +1900,146 @@ int32_t* InterpCompiler::GetCode(int32_t *pCodeSize) return m_pMethodCode; } +uint32_t InterpCompiler::GetTotalAllocationSize() +{ + return m_methodDataBuilder.GetTotalSize(); +} + +InterpMethod* InterpCompiler::FinalizeMethodData(void* baseAddressRW, void* baseAddressRX) +{ + uint8_t* rwBase = (uint8_t*)baseAddressRW; + uint8_t* rxBase = (uint8_t*)baseAddressRX; + + // Get section offsets + uint32_t headerOffset = m_methodDataBuilder.GetSectionOffset(InterpMethodDataSection::Header); + uint32_t bytecodeOffset = m_methodDataBuilder.GetSectionOffset(InterpMethodDataSection::Bytecode); + uint32_t interpMethodOffset = m_methodDataBuilder.GetSectionOffset(InterpMethodDataSection::InterpMethod); + uint32_t dataItemsOffset = m_methodDataBuilder.GetSectionOffset(InterpMethodDataSection::DataItems); + uint32_t asyncSuspendDataOffset = m_methodDataBuilder.GetSectionOffset(InterpMethodDataSection::AsyncSuspendData); + uint32_t intervalMapsOffset = m_methodDataBuilder.GetSectionOffset(InterpMethodDataSection::IntervalMaps); + + const uint32_t bytecodeSectionSize = m_methodDataBuilder.GetSectionSize(InterpMethodDataSection::Bytecode); + const uint32_t interpMethodSectionSize = m_methodDataBuilder.GetSectionSize(InterpMethodDataSection::InterpMethod); + const uint32_t dataItemsSectionSize = m_methodDataBuilder.GetSectionSize(InterpMethodDataSection::DataItems); + const uint32_t asyncSuspendDataSectionSize = m_methodDataBuilder.GetSectionSize(InterpMethodDataSection::AsyncSuspendData); + const uint32_t intervalMapsSectionSize = m_methodDataBuilder.GetSectionSize(InterpMethodDataSection::IntervalMaps); + + assert((uint64_t)m_methodCodeSize * sizeof(int32_t) <= bytecodeSectionSize); + assert(sizeof(InterpMethod) <= interpMethodSectionSize); + + // Copy bytecode + memcpy(rwBase + bytecodeOffset, m_pMethodCode, m_methodCodeSize * sizeof(int32_t)); + + // Calculate data items pointer in final allocation + int numDataItems = m_dataItems.GetSize(); + void** pDataItems = (numDataItems > 0) ? (void**)(rxBase + dataItemsOffset) : nullptr; + + assert((uint64_t)numDataItems * sizeof(void*) <= dataItemsSectionSize); + + // Copy data items + if (numDataItems > 0) + { + void** pDataItemsRW = (void**)(rwBase + dataItemsOffset); + for (int i = 0; i < numDataItems; i++) + { + pDataItemsRW[i] = m_dataItems.Get(i); + } + } + + // Construct InterpMethod in the final allocation + InterpMethod* pMethodRW = (InterpMethod*)(rwBase + interpMethodOffset); + InterpMethod* pMethodRX = (InterpMethod*)(rxBase + interpMethodOffset); + new (pMethodRW) InterpMethod(m_methodHnd, m_ILLocalsOffset, m_totalVarsStackSize, pDataItems, + m_initLocals, m_unmanagedCallersOnly, m_publishSecretStubParam); + + // Copy async suspend data and fix up pointers + uint32_t currentAsyncOffset = asyncSuspendDataOffset; + uint32_t currentIntervalMapOffset = intervalMapsOffset; + const uint32_t asyncSuspendDataSectionEnd = asyncSuspendDataOffset + asyncSuspendDataSectionSize; + const uint32_t intervalMapsSectionEnd = intervalMapsOffset + intervalMapsSectionSize; + + InterpByteCodeStart* pByteCodeStart = (InterpByteCodeStart*)rxBase; + + for (int32_t i = 0; i < m_asyncSuspendDataItems.GetSize(); i++) + { + assert(currentAsyncOffset + sizeof(InterpAsyncSuspendData) <= asyncSuspendDataSectionEnd); + + InterpAsyncSuspendData* srcData = m_asyncSuspendDataItems.Get(i); + InterpAsyncSuspendData* dstDataRW = (InterpAsyncSuspendData*)(rwBase + currentAsyncOffset); + + // Copy the struct + memcpy(dstDataRW, srcData, sizeof(InterpAsyncSuspendData)); + + // Fix up the methodStartIP to point to the final bytecode start + dstDataRW->methodStartIP = pByteCodeStart; + + // Fix up interval map pointers if they exist + // Note: The interval maps were allocated via AllocMethodData in the old model, + // we need to copy them to the new allocation and fix up the pointers + if (srcData->liveLocalsIntervals != nullptr) + { + // Count entries + int32_t count = 0; + while (srcData->liveLocalsIntervals[count].countBytes != 0) count++; + count++; // Include terminator + + uint32_t mapSize = (uint32_t)count * sizeof(InterpIntervalMapEntry); + assert(currentIntervalMapOffset + mapSize <= intervalMapsSectionEnd); + + InterpIntervalMapEntry* dstMapRW = (InterpIntervalMapEntry*)(rwBase + currentIntervalMapOffset); + InterpIntervalMapEntry* dstMapRX = (InterpIntervalMapEntry*)(rxBase + currentIntervalMapOffset); + memcpy(dstMapRW, srcData->liveLocalsIntervals, mapSize); + dstDataRW->liveLocalsIntervals = dstMapRX; + currentIntervalMapOffset += mapSize; + } + + if (srcData->zeroedLocalsIntervals != nullptr) + { + // Count entries + int32_t count = 0; + while (srcData->zeroedLocalsIntervals[count].countBytes != 0) count++; + count++; // Include terminator + + uint32_t mapSize = (uint32_t)count * sizeof(InterpIntervalMapEntry); + assert(currentIntervalMapOffset + mapSize <= intervalMapsSectionEnd); + + InterpIntervalMapEntry* dstMapRW = (InterpIntervalMapEntry*)(rwBase + currentIntervalMapOffset); + InterpIntervalMapEntry* dstMapRX = (InterpIntervalMapEntry*)(rxBase + currentIntervalMapOffset); + memcpy(dstMapRW, srcData->zeroedLocalsIntervals, mapSize); + dstDataRW->zeroedLocalsIntervals = dstMapRX; + currentIntervalMapOffset += mapSize; + } + + currentAsyncOffset += sizeof(InterpAsyncSuspendData); + } + + assert(currentAsyncOffset <= asyncSuspendDataSectionEnd); + assert(currentIntervalMapOffset <= intervalMapsSectionEnd); + + // Fix up data item pointers that reference async suspend data + // These pointers were recorded during compilation and now need to point to the final locations + if (numDataItems > 0) + { + void** pDataItemsRW = (void**)(rwBase + dataItemsOffset); + for (int32_t i = 0; i < m_dataItemAsyncSuspendRefs.GetSize(); i++) + { + DataItemAsyncSuspendRef ref = m_dataItemAsyncSuspendRefs.Get(i); + // Calculate the final address of this async suspend data in the RX allocation + InterpAsyncSuspendData* finalAddr = (InterpAsyncSuspendData*)(rxBase + asyncSuspendDataOffset + + ref.asyncSuspendDataIndex * sizeof(InterpAsyncSuspendData)); + pDataItemsRW[ref.dataItemIndex] = finalAddr; + } + } + + // Write the InterpMethod pointer to the header (InterpByteCodeStart) + *(InterpMethod**)(rwBase + headerOffset) = pMethodRX; + + // Record the finalized base addresses in the method data builder + m_methodDataBuilder.Finalize(baseAddressRW, baseAddressRX); + + return pMethodRX; +} + InterpreterStackMap* InterpCompiler::GetInterpreterStackMap(CORINFO_CLASS_HANDLE classHandle) { InterpreterStackMap* result = nullptr; @@ -1895,6 +2060,7 @@ InterpCompiler::InterpCompiler(COMP_HANDLE compHnd, CORINFO_METHOD_INFO* methodInfo, InterpreterRetryData* pRetryData, InterpArenaAllocator *arenaAllocator) : m_arenaAllocator(arenaAllocator) + , m_methodDataBuilder() , m_stackmapsByClass(getAllocator(IMK_StackMapHash)) , m_pRetryData(pRetryData) , m_pInitLocalsIns(nullptr) @@ -1904,6 +2070,10 @@ InterpCompiler::InterpCompiler(COMP_HANDLE compHnd, , m_leavesTable(GetMemPoolAllocator(IMK_EHClause)) , m_dataItems(GetMemPoolAllocator(IMK_DataItem)) , m_asyncSuspendDataItems(GetMemPoolAllocator(IMK_DataItem)) + , m_dataItemAsyncSuspendRefs(GetMemPoolAllocator(IMK_DataItem)) + , m_initLocals(false) + , m_unmanagedCallersOnly(false) + , m_publishSecretStubParam(false) , m_globalVarsWithRefsStackTop(0) , m_varIntervalMaps(GetMemPoolAllocator(IMK_IntervalMap)) #ifdef DEBUG @@ -1943,7 +2113,7 @@ InterpCompiler::~InterpCompiler() m_compHnd->freeArray(m_pILToNativeMap); } -InterpMethod* InterpCompiler::CompileMethod() +bool InterpCompiler::CompileMethod() { #ifdef DEBUG if (IsInterpDumpActive() || InterpConfig.InterpList()) @@ -1981,7 +2151,7 @@ InterpMethod* InterpCompiler::CompileMethod() if (m_pRetryData->NeedsRetry()) { INTERP_DUMP("Retrying compilation due to %s\n", m_pRetryData->GetReasonString()); - return nullptr; + return false; } #ifdef DEBUG @@ -2008,7 +2178,8 @@ InterpMethod* InterpCompiler::CompileMethod() } #endif - return CreateInterpMethod(); + PrepareInterpMethod(); + return true; } void InterpCompiler::PatchInitLocals(CORINFO_METHOD_INFO* methodInfo) @@ -5890,6 +6061,13 @@ void InterpCompiler::EmitSuspend(const CORINFO_CALL_INFO &callInfo, Continuation AddIns(handleContinuationOpcode); int32_t suspendDataIndex = GetDataItemIndex(suspendData); + + // Track this data item -> async suspend data reference for fixup during finalization + DataItemAsyncSuspendRef ref; + ref.dataItemIndex = suspendDataIndex; + ref.asyncSuspendDataIndex = m_asyncSuspendDataItems.GetSize() - 1; // suspendData was just added + m_dataItemAsyncSuspendRefs.Add(ref); + m_pLastNewIns->data[0] = suspendDataIndex; m_pLastNewIns->data[1] = GetDataForHelperFtn(helperFuncForAllocatingContinuation); PushInterpType(InterpTypeO, NULL); @@ -10935,14 +11113,6 @@ void InterpCompiler::UnlinkUnreachableBBlocks() } } -void InterpCompiler::UpdateWithFinalMethodByteCodeAddress(InterpByteCodeStart *pByteCodeStart) -{ - for (int32_t i = 0; i < m_asyncSuspendDataItems.GetSize(); i++) - { - m_asyncSuspendDataItems.Get(i)->methodStartIP = pByteCodeStart; - } -} - void InterpreterRetryData::SetOverrideILMergePointStack(int32_t ilOffset, uint32_t stackHeight, StackInfo *pStackInfo) { assert(stackHeight > 0); diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index ff41961e985256..6f58d0c065cf40 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -12,6 +12,7 @@ #include "simdhash.h" #include "intrinsics.h" #include "interpalloc.h" +#include "interpmethoddata.h" struct InterpException { @@ -27,21 +28,6 @@ struct InterpException class InterpreterStackMap; class InterpCompiler; -// MemPoolAllocator provides an allocator interface for use with TArray and other -// data structures. It wraps the InterpCompiler's arena allocator with a specific -// memory kind for statistics tracking. -class MemPoolAllocator -{ - InterpCompiler* const m_compiler; - InterpMemKind m_memKind; -public: - MemPoolAllocator(InterpCompiler* compiler, InterpMemKind memKind) - : m_compiler(compiler), m_memKind(memKind) {} - void* Alloc(size_t sz) const; - void Free(void* ptr) const; - InterpMemKind getMemKind() const { return m_memKind; } -}; - class InterpDataItemIndexMap { struct VarSizedData @@ -625,6 +611,9 @@ class InterpCompiler // All memory allocated via AllocMemPool is freed when the compiler is destroyed. InterpArenaAllocator *m_arenaAllocator; + // Builder for the unified method data allocation + InterpMethodDataBuilder m_methodDataBuilder; + CORINFO_METHOD_HANDLE m_methodHnd; CORINFO_MODULE_HANDLE m_compScopeHnd; COMP_HANDLE m_compHnd; @@ -707,6 +696,20 @@ class InterpCompiler TArray m_asyncSuspendDataItems; + // Tracks which data items contain pointers to async suspend data + // First = data item index, Second = index into m_asyncSuspendDataItems + struct DataItemAsyncSuspendRef + { + int32_t dataItemIndex; + int32_t asyncSuspendDataIndex; + }; + TArray m_dataItemAsyncSuspendRefs; + + // Prepared InterpMethod data (stored temporarily until finalization) + bool m_initLocals; + bool m_unmanagedCallersOnly; + bool m_publishSecretStubParam; + InterpDataItemIndexMap m_genericLookupToDataItemIndex; int32_t GetDataItemIndex(void* data) { @@ -811,7 +814,7 @@ class InterpCompiler InterpAllocator getAllocatorInstruction() { return getAllocator(IMK_Instruction); } // Legacy allocation methods - use getAllocator() for new code - MemPoolAllocator GetMemPoolAllocator(InterpMemKind imk) { return MemPoolAllocator(this, imk); } + MemPoolAllocator GetMemPoolAllocator(InterpMemKind imk) { return MemPoolAllocator(getAllocator(imk)); } private: // Instructions @@ -1035,7 +1038,8 @@ class InterpCompiler int32_t* EmitBBCode(int32_t *ip, InterpBasicBlock *bb, TArray *relocs); int32_t* EmitCodeIns(int32_t *ip, InterpInst *pIns, TArray *relocs); void PatchRelocations(TArray *relocs); - InterpMethod* CreateInterpMethod(); + void PrepareInterpMethod(); + void CreateBasicBlocks(CORINFO_METHOD_INFO* methodInfo); void InitializeClauseBuildingBlocks(CORINFO_METHOD_INFO* methodInfo); void CreateLeaveChainIslandBasicBlocks(CORINFO_METHOD_INFO* methodInfo, int32_t leaveOffset, InterpBasicBlock* pLeaveTargetBB); @@ -1081,12 +1085,25 @@ class InterpCompiler InterpCompiler(COMP_HANDLE compHnd, CORINFO_METHOD_INFO* methodInfo, InterpreterRetryData *pretryData, InterpArenaAllocator *arenaAllocator); ~InterpCompiler(); - InterpMethod* CompileMethod(); + // Compile the method. Returns true on success, false if retry is needed. + bool CompileMethod(); + + // Get the total size needed for the unified method data allocation. + // Must be called after CompileMethod() succeeds. + uint32_t GetTotalAllocationSize(); + + // Finalize the method data into the provided allocation. + // baseAddressRW is the writable address, baseAddressRX is the executable address. + // Returns the InterpMethod pointer within the allocation. + InterpMethod* FinalizeMethodData(void* baseAddressRW, void* baseAddressRX); + void BuildGCInfo(InterpMethod *pInterpMethod); void BuildEHInfo(); - void UpdateWithFinalMethodByteCodeAddress(InterpByteCodeStart *pByteCodeStart); void dumpMethodMemStats(); + // Get the method data builder for additional customization + InterpMethodDataBuilder& GetMethodDataBuilder() { return m_methodDataBuilder; } + int32_t* GetCode(int32_t *pCodeSize); #if MEASURE_MEM_ALLOC diff --git a/src/coreclr/interpreter/eeinterp.cpp b/src/coreclr/interpreter/eeinterp.cpp index 09512b2934f9ac..c7569a9e103174 100644 --- a/src/coreclr/interpreter/eeinterp.cpp +++ b/src/coreclr/interpreter/eeinterp.cpp @@ -118,8 +118,8 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd, { retryData.StartCompilationAttempt(); InterpCompiler compiler(compHnd, methodInfo, &retryData, &arenaAllocator); - InterpMethod *pMethod = compiler.CompileMethod(); - if (pMethod == NULL) + bool success = compiler.CompileMethod(); + if (!success) { assert(retryData.NeedsRetry()); continue; @@ -128,15 +128,12 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd, // Once we reach here we will not attempt to retry again. assert(!retryData.NeedsRetry()); - int32_t IRCodeSize = 0; - int32_t *pIRCode = compiler.GetCode(&IRCodeSize); - - uint32_t sizeOfCode = sizeof(InterpMethod*) + IRCodeSize * sizeof(int32_t); - uint8_t unwindInfo[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + // Get the total size needed for the unified method data allocation + uint32_t totalSize = compiler.GetTotalAllocationSize(); AllocMemChunk codeChunk {}; - codeChunk.alignment = 1; - codeChunk.size = sizeOfCode; + codeChunk.alignment = sizeof(void*); // Align to pointer size for InterpMethod access + codeChunk.size = totalSize; codeChunk.flags = CORJIT_ALLOCMEM_HOT_CODE; AllocMemArgs args {}; @@ -145,13 +142,11 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd, args.xcptnsCount = 0; compHnd->allocMem(&args); - // We store first the InterpMethod pointer as the code header, followed by the actual code - *(InterpMethod**)codeChunk.blockRW = pMethod; - memcpy ((uint8_t*)codeChunk.blockRW + sizeof(InterpMethod*), pIRCode, IRCodeSize * sizeof(int32_t)); + // Finalize all method data into the unified allocation + InterpMethod* pMethod = compiler.FinalizeMethodData(codeChunk.blockRW, codeChunk.block); - compiler.UpdateWithFinalMethodByteCodeAddress((InterpByteCodeStart*)codeChunk.block); *entryAddress = (uint8_t*)codeChunk.block; - *nativeSizeOfCode = sizeOfCode; + *nativeSizeOfCode = totalSize; // We can't do this until we've called allocMem compiler.BuildGCInfo(pMethod); diff --git a/src/coreclr/interpreter/interpalloc.h b/src/coreclr/interpreter/interpalloc.h index c487ef27a9ff5a..157984e0e8f3f8 100644 --- a/src/coreclr/interpreter/interpalloc.h +++ b/src/coreclr/interpreter/interpalloc.h @@ -54,4 +54,17 @@ using InterpArenaAllocator = ArenaAllocatorT; // It wraps ArenaAllocator and tracks allocations by InterpMemKind. using InterpAllocator = CompAllocatorT; +// MemPoolAllocator provides an allocator interface for use with TArray and other +// data structures. It wraps the InterpCompiler's arena allocator with a specific +// memory kind for statistics tracking. +class MemPoolAllocator +{ + InterpAllocator m_allocator; +public: + MemPoolAllocator(InterpAllocator allocator) + : m_allocator(allocator) {} + void* Alloc(size_t sz) const; + void Free(void* ptr) const; +}; + #endif // _INTERPALLOC_H_ diff --git a/src/coreclr/interpreter/interpmemkind.h b/src/coreclr/interpreter/interpmemkind.h index 0390b327ee9710..b40ef53c75f4db 100644 --- a/src/coreclr/interpreter/interpmemkind.h +++ b/src/coreclr/interpreter/interpmemkind.h @@ -27,6 +27,7 @@ InterpMemKindMacro(ILCode) // IL code buffers InterpMemKindMacro(InterpCode) // Interpreter bytecode InterpMemKindMacro(Instruction) // InterpInst allocations InterpMemKindMacro(IntervalMap) // Variable interval maps +InterpMemKindMacro(MethodData) // Temporary method data during compilation InterpMemKindMacro(NativeToILMapping) // Native to IL offset mappings InterpMemKindMacro(Reloc) // Relocations InterpMemKindMacro(RetryData) // Data for retrying compilation diff --git a/src/coreclr/interpreter/interpmethoddata.cpp b/src/coreclr/interpreter/interpmethoddata.cpp new file mode 100644 index 00000000000000..bfa5ea02b15c7e --- /dev/null +++ b/src/coreclr/interpreter/interpmethoddata.cpp @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "interpreter.h" +#include "interpmethoddata.h" + +uint32_t InterpMethodDataBuilder::AlignUp(uint32_t value, uint32_t alignment) +{ + assert(alignment != 0); + assert((alignment & (alignment - 1)) == 0); + return (value + alignment - 1) & ~(alignment - 1); +} + +InterpMethodDataBuilder::InterpMethodDataBuilder() +{ + // Initialize section alignments + m_sections[(int)InterpMethodDataSection::Header].alignment = sizeof(void*); + m_sections[(int)InterpMethodDataSection::Bytecode].alignment = sizeof(int32_t); + m_sections[(int)InterpMethodDataSection::InterpMethod].alignment = sizeof(void*); + m_sections[(int)InterpMethodDataSection::DataItems].alignment = sizeof(void*); + m_sections[(int)InterpMethodDataSection::AsyncSuspendData].alignment = sizeof(void*); + m_sections[(int)InterpMethodDataSection::IntervalMaps].alignment = sizeof(uint32_t); + + // Header is always sizeof(InterpMethod*) for the InterpByteCodeStart + m_sections[(int)InterpMethodDataSection::Header].size = sizeof(InterpMethod*); +} + +InterpMethodDataBuilder::~InterpMethodDataBuilder() +{ +} + +InterpSectionRef InterpMethodDataBuilder::AllocateInSection(InterpMethodDataSection section, uint32_t size, uint32_t alignment) +{ + assert(!m_finalized); + InterpSectionData& sectionData = m_sections[(int)section]; + + if (alignment == 0) + { + alignment = sectionData.alignment; + } + + // Align the current size + uint32_t alignedOffset = AlignUp(sectionData.size, alignment); + sectionData.size = alignedOffset + size; + + return InterpSectionRef(section, alignedOffset); +} + +void InterpMethodDataBuilder::SetBytecodeSize(uint32_t sizeInBytes) +{ + assert(!m_finalized); + m_sections[(int)InterpMethodDataSection::Bytecode].size = sizeInBytes; +} + +uint32_t InterpMethodDataBuilder::GetTotalSize() +{ + uint32_t offset = 0; + + // Sections are laid out in order + for (int i = 0; i < (int)InterpMethodDataSection::Count; i++) + { + InterpSectionData& section = m_sections[i]; + if (section.size > 0) + { + offset = AlignUp(offset, section.alignment); + section.finalOffset = offset; + offset += section.size; + } + else + { + section.finalOffset = offset; // Empty section points to next + } + } + + return offset; +} + +uint32_t InterpMethodDataBuilder::GetSectionOffset(InterpMethodDataSection section) const +{ + return m_sections[(int)section].finalOffset; +} + +uint32_t InterpMethodDataBuilder::GetSectionSize(InterpMethodDataSection section) const +{ + return m_sections[(int)section].size; +} + +void* InterpMethodDataBuilder::GetFinalPointer(InterpSectionRef ref) const +{ + assert(m_finalized); + return m_finalBaseAddress + m_sections[(int)ref.section].finalOffset + ref.offset; +} + +void InterpMethodDataBuilder::Finalize(void* baseAddressRW, void* baseAddressRX) +{ + assert(!m_finalized); + m_finalBaseAddress = (uint8_t*)baseAddressRX; + + m_finalized = true; +} + +InterpByteCodeStart* InterpMethodDataBuilder::GetByteCodeStart() const +{ + assert(m_finalized); + return (InterpByteCodeStart*)m_finalBaseAddress; +} + +void* InterpMethodDataBuilder::GetWritablePointer(void* baseAddressRW, InterpSectionRef ref) const +{ + return (uint8_t*)baseAddressRW + m_sections[(int)ref.section].finalOffset + ref.offset; +} + +InterpSectionRef InterpMethodDataBuilder::AllocateInterpMethod() +{ + return AllocateInSection(InterpMethodDataSection::InterpMethod, sizeof(InterpMethod)); +} + +InterpSectionRef InterpMethodDataBuilder::AllocateDataItems(int32_t count) +{ + return AllocateInSection(InterpMethodDataSection::DataItems, count * sizeof(void*)); +} + +InterpSectionRef InterpMethodDataBuilder::AllocateAsyncSuspendData() +{ + return AllocateInSection(InterpMethodDataSection::AsyncSuspendData, sizeof(InterpAsyncSuspendData)); +} + +InterpSectionRef InterpMethodDataBuilder::AllocateIntervalMap(int32_t count) +{ + return AllocateInSection(InterpMethodDataSection::IntervalMaps, count * sizeof(InterpIntervalMapEntry)); +} diff --git a/src/coreclr/interpreter/interpmethoddata.h b/src/coreclr/interpreter/interpmethoddata.h new file mode 100644 index 00000000000000..67c55e09bac1b5 --- /dev/null +++ b/src/coreclr/interpreter/interpmethoddata.h @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _INTERPMETHODDATA_H_ +#define _INTERPMETHODDATA_H_ + +#include "interpalloc.h" + +// Forward declarations - actual definitions in interpretershared.h +struct InterpMethod; +struct InterpByteCodeStart; +struct InterpGenericLookup; +struct InterpAsyncSuspendData; +struct InterpIntervalMapEntry; + +// InterpMethodDataBuilder accumulates all persistent data for a compiled method +// during compilation, then finalizes it into a single contiguous allocation. +// +// Memory Layout after finalization: +// ┌────────────────────────────────────────┐ +// │ InterpByteCodeStart (Method ptr) │ <- baseAddress +// ├────────────────────────────────────────┤ +// │ Bytecodes (int32_t[]) │ +// ├────────────────────────────────────────┤ +// │ InterpMethod struct │ +// ├────────────────────────────────────────┤ +// │ DataItems array (void*[]) │ +// ├────────────────────────────────────────┤ +// │ InterpAsyncSuspendData structs │ +// ├────────────────────────────────────────┤ +// │ InterpIntervalMapEntry arrays │ +// └────────────────────────────────────────┘ + +// Sections within the unified method data allocation +enum class InterpMethodDataSection : uint8_t +{ + Header, // InterpByteCodeStart + Bytecode, // int32_t[] opcodes + InterpMethod, // InterpMethod struct + DataItems, // void*[] array + AsyncSuspendData, // InterpAsyncSuspendData structs + IntervalMaps, // InterpIntervalMapEntry arrays + Count +}; + +// Reference to a location within a section, used during compilation +// before final addresses are known +struct InterpSectionRef +{ + InterpMethodDataSection section; + uint32_t offset; // Offset within the section + + InterpSectionRef() : section(InterpMethodDataSection::Count), offset(0) {} + InterpSectionRef(InterpMethodDataSection s, uint32_t o) : section(s), offset(o) {} + + bool IsNull() const { return section == InterpMethodDataSection::Count; } +}; + +// Tracks data for a single section during building +struct InterpSectionData +{ + uint32_t size = 0; + uint32_t alignment = sizeof(void*); // Default to pointer alignment + uint32_t finalOffset = 0; // Offset in final allocation (set during GetTotalSize) +}; + +class InterpMethodDataBuilder +{ +private: + InterpSectionData m_sections[(int)InterpMethodDataSection::Count]; + + // Cached section base addresses after finalization + uint8_t* m_finalBaseAddress = nullptr; + bool m_finalized = false; + + static uint32_t AlignUp(uint32_t value, uint32_t alignment); + +public: + InterpMethodDataBuilder(); + ~InterpMethodDataBuilder(); + + // Allocate space in a section and return a reference to it + InterpSectionRef AllocateInSection(InterpMethodDataSection section, uint32_t size, uint32_t alignment = 0); + + // Set the bytecode section size (bytecodes are written directly by the compiler) + void SetBytecodeSize(uint32_t sizeInBytes); + + // Calculate total size needed for the unified allocation + uint32_t GetTotalSize(); + + // Get the final offset of a section within the allocation + uint32_t GetSectionOffset(InterpMethodDataSection section) const; + + // Get the size of a section + uint32_t GetSectionSize(InterpMethodDataSection section) const; + + // Convert a section reference to a final pointer (only valid after Finalize) + void* GetFinalPointer(InterpSectionRef ref) const; + + // Finalize the method data + // baseAddressRW is the writable address, baseAddressRX is the executable address + void Finalize(void* baseAddressRW, void* baseAddressRX); + + // Get the InterpByteCodeStart pointer (only valid after Finalize) + InterpByteCodeStart* GetByteCodeStart() const; + + // Get the writable location for a section reference (for copying data during finalization) + void* GetWritablePointer(void* baseAddressRW, InterpSectionRef ref) const; + + // Helper: Allocate InterpMethod and return its reference + InterpSectionRef AllocateInterpMethod(); + + // Helper: Allocate data items array + InterpSectionRef AllocateDataItems(int32_t count); + + // Helper: Allocate async suspend data + InterpSectionRef AllocateAsyncSuspendData(); + + // Helper: Allocate interval map entries + InterpSectionRef AllocateIntervalMap(int32_t count); + + bool IsFinalized() const { return m_finalized; } +}; + +#endif // _INTERPMETHODDATA_H_