| // 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/ic/accessor-assembler.h" | |
| #include "src/code-factory.h" | |
| #include "src/code-stubs.h" | |
| #include "src/counters.h" | |
| #include "src/ic/handler-configuration.h" | |
| #include "src/ic/ic.h" | |
| #include "src/ic/stub-cache.h" | |
| #include "src/objects-inl.h" | |
| #include "src/objects/module.h" | |
| namespace v8 { | |
| namespace internal { | |
| using compiler::CodeAssemblerState; | |
| using compiler::Node; | |
| template <typename T> | |
| using TNode = compiler::TNode<T>; | |
| template <typename T> | |
| using SloppyTNode = compiler::SloppyTNode<T>; | |
| //////////////////// Private helpers. | |
| // Loads dataX field from the DataHandler object. | |
| TNode<MaybeObject> AccessorAssembler::LoadHandlerDataField( | |
| SloppyTNode<DataHandler> handler, int data_index) { | |
| #ifdef DEBUG | |
| TNode<Map> handler_map = LoadMap(handler); | |
| TNode<Int32T> instance_type = LoadMapInstanceType(handler_map); | |
| #endif | |
| CSA_ASSERT(this, | |
| Word32Or(InstanceTypeEqual(instance_type, LOAD_HANDLER_TYPE), | |
| InstanceTypeEqual(instance_type, STORE_HANDLER_TYPE))); | |
| int offset = 0; | |
| int minimum_size = 0; | |
| switch (data_index) { | |
| case 1: | |
| offset = DataHandler::kData1Offset; | |
| minimum_size = DataHandler::kSizeWithData1; | |
| break; | |
| case 2: | |
| offset = DataHandler::kData2Offset; | |
| minimum_size = DataHandler::kSizeWithData2; | |
| break; | |
| case 3: | |
| offset = DataHandler::kData3Offset; | |
| minimum_size = DataHandler::kSizeWithData3; | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| USE(minimum_size); | |
| CSA_ASSERT(this, UintPtrGreaterThanOrEqual( | |
| LoadMapInstanceSizeInWords(handler_map), | |
| IntPtrConstant(minimum_size / kPointerSize))); | |
| return LoadMaybeWeakObjectField(handler, offset); | |
| } | |
| TNode<MaybeObject> AccessorAssembler::TryMonomorphicCase( | |
| Node* slot, Node* vector, Node* receiver_map, Label* if_handler, | |
| TVariable<MaybeObject>* var_handler, Label* if_miss) { | |
| Comment("TryMonomorphicCase"); | |
| DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep()); | |
| // TODO(ishell): add helper class that hides offset computations for a series | |
| // of loads. | |
| CSA_ASSERT(this, IsFeedbackVector(vector), vector); | |
| int32_t header_size = FeedbackVector::kFeedbackSlotsOffset - kHeapObjectTag; | |
| // Adding |header_size| with a separate IntPtrAdd rather than passing it | |
| // into ElementOffsetFromIndex() allows it to be folded into a single | |
| // [base, index, offset] indirect memory access on x64. | |
| Node* offset = ElementOffsetFromIndex(slot, HOLEY_ELEMENTS, SMI_PARAMETERS); | |
| TNode<MaybeObject> feedback = ReinterpretCast<MaybeObject>( | |
| Load(MachineType::AnyTagged(), vector, | |
| IntPtrAdd(offset, IntPtrConstant(header_size)))); | |
| // Try to quickly handle the monomorphic case without knowing for sure | |
| // if we have a weak reference in feedback. | |
| GotoIf(IsNotWeakReferenceTo(feedback, CAST(receiver_map)), if_miss); | |
| TNode<MaybeObject> handler = UncheckedCast<MaybeObject>( | |
| Load(MachineType::AnyTagged(), vector, | |
| IntPtrAdd(offset, IntPtrConstant(header_size + kPointerSize)))); | |
| *var_handler = handler; | |
| Goto(if_handler); | |
| return feedback; | |
| } | |
| void AccessorAssembler::HandlePolymorphicCase( | |
| Node* receiver_map, TNode<WeakFixedArray> feedback, Label* if_handler, | |
| TVariable<MaybeObject>* var_handler, Label* if_miss, | |
| int min_feedback_capacity) { | |
| Comment("HandlePolymorphicCase"); | |
| DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep()); | |
| // Deferred so the unrolled case can omit frame construction in bytecode | |
| // handler. | |
| Label loop(this, Label::kDeferred); | |
| // Iterate {feedback} array. | |
| const int kEntrySize = 2; | |
| // Loading feedback's length is delayed until we need it when looking past | |
| // the first {min_feedback_capacity} (map, handler) pairs. | |
| Node* length = nullptr; | |
| CSA_ASSERT(this, SmiGreaterThanOrEqual( | |
| LoadWeakFixedArrayLength(feedback), | |
| SmiConstant(min_feedback_capacity * kEntrySize))); | |
| const int kUnrolledIterations = IC::kMaxPolymorphicMapCount; | |
| for (int i = 0; i < kUnrolledIterations; i++) { | |
| int map_index = i * kEntrySize; | |
| int handler_index = i * kEntrySize + 1; | |
| if (i >= min_feedback_capacity) { | |
| if (length == nullptr) length = LoadWeakFixedArrayLength(feedback); | |
| GotoIf(SmiGreaterThanOrEqual(SmiConstant(handler_index), CAST(length)), | |
| if_miss); | |
| } | |
| Label next_entry(this); | |
| TNode<MaybeObject> maybe_cached_map = | |
| LoadWeakFixedArrayElement(feedback, map_index); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_cached_map)); | |
| GotoIf(IsNotWeakReferenceTo(maybe_cached_map, CAST(receiver_map)), | |
| &next_entry); | |
| // Found, now call handler. | |
| TNode<MaybeObject> handler = | |
| LoadWeakFixedArrayElement(feedback, handler_index); | |
| *var_handler = handler; | |
| Goto(if_handler); | |
| BIND(&next_entry); | |
| } | |
| Goto(&loop); | |
| // Loop from {kUnrolledIterations}*kEntrySize to {length}. | |
| BIND(&loop); | |
| Node* start_index = IntPtrConstant(kUnrolledIterations * kEntrySize); | |
| Node* end_index = LoadAndUntagWeakFixedArrayLength(feedback); | |
| BuildFastLoop( | |
| start_index, end_index, | |
| [this, receiver_map, feedback, if_handler, var_handler](Node* index) { | |
| Label next_entry(this); | |
| TNode<MaybeObject> maybe_cached_map = | |
| LoadWeakFixedArrayElement(feedback, index); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_cached_map)); | |
| GotoIf(IsNotWeakReferenceTo(maybe_cached_map, CAST(receiver_map)), | |
| &next_entry); | |
| // Found, now call handler. | |
| TNode<MaybeObject> handler = | |
| LoadWeakFixedArrayElement(feedback, index, kPointerSize); | |
| *var_handler = handler; | |
| Goto(if_handler); | |
| BIND(&next_entry); | |
| }, | |
| kEntrySize, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); | |
| // The loop falls through if no handler was found. | |
| Goto(if_miss); | |
| } | |
| void AccessorAssembler::HandleLoadICHandlerCase( | |
| const LoadICParameters* p, TNode<Object> handler, Label* miss, | |
| ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent, | |
| ElementSupport support_elements) { | |
| Comment("have_handler"); | |
| VARIABLE(var_holder, MachineRepresentation::kTagged, p->holder); | |
| VARIABLE(var_smi_handler, MachineRepresentation::kTagged, handler); | |
| Variable* vars[] = {&var_holder, &var_smi_handler}; | |
| Label if_smi_handler(this, 2, vars); | |
| Label try_proto_handler(this, Label::kDeferred), | |
| call_handler(this, Label::kDeferred); | |
| Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler); | |
| // |handler| is a Smi, encoding what to do. See SmiHandler methods | |
| // for the encoding format. | |
| BIND(&if_smi_handler); | |
| { | |
| HandleLoadICSmiHandlerCase(p, var_holder.value(), var_smi_handler.value(), | |
| handler, miss, exit_point, on_nonexistent, | |
| support_elements); | |
| } | |
| BIND(&try_proto_handler); | |
| { | |
| GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler); | |
| HandleLoadICProtoHandler(p, handler, &var_holder, &var_smi_handler, | |
| &if_smi_handler, miss, exit_point, ic_mode); | |
| } | |
| BIND(&call_handler); | |
| { | |
| exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, handler, p->context, | |
| p->receiver, p->name, p->slot, p->vector); | |
| } | |
| } | |
| void AccessorAssembler::HandleLoadCallbackProperty(const LoadICParameters* p, | |
| TNode<JSObject> holder, | |
| TNode<WordT> handler_word, | |
| ExitPoint* exit_point) { | |
| Comment("native_data_property_load"); | |
| Node* descriptor = DecodeWord<LoadHandler::DescriptorBits>(handler_word); | |
| Label runtime(this, Label::kDeferred); | |
| Callable callable = CodeFactory::ApiGetter(isolate()); | |
| TNode<AccessorInfo> accessor_info = | |
| CAST(LoadDescriptorValue(LoadMap(holder), descriptor)); | |
| GotoIf(IsRuntimeCallStatsEnabled(), &runtime); | |
| exit_point->ReturnCallStub(callable, p->context, p->receiver, holder, | |
| accessor_info); | |
| BIND(&runtime); | |
| exit_point->ReturnCallRuntime(Runtime::kLoadCallbackProperty, p->context, | |
| p->receiver, holder, accessor_info, p->name); | |
| } | |
| void AccessorAssembler::HandleLoadAccessor( | |
| const LoadICParameters* p, TNode<CallHandlerInfo> call_handler_info, | |
| TNode<WordT> handler_word, TNode<DataHandler> handler, | |
| TNode<IntPtrT> handler_kind, ExitPoint* exit_point) { | |
| Comment("api_getter"); | |
| Label runtime(this, Label::kDeferred); | |
| // Context is stored either in data2 or data3 field depending on whether | |
| // the access check is enabled for this handler or not. | |
| TNode<MaybeObject> maybe_context = Select<MaybeObject>( | |
| IsSetWord<LoadHandler::DoAccessCheckOnReceiverBits>(handler_word), | |
| [=] { return LoadHandlerDataField(handler, 3); }, | |
| [=] { return LoadHandlerDataField(handler, 2); }); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_context)); | |
| CSA_CHECK(this, IsNotClearedWeakHeapObject(maybe_context)); | |
| TNode<Object> context = ToWeakHeapObject(maybe_context); | |
| GotoIf(IsRuntimeCallStatsEnabled(), &runtime); | |
| { | |
| TNode<Foreign> foreign = CAST( | |
| LoadObjectField(call_handler_info, CallHandlerInfo::kJsCallbackOffset)); | |
| TNode<WordT> callback = TNode<WordT>::UncheckedCast(LoadObjectField( | |
| foreign, Foreign::kForeignAddressOffset, MachineType::Pointer())); | |
| TNode<Object> data = | |
| LoadObjectField(call_handler_info, CallHandlerInfo::kDataOffset); | |
| VARIABLE(api_holder, MachineRepresentation::kTagged, p->receiver); | |
| Label load(this); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)), | |
| &load); | |
| CSA_ASSERT( | |
| this, | |
| WordEqual(handler_kind, | |
| IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype))); | |
| api_holder.Bind(LoadMapPrototype(LoadMap(p->receiver))); | |
| Goto(&load); | |
| BIND(&load); | |
| Callable callable = CodeFactory::CallApiCallback(isolate(), 0); | |
| exit_point->Return(CallStub(callable, nullptr, context, data, | |
| api_holder.value(), callback, p->receiver)); | |
| } | |
| BIND(&runtime); | |
| exit_point->ReturnCallRuntime(Runtime::kLoadAccessorProperty, context, | |
| p->receiver, SmiTag(handler_kind), | |
| call_handler_info); | |
| } | |
| void AccessorAssembler::HandleLoadField(Node* holder, Node* handler_word, | |
| Variable* var_double_value, | |
| Label* rebox_double, | |
| ExitPoint* exit_point) { | |
| Comment("field_load"); | |
| Node* index = DecodeWord<LoadHandler::FieldIndexBits>(handler_word); | |
| Node* offset = IntPtrMul(index, IntPtrConstant(kPointerSize)); | |
| Label inobject(this), out_of_object(this); | |
| Branch(IsSetWord<LoadHandler::IsInobjectBits>(handler_word), &inobject, | |
| &out_of_object); | |
| BIND(&inobject); | |
| { | |
| Label is_double(this); | |
| GotoIf(IsSetWord<LoadHandler::IsDoubleBits>(handler_word), &is_double); | |
| exit_point->Return(LoadObjectField(holder, offset)); | |
| BIND(&is_double); | |
| if (FLAG_unbox_double_fields) { | |
| var_double_value->Bind( | |
| LoadObjectField(holder, offset, MachineType::Float64())); | |
| } else { | |
| Node* mutable_heap_number = LoadObjectField(holder, offset); | |
| var_double_value->Bind(LoadHeapNumberValue(mutable_heap_number)); | |
| } | |
| Goto(rebox_double); | |
| } | |
| BIND(&out_of_object); | |
| { | |
| Label is_double(this); | |
| Node* properties = LoadFastProperties(holder); | |
| Node* value = LoadObjectField(properties, offset); | |
| GotoIf(IsSetWord<LoadHandler::IsDoubleBits>(handler_word), &is_double); | |
| exit_point->Return(value); | |
| BIND(&is_double); | |
| var_double_value->Bind(LoadHeapNumberValue(value)); | |
| Goto(rebox_double); | |
| } | |
| } | |
| TNode<Object> AccessorAssembler::LoadDescriptorValue(TNode<Map> map, | |
| Node* descriptor) { | |
| return CAST(LoadDescriptorValueOrFieldType(map, descriptor)); | |
| } | |
| TNode<MaybeObject> AccessorAssembler::LoadDescriptorValueOrFieldType( | |
| TNode<Map> map, SloppyTNode<IntPtrT> descriptor) { | |
| TNode<DescriptorArray> descriptors = LoadMapDescriptors(map); | |
| TNode<IntPtrT> scaled_descriptor = | |
| IntPtrMul(descriptor, IntPtrConstant(DescriptorArray::kEntrySize)); | |
| TNode<IntPtrT> value_index = IntPtrAdd( | |
| scaled_descriptor, IntPtrConstant(DescriptorArray::kFirstIndex + | |
| DescriptorArray::kEntryValueIndex)); | |
| CSA_ASSERT(this, UintPtrLessThan(descriptor, LoadAndUntagWeakFixedArrayLength( | |
| descriptors))); | |
| return LoadWeakFixedArrayElement(descriptors, value_index); | |
| } | |
| void AccessorAssembler::HandleLoadICSmiHandlerCase( | |
| const LoadICParameters* p, Node* holder, SloppyTNode<Smi> smi_handler, | |
| SloppyTNode<Object> handler, Label* miss, ExitPoint* exit_point, | |
| OnNonExistent on_nonexistent, ElementSupport support_elements) { | |
| VARIABLE(var_double_value, MachineRepresentation::kFloat64); | |
| Label rebox_double(this, &var_double_value); | |
| TNode<WordT> handler_word = SmiUntag(smi_handler); | |
| TNode<IntPtrT> handler_kind = | |
| Signed(DecodeWord<LoadHandler::KindBits>(handler_word)); | |
| if (support_elements == kSupportElements) { | |
| Label if_element(this), if_indexed_string(this), if_property(this); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kElement)), | |
| &if_element); | |
| Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)), | |
| &if_indexed_string, &if_property); | |
| BIND(&if_element); | |
| Comment("element_load"); | |
| Node* intptr_index = TryToIntptr(p->name, miss); | |
| Node* elements = LoadElements(holder); | |
| Node* is_jsarray_condition = | |
| IsSetWord<LoadHandler::IsJsArrayBits>(handler_word); | |
| Node* elements_kind = | |
| DecodeWord32FromWord<LoadHandler::ElementsKindBits>(handler_word); | |
| Label if_hole(this), unimplemented_elements_kind(this), | |
| if_oob(this, Label::kDeferred); | |
| EmitElementLoad(holder, elements, elements_kind, intptr_index, | |
| is_jsarray_condition, &if_hole, &rebox_double, | |
| &var_double_value, &unimplemented_elements_kind, &if_oob, | |
| miss, exit_point); | |
| BIND(&unimplemented_elements_kind); | |
| { | |
| // Smi handlers should only be installed for supported elements kinds. | |
| // Crash if we get here. | |
| DebugBreak(); | |
| Goto(miss); | |
| } | |
| BIND(&if_oob); | |
| { | |
| Comment("out of bounds elements access"); | |
| Label return_undefined(this); | |
| // Check if we're allowed to handle OOB accesses. | |
| Node* allow_out_of_bounds = | |
| IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word); | |
| GotoIfNot(allow_out_of_bounds, miss); | |
| // Negative indices aren't valid array indices (according to | |
| // the ECMAScript specification), and are stored as properties | |
| // in V8, not elements. So we cannot handle them here, except | |
| // in case of typed arrays, where integer indexed properties | |
| // aren't looked up in the prototype chain. | |
| GotoIf(IsJSTypedArray(holder), &return_undefined); | |
| GotoIf(IntPtrLessThan(intptr_index, IntPtrConstant(0)), miss); | |
| // For all other receivers we need to check that the prototype chain | |
| // doesn't contain any elements. | |
| BranchIfPrototypesHaveNoElements(LoadMap(holder), &return_undefined, | |
| miss); | |
| BIND(&return_undefined); | |
| exit_point->Return(UndefinedConstant()); | |
| } | |
| BIND(&if_hole); | |
| { | |
| Comment("convert hole"); | |
| GotoIfNot(IsSetWord<LoadHandler::ConvertHoleBits>(handler_word), miss); | |
| GotoIf(IsNoElementsProtectorCellInvalid(), miss); | |
| exit_point->Return(UndefinedConstant()); | |
| } | |
| BIND(&if_indexed_string); | |
| { | |
| Label if_oob(this, Label::kDeferred); | |
| Comment("indexed string"); | |
| Node* intptr_index = TryToIntptr(p->name, miss); | |
| Node* length = LoadStringLengthAsWord(holder); | |
| GotoIf(UintPtrGreaterThanOrEqual(intptr_index, length), &if_oob); | |
| TNode<Int32T> code = StringCharCodeAt(holder, intptr_index); | |
| TNode<String> result = StringFromSingleCharCode(code); | |
| Return(result); | |
| BIND(&if_oob); | |
| Node* allow_out_of_bounds = | |
| IsSetWord<LoadHandler::AllowOutOfBoundsBits>(handler_word); | |
| GotoIfNot(allow_out_of_bounds, miss); | |
| GotoIf(IsNoElementsProtectorCellInvalid(), miss); | |
| Return(UndefinedConstant()); | |
| } | |
| BIND(&if_property); | |
| Comment("property_load"); | |
| } | |
| Label constant(this), field(this), normal(this, Label::kDeferred), | |
| interceptor(this, Label::kDeferred), nonexistent(this), | |
| accessor(this, Label::kDeferred), global(this, Label::kDeferred), | |
| module_export(this, Label::kDeferred), proxy(this, Label::kDeferred), | |
| native_data_property(this), api_getter(this); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &field); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstant)), | |
| &constant); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)), | |
| &nonexistent); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)), | |
| &normal); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)), | |
| &accessor); | |
| GotoIf( | |
| WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)), | |
| &native_data_property); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)), | |
| &api_getter); | |
| GotoIf(WordEqual(handler_kind, | |
| IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)), | |
| &api_getter); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)), | |
| &global); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), &proxy); | |
| Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kModuleExport)), | |
| &module_export, &interceptor); | |
| BIND(&field); | |
| HandleLoadField(holder, handler_word, &var_double_value, &rebox_double, | |
| exit_point); | |
| BIND(&nonexistent); | |
| // This is a handler for a load of a non-existent value. | |
| if (on_nonexistent == OnNonExistent::kThrowReferenceError) { | |
| exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context, | |
| p->name); | |
| } else { | |
| DCHECK_EQ(OnNonExistent::kReturnUndefined, on_nonexistent); | |
| exit_point->Return(UndefinedConstant()); | |
| } | |
| BIND(&constant); | |
| { | |
| Comment("constant_load"); | |
| Node* descriptor = DecodeWord<LoadHandler::DescriptorBits>(handler_word); | |
| Node* value = LoadDescriptorValue(LoadMap(holder), descriptor); | |
| exit_point->Return(value); | |
| } | |
| BIND(&normal); | |
| { | |
| Comment("load_normal"); | |
| TNode<NameDictionary> properties = CAST(LoadSlowProperties(holder)); | |
| TVARIABLE(IntPtrT, var_name_index); | |
| Label found(this, &var_name_index); | |
| NameDictionaryLookup<NameDictionary>(properties, CAST(p->name), &found, | |
| &var_name_index, miss); | |
| BIND(&found); | |
| { | |
| VARIABLE(var_details, MachineRepresentation::kWord32); | |
| VARIABLE(var_value, MachineRepresentation::kTagged); | |
| LoadPropertyFromNameDictionary(properties, var_name_index.value(), | |
| &var_details, &var_value); | |
| Node* value = CallGetterIfAccessor(var_value.value(), var_details.value(), | |
| p->context, p->receiver, miss); | |
| exit_point->Return(value); | |
| } | |
| } | |
| BIND(&accessor); | |
| { | |
| Comment("accessor_load"); | |
| Node* descriptor = DecodeWord<LoadHandler::DescriptorBits>(handler_word); | |
| Node* accessor_pair = LoadDescriptorValue(LoadMap(holder), descriptor); | |
| CSA_ASSERT(this, IsAccessorPair(accessor_pair)); | |
| Node* getter = LoadObjectField(accessor_pair, AccessorPair::kGetterOffset); | |
| CSA_ASSERT(this, Word32BinaryNot(IsTheHole(getter))); | |
| Callable callable = CodeFactory::Call(isolate()); | |
| exit_point->Return(CallJS(callable, p->context, getter, p->receiver)); | |
| } | |
| BIND(&native_data_property); | |
| HandleLoadCallbackProperty(p, CAST(holder), handler_word, exit_point); | |
| BIND(&api_getter); | |
| HandleLoadAccessor(p, CAST(holder), handler_word, CAST(handler), handler_kind, | |
| exit_point); | |
| BIND(&proxy); | |
| { | |
| VARIABLE(var_index, MachineType::PointerRepresentation()); | |
| VARIABLE(var_unique, MachineRepresentation::kTagged); | |
| Label if_index(this), if_unique_name(this), | |
| to_name_failed(this, Label::kDeferred); | |
| if (support_elements == kSupportElements) { | |
| DCHECK_NE(on_nonexistent, OnNonExistent::kThrowReferenceError); | |
| TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique, | |
| &to_name_failed); | |
| BIND(&if_unique_name); | |
| exit_point->ReturnCallStub( | |
| Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty), | |
| p->context, holder, var_unique.value(), p->receiver, | |
| SmiConstant(on_nonexistent)); | |
| BIND(&if_index); | |
| // TODO(mslekova): introduce TryToName that doesn't try to compute | |
| // the intptr index value | |
| Goto(&to_name_failed); | |
| BIND(&to_name_failed); | |
| exit_point->ReturnCallRuntime(Runtime::kGetPropertyWithReceiver, | |
| p->context, holder, p->name, p->receiver, | |
| SmiConstant(on_nonexistent)); | |
| } else { | |
| exit_point->ReturnCallStub( | |
| Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty), | |
| p->context, holder, p->name, p->receiver, | |
| SmiConstant(on_nonexistent)); | |
| } | |
| } | |
| BIND(&global); | |
| { | |
| CSA_ASSERT(this, IsPropertyCell(holder)); | |
| // Ensure the property cell doesn't contain the hole. | |
| Node* value = LoadObjectField(holder, PropertyCell::kValueOffset); | |
| Node* details = | |
| LoadAndUntagToWord32ObjectField(holder, PropertyCell::kDetailsOffset); | |
| GotoIf(IsTheHole(value), miss); | |
| exit_point->Return( | |
| CallGetterIfAccessor(value, details, p->context, p->receiver, miss)); | |
| } | |
| BIND(&interceptor); | |
| { | |
| Comment("load_interceptor"); | |
| exit_point->ReturnCallRuntime(Runtime::kLoadPropertyWithInterceptor, | |
| p->context, p->name, p->receiver, holder, | |
| p->slot, p->vector); | |
| } | |
| BIND(&module_export); | |
| { | |
| Comment("module export"); | |
| Node* index = DecodeWord<LoadHandler::ExportsIndexBits>(handler_word); | |
| Node* module = | |
| LoadObjectField(p->receiver, JSModuleNamespace::kModuleOffset, | |
| MachineType::TaggedPointer()); | |
| TNode<ObjectHashTable> exports = CAST(LoadObjectField( | |
| module, Module::kExportsOffset, MachineType::TaggedPointer())); | |
| Node* cell = LoadFixedArrayElement(exports, index); | |
| // The handler is only installed for exports that exist. | |
| CSA_ASSERT(this, IsCell(cell)); | |
| Node* value = LoadCellValue(cell); | |
| Label is_the_hole(this, Label::kDeferred); | |
| GotoIf(IsTheHole(value), &is_the_hole); | |
| exit_point->Return(value); | |
| BIND(&is_the_hole); | |
| { | |
| Node* message = SmiConstant(MessageTemplate::kNotDefined); | |
| exit_point->ReturnCallRuntime(Runtime::kThrowReferenceError, p->context, | |
| message, p->name); | |
| } | |
| } | |
| BIND(&rebox_double); | |
| exit_point->Return(AllocateHeapNumberWithValue(var_double_value.value())); | |
| } | |
| // Performs actions common to both load and store handlers: | |
| // 1. Checks prototype validity cell. | |
| // 2. If |on_code_handler| is provided, then it checks if the sub handler is | |
| // a smi or code and if it's a code then it calls |on_code_handler| to | |
| // generate a code that handles Code handlers. | |
| // If |on_code_handler| is not provided, then only smi sub handler are | |
| // expected. | |
| // 3. Does access check on receiver if ICHandler::DoAccessCheckOnReceiverBits | |
| // bit is set in the smi handler. | |
| // 4. Does dictionary lookup on receiver if ICHandler::LookupOnReceiverBits bit | |
| // is set in the smi handler. If |on_found_on_receiver| is provided then | |
| // it calls it to generate a code that handles the "found on receiver case" | |
| // or just misses if the |on_found_on_receiver| is not provided. | |
| // 5. Falls through in a case of a smi handler which is returned from this | |
| // function (tagged!). | |
| // TODO(ishell): Remove templatezation once we move common bits from | |
| // Load/StoreHandler to the base class. | |
| template <typename ICHandler, typename ICParameters> | |
| Node* AccessorAssembler::HandleProtoHandler( | |
| const ICParameters* p, Node* handler, const OnCodeHandler& on_code_handler, | |
| const OnFoundOnReceiver& on_found_on_receiver, Label* miss, | |
| ICMode ic_mode) { | |
| // | |
| // Check prototype validity cell. | |
| // | |
| { | |
| Node* maybe_validity_cell = | |
| LoadObjectField(handler, ICHandler::kValidityCellOffset); | |
| CheckPrototypeValidityCell(maybe_validity_cell, miss); | |
| } | |
| // | |
| // Check smi handler bits. | |
| // | |
| { | |
| Node* smi_or_code_handler = | |
| LoadObjectField(handler, ICHandler::kSmiHandlerOffset); | |
| if (on_code_handler) { | |
| Label if_smi_handler(this); | |
| GotoIf(TaggedIsSmi(smi_or_code_handler), &if_smi_handler); | |
| CSA_ASSERT(this, IsCodeMap(LoadMap(smi_or_code_handler))); | |
| on_code_handler(smi_or_code_handler); | |
| BIND(&if_smi_handler); | |
| } else { | |
| CSA_ASSERT(this, TaggedIsSmi(smi_or_code_handler)); | |
| } | |
| Node* handler_flags = SmiUntag(smi_or_code_handler); | |
| // Lookup on receiver and access checks are not necessary for global ICs | |
| // because in the former case the validity cell check guards modifications | |
| // of the global object and the latter is not applicable to the global | |
| // object. | |
| int mask = ICHandler::LookupOnReceiverBits::kMask | | |
| ICHandler::DoAccessCheckOnReceiverBits::kMask; | |
| if (ic_mode == ICMode::kGlobalIC) { | |
| CSA_ASSERT(this, IsClearWord(handler_flags, mask)); | |
| } else { | |
| DCHECK_EQ(ICMode::kNonGlobalIC, ic_mode); | |
| Label done(this), if_do_access_check(this), if_lookup_on_receiver(this); | |
| GotoIf(IsClearWord(handler_flags, mask), &done); | |
| // Only one of the bits can be set at a time. | |
| CSA_ASSERT(this, | |
| WordNotEqual(WordAnd(handler_flags, IntPtrConstant(mask)), | |
| IntPtrConstant(mask))); | |
| Branch(IsSetWord<LoadHandler::DoAccessCheckOnReceiverBits>(handler_flags), | |
| &if_do_access_check, &if_lookup_on_receiver); | |
| BIND(&if_do_access_check); | |
| { | |
| TNode<MaybeObject> data2 = LoadHandlerDataField(handler, 2); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(data2)); | |
| TNode<Object> expected_native_context = ToWeakHeapObject(data2, miss); | |
| EmitAccessCheck(expected_native_context, p->context, p->receiver, &done, | |
| miss); | |
| } | |
| // Dictionary lookup on receiver is not necessary for Load/StoreGlobalIC | |
| // because prototype validity cell check already guards modifications of | |
| // the global object. | |
| BIND(&if_lookup_on_receiver); | |
| { | |
| DCHECK_EQ(ICMode::kNonGlobalIC, ic_mode); | |
| CSA_ASSERT(this, Word32BinaryNot(HasInstanceType( | |
| p->receiver, JS_GLOBAL_OBJECT_TYPE))); | |
| TNode<NameDictionary> properties = | |
| CAST(LoadSlowProperties(p->receiver)); | |
| TVARIABLE(IntPtrT, var_name_index); | |
| Label found(this, &var_name_index); | |
| NameDictionaryLookup<NameDictionary>(properties, CAST(p->name), &found, | |
| &var_name_index, &done); | |
| BIND(&found); | |
| { | |
| if (on_found_on_receiver) { | |
| on_found_on_receiver(properties, var_name_index.value()); | |
| } else { | |
| Goto(miss); | |
| } | |
| } | |
| } | |
| BIND(&done); | |
| } | |
| return smi_or_code_handler; | |
| } | |
| } | |
| void AccessorAssembler::HandleLoadICProtoHandler( | |
| const LoadICParameters* p, Node* handler, Variable* var_holder, | |
| Variable* var_smi_handler, Label* if_smi_handler, Label* miss, | |
| ExitPoint* exit_point, ICMode ic_mode) { | |
| DCHECK_EQ(MachineRepresentation::kTagged, var_holder->rep()); | |
| DCHECK_EQ(MachineRepresentation::kTagged, var_smi_handler->rep()); | |
| Node* smi_handler = HandleProtoHandler<LoadHandler>( | |
| p, handler, | |
| // Code sub-handlers are not expected in LoadICs, so no |on_code_handler|. | |
| nullptr, | |
| // on_found_on_receiver | |
| [=](Node* properties, Node* name_index) { | |
| VARIABLE(var_details, MachineRepresentation::kWord32); | |
| VARIABLE(var_value, MachineRepresentation::kTagged); | |
| LoadPropertyFromNameDictionary(properties, name_index, &var_details, | |
| &var_value); | |
| Node* value = | |
| CallGetterIfAccessor(var_value.value(), var_details.value(), | |
| p->context, p->receiver, miss); | |
| exit_point->Return(value); | |
| }, | |
| miss, ic_mode); | |
| TNode<MaybeObject> maybe_holder = LoadHandlerDataField(handler, 1); | |
| Label load_from_cached_holder(this), done(this); | |
| Branch(IsStrongReferenceTo(maybe_holder, NullConstant()), &done, | |
| &load_from_cached_holder); | |
| BIND(&load_from_cached_holder); | |
| { | |
| // For regular holders, having passed the receiver map check and the | |
| // validity cell check implies that |holder| is alive. However, for global | |
| // object receivers, |maybe_holder| may be cleared. | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_holder)); | |
| Node* holder = ToWeakHeapObject(maybe_holder, miss); | |
| var_holder->Bind(holder); | |
| Goto(&done); | |
| } | |
| BIND(&done); | |
| { | |
| var_smi_handler->Bind(smi_handler); | |
| Goto(if_smi_handler); | |
| } | |
| } | |
| void AccessorAssembler::EmitAccessCheck(Node* expected_native_context, | |
| Node* context, Node* receiver, | |
| Label* can_access, Label* miss) { | |
| CSA_ASSERT(this, IsNativeContext(expected_native_context)); | |
| Node* native_context = LoadNativeContext(context); | |
| GotoIf(WordEqual(expected_native_context, native_context), can_access); | |
| // If the receiver is not a JSGlobalProxy then we miss. | |
| GotoIfNot(IsJSGlobalProxy(receiver), miss); | |
| // For JSGlobalProxy receiver try to compare security tokens of current | |
| // and expected native contexts. | |
| Node* expected_token = LoadContextElement(expected_native_context, | |
| Context::SECURITY_TOKEN_INDEX); | |
| Node* current_token = | |
| LoadContextElement(native_context, Context::SECURITY_TOKEN_INDEX); | |
| Branch(WordEqual(expected_token, current_token), can_access, miss); | |
| } | |
| void AccessorAssembler::JumpIfDataProperty(Node* details, Label* writable, | |
| Label* readonly) { | |
| // Accessor properties never have the READ_ONLY attribute set. | |
| GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask), | |
| readonly); | |
| Node* kind = DecodeWord32<PropertyDetails::KindField>(details); | |
| GotoIf(Word32Equal(kind, Int32Constant(kData)), writable); | |
| // Fall through if it's an accessor property. | |
| } | |
| void AccessorAssembler::HandleStoreICNativeDataProperty( | |
| const StoreICParameters* p, Node* holder, Node* handler_word) { | |
| Comment("native_data_property_store"); | |
| Node* descriptor = DecodeWord<StoreHandler::DescriptorBits>(handler_word); | |
| Node* accessor_info = LoadDescriptorValue(LoadMap(holder), descriptor); | |
| CSA_CHECK(this, IsAccessorInfo(accessor_info)); | |
| Node* language_mode = GetLanguageMode(p->vector, p->slot); | |
| TailCallRuntime(Runtime::kStoreCallbackProperty, p->context, p->receiver, | |
| holder, accessor_info, p->name, p->value, language_mode); | |
| } | |
| void AccessorAssembler::HandleStoreICHandlerCase( | |
| const StoreICParameters* p, TNode<MaybeObject> handler, Label* miss, | |
| ICMode ic_mode, ElementSupport support_elements) { | |
| Label if_smi_handler(this), if_nonsmi_handler(this); | |
| Label if_proto_handler(this), if_element_handler(this), call_handler(this), | |
| store_transition_or_global(this); | |
| Branch(TaggedIsSmi(handler), &if_smi_handler, &if_nonsmi_handler); | |
| // |handler| is a Smi, encoding what to do. See SmiHandler methods | |
| // for the encoding format. | |
| BIND(&if_smi_handler); | |
| { | |
| Node* holder = p->receiver; | |
| Node* handler_word = SmiUntag(CAST(handler)); | |
| Label if_fast_smi(this), if_proxy(this); | |
| STATIC_ASSERT(StoreHandler::kGlobalProxy + 1 == StoreHandler::kNormal); | |
| STATIC_ASSERT(StoreHandler::kNormal + 1 == StoreHandler::kProxy); | |
| STATIC_ASSERT(StoreHandler::kProxy + 1 == StoreHandler::kKindsNumber); | |
| Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word); | |
| GotoIf(IntPtrLessThan(handler_kind, | |
| IntPtrConstant(StoreHandler::kGlobalProxy)), | |
| &if_fast_smi); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kProxy)), | |
| &if_proxy); | |
| CSA_ASSERT(this, | |
| WordEqual(handler_kind, IntPtrConstant(StoreHandler::kNormal))); | |
| TNode<NameDictionary> properties = CAST(LoadSlowProperties(holder)); | |
| TVARIABLE(IntPtrT, var_name_index); | |
| Label dictionary_found(this, &var_name_index); | |
| NameDictionaryLookup<NameDictionary>( | |
| properties, CAST(p->name), &dictionary_found, &var_name_index, miss); | |
| BIND(&dictionary_found); | |
| { | |
| Node* details = LoadDetailsByKeyIndex<NameDictionary>( | |
| properties, var_name_index.value()); | |
| // Check that the property is a writable data property (no accessor). | |
| const int kTypeAndReadOnlyMask = PropertyDetails::KindField::kMask | | |
| PropertyDetails::kAttributesReadOnlyMask; | |
| STATIC_ASSERT(kData == 0); | |
| GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss); | |
| StoreValueByKeyIndex<NameDictionary>(properties, var_name_index.value(), | |
| p->value); | |
| Return(p->value); | |
| } | |
| BIND(&if_fast_smi); | |
| { | |
| Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word); | |
| Label data(this), accessor(this), native_data_property(this); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kAccessor)), | |
| &accessor); | |
| Branch(WordEqual(handler_kind, | |
| IntPtrConstant(StoreHandler::kNativeDataProperty)), | |
| &native_data_property, &data); | |
| BIND(&accessor); | |
| HandleStoreAccessor(p, holder, handler_word); | |
| BIND(&native_data_property); | |
| HandleStoreICNativeDataProperty(p, holder, handler_word); | |
| BIND(&data); | |
| // Handle non-transitioning field stores. | |
| HandleStoreICSmiHandlerCase(handler_word, holder, p->value, miss); | |
| } | |
| BIND(&if_proxy); | |
| HandleStoreToProxy(p, holder, miss, support_elements); | |
| } | |
| BIND(&if_nonsmi_handler); | |
| { | |
| GotoIf(IsWeakOrClearedHeapObject(handler), &store_transition_or_global); | |
| TNode<HeapObject> strong_handler = CAST(handler); | |
| TNode<Map> handler_map = LoadMap(strong_handler); | |
| Branch(IsCodeMap(handler_map), &call_handler, &if_proto_handler); | |
| BIND(&if_proto_handler); | |
| { | |
| HandleStoreICProtoHandler(p, CAST(strong_handler), miss, ic_mode, | |
| support_elements); | |
| } | |
| // |handler| is a heap object. Must be code, call it. | |
| BIND(&call_handler); | |
| { | |
| TailCallStub(StoreWithVectorDescriptor{}, CAST(strong_handler), | |
| CAST(p->context), p->receiver, p->name, p->value, p->slot, | |
| p->vector); | |
| } | |
| } | |
| BIND(&store_transition_or_global); | |
| { | |
| // Load value or miss if the {handler} weak cell is cleared. | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(handler)); | |
| TNode<HeapObject> map_or_property_cell = ToWeakHeapObject(handler, miss); | |
| Label store_global(this), store_transition(this); | |
| Branch(IsMap(map_or_property_cell), &store_transition, &store_global); | |
| BIND(&store_global); | |
| { | |
| TNode<PropertyCell> property_cell = CAST(map_or_property_cell); | |
| ExitPoint direct_exit(this); | |
| StoreGlobalIC_PropertyCellCase(property_cell, p->value, &direct_exit, | |
| miss); | |
| } | |
| BIND(&store_transition); | |
| { | |
| TNode<Map> map = CAST(map_or_property_cell); | |
| HandleStoreICTransitionMapHandlerCase(p, map, miss, false); | |
| Return(p->value); | |
| } | |
| } | |
| } | |
| void AccessorAssembler::HandleStoreICTransitionMapHandlerCase( | |
| const StoreICParameters* p, TNode<Map> transition_map, Label* miss, | |
| bool validate_transition_handler) { | |
| Node* maybe_validity_cell = | |
| LoadObjectField(transition_map, Map::kPrototypeValidityCellOffset); | |
| CheckPrototypeValidityCell(maybe_validity_cell, miss); | |
| TNode<Uint32T> bitfield3 = LoadMapBitField3(transition_map); | |
| CSA_ASSERT(this, IsClearWord32<Map::IsDictionaryMapBit>(bitfield3)); | |
| GotoIf(IsSetWord32<Map::IsDeprecatedBit>(bitfield3), miss); | |
| // Load last descriptor details. | |
| Node* nof = DecodeWordFromWord32<Map::NumberOfOwnDescriptorsBits>(bitfield3); | |
| CSA_ASSERT(this, WordNotEqual(nof, IntPtrConstant(0))); | |
| TNode<DescriptorArray> descriptors = LoadMapDescriptors(transition_map); | |
| Node* factor = IntPtrConstant(DescriptorArray::kEntrySize); | |
| TNode<IntPtrT> last_key_index = UncheckedCast<IntPtrT>(IntPtrAdd( | |
| IntPtrConstant(DescriptorArray::ToKeyIndex(-1)), IntPtrMul(nof, factor))); | |
| if (validate_transition_handler) { | |
| Node* key = LoadWeakFixedArrayElement(descriptors, last_key_index); | |
| GotoIf(WordNotEqual(key, p->name), miss); | |
| } else { | |
| CSA_ASSERT(this, | |
| WordEqual(BitcastMaybeObjectToWord(LoadWeakFixedArrayElement( | |
| descriptors, last_key_index)), | |
| p->name)); | |
| } | |
| Node* details = LoadDetailsByKeyIndex(descriptors, last_key_index); | |
| if (validate_transition_handler) { | |
| // Follow transitions only in the following cases: | |
| // 1) name is a non-private symbol and attributes equal to NONE, | |
| // 2) name is a private symbol and attributes equal to DONT_ENUM. | |
| Label attributes_ok(this); | |
| const int kAttributesDontDeleteReadOnlyMask = | |
| PropertyDetails::kAttributesDontDeleteMask | | |
| PropertyDetails::kAttributesReadOnlyMask; | |
| // Both DontDelete and ReadOnly attributes must not be set. | |
| GotoIf(IsSetWord32(details, kAttributesDontDeleteReadOnlyMask), miss); | |
| // DontEnum attribute is allowed only for private symbols and vice versa. | |
| Branch(Word32Equal( | |
| IsSetWord32(details, PropertyDetails::kAttributesDontEnumMask), | |
| IsPrivateSymbol(p->name)), | |
| &attributes_ok, miss); | |
| BIND(&attributes_ok); | |
| } | |
| OverwriteExistingFastDataProperty(p->receiver, transition_map, descriptors, | |
| last_key_index, details, p->value, miss, | |
| true); | |
| } | |
| void AccessorAssembler::CheckFieldType(TNode<DescriptorArray> descriptors, | |
| Node* name_index, Node* representation, | |
| Node* value, Label* bailout) { | |
| Label r_smi(this), r_double(this), r_heapobject(this), all_fine(this); | |
| // Ignore FLAG_track_fields etc. and always emit code for all checks, | |
| // because this builtin is part of the snapshot and therefore should | |
| // be flag independent. | |
| GotoIf(Word32Equal(representation, Int32Constant(Representation::kSmi)), | |
| &r_smi); | |
| GotoIf(Word32Equal(representation, Int32Constant(Representation::kDouble)), | |
| &r_double); | |
| GotoIf( | |
| Word32Equal(representation, Int32Constant(Representation::kHeapObject)), | |
| &r_heapobject); | |
| GotoIf(Word32Equal(representation, Int32Constant(Representation::kNone)), | |
| bailout); | |
| CSA_ASSERT(this, Word32Equal(representation, | |
| Int32Constant(Representation::kTagged))); | |
| Goto(&all_fine); | |
| BIND(&r_smi); | |
| { Branch(TaggedIsSmi(value), &all_fine, bailout); } | |
| BIND(&r_double); | |
| { | |
| GotoIf(TaggedIsSmi(value), &all_fine); | |
| Node* value_map = LoadMap(value); | |
| // While supporting mutable HeapNumbers would be straightforward, such | |
| // objects should not end up here anyway. | |
| CSA_ASSERT(this, | |
| WordNotEqual(value_map, | |
| LoadRoot(Heap::kMutableHeapNumberMapRootIndex))); | |
| Branch(IsHeapNumberMap(value_map), &all_fine, bailout); | |
| } | |
| BIND(&r_heapobject); | |
| { | |
| GotoIf(TaggedIsSmi(value), bailout); | |
| TNode<MaybeObject> field_type = LoadFieldTypeByKeyIndex( | |
| descriptors, UncheckedCast<IntPtrT>(name_index)); | |
| intptr_t kNoneType = reinterpret_cast<intptr_t>(FieldType::None()); | |
| intptr_t kAnyType = reinterpret_cast<intptr_t>(FieldType::Any()); | |
| DCHECK_NE(kNoneType, kClearedWeakHeapObject); | |
| DCHECK_NE(kAnyType, kClearedWeakHeapObject); | |
| // FieldType::None can't hold any value. | |
| GotoIf(WordEqual(BitcastMaybeObjectToWord(field_type), | |
| IntPtrConstant(kNoneType)), | |
| bailout); | |
| // FieldType::Any can hold any value. | |
| GotoIf(WordEqual(BitcastMaybeObjectToWord(field_type), | |
| IntPtrConstant(kAnyType)), | |
| &all_fine); | |
| // Cleared weak references count as FieldType::None, which can't hold any | |
| // value. | |
| TNode<Map> field_type_map = CAST(ToWeakHeapObject(field_type, bailout)); | |
| // FieldType::Class(...) performs a map check. | |
| Branch(WordEqual(LoadMap(value), field_type_map), &all_fine, bailout); | |
| } | |
| BIND(&all_fine); | |
| } | |
| void AccessorAssembler::OverwriteExistingFastDataProperty( | |
| Node* object, Node* object_map, Node* descriptors, | |
| Node* descriptor_name_index, Node* details, Node* value, Label* slow, | |
| bool do_transitioning_store) { | |
| Label done(this), if_field(this), if_descriptor(this); | |
| CSA_ASSERT(this, | |
| Word32Equal(DecodeWord32<PropertyDetails::KindField>(details), | |
| Int32Constant(kData))); | |
| Branch(Word32Equal(DecodeWord32<PropertyDetails::LocationField>(details), | |
| Int32Constant(kField)), | |
| &if_field, &if_descriptor); | |
| BIND(&if_field); | |
| { | |
| if (FLAG_track_constant_fields && !do_transitioning_store) { | |
| // TODO(ishell): Taking the slow path is not necessary if new and old | |
| // values are identical. | |
| GotoIf(Word32Equal( | |
| DecodeWord32<PropertyDetails::ConstnessField>(details), | |
| Int32Constant(static_cast<int32_t>(VariableMode::kConst))), | |
| slow); | |
| } | |
| Node* representation = | |
| DecodeWord32<PropertyDetails::RepresentationField>(details); | |
| CheckFieldType(CAST(descriptors), descriptor_name_index, representation, | |
| value, slow); | |
| Node* field_index = | |
| DecodeWordFromWord32<PropertyDetails::FieldIndexField>(details); | |
| field_index = IntPtrAdd(field_index, | |
| LoadMapInobjectPropertiesStartInWords(object_map)); | |
| Node* instance_size_in_words = LoadMapInstanceSizeInWords(object_map); | |
| Label inobject(this), backing_store(this); | |
| Branch(UintPtrLessThan(field_index, instance_size_in_words), &inobject, | |
| &backing_store); | |
| BIND(&inobject); | |
| { | |
| Node* field_offset = TimesPointerSize(field_index); | |
| Label tagged_rep(this), double_rep(this); | |
| Branch( | |
| Word32Equal(representation, Int32Constant(Representation::kDouble)), | |
| &double_rep, &tagged_rep); | |
| BIND(&double_rep); | |
| { | |
| Node* double_value = ChangeNumberToFloat64(value); | |
| if (FLAG_unbox_double_fields) { | |
| if (do_transitioning_store) { | |
| StoreMap(object, object_map); | |
| } | |
| StoreObjectFieldNoWriteBarrier(object, field_offset, double_value, | |
| MachineRepresentation::kFloat64); | |
| } else { | |
| if (do_transitioning_store) { | |
| Node* mutable_heap_number = | |
| AllocateMutableHeapNumberWithValue(double_value); | |
| StoreMap(object, object_map); | |
| StoreObjectField(object, field_offset, mutable_heap_number); | |
| } else { | |
| Node* mutable_heap_number = LoadObjectField(object, field_offset); | |
| StoreHeapNumberValue(mutable_heap_number, double_value); | |
| } | |
| } | |
| Goto(&done); | |
| } | |
| BIND(&tagged_rep); | |
| { | |
| if (do_transitioning_store) { | |
| StoreMap(object, object_map); | |
| } | |
| StoreObjectField(object, field_offset, value); | |
| Goto(&done); | |
| } | |
| } | |
| BIND(&backing_store); | |
| { | |
| Node* backing_store_index = | |
| IntPtrSub(field_index, instance_size_in_words); | |
| if (do_transitioning_store) { | |
| // Allocate mutable heap number before extending properties backing | |
| // store to ensure that heap verifier will not see the heap in | |
| // inconsistent state. | |
| VARIABLE(var_value, MachineRepresentation::kTagged, value); | |
| { | |
| Label cont(this); | |
| GotoIf(Word32NotEqual(representation, | |
| Int32Constant(Representation::kDouble)), | |
| &cont); | |
| { | |
| Node* double_value = ChangeNumberToFloat64(value); | |
| Node* mutable_heap_number = | |
| AllocateMutableHeapNumberWithValue(double_value); | |
| var_value.Bind(mutable_heap_number); | |
| Goto(&cont); | |
| } | |
| BIND(&cont); | |
| } | |
| TNode<PropertyArray> properties = | |
| CAST(ExtendPropertiesBackingStore(object, backing_store_index)); | |
| StorePropertyArrayElement(properties, backing_store_index, | |
| var_value.value()); | |
| StoreMap(object, object_map); | |
| Goto(&done); | |
| } else { | |
| Label tagged_rep(this), double_rep(this); | |
| TNode<PropertyArray> properties = CAST(LoadFastProperties(object)); | |
| Branch( | |
| Word32Equal(representation, Int32Constant(Representation::kDouble)), | |
| &double_rep, &tagged_rep); | |
| BIND(&double_rep); | |
| { | |
| Node* mutable_heap_number = | |
| LoadPropertyArrayElement(properties, backing_store_index); | |
| Node* double_value = ChangeNumberToFloat64(value); | |
| StoreHeapNumberValue(mutable_heap_number, double_value); | |
| Goto(&done); | |
| } | |
| BIND(&tagged_rep); | |
| { | |
| StorePropertyArrayElement(properties, backing_store_index, value); | |
| Goto(&done); | |
| } | |
| } | |
| } | |
| } | |
| BIND(&if_descriptor); | |
| { | |
| // Check that constant matches value. | |
| Node* constant = LoadValueByKeyIndex( | |
| CAST(descriptors), UncheckedCast<IntPtrT>(descriptor_name_index)); | |
| GotoIf(WordNotEqual(value, constant), slow); | |
| if (do_transitioning_store) { | |
| StoreMap(object, object_map); | |
| } | |
| Goto(&done); | |
| } | |
| BIND(&done); | |
| } | |
| void AccessorAssembler::CheckPrototypeValidityCell(Node* maybe_validity_cell, | |
| Label* miss) { | |
| Label done(this); | |
| GotoIf(WordEqual(maybe_validity_cell, SmiConstant(Map::kPrototypeChainValid)), | |
| &done); | |
| CSA_ASSERT(this, TaggedIsNotSmi(maybe_validity_cell)); | |
| Node* cell_value = LoadObjectField(maybe_validity_cell, Cell::kValueOffset); | |
| Branch(WordEqual(cell_value, SmiConstant(Map::kPrototypeChainValid)), &done, | |
| miss); | |
| BIND(&done); | |
| } | |
| void AccessorAssembler::HandleStoreAccessor(const StoreICParameters* p, | |
| Node* holder, Node* handler_word) { | |
| Comment("accessor_store"); | |
| Node* descriptor = DecodeWord<StoreHandler::DescriptorBits>(handler_word); | |
| Node* accessor_pair = LoadDescriptorValue(LoadMap(holder), descriptor); | |
| CSA_ASSERT(this, IsAccessorPair(accessor_pair)); | |
| Node* setter = LoadObjectField(accessor_pair, AccessorPair::kSetterOffset); | |
| CSA_ASSERT(this, Word32BinaryNot(IsTheHole(setter))); | |
| Callable callable = CodeFactory::Call(isolate()); | |
| Return(CallJS(callable, p->context, setter, p->receiver, p->value)); | |
| } | |
| void AccessorAssembler::HandleStoreICProtoHandler( | |
| const StoreICParameters* p, TNode<StoreHandler> handler, Label* miss, | |
| ICMode ic_mode, ElementSupport support_elements) { | |
| Comment("HandleStoreICProtoHandler"); | |
| OnCodeHandler on_code_handler; | |
| if (support_elements == kSupportElements) { | |
| // Code sub-handlers are expected only in KeyedStoreICs. | |
| on_code_handler = [=](Node* code_handler) { | |
| // This is either element store or transitioning element store. | |
| Label if_element_store(this), if_transitioning_element_store(this); | |
| Branch(IsStoreHandler0Map(LoadMap(handler)), &if_element_store, | |
| &if_transitioning_element_store); | |
| BIND(&if_element_store); | |
| { | |
| TailCallStub(StoreWithVectorDescriptor{}, code_handler, p->context, | |
| p->receiver, p->name, p->value, p->slot, p->vector); | |
| } | |
| BIND(&if_transitioning_element_store); | |
| { | |
| TNode<MaybeObject> maybe_transition_map = | |
| LoadHandlerDataField(handler, 1); | |
| TNode<Map> transition_map = | |
| CAST(ToWeakHeapObject(maybe_transition_map, miss)); | |
| GotoIf(IsDeprecatedMap(transition_map), miss); | |
| TailCallStub(StoreTransitionDescriptor{}, code_handler, p->context, | |
| p->receiver, p->name, transition_map, p->value, p->slot, | |
| p->vector); | |
| } | |
| }; | |
| } | |
| Node* smi_handler = HandleProtoHandler<StoreHandler>( | |
| p, handler, on_code_handler, | |
| // on_found_on_receiver | |
| [=](Node* properties, Node* name_index) { | |
| Node* details = | |
| LoadDetailsByKeyIndex<NameDictionary>(properties, name_index); | |
| // Check that the property is a writable data property (no accessor). | |
| const int kTypeAndReadOnlyMask = | |
| PropertyDetails::KindField::kMask | | |
| PropertyDetails::kAttributesReadOnlyMask; | |
| STATIC_ASSERT(kData == 0); | |
| GotoIf(IsSetWord32(details, kTypeAndReadOnlyMask), miss); | |
| StoreValueByKeyIndex<NameDictionary>( | |
| CAST(properties), UncheckedCast<IntPtrT>(name_index), p->value); | |
| Return(p->value); | |
| }, | |
| miss, ic_mode); | |
| { | |
| Label if_add_normal(this), if_store_global_proxy(this), if_api_setter(this), | |
| if_accessor(this), if_native_data_property(this); | |
| CSA_ASSERT(this, TaggedIsSmi(smi_handler)); | |
| Node* handler_word = SmiUntag(smi_handler); | |
| Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kNormal)), | |
| &if_add_normal); | |
| TNode<MaybeObject> maybe_holder = LoadHandlerDataField(handler, 1); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_holder)); | |
| TNode<Object> holder = ToWeakHeapObject(maybe_holder, miss); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kGlobalProxy)), | |
| &if_store_global_proxy); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kAccessor)), | |
| &if_accessor); | |
| GotoIf(WordEqual(handler_kind, | |
| IntPtrConstant(StoreHandler::kNativeDataProperty)), | |
| &if_native_data_property); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kApiSetter)), | |
| &if_api_setter); | |
| GotoIf(WordEqual(handler_kind, | |
| IntPtrConstant(StoreHandler::kApiSetterHolderIsPrototype)), | |
| &if_api_setter); | |
| CSA_ASSERT(this, | |
| WordEqual(handler_kind, IntPtrConstant(StoreHandler::kProxy))); | |
| HandleStoreToProxy(p, holder, miss, support_elements); | |
| BIND(&if_add_normal); | |
| { | |
| // This is a case of "transitioning store" to a dictionary mode object | |
| // when the property is still does not exist. The "existing property" | |
| // case is covered above by LookupOnReceiver bit handling of the smi | |
| // handler. | |
| Label slow(this); | |
| TNode<Map> receiver_map = LoadMap(p->receiver); | |
| InvalidateValidityCellIfPrototype(receiver_map); | |
| TNode<NameDictionary> properties = CAST(LoadSlowProperties(p->receiver)); | |
| Add<NameDictionary>(properties, CAST(p->name), p->value, &slow); | |
| Return(p->value); | |
| BIND(&slow); | |
| TailCallRuntime(Runtime::kAddDictionaryProperty, p->context, p->receiver, | |
| p->name, p->value); | |
| } | |
| BIND(&if_accessor); | |
| HandleStoreAccessor(p, holder, handler_word); | |
| BIND(&if_native_data_property); | |
| HandleStoreICNativeDataProperty(p, holder, handler_word); | |
| BIND(&if_api_setter); | |
| { | |
| Comment("api_setter"); | |
| CSA_ASSERT(this, TaggedIsNotSmi(handler)); | |
| Node* call_handler_info = holder; | |
| // Context is stored either in data2 or data3 field depending on whether | |
| // the access check is enabled for this handler or not. | |
| TNode<MaybeObject> maybe_context = Select<MaybeObject>( | |
| IsSetWord<LoadHandler::DoAccessCheckOnReceiverBits>(handler_word), | |
| [=] { return LoadHandlerDataField(handler, 3); }, | |
| [=] { return LoadHandlerDataField(handler, 2); }); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_context)); | |
| TNode<Object> context = | |
| Select<Object>(IsClearedWeakHeapObject(maybe_context), | |
| [=] { return SmiConstant(0); }, | |
| [=] { return ToWeakHeapObject(maybe_context); }); | |
| Node* foreign = LoadObjectField(call_handler_info, | |
| CallHandlerInfo::kJsCallbackOffset); | |
| Node* callback = LoadObjectField(foreign, Foreign::kForeignAddressOffset, | |
| MachineType::Pointer()); | |
| Node* data = | |
| LoadObjectField(call_handler_info, CallHandlerInfo::kDataOffset); | |
| VARIABLE(api_holder, MachineRepresentation::kTagged, p->receiver); | |
| Label store(this); | |
| GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kApiSetter)), | |
| &store); | |
| CSA_ASSERT( | |
| this, | |
| WordEqual(handler_kind, | |
| IntPtrConstant(StoreHandler::kApiSetterHolderIsPrototype))); | |
| api_holder.Bind(LoadMapPrototype(LoadMap(p->receiver))); | |
| Goto(&store); | |
| BIND(&store); | |
| Callable callable = CodeFactory::CallApiCallback(isolate(), 1); | |
| Return(CallStub(callable, nullptr, context, data, api_holder.value(), | |
| callback, p->receiver, p->value)); | |
| } | |
| BIND(&if_store_global_proxy); | |
| { | |
| ExitPoint direct_exit(this); | |
| StoreGlobalIC_PropertyCellCase(holder, p->value, &direct_exit, miss); | |
| } | |
| } | |
| } | |
| Node* AccessorAssembler::GetLanguageMode(Node* vector, Node* slot) { | |
| VARIABLE(var_language_mode, MachineRepresentation::kTaggedSigned, | |
| SmiConstant(LanguageMode::kStrict)); | |
| Label language_mode_determined(this); | |
| BranchIfStrictMode(vector, slot, &language_mode_determined); | |
| var_language_mode.Bind(SmiConstant(LanguageMode::kSloppy)); | |
| Goto(&language_mode_determined); | |
| BIND(&language_mode_determined); | |
| return var_language_mode.value(); | |
| } | |
| void AccessorAssembler::HandleStoreToProxy(const StoreICParameters* p, | |
| Node* proxy, Label* miss, | |
| ElementSupport support_elements) { | |
| VARIABLE(var_index, MachineType::PointerRepresentation()); | |
| VARIABLE(var_unique, MachineRepresentation::kTagged); | |
| Label if_index(this), if_unique_name(this), | |
| to_name_failed(this, Label::kDeferred); | |
| Node* language_mode = GetLanguageMode(p->vector, p->slot); | |
| if (support_elements == kSupportElements) { | |
| TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique, | |
| &to_name_failed); | |
| BIND(&if_unique_name); | |
| CallBuiltin(Builtins::kProxySetProperty, p->context, proxy, | |
| var_unique.value(), p->value, p->receiver, language_mode); | |
| Return(p->value); | |
| // The index case is handled earlier by the runtime. | |
| BIND(&if_index); | |
| // TODO(mslekova): introduce TryToName that doesn't try to compute | |
| // the intptr index value | |
| Goto(&to_name_failed); | |
| BIND(&to_name_failed); | |
| TailCallRuntime(Runtime::kSetPropertyWithReceiver, p->context, proxy, | |
| p->name, p->value, p->receiver, language_mode); | |
| } else { | |
| Node* name = ToName(p->context, p->name); | |
| TailCallBuiltin(Builtins::kProxySetProperty, p->context, proxy, name, | |
| p->value, p->receiver, language_mode); | |
| } | |
| } | |
| void AccessorAssembler::HandleStoreICSmiHandlerCase(Node* handler_word, | |
| Node* holder, Node* value, | |
| Label* miss) { | |
| Comment("field store"); | |
| #ifdef DEBUG | |
| Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word); | |
| if (FLAG_track_constant_fields) { | |
| CSA_ASSERT( | |
| this, | |
| Word32Or(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kField)), | |
| WordEqual(handler_kind, | |
| IntPtrConstant(StoreHandler::kConstField)))); | |
| } else { | |
| CSA_ASSERT(this, | |
| WordEqual(handler_kind, IntPtrConstant(StoreHandler::kField))); | |
| } | |
| #endif | |
| Node* field_representation = | |
| DecodeWord<StoreHandler::FieldRepresentationBits>(handler_word); | |
| Label if_smi_field(this), if_double_field(this), if_heap_object_field(this), | |
| if_tagged_field(this); | |
| GotoIf(WordEqual(field_representation, IntPtrConstant(StoreHandler::kTagged)), | |
| &if_tagged_field); | |
| GotoIf(WordEqual(field_representation, | |
| IntPtrConstant(StoreHandler::kHeapObject)), | |
| &if_heap_object_field); | |
| GotoIf(WordEqual(field_representation, IntPtrConstant(StoreHandler::kDouble)), | |
| &if_double_field); | |
| CSA_ASSERT(this, WordEqual(field_representation, | |
| IntPtrConstant(StoreHandler::kSmi))); | |
| Goto(&if_smi_field); | |
| BIND(&if_tagged_field); | |
| { | |
| Comment("store tagged field"); | |
| HandleStoreFieldAndReturn(handler_word, holder, Representation::Tagged(), | |
| value, miss); | |
| } | |
| BIND(&if_double_field); | |
| { | |
| Comment("store double field"); | |
| HandleStoreFieldAndReturn(handler_word, holder, Representation::Double(), | |
| value, miss); | |
| } | |
| BIND(&if_heap_object_field); | |
| { | |
| Comment("store heap object field"); | |
| HandleStoreFieldAndReturn(handler_word, holder, | |
| Representation::HeapObject(), value, miss); | |
| } | |
| BIND(&if_smi_field); | |
| { | |
| Comment("store smi field"); | |
| HandleStoreFieldAndReturn(handler_word, holder, Representation::Smi(), | |
| value, miss); | |
| } | |
| } | |
| void AccessorAssembler::HandleStoreFieldAndReturn(Node* handler_word, | |
| Node* holder, | |
| Representation representation, | |
| Node* value, Label* miss) { | |
| Node* prepared_value = | |
| PrepareValueForStore(handler_word, holder, representation, value, miss); | |
| Label if_inobject(this), if_out_of_object(this); | |
| Branch(IsSetWord<StoreHandler::IsInobjectBits>(handler_word), &if_inobject, | |
| &if_out_of_object); | |
| BIND(&if_inobject); | |
| { | |
| StoreNamedField(handler_word, holder, true, representation, prepared_value, | |
| miss); | |
| Return(value); | |
| } | |
| BIND(&if_out_of_object); | |
| { | |
| StoreNamedField(handler_word, holder, false, representation, prepared_value, | |
| miss); | |
| Return(value); | |
| } | |
| } | |
| Node* AccessorAssembler::PrepareValueForStore(Node* handler_word, Node* holder, | |
| Representation representation, | |
| Node* value, Label* bailout) { | |
| if (representation.IsDouble()) { | |
| value = TryTaggedToFloat64(value, bailout); | |
| } else if (representation.IsHeapObject()) { | |
| GotoIf(TaggedIsSmi(value), bailout); | |
| Label done(this); | |
| if (FLAG_track_constant_fields) { | |
| // Skip field type check in favor of constant value check when storing | |
| // to constant field. | |
| GotoIf(WordEqual(DecodeWord<StoreHandler::KindBits>(handler_word), | |
| IntPtrConstant(StoreHandler::kConstField)), | |
| &done); | |
| } | |
| Node* descriptor = DecodeWord<StoreHandler::DescriptorBits>(handler_word); | |
| TNode<MaybeObject> maybe_field_type = | |
| LoadDescriptorValueOrFieldType(LoadMap(holder), descriptor); | |
| GotoIf(TaggedIsSmi(maybe_field_type), &done); | |
| // Check that value type matches the field type. | |
| { | |
| Node* field_type = ToWeakHeapObject(maybe_field_type, bailout); | |
| Branch(WordEqual(LoadMap(value), field_type), &done, bailout); | |
| } | |
| BIND(&done); | |
| } else if (representation.IsSmi()) { | |
| GotoIfNot(TaggedIsSmi(value), bailout); | |
| } else { | |
| DCHECK(representation.IsTagged()); | |
| } | |
| return value; | |
| } | |
| Node* AccessorAssembler::ExtendPropertiesBackingStore(Node* object, | |
| Node* index) { | |
| Comment("[ Extend storage"); | |
| ParameterMode mode = OptimalParameterMode(); | |
| // TODO(gsathya): Clean up the type conversions by creating smarter | |
| // helpers that do the correct op based on the mode. | |
| VARIABLE(var_properties, MachineRepresentation::kTaggedPointer); | |
| VARIABLE(var_encoded_hash, MachineRepresentation::kWord32); | |
| VARIABLE(var_length, ParameterRepresentation(mode)); | |
| Node* properties = LoadObjectField(object, JSObject::kPropertiesOrHashOffset); | |
| var_properties.Bind(properties); | |
| Label if_smi_hash(this), if_property_array(this), extend_store(this); | |
| Branch(TaggedIsSmi(properties), &if_smi_hash, &if_property_array); | |
| BIND(&if_smi_hash); | |
| { | |
| Node* hash = SmiToInt32(properties); | |
| Node* encoded_hash = | |
| Word32Shl(hash, Int32Constant(PropertyArray::HashField::kShift)); | |
| var_encoded_hash.Bind(encoded_hash); | |
| var_length.Bind(IntPtrOrSmiConstant(0, mode)); | |
| var_properties.Bind(EmptyFixedArrayConstant()); | |
| Goto(&extend_store); | |
| } | |
| BIND(&if_property_array); | |
| { | |
| Node* length_and_hash_int32 = LoadAndUntagToWord32ObjectField( | |
| var_properties.value(), PropertyArray::kLengthAndHashOffset); | |
| var_encoded_hash.Bind(Word32And( | |
| length_and_hash_int32, Int32Constant(PropertyArray::HashField::kMask))); | |
| Node* length_intptr = ChangeInt32ToIntPtr( | |
| Word32And(length_and_hash_int32, | |
| Int32Constant(PropertyArray::LengthField::kMask))); | |
| Node* length = IntPtrToParameter(length_intptr, mode); | |
| var_length.Bind(length); | |
| Goto(&extend_store); | |
| } | |
| BIND(&extend_store); | |
| { | |
| VARIABLE(var_new_properties, MachineRepresentation::kTaggedPointer, | |
| var_properties.value()); | |
| Label done(this); | |
| // Previous property deletion could have left behind unused backing store | |
| // capacity even for a map that think it doesn't have any unused fields. | |
| // Perform a bounds check to see if we actually have to grow the array. | |
| GotoIf(UintPtrLessThan(index, ParameterToIntPtr(var_length.value(), mode)), | |
| &done); | |
| Node* delta = IntPtrOrSmiConstant(JSObject::kFieldsAdded, mode); | |
| Node* new_capacity = IntPtrOrSmiAdd(var_length.value(), delta, mode); | |
| // Grow properties array. | |
| DCHECK(kMaxNumberOfDescriptors + JSObject::kFieldsAdded < | |
| FixedArrayBase::GetMaxLengthForNewSpaceAllocation(PACKED_ELEMENTS)); | |
| // The size of a new properties backing store is guaranteed to be small | |
| // enough that the new backing store will be allocated in new space. | |
| CSA_ASSERT(this, | |
| UintPtrOrSmiLessThan( | |
| new_capacity, | |
| IntPtrOrSmiConstant( | |
| kMaxNumberOfDescriptors + JSObject::kFieldsAdded, mode), | |
| mode)); | |
| Node* new_properties = AllocatePropertyArray(new_capacity, mode); | |
| var_new_properties.Bind(new_properties); | |
| FillPropertyArrayWithUndefined(new_properties, var_length.value(), | |
| new_capacity, mode); | |
| // |new_properties| is guaranteed to be in new space, so we can skip | |
| // the write barrier. | |
| CopyPropertyArrayValues(var_properties.value(), new_properties, | |
| var_length.value(), SKIP_WRITE_BARRIER, mode); | |
| // TODO(gsathya): Clean up the type conversions by creating smarter | |
| // helpers that do the correct op based on the mode. | |
| Node* new_capacity_int32 = | |
| TruncateIntPtrToInt32(ParameterToIntPtr(new_capacity, mode)); | |
| Node* new_length_and_hash_int32 = | |
| Word32Or(var_encoded_hash.value(), new_capacity_int32); | |
| StoreObjectField(new_properties, PropertyArray::kLengthAndHashOffset, | |
| SmiFromInt32(new_length_and_hash_int32)); | |
| StoreObjectField(object, JSObject::kPropertiesOrHashOffset, new_properties); | |
| Comment("] Extend storage"); | |
| Goto(&done); | |
| BIND(&done); | |
| return var_new_properties.value(); | |
| } | |
| } | |
| void AccessorAssembler::StoreNamedField(Node* handler_word, Node* object, | |
| bool is_inobject, | |
| Representation representation, | |
| Node* value, Label* bailout) { | |
| bool store_value_as_double = representation.IsDouble(); | |
| Node* property_storage = object; | |
| if (!is_inobject) { | |
| property_storage = LoadFastProperties(object); | |
| } | |
| Node* index = DecodeWord<StoreHandler::FieldIndexBits>(handler_word); | |
| Node* offset = IntPtrMul(index, IntPtrConstant(kPointerSize)); | |
| if (representation.IsDouble()) { | |
| if (!FLAG_unbox_double_fields || !is_inobject) { | |
| // Load the mutable heap number. | |
| property_storage = LoadObjectField(property_storage, offset); | |
| // Store the double value into it. | |
| offset = IntPtrConstant(HeapNumber::kValueOffset); | |
| } | |
| } | |
| // Do constant value check if necessary. | |
| if (FLAG_track_constant_fields) { | |
| Label done(this); | |
| GotoIfNot(WordEqual(DecodeWord<StoreHandler::KindBits>(handler_word), | |
| IntPtrConstant(StoreHandler::kConstField)), | |
| &done); | |
| { | |
| if (store_value_as_double) { | |
| Node* current_value = | |
| LoadObjectField(property_storage, offset, MachineType::Float64()); | |
| GotoIfNot(Float64Equal(current_value, value), bailout); | |
| } else { | |
| Node* current_value = LoadObjectField(property_storage, offset); | |
| GotoIfNot(WordEqual(current_value, value), bailout); | |
| } | |
| Goto(&done); | |
| } | |
| BIND(&done); | |
| } | |
| // Do the store. | |
| if (store_value_as_double) { | |
| StoreObjectFieldNoWriteBarrier(property_storage, offset, value, | |
| MachineRepresentation::kFloat64); | |
| } else if (representation.IsSmi()) { | |
| StoreObjectFieldNoWriteBarrier(property_storage, offset, value); | |
| } else { | |
| StoreObjectField(property_storage, offset, value); | |
| } | |
| } | |
| void AccessorAssembler::EmitFastElementsBoundsCheck(Node* object, | |
| Node* elements, | |
| Node* intptr_index, | |
| Node* is_jsarray_condition, | |
| Label* miss) { | |
| VARIABLE(var_length, MachineType::PointerRepresentation()); | |
| Comment("Fast elements bounds check"); | |
| Label if_array(this), length_loaded(this, &var_length); | |
| GotoIf(is_jsarray_condition, &if_array); | |
| { | |
| var_length.Bind(SmiUntag(LoadFixedArrayBaseLength(elements))); | |
| Goto(&length_loaded); | |
| } | |
| BIND(&if_array); | |
| { | |
| var_length.Bind(SmiUntag(LoadFastJSArrayLength(object))); | |
| Goto(&length_loaded); | |
| } | |
| BIND(&length_loaded); | |
| GotoIfNot(UintPtrLessThan(intptr_index, var_length.value()), miss); | |
| } | |
| void AccessorAssembler::EmitElementLoad( | |
| Node* object, Node* elements, Node* elements_kind, | |
| SloppyTNode<IntPtrT> intptr_index, Node* is_jsarray_condition, | |
| Label* if_hole, Label* rebox_double, Variable* var_double_value, | |
| Label* unimplemented_elements_kind, Label* out_of_bounds, Label* miss, | |
| ExitPoint* exit_point) { | |
| Label if_typed_array(this), if_fast_packed(this), if_fast_holey(this), | |
| if_fast_double(this), if_fast_holey_double(this), if_nonfast(this), | |
| if_dictionary(this); | |
| GotoIf( | |
| Int32GreaterThan(elements_kind, Int32Constant(LAST_FAST_ELEMENTS_KIND)), | |
| &if_nonfast); | |
| EmitFastElementsBoundsCheck(object, elements, intptr_index, | |
| is_jsarray_condition, out_of_bounds); | |
| int32_t kinds[] = {// Handled by if_fast_packed. | |
| PACKED_SMI_ELEMENTS, PACKED_ELEMENTS, | |
| // Handled by if_fast_holey. | |
| HOLEY_SMI_ELEMENTS, HOLEY_ELEMENTS, | |
| // Handled by if_fast_double. | |
| PACKED_DOUBLE_ELEMENTS, | |
| // Handled by if_fast_holey_double. | |
| HOLEY_DOUBLE_ELEMENTS}; | |
| Label* labels[] = {// FAST_{SMI,}_ELEMENTS | |
| &if_fast_packed, &if_fast_packed, | |
| // FAST_HOLEY_{SMI,}_ELEMENTS | |
| &if_fast_holey, &if_fast_holey, | |
| // PACKED_DOUBLE_ELEMENTS | |
| &if_fast_double, | |
| // HOLEY_DOUBLE_ELEMENTS | |
| &if_fast_holey_double}; | |
| Switch(elements_kind, unimplemented_elements_kind, kinds, labels, | |
| arraysize(kinds)); | |
| BIND(&if_fast_packed); | |
| { | |
| Comment("fast packed elements"); | |
| exit_point->Return(LoadFixedArrayElement(CAST(elements), intptr_index)); | |
| } | |
| BIND(&if_fast_holey); | |
| { | |
| Comment("fast holey elements"); | |
| Node* element = LoadFixedArrayElement(CAST(elements), intptr_index); | |
| GotoIf(WordEqual(element, TheHoleConstant()), if_hole); | |
| exit_point->Return(element); | |
| } | |
| BIND(&if_fast_double); | |
| { | |
| Comment("packed double elements"); | |
| var_double_value->Bind(LoadFixedDoubleArrayElement(elements, intptr_index, | |
| MachineType::Float64())); | |
| Goto(rebox_double); | |
| } | |
| BIND(&if_fast_holey_double); | |
| { | |
| Comment("holey double elements"); | |
| Node* value = LoadFixedDoubleArrayElement(elements, intptr_index, | |
| MachineType::Float64(), 0, | |
| INTPTR_PARAMETERS, if_hole); | |
| var_double_value->Bind(value); | |
| Goto(rebox_double); | |
| } | |
| BIND(&if_nonfast); | |
| { | |
| STATIC_ASSERT(LAST_ELEMENTS_KIND == LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND); | |
| GotoIf(Int32GreaterThanOrEqual( | |
| elements_kind, | |
| Int32Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)), | |
| &if_typed_array); | |
| GotoIf(Word32Equal(elements_kind, Int32Constant(DICTIONARY_ELEMENTS)), | |
| &if_dictionary); | |
| Goto(unimplemented_elements_kind); | |
| } | |
| BIND(&if_dictionary); | |
| { | |
| Comment("dictionary elements"); | |
| GotoIf(IntPtrLessThan(intptr_index, IntPtrConstant(0)), out_of_bounds); | |
| TNode<Object> value = BasicLoadNumberDictionaryElement( | |
| CAST(elements), intptr_index, miss, if_hole); | |
| exit_point->Return(value); | |
| } | |
| BIND(&if_typed_array); | |
| { | |
| Comment("typed elements"); | |
| // Check if buffer has been neutered. | |
| Node* buffer = LoadObjectField(object, JSArrayBufferView::kBufferOffset); | |
| GotoIf(IsDetachedBuffer(buffer), miss); | |
| // Bounds check. | |
| Node* length = SmiUntag(LoadTypedArrayLength(CAST(object))); | |
| GotoIfNot(UintPtrLessThan(intptr_index, length), out_of_bounds); | |
| Node* backing_store = LoadFixedTypedArrayBackingStore(CAST(elements)); | |
| Label uint8_elements(this), int8_elements(this), uint16_elements(this), | |
| int16_elements(this), uint32_elements(this), int32_elements(this), | |
| float32_elements(this), float64_elements(this), bigint64_elements(this), | |
| biguint64_elements(this); | |
| Label* elements_kind_labels[] = { | |
| &uint8_elements, &uint8_elements, &int8_elements, | |
| &uint16_elements, &int16_elements, &uint32_elements, | |
| &int32_elements, &float32_elements, &float64_elements, | |
| &bigint64_elements, &biguint64_elements}; | |
| int32_t elements_kinds[] = { | |
| UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS, | |
| UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS, | |
| INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS, | |
| BIGINT64_ELEMENTS, BIGUINT64_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)); | |
| Switch(elements_kind, miss, elements_kinds, elements_kind_labels, | |
| kTypedElementsKindCount); | |
| BIND(&uint8_elements); | |
| { | |
| Comment("UINT8_ELEMENTS"); // Handles UINT8_CLAMPED_ELEMENTS too. | |
| Node* element = Load(MachineType::Uint8(), backing_store, intptr_index); | |
| exit_point->Return(SmiFromInt32(element)); | |
| } | |
| BIND(&int8_elements); | |
| { | |
| Comment("INT8_ELEMENTS"); | |
| Node* element = Load(MachineType::Int8(), backing_store, intptr_index); | |
| exit_point->Return(SmiFromInt32(element)); | |
| } | |
| BIND(&uint16_elements); | |
| { | |
| Comment("UINT16_ELEMENTS"); | |
| Node* index = WordShl(intptr_index, IntPtrConstant(1)); | |
| Node* element = Load(MachineType::Uint16(), backing_store, index); | |
| exit_point->Return(SmiFromInt32(element)); | |
| } | |
| BIND(&int16_elements); | |
| { | |
| Comment("INT16_ELEMENTS"); | |
| Node* index = WordShl(intptr_index, IntPtrConstant(1)); | |
| Node* element = Load(MachineType::Int16(), backing_store, index); | |
| exit_point->Return(SmiFromInt32(element)); | |
| } | |
| BIND(&uint32_elements); | |
| { | |
| Comment("UINT32_ELEMENTS"); | |
| Node* index = WordShl(intptr_index, IntPtrConstant(2)); | |
| Node* element = Load(MachineType::Uint32(), backing_store, index); | |
| exit_point->Return(ChangeUint32ToTagged(element)); | |
| } | |
| BIND(&int32_elements); | |
| { | |
| Comment("INT32_ELEMENTS"); | |
| Node* index = WordShl(intptr_index, IntPtrConstant(2)); | |
| Node* element = Load(MachineType::Int32(), backing_store, index); | |
| exit_point->Return(ChangeInt32ToTagged(element)); | |
| } | |
| BIND(&float32_elements); | |
| { | |
| Comment("FLOAT32_ELEMENTS"); | |
| Node* index = WordShl(intptr_index, IntPtrConstant(2)); | |
| Node* element = Load(MachineType::Float32(), backing_store, index); | |
| var_double_value->Bind(ChangeFloat32ToFloat64(element)); | |
| Goto(rebox_double); | |
| } | |
| BIND(&float64_elements); | |
| { | |
| Comment("FLOAT64_ELEMENTS"); | |
| Node* index = WordShl(intptr_index, IntPtrConstant(3)); | |
| Node* element = Load(MachineType::Float64(), backing_store, index); | |
| var_double_value->Bind(element); | |
| Goto(rebox_double); | |
| } | |
| BIND(&bigint64_elements); | |
| { | |
| Comment("BIGINT64_ELEMENTS"); | |
| exit_point->Return(LoadFixedTypedArrayElementAsTagged( | |
| backing_store, intptr_index, BIGINT64_ELEMENTS, INTPTR_PARAMETERS)); | |
| } | |
| BIND(&biguint64_elements); | |
| { | |
| Comment("BIGUINT64_ELEMENTS"); | |
| exit_point->Return(LoadFixedTypedArrayElementAsTagged( | |
| backing_store, intptr_index, BIGUINT64_ELEMENTS, INTPTR_PARAMETERS)); | |
| } | |
| } | |
| } | |
| void AccessorAssembler::NameDictionaryNegativeLookup(Node* object, | |
| SloppyTNode<Name> name, | |
| Label* miss) { | |
| CSA_ASSERT(this, IsDictionaryMap(LoadMap(object))); | |
| TNode<NameDictionary> properties = CAST(LoadSlowProperties(object)); | |
| // Ensure the property does not exist in a dictionary-mode object. | |
| TVARIABLE(IntPtrT, var_name_index); | |
| Label done(this); | |
| NameDictionaryLookup<NameDictionary>(properties, name, miss, &var_name_index, | |
| &done); | |
| BIND(&done); | |
| } | |
| void AccessorAssembler::BranchIfStrictMode(Node* vector, Node* slot, | |
| Label* if_strict) { | |
| Node* sfi = | |
| LoadObjectField(vector, FeedbackVector::kSharedFunctionInfoOffset); | |
| TNode<FeedbackMetadata> metadata = CAST(LoadObjectField( | |
| sfi, SharedFunctionInfo::kOuterScopeInfoOrFeedbackMetadataOffset)); | |
| Node* slot_int = SmiToInt32(slot); | |
| // See VectorICComputer::index(). | |
| const int kItemsPerWord = FeedbackMetadata::VectorICComputer::kItemsPerWord; | |
| Node* word_index = Int32Div(slot_int, Int32Constant(kItemsPerWord)); | |
| Node* word_offset = Int32Mod(slot_int, Int32Constant(kItemsPerWord)); | |
| int32_t first_item = FeedbackMetadata::kHeaderSize - kHeapObjectTag; | |
| Node* offset = | |
| ElementOffsetFromIndex(ChangeInt32ToIntPtr(word_index), UINT32_ELEMENTS, | |
| INTPTR_PARAMETERS, first_item); | |
| Node* data = Load(MachineType::Int32(), metadata, offset); | |
| // See VectorICComputer::decode(). | |
| const int kBitsPerItem = FeedbackMetadata::kFeedbackSlotKindBits; | |
| Node* shift = Int32Mul(word_offset, Int32Constant(kBitsPerItem)); | |
| const int kMask = FeedbackMetadata::VectorICComputer::kMask; | |
| Node* kind = Word32And(Word32Shr(data, shift), Int32Constant(kMask)); | |
| STATIC_ASSERT(FeedbackSlotKind::kStoreGlobalSloppy <= | |
| FeedbackSlotKind::kLastSloppyKind); | |
| STATIC_ASSERT(FeedbackSlotKind::kStoreKeyedSloppy <= | |
| FeedbackSlotKind::kLastSloppyKind); | |
| STATIC_ASSERT(FeedbackSlotKind::kStoreNamedSloppy <= | |
| FeedbackSlotKind::kLastSloppyKind); | |
| GotoIfNot(Int32LessThanOrEqual(kind, Int32Constant(static_cast<int>( | |
| FeedbackSlotKind::kLastSloppyKind))), | |
| if_strict); | |
| } | |
| void AccessorAssembler::InvalidateValidityCellIfPrototype(Node* map, | |
| Node* bitfield2) { | |
| Label is_prototype(this), cont(this); | |
| if (bitfield2 == nullptr) { | |
| bitfield2 = LoadMapBitField2(map); | |
| } | |
| Branch(IsSetWord32(bitfield2, Map::IsPrototypeMapBit::kMask), &is_prototype, | |
| &cont); | |
| BIND(&is_prototype); | |
| { | |
| Node* maybe_prototype_info = | |
| LoadObjectField(map, Map::kTransitionsOrPrototypeInfoOffset); | |
| // If there's no prototype info then there's nothing to invalidate. | |
| GotoIf(TaggedIsSmi(maybe_prototype_info), &cont); | |
| Node* function = ExternalConstant( | |
| ExternalReference::invalidate_prototype_chains_function()); | |
| CallCFunction1(MachineType::AnyTagged(), MachineType::AnyTagged(), function, | |
| map); | |
| Goto(&cont); | |
| } | |
| BIND(&cont); | |
| } | |
| void AccessorAssembler::GenericElementLoad(Node* receiver, Node* receiver_map, | |
| SloppyTNode<Int32T> instance_type, | |
| Node* index, Label* slow) { | |
| Comment("integer index"); | |
| ExitPoint direct_exit(this); | |
| Label if_custom(this), if_element_hole(this), if_oob(this); | |
| // Receivers requiring non-standard element accesses (interceptors, access | |
| // checks, strings and string wrappers, proxies) are handled in the runtime. | |
| GotoIf(IsCustomElementsReceiverInstanceType(instance_type), &if_custom); | |
| Node* elements = LoadElements(receiver); | |
| Node* elements_kind = LoadMapElementsKind(receiver_map); | |
| Node* is_jsarray_condition = InstanceTypeEqual(instance_type, JS_ARRAY_TYPE); | |
| VARIABLE(var_double_value, MachineRepresentation::kFloat64); | |
| Label rebox_double(this, &var_double_value); | |
| // Unimplemented elements kinds fall back to a runtime call. | |
| Label* unimplemented_elements_kind = slow; | |
| IncrementCounter(isolate()->counters()->ic_keyed_load_generic_smi(), 1); | |
| EmitElementLoad(receiver, elements, elements_kind, index, | |
| is_jsarray_condition, &if_element_hole, &rebox_double, | |
| &var_double_value, unimplemented_elements_kind, &if_oob, slow, | |
| &direct_exit); | |
| BIND(&rebox_double); | |
| Return(AllocateHeapNumberWithValue(var_double_value.value())); | |
| BIND(&if_oob); | |
| { | |
| Comment("out of bounds"); | |
| // Positive OOB indices are effectively the same as hole loads. | |
| GotoIf(IntPtrGreaterThanOrEqual(index, IntPtrConstant(0)), | |
| &if_element_hole); | |
| // Negative keys can't take the fast OOB path, except for typed arrays. | |
| GotoIfNot(InstanceTypeEqual(instance_type, JS_TYPED_ARRAY_TYPE), slow); | |
| Return(UndefinedConstant()); | |
| } | |
| BIND(&if_element_hole); | |
| { | |
| Comment("found the hole"); | |
| Label return_undefined(this); | |
| BranchIfPrototypesHaveNoElements(receiver_map, &return_undefined, slow); | |
| BIND(&return_undefined); | |
| Return(UndefinedConstant()); | |
| } | |
| BIND(&if_custom); | |
| { | |
| Comment("check if string"); | |
| GotoIfNot(IsStringInstanceType(instance_type), slow); | |
| Comment("load string character"); | |
| Node* length = LoadAndUntagObjectField(receiver, String::kLengthOffset); | |
| GotoIfNot(UintPtrLessThan(index, length), slow); | |
| IncrementCounter(isolate()->counters()->ic_keyed_load_generic_smi(), 1); | |
| TailCallBuiltin(Builtins::kStringCharAt, NoContextConstant(), receiver, | |
| index); | |
| } | |
| } | |
| void AccessorAssembler::GenericPropertyLoad(Node* receiver, Node* receiver_map, | |
| SloppyTNode<Int32T> instance_type, | |
| const LoadICParameters* p, | |
| Label* slow, | |
| UseStubCache use_stub_cache) { | |
| ExitPoint direct_exit(this); | |
| Comment("key is unique name"); | |
| Label if_found_on_receiver(this), if_property_dictionary(this), | |
| lookup_prototype_chain(this), special_receiver(this); | |
| VARIABLE(var_details, MachineRepresentation::kWord32); | |
| VARIABLE(var_value, MachineRepresentation::kTagged); | |
| // Receivers requiring non-standard accesses (interceptors, access | |
| // checks, strings and string wrappers) are handled in the runtime. | |
| GotoIf(IsSpecialReceiverInstanceType(instance_type), &special_receiver); | |
| // Check if the receiver has fast or slow properties. | |
| Node* bitfield3 = LoadMapBitField3(receiver_map); | |
| GotoIf(IsSetWord32<Map::IsDictionaryMapBit>(bitfield3), | |
| &if_property_dictionary); | |
| // Try looking up the property on the receiver; if unsuccessful, look | |
| // for a handler in the stub cache. | |
| TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map); | |
| Label if_descriptor_found(this), stub_cache(this); | |
| TVARIABLE(IntPtrT, var_name_index); | |
| Label* notfound = | |
| use_stub_cache == kUseStubCache ? &stub_cache : &lookup_prototype_chain; | |
| DescriptorLookup(p->name, descriptors, bitfield3, &if_descriptor_found, | |
| &var_name_index, notfound); | |
| BIND(&if_descriptor_found); | |
| { | |
| LoadPropertyFromFastObject(receiver, receiver_map, descriptors, | |
| var_name_index.value(), &var_details, | |
| &var_value); | |
| Goto(&if_found_on_receiver); | |
| } | |
| if (use_stub_cache == kUseStubCache) { | |
| BIND(&stub_cache); | |
| Comment("stub cache probe for fast property load"); | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label found_handler(this, &var_handler), stub_cache_miss(this); | |
| TryProbeStubCache(isolate()->load_stub_cache(), receiver, p->name, | |
| &found_handler, &var_handler, &stub_cache_miss); | |
| BIND(&found_handler); | |
| { | |
| HandleLoadICHandlerCase(p, CAST(var_handler.value()), &stub_cache_miss, | |
| &direct_exit); | |
| } | |
| BIND(&stub_cache_miss); | |
| { | |
| // TODO(jkummerow): Check if the property exists on the prototype | |
| // chain. If it doesn't, then there's no point in missing. | |
| Comment("KeyedLoadGeneric_miss"); | |
| TailCallRuntime(Runtime::kKeyedLoadIC_Miss, p->context, p->receiver, | |
| p->name, p->slot, p->vector); | |
| } | |
| } | |
| BIND(&if_property_dictionary); | |
| { | |
| Comment("dictionary property load"); | |
| // We checked for LAST_CUSTOM_ELEMENTS_RECEIVER before, which rules out | |
| // seeing global objects here (which would need special handling). | |
| TVARIABLE(IntPtrT, var_name_index); | |
| Label dictionary_found(this, &var_name_index); | |
| TNode<NameDictionary> properties = CAST(LoadSlowProperties(receiver)); | |
| NameDictionaryLookup<NameDictionary>(properties, CAST(p->name), | |
| &dictionary_found, &var_name_index, | |
| &lookup_prototype_chain); | |
| BIND(&dictionary_found); | |
| { | |
| LoadPropertyFromNameDictionary(properties, var_name_index.value(), | |
| &var_details, &var_value); | |
| Goto(&if_found_on_receiver); | |
| } | |
| } | |
| BIND(&if_found_on_receiver); | |
| { | |
| Node* value = CallGetterIfAccessor(var_value.value(), var_details.value(), | |
| p->context, receiver, slow); | |
| IncrementCounter(isolate()->counters()->ic_keyed_load_generic_symbol(), 1); | |
| Return(value); | |
| } | |
| BIND(&lookup_prototype_chain); | |
| { | |
| VARIABLE(var_holder_map, MachineRepresentation::kTagged); | |
| VARIABLE(var_holder_instance_type, MachineRepresentation::kWord32); | |
| Label return_undefined(this); | |
| Variable* merged_variables[] = {&var_holder_map, &var_holder_instance_type}; | |
| Label loop(this, arraysize(merged_variables), merged_variables); | |
| var_holder_map.Bind(receiver_map); | |
| var_holder_instance_type.Bind(instance_type); | |
| // Private symbols must not be looked up on the prototype chain. | |
| GotoIf(IsPrivateSymbol(p->name), &return_undefined); | |
| Goto(&loop); | |
| BIND(&loop); | |
| { | |
| // Bailout if it can be an integer indexed exotic case. | |
| GotoIf(InstanceTypeEqual(var_holder_instance_type.value(), | |
| JS_TYPED_ARRAY_TYPE), | |
| slow); | |
| Node* proto = LoadMapPrototype(var_holder_map.value()); | |
| GotoIf(WordEqual(proto, NullConstant()), &return_undefined); | |
| Node* proto_map = LoadMap(proto); | |
| Node* proto_instance_type = LoadMapInstanceType(proto_map); | |
| var_holder_map.Bind(proto_map); | |
| var_holder_instance_type.Bind(proto_instance_type); | |
| Label next_proto(this), return_value(this, &var_value), goto_slow(this); | |
| TryGetOwnProperty(p->context, receiver, proto, proto_map, | |
| proto_instance_type, p->name, &return_value, &var_value, | |
| &next_proto, &goto_slow); | |
| // This trampoline and the next are required to appease Turbofan's | |
| // variable merging. | |
| BIND(&next_proto); | |
| Goto(&loop); | |
| BIND(&goto_slow); | |
| Goto(slow); | |
| BIND(&return_value); | |
| Return(var_value.value()); | |
| } | |
| BIND(&return_undefined); | |
| Return(UndefinedConstant()); | |
| } | |
| BIND(&special_receiver); | |
| { | |
| // TODO(jkummerow): Consider supporting JSModuleNamespace. | |
| GotoIfNot(InstanceTypeEqual(instance_type, JS_PROXY_TYPE), slow); | |
| // Private field/symbol lookup is not supported. | |
| GotoIf(IsPrivateSymbol(p->name), slow); | |
| direct_exit.ReturnCallStub( | |
| Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty), | |
| p->context, receiver /*holder is the same as receiver*/, p->name, | |
| receiver, SmiConstant(OnNonExistent::kReturnUndefined)); | |
| } | |
| } | |
| //////////////////// Stub cache access helpers. | |
| enum AccessorAssembler::StubCacheTable : int { | |
| kPrimary = static_cast<int>(StubCache::kPrimary), | |
| kSecondary = static_cast<int>(StubCache::kSecondary) | |
| }; | |
| Node* AccessorAssembler::StubCachePrimaryOffset(Node* name, Node* map) { | |
| // See v8::internal::StubCache::PrimaryOffset(). | |
| STATIC_ASSERT(StubCache::kCacheIndexShift == Name::kHashShift); | |
| // Compute the hash of the name (use entire hash field). | |
| Node* hash_field = LoadNameHashField(name); | |
| CSA_ASSERT(this, | |
| Word32Equal(Word32And(hash_field, | |
| Int32Constant(Name::kHashNotComputedMask)), | |
| Int32Constant(0))); | |
| // Using only the low bits in 64-bit mode is unlikely to increase the | |
| // risk of collision even if the heap is spread over an area larger than | |
| // 4Gb (and not at all if it isn't). | |
| Node* map32 = TruncateIntPtrToInt32(BitcastTaggedToWord(map)); | |
| // Base the offset on a simple combination of name and map. | |
| Node* hash = Int32Add(hash_field, map32); | |
| uint32_t mask = (StubCache::kPrimaryTableSize - 1) | |
| << StubCache::kCacheIndexShift; | |
| return ChangeUint32ToWord(Word32And(hash, Int32Constant(mask))); | |
| } | |
| Node* AccessorAssembler::StubCacheSecondaryOffset(Node* name, Node* seed) { | |
| // See v8::internal::StubCache::SecondaryOffset(). | |
| // Use the seed from the primary cache in the secondary cache. | |
| Node* name32 = TruncateIntPtrToInt32(BitcastTaggedToWord(name)); | |
| Node* hash = Int32Sub(TruncateIntPtrToInt32(seed), name32); | |
| hash = Int32Add(hash, Int32Constant(StubCache::kSecondaryMagic)); | |
| int32_t mask = (StubCache::kSecondaryTableSize - 1) | |
| << StubCache::kCacheIndexShift; | |
| return ChangeUint32ToWord(Word32And(hash, Int32Constant(mask))); | |
| } | |
| void AccessorAssembler::TryProbeStubCacheTable( | |
| StubCache* stub_cache, StubCacheTable table_id, Node* entry_offset, | |
| Node* name, Node* map, Label* if_handler, | |
| TVariable<MaybeObject>* var_handler, Label* if_miss) { | |
| StubCache::Table table = static_cast<StubCache::Table>(table_id); | |
| #ifdef DEBUG | |
| if (FLAG_test_secondary_stub_cache && table == StubCache::kPrimary) { | |
| Goto(if_miss); | |
| return; | |
| } else if (FLAG_test_primary_stub_cache && table == StubCache::kSecondary) { | |
| Goto(if_miss); | |
| return; | |
| } | |
| #endif | |
| // The {table_offset} holds the entry offset times four (due to masking | |
| // and shifting optimizations). | |
| const int kMultiplier = sizeof(StubCache::Entry) >> Name::kHashShift; | |
| entry_offset = IntPtrMul(entry_offset, IntPtrConstant(kMultiplier)); | |
| // Check that the key in the entry matches the name. | |
| Node* key_base = ExternalConstant( | |
| ExternalReference::Create(stub_cache->key_reference(table))); | |
| Node* entry_key = Load(MachineType::Pointer(), key_base, entry_offset); | |
| GotoIf(WordNotEqual(name, entry_key), if_miss); | |
| // Get the map entry from the cache. | |
| DCHECK_EQ(kPointerSize * 2, stub_cache->map_reference(table).address() - | |
| stub_cache->key_reference(table).address()); | |
| Node* entry_map = | |
| Load(MachineType::Pointer(), key_base, | |
| IntPtrAdd(entry_offset, IntPtrConstant(kPointerSize * 2))); | |
| GotoIf(WordNotEqual(map, entry_map), if_miss); | |
| DCHECK_EQ(kPointerSize, stub_cache->value_reference(table).address() - | |
| stub_cache->key_reference(table).address()); | |
| TNode<MaybeObject> handler = ReinterpretCast<MaybeObject>( | |
| Load(MachineType::TaggedPointer(), key_base, | |
| IntPtrAdd(entry_offset, IntPtrConstant(kPointerSize)))); | |
| // We found the handler. | |
| *var_handler = handler; | |
| Goto(if_handler); | |
| } | |
| void AccessorAssembler::TryProbeStubCache(StubCache* stub_cache, Node* receiver, | |
| Node* name, Label* if_handler, | |
| TVariable<MaybeObject>* var_handler, | |
| Label* if_miss) { | |
| Label try_secondary(this), miss(this); | |
| Counters* counters = isolate()->counters(); | |
| IncrementCounter(counters->megamorphic_stub_cache_probes(), 1); | |
| // Check that the {receiver} isn't a smi. | |
| GotoIf(TaggedIsSmi(receiver), &miss); | |
| Node* receiver_map = LoadMap(receiver); | |
| // Probe the primary table. | |
| Node* primary_offset = StubCachePrimaryOffset(name, receiver_map); | |
| TryProbeStubCacheTable(stub_cache, kPrimary, primary_offset, name, | |
| receiver_map, if_handler, var_handler, &try_secondary); | |
| BIND(&try_secondary); | |
| { | |
| // Probe the secondary table. | |
| Node* secondary_offset = StubCacheSecondaryOffset(name, primary_offset); | |
| TryProbeStubCacheTable(stub_cache, kSecondary, secondary_offset, name, | |
| receiver_map, if_handler, var_handler, &miss); | |
| } | |
| BIND(&miss); | |
| { | |
| IncrementCounter(counters->megamorphic_stub_cache_misses(), 1); | |
| Goto(if_miss); | |
| } | |
| } | |
| //////////////////// Entry points into private implementation (one per stub). | |
| void AccessorAssembler::LoadIC_BytecodeHandler(const LoadICParameters* p, | |
| ExitPoint* exit_point) { | |
| // Must be kept in sync with LoadIC. | |
| // This function is hand-tuned to omit frame construction for common cases, | |
| // e.g.: monomorphic field and constant loads through smi handlers. | |
| // Polymorphic ICs with a hit in the first two entries also omit frames. | |
| // TODO(jgruber): Frame omission is fragile and can be affected by minor | |
| // changes in control flow and logic. We currently have no way of ensuring | |
| // that no frame is constructed, so it's easy to break this optimization by | |
| // accident. | |
| Label stub_call(this, Label::kDeferred), miss(this, Label::kDeferred); | |
| // Inlined fast path. | |
| { | |
| Comment("LoadIC_BytecodeHandler_fast"); | |
| Node* recv_map = LoadReceiverMap(p->receiver); | |
| GotoIf(IsDeprecatedMap(recv_map), &miss); | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label try_polymorphic(this), if_handler(this, &var_handler); | |
| TNode<MaybeObject> feedback = | |
| TryMonomorphicCase(p->slot, p->vector, recv_map, &if_handler, | |
| &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point); | |
| BIND(&try_polymorphic); | |
| { | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call); | |
| HandlePolymorphicCase(recv_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| } | |
| BIND(&stub_call); | |
| { | |
| Comment("LoadIC_BytecodeHandler_noninlined"); | |
| // Call into the stub that implements the non-inlined parts of LoadIC. | |
| Callable ic = | |
| Builtins::CallableFor(isolate(), Builtins::kLoadIC_Noninlined); | |
| Node* code_target = HeapConstant(ic.code()); | |
| exit_point->ReturnCallStub(ic.descriptor(), code_target, p->context, | |
| p->receiver, p->name, p->slot, p->vector); | |
| } | |
| BIND(&miss); | |
| { | |
| Comment("LoadIC_BytecodeHandler_miss"); | |
| exit_point->ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context, | |
| p->receiver, p->name, p->slot, p->vector); | |
| } | |
| } | |
| void AccessorAssembler::LoadIC(const LoadICParameters* p) { | |
| // Must be kept in sync with LoadIC_BytecodeHandler. | |
| ExitPoint direct_exit(this); | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler), non_inlined(this, Label::kDeferred), | |
| try_polymorphic(this), miss(this, Label::kDeferred); | |
| Node* receiver_map = LoadReceiverMap(p->receiver); | |
| GotoIf(IsDeprecatedMap(receiver_map), &miss); | |
| // Check monomorphic case. | |
| TNode<MaybeObject> feedback = | |
| TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler, | |
| &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit); | |
| BIND(&try_polymorphic); | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| { | |
| // Check polymorphic case. | |
| Comment("LoadIC_try_polymorphic"); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined); | |
| HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| BIND(&non_inlined); | |
| { | |
| LoadIC_Noninlined(p, receiver_map, strong_feedback, &var_handler, | |
| &if_handler, &miss, &direct_exit); | |
| } | |
| BIND(&miss); | |
| direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context, p->receiver, | |
| p->name, p->slot, p->vector); | |
| } | |
| void AccessorAssembler::LoadIC_Noninlined(const LoadICParameters* p, | |
| Node* receiver_map, | |
| TNode<HeapObject> feedback, | |
| TVariable<MaybeObject>* var_handler, | |
| Label* if_handler, Label* miss, | |
| ExitPoint* exit_point) { | |
| Label try_uninitialized(this, Label::kDeferred); | |
| // Neither deprecated map nor monomorphic. These cases are handled in the | |
| // bytecode handler. | |
| CSA_ASSERT(this, Word32BinaryNot(IsDeprecatedMap(receiver_map))); | |
| CSA_ASSERT(this, WordNotEqual(receiver_map, feedback)); | |
| CSA_ASSERT(this, Word32BinaryNot(IsWeakFixedArrayMap(LoadMap(feedback)))); | |
| DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep()); | |
| { | |
| // Check megamorphic case. | |
| GotoIfNot(WordEqual(feedback, LoadRoot(Heap::kmegamorphic_symbolRootIndex)), | |
| &try_uninitialized); | |
| TryProbeStubCache(isolate()->load_stub_cache(), p->receiver, p->name, | |
| if_handler, var_handler, miss); | |
| } | |
| BIND(&try_uninitialized); | |
| { | |
| // Check uninitialized case. | |
| GotoIfNot( | |
| WordEqual(feedback, LoadRoot(Heap::kuninitialized_symbolRootIndex)), | |
| miss); | |
| exit_point->ReturnCallStub( | |
| Builtins::CallableFor(isolate(), Builtins::kLoadIC_Uninitialized), | |
| p->context, p->receiver, p->name, p->slot, p->vector); | |
| } | |
| } | |
| void AccessorAssembler::LoadIC_Uninitialized(const LoadICParameters* p) { | |
| Label miss(this, Label::kDeferred); | |
| Node* receiver = p->receiver; | |
| GotoIf(TaggedIsSmi(receiver), &miss); | |
| Node* receiver_map = LoadMap(receiver); | |
| Node* instance_type = LoadMapInstanceType(receiver_map); | |
| // Optimistically write the state transition to the vector. | |
| StoreFeedbackVectorSlot(p->vector, p->slot, | |
| LoadRoot(Heap::kpremonomorphic_symbolRootIndex), | |
| SKIP_WRITE_BARRIER, 0, SMI_PARAMETERS); | |
| StoreWeakReferenceInFeedbackVector(p->vector, p->slot, receiver_map, | |
| kPointerSize, SMI_PARAMETERS); | |
| { | |
| // Special case for Function.prototype load, because it's very common | |
| // for ICs that are only executed once (MyFunc.prototype.foo = ...). | |
| Label not_function_prototype(this, Label::kDeferred); | |
| GotoIfNot(InstanceTypeEqual(instance_type, JS_FUNCTION_TYPE), | |
| ¬_function_prototype); | |
| GotoIfNot(IsPrototypeString(p->name), ¬_function_prototype); | |
| GotoIfPrototypeRequiresRuntimeLookup(CAST(receiver), CAST(receiver_map), | |
| ¬_function_prototype); | |
| Return(LoadJSFunctionPrototype(receiver, &miss)); | |
| BIND(¬_function_prototype); | |
| } | |
| GenericPropertyLoad(receiver, receiver_map, instance_type, p, &miss, | |
| kDontUseStubCache); | |
| BIND(&miss); | |
| { | |
| // Undo the optimistic state transition. | |
| StoreFeedbackVectorSlot(p->vector, p->slot, | |
| LoadRoot(Heap::kuninitialized_symbolRootIndex), | |
| SKIP_WRITE_BARRIER, 0, SMI_PARAMETERS); | |
| TailCallRuntime(Runtime::kLoadIC_Miss, p->context, p->receiver, p->name, | |
| p->slot, p->vector); | |
| } | |
| } | |
| void AccessorAssembler::LoadGlobalIC(TNode<FeedbackVector> vector, Node* slot, | |
| const LazyNode<Context>& lazy_context, | |
| const LazyNode<Name>& lazy_name, | |
| TypeofMode typeof_mode, | |
| ExitPoint* exit_point, | |
| ParameterMode slot_mode) { | |
| Label try_handler(this, Label::kDeferred), miss(this, Label::kDeferred); | |
| LoadGlobalIC_TryPropertyCellCase(vector, slot, lazy_context, exit_point, | |
| &try_handler, &miss, slot_mode); | |
| BIND(&try_handler); | |
| LoadGlobalIC_TryHandlerCase(vector, slot, lazy_context, lazy_name, | |
| typeof_mode, exit_point, &miss, slot_mode); | |
| BIND(&miss); | |
| { | |
| Comment("LoadGlobalIC_MissCase"); | |
| TNode<Context> context = lazy_context(); | |
| TNode<Name> name = lazy_name(); | |
| exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Miss, context, name, | |
| ParameterToTagged(slot, slot_mode), vector); | |
| } | |
| } | |
| void AccessorAssembler::LoadGlobalIC_TryPropertyCellCase( | |
| TNode<FeedbackVector> vector, Node* slot, | |
| const LazyNode<Context>& lazy_context, ExitPoint* exit_point, | |
| Label* try_handler, Label* miss, ParameterMode slot_mode) { | |
| Comment("LoadGlobalIC_TryPropertyCellCase"); | |
| Label if_lexical_var(this), if_property_cell(this); | |
| TNode<MaybeObject> maybe_weak_ref = | |
| LoadFeedbackVectorSlot(vector, slot, 0, slot_mode); | |
| Branch(TaggedIsSmi(maybe_weak_ref), &if_lexical_var, &if_property_cell); | |
| BIND(&if_property_cell); | |
| { | |
| // Load value or try handler case if the weak reference is cleared. | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_weak_ref)); | |
| TNode<PropertyCell> property_cell = | |
| CAST(ToWeakHeapObject(maybe_weak_ref, try_handler)); | |
| TNode<Object> value = | |
| LoadObjectField(property_cell, PropertyCell::kValueOffset); | |
| GotoIf(WordEqual(value, TheHoleConstant()), miss); | |
| exit_point->Return(value); | |
| } | |
| BIND(&if_lexical_var); | |
| { | |
| Comment("Load lexical variable"); | |
| TNode<IntPtrT> lexical_handler = SmiUntag(CAST(maybe_weak_ref)); | |
| TNode<IntPtrT> context_index = | |
| Signed(DecodeWord<FeedbackNexus::ContextIndexBits>(lexical_handler)); | |
| TNode<IntPtrT> slot_index = | |
| Signed(DecodeWord<FeedbackNexus::SlotIndexBits>(lexical_handler)); | |
| TNode<Context> context = lazy_context(); | |
| TNode<Context> script_context = LoadScriptContext(context, context_index); | |
| TNode<Object> result = LoadContextElement(script_context, slot_index); | |
| exit_point->Return(result); | |
| } | |
| } | |
| void AccessorAssembler::LoadGlobalIC_TryHandlerCase( | |
| TNode<FeedbackVector> vector, Node* slot, | |
| const LazyNode<Context>& lazy_context, const LazyNode<Name>& lazy_name, | |
| TypeofMode typeof_mode, ExitPoint* exit_point, Label* miss, | |
| ParameterMode slot_mode) { | |
| Comment("LoadGlobalIC_TryHandlerCase"); | |
| Label call_handler(this), non_smi(this); | |
| TNode<MaybeObject> feedback_element = | |
| LoadFeedbackVectorSlot(vector, slot, kPointerSize, slot_mode); | |
| TNode<Object> handler = CAST(feedback_element); | |
| GotoIf(WordEqual(handler, LoadRoot(Heap::kuninitialized_symbolRootIndex)), | |
| miss); | |
| OnNonExistent on_nonexistent = typeof_mode == NOT_INSIDE_TYPEOF | |
| ? OnNonExistent::kThrowReferenceError | |
| : OnNonExistent::kReturnUndefined; | |
| TNode<Context> context = lazy_context(); | |
| TNode<Context> native_context = LoadNativeContext(context); | |
| TNode<JSGlobalProxy> receiver = | |
| CAST(LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX)); | |
| Node* holder = LoadContextElement(native_context, Context::EXTENSION_INDEX); | |
| LoadICParameters p(context, receiver, lazy_name(), | |
| ParameterToTagged(slot, slot_mode), vector, holder); | |
| HandleLoadICHandlerCase(&p, handler, miss, exit_point, ICMode::kGlobalIC, | |
| on_nonexistent); | |
| } | |
| void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) { | |
| ExitPoint direct_exit(this); | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler), try_polymorphic(this, Label::kDeferred), | |
| try_megamorphic(this, Label::kDeferred), | |
| try_polymorphic_name(this, Label::kDeferred), | |
| miss(this, Label::kDeferred); | |
| Node* receiver_map = LoadReceiverMap(p->receiver); | |
| GotoIf(IsDeprecatedMap(receiver_map), &miss); | |
| // Check monomorphic case. | |
| TNode<MaybeObject> feedback = | |
| TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler, | |
| &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| { | |
| HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit, | |
| ICMode::kNonGlobalIC, | |
| OnNonExistent::kReturnUndefined, kSupportElements); | |
| } | |
| BIND(&try_polymorphic); | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| { | |
| // Check polymorphic case. | |
| Comment("KeyedLoadIC_try_polymorphic"); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); | |
| HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| BIND(&try_megamorphic); | |
| { | |
| // Check megamorphic case. | |
| Comment("KeyedLoadIC_try_megamorphic"); | |
| GotoIfNot(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)), | |
| &try_polymorphic_name); | |
| // TODO(jkummerow): Inline this? Or some of it? | |
| TailCallBuiltin(Builtins::kKeyedLoadIC_Megamorphic, p->context, p->receiver, | |
| p->name, p->slot, p->vector); | |
| } | |
| BIND(&try_polymorphic_name); | |
| { | |
| // We might have a name in feedback, and a weak fixed array in the next | |
| // slot. | |
| Node* name = p->name; | |
| Comment("KeyedLoadIC_try_polymorphic_name"); | |
| VARIABLE(var_name, MachineRepresentation::kTagged, name); | |
| VARIABLE(var_index, MachineType::PointerRepresentation()); | |
| Label if_polymorphic_name(this, &var_name), if_internalized(this), | |
| if_notinternalized(this, Label::kDeferred); | |
| // Fast-case: The recorded {feedback} matches the {name}. | |
| GotoIf(WordEqual(strong_feedback, name), &if_polymorphic_name); | |
| // Try to internalize the {name} if it isn't already. | |
| TryToName(name, &miss, &var_index, &if_internalized, &var_name, &miss, | |
| &if_notinternalized); | |
| BIND(&if_internalized); | |
| { | |
| // The {var_name} now contains a unique name. | |
| Branch(WordEqual(strong_feedback, var_name.value()), &if_polymorphic_name, | |
| &miss); | |
| } | |
| BIND(&if_notinternalized); | |
| { | |
| // Try to internalize the {name}. | |
| Node* function = ExternalConstant( | |
| ExternalReference::try_internalize_string_function()); | |
| Node* const isolate_ptr = | |
| ExternalConstant(ExternalReference::isolate_address(isolate())); | |
| var_name.Bind(CallCFunction2( | |
| MachineType::AnyTagged(), MachineType::Pointer(), | |
| MachineType::AnyTagged(), function, isolate_ptr, name)); | |
| Goto(&if_internalized); | |
| } | |
| BIND(&if_polymorphic_name); | |
| { | |
| // If the name comparison succeeded, we know we have a weak fixed array | |
| // with at least one map/handler pair. | |
| Node* name = var_name.value(); | |
| TailCallBuiltin(Builtins::kKeyedLoadIC_PolymorphicName, p->context, | |
| p->receiver, name, p->slot, p->vector); | |
| } | |
| } | |
| BIND(&miss); | |
| { | |
| Comment("KeyedLoadIC_miss"); | |
| TailCallRuntime(Runtime::kKeyedLoadIC_Miss, p->context, p->receiver, | |
| p->name, p->slot, p->vector); | |
| } | |
| } | |
| void AccessorAssembler::KeyedLoadICGeneric(const LoadICParameters* p) { | |
| VARIABLE(var_index, MachineType::PointerRepresentation()); | |
| VARIABLE(var_unique, MachineRepresentation::kTagged); | |
| var_unique.Bind(p->name); // Dummy initialization. | |
| Label if_index(this), if_unique_name(this), if_notunique(this), slow(this); | |
| Node* receiver = p->receiver; | |
| GotoIf(TaggedIsSmi(receiver), &slow); | |
| Node* receiver_map = LoadMap(receiver); | |
| Node* instance_type = LoadMapInstanceType(receiver_map); | |
| TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique, &slow, | |
| &if_notunique); | |
| BIND(&if_index); | |
| { | |
| GenericElementLoad(receiver, receiver_map, instance_type, var_index.value(), | |
| &slow); | |
| } | |
| BIND(&if_unique_name); | |
| { | |
| LoadICParameters pp = *p; | |
| pp.name = var_unique.value(); | |
| GenericPropertyLoad(receiver, receiver_map, instance_type, &pp, &slow); | |
| } | |
| BIND(&if_notunique); | |
| { | |
| if (FLAG_internalize_on_the_fly) { | |
| // Ideally we could return undefined directly here if the name is not | |
| // found in the string table, i.e. it was never internalized, but that | |
| // invariant doesn't hold with named property interceptors (at this | |
| // point), so we take the {slow} path instead. | |
| Label if_in_string_table(this); | |
| TryInternalizeString(p->name, &if_index, &var_index, &if_in_string_table, | |
| &var_unique, &slow, &slow); | |
| BIND(&if_in_string_table); | |
| { | |
| // TODO(bmeurer): We currently use a version of GenericPropertyLoad | |
| // here, where we don't try to probe the megamorphic stub cache after | |
| // successfully internalizing the incoming string. Past experiments | |
| // with this have shown that it causes too much traffic on the stub | |
| // cache. We may want to re-evaluate that in the future. | |
| LoadICParameters pp = *p; | |
| pp.name = var_unique.value(); | |
| GenericPropertyLoad(receiver, receiver_map, instance_type, &pp, &slow, | |
| kDontUseStubCache); | |
| } | |
| } else { | |
| Goto(&slow); | |
| } | |
| } | |
| BIND(&slow); | |
| { | |
| Comment("KeyedLoadGeneric_slow"); | |
| IncrementCounter(isolate()->counters()->ic_keyed_load_generic_slow(), 1); | |
| // TODO(jkummerow): Should we use the GetProperty TF stub instead? | |
| TailCallRuntime(Runtime::kKeyedGetProperty, p->context, p->receiver, | |
| p->name); | |
| } | |
| } | |
| void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p) { | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler), miss(this, Label::kDeferred); | |
| Node* receiver = p->receiver; | |
| Node* receiver_map = LoadReceiverMap(receiver); | |
| Node* name = p->name; | |
| Node* vector = p->vector; | |
| Node* slot = p->slot; | |
| Node* context = p->context; | |
| // When we get here, we know that the {name} matches the recorded | |
| // feedback name in the {vector} and can safely be used for the | |
| // LoadIC handler logic below. | |
| CSA_ASSERT(this, IsName(name)); | |
| CSA_ASSERT(this, Word32BinaryNot(IsDeprecatedMap(receiver_map))); | |
| CSA_ASSERT(this, WordEqual(name, CAST(LoadFeedbackVectorSlot( | |
| vector, slot, 0, SMI_PARAMETERS)))); | |
| // Check if we have a matching handler for the {receiver_map}. | |
| TNode<MaybeObject> feedback_element = | |
| LoadFeedbackVectorSlot(vector, slot, kPointerSize, SMI_PARAMETERS); | |
| TNode<WeakFixedArray> array = CAST(feedback_element); | |
| HandlePolymorphicCase(receiver_map, array, &if_handler, &var_handler, &miss, | |
| 1); | |
| BIND(&if_handler); | |
| { | |
| ExitPoint direct_exit(this); | |
| HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit, | |
| ICMode::kNonGlobalIC, | |
| OnNonExistent::kReturnUndefined, kOnlyProperties); | |
| } | |
| BIND(&miss); | |
| { | |
| Comment("KeyedLoadIC_miss"); | |
| TailCallRuntime(Runtime::kKeyedLoadIC_Miss, context, receiver, name, slot, | |
| vector); | |
| } | |
| } | |
| void AccessorAssembler::StoreIC(const StoreICParameters* p) { | |
| TVARIABLE(MaybeObject, var_handler, | |
| ReinterpretCast<MaybeObject>(SmiConstant(0))); | |
| Label if_handler(this, &var_handler), | |
| if_handler_from_stub_cache(this, &var_handler, Label::kDeferred), | |
| try_polymorphic(this, Label::kDeferred), | |
| try_megamorphic(this, Label::kDeferred), | |
| try_uninitialized(this, Label::kDeferred), miss(this, Label::kDeferred); | |
| Node* receiver_map = LoadReceiverMap(p->receiver); | |
| GotoIf(IsDeprecatedMap(receiver_map), &miss); | |
| // Check monomorphic case. | |
| TNode<MaybeObject> feedback = | |
| TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler, | |
| &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| { | |
| Comment("StoreIC_if_handler"); | |
| HandleStoreICHandlerCase(p, var_handler.value(), &miss, | |
| ICMode::kNonGlobalIC); | |
| } | |
| BIND(&try_polymorphic); | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| { | |
| // Check polymorphic case. | |
| Comment("StoreIC_try_polymorphic"); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); | |
| HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| BIND(&try_megamorphic); | |
| { | |
| // Check megamorphic case. | |
| GotoIfNot(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)), | |
| &try_uninitialized); | |
| TryProbeStubCache(isolate()->store_stub_cache(), p->receiver, p->name, | |
| &if_handler, &var_handler, &miss); | |
| } | |
| BIND(&try_uninitialized); | |
| { | |
| // Check uninitialized case. | |
| GotoIfNot(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kuninitialized_symbolRootIndex)), | |
| &miss); | |
| TailCallBuiltin(Builtins::kStoreIC_Uninitialized, p->context, p->receiver, | |
| p->name, p->value, p->slot, p->vector); | |
| } | |
| BIND(&miss); | |
| { | |
| TailCallRuntime(Runtime::kStoreIC_Miss, p->context, p->value, p->slot, | |
| p->vector, p->receiver, p->name); | |
| } | |
| } | |
| void AccessorAssembler::StoreGlobalIC(const StoreICParameters* pp) { | |
| Label if_lexical_var(this), if_property_cell(this); | |
| TNode<MaybeObject> maybe_weak_ref = | |
| LoadFeedbackVectorSlot(pp->vector, pp->slot, 0, SMI_PARAMETERS); | |
| Branch(TaggedIsSmi(maybe_weak_ref), &if_lexical_var, &if_property_cell); | |
| BIND(&if_property_cell); | |
| { | |
| Label try_handler(this), miss(this, Label::kDeferred); | |
| CSA_ASSERT(this, IsWeakOrClearedHeapObject(maybe_weak_ref)); | |
| TNode<PropertyCell> property_cell = | |
| CAST(ToWeakHeapObject(maybe_weak_ref, &try_handler)); | |
| ExitPoint direct_exit(this); | |
| StoreGlobalIC_PropertyCellCase(property_cell, pp->value, &direct_exit, | |
| &miss); | |
| BIND(&try_handler); | |
| { | |
| Comment("StoreGlobalIC_try_handler"); | |
| TNode<MaybeObject> handler = LoadFeedbackVectorSlot( | |
| pp->vector, pp->slot, kPointerSize, SMI_PARAMETERS); | |
| GotoIf(WordEqual(handler, LoadRoot(Heap::kuninitialized_symbolRootIndex)), | |
| &miss); | |
| StoreICParameters p = *pp; | |
| DCHECK_NULL(p.receiver); | |
| Node* native_context = LoadNativeContext(p.context); | |
| p.receiver = | |
| LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX); | |
| HandleStoreICHandlerCase(&p, handler, &miss, ICMode::kGlobalIC); | |
| } | |
| BIND(&miss); | |
| { | |
| TailCallRuntime(Runtime::kStoreGlobalIC_Miss, pp->context, pp->value, | |
| pp->slot, pp->vector, pp->name); | |
| } | |
| } | |
| BIND(&if_lexical_var); | |
| { | |
| Comment("Store lexical variable"); | |
| TNode<IntPtrT> lexical_handler = SmiUntag(CAST(maybe_weak_ref)); | |
| TNode<IntPtrT> context_index = | |
| Signed(DecodeWord<FeedbackNexus::ContextIndexBits>(lexical_handler)); | |
| TNode<IntPtrT> slot_index = | |
| Signed(DecodeWord<FeedbackNexus::SlotIndexBits>(lexical_handler)); | |
| TNode<Context> script_context = | |
| LoadScriptContext(CAST(pp->context), context_index); | |
| StoreContextElement(script_context, slot_index, pp->value); | |
| Return(pp->value); | |
| } | |
| } | |
| void AccessorAssembler::StoreGlobalIC_PropertyCellCase(Node* property_cell, | |
| Node* value, | |
| ExitPoint* exit_point, | |
| Label* miss) { | |
| Comment("StoreGlobalIC_TryPropertyCellCase"); | |
| CSA_ASSERT(this, IsPropertyCell(property_cell)); | |
| // Load the payload of the global parameter cell. A hole indicates that | |
| // the cell has been invalidated and that the store must be handled by the | |
| // runtime. | |
| Node* cell_contents = | |
| LoadObjectField(property_cell, PropertyCell::kValueOffset); | |
| Node* details = LoadAndUntagToWord32ObjectField(property_cell, | |
| PropertyCell::kDetailsOffset); | |
| GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask), miss); | |
| CSA_ASSERT(this, | |
| Word32Equal(DecodeWord32<PropertyDetails::KindField>(details), | |
| Int32Constant(kData))); | |
| Node* type = DecodeWord32<PropertyDetails::PropertyCellTypeField>(details); | |
| Label constant(this), store(this), not_smi(this); | |
| GotoIf(Word32Equal(type, Int32Constant( | |
| static_cast<int>(PropertyCellType::kConstant))), | |
| &constant); | |
| GotoIf(IsTheHole(cell_contents), miss); | |
| GotoIf(Word32Equal( | |
| type, Int32Constant(static_cast<int>(PropertyCellType::kMutable))), | |
| &store); | |
| CSA_ASSERT(this, | |
| Word32Or(Word32Equal(type, Int32Constant(static_cast<int>( | |
| PropertyCellType::kConstantType))), | |
| Word32Equal(type, Int32Constant(static_cast<int>( | |
| PropertyCellType::kUndefined))))); | |
| GotoIfNot(TaggedIsSmi(cell_contents), ¬_smi); | |
| GotoIfNot(TaggedIsSmi(value), miss); | |
| Goto(&store); | |
| BIND(¬_smi); | |
| { | |
| GotoIf(TaggedIsSmi(value), miss); | |
| Node* expected_map = LoadMap(cell_contents); | |
| Node* map = LoadMap(value); | |
| GotoIfNot(WordEqual(expected_map, map), miss); | |
| Goto(&store); | |
| } | |
| BIND(&store); | |
| { | |
| StoreObjectField(property_cell, PropertyCell::kValueOffset, value); | |
| exit_point->Return(value); | |
| } | |
| BIND(&constant); | |
| { | |
| GotoIfNot(WordEqual(cell_contents, value), miss); | |
| exit_point->Return(value); | |
| } | |
| } | |
| void AccessorAssembler::KeyedStoreIC(const StoreICParameters* p) { | |
| Label miss(this, Label::kDeferred); | |
| { | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler), | |
| try_polymorphic(this, Label::kDeferred), | |
| try_megamorphic(this, Label::kDeferred), | |
| try_polymorphic_name(this, Label::kDeferred); | |
| Node* receiver_map = LoadReceiverMap(p->receiver); | |
| GotoIf(IsDeprecatedMap(receiver_map), &miss); | |
| // Check monomorphic case. | |
| TNode<MaybeObject> feedback = | |
| TryMonomorphicCase(p->slot, p->vector, receiver_map, &if_handler, | |
| &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| { | |
| Comment("KeyedStoreIC_if_handler"); | |
| HandleStoreICHandlerCase(p, var_handler.value(), &miss, | |
| ICMode::kNonGlobalIC, kSupportElements); | |
| } | |
| BIND(&try_polymorphic); | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| { | |
| // CheckPolymorphic case. | |
| Comment("KeyedStoreIC_try_polymorphic"); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), | |
| &try_megamorphic); | |
| HandlePolymorphicCase(receiver_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| BIND(&try_megamorphic); | |
| { | |
| // Check megamorphic case. | |
| Comment("KeyedStoreIC_try_megamorphic"); | |
| GotoIfNot(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)), | |
| &try_polymorphic_name); | |
| TailCallBuiltin(Builtins::kKeyedStoreIC_Megamorphic, p->context, | |
| p->receiver, p->name, p->value, p->slot, p->vector); | |
| } | |
| BIND(&try_polymorphic_name); | |
| { | |
| // We might have a name in feedback, and a fixed array in the next slot. | |
| Comment("KeyedStoreIC_try_polymorphic_name"); | |
| GotoIfNot(WordEqual(strong_feedback, p->name), &miss); | |
| // If the name comparison succeeded, we know we have a feedback vector | |
| // with at least one map/handler pair. | |
| TNode<MaybeObject> feedback_element = LoadFeedbackVectorSlot( | |
| p->vector, p->slot, kPointerSize, SMI_PARAMETERS); | |
| TNode<WeakFixedArray> array = CAST(feedback_element); | |
| HandlePolymorphicCase(receiver_map, array, &if_handler, &var_handler, | |
| &miss, 1); | |
| } | |
| } | |
| BIND(&miss); | |
| { | |
| Comment("KeyedStoreIC_miss"); | |
| TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context, p->value, p->slot, | |
| p->vector, p->receiver, p->name); | |
| } | |
| } | |
| void AccessorAssembler::StoreInArrayLiteralIC(const StoreICParameters* p) { | |
| Label miss(this, Label::kDeferred); | |
| { | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler), | |
| try_polymorphic(this, Label::kDeferred), | |
| try_megamorphic(this, Label::kDeferred); | |
| Node* array_map = LoadReceiverMap(p->receiver); | |
| GotoIf(IsDeprecatedMap(array_map), &miss); | |
| TNode<MaybeObject> feedback = | |
| TryMonomorphicCase(p->slot, p->vector, array_map, &if_handler, | |
| &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| { | |
| Comment("StoreInArrayLiteralIC_if_handler"); | |
| // This is a stripped-down version of HandleStoreICHandlerCase. | |
| TNode<HeapObject> handler = CAST(var_handler.value()); | |
| Label if_transitioning_element_store(this); | |
| GotoIfNot(IsCode(handler), &if_transitioning_element_store); | |
| TailCallStub(StoreWithVectorDescriptor{}, CAST(handler), CAST(p->context), | |
| p->receiver, p->name, p->value, p->slot, p->vector); | |
| BIND(&if_transitioning_element_store); | |
| { | |
| TNode<MaybeObject> maybe_transition_map = | |
| LoadHandlerDataField(CAST(handler), 1); | |
| TNode<Map> transition_map = | |
| CAST(ToWeakHeapObject(maybe_transition_map, &miss)); | |
| GotoIf(IsDeprecatedMap(transition_map), &miss); | |
| Node* code = LoadObjectField(handler, StoreHandler::kSmiHandlerOffset); | |
| CSA_ASSERT(this, IsCode(code)); | |
| TailCallStub(StoreTransitionDescriptor{}, code, p->context, p->receiver, | |
| p->name, transition_map, p->value, p->slot, p->vector); | |
| } | |
| } | |
| BIND(&try_polymorphic); | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| { | |
| Comment("StoreInArrayLiteralIC_try_polymorphic"); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), | |
| &try_megamorphic); | |
| HandlePolymorphicCase(array_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| BIND(&try_megamorphic); | |
| { | |
| Comment("StoreInArrayLiteralIC_try_megamorphic"); | |
| CSA_ASSERT( | |
| this, | |
| Word32Or(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kuninitialized_symbolRootIndex)), | |
| WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)))); | |
| GotoIfNot(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)), | |
| &miss); | |
| TailCallRuntime(Runtime::kStoreInArrayLiteralIC_Slow, p->context, | |
| p->value, p->receiver, p->name); | |
| } | |
| } | |
| BIND(&miss); | |
| { | |
| Comment("StoreInArrayLiteralIC_miss"); | |
| // TODO(neis): Introduce Runtime::kStoreInArrayLiteralIC_Miss. | |
| TailCallRuntime(Runtime::kKeyedStoreIC_Miss, p->context, p->value, p->slot, | |
| p->vector, p->receiver, p->name); | |
| } | |
| } | |
| //////////////////// Public methods. | |
| void AccessorAssembler::GenerateLoadIC() { | |
| typedef LoadWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| LoadICParameters p(context, receiver, name, slot, vector); | |
| LoadIC(&p); | |
| } | |
| void AccessorAssembler::GenerateLoadIC_Noninlined() { | |
| typedef LoadWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| ExitPoint direct_exit(this); | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler), miss(this, Label::kDeferred); | |
| Node* receiver_map = LoadReceiverMap(receiver); | |
| TNode<MaybeObject> feedback_element = | |
| LoadFeedbackVectorSlot(vector, slot, 0, SMI_PARAMETERS); | |
| TNode<HeapObject> feedback = CAST(feedback_element); | |
| LoadICParameters p(context, receiver, name, slot, vector); | |
| LoadIC_Noninlined(&p, receiver_map, feedback, &var_handler, &if_handler, | |
| &miss, &direct_exit); | |
| BIND(&if_handler); | |
| HandleLoadICHandlerCase(&p, CAST(var_handler.value()), &miss, &direct_exit); | |
| BIND(&miss); | |
| direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, context, receiver, name, | |
| slot, vector); | |
| } | |
| void AccessorAssembler::GenerateLoadIC_Uninitialized() { | |
| typedef LoadWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| LoadICParameters p(context, receiver, name, slot, vector); | |
| LoadIC_Uninitialized(&p); | |
| } | |
| void AccessorAssembler::GenerateLoadICTrampoline() { | |
| typedef LoadDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* context = Parameter(Descriptor::kContext); | |
| Node* vector = LoadFeedbackVectorForStub(); | |
| TailCallBuiltin(Builtins::kLoadIC, context, receiver, name, slot, vector); | |
| } | |
| void AccessorAssembler::GenerateLoadGlobalIC(TypeofMode typeof_mode) { | |
| typedef LoadGlobalWithVectorDescriptor Descriptor; | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| ExitPoint direct_exit(this); | |
| LoadGlobalIC(CAST(vector), slot, | |
| // lazy_context | |
| [=] { return CAST(context); }, | |
| // lazy_name | |
| [=] { return CAST(name); }, typeof_mode, &direct_exit); | |
| } | |
| void AccessorAssembler::GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode) { | |
| typedef LoadGlobalDescriptor Descriptor; | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* context = Parameter(Descriptor::kContext); | |
| Node* vector = LoadFeedbackVectorForStub(); | |
| Callable callable = | |
| CodeFactory::LoadGlobalICInOptimizedCode(isolate(), typeof_mode); | |
| TailCallStub(callable, context, name, slot, vector); | |
| } | |
| void AccessorAssembler::GenerateKeyedLoadIC() { | |
| typedef LoadWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| LoadICParameters p(context, receiver, name, slot, vector); | |
| KeyedLoadIC(&p); | |
| } | |
| void AccessorAssembler::GenerateKeyedLoadICTrampoline() { | |
| typedef LoadDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* context = Parameter(Descriptor::kContext); | |
| Node* vector = LoadFeedbackVectorForStub(); | |
| TailCallBuiltin(Builtins::kKeyedLoadIC, context, receiver, name, slot, | |
| vector); | |
| } | |
| void AccessorAssembler::GenerateKeyedLoadIC_Megamorphic() { | |
| typedef LoadWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| LoadICParameters p(context, receiver, name, slot, vector); | |
| KeyedLoadICGeneric(&p); | |
| } | |
| void AccessorAssembler::GenerateKeyedLoadIC_PolymorphicName() { | |
| typedef LoadWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| LoadICParameters p(context, receiver, name, slot, vector); | |
| KeyedLoadICPolymorphicName(&p); | |
| } | |
| void AccessorAssembler::GenerateStoreGlobalIC() { | |
| typedef StoreGlobalWithVectorDescriptor Descriptor; | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| StoreICParameters p(context, nullptr, name, value, slot, vector); | |
| StoreGlobalIC(&p); | |
| } | |
| void AccessorAssembler::GenerateStoreGlobalICTrampoline() { | |
| typedef StoreGlobalDescriptor Descriptor; | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* context = Parameter(Descriptor::kContext); | |
| Node* vector = LoadFeedbackVectorForStub(); | |
| TailCallBuiltin(Builtins::kStoreGlobalIC, context, name, value, slot, vector); | |
| } | |
| void AccessorAssembler::GenerateStoreIC() { | |
| typedef StoreWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| StoreICParameters p(context, receiver, name, value, slot, vector); | |
| StoreIC(&p); | |
| } | |
| void AccessorAssembler::GenerateStoreICTrampoline() { | |
| typedef StoreDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* context = Parameter(Descriptor::kContext); | |
| Node* vector = LoadFeedbackVectorForStub(); | |
| TailCallBuiltin(Builtins::kStoreIC, context, receiver, name, value, slot, | |
| vector); | |
| } | |
| void AccessorAssembler::GenerateKeyedStoreIC() { | |
| typedef StoreWithVectorDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| StoreICParameters p(context, receiver, name, value, slot, vector); | |
| KeyedStoreIC(&p); | |
| } | |
| void AccessorAssembler::GenerateKeyedStoreICTrampoline() { | |
| typedef StoreDescriptor Descriptor; | |
| Node* receiver = Parameter(Descriptor::kReceiver); | |
| Node* name = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* context = Parameter(Descriptor::kContext); | |
| Node* vector = LoadFeedbackVectorForStub(); | |
| TailCallBuiltin(Builtins::kKeyedStoreIC, context, receiver, name, value, slot, | |
| vector); | |
| } | |
| void AccessorAssembler::GenerateStoreInArrayLiteralIC() { | |
| typedef StoreWithVectorDescriptor Descriptor; | |
| Node* array = Parameter(Descriptor::kReceiver); | |
| Node* index = Parameter(Descriptor::kName); | |
| Node* value = Parameter(Descriptor::kValue); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| StoreICParameters p(context, array, index, value, slot, vector); | |
| StoreInArrayLiteralIC(&p); | |
| } | |
| void AccessorAssembler::GenerateCloneObjectIC() { | |
| typedef CloneObjectWithVectorDescriptor Descriptor; | |
| Node* source = Parameter(Descriptor::kSource); | |
| Node* flags = Parameter(Descriptor::kFlags); | |
| Node* slot = Parameter(Descriptor::kSlot); | |
| Node* vector = Parameter(Descriptor::kVector); | |
| Node* context = Parameter(Descriptor::kContext); | |
| TVARIABLE(MaybeObject, var_handler); | |
| Label if_handler(this, &var_handler); | |
| Label miss(this, Label::kDeferred), try_polymorphic(this, Label::kDeferred), | |
| try_megamorphic(this, Label::kDeferred); | |
| CSA_SLOW_ASSERT(this, TaggedIsNotSmi(source)); | |
| Node* source_map = LoadMap(UncheckedCast<HeapObject>(source)); | |
| GotoIf(IsDeprecatedMap(source_map), &miss); | |
| TNode<MaybeObject> feedback = TryMonomorphicCase( | |
| slot, vector, source_map, &if_handler, &var_handler, &try_polymorphic); | |
| BIND(&if_handler); | |
| { | |
| Comment("CloneObjectIC_if_handler"); | |
| // Handlers for the CloneObjectIC stub are weak references to the Map of | |
| // a result object. | |
| TNode<Map> result_map = CAST(var_handler.value()); | |
| TVARIABLE(Object, var_properties, EmptyFixedArrayConstant()); | |
| TVARIABLE(FixedArrayBase, var_elements, EmptyFixedArrayConstant()); | |
| Label allocate_object(this); | |
| GotoIf(IsNullOrUndefined(source), &allocate_object); | |
| CSA_SLOW_ASSERT(this, IsJSObjectMap(result_map)); | |
| // The IC fast case should only be taken if the result map a compatible | |
| // elements kind with the source object. | |
| TNode<FixedArrayBase> source_elements = LoadElements(source); | |
| auto flags = ExtractFixedArrayFlag::kAllFixedArraysDontCopyCOW; | |
| var_elements = CAST(CloneFixedArray(source_elements, flags)); | |
| // Copy the PropertyArray backing store. The source PropertyArray must be | |
| // either an Smi, or a PropertyArray. | |
| // FIXME: Make a CSA macro for this | |
| TNode<Object> source_properties = | |
| LoadObjectField(source, JSObject::kPropertiesOrHashOffset); | |
| { | |
| GotoIf(TaggedIsSmi(source_properties), &allocate_object); | |
| GotoIf(IsEmptyFixedArray(source_properties), &allocate_object); | |
| // This IC requires that the source object has fast properties | |
| CSA_SLOW_ASSERT(this, IsPropertyArray(CAST(source_properties))); | |
| TNode<IntPtrT> length = LoadPropertyArrayLength( | |
| UncheckedCast<PropertyArray>(source_properties)); | |
| GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &allocate_object); | |
| auto mode = INTPTR_PARAMETERS; | |
| var_properties = CAST(AllocatePropertyArray(length, mode)); | |
| CopyPropertyArrayValues(source_properties, var_properties.value(), length, | |
| SKIP_WRITE_BARRIER, mode); | |
| } | |
| Goto(&allocate_object); | |
| BIND(&allocate_object); | |
| TNode<JSObject> object = UncheckedCast<JSObject>(AllocateJSObjectFromMap( | |
| result_map, var_properties.value(), var_elements.value())); | |
| ReturnIf(IsNullOrUndefined(source), object); | |
| // Lastly, clone any in-object properties. | |
| // Determine the inobject property capacity of both objects, and copy the | |
| // smaller number into the resulting object. | |
| Node* source_start = LoadMapInobjectPropertiesStartInWords(source_map); | |
| Node* source_size = LoadMapInstanceSizeInWords(source_map); | |
| Node* result_start = LoadMapInobjectPropertiesStartInWords(result_map); | |
| Node* field_offset_difference = | |
| TimesPointerSize(IntPtrSub(result_start, source_start)); | |
| BuildFastLoop(source_start, source_size, | |
| [=](Node* field_index) { | |
| Node* field_offset = TimesPointerSize(field_index); | |
| Node* field = LoadObjectField(source, field_offset); | |
| Node* result_offset = | |
| IntPtrAdd(field_offset, field_offset_difference); | |
| StoreObjectFieldNoWriteBarrier(object, result_offset, | |
| field); | |
| }, | |
| 1, INTPTR_PARAMETERS, IndexAdvanceMode::kPost); | |
| Return(object); | |
| } | |
| BIND(&try_polymorphic); | |
| TNode<HeapObject> strong_feedback = ToStrongHeapObject(feedback, &miss); | |
| { | |
| Comment("CloneObjectIC_try_polymorphic"); | |
| GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); | |
| HandlePolymorphicCase(source_map, CAST(strong_feedback), &if_handler, | |
| &var_handler, &miss, 2); | |
| } | |
| BIND(&try_megamorphic); | |
| { | |
| Comment("CloneObjectIC_try_megamorphic"); | |
| CSA_ASSERT( | |
| this, | |
| Word32Or(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kuninitialized_symbolRootIndex)), | |
| WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)))); | |
| GotoIfNot(WordEqual(strong_feedback, | |
| LoadRoot(Heap::kmegamorphic_symbolRootIndex)), | |
| &miss); | |
| TailCallRuntime(Runtime::kCloneObjectIC_Slow, context, source, flags); | |
| } | |
| BIND(&miss); | |
| { | |
| Comment("CloneObjectIC_miss"); | |
| Node* map_or_result = CallRuntime(Runtime::kCloneObjectIC_Miss, context, | |
| source, flags, slot, vector); | |
| var_handler = UncheckedCast<MaybeObject>(map_or_result); | |
| GotoIf(IsMap(map_or_result), &if_handler); | |
| CSA_ASSERT(this, IsJSObject(map_or_result)); | |
| Return(map_or_result); | |
| } | |
| } | |
| } // namespace internal | |
| } // namespace v8 |