Skip to content

Commit f392712

Browse files
JIT: Fix physical promotion creating overlapping local uses (#91088)
Physical promotion could in some cases create uses overlapping illegally with defs when faced with seemingly last uses of structs. This is a result of a mismatch between our model for liveness and the actual model of local uses in the backend. In the actual model, the uses of LCL_VARs occur at the user, which means that it is possible for there to be no place at which to insert IR between to local uses. The example looks like the following. Physical promotion would be faced with a tree like ``` ▌ CALL void Program:Foo(Program+S,Program+S) ├──▌ LCL_VAR struct<Program+S, 4> V01 loc0 └──▌ LCL_VAR struct<Program+S, 4> V01 loc0 ``` When V01 was fully promoted, both of these are logically last uses since all state of V01 is stored in promoted field locals. Because of that we would make the following transformation: ``` ▌ CALL void Program:Foo(Program+S,Program+S) ├──▌ LCL_VAR struct<Program+S, 4> V01 loc0 (last use) └──▌ COMMA struct ├──▌ STORE_LCL_FLD int V01 loc0 [+0] │ └──▌ LCL_VAR int V02 tmp0 └──▌ LCL_VAR struct<Program+S, 4> V01 loc0 (last use) ``` This creates an illegally overlapping use and def; additionally, it is correct only in a world where the store actually would happen between the two uses. It is also moderately dangerous to mark both of these as last uses given the implicit byref transformation. The fix is to avoid marking a struct use as a last use if we see more struct uses in the same statement. Fix #91056 Co-authored-by: Jakob Botsch Nielsen <[email protected]>
1 parent 94662e1 commit f392712

File tree

5 files changed

+87
-5
lines changed

5 files changed

+87
-5
lines changed

src/coreclr/jit/promotion.cpp

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2340,8 +2340,36 @@ void ReplaceVisitor::ReadBackAfterCall(GenTreeCall* call, GenTree* user)
23402340
//
23412341
// If the remainder of the struct local is dying, then we expect that this
23422342
// entire struct local is now dying, since all field accesses are going to be
2343-
// replaced with other locals. The exception is if there is a queued read
2344-
// back for any of the fields.
2343+
// replaced with other locals.
2344+
//
2345+
// There are two exceptions to the above:
2346+
//
2347+
// 1) If there is a queued readback for any of the fields, then there is
2348+
// live state in the struct local, so it is not dying.
2349+
//
2350+
// 2) If there are further uses of the local in the same statement then we cannot
2351+
// actually act on the last-use information we would provide here. That's because
2352+
// uses of locals occur at the user and we do not model that here. In the real model
2353+
// there are cases where we do not have any place to insert any IR between the two uses.
2354+
// For example, consider:
2355+
//
2356+
// ▌ CALL void Program:Foo(Program+S,Program+S)
2357+
// ├──▌ LCL_VAR struct<Program+S, 4> V01 loc0
2358+
// └──▌ LCL_VAR struct<Program+S, 4> V01 loc0
2359+
//
2360+
// If V01 is promoted fully then both uses of V01 are last uses here; but
2361+
// replacing the IR with
2362+
//
2363+
// ▌ CALL void Program:Foo(Program+S,Program+S)
2364+
// ├──▌ LCL_VAR struct<Program+S, 4> V01 loc0 (last use)
2365+
// └──▌ COMMA struct
2366+
// ├──▌ STORE_LCL_FLD int V01 loc0 [+0]
2367+
// │ └──▌ LCL_VAR int V02 tmp0
2368+
// └──▌ LCL_VAR struct<Program+S, 4> V01 loc0 (last use)
2369+
//
2370+
// would be illegal since the created store overlaps with the first local,
2371+
// and does not take into account that both uses occur simultaneously at
2372+
// the position of the CALL node.
23452373
//
23462374
bool ReplaceVisitor::IsPromotedStructLocalDying(GenTreeLclVarCommon* lcl)
23472375
{
@@ -2362,6 +2390,15 @@ bool ReplaceVisitor::IsPromotedStructLocalDying(GenTreeLclVarCommon* lcl)
23622390
}
23632391
}
23642392

2393+
for (GenTree* cur = lcl->gtNext; cur != nullptr; cur = cur->gtNext)
2394+
{
2395+
assert(cur->OperIsAnyLocal());
2396+
if (cur->TypeIs(TYP_STRUCT) && (cur->AsLclVarCommon()->GetLclNum() == lcl->GetLclNum()))
2397+
{
2398+
return false;
2399+
}
2400+
}
2401+
23652402
return true;
23662403
}
23672404

@@ -2577,7 +2614,7 @@ void ReplaceVisitor::WriteBackBeforeCurrentStatement(unsigned lcl, unsigned offs
25772614

25782615
GenTree* readBack = Promotion::CreateWriteBack(m_compiler, lcl, rep);
25792616
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack);
2580-
JITDUMP("Writing back %s before " FMT_STMT "\n", rep.Description, stmt->GetID());
2617+
JITDUMP("Writing back %s before " FMT_STMT "\n", rep.Description, m_currentStmt->GetID());
25812618
DISPSTMT(stmt);
25822619
m_compiler->fgInsertStmtBefore(m_currentBlock, m_currentStmt, stmt);
25832620
ClearNeedsWriteBack(rep);

src/tests/JIT/Regression/JitBlue/Runtime_81725/Runtime_81725.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
</PropertyGroup>
88
<ItemGroup>
99
<Compile Include="$(MSBuildProjectName).cs" />
10-
</ItemGroup>
11-
<ItemGroup>
1210
<CLRTestEnvironmentVariable Include="DOTNET_JitStressModeNames" Value="STRESS_FOLD" />
1311
</ItemGroup>
1412
</Project>

src/tests/JIT/Regression/JitBlue/Runtime_85088/Runtime_85088.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3+
<!-- Needed for CLRTestEnvironmentVariable -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
35
<Optimize>True</Optimize>
46
</PropertyGroup>
57
<ItemGroup>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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 Xunit;
5+
using System.Runtime.CompilerServices;
6+
7+
public class Runtime_91056
8+
{
9+
[Fact]
10+
public static void TestEntryPoint()
11+
{
12+
S s = default;
13+
if (False())
14+
{
15+
s.A = 1234;
16+
}
17+
18+
Foo(0, 0, s, s);
19+
}
20+
21+
[MethodImpl(MethodImplOptions.NoInlining)]
22+
private static bool False() => false;
23+
24+
[MethodImpl(MethodImplOptions.NoInlining)]
25+
private static void Foo(int a, int b, S s1, S s2)
26+
{
27+
}
28+
29+
public struct S
30+
{
31+
public int A;
32+
}
33+
}
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+
<!-- Needed for CLRTestEnvironmentVariable -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
5+
<Optimize>True</Optimize>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<Compile Include="$(MSBuildProjectName).cs" />
9+
<CLRTestEnvironmentVariable Include="DOTNET_JitStressModeNames" Value="STRESS_PHYSICAL_PROMOTION STRESS_PHYSICAL_PROMOTION_COST STRESS_NO_OLD_PROMOTION" />
10+
<CLRTestEnvironmentVariable Include="DOTNET_JitNoCSE" Value="1" />
11+
</ItemGroup>
12+
</Project>

0 commit comments

Comments
 (0)