Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

New fix for #26417 - Don't allow the hoisting of GT_CLS_VARs that were assigned a constant value. #26952

Merged
merged 4 commits into from
Oct 6, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 42 additions & 9 deletions src/jit/optimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6795,8 +6795,20 @@ void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack<BasicBlock*>* blo

bool IsTreeVNInvariant(GenTree* tree)
{
return m_compiler->optVNIsLoopInvariant(tree->gtVNPair.GetLiberal(), m_loopNum,
&m_hoistContext->m_curLoopVnInvariantCache);
ValueNum vn = tree->gtVNPair.GetLiberal();
if (m_compiler->vnStore->IsVNConstant(vn))
{
// It is unsafe to allow a GT_CLS_VAR that has been assigned a constant.
// The logic in optVNIsLoopInvariant would consider it to be loop-invariant, even
// if the assignment of the constant to the GT_CLS_VAR was inside the loop.
//
if (tree->OperIs(GT_CLS_VAR))
{
return false;
}
}

return m_compiler->optVNIsLoopInvariant(vn, m_loopNum, &m_hoistContext->m_curLoopVnInvariantCache);
}

public:
Expand Down Expand Up @@ -7003,22 +7015,36 @@ void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack<BasicBlock*>* blo
// Is the value of the whole tree loop invariant?
if (!treeIsInvariant)
{
// Here we have a tree that is not loop invariant and we thus cannot hoist
treeIsHoistable = false;
}
}

// Check if we need to set 'm_beforeSideEffect' to false.
// If we encounter a tree with a call in it
// or if we see an assignment to global we set it to false.
// Next check if we need to set 'm_beforeSideEffect' to false.
//
// If we are already set to false then we can skip these checks
// If we have already set it to false then we can skip these checks
//
if (m_beforeSideEffect)
{
// For this purpose, we only care about memory side effects. We assume that expressions will
// Is the value of the whole tree loop invariant?
if (!treeIsInvariant)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the right check here is if (!treeIsHoistable). If the tree is invariant and can throw but we don't hoist it, we need to set m_beforeSideEffect to false.

Copy link
Author

@briansull briansull Oct 3, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we have trees that switch back and forth from having treeIsHoistable as true to false and back to true.
Only the switch to treeIsInvariant is permanent.

One such tree that is important to be able to hoist is an invariant array read inside a loop:

Because ARR_BOUNDS_CHECK is a void, (it doesn't produce a value) it isn't a CSE candidate,
and anything that isn't a CSE candidate has treeIsHoistable set to false.
It's parent node is a GT_COMMA which produces a ushort value and can be hoistable.

               [000054] ---XG+------                 +--*  COMMA     ushort
               [000047] ---X-+------                 |  +--*  ARR_BOUNDS_CHECK_Rng void  
               [000009] -----+------                 |  |  +--*  LCL_VAR   int    V02 loc0         
               [000046] ---X-+------                 |  |  \--*  ARR_LENGTH int   
               [000008] -----+------                 |  |     \--*  LCL_VAR   ref    V01 arg1         
               [000010] a--XG+------                 |  \--*  IND       ushort
               [000053] -----+------                 |     \--*  ADD       byref 
               [000044] -----+------                 |        +--*  LCL_VAR   ref    V01 arg1         
               [000052] -----+------                 |        \--*  ADD       long  
               [000050] -----+------                 |           +--*  LSH       long  
               [000048] -----+------                 |           |  +--*  CAST      long <- int
               [000045] i----+------                 |           |  |  \--*  LCL_VAR   int    V02 loc0         
               [000049] -----+-N----                 |           |  \--*  CNS_INT   long   1
               [000051] -----+------                 |           \--*  CNS_INT   long   12 Fseq[#FirstElem]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may still have a hole here if we encounter a tree that's invariant and can throw an exception but we never hoist it (directly or as part of a parent). I tried to construct an example but ran into another bug where we do an incorrect exception reordering even without a loop: https://github.com/dotnet/coreclr/issues/27027.
I'm ok with this fix as it makes us "more correct" than before but we'll need to revisit this code.

{
// We have a tree that is not loop invariant and we thus cannot hoist
assert(treeIsHoistable == false);

// Check if we should clear m_beforeSideEffect.
// If 'tree' can throw an exception then we need to set m_beforeSideEffect to false.
// Note that calls are handled below
if (tree->OperMayThrow(m_compiler) && !tree->IsCall())
{
m_beforeSideEffect = false;
}
}

// In the section below, we only care about memory side effects. We assume that expressions will
// be hoisted so that they are evaluated in the same order as they would have been in the loop,
// and therefore throw exceptions in the same order. (So we don't use GTF_GLOBALLY_VISIBLE_SIDE_EFFECTS
// here, since that includes exceptions.)
// and therefore throw exceptions in the same order.
//
if (tree->IsCall())
{
// If it's a call, it must be a helper call that does not mutate the heap.
Expand Down Expand Up @@ -7052,6 +7078,13 @@ void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack<BasicBlock*>* blo
m_beforeSideEffect = false;
}
}
else if (tree->OperIs(GT_XADD, GT_XCHG, GT_LOCKADD, GT_CMPXCHG, GT_MEMORYBARRIER))
{
// If this node is a MEMORYBARRIER or an Atomic operation
// then don't hoist and stop any further hoisting after this node
treeIsHoistable = false;
m_beforeSideEffect = false;
}
}

// If this 'tree' is hoistable then we return and the caller will
Expand Down
47 changes: 47 additions & 0 deletions tests/src/JIT/Regression/JitBlue/GitHub_26417/GitHub_26417.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Runtime.CompilerServices;

class GitHub_26417
{
static int _a;

[MethodImplAttribute(MethodImplOptions.NoInlining)]
static void MyWriteLine(int v)
{
Console.WriteLine(v);
if (v == 0)
{
throw new Exception();
}
}

[MethodImplAttribute(MethodImplOptions.NoInlining)]
static void Test()
{
_a = 1;

while (_a == 1)
{
MyWriteLine(_a);
_a = 0;
}
}

static int Main()
{
int result = 100;
try {
Test();
}
catch (Exception)
{
Console.WriteLine("FAILED");
result = -1;
}
return result;
}
}
12 changes: 12 additions & 0 deletions tests/src/JIT/Regression/JitBlue/GitHub_26417/GitHub_26417.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup>
<DebugType>None</DebugType>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildProjectName).cs" />
</ItemGroup>
</Project>