Bug 1441657 - Implement mozilla::WrappingMultiply. r=froydnj
authorJeff Walden <jwalden@mit.edu>
Thu, 15 Feb 2018 17:36:55 -0800
changeset 762440 cf32e82edfb795a94740fef748c9ef503425e448
parent 762439 bc01e52c16d77ba3a7ab45ec195892c88e5f861b
child 762441 0dc87bc686083ce1ee3721c6cfb38f31954f24bd
push id101169
push userluca.greco@alcacoop.it
push dateFri, 02 Mar 2018 12:32:00 +0000
reviewersfroydnj
bugs1441657
milestone60.0a1
Bug 1441657 - Implement mozilla::WrappingMultiply. r=froydnj
build/sanitizers/ubsan_signed_overflow_blacklist.txt
build/sanitizers/ubsan_unsigned_overflow_blacklist.txt
js/public/Utility.h
js/src/jsmath.cpp
mfbt/HashFunctions.h
mfbt/WrappingOperations.h
mfbt/tests/TestWrappingOperations.cpp
--- a/build/sanitizers/ubsan_signed_overflow_blacklist.txt
+++ b/build/sanitizers/ubsan_signed_overflow_blacklist.txt
@@ -243,19 +243,16 @@ src:*/security/nss/lib/dbm/src/h_func.c
 src:*/security/nss/lib/freebl/sha512.c
 src:*/security/nss/lib/freebl/md5.c
 src:*/XorShift128PlusRNG.h
 src:*/xpcom/ds/PLDHashTable.cpp
 
 # Hash/Cache function in Skia
 fun:*GradientShaderCache*Build32bitCache*
 
-# Hash function in js/public/Utility.h
-fun:ScrambleHashCode*
-
 # Hashing functions in Cairo
 fun:*_hash_matrix_fnv*
 fun:*_hash_mix_bits*
 fun:*_cairo_hash_string*
 fun:*_cairo_hash_bytes*
 
 # Hash function in modules/libjar/nsZipArchive.cpp
 fun:*HashName*
--- a/build/sanitizers/ubsan_unsigned_overflow_blacklist.txt
+++ b/build/sanitizers/ubsan_unsigned_overflow_blacklist.txt
@@ -250,19 +250,16 @@ src:*/security/nss/lib/dbm/src/h_func.c
 src:*/security/nss/lib/freebl/sha512.c
 src:*/security/nss/lib/freebl/md5.c
 src:*/XorShift128PlusRNG.h
 src:*/xpcom/ds/PLDHashTable.cpp
 
 # Hash/Cache function in Skia
 fun:*GradientShaderCache*Build32bitCache*
 
-# Hash function in js/public/Utility.h
-fun:ScrambleHashCode*
-
 # Hashing functions in Cairo
 fun:*_hash_matrix_fnv*
 fun:*_hash_mix_bits*
 fun:*_cairo_hash_string*
 fun:*_cairo_hash_bytes*
 
 # Hash function in modules/libjar/nsZipArchive.cpp
 fun:*HashName*
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -10,16 +10,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Compiler.h"
 #include "mozilla/Move.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/TemplateLib.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/WrappingOperations.h"
 
 #include <stdlib.h>
 #include <string.h>
 
 #ifdef JS_OOM_DO_BACKTRACES
 #include <execinfo.h>
 #include <stdio.h>
 #endif
@@ -692,17 +693,17 @@ ScrambleHashCode(HashNumber h)
      * Programming, 6.4. This mixes all the bits of the input hash code h.
      *
      * The value of goldenRatio is taken from the hex
      * expansion of the golden ratio, which starts 1.9E3779B9....
      * This value is especially good if values with consecutive hash codes
      * are stored in a hash table; see Knuth for details.
      */
     static const HashNumber goldenRatio = 0x9E3779B9U;
-    return h * goldenRatio;
+    return mozilla::WrappingMultiply(h, goldenRatio);
 }
 
 } /* namespace detail */
 
 } /* namespace js */
 
 /* sixgill annotation defines */
 #ifndef HAVE_STATIC_ANNOTATIONS
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -110,17 +110,17 @@ using mozilla::ExponentComponent;
 using mozilla::FloatingPoint;
 using mozilla::IsFinite;
 using mozilla::IsInfinite;
 using mozilla::IsNaN;
 using mozilla::IsNegative;
 using mozilla::IsNegativeZero;
 using mozilla::PositiveInfinity;
 using mozilla::NegativeInfinity;
-using mozilla::WrapToSigned;
+using mozilla::WrappingMultiply;
 using JS::ToNumber;
 using JS::GenericNaN;
 
 static const JSConstDoubleSpec math_constants[] = {
     {"E"      ,  M_E       },
     {"LOG2E"  ,  M_LOG2E   },
     {"LOG10E" ,  M_LOG10E  },
     {"LN2"    ,  M_LN2     },
@@ -479,24 +479,23 @@ js::math_floor(JSContext* cx, unsigned a
     }
 
     return math_floor_handle(cx, args[0], args.rval());
 }
 
 bool
 js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res)
 {
-    uint32_t a = 0, b = 0;
-    if (!lhs.isUndefined() && !ToUint32(cx, lhs, &a))
+    int32_t a = 0, b = 0;
+    if (!lhs.isUndefined() && !ToInt32(cx, lhs, &a))
         return false;
-    if (!rhs.isUndefined() && !ToUint32(cx, rhs, &b))
+    if (!rhs.isUndefined() && !ToInt32(cx, rhs, &b))
         return false;
 
-    uint32_t product = a * b;
-    res.setInt32(WrapToSigned(product));
+    res.setInt32(WrappingMultiply(a, b));
     return true;
 }
 
 bool
 js::math_imul(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
--- a/mfbt/HashFunctions.h
+++ b/mfbt/HashFunctions.h
@@ -47,16 +47,17 @@
 #ifndef mozilla_HashFunctions_h
 #define mozilla_HashFunctions_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Char16.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Types.h"
+#include "mozilla/WrappingOperations.h"
 
 #include <stdint.h>
 
 #ifdef __cplusplus
 namespace mozilla {
 
 /**
  * The golden ratio as a 32-bit fixed-point value.
@@ -91,17 +92,19 @@ AddU32ToHash(uint32_t aHash, uint32_t aV
    *
    * The rotation length of 5 is also arbitrary, although an odd number is again
    * preferable so our hash explores the whole universe of possible rotations.
    *
    * Finally, we multiply by the golden ratio *after* xor'ing, not before.
    * Otherwise, if |aHash| is 0 (as it often is for the beginning of a
    * message), the expression
    *
-   *   (kGoldenRatioU32 * RotateBitsLeft(aHash, 5)) |xor| aValue
+   *   mozilla::WrappingMultiply(kGoldenRatioU32, RotateBitsLeft(aHash, 5))
+   *   |xor|
+   *   aValue
    *
    * evaluates to |aValue|.
    *
    * (Number-theoretic aside: Because any odd number |m| is relatively prime to
    * our modulus (2^32), the list
    *
    *    [x * m (mod 2^32) for 0 <= x < 2^32]
    *
@@ -109,17 +112,18 @@ AddU32ToHash(uint32_t aHash, uint32_t aV
    * cause us to skip any possible hash values.
    *
    * It's also nice if |m| has large-ish order mod 2^32 -- that is, if the
    * smallest k such that m^k == 1 (mod 2^32) is large -- so we can safely
    * multiply our hash value by |m| a few times without negating the
    * multiplicative effect.  Our golden ratio constant has order 2^29, which is
    * more than enough for our purposes.)
    */
-  return kGoldenRatioU32 * (RotateBitsLeft32(aHash, 5) ^ aValue);
+  return mozilla::WrappingMultiply(kGoldenRatioU32,
+                                   (RotateBitsLeft32(aHash, 5) ^ aValue));
 }
 
 /**
  * AddUintptrToHash takes sizeof(uintptr_t) as a template parameter.
  */
 template<size_t PtrSize>
 inline uint32_t
 AddUintptrToHash(uint32_t aHash, uintptr_t aValue)
--- a/mfbt/WrappingOperations.h
+++ b/mfbt/WrappingOperations.h
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*
- * Math operations that implement wraparound semantics on overflow or underflow.
+ * Math operations that implement wraparound semantics on overflow or underflow
+ * without performing C++ undefined behavior or tripping up compiler-based
+ * integer-overflow sanitizers.
  */
 
 #ifndef mozilla_WrappingOperations_h
 #define mozilla_WrappingOperations_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/TypeTraits.h"
 
@@ -82,11 +84,105 @@ struct WrapToSignedHelper
  */
 template<typename UnsignedType>
 inline constexpr typename detail::WrapToSignedHelper<UnsignedType>::SignedType
 WrapToSigned(UnsignedType aValue)
 {
   return detail::WrapToSignedHelper<UnsignedType>::compute(aValue);
 }
 
+namespace detail {
+
+template<typename T>
+struct WrappingMultiplyHelper
+{
+private:
+  using UnsignedT = typename MakeUnsigned<T>::Type;
+
+  MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
+  static UnsignedT
+  multiply(UnsignedT aX, UnsignedT aY)
+  {
+  // |mozilla::WrappingMultiply| isn't constexpr because MSVC warns about well-
+  // defined unsigned integer overflows that may happen here.
+  // https://msdn.microsoft.com/en-us/library/4kze989h.aspx  And constexpr
+  // seems to cause the warning to be emitted at |WrappingMultiply| call *sites*
+  // instead of here, so these #pragmas are ineffective.
+  //
+  // https://stackoverflow.com/questions/37658794/integer-constant-overflow-warning-in-constexpr
+  //
+  // If/when MSVC fix this bug, we should make these functions constexpr.
+
+    // Begin with |1U| to ensure the overall operation chain is never promoted
+    // to signed integer operations that might have *signed* integer overflow.
+    return static_cast<UnsignedT>(1U * aX * aY);
+  }
+
+  static T
+  toResult(UnsignedT aX, UnsignedT aY)
+  {
+    // We could always return WrapToSigned and rely on unsigned conversion
+    // undoing the wrapping when |T| is unsigned, but this seems clearer.
+    return IsSigned<T>::value
+           ? WrapToSigned(multiply(aX, aY))
+           : multiply(aX, aY);
+  }
+
+public:
+  MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW
+  static T compute(T aX, T aY)
+  {
+    return toResult(static_cast<UnsignedT>(aX), static_cast<UnsignedT>(aY));
+  }
+};
+
+} // namespace detail
+
+/**
+ * Multiply two integers of the same type, and return the result converted to
+ * that type using wraparound semantics.  This function:
+ *
+ *   1) makes explicit the desire for and dependence upon wraparound semantics,
+ *   2) provides wraparound semantics *safely* with no signed integer overflow
+ *      that would have undefined behavior, and
+ *   3) won't trip up {,un}signed-integer overflow sanitizers (see
+ *      build/autoconf/sanitize.m4) at runtime.
+ *
+ * For N-bit unsigned integer types, this is equivalent to multiplying the two
+ * numbers, then taking the result mod 2**N:
+ *
+ *   WrappingMultiply(uint32_t(42), uint32_t(17)) is 714 (714 mod 2**32);
+ *   WrappingMultiply(uint8_t(16), uint8_t(24)) is 128 (384 mod 2**8);
+ *   WrappingMultiply(uint16_t(3), uint16_t(32768)) is 32768 (98304 mod 2*16).
+ *
+ * Use this function for any unsigned multiplication that can wrap (instead of
+ * normal C++ multiplication) to play nice with the sanitizers.  But it's
+ * especially important to use it for uint16_t multiplication: in most compilers
+ * for uint16_t*uint16_t some operand values will trigger signed integer
+ * overflow with undefined behavior!  http://kqueue.org/blog/2013/09/17/cltq/
+ * has the grody details.  Other than that one weird case, WrappingMultiply on
+ * unsigned types is the same as C++ multiplication.
+ *
+ * For N-bit signed integer types, this is equivalent to multiplying the two
+ * numbers wrapped to unsigned, taking the product mod 2**N, then wrapping that
+ * number to the signed range:
+ *
+ *   WrappingMultiply(int16_t(-456), int16_t(123)) is 9448 ((-56088 mod 2**16) + 2**16);
+ *   WrappingMultiply(int32_t(-7), int32_t(-9)) is 63 (63 mod 2**32);
+ *   WrappingMultiply(int8_t(16), int8_t(24)) is -128 ((384 mod 2**8) - 2**8);
+ *   WrappingMultiply(int8_t(16), int8_t(255)) is -16 ((4080 mod 2**8) - 2**8).
+ *
+ * There is no ready equivalent to this operation in C++, as applying C++
+ * multiplication to signed integer types in ways that trigger overflow has
+ * undefined behavior.  However, it's how multiplication *tends* to behave with
+ * most compilers in most situations, even though it's emphatically not required
+ * to do so.
+ */
+template<typename T>
+inline T
+WrappingMultiply(T aX, T aY)
+{
+  return detail::WrappingMultiplyHelper<T>::compute(aX, aY);
+}
+
 } /* namespace mozilla */
 
 #endif /* mozilla_WrappingOperations_h */
--- a/mfbt/tests/TestWrappingOperations.cpp
+++ b/mfbt/tests/TestWrappingOperations.cpp
@@ -1,18 +1,20 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "mozilla/Assertions.h"
 #include "mozilla/WrappingOperations.h"
 
 #include <stdint.h>
 
+using mozilla::WrappingMultiply;
 using mozilla::WrapToSigned;
 
 // NOTE: In places below |-FOO_MAX - 1| is used instead of |-FOO_MIN| because
 //       in C++ numeric literals are full expressions -- the |-| in a negative
 //       number is technically separate.  So with most compilers that limit
 //       |int| to the signed 32-bit range, something like |-2147483648| is
 //       operator-() applied to an *unsigned* expression.  And MSVC, at least,
 //       warns when you do that.  (The operation is well-defined, but it likely
@@ -54,13 +56,193 @@ static_assert(WrapToSigned(uint64_t(9223
               "works for 64-bit numbers, wraparound low end");
 static_assert(WrapToSigned(uint64_t(9223372036854775808ULL + 8005552368LL)) ==
                 -9223372036854775807LL - 1 + 8005552368LL,
               "works for 64-bit numbers, wraparound mid");
 static_assert(WrapToSigned(uint64_t(9223372036854775808ULL + 9223372036854775807ULL)) ==
                 -9223372036854775807LL - 1 + 9223372036854775807LL,
               "works for 64-bit numbers, wraparound high end");
 
+template<typename T>
+inline constexpr bool
+TestEqual(T aX, T aY)
+{
+  return aX == aY;
+}
+
+static void
+TestWrappingMultiply8()
+{
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint8_t(0), uint8_t(128)),
+                               uint8_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint8_t(128), uint8_t(1)),
+                               uint8_t(128)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint8_t(2), uint8_t(128)),
+                               uint8_t(0)),
+                     "2 times high bit overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint8_t(8), uint8_t(16)),
+                               uint8_t(128)),
+                     "multiply that populates the high bit produces that value");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint8_t(127), uint8_t(127)),
+                               uint8_t(1)),
+                     "multiplying signed maxvals overflows all the way to 1");
+
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int8_t(0), int8_t(-128)),
+                               int8_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int8_t(-128), int8_t(1)),
+                               int8_t(-128)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int8_t(2), int8_t(-128)),
+                               int8_t(0)),
+                     "2 times min overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int8_t(16), int8_t(24)),
+                               int8_t(-128)),
+                     "multiply that populates the sign bit produces minval");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int8_t(8), int8_t(16)),
+                               int8_t(-128)),
+                     "multiply that populates the sign bit produces minval");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int8_t(127), int8_t(127)),
+                               int8_t(1)),
+                     "multiplying maxvals overflows all the way to 1");
+}
+
+static void
+TestWrappingMultiply16()
+{
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint16_t(0), uint16_t(32768)),
+                               uint16_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint16_t(32768), uint16_t(1)),
+                               uint16_t(32768)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint16_t(2), uint16_t(32768)),
+                               uint16_t(0)),
+                     "2 times high bit overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint16_t(3), uint16_t(32768)),
+                               uint16_t(-32768)),
+                     "3 * 32768 - 65536 is 32768");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint16_t(64), uint16_t(512)),
+                               uint16_t(32768)),
+                     "multiply that populates the high bit produces that value");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint16_t(32767), uint16_t(32767)),
+                               uint16_t(1)),
+                    "multiplying signed maxvals overflows all the way to 1");
+
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int16_t(0), int16_t(-32768)),
+                               int16_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int16_t(-32768), int16_t(1)),
+                               int16_t(-32768)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int16_t(-456), int16_t(123)),
+                               int16_t(9448)),
+                     "multiply opposite signs, then add 2**16 for the result");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int16_t(2), int16_t(-32768)),
+                               int16_t(0)),
+                     "2 times min overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int16_t(64), int16_t(512)),
+                               int16_t(-32768)),
+                     "multiply that populates the sign bit produces minval");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int16_t(32767), int16_t(32767)),
+                               int16_t(1)),
+                     "multiplying maxvals overflows all the way to 1");
+}
+
+static void
+TestWrappingMultiply32()
+{
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint32_t(0), uint32_t(2147483648)),
+                               uint32_t(0)),
+                    "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint32_t(42), uint32_t(17)),
+                               uint32_t(714)),
+                     "42 * 17 is 714 without wraparound");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint32_t(2147483648), uint32_t(1)),
+                               uint32_t(2147483648)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint32_t(2), uint32_t(2147483648)),
+                               uint32_t(0)),
+                     "2 times high bit overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint32_t(8192), uint32_t(262144)),
+                               uint32_t(2147483648)),
+                     "multiply that populates the high bit produces that value");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint32_t(2147483647),
+                                                uint32_t(2147483647)),
+                               uint32_t(1)),
+                     "multiplying signed maxvals overflows all the way to 1");
+
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int32_t(0), int32_t(-2147483647 - 1)),
+                               int32_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int32_t(-2147483647 - 1), int32_t(1)),
+                               int32_t(-2147483647 - 1)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int32_t(2), int32_t(-2147483647 - 1)),
+                               int32_t(0)),
+                     "2 times min overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int32_t(-7), int32_t(-9)),
+                               int32_t(63)),
+                     "-7 * -9 is 63, no wraparound needed");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int32_t(8192), int32_t(262144)),
+                               int32_t(-2147483647 - 1)),
+                     "multiply that populates the sign bit produces minval");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int32_t(2147483647), int32_t(2147483647)),
+                               int32_t(1)),
+                     "multiplying maxvals overflows all the way to 1");
+}
+
+static void
+TestWrappingMultiply64()
+{
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint64_t(0),
+                                                uint64_t(9223372036854775808ULL)),
+                               uint64_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint64_t(9223372036854775808ULL),
+                                                uint64_t(1)),
+                               uint64_t(9223372036854775808ULL)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint64_t(2),
+                                                uint64_t(9223372036854775808ULL)),
+                               uint64_t(0)),
+                     "2 times high bit overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint64_t(131072),
+                                                uint64_t(70368744177664)),
+                               uint64_t(9223372036854775808ULL)),
+                     "multiply that populates the high bit produces that value");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(uint64_t(9223372036854775807),
+                                                uint64_t(9223372036854775807)),
+                               uint64_t(1)),
+                     "multiplying signed maxvals overflows all the way to 1");
+
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int64_t(0), int64_t(-9223372036854775807 - 1)),
+                               int64_t(0)),
+                     "zero times anything is zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int64_t(-9223372036854775807 - 1),
+                                                int64_t(1)),
+                               int64_t(-9223372036854775807 - 1)),
+                     "1 times anything is anything");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int64_t(2),
+                                                int64_t(-9223372036854775807 - 1)),
+                               int64_t(0)),
+                     "2 times min overflows, produces zero");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int64_t(131072),
+                                                int64_t(70368744177664)),
+                               int64_t(-9223372036854775807 - 1)),
+                     "multiply that populates the sign bit produces minval");
+  MOZ_RELEASE_ASSERT(TestEqual(WrappingMultiply(int64_t(9223372036854775807),
+                                                int64_t(9223372036854775807)),
+                               int64_t(1)),
+                     "multiplying maxvals overflows all the way to 1");
+}
+
 int
 main()
 {
+  TestWrappingMultiply8();
+  TestWrappingMultiply16();
+  TestWrappingMultiply32();
+  TestWrappingMultiply64();
   return 0;
 }