Skip to content

Commit dc43e19

Browse files
authored
JIT: support OSR for synchronized methods (#61712)
OSR methods share the "monitor acquired" flag with the original method. The monitor acquired flag is s bit of non-IL live state that must be recorded in the patchpoint. Also, OSR methods only need to release the monitor as acquisition can only happen in the original method.
1 parent cc44d43 commit dc43e19

File tree

10 files changed

+176
-24
lines changed

10 files changed

+176
-24
lines changed

src/coreclr/inc/patchpointinfo.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct PatchpointInfo
4040
m_genericContextArgOffset = -1;
4141
m_keptAliveThisOffset = -1;
4242
m_securityCookieOffset = -1;
43+
m_monitorAcquiredOffset = -1;
4344
}
4445

4546
// Total size of this patchpoint info record, in bytes
@@ -108,6 +109,22 @@ struct PatchpointInfo
108109
m_securityCookieOffset = offset;
109110
}
110111

112+
// Original method FP relative offset for monitor acquired flag
113+
int MonitorAcquiredOffset() const
114+
{
115+
return m_monitorAcquiredOffset;
116+
}
117+
118+
bool HasMonitorAcquired() const
119+
{
120+
return m_monitorAcquiredOffset != -1;
121+
}
122+
123+
void SetMonitorAcquiredOffset(int offset)
124+
{
125+
m_monitorAcquiredOffset = offset;
126+
}
127+
111128
// True if this local was address exposed in the original method
112129
bool IsExposed(unsigned localNum) const
113130
{
@@ -141,6 +158,7 @@ struct PatchpointInfo
141158
int m_genericContextArgOffset;
142159
int m_keptAliveThisOffset;
143160
int m_securityCookieOffset;
161+
int m_monitorAcquiredOffset;
144162
int m_offsetAndExposureData[];
145163
};
146164

src/coreclr/jit/compiler.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5368,6 +5368,14 @@ void Compiler::generatePatchpointInfo()
53685368
patchpointInfo->SecurityCookieOffset());
53695369
}
53705370

5371+
if (lvaMonAcquired != BAD_VAR_NUM)
5372+
{
5373+
LclVarDsc* const varDsc = lvaGetDesc(lvaMonAcquired);
5374+
patchpointInfo->SetMonitorAcquiredOffset(varDsc->GetStackOffset());
5375+
JITDUMP("--OSR-- monitor acquired V%02u offset is FP %d\n", lvaMonAcquired,
5376+
patchpointInfo->MonitorAcquiredOffset());
5377+
}
5378+
53715379
// Register this with the runtime.
53725380
info.compCompHnd->setPatchpointInfo(patchpointInfo);
53735381
}

src/coreclr/jit/compiler.hpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4736,10 +4736,6 @@ inline bool Compiler::compCanHavePatchpoints(const char** reason)
47364736
{
47374737
whyNot = "localloc";
47384738
}
4739-
else if ((info.compFlags & CORINFO_FLG_SYNCH) != 0)
4740-
{
4741-
whyNot = "synchronized method";
4742-
}
47434739
else if (opts.IsReversePInvoke())
47444740
{
47454741
whyNot = "reverse pinvoke";

src/coreclr/jit/flowgraph.cpp

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,12 +1683,9 @@ void Compiler::fgAddSyncMethodEnterExit()
16831683

16841684
// Create a block for the start of the try region, where the monitor enter call
16851685
// will go.
1686-
1687-
assert(fgFirstBB->bbFallsThrough());
1688-
1689-
BasicBlock* tryBegBB = fgNewBBafter(BBJ_NONE, fgFirstBB, false);
1690-
BasicBlock* tryNextBB = tryBegBB->bbNext;
1691-
BasicBlock* tryLastBB = fgLastBB;
1686+
BasicBlock* const tryBegBB = fgSplitBlockAtEnd(fgFirstBB);
1687+
BasicBlock* const tryNextBB = tryBegBB->bbNext;
1688+
BasicBlock* const tryLastBB = fgLastBB;
16921689

16931690
// If we have profile data the new block will inherit the next block's weight
16941691
if (tryNextBB->hasProfileWeight())
@@ -1799,10 +1796,10 @@ void Compiler::fgAddSyncMethodEnterExit()
17991796

18001797
lvaTable[lvaMonAcquired].lvType = typeMonAcquired;
18011798

1802-
{ // Scope the variables of the variable initialization
1803-
1804-
// Initialize the 'acquired' boolean.
1805-
1799+
// Create IR to initialize the 'acquired' boolean.
1800+
//
1801+
if (!opts.IsOSR())
1802+
{
18061803
GenTree* zero = gtNewZeroConNode(genActualType(typeMonAcquired));
18071804
GenTree* varNode = gtNewLclvNode(lvaMonAcquired, typeMonAcquired);
18081805
GenTree* initNode = gtNewAssignNode(varNode, zero);
@@ -1825,7 +1822,7 @@ void Compiler::fgAddSyncMethodEnterExit()
18251822
unsigned lvaCopyThis = 0;
18261823
if (!info.compIsStatic)
18271824
{
1828-
lvaCopyThis = lvaGrabTemp(true DEBUGARG("Synchronized method monitor acquired boolean"));
1825+
lvaCopyThis = lvaGrabTemp(true DEBUGARG("Synchronized method copy of this for handler"));
18291826
lvaTable[lvaCopyThis].lvType = TYP_REF;
18301827

18311828
GenTree* thisNode = gtNewLclvNode(info.compThisArg, TYP_REF);
@@ -1835,7 +1832,12 @@ void Compiler::fgAddSyncMethodEnterExit()
18351832
fgNewStmtAtEnd(tryBegBB, initNode);
18361833
}
18371834

1838-
fgCreateMonitorTree(lvaMonAcquired, info.compThisArg, tryBegBB, true /*enter*/);
1835+
// For OSR, we do not need the enter tree as the monitor is acquired by the original method.
1836+
//
1837+
if (!opts.IsOSR())
1838+
{
1839+
fgCreateMonitorTree(lvaMonAcquired, info.compThisArg, tryBegBB, true /*enter*/);
1840+
}
18391841

18401842
// exceptional case
18411843
fgCreateMonitorTree(lvaMonAcquired, lvaCopyThis, faultBB, false /*exit*/);

src/coreclr/jit/importer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11644,7 +11644,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1164411644
block->bbFlags |= BBF_PARTIAL_COMPILATION_PATCHPOINT;
1164511645
setMethodHasPartialCompilationPatchpoint();
1164611646

11647-
// Change block to BBJ_THROW so we won't trigger importation of sucessors.
11647+
// Change block to BBJ_THROW so we won't trigger importation of successors.
1164811648
//
1164911649
block->bbJumpKind = BBJ_THROW;
1165011650

@@ -18855,7 +18855,7 @@ unsigned Compiler::impGetSpillTmpBase(BasicBlock* block)
1885518855
SetSpillTempsBase callback(baseTmp);
1885618856

1885718857
// We do *NOT* need to reset the SpillClique*Members because a block can only be the predecessor
18858-
// to one spill clique, and similarly can only be the sucessor to one spill clique
18858+
// to one spill clique, and similarly can only be the successor to one spill clique
1885918859
impWalkSpillCliqueFromPred(block, &callback);
1886018860

1886118861
return baseTmp;

src/coreclr/jit/jiteh.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1405,7 +1405,12 @@ void Compiler::fgRemoveEHTableEntry(unsigned XTnum)
14051405
if (compHndBBtabCount == 0)
14061406
{
14071407
// No more entries remaining.
1408-
compHndBBtab = nullptr;
1408+
//
1409+
// We used to null out compHndBBtab here, but with OSR + Synch method
1410+
// we may remove all the initial EH entries if not reachable in the
1411+
// OSR portion, then need to add one for the synchronous exit.
1412+
//
1413+
// So now we just leave it be.
14091414
}
14101415
else
14111416
{

src/coreclr/jit/lclvars.cpp

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6259,10 +6259,27 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals()
62596259

62606260
if (lvaMonAcquired != BAD_VAR_NUM)
62616261
{
6262-
// This var must go first, in what is called the 'frame header' for EnC so that it is
6263-
// preserved when remapping occurs. See vm\eetwain.cpp for detailed comment specifying frame
6264-
// layout requirements for EnC to work.
6265-
stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaMonAcquired, lvaLclSize(lvaMonAcquired), stkOffs);
6262+
// For OSR we use the flag set up by the original method.
6263+
//
6264+
if (opts.IsOSR())
6265+
{
6266+
assert(info.compPatchpointInfo->HasMonitorAcquired());
6267+
int originalOffset = info.compPatchpointInfo->MonitorAcquiredOffset();
6268+
int offset = originalFrameStkOffs + originalOffset;
6269+
6270+
JITDUMP("---OSR--- V%02u (on old frame, monitor aquired) old rbp offset %d old frame offset %d new "
6271+
"virt offset %d\n",
6272+
lvaMonAcquired, originalOffset, originalFrameStkOffs, offset);
6273+
6274+
lvaTable[lvaMonAcquired].SetStackOffset(offset);
6275+
}
6276+
else
6277+
{
6278+
// This var must go first, in what is called the 'frame header' for EnC so that it is
6279+
// preserved when remapping occurs. See vm\eetwain.cpp for detailed comment specifying frame
6280+
// layout requirements for EnC to work.
6281+
stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaMonAcquired, lvaLclSize(lvaMonAcquired), stkOffs);
6282+
}
62666283
}
62676284

62686285
#ifdef JIT32_GCENCODER

src/coreclr/jit/patchpoint.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
//
2727
// * no patchpoints in handler regions
2828
// * no patchpoints for localloc methods
29-
// * no patchpoints for synchronized methods (workaround)
3029
//
3130
class PatchpointTransformer
3231
{
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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.Threading;
7+
8+
struct S
9+
{
10+
public long y;
11+
public int x;
12+
}
13+
14+
class Z
15+
{
16+
virtual public S F()
17+
{
18+
S s = new S();
19+
s.x = 100;
20+
s.y = -1;
21+
return s;
22+
}
23+
}
24+
25+
class X
26+
{
27+
Z z;
28+
29+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.Synchronized)]
30+
public S G()
31+
{
32+
S s = new S();
33+
34+
for (int i = 0; i < 100_000; i++)
35+
{
36+
if (!Monitor.IsEntered(this))
37+
{
38+
throw new Exception();
39+
}
40+
s = z.F();
41+
}
42+
43+
return s;
44+
}
45+
46+
public static int Main()
47+
{
48+
int result = -1;
49+
try
50+
{
51+
result = Test();
52+
}
53+
catch (Exception)
54+
{
55+
Console.WriteLine("EXCEPTION");
56+
}
57+
58+
if (result == 100)
59+
{
60+
Console.WriteLine("SUCCESS");
61+
}
62+
else
63+
{
64+
Console.WriteLine("FAILURE");
65+
}
66+
return result;
67+
}
68+
69+
[MethodImpl(MethodImplOptions.NoInlining)]
70+
public static int Test()
71+
{
72+
var x = new X();
73+
x.z = new Z();
74+
75+
for (int i = 0; i < 100; i++)
76+
{
77+
_ = x.G();
78+
Thread.Sleep(15);
79+
}
80+
81+
return x.G().x;
82+
}
83+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<DebugType />
5+
<Optimize>True</Optimize>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<Compile Include="$(MSBuildProjectName).cs" />
9+
</ItemGroup>
10+
<PropertyGroup>
11+
<CLRTestBatchPreCommands><![CDATA[
12+
$(CLRTestBatchPreCommands)
13+
set COMPlus_TieredCompilation=1
14+
set COMPlus_TC_QuickJitForLoops=1
15+
set COMPlus_TC_OnStackReplacement=1
16+
]]></CLRTestBatchPreCommands>
17+
<BashCLRTestPreCommands><![CDATA[
18+
$(BashCLRTestPreCommands)
19+
export COMPlus_TieredCompilation=1
20+
export COMPlus_TC_QuickJitForLoops=1
21+
export COMPlus_TC_OnStackReplacement=1
22+
]]></BashCLRTestPreCommands>
23+
</PropertyGroup>
24+
</Project>

0 commit comments

Comments
 (0)