Skip to content

Commit fc3708f

Browse files
[release/9.0-staging] JIT: Read back all replacements before statements with implicit EH control flow (#109143)
Physical promotion sometimes needs to insert read backs of all stale pending replacements when it encounters potential implicit EH control flow (that is, intra-function control flow because of locally caught exceptions). It would be possible for this to interact with QMARKs such that we inserted readbacks inside one of the branches, yet believed we had read back the replacement on all paths. The fix here is to indiscriminately start reading back all replacements before a statement that may cause potential intra-function control flow to occur. Fix #108969
1 parent 30c4237 commit fc3708f

File tree

4 files changed

+126
-24
lines changed

4 files changed

+126
-24
lines changed

src/coreclr/jit/promotion.cpp

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1885,41 +1885,79 @@ void ReplaceVisitor::InsertPreStatementReadBacks()
18851885
// 3. Creating embedded stores in ReplaceLocal disables local copy prop for
18861886
// that local (see ReplaceLocal).
18871887

1888-
for (GenTreeLclVarCommon* lcl : m_currentStmt->LocalsTreeList())
1888+
// Normally, we read back only for the uses we will see. However, with
1889+
// implicit EH flow we may also read back all replacements mid-tree (see
1890+
// InsertMidTreeReadBacks). So for that case we read back everything. This
1891+
// is a correctness requirement for QMARKs, but we do it indiscriminately
1892+
// for the same reasons as mentioned above.
1893+
if (((m_currentStmt->GetRootNode()->gtFlags & (GTF_EXCEPT | GTF_CALL)) != 0) &&
1894+
m_compiler->ehBlockHasExnFlowDsc(m_currentBlock))
18891895
{
1890-
if (lcl->TypeIs(TYP_STRUCT))
1891-
{
1892-
continue;
1893-
}
1896+
JITDUMP(
1897+
"Reading back pending replacements before statement with possible exception side effect inside block in try region\n");
18941898

1895-
AggregateInfo* agg = m_aggregates.Lookup(lcl->GetLclNum());
1896-
if (agg == nullptr)
1899+
for (AggregateInfo* agg : m_aggregates)
18971900
{
1898-
continue;
1901+
for (Replacement& rep : agg->Replacements)
1902+
{
1903+
InsertPreStatementReadBackIfNecessary(agg->LclNum, rep);
1904+
}
18991905
}
1900-
1901-
size_t index = Promotion::BinarySearch<Replacement, &Replacement::Offset>(agg->Replacements, lcl->GetLclOffs());
1902-
if ((ssize_t)index < 0)
1906+
}
1907+
else
1908+
{
1909+
// Otherwise just read back the locals we see uses of.
1910+
for (GenTreeLclVarCommon* lcl : m_currentStmt->LocalsTreeList())
19031911
{
1904-
continue;
1905-
}
1912+
if (lcl->TypeIs(TYP_STRUCT))
1913+
{
1914+
continue;
1915+
}
19061916

1907-
Replacement& rep = agg->Replacements[index];
1908-
if (rep.NeedsReadBack)
1909-
{
1910-
JITDUMP("Reading back replacement V%02u.[%03u..%03u) -> V%02u before [%06u]:\n", agg->LclNum, rep.Offset,
1911-
rep.Offset + genTypeSize(rep.AccessType), rep.LclNum,
1912-
Compiler::dspTreeID(m_currentStmt->GetRootNode()));
1917+
AggregateInfo* agg = m_aggregates.Lookup(lcl->GetLclNum());
1918+
if (agg == nullptr)
1919+
{
1920+
continue;
1921+
}
19131922

1914-
GenTree* readBack = Promotion::CreateReadBack(m_compiler, agg->LclNum, rep);
1915-
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack);
1916-
DISPSTMT(stmt);
1917-
m_compiler->fgInsertStmtBefore(m_currentBlock, m_currentStmt, stmt);
1918-
ClearNeedsReadBack(rep);
1923+
size_t index =
1924+
Promotion::BinarySearch<Replacement, &Replacement::Offset>(agg->Replacements, lcl->GetLclOffs());
1925+
if ((ssize_t)index < 0)
1926+
{
1927+
continue;
1928+
}
1929+
1930+
InsertPreStatementReadBackIfNecessary(agg->LclNum, agg->Replacements[index]);
19191931
}
19201932
}
19211933
}
19221934

1935+
//------------------------------------------------------------------------
1936+
// InsertPreStatementReadBackIfNecessary:
1937+
// Insert a read back of the specified replacement before the current
1938+
// statement, if the replacement needs it.
1939+
//
1940+
// Parameters:
1941+
// aggLclNum - Struct local
1942+
// rep - The replacement
1943+
//
1944+
void ReplaceVisitor::InsertPreStatementReadBackIfNecessary(unsigned aggLclNum, Replacement& rep)
1945+
{
1946+
if (!rep.NeedsReadBack)
1947+
{
1948+
return;
1949+
}
1950+
1951+
JITDUMP("Reading back replacement V%02u.[%03u..%03u) -> V%02u before [%06u]:\n", aggLclNum, rep.Offset,
1952+
rep.Offset + genTypeSize(rep.AccessType), rep.LclNum, Compiler::dspTreeID(m_currentStmt->GetRootNode()));
1953+
1954+
GenTree* readBack = Promotion::CreateReadBack(m_compiler, aggLclNum, rep);
1955+
Statement* stmt = m_compiler->fgNewStmtFromTree(readBack);
1956+
DISPSTMT(stmt);
1957+
m_compiler->fgInsertStmtBefore(m_currentBlock, m_currentStmt, stmt);
1958+
ClearNeedsReadBack(rep);
1959+
}
1960+
19231961
//------------------------------------------------------------------------
19241962
// VisitOverlappingReplacements:
19251963
// Call a function for every replacement that overlaps a specified segment.

src/coreclr/jit/promotion.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ class ReplaceVisitor : public GenTreeVisitor<ReplaceVisitor>
290290
bool VisitOverlappingReplacements(unsigned lcl, unsigned offs, unsigned size, Func func);
291291

292292
void InsertPreStatementReadBacks();
293+
void InsertPreStatementReadBackIfNecessary(unsigned aggLclNum, Replacement& rep);
293294
void InsertPreStatementWriteBacks();
294295
GenTree** InsertMidTreeReadBacks(GenTree** use);
295296

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 System.Runtime.Intrinsics;
7+
using Xunit;
8+
9+
public class Runtime_108969
10+
{
11+
[Fact]
12+
public static int TestEntryPoint() => Foo(null);
13+
14+
[MethodImpl(MethodImplOptions.NoInlining)]
15+
private static int Foo(object o)
16+
{
17+
S v = default;
18+
try
19+
{
20+
v = Bar();
21+
22+
// "(int?)o" creates a QMARK with a branch that may throw; we would
23+
// end up reading back v.A inside the QMARK
24+
Use((int?)o);
25+
}
26+
catch (Exception)
27+
{
28+
}
29+
30+
// Induce promotion of v.A field
31+
Use(v.A);
32+
Use(v.A);
33+
Use(v.A);
34+
Use(v.A);
35+
Use(v.A);
36+
Use(v.A);
37+
return v.A;
38+
}
39+
40+
[MethodImpl(MethodImplOptions.NoInlining)]
41+
private static S Bar()
42+
{
43+
return new S { A = 100 };
44+
}
45+
46+
[MethodImpl(MethodImplOptions.NoInlining)]
47+
private static void Use<T>(T x)
48+
{
49+
}
50+
51+
private struct S
52+
{
53+
public int A, B, C, D;
54+
}
55+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<Optimize>True</Optimize>
4+
</PropertyGroup>
5+
<ItemGroup>
6+
<Compile Include="$(MSBuildProjectName).cs" />
7+
</ItemGroup>
8+
</Project>

0 commit comments

Comments
 (0)