diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 5183e30b02a0c9..18c9a8f9c295ed 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1048,7 +1048,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)); + + // 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(); @@ -1472,6 +1485,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); @@ -1482,7 +1497,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; } @@ -1510,6 +1525,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)) { @@ -1597,7 +1617,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)); } diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 896873d49da097..eee82e571b5cf9 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -6601,6 +6601,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))) + { + // 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)) + { + 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). @@ -6676,6 +6718,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: @@ -9978,6 +10027,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); diff --git a/src/coreclr/jit/valuenum.h b/src/coreclr/jit/valuenum.h index 0edf313ed41ae3..9c96241809b35c 100644 --- a/src/coreclr/jit/valuenum.h +++ b/src/coreclr/jit/valuenum.h @@ -1056,6 +1056,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 CheckedBoundVNSet; // Returns true if the VN is known or likely to appear as the conservative value number @@ -1409,6 +1412,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 + bool IsVNBinFuncWithConst(ValueNum vn, VNFunc func, ValueNum* op, T* cns) + { + 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); diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs index 35f7bba785ae55..5c507bff22f760 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs @@ -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 powersOf10 = @@ -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); }