| // Copyright 2016 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-constructor-gen.h" |
| |
| #include "src/ast/ast.h" |
| #include "src/builtins/builtins-call-gen.h" |
| #include "src/builtins/builtins-constructor.h" |
| #include "src/builtins/builtins-utils-gen.h" |
| #include "src/builtins/builtins.h" |
| #include "src/codegen/code-factory.h" |
| #include "src/codegen/code-stub-assembler.h" |
| #include "src/codegen/interface-descriptors.h" |
| #include "src/codegen/macro-assembler.h" |
| #include "src/logging/counters.h" |
| #include "src/objects/objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| void Builtins::Generate_ConstructVarargs(MacroAssembler* masm) { |
| Generate_CallOrConstructVarargs(masm, |
| BUILTIN_CODE(masm->isolate(), Construct)); |
| } |
| |
| void Builtins::Generate_ConstructForwardVarargs(MacroAssembler* masm) { |
| Generate_CallOrConstructForwardVarargs( |
| masm, CallOrConstructMode::kConstruct, |
| BUILTIN_CODE(masm->isolate(), Construct)); |
| } |
| |
| void Builtins::Generate_ConstructFunctionForwardVarargs(MacroAssembler* masm) { |
| Generate_CallOrConstructForwardVarargs( |
| masm, CallOrConstructMode::kConstruct, |
| BUILTIN_CODE(masm->isolate(), ConstructFunction)); |
| } |
| |
| TF_BUILTIN(Construct_WithFeedback, CallOrConstructBuiltinsAssembler) { |
| auto target = Parameter<Object>(Descriptor::kTarget); |
| auto new_target = Parameter<Object>(Descriptor::kNewTarget); |
| auto argc = UncheckedParameter<Int32T>(Descriptor::kActualArgumentsCount); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| auto maybe_feedback_vector = |
| Parameter<HeapObject>(Descriptor::kMaybeFeedbackVector); |
| auto slot = UncheckedParameter<Int32T>(Descriptor::kSlot); |
| |
| TVARIABLE(AllocationSite, allocation_site); |
| Label if_construct_generic(this), if_construct_array(this); |
| CollectConstructFeedback(context, target, new_target, maybe_feedback_vector, |
| Unsigned(ChangeInt32ToIntPtr(slot)), |
| &if_construct_generic, &if_construct_array, |
| &allocation_site); |
| |
| BIND(&if_construct_generic); |
| TailCallBuiltin(Builtins::kConstruct, context, target, new_target, argc); |
| |
| BIND(&if_construct_array); |
| TailCallBuiltin(Builtins::kArrayConstructorImpl, context, target, new_target, |
| argc, allocation_site.value()); |
| } |
| |
| TF_BUILTIN(ConstructWithArrayLike, CallOrConstructBuiltinsAssembler) { |
| auto target = Parameter<Object>(Descriptor::kTarget); |
| auto new_target = Parameter<Object>(Descriptor::kNewTarget); |
| auto arguments_list = Parameter<Object>(Descriptor::kArgumentsList); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| CallOrConstructWithArrayLike(target, new_target, arguments_list, context); |
| } |
| |
| TF_BUILTIN(ConstructWithArrayLike_WithFeedback, |
| CallOrConstructBuiltinsAssembler) { |
| auto target = Parameter<Object>(Descriptor::kTarget); |
| auto new_target = Parameter<Object>(Descriptor::kNewTarget); |
| auto arguments_list = Parameter<Object>(Descriptor::kArgumentsList); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| auto maybe_feedback_vector = |
| Parameter<HeapObject>(Descriptor::kMaybeFeedbackVector); |
| auto slot = UncheckedParameter<Int32T>(Descriptor::kSlot); |
| |
| TVARIABLE(AllocationSite, allocation_site); |
| Label if_construct_generic(this), if_construct_array(this); |
| CollectConstructFeedback(context, target, new_target, maybe_feedback_vector, |
| Unsigned(ChangeInt32ToIntPtr(slot)), |
| &if_construct_generic, &if_construct_array, |
| &allocation_site); |
| |
| BIND(&if_construct_array); |
| Goto(&if_construct_generic); // Not implemented. |
| |
| BIND(&if_construct_generic); |
| CallOrConstructWithArrayLike(target, new_target, arguments_list, context); |
| } |
| |
| TF_BUILTIN(ConstructWithSpread, CallOrConstructBuiltinsAssembler) { |
| auto target = Parameter<Object>(Descriptor::kTarget); |
| auto new_target = Parameter<Object>(Descriptor::kNewTarget); |
| auto spread = Parameter<Object>(Descriptor::kSpread); |
| auto args_count = |
| UncheckedParameter<Int32T>(Descriptor::kActualArgumentsCount); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| CallOrConstructWithSpread(target, new_target, spread, args_count, context); |
| } |
| |
| TF_BUILTIN(ConstructWithSpread_WithFeedback, CallOrConstructBuiltinsAssembler) { |
| auto target = Parameter<Object>(Descriptor::kTarget); |
| auto new_target = Parameter<Object>(Descriptor::kNewTarget); |
| auto spread = Parameter<Object>(Descriptor::kSpread); |
| auto args_count = |
| UncheckedParameter<Int32T>(Descriptor::kActualArgumentsCount); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| auto maybe_feedback_vector = |
| Parameter<HeapObject>(Descriptor::kMaybeFeedbackVector); |
| auto slot = UncheckedParameter<Int32T>(Descriptor::kSlot); |
| |
| TVARIABLE(AllocationSite, allocation_site); |
| Label if_construct_generic(this), if_construct_array(this); |
| CollectConstructFeedback(context, target, new_target, maybe_feedback_vector, |
| Unsigned(ChangeInt32ToIntPtr(slot)), |
| &if_construct_generic, &if_construct_array, |
| &allocation_site); |
| |
| BIND(&if_construct_array); |
| Goto(&if_construct_generic); // Not implemented. |
| |
| BIND(&if_construct_generic); |
| CallOrConstructWithSpread(target, new_target, spread, args_count, context); |
| } |
| |
| using Node = compiler::Node; |
| |
| TF_BUILTIN(FastNewClosure, ConstructorBuiltinsAssembler) { |
| auto shared_function_info = |
| Parameter<SharedFunctionInfo>(Descriptor::kSharedFunctionInfo); |
| auto feedback_cell = Parameter<FeedbackCell>(Descriptor::kFeedbackCell); |
| auto context = Parameter<Context>(Descriptor::kContext); |
| |
| IncrementCounter(isolate()->counters()->fast_new_closure_total(), 1); |
| |
| // Bump the closure counter encoded the {feedback_cell}s map. |
| { |
| const TNode<Map> feedback_cell_map = LoadMap(feedback_cell); |
| Label no_closures(this), one_closure(this), cell_done(this); |
| |
| GotoIf(IsNoClosuresCellMap(feedback_cell_map), &no_closures); |
| GotoIf(IsOneClosureCellMap(feedback_cell_map), &one_closure); |
| CSA_ASSERT(this, IsManyClosuresCellMap(feedback_cell_map), |
| feedback_cell_map, feedback_cell); |
| Goto(&cell_done); |
| |
| BIND(&no_closures); |
| StoreMapNoWriteBarrier(feedback_cell, RootIndex::kOneClosureCellMap); |
| Goto(&cell_done); |
| |
| BIND(&one_closure); |
| StoreMapNoWriteBarrier(feedback_cell, RootIndex::kManyClosuresCellMap); |
| Goto(&cell_done); |
| |
| BIND(&cell_done); |
| } |
| |
| // The calculation of |function_map_index| must be in sync with |
| // SharedFunctionInfo::function_map_index(). |
| TNode<Uint32T> flags = LoadObjectField<Uint32T>( |
| shared_function_info, SharedFunctionInfo::kFlagsOffset); |
| const TNode<IntPtrT> function_map_index = Signed(IntPtrAdd( |
| DecodeWordFromWord32<SharedFunctionInfo::FunctionMapIndexBits>(flags), |
| IntPtrConstant(Context::FIRST_FUNCTION_MAP_INDEX))); |
| CSA_ASSERT(this, UintPtrLessThanOrEqual( |
| function_map_index, |
| IntPtrConstant(Context::LAST_FUNCTION_MAP_INDEX))); |
| |
| // Get the function map in the current native context and set that |
| // as the map of the allocated object. |
| const TNode<NativeContext> native_context = LoadNativeContext(context); |
| const TNode<Map> function_map = |
| CAST(LoadContextElement(native_context, function_map_index)); |
| |
| // Create a new closure from the given function info in new space |
| TNode<IntPtrT> instance_size_in_bytes = |
| TimesTaggedSize(LoadMapInstanceSizeInWords(function_map)); |
| TNode<HeapObject> result = Allocate(instance_size_in_bytes); |
| StoreMapNoWriteBarrier(result, function_map); |
| InitializeJSObjectBodyNoSlackTracking(result, function_map, |
| instance_size_in_bytes, |
| JSFunction::kSizeWithoutPrototype); |
| |
| // Initialize the rest of the function. |
| StoreObjectFieldRoot(result, JSObject::kPropertiesOrHashOffset, |
| RootIndex::kEmptyFixedArray); |
| StoreObjectFieldRoot(result, JSObject::kElementsOffset, |
| RootIndex::kEmptyFixedArray); |
| { |
| // Set function prototype if necessary. |
| Label done(this), init_prototype(this); |
| Branch(IsFunctionWithPrototypeSlotMap(function_map), &init_prototype, |
| &done); |
| |
| BIND(&init_prototype); |
| StoreObjectFieldRoot(result, JSFunction::kPrototypeOrInitialMapOffset, |
| RootIndex::kTheHoleValue); |
| Goto(&done); |
| BIND(&done); |
| } |
| |
| STATIC_ASSERT(JSFunction::kSizeWithoutPrototype == 7 * kTaggedSize); |
| StoreObjectFieldNoWriteBarrier(result, JSFunction::kFeedbackCellOffset, |
| feedback_cell); |
| StoreObjectFieldNoWriteBarrier(result, JSFunction::kSharedFunctionInfoOffset, |
| shared_function_info); |
| StoreObjectFieldNoWriteBarrier(result, JSFunction::kContextOffset, context); |
| Handle<Code> lazy_builtin_handle = |
| isolate()->builtins()->builtin_handle(Builtins::kCompileLazy); |
| TNode<Code> lazy_builtin = HeapConstant(lazy_builtin_handle); |
| StoreObjectFieldNoWriteBarrier(result, JSFunction::kCodeOffset, lazy_builtin); |
| Return(result); |
| } |
| |
| TF_BUILTIN(FastNewObject, ConstructorBuiltinsAssembler) { |
| auto context = Parameter<Context>(Descriptor::kContext); |
| auto target = Parameter<JSFunction>(Descriptor::kTarget); |
| auto new_target = Parameter<JSReceiver>(Descriptor::kNewTarget); |
| |
| Label call_runtime(this); |
| |
| TNode<JSObject> result = |
| FastNewObject(context, target, new_target, &call_runtime); |
| Return(result); |
| |
| BIND(&call_runtime); |
| TailCallRuntime(Runtime::kNewObject, context, target, new_target); |
| } |
| |
| TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject( |
| TNode<Context> context, TNode<JSFunction> target, |
| TNode<JSReceiver> new_target) { |
| TVARIABLE(JSObject, var_obj); |
| Label call_runtime(this), end(this); |
| |
| var_obj = FastNewObject(context, target, new_target, &call_runtime); |
| Goto(&end); |
| |
| BIND(&call_runtime); |
| var_obj = CAST(CallRuntime(Runtime::kNewObject, context, target, new_target)); |
| Goto(&end); |
| |
| BIND(&end); |
| return var_obj.value(); |
| } |
| |
| TNode<JSObject> ConstructorBuiltinsAssembler::FastNewObject( |
| TNode<Context> context, TNode<JSFunction> target, |
| TNode<JSReceiver> new_target, Label* call_runtime) { |
| // Verify that the new target is a JSFunction. |
| Label end(this); |
| TNode<JSFunction> new_target_func = |
| HeapObjectToJSFunctionWithPrototypeSlot(new_target, call_runtime); |
| // Fast path. |
| |
| // Load the initial map and verify that it's in fact a map. |
| TNode<Object> initial_map_or_proto = |
| LoadJSFunctionPrototypeOrInitialMap(new_target_func); |
| GotoIf(TaggedIsSmi(initial_map_or_proto), call_runtime); |
| GotoIf(DoesntHaveInstanceType(CAST(initial_map_or_proto), MAP_TYPE), |
| call_runtime); |
| TNode<Map> initial_map = CAST(initial_map_or_proto); |
| |
| // Fall back to runtime if the target differs from the new target's |
| // initial map constructor. |
| TNode<Object> new_target_constructor = LoadObjectField( |
| initial_map, Map::kConstructorOrBackPointerOrNativeContextOffset); |
| GotoIf(TaggedNotEqual(target, new_target_constructor), call_runtime); |
| |
| TVARIABLE(HeapObject, properties); |
| |
| Label instantiate_map(this), allocate_properties(this); |
| GotoIf(IsDictionaryMap(initial_map), &allocate_properties); |
| { |
| properties = EmptyFixedArrayConstant(); |
| Goto(&instantiate_map); |
| } |
| BIND(&allocate_properties); |
| { |
| properties = AllocateNameDictionary(NameDictionary::kInitialCapacity); |
| Goto(&instantiate_map); |
| } |
| |
| BIND(&instantiate_map); |
| return AllocateJSObjectFromMap(initial_map, properties.value(), base::nullopt, |
| kNone, kWithSlackTracking); |
| } |
| |
| TNode<Context> ConstructorBuiltinsAssembler::FastNewFunctionContext( |
| TNode<ScopeInfo> scope_info, TNode<Uint32T> slots, TNode<Context> context, |
| ScopeType scope_type) { |
| TNode<IntPtrT> slots_intptr = Signed(ChangeUint32ToWord(slots)); |
| TNode<IntPtrT> size = ElementOffsetFromIndex(slots_intptr, PACKED_ELEMENTS, |
| Context::kTodoHeaderSize); |
| |
| // Create a new closure from the given function info in new space |
| TNode<Context> function_context = |
| UncheckedCast<Context>(AllocateInNewSpace(size)); |
| |
| TNode<NativeContext> native_context = LoadNativeContext(context); |
| Context::Field index; |
| switch (scope_type) { |
| case EVAL_SCOPE: |
| index = Context::EVAL_CONTEXT_MAP_INDEX; |
| break; |
| case FUNCTION_SCOPE: |
| index = Context::FUNCTION_CONTEXT_MAP_INDEX; |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| TNode<Map> map = CAST(LoadContextElement(native_context, index)); |
| // Set up the header. |
| StoreMapNoWriteBarrier(function_context, map); |
| TNode<IntPtrT> min_context_slots = IntPtrConstant(Context::MIN_CONTEXT_SLOTS); |
| // TODO(ishell): for now, length also includes MIN_CONTEXT_SLOTS. |
| TNode<IntPtrT> length = IntPtrAdd(slots_intptr, min_context_slots); |
| StoreObjectFieldNoWriteBarrier(function_context, Context::kLengthOffset, |
| SmiTag(length)); |
| StoreObjectFieldNoWriteBarrier(function_context, Context::kScopeInfoOffset, |
| scope_info); |
| StoreObjectFieldNoWriteBarrier(function_context, Context::kPreviousOffset, |
| context); |
| |
| // Initialize the varrest of the slots to undefined. |
| TNode<Oddball> undefined = UndefinedConstant(); |
| TNode<IntPtrT> start_offset = IntPtrConstant(Context::kTodoHeaderSize); |
| CodeStubAssembler::VariableList vars(0, zone()); |
| BuildFastLoop<IntPtrT>( |
| vars, start_offset, size, |
| [=](TNode<IntPtrT> offset) { |
| StoreObjectFieldNoWriteBarrier(function_context, offset, undefined); |
| }, |
| kTaggedSize, IndexAdvanceMode::kPost); |
| return function_context; |
| } |
| |
| TNode<JSRegExp> ConstructorBuiltinsAssembler::CreateRegExpLiteral( |
| TNode<HeapObject> maybe_feedback_vector, TNode<TaggedIndex> slot, |
| TNode<Object> pattern, TNode<Smi> flags, TNode<Context> context) { |
| Label call_runtime(this, Label::kDeferred), end(this); |
| |
| GotoIf(IsUndefined(maybe_feedback_vector), &call_runtime); |
| |
| TVARIABLE(JSRegExp, result); |
| TNode<FeedbackVector> feedback_vector = CAST(maybe_feedback_vector); |
| TNode<Object> literal_site = |
| CAST(LoadFeedbackVectorSlot(feedback_vector, slot)); |
| GotoIfNot(HasBoilerplate(literal_site), &call_runtime); |
| { |
| TNode<JSRegExp> boilerplate = CAST(literal_site); |
| int size = |
| JSRegExp::kHeaderSize + JSRegExp::kInObjectFieldCount * kTaggedSize; |
| TNode<HeapObject> copy = Allocate(size); |
| for (int offset = 0; offset < size; offset += kTaggedSize) { |
| TNode<Object> value = LoadObjectField(boilerplate, offset); |
| StoreObjectFieldNoWriteBarrier(copy, offset, value); |
| } |
| result = CAST(copy); |
| Goto(&end); |
| } |
| |
| BIND(&call_runtime); |
| { |
| result = CAST(CallRuntime(Runtime::kCreateRegExpLiteral, context, |
| maybe_feedback_vector, slot, pattern, flags)); |
| Goto(&end); |
| } |
| |
| BIND(&end); |
| return result.value(); |
| } |
| |
| TNode<JSArray> ConstructorBuiltinsAssembler::CreateShallowArrayLiteral( |
| TNode<FeedbackVector> feedback_vector, TNode<TaggedIndex> slot, |
| TNode<Context> context, AllocationSiteMode allocation_site_mode, |
| Label* call_runtime) { |
| Label zero_capacity(this), cow_elements(this), fast_elements(this), |
| return_result(this); |
| |
| TNode<Object> maybe_allocation_site = |
| CAST(LoadFeedbackVectorSlot(feedback_vector, slot)); |
| GotoIfNot(HasBoilerplate(maybe_allocation_site), call_runtime); |
| |
| TNode<AllocationSite> allocation_site = CAST(maybe_allocation_site); |
| TNode<JSArray> boilerplate = CAST(LoadBoilerplate(allocation_site)); |
| |
| if (allocation_site_mode == TRACK_ALLOCATION_SITE) { |
| return CloneFastJSArray(context, boilerplate, allocation_site); |
| } else { |
| return CloneFastJSArray(context, boilerplate); |
| } |
| } |
| |
| TNode<JSArray> ConstructorBuiltinsAssembler::CreateEmptyArrayLiteral( |
| TNode<FeedbackVector> feedback_vector, TNode<TaggedIndex> slot, |
| TNode<Context> context) { |
| // Array literals always have a valid AllocationSite to properly track |
| // elements transitions. |
| TNode<Object> maybe_allocation_site = |
| CAST(LoadFeedbackVectorSlot(feedback_vector, slot)); |
| TVARIABLE(AllocationSite, allocation_site); |
| |
| Label create_empty_array(this), |
| initialize_allocation_site(this, Label::kDeferred), done(this); |
| GotoIf(TaggedIsSmi(maybe_allocation_site), &initialize_allocation_site); |
| { |
| allocation_site = CAST(maybe_allocation_site); |
| Goto(&create_empty_array); |
| } |
| // TODO(cbruni): create the AllocationSite in CSA. |
| BIND(&initialize_allocation_site); |
| { |
| allocation_site = CreateAllocationSiteInFeedbackVector( |
| feedback_vector, |
| // TODO(v8:10047): pass slot as TaggedIndex here |
| Unsigned(TaggedIndexToIntPtr(slot))); |
| Goto(&create_empty_array); |
| } |
| |
| BIND(&create_empty_array); |
| TNode<Int32T> kind = LoadElementsKind(allocation_site.value()); |
| TNode<NativeContext> native_context = LoadNativeContext(context); |
| Comment("LoadJSArrayElementsMap"); |
| TNode<Map> array_map = LoadJSArrayElementsMap(kind, native_context); |
| TNode<IntPtrT> zero_intptr = IntPtrConstant(0); |
| TNode<Smi> zero = SmiConstant(0); |
| Comment("Allocate JSArray"); |
| TNode<JSArray> result = |
| AllocateJSArray(GetInitialFastElementsKind(), array_map, zero_intptr, |
| zero, allocation_site.value()); |
| |
| Goto(&done); |
| BIND(&done); |
| |
| return result; |
| } |
| |
| TNode<HeapObject> ConstructorBuiltinsAssembler::CreateShallowObjectLiteral( |
| TNode<FeedbackVector> feedback_vector, TNode<TaggedIndex> slot, |
| Label* call_runtime) { |
| TNode<Object> maybe_allocation_site = |
| CAST(LoadFeedbackVectorSlot(feedback_vector, slot)); |
| GotoIfNot(HasBoilerplate(maybe_allocation_site), call_runtime); |
| |
| TNode<AllocationSite> allocation_site = CAST(maybe_allocation_site); |
| TNode<JSObject> boilerplate = LoadBoilerplate(allocation_site); |
| TNode<Map> boilerplate_map = LoadMap(boilerplate); |
| CSA_ASSERT(this, IsJSObjectMap(boilerplate_map)); |
| |
| TVARIABLE(FixedArray, var_properties); |
| { |
| TNode<Uint32T> bit_field_3 = LoadMapBitField3(boilerplate_map); |
| GotoIf(IsSetWord32<Map::Bits3::IsDeprecatedBit>(bit_field_3), call_runtime); |
| // Directly copy over the property store for dict-mode boilerplates. |
| Label if_dictionary(this), if_fast(this), done(this); |
| Branch(IsSetWord32<Map::Bits3::IsDictionaryMapBit>(bit_field_3), |
| &if_dictionary, &if_fast); |
| BIND(&if_dictionary); |
| { |
| Comment("Copy dictionary properties"); |
| var_properties = CopyNameDictionary(CAST(LoadSlowProperties(boilerplate)), |
| call_runtime); |
| // Slow objects have no in-object properties. |
| Goto(&done); |
| } |
| BIND(&if_fast); |
| { |
| // TODO(cbruni): support copying out-of-object properties. |
| TNode<HeapObject> boilerplate_properties = |
| LoadFastProperties(boilerplate); |
| GotoIfNot(IsEmptyFixedArray(boilerplate_properties), call_runtime); |
| var_properties = EmptyFixedArrayConstant(); |
| Goto(&done); |
| } |
| BIND(&done); |
| } |
| |
| TVARIABLE(FixedArrayBase, var_elements); |
| { |
| // Copy the elements backing store, assuming that it's flat. |
| Label if_empty_fixed_array(this), if_copy_elements(this), done(this); |
| TNode<FixedArrayBase> boilerplate_elements = LoadElements(boilerplate); |
| Branch(IsEmptyFixedArray(boilerplate_elements), &if_empty_fixed_array, |
| &if_copy_elements); |
| |
| BIND(&if_empty_fixed_array); |
| var_elements = boilerplate_elements; |
| Goto(&done); |
| |
| BIND(&if_copy_elements); |
| CSA_ASSERT(this, Word32BinaryNot( |
| IsFixedCOWArrayMap(LoadMap(boilerplate_elements)))); |
| ExtractFixedArrayFlags flags; |
| flags |= ExtractFixedArrayFlag::kAllFixedArrays; |
| flags |= ExtractFixedArrayFlag::kNewSpaceAllocationOnly; |
| flags |= ExtractFixedArrayFlag::kDontCopyCOW; |
| var_elements = CloneFixedArray(boilerplate_elements, flags); |
| Goto(&done); |
| BIND(&done); |
| } |
| |
| // Ensure new-space allocation for a fresh JSObject so we can skip write |
| // barriers when copying all object fields. |
| STATIC_ASSERT(JSObject::kMaxInstanceSize < kMaxRegularHeapObjectSize); |
| TNode<IntPtrT> instance_size = |
| TimesTaggedSize(LoadMapInstanceSizeInWords(boilerplate_map)); |
| TNode<IntPtrT> allocation_size = instance_size; |
| bool needs_allocation_memento = FLAG_allocation_site_pretenuring; |
| if (needs_allocation_memento) { |
| // Prepare for inner-allocating the AllocationMemento. |
| allocation_size = |
| IntPtrAdd(instance_size, IntPtrConstant(AllocationMemento::kSize)); |
| } |
| |
| TNode<HeapObject> copy = |
| UncheckedCast<HeapObject>(AllocateInNewSpace(allocation_size)); |
| { |
| Comment("Initialize Literal Copy"); |
| // Initialize Object fields. |
| StoreMapNoWriteBarrier(copy, boilerplate_map); |
| StoreObjectFieldNoWriteBarrier(copy, JSObject::kPropertiesOrHashOffset, |
| var_properties.value()); |
| StoreObjectFieldNoWriteBarrier(copy, JSObject::kElementsOffset, |
| var_elements.value()); |
| } |
| |
| // Initialize the AllocationMemento before potential GCs due to heap number |
| // allocation when copying the in-object properties. |
| if (needs_allocation_memento) { |
| InitializeAllocationMemento(copy, instance_size, allocation_site); |
| } |
| |
| { |
| // Copy over in-object properties. |
| Label continue_with_write_barrier(this), done_init(this); |
| TVARIABLE(IntPtrT, offset, IntPtrConstant(JSObject::kHeaderSize)); |
| // Heap numbers are only mutable on 32-bit platforms. |
| bool may_use_mutable_heap_numbers = !FLAG_unbox_double_fields; |
| { |
| Comment("Copy in-object properties fast"); |
| Label continue_fast(this, &offset); |
| Branch(IntPtrEqual(offset.value(), instance_size), &done_init, |
| &continue_fast); |
| BIND(&continue_fast); |
| if (may_use_mutable_heap_numbers) { |
| TNode<Object> field = LoadObjectField(boilerplate, offset.value()); |
| Label store_field(this); |
| GotoIf(TaggedIsSmi(field), &store_field); |
| // TODO(leszeks): Read the field descriptor to decide if this heap |
| // number is mutable or not. |
| GotoIf(IsHeapNumber(CAST(field)), &continue_with_write_barrier); |
| Goto(&store_field); |
| BIND(&store_field); |
| StoreObjectFieldNoWriteBarrier(copy, offset.value(), field); |
| } else { |
| // Copy fields as raw data. |
| TNode<TaggedT> field = |
| LoadObjectField<TaggedT>(boilerplate, offset.value()); |
| StoreObjectFieldNoWriteBarrier(copy, offset.value(), field); |
| } |
| offset = IntPtrAdd(offset.value(), IntPtrConstant(kTaggedSize)); |
| Branch(WordNotEqual(offset.value(), instance_size), &continue_fast, |
| &done_init); |
| } |
| |
| if (!may_use_mutable_heap_numbers) { |
| BIND(&done_init); |
| return copy; |
| } |
| // Continue initializing the literal after seeing the first sub-object |
| // potentially causing allocation. In this case we prepare the new literal |
| // by copying all pending fields over from the boilerplate and emit full |
| // write barriers from here on. |
| BIND(&continue_with_write_barrier); |
| { |
| Comment("Copy in-object properties slow"); |
| BuildFastLoop<IntPtrT>( |
| offset.value(), instance_size, |
| [=](TNode<IntPtrT> offset) { |
| // TODO(ishell): value decompression is not necessary here. |
| TNode<Object> field = LoadObjectField(boilerplate, offset); |
| StoreObjectFieldNoWriteBarrier(copy, offset, field); |
| }, |
| kTaggedSize, IndexAdvanceMode::kPost); |
| CopyMutableHeapNumbersInObject(copy, offset.value(), instance_size); |
| Goto(&done_init); |
| } |
| BIND(&done_init); |
| } |
| return copy; |
| } |
| |
| // Used by the CreateEmptyObjectLiteral bytecode and the Object constructor. |
| TNode<JSObject> ConstructorBuiltinsAssembler::CreateEmptyObjectLiteral( |
| TNode<Context> context) { |
| TNode<NativeContext> native_context = LoadNativeContext(context); |
| TNode<Map> map = LoadObjectFunctionInitialMap(native_context); |
| // Ensure that slack tracking is disabled for the map. |
| STATIC_ASSERT(Map::kNoSlackTracking == 0); |
| CSA_ASSERT(this, IsClearWord32<Map::Bits3::ConstructionCounterBits>( |
| LoadMapBitField3(map))); |
| TNode<FixedArray> empty_fixed_array = EmptyFixedArrayConstant(); |
| TNode<JSObject> result = |
| AllocateJSObjectFromMap(map, empty_fixed_array, empty_fixed_array); |
| return result; |
| } |
| |
| void ConstructorBuiltinsAssembler::CopyMutableHeapNumbersInObject( |
| TNode<HeapObject> copy, TNode<IntPtrT> start_offset, |
| TNode<IntPtrT> end_offset) { |
| // Iterate over all object properties of a freshly copied object and |
| // duplicate mutable heap numbers. |
| if (FLAG_unbox_double_fields) return; |
| Comment("Copy mutable HeapNumber values"); |
| BuildFastLoop<IntPtrT>( |
| start_offset, end_offset, |
| [=](TNode<IntPtrT> offset) { |
| TNode<Object> field = LoadObjectField(copy, offset); |
| Label copy_heap_number(this, Label::kDeferred), continue_loop(this); |
| // We only have to clone complex field values. |
| GotoIf(TaggedIsSmi(field), &continue_loop); |
| // TODO(leszeks): Read the field descriptor to decide if this heap |
| // number is mutable or not. |
| Branch(IsHeapNumber(CAST(field)), ©_heap_number, &continue_loop); |
| BIND(©_heap_number); |
| { |
| TNode<Float64T> double_value = LoadHeapNumberValue(CAST(field)); |
| TNode<HeapNumber> heap_number = |
| AllocateHeapNumberWithValue(double_value); |
| StoreObjectField(copy, offset, heap_number); |
| Goto(&continue_loop); |
| } |
| BIND(&continue_loop); |
| }, |
| kTaggedSize, IndexAdvanceMode::kPost); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |