1553 lines
50 KiB
C++
1553 lines
50 KiB
C++
/* -*- 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 "mozilla/Sprintf.h"
|
|
|
|
#include "jsapi.h"
|
|
#include "jsfriendapi.h"
|
|
#include "jsnum.h"
|
|
#include "jsprf.h"
|
|
|
|
#include "builtin/TypedObject.h"
|
|
#include "jit/InlinableNatives.h"
|
|
#include "js/GCAPI.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_assert(unsigned(SimdType::Count) == 12, "sync with TypedObjectConstants.h");
|
|
|
|
static bool ArgumentToLaneIndex(JSContext* cx, JS::HandleValue v, unsigned limit, unsigned* lane);
|
|
|
|
static bool
|
|
CheckVectorObject(HandleValue v, SimdType 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);
|
|
}
|
|
|
|
#define FOR_EACH_SIMD(macro) \
|
|
macro(Int8x16) \
|
|
macro(Int16x8) \
|
|
macro(Int32x4) \
|
|
macro(Uint8x16) \
|
|
macro(Uint16x8) \
|
|
macro(Uint32x4) \
|
|
macro(Float32x4) \
|
|
macro(Float64x2) \
|
|
macro(Bool8x16) \
|
|
macro(Bool16x8) \
|
|
macro(Bool32x4) \
|
|
macro(Bool64x2)
|
|
|
|
#define InstantiateIsVectorObject_(T) \
|
|
template bool js::IsVectorObject<T>(HandleValue v);
|
|
FOR_EACH_SIMD(InstantiateIsVectorObject_)
|
|
#undef InstantiateIsVectorObject_
|
|
|
|
const char*
|
|
js::SimdTypeToString(SimdType type)
|
|
{
|
|
switch (type) {
|
|
#define RETSTR_(TYPE) case SimdType::TYPE: return #TYPE;
|
|
FOR_EACH_SIMD(RETSTR_)
|
|
#undef RETSTR_
|
|
case SimdType::Count: break;
|
|
}
|
|
return "<bad SimdType>";
|
|
}
|
|
|
|
PropertyName*
|
|
js::SimdTypeToName(const JSAtomState& atoms, SimdType type)
|
|
{
|
|
switch (type) {
|
|
#define CASE_(TypeName) case SimdType::TypeName: return atoms.TypeName;
|
|
FOR_EACH_SIMD(CASE_)
|
|
#undef CASE_
|
|
case SimdType::Count: break;
|
|
}
|
|
MOZ_CRASH("bad SIMD type");
|
|
}
|
|
|
|
bool
|
|
js::IsSimdTypeName(const JSAtomState& atoms, const PropertyName* name, SimdType* type)
|
|
{
|
|
#define CHECK_(TypeName) if (name == atoms.TypeName) { \
|
|
*type = SimdType::TypeName; \
|
|
return true; \
|
|
}
|
|
FOR_EACH_SIMD(CHECK_)
|
|
#undef CHECK_
|
|
return false;
|
|
}
|
|
|
|
static inline bool
|
|
ErrorBadArgs(JSContext* cx)
|
|
{
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
|
|
return false;
|
|
}
|
|
|
|
static inline bool
|
|
ErrorWrongTypeArg(JSContext* cx, unsigned argIndex, Handle<TypeDescr*> typeDescr)
|
|
{
|
|
MOZ_ASSERT(argIndex < 10);
|
|
char charArgIndex[2];
|
|
SprintfLiteral(charArgIndex, "%u", argIndex);
|
|
|
|
HeapSlot& typeNameSlot = typeDescr->getReservedSlotRef(JS_DESCR_SLOT_STRING_REPR);
|
|
char* typeNameStr = JS_EncodeString(cx, typeNameSlot.toString());
|
|
if (!typeNameStr)
|
|
return false;
|
|
|
|
JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_SIMD_NOT_A_VECTOR,
|
|
typeNameStr, charArgIndex);
|
|
JS_free(cx, typeNameStr);
|
|
return false;
|
|
}
|
|
|
|
static inline bool
|
|
ErrorBadIndex(JSContext* cx)
|
|
{
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
|
|
return false;
|
|
}
|
|
|
|
template<typename T>
|
|
static SimdTypeDescr*
|
|
GetTypeDescr(JSContext* cx)
|
|
{
|
|
RootedGlobalObject global(cx, cx->global());
|
|
return GlobalObject::getOrCreateSimdTypeDescr(cx, global, T::type);
|
|
}
|
|
|
|
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);
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* mem = reinterpret_cast<Elem*>(v.toObject().as<TypedObject>().typedMem(nogc));
|
|
*out = jit::SimdConstant::CreateSimd128(mem);
|
|
return true;
|
|
}
|
|
|
|
template bool js::ToSimdConstant<Int8x16>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
|
|
template bool js::ToSimdConstant<Int16x8>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
|
|
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 bool js::ToSimdConstant<Bool8x16>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
|
|
template bool js::ToSimdConstant<Bool16x8>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
|
|
template bool js::ToSimdConstant<Bool32x4>(JSContext* cx, HandleValue v, jit::SimdConstant* out);
|
|
|
|
template<typename Elem>
|
|
static Elem
|
|
TypedObjectMemory(HandleValue v, const JS::AutoRequireNoGC& nogc)
|
|
{
|
|
TypedObject& obj = v.toObject().as<TypedObject>();
|
|
return reinterpret_cast<Elem>(obj.typedMem(nogc));
|
|
}
|
|
|
|
static const ClassOps SimdTypeDescrClassOps = {
|
|
nullptr, /* addProperty */
|
|
nullptr, /* delProperty */
|
|
nullptr, /* getProperty */
|
|
nullptr, /* setProperty */
|
|
nullptr, /* enumerate */
|
|
nullptr, /* resolve */
|
|
nullptr, /* mayResolve */
|
|
TypeDescr::finalize,
|
|
SimdTypeDescr::call
|
|
};
|
|
|
|
const Class SimdTypeDescr::class_ = {
|
|
"SIMD",
|
|
JSCLASS_HAS_RESERVED_SLOTS(JS_DESCR_SLOTS) | JSCLASS_BACKGROUND_FINALIZE,
|
|
&SimdTypeDescrClassOps
|
|
};
|
|
|
|
namespace {
|
|
|
|
// Define classes (Int8x16Defn, Int16x8Defn, etc.) to group together various
|
|
// properties and so on.
|
|
#define DEFINE_DEFN_(TypeName) \
|
|
class TypeName##Defn { \
|
|
public: \
|
|
static const JSFunctionSpec Methods[]; \
|
|
};
|
|
|
|
FOR_EACH_SIMD(DEFINE_DEFN_)
|
|
#undef DEFINE_DEFN_
|
|
|
|
} // namespace
|
|
|
|
// Shared type descriptor methods for all SIMD types.
|
|
static const JSFunctionSpec 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
|
|
};
|
|
|
|
// Shared TypedObject methods for all SIMD types.
|
|
static const JSFunctionSpec SimdTypedObjectMethods[] = {
|
|
JS_SELF_HOSTED_FN("toString", "SimdToString", 0, 0),
|
|
JS_SELF_HOSTED_FN("valueOf", "SimdValueOf", 0, 0),
|
|
JS_SELF_HOSTED_FN("toSource", "SimdToSource", 0, 0),
|
|
JS_FS_END
|
|
};
|
|
|
|
// Provide JSJitInfo structs for those types that are supported by Ion.
|
|
// The controlling SIMD type is encoded as the InlinableNative primary opcode.
|
|
// The SimdOperation within the type is encoded in the .depth field.
|
|
//
|
|
// The JS_INLINABLE_FN macro refers to js::JitInfo_##native which we provide as
|
|
// Simd##Type##_##Operation
|
|
//
|
|
// /!\ Don't forget to keep this list in sync with the SIMD instrinics used in
|
|
// SelfHosting.cpp.
|
|
|
|
namespace js {
|
|
namespace jit {
|
|
|
|
static_assert(uint64_t(SimdOperation::Last) <= UINT16_MAX, "SimdOperation must fit in uint16_t");
|
|
|
|
// See also JitInfo_* in MCallOptimize.cpp. We provide a JSJitInfo for all the
|
|
// named functions here. The default JitInfo_SimdInt32x4 etc structs represent the
|
|
// SimdOperation::Constructor.
|
|
#define DEFN(TYPE, OP) const JSJitInfo JitInfo_Simd##TYPE##_##OP = { \
|
|
/* .getter, unused for inlinable natives. */ \
|
|
{ nullptr }, \
|
|
/* .inlinableNative, but we have to init first union member: .protoID. */ \
|
|
{ uint16_t(InlinableNative::Simd##TYPE) }, \
|
|
/* .nativeOp. Actually initializing first union member .depth. */ \
|
|
{ uint16_t(SimdOperation::Fn_##OP) }, \
|
|
/* .type_ bitfield says this in an inlinable native function. */ \
|
|
JSJitInfo::InlinableNative \
|
|
/* Remaining fields are not used for inlinable natives. They are zero-initialized. */ \
|
|
};
|
|
|
|
// This list of inlinable types should match the one in jit/InlinableNatives.h.
|
|
#define TDEFN(Name, Func, Operands) DEFN(Float32x4, Name)
|
|
FLOAT32X4_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Int8x16, Name)
|
|
INT8X16_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Uint8x16, Name)
|
|
UINT8X16_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Int16x8, Name)
|
|
INT16X8_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Uint16x8, Name)
|
|
UINT16X8_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Int32x4, Name)
|
|
INT32X4_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Uint32x4, Name)
|
|
UINT32X4_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Bool8x16, Name)
|
|
BOOL8X16_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Bool16x8, Name)
|
|
BOOL16X8_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
#define TDEFN(Name, Func, Operands) DEFN(Bool32x4, Name)
|
|
BOOL32X4_FUNCTION_LIST(TDEFN)
|
|
#undef TDEFN
|
|
|
|
} // namespace jit
|
|
} // namespace js
|
|
|
|
const JSFunctionSpec Float32x4Defn::Methods[] = {
|
|
#define SIMD_FLOAT32X4_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_float32x4_##Name, Operands, 0, SimdFloat32x4_##Name),
|
|
FLOAT32X4_FUNCTION_LIST(SIMD_FLOAT32X4_FUNCTION_ITEM)
|
|
#undef SIMD_FLOAT32x4_FUNCTION_ITEM
|
|
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::Methods[] = {
|
|
#define SIMD_INT8X16_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_int8x16_##Name, Operands, 0, SimdInt8x16_##Name),
|
|
INT8X16_FUNCTION_LIST(SIMD_INT8X16_FUNCTION_ITEM)
|
|
#undef SIMD_INT8X16_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Int16x8Defn::Methods[] = {
|
|
#define SIMD_INT16X8_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_int16x8_##Name, Operands, 0, SimdInt16x8_##Name),
|
|
INT16X8_FUNCTION_LIST(SIMD_INT16X8_FUNCTION_ITEM)
|
|
#undef SIMD_INT16X8_FUNCTION_ITEM
|
|
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_##Name),
|
|
INT32X4_FUNCTION_LIST(SIMD_INT32X4_FUNCTION_ITEM)
|
|
#undef SIMD_INT32X4_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Uint8x16Defn::Methods[] = {
|
|
#define SIMD_UINT8X16_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_uint8x16_##Name, Operands, 0, SimdUint8x16_##Name),
|
|
UINT8X16_FUNCTION_LIST(SIMD_UINT8X16_FUNCTION_ITEM)
|
|
#undef SIMD_UINT8X16_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Uint16x8Defn::Methods[] = {
|
|
#define SIMD_UINT16X8_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_uint16x8_##Name, Operands, 0, SimdUint16x8_##Name),
|
|
UINT16X8_FUNCTION_LIST(SIMD_UINT16X8_FUNCTION_ITEM)
|
|
#undef SIMD_UINT16X8_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Uint32x4Defn::Methods[] = {
|
|
#define SIMD_UINT32X4_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_uint32x4_##Name, Operands, 0, SimdUint32x4_##Name),
|
|
UINT32X4_FUNCTION_LIST(SIMD_UINT32X4_FUNCTION_ITEM)
|
|
#undef SIMD_UINT32X4_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Bool8x16Defn::Methods[] = {
|
|
#define SIMD_BOOL8X16_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_bool8x16_##Name, Operands, 0, SimdBool8x16_##Name),
|
|
BOOL8X16_FUNCTION_LIST(SIMD_BOOL8X16_FUNCTION_ITEM)
|
|
#undef SIMD_BOOL8X16_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Bool16x8Defn::Methods[] = {
|
|
#define SIMD_BOOL16X8_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_bool16x8_##Name, Operands, 0, SimdBool16x8_##Name),
|
|
BOOL16X8_FUNCTION_LIST(SIMD_BOOL16X8_FUNCTION_ITEM)
|
|
#undef SIMD_BOOL16X8_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Bool32x4Defn::Methods[] = {
|
|
#define SIMD_BOOL32X4_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_INLINABLE_FN(#Name, js::simd_bool32x4_##Name, Operands, 0, SimdBool32x4_##Name),
|
|
BOOL32X4_FUNCTION_LIST(SIMD_BOOL32X4_FUNCTION_ITEM)
|
|
#undef SIMD_BOOL32X4_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
const JSFunctionSpec Bool64x2Defn::Methods[] = {
|
|
#define SIMD_BOOL64X2_FUNCTION_ITEM(Name, Func, Operands) \
|
|
JS_FN(#Name, js::simd_bool64x2_##Name, Operands, 0),
|
|
BOOL64X2_FUNCTION_LIST(SIMD_BOOL64X2_FUNCTION_ITEM)
|
|
#undef SIMD_BOOL64x2_FUNCTION_ITEM
|
|
JS_FS_END
|
|
};
|
|
|
|
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;
|
|
// Reassure typedMem() that we won't GC while holding onto the returned
|
|
// pointer, even though we could GC on every iteration of this loop
|
|
// (but it is safe because we re-fetch each time.)
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
reinterpret_cast<Elem*>(result->typedMem(nogc))[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>());
|
|
Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, descr, 0));
|
|
if (!result)
|
|
return false;
|
|
|
|
#define CASE_CALL_(Type) \
|
|
case SimdType::Type: return FillLanes< ::Type>(cx, result, args);
|
|
|
|
switch (descr->type()) {
|
|
FOR_EACH_SIMD(CASE_CALL_)
|
|
case SimdType::Count: break;
|
|
}
|
|
|
|
#undef CASE_CALL_
|
|
MOZ_CRASH("unexpected SIMD descriptor");
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// SIMD class
|
|
|
|
static const ClassOps SimdObjectClassOps = {
|
|
nullptr, /* addProperty */
|
|
nullptr, /* delProperty */
|
|
nullptr, /* getProperty */
|
|
nullptr, /* setProperty */
|
|
nullptr, /* enumerate */
|
|
SimdObject::resolve
|
|
};
|
|
|
|
const Class SimdObject::class_ = {
|
|
"SIMD",
|
|
JSCLASS_HAS_RESERVED_SLOTS(uint32_t(SimdType::Count)),
|
|
&SimdObjectClassOps
|
|
};
|
|
|
|
/* static */ 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 (!GlobalObject::getOrCreateTypedObjectModule(cx, global))
|
|
return false;
|
|
|
|
RootedObject globalSimdObject(cx);
|
|
RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
|
|
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;
|
|
}
|
|
|
|
static bool
|
|
CreateSimdType(JSContext* cx, Handle<GlobalObject*> global, HandlePropertyName stringRepr,
|
|
SimdType simdType, const JSFunctionSpec* methods)
|
|
{
|
|
RootedObject funcProto(cx, GlobalObject::getOrCreateFunctionPrototype(cx, global));
|
|
if (!funcProto)
|
|
return false;
|
|
|
|
// Create type constructor itself and initialize its reserved slots.
|
|
Rooted<SimdTypeDescr*> typeDescr(cx);
|
|
typeDescr = NewObjectWithGivenProto<SimdTypeDescr>(cx, funcProto, SingletonObject);
|
|
if (!typeDescr)
|
|
return false;
|
|
|
|
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(simdType)));
|
|
typeDescr->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(SimdTypeDescr::size(simdType)));
|
|
typeDescr->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(false));
|
|
typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPE, Int32Value(uint8_t(simdType)));
|
|
|
|
if (!CreateUserSizeAndAlignmentProperties(cx, typeDescr))
|
|
return false;
|
|
|
|
// Create prototype property, which inherits from Object.prototype.
|
|
RootedObject objProto(cx, GlobalObject::getOrCreateObjectPrototype(cx, global));
|
|
if (!objProto)
|
|
return false;
|
|
Rooted<TypedProto*> proto(cx);
|
|
proto = NewObjectWithGivenProto<TypedProto>(cx, objProto, SingletonObject);
|
|
if (!proto)
|
|
return false;
|
|
typeDescr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto));
|
|
|
|
// Link constructor to prototype and install properties.
|
|
if (!JS_DefineFunctions(cx, typeDescr, TypeDescriptorMethods))
|
|
return false;
|
|
|
|
if (!LinkConstructorAndPrototype(cx, typeDescr, proto) ||
|
|
!JS_DefineFunctions(cx, proto, SimdTypedObjectMethods))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Bind type descriptor to the global SIMD object
|
|
RootedObject globalSimdObject(cx, GlobalObject::getOrCreateSimdGlobalObject(cx, global));
|
|
MOZ_ASSERT(globalSimdObject);
|
|
|
|
RootedValue typeValue(cx, ObjectValue(*typeDescr));
|
|
if (!JS_DefineFunctions(cx, typeDescr, methods) ||
|
|
!DefineProperty(cx, globalSimdObject, stringRepr, typeValue, nullptr, nullptr,
|
|
JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
uint32_t slot = uint32_t(typeDescr->type());
|
|
MOZ_ASSERT(globalSimdObject->as<NativeObject>().getReservedSlot(slot).isUndefined());
|
|
globalSimdObject->as<NativeObject>().setReservedSlot(slot, ObjectValue(*typeDescr));
|
|
return !!typeDescr;
|
|
}
|
|
|
|
/* static */ bool
|
|
GlobalObject::initSimdType(JSContext* cx, Handle<GlobalObject*> global, SimdType simdType)
|
|
{
|
|
#define CREATE_(Type) \
|
|
case SimdType::Type: \
|
|
return CreateSimdType(cx, global, cx->names().Type, simdType, Type##Defn::Methods);
|
|
|
|
switch (simdType) {
|
|
FOR_EACH_SIMD(CREATE_)
|
|
case SimdType::Count: break;
|
|
}
|
|
MOZ_CRASH("unexpected simd type");
|
|
|
|
#undef CREATE_
|
|
}
|
|
|
|
/* static */ SimdTypeDescr*
|
|
GlobalObject::getOrCreateSimdTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
|
|
SimdType simdType)
|
|
{
|
|
MOZ_ASSERT(unsigned(simdType) < unsigned(SimdType::Count), "Invalid SIMD type");
|
|
|
|
RootedObject globalSimdObject(cx, GlobalObject::getOrCreateSimdGlobalObject(cx, global));
|
|
if (!globalSimdObject)
|
|
return nullptr;
|
|
|
|
uint32_t typeSlotIndex = uint32_t(simdType);
|
|
if (globalSimdObject->as<NativeObject>().getReservedSlot(typeSlotIndex).isUndefined() &&
|
|
!GlobalObject::initSimdType(cx, global, simdType))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const Value& slot = globalSimdObject->as<NativeObject>().getReservedSlot(typeSlotIndex);
|
|
MOZ_ASSERT(slot.isObject());
|
|
return &slot.toObject().as<SimdTypeDescr>();
|
|
}
|
|
|
|
bool
|
|
SimdObject::resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool* resolved)
|
|
{
|
|
*resolved = false;
|
|
if (!JSID_IS_ATOM(id))
|
|
return true;
|
|
JSAtom* str = JSID_TO_ATOM(id);
|
|
Rooted<GlobalObject*> global(cx, cx->global());
|
|
#define TRY_RESOLVE_(Type) \
|
|
if (str == cx->names().Type) { \
|
|
*resolved = CreateSimdType(cx, global, cx->names().Type, \
|
|
SimdType::Type, Type##Defn::Methods); \
|
|
return *resolved; \
|
|
}
|
|
FOR_EACH_SIMD(TRY_RESOLVE_)
|
|
#undef TRY_RESOLVE_
|
|
return true;
|
|
}
|
|
|
|
JSObject*
|
|
js::InitSimdClass(JSContext* cx, HandleObject obj)
|
|
{
|
|
Handle<GlobalObject*> global = obj.as<GlobalObject>();
|
|
return GlobalObject::getOrCreateSimdGlobalObject(cx, global);
|
|
}
|
|
|
|
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;
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* resultMem = reinterpret_cast<Elem*>(result->typedMem(nogc));
|
|
memcpy(resultMem, data, sizeof(Elem) * V::lanes);
|
|
return result;
|
|
}
|
|
|
|
#define InstantiateCreateSimd_(Type) \
|
|
template JSObject* js::CreateSimd<Type>(JSContext* cx, const Type::Elem* data);
|
|
|
|
FOR_EACH_SIMD(InstantiateCreateSimd_)
|
|
|
|
#undef InstantiateCreateSimd_
|
|
|
|
#undef FOR_EACH_SIMD
|
|
|
|
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 LogicalNot {
|
|
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. However, left-shifting an
|
|
// unsigned type is well-defined.
|
|
//
|
|
// In C++, shifting by an amount outside the range [0;N-1] is undefined
|
|
// behavior. SIMD.js reduces the shift amount modulo the number of bits in a
|
|
// lane and has defined behavior for all shift amounts.
|
|
template<typename T>
|
|
struct ShiftLeft {
|
|
static T apply(T v, int32_t bits) {
|
|
typedef typename mozilla::MakeUnsigned<T>::Type UnsignedT;
|
|
uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8);
|
|
return UnsignedT(v) << maskedBits;
|
|
}
|
|
};
|
|
template<typename T>
|
|
struct ShiftRightArithmetic {
|
|
static T apply(T v, int32_t bits) {
|
|
typedef typename mozilla::MakeSigned<T>::Type SignedT;
|
|
uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8);
|
|
return SignedT(v) >> maskedBits;
|
|
}
|
|
};
|
|
template<typename T>
|
|
struct ShiftRightLogical {
|
|
static T apply(T v, int32_t bits) {
|
|
typedef typename mozilla::MakeUnsigned<T>::Type UnsignedT;
|
|
uint32_t maskedBits = uint32_t(bits) % (sizeof(T) * 8);
|
|
return UnsignedT(v) >> maskedBits;
|
|
}
|
|
};
|
|
|
|
// 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)
|
|
return false;
|
|
args.rval().setObject(*obj);
|
|
return true;
|
|
}
|
|
|
|
// StoreResult can GC, and it is commonly used after pulling something out of a
|
|
// TypedObject:
|
|
//
|
|
// Elem result = op(TypedObjectMemory<Elem>(args[0]));
|
|
// StoreResult<Out>(..., result);
|
|
//
|
|
// The pointer extracted from the typed object in args[0] in the above example
|
|
// could be an interior pointer, and therefore be invalidated by GC.
|
|
// TypedObjectMemory() requires an assertion token to be passed in to prove
|
|
// that we won't GC, but the scope of eg an AutoCheckCannotGC RAII object
|
|
// extends to the end of its containing scope -- which would include the call
|
|
// to StoreResult, resulting in a rooting hazard.
|
|
//
|
|
// TypedObjectElemArray fixes this by wrapping the problematic pointer in a
|
|
// type, and the analysis is able to see that it is dead before calling
|
|
// StoreResult. (But if another GC called is made before the pointer is dead,
|
|
// it will correctly report a hazard.)
|
|
//
|
|
template <typename Elem>
|
|
class TypedObjectElemArray {
|
|
Elem* elements;
|
|
public:
|
|
explicit TypedObjectElemArray(HandleValue objVal) {
|
|
JS::AutoCheckCannotGC nogc;
|
|
elements = TypedObjectMemory<Elem*>(objVal, nogc);
|
|
}
|
|
Elem& operator[](int i) { return elements[i]; }
|
|
} JS_HAZ_GC_POINTER;
|
|
|
|
// 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];
|
|
TypedObjectElemArray<CoercionElem> val(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];
|
|
TypedObjectElemArray<CoercionElem> left(args[0]);
|
|
TypedObjectElemArray<CoercionElem> right(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]))
|
|
return ErrorBadArgs(cx);
|
|
|
|
unsigned lane;
|
|
if (!ArgumentToLaneIndex(cx, args[1], V::lanes, &lane))
|
|
return false;
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* vec = TypedObjectMemory<Elem*>(args[0], nogc);
|
|
Elem val = vec[lane];
|
|
args.rval().set(V::ToValue(val));
|
|
return true;
|
|
}
|
|
|
|
template<typename V>
|
|
static bool
|
|
AllTrue(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
typedef typename V::Elem Elem;
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() < 1 || !IsVectorObject<V>(args[0]))
|
|
return ErrorBadArgs(cx);
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* vec = TypedObjectMemory<Elem*>(args[0], nogc);
|
|
bool allTrue = true;
|
|
for (unsigned i = 0; allTrue && i < V::lanes; i++)
|
|
allTrue = vec[i];
|
|
|
|
args.rval().setBoolean(allTrue);
|
|
return true;
|
|
}
|
|
|
|
template<typename V>
|
|
static bool
|
|
AnyTrue(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
typedef typename V::Elem Elem;
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() < 1 || !IsVectorObject<V>(args[0]))
|
|
return ErrorBadArgs(cx);
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* vec = TypedObjectMemory<Elem*>(args[0], nogc);
|
|
bool anyTrue = false;
|
|
for (unsigned i = 0; !anyTrue && i < V::lanes; i++)
|
|
anyTrue = vec[i];
|
|
|
|
args.rval().setBoolean(anyTrue);
|
|
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);
|
|
|
|
unsigned lane;
|
|
if (!ArgumentToLaneIndex(cx, args[1], V::lanes, &lane))
|
|
return false;
|
|
|
|
Elem value;
|
|
if (!V::Cast(cx, args.get(2), &value))
|
|
return false;
|
|
|
|
TypedObjectElemArray<Elem> vec(args[0]);
|
|
Elem result[V::lanes];
|
|
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);
|
|
|
|
unsigned lanes[V::lanes];
|
|
for (unsigned i = 0; i < V::lanes; i++) {
|
|
if (!ArgumentToLaneIndex(cx, args[i + 1], V::lanes, &lanes[i]))
|
|
return false;
|
|
}
|
|
|
|
TypedObjectElemArray<Elem> val(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);
|
|
|
|
unsigned lanes[V::lanes];
|
|
for (unsigned i = 0; i < V::lanes; i++) {
|
|
if (!ArgumentToLaneIndex(cx, args[i + 2], 2 * V::lanes, &lanes[i]))
|
|
return false;
|
|
}
|
|
|
|
Elem result[V::lanes];
|
|
{
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* lhs = TypedObjectMemory<Elem*>(args[0], nogc);
|
|
Elem* rhs = TypedObjectMemory<Elem*>(args[1], nogc);
|
|
|
|
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);
|
|
|
|
if (!IsVectorObject<V>(args[0]))
|
|
return ErrorBadArgs(cx);
|
|
|
|
int32_t bits;
|
|
if (!ToInt32(cx, args[1], &bits))
|
|
return false;
|
|
|
|
TypedObjectElemArray<Elem> val(args[0]);
|
|
Elem result[V::lanes];
|
|
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];
|
|
TypedObjectElemArray<InElem> left(args[0]);
|
|
TypedObjectElemArray<InElem> right(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 {};
|
|
|
|
template<>
|
|
struct ThrowOnConvert<uint32_t, float> : public NeverThrow {};
|
|
|
|
// All int32 can be safely converted to doubles.
|
|
template<>
|
|
struct ThrowOnConvert<int32_t, double> : public NeverThrow {};
|
|
|
|
template<>
|
|
struct ThrowOnConvert<uint32_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) {
|
|
// Truncate to integer value before the range check.
|
|
double d = trunc(double(v));
|
|
// Arrange relations so NaN returns true (i.e., it throws a RangeError).
|
|
return !(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<double, uint32_t> : public ThrowIfNotInRange<double, uint32_t> {};
|
|
|
|
template<>
|
|
struct ThrowOnConvert<float, int32_t> : public ThrowIfNotInRange<float, int32_t> {};
|
|
|
|
template<>
|
|
struct ThrowOnConvert<float, uint32_t> : public ThrowIfNotInRange<float, uint32_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;
|
|
|
|
static_assert(!mozilla::IsSame<V,Vret>::value, "Can't convert SIMD type to itself");
|
|
static_assert(V::lanes == Vret::lanes, "Can only convert from same number of lanes");
|
|
static_assert(!mozilla::IsIntegral<Elem>::value || !mozilla::IsIntegral<RetElem>::value,
|
|
"Cannot convert between integer SIMD types");
|
|
|
|
CallArgs args = CallArgsFromVp(argc, vp);
|
|
if (args.length() != 1 || !IsVectorObject<V>(args[0]))
|
|
return ErrorBadArgs(cx);
|
|
|
|
TypedObjectElemArray<Elem> val(args[0]);
|
|
RetElem result[Vret::lanes];
|
|
for (unsigned i = 0; i < V::lanes; i++) {
|
|
if (ThrowOnConvert<Elem, RetElem>::value(val[i])) {
|
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SIMD_FAILED_CONVERSION);
|
|
return false;
|
|
}
|
|
result[i] = ConvertScalar<RetElem>(val[i]);
|
|
}
|
|
|
|
return StoreResult<Vret>(cx, args, result);
|
|
}
|
|
|
|
template<typename V, typename Vret>
|
|
static bool
|
|
FuncConvertBits(JSContext* cx, unsigned argc, Value* vp)
|
|
{
|
|
typedef typename V::Elem Elem;
|
|
typedef typename Vret::Elem RetElem;
|
|
|
|
static_assert(!mozilla::IsSame<V, Vret>::value, "Can't convert SIMD type to itself");
|
|
static_assert(V::lanes * sizeof(Elem) == Vret::lanes * sizeof(RetElem),
|
|
"Can only bitcast from the same number of bits");
|
|
|
|
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];
|
|
{
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
memcpy(copy, TypedObjectMemory<RetElem*>(args[0], nogc), 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);
|
|
}
|
|
|
|
TypedObjectElemArray<MaskTypeElem> val(args[0]);
|
|
TypedObjectElemArray<MaskTypeElem> tv(args[1]);
|
|
TypedObjectElemArray<MaskTypeElem> fv(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);
|
|
}
|
|
|
|
TypedObjectElemArray<MaskTypeElem> mask(args[0]);
|
|
TypedObjectElemArray<Elem> tv(args[1]);
|
|
TypedObjectElemArray<Elem> fv(args[2]);
|
|
|
|
Elem result[V::lanes];
|
|
for (unsigned i = 0; i < V::lanes; i++)
|
|
result[i] = mask[i] ? tv[i] : fv[i];
|
|
|
|
return StoreResult<V>(cx, args, result);
|
|
}
|
|
|
|
// Extract an integer lane index from a function argument.
|
|
//
|
|
// Register an exception and return false if the argument is not suitable.
|
|
static bool
|
|
ArgumentToLaneIndex(JSContext* cx, JS::HandleValue v, unsigned limit, unsigned* lane)
|
|
{
|
|
uint64_t arg;
|
|
if (!ToIntegerIndex(cx, v, &arg))
|
|
return false;
|
|
if (arg >= limit)
|
|
return ErrorBadIndex(cx);
|
|
|
|
*lane = unsigned(arg);
|
|
return true;
|
|
}
|
|
|
|
// Look for arguments (ta, idx) where ta is a TypedArray and idx is a
|
|
// non-negative integer.
|
|
// Check that accessBytes can be accessed starting from index idx in the array.
|
|
// Return the array handle in typedArray and idx converted to a byte offset in byteStart.
|
|
static bool
|
|
TypedArrayFromArgs(JSContext* cx, const CallArgs& args, uint32_t accessBytes,
|
|
MutableHandleObject typedArray, size_t* byteStart)
|
|
{
|
|
if (!args[0].isObject())
|
|
return ErrorBadArgs(cx);
|
|
|
|
JSObject& argobj = args[0].toObject();
|
|
if (!argobj.is<TypedArrayObject>())
|
|
return ErrorBadArgs(cx);
|
|
|
|
typedArray.set(&argobj);
|
|
|
|
uint64_t index;
|
|
if (!ToIntegerIndex(cx, args[1], &index))
|
|
return false;
|
|
|
|
// Do the range check in 64 bits even when size_t is 32 bits.
|
|
// This can't overflow because index <= 2^53.
|
|
uint64_t bytes = index * typedArray->as<TypedArrayObject>().bytesPerElement();
|
|
// Keep in sync with AsmJS OnOutOfBounds function.
|
|
if ((bytes + accessBytes) > typedArray->as<TypedArrayObject>().byteLength())
|
|
return ErrorBadIndex(cx);
|
|
|
|
*byteStart = bytes;
|
|
|
|
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);
|
|
|
|
size_t byteStart;
|
|
RootedObject typedArray(cx);
|
|
if (!TypedArrayFromArgs(cx, args, sizeof(Elem) * NumElem, &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;
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
SharedMem<Elem*> src =
|
|
typedArray->as<TypedArrayObject>().viewDataEither().addBytes(byteStart).cast<Elem*>();
|
|
Elem* dst = reinterpret_cast<Elem*>(result->typedMem(nogc));
|
|
jit::AtomicOperations::podCopySafeWhenRacy(SharedMem<Elem*>::unshared(dst), src, 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);
|
|
|
|
size_t byteStart;
|
|
RootedObject typedArray(cx);
|
|
if (!TypedArrayFromArgs(cx, args, sizeof(Elem) * NumElem, &typedArray, &byteStart))
|
|
return false;
|
|
|
|
if (!IsVectorObject<V>(args[2]))
|
|
return ErrorBadArgs(cx);
|
|
|
|
JS::AutoCheckCannotGC nogc(cx);
|
|
Elem* src = TypedObjectMemory<Elem*>(args[2], nogc);
|
|
SharedMem<Elem*> dst =
|
|
typedArray->as<TypedArrayObject>().viewDataEither().addBytes(byteStart).cast<Elem*>();
|
|
js::jit::AtomicOperations::podCopySafeWhenRacy(dst, SharedMem<Elem*>::unshared(src), 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
|
|
|
|
#define DEFINE_SIMD_UINT8X16_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_uint8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
UINT8X16_FUNCTION_LIST(DEFINE_SIMD_UINT8X16_FUNCTION)
|
|
#undef DEFINE_SIMD_UINT8X16_FUNCTION
|
|
|
|
#define DEFINE_SIMD_UINT16X8_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_uint16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
UINT16X8_FUNCTION_LIST(DEFINE_SIMD_UINT16X8_FUNCTION)
|
|
#undef DEFINE_SIMD_UINT16X8_FUNCTION
|
|
|
|
#define DEFINE_SIMD_UINT32X4_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_uint32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
UINT32X4_FUNCTION_LIST(DEFINE_SIMD_UINT32X4_FUNCTION)
|
|
#undef DEFINE_SIMD_UINT32X4_FUNCTION
|
|
|
|
#define DEFINE_SIMD_BOOL8X16_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_bool8x16_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
|
|
BOOL8X16_FUNCTION_LIST(DEFINE_SIMD_BOOL8X16_FUNCTION)
|
|
#undef DEFINE_SIMD_BOOL8X16_FUNCTION
|
|
|
|
#define DEFINE_SIMD_BOOL16X8_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_bool16x8_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
BOOL16X8_FUNCTION_LIST(DEFINE_SIMD_BOOL16X8_FUNCTION)
|
|
#undef DEFINE_SIMD_BOOL16X8_FUNCTION
|
|
|
|
#define DEFINE_SIMD_BOOL32X4_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_bool32x4_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
BOOL32X4_FUNCTION_LIST(DEFINE_SIMD_BOOL32X4_FUNCTION)
|
|
#undef DEFINE_SIMD_BOOL32X4_FUNCTION
|
|
|
|
#define DEFINE_SIMD_BOOL64X2_FUNCTION(Name, Func, Operands) \
|
|
bool \
|
|
js::simd_bool64x2_##Name(JSContext* cx, unsigned argc, Value* vp) \
|
|
{ \
|
|
return Func(cx, argc, vp); \
|
|
}
|
|
BOOL64X2_FUNCTION_LIST(DEFINE_SIMD_BOOL64X2_FUNCTION)
|
|
#undef DEFINE_SIMD_BOOL64X2_FUNCTION
|