diff --git a/include/GcInfo/GcInfo.h b/include/GcInfo/GcInfo.h index 317a3c0f7cb..347b64faa24 100644 --- a/include/GcInfo/GcInfo.h +++ b/include/GcInfo/GcInfo.h @@ -9,14 +9,44 @@ //===----------------------------------------------------------------------===// /// /// \file -/// \brief Wrapper that includes all GcInfo related headers +/// \brief GCInfo Generator for LLILC /// //===----------------------------------------------------------------------===// #ifndef GCINFO_H #define GCINFO_H -#define STANDALONE_BUILD -#include "gcinfoencoder.h" +#include "jitpch.h" +#include "LLILCJit.h" + +class GcInfoAllocator; +class GcInfoEncoder; + +/// \brief This is the translator from LLVM's GC StackMaps +/// to CoreCLR's GcInfo encoding. +class GCInfo { +public: + /// Construct a GCInfo object + /// \param JitContext Context record for the method's jit request. + /// \param StackMapData A pointer to the .llvm_stackmaps section + /// loaded in memory + /// \param Start address of the Code section block + GCInfo(LLILCJitContext *JitContext, uint8_t *LLVMStackMapData, + uint8_t *CodeBlockStart); + + /// Emit GC Info to the EE using GcInfoEncoder. + void emitGCInfo(); + +private: + void encodeHeader(); + void encodeLiveness(); + void emitEncoding(); + + LLILCJitContext *JitContext; + uint8_t *LLVMStackMapData; + GcInfoEncoder *Encoder; + uint8_t *CodeBlockStart; + bool EmitLogs; +}; #endif // GCINFO_H diff --git a/include/GcInfo/GcInfoUtil.h b/include/GcInfo/GcInfoUtil.h index 976bd6c464e..9bce562c6b8 100644 --- a/include/GcInfo/GcInfoUtil.h +++ b/include/GcInfo/GcInfoUtil.h @@ -503,10 +503,10 @@ class StructArrayListBase { }; friend class ArrayIteratorBase; - StructArrayListEntryBase * - m_pChunkListHead; // actually StructArrayListEntry* - StructArrayListEntryBase * - m_pChunkListTail; // actually StructArrayListEntry* + StructArrayListEntryBase + *m_pChunkListHead; // actually StructArrayListEntry* + StructArrayListEntryBase + *m_pChunkListTail; // actually StructArrayListEntry* SIZE_T m_nItemsInLastChunk; SIZE_T m_nTotalItems; SIZE_T m_nLastChunkCapacity; diff --git a/include/GcInfo/Target.h b/include/GcInfo/Target.h new file mode 100644 index 00000000000..f35c9460618 --- /dev/null +++ b/include/GcInfo/Target.h @@ -0,0 +1,51 @@ +//===---- include/gcinfo/target.h -------------------------------*- C++ -*-===// +// +// LLILC +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// See LICENSE file in the project root for full license information. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Target specific definitions for GCInfo generation +/// +//===----------------------------------------------------------------------===// + +#ifndef GCINFO_TARGET_H +#define GCINFO_TARGET_H + +#include "global.h" + +#if (defined(_TARGET_X86_) || defined(_TARGET_X64_) || defined(_TARGET_AMD64_)) + +// Define DWARF encodings for registers +// Size variants (ex: AL,AH,AX,EAX,RAX) all get the same Dwarf register number + +#define DW_RAX 0 +#define DW_RBX 3 +#define DW_RCX 2 +#define DW_RDX 1 +#define DW_RSI 4 +#define DW_RDI 5 +#define DW_RBP 6 +#define DW_RSP 7 +#define DW_RIP 16 +#define DW_R8 8 +#define DW_R9 9 +#define DW_R10 10 +#define DW_R11 11 +#define DW_R12 12 +#define DW_R13 13 +#define DW_R14 14 +#define DW_R15 15 + +#define DW_FRAME_POINTER DW_RBP +#define DW_STACK_POINTER DW_RSP + +#else +#error GCTables not implemented for this target +#endif // defined(_TARGET_X86_ || _TARGET_X64_ || _TARGET_AMD64_) + +#endif // GCINFO_TARGET_H diff --git a/include/Jit/EEMemoryManager.h b/include/Jit/EEMemoryManager.h index 1542563ef22..9d74f9b48a0 100644 --- a/include/Jit/EEMemoryManager.h +++ b/include/Jit/EEMemoryManager.h @@ -34,7 +34,7 @@ class EEMemoryManager : public RTDyldMemoryManager { /// \param C Jit context for the method being jitted. EEMemoryManager(LLILCJitContext *C) : Context(C), HotCodeBlock(nullptr), ColdCodeBlock(nullptr), - ReadOnlyDataBlock(nullptr) {} + ReadOnlyDataBlock(nullptr), StackMapBlock(nullptr) {} /// Destroy an \p EEMemoryManager ~EEMemoryManager() override; @@ -127,11 +127,26 @@ class EEMemoryManager : public RTDyldMemoryManager { void deregisterEHFrames(uint8_t *Addr, uint64_t LoadAddr, size_t Size) override; + /// \brief Get the LLVM Stackmap section if allocated. + /// + /// Returns a pointer to the .llvm_stackmaps section + /// if it is already loaded into memory. + + uint8_t *getStackMapSection() { return StackMapBlock; } + + /// \brief Get the HotCode section if allocated. + /// + /// Returns a pointer to the HotCode section + /// if it is already loaded into memory. + + uint8_t *getHotCodeBlock() { return HotCodeBlock; } + private: LLILCJitContext *Context; ///< LLVM context for types, etc. uint8_t *HotCodeBlock; ///< Memory to hold the hot method code. uint8_t *ColdCodeBlock; ///< Memory to hold the cold method code. uint8_t *ReadOnlyDataBlock; ///< Memory to hold the readonly data. + uint8_t *StackMapBlock; ///< Memory to hold the readonly StackMap uint8_t *ReadOnlyDataUnallocated; ///< Address of unallocated part of RO data. }; } // namespace llvm diff --git a/include/Jit/LLILCJit.h b/include/Jit/LLILCJit.h index 65e29f9db9d..37137f09b68 100644 --- a/include/Jit/LLILCJit.h +++ b/include/Jit/LLILCJit.h @@ -17,7 +17,7 @@ #define LLILC_JIT_H #include "Pal/LLILCPal.h" -#include "options.h" +#include "Reader/options.h" #include "llvm/ExecutionEngine/ExecutionEngine.h" #include "llvm/ExecutionEngine/RuntimeDyld.h" #include "llvm/IR/LLVMContext.h" @@ -106,9 +106,10 @@ struct LLILCJitContext { /// \name Jit output sizes //@{ - uint32_t HotCodeSize = 0; ///< Size of hot code section in bytes. - uint32_t ColdCodeSize = 0; ///< Size of cold code section in bytes. - uint32_t ReadOnlyDataSize = 0; ///< Size of readonly data ref'd from code. + uintptr_t HotCodeSize = 0; ///< Size of hot code section in bytes. + uintptr_t ColdCodeSize = 0; ///< Size of cold code section in bytes. + uintptr_t ReadOnlyDataSize = 0; ///< Size of readonly data ref'd from code. + uintptr_t StackMapSize = 0; ///< Size of readonly Stackmap section. //@} }; @@ -236,10 +237,6 @@ class LLILCJit : public ICorJitCompiler { /// \returns \p true if the conversion was successful. bool readMethod(LLILCJitContext *JitContext); - /// Output GC info to the EE. - /// \param JitContext Context record for the method's jit request. - void outputGCInfo(LLILCJitContext *JitContext); - public: /// A pointer to the singleton jit instance. static LLILCJit *TheJit; diff --git a/include/Jit/jitoptions.h b/include/Jit/jitoptions.h index 0be1fe82718..59d0a509012 100644 --- a/include/Jit/jitoptions.h +++ b/include/Jit/jitoptions.h @@ -69,9 +69,14 @@ class JitOptions : public Options { /// \brief Set DoTailCallOpt based on environment variable. /// - /// \returns true if COMPLUS_TAILCALLOPT is set in the environment set. + /// \returns true if COMPLUS_TAILCALLOPT is set in the environment. static bool queryDoTailCallOpt(LLILCJitContext &JitContext); + /// \brief Set LogGcInfo based on environment variable. + /// + /// \returns true if COMPLUS_JitGCInfoLogging is set in the environment. + static bool queryLogGcInfo(LLILCJitContext &JitContext); + public: bool IsAltJit; ///< True if running as the alternative JIT. diff --git a/include/Reader/options.h b/include/Reader/options.h index 179e94f2311..02e0322faa5 100644 --- a/include/Reader/options.h +++ b/include/Reader/options.h @@ -53,5 +53,6 @@ struct Options { bool UseConservativeGC; ///< True if the environment is set to use CGC. bool DoInsertStatepoints; ///< True if the environment calls for statepoints. bool DoTailCallOpt; ///< Tail call optimization. + bool LogGcInfo; ///< Generate GCInfo Translation logs }; #endif // OPTIONS_H diff --git a/lib/GcInfo/CMakeLists.txt b/lib/GcInfo/CMakeLists.txt index fb37252632d..741caf69283 100644 --- a/lib/GcInfo/CMakeLists.txt +++ b/lib/GcInfo/CMakeLists.txt @@ -19,4 +19,5 @@ add_llilcjit_library(GcInfo STATIC ${CORECLR_GCINFO}/gcinfoencoder.cpp GcInfoUtil.cpp + GcInfo.cpp ) diff --git a/lib/GcInfo/GcInfo.cpp b/lib/GcInfo/GcInfo.cpp new file mode 100644 index 00000000000..2a6ca69af3a --- /dev/null +++ b/lib/GcInfo/GcInfo.cpp @@ -0,0 +1,271 @@ +//===-------- include/gcinfo/gcinfo.cpp -------------------------*- C++ -*-===// +// +// LLILC +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. +// See LICENSE file in the project root for full license information. +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// \brief Implements the generation of CLR GCTables in LLILC +/// +//===----------------------------------------------------------------------===// + +#include "earlyincludes.h" +#include "GcInfo.h" +#include "gcinfoencoder.h" +#include "jitpch.h" +#include "LLILCJit.h" +#include "Target.h" +#include "llvm/Object/StackMapParser.h" +#include +#include +#include + +using namespace llvm; +using namespace std; + +GCInfo::GCInfo(LLILCJitContext *JitContext, uint8_t *LLVMStackMapData, + uint8_t *CodeBlockStart) { + this->JitContext = JitContext; + this->LLVMStackMapData = LLVMStackMapData; + this->CodeBlockStart = CodeBlockStart; + + this->Encoder = new GcInfoEncoder(JitContext->JitInfo, JitContext->MethodInfo, + new GcInfoAllocator()); + +#if !defined(NDEBUG) + this->EmitLogs = JitContext->Options->LogGcInfo; +#endif // !NDEBUG +} + +void GCInfo::encodeHeader() { + if (EmitLogs) { + dbgs() << "GcTable for Function: " << JitContext->MethodName << "\n" + << " Size: " << JitContext->HotCodeSize << "\n"; + } + + // TODO: Set Code Length accurately. + // https://github.com/dotnet/llilc/issues/679 + // JitContext->HotCodeSize is the size of the allocated code block. + // It is not the actual length of the current function's code. + Encoder->SetCodeLength(JitContext->HotCodeSize); + Encoder->SetSizeOfStackOutgoingAndScratchArea(0); +} + +void GCInfo::encodeLiveness() { + if (LLVMStackMapData == nullptr) { + return; + } + + ArrayRef StackMapContentsArray( + reinterpret_cast(LLVMStackMapData), + JitContext->StackMapSize); + +#if defined(BIGENDIAN) + typedef StackMapV1Parser StackMapParserType; +#else + typedef StackMapV1Parser StackMapParserType; +#endif + StackMapParserType StackMapParser(StackMapContentsArray); + + assert(StackMapParser.getNumFunctions() == 1 && + "Expect only one function with GcInfo in the module"); + + // The InstructionOffsets for Call-sites are with respect to: + // (1) FunctionEntry in LLVM's StackMap + // (2) CodeBlockStart in CoreCLR's GcTable + // + // There is typically a difference between the two even in the JIT case + // (where we emit one function per module) because of some additional + // code like the gc.statepoint_poll() method. + + uint8_t *FunctionEntry = + (uint8_t *)StackMapParser.getFunction(0).getFunctionAddress(); + + size_t OffsetCorrection = FunctionEntry - CodeBlockStart; + + // Loop over LLVM StackMap records to: + // 1) Note CallSites (safepoints) + // 2) Assign Slot-IDs to each unique gc-pointer location (slot) + // 3) Record liveness (birth/death) of slots per call-site. + + size_t NumCallSites = StackMapParser.getNumRecords(); + unsigned *CallSites = new unsigned[NumCallSites]; + BYTE *CallSiteSizes = new BYTE[NumCallSites]; + map SlotMap; + size_t NumSlots = 0; + + // LLVM StackMap records all live-pointers per Safepoint, whereas + // CoreCLR's GCTables record pointer birth/deaths per Safepoint. + // So, we do the translation using old/new live-pointer-sets + // + // We need bit-sets for recording the liveness -- one bit per slot. + // But std::BitSet only supports fixed size bitsets, so we use + // vector which should be optimized by the compiler to use + // a bit-wide representation. + + vector OldLiveSet; + vector NewLiveSet; + + // TODO: Identify Object and Managed pointers differently + // https://github.com/dotnet/llilc/issues/28 + // We currently conservatively describe all slots as containing + // interior pointers + GcSlotFlags SlotFlags = (GcSlotFlags)GC_SLOT_INTERIOR; + + // TODO: Determine call-site-size accurately + // https://github.com/Microsoft/llvm/issues/56 + // Call-site size is not yet available in LLVM's StackMap, + // so make up a value for now. + // The Call-instruction generated by LLILC on X86/X64 is typically + // Call [rax], which has a two-byte encoding. + uint8_t CallSiteSize = 2; + +#if !defined(NDEBUG) + if (EmitLogs) { + dbgs() << " FunctionEntry: " << FunctionEntry << "\n" + << " #Safepoints: " << NumCallSites << "\n"; + } +#endif // !NDEBUG + + size_t RecordIndex = 0; + for (const auto &R : StackMapParser.records()) { + + // InstructionOffset: + // + OffsetCorrection: to account for any bytes before the start + // of the function. + // (-1) CLR expects the statepoint to be one byte before the start of the + // instruction following the Call-instruction. + // (-1) Safepoints are encoded with a -1 adjustment. + unsigned InstructionOffset = + R.getInstructionOffset() + OffsetCorrection - 2; + + CallSites[RecordIndex] = InstructionOffset; + CallSiteSizes[RecordIndex] = 2; + +#if !defined(NDEBUG) + ostringstream SlotStream; + ostringstream LiveStream; + + if (EmitLogs) { + LiveStream << "Safepoint: " << RecordIndex << ": (" + << CallSites[RecordIndex] << " - " + << (CallSites[RecordIndex] + CallSiteSizes[RecordIndex]) + << ")\n"; + } +#endif // !NDEBUG + + // We don't generate GC_CALLER_SP_REL locatons, just + // using this as a default value other than SP/FP REL. + GcStackSlotBase SpBase = GC_CALLER_SP_REL; + + for (const auto &Loc : R.locations()) { + + switch (Loc.getKind()) { + case StackMapParserType::LocationKind::Constant: + case StackMapParserType::LocationKind::ConstantIndex: + continue; + + case StackMapParserType::LocationKind::Register: + // TODO: Report Live - GC values in Registers + // https://github.com/dotnet/llilc/issues/474 + // Live gc-pointers are currently spilled to the stack at Safepoints. + assert(false && "GC-Pointer Live in Register"); + break; + + case StackMapParserType::LocationKind::Direct: { + + uint16_t DwReg = Loc.getDwarfRegNum(); + switch (DwReg) { + case DW_FRAME_POINTER: + assert(SpBase != GC_SP_REL && "Mixed SP/FP based Locations"); + SpBase = GC_FRAMEREG_REL; + break; + case DW_STACK_POINTER: + assert(SpBase != GC_FRAMEREG_REL && "Mixed SP/FP based Locations"); + SpBase = GC_SP_REL; + break; + default: + assert(false && "Unexpected stack base-pointer"); + } + + GcSlotId SlotID; + int32_t Offset = Loc.getOffset(); + map::iterator ExistingSlot = SlotMap.find(Offset); + if (ExistingSlot == SlotMap.end()) { + SlotID = Encoder->GetStackSlotId(Offset, SlotFlags, SpBase); + + SlotMap[Offset] = SlotID; + + // Make space for another slot in the Lifetime trackers. + NumSlots++; + OldLiveSet.push_back(false); + NewLiveSet.push_back(false); + +#if !defined(NDEBUG) + if (EmitLogs) { + SlotStream << " [" << SlotID + << "]:" << ((SpBase == GC_SP_REL) ? "sp+" : "fp+") + << Offset; + } +#endif // !NDEBUG + } else { + SlotID = ExistingSlot->second; + } + + NewLiveSet[SlotID] = true; + break; + } + } + } + + for (GcSlotId SlotID = 0; SlotID < NumSlots; SlotID++) { + if (!OldLiveSet[SlotID] && NewLiveSet[SlotID]) { +#if !defined(NDEBUG) + if (EmitLogs) { + LiveStream << " Live:" << SlotID; + } +#endif // !NDEBUG + Encoder->SetSlotState(InstructionOffset, SlotID, GC_SLOT_LIVE); + } else if (OldLiveSet[SlotID] && !NewLiveSet[SlotID]) { +#if !defined(NDEBUG) + if (EmitLogs) { + LiveStream << " Dead:" << SlotID; + } +#endif // !NDEBUG + Encoder->SetSlotState(InstructionOffset, SlotID, GC_SLOT_DEAD); + } + + OldLiveSet[SlotID] = NewLiveSet[SlotID]; + NewLiveSet[SlotID] = false; + } + +#if !defined(NDEBUG) + if (EmitLogs) { + dbgs() << " Slots: " << SlotStream.str() << "\n"; + dbgs() << " Liveness: " << LiveStream.str() << "\n"; + } +#endif // !NDEBUG + + RecordIndex++; + } + // Finalize Slot IDs to enable compact representation + Encoder->FinalizeSlotIds(); + + // Encode Call-sites + Encoder->DefineCallSites(CallSites, CallSiteSizes, NumCallSites); +} + +void GCInfo::emitEncoding() { + Encoder->Build(); + Encoder->Emit(); +} + +void GCInfo::emitGCInfo() { + encodeHeader(); + encodeLiveness(); + emitEncoding(); +} diff --git a/lib/Jit/EEMemoryManager.cpp b/lib/Jit/EEMemoryManager.cpp index 44c65506835..084e28fe382 100644 --- a/lib/Jit/EEMemoryManager.cpp +++ b/lib/Jit/EEMemoryManager.cpp @@ -65,6 +65,14 @@ uint8_t *EEMemoryManager::allocateDataSection(uintptr_t Size, assert(ReadOnlyDataUnallocated <= (ReadOnlyDataBlock + this->Context->ReadOnlyDataSize)); + if (SectionName.equals(".llvm_stackmaps")) { + assert((this->StackMapBlock == nullptr) && + "Unexpected second Stackmap Section"); + + this->Context->StackMapSize = Size; + this->StackMapBlock = Result; + } + return Result; } @@ -83,14 +91,14 @@ void EEMemoryManager::reserveAllocationSpace(uintptr_t CodeSize, this->Context->JitInfo->reserveUnwindInfo(FALSE, FALSE, DataSizeRO); // Treat all code for now as "hot section" - uint32_t HotCodeSize = CodeSize; - uint32_t ColdCodeSize = 0; + uintptr_t HotCodeSize = CodeSize; + uintptr_t ColdCodeSize = 0; // We still need to allocate space for the RO data here too, because // LLVM's dynamic loader does not know where the EE's reservation was made. // So this gives the dyamic loader room to copy the RO sections, and later // the EE will copy from there to the place it really keeps unwind data. - uint32_t ReadOnlyDataSize = DataSizeRO; + uintptr_t ReadOnlyDataSize = DataSizeRO; uint32_t ExceptionCount = 0; // Remap alignment to the EE notion of alignment diff --git a/lib/Jit/LLILCJit.cpp b/lib/Jit/LLILCJit.cpp index 4e3fee47dc9..cbd05518e34 100644 --- a/lib/Jit/LLILCJit.cpp +++ b/lib/Jit/LLILCJit.cpp @@ -14,9 +14,9 @@ //===----------------------------------------------------------------------===// #include "earlyincludes.h" -#include "GcInfo.h" #include "jitpch.h" #include "LLILCJit.h" +#include "GcInfo.h" #include "jitoptions.h" #include "readerir.h" #include "abi.h" @@ -278,11 +278,8 @@ CorJitResult LLILCJit::compileMethod(ICorJitInfo *JitInfo, // TODO: ColdCodeSize, or separated code, is not enabled or included. *NativeSizeOfCode = Context.HotCodeSize + Context.ReadOnlyDataSize; - // This is a stop-gap point to issue a default stub of GC info. This lets - // the CLR consume our methods cleanly. (and the ETW tracing still works) - // Down the road this will be superseded by a CLR specific - // GCMetadataPrinter instance or similar. - this->outputGCInfo(&Context); + GCInfo GcInfo(&Context, MM.getStackMapSection(), MM.getHotCodeBlock()); + GcInfo.emitGCInfo(); // Dump out any enabled timing info. TimerGroup::printAll(errs()); @@ -396,20 +393,6 @@ bool LLILCJit::readMethod(LLILCJitContext *JitContext) { return IsOk; } -void LLILCJit::outputGCInfo(LLILCJitContext *JitContext) { - GcInfoAllocator Allocator; - GcInfoEncoder gcInfoEncoder(JitContext->JitInfo, JitContext->MethodInfo, - &Allocator); - - // The Encoder currently only encodes the CodeSize - // TODO: Encode pointer liveness information for GC-safepoints in the method - - gcInfoEncoder.SetCodeLength(JitContext->HotCodeSize); - - gcInfoEncoder.Build(); - gcInfoEncoder.Emit(); -} - // Notification from the runtime that any caches should be cleaned up. void LLILCJit::clearCache() { return; } diff --git a/lib/Jit/jitoptions.cpp b/lib/Jit/jitoptions.cpp index 3e29bc22e15..58c7855e4c2 100644 --- a/lib/Jit/jitoptions.cpp +++ b/lib/Jit/jitoptions.cpp @@ -68,6 +68,8 @@ JitOptions::JitOptions(LLILCJitContext &Context) { // Set whether to do tail call opt. DoTailCallOpt = queryDoTailCallOpt(Context); + LogGcInfo = queryLogGcInfo(Context); + // Validate Statepoint and Conservative GC state. assert(DoInsertStatepoints || UseConservativeGC && "Statepoints required for precise-GC"); @@ -173,6 +175,13 @@ bool JitOptions::queryDoInsertStatepoints(LLILCJitContext &Context) { return (StatePointStr != nullptr); } +// Determine if GCInfo encoding logs should be emitted +bool JitOptions::queryLogGcInfo(LLILCJitContext &Context) { + char16_t *LogGcInfoStr = + getStringConfigValue(Context.JitInfo, UTF16("JitGCInfoLogging")); + return (LogGcInfoStr != nullptr); +} + OptLevel JitOptions::queryOptLevel(LLILCJitContext &Context) { ::OptLevel JitOptLevel = ::OptLevel::INVALID; // Currently we only check for the debug flag but this will be extended