Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions src/coreclr/jit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ set( JIT_SOURCES
asyncanalysis.cpp
bitset.cpp
block.cpp
boundscheckcoalesce.cpp
buildstring.cpp
codegencommon.cpp
codegenlinear.cpp
Expand Down
253 changes: 253 additions & 0 deletions src/coreclr/jit/boundscheckcoalesce.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

//
// Bounds Check Coalescing
//
// Within a single block, when multiple GT_BOUNDS_CHECK nodes share the same
// length VN and use constant indices, only the bounds check with the largest
// constant index is actually needed. This pass finds such groups and
// strengthens the first bounds check in the group by replacing its constant
// index with the maximum constant index in the group. Forward assertion prop
// then drops the now-redundant later bounds checks.
//
// Example: `a[0] + a[1] + a[2] + a[3]` produces four bounds checks with
// indices 0, 1, 2, 3 and the same length. We rewrite the first BC's index
// to 3; forward assertion prop then drops the other three as redundant.
//
// We ensure no observable side effects (other than a bounds check exception
// can occur between the original check and the subsequent checks.
//
// This phase runs before assertion prop, which then optimizes away
// the trailing, now-redundant checks.
//

#include "jitpch.h"

#ifdef _MSC_VER
#pragma hdrstop
#endif

namespace
{
struct BoundsCheckCandidate
{
// leading bounds check in a candidate set
GenTreeBoundsChk* m_bc;

// array length being checked
ValueNum m_lenVN;

// Max index being checked
int m_offset;

BoundsCheckCandidate(GenTreeBoundsChk* bc, ValueNum lenVN, int offset)
: m_bc(bc)
, m_lenVN(lenVN)
, m_offset(offset)
{
}
};

//------------------------------------------------------------------------
// IsSideEffectBarrier: check if a node blocks bounds check coalescing
//
// Arguments:
// comp - the compiler instance
// node - the node to check
// blockHasEHSuccs - whether the block containing the node has reachable EH successors
//
// Returns:
// true if a node may have a side effect that should prevent us from
// coalescing bounds checks across it. Uses the per-node
// (non-summary) effect flags from GenTree::OperEffects.
//
bool IsSideEffectBarrier(Compiler* comp, GenTree* node, bool blockHasEHSuccs)
{
ExceptionSetFlags exSet;
GenTreeFlags const effects = node->OperEffects(comp, &exSet);

if ((effects & GTF_CALL) != 0)
{
return true;
}

if ((effects & GTF_EXCEPT) != 0)
{
if ((exSet & ~ExceptionSetFlags::IndexOutOfRangeException) != ExceptionSetFlags::None)
{
return true;
}
}

if ((effects & GTF_ASG) != 0)
{
if (!node->OperIsLocalStore())
{
return true;
}
if (!blockHasEHSuccs)
{
return false;
}
LclVarDsc const* const dsc = comp->lvaGetDesc(node->AsLclVarCommon());
return !dsc->lvTracked || dsc->lvLiveInOutOfHndlr;
}

return false;
}
} // namespace

//------------------------------------------------------------------------
// optBoundsCheckCoalesce: Coalesce bounds checks within each block.
//
// Returns:
// Suitable phase status.
//
PhaseStatus Compiler::optBoundsCheckCoalesce()
{
if (!doesMethodHaveBoundsChecks())
{
JITDUMP("Method has no bounds checks\n");
return PhaseStatus::MODIFIED_NOTHING;
}

if (fgSsaPassesCompleted == 0)
{
return PhaseStatus::MODIFIED_NOTHING;
}

// Track the current maximum offset seen for a given length VN
// optimization barrier count.
//
struct Key
{
int m_barrierCount;
ValueNum m_lengthVN;

Key(int barrierCount, ValueNum lengthVN)
: m_barrierCount(barrierCount)
, m_lengthVN(lengthVN)
{
}

bool operator==(const Key& other) const
{
return (m_barrierCount == other.m_barrierCount) && (m_lengthVN == other.m_lengthVN);
}

static bool Equals(const Key& x, const Key& y)
{
return (x.m_barrierCount == y.m_barrierCount) && (x.m_lengthVN == y.m_lengthVN);
}

static unsigned GetHashCode(const Key& x)
{
return (unsigned)x.m_lengthVN ^ (unsigned)x.m_barrierCount;
}
};

typedef JitHashTable<Key, Key, int> GroupMap;

bool modified = false;
CompAllocator alloc(getAllocator(CMK_AssertionProp));

ArrayStack<BoundsCheckCandidate> candidates(alloc);
GroupMap groupMap(alloc);

for (BasicBlock* const block : Blocks())
{
candidates.Reset();
groupMap.RemoveAll();
int barrierCount = 0;
bool const blockHasEHSuccs = block->HasPotentialEHSuccs(this);

for (Statement* const stmt : block->Statements())
{
for (GenTree* const node : stmt->TreeList())
{
if (node->OperIs(GT_BOUNDS_CHECK))
{
GenTreeBoundsChk* const bc = node->AsBoundsChk();
if (bc->gtThrowKind != SCK_RNGCHK_FAIL)
{
continue;
}

GenTree* const idx = bc->GetIndex();
if (!idx->IsIntCnsFitsInI32())
{
continue;
}

int const offset = static_cast<int>(idx->AsIntCon()->IconValue());
if (offset < 0)
{
continue;
}

// Look through comma-wrapped length nodes.
//
GenTree* const lenNode = bc->GetArrayLength()->gtEffectiveVal();
ValueNum const lenVN = vnStore->VNConservativeNormalValue(lenNode->gtVNPair);
if (lenVN == ValueNumStore::NoVN)
{
continue;
}

Key key(barrierCount, lenVN);
int headIndex;
if (!groupMap.Lookup(key, &headIndex))
{
// First constant-index bounds check with this length VN and this barrier count.
// Start a new group.
//
groupMap.Set(key, candidates.Height());
candidates.Emplace(bc, lenVN, offset);
continue;
}

// Following bounds check. See if this is a new max index.
//
BoundsCheckCandidate& head = candidates.BottomRef(headIndex);
JITDUMP("BC coalesce in " FMT_BB ": [%06u] (offset %d) is redundant given [%06u]\n", block->bbNum,
dspTreeID(bc), offset, dspTreeID(head.m_bc));
if (offset > head.m_offset)
{
head.m_offset = offset;
}
continue;
}

if (IsSideEffectBarrier(this, node, blockHasEHSuccs))
{
barrierCount++;
continue;
}
}
}

// Revise the check made by the first entry in each group, if we
// found a subsequent check at a higher constant index.
//
for (int i = 0; i < candidates.Height(); i++)
{
BoundsCheckCandidate& head = candidates.BottomRef(i);
GenTreeIntCon* const idxCns = head.m_bc->GetIndex()->AsIntCon();
int const original = static_cast<int>(idxCns->IconValue());
if (head.m_offset == original)
{
continue;
}

JITDUMP("BC coalesce in " FMT_BB ": strengthen [%06u] offset %d -> %d (lenVN " FMT_VN ")\n", block->bbNum,
dspTreeID(head.m_bc), original, head.m_offset, head.m_lenVN);

idxCns->SetIconValue(head.m_offset);
idxCns->gtVNPair.SetBoth(vnStore->VNForIntCon(head.m_offset));
Comment thread
AndyAyersMS marked this conversation as resolved.
modified = true;
}
}

return modified ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING;
}
4 changes: 4 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4807,6 +4807,10 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl

if (doAssertionProp)
{
// Coalesce groups of constant-indexed bounds checks.
//
DoPhase(this, PHASE_BOUNDS_CHECK_COALESCE, &Compiler::optBoundsCheckCoalesce);
Comment thread
AndyAyersMS marked this conversation as resolved.

// Assertion propagation
//
DoPhase(this, PHASE_ASSERTION_PROP_MAIN, &Compiler::optAssertionPropMain);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7500,6 +7500,7 @@ class Compiler

PhaseStatus optCloneLoops();
PhaseStatus optRangeCheckCloning();
PhaseStatus optBoundsCheckCoalesce();
void optCloneLoop(FlowGraphNaturalLoop* loop, LoopCloneContext* context);
PhaseStatus optUnrollLoops(); // Unrolls loops (needs to have cost info)
bool optTryUnrollLoop(FlowGraphNaturalLoop* loop, bool* changedIR);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compphases.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ CompPhaseNameMacro(PHASE_OPTIMIZE_VALNUM_CSES, "Optimize Valnum CSEs",
CompPhaseNameMacro(PHASE_VN_COPY_PROP, "VN based copy prop", false, -1, false)
CompPhaseNameMacro(PHASE_VN_BASED_INTRINSIC_EXPAND, "VN based intrinsic expansion", false, -1, false)
CompPhaseNameMacro(PHASE_OPTIMIZE_BRANCHES, "Redundant branch opts", false, -1, false)
CompPhaseNameMacro(PHASE_BOUNDS_CHECK_COALESCE, "Coalesce bounds checks", false, -1, false)
CompPhaseNameMacro(PHASE_ASSERTION_PROP_MAIN, "Assertion prop", false, -1, false)
CompPhaseNameMacro(PHASE_RANGE_CHECK_CLONING, "Clone blocks with range checks", false, -1, false)
CompPhaseNameMacro(PHASE_IF_CONVERSION, "If conversion", false, -1, false)
Expand Down
Loading
Loading