Skip to content

Commit 954ebc3

Browse files
committed
Test for dynamic assignment of COR_PRF_DISABLE_OPTIMIZATIONS and COR_PRF_DISABLE_INLINING
1 parent 5960220 commit 954ebc3

File tree

9 files changed

+394
-0
lines changed

9 files changed

+394
-0
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Diagnostics.Tracing;
8+
using System.Linq;
9+
using System.IO;
10+
using System.Reflection;
11+
using System.Runtime.CompilerServices;
12+
using System.Runtime.InteropServices;
13+
using System.Runtime.Loader;
14+
15+
using Microsoft.Diagnostics.NETCore.Client;
16+
using Microsoft.Diagnostics.Tracing;
17+
using Microsoft.Diagnostics.Tracing.Parsers;
18+
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
19+
using Tracing.Tests.Common;
20+
21+
namespace Profiler.Tests
22+
{
23+
class DynamicOptimization
24+
{
25+
static readonly Guid DynamicOptimizationProfilerGuid = new Guid("C26D02FE-9E4C-484E-8984-F86724AA98B5");
26+
27+
public static int RunTest(String[] args)
28+
{
29+
// This test validates that:
30+
// - Switching COR_PRF_DISABLE_OPTIMIZATIONS can be done dynamically (by calling SwitchJitOptimization) that makes the profiler update the event mask
31+
// - Modules loaded while COR_PRF_DISABLE_OPTIMIZATIONS is 0 are Jitted with optimizations even if it set to 1 later
32+
// - Modules loaded while COR_PRF_DISABLE_OPTIMIZATIONS is 1 are Jitted without optimization even if it is set to 0 later
33+
34+
// to do so, we load the same assembly 3 time in different alc before / after enabling / disalbling COR_PRF_DISABLE_OPTIMIZATIONS.
35+
// 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
36+
37+
// Then it make similar tests for inlining (we get inline counts from the profiler callbacks this time)
38+
39+
// Load assembly in different states of COR_PRF_DISABLE_OPTIMIZATIONS
40+
string currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
41+
string testAssemblyFullPath = Path.Combine(currentAssemblyDirectory, "..", "DynamicOptimizationTestLib", "DynamicOptimizationTestLib.dll");
42+
var beforeDisableOptimizations = new AssemblyLoadContext("before disable", true).LoadFromAssemblyPath(testAssemblyFullPath);
43+
SwitchJitOptimization(true);
44+
var afterDisableOptimizations = new AssemblyLoadContext("after disable", true).LoadFromAssemblyPath(testAssemblyFullPath);
45+
SwitchJitOptimization(false);
46+
var afterReenableOptimizations = new AssemblyLoadContext("after reenable", true).LoadFromAssemblyPath(testAssemblyFullPath);
47+
48+
// JIT and check each case
49+
Console.WriteLine("Trigger JIT and check that module loaded before disabling optimizations is not jitted with MinOpts");
50+
var r = CompileTestMethodAndCheckOptimizationTier(beforeDisableOptimizations, false);
51+
if (r != 100)
52+
{
53+
return r;
54+
}
55+
Console.WriteLine("Trigger JIT and check that module loaded after disabling optimizations is jitted with MinOpts");
56+
r = CompileTestMethodAndCheckOptimizationTier(afterDisableOptimizations, true);
57+
if (r != 100)
58+
{
59+
return r;
60+
}
61+
Console.WriteLine("Trigger JIT and check that module loaded after re-enabling optimizations is not jitted with MinOpts");
62+
r = CompileTestMethodAndCheckOptimizationTier(afterReenableOptimizations, false);
63+
if (r != 100)
64+
{
65+
return r;
66+
}
67+
68+
// now we do a similar test for inlining
69+
Console.WriteLine("Testing disabling inlining");
70+
71+
var beforeDisableInlining = new AssemblyLoadContext("before disable inlining", true).LoadFromAssemblyPath(testAssemblyFullPath);
72+
SwitchInlining(true);
73+
var afterDisableInlining = new AssemblyLoadContext("after disable inlining", true).LoadFromAssemblyPath(testAssemblyFullPath);
74+
SwitchInlining(false);
75+
var afterReenableInlining = new AssemblyLoadContext("after reenable inlining", true).LoadFromAssemblyPath(testAssemblyFullPath);
76+
77+
var initialInlineCount = GetInlineCount();
78+
Console.WriteLine($"Before starting inlining tests, inline count is: {initialInlineCount}");
79+
// first case: inlining count should have increased
80+
RuntimeHelpers.PrepareMethod(GetMainMethod(beforeDisableInlining).MethodHandle);
81+
var actual = GetInlineCount();
82+
Console.WriteLine($"After jitting first case, inline count is: {actual}");
83+
var expected = initialInlineCount + 1;
84+
if (expected != actual)
85+
{
86+
throw new Exception($"Expected {expected}, got {actual}");
87+
}
88+
// first case: inlining count should not have increased
89+
RuntimeHelpers.PrepareMethod(GetMainMethod(afterDisableInlining).MethodHandle);
90+
actual = GetInlineCount();
91+
Console.WriteLine($"After jitting second case, inline count is: {actual}");
92+
if (expected != actual)
93+
{
94+
throw new Exception($"Expected {expected}, got {GetInlineCount()}");
95+
}
96+
// third case: should have inlined
97+
RuntimeHelpers.PrepareMethod(GetMainMethod(afterReenableInlining).MethodHandle);
98+
actual = GetInlineCount();
99+
Console.WriteLine($"After jitting third case, inline count is: {actual}");
100+
expected++;
101+
if (expected != actual)
102+
{
103+
throw new Exception($"Expected {expected}, got {GetInlineCount()}");
104+
}
105+
106+
Console.WriteLine("PROFILER TEST PASSES");
107+
return 100;
108+
}
109+
110+
static EventPipeProvider _jitEventProvider = new EventPipeProvider("Microsoft-Windows-DotNETRuntime", eventLevel: EventLevel.Verbose, keywords: (long)ClrTraceEventParser.Keywords.Jit);
111+
112+
static MethodInfo GetMainMethod(Assembly assembly) => assembly.GetType("Profiler.Tests.DynamicOptimizationTestLib").GetMethod("Main");
113+
static int CompileTestMethodAndCheckOptimizationTier(Assembly assembly, bool optimizationsDisabled)
114+
{
115+
var method = GetMainMethod(assembly);
116+
117+
return
118+
IpcTraceTest.RunAndValidateEventCounts(
119+
new Dictionary<string, ExpectedEventCount>(),
120+
() => RuntimeHelpers.PrepareMethod(method.MethodHandle),
121+
new List<EventPipeProvider> { _jitEventProvider },
122+
1024,
123+
optimizationsDisabled ? ValidateUnoptimized : ValidateOptimized);
124+
}
125+
126+
private static Func<int> ValidateUnoptimized(EventPipeEventSource source)
127+
{
128+
OptimizationTier lastTier = OptimizationTier.Unknown;
129+
130+
source.Clr.MethodLoadVerbose += e =>
131+
{
132+
if (e.MethodName == "Main")
133+
{
134+
lastTier = e.OptimizationTier;
135+
Console.WriteLine($"MethodLoadVerbose: {e}");
136+
}
137+
};
138+
return () =>
139+
{
140+
if (lastTier != OptimizationTier.MinOptJitted && lastTier != OptimizationTier.Unknown)
141+
{
142+
Console.WriteLine($"Expected MinOptJitted, got {lastTier}");
143+
return -1;
144+
}
145+
return 100;
146+
};
147+
}
148+
private static Func<int> ValidateOptimized(EventPipeEventSource source)
149+
{
150+
OptimizationTier lastTier = OptimizationTier.Unknown;
151+
152+
source.Clr.MethodLoadVerbose += e =>
153+
{
154+
if (e.MethodName == "Main")
155+
{
156+
lastTier = e.OptimizationTier;
157+
Console.WriteLine($"MethodLoadVerbose: {e}");
158+
}
159+
};
160+
161+
return () =>
162+
{
163+
if (lastTier == OptimizationTier.MinOptJitted)
164+
{
165+
Console.WriteLine($"Expected not MinOptJitted, got {lastTier}");
166+
return -1;
167+
}
168+
return 100;
169+
};
170+
}
171+
172+
public static int Main(string[] args)
173+
{
174+
if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase))
175+
{
176+
return RunTest(args);
177+
}
178+
179+
return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location,
180+
testName: "DynamicOptimization",
181+
profilerClsid: DynamicOptimizationProfilerGuid);
182+
}
183+
184+
// this makes the profiler enable / disable COR_PRF_DISABLE_OPTIMIZATIONS dynamically
185+
[DllImport("Profiler")]
186+
public static extern int SwitchJitOptimization(bool disable);
187+
188+
// this makes the profiler enable / disable COR_PRF_DISABLE_INLINING dynamically
189+
[DllImport("Profiler")]
190+
public static extern int SwitchInlining(bool disable);
191+
192+
// this retrieves the number of inlining operations that occured
193+
[DllImport("Profiler")]
194+
public static extern int GetInlineCount();
195+
}
196+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
4+
<OutputType>exe</OutputType>
5+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
6+
<Optimize>true</Optimize>
7+
<!-- The test launches a secondary process and process launch creates
8+
an infinite event loop in the SocketAsyncEngine on Linux. Since
9+
runincontext loads even framework assemblies into the unloadable
10+
context, locals in this loop prevent unloading -->
11+
<UnloadabilityIncompatible>true</UnloadabilityIncompatible>
12+
<!-- Temporarily disabled due to https://github.com/dotnet/runtime/issues/106241 -->
13+
<GCStressIncompatible>true</GCStressIncompatible>
14+
</PropertyGroup>
15+
<ItemGroup>
16+
<Compile Include="$(MSBuildProjectName).cs" />
17+
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
18+
<ProjectReference Include="../common/profiler_common.csproj" />
19+
<CMakeProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
20+
<ProjectReference Include="../../tracing/eventpipe/common/eventpipe_common.csproj" />
21+
<ProjectReference Include="../../tracing/eventpipe/common/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj" />
22+
</ItemGroup>
23+
</Project>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace Profiler.Tests
8+
{
9+
public class DynamicOptimizationTestLib
10+
{
11+
12+
public static int Main(string[] args)
13+
{
14+
return Inlinee();
15+
}
16+
17+
// this should get inlined except if module is loaded while profiler has inlining disabled
18+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
19+
public static int Inlinee()
20+
{
21+
return 100;
22+
}
23+
}
24+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
4+
<OutputType>exe</OutputType>
5+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
6+
<Optimize>true</Optimize>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="$(MSBuildProjectName).cs" />
10+
<ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
11+
<ProjectReference Include="../common/profiler_common.csproj" />
12+
<CMakeProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
13+
</ItemGroup>
14+
</Project>

src/tests/profiler/native/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ project(Profiler)
44

55
set(SOURCES
66
assemblyprofiler/assemblyprofiler.cpp
7+
dynamicjitoptimization/dynamicjitoptimization.cpp
78
eltprofiler/slowpatheltprofiler.cpp
89
enumthreadsprofiler/enumthreadsprofiler.cpp
910
eventpipeprofiler/eventpipereadingprofiler.cpp

src/tests/profiler/native/classfactory.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "inlining/inlining.h"
2323
#include "moduleload/moduleload.h"
2424
#include "assemblyprofiler/assemblyprofiler.h"
25+
#include "dynamicjitoptimization/dynamicjitoptimization.h"
2526

2627
ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid)
2728
{
@@ -149,6 +150,10 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI
149150
{
150151
profiler = new EnumThreadsProfiler();
151152
}
153+
else if (clsid == DynamicJitOptimizations::GetClsid())
154+
{
155+
profiler = new DynamicJitOptimizations();
156+
}
152157
else
153158
{
154159
printf("No profiler found in ClassFactory::CreateInstance. Did you add your profiler to the list?\n");
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
5+
#include "dynamicjitoptimization.h"
6+
7+
static DynamicJitOptimizations* gInstance = nullptr;
8+
static bool gDisableOptimizations = false;
9+
static bool gDisableInlining = false;
10+
static int gInlineCount = 0;
11+
12+
GUID DynamicJitOptimizations::GetClsid()
13+
{
14+
// {C26D02FE-9E4C-484E-8984-F86724AA98B5}
15+
GUID clsid = { 0xc26d02fe, 0x9e4c, 0x484e, { 0x89, 0x84, 0xf8, 0x67, 0x24, 0xaa, 0x98, 0xb5 } };
16+
return clsid;
17+
}
18+
19+
int SetEventMask()
20+
{
21+
if (!gInstance)
22+
{
23+
return -1;
24+
}
25+
DWORD mask = COR_PRF_MONITOR_JIT_COMPILATION;
26+
if (gDisableOptimizations)
27+
{
28+
mask |= COR_PRF_DISABLE_OPTIMIZATIONS;
29+
}
30+
if (gDisableInlining)
31+
{
32+
mask |= COR_PRF_DISABLE_INLINING;
33+
}
34+
return gInstance->pCorProfilerInfo->SetEventMask2(mask, 0);
35+
}
36+
37+
HRESULT DynamicJitOptimizations::Initialize(IUnknown* pICorProfilerInfoUnk)
38+
{
39+
Profiler::Initialize(pICorProfilerInfoUnk);
40+
41+
gInstance = this;
42+
return SetEventMask();
43+
}
44+
45+
HRESULT DynamicJitOptimizations::JITInlining(
46+
FunctionID callerId,
47+
FunctionID calleeId,
48+
BOOL *pfShouldInline)
49+
{
50+
if (*pfShouldInline)
51+
{
52+
// filter for testee module
53+
ClassID classId = 0;
54+
ModuleID moduleId = 0;
55+
mdToken token = 0;
56+
ULONG32 nTypeArgs = 0;
57+
ClassID typeArgs[SHORT_LENGTH];
58+
COR_PRF_FRAME_INFO frameInfo = 0;
59+
60+
HRESULT hr = S_OK;
61+
hr = pCorProfilerInfo->GetFunctionInfo2(callerId,
62+
frameInfo,
63+
&classId,
64+
&moduleId,
65+
&token,
66+
SHORT_LENGTH,
67+
&nTypeArgs,
68+
typeArgs);
69+
if (FAILED(hr))
70+
{
71+
printf("FAIL: GetFunctionInfo2 call failed with hr=0x%x\n", hr);
72+
return hr;
73+
}
74+
auto moduleName = GetModuleIDName(moduleId);
75+
if (EndsWith(moduleName, WCHAR("DynamicOptimizationTestLib.dll")))
76+
{
77+
gInlineCount++;
78+
}
79+
}
80+
return S_OK;
81+
}
82+
83+
HRESULT DynamicJitOptimizations::Shutdown()
84+
{
85+
Profiler::Shutdown();
86+
87+
gInstance = nullptr;
88+
return S_OK;
89+
}
90+
91+
92+
extern "C" EXPORT int STDMETHODCALLTYPE SwitchJitOptimization(bool disable)
93+
{
94+
gDisableOptimizations = disable;
95+
return SetEventMask();
96+
}
97+
98+
99+
extern "C" EXPORT int STDMETHODCALLTYPE SwitchInlining(bool disable)
100+
{
101+
gDisableInlining = disable;
102+
return SetEventMask();
103+
}
104+
105+
extern "C" EXPORT int STDMETHODCALLTYPE GetInlineCount(){
106+
return gInlineCount;
107+
}

0 commit comments

Comments
 (0)