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
76 changes: 75 additions & 1 deletion src/coreclr/jit/assertionprop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2604,7 +2604,9 @@ GenTree* Compiler::optVNBasedFoldExpr(BasicBlock* block, GenTree* parent, GenTre
{
case GT_CALL:
return optVNBasedFoldExpr_Call(block, parent, tree->AsCall());

case GT_ADD:
case GT_SUB:
return optVNBasedFoldAddOrSubExpr(block, parent, tree);
// We can add more VN-based foldings here.

default:
Expand All @@ -2613,6 +2615,78 @@ GenTree* Compiler::optVNBasedFoldExpr(BasicBlock* block, GenTree* parent, GenTre
return nullptr;
}

//------------------------------------------------------------------------------
// optVNBasedFoldAddOrSubExpr: reduce successive variable adds/subs into a single multiply.
// e.g., i + i + i + i => i * 4.
// e.g., i - i - i - i => - i * 2.
//
// Arguments:
// block - The block containing the tree.
// parent - The parent node of the tree.
// tree - The ADD/SUB tree.
//
// Return Value:
// Returns a potentially new or a transformed tree node.
// Returns nullptr when no transformation is possible.
//
//
GenTree* Compiler::optVNBasedFoldAddOrSubExpr(BasicBlock* block, GenTree* parent, GenTree* tree)
{
// ADD(_, V0) OR SUB(_, V0) starts the pattern match.
if (!tree->OperIs(GT_ADD, GT_SUB) || tree->gtOverflow())
{
return nullptr;
}

genTreeOps targetOp = tree->OperGet();
#if !defined(TARGET_64BIT) && !defined(TARGET_WASM)
// Transforming 64-bit ADD/SUB to 64-bit MUL on 32-bit system results in replacing
// ADD/SUB ops with a helper function call. Don't apply optimization in that case.
if (tree->TypeIs(TYP_LONG))
{
return nullptr;
}
#endif // !defined(TARGET_64BIT) && !defined(TARGET_WASM)

GenTree* lclVarTree = tree->AsOp()->gtOp2;
GenTree* consTree = tree->AsOp()->gtOp1;

GenTree* op1 = consTree;
GenTree* op2 = lclVarTree;

if (!op2->OperIs(GT_LCL_VAR) || !varTypeIsIntegral(op2))
{
return nullptr;
}

unsigned lclNum = op2->AsLclVarCommon()->GetLclNum();

if ((op1->OperIs(GT_LCL_VAR) && op1->AsLclVarCommon()->GetLclNum() == lclNum) ||
(op1->OperIs(GT_COMMA) && op1->AsOp()->gtOp1->OperIs(GT_STORE_LCL_VAR) &&
op1->AsOp()->gtOp1->AsLclVar()->GetLclNum() == lclNum))
{
return gtNewOperNode(GT_MUL, tree->TypeGet(), consTree,
gtNewIconNode(targetOp == GT_ADD ? 2 : 0, tree->TypeGet()));
}
else if (targetOp == GT_SUB && op1->OperIs(GT_CNS_INT) && op1->AsIntCon()->gtIconVal == 0)
{
return gtNewOperNode(GT_MUL, tree->TypeGet(), op2, gtNewIconNode(-1, tree->TypeGet()));
}
else if (op1->OperIs(GT_MUL) &&
(((op1->AsOp()->gtOp1->OperIs(GT_LCL_VAR) &&
op1->AsOp()->gtOp1->AsLclVarCommon()->GetLclNum() == lclNum) ||
(op1->AsOp()->gtOp1->OperIs(GT_COMMA) && op1->AsOp()->gtOp1->AsOp()->gtOp1->OperIs(GT_STORE_LCL_VAR) &&
op1->AsOp()->gtOp1->AsOp()->gtOp1->AsLclVar()->GetLclNum() == lclNum)) &&
op1->AsOp()->gtOp2->OperIs(GT_CNS_INT)))
{
return gtNewOperNode(GT_MUL, tree->TypeGet(), op1->AsOp()->gtOp1,
Copy link
Member

@EgorBo EgorBo Mar 21, 2026

Choose a reason for hiding this comment

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

TBH, I am not sure what SSA in the PR title means - you just moved morph logic to this place which doesn't make sense to me, morph is supposed to be called from this phase (if it is not, we can experiment calling it unconditionally as today it might be called only if we constant folded something or changed based on assertions).

this function is a good place to, in theory, to rely on SSA, but the current PR doesn't do it.

gtNewIconNode(op1->AsOp()->gtOp2->AsIntCon()->gtIconVal + (targetOp == GT_ADD ? 1 : -1),
tree->TypeGet()));
}

return nullptr;
}

//------------------------------------------------------------------------------
// optVNBasedFoldConstExpr: Substitutes tree with an evaluated constant while
// managing side-effects.
Expand Down
3 changes: 1 addition & 2 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6787,8 +6787,6 @@ class Compiler

GenTreeOp* fgMorphCommutative(GenTreeOp* tree);

GenTree* fgMorphReduceAddOps(GenTree* tree);

public:
GenTree* fgMorphTree(GenTree* tree, MorphAddrContext* mac = nullptr);

Expand Down Expand Up @@ -8568,6 +8566,7 @@ class Compiler
GenTree* optVNBasedFoldExpr_Call(BasicBlock* block, GenTree* parent, GenTreeCall* call);
GenTree* optVNBasedFoldExpr_Call_Memmove(GenTreeCall* call);
GenTree* optVNBasedFoldExpr_Call_Memset(GenTreeCall* call);
GenTree* optVNBasedFoldAddOrSubExpr(BasicBlock* block, GenTree* parent, GenTree* tree);

AssertionIndex GetAssertionCount()
{
Expand Down
83 changes: 0 additions & 83 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7521,13 +7521,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
break;
}

if (opts.OptimizationEnabled() && fgGlobalMorph)
{
GenTree* morphed = fgMorphReduceAddOps(tree);
if (morphed != tree)
return fgMorphTree(morphed);
}

/*-------------------------------------------------------------------------
* Process the first operand, if any
*/
Expand Down Expand Up @@ -15446,82 +15439,6 @@ bool Compiler::fgCanTailCallViaJitHelper(GenTreeCall* call)
#endif
}

//------------------------------------------------------------------------
// fgMorphReduceAddOps: reduce successive variable adds into a single multiply,
// e.g., i + i + i + i => i * 4.
//
// Arguments:
// tree - tree for reduction
//
// Return Value:
// reduced tree if pattern matches, original tree otherwise
//
GenTree* Compiler::fgMorphReduceAddOps(GenTree* tree)
{
// ADD(_, V0) starts the pattern match.
if (!tree->OperIs(GT_ADD) || tree->gtOverflow())
{
return tree;
}

#if !defined(TARGET_64BIT) && !defined(TARGET_WASM)
// Transforming 64-bit ADD to 64-bit MUL on 32-bit system results in replacing
// ADD ops with a helper function call. Don't apply optimization in that case.
if (tree->TypeIs(TYP_LONG))
{
return tree;
}
#endif // !defined(TARGET_64BIT) && !defined(TARGET_WASM)

GenTree* lclVarTree = tree->AsOp()->gtOp2;
GenTree* consTree = tree->AsOp()->gtOp1;

GenTree* op1 = consTree;
GenTree* op2 = lclVarTree;

if (!op2->OperIs(GT_LCL_VAR) || !varTypeIsIntegral(op2))
{
return tree;
}

int foldCount = 0;
unsigned lclNum = op2->AsLclVarCommon()->GetLclNum();

// Search for pattern of shape ADD(ADD(ADD(lclNum, lclNum), lclNum), lclNum).
while (true)
{
// ADD(lclNum, lclNum), end of tree
if (op1->OperIs(GT_LCL_VAR) && op1->AsLclVarCommon()->GetLclNum() == lclNum && op2->OperIs(GT_LCL_VAR) &&
op2->AsLclVarCommon()->GetLclNum() == lclNum)
{
foldCount += 2;
break;
}
// ADD(ADD(X, Y), lclNum), keep descending
else if (op1->OperIs(GT_ADD) && !op1->gtOverflow() && op2->OperIs(GT_LCL_VAR) &&
op2->AsLclVarCommon()->GetLclNum() == lclNum)
{
foldCount++;
op2 = op1->AsOp()->gtOp2;
op1 = op1->AsOp()->gtOp1;
}
// Any other case is a pattern we won't attempt to fold for now.
else
{
return tree;
}
}

// V0 + V0 ... + V0 becomes V0 * foldCount, where postorder transform will optimize
// accordingly
consTree->BashToConst(foldCount, tree->TypeGet());

GenTree* morphed = gtNewOperNode(GT_MUL, tree->TypeGet(), lclVarTree, consTree);
DEBUG_DESTROY_NODE(tree);

return morphed;
}

//------------------------------------------------------------------------
// Compiler::MorphMDArrayTempCache::TempList::GetTemp: return a local variable number to use as a temporary variable
// in multi-dimensional array operation expansion.
Expand Down
Loading