diff options
Diffstat (limited to 'src/vm/multicorejitplayer.cpp')
-rw-r--r-- | src/vm/multicorejitplayer.cpp | 1486 |
1 files changed, 1486 insertions, 0 deletions
diff --git a/src/vm/multicorejitplayer.cpp b/src/vm/multicorejitplayer.cpp new file mode 100644 index 0000000000..0c69fdcf94 --- /dev/null +++ b/src/vm/multicorejitplayer.cpp @@ -0,0 +1,1486 @@ +// 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. +// =========================================================================== +// File: MultiCoreJITPlayer.cpp +// + +// =========================================================================== +// This file contains the implementation for MultiCore JIT profile playing back +// =========================================================================== +// + +#include "common.h" +#include "vars.hpp" +#include "security.h" +#include "eeconfig.h" +#include "dllimport.h" +#include "comdelegate.h" +#include "dbginterface.h" +#include "listlock.inl" +#include "stubgen.h" +#include "eventtrace.h" +#include "array.h" +#include "fstream.h" +#include "hash.h" +#include "clrex.h" + +#include "appdomain.hpp" + +#include "multicorejit.h" +#include "multicorejitimpl.h" + +// Options for controlling multicore JIT + +unsigned g_MulticoreJitDelay = 0; // Delay in StartProfile + +bool g_MulticoreJitEnabled = true; // Enable/Disable feature + +/////////////////////////////////////////////////////////////////////////////////// +// +// class MulticoreJitCodeStorage +// +/////////////////////////////////////////////////////////////////////////////////// + + +void MulticoreJitCodeStorage::Init() +{ + CONTRACTL + { + THROWS; + MODE_ANY; // called from BaseDomain::Init which is MODE_ANY + } + CONTRACTL_END; + + m_nStored = 0; + m_nReturned = 0; + m_crstCodeMap.Init(CrstMulticoreJitHash); +} + + +// Destructor +MulticoreJitCodeStorage::~MulticoreJitCodeStorage() +{ + LIMITED_METHOD_CONTRACT; + + m_crstCodeMap.Destroy(); +} + + +// Callback from MakeJitWorker to store compiled code, under MethodDesc lock +void MulticoreJitCodeStorage::StoreMethodCode(MethodDesc * pMD, PCODE pCode) +{ + STANDARD_VM_CONTRACT; + +#ifdef PROFILING_SUPPORTED + if (CORProfilerTrackJITInfo()) + { + return; + } +#endif + + if (pCode != NULL) + { + CrstHolder holder(& m_crstCodeMap); + +#ifdef MULTICOREJIT_LOGGING + if (Logging2On(LF2_MULTICOREJIT, LL_INFO1000)) + { + MulticoreJitTrace(("%p %p StoredMethodCode", pMD, pCode)); + } +#endif + + PCODE code = NULL; + + if (! m_nativeCodeMap.Lookup(pMD, & code)) + { + m_nativeCodeMap.Add(pMD, pCode); + + m_nStored ++; + } + } +} + + +// Query from MakeJitWorker: Lookup stored JITted methods +PCODE MulticoreJitCodeStorage::QueryMethodCode(MethodDesc * pMethod) +{ + STANDARD_VM_CONTRACT; + + PCODE code = NULL; + + if (m_nStored > m_nReturned) // Quick check before taking lock + { + CrstHolder holder(& m_crstCodeMap); + + if (m_nativeCodeMap.Lookup(pMethod, & code)) + { + m_nReturned ++; + + // Remove it to keep storage small (hopefully flat) + m_nativeCodeMap.Remove(pMethod); + } + } + +#ifdef MULTICOREJIT_LOGGING + if (Logging2On(LF2_MULTICOREJIT, LL_INFO1000)) + { + MulticoreJitTrace(("%p %p QueryMethodCode", pMethod, code)); + } +#endif + + return code; +} + + +/////////////////////////////////////////////////////////////////////////////////// +// +// class PlayerModuleInfo +// +/////////////////////////////////////////////////////////////////////////////////// + +// Per module information kept for mapping to Module object + +class PlayerModuleInfo +{ +public: + + const ModuleRecord * m_pRecord; + Module * m_pModule; + int m_needLevel; + int m_curLevel; + bool m_enableJit; + + PlayerModuleInfo() + { + LIMITED_METHOD_CONTRACT; + + m_pRecord = NULL; + m_pModule = NULL; + m_needLevel = -1; + m_curLevel = -1; + m_enableJit = true; + } + + bool MeetLevel(FileLoadLevel level) const + { + LIMITED_METHOD_CONTRACT; + + return (m_pModule != NULL) && (m_curLevel >= (int) level); + } + + bool IsModuleLoaded() const + { + LIMITED_METHOD_CONTRACT; + + return m_pModule != NULL; + } + + bool LoadOkay() const + { + LIMITED_METHOD_CONTRACT; + + return (m_pRecord->flags & FLAG_LOADOKAY) != 0; + } + + // UpdateNeedLevel called + bool IsDependency() const + { + LIMITED_METHOD_CONTRACT; + + return m_needLevel > -1; + } + + bool IsLowerLevel() const + { + LIMITED_METHOD_CONTRACT; + + return m_curLevel < m_needLevel; + } + + // If module is loaded, lower then needed level, update its level + void UpdateCurrentLevel() + { + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + } + CONTRACTL_END; + + if (m_pModule != NULL) + { + if (m_curLevel < m_needLevel) + { + m_curLevel = (int) MulticoreJitManager::GetModuleFileLoadLevel(m_pModule); + } + } + } + + bool UpdateNeedLevel(FileLoadLevel level) + { + LIMITED_METHOD_CONTRACT; + + if (m_needLevel < (int) level) + { + m_needLevel = (int) level; + + return true; + } + + return false; + } + + bool MatchWith(ModuleVersion & version, bool & gotVersion, Module * pModule, bool & shortAbort, bool fAppx); + +#ifdef MULTICOREJIT_LOGGING + void Dump(const wchar_t * prefix, int index); +#endif + +}; + + +bool PlayerModuleInfo::MatchWith(ModuleVersion & version, bool & gotVersion, Module * pModule, bool & shortAbort, bool fAppx) +{ + STANDARD_VM_CONTRACT; + + if ((m_pModule == NULL) && m_pRecord->MatchWithModule(version, gotVersion, pModule, shortAbort, fAppx)) + { + m_pModule = pModule; + m_curLevel = (int) MulticoreJitManager::GetModuleFileLoadLevel(pModule); + + if (m_pRecord->jitMethodCount == 0) + { + m_enableJit = false; // No method to JIT for this module, not really needed; just to be correct + } + else if (CORDebuggerEnCMode(pModule->GetDebuggerInfoBits())) + { + m_enableJit = false; + MulticoreJitTrace(("Jit disable for module due to EnC")); + _FireEtwMulticoreJit(W("FILTERMETHOD-EnC"), W(""), 0, 0, 0); + } + + return true; + } + + return false; +} + + +#ifdef MULTICOREJIT_LOGGING + +void PlayerModuleInfo::Dump(const wchar_t * prefix, int index) +{ + WRAPPER_NO_CONTRACT; + +#ifdef LOGGING + if (!Logging2On(LF2_MULTICOREJIT, LL_INFO100)) + return; + + DEBUG_ONLY_FUNCTION; +#endif + + StackSString ssBuff; + + ssBuff.Append(prefix); + ssBuff.AppendPrintf(W("[%2d]: "), index); + + const ModuleVersion & ver = m_pRecord->version; + + ssBuff.AppendPrintf(W(" %d.%d.%05d.%04d.%d level %2d, need %2d"), ver.major, ver.minor, ver.build, ver.revision, ver.versionFlags, m_curLevel, m_needLevel); + + ssBuff.AppendPrintf(W(" pModule: %p "), m_pModule); + + unsigned i; + + for (i = 0; i < m_pRecord->ModuleNameLen(); i ++) + { + ssBuff.Append((wchar_t) m_pRecord->GetModuleName()[i]); + } + + while (i < 32) + { + ssBuff.Append(' '); + i ++; + } + + MulticoreJitTrace(("%S", ssBuff.GetUnicode())); +} + +#endif + + + +/////////////////////////////////////////////////////////////////////////////////// +// +// MulticoreJitProfilePlayer +// +/////////////////////////////////////////////////////////////////////////////////// + +const unsigned EmptyToken = 0xFFFFFFFF; + +bool ModuleRecord::MatchWithModule(ModuleVersion & modVersion, bool & gotVersion, Module * pModule, bool & shouldAbort, bool fAppx) const +{ + STANDARD_VM_CONTRACT; + + LPCUTF8 pModuleName = pModule->GetSimpleName(); + const char * pName = GetModuleName(); + + size_t len = strlen(pModuleName); + + if ((len == lenModuleName) && (memcmp(pModuleName, pName, lenModuleName) == 0)) + { + // Ignore version check on play back when running under Appx (also GetModuleVersion is expensive) + + // For Appx, multicore JIT profile is pre-generated by application vendor, installed together with the package. + // So it may not have exact match with its own assemblies, and assemblies installed on the system. + if (fAppx) + { + return true; + } + + if (! gotVersion) // Calling expensive GetModuleVersion only when simple name matches + { + gotVersion = true; + + if (! modVersion.GetModuleVersion(pModule)) + { + return false; + } + } + + if (version.MatchWith(modVersion)) + { + // If matching image with different native image flag is detected, mark and abort playing profile back + if (version.NativeImageFlagDiff(modVersion)) + { + MulticoreJitTrace((" Module with different native image flag: %s", pName)); + + shouldAbort = true; + } + + return true; + } + } + + return false; +} + + +MulticoreJitProfilePlayer::MulticoreJitProfilePlayer(AppDomain * pDomain, ICLRPrivBinder * pBinderContext, LONG nSession, bool fAppxMode) + : m_stats(pDomain->GetMulticoreJitManager().GetStats()), m_appdomainSession(pDomain->GetMulticoreJitManager().GetProfileSession()) +{ + LIMITED_METHOD_CONTRACT; + + m_DomainID = pDomain->GetId(); +#if defined(FEATURE_CORECLR) + m_pBinderContext = pBinderContext; +#endif + m_nMySession = nSession; + m_moduleCount = 0; + m_headerModuleCount = 0; + m_pModules = NULL; + m_nBlockingCount = 0; + m_nMissingModule = 0; + m_nLoadedModuleCount = 0; + m_shouldAbort = false; + m_fAppxMode = fAppxMode; + + m_pThread = NULL; + m_pFileBuffer = NULL; + m_nFileSize = 0; + + m_busyWith = EmptyToken; + + m_nStartTime = GetTickCount(); +} + + +MulticoreJitProfilePlayer::~MulticoreJitProfilePlayer() +{ + LIMITED_METHOD_CONTRACT; + + if (m_pModules != NULL) + { + delete [] m_pModules; + m_pModules = NULL; + } + + if (m_pFileBuffer != NULL) + { + delete [] m_pFileBuffer; + } +} + + +// static +bool MulticoreJitManager::ModuleHasNoCode(Module * pModule) +{ + LIMITED_METHOD_CONTRACT; + + if (pModule->IsResource()) + { + return true; + } + + IMDInternalImport * pImport = pModule->GetMDImport(); + + if (pImport != NULL) + { + if ((pImport->GetCountWithTokenKind(mdtTypeDef) == 0) && + (pImport->GetCountWithTokenKind(mdtMethodDef) == 0) && + (pImport->GetCountWithTokenKind(mdtFieldDef) == 0) + ) + { + return true; + } + } + + return false; +} + + +// We only support default load context, non dynamic module, non domain neutral (needed for dependency) +bool MulticoreJitManager::IsSupportedModule(Module * pModule, bool fMethodJit, bool fAppx) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if (pModule == NULL) + { + return false; + } + + PEFile * pFile = pModule->GetFile(); + + // dynamic module. + if (pFile->IsDynamic()) // Ignore dynamic modules + { + return false; + } + +#if defined(FEATURE_CORECLR) + if (pFile->GetPath().IsEmpty()) // Ignore in-memory modules + { + return false; + } +#endif + + + if (! fMethodJit) + { + if (ModuleHasNoCode(pModule)) + { + return false; + } + } + + Assembly * pAssembly = pModule->GetAssembly(); + +#ifdef FEATURE_FUSION + + LOADCTX_TYPE context = pAssembly->GetManifestFile()->GetLoadContext(); + +#if defined(FEATURE_APPX_BINDER) + + if (fAppx) + { + if (context == LOADCTX_TYPE_HOSTED) + { + return true; + } + } + +#endif + + return ((context == LOADCTX_TYPE_DEFAULT) || (context == LOADCTX_TYPE_LOADFROM)); + +#else + + return true; + +#endif +} + + +// ModuleRecord handling: add to m_ModuleList + +HRESULT MulticoreJitProfilePlayer::HandleModuleRecord(const ModuleRecord * pMod) +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = S_OK; + + PlayerModuleInfo & info = m_pModules[m_moduleCount]; + + info.m_pModule = NULL; + info.m_pRecord = pMod; + +#ifdef MULTICOREJIT_LOGGING + info.Dump(W("ModuleRecord"), m_moduleCount); +#endif + + m_moduleCount ++; + + return hr; +} + + +// Call JIT to compile a method + +bool MulticoreJitProfilePlayer::CompileMethodDesc(Module * pModule, MethodDesc * pMD) +{ + STANDARD_VM_CONTRACT; + + COR_ILMETHOD_DECODER::DecoderStatus status; + + COR_ILMETHOD_DECODER header(pMD->GetILHeader(), pModule->GetMDImport(), & status); + + if (status == COR_ILMETHOD_DECODER::SUCCESS) + { + if (m_stats.m_nTryCompiling == 0) + { + MulticoreJitTrace(("First call to MakeJitWorker")); + } + + m_stats.m_nTryCompiling ++; + +#if defined(FEATURE_CORECLR) + // Reset the flag to allow managed code to be called in multicore JIT background thread from this routine + ThreadStateNCStackHolder holder(-1, Thread::TSNC_CallingManagedCodeDisabled); +#endif + + // MakeJitWorker calls back to MulticoreJitCodeStorage::StoreMethodCode under MethodDesc lock + pMD->MakeJitWorker(& header, CORJIT_FLG_MCJIT_BACKGROUND, 0); + + return true; + } + + return false; +} + + +// Conditional JIT of a method +void MulticoreJitProfilePlayer::JITMethod(Module * pModule, unsigned methodIndex) +{ + STANDARD_VM_CONTRACT; + + // Ensure non-null module + if (pModule == NULL) + { + if (ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, TRACE_LEVEL_VERBOSE, CLR_PRIVATEMULTICOREJIT_KEYWORD)) + { + _FireEtwMulticoreJitA(W("NULLMODULEPOINTER"), NULL, methodIndex, 0, 0); + } + return; + } + + methodIndex &= METHODINDEX_MASK; // 20-bit + + unsigned token = TokenFromRid(methodIndex, mdtMethodDef); + + // Similar to Module::FindMethod + Module::FindMethodThrowing, + // except it calls GetMethodDescFromMemberDefOrRefOrSpec with strictMetadataChecks=FALSE to allow generic instantiation + MethodDesc * pMethod = MemberLoader::GetMethodDescFromMemberDefOrRefOrSpec(pModule, token, NULL, FALSE, FALSE); + + if ((pMethod != NULL) && ! pMethod->IsDynamicMethod() && pMethod->HasILHeader()) + { + // MethodDesc::FindOrCreateTypicalSharedInstantiation is expensive, avoid calling it unless the method or class has generic arguments + if (pMethod->HasClassOrMethodInstantiation()) + { + pMethod = pMethod->FindOrCreateTypicalSharedInstantiation(); + + if (pMethod == NULL) + { + goto BadMethod; + } + + pModule = pMethod->GetModule_NoLogging(); + } + + if (pMethod->GetNativeCode() != NULL) // last check before + { + m_stats.m_nHasNativeCode ++; + + return; + } + else + { + m_busyWith = methodIndex; + + bool rslt = CompileMethodDesc(pModule, pMethod); + + m_busyWith = EmptyToken; + + if (rslt) + { + return; + } + } + } + +BadMethod: + + m_stats.m_nFilteredMethods ++; + + MulticoreJitTrace(("Filtered out methods: pModule:[%s] token:[%x]", pModule->GetSimpleName(), token)); + + if (ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, TRACE_LEVEL_VERBOSE, CLR_PRIVATEMULTICOREJIT_KEYWORD)) + { + _FireEtwMulticoreJitA(W("FILTERMETHOD-GENERIC"), pModule->GetSimpleName(), token, 0, 0); + } +} + + +class MulticoreJitPlayerModuleEnumerator : public MulticoreJitModuleEnumerator +{ + MulticoreJitProfilePlayer * m_pPlayer; + + // Implementation of MulticoreJitModuleEnumerator::OnModule + HRESULT OnModule(Module * pModule) + { + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_PREEMPTIVE; + CAN_TAKE_LOCK; + } + CONTRACTL_END; + + return m_pPlayer->OnModule(pModule); + } + +public: + + MulticoreJitPlayerModuleEnumerator(MulticoreJitProfilePlayer * pPlayer) + { + m_pPlayer = pPlayer; + } +}; + + +HRESULT MulticoreJitProfilePlayer::OnModule(Module * pModule) +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = S_OK; + + // Check if already matched + for (unsigned i = 0; i < m_moduleCount; i ++) + { + if (m_pModules[i].m_pModule == pModule) + { + return hr; + } + } + + ModuleVersion version; // GetModuleVersion is called on-demand when simple names matches + + bool gotVersion = false; + + // Match with simple name, and then version/flag/guid + for (unsigned i = 0; i < m_moduleCount; i ++) + { + if (m_pModules[i].MatchWith(version, gotVersion, pModule, m_shouldAbort, m_fAppxMode)) + { + m_nLoadedModuleCount ++; + return hr; + } + } + + return hr; +} + + +HRESULT MulticoreJitProfilePlayer::UpdateModuleInfo() +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = S_OK; + + MulticoreJitTrace(("UpdateModuleInfo")); + + // Enumerate module if there is a module needed, but not loaded yet + for (unsigned i = 0; i < m_moduleCount; i ++) + { + PlayerModuleInfo & info = m_pModules[i]; + + if (! info.LoadOkay() && info.IsDependency() && ! info.IsModuleLoaded()) + { + MulticoreJitTrace((" Enumerate modules for player")); + + MulticoreJitPlayerModuleEnumerator enumerator(this); + + enumerator.EnumerateLoadedModules(GetAppDomain()); // Enumerate modules, hope to find new matches + + break; + } + } + + // Update load level, re-calculate blocking count + m_nBlockingCount = 0; + m_nMissingModule = 0; + + if (m_shouldAbort) + { + hr = E_ABORT; + } + else + { + // Check for blocking level + for (unsigned i = 0; i < m_moduleCount; i ++) + { + PlayerModuleInfo & info = m_pModules[i]; + + if (! info.LoadOkay() && info.IsLowerLevel()) + { + if (info.IsModuleLoaded()) + { + info.UpdateCurrentLevel(); + } + else + { + m_nMissingModule ++; + } + + if (info.IsLowerLevel()) + { + #ifdef MULTICOREJIT_LOGGING + info.Dump(W(" BlockingModule"), i); + #endif + + if (ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, TRACE_LEVEL_VERBOSE, CLR_PRIVATEMULTICOREJIT_KEYWORD)) + { + _FireEtwMulticoreJitA(W("BLOCKINGMODULE"), info.m_pRecord->GetModuleName(), i, info.m_curLevel, info.m_needLevel); + } + + m_nBlockingCount ++; + } + } + } + } + + MulticoreJitTrace(("Blocking count: %d, missing module: %d, hr=%x", m_nBlockingCount, m_nMissingModule, hr)); + + return hr; +} + + +bool MulticoreJitProfilePlayer::ShouldAbort(bool fast) const +{ + LIMITED_METHOD_CONTRACT; + + if (m_nMySession != m_appdomainSession.GetValue()) + { + MulticoreJitTrace(("MulticoreJitProfilePlayer::ShouldAbort session over")); + _FireEtwMulticoreJit(W("ABORTPLAYER"), W("Session over"), 0, 0, 0); + return true; + } + + if (fast) + { + return false; + } + + if (GetTickCount() - m_nStartTime > MULTICOREJITLIFE) + { + MulticoreJitTrace(("MulticoreJitProfilePlayer::ShouldAbort time over")); + + _FireEtwMulticoreJit(W("ABORTPLAYER"), W("Time out"), 0, 0, 0); + + return true; + } + + return false; +} + + +// Basic delay unit +const int DelayUnit = 1; // 1 ms delay +const int MissingModuleDelay = 10; // 10 ms for each missing module + + +// Wait for all the module loading and level requests to be fullfilled +// This allows for longer delay based on number of mismatches, to reduce CPU usage + +// Return true blocking count is 0, false if aborted +bool MulticoreJitProfilePlayer::GroupWaitForModuleLoad(int pos) +{ + STANDARD_VM_CONTRACT; + + MulticoreJitTrace(("Enter GroupWaitForModuleLoad(pos=%4d): %d modules loaded, blocking count=%d", pos, m_nLoadedModuleCount, m_nBlockingCount)); + + _FireEtwMulticoreJit(W("GROUPWAIT"), W("Enter"), m_nLoadedModuleCount, m_nBlockingCount, pos); + + bool rslt = false; + + // Ensure that we don't block in this particular case for longer than the block limit. + // This limit is smaller than the overall MULTICOREJITLIFE and ensures that we don't sit for the + // full player lifetime waiting for a module when the app behavior has changed. + DWORD currentModuleBlockStart = GetTickCount(); + + // Only allow module blocking to occur a certain number of times. + + while (! ShouldAbort(false)) + { + if (FAILED(UpdateModuleInfo())) + { + break; + } + + if (m_nBlockingCount == 0) + { + rslt = true; + break; + } + + if(GetTickCount() - currentModuleBlockStart > MULTICOREJITBLOCKLIMIT) + { + MulticoreJitTrace(("MulticoreJitProfilePlayer::GroupWaitForModuleLoad timeout exceeded.")); + _FireEtwMulticoreJit(W("ABORTPLAYER"), W("GroupWaitForModuleLoad timeout exceeded."), 0, 0, 0); + + break; + } + + // Heuristic for reducing CPU usage: delay longer when there are more blocking modules + unsigned delay = min((m_nMissingModule * MissingModuleDelay + m_nBlockingCount) * DelayUnit, 50); + + MulticoreJitTrace(("Delay: %d ms", delay)); + + if (ETW_TRACING_CATEGORY_ENABLED(MICROSOFT_WINDOWS_DOTNETRUNTIME_PRIVATE_PROVIDER_Context, TRACE_LEVEL_VERBOSE, CLR_PRIVATEMULTICOREJIT_KEYWORD)) + { + _FireEtwMulticoreJit(W("GROUPWAIT"), W("Delay"), delay, 0, 0); + } + + ClrSleepEx(delay, FALSE); + + m_stats.m_nTotalDelay += (unsigned short) delay; + m_stats.m_nDelayCount ++; + } + + MulticoreJitTrace(("Leave GroupWaitForModuleLoad(pos=%4d): blocking count=%d (rslt=%d)", pos, m_nBlockingCount, rslt)); + + _FireEtwMulticoreJit(W("GROUPWAIT"), W("Leave"), m_nLoadedModuleCount, m_nBlockingCount, rslt); + + return rslt; +} + + +bool MulticoreJitProfilePlayer::HandleModuleDependency(unsigned jitInfo) +{ + STANDARD_VM_CONTRACT; + + // depends on moduleTo, which may not loaded yet + + unsigned moduleTo = jitInfo & MODULE_MASK; + + if (moduleTo < m_moduleCount) + { + unsigned level = (jitInfo >> LEVEL_SHIFT) & LEVEL_MASK; + + PlayerModuleInfo & mod = m_pModules[moduleTo]; + +#if defined(FEATURE_CORECLR) + // Load the module if necessary. + if (!mod.m_pModule) + { + // Update loaded module status. + AppDomain * pAppDomain = GetAppDomain(); + _ASSERTE(pAppDomain != NULL); + + MulticoreJitPlayerModuleEnumerator moduleEnumerator(this); + moduleEnumerator.EnumerateLoadedModules(pAppDomain); + + if (!mod.m_pModule) + { + HRESULT hr; + + // Get the assembly name. + SString assemblyName; + assemblyName.SetASCII(mod.m_pRecord->GetAssemblyName(), mod.m_pRecord->AssemblyNameLen()); + + // Load the assembly. + DomainAssembly * pDomainAssembly = LoadAssembly(assemblyName); + + if (pDomainAssembly) + { + // If we successfully loaded the assembly, enumerate the modules in the assembly + // and update all modules status. + moduleEnumerator.HandleAssembly(pDomainAssembly); + + if (mod.m_pModule == NULL) + { + // Unable to load the assembly, so abort. + return false; + } + } + else + { + // Unable to load the assembly, so abort. + return false; + } + } + } +#endif + + if (mod.UpdateNeedLevel((FileLoadLevel) level)) + { + if (! mod.LoadOkay()) // allow first part WinMD to load in background thread + { + m_nBlockingCount ++; + } + } + } + + return true; +} + +#if defined(FEATURE_CORECLR) +DomainAssembly * MulticoreJitProfilePlayer::LoadAssembly(SString & assemblyName) +{ + STANDARD_VM_CONTRACT; + + // Get the assembly name. + StackScratchBuffer scratch; + const ANSI* pAnsiAssemblyName = assemblyName.GetANSI(scratch); + + AssemblySpec spec; + + // Initialize the assembly spec. + HRESULT hr = spec.Init(pAnsiAssemblyName); + if (FAILED(hr)) + { + return NULL; + } + + // Set the binding context to the assembly load context. + if (m_pBinderContext != NULL) + { + spec.SetBindingContext(m_pBinderContext); + } + + DomainAssembly *pDomainAssembly = NULL; + + // Setup the AssemblyLoadSecurity to perform the assembly load + GCX_COOP(); + + PTR_AppDomain pCurDomain = GetAppDomain(); + IApplicationSecurityDescriptor *pDomainSecDesc = pCurDomain->GetSecurityDescriptor(); + + OBJECTREF refGrantedPermissionSet = NULL; + AssemblyLoadSecurity loadSecurity; + + GCPROTECT_BEGIN(refGrantedPermissionSet); + + loadSecurity.m_dwSpecialFlags = pDomainSecDesc->GetSpecialFlags(); + refGrantedPermissionSet = pDomainSecDesc->GetGrantedPermissionSet(); + loadSecurity.m_pGrantSet = &refGrantedPermissionSet; + + // Bind and load the assembly. + pDomainAssembly = spec.LoadDomainAssembly( + FILE_LOADED, + &loadSecurity, + FALSE); // Don't throw on FileNotFound. + + GCPROTECT_END(); + + return pDomainAssembly; +} +#endif + + +inline bool MethodJifInfo(unsigned inst) +{ + LIMITED_METHOD_CONTRACT; + + return ((inst & MODULE_DEPENDENCY) == 0); +} + + +// Process a block of methodDef, call JIT if not blocked +HRESULT MulticoreJitProfilePlayer::HandleMethodRecord(unsigned * buffer, int count) +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = E_ABORT; + + MulticoreJitTrace(("MethodRecord(%d) start %d methods, %d mod loaded", m_stats.m_nTotalMethod, count, m_nLoadedModuleCount)); + + MulticoreJitManager & manager = GetAppDomain()->GetMulticoreJitManager(); + +#ifdef MULTICOREJIT_LOGGING + + MulticoreJitCodeStorage & curStorage = manager.GetMulticoreJitCodeStorage(); + + int lastCompiled = curStorage.GetStored(); + +#endif + + int pos = 0; + + while (! ShouldAbort(true) && (pos < count)) + { + unsigned jitInfo = buffer[pos]; // moduleIndex + methodIndex + + unsigned moduleIndex = jitInfo >> 24; + + if (moduleIndex < m_moduleCount) + { + if (jitInfo & MODULE_DEPENDENCY) // Module depedency information + { + if (! HandleModuleDependency(jitInfo)) + { + goto Abort; + } + } + else + { + PlayerModuleInfo & info = m_pModules[moduleIndex]; + + m_stats.m_nTotalMethod ++; + + // If module is disabled for Jitting, just skip method without even waiting + if (! info.m_enableJit) + { + m_stats.m_nFilteredMethods ++; + } + else + { +#if !defined(FEATURE_CORECLR) + if (m_nBlockingCount != 0) + { + if (! GroupWaitForModuleLoad(m_stats.m_nTotalMethod + pos)) // wait for blocking modules + { + goto Abort; + } + + _ASSERTE(m_nBlockingCount == 0); + } +#endif + + // To reduce contention with foreground thread, walk backward within the group of methods Jittable methods, not broken apart by dependency + { + int run = 1; // size of the group + + while (((pos + run) < count) && MethodJifInfo(buffer[pos + run])) + { + run ++; + + // If walk-back run is too long, lots of methods in the front will be missed by background thread + if (run > MAX_WALKBACK) + { + break; + } + } + + if (run > 1) + { + MulticoreJitTrace(("Jit backwards %d methods", run)); + } + + // Walk backwards within the same group, may be from different modules + for (int p = pos + run - 1; p >= pos; p --) + { + unsigned inst = buffer[p]; + + _ASSERTE(MethodJifInfo(inst)); + + PlayerModuleInfo & mod = m_pModules[inst >> 24]; + +#if defined(FEATURE_CORECLR) + _ASSERTE(mod.IsModuleLoaded()); +#else + _ASSERTE(mod.IsModuleLoaded() && ! mod.IsLowerLevel()); +#endif + + if (mod.m_enableJit) + { + JITMethod(mod.m_pModule, inst); + } + else + { + m_stats.m_nFilteredMethods ++; + } + } + + m_stats.m_nWalkBack += (short) (run - 1); + m_stats.m_nTotalMethod += (short) (run - 1); + + pos += run - 1; // Skip the group + } + } + } + } + else + { + hr = COR_E_BADIMAGEFORMAT; + goto Abort; + } + + pos ++; + } + + // Mark success + hr = S_OK; + +Abort: + + m_stats.m_nMissingModuleSkip += (short) (count - pos); + + MulticoreJitTrace(("MethodRecord(%d) end %d compiled, %d aborted / %d methods, hr=%x", + m_stats.m_nTotalMethod, + curStorage.GetStored() - lastCompiled, + count - pos, count, hr)); + + TraceSummary(); + + return hr; +} + + +void MulticoreJitProfilePlayer::TraceSummary() +{ + LIMITED_METHOD_CONTRACT; + + MulticoreJitCodeStorage & curStorage = GetAppDomain()->GetMulticoreJitManager().GetMulticoreJitCodeStorage(); + + unsigned returned = curStorage.GetReturned(); + +#ifdef MULTICOREJIT_LOGGING + + unsigned compiled = curStorage.GetStored(); + + MulticoreJitTrace(("PlayerSummary: %d total: %d no mod, %d filtered out, %d had code, %d other, %d tried, %d compiled, %d returned, %d%% efficiency, %d mod loaded, %d ms delay(%d)", + m_stats.m_nTotalMethod, + m_stats.m_nMissingModuleSkip, + m_stats.m_nFilteredMethods, + m_stats.m_nHasNativeCode, + m_stats.m_nTotalMethod - m_stats.m_nMissingModuleSkip - m_stats.m_nFilteredMethods - m_stats.m_nHasNativeCode - m_stats.m_nTryCompiling, + m_stats.m_nTryCompiling, + compiled, + returned, + (m_stats.m_nTotalMethod == 0) ? 100 : returned * 100 / m_stats.m_nTotalMethod, + m_nLoadedModuleCount, + m_stats.m_nTotalDelay, + m_stats.m_nDelayCount + )); + +#endif + + _FireEtwMulticoreJit(W("PLAYERSUMMARY"), W(""), m_stats.m_nTryCompiling, m_stats.m_nHasNativeCode, returned); +} + + +HRESULT MulticoreJitProfilePlayer::ReadCheckFile(const wchar_t * pFileName) +{ + CONTRACTL + { + THROWS; + MODE_PREEMPTIVE; + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + { + HANDLE hFile = WszCreateFile(pFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + return COR_E_FILENOTFOUND; + } + + HeaderRecord header; + + DWORD cbRead = 0; + + if (! ::ReadFile(hFile, & header, sizeof(header), &cbRead, NULL)) + { + hr = COR_E_BADIMAGEFORMAT; + } + else if (cbRead != sizeof(header)) + { + hr = COR_E_BADIMAGEFORMAT; + } + else + { + m_headerModuleCount = header.moduleCount; + + MulticoreJitTrace(("HeaderRecord(version=%d, module=%d, method=%d)", header.version, m_headerModuleCount, header.methodCount)); + + if ((header.version != MULTICOREJIT_PROFILE_VERSION) || (header.moduleCount > MAX_MODULES) || (header.methodCount > MAX_METHOD_ARRAY) || + (header.recordID != Pack8_24(MULTICOREJIT_HEADER_RECORD_ID, sizeof(HeaderRecord)))) + { + hr = COR_E_BADIMAGEFORMAT; + } + else + { + m_pModules = new (nothrow) PlayerModuleInfo[m_headerModuleCount]; + + if (m_pModules == NULL) + { + hr = E_OUTOFMEMORY; + } + } + } + + if (SUCCEEDED(hr)) + { + m_nFileSize = SafeGetFileSize(hFile, 0); + + if (m_nFileSize > sizeof(header)) + { + m_nFileSize -= sizeof(header); + + m_pFileBuffer = new (nothrow) BYTE[m_nFileSize]; + + if (m_pFileBuffer == NULL) + { + hr = E_OUTOFMEMORY; + } + else if (::ReadFile(hFile, m_pFileBuffer, m_nFileSize, & cbRead, NULL)) + { + if (cbRead != m_nFileSize) + { + hr = COR_E_BADIMAGEFORMAT; + } + } + else + { + hr = CLDB_E_FILE_BADREAD; + } + } + else + { + hr = COR_E_BADIMAGEFORMAT; + } + } + + CloseHandle(hFile); + + _FireEtwMulticoreJit(W("PLAYER"), W("Header"), hr, m_headerModuleCount, header.methodCount); + } + + + return hr; +} + + +HRESULT MulticoreJitProfilePlayer::PlayProfile() +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = S_OK; + + DWORD start = GetTickCount(); + + Thread * pThread = GetThread(); + + { + // 1 marks background thread + FireEtwThreadCreated((ULONGLONG) pThread, (ULONGLONG) GetAppDomain(), 1, pThread->GetThreadId(), pThread->GetOSThreadId(), GetClrInstanceId()); + } + + const BYTE * pBuffer = m_pFileBuffer; + + unsigned nSize = m_nFileSize; + + MulticoreJitTrace(("PlayProfile %d bytes in (%d, %s)", + nSize, + GetAppDomain()->GetId().m_dwId, + GetAppDomain()->GetFriendlyNameForLogging())); + + while ((SUCCEEDED(hr)) && (nSize > sizeof(unsigned))) + { + unsigned data = * (const unsigned *) pBuffer; + unsigned rcdLen = data & 0xFFFFFF; + unsigned rcdTyp = data >> 24; + + if ((rcdLen > nSize) || (rcdLen & 3)) // Better DWORD align + { + hr = COR_E_BADIMAGEFORMAT; + } + else + { + if (rcdTyp == MULTICOREJIT_MODULE_RECORD_ID) + { + const ModuleRecord * pRec = (const ModuleRecord * ) pBuffer; + + if (((unsigned)(pRec->lenModuleName +#if defined(FEATURE_CORECLR) + + pRec->lenAssemblyName +#endif + ) > (rcdLen - sizeof(ModuleRecord))) || + (m_moduleCount >= m_headerModuleCount)) + { + hr = COR_E_BADIMAGEFORMAT; + } + else + { + hr = HandleModuleRecord(pRec); + } + } + else if (rcdTyp == MULTICOREJIT_JITINF_RECORD_ID) + { + int mCount = (rcdLen - sizeof(unsigned)) / sizeof(unsigned); + + hr = HandleMethodRecord((unsigned *) (pBuffer + sizeof(unsigned)), mCount); + } + else + { + hr = COR_E_BADIMAGEFORMAT; + } + + pBuffer += rcdLen; + nSize -= rcdLen; + } + + if (SUCCEEDED(hr) && ShouldAbort(false)) + { + hr = E_ABORT; + } + } + + start = GetTickCount() - start; + + { + FireEtwThreadTerminated((ULONGLONG) pThread, (ULONGLONG) GetAppDomain(), GetClrInstanceId()); + } + + MulticoreJitTrace(("Background thread running for %d ms, %d methods, hr=%x", start, m_stats.m_nTotalMethod, hr)); + + TraceSummary(); + + return hr; +} + + +HRESULT MulticoreJitProfilePlayer::JITThreadProc(Thread * pThread) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + m_stats.m_hr = S_OK; + + EX_TRY + { + ENTER_DOMAIN_ID(m_DomainID); + { + // Go into preemptive mode + GCX_PREEMP(); + + m_stats.m_hr = PlayProfile(); + } + END_DOMAIN_TRANSITION; + } + EX_CATCH + { + if (SUCCEEDED(m_stats.m_hr)) + { + m_stats.m_hr = COR_E_EXCEPTION; + } + } + EX_END_CATCH(SwallowAllExceptions); + + return (DWORD) m_stats.m_hr; +} + + +DWORD WINAPI MulticoreJitProfilePlayer::StaticJITThreadProc(void *args) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + ENTRY_POINT; + INJECT_FAULT(COMPlusThrowOM();); + } + CONTRACTL_END; + + HRESULT hr = S_OK; + + BEGIN_ENTRYPOINT_NOTHROW; + + MulticoreJitTrace(("StaticJITThreadProc starting")); + + // Mark the background thread via an ETW event for diagnostics. + _FireEtwMulticoreJit(W("JITTHREAD"), W(""), 0, 0, 0); + + MulticoreJitProfilePlayer * pPlayer = (MulticoreJitProfilePlayer *) args; + + if (pPlayer != NULL) + { + Thread * pThread = pPlayer->m_pThread; + + if ((pThread != NULL) && pThread->HasStarted()) + { + // Disable calling managed code in background thread + ThreadStateNCStackHolder holder(TRUE, Thread::TSNC_CallingManagedCodeDisabled); + + // Run as background thread, so ThreadStore::WaitForOtherThreads will not wait for it + pThread->SetBackground(TRUE); + + hr = pPlayer->JITThreadProc(pThread); + } + + // It needs to be deleted after GCX_PREEMP ends + if (pThread != NULL) + { + DestroyThread(pThread); + } + + // The background thread is reponsible for deleting the MulticoreJitProfilePlayer object once it's started + // Actually after Thread::StartThread succeeds + delete pPlayer; + } + + MulticoreJitTrace(("StaticJITThreadProc endding(%x)", hr)); + + END_ENTRYPOINT_NOTHROW; + + return (DWORD) hr; +} + + +HRESULT MulticoreJitProfilePlayer::ProcessProfile(const wchar_t * pFileName) +{ + STANDARD_VM_CONTRACT; + + HRESULT hr = ReadCheckFile(pFileName); + + if (SUCCEEDED(hr)) + { + _ASSERTE(m_pThread == NULL); + + m_pThread = SetupUnstartedThread(); + + _ASSERTE(m_pThread != NULL); + + if (m_pThread->CreateNewThread(0, StaticJITThreadProc, this)) + { + int t = (int) m_pThread->StartThread(); + + if (t > 0) + { + hr = S_OK; + } + } + } + + return hr; +} + + |