Skip to content

Commit 9749add

Browse files
[release/10.0] Revert "JIT: Remove side effect detection quirk in loop hoisting (#118463)" with some improvements (#119346)
* Revert "JIT: Remove side effect detection quirk in loop hoisting (#118463)" This reverts commit e0b14e9. That change is not correct as it removes the safe guard that prevents hoisting out of conditionally executed blocks. * Add test * Rename m_beforeSideEffect -> m_canHoistSideEffects * Fix comment * Move some code around --------- Co-authored-by: Jakob Botsch Nielsen <[email protected]>
1 parent 734fa7b commit 9749add

File tree

3 files changed

+135
-50
lines changed

3 files changed

+135
-50
lines changed

src/coreclr/jit/optimizer.cpp

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3655,12 +3655,21 @@ bool Compiler::optHoistThisLoop(FlowGraphNaturalLoop* loop, LoopHoistContext* ho
36553655
}
36563656
#endif // FEATURE_MASKED_HW_INTRINSICS
36573657

3658-
// Find the set of definitely-executed blocks. These will be given priority for hoisting.
3658+
// Find the set of definitely-executed blocks.
36593659
// Ideally, the definitely-executed blocks are the ones that post-dominate the entry block.
36603660
// Until we have post-dominators, we'll special-case for single-exit blocks.
36613661
//
3662-
// TODO: We ought to consider hoisting more aggressively from conditionally executed blocks,
3663-
// if they are frequently executed and it is safe to evaluate the tree early.
3662+
// Todo: it is not clear if this is a correctness requirement or a profitability heuristic.
3663+
// It seems like the latter. Ideally there are enough safeguards to prevent hoisting exception
3664+
// or side-effect dependent things. Note that HoistVisitor uses `m_canHoistSideEffects` to determine if it's
3665+
// ok to hoist a side-effect. It allows this only for the first block (the entry block), before any
3666+
// side-effect has been seen. After the first block, it assumes that there has been a side effect and
3667+
// no further side-effect can be hoisted. It is true that we don't analyze any program behavior in the
3668+
// flow graph between the entry block and the subsequent blocks, whether they be the next block dominating
3669+
// the exit block, or the pre-headers of nested loops.
3670+
//
3671+
// We really should consider hoisting from conditionally executed blocks, if they are frequently executed
3672+
// and it is safe to evaluate the tree early.
36643673
//
36653674
assert(m_dfsTree != nullptr);
36663675
BitVecTraits traits(m_dfsTree->PostOrderTraits());
@@ -4116,7 +4125,7 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
41164125
};
41174126

41184127
ArrayStack<Value> m_valueStack;
4119-
bool m_beforeSideEffect;
4128+
bool m_canHoistSideEffects;
41204129
FlowGraphNaturalLoop* m_loop;
41214130
LoopHoistContext* m_hoistContext;
41224131
BasicBlock* m_currentBlock;
@@ -4251,7 +4260,7 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
42514260
BitVec defExec)
42524261
: GenTreeVisitor(compiler)
42534262
, m_valueStack(compiler->getAllocator(CMK_LoopHoist))
4254-
, m_beforeSideEffect(true)
4263+
, m_canHoistSideEffects(true)
42554264
, m_loop(loop)
42564265
, m_hoistContext(hoistContext)
42574266
, m_currentBlock(nullptr)
@@ -4263,26 +4272,46 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
42634272
void HoistBlock(BasicBlock* block)
42644273
{
42654274
m_currentBlock = block;
4266-
for (Statement* const stmt : block->NonPhiStatements())
4267-
{
4268-
WalkTree(stmt->GetRootNodePointer(), nullptr);
4269-
Value& top = m_valueStack.TopRef();
4270-
assert(top.Node() == stmt->GetRootNode());
42714275

4272-
// hoist the top node?
4273-
if (top.m_hoistable)
4274-
{
4275-
const bool defExecuted = BitVecOps::IsMember(m_traits, m_defExec, block->bbPostorderNum);
4276-
m_compiler->optHoistCandidate(stmt->GetRootNode(), block, m_loop, m_hoistContext, defExecuted);
4277-
}
4278-
else
4276+
const weight_t blockWeight = block->getBBWeight(m_compiler);
4277+
4278+
JITDUMP("\n HoistBlock " FMT_BB " (weight=%6s) of loop " FMT_LP " (head: " FMT_BB ")\n", block->bbNum,
4279+
refCntWtd2str(blockWeight, /* padForDecimalPlaces */ true), m_loop->GetIndex(),
4280+
m_loop->GetHeader()->bbNum);
4281+
4282+
if (blockWeight < (BB_UNITY_WEIGHT / 10))
4283+
{
4284+
JITDUMP(" block weight is too small to perform hoisting.\n");
4285+
}
4286+
else
4287+
{
4288+
for (Statement* const stmt : block->NonPhiStatements())
42794289
{
4280-
JITDUMP(" [%06u] %s: %s\n", dspTreeID(top.Node()),
4281-
top.m_invariant ? "not hoistable" : "not invariant", top.m_failReason);
4282-
}
4290+
WalkTree(stmt->GetRootNodePointer(), nullptr);
4291+
Value& top = m_valueStack.TopRef();
4292+
assert(top.Node() == stmt->GetRootNode());
42834293

4284-
m_valueStack.Reset();
4294+
// hoist the top node?
4295+
if (top.m_hoistable)
4296+
{
4297+
const bool defExecuted = BitVecOps::IsMember(m_traits, m_defExec, block->bbPostorderNum);
4298+
m_compiler->optHoistCandidate(stmt->GetRootNode(), block, m_loop, m_hoistContext, defExecuted);
4299+
}
4300+
else
4301+
{
4302+
JITDUMP(" [%06u] %s: %s\n", dspTreeID(top.Node()),
4303+
top.m_invariant ? "not hoistable" : "not invariant", top.m_failReason);
4304+
}
4305+
4306+
m_valueStack.Reset();
4307+
}
42854308
}
4309+
4310+
assert(!m_canHoistSideEffects || (block == m_loop->GetHeader()));
4311+
// After visiting the first block (which is expected to always be
4312+
// the loop header) we can no longer hoist out side effecting trees
4313+
// as the next blocks could be conditionally executed.
4314+
m_canHoistSideEffects = false;
42864315
}
42874316

42884317
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
@@ -4458,10 +4487,11 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
44584487

44594488
if (treeIsHoistable)
44604489
{
4461-
if (!m_beforeSideEffect)
4490+
if (!m_canHoistSideEffects)
44624491
{
44634492
// For now, we give up on an expression that might raise an exception if it is after the
4464-
// first possible global side effect.
4493+
// first possible global side effect (and we assume we're after that if we're not in the first
4494+
// block).
44654495
// TODO-CQ: this is when we might do loop cloning.
44664496
//
44674497
if ((tree->gtFlags & GTF_EXCEPT) != 0)
@@ -4484,24 +4514,24 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
44844514
}
44854515
}
44864516

4487-
// Next check if we need to set 'm_beforeSideEffect' to false.
4517+
// Next check if we need to set 'm_canHoistSideEffects' to false.
44884518
//
44894519
// If we have already set it to false then we can skip these checks
44904520
//
4491-
if (m_beforeSideEffect)
4521+
if (m_canHoistSideEffects)
44924522
{
44934523
// Is the value of the whole tree loop invariant?
44944524
if (!treeIsInvariant)
44954525
{
44964526
// We have a tree that is not loop invariant and we thus cannot hoist
44974527
assert(treeIsHoistable == false);
44984528

4499-
// Check if we should clear m_beforeSideEffect.
4500-
// If 'tree' can throw an exception then we need to set m_beforeSideEffect to false.
4529+
// Check if we should clear m_canHoistSideEffects.
4530+
// If 'tree' can throw an exception then we need to set m_canHoistSideEffects to false.
45014531
// Note that calls are handled below
45024532
if (tree->OperMayThrow(m_compiler) && !tree->IsCall())
45034533
{
4504-
m_beforeSideEffect = false;
4534+
m_canHoistSideEffects = false;
45054535
}
45064536
}
45074537

@@ -4517,19 +4547,19 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
45174547
GenTreeCall* call = tree->AsCall();
45184548
if (!call->IsHelperCall())
45194549
{
4520-
m_beforeSideEffect = false;
4550+
m_canHoistSideEffects = false;
45214551
}
45224552
else
45234553
{
45244554
CorInfoHelpFunc helpFunc = eeGetHelperNum(call->gtCallMethHnd);
45254555
if (s_helperCallProperties.MutatesHeap(helpFunc))
45264556
{
4527-
m_beforeSideEffect = false;
4557+
m_canHoistSideEffects = false;
45284558
}
45294559
else if (s_helperCallProperties.MayRunCctor(helpFunc) &&
45304560
(call->gtFlags & GTF_CALL_HOISTABLE) == 0)
45314561
{
4532-
m_beforeSideEffect = false;
4562+
m_canHoistSideEffects = false;
45334563
}
45344564

45354565
// Additional check for helper calls that throw exceptions
@@ -4541,7 +4571,7 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
45414571
// Does this helper call throw?
45424572
if (!s_helperCallProperties.NoThrow(helpFunc))
45434573
{
4544-
m_beforeSideEffect = false;
4574+
m_canHoistSideEffects = false;
45454575
}
45464576
}
45474577
}
@@ -4562,8 +4592,8 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
45624592
if (isGloballyVisibleStore)
45634593
{
45644594
INDEBUG(failReason = "store to globally visible memory");
4565-
treeIsHoistable = false;
4566-
m_beforeSideEffect = false;
4595+
treeIsHoistable = false;
4596+
m_canHoistSideEffects = false;
45674597
}
45684598
}
45694599
}
@@ -4667,22 +4697,8 @@ void Compiler::optHoistLoopBlocks(FlowGraphNaturalLoop* loop,
46674697
};
46684698

46694699
HoistVisitor visitor(this, loop, hoistContext, traits, defExecuted);
4670-
loop->VisitLoopBlocks([&](BasicBlock* block) -> BasicBlockVisit {
4671-
const weight_t blockWeight = block->getBBWeight(this);
4672-
4673-
JITDUMP("\n optHoistLoopBlocks " FMT_BB " (weight=%6s) of loop " FMT_LP " (head: " FMT_BB ")\n",
4674-
block->bbNum, refCntWtd2str(blockWeight, /* padForDecimalPlaces */ true), loop->GetIndex(),
4675-
loop->GetHeader()->bbNum);
4676-
4677-
if (blockWeight < (BB_UNITY_WEIGHT / 10))
4678-
{
4679-
JITDUMP(" block weight is too small to perform hoisting.\n");
4680-
}
4681-
else
4682-
{
4683-
visitor.HoistBlock(block);
4684-
}
4685-
4700+
loop->VisitLoopBlocksReversePostOrder([&](BasicBlock* block) -> BasicBlockVisit {
4701+
visitor.HoistBlock(block);
46864702
return BasicBlockVisit::Continue;
46874703
});
46884704

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.Numerics;
6+
using System.Runtime.Intrinsics;
7+
using Xunit;
8+
9+
public class Runtime_119061
10+
{
11+
private static C1 s_6 = new C1();
12+
13+
[Fact]
14+
public static void TestEntryPoint()
15+
{
16+
s_6.F8 = new S0(-1);
17+
var vr4 = s_6.F8.F0;
18+
Vector128<short> vr6 = default(Vector128<short>);
19+
M3(vr4, vr6);
20+
}
21+
22+
private static ushort M3(int arg1, Vector128<short> arg2)
23+
{
24+
bool[] var0 = new bool[]
25+
{
26+
false
27+
};
28+
for (sbyte lvar1 = 10; lvar1 < 12; lvar1++)
29+
{
30+
if (var0[0])
31+
{
32+
var vr2 = (uint)(3329109910U / (-9223372036854775808L / (arg1 | 1)));
33+
var vr1 = (short)BitOperations.LeadingZeroCount(vr2);
34+
arg2 = Vector128.CreateScalar(vr1);
35+
}
36+
}
37+
38+
C0 vr8 = new C0();
39+
return vr8.F1;
40+
}
41+
42+
private struct S0
43+
{
44+
public int F0;
45+
public S0(int f0) : this()
46+
{
47+
F0 = f0;
48+
}
49+
}
50+
51+
private class C0
52+
{
53+
public ushort F1;
54+
}
55+
56+
private class C1
57+
{
58+
public S0 F8;
59+
}
60+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<DebugType>None</DebugType>
4+
<Optimize>True</Optimize>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="$(MSBuildProjectName).cs" />
8+
</ItemGroup>
9+
</Project>

0 commit comments

Comments
 (0)