Skip to content

Commit 17828d1

Browse files
Fix CQ regression & correctness bug in morphing of long muls (#53566)
* Add a test covering GTF_MUL_64RSLT transform * Disable the test on Mono * Add genActualTypeIsInt helper * Add some gtFlags helpers * Prepare decomposition for new long muls * Update gtIsValid64RsltMul To understand the new format for long muls. * Rework morphing of long muls Previously, morph was looking for the exact pattern of MUL(CAST(long <- int), CAST(long <- int)) when assessing candidacy of GT_MUL for being marked with "GTF_MUL_64RSLT" and emitted as "long mul". This worked fine, until the importer was changed to fold all casts with constant operands. This broke the pattern matching and thus all MULs in the form of (long)value * 10 started being emitted as helper calls. This change updates morph to understand the new folded casts and in general updates the "format" of long mul from "CAST * CAST" to "CAST * (CAST | CONST)". In the process, new helper functions have been introduced, to avoid bloating fgMorphSmpOp with the new sizeable logic. Recognition of overflowing cases has been upgraded, and a correctness bug, where "checked((long)uint.MaxValue * (long)uint.MaxValue)" was wrongly treated as non-overflowing, fixed. Additionally, the logic to emit intermediate NOPs has been changed to instead always skip morphing the casts themselves, even when remorphing. * Add the script to generate the longmul test The test itself has been regenerated using it and there were no diffs, as expected.
1 parent ec42090 commit 17828d1

File tree

10 files changed

+6484
-120
lines changed

10 files changed

+6484
-120
lines changed

src/coreclr/jit/compiler.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5833,6 +5833,12 @@ class Compiler
58335833
GenTree* fgRecognizeAndMorphBitwiseRotation(GenTree* tree);
58345834
bool fgOperIsBitwiseRotationRoot(genTreeOps oper);
58355835

5836+
#if !defined(TARGET_64BIT)
5837+
// Recognize and morph a long multiplication with 32 bit operands.
5838+
GenTreeOp* fgRecognizeAndMorphLongMul(GenTreeOp* mul);
5839+
GenTreeOp* fgMorphLongMul(GenTreeOp* mul);
5840+
#endif
5841+
58365842
//-------- Determine the order in which the trees will be evaluated -------
58375843

58385844
unsigned fgTreeSeqNum;

src/coreclr/jit/decomposelongs.cpp

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -608,12 +608,12 @@ GenTree* DecomposeLongs::DecomposeCast(LIR::Use& use)
608608
{
609609
//
610610
// This int->long cast is used by a GT_MUL that will be transformed by DecomposeMul into a
611-
// GT_LONG_MUL and as a result the high operand produced by the cast will become dead.
611+
// GT_MUL_LONG and as a result the high operand produced by the cast will become dead.
612612
// Skip cast decomposition so DecomposeMul doesn't need to bother with dead code removal,
613613
// especially in the case of sign extending casts that also introduce new lclvars.
614614
//
615615

616-
assert((use.User()->gtFlags & GTF_MUL_64RSLT) != 0);
616+
assert(use.User()->Is64RsltMul());
617617

618618
skipDecomposition = true;
619619
}
@@ -1541,19 +1541,29 @@ GenTree* DecomposeLongs::DecomposeMul(LIR::Use& use)
15411541
{
15421542
assert(use.IsInitialized());
15431543

1544-
GenTree* tree = use.Def();
1545-
genTreeOps oper = tree->OperGet();
1544+
GenTree* tree = use.Def();
15461545

1547-
assert(oper == GT_MUL);
1548-
assert((tree->gtFlags & GTF_MUL_64RSLT) != 0);
1546+
assert(tree->OperIs(GT_MUL));
1547+
assert(tree->Is64RsltMul());
15491548

15501549
GenTree* op1 = tree->gtGetOp1();
15511550
GenTree* op2 = tree->gtGetOp2();
15521551

1553-
// We expect both operands to be int->long casts. DecomposeCast specifically
1554-
// ignores such casts when they are used by GT_MULs.
1555-
assert((op1->OperGet() == GT_CAST) && (op1->TypeGet() == TYP_LONG));
1556-
assert((op2->OperGet() == GT_CAST) && (op2->TypeGet() == TYP_LONG));
1552+
assert(op1->TypeIs(TYP_LONG) && op2->TypeIs(TYP_LONG));
1553+
1554+
// We expect the first operand to be an int->long cast.
1555+
// DecomposeCast specifically ignores such casts when they are used by GT_MULs.
1556+
assert(op1->OperIs(GT_CAST));
1557+
1558+
// The second operand can be a cast or a constant.
1559+
if (!op2->OperIs(GT_CAST))
1560+
{
1561+
assert(op2->OperIs(GT_LONG));
1562+
assert(op2->gtGetOp1()->IsIntegralConst());
1563+
assert(op2->gtGetOp2()->IsIntegralConst());
1564+
1565+
Range().Remove(op2->gtGetOp2());
1566+
}
15571567

15581568
Range().Remove(op1);
15591569
Range().Remove(op2);

src/coreclr/jit/gentree.cpp

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,15 +2438,15 @@ GenTree* Compiler::gtReverseCond(GenTree* tree)
24382438

24392439
bool GenTree::gtIsValid64RsltMul()
24402440
{
2441-
if ((gtOper != GT_MUL) || !(gtFlags & GTF_MUL_64RSLT))
2441+
if (!OperIs(GT_MUL) || !Is64RsltMul())
24422442
{
24432443
return false;
24442444
}
24452445

2446-
GenTree* op1 = AsOp()->gtOp1;
2447-
GenTree* op2 = AsOp()->gtOp2;
2446+
GenTree* op1 = AsOp()->gtGetOp1();
2447+
GenTree* op2 = AsOp()->gtGetOp2();
24482448

2449-
if (TypeGet() != TYP_LONG || op1->TypeGet() != TYP_LONG || op2->TypeGet() != TYP_LONG)
2449+
if (!TypeIs(TYP_LONG) || !op1->TypeIs(TYP_LONG) || !op2->TypeIs(TYP_LONG))
24502450
{
24512451
return false;
24522452
}
@@ -2456,26 +2456,30 @@ bool GenTree::gtIsValid64RsltMul()
24562456
return false;
24572457
}
24582458

2459-
// op1 has to be conv.i8(i4Expr)
2460-
if ((op1->gtOper != GT_CAST) || (genActualType(op1->CastFromType()) != TYP_INT))
2459+
// op1 has to be CAST(long <- int).
2460+
if (!(op1->OperIs(GT_CAST) && genActualTypeIsInt(op1->AsCast()->CastOp())))
24612461
{
24622462
return false;
24632463
}
24642464

2465-
// op2 has to be conv.i8(i4Expr)
2466-
if ((op2->gtOper != GT_CAST) || (genActualType(op2->CastFromType()) != TYP_INT))
2465+
// op2 has to be CAST(long <- int) or a suitably small constant.
2466+
if (!(op2->OperIs(GT_CAST) && genActualTypeIsInt(op2->AsCast()->CastOp())) &&
2467+
!(op2->IsIntegralConst() && FitsIn<int32_t>(op2->AsIntConCommon()->IntegralValue())))
24672468
{
24682469
return false;
24692470
}
24702471

2471-
// The signedness of both casts must be the same
2472-
if (((op1->gtFlags & GTF_UNSIGNED) != 0) != ((op2->gtFlags & GTF_UNSIGNED) != 0))
2472+
// Both operands must extend the same way.
2473+
bool op1ZeroExtends = op1->IsUnsigned();
2474+
bool op2ZeroExtends = op2->OperIs(GT_CAST) ? op2->IsUnsigned() : op2->AsIntConCommon()->IntegralValue() >= 0;
2475+
bool op2AnyExtensionIsSuitable = op2->IsIntegralConst() && op2ZeroExtends;
2476+
if ((op1ZeroExtends != op2ZeroExtends) && !op2AnyExtensionIsSuitable)
24732477
{
24742478
return false;
24752479
}
24762480

2477-
// Do unsigned mul iff both the casts are unsigned
2478-
if (((op1->gtFlags & GTF_UNSIGNED) != 0) != ((gtFlags & GTF_UNSIGNED) != 0))
2481+
// Do unsigned mul iff both operands are zero-extending.
2482+
if (op1->IsUnsigned() != IsUnsigned())
24792483
{
24802484
return false;
24812485
}

src/coreclr/jit/gentree.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,6 +2117,63 @@ struct GenTree
21172117
return ((gtFlags & GTF_UNSIGNED) != 0);
21182118
}
21192119

2120+
void SetUnsigned()
2121+
{
2122+
assert(OperIs(GT_ADD, GT_SUB, GT_MUL, GT_CAST));
2123+
gtFlags |= GTF_UNSIGNED;
2124+
}
2125+
2126+
void ClearUnsigned()
2127+
{
2128+
assert(OperIs(GT_ADD, GT_SUB, GT_MUL, GT_CAST));
2129+
gtFlags &= ~GTF_UNSIGNED;
2130+
}
2131+
2132+
void SetOverflow()
2133+
{
2134+
assert(OperMayOverflow());
2135+
gtFlags |= GTF_OVERFLOW;
2136+
}
2137+
2138+
void ClearOverflow()
2139+
{
2140+
assert(OperMayOverflow());
2141+
gtFlags &= ~GTF_OVERFLOW;
2142+
}
2143+
2144+
bool Is64RsltMul() const
2145+
{
2146+
return (gtFlags & GTF_MUL_64RSLT) != 0;
2147+
}
2148+
2149+
void Set64RsltMul()
2150+
{
2151+
gtFlags |= GTF_MUL_64RSLT;
2152+
}
2153+
2154+
void Clear64RsltMul()
2155+
{
2156+
gtFlags &= ~GTF_MUL_64RSLT;
2157+
}
2158+
2159+
void SetAllEffectsFlags(GenTree* source)
2160+
{
2161+
SetAllEffectsFlags(source->gtFlags & GTF_ALL_EFFECT);
2162+
}
2163+
2164+
void SetAllEffectsFlags(GenTree* source, GenTree* otherSource)
2165+
{
2166+
SetAllEffectsFlags((source->gtFlags | otherSource->gtFlags) & GTF_ALL_EFFECT);
2167+
}
2168+
2169+
void SetAllEffectsFlags(GenTreeFlags sourceFlags)
2170+
{
2171+
assert((sourceFlags & ~GTF_ALL_EFFECT) == 0);
2172+
2173+
gtFlags &= ~GTF_ALL_EFFECT;
2174+
gtFlags |= sourceFlags;
2175+
}
2176+
21202177
inline bool IsCnsIntOrI() const;
21212178

21222179
inline bool IsIntegralConst() const;

0 commit comments

Comments
 (0)