diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 804a768439565..d7369b3adcce2 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -196,7 +196,7 @@ bool IntegralRange::Contains(int64_t value) const ForNode(node->AsQmark()->ElseNode(), compiler)); case GT_CAST: - return ForCastOutput(node->AsCast()); + return ForCastOutput(node->AsCast(), compiler); #if defined(FEATURE_HW_INTRINSICS) case GT_HWINTRINSIC: @@ -369,12 +369,13 @@ bool IntegralRange::Contains(int64_t value) const // Unlike ForCastInput, this method supports casts from floating point types. // // Arguments: -// cast - the cast node for which the range will be computed +// cast - the cast node for which the range will be computed +// compiler - Compiler object // // Return Value: // The range this cast produces - see description. // -/* static */ IntegralRange IntegralRange::ForCastOutput(GenTreeCast* cast) +/* static */ IntegralRange IntegralRange::ForCastOutput(GenTreeCast* cast, Compiler* compiler) { var_types fromType = genActualType(cast->CastOp()); var_types toType = cast->CastToType(); @@ -407,6 +408,13 @@ bool IntegralRange::Contains(int64_t value) const return ForCastInput(cast); } + // if we're upcasting and the cast op is a known non-negative - consider + // this cast unsigned + if (!fromUnsigned && (genTypeSize(toType) >= genTypeSize(fromType))) + { + fromUnsigned = cast->CastOp()->IsNeverNegative(compiler); + } + // CAST(uint/int <- ulong/long) - [INT_MIN..INT_MAX] // CAST(ulong/long <- uint) - [0..UINT_MAX] // CAST(ulong/long <- int) - [INT_MIN..INT_MAX] diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 21d88cabdc3b1..f9b67f0415d99 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1355,7 +1355,7 @@ class IntegralRange static IntegralRange ForNode(GenTree* node, Compiler* compiler); static IntegralRange ForCastInput(GenTreeCast* cast); - static IntegralRange ForCastOutput(GenTreeCast* cast); + static IntegralRange ForCastOutput(GenTreeCast* cast, Compiler* compiler); static IntegralRange Union(IntegralRange range1, IntegralRange range2); #ifdef DEBUG diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 0462eab5ae842..f9ad4c2c36500 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -325,6 +325,21 @@ void RangeCheck::OptimizeRangeCheck(BasicBlock* block, Statement* stmt, GenTree* } } + if (m_pCompiler->vnStore->GetVNFunc(idxVn, &funcApp) && (funcApp.m_func == (VNFunc)GT_UMOD)) + { + // We can always omit bound checks for Arr[X u% Arr.Length] pattern (unsigned MOD). + // + // if arr.Length is 0 we technically should keep the bounds check, but since the expression + // has to throw DividedByZeroException anyway - no special handling needed. + if (funcApp.m_args[1] == arrLenVn) + { + JITDUMP("[RangeCheck::OptimizeRangeCheck] UMOD(X, ARR_LEN) is always between bounds\n"); + m_pCompiler->optRemoveRangeCheck(bndsChk, comma, stmt); + m_updateStmt = true; + return; + } + } + // Get the range for this index. Range range = GetRange(block, treeIndex, false DEBUGARG(0)); diff --git a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Utils/HashLookup.cs b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Utils/HashLookup.cs index fae7beb9589b4..08ac4fa0995ab 100644 --- a/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Utils/HashLookup.cs +++ b/src/libraries/System.Linq.Parallel/src/System/Linq/Parallel/Utils/HashLookup.cs @@ -78,7 +78,7 @@ private bool Find(TKey key, bool add, bool set, [MaybeNullWhen(false)] ref TValu { int hashCode = GetKeyHashCode(key); - for (int i = buckets[hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) + for (int i = buckets[(uint)hashCode % buckets.Length] - 1; i >= 0; i = slots[i].next) { if (slots[i].hashCode == hashCode && AreKeysEqual(slots[i].key, key)) { diff --git a/src/libraries/System.Linq/src/System/Linq/Lookup.cs b/src/libraries/System.Linq/src/System/Linq/Lookup.cs index 65ee9906894ac..2ee2d329255eb 100644 --- a/src/libraries/System.Linq/src/System/Linq/Lookup.cs +++ b/src/libraries/System.Linq/src/System/Linq/Lookup.cs @@ -196,7 +196,7 @@ private int InternalGetHashCode(TKey key) internal Grouping? GetGrouping(TKey key, bool create) { int hashCode = InternalGetHashCode(key); - for (Grouping? g = _groupings[hashCode % _groupings.Length]; g != null; g = g._hashNext) + for (Grouping? g = _groupings[(uint)hashCode % _groupings.Length]; g != null; g = g._hashNext) { if (g._hashCode == hashCode && _comparer.Equals(g._key, key)) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs index bb470cc9b5fd8..daaceaefef1ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs @@ -1322,7 +1322,7 @@ private ref int GetBucket(uint hashCode) #if TARGET_64BIT return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)]; #else - return ref buckets[hashCode % (uint)buckets.Length]; + return ref buckets[(uint)hashCode % buckets.Length]; #endif } diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs index 5cd51ae4086fa..6edff0f0eb414 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Timer.cs @@ -508,7 +508,7 @@ internal TimerQueueTimer(TimerCallback timerCallback, object? state, uint dueTim { _executionContext = ExecutionContext.Capture(); } - _associatedTimerQueue = TimerQueue.Instances[Thread.GetCurrentProcessorId() % TimerQueue.Instances.Length]; + _associatedTimerQueue = TimerQueue.Instances[(uint)Thread.GetCurrentProcessorId() % TimerQueue.Instances.Length]; // After the following statement, the timer may fire. No more manipulation of timer state outside of // the lock is permitted beyond this point! diff --git a/src/tests/JIT/opt/RangeChecks/ModLength.cs b/src/tests/JIT/opt/RangeChecks/ModLength.cs new file mode 100644 index 0000000000000..765a99dd7d85d --- /dev/null +++ b/src/tests/JIT/opt/RangeChecks/ModLength.cs @@ -0,0 +1,99 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +public class ModLength +{ + public static int Main() + { + Throws(() => Test1(new int[0], 0)); + Throws(() => Test2(new int[0], 1)); + Throws(() => Test3(new int[0], int.MaxValue)); + Throws(() => Test4(new int[0], 0)); + Throws(() => Test5(new int[0], 0)); + Throws(() => Test6(new int[0], 0)); + Throws(() => Test7(new int[0], 0)); + Throws(() => Test8(new int[0], 0)); + Throws(() => Test9(new int[0], 0)); + Test1(new int[1], 1); + Test2(new int[1], 1); + Throws(() => Test9(new int[1], 2)); + Test3(new int[1], int.MaxValue); + Throws(() => Test4(new int[1], int.MinValue)); + Test5(new int[1], -1); + Test6(new int[1], 1); + Test7(new int[1], 1); + Test8(new int[1], 1); + Test1(new int[10], 10); + Test2(new int[10], 11); + Test3(new int[10], int.MaxValue); + Throws(() => Test4(new int[10], int.MinValue)); + Throws(() => Test5(new int[10], -1)); + Test6(new int[10], 0); + Test7(new int[10], 0); + Throws(() => Test8(new int[10], 0)); + return 100; + } + + static void Throws(Action action, [CallerLineNumber] int line = 0) + { + try + { + action(); + } + catch (Exception e) + { + if (e is T) + { + return; + } + throw new InvalidOperationException($"{typeof(T)} exception was expected, actual: {e.GetType()}"); + } + throw new InvalidOperationException($"{typeof(T)} exception was expected"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test1(int[] arr, int index) => arr[index % arr.Length]; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test2(int[] arr, int index) => arr[(int)index % (int)arr.Length]; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test3(int[] arr, int index) => arr[(int)((uint)index % (uint)arr.Length)]; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test4(int[] arr, int index) => arr[arr.Length % index]; + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test5(int[] arr, int index) + { + var span = arr.AsSpan(); + return arr[index % arr.Length]; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test6(int[] arr, int index) + { + var span = arr.AsSpan(); + return span[(int)index % (int)span.Length]; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test7(int[] arr, int index) + { + var span = arr.AsSpan(); + return span[(int)((uint)index % (uint)span.Length)]; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test8(int[] arr, int index) + { + var span = arr.AsSpan(); + return span[span.Length % index]; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static int Test9(int[] arr, int index) => arr[index / arr.Length]; +} diff --git a/src/tests/JIT/opt/RangeChecks/ModLength.csproj b/src/tests/JIT/opt/RangeChecks/ModLength.csproj new file mode 100644 index 0000000000000..6946bed81bfd5 --- /dev/null +++ b/src/tests/JIT/opt/RangeChecks/ModLength.csproj @@ -0,0 +1,9 @@ + + + Exe + True + + + + +