| // Copyright 2023 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. |
| |
| #ifndef V8_CODEGEN_CODE_STUB_ASSEMBLER_INL_H_ |
| #define V8_CODEGEN_CODE_STUB_ASSEMBLER_INL_H_ |
| |
| #include "src/codegen/code-stub-assembler.h" |
| // Include the non-inl header before the rest of the headers. |
| |
| #include <functional> |
| |
| #include "src/builtins/builtins-constructor-gen.h" |
| #include "src/builtins/builtins-inl.h" |
| #include "src/common/globals.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| #include "src/codegen/define-code-stub-assembler-macros.inc" |
| |
| template <typename TCallable, class... TArgs> |
| TNode<JSAny> CodeStubAssembler::Call(TNode<Context> context, |
| TNode<TCallable> callable, |
| ConvertReceiverMode mode, |
| TNode<JSAny> receiver, TArgs... args) { |
| static_assert(is_subtype_v<TCallable, Object>); |
| static_assert(!is_subtype_v<TCallable, JSFunction>, |
| "Use CallFunction() when the callable is a JSFunction."); |
| |
| if (IsUndefinedConstant(receiver) || IsNullConstant(receiver)) { |
| DCHECK_NE(mode, ConvertReceiverMode::kNotNullOrUndefined); |
| return CallJS(Builtins::Call(ConvertReceiverMode::kNullOrUndefined), |
| context, callable, receiver, args...); |
| } |
| DCheckReceiver(mode, receiver); |
| return CallJS(Builtins::Call(mode), context, callable, receiver, args...); |
| } |
| |
| template <typename TCallable, class... TArgs> |
| TNode<JSAny> CodeStubAssembler::Call(TNode<Context> context, |
| TNode<TCallable> callable, |
| TNode<JSReceiver> receiver, |
| TArgs... args) { |
| return Call(context, callable, ConvertReceiverMode::kNotNullOrUndefined, |
| receiver, args...); |
| } |
| |
| template <typename TCallable, class... TArgs> |
| TNode<JSAny> CodeStubAssembler::Call(TNode<Context> context, |
| TNode<TCallable> callable, |
| TNode<JSAny> receiver, TArgs... args) { |
| return Call(context, callable, ConvertReceiverMode::kAny, receiver, args...); |
| } |
| |
| template <class... TArgs> |
| TNode<JSAny> CodeStubAssembler::CallFunction(TNode<Context> context, |
| TNode<JSFunction> callable, |
| ConvertReceiverMode mode, |
| TNode<JSAny> receiver, |
| TArgs... args) { |
| if (IsUndefinedConstant(receiver) || IsNullConstant(receiver)) { |
| DCHECK_NE(mode, ConvertReceiverMode::kNotNullOrUndefined); |
| return CallJS(Builtins::CallFunction(ConvertReceiverMode::kNullOrUndefined), |
| context, callable, receiver, args...); |
| } |
| DCheckReceiver(mode, receiver); |
| return CallJS(Builtins::CallFunction(mode), context, callable, receiver, |
| args...); |
| } |
| |
| template <class... TArgs> |
| TNode<JSAny> CodeStubAssembler::CallFunction(TNode<Context> context, |
| TNode<JSFunction> callable, |
| TNode<JSReceiver> receiver, |
| TArgs... args) { |
| return CallFunction(context, callable, |
| ConvertReceiverMode::kNotNullOrUndefined, receiver, |
| args...); |
| } |
| |
| template <class... TArgs> |
| TNode<JSAny> CodeStubAssembler::CallFunction(TNode<Context> context, |
| TNode<JSFunction> callable, |
| TNode<JSAny> receiver, |
| TArgs... args) { |
| return CallFunction(context, callable, ConvertReceiverMode::kAny, receiver, |
| args...); |
| } |
| |
| template <typename Function> |
| TNode<Object> CodeStubAssembler::FastCloneJSObject( |
| TNode<HeapObject> object, TNode<Map> source_map, TNode<Map> target_map, |
| const Function& materialize_target, bool target_is_new) { |
| Label done_copy_properties(this), done_copy_elements(this); |
| |
| // This macro only suport JSObjects. |
| CSA_DCHECK(this, InstanceTypeEqual(LoadInstanceType(object), JS_OBJECT_TYPE)); |
| CSA_DCHECK(this, IsStrong(TNode<MaybeObject>(target_map))); |
| CSA_DCHECK( |
| this, InstanceTypeEqual(LoadMapInstanceType(target_map), JS_OBJECT_TYPE)); |
| // We do not want to deal with slack-tracking here. |
| CSA_DCHECK(this, IsNotSetWord32<Map::Bits3::ConstructionCounterBits>( |
| LoadMapBitField3(source_map))); |
| CSA_DCHECK(this, IsNotSetWord32<Map::Bits3::ConstructionCounterBits>( |
| LoadMapBitField3(target_map))); |
| |
| TVARIABLE((Union<FixedArray, PropertyArray>), var_properties, |
| EmptyFixedArrayConstant()); |
| TVARIABLE(FixedArray, var_elements, EmptyFixedArrayConstant()); |
| |
| // Copy the PropertyArray backing store. The source PropertyArray |
| // must be either an Smi, or a PropertyArray. |
| Comment("FastCloneJSObject: cloning properties"); |
| TNode<Object> source_properties = |
| LoadObjectField(object, JSObject::kPropertiesOrHashOffset); |
| { |
| GotoIf(TaggedIsSmi(source_properties), &done_copy_properties); |
| GotoIf(IsEmptyFixedArray(source_properties), &done_copy_properties); |
| |
| // This fastcase requires that the source object has fast properties. |
| TNode<PropertyArray> source_property_array = CAST(source_properties); |
| |
| TNode<IntPtrT> length = LoadPropertyArrayLength(source_property_array); |
| GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &done_copy_properties); |
| |
| TNode<PropertyArray> property_array = AllocatePropertyArray(length); |
| FillPropertyArrayWithUndefined(property_array, IntPtrConstant(0), length); |
| CopyPropertyArrayValues(source_property_array, property_array, length, |
| SKIP_WRITE_BARRIER, DestroySource::kNo); |
| var_properties = property_array; |
| } |
| |
| Goto(&done_copy_properties); |
| BIND(&done_copy_properties); |
| |
| Comment("FastCloneJSObject: cloning elements"); |
| TNode<FixedArrayBase> source_elements = LoadElements(CAST(object)); |
| GotoIf(TaggedEqual(source_elements, EmptyFixedArrayConstant()), |
| &done_copy_elements); |
| var_elements = CAST(CloneFixedArray( |
| source_elements, ExtractFixedArrayFlag::kAllFixedArraysDontCopyCOW)); |
| |
| Goto(&done_copy_elements); |
| BIND(&done_copy_elements); |
| |
| Comment("FastCloneJSObject: initialize the target object"); |
| TNode<JSReceiver> target = materialize_target( |
| target_map, var_properties.value(), var_elements.value()); |
| |
| // Lastly, clone any in-object properties. |
| #ifdef DEBUG |
| { |
| TNode<IntPtrT> source_used_instance_size = |
| MapUsedInstanceSizeInWords(source_map); |
| TNode<IntPtrT> target_used_instance_size = |
| MapUsedInstanceSizeInWords(target_map); |
| TNode<IntPtrT> source_inobject_properties_start = |
| LoadMapInobjectPropertiesStartInWords(source_map); |
| TNode<IntPtrT> target_inobject_properties_start = |
| LoadMapInobjectPropertiesStartInWords(target_map); |
| CSA_DCHECK(this, IntPtrEqual(IntPtrSub(target_used_instance_size, |
| target_inobject_properties_start), |
| IntPtrSub(source_used_instance_size, |
| source_inobject_properties_start))); |
| } |
| #endif // DEBUG |
| |
| // 1) Initialize unused in-object properties. |
| Comment("FastCloneJSObject: initializing unused in-object properties"); |
| TNode<IntPtrT> target_used_payload_end = |
| TimesTaggedSize(MapUsedInstanceSizeInWords(target_map)); |
| TNode<IntPtrT> target_payload_end = |
| TimesTaggedSize(LoadMapInstanceSizeInWords(target_map)); |
| InitializeFieldsWithRoot(target, target_used_payload_end, target_payload_end, |
| RootIndex::kUndefinedValue); |
| |
| // 2) Copy all used in-object properties. |
| Comment("FastCloneJSObject: copying used in-object properties"); |
| TNode<IntPtrT> source_payload_start = |
| TimesTaggedSize(LoadMapInobjectPropertiesStartInWords(source_map)); |
| TNode<IntPtrT> target_payload_start = |
| TimesTaggedSize(LoadMapInobjectPropertiesStartInWords(target_map)); |
| TNode<IntPtrT> field_offset_difference = |
| IntPtrSub(source_payload_start, target_payload_start); |
| |
| Label done_copy_used(this); |
| auto EmitCopyLoop = [&](bool write_barrier) { |
| if (write_barrier) { |
| Comment( |
| "FastCloneJSObject: copying used in-object properties with write " |
| "barrier"); |
| } else { |
| Comment( |
| "FastCloneJSObject: copying used in-object properties without write " |
| "barrier"); |
| } |
| BuildFastLoop<IntPtrT>( |
| target_payload_start, target_used_payload_end, |
| [&](TNode<IntPtrT> result_offset) { |
| TNode<IntPtrT> source_offset = |
| IntPtrSub(result_offset, field_offset_difference); |
| if (write_barrier) { |
| TNode<Object> field = LoadObjectField(object, source_offset); |
| StoreObjectField(target, result_offset, field); |
| } else { |
| TNode<TaggedT> field = |
| LoadObjectField<TaggedT>(object, source_offset); |
| StoreObjectFieldNoWriteBarrier(target, result_offset, field); |
| } |
| }, |
| kTaggedSize, LoopUnrollingMode::kYes, IndexAdvanceMode::kPost); |
| }; |
| |
| if (!target_is_new) { |
| Label if_no_write_barrier(this), |
| if_needs_write_barrier(this, Label::kDeferred); |
| |
| TrySkipWriteBarrier(target, &if_needs_write_barrier); |
| Goto(&if_no_write_barrier); |
| |
| BIND(&if_needs_write_barrier); |
| EmitCopyLoop(true); |
| |
| Goto(&done_copy_used); |
| BIND(&if_no_write_barrier); |
| } |
| |
| EmitCopyLoop(false); |
| Goto(&done_copy_used); |
| |
| BIND(&done_copy_used); |
| |
| // 3) Duplicate heap number boxes if needed. |
| // We need to go through the {object} again here and properly clone |
| // them. We use a second loop here to ensure that the GC (and heap |
| // verifier) always sees properly initialized objects, i.e. never |
| // hits undefined values in double fields. |
| Comment("FastCloneJSObject: cloning heap numbers"); |
| ConstructorBuiltinsAssembler(state()).CopyMutableHeapNumbersInObject( |
| target, target_payload_start, target_used_payload_end); |
| |
| return target; |
| } |
| |
| #include "src/codegen/undef-code-stub-assembler-macros.inc" |
| |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_CODEGEN_CODE_STUB_ASSEMBLER_INL_H_ |