blob: 8d39988f1f304cc0b57ff0352fb44ccbddcd15a0 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/bootstrap_natives.h"
#include "include/dart_api.h"
#include "vm/exceptions.h"
#include "vm/native_entry.h"
#include "vm/object.h"
namespace dart {
// TypedData.
// Checks to see if offsetInBytes + num_bytes is in the range.
static void RangeCheck(intptr_t offset_in_bytes,
intptr_t access_size,
intptr_t length_in_bytes,
intptr_t element_size_in_bytes) {
if (!Utils::RangeCheck(offset_in_bytes, access_size, length_in_bytes)) {
const intptr_t index =
(offset_in_bytes + access_size) / element_size_in_bytes;
const intptr_t length =
length_in_bytes / element_size_in_bytes;
Exceptions::ThrowRangeError(
"index", Integer::Handle(Integer::New(index)), 0, length);
}
}
// Checks to see if a length will not result in an OOM error.
static void LengthCheck(intptr_t len, intptr_t max) {
if (len < 0 || len > max) {
const String& error = String::Handle(String::NewFormatted(
"Length (%" Pd ") of object must be in range [0..%" Pd "]",
len, max));
Exceptions::ThrowArgumentError(error);
}
}
static void PeerFinalizer(void* isolate_callback_data,
Dart_WeakPersistentHandle handle,
void* peer) {
OS::AlignedFree(peer);
}
DEFINE_NATIVE_ENTRY(TypedData_length, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0));
if (instance.IsTypedData()) {
const TypedData& array = TypedData::Cast(instance);
return Smi::New(array.Length());
}
if (instance.IsExternalTypedData()) {
const ExternalTypedData& array = ExternalTypedData::Cast(instance);
return Smi::New(array.Length());
}
const String& error = String::Handle(String::NewFormatted(
"Expected a TypedData object but found %s", instance.ToCString()));
Exceptions::ThrowArgumentError(error);
return Integer::null();
}
template <typename DstType, typename SrcType>
static RawBool* CopyData(const Instance& dst, const Instance& src,
const Smi& dst_start, const Smi& src_start,
const Smi& length,
bool clamped) {
const DstType& dst_array = DstType::Cast(dst);
const SrcType& src_array = SrcType::Cast(src);
const intptr_t dst_offset_in_bytes = dst_start.Value();
const intptr_t src_offset_in_bytes = src_start.Value();
const intptr_t length_in_bytes = length.Value();
ASSERT(Utils::RangeCheck(
src_offset_in_bytes, length_in_bytes, src_array.LengthInBytes()));
ASSERT(Utils::RangeCheck(
dst_offset_in_bytes, length_in_bytes, dst_array.LengthInBytes()));
if (clamped) {
TypedData::ClampedCopy<DstType, SrcType>(dst_array, dst_offset_in_bytes,
src_array, src_offset_in_bytes,
length_in_bytes);
} else {
TypedData::Copy<DstType, SrcType>(dst_array, dst_offset_in_bytes,
src_array, src_offset_in_bytes,
length_in_bytes);
}
return Bool::True().raw();
}
static bool IsClamped(intptr_t cid) {
switch (cid) {
case kTypedDataUint8ClampedArrayCid:
case kExternalTypedDataUint8ClampedArrayCid:
case kTypedDataUint8ClampedArrayViewCid:
return true;
default:
return false;
}
}
static bool IsUint8(intptr_t cid) {
switch (cid) {
case kTypedDataUint8ClampedArrayCid:
case kExternalTypedDataUint8ClampedArrayCid:
case kTypedDataUint8ClampedArrayViewCid:
case kTypedDataUint8ArrayCid:
case kExternalTypedDataUint8ArrayCid:
case kTypedDataUint8ArrayViewCid:
return true;
default:
return false;
}
}
DEFINE_NATIVE_ENTRY(TypedData_setRange, 7) {
const Instance& dst = Instance::CheckedHandle(arguments->NativeArgAt(0));
const Smi& dst_start = Smi::CheckedHandle(arguments->NativeArgAt(1));
const Smi& length = Smi::CheckedHandle(arguments->NativeArgAt(2));
const Instance& src = Instance::CheckedHandle(arguments->NativeArgAt(3));
const Smi& src_start = Smi::CheckedHandle(arguments->NativeArgAt(4));
const Smi& to_cid_smi = Smi::CheckedHandle(arguments->NativeArgAt(5));
const Smi& from_cid_smi = Smi::CheckedHandle(arguments->NativeArgAt(6));
if (length.Value() < 0) {
const String& error = String::Handle(String::NewFormatted(
"length (%" Pd ") must be non-negative", length.Value()));
Exceptions::ThrowArgumentError(error);
}
const intptr_t to_cid = to_cid_smi.Value();
const intptr_t from_cid = from_cid_smi.Value();
const bool needs_clamping = IsClamped(to_cid) && !IsUint8(from_cid);
if (dst.IsTypedData()) {
if (src.IsTypedData()) {
return CopyData<TypedData, TypedData>(
dst, src, dst_start, src_start, length, needs_clamping);
} else if (src.IsExternalTypedData()) {
return CopyData<TypedData, ExternalTypedData>(
dst, src, dst_start, src_start, length, needs_clamping);
}
} else if (dst.IsExternalTypedData()) {
if (src.IsTypedData()) {
return CopyData<ExternalTypedData, TypedData>(
dst, src, dst_start, src_start, length, needs_clamping);
} else if (src.IsExternalTypedData()) {
return CopyData<ExternalTypedData, ExternalTypedData>(
dst, src, dst_start, src_start, length, needs_clamping);
}
}
UNREACHABLE();
return Bool::False().raw();
}
// We check the length parameter against a possible maximum length for the
// array based on available physical addressable memory on the system. The
// maximum possible length is a scaled value of kSmiMax which is set up based
// on whether the underlying architecture is 32-bit or 64-bit.
// Argument 0 is type arguments and is ignored.
#define TYPED_DATA_NEW(name) \
DEFINE_NATIVE_ENTRY(TypedData_##name##_new, 2) { \
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(1)); \
intptr_t cid = kTypedData##name##Cid; \
intptr_t len = length.Value(); \
intptr_t max = TypedData::MaxElements(cid); \
LengthCheck(len, max); \
return TypedData::New(cid, len); \
} \
// We check the length parameter against a possible maximum length for the
// array based on available physical addressable memory on the system. The
// maximum possible length is a scaled value of kSmiMax which is set up based
// on whether the underlying architecture is 32-bit or 64-bit.
// Argument 0 is type arguments and is ignored.
#define EXT_TYPED_DATA_NEW(name) \
DEFINE_NATIVE_ENTRY(ExternalTypedData_##name##_new, 2) { \
const int kAlignment = 16; \
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(1)); \
intptr_t cid = kExternalTypedData##name##Cid; \
intptr_t len = length.Value(); \
intptr_t max = ExternalTypedData::MaxElements(cid); \
LengthCheck(len, max); \
intptr_t len_bytes = len * ExternalTypedData::ElementSizeInBytes(cid); \
uint8_t* data = OS::AllocateAlignedArray<uint8_t>(len_bytes, kAlignment); \
const ExternalTypedData& obj = \
ExternalTypedData::Handle(ExternalTypedData::New(cid, data, len)); \
obj.AddFinalizer(data, PeerFinalizer); \
return obj.raw(); \
} \
#define TYPED_DATA_NEW_NATIVE(name) \
TYPED_DATA_NEW(name) \
EXT_TYPED_DATA_NEW(name) \
CLASS_LIST_TYPED_DATA(TYPED_DATA_NEW_NATIVE)
#define TYPED_DATA_GETTER(getter, object, ctor, access_size) \
DEFINE_NATIVE_ENTRY(TypedData_##getter, 2) { \
GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); \
GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, arguments->NativeArgAt(1)); \
if (instance.IsTypedData()) { \
const TypedData& array = TypedData::Cast(instance); \
RangeCheck(offsetInBytes.Value(), access_size, \
array.LengthInBytes(), access_size); \
return object::ctor(array.getter(offsetInBytes.Value())); \
} \
if (instance.IsExternalTypedData()) { \
const ExternalTypedData& array = ExternalTypedData::Cast(instance); \
RangeCheck(offsetInBytes.Value(), access_size, \
array.LengthInBytes(), access_size); \
return object::ctor(array.getter(offsetInBytes.Value())); \
} \
const String& error = String::Handle(String::NewFormatted( \
"Expected a TypedData object but found %s", instance.ToCString())); \
Exceptions::ThrowArgumentError(error); \
return object::null(); \
} \
#define TYPED_DATA_SETTER(setter, \
object, \
get_object_value, \
access_size, \
access_type) \
DEFINE_NATIVE_ENTRY(TypedData_##setter, 3) { \
GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0)); \
GET_NON_NULL_NATIVE_ARGUMENT(Smi, offsetInBytes, arguments->NativeArgAt(1)); \
GET_NON_NULL_NATIVE_ARGUMENT(object, value, arguments->NativeArgAt(2)); \
if (instance.IsTypedData()) { \
const TypedData& array = TypedData::Cast(instance); \
RangeCheck(offsetInBytes.Value(), access_size, \
array.LengthInBytes(), access_size); \
array.setter(offsetInBytes.Value(), \
static_cast<access_type>(value.get_object_value())); \
} else if (instance.IsExternalTypedData()) { \
const ExternalTypedData& array = ExternalTypedData::Cast(instance); \
RangeCheck(offsetInBytes.Value(), access_size, \
array.LengthInBytes(), access_size); \
array.setter(offsetInBytes.Value(), \
static_cast<access_type>(value.get_object_value())); \
} else { \
const String& error = String::Handle(String::NewFormatted( \
"Expected a TypedData object but found %s", instance.ToCString())); \
Exceptions::ThrowArgumentError(error); \
} \
return Object::null(); \
}
#define TYPED_DATA_NATIVES(type_name, \
object, \
ctor, \
get_object_value, \
access_size, \
access_type) \
TYPED_DATA_GETTER(Get##type_name, object, ctor, access_size) \
TYPED_DATA_SETTER(Set##type_name, object, \
get_object_value, access_size, access_type) \
TYPED_DATA_NATIVES(Int8, Integer, New, AsTruncatedUint32Value, 1, int8_t)
TYPED_DATA_NATIVES(Uint8, Integer, New, AsTruncatedUint32Value, 1, uint8_t)
TYPED_DATA_NATIVES(Int16, Integer, New, AsTruncatedUint32Value, 2, int16_t)
TYPED_DATA_NATIVES(Uint16, Integer, New, AsTruncatedUint32Value, 2, uint16_t)
TYPED_DATA_NATIVES(Int32, Integer, New, AsTruncatedUint32Value, 4, int32_t)
TYPED_DATA_NATIVES(Uint32, Integer, New, AsTruncatedUint32Value, 4, uint32_t)
TYPED_DATA_NATIVES(Int64, Integer, New, AsTruncatedInt64Value, 8, int64_t)
TYPED_DATA_NATIVES(
Uint64, Integer, NewFromUint64, AsTruncatedInt64Value, 8, uint64_t)
TYPED_DATA_NATIVES(Float32, Double, New, value, 4, float)
TYPED_DATA_NATIVES(Float64, Double, New, value, 8, double)
TYPED_DATA_NATIVES(Float32x4, Float32x4, New, value, 16, simd128_value_t)
TYPED_DATA_NATIVES(Int32x4, Int32x4, New, value, 16, simd128_value_t)
TYPED_DATA_NATIVES(Float64x2, Float64x2, New, value, 16, simd128_value_t)
DEFINE_NATIVE_ENTRY(ByteData_ToEndianInt16, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Smi, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
int16_t value = host_value.Value();
if (little_endian.value()) {
value = Utils::HostToLittleEndian16(value);
} else {
value = Utils::HostToBigEndian16(value);
}
return Smi::New(value);
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianUint16, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Smi, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
uint16_t value = host_value.Value();
if (little_endian.value()) {
return Smi::New(Utils::HostToLittleEndian16(value));
}
return Smi::New(Utils::HostToBigEndian16(value));
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianInt32, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
ASSERT((host_value.AsInt64Value() >= kMinInt32) ||
(host_value.AsInt64Value() <= kMaxInt32));
int32_t value = static_cast<int32_t>(host_value.AsInt64Value());
if (little_endian.value()) {
value = Utils::HostToLittleEndian32(value);
} else {
value = Utils::HostToBigEndian32(value);
}
return Integer::New(value);
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianUint32, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
ASSERT(host_value.AsInt64Value() <= kMaxUint32);
uint32_t value = static_cast<uint32_t>(host_value.AsInt64Value());
if (little_endian.value()) {
value = Utils::HostToLittleEndian32(value);
} else {
value = Utils::HostToBigEndian32(value);
}
return Integer::New(value);
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianInt64, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
int64_t value = host_value.AsInt64Value();
if (little_endian.value()) {
value = Utils::HostToLittleEndian64(value);
} else {
value = Utils::HostToBigEndian64(value);
}
return Integer::New(value);
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianUint64, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Integer, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
uint64_t value;
if (host_value.IsBigint()) {
const Bigint& bigint = Bigint::Cast(host_value);
ASSERT(bigint.FitsIntoUint64());
value = bigint.AsUint64Value();
} else {
ASSERT(host_value.IsMint() || host_value.IsSmi());
value = host_value.AsInt64Value();
}
if (little_endian.value()) {
value = Utils::HostToLittleEndian64(value);
} else {
value = Utils::HostToBigEndian64(value);
}
return Integer::NewFromUint64(value);
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianFloat32, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Double, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
float value = host_value.value();
if (little_endian.value()) {
value = bit_cast<float>(
Utils::HostToLittleEndian32(bit_cast<uint32_t>(value)));
} else {
value = bit_cast<float>(
Utils::HostToBigEndian32(bit_cast<uint32_t>(value)));
}
return Double::New(value);
}
DEFINE_NATIVE_ENTRY(ByteData_ToEndianFloat64, 2) {
GET_NON_NULL_NATIVE_ARGUMENT(Double, host_value, arguments->NativeArgAt(0));
GET_NON_NULL_NATIVE_ARGUMENT(Bool, little_endian, arguments->NativeArgAt(1));
double value = host_value.value();
if (little_endian.value()) {
value = bit_cast<double>(
Utils::HostToLittleEndian64(bit_cast<uint64_t>(value)));
} else {
value = bit_cast<double>(
Utils::HostToBigEndian64(bit_cast<uint64_t>(value)));
}
return Double::New(value);
}
} // namespace dart