// 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. #include "src/builtins/builtins-typed-array-gen.h" #include "src/builtins/builtins-constructor-gen.h" #include "src/builtins/builtins-utils-gen.h" #include "src/builtins/builtins.h" #include "src/builtins/growable-fixed-array-gen.h" #include "src/handles-inl.h" #include "src/heap/factory-inl.h" namespace v8 { namespace internal { using compiler::Node; template using TNode = compiler::TNode; // 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 #define V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP 64 #endif // ----------------------------------------------------------------------------- // ES6 section 22.2 TypedArray Objects TNode TypedArrayBuiltinsAssembler::LoadMapForType( TNode array) { TVARIABLE(Map, var_typed_map); TNode array_map = LoadMap(array); TNode elements_kind = LoadMapElementsKind(array_map); ReadOnlyRoots roots(isolate()); DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind kind, int size, int typed_array_fun_index) { Handle map(roots.MapForFixedTypedArray(kind), isolate()); var_typed_map = HeapConstant(map); }); return var_typed_map.value(); } // The byte_offset can be higher than Smi range, in which case to perform the // pointer arithmetic necessary to calculate external_pointer, converting // byte_offset to an intptr is more difficult. The max byte_offset is 8 * MaxSmi // on the particular platform. 32 bit platforms are self-limiting, because we // 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. TNode TypedArrayBuiltinsAssembler::CalculateExternalPointer( TNode backing_store, TNode byte_offset) { return Unsigned( IntPtrAdd(backing_store, ChangeNonnegativeNumberToUintPtr(byte_offset))); } // Setup the TypedArray which is under construction. // - Set the length. // - Set the byte_offset. // - Set the byte_length. // - Set EmbedderFields to 0. void TypedArrayBuiltinsAssembler::SetupTypedArray(TNode holder, TNode length, TNode byte_offset, TNode byte_length) { StoreObjectField(holder, JSTypedArray::kLengthOffset, length); StoreObjectFieldNoWriteBarrier(holder, JSArrayBufferView::kByteOffsetOffset, byte_offset, MachineType::PointerRepresentation()); StoreObjectFieldNoWriteBarrier(holder, JSArrayBufferView::kByteLengthOffset, byte_length, MachineType::PointerRepresentation()); for (int offset = JSTypedArray::kSize; offset < JSTypedArray::kSizeWithEmbedderFields; offset += kPointerSize) { StoreObjectField(holder, offset, SmiConstant(0)); } } // Attach an off-heap buffer to a TypedArray. void TypedArrayBuiltinsAssembler::AttachBuffer(TNode holder, TNode buffer, TNode map, TNode length, TNode byte_offset) { StoreObjectField(holder, JSArrayBufferView::kBufferOffset, buffer); Node* elements = Allocate(FixedTypedArrayBase::kHeaderSize); StoreMapNoWriteBarrier(elements, map); StoreObjectFieldNoWriteBarrier(elements, FixedArray::kLengthOffset, length); StoreObjectFieldNoWriteBarrier( elements, FixedTypedArrayBase::kBasePointerOffset, SmiConstant(0)); TNode backing_store = LoadObjectField(buffer, JSArrayBuffer::kBackingStoreOffset); TNode external_pointer = CalculateExternalPointer(backing_store, byte_offset); StoreObjectFieldNoWriteBarrier( elements, FixedTypedArrayBase::kExternalPointerOffset, external_pointer, MachineType::PointerRepresentation()); StoreObjectField(holder, JSObject::kElementsOffset, elements); } TF_BUILTIN(TypedArrayInitializeWithBuffer, TypedArrayBuiltinsAssembler) { TNode holder = CAST(Parameter(Descriptor::kHolder)); TNode length = CAST(Parameter(Descriptor::kLength)); TNode buffer = CAST(Parameter(Descriptor::kBuffer)); TNode element_size = CAST(Parameter(Descriptor::kElementSize)); TNode byte_offset = CAST(Parameter(Descriptor::kByteOffset)); TNode fixed_typed_map = LoadMapForType(holder); // SmiMul returns a heap number in case of Smi overflow. TNode byte_length = SmiMul(length, element_size); SetupTypedArray(holder, length, ChangeNonnegativeNumberToUintPtr(byte_offset), ChangeNonnegativeNumberToUintPtr(byte_length)); AttachBuffer(holder, buffer, fixed_typed_map, length, byte_offset); Return(UndefinedConstant()); } TF_BUILTIN(TypedArrayInitialize, TypedArrayBuiltinsAssembler) { TNode holder = CAST(Parameter(Descriptor::kHolder)); TNode length = CAST(Parameter(Descriptor::kLength)); TNode element_size = CAST(Parameter(Descriptor::kElementSize)); Node* initialize = Parameter(Descriptor::kInitialize); TNode buffer_constructor = CAST(Parameter(Descriptor::kBufferConstructor)); TNode context = CAST(Parameter(Descriptor::kContext)); CSA_ASSERT(this, TaggedIsPositiveSmi(length)); CSA_ASSERT(this, TaggedIsPositiveSmi(element_size)); CSA_ASSERT(this, IsBoolean(initialize)); TNode byte_offset = SmiConstant(0); static const int32_t fta_base_data_offset = FixedTypedArrayBase::kDataOffset - kHeapObjectTag; Label setup_holder(this), allocate_on_heap(this), aligned(this), allocate_elements(this), allocate_off_heap(this), allocate_off_heap_custom_constructor(this), allocate_off_heap_no_init(this), attach_buffer(this), done(this); TVARIABLE(IntPtrT, var_total_size); // SmiMul returns a heap number in case of Smi overflow. TNode byte_length = SmiMul(length, element_size); TNode fixed_typed_map = LoadMapForType(holder); // If target and new_target for the buffer differ, allocate off-heap. TNode default_constructor = CAST(LoadContextElement( LoadNativeContext(context), Context::ARRAY_BUFFER_FUN_INDEX)); GotoIfNot(WordEqual(buffer_constructor, default_constructor), &allocate_off_heap_custom_constructor); // For buffers with byte_length over the threshold, allocate off-heap. GotoIf(TaggedIsNotSmi(byte_length), &allocate_off_heap); TNode smi_byte_length = CAST(byte_length); GotoIf(SmiGreaterThan(smi_byte_length, SmiConstant(V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP)), &allocate_off_heap); TNode word_byte_length = SmiToIntPtr(smi_byte_length); Goto(&allocate_on_heap); BIND(&allocate_on_heap); { CSA_ASSERT(this, TaggedIsPositiveSmi(byte_length)); // Allocate a new ArrayBuffer and initialize it with empty properties and // elements. Node* native_context = LoadNativeContext(context); Node* map = LoadContextElement(native_context, Context::ARRAY_BUFFER_MAP_INDEX); Node* empty_fixed_array = LoadRoot(RootIndex::kEmptyFixedArray); Node* buffer = Allocate(JSArrayBuffer::kSizeWithEmbedderFields); StoreMapNoWriteBarrier(buffer, map); StoreObjectFieldNoWriteBarrier(buffer, JSArray::kPropertiesOrHashOffset, empty_fixed_array); StoreObjectFieldNoWriteBarrier(buffer, JSArray::kElementsOffset, empty_fixed_array); // Setup the ArrayBuffer. // - Set BitField to 0. // - Set IsExternal and IsNeuterable bits of BitFieldSlot. // - Set the byte_length field to byte_length. // - Set backing_store to null/Smi(0). // - Set all embedder fields to Smi(0). StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldSlot, SmiConstant(0)); int32_t bitfield_value = (1 << JSArrayBuffer::IsExternalBit::kShift) | (1 << JSArrayBuffer::IsNeuterableBit::kShift); StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldOffset, Int32Constant(bitfield_value), MachineRepresentation::kWord32); StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kByteLengthOffset, SmiToIntPtr(CAST(byte_length)), MachineType::PointerRepresentation()); StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBackingStoreOffset, SmiConstant(0)); for (int i = 0; i < v8::ArrayBuffer::kEmbedderFieldCount; i++) { int offset = JSArrayBuffer::kSize + i * kPointerSize; StoreObjectFieldNoWriteBarrier(buffer, offset, SmiConstant(0)); } StoreObjectField(holder, JSArrayBufferView::kBufferOffset, buffer); // Check the alignment. // TODO(ishell): remove GotoIf(WordEqual( SmiMod(element_size, SmiConstant(kObjectAlignment)), SmiConstant(0)), &aligned); // Fix alignment if needed. DCHECK_EQ(0, FixedTypedArrayBase::kHeaderSize & kObjectAlignmentMask); TNode aligned_header_size = IntPtrConstant(FixedTypedArrayBase::kHeaderSize + kObjectAlignmentMask); TNode size = IntPtrAdd(word_byte_length, aligned_header_size); var_total_size = WordAnd(size, IntPtrConstant(~kObjectAlignmentMask)); Goto(&allocate_elements); } BIND(&aligned); { TNode header_size = IntPtrConstant(FixedTypedArrayBase::kHeaderSize); var_total_size = IntPtrAdd(word_byte_length, header_size); Goto(&allocate_elements); } BIND(&allocate_elements); { // Allocate a FixedTypedArray and set the length, base pointer and external // pointer. CSA_ASSERT(this, IsRegularHeapObjectSize(var_total_size.value())); Node* elements; if (UnalignedLoadSupported(MachineRepresentation::kFloat64) && UnalignedStoreSupported(MachineRepresentation::kFloat64)) { elements = AllocateInNewSpace(var_total_size.value()); } else { elements = AllocateInNewSpace(var_total_size.value(), kDoubleAlignment); } StoreMapNoWriteBarrier(elements, fixed_typed_map); StoreObjectFieldNoWriteBarrier(elements, FixedArray::kLengthOffset, length); StoreObjectFieldNoWriteBarrier( elements, FixedTypedArrayBase::kBasePointerOffset, elements); StoreObjectFieldNoWriteBarrier(elements, FixedTypedArrayBase::kExternalPointerOffset, IntPtrConstant(fta_base_data_offset), MachineType::PointerRepresentation()); StoreObjectField(holder, JSObject::kElementsOffset, elements); GotoIf(IsFalse(initialize), &done); // Initialize the backing store by filling it with 0s. Node* backing_store = IntPtrAdd(BitcastTaggedToWord(elements), IntPtrConstant(fta_base_data_offset)); // Call out to memset to perform initialization. Node* memset = ExternalConstant(ExternalReference::libc_memset_function()); CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(), MachineType::IntPtr(), MachineType::UintPtr(), memset, backing_store, IntPtrConstant(0), word_byte_length); Goto(&done); } TVARIABLE(JSArrayBuffer, var_buffer); BIND(&allocate_off_heap); { GotoIf(IsFalse(initialize), &allocate_off_heap_no_init); var_buffer = CAST(ConstructJS(CodeFactory::Construct(isolate()), context, default_constructor, byte_length)); Goto(&attach_buffer); } BIND(&allocate_off_heap_custom_constructor); { var_buffer = CAST(CallStub(CodeFactory::Construct(isolate()), context, default_constructor, buffer_constructor, Int32Constant(1), UndefinedConstant(), byte_length)); Goto(&attach_buffer); } BIND(&allocate_off_heap_no_init); { Node* buffer_constructor_noinit = LoadContextElement( LoadNativeContext(context), Context::ARRAY_BUFFER_NOINIT_FUN_INDEX); var_buffer = CAST(CallJS(CodeFactory::Call(isolate()), context, buffer_constructor_noinit, UndefinedConstant(), byte_length)); Goto(&attach_buffer); } BIND(&attach_buffer); { AttachBuffer(holder, var_buffer.value(), fixed_typed_map, length, byte_offset); Goto(&done); } BIND(&done); SetupTypedArray(holder, length, ChangeNonnegativeNumberToUintPtr(byte_offset), ChangeNonnegativeNumberToUintPtr(byte_length)); Return(UndefinedConstant()); } // ES6 #sec-typedarray-length void TypedArrayBuiltinsAssembler::ConstructByLength(TNode context, TNode holder, TNode length, TNode element_size) { // TODO(7881): support larger-than-smi typed array lengths CSA_ASSERT(this, TaggedIsPositiveSmi(element_size)); Label invalid_length(this, Label::kDeferred), done(this); TNode converted_length = ToInteger_Inline(context, length, CodeStubAssembler::kTruncateMinusZero); // The maximum length of a TypedArray is MaxSmi(). // Note: this is not per spec, but rather a constraint of our current // representation (which uses Smis). // TODO(7881): support larger-than-smi typed array lengths GotoIf(TaggedIsNotSmi(converted_length), &invalid_length); // The goto above ensures that byte_length is a Smi. TNode smi_converted_length = CAST(converted_length); GotoIf(SmiLessThan(smi_converted_length, SmiConstant(0)), &invalid_length); Node* initialize = TrueConstant(); TNode default_constructor = CAST(LoadContextElement( LoadNativeContext(context), Context::ARRAY_BUFFER_FUN_INDEX)); CallBuiltin(Builtins::kTypedArrayInitialize, context, holder, converted_length, element_size, initialize, default_constructor); Goto(&done); BIND(&invalid_length); { ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength, converted_length); } BIND(&done); } // ES6 #sec-typedarray-buffer-byteoffset-length void TypedArrayBuiltinsAssembler::ConstructByArrayBuffer( TNode context, TNode holder, TNode buffer, TNode byte_offset, TNode length, TNode element_size) { CSA_ASSERT(this, TaggedIsPositiveSmi(element_size)); VARIABLE(new_byte_length, MachineRepresentation::kTagged, SmiConstant(0)); VARIABLE(offset, MachineRepresentation::kTagged, SmiConstant(0)); Label start_offset_error(this, Label::kDeferred), byte_length_error(this, Label::kDeferred), invalid_offset_error(this, Label::kDeferred); Label offset_is_smi(this), offset_not_smi(this, Label::kDeferred), check_length(this), call_init(this), invalid_length(this), length_undefined(this), length_defined(this), done(this); GotoIf(IsUndefined(byte_offset), &check_length); offset.Bind(ToInteger_Inline(context, byte_offset, CodeStubAssembler::kTruncateMinusZero)); Branch(TaggedIsSmi(offset.value()), &offset_is_smi, &offset_not_smi); // Check that the offset is a multiple of the element size. BIND(&offset_is_smi); { TNode smi_offset = CAST(offset.value()); GotoIf(SmiEqual(smi_offset, SmiConstant(0)), &check_length); GotoIf(SmiLessThan(smi_offset, SmiConstant(0)), &invalid_length); TNode remainder = SmiMod(smi_offset, element_size); // TODO(ishell): remove Branch(WordEqual(remainder, SmiConstant(0)), &check_length, &start_offset_error); } BIND(&offset_not_smi); { GotoIf(IsTrue(CallBuiltin(Builtins::kLessThan, context, offset.value(), SmiConstant(0))), &invalid_length); Node* remainder = CallBuiltin(Builtins::kModulus, context, offset.value(), element_size); // Remainder can be a heap number. Branch(IsTrue(CallBuiltin(Builtins::kEqual, context, remainder, SmiConstant(0))), &check_length, &start_offset_error); } BIND(&check_length); Branch(IsUndefined(length), &length_undefined, &length_defined); BIND(&length_undefined); { ThrowIfArrayBufferIsDetached(context, buffer, "Construct"); TNode buffer_byte_length = ChangeUintPtrToTagged( LoadObjectField(buffer, JSArrayBuffer::kByteLengthOffset)); Node* remainder = CallBuiltin(Builtins::kModulus, context, buffer_byte_length, element_size); // Remainder can be a heap number. GotoIf(IsFalse(CallBuiltin(Builtins::kEqual, context, remainder, SmiConstant(0))), &byte_length_error); new_byte_length.Bind(CallBuiltin(Builtins::kSubtract, context, buffer_byte_length, offset.value())); Branch(IsTrue(CallBuiltin(Builtins::kLessThan, context, new_byte_length.value(), SmiConstant(0))), &invalid_offset_error, &call_init); } BIND(&length_defined); { TNode new_length = ToSmiIndex(length, context, &invalid_length); ThrowIfArrayBufferIsDetached(context, buffer, "Construct"); new_byte_length.Bind(SmiMul(new_length, element_size)); // Reading the byte length must come after the ToIndex operation, which // could cause the buffer to become detached. TNode buffer_byte_length = ChangeUintPtrToTagged( LoadObjectField(buffer, JSArrayBuffer::kByteLengthOffset)); Node* end = CallBuiltin(Builtins::kAdd, context, offset.value(), new_byte_length.value()); Branch(IsTrue(CallBuiltin(Builtins::kGreaterThan, context, end, buffer_byte_length)), &invalid_length, &call_init); } BIND(&call_init); { TNode raw_length = CallBuiltin( Builtins::kDivide, context, new_byte_length.value(), element_size); // Force the result into a Smi, or throw a range error if it doesn't fit. TNode new_length = ToSmiIndex(raw_length, context, &invalid_length); CallBuiltin(Builtins::kTypedArrayInitializeWithBuffer, context, holder, new_length, buffer, element_size, offset.value()); Goto(&done); } BIND(&invalid_offset_error); { ThrowRangeError(context, MessageTemplate::kInvalidOffset, byte_offset); } BIND(&start_offset_error); { Node* holder_map = LoadMap(holder); Node* problem_string = StringConstant("start offset"); CallRuntime(Runtime::kThrowInvalidTypedArrayAlignment, context, holder_map, problem_string); Unreachable(); } BIND(&byte_length_error); { Node* holder_map = LoadMap(holder); Node* problem_string = StringConstant("byte length"); CallRuntime(Runtime::kThrowInvalidTypedArrayAlignment, context, holder_map, problem_string); Unreachable(); } BIND(&invalid_length); { ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength, length); } BIND(&done); } void TypedArrayBuiltinsAssembler::ConstructByTypedArray( TNode context, TNode holder, TNode typed_array, TNode element_size) { CSA_ASSERT(this, TaggedIsPositiveSmi(element_size)); TNode const default_constructor = CAST(LoadContextElement( LoadNativeContext(context), Context::ARRAY_BUFFER_FUN_INDEX)); Label construct(this), if_detached(this), if_notdetached(this), check_for_sab(this), if_buffernotshared(this), check_prototype(this), done(this); TVARIABLE(JSReceiver, buffer_constructor, default_constructor); TNode source_buffer = LoadObjectField( typed_array, JSArrayBufferView::kBufferOffset); Branch(IsDetachedBuffer(source_buffer), &if_detached, &if_notdetached); // TODO(petermarshall): Throw on detached typedArray. TVARIABLE(Smi, source_length); BIND(&if_detached); source_length = SmiConstant(0); Goto(&check_for_sab); BIND(&if_notdetached); source_length = LoadJSTypedArrayLength(typed_array); Goto(&check_for_sab); // The spec requires that constructing a typed array using a SAB-backed typed // array use the ArrayBuffer constructor, not the species constructor. See // https://tc39.github.io/ecma262/#sec-typedarray-typedarray. BIND(&check_for_sab); TNode bitfield = LoadObjectField(source_buffer, JSArrayBuffer::kBitFieldOffset); Branch(IsSetWord32(bitfield), &construct, &if_buffernotshared); BIND(&if_buffernotshared); { buffer_constructor = CAST(SpeciesConstructor(context, source_buffer, default_constructor)); // TODO(petermarshall): Throw on detached typedArray. GotoIfNot(IsDetachedBuffer(source_buffer), &construct); source_length = SmiConstant(0); Goto(&construct); } BIND(&construct); { ConstructByArrayLike(context, holder, typed_array, source_length.value(), element_size, buffer_constructor.value()); Goto(&done); } BIND(&done); } Node* TypedArrayBuiltinsAssembler::LoadDataPtr(Node* typed_array) { CSA_ASSERT(this, IsJSTypedArray(typed_array)); Node* elements = LoadElements(typed_array); CSA_ASSERT(this, IsFixedTypedArray(elements)); return LoadFixedTypedArrayBackingStore(CAST(elements)); } TNode TypedArrayBuiltinsAssembler::ByteLengthIsValid( TNode byte_length) { Label smi(this), done(this); TVARIABLE(BoolT, is_valid); GotoIf(TaggedIsSmi(byte_length), &smi); TNode float_value = LoadHeapNumberValue(CAST(byte_length)); TNode max_byte_length_double = Float64Constant(FixedTypedArrayBase::kMaxByteLength); is_valid = Float64LessThanOrEqual(float_value, max_byte_length_double); Goto(&done); BIND(&smi); TNode max_byte_length = IntPtrConstant(FixedTypedArrayBase::kMaxByteLength); is_valid = UintPtrLessThanOrEqual(SmiUntag(CAST(byte_length)), max_byte_length); Goto(&done); BIND(&done); return is_valid.value(); } void TypedArrayBuiltinsAssembler::ConstructByArrayLike( TNode context, TNode holder, TNode array_like, TNode initial_length, TNode element_size, TNode buffer_constructor) { Label invalid_length(this, Label::kDeferred), fill(this), fast_copy(this), detached_check(this), done(this); // The caller has looked up length on array_like, which is observable. TNode length = ToSmiLength(initial_length, context, &invalid_length); Node* initialize = FalseConstant(); CallBuiltin(Builtins::kTypedArrayInitialize, context, holder, length, element_size, initialize, buffer_constructor); GotoIf(IsJSTypedArray(array_like), &detached_check); Goto(&fill); BIND(&detached_check); ThrowIfArrayBufferViewBufferIsDetached(context, CAST(array_like), "Construct"); Goto(&fill); BIND(&fill); GotoIf(SmiEqual(length, SmiConstant(0)), &done); TNode holder_kind = LoadElementsKind(holder); TNode source_kind = LoadElementsKind(array_like); GotoIf(Word32Equal(holder_kind, source_kind), &fast_copy); // Copy using the elements accessor. CallRuntime(Runtime::kTypedArrayCopyElements, context, holder, array_like, length); Goto(&done); BIND(&fast_copy); { Node* holder_data_ptr = LoadDataPtr(holder); Node* source_data_ptr = LoadDataPtr(array_like); // Calculate the byte length. We shouldn't be trying to copy if the typed // array was neutered. CSA_ASSERT(this, SmiNotEqual(length, SmiConstant(0))); CSA_ASSERT(this, Word32Equal(IsDetachedBuffer(LoadObjectField( array_like, JSTypedArray::kBufferOffset)), Int32Constant(0))); TNode byte_length = SmiMul(length, element_size); CSA_ASSERT(this, ByteLengthIsValid(byte_length)); TNode byte_length_intptr = ChangeNonnegativeNumberToUintPtr(byte_length); CSA_ASSERT(this, UintPtrLessThanOrEqual( byte_length_intptr, IntPtrConstant(FixedTypedArrayBase::kMaxByteLength))); Node* memcpy = ExternalConstant(ExternalReference::libc_memcpy_function()); CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(), MachineType::Pointer(), MachineType::UintPtr(), memcpy, holder_data_ptr, source_data_ptr, byte_length_intptr); Goto(&done); } BIND(&invalid_length); { ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength, initial_length); } BIND(&done); } void TypedArrayBuiltinsAssembler::ConstructByIterable( TNode context, TNode holder, TNode iterable, TNode iterator_fn, TNode element_size) { Label fast_path(this), slow_path(this), done(this); CSA_ASSERT(this, IsCallable(iterator_fn)); TNode array_like = CAST(CallBuiltin(Builtins::kIterableToListMayPreserveHoles, context, iterable, iterator_fn)); TNode initial_length = LoadJSArrayLength(array_like); TNode default_constructor = CAST(LoadContextElement( LoadNativeContext(context), Context::ARRAY_BUFFER_FUN_INDEX)); ConstructByArrayLike(context, holder, array_like, initial_length, element_size, default_constructor); } TF_BUILTIN(TypedArrayBaseConstructor, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); ThrowTypeError(context, MessageTemplate::kConstructAbstractClass, "TypedArray"); } // ES #sec-typedarray-constructors TF_BUILTIN(CreateTypedArray, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode target = CAST(Parameter(Descriptor::kTarget)); TNode new_target = CAST(Parameter(Descriptor::kNewTarget)); TNode arg1 = CAST(Parameter(Descriptor::kArg1)); TNode arg2 = CAST(Parameter(Descriptor::kArg2)); TNode arg3 = CAST(Parameter(Descriptor::kArg3)); CSA_ASSERT(this, IsConstructor(target)); CSA_ASSERT(this, IsJSReceiver(new_target)); Label if_arg1isbuffer(this), if_arg1istypedarray(this), if_arg1isreceiver(this), if_arg1isnumber(this), return_result(this); ConstructorBuiltinsAssembler constructor_assembler(this->state()); TNode result = CAST( constructor_assembler.EmitFastNewObject(context, target, new_target)); // We need to set the byte_offset / byte_length to some sane values // to keep the heap verifier happy. // TODO(bmeurer): Fix this initialization to not use EmitFastNewObject, // which causes the problem, since it puts Undefined into all slots of // the object even though that doesn't make any sense for these fields. StoreObjectFieldNoWriteBarrier(result, JSTypedArray::kByteOffsetOffset, UintPtrConstant(0), MachineType::PointerRepresentation()); StoreObjectFieldNoWriteBarrier(result, JSTypedArray::kByteLengthOffset, UintPtrConstant(0), MachineType::PointerRepresentation()); TNode element_size = SmiTag(GetTypedArrayElementSize(LoadElementsKind(result))); GotoIf(TaggedIsSmi(arg1), &if_arg1isnumber); TNode arg1_heap_object = UncheckedCast(arg1); GotoIf(IsJSArrayBuffer(arg1_heap_object), &if_arg1isbuffer); GotoIf(IsJSTypedArray(arg1_heap_object), &if_arg1istypedarray); GotoIf(IsJSReceiver(arg1_heap_object), &if_arg1isreceiver); Goto(&if_arg1isnumber); // https://tc39.github.io/ecma262/#sec-typedarray-buffer-byteoffset-length BIND(&if_arg1isbuffer); { ConstructByArrayBuffer(context, result, CAST(arg1), arg2, arg3, element_size); Goto(&return_result); } // https://tc39.github.io/ecma262/#sec-typedarray-typedarray BIND(&if_arg1istypedarray); { TNode typed_array = CAST(arg1_heap_object); ConstructByTypedArray(context, result, typed_array, element_size); Goto(&return_result); } // https://tc39.github.io/ecma262/#sec-typedarray-object BIND(&if_arg1isreceiver); { Label if_iteratorundefined(this), if_iteratornotcallable(this); // Get iterator symbol TNode iteratorFn = CAST(GetMethod( context, arg1_heap_object, isolate()->factory()->iterator_symbol(), &if_iteratorundefined)); GotoIf(TaggedIsSmi(iteratorFn), &if_iteratornotcallable); GotoIfNot(IsCallable(CAST(iteratorFn)), &if_iteratornotcallable); ConstructByIterable(context, result, CAST(arg1_heap_object), CAST(iteratorFn), element_size); Goto(&return_result); BIND(&if_iteratorundefined); { TNode array_like = arg1_heap_object; TNode initial_length = GetProperty(context, arg1, LengthStringConstant()); TNode default_constructor = CAST(LoadContextElement( LoadNativeContext(context), Context::ARRAY_BUFFER_FUN_INDEX)); ConstructByArrayLike(context, result, array_like, initial_length, element_size, default_constructor); Goto(&return_result); } BIND(&if_iteratornotcallable); { ThrowTypeError(context, MessageTemplate::kIteratorSymbolNonCallable); } } // The first argument was a number or fell through and is treated as // a number. https://tc39.github.io/ecma262/#sec-typedarray-length BIND(&if_arg1isnumber); { ConstructByLength(context, result, arg1, element_size); Goto(&return_result); } BIND(&return_result); Return(result); } // ES #sec-typedarray-constructors TF_BUILTIN(TypedArrayConstructor, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode target = CAST(Parameter(Descriptor::kJSTarget)); TNode new_target = CAST(Parameter(Descriptor::kJSNewTarget)); Node* argc = ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); CodeStubArguments args(this, argc); Node* arg1 = args.GetOptionalArgumentValue(0); Node* arg2 = args.GetOptionalArgumentValue(1); Node* arg3 = args.GetOptionalArgumentValue(2); // If NewTarget is undefined, throw a TypeError exception. // All the TypedArray constructors have this as the first step: // https://tc39.github.io/ecma262/#sec-typedarray-constructors Label throwtypeerror(this, Label::kDeferred); GotoIf(IsUndefined(new_target), &throwtypeerror); Node* result = CallBuiltin(Builtins::kCreateTypedArray, context, target, new_target, arg1, arg2, arg3); args.PopAndReturn(result); BIND(&throwtypeerror); { TNode name = CAST(CallRuntime(Runtime::kGetFunctionName, context, target)); ThrowTypeError(context, MessageTemplate::kConstructorNotFunction, name); } } // ES6 #sec-get-%typedarray%.prototype.bytelength TF_BUILTIN(TypedArrayPrototypeByteLength, TypedArrayBuiltinsAssembler) { const char* const kMethodName = "get TypedArray.prototype.byteLength"; Node* context = Parameter(Descriptor::kContext); Node* receiver = Parameter(Descriptor::kReceiver); // Check if the {receiver} is actually a JSTypedArray. ThrowIfNotInstanceType(context, receiver, JS_TYPED_ARRAY_TYPE, kMethodName); // Default to zero if the {receiver}s buffer was neutered. TNode receiver_buffer = LoadJSArrayBufferViewBuffer(CAST(receiver)); TNode byte_length = Select( IsDetachedBuffer(receiver_buffer), [=] { return UintPtrConstant(0); }, [=] { return LoadJSArrayBufferViewByteLength(CAST(receiver)); }); Return(ChangeUintPtrToTagged(byte_length)); } // ES6 #sec-get-%typedarray%.prototype.byteoffset TF_BUILTIN(TypedArrayPrototypeByteOffset, TypedArrayBuiltinsAssembler) { const char* const kMethodName = "get TypedArray.prototype.byteOffset"; Node* context = Parameter(Descriptor::kContext); Node* receiver = Parameter(Descriptor::kReceiver); // Check if the {receiver} is actually a JSTypedArray. ThrowIfNotInstanceType(context, receiver, JS_TYPED_ARRAY_TYPE, kMethodName); // Default to zero if the {receiver}s buffer was neutered. TNode receiver_buffer = LoadJSArrayBufferViewBuffer(CAST(receiver)); TNode byte_offset = Select( IsDetachedBuffer(receiver_buffer), [=] { return UintPtrConstant(0); }, [=] { return LoadJSArrayBufferViewByteOffset(CAST(receiver)); }); Return(ChangeUintPtrToTagged(byte_offset)); } // ES6 #sec-get-%typedarray%.prototype.length TF_BUILTIN(TypedArrayPrototypeLength, TypedArrayBuiltinsAssembler) { const char* const kMethodName = "get TypedArray.prototype.length"; Node* context = Parameter(Descriptor::kContext); Node* receiver = Parameter(Descriptor::kReceiver); // Check if the {receiver} is actually a JSTypedArray. ThrowIfNotInstanceType(context, receiver, JS_TYPED_ARRAY_TYPE, kMethodName); // Default to zero if the {receiver}s buffer was neutered. TNode receiver_buffer = LoadJSArrayBufferViewBuffer(CAST(receiver)); TNode length = Select( IsDetachedBuffer(receiver_buffer), [=] { return SmiConstant(0); }, [=] { return LoadJSTypedArrayLength(CAST(receiver)); }); Return(length); } TNode TypedArrayBuiltinsAssembler::IsUint8ElementsKind( TNode kind) { return Word32Or(Word32Equal(kind, Int32Constant(UINT8_ELEMENTS)), Word32Equal(kind, Int32Constant(UINT8_CLAMPED_ELEMENTS))); } TNode TypedArrayBuiltinsAssembler::IsBigInt64ElementsKind( TNode kind) { return Word32Or(Word32Equal(kind, Int32Constant(BIGINT64_ELEMENTS)), Word32Equal(kind, Int32Constant(BIGUINT64_ELEMENTS))); } TNode TypedArrayBuiltinsAssembler::GetTypedArrayElementSize( TNode elements_kind) { TVARIABLE(IntPtrT, element_size); DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind el_kind, int size, int typed_array_fun_index) { element_size = IntPtrConstant(size); }); return element_size.value(); } TNode TypedArrayBuiltinsAssembler::GetDefaultConstructor( TNode context, TNode exemplar) { TVARIABLE(IntPtrT, context_slot); TNode elements_kind = LoadElementsKind(exemplar); DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind el_kind, int size, int typed_array_function_index) { context_slot = IntPtrConstant(typed_array_function_index); }); return LoadContextElement(LoadNativeContext(context), context_slot.value()); } TNode TypedArrayBuiltinsAssembler::TypedArraySpeciesConstructor( TNode context, TNode exemplar) { TVARIABLE(Object, var_constructor); Label slow(this), done(this); // Let defaultConstructor be the intrinsic object listed in column one of // Table 52 for exemplar.[[TypedArrayName]]. TNode default_constructor = GetDefaultConstructor(context, exemplar); var_constructor = default_constructor; Node* map = LoadMap(exemplar); GotoIfNot(IsPrototypeTypedArrayPrototype(context, map), &slow); Branch(IsTypedArraySpeciesProtectorCellInvalid(), &slow, &done); BIND(&slow); var_constructor = SpeciesConstructor(context, exemplar, default_constructor); Goto(&done); BIND(&done); return var_constructor.value(); } TNode TypedArrayBuiltinsAssembler::SpeciesCreateByArrayBuffer( TNode context, TNode exemplar, TNode buffer, TNode byte_offset, TNode len, const char* method_name) { // Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). TNode constructor = TypedArraySpeciesConstructor(context, exemplar); // Let newTypedArray be ? Construct(constructor, argumentList). TNode new_object = CAST(ConstructJS(CodeFactory::Construct(isolate()), context, constructor, buffer, byte_offset, len)); // Perform ? ValidateTypedArray(newTypedArray). return ValidateTypedArray(context, new_object, method_name); } TNode TypedArrayBuiltinsAssembler::SpeciesCreateByLength( TNode context, TNode exemplar, TNode len, const char* method_name) { CSA_ASSERT(this, TaggedIsPositiveSmi(len)); // Let constructor be ? SpeciesConstructor(exemplar, defaultConstructor). TNode constructor = CAST(TypedArraySpeciesConstructor(context, exemplar)); return CreateByLength(context, constructor, len, method_name); } TNode TypedArrayBuiltinsAssembler::CreateByLength( TNode context, TNode constructor, TNode len, const char* method_name) { // Let newTypedArray be ? Construct(constructor, argumentList). TNode new_object = CAST(ConstructJS(CodeFactory::Construct(isolate()), context, constructor, len)); // Perform ? ValidateTypedArray(newTypedArray). TNode new_typed_array = ValidateTypedArray(context, new_object, method_name); // If newTypedArray.[[ArrayLength]] < argumentList[0], throw a TypeError // exception. Label if_length_is_not_short(this); TNode new_length = LoadJSTypedArrayLength(new_typed_array); GotoIfNot(SmiLessThan(new_length, len), &if_length_is_not_short); ThrowTypeError(context, MessageTemplate::kTypedArrayTooShort); BIND(&if_length_is_not_short); return new_typed_array; } TNode TypedArrayBuiltinsAssembler::GetBuffer( TNode context, TNode array) { Label call_runtime(this), done(this); TVARIABLE(Object, var_result); TNode buffer = LoadObjectField(array, JSTypedArray::kBufferOffset); GotoIf(IsDetachedBuffer(buffer), &call_runtime); TNode backing_store = LoadObjectField( CAST(buffer), JSArrayBuffer::kBackingStoreOffset); GotoIf(WordEqual(backing_store, IntPtrConstant(0)), &call_runtime); var_result = buffer; Goto(&done); BIND(&call_runtime); { var_result = CallRuntime(Runtime::kTypedArrayGetBuffer, context, array); Goto(&done); } BIND(&done); return CAST(var_result.value()); } TNode TypedArrayBuiltinsAssembler::ValidateTypedArray( TNode context, TNode obj, const char* method_name) { // If it is not a typed array, throw ThrowIfNotInstanceType(context, obj, JS_TYPED_ARRAY_TYPE, method_name); // If the typed array's buffer is detached, throw ThrowIfArrayBufferViewBufferIsDetached(context, CAST(obj), method_name); return CAST(obj); } void TypedArrayBuiltinsAssembler::SetTypedArraySource( TNode context, TNode source, TNode target, TNode offset, Label* call_runtime, Label* if_source_too_large) { CSA_ASSERT(this, Word32BinaryNot(IsDetachedBuffer( LoadObjectField(source, JSTypedArray::kBufferOffset)))); CSA_ASSERT(this, Word32BinaryNot(IsDetachedBuffer( LoadObjectField(target, JSTypedArray::kBufferOffset)))); CSA_ASSERT(this, IntPtrGreaterThanOrEqual(offset, IntPtrConstant(0))); CSA_ASSERT(this, IntPtrLessThanOrEqual(offset, IntPtrConstant(Smi::kMaxValue))); // Check for possible range errors. TNode source_length = SmiUntag(LoadJSTypedArrayLength(source)); TNode target_length = SmiUntag(LoadJSTypedArrayLength(target)); TNode required_target_length = IntPtrAdd(source_length, offset); GotoIf(IntPtrGreaterThan(required_target_length, target_length), if_source_too_large); // Grab pointers and byte lengths we need later on. TNode target_data_ptr = UncheckedCast(LoadDataPtr(target)); TNode source_data_ptr = UncheckedCast(LoadDataPtr(source)); TNode source_el_kind = LoadElementsKind(source); TNode target_el_kind = LoadElementsKind(target); TNode source_el_size = GetTypedArrayElementSize(source_el_kind); TNode target_el_size = GetTypedArrayElementSize(target_el_kind); // A note on byte lengths: both source- and target byte lengths must be valid, // i.e. it must be possible to allocate an array of the given length. That // means we're safe from overflows in the following multiplication. TNode source_byte_length = IntPtrMul(source_length, source_el_size); CSA_ASSERT(this, UintPtrGreaterThanOrEqual(source_byte_length, IntPtrConstant(0))); Label call_memmove(this), fast_c_call(this), out(this), exception(this); // A fast memmove call can be used when the source and target types are are // the same or either Uint8 or Uint8Clamped. GotoIf(Word32Equal(source_el_kind, target_el_kind), &call_memmove); GotoIfNot(IsUint8ElementsKind(source_el_kind), &fast_c_call); Branch(IsUint8ElementsKind(target_el_kind), &call_memmove, &fast_c_call); BIND(&call_memmove); { TNode target_start = IntPtrAdd(target_data_ptr, IntPtrMul(offset, target_el_size)); CallCMemmove(target_start, source_data_ptr, source_byte_length); Goto(&out); } BIND(&fast_c_call); { CSA_ASSERT( this, UintPtrGreaterThanOrEqual( IntPtrMul(target_length, target_el_size), IntPtrConstant(0))); GotoIf(Word32NotEqual(IsBigInt64ElementsKind(source_el_kind), IsBigInt64ElementsKind(target_el_kind)), &exception); TNode source_length = SmiUntag(LoadJSTypedArrayLength(source)); CallCCopyTypedArrayElementsToTypedArray(source, target, source_length, offset); Goto(&out); } BIND(&exception); ThrowTypeError(context, MessageTemplate::kBigIntMixedTypes); BIND(&out); } void TypedArrayBuiltinsAssembler::SetJSArraySource( TNode context, TNode source, TNode target, TNode offset, Label* call_runtime, Label* if_source_too_large) { CSA_ASSERT(this, IsFastJSArray(source, context)); CSA_ASSERT(this, IntPtrGreaterThanOrEqual(offset, IntPtrConstant(0))); CSA_ASSERT(this, IntPtrLessThanOrEqual(offset, IntPtrConstant(Smi::kMaxValue))); TNode source_length = SmiUntag(LoadFastJSArrayLength(source)); TNode target_length = SmiUntag(LoadJSTypedArrayLength(target)); // Maybe out of bounds? GotoIf(IntPtrGreaterThan(IntPtrAdd(source_length, offset), target_length), if_source_too_large); // Nothing to do if {source} is empty. Label out(this), fast_c_call(this); GotoIf(IntPtrEqual(source_length, IntPtrConstant(0)), &out); // Dispatch based on the source elements kind. { // These are the supported elements kinds in TryCopyElementsFastNumber. int32_t values[] = { PACKED_SMI_ELEMENTS, HOLEY_SMI_ELEMENTS, PACKED_DOUBLE_ELEMENTS, HOLEY_DOUBLE_ELEMENTS, }; Label* labels[] = { &fast_c_call, &fast_c_call, &fast_c_call, &fast_c_call, }; STATIC_ASSERT(arraysize(values) == arraysize(labels)); TNode source_elements_kind = LoadElementsKind(source); Switch(source_elements_kind, call_runtime, values, labels, arraysize(values)); } BIND(&fast_c_call); GotoIf(IsBigInt64ElementsKind(LoadElementsKind(target)), call_runtime); CallCCopyFastNumberJSArrayElementsToTypedArray(context, source, target, source_length, offset); Goto(&out); BIND(&out); } void TypedArrayBuiltinsAssembler::CallCMemmove(TNode dest_ptr, TNode src_ptr, TNode byte_length) { TNode memmove = ExternalConstant(ExternalReference::libc_memmove_function()); CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(), MachineType::Pointer(), MachineType::UintPtr(), memmove, dest_ptr, src_ptr, byte_length); } void TypedArrayBuiltinsAssembler:: CallCCopyFastNumberJSArrayElementsToTypedArray(TNode context, TNode source, TNode dest, TNode source_length, TNode offset) { CSA_ASSERT(this, Word32BinaryNot(IsBigInt64ElementsKind(LoadElementsKind(dest)))); TNode f = ExternalConstant( ExternalReference::copy_fast_number_jsarray_elements_to_typed_array()); CallCFunction5(MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::UintPtr(), MachineType::UintPtr(), f, context, source, dest, source_length, offset); } void TypedArrayBuiltinsAssembler::CallCCopyTypedArrayElementsToTypedArray( TNode source, TNode dest, TNode source_length, TNode offset) { TNode f = ExternalConstant( ExternalReference::copy_typed_array_elements_to_typed_array()); CallCFunction4(MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::UintPtr(), MachineType::UintPtr(), f, source, dest, source_length, offset); } void TypedArrayBuiltinsAssembler::CallCCopyTypedArrayElementsSlice( TNode source, TNode dest, TNode start, TNode end) { TNode f = ExternalConstant(ExternalReference::copy_typed_array_elements_slice()); CallCFunction4(MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::AnyTagged(), MachineType::UintPtr(), MachineType::UintPtr(), f, source, dest, start, end); } void TypedArrayBuiltinsAssembler::DispatchTypedArrayByElementsKind( TNode elements_kind, const TypedArraySwitchCase& case_function) { Label next(this), if_unknown_type(this, Label::kDeferred); int32_t elements_kinds[] = { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) TYPE##_ELEMENTS, TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE }; #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) Label if_##type##array(this); TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE Label* elements_kind_labels[] = { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) &if_##type##array, TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE }; STATIC_ASSERT(arraysize(elements_kinds) == arraysize(elements_kind_labels)); Switch(elements_kind, &if_unknown_type, elements_kinds, elements_kind_labels, arraysize(elements_kinds)); #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ BIND(&if_##type##array); \ { \ case_function(TYPE##_ELEMENTS, sizeof(ctype), \ Context::TYPE##_ARRAY_FUN_INDEX); \ Goto(&next); \ } TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE BIND(&if_unknown_type); Unreachable(); BIND(&next); } // ES #sec-get-%typedarray%.prototype.set TF_BUILTIN(TypedArrayPrototypeSet, TypedArrayBuiltinsAssembler) { const char* method_name = "%TypedArray%.prototype.set"; TNode context = CAST(Parameter(Descriptor::kContext)); CodeStubArguments args( this, ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount))); Label if_source_is_typed_array(this), if_source_is_fast_jsarray(this), if_offset_is_out_of_bounds(this, Label::kDeferred), if_source_too_large(this, Label::kDeferred), if_receiver_is_not_typedarray(this, Label::kDeferred); // Check the receiver is a typed array. TNode receiver = args.GetReceiver(); GotoIf(TaggedIsSmi(receiver), &if_receiver_is_not_typedarray); GotoIfNot(IsJSTypedArray(CAST(receiver)), &if_receiver_is_not_typedarray); // Normalize offset argument (using ToInteger) and handle heap number cases. TNode offset = args.GetOptionalArgumentValue(1, SmiConstant(0)); TNode offset_num = ToInteger_Inline(context, offset, kTruncateMinusZero); // 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. // TODO(jgruber): It's an observable spec violation to throw here if // {offset_num} is a positive number outside the Smi range. Per spec, we need // to check for detached buffers and call the observable ToObject/ToLength // operations first. GotoIfNot(TaggedIsPositiveSmi(offset_num), &if_offset_is_out_of_bounds); TNode offset_smi = CAST(offset_num); // Check the receiver is not neutered. ThrowIfArrayBufferViewBufferIsDetached(context, CAST(receiver), method_name); // Check the source argument is valid and whether a fast path can be taken. Label call_runtime(this); TNode source = args.GetOptionalArgumentValue(0); GotoIf(TaggedIsSmi(source), &call_runtime); GotoIf(IsJSTypedArray(CAST(source)), &if_source_is_typed_array); BranchIfFastJSArray(source, context, &if_source_is_fast_jsarray, &call_runtime); // Fast path for a typed array source argument. BIND(&if_source_is_typed_array); { // Check the source argument is not neutered. ThrowIfArrayBufferViewBufferIsDetached(context, CAST(source), method_name); SetTypedArraySource(context, CAST(source), CAST(receiver), SmiUntag(offset_smi), &call_runtime, &if_source_too_large); args.PopAndReturn(UndefinedConstant()); } // Fast path for a fast JSArray source argument. BIND(&if_source_is_fast_jsarray); { SetJSArraySource(context, CAST(source), CAST(receiver), SmiUntag(offset_smi), &call_runtime, &if_source_too_large); args.PopAndReturn(UndefinedConstant()); } 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_source_too_large); ThrowRangeError(context, MessageTemplate::kTypedArraySetSourceTooLarge); BIND(&if_receiver_is_not_typedarray); ThrowTypeError(context, MessageTemplate::kNotTypedArray); } // ES %TypedArray%.prototype.slice TF_BUILTIN(TypedArrayPrototypeSlice, TypedArrayBuiltinsAssembler) { const char* method_name = "%TypedArray%.prototype.slice"; Label call_c(this), call_memmove(this), if_count_is_not_zero(this), if_bigint_mixed_types(this, Label::kDeferred); TNode context = CAST(Parameter(Descriptor::kContext)); CodeStubArguments args( this, ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount))); TNode receiver = args.GetReceiver(); TNode source = ValidateTypedArray(context, receiver, method_name); TNode source_length = LoadJSTypedArrayLength(source); // Convert start offset argument to integer, and calculate relative offset. TNode start = args.GetOptionalArgumentValue(0, SmiConstant(0)); TNode start_index = SmiTag(ConvertToRelativeIndex(context, start, SmiUntag(source_length))); // Convert end offset argument to integer, and calculate relative offset. // If end offset is not given or undefined is given, set source_length to // "end_index". TNode end = args.GetOptionalArgumentValue(1, UndefinedConstant()); TNode end_index = Select(IsUndefined(end), [=] { return source_length; }, [=] { return SmiTag(ConvertToRelativeIndex( context, end, SmiUntag(source_length))); }); // Create a result array by invoking TypedArraySpeciesCreate. TNode count = SmiMax(SmiSub(end_index, start_index), SmiConstant(0)); TNode result_array = SpeciesCreateByLength(context, source, count, method_name); // If count is zero, return early. GotoIf(SmiGreaterThan(count, SmiConstant(0)), &if_count_is_not_zero); args.PopAndReturn(result_array); BIND(&if_count_is_not_zero); // Check the source array is neutered or not. We don't need to check if the // result array is neutered or not since TypedArraySpeciesCreate checked it. CSA_ASSERT(this, Word32BinaryNot(IsDetachedBuffer(LoadObjectField( result_array, JSTypedArray::kBufferOffset)))); TNode receiver_buffer = LoadJSArrayBufferViewBuffer(CAST(receiver)); ThrowIfArrayBufferIsDetached(context, receiver_buffer, method_name); // result_array could be a different type from source or share the same // buffer with the source because of custom species constructor. // If the types of source and result array are the same and they are not // sharing the same buffer, use memmove. TNode source_el_kind = LoadElementsKind(source); TNode target_el_kind = LoadElementsKind(result_array); GotoIfNot(Word32Equal(source_el_kind, target_el_kind), &call_c); TNode target_buffer = LoadObjectField(result_array, JSTypedArray::kBufferOffset); Branch(WordEqual(receiver_buffer, target_buffer), &call_c, &call_memmove); BIND(&call_memmove); { GotoIfForceSlowPath(&call_c); TNode target_data_ptr = UncheckedCast(LoadDataPtr(result_array)); TNode source_data_ptr = UncheckedCast(LoadDataPtr(source)); TNode source_el_size = GetTypedArrayElementSize(source_el_kind); TNode source_start_bytes = IntPtrMul(SmiToIntPtr(start_index), source_el_size); TNode source_start = IntPtrAdd(source_data_ptr, source_start_bytes); TNode count_bytes = IntPtrMul(SmiToIntPtr(count), source_el_size); #ifdef DEBUG TNode target_byte_length = LoadJSArrayBufferViewByteLength(result_array); CSA_ASSERT(this, UintPtrLessThanOrEqual(Unsigned(count_bytes), target_byte_length)); TNode source_byte_length = LoadJSArrayBufferViewByteLength(source); TNode source_size_in_bytes = UintPtrSub(source_byte_length, Unsigned(source_start_bytes)); CSA_ASSERT(this, UintPtrLessThanOrEqual(Unsigned(count_bytes), source_size_in_bytes)); #endif // DEBUG CallCMemmove(target_data_ptr, source_start, count_bytes); args.PopAndReturn(result_array); } BIND(&call_c); { GotoIf(Word32NotEqual(IsBigInt64ElementsKind(source_el_kind), IsBigInt64ElementsKind(target_el_kind)), &if_bigint_mixed_types); CallCCopyTypedArrayElementsSlice( source, result_array, SmiToIntPtr(start_index), SmiToIntPtr(end_index)); args.PopAndReturn(result_array); } BIND(&if_bigint_mixed_types); ThrowTypeError(context, MessageTemplate::kBigIntMixedTypes); } // ES %TypedArray%.prototype.subarray TF_BUILTIN(TypedArrayPrototypeSubArray, TypedArrayBuiltinsAssembler) { const char* method_name = "%TypedArray%.prototype.subarray"; Label offset_done(this); TVARIABLE(Smi, var_begin); TVARIABLE(Smi, var_end); TNode context = CAST(Parameter(Descriptor::kContext)); CodeStubArguments args( this, ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount))); // 1. Let O be the this value. // 3. If O does not have a [[TypedArrayName]] internal slot, throw a TypeError // exception. TNode receiver = args.GetReceiver(); ThrowIfNotInstanceType(context, receiver, JS_TYPED_ARRAY_TYPE, method_name); TNode source = CAST(receiver); // 5. Let buffer be O.[[ViewedArrayBuffer]]. TNode buffer = GetBuffer(context, source); // 6. Let srcLength be O.[[ArrayLength]]. TNode source_length = LoadJSTypedArrayLength(source); // 7. Let relativeBegin be ? ToInteger(begin). // 8. If relativeBegin < 0, let beginIndex be max((srcLength + relativeBegin), // 0); else let beginIndex be min(relativeBegin, srcLength). TNode begin = args.GetOptionalArgumentValue(0, SmiConstant(0)); var_begin = SmiTag(ConvertToRelativeIndex(context, begin, SmiUntag(source_length))); TNode end = args.GetOptionalArgumentValue(1, UndefinedConstant()); // 9. If end is undefined, let relativeEnd be srcLength; var_end = source_length; GotoIf(IsUndefined(end), &offset_done); // else, let relativeEnd be ? ToInteger(end). // 10. If relativeEnd < 0, let endIndex be max((srcLength + relativeEnd), 0); // else let endIndex be min(relativeEnd, srcLength). var_end = SmiTag(ConvertToRelativeIndex(context, end, SmiUntag(source_length))); Goto(&offset_done); BIND(&offset_done); // 11. Let newLength be max(endIndex - beginIndex, 0). TNode new_length = SmiMax(SmiSub(var_end.value(), var_begin.value()), SmiConstant(0)); // 12. Let constructorName be the String value of O.[[TypedArrayName]]. // 13. Let elementSize be the Number value of the Element Size value specified // in Table 52 for constructorName. TNode element_kind = LoadElementsKind(source); TNode element_size = GetTypedArrayElementSize(element_kind); // 14. Let srcByteOffset be O.[[ByteOffset]]. TNode source_byte_offset = ChangeUintPtrToTagged(LoadJSArrayBufferViewByteOffset(source)); // 15. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. TNode offset = SmiMul(var_begin.value(), SmiFromIntPtr(element_size)); TNode begin_byte_offset = NumberAdd(source_byte_offset, offset); // 16. Let argumentsList be « buffer, beginByteOffset, newLength ». // 17. Return ? TypedArraySpeciesCreate(O, argumentsList). args.PopAndReturn(SpeciesCreateByArrayBuffer( context, source, buffer, begin_byte_offset, new_length, method_name)); } // ES #sec-get-%typedarray%.prototype-@@tostringtag TF_BUILTIN(TypedArrayPrototypeToStringTag, TypedArrayBuiltinsAssembler) { Node* receiver = Parameter(Descriptor::kReceiver); Label if_receiverisheapobject(this), return_undefined(this); Branch(TaggedIsSmi(receiver), &return_undefined, &if_receiverisheapobject); // Dispatch on the elements kind, offset by // FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND. size_t const kTypedElementsKindCount = LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND - FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1; #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ Label return_##type##array(this); \ BIND(&return_##type##array); \ Return(StringConstant(#Type "Array")); TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE Label* elements_kind_labels[kTypedElementsKindCount] = { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) &return_##type##array, TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE }; int32_t elements_kinds[kTypedElementsKindCount] = { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ TYPE##_ELEMENTS - FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND, TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE }; // We offset the dispatch by FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND, so // that this can be turned into a non-sparse table switch for ideal // performance. BIND(&if_receiverisheapobject); Node* elements_kind = Int32Sub(LoadElementsKind(receiver), Int32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); Switch(elements_kind, &return_undefined, elements_kinds, elements_kind_labels, kTypedElementsKindCount); BIND(&return_undefined); Return(UndefinedConstant()); } void TypedArrayBuiltinsAssembler::GenerateTypedArrayPrototypeIterationMethod( TNode context, TNode receiver, const char* method_name, IterationKind kind) { Label throw_bad_receiver(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver), &throw_bad_receiver); GotoIfNot(IsJSTypedArray(CAST(receiver)), &throw_bad_receiver); // Check if the {receiver}'s JSArrayBuffer was neutered. ThrowIfArrayBufferViewBufferIsDetached(context, CAST(receiver), method_name); Return(CreateArrayIterator(context, receiver, kind)); BIND(&throw_bad_receiver); ThrowTypeError(context, MessageTemplate::kNotTypedArray, method_name); } // ES #sec-%typedarray%.prototype.values TF_BUILTIN(TypedArrayPrototypeValues, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); GenerateTypedArrayPrototypeIterationMethod(context, receiver, "%TypedArray%.prototype.values()", IterationKind::kValues); } // ES #sec-%typedarray%.prototype.entries TF_BUILTIN(TypedArrayPrototypeEntries, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); GenerateTypedArrayPrototypeIterationMethod(context, receiver, "%TypedArray%.prototype.entries()", IterationKind::kEntries); } // ES #sec-%typedarray%.prototype.keys TF_BUILTIN(TypedArrayPrototypeKeys, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(Parameter(Descriptor::kReceiver)); GenerateTypedArrayPrototypeIterationMethod( context, receiver, "%TypedArray%.prototype.keys()", IterationKind::kKeys); } // ES6 #sec-%typedarray%.of TF_BUILTIN(TypedArrayOf, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); // 1. Let len be the actual number of arguments passed to this function. TNode length = ChangeInt32ToIntPtr( UncheckedCast(Parameter(Descriptor::kJSActualArgumentsCount))); // 2. Let items be the List of arguments passed to this function. CodeStubArguments args(this, length, nullptr, INTPTR_PARAMETERS, CodeStubArguments::ReceiverMode::kHasReceiver); Label if_not_constructor(this, Label::kDeferred), if_neutered(this, Label::kDeferred); // 3. Let C be the this value. // 4. If IsConstructor(C) is false, throw a TypeError exception. TNode receiver = args.GetReceiver(); GotoIf(TaggedIsSmi(receiver), &if_not_constructor); GotoIfNot(IsConstructor(CAST(receiver)), &if_not_constructor); // 5. Let newObj be ? TypedArrayCreate(C, len). TNode new_typed_array = CreateByLength(context, receiver, SmiTag(length), "%TypedArray%.of"); TNode elements_kind = LoadElementsKind(new_typed_array); // 6. Let k be 0. // 7. Repeat, while k < len // a. Let kValue be items[k]. // b. Let Pk be ! ToString(k). // c. Perform ? Set(newObj, Pk, kValue, true). // d. Increase k by 1. DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind kind, int size, int typed_array_fun_index) { TNode elements = CAST(LoadElements(new_typed_array)); BuildFastLoop( IntPtrConstant(0), length, [&](Node* index) { TNode item = args.AtIndex(index, INTPTR_PARAMETERS); TNode intptr_index = UncheckedCast(index); if (kind == BIGINT64_ELEMENTS || kind == BIGUINT64_ELEMENTS) { EmitBigTypedArrayElementStore(new_typed_array, elements, intptr_index, item, context, &if_neutered); } else { Node* value = PrepareValueForWriteToTypedArray(item, kind, context); // ToNumber may execute JavaScript code, which could neuter // the array's buffer. Node* buffer = LoadObjectField(new_typed_array, JSTypedArray::kBufferOffset); GotoIf(IsDetachedBuffer(buffer), &if_neutered); // GC may move backing store in ToNumber, thus load backing // store everytime in this loop. TNode backing_store = LoadFixedTypedArrayBackingStore(elements); StoreElement(backing_store, kind, index, value, INTPTR_PARAMETERS); } }, 1, ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost); }); // 8. Return newObj. args.PopAndReturn(new_typed_array); BIND(&if_not_constructor); ThrowTypeError(context, MessageTemplate::kNotConstructor, receiver); BIND(&if_neutered); ThrowTypeError(context, MessageTemplate::kDetachedOperation, "%TypedArray%.of"); } // ES6 #sec-%typedarray%.from TF_BUILTIN(TypedArrayFrom, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); Label check_iterator(this), from_array_like(this), fast_path(this), slow_path(this), create_typed_array(this), if_not_constructor(this, Label::kDeferred), if_map_fn_not_callable(this, Label::kDeferred), if_iterator_fn_not_callable(this, Label::kDeferred), if_neutered(this, Label::kDeferred); CodeStubArguments args( this, ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount))); TNode source = args.GetOptionalArgumentValue(0); // 5. If thisArg is present, let T be thisArg; else let T be undefined. TNode this_arg = args.GetOptionalArgumentValue(2); // 1. Let C be the this value. // 2. If IsConstructor(C) is false, throw a TypeError exception. TNode receiver = args.GetReceiver(); GotoIf(TaggedIsSmi(receiver), &if_not_constructor); GotoIfNot(IsConstructor(CAST(receiver)), &if_not_constructor); // 3. If mapfn is present and mapfn is not undefined, then TNode map_fn = args.GetOptionalArgumentValue(1); TVARIABLE(BoolT, mapping, Int32FalseConstant()); GotoIf(IsUndefined(map_fn), &check_iterator); // a. If IsCallable(mapfn) is false, throw a TypeError exception. // b. Let mapping be true. // 4. Else, let mapping be false. GotoIf(TaggedIsSmi(map_fn), &if_map_fn_not_callable); GotoIfNot(IsCallable(CAST(map_fn)), &if_map_fn_not_callable); mapping = Int32TrueConstant(); Goto(&check_iterator); TVARIABLE(Object, final_source); TVARIABLE(Smi, final_length); // We split up this builtin differently to the way it is written in the spec. // We already have great code in the elements accessor for copying from a // JSArray into a TypedArray, so we use that when possible. We only avoid // calling into the elements accessor when we have a mapping function, because // we can't handle that. Here, presence of a mapping function is the slow // path. We also combine the two different loops in the specification // (starting at 7.e and 13) because they are essentially identical. We also // save on code-size this way. BIND(&check_iterator); { // 6. Let usingIterator be ? GetMethod(source, @@iterator). TNode iterator_fn = CAST(GetMethod(context, source, isolate()->factory()->iterator_symbol(), &from_array_like)); GotoIf(TaggedIsSmi(iterator_fn), &if_iterator_fn_not_callable); GotoIfNot(IsCallable(CAST(iterator_fn)), &if_iterator_fn_not_callable); // We are using the iterator. Label if_length_not_smi(this, Label::kDeferred); // 7. If usingIterator is not undefined, then // a. Let values be ? IterableToList(source, usingIterator). // b. Let len be the number of elements in values. TNode values = CAST( CallBuiltin(Builtins::kIterableToList, context, source, iterator_fn)); // This is not a spec'd limit, so it doesn't particularly matter when we // throw the range error for typed array length > MaxSmi. TNode raw_length = LoadJSArrayLength(values); GotoIfNot(TaggedIsSmi(raw_length), &if_length_not_smi); final_length = CAST(raw_length); final_source = values; Goto(&create_typed_array); BIND(&if_length_not_smi); ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength, raw_length); } BIND(&from_array_like); { // TODO(7881): support larger-than-smi typed array lengths Label if_length_not_smi(this, Label::kDeferred); final_source = source; // 10. Let len be ? ToLength(? Get(arrayLike, "length")). TNode raw_length = GetProperty(context, final_source.value(), LengthStringConstant()); final_length = ToSmiLength(raw_length, context, &if_length_not_smi); Goto(&create_typed_array); BIND(&if_length_not_smi); ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength, raw_length); } TVARIABLE(JSTypedArray, target_obj); BIND(&create_typed_array); { // 7c/11. Let targetObj be ? TypedArrayCreate(C, «len»). target_obj = CreateByLength(context, receiver, final_length.value(), "%TypedArray%.from"); Branch(mapping.value(), &slow_path, &fast_path); } BIND(&fast_path); { Label done(this); GotoIf(SmiEqual(final_length.value(), SmiConstant(0)), &done); CallRuntime(Runtime::kTypedArrayCopyElements, context, target_obj.value(), final_source.value(), final_length.value()); Goto(&done); BIND(&done); args.PopAndReturn(target_obj.value()); } BIND(&slow_path); TNode elements_kind = LoadElementsKind(target_obj.value()); // 7e/13 : Copy the elements TNode elements = CAST(LoadElements(target_obj.value())); BuildFastLoop( SmiConstant(0), final_length.value(), [&](Node* index) { TNode const k_value = GetProperty(context, final_source.value(), index); TNode const mapped_value = CAST(CallJS(CodeFactory::Call(isolate()), context, map_fn, this_arg, k_value, index)); TNode intptr_index = SmiUntag(index); DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind kind, int size, int typed_array_fun_index) { if (kind == BIGINT64_ELEMENTS || kind == BIGUINT64_ELEMENTS) { EmitBigTypedArrayElementStore(target_obj.value(), elements, intptr_index, mapped_value, context, &if_neutered); } else { Node* const final_value = PrepareValueForWriteToTypedArray( mapped_value, kind, context); // ToNumber may execute JavaScript code, which could neuter // the array's buffer. Node* buffer = LoadObjectField(target_obj.value(), JSTypedArray::kBufferOffset); GotoIf(IsDetachedBuffer(buffer), &if_neutered); // GC may move backing store in map_fn, thus load backing // store in each iteration of this loop. TNode backing_store = LoadFixedTypedArrayBackingStore(elements); StoreElement(backing_store, kind, index, final_value, SMI_PARAMETERS); } }); }, 1, ParameterMode::SMI_PARAMETERS, IndexAdvanceMode::kPost); args.PopAndReturn(target_obj.value()); BIND(&if_not_constructor); ThrowTypeError(context, MessageTemplate::kNotConstructor, receiver); BIND(&if_map_fn_not_callable); ThrowTypeError(context, MessageTemplate::kCalledNonCallable, map_fn); BIND(&if_iterator_fn_not_callable); ThrowTypeError(context, MessageTemplate::kIteratorSymbolNonCallable); BIND(&if_neutered); ThrowTypeError(context, MessageTemplate::kDetachedOperation, "%TypedArray%.from"); } // ES %TypedArray%.prototype.filter TF_BUILTIN(TypedArrayPrototypeFilter, TypedArrayBuiltinsAssembler) { const char* method_name = "%TypedArray%.prototype.filter"; TNode context = CAST(Parameter(Descriptor::kContext)); CodeStubArguments args( this, ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount))); Label if_callback_not_callable(this, Label::kDeferred), detached(this, Label::kDeferred); // 1. Let O be the this value. // 2. Perform ? ValidateTypedArray(O). TNode receiver = args.GetReceiver(); TNode source = ValidateTypedArray(context, receiver, method_name); // 3. Let len be O.[[ArrayLength]]. TNode length = LoadJSTypedArrayLength(source); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. TNode callbackfn = args.GetOptionalArgumentValue(0); GotoIf(TaggedIsSmi(callbackfn), &if_callback_not_callable); GotoIfNot(IsCallable(CAST(callbackfn)), &if_callback_not_callable); // 5. If thisArg is present, let T be thisArg; else let T be undefined. TNode this_arg = args.GetOptionalArgumentValue(1); TNode source_buffer = LoadObjectField(source, JSArrayBufferView::kBufferOffset); TNode elements_kind = LoadElementsKind(source); GrowableFixedArray values(state()); VariableList vars( {values.var_array(), values.var_length(), values.var_capacity()}, zone()); // 6. Let kept be a new empty List. // 7. Let k be 0. // 8. Let captured be 0. // 9. Repeat, while k < len BuildFastLoop( vars, SmiConstant(0), length, [&](Node* index) { GotoIf(IsDetachedBuffer(source_buffer), &detached); TVARIABLE(Numeric, value); // a. Let Pk be ! ToString(k). // b. Let kValue be ? Get(O, Pk). DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind kind, int size, int typed_array_fun_index) { TNode backing_store = UncheckedCast(LoadDataPtr(source)); value = CAST(LoadFixedTypedArrayElementAsTagged( backing_store, index, kind, ParameterMode::SMI_PARAMETERS)); }); // c. Let selected be ToBoolean(Call(callbackfn, T, kValue, k, O)) Node* selected = CallJS(CodeFactory::Call(isolate()), context, callbackfn, this_arg, value.value(), index, source); Label true_continue(this), false_continue(this); BranchIfToBooleanIsTrue(selected, &true_continue, &false_continue); BIND(&true_continue); // d. If selected is true, then // i. Append kValue to the end of kept. // ii. Increase captured by 1. values.Push(value.value()); Goto(&false_continue); BIND(&false_continue); }, 1, ParameterMode::SMI_PARAMETERS, IndexAdvanceMode::kPost); TNode values_array = values.ToJSArray(context); TNode captured = LoadFastJSArrayLength(values_array); // 10. Let A be ? TypedArraySpeciesCreate(O, captured). TNode result_array = SpeciesCreateByLength(context, source, captured, method_name); // 11. Let n be 0. // 12. For each element e of kept, do // a. Perform ! Set(A, ! ToString(n), e, true). // b. Increment n by 1. CallRuntime(Runtime::kTypedArrayCopyElements, context, result_array, values_array, captured); // 13. Return A. args.PopAndReturn(result_array); BIND(&if_callback_not_callable); ThrowTypeError(context, MessageTemplate::kCalledNonCallable, callbackfn); BIND(&detached); ThrowTypeError(context, MessageTemplate::kDetachedOperation, method_name); } #undef V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP } // namespace internal } // namespace v8