// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include "qv4typedarray_p.h"
#include "qv4arrayiterator_p.h"
#include "qv4arraybuffer_p.h"
#include "qv4symbol_p.h"
#include "qv4runtime_p.h"
#include <QtCore/qatomic.h>

#include <cmath>

using namespace QV4;

DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayCtor);
DEFINE_OBJECT_VTABLE(IntrinsicTypedArrayPrototype);
DEFINE_OBJECT_VTABLE(TypedArrayCtor);
DEFINE_OBJECT_VTABLE(TypedArrayPrototype);
DEFINE_OBJECT_VTABLE(TypedArray);

Q_STATIC_ASSERT((int)ExecutionEngine::NTypedArrayTypes == (int)NTypedArrayTypes);

static inline int toInt32(Value v)
{
    Q_ASSERT(v.isNumber());
    if (v.isInteger())
        return v.integerValue();
    return QJSNumberCoercion::toInteger(v.doubleValue());
}

static inline double toDouble(Value v)
{
    Q_ASSERT(v.isNumber());
    if (v.isInteger())
        return v.integerValue();
    return v.doubleValue();
}

struct ClampedUInt8 {
    quint8 c;
};

template <typename T>
ReturnedValue typeToValue(T t) {
    return Encode(t);
}

template <>
ReturnedValue typeToValue(ClampedUInt8 t) {
    return Encode(t.c);
}

template <typename T>
T valueToType(Value value)
{
    Q_ASSERT(value.isNumber());
    int n = toInt32(value);
    return static_cast<T>(n);
}

template <>
ClampedUInt8 valueToType(Value value)
{
    Q_ASSERT(value.isNumber());
    if (value.isInteger())
        return { static_cast<quint8>(qBound(0, value.integerValue(), 255)) };
    Q_ASSERT(value.isDouble());
    double d = value.doubleValue();
    // ### is there a way to optimise this?
    if (d <= 0 || std::isnan(d))
        return { 0 };
    if (d >= 255)
        return { 255 };
    double f = std::floor(d);
    if (f + 0.5 < d)
        return { (quint8)(f + 1) };
    if (d < f + 0.5)
        return { (quint8)(f) };
    if (int(f) % 2)
        // odd number
        return { (quint8)(f + 1) };
    return { (quint8)(f) };
}

template <>
float valueToType(Value value)
{
    Q_ASSERT(value.isNumber());
    double d = toDouble(value);
    return static_cast<float>(d);
}

template <>
double valueToType(Value value)
{
    Q_ASSERT(value.isNumber());
    return toDouble(value);
}

template <typename T>
ReturnedValue read(const char *data) {
    return typeToValue(*reinterpret_cast<const T *>(data));
}
template <typename T>
void write(char *data, Value value)
{
    *reinterpret_cast<T *>(data) = valueToType<T>(value);
}

template <typename T>
ReturnedValue atomicAdd(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    value = QAtomicOps<T>::fetchAndAddOrdered(*mem, value);
    return typeToValue(value);
}

template <typename T>
ReturnedValue atomicAnd(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    value = QAtomicOps<T>::fetchAndAndOrdered(*mem, value);
    return typeToValue(value);
}

template <typename T>
ReturnedValue atomicExchange(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    value = QAtomicOps<T>::fetchAndStoreOrdered(*mem, value);
    return typeToValue(value);
}

template <typename T>
ReturnedValue atomicOr(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    value = QAtomicOps<T>::fetchAndOrOrdered(*mem, value);
    return typeToValue(value);
}

template <typename T>
ReturnedValue atomicSub(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    value = QAtomicOps<T>::fetchAndSubOrdered(*mem, value);
    return typeToValue(value);
}

template <typename T>
ReturnedValue atomicXor(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    value = QAtomicOps<T>::fetchAndXorOrdered(*mem, value);
    return typeToValue(value);
}

template <typename T>
ReturnedValue atomicCompareExchange(char *data, Value expected, Value v)
{
    T value = valueToType<T>(v);
    T exp = valueToType<T>(expected);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    T old;
    QAtomicOps<T>::testAndSetOrdered(*mem, exp, value, &old);
    return typeToValue(old);
}

template <typename T>
ReturnedValue atomicLoad(char *data)
{
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    T val = QAtomicOps<T>::loadRelaxed(*mem);
    return typeToValue(val);
}

template <typename T>
ReturnedValue atomicStore(char *data, Value v)
{
    T value = valueToType<T>(v);
    typename QAtomicOps<T>::Type *mem = reinterpret_cast<typename QAtomicOps<T>::Type *>(data);
    QAtomicOps<T>::storeRelaxed(*mem, value);
    return typeToValue(value);
}


template<typename T>
constexpr TypedArrayOperations TypedArrayOperations::create(const char *name)
{
    return { sizeof(T),
             name,
             ::read<T>,
             ::write<T>,
             { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr },
             nullptr,
             nullptr,
             nullptr
    };
}

template<typename T>
constexpr TypedArrayOperations TypedArrayOperations::createWithAtomics(const char *name)
{
    return { sizeof(T),
             name,
             ::read<T>,
             ::write<T>,
             { ::atomicAdd<T>, ::atomicAnd<T>, ::atomicExchange<T>, ::atomicOr<T>, ::atomicSub<T>, ::atomicXor<T> },
             ::atomicCompareExchange<T>,
             ::atomicLoad<T>,
             ::atomicStore<T>
    };
}

const TypedArrayOperations operations[NTypedArrayTypes] = {
#ifdef Q_ATOMIC_INT8_IS_SUPPORTED
    TypedArrayOperations::createWithAtomics<qint8>("Int8Array"),
    TypedArrayOperations::createWithAtomics<quint8>("Uint8Array"),
#else
    TypedArrayOperations::create<qint8>("Int8Array"),
    TypedArrayOperations::create<quint8>("Uint8Array"),
#endif
    TypedArrayOperations::createWithAtomics<qint16>("Int16Array"),
    TypedArrayOperations::createWithAtomics<quint16>("Uint16Array"),
    TypedArrayOperations::createWithAtomics<qint32>("Int32Array"),
    TypedArrayOperations::createWithAtomics<quint32>("Uint32Array"),
    TypedArrayOperations::create<ClampedUInt8>("Uint8ClampedArray"),
    TypedArrayOperations::create<float>("Float32Array"),
    TypedArrayOperations::create<double>("Float64Array")
};


void Heap::TypedArrayCtor::init(QV4::ExecutionEngine *engine, TypedArray::Type t)
{
    Heap::FunctionObject::init(engine, QLatin1String(operations[t].name));
    type = t;
}

ReturnedValue TypedArrayCtor::virtualCallAsConstructor(const FunctionObject *f, const Value *argv, int argc, const Value *newTarget)
{
    Scope scope(f->engine());
    const TypedArrayCtor *that = static_cast<const TypedArrayCtor *>(f);

    auto updateProto = [=](Scope &scope, Scoped<TypedArray> &a) {
        if (newTarget->heapObject() != f->heapObject() && newTarget->isFunctionObject()) {
            const FunctionObject *nt = static_cast<const FunctionObject *>(newTarget);
            ScopedObject o(scope, nt->protoProperty());
            if (o)
                a->setPrototypeOf(o);
        }
    };

    if (!argc || !argv[0].isObject()) {
        // ECMA 6 22.2.1.1
        const double l = argc ? argv[0].toInteger() : 0;
        if (scope.hasException())
            return Encode::undefined();
        if (l < 0 || l > std::numeric_limits<int>::max())
            return scope.engine->throwRangeError(QLatin1String("Index out of range."));

        const double byteLength = l * operations[that->d()->type].bytesPerElement;

        // TODO: This is an artificial restriction due to the fact that we store the byteLength in
        //       uint below. We should allow up to INT_MAX elements of any size.
        if (byteLength > std::numeric_limits<uint>::max())
            return scope.engine->throwRangeError(QLatin1String("Index out of range."));

        Scoped<ArrayBuffer> buffer(scope, scope.engine->newArrayBuffer(size_t(byteLength)));
        if (scope.hasException())
            return Encode::undefined();

        Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type));
        array->d()->buffer.set(scope.engine, buffer->d());
        array->d()->byteLength = byteLength;
        array->d()->byteOffset = 0;

        updateProto(scope, array);
        return array.asReturnedValue();
    }
    Scoped<TypedArray> typedArray(scope, argc ? argv[0] : Value::undefinedValue());
    if (!!typedArray) {
        // ECMA 6 22.2.1.2
        Scoped<ArrayBuffer> buffer(scope, typedArray->d()->buffer);
        if (!buffer || buffer->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        uint srcElementSize = typedArray->bytesPerElement();
        uint destElementSize = operations[that->d()->type].bytesPerElement;
        uint byteLength = typedArray->byteLength();
        uint destByteLength = byteLength*destElementSize/srcElementSize;

        Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(destByteLength));
        if (scope.hasException())
            return Encode::undefined();

        Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type));
        array->d()->buffer.set(scope.engine, newBuffer->d());
        array->d()->byteLength = destByteLength;
        array->d()->byteOffset = 0;

        const char *src = buffer->constArrayData() + typedArray->byteOffset();
        char *dest = newBuffer->arrayData();

        // check if src and new type have the same size. In that case we can simply memcpy the data
        if (srcElementSize == destElementSize) {
            memcpy(dest, src, byteLength);
        } else {
            // not same size, we need to loop
            uint l = typedArray->length();
            TypedArrayOperations::Read read = typedArray->d()->type->read;
            TypedArrayOperations::Write write =array->d()->type->write;
            for (uint i = 0; i < l; ++i) {
                Value val;
                val.setRawValue(read(src + i*srcElementSize));
                write(dest + i*destElementSize, val);
            }
        }

        updateProto(scope, array);
        return array.asReturnedValue();
    }
    Scoped<ArrayBuffer> buffer(scope, argc ? argv[0] : Value::undefinedValue());
    if (!!buffer) {
        // ECMA 6 22.2.1.4

        double dbyteOffset = argc > 1 ? argv[1].toInteger() : 0;

        if (buffer->hasDetachedArrayData())
            return scope.engine->throwTypeError();

        uint byteOffset = (uint)dbyteOffset;
        uint elementSize = operations[that->d()->type].bytesPerElement;
        if (dbyteOffset < 0 || (byteOffset % elementSize) || dbyteOffset > buffer->arrayDataLength())
            return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid byteOffset"));

        uint byteLength;
        if (argc < 3 || argv[2].isUndefined()) {
            byteLength = buffer->arrayDataLength() - byteOffset;
            if (buffer->arrayDataLength() < byteOffset || byteLength % elementSize)
                return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length"));
        } else {
            double l = qBound(0., argv[2].toInteger(), (double)UINT_MAX);
            if (scope.hasException())
                return Encode::undefined();
            if (buffer->hasDetachedArrayData())
                return scope.engine->throwTypeError();
            l *= elementSize;
            if (buffer->arrayDataLength() - byteOffset < l)
                return scope.engine->throwRangeError(QStringLiteral("new TypedArray: invalid length"));
            byteLength = (uint)l;
        }

        Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type));
        array->d()->buffer.set(scope.engine, buffer->d());
        array->d()->byteLength = byteLength;
        array->d()->byteOffset = byteOffset;

        updateProto(scope, array);
        return array.asReturnedValue();
    }

    // ECMA 6 22.2.1.3

    ScopedObject o(scope, argc ? argv[0] : Value::undefinedValue());
    uint l = (uint) qBound(0., ScopedValue(scope, o->get(scope.engine->id_length()))->toInteger(), (double)UINT_MAX);
    if (scope.hasException())
        return scope.engine->throwTypeError();

    uint elementSize = operations[that->d()->type].bytesPerElement;
    size_t bufferSize;
    if (qMulOverflow(size_t(l), size_t(elementSize), &bufferSize))
        return scope.engine->throwRangeError(QLatin1String("new TypedArray: invalid length"));
    Scoped<ArrayBuffer> newBuffer(scope, scope.engine->newArrayBuffer(bufferSize));
    if (scope.hasException())
        return Encode::undefined();

    Scoped<TypedArray> array(scope, TypedArray::create(scope.engine, that->d()->type));
    array->d()->buffer.set(scope.engine, newBuffer->d());
    array->d()->byteLength = l * elementSize;
    array->d()->byteOffset = 0;

    uint idx = 0;
    char *b = newBuffer->arrayData();
    ScopedValue val(scope);
    while (idx < l) {
        val = o->get(idx);
        val = val->convertedToNumber();
        if (scope.hasException())
            return Encode::undefined();
        array->d()->type->write(b, val);
        if (scope.hasException())
            return Encode::undefined();
        ++idx;
        b += elementSize;
    }

    updateProto(scope, array);
    return array.asReturnedValue();
}

ReturnedValue TypedArrayCtor::virtualCall(const FunctionObject *f, const Value *, const Value *, int)
{
    return f->engine()->throwTypeError(QStringLiteral("calling a TypedArray constructor without new is invalid"));
}

void Heap::TypedArray::init(Type t)
{
    Object::init();
    type = operations + static_cast<int>(t);
    arrayType = static_cast<int>(t);
}

Heap::TypedArray *TypedArray::create(ExecutionEngine *e, Heap::TypedArray::Type t)
{
    Scope scope(e);
    Scoped<InternalClass> ic(scope, e->newInternalClass(staticVTable(), e->typedArrayPrototype + static_cast<int>(t)));
    return e->memoryManager->allocObject<TypedArray>(ic->d(), t);
}

ReturnedValue TypedArray::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
{
    const bool isArrayIndex = id.isArrayIndex();
    if (!isArrayIndex && !id.isCanonicalNumericIndexString())
        return Object::virtualGet(m, id, receiver, hasProperty);

    Scope scope(static_cast<const Object *>(m)->engine());
    Scoped<TypedArray> a(scope, static_cast<const TypedArray *>(m));
    if (a->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    if (!isArrayIndex || id.asArrayIndex() >= a->length()) {
        if (hasProperty)
            *hasProperty = false;
        return Encode::undefined();
    }

    uint bytesPerElement = a->bytesPerElement();
    uint byteOffset = a->byteOffset() + id.asArrayIndex() * bytesPerElement;
    Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength());

    if (hasProperty)
        *hasProperty = true;
    return a->d()->type->read(a->constArrayData() + byteOffset);
}

bool TypedArray::virtualHasProperty(const Managed *m, PropertyKey id)
{
    const bool isArrayIndex = id.isArrayIndex();
    if (!isArrayIndex && !id.isCanonicalNumericIndexString())
        return Object::virtualHasProperty(m, id);

    const TypedArray *a = static_cast<const TypedArray *>(m);
    if (a->hasDetachedArrayData()) {
        a->engine()->throwTypeError();
        return false;
    }
    return isArrayIndex && id.asArrayIndex() < a->length();
}

PropertyAttributes TypedArray::virtualGetOwnProperty(const Managed *m, PropertyKey id, Property *p)
{
    if (!id.isArrayIndex() && !id.isCanonicalNumericIndexString())
        return Object::virtualGetOwnProperty(m, id, p);

    bool hasProperty = false;
    ReturnedValue v = virtualGet(m, id, m, &hasProperty);
    if (p)
        p->value = v;
    return hasProperty ? Attr_NotConfigurable : PropertyAttributes();
}

bool TypedArray::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
{
    const bool isArrayIndex = id.isArrayIndex();
    if (!isArrayIndex && !id.isCanonicalNumericIndexString())
        return Object::virtualPut(m, id, value, receiver);

    ExecutionEngine *v4 = static_cast<Object *>(m)->engine();
    if (v4->hasException)
        return false;

    Scope scope(v4);
    Scoped<TypedArray> a(scope, static_cast<TypedArray *>(m));
    if (a->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    if (!isArrayIndex)
        return false;

    const uint index = id.asArrayIndex();
    if (index >= a->length())
        return false;

    uint bytesPerElement = a->bytesPerElement();
    uint byteOffset = a->byteOffset() + index * bytesPerElement;
    Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength());

    Value v = Value::fromReturnedValue(value.convertedToNumber());
    if (scope.hasException() || a->hasDetachedArrayData())
        return scope.engine->throwTypeError();
    a->d()->type->write(a->arrayData() + byteOffset, v);
    return true;
}

bool TypedArray::virtualDefineOwnProperty(Managed *m, PropertyKey id, const Property *p, PropertyAttributes attrs)
{
    if (!id.isArrayIndex()) {
        return !id.isCanonicalNumericIndexString()
                && Object::virtualDefineOwnProperty(m, id, p, attrs);
    }

    const uint index = id.asArrayIndex();
    TypedArray *a = static_cast<TypedArray *>(m);
    if (index >= a->length() || attrs.isAccessor())
        return false;

    if (attrs.hasConfigurable() && attrs.isConfigurable())
        return false;
    if (attrs.hasEnumerable() && !attrs.isEnumerable())
        return false;
    if (attrs.hasWritable() && !attrs.isWritable())
        return false;
    if (!p->value.isEmpty()) {
        ExecutionEngine *engine = a->engine();

        Value v = Value::fromReturnedValue(p->value.convertedToNumber());
        if (engine->hasException || a->hasDetachedArrayData())
            return engine->throwTypeError();
        uint bytesPerElement = a->bytesPerElement();
        uint byteOffset = a->byteOffset() + index * bytesPerElement;
        Q_ASSERT(byteOffset + bytesPerElement <= a->arrayDataLength());
        a->d()->type->write(a->arrayData() + byteOffset, v);
    }
    return true;
}

struct TypedArrayOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
{
    ~TypedArrayOwnPropertyKeyIterator() override = default;
    PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;

};

PropertyKey TypedArrayOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
{
    const TypedArray *a = static_cast<const TypedArray *>(o);
    if (arrayIndex < a->length()) {
        if (attrs)
            *attrs = Attr_NotConfigurable;
        PropertyKey id = PropertyKey::fromArrayIndex(arrayIndex);
        if (pd) {
            bool hasProperty = false;
            pd->value = TypedArray::virtualGet(a, id, a, &hasProperty);
        }
        ++arrayIndex;
        return id;
    }

    arrayIndex = UINT_MAX;
    return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
}

OwnPropertyKeyIterator *TypedArray::virtualOwnPropertyKeys(const Object *m, Value *target)
{
    *target = *m;
    return new TypedArrayOwnPropertyKeyIterator();
}

void TypedArrayPrototype::init(ExecutionEngine *engine, TypedArrayCtor *ctor)
{
    Scope scope(engine);
    ScopedObject o(scope);

    ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(3));
    ctor->defineReadonlyProperty(engine->id_prototype(), *this);
    ctor->defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(ctor->d()->type)].bytesPerElement));
    ctor->setPrototypeOf(engine->intrinsicTypedArrayCtor());

    setPrototypeOf(engine->intrinsicTypedArrayPrototype());
    defineDefaultProperty(engine->id_constructor(), (o = ctor));
    defineReadonlyProperty(QStringLiteral("BYTES_PER_ELEMENT"), Value::fromInt32(operations[static_cast<int>(ctor->d()->type)].bytesPerElement));
}

ReturnedValue IntrinsicTypedArrayPrototype::method_get_buffer(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    const TypedArray *v = thisObject->as<TypedArray>();
    if (!v)
        return v4->throwTypeError();

    return v->d()->buffer->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteLength(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    const TypedArray *v = thisObject->as<TypedArray>();
    if (!v)
        return v4->throwTypeError();

    if (v->hasDetachedArrayData())
        return Encode(0);

    return Encode(v->byteLength());
}

ReturnedValue IntrinsicTypedArrayPrototype::method_get_byteOffset(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    const TypedArray *v = thisObject->as<TypedArray>();
    if (!v)
        return v4->throwTypeError();

    if (v->hasDetachedArrayData())
        return Encode(0);

    return Encode(v->byteOffset());
}

ReturnedValue IntrinsicTypedArrayPrototype::method_get_length(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    ExecutionEngine *v4 = b->engine();
    const TypedArray *v = thisObject->as<TypedArray>();
    if (!v)
        return v4->throwTypeError();

    if (v->hasDetachedArrayData())
        return Encode(0);

    return Encode(v->length());
}

ReturnedValue IntrinsicTypedArrayPrototype::method_copyWithin(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(f);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    if (!argc)
        return instance->asReturnedValue();

    const double len = instance->length();
    Q_ASSERT(std::isfinite(len));

    const double target = argv[0].toInteger();

    const double start = (argc > 1)
            ? argv[1].toInteger()
            : 0;

    const double end = (argc > 2 && !argv[2].isUndefined())
            ? argv[2].toInteger()
            : len;

    const double fin = end < 0
            ? std::max(len + end, 0.0)
            : std::min(end, len);

    const qsizetype from = start < 0
            ? std::max(len + start, 0.0)
            : std::min(start, len);

    const qsizetype to = target < 0
            ? std::max(len + target, 0.0)
            : std::min(target, len);

    const qsizetype count = std::min(fin - from, len - to);

    if (count <= 0)
        return instance->asReturnedValue();

    if (from != to) {
        int elementSize = instance->bytesPerElement();
        char *data = instance->arrayData() + instance->byteOffset();
        memmove(data + to * elementSize, data + from * elementSize, count * elementSize);
    }

    return instance->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_entries(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v));
    ao->d()->iterationKind = IteratorKind::KeyValueIteratorKind;
    return ao->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_every(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
    ScopedValue r(scope);
    Value *arguments = scope.alloc(3);

    const char *data = v->constArrayData();
    uint bytesPerElement = v->bytesPerElement();
    uint byteOffset = v->byteOffset();

    bool ok = true;
    for (uint k = 0; ok && k < len; ++k) {
        if (v->hasDetachedArrayData())
            return scope.engine->throwTypeError();

        arguments[0] = v->d()->type->read(data + byteOffset + k * bytesPerElement);

        arguments[1] = Value::fromDouble(k);
        arguments[2] = v;
        r = callback->call(that, arguments, 3);
        CHECK_EXCEPTION();
        ok = r->toBoolean();
    }
    return Encode(ok);
}

ReturnedValue IntrinsicTypedArrayPrototype::method_fill(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();
    double dlen = len;
    double relativeStart = argc > 1 ? argv[1].toInteger() : 0.;
    double relativeEnd = len;
    if (argc > 2 && !argv[2].isUndefined())
        relativeEnd = argv[2].toInteger();

    uint k = 0;
    uint fin = 0;

    if (relativeStart < 0) {
        k = static_cast<uint>(std::max(len+relativeStart, 0.));
    } else {
        k = static_cast<uint>(std::min(relativeStart, dlen));
    }

    if (relativeEnd < 0) {
        fin = static_cast<uint>(std::max(len + relativeEnd, 0.));
    } else {
        fin = static_cast<uint>(std::min(relativeEnd, dlen));
    }

    if (scope.hasException() || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    char *data = v->arrayData();
    uint bytesPerElement = v->bytesPerElement();
    uint byteOffset = v->byteOffset();

    Value value;
    if (!argc)
        value.setDouble(std::numeric_limits<double>::quiet_NaN());
    else if (argv[0].isNumber())
        value = argv[0];
    else
        value.setDouble(argv[0].toNumber());

    while (k < fin) {
        v->d()->type->write(data + byteOffset + k * bytesPerElement, value);
        k++;
    }

    return v.asReturnedValue();
}

static TypedArray *typedArraySpeciesCreate(Scope &scope, const TypedArray *instance, uint len)
{
    const FunctionObject *constructor = instance->speciesConstructor(scope, scope.engine->typedArrayCtors + instance->d()->arrayType);
    if (!constructor) {
        scope.engine->throwTypeError();
        return nullptr;
    }

    Value *arguments = scope.alloc(1);
    arguments[0] = Encode(len);
    Scoped<TypedArray> a(scope, constructor->callAsConstructor(arguments, 1));
    if (!a || a->hasDetachedArrayData() || a->length() < len) {
        scope.engine->throwTypeError();
        return nullptr;
    }
    return a;
}

ReturnedValue IntrinsicTypedArrayPrototype::method_filter(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    ScopedValue selected(scope);
    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
    Value *arguments = scope.alloc(3);
    Value *list = arguments;

    uint to = 0;
    for (uint k = 0; k < len; ++k) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        bool exists;
        arguments[0] = instance->get(k, &exists);
        if (!exists)
            continue;

        arguments[1] = Value::fromDouble(k);
        arguments[2] = instance;
        selected = callback->call(that, arguments, 3);
        CHECK_EXCEPTION();
        if (selected->toBoolean()) {
            ++arguments;
            scope.alloc(1);
            ++to;
        }
    }

    TypedArray *a = typedArraySpeciesCreate(scope, instance, to);
    if (!a)
        return Encode::undefined();

    for (uint i = 0; i < to; ++i)
        a->put(i, list[i]);

    return a->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_find(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();

    if (!argc || !argv[0].isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    ScopedValue result(scope);
    Value *arguments = scope.alloc(3);

    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());

    for (uint k = 0; k < len; ++k) {
        if (v->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        arguments[0] = v->get(k);
        CHECK_EXCEPTION();

        arguments[1] = Value::fromDouble(k);
        arguments[2] = v;
        result = callback->call(that, arguments, 3);

        CHECK_EXCEPTION();
        if (result->toBoolean())
            return arguments[0].asReturnedValue();
    }

    RETURN_UNDEFINED();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_findIndex(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();

    if (!argc || !argv[0].isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    ScopedValue result(scope);
    Value *arguments = scope.alloc(3);

    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());

    for (uint k = 0; k < len; ++k) {
        if (v->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        arguments[0] = v->get(k);
        CHECK_EXCEPTION();

        arguments[1] = Value::fromDouble(k);
        arguments[2] = v;
        result = callback->call(that, arguments, 3);

        CHECK_EXCEPTION();
        if (result->toBoolean())
            return Encode(k);
    }

    return Encode(-1);
}

ReturnedValue IntrinsicTypedArrayPrototype::method_forEach(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
    Value *arguments = scope.alloc(3);

    for (uint k = 0; k < len; ++k) {
        if (v->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        bool exists;
        arguments[0] = v->get(k, &exists);
        if (!exists)
            continue;

        arguments[1] = Value::fromDouble(k);
        arguments[2] = v;
        callback->call(that, arguments, 3);
    }
    RETURN_UNDEFINED();
}


ReturnedValue IntrinsicTypedArrayPrototype::method_includes(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();
    if (len == 0) {
        return Encode(false);
    }

    double n = 0;
    if (argc > 1 && !argv[1].isUndefined()) {
        n = argv[1].toInteger();
    }

    double k = 0;
    if (n >= 0) {
        k = n;
    } else {
        k = len + n;
        if (k < 0) {
            k = 0;
        }
    }

    while (k < len) {
        ScopedValue val(scope, v->get(k));
        if (val->sameValueZero(argv[0])) {
            return Encode(true);
        }
        k++;
    }

    return Encode(false);
}

ReturnedValue IntrinsicTypedArrayPrototype::method_indexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = v->length();
    if (!len)
        return Encode(-1);

    ScopedValue searchValue(scope, argc ? argv[0] : Value::undefinedValue());
    uint fromIndex = 0;

    if (argc >= 2) {
        double f = argv[1].toInteger();
        CHECK_EXCEPTION();
        if (f >= len)
            return Encode(-1);
        if (f < 0)
            f = qMax(len + f, 0.);
        fromIndex = (uint) f;
    }

    if (v->isStringObject()) {
        ScopedValue value(scope);
        for (uint k = fromIndex; k < len; ++k) {
            bool exists;
            value = v->get(k, &exists);
            if (exists && RuntimeHelpers::strictEqual(value, searchValue))
                return Encode(k);
        }
        return Encode(-1);
    }

    ScopedValue value(scope);

    for (uint i = fromIndex; i < len; ++i) {
        bool exists;
        value = v->get(i, &exists);
        CHECK_EXCEPTION();
        if (exists && RuntimeHelpers::strictEqual(value, searchValue))
            return Encode(i);
    }
    return Encode(-1);
}

ReturnedValue IntrinsicTypedArrayPrototype::method_join(
        const FunctionObject *functionObject, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(functionObject);
    Scoped<TypedArray> typedArray(scope, thisObject);
    if (!typedArray || typedArray->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    // We cannot optimize the resolution of the argument away if length is 0.
    // It may have side effects.
    ScopedValue argument(scope, argc ? argv[0] : Value::undefinedValue());
    const QString separator = argument->isUndefined()
            ? QStringLiteral(",")
            : argument->toQString();

    const quint32 length = typedArray->length();
    if (!length)
        return Encode(scope.engine->newString());

    QString result;

    ScopedString name(scope, scope.engine->newString(QStringLiteral("0")));
    ScopedValue value(scope, typedArray->get(name));
    if (!value->isNullOrUndefined())
        result = value->toQString();

    for (quint32 i = 1; i < length; ++i) {
        result += separator;

        name = Value::fromDouble(i).toString(scope.engine);
        value = typedArray->get(name);
        CHECK_EXCEPTION();

        if (!value->isNullOrUndefined())
            result += value->toQString();
    }

    return Encode(scope.engine->newString(result));
}

ReturnedValue IntrinsicTypedArrayPrototype::method_keys(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v));
    ao->d()->iterationKind = IteratorKind::KeyIteratorKind;
    return ao->asReturnedValue();
}


ReturnedValue IntrinsicTypedArrayPrototype::method_lastIndexOf(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();
    if (!len)
        return Encode(-1);

    ScopedValue searchValue(scope);
    uint fromIndex = len;

    if (argc >= 1)
        searchValue = argv[0];
    else
        searchValue = Value::undefinedValue();

    if (argc >= 2) {
        double f = argv[1].toInteger();
        CHECK_EXCEPTION();
        if (f > 0)
            f = qMin(f, (double)(len - 1));
        else if (f < 0) {
            f = len + f;
            if (f < 0)
                return Encode(-1);
        }
        fromIndex = (uint) f + 1;
    }

    ScopedValue value(scope);
    for (uint k = fromIndex; k > 0;) {
        --k;
        bool exists;
        value = instance->get(k, &exists);
        if (exists && RuntimeHelpers::strictEqual(value, searchValue))
            return Encode(k);
    }
    return Encode(-1);
}

ReturnedValue IntrinsicTypedArrayPrototype::method_map(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    TypedArray *a = typedArraySpeciesCreate(scope, instance, len);
    if (!a)
        return Encode::undefined();

    ScopedValue v(scope);
    ScopedValue mapped(scope);
    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
    Value *arguments = scope.alloc(3);

    for (uint k = 0; k < len; ++k) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        arguments[0] = instance->get(k);

        arguments[1] = Value::fromDouble(k);
        arguments[2] = instance;
        mapped = callback->call(that, arguments, 3);
        CHECK_EXCEPTION();
        a->put(k, mapped);
    }
    return a->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_reduce(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    uint k = 0;
    ScopedValue acc(scope);
    ScopedValue v(scope);

    if (argc > 1) {
        acc = argv[1];
    } else {
        bool kPresent = false;
        while (k < len && !kPresent) {
            v = instance->get(k, &kPresent);
            if (kPresent)
                acc = v;
            ++k;
        }
        if (!kPresent)
            THROW_TYPE_ERROR();
    }

    Value *arguments = scope.alloc(4);

    while (k < len) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        bool kPresent;
        v = instance->get(k, &kPresent);
        if (kPresent) {
            arguments[0] = acc;
            arguments[1] = v;
            arguments[2] = Value::fromDouble(k);
            arguments[3] = instance;
            acc = callback->call(nullptr, arguments, 4);
            CHECK_EXCEPTION();
        }
        ++k;
    }
    return acc->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_reduceRight(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    if (len == 0) {
        if (argc == 1)
            THROW_TYPE_ERROR();
        return argv[1].asReturnedValue();
    }

    uint k = len;
    ScopedValue acc(scope);
    ScopedValue v(scope);
    if (argc > 1) {
        acc = argv[1];
    } else {
        bool kPresent = false;
        while (k > 0 && !kPresent) {
            v = instance->get(k - 1, &kPresent);
            if (kPresent)
                acc = v;
            --k;
        }
        if (!kPresent)
            THROW_TYPE_ERROR();
    }

    Value *arguments = scope.alloc(4);

    while (k > 0) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        bool kPresent;
        v = instance->get(k - 1, &kPresent);
        if (kPresent) {
            arguments[0] = acc;
            arguments[1] = v;
            arguments[2] = Value::fromDouble(k - 1);
            arguments[3] = instance;
            acc = callback->call(nullptr, arguments, 4);
            CHECK_EXCEPTION();
        }
        --k;
    }
    return acc->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_reverse(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint length = instance->length();

    int lo = 0, hi = length - 1;

    ScopedValue lval(scope);
    ScopedValue hval(scope);
    for (; lo < hi; ++lo, --hi) {
        bool loExists, hiExists;
        lval = instance->get(lo, &loExists);
        hval = instance->get(hi, &hiExists);
        Q_ASSERT(hiExists && loExists);
        bool ok;
        ok = instance->put(lo, hval);
        Q_ASSERT(ok);
        ok = instance->put(hi, lval);
        Q_ASSERT(ok);
    }
    return instance->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_some(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();

    if (!argc || !argv->isFunctionObject())
        THROW_TYPE_ERROR();
    const FunctionObject *callback = static_cast<const FunctionObject *>(argv);

    ScopedValue that(scope, argc > 1 ? argv[1] : Value::undefinedValue());
    ScopedValue result(scope);
    Value *arguments = scope.alloc(3);

    for (uint k = 0; k < len; ++k) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        bool exists;
        arguments[0] = instance->get(k, &exists);
        if (!exists)
            continue;

        arguments[1] = Value::fromDouble(k);
        arguments[2] = instance;
        result = callback->call(that, arguments, 3);
        CHECK_EXCEPTION();
        if (result->toBoolean())
            return Encode(true);
    }
    return Encode(false);
}


ReturnedValue IntrinsicTypedArrayPrototype::method_values(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    Scope scope(b);
    Scoped<TypedArray> v(scope, thisObject);
    if (!v || v->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    Scoped<ArrayIteratorObject> ao(scope, scope.engine->newArrayIteratorObject(v));
    ao->d()->iterationKind = IteratorKind::ValueIteratorKind;
    return ao->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> a(scope, *thisObject);
    if (!a)
        return scope.engine->throwTypeError();
    Scoped<ArrayBuffer> buffer(scope, a->d()->buffer);

    double doffset = argc >= 2 ? argv[1].toInteger() : 0;
    if (scope.hasException())
        RETURN_UNDEFINED();
    if (!buffer || buffer->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    if (doffset < 0 || doffset >= UINT_MAX)
        RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range")));
    uint offset = (uint)doffset;
    uint elementSize = a->bytesPerElement();

    Scoped<TypedArray> srcTypedArray(scope, argv[0]);
    if (!srcTypedArray) {
        // src is a regular object
        ScopedObject o(scope, argv[0].toObject(scope.engine));
        if (scope.hasException() || !o)
            return scope.engine->throwTypeError();

        double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber();
        uint l = (uint)len;
        if (scope.hasException() || l != len)
            return scope.engine->throwTypeError();

        const uint aLength = a->length();
        if (offset > aLength || l > aLength - offset)
            RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range")));

        uint idx = 0;
        if (buffer->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        char *b = buffer->arrayData() + a->byteOffset() + offset*elementSize;
        ScopedValue val(scope);
        while (idx < l) {
            val = o->get(idx);
            if (scope.hasException())
                return Encode::undefined();
            val = val->convertedToNumber();
            if (scope.hasException() || buffer->hasDetachedArrayData())
                return scope.engine->throwTypeError();
            a->d()->type->write(b, val);
            if (scope.hasException())
                RETURN_UNDEFINED();
            ++idx;
            b += elementSize;
        }
        RETURN_UNDEFINED();
    }

    // src is a typed array
    Scoped<ArrayBuffer> srcBuffer(scope, srcTypedArray->d()->buffer);
    if (!srcBuffer || srcBuffer->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint l = srcTypedArray->length();

    const uint aLength = a->length();
    if (offset > aLength || l > aLength - offset)
        RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range")));

    char *dest = buffer->arrayData() + a->byteOffset() + offset*elementSize;
    const char *src = srcBuffer->d()->constArrayData() + srcTypedArray->byteOffset();
    if (srcTypedArray->d()->type == a->d()->type) {
        // same type of typed arrays, use memmove (as srcbuffer and buffer could be the same)
        memmove(dest, src, srcTypedArray->byteLength());
        RETURN_UNDEFINED();
    }

    char *srcCopy = nullptr;
    if (buffer->d() == srcBuffer->d()) {
        // same buffer, need to take a temporary copy, to not run into problems
        srcCopy = new char[srcTypedArray->byteLength()];
        memcpy(srcCopy, src, srcTypedArray->byteLength());
        src = srcCopy;
    }

    // typed arrays of different kind, need to manually loop
    uint srcElementSize = srcTypedArray->bytesPerElement();
    TypedArrayOperations::Read read = srcTypedArray->d()->type->read;
    TypedArrayOperations::Write write = a->d()->type->write;
    for (uint i = 0; i < l; ++i) {
        Value val;
        val.setRawValue(read(src + i*srcElementSize));
        write(dest + i*elementSize, val);
    }

    if (srcCopy)
        delete [] srcCopy;

    RETURN_UNDEFINED();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_slice(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();

    double s = (argc ? argv[0] : Value::undefinedValue()).toInteger();
    uint start;
    if (s < 0)
        start = (uint)qMax(len + s, 0.);
    else if (s > len)
        start = len;
    else
        start = (uint) s;
    uint end = len;
    if (argc > 1 && !argv[1].isUndefined()) {
        double e = argv[1].toInteger();
        if (e < 0)
            end = (uint)qMax(len + e, 0.);
        else if (e > len)
            end = len;
        else
            end = (uint) e;
    }
    uint count = start > end ? 0 : end - start;

    TypedArray *a = typedArraySpeciesCreate(scope, instance, count);
    if (!a)
        return Encode::undefined();

    ScopedValue v(scope);
    uint n = 0;
    for (uint i = start; i < end; ++i) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        v = instance->get(i);
        if (a->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        a->put(n, v);
        ++n;
    }
    return a->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_subarray(const FunctionObject *builtin, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(builtin);
    Scoped<TypedArray> a(scope, *thisObject);

    if (!a)
        return scope.engine->throwTypeError();

    Scoped<ArrayBuffer> buffer(scope, a->d()->buffer);
    Q_ASSERT(buffer);

    int len = a->length();
    double b = argc > 0 ? argv[0].toInteger() : 0;
    if (b < 0)
        b = len + b;
    uint begin = (uint)qBound(0., b, (double)len);

    double e = argc < 2 || argv[1].isUndefined() ? len : argv[1].toInteger();
    if (e < 0)
        e = len + e;
    uint end = (uint)qBound(0., e, (double)len);
    if (end < begin)
        end = begin;

    if (scope.hasException())
        RETURN_UNDEFINED();

    int newLen = end - begin;

    ScopedFunctionObject constructor(scope, a->speciesConstructor(scope, scope.engine->typedArrayCtors + a->d()->arrayType));
    if (!constructor)
        return scope.engine->throwTypeError();

    Value *arguments = scope.alloc(3);
    arguments[0] = buffer;
    arguments[1] = Encode(a->byteOffset() + begin * a->bytesPerElement());
    arguments[2] = Encode(newLen);
    a = constructor->callAsConstructor(arguments, 3);
    if (!a || a->hasDetachedArrayData())
        return scope.engine->throwTypeError();
    return a->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_toLocaleString(const FunctionObject *b, const Value *thisObject, const Value *, int)
{
    Scope scope(b);
    Scoped<TypedArray> instance(scope, thisObject);
    if (!instance || instance->hasDetachedArrayData())
        return scope.engine->throwTypeError();

    uint len = instance->length();
    const QString separator = QStringLiteral(",");

    QString R;

    ScopedValue v(scope);
    ScopedString s(scope);

    ScopedPropertyKey tolocaleString(scope, scope.engine->id_toLocaleString()->toPropertyKey());
    Q_ASSERT(!scope.engine->hasException);

    for (uint k = 0; k < len; ++k) {
        if (instance->hasDetachedArrayData())
            return scope.engine->throwTypeError();
        if (k)
            R += separator;

        v = instance->get(k);
        Q_ASSERT(!v->isNullOrUndefined()); // typed array cannot hold null or undefined

        ScopedObject valueAsObject(scope, v->toObject(scope.engine));
        Q_ASSERT(valueAsObject); // only null or undefined cannot be converted to object

        ScopedFunctionObject function(scope, valueAsObject->get(tolocaleString));
        if (!function)
            return scope.engine->throwTypeError();

        v = function->call(valueAsObject, nullptr, 0);
        if (scope.hasException())
            return Encode::undefined();

        s = v->toString(scope.engine);
        if (scope.hasException())
            return Encode::undefined();

        R += s->toQString();
    }
    return scope.engine->newString(R)->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayPrototype::method_get_toStringTag(const FunctionObject *, const Value *thisObject, const Value *, int)
{
    const TypedArray *a = thisObject->as<TypedArray>();
    if (!a)
        return Encode::undefined();

    return a->engine()->newString(QString::fromLatin1(a->d()->type->name))->asReturnedValue();
}

static bool validateTypedArray(const Object *o)
{
    const TypedArray *a = o->as<TypedArray>();
    if (!a)
        return false;
    if (a->hasDetachedArrayData())
        return false;
    return true;
}

ReturnedValue IntrinsicTypedArrayCtor::method_of(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(f);
    int len = argc;
    const Value *items = argv;
    const FunctionObject *C = thisObject->as<FunctionObject>();
    if (!C || !C->isConstructor())
        return scope.engine->throwTypeError();

    Value lenValue = Value::fromInt32(len);
    ScopedObject newObj(scope, C->callAsConstructor(&lenValue, 1));
    if (scope.hasException())
        return Encode::undefined();
    if (!::validateTypedArray(newObj))
        return scope.engine->throwTypeError();
    TypedArray *a = newObj->as<TypedArray>();
    Q_ASSERT(a);
    if (a->length() < static_cast<uint>(len))
        return scope.engine->throwTypeError();

    for (int k = 0; k < len; ++k) {
        newObj->put(PropertyKey::fromArrayIndex(k), items[k]);
    }
    return newObj->asReturnedValue();
}

ReturnedValue IntrinsicTypedArrayCtor::method_from(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(f);
    ScopedObject itemsObject(scope, argv[0]);
    bool usingIterator = false;

    ScopedFunctionObject mapfn(scope, Value::undefinedValue());
    Value *mapArguments = nullptr;
    if (argc > 1) {
        mapfn = ScopedFunctionObject(scope, argv[1]);
        if (!mapfn)
            return scope.engine->throwTypeError(QString::fromLatin1("%1 is not a function").arg(argv[1].toQStringNoThrow()));
        mapArguments = scope.alloc(2);
    }

    // Iterator validity check goes after map function validity has been checked.
    if (itemsObject) {
        // If the object claims to support iterators, then let's try use them.
        ScopedValue it(scope, itemsObject->get(scope.engine->symbol_iterator()));
        CHECK_EXCEPTION();
        if (!it->isNullOrUndefined()) {
            ScopedFunctionObject itfunc(scope, it);
            if (!itfunc)
                return scope.engine->throwTypeError();
            usingIterator = true;
        }
    }

    ScopedValue thisArg(scope);
    if (argc > 2)
        thisArg = argv[2];

    const FunctionObject *C = thisObject->as<FunctionObject>();

    if (usingIterator) {
        // Item iteration supported, so let's go ahead and try use that.
        CHECK_EXCEPTION();

        qint64 iterableLength = 0;
        Value *nextValue = scope.alloc(1);
        ScopedValue done(scope);

        ScopedObject lengthIterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true));
        CHECK_EXCEPTION(); // symbol_iterator threw; whoops.
        if (!lengthIterator) {
            return scope.engine->throwTypeError(); // symbol_iterator wasn't an object.
        }

        forever {
            // Here we calculate the length of the iterable range.
            if (iterableLength > (static_cast<qint64>(1) << 53) - 1) {
                ScopedValue error(scope, scope.engine->throwTypeError());
                return Runtime::IteratorClose::call(scope.engine, lengthIterator);
            }
            // Retrieve the next value. If the iteration ends, we're done here.
            done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, lengthIterator, nextValue));
            if (scope.hasException())
                return Runtime::IteratorClose::call(scope.engine, lengthIterator);
            if (done->toBoolean()) {
                break;
            }
            iterableLength++;
        }

        // Constructor validity check goes after we have calculated the length, because that calculation can throw
        // errors that are not type errors and at least the tests expect those rather than type errors.
        if (!C || !C->isConstructor())
            return scope.engine->throwTypeError();

        ScopedObject iterator(scope, Runtime::GetIterator::call(scope.engine, itemsObject, true));
        CHECK_EXCEPTION(); // symbol_iterator can throw.
        if (!iterator) {
            return scope.engine->throwTypeError(); // symbol_iterator wasn't an object.
        }

        ScopedObject a(scope, Value::undefinedValue());
        ScopedValue ctorArgument(scope, Value::fromReturnedValue(QV4::Encode(int(iterableLength))));
        a = C->callAsConstructor(ctorArgument, 1);
        CHECK_EXCEPTION();

        // We check exceptions above, and only after doing so, check the array's validity after construction.
        if (!::validateTypedArray(a) || (a->getLength() < iterableLength))
            return scope.engine->throwTypeError();


        // The loop below traverses the iterator, and puts elements into the created array.
        ScopedValue mappedValue(scope, Value::undefinedValue());
        for (qint64 k = 0; k < iterableLength; ++k) {
            done = Value::fromReturnedValue(Runtime::IteratorNext::call(scope.engine, iterator, nextValue));
            if (scope.hasException())
                return Runtime::IteratorClose::call(scope.engine, iterator);

            if (mapfn) {
                mapArguments[0] = *nextValue;
                mapArguments[1] = Value::fromDouble(k);
                mappedValue = mapfn->call(thisArg, mapArguments, 2);
                if (scope.hasException())
                    return Runtime::IteratorClose::call(scope.engine, iterator);
            } else {
                mappedValue = *nextValue;
            }

            a->put(k, mappedValue);
            if (scope.hasException())
                return Runtime::IteratorClose::call(scope.engine, iterator);
        }
        return a.asReturnedValue();
    } else {
        // Array-like fallback. We request elements by index, and put them into the created array.
        ScopedObject arrayLike(scope, argv[0].toObject(scope.engine));
        if (!arrayLike)
            return scope.engine->throwTypeError(QString::fromLatin1("Cannot convert %1 to object").arg(argv[0].toQStringNoThrow()));

        int len = arrayLike->getLength();
        CHECK_EXCEPTION();

        // Getting the length may throw, and must do so before we check the constructor validity.
        if (!C || !C->isConstructor())
            return scope.engine->throwTypeError();

        ScopedObject a(scope, Value::undefinedValue());
        ScopedValue ctorArgument(scope, Value::fromReturnedValue(QV4::Encode(len)));
        a = C->callAsConstructor(ctorArgument, 1);
        CHECK_EXCEPTION();

        // We check exceptions above, and only after doing so, check the array's validity after construction.
        if (!::validateTypedArray(a) || (a->getLength() < len))
            return scope.engine->throwTypeError();

        ScopedValue mappedValue(scope, Value::undefinedValue());
        ScopedValue kValue(scope);
        for (int k = 0; k < len; ++k) {
            kValue = arrayLike->get(k);
            CHECK_EXCEPTION();

            if (mapfn) {
                mapArguments[0] = kValue;
                mapArguments[1] = Value::fromDouble(k);
                mappedValue = mapfn->call(thisArg, mapArguments, 2);
                CHECK_EXCEPTION();
            } else {
                mappedValue = kValue;
            }

            a->put(k, mappedValue);
            CHECK_EXCEPTION();
        }
        return a.asReturnedValue();
    }
}

void IntrinsicTypedArrayPrototype::init(ExecutionEngine *engine, IntrinsicTypedArrayCtor *ctor)
{
    Scope scope(engine);
    ctor->defineReadonlyProperty(engine->id_prototype(), *this);
    ctor->defineReadonlyConfigurableProperty(engine->id_length(), Value::fromInt32(0));
    ScopedString s(scope, engine->newString(QStringLiteral("TypedArray")));
    ctor->defineReadonlyConfigurableProperty(engine->id_name(), s);
    s = scope.engine->newString(QStringLiteral("of"));
    ctor->defineDefaultProperty(s, IntrinsicTypedArrayCtor::method_of);
    s = scope.engine->newString(QStringLiteral("from"));
    ctor->defineDefaultProperty(s, IntrinsicTypedArrayCtor::method_from, 1);
    ctor->addSymbolSpecies();

    defineAccessorProperty(QStringLiteral("buffer"), method_get_buffer, nullptr);
    defineAccessorProperty(QStringLiteral("byteLength"), method_get_byteLength, nullptr);
    defineAccessorProperty(QStringLiteral("byteOffset"), method_get_byteOffset, nullptr);
    defineAccessorProperty(QStringLiteral("length"), method_get_length, nullptr);

    defineDefaultProperty(QStringLiteral("copyWithin"), method_copyWithin, 2);
    defineDefaultProperty(QStringLiteral("entries"), method_entries, 0);
    defineDefaultProperty(QStringLiteral("every"), method_every, 1);
    defineDefaultProperty(QStringLiteral("fill"), method_fill, 1);
    defineDefaultProperty(QStringLiteral("filter"), method_filter, 1);
    defineDefaultProperty(QStringLiteral("find"), method_find, 1);
    defineDefaultProperty(QStringLiteral("findIndex"), method_findIndex, 1);
    defineDefaultProperty(QStringLiteral("forEach"), method_forEach, 1);
    defineDefaultProperty(QStringLiteral("includes"), method_includes, 1);
    defineDefaultProperty(QStringLiteral("indexOf"), method_indexOf, 1);
    defineDefaultProperty(QStringLiteral("join"), method_join, 1);
    defineDefaultProperty(QStringLiteral("keys"), method_keys, 0);
    defineDefaultProperty(QStringLiteral("lastIndexOf"), method_lastIndexOf, 1);
    defineDefaultProperty(QStringLiteral("map"), method_map, 1);
    defineDefaultProperty(QStringLiteral("reduce"), method_reduce, 1);
    defineDefaultProperty(QStringLiteral("reduceRight"), method_reduceRight, 1);
    defineDefaultProperty(QStringLiteral("reverse"), method_reverse, 0);
    defineDefaultProperty(QStringLiteral("some"), method_some, 1);
    defineDefaultProperty(QStringLiteral("set"), method_set, 1);
    defineDefaultProperty(QStringLiteral("slice"), method_slice, 2);
    defineDefaultProperty(QStringLiteral("subarray"), method_subarray, 2);
    defineDefaultProperty(engine->id_toLocaleString(), method_toLocaleString, 0);
    ScopedObject f(scope, engine->arrayPrototype()->get(engine->id_toString()));
    defineDefaultProperty(engine->id_toString(), f);

    ScopedString valuesString(scope, engine->newIdentifier(QStringLiteral("values")));
    ScopedObject values(scope, FunctionObject::createBuiltinFunction(engine, valuesString, method_values, 0));
    defineDefaultProperty(QStringLiteral("values"), values);
    defineDefaultProperty(engine->symbol_iterator(), values);

    defineAccessorProperty(engine->symbol_toStringTag(), method_get_toStringTag, nullptr);
}
