зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1112627: Throw on lossy SIMD conversions; r=Waldo
--HG-- extra : rebase_source : e4e44b77f056d72c3d46723d02c92afb3669c97d
This commit is contained in:
Родитель
8d6bd07f3f
Коммит
0c84901c44
|
@ -14,6 +14,7 @@
|
|||
#include "builtin/SIMD.h"
|
||||
|
||||
#include "mozilla/IntegerTypeTraits.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
|
@ -822,6 +823,62 @@ CompareFunc(JSContext* cx, unsigned argc, Value* vp)
|
|||
return StoreResult<Int32x4>(cx, args, result);
|
||||
}
|
||||
|
||||
// This struct defines whether we should throw during a conversion attempt,
|
||||
// when trying to convert a value of type from From to the type To. This
|
||||
// happens whenever a C++ conversion would have undefined behavior (and perhaps
|
||||
// be platform-dependent).
|
||||
template<typename From, typename To>
|
||||
struct ThrowOnConvert;
|
||||
|
||||
struct NeverThrow
|
||||
{
|
||||
static bool value(int32_t v) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// While int32 to float conversions can be lossy, these conversions have
|
||||
// defined behavior in C++, so we don't need to care about them here. In practice,
|
||||
// this means round to nearest, tie with even (zero bit in significand).
|
||||
template<>
|
||||
struct ThrowOnConvert<int32_t, float> : public NeverThrow {};
|
||||
|
||||
// All int32 can be safely converted to doubles.
|
||||
template<>
|
||||
struct ThrowOnConvert<int32_t, double> : public NeverThrow {};
|
||||
|
||||
// All floats can be safely converted to doubles.
|
||||
template<>
|
||||
struct ThrowOnConvert<float, double> : public NeverThrow {};
|
||||
|
||||
// Double to float conversion for inputs which aren't in the float range are
|
||||
// undefined behavior in C++, but they're defined in IEEE754.
|
||||
template<>
|
||||
struct ThrowOnConvert<double, float> : public NeverThrow {};
|
||||
|
||||
// Float to integer conversions have undefined behavior if the float value
|
||||
// is out of the representable integer range (on x86, will yield the undefined
|
||||
// value pattern, namely 0x80000000; on arm, will clamp the input value), so
|
||||
// check this here.
|
||||
template<typename From, typename IntegerType>
|
||||
struct ThrowIfNotInRange
|
||||
{
|
||||
static_assert(mozilla::IsIntegral<IntegerType>::value, "bad destination type");
|
||||
|
||||
static bool value(From v) {
|
||||
double d(v);
|
||||
return mozilla::IsNaN(d) ||
|
||||
d < double(mozilla::MinValue<IntegerType>::value) ||
|
||||
d > double(mozilla::MaxValue<IntegerType>::value);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct ThrowOnConvert<double, int32_t> : public ThrowIfNotInRange<double, int32_t> {};
|
||||
|
||||
template<>
|
||||
struct ThrowOnConvert<float, int32_t> : public ThrowIfNotInRange<float, int32_t> {};
|
||||
|
||||
template<typename V, typename Vret>
|
||||
static bool
|
||||
FuncConvert(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
@ -834,9 +891,20 @@ FuncConvert(JSContext* cx, unsigned argc, Value* vp)
|
|||
return ErrorBadArgs(cx);
|
||||
|
||||
Elem* val = TypedObjectMemory<Elem*>(args[0]);
|
||||
|
||||
RetElem result[Vret::lanes];
|
||||
for (unsigned i = 0; i < Vret::lanes; i++)
|
||||
result[i] = i < V::lanes ? ConvertScalar<RetElem>(val[i]) : 0;
|
||||
for (unsigned i = 0; i < Min(V::lanes, Vret::lanes); i++) {
|
||||
if (ThrowOnConvert<Elem, RetElem>::value(val[i])) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_SIMD_FAILED_CONVERSION);
|
||||
return false;
|
||||
}
|
||||
result[i] = ConvertScalar<RetElem>(val[i]);
|
||||
}
|
||||
|
||||
// Fill remaining lanes with 0
|
||||
for (unsigned i = V::lanes; i < Vret::lanes; i++)
|
||||
result[i] = 0;
|
||||
|
||||
return StoreResult<Vret>(cx, args, result);
|
||||
}
|
||||
|
|
|
@ -440,13 +440,14 @@ MSG_DEF(JSMSG_UNTERM_CLASS, 0, JSEXN_SYNTAXERR, "unterminated charact
|
|||
MSG_DEF(JSMSG_DEFAULT_LOCALE_ERROR, 0, JSEXN_ERR, "internal error getting the default locale")
|
||||
MSG_DEF(JSMSG_NO_SUCH_SELF_HOSTED_PROP,1, JSEXN_ERR, "No such property on self-hosted object: {0}")
|
||||
|
||||
// Typed object
|
||||
// Typed object / SIMD
|
||||
MSG_DEF(JSMSG_INVALID_PROTOTYPE, 0, JSEXN_TYPEERR, "prototype field is not an object")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_BAD_ARGS, 0, JSEXN_TYPEERR, "invalid arguments")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED, 0, JSEXN_TYPEERR, "handle unattached")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS, 0, JSEXN_RANGEERR, "invalid field descriptor")
|
||||
MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG, 0, JSEXN_ERR, "Type is too large to allocate")
|
||||
MSG_DEF(JSMSG_SIMD_FAILED_CONVERSION, 0, JSEXN_RANGEERR, "SIMD conversion loses precision")
|
||||
|
||||
// Typed array
|
||||
MSG_DEF(JSMSG_BAD_INDEX, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
|
||||
|
|
|
@ -19,6 +19,48 @@ function testFloat32x4FromFloat64x2() {
|
|||
for (var v of vals) {
|
||||
assertEqX4(float32x4.fromFloat64x2(float64x2(...v)), expected(v));
|
||||
}
|
||||
|
||||
// Test rounding to nearest, break tie with even
|
||||
var f1 = makeFloat(0, 127, 0);
|
||||
assertEq(f1, 1);
|
||||
|
||||
var f2 = makeFloat(0, 127, 1);
|
||||
var d = makeDouble(0, 1023, 0x0000020000000);
|
||||
assertEq(f2, d);
|
||||
|
||||
var mid = makeDouble(0, 1023, 0x0000010000000);
|
||||
assertEq((1 + d) / 2, mid);
|
||||
|
||||
var nextMid = makeDouble(0, 1023, 0x0000010000001);
|
||||
|
||||
// mid is halfway between f1 and f2 => tie to even, which is f1
|
||||
var v = float64x2(mid, nextMid);
|
||||
assertEqX4(float32x4.fromFloat64x2(v), [f1, f2, 0, 0]);
|
||||
|
||||
var f3 = makeFloat(0, 127, 2);
|
||||
var d = makeDouble(0, 1023, 0x0000040000000);
|
||||
assertEq(f3, d);
|
||||
|
||||
mid = makeDouble(0, 1023, 0x0000030000000);
|
||||
assertEq((f2 + f3) / 2, mid);
|
||||
|
||||
// same here. tie to even, which is f3 here
|
||||
nextMid = makeDouble(0, 1023, 0x0000030000001);
|
||||
var v = float64x2(mid, nextMid);
|
||||
assertEqX4(float32x4.fromFloat64x2(v), [f3, f3, 0, 0]);
|
||||
|
||||
// Test boundaries
|
||||
var biggestFloat = makeFloat(0, 127 + 127, 0x7fffff);
|
||||
assertEq(makeDouble(0, 1023 + 127, 0xfffffe0000000), biggestFloat);
|
||||
|
||||
var lowestFloat = makeFloat(1, 127 + 127, 0x7fffff);
|
||||
assertEq(makeDouble(1, 1023 + 127, 0xfffffe0000000), lowestFloat);
|
||||
|
||||
var v = float64x2(lowestFloat, biggestFloat);
|
||||
assertEqX4(float32x4.fromFloat64x2(v), [lowestFloat, biggestFloat, 0, 0]);
|
||||
|
||||
var v = float64x2(makeDouble(0, 1023 + 127, 0xfffffe0000001), makeDouble(0, 1023 + 127, 0xffffff0000000));
|
||||
assertEqX4(float32x4.fromFloat64x2(v), [biggestFloat, Infinity, 0, 0]);
|
||||
}
|
||||
|
||||
function testFloat32x4FromFloat64x2Bits() {
|
||||
|
@ -45,6 +87,45 @@ function testFloat32x4FromInt32x4() {
|
|||
for (var v of vals) {
|
||||
assertEqX4(float32x4.fromInt32x4(int32x4(...v)), expected(v));
|
||||
}
|
||||
|
||||
// Check that rounding to nearest, even is applied.
|
||||
{
|
||||
var num = makeFloat(0, 150 + 2, 0);
|
||||
var next = makeFloat(0, 150 + 2, 1);
|
||||
assertEq(num + 4, next);
|
||||
|
||||
v = float32x4.fromInt32x4(int32x4(num, num + 1, num + 2, num + 3));
|
||||
assertEqX4(v, [num, num, /* even */ num, next]);
|
||||
}
|
||||
|
||||
{
|
||||
var num = makeFloat(0, 150 + 2, 1);
|
||||
var next = makeFloat(0, 150 + 2, 2);
|
||||
assertEq(num + 4, next);
|
||||
|
||||
v = float32x4.fromInt32x4(int32x4(num, num + 1, num + 2, num + 3));
|
||||
assertEqX4(v, [num, num, /* even */ next, next]);
|
||||
}
|
||||
|
||||
{
|
||||
var last = makeFloat(0, 157, 0x7fffff);
|
||||
|
||||
assertEq(last, Math.fround(last), "float");
|
||||
assertEq(last < Math.pow(2, 31), true, "less than 2**31");
|
||||
assertEq(last | 0, last, "it should be an integer, as exponent >= 150");
|
||||
|
||||
var diff = (Math.pow(2, 31) - 1) - last;
|
||||
v = float32x4.fromInt32x4(int32x4(Math.pow(2, 31) - 1,
|
||||
Math.pow(2, 30) + 1,
|
||||
last + (diff / 2) | 0, // nearest is last
|
||||
last + (diff / 2) + 1 | 0 // nearest is Math.pow(2, 31)
|
||||
));
|
||||
assertEqX4(v, [Math.pow(2, 31),
|
||||
Math.pow(2, 30),
|
||||
last,
|
||||
Math.pow(2, 31)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function testFloat32x4FromInt32x4Bits() {
|
||||
|
@ -113,14 +194,35 @@ function testFloat64x2FromInt32x4Bits() {
|
|||
}
|
||||
|
||||
function testInt32x4FromFloat32x4() {
|
||||
var valsExp = [
|
||||
[[1.1, 2.2, 3.3, 4.6], [1, 2, 3, 4]],
|
||||
[[NaN, -0, Infinity, -Infinity], [0, 0, 0, 0]]
|
||||
];
|
||||
var d = float32x4(1.1, 2.2, 3.3, 4.6);
|
||||
assertEqX4(int32x4.fromFloat32x4(d), [1, 2, 3, 4]);
|
||||
|
||||
for (var [v,w] of valsExp) {
|
||||
assertEqX4(int32x4.fromFloat32x4(float32x4(...v)), w);
|
||||
}
|
||||
var d = float32x4(NaN, 0, 0, 0);
|
||||
assertThrowsInstanceOf(() => SIMD.int32x4.fromFloat32x4(d), RangeError);
|
||||
|
||||
var d = float32x4(Infinity, 0, 0, 0);
|
||||
assertThrowsInstanceOf(() => SIMD.int32x4.fromFloat32x4(d), RangeError);
|
||||
|
||||
var d = float32x4(-Infinity, 0, 0, 0);
|
||||
assertThrowsInstanceOf(() => SIMD.int32x4.fromFloat32x4(d), RangeError);
|
||||
|
||||
// Test high boundaries: float(0, 157, 0x7fffff) < INT32_MAX < float(0, 158, 0)
|
||||
var d = float32x4(makeFloat(0, 127 + 31, 0), 0, 0, 0);
|
||||
assertThrowsInstanceOf(() => SIMD.int32x4.fromFloat32x4(d), RangeError);
|
||||
|
||||
var lastFloat = makeFloat(0, 127 + 30, 0x7FFFFF);
|
||||
var d = float32x4(lastFloat, 0, 0, 0);
|
||||
var e = SIMD.int32x4.fromFloat32x4(d);
|
||||
assertEqX4(e, [lastFloat, 0, 0, 0]);
|
||||
|
||||
// Test low boundaries
|
||||
assertEq(makeFloat(1, 127 + 31, 0), INT32_MIN);
|
||||
var d = float32x4(makeFloat(1, 127 + 31, 0), 0, 0, 0);
|
||||
var e = SIMD.int32x4.fromFloat32x4(d);
|
||||
assertEqX4(e, [INT32_MIN, 0, 0, 0]);
|
||||
|
||||
var d = float32x4(makeFloat(1, 127 + 31, 1), 0, 0, 0);
|
||||
assertThrowsInstanceOf(() => SIMD.int32x4.fromFloat32x4(d), RangeError);
|
||||
}
|
||||
|
||||
function testInt32x4FromFloat32x4Bits() {
|
||||
|
@ -135,16 +237,39 @@ function testInt32x4FromFloat32x4Bits() {
|
|||
}
|
||||
|
||||
function testInt32x4FromFloat64x2() {
|
||||
var valsExp = [
|
||||
[[1, 2.2], [1, 2, 0, 0]],
|
||||
[[NaN, -0], [0, 0, 0, 0]],
|
||||
[[Infinity, -Infinity], [0, 0, 0, 0]],
|
||||
[[Math.pow(2, 31), -Math.pow(2, 31) - 1], [INT32_MIN, INT32_MAX, 0, 0]]
|
||||
];
|
||||
assertEqX4(int32x4.fromFloat64x2(float64x2(1, 2.2)), [1, 2, 0, 0]);
|
||||
|
||||
var g = float64x2(Infinity, 0);
|
||||
assertThrowsInstanceOf(() => int32x4.fromFloat64x2(g), RangeError);
|
||||
|
||||
var g = float64x2(-Infinity, 0);
|
||||
assertThrowsInstanceOf(() => int32x4.fromFloat64x2(g), RangeError);
|
||||
|
||||
var g = float64x2(NaN, 0);
|
||||
assertThrowsInstanceOf(() => int32x4.fromFloat64x2(g), RangeError);
|
||||
|
||||
// Testing high boundaries
|
||||
// double(0, 1023 + 30, 0) < INT32_MAX < double(0, 1023 + 31, 0), so the
|
||||
// lowest exactly representable quantity at this scale is 2**(-52 + 30) ==
|
||||
// 2**-22.
|
||||
assertEq(makeDouble(0, 1023 + 30, 0) + Math.pow(2, -22), makeDouble(0, 1023 + 30, 1));
|
||||
assertEq(makeDouble(0, 1023 + 30, 0) + Math.pow(2, -23), makeDouble(0, 1023 + 30, 0));
|
||||
|
||||
var g = float64x2(INT32_MAX, 0);
|
||||
assertEqX4(int32x4.fromFloat64x2(g), [INT32_MAX, 0, 0, 0]);
|
||||
|
||||
var g = float64x2(INT32_MAX + Math.pow(2, -22), 0);
|
||||
assertThrowsInstanceOf(() => int32x4.fromFloat64x2(g), RangeError);
|
||||
|
||||
// Testing low boundaries
|
||||
assertEq(makeDouble(1, 1023 + 31, 0), INT32_MIN);
|
||||
|
||||
var g = float64x2(makeDouble(1, 1023 + 31, 0), 0);
|
||||
assertEqX4(int32x4.fromFloat64x2(g), [INT32_MIN, 0, 0, 0]);
|
||||
|
||||
var g = float64x2(makeDouble(1, 1023 + 31, 1), 0);
|
||||
assertThrowsInstanceOf(() => int32x4.fromFloat64x2(g), RangeError);
|
||||
|
||||
for (var [v,w] of valsExp) {
|
||||
assertEqX4(int32x4.fromFloat64x2(float64x2(...v)), w);
|
||||
}
|
||||
}
|
||||
|
||||
function testInt32x4FromFloat64x2Bits() {
|
||||
|
|
|
@ -1,3 +1,34 @@
|
|||
function makeFloat(sign, exp, mantissa) {
|
||||
assertEq(sign, sign & 0x1);
|
||||
assertEq(exp, exp & 0xFF);
|
||||
assertEq(mantissa, mantissa & 0x7FFFFF);
|
||||
|
||||
var i32 = new Int32Array(1);
|
||||
var f32 = new Float32Array(i32.buffer);
|
||||
|
||||
i32[0] = (sign << 31) | (exp << 23) | mantissa;
|
||||
return f32[0];
|
||||
}
|
||||
|
||||
function makeDouble(sign, exp, mantissa) {
|
||||
assertEq(sign, sign & 0x1);
|
||||
assertEq(exp, exp & 0x7FF);
|
||||
|
||||
// Can't use bitwise operations on mantissa, as it might be a double
|
||||
assertEq(mantissa <= 0xfffffffffffff, true);
|
||||
var highBits = (mantissa / Math.pow(2, 32)) | 0;
|
||||
var lowBits = mantissa - highBits * Math.pow(2, 32);
|
||||
|
||||
var i32 = new Int32Array(2);
|
||||
var f64 = new Float64Array(i32.buffer);
|
||||
|
||||
// Note that this assumes little-endian order, which is the case on tier-1
|
||||
// platforms.
|
||||
i32[0] = lowBits;
|
||||
i32[1] = (sign << 31) | (exp << 20) | highBits;
|
||||
return f64[0];
|
||||
}
|
||||
|
||||
function assertEqX2(v, arr) {
|
||||
try {
|
||||
assertEq(v.x, arr[0]);
|
||||
|
|
|
@ -29,11 +29,11 @@ namespace js {
|
|||
*
|
||||
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
|
||||
*/
|
||||
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 279;
|
||||
static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 280;
|
||||
static const uint32_t XDR_BYTECODE_VERSION =
|
||||
uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
|
||||
|
||||
static_assert(JSErr_Limit == 391,
|
||||
static_assert(JSErr_Limit == 392,
|
||||
"GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
|
||||
"removed MSG_DEFs from js.msg, you should increment "
|
||||
"XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
|
||||
|
|
Загрузка…
Ссылка в новой задаче