// 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/execution/protectors.h" #include "src/handles/handles-inl.h" #include "src/heap/factory-inl.h" namespace v8 { namespace internal { using compiler::Node; // ----------------------------------------------------------------------------- // ES6 section 22.2 TypedArray Objects // Sets the embedder fields to 0 for a TypedArray which is under construction. void TypedArrayBuiltinsAssembler::SetupTypedArrayEmbedderFields( TNode holder) { for (int offset = JSTypedArray::kHeaderSize; offset < JSTypedArray::kSizeWithEmbedderFields; offset += kTaggedSize) { StoreObjectField(holder, offset, SmiConstant(0)); } } // Allocate a new ArrayBuffer and initialize it with empty properties and // elements. // TODO(bmeurer,v8:4153): Rename this and maybe fix up the implementation a bit. TNode TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer( TNode context, TNode byte_length) { TNode native_context = LoadNativeContext(context); TNode map = CAST(LoadContextElement(native_context, Context::ARRAY_BUFFER_MAP_INDEX)); TNode empty_fixed_array = EmptyFixedArrayConstant(); TNode buffer = UncheckedCast( 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 IsDetachable bits of BitFieldSlot. // - Set the byte_length field to byte_length. // - Set backing_store to null/Smi(0). // - Set extension to null. // - Set all embedder fields to Smi(0). if (FIELD_SIZE(JSArrayBuffer::kOptionalPaddingOffset) != 0) { DCHECK_EQ(4, FIELD_SIZE(JSArrayBuffer::kOptionalPaddingOffset)); StoreObjectFieldNoWriteBarrier( buffer, JSArrayBuffer::kOptionalPaddingOffset, Int32Constant(0)); } int32_t bitfield_value = (1 << JSArrayBuffer::IsExternalBit::kShift) | (1 << JSArrayBuffer::IsDetachableBit::kShift); StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBitFieldOffset, Int32Constant(bitfield_value)); StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kByteLengthOffset, byte_length); StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kBackingStoreOffset, IntPtrConstant(0)); if (V8_ARRAY_BUFFER_EXTENSION_BOOL) { StoreObjectFieldNoWriteBarrier(buffer, JSArrayBuffer::kExtensionOffset, IntPtrConstant(0)); } for (int offset = JSArrayBuffer::kHeaderSize; offset < JSArrayBuffer::kSizeWithEmbedderFields; offset += kTaggedSize) { StoreObjectFieldNoWriteBarrier(buffer, offset, SmiConstant(0)); } return buffer; } TF_BUILTIN(TypedArrayBaseConstructor, TypedArrayBuiltinsAssembler) { TNode context = CAST(Parameter(Descriptor::kContext)); ThrowTypeError(context, MessageTemplate::kConstructAbstractClass, "TypedArray"); } // 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)); TNode argc = ChangeInt32ToIntPtr( UncheckedCast(Parameter(Descriptor::kJSActualArgumentsCount))); CodeStubArguments args(this, argc); TNode arg1 = args.GetOptionalArgumentValue(0); TNode arg2 = args.GetOptionalArgumentValue(1); TNode 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); TNode 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"; TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(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 detached. 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"; TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(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 detached. 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"; TNode context = CAST(Parameter(Descriptor::kContext)); TNode receiver = CAST(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 detached. TNode receiver_buffer = LoadJSArrayBufferViewBuffer(CAST(receiver)); TNode length = Select( IsDetachedBuffer(receiver_buffer), [=] { return UintPtrConstant(0); }, [=] { return LoadJSTypedArrayLength(CAST(receiver)); }); Return(ChangeUintPtrToTagged(length)); } TNode TypedArrayBuiltinsAssembler::IsUint8ElementsKind( TNode kind) { return Word32Or(Word32Equal(kind, Int32Constant(UINT8_ELEMENTS)), Word32Equal(kind, Int32Constant(UINT8_CLAMPED_ELEMENTS))); } TNode TypedArrayBuiltinsAssembler::IsBigInt64ElementsKind( TNode kind) { STATIC_ASSERT(BIGUINT64_ELEMENTS + 1 == BIGINT64_ELEMENTS); return IsElementsKindInRange(kind, BIGUINT64_ELEMENTS, BIGINT64_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(); } TorqueStructTypedArrayElementsInfo TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo( TNode typed_array) { return GetTypedArrayElementsInfo(LoadMap(typed_array)); } TorqueStructTypedArrayElementsInfo TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(TNode map) { TNode elements_kind = LoadMapElementsKind(map); TVARIABLE(UintPtrT, var_size_log2); TVARIABLE(Map, var_map); ReadOnlyRoots roots(isolate()); DispatchTypedArrayByElementsKind( elements_kind, [&](ElementsKind kind, int size, int typed_array_fun_index) { DCHECK_GT(size, 0); var_size_log2 = UintPtrConstant(ElementsKindToShiftSize(kind)); }); return TorqueStructTypedArrayElementsInfo{var_size_log2.value(), elements_kind}; } 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 CAST( LoadContextElement(LoadNativeContext(context), context_slot.value())); } TNode TypedArrayBuiltinsAssembler::GetBuffer( TNode context, TNode array) { Label call_runtime(this), done(this); TVARIABLE(Object, var_result); TNode buffer = LoadJSArrayBufferViewBuffer(array); GotoIf(IsDetachedBuffer(buffer), &call_runtime); TNode backing_store = LoadJSArrayBufferBackingStore(buffer); 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::CallCMemmove(TNode dest_ptr, TNode src_ptr, TNode byte_length) { TNode memmove = ExternalConstant(ExternalReference::libc_memmove_function()); CallCFunction(memmove, MachineType::AnyTagged(), std::make_pair(MachineType::Pointer(), dest_ptr), std::make_pair(MachineType::Pointer(), src_ptr), std::make_pair(MachineType::UintPtr(), byte_length)); } void TypedArrayBuiltinsAssembler::CallCMemcpy(TNode dest_ptr, TNode src_ptr, TNode byte_length) { TNode memcpy = ExternalConstant(ExternalReference::libc_memcpy_function()); CallCFunction(memcpy, MachineType::AnyTagged(), std::make_pair(MachineType::Pointer(), dest_ptr), std::make_pair(MachineType::Pointer(), src_ptr), std::make_pair(MachineType::UintPtr(), byte_length)); } void TypedArrayBuiltinsAssembler::CallCMemset(TNode dest_ptr, TNode value, TNode length) { TNode memset = ExternalConstant(ExternalReference::libc_memset_function()); CallCFunction(memset, MachineType::AnyTagged(), std::make_pair(MachineType::Pointer(), dest_ptr), std::make_pair(MachineType::IntPtr(), value), std::make_pair(MachineType::UintPtr(), 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()); CallCFunction(f, MachineType::AnyTagged(), std::make_pair(MachineType::AnyTagged(), context), std::make_pair(MachineType::AnyTagged(), source), std::make_pair(MachineType::AnyTagged(), dest), std::make_pair(MachineType::UintPtr(), source_length), std::make_pair(MachineType::UintPtr(), offset)); } void TypedArrayBuiltinsAssembler::CallCCopyTypedArrayElementsToTypedArray( TNode source, TNode dest, TNode source_length, TNode offset) { TNode f = ExternalConstant( ExternalReference::copy_typed_array_elements_to_typed_array()); CallCFunction(f, MachineType::AnyTagged(), std::make_pair(MachineType::AnyTagged(), source), std::make_pair(MachineType::AnyTagged(), dest), std::make_pair(MachineType::UintPtr(), source_length), std::make_pair(MachineType::UintPtr(), offset)); } void TypedArrayBuiltinsAssembler::CallCCopyTypedArrayElementsSlice( TNode source, TNode dest, TNode start, TNode end) { TNode f = ExternalConstant(ExternalReference::copy_typed_array_elements_slice()); CallCFunction(f, MachineType::AnyTagged(), std::make_pair(MachineType::AnyTagged(), source), std::make_pair(MachineType::AnyTagged(), dest), std::make_pair(MachineType::UintPtr(), start), std::make_pair(MachineType::UintPtr(), 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); } void TypedArrayBuiltinsAssembler::SetJSTypedArrayOnHeapDataPtr( TNode holder, TNode base, TNode offset) { offset = UintPtrAdd(UintPtrConstant(ByteArray::kHeaderSize - kHeapObjectTag), offset); if (COMPRESS_POINTERS_BOOL) { TNode full_base = Signed(BitcastTaggedToWord(base)); TNode compressed_base = TruncateIntPtrToInt32(full_base); // TODO(v8:9706): Add a way to directly use kRootRegister value. TNode isolate_root = IntPtrSub(full_base, Signed(ChangeUint32ToWord(compressed_base))); // Add JSTypedArray::ExternalPointerCompensationForOnHeapArray() to offset. DCHECK_EQ( isolate()->isolate_root(), JSTypedArray::ExternalPointerCompensationForOnHeapArray(isolate())); // See JSTypedArray::SetOnHeapDataPtr() for details. offset = Unsigned(IntPtrAdd(offset, isolate_root)); } StoreObjectField(holder, JSTypedArray::kBasePointerOffset, base); StoreObjectFieldNoWriteBarrier( holder, JSTypedArray::kExternalPointerOffset, offset); } void TypedArrayBuiltinsAssembler::SetJSTypedArrayOffHeapDataPtr( TNode holder, TNode base, TNode offset) { StoreObjectFieldNoWriteBarrier(holder, JSTypedArray::kBasePointerOffset, SmiConstant(0)); base = RawPtrAdd(base, Signed(offset)); StoreObjectFieldNoWriteBarrier( holder, JSTypedArray::kExternalPointerOffset, base); } void TypedArrayBuiltinsAssembler::StoreJSTypedArrayElementFromNumeric( TNode context, TNode typed_array, TNode index, TNode value, ElementsKind elements_kind) { TNode data_ptr = LoadJSTypedArrayDataPtr(typed_array); switch (elements_kind) { case UINT8_ELEMENTS: case UINT8_CLAMPED_ELEMENTS: case INT8_ELEMENTS: case UINT16_ELEMENTS: case INT16_ELEMENTS: StoreElement(data_ptr, elements_kind, index, SmiToInt32(CAST(value))); break; case UINT32_ELEMENTS: case INT32_ELEMENTS: StoreElement(data_ptr, elements_kind, index, TruncateTaggedToWord32(context, value)); break; case FLOAT32_ELEMENTS: StoreElement(data_ptr, elements_kind, index, TruncateFloat64ToFloat32(LoadHeapNumberValue(CAST(value)))); break; case FLOAT64_ELEMENTS: StoreElement(data_ptr, elements_kind, index, LoadHeapNumberValue(CAST(value))); break; case BIGUINT64_ELEMENTS: case BIGINT64_ELEMENTS: StoreElement(data_ptr, elements_kind, index, UncheckedCast(value)); break; default: UNREACHABLE(); } } void TypedArrayBuiltinsAssembler::StoreJSTypedArrayElementFromTagged( TNode context, TNode typed_array, TNode index, TNode value, ElementsKind elements_kind, Label* if_detached) { // |prepared_value| is Word32T or Float64T or Float32T or BigInt. Node* prepared_value = PrepareValueForWriteToTypedArray(value, elements_kind, context); // ToNumber/ToBigInt may execute JavaScript code, which could detach // the array's buffer. TNode buffer = LoadJSArrayBufferViewBuffer(typed_array); GotoIf(IsDetachedBuffer(buffer), if_detached); TNode data_ptr = LoadJSTypedArrayDataPtr(typed_array); StoreElement(data_ptr, elements_kind, index, prepared_value); } // ES #sec-get-%typedarray%.prototype-@@tostringtag TF_BUILTIN(TypedArrayPrototypeToStringTag, TypedArrayBuiltinsAssembler) { TNode receiver = CAST(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); TNode receiver_heap_object = CAST(receiver); TNode elements_kind = Int32Sub(LoadElementsKind(receiver_heap_object), 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 detached. 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); } } // namespace internal } // namespace v8