Bug 1233111 - Implement saturating arithmetic for SIMD. r?bbouvier draft
authorJakob Stoklund Olesen <jolesen@mozilla.com>
Wed, 23 Dec 2015 09:52:39 -0800
changeset 317288 273ca6f5e38d760d33c34e65877cb371c2913de4
parent 317287 3dfaaa091673a5cf7a03bcda7a1e2a1c6326cdc8
child 512282 6da2a3e5aa34ebbfbf9c4a7b53fd95e8b3f0650c
push id8682
push userjolesen@mozilla.com
push dateWed, 23 Dec 2015 17:52:08 +0000
reviewersbbouvier
bugs1233111
milestone46.0a1
Bug 1233111 - Implement saturating arithmetic for SIMD. r?bbouvier The functions addSaturate() and subSaturate() are defined on the 8x16 and 16x8 integer SIMD types only. Theee are no 32x4 variants defined in SIMD.js because current hardware doesn't support it directly.
js/src/builtin/SIMD.cpp
js/src/builtin/SIMD.h
js/src/tests/ecma_7/SIMD/binary-operations.js
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -694,16 +694,47 @@ struct ShiftRightArithmetic {
 };
 template<typename T>
 struct ShiftRightLogical {
     static T apply(T v, int32_t bits) {
         return uint32_t(bits) >= sizeof(T) * 8 ? 0 : uint32_t(v) >> bits;
     }
 };
 
+// Saturating arithmetic is only defined on types smaller than int.
+// Clamp `x` into the range supported by the integral type T.
+template<typename T>
+static T
+Saturate(int x)
+{
+    static_assert(mozilla::IsIntegral<T>::value, "Only integer saturation supported");
+    static_assert(sizeof(T) < sizeof(int), "Saturating int-sized arithmetic is not safe");
+    const T lower = mozilla::MinValue<T>::value;
+    const T upper = mozilla::MaxValue<T>::value;
+    if (x > int(upper))
+        return upper;
+    if (x < int(lower))
+        return lower;
+    return T(x);
+}
+
+// Since signed integer overflow is undefined behavior in C++, it would be
+// wildly irresponsible to attempt something as dangerous as adding two numbers
+// coming from user code. However, in this case we know that T is smaller than
+// int, so there is no way these operations can cause overflow. The
+// static_assert in Saturate() enforces this for us.
+template<typename T>
+struct AddSaturate {
+    static T apply(T l, T r) { return Saturate<T>(l + r); }
+};
+template<typename T>
+struct SubSaturate {
+    static T apply(T l, T r) { return Saturate<T>(l - r); }
+};
+
 } // namespace js
 
 template<typename Out>
 static bool
 StoreResult(JSContext* cx, CallArgs& args, typename Out::Elem* result)
 {
     RootedObject obj(cx, CreateSimd<Out>(cx, result));
     if (!obj)
--- a/js/src/builtin/SIMD.h
+++ b/js/src/builtin/SIMD.h
@@ -229,28 +229,30 @@
   V(fromUint16x8Bits,  (FuncConvertBits<Uint16x8,  Int8x16>), 1)                      \
   V(fromUint32x4Bits,  (FuncConvertBits<Uint32x4,  Int8x16>), 1)                      \
   V(neg, (UnaryFunc<Int8x16, Neg, Int8x16>), 1)                                       \
   V(not, (UnaryFunc<Int8x16, Not, Int8x16>), 1)                                       \
   V(splat, (FuncSplat<Int8x16>), 1)
 
 #define INT8X16_BINARY_FUNCTION_LIST(V)                                               \
   V(add, (BinaryFunc<Int8x16, Add, Int8x16>), 2)                                      \
+  V(addSaturate, (BinaryFunc<Int8x16, AddSaturate, Int8x16>), 2)                      \
   V(and, (BinaryFunc<Int8x16, And, Int8x16>), 2)                                      \
   V(equal, (CompareFunc<Int8x16, Equal, Bool8x16>), 2)                                \
   V(extractLane, (ExtractLane<Int8x16>), 2)                                           \
   V(greaterThan, (CompareFunc<Int8x16, GreaterThan, Bool8x16>), 2)                    \
   V(greaterThanOrEqual, (CompareFunc<Int8x16, GreaterThanOrEqual, Bool8x16>), 2)      \
   V(lessThan, (CompareFunc<Int8x16, LessThan, Bool8x16>), 2)                          \
   V(lessThanOrEqual, (CompareFunc<Int8x16, LessThanOrEqual, Bool8x16>), 2)            \
   V(load, (Load<Int8x16, 16>), 2)                                                     \
   V(mul, (BinaryFunc<Int8x16, Mul, Int8x16>), 2)                                      \
   V(notEqual, (CompareFunc<Int8x16, NotEqual, Bool8x16>), 2)                          \
   V(or, (BinaryFunc<Int8x16, Or, Int8x16>), 2)                                        \
   V(sub, (BinaryFunc<Int8x16, Sub, Int8x16>), 2)                                      \
+  V(subSaturate, (BinaryFunc<Int8x16, SubSaturate, Int8x16>), 2)                      \
   V(shiftLeftByScalar, (BinaryScalar<Int8x16, ShiftLeft>), 2)                         \
   V(shiftRightByScalar, (BinaryScalar<Int8x16, ShiftRightArithmetic>), 2)             \
   V(shiftRightArithmeticByScalar, (BinaryScalar<Int8x16, ShiftRightArithmetic>), 2)   \
   V(shiftRightLogicalByScalar, (BinaryScalar<Int8x16, ShiftRightLogical>), 2)         \
   V(xor, (BinaryFunc<Int8x16, Xor, Int8x16>), 2)
 
 #define INT8X16_TERNARY_FUNCTION_LIST(V)                                              \
   V(replaceLane, (ReplaceLane<Int8x16>), 3)                                           \
@@ -278,28 +280,30 @@
   V(fromUint16x8Bits,  (FuncConvertBits<Uint16x8,  Uint8x16>), 1)                     \
   V(fromUint32x4Bits,  (FuncConvertBits<Uint32x4,  Uint8x16>), 1)                     \
   V(neg, (UnaryFunc<Uint8x16, Neg, Uint8x16>), 1)                                     \
   V(not, (UnaryFunc<Uint8x16, Not, Uint8x16>), 1)                                     \
   V(splat, (FuncSplat<Uint8x16>), 1)
 
 #define UINT8X16_BINARY_FUNCTION_LIST(V)                                              \
   V(add, (BinaryFunc<Uint8x16, Add, Uint8x16>), 2)                                    \
+  V(addSaturate, (BinaryFunc<Uint8x16, AddSaturate, Uint8x16>), 2)                    \
   V(and, (BinaryFunc<Uint8x16, And, Uint8x16>), 2)                                    \
   V(equal, (CompareFunc<Uint8x16, Equal, Bool8x16>), 2)                               \
   V(extractLane, (ExtractLane<Uint8x16>), 2)                                          \
   V(greaterThan, (CompareFunc<Uint8x16, GreaterThan, Bool8x16>), 2)                   \
   V(greaterThanOrEqual, (CompareFunc<Uint8x16, GreaterThanOrEqual, Bool8x16>), 2)     \
   V(lessThan, (CompareFunc<Uint8x16, LessThan, Bool8x16>), 2)                         \
   V(lessThanOrEqual, (CompareFunc<Uint8x16, LessThanOrEqual, Bool8x16>), 2)           \
   V(load, (Load<Uint8x16, 16>), 2)                                                    \
   V(mul, (BinaryFunc<Uint8x16, Mul, Uint8x16>), 2)                                    \
   V(notEqual, (CompareFunc<Uint8x16, NotEqual, Bool8x16>), 2)                         \
   V(or, (BinaryFunc<Uint8x16, Or, Uint8x16>), 2)                                      \
   V(sub, (BinaryFunc<Uint8x16, Sub, Uint8x16>), 2)                                    \
+  V(subSaturate, (BinaryFunc<Uint8x16, SubSaturate, Uint8x16>), 2)                    \
   V(shiftLeftByScalar, (BinaryScalar<Uint8x16, ShiftLeft>), 2)                        \
   V(shiftRightByScalar, (BinaryScalar<Uint8x16, ShiftRightLogical>), 2)               \
   V(shiftRightArithmeticByScalar, (BinaryScalar<Uint8x16, ShiftRightArithmetic>), 2)  \
   V(shiftRightLogicalByScalar, (BinaryScalar<Uint8x16, ShiftRightLogical>), 2)        \
   V(xor, (BinaryFunc<Uint8x16, Xor, Uint8x16>), 2)
 
 #define UINT8X16_TERNARY_FUNCTION_LIST(V)                                             \
   V(replaceLane, (ReplaceLane<Uint8x16>), 3)                                          \
@@ -327,28 +331,30 @@
   V(fromUint16x8Bits,  (FuncConvertBits<Uint16x8,  Int16x8>), 1)                      \
   V(fromUint32x4Bits,  (FuncConvertBits<Uint32x4,  Int16x8>), 1)                      \
   V(neg, (UnaryFunc<Int16x8, Neg, Int16x8>), 1)                                       \
   V(not, (UnaryFunc<Int16x8, Not, Int16x8>), 1)                                       \
   V(splat, (FuncSplat<Int16x8>), 1)
 
 #define INT16X8_BINARY_FUNCTION_LIST(V)                                               \
   V(add, (BinaryFunc<Int16x8, Add, Int16x8>), 2)                                      \
+  V(addSaturate, (BinaryFunc<Int16x8, AddSaturate, Int16x8>), 2)                      \
   V(and, (BinaryFunc<Int16x8, And, Int16x8>), 2)                                      \
   V(equal, (CompareFunc<Int16x8, Equal, Bool16x8>), 2)                                \
   V(extractLane, (ExtractLane<Int16x8>), 2)                                           \
   V(greaterThan, (CompareFunc<Int16x8, GreaterThan, Bool16x8>), 2)                    \
   V(greaterThanOrEqual, (CompareFunc<Int16x8, GreaterThanOrEqual, Bool16x8>), 2)      \
   V(lessThan, (CompareFunc<Int16x8, LessThan, Bool16x8>), 2)                          \
   V(lessThanOrEqual, (CompareFunc<Int16x8, LessThanOrEqual, Bool16x8>), 2)            \
   V(load, (Load<Int16x8, 8>), 2)                                                      \
   V(mul, (BinaryFunc<Int16x8, Mul, Int16x8>), 2)                                      \
   V(notEqual, (CompareFunc<Int16x8, NotEqual, Bool16x8>), 2)                          \
   V(or, (BinaryFunc<Int16x8, Or, Int16x8>), 2)                                        \
   V(sub, (BinaryFunc<Int16x8, Sub, Int16x8>), 2)                                      \
+  V(subSaturate, (BinaryFunc<Int16x8, SubSaturate, Int16x8>), 2)                      \
   V(shiftLeftByScalar, (BinaryScalar<Int16x8, ShiftLeft>), 2)                         \
   V(shiftRightByScalar, (BinaryScalar<Int16x8, ShiftRightArithmetic>), 2)             \
   V(shiftRightArithmeticByScalar, (BinaryScalar<Int16x8, ShiftRightArithmetic>), 2)   \
   V(shiftRightLogicalByScalar, (BinaryScalar<Int16x8, ShiftRightLogical>), 2)         \
   V(xor, (BinaryFunc<Int16x8, Xor, Int16x8>), 2)
 
 #define INT16X8_TERNARY_FUNCTION_LIST(V)                                              \
   V(replaceLane, (ReplaceLane<Int16x8>), 3)                                           \
@@ -376,28 +382,30 @@
   V(fromUint8x16Bits,  (FuncConvertBits<Uint8x16,  Uint16x8>), 1)                     \
   V(fromUint32x4Bits,  (FuncConvertBits<Uint32x4,  Uint16x8>), 1)                     \
   V(neg, (UnaryFunc<Uint16x8, Neg, Uint16x8>), 1)                                     \
   V(not, (UnaryFunc<Uint16x8, Not, Uint16x8>), 1)                                     \
   V(splat, (FuncSplat<Uint16x8>), 1)
 
 #define UINT16X8_BINARY_FUNCTION_LIST(V)                                              \
   V(add, (BinaryFunc<Uint16x8, Add, Uint16x8>), 2)                                    \
+  V(addSaturate, (BinaryFunc<Uint16x8, AddSaturate, Uint16x8>), 2)                    \
   V(and, (BinaryFunc<Uint16x8, And, Uint16x8>), 2)                                    \
   V(equal, (CompareFunc<Uint16x8, Equal, Bool16x8>), 2)                               \
   V(extractLane, (ExtractLane<Uint16x8>), 2)                                          \
   V(greaterThan, (CompareFunc<Uint16x8, GreaterThan, Bool16x8>), 2)                   \
   V(greaterThanOrEqual, (CompareFunc<Uint16x8, GreaterThanOrEqual, Bool16x8>), 2)     \
   V(lessThan, (CompareFunc<Uint16x8, LessThan, Bool16x8>), 2)                         \
   V(lessThanOrEqual, (CompareFunc<Uint16x8, LessThanOrEqual, Bool16x8>), 2)           \
   V(load, (Load<Uint16x8, 8>), 2)                                                     \
   V(mul, (BinaryFunc<Uint16x8, Mul, Uint16x8>), 2)                                    \
   V(notEqual, (CompareFunc<Uint16x8, NotEqual, Bool16x8>), 2)                         \
   V(or, (BinaryFunc<Uint16x8, Or, Uint16x8>), 2)                                      \
   V(sub, (BinaryFunc<Uint16x8, Sub, Uint16x8>), 2)                                    \
+  V(subSaturate, (BinaryFunc<Uint16x8, SubSaturate, Uint16x8>), 2)                    \
   V(shiftLeftByScalar, (BinaryScalar<Uint16x8, ShiftLeft>), 2)                        \
   V(shiftRightByScalar, (BinaryScalar<Uint16x8, ShiftRightLogical>), 2)               \
   V(shiftRightArithmeticByScalar, (BinaryScalar<Uint16x8, ShiftRightArithmetic>), 2)  \
   V(shiftRightLogicalByScalar, (BinaryScalar<Uint16x8, ShiftRightLogical>), 2)        \
   V(xor, (BinaryFunc<Uint16x8, Xor, Uint16x8>), 2)
 
 #define UINT16X8_TERNARY_FUNCTION_LIST(V)                                             \
   V(replaceLane, (ReplaceLane<Uint16x8>), 3)                                          \
--- a/js/src/tests/ecma_7/SIMD/binary-operations.js
+++ b/js/src/tests/ecma_7/SIMD/binary-operations.js
@@ -71,16 +71,27 @@ function testFloat32x4sub() {
     [[NaN, -0, -Infinity, -Infinity], [NaN, -0, Infinity, -Infinity]]
   ];
 
   for (var [v,w] of vals) {
     testBinaryFunc(Float32x4(...v), Float32x4(...w), Float32x4.sub, subf);
   }
 }
 
+// Helper for saturating arithmetic.
+// See SIMD.js, 5.1.25 Saturate(descriptor, x)
+function saturate(lower, upper, x) {
+    x = x | 0;
+    if (x > upper)
+        return upper;
+    if (x < lower)
+        return lower;
+    return x;
+}
+
 var i8x16vals = [
   [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
    [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160]],
   [[INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, INT8_MAX, INT8_MIN, -2, -3, -4, -5, -6, -7, -8, -9],
    [1, 1, -1, -1, INT8_MAX, INT8_MAX, INT8_MIN, INT8_MIN, 8, 9, 10, 11, 12, 13, 14, 15]]
 ];
 
 // Int8x16.
@@ -139,16 +150,36 @@ function testInt8x16xor() {
     return (a ^ b) << 24 >> 24;
   }
 
   for (var [v,w] of i8x16vals) {
     testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.xor, xori);
   }
 }
 
+function testInt8x16addSaturate() {
+  function satadd(a, b) {
+    return saturate(INT8_MIN, INT8_MAX, a + b);
+  }
+
+  for (var [v,w] of i8x16vals) {
+    testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.addSaturate, satadd);
+  }
+}
+
+function testInt8x16subSaturate() {
+  function satsub(a, b) {
+    return saturate(INT8_MIN, INT8_MAX, a - b);
+  }
+
+  for (var [v,w] of i8x16vals) {
+    testBinaryFunc(Int8x16(...v), Int8x16(...w), Int8x16.subSaturate, satsub);
+  }
+}
+
 // Uint8x16.
 function testUint8x16add() {
   function addi(a, b) {
     return (a + b) << 24 >>> 24;
   }
 
   for (var [v,w] of i8x16vals) {
     testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.add, addi);
@@ -200,16 +231,36 @@ function testUint8x16xor() {
     return (a ^ b) << 24 >>> 24;
   }
 
   for (var [v,w] of i8x16vals) {
     testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.xor, xori);
   }
 }
 
+function testUint8x16addSaturate() {
+  function satadd(a, b) {
+    return saturate(0, UINT8_MAX, a + b);
+  }
+
+  for (var [v,w] of i8x16vals) {
+    testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.addSaturate, satadd);
+  }
+}
+
+function testUint8x16subSaturate() {
+  function satsub(a, b) {
+    return saturate(0, UINT8_MAX, a - b);
+  }
+
+  for (var [v,w] of i8x16vals) {
+    testBinaryFunc(Uint8x16(...v), Uint8x16(...w), Uint8x16.subSaturate, satsub);
+  }
+}
+
 var i16x8vals = [
   [[1, 2, 3, 4, 5, 6, 7, 8],
    [10, 20, 30, 40, 50, 60, 70, 80]],
   [[INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN, INT16_MAX, INT16_MIN],
    [1, 1, -1, -1, INT16_MAX, INT16_MAX, INT16_MIN, INT16_MIN]]
 ];
 
 // Int16x8.
@@ -268,16 +319,36 @@ function testInt16x8xor() {
     return (a ^ b) << 16 >> 16;
   }
 
   for (var [v,w] of i16x8vals) {
     testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.xor, xori);
   }
 }
 
+function testInt16x8addSaturate() {
+  function satadd(a, b) {
+    return saturate(INT16_MIN, INT16_MAX, a + b);
+  }
+
+  for (var [v,w] of i16x8vals) {
+    testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.addSaturate, satadd);
+  }
+}
+
+function testInt16x8subSaturate() {
+  function satsub(a, b) {
+    return saturate(INT16_MIN, INT16_MAX, a - b);
+  }
+
+  for (var [v,w] of i16x8vals) {
+    testBinaryFunc(Int16x8(...v), Int16x8(...w), Int16x8.subSaturate, satsub);
+  }
+}
+
 // Uint16x8.
 function testUint16x8add() {
   function addi(a, b) {
     return (a + b) << 16 >>> 16;
   }
 
   for (var [v,w] of i16x8vals) {
     testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.add, addi);
@@ -329,16 +400,36 @@ function testUint16x8xor() {
     return (a ^ b) << 16 >>> 16;
   }
 
   for (var [v,w] of i16x8vals) {
     testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.xor, xori);
   }
 }
 
+function testUint16x8addSaturate() {
+  function satadd(a, b) {
+    return saturate(0, UINT16_MAX, a + b);
+  }
+
+  for (var [v,w] of i16x8vals) {
+    testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.addSaturate, satadd);
+  }
+}
+
+function testUint16x8subSaturate() {
+  function satsub(a, b) {
+    return saturate(0, UINT16_MAX, a - b);
+  }
+
+  for (var [v,w] of i16x8vals) {
+    testBinaryFunc(Uint16x8(...v), Uint16x8(...w), Uint16x8.subSaturate, satsub);
+  }
+}
+
 var i32x4vals = [
   [[1, 2, 3, 4], [10, 20, 30, 40]],
   [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [1, -1, 0, 0]],
   [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [INT32_MIN, INT32_MAX, INT32_MAX, INT32_MIN]],
   [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [-1, -1, INT32_MIN, INT32_MIN]],
   [[INT32_MAX, INT32_MIN, INT32_MAX, INT32_MIN], [-1, 1, INT32_MAX, INT32_MIN]],
   [[UINT32_MAX, 0, UINT32_MAX, 0], [1, -1, 0, 0]],
   [[UINT32_MAX, 0, UINT32_MAX, 0], [-1, -1, INT32_MIN, INT32_MIN]],
@@ -621,37 +712,45 @@ function test() {
   testFloat32x4sub();
 
   testInt8x16add();
   testInt8x16and();
   testInt8x16mul();
   testInt8x16or();
   testInt8x16sub();
   testInt8x16xor();
+  testInt8x16addSaturate();
+  testInt8x16subSaturate();
 
   testUint8x16add();
   testUint8x16and();
   testUint8x16mul();
   testUint8x16or();
   testUint8x16sub();
   testUint8x16xor();
+  testUint8x16addSaturate();
+  testUint8x16subSaturate();
 
   testInt16x8add();
   testInt16x8and();
   testInt16x8mul();
   testInt16x8or();
   testInt16x8sub();
   testInt16x8xor();
+  testInt16x8addSaturate();
+  testInt16x8subSaturate();
 
   testUint16x8add();
   testUint16x8and();
   testUint16x8mul();
   testUint16x8or();
   testUint16x8sub();
   testUint16x8xor();
+  testUint16x8addSaturate();
+  testUint16x8subSaturate();
 
   testInt32x4add();
   testInt32x4and();
   testInt32x4mul();
   testInt32x4or();
   testInt32x4sub();
   testInt32x4xor();