/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/ic/ic.cc
4 436 строк
176 KB
Igor Sheludko
deps: V8: backport 088b7112e7ab
24 апр 2026, 19:01
Не верифицирован
24 апр 2026, 19:01
4dafa68
Код
Авторство
О чём код?
// Copyright 2012 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/ic.h" #include <optional> #include <tuple> #include "src/api/api-arguments-inl.h" #include "src/ast/ast.h" #include "src/base/logging.h" #include "src/builtins/accessors.h" #include "src/common/assert-scope.h" #include "src/common/globals.h" #include "src/execution/arguments-inl.h" #include "src/execution/execution.h" #include "src/execution/frames-inl.h" #include "src/execution/isolate-inl.h" #include "src/execution/isolate.h" #include "src/execution/protectors-inl.h" #include "src/execution/tiering-manager.h" #include "src/handles/handles-inl.h" #include "src/handles/maybe-handles.h" #include "src/heap/heap-layout-inl.h" #include "src/ic/call-optimization.h" #include "src/ic/handler-configuration-inl.h" #include "src/ic/handler-configuration.h" #include "src/ic/ic-inl.h" #include "src/ic/ic-stats.h" #include "src/ic/stub-cache.h" #include "src/numbers/conversions.h" #include "src/objects/api-callbacks.h" #include "src/objects/field-type.h" #include "src/objects/instance-type.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-array-inl.h" #include "src/objects/megadom-handler.h" #include "src/objects/property-descriptor.h" #include "src/objects/prototype.h" #include "src/runtime/runtime.h" #include "src/tracing/trace-event.h" #include "src/tracing/tracing-category-observer.h" #include "src/utils/ostreams.h" #if V8_ENABLE_WEBASSEMBLY #include "src/wasm/struct-types.h" #endif // V8_ENABLE_WEBASSEMBLY #if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_ARM64 #include "src/common/code-memory-access.h" #endif // V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_ARM64 namespace v8 { namespace internal { using enum InlineCacheState; char IC::TransitionMarkFromState(IC::State state) { switch (state) { case NO_FEEDBACK: return 'X'; case UNINITIALIZED: return '0'; case MONOMORPHIC: return '1'; case RECOMPUTE_HANDLER: return '^'; case POLYMORPHIC: return 'P'; case MEGAMORPHIC: return 'N'; case MEGADOM: return 'D'; case GENERIC: return 'G'; } UNREACHABLE(); } namespace { const char* GetModifier(KeyedAccessLoadMode mode) { switch (mode) { case KeyedAccessLoadMode::kHandleOOB: return ".OOB"; case KeyedAccessLoadMode::kHandleHoles: return ".HOLES"; case KeyedAccessLoadMode::kHandleOOBAndHoles: return ".OOB+HOLES"; case KeyedAccessLoadMode::kInBounds: return ""; } } const char* GetModifier(KeyedAccessStoreMode mode) { switch (mode) { case KeyedAccessStoreMode::kHandleCOW: return ".COW"; case KeyedAccessStoreMode::kGrowAndHandleCOW: return ".STORE+COW"; case KeyedAccessStoreMode::kIgnoreTypedArrayOOB: return ".IGNORE_OOB"; case KeyedAccessStoreMode::kInBounds: return ""; } UNREACHABLE(); } } // namespace void IC::TraceIC(const char* type, DirectHandle<Object> name) { if (V8_LIKELY(!TracingFlags::is_ic_stats_enabled())) return; State new_state = (state() == NO_FEEDBACK) ? NO_FEEDBACK : nexus()->ic_state(); TraceIC(type, name, state(), new_state); } void IC::TraceIC(const char* type, DirectHandle<Object> name, State old_state, State new_state) { if (V8_LIKELY(!TracingFlags::is_ic_stats_enabled())) return; DirectHandle<Map> map = lookup_start_object_map(); // Might be empty. const char* modifier = ""; if (state() == NO_FEEDBACK) { modifier = ""; } else if (IsKeyedLoadIC()) { KeyedAccessLoadMode mode = nexus()->GetKeyedAccessLoadMode(); modifier = GetModifier(mode); } else if (IsKeyedStoreIC() || IsStoreInArrayLiteralIC() || IsDefineKeyedOwnIC()) { KeyedAccessStoreMode mode = nexus()->GetKeyedAccessStoreMode(); modifier = GetModifier(mode); } bool keyed_prefix = is_keyed() && !IsStoreInArrayLiteralIC(); if (!(TracingFlags::ic_stats.load(std::memory_order_relaxed) & v8::tracing::TracingCategoryObserver::ENABLED_BY_TRACING)) { LOG(isolate(), ICEvent(type, keyed_prefix, map, name, TransitionMarkFromState(old_state), TransitionMarkFromState(new_state), modifier, slow_stub_reason_)); return; } JavaScriptStackFrameIterator it(isolate()); JavaScriptFrame* frame = it.frame(); DisallowGarbageCollection no_gc; Tagged<JSFunction> function = frame->function(); ICStats::instance()->Begin(); ICInfo& ic_info = ICStats::instance()->Current(); ic_info.type = keyed_prefix ? "Keyed" : ""; ic_info.type += type; int code_offset = 0; Tagged<AbstractCode> code; std::tie(code, code_offset) = frame->GetActiveCodeAndOffset(); JavaScriptFrame::CollectFunctionAndOffsetForICStats(isolate(), function, code, code_offset); // Reserve enough space for IC transition state, the longest length is 17. ic_info.state.reserve(17); ic_info.state = "("; ic_info.state += TransitionMarkFromState(old_state); ic_info.state += "->"; ic_info.state += TransitionMarkFromState(new_state); ic_info.state += modifier; ic_info.state += ")"; if (!map.is_null()) { ic_info.map = reinterpret_cast<void*>(map->ptr()); ic_info.is_dictionary_map = map->is_dictionary_map(); ic_info.number_of_own_descriptors = map->NumberOfOwnDescriptors(); ic_info.instance_type = std::to_string(map->instance_type()); } else { ic_info.map = nullptr; } // TODO(lpy) Add name as key field in ICStats. ICStats::instance()->End(); } IC::IC(Isolate* isolate, Handle<FeedbackVector> vector, FeedbackSlot slot, FeedbackSlotKind kind, CallerFrameType caller_frame_type) : isolate_(isolate), vector_set_(false), kind_(kind), target_maps_(isolate), target_maps_set_(false), slow_stub_reason_(nullptr), nexus_(isolate, vector, slot) { DCHECK_IMPLIES(!vector.is_null(), kind_ == nexus_.kind()); state_ = (vector.is_null()) ? NO_FEEDBACK : nexus_.ic_state(); old_state_ = state_; caller_frame_type_ = caller_frame_type; } static void LookupForRead(LookupIterator* it, bool is_has_property) { for (;; it->Next()) { switch (it->state()) { case LookupIterator::TRANSITION: UNREACHABLE(); case LookupIterator::JSPROXY: return; case LookupIterator::WASM_OBJECT: continue; // Continue to the prototype, if present. case LookupIterator::INTERCEPTOR: { // If there is a getter, return; otherwise loop to perform the lookup. DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); { Tagged<InterceptorInfo> interceptor = holder->GetNamedInterceptor(); if (interceptor->has_getter()) { return; } if (is_has_property && interceptor->has_query()) { return; } } continue; } case LookupIterator::ACCESS_CHECK: // ICs know how to perform access checks on global proxies. if (!IsAccessCheckNeeded(*it->GetHolder<JSObject>())) continue; return; case LookupIterator::MODULE_NAMESPACE: { if (JSDeferredModuleNamespace::TriggersEvaluation(it)) { return; } // Once a deferred module is evaluated, we will fallback to perform IC // as an ordinary module namespace. This way it can be either ACCESSOR // or NOT_FOUND state. continue; } case LookupIterator::ACCESSOR: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: case LookupIterator::DATA: case LookupIterator::STRING_LOOKUP_START_OBJECT: case LookupIterator::NOT_FOUND: return; } UNREACHABLE(); } } bool IC::ShouldRecomputeHandler(DirectHandle<String> name) { if (!RecomputeHandlerForName(name)) return false; // This is a contextual access, always just update the handler and stay // monomorphic. if (IsGlobalIC()) return true; MaybeObjectDirectHandle maybe_handler = nexus()->FindHandlerForMap(lookup_start_object_map()); // The current map wasn't handled yet. There's no reason to stay monomorphic, // *unless* we're moving from a deprecated map to its replacement, or // to a more general elements kind. // TODO(verwaest): Check if the current map is actually what the old map // would transition to. if (maybe_handler.is_null()) { if (!IsJSObjectMap(*lookup_start_object_map())) return false; Tagged<Map> first_map = FirstTargetMap(); if (first_map.is_null()) return false; DirectHandle<Map> old_map(first_map, isolate()); if (old_map->is_deprecated()) return true; return IsMoreGeneralElementsKindTransition( old_map->elements_kind(), lookup_start_object_map()->elements_kind()); } return true; } bool IC::RecomputeHandlerForName(DirectHandle<Object> name) { if (is_keyed()) { // Determine whether the failure is due to a name failure. if (!IsName(*name)) return false; Tagged<Name> stub_name = nexus()->GetName(); if (*name != stub_name) return false; } return true; } void IC::UpdateState(DirectHandle<Object> lookup_start_object, DirectHandle<Object> name) { if (state() == NO_FEEDBACK) return; update_lookup_start_object_map(lookup_start_object); if (!IsString(*name)) return; if (state() != MONOMORPHIC && state() != POLYMORPHIC) return; if (IsNullOrUndefined(*lookup_start_object, isolate())) return; // Remove the target from the code cache if it became invalid // because of changes in the prototype chain to avoid hitting it // again. if (ShouldRecomputeHandler(Cast<String>(name))) { MarkRecomputeHandler(name); } } MaybeDirectHandle<Object> IC::TypeError(MessageTemplate index, Handle<Object> object, Handle<Object> key) { HandleScope scope(isolate()); THROW_NEW_ERROR(isolate(), NewTypeError(index, key, object)); } MaybeDirectHandle<Object> IC::ReferenceError(Handle<Name> name) { HandleScope scope(isolate()); THROW_NEW_ERROR(isolate(), NewReferenceError(MessageTemplate::kNotDefined, name)); } void IC::OnFeedbackChanged(const char* reason) { vector_set_ = true; Tagged<FeedbackVector> vector = nexus()->vector(); FeedbackSlot slot = nexus()->slot(); OnFeedbackChanged(isolate(), vector, slot, reason); } // static void IC::OnFeedbackChanged(Isolate* isolate, Tagged<FeedbackVector> vector, FeedbackSlot slot, const char* reason) { #ifdef V8_TRACE_FEEDBACK_UPDATES if (v8_flags.trace_feedback_updates) { FeedbackVector::TraceFeedbackChange(isolate, vector, slot, reason); } #endif isolate->tiering_manager()->NotifyICChanged(vector); } namespace { bool MigrateDeprecated(Isolate* isolate, DirectHandle<Object> object) { if (!IsJSObject(*object)) return false; DirectHandle<JSObject> receiver = Cast<JSObject>(object); if (!receiver->map()->is_deprecated()) return false; JSObject::MigrateInstance(isolate, receiver); return true; } } // namespace bool IC::ConfigureVectorState(IC::State new_state, DirectHandle<Object> key) { DCHECK_EQ(MEGAMORPHIC, new_state); DCHECK_IMPLIES(!is_keyed(), IsName(*key)); bool changed = nexus()->ConfigureMegamorphic( IsName(*key) ? IcCheckType::kProperty : IcCheckType::kElement); if (changed) { OnFeedbackChanged("Megamorphic"); } return changed; } void IC::ConfigureVectorState(DirectHandle<Name> name, DirectHandle<Map> map, DirectHandle<Object> handler) { ConfigureVectorState(name, map, MaybeObjectDirectHandle(handler)); } void IC::ConfigureVectorState(DirectHandle<Name> name, DirectHandle<Map> map, const MaybeObjectDirectHandle& handler) { if (IsGlobalIC()) { nexus()->ConfigureHandlerMode(handler); } else { // Non-keyed ICs don't track the name explicitly. if (!is_keyed()) name = Handle<Name>::null(); nexus()->ConfigureMonomorphic(name, map, handler); } OnFeedbackChanged(IsLoadGlobalIC() ? "LoadGlobal" : "Monomorphic"); } void IC::ConfigureVectorState(DirectHandle<Name> name, MapHandlesSpan maps, MaybeObjectHandles* handlers) { DCHECK(!IsGlobalIC()); MapsAndHandlers maps_and_handlers(isolate()); maps_and_handlers.reserve(maps.size()); DCHECK_EQ(maps.size(), handlers->size()); for (size_t i = 0; i < maps.size(); i++) { maps_and_handlers.emplace_back(maps[i], handlers->at(i)); } ConfigureVectorState(name, maps_and_handlers); } void IC::ConfigureVectorState(DirectHandle<Name> name, MapsAndHandlers const& maps_and_handlers) { DCHECK(!IsGlobalIC()); // Non-keyed ICs don't track the name explicitly. if (!is_keyed()) name = Handle<Name>::null(); nexus()->ConfigurePolymorphic(name, maps_and_handlers); OnFeedbackChanged("Polymorphic"); } MaybeDirectHandle<Object> LoadIC::Load(Handle<JSAny> object, Handle<Name> name, bool update_feedback, DirectHandle<JSAny> receiver) { bool use_ic = (state() != NO_FEEDBACK) && v8_flags.use_ic && update_feedback; if (receiver.is_null()) { receiver = object; } // If the object is undefined or null it's illegal to try to get any // of its properties; throw a TypeError in that case. if (IsAnyHas() ? !IsJSReceiver(*object) : IsNullOrUndefined(*object, isolate())) { if (use_ic) { // Ensure the IC state progresses. TRACE_HANDLER_STATS(isolate(), LoadIC_NonReceiver); update_lookup_start_object_map(object); SetCache(name, LoadHandler::LoadSlow(isolate())); TraceIC("LoadIC", name); } if (IsAnyHas()) { return TypeError(MessageTemplate::kInvalidInOperatorUse, object, name); } else { DCHECK(IsNullOrUndefined(*object, isolate())); ErrorUtils::ThrowLoadFromNullOrUndefined(isolate(), object, name); return MaybeDirectHandle<Object>(); } } // If we encounter an object with a deprecated map, we want to update the // feedback vector with the migrated map. // Mark ourselves as RECOMPUTE_HANDLER so that we don't turn megamorphic due // to seeing the same map and handler. if (MigrateDeprecated(isolate(), object)) { UpdateState(object, name); } JSObject::MakePrototypesFast(object, kStartAtReceiver, isolate()); update_lookup_start_object_map(object); PropertyKey key(isolate(), name); LookupIterator it = LookupIterator(isolate(), receiver, key, object); // Named lookup in the object. LookupForRead(&it, IsAnyHas()); if (it.IsFound() || !ShouldThrowReferenceError()) { // Update inline cache and stub cache. if (use_ic) { UpdateCaches(&it); } else if (state() == NO_FEEDBACK) { // Tracing IC stats IsLoadGlobalIC() ? TraceIC("LoadGlobalIC", name) : TraceIC("LoadIC", name); } if (IsAnyHas()) { // Named lookup in the object. Maybe<bool> maybe = JSReceiver::HasProperty(&it); if (maybe.IsNothing()) return MaybeDirectHandle<Object>(); return isolate()->factory()->ToBoolean(maybe.FromJust()); } // Get the property. DirectHandle<Object> result; ASSIGN_RETURN_ON_EXCEPTION(isolate(), result, Object::GetProperty(&it, IsLoadGlobalIC())); if (it.IsFound()) { return result; } else if (!ShouldThrowReferenceError()) { return result; } } return ReferenceError(name); } MaybeDirectHandle<Object> LoadGlobalIC::Load(Handle<Name> name, bool update_feedback) { Handle<JSGlobalObject> global = isolate()->global_object(); if (IsString(*name)) { // Look up in script context table. DirectHandle<String> str_name = Cast<String>(name); DirectHandle<ScriptContextTable> script_contexts( global->native_context()->script_context_table(), isolate()); VariableLookupResult lookup_result; if (script_contexts->Lookup(str_name, &lookup_result)) { DirectHandle<Context> script_context( script_contexts->get(lookup_result.context_index), isolate()); if (script_context->IsElementTheHole(lookup_result.slot_index)) { // Do not install stubs and stay pre-monomorphic for // uninitialized accesses. THROW_NEW_ERROR( isolate(), NewReferenceError(MessageTemplate::kAccessedUninitializedVariable, name)); } bool use_ic = (state() != NO_FEEDBACK) && v8_flags.use_ic && update_feedback; if (use_ic) { // 'const' Variables are mutable if REPL mode is enabled. This disables // compiler inlining for all 'const' variables declared in REPL mode. if (nexus()->ConfigureLexicalVarMode( lookup_result.context_index, lookup_result.slot_index, (IsImmutableLexicalVariableMode(lookup_result.mode) && !lookup_result.is_repl_mode))) { TRACE_HANDLER_STATS(isolate(), LoadGlobalIC_LoadScriptContextField); } else { // Given combination of indices can't be encoded, so use slow stub. TRACE_HANDLER_STATS(isolate(), LoadGlobalIC_SlowStub); SetCache(name, LoadHandler::LoadSlow(isolate())); } TraceIC("LoadGlobalIC", name); } else if (state() == NO_FEEDBACK) { TraceIC("LoadGlobalIC", name); } return Context::Get(script_context, lookup_result.slot_index, isolate()); } } return LoadIC::Load(global, name, update_feedback); } namespace { bool AddOneReceiverMapIfMissing(MapsAndHandlers* receiver_maps_and_handlers, Handle<Map> new_receiver_map) { DCHECK(!new_receiver_map.is_null()); if (new_receiver_map->is_deprecated()) return false; for (DirectHandle<Map> map : receiver_maps_and_handlers->maps()) { if (!map.is_null() && map.is_identical_to(new_receiver_map)) { return false; } } receiver_maps_and_handlers->emplace_back(new_receiver_map, {}); return true; } DirectHandle<NativeContext> GetAccessorContext( const CallOptimization& call_optimization, Tagged<Map> holder_map, Isolate* isolate) { std::optional<Tagged<NativeContext>> maybe_context = call_optimization.GetAccessorContext(holder_map); // Holders which are remote objects are not expected in the IC system. CHECK(maybe_context.has_value()); return direct_handle(maybe_context.value(), isolate); } } // namespace bool IC::UpdateMegaDOMIC(const MaybeObjectDirectHandle& handler, DirectHandle<Name> name) { if (!v8_flags.mega_dom_ic) return false; // TODO(gsathya): Enable fuzzing once this feature is more stable. if (v8_flags.fuzzing) return false; // TODO(gsathya): Support KeyedLoadIC, StoreIC and KeyedStoreIC. if (!IsLoadIC()) return false; // Check if DOM protector cell is valid. if (!Protectors::IsMegaDOMIntact(isolate())) return false; // Check if current lookup object is an API object DirectHandle<Map> map = lookup_start_object_map(); if (!InstanceTypeChecker::IsJSApiObject(map->instance_type())) return false; Handle<Object> accessor_obj; // TODO(gsathya): Check if there are overloads possible for this accessor and // transition only if it isn't possible. if (!accessor().ToHandle(&accessor_obj)) return false; // TODO(gsathya): This is also created in IC::ComputeHandler, find a way to // reuse it here. CallOptimization call_optimization(isolate(), accessor_obj); // Check if accessor is an API function if (!call_optimization.is_simple_api_call()) return false; // Check if accessor requires access checks if (call_optimization.accept_any_receiver()) return false; // Check if accessor requires signature checks if (!call_optimization.requires_signature_check()) return false; // Check if the receiver is the holder CallOptimization::HolderLookup holder_lookup; call_optimization.LookupHolderOfExpectedType(isolate(), map, &holder_lookup); if (holder_lookup != CallOptimization::kHolderIsReceiver) return false; DirectHandle<NativeContext> accessor_context = GetAccessorContext(call_optimization, *map, isolate()); DirectHandle<FunctionTemplateInfo> fti; if (IsJSFunction(*accessor_obj)) { fti = direct_handle( Cast<JSFunction>(*accessor_obj)->shared()->api_func_data(), isolate()); } else { fti = Cast<FunctionTemplateInfo>(accessor_obj); } DirectHandle<MegaDomHandler> new_handler = isolate()->factory()->NewMegaDomHandler( MaybeObjectDirectHandle::Weak(fti), MaybeObjectDirectHandle::Weak(accessor_context)); nexus()->ConfigureMegaDOM(MaybeObjectDirectHandle(new_handler)); return true; } bool IC::UpdateOneMapManyNamesIC(DirectHandle<Name> new_name) { #if V8_ENABLE_WEBASSEMBLY if (state() != MONOMORPHIC) return false; if (!IsKeyedLoadIC() && !IsKeyedStoreIC()) return false; // For JS objects, using the generic stub is faster. Wasm objects benefit // from collecting a map that the optimizing compiler can use. Tagged<Map> old_map = nexus()->GetFirstMap(); if (old_map.is_null() || !IsWasmObjectMap(old_map)) return false; if (old_map != *lookup_start_object_map()) return false; Tagged<Name> old_name = nexus()->GetName(); if (old_name.is_null()) return false; // Saw indexed access before. if (old_name == *new_name) return false; // Something else is wrong. DirectHandle<Smi> generic = IsKeyedLoadIC() ? LoadHandler::LoadGeneric(isolate()) : StoreHandler::StoreGeneric(isolate()); ConfigureVectorState(DirectHandle<Name>(), lookup_start_object_map(), generic); return true; #else return false; #endif // V8_ENABLE_WEBASSEMBLY } bool IC::UpdatePolymorphicIC(DirectHandle<Name> name, const MaybeObjectDirectHandle& handler) { DCHECK(IsHandler(*handler)); if (is_keyed() && state() != RECOMPUTE_HANDLER) { if (nexus()->GetName() != *name) return false; } DirectHandle<Map> map = lookup_start_object_map(); MapsAndHandlers maps_and_handlers(isolate()); maps_and_handlers.reserve(v8_flags.max_valid_polymorphic_map_count); int deprecated_maps = 0; int handler_to_overwrite = -1; { DisallowGarbageCollection no_gc; int i = 0; for (FeedbackIterator it(nexus()); !it.done(); it.Advance()) { if (it.handler().IsCleared()) continue; MaybeObjectDirectHandle existing_handler(it.handler(), isolate()); DirectHandle<Map> existing_map(it.map(), isolate()); maps_and_handlers.emplace_back(existing_map, existing_handler); if (existing_map->is_deprecated()) { // Filter out deprecated maps to ensure their instances get migrated. deprecated_maps++; } else if (map.is_identical_to(existing_map)) { // If both map and handler stayed the same (and the name is also the // same as checked above, for keyed accesses), we're not progressing // in the lattice and need to go MEGAMORPHIC instead. There's one // exception to this rule, which is when we're in RECOMPUTE_HANDLER // state, there we allow to migrate to a new handler. if (handler.is_identical_to(existing_handler) && state() != RECOMPUTE_HANDLER) { return false; } // If the receiver type is already in the polymorphic IC, this indicates // there was a prototoype chain failure. In that case, just overwrite // the handler. handler_to_overwrite = i; } else if (handler_to_overwrite == -1 && IsTransitionOfMonomorphicTarget(*existing_map, *map)) { handler_to_overwrite = i; } i++; } DCHECK_LE(i, maps_and_handlers.size()); } int number_of_maps = static_cast<int>(maps_and_handlers.size()); int number_of_valid_maps = number_of_maps - deprecated_maps - (handler_to_overwrite != -1); if (number_of_valid_maps >= v8_flags.max_valid_polymorphic_map_count) { return false; } if (deprecated_maps >= v8_flags.max_valid_polymorphic_map_count) { return false; } if (number_of_maps == 0 && state() != MONOMORPHIC && state() != POLYMORPHIC) { return false; } number_of_valid_maps++; if (number_of_valid_maps == 1) { ConfigureVectorState(name, lookup_start_object_map(), handler); } else { if (is_keyed() && nexus()->GetName() != *name) return false; if (handler_to_overwrite >= 0) { maps_and_handlers.set_handler(handler_to_overwrite, handler); if (!map.is_identical_to( maps_and_handlers.maps()[handler_to_overwrite])) { maps_and_handlers.set_map(handler_to_overwrite, map); } } else { maps_and_handlers.emplace_back(map, handler); } ConfigureVectorState(name, maps_and_handlers); } return true; } #ifdef V8_ENABLE_SPARKPLUG_PLUS namespace { Builtin CalculatePatchingTarget(Builtin current_builtin, Builtin handler) { static_assert(Builtin::kFirstLoadICHandler == Builtin::kLoadICUninitializedBaseline); static_assert(Builtin::kLastLoadICHandler == Builtin::kLoadICGenericBaseline); // Currently we only have LoadIC handlers. {current_builtin} should not be the // generic handler because we should be able to return early in that case. DCHECK(current_builtin >= Builtin::kFirstLoadICHandler && current_builtin < Builtin::kLastLoadICHandler); DCHECK(handler > Builtin::kFirstLoadICHandler && handler <= Builtin::kLastLoadICHandler); // No need to patch when the current and target handlers are the same. if (current_builtin == handler) return Builtin::kNoBuiltinId; // Uninitialized handler can be patch to any other handlers. if (current_builtin == Builtin::kLoadICUninitializedBaseline) { return handler; } // Other handlers are only allowed to be patched to a more generic handler. return Builtin::kLoadICGenericBaseline; } } // namespace #endif // V8_ENABLE_SPARKPLUG_PLUS void IC::MaybePatchCode(Builtin handler) { CHECK(v8_flags.sparkplug_plus); #ifdef V8_ENABLE_SPARKPLUG_PLUS if (handler == Builtin::kIllegal) return; if (!isolate()->is_short_builtin_calls_enabled()) return; // Patch baseline code if it is from baseline frame. if (caller_frame_type() == CallerFrameType::kBaseline) { const Address entry = Isolate::c_entry_fp(isolate_->thread_local_top()); Address* pc_address = reinterpret_cast<Address*>(entry + ExitFrameConstants::kCallerPCOffset); Address pc = StackFrame::ReadPC(pc_address) - Assembler::kCallTargetAddressOffset; Address current = Assembler::target_address_at(pc, kNullAddress); // TODO(chromium:429351411): Consider using a cache. Builtin current_builtin = OffHeapInstructionStream::TryLookupCode(isolate_, current); DCHECK_EQ(current, Builtins::EntryOf(current_builtin, isolate_)); Builtin target_builtin = CalculatePatchingTarget(current_builtin, handler); if (target_builtin == Builtin::kNoBuiltinId) return; Address target = Builtins::EntryOf(target_builtin, isolate_); WritableJitAllocation jit_allocation = WritableJitAllocation::ForPatchableBaselineJIT( pc, Assembler::kCallTargetAddressOffset); Assembler::set_target_address_at(pc, kNullAddress, target, &jit_allocation, FLUSH_ICACHE_IF_NEEDED); } #endif // V8_ENABLE_SPARKPLUG_PLUS } void IC::UpdateMonomorphicIC(const MaybeObjectDirectHandle& handler, DirectHandle<Name> name) { DCHECK(IsHandler(*handler)); ConfigureVectorState(name, lookup_start_object_map(), handler); } void IC::CopyICToMegamorphicCache(DirectHandle<Name> name) { MapsAndHandlers maps_and_handlers(isolate()); nexus()->ExtractMapsAndHandlers(&maps_and_handlers); for (auto [map, handler] : maps_and_handlers) { UpdateMegamorphicCache(map, name, handler); } } bool IC::IsTransitionOfMonomorphicTarget(Tagged<Map> source_map, Tagged<Map> target_map) { if (source_map.is_null()) return true; if (target_map.is_null()) return false; if (source_map->is_abandoned_prototype_map()) return false; ElementsKind target_elements_kind = target_map->elements_kind(); bool more_general_transition = IsMoreGeneralElementsKindTransition( source_map->elements_kind(), target_elements_kind); Tagged<Map> transitioned_map; if (more_general_transition) { DirectHandle<Map> single_map[1] = {direct_handle(target_map, isolate_)}; transitioned_map = source_map->FindElementsKindTransitionedMap( isolate(), single_map, ConcurrencyMode::kSynchronous); } return transitioned_map == target_map; } Builtin IC::GetHandlerPolymorphic() { if (IsLoadIC()) { return Builtin::kLoadICGenericBaseline; } return Builtin::kIllegal; } Builtin IC::GetHandlerMegamorphic() { if (IsLoadIC()) { return Builtin::kLoadICGenericBaseline; } return Builtin::kIllegal; } void IC::SetCache(DirectHandle<Name> name, Handle<Object> handler) { SetCache(name, MaybeObjectHandle(handler)); } void IC::SetCache(DirectHandle<Name> name, const MaybeObjectHandle& handler) { DCHECK(IsHandler(*handler)); // Currently only load and store ICs support non-code handlers. DCHECK(IsAnyLoad() || IsAnyStore() || IsAnyHas()); switch (state()) { case NO_FEEDBACK: UNREACHABLE(); case UNINITIALIZED: { UpdateMonomorphicIC(handler, name); if (v8_flags.sparkplug_plus) { Builtin ic_handler = FeedbackNexus::ic_handler(*handler, kind()); MaybePatchCode(ic_handler); } break; } case RECOMPUTE_HANDLER: case MONOMORPHIC: if (IsGlobalIC()) { UpdateMonomorphicIC(handler, name); break; } if (UpdateOneMapManyNamesIC(name)) break; [[fallthrough]]; case POLYMORPHIC: if (UpdatePolymorphicIC(name, handler)) { if (v8_flags.sparkplug_plus) { MaybePatchCode(GetHandlerPolymorphic()); } break; } if (UpdateMegaDOMIC(handler, name)) break; if (!is_keyed() || state() == RECOMPUTE_HANDLER) { CopyICToMegamorphicCache(name); } [[fallthrough]]; case MEGADOM: ConfigureVectorState(MEGAMORPHIC, name); [[fallthrough]]; case MEGAMORPHIC: UpdateMegamorphicCache(lookup_start_object_map(), name, handler); if (v8_flags.sparkplug_plus) { MaybePatchCode(GetHandlerMegamorphic()); } // Indicate that we've handled this case. vector_set_ = true; break; case GENERIC: UNREACHABLE(); } } void LoadIC::UpdateCaches(LookupIterator* lookup) { MaybeObjectHandle handler; if (lookup->state() == LookupIterator::ACCESS_CHECK) { handler = MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } else if (!lookup->IsFound()) { if (lookup->IsAnyPrivateName()) { handler = MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } else { TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNonexistentDH); handler = MaybeObjectHandle( LoadHandler::LoadNonExistent(isolate(), lookup_start_object_map())); } } else if (IsLoadGlobalIC() && lookup->state() == LookupIterator::JSPROXY) { // If there is proxy just install the slow stub since we need to call the // HasProperty trap for global loads. The ProxyGetProperty builtin doesn't // handle this case. handler = MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } else if (lookup->state() == LookupIterator::MODULE_NAMESPACE) { // We only hit this state for a non-evaluated deferred module state and we // keep trying to cache later. Once this deferred module gets evaluated // (likely this call), the access for this namespace object can be NOT_FOUND // or ACCESSOR, which will allow them to be cached as ordinary module // namespace access. #ifdef DEBUG DirectHandle<JSModuleNamespace> ns = lookup->GetHolder<JSModuleNamespace>(); DCHECK(IsJSDeferredModuleNamespace(*ns)); DCHECK_NE(ns->module()->status(), Module::kEvaluated); #endif return; } else { if (IsLoadGlobalIC()) { if (lookup->TryLookupCachedProperty()) { DCHECK_EQ(LookupIterator::DATA, lookup->state()); } if (lookup->state() == LookupIterator::DATA && lookup->GetReceiver().is_identical_to(lookup->GetHolder<Object>())) { DCHECK(IsJSGlobalObject(*lookup->GetReceiver())); // Now update the cell in the feedback vector. nexus()->ConfigurePropertyCellMode(lookup->GetPropertyCell()); TraceIC("LoadGlobalIC", lookup->name()); return; } } handler = ComputeHandler(lookup); CHECK(lookup->state() == LookupIterator::STRING_LOOKUP_START_OBJECT || *lookup->GetHolder<Object>() == *(lookup->lookup_start_object()) || LoadHandler::CanHandleHolderNotLookupStart(*handler.object())); } // Can't use {lookup->name()} because the LookupIterator might be in // "elements" mode for keys that are strings representing integers above // JSArray::kMaxIndex. SetCache(lookup->GetName(), handler); TraceIC("LoadIC", lookup->GetName()); } StubCache* IC::stub_cache() { // HasICs and each of the store own ICs require its own stub cache. // Until we create them, don't allow accessing the load/store stub caches. DCHECK(!IsAnyHas()); if (IsAnyLoad()) { return isolate()->load_stub_cache(); } else if (IsAnyDefineOwn()) { return isolate()->define_own_stub_cache(); } else { DCHECK(IsAnyStore()); return isolate()->store_stub_cache(); } } void IC::UpdateMegamorphicCache(DirectHandle<Map> map, DirectHandle<Name> name, const MaybeObjectDirectHandle& handler) { if (!IsAnyHas()) { stub_cache()->Set(*name, *map, *handler); } } MaybeObjectHandle LoadIC::ComputeHandler(LookupIterator* lookup) { DirectHandle<Object> receiver = lookup->GetReceiver(); ReadOnlyRoots roots(isolate()); DirectHandle<Object> lookup_start_object = lookup->lookup_start_object(); // `in` cannot be called on strings, and will always return true for string // wrapper length and function prototypes. The latter two cases are given // LoadHandler::LoadNativeDataProperty below. if (!IsAnyHas() && !lookup->IsElement()) { if (IsString(*lookup_start_object) && *lookup->name() == roots.length_string()) { TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength); return MaybeObjectHandle(BUILTIN_CODE(isolate(), LoadIC_StringLength)); } if (IsStringWrapper(*lookup_start_object) && *lookup->name() == roots.length_string()) { TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength); return MaybeObjectHandle( BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength)); } // Use specialized code for getting prototype of functions. if (IsJSFunction(*lookup_start_object) && *lookup->name() == roots.prototype_string() && !Cast<JSFunction>(*lookup_start_object) ->PrototypeRequiresRuntimeLookup()) { TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub); return MaybeObjectHandle( BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype)); } } DirectHandle<Map> map = lookup_start_object_map(); bool holder_is_lookup_start_object = lookup_start_object.is_identical_to(lookup->GetHolder<JSReceiver>()); switch (lookup->state()) { case LookupIterator::INTERCEPTOR: { DirectHandle<JSObject> holder = lookup->GetHolder<JSObject>(); DirectHandle<InterceptorInfo> interceptor_info( holder->GetNamedInterceptor(), isolate()); if (holder_is_lookup_start_object) { TRACE_HANDLER_STATS(isolate(), LoadIC_LoadInterceptorDH); if (interceptor_info->non_masking()) { TRACE_HANDLER_STATS(isolate(), LoadIC_LoadInterceptorNonMaskingDH); } Handle<LoadHandler> handler = LoadHandler::LoadInterceptorHolderIsLookupStartupObject( isolate(), map, interceptor_info); return MaybeObjectHandle(handler); } TRACE_HANDLER_STATS(isolate(), LoadIC_LoadInterceptorFromPrototypeDH); DirectHandle<JSObject> holder_for_api(lookup->GetHolderForApi(), isolate()); Tagged<Smi> smi_handler = LoadHandler::LoadInterceptor(interceptor_info->non_masking()); Handle<LoadHandler> handler = LoadHandler::LoadFromPrototype( isolate(), map, holder_for_api, smi_handler, {}, // no data1 (make it use holder instead). MaybeObjectDirectHandle(interceptor_info)); return MaybeObjectHandle(handler); } case LookupIterator::ACCESSOR: { Handle<JSObject> holder = indirect_handle(lookup->GetHolder<JSObject>(), isolate()); // Use simple field loads for some well-known callback properties. // The method will only return true for absolute truths based on the // lookup start object maps. FieldIndex field_index; if (Accessors::IsJSObjectFieldAccessor(isolate(), map, lookup->name(), &field_index)) { TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldDH); return MaybeObjectHandle( LoadHandler::LoadField(isolate(), field_index)); } if (IsJSModuleNamespace(*holder)) { DirectHandle<ObjectHashTable> exports( Cast<JSModuleNamespace>(holder)->module()->exports(), isolate()); InternalIndex entry = exports->FindEntry(isolate(), roots, lookup->name(), Smi::ToInt(Object::GetHash(*lookup->name()))); // We found the accessor, so the entry must exist. DCHECK(entry.is_found()); int value_index = ObjectHashTable::EntryToValueIndex(entry); Handle<Smi> smi_handler = LoadHandler::LoadModuleExport(isolate(), value_index); if (holder_is_lookup_start_object) { return MaybeObjectHandle(smi_handler); } return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *smi_handler)); } DirectHandle<Object> accessors = lookup->GetAccessors(); if (IsAccessorPair(*accessors)) { DirectHandle<AccessorPair> accessor_pair = Cast<AccessorPair>(accessors); if (lookup->TryLookupCachedProperty(accessor_pair)) { DCHECK_EQ(LookupIterator::DATA, lookup->state()); return MaybeObjectHandle(ComputeHandler(lookup)); } Handle<Object> getter(accessor_pair->getter(), isolate()); if (!IsCallableJSFunction(*getter) && !IsFunctionTemplateInfo(*getter)) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), LoadIC_SlowStub); return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } set_accessor(getter); if ((IsFunctionTemplateInfo(*getter) && Cast<FunctionTemplateInfo>(*getter)->BreakAtEntry(isolate())) || (IsJSFunction(*getter) && Cast<JSFunction>(*getter)->shared()->BreakAtEntry(isolate()))) { // Do not install an IC if the api function has a breakpoint. TRACE_HANDLER_STATS(isolate(), LoadIC_SlowStub); return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } Handle<Smi> smi_handler; CallOptimization call_optimization(isolate(), getter); if (call_optimization.is_simple_api_call()) { CallOptimization::HolderLookup holder_lookup; DirectHandle<JSObject> api_holder = call_optimization.LookupHolderOfExpectedType(isolate(), map, &holder_lookup); if (!call_optimization.IsCompatibleReceiverMap(api_holder, holder, holder_lookup) || !holder->HasFastProperties()) { TRACE_HANDLER_STATS(isolate(), LoadIC_SlowStub); return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } smi_handler = LoadHandler::LoadApiGetter(isolate()); DirectHandle<NativeContext> accessor_context = GetAccessorContext(call_optimization, holder->map(), isolate()); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadApiGetterFromPrototypeDH); return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *smi_handler, MaybeObjectDirectHandle::Weak(call_optimization.api_call_info()), MaybeObjectDirectHandle::Weak(accessor_context))); } if (holder->HasFastProperties()) { DCHECK(IsCallableJSFunction(*getter)); if (holder_is_lookup_start_object) { TRACE_HANDLER_STATS(isolate(), LoadIC_LoadAccessorDH); return MaybeObjectHandle::Weak( indirect_handle(accessor_pair, isolate())); } TRACE_HANDLER_STATS(isolate(), LoadIC_LoadAccessorFromPrototypeDH); return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *LoadHandler::LoadAccessorFromPrototype(isolate()), MaybeObjectDirectHandle::Weak(getter))); } if (IsJSGlobalObject(*holder)) { TRACE_HANDLER_STATS(isolate(), LoadIC_LoadGlobalFromPrototypeDH); smi_handler = LoadHandler::LoadGlobal(isolate()); return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *smi_handler, MaybeObjectDirectHandle::Weak(lookup->GetPropertyCell()))); } else { smi_handler = LoadHandler::LoadNormal(isolate()); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNormalDH); if (holder_is_lookup_start_object) return MaybeObjectHandle(smi_handler); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNormalFromPrototypeDH); } return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *smi_handler)); } DirectHandle<AccessorInfo> info = Cast<AccessorInfo>(accessors); if (info->replace_on_access()) { set_slow_stub_reason( "getter needs to be reconfigured to data property"); TRACE_HANDLER_STATS(isolate(), LoadIC_SlowStub); return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } if (!info->has_getter(isolate()) || !holder->HasFastProperties() || (info->is_sloppy() && !IsJSReceiver(*receiver))) { TRACE_HANDLER_STATS(isolate(), LoadIC_SlowStub); return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } Handle<Smi> smi_handler = LoadHandler::LoadNativeDataProperty( isolate(), lookup->GetAccessorIndex()); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNativeDataPropertyDH); if (holder_is_lookup_start_object) return MaybeObjectHandle(smi_handler); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNativeDataPropertyFromPrototypeDH); return MaybeObjectHandle( LoadHandler::LoadFromPrototype(isolate(), map, holder, *smi_handler)); } case LookupIterator::DATA: { DirectHandle<JSReceiver> holder = lookup->GetHolder<JSReceiver>(); DCHECK_EQ(PropertyKind::kData, lookup->property_details().kind()); Handle<Smi> smi_handler; if (lookup->is_dictionary_holder()) { if (IsJSGlobalObject(*holder, isolate())) { // TODO(verwaest): Also supporting the global object as receiver is a // workaround for code that leaks the global object. TRACE_HANDLER_STATS(isolate(), LoadIC_LoadGlobalDH); smi_handler = LoadHandler::LoadGlobal(isolate()); return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *smi_handler, MaybeObjectDirectHandle::Weak(lookup->GetPropertyCell()))); } smi_handler = LoadHandler::LoadNormal(isolate()); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNormalDH); if (holder_is_lookup_start_object) return MaybeObjectHandle(smi_handler); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadNormalFromPrototypeDH); } else if (lookup->IsElement(*holder)) { TRACE_HANDLER_STATS(isolate(), LoadIC_SlowStub); return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } else { DCHECK_EQ(PropertyLocation::kField, lookup->property_details().location()); DCHECK(IsJSObject(*holder, isolate())); FieldIndex field = lookup->GetFieldIndex(); smi_handler = LoadHandler::LoadField(isolate(), field); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldDH); if (holder_is_lookup_start_object) return MaybeObjectHandle(smi_handler); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadFieldFromPrototypeDH); } if (lookup->constness() == PropertyConstness::kConst && !holder_is_lookup_start_object) { DCHECK_IMPLIES(!V8_DICT_PROPERTY_CONST_TRACKING_BOOL, !lookup->is_dictionary_holder()); DirectHandle<Object> value = lookup->GetDataValue(); if (IsThinString(*value)) { value = direct_handle(Cast<ThinString>(*value)->actual(), isolate()); } // Non internalized strings could turn into thin/cons strings // when internalized. Weak references to thin/cons strings are // not supported in the GC. If concurrent marking is running // and the thin/cons string is marked but the actual string is // not, then the weak reference could be missed. if (!IsString(*value) || (IsString(*value) && IsInternalizedString(*value))) { MaybeObjectDirectHandle weak_value = IsSmi(*value) ? MaybeObjectHandle(*value, isolate()) : MaybeObjectHandle::Weak(*value, isolate()); smi_handler = LoadHandler::LoadConstantFromPrototype(isolate()); TRACE_HANDLER_STATS(isolate(), LoadIC_LoadConstantFromPrototypeDH); return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder, *smi_handler, weak_value)); } } return MaybeObjectHandle( LoadHandler::LoadFromPrototype(isolate(), map, holder, *smi_handler)); } case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: TRACE_HANDLER_STATS(isolate(), LoadIC_LoadIntegerIndexedExoticDH); return MaybeObjectHandle(LoadHandler::LoadNonExistent(isolate())); case LookupIterator::JSPROXY: { // Private names on JSProxy is currently not supported. if (lookup->name()->IsAnyPrivate()) { return MaybeObjectHandle(LoadHandler::LoadSlow(isolate())); } Handle<Smi> smi_handler = LoadHandler::LoadProxy(isolate()); if (holder_is_lookup_start_object) return MaybeObjectHandle(smi_handler); DirectHandle<JSProxy> holder_proxy = lookup->GetHolder<JSProxy>(); return MaybeObjectHandle(LoadHandler::LoadFromPrototype( isolate(), map, holder_proxy, *smi_handler)); } case LookupIterator::MODULE_NAMESPACE: case LookupIterator::WASM_OBJECT: case LookupIterator::ACCESS_CHECK: case LookupIterator::NOT_FOUND: case LookupIterator::TRANSITION: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); } return MaybeObjectHandle(Handle<InstructionStream>::null()); } KeyedAccessLoadMode KeyedLoadIC::GetKeyedAccessLoadModeFor( DirectHandle<Map> receiver_map) const { const MaybeObjectDirectHandle& handler = nexus()->FindHandlerForMap(receiver_map); if (handler.is_null()) return KeyedAccessLoadMode::kInBounds; return LoadHandler::GetKeyedAccessLoadMode(*handler); } // Returns whether the load mode transition is allowed. bool AllowedHandlerChange(KeyedAccessLoadMode old_mode, KeyedAccessLoadMode new_mode) { // Only allow transitions to allow OOB or allow converting a hole to // undefined. using T = std::underlying_type_t<KeyedAccessLoadMode>; return ((static_cast<T>(old_mode) ^ static_cast<T>(GeneralizeKeyedAccessLoadMode(old_mode, new_mode))) & 0b11) != 0; } void KeyedLoadIC::UpdateLoadElement(DirectHandle<HeapObject> receiver, const KeyedAccessLoadMode new_load_mode) { Handle<Map> receiver_map(receiver->map(), isolate()); DCHECK(receiver_map->instance_type() != JS_PRIMITIVE_WRAPPER_TYPE); // Checked by caller. MapsAndHandlers target_maps_and_handlers(isolate()); nexus()->ExtractMapsAndHandlers(&target_maps_and_handlers); if (target_maps_and_handlers.empty()) { DirectHandle<Object> handler = LoadElementHandler(receiver_map, new_load_mode); return ConfigureVectorState(DirectHandle<Name>(), receiver_map, handler); } for (DirectHandle<Map> map : target_maps_and_handlers.maps()) { if (map.is_null()) continue; if (map->instance_type() == JS_PRIMITIVE_WRAPPER_TYPE) { set_slow_stub_reason("JSPrimitiveWrapper"); return; } if (map->instance_type() == JS_PROXY_TYPE) { set_slow_stub_reason("JSProxy"); return; } } // The first time a receiver is seen that is a transitioned version of the // previous monomorphic receiver type, assume the new ElementsKind is the // monomorphic type. This benefits global arrays that only transition // once, and all call sites accessing them are faster if they remain // monomorphic. If this optimistic assumption is not true, the IC will // miss again and it will become polymorphic and support both the // untransitioned and transitioned maps. if (state() == MONOMORPHIC) { if ((IsJSObject(*receiver) && IsMoreGeneralElementsKindTransition( target_maps_and_handlers[0].first->elements_kind(), Cast<JSObject>(receiver)->GetElementsKind())) || IsWasmObject(*receiver)) { DirectHandle<Object> handler = LoadElementHandler(receiver_map, new_load_mode); return ConfigureVectorState(DirectHandle<Name>(), receiver_map, handler); } } DCHECK(state() != GENERIC); // Determine the list of receiver maps that this call site has seen, // adding the map that was just encountered. KeyedAccessLoadMode old_load_mode = KeyedAccessLoadMode::kInBounds; if (!AddOneReceiverMapIfMissing(&target_maps_and_handlers, receiver_map)) { old_load_mode = GetKeyedAccessLoadModeFor(receiver_map); if (!AllowedHandlerChange(old_load_mode, new_load_mode)) { set_slow_stub_reason("same map added twice"); return; } } // If the maximum number of receiver maps has been exceeded, use the generic // version of the IC. if (static_cast<int>(target_maps_and_handlers.size()) > v8_flags.max_valid_polymorphic_map_count) { set_slow_stub_reason("max polymorph exceeded"); return; } MapsAndHandlers new_maps_and_handlers(isolate()); new_maps_and_handlers.reserve(target_maps_and_handlers.size()); KeyedAccessLoadMode load_mode = GeneralizeKeyedAccessLoadMode(old_load_mode, new_load_mode); // Update the polymorphic handlers with load_mode. LoadElementPolymorphicHandlers(&target_maps_and_handlers, &new_maps_and_handlers, load_mode); if (new_maps_and_handlers.empty()) { DirectHandle<Object> handler = LoadElementHandler(receiver_map, new_load_mode); ConfigureVectorState(DirectHandle<Name>(), receiver_map, handler); } else if (new_maps_and_handlers.size() == 1) { ConfigureVectorState(DirectHandle<Name>(), new_maps_and_handlers[0].first, new_maps_and_handlers[0].second); } else { ConfigureVectorState(DirectHandle<Name>(), new_maps_and_handlers); } } namespace { bool AllowConvertHoleElementToUndefined(Isolate* isolate, DirectHandle<Map> receiver_map) { if (IsJSTypedArrayMap(*receiver_map)) { // For JSTypedArray we never lookup elements in the prototype chain. return true; } // For other {receiver}s we need to check the "no elements" protector. if (Protectors::IsNoElementsIntact(isolate)) { if (IsStringMap(*receiver_map)) { return true; } if (IsJSObjectMap(*receiver_map)) { // For other JSObjects (including JSArrays) we can only continue if // the {receiver}s prototype is either the initial Object.prototype // or the initial Array.prototype, which are both guarded by the // "no elements" protector checked above. DirectHandle<HeapObject> receiver_prototype(receiver_map->prototype(), isolate); InstanceType prototype_type = receiver_prototype->map()->instance_type(); if (prototype_type == JS_OBJECT_PROTOTYPE_TYPE || (prototype_type == JS_ARRAY_TYPE && isolate->IsInCreationContext( Cast<JSObject>(*receiver_prototype), Context::INITIAL_ARRAY_PROTOTYPE_INDEX))) { return true; } } } return false; } bool IsOutOfBoundsAccess(DirectHandle<Object> receiver, size_t index) { size_t length; if (IsJSArray(*receiver)) { length = Object::NumberValue(Cast<JSArray>(*receiver)->length()); } else if (IsJSTypedArray(*receiver)) { length = Cast<JSTypedArray>(*receiver)->GetLength(); } else if (IsJSObject(*receiver)) { length = Cast<JSObject>(*receiver)->elements()->length(); } else if (IsString(*receiver)) { length = Cast<String>(*receiver)->length(); } else { return false; } return index >= length; } KeyedAccessLoadMode GetNewKeyedLoadMode(Isolate* isolate, DirectHandle<HeapObject> receiver, size_t index, bool is_found, MaybeDirectHandle<Object> result) { DirectHandle<Map> receiver_map(Cast<HeapObject>(receiver)->map(), isolate); if (!AllowConvertHoleElementToUndefined(isolate, receiver_map)) { return KeyedAccessLoadMode::kInBounds; } // Always handle holes when the elements kind is HOLEY_ELEMENTS, since the // optimizer compilers can not benefit from this information to narrow the // type. That is, the load type will always just be a generic tagged value. // This avoid an IC miss if we see a hole. ElementsKind elements_kind = receiver_map->elements_kind(); bool always_handle_holes = (elements_kind == HOLEY_ELEMENTS); // In bound access and did not read a hole. if (is_found) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE // We can encode undefined in HOLEY_DOUBLE_ELEMENTS, so we always have to // check for those even if we found a value. if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { DirectHandle<Object> result_handle; if (result.ToHandle(&result_handle)) { if (Is<Undefined>(result_handle)) { always_handle_holes = true; } } } #endif // V8_ENABLE_UNDEFINED_DOUBLE return always_handle_holes ? KeyedAccessLoadMode::kHandleHoles : KeyedAccessLoadMode::kInBounds; } // OOB access. bool is_oob_access = IsOutOfBoundsAccess(receiver, index); if (is_oob_access) { return always_handle_holes ? KeyedAccessLoadMode::kHandleOOBAndHoles : KeyedAccessLoadMode::kHandleOOB; } // Read a hole. DCHECK(!is_found && !is_oob_access); bool handle_hole = IsHoleyElementsKind(elements_kind); DCHECK_IMPLIES(always_handle_holes, handle_hole); return handle_hole ? KeyedAccessLoadMode::kHandleHoles : KeyedAccessLoadMode::kInBounds; } KeyedAccessLoadMode GetUpdatedLoadModeForMap(Isolate* isolate, DirectHandle<Map> map, KeyedAccessLoadMode old_load_mode, KeyedAccessLoadMode load_mode) { // If we are not allowed to convert a hole to undefined, then we should not // handle OOB nor reading holes. if (!AllowConvertHoleElementToUndefined(isolate, map)) { return KeyedAccessLoadMode::kInBounds; } // Check if the elements kind allow reading a hole. bool allow_reading_hole_element = IsHoleyElementsKind(map->elements_kind()); bool old_allow_oob = LoadModeHandlesOOB(old_load_mode); bool old_allow_holes = LoadModeHandlesHoles(old_load_mode); bool new_allow_oob = LoadModeHandlesOOB(load_mode); bool new_allow_holes = LoadModeHandlesHoles(load_mode); bool updated_allow_oob = old_allow_oob || new_allow_oob; bool updated_allow_holes = allow_reading_hole_element && (old_allow_holes || new_allow_holes); return CreateKeyedAccessLoadMode(updated_allow_oob, updated_allow_holes); } } // namespace Handle<Object> KeyedLoadIC::LoadElementHandler( DirectHandle<Map> receiver_map, KeyedAccessLoadMode new_load_mode, MaybeDirectHandle<Map> maybe_transition_target) { // Has a getter interceptor, or is any has and has a query interceptor. if (receiver_map->has_indexed_interceptor() && (receiver_map->GetIndexedInterceptor()->has_getter() || (IsAnyHas() && receiver_map->GetIndexedInterceptor()->has_query())) && !receiver_map->GetIndexedInterceptor()->non_masking()) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub); return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC) : BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC); } InstanceType instance_type = receiver_map->instance_type(); if (instance_type < FIRST_NONSTRING_TYPE) { TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedStringDH); if (IsAnyHas()) return LoadHandler::LoadSlow(isolate()); return LoadHandler::LoadIndexedString(isolate(), new_load_mode); } if (instance_type < FIRST_JS_RECEIVER_TYPE) { TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_SlowStub); return LoadHandler::LoadSlow(isolate()); } if (instance_type == JS_PROXY_TYPE) { return LoadHandler::LoadProxy(isolate()); } #if V8_ENABLE_WEBASSEMBLY if (InstanceTypeChecker::IsWasmObject(instance_type)) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_SlowStub); return LoadHandler::LoadSlow(isolate()); } #endif // V8_ENABLE_WEBASSEMBLY ElementsKind elements_kind = receiver_map->elements_kind(); if (IsSloppyArgumentsElementsKind(elements_kind)) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_KeyedLoadSloppyArgumentsStub); return IsAnyHas() ? BUILTIN_CODE(isolate(), KeyedHasIC_SloppyArguments) : BUILTIN_CODE(isolate(), KeyedLoadIC_SloppyArguments); } bool is_js_array = instance_type == JS_ARRAY_TYPE; if (elements_kind == DICTIONARY_ELEMENTS) { TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadElementDH); return LoadHandler::LoadElement(isolate(), elements_kind, is_js_array, new_load_mode); } DCHECK(IsFastElementsKind(elements_kind) || IsAnyNonextensibleElementsKind(elements_kind) || IsTypedArrayOrRabGsabTypedArrayElementsKind(elements_kind)); DCHECK_IMPLIES( LoadModeHandlesHoles(new_load_mode), IsHoleyElementsKind(elements_kind) && AllowConvertHoleElementToUndefined(isolate(), receiver_map)); DirectHandle<Map> transition_target; if (is_js_array && maybe_transition_target.ToHandle(&transition_target)) { DCHECK(IsFastElementsKind(elements_kind)); return LoadHandler::TransitionAndLoadElement( isolate(), transition_target->elements_kind(), new_load_mode); } TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadElementDH); return LoadHandler::LoadElement(isolate(), elements_kind, is_js_array, new_load_mode); } void KeyedLoadIC::LoadElementPolymorphicHandlers( MapsAndHandlers* old_maps_and_handlers, MapsAndHandlers* new_maps_and_handlers, KeyedAccessLoadMode new_load_mode) { for (auto [old_map, maybe_old_handler] : *old_maps_and_handlers) { // Filter out deprecated maps to ensure their instances get migrated. if (old_map->is_deprecated()) continue; KeyedAccessLoadMode old_load_mode = KeyedAccessLoadMode::kInBounds; if (!maybe_old_handler.is_null()) { old_load_mode = LoadHandler::GetKeyedAccessLoadMode(*maybe_old_handler); } Tagged<Map> tmap = old_map->FindElementsKindTransitionedMap( isolate(), MapHandlesSpan(old_maps_and_handlers->maps().begin(), old_maps_and_handlers->maps().end()), ConcurrencyMode::kSynchronous); if (!tmap.is_null()) { // Mark all stable receiver maps that have elements kind transition map // among receiver_maps as unstable because the ICs and the optimizing // compilers may perform an elements kind transition for this kind of // receivers. if (old_map->is_stable()) { old_map->NotifyLeafMapLayoutChange(isolate()); } new_maps_and_handlers->emplace_back( old_map, MaybeObjectHandle(LoadElementHandler( old_map, GetUpdatedLoadModeForMap(isolate(), old_map, old_load_mode, new_load_mode), handle(tmap, isolate())))); } else { new_maps_and_handlers->emplace_back( old_map, MaybeObjectHandle(LoadElementHandler( old_map, GetUpdatedLoadModeForMap( isolate(), old_map, old_load_mode, new_load_mode)))); } } } namespace { enum KeyType { kIntPtr, kName, kBailout }; // The cases where kIntPtr is returned must match what // CodeStubAssembler::TryToIntptr can handle! KeyType TryConvertKey(Handle<Object> key, Isolate* isolate, intptr_t* index_out, Handle<Name>* name_out) { if (IsSmi(*key)) { *index_out = Smi::ToInt(*key); return kIntPtr; } if (IsHeapNumber(*key)) { double num = Cast<HeapNumber>(*key)->value(); if (!(num >= -kMaxSafeInteger)) return kBailout; if (num > kMaxSafeInteger) return kBailout; *index_out = static_cast<intptr_t>(num); if (*index_out != num) return kBailout; return kIntPtr; } if (IsString(*key)) { key = isolate->factory()->InternalizeString(Cast<String>(key)); uint32_t maybe_array_index; if (Cast<String>(*key)->AsArrayIndex(&maybe_array_index)) { if (maybe_array_index <= INT_MAX) { *index_out = static_cast<intptr_t>(maybe_array_index); return kIntPtr; } // {key} is a string representation of an array index beyond the range // that the IC could handle. Don't try to take the named-property path. return kBailout; } *name_out = Cast<String>(key); return kName; } if (IsSymbol(*key)) { *name_out = Cast<Symbol>(key); return kName; } return kBailout; } bool IntPtrKeyToSize(intptr_t index, DirectHandle<HeapObject> receiver, size_t* out) { if (index < 0) { if (IsJSTypedArray(*receiver)) { // For JSTypedArray receivers, we can support negative keys, which we // just map to a very large value. This is valid because all OOB accesses // (negative or positive) are handled the same way, and size_t::max is // guaranteed to be an OOB access. *out = std::numeric_limits<size_t>::max(); return true; } return false; } #if V8_HOST_ARCH_64_BIT if (index > JSObject::kMaxElementIndex && !IsJSTypedArray(*receiver)) { return false; } #else // On 32-bit platforms, any intptr_t is less than kMaxElementIndex. static_assert( static_cast<double>(std::numeric_limits<decltype(index)>::max()) <= static_cast<double>(JSObject::kMaxElementIndex)); #endif *out = static_cast<size_t>(index); return true; } bool CanCache(DirectHandle<Object> receiver, InlineCacheState state) { if (!v8_flags.use_ic || state == NO_FEEDBACK) return false; if (!IsJSReceiver(*receiver) && !IsString(*receiver)) return false; return !IsAccessCheckNeeded(*receiver) && !IsJSPrimitiveWrapper(*receiver); } } // namespace MaybeDirectHandle<Object> KeyedLoadIC::RuntimeLoad(DirectHandle<JSAny> object, DirectHandle<Object> key, bool* is_found) { DirectHandle<Object> result; if (IsKeyedLoadIC()) { ASSIGN_RETURN_ON_EXCEPTION( isolate(), result, Runtime::GetObjectProperty(isolate(), object, key, DirectHandle<JSAny>(), is_found)); } else { DCHECK(IsKeyedHasIC()); ASSIGN_RETURN_ON_EXCEPTION(isolate(), result, Runtime::HasProperty(isolate(), object, key)); } return result; } MaybeDirectHandle<Object> KeyedLoadIC::LoadName(Handle<JSAny> object, DirectHandle<Object> key, Handle<Name> name) { DirectHandle<Object> load_handle; ASSIGN_RETURN_ON_EXCEPTION(isolate(), load_handle, LoadIC::Load(object, name)); if (vector_needs_update()) { ConfigureVectorState(MEGAMORPHIC, key); TraceIC("LoadIC", key); } DCHECK(!load_handle.is_null()); return load_handle; } MaybeDirectHandle<Object> KeyedLoadIC::Load(Handle<JSAny> object, Handle<Object> key) { if (MigrateDeprecated(isolate(), object)) { return RuntimeLoad(object, key); } intptr_t maybe_index; Handle<Name> maybe_name; KeyType key_type = TryConvertKey(key, isolate(), &maybe_index, &maybe_name); if (key_type == kName) return LoadName(object, key, maybe_name); bool is_found = false; MaybeDirectHandle<Object> result = RuntimeLoad(object, key, &is_found); size_t index; if (key_type == kIntPtr && CanCache(object, state()) && IntPtrKeyToSize(maybe_index, Cast<HeapObject>(object), &index)) { DirectHandle<HeapObject> receiver = Cast<HeapObject>(object); KeyedAccessLoadMode load_mode = GetNewKeyedLoadMode(isolate(), receiver, index, is_found, result); UpdateLoadElement(receiver, load_mode); if (is_vector_set()) { TraceIC("LoadIC", key); } } if (vector_needs_update()) { ConfigureVectorState(MEGAMORPHIC, key); TraceIC("LoadIC", key); } return result; } bool StoreIC::LookupForWrite(LookupIterator* it, DirectHandle<Object> value, StoreOrigin store_origin) { // Disable ICs for non-JSObjects for now. DirectHandle<Object> object = it->GetReceiver(); if (IsJSProxy(*object)) return true; if (!IsJSObject(*object) && !IsWasmObject(*object)) return false; DirectHandle<JSReceiver> receiver = Cast<JSReceiver>(object); DCHECK(!receiver->map()->is_deprecated()); for (;; it->Next()) { switch (it->state()) { case LookupIterator::TRANSITION: UNREACHABLE(); case LookupIterator::WASM_OBJECT: continue; // Continue to the prototype, if present. case LookupIterator::JSPROXY: return true; case LookupIterator::MODULE_NAMESPACE: return false; case LookupIterator::INTERCEPTOR: { DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); Tagged<InterceptorInfo> info = holder->GetNamedInterceptor(); if (it->HolderIsReceiverOrHiddenPrototype() || info->has_getter() || info->has_query()) { return true; } continue; } case LookupIterator::ACCESS_CHECK: // ICs know how to perform access checks on global proxies. if (!IsAccessCheckNeeded(*it->GetHolder<JSObject>())) continue; return false; case LookupIterator::ACCESSOR: return !it->IsReadOnly(); case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: return false; case LookupIterator::DATA: { if (it->IsReadOnly()) return false; if (IsAnyDefineOwn() && it->property_attributes() != NONE) { // IC doesn't support reconfiguration of property attributes, // so just bail out to the slow handler. return false; } DirectHandle<JSObject> holder = it->GetHolder<JSObject>(); if (receiver.is_identical_to(holder)) { it->PrepareForDataProperty(value); // The previous receiver map might just have been deprecated, // so reload it. update_lookup_start_object_map(receiver); return true; } // Receiver != holder. if (IsJSGlobalProxy(*receiver)) { PrototypeIterator iter(isolate(), receiver); return it->GetHolder<Object>().is_identical_to( PrototypeIterator::GetCurrent(iter)); } if (it->HolderIsReceiverOrHiddenPrototype()) return false; if (it->ExtendingNonExtensible(receiver)) return false; DirectHandle<JSTransitionableReceiver> rec = Cast<JSTransitionableReceiver>(receiver); it->PrepareTransitionToDataProperty(rec, value, NONE, store_origin); return it->IsCacheableTransition(); } case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::NOT_FOUND: // If we are in StoreGlobal then check if we should throw on // non-existent properties. // TODO(ishell): make store global ic kind reliable, currently it is // not if the feedback vector is not available. if (IsStoreGlobalIC() && (GetShouldThrow(it->isolate(), Nothing<ShouldThrow>()) == ShouldThrow::kThrowOnError)) { // ICs typically does the store in two steps: prepare receiver for the // transition followed by the actual store. For global objects we // create a property cell when preparing for transition and install // this cell in the handler. In strict mode, we throw and never // initialize this property cell. The IC handler assumes that the // property cell it is holding is for a property that is existing. // This case violates this assumption. If we happen to invalidate this // property cell later, it leads to incorrect behaviour. For now just // use a slow stub and don't install the property cell for these // cases. Hopefully these cases are not frequent enough to impact // performance. // // TODO(mythria): If we find this to be happening often, we could // install a new kind of handler for non-existent properties. These // handlers can then miss to runtime if the value is not hole (i.e. // cell got invalidated) and handle these stores correctly. return false; } receiver = it->GetStoreTarget<JSReceiver>(); if (it->ExtendingNonExtensible(receiver)) return false; DirectHandle<JSTransitionableReceiver> rec = Cast<JSTransitionableReceiver>(receiver); it->PrepareTransitionToDataProperty(rec, value, NONE, store_origin); return it->IsCacheableTransition(); } UNREACHABLE(); } } MaybeDirectHandle<Object> StoreGlobalIC::Store(Handle<Name> name, DirectHandle<Object> value) { DCHECK(IsString(*name)); // Look up in script context table. DirectHandle<String> str_name = Cast<String>(name); Handle<JSGlobalObject> global = isolate()->global_object(); DirectHandle<ScriptContextTable> script_contexts( global->native_context()->script_context_table(), isolate()); VariableLookupResult lookup_result; if (script_contexts->Lookup(str_name, &lookup_result)) { DisallowGarbageCollection no_gc; DisableGCMole no_gcmole; Tagged<Context> script_context = script_contexts->get(lookup_result.context_index); if (IsImmutableLexicalVariableMode(lookup_result.mode)) { AllowGarbageCollection yes_gc; return TypeError(MessageTemplate::kConstAssign, global, name); } if (script_context->IsElementTheHole(lookup_result.slot_index)) { // Do not install stubs and stay pre-monomorphic for uninitialized // accesses. AllowGarbageCollection yes_gc; THROW_NEW_ERROR( isolate(), NewReferenceError(MessageTemplate::kAccessedUninitializedVariable, name)); } bool use_ic = (state() != NO_FEEDBACK) && v8_flags.use_ic; if (use_ic) { if (nexus()->ConfigureLexicalVarMode( lookup_result.context_index, lookup_result.slot_index, IsImmutableLexicalVariableMode(lookup_result.mode))) { TRACE_HANDLER_STATS(isolate(), StoreGlobalIC_StoreScriptContextField); } else { // Given combination of indices can't be encoded, so use slow stub. TRACE_HANDLER_STATS(isolate(), StoreGlobalIC_SlowStub); SetCache(name, StoreHandler::StoreSlow(isolate())); } TraceIC("StoreGlobalIC", name); } else if (state() == NO_FEEDBACK) { TraceIC("StoreGlobalIC", name); } AllowGarbageCollection yes_gc; Context::Set(handle(script_context, isolate()), lookup_result.slot_index, value, isolate()); return value; } return StoreIC::Store(global, name, value); } namespace { Maybe<bool> DefineOwnDataProperty(LookupIterator* it, LookupIterator::State original_state, DirectHandle<JSAny> value, Maybe<ShouldThrow> should_throw, StoreOrigin store_origin) { // It should not be possible to call DefineOwnDataProperty in a // contextual store (indicated by IsJSGlobalObject()). DCHECK(!IsJSGlobalObject(*it->GetReceiver(), it->isolate())); // Handle special cases that can't be handled by // DefineOwnPropertyIgnoreAttributes first. switch (it->state()) { case LookupIterator::JSPROXY: { PropertyDescriptor new_desc; new_desc.set_value(value); new_desc.set_writable(true); new_desc.set_enumerable(true); new_desc.set_configurable(true); DCHECK_EQ(original_state, LookupIterator::JSPROXY); // TODO(joyee): this will start the lookup again. Ideally we should // implement something that reuses the existing LookupIterator. return JSProxy::DefineOwnProperty(it->isolate(), it->GetHolder<JSProxy>(), it->GetName(), &new_desc, should_throw); } case LookupIterator::WASM_OBJECT: RETURN_FAILURE(it->isolate(), kThrowOnError, NewTypeError(MessageTemplate::kWasmObjectsAreOpaque)); // When lazy feedback is disabled, the original state could be different // while the object is already prepared for TRANSITION. case LookupIterator::TRANSITION: { switch (original_state) { case LookupIterator::MODULE_NAMESPACE: case LookupIterator::JSPROXY: case LookupIterator::WASM_OBJECT: case LookupIterator::TRANSITION: case LookupIterator::DATA: case LookupIterator::INTERCEPTOR: case LookupIterator::ACCESSOR: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); case LookupIterator::ACCESS_CHECK: { DCHECK(!IsAccessCheckNeeded(*it->GetHolder<JSObject>())); [[fallthrough]]; } case LookupIterator::NOT_FOUND: return Object::AddDataProperty(it, value, NONE, Nothing<ShouldThrow>(), store_origin, EnforceDefineSemantics::kDefine); } } case LookupIterator::MODULE_NAMESPACE: case LookupIterator::ACCESS_CHECK: case LookupIterator::NOT_FOUND: case LookupIterator::DATA: case LookupIterator::ACCESSOR: case LookupIterator::INTERCEPTOR: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: break; case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); } // We need to restart to handle interceptors properly. it->Restart(); return JSObject::DefineOwnPropertyIgnoreAttributes( it, value, NONE, should_throw, JSObject::DONT_FORCE_FIELD, EnforceDefineSemantics::kDefine, store_origin); } } // namespace MaybeDirectHandle<Object> StoreIC::Store(Handle<JSAny> object, Handle<Name> name, DirectHandle<Object> value, StoreOrigin store_origin) { // TODO(verwaest): Let SetProperty do the migration, since storing a property // might deprecate the current map again, if value does not fit. if (MigrateDeprecated(isolate(), object)) { // KeyedStoreIC should handle DefineKeyedOwnIC with deprecated maps directly // instead of reusing this method. DCHECK(!IsDefineKeyedOwnIC()); DCHECK(!name->IsAnyPrivateName()); PropertyKey key(isolate(), name); if (IsDefineNamedOwnIC()) { MAYBE_RETURN_NULL(JSReceiver::CreateDataProperty( isolate(), object, key, value, Nothing<ShouldThrow>())); } else { LookupIterator it(isolate(), object, key, LookupIterator::DEFAULT); MAYBE_RETURN_NULL(Object::SetProperty(&it, value, StoreOrigin::kNamed)); } return value; } bool use_ic = (state() != NO_FEEDBACK) && v8_flags.use_ic; // If the object is undefined or null it's illegal to try to set any // properties on it; throw a TypeError in that case. if (IsNullOrUndefined(*object, isolate())) { if (use_ic) { // Ensure the IC state progresses. TRACE_HANDLER_STATS(isolate(), StoreIC_NonReceiver); update_lookup_start_object_map(object); SetCache(name, StoreHandler::StoreSlow(isolate())); TraceIC("StoreIC", name); } return TypeError(MessageTemplate::kNonObjectPropertyStoreWithProperty, name, object); } JSObject::MakePrototypesFast(object, kStartAtPrototype, isolate()); PropertyKey key(isolate(), name); LookupIterator it( isolate(), object, key, IsAnyDefineOwn() ? LookupIterator::OWN : LookupIterator::DEFAULT); if (name->IsAnyPrivate()) { if (name->IsAnyPrivateName()) { DCHECK(!IsDefineNamedOwnIC()); Maybe<bool> can_store = JSReceiver::CheckPrivateNameStore(&it, IsDefineKeyedOwnIC()); MAYBE_RETURN_NULL(can_store); if (!can_store.FromJust()) { return isolate()->factory()->undefined_value(); } } // IC handling of private fields/symbols stores on JSProxy is not // supported. if (IsJSProxy(*object)) { use_ic = false; } } // For IsAnyDefineOwn(), we can't simply do CreateDataProperty below // because we need to check the attributes before UpdateCaches updates // the state of the LookupIterator. LookupIterator::State original_state = it.state(); // We'll defer the check for JSProxy and objects with named interceptors, // because the defineProperty traps need to be called first if they are // present. We can also skip this for private names since they are not // bound by configurability or extensibility checks, and errors would've // been thrown if the private field already exists in the object. if (IsAnyDefineOwn() && !name->IsAnyPrivateName() && IsJSObject(*object) && !Cast<JSObject>(object)->HasNamedInterceptor()) { Maybe<bool> can_define = JSObject::CheckIfCanDefineAsConfigurable( isolate(), &it, value, Nothing<ShouldThrow>()); MAYBE_RETURN_NULL(can_define); if (!can_define.FromJust()) { return isolate()->factory()->undefined_value(); } // Restart the lookup iterator updated by CheckIfCanDefineAsConfigurable() // for UpdateCaches() to handle access checks. if (use_ic && IsAccessCheckNeeded(*object)) { it.Restart(); } } if (use_ic) { UpdateCaches(&it, value, store_origin); } else if (state() == NO_FEEDBACK) { // Tracing IC Stats for No Feedback State. IsStoreGlobalIC() ? TraceIC("StoreGlobalIC", name) : TraceIC("StoreIC", name); } // TODO(v8:12548): refactor DefinedNamedOwnIC and SetNamedIC as subclasses // of StoreIC so their logic doesn't get mixed here. // ES #sec-definefield // ES #sec-runtime-semantics-propertydefinitionevaluation // IsAnyDefineOwn() can be true when this method is reused by KeyedStoreIC. if (IsAnyDefineOwn()) { if (name->IsAnyPrivateName()) { // We should define private fields without triggering traps or checking // extensibility. MAYBE_RETURN_NULL( JSReceiver::AddPrivateField(&it, value, Nothing<ShouldThrow>())); } else { MAYBE_RETURN_NULL( DefineOwnDataProperty(&it, original_state, Cast<JSAny>(value), Nothing<ShouldThrow>(), store_origin)); } } else { // TODO(ishell): deduce should throw from IC kind once it's reliable, // currently it is not if the feedback vector is not available. MAYBE_RETURN_NULL(Object::SetProperty(&it, value, store_origin)); } return value; } void StoreIC::UpdateCaches(LookupIterator* lookup, DirectHandle<Object> value, StoreOrigin store_origin) { MaybeObjectHandle handler; if (LookupForWrite(lookup, value, store_origin)) { if (IsStoreGlobalIC()) { if (lookup->state() == LookupIterator::DATA && lookup->GetReceiver().is_identical_to(lookup->GetHolder<Object>())) { DCHECK(IsJSGlobalObject(*lookup->GetReceiver())); // Now update the cell in the feedback vector. nexus()->ConfigurePropertyCellMode(lookup->GetPropertyCell()); TraceIC("StoreGlobalIC", lookup->GetName()); return; } } handler = ComputeHandler(lookup); } else { set_slow_stub_reason("LookupForWrite said 'false'"); handler = MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } // Can't use {lookup->name()} because the LookupIterator might be in // "elements" mode for keys that are strings representing integers above // JSArray::kMaxIndex. SetCache(lookup->GetName(), handler); TraceIC("StoreIC", lookup->GetName()); } MaybeObjectHandle StoreIC::ComputeHandler(LookupIterator* lookup) { switch (lookup->state()) { case LookupIterator::TRANSITION: { DirectHandle<JSObject> store_target = lookup->GetStoreTarget<JSObject>(); if (IsJSGlobalObject(*store_target)) { TRACE_HANDLER_STATS(isolate(), StoreIC_StoreGlobalTransitionDH); if (IsJSGlobalObject(*lookup_start_object_map())) { DCHECK(IsStoreGlobalIC()); #ifdef DEBUG DirectHandle<JSObject> holder = lookup->GetHolder<JSObject>(); DCHECK_EQ(*lookup->GetReceiver(), *holder); DCHECK_EQ(*store_target, *holder); #endif return StoreHandler::StoreGlobal( indirect_handle(lookup->transition_cell(), isolate())); } if (IsDefineKeyedOwnIC()) { // Private field can't be deleted from this global object and can't // be overwritten, so install slow handler in order to make store IC // throw if a private name already exists. TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } DirectHandle<Smi> smi_handler = StoreHandler::StoreGlobalProxy(isolate()); Handle<Object> handler = StoreHandler::StoreThroughPrototype( isolate(), lookup_start_object_map(), store_target, *smi_handler, MaybeObjectDirectHandle::Weak(lookup->transition_cell())); return MaybeObjectHandle(handler); } // Dictionary-to-fast transitions are not expected and not supported. DCHECK_IMPLIES(!lookup->transition_map()->is_dictionary_map(), !lookup_start_object_map()->is_dictionary_map()); DCHECK(lookup->IsCacheableTransition()); if (IsAnyDefineOwn()) { return StoreHandler::StoreOwnTransition( isolate(), indirect_handle(lookup->transition_map(), isolate())); } return StoreHandler::StoreTransition( isolate(), indirect_handle(lookup->transition_map(), isolate())); } case LookupIterator::INTERCEPTOR: { DirectHandle<JSObject> holder = lookup->GetHolder<JSObject>(); DirectHandle<InterceptorInfo> info(holder->GetNamedInterceptor(), isolate()); if (lookup->HolderIsReceiverOrHiddenPrototype()) { // TODO(ishell): make store global ic kind reliable, currently it is // not if the feedback vector is not available. if (IsStoreGlobalIC() && (GetShouldThrow(isolate(), Nothing<ShouldThrow>()) == ShouldThrow::kThrowOnError)) { DCHECK(IsJSGlobalObject(*lookup->GetHolder<Object>())); // This is a contextual store to an interceptor object. Use slow // handler because before calling the setter callback we need to // check whether the property exists. See // https://tc39.es/ecma262/#sec-object-environment-records-setmutablebinding-n-v-s Handle<Smi> smi_handler = StoreHandler::StoreSlow(isolate()); return MaybeObjectHandle(smi_handler); } if (info->has_setter() && !IsAnyDefineOwn()) { TRACE_HANDLER_STATS(isolate(), StoreIC_StoreInterceptorDH); if (info->non_masking()) { TRACE_HANDLER_STATS(isolate(), StoreIC_StoreInterceptorNonMaskingDH); } // We use complex data handler in order to cache the holder and // interceptor info while also being able to use validity cell // guarding against prototype chain modifications necessary for // non-masking interceptors. Handle<StoreHandler> handler = StoreHandler::StoreInterceptorHolderIsReceiver( isolate(), lookup_start_object_map(), info); return MaybeObjectHandle(handler); } // If the interceptor does not have setter of if the IC is a // DefineNamedOwnIC or DefineKeyedOwnIC we should call definer // callback. This is not supported yet, so we use slow handler. // TODO(ishell): support calling definer callbacks. TRACE_HANDLER_STATS(isolate(), StoreIC_StoreInterceptorSlowDH); Handle<Smi> smi_handler = StoreHandler::StoreSlow(isolate()); return MaybeObjectHandle(smi_handler); } // If the interceptor is a getter/query interceptor on the prototype // chain, return an invalidatable slow handler so it can turn fast // if the interceptor is masked by a regular property later. // Note, that in this case we don't need to call the setter callback // at all, we just need to query the interceptor to ensure that the // property does not exist or it's writable. // TODO(ishell): consider creating an appropriate simple store handler // instead of kSlow when possible. DCHECK(info->has_getter() || info->has_query()); TRACE_HANDLER_STATS(isolate(), StoreIC_StoreInterceptorThroughPrototypeDH); DirectHandle<JSObject> holder_for_api(lookup->GetHolderForApi(), isolate()); Tagged<Smi> smi_handler = StoreHandler::StoreSlow(); Handle<Object> handler = StoreHandler::StoreThroughPrototype( isolate(), lookup_start_object_map(), holder_for_api, smi_handler); return MaybeObjectHandle(handler); } case LookupIterator::ACCESSOR: { // This is currently guaranteed by checks in StoreIC::Store. DirectHandle<JSReceiver> receiver = Cast<JSReceiver>(lookup->GetReceiver()); Handle<JSObject> holder = indirect_handle(lookup->GetHolder<JSObject>(), isolate()); DCHECK(!IsAccessCheckNeeded(*receiver) || lookup->name()->IsAnyPrivate()); if (IsAnyDefineOwn()) { set_slow_stub_reason("define own with existing accessor"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } if (!holder->HasFastProperties()) { set_slow_stub_reason("accessor on slow map"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } DirectHandle<Object> accessors = lookup->GetAccessors(); if (IsAccessorInfo(*accessors)) { DirectHandle<AccessorInfo> info = Cast<AccessorInfo>(accessors); if (!info->has_setter(isolate())) { set_slow_stub_reason("setter == kNullAddress"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } if (!lookup->HolderIsReceiverOrHiddenPrototype()) { set_slow_stub_reason("native data property in prototype chain"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } Handle<Smi> smi_handler = StoreHandler::StoreNativeDataProperty( isolate(), lookup->GetAccessorIndex()); TRACE_HANDLER_STATS(isolate(), StoreIC_StoreNativeDataPropertyDH); if (receiver.is_identical_to(holder)) { return MaybeObjectHandle(smi_handler); } TRACE_HANDLER_STATS(isolate(), StoreIC_StoreNativeDataPropertyOnPrototypeDH); return MaybeObjectHandle(StoreHandler::StoreThroughPrototype( isolate(), lookup_start_object_map(), holder, *smi_handler)); } else if (IsAccessorPair(*accessors)) { DirectHandle<AccessorPair> accessor_pair = Cast<AccessorPair>(accessors); Handle<Object> setter(accessor_pair->setter(), isolate()); if (!IsCallableJSFunction(*setter) && !IsFunctionTemplateInfo(*setter)) { set_slow_stub_reason("setter not a function"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } if ((IsFunctionTemplateInfo(*setter) && Cast<FunctionTemplateInfo>(*setter)->BreakAtEntry(isolate())) || (IsJSFunction(*setter) && Cast<JSFunction>(*setter)->shared()->BreakAtEntry(isolate()))) { // Do not install an IC if the api function has a breakpoint. TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } CallOptimization call_optimization(isolate(), setter); if (call_optimization.is_simple_api_call()) { CallOptimization::HolderLookup holder_lookup; DirectHandle<JSObject> api_holder = call_optimization.LookupHolderOfExpectedType( isolate(), lookup_start_object_map(), &holder_lookup); if (call_optimization.IsCompatibleReceiverMap(api_holder, holder, holder_lookup)) { DirectHandle<Smi> smi_handler = StoreHandler::StoreApiSetter(isolate()); DirectHandle<NativeContext> accessor_context = GetAccessorContext(call_optimization, holder->map(), isolate()); TRACE_HANDLER_STATS(isolate(), StoreIC_StoreApiSetterOnPrototypeDH); return MaybeObjectHandle(StoreHandler::StoreThroughPrototype( isolate(), lookup_start_object_map(), holder, *smi_handler, MaybeObjectDirectHandle::Weak( call_optimization.api_call_info()), MaybeObjectDirectHandle::Weak(accessor_context))); } set_slow_stub_reason("incompatible receiver"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } else if (IsFunctionTemplateInfo(*setter)) { set_slow_stub_reason("setter non-simple template"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } DCHECK(IsCallableJSFunction(*setter)); if (receiver.is_identical_to(holder)) { TRACE_HANDLER_STATS(isolate(), StoreIC_StoreAccessorDH); return MaybeObjectHandle::Weak( indirect_handle(accessor_pair, isolate())); } TRACE_HANDLER_STATS(isolate(), StoreIC_StoreAccessorOnPrototypeDH); return MaybeObjectHandle(StoreHandler::StoreThroughPrototype( isolate(), lookup_start_object_map(), holder, *StoreHandler::StoreAccessorFromPrototype(isolate()), MaybeObjectDirectHandle::Weak(setter))); } TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } case LookupIterator::DATA: { // This is currently guaranteed by checks in StoreIC::Store. DirectHandle<JSObject> receiver = Cast<JSObject>(lookup->GetReceiver()); USE(receiver); DirectHandle<JSObject> holder = lookup->GetHolder<JSObject>(); DCHECK(!IsAccessCheckNeeded(*receiver) || lookup->name()->IsAnyPrivate()); DCHECK_EQ(PropertyKind::kData, lookup->property_details().kind()); if (lookup->is_dictionary_holder()) { if (IsJSGlobalObject(*holder)) { TRACE_HANDLER_STATS(isolate(), StoreIC_StoreGlobalDH); return MaybeObjectHandle(StoreHandler::StoreGlobal( indirect_handle(lookup->GetPropertyCell(), isolate()))); } TRACE_HANDLER_STATS(isolate(), StoreIC_StoreNormalDH); DCHECK(holder.is_identical_to(receiver)); DCHECK_IMPLIES(!V8_DICT_PROPERTY_CONST_TRACKING_BOOL, lookup->constness() == PropertyConstness::kMutable); Handle<Smi> handler = StoreHandler::StoreNormal(isolate()); return MaybeObjectHandle(handler); } // -------------- Elements (for TypedArrays) ------------- if (lookup->IsElement(*holder)) { TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } // -------------- Fields -------------- if (lookup->property_details().location() == PropertyLocation::kField) { TRACE_HANDLER_STATS(isolate(), StoreIC_StoreFieldDH); int descriptor = lookup->GetFieldDescriptorIndex(); FieldIndex index = lookup->GetFieldIndex(); if (V8_UNLIKELY(IsJSSharedStruct(*holder))) { return MaybeObjectHandle(StoreHandler::StoreSharedStructField( isolate(), descriptor, index, lookup->representation())); } PropertyConstness constness = lookup->constness(); if (constness == PropertyConstness::kConst && IsDefineNamedOwnICKind(nexus()->kind())) { // DefineNamedOwnICs are used for initializing object literals // therefore we must store the value unconditionally even to // VariableMode::kConst fields. constness = PropertyConstness::kMutable; } return MaybeObjectHandle(StoreHandler::StoreField( isolate(), descriptor, index, constness, lookup->representation())); } // -------------- Constant properties -------------- DCHECK_EQ(PropertyLocation::kDescriptor, lookup->property_details().location()); set_slow_stub_reason("constant property"); TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } case LookupIterator::JSPROXY: { DirectHandle<JSReceiver> receiver = Cast<JSReceiver>(lookup->GetReceiver()); Handle<JSProxy> holder = indirect_handle(lookup->GetHolder<JSProxy>(), isolate()); // IsDefineNamedOwnIC() is true when we are defining public fields on a // Proxy. IsDefineKeyedOwnIC() is true when we are defining computed // fields in a Proxy. In these cases use the slow stub to invoke the // define trap. if (IsDefineNamedOwnIC() || IsDefineKeyedOwnIC()) { TRACE_HANDLER_STATS(isolate(), StoreIC_SlowStub); return MaybeObjectHandle(StoreHandler::StoreSlow(isolate())); } return MaybeObjectHandle(StoreHandler::StoreProxy( isolate(), lookup_start_object_map(), holder, receiver)); } case LookupIterator::MODULE_NAMESPACE: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: case LookupIterator::ACCESS_CHECK: case LookupIterator::NOT_FOUND: case LookupIterator::WASM_OBJECT: case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); } return MaybeObjectHandle(); } void KeyedStoreIC::UpdateStoreElement(Handle<Map> receiver_map, KeyedAccessStoreMode store_mode, Handle<Map> new_receiver_map) { MapsAndHandlers target_maps_and_handlers(isolate()); nexus()->ExtractMapsAndHandlers( &target_maps_and_handlers, [this](Handle<Map> map) { return Map::TryUpdate(isolate(), map); }); if (target_maps_and_handlers.empty()) { DirectHandle<Map> monomorphic_map = receiver_map; // If we transitioned to a map that is a more general map than incoming // then use the new map. if (IsTransitionOfMonomorphicTarget(*receiver_map, *new_receiver_map)) { monomorphic_map = new_receiver_map; } DirectHandle<Object> handler = StoreElementHandler(monomorphic_map, store_mode); return ConfigureVectorState(DirectHandle<Name>(), monomorphic_map, handler); } for (DirectHandle<Map> map : target_maps_and_handlers.maps()) { if (!map.is_null() && map->instance_type() == JS_PRIMITIVE_WRAPPER_TYPE) { DCHECK(!IsStoreInArrayLiteralIC()); set_slow_stub_reason("JSPrimitiveWrapper"); return; } } // There are several special cases where an IC that is MONOMORPHIC can still // transition to a different IC that handles a superset of the original IC. // Handle those here if the receiver map hasn't changed or it has transitioned // to a more general kind. KeyedAccessStoreMode old_store_mode = GetKeyedAccessStoreMode(); DirectHandle<Map> previous_receiver_map = target_maps_and_handlers.maps()[0]; if (state() == MONOMORPHIC) { DirectHandle<Map> transitioned_receiver_map = new_receiver_map; if (IsTransitionOfMonomorphicTarget(*previous_receiver_map, *transitioned_receiver_map)) { // If the "old" and "new" maps are in the same elements map family, or // if they at least come from the same origin for a transitioning store, // stay MONOMORPHIC and use the map for the most generic ElementsKind. DirectHandle<Object> handler = StoreElementHandler(transitioned_receiver_map, store_mode); ConfigureVectorState(DirectHandle<Name>(), transitioned_receiver_map, handler); return; } // If there is no transition and if we have seen the same map earlier and // there is only a change in the store_mode we can still stay monomorphic. if (receiver_map.is_identical_to(previous_receiver_map) && new_receiver_map.is_identical_to(receiver_map) && StoreModeIsInBounds(old_store_mode) && !StoreModeIsInBounds(store_mode)) { if (IsJSArrayMap(*receiver_map) && JSArray::MayHaveReadOnlyLength(*receiver_map)) { set_slow_stub_reason( "can't generalize store mode (potentially read-only length)"); return; } // A "normal" IC that handles stores can switch to a version that can // grow at the end of the array, handle OOB accesses or copy COW arrays // and still stay MONOMORPHIC. DirectHandle<Object> handler = StoreElementHandler(receiver_map, store_mode); return ConfigureVectorState(DirectHandle<Name>(), receiver_map, handler); } } DCHECK(state() != GENERIC); bool map_added = AddOneReceiverMapIfMissing(&target_maps_and_handlers, receiver_map); if (IsTransitionOfMonomorphicTarget(*receiver_map, *new_receiver_map)) { map_added |= AddOneReceiverMapIfMissing(&target_maps_and_handlers, new_receiver_map); } if (!map_added) { // If the miss wasn't due to an unseen map, a polymorphic stub // won't help, use the megamorphic stub which can handle everything. set_slow_stub_reason("same map added twice"); return; } // If the maximum number of receiver maps has been exceeded, use the // megamorphic version of the IC. if (static_cast<int>(target_maps_and_handlers.size()) > v8_flags.max_valid_polymorphic_map_count) { return; } // Make sure all polymorphic handlers have the same store mode, otherwise the // megamorphic stub must be used. if (!StoreModeIsInBounds(old_store_mode)) { if (StoreModeIsInBounds(store_mode)) { store_mode = old_store_mode; } else if (store_mode != old_store_mode) { set_slow_stub_reason("store mode mismatch"); return; } } // If the store mode isn't the standard mode, make sure that all polymorphic // receivers are either external arrays, or all "normal" arrays with writable // length. Otherwise, use the megamorphic stub. if (!StoreModeIsInBounds(store_mode)) { size_t external_arrays = 0; for (DirectHandle<Map> map : target_maps_and_handlers.maps()) { if (IsJSArrayMap(*map) && JSArray::MayHaveReadOnlyLength(*map)) { set_slow_stub_reason( "unsupported combination of arrays (potentially read-only length)"); return; } else if (map->has_typed_array_or_rab_gsab_typed_array_elements()) { DCHECK(!IsStoreInArrayLiteralIC()); external_arrays++; } } if (external_arrays != 0 && external_arrays != target_maps_and_handlers.size()) { DCHECK(!IsStoreInArrayLiteralIC()); set_slow_stub_reason( "unsupported combination of external and normal arrays"); return; } } StoreElementPolymorphicHandlers(&target_maps_and_handlers, store_mode); if (target_maps_and_handlers.empty()) { DirectHandle<Object> handler = StoreElementHandler(receiver_map, store_mode); ConfigureVectorState(DirectHandle<Name>(), receiver_map, handler); } else if (target_maps_and_handlers.size() == 1) { auto [map, handler] = target_maps_and_handlers[0]; ConfigureVectorState(DirectHandle<Name>(), map, handler); } else { ConfigureVectorState(DirectHandle<Name>(), target_maps_and_handlers); } } Handle<Object> KeyedStoreIC::StoreElementHandler( DirectHandle<Map> receiver_map, KeyedAccessStoreMode store_mode, MaybeDirectHandle<UnionOf<Smi, Cell>> prev_validity_cell) { if (!IsJSObjectMap(*receiver_map)) { // DefineKeyedOwnIC, which is used to define computed fields in instances, // should handled by the slow stub below instead of the proxy stub. if (IsJSProxyMap(*receiver_map) && !IsDefineKeyedOwnIC()) { return StoreHandler::StoreProxy(isolate()); } #if V8_ENABLE_WEBASSEMBLY if (IsWasmObjectMap(*receiver_map)) { set_slow_stub_reason("wasm object"); } #endif // V8_ENABLE_WEBASSEMBLY // Wasm objects or other kind of special objects go through the slow stub. TRACE_HANDLER_STATS(isolate(), KeyedStoreIC_SlowStub); return StoreHandler::StoreSlow(isolate(), store_mode); } // The only case when could keep using non-slow element store handler for // a fast array with potentially read-only elements is when it's an // initializing store to array literal. DCHECK_IMPLIES( !receiver_map->has_dictionary_elements() && receiver_map->ShouldCheckForReadOnlyElementsInPrototypeChain( isolate()), IsStoreInArrayLiteralIC()); // TODO(ishell): move to StoreHandler::StoreElement(). Handle<Code> code; if (receiver_map->has_sloppy_arguments_elements()) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedStoreIC_KeyedStoreSloppyArgumentsStub); code = StoreHandler::StoreSloppyArgumentsBuiltin(isolate(), store_mode); } else if (receiver_map->has_fast_elements() || receiver_map->has_sealed_elements() || receiver_map->has_nonextensible_elements() || receiver_map->has_typed_array_or_rab_gsab_typed_array_elements()) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedStoreIC_StoreFastElementStub); if (IsJSArgumentsObjectMap(*receiver_map) && receiver_map->has_fast_packed_elements()) { // Allow fast behaviour for in-bounds stores while making it miss and // properly handle the out of bounds store case. code = StoreHandler::StoreFastElementBuiltin( isolate(), KeyedAccessStoreMode::kInBounds); } else { code = StoreHandler::StoreFastElementBuiltin(isolate(), store_mode); if (receiver_map->has_typed_array_or_rab_gsab_typed_array_elements()) { return code; } } } else if (IsStoreInArrayLiteralIC()) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), StoreInArrayLiteralIC_SlowStub); return StoreHandler::StoreSlow(isolate(), store_mode); } else { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedStoreIC_StoreElementStub); DCHECK(DICTIONARY_ELEMENTS == receiver_map->elements_kind() || receiver_map->has_frozen_elements()); return StoreHandler::StoreSlow(isolate(), store_mode); } if (IsAnyDefineOwn() || IsStoreInArrayLiteralIC()) return code; DirectHandle<UnionOf<Smi, Cell>> validity_cell; if (!prev_validity_cell.ToHandle(&validity_cell)) { validity_cell = Map::GetOrCreatePrototypeChainValidityCell(receiver_map, isolate()); } if (*validity_cell == Map::kNoValidityCellSentinel) { // There's no prototype validity cell to check, so we can just use the stub. return code; } Handle<StoreHandler> handler = isolate()->factory()->NewStoreHandler(0); handler->set_validity_cell(*validity_cell); handler->set_smi_handler(*code); return handler; } void KeyedStoreIC::StoreElementPolymorphicHandlers( MapsAndHandlers* receiver_maps_and_handlers, KeyedAccessStoreMode store_mode) { DirectHandleVector<Map> receiver_maps(isolate()); receiver_maps.reserve(receiver_maps_and_handlers->size()); for (DirectHandle<Map> map : receiver_maps_and_handlers->maps()) { receiver_maps.push_back(map); } for (size_t i = 0; i < receiver_maps_and_handlers->size(); i++) { auto [receiver_map, old_handler] = (*receiver_maps_and_handlers)[i]; DCHECK(!receiver_map->is_deprecated()); DirectHandle<Object> handler; DirectHandle<Map> transition; if (receiver_map->instance_type() < FIRST_JS_RECEIVER_TYPE || receiver_map->ShouldCheckForReadOnlyElementsInPrototypeChain( isolate())) { // TODO(mvstanton): Consider embedding store_mode in the state of the slow // keyed store ic for uniformity. TRACE_HANDLER_STATS(isolate(), KeyedStoreIC_SlowStub); handler = StoreHandler::StoreSlow(isolate()); } else { { Tagged<Map> tmap = receiver_map->FindElementsKindTransitionedMap( isolate(), MapHandlesSpan(receiver_maps.begin(), receiver_maps.end()), ConcurrencyMode::kSynchronous); if (!tmap.is_null()) { if (receiver_map->is_stable()) { receiver_map->NotifyLeafMapLayoutChange(isolate()); } transition = direct_handle(tmap, isolate()); } } MaybeDirectHandle<UnionOf<Smi, Cell>> validity_cell; Tagged<HeapObject> old_handler_obj; if (!old_handler.is_null() && (*old_handler).GetHeapObject(&old_handler_obj) && IsDataHandler(old_handler_obj)) { validity_cell = direct_handle( Cast<DataHandler>(old_handler_obj)->validity_cell(), isolate()); } // TODO(mythria): Do not recompute the handler if we know there is no // change in the handler. // TODO(mvstanton): The code below is doing pessimistic elements // transitions. I would like to stop doing that and rely on Allocation // Site Tracking to do a better job of ensuring the data types are what // they need to be. Not all the elements are in place yet, pessimistic // elements transitions are still important for performance. if (!transition.is_null()) { TRACE_HANDLER_STATS(isolate(), KeyedStoreIC_ElementsTransitionAndStoreStub); handler = StoreHandler::StoreElementTransition( isolate(), receiver_map, transition, store_mode, validity_cell); } else { handler = StoreElementHandler(receiver_map, store_mode, validity_cell); } } DCHECK(!handler.is_null()); receiver_maps_and_handlers->set_map(i, receiver_map); receiver_maps_and_handlers->set_handler(i, MaybeObjectDirectHandle(handler)); } } namespace { bool MayHaveTypedArrayInPrototypeChain(Isolate* isolate, DirectHandle<JSObject> object) { for (PrototypeIterator iter(isolate, *object); !iter.IsAtEnd(); iter.Advance()) { // Be conservative, don't walk into proxies. if (IsJSProxy(iter.GetCurrent())) return true; if (IsJSTypedArray(iter.GetCurrent())) return true; } return false; } KeyedAccessStoreMode GetStoreMode(DirectHandle<JSObject> receiver, size_t index) { bool oob_access = IsOutOfBoundsAccess(receiver, index); // Don't consider this a growing store if the store would send the receiver to // dictionary mode. bool allow_growth = IsJSArray(*receiver) && oob_access && index <= JSArray::kMaxArrayIndex && !receiver->WouldConvertToSlowElements(static_cast<uint32_t>(index)); if (allow_growth) { return KeyedAccessStoreMode::kGrowAndHandleCOW; } if (receiver->map()->has_typed_array_or_rab_gsab_typed_array_elements() && oob_access) { return KeyedAccessStoreMode::kIgnoreTypedArrayOOB; } return receiver->elements()->IsCowArray() ? KeyedAccessStoreMode::kHandleCOW : KeyedAccessStoreMode::kInBounds; } } // namespace MaybeDirectHandle<Object> KeyedStoreIC::Store(Handle<JSAny> object, Handle<Object> key, DirectHandle<Object> value) { // TODO(verwaest): Let SetProperty do the migration, since storing a property // might deprecate the current map again, if value does not fit. if (MigrateDeprecated(isolate(), object)) { DirectHandle<Object> result; // TODO(v8:12548): refactor DefineKeyedOwnIC as a subclass of StoreIC // so the logic doesn't get mixed here. ASSIGN_RETURN_ON_EXCEPTION( isolate(), result, IsDefineKeyedOwnIC() ? Runtime::DefineObjectOwnProperty(isolate(), object, key, value, StoreOrigin::kNamed) : Runtime::SetObjectProperty(isolate(), object, key, value, StoreOrigin::kMaybeKeyed)); return result; } DirectHandle<Object> store_handle; intptr_t maybe_index; Handle<Name> maybe_name; KeyType key_type = TryConvertKey(key, isolate(), &maybe_index, &maybe_name); if (key_type == kName) { ASSIGN_RETURN_ON_EXCEPTION( isolate(), store_handle, StoreIC::Store(object, maybe_name, value, StoreOrigin::kMaybeKeyed)); if (vector_needs_update()) { if (ConfigureVectorState(MEGAMORPHIC, key)) { set_slow_stub_reason("unhandled internalized string key"); TraceIC("StoreIC", key); } } return store_handle; } JSObject::MakePrototypesFast(object, kStartAtPrototype, isolate()); // TODO(jkummerow): Refactor the condition logic here and below. bool use_ic = (state() != NO_FEEDBACK) && v8_flags.use_ic && !IsStringWrapper(*object) && !IsAccessCheckNeeded(*object) && !IsJSGlobalProxy(*object); if (use_ic && !IsSmi(*object)) { // Don't use ICs for maps of the objects in Array's prototype chain. We // expect to be able to trap element sets to objects with those maps in // the runtime to enable optimization of element hole access. DirectHandle<HeapObject> heap_object = Cast<HeapObject>(object); if (heap_object->map()->IsMapInArrayPrototypeChain(isolate())) { set_slow_stub_reason("map in array prototype"); use_ic = false; } } Handle<Map> old_receiver_map; bool is_arguments = false; bool key_is_valid_index = (key_type == kIntPtr); KeyedAccessStoreMode store_mode = KeyedAccessStoreMode::kInBounds; if (use_ic && IsJSReceiver(*object) && key_is_valid_index) { DirectHandle<JSReceiver> receiver = Cast<JSReceiver>(object); old_receiver_map = handle(receiver->map(), isolate()); is_arguments = IsJSArgumentsObject(*receiver); bool is_jsobject = IsJSObject(*receiver); size_t index; key_is_valid_index = IntPtrKeyToSize(maybe_index, receiver, &index); if (is_jsobject && !is_arguments && key_is_valid_index) { DirectHandle<JSObject> receiver_object = Cast<JSObject>(object); store_mode = GetStoreMode(receiver_object, index); } } DCHECK(store_handle.is_null()); // TODO(v8:12548): refactor DefineKeyedOwnIC as a subclass of StoreIC // so the logic doesn't get mixed here. MaybeDirectHandle<Object> result = IsDefineKeyedOwnIC() ? Runtime::DefineObjectOwnProperty(isolate(), object, key, value, StoreOrigin::kNamed) : Runtime::SetObjectProperty(isolate(), object, key, value, StoreOrigin::kMaybeKeyed); if (result.is_null()) { DCHECK(isolate()->has_exception()); set_slow_stub_reason("failed to set property"); use_ic = false; } if (use_ic) { if (!old_receiver_map.is_null()) { if (is_arguments) { set_slow_stub_reason("arguments receiver"); } else if (IsJSArray(*object) && StoreModeCanGrow(store_mode) && JSArray::HasReadOnlyLength(Cast<JSArray>(object))) { set_slow_stub_reason("array has read only length"); } else if (IsJSObject(*object) && MayHaveTypedArrayInPrototypeChain(isolate(), Cast<JSObject>(object))) { // Make sure we don't handle this in IC if there's any JSTypedArray in // the {receiver}'s prototype chain, since that prototype is going to // swallow all stores that are out-of-bounds for said prototype, and we // just let the runtime deal with the complexity of this. set_slow_stub_reason("typed array in the prototype chain"); } else if (key_is_valid_index) { if (old_receiver_map->is_abandoned_prototype_map()) { set_slow_stub_reason("receiver with prototype map"); #if V8_ENABLE_WEBASSEMBLY } else if (IsWasmObjectMap(*old_receiver_map)) { // Handle object types for which we don't need to check for // read-only prototype elements because we'll use the slow handler // anyway. DirectHandle<HeapObject> receiver = Cast<HeapObject>(object); UpdateStoreElement(old_receiver_map, store_mode, handle(receiver->map(), isolate())); #endif // V8_ENABLE_WEBASSEMBLY } else if (old_receiver_map->has_dictionary_elements() || !old_receiver_map ->ShouldCheckForReadOnlyElementsInPrototypeChain( isolate())) { // We should go generic if receiver isn't a dictionary, but our // prototype chain does have dictionary elements. This ensures that // other non-dictionary receivers in the polymorphic case benefit // from fast path keyed stores. DirectHandle<HeapObject> receiver = Cast<HeapObject>(object); UpdateStoreElement(old_receiver_map, store_mode, handle(receiver->map(), isolate())); } else { set_slow_stub_reason("prototype with potentially read-only elements"); } } else { set_slow_stub_reason("non-smi-like key"); } } else { set_slow_stub_reason("non-JSObject receiver"); } } if (vector_needs_update()) { ConfigureVectorState(MEGAMORPHIC, key); } TraceIC("StoreIC", key); return result; } namespace { Maybe<bool> StoreOwnElement(Isolate* isolate, DirectHandle<JSArray> array, Handle<Object> index, DirectHandle<Object> value) { DCHECK(IsNumber(*index)); PropertyKey key(isolate, index); LookupIterator it(isolate, array, key, LookupIterator::OWN); MAYBE_RETURN(JSObject::DefineOwnPropertyIgnoreAttributes( &it, value, NONE, Just(ShouldThrow::kThrowOnError)), Nothing<bool>()); return Just(true); } } // namespace MaybeDirectHandle<Object> StoreInArrayLiteralIC::Store( DirectHandle<JSArray> array, Handle<Object> index, DirectHandle<Object> value) { DCHECK(!array->map()->IsMapInArrayPrototypeChain(isolate())); DCHECK(IsNumber(*index)); if (!v8_flags.use_ic || state() == NO_FEEDBACK || MigrateDeprecated(isolate(), array)) { MAYBE_RETURN_NULL(StoreOwnElement(isolate(), array, index, value)); TraceIC("StoreInArrayLiteralIC", index); return value; } // TODO(neis): Convert HeapNumber to Smi if possible? KeyedAccessStoreMode store_mode = KeyedAccessStoreMode::kInBounds; if (IsSmi(*index)) { DCHECK_GE(Smi::ToInt(*index), 0); uint32_t index32 = static_cast<uint32_t>(Smi::ToInt(*index)); store_mode = GetStoreMode(array, index32); } Handle<Map> old_array_map(array->map(), isolate()); MAYBE_RETURN_NULL(StoreOwnElement(isolate(), array, index, value)); if (IsSmi(*index)) { DCHECK(!old_array_map->is_abandoned_prototype_map()); UpdateStoreElement(old_array_map, store_mode, handle(array->map(), isolate())); } else { set_slow_stub_reason("index out of Smi range"); } if (vector_needs_update()) { ConfigureVectorState(MEGAMORPHIC, index); } TraceIC("StoreInArrayLiteralIC", index); return value; } // ---------------------------------------------------------------------------- // Static IC stub generators. // // RUNTIME_FUNCTION(Runtime_LoadIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. Handle<JSAny> receiver = args.at<JSAny>(0); Handle<Name> key = args.at<Name>(1); int slot = args.tagged_index_value_at(2); Handle<FeedbackVector> vector = args.at<FeedbackVector>(3); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); // A monomorphic or polymorphic KeyedLoadIC with a string key can call the // LoadIC miss handler if the handler misses. Since the vector Nexus is // set up outside the IC, handle that here. FeedbackSlotKind kind = vector->GetKind(vector_slot); if (IsLoadICKind(kind)) { LoadIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); } else if (IsLoadGlobalICKind(kind)) { DCHECK_EQ(isolate->native_context()->global_proxy(), *receiver); receiver = isolate->global_object(); LoadGlobalIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(key)); } else { DCHECK(IsKeyedLoadICKind(kind)); KeyedLoadIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); } } RUNTIME_FUNCTION(Runtime_LoadIC_Miss_FromBaseline) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. Handle<JSAny> receiver = args.at<JSAny>(0); Handle<Name> key = args.at<Name>(1); int slot = args.tagged_index_value_at(2); Handle<FeedbackVector> vector = args.at<FeedbackVector>(3); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind kind = vector->GetKind(vector_slot); DCHECK(IsLoadICKind(kind)); LoadIC ic(isolate, vector, vector_slot, kind, CallerFrameType::kBaseline); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); } RUNTIME_FUNCTION(Runtime_PatchLoadICUninitializedBaseline) { #ifdef V8_ENABLE_SPARKPLUG_PLUS DCHECK(v8_flags.sparkplug_plus); HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. Handle<JSAny> receiver = args.at<JSAny>(0); Handle<Name> key = args.at<Name>(1); int slot = args.tagged_index_value_at(2); Handle<FeedbackVector> vector = args.at<FeedbackVector>(3); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind kind = vector->GetKind(vector_slot); // Get target builtin's address. FeedbackNexus nexus(isolate, vector, vector_slot); Builtin target_builtin = nexus.ic_handler(); DCHECK(target_builtin > Builtin::kFirstLoadICHandler && target_builtin <= Builtin::kLastLoadICHandler); Address target = Builtins::EntryOf(target_builtin, isolate); { // Get Caller's pc. const Address entry = Isolate::c_entry_fp(isolate->thread_local_top()); Address* pc_address = reinterpret_cast<Address*>(entry + ExitFrameConstants::kCallerPCOffset); Address pc = StackFrame::ReadPC(pc_address) - Assembler::kCallTargetAddressOffset; DCHECK_EQ( Assembler::target_address_at(pc, kNullAddress), Builtins::EntryOf(Builtin::kLoadICUninitializedBaseline, isolate)); // Patch caller to the target address. WritableJitAllocation jit_allocation = WritableJitAllocation::ForPatchableBaselineJIT( pc, Assembler::kCallTargetAddressOffset); Assembler::set_target_address_at(pc, kNullAddress, target, &jit_allocation, FLUSH_ICACHE_IF_NEEDED); } // TODO(chromium:429351411): LoadIC as NO_FEEDBACK. This will go down the slow // path and might fail to update the feedback. However, in the future we would // like to directly generate a call to the handler and then it will be less // likely to go to this path. LoadIC ic(isolate, Handle<FeedbackVector>(), vector_slot, kind); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); #else UNREACHABLE(); #endif // V8_ENABLE_SPARKPLUG_PLUS } RUNTIME_FUNCTION(Runtime_GetStringLengthAndUpdateFeedback) { #ifdef V8_ENABLE_SPARKPLUG_PLUS Handle<String> receiver = args.at<String>(0); int slot = args.tagged_index_value_at(1); Handle<FeedbackVector> vector = args.at<FeedbackVector>(2); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackNexus nexus(isolate, vector, vector_slot); MaybeObjectHandle handler = MaybeObjectHandle(BUILTIN_CODE(isolate, LoadIC_StringLength)); DirectHandle<Map> receiver_map(receiver->map(), isolate); // Update feedback. nexus.ConfigureMonomorphic(Handle<Name>::null(), receiver_map, handler); IC::OnFeedbackChanged(isolate, *vector, vector_slot, "Monomorphic"); return Smi::FromInt(receiver->length()); #else UNREACHABLE(); #endif // V8_ENABLE_SPARKPLUG_PLUS } RUNTIME_FUNCTION(Runtime_LoadNoFeedbackIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); // Runtime functions don't follow the IC's calling convention. Handle<JSAny> receiver = args.at<JSAny>(0); Handle<Name> key = args.at<Name>(1); int slot_kind = args.smi_value_at(2); FeedbackSlotKind kind = static_cast<FeedbackSlotKind>(slot_kind); Handle<FeedbackVector> vector = Handle<FeedbackVector>(); FeedbackSlot vector_slot = FeedbackSlot::Invalid(); // This function is only called after looking up in the ScriptContextTable so // it is safe to call LoadIC::Load for global loads as well. LoadIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); } RUNTIME_FUNCTION(Runtime_LoadWithReceiverNoFeedbackIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<JSAny> receiver = args.at<JSAny>(0); Handle<JSAny> object = args.at<JSAny>(1); Handle<Name> key = args.at<Name>(2); Handle<FeedbackVector> vector = Handle<FeedbackVector>(); FeedbackSlot vector_slot = FeedbackSlot::Invalid(); LoadIC ic(isolate, vector, vector_slot, FeedbackSlotKind::kLoadProperty); ic.UpdateState(object, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(object, key, true, receiver)); } RUNTIME_FUNCTION(Runtime_LoadGlobalIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<JSGlobalObject> global = isolate->global_object(); Handle<String> name = args.at<String>(0); int slot = args.tagged_index_value_at(1); Handle<HeapObject> maybe_vector = args.at<HeapObject>(2); int typeof_value = args.smi_value_at(3); TypeofMode typeof_mode = static_cast<TypeofMode>(typeof_value); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); vector = Cast<FeedbackVector>(maybe_vector); } FeedbackSlotKind kind = (typeof_mode == TypeofMode::kInside) ? FeedbackSlotKind::kLoadGlobalInsideTypeof : FeedbackSlotKind::kLoadGlobalNotInsideTypeof; LoadGlobalIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(global, name); DirectHandle<Object> result; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, ic.Load(name)); return *result; } RUNTIME_FUNCTION(Runtime_LoadGlobalIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); Handle<String> name = args.at<String>(0); int slot = args.tagged_index_value_at(1); Handle<FeedbackVector> vector = args.at<FeedbackVector>(2); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind kind = vector->GetKind(vector_slot); LoadGlobalIC ic(isolate, vector, vector_slot, kind); DirectHandle<Object> result; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, ic.Load(name, false)); return *result; } RUNTIME_FUNCTION(Runtime_LoadWithReceiverIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<JSAny> receiver = args.at<JSAny>(0); Handle<JSAny> object = args.at<JSAny>(1); Handle<Name> key = args.at<Name>(2); int slot = args.tagged_index_value_at(3); Handle<FeedbackVector> vector = args.at<FeedbackVector>(4); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); DCHECK(IsLoadICKind(vector->GetKind(vector_slot))); LoadIC ic(isolate, vector, vector_slot, FeedbackSlotKind::kLoadProperty); ic.UpdateState(object, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(object, key, true, receiver)); } RUNTIME_FUNCTION(Runtime_KeyedLoadIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. Handle<JSAny> receiver = args.at<JSAny>(0); Handle<Object> key = args.at(1); int slot = args.tagged_index_value_at(2); Handle<HeapObject> maybe_vector = args.at<HeapObject>(3); Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); vector = Cast<FeedbackVector>(maybe_vector); } FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); KeyedLoadIC ic(isolate, vector, vector_slot, FeedbackSlotKind::kLoadKeyed); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); } RUNTIME_FUNCTION(Runtime_StoreIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); int slot = args.tagged_index_value_at(1); Handle<HeapObject> maybe_vector = args.at<HeapObject>(2); Handle<JSAny> receiver = args.at<JSAny>(3); Handle<Name> key = args.at<Name>(4); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); // When there is no feedback vector it is OK to use the SetNamedStrict as // the feedback slot kind. We only reuse this for DefineNamedOwnIC when // installing the handler for storing const properties. This will happen only // when feedback vector is available. FeedbackSlotKind kind = FeedbackSlotKind::kSetNamedStrict; Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); DCHECK(!vector_slot.IsInvalid()); vector = Cast<FeedbackVector>(maybe_vector); kind = vector->GetKind(vector_slot); } DCHECK(IsSetNamedICKind(kind) || IsDefineNamedOwnICKind(kind)); StoreIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value)); } RUNTIME_FUNCTION(Runtime_DefineNamedOwnIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); int slot = args.tagged_index_value_at(1); Handle<HeapObject> maybe_vector = args.at<HeapObject>(2); Handle<JSAny> receiver = args.at<JSAny>(3); Handle<Name> key = args.at<Name>(4); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); // When there is no feedback vector it is OK to use the DefineNamedOwn // feedback kind. There _should_ be a vector, though. FeedbackSlotKind kind = FeedbackSlotKind::kDefineNamedOwn; Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); DCHECK(!vector_slot.IsInvalid()); vector = Cast<FeedbackVector>(maybe_vector); kind = vector->GetKind(vector_slot); } DCHECK(IsDefineNamedOwnICKind(kind)); // TODO(v8:12548): refactor DefineNamedOwnIC as a subclass of StoreIC, which // can be called here. StoreIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value)); } RUNTIME_FUNCTION(Runtime_DefineNamedOwnIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); DirectHandle<Object> value = args.at(0); DirectHandle<JSAny> object = args.at<JSAny>(1); Handle<Object> key = args.at(2); // Unlike DefineKeyedOwnIC, DefineNamedOwnIC doesn't handle private // fields and is used for defining data properties in object literals // and defining named public class fields. DCHECK(!IsSymbol(*key) || !Cast<Symbol>(*key)->is_any_private_name()); PropertyKey lookup_key(isolate, key); MAYBE_RETURN(JSReceiver::CreateDataProperty(isolate, object, lookup_key, value, Nothing<ShouldThrow>()), ReadOnlyRoots(isolate).exception()); return *value; } RUNTIME_FUNCTION(Runtime_StoreGlobalIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); int slot = args.tagged_index_value_at(1); Handle<FeedbackVector> vector = args.at<FeedbackVector>(2); Handle<Name> key = args.at<Name>(3); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind kind = vector->GetKind(vector_slot); StoreGlobalIC ic(isolate, vector, vector_slot, kind); DirectHandle<JSGlobalObject> global = isolate->global_object(); ic.UpdateState(global, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(key, value)); } RUNTIME_FUNCTION(Runtime_StoreGlobalICNoFeedback_Miss) { HandleScope scope(isolate); DCHECK_EQ(2, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); Handle<Name> key = args.at<Name>(1); // TODO(mythria): Replace StoreGlobalStrict/Sloppy with SetNamedProperty. StoreGlobalIC ic(isolate, Handle<FeedbackVector>(), FeedbackSlot(), FeedbackSlotKind::kStoreGlobalStrict); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(key, value)); } // TODO(mythria): Remove Feedback vector and slot. Since they are not used apart // from the DCHECK. RUNTIME_FUNCTION(Runtime_StoreGlobalIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); Handle<String> name = args.at<String>(4); #ifdef DEBUG { int slot = args.tagged_index_value_at(1); DirectHandle<FeedbackVector> vector = args.at<FeedbackVector>(2); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind slot_kind = vector->GetKind(vector_slot); DCHECK(IsStoreGlobalICKind(slot_kind)); DirectHandle<JSAny> receiver = args.at<JSAny>(3); DCHECK(IsJSGlobalProxy(*receiver)); } #endif Handle<JSGlobalObject> global = isolate->global_object(); DirectHandle<Context> native_context = isolate->native_context(); DirectHandle<ScriptContextTable> script_contexts( native_context->script_context_table(), isolate); VariableLookupResult lookup_result; if (script_contexts->Lookup(name, &lookup_result)) { DirectHandle<Context> script_context( script_contexts->get(lookup_result.context_index), isolate); if (IsImmutableLexicalVariableMode(lookup_result.mode)) { THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kConstAssign, global, name)); } { DisallowGarbageCollection no_gc; if (script_context->IsElementTheHole(lookup_result.slot_index)) { AllowGarbageCollection yes_gc; THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewReferenceError(MessageTemplate::kAccessedUninitializedVariable, name)); } } Context::Set(script_context, lookup_result.slot_index, value, isolate); return *value; } RETURN_RESULT_OR_FAILURE( isolate, Runtime::SetObjectProperty(isolate, global, name, value, StoreOrigin::kMaybeKeyed)); } RUNTIME_FUNCTION(Runtime_KeyedStoreIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); Handle<HeapObject> maybe_vector = args.at<HeapObject>(2); Handle<JSAny> receiver = args.at<JSAny>(3); Handle<Object> key = args.at(4); FeedbackSlot vector_slot; // When the feedback vector is not valid the slot can only be of type // StoreKeyed. Storing in array literals falls back to // StoreInArrayLiterIC_Miss. This function is also used from store handlers // installed in feedback vectors. In such cases, we need to get the kind from // feedback vector slot since the handlers are used for both for StoreKeyed // and StoreInArrayLiteral kinds. FeedbackSlotKind kind = FeedbackSlotKind::kSetKeyedStrict; Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); vector = Cast<FeedbackVector>(maybe_vector); int slot = args.tagged_index_value_at(1); vector_slot = FeedbackVector::ToSlot(slot); kind = vector->GetKind(vector_slot); } // The elements store stubs miss into this function, but they are shared by // different ICs. // TODO(v8:12548): refactor DefineKeyedOwnIC as a subclass of KeyedStoreIC, // which can be called here. if (IsKeyedStoreICKind(kind) || IsDefineKeyedOwnICKind(kind)) { KeyedStoreIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value)); } else { DCHECK(IsStoreInArrayLiteralICKind(kind)); DCHECK(IsJSArray(*receiver)); DCHECK(IsNumber(*key)); StoreInArrayLiteralIC ic(isolate, vector, vector_slot); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(Cast<JSArray>(receiver), key, value)); } } RUNTIME_FUNCTION(Runtime_DefineKeyedOwnIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); int slot = args.tagged_index_value_at(1); Handle<HeapObject> maybe_vector = args.at<HeapObject>(2); Handle<JSAny> receiver = args.at<JSAny>(3); Handle<Object> key = args.at(4); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind kind = FeedbackSlotKind::kDefineKeyedOwn; Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); vector = Cast<FeedbackVector>(maybe_vector); kind = vector->GetKind(vector_slot); DCHECK(IsDefineKeyedOwnICKind(kind)); } // TODO(v8:12548): refactor DefineKeyedOwnIC as a subclass of KeyedStoreIC, // which can be called here. KeyedStoreIC ic(isolate, vector, vector_slot, kind); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(receiver, key, value)); } RUNTIME_FUNCTION(Runtime_StoreInArrayLiteralIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); int slot = args.tagged_index_value_at(1); Handle<HeapObject> maybe_vector = args.at<HeapObject>(2); DirectHandle<JSAny> receiver = args.at<JSAny>(3); Handle<Object> key = args.at(4); Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); vector = Cast<FeedbackVector>(maybe_vector); } DCHECK(IsJSArray(*receiver)); DCHECK(IsNumber(*key)); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); StoreInArrayLiteralIC ic(isolate, vector, vector_slot); RETURN_RESULT_OR_FAILURE(isolate, ic.Store(Cast<JSArray>(receiver), key, value)); } RUNTIME_FUNCTION(Runtime_KeyedStoreIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); DirectHandle<JSAny> object = args.at<JSAny>(1); DirectHandle<Object> key = args.at(2); RETURN_RESULT_OR_FAILURE( isolate, Runtime::SetObjectProperty(isolate, object, key, value, StoreOrigin::kMaybeKeyed)); } RUNTIME_FUNCTION(Runtime_DefineKeyedOwnIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); DirectHandle<JSAny> object = args.at<JSAny>(1); DirectHandle<Object> key = args.at(2); RETURN_RESULT_OR_FAILURE( isolate, Runtime::DefineObjectOwnProperty(isolate, object, key, value, StoreOrigin::kNamed)); } RUNTIME_FUNCTION(Runtime_StoreInArrayLiteralIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(3, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); DirectHandle<Object> array = args.at(1); Handle<Object> index = args.at(2); StoreOwnElement(isolate, Cast<JSArray>(array), index, value); return *value; } RUNTIME_FUNCTION(Runtime_ElementsTransitionAndStoreIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(6, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<JSAny> object = args.at<JSAny>(0); Handle<Object> key = args.at(1); DirectHandle<Object> value = args.at(2); DirectHandle<Map> map = args.at<Map>(3); int slot = args.tagged_index_value_at(4); DirectHandle<FeedbackVector> vector = args.at<FeedbackVector>(5); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind kind = vector->GetKind(vector_slot); if (IsJSObject(*object)) { JSObject::TransitionElementsKind(isolate, Cast<JSObject>(object), map->elements_kind()); } if (IsStoreInArrayLiteralICKind(kind)) { StoreOwnElement(isolate, Cast<JSArray>(object), key, value); return *value; } else { DCHECK(IsKeyedStoreICKind(kind) || IsSetNamedICKind(kind) || IsDefineKeyedOwnICKind(kind)); RETURN_RESULT_OR_FAILURE( isolate, IsDefineKeyedOwnICKind(kind) ? Runtime::DefineObjectOwnProperty( isolate, object, key, value, StoreOrigin::kNamed) : Runtime::SetObjectProperty(isolate, object, key, value, StoreOrigin::kMaybeKeyed)); } } namespace { enum class FastCloneObjectMode { // The clone has the same map as the input. kIdenticalMap, // The clone is the empty object literal. kEmptyObject, // The clone has an empty object literal map. kDifferentMap, // The source map is to complicated to handle. kNotSupported, // Returned by PreCheck kMaybeSupported }; FastCloneObjectMode GetCloneModeForMapPreCheck(DirectHandle<Map> map, bool null_proto_literal, Isolate* isolate) { DisallowGarbageCollection no_gc; if (!IsJSObjectMap(*map)) { // Everything that produces the empty object literal can be supported since // we have a special case for that. if (null_proto_literal) return FastCloneObjectMode::kNotSupported; return IsNullOrUndefinedMap(*map) || IsBooleanMap(*map) || IsHeapNumberMap(*map) ? FastCloneObjectMode::kEmptyObject : FastCloneObjectMode::kNotSupported; } ElementsKind elements_kind = map->elements_kind(); if (!IsSmiOrObjectElementsKind(elements_kind) && !IsAnyNonextensibleElementsKind(elements_kind)) { return FastCloneObjectMode::kNotSupported; } if (!map->OnlyHasSimpleProperties()) { return FastCloneObjectMode::kNotSupported; } // TODO(olivf): Think about cases where cross-context copies are safe. if (!map->BelongsToSameNativeContextAs(isolate->context())) { return FastCloneObjectMode::kNotSupported; } return FastCloneObjectMode::kMaybeSupported; } FastCloneObjectMode GetCloneModeForMap(DirectHandle<Map> map, bool null_proto_literal, Isolate* isolate) { FastCloneObjectMode pre_check = GetCloneModeForMapPreCheck(map, null_proto_literal, isolate); if (pre_check != FastCloneObjectMode::kMaybeSupported) { return pre_check; } // The clone must always start from an object literal map, it must be an // instance of the object function, have the default prototype and not be a // prototype itself. Only if the source map fits that criterion we can // directly use it as the target map. FastCloneObjectMode mode = map->instance_type() == JS_OBJECT_TYPE && !IsAnyNonextensibleElementsKind(map->elements_kind()) && map->GetConstructor() == *isolate->object_function() && map->prototype() == *isolate->object_function_prototype() && !map->is_prototype_map() ? FastCloneObjectMode::kIdenticalMap : FastCloneObjectMode::kDifferentMap; if (null_proto_literal || IsNull(map->prototype())) { mode = FastCloneObjectMode::kDifferentMap; } Tagged<DescriptorArray> descriptors = map->instance_descriptors(); for (InternalIndex i : map->IterateOwnDescriptors()) { PropertyDetails details = descriptors->GetDetails(i); Tagged<Name> key = descriptors->GetKey(i); if (details.kind() != PropertyKind::kData || !details.IsEnumerable() || key->IsAnyPrivateName()) { return FastCloneObjectMode::kNotSupported; } if (!details.IsConfigurable() || details.IsReadOnly()) { mode = FastCloneObjectMode::kDifferentMap; } } DCHECK_IMPLIES(mode == FastCloneObjectMode::kIdenticalMap, !map->is_prototype_map()); return mode; } bool CanCacheCloneTargetMapTransition( DirectHandle<Map> source_map, std::optional<DirectHandle<Map>> target_map, bool null_proto_literal, Isolate* isolate) { if (!v8_flags.clone_object_sidestep_transitions || null_proto_literal) { return false; } // As of now any R/O source object should end up in the kEmptyObject case, but // there is not really a way of ensuring it. Thus, we also check it below. // This is a performance dcheck. If it fails, the clone IC does not handle a // case it probably could. // TODO(olivf): Either remove that dcheck or move it to GetCloneModeForMap. DCHECK(!HeapLayout::InReadOnlySpace(*source_map)); if (HeapLayout::InReadOnlySpace(*source_map) || source_map->is_deprecated() || source_map->is_prototype_map()) { return false; } if (!target_map) { return true; } CHECK(!HeapLayout::InReadOnlySpace(**target_map)); return !(*target_map)->is_deprecated(); } // Check if an object with `source_map` can be cloned by `FastCloneJSObject` // when the result shall have `target_map`. Optionally `override_map` is the map // of an already existing object that will be written into. If no `override_map` // is given, we assume that a fresh target object can be allocated with // already the correct `target_map`. bool CanFastCloneObjectToObjectLiteral(DirectHandle<Map> source_map, DirectHandle<Map> target_map, DirectHandle<Map> override_map, bool null_proto_literal, Isolate* isolate) { DisallowGarbageCollection no_gc; DCHECK(!target_map->is_deprecated()); DCHECK(source_map->OnlyHasSimpleProperties()); DCHECK(!source_map->IsInobjectSlackTrackingInProgress()); DCHECK(!target_map->IsInobjectSlackTrackingInProgress()); DCHECK_EQ(*target_map->map(), *source_map->map()); DCHECK_EQ(target_map->GetConstructor(), *isolate->object_function()); DCHECK_IMPLIES( !null_proto_literal, *target_map->prototype() == *isolate->object_function_prototype()); // Ensure source and target have identical binary representation of properties // and elements as the IC relies on copying the raw bytes. This also excludes // cases with non-enumerable properties or accessors on the source object. if (source_map->instance_type() != JS_OBJECT_TYPE || target_map->instance_type() != JS_OBJECT_TYPE || !target_map->OnlyHasSimpleProperties() || !target_map->has_fast_elements()) { return false; } if (!override_map.is_null()) { // No cross-context object reuse. if (target_map->map() != override_map->map()) { return false; } // In case we want to clone into an existing target object, we must ensure // that this existing object has a compatible size. In particular we cannot // shrink or grow the already given object. We also exclude a different // start offset, since this doesn't allow us to change the object in-place // in a GC safe way. DCHECK_EQ(*override_map, isolate->object_function()->initial_map()); DCHECK(override_map->instance_type() == JS_OBJECT_TYPE); DCHECK_EQ(override_map->NumberOfOwnDescriptors(), 0); DCHECK(!override_map->IsInobjectSlackTrackingInProgress()); if (override_map->instance_size() != target_map->instance_size() || override_map->GetInObjectPropertiesStartInWords() != target_map->GetInObjectPropertiesStartInWords()) { return false; } } #ifdef DEBUG ElementsKind source_elements_kind = source_map->elements_kind(); ElementsKind target_elements_kind = target_map->elements_kind(); DCHECK(IsSmiOrObjectElementsKind(source_elements_kind) || IsAnyNonextensibleElementsKind(source_elements_kind)); DCHECK(IsSmiOrObjectElementsKind(target_elements_kind)); DCHECK_IMPLIES(IsHoleyElementsKindForRead(source_elements_kind), IsHoleyElementsKind(target_elements_kind)); #endif // DEBUG // There are no transitions between prototype maps. if (source_map->is_prototype_map() || target_map->is_prototype_map()) { return false; } // Exclude edge-cases like not copying a __proto__ property. if (source_map->NumberOfOwnDescriptors() != target_map->NumberOfOwnDescriptors()) { return false; } // Check that the source inobject properties fit into the target. int source_used_inobj_properties = source_map->GetInObjectProperties() - source_map->UnusedInObjectProperties(); int target_used_inobj_properties = target_map->GetInObjectProperties() - target_map->UnusedInObjectProperties(); if (source_used_inobj_properties != target_used_inobj_properties) { return false; } // The properties backing store must be of the same size as the clone ic again // blindly copies it. if (source_map->HasOutOfObjectProperties() != target_map->HasOutOfObjectProperties() || (target_map->HasOutOfObjectProperties() && source_map->UnusedPropertyFields() != target_map->UnusedPropertyFields())) { return false; } Tagged<DescriptorArray> descriptors = source_map->instance_descriptors(); Tagged<DescriptorArray> target_descriptors = target_map->instance_descriptors(); for (InternalIndex i : target_map->IterateOwnDescriptors()) { if (descriptors->GetKey(i) != target_descriptors->GetKey(i)) { return false; } PropertyDetails details = descriptors->GetDetails(i); PropertyDetails target_details = target_descriptors->GetDetails(i); DCHECK_EQ(details.kind(), PropertyKind::kData); DCHECK_EQ(target_details.kind(), PropertyKind::kData); Tagged<FieldType> type = descriptors->GetFieldType(i); Tagged<FieldType> target_type = target_descriptors->GetFieldType(i); // This DCHECK rests on the fact that we only clear field types when there // are no instances of the host map left. Thus, to enter the clone IC at // least one object of the source map needs to be created, which in turn // will re-initialize the source maps field type. This is guaranteed to also // update the target map through the sidestep transition, unless the target // map is deprecated. DCHECK(!IsNone(type)); DCHECK(!IsNone(target_type)); // Field updates don't generalize across prototype transitions, because the // transitions happen on root maps (i.e., before any field is added). In // other words we cannot rely on changes in the source map propagating to // the target map when there is a SetPrototype involved. bool prototype_transition_is_shortcutted = source_map->prototype() != target_map->prototype(); if (!prototype_transition_is_shortcutted && CanCacheCloneTargetMapTransition(source_map, target_map, null_proto_literal, isolate)) { if (!details.representation().fits_into( target_details.representation()) || (target_details.representation().IsDouble() && details.representation().IsSmi())) { return false; } if (!FieldType::NowIs(type, target_type)) { return false; } } else { // In the case we cannot connect the maps in the transition tree (e.g., // the clone also involves a proto transition) we cannot keep track of // representation dependencies. We can only allow the most generic target // representation. The same goes for field types. if (!details.representation().MostGenericInPlaceChange().Equals( target_details.representation()) || !IsAny(target_type)) { return false; } } } return true; } } // namespace static MaybeDirectHandle<JSObject> CloneObjectSlowPath( Isolate* isolate, DirectHandle<Object> source, int flags) { DirectHandle<JSObject> new_object; if (flags & ObjectLiteral::kHasNullPrototype) { new_object = isolate->factory()->NewJSObjectWithNullProto(); } else if (IsJSObject(*source) && Cast<JSObject>(*source)->map()->OnlyHasSimpleProperties()) { Tagged<Map> source_map = Cast<JSObject>(*source)->map(); // TODO(olivf, chrome:1204540) It might be interesting to pick a map with // more properties, depending how many properties are added by the // surrounding literal. int properties = source_map->GetInObjectProperties() - source_map->UnusedInObjectProperties(); DirectHandle<Map> map = isolate->factory()->ObjectLiteralMapFromCache( isolate->native_context(), properties); new_object = isolate->factory()->NewFastOrSlowJSObjectFromMap(map); } else { DirectHandle<JSFunction> constructor( isolate->native_context()->object_function(), isolate); new_object = isolate->factory()->NewJSObject(constructor); } if (IsNullOrUndefined(*source)) { return new_object; } MAYBE_RETURN( JSReceiver::SetOrCopyDataProperties( isolate, new_object, source, PropertiesEnumerationMode::kPropertyAdditionOrder, {}, false), MaybeDirectHandle<JSObject>()); return new_object; } RUNTIME_FUNCTION(Runtime_CloneObjectIC_Slow) { HandleScope scope(isolate); DCHECK_EQ(2, args.length()); DirectHandle<Object> source = args.at(0); int flags = args.smi_value_at(1); RETURN_RESULT_OR_FAILURE(isolate, CloneObjectSlowPath(isolate, source, flags)); } namespace { template <SideStepTransition::Kind kind> Tagged<Object> GetCloneTargetMap(Isolate* isolate, DirectHandle<Map> source_map, DirectHandle<Map> override_map) { static_assert(kind == SideStepTransition::Kind::kObjectAssign || kind == SideStepTransition::Kind::kCloneObject); if (!v8_flags.clone_object_sidestep_transitions) { return SideStepTransition::Empty; } // Ensure we can follow the sidestep transition NativeContext-wise. if (!source_map->BelongsToSameNativeContextAs(isolate->context())) { return SideStepTransition::Empty; } Tagged<Object> result = SideStepTransition::Empty; TransitionsAccessor transitions(isolate, *source_map); if (transitions.HasSideStepTransitions()) { result = transitions.GetSideStepTransition(kind); if (result.IsHeapObject()) { // Exclude deprecated maps. auto map = Cast<Map>(result.GetHeapObject()); bool is_valid = !map->is_deprecated(); // In the case of object assign we need to check the prototype validity // cell on the override map. If the override map changed we cannot assume // that it is correct to set all properties without any getter/setter in // the prototype chain interfering. if constexpr (kind == SideStepTransition::Kind::kObjectAssign) { if (is_valid) { DCHECK_EQ(*override_map, isolate->object_function()->initial_map()); Tagged<Object> validity_cell = transitions.GetSideStepTransition( SideStepTransition::Kind::kObjectAssignValidityCell); is_valid = validity_cell.IsHeapObject() && Cast<Cell>(validity_cell)->maybe_value() != Map::kPrototypeChainInvalid; } } if (V8_LIKELY(is_valid)) { if (result.IsHeapObject()) { CHECK_EQ(GetCloneModeForMapPreCheck(source_map, false, isolate), FastCloneObjectMode::kMaybeSupported); } } else { result = SideStepTransition::Empty; } } } #ifdef DEBUG FastCloneObjectMode clone_mode = GetCloneModeForMap(source_map, false, isolate); if (result == SideStepTransition::Unreachable) { switch (clone_mode) { case FastCloneObjectMode::kNotSupported: case FastCloneObjectMode::kDifferentMap: break; case FastCloneObjectMode::kEmptyObject: case FastCloneObjectMode::kIdenticalMap: DCHECK_EQ(kind, SideStepTransition::Kind::kObjectAssign); break; case FastCloneObjectMode::kMaybeSupported: UNREACHABLE(); } } else if (result != SideStepTransition::Empty) { Tagged<Map> target = Cast<Map>(result.GetHeapObject()); switch (clone_mode) { case FastCloneObjectMode::kIdenticalMap: if (kind == SideStepTransition::Kind::kCloneObject) { DCHECK_EQ(*source_map, target); break; } DCHECK_EQ(kind, SideStepTransition::Kind::kObjectAssign); [[fallthrough]]; case FastCloneObjectMode::kDifferentMap: DCHECK(CanFastCloneObjectToObjectLiteral(source_map, direct_handle(target, isolate), override_map, false, isolate)); break; default: UNREACHABLE(); } } else { DCHECK_EQ(result, SideStepTransition::Empty); } #endif // DEBUG return result; } template <SideStepTransition::Kind kind> void SetCloneTargetMap(Isolate* isolate, DirectHandle<Map> source_map, DirectHandle<Map> new_target_map, DirectHandle<Map> override_map) { if (!v8_flags.clone_object_sidestep_transitions) return; DCHECK(CanCacheCloneTargetMapTransition(source_map, new_target_map, false, isolate)); DCHECK_EQ(GetCloneTargetMap<kind>(isolate, source_map, override_map), SideStepTransition::Empty); DCHECK(!new_target_map->is_deprecated()); // Adding this transition also ensures that when the source map field // generalizes, we also generalize the target map. DCHECK(IsSmiOrObjectElementsKind(new_target_map->elements_kind())); constexpr bool need_validity_cell = kind == SideStepTransition::Kind::kObjectAssign; DirectHandle<Cell> validity_cell; if constexpr (need_validity_cell) { // Since we only clone into empty object literals we only need one validity // cell on that prototype chain. DCHECK_EQ(*override_map, isolate->object_function()->initial_map()); validity_cell = Cast<Cell>( Map::GetOrCreatePrototypeChainValidityCell(override_map, isolate)); } TransitionsAccessor::EnsureHasSideStepTransitions(isolate, source_map); TransitionsAccessor transitions(isolate, *source_map); transitions.SetSideStepTransition(kind, *new_target_map); if constexpr (need_validity_cell) { transitions.SetSideStepTransition( SideStepTransition::Kind::kObjectAssignValidityCell, *validity_cell); } DCHECK_EQ(GetCloneTargetMap<kind>(isolate, source_map, override_map), *new_target_map); } template <SideStepTransition::Kind kind> void SetCloneTargetMapUnsupported(Isolate* isolate, DirectHandle<Map> source_map, DirectHandle<Map> override_map) { if (!v8_flags.clone_object_sidestep_transitions) return; DCHECK_EQ(GetCloneTargetMap<kind>(isolate, source_map, override_map), SideStepTransition::Empty); DCHECK(CanCacheCloneTargetMapTransition(source_map, {}, false, isolate)); // Adding this transition also ensures that when the source map field // generalizes, we also generalize the target map. TransitionsAccessor::EnsureHasSideStepTransitions(isolate, source_map); TransitionsAccessor(isolate, *source_map) .SetSideStepTransition(kind, SideStepTransition::Unreachable); DCHECK_EQ(GetCloneTargetMap<kind>(isolate, source_map, override_map), SideStepTransition::Unreachable); } } // namespace RUNTIME_FUNCTION(Runtime_CloneObjectIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); DirectHandle<Object> source = args.at(0); int flags = args.smi_value_at(1); if (!MigrateDeprecated(isolate, source)) { Handle<HeapObject> maybe_vector = args.at<HeapObject>(3); std::optional<FeedbackNexus> nexus; if (IsFeedbackVector(*maybe_vector)) { int index = args.tagged_index_value_at(2); FeedbackSlot slot = FeedbackVector::ToSlot(index); nexus.emplace(isolate, Cast<FeedbackVector>(maybe_vector), slot); } if (!IsSmi(*source) && (!nexus || !nexus->IsMegamorphic())) { bool null_proto_literal = flags & ObjectLiteral::kHasNullPrototype; Handle<Map> source_map(Cast<HeapObject>(source)->map(), isolate); // In case we are still slack tracking let's defer a decision. The fast // case does not support it. if (!source_map->IsInobjectSlackTrackingInProgress()) { auto UpdateNexus = [&](Handle<Object> target_map) { if (!nexus) return; nexus->ConfigureCloneObject(source_map, MaybeObjectHandle(target_map)); }; ReadOnlyRoots roots(isolate); bool unsupported = false; if (!null_proto_literal) { auto maybe_target = GetCloneTargetMap<SideStepTransition::Kind::kCloneObject>( isolate, source_map, {}); if (maybe_target == SideStepTransition::Unreachable) { unsupported = true; } else if (maybe_target != SideStepTransition::Empty) { Handle<Map> target = handle(Cast<Map>(maybe_target.GetHeapObject()), isolate); UpdateNexus(target); return *target; } } FastCloneObjectMode clone_mode = unsupported ? FastCloneObjectMode::kNotSupported : GetCloneModeForMap(source_map, null_proto_literal, isolate); auto UpdateState = [&](Handle<Map> target_map) { UpdateNexus(target_map); if (CanCacheCloneTargetMapTransition(source_map, target_map, null_proto_literal, isolate)) { SetCloneTargetMap<SideStepTransition::Kind::kCloneObject>( isolate, source_map, target_map, {}); } }; switch (clone_mode) { case FastCloneObjectMode::kIdenticalMap: { UpdateState(source_map); // When returning a map the IC miss handler re-starts from the top. return *source_map; } case FastCloneObjectMode::kEmptyObject: { UpdateNexus(handle(Smi::zero(), isolate)); RETURN_RESULT_OR_FAILURE( isolate, CloneObjectSlowPath(isolate, source, flags)); } case FastCloneObjectMode::kDifferentMap: { DirectHandle<Object> res; ASSIGN_RETURN_FAILURE_ON_EXCEPTION( isolate, res, CloneObjectSlowPath(isolate, source, flags)); Handle<Map> result_map(Cast<HeapObject>(res)->map(), isolate); if (result_map->IsInobjectSlackTrackingInProgress()) { return *res; } if (CanFastCloneObjectToObjectLiteral( source_map, result_map, {}, null_proto_literal, isolate)) { DCHECK(result_map->OnlyHasSimpleProperties()); DCHECK_EQ(source_map->GetInObjectProperties() - source_map->UnusedInObjectProperties(), result_map->GetInObjectProperties() - result_map->UnusedInObjectProperties()); UpdateState(result_map); } else { if (CanCacheCloneTargetMapTransition( source_map, {}, null_proto_literal, isolate)) { SetCloneTargetMapUnsupported< SideStepTransition::Kind::kCloneObject>(isolate, source_map, {}); } if (nexus) { nexus->ConfigureMegamorphic(); } } return *res; } case FastCloneObjectMode::kNotSupported: { break; } case FastCloneObjectMode::kMaybeSupported: UNREACHABLE(); } DCHECK(clone_mode == FastCloneObjectMode::kNotSupported); if (nexus) { nexus->ConfigureMegamorphic(); } } } } RETURN_RESULT_OR_FAILURE(isolate, CloneObjectSlowPath(isolate, source, flags)); } RUNTIME_FUNCTION(Runtime_StoreCallbackProperty) { DirectHandle<JSObject> receiver = args.at<JSObject>(0); DirectHandle<JSObject> holder = args.at<JSObject>(1); DirectHandle<AccessorInfo> info = args.at<AccessorInfo>(2); DirectHandle<Name> name = args.at<Name>(3); DirectHandle<Object> value = args.at(4); HandleScope scope(isolate); #ifdef V8_RUNTIME_CALL_STATS if (V8_UNLIKELY(TracingFlags::is_runtime_stats_enabled())) { RETURN_RESULT_OR_FAILURE( isolate, Runtime::SetObjectProperty(isolate, receiver, name, value, StoreOrigin::kMaybeKeyed)); } #endif Maybe<ShouldThrow> should_throw = Nothing<ShouldThrow>(); PropertyCallbackArguments arguments(isolate, *holder, should_throw); bool result = arguments.CallAccessorSetter(isolate, info, name, value); RETURN_FAILURE_IF_EXCEPTION(isolate); if (!result && GetShouldThrow(isolate, should_throw) == kThrowOnError) { // Throw TypeError if necessary in case the callback failed // to set the property. THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewTypeError(MessageTemplate::kStrictCannotSetProperty, name, receiver)); } return *value; } namespace { bool MaybeCanCloneObjectForObjectAssign(DirectHandle<JSReceiver> source, DirectHandle<Map> source_map, DirectHandle<JSReceiver> target, Isolate* isolate) { FastCloneObjectMode clone_mode = GetCloneModeForMap(source_map, false, isolate); switch (clone_mode) { case FastCloneObjectMode::kIdenticalMap: case FastCloneObjectMode::kDifferentMap: break; case FastCloneObjectMode::kNotSupported: return false; case FastCloneObjectMode::kEmptyObject: case FastCloneObjectMode::kMaybeSupported: // Cannot happen since we should only be called with JSObjects. UNREACHABLE(); } // We need to be sure that there are no setters or other nastiness installed // on the Object.prototype which clash with the properties we intende to copy. DirectHandle<FixedArray> keys; auto res = KeyAccumulator::GetKeys(isolate, source, KeyCollectionMode::kOwnOnly, ONLY_ENUMERABLE, GetKeysConversion::kKeepNumbers); CHECK(res.ToHandle(&keys)); for (int i = 0; i < keys->length(); ++i) { Handle<Object> next_key(keys->get(i), isolate); PropertyKey key(isolate, next_key); LookupIterator it(isolate, target, key); switch (it.state()) { case LookupIterator::NOT_FOUND: continue; case LookupIterator::DATA: if (it.property_attributes() & PropertyAttributes::READ_ONLY) { return false; } continue; case LookupIterator::MODULE_NAMESPACE: case LookupIterator::INTERCEPTOR: case LookupIterator::TRANSITION: case LookupIterator::ACCESS_CHECK: case LookupIterator::JSPROXY: case LookupIterator::WASM_OBJECT: case LookupIterator::TYPED_ARRAY_INDEX_NOT_FOUND: case LookupIterator::ACCESSOR: return false; case LookupIterator::STRING_LOOKUP_START_OBJECT: UNREACHABLE(); } UNREACHABLE(); } return true; } } // namespace // Returns one of: // * A map to be used with FastCloneJSObject // * Undefined if fast cloning is not possible // * True if assignment must be skipped (i.e., the runtime already did it) RUNTIME_FUNCTION(Runtime_ObjectAssignTryFastcase) { HandleScope scope(isolate); DCHECK_EQ(2, args.length()); auto source = Cast<JSReceiver>(args.at(0)); auto target = Cast<JSReceiver>(args.at(1)); DCHECK(IsJSObject(*source)); DCHECK(IsJSObject(*target)); Handle<Map> source_map = handle(source->map(), isolate); Handle<Map> target_map = handle(target->map(), isolate); DCHECK_EQ(target_map->NumberOfOwnDescriptors(), 0); DCHECK(!source_map->is_dictionary_map()); DCHECK(!target_map->is_dictionary_map()); DCHECK(!source_map->is_deprecated()); DCHECK(!target_map->is_deprecated()); DCHECK(target_map->is_extensible()); DCHECK(!IsUndefined(*source, isolate) && !IsNull(*source, isolate)); DCHECK(source_map->BelongsToSameNativeContextAs(isolate->context())); ReadOnlyRoots roots(isolate); { Tagged<Object> maybe_clone_target = GetCloneTargetMap<SideStepTransition::Kind::kObjectAssign>( isolate, source_map, target_map); if (maybe_clone_target == SideStepTransition::Unreachable) { return roots.undefined_value(); } else if (maybe_clone_target != SideStepTransition::Empty) { return Cast<Map>(maybe_clone_target.GetHeapObject()); } } auto UpdateCache = [&](Handle<Map> clone_target_map) { if (CanCacheCloneTargetMapTransition(source_map, clone_target_map, false, isolate)) { SetCloneTargetMap<SideStepTransition::Kind::kObjectAssign>( isolate, source_map, clone_target_map, target_map); } }; auto UpdateCacheNotClonable = [&]() { if (CanCacheCloneTargetMapTransition(source_map, {}, false, isolate)) { SetCloneTargetMapUnsupported<SideStepTransition::Kind::kObjectAssign>( isolate, source_map, target_map); } }; // In case we are still slack tracking let's defer a decision. The fast case // does not support it. if (source_map->IsInobjectSlackTrackingInProgress() || target_map->IsInobjectSlackTrackingInProgress()) { return roots.undefined_value(); } if (MaybeCanCloneObjectForObjectAssign(source, source_map, target, isolate)) { CHECK(target->map()->OnlyHasSimpleProperties()); Maybe<bool> res = JSReceiver::SetOrCopyDataProperties( isolate, target, source, PropertiesEnumerationMode::kEnumerationOrder); DCHECK(res.FromJust()); USE(res); Handle<Map> clone_target_map = handle(target->map(), isolate); if (clone_target_map->IsInobjectSlackTrackingInProgress()) { return roots.true_value(); } if (CanFastCloneObjectToObjectLiteral(source_map, clone_target_map, target_map, false, isolate)) { CHECK(target->map()->OnlyHasSimpleProperties()); UpdateCache(clone_target_map); } else { UpdateCacheNotClonable(); } // We already did the copying here. Thus, returning true to cause the // CSA builtin to skip assigning anything. return roots.true_value(); } UpdateCacheNotClonable(); return roots.undefined_value(); } /** * Loads a property with an interceptor performing post interceptor * lookup if interceptor failed. */ RUNTIME_FUNCTION(Runtime_LoadPropertyPastInterceptor) { HandleScope scope(isolate); DCHECK_EQ(6, args.length()); DirectHandle<Name> name = args.at<Name>(0); DirectHandle<JSAny> receiver = args.at<JSAny>(1); DirectHandle<JSObject> holder = args.at<JSObject>(2); DirectHandle<InterceptorInfo> interceptor = args.at<InterceptorInfo>(3); #ifdef DEBUG if (IsJSGlobalProxy(*holder)) { DCHECK_EQ(Cast<JSObject>(holder->map()->prototype())->GetNamedInterceptor(), *interceptor); } else { DCHECK_EQ(holder->GetNamedInterceptor(), *interceptor); } #endif // If the interceptor hasn't handled the store request then // - for non-masking interceptor the lookup is over, // - for masking interceptor the store lookup needs to be proceed past the // interceptor. if (!interceptor->non_masking()) { LookupIterator it(isolate, receiver, name, holder); // Skip any lookup work until we hit the interceptor. while (it.state() != LookupIterator::INTERCEPTOR) { DCHECK_IMPLIES(it.state() == LookupIterator::ACCESS_CHECK, it.HasAccess()); CHECK(it.IsFound()); it.Next(); } // Skip past the interceptor. it.Next(); DirectHandle<Object> result; ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, Object::GetProperty(&it)); if (it.IsFound()) return *result; } int slot = args.tagged_index_value_at(4); DirectHandle<FeedbackVector> vector = args.at<FeedbackVector>(5); FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); FeedbackSlotKind slot_kind = vector->GetKind(vector_slot); // It could actually be any kind of load IC slot here but the predicate // handles all the cases properly. if (!LoadIC::ShouldThrowReferenceError(slot_kind)) { return ReadOnlyRoots(isolate).undefined_value(); } // Throw a reference error. THROW_NEW_ERROR_RETURN_FAILURE( isolate, NewReferenceError(MessageTemplate::kNotDefined, name)); } RUNTIME_FUNCTION(Runtime_StorePropertyPastInterceptor) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. DirectHandle<Object> value = args.at(0); DirectHandle<JSObject> receiver = args.at<JSObject>(1); DirectHandle<Name> name = args.at<Name>(2); DirectHandle<InterceptorInfo> interceptor = args.at<InterceptorInfo>(3); DirectHandle<JSObject> holder = receiver; #ifdef DEBUG if (IsJSGlobalProxy(*holder)) { DCHECK_EQ(Cast<JSObject>(holder->map()->prototype())->GetNamedInterceptor(), *interceptor); } else { DCHECK_EQ(holder->GetNamedInterceptor(), *interceptor); } #endif bool non_masking = interceptor->non_masking(); // If the interceptor hasn't handled the store request then // - for non-masking interceptor the lookup is over and we should proceed // adding property to the receiver, // - for masking interceptor the store lookup needs to be proceed past the // interceptor. LookupIterator::Configuration config = non_masking ? LookupIterator::OWN_SKIP_INTERCEPTOR : LookupIterator::DEFAULT; LookupIterator it(isolate, receiver, name, holder, config); // Skip past any access check on the receiver - they were already done // by the IC dispatcher. while (it.state() == LookupIterator::ACCESS_CHECK) { DCHECK(it.HasAccess()); it.Next(); } if (!non_masking) { // Skip past the interceptor on the interceptor_holder. DCHECK_EQ(LookupIterator::INTERCEPTOR, it.state()); it.Next(); } MAYBE_RETURN(Object::SetProperty(&it, value, StoreOrigin::kNamed), ReadOnlyRoots(isolate).exception()); return *value; } RUNTIME_FUNCTION(Runtime_LoadElementWithInterceptor) { // TODO(verwaest): This should probably get the holder and receiver as input. HandleScope scope(isolate); DirectHandle<JSObject> holder = args.at<JSObject>(0); // This function is called only for receiver-is-holder case. DirectHandle<JSObject> receiver = holder; DCHECK_GE(args.smi_value_at(1), 0); uint32_t index = args.smi_value_at(1); DirectHandle<InterceptorInfo> interceptor(holder->GetIndexedInterceptor(), isolate); PropertyCallbackArguments arguments(isolate, *holder); DirectHandle<Object> result = arguments.CallIndexedGetter(isolate, interceptor, index); // An exception was thrown in the interceptor. Propagate. RETURN_FAILURE_IF_EXCEPTION_DETECTOR(isolate, arguments); if (result.is_null()) { LookupIterator it(isolate, receiver, index, holder); DCHECK_EQ(LookupIterator::INTERCEPTOR, it.state()); it.Next(); ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, Object::GetProperty(&it)); } return *result; } RUNTIME_FUNCTION(Runtime_KeyedHasIC_Miss) { HandleScope scope(isolate); DCHECK_EQ(4, args.length()); // Runtime functions don't follow the IC's calling convention. Handle<JSAny> receiver = args.at<JSAny>(0); Handle<Object> key = args.at(1); int slot = args.tagged_index_value_at(2); Handle<HeapObject> maybe_vector = args.at<HeapObject>(3); Handle<FeedbackVector> vector = Handle<FeedbackVector>(); if (!IsUndefined(*maybe_vector, isolate)) { DCHECK(IsFeedbackVector(*maybe_vector)); vector = Cast<FeedbackVector>(maybe_vector); } FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot); KeyedLoadIC ic(isolate, vector, vector_slot, FeedbackSlotKind::kHasKeyed); ic.UpdateState(receiver, key); RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); } RUNTIME_FUNCTION(Runtime_HasElementWithInterceptor) { HandleScope scope(isolate); DirectHandle<JSObject> holder = args.at<JSObject>(0); DCHECK_GE(args.smi_value_at(1), 0); uint32_t index = args.smi_value_at(1); { DirectHandle<InterceptorInfo> interceptor(holder->GetIndexedInterceptor(), isolate); PropertyCallbackArguments arguments(isolate, *holder); if (interceptor->has_query()) { DirectHandle<Object> result = arguments.CallIndexedQuery(isolate, interceptor, index); // An exception was thrown in the interceptor. Propagate. RETURN_FAILURE_IF_EXCEPTION_DETECTOR(isolate, arguments); if (!result.is_null()) { int32_t value; CHECK(Object::ToInt32(*result, &value)); // TODO(ishell): PropertyAttributes::ABSENT is not exposed in the Api, // so it can't be officially returned. We should fix the tests instead. if (value == ABSENT) return ReadOnlyRoots(isolate).false_value(); arguments.AcceptSideEffects(); return ReadOnlyRoots(isolate).true_value(); } } else if (interceptor->has_getter()) { DirectHandle<Object> result = arguments.CallIndexedGetter(isolate, interceptor, index); // An exception was thrown in the interceptor. Propagate. RETURN_FAILURE_IF_EXCEPTION_DETECTOR(isolate, arguments); if (!result.is_null()) { arguments.AcceptSideEffects(); return ReadOnlyRoots(isolate).true_value(); } } // If the interceptor didn't handle the request, then there must be no // side effects. } LookupIterator it(isolate, holder, index, holder); DCHECK_EQ(LookupIterator::INTERCEPTOR, it.state()); it.Next(); Maybe<bool> maybe = JSReceiver::HasProperty(&it); if (maybe.IsNothing()) return ReadOnlyRoots(isolate).exception(); return ReadOnlyRoots(isolate).boolean_value(maybe.FromJust()); } } // namespace internal } // namespace v8