/* -*- 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/. */ #ifndef vm_TypedArrayCommon_h #define vm_TypedArrayCommon_h /* Utilities and common inline code for TypedArray */ #include "mozilla/Assertions.h" #include "mozilla/FloatingPoint.h" #include "mozilla/PodOperations.h" #include "jsarray.h" #include "jscntxt.h" #include "jsnum.h" #include "jit/AtomicOperations.h" #include "js/Conversions.h" #include "js/Value.h" #include "vm/NativeObject.h" #include "vm/TypedArrayObject.h" namespace js { // ValueIsLength happens not to be according to ES6, which mandates // the use of ToLength, which in turn includes ToNumber, ToInteger, // and clamping. ValueIsLength is used in the current TypedArray code // but will disappear when that code is made spec-compliant. inline bool ValueIsLength(const Value& v, uint32_t* len) { if (v.isInt32()) { int32_t i = v.toInt32(); if (i < 0) return false; *len = i; return true; } if (v.isDouble()) { double d = v.toDouble(); if (mozilla::IsNaN(d)) return false; uint32_t length = uint32_t(d); if (d != double(length)) return false; *len = length; return true; } return false; } template inline To ConvertNumber(From src); template<> inline int8_t ConvertNumber(float src) { return JS::ToInt8(src); } template<> inline uint8_t ConvertNumber(float src) { return JS::ToUint8(src); } template<> inline uint8_clamped ConvertNumber(float src) { return uint8_clamped(src); } template<> inline int16_t ConvertNumber(float src) { return JS::ToInt16(src); } template<> inline uint16_t ConvertNumber(float src) { return JS::ToUint16(src); } template<> inline int32_t ConvertNumber(float src) { return JS::ToInt32(src); } template<> inline uint32_t ConvertNumber(float src) { return JS::ToUint32(src); } template<> inline int8_t ConvertNumber(double src) { return JS::ToInt8(src); } template<> inline uint8_t ConvertNumber(double src) { return JS::ToUint8(src); } template<> inline uint8_clamped ConvertNumber(double src) { return uint8_clamped(src); } template<> inline int16_t ConvertNumber(double src) { return JS::ToInt16(src); } template<> inline uint16_t ConvertNumber(double src) { return JS::ToUint16(src); } template<> inline int32_t ConvertNumber(double src) { return JS::ToInt32(src); } template<> inline uint32_t ConvertNumber(double src) { return JS::ToUint32(src); } template inline To ConvertNumber(From src) { static_assert(!mozilla::IsFloatingPoint::value || (mozilla::IsFloatingPoint::value && mozilla::IsFloatingPoint::value), "conversion from floating point to int should have been handled by " "specializations above"); return To(src); } template struct TypeIDOfType; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Int8; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint8; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Int16; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint16; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Int32; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint32; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Float32; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Float64; }; template<> struct TypeIDOfType { static const Scalar::Type id = Scalar::Uint8Clamped; }; class SharedOps { public: template static T load(SharedMem addr) { return js::jit::AtomicOperations::loadSafeWhenRacy(addr); } template static void store(SharedMem addr, T value) { js::jit::AtomicOperations::storeSafeWhenRacy(addr, value); } template static void memcpy(SharedMem dest, SharedMem src, size_t size) { js::jit::AtomicOperations::memcpySafeWhenRacy(dest, src, size); } template static void memmove(SharedMem dest, SharedMem src, size_t size) { js::jit::AtomicOperations::memmoveSafeWhenRacy(dest, src, size); } template static void podCopy(SharedMem dest, SharedMem src, size_t nelem) { js::jit::AtomicOperations::podCopySafeWhenRacy(dest, src, nelem); } template static void podMove(SharedMem dest, SharedMem src, size_t nelem) { js::jit::AtomicOperations::podMoveSafeWhenRacy(dest, src, nelem); } static SharedMem extract(TypedArrayObject* obj) { return obj->viewDataEither(); } }; class UnsharedOps { public: template static T load(SharedMem addr) { return *addr.unwrapUnshared(); } template static void store(SharedMem addr, T value) { *addr.unwrapUnshared() = value; } template static void memcpy(SharedMem dest, SharedMem src, size_t size) { ::memcpy(dest.unwrapUnshared(), src.unwrapUnshared(), size); } template static void memmove(SharedMem dest, SharedMem src, size_t size) { ::memmove(dest.unwrapUnshared(), src.unwrapUnshared(), size); } template static void podCopy(SharedMem dest, SharedMem src, size_t nelem) { mozilla::PodCopy(dest.unwrapUnshared(), src.unwrapUnshared(), nelem); } template static void podMove(SharedMem dest, SharedMem src, size_t nelem) { mozilla::PodMove(dest.unwrapUnshared(), src.unwrapUnshared(), nelem); } static SharedMem extract(TypedArrayObject* obj) { return SharedMem::unshared(obj->viewDataUnshared()); } }; template class ElementSpecific { typedef typename SpecificArray::ElementType T; typedef typename SpecificArray::SomeTypedArray SomeTypedArray; public: /* * Copy |source|'s elements into |target|, starting at |target[offset]|. * Act as if the assignments occurred from a fresh copy of |source|, in * case the two memory ranges overlap. */ static bool setFromTypedArray(JSContext* cx, Handle target, HandleObject source, uint32_t offset) { MOZ_ASSERT(SpecificArray::ArrayTypeID() == target->type(), "calling wrong setFromTypedArray specialization"); MOZ_ASSERT(offset <= target->length()); MOZ_ASSERT(source->as().length() <= target->length() - offset); if (source->is()) { Rooted src(cx, source.as()); if (SomeTypedArray::sameBuffer(target, src)) return setFromOverlappingTypedArray(cx, target, src, offset); } SharedMem dest = target->template as().viewDataEither().template cast() + offset; uint32_t count = source->as().length(); if (source->as().type() == target->type()) { Ops::podCopy(dest, source->as().viewDataEither().template cast(), count); return true; } // Inhibit unaligned accesses on ARM (bug 1097253, a compiler bug). #ifdef __arm__ # define JS_VOLATILE_ARM volatile #else # define JS_VOLATILE_ARM #endif SharedMem data = Ops::extract(source.as()); switch (source->as().type()) { case Scalar::Int8: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Uint8: case Scalar::Uint8Clamped: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Int16: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Uint16: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Int32: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Uint32: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Float32: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } case Scalar::Float64: { SharedMem src = data.cast(); for (uint32_t i = 0; i < count; ++i) Ops::store(dest++, ConvertNumber(Ops::load(src++))); break; } default: MOZ_CRASH("setFromTypedArray with a typed array with bogus type"); } #undef JS_VOLATILE_ARM return true; } /* * Copy |source[0]| to |source[len]| (exclusive) elements into the typed * array |target|, starting at index |offset|. |source| must not be a * typed array. */ static bool setFromNonTypedArray(JSContext* cx, Handle target, HandleObject source, uint32_t len, uint32_t offset = 0) { MOZ_ASSERT(target->type() == SpecificArray::ArrayTypeID(), "target type and NativeType must match"); MOZ_ASSERT(!source->is(), "use setFromTypedArray instead of this method"); uint32_t i = 0; if (source->isNative()) { // Attempt fast-path infallible conversion of dense elements up to // the first potentially side-effectful lookup or conversion. uint32_t bound = Min(source->as().getDenseInitializedLength(), len); SharedMem dest = target->template as().viewDataEither().template cast() + offset; MOZ_ASSERT(!canConvertInfallibly(MagicValue(JS_ELEMENTS_HOLE)), "the following loop must abort on holes"); const Value* srcValues = source->as().getDenseElements(); for (; i < bound; i++) { if (!canConvertInfallibly(srcValues[i])) break; Ops::store(dest + i, infallibleValueToNative(srcValues[i])); } if (i == len) return true; } // Convert and copy any remaining elements generically. RootedValue v(cx); for (; i < len; i++) { if (!GetElement(cx, source, source, i, &v)) return false; T n; if (!valueToNative(cx, v, &n)) return false; len = Min(len, target->length()); if (i >= len) break; // Compute every iteration in case getElement/valueToNative is wacky. SharedMem dest = target->template as().viewDataEither().template cast() + offset + i; Ops::store(dest, n); } return true; } /* * Copy |source| into the typed array |target|. */ static bool initFromIterablePackedArray(JSContext* cx, Handle target, HandleArrayObject source) { MOZ_ASSERT(target->type() == SpecificArray::ArrayTypeID(), "target type and NativeType must match"); MOZ_ASSERT(IsPackedArray(source), "source array must be packed"); MOZ_ASSERT(source->getDenseInitializedLength() <= target->length()); uint32_t len = source->getDenseInitializedLength(); uint32_t i = 0; // Attempt fast-path infallible conversion of dense elements up to the // first potentially side-effectful conversion. SharedMem dest = target->template as().viewDataEither().template cast(); const Value* srcValues = source->getDenseElements(); for (; i < len; i++) { if (!canConvertInfallibly(srcValues[i])) break; Ops::store(dest + i, infallibleValueToNative(srcValues[i])); } if (i == len) return true; // Convert any remaining elements by first collecting them into a // temporary list, and then copying them into the typed array. AutoValueVector values(cx); if (!values.append(srcValues + i, len - i)) return false; RootedValue v(cx); for (uint32_t j = 0; j < values.length(); i++, j++) { v = values[j]; T n; if (!valueToNative(cx, v, &n)) return false; // |target| is a newly allocated typed array and not yet visible to // content script, so valueToNative can't detach the underlying // buffer. MOZ_ASSERT(i < target->length()); // Compute every iteration in case GC moves the data. SharedMem newDest = target->template as().viewDataEither().template cast(); Ops::store(newDest + i, n); } return true; } private: static bool setFromOverlappingTypedArray(JSContext* cx, Handle target, Handle source, uint32_t offset) { MOZ_ASSERT(SpecificArray::ArrayTypeID() == target->type(), "calling wrong setFromTypedArray specialization"); MOZ_ASSERT(SomeTypedArray::sameBuffer(target, source), "the provided arrays don't actually overlap, so it's " "undesirable to use this method"); MOZ_ASSERT(offset <= target->length()); MOZ_ASSERT(source->length() <= target->length() - offset); SharedMem dest = target->template as().viewDataEither().template cast() + offset; uint32_t len = source->length(); if (source->type() == target->type()) { SharedMem src = source->template as().viewDataEither().template cast(); Ops::podMove(dest, src, len); return true; } // Copy |source| in case it overlaps the target elements being set. size_t sourceByteLen = len * source->bytesPerElement(); void* data = target->zone()->template pod_malloc(sourceByteLen); if (!data) return false; Ops::memcpy(SharedMem::unshared(data), source->template as().viewDataEither(), sourceByteLen); switch (source->type()) { case Scalar::Int8: { int8_t* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Uint8: case Scalar::Uint8Clamped: { uint8_t* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Int16: { int16_t* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Uint16: { uint16_t* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Int32: { int32_t* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Uint32: { uint32_t* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Float32: { float* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } case Scalar::Float64: { double* src = static_cast(data); for (uint32_t i = 0; i < len; ++i) Ops::store(dest++, ConvertNumber(*src++)); break; } default: MOZ_CRASH("setFromOverlappingTypedArray with a typed array with bogus type"); } js_free(data); return true; } static bool canConvertInfallibly(const Value& v) { return v.isNumber() || v.isBoolean() || v.isNull() || v.isUndefined(); } static T infallibleValueToNative(const Value& v) { if (v.isInt32()) return T(v.toInt32()); if (v.isDouble()) return doubleToNative(v.toDouble()); if (v.isBoolean()) return T(v.toBoolean()); if (v.isNull()) return T(0); MOZ_ASSERT(v.isUndefined()); return TypeIsFloatingPoint() ? T(JS::GenericNaN()) : T(0); } static bool valueToNative(JSContext* cx, HandleValue v, T* result) { MOZ_ASSERT(!v.isMagic()); if (MOZ_LIKELY(canConvertInfallibly(v))) { *result = infallibleValueToNative(v); return true; } double d; MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol()); if (!(v.isString() ? StringToNumber(cx, v.toString(), &d) : ToNumber(cx, v, &d))) return false; *result = doubleToNative(d); return true; } static T doubleToNative(double d) { if (TypeIsFloatingPoint()) { #ifdef JS_MORE_DETERMINISTIC // The JS spec doesn't distinguish among different NaN values, and // it deliberately doesn't specify the bit pattern written to a // typed array when NaN is written into it. This bit-pattern // inconsistency could confuse deterministic testing, so always // canonicalize NaN values in more-deterministic builds. d = JS::CanonicalizeNaN(d); #endif return T(d); } if (MOZ_UNLIKELY(mozilla::IsNaN(d))) return T(0); if (SpecificArray::ArrayTypeID() == Scalar::Uint8Clamped) return T(d); if (TypeIsUnsigned()) return T(JS::ToUint32(d)); return T(JS::ToInt32(d)); } }; template class TypedArrayMethods { static_assert(mozilla::IsSame::value, "methods must be shared/unshared-specific, not " "element-type-specific"); typedef typename SomeTypedArray::BufferType BufferType; typedef typename SomeTypedArray::template OfType::Type Int8ArrayType; typedef typename SomeTypedArray::template OfType::Type Uint8ArrayType; typedef typename SomeTypedArray::template OfType::Type Int16ArrayType; typedef typename SomeTypedArray::template OfType::Type Uint16ArrayType; typedef typename SomeTypedArray::template OfType::Type Int32ArrayType; typedef typename SomeTypedArray::template OfType::Type Uint32ArrayType; typedef typename SomeTypedArray::template OfType::Type Float32ArrayType; typedef typename SomeTypedArray::template OfType::Type Float64ArrayType; typedef typename SomeTypedArray::template OfType::Type Uint8ClampedArrayType; public: /* set(array[, offset]) */ static bool set(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(SomeTypedArray::is(args.thisv())); Rooted target(cx, &args.thisv().toObject().as()); // The first argument must be either a typed array or arraylike. if (args.length() == 0 || !args[0].isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS); return false; } int32_t offset = 0; if (args.length() > 1) { if (!ToInt32(cx, args[1], &offset)) return false; if (offset < 0 || uint32_t(offset) > target->length()) { // the given offset is bogus JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); return false; } } RootedObject arg0(cx, &args[0].toObject()); if (arg0->is()) { if (arg0->as().length() > target->length() - offset) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } if (!setFromTypedArray(cx, target, arg0, offset)) return false; } else { uint32_t len; if (!GetLengthProperty(cx, arg0, &len)) return false; if (uint32_t(offset) > target->length() || len > target->length() - offset) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } if (!setFromNonTypedArray(cx, target, arg0, len, offset)) return false; } args.rval().setUndefined(); return true; } static bool setFromTypedArray(JSContext* cx, Handle target, HandleObject source, uint32_t offset = 0) { MOZ_ASSERT(source->is(), "use setFromNonTypedArray"); bool isShared = target->isSharedMemory() || source->as().isSharedMemory(); switch (target->type()) { case Scalar::Int8: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Uint8: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Int16: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Uint16: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Int32: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Uint32: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Float32: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Float64: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Uint8Clamped: if (isShared) return ElementSpecific::setFromTypedArray(cx, target, source, offset); return ElementSpecific::setFromTypedArray(cx, target, source, offset); case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("nonsense target element type"); } static bool setFromNonTypedArray(JSContext* cx, Handle target, HandleObject source, uint32_t len, uint32_t offset = 0) { MOZ_ASSERT(!source->is(), "use setFromTypedArray"); bool isShared = target->isSharedMemory(); switch (target->type()) { case Scalar::Int8: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Uint8: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Int16: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Uint16: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Int32: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Uint32: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Float32: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Float64: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Uint8Clamped: if (isShared) return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("bad target array type"); } static bool initFromIterablePackedArray(JSContext* cx, Handle target, HandleArrayObject source) { bool isShared = target->isSharedMemory(); switch (target->type()) { case Scalar::Int8: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Uint8: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Int16: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Uint16: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Int32: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Uint32: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Float32: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Float64: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Uint8Clamped: if (isShared) return ElementSpecific::initFromIterablePackedArray(cx, target, source); return ElementSpecific::initFromIterablePackedArray(cx, target, source); case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: case Scalar::MaxTypedArrayViewType: break; } MOZ_CRASH("bad target array type"); } }; } // namespace js #endif // vm_TypedArrayCommon_h