Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
26 changes: 23 additions & 3 deletions src/coreclr/jit/rangecheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,20 @@ void RangeCheck::MergeAssertion(BasicBlock* block, GenTree* op, Range* pRange DE
// Compute the range for a binary operation.
Range RangeCheck::ComputeRangeForBinOp(BasicBlock* block, GenTreeOp* binop, bool monIncreasing DEBUGARG(int indent))
{
assert(binop->OperIs(GT_ADD, GT_AND, GT_RSH, GT_RSZ, GT_LSH, GT_UMOD, GT_MUL));
assert(binop->OperIs(GT_ADD, GT_XOR, GT_AND, GT_RSH, GT_RSZ, GT_LSH, GT_UMOD, GT_MUL));
Copy link
Member Author

Choose a reason for hiding this comment

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

NOTE: ideally, we should have handled XOR/OR/HWINTRINSIC all separately for GetRange, but it seems that it has no diffs outside of Log2 pattern, so I decided to just do an ad-hoc Log2 matching.


// For XOR we only care about Log2 pattern for now
if (binop->OperIs(GT_XOR))
{
int upperBound;
if (m_pCompiler->vnStore->IsVNLog2(m_pCompiler->vnStore->VNConservativeNormalValue(binop->gtVNPair),
&upperBound))
{
assert(upperBound > 0);
return Range(Limit(Limit::keConstant, 0), Limit(Limit::keConstant, upperBound));
}
return Range(Limit(Limit::keUnknown));
}

GenTree* op1 = binop->gtGetOp1();
GenTree* op2 = binop->gtGetOp2();
Expand Down Expand Up @@ -1471,6 +1484,8 @@ bool RangeCheck::DoesOverflow(BasicBlock* block, GenTree* expr, const Range& ran

bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr, const Range& range)
{
ValueNumStore* vnStore = m_pCompiler->vnStore;

JITDUMP("Does overflow [%06d]?\n", Compiler::dspTreeID(expr));
GetSearchPath()->Set(expr, block, SearchPath::Overwrite);

Expand All @@ -1481,7 +1496,7 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr, const Ran
overflows = true;
}
// If the definition chain resolves to a constant, it doesn't overflow.
else if (m_pCompiler->vnStore->IsVNConstant(expr->gtVNPair.GetConservative()))
else if (vnStore->IsVNConstant(expr->gtVNPair.GetConservative()))
{
overflows = false;
}
Expand Down Expand Up @@ -1509,6 +1524,11 @@ bool RangeCheck::ComputeDoesOverflow(BasicBlock* block, GenTree* expr, const Ran
{
overflows = false;
}
else if (expr->OperIs(GT_XOR) && vnStore->IsVNLog2(m_pCompiler->vnStore->VNConservativeNormalValue(expr->gtVNPair)))
{
// For XOR we only care about Log2 pattern for now, which never overflows.
overflows = false;
}
// Walk through phi arguments to check if phi arguments involve arithmetic that overflows.
else if (expr->OperIs(GT_PHI))
{
Expand Down Expand Up @@ -1596,7 +1616,7 @@ Range RangeCheck::ComputeRange(BasicBlock* block, GenTree* expr, bool monIncreas
MergeAssertion(block, expr, &range DEBUGARG(indent + 1));
}
// compute the range for binary operation
else if (expr->OperIs(GT_ADD, GT_AND, GT_RSH, GT_RSZ, GT_LSH, GT_UMOD, GT_MUL))
else if (expr->OperIs(GT_XOR, GT_ADD, GT_AND, GT_RSH, GT_RSZ, GT_LSH, GT_UMOD, GT_MUL))
{
range = ComputeRangeForBinOp(block, expr->AsOp(), monIncreasing DEBUGARG(indent + 1));
}
Expand Down
80 changes: 80 additions & 0 deletions src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6565,6 +6565,48 @@ bool ValueNumStore::IsVNInt32Constant(ValueNum vn)
return TypeOfVN(vn) == TYP_INT;
}

//------------------------------------------------------------------------
// IsVNLog2: Determine if the value number is a log2 pattern, which is
// "XOR(LZCNT32(OR(X, 1), 31)" or "XOR(LZCNT64(OR(X, 1), 63))".
//
// Arguments:
// vn - the value number to analyze
// upperBound - if not null, will be set to the upper bound of the log2 pattern (31 or 63)
//
// Return value:
// true if the value number is a log2 pattern, false otherwise.
//
bool ValueNumStore::IsVNLog2(ValueNum vn, int* upperBound)
{
#if defined(FEATURE_HW_INTRINSICS) && (defined(TARGET_XARCH) || defined(TARGET_ARM64))
int xorBy;
ValueNum op;
// First, see if it's "X ^ 31" or "X ^ 63".
if (IsVNBinFuncWithConst(vn, VNF_XOR, &op, &xorBy) && ((xorBy == 31) || (xorBy == 63)))
Copy link
Member

Choose a reason for hiding this comment

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

nit: This is missing handling for X86Base_BitScanReverse which is used for R2R and NAOT, since LZCNT isn't baseline.

{
// Drop any integer cast if any, we're dealing with [0..63] range, any integer cast is redundant.
IsVNBinFunc(op, VNF_Cast, &op);

#ifdef TARGET_XARCH
VNFunc lzcntFunc = (xorBy == 31) ? VNF_HWI_LZCNT_LeadingZeroCount : VNF_HWI_LZCNT_X64_LeadingZeroCount;
#else
VNFunc lzcntFunc = (xorBy == 31) ? VNF_HWI_ArmBase_LeadingZeroCount : VNF_HWI_ArmBase_Arm64_LeadingZeroCount;
#endif
// Next, see if it's "LZCNT32(X | 1)" or "LZCNT64(X | 1)".
int orBy;
if (IsVNBinFunc(op, lzcntFunc, &op) && IsVNBinFuncWithConst(op, VNF_OR, &op, &orBy) && (orBy == 1))
Copy link
Member

Choose a reason for hiding this comment

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

Just noting, that X | 1 isn't required if we know that X != 0, but its probably not necessary to handle.

{
if (upperBound != nullptr)
{
*upperBound = xorBy;
}
return true;
}
}
#endif
return false;
}

//------------------------------------------------------------------------
// IsVNNeverNegative: Determines if the given value number can never take on a negative value
// in a signed context (i.e. when the most-significant bit represents signedness).
Expand Down Expand Up @@ -6640,6 +6682,13 @@ bool ValueNumStore::IsVNNeverNegative(ValueNum vn)
case VNF_HWI_ArmBase_Arm64_LeadingSignCount:
return VNVisit::Continue;
#endif
case VNF_XOR:
if (IsVNLog2(vn))
{
return VNVisit::Continue;
}
break;

#endif // FEATURE_HW_INTRINSICS

default:
Expand Down Expand Up @@ -9823,6 +9872,37 @@ bool ValueNumStore::GetVNFunc(ValueNum vn, VNFuncApp* funcApp)
return false;
}

//----------------------------------------------------------------------------------
// IsVNBinFunc: A specialized version of GetVNFunc that checks if the given ValueNum
// is the given VNFunc with arity 2. If so, it returns the two operands.
//
// Arguments:
// vn - The ValueNum to check.
// func - The VNFunc to check for.
// op1 - The first operand (if not null).
// op2 - The second operand (if not null).
//
// Return Value:
// true if the given vn is the given VNFunc with two operands.
//
bool ValueNumStore::IsVNBinFunc(ValueNum vn, VNFunc func, ValueNum* op1, ValueNum* op2)
{
VNFuncApp funcApp;
if (GetVNFunc(vn, &funcApp) && (funcApp.m_func == func) && (funcApp.m_arity == 2))
{
if (op1 != nullptr)
{
*op1 = funcApp.m_args[0];
}
if (op2 != nullptr)
{
*op2 = funcApp.m_args[1];
}
return true;
}
return false;
}

bool ValueNumStore::VNIsValid(ValueNum vn)
{
ChunkNum cn = GetChunkNum(vn);
Expand Down
35 changes: 35 additions & 0 deletions src/coreclr/jit/valuenum.h
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,9 @@ class ValueNumStore
// Returns true if the VN represents a node that is never negative.
bool IsVNNeverNegative(ValueNum vn);

// Returns true if the VN represents BitOperations.Log2 pattern
bool IsVNLog2(ValueNum vn, int* upperBound = nullptr);

typedef SmallHashTable<ValueNum, bool, 8U> CheckedBoundVNSet;

// Returns true if the VN is known or likely to appear as the conservative value number
Expand Down Expand Up @@ -1408,6 +1411,38 @@ class ValueNumStore
// the function application it represents; otherwise, return "false."
bool GetVNFunc(ValueNum vn, VNFuncApp* funcApp);

// Returns "true" iff "vn" is a function application of the form "func(op1, op2)".
bool IsVNBinFunc(ValueNum vn, VNFunc func, ValueNum* op1 = nullptr, ValueNum* op2 = nullptr);

// Returns "true" iff "vn" is a function application of the form "func(op, cns)"
// the cns can be on the left side if the function is commutative.
template <typename T>
bool IsVNBinFuncWithConst(ValueNum vn, VNFunc func, ValueNum* op, T* cns)
Comment on lines +1416 to +1421
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Other functions in this class seem to write out Binary in full (e.g. EvalMathFuncBinary above).

Feel free to ignore or address as part of a separate PR.

{
T opCns;
ValueNum op1, op2;
if (IsVNBinFunc(vn, func, &op1, &op2))
{
if (IsVNIntegralConstant(op2, &opCns))
{
if (op != nullptr)
*op = op1;
if (cns != nullptr)
*cns = opCns;
return true;
}
else if (VNFuncIsCommutative(func) && IsVNIntegralConstant(op1, &opCns))
{
if (op != nullptr)
*op = op2;
if (cns != nullptr)
*cns = opCns;
return true;
}
}
return false;
}

// Returns "true" iff "vn" is a valid value number -- one that has been previously returned.
bool VNIsValid(ValueNum vn);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ public static int CountDigits(ulong value)
];
Debug.Assert(log2ToPow10.Length == 64);

// TODO: Replace with log2ToPow10[BitOperations.Log2(value)] once https://github.com/dotnet/runtime/issues/79257 is fixed
nint elementOffset = Unsafe.Add(ref MemoryMarshal.GetReference(log2ToPow10), BitOperations.Log2(value));
nint elementOffset = log2ToPow10[(int)ulong.Log2(value)];

// Read the associated power of 10.
ReadOnlySpan<ulong> powersOf10 =
Expand Down Expand Up @@ -102,8 +101,7 @@ public static int CountDigits(uint value)
];
Debug.Assert(table.Length == 32, "Every result of uint.Log2(value) needs a long entry in the table.");

// TODO: Replace with table[uint.Log2(value)] once https://github.com/dotnet/runtime/issues/79257 is fixed
long tableValue = Unsafe.Add(ref MemoryMarshal.GetReference(table), uint.Log2(value));
long tableValue = table[(int)uint.Log2(value)];
return (int)((value + tableValue) >> 32);
}

Expand Down
Loading