From 60e214524def5240412f7f8f3f3dd98dd94a2885 Mon Sep 17 00:00:00 2001 From: danmosemsft Date: Sun, 12 Feb 2017 20:42:42 -0800 Subject: [PATCH] Remove constrainedexecutionregion.cpp Commit migrated from https://github.com/dotnet/coreclr/commit/6f05c7c8d283345b811cfe7abe1c2ad416301a12 --- src/coreclr/src/inc/clrconfigvalues.h | 1 - .../Reliability/ReliabilityContractAttribute.cs | 7 - src/coreclr/src/vm/constrainedexecutionregion.cpp | 2264 -------------------- 3 files changed, 2272 deletions(-) delete mode 100644 src/coreclr/src/vm/constrainedexecutionregion.cpp diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h index 347cdd0..6a30af1 100644 --- a/src/coreclr/src/inc/clrconfigvalues.h +++ b/src/coreclr/src/inc/clrconfigvalues.h @@ -1058,7 +1058,6 @@ CONFIG_DWORD_INFO_DIRECT_ACCESS(INTERNAL_AlwaysUseMetadataInterfaceMapLayout, W( CONFIG_DWORD_INFO(INTERNAL_AssertOnUnneededThis, W("AssertOnUnneededThis"), 0, "While the ConfigDWORD is unnecessary, the contained ASSERT should be kept. This may result in some work tracking down violating MethodDescCallSites.") CONFIG_DWORD_INFO_EX(INTERNAL_AssertStacktrace, W("AssertStacktrace"), 1, "", CLRConfig::REGUTIL_default) RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS(UNSUPPORTED_BuildFlavor, W("BuildFlavor"), "Choice of build flavor (wks or svr) of CLR") -CONFIG_DWORD_INFO(INTERNAL_CerLogging, W("CerLogging"), 0, "In vm\\ConstrainedExecutionRegion.cpp. Debug-only logging when we prepare methods, find reliability contract problems, restore stuff from ngen images, etc.") CONFIG_DWORD_INFO_EX(INTERNAL_clearNativeImageStress, W("clearNativeImageStress"), 0, "", CLRConfig::REGUTIL_default) RETAIL_CONFIG_STRING_INFO_DIRECT_ACCESS(INTERNAL_CLRLoadLogDir, W("CLRLoadLogDir"), "Enable logging of CLR selection") RETAIL_CONFIG_STRING_INFO_EX(EXTERNAL_CONFIG, W("CONFIG"), "Used to specify an XML config file for EEConfig", CLRConfig::REGUTIL_default) diff --git a/src/coreclr/src/mscorlib/src/System/Runtime/Reliability/ReliabilityContractAttribute.cs b/src/coreclr/src/mscorlib/src/System/Runtime/Reliability/ReliabilityContractAttribute.cs index 4a14e57..a889f15 100644 --- a/src/coreclr/src/mscorlib/src/System/Runtime/Reliability/ReliabilityContractAttribute.cs +++ b/src/coreclr/src/mscorlib/src/System/Runtime/Reliability/ReliabilityContractAttribute.cs @@ -20,13 +20,6 @@ namespace System.Runtime.ConstrainedExecution { using System.Runtime.InteropServices; using System; - // ************************************************************************************************************************** - // - // Note that if you change either of the enums below or the constructors, fields or properties of the custom attribute itself - // you must also change the logic and definitions in vm\ConstrainedExecutionRegion.cpp to match. - // - // ************************************************************************************************************************** - [Serializable] public enum Consistency : int { diff --git a/src/coreclr/src/vm/constrainedexecutionregion.cpp b/src/coreclr/src/vm/constrainedexecutionregion.cpp deleted file mode 100644 index 0745bd5..0000000 --- a/src/coreclr/src/vm/constrainedexecutionregion.cpp +++ /dev/null @@ -1,2264 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. -// -// Methods to support the implementation of Constrained Execution Regions (CERs). This includes logic to walk the IL of methods to -// determine the statically determinable call graph and prepare each submethod (jit, prepopulate generic dictionaries etc., -// everything needed to ensure that the runtime won't generate implicit failure points during the execution of said call graph). -// - -// - - -#include "common.h" -#include -#include -#include -#include -#include - -#ifdef FEATURE_PREJIT -#include -#endif - - -// Internal debugging support. Would be nice to use the common logging code but we've run out of unique facility codes and the debug -// info we spew out is of interest to a limited audience anyhow. -#ifdef _DEBUG - -#define CER_NOISY_PREPARE 0x00000001 -#define CER_NOISY_RESTORE 0x00000002 -#define CER_NOISY_CONTRACTS 0x00000004 -#define CER_NOISY_WARNINGS 0x00000008 -#define CER_NOISY_NGEN_STATS 0x00000010 - -DWORD g_dwCerLogActions = 0xffffffff; -DWORD GetCerLoggingOptions() -{ - WRAPPER_NO_CONTRACT; - if (g_dwCerLogActions != 0xffffffff) - return g_dwCerLogActions; - return g_dwCerLogActions = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_CerLogging); -} - -#define CER_LOG(_reason, _msg) do { if (GetCerLoggingOptions() & CER_NOISY_##_reason) printf _msg; } while (false) -#else -#define CER_LOG(_reason, _msg) -#endif - - -// Enumeration used to determine the number of inline data bytes included inside a given IL instruction (except for the case of a -// SWITCH instruction, where a dynamic calculation is required). -enum -{ - ArgBytes_InlineNone = 0, // no inline args - ArgBytes_InlineVar = 2, // local variable (U2 (U1 if Short on)) - ArgBytes_InlineI = 4, // an signed integer (I4 (I1 if Short on)) - ArgBytes_InlineR = 8, // a real number (R8 (R4 if Short on)) - ArgBytes_InlineBrTarget = 4, // branch target (I4 (I1 if Short on)) - ArgBytes_InlineI8 = 8, - ArgBytes_InlineMethod = 4, // method token (U4) - ArgBytes_InlineField = 4, // field token (U4) - ArgBytes_InlineType = 4, // type token (U4) - ArgBytes_InlineString = 4, // string TOKEN (U4) - ArgBytes_InlineSig = 4, // signature tok (U4) - ArgBytes_InlineRVA = 4, // ldptr token (U4) - ArgBytes_InlineTok = 4, // a meta-data token of unknown type (U4) - ArgBytes_InlineSwitch = 4, // count (U4), pcrel1 (U4) .... pcrelN (U4) - ArgBytes_ShortInlineVar = 1, - ArgBytes_ShortInlineI = 1, - ArgBytes_ShortInlineR = 4, - ArgBytes_ShortInlineBrTarget = 1 -}; - -// Build an array of argument byte counts as described above by extracting the 'args' field of each entry in opcode.def. -#define OPDEF(c, s, pop, push, args, type, l, s1, s2, ctrl) ArgBytes_##args, -const BYTE g_rOpArgs[] = { -#include -}; -#undef OPDEF - - -// Global cache of methods and their reliability contract state. -PtrHashCache *g_pMethodContractCache = NULL; - - -// Private method forward references. -bool IsPcrReference(Module *pModule, mdToken tkMethod); -MethodContext *TokenToMethodDesc(Module *pModule, mdToken tokMethod, SigTypeContext *pTypeContext); -TypeHandle GetTypeFromMemberDefOrRefOrSpecThrowing(Module *pModule, - mdToken tokMethod, - SigTypeContext *pTypeContext); - -bool MethodCallGraphPreparer::ShouldGatherExplicitCERCallInfo() -{ - LIMITED_METHOD_CONTRACT; - - // If we're partially processing a method body (at the top of the call graph), we need to fetch exception handling - // information to determine possible ranges of interesting IL (potentially each finally and catch clause). - // - // And if we are probing for stack overflow, we need to know if the explicit CER region contains any calls out, in - // which case we want to probe in the call to PrepareConstrainedExecutionRegions. This will ensure that we don't - // take an SO in boundary code and not be able to call the CER. When stack probing is disabled, we rip the process - // if we take an SO anywhere but managed, or if we take an SO with a CER on the stack. For NGEN images, we need - // to always probe because stack probing may be enabled in the runtime, but if we haven't probed in the NGEN image - // then we could take an SO in boundary code and not be able to crawl the stack to know that we've skipped a CER and - // need to tear the process. - // - // Additionally, if the MDA for illegal PrepareConstrainedRegions call positioning is enabled we gather this information for - // all methods in the graph. - return !m_fEntireMethod -#ifdef MDA_SUPPORTED - || MDA_GET_ASSISTANT(IllegalPrepareConstrainedRegion) -#endif -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - || m_fNgen -#endif - || g_pConfig->ProbeForStackOverflow(); -} - -MethodCallGraphPreparer::MethodCallGraphPreparer(MethodDesc *pRootMD, SigTypeContext *pRootTypeContext, bool fEntireMethod, bool fExactTypeContext, bool fIgnoreVirtualCERCallMDA) -{ - CONTRACTL { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pRootMD)); - PRECONDITION(CheckPointer(pRootTypeContext)); - } CONTRACTL_END; - - // Canonicalize value type unboxing stubs into their underlying method desc. - if (pRootMD->IsUnboxingStub()) - pRootMD = pRootMD->GetWrappedMethodDesc(); - - m_pRootMD = pRootMD; - m_pRootTypeContext = pRootTypeContext; - m_fEntireMethod = fEntireMethod; - m_fExactTypeContext = fExactTypeContext; - m_fIgnoreVirtualCERCallMDA = fIgnoreVirtualCERCallMDA; - - m_pEHClauses = NULL; - m_cEHClauses = 0; - m_pCerPrepInfo = NULL; - m_pMethodDecoder = NULL; - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - m_fNgen = false; -#endif - - m_pThread = GetThread(); - m_fPartialPreparation = false; - m_fMethodHasCallsWithinExplicitCer = false; -} - -// Walk the call graph of the method given by pRootMD (and type context in pRootTypeContext which provides instantiation information -// for generic methods/classes). -// -// If fEntireMethod is true then the entire body of pRootMD is scanned for callsites, otherwise we assume that one or more CER -// exception handlers exist in the method and only the finally and catch blocks of such handlers are scanned for graph roots. -// -// Each method we come across in the call graph (excluding late bound invocation destinations precipitated by virtual or interface -// calls) is jitted and has any generic dictionary information we can determine at jit time prepopulated. This includes implicit -// cctor invocations. If this method is called at ngen time we will attach extra fixup information to the affected method to ensure -// that fixing up the root method of the graph will cause all methods in the graph to be fixed up at that point also. -// -// Some generic dictionary entries may not be prepopulated if unbound type variables exist at the root of the call tree. Such cases -// will be ignored (as for the virtual/interface dispatch case we assume the caller will use an out-of-band mechanism to pre-prepare -// these entries explicitly). -bool MethodCallGraphPreparer::Run() -{ - STANDARD_VM_CONTRACT; - - // Avoid recursion while jitting methods for another preparation. - if (!m_pThread->GetCerPreparationState()->CanPreparationProceed(m_pRootMD, m_pRootTypeContext)) - return TRUE; // Assume the worst - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - // Determine if we're being called in order to provide an ngen image. This impacts whether we actually prepare methods and the - // type of tracking data we produce. Ideally we'd call GetAppDomain()->IsCompilationDomain() here, but we have to deal with the - // problem of ngen'ing mscorlib. Mscorlib code is always shared and some of it is run before the compilation domain is fully - // created (so we'd end up with some methods being prepared without saving any ngen metadata to that effect). So instead we - // check to see whether this is an ngen process. This will catch those first few mscorlib methods. - m_fNgen = IsCompilationProcess() != FALSE; - - // We keep a hash table of CERs we've processed on the module object of the root method. See if any work has been done on this - // CER before. We store different data for ngen and non-ngen cases. - if (m_fNgen) { - - // Pretty simple in ngen case -- if we've stored a context record for this method at all then we've already processed it. - if (m_pRootMD->GetModule()->IsNgenCerRootMethod(m_pRootMD)) { - m_pCerPrepInfo = m_pRootMD->GetModule()->GetCerPrepInfo(m_pRootMD); - - // We always store CerPrepInfo if the method has calls, so if we haven't stored - // anything then we know it doesn't have any calls - return (m_pCerPrepInfo && m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer); - } - } else -#endif - { - // The non-ngen case (normal jit, call to PrepareMethod etc). - m_pCerPrepInfo = m_pRootMD->GetModule()->GetCerPrepInfo(m_pRootMD); - if (m_pCerPrepInfo) { - - // Check for the "everything's done" case. - if (m_pCerPrepInfo->m_fFullyPrepared) - return m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer; - - // Check for the "we can't do anything" case (see below for descriptions of that). - if (m_pCerPrepInfo->m_fRequiresInstantiation && !m_fExactTypeContext) - return m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer; - - // Check for the "need to prepare per-instantiation, but we've already done this one" case. - if (m_pCerPrepInfo->m_fRequiresInstantiation) { - HashDatum sDatum; - if (m_pCerPrepInfo->m_sIsInitAtInstHash.GetValue(m_pRootTypeContext, &sDatum)) - return m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer; - } - } - } - - // We can't deal with generic methods or methods on generic types that may have some representative type parameters in their - // instantiation (i.e. some reference types indicated by Object rather than the exact type). The jit will tend to pass us these - // since it shares code between instantiations over reference types. We can't prepare methods like these completely -- even - // though we can jit all the method bodies the code might require generic dictionary information at the class or method level - // that is populated at runtime and can introduce failure points. So we reject such methods immediately (they will need to be - // prepared at non-jit time by an explicit call to PrepareMethod with a fully instantiated method). - // - // In the case where the type context is marked as suspect (m_fExactTypeContext == false) there are a number of possibilites for - // bad methods the jit will pass us: - // 1) We're passed a MethodDesc that shared between instantiations (bogus because exact method descs are never shared). - // 2) We're passed a MethodDesc that's an instantiating stub (bogus because non-shared methods don't need this). - // 3) We're passed a MethodDesc that has generic variables in its instantiations (I've seen this during ngen). - // - // Technically we could do a little better than this -- we could determine whether any of the representative type parameters are - // actually used within the CER call graph itself. But this would require us to understand the IL at a much deeper level (i.e. - // parse every instruction that could take a type or member spec and pull apart those specs to see if a type var is used). Plus - // we couldn't make this determination until we've prepared the entire region and the result is rather brittle from the code - // author's point of view (i.e. we might prepare a CER automatically one day but stop doing after some relatively subtle changes - // in the source code). - m_fPartialPreparation = m_pRootMD->IsSharedByGenericInstantiations() || m_pRootMD->IsInstantiatingStub() || m_pRootMD->ContainsGenericVariables(); - if (!m_fExactTypeContext && m_fPartialPreparation) { -#ifdef MDA_SUPPORTED - MDA_TRIGGER_ASSISTANT(OpenGenericCERCall, ReportViolation(m_pRootMD)); -#endif - CER_LOG(WARNINGS, ("CER: %s has open type parameters and can't be pre-prepared\n", m_pRootMD->m_pszDebugMethodName)); - - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - if (!m_fNgen) -#endif - { - // Set up a prep info structure for this method if it's not there already (the create method takes care of races). - if (m_pCerPrepInfo == NULL) - m_pCerPrepInfo = m_pRootMD->GetModule()->CreateCerPrepInfo(m_pRootMD); - - // We may be racing to update the structure at this point but that's OK since the flag we're setting is never cleared once - // it's set and is always guaranteed to be set before we rely on its value (setting it here is just a performance thing, - // letting us early-out on multiple attempts to prepare this CER from the jit). - m_pCerPrepInfo->m_fRequiresInstantiation = true; - } - - if (! g_pConfig->ProbeForStackOverflow()) - { - return FALSE; - } - m_pCerPrepInfo = m_pRootMD->GetModule()->GetCerPrepInfo(m_pRootMD); - - // We always store CerPrepInfo if the method has calls, so if we haven't stored - // anything then we know it doesn't have any calls - return (m_pCerPrepInfo && m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer); - - } - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - // If we've been called for a shared generic root method and the exact instantiation (this can happen because ngen lets code - // execute under some circumstances) we don't currently support saving this information in the ngen image (we don't have a - // format for the instantiation info). We just ignore the preparation in this case (it will be prepared at runtime). - if (m_fNgen && m_fPartialPreparation) - return TRUE; -#endif - - // Prevent inlining of the root method (otherwise it's hard to tell where ThreadAbort exceptions should be delayed). Note that - // MethodDesc::SetNotInline is thread safe. - m_pRootMD->SetNotInline(true); - - // Remember the checkpoint for all of our allocations. Keep it in a holder so they'll be unwound if we throw an exception past - // here. - CheckPointHolder sCheckpoint(m_pThread->m_MarshalAlloc.GetCheckpoint()); - - // Push the current method as the one and only method to process so far. - m_sLeftToProcess.Push(MethodContext::PerThreadAllocate(m_pRootMD, m_pRootTypeContext)); - - MethodContext *pContext = NULL; // The current MethodContext we're processing - - // Iterate until we run out of methods to process. - while ((pContext = m_sLeftToProcess.Pop()) != NULL) { - - // Restore the MD if necessary. In particular, if this is an instantiating stub and the wrapped MethodDesc could - // not be hard bound, then we'll need to restore that pointer before getting it. - pContext->m_pMethodDesc->CheckRestore(); - - // Transfer the method to the already seen stack immediately (we don't want to loop infinitely in the case of method - // recursion). - m_sAlreadySeen.Push(pContext); - - // Check if the enclosing class requires a static class constructor to be run. If so, we need to prepare that method as - // though it were any other call. - if (pContext->m_pMethodDesc->GetMethodTable()->HasClassConstructor()) { - - // Decode target method into MethodDesc and new SigTypeContext. - // The type context is easy to derive here : .cctors never have any method type parameters and the class instantiations - // are those of the method we're currently parsing, so can be simply copied down. - MethodDesc *pCctor = pContext->m_pMethodDesc->GetCanonicalMethodTable()->GetClassConstructor(); - SigTypeContext sCctorTypeContext(pCctor, pContext->m_sTypeContext.m_classInst, Instantiation()); - MethodContext *pCctorContext = MethodContext::PerThreadAllocate(pCctor, &sCctorTypeContext); - - // Only process this cctor the first time we find it in this call graph. - if (!m_sAlreadySeen.IsInStack(pCctorContext) && !m_sLeftToProcess.IsInStack(pCctorContext)) - m_sLeftToProcess.Push(pCctorContext); - } - - // Skip further processing if this method doesn't have an IL body (note that we assume the method we entered with was IL, so - // we don't need to bother with partial method processing). - if (!pContext->m_pMethodDesc->IsIL()) { - _ASSERTE(m_fEntireMethod); - continue; - } - - // Locate the IL body of the current method. May have to account for the fact that the current method desc is an - // instantiating stub and burrow down for the real method desc. - MethodDesc *pRealMethod = pContext->m_pMethodDesc; - if (pRealMethod->IsInstantiatingStub()) { - _ASSERTE(!pRealMethod->ContainsGenericVariables()); - pRealMethod = pRealMethod->GetWrappedMethodDesc(); - } - - COR_ILMETHOD* pILHeader = pRealMethod->GetILHeader(); - - // Skip malformed methods. (We should always have method with IL for well-formed images here.) - if (pILHeader == NULL) { - continue; - } - - COR_ILMETHOD_DECODER method(pILHeader); - m_pMethodDecoder = &method; - - // We want to reget the EH clauses for the current method so that we can scan its handlers - GetEHClauses(); - - LookForInterestingCallsites(pContext); - - // Whatever we've done, we're definitely not processing the top-level method at this point (so we'll be processing full - // method bodies from now on). - m_fEntireMethod = true; - } - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - if (!m_fNgen) -#endif - { - // Set up a prep info structure for this method if it's not there already (the create method takes care of races). - // This needs to happen before we start JITing the methods as part of preparation. The JIT needs to know - // about the CER root in CEEInfo::canTailCall. - if (m_pCerPrepInfo == NULL) - m_pCerPrepInfo = m_pRootMD->GetModule()->CreateCerPrepInfo(m_pRootMD); - } - - // Prevent infinite recursion by recording on the thread which roots we're currently preparing. - ThreadPreparingCerHolder sCerHolder(this); - - // Once we get here we've run out of methods to process and have recorded each method we visited in the m_sAlreadySeen stack. Now - // it's time to prepare each of these methods (jit, prepopulate generic dictionaries etc.). - PrepareMethods(); - - return RecordResults(); -} - - -// Determine whether a CER preparation for the given root method (with type context for generic instantiation -// if necessary) can go ahead given any current preparation already being performed on the current thread. -BOOL MethodCallGraphPreparer::CanPreparationProceed(MethodDesc * pMD, SigTypeContext * pTypeContext) -{ - WRAPPER_NO_CONTRACT; - MethodCallGraphPreparer * pCurrPrep = this; - while (pCurrPrep) - { - // Is the prepartion request for the root method of the current preparer? - if (pMD == pCurrPrep->m_pRootMD && SigTypeContext::Equal(pTypeContext, pCurrPrep->m_pRootTypeContext)) - { - // We're already preparing this root, return FALSE to turn the request into a no-op and avoid - // infinite recursion. - return FALSE; - } - - pCurrPrep = pCurrPrep->m_pNext; - } - - // We found no previous preparation for the same root, so the request can proceed. - return TRUE; -} - -// Methods that push and pop thread local state used to determine if a re-entrant preparation request should -// complete immediately as a no-op (because it would lead to an infinite recursion) or should proceed -// recursively. - -//static -void MethodCallGraphPreparer::BeginPrepareCerForHolder(MethodCallGraphPreparer * pPrepState) -{ - LIMITED_METHOD_CONTRACT; - - Thread * pThread = pPrepState->m_pThread; - pPrepState->m_pNext = pThread->GetCerPreparationState(); - pThread->SetCerPreparationState(pPrepState); -} - -//static -void MethodCallGraphPreparer::EndPrepareCerForHolder(MethodCallGraphPreparer * pPrepState) -{ - LIMITED_METHOD_CONTRACT; - - Thread * pThread = pPrepState->m_pThread; - _ASSERTE(pThread && pThread->GetCerPreparationState() == pPrepState); - pThread->SetCerPreparationState(pPrepState->m_pNext); -} - - -void MethodCallGraphPreparer::GetEHClauses() -{ - STANDARD_VM_CONTRACT; - - if (! ShouldGatherExplicitCERCallInfo()) - { - return; - } - - m_cEHClauses = 0; - m_pEHClauses = NULL; // we use the StackingAllocator, so don't have to delete the previous storage - - COR_ILMETHOD_SECT_EH const * pEH = m_pMethodDecoder->EH; - if (pEH == NULL ||pEH->EHCount() == 0) - { - return; - } - - m_cEHClauses = pEH->EHCount(); - m_pEHClauses = new (&m_pThread->m_MarshalAlloc) EHClauseRange[m_cEHClauses]; - - for (DWORD i = 0; i < m_cEHClauses; i++) - { - COR_ILMETHOD_SECT_EH_CLAUSE_FAT sEHClauseBuffer; - const COR_ILMETHOD_SECT_EH_CLAUSE_FAT *pEHClause; - - pEHClause = (COR_ILMETHOD_SECT_EH_CLAUSE_FAT*)pEH->EHClause(i, &sEHClauseBuffer); - - // The algorithm below assumes handlers are located after their associated try blocks. If this turns out to be a - // false assumption we need to move to a two pass technique (or defer callsite handling in some other fashion until - // we've scanned the IL for all calls to our preparation marker method). - if (!(pEHClause->GetTryOffset() < pEHClause->GetHandlerOffset())) - { - COMPlusThrowHR(COR_E_NOTSUPPORTED, IDS_EE_NOTSUPPORTED_CATCHBEFORETRY); - } - - m_pEHClauses[i].m_dwTryOffset = pEHClause->GetTryOffset(); - m_pEHClauses[i].m_dwHandlerOffset = pEHClause->GetHandlerOffset(); - m_pEHClauses[i].m_dwHandlerLength = pEHClause->GetHandlerLength(); - m_pEHClauses[i].m_fActive = false; - - //printf("Try: %u Handler: %u -> %u\n", pEHClause->GetTryOffset(), pEHClause->GetHandlerOffset(), pEHClause->GetHandlerOffset() + pEHClause->GetHandlerLength() - 1); - } - -} - -void MethodCallGraphPreparer::MarkEHClauseActivatedByCERCall(MethodContext *pContext, BYTE *pbIL, DWORD cbIL) -{ - STANDARD_VM_CONTRACT; - - DWORD dwOffset = (DWORD)(SIZE_T)((pbIL + ArgBytes_InlineTok) - (BYTE*)m_pMethodDecoder->Code); - - // Additionally we need to cope with the fact that VB and C# (for debug builds) can generate NOP instructions - // between the PCR call and the beginning of the try block. So we're potentially looking for the - // intersection of the try with a range of instructions. Count the number of consecutive NOP instructions - // which follow the call. - DWORD dwLength = 0; - BYTE *pbTmpIL = pbIL + ArgBytes_InlineTok; - while (pbTmpIL < (pbIL + cbIL) && *pbTmpIL++ == CEE_NOP) - { - dwLength++; - } - - bool fMatched = false; - for (DWORD i = 0; i < m_cEHClauses; i++) - { - if (m_pEHClauses[i].m_dwTryOffset >= dwOffset && - m_pEHClauses[i].m_dwTryOffset <= (dwOffset + dwLength)) - { - fMatched = true; - m_pEHClauses[i].m_fActive = true; - } - } - if (!fMatched) - { -#if defined(_DEBUG) || defined(MDA_SUPPORTED) - DWORD dwPCROffset = (DWORD)(SIZE_T)((pbIL - 1) - (BYTE*)m_pMethodDecoder->Code); -#endif // defined(_DEBUG) || defined(MDA_SUPPORTED) -#ifdef MDA_SUPPORTED - MDA_TRIGGER_ASSISTANT(IllegalPrepareConstrainedRegion, ReportViolation(pContext->m_pMethodDesc, dwPCROffset)); -#endif - CER_LOG(WARNINGS, ("CER: %s: Invalid call to PrepareConstrainedRegions() at IL +%04X\n", - pContext->m_pMethodDesc->m_pszDebugMethodName, dwPCROffset)); - } -} - -bool MethodCallGraphPreparer::CheckIfCallsiteWithinCER(DWORD dwOffset) -{ - STANDARD_VM_CONTRACT; - - //printf("Found: %s at %u\n", pCallTarget->m_pMethodDesc->m_pszDebugMethodName, dwOffset); - - // Search all the EH regions we know about. - for (DWORD i = 0; i < m_cEHClauses; i++) - { - bool fCallsiteWithinCER = false; - if (! m_pEHClauses[i].m_fActive) - { - // clause not CER-active so skip it - continue; - } - if (dwOffset >= (m_pEHClauses[i].m_dwHandlerOffset + m_pEHClauses[i].m_dwHandlerLength)) - { - // offset beyond clause, so skip it - continue; - } - if (dwOffset >= m_pEHClauses[i].m_dwTryOffset) - { - // For stack probing optimization, we care if either the try or the handler has a call. If neither - // does, then we can optimize the probe out. - m_fMethodHasCallsWithinExplicitCer = true; - if (dwOffset >= m_pEHClauses[i].m_dwHandlerOffset) - { - fCallsiteWithinCER = true; - } - } - // Only terminate if we got a positive result (i.e. the calliste is within a hardened clause). - // We can't terminate early in the negative case because the callsite could be nested - // in another EH region which may be hardened. - if (fCallsiteWithinCER == true) - { - return true; - } - } - - return false; -} - - -// Iterate through the body of the method looking for interesting call sites. -void MethodCallGraphPreparer::LookForInterestingCallsites(MethodContext *pContext) -{ - STANDARD_VM_CONTRACT; - - BYTE *pbIL = (BYTE*)m_pMethodDecoder->Code; - DWORD cbIL = m_pMethodDecoder->GetCodeSize(); - - while (cbIL) { - - // Read the IL op. - DWORD dwOp = *pbIL++; cbIL--; - - // Handle prefix codes (only CEE_PREFIX1 is legal so far). - if (dwOp == CEE_PREFIX1) { - if (!cbIL) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - dwOp = 256 + *pbIL++; cbIL--; - } else if (dwOp >= CEE_PREFIX7) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - - // We're interested in NEWOBJ, JMP, CALL and CALLVIRT (can't trace through CALLI). We include CALLVIRT becase C# - // routinely calls non-virtual instance methods this way in order to get this pointer null checking. We prepare NEWOBJ - // because that covers the corner case of value types which can be constructed with no failure path. - if (dwOp == CEE_CALL || dwOp == CEE_CALLVIRT || dwOp == CEE_NEWOBJ || dwOp == CEE_JMP) { - - if (cbIL < sizeof(DWORD)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - - // Decode target method into MethodDesc and new SigTypeContext. - mdToken tkCallTarget = (mdToken)GET_UNALIGNED_VAL32(pbIL); - MethodContext *pCallTarget = TokenToMethodDesc(pContext->m_pMethodDesc->GetModule(), tkCallTarget, &pContext->m_sTypeContext); - - // Check whether we've found a call to our own preparation marker method. - if (pCallTarget->m_pMethodDesc == g_pPrepareConstrainedRegionsMethod) { - - if (ShouldGatherExplicitCERCallInfo()) { - // If we're preparing a partial method these callsites are significant (we mark which EH clauses are now - // 'activated' by proximity to this marker method call). Look for EH regions that are 'activated' by the call to - // PrepareConstrainedRegions by comparing the IL offset of the start of the try to the offset immediately after - // the callsite (remember to account for the rest of the CALLVIRT instruction we haven't skipped yet). - MarkEHClauseActivatedByCERCall(pContext, pbIL, cbIL); - } - - // Record the fact that we found a method in the CER which is the root of a sub-CER. This is important since the - // rude thread abort protection algorithm relies on root CER methods being marked as such. - pContext->m_fRoot = true; - } - - // Determine if this was really a virtual call (we discard those since we can't reliably determine the call target). - bool fNonVirtualCall = dwOp == CEE_CALL || !pCallTarget->m_pMethodDesc->IsVirtual() || pCallTarget->m_pMethodDesc->IsFinal(); - - // When we're only processing qualified catch / finally handlers then we need to compute whether this call site - // lands in one of them. The callsite is always within a cer if we are processing the full method. - // If we have stackoverflow probing on, also call to determine if the CER try or finally makes any calls - bool fCallsiteWithinCerInThisFunction = false; - if (!m_fEntireMethod || g_pConfig->ProbeForStackOverflow()) { - DWORD dwOffset = (DWORD)(SIZE_T)((pbIL - 1) - (BYTE*)m_pMethodDecoder->Code); - fCallsiteWithinCerInThisFunction = CheckIfCallsiteWithinCER(dwOffset); - } - bool fCallsiteWithinCer = m_fEntireMethod || fCallsiteWithinCerInThisFunction; - - // Check for the presence of some sort of reliability contract (on the method, class or assembly). This will - // determine whether we log an error, ignore the method or treat it as part of the prepared call graph. - ReliabilityContractLevel eLevel = RCL_UNKNOWN; - if (fNonVirtualCall && // Ignore virtual calls - fCallsiteWithinCer && // And calls outside CERs - !m_sAlreadySeen.IsInStack(pCallTarget) && // And methods we've seen before - !m_sLeftToProcess.IsInStack(pCallTarget) && // And methods we've already queued for processing - (eLevel = CheckForReliabilityContract(pCallTarget->m_pMethodDesc)) >= RCL_PREPARE_CONTRACT) // And unreliable methods - m_sLeftToProcess.Push(pCallTarget); // Otherwise add this method to the list to process - else if (fCallsiteWithinCer) { -#if defined(_DEBUG) || defined(MDA_SUPPORTED) - DWORD dwOffset = (DWORD)(SIZE_T)((pbIL - 1) - (BYTE*)m_pMethodDecoder->Code); -#endif // defined(_DEBUG) || defined(MDA_SUPPORTED) - if (eLevel == RCL_NO_CONTRACT) { - // Method was sufficiently unreliable for us to warn interested users that something may be amiss. Do this - // through MDA logging. -#ifdef MDA_SUPPORTED - MDA_TRIGGER_ASSISTANT(InvalidCERCall, ReportViolation(pContext->m_pMethodDesc, pCallTarget->m_pMethodDesc, dwOffset)); -#endif - CER_LOG(WARNINGS, ("CER: %s +%04X -> %s: weak contract\n", pContext->ToString(), dwOffset, pCallTarget->ToString())); - } else if (!fNonVirtualCall && !m_fIgnoreVirtualCERCallMDA) { - // Warn users about virtual calls in CERs (so they can go back and consider which target methods need to be - // prepared ahead of time). -#ifdef MDA_SUPPORTED - MDA_TRIGGER_ASSISTANT(VirtualCERCall, ReportViolation(pContext->m_pMethodDesc, pCallTarget->m_pMethodDesc, dwOffset)); -#endif - CER_LOG(WARNINGS, ("CER: %s +%04X -> %s: virtual call\n", pContext->ToString(), dwOffset, pCallTarget->ToString())); - } - } - } - - // Skip the rest of the current IL instruction. Look up the table built statically at the top of this module for most - // instructions, but CEE_SWITCH requires special processing (the length of that instruction depends on a count DWORD - // embedded right after the opcode). - if (dwOp == CEE_SWITCH) { - DWORD dwTargets = GET_UNALIGNED_VAL32(pbIL); - if (dwTargets >= (MAXDWORD / sizeof(DWORD))) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // multiplication below would overflow - DWORD cbSwitch = (1 + dwTargets) * sizeof(DWORD); - if (cbIL < cbSwitch) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - pbIL += cbSwitch; - cbIL -= cbSwitch; - } else { - if (dwOp >= _countof(g_rOpArgs)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - DWORD cbOp = g_rOpArgs[dwOp]; - if (cbIL < cbOp) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - pbIL += cbOp; - cbIL -= cbOp; - } - - } // End of IL parsing loop -} - -void MethodCallGraphPreparer::PrepareMethods() -{ - STANDARD_VM_CONTRACT; - -#ifdef _DEBUG - DWORD dwCount = 0; - if (GetCerLoggingOptions()) - { - CER_LOG(PREPARE, ("---------------------------------------------------------------\n")); - SString ssMethod; - TypeString::AppendMethodInternal(ssMethod, m_pRootMD, TypeString::FormatNamespace | TypeString::FormatStubInfo); - CER_LOG(PREPARE, ("Preparing from %S\n", ssMethod.GetUnicode())); - } -#endif - - MethodContext *pContext; // The current MethodContext we're processing - - while ((pContext = m_sAlreadySeen.Pop()) != NULL) { - MethodDesc *pMD = pContext->m_pMethodDesc; - -#ifndef CROSSGEN_COMPILE - // Jitting. Don't need to do this for the ngen case. -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - if (!m_fNgen) -#endif - { - // Also skip the jit for the root method in the activated from jit case (where this would result in a recursive - // jit). We'd cope with this just fine, the main reason for this logic is to avoid unbalancing some profiler event - // counts that upset some of our test cases. This is safe in the face of multiple instantiations of the same method - // because in the jit activated case (where we're told the root type context is not exact) we early exit if the root - // method desc isn't 'unique' (i.e. independent of the type context). - if (m_fExactTypeContext || pMD != m_pRootMD) { - - // Jit the method we traced. - if (pMD->IsPointingToPrestub()) - { - pMD->EnsureActive(); - pMD->DoPrestub(NULL); - } - - // If we traced an instantiating stub we need to jit the wrapped (real) method as well. - if (pMD->IsInstantiatingStub()) { - _ASSERTE(!pMD->ContainsGenericVariables()); - MethodDesc *pRealMD = pMD->GetWrappedMethodDesc(); - if (pRealMD->IsPointingToPrestub()) - { - pMD->EnsureActive(); - pRealMD->DoPrestub(NULL); - } - } - } - - // Remember sub-CER root methods for further processing in RecordResults. We need to build CerPrepInfo structures for - // these just the same as top-level CERs since we may wander in to them by a route that doesn't include the top-level CER - // and the thread abort deflection algorithm relies on each CER root method being marked by a CerPrepInfo. Defer this - // processing to RecordResults since we aren't guaranteed to have prepared all the methods of the sub-graph at this - // point. - if (pContext->m_fRoot && pMD != m_pRootMD) - m_sPersist.Push(pContext); - } -#endif // CROSSGEN_COMPILE - - // Prepare generic dictionaries (both class and method as needed). We do this even in the ngen scenario, trying to get - // as many slots filled as possible. By the looks of it, it's possible that not all of these entries will make it across - // to runtime (the fixup code seems to give up on some of the more complex entries, not sure of the details). But we'll - // do as best we can here to hopefully minimize any real work on the other side. - - // Don't use the direct PrepopulateDictionary method on MethodTable here, it takes binding considerations into account - // (which we don't care about). - DictionaryLayout *pClassDictLayout = pMD->GetClass()->GetDictionaryLayout(); - if (pClassDictLayout) { - // Translate the representative method table we can find from our method desc into an exact instantiation using the - // type context we have. - MethodTable *pMT = TypeHandle(pMD->GetMethodTable()).Instantiate(pContext->m_sTypeContext.m_classInst).AsMethodTable(); - - pMT->GetDictionary()->PrepopulateDictionary(NULL, pMT, false); - - // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. - PrepopulateGenericHandleCache(pClassDictLayout, NULL, pMT); - } - - // Don't use the direct PrepopulateDictionary method on MethodDesc here, it appears to use a representative class - // instantiation (and we have the exact one handy). - DictionaryLayout *pMethDictLayout = pMD->GetDictionaryLayout(); - if (pMethDictLayout) { - pMD->GetMethodDictionary()->PrepopulateDictionary(pMD, NULL, false); - - // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. - PrepopulateGenericHandleCache(pMethDictLayout, pMD, NULL); - } - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - // Keep some of the method contexts around for the ngen case (the ones that might still need fixup at runtime). We'll - // write them into a persisted data structure in the next step. - // @todo: We use a horrible workaround here to get round the fact that while ngen'ing mscorlib we may prepare some of its - // methods before we've had a chance to start up the compilation domain (mscorlib code is shared and used by the ngen - // process itself). So we can't blindly call NeedsRestore() on an mscorlib method since that code asserts we're in the - // compilation domain. Instead, if we're in the ngen process and we're outside the compilation domain we're going to - // assume that the method doesn't need restoration. This only affects a handful of methods (six at last count, all to do - // with security safe handles or some CERs in remoting). - if (m_fNgen) { - if (GetAppDomain() == NULL || - !GetAppDomain()->IsCompilationDomain() || - !(GetAppDomain()->ToCompilationDomain()->canCallNeedsRestore()) || - !(GetAppDomain()->ToCompilationDomain()->GetTargetImage()->CanPrerestoreEagerBindToMethodDesc(pMD, NULL))|| - pMD->HasClassOrMethodInstantiation() || - pMD->IsNDirect() || - pMD->IsComPlusCall() || - pMD->IsFCall() || - pContext->m_fRoot) - m_sPersist.Push(pContext); - } -#endif - -#ifdef _DEBUG - CER_LOG(PREPARE, (" %s\n", pContext->ToString())); - dwCount++; -#endif - } - -#ifdef _DEBUG - CER_LOG(PREPARE, ("Prepared a total of %u methods\n", dwCount)); -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - if (m_fNgen) - CER_LOG(PREPARE, ("Saved data for %u of them in the ngen image\n", m_sPersist.GetCount())); -#endif - CER_LOG(PREPARE, ("---------------------------------------------------------------\n")); -#endif -} - -// Common code used in creating/looking up a CerPrepInfo and initializing/updating it. -void InitPrepInfo(MethodDesc *pMD, SigTypeContext *pTypeContext, bool fMethodHasCallsWithinExplicitCer) -{ - CONTRACTL { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pMD)); - } CONTRACTL_END; - - // Lookup or allocate the CerPrepInfo. - CerPrepInfo *pInfo = pMD->GetModule()->CreateCerPrepInfo(pMD); - - pInfo->m_fMethodHasCallsWithinExplicitCer = fMethodHasCallsWithinExplicitCer; - - // Work out if this was a partial preparation. - bool fPartialPreparation = pMD->IsSharedByGenericInstantiations() || - pMD->IsInstantiatingStub() || - pMD->ContainsGenericVariables(); - - // Simple case first: if this isn't a partial preparation (no pesky unbound type vars to worry about), then the method is - // now fully prepared. - if (!fPartialPreparation) { - pInfo->m_fFullyPrepared = true; - return; - } - - // Else we know we require per-instantiation initialization. We need to update a hash table to record the preparation we did - // in this case, and that requires taking a mutex. We could check that nobody beat us to it first, but that will hardly ever - // happen, so it's not really worth it. So just acquire the mutex right away. - CrstHolder sHolder(pMD->GetModule()->GetCerCrst()); - - pInfo->m_fRequiresInstantiation = true; - - // Add an entry to a hash that records which instantiations we've prep'd for (again, only if someone hasn't beaten us). - HashDatum sDatum; - if (!pInfo->m_sIsInitAtInstHash.GetValue(pTypeContext, &sDatum)) - { - pInfo->m_sIsInitAtInstHash.InsertKeyAsValue(pTypeContext); - } -} - -bool MethodCallGraphPreparer::RecordResults() -{ - STANDARD_VM_CONTRACT; - - // Preparation has been successful, record what we've done in a manner consistent with whether we're ngen'ing or running for - // real. - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION - // If we're ngen'ing an image we save our progess as a list of method contexts that might need restoration at runtime (since - // even with prejitting there are some things that need to be prepared at runtime). This list goes into a per module table (the - // module in question being that of the root method in the CER). - if (m_fNgen) { - - // We have the list of MethodContexts ready, but they're in cheap storage that will go away once we exit this method. - // Not only do we have to copy them to heap memory, but we also know exactly how many there are. So we can allocate a - // single array with a more compact form of MethodContext for each element. We allocate an extra sentinel value for the end - // of the list. This means we can store just a pointer to the list without a count (the code that accesses this list cares - // about keeping the list heads compact and densely packed and doesn't care about counting the elements in the list). - DWORD cContexts = m_sPersist.GetCount(); - LoaderHeap *pHeap = m_pRootMD->GetAssembly()->GetLowFrequencyHeap(); - MethodContextElement *pContexts = (MethodContextElement*)(void*)pHeap->AllocMem(S_SIZE_T(sizeof(MethodContextElement)) * (S_SIZE_T(cContexts) + S_SIZE_T(1))); - DWORD i = 0; - - MethodContext *pContext; // The current MethodContext we're processing - while ((pContext = m_sPersist.Pop()) != NULL) { - pContexts[i].m_pMethodDesc.SetValue(pContext->m_pMethodDesc); - - MethodTable * pExactMT = NULL; - if (!pContext->m_sTypeContext.m_classInst.IsEmpty()) - { - pExactMT = TypeHandle(pContext->m_pMethodDesc->GetMethodTable()).Instantiate(pContext->m_sTypeContext.m_classInst).AsMethodTable(); - _ASSERTE(pExactMT->HasInstantiation()); - } - else - { - _ASSERTE(!pContext->m_pMethodDesc->GetMethodTable()->HasInstantiation()); - } - pContexts[i].m_pExactMT.SetValue(pExactMT); - - i++; - - // Keep the context round for a little longer if the method in question was the root of a sub-CER. - if (pContext->m_fRoot) - m_sRootMethods.Push(pContext); - } - - // Write sentinel entry terminating list. - _ASSERTE(i == cContexts); - - // Add list representing this CER to the per-module table (keyed by root method desc). - m_pRootMD->GetModule()->AddCerListToRootTable(m_pRootMD, pContexts); - - // If this did have an call from an explicit PCER range, create a PrepInfo for it so that we can - // quickly grab that information later when we jit that method. This allows us to optimize the probe - // away if there are no calls from the PCER range. This is an issue when we've prepared a method - // as part of a CER call from another method, but haven't ngened that method yet. When we get - // around to finally ngening that method, we want to be able to optimize the CER probe out if - // we can, but don't want to reprepare the method. - if (g_pConfig->ProbeForStackOverflow() && m_fMethodHasCallsWithinExplicitCer) - { - if (m_pCerPrepInfo == NULL) - m_pCerPrepInfo = m_pRootMD->GetModule()->CreateCerPrepInfo(m_pRootMD); - m_pCerPrepInfo->m_fMethodHasCallsWithinExplicitCer = TRUE; - } - - - // We need to be careful with sub-CERs in the ngen case. In the jit case they're dealt with automatically (preparing a - // super-CER always completely prepares a sub-CER). But in the ngen case we potentially need to run further preparation - // steps at the point that a CER root is executed for the first time. If the sub-root is encountered before the super-root - // then the sub-CER won't have been prepared correctly. - // We solve this simply by recursively running this routine over the methods we noted were sub-roots earlier (this list - // doesn't include the main root). We could potentially do a little better than this given that we've calculated the - // super-graph, but this is complicated somewhat by the fact that we don't retain the graph structure (i.e. we can't extract - // sub-graphs easily) and the effort seems wasted just to avoid a little CPU time and stack space just for the ngen creation - // scenario. - while ((pContext = m_sRootMethods.Pop()) != NULL) - { - MethodCallGraphPreparer mgcp(pContext->m_pMethodDesc, &pContext->m_sTypeContext, false, false); - mgcp.Run(); - } - - return m_fMethodHasCallsWithinExplicitCer; - } -#endif // FEATURE_NATIVE_IMAGE_GENERATION - - // This is the runtime (non-ngen case). Record our progress in an info structure placed in a hash table hung off the module - // which owns the root method desc in the CER. The methods which create this info structure handle race conditions (to - // ensure we don't leak memory or data), but the updates to the info structure itself might not require any serialization - // (the values are 'latched' -- recomputation should yield the same result). The exception is any update to a more complex - // data fields (lists and hash tables) that require serialization to prevent corruption of the basic data structure. - - // Process sub-CER roots first. We need to build CerPrepInfo structures for these just as same as top-level CERs since we may - // wander in to them by a route that doesn't include the top-level CER and the thread abort deflection algorithm relies on each - // CER root method being marked by a CerPrepInfo. - MethodContext *pContext; - while ((pContext = m_sPersist.Pop()) != NULL) { - _ASSERTE(pContext->m_fRoot); - - // @todo: need to flow fMethodHasCallsWithinExplicitCer information through method contexts. For now just make a - // conservative, safe choice. - InitPrepInfo(pContext->m_pMethodDesc, &pContext->m_sTypeContext, true); - } - - // Now process the top-level CER. - InitPrepInfo(m_pRootMD, m_pRootTypeContext, m_fMethodHasCallsWithinExplicitCer); - - return m_fMethodHasCallsWithinExplicitCer; -} - -// Determines whether the given method contains a CER root that can be pre-prepared (i.e. prepared at jit time). -bool ContainsPrePreparableCerRoot(MethodDesc *pMD) -{ - CONTRACTL { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pMD)); - } CONTRACTL_END; - - // Deal with exotic cases (non-IL methods and the like). - if (!pMD->IsIL() || pMD->IsAbstract()) - return false; - - // And cases where we can't jit prepare (because the code is shared between instantiations). - if (pMD->IsSharedByGenericInstantiations() || pMD->IsInstantiatingStub() || pMD->ContainsGenericVariables()) - return false; - - // Otherwise we have a trickier calculation. We don't want to force the jit of the method at this point (may cause infinite - // recursion problems when we're called from the jit in the presence of call cycles). Instead we walk the top-level of the - // method IL using the same algorithm as PrepareMethodCallGraph. - - // Locate the IL body of the current method. May have to account for the fact that the current method desc is an - // instantiating stub and burrow down for the real method desc. - MethodDesc *pRealMethod = pMD; - if (pRealMethod->IsInstantiatingStub()) { - _ASSERTE(!pRealMethod->ContainsGenericVariables()); - pRealMethod = pRealMethod->GetWrappedMethodDesc(); - } - COR_ILMETHOD_DECODER method(pRealMethod->GetILHeader()); - BYTE *pbIL = (BYTE*)method.Code; - DWORD cbIL = method.GetCodeSize(); - - // Look for exception handling information for the method. If there isn't any then we know there can't be a CER rooted here. - COR_ILMETHOD_SECT_EH const * pEH = method.EH; - if (pEH == NULL || pEH->EHCount() == 0) - return false; - - // Iterate through the body of the method looking for interesting call sites. - while (cbIL) { - - // Read the IL op. - DWORD dwOp = *pbIL++; cbIL--; - - // Handle prefix codes (only CEE_PREFIX1 is legal so far). - if (dwOp == CEE_PREFIX1) { - if (!cbIL) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - dwOp = 256 + *pbIL++; cbIL--; - if (dwOp >= CEE_ILLEGAL) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - } else if (dwOp >= CEE_PREFIX7) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - - // We'll only ever see CALL instructions targeting PrepareConstrainedRegions (well those are the ones we're interested in - // anyway). - if (dwOp == CEE_CALL) - { - if (cbIL < sizeof(DWORD)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - if (IsPcrReference(pMD->GetModule(), (mdToken)GET_UNALIGNED_VAL32(pbIL))) - return true; - } - - // Skip the rest of the current IL instruction. Look up the table built statically at the top of this module for most - // instructions, but CEE_SWITCH requires special processing (the length of that instruction depends on a count DWORD - // embedded right after the opcode). - if (dwOp == CEE_SWITCH) { - if (cbIL < sizeof(DWORD)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - DWORD dwTargets = GET_UNALIGNED_VAL32(pbIL); - if (dwTargets >= (MAXDWORD / sizeof(DWORD))) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); // multiplication below would overflow - DWORD cbSwitch = (1 + dwTargets) * sizeof(DWORD); - if (cbIL < cbSwitch) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - pbIL += cbSwitch; - cbIL -= cbSwitch; - } else { - if (dwOp >= _countof(g_rOpArgs)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - DWORD cbOp = g_rOpArgs[dwOp]; - if (cbIL < cbOp) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - pbIL += cbOp; - cbIL -= cbOp; - } - - } // End of IL parsing loop - - // If we get here then there was no CER-root. - return false; -} - -// The name of the PrepareConstrainedRegions method, broken down into its components (the code below scans for these directly in the -// metadata). -#define PCR_METHOD "PrepareConstrainedRegions" -#define PCR_TYPE "RuntimeHelpers" -#define PCR_NAMESPACE "System.Runtime.CompilerServices" - -// Given a token and a module scoping it, determine if that token is a reference to PrepareConstrainedRegions. We want to do this -// without loading any random types since we're called in a context where type loading is prohibited. -bool IsPcrReference(Module *pModule, mdToken tkMethod) -{ - CONTRACTL { - STANDARD_VM_CHECK; - PRECONDITION(CheckPointer(pModule)); - } CONTRACTL_END; - - IMDInternalImport *pImport = pModule->GetMDImport(); - - // Validate that the token is one that we can handle. - if (!pImport->IsValidToken(tkMethod) || (TypeFromToken(tkMethod) != mdtMethodDef && - TypeFromToken(tkMethod) != mdtMethodSpec && - TypeFromToken(tkMethod) != mdtMemberRef)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_METHOD_TOKEN); - - // No reason to see a method spec for a call to something as simple as PrepareConstrainedRegions. - if (TypeFromToken(tkMethod) == mdtMethodSpec) - return false; - - // If it's a method def then the module had better be mscorlib. - if (TypeFromToken(tkMethod) == mdtMethodDef) { - if (pModule->GetAssembly()->GetManifestModule() == SystemDomain::SystemAssembly()->GetManifestModule()) - return tkMethod == g_pPrepareConstrainedRegionsMethod->GetMemberDef(); - else - return false; - } - - // That leaves the cross module reference case. - _ASSERTE(TypeFromToken(tkMethod) == mdtMemberRef); - - // First get the method name and signature and validate it. - PCCOR_SIGNATURE pSig; - DWORD cbSig; - LPCSTR szMethod; - IfFailThrow(pImport->GetNameAndSigOfMemberRef(tkMethod, &pSig, &cbSig, &szMethod)); - - { - SigParser sig(pSig, cbSig); - ULONG nCallingConvention; - ULONG nArgumentsCount; - BYTE bReturnType; - - // Signature is easy: void PCR(). - // Must be a static method signature. - if (FAILED(sig.GetCallingConvInfo(&nCallingConvention))) - return false; - if (nCallingConvention != IMAGE_CEE_CS_CALLCONV_DEFAULT) - return false; - // With no arguments. - if (FAILED(sig.GetData(&nArgumentsCount))) - return false; - if (nArgumentsCount != 0) - return false; - // And a void return type. - if (FAILED(sig.GetByte(&bReturnType))) - return false; - if (bReturnType != (BYTE)ELEMENT_TYPE_VOID) - return false; - } - - // Validate the name. - if (strcmp(szMethod, PCR_METHOD) != 0) - return false; - - // The method looks OK, move up to the type and validate that. - mdToken tkType; - IfFailThrow(pImport->GetParentOfMemberRef(tkMethod, &tkType)); - - if (!pImport->IsValidToken(tkType)) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); - - // If the parent is not a type ref then this isn't our target (we assume that mscorlib never uses a member ref to target - // PrepareConstrainedRegions, check that with the assert below, if it ever fails we need to add some additional logic below). - _ASSERTE(TypeFromToken(tkType) != mdtTypeDef || - pModule->GetAssembly()->GetManifestModule() != SystemDomain::SystemAssembly()->GetManifestModule()); - if (TypeFromToken(tkType) != mdtTypeRef) - return false; - - // Get the type name and validate it. - LPCSTR szNamespace; - LPCSTR szType; - IfFailThrow(pImport->GetNameOfTypeRef(tkType, &szNamespace, &szType)); - - if (strcmp(szType, PCR_TYPE) != 0) - return false; - if (strcmp(szNamespace, PCR_NAMESPACE) != 0) - return false; - - // Type is OK as well. Check the assembly reference. - mdToken tkScope; - IfFailThrow(pImport->GetResolutionScopeOfTypeRef(tkType, &tkScope)); - - if (TypeFromToken(tkScope) != mdtAssemblyRef) - return false; - if (!pImport->IsValidToken(tkScope)) - { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); - } - - // Fetch the name and public key or public key token. - BYTE *pbPublicKeyOrToken; - DWORD cbPublicKeyOrToken; - LPCSTR szAssembly; - DWORD dwAssemblyFlags; - IfFailThrow(pImport->GetAssemblyRefProps( - tkScope, - (const void**)&pbPublicKeyOrToken, - &cbPublicKeyOrToken, - &szAssembly, - NULL, // AssemblyMetaDataInternal: we don't care about version, culture etc. - NULL, // Hash value pointer, obsolete information - NULL, // Byte count for above - &dwAssemblyFlags)); - - // Validate the name. - if (stricmpUTF8(szAssembly, g_psBaseLibraryName) != 0) - return false; - - // And the public key or token, which ever was burned into the reference by the compiler. For mscorlib this is the ECMA key or - // token. - if (IsAfPublicKeyToken(dwAssemblyFlags)) { - if (cbPublicKeyOrToken != sizeof(g_rbNeutralPublicKeyToken) || - memcmp(pbPublicKeyOrToken, g_rbNeutralPublicKeyToken, cbPublicKeyOrToken) != 0) - return false; - } else { - if (cbPublicKeyOrToken != sizeof(g_rbNeutralPublicKey) || - memcmp(pbPublicKeyOrToken, g_rbNeutralPublicKey, cbPublicKeyOrToken) != 0) - return false; - } - - // If we get here we've finally proved the call target was indeed PrepareConstrainedRegions. Whew. - return true; -} - -// Prepares a method as a CER root. In some scenarios we set -// fIgnoreVirtualCERCallMDA=TRUE, this happens when we want to ignore firing a -// VirtualCERCall MDA because we know for sure that the virtual methods are -// already prepared. A good example of this case is preparing -// g_pExecuteBackoutCodeHelperMethod method. -void PrepareMethodDesc(MethodDesc* pMD, Instantiation classInst, Instantiation methodInst, BOOL onlyContractedMethod, BOOL fIgnoreVirtualCERCallMDA) -{ - CONTRACTL - { - THROWS; - DISABLED(GC_TRIGGERS); - MODE_ANY; - } - CONTRACTL_END; - - GCX_PREEMP(); - -#ifdef FEATURE_PREJIT - // This method may have some ngen fixup information provided, in which case we just check that it's been restored and can - // dispense with the preparation altogether. - Module *pModule = pMD->GetModule(); - if (pModule->IsNgenCerRootMethod(pMD)) - { - pMD->CheckRestore(); - pModule->RestoreCer(pMD); - return; - } -#endif - - // If we are only going to prepare contracted methods and this method does - // not have a contract then we just return. - if (onlyContractedMethod && CheckForReliabilityContract(pMD) < RCL_BASIC_CONTRACT) - { - return; - } - - SigTypeContext sTypeContext(pMD, classInst, methodInst); - MethodCallGraphPreparer mcgp(pMD, &sTypeContext, true, true, fIgnoreVirtualCERCallMDA == TRUE); - mcgp.Run(); -} - -// Prepares the critical finalizer call graph for the given object type (which -// must derive from CriticalFinalizerObject). This involves preparing at least -// the finalizer method and possibly some others (for SafeHandle and -// CriticalHandle derivations). If a module pointer is supplied then only the -// critical methods introduced in that module are prepared (this is used at -// ngen time to ensure that we're only generating ngen preparation info for the -// targetted module). -void PrepareCriticalFinalizerObject(MethodTable *pMT, Module *pModule) -{ - CONTRACTL { - THROWS; - GC_TRIGGERS; - MODE_ANY; - PRECONDITION(CheckPointer(pMT)); - } CONTRACTL_END; - - // Have we prepared this type before? - if (pMT->CriticalTypeHasBeenPrepared()) - return; - - GCX_PREEMP(); - - // Restore the method table if necessary. - pMT->CheckRestore(); - - // Determine the interesting parent class (either SafeHandle, CriticalHandle or CriticalFinalizerObject). - MethodTable *pSafeHandleClass = MscorlibBinder::GetClass(CLASS__SAFE_HANDLE); - MethodTable *pCriticalHandleClass = MscorlibBinder::GetClass(CLASS__CRITICAL_HANDLE); - MethodTable *pParent = pMT; - while (pParent) { - if (pParent == g_pCriticalFinalizerObjectClass || - pParent == pSafeHandleClass || - pParent == pCriticalHandleClass) { - break; - } - pParent = pParent->GetParentMethodTable(); - } - _ASSERTE(pParent != NULL); - - BinderMethodID rgMethods[5]; - int nMethods; - - // Prepare the method or methods based on the parent class. - if (pParent == pSafeHandleClass) { - rgMethods[0] = METHOD__CRITICAL_FINALIZER_OBJECT__FINALIZE; - rgMethods[1] = METHOD__SAFE_HANDLE__RELEASE_HANDLE; - rgMethods[2] = METHOD__SAFE_HANDLE__GET_IS_INVALID; - rgMethods[3] = METHOD__SAFE_HANDLE__DISPOSE; - rgMethods[4] = METHOD__SAFE_HANDLE__DISPOSE_BOOL; - nMethods = 5; - } else if (pParent == pCriticalHandleClass) { - rgMethods[0] = METHOD__CRITICAL_FINALIZER_OBJECT__FINALIZE; - rgMethods[1] = METHOD__CRITICAL_HANDLE__RELEASE_HANDLE; - rgMethods[2] = METHOD__CRITICAL_HANDLE__GET_IS_INVALID; - rgMethods[3] = METHOD__CRITICAL_HANDLE__DISPOSE; - rgMethods[4] = METHOD__CRITICAL_HANDLE__DISPOSE_BOOL; - nMethods = 5; - } else { - _ASSERTE(pParent == g_pCriticalFinalizerObjectClass); - rgMethods[0] = METHOD__CRITICAL_FINALIZER_OBJECT__FINALIZE; - nMethods = 1; - } - - for (int iMethod = 0; iMethod < nMethods; iMethod++) - { - // Prepare a (possibly virtual) method on an instance. The method is identified via a binder ID, so the initial - // declaration of the method must reside within mscorlib. We might have ngen fixup information for the method and can avoid direct - // preparation as well. - - MethodDesc *pPrepMethod = pMT->GetMethodDescForSlot(MscorlibBinder::GetMethod(rgMethods[iMethod])->GetSlot()); -#ifdef FEATURE_PREJIT - if (pPrepMethod->GetModule()->IsNgenCerRootMethod(pPrepMethod)) { - pPrepMethod->GetModule()->RestoreCer(pPrepMethod); - } - else - if (IsCompilationProcess() && pPrepMethod->IsAbstract()) { - // Skip abstract methods during NGen (we should not ever get abstract methods here at runtime) - } - else -#endif - { - if (pModule == NULL || pPrepMethod->GetModule() == pModule) { - SigTypeContext _sTypeContext(pPrepMethod, TypeHandle(pMT)); - MethodCallGraphPreparer mcgp(pPrepMethod, &_sTypeContext, true, true); - mcgp.Run(); - } - } - } - - // Note the fact that we've prepared this type before to prevent repetition of the work above. (Though repetition is harmless in - // all other respects, so there's no need to worry about the race setting this flag). - pMT->SetCriticalTypeHasBeenPrepared(); -} - -#ifdef _DEBUG - -static const char * const g_rszContractNames[] = { "RCL_NO_CONTRACT", "RCL_BASIC_CONTRACT", "RCL_PREPARE_CONTRACT" }; -static DWORD g_dwContractChecks = 0; - -#define ReturnContractLevel(_level) do { \ - g_dwContractChecks++; \ - if ((g_dwContractChecks % 100) == 0 && g_pMethodContractCache) \ - g_pMethodContractCache->DbgDumpStats(); \ - ReliabilityContractLevel __level = (_level); \ - CER_LOG(CONTRACTS, ("%s -- %s\n", pMD->m_pszDebugMethodName, g_rszContractNames[__level])); \ - return __level; \ -} while (false) -#else -#define ReturnContractLevel(_level) return (_level) -#endif - -// Look for reliability contracts at the method, class and assembly level and parse them to extract the information we're interested -// in from a runtime preparation viewpoint. This information is abstracted in the form of the ReliabilityContractLevel enumeration. -ReliabilityContractLevel CheckForReliabilityContract(MethodDesc *pMD) -{ - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(CheckPointer(pMD)); - } CONTRACTL_END; - - // We are attempting to abstract reliability contracts for the given method into three different buckets: those methods that - // will cause an error (or a MDA report at least) during preparation (RCL_NO_CONTRACT), those we allow but don't prepare - // (RCL_BASIC_CONTRACT) and those we allow and prepare (RCL_PREPARE_CONTRACT). - // - // We place methods into the first bucket below that matches: - // RCL_NO_CONTRACT -- Methods with no consistency or whose consistency states they may corrupt the appdomain or process. - // RCL_BASIC_CONTRACT -- Methods that state CER.None (or don't specify a CER attribute) - // RCL_PREPARE_CONTRACT -- Methods that state CER.MayFail or CER.Success - // - // We look for reliability contracts at three levels: method, class and assembly. Definitions found at the method level override - // those at the class and assembly level and those at the class level override assembly settings. - // - // In the interests of efficiency we cache contract information in a number of ways. Firstly we look at a hash of recently - // queried MethodDescs. This contains authoritative answers (assembly/class/method information has already been composed so on a - // hit we don't need to look anywhere else). This cache is allocated lazily, never grows (newer items eventually displace older - // ones), is global, requires no locks and is never freed. The idea is to limit the amount of working set we ever occupy while - // keeping the CPU usage as low as possible. Typical usages of this method involve querying a small number of methods in a stack - // walk, possibly multiple times, so a small hash cache should work reasonably well here. - // - // On a miss we're going to have to bite the bullet and look at the assembly, class and method. The assembly and class cache - // this information at load (ngen) time though, so they're not so expensive (class level data is cached on the EEClass, so it's - // cold data, but the most performance sensitive scenario in which we're called here, ThreadAbort, isn't all that hot). - - // Check the cache first, it contains a raw contract level. - ReliabilityContractLevel eLevel; - if (g_pMethodContractCache && g_pMethodContractCache->Lookup(pMD, (DWORD*)&eLevel)) - ReturnContractLevel(eLevel); - - // Start at the method level and work up until we've found enough information to make a decision. The contract level is composed - // in an encoded DWORD form that lets us track both parts of the state (consistency and cer) and whether each has been supplied - // yet. See the RC_* macros for encoding details. - DWORD dwMethodContractInfo = GetReliabilityContract(pMD->GetMDImport(), pMD->GetMemberDef()); - if (RC_INCOMPLETE(dwMethodContractInfo)) { - DWORD dwClassContractInfo = pMD->GetClass()->GetReliabilityContract(); - dwMethodContractInfo = RC_MERGE(dwMethodContractInfo, dwClassContractInfo); - if (RC_INCOMPLETE(dwMethodContractInfo)) { - DWORD dwAssemblyContractInfo = pMD->GetModule()->GetReliabilityContract(); - dwMethodContractInfo = RC_MERGE(dwMethodContractInfo, dwAssemblyContractInfo); - } - } - - // We've got an answer, so attempt to cache it for the next time. - - // First check we have a cache (we allocate it lazily). - if (g_pMethodContractCache == NULL) { - PtrHashCache *pCache = new (nothrow) PtrHashCache(); - if (pCache) - if (FastInterlockCompareExchangePointer(&g_pMethodContractCache, pCache, NULL) != NULL) - delete pCache; - } - - // We still might not have a cache in low memory situations. That's OK. - if (g_pMethodContractCache) - g_pMethodContractCache->Add(pMD, RC_ENCODED_TO_LEVEL(dwMethodContractInfo)); - - ReturnContractLevel(RC_ENCODED_TO_LEVEL(dwMethodContractInfo)); -} - - -// Macro used to handle failures in the routine below. -#define IfFailRetRcNull(_hr) do { if (FAILED(_hr)) return RC_NULL; } while (false) - -// Look for a reliability contract attached to the given metadata token in the given scope. Return the result as an encoded DWORD -// (see the RC_ENCODE macro). -DWORD GetReliabilityContract(IMDInternalImport *pImport, mdToken tkParent) -{ - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(CheckPointer(pImport)); - } CONTRACTL_END; - - HRESULT hr; - DWORD dwResult = RC_NULL; - - // Sadly we only have two unmanaged APIs available to us for looking at custom attributes. One looks up attributes by name but - // only returns the byte blob, not the attribute ctor information (which we need to parse the blob) while the other returns - // everything but requires us to enumerate all attributes on a given token looking for the one we're interested in. To keep the - // cost down we probe for the existence of the attribute using the first API and then use the enumeration method if we get a - // hit. - hr = pImport->GetCustomAttributeByName(tkParent, RELIABILITY_CONTRACT_NAME, NULL, NULL); - if (hr == S_FALSE) - return RC_NULL; - - IfFailRetRcNull(hr); - - // Got at least one contract against this parent. Enumerate them all (filtering by name). - MDEnumHolder hEnum(pImport); - hr = pImport->SafeAndSlowEnumCustomAttributeByNameInit(tkParent, RELIABILITY_CONTRACT_NAME, &hEnum); - _ASSERTE(hr != S_FALSE); - IfFailRetRcNull(hr); - - // Enumerate over all the contracts. - mdToken tkContract; - while (S_OK == pImport->SafeAndSlowEnumCustomAttributeByNameNext(tkParent, RELIABILITY_CONTRACT_NAME, &hEnum, &tkContract)) { - - // Get the attribute type (token of the ctor used) since we need this information in order to parse the blob we'll find - // next. - mdToken tkAttrType; - IfFailRetRcNull(pImport->GetCustomAttributeProps(tkContract, &tkAttrType)); - if (!pImport->IsValidToken(tkAttrType)) - continue; - - // The token should be a member ref or method def. - // Get the signature of the ctor so we know which version has been called. - PCCOR_SIGNATURE pSig; - DWORD cbSig; - LPCSTR szName_Ignore; - if (TypeFromToken(tkAttrType) == mdtMemberRef) - { - IfFailRetRcNull(pImport->GetNameAndSigOfMemberRef(tkAttrType, &pSig, &cbSig, &szName_Ignore)); - } - else - { - if (TypeFromToken(tkAttrType) != mdtMethodDef) - continue; - IfFailRetRcNull(pImport->GetNameAndSigOfMethodDef(tkAttrType, &pSig, &cbSig, &szName_Ignore)); - } - - // Only two signatures are supported: the null sig '()' and the full sig '(Consistency, CER)'. - // Set a boolean based on which one was provided. - bool fNullCtor; - ULONG eCallConv; - - SigPointer sig(pSig, cbSig); - - // Check the calling convention is what we expect (default convention on an instance method). - IfFailRetRcNull(sig.GetCallingConvInfo(&eCallConv)); - _ASSERTE(eCallConv == (IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS)); - if (eCallConv != (IMAGE_CEE_CS_CALLCONV_DEFAULT | IMAGE_CEE_CS_CALLCONV_HASTHIS)) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - - // If so, the next datum is the count of arguments, and this is all we need to determine which ctor has been used. - ULONG dwArgs; - IfFailRetRcNull(sig.GetData(&dwArgs)); - _ASSERTE(dwArgs == 0 || dwArgs == 2); - if (dwArgs != 0 && dwArgs != 2) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - - fNullCtor = dwArgs == 0; - - // Now we know how to parse the blob, let's fetch a pointer to it. - BYTE const *pbData; - DWORD cbData; - IfFailRetRcNull(pImport->GetCustomAttributeAsBlob(tkContract, (const void **)&pbData, &cbData)); - - // Check serialization format (we support version 1 only). - if (cbData < sizeof(WORD) || GET_UNALIGNED_VAL16(pbData) != 1) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - pbData += sizeof(WORD); - cbData -= sizeof(WORD); - - // Parse ctor arguments if we have any. - if (!fNullCtor) { - - // We assume the enums are based on DWORDS. - if (cbData < (sizeof(DWORD) * 2)) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - - // Consistency first. - DWORD dwConsistency = GET_UNALIGNED_VAL32(pbData); - pbData += sizeof(DWORD); - cbData -= sizeof(DWORD); - if (dwConsistency > RC_CONSISTENCY_CORRUPT_NOTHING) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - - // Followed by Cer. - DWORD dwCer = GET_UNALIGNED_VAL32(pbData); - pbData += sizeof(DWORD); - cbData -= sizeof(DWORD); - if (dwCer > RC_CER_SUCCESS) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - - dwResult = RC_MERGE(dwResult, RC_ENCODE(dwConsistency, dwCer)); - } - - // Get the count of field/property, value pairs. - if (cbData < sizeof(WORD)) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - WORD cPairs = GET_UNALIGNED_VAL16(pbData); - pbData += sizeof(WORD); - cbData -= sizeof(WORD); - - // Iterate over any such pairs, looking for values we haven't picked up yet. - for (DWORD i = 0 ; i < cPairs; i++) { - - // First is a field vs property selector. We expect only properties. - if (cbData < sizeof(BYTE) || *(BYTE*)pbData != SERIALIZATION_TYPE_PROPERTY) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - pbData += sizeof(BYTE); - cbData -= sizeof(BYTE); - - // Next is the type of the property. It had better be an enum. - if (cbData < sizeof(BYTE) || *(BYTE*)pbData != SERIALIZATION_TYPE_ENUM) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - pbData += sizeof(BYTE); - cbData -= sizeof(BYTE); - - // Next we have the assembly qualified enum type name. This is preceded by a metadata style packed byte length (the - // string itself is 8-bit and not null terminated). Ignore it (just skip across) and we'll key off the property name - // (coming up) instead. - DWORD cbName; - BYTE const * pbPostEncodedLength; - IfFailRetRcNull(CPackedLen::SafeGetData(pbData, cbData, &cbName, &pbPostEncodedLength)); - DWORD cbEncodedLength = static_cast(pbPostEncodedLength - pbData); - pbData += cbEncodedLength + cbName; - cbData -= cbEncodedLength + cbName; - - // Now we have the name of the property (in a similar format to above). - IfFailRetRcNull(CPackedLen::SafeGetData(pbData, cbData, &cbName, &pbPostEncodedLength)); - cbEncodedLength = static_cast(pbPostEncodedLength - pbData); - pbData += cbEncodedLength; - cbData -= cbEncodedLength; - - bool fConsistencyProp = false; - if (cbName == strlen(RC_CONSISTENCY_PROP_NAME) && strncmp((const char*)pbData, RC_CONSISTENCY_PROP_NAME, cbName) == 0) - fConsistencyProp = true; - else if (cbName == strlen(RC_CER_PROP_NAME) && strncmp((const char*)pbData, RC_CER_PROP_NAME, cbName) == 0) - fConsistencyProp = false; - else - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - pbData += cbName; - cbData -= cbName; - - // And finally the actual enum value (again, we assume the underlying type is a DWORD). - if (cbData < sizeof(DWORD)) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - DWORD dwValue = GET_UNALIGNED_VAL32(pbData); - pbData += sizeof(DWORD); - cbData -= sizeof(DWORD); - - if (fConsistencyProp) { - if (dwValue > RC_CONSISTENCY_CORRUPT_NOTHING) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - dwResult = RC_MERGE(dwResult, RC_ENCODE(dwValue, RC_CER_UNDEFINED)); - } else { - if (dwValue > RC_CER_SUCCESS) - IfFailRetRcNull(COR_E_BADIMAGEFORMAT); - dwResult = RC_MERGE(dwResult, RC_ENCODE(RC_CONSISTENCY_UNDEFINED, dwValue)); - } - } - - // Shouldn't have any bytes left in the blob at this stage. - _ASSERTE(cbData == 0); - } - - // Return the result encoded and packed into the 2 low order bits of a DWORD. - return dwResult; -} - -// Given a metadata token, a scoping module and a type context, look up the appropriate MethodDesc (and recomputed accompanying type -// context). -MethodContext *TokenToMethodDesc(Module *pModule, mdToken tokMethod, SigTypeContext *pTypeContext) -{ - STANDARD_VM_CONTRACT; - - // Validate that the token is one that we can handle. - if (!pModule->GetMDImport()->IsValidToken(tokMethod) || (TypeFromToken(tokMethod) != mdtMethodDef && - TypeFromToken(tokMethod) != mdtMethodSpec && - TypeFromToken(tokMethod) != mdtMemberRef)) { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_METHOD_TOKEN); - } - - // Look up the MethodDesc based on the token and type context. - MethodDesc *pMD = MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec(pModule, - tokMethod, - pTypeContext, - TRUE, - FALSE); - - // The MethodDesc we get might be shared between several types. If so we'll need to do extra work to locate the exact - // class instantiation instead of the default representative one. - SigTypeContext sNewTypeContext; - if (pMD->IsSharedByGenericInstantiations()) { - TypeHandle th = GetTypeFromMemberDefOrRefOrSpecThrowing(pModule, - tokMethod, - pTypeContext); - SigTypeContext::InitTypeContext(pMD, th,&sNewTypeContext); - } else - SigTypeContext::InitTypeContext(pMD, pMD->GetClassInstantiation(), pMD->GetMethodInstantiation(),&sNewTypeContext); - - return MethodContext::PerThreadAllocate(pMD, &sNewTypeContext); -} - -// Locate an exact type definition given a method token and the type context in which it can be resolved. -TypeHandle GetTypeFromMemberDefOrRefOrSpecThrowing(Module *pModule, - mdToken tokMethod, - SigTypeContext *pTypeContext) -{ - STANDARD_VM_CONTRACT; - - IMDInternalImport *pImport = pModule->GetMDImport(); - - // Convert method specs into the underlying member ref. - if (TypeFromToken(tokMethod) == mdtMethodSpec) - { - PCCOR_SIGNATURE pSig; - ULONG cSig; - mdMemberRef tkGenericMemberRef; - - IfFailThrow(pImport->GetMethodSpecProps(tokMethod, &tkGenericMemberRef, &pSig, &cSig)); - - if (!pImport->IsValidToken(tkGenericMemberRef) || - (TypeFromToken(tkGenericMemberRef) != mdtMethodDef && - TypeFromToken(tkGenericMemberRef) != mdtMemberRef)) - { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN_TYPE); - } - - tokMethod = tkGenericMemberRef; - } - - // Follow the member ref/def back up to the type def/ref/spec or module (for global methods). - if (TypeFromToken(tokMethod) == mdtMemberRef) - { - IfFailThrow(pImport->GetParentOfMemberRef(tokMethod, &tokMethod)); - - // For varargs, a memberref can point to a methodDef - if (TypeFromToken(tokMethod) == mdtMethodDef) - { - if (!pImport->IsValidToken(tokMethod)) - { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); - } - IfFailThrow(pImport->GetParentToken(tokMethod, &tokMethod)); - } - } - else if (TypeFromToken(tokMethod) == mdtMethodDef) - { - IfFailThrow(pImport->GetParentToken(tokMethod, &tokMethod)); - } - - if (!pImport->IsValidToken(tokMethod) || (TypeFromToken(tokMethod) != mdtTypeDef && - TypeFromToken(tokMethod) != mdtTypeRef && - TypeFromToken(tokMethod) != mdtTypeSpec && - TypeFromToken(tokMethod) != mdtModuleRef)) - { - COMPlusThrowHR(COR_E_BADIMAGEFORMAT, BFA_INVALID_TOKEN); - } - - // Load the type in question, using a type context if necessary to get an exact representation. - TypeHandle th; - if (TypeFromToken(tokMethod) == mdtModuleRef) { - DomainFile *pNewModule = pModule->LoadModule(GetAppDomain(), tokMethod, FALSE); - if (pNewModule != NULL) - th = TypeHandle(pNewModule->GetModule()->GetGlobalMethodTable()); - } else { - th = ClassLoader::LoadTypeDefOrRefOrSpecThrowing(pModule, - tokMethod, - pTypeContext); - } - - if (th.IsNull()) - COMPlusThrowHR(COR_E_BADIMAGEFORMAT); - - return th; -} - -// Determine whether the method given as a parameter is the root of a CER. -// @todo: Need an x86 offset as well and logic to determine whether we're actually in a root-CER portion of the method (if the whole -// thing isn't the root). -bool IsCerRootMethod(MethodDesc *pMD) -{ - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(CheckPointer(pMD)); - SO_TOLERANT; - } CONTRACTL_END; - - // Treat IL stubs as CER roots (marshaling code needs to string together operations without being interruped by thread aborts). - if (pMD->IsILStub()) - return true; - - // There are some well defined root methods defined by the system. - if (pMD == g_pExecuteBackoutCodeHelperMethod) - return true; - - // For now we just look to see whether there is some prep or fixup info stored for this method. - Module *pModule = pMD->GetModule(); - - if (pModule->GetCerPrepInfo(pMD) != NULL) - return true; - -#ifdef FEATURE_PREJIT - if (pModule->IsNgenCerRootMethod(pMD)) - return true; -#endif - - return false; -} - -// Fill the cache of overflowed generic dictionary entries that the jit maintains with all the overflow slots stored so far in the -// dictionary layout. -void PrepopulateGenericHandleCache(DictionaryLayout *pDictionaryLayout, - MethodDesc *pMD, - MethodTable *pMT) -{ - CONTRACTL { - THROWS; - GC_TRIGGERS; - MODE_ANY; - } CONTRACTL_END; - - // Dictionary overflow entries are recorded starting in the second bucket of the dictionary layout. - DictionaryLayout *pOverflows = pDictionaryLayout->GetNextLayout(); - - while (pOverflows) { - for (DWORD i = 0; i < pOverflows->GetMaxSlots(); i++) { - DictionaryEntryLayout *pEntry = pOverflows->GetEntryLayout(i); - - // We've finished as soon as we find the first unused slot. - if (!pEntry->m_signature) - return; - - // We have a valid overflow entry. Determine the handle value given the type context we have and push it into the JIT's - // cache. - JIT_GenericHandleWorker(pMD, pMT, pEntry->m_signature); - } - pOverflows = pOverflows->GetNextLayout(); - } -} - -#ifdef FEATURE_PREJIT - -// Prepare the CER rooted at the given method (it's OK to pass a MethodDesc* that doesn't root a CER, in which case the method -// is a no-op). -void CerNgenRootTable::Restore(MethodDesc *pRootMD) -{ -#ifndef CROSSGEN_COMPILE - STANDARD_VM_CONTRACT; - - // We don't have a restoration bitmap at ngen time. No matter, we just always claim everything is restored. - if (m_pRestoreBitmap == NULL) - return; - - // Locate the root index from the table. Failure indicates there's no work to do. - DWORD dwIndex = FindIndex(pRootMD); - if (dwIndex == NoSuchRoot) - return; - - _ASSERTE(m_pRoots[dwIndex].m_pRootMD == pRootMD); - - // Check then mark the fact that we're preparing (to prevent potential recursion). - SigTypeContext typeContext; - if (!GetThread()->GetCerPreparationState()->CanPreparationProceed(pRootMD, &typeContext)) - return; - - MethodCallGraphPreparer sPrep(pRootMD, &typeContext, true, true); - MethodCallGraphPreparer::ThreadPreparingCerHolder sCerHolder(&sPrep); - -#ifdef _DEBUG - if (GetCerLoggingOptions()) - { - CER_LOG(RESTORE, ("---------------------------------------------------------------\n")); - SString ssRootMethod; - TypeString::AppendMethodInternal(ssRootMethod, pRootMD, TypeString::FormatNamespace | TypeString::FormatStubInfo); - CER_LOG(RESTORE, ("Restoring CER graph from %S\n", ssRootMethod.GetUnicode())); - } -#endif - - g_IBCLogger.LogCerMethodListReadAccess(pRootMD); - - // Retrieve the CerRoot structure. - CerRoot *pRoot = &m_pRoots[dwIndex]; - _ASSERTE(pRoot->m_pRootMD == pRootMD); - - // Scan the list of methods in the CER (the last one is a sentinel with a NULL MethodDesc*). Restore each method as we go. - MethodContextElement *pEntry = pRoot->m_pList; - while (pEntry->GetMethodDesc()) - { - // Method desc and type handle pointers may still be tokenized at this point. - Module::RestoreMethodDescPointer(&pEntry->m_pMethodDesc); - Module::RestoreMethodTablePointer(&pEntry->m_pExactMT); - - g_IBCLogger.LogCerMethodListReadAccess(pEntry->GetMethodDesc()); - - MethodDesc * pMD = pEntry->GetMethodDesc(); - - // Check whether there are generic dictionaries that need to be pre-populated. - - // Don't use the direct PrepopulateDictionary method here for MethodTable/MethodDesc - // - MethodTable: Takes binding considerations into account (which we don't care about) - // - MethodDesc: Appears to use a representative class instantiation (and we have the exact one handy) - // - // Additionally, avoid touching EE Class if we don't need to - MethodTable * pMT = pEntry->GetExactMT(); - if (pMT != NULL) - { - // MethodTable - DictionaryLayout *pClassDictLayout = pMT->GetClass()->GetDictionaryLayout(); - if (pClassDictLayout) - { - pMT->GetDictionary()->PrepopulateDictionary(NULL, pMT, false); - - // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. - PrepopulateGenericHandleCache(pClassDictLayout, NULL, pMT); - } - - // MethodDesc - DictionaryLayout *pMethDictLayout = pMD->GetDictionaryLayout(); - if (pMethDictLayout) - { - pMD->GetMethodDictionary()->PrepopulateDictionary(pMD, NULL, false); - - // The dictionary may have overflowed in which case we need to prepopulate the jit's lookup cache as well. - PrepopulateGenericHandleCache(pMethDictLayout, pMD, NULL); - } - } - - // Recreate stubs used by P/Invoke, COM calls, or FCalls by exercising the prestub. - if (pMD->IsPointingToPrestub() && (pMD->IsNDirect() || pMD->IsComPlusCall() || pMD->IsFCall())) - { - pMD->EnsureActive(); - pMD->DoPrestub(NULL); - } - -#ifdef _DEBUG - if (GetCerLoggingOptions()) - { - SString ssMethod; - TypeString::AppendMethodInternal(ssMethod, pMD, TypeString::FormatNamespace | TypeString::FormatStubInfo); - CER_LOG(RESTORE, (" %S\n", ssMethod.GetUnicode())); - } -#endif - - // Move to next entry. - pEntry++; - } - - CER_LOG(RESTORE, ("---------------------------------------------------------------\n")); - - // Mark this whole CER region as fixed up by setting a flag in the restore bitmap (kept separate so we can cluster all our page - // writes). - // Compute the DWORD offset into the flag array and then the mask for the specific bit in that DWORD. - DWORD dwOffset = dwIndex / (sizeof(DWORD) * 8); - DWORD dwMask = 1 << (dwIndex % (sizeof(DWORD) * 8)); - EnsureWritablePages(m_pRestoreBitmap, sizeof(DWORD) * SizeOfRestoreBitmap()); - FastInterlockOr(&m_pRestoreBitmap[dwOffset], dwMask); - - // If we fixed up any methods with their own CERs then we will have implicitly fixed up those too. Mark their fixup records as - // completed as well to avoid further unecessary work. - pEntry = pRoot->m_pList; - while (pEntry->GetMethodDesc()) { - dwIndex = FindIndex(pEntry->GetMethodDesc()); - if (dwIndex != NoSuchRoot) { - dwOffset = dwIndex / (sizeof(DWORD) * 8); - dwMask = 1 << (dwIndex % (sizeof(DWORD) * 8)); - FastInterlockOr(&m_pRestoreBitmap[dwOffset], dwMask); - } - pEntry++; - } -#endif // CROSSGEN_COMPILE -} - -#ifdef FEATURE_NATIVE_IMAGE_GENERATION -// Add a new root to the table, expanding it as necessary. Note that this method must be called with the CerCrst already held. -void CerNgenRootTable::AddRoot(MethodDesc *pRootMD, MethodContextElement *pList) -{ - CONTRACTL { - STANDARD_VM_CHECK; - PRECONDITION(IsOwnerOfCrst(pRootMD->GetModule()->GetCerCrst())); - } CONTRACTL_END; - - // Ensure we have enough space first. - if (m_cRoots == m_cSlots) { - DWORD cNewSize = m_cSlots + 16; - CerRoot *pNewArray = new CerRoot[cNewSize]; - memcpyNoGCRefs(pNewArray, m_pRoots, m_cRoots * sizeof(CerRoot)); - MethodContextElement **pNewRootsInCompilationOrder = new MethodContextElement*[cNewSize]; - memcpyNoGCRefs(pNewRootsInCompilationOrder, m_pRootsInCompilationOrder, m_cRoots * sizeof(MethodContextElement*) ); - m_cSlots = cNewSize; - delete m_pRoots; - m_pRoots = pNewArray; - delete m_pRootsInCompilationOrder; - m_pRootsInCompilationOrder = pNewRootsInCompilationOrder; - } - - // Fill in the new entry in sorted order. - DWORD i; - for (i = 0; i < m_cRoots; i++) - if ((UPTR) m_pRoots[i].m_pRootMD > (UPTR) pRootMD) - break; - if (i < m_cRoots) - memmove(&m_pRoots[i + 1], &m_pRoots[i], (m_cRoots - i) * sizeof(CerRoot)); - m_pRoots[i].m_pRootMD = pRootMD; - m_pRoots[i].m_pList = pList; - - m_pRootsInCompilationOrder[m_cRoots] = pList; - - m_cRoots++; -} - -// Ngen callouts to help serialize this structure and its children to storage. -void CerNgenRootTable::Save(DataImage *image, CorProfileData *profileData) -{ - STANDARD_VM_CONTRACT; - -#ifdef _DEBUG - DWORD dwMaxEntries = 0; - DWORD dwTotalEntries = 0; -#endif - - image->StoreStructure(this, sizeof(CerNgenRootTable), DataImage::ITEM_CER_ROOT_TABLE); - image->StoreStructure(m_pRoots, m_cRoots * sizeof(CerRoot), DataImage::ITEM_CER_ROOT_TABLE); - - // Create a bitmap of boolean flags (1 bit per flag) indicating whether the CER at a given index in the array has been restored. - // This is initially all zero and only filled in at runtime (keep all the flags together this way because they're the only - // things we have to write at runtime and we want to keep them as dense as possible). - _ASSERTE((SizeOfRestoreBitmap() % sizeof(DWORD)) == 0); - m_pRestoreBitmap = new DWORD[SizeOfRestoreBitmap() / sizeof(DWORD)]; - memset(m_pRestoreBitmap, 0xff, SizeOfRestoreBitmap()); - - image->StoreStructure(m_pRestoreBitmap, - SizeOfRestoreBitmap(), - DataImage::ITEM_CER_RESTORE_FLAGS); - - // Next save off the list of MethodContextElements associated with each root. - for (DWORD i = 0; i < m_cRoots; i++) { - MethodContextElement *pEntry = m_pRootsInCompilationOrder[i]; - - // Count entries in list. - DWORD cEntries = 0; - while (pEntry->GetMethodDesc()) { - cEntries++; - pEntry++; - } - - // Plus one for the sentinel value. - cEntries++; - -#ifdef _DEBUG - dwTotalEntries += cEntries; - if (cEntries > dwMaxEntries) - dwMaxEntries = cEntries; -#endif - - // Store this list. - image->StoreStructure(m_pRootsInCompilationOrder[i], - cEntries * sizeof(MethodContextElement), - DataImage::ITEM_CER_METHOD_LIST); - } - -#ifdef _DEBUG - if (m_cRoots > 0) { - CER_LOG(NGEN_STATS, ("Saving %u CER roots in ngen image\n", m_cRoots)); - CER_LOG(NGEN_STATS, (" Max methods in CER: %u\n", dwMaxEntries)); - CER_LOG(NGEN_STATS, (" Avg methods in CER: %.1f\n", (float)((float)dwTotalEntries / (float)m_cRoots))); - } else - CER_LOG(NGEN_STATS, ("No CER roots in ngen image\n")); -#endif -} - -void CerNgenRootTable::Fixup(DataImage *image) -{ - STANDARD_VM_CONTRACT; - - DWORD i; - - // We still use the point to the root array even though at runtime the two structures will be adjacent. - image->FixupPointerField(this, offsetof(CerNgenRootTable, m_pRoots)); - - // Restoration flags are used only at runtime and must start off zeroed. - image->FixupPointerField(this, offsetof(CerNgenRootTable, m_pRestoreBitmap)); - image->ZeroField(m_pRestoreBitmap, 0, SizeOfRestoreBitmap()); - - // The root list in compilation order is only used at ngen time, and is not written into native image. - image->ZeroPointerField(this, offsetof(CerNgenRootTable, m_pRootsInCompilationOrder)); - - // Fixup all the pointers in the individual CERs. - for (i = 0; i < m_cRoots; i++) { - - // For each MethodContextElement in the list we need to fixup a pointer to a MethodDesc and two array pointers (one for any - // class instantiation and one for any method instantiation). The actual MethodDescs and TypeHandles themselves are already - // fixed up as are the instantiation arrays we point to (they're the ones inside the generic dictionaries of the class/method - // concerned). - MethodContextElement *pList = m_pRootsInCompilationOrder[i]; - MethodContextElement *pEntry = pList; - while (pEntry->GetMethodDesc()) { - image->FixupMethodDescPointer(pList, &pEntry->m_pMethodDesc); - image->FixupMethodTablePointer(pList, &pEntry->m_pExactMT); - pEntry++; - } - } -} - -void CerNgenRootTable::FixupRVAs(DataImage *image) -{ - STANDARD_VM_CONTRACT; - - DWORD i, j; - - // Now we go back through the root table and sort the entries based on the locations of the root method descs in the new image - // (they may be rearranged due to IBC profiling). - CerRoot *pNewRoots = (CerRoot*)image->GetImagePointer(m_pRoots); - PREFIX_ASSUME(pNewRoots != NULL); - - // Simple insertion sort. Starting at the second element insert a candidate into its correct location in the sub-list - // preceding it (which by definition will already be sorted). - for (i = 1; i < m_cRoots; i++) - { - // Look at all of the preceding elements for the first that is larger than the candidate (i.e. should succeed the - // candidate in sorted order). If we don't find one then the candidate is already in place and we can proceed to the - // next candidate. - for (j = 0; j < i; j++) - if (image->GetRVA(pNewRoots[j].m_pRootMD) > image->GetRVA(pNewRoots[i].m_pRootMD)) { - - // Need to move candidate element up. Cache its value because we're about to overwrite it. - MethodDesc *pTmpRootMD = pNewRoots[i].m_pRootMD; - MethodContextElement *pTmpList = pNewRoots[i].m_pList; - - // Shuffle the sorted list one up to make room for the candidate. - memmove(&pNewRoots[j + 1], &pNewRoots[j], (i - j) * sizeof(CerRoot)); - - // Insert the candidate into position. - pNewRoots[j].m_pRootMD = pTmpRootMD; - pNewRoots[j].m_pList = pTmpList; - - // Sorted the candidate, move onto the next. - break; - } - } - - // Fixup all the pointers in the individual CERs. - for (i = 0; i < m_cRoots; i++) { - // Fix up the pointer to the root method and the list of methods in the CER. - image->FixupField(m_pRoots, sizeof(CerRoot) * i + offsetof(CerRoot, m_pRootMD), - pNewRoots[i].m_pRootMD); - image->FixupField(m_pRoots, sizeof(CerRoot) * i + offsetof(CerRoot, m_pList), - pNewRoots[i].m_pList); - } -} -#endif // FEATURE_NATIVE_IMAGE_GENERATION - -// Locate the index of a given CerRoot record in the array given the root method. This is used to access the array and to locate the -// restored flag for the entry in the restored bitmap. NoSuchRoot is returned if the root cannot be found. -DWORD CerNgenRootTable::FindIndex(MethodDesc *pRootMD) -{ - CONTRACTL { - NOTHROW; - MODE_ANY; - GC_NOTRIGGER; - PRECONDITION(CheckPointer(pRootMD)); - SO_TOLERANT; - } CONTRACTL_END; - - // The table is guaranteed to be sorted, so we can lookup our target with a binary search. - DWORD dwLow = 0; - DWORD dwHigh = m_cRoots - 1; - while (true) { - - // Take out the simple cases first. - - // The range has only one entry. - if (dwLow == dwHigh) { - if (m_pRoots[dwLow].m_pRootMD == pRootMD) - return dwLow; -#ifdef _DEBUG - for (DWORD i = 0; i < m_cRoots; i++) - _ASSERTE(m_pRoots[i].m_pRootMD != pRootMD); -#endif - return NoSuchRoot; - } - - // The range has only two entries. - if (dwLow == dwHigh - 1) { - if (m_pRoots[dwLow].m_pRootMD == pRootMD) - return dwLow; - if (m_pRoots[dwHigh].m_pRootMD == pRootMD) - return dwHigh; -#ifdef _DEBUG - for (DWORD i = 0; i < m_cRoots; i++) - _ASSERTE(m_pRoots[i].m_pRootMD != pRootMD); -#endif - return NoSuchRoot; - } - - // Now we can compute a midpoint that is definitely distinct and in-between the endpoints. - DWORD dwMid = dwLow + ((dwHigh - dwLow) / 2); - - // Did we nail it? - if (m_pRoots[dwMid].m_pRootMD == pRootMD) - return dwMid; - - // Otherwise adjust our range to be the bit we haven't looked at and iterate. - if ((UPTR)m_pRoots[dwMid].m_pRootMD < (UPTR)pRootMD) - dwLow = dwMid + 1; - else - dwHigh = dwMid - 1; - } -} - -// Prepare the class if it is derived from CriticalFinalizerObject. This is used at ngen time since such classes are normally -// prepared at runtime (at instantiation) and would therefore miss the ngen image. -void PrepareCriticalType(MethodTable * pMT) -{ - STANDARD_VM_CONTRACT; - - // Prepare any class that satisfies the criteria. Pass a pointer to this module so that we'll only prepare any overrides of - // the critical methods that were actually introduced here. - if (pMT->HasCriticalFinalizer()) - PrepareCriticalFinalizerObject(pMT, pMT->GetLoaderModule()); -} - -// Prepare a method and its statically determinable call graph if a hint attribute has been applied. This is only called at ngen -// time to save additional preparation information into the ngen image that wouldn't normally be there (and thus lower runtime -// overheads). -void PrePrepareMethodIfNecessary(CORINFO_METHOD_HANDLE hMethod) -{ - STANDARD_VM_CONTRACT; - - EX_TRY { - - // Translate jit-style method handle into method desc. - MethodDesc *pMD = GetMethod(hMethod); - - // Check for the existance of the attribute. - IMDInternalImport *pImport = pMD->GetMDImport(); - mdToken tkMethod = pMD->GetMemberDef(); - HRESULT hr = pImport->GetCustomAttributeByName(tkMethod, - "System.Runtime.ConstrainedExecution.PrePrepareMethodAttribute", - NULL, NULL); - - // TODO: We should add IBC probes which indicate that methods need to be preprepared - // which can then be reflected in the IBC data, we can add an additional check - // here to cover that case, then we can get around this problem with profiling - // instead of manual programmer effort. - - // Only prepare if we definitely saw the attribute. - if (hr == S_OK) { - // Prepare the method and its graph. There should never be any open type parameters (we can't do much at ngen time with these), - // so we can pass a null type context. - SigTypeContext sTypeContext; - MethodCallGraphPreparer mcgp(pMD, &sTypeContext, true, true); - mcgp.Run(); - } - - } EX_CATCH { - } EX_END_CATCH(SwallowAllExceptions); -} - -#endif // FEATURE_PREJIT - -PtrHashCache::PtrHashCache() -{ - LIMITED_METHOD_CONTRACT; - ZeroMemory(this, sizeof(*this)); - - // First entry in each bucket is a chain index used to evenly distribute inserts within a bucket. - _ASSERTE(PHC_CHAIN > 1); -} - -bool PtrHashCache::Lookup(void *pKey, DWORD *pdwValue) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(((UINT_PTR)pKey & PHC_DATA_MASK) == 0); - - DWORD dwBucket = GetHash(pKey); - - // Skip first entry in bucket, it's a sequence number used for insertions. - for (DWORD i = 1; i < PHC_CHAIN; i++) { - UINT_PTR uipEntry = VolatileLoad(&m_rEntries[(dwBucket * PHC_CHAIN) + i]); - if ((uipEntry & ~PHC_DATA_MASK) == (UINT_PTR)pKey) { -#ifdef _DEBUG - FastInterlockIncrement((LONG*)&m_dwHits); -#endif - *pdwValue = uipEntry & PHC_DATA_MASK; - return true; - } - } - -#ifdef _DEBUG - FastInterlockIncrement((LONG*)&m_dwMisses); -#endif - return false; -} - -void PtrHashCache::Add(void *pKey, DWORD dwValue) -{ - LIMITED_METHOD_CONTRACT; - _ASSERTE(((UINT_PTR)pKey & PHC_DATA_MASK) == 0); - _ASSERTE((dwValue & ~PHC_DATA_MASK) == 0); - - DWORD dwBucket = GetHash(pKey); - - // We keep a sequence number in the first entry of the bucket so that we distribute insertions within the bucket evenly. We're - // racing when we update this value, but it doesn't matter if we lose an update (we're a cache after all). We don't bother being - // careful to avoid overflowing the value here (we just keep incrementing); we'll do the modulo logic when we insert our value - // instead. - DWORD dwIndex = static_cast(m_rEntries[dwBucket * PHC_CHAIN]++); - dwIndex = (dwIndex % (PHC_CHAIN - 1)) + 1; - m_rEntries[(dwBucket * PHC_CHAIN) + dwIndex] = ((UINT_PTR)pKey & ~PHC_DATA_MASK) | dwValue; -} - -DWORD PtrHashCache::GetHash(void *pKey) -{ - LIMITED_METHOD_CONTRACT; - - return (DWORD)(((UINT_PTR)pKey >> 4) % PHC_BUCKETS); -} - -#ifdef _DEBUG -void PtrHashCache::DbgDumpStats() -{ -#if 0 - if ((m_dwHits + m_dwMisses) == 0) - return; - - printf("Dumping stats for PtrHashCache %08X\n", this); - printf(" %u hits, %u misses (%u%% hit rate)\n", m_dwHits, m_dwMisses, (m_dwHits * 100) / (m_dwHits + m_dwMisses)); - for (DWORD i = 0; i < PHC_BUCKETS; i++) - printf(" [%2u] : %u insertions\n", i, m_rEntries[i * PHC_CHAIN]); - printf("\n"); -#endif -} -#endif -- 2.7.4