From f4d1feff5c63d13538ce0043bcf616e58388acf9 Mon Sep 17 00:00:00 2001 From: Simon Ferquel Date: Thu, 3 Apr 2025 10:33:43 +0200 Subject: [PATCH 1/5] Make COR_PRF_DISABLE_OPTIMIZATIONS and COR_PRF_DISABLE_INLINING mutable flags --- src/coreclr/inc/corprof.idl | 10 +++++----- src/coreclr/pal/prebuilt/inc/corprof.h | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/inc/corprof.idl b/src/coreclr/inc/corprof.idl index 33e3619a962251..dadacf934847f8 100644 --- a/src/coreclr/inc/corprof.idl +++ b/src/coreclr/inc/corprof.idl @@ -592,6 +592,8 @@ typedef enum COR_PRF_MONITOR_CLASS_LOADS | COR_PRF_MONITOR_EXCEPTIONS | COR_PRF_MONITOR_JIT_COMPILATION | + COR_PRF_DISABLE_INLINING | + COR_PRF_DISABLE_OPTIMIZATIONS | COR_PRF_ENABLE_REJIT, COR_PRF_ALLOWABLE_NOTIFICATION_PROFILER @@ -625,8 +627,6 @@ typedef enum COR_PRF_MONITOR_REMOTING_ASYNC | COR_PRF_ENABLE_INPROC_DEBUGGING | COR_PRF_ENABLE_JIT_MAPS | - COR_PRF_DISABLE_OPTIMIZATIONS | - COR_PRF_DISABLE_INLINING | COR_PRF_ENABLE_OBJECT_ALLOCATED | COR_PRF_ENABLE_FUNCTION_ARGS | COR_PRF_ENABLE_FUNCTION_RETVAL | @@ -4292,10 +4292,10 @@ interface ICorProfilerInfo14 : ICorProfilerInfo13 [out, size_is(cObjectRanges), length_is(*pcObjectRanges)] COR_PRF_NONGC_HEAP_RANGE ranges[]); - // EventPipeCreateProvider2 allows you to pass in a callback which will be called whenever a + // EventPipeCreateProvider2 allows you to pass in a callback which will be called whenever a // session enables your provider. The behavior of the callback matches the ETW behavior which - // can be counter intuitive. You will get a callback any time a session changes with the updated - // global keywords enabled for your session. The is_enabled parameter will be true if any + // can be counter intuitive. You will get a callback any time a session changes with the updated + // global keywords enabled for your session. The is_enabled parameter will be true if any // session has your provider enabled. The source_id parameter will be a valid id if the callback // was triggered due to a session enabling and it will be NULL if it was triggered due to a session // disabling. diff --git a/src/coreclr/pal/prebuilt/inc/corprof.h b/src/coreclr/pal/prebuilt/inc/corprof.h index ad54c4e6b818db..b45d1c8f7d65b1 100644 --- a/src/coreclr/pal/prebuilt/inc/corprof.h +++ b/src/coreclr/pal/prebuilt/inc/corprof.h @@ -583,9 +583,9 @@ enum __MIDL___MIDL_itf_corprof_0000_0000_0005 COR_PRF_DISABLE_ALL_NGEN_IMAGES = 0x80000000, COR_PRF_ALL = 0x8fffffff, COR_PRF_REQUIRE_PROFILE_IMAGE = ( ( COR_PRF_USE_PROFILE_IMAGES | COR_PRF_MONITOR_CODE_TRANSITIONS ) | COR_PRF_MONITOR_ENTERLEAVE ) , - COR_PRF_ALLOWABLE_AFTER_ATTACH = ( ( ( ( ( ( ( ( ( ( COR_PRF_MONITOR_THREADS | COR_PRF_MONITOR_MODULE_LOADS ) | COR_PRF_MONITOR_ASSEMBLY_LOADS ) | COR_PRF_MONITOR_APPDOMAIN_LOADS ) | COR_PRF_ENABLE_STACK_SNAPSHOT ) | COR_PRF_MONITOR_GC ) | COR_PRF_MONITOR_SUSPENDS ) | COR_PRF_MONITOR_CLASS_LOADS ) | COR_PRF_MONITOR_EXCEPTIONS ) | COR_PRF_MONITOR_JIT_COMPILATION ) | COR_PRF_ENABLE_REJIT ) , + COR_PRF_ALLOWABLE_AFTER_ATTACH = ( ( ( ( ( ( ( ( ( ( ( ( COR_PRF_MONITOR_THREADS | COR_PRF_MONITOR_MODULE_LOADS ) | COR_PRF_MONITOR_ASSEMBLY_LOADS ) | COR_PRF_MONITOR_APPDOMAIN_LOADS ) | COR_PRF_ENABLE_STACK_SNAPSHOT ) | COR_PRF_MONITOR_GC ) | COR_PRF_MONITOR_SUSPENDS ) | COR_PRF_MONITOR_CLASS_LOADS ) | COR_PRF_MONITOR_EXCEPTIONS ) | COR_PRF_MONITOR_JIT_COMPILATION ) | COR_PRF_DISABLE_INLINING ) | COR_PRF_DISABLE_OPTIMIZATIONS ) | COR_PRF_ENABLE_REJIT ) , COR_PRF_ALLOWABLE_NOTIFICATION_PROFILER = ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( COR_PRF_MONITOR_FUNCTION_UNLOADS | COR_PRF_MONITOR_CLASS_LOADS ) | COR_PRF_MONITOR_MODULE_LOADS ) | COR_PRF_MONITOR_ASSEMBLY_LOADS ) | COR_PRF_MONITOR_APPDOMAIN_LOADS ) | COR_PRF_MONITOR_JIT_COMPILATION ) | COR_PRF_MONITOR_EXCEPTIONS ) | COR_PRF_MONITOR_OBJECT_ALLOCATED ) | COR_PRF_MONITOR_THREADS ) | COR_PRF_MONITOR_CODE_TRANSITIONS ) | COR_PRF_MONITOR_CCW ) | COR_PRF_MONITOR_SUSPENDS ) | COR_PRF_MONITOR_CACHE_SEARCHES ) | COR_PRF_DISABLE_INLINING ) | COR_PRF_DISABLE_OPTIMIZATIONS ) | COR_PRF_ENABLE_OBJECT_ALLOCATED ) | COR_PRF_MONITOR_CLR_EXCEPTIONS ) | COR_PRF_ENABLE_STACK_SNAPSHOT ) | COR_PRF_USE_PROFILE_IMAGES ) | COR_PRF_DISABLE_ALL_NGEN_IMAGES ) , - COR_PRF_MONITOR_IMMUTABLE = ( ( ( ( ( ( ( ( ( ( ( ( ( ( COR_PRF_MONITOR_CODE_TRANSITIONS | COR_PRF_MONITOR_REMOTING ) | COR_PRF_MONITOR_REMOTING_COOKIE ) | COR_PRF_MONITOR_REMOTING_ASYNC ) | COR_PRF_ENABLE_INPROC_DEBUGGING ) | COR_PRF_ENABLE_JIT_MAPS ) | COR_PRF_DISABLE_OPTIMIZATIONS ) | COR_PRF_DISABLE_INLINING ) | COR_PRF_ENABLE_OBJECT_ALLOCATED ) | COR_PRF_ENABLE_FUNCTION_ARGS ) | COR_PRF_ENABLE_FUNCTION_RETVAL ) | COR_PRF_ENABLE_FRAME_INFO ) | COR_PRF_USE_PROFILE_IMAGES ) | COR_PRF_DISABLE_TRANSPARENCY_CHECKS_UNDER_FULL_TRUST ) | COR_PRF_DISABLE_ALL_NGEN_IMAGES ) + COR_PRF_MONITOR_IMMUTABLE = ( ( ( ( ( ( ( ( ( ( ( ( COR_PRF_MONITOR_CODE_TRANSITIONS | COR_PRF_MONITOR_REMOTING ) | COR_PRF_MONITOR_REMOTING_COOKIE ) | COR_PRF_MONITOR_REMOTING_ASYNC ) | COR_PRF_ENABLE_INPROC_DEBUGGING ) | COR_PRF_ENABLE_JIT_MAPS ) | COR_PRF_ENABLE_OBJECT_ALLOCATED ) | COR_PRF_ENABLE_FUNCTION_ARGS ) | COR_PRF_ENABLE_FUNCTION_RETVAL ) | COR_PRF_ENABLE_FRAME_INFO ) | COR_PRF_USE_PROFILE_IMAGES ) | COR_PRF_DISABLE_TRANSPARENCY_CHECKS_UNDER_FULL_TRUST ) | COR_PRF_DISABLE_ALL_NGEN_IMAGES ) } COR_PRF_MONITOR; typedef /* [public] */ From 56615999c4ea379ef706ac006836c93d966e8071 Mon Sep 17 00:00:00 2001 From: Simon Ferquel Date: Thu, 3 Apr 2025 10:34:55 +0200 Subject: [PATCH 2/5] On module initialization, capture the profiler JIT flags so they apply consistently on that module --- src/coreclr/vm/ceeload.cpp | 15 ++++++++++++--- src/coreclr/vm/ceeload.h | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 2db27a253fd829..8e835eca503401 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -478,14 +478,23 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_dwTypeCount = 0; m_dwExportedTypeCount = 0; m_dwCustomAttributeCount = 0; +#ifdef PROFILING_SUPPORTED + // set profiler related JIT flags + if (CORProfilerDisableInlining()){ + m_dwTransientFlags |= PROF_DISABLE_INLINING; + } + if (CORProfilerDisableOptimizations()){ + m_dwTransientFlags |= PROF_DISABLE_OPTIMIZATIONS; + } -#if defined(PROFILING_SUPPORTED) && !defined(DACCESS_COMPILE) +#if !defined(DACCESS_COMPILE) m_pJitInlinerTrackingMap = NULL; if (ReJitManager::IsReJITInlineTrackingEnabled()) { m_pJitInlinerTrackingMap = new JITInlineTrackingMap(GetLoaderAllocator()); } -#endif // defined (PROFILING_SUPPORTED) &&!defined(DACCESS_COMPILE) +#endif // !defined(DACCESS_COMPILE) +#endif // PROFILING_SUPPORTED LOG((LF_CLASSLOADER, LL_INFO10, "Loaded pModule: \"%s\".\n", GetDebugName())); } @@ -507,7 +516,7 @@ void Module::SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits) #ifdef DEBUGGING_SUPPORTED if (IsEditAndContinueCapable()) { - BOOL setEnC = (newBits & DACF_ENC_ENABLED) != 0 || g_pConfig->ForceEnc() || (g_pConfig->DebugAssembliesModifiable() && CORDisableJITOptimizations(GetDebuggerInfoBits())); + BOOL setEnC = (newBits & DACF_ENC_ENABLED) != 0 || g_pConfig->ForceEnc() || (g_pConfig->DebugAssembliesModifiable() && AreJITOptimizationsDisabled()); if (setEnC) { EnableEditAndContinue(); diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index fa11dd2ce20e36..431b84085ed7ea 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -627,6 +627,8 @@ class Module : public ModuleBase IS_ETW_NOTIFIED = 0x00000020, IS_REFLECTION_EMIT = 0x00000040, + PROF_DISABLE_OPTIMIZATIONS = 0x00000080, // indicates if Profiler disabled JIT optimization event mask was set when loaded + PROF_DISABLE_INLINING = 0x00000100, // indicates if Profiler disabled JIT Inlining event mask was set when loaded // // Note: The values below must match the ones defined in @@ -915,6 +917,39 @@ class Module : public ModuleBase return (m_dwTransientFlags & IS_EDIT_AND_CONTINUE) != 0; } + BOOL IsInliningDisabledByProfiler() const + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + + return (m_dwTransientFlags & PROF_DISABLE_INLINING) != 0; + } + + BOOL AreJITOptimizationsDisabled() const + { + WRAPPER_NO_CONTRACT; + SUPPORTS_DAC; + +#ifdef DEBUGGING_SUPPORTED + // check if debugger has disallowed JIT optimizations + auto dwDebuggerBits = GetDebuggerInfoBits(); + if (!CORDebuggerAllowJITOpts(dwDebuggerBits)) + { + return TRUE; + } +#endif // DEBUGGING_SUPPORTED + +#if defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) + // check if profiler had disabled JIT optimizations when module was loaded + if (m_dwTransientFlags & PROF_DISABLE_OPTIMIZATIONS) + { + return TRUE; + } +#endif // defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) + + return FALSE; + } + #ifdef FEATURE_METADATA_UPDATER // Holds a table of EnCEEClassData object for classes in this module that have been modified CUnorderedArray m_ClassList; @@ -1339,7 +1374,7 @@ class Module : public ModuleBase void SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits); - DebuggerAssemblyControlFlags GetDebuggerInfoBits(void) + DebuggerAssemblyControlFlags GetDebuggerInfoBits(void) const { LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; From 7800e6fd94385e03f1ba112c93827e62620b0bc3 Mon Sep 17 00:00:00 2001 From: Simon Ferquel Date: Thu, 3 Apr 2025 10:35:42 +0200 Subject: [PATCH 3/5] Change old macro usages and make them call Module::AreJITOptimizationsDisabled() instead --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 3 +- src/coreclr/debug/ee/debugger.cpp | 7 ++-- src/coreclr/debug/ee/debugger.h | 2 -- src/coreclr/debug/ee/debugger.inl | 7 +--- src/coreclr/vm/jitinterface.cpp | 20 +++++------ src/coreclr/vm/method.cpp | 2 +- src/coreclr/vm/methodtable.cpp | 2 +- src/coreclr/vm/vars.hpp | 42 ++---------------------- 8 files changed, 18 insertions(+), 67 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index a5f5f7e7653a57..5b65dc24e7ab2d 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -667,8 +667,7 @@ void DacDbiInterfaceImpl::GetCompilerFlags ( // Get the underlying module - none of this is AppDomain specific Module * pModule = pDomainAssembly->GetAssembly()->GetModule(); - DWORD dwBits = pModule->GetDebuggerInfoBits(); - *pfAllowJITOpts = !CORDisableJITOptimizations(dwBits); + *pfAllowJITOpts = !pModule->AreJITOptimizationsDisabled(); *pfEnableEnC = pModule->IsEditAndContinueEnabled(); diff --git a/src/coreclr/debug/ee/debugger.cpp b/src/coreclr/debug/ee/debugger.cpp index fe9cb0f022d364..c3ef802be3a0ad 100644 --- a/src/coreclr/debug/ee/debugger.cpp +++ b/src/coreclr/debug/ee/debugger.cpp @@ -3301,7 +3301,7 @@ void Debugger::getBoundaries(MethodDesc * md, // lives in, then don't grab specific boundaries from the symbol // store since any boundaries we give the JIT will be pretty much // ignored anyway. - if (!CORDisableJITOptimizations(md->GetModule()->GetDebuggerInfoBits())) + if (!md->GetModule()->AreJITOptimizationsDisabled()) { *implicitBoundaries = ICorDebugInfo::BoundaryTypes(ICorDebugInfo::STACK_EMPTY_BOUNDARIES | ICorDebugInfo::CALL_SITE_BOUNDARIES); @@ -3379,13 +3379,10 @@ void Debugger::getVars(MethodDesc * md, ULONG32 *cVars, ICorDebugInfo::ILVarInfo // free to ignore *extendOthers *extendOthers = true; - DWORD bits = md->GetModule()->GetDebuggerInfoBits(); - if (CORDBUnrecoverableError(this)) goto Exit; - if (CORDisableJITOptimizations(bits)) -// if (!CORDebuggerAllowJITOpts(bits)) + if (md->GetModule()->AreJITOptimizationsDisabled()) { // // @TODO: Do we really need this code since *extendOthers==true? diff --git a/src/coreclr/debug/ee/debugger.h b/src/coreclr/debug/ee/debugger.h index a7d5335835fe15..9462407252388c 100644 --- a/src/coreclr/debug/ee/debugger.h +++ b/src/coreclr/debug/ee/debugger.h @@ -481,8 +481,6 @@ class DebuggerModule PTR_Module m_pRuntimeModule; PTR_DomainAssembly m_pRuntimeDomainAssembly; - bool m_fHasOptimizedCode; - // Can we change jit flags on the module? // This is true during the Module creation bool m_fCanChangeJitFlags; diff --git a/src/coreclr/debug/ee/debugger.inl b/src/coreclr/debug/ee/debugger.inl index 4f69164a292f9f..e9fc233e263741 100644 --- a/src/coreclr/debug/ee/debugger.inl +++ b/src/coreclr/debug/ee/debugger.inl @@ -60,10 +60,6 @@ inline DebuggerModule::DebuggerModule(Module * pRuntimeModule, LOG((LF_CORDB,LL_INFO10000, "DM::DM this:0x%x Module:0x%x DF:0x%x\n", this, pRuntimeModule, pDomainAssembly)); - // Do we have any optimized code? - DWORD dwDebugBits = pRuntimeModule->GetDebuggerInfoBits(); - m_fHasOptimizedCode = CORDebuggerAllowJITOpts(dwDebugBits); - // Dynamic modules must receive ClassLoad callbacks in order to receive metadata updates as the module // evolves. So we force this on here and refuse to change it for all dynamic modules. if (pRuntimeModule->IsReflectionEmit()) @@ -83,8 +79,7 @@ inline bool DebuggerModule::HasAnyOptimizedCode() { LIMITED_METHOD_CONTRACT; Module * pModule = GetRuntimeModule(); - DWORD dwDebugBits = pModule->GetDebuggerInfoBits(); - return CORDebuggerAllowJITOpts(dwDebugBits); + return !pModule->AreJITOptimizationsDisabled(); } //----------------------------------------------------------------------------- diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 4957ab388b463f..bb0f807ab00500 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -7891,6 +7891,13 @@ CorInfoInline CEEInfo::canInline (CORINFO_METHOD_HANDLE hCaller, } #ifdef PROFILING_SUPPORTED + if (pOrigCallerModule->IsInliningDisabledByProfiler()) + { + result = INLINE_FAIL; + szFailReason = "Inlining is disabled in the compiled method's module by a profiler"; + goto exit; + } + if (CORProfilerPresent()) { // #rejit @@ -7905,15 +7912,6 @@ CorInfoInline CEEInfo::canInline (CORINFO_METHOD_HANDLE hCaller, goto exit; } - // If the profiler has set a mask preventing inlining, always return - // false to the jit. - if (CORProfilerDisableInlining()) - { - result = INLINE_FAIL; - szFailReason = "Profiler disabled inlining globally"; - goto exit; - } - #if defined(FEATURE_REJIT) && !defined(DACCESS_COMPILE) if (CORProfilerEnableRejit()) { @@ -9569,7 +9567,7 @@ CorInfoTypeWithMod CEEInfo::getArgType ( case ELEMENT_TYPE_PTR: // Load the type eagerly under debugger to make the eval work - if (CORDisableJITOptimizations(pModule->GetDebuggerInfoBits())) + if (pModule->AreJITOptimizationsDisabled()) { // NOTE: in some IJW cases, when the type pointed at is unmanaged, // the GetTypeHandle may fail, because there is no TypeDef for such type. @@ -12898,7 +12896,7 @@ CORJIT_FLAGS GetDebuggerCompileFlags(Module* pModule, CORJIT_FLAGS flags) flags.Set(CORJIT_FLAGS::CORJIT_FLAG_DEBUG_INFO); #endif // DEBUGGING_SUPPORTED - if (CORDisableJITOptimizations(pModule->GetDebuggerInfoBits())) + if (pModule->AreJITOptimizationsDisabled()) { flags.Set(CORJIT_FLAGS::CORJIT_FLAG_DEBUG_CODE); } diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp index 5a103f193ac3fe..bafea6ab4e7d75 100644 --- a/src/coreclr/vm/method.cpp +++ b/src/coreclr/vm/method.cpp @@ -2888,7 +2888,7 @@ bool MethodDesc::IsJitOptimizationDisabledForAllMethodsInChunk() return g_pConfig->JitMinOpts() || g_pConfig->GenDebuggableCode() || - CORDisableJITOptimizations(GetModule()->GetDebuggerInfoBits()); + GetModule()->AreJITOptimizationsDisabled(); } #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index 52950710cfa2a9..0b00bf1647b3fd 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -4329,7 +4329,7 @@ void MethodTable::DoFullyLoad(Generics::RecursionGraph * const pVisited, const } if ((level == CLASS_LOADED) && - CORDisableJITOptimizations(this->GetModule()->GetDebuggerInfoBits()) && + this->GetModule()->AreJITOptimizationsDisabled() && !HasInstantiation() && !GetModule()->GetAssembly()->IsLoading()) // Do not do this during the vtable fixup stage of C++/CLI assembly loading. See https://github.com/dotnet/runtime/issues/110365 { diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index 73572da40a49a3..c03d1883222a61 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -532,6 +532,9 @@ inline bool CORDebuggerAttached() return (g_CORDebuggerControlFlags & DBCF_ATTACHED) && !IsAtProcessExit(); } +// This only check debugger bits. However JIT optimizations can be disabled by other ways on a module +// In most cases Module::AreJITOptimizationsDisabled() should be the prefered for checking if JIT optimizations +// are disabled for a module (it does check both debugger bits and profiler jit deoptimization flag) #define CORDebuggerAllowJITOpts(dwDebuggerBits) \ (((dwDebuggerBits) & DACF_ALLOW_JIT_OPTS) \ || \ @@ -544,45 +547,6 @@ inline bool CORDebuggerAttached() #define CORDebuggerTraceCall() \ (CORDebuggerAttached() && GetThread()->IsTraceCall()) - - -// -// Define stuff for precedence between profiling and debugging -// flags that can both be set. -// - -#if defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) - -#ifdef DEBUGGING_SUPPORTED - -#define CORDisableJITOptimizations(dwDebuggerBits) \ - (CORProfilerDisableOptimizations() || \ - !CORDebuggerAllowJITOpts(dwDebuggerBits)) - -#else // !DEBUGGING_SUPPORTED - -#define CORDisableJITOptimizations(dwDebuggerBits) \ - CORProfilerDisableOptimizations() - -#endif// DEBUGGING_SUPPORTED - -#else // !defined(PROFILING_SUPPORTED) && !defined(PROFILING_SUPPORTED_DATA) - -#ifdef DEBUGGING_SUPPORTED - -#define CORDisableJITOptimizations(dwDebuggerBits) \ - !CORDebuggerAllowJITOpts(dwDebuggerBits) - -#else // DEBUGGING_SUPPORTED - -#define CORDisableJITOptimizations(dwDebuggerBits) FALSE - -#endif// DEBUGGING_SUPPORTED - -#endif// defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) - - - #ifndef TARGET_UNIX GVAL_DECL(SIZE_T, g_runtimeLoadedBaseAddress); GVAL_DECL(SIZE_T, g_runtimeVirtualSize); From 6fedeeaeccb49a9ffaddfc0ab3e2a8c5ba7dba87 Mon Sep 17 00:00:00 2001 From: Simon Ferquel Date: Thu, 3 Apr 2025 10:36:15 +0200 Subject: [PATCH 4/5] Test for dynamic assignment of COR_PRF_DISABLE_OPTIMIZATIONS and COR_PRF_DISABLE_INLINING --- .../DynamicOptimization.cs | 196 ++++++++++++++++++ .../DynamicOptimization.csproj | 23 ++ .../DynamicOptimizationTestLib.cs | 24 +++ .../DynamicOptimizationTestLib.csproj | 14 ++ src/tests/profiler/native/CMakeLists.txt | 1 + src/tests/profiler/native/classfactory.cpp | 5 + .../dynamicjitoptimization.cpp | 107 ++++++++++ .../dynamicjitoptimization.h | 21 ++ src/tests/profiler/native/profiler.def | 3 + 9 files changed, 394 insertions(+) create mode 100644 src/tests/profiler/dynamicoptimization/DynamicOptimization.cs create mode 100644 src/tests/profiler/dynamicoptimization/DynamicOptimization.csproj create mode 100644 src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.cs create mode 100644 src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.csproj create mode 100644 src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.cpp create mode 100644 src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.h diff --git a/src/tests/profiler/dynamicoptimization/DynamicOptimization.cs b/src/tests/profiler/dynamicoptimization/DynamicOptimization.cs new file mode 100644 index 00000000000000..141a645aa05c49 --- /dev/null +++ b/src/tests/profiler/dynamicoptimization/DynamicOptimization.cs @@ -0,0 +1,196 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Linq; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +using Microsoft.Diagnostics.NETCore.Client; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Diagnostics.Tracing.Parsers; +using Microsoft.Diagnostics.Tracing.Parsers.Clr; +using Tracing.Tests.Common; + +namespace Profiler.Tests +{ + class DynamicOptimization + { + static readonly Guid DynamicOptimizationProfilerGuid = new Guid("C26D02FE-9E4C-484E-8984-F86724AA98B5"); + + public static int RunTest(String[] args) + { + // This test validates that: + // - Switching COR_PRF_DISABLE_OPTIMIZATIONS can be done dynamically (by calling SwitchJitOptimization) that makes the profiler update the event mask + // - Modules loaded while COR_PRF_DISABLE_OPTIMIZATIONS is 0 are Jitted with optimizations even if it set to 1 later + // - Modules loaded while COR_PRF_DISABLE_OPTIMIZATIONS is 1 are Jitted without optimization even if it is set to 0 later + + // to do so, we load the same assembly 3 time in different alc before / after enabling / disalbling COR_PRF_DISABLE_OPTIMIZATIONS. + // then we explicitly JIT a method in each of the loaded modules and use event traces to check that the method is loaded with an expected Optimization Tier + + // Then it make similar tests for inlining (we get inline counts from the profiler callbacks this time) + + // Load assembly in different states of COR_PRF_DISABLE_OPTIMIZATIONS + string currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string testAssemblyFullPath = Path.Combine(currentAssemblyDirectory, "..", "DynamicOptimizationTestLib", "DynamicOptimizationTestLib.dll"); + var beforeDisableOptimizations = new AssemblyLoadContext("before disable", true).LoadFromAssemblyPath(testAssemblyFullPath); + SwitchJitOptimization(true); + var afterDisableOptimizations = new AssemblyLoadContext("after disable", true).LoadFromAssemblyPath(testAssemblyFullPath); + SwitchJitOptimization(false); + var afterReenableOptimizations = new AssemblyLoadContext("after reenable", true).LoadFromAssemblyPath(testAssemblyFullPath); + + // JIT and check each case + Console.WriteLine("Trigger JIT and check that module loaded before disabling optimizations is not jitted with MinOpts"); + var r = CompileTestMethodAndCheckOptimizationTier(beforeDisableOptimizations, false); + if (r != 100) + { + return r; + } + Console.WriteLine("Trigger JIT and check that module loaded after disabling optimizations is jitted with MinOpts"); + r = CompileTestMethodAndCheckOptimizationTier(afterDisableOptimizations, true); + if (r != 100) + { + return r; + } + Console.WriteLine("Trigger JIT and check that module loaded after re-enabling optimizations is not jitted with MinOpts"); + r = CompileTestMethodAndCheckOptimizationTier(afterReenableOptimizations, false); + if (r != 100) + { + return r; + } + + // now we do a similar test for inlining + Console.WriteLine("Testing disabling inlining"); + + var beforeDisableInlining = new AssemblyLoadContext("before disable inlining", true).LoadFromAssemblyPath(testAssemblyFullPath); + SwitchInlining(true); + var afterDisableInlining = new AssemblyLoadContext("after disable inlining", true).LoadFromAssemblyPath(testAssemblyFullPath); + SwitchInlining(false); + var afterReenableInlining = new AssemblyLoadContext("after reenable inlining", true).LoadFromAssemblyPath(testAssemblyFullPath); + + var initialInlineCount = GetInlineCount(); + Console.WriteLine($"Before starting inlining tests, inline count is: {initialInlineCount}"); + // first case: inlining count should have increased + RuntimeHelpers.PrepareMethod(GetMainMethod(beforeDisableInlining).MethodHandle); + var actual = GetInlineCount(); + Console.WriteLine($"After jitting first case, inline count is: {actual}"); + var expected = initialInlineCount + 1; + if (expected != actual) + { + throw new Exception($"Expected {expected}, got {actual}"); + } + // first case: inlining count should not have increased + RuntimeHelpers.PrepareMethod(GetMainMethod(afterDisableInlining).MethodHandle); + actual = GetInlineCount(); + Console.WriteLine($"After jitting second case, inline count is: {actual}"); + if (expected != actual) + { + throw new Exception($"Expected {expected}, got {GetInlineCount()}"); + } + // third case: should have inlined + RuntimeHelpers.PrepareMethod(GetMainMethod(afterReenableInlining).MethodHandle); + actual = GetInlineCount(); + Console.WriteLine($"After jitting third case, inline count is: {actual}"); + expected++; + if (expected != actual) + { + throw new Exception($"Expected {expected}, got {GetInlineCount()}"); + } + + Console.WriteLine("PROFILER TEST PASSES"); + return 100; + } + + static EventPipeProvider _jitEventProvider = new EventPipeProvider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (long)ClrTraceEventParser.Keywords.Jit); + + static MethodInfo GetMainMethod(Assembly assembly) => assembly.GetType("Profiler.Tests.DynamicOptimizationTestLib").GetMethod("Main"); + static int CompileTestMethodAndCheckOptimizationTier(Assembly assembly, bool optimizationsDisabled) + { + var method = GetMainMethod(assembly); + + return + IpcTraceTest.RunAndValidateEventCounts( + new Dictionary(), + () => RuntimeHelpers.PrepareMethod(method.MethodHandle), + new List { _jitEventProvider }, + 1024, + optimizationsDisabled ? ValidateUnoptimized : ValidateOptimized); + } + + private static Func ValidateUnoptimized(EventPipeEventSource source) + { + OptimizationTier lastTier = OptimizationTier.Unknown; + + source.Clr.MethodLoadVerbose += e => + { + if (e.MethodName == "Main") + { + lastTier = e.OptimizationTier; + Console.WriteLine($"MethodLoadVerbose: {e}"); + } + }; + return () => + { + if (lastTier != OptimizationTier.MinOptJitted && lastTier != OptimizationTier.Unknown) + { + Console.WriteLine($"Expected MinOptJitted, got {lastTier}"); + return -1; + } + return 100; + }; + } + private static Func ValidateOptimized(EventPipeEventSource source) + { + OptimizationTier lastTier = OptimizationTier.Unknown; + + source.Clr.MethodLoadVerbose += e => + { + if (e.MethodName == "Main") + { + lastTier = e.OptimizationTier; + Console.WriteLine($"MethodLoadVerbose: {e}"); + } + }; + + return () => + { + if (lastTier == OptimizationTier.MinOptJitted) + { + Console.WriteLine($"Expected not MinOptJitted, got {lastTier}"); + return -1; + } + return 100; + }; + } + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return RunTest(args); + } + + return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location, + testName: "DynamicOptimization", + profilerClsid: DynamicOptimizationProfilerGuid); + } + + // this makes the profiler enable / disable COR_PRF_DISABLE_OPTIMIZATIONS dynamically + [DllImport("Profiler")] + public static extern int SwitchJitOptimization(bool disable); + + // this makes the profiler enable / disable COR_PRF_DISABLE_INLINING dynamically + [DllImport("Profiler")] + public static extern int SwitchInlining(bool disable); + + // this retrieves the number of inlining operations that occured + [DllImport("Profiler")] + public static extern int GetInlineCount(); + } +} diff --git a/src/tests/profiler/dynamicoptimization/DynamicOptimization.csproj b/src/tests/profiler/dynamicoptimization/DynamicOptimization.csproj new file mode 100644 index 00000000000000..588070e3d4b16c --- /dev/null +++ b/src/tests/profiler/dynamicoptimization/DynamicOptimization.csproj @@ -0,0 +1,23 @@ + + + .NETCoreApp + exe + true + true + + true + + true + + + + + + + + + + diff --git a/src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.cs b/src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.cs new file mode 100644 index 00000000000000..748840b9e65252 --- /dev/null +++ b/src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +namespace Profiler.Tests +{ + public class DynamicOptimizationTestLib + { + + public static int Main(string[] args) + { + return Inlinee(); + } + + // this should get inlined except if module is loaded while profiler has inlining disabled + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Inlinee() + { + return 100; + } + } +} diff --git a/src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.csproj b/src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.csproj new file mode 100644 index 00000000000000..f47300c2a5258a --- /dev/null +++ b/src/tests/profiler/dynamicoptimization/DynamicOptimizationTestLib.csproj @@ -0,0 +1,14 @@ + + + .NETCoreApp + exe + true + true + + + + + + + + diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt index 8e54167fdeaf0d..a0590d61d1127e 100644 --- a/src/tests/profiler/native/CMakeLists.txt +++ b/src/tests/profiler/native/CMakeLists.txt @@ -5,6 +5,7 @@ project(Profiler) set(SOURCES assemblyprofiler/assemblyprofiler.cpp classload/classload.cpp + dynamicjitoptimization/dynamicjitoptimization.cpp eltprofiler/slowpatheltprofiler.cpp enumthreadsprofiler/enumthreadsprofiler.cpp eventpipeprofiler/eventpipereadingprofiler.cpp diff --git a/src/tests/profiler/native/classfactory.cpp b/src/tests/profiler/native/classfactory.cpp index 1673b1d99de4e5..e7702bb6f1520f 100644 --- a/src/tests/profiler/native/classfactory.cpp +++ b/src/tests/profiler/native/classfactory.cpp @@ -23,6 +23,7 @@ #include "moduleload/moduleload.h" #include "assemblyprofiler/assemblyprofiler.h" #include "classload/classload.h" +#include "dynamicjitoptimization/dynamicjitoptimization.h" ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid) { @@ -154,6 +155,10 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI { profiler = new ClassLoad(); } + else if (clsid == DynamicJitOptimizations::GetClsid()) + { + profiler = new DynamicJitOptimizations(); + } else { printf("No profiler found in ClassFactory::CreateInstance. Did you add your profiler to the list?\n"); diff --git a/src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.cpp b/src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.cpp new file mode 100644 index 00000000000000..2c3bf3f66f2fb6 --- /dev/null +++ b/src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.cpp @@ -0,0 +1,107 @@ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "dynamicjitoptimization.h" + +static DynamicJitOptimizations* gInstance = nullptr; +static bool gDisableOptimizations = false; +static bool gDisableInlining = false; +static int gInlineCount = 0; + +GUID DynamicJitOptimizations::GetClsid() +{ + // {C26D02FE-9E4C-484E-8984-F86724AA98B5} + GUID clsid = { 0xc26d02fe, 0x9e4c, 0x484e, { 0x89, 0x84, 0xf8, 0x67, 0x24, 0xaa, 0x98, 0xb5 } }; + return clsid; +} + +int SetEventMask() +{ + if (!gInstance) + { + return -1; + } + DWORD mask = COR_PRF_MONITOR_JIT_COMPILATION; + if (gDisableOptimizations) + { + mask |= COR_PRF_DISABLE_OPTIMIZATIONS; + } + if (gDisableInlining) + { + mask |= COR_PRF_DISABLE_INLINING; + } + return gInstance->pCorProfilerInfo->SetEventMask2(mask, 0); +} + +HRESULT DynamicJitOptimizations::Initialize(IUnknown* pICorProfilerInfoUnk) +{ + Profiler::Initialize(pICorProfilerInfoUnk); + + gInstance = this; + return SetEventMask(); +} + +HRESULT DynamicJitOptimizations::JITInlining( + FunctionID callerId, + FunctionID calleeId, + BOOL *pfShouldInline) +{ + if (*pfShouldInline) + { + // filter for testee module + ClassID classId = 0; + ModuleID moduleId = 0; + mdToken token = 0; + ULONG32 nTypeArgs = 0; + ClassID typeArgs[SHORT_LENGTH]; + COR_PRF_FRAME_INFO frameInfo = 0; + + HRESULT hr = S_OK; + hr = pCorProfilerInfo->GetFunctionInfo2(callerId, + frameInfo, + &classId, + &moduleId, + &token, + SHORT_LENGTH, + &nTypeArgs, + typeArgs); + if (FAILED(hr)) + { + printf("FAIL: GetFunctionInfo2 call failed with hr=0x%x\n", hr); + return hr; + } + auto moduleName = GetModuleIDName(moduleId); + if (EndsWith(moduleName, WCHAR("DynamicOptimizationTestLib.dll"))) + { + gInlineCount++; + } + } + return S_OK; +} + +HRESULT DynamicJitOptimizations::Shutdown() +{ + Profiler::Shutdown(); + + gInstance = nullptr; + return S_OK; +} + + +extern "C" EXPORT int STDMETHODCALLTYPE SwitchJitOptimization(bool disable) +{ + gDisableOptimizations = disable; + return SetEventMask(); +} + + +extern "C" EXPORT int STDMETHODCALLTYPE SwitchInlining(bool disable) +{ + gDisableInlining = disable; + return SetEventMask(); +} + +extern "C" EXPORT int STDMETHODCALLTYPE GetInlineCount(){ + return gInlineCount; +} diff --git a/src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.h b/src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.h new file mode 100644 index 00000000000000..1c86f7cfc1047d --- /dev/null +++ b/src/tests/profiler/native/dynamicjitoptimization/dynamicjitoptimization.h @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "../profiler.h" + +class DynamicJitOptimizations : public Profiler +{ +public: + DynamicJitOptimizations() = default; + virtual ~DynamicJitOptimizations() = default; + + static GUID GetClsid(); + virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk) override; + virtual HRESULT STDMETHODCALLTYPE JITInlining( + FunctionID callerId, + FunctionID calleeId, + BOOL *pfShouldInline) override; + virtual HRESULT STDMETHODCALLTYPE Shutdown() override; +}; diff --git a/src/tests/profiler/native/profiler.def b/src/tests/profiler/native/profiler.def index 11976272a21331..f98e523f4359ca 100644 --- a/src/tests/profiler/native/profiler.def +++ b/src/tests/profiler/native/profiler.def @@ -6,4 +6,7 @@ EXPORTS PassCallbackToProfiler PRIVATE DoPInvoke PRIVATE DoPInvokeWithCallbackOnOtherThread PRIVATE + SwitchJitOptimization PRIVATE + SwitchInlining PRIVATE + GetInlineCount PRIVATE From 2907634e7bd3fe373fb33db6fba2b743a04f9582 Mon Sep 17 00:00:00 2001 From: Simon Ferquel Date: Fri, 4 Apr 2025 09:52:32 +0200 Subject: [PATCH 5/5] Update src/coreclr/vm/ceeload.cpp Co-authored-by: Jan Kotas --- src/coreclr/vm/ceeload.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index 8e835eca503401..3494962fe425df 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -480,10 +480,12 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_dwCustomAttributeCount = 0; #ifdef PROFILING_SUPPORTED // set profiler related JIT flags - if (CORProfilerDisableInlining()){ + if (CORProfilerDisableInlining()) + { m_dwTransientFlags |= PROF_DISABLE_INLINING; } - if (CORProfilerDisableOptimizations()){ + if (CORProfilerDisableOptimizations()) + { m_dwTransientFlags |= PROF_DISABLE_OPTIMIZATIONS; }