[builtins]: Simple port of %TypedArray%.prototype.set() to CSA TFJ.
- Fast path for same type source typed array - Move previous CPP implementation into a runtime function "TypedArraySet" - Remove parts covered by the TFJ - Basic receiver, offset, source checks - Handling of same type source typed array Bug: v8:3590 Change-Id: I0f19d961424c30cc8bbcb8648b623e7e6dfa33f4 Reviewed-on: https://chromium-review.googlesource.com/786414 Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Commit-Queue: Jakob Gruber <jgruber@chromium.org> Cr-Commit-Position: refs/heads/master@{#49608}
This commit is contained in:
parent
0bc1b967f2
commit
5a88950c8f
@ -1052,7 +1052,7 @@ namespace internal {
|
||||
/* ES6 #sec-%typedarray%.prototype.reverse */ \
|
||||
CPP(TypedArrayPrototypeReverse) \
|
||||
/* ES6 %TypedArray%.prototype.set */ \
|
||||
CPP(TypedArrayPrototypeSet) \
|
||||
TFJ(TypedArrayPrototypeSet, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
|
||||
/* ES6 #sec-%typedarray%.prototype.slice */ \
|
||||
CPP(TypedArrayPrototypeSlice) \
|
||||
/* ES6 #sec-get-%typedarray%.prototype-@@tostringtag */ \
|
||||
|
@ -10,6 +10,10 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
using compiler::Node;
|
||||
template <class T>
|
||||
using TNode = compiler::TNode<T>;
|
||||
|
||||
// This is needed for gc_mole which will compile this file without the full set
|
||||
// of GN defined macros.
|
||||
#ifndef V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP
|
||||
@ -41,9 +45,22 @@ class TypedArrayBuiltinsAssembler : public CodeStubAssembler {
|
||||
Node* CalculateExternalPointer(Node* backing_store, Node* byte_offset);
|
||||
Node* LoadDataPtr(Node* typed_array);
|
||||
Node* ByteLengthIsValid(Node* byte_length);
|
||||
|
||||
// Loads the element kind of TypedArray instance.
|
||||
TNode<Word32T> LoadElementsKind(TNode<Object> typed_array);
|
||||
|
||||
// Returns the byte size of an element for a TypedArray elements kind.
|
||||
TNode<IntPtrT> GetTypedArrayElementSize(TNode<Word32T> elements_kind);
|
||||
|
||||
// Fast path for setting a TypedArray (source) onto another TypedArray
|
||||
// (target) at an element offset. Currently, only handles when the source and
|
||||
// target types match.
|
||||
void SetTypedArraySource(TNode<Context> context, TNode<HeapObject> source,
|
||||
TNode<HeapObject> target, TNode<IntPtrT> offset,
|
||||
Label* if_not_same_type);
|
||||
};
|
||||
|
||||
compiler::Node* TypedArrayBuiltinsAssembler::LoadMapForType(Node* array) {
|
||||
Node* TypedArrayBuiltinsAssembler::LoadMapForType(Node* array) {
|
||||
CSA_ASSERT(this, IsJSTypedArray(array));
|
||||
|
||||
Label unreachable(this), done(this);
|
||||
@ -96,8 +113,8 @@ compiler::Node* TypedArrayBuiltinsAssembler::LoadMapForType(Node* array) {
|
||||
// can't allocate an array bigger than our 32-bit arithmetic range anyway. 64
|
||||
// bit platforms could theoretically have an offset up to 2^35 - 1, so we may
|
||||
// need to convert the float heap number to an intptr.
|
||||
compiler::Node* TypedArrayBuiltinsAssembler::CalculateExternalPointer(
|
||||
Node* backing_store, Node* byte_offset) {
|
||||
Node* TypedArrayBuiltinsAssembler::CalculateExternalPointer(Node* backing_store,
|
||||
Node* byte_offset) {
|
||||
return IntPtrAdd(backing_store,
|
||||
ChangeNonnegativeNumberToUintPtr(byte_offset));
|
||||
}
|
||||
@ -519,7 +536,7 @@ TF_BUILTIN(TypedArrayConstructByArrayBuffer, TypedArrayBuiltinsAssembler) {
|
||||
}
|
||||
}
|
||||
|
||||
compiler::Node* TypedArrayBuiltinsAssembler::LoadDataPtr(Node* typed_array) {
|
||||
Node* TypedArrayBuiltinsAssembler::LoadDataPtr(Node* typed_array) {
|
||||
CSA_ASSERT(this, IsJSTypedArray(typed_array));
|
||||
Node* elements = LoadElements(typed_array);
|
||||
CSA_ASSERT(this, IsFixedTypedArray(elements));
|
||||
@ -530,8 +547,7 @@ compiler::Node* TypedArrayBuiltinsAssembler::LoadDataPtr(Node* typed_array) {
|
||||
return IntPtrAdd(base_pointer, external_pointer);
|
||||
}
|
||||
|
||||
compiler::Node* TypedArrayBuiltinsAssembler::ByteLengthIsValid(
|
||||
Node* byte_length) {
|
||||
Node* TypedArrayBuiltinsAssembler::ByteLengthIsValid(Node* byte_length) {
|
||||
Label smi(this), done(this);
|
||||
VARIABLE(is_valid, MachineRepresentation::kWord32);
|
||||
GotoIf(TaggedIsSmi(byte_length), &smi);
|
||||
@ -675,6 +691,173 @@ TF_BUILTIN(TypedArrayPrototypeLength, TypedArrayBuiltinsAssembler) {
|
||||
JSTypedArray::kLengthOffset);
|
||||
}
|
||||
|
||||
TNode<Word32T> TypedArrayBuiltinsAssembler::LoadElementsKind(
|
||||
TNode<Object> typed_array) {
|
||||
CSA_ASSERT(this, IsJSTypedArray(typed_array));
|
||||
return Int32Sub(LoadMapElementsKind(LoadMap(CAST(typed_array))),
|
||||
Int32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND));
|
||||
}
|
||||
|
||||
TNode<IntPtrT> TypedArrayBuiltinsAssembler::GetTypedArrayElementSize(
|
||||
TNode<Word32T> elements_kind) {
|
||||
TVARIABLE(IntPtrT, element_size);
|
||||
Label next(this), if_unknown_type(this, Label::kDeferred);
|
||||
|
||||
size_t const kTypedElementsKindCount = LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND -
|
||||
FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND +
|
||||
1;
|
||||
|
||||
int32_t elements_kinds[kTypedElementsKindCount] = {
|
||||
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
|
||||
TYPE##_ELEMENTS - FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND,
|
||||
TYPED_ARRAYS(TYPED_ARRAY_CASE)
|
||||
#undef TYPED_ARRAY_CASE
|
||||
};
|
||||
|
||||
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
|
||||
Label if_##type##array(this);
|
||||
TYPED_ARRAYS(TYPED_ARRAY_CASE)
|
||||
#undef TYPED_ARRAY_CASE
|
||||
|
||||
Label* elements_kind_labels[kTypedElementsKindCount] = {
|
||||
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) &if_##type##array,
|
||||
TYPED_ARRAYS(TYPED_ARRAY_CASE)
|
||||
#undef TYPED_ARRAY_CASE
|
||||
};
|
||||
|
||||
Switch(elements_kind, &if_unknown_type, elements_kinds, elements_kind_labels,
|
||||
kTypedElementsKindCount);
|
||||
|
||||
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
|
||||
BIND(&if_##type##array); \
|
||||
{ \
|
||||
element_size = IntPtrConstant(size); \
|
||||
Goto(&next); \
|
||||
}
|
||||
TYPED_ARRAYS(TYPED_ARRAY_CASE)
|
||||
#undef TYPED_ARRAY_CASE
|
||||
|
||||
BIND(&if_unknown_type);
|
||||
{
|
||||
element_size = IntPtrConstant(0);
|
||||
Goto(&next);
|
||||
}
|
||||
BIND(&next);
|
||||
return element_size;
|
||||
}
|
||||
|
||||
void TypedArrayBuiltinsAssembler::SetTypedArraySource(TNode<Context> context,
|
||||
TNode<HeapObject> source,
|
||||
TNode<HeapObject> target,
|
||||
TNode<IntPtrT> offset,
|
||||
Label* if_not_same_type) {
|
||||
CSA_ASSERT(this, IsJSTypedArray(source));
|
||||
CSA_ASSERT(this, IsJSTypedArray(target));
|
||||
CSA_ASSERT(this, IntPtrGreaterThanOrEqual(offset, IntPtrConstant(0)));
|
||||
CSA_ASSERT(this,
|
||||
IntPtrLessThanOrEqual(offset, IntPtrConstant(Smi::kMaxValue)));
|
||||
|
||||
Label next(this), if_source_too_large(this, Label::kDeferred);
|
||||
|
||||
// TODO(pwong): Widen this fast path. See CopyElementsHandleFromTypedArray for
|
||||
// the corresponding check in runtime.
|
||||
TNode<Word32T> source_el_kind = LoadElementsKind(source);
|
||||
TNode<Word32T> target_el_kind = LoadElementsKind(target);
|
||||
GotoIfNot(Word32Equal(source_el_kind, target_el_kind), if_not_same_type);
|
||||
|
||||
TNode<IntPtrT> source_byte_length =
|
||||
LoadAndUntagObjectField(source, JSTypedArray::kByteLengthOffset);
|
||||
TNode<IntPtrT> target_byte_length =
|
||||
LoadAndUntagObjectField(target, JSTypedArray::kByteLengthOffset);
|
||||
TNode<IntPtrT> source_el_size = GetTypedArrayElementSize(source_el_kind);
|
||||
TNode<IntPtrT> required_byte_length =
|
||||
IntPtrAdd(IntPtrMul(offset, source_el_size), source_byte_length);
|
||||
GotoIf(IntPtrGreaterThan(required_byte_length, target_byte_length),
|
||||
&if_source_too_large);
|
||||
|
||||
// If source and target are the same TypedArray type.
|
||||
{
|
||||
TNode<ExternalReference> memmove =
|
||||
ExternalConstant(ExternalReference::libc_memmove_function(isolate()));
|
||||
TNode<IntPtrT> target_el_size = GetTypedArrayElementSize(target_el_kind);
|
||||
TNode<IntPtrT> target_data_ptr =
|
||||
UncheckedCast<IntPtrT>(LoadDataPtr(target));
|
||||
TNode<IntPtrT> source_data_ptr =
|
||||
UncheckedCast<IntPtrT>(LoadDataPtr(source));
|
||||
TNode<IntPtrT> target_start =
|
||||
IntPtrAdd(target_data_ptr, IntPtrMul(offset, target_el_size));
|
||||
CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(),
|
||||
MachineType::Pointer(), MachineType::UintPtr(), memmove,
|
||||
target_start, source_data_ptr, source_byte_length);
|
||||
Goto(&next);
|
||||
}
|
||||
|
||||
BIND(&if_source_too_large);
|
||||
ThrowRangeError(context, MessageTemplate::kTypedArraySetSourceTooLarge);
|
||||
|
||||
BIND(&next);
|
||||
}
|
||||
|
||||
// ES #sec-get-%typedarray%.prototype.set
|
||||
TF_BUILTIN(TypedArrayPrototypeSet, TypedArrayBuiltinsAssembler) {
|
||||
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
|
||||
CodeStubArguments args(
|
||||
this, ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount)));
|
||||
|
||||
Label if_offset_is_out_of_bounds(this, Label::kDeferred),
|
||||
if_receiver_is_neutered(this, Label::kDeferred),
|
||||
if_receiver_is_not_typedarray(this, Label::kDeferred);
|
||||
|
||||
// Check the receiver is a typed array.
|
||||
TNode<Object> receiver = args.GetReceiver();
|
||||
GotoIf(TaggedIsSmi(receiver), &if_receiver_is_not_typedarray);
|
||||
GotoIfNot(IsJSTypedArray(receiver), &if_receiver_is_not_typedarray);
|
||||
|
||||
// Normalize offset argument (using ToInteger) and handle heap number cases.
|
||||
TNode<Object> offset = args.GetOptionalArgumentValue(1, SmiConstant(0));
|
||||
TNode<Number> offset_num = ToInteger(context, offset, kTruncateMinusZero);
|
||||
CSA_ASSERT(this, IsNumberNormalized(offset_num));
|
||||
|
||||
// Since ToInteger always returns a Smi if the given value is within Smi
|
||||
// range, and the only corner case of -0.0 has already been truncated to 0.0,
|
||||
// we can simply throw unless the offset is a non-negative Smi.
|
||||
GotoIfNot(TaggedIsPositiveSmi(offset_num), &if_offset_is_out_of_bounds);
|
||||
TNode<Smi> offset_smi = CAST(offset_num);
|
||||
|
||||
// Check the receiver is not neutered.
|
||||
TNode<Object> receiver_buffer =
|
||||
LoadObjectField(CAST(receiver), JSTypedArray::kBufferOffset);
|
||||
GotoIf(IsDetachedBuffer(receiver_buffer), &if_receiver_is_neutered);
|
||||
|
||||
// Check the source argument is valid and whether a fast path can be taken.
|
||||
Label call_runtime(this);
|
||||
TNode<Object> source = args.GetOptionalArgumentValue(0);
|
||||
GotoIf(TaggedIsSmi(source), &call_runtime);
|
||||
GotoIfNot(IsJSTypedArray(source), &call_runtime);
|
||||
|
||||
// Fast path for a typed array source argument.
|
||||
{
|
||||
SetTypedArraySource(context, CAST(source), CAST(receiver),
|
||||
SmiUntag(offset_smi), &call_runtime);
|
||||
args.PopAndReturn(UndefinedConstant());
|
||||
}
|
||||
|
||||
// TODO(pwong): This is an opportunity to add a fast path for fast JS Array.
|
||||
BIND(&call_runtime);
|
||||
args.PopAndReturn(CallRuntime(Runtime::kTypedArraySet, context, receiver,
|
||||
source, offset_smi));
|
||||
|
||||
BIND(&if_offset_is_out_of_bounds);
|
||||
ThrowRangeError(context, MessageTemplate::kTypedArraySetOffsetOutOfBounds);
|
||||
|
||||
BIND(&if_receiver_is_neutered);
|
||||
ThrowTypeError(context, MessageTemplate::kDetachedOperation,
|
||||
"%TypedArray%.prototype.set");
|
||||
|
||||
BIND(&if_receiver_is_not_typedarray);
|
||||
ThrowTypeError(context, MessageTemplate::kNotTypedArray);
|
||||
}
|
||||
|
||||
// ES #sec-get-%typedarray%.prototype-@@tostringtag
|
||||
TF_BUILTIN(TypedArrayPrototypeToStringTag, TypedArrayBuiltinsAssembler) {
|
||||
Node* receiver = Parameter(Descriptor::kReceiver);
|
||||
|
@ -277,263 +277,6 @@ BUILTIN(TypedArrayPrototypeReverse) {
|
||||
return *array;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Object* TypedArrayCopyElements(Handle<JSTypedArray> target,
|
||||
Handle<JSReceiver> source, uint32_t length,
|
||||
uint32_t offset) {
|
||||
ElementsAccessor* accessor = target->GetElementsAccessor();
|
||||
return accessor->CopyElements(source, target, length, offset);
|
||||
}
|
||||
|
||||
enum class TypedArraySetResultCodes {
|
||||
// Set from typed array of the same type.
|
||||
// This is processed by TypedArraySetFastCases
|
||||
SAME_TYPE,
|
||||
// Set from typed array of the different type, overlapping in memory.
|
||||
OVERLAPPING,
|
||||
// Set from typed array of the different type, non-overlapping.
|
||||
NONOVERLAPPING,
|
||||
// Set from non-typed array.
|
||||
NON_TYPED_ARRAY
|
||||
};
|
||||
|
||||
MaybeHandle<Object> TypedArraySetFromOverlapping(Isolate* isolate,
|
||||
Handle<JSTypedArray> target,
|
||||
Handle<JSTypedArray> source,
|
||||
uint32_t offset) {
|
||||
DCHECK_GE(offset, 0);
|
||||
|
||||
size_t sourceElementSize = source->element_size();
|
||||
size_t targetElementSize = target->element_size();
|
||||
|
||||
uint32_t source_length = source->length_value();
|
||||
if (source_length == 0) return target;
|
||||
|
||||
// Copy left part.
|
||||
|
||||
// First un-mutated byte after the next write
|
||||
uint32_t target_ptr = 0;
|
||||
CHECK(target->byte_offset()->ToUint32(&target_ptr));
|
||||
target_ptr += (offset + 1) * targetElementSize;
|
||||
|
||||
// Next read at sourcePtr. We do not care for memory changing before
|
||||
// sourcePtr - we have already copied it.
|
||||
uint32_t source_ptr = 0;
|
||||
CHECK(source->byte_offset()->ToUint32(&source_ptr));
|
||||
|
||||
ElementsAccessor* source_accessor = source->GetElementsAccessor();
|
||||
ElementsAccessor* target_accessor = target->GetElementsAccessor();
|
||||
|
||||
uint32_t left_index;
|
||||
for (left_index = 0; left_index < source_length && target_ptr <= source_ptr;
|
||||
left_index++) {
|
||||
Handle<Object> value = source_accessor->Get(source, left_index);
|
||||
target_accessor->Set(target, offset + left_index, *value);
|
||||
|
||||
target_ptr += targetElementSize;
|
||||
source_ptr += sourceElementSize;
|
||||
}
|
||||
|
||||
// Copy right part;
|
||||
// First unmutated byte before the next write
|
||||
CHECK(target->byte_offset()->ToUint32(&target_ptr));
|
||||
target_ptr += (offset + source_length - 1) * targetElementSize;
|
||||
|
||||
// Next read before sourcePtr. We do not care for memory changing after
|
||||
// sourcePtr - we have already copied it.
|
||||
CHECK(target->byte_offset()->ToUint32(&source_ptr));
|
||||
source_ptr += source_length * sourceElementSize;
|
||||
|
||||
uint32_t right_index;
|
||||
DCHECK_GE(source_length, 1);
|
||||
for (right_index = source_length - 1;
|
||||
right_index > left_index && target_ptr >= source_ptr; right_index--) {
|
||||
Handle<Object> value = source_accessor->Get(source, right_index);
|
||||
target_accessor->Set(target, offset + right_index, *value);
|
||||
|
||||
target_ptr -= targetElementSize;
|
||||
source_ptr -= sourceElementSize;
|
||||
}
|
||||
|
||||
std::vector<Handle<Object>> temp(right_index + 1 - left_index);
|
||||
|
||||
for (uint32_t i = left_index; i <= right_index; i++) {
|
||||
temp[i - left_index] = source_accessor->Get(source, i);
|
||||
}
|
||||
|
||||
for (uint32_t i = left_index; i <= right_index; i++) {
|
||||
target_accessor->Set(target, offset + i, *temp[i - left_index]);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
MaybeHandle<Smi> TypedArraySetFastCases(Isolate* isolate,
|
||||
Handle<JSTypedArray> target,
|
||||
Handle<Object> source_obj,
|
||||
Handle<Object> offset_obj) {
|
||||
if (!source_obj->IsJSTypedArray()) {
|
||||
return MaybeHandle<Smi>(
|
||||
Smi::FromEnum(TypedArraySetResultCodes::NON_TYPED_ARRAY), isolate);
|
||||
}
|
||||
|
||||
Handle<JSTypedArray> source = Handle<JSTypedArray>::cast(source_obj);
|
||||
|
||||
size_t offset = 0;
|
||||
CHECK(TryNumberToSize(*offset_obj, &offset));
|
||||
size_t target_length = target->length_value();
|
||||
size_t source_length = source->length_value();
|
||||
size_t target_byte_length = NumberToSize(target->byte_length());
|
||||
size_t source_byte_length = NumberToSize(source->byte_length());
|
||||
if (offset > target_length || offset + source_length > target_length ||
|
||||
offset + source_length < offset) { // overflow
|
||||
THROW_NEW_ERROR(
|
||||
isolate, NewRangeError(MessageTemplate::kTypedArraySetSourceTooLarge),
|
||||
Smi);
|
||||
}
|
||||
|
||||
size_t target_offset = NumberToSize(target->byte_offset());
|
||||
size_t source_offset = NumberToSize(source->byte_offset());
|
||||
uint8_t* target_base =
|
||||
static_cast<uint8_t*>(target->GetBuffer()->backing_store()) +
|
||||
target_offset;
|
||||
uint8_t* source_base =
|
||||
static_cast<uint8_t*>(source->GetBuffer()->backing_store()) +
|
||||
source_offset;
|
||||
|
||||
// Typed arrays of the same type: use memmove.
|
||||
if (target->type() == source->type()) {
|
||||
memmove(target_base + offset * target->element_size(), source_base,
|
||||
source_byte_length);
|
||||
return MaybeHandle<Smi>(Smi::FromEnum(TypedArraySetResultCodes::SAME_TYPE),
|
||||
isolate);
|
||||
}
|
||||
|
||||
// Typed arrays of different types over the same backing store
|
||||
if ((source_base <= target_base &&
|
||||
source_base + source_byte_length > target_base) ||
|
||||
(target_base <= source_base &&
|
||||
target_base + target_byte_length > source_base)) {
|
||||
// We do not support overlapping ArrayBuffers
|
||||
DCHECK(target->GetBuffer()->backing_store() ==
|
||||
source->GetBuffer()->backing_store());
|
||||
return MaybeHandle<Smi>(
|
||||
Smi::FromEnum(TypedArraySetResultCodes::OVERLAPPING), isolate);
|
||||
} else { // Non-overlapping typed arrays
|
||||
return MaybeHandle<Smi>(
|
||||
Smi::FromEnum(TypedArraySetResultCodes::NONOVERLAPPING), isolate);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// 22.2.3.23%TypedArray%.prototype.set ( overloaded [ , offset ] )
|
||||
BUILTIN(TypedArrayPrototypeSet) {
|
||||
HandleScope scope(isolate);
|
||||
|
||||
Handle<Object> target = args.receiver();
|
||||
Handle<Object> obj = args.atOrUndefined(isolate, 1);
|
||||
Handle<Object> offset = args.atOrUndefined(isolate, 2);
|
||||
const char* method = "%TypedArray%.prototype.set";
|
||||
|
||||
if (!target->IsJSTypedArray()) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewTypeError(MessageTemplate::kNotTypedArray));
|
||||
}
|
||||
|
||||
if (offset->IsUndefined(isolate)) {
|
||||
offset = Handle<Object>(Smi::kZero, isolate);
|
||||
} else {
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, offset,
|
||||
Object::ToInteger(isolate, offset));
|
||||
}
|
||||
|
||||
if (offset->Number() < 0) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewRangeError(MessageTemplate::kTypedArraySetNegativeOffset));
|
||||
}
|
||||
|
||||
if (offset->Number() > Smi::kMaxValue) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewRangeError(MessageTemplate::kTypedArraySetSourceTooLarge));
|
||||
}
|
||||
|
||||
Handle<JSTypedArray> target_array = Handle<JSTypedArray>::cast(target);
|
||||
if (V8_UNLIKELY(target_array->WasNeutered())) {
|
||||
const MessageTemplate::Template message =
|
||||
MessageTemplate::kDetachedOperation;
|
||||
Handle<String> operation =
|
||||
isolate->factory()->NewStringFromAsciiChecked(method);
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(isolate, NewTypeError(message, operation));
|
||||
}
|
||||
|
||||
uint32_t uint_offset;
|
||||
CHECK(offset->ToUint32(&uint_offset));
|
||||
|
||||
// TODO(cwhan.tunz): Implement CopyElements for overlapping cases, and use
|
||||
// TypedArrayCopyElements for all case instead of this result code based
|
||||
// branches
|
||||
Handle<Smi> result_code;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, result_code,
|
||||
TypedArraySetFastCases(isolate, target_array, obj, offset));
|
||||
|
||||
switch (static_cast<TypedArraySetResultCodes>(result_code->value())) {
|
||||
case TypedArraySetResultCodes::SAME_TYPE: {
|
||||
break;
|
||||
}
|
||||
case TypedArraySetResultCodes::OVERLAPPING: {
|
||||
RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, TypedArraySetFromOverlapping(isolate, target_array,
|
||||
Handle<JSTypedArray>::cast(obj),
|
||||
uint_offset));
|
||||
break;
|
||||
}
|
||||
case TypedArraySetResultCodes::NONOVERLAPPING: {
|
||||
return TypedArrayCopyElements(
|
||||
target_array, Handle<JSTypedArray>::cast(obj),
|
||||
Handle<JSTypedArray>::cast(obj)->length_value(), uint_offset);
|
||||
break;
|
||||
}
|
||||
case TypedArraySetResultCodes::NON_TYPED_ARRAY: {
|
||||
if (obj->IsNumber()) {
|
||||
// For number as a first argument, throw TypeError
|
||||
// instead of silently ignoring the call, so that
|
||||
// users know they did something wrong.
|
||||
// (Consistent with Firefox and Blink/WebKit)
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
|
||||
}
|
||||
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, obj,
|
||||
Object::ToObject(isolate, obj));
|
||||
|
||||
Handle<Object> len;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, len,
|
||||
Object::GetProperty(obj, isolate->factory()->length_string()));
|
||||
if (len->IsUndefined(isolate)) {
|
||||
break;
|
||||
}
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
|
||||
Object::ToLength(isolate, len));
|
||||
|
||||
DCHECK_GE(uint_offset, 0);
|
||||
if (uint_offset + len->Number() > target_array->length_value()) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate,
|
||||
NewRangeError(MessageTemplate::kTypedArraySetSourceTooLarge));
|
||||
}
|
||||
uint32_t int_l;
|
||||
CHECK(DoubleToUint32IfEqualToSelf(len->Number(), &int_l));
|
||||
return TypedArrayCopyElements(target_array, Handle<JSReceiver>::cast(obj),
|
||||
int_l, uint_offset);
|
||||
} break;
|
||||
}
|
||||
|
||||
return *isolate->factory()->undefined_value();
|
||||
}
|
||||
|
||||
BUILTIN(TypedArrayPrototypeSlice) {
|
||||
HandleScope scope(isolate);
|
||||
|
||||
|
@ -3972,6 +3972,23 @@ Node* CodeStubAssembler::ThrowIfNotJSReceiver(
|
||||
return var_value_map.value();
|
||||
}
|
||||
|
||||
void CodeStubAssembler::ThrowRangeError(Node* context,
|
||||
MessageTemplate::Template message,
|
||||
Node* arg0, Node* arg1, Node* arg2) {
|
||||
Node* template_index = SmiConstant(message);
|
||||
if (arg0 == nullptr) {
|
||||
CallRuntime(Runtime::kThrowRangeError, context, template_index);
|
||||
} else if (arg1 == nullptr) {
|
||||
CallRuntime(Runtime::kThrowRangeError, context, template_index, arg0);
|
||||
} else if (arg2 == nullptr) {
|
||||
CallRuntime(Runtime::kThrowRangeError, context, template_index, arg0, arg1);
|
||||
} else {
|
||||
CallRuntime(Runtime::kThrowRangeError, context, template_index, arg0, arg1,
|
||||
arg2);
|
||||
}
|
||||
Unreachable();
|
||||
}
|
||||
|
||||
void CodeStubAssembler::ThrowTypeError(Node* context,
|
||||
MessageTemplate::Template message,
|
||||
char const* arg0, char const* arg1) {
|
||||
|
@ -1015,6 +1015,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
|
||||
Node* ThrowIfNotJSReceiver(Node* context, Node* value,
|
||||
MessageTemplate::Template msg_template,
|
||||
const char* method_name = nullptr);
|
||||
|
||||
void ThrowRangeError(Node* context, MessageTemplate::Template message,
|
||||
Node* arg0 = nullptr, Node* arg1 = nullptr,
|
||||
Node* arg2 = nullptr);
|
||||
void ThrowTypeError(Node* context, MessageTemplate::Template message,
|
||||
char const* arg0 = nullptr, char const* arg1 = nullptr);
|
||||
void ThrowTypeError(Node* context, MessageTemplate::Template message,
|
||||
|
@ -551,7 +551,7 @@ class ErrorUtils : public AllStatic {
|
||||
T(ToPrecisionFormatRange, \
|
||||
"toPrecision() argument must be between 1 and 100") \
|
||||
T(ToRadixFormatRange, "toString() radix argument must be between 2 and 36") \
|
||||
T(TypedArraySetNegativeOffset, "Start offset is negative") \
|
||||
T(TypedArraySetOffsetOutOfBounds, "offset is out of bounds") \
|
||||
T(TypedArraySetSourceTooLarge, "Source is too large") \
|
||||
T(UnsupportedTimeZone, "Unsupported time zone specified %") \
|
||||
T(ValueOutOfRange, "Value % out of range for % options property %") \
|
||||
|
@ -214,5 +214,218 @@ RUNTIME_FUNCTION(Runtime_TypedArraySpeciesCreateByLength) {
|
||||
return *result_array;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Object* TypedArrayCopyElements(Handle<JSTypedArray> target,
|
||||
Handle<JSReceiver> source, uint32_t length,
|
||||
uint32_t offset) {
|
||||
ElementsAccessor* accessor = target->GetElementsAccessor();
|
||||
return accessor->CopyElements(source, target, length, offset);
|
||||
}
|
||||
|
||||
enum class TypedArraySetResultCodes {
|
||||
// Set from typed array of the different type, overlapping in memory.
|
||||
OVERLAPPING,
|
||||
// Set from typed array of the different type, non-overlapping.
|
||||
NONOVERLAPPING,
|
||||
// Set from non-typed array.
|
||||
NON_TYPED_ARRAY,
|
||||
};
|
||||
|
||||
MaybeHandle<Object> TypedArraySetFromOverlapping(Isolate* isolate,
|
||||
Handle<JSTypedArray> target,
|
||||
Handle<JSTypedArray> source,
|
||||
uint32_t offset) {
|
||||
DCHECK_GE(offset, 0);
|
||||
|
||||
size_t sourceElementSize = source->element_size();
|
||||
size_t targetElementSize = target->element_size();
|
||||
|
||||
uint32_t source_length = source->length_value();
|
||||
if (source_length == 0) return target;
|
||||
|
||||
// Copy left part.
|
||||
|
||||
// First un-mutated byte after the next write
|
||||
uint32_t target_ptr = 0;
|
||||
CHECK(target->byte_offset()->ToUint32(&target_ptr));
|
||||
target_ptr += (offset + 1) * targetElementSize;
|
||||
|
||||
// Next read at sourcePtr. We do not care for memory changing before
|
||||
// sourcePtr - we have already copied it.
|
||||
uint32_t source_ptr = 0;
|
||||
CHECK(source->byte_offset()->ToUint32(&source_ptr));
|
||||
|
||||
ElementsAccessor* source_accessor = source->GetElementsAccessor();
|
||||
ElementsAccessor* target_accessor = target->GetElementsAccessor();
|
||||
|
||||
uint32_t left_index;
|
||||
for (left_index = 0; left_index < source_length && target_ptr <= source_ptr;
|
||||
left_index++) {
|
||||
Handle<Object> value = source_accessor->Get(source, left_index);
|
||||
target_accessor->Set(target, offset + left_index, *value);
|
||||
|
||||
target_ptr += targetElementSize;
|
||||
source_ptr += sourceElementSize;
|
||||
}
|
||||
|
||||
// Copy right part;
|
||||
// First unmutated byte before the next write
|
||||
CHECK(target->byte_offset()->ToUint32(&target_ptr));
|
||||
target_ptr += (offset + source_length - 1) * targetElementSize;
|
||||
|
||||
// Next read before sourcePtr. We do not care for memory changing after
|
||||
// sourcePtr - we have already copied it.
|
||||
CHECK(target->byte_offset()->ToUint32(&source_ptr));
|
||||
source_ptr += source_length * sourceElementSize;
|
||||
|
||||
uint32_t right_index;
|
||||
DCHECK_GE(source_length, 1);
|
||||
for (right_index = source_length - 1;
|
||||
right_index > left_index && target_ptr >= source_ptr; right_index--) {
|
||||
Handle<Object> value = source_accessor->Get(source, right_index);
|
||||
target_accessor->Set(target, offset + right_index, *value);
|
||||
|
||||
target_ptr -= targetElementSize;
|
||||
source_ptr -= sourceElementSize;
|
||||
}
|
||||
|
||||
std::vector<Handle<Object>> temp(right_index + 1 - left_index);
|
||||
|
||||
for (uint32_t i = left_index; i <= right_index; i++) {
|
||||
temp[i - left_index] = source_accessor->Get(source, i);
|
||||
}
|
||||
|
||||
for (uint32_t i = left_index; i <= right_index; i++) {
|
||||
target_accessor->Set(target, offset + i, *temp[i - left_index]);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
MaybeHandle<Smi> TypedArraySetFastCases(Isolate* isolate,
|
||||
Handle<JSTypedArray> target,
|
||||
Handle<Object> source_obj,
|
||||
Handle<Object> offset_obj) {
|
||||
if (!source_obj->IsJSTypedArray()) {
|
||||
return MaybeHandle<Smi>(
|
||||
Smi::FromEnum(TypedArraySetResultCodes::NON_TYPED_ARRAY), isolate);
|
||||
}
|
||||
|
||||
Handle<JSTypedArray> source = Handle<JSTypedArray>::cast(source_obj);
|
||||
DCHECK_NE(target->type(), source->type()); // Handled in SetTypedArraySource.
|
||||
|
||||
size_t offset = 0;
|
||||
CHECK(TryNumberToSize(*offset_obj, &offset));
|
||||
size_t target_length = target->length_value();
|
||||
size_t source_length = source->length_value();
|
||||
size_t target_byte_length = NumberToSize(target->byte_length());
|
||||
size_t source_byte_length = NumberToSize(source->byte_length());
|
||||
if (offset > target_length || offset + source_length > target_length ||
|
||||
offset + source_length < offset) { // overflow
|
||||
THROW_NEW_ERROR(
|
||||
isolate, NewRangeError(MessageTemplate::kTypedArraySetSourceTooLarge),
|
||||
Smi);
|
||||
}
|
||||
|
||||
size_t target_offset = NumberToSize(target->byte_offset());
|
||||
size_t source_offset = NumberToSize(source->byte_offset());
|
||||
uint8_t* target_base =
|
||||
static_cast<uint8_t*>(target->GetBuffer()->backing_store()) +
|
||||
target_offset;
|
||||
uint8_t* source_base =
|
||||
static_cast<uint8_t*>(source->GetBuffer()->backing_store()) +
|
||||
source_offset;
|
||||
|
||||
// Typed arrays of different types over the same backing store
|
||||
if ((source_base <= target_base &&
|
||||
source_base + source_byte_length > target_base) ||
|
||||
(target_base <= source_base &&
|
||||
target_base + target_byte_length > source_base)) {
|
||||
// We do not support overlapping ArrayBuffers
|
||||
DCHECK(target->GetBuffer()->backing_store() ==
|
||||
source->GetBuffer()->backing_store());
|
||||
return MaybeHandle<Smi>(
|
||||
Smi::FromEnum(TypedArraySetResultCodes::OVERLAPPING), isolate);
|
||||
} else { // Non-overlapping typed arrays
|
||||
return MaybeHandle<Smi>(
|
||||
Smi::FromEnum(TypedArraySetResultCodes::NONOVERLAPPING), isolate);
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// 22.2.3.23%TypedArray%.prototype.set ( overloaded [ , offset ] )
|
||||
RUNTIME_FUNCTION(Runtime_TypedArraySet) {
|
||||
HandleScope scope(isolate);
|
||||
Handle<JSTypedArray> target = args.at<JSTypedArray>(0);
|
||||
Handle<Object> obj = args.at(1);
|
||||
Handle<Smi> offset = args.at<Smi>(2);
|
||||
|
||||
DCHECK(!target->WasNeutered()); // Checked in TypedArrayPrototypeSet.
|
||||
DCHECK(0 <= offset->value() && offset->value() <= Smi::kMaxValue);
|
||||
|
||||
const uint32_t uint_offset = static_cast<uint32_t>(offset->value());
|
||||
|
||||
// TODO(cwhan.tunz): Implement CopyElements for overlapping cases, and use
|
||||
// TypedArrayCopyElements for all case instead of this result code based
|
||||
// branches
|
||||
Handle<Smi> result_code;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, result_code,
|
||||
TypedArraySetFastCases(isolate, target, obj, offset));
|
||||
|
||||
switch (static_cast<TypedArraySetResultCodes>(result_code->value())) {
|
||||
case TypedArraySetResultCodes::OVERLAPPING: {
|
||||
RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate,
|
||||
TypedArraySetFromOverlapping(
|
||||
isolate, target, Handle<JSTypedArray>::cast(obj), uint_offset));
|
||||
break;
|
||||
}
|
||||
case TypedArraySetResultCodes::NONOVERLAPPING: {
|
||||
return TypedArrayCopyElements(
|
||||
target, Handle<JSTypedArray>::cast(obj),
|
||||
Handle<JSTypedArray>::cast(obj)->length_value(), uint_offset);
|
||||
break;
|
||||
}
|
||||
case TypedArraySetResultCodes::NON_TYPED_ARRAY: {
|
||||
if (obj->IsNumber()) {
|
||||
// For number as a first argument, throw TypeError
|
||||
// instead of silently ignoring the call, so that
|
||||
// users know they did something wrong.
|
||||
// (Consistent with Firefox and Blink/WebKit)
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate, NewTypeError(MessageTemplate::kInvalidArgument));
|
||||
}
|
||||
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, obj,
|
||||
Object::ToObject(isolate, obj));
|
||||
|
||||
Handle<Object> len;
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
|
||||
isolate, len,
|
||||
Object::GetProperty(obj, isolate->factory()->length_string()));
|
||||
if (len->IsUndefined(isolate)) {
|
||||
break;
|
||||
}
|
||||
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
|
||||
Object::ToLength(isolate, len));
|
||||
|
||||
DCHECK_GE(uint_offset, 0);
|
||||
if (uint_offset + len->Number() > target->length_value()) {
|
||||
THROW_NEW_ERROR_RETURN_FAILURE(
|
||||
isolate,
|
||||
NewRangeError(MessageTemplate::kTypedArraySetSourceTooLarge));
|
||||
}
|
||||
uint32_t int_l;
|
||||
CHECK(DoubleToUint32IfEqualToSelf(len->Number(), &int_l));
|
||||
return TypedArrayCopyElements(target, Handle<JSReceiver>::cast(obj),
|
||||
int_l, uint_offset);
|
||||
} break;
|
||||
}
|
||||
|
||||
return *isolate->factory()->undefined_value();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -643,6 +643,7 @@ namespace internal {
|
||||
F(TypedArrayGetLength, 1, 1) \
|
||||
F(TypedArrayGetBuffer, 1, 1) \
|
||||
F(TypedArraySortFast, 1, 1) \
|
||||
F(TypedArraySet, 2, 1) \
|
||||
F(IsTypedArray, 1, 1) \
|
||||
F(IsSharedTypedArray, 1, 1) \
|
||||
F(IsSharedIntegerTypedArray, 1, 1) \
|
||||
|
@ -1,9 +1,9 @@
|
||||
# Copyright 2017 the V8 project authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
*%(basename)s:6: RangeError: Start offset is negative
|
||||
*%(basename)s:6: RangeError: offset is out of bounds
|
||||
a.set([2], -1);
|
||||
^
|
||||
RangeError: Start offset is negative
|
||||
RangeError: offset is out of bounds
|
||||
at Uint8Array.set (<anonymous>)
|
||||
at *%(basename)s:6:3
|
Loading…
Reference in New Issue
Block a user