From b7625af1486acf5d99b13e4503dd0810296ff2d6 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 10 Dec 2022 11:12:43 -0700 Subject: [PATCH] Use generic math in Util.IntUtil --- nuget.config | 6 - src/LibHac/FsSrv/Impl/DeviceOperator.cs | 4 +- src/LibHac/Result.cs | 2 +- src/LibHac/Util/IntUtil.cs | 201 +++++++++++++++--- .../Kvdb/FlatMapKeyValueStoreTests.cs | 2 +- tests/LibHac.Tests/Random.cs | 34 +-- tests/LibHac.Tests/Util/IntUtilTests.cs | 200 +++++++++++++++++ 7 files changed, 398 insertions(+), 51 deletions(-) delete mode 100644 nuget.config create mode 100644 tests/LibHac.Tests/Util/IntUtilTests.cs diff --git a/nuget.config b/nuget.config deleted file mode 100644 index 41b398ea..00000000 --- a/nuget.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/LibHac/FsSrv/Impl/DeviceOperator.cs b/src/LibHac/FsSrv/Impl/DeviceOperator.cs index 4c79e93e..ba4466a6 100644 --- a/src/LibHac/FsSrv/Impl/DeviceOperator.cs +++ b/src/LibHac/FsSrv/Impl/DeviceOperator.cs @@ -39,14 +39,14 @@ public class DeviceOperator : IDeviceOperator private static Span GetSpan(OutBuffer buffer, long size) { - Assert.True(IntUtil.IsIntValueRepresentableAsInt(size)); + Assert.True(IntUtil.IsIntValueRepresentable(size)); return buffer.Buffer.Slice(0, (int)size); } private static ReadOnlySpan GetSpan(InBuffer buffer, long size) { - Assert.True(IntUtil.IsIntValueRepresentableAsInt(size)); + Assert.True(IntUtil.IsIntValueRepresentable(size)); return buffer.Buffer.Slice(0, (int)size); } diff --git a/src/LibHac/Result.cs b/src/LibHac/Result.cs index cfd6fc79..066441c9 100644 --- a/src/LibHac/Result.cs +++ b/src/LibHac/Result.cs @@ -386,7 +386,7 @@ public readonly struct Result : IEquatable [MethodImpl(MethodImplOptions.AggressiveInlining)] private static ulong SetBitsValueLong(int value, int bitsOffset, int bitsCount) { - return ((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset; + return unchecked(((uint)value & ~(~default(ulong) << bitsCount)) << bitsOffset); } /// diff --git a/src/LibHac/Util/IntUtil.cs b/src/LibHac/Util/IntUtil.cs index 52524e31..5e29b4f0 100644 --- a/src/LibHac/Util/IntUtil.cs +++ b/src/LibHac/Util/IntUtil.cs @@ -1,42 +1,189 @@ -namespace LibHac.Util; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace LibHac.Util; public static class IntUtil { - // Todo: Use generic math once C# 11 is out - public static bool IsIntValueRepresentableAsLong(ulong value) - { - return value <= long.MaxValue; - } + private static bool IsSignedType() where T : INumber => unchecked(T.IsNegative(T.Zero - T.One)); - public static bool IsIntValueRepresentableAsULong(long value) + // Todo .NET 8: Remove NoInlining. .NET 7's JIT doesn't inline everything properly + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool IsIntValueRepresentable(TFrom value) + where TTo : IMinMaxValue, INumber + where TFrom : IMinMaxValue, INumber { - return value >= 0; - } - - public static bool IsIntValueRepresentableAsInt(long value) - { - return value >= int.MinValue && value <= int.MaxValue; - } - - public static bool IsIntValueRepresentableAsUInt(long value) - { - return value >= uint.MinValue && value <= uint.MaxValue; - } - - public static bool CanAddWithoutOverflow(long x, long y) - { - if (y >= 0) + if (IsSignedType()) { - return x <= long.MaxValue - y; + if (IsSignedType()) + { + return IsIntValueRepresentableImplSToS(value); + } + else + { + return IsIntValueRepresentableImplSToU(value); + } } else { - return x >= unchecked(long.MinValue - y); + if (IsSignedType()) + { + return IsIntValueRepresentableImplUToS(value); + } + else + { + return IsIntValueRepresentableImplUToU(value); + } } } - public static bool CanAddWithoutOverflow(ulong x, ulong y) + // Methods for the 4 signed/unsigned TTo/TFrom permutations. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsIntValueRepresentableImplSToS(TFrom value) + where TTo : IMinMaxValue, INumber + where TFrom : IMinMaxValue, INumber { - return x <= ulong.MaxValue - y; + if (long.CreateTruncating(TTo.MinValue) <= long.CreateTruncating(TFrom.MinValue) && + long.CreateTruncating(TFrom.MaxValue) <= long.CreateTruncating(TTo.MaxValue)) + { + return true; + } + + return long.CreateTruncating(TTo.MinValue) <= long.CreateTruncating(value) && + long.CreateTruncating(value) <= long.CreateTruncating(TTo.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsIntValueRepresentableImplUToU(TFrom value) + where TTo : IMinMaxValue, INumber + where TFrom : IMinMaxValue, INumber + { + if (ulong.CreateTruncating(TTo.MinValue) <= ulong.CreateTruncating(TFrom.MinValue) && + ulong.CreateTruncating(TFrom.MaxValue) <= ulong.CreateTruncating(TTo.MaxValue)) + { + return true; + } + + return ulong.CreateTruncating(TTo.MinValue) <= ulong.CreateTruncating(value) && + ulong.CreateTruncating(value) <= ulong.CreateTruncating(TTo.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsIntValueRepresentableImplSToU(TFrom value) + where TTo : IMinMaxValue, INumber + where TFrom : IMinMaxValue, INumber, IComparisonOperators + { + if (value < TFrom.Zero) + { + return false; + } + + return IsIntValueRepresentableImplUToU(value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsIntValueRepresentableImplUToS(TFrom value) + where TTo : IMinMaxValue, INumber + where TFrom : IMinMaxValue, INumber + { + return ulong.CreateTruncating(value) <= ulong.CreateTruncating(TTo.MaxValue); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanAddWithoutOverflow(T x, T y) where T : IMinMaxValue, INumber + { + if (y >= T.Zero) + { + return x <= T.MaxValue - y; + } + else + { + return x >= T.MinValue - y; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanSubtractWithoutOverflow(T x, T y) where T : IMinMaxValue, INumber + { + if (y >= T.Zero) + { + return x >= T.MinValue + y; + } + else + { + return x <= T.MaxValue + y; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CanMultiplyWithoutOverflow(T x, T y) where T : IMinMaxValue, INumber + { + if (x == T.Zero || y == T.Zero) + return true; + + if (x > T.Zero) + { + if (y > T.Zero) + { + return y <= T.MaxValue / x; + } + else + { + return y >= T.MinValue / x; + } + } + else + { + if (y > T.Zero) + { + return x >= T.MinValue / y; + } + else + { + return y >= T.MaxValue / x; + } + } + } + + public static bool TryAddWithoutOverflow(out T value, T x, T y) where T : IMinMaxValue, INumber + { + if (CanAddWithoutOverflow(x, y)) + { + value = x + y; + return true; + } + else + { + value = default; + return false; + } + } + + public static bool TrySubtractWithoutOverflow(out T value, T x, T y) where T : IMinMaxValue, INumber + { + if (CanSubtractWithoutOverflow(x, y)) + { + value = x - y; + return true; + } + else + { + value = default; + return false; + } + } + + public static bool TryMultiplyWithoutOverflow(out T value, T x, T y) where T : IMinMaxValue, INumber + { + if (CanMultiplyWithoutOverflow(x, y)) + { + value = x * y; + return true; + } + else + { + value = default; + return false; + } } } \ No newline at end of file diff --git a/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs b/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs index 7b36f616..78a4abb5 100644 --- a/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs +++ b/tests/LibHac.Tests/Kvdb/FlatMapKeyValueStoreTests.cs @@ -47,7 +47,7 @@ public class FlatMapKeyValueStoreTests for (int i = 0; i < count; i++) { byte[] value = new byte[startingSize + i]; - value.AsSpan().Fill((byte)count); + value.AsSpan().Fill(unchecked((byte)count)); values[i] = value; } diff --git a/tests/LibHac.Tests/Random.cs b/tests/LibHac.Tests/Random.cs index d776d39f..abd14ad6 100644 --- a/tests/LibHac.Tests/Random.cs +++ b/tests/LibHac.Tests/Random.cs @@ -11,23 +11,26 @@ public struct Random public Random(ulong seed) { - ulong x = seed; - ulong z = x + 0x9e3779b97f4a7c15; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - x = z ^ (z >> 31); - z = (x += 0x9e3779b97f4a7c15); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - _state1 = z ^ (z >> 31); - _state2 = x; + unchecked + { + ulong x = seed; + ulong z = x + 0x9e3779b97f4a7c15; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + x = z ^ (z >> 31); + z = (x += 0x9e3779b97f4a7c15); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + _state1 = z ^ (z >> 31); + _state2 = x; + } } ulong Next() { ulong s0 = _state1; ulong s1 = _state2; - ulong result = BitOperations.RotateLeft(s0 + s1, 17) + s0; + ulong result = unchecked(BitOperations.RotateLeft(s0 + s1, 17) + s0); s1 ^= s0; _state1 = BitOperations.RotateLeft(s0, 49) ^ s1 ^ (s1 << 21); @@ -43,8 +46,11 @@ public struct Random throw new ArgumentOutOfRangeException(nameof(minValue)); } - long range = (long)maxValue - minValue; - return (int)((uint)Next() * (1.0 / uint.MaxValue) * range) + minValue; + unchecked + { + long range = (long)maxValue - minValue; + return (int)((uint)Next() * (1.0 / uint.MaxValue) * range) + minValue; + } } public void NextBytes(Span buffer) @@ -58,7 +64,7 @@ public struct Random for (int i = bufferUlong.Length * sizeof(ulong); i < buffer.Length; i++) { - buffer[i] = (byte)Next(); + buffer[i] = unchecked((byte)Next()); } } } \ No newline at end of file diff --git a/tests/LibHac.Tests/Util/IntUtilTests.cs b/tests/LibHac.Tests/Util/IntUtilTests.cs new file mode 100644 index 00000000..b89c6e56 --- /dev/null +++ b/tests/LibHac.Tests/Util/IntUtilTests.cs @@ -0,0 +1,200 @@ +using LibHac.Util; +using Xunit; + +namespace LibHac.Tests.Util; + +public class IntUtilTests +{ + [Theory] + [InlineData(0x80, 0x7F, true)] + [InlineData(0x80, 0x80, false)] + public void CanAddWithoutOverflow_Byte(byte x, byte y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(x, y)); + Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(y, x)); + } + + [Theory] + [InlineData(-0x8000, -1, false)] + [InlineData(-0x8000, 0, true)] + [InlineData(0x4000, 0x3FFF, true)] + [InlineData(0x4000, 0x4000, false)] + [InlineData(-0x4000, -0x4000, true)] + public void CanAddWithoutOverflow_Short(short x, short y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(x, y)); + Assert.Equal(expectedResult, IntUtil.CanAddWithoutOverflow(y, x)); + } + + [Theory] + [InlineData(0x80, 0x80, true)] + [InlineData(0x80, 0x81, false)] + public void CanSubtractWithoutOverflow_Byte(byte x, byte y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanSubtractWithoutOverflow(x, y)); + } + + [Theory] + [InlineData(-0x8000, 1, false)] + [InlineData(-0x8000, 0, true)] + [InlineData(-0x4000, 0x4000, true)] + [InlineData(-0x4000, 0x4001, false)] + [InlineData(0x4000, -0x4000, false)] + public void CanSubtractWithoutOverflow_Short(short x, short y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanSubtractWithoutOverflow(x, y)); + } + + [Theory] + [InlineData(0xFF, 0, true)] + [InlineData(0x55, 3, true)] + [InlineData(0x40, 4, false)] + public void CanMultiplyWithoutOverflow_Byte(byte x, byte y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(x, y)); + Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(y, x)); + } + + [Theory] + [InlineData(0x7FFF, 0, true)] + [InlineData(+0x1249, +7, true)] + [InlineData(-0x1249, +7, true)] + [InlineData(-0x1249, -7, true)] + [InlineData(+0x1249, -7, true)] + [InlineData(+0x2000, +4, false)] + [InlineData(-0x2000, +4, true)] + [InlineData(-0x2000, -4, false)] + [InlineData(+0x2000, -4, true)] + [InlineData(-0x2001, +4, false)] + [InlineData(+0x2001, -4, false)] + [InlineData(-0x8000, -1, false)] + [InlineData(-0x7FFF, -1, true)] + public void CanMultiplyWithoutOverflow_Short(short x, short y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(x, y)); + Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(y, x)); + } + + [Theory] + [InlineData(0x5555555555555555, 3, true)] + [InlineData(0x4000000000000000, 4, false)] + public void CanMultiplyWithoutOverflow_Ulong(ulong x, ulong y, bool expectedResult) + { + Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(x, y)); + Assert.Equal(expectedResult, IntUtil.CanMultiplyWithoutOverflow(y, x)); + } + + [Theory] + [InlineData(0, true)] + [InlineData(-1, false)] + [InlineData(unchecked((long)0x8111111100000000), false)] + [InlineData(long.MinValue, false)] + [InlineData(long.MaxValue, true)] + public void IsIntValueRepresentable_LongToUlong(long value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(5, true)] + [InlineData(long.MaxValue, true)] + [InlineData(long.MaxValue + 1LU, false)] + [InlineData(ulong.MaxValue, false)] + public void IsIntValueRepresentable_ULongToLong(ulong value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(-1, true)] + [InlineData(int.MinValue, true)] + [InlineData(int.MaxValue, true)] + [InlineData(int.MinValue - 1L, false)] + [InlineData(int.MaxValue + 1L, false)] + public void IsIntValueRepresentable_LongToInt(long value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(-1, true)] + [InlineData(short.MinValue, true)] + [InlineData(short.MaxValue, true)] + [InlineData(short.MinValue - 1L, false)] + [InlineData(short.MaxValue + 1L, false)] + public void IsIntValueRepresentable_LongToShort(long value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(-1, false)] + [InlineData(uint.MaxValue, true)] + [InlineData(uint.MaxValue + 1L, false)] + public void IsIntValueRepresentable_LongToUint(long value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(-1, false)] + [InlineData(ushort.MaxValue, true)] + [InlineData(ushort.MaxValue + 1L, false)] + public void IsIntValueRepresentable_LongToUshort(long value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(-1, false)] + [InlineData(short.MinValue, false)] + [InlineData(short.MaxValue, true)] + public void IsIntValueRepresentable_ShortToUshort(short value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(0xFFFFF000, false)] + [InlineData(0xFFFF7FFF, false)] + [InlineData(short.MaxValue, true)] + [InlineData(short.MaxValue + 1, false)] + public void IsIntValueRepresentable_UintToShort(uint value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(uint.MaxValue, true)] + [InlineData((ulong)uint.MaxValue + 1, false)] + public void IsIntValueRepresentable_UlongToUint(ulong value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable(value); + Assert.Equal(expectedResult, actualResult); + } + + [Theory] + [InlineData(0, true)] + [InlineData(uint.MaxValue, true)] + public void IsIntValueRepresentable_UintToUlong(uint value, bool expectedResult) + { + bool actualResult = IntUtil.IsIntValueRepresentable (value); + Assert.Equal(expectedResult, actualResult); + } +} \ No newline at end of file