From 0eefeabe2562cef57c3c0439cba3b334c011ddf9 Mon Sep 17 00:00:00 2001 From: Jeff Walden Date: Tue, 6 Mar 2018 19:22:20 -0800 Subject: [PATCH] Bug 1445024 - Implement mozilla::WrappingSubtract. r=froydnj --HG-- extra : rebase_source : 026268df1cb1cfc56873e61834ea90257645c508 --- mfbt/WrappingOperations.h | 50 +++++++++ mfbt/tests/TestWrappingOperations.cpp | 156 ++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/mfbt/WrappingOperations.h b/mfbt/WrappingOperations.h index 3f44ab19dc2a..dc11af882b40 100644 --- a/mfbt/WrappingOperations.h +++ b/mfbt/WrappingOperations.h @@ -173,6 +173,56 @@ WrappingAdd(T aX, T aY) namespace detail { +template +struct WrappingSubtractHelper +{ +private: + using UnsignedT = typename MakeUnsigned::Type; + +public: + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW + static T compute(T aX, T aY) + { + return ToResult(static_cast(aX) - static_cast(aY)); + } +}; + +} // namespace detail + +/** + * Subtract two integers of the same type and return the result converted to + * that type using wraparound semantics, without triggering overflow sanitizers. + * + * For N-bit unsigned integer types, this is equivalent to subtracting the two + * numbers, then taking the result mod 2**N: + * + * WrappingSubtract(uint32_t(42), uint32_t(17)) is 29 (29 mod 2**32); + * WrappingSubtract(uint8_t(5), uint8_t(20)) is 241 (-15 mod 2**8). + * + * Unsigned WrappingSubtract acts exactly like C++ unsigned subtraction. + * + * For N-bit signed integer types, this is equivalent to subtracting the two + * numbers wrapped to unsigned, then wrapping the difference mod 2**N to the + * signed range: + * + * WrappingSubtract(int16_t(32767), int16_t(-5)) is -32764 ((32772 mod 2**16) - 2**16); + * WrappingSubtract(int8_t(-128), int8_t(127)) is 1 (-255 mod 2**8); + * WrappingSubtract(int32_t(-17), int32_t(-42)) is 25 (25 mod 2**32). + * + * There's no equivalent to this operation in C++, as C++ signed subtraction + * that overflows has undefined behavior. But it's how such subtraction *tends* + * to behave with most compilers, unless an optimization or similar -- quite + * permissibly -- triggers different behavior. + */ +template +inline T +WrappingSubtract(T aX, T aY) +{ + return detail::WrappingSubtractHelper::compute(aX, aY); +} + +namespace detail { + template struct WrappingMultiplyHelper { diff --git a/mfbt/tests/TestWrappingOperations.cpp b/mfbt/tests/TestWrappingOperations.cpp index ca76f98d6ea3..211d522fb411 100644 --- a/mfbt/tests/TestWrappingOperations.cpp +++ b/mfbt/tests/TestWrappingOperations.cpp @@ -12,6 +12,7 @@ using mozilla::WrappingAdd; using mozilla::WrappingMultiply; using mozilla::WrapToSigned; +using mozilla::WrappingSubtract; // 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 @@ -223,6 +224,160 @@ TestWrappingAdd() TestWrappingAdd64(); } +static void +TestWrappingSubtract8() +{ + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint8_t(0), uint8_t(128)), + uint8_t(128)), + "zero minus half is half"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint8_t(17), uint8_t(42)), + uint8_t(231)), + "17 - 42 == -25 added to 256 is 231"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint8_t(0), uint8_t(1)), + uint8_t(255)), + "zero underflows to all bits"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint8_t(128), uint8_t(127)), + uint8_t(1)), + "128 - 127 == 1"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint8_t(128), uint8_t(193)), + uint8_t(191)), + "128 - 193 is -65 so -65 + 256 == 191"); + + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int8_t(0), int8_t(-128)), + int8_t(-128)), + "zero minus high bit wraps to high bit"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int8_t(-126), int8_t(4)), + int8_t(126)), + "underflow to positive"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int8_t(5), int8_t(-123)), + int8_t(-128)), + "overflow to negative"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int8_t(-85), int8_t(-73)), + int8_t(-12)), + "negative minus smaller negative"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int8_t(-128), int8_t(127)), + int8_t(1)), + "underflow to 1"); +} + +static void +TestWrappingSubtract16() +{ + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint16_t(0), uint16_t(32768)), + uint16_t(32768)), + "zero minus half is half"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint16_t(24389), uint16_t(2682)), + uint16_t(21707)), + "24389 - 2682 == 21707"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint16_t(0), uint16_t(1)), + uint16_t(65535)), + "zero underflows to all bits"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint16_t(32768), uint16_t(32767)), + uint16_t(1)), + "high bit minus all lower bits is one"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint16_t(32768), uint16_t(47582)), + uint16_t(50722)), + "32768 - 47582 + 65536 is 50722"); + + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int16_t(0), int16_t(-32768)), + int16_t(-32768)), + "zero minus high bit wraps to high bit"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int16_t(-32766), int16_t(4)), + int16_t(32766)), + "underflow to positive"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int16_t(5), int16_t(-28933)), + int16_t(28938)), + "5 - -28933 is 28938"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int16_t(-23892), int16_t(-12893)), + int16_t(-10999)), + "negative minus smaller negative"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int16_t(-32768), int16_t(32767)), + int16_t(1)), + "underflow to 1"); +} + +static void +TestWrappingSubtract32() +{ + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint32_t(0), uint32_t(2147483648)), + uint32_t(2147483648)), + "zero minus half is half"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint32_t(1398742328), uint32_t(714192829)), + uint32_t(684549499)), + "1398742328 - 714192829 == 684549499"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint32_t(0), uint32_t(1)), + uint32_t(4294967295)), + "zero underflows to all bits"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint32_t(2147483648), uint32_t(2147483647)), + uint32_t(1)), + "high bit minus all lower bits is one"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint32_t(2147483648), uint32_t(3146492712)), + uint32_t(3295958232)), + "2147483648 - 3146492712 + 4294967296 is 3295958232"); + + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int32_t(0), int32_t(-2147483647 - 1)), + int32_t(-2147483647 - 1)), + "zero minus high bit wraps to high bit"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int32_t(-2147483646), int32_t(4)), + int32_t(2147483646)), + "underflow to positive"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int32_t(257), int32_t(-23947248)), + int32_t(23947505)), + "257 - -23947248 is 23947505"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int32_t(-2147483220), int32_t(-12893)), + int32_t(-2147470327)), + "negative minus smaller negative"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int32_t(-2147483647 - 1), int32_t(2147483647)), + int32_t(1)), + "underflow to 1"); +} + +static void +TestWrappingSubtract64() +{ + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint64_t(0), uint64_t(9223372036854775808ULL)), + uint64_t(9223372036854775808ULL)), + "zero minus half is half"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint64_t(70368744177664), uint64_t(3740873592)), + uint64_t(70365003304072)), + "70368744177664 - 3740873592 == 70365003304072"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint64_t(0), uint64_t(1)), + uint64_t(18446744073709551615ULL)), + "zero underflows to all bits"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint64_t(9223372036854775808ULL), + uint64_t(9223372036854775807ULL)), + uint64_t(1)), + "high bit minus all lower bits is one"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(uint64_t(14552598638644786479ULL), uint64_t(3894174382537247221ULL)), + uint64_t(10658424256107539258ULL)), + "14552598638644786479 - 39763621533397112216 is 10658424256107539258L"); + + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int64_t(0), int64_t(-9223372036854775807LL - 1)), + int64_t(-9223372036854775807LL - 1)), + "zero minus high bit wraps to high bit"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int64_t(-9223372036854775802LL), int64_t(8)), + int64_t(9223372036854775806LL)), + "overflow to negative"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int64_t(37482739294298742LL), int64_t(-437843573929483498LL)), + int64_t(475326313223782240)), + "37482739294298742 - -437843573929483498 is 475326313223782240"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int64_t(-9127837934058953374LL), int64_t(-4173572032144775807LL)), + int64_t(-4954265901914177567LL)), + "negative minus smaller negative"); + MOZ_RELEASE_ASSERT(TestEqual(WrappingSubtract(int64_t(-9223372036854775807LL - 1), int64_t(9223372036854775807LL)), + int64_t(1)), + "underflow to 1"); +} + +static void +TestWrappingSubtract() +{ + TestWrappingSubtract8(); + TestWrappingSubtract16(); + TestWrappingSubtract32(); + TestWrappingSubtract64(); +} + static void TestWrappingMultiply8() { @@ -405,6 +560,7 @@ int main() { TestWrappingAdd(); + TestWrappingSubtract(); TestWrappingMultiply(); return 0; }