Skip to content

Commit 530271d

Browse files
CopilotEgorBo
andauthored
Fix #121066: Mark INITCLASS helpers as mutating heap to respect static constructor side-effects (#121069)
## Summary This PR fixes issue #121066 where JIT optimizations (inlining, CSE, and redundant branch removal) were not respecting the side-effects of precise static constructors, leading to incorrect program behavior. ## Problem The `CORINFO_HELP_INITCLASS` and `CORINFO_HELP_INITINSTCLASS` JIT helpers were incorrectly marked as `isPure = true` in `HelperCallProperties::init()`. This caused the JIT to treat these helpers as having no side effects, allowing optimizations to eliminate or reorder static constructor calls. However, static constructors can have observable side effects (modifying static fields, I/O operations, etc.) that must be preserved. Consider this reproduction case: ```csharp class MyPreciseInitClass<T> { static MyPreciseInitClass() { preciseInitCctorsRun++; // Side effect! } public static void TriggerCctorClass() { } } // With tiered compilation disabled MyPreciseInitClass<int>.TriggerCctorClass(); // Expected: preciseInitCctorsRun == 1 // Actual: preciseInitCctorsRun == 0 (constructor was optimized away!) ``` ## Changes **src/coreclr/jit/utils.cpp** - Removed `isPure = true` for `CORINFO_HELP_INITCLASS` and `CORINFO_HELP_INITINSTCLASS` cases - Added `mutatesHeap = true` to properly indicate these helpers modify program state **src/tests/JIT/Regression/JitBlue/Runtime_121066/** - Added regression test that verifies static constructors are invoked exactly once per generic instantiation - Test validates the fix by checking that side effects (counter increments) occur as expected ## Impact This change ensures that the JIT compiler treats static constructor initialization helpers as having side effects, preventing incorrect optimizations that could eliminate or reorder these critical operations. The performance impact should be minimal since: 1. Static constructors are already guarded by checks in most scenarios 2. The `mayRunCctor` flag was already set (indicating potential side effects) 3. This primarily affects edge cases with aggressive optimization Fixes #121066 > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `https://api.github.com/repos/dotnet/runtime/issues/121066` > - Triggering command: `curl -s REDACTED` (http block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/dotnet/runtime/settings/copilot/coding_agent) (admins only) > > </details> <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > Fix #121066 isssue: in jit's utils.cpp in the function HelperCallProperties::init() find > ```cpp > case CORINFO_HELP_INITCLASS: > case CORINFO_HELP_INITINSTCLASS: > ``` > and remove isPure = true for it and add mutatesHeap = true. > > Also, add the test case for it that was reported in #121066 (adapt it to be xunit test), similar to `src/tests/JIT/Regression/JitBlue/Runtime_120792/Runtime_120792.cs` (but with the issue id) </details> <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: EgorBo <[email protected]>
1 parent 8c6d20c commit 530271d

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

src/coreclr/jit/utils.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,7 @@ void HelperCallProperties::init()
17271727

17281728
case CORINFO_HELP_INITCLASS:
17291729
case CORINFO_HELP_INITINSTCLASS:
1730-
isPure = true;
1730+
mutatesHeap = true;
17311731
mayRunCctor = true;
17321732
break;
17331733

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
using Xunit;
7+
8+
public class Runtime_121066
9+
{
10+
public static int preciseInitCctorsRun = 0;
11+
12+
class MyPreciseInitClass<T>
13+
{
14+
static MyPreciseInitClass()
15+
{
16+
preciseInitCctorsRun++;
17+
}
18+
19+
public static void TriggerCctorClass()
20+
{
21+
}
22+
23+
public static void TriggerCctorMethod<U>()
24+
{ }
25+
}
26+
27+
class MyClass<T>
28+
{
29+
static Type staticVarType = typeof(MyClass<T>);
30+
public Type GetTypeOf()
31+
{
32+
return typeof(MyClass<T>);
33+
}
34+
public static Type GetTypeOfStatic()
35+
{
36+
return typeof(MyClass<T>);
37+
}
38+
39+
public static Type GetTypeThroughStaticVar()
40+
{
41+
return staticVarType;
42+
}
43+
}
44+
45+
[Fact]
46+
public static void TestEntryPoint()
47+
{
48+
Assert.True(TestPreciseInitCctors());
49+
}
50+
51+
public static bool TestPreciseInitCctors()
52+
{
53+
if (preciseInitCctorsRun != 0)
54+
{
55+
Console.WriteLine("preciseInitCctorsRun should be 0, but is {0}", preciseInitCctorsRun);
56+
return false;
57+
}
58+
MyPreciseInitClass<int>.TriggerCctorClass();
59+
if (preciseInitCctorsRun != 1)
60+
{
61+
Console.WriteLine("preciseInitCctorsRun should be 1, but is {0}", preciseInitCctorsRun);
62+
return false;
63+
}
64+
MyPreciseInitClass<short>.TriggerCctorMethod<int>();
65+
if (preciseInitCctorsRun != 2)
66+
{
67+
Console.WriteLine("TriggerCctorClass should return 2, but is {0}", preciseInitCctorsRun);
68+
return false;
69+
}
70+
71+
object o = new MyPreciseInitClass<double>();
72+
if (preciseInitCctorsRun != 3)
73+
{
74+
Console.WriteLine("TriggerCctorClass should return 3, but is {0}", preciseInitCctorsRun);
75+
return false;
76+
}
77+
78+
MyPreciseInitClass<object>.TriggerCctorClass();
79+
if (preciseInitCctorsRun != 4)
80+
{
81+
Console.WriteLine("preciseInitCctorsRun should be 4 but is {0}", preciseInitCctorsRun);
82+
return false;
83+
}
84+
MyPreciseInitClass<string>.TriggerCctorMethod<object>();
85+
if (preciseInitCctorsRun != 5)
86+
{
87+
Console.WriteLine("TriggerCctorClass should return 5, but is {0}", preciseInitCctorsRun);
88+
return false;
89+
}
90+
91+
o = new MyPreciseInitClass<Type>();
92+
if (preciseInitCctorsRun != 6)
93+
{
94+
Console.WriteLine("TriggerCctorClass should return 6, but is {0}", preciseInitCctorsRun);
95+
return false;
96+
}
97+
98+
return true;
99+
}
100+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<DebugType>None</DebugType>
4+
<Optimize>True</Optimize>
5+
<!-- Needed for CLRTestEnvironmentVariable -->
6+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<Compile Include="$(MSBuildProjectName).cs" />
10+
<CLRTestEnvironmentVariable Include="DOTNET_TieredCompilation" Value="0" />
11+
</ItemGroup>
12+
</Project>

0 commit comments

Comments
 (0)