| // 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-utils-gen.h" |
| #include "src/builtins/builtins.h" |
| #include "src/code-stub-assembler.h" |
| #include "src/handles-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| // 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 |
| |
| class TypedArrayBuiltinsAssembler : public CodeStubAssembler { |
| public: |
| explicit TypedArrayBuiltinsAssembler(compiler::CodeAssemblerState* state) |
| : CodeStubAssembler(state) {} |
| |
| protected: |
| void GenerateTypedArrayPrototypeGetter(Node* context, Node* receiver, |
| const char* method_name, |
| int object_offset); |
| void GenerateTypedArrayPrototypeIterationMethod(Node* context, Node* receiver, |
| const char* method_name, |
| IterationKind iteration_kind); |
| |
| void SetupTypedArray(Node* holder, Node* length, Node* byte_offset, |
| Node* byte_length); |
| void AttachBuffer(Node* holder, Node* buffer, Node* map, Node* length, |
| Node* byte_offset); |
| |
| Node* LoadMapForType(Node* array); |
| Node* CalculateExternalPointer(Node* backing_store, Node* byte_offset); |
| Node* LoadDataPtr(Node* typed_array); |
| Node* ByteLengthIsValid(Node* byte_length); |
| }; |
| |
| compiler::Node* TypedArrayBuiltinsAssembler::LoadMapForType(Node* array) { |
| CSA_ASSERT(this, IsJSTypedArray(array)); |
| |
| Label unreachable(this), done(this); |
| Label uint8_elements(this), uint8_clamped_elements(this), int8_elements(this), |
| uint16_elements(this), int16_elements(this), uint32_elements(this), |
| int32_elements(this), float32_elements(this), float64_elements(this); |
| Label* elements_kind_labels[] = { |
| &uint8_elements, &uint8_clamped_elements, &int8_elements, |
| &uint16_elements, &int16_elements, &uint32_elements, |
| &int32_elements, &float32_elements, &float64_elements}; |
| int32_t elements_kinds[] = { |
| UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS, |
| UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS, |
| INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS}; |
| const size_t kTypedElementsKindCount = LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND - |
| FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + |
| 1; |
| DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kinds)); |
| DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kind_labels)); |
| |
| VARIABLE(var_typed_map, MachineRepresentation::kTagged); |
| |
| Node* array_map = LoadMap(array); |
| Node* elements_kind = LoadMapElementsKind(array_map); |
| Switch(elements_kind, &unreachable, elements_kinds, elements_kind_labels, |
| kTypedElementsKindCount); |
| |
| for (int i = 0; i < static_cast<int>(kTypedElementsKindCount); i++) { |
| BIND(elements_kind_labels[i]); |
| { |
| ElementsKind kind = static_cast<ElementsKind>(elements_kinds[i]); |
| ExternalArrayType type = |
| isolate()->factory()->GetArrayTypeFromElementsKind(kind); |
| Handle<Map> map(isolate()->heap()->MapForFixedTypedArray(type)); |
| var_typed_map.Bind(HeapConstant(map)); |
| Goto(&done); |
| } |
| } |
| |
| BIND(&unreachable); |
| { Unreachable(); } |
| BIND(&done); |
| 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. |
| compiler::Node* TypedArrayBuiltinsAssembler::CalculateExternalPointer( |
| Node* backing_store, Node* byte_offset) { |
| return IntPtrAdd(backing_store, ChangeNumberToIntPtr(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(Node* holder, Node* length, |
| Node* byte_offset, |
| Node* byte_length) { |
| CSA_ASSERT(this, IsJSTypedArray(holder)); |
| CSA_ASSERT(this, TaggedIsSmi(length)); |
| CSA_ASSERT(this, IsNumber(byte_offset)); |
| CSA_ASSERT(this, IsNumber(byte_length)); |
| |
| StoreObjectField(holder, JSTypedArray::kLengthOffset, length); |
| StoreObjectField(holder, JSArrayBufferView::kByteOffsetOffset, byte_offset); |
| StoreObjectField(holder, JSArrayBufferView::kByteLengthOffset, byte_length); |
| 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(Node* holder, Node* buffer, |
| Node* map, Node* length, |
| Node* byte_offset) { |
| CSA_ASSERT(this, IsJSTypedArray(holder)); |
| CSA_ASSERT(this, IsJSArrayBuffer(buffer)); |
| CSA_ASSERT(this, IsMap(map)); |
| CSA_ASSERT(this, TaggedIsSmi(length)); |
| CSA_ASSERT(this, IsNumber(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)); |
| |
| Node* backing_store = LoadObjectField( |
| buffer, JSArrayBuffer::kBackingStoreOffset, MachineType::Pointer()); |
| |
| Node* external_pointer = CalculateExternalPointer(backing_store, byte_offset); |
| StoreObjectFieldNoWriteBarrier( |
| elements, FixedTypedArrayBase::kExternalPointerOffset, external_pointer, |
| MachineType::PointerRepresentation()); |
| |
| StoreObjectField(holder, JSObject::kElementsOffset, elements); |
| } |
| |
| TF_BUILTIN(TypedArrayInitializeWithBuffer, TypedArrayBuiltinsAssembler) { |
| Node* holder = Parameter(Descriptor::kHolder); |
| Node* length = Parameter(Descriptor::kLength); |
| Node* buffer = Parameter(Descriptor::kBuffer); |
| Node* element_size = Parameter(Descriptor::kElementSize); |
| Node* byte_offset = Parameter(Descriptor::kByteOffset); |
| |
| CSA_ASSERT(this, IsJSTypedArray(holder)); |
| CSA_ASSERT(this, TaggedIsSmi(length)); |
| CSA_ASSERT(this, IsJSArrayBuffer(buffer)); |
| CSA_ASSERT(this, TaggedIsSmi(element_size)); |
| CSA_ASSERT(this, IsNumber(byte_offset)); |
| |
| Node* fixed_typed_map = LoadMapForType(holder); |
| |
| // SmiMul returns a heap number in case of Smi overflow. |
| Node* byte_length = SmiMul(length, element_size); |
| CSA_ASSERT(this, IsNumber(byte_length)); |
| |
| SetupTypedArray(holder, length, byte_offset, byte_length); |
| AttachBuffer(holder, buffer, fixed_typed_map, length, byte_offset); |
| Return(UndefinedConstant()); |
| } |
| |
| TF_BUILTIN(TypedArrayInitialize, TypedArrayBuiltinsAssembler) { |
| Node* holder = Parameter(Descriptor::kHolder); |
| Node* length = Parameter(Descriptor::kLength); |
| Node* element_size = Parameter(Descriptor::kElementSize); |
| Node* initialize = Parameter(Descriptor::kInitialize); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| CSA_ASSERT(this, IsJSTypedArray(holder)); |
| CSA_ASSERT(this, TaggedIsPositiveSmi(length)); |
| CSA_ASSERT(this, TaggedIsPositiveSmi(element_size)); |
| CSA_ASSERT(this, IsBoolean(initialize)); |
| |
| Node* 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_no_init(this), attach_buffer(this), done(this); |
| VARIABLE(var_total_size, MachineType::PointerRepresentation()); |
| |
| // SmiMul returns a heap number in case of Smi overflow. |
| Node* byte_length = SmiMul(length, element_size); |
| CSA_ASSERT(this, IsNumber(byte_length)); |
| |
| SetupTypedArray(holder, length, byte_offset, byte_length); |
| |
| Node* fixed_typed_map = LoadMapForType(holder); |
| GotoIf(TaggedIsNotSmi(byte_length), &allocate_off_heap); |
| GotoIf( |
| SmiGreaterThan(byte_length, SmiConstant(V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP)), |
| &allocate_off_heap); |
| 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(Heap::kEmptyFixedArrayRootIndex); |
| |
| 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::IsExternal::kShift) | |
| (1 << JSArrayBuffer::IsNeuterable::kShift); |
| StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldOffset, |
| Int32Constant(bitfield_value), |
| MachineRepresentation::kWord32); |
| |
| StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kByteLengthOffset, |
| byte_length); |
| 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. |
| GotoIf(SmiEqual(SmiMod(element_size, SmiConstant(kObjectAlignment)), |
| SmiConstant(0)), |
| &aligned); |
| |
| // Fix alignment if needed. |
| DCHECK_EQ(0, FixedTypedArrayBase::kHeaderSize & kObjectAlignmentMask); |
| Node* aligned_header_size = |
| IntPtrConstant(FixedTypedArrayBase::kHeaderSize + kObjectAlignmentMask); |
| Node* size = IntPtrAdd(SmiToWord(byte_length), aligned_header_size); |
| var_total_size.Bind(WordAnd(size, IntPtrConstant(~kObjectAlignmentMask))); |
| Goto(&allocate_elements); |
| } |
| |
| BIND(&aligned); |
| { |
| Node* header_size = IntPtrConstant(FixedTypedArrayBase::kHeaderSize); |
| var_total_size.Bind(IntPtrAdd(SmiToWord(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(isolate())); |
| CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(), |
| MachineType::IntPtr(), MachineType::UintPtr(), memset, |
| backing_store, IntPtrConstant(0), SmiToWord(byte_length)); |
| Goto(&done); |
| } |
| |
| VARIABLE(var_buffer, MachineRepresentation::kTagged); |
| |
| BIND(&allocate_off_heap); |
| { |
| GotoIf(IsFalse(initialize), &allocate_off_heap_no_init); |
| |
| Node* buffer_constructor = LoadContextElement( |
| LoadNativeContext(context), Context::ARRAY_BUFFER_FUN_INDEX); |
| var_buffer.Bind(ConstructJS(CodeFactory::Construct(isolate()), context, |
| buffer_constructor, 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.Bind(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); |
| Return(UndefinedConstant()); |
| } |
| |
| // ES6 #sec-typedarray-length |
| TF_BUILTIN(TypedArrayConstructByLength, TypedArrayBuiltinsAssembler) { |
| Node* holder = Parameter(Descriptor::kHolder); |
| Node* length = Parameter(Descriptor::kLength); |
| Node* element_size = Parameter(Descriptor::kElementSize); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| CSA_ASSERT(this, IsJSTypedArray(holder)); |
| CSA_ASSERT(this, TaggedIsPositiveSmi(element_size)); |
| |
| Node* initialize = BooleanConstant(true); |
| |
| Label invalid_length(this); |
| |
| length = ToInteger(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 smi's). |
| GotoIf(TaggedIsNotSmi(length), &invalid_length); |
| GotoIf(SmiLessThan(length, SmiConstant(0)), &invalid_length); |
| |
| CallBuiltin(Builtins::kTypedArrayInitialize, context, holder, length, |
| element_size, initialize); |
| Return(UndefinedConstant()); |
| |
| BIND(&invalid_length); |
| { |
| CallRuntime(Runtime::kThrowRangeError, context, |
| SmiConstant(MessageTemplate::kInvalidTypedArrayLength), length); |
| Unreachable(); |
| } |
| } |
| |
| // ES6 #sec-typedarray-buffer-byteoffset-length |
| TF_BUILTIN(TypedArrayConstructByArrayBuffer, TypedArrayBuiltinsAssembler) { |
| Node* holder = Parameter(Descriptor::kHolder); |
| Node* buffer = Parameter(Descriptor::kBuffer); |
| Node* byte_offset = Parameter(Descriptor::kByteOffset); |
| Node* length = Parameter(Descriptor::kLength); |
| Node* element_size = Parameter(Descriptor::kElementSize); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| CSA_ASSERT(this, IsJSTypedArray(holder)); |
| CSA_ASSERT(this, IsJSArrayBuffer(buffer)); |
| 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); |
| |
| GotoIf(IsUndefined(byte_offset), &check_length); |
| |
| offset.Bind( |
| ToInteger(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); |
| { |
| GotoIf(SmiEqual(offset.value(), SmiConstant(0)), &check_length); |
| GotoIf(SmiLessThan(offset.value(), SmiConstant(0)), &invalid_length); |
| Node* remainder = SmiMod(offset.value(), element_size); |
| Branch(SmiEqual(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); |
| // TODO(petermarshall): Throw on detached typedArray. |
| Branch(IsUndefined(length), &length_undefined, &length_defined); |
| |
| BIND(&length_undefined); |
| { |
| Node* buffer_byte_length = |
| 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); |
| { |
| Node* new_length = ToSmiIndex(length, context, &invalid_length); |
| 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. |
| Node* buffer_byte_length = |
| 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); |
| { |
| Node* new_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. |
| new_length = ToSmiIndex(new_length, context, &invalid_length); |
| |
| CallBuiltin(Builtins::kTypedArrayInitializeWithBuffer, context, holder, |
| new_length, buffer, element_size, offset.value()); |
| Return(UndefinedConstant()); |
| } |
| |
| BIND(&invalid_offset_error); |
| { |
| CallRuntime(Runtime::kThrowRangeError, context, |
| SmiConstant(MessageTemplate::kInvalidOffset), byte_offset); |
| Unreachable(); |
| } |
| |
| 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); |
| { |
| CallRuntime(Runtime::kThrowRangeError, context, |
| SmiConstant(MessageTemplate::kInvalidTypedArrayLength), length); |
| Unreachable(); |
| } |
| } |
| |
| compiler::Node* TypedArrayBuiltinsAssembler::LoadDataPtr(Node* typed_array) { |
| CSA_ASSERT(this, IsJSTypedArray(typed_array)); |
| Node* elements = LoadElements(typed_array); |
| CSA_ASSERT(this, IsFixedTypedArray(elements)); |
| Node* base_pointer = BitcastTaggedToWord( |
| LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset)); |
| Node* external_pointer = BitcastTaggedToWord( |
| LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset)); |
| return IntPtrAdd(base_pointer, external_pointer); |
| } |
| |
| compiler::Node* TypedArrayBuiltinsAssembler::ByteLengthIsValid( |
| Node* byte_length) { |
| Label smi(this), done(this); |
| VARIABLE(is_valid, MachineRepresentation::kWord32); |
| GotoIf(TaggedIsSmi(byte_length), &smi); |
| |
| CSA_ASSERT(this, IsHeapNumber(byte_length)); |
| Node* float_value = LoadHeapNumberValue(byte_length); |
| Node* max_byte_length_double = |
| Float64Constant(FixedTypedArrayBase::kMaxByteLength); |
| is_valid.Bind(Float64LessThanOrEqual(float_value, max_byte_length_double)); |
| Goto(&done); |
| |
| BIND(&smi); |
| Node* max_byte_length = IntPtrConstant(FixedTypedArrayBase::kMaxByteLength); |
| is_valid.Bind(UintPtrLessThanOrEqual(SmiUntag(byte_length), max_byte_length)); |
| Goto(&done); |
| |
| BIND(&done); |
| return is_valid.value(); |
| } |
| |
| TF_BUILTIN(TypedArrayConstructByArrayLike, TypedArrayBuiltinsAssembler) { |
| Node* holder = Parameter(Descriptor::kHolder); |
| Node* array_like = Parameter(Descriptor::kArrayLike); |
| Node* initial_length = Parameter(Descriptor::kLength); |
| Node* element_size = Parameter(Descriptor::kElementSize); |
| CSA_ASSERT(this, TaggedIsSmi(element_size)); |
| Node* context = Parameter(Descriptor::kContext); |
| |
| Node* initialize = BooleanConstant(false); |
| |
| Label invalid_length(this), fill(this), fast_copy(this); |
| |
| // The caller has looked up length on array_like, which is observable. |
| Node* length = ToSmiLength(initial_length, context, &invalid_length); |
| |
| CallBuiltin(Builtins::kTypedArrayInitialize, context, holder, length, |
| element_size, initialize); |
| GotoIf(SmiNotEqual(length, SmiConstant(0)), &fill); |
| Return(UndefinedConstant()); |
| |
| BIND(&fill); |
| Node* holder_kind = LoadMapElementsKind(LoadMap(holder)); |
| Node* source_kind = LoadMapElementsKind(LoadMap(array_like)); |
| GotoIf(Word32Equal(holder_kind, source_kind), &fast_copy); |
| |
| // Copy using the elements accessor. |
| CallRuntime(Runtime::kTypedArrayCopyElements, context, holder, array_like, |
| length); |
| Return(UndefinedConstant()); |
| |
| 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))); |
| |
| Node* byte_length = SmiMul(length, element_size); |
| CSA_ASSERT(this, ByteLengthIsValid(byte_length)); |
| Node* byte_length_intptr = ChangeNumberToIntPtr(byte_length); |
| CSA_ASSERT(this, UintPtrLessThanOrEqual( |
| byte_length_intptr, |
| IntPtrConstant(FixedTypedArrayBase::kMaxByteLength))); |
| |
| Node* memcpy = |
| ExternalConstant(ExternalReference::libc_memcpy_function(isolate())); |
| CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(), |
| MachineType::Pointer(), MachineType::UintPtr(), memcpy, |
| holder_data_ptr, source_data_ptr, byte_length_intptr); |
| Return(UndefinedConstant()); |
| } |
| |
| BIND(&invalid_length); |
| { |
| CallRuntime(Runtime::kThrowRangeError, context, |
| SmiConstant(MessageTemplate::kInvalidTypedArrayLength), |
| initial_length); |
| Unreachable(); |
| } |
| } |
| |
| void TypedArrayBuiltinsAssembler::GenerateTypedArrayPrototypeGetter( |
| Node* context, Node* receiver, const char* method_name, int object_offset) { |
| // Check if the {receiver} is actually a JSTypedArray. |
| Label receiver_is_incompatible(this, Label::kDeferred); |
| GotoIf(TaggedIsSmi(receiver), &receiver_is_incompatible); |
| GotoIfNot(HasInstanceType(receiver, JS_TYPED_ARRAY_TYPE), |
| &receiver_is_incompatible); |
| |
| // Check if the {receiver}'s JSArrayBuffer was neutered. |
| Node* receiver_buffer = |
| LoadObjectField(receiver, JSTypedArray::kBufferOffset); |
| Label if_receiverisneutered(this, Label::kDeferred); |
| GotoIf(IsDetachedBuffer(receiver_buffer), &if_receiverisneutered); |
| Return(LoadObjectField(receiver, object_offset)); |
| |
| BIND(&if_receiverisneutered); |
| { |
| // The {receiver}s buffer was neutered, default to zero. |
| Return(SmiConstant(0)); |
| } |
| |
| BIND(&receiver_is_incompatible); |
| { |
| // The {receiver} is not a valid JSTypedArray. |
| CallRuntime(Runtime::kThrowIncompatibleMethodReceiver, context, |
| StringConstant(method_name), receiver); |
| Unreachable(); |
| } |
| } |
| |
| // ES6 #sec-get-%typedarray%.prototype.bytelength |
| TF_BUILTIN(TypedArrayPrototypeByteLength, TypedArrayBuiltinsAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* receiver = Parameter(Descriptor::kReceiver); |
| GenerateTypedArrayPrototypeGetter(context, receiver, |
| "get TypedArray.prototype.byteLength", |
| JSTypedArray::kByteLengthOffset); |
| } |
| |
| // ES6 #sec-get-%typedarray%.prototype.byteoffset |
| TF_BUILTIN(TypedArrayPrototypeByteOffset, TypedArrayBuiltinsAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* receiver = Parameter(Descriptor::kReceiver); |
| GenerateTypedArrayPrototypeGetter(context, receiver, |
| "get TypedArray.prototype.byteOffset", |
| JSTypedArray::kByteOffsetOffset); |
| } |
| |
| // ES6 #sec-get-%typedarray%.prototype.length |
| TF_BUILTIN(TypedArrayPrototypeLength, TypedArrayBuiltinsAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* receiver = Parameter(Descriptor::kReceiver); |
| GenerateTypedArrayPrototypeGetter(context, receiver, |
| "get TypedArray.prototype.length", |
| JSTypedArray::kLengthOffset); |
| } |
| |
| // 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, size) \ |
| 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, size) &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, size) \ |
| 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(LoadMapElementsKind(LoadMap(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( |
| Node* context, Node* receiver, const char* method_name, |
| IterationKind iteration_kind) { |
| Label throw_bad_receiver(this, Label::kDeferred); |
| Label throw_typeerror(this, Label::kDeferred); |
| |
| GotoIf(TaggedIsSmi(receiver), &throw_bad_receiver); |
| |
| Node* map = LoadMap(receiver); |
| Node* instance_type = LoadMapInstanceType(map); |
| GotoIf(Word32NotEqual(instance_type, Int32Constant(JS_TYPED_ARRAY_TYPE)), |
| &throw_bad_receiver); |
| |
| // Check if the {receiver}'s JSArrayBuffer was neutered. |
| Node* receiver_buffer = |
| LoadObjectField(receiver, JSTypedArray::kBufferOffset); |
| Label if_receiverisneutered(this, Label::kDeferred); |
| GotoIf(IsDetachedBuffer(receiver_buffer), &if_receiverisneutered); |
| |
| Return(CreateArrayIterator(receiver, map, instance_type, context, |
| iteration_kind)); |
| |
| VARIABLE(var_message, MachineRepresentation::kTagged); |
| BIND(&throw_bad_receiver); |
| var_message.Bind(SmiConstant(MessageTemplate::kNotTypedArray)); |
| Goto(&throw_typeerror); |
| |
| BIND(&if_receiverisneutered); |
| var_message.Bind(SmiConstant(MessageTemplate::kDetachedOperation)); |
| Goto(&throw_typeerror); |
| |
| BIND(&throw_typeerror); |
| { |
| Node* method_arg = StringConstant(method_name); |
| Node* result = CallRuntime(Runtime::kThrowTypeError, context, |
| var_message.value(), method_arg); |
| Return(result); |
| } |
| } |
| |
| // ES6 #sec-%typedarray%.prototype.values |
| TF_BUILTIN(TypedArrayPrototypeValues, TypedArrayBuiltinsAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* receiver = Parameter(Descriptor::kReceiver); |
| GenerateTypedArrayPrototypeIterationMethod(context, receiver, |
| "%TypedArray%.prototype.values()", |
| IterationKind::kValues); |
| } |
| |
| // ES6 #sec-%typedarray%.prototype.entries |
| TF_BUILTIN(TypedArrayPrototypeEntries, TypedArrayBuiltinsAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* receiver = Parameter(Descriptor::kReceiver); |
| GenerateTypedArrayPrototypeIterationMethod(context, receiver, |
| "%TypedArray%.prototype.entries()", |
| IterationKind::kEntries); |
| } |
| |
| // ES6 #sec-%typedarray%.prototype.keys |
| TF_BUILTIN(TypedArrayPrototypeKeys, TypedArrayBuiltinsAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* receiver = Parameter(Descriptor::kReceiver); |
| GenerateTypedArrayPrototypeIterationMethod( |
| context, receiver, "%TypedArray%.prototype.keys()", IterationKind::kKeys); |
| } |
| |
| #undef V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP |
| |
| } // namespace internal |
| } // namespace v8 |