Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Prerequisite work item for the CSE of GT_CNS_INT for ARM64 work item by briansull · Pull Request #39021 · dotnet/runtime · GitHub
Skip to content
Merged
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
22 changes: 18 additions & 4 deletions src/coreclr/src/jit/assertionprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -3236,7 +3236,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
return nullptr;
}

AssertionDsc* curAssertion = optGetAssertion(index);
AssertionDsc* curAssertion = optGetAssertion(index);
bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL);

// Allow or not to reverse condition for OAK_NOT_EQUAL assertions.
bool allowReverse = true;
Expand All@@ -3251,7 +3252,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen
printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum);
printf("Assertion index=#%02u: ", index);
printTreeID(op1);
printf(" %s ", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=");
printf(" %s ", assertionKindIsEqual ? "==" : "!=");
if (genActualType(op1->TypeGet()) == TYP_INT)
{
printf("%d\n", vnStore->ConstantValue<int>(vnCns));
Expand DownExpand Up@@ -3336,8 +3337,15 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, Gen

op1->gtVNPair.SetBoth(vnCns); // Preserve the ValueNumPair, as ChangeOperConst/SetOper will clear it.

// Also set the value number on the relop.
if (curAssertion->assertionKind == OAK_EQUAL)
// set foldResult to either 0 or 1
bool foldResult = assertionKindIsEqual;
if (tree->gtOper == GT_NE)
{
foldResult = !foldResult;
}

// Set the value number on the relop to 1 (true) or 0 (false)
if (foldResult)
{
tree->gtVNPair.SetBoth(vnStore->VNOneForType(TYP_INT));
}
Expand DownExpand Up@@ -4947,6 +4955,12 @@ GenTree* Compiler::optVNConstantPropOnJTrue(BasicBlock* block, GenTree* test)
//
Compiler::fgWalkResult Compiler::optVNConstantPropCurStmt(BasicBlock* block, Statement* stmt, GenTree* tree)
{
// Don't perform const prop on expressions marked with GTF_DONT_CSE
if (!tree->CanCSE())
{
return WALK_CONTINUE;
}

// Don't propagate floating-point constants into a TYP_STRUCT LclVar
// This can occur for HFA return values (see hfa_sf3E_r.exe)
if (tree->TypeGet() == TYP_STRUCT)
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/src/jit/block.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -309,6 +309,10 @@ void BasicBlock::dspFlags()
{
printf("jmp ");
}
if (bbFlags & BBF_HAS_CALL)
{
printf("hascall ");
}
if (bbFlags & BBF_GC_SAFE_POINT)
{
printf("gcsafe ");
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/src/jit/earlyprop.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -445,6 +445,14 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck
// actualValClone has small tree node size, it is safe to use CopyFrom here.
tree->ReplaceWith(actualValClone, this);

// Propagating a constant may create an opportunity to use a division by constant optimization
//
if ((tree->gtNext != nullptr) && tree->gtNext->OperIsBinary())
{
// We need to mark the parent divide/mod operation when this occurs
tree->gtNext->AsOp()->CheckDivideByConstOptimized(this);
}

#ifdef DEBUG
if (verbose)
{
Expand Down
205 changes: 197 additions & 8 deletions src/coreclr/src/jit/gentree.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -615,10 +615,6 @@ void GenTree::CopyReg(GenTree* from)
// GT_COPY/GT_RELOAD is considered having a reg if it
// has a reg assigned to any of its positions.
//
// Assumption:
// In order for this to work properly, gtClearReg must be called
// prior to setting the register value.
//
bool GenTree::gtHasReg() const
{
bool hasReg = false;
Expand DownExpand Up@@ -6674,6 +6670,173 @@ void GenTreeIntCon::FixupInitBlkValue(var_types asgType)
}
}

//----------------------------------------------------------------------------
// UsesDivideByConstOptimized:
// returns true if rationalize will use the division by constant
// optimization for this node.
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
// Return Value:
// Return true iff the node is a GT_DIV,GT_UDIV, GT_MOD or GT_UMOD with
// an integer constant and we can perform the division operation using
// a reciprocal multiply or a shift operation.
//
bool GenTreeOp::UsesDivideByConstOptimized(Compiler* comp)
{
if (!comp->opts.OptimizationEnabled())
{
return false;
}

if (!OperIs(GT_DIV, GT_MOD, GT_UDIV, GT_UMOD))
{
return false;
}
#if defined(TARGET_ARM64)
if (OperIs(GT_MOD, GT_UMOD))
{
// MOD, UMOD not supported for ARM64
return false;
}
#endif // TARGET_ARM64

bool isSignedDivide = OperIs(GT_DIV, GT_MOD);
GenTree* dividend = gtGetOp1()->gtEffectiveVal(/*commaOnly*/ true);
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);

#if !defined(TARGET_64BIT)
if (dividend->OperIs(GT_LONG))
{
return false;
}
#endif

if (dividend->IsCnsIntOrI())
{
// We shouldn't see a divmod with constant operands here but if we do then it's likely
// because optimizations are disabled or it's a case that's supposed to throw an exception.
// Don't optimize this.
return false;
}

ssize_t divisorValue;
if (divisor->IsCnsIntOrI())
{
divisorValue = static_cast<ssize_t>(divisor->AsIntCon()->IconValue());
}
else
{
ValueNum vn = divisor->gtVNPair.GetLiberal();
if (comp->vnStore->IsVNConstant(vn))
{
divisorValue = comp->vnStore->CoercedConstantValue<ssize_t>(vn);
}
else
{
return false;
}
}

const var_types divType = TypeGet();

if (divisorValue == 0)
{
// x / 0 and x % 0 can't be optimized because they are required to throw an exception.
return false;
}
else if (isSignedDivide)
{
if (divisorValue == -1)
{
// x / -1 can't be optimized because INT_MIN / -1 is required to throw an exception.
return false;
}
else if (isPow2(divisorValue))
{
return true;
}
}
else // unsigned divide
{
if (divType == TYP_INT)
{
// Clear up the upper 32 bits of the value, they may be set to 1 because constants
// are treated as signed and stored in ssize_t which is 64 bit in size on 64 bit targets.
divisorValue &= UINT32_MAX;
}

size_t unsignedDivisorValue = (size_t)divisorValue;
if (isPow2(unsignedDivisorValue))
{
return true;
}
}

const bool isDiv = OperIs(GT_DIV, GT_UDIV);

if (isDiv)
{
if (isSignedDivide)
{
// If the divisor is the minimum representable integer value then the result is either 0 or 1
if ((divType == TYP_INT && divisorValue == INT_MIN) || (divType == TYP_LONG && divisorValue == INT64_MIN))
{
return true;
}
}
else
{
// If the divisor is greater or equal than 2^(N - 1) then the result is either 0 or 1
if (((divType == TYP_INT) && (divisorValue > (UINT32_MAX / 2))) ||
((divType == TYP_LONG) && (divisorValue > (UINT64_MAX / 2))))
{
return true;
}
}
}

// TODO-ARM-CQ: Currently there's no GT_MULHI for ARM32
#if defined(TARGET_XARCH) || defined(TARGET_ARM64)
if (!comp->opts.MinOpts() && ((divisorValue >= 3) || !isSignedDivide))
{
// All checks pass we can perform the division operation using a reciprocal multiply.
return true;
}
#endif

return false;
}

//------------------------------------------------------------------------
// CheckDivideByConstOptimized:
// Checks if we can use the division by constant optimization
// on this node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: formatting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

// on this node
// and if so sets the flag GTF_DIV_BY_CNS_OPT and

it looks like an unintentional new line.

// and if so sets the flag GTF_DIV_BY_CNS_OPT and
// set GTF_DONT_CSE on the constant node
//
// Arguments:
// this - a GenTreeOp node
// comp - the compiler instance
//
void GenTreeOp::CheckDivideByConstOptimized(Compiler* comp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we want this to happen in minopts (opts.OptimizationEnabled() == false?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I will add a check for minopts to UsesDivideByConstOptimization()

{
if (UsesDivideByConstOptimized(comp))
{
gtFlags |= GTF_DIV_BY_CNS_OPT;

// Now set DONT_CSE on the GT_CNS_INT divisor, note that
// with ValueNumbering we can have a non GT_CNS_INT divisior
GenTree* divisor = gtGetOp2()->gtEffectiveVal(/*commaOnly*/ true);
if (divisor->OperIs(GT_CNS_INT))
{
divisor->gtFlags |= GTF_DONT_CSE;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

you have answered earlier why we check GTF_DONT_CSE in optVNConstantPropOnJTrue and as I understood the idea was to replace constant values with CSE lclVars, is my understanding correct?
If so why do we forbid replacing these const with a CSE lclVar here?

@briansullbriansullJul 9, 2020

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.
Thus we would generate a slow divide instruction instead of the faster multiply or shift sequence.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ah, I think I understand now.

From these two comments:

Doing a constant prop here would replace the CSE LclVar with the original constant.
Essentially undoing the CSE of the constant.

Because changing the CNS_INT to a CSE LclVar will cause lower to fail to use the multiplication by reciprocal optimization.

In general we want to set DONT_CSE on constants under DIV/MOD so they are not replaced with a CSE LCL_VAR. Other constants (that are not under 'DIV/MOD) are not marked as DONT_CSEso they could be replaced and, once they are replaced, we mark it withDONT_CSE` because it is their final state. Is it correct?

}
}
}

//
//------------------------------------------------------------------------
// gtBlockOpInit: Initializes a BlkOp GenTree
Expand DownExpand Up@@ -9899,6 +10062,18 @@ void Compiler::gtDispNode(GenTree* tree, IndentStack* indentStack, __in __in_z _
}
goto DASH;

case GT_DIV:
case GT_MOD:
case GT_UDIV:
case GT_UMOD:
if (tree->gtFlags & GTF_DIV_BY_CNS_OPT)
{
printf("M"); // We will use a Multiply by reciprical
--msgLength;
break;
}
goto DASH;

case GT_LCL_FLD:
case GT_LCL_VAR:
case GT_LCL_VAR_ADDR:
Expand DownExpand Up@@ -10566,16 +10741,30 @@ void Compiler::gtDispConst(GenTree* tree)
else if ((tree->AsIntCon()->gtIconVal > -1000) && (tree->AsIntCon()->gtIconVal < 1000))
{
printf(" %ld", dspIconVal);
#ifdef TARGET_64BIT
}
#ifdef TARGET_64BIT
else if ((tree->AsIntCon()->gtIconVal & 0xFFFFFFFF00000000LL) != 0)
{
printf(" 0x%llx", dspIconVal);
#endif
if (dspIconVal >= 0)
{
printf(" 0x%llx", dspIconVal);
}
else
{
printf(" -0x%llx", -dspIconVal);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Starting from VS2013 we have support for "%zd" (and other runtime parts are already using it), so I would suggest to just replace this block with:
printf(" %zd", dspIconVal);
that will handle both 32/64 and negative/positive.

Note: gcc and clang have always supported that.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I like see these numbers in hex, especially with my shared CSE constant changes where we often have to add or subtract a small offset.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I have not seen hex numbers being printed with a negative sign, but maybe it is fine. Thanks for the explanation.

}
}
#endif
else
{
printf(" 0x%X", dspIconVal);
if (dspIconVal >= 0)
{
printf(" 0x%X", dspIconVal);
}
else
{
printf(" -0x%X", -dspIconVal);
}
}

if (tree->IsIconHandle())
Expand Down
21 changes: 21 additions & 0 deletions src/coreclr/src/jit/gentree.h
Original file line numberDiff line numberDiff line change
Expand Up@@ -633,6 +633,12 @@ struct GenTree
assert(_gtRegNum == reg);
}

void ClearRegNum()
{
_gtRegNum = REG_NA;
INDEBUG(gtRegTag = GT_REGTAG_NONE;)
}

// Copy the _gtRegNum/gtRegTag fields
void CopyReg(GenTree* from);
bool gtHasReg() const;
Expand DownExpand Up@@ -922,6 +928,8 @@ struct GenTree
#define GTF_OVERFLOW 0x10000000 // Supported for: GT_ADD, GT_SUB, GT_MUL and GT_CAST.
// Requires an overflow check. Use gtOverflow(Ex)() to check this flag.

#define GTF_DIV_BY_CNS_OPT 0x80000000 // GT_DIV -- Uses the division by constant optimization to compute this division

#define GTF_ARR_BOUND_INBND 0x80000000 // GT_ARR_BOUNDS_CHECK -- have proved this check is always in-bounds

#define GTF_ARRLEN_ARR_IDX 0x80000000 // GT_ARR_LENGTH -- Length which feeds into an array index expression
Expand DownExpand Up@@ -2853,6 +2861,19 @@ struct GenTreeOp : public GenTreeUnOp
assert(oper == GT_NOP || oper == GT_RETURN || oper == GT_RETFILT || OperIsBlk(oper));
}

// returns true if we will use the division by constant optimization for this node.
bool UsesDivideByConstOptimized(Compiler* comp);

// checks if we will use the division by constant optimization this node
// then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant
void CheckDivideByConstOptimized(Compiler* comp);

// True if this node is marked as using the division by constant optimization
bool MarkedDivideByConstOptimized() const
{
return (gtFlags & GTF_DIV_BY_CNS_OPT) != 0;
}

#if DEBUGGABLE_GENTREE
GenTreeOp() : GenTreeUnOp(), gtOp2(nullptr)
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/src/jit/lower.cpp
Original file line numberDiff line numberDiff line change
Expand Up@@ -5134,6 +5134,7 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
unreached();
#endif
}
assert(divMod->MarkedDivideByConstOptimized());

// Depending on the "add" flag returned by GetUnsignedMagicNumberForDivide we need to generate:
// add == false (when divisor == 3 for example):
Expand DownExpand Up@@ -5207,7 +5208,6 @@ bool Lowering::LowerUnsignedDivOrMod(GenTreeOp* divMod)
BlockRange().InsertBefore(divMod, div, divisor, mul, dividend);
}
ContainCheckRange(firstNode, divMod);

return true;
}
#endif
Expand Down
Loading