runtime: System.UInt64.TryMultiply to check for overflow without throwing exceptions

@tannergooding, @pgovind What do you guys think of the idea of adding the following TryMultiply methods to System.UInt64 or System.Math? They return null if the multiplication would overflow:

namespace System
{
	public struct UInt64  // Alternatively System.Math
	{
		public static unsafe UInt64? TryMultiply(UInt64 a, UInt64 b)
		{
			unchecked {
				if (System.Runtime.Intrinsics.X86.Bmi2.X64.IsSupported)
				{
					uint64 low;
					if (System.Runtime.Intrinsics.X86.Bmi2.X64.MultiplyNoFlags(a, b, &low) == 0)
						return low;
					return null;
				}
				if (b == 0 || a <= (UInt64.MaxValue / b)) return a * b;
				return null;
			}
		}

		// And for System.UInt32.TryMultiply or System.Math.TryMultiply(UInt32, UInt32):
		[MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
		public static UInt32? TryMultiply(UInt32 a, UInt32 b)
		{
			unchecked {
				UInt64 product64 = (UInt64)a * b;
				UInt32 product32 = (UInt32)product64;
				if (product32 == product64) return product32;
				return null;
			}
		}
		
		[MethodImplAttribute(MethodImplOptions.AggressiveInlining)]
		public static UInt64? TryAdd(UInt64 a, UInt64 b)
		{
			UInt64 result = unchecked(a + b);
			return (result >= a) ? result : default(UInt64?);
		}

	}
}

These methods intentionally do not use checked(a * b) because they’re intended for scenarios where the high cost of throwing exceptions is unacceptable.

Maybe you know, and could answer, the question of whether my TryMultiply implementations above are the best-possible implementations. Because of gaps in my knowledge of Intel processors, I’m unsure whether the above are the best implementations. I wonder whether or not it would be beneficial to add a few more intrinsics/methods to System.Runtime.Intrinsics.X86 in order to gain the ability to read the carry and/or overflow flags – if necessary. I don’t know enough CPU detail to say whether or not the x86-64 carry and/or overflow flags should be used, versus whether the above implementations are already the best overall. Maybe someone with detailed CPU knowledge could answer this question.

In any event, regardless of whether the above implementations are the best-possible, they’re already at least good implementations that could be added to NETFW 5.x immediately, and later further optimized if necessary.

See also issue https://github.com/dotnet/runtime/issues/13026 where @RobertBouillon suggests new CIL instructions. Although I like his idea, it’s far more work than the alternative of TryMultiply etc methods such as the above. The above TryMultiply etc methods could provide the desired functionality very soon, unlike the complexity and politics of creating new CIL instructions.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (11 by maintainers)

Most upvoted comments

I agree, however we have prior art in static ReadOnlySpan<byte> ThisDoesNotActuallyAllocate => new byte[123] { ... };.

Disable overflow exceptions per-thread at runtime?

You can express that using try/catch today. The only difference is performance. If performance is the only reason for introducing new API, we should always look at whether it is feasible to optimized the existing pattern in the JIT for a reasonable cost.

What would it take for the JIT to optimize try { int x = checked(a+b); } catch (OverflowException) { DoSomething(); } into what you have suggested? No new APIs necessary. The existing code just gets faster.