blob: 36492c94b97d53bfdd9974acb7f21dcb97582f2c [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* 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/. */
/*
* JS SIMD pseudo-module.
* Specification matches polyfill:
* https://github.com/johnmccutchan/ecmascript_simd/blob/master/src/ecmascript_simd.js
* The objects float32x4 and int32x4 are installed on the SIMD pseudo-module.
*/
#include "builtin/SIMD.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/IntegerTypeTraits.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "jsprf.h"
#include "builtin/TypedObject.h"
#include "jit/InlinableNatives.h"
#include "js/Value.h"
#include "jsobjinlines.h"
using namespace js;
using mozilla::ArrayLength;
using mozilla::IsFinite;
using mozilla::IsNaN;
using mozilla::FloorLog2;
using mozilla::NumberIsInt32;
///////////////////////////////////////////////////////////////////////////
// SIMD
static bool
CheckVectorObject(HandleValue v, SimdTypeDescr::Type expectedType)
{
if (!v.isObject())
return false;
JSObject& obj = v.toObject();
if (!obj.is<TypedObject>())
return false;
TypeDescr& typeRepr = obj.as<TypedObject>().typeDescr();
if (typeRepr.kind() != type::Simd)
return false;
return typeRepr.as<SimdTypeDescr>().type() == expectedType;
}
template<class V>
bool
js::IsVectorObject(HandleValue v)
{
return CheckVectorObject(v, V::type);
}
template bool js::IsVectorObject<Int8x16>(HandleValue v);
template bool js::IsVectorObject<Int16x8>(HandleValue v);
template bool js::IsVectorObject<Int32x4>(HandleValue v);
template bool js::IsVectorObject<Float32x4>(HandleValue v);
template bool js::IsVectorObject<Float64x2>(HandleValue v);
static inline bool
ErrorBadArgs(JSContext* cx)
{
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
return false;
}
static inline bool
ErrorWrongTypeArg(JSContext* cx, size_t argIndex, Handle<TypeDescr*> typeDescr)
{
MOZ_ASSERT(argIndex < 10);
char charArgIndex[2];
JS_snprintf(charArgIndex, sizeof charArgIndex, "%d", argIndex);
HeapSlot& typeNameSlot = typeDescr->getReservedSlotRef(JS_DESCR_SLOT_STRING_REPR);
char* typeNameStr = JS_EncodeString(cx, typeNameSlot.toString());
if (!typeNameStr)
return false;
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_SIMD_NOT_A_VECTOR,
typeNameStr, charArgIndex);
JS_free(cx, typeNameStr);
return false;
}
template<typename T>
static SimdTypeDescr*
GetTypeDescr(JSContext* cx)
{
return cx->global()->getOrCreateSimdTypeDescr<T>(cx);
}
template<typename V>
bool
js::ToSimdConstant(JSContext* cx, HandleValue v, jit::SimdConstant* out)
{
typedef typename V::Elem Elem;
Rooted<TypeDescr*> typeDescr(cx, GetTypeDescr<V>(cx));
if (!typeDescr)
return false;
if (!IsVectorObject<V>(v))
return ErrorWrongTypeArg(cx, 1, typeDescr);
Elem* mem = reinterpret_cast<Elem*>(v.toObject().as<TypedObject>().typedMem());
*out = jit::SimdConstant::CreateX4(mem);
return true;
}
template bool js::ToSimdConstant<Int32x4>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
template bool js::ToSimdConstant<Float32x4>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
template<typename Elem>
static Elem
TypedObjectMemory(HandleValue v)
{
TypedObject& obj = v.toObject().as<TypedObject>();
return reinterpret_cast<Elem>(obj.typedMem());
}
template<typename SimdType>
static bool SignMask(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename SimdType::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
SimdTypeDescr::class_.name, "signMask",
InformalValueTypeName(args.thisv()));
return false;
}
TypedObject& typedObj = args.thisv().toObject().as<TypedObject>();
TypeDescr& descr = typedObj.typeDescr();
if (descr.kind() != type::Simd || descr.as<SimdTypeDescr>().type() != SimdType::type) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
SimdTypeDescr::class_.name, "signMask",
InformalValueTypeName(args.thisv()));
return false;
}
// Load the data as integer so that we treat the sign bit consistently,
// since -0.0 is not less than zero, but it still has the sign bit set.
typedef typename mozilla::SignedStdintTypeForSize<sizeof(Elem)>::Type Int;
static_assert(SimdType::lanes * sizeof(Int) <= jit::Simd128DataSize,
"signMask access should respect the bounds of the type");
const Elem* elems = reinterpret_cast<const Elem*>(typedObj.typedMem());
int32_t result = 0;
for (unsigned i = 0; i < SimdType::lanes; ++i) {
Int x = mozilla::BitwiseCast<Int>(elems[i]);
result |= (x < 0) << i;
}
args.rval().setInt32(result);
return true;
}
#define SIGN_MASK(type) \
static bool type##SignMask(JSContext* cx, unsigned argc, Value* vp) { \
return SignMask<type>(cx, argc, vp); \
}
SIGN_MASK(Float32x4);
SIGN_MASK(Float64x2);
SIGN_MASK(Int8x16);
SIGN_MASK(Int16x8);
SIGN_MASK(Int32x4);
#undef SIGN_MASK
const Class SimdTypeDescr::class_ = {
"SIMD",
JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
TypeDescr::finalize,
call
};
namespace {
// These classes just exist to group together various properties and so on.
class Int8x16Defn {
public:
static const SimdTypeDescr::Type type = SimdTypeDescr::Int8x16;
static const JSFunctionSpec TypeDescriptorMethods[];
static const JSPropertySpec TypedObjectProperties[];
static const JSFunctionSpec TypedObjectMethods[];
static const JSFunctionSpec Methods[];
};
class Int16x8Defn {
public:
static const SimdTypeDescr::Type type = SimdTypeDescr::Int16x8;
static const JSFunctionSpec TypeDescriptorMethods[];
static const JSPropertySpec TypedObjectProperties[];
static const JSFunctionSpec TypedObjectMethods[];
static const JSFunctionSpec Methods[];
};
class Int32x4Defn {
public:
static const SimdTypeDescr::Type type = SimdTypeDescr::Int32x4;
static const JSFunctionSpec TypeDescriptorMethods[];
static const JSPropertySpec TypedObjectProperties[];
static const JSFunctionSpec TypedObjectMethods[];
static const JSFunctionSpec Methods[];
};
class Float32x4Defn {
public:
static const SimdTypeDescr::Type type = SimdTypeDescr::Float32x4;
static const JSFunctionSpec TypeDescriptorMethods[];
static const JSPropertySpec TypedObjectProperties[];
static const JSFunctionSpec TypedObjectMethods[];
static const JSFunctionSpec Methods[];
};
class Float64x2Defn {
public:
static const SimdTypeDescr::Type type = SimdTypeDescr::Float64x2;
static const JSFunctionSpec TypeDescriptorMethods[];
static const JSPropertySpec TypedObjectProperties[];
static const JSFunctionSpec TypedObjectMethods[];
static const JSFunctionSpec Methods[];
};
} // namespace
const JSFunctionSpec Float32x4Defn::TypeDescriptorMethods[] = {
JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0),
JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
JS_FS_END
};
const JSPropertySpec Float32x4Defn::TypedObjectProperties[] = {
JS_PSG("signMask", Float32x4SignMask, JSPROP_PERMANENT),
JS_PS_END
};
const JSFunctionSpec Float32x4Defn::TypedObjectMethods[] = {
JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
JS_FS_END
};
const JSFunctionSpec Float32x4Defn::Methods[] = {
#define SIMD_FLOAT32X4_FUNCTION_ITEM(Name, Func, Operands) \
JS_INLINABLE_FN(#Name, js::simd_float32x4_##Name, Operands, 0, SimdFloat32x4),
FLOAT32X4_FUNCTION_LIST(SIMD_FLOAT32X4_FUNCTION_ITEM)
#undef SIMD_FLOAT32x4_FUNCTION_ITEM
JS_FS_END
};
const JSFunctionSpec Float64x2Defn::TypeDescriptorMethods[] = {
JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0),
JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
JS_FS_END
};
const JSPropertySpec Float64x2Defn::TypedObjectProperties[] = {
JS_PSG("signMask", Float64x2SignMask, JSPROP_PERMANENT),
JS_PS_END
};
const JSFunctionSpec Float64x2Defn::TypedObjectMethods[] = {
JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
JS_FS_END
};
const JSFunctionSpec Float64x2Defn::Methods[] = {
#define SIMD_FLOAT64X2_FUNCTION_ITEM(Name, Func, Operands) \
JS_FN(#Name, js::simd_float64x2_##Name, Operands, 0),
FLOAT64X2_FUNCTION_LIST(SIMD_FLOAT64X2_FUNCTION_ITEM)
#undef SIMD_FLOAT64X2_FUNCTION_ITEM
JS_FS_END
};
const JSFunctionSpec Int8x16Defn::TypeDescriptorMethods[] = {
JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0),
JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
JS_FS_END,
};
const JSPropertySpec Int8x16Defn::TypedObjectProperties[] = {
JS_PSG("signMask", Int8x16SignMask, JSPROP_PERMANENT),
JS_PS_END
};
const JSFunctionSpec Int8x16Defn::TypedObjectMethods[] = {
JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
JS_FS_END
};
const JSFunctionSpec Int8x16Defn::Methods[] = {
#define SIMD_INT8X16_FUNCTION_ITEM(Name, Func, Operands) \
JS_FN(#Name, js::simd_int8x16_##Name, Operands, 0),
INT8X16_FUNCTION_LIST(SIMD_INT8X16_FUNCTION_ITEM)
#undef SIMD_INT8X16_FUNCTION_ITEM
JS_FS_END
};
const JSFunctionSpec Int16x8Defn::TypeDescriptorMethods[] = {
JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0),
JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
JS_FS_END,
};
const JSPropertySpec Int16x8Defn::TypedObjectProperties[] = {
JS_PSG("signMask", Int16x8SignMask, JSPROP_PERMANENT),
JS_PS_END
};
const JSFunctionSpec Int16x8Defn::TypedObjectMethods[] = {
JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
JS_FS_END
};
const JSFunctionSpec Int16x8Defn::Methods[] = {
#define SIMD_INT16X8_FUNCTION_ITEM(Name, Func, Operands) \
JS_FN(#Name, js::simd_int16x8_##Name, Operands, 0),
INT16X8_FUNCTION_LIST(SIMD_INT16X8_FUNCTION_ITEM)
#undef SIMD_INT16X8_FUNCTION_ITEM
JS_FS_END
};
const JSFunctionSpec Int32x4Defn::TypeDescriptorMethods[] = {
JS_SELF_HOSTED_FN("toSource", "DescrToSource", 0, 0),
JS_SELF_HOSTED_FN("array", "ArrayShorthand", 1, 0),
JS_SELF_HOSTED_FN("equivalent", "TypeDescrEquivalent", 1, 0),
JS_FS_END,
};
const JSPropertySpec Int32x4Defn::TypedObjectProperties[] = {
JS_PSG("signMask", Int32x4SignMask, JSPROP_PERMANENT),
JS_PS_END
};
const JSFunctionSpec Int32x4Defn::TypedObjectMethods[] = {
JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
JS_FS_END
};
const JSFunctionSpec Int32x4Defn::Methods[] = {
#define SIMD_INT32X4_FUNCTION_ITEM(Name, Func, Operands) \
JS_INLINABLE_FN(#Name, js::simd_int32x4_##Name, Operands, 0, SimdInt32x4),
INT32X4_FUNCTION_LIST(SIMD_INT32X4_FUNCTION_ITEM)
#undef SIMD_INT32X4_FUNCTION_ITEM
JS_FS_END
};
template<typename T>
static JSObject*
CreateAndBindSimdClass(JSContext* cx, Handle<GlobalObject*> global, HandleObject globalSimdObject,
HandlePropertyName stringRepr)
{
const SimdTypeDescr::Type type = T::type;
RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
if (!funcProto)
return nullptr;
// Create type constructor itself and initialize its reserved slots.
Rooted<SimdTypeDescr*> typeDescr(cx);
typeDescr = NewObjectWithGivenProto<SimdTypeDescr>(cx, funcProto, SingletonObject);
if (!typeDescr)
return nullptr;
typeDescr->initReservedSlot(JS_DESCR_SLOT_KIND, Int32Value(type::Simd));
typeDescr->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr));
typeDescr->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(SimdTypeDescr::alignment(type)));
typeDescr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(SimdTypeDescr::size(type)));
typeDescr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(false));
typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(T::type));
if (!CreateUserSizeAndAlignmentProperties(cx, typeDescr))
return nullptr;
// Create prototype property, which inherits from Object.prototype.
RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
if (!objProto)
return nullptr;
Rooted<TypedProto*> proto(cx);
proto = NewObjectWithGivenProto<TypedProto>(cx, objProto, SingletonObject);
if (!proto)
return nullptr;
typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));
// Link constructor to prototype and install properties.
if (!JS_DefineFunctions(cx, typeDescr, T::TypeDescriptorMethods))
return nullptr;
if (!LinkConstructorAndPrototype(cx, typeDescr, proto) ||
!DefinePropertiesAndFunctions(cx, proto, T::TypedObjectProperties,
T::TypedObjectMethods))
{
return nullptr;
}
// Bind type descriptor to the global SIMD object
RootedValue typeValue(cx, ObjectValue(*typeDescr));
if (!JS_DefineFunctions(cx, typeDescr, T::Methods) ||
!DefineProperty(cx, globalSimdObject, stringRepr, typeValue, nullptr, nullptr,
JSPROP_READONLY | JSPROP_PERMANENT))
{
return nullptr;
}
uint32_t slot = uint32_t(typeDescr->type());
globalSimdObject->as<NativeObject>().setReservedSlot(slot, ObjectValue(*typeDescr));
return typeDescr;
}
template <typename T>
static bool
FillLanes(JSContext* cx, Handle<TypedObject*> result, const CallArgs& args)
{
typedef typename T::Elem Elem;
Elem tmp;
for (unsigned i = 0; i < T::lanes; i++) {
if (!T::Cast(cx, args.get(i), &tmp))
return false;
reinterpret_cast<Elem*>(result->typedMem())[i] = tmp;
}
args.rval().setObject(*result);
return true;
}
bool
SimdTypeDescr::call(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<SimdTypeDescr*> descr(cx, &args.callee().as<SimdTypeDescr>());
MOZ_ASSERT(size_t(static_cast<TypeDescr*>(descr)->size()) <= InlineTypedObject::MaximumSize,
"inline storage is needed for using InternalHandle belows");
Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, descr, 0));
if (!result)
return false;
switch (descr->type()) {
case SimdTypeDescr::Int8x16: return FillLanes< ::Int8x16>(cx, result, args);
case SimdTypeDescr::Int16x8: return FillLanes< ::Int16x8>(cx, result, args);
case SimdTypeDescr::Int32x4: return FillLanes< ::Int32x4>(cx, result, args);
case SimdTypeDescr::Float32x4: return FillLanes< ::Float32x4>(cx, result, args);
case SimdTypeDescr::Float64x2: return FillLanes< ::Float64x2>(cx, result, args);
}
MOZ_CRASH("unexpected SIMD descriptor");
return false;
}
///////////////////////////////////////////////////////////////////////////
// SIMD class
static const uint32_t SIMD_SLOTS_COUNT = SimdTypeDescr::LAST_TYPE + 1;
const Class SIMDObject::class_ = {
"SIMD",
JSCLASS_HAS_RESERVED_SLOTS(SIMD_SLOTS_COUNT)
};
bool
GlobalObject::initSimdObject(JSContext* cx, Handle<GlobalObject*> global)
{
// SIMD relies on the TypedObject module being initialized.
// In particular, the self-hosted code for array() wants
// to be able to call GetTypedObjectModule(). It is NOT necessary
// to install the TypedObjectModule global, but at the moment
// those two things are not separable.
if (!global->getOrCreateTypedObjectModule(cx))
return false;
RootedObject globalSimdObject(cx);
RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
if (!objProto)
return false;
globalSimdObject = NewObjectWithGivenProto(cx, &SIMDObject::class_, objProto, SingletonObject);
if (!globalSimdObject)
return false;
RootedValue globalSimdValue(cx, ObjectValue(*globalSimdObject));
if (!DefineProperty(cx, global, cx->names().SIMD, globalSimdValue, nullptr, nullptr,
JSPROP_RESOLVING))
{
return false;
}
global->setConstructor(JSProto_SIMD, globalSimdValue);
return true;
}
JSObject*
js::InitSIMDClass(JSContext* cx, HandleObject obj)
{
MOZ_ASSERT(obj->is<GlobalObject>());
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedObject globalSimdObject(cx, global->getOrCreateSimdGlobalObject(cx));
if (!globalSimdObject)
return nullptr;
RootedObject i8x16(cx);
i8x16 = CreateAndBindSimdClass<Int8x16Defn>(cx, global, globalSimdObject, cx->names().int8x16);
if (!i8x16)
return nullptr;
RootedObject i16x8(cx);
i16x8 = CreateAndBindSimdClass<Int16x8Defn>(cx, global, globalSimdObject, cx->names().int16x8);
if (!i16x8)
return nullptr;
RootedObject f32x4(cx);
f32x4 = CreateAndBindSimdClass<Float32x4Defn>(cx, global, globalSimdObject, cx->names().float32x4);
if (!f32x4)
return nullptr;
RootedObject i32x4(cx);
i32x4 = CreateAndBindSimdClass<Int32x4Defn>(cx, global, globalSimdObject, cx->names().int32x4);
if (!i32x4)
return nullptr;
RootedObject f64x2(cx);
f64x2 = CreateAndBindSimdClass<Float64x2Defn>(cx, global, globalSimdObject, cx->names().float64x2);
if (!f64x2)
return nullptr;
return globalSimdObject;
}
template<typename V>
JSObject*
js::CreateSimd(JSContext* cx, const typename V::Elem* data)
{
typedef typename V::Elem Elem;
Rooted<TypeDescr*> typeDescr(cx, GetTypeDescr<V>(cx));
if (!typeDescr)
return nullptr;
Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, typeDescr, 0));
if (!result)
return nullptr;
Elem* resultMem = reinterpret_cast<Elem*>(result->typedMem());
memcpy(resultMem, data, sizeof(Elem) * V::lanes);
return result;
}
template JSObject* js::CreateSimd<Float32x4>(JSContext* cx, const Float32x4::Elem* data);
template JSObject* js::CreateSimd<Float64x2>(JSContext* cx, const Float64x2::Elem* data);
template JSObject* js::CreateSimd<Int8x16>(JSContext* cx, const Int8x16::Elem* data);
template JSObject* js::CreateSimd<Int16x8>(JSContext* cx, const Int16x8::Elem* data);
template JSObject* js::CreateSimd<Int32x4>(JSContext* cx, const Int32x4::Elem* data);
namespace js {
// Unary SIMD operators
template<typename T>
struct Identity {
static T apply(T x) { return x; }
};
template<typename T>
struct Abs {
static T apply(T x) { return mozilla::Abs(x); }
};
template<typename T>
struct Neg {
static T apply(T x) { return -1 * x; }
};
template<typename T>
struct Not {
static T apply(T x) { return ~x; }
};
template<typename T>
struct RecApprox {
static T apply(T x) { return 1 / x; }
};
template<typename T>
struct RecSqrtApprox {
static T apply(T x) { return 1 / sqrt(x); }
};
template<typename T>
struct Sqrt {
static T apply(T x) { return sqrt(x); }
};
// Binary SIMD operators
template<typename T>
struct Add {
static T apply(T l, T r) { return l + r; }
};
template<typename T>
struct Sub {
static T apply(T l, T r) { return l - r; }
};
template<typename T>
struct Div {
static T apply(T l, T r) { return l / r; }
};
template<typename T>
struct Mul {
static T apply(T l, T r) { return l * r; }
};
template<typename T>
struct Minimum {
static T apply(T l, T r) { return math_min_impl(l, r); }
};
template<typename T>
struct MinNum {
static T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_min_impl(l, r)); }
};
template<typename T>
struct Maximum {
static T apply(T l, T r) { return math_max_impl(l, r); }
};
template<typename T>
struct MaxNum {
static T apply(T l, T r) { return IsNaN(l) ? r : (IsNaN(r) ? l : math_max_impl(l, r)); }
};
template<typename T>
struct LessThan {
static bool apply(T l, T r) { return l < r; }
};
template<typename T>
struct LessThanOrEqual {
static bool apply(T l, T r) { return l <= r; }
};
template<typename T>
struct GreaterThan {
static bool apply(T l, T r) { return l > r; }
};
template<typename T>
struct GreaterThanOrEqual {
static bool apply(T l, T r) { return l >= r; }
};
template<typename T>
struct Equal {
static bool apply(T l, T r) { return l == r; }
};
template<typename T>
struct NotEqual {
static bool apply(T l, T r) { return l != r; }
};
template<typename T>
struct Xor {
static T apply(T l, T r) { return l ^ r; }
};
template<typename T>
struct And {
static T apply(T l, T r) { return l & r; }
};
template<typename T>
struct Or {
static T apply(T l, T r) { return l | r; }
};
// For the following three operators, if the value v we're trying to shift is
// such that v << bits can't fit in the int32 range, then we have undefined
// behavior, according to C++11 [expr.shift]p2.
template<typename T>
struct ShiftLeft {
static T apply(T v, int32_t bits) {
return uint32_t(bits) >= sizeof(T) * 8 ? 0 : v << bits;
}
};
template<typename T>
struct ShiftRightArithmetic {
static T apply(T v, int32_t bits) {
uint32_t maxBits = sizeof(T) * 8;
return v >> (uint32_t(bits) >= maxBits ? maxBits - 1 : bits);
}
};
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;
}
};
} // 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)
return false;
args.rval().setObject(*obj);
return true;
}
// Coerces the inputs of type In to the type Coercion, apply the operator Op
// and converts the result to the type Out.
template<typename In, typename Coercion, template<typename C> class Op, typename Out>
static bool
CoercedUnaryFunc(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename Coercion::Elem CoercionElem;
typedef typename Out::Elem RetElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !IsVectorObject<In>(args[0]))
return ErrorBadArgs(cx);
CoercionElem result[Coercion::lanes];
CoercionElem* val = TypedObjectMemory<CoercionElem*>(args[0]);
for (unsigned i = 0; i < Coercion::lanes; i++)
result[i] = Op<CoercionElem>::apply(val[i]);
return StoreResult<Out>(cx, args, (RetElem*) result);
}
// Coerces the inputs of type In to the type Coercion, apply the operator Op
// and converts the result to the type Out.
template<typename In, typename Coercion, template<typename C> class Op, typename Out>
static bool
CoercedBinaryFunc(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename Coercion::Elem CoercionElem;
typedef typename Out::Elem RetElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2 || !IsVectorObject<In>(args[0]) || !IsVectorObject<In>(args[1]))
return ErrorBadArgs(cx);
CoercionElem result[Coercion::lanes];
CoercionElem* left = TypedObjectMemory<CoercionElem*>(args[0]);
CoercionElem* right = TypedObjectMemory<CoercionElem*>(args[1]);
for (unsigned i = 0; i < Coercion::lanes; i++)
result[i] = Op<CoercionElem>::apply(left[i], right[i]);
return StoreResult<Out>(cx, args, (RetElem*) result);
}
// Same as above, with no coercion, i.e. Coercion == In.
template<typename In, template<typename C> class Op, typename Out>
static bool
UnaryFunc(JSContext* cx, unsigned argc, Value* vp)
{
return CoercedUnaryFunc<In, Out, Op, Out>(cx, argc, vp);
}
template<typename In, template<typename C> class Op, typename Out>
static bool
BinaryFunc(JSContext* cx, unsigned argc, Value* vp)
{
return CoercedBinaryFunc<In, Out, Op, Out>(cx, argc, vp);
}
template<typename V>
static bool
ExtractLane(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 2 || !IsVectorObject<V>(args[0]) || !args[1].isNumber())
return ErrorBadArgs(cx);
int32_t lane;
if (!NumberIsInt32(args[1].toNumber(), &lane))
return ErrorBadArgs(cx);
if (lane < 0 || uint32_t(lane) >= V::lanes)
return ErrorBadArgs(cx);
Elem* vec = TypedObjectMemory<Elem*>(args[0]);
Elem val = vec[lane];
args.rval().set(V::ToValue(val));
return true;
}
template<typename V>
static bool
ReplaceLane(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
// Only the first and second arguments are mandatory
if (args.length() < 2 || !IsVectorObject<V>(args[0]))
return ErrorBadArgs(cx);
Elem* vec = TypedObjectMemory<Elem*>(args[0]);
Elem result[V::lanes];
int32_t lanearg;
if (!args[1].isNumber() || !NumberIsInt32(args[1].toNumber(), &lanearg))
return ErrorBadArgs(cx);
if (lanearg < 0 || uint32_t(lanearg) >= V::lanes)
return ErrorBadArgs(cx);
uint32_t lane = uint32_t(lanearg);
Elem value;
if (!V::Cast(cx, args.get(2), &value))
return false;
for (unsigned i = 0; i < V::lanes; i++)
result[i] = i == lane ? value : vec[i];
return StoreResult<V>(cx, args, result);
}
template<typename V>
static bool
Swizzle(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != (V::lanes + 1) || !IsVectorObject<V>(args[0]))
return ErrorBadArgs(cx);
uint32_t lanes[V::lanes];
for (unsigned i = 0; i < V::lanes; i++) {
int32_t lane;
if (!args[i + 1].isNumber() || !NumberIsInt32(args[i + 1].toNumber(), &lane))
return ErrorBadArgs(cx);
if (lane < 0 || uint32_t(lane) >= V::lanes)
return ErrorBadArgs(cx);
lanes[i] = uint32_t(lane);
}
Elem* val = TypedObjectMemory<Elem*>(args[0]);
Elem result[V::lanes];
for (unsigned i = 0; i < V::lanes; i++)
result[i] = val[lanes[i]];
return StoreResult<V>(cx, args, result);
}
template<typename V>
static bool
Shuffle(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != (V::lanes + 2) || !IsVectorObject<V>(args[0]) || !IsVectorObject<V>(args[1]))
return ErrorBadArgs(cx);
uint32_t lanes[V::lanes];
for (unsigned i = 0; i < V::lanes; i++) {
int32_t lane;
if (!args[i + 2].isNumber() || !NumberIsInt32(args[i + 2].toNumber(), &lane))
return ErrorBadArgs(cx);
if (lane < 0 || uint32_t(lane) >= (2 * V::lanes))
return ErrorBadArgs(cx);
lanes[i] = uint32_t(lane);
}
Elem* lhs = TypedObjectMemory<Elem*>(args[0]);
Elem* rhs = TypedObjectMemory<Elem*>(args[1]);
Elem result[V::lanes];
for (unsigned i = 0; i < V::lanes; i++) {
Elem* selectedInput = lanes[i] < V::lanes ? lhs : rhs;
result[i] = selectedInput[lanes[i] % V::lanes];
}
return StoreResult<V>(cx, args, result);
}
template<typename V, template<typename T> class Op>
static bool
BinaryScalar(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2)
return ErrorBadArgs(cx);
Elem result[V::lanes];
if (!IsVectorObject<V>(args[0]))
return ErrorBadArgs(cx);
Elem* val = TypedObjectMemory<Elem*>(args[0]);
int32_t bits;
if (!ToInt32(cx, args[1], &bits))
return false;
for (unsigned i = 0; i < V::lanes; i++)
result[i] = Op<Elem>::apply(val[i], bits);
return StoreResult<V>(cx, args, result);
}
template<typename In, template<typename C> class Op, typename Out>
static bool
CompareFunc(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename In::Elem InElem;
typedef typename Out::Elem OutElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2 || !IsVectorObject<In>(args[0]) || !IsVectorObject<In>(args[1]))
return ErrorBadArgs(cx);
OutElem result[Out::lanes];
InElem* left = TypedObjectMemory<InElem*>(args[0]);
InElem* right = TypedObjectMemory<InElem*>(args[1]);
for (unsigned i = 0; i < Out::lanes; i++) {
unsigned j = (i * In::lanes) / Out::lanes;
result[i] = Op<InElem>::apply(left[j], right[j]) ? -1 : 0;
}
return StoreResult<Out>(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)
{
typedef typename V::Elem Elem;
typedef typename Vret::Elem RetElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !IsVectorObject<V>(args[0]))
return ErrorBadArgs(cx);
Elem* val = TypedObjectMemory<Elem*>(args[0]);
RetElem result[Vret::lanes];
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);
}
template<typename V, typename Vret>
static bool
FuncConvertBits(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename Vret::Elem RetElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1 || !IsVectorObject<V>(args[0]))
return ErrorBadArgs(cx);
// While we could just pass the typedMem of args[0] as StoreResults' last
// argument, a GC could move the pointer to its memory in the meanwhile.
// For consistency with other SIMD functions, simply copy the input in a
// temporary array.
RetElem copy[Vret::lanes];
memcpy(copy, TypedObjectMemory<RetElem*>(args[0]), Vret::lanes * sizeof(RetElem));
return StoreResult<Vret>(cx, args, copy);
}
template<typename Vret>
static bool
FuncSplat(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename Vret::Elem RetElem;
CallArgs args = CallArgsFromVp(argc, vp);
RetElem arg;
if (!Vret::Cast(cx, args.get(0), &arg))
return false;
RetElem result[Vret::lanes];
for (unsigned i = 0; i < Vret::lanes; i++)
result[i] = arg;
return StoreResult<Vret>(cx, args, result);
}
template<typename V>
static bool
Bool(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
Elem result[V::lanes];
for (unsigned i = 0; i < V::lanes; i++)
result[i] = ToBoolean(args.get(i)) ? -1 : 0;
return StoreResult<V>(cx, args, result);
}
template<typename V, typename MaskType>
static bool
SelectBits(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
typedef typename MaskType::Elem MaskTypeElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 3 || !IsVectorObject<MaskType>(args[0]) ||
!IsVectorObject<V>(args[1]) || !IsVectorObject<V>(args[2]))
{
return ErrorBadArgs(cx);
}
MaskTypeElem* val = TypedObjectMemory<MaskTypeElem*>(args[0]);
MaskTypeElem* tv = TypedObjectMemory<MaskTypeElem*>(args[1]);
MaskTypeElem* fv = TypedObjectMemory<MaskTypeElem*>(args[2]);
MaskTypeElem tr[MaskType::lanes];
for (unsigned i = 0; i < MaskType::lanes; i++)
tr[i] = And<MaskTypeElem>::apply(val[i], tv[i]);
MaskTypeElem fr[MaskType::lanes];
for (unsigned i = 0; i < MaskType::lanes; i++)
fr[i] = And<MaskTypeElem>::apply(Not<MaskTypeElem>::apply(val[i]), fv[i]);
MaskTypeElem orInt[MaskType::lanes];
for (unsigned i = 0; i < MaskType::lanes; i++)
orInt[i] = Or<MaskTypeElem>::apply(tr[i], fr[i]);
Elem* result = reinterpret_cast<Elem*>(orInt);
return StoreResult<V>(cx, args, result);
}
template<typename V, typename MaskType>
static bool
Select(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
typedef typename MaskType::Elem MaskTypeElem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 3 || !IsVectorObject<MaskType>(args[0]) ||
!IsVectorObject<V>(args[1]) || !IsVectorObject<V>(args[2]))
{
return ErrorBadArgs(cx);
}
MaskTypeElem* mask = TypedObjectMemory<MaskTypeElem*>(args[0]);
Elem* tv = TypedObjectMemory<Elem*>(args[1]);
Elem* fv = TypedObjectMemory<Elem*>(args[2]);
Elem result[V::lanes];
for (unsigned i = 0; i < V::lanes; i++)
result[i] = mask[i] < 0 ? tv[i] : fv[i];
return StoreResult<V>(cx, args, result);
}
template<class VElem, unsigned NumElem>
static bool
TypedArrayFromArgs(JSContext* cx, const CallArgs& args,
MutableHandleObject typedArray, int32_t* byteStart)
{
if (!args[0].isObject())
return ErrorBadArgs(cx);
JSObject& argobj = args[0].toObject();
if (!IsAnyTypedArray(&argobj))
return ErrorBadArgs(cx);
typedArray.set(&argobj);
int32_t index;
if (!ToInt32(cx, args[1], &index))
return false;
*byteStart = index * AnyTypedArrayBytesPerElement(typedArray);
if (*byteStart < 0 ||
(uint32_t(*byteStart) + NumElem * sizeof(VElem)) > AnyTypedArrayByteLength(typedArray))
{
// Keep in sync with AsmJS OnOutOfBounds function.
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
return true;
}
template<class V, unsigned NumElem>
static bool
Load(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 2)
return ErrorBadArgs(cx);
int32_t byteStart;
RootedObject typedArray(cx);
if (!TypedArrayFromArgs<Elem, NumElem>(cx, args, &typedArray, &byteStart))
return false;
Rooted<TypeDescr*> typeDescr(cx, GetTypeDescr<V>(cx));
if (!typeDescr)
return false;
Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, typeDescr, 0));
if (!result)
return false;
SharedMem<Elem*> src = AnyTypedArrayViewData(typedArray).addBytes(byteStart).cast<Elem*>();
Elem* dst = reinterpret_cast<Elem*>(result->typedMem());
jit::AtomicOperations::memcpySafeWhenRacy(dst, src, sizeof(Elem) * NumElem);
args.rval().setObject(*result);
return true;
}
template<class V, unsigned NumElem>
static bool
Store(JSContext* cx, unsigned argc, Value* vp)
{
typedef typename V::Elem Elem;
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 3)
return ErrorBadArgs(cx);
int32_t byteStart;
RootedObject typedArray(cx);
if (!TypedArrayFromArgs<Elem, NumElem>(cx, args, &typedArray, &byteStart))
return false;
if (!IsVectorObject<V>(args[2]))
return ErrorBadArgs(cx);
Elem* src = TypedObjectMemory<Elem*>(args[2]);
SharedMem<Elem*> dst = AnyTypedArrayViewData(typedArray).addBytes(byteStart).cast<Elem*>();
js::jit::AtomicOperations::memcpySafeWhenRacy(dst, src, sizeof(Elem) * NumElem);
args.rval().setObject(args[2].toObject());
return true;
}
#define DEFINE_SIMD_FLOAT32X4_FUNCTION(Name, Func, Operands) \
bool \
js::simd_float32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
{ \
return Func(cx, argc, vp); \
}
FLOAT32X4_FUNCTION_LIST(DEFINE_SIMD_FLOAT32X4_FUNCTION)
#undef DEFINE_SIMD_FLOAT32X4_FUNCTION
#define DEFINE_SIMD_FLOAT64X2_FUNCTION(Name, Func, Operands) \
bool \
js::simd_float64x2_##Name(JSContext* cx, unsigned argc, Value* vp) \
{ \
return Func(cx, argc, vp); \
}
FLOAT64X2_FUNCTION_LIST(DEFINE_SIMD_FLOAT64X2_FUNCTION)
#undef DEFINE_SIMD_FLOAT64X2_FUNCTION
#define DEFINE_SIMD_INT8X16_FUNCTION(Name, Func, Operands) \
bool \
js::simd_int8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \
{ \
return Func(cx, argc, vp); \
}
INT8X16_FUNCTION_LIST(DEFINE_SIMD_INT8X16_FUNCTION)
#undef DEFINE_SIMD_INT8X16_FUNCTION
#define DEFINE_SIMD_INT16X8_FUNCTION(Name, Func, Operands) \
bool \
js::simd_int16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \
{ \
return Func(cx, argc, vp); \
}
INT16X8_FUNCTION_LIST(DEFINE_SIMD_INT16X8_FUNCTION)
#undef DEFINE_SIMD_INT16X8_FUNCTION
#define DEFINE_SIMD_INT32X4_FUNCTION(Name, Func, Operands) \
bool \
js::simd_int32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
{ \
return Func(cx, argc, vp); \
}
INT32X4_FUNCTION_LIST(DEFINE_SIMD_INT32X4_FUNCTION)
#undef DEFINE_SIMD_INT32X4_FUNCTION