js/src/vm/TypedArrayObject-inl.h
author Yoshi Cheng-Hao Huang <allstars.chh@gmail.com>
Tue, 26 Mar 2019 13:58:20 +0000
changeset 466288 66414629b2e353ef5740bd57da3a8ffdf39e5ce0
parent 459146 fdb6f449e8c0858442997e9fdf5d35fbc35d5fec
child 467502 6ccad746f5d83af425dec5b26c5bca6f6bcfbb46
permissions -rw-r--r--
Bug 1521732 - Part 3: Use RootedValueVector instead. r=sfink,jonco s/AutoValueVector/RootedValueVector/g Depends on D23183 Differential Revision: https://phabricator.services.mozilla.com/D23184

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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_TypedArrayObject_inl_h
#define vm_TypedArrayObject_inl_h

/* Utilities and common inline code for TypedArray */

#include "vm/TypedArrayObject.h"

#include "mozilla/Assertions.h"
#include "mozilla/Compiler.h"
#include "mozilla/FloatingPoint.h"

#include <algorithm>

#include "jsnum.h"

#include "builtin/Array.h"
#include "gc/Zone.h"
#include "jit/AtomicOperations.h"
#include "js/Conversions.h"
#include "js/Value.h"
#include "vm/JSContext.h"
#include "vm/NativeObject.h"

#include "gc/ObjectKind-inl.h"
#include "vm/ObjectOperations-inl.h"

namespace js {

template <typename To, typename From>
inline To ConvertNumber(From src);

template <>
inline int8_t ConvertNumber<int8_t, float>(float src) {
  return JS::ToInt8(src);
}

template <>
inline uint8_t ConvertNumber<uint8_t, float>(float src) {
  return JS::ToUint8(src);
}

template <>
inline uint8_clamped ConvertNumber<uint8_clamped, float>(float src) {
  return uint8_clamped(src);
}

template <>
inline int16_t ConvertNumber<int16_t, float>(float src) {
  return JS::ToInt16(src);
}

template <>
inline uint16_t ConvertNumber<uint16_t, float>(float src) {
  return JS::ToUint16(src);
}

template <>
inline int32_t ConvertNumber<int32_t, float>(float src) {
  return JS::ToInt32(src);
}

template <>
inline uint32_t ConvertNumber<uint32_t, float>(float src) {
  return JS::ToUint32(src);
}

template <>
inline int8_t ConvertNumber<int8_t, double>(double src) {
  return JS::ToInt8(src);
}

template <>
inline uint8_t ConvertNumber<uint8_t, double>(double src) {
  return JS::ToUint8(src);
}

template <>
inline uint8_clamped ConvertNumber<uint8_clamped, double>(double src) {
  return uint8_clamped(src);
}

template <>
inline int16_t ConvertNumber<int16_t, double>(double src) {
  return JS::ToInt16(src);
}

template <>
inline uint16_t ConvertNumber<uint16_t, double>(double src) {
  return JS::ToUint16(src);
}

template <>
inline int32_t ConvertNumber<int32_t, double>(double src) {
  return JS::ToInt32(src);
}

template <>
inline uint32_t ConvertNumber<uint32_t, double>(double src) {
  return JS::ToUint32(src);
}

template <typename To, typename From>
inline To ConvertNumber(From src) {
  static_assert(
      !mozilla::IsFloatingPoint<From>::value ||
          (mozilla::IsFloatingPoint<From>::value &&
           mozilla::IsFloatingPoint<To>::value),
      "conversion from floating point to int should have been handled by "
      "specializations above");
  return To(src);
}

template <typename NativeType>
struct TypeIDOfType;
template <>
struct TypeIDOfType<int8_t> {
  static const Scalar::Type id = Scalar::Int8;
  static const JSProtoKey protoKey = JSProto_Int8Array;
};
template <>
struct TypeIDOfType<uint8_t> {
  static const Scalar::Type id = Scalar::Uint8;
  static const JSProtoKey protoKey = JSProto_Uint8Array;
};
template <>
struct TypeIDOfType<int16_t> {
  static const Scalar::Type id = Scalar::Int16;
  static const JSProtoKey protoKey = JSProto_Int16Array;
};
template <>
struct TypeIDOfType<uint16_t> {
  static const Scalar::Type id = Scalar::Uint16;
  static const JSProtoKey protoKey = JSProto_Uint16Array;
};
template <>
struct TypeIDOfType<int32_t> {
  static const Scalar::Type id = Scalar::Int32;
  static const JSProtoKey protoKey = JSProto_Int32Array;
};
template <>
struct TypeIDOfType<uint32_t> {
  static const Scalar::Type id = Scalar::Uint32;
  static const JSProtoKey protoKey = JSProto_Uint32Array;
};
template <>
struct TypeIDOfType<float> {
  static const Scalar::Type id = Scalar::Float32;
  static const JSProtoKey protoKey = JSProto_Float32Array;
};
template <>
struct TypeIDOfType<double> {
  static const Scalar::Type id = Scalar::Float64;
  static const JSProtoKey protoKey = JSProto_Float64Array;
};
template <>
struct TypeIDOfType<uint8_clamped> {
  static const Scalar::Type id = Scalar::Uint8Clamped;
  static const JSProtoKey protoKey = JSProto_Uint8ClampedArray;
};

class SharedOps {
 public:
  template <typename T>
  static T load(SharedMem<T*> addr) {
    return js::jit::AtomicOperations::loadSafeWhenRacy(addr);
  }

  template <typename T>
  static void store(SharedMem<T*> addr, T value) {
    js::jit::AtomicOperations::storeSafeWhenRacy(addr, value);
  }

  template <typename T>
  static void memcpy(SharedMem<T*> dest, SharedMem<T*> src, size_t size) {
    js::jit::AtomicOperations::memcpySafeWhenRacy(dest, src, size);
  }

  template <typename T>
  static void memmove(SharedMem<T*> dest, SharedMem<T*> src, size_t size) {
    js::jit::AtomicOperations::memmoveSafeWhenRacy(dest, src, size);
  }

  template <typename T>
  static void podCopy(SharedMem<T*> dest, SharedMem<T*> src, size_t nelem) {
    js::jit::AtomicOperations::podCopySafeWhenRacy(dest, src, nelem);
  }

  template <typename T>
  static void podMove(SharedMem<T*> dest, SharedMem<T*> src, size_t nelem) {
    js::jit::AtomicOperations::podMoveSafeWhenRacy(dest, src, nelem);
  }

  static SharedMem<void*> extract(TypedArrayObject* obj) {
    return obj->dataPointerEither();
  }
};

class UnsharedOps {
 public:
  template <typename T>
  static T load(SharedMem<T*> addr) {
    return *addr.unwrapUnshared();
  }

  template <typename T>
  static void store(SharedMem<T*> addr, T value) {
    *addr.unwrapUnshared() = value;
  }

  template <typename T>
  static void memcpy(SharedMem<T*> dest, SharedMem<T*> src, size_t size) {
    ::memcpy(dest.unwrapUnshared(), src.unwrapUnshared(), size);
  }

  template <typename T>
  static void memmove(SharedMem<T*> dest, SharedMem<T*> src, size_t size) {
    ::memmove(dest.unwrapUnshared(), src.unwrapUnshared(), size);
  }

  template <typename T>
  static void podCopy(SharedMem<T*> dest, SharedMem<T*> src, size_t nelem) {
    // std::copy_n better matches the argument values/types of this
    // function, but as noted below it allows the input/output ranges to
    // overlap.  std::copy does not, so use it so the compiler has extra
    // ability to optimize.
    const auto* first = src.unwrapUnshared();
    const auto* last = first + nelem;
    auto* result = dest.unwrapUnshared();
    std::copy(first, last, result);
  }

  template <typename T>
  static void podMove(SharedMem<T*> dest, SharedMem<T*> src, size_t n) {
    // std::copy_n copies from |src| to |dest| starting from |src|, so
    // input/output ranges *may* permissibly overlap, as this function
    // allows.
    const auto* start = src.unwrapUnshared();
    auto* result = dest.unwrapUnshared();
    std::copy_n(start, n, result);
  }

  static SharedMem<void*> extract(TypedArrayObject* obj) {
    return SharedMem<void*>::unshared(obj->dataPointerUnshared());
  }
};

template <typename T, typename Ops>
class ElementSpecific {
 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(Handle<TypedArrayObject*> target,
                                Handle<TypedArrayObject*> source,
                                uint32_t offset) {
    // WARNING: |source| may be an unwrapped typed array from a different
    // compartment. Proceed with caution!

    MOZ_ASSERT(TypeIDOfType<T>::id == target->type(),
               "calling wrong setFromTypedArray specialization");
    MOZ_ASSERT(!target->hasDetachedBuffer(), "target isn't detached");
    MOZ_ASSERT(!source->hasDetachedBuffer(), "source isn't detached");

    MOZ_ASSERT(offset <= target->length());
    MOZ_ASSERT(source->length() <= target->length() - offset);

    if (TypedArrayObject::sameBuffer(target, source)) {
      return setFromOverlappingTypedArray(target, source, offset);
    }

    SharedMem<T*> dest =
        target->dataPointerEither().template cast<T*>() + offset;
    uint32_t count = source->length();

    if (source->type() == target->type()) {
      Ops::podCopy(dest, source->dataPointerEither().template cast<T*>(),
                   count);
      return true;
    }

    SharedMem<void*> data = Ops::extract(source);
    switch (source->type()) {
      case Scalar::Int8: {
        SharedMem<int8_t*> src = data.cast<int8_t*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Uint8:
      case Scalar::Uint8Clamped: {
        SharedMem<uint8_t*> src = data.cast<uint8_t*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Int16: {
        SharedMem<int16_t*> src = data.cast<int16_t*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Uint16: {
        SharedMem<uint16_t*> src = data.cast<uint16_t*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Int32: {
        SharedMem<int32_t*> src = data.cast<int32_t*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Uint32: {
        SharedMem<uint32_t*> src = data.cast<uint32_t*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Float32: {
        SharedMem<float*> src = data.cast<float*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      case Scalar::Float64: {
        SharedMem<double*> src = data.cast<double*>();
        for (uint32_t i = 0; i < count; ++i) {
          Ops::store(dest++, ConvertNumber<T>(Ops::load(src++)));
        }
        break;
      }
      default:
        MOZ_CRASH("setFromTypedArray with a typed array with bogus type");
    }

    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<TypedArrayObject*> target,
                                   HandleObject source, uint32_t len,
                                   uint32_t offset = 0) {
    MOZ_ASSERT(target->type() == TypeIDOfType<T>::id,
               "target type and NativeType must match");
    MOZ_ASSERT(!target->hasDetachedBuffer(), "target isn't detached");
    MOZ_ASSERT(!source->is<TypedArrayObject>(),
               "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<NativeObject>().getDenseInitializedLength(), len);

      SharedMem<T*> dest =
          target->dataPointerEither().template cast<T*>() + offset;

      MOZ_ASSERT(!canConvertInfallibly(MagicValue(JS_ELEMENTS_HOLE)),
                 "the following loop must abort on holes");

      const Value* srcValues = source->as<NativeObject>().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
      // detaches the underlying array buffer or GC moves the data.
      SharedMem<T*> dest =
          target->dataPointerEither().template cast<T*>() + offset + i;
      Ops::store(dest, n);
    }

    return true;
  }

  /*
   * Copy |source| into the typed array |target|.
   */
  static bool initFromIterablePackedArray(JSContext* cx,
                                          Handle<TypedArrayObject*> target,
                                          HandleArrayObject source) {
    MOZ_ASSERT(target->type() == TypeIDOfType<T>::id,
               "target type and NativeType must match");
    MOZ_ASSERT(!target->hasDetachedBuffer(), "target isn't detached");
    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<T*> dest = target->dataPointerEither().template cast<T*>();

    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.
    RootedValueVector 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<T*> newDest = target->dataPointerEither().template cast<T*>();
      Ops::store(newDest + i, n);
    }

    return true;
  }

 private:
  static bool setFromOverlappingTypedArray(Handle<TypedArrayObject*> target,
                                           Handle<TypedArrayObject*> source,
                                           uint32_t offset) {
    // WARNING: |source| may be an unwrapped typed array from a different
    // compartment. Proceed with caution!

    MOZ_ASSERT(TypeIDOfType<T>::id == target->type(),
               "calling wrong setFromTypedArray specialization");
    MOZ_ASSERT(!target->hasDetachedBuffer(), "target isn't detached");
    MOZ_ASSERT(!source->hasDetachedBuffer(), "source isn't detached");
    MOZ_ASSERT(TypedArrayObject::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<T*> dest =
        target->dataPointerEither().template cast<T*>() + offset;
    uint32_t len = source->length();

    if (source->type() == target->type()) {
      SharedMem<T*> src = source->dataPointerEither().template cast<T*>();
      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<uint8_t>(sourceByteLen);
    if (!data) {
      return false;
    }
    Ops::memcpy(SharedMem<void*>::unshared(data), source->dataPointerEither(),
                sourceByteLen);

    switch (source->type()) {
      case Scalar::Int8: {
        int8_t* src = static_cast<int8_t*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Uint8:
      case Scalar::Uint8Clamped: {
        uint8_t* src = static_cast<uint8_t*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Int16: {
        int16_t* src = static_cast<int16_t*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Uint16: {
        uint16_t* src = static_cast<uint16_t*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Int32: {
        int32_t* src = static_cast<int32_t*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Uint32: {
        uint32_t* src = static_cast<uint32_t*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Float32: {
        float* src = static_cast<float*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*src++));
        }
        break;
      }
      case Scalar::Float64: {
        double* src = static_cast<double*>(data);
        for (uint32_t i = 0; i < len; ++i) {
          Ops::store(dest++, ConvertNumber<T>(*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>() ? 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() || v.isBigInt());
    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<T>()) {
#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 (TypeIDOfType<T>::id == Scalar::Uint8Clamped) {
      return T(d);
    }
    if (TypeIsUnsigned<T>()) {
      return T(JS::ToUint32(d));
    }
    return T(JS::ToInt32(d));
  }
};

/* static */ gc::AllocKind js::TypedArrayObject::AllocKindForLazyBuffer(
    size_t nbytes) {
  MOZ_ASSERT(nbytes <= INLINE_BUFFER_LIMIT);
  if (nbytes == 0) {
    nbytes += sizeof(uint8_t);
  }
  size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value);
  MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value));
  return gc::GetGCObjectKind(FIXED_DATA_START + dataSlots);
}

}  // namespace js

#endif  // vm_TypedArrayObject_inl_h