// 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/ast/ast.h" #include "src/base/optional.h" #include "src/builtins/builtins-constructor-gen.h" #include "src/codegen/code-factory.h" #include "src/ic/handler-configuration.h" #include "src/ic/ic.h" #include "src/ic/keyed-store-generic.h" #include "src/ic/stub-cache.h" #include "src/logging/counters.h" #include "src/objects/cell.h" #include "src/objects/foreign.h" #include "src/objects/heap-number.h" #include "src/objects/module.h" #include "src/objects/objects-inl.h" #include "src/objects/property-details.h" #include "src/objects/smi.h" namespace v8 { namespace internal { using compiler::CodeAssemblerState; using compiler::Node; //////////////////// Private helpers. // Loads dataX field from the DataHandler object. TNode AccessorAssembler::LoadHandlerDataField( TNode handler, int data_index) { #ifdef DEBUG TNode handler_map = LoadMap(handler); TNode 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(); } USE(minimum_size); CSA_ASSERT(this, UintPtrGreaterThanOrEqual( LoadMapInstanceSizeInWords(handler_map), IntPtrConstant(minimum_size / kTaggedSize))); return LoadMaybeWeakObjectField(handler, offset); } TNode AccessorAssembler::TryMonomorphicCase( TNode slot, TNode vector, TNode lookup_start_object_map, Label* if_handler, TVariable* 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. int32_t header_size = FeedbackVector::kRawFeedbackSlotsOffset - 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. TNode offset = ElementOffsetFromIndex(slot, HOLEY_ELEMENTS); TNode feedback = ReinterpretCast( 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. GotoIfNot(IsWeakReferenceTo(feedback, lookup_start_object_map), if_miss); TNode handler = UncheckedCast( Load(MachineType::AnyTagged(), vector, IntPtrAdd(offset, IntPtrConstant(header_size + kTaggedSize)))); *var_handler = handler; Goto(if_handler); return feedback; } void AccessorAssembler::HandlePolymorphicCase( TNode lookup_start_object_map, TNode feedback, Label* if_handler, TVariable* var_handler, Label* if_miss) { Comment("HandlePolymorphicCase"); DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep()); // Iterate {feedback} array. const int kEntrySize = 2; // Load the {feedback} array length. TNode length = LoadAndUntagWeakFixedArrayLength(feedback); CSA_ASSERT(this, IntPtrLessThanOrEqual(IntPtrConstant(kEntrySize), length)); // This is a hand-crafted loop that iterates backwards and only compares // against zero at the end, since we already know that we will have at least a // single entry in the {feedback} array anyways. TVARIABLE(IntPtrT, var_index, IntPtrSub(length, IntPtrConstant(kEntrySize))); Label loop(this, &var_index), loop_next(this); Goto(&loop); BIND(&loop); { TNode maybe_cached_map = LoadWeakFixedArrayElement(feedback, var_index.value()); CSA_ASSERT(this, IsWeakOrCleared(maybe_cached_map)); GotoIfNot(IsWeakReferenceTo(maybe_cached_map, lookup_start_object_map), &loop_next); // Found, now call handler. TNode handler = LoadWeakFixedArrayElement(feedback, var_index.value(), kTaggedSize); *var_handler = handler; Goto(if_handler); BIND(&loop_next); var_index = Signed(IntPtrSub(var_index.value(), IntPtrConstant(kEntrySize))); Branch(IntPtrGreaterThanOrEqual(var_index.value(), IntPtrConstant(0)), &loop, if_miss); } } void AccessorAssembler::HandleLoadICHandlerCase( const LazyLoadICParameters* p, TNode handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent, ElementSupport support_elements, LoadAccessMode access_mode) { Comment("have_handler"); TVARIABLE(Object, var_holder, p->lookup_start_object()); TVARIABLE(Object, var_smi_handler, handler); Label if_smi_handler(this, {&var_holder, &var_smi_handler}); Label try_proto_handler(this, Label::kDeferred), call_handler(this, Label::kDeferred); Branch(TaggedIsSmi(handler), &if_smi_handler, &try_proto_handler); BIND(&try_proto_handler); { GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler); HandleLoadICProtoHandler(p, CAST(handler), &var_holder, &var_smi_handler, &if_smi_handler, miss, exit_point, ic_mode, access_mode); } // |handler| is a Smi, encoding what to do. See SmiHandler methods // for the encoding format. BIND(&if_smi_handler); { HandleLoadICSmiHandlerCase( p, var_holder.value(), CAST(var_smi_handler.value()), handler, miss, exit_point, ic_mode, on_nonexistent, support_elements, access_mode); } BIND(&call_handler); { exit_point->ReturnCallStub(LoadWithVectorDescriptor{}, CAST(handler), p->context(), p->receiver(), p->name(), p->slot(), p->vector()); } } void AccessorAssembler::HandleLoadCallbackProperty( const LazyLoadICParameters* p, TNode holder, TNode handler_word, ExitPoint* exit_point) { Comment("native_data_property_load"); TNode descriptor = Signed(DecodeWord(handler_word)); Callable callable = CodeFactory::ApiGetter(isolate()); TNode accessor_info = CAST(LoadDescriptorValue(LoadMap(holder), descriptor)); exit_point->ReturnCallStub(callable, p->context(), p->receiver(), holder, accessor_info); } void AccessorAssembler::HandleLoadAccessor( const LazyLoadICParameters* p, TNode call_handler_info, TNode handler_word, TNode handler, TNode handler_kind, ExitPoint* exit_point) { Comment("api_getter"); // Context is stored either in data2 or data3 field depending on whether // the access check is enabled for this handler or not. TNode maybe_context = Select( IsSetWord( handler_word), [=] { return LoadHandlerDataField(handler, 3); }, [=] { return LoadHandlerDataField(handler, 2); }); CSA_ASSERT(this, IsWeakOrCleared(maybe_context)); CSA_CHECK(this, IsNotCleared(maybe_context)); TNode context = GetHeapObjectAssumeWeak(maybe_context); TNode foreign = LoadObjectField( call_handler_info, CallHandlerInfo::kJsCallbackOffset); TNode callback = LoadForeignForeignAddressPtr(foreign); TNode data = LoadObjectField(call_handler_info, CallHandlerInfo::kDataOffset); TVARIABLE(HeapObject, api_holder, CAST(p->lookup_start_object())); Label load(this); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)), &load); CSA_ASSERT( this, WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype))); api_holder = LoadMapPrototype(LoadMap(CAST(p->lookup_start_object()))); Goto(&load); BIND(&load); TNode argc = IntPtrConstant(0); exit_point->Return(CallApiCallback(context, callback, argc, data, api_holder.value(), p->receiver())); } void AccessorAssembler::HandleLoadField(TNode holder, TNode handler_word, TVariable* var_double_value, Label* rebox_double, Label* miss, ExitPoint* exit_point) { Comment("field_load"); TNode index = Signed(DecodeWord(handler_word)); TNode offset = IntPtrMul(index, IntPtrConstant(kTaggedSize)); Label inobject(this), out_of_object(this); Branch(IsSetWord(handler_word), &inobject, &out_of_object); BIND(&inobject); { Label is_double(this); GotoIf(IsSetWord(handler_word), &is_double); exit_point->Return(LoadObjectField(holder, offset)); BIND(&is_double); if (FLAG_unbox_double_fields) { *var_double_value = LoadObjectField(holder, offset); } else { TNode heap_number = LoadObjectField(holder, offset); // This is not an "old" Smi value from before a Smi->Double transition. // Rather, it's possible that since the last update of this IC, the Double // field transitioned to a Tagged field, and was then assigned a Smi. GotoIf(TaggedIsSmi(heap_number), miss); GotoIfNot(IsHeapNumber(CAST(heap_number)), miss); *var_double_value = LoadHeapNumberValue(CAST(heap_number)); } Goto(rebox_double); } BIND(&out_of_object); { Label is_double(this); TNode properties = LoadFastProperties(holder); TNode value = LoadObjectField(properties, offset); GotoIf(IsSetWord(handler_word), &is_double); exit_point->Return(value); BIND(&is_double); if (!FLAG_unbox_double_fields) { // This is not an "old" Smi value from before a Smi->Double transition. // Rather, it's possible that since the last update of this IC, the Double // field transitioned to a Tagged field, and was then assigned a Smi. GotoIf(TaggedIsSmi(value), miss); GotoIfNot(IsHeapNumber(CAST(value)), miss); } *var_double_value = LoadHeapNumberValue(CAST(value)); Goto(rebox_double); } } TNode AccessorAssembler::LoadDescriptorValue( TNode map, TNode descriptor_entry) { return CAST(LoadDescriptorValueOrFieldType(map, descriptor_entry)); } TNode AccessorAssembler::LoadDescriptorValueOrFieldType( TNode map, TNode descriptor_entry) { TNode descriptors = LoadMapDescriptors(map); return LoadFieldTypeByDescriptorEntry(descriptors, descriptor_entry); } void AccessorAssembler::HandleLoadICSmiHandlerCase( const LazyLoadICParameters* p, TNode holder, TNode smi_handler, TNode handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent, ElementSupport support_elements, LoadAccessMode access_mode) { TVARIABLE(Float64T, var_double_value); Label rebox_double(this, &var_double_value); TNode handler_word = SmiUntag(smi_handler); TNode handler_kind = Signed(DecodeWord(handler_word)); if (support_elements == kSupportElements) { Label if_element(this), if_indexed_string(this), if_property(this), if_hole(this), unimplemented_elements_kind(this), if_oob(this, Label::kDeferred), try_string_to_array_index(this), emit_element_load(this); TVARIABLE(IntPtrT, var_intptr_index); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kElement)), &if_element); if (access_mode == LoadAccessMode::kHas) { CSA_ASSERT(this, WordNotEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString))); Goto(&if_property); } else { Branch( WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)), &if_indexed_string, &if_property); } BIND(&if_element); { Comment("element_load"); TVARIABLE(Int32T, var_instance_type); TNode intptr_index = TryToIntptr( p->name(), &try_string_to_array_index, &var_instance_type); var_intptr_index = intptr_index; Goto(&emit_element_load); BIND(&try_string_to_array_index); { GotoIfNot(IsStringInstanceType(var_instance_type.value()), miss); TNode function = ExternalConstant( ExternalReference::string_to_array_index_function()); TNode result = UncheckedCast( CallCFunction(function, MachineType::Int32(), std::make_pair(MachineType::AnyTagged(), p->name()))); GotoIf(Word32Equal(Int32Constant(-1), result), miss); CSA_ASSERT(this, Int32GreaterThanOrEqual(result, Int32Constant(0))); var_intptr_index = ChangeInt32ToIntPtr(result); Goto(&emit_element_load); } BIND(&emit_element_load); { TNode is_jsarray_condition = IsSetWord(handler_word); TNode elements_kind = DecodeWord32FromWord(handler_word); EmitElementLoad(CAST(holder), elements_kind, var_intptr_index.value(), is_jsarray_condition, &if_hole, &rebox_double, &var_double_value, &unimplemented_elements_kind, &if_oob, miss, exit_point, access_mode); } } 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. TNode allow_out_of_bounds = IsSetWord(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(CAST(holder)), &return_undefined); if (Is64()) { GotoIfNot( UintPtrLessThanOrEqual(var_intptr_index.value(), IntPtrConstant(JSArray::kMaxArrayIndex)), miss); } else { GotoIf(IntPtrLessThan(var_intptr_index.value(), IntPtrConstant(0)), miss); } // For all other receivers we need to check that the prototype chain // doesn't contain any elements. BranchIfPrototypesHaveNoElements(LoadMap(CAST(holder)), &return_undefined, miss); BIND(&return_undefined); exit_point->Return(access_mode == LoadAccessMode::kHas ? FalseConstant() : UndefinedConstant()); } BIND(&if_hole); { Comment("convert hole"); GotoIfNot(IsSetWord(handler_word), miss); GotoIf(IsNoElementsProtectorCellInvalid(), miss); exit_point->Return(access_mode == LoadAccessMode::kHas ? FalseConstant() : UndefinedConstant()); } if (access_mode != LoadAccessMode::kHas) { BIND(&if_indexed_string); { Label if_oob(this, Label::kDeferred); Comment("indexed string"); TNode string_holder = CAST(holder); TNode index = Unsigned(TryToIntptr(p->name(), miss)); TNode length = Unsigned(LoadStringLengthAsWord(string_holder)); GotoIf(UintPtrGreaterThanOrEqual(index, length), &if_oob); TNode code = StringCharCodeAt(string_holder, index); TNode result = StringFromSingleCharCode(code); Return(result); BIND(&if_oob); TNode allow_out_of_bounds = IsSetWord(handler_word); GotoIfNot(allow_out_of_bounds, miss); GotoIf(IsNoElementsProtectorCellInvalid(), miss); Return(UndefinedConstant()); } } BIND(&if_property); Comment("property_load"); } if (access_mode == LoadAccessMode::kHas) { HandleLoadICSmiHandlerHasNamedCase(p, holder, handler_kind, miss, exit_point, ic_mode); } else { HandleLoadICSmiHandlerLoadNamedCase( p, holder, handler_kind, handler_word, &rebox_double, &var_double_value, handler, miss, exit_point, ic_mode, on_nonexistent, support_elements); } } void AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase( const LazyLoadICParameters* p, TNode holder, TNode handler_kind, TNode handler_word, Label* rebox_double, TVariable* var_double_value, TNode handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent, ElementSupport support_elements) { Label constant(this), field(this), normal(this, Label::kDeferred), slow(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, Label::kDeferred), api_getter(this, Label::kDeferred); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &field); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstantFromPrototype)), &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::kSlow)), &slow); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), &proxy); Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kModuleExport)), &module_export, &interceptor); BIND(&field); HandleLoadField(CAST(holder), handler_word, var_double_value, rebox_double, miss, 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"); exit_point->Return(holder); } BIND(&normal); { Comment("load_normal"); TNode properties = CAST(LoadSlowProperties(CAST(holder))); TVARIABLE(IntPtrT, var_name_index); Label found(this, &var_name_index); NameDictionaryLookup(properties, CAST(p->name()), &found, &var_name_index, miss); BIND(&found); { TVARIABLE(Uint32T, var_details); TVARIABLE(Object, var_value); LoadPropertyFromNameDictionary(properties, var_name_index.value(), &var_details, &var_value); TNode value = CallGetterIfAccessor( var_value.value(), CAST(holder), var_details.value(), p->context(), p->receiver(), miss); exit_point->Return(value); } } BIND(&accessor); { Comment("accessor_load"); TNode descriptor = Signed(DecodeWord(handler_word)); TNode accessor_pair = CAST(LoadDescriptorValue(LoadMap(CAST(holder)), descriptor)); TNode getter = LoadObjectField(accessor_pair, AccessorPair::kGetterOffset); CSA_ASSERT(this, Word32BinaryNot(IsTheHole(getter))); exit_point->Return(Call(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); { // TODO(mythria): LoadGlobals don't use this path. LoadGlobals need special // handling with proxies which is currently not supported by builtins. So // for such cases, we should install a slow path and never reach here. Fix // it to not generate this for LoadGlobals. CSA_ASSERT(this, WordNotEqual(IntPtrConstant(static_cast(on_nonexistent)), IntPtrConstant(static_cast( OnNonExistent::kThrowReferenceError)))); TVARIABLE(IntPtrT, var_index); TVARIABLE(Name, var_unique); 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); // TODO(duongn): use GetPropertyWithReceiver builtin once // |lookup_element_in_holder| supports elements. 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(CAST(holder))); // Ensure the property cell doesn't contain the hole. TNode value = LoadObjectField(CAST(holder), PropertyCell::kValueOffset); TNode details = Unsigned(LoadAndUntagToWord32ObjectField( CAST(holder), PropertyCell::kPropertyDetailsRawOffset)); GotoIf(IsTheHole(value), miss); exit_point->Return(CallGetterIfAccessor(value, CAST(holder), 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(&slow); { Comment("load_slow"); if (ic_mode == ICMode::kGlobalIC) { exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Slow, p->context(), p->name(), p->slot(), p->vector()); } else { exit_point->ReturnCallRuntime(Runtime::kGetProperty, p->context(), holder, p->name(), p->receiver()); } } BIND(&module_export); { Comment("module export"); TNode index = DecodeWord(handler_word); TNode module = LoadObjectField( CAST(p->receiver()), JSModuleNamespace::kModuleOffset); TNode exports = LoadObjectField(module, Module::kExportsOffset); TNode cell = CAST(LoadFixedArrayElement(exports, index)); // The handler is only installed for exports that exist. TNode value = LoadCellValue(cell); Label is_the_hole(this, Label::kDeferred); GotoIf(IsTheHole(value), &is_the_hole); exit_point->Return(value); BIND(&is_the_hole); { TNode 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())); } void AccessorAssembler::HandleLoadICSmiHandlerHasNamedCase( const LazyLoadICParameters* p, TNode holder, TNode handler_kind, Label* miss, ExitPoint* exit_point, ICMode ic_mode) { Label return_true(this), return_false(this), return_lookup(this), normal(this), global(this), slow(this); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &return_true); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstantFromPrototype)), &return_true); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)), &return_false); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)), &normal); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)), &return_true); GotoIf( WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)), &return_true); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)), &return_true); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)), &return_true); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kSlow)), &slow); Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)), &global, &return_lookup); BIND(&return_true); exit_point->Return(TrueConstant()); BIND(&return_false); exit_point->Return(FalseConstant()); BIND(&return_lookup); { CSA_ASSERT( this, Word32Or( WordEqual(handler_kind, IntPtrConstant(LoadHandler::kInterceptor)), Word32Or( WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), WordEqual(handler_kind, IntPtrConstant(LoadHandler::kModuleExport))))); exit_point->ReturnCallStub( Builtins::CallableFor(isolate(), Builtins::kHasProperty), p->context(), p->receiver(), p->name()); } BIND(&normal); { Comment("has_normal"); TNode properties = CAST(LoadSlowProperties(CAST(holder))); TVARIABLE(IntPtrT, var_name_index); Label found(this); NameDictionaryLookup(properties, CAST(p->name()), &found, &var_name_index, miss); BIND(&found); exit_point->Return(TrueConstant()); } BIND(&global); { CSA_ASSERT(this, IsPropertyCell(CAST(holder))); // Ensure the property cell doesn't contain the hole. TNode value = LoadObjectField(CAST(holder), PropertyCell::kValueOffset); GotoIf(IsTheHole(value), miss); exit_point->Return(TrueConstant()); } BIND(&slow); { Comment("load_slow"); if (ic_mode == ICMode::kGlobalIC) { exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Slow, p->context(), p->name(), p->slot(), p->vector()); } else { exit_point->ReturnCallRuntime(Runtime::kHasProperty, p->context(), p->receiver(), p->name()); } } } // 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 lookup start object if // ICHandler::DoAccessCheckOnLookupStartObjectBits bit is set in the smi // handler. // 4. Does dictionary lookup on receiver if // ICHandler::LookupOnLookupStartObjectBits bit is set in the smi handler. If // |on_found_on_lookup_start_object| 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_lookup_start_object| 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 TNode AccessorAssembler::HandleProtoHandler( const ICParameters* p, TNode handler, const OnCodeHandler& on_code_handler, const OnFoundOnLookupStartObject& on_found_on_lookup_start_object, Label* miss, ICMode ic_mode) { // // Check prototype validity cell. // { TNode maybe_validity_cell = LoadObjectField(handler, ICHandler::kValidityCellOffset); CheckPrototypeValidityCell(maybe_validity_cell, miss); } // // Check smi handler bits. // { TNode 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); on_code_handler(CAST(smi_or_code_handler)); BIND(&if_smi_handler); } TNode handler_flags = SmiUntag(CAST(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::LookupOnLookupStartObjectBits::kMask | ICHandler::DoAccessCheckOnLookupStartObjectBits::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_lookup_start_object(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( handler_flags), &if_do_access_check, &if_lookup_on_lookup_start_object); BIND(&if_do_access_check); { TNode data2 = LoadHandlerDataField(handler, 2); CSA_ASSERT(this, IsWeakOrCleared(data2)); TNode expected_native_context = CAST(GetHeapObjectAssumeWeak(data2, miss)); EmitAccessCheck(expected_native_context, p->context(), p->lookup_start_object(), &done, miss); } BIND(&if_lookup_on_lookup_start_object); { // Dictionary lookup on lookup start object is not necessary for // Load/StoreGlobalIC (which is the only case when the // lookup_start_object can be a JSGlobalObject) because prototype // validity cell check already guards modifications of the global // object. CSA_ASSERT(this, Word32BinaryNot(HasInstanceType( CAST(p->lookup_start_object()), JS_GLOBAL_OBJECT_TYPE))); TNode properties = CAST(LoadSlowProperties(CAST(p->lookup_start_object()))); TVARIABLE(IntPtrT, var_name_index); Label found(this, &var_name_index); NameDictionaryLookup(properties, CAST(p->name()), &found, &var_name_index, &done); BIND(&found); { if (on_found_on_lookup_start_object) { on_found_on_lookup_start_object(properties, var_name_index.value()); } else { Goto(miss); } } } BIND(&done); } return smi_or_code_handler; } } void AccessorAssembler::HandleLoadICProtoHandler( const LazyLoadICParameters* p, TNode handler, TVariable* var_holder, TVariable* var_smi_handler, Label* if_smi_handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode, LoadAccessMode access_mode) { TNode smi_handler = CAST(HandleProtoHandler( p, handler, // Code sub-handlers are not expected in LoadICs, so no |on_code_handler|. nullptr, // on_found_on_lookup_start_object [=](TNode properties, TNode name_index) { if (access_mode == LoadAccessMode::kHas) { exit_point->Return(TrueConstant()); } else { TVARIABLE(Uint32T, var_details); TVARIABLE(Object, var_value); LoadPropertyFromNameDictionary(properties, name_index, &var_details, &var_value); TNode value = CallGetterIfAccessor( var_value.value(), CAST(var_holder->value()), var_details.value(), p->context(), p->receiver(), miss); exit_point->Return(value); } }, miss, ic_mode)); TNode maybe_holder_or_constant = LoadHandlerDataField(handler, 1); Label load_from_cached_holder(this), is_smi(this), done(this); GotoIf(TaggedIsSmi(maybe_holder_or_constant), &is_smi); Branch(TaggedEqual(maybe_holder_or_constant, NullConstant()), &done, &load_from_cached_holder); BIND(&is_smi); { CSA_ASSERT( this, WordEqual( Signed(DecodeWord(SmiUntag(smi_handler))), IntPtrConstant(LoadHandler::kConstantFromPrototype))); if (access_mode == LoadAccessMode::kHas) { exit_point->Return(TrueConstant()); } else { exit_point->Return(CAST(maybe_holder_or_constant)); } } 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, IsWeakOrCleared(maybe_holder_or_constant)); TNode holder = GetHeapObjectAssumeWeak(maybe_holder_or_constant, miss); *var_holder = holder; Goto(&done); } BIND(&done); { *var_smi_handler = smi_handler; Goto(if_smi_handler); } } void AccessorAssembler::EmitAccessCheck(TNode expected_native_context, TNode context, TNode receiver, Label* can_access, Label* miss) { CSA_ASSERT(this, IsNativeContext(expected_native_context)); TNode native_context = LoadNativeContext(context); GotoIf(TaggedEqual(expected_native_context, native_context), can_access); // If the receiver is not a JSGlobalProxy then we miss. GotoIfNot(IsJSGlobalProxy(CAST(receiver)), miss); // For JSGlobalProxy receiver try to compare security tokens of current // and expected native contexts. TNode expected_token = LoadContextElement( expected_native_context, Context::SECURITY_TOKEN_INDEX); TNode current_token = LoadContextElement(native_context, Context::SECURITY_TOKEN_INDEX); Branch(TaggedEqual(expected_token, current_token), can_access, miss); } void AccessorAssembler::JumpIfDataProperty(TNode details, Label* writable, Label* readonly) { if (readonly) { // Accessor properties never have the READ_ONLY attribute set. GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask), readonly); } else { CSA_ASSERT(this, IsNotSetWord32(details, PropertyDetails::kAttributesReadOnlyMask)); } TNode kind = DecodeWord32(details); GotoIf(Word32Equal(kind, Int32Constant(kData)), writable); // Fall through if it's an accessor property. } void AccessorAssembler::HandleStoreICNativeDataProperty( const StoreICParameters* p, TNode holder, TNode handler_word) { Comment("native_data_property_store"); TNode descriptor = Signed(DecodeWordFromWord32(handler_word)); TNode accessor_info = CAST(LoadDescriptorValue(LoadMap(holder), descriptor)); TailCallRuntime(Runtime::kStoreCallbackProperty, p->context(), p->receiver(), holder, accessor_info, p->name(), p->value()); } void AccessorAssembler::HandleStoreICHandlerCase( const StoreICParameters* p, TNode handler, Label* miss, ICMode ic_mode, ElementSupport support_elements) { Label if_smi_handler(this), if_nonsmi_handler(this); Label if_proto_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); { TNode holder = p->receiver(); TNode handler_word = SmiToInt32(CAST(handler)); Label if_fast_smi(this), if_proxy(this), if_interceptor(this), if_slow(this); STATIC_ASSERT(StoreHandler::kGlobalProxy + 1 == StoreHandler::kNormal); STATIC_ASSERT(StoreHandler::kNormal + 1 == StoreHandler::kInterceptor); STATIC_ASSERT(StoreHandler::kInterceptor + 1 == StoreHandler::kSlow); STATIC_ASSERT(StoreHandler::kSlow + 1 == StoreHandler::kProxy); STATIC_ASSERT(StoreHandler::kProxy + 1 == StoreHandler::kKindsNumber); TNode handler_kind = DecodeWord32(handler_word); GotoIf( Int32LessThan(handler_kind, Int32Constant(StoreHandler::kGlobalProxy)), &if_fast_smi); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kProxy)), &if_proxy); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kInterceptor)), &if_interceptor); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kSlow)), &if_slow); CSA_ASSERT(this, Word32Equal(handler_kind, Int32Constant(StoreHandler::kNormal))); TNode properties = CAST(LoadSlowProperties(CAST(holder))); TVARIABLE(IntPtrT, var_name_index); Label dictionary_found(this, &var_name_index); NameDictionaryLookup( properties, CAST(p->name()), &dictionary_found, &var_name_index, miss); BIND(&dictionary_found); { TNode details = LoadDetailsByKeyIndex(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(properties, var_name_index.value(), p->value()); Return(p->value()); } BIND(&if_fast_smi); { TNode handler_kind = DecodeWord32(handler_word); Label data(this), accessor(this), native_data_property(this); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kAccessor)), &accessor); Branch(Word32Equal(handler_kind, Int32Constant(StoreHandler::kNativeDataProperty)), &native_data_property, &data); BIND(&accessor); HandleStoreAccessor(p, CAST(holder), handler_word); BIND(&native_data_property); HandleStoreICNativeDataProperty(p, CAST(holder), handler_word); BIND(&data); // Handle non-transitioning field stores. HandleStoreICSmiHandlerCase(handler_word, CAST(holder), p->value(), miss); } BIND(&if_proxy); HandleStoreToProxy(p, CAST(holder), miss, support_elements); BIND(&if_interceptor); { Comment("store_interceptor"); TailCallRuntime(Runtime::kStorePropertyWithInterceptor, p->context(), p->value(), p->receiver(), p->name()); } BIND(&if_slow); { Comment("store_slow"); // The slow case calls into the runtime to complete the store without // causing an IC miss that would otherwise cause a transition to the // generic stub. if (ic_mode == ICMode::kGlobalIC) { TailCallRuntime(Runtime::kStoreGlobalIC_Slow, p->context(), p->value(), p->slot(), p->vector(), p->receiver(), p->name()); } else { TailCallRuntime(Runtime::kKeyedStoreIC_Slow, p->context(), p->value(), p->receiver(), p->name()); } } } BIND(&if_nonsmi_handler); { GotoIf(IsWeakOrCleared(handler), &store_transition_or_global); TNode strong_handler = CAST(handler); TNode 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), 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, IsWeakOrCleared(handler)); TNode map_or_property_cell = GetHeapObjectAssumeWeak(handler, miss); Label store_global(this), store_transition(this); Branch(IsMap(map_or_property_cell), &store_transition, &store_global); BIND(&store_global); { TNode 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 = CAST(map_or_property_cell); HandleStoreICTransitionMapHandlerCase(p, map, miss, kCheckPrototypeValidity); Return(p->value()); } } } void AccessorAssembler::HandleStoreICTransitionMapHandlerCase( const StoreICParameters* p, TNode transition_map, Label* miss, StoreTransitionMapFlags flags) { DCHECK_EQ(0, flags & ~kStoreTransitionMapFlagsMask); if (flags & kCheckPrototypeValidity) { TNode maybe_validity_cell = LoadObjectField(transition_map, Map::kPrototypeValidityCellOffset); CheckPrototypeValidityCell(maybe_validity_cell, miss); } TNode bitfield3 = LoadMapBitField3(transition_map); CSA_ASSERT(this, IsClearWord32(bitfield3)); GotoIf(IsSetWord32(bitfield3), miss); // Load last descriptor details. TNode nof = DecodeWordFromWord32(bitfield3); CSA_ASSERT(this, WordNotEqual(nof, IntPtrConstant(0))); TNode descriptors = LoadMapDescriptors(transition_map); TNode factor = IntPtrConstant(DescriptorArray::kEntrySize); TNode last_key_index = UncheckedCast(IntPtrAdd( IntPtrConstant(DescriptorArray::ToKeyIndex(-1)), IntPtrMul(nof, factor))); if (flags & kValidateTransitionHandler) { TNode key = LoadKeyByKeyIndex(descriptors, last_key_index); GotoIf(TaggedNotEqual(key, p->name()), miss); } else { CSA_ASSERT(this, TaggedEqual(LoadKeyByKeyIndex(descriptors, last_key_index), p->name())); } TNode details = LoadDetailsByKeyIndex(descriptors, last_key_index); if (flags & kValidateTransitionHandler) { // 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 kKindAndAttributesDontDeleteReadOnlyMask = PropertyDetails::KindField::kMask | PropertyDetails::kAttributesDontDeleteMask | PropertyDetails::kAttributesReadOnlyMask; STATIC_ASSERT(kData == 0); // Both DontDelete and ReadOnly attributes must not be set and it has to be // a kData property. GotoIf(IsSetWord32(details, kKindAndAttributesDontDeleteReadOnlyMask), miss); // DontEnum attribute is allowed only for private symbols and vice versa. Branch(Word32Equal( IsSetWord32(details, PropertyDetails::kAttributesDontEnumMask), IsPrivateSymbol(CAST(p->name()))), &attributes_ok, miss); BIND(&attributes_ok); } OverwriteExistingFastDataProperty(CAST(p->receiver()), transition_map, descriptors, last_key_index, details, p->value(), miss, true); } void AccessorAssembler::CheckFieldType(TNode descriptors, TNode name_index, TNode representation, TNode 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); Branch(IsHeapNumber(CAST(value)), &all_fine, bailout); } BIND(&r_heapobject); { GotoIf(TaggedIsSmi(value), bailout); TNode field_type = LoadFieldTypeByKeyIndex(descriptors, name_index); const Address kNoneType = FieldType::None().ptr(); const Address kAnyType = FieldType::Any().ptr(); DCHECK_NE(static_cast(kNoneType), kClearedWeakHeapObjectLower32); DCHECK_NE(static_cast(kAnyType), kClearedWeakHeapObjectLower32); // FieldType::None can't hold any value. GotoIf( TaggedEqual(field_type, BitcastWordToTagged(IntPtrConstant(kNoneType))), bailout); // FieldType::Any can hold any value. GotoIf( TaggedEqual(field_type, BitcastWordToTagged(IntPtrConstant(kAnyType))), &all_fine); // Cleared weak references count as FieldType::None, which can't hold any // value. TNode field_type_map = CAST(GetHeapObjectAssumeWeak(field_type, bailout)); // FieldType::Class(...) performs a map check. Branch(TaggedEqual(LoadMap(CAST(value)), field_type_map), &all_fine, bailout); } BIND(&all_fine); } TNode AccessorAssembler::IsPropertyDetailsConst(TNode details) { return Word32Equal(DecodeWord32(details), Int32Constant(static_cast(VariableMode::kConst))); } void AccessorAssembler::OverwriteExistingFastDataProperty( TNode object, TNode object_map, TNode descriptors, TNode descriptor_name_index, TNode details, TNode value, Label* slow, bool do_transitioning_store) { Label done(this), if_field(this), if_descriptor(this); CSA_ASSERT(this, Word32Equal(DecodeWord32(details), Int32Constant(kData))); Branch(Word32Equal(DecodeWord32(details), Int32Constant(kField)), &if_field, &if_descriptor); BIND(&if_field); { TNode representation = DecodeWord32(details); CheckFieldType(descriptors, descriptor_name_index, representation, value, slow); TNode field_index = DecodeWordFromWord32(details); field_index = Unsigned( IntPtrAdd(field_index, Unsigned(LoadMapInobjectPropertiesStartInWords(object_map)))); TNode 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); { TNode field_offset = Signed(TimesTaggedSize(field_index)); Label tagged_rep(this), double_rep(this); Branch( Word32Equal(representation, Int32Constant(Representation::kDouble)), &double_rep, &tagged_rep); BIND(&double_rep); { TNode double_value = ChangeNumberToFloat64(CAST(value)); if (FLAG_unbox_double_fields) { if (do_transitioning_store) { StoreMap(object, object_map); } else { Label store_value(this); GotoIfNot(IsPropertyDetailsConst(details), &store_value); TNode current_value = LoadObjectField(object, field_offset); BranchIfSameNumberValue(current_value, double_value, &store_value, slow); BIND(&store_value); } StoreObjectFieldNoWriteBarrier(object, field_offset, double_value); } else { if (do_transitioning_store) { TNode heap_number = AllocateHeapNumberWithValue(double_value); StoreMap(object, object_map); StoreObjectField(object, field_offset, heap_number); } else { TNode heap_number = CAST(LoadObjectField(object, field_offset)); Label store_value(this); GotoIfNot(IsPropertyDetailsConst(details), &store_value); TNode current_value = LoadHeapNumberValue(heap_number); BranchIfSameNumberValue(current_value, double_value, &store_value, slow); BIND(&store_value); StoreHeapNumberValue(heap_number, double_value); } } Goto(&done); } BIND(&tagged_rep); { if (do_transitioning_store) { StoreMap(object, object_map); } else { Label if_mutable(this); GotoIfNot(IsPropertyDetailsConst(details), &if_mutable); TNode current_value = LoadObjectField(object, field_offset); BranchIfSameValue(current_value, value, &done, slow, SameValueMode::kNumbersOnly); BIND(&if_mutable); } StoreObjectField(object, field_offset, value); Goto(&done); } } BIND(&backing_store); { TNode backing_store_index = Signed(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. TVARIABLE(Object, var_value, value); { Label cont(this); GotoIf(Word32NotEqual(representation, Int32Constant(Representation::kDouble)), &cont); { TNode double_value = ChangeNumberToFloat64(CAST(value)); TNode heap_number = AllocateHeapNumberWithValue(double_value); var_value = heap_number; Goto(&cont); } BIND(&cont); } TNode properties = 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 properties = CAST(LoadFastProperties(CAST(object))); Branch( Word32Equal(representation, Int32Constant(Representation::kDouble)), &double_rep, &tagged_rep); BIND(&double_rep); { TNode heap_number = CAST(LoadPropertyArrayElement(properties, backing_store_index)); TNode double_value = ChangeNumberToFloat64(CAST(value)); Label if_mutable(this); GotoIfNot(IsPropertyDetailsConst(details), &if_mutable); TNode current_value = LoadHeapNumberValue(heap_number); BranchIfSameNumberValue(current_value, double_value, &done, slow); BIND(&if_mutable); StoreHeapNumberValue(heap_number, double_value); Goto(&done); } BIND(&tagged_rep); { Label if_mutable(this); GotoIfNot(IsPropertyDetailsConst(details), &if_mutable); TNode current_value = LoadPropertyArrayElement(properties, backing_store_index); BranchIfSameValue(current_value, value, &done, slow, SameValueMode::kNumbersOnly); BIND(&if_mutable); StorePropertyArrayElement(properties, backing_store_index, value); Goto(&done); } } } } BIND(&if_descriptor); { // Check that constant matches value. TNode constant = LoadValueByKeyIndex(descriptors, descriptor_name_index); GotoIf(TaggedNotEqual(value, constant), slow); if (do_transitioning_store) { StoreMap(object, object_map); } Goto(&done); } BIND(&done); } void AccessorAssembler::CheckPrototypeValidityCell( TNode maybe_validity_cell, Label* miss) { Label done(this); GotoIf( TaggedEqual(maybe_validity_cell, SmiConstant(Map::kPrototypeChainValid)), &done); CSA_ASSERT(this, TaggedIsNotSmi(maybe_validity_cell)); TNode cell_value = LoadObjectField(CAST(maybe_validity_cell), Cell::kValueOffset); Branch(TaggedEqual(cell_value, SmiConstant(Map::kPrototypeChainValid)), &done, miss); BIND(&done); } void AccessorAssembler::HandleStoreAccessor(const StoreICParameters* p, TNode holder, TNode handler_word) { Comment("accessor_store"); TNode descriptor = Signed(DecodeWordFromWord32(handler_word)); TNode accessor_pair = CAST(LoadDescriptorValue(LoadMap(holder), descriptor)); CSA_ASSERT(this, IsAccessorPair(accessor_pair)); TNode setter = LoadObjectField(accessor_pair, AccessorPair::kSetterOffset); CSA_ASSERT(this, Word32BinaryNot(IsTheHole(setter))); Callable callable = CodeFactory::Call(isolate()); Return(Call(p->context(), setter, p->receiver(), p->value())); } void AccessorAssembler::HandleStoreICProtoHandler( const StoreICParameters* p, TNode 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 = [=](TNode 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 maybe_transition_map = LoadHandlerDataField(handler, 1); TNode transition_map = CAST(GetHeapObjectAssumeWeak(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()); } }; } TNode smi_handler = HandleProtoHandler( p, handler, on_code_handler, // on_found_on_lookup_start_object [=](TNode properties, TNode name_index) { TNode details = LoadDetailsByKeyIndex(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(properties, 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), if_slow(this); CSA_ASSERT(this, TaggedIsSmi(smi_handler)); TNode handler_word = SmiToInt32(CAST(smi_handler)); TNode handler_kind = DecodeWord32(handler_word); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kNormal)), &if_add_normal); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kSlow)), &if_slow); TNode maybe_holder = LoadHandlerDataField(handler, 1); CSA_ASSERT(this, IsWeakOrCleared(maybe_holder)); TNode holder = GetHeapObjectAssumeWeak(maybe_holder, miss); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kGlobalProxy)), &if_store_global_proxy); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kAccessor)), &if_accessor); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kNativeDataProperty)), &if_native_data_property); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kApiSetter)), &if_api_setter); GotoIf( Word32Equal(handler_kind, Int32Constant(StoreHandler::kApiSetterHolderIsPrototype)), &if_api_setter); CSA_ASSERT(this, Word32Equal(handler_kind, Int32Constant(StoreHandler::kProxy))); HandleStoreToProxy(p, CAST(holder), miss, support_elements); BIND(&if_slow); { Comment("store_slow"); // The slow case calls into the runtime to complete the store without // causing an IC miss that would otherwise cause a transition to the // generic stub. if (ic_mode == ICMode::kGlobalIC) { TailCallRuntime(Runtime::kStoreGlobalIC_Slow, p->context(), p->value(), p->slot(), p->vector(), p->receiver(), p->name()); } else { TailCallRuntime(Runtime::kKeyedStoreIC_Slow, p->context(), p->value(), p->receiver(), p->name()); } } BIND(&if_add_normal); { // This is a case of "transitioning store" to a dictionary mode object // when the property does not exist. The "existing property" case is // covered above by LookupOnLookupStartObject bit handling of the smi // handler. Label slow(this); TNode receiver_map = LoadMap(CAST(p->receiver())); InvalidateValidityCellIfPrototype(receiver_map); TNode properties = CAST(LoadSlowProperties(CAST(p->receiver()))); Add(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)); TNode call_handler_info = CAST(holder); // Context is stored either in data2 or data3 field depending on whether // the access check is enabled for this handler or not. TNode maybe_context = Select( IsSetWord32( handler_word), [=] { return LoadHandlerDataField(handler, 3); }, [=] { return LoadHandlerDataField(handler, 2); }); CSA_ASSERT(this, IsWeakOrCleared(maybe_context)); TNode context = Select( IsCleared(maybe_context), [=] { return SmiConstant(0); }, [=] { return GetHeapObjectAssumeWeak(maybe_context); }); TNode foreign = LoadObjectField( call_handler_info, CallHandlerInfo::kJsCallbackOffset); TNode callback = LoadForeignForeignAddressPtr(foreign); TNode data = LoadObjectField(call_handler_info, CallHandlerInfo::kDataOffset); TVARIABLE(Object, api_holder, p->receiver()); Label store(this); GotoIf(Word32Equal(handler_kind, Int32Constant(StoreHandler::kApiSetter)), &store); CSA_ASSERT(this, Word32Equal( handler_kind, Int32Constant(StoreHandler::kApiSetterHolderIsPrototype))); api_holder = LoadMapPrototype(LoadMap(CAST(p->receiver()))); Goto(&store); BIND(&store); TNode argc = IntPtrConstant(1); Return(CallApiCallback(context, callback, argc, data, api_holder.value(), p->receiver(), p->value())); } BIND(&if_store_global_proxy); { ExitPoint direct_exit(this); StoreGlobalIC_PropertyCellCase(CAST(holder), p->value(), &direct_exit, miss); } } } void AccessorAssembler::HandleStoreToProxy(const StoreICParameters* p, TNode proxy, Label* miss, ElementSupport support_elements) { TVARIABLE(IntPtrT, var_index); TVARIABLE(Name, var_unique); Label if_index(this), if_unique_name(this), to_name_failed(this, Label::kDeferred); 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()); 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()); } else { TNode name = CallBuiltin(Builtins::kToName, p->context(), p->name()); TailCallBuiltin(Builtins::kProxySetProperty, p->context(), proxy, name, p->value(), p->receiver()); } } void AccessorAssembler::HandleStoreICSmiHandlerCase(TNode handler_word, TNode holder, TNode value, Label* miss) { Comment("field store"); #ifdef DEBUG TNode handler_kind = DecodeWord32(handler_word); CSA_ASSERT( this, Word32Or( Word32Equal(handler_kind, Int32Constant(StoreHandler::kField)), Word32Equal(handler_kind, Int32Constant(StoreHandler::kConstField)))); #endif TNode field_representation = DecodeWord32(handler_word); Label if_smi_field(this), if_double_field(this), if_heap_object_field(this), if_tagged_field(this); int32_t case_values[] = {Representation::kTagged, Representation::kHeapObject, Representation::kSmi}; Label* case_labels[] = {&if_tagged_field, &if_heap_object_field, &if_smi_field}; Switch(field_representation, &if_double_field, case_values, case_labels, 3); BIND(&if_tagged_field); { Comment("store tagged field"); HandleStoreFieldAndReturn(handler_word, holder, value, base::nullopt, Representation::Tagged(), miss); } BIND(&if_heap_object_field); { Comment("heap object field checks"); CheckHeapObjectTypeMatchesDescriptor(handler_word, holder, value, miss); Comment("store heap object field"); HandleStoreFieldAndReturn(handler_word, holder, value, base::nullopt, Representation::HeapObject(), miss); } BIND(&if_smi_field); { Comment("smi field checks"); GotoIfNot(TaggedIsSmi(value), miss); Comment("store smi field"); HandleStoreFieldAndReturn(handler_word, holder, value, base::nullopt, Representation::Smi(), miss); } BIND(&if_double_field); { CSA_ASSERT(this, Word32Equal(field_representation, Int32Constant(Representation::kDouble))); Comment("double field checks"); TNode double_value = TryTaggedToFloat64(value, miss); CheckDescriptorConsidersNumbersMutable(handler_word, holder, miss); Comment("store double field"); HandleStoreFieldAndReturn(handler_word, holder, value, double_value, Representation::Double(), miss); } } void AccessorAssembler::CheckHeapObjectTypeMatchesDescriptor( TNode handler_word, TNode holder, TNode value, Label* bailout) { GotoIf(TaggedIsSmi(value), bailout); Label done(this); // Skip field type check in favor of constant value check when storing // to constant field. GotoIf(Word32Equal(DecodeWord32(handler_word), Int32Constant(StoreHandler::kConstField)), &done); TNode descriptor = Signed(DecodeWordFromWord32(handler_word)); TNode maybe_field_type = LoadDescriptorValueOrFieldType(LoadMap(holder), descriptor); GotoIf(TaggedIsSmi(maybe_field_type), &done); // Check that value type matches the field type. { TNode field_type = GetHeapObjectAssumeWeak(maybe_field_type, bailout); Branch(TaggedEqual(LoadMap(CAST(value)), field_type), &done, bailout); } BIND(&done); } void AccessorAssembler::CheckDescriptorConsidersNumbersMutable( TNode handler_word, TNode holder, Label* bailout) { // We have to check that the representation is Double. Checking the value // (either in the field or being assigned) is not enough, as we could have // transitioned to Tagged but still be holding a HeapNumber, which would no // longer be allowed to be mutable. // TODO(leszeks): We could skip the representation check in favor of a // constant value check in HandleStoreFieldAndReturn here, but then // HandleStoreFieldAndReturn would need an IsHeapNumber check in case both the // representation changed and the value is no longer a HeapNumber. TNode descriptor_entry = Signed(DecodeWordFromWord32(handler_word)); TNode descriptors = LoadMapDescriptors(LoadMap(holder)); TNode details = LoadDetailsByDescriptorEntry(descriptors, descriptor_entry); GotoIfNot(IsEqualInWord32( details, Representation::kDouble), bailout); } void AccessorAssembler::HandleStoreFieldAndReturn( TNode handler_word, TNode holder, TNode value, base::Optional> double_value, Representation representation, Label* miss) { Label done(this); bool store_value_as_double = representation.IsDouble(); TNode is_inobject = IsSetWord32(handler_word); TNode property_storage = Select( is_inobject, [&]() { return holder; }, [&]() { return LoadFastProperties(holder); }); TNode index = DecodeWordFromWord32(handler_word); TNode offset = Signed(TimesTaggedSize(index)); // For Double fields, we want to mutate the current double-value // field rather than changing it to point at a new HeapNumber. if (store_value_as_double) { TVARIABLE(HeapObject, actual_property_storage, property_storage); TVARIABLE(IntPtrT, actual_offset, offset); Label property_and_offset_ready(this); // If we are unboxing double fields, and this is an in-object field, the // property_storage and offset are already pointing to the double-valued // field. if (FLAG_unbox_double_fields) { GotoIf(is_inobject, &property_and_offset_ready); } // Store the double value directly into the mutable HeapNumber. TNode field = LoadObjectField(property_storage, offset); CSA_ASSERT(this, IsHeapNumber(CAST(field))); actual_property_storage = CAST(field); actual_offset = IntPtrConstant(HeapNumber::kValueOffset); Goto(&property_and_offset_ready); BIND(&property_and_offset_ready); property_storage = actual_property_storage.value(); offset = actual_offset.value(); } // Do constant value check if necessary. Label do_store(this); GotoIfNot(Word32Equal(DecodeWord32(handler_word), Int32Constant(StoreHandler::kConstField)), &do_store); { if (store_value_as_double) { Label done(this); TNode current_value = LoadObjectField(property_storage, offset); BranchIfSameNumberValue(current_value, *double_value, &done, miss); BIND(&done); Return(value); } else { TNode current_value = LoadObjectField(property_storage, offset); GotoIfNot(TaggedEqual(current_value, value), miss); Return(value); } } BIND(&do_store); // Do the store. if (store_value_as_double) { StoreObjectFieldNoWriteBarrier(property_storage, offset, *double_value); } else if (representation.IsSmi()) { TNode value_smi = CAST(value); StoreObjectFieldNoWriteBarrier(property_storage, offset, value_smi); } else { StoreObjectField(property_storage, offset, value); } Return(value); } TNode AccessorAssembler::ExtendPropertiesBackingStore( TNode object, TNode index) { Comment("[ Extend storage"); TVARIABLE(HeapObject, var_properties); TVARIABLE(Int32T, var_encoded_hash); TVARIABLE(IntPtrT, var_length); TNode properties = LoadObjectField(object, JSObject::kPropertiesOrHashOffset); 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); { TNode hash = SmiToInt32(CAST(properties)); TNode encoded_hash = Word32Shl(hash, Int32Constant(PropertyArray::HashField::kShift)); var_encoded_hash = encoded_hash; var_length = IntPtrConstant(0); var_properties = EmptyFixedArrayConstant(); Goto(&extend_store); } BIND(&if_property_array); { var_properties = CAST(properties); TNode length_and_hash_int32 = LoadAndUntagToWord32ObjectField( var_properties.value(), PropertyArray::kLengthAndHashOffset); var_encoded_hash = Word32And( length_and_hash_int32, Int32Constant(PropertyArray::HashField::kMask)); var_length = ChangeInt32ToIntPtr( Word32And(length_and_hash_int32, Int32Constant(PropertyArray::LengthField::kMask))); Goto(&extend_store); } BIND(&extend_store); { TVARIABLE(HeapObject, var_new_properties, 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())), &done); TNode delta = IntPtrConstant(JSObject::kFieldsAdded); TNode new_capacity = IntPtrAdd(var_length.value(), delta); // 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, IntPtrLessThan(new_capacity, IntPtrConstant(kMaxNumberOfDescriptors + JSObject::kFieldsAdded))); TNode new_properties = AllocatePropertyArray(new_capacity); var_new_properties = new_properties; FillPropertyArrayWithUndefined(new_properties, var_length.value(), new_capacity); // |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, DestroySource::kYes); TNode new_capacity_int32 = TruncateIntPtrToInt32(new_capacity); TNode 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 CAST(var_new_properties.value()); } } void AccessorAssembler::EmitFastElementsBoundsCheck( TNode object, TNode elements, TNode intptr_index, TNode is_jsarray_condition, Label* miss) { TVARIABLE(IntPtrT, var_length); Comment("Fast elements bounds check"); Label if_array(this), length_loaded(this, &var_length); GotoIf(is_jsarray_condition, &if_array); { var_length = SmiUntag(LoadFixedArrayBaseLength(elements)); Goto(&length_loaded); } BIND(&if_array); { var_length = SmiUntag(LoadFastJSArrayLength(CAST(object))); Goto(&length_loaded); } BIND(&length_loaded); GotoIfNot(UintPtrLessThan(intptr_index, var_length.value()), miss); } void AccessorAssembler::EmitElementLoad( TNode object, TNode elements_kind, TNode intptr_index, TNode is_jsarray_condition, Label* if_hole, Label* rebox_double, TVariable* var_double_value, Label* unimplemented_elements_kind, Label* out_of_bounds, Label* miss, ExitPoint* exit_point, LoadAccessMode access_mode) { Label if_typed_array(this), if_fast(this), if_fast_packed(this), if_fast_holey(this), if_fast_double(this), if_fast_holey_double(this), if_nonfast(this), if_dictionary(this); Branch(Int32GreaterThan(elements_kind, Int32Constant(LAST_ANY_NONEXTENSIBLE_ELEMENTS_KIND)), &if_nonfast, &if_fast); BIND(&if_fast); { TNode elements = LoadJSObjectElements(CAST(object)); EmitFastElementsBoundsCheck(CAST(object), elements, intptr_index, is_jsarray_condition, out_of_bounds); int32_t kinds[] = { // Handled by if_fast_packed. PACKED_SMI_ELEMENTS, PACKED_ELEMENTS, PACKED_NONEXTENSIBLE_ELEMENTS, PACKED_SEALED_ELEMENTS, PACKED_FROZEN_ELEMENTS, // Handled by if_fast_holey. HOLEY_SMI_ELEMENTS, HOLEY_ELEMENTS, HOLEY_NONEXTENSIBLE_ELEMENTS, HOLEY_FROZEN_ELEMENTS, HOLEY_SEALED_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, &if_fast_packed, &if_fast_packed, &if_fast_packed, // FAST_HOLEY_{SMI,}_ELEMENTS &if_fast_holey, &if_fast_holey, &if_fast_holey, &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( access_mode == LoadAccessMode::kHas ? TrueConstant() : UnsafeLoadFixedArrayElement(CAST(elements), intptr_index)); } BIND(&if_fast_holey); { Comment("fast holey elements"); TNode element = UnsafeLoadFixedArrayElement(CAST(elements), intptr_index); GotoIf(TaggedEqual(element, TheHoleConstant()), if_hole); exit_point->Return(access_mode == LoadAccessMode::kHas ? TrueConstant() : element); } BIND(&if_fast_double); { Comment("packed double elements"); if (access_mode == LoadAccessMode::kHas) { exit_point->Return(TrueConstant()); } else { *var_double_value = LoadFixedDoubleArrayElement(CAST(elements), intptr_index); Goto(rebox_double); } } BIND(&if_fast_holey_double); { Comment("holey double elements"); TNode value = LoadFixedDoubleArrayElement(CAST(elements), intptr_index, if_hole); if (access_mode == LoadAccessMode::kHas) { exit_point->Return(TrueConstant()); } else { *var_double_value = 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"); if (Is64()) { GotoIf(UintPtrLessThan(IntPtrConstant(JSArray::kMaxArrayIndex), intptr_index), out_of_bounds); } else { GotoIf(IntPtrLessThan(intptr_index, IntPtrConstant(0)), out_of_bounds); } TNode elements = LoadJSObjectElements(CAST(object)); TNode value = BasicLoadNumberDictionaryElement( CAST(elements), intptr_index, miss, if_hole); exit_point->Return(access_mode == LoadAccessMode::kHas ? TrueConstant() : value); } BIND(&if_typed_array); { Comment("typed elements"); // Check if buffer has been detached. TNode buffer = LoadJSArrayBufferViewBuffer(CAST(object)); GotoIf(IsDetachedBuffer(buffer), miss); // Bounds check. TNode length = LoadJSTypedArrayLength(CAST(object)); GotoIfNot(UintPtrLessThan(intptr_index, length), out_of_bounds); if (access_mode == LoadAccessMode::kHas) { exit_point->Return(TrueConstant()); } else { TNode data_ptr = LoadJSTypedArrayDataPtr(CAST(object)); 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. TNode element = Load(data_ptr, intptr_index); exit_point->Return(SmiFromInt32(element)); } BIND(&int8_elements); { Comment("INT8_ELEMENTS"); TNode element = Load(data_ptr, intptr_index); exit_point->Return(SmiFromInt32(element)); } BIND(&uint16_elements); { Comment("UINT16_ELEMENTS"); TNode index = WordShl(intptr_index, IntPtrConstant(1)); TNode element = Load(data_ptr, index); exit_point->Return(SmiFromInt32(element)); } BIND(&int16_elements); { Comment("INT16_ELEMENTS"); TNode index = WordShl(intptr_index, IntPtrConstant(1)); TNode element = Load(data_ptr, index); exit_point->Return(SmiFromInt32(element)); } BIND(&uint32_elements); { Comment("UINT32_ELEMENTS"); TNode index = WordShl(intptr_index, IntPtrConstant(2)); TNode element = Load(data_ptr, index); exit_point->Return(ChangeUint32ToTagged(element)); } BIND(&int32_elements); { Comment("INT32_ELEMENTS"); TNode index = WordShl(intptr_index, IntPtrConstant(2)); TNode element = Load(data_ptr, index); exit_point->Return(ChangeInt32ToTagged(element)); } BIND(&float32_elements); { Comment("FLOAT32_ELEMENTS"); TNode index = WordShl(intptr_index, IntPtrConstant(2)); TNode element = Load(data_ptr, index); *var_double_value = ChangeFloat32ToFloat64(element); Goto(rebox_double); } BIND(&float64_elements); { Comment("FLOAT64_ELEMENTS"); TNode index = WordShl(intptr_index, IntPtrConstant(3)); TNode element = Load(data_ptr, index); *var_double_value = element; Goto(rebox_double); } BIND(&bigint64_elements); { Comment("BIGINT64_ELEMENTS"); exit_point->Return(LoadFixedTypedArrayElementAsTagged( data_ptr, Unsigned(intptr_index), BIGINT64_ELEMENTS)); } BIND(&biguint64_elements); { Comment("BIGUINT64_ELEMENTS"); exit_point->Return(LoadFixedTypedArrayElementAsTagged( data_ptr, Unsigned(intptr_index), BIGUINT64_ELEMENTS)); } } } } } void AccessorAssembler::InvalidateValidityCellIfPrototype( TNode map, base::Optional> maybe_bitfield3) { Label is_prototype(this), cont(this); TNode bitfield3; if (bitfield3) { bitfield3 = maybe_bitfield3.value(); } else { bitfield3 = LoadMapBitField3(map); } Branch(IsSetWord32(bitfield3, Map::Bits3::IsPrototypeMapBit::kMask), &is_prototype, &cont); BIND(&is_prototype); { TNode 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); TNode function = ExternalConstant( ExternalReference::invalidate_prototype_chains_function()); CallCFunction(function, MachineType::AnyTagged(), std::make_pair(MachineType::AnyTagged(), map)); Goto(&cont); } BIND(&cont); } void AccessorAssembler::GenericElementLoad( TNode lookup_start_object, TNode lookup_start_object_map, TNode lookup_start_object_instance_type, TNode index, Label* slow) { Comment("integer index"); ExitPoint direct_exit(this); Label if_custom(this), if_element_hole(this), if_oob(this); Label return_undefined(this); // Receivers requiring non-standard element accesses (interceptors, access // checks, strings and string wrappers, proxies) are handled in the runtime. GotoIf( IsCustomElementsReceiverInstanceType(lookup_start_object_instance_type), &if_custom); TNode elements_kind = LoadMapElementsKind(lookup_start_object_map); TNode is_jsarray_condition = IsJSArrayInstanceType(lookup_start_object_instance_type); TVARIABLE(Float64T, var_double_value); 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(lookup_start_object, 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"); // On TypedArrays, all OOB loads (positive and negative) return undefined // without ever checking the prototype chain. GotoIf(IsJSTypedArrayInstanceType(lookup_start_object_instance_type), &return_undefined); // Positive OOB indices within JSArray index range are effectively the same // as hole loads. Larger keys and negative keys are named loads. if (Is64()) { Branch(UintPtrLessThanOrEqual(index, IntPtrConstant(JSArray::kMaxArrayIndex)), &if_element_hole, slow); } else { Branch(IntPtrLessThan(index, IntPtrConstant(0)), slow, &if_element_hole); } } BIND(&if_element_hole); { Comment("found the hole"); BranchIfPrototypesHaveNoElements(lookup_start_object_map, &return_undefined, slow); } BIND(&if_custom); { Comment("check if string"); GotoIfNot(IsStringInstanceType(lookup_start_object_instance_type), slow); Comment("load string character"); TNode length = LoadStringLengthAsWord(CAST(lookup_start_object)); GotoIfNot(UintPtrLessThan(index, length), slow); IncrementCounter(isolate()->counters()->ic_keyed_load_generic_smi(), 1); TailCallBuiltin(Builtins::kStringCharAt, NoContextConstant(), lookup_start_object, index); } BIND(&return_undefined); Return(UndefinedConstant()); } void AccessorAssembler::GenericPropertyLoad( TNode lookup_start_object, TNode lookup_start_object_map, TNode lookup_start_object_instance_type, const LoadICParameters* p, Label* slow, UseStubCache use_stub_cache) { DCHECK_EQ(lookup_start_object, p->lookup_start_object()); ExitPoint direct_exit(this); Comment("key is unique name"); Label if_found_on_lookup_start_object(this), if_property_dictionary(this), lookup_prototype_chain(this), special_receiver(this); TVARIABLE(Uint32T, var_details); TVARIABLE(Object, var_value); TNode name = CAST(p->name()); // Receivers requiring non-standard accesses (interceptors, access // checks, strings and string wrappers) are handled in the runtime. GotoIf(IsSpecialReceiverInstanceType(lookup_start_object_instance_type), &special_receiver); // Check if the lookup_start_object has fast or slow properties. TNode bitfield3 = LoadMapBitField3(lookup_start_object_map); GotoIf(IsSetWord32(bitfield3), &if_property_dictionary); // Try looking up the property on the lookup_start_object; if unsuccessful, // look for a handler in the stub cache. TNode descriptors = LoadMapDescriptors(lookup_start_object_map); Label if_descriptor_found(this), try_stub_cache(this); TVARIABLE(IntPtrT, var_name_index); Label* notfound = use_stub_cache == kUseStubCache ? &try_stub_cache : &lookup_prototype_chain; DescriptorLookup(name, descriptors, bitfield3, &if_descriptor_found, &var_name_index, notfound); BIND(&if_descriptor_found); { LoadPropertyFromFastObject(lookup_start_object, lookup_start_object_map, descriptors, var_name_index.value(), &var_details, &var_value); Goto(&if_found_on_lookup_start_object); } if (use_stub_cache == kUseStubCache) { DCHECK_EQ(lookup_start_object, p->receiver_and_lookup_start_object()); Label stub_cache(this); BIND(&try_stub_cache); // When there is no feedback vector don't use stub cache. GotoIfNot(IsUndefined(p->vector()), &stub_cache); // Fall back to the slow path for private symbols. Branch(IsPrivateSymbol(name), slow, &lookup_prototype_chain); 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(), lookup_start_object, name, &found_handler, &var_handler, &stub_cache_miss); BIND(&found_handler); { LazyLoadICParameters lazy_p(p); HandleLoadICHandlerCase(&lazy_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_and_lookup_start_object(), 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 properties = CAST(LoadSlowProperties(CAST(lookup_start_object))); NameDictionaryLookup(properties, 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_lookup_start_object); } } BIND(&if_found_on_lookup_start_object); { TNode value = CallGetterIfAccessor( var_value.value(), lookup_start_object, var_details.value(), p->context(), p->receiver(), slow); IncrementCounter(isolate()->counters()->ic_keyed_load_generic_symbol(), 1); Return(value); } BIND(&lookup_prototype_chain); { TVARIABLE(Map, var_holder_map); TVARIABLE(Int32T, var_holder_instance_type); Label return_undefined(this), is_private_symbol(this); Label loop(this, {&var_holder_map, &var_holder_instance_type}); var_holder_map = lookup_start_object_map; var_holder_instance_type = lookup_start_object_instance_type; GotoIf(IsPrivateSymbol(name), &is_private_symbol); 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); TNode proto = LoadMapPrototype(var_holder_map.value()); GotoIf(TaggedEqual(proto, NullConstant()), &return_undefined); TNode proto_map = LoadMap(proto); TNode proto_instance_type = LoadMapInstanceType(proto_map); var_holder_map = proto_map; var_holder_instance_type = proto_instance_type; Label next_proto(this), return_value(this, &var_value), goto_slow(this); TryGetOwnProperty(p->context(), p->receiver(), CAST(proto), proto_map, proto_instance_type, 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(&is_private_symbol); { CSA_ASSERT(this, IsPrivateSymbol(name)); // For private names that don't exist on the receiver, we bail // to the runtime to throw. For private symbols, we just return // undefined. Branch(IsPrivateName(CAST(name)), slow, &return_undefined); } BIND(&return_undefined); Return(UndefinedConstant()); } BIND(&special_receiver); { // TODO(jkummerow): Consider supporting JSModuleNamespace. GotoIfNot( InstanceTypeEqual(lookup_start_object_instance_type, JS_PROXY_TYPE), slow); // Private field/symbol lookup is not supported. GotoIf(IsPrivateSymbol(name), slow); direct_exit.ReturnCallStub( Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty), p->context(), lookup_start_object, name, p->receiver(), SmiConstant(OnNonExistent::kReturnUndefined)); } } //////////////////// Stub cache access helpers. enum AccessorAssembler::StubCacheTable : int { kPrimary = static_cast(StubCache::kPrimary), kSecondary = static_cast(StubCache::kSecondary) }; TNode AccessorAssembler::StubCachePrimaryOffset(TNode name, TNode map) { // Compute the hash of the name (use entire hash field). TNode 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). TNode map_word = BitcastTaggedToWord(map); TNode map32 = TruncateIntPtrToInt32(UncheckedCast( WordXor(map_word, WordShr(map_word, StubCache::kMapKeyShift)))); // Base the offset on a simple combination of name and map. TNode hash = Int32Add(hash_field, map32); uint32_t mask = (StubCache::kPrimaryTableSize - 1) << StubCache::kCacheIndexShift; TNode result = ChangeUint32ToWord(Word32And(hash, Int32Constant(mask))); return Signed(result); } TNode AccessorAssembler::StubCacheSecondaryOffset( TNode name, TNode seed) { // See v8::internal::StubCache::SecondaryOffset(). // Use the seed from the primary cache in the secondary cache. TNode name32 = TruncateIntPtrToInt32(BitcastTaggedToWord(name)); TNode hash = Int32Sub(TruncateIntPtrToInt32(seed), name32); hash = Int32Add(hash, Int32Constant(StubCache::kSecondaryMagic)); int32_t mask = (StubCache::kSecondaryTableSize - 1) << StubCache::kCacheIndexShift; TNode result = ChangeUint32ToWord(Word32And(hash, Int32Constant(mask))); return Signed(result); } void AccessorAssembler::TryProbeStubCacheTable( StubCache* stub_cache, StubCacheTable table_id, TNode entry_offset, TNode name, TNode map, Label* if_handler, TVariable* var_handler, Label* if_miss) { StubCache::Table table = static_cast(table_id); // The {table_offset} holds the entry offset times four (due to masking // and shifting optimizations). const int kMultiplier = sizeof(StubCache::Entry) >> StubCache::kCacheIndexShift; entry_offset = IntPtrMul(entry_offset, IntPtrConstant(kMultiplier)); TNode key_base = ExternalConstant( ExternalReference::Create(stub_cache->key_reference(table))); // Check that the key in the entry matches the name. DCHECK_EQ(0, offsetof(StubCache::Entry, key)); TNode cached_key = CAST(Load(MachineType::TaggedPointer(), key_base, entry_offset)); GotoIf(TaggedNotEqual(name, cached_key), if_miss); // Check that the map in the entry matches. TNode cached_map = Load( key_base, IntPtrAdd(entry_offset, IntPtrConstant(offsetof(StubCache::Entry, map)))); GotoIf(TaggedNotEqual(map, cached_map), if_miss); TNode handler = ReinterpretCast( Load(MachineType::AnyTagged(), key_base, IntPtrAdd(entry_offset, IntPtrConstant(offsetof(StubCache::Entry, value))))); // We found the handler. *var_handler = handler; Goto(if_handler); } void AccessorAssembler::TryProbeStubCache(StubCache* stub_cache, TNode lookup_start_object, TNode name, Label* if_handler, TVariable* 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 {lookup_start_object} isn't a smi. GotoIf(TaggedIsSmi(lookup_start_object), &miss); TNode lookup_start_object_map = LoadMap(CAST(lookup_start_object)); // Probe the primary table. TNode primary_offset = StubCachePrimaryOffset(name, lookup_start_object_map); TryProbeStubCacheTable(stub_cache, kPrimary, primary_offset, name, lookup_start_object_map, if_handler, var_handler, &try_secondary); BIND(&try_secondary); { // Probe the secondary table. TNode secondary_offset = StubCacheSecondaryOffset(name, primary_offset); TryProbeStubCacheTable(stub_cache, kSecondary, secondary_offset, name, lookup_start_object_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 LazyLoadICParameters* 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), no_feedback(this, Label::kDeferred); GotoIf(IsUndefined(p->vector()), &no_feedback); TNode lookup_start_object_map = LoadReceiverMap(p->receiver_and_lookup_start_object()); GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); // Inlined fast path. { Comment("LoadIC_BytecodeHandler_fast"); TVARIABLE(MaybeObject, var_handler); Label try_polymorphic(this), if_handler(this, &var_handler); TNode feedback = TryMonomorphicCase( p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler, &var_handler, &try_polymorphic); BIND(&if_handler); HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, exit_point); BIND(&try_polymorphic); { TNode strong_feedback = GetHeapObjectIfStrong(feedback, &miss); GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &stub_call); HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback), &if_handler, &var_handler, &miss); } } 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); TNode code_target = HeapConstant(ic.code()); exit_point->ReturnCallStub(ic.descriptor(), code_target, p->context(), p->receiver_and_lookup_start_object(), p->name(), p->slot(), p->vector()); } BIND(&no_feedback); { Comment("LoadIC_BytecodeHandler_nofeedback"); // Call into the stub that implements the non-inlined parts of LoadIC. exit_point->ReturnCallStub( Builtins::CallableFor(isolate(), Builtins::kLoadIC_NoFeedback), p->context(), p->receiver(), p->name(), SmiConstant(FeedbackSlotKind::kLoadProperty)); } 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); TNode lookup_start_object_map = LoadReceiverMap(p->receiver_and_lookup_start_object()); GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); // Check monomorphic case. TNode feedback = TryMonomorphicCase(p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler, &var_handler, &try_polymorphic); BIND(&if_handler); { LazyLoadICParameters lazy_p(p); HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss, &direct_exit); } BIND(&try_polymorphic); TNode strong_feedback = GetHeapObjectIfStrong(feedback, &miss); { // Check polymorphic case. Comment("LoadIC_try_polymorphic"); GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined); HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback), &if_handler, &var_handler, &miss); } BIND(&non_inlined); { LoadIC_Noninlined(p, lookup_start_object_map, strong_feedback, &var_handler, &if_handler, &miss, &direct_exit); } BIND(&miss); direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, p->context(), p->receiver_and_lookup_start_object(), p->name(), p->slot(), p->vector()); } void AccessorAssembler::LoadSuperIC(const LoadICParameters* p) { ExitPoint direct_exit(this); TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), no_feedback(this), non_inlined(this, Label::kDeferred), try_polymorphic(this), miss(this, Label::kDeferred); GotoIf(IsUndefined(p->vector()), &no_feedback); // The lookup start object cannot be a SMI, since it's the home object's // prototype, and it's not possible to set SMIs as prototypes. TNode lookup_start_object_map = LoadReceiverMap(p->lookup_start_object()); GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); TNode feedback = TryMonomorphicCase(p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler, &var_handler, &try_polymorphic); BIND(&if_handler); { LazyLoadICParameters lazy_p(p); HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss, &direct_exit); } BIND(&no_feedback); { LoadSuperIC_NoFeedback(p); } BIND(&try_polymorphic); TNode strong_feedback = GetHeapObjectIfStrong(feedback, &miss); { Comment("LoadSuperIC_try_polymorphic"); GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &non_inlined); HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback), &if_handler, &var_handler, &miss); } BIND(&non_inlined); { // LoadIC_Noninlined can be used here, since it handles the // lookup_start_object != receiver case gracefully. LoadIC_Noninlined(p, lookup_start_object_map, strong_feedback, &var_handler, &if_handler, &miss, &direct_exit); } BIND(&miss); direct_exit.ReturnCallRuntime(Runtime::kLoadWithReceiverIC_Miss, p->context(), p->receiver(), p->lookup_start_object(), p->name(), p->slot(), p->vector()); } void AccessorAssembler::LoadIC_Noninlined(const LoadICParameters* p, TNode lookup_start_object_map, TNode feedback, TVariable* var_handler, Label* if_handler, Label* miss, ExitPoint* exit_point) { // Neither deprecated map nor monomorphic. These cases are handled in the // bytecode handler. CSA_ASSERT(this, Word32BinaryNot(IsDeprecatedMap(lookup_start_object_map))); CSA_ASSERT(this, TaggedNotEqual(lookup_start_object_map, feedback)); CSA_ASSERT(this, Word32BinaryNot(IsWeakFixedArrayMap(LoadMap(feedback)))); DCHECK_EQ(MachineRepresentation::kTagged, var_handler->rep()); { // Check megamorphic case. GotoIfNot(TaggedEqual(feedback, MegamorphicSymbolConstant()), miss); TryProbeStubCache(isolate()->load_stub_cache(), p->lookup_start_object(), CAST(p->name()), if_handler, var_handler, miss); } } void AccessorAssembler::LoadIC_NoFeedback(const LoadICParameters* p, TNode ic_kind) { Label miss(this, Label::kDeferred); TNode lookup_start_object = p->receiver_and_lookup_start_object(); GotoIf(TaggedIsSmi(lookup_start_object), &miss); TNode lookup_start_object_map = LoadMap(CAST(lookup_start_object)); GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); TNode instance_type = LoadMapInstanceType(lookup_start_object_map); { // 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(lookup_start_object), lookup_start_object_map, ¬_function_prototype); Return(LoadJSFunctionPrototype(CAST(lookup_start_object), &miss)); BIND(¬_function_prototype); } GenericPropertyLoad(CAST(lookup_start_object), lookup_start_object_map, instance_type, p, &miss, kDontUseStubCache); BIND(&miss); { TailCallRuntime(Runtime::kLoadNoFeedbackIC_Miss, p->context(), p->receiver(), p->name(), ic_kind); } } void AccessorAssembler::LoadSuperIC_NoFeedback(const LoadICParameters* p) { Label miss(this, Label::kDeferred); TNode lookup_start_object = p->lookup_start_object(); // The lookup start object cannot be a SMI, since it's the home object's // prototype, and it's not possible to set SMIs as prototypes. TNode lookup_start_object_map = LoadMap(CAST(lookup_start_object)); GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); TNode instance_type = LoadMapInstanceType(lookup_start_object_map); GenericPropertyLoad(CAST(lookup_start_object), lookup_start_object_map, instance_type, p, &miss, kDontUseStubCache); BIND(&miss); { TailCallRuntime(Runtime::kLoadWithReceiverNoFeedbackIC_Miss, p->context(), p->receiver(), p->lookup_start_object(), p->name()); } } void AccessorAssembler::LoadGlobalIC(TNode maybe_feedback_vector, const LazyNode& lazy_slot, const LazyNode& lazy_context, const LazyNode& lazy_name, TypeofMode typeof_mode, ExitPoint* exit_point) { Label try_handler(this, Label::kDeferred), miss(this, Label::kDeferred), no_feedback(this, Label::kDeferred); GotoIf(IsUndefined(maybe_feedback_vector), &no_feedback); { TNode slot = lazy_slot(); { TNode vector = CAST(maybe_feedback_vector); LoadGlobalIC_TryPropertyCellCase(vector, slot, lazy_context, exit_point, &try_handler, &miss); BIND(&try_handler); LoadGlobalIC_TryHandlerCase(vector, slot, lazy_context, lazy_name, typeof_mode, exit_point, &miss); } BIND(&miss); { Comment("LoadGlobalIC_MissCase"); TNode context = lazy_context(); TNode name = lazy_name(); exit_point->ReturnCallRuntime(Runtime::kLoadGlobalIC_Miss, context, name, slot, maybe_feedback_vector, SmiConstant(typeof_mode)); } } BIND(&no_feedback); { int ic_kind = static_cast((typeof_mode == INSIDE_TYPEOF) ? FeedbackSlotKind::kLoadGlobalInsideTypeof : FeedbackSlotKind::kLoadGlobalNotInsideTypeof); exit_point->ReturnCallStub( Builtins::CallableFor(isolate(), Builtins::kLoadGlobalIC_NoFeedback), lazy_context(), lazy_name(), SmiConstant(ic_kind)); } } void AccessorAssembler::LoadGlobalIC_TryPropertyCellCase( TNode vector, TNode slot, const LazyNode& lazy_context, ExitPoint* exit_point, Label* try_handler, Label* miss) { Comment("LoadGlobalIC_TryPropertyCellCase"); Label if_lexical_var(this), if_property_cell(this); TNode maybe_weak_ref = LoadFeedbackVectorSlot(vector, slot); 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, IsWeakOrCleared(maybe_weak_ref)); TNode property_cell = CAST(GetHeapObjectAssumeWeak(maybe_weak_ref, try_handler)); TNode value = LoadObjectField(property_cell, PropertyCell::kValueOffset); GotoIf(TaggedEqual(value, TheHoleConstant()), miss); exit_point->Return(value); } BIND(&if_lexical_var); { Comment("Load lexical variable"); TNode lexical_handler = SmiUntag(CAST(maybe_weak_ref)); TNode context_index = Signed(DecodeWord(lexical_handler)); TNode slot_index = Signed(DecodeWord(lexical_handler)); TNode context = lazy_context(); TNode script_context = LoadScriptContext(context, context_index); TNode result = LoadContextElement(script_context, slot_index); exit_point->Return(result); } } void AccessorAssembler::LoadGlobalIC_TryHandlerCase( TNode vector, TNode slot, const LazyNode& lazy_context, const LazyNode& lazy_name, TypeofMode typeof_mode, ExitPoint* exit_point, Label* miss) { Comment("LoadGlobalIC_TryHandlerCase"); Label call_handler(this), non_smi(this); TNode feedback_element = LoadFeedbackVectorSlot(vector, slot, kTaggedSize); TNode handler = CAST(feedback_element); GotoIf(TaggedEqual(handler, UninitializedSymbolConstant()), miss); OnNonExistent on_nonexistent = typeof_mode == NOT_INSIDE_TYPEOF ? OnNonExistent::kThrowReferenceError : OnNonExistent::kReturnUndefined; TNode context = lazy_context(); TNode native_context = LoadNativeContext(context); TNode receiver = CAST(LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX)); TNode global = LoadContextElement(native_context, Context::EXTENSION_INDEX); LazyLoadICParameters p([=] { return context; }, receiver, lazy_name, [=] { return slot; }, vector, global); HandleLoadICHandlerCase(&p, handler, miss, exit_point, ICMode::kGlobalIC, on_nonexistent); } void AccessorAssembler::ScriptContextTableLookup( TNode name, TNode native_context, Label* found_hole, Label* not_found) { TNode script_context_table = CAST( LoadContextElement(native_context, Context::SCRIPT_CONTEXT_TABLE_INDEX)); TVARIABLE(IntPtrT, context_index, IntPtrConstant(-1)); Label loop(this, &context_index); TNode num_script_contexts = SmiUntag(CAST(LoadFixedArrayElement( script_context_table, ScriptContextTable::kUsedSlotIndex))); Goto(&loop); BIND(&loop); { context_index = IntPtrAdd(context_index.value(), IntPtrConstant(1)); GotoIf(IntPtrGreaterThanOrEqual(context_index.value(), num_script_contexts), not_found); TNode script_context = CAST(LoadFixedArrayElement( script_context_table, context_index.value(), ScriptContextTable::kFirstContextSlotIndex * kTaggedSize)); TNode scope_info = CAST(LoadContextElement(script_context, Context::SCOPE_INFO_INDEX)); TNode length = LoadAndUntagFixedArrayBaseLength(scope_info); GotoIf(IntPtrLessThanOrEqual(length, IntPtrConstant(0)), &loop); TVARIABLE(IntPtrT, scope_var_index, IntPtrConstant(ScopeInfo::kVariablePartIndex - 1)); TNode num_scope_vars = SmiUntag(CAST(LoadFixedArrayElement( scope_info, IntPtrConstant(ScopeInfo::Fields::kContextLocalCount)))); TNode end_index = IntPtrAdd( num_scope_vars, IntPtrConstant(ScopeInfo::kVariablePartIndex)); Label loop_scope_info(this, &scope_var_index); Goto(&loop_scope_info); BIND(&loop_scope_info); { scope_var_index = IntPtrAdd(scope_var_index.value(), IntPtrConstant(1)); GotoIf(IntPtrGreaterThanOrEqual(scope_var_index.value(), end_index), &loop); TNode var_name = LoadFixedArrayElement(scope_info, scope_var_index.value(), 0); GotoIf(TaggedNotEqual(var_name, name), &loop_scope_info); TNode var_index = IntPtrAdd(IntPtrConstant(Context::MIN_CONTEXT_SLOTS), IntPtrSub(scope_var_index.value(), IntPtrConstant(ScopeInfo::kVariablePartIndex))); TNode result = LoadContextElement(script_context, var_index); GotoIf(IsTheHole(result), found_hole); Return(result); } } } void AccessorAssembler::LoadGlobalIC_NoFeedback(TNode context, TNode name, TNode smi_typeof_mode) { TNode native_context = LoadNativeContext(context); Label regular_load(this), throw_reference_error(this, Label::kDeferred); GotoIfNot(IsString(CAST(name)), ®ular_load); ScriptContextTableLookup(CAST(name), native_context, &throw_reference_error, ®ular_load); BIND(&throw_reference_error); Return(CallRuntime(Runtime::kThrowReferenceError, context, name)); BIND(®ular_load); TNode global_object = CAST(LoadContextElement(native_context, Context::EXTENSION_INDEX)); TailCallStub(Builtins::CallableFor(isolate(), Builtins::kLoadIC_NoFeedback), context, global_object, name, smi_typeof_mode); } void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p, LoadAccessMode access_mode) { 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_uninitialized(this, Label::kDeferred), try_polymorphic_name(this, Label::kDeferred), miss(this, Label::kDeferred), generic(this, Label::kDeferred); TNode lookup_start_object_map = LoadReceiverMap(p->receiver_and_lookup_start_object()); GotoIf(IsDeprecatedMap(lookup_start_object_map), &miss); GotoIf(IsUndefined(p->vector()), &generic); // Check monomorphic case. TNode feedback = TryMonomorphicCase(p->slot(), CAST(p->vector()), lookup_start_object_map, &if_handler, &var_handler, &try_polymorphic); BIND(&if_handler); { LazyLoadICParameters lazy_p(p); HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss, &direct_exit, ICMode::kNonGlobalIC, OnNonExistent::kReturnUndefined, kSupportElements, access_mode); } BIND(&try_polymorphic); TNode strong_feedback = GetHeapObjectIfStrong(feedback, &miss); { // Check polymorphic case. Comment("KeyedLoadIC_try_polymorphic"); GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); HandlePolymorphicCase(lookup_start_object_map, CAST(strong_feedback), &if_handler, &var_handler, &miss); } BIND(&try_megamorphic); { // Check megamorphic case. Comment("KeyedLoadIC_try_megamorphic"); Branch(TaggedEqual(strong_feedback, MegamorphicSymbolConstant()), &generic, &try_uninitialized); } BIND(&generic); { // TODO(jkummerow): Inline this? Or some of it? TailCallBuiltin(access_mode == LoadAccessMode::kLoad ? Builtins::kKeyedLoadIC_Megamorphic : Builtins::kKeyedHasIC_Megamorphic, p->context(), p->receiver(), p->name(), p->slot(), p->vector()); } BIND(&try_uninitialized); { // Check uninitialized case. Comment("KeyedLoadIC_try_uninitialized"); Branch(TaggedEqual(strong_feedback, UninitializedSymbolConstant()), &miss, &try_polymorphic_name); } BIND(&try_polymorphic_name); { // We might have a name in feedback, and a weak fixed array in the next // slot. Comment("KeyedLoadIC_try_polymorphic_name"); TVARIABLE(Name, var_name); TVARIABLE(IntPtrT, var_index); Label if_polymorphic_name(this), feedback_matches(this), if_internalized(this), if_notinternalized(this, Label::kDeferred); // Fast-case: The recorded {feedback} matches the {name}. GotoIf(TaggedEqual(strong_feedback, p->name()), &feedback_matches); // Try to internalize the {name} if it isn't already. TryToName(p->name(), &miss, &var_index, &if_internalized, &var_name, &miss, &if_notinternalized); BIND(&if_internalized); { // The {var_name} now contains a unique name. Branch(TaggedEqual(strong_feedback, var_name.value()), &if_polymorphic_name, &miss); } BIND(&if_notinternalized); { TVARIABLE(IntPtrT, var_index); TryInternalizeString(CAST(p->name()), &miss, &var_index, &if_internalized, &var_name, &miss, &miss); } BIND(&feedback_matches); { var_name = CAST(p->name()); Goto(&if_polymorphic_name); } BIND(&if_polymorphic_name); { // If the name comparison succeeded, we know we have a weak fixed array // with at least one map/handler pair. TailCallBuiltin(access_mode == LoadAccessMode::kLoad ? Builtins::kKeyedLoadIC_PolymorphicName : Builtins::kKeyedHasIC_PolymorphicName, p->context(), p->receiver(), var_name.value(), p->slot(), p->vector()); } } BIND(&miss); { Comment("KeyedLoadIC_miss"); TailCallRuntime( access_mode == LoadAccessMode::kLoad ? Runtime::kKeyedLoadIC_Miss : Runtime::kKeyedHasIC_Miss, p->context(), p->receiver(), p->name(), p->slot(), p->vector()); } } void AccessorAssembler::KeyedLoadICGeneric(const LoadICParameters* p) { TVARIABLE(Object, var_name, p->name()); Label if_runtime(this, Label::kDeferred); TNode lookup_start_object = p->lookup_start_object(); GotoIf(TaggedIsSmi(lookup_start_object), &if_runtime); GotoIf(IsNullOrUndefined(lookup_start_object), &if_runtime); { TVARIABLE(IntPtrT, var_index); TVARIABLE(Name, var_unique); Label if_index(this), if_unique_name(this, &var_name), if_notunique(this), if_other(this, Label::kDeferred); TryToName(var_name.value(), &if_index, &var_index, &if_unique_name, &var_unique, &if_other, &if_notunique); BIND(&if_unique_name); { LoadICParameters pp(p, var_unique.value()); TNode lookup_start_object_map = LoadMap(CAST(lookup_start_object)); GenericPropertyLoad(CAST(lookup_start_object), lookup_start_object_map, LoadMapInstanceType(lookup_start_object_map), &pp, &if_runtime); } BIND(&if_other); { var_name = CallBuiltin(Builtins::kToName, p->context(), var_name.value()); TryToName(var_name.value(), &if_index, &var_index, &if_unique_name, &var_unique, &if_runtime, &if_notunique); } 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 {if_runtime} path instead. Label if_in_string_table(this); TryInternalizeString(CAST(var_name.value()), &if_index, &var_index, &if_in_string_table, &var_unique, &if_runtime, &if_runtime); 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, var_unique.value()); TNode lookup_start_object_map = LoadMap(CAST(lookup_start_object)); GenericPropertyLoad(CAST(lookup_start_object), lookup_start_object_map, LoadMapInstanceType(lookup_start_object_map), &pp, &if_runtime, kDontUseStubCache); } } else { Goto(&if_runtime); } } BIND(&if_index); { TNode lookup_start_object_map = LoadMap(CAST(lookup_start_object)); GenericElementLoad(CAST(lookup_start_object), lookup_start_object_map, LoadMapInstanceType(lookup_start_object_map), var_index.value(), &if_runtime); } } BIND(&if_runtime); { Comment("KeyedLoadGeneric_slow"); IncrementCounter(isolate()->counters()->ic_keyed_load_generic_slow(), 1); // TODO(jkummerow): Should we use the GetProperty TF stub instead? TailCallRuntime(Runtime::kGetProperty, p->context(), p->receiver_and_lookup_start_object(), var_name.value()); } } void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p, LoadAccessMode access_mode) { TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), miss(this, Label::kDeferred); TNode lookup_start_object = p->lookup_start_object(); TNode lookup_start_object_map = LoadReceiverMap(lookup_start_object); TNode name = CAST(p->name()); TNode vector = CAST(p->vector()); TNode slot = p->slot(); TNode 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, Word32BinaryNot(IsDeprecatedMap(lookup_start_object_map))); CSA_ASSERT(this, TaggedEqual(name, LoadFeedbackVectorSlot(vector, slot)), name, vector); // Check if we have a matching handler for the {lookup_start_object_map}. TNode feedback_element = LoadFeedbackVectorSlot(vector, slot, kTaggedSize); TNode array = CAST(feedback_element); HandlePolymorphicCase(lookup_start_object_map, array, &if_handler, &var_handler, &miss); BIND(&if_handler); { ExitPoint direct_exit(this); LazyLoadICParameters lazy_p(p); HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss, &direct_exit, ICMode::kNonGlobalIC, OnNonExistent::kReturnUndefined, kOnlyProperties, access_mode); } BIND(&miss); { Comment("KeyedLoadIC_miss"); TailCallRuntime( access_mode == LoadAccessMode::kLoad ? Runtime::kKeyedLoadIC_Miss : Runtime::kKeyedHasIC_Miss, context, p->receiver_and_lookup_start_object(), name, slot, vector); } } void AccessorAssembler::StoreIC(const StoreICParameters* p) { TVARIABLE(MaybeObject, var_handler, ReinterpretCast(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), miss(this, Label::kDeferred), no_feedback(this, Label::kDeferred); TNode receiver_map = LoadReceiverMap(p->receiver()); GotoIf(IsDeprecatedMap(receiver_map), &miss); GotoIf(IsUndefined(p->vector()), &no_feedback); // Check monomorphic case. TNode feedback = TryMonomorphicCase(p->slot(), CAST(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 strong_feedback = GetHeapObjectIfStrong(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); } BIND(&try_megamorphic); { // Check megamorphic case. GotoIfNot(TaggedEqual(strong_feedback, MegamorphicSymbolConstant()), &miss); TryProbeStubCache(isolate()->store_stub_cache(), p->receiver(), CAST(p->name()), &if_handler, &var_handler, &miss); } BIND(&no_feedback); { TailCallBuiltin(Builtins::kStoreIC_NoFeedback, p->context(), p->receiver(), p->name(), p->value(), p->slot()); } 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_heapobject(this); TNode maybe_weak_ref = LoadFeedbackVectorSlot(CAST(pp->vector()), pp->slot()); Branch(TaggedIsSmi(maybe_weak_ref), &if_lexical_var, &if_heapobject); BIND(&if_heapobject); { Label try_handler(this), miss(this, Label::kDeferred); CSA_ASSERT(this, IsWeakOrCleared(maybe_weak_ref)); TNode property_cell = CAST(GetHeapObjectAssumeWeak(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 handler = LoadFeedbackVectorSlot(CAST(pp->vector()), pp->slot(), kTaggedSize); GotoIf(TaggedEqual(handler, UninitializedSymbolConstant()), &miss); DCHECK(pp->receiver_is_null()); TNode native_context = LoadNativeContext(pp->context()); StoreICParameters p( pp->context(), LoadContextElement(native_context, Context::GLOBAL_PROXY_INDEX), pp->name(), pp->value(), pp->slot(), pp->vector()); 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 lexical_handler = SmiUntag(CAST(maybe_weak_ref)); TNode context_index = Signed(DecodeWord(lexical_handler)); TNode slot_index = Signed(DecodeWord(lexical_handler)); TNode script_context = LoadScriptContext(pp->context(), context_index); StoreContextElement(script_context, slot_index, pp->value()); Return(pp->value()); } } void AccessorAssembler::StoreGlobalIC_PropertyCellCase( TNode property_cell, TNode value, ExitPoint* exit_point, Label* miss) { Comment("StoreGlobalIC_TryPropertyCellCase"); // 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. TNode cell_contents = LoadObjectField(property_cell, PropertyCell::kValueOffset); TNode details = LoadAndUntagToWord32ObjectField( property_cell, PropertyCell::kPropertyDetailsRawOffset); GotoIf(IsSetWord32(details, PropertyDetails::kAttributesReadOnlyMask), miss); CSA_ASSERT(this, Word32Equal(DecodeWord32(details), Int32Constant(kData))); TNode type = DecodeWord32(details); Label constant(this), store(this), not_smi(this); GotoIf(Word32Equal(type, Int32Constant( static_cast(PropertyCellType::kConstant))), &constant); GotoIf(IsTheHole(cell_contents), miss); GotoIf(Word32Equal( type, Int32Constant(static_cast(PropertyCellType::kMutable))), &store); CSA_ASSERT(this, Word32Or(Word32Equal(type, Int32Constant(static_cast( PropertyCellType::kConstantType))), Word32Equal(type, Int32Constant(static_cast( PropertyCellType::kUndefined))))); GotoIfNot(TaggedIsSmi(cell_contents), ¬_smi); GotoIfNot(TaggedIsSmi(value), miss); Goto(&store); BIND(¬_smi); { GotoIf(TaggedIsSmi(value), miss); TNode expected_map = LoadMap(CAST(cell_contents)); TNode map = LoadMap(CAST(value)); GotoIfNot(TaggedEqual(expected_map, map), miss); Goto(&store); } BIND(&store); { StoreObjectField(property_cell, PropertyCell::kValueOffset, value); exit_point->Return(value); } BIND(&constant); { GotoIfNot(TaggedEqual(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), no_feedback(this, Label::kDeferred), try_polymorphic_name(this, Label::kDeferred); TNode receiver_map = LoadReceiverMap(p->receiver()); GotoIf(IsDeprecatedMap(receiver_map), &miss); GotoIf(IsUndefined(p->vector()), &no_feedback); // Check monomorphic case. TNode feedback = TryMonomorphicCase(p->slot(), CAST(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 strong_feedback = GetHeapObjectIfStrong(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); } BIND(&try_megamorphic); { // Check megamorphic case. Comment("KeyedStoreIC_try_megamorphic"); Branch(TaggedEqual(strong_feedback, MegamorphicSymbolConstant()), &no_feedback, &try_polymorphic_name); } BIND(&no_feedback); { TailCallBuiltin(Builtins::kKeyedStoreIC_Megamorphic, p->context(), p->receiver(), p->name(), p->value(), p->slot()); } 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(TaggedEqual(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 feedback_element = LoadFeedbackVectorSlot(CAST(p->vector()), p->slot(), kTaggedSize); TNode array = CAST(feedback_element); HandlePolymorphicCase(receiver_map, array, &if_handler, &var_handler, &miss); } } 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), no_feedback(this, Label::kDeferred); { TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), try_polymorphic(this, Label::kDeferred), try_megamorphic(this, Label::kDeferred); TNode array_map = LoadReceiverMap(p->receiver()); GotoIf(IsDeprecatedMap(array_map), &miss); GotoIf(IsUndefined(p->vector()), &no_feedback); TNode feedback = TryMonomorphicCase(p->slot(), CAST(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. Label if_transitioning_element_store(this), if_smi_handler(this); // Check used to identify the Slow case. // Currently only the Slow case uses a Smi handler. GotoIf(TaggedIsSmi(var_handler.value()), &if_smi_handler); TNode handler = CAST(var_handler.value()); GotoIfNot(IsCode(handler), &if_transitioning_element_store); TailCallStub(StoreWithVectorDescriptor{}, CAST(handler), p->context(), p->receiver(), p->name(), p->value(), p->slot(), p->vector()); BIND(&if_transitioning_element_store); { TNode maybe_transition_map = LoadHandlerDataField(CAST(handler), 1); TNode transition_map = CAST(GetHeapObjectAssumeWeak(maybe_transition_map, &miss)); GotoIf(IsDeprecatedMap(transition_map), &miss); TNode code = CAST(LoadObjectField(handler, StoreHandler::kSmiHandlerOffset)); TailCallStub(StoreTransitionDescriptor{}, code, p->context(), p->receiver(), p->name(), transition_map, p->value(), p->slot(), p->vector()); } BIND(&if_smi_handler); { #ifdef DEBUG // A check to ensure that no other Smi handler uses this path. TNode handler_word = SmiToInt32(CAST(var_handler.value())); TNode handler_kind = DecodeWord32(handler_word); CSA_ASSERT(this, Word32Equal(handler_kind, Int32Constant(StoreHandler::kSlow))); #endif Comment("StoreInArrayLiteralIC_Slow"); TailCallRuntime(Runtime::kStoreInArrayLiteralIC_Slow, p->context(), p->value(), p->receiver(), p->name()); } } BIND(&try_polymorphic); TNode strong_feedback = GetHeapObjectIfStrong(feedback, &miss); { Comment("StoreInArrayLiteralIC_try_polymorphic"); GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); HandlePolymorphicCase(array_map, CAST(strong_feedback), &if_handler, &var_handler, &miss); } BIND(&try_megamorphic); { Comment("StoreInArrayLiteralIC_try_megamorphic"); CSA_ASSERT( this, Word32Or(TaggedEqual(strong_feedback, UninitializedSymbolConstant()), TaggedEqual(strong_feedback, MegamorphicSymbolConstant()))); GotoIfNot(TaggedEqual(strong_feedback, MegamorphicSymbolConstant()), &miss); TailCallRuntime(Runtime::kStoreInArrayLiteralIC_Slow, p->context(), p->value(), p->receiver(), p->name()); } } BIND(&no_feedback); { Comment("StoreInArrayLiteralIC_NoFeedback"); TailCallBuiltin(Builtins::kSetPropertyInLiteral, p->context(), p->receiver(), p->name(), p->value()); } BIND(&miss); { Comment("StoreInArrayLiteralIC_miss"); TailCallRuntime(Runtime::kStoreInArrayLiteralIC_Miss, p->context(), p->value(), p->slot(), p->vector(), p->receiver(), p->name()); } } //////////////////// Public methods. void AccessorAssembler::GenerateLoadIC() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); LoadIC(&p); } void AccessorAssembler::GenerateLoadIC_Megamorphic() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); ExitPoint direct_exit(this); TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), miss(this, Label::kDeferred); CSA_ASSERT(this, TaggedEqual(LoadFeedbackVectorSlot(CAST(vector), slot), MegamorphicSymbolConstant())); TryProbeStubCache(isolate()->load_stub_cache(), receiver, CAST(name), &if_handler, &var_handler, &miss); BIND(&if_handler); LazyLoadICParameters p( // lazy_context [=] { return context; }, receiver, // lazy_name [=] { return name; }, // lazy_slot [=] { return slot; }, vector); 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_Noninlined() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); ExitPoint direct_exit(this); TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), miss(this, Label::kDeferred); TNode feedback_element = LoadFeedbackVectorSlot(vector, slot); TNode feedback = CAST(feedback_element); LoadICParameters p(context, receiver, name, slot, vector); TNode lookup_start_object_map = LoadReceiverMap(p.lookup_start_object()); LoadIC_Noninlined(&p, lookup_start_object_map, feedback, &var_handler, &if_handler, &miss, &direct_exit); BIND(&if_handler); { LazyLoadICParameters lazy_p(&p); HandleLoadICHandlerCase(&lazy_p, CAST(var_handler.value()), &miss, &direct_exit); } BIND(&miss); direct_exit.ReturnCallRuntime(Runtime::kLoadIC_Miss, context, receiver, name, slot, vector); } void AccessorAssembler::GenerateLoadIC_NoFeedback() { using Descriptor = LoadNoFeedbackDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto context = Parameter(Descriptor::kContext); auto ic_kind = Parameter(Descriptor::kICKind); LoadICParameters p(context, receiver, name, TaggedIndexConstant(FeedbackSlot::Invalid().ToInt()), UndefinedConstant()); LoadIC_NoFeedback(&p, ic_kind); } void AccessorAssembler::GenerateLoadICTrampoline() { using Descriptor = LoadDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kLoadIC, context, receiver, name, slot, vector); } void AccessorAssembler::GenerateLoadICTrampoline_Megamorphic() { using Descriptor = LoadDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kLoadIC_Megamorphic, context, receiver, name, slot, vector); } void AccessorAssembler::GenerateLoadSuperIC() { using Descriptor = LoadWithReceiverAndVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto lookup_start_object = Parameter(Descriptor::kLookupStartObject); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector, lookup_start_object); LoadSuperIC(&p); } void AccessorAssembler::GenerateLoadGlobalIC_NoFeedback() { using Descriptor = LoadGlobalNoFeedbackDescriptor; auto name = Parameter(Descriptor::kName); auto context = Parameter(Descriptor::kContext); auto ic_kind = Parameter(Descriptor::kICKind); LoadGlobalIC_NoFeedback(context, name, ic_kind); } void AccessorAssembler::GenerateLoadGlobalIC(TypeofMode typeof_mode) { using Descriptor = LoadGlobalWithVectorDescriptor; auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); ExitPoint direct_exit(this); LoadGlobalIC( vector, // lazy_slot [=] { return slot; }, // lazy_context [=] { return context; }, // lazy_name [=] { return name; }, typeof_mode, &direct_exit); } void AccessorAssembler::GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode) { using Descriptor = LoadGlobalDescriptor; auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); Callable callable = CodeFactory::LoadGlobalICInOptimizedCode(isolate(), typeof_mode); TailCallStub(callable, context, name, slot, vector); } void AccessorAssembler::GenerateKeyedLoadIC() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); KeyedLoadIC(&p, LoadAccessMode::kLoad); } void AccessorAssembler::GenerateKeyedLoadIC_Megamorphic() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); KeyedLoadICGeneric(&p); } void AccessorAssembler::GenerateKeyedLoadICTrampoline() { using Descriptor = LoadDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kKeyedLoadIC, context, receiver, name, slot, vector); } void AccessorAssembler::GenerateKeyedLoadICTrampoline_Megamorphic() { using Descriptor = LoadDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kKeyedLoadIC_Megamorphic, context, receiver, name, slot, vector); } void AccessorAssembler::GenerateKeyedLoadIC_PolymorphicName() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); KeyedLoadICPolymorphicName(&p, LoadAccessMode::kLoad); } void AccessorAssembler::GenerateStoreGlobalIC() { using Descriptor = StoreGlobalWithVectorDescriptor; auto name = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); StoreICParameters p(context, base::nullopt, name, value, slot, vector); StoreGlobalIC(&p); } void AccessorAssembler::GenerateStoreGlobalICTrampoline() { using Descriptor = StoreGlobalDescriptor; auto name = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kStoreGlobalIC, context, name, value, slot, vector); } void AccessorAssembler::GenerateStoreIC() { using Descriptor = StoreWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); StoreICParameters p(context, receiver, name, value, slot, vector); StoreIC(&p); } void AccessorAssembler::GenerateStoreICTrampoline() { using Descriptor = StoreDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kStoreIC, context, receiver, name, value, slot, vector); } void AccessorAssembler::GenerateKeyedStoreIC() { using Descriptor = StoreWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); StoreICParameters p(context, receiver, name, value, slot, vector); KeyedStoreIC(&p); } void AccessorAssembler::GenerateKeyedStoreICTrampoline() { using Descriptor = StoreDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto context = Parameter(Descriptor::kContext); TNode vector = LoadFeedbackVectorForStub(); TailCallBuiltin(Builtins::kKeyedStoreIC, context, receiver, name, value, slot, vector); } void AccessorAssembler::GenerateStoreInArrayLiteralIC() { using Descriptor = StoreWithVectorDescriptor; auto array = Parameter(Descriptor::kReceiver); auto index = Parameter(Descriptor::kName); auto value = Parameter(Descriptor::kValue); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); StoreICParameters p(context, array, index, value, slot, vector); StoreInArrayLiteralIC(&p); } void AccessorAssembler::GenerateCloneObjectIC_Slow() { using Descriptor = CloneObjectWithVectorDescriptor; auto source = Parameter(Descriptor::kSource); auto flags = Parameter(Descriptor::kFlags); auto context = Parameter(Descriptor::kContext); // The Slow case uses the same call interface as CloneObjectIC, so that it // can be tail called from it. However, the feedback slot and vector are not // used. TNode native_context = LoadNativeContext(context); TNode initial_map = LoadObjectFunctionInitialMap(native_context); TNode result = AllocateJSObjectFromMap(initial_map); { Label did_set_proto_if_needed(this); TNode is_null_proto = SmiNotEqual( SmiAnd(flags, SmiConstant(ObjectLiteral::kHasNullPrototype)), SmiConstant(Smi::zero())); GotoIfNot(is_null_proto, &did_set_proto_if_needed); CallRuntime(Runtime::kInternalSetPrototype, context, result, NullConstant()); Goto(&did_set_proto_if_needed); BIND(&did_set_proto_if_needed); } ReturnIf(IsNullOrUndefined(source), result); source = ToObject_Inline(context, source); Label call_runtime(this, Label::kDeferred), done(this); TNode source_map = LoadMap(CAST(source)); GotoIfNot(IsJSObjectMap(source_map), &call_runtime); GotoIfNot(IsEmptyFixedArray(LoadElements(CAST(source))), &call_runtime); ForEachEnumerableOwnProperty( context, source_map, CAST(source), kPropertyAdditionOrder, [=](TNode key, TNode value) { SetPropertyInLiteral(context, result, key, value); }, &call_runtime); Goto(&done); BIND(&call_runtime); CallRuntime(Runtime::kCopyDataProperties, context, result, source); Goto(&done); BIND(&done); Return(result); } void AccessorAssembler::GenerateCloneObjectIC() { using Descriptor = CloneObjectWithVectorDescriptor; auto source = Parameter(Descriptor::kSource); auto flags = Parameter(Descriptor::kFlags); auto slot = Parameter(Descriptor::kSlot); auto maybe_vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), miss(this, Label::kDeferred), try_polymorphic(this, Label::kDeferred), try_megamorphic(this, Label::kDeferred), slow(this, Label::kDeferred); TNode source_map = LoadReceiverMap(source); GotoIf(IsDeprecatedMap(source_map), &miss); GotoIf(IsUndefined(maybe_vector), &slow); TNode feedback = TryMonomorphicCase(slot, CAST(maybe_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 result_map = CAST(var_handler.value()); TVARIABLE(HeapObject, var_properties, EmptyFixedArrayConstant()); TVARIABLE(FixedArray, var_elements, EmptyFixedArrayConstant()); Label allocate_object(this); GotoIf(IsNullOrUndefined(source), &allocate_object); CSA_SLOW_ASSERT(this, IsJSObjectMap(source_map)); 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 source_elements = LoadElements(CAST(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 source_properties = LoadObjectField(CAST(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. TNode source_property_array = CAST(source_properties); TNode length = LoadPropertyArrayLength(source_property_array); GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &allocate_object); TNode property_array = AllocatePropertyArray(length); FillPropertyArrayWithUndefined(property_array, IntPtrConstant(0), length); CopyPropertyArrayValues(source_property_array, property_array, length, SKIP_WRITE_BARRIER, DestroySource::kNo); var_properties = property_array; } Goto(&allocate_object); BIND(&allocate_object); TNode object = UncheckedCast(AllocateJSObjectFromMap( result_map, var_properties.value(), var_elements.value())); ReturnIf(IsNullOrUndefined(source), object); // Lastly, clone any in-object properties. TNode source_start = LoadMapInobjectPropertiesStartInWords(source_map); TNode source_size = LoadMapInstanceSizeInWords(source_map); TNode result_start = LoadMapInobjectPropertiesStartInWords(result_map); TNode field_offset_difference = TimesTaggedSize(IntPtrSub(result_start, source_start)); // Just copy the fields as raw data (pretending that there are no mutable // HeapNumbers). This doesn't need write barriers. BuildFastLoop( source_start, source_size, [=](TNode field_index) { TNode field_offset = TimesTaggedSize(field_index); TNode field = LoadObjectField(CAST(source), field_offset); TNode result_offset = IntPtrAdd(field_offset, field_offset_difference); StoreObjectFieldNoWriteBarrier(object, result_offset, field); }, 1, IndexAdvanceMode::kPost); // If mutable HeapNumbers can occur, we need to go through the {object} // again here and properly clone them. We use a second loop here to // ensure that the GC (and heap verifier) always sees properly initialized // objects, i.e. never hits undefined values in double fields. if (!FLAG_unbox_double_fields) { TNode start_offset = TimesTaggedSize(result_start); TNode end_offset = IntPtrAdd(TimesTaggedSize(source_size), field_offset_difference); ConstructorBuiltinsAssembler(state()).CopyMutableHeapNumbersInObject( object, start_offset, end_offset); } Return(object); } BIND(&try_polymorphic); TNode strong_feedback = GetHeapObjectIfStrong(feedback, &miss); { Comment("CloneObjectIC_try_polymorphic"); GotoIfNot(IsWeakFixedArrayMap(LoadMap(strong_feedback)), &try_megamorphic); HandlePolymorphicCase(source_map, CAST(strong_feedback), &if_handler, &var_handler, &miss); } BIND(&try_megamorphic); { Comment("CloneObjectIC_try_megamorphic"); CSA_ASSERT( this, Word32Or(TaggedEqual(strong_feedback, UninitializedSymbolConstant()), TaggedEqual(strong_feedback, MegamorphicSymbolConstant()))); GotoIfNot(TaggedEqual(strong_feedback, MegamorphicSymbolConstant()), &miss); Goto(&slow); } BIND(&slow); { TailCallBuiltin(Builtins::kCloneObjectIC_Slow, context, source, flags, slot, maybe_vector); } BIND(&miss); { Comment("CloneObjectIC_miss"); TNode map_or_result = CAST(CallRuntime(Runtime::kCloneObjectIC_Miss, context, source, flags, slot, maybe_vector)); var_handler = UncheckedCast(map_or_result); GotoIf(IsMap(map_or_result), &if_handler); CSA_ASSERT(this, IsJSObject(map_or_result)); Return(map_or_result); } } void AccessorAssembler::GenerateKeyedHasIC() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); KeyedLoadIC(&p, LoadAccessMode::kHas); } void AccessorAssembler::GenerateKeyedHasIC_Megamorphic() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto context = Parameter(Descriptor::kContext); // TODO(magardn): implement HasProperty handling in KeyedLoadICGeneric Return(HasProperty(context, receiver, name, HasPropertyLookupMode::kHasProperty)); } void AccessorAssembler::GenerateKeyedHasIC_PolymorphicName() { using Descriptor = LoadWithVectorDescriptor; auto receiver = Parameter(Descriptor::kReceiver); auto name = Parameter(Descriptor::kName); auto slot = Parameter(Descriptor::kSlot); auto vector = Parameter(Descriptor::kVector); auto context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); KeyedLoadICPolymorphicName(&p, LoadAccessMode::kHas); } void AccessorAssembler::BranchIfPrototypesHaveNoElements( TNode receiver_map, Label* definitely_no_elements, Label* possibly_elements) { TVARIABLE(Map, var_map, receiver_map); Label loop_body(this, &var_map); TNode empty_fixed_array = EmptyFixedArrayConstant(); TNode empty_slow_element_dictionary = EmptySlowElementDictionaryConstant(); Goto(&loop_body); BIND(&loop_body); { TNode map = var_map.value(); TNode prototype = LoadMapPrototype(map); GotoIf(IsNull(prototype), definitely_no_elements); TNode prototype_map = LoadMap(prototype); TNode prototype_instance_type = LoadMapInstanceType(prototype_map); // Pessimistically assume elements if a Proxy, Special API Object, // or JSPrimitiveWrapper wrapper is found on the prototype chain. After this // instance type check, it's not necessary to check for interceptors or // access checks. Label if_custom(this, Label::kDeferred), if_notcustom(this); Branch(IsCustomElementsReceiverInstanceType(prototype_instance_type), &if_custom, &if_notcustom); BIND(&if_custom); { // For string JSPrimitiveWrapper wrappers we still support the checks as // long as they wrap the empty string. GotoIfNot( InstanceTypeEqual(prototype_instance_type, JS_PRIMITIVE_WRAPPER_TYPE), possibly_elements); TNode prototype_value = LoadJSPrimitiveWrapperValue(CAST(prototype)); Branch(IsEmptyString(prototype_value), &if_notcustom, possibly_elements); } BIND(&if_notcustom); { TNode prototype_elements = LoadElements(CAST(prototype)); var_map = prototype_map; GotoIf(TaggedEqual(prototype_elements, empty_fixed_array), &loop_body); Branch(TaggedEqual(prototype_elements, empty_slow_element_dictionary), &loop_body, possibly_elements); } } } } // namespace internal } // namespace v8