Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gtneg mul divoptimizations #45604

Merged
merged 19 commits into from
Jan 12, 2021
Merged
Changes from 11 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
102 changes: 78 additions & 24 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2833,8 +2833,8 @@ void Compiler::fgInitArgInfo(GenTreeCall* call)
{
noway_assert(call->gtCallArgs->GetNode()->TypeGet() == TYP_I_IMPL ||
call->gtCallArgs->GetNode()->TypeGet() == TYP_BYREF ||
call->gtCallArgs->GetNode()->gtOper ==
GT_NOP); // the arg was already morphed to a register (fgMorph called twice)
call->gtCallArgs->GetNode()->gtOper == GT_NOP); // the arg was already morphed to a register
// (fgMorph called twice)
maxRegArgs = 1;
}
else
Expand Down Expand Up @@ -9090,8 +9090,8 @@ GenTree* Compiler::fgMorphCall(GenTreeCall* call)
// copy-back).
unsigned retValTmpNum = BAD_VAR_NUM;
CORINFO_CLASS_HANDLE structHnd = nullptr;
if (call->HasRetBufArg() &&
call->gtCallLateArgs == nullptr) // Don't do this if we're re-morphing (which will make late args non-null).
if (call->HasRetBufArg() && call->gtCallLateArgs == nullptr) // Don't do this if we're re-morphing (which will make
// late args non-null).
{
// We're enforcing the invariant that return buffers pointers (at least for
// struct return types containing GC pointers) are never pointers into the heap.
Expand Down Expand Up @@ -11605,16 +11605,16 @@ GenTree* Compiler::getSIMDStructFromField(GenTree* tree,
}

/*****************************************************************************
* If a read operation tries to access simd struct field, then transform the
* operation to the SIMD intrinsic SIMDIntrinsicGetItem, and return the new tree.
* Otherwise, return the old tree.
* Argument:
* tree - GenTree*. If this pointer points to simd struct which is used for simd
* intrinsic, we will morph it as simd intrinsic SIMDIntrinsicGetItem.
* Return:
* A GenTree* which points to the new tree. If the tree is not for simd intrinsic,
* return nullptr.
*/
* If a read operation tries to access simd struct field, then transform the
Copy link
Contributor

Choose a reason for hiding this comment

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

did jit-format cause these changes or were these unintentional edits? Could you please try to delete them and run jit-format again?

* operation to the SIMD intrinsic SIMDIntrinsicGetItem, and return the new tree.
* Otherwise, return the old tree.
* Argument:
* tree - GenTree*. If this pointer points to simd struct which is used for simd
* intrinsic, we will morph it as simd intrinsic SIMDIntrinsicGetItem.
* Return:
* A GenTree* which points to the new tree. If the tree is not for simd intrinsic,
* return nullptr.
*/

GenTree* Compiler::fgMorphFieldToSIMDIntrinsicGet(GenTree* tree)
{
Expand All @@ -11635,16 +11635,16 @@ GenTree* Compiler::fgMorphFieldToSIMDIntrinsicGet(GenTree* tree)
}

/*****************************************************************************
* Transform an assignment of a SIMD struct field to SIMD intrinsic
* SIMDIntrinsicSet*, and return a new tree. If it is not such an assignment,
* then return the old tree.
* Argument:
* tree - GenTree*. If this pointer points to simd struct which is used for simd
* intrinsic, we will morph it as simd intrinsic set.
* Return:
* A GenTree* which points to the new tree. If the tree is not for simd intrinsic,
* return nullptr.
*/
* Transform an assignment of a SIMD struct field to SIMD intrinsic
* SIMDIntrinsicSet*, and return a new tree. If it is not such an assignment,
* then return the old tree.
* Argument:
* tree - GenTree*. If this pointer points to simd struct which is used for simd
* intrinsic, we will morph it as simd intrinsic set.
* Return:
* A GenTree* which points to the new tree. If the tree is not for simd intrinsic,
* return nullptr.
*/

GenTree* Compiler::fgMorphFieldAssignToSIMDIntrinsicSet(GenTree* tree)
{
Expand Down Expand Up @@ -11909,6 +11909,22 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
return fgMorphCast(tree);

case GT_MUL:
// -a * C => a * -C, where C is constant
// MUL(NEG(a), C) => MUL(a, NEG(C))
if (fgGlobalMorph && op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() &&
Copy link
Contributor

Choose a reason for hiding this comment

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

opts.OptimizationEnabled() check is missed here and in the other cases, when this flag is set to false we don't want to do any CQ transformations, only what is needed for correctness, it is used, for example, in minopts.

!op2->IsIconHandle() && op2->AsIntCon()->IconValue() != 1 && op2->AsIntCon()->IconValue() != 0 &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you check fgGlobalMorph here? Did you want to use !gtIsActiveCSE_Candidate ? gtIsActiveCSE_Candidate means it is not CSE phase (it could be a global morph phase instead) or this tree is not the current candidate that could be dangerous to remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I checked fgGlobalMorph because it was checked in #43921, which is what I based most of this work on. I can use !gtIsActiveCSE_Candidate if that would be better.

Still learning about the CoreCLR JIT, I greatly appreciate the advice :).

Copy link
Contributor

Choose a reason for hiding this comment

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

On second thought I think fgGlobalMorph check is fine here because you are changing node value without updating its VN.
An alternative could be to do the optimization with !gtIsActiveCSE_Candidate but create a new tree for the constant:

tree->AsOp()->gtOp2 = gtNewIconNode(-op2->AsIntCon()->IconValue(), op2->TypeGet());
DEBUG_DESTROY_NODE(op2);

it would allow the optimization to happen in other phases, so I think it would be better.

op2->AsIntCon()->IconValue() != -1 && !tree->gtOverflow())
{
// tree: MUL
Copy link
Contributor

Choose a reason for hiding this comment

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

is this the comment for the tree before the transformation?
then it should be

// tree: MUL
// op1: Neg
// op1Child: a
// op2: Const

// op1: a
// op2: NEG
// op2Child: C

tree->AsOp()->gtOp1 = op1->gtGetOp1();
DEBUG_DESTROY_NODE(op1);
op2->AsIntCon()->SetIconValue(-op2->AsIntCon()->IconValue()); // -C
return fgMorphSmpOp(tree, mac);
}

#ifndef TARGET_64BIT
if (typ == TYP_LONG)
Expand Down Expand Up @@ -12056,6 +12072,23 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
return fgMorphSmpOp(tree, mac);
}

// -a / C => a / -C, where C is constant
// DIV(NEG(a), C) => DIV(a, NEG(C))
if (fgGlobalMorph && op1->OperIs(GT_NEG) && !op1->gtGetOp1()->IsCnsIntOrI() && op2->IsCnsIntOrI() &&
!op2->IsIconHandle() && op2->AsIntCon()->IconValue() != 1 && op2->AsIntCon()->IconValue() != 0 &&
op2->AsIntCon()->IconValue() != -1)
{
// tree: DIV
// op1: a
// op2: NEG
Copy link
Contributor

Choose a reason for hiding this comment

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

the same here.

// op2Child: C

tree->AsOp()->gtOp1 = op1->gtGetOp1();
DEBUG_DESTROY_NODE(op1);
op2->AsIntCon()->SetIconValue(-op2->AsIntCon()->IconValue()); // -C
return fgMorphSmpOp(tree, mac);
}

#ifndef TARGET_64BIT
if (typ == TYP_LONG)
{
Expand Down Expand Up @@ -13920,6 +13953,27 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
return child;
}

// Distribute negation over simple multiplication/division expressions
if (op1->OperIs(GT_MUL) || op1->OperIs(GT_DIV))
{
if (!op1->AsOp()->gtOp1->IsCnsIntOrI() && op1->AsOp()->gtOp2->IsCnsIntOrI())
{
// -(a * C) => a * -C
// -(a / C) => a / -C
oper = op1->gtOper;
tree->SetOper(op1->gtOper);
GenTree* newOp1 = op1->AsOp()->gtOp1; // a
GenTree* newOp2 = op1->AsOp()->gtOp2; // C
newOp2->AsIntCon()->SetIconValue(-newOp2->AsIntCon()->IconValue());

// Only need to destroy op1 since op2 will be null already
DEBUG_DESTROY_NODE(op1);

tree->AsOp()->gtOp1 = op1 = newOp1;
tree->AsOp()->gtOp2 = op2 = newOp2;
}
}

/* Any constant cases should have been folded earlier */
noway_assert(!op1->OperIsConst() || !opts.OptEnabled(CLFLG_CONSTANTFOLD) || optValnumCSE_phase);
break;
Expand Down