/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
deps/v8/src/maglev/maglev-graph-builder.cc
18 120 строк
717 KB
Node.js GitHub Bot
deps: patch V8 to 14.6.202.34
28 апр 2026, 04:49
Не верифицирован
28 апр 2026, 04:49
4e3a873
Код
Авторство
О чём код?
// Copyright 2022 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/maglev/maglev-graph-builder.h" #include <algorithm> #include <iomanip> #include <limits> #include <optional> #include <type_traits> #include <utility> #include "src/base/bits.h" #include "src/base/bounds.h" #include "src/base/container-utils.h" #include "src/base/division-by-constant.h" #include "src/base/ieee754.h" #include "src/base/logging.h" #include "src/base/small-vector.h" #include "src/base/vector.h" #include "src/builtins/builtins-constructor.h" #include "src/builtins/builtins.h" #include "src/codegen/bailout-reason.h" #include "src/codegen/cpu-features.h" #include "src/codegen/interface-descriptors-inl.h" #include "src/common/assert-scope.h" #include "src/common/globals.h" #include "src/common/message-template.h" #include "src/compiler-dispatcher/optimizing-compile-dispatcher.h" #include "src/compiler/access-info.h" #include "src/compiler/bytecode-liveness-map.h" #include "src/compiler/common-utils.h" #include "src/compiler/compilation-dependencies.h" #include "src/compiler/feedback-source.h" #include "src/compiler/frame-states.h" #include "src/compiler/heap-refs.h" #include "src/compiler/js-heap-broker-inl.h" #include "src/compiler/js-heap-broker.h" #include "src/compiler/processed-feedback.h" #include "src/deoptimizer/deoptimize-reason.h" #include "src/execution/protectors.h" #include "src/flags/flags.h" #include "src/handles/maybe-handles-inl.h" #include "src/ic/handler-configuration-inl.h" #include "src/interpreter/bytecode-array-iterator.h" #include "src/interpreter/bytecode-decoder.h" #include "src/interpreter/bytecode-flags-and-tokens.h" #include "src/interpreter/bytecode-register.h" #include "src/interpreter/bytecodes.h" #include "src/maglev/maglev-compilation-info.h" #include "src/maglev/maglev-compilation-unit.h" #include "src/maglev/maglev-graph-printer.h" #include "src/maglev/maglev-interpreter-frame-state.h" #include "src/maglev/maglev-ir-inl.h" #include "src/maglev/maglev-ir.h" #include "src/maglev/maglev-known-node-aspects.h" #include "src/maglev/maglev-node-type.h" #include "src/maglev/maglev-reducer-inl.h" #include "src/maglev/maglev-reducer.h" #include "src/numbers/conversions.h" #include "src/numbers/ieee754.h" #include "src/objects/arguments.h" #include "src/objects/elements-kind.h" #include "src/objects/feedback-vector.h" #include "src/objects/fixed-array.h" #include "src/objects/function-kind.h" #include "src/objects/heap-number-inl.h" #include "src/objects/heap-object.h" #include "src/objects/instance-type-inl.h" #include "src/objects/instance-type.h" #include "src/objects/js-array.h" #include "src/objects/js-function.h" #include "src/objects/js-generator.h" #include "src/objects/js-objects.h" #include "src/objects/literal-objects-inl.h" #include "src/objects/name-inl.h" #include "src/objects/object-list-macros.h" #include "src/objects/property-cell.h" #include "src/objects/property-details.h" #include "src/objects/shared-function-info.h" #include "src/objects/slots-inl.h" #include "src/objects/type-hints.h" #include "src/roots/roots.h" #include "src/utils/utils.h" #include "src/zone/zone.h" #ifdef V8_INTL_SUPPORT #include "src/objects/intl-objects.h" #endif #define TRACE(...) \ if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && \ is_tracing_enabled())) { \ std::cout << __VA_ARGS__ << std::endl; \ } #define FAIL(...) \ TRACE("Failed " << __func__ << ":" << __LINE__ << ": " << __VA_ARGS__); \ return {}; namespace v8::internal::maglev { namespace { enum class CpuOperation { kFloat64Round, kMathClz32, }; // TODO(leszeks): Add a generic mechanism for marking nodes as optionally // supported. bool IsSupported(CpuOperation op) { switch (op) { case CpuOperation::kFloat64Round: #if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_IA32) return CpuFeatures::IsSupported(SSE4_1) || CpuFeatures::IsSupported(AVX); #elif defined(V8_TARGET_ARCH_ARM) return CpuFeatures::IsSupported(ARMv8); #elif defined(V8_TARGET_ARCH_ARM64) || defined(V8_TARGET_ARCH_PPC64) || \ defined(V8_TARGET_ARCH_S390X) || defined(V8_TARGET_ARCH_RISCV64) || \ defined(V8_TARGET_ARCH_RISCV32) || defined(V8_TARGET_ARCH_LOONG64) || \ defined(V8_TARGET_ARCH_MIPS64) return true; #else #error "V8 does not support this architecture." #endif case CpuOperation::kMathClz32: #if defined(V8_TARGET_ARCH_ARM64) || defined(V8_TARGET_ARCH_S390X) || \ defined(V8_TARGET_ARCH_PPC64) return true; #elif defined(V8_TARGET_ARCH_ARM) return CpuFeatures::IsSupported(ARMv8); #elif defined(V8_TARGET_ARCH_X64) return CpuFeatures::IsSupported(LZCNT); #elif defined(V8_TARGET_ARCH_RISCV64) || defined(V8_TARGET_ARCH_RISCV32) return CpuFeatures::IsSupported(ZBB); #elif defined(V8_TARGET_ARCH_IA32) || defined(V8_TARGET_ARCH_PPC64) || \ defined(V8_TARGET_ARCH_LOONG64) || defined(V8_TARGET_ARCH_MIPS64) return false; #else #error "V8 does not support this architecture." #endif } } class FunctionContextSpecialization final : public AllStatic { public: static compiler::OptionalContextRef TryToRef( const MaglevCompilationUnit* unit, ValueNode* context, size_t* depth) { DCHECK(unit->info()->specialize_to_function_context()); if (Constant* n = context->TryCast<Constant>()) { return n->ref().AsContext().previous(unit->broker(), depth); } return {}; } }; } // namespace ValueNode* MaglevGraphBuilder::TryGetParentContext(ValueNode* node) { if (CreateFunctionContext* n = node->TryCast<CreateFunctionContext>()) { return n->ContextInput().node(); } if (InlinedAllocation* alloc = node->TryCast<InlinedAllocation>()) { return alloc->object()->get( Context::OffsetOfElementAt(Context::PREVIOUS_INDEX)); } if (CallRuntime* n = node->TryCast<CallRuntime>()) { switch (n->function_id()) { case Runtime::kPushBlockContext: case Runtime::kPushCatchContext: case Runtime::kNewFunctionContext: return n->ContextInput().node(); default: break; } } return nullptr; } // Attempts to walk up the context chain through the graph in order to reduce // depth and thus the number of runtime loads. void MaglevGraphBuilder::MinimizeContextChainDepth(ValueNode** context, size_t* depth) { while (*depth > 0) { ValueNode* parent_context = TryGetParentContext(*context); if (parent_context == nullptr) return; *context = parent_context; (*depth)--; } } void MaglevGraphBuilder::EscapeContext() { ValueNode* context = GetContext(); if (InlinedAllocation* alloc = context->TryCast<InlinedAllocation>()) { alloc->ForceEscaping(); } } class CallArguments { public: enum Mode { kDefault, kWithSpread, kWithArrayLike, }; CallArguments(ConvertReceiverMode receiver_mode, interpreter::RegisterList reglist, const InterpreterFrameState& frame, Mode mode = kDefault) : receiver_mode_(receiver_mode), args_(reglist.register_count()), mode_(mode) { for (int i = 0; i < reglist.register_count(); i++) { args_[i] = frame.get(reglist[i]); } DCHECK_IMPLIES(args_.size() == 0, receiver_mode == ConvertReceiverMode::kNullOrUndefined); DCHECK_IMPLIES(mode == kWithArrayLike, receiver_mode == ConvertReceiverMode::kAny); DCHECK_IMPLIES(mode == kWithArrayLike, args_.size() == 2); } explicit CallArguments(ConvertReceiverMode receiver_mode) : receiver_mode_(receiver_mode), args_(), mode_(kDefault) { DCHECK_EQ(receiver_mode, ConvertReceiverMode::kNullOrUndefined); } CallArguments(ConvertReceiverMode receiver_mode, std::initializer_list<ValueNode*> args, Mode mode = kDefault) : receiver_mode_(receiver_mode), args_(args), mode_(mode) { DCHECK_IMPLIES(mode != kDefault, receiver_mode == ConvertReceiverMode::kAny); DCHECK_IMPLIES(mode == kWithArrayLike, args_.size() == 2); CheckArgumentsAreNotConversionNodes(); } CallArguments(ConvertReceiverMode receiver_mode, base::SmallVector<ValueNode*, 8>&& args, Mode mode = kDefault) : receiver_mode_(receiver_mode), args_(std::move(args)), mode_(mode) { DCHECK_IMPLIES(mode != kDefault, receiver_mode == ConvertReceiverMode::kAny); DCHECK_IMPLIES(mode == kWithArrayLike, args_.size() == 2); CheckArgumentsAreNotConversionNodes(); } ValueNode* receiver() const { if (receiver_mode_ == ConvertReceiverMode::kNullOrUndefined) { return nullptr; } return args_[0]; } void set_receiver(ValueNode* receiver) { if (receiver_mode_ == ConvertReceiverMode::kNullOrUndefined) { args_.insert(args_.data(), receiver); receiver_mode_ = ConvertReceiverMode::kAny; } else { DCHECK(!receiver->is_conversion()); args_[0] = receiver; } } ValueNode* array_like_argument() { DCHECK_EQ(mode_, kWithArrayLike); DCHECK_GT(count(), 0); return args_[args_.size() - 1]; } ValueNode* spread() { DCHECK_EQ(mode_, kWithSpread); DCHECK_GT(count(), 0); return args_[args_.size() - 1]; } size_t count() const { DCHECK_LE(index_offset(), args_.size()); return args_.size() - index_offset(); } size_t count_with_receiver() const { return count() + 1; } ValueNode* operator[](size_t i) const { i += index_offset(); if (i >= args_.size()) return nullptr; return args_[i]; } ValueNode** begin() { return args_.begin() + index_offset(); } const ValueNode* const* begin() const { return args_.begin() + index_offset(); } ValueNode** end() { return args_.end(); } const ValueNode* const* end() const { return args_.end(); } Mode mode() const { return mode_; } ConvertReceiverMode receiver_mode() const { return receiver_mode_; } void PopArrayLikeArgument() { DCHECK_EQ(mode_, kWithArrayLike); DCHECK_GT(count(), 0); args_.pop_back(); } void PopSpread() { DCHECK_EQ(mode_, kWithSpread); DCHECK_GT(count(), 0); args_.pop_back(); } void ResizeDefaultArguments(size_t new_count) { DCHECK_EQ(mode_, kDefault); DCHECK_GT(count(), new_count); args_.resize(new_count + index_offset()); DCHECK_EQ(count(), new_count); } void PopReceiver(ConvertReceiverMode new_receiver_mode) { DCHECK_NE(receiver_mode_, ConvertReceiverMode::kNullOrUndefined); DCHECK_NE(new_receiver_mode, ConvertReceiverMode::kNullOrUndefined); DCHECK_GT(args_.size(), 0); // We have at least a receiver to pop! size_t new_args_size_in_bytes = (args_.size() - 1) * sizeof(args_[0]); MemMove(args_.data(), args_.data() + 1, new_args_size_in_bytes); args_.pop_back(); // If there is no non-receiver argument to become the new receiver, // consider the new receiver to be known undefined. receiver_mode_ = args_.empty() ? ConvertReceiverMode::kNullOrUndefined : new_receiver_mode; } private: ConvertReceiverMode receiver_mode_; base::SmallVector<ValueNode*, 8> args_; Mode mode_; int index_offset() const { return receiver_mode_ == ConvertReceiverMode::kNullOrUndefined ? 0 : 1; } void CheckArgumentsAreNotConversionNodes() { #ifdef DEBUG // Arguments can leak to the interpreter frame if the call is inlined, // conversions should be stored in known_node_aspects/NodeInfo. for (ValueNode* arg : args_) { DCHECK(!arg->is_conversion()); } #endif // DEBUG } }; // TODO(victorgomes): This scope should be moved to MaglevReducer after all its // uses were moved. class V8_NODISCARD MaglevGraphBuilder::SaveCallSpeculationScope { public: explicit SaveCallSpeculationScope( MaglevGraphBuilder* builder, compiler::FeedbackSource feedback_source = compiler::FeedbackSource()) : builder_(builder) { saved_ = builder_->current_speculation_feedback(); // Only set the current speculation feedback if speculation is allowed. SpeculationMode mode = MaglevGraphBuilder::GetSpeculationMode( builder_->broker(), feedback_source); if (mode != SpeculationMode::kDisallowSpeculation) { builder_->reducer_.set_current_speculation_feedback(feedback_source); builder_->current_speculation_mode_ = mode; } else { builder->reducer_.set_current_speculation_feedback( compiler::FeedbackSource()); builder_->current_speculation_mode_ = SpeculationMode::kDisallowSpeculation; } } ~SaveCallSpeculationScope() { builder_->reducer_.set_current_speculation_feedback(saved_); builder_->current_speculation_mode_ = saved_mode_; } const compiler::FeedbackSource& value() { return saved_; } private: MaglevGraphBuilder* builder_; compiler::FeedbackSource saved_; SpeculationMode saved_mode_; }; // static SpeculationMode MaglevGraphBuilder::GetSpeculationMode( compiler::JSHeapBroker* broker, compiler::FeedbackSource feedback_source) { if (!feedback_source.IsValid()) return SpeculationMode::kDisallowSpeculation; compiler::ProcessedFeedback const& processed_feedback = broker->GetFeedbackForCall(feedback_source); return processed_feedback.IsInsufficient() ? SpeculationMode::kDisallowSpeculation : processed_feedback.AsCall().speculation_mode(); } MaglevGraphBuilder::LazyDeoptResultLocationScope::LazyDeoptResultLocationScope( MaglevGraphBuilder* builder, interpreter::Register result_location, int result_size) : builder_(builder), previous_(builder->lazy_deopt_result_location_scope_), result_location_(result_location), result_size_(result_size) { builder_->lazy_deopt_result_location_scope_ = this; } MaglevGraphBuilder::LazyDeoptResultLocationScope:: ~LazyDeoptResultLocationScope() { builder_->lazy_deopt_result_location_scope_ = previous_; } class V8_NODISCARD MaglevGraphBuilder::DeoptFrameScopeBase { public: DeoptFrameScopeBase(MaglevGraphBuilder* builder, Builtin continuation, compiler::OptionalJSFunctionRef maybe_js_target = {}, base::Vector<ValueNode* const> parameters = {}) : builder_(builder), data_(DeoptFrame::BuiltinContinuationFrameData{ continuation, parameters.empty() ? base::Vector<ValueNode*>{} : builder->zone()->CloneVector(parameters), builder->GetContext(), maybe_js_target}) { builder->current_interpreter_frame().virtual_objects().Snapshot(); if (parameters.size() > 0) { if (InlinedAllocation* receiver = parameters[0]->TryCast<InlinedAllocation>()) { // We escape the first argument, since the builtin continuation call can // trigger a stack iteration, which expects the receiver to be a // meterialized object. receiver->ForceEscaping(); } } else { DCHECK(data_.get<DeoptFrame::BuiltinContinuationFrameData>() .parameters.empty()); } } DeoptFrameScopeBase(MaglevGraphBuilder* builder, ValueNode* receiver) : builder_(builder), data_(DeoptFrame::ConstructInvokeStubFrameData{ *builder->compilation_unit(), builder->GetCurrentSourcePosition(), receiver, builder->GetContext()}) { builder_->current_interpreter_frame().virtual_objects().Snapshot(); } ~DeoptFrameScopeBase() { // We might have cached a checkpointed frame which includes this scope; // reset it just in case. builder_->latest_checkpointed_frame_ = nullptr; } DeoptFrame::FrameData& data() { return data_; } const DeoptFrame::FrameData& data() const { return data_; } protected: MaglevGraphBuilder* builder() { return builder_; } private: MaglevGraphBuilder* builder_; DeoptFrame::FrameData data_; }; namespace { void DebugVerifyBuiltinDeoptFrame(const DeoptFrame::FrameData& data, compiler::ContinuationFrameStateMode mode) { #ifdef DEBUG DCHECK_EQ(data.tag(), DeoptFrame::FrameType::kBuiltinContinuationFrame); const DeoptFrame::BuiltinContinuationFrameData& frame = data.get<DeoptFrame::BuiltinContinuationFrameData>(); if (frame.maybe_js_target) { int stack_parameter_count = Builtins::GetStackParameterCount(frame.builtin_id); DCHECK_EQ(stack_parameter_count, frame.parameters.length() + compiler::DeoptimizerParameterCountFor(mode)); } else { CallInterfaceDescriptor descriptor = Builtins::CallInterfaceDescriptorFor(frame.builtin_id); DCHECK_EQ(descriptor.GetParameterCount(), frame.parameters.length() + compiler::DeoptimizerParameterCountFor(mode)); } #endif } } // namespace class V8_NODISCARD MaglevGraphBuilder::LazyDeoptFrameScope : public MaglevGraphBuilder::DeoptFrameScopeBase { public: LazyDeoptFrameScope(MaglevGraphBuilder* builder, Builtin continuation, compiler::OptionalJSFunctionRef maybe_js_target = {}, base::Vector<ValueNode* const> parameters = {}) : DeoptFrameScopeBase(builder, continuation, maybe_js_target, parameters), parent_(builder->current_lazy_deopt_scope_) { DebugVerifyBuiltinDeoptFrame(data(), compiler::ContinuationFrameStateMode::LAZY); builder->current_lazy_deopt_scope_ = this; } LazyDeoptFrameScope(MaglevGraphBuilder* builder, ValueNode* receiver) : DeoptFrameScopeBase(builder, receiver), parent_(builder->current_lazy_deopt_scope_) { builder->current_lazy_deopt_scope_ = this; } ~LazyDeoptFrameScope() { builder()->current_lazy_deopt_scope_ = parent_; } LazyDeoptFrameScope* parent() const { return parent_; } private: MaglevGraphBuilder::LazyDeoptFrameScope* parent_; }; class V8_NODISCARD MaglevGraphBuilder::EagerDeoptFrameScope : public MaglevGraphBuilder::DeoptFrameScopeBase { public: EagerDeoptFrameScope(MaglevGraphBuilder* builder, Builtin continuation, compiler::OptionalJSFunctionRef maybe_js_target = {}, base::Vector<ValueNode* const> parameters = {}) : DeoptFrameScopeBase(builder, continuation, maybe_js_target, parameters), parent_(builder->current_lazy_deopt_scope_) { DebugVerifyBuiltinDeoptFrame(data(), compiler::ContinuationFrameStateMode::EAGER); // Eager deopt continuations cannot be nested, so this should always be // null. DCHECK_NULL(builder->current_eager_deopt_scope_); builder->current_eager_deopt_scope_ = this; } ~EagerDeoptFrameScope() { builder()->current_eager_deopt_scope_ = nullptr; } LazyDeoptFrameScope* parent() const { return parent_; } private: // An eager deopt continuation's parent has to be a lazy deopt continuation, // because we want to continue _after_ the eager deopt continuation completes. MaglevGraphBuilder::LazyDeoptFrameScope* parent_; }; class MaglevGraphBuilder::MaglevSubGraphBuilder::Variable { public: explicit Variable(int index) : pseudo_register_(index) {} private: friend class MaglevSubGraphBuilder; // Variables pretend to be interpreter registers as far as the dummy // compilation unit and merge states are concerned. interpreter::Register pseudo_register_; }; class MaglevGraphBuilder::MaglevSubGraphBuilder:: LabelForTrackingInterpreterFrameState { public: LabelForTrackingInterpreterFrameState(MaglevSubGraphBuilder* sub_builder, int predecessor_count, int future_bind_offset) : sub_builder_(sub_builder), predecessor_count_(predecessor_count), future_bind_offset_(future_bind_offset) {} LabelForTrackingInterpreterFrameState(MaglevSubGraphBuilder* sub_builder, int predecessor_count, std::initializer_list<Variable*> vars, int future_bind_offset) : sub_builder_(sub_builder), predecessor_count_(predecessor_count), vars_(vars), future_bind_offset_(future_bind_offset) {} private: friend class Label; MaglevSubGraphBuilder* sub_builder_; int predecessor_count_; std::vector<Variable*> vars_; int future_bind_offset_; }; class MaglevGraphBuilder::MaglevSubGraphBuilder::Label { public: Label(MaglevSubGraphBuilder* sub_builder, int predecessor_count) : predecessor_count_(predecessor_count), variable_liveness_( sub_builder->builder_->zone()->New<compiler::BytecodeLivenessState>( sub_builder->variable_compilation_unit_->register_count(), sub_builder->builder_->zone())) {} Label(MaglevSubGraphBuilder* sub_builder, int predecessor_count, std::initializer_list<Variable*> vars) : Label(sub_builder, predecessor_count) { for (Variable* var : vars) { variable_liveness_->MarkRegisterLive(var->pseudo_register_.index()); } } // NOLINTNEXTLINE(runtime/explicit) Label(const LabelForTrackingInterpreterFrameState& label) : Label(label.sub_builder_, label.predecessor_count_) { for (Variable* var : label.vars_) { variable_liveness_->MarkRegisterLive(var->pseudo_register_.index()); } future_bind_offset_ = label.future_bind_offset_; } bool ShouldTrackInterpreterFrameState() const { return future_bind_offset_.has_value(); } private: friend class MaglevSubGraphBuilder; friend class BranchBuilder; int predecessor_count_ = -1; // These are for tracking the values of Variables and merging them into // variable_frame_. MergePointInterpreterFrameState* variable_merge_state_ = nullptr; compiler::BytecodeLivenessState* variable_liveness_ = nullptr; // These are for tracking the values of registers and merging into the // interpreter frame. Setting the future_bind_offset to the bytecode offset // where the "bind" for this label will be enables tracking. // TODO(marja): Unify merge_state_ and variable_merge_state_ and only have // one. std::optional<int> future_bind_offset_; MergePointInterpreterFrameState* merge_state_ = nullptr; BasicBlockRef ref_; }; class MaglevGraphBuilder::MaglevSubGraphBuilder::LoopLabel { public: private: explicit LoopLabel(MergePointInterpreterFrameState* merge_state, BasicBlock* loop_header) : merge_state_(merge_state), loop_header_(loop_header) {} friend class MaglevSubGraphBuilder; MergePointInterpreterFrameState* merge_state_ = nullptr; BasicBlock* loop_header_; }; class MaglevGraphBuilder::MaglevSubGraphBuilder:: BorrowParentKnownNodeAspectsAndVOs { public: explicit BorrowParentKnownNodeAspectsAndVOs( MaglevSubGraphBuilder* sub_builder) : sub_builder_(sub_builder) { sub_builder_->TakeKnownNodeAspectsAndVOsFromParent(); } ~BorrowParentKnownNodeAspectsAndVOs() { sub_builder_->MoveKnownNodeAspectsAndVOsToParent(); } private: MaglevSubGraphBuilder* sub_builder_; }; void MaglevGraphBuilder::BranchBuilder::StartFallthroughBlock( BasicBlock* predecessor) { switch (mode()) { case kBytecodeJumpTarget: { auto& data = data_.bytecode_target; if (data.patch_accumulator_scope && (data.patch_accumulator_scope->node_ == builder_->GetAccumulator())) { SetAccumulatorInBranch(BranchType::kBranchIfTrue); builder_->MergeIntoFrameState(predecessor, data.jump_target_offset); SetAccumulatorInBranch(BranchType::kBranchIfFalse); builder_->StartFallthroughBlock(data.fallthrough_offset, predecessor); } else { builder_->MergeIntoFrameState(predecessor, data.jump_target_offset); builder_->StartFallthroughBlock(data.fallthrough_offset, predecessor); } break; } case kLabelJumpTarget: auto& data = data_.label_target; sub_builder_->MergeIntoLabel(data.jump_label, predecessor); builder_->StartNewBlock(predecessor, nullptr, data.fallthrough); break; } } void MaglevGraphBuilder::BranchBuilder::SetAccumulatorInBranch( BranchType jump_type) const { DCHECK_EQ(mode(), kBytecodeJumpTarget); auto& data = data_.bytecode_target; if (branch_specialization_mode_ == BranchSpecializationMode::kAlwaysBoolean) { builder_->SetAccumulatorInBranch(builder_->GetBooleanConstant( data.patch_accumulator_scope->jump_type_ == jump_type)); } else if (data.patch_accumulator_scope->jump_type_ == jump_type) { builder_->SetAccumulatorInBranch( builder_->GetRootConstant(data.patch_accumulator_scope->root_index_)); } else { builder_->SetAccumulatorInBranch(data.patch_accumulator_scope->node_); } } BasicBlockRef* MaglevGraphBuilder::BranchBuilder::jump_target() { switch (mode()) { case kBytecodeJumpTarget: return &builder_->jump_targets_[data_.bytecode_target.jump_target_offset]; case kLabelJumpTarget: return &data_.label_target.jump_label->ref_; } } BasicBlockRef* MaglevGraphBuilder::BranchBuilder::fallthrough() { switch (mode()) { case kBytecodeJumpTarget: return &builder_->jump_targets_[data_.bytecode_target.fallthrough_offset]; case kLabelJumpTarget: return &data_.label_target.fallthrough; } } BasicBlockRef* MaglevGraphBuilder::BranchBuilder::true_target() { return jump_type_ == BranchType::kBranchIfTrue ? jump_target() : fallthrough(); } BasicBlockRef* MaglevGraphBuilder::BranchBuilder::false_target() { return jump_type_ == BranchType::kBranchIfFalse ? jump_target() : fallthrough(); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BranchBuilder::FromBool( bool value) const { switch (mode()) { case kBytecodeJumpTarget: { BranchType type_if_need_to_jump = (value ? BranchType::kBranchIfTrue : BranchType::kBranchIfFalse); builder_->MarkBranchDeadAndJumpIfNeeded(jump_type_ == type_if_need_to_jump); return BranchResult::kDefault; } case kLabelJumpTarget: return value ? BranchResult::kAlwaysTrue : BranchResult::kAlwaysFalse; } } template <typename ControlNodeT, typename... Args> MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BranchBuilder::Build( std::initializer_list<ValueNode*> control_inputs, Args&&... args) { static_assert(IsConditionalControlNode(Node::opcode_of<ControlNodeT>)); auto result = builder_->FinishBlock<ControlNodeT>( control_inputs, std::forward<Args>(args)..., true_target(), false_target()); if (!result) { return Abort(); } BasicBlock* block = *result; DCHECK_NOT_NULL(block); StartFallthroughBlock(block); return BranchResult::kDefault; } MaglevGraphBuilder::MaglevSubGraphBuilder::MaglevSubGraphBuilder( MaglevGraphBuilder* builder, int variable_count) : builder_(builder), variable_compilation_unit_(MaglevCompilationUnit::NewDummy( builder->zone(), builder->compilation_unit(), variable_count, 0, 0)), variable_frame_(*variable_compilation_unit_, nullptr) { // We need to set a context, since this is unconditional in the frame state, // so set it to the real context. variable_frame_.set(interpreter::Register::current_context(), builder_->current_interpreter_frame().get( interpreter::Register::current_context())); DCHECK_NULL(variable_frame_.known_node_aspects()); } MaglevGraphBuilder::MaglevSubGraphBuilder::LoopLabel MaglevGraphBuilder::MaglevSubGraphBuilder::BeginLoop( std::initializer_list<Variable*> loop_vars) { // Create fake liveness and loop info for the loop, with all given loop vars // set to be live and assigned inside the loop. compiler::BytecodeLivenessState* loop_header_liveness = builder_->zone()->New<compiler::BytecodeLivenessState>( variable_compilation_unit_->register_count(), builder_->zone()); compiler::LoopInfo* loop_info = builder_->zone()->New<compiler::LoopInfo>( -1, 0, kMaxInt, variable_compilation_unit_->parameter_count(), variable_compilation_unit_->register_count(), builder_->zone()); for (Variable* var : loop_vars) { loop_header_liveness->MarkRegisterLive(var->pseudo_register_.index()); loop_info->assignments().Add(var->pseudo_register_); } // Finish the current block, jumping (as a fallthrough) to the loop header. BasicBlockRef loop_header_ref; BasicBlock* loop_predecessor = builder_->FinishBlockNoAbort<Jump>({}, &loop_header_ref); // Create a state for the loop header, with two predecessors (the above jump // and the back edge), and initialise with the current state. MergePointInterpreterFrameState* loop_state = MergePointInterpreterFrameState::NewForLoop( variable_frame_, builder_, *variable_compilation_unit_, 0, 2, loop_header_liveness, loop_info); { BorrowParentKnownNodeAspectsAndVOs borrow(this); loop_state->Merge(builder_, *variable_compilation_unit_, variable_frame_, loop_predecessor); } // Start a new basic block for the loop. DCHECK_NULL(variable_frame_.known_node_aspects()); variable_frame_.CopyFrom(*variable_compilation_unit_, *loop_state); MoveKnownNodeAspectsAndVOsToParent(); builder_->ProcessMergePointPredecessors(*loop_state, loop_header_ref); builder_->StartNewBlock(nullptr, loop_state, loop_header_ref); return LoopLabel{loop_state, loop_header_ref.block_ptr()}; } template <typename ControlNodeT, typename... Args> ReduceResult MaglevGraphBuilder::MaglevSubGraphBuilder::GotoIfTrue( Label* true_target, std::initializer_list<ValueNode*> control_inputs, Args&&... args) { static_assert(IsConditionalControlNode(Node::opcode_of<ControlNodeT>)); BasicBlockRef fallthrough_ref; // Pass through to FinishBlock, converting Labels to BasicBlockRefs and the // fallthrough label to the fallthrough ref. BasicBlock* block; GET_BLOCK_OR_ABORT(block, builder_->FinishBlock<ControlNodeT>( control_inputs, std::forward<Args>(args)..., &true_target->ref_, &fallthrough_ref)); MergeIntoLabel(true_target, block); builder_->StartNewBlock(block, nullptr, fallthrough_ref); return ReduceResult::Done(); } template <typename ControlNodeT, typename... Args> ReduceResult MaglevGraphBuilder::MaglevSubGraphBuilder::GotoIfFalse( Label* false_target, std::initializer_list<ValueNode*> control_inputs, Args&&... args) { static_assert(IsConditionalControlNode(Node::opcode_of<ControlNodeT>)); BasicBlockRef fallthrough_ref; // Pass through to FinishBlock, converting Labels to BasicBlockRefs and the // fallthrough label to the fallthrough ref. BasicBlock* block; GET_BLOCK_OR_ABORT(block, builder_->FinishBlock<ControlNodeT>( control_inputs, std::forward<Args>(args)..., &fallthrough_ref, &false_target->ref_)); MergeIntoLabel(false_target, block); builder_->StartNewBlock(block, nullptr, fallthrough_ref); return ReduceResult::Done(); } void MaglevGraphBuilder::MaglevSubGraphBuilder::GotoOrTrim(Label* label) { if (builder_->current_block() == nullptr) { ReducePredecessorCount(label); return; } Goto(label); } void MaglevGraphBuilder::MaglevSubGraphBuilder::Goto(Label* label) { CHECK_NOT_NULL(builder_->current_block()); BasicBlock* block = builder_->FinishBlockNoAbort<Jump>({}, &label->ref_); MergeIntoLabel(label, block); } void MaglevGraphBuilder::MaglevSubGraphBuilder::ReducePredecessorCount( Label* label, unsigned num) { DCHECK_GE(label->predecessor_count_, num); if (num == 0) { return; } label->predecessor_count_ -= num; if (label->variable_merge_state_ != nullptr) { label->variable_merge_state_->MergeDead(*variable_compilation_unit_, num); if (label->ShouldTrackInterpreterFrameState()) { DCHECK_NOT_NULL(label->merge_state_); label->merge_state_->MergeDead(*compilation_unit(), num); } } } void MaglevGraphBuilder::MaglevSubGraphBuilder::EndLoop(LoopLabel* loop_label) { if (builder_->current_block() == nullptr) { loop_label->merge_state_->MergeDeadLoop(*variable_compilation_unit_); return; } BasicBlock* block = builder_->FinishBlockNoAbort<JumpLoop>({}, loop_label->loop_header_); { BorrowParentKnownNodeAspectsAndVOs borrow(this); loop_label->merge_state_->MergeLoop(builder_, *variable_compilation_unit_, variable_frame_, block); } block->set_predecessor_id(loop_label->merge_state_->predecessor_count() - 1); } ReduceResult MaglevGraphBuilder::MaglevSubGraphBuilder::TrimPredecessorsAndBind( Label* label) { int predecessors_so_far = label->variable_merge_state_ == nullptr ? 0 : label->variable_merge_state_->predecessors_so_far(); if (label->ShouldTrackInterpreterFrameState()) { DCHECK_EQ(predecessors_so_far, label->merge_state_ == nullptr ? 0 : label->merge_state_->predecessors_so_far()); } DCHECK_LE(predecessors_so_far, label->predecessor_count_); builder_->set_current_block(nullptr); ReducePredecessorCount(label, label->predecessor_count_ - predecessors_so_far); if (predecessors_so_far == 0) return ReduceResult::DoneWithAbort(); Bind(label); return ReduceResult::Done(); } void MaglevGraphBuilder::MaglevSubGraphBuilder::Bind(Label* label) { DCHECK_NULL(builder_->current_block()); DCHECK_NULL(variable_frame_.known_node_aspects()); variable_frame_.CopyFrom(*variable_compilation_unit_, *label->variable_merge_state_); if (label->ShouldTrackInterpreterFrameState()) { DCHECK_EQ(*label->future_bind_offset_, builder_->iterator_.current_offset()); builder_->current_interpreter_frame_.CopyFrom(*compilation_unit(), *label->merge_state_); } MoveKnownNodeAspectsAndVOsToParent(); CHECK_EQ(label->variable_merge_state_->predecessors_so_far(), label->predecessor_count_); if (label->ShouldTrackInterpreterFrameState()) { DCHECK_EQ(label->merge_state_->predecessors_so_far(), label->predecessor_count_); // The new block created below only points to label->variable_merge_state_. // Move the phis in label->merge_state_ into it, so that they get processed, // too. label->variable_merge_state_->phis()->Append( std::move(*label->merge_state_->phis())); } builder_->ProcessMergePointPredecessors(*label->variable_merge_state_, label->ref_); builder_->StartNewBlock(nullptr, label->variable_merge_state_, label->ref_); } void MaglevGraphBuilder::MaglevSubGraphBuilder::set(Variable& var, ValueNode* value) { variable_frame_.set(var.pseudo_register_, value); } ValueNode* MaglevGraphBuilder::MaglevSubGraphBuilder::get( const Variable& var) const { return variable_frame_.get(var.pseudo_register_); } template <typename FCond, typename FTrue, typename FFalse> ReduceResult MaglevGraphBuilder::MaglevSubGraphBuilder::Branch( std::initializer_list<MaglevSubGraphBuilder::Variable*> vars, FCond cond, FTrue if_true, FFalse if_false) { MaglevSubGraphBuilder::Label else_branch(this, 1); BranchBuilder builder(builder_, this, BranchType::kBranchIfFalse, &else_branch); BranchResult branch_result = cond(builder); switch (branch_result) { case BranchResult::kAlwaysTrue: return if_true(); case BranchResult::kAlwaysFalse: return if_false(); case BranchResult::kAbort: return ReduceResult::DoneWithAbort(); case BranchResult::kDefault: break; } MaglevSubGraphBuilder::Label done(this, 2, vars); MaybeReduceResult result_if_true = if_true(); CHECK(result_if_true.IsDone()); GotoOrTrim(&done); Bind(&else_branch); MaybeReduceResult result_if_false = if_false(); CHECK(result_if_false.IsDone()); if (result_if_true.IsDoneWithAbort() && result_if_false.IsDoneWithAbort()) { return ReduceResult::DoneWithAbort(); } GotoOrTrim(&done); Bind(&done); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildInt32Max(ValueNode* a, ValueNode* b) { return Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare(builder, Operation::kLessThan, a, b); }, [&]() -> ReduceResult { return b; }, [&]() -> ReduceResult { return a; }); } ReduceResult MaglevGraphBuilder::BuildInt32Min(ValueNode* a, ValueNode* b) { return Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare(builder, Operation::kLessThan, a, b); }, [&]() -> ReduceResult { return a; }, [&]() -> ReduceResult { return b; }); } ReduceResult MaglevGraphBuilder::Select( base::FunctionRef<BranchResult(BranchBuilder&)> cond, base::FunctionRef<ReduceResult()> if_true, base::FunctionRef<ReduceResult()> if_false) { MaglevSubGraphBuilder subgraph(this, 1); MaglevSubGraphBuilder::Label else_branch(&subgraph, 1); BranchBuilder builder(this, &subgraph, BranchType::kBranchIfFalse, &else_branch); BranchResult branch_result = cond(builder); switch (branch_result) { case BranchResult::kAlwaysTrue: return if_true(); case BranchResult::kAlwaysFalse: return if_false(); case BranchResult::kAbort: return ReduceResult::DoneWithAbort(); case BranchResult::kDefault: break; } MaglevSubGraphBuilder::Variable ret_val(0); MaglevSubGraphBuilder::Label done(&subgraph, 2, {&ret_val}); ReduceResult result_if_true = if_true(); CHECK(result_if_true.IsDone()); if (result_if_true.IsDoneWithValue()) { subgraph.set(ret_val, result_if_true.value()); } subgraph.GotoOrTrim(&done); subgraph.Bind(&else_branch); ReduceResult result_if_false = if_false(); CHECK(result_if_false.IsDone()); if (result_if_true.IsDoneWithAbort() && result_if_false.IsDoneWithAbort()) { return ReduceResult::DoneWithAbort(); } if (result_if_false.IsDoneWithValue()) { subgraph.set(ret_val, result_if_false.value()); } subgraph.GotoOrTrim(&done); subgraph.Bind(&done); return subgraph.get(ret_val); } // Known node aspects for the pseudo frame are null aside from when merging -- // before each merge, we should borrow the node aspects from the parent // builder, and after each merge point, we should copy the node aspects back // to the parent. This is so that the parent graph builder can update its own // known node aspects without having to worry about this pseudo frame. void MaglevGraphBuilder::MaglevSubGraphBuilder:: TakeKnownNodeAspectsAndVOsFromParent() { DCHECK_NULL(variable_frame_.known_node_aspects()); variable_frame_.set_known_node_aspects( builder_->current_interpreter_frame_.known_node_aspects()); } void MaglevGraphBuilder::MaglevSubGraphBuilder:: MoveKnownNodeAspectsAndVOsToParent() { DCHECK_NOT_NULL(variable_frame_.known_node_aspects()); builder_->current_interpreter_frame_.set_known_node_aspects( variable_frame_.known_node_aspects()); variable_frame_.clear_known_node_aspects(); } void MaglevGraphBuilder::MaglevSubGraphBuilder::MergeIntoLabel( Label* label, BasicBlock* predecessor) { BorrowParentKnownNodeAspectsAndVOs borrow(this); if (label->variable_merge_state_ == nullptr) { // If there's no merge state, allocate a new one. DCHECK_NULL(label->merge_state_); label->variable_merge_state_ = MergePointInterpreterFrameState::New( *variable_compilation_unit_, variable_frame_, 0, label->predecessor_count_, predecessor, label->variable_liveness_); if (label->ShouldTrackInterpreterFrameState()) { label->merge_state_ = MergePointInterpreterFrameState::New( *compilation_unit(), builder_->current_interpreter_frame_, 0, label->predecessor_count_, predecessor, builder_->GetInLivenessFor(*label->future_bind_offset_)); } } else { // If there already is a frame state, merge. label->variable_merge_state_->Merge(builder_, *variable_compilation_unit_, variable_frame_, predecessor); if (label->ShouldTrackInterpreterFrameState()) { DCHECK_NOT_NULL(label->merge_state_); label->merge_state_->Merge(builder_, *compilation_unit(), builder_->current_interpreter_frame_, predecessor); } } } MaglevGraphBuilder::MaglevGraphBuilder(LocalIsolate* local_isolate, MaglevCompilationUnit* compilation_unit, Graph* graph, MaglevCallerDetails* caller_details) : reducer_(this, graph, compilation_unit), local_isolate_(local_isolate), compilation_unit_(compilation_unit), caller_details_(caller_details), flags_(compilation_unit->info()->flags()), graph_(graph), bytecode_analysis_(bytecode().object(), zone(), compilation_unit->osr_offset(), true), iterator_(bytecode().object()), source_position_iterator_(bytecode().SourcePositionTable(broker())), allow_loop_peeling_(v8_flags.maglev_loop_peeling), loop_effects_stack_(zone()), decremented_predecessor_offsets_(zone()), loop_headers_to_peel_(bytecode().length(), zone()), // Add an extra jump_target slot for the inline exit if needed. jump_targets_(zone()->AllocateArray<BasicBlockRef>( bytecode().length() + (is_inline() ? 1 : 0))), // Overallocate merge_states_ by one to allow always looking up the // next offset. This overallocated slot can also be used for the inline // exit when needed. merge_states_(zone()->AllocateArray<MergePointInterpreterFrameState*>( bytecode().length() + 1)), current_interpreter_frame_( *compilation_unit_, is_inline() ? caller_details->known_node_aspects : compilation_unit_->zone()->New<KnownNodeAspects>( compilation_unit_->zone())), is_turbolev_(compilation_unit->info()->is_turbolev()), entrypoint_(compilation_unit->is_osr() ? bytecode_analysis_.osr_entry_point() : 0), catch_block_stack_(zone()), unobserved_context_slot_stores_(zone()) { if (V8_UNLIKELY(v8_flags.print_turbolev_inline_functions)) { current_inlining_tree_debug_info_ = zone()->New<InliningTreeDebugInfo>( zone(), compilation_unit->shared_function_info(), caller_details); if (is_inline()) { DCHECK_NOT_NULL(caller_details->parent_inlining_tree_debug_info); current_inlining_tree_debug_info_->budget = caller_details->is_eager_inline ? graph_->total_inlined_bytecode_size_small() : graph_->total_inlined_bytecode_size(); current_inlining_tree_debug_info_->order = static_cast<int>(graph_->inlined_functions().size()) + 1; caller_details->parent_inlining_tree_debug_info->children.push_back( current_inlining_tree_debug_info_); } else { graph_->set_inlining_tree_debug_info(current_inlining_tree_debug_info_); } } memset(merge_states_, 0, (bytecode().length() + 1) * sizeof(InterpreterFrameState*)); // Default construct basic block refs. // TODO(leszeks): This could be a memset of nullptr to ..._jump_targets_. for (int i = 0; i < bytecode().length(); ++i) { new (&jump_targets_[i]) BasicBlockRef(); } if (is_inline()) { DCHECK_NOT_NULL(caller_details); DCHECK_GT(compilation_unit->inlining_depth(), 0); // The allocation/initialisation logic here relies on inline_exit_offset // being the offset one past the end of the bytecode. DCHECK_EQ(inline_exit_offset(), bytecode().length()); merge_states_[inline_exit_offset()] = nullptr; new (&jump_targets_[inline_exit_offset()]) BasicBlockRef(); if (caller_details->loop_effects) { loop_effects_ = caller_details->loop_effects; loop_effects_stack_.push_back(loop_effects_); } unobserved_context_slot_stores_ = caller_details->unobserved_context_slot_stores; } CHECK_IMPLIES(compilation_unit_->is_osr(), graph_->is_osr()); CHECK_EQ(compilation_unit_->info()->toplevel_osr_offset() != BytecodeOffset::None(), graph_->is_osr()); if (compilation_unit_->is_osr()) { CHECK(!is_inline()); // Make sure that we're at a valid OSR entrypoint. // // This is also a defense-in-depth check to make sure that we're not // compiling invalid bytecode if the OSR offset is wrong (e.g. because it // belongs to different bytecode). // // OSR'ing into the middle of a loop is currently not supported. There // should not be any issue with OSR'ing outside of loops, just we currently // dont do it... interpreter::BytecodeArrayIterator it(bytecode().object()); it.AdvanceTo(compilation_unit_->osr_offset().ToInt()); CHECK(it.CurrentBytecodeIsValidOSREntry()); CHECK_EQ(entrypoint_, it.GetJumpTargetOffset()); iterator_.AdvanceTo(entrypoint_); TRACE("- Non-standard entrypoint @" << entrypoint_ << " by OSR from @" << compilation_unit_->osr_offset().ToInt()); } CHECK_IMPLIES(!compilation_unit_->is_osr(), entrypoint_ == 0); CalculatePredecessorCounts(); } void MaglevGraphBuilder::StartPrologue() { set_current_block(zone()->New<BasicBlock>(nullptr, zone())); } BasicBlock* MaglevGraphBuilder::EndPrologue() { BasicBlock* first_block; if (!is_inline() && (v8_flags.maglev_hoist_osr_value_phi_untagging && graph_->is_osr())) { first_block = FinishBlockNoAbort<CheckpointedJump>({}, &jump_targets_[entrypoint_]); } else { first_block = FinishBlockNoAbort<Jump>({}, &jump_targets_[entrypoint_]); } MergeIntoFrameState(first_block, entrypoint_); return first_block; } void MaglevGraphBuilder::SetArgument(int i, ValueNode* value) { interpreter::Register reg = interpreter::Register::FromParameterIndex(i); current_interpreter_frame_.set(reg, value); } ValueNode* MaglevGraphBuilder::GetArgument(int i) { DCHECK_LT(i, parameter_count()); interpreter::Register reg = interpreter::Register::FromParameterIndex(i); return current_interpreter_frame_.get(reg); } ValueNode* MaglevGraphBuilder::GetInlinedArgument(int i) { DCHECK(is_inline()); DCHECK_LT(i, argument_count()); return caller_details_->arguments[i]; } void MaglevGraphBuilder::InitializeRegister(interpreter::Register reg, ValueNode* value) { current_interpreter_frame_.set( reg, value ? value : AddNewNodeNoInputConversion<InitialValue>({}, reg)); } void MaglevGraphBuilder::BuildRegisterFrameInitialization( ValueNode* context, ValueNode* closure, ValueNode* new_target) { if (closure == nullptr && compilation_unit_->info()->specialize_to_function_context()) { compiler::JSFunctionRef function = compiler::MakeRefAssumeMemoryFence( broker(), broker()->CanonicalPersistentHandle( compilation_unit_->info()->toplevel_function())); closure = GetConstant(function); context = GetConstant(function.context(broker())); } InitializeRegister(interpreter::Register::current_context(), context); InitializeRegister(interpreter::Register::function_closure(), closure); EnsureType(GetContext(), NodeType::kContext); EnsureType(GetClosure(), NodeType::kJSFunction); interpreter::Register new_target_or_generator_register = bytecode().incoming_new_target_or_generator_register(); int register_index = 0; if (compilation_unit_->is_osr()) { for (; register_index < register_count(); register_index++) { auto val = AddNewNodeNoInputConversion<InitialValue>( {}, interpreter::Register(register_index)); InitializeRegister(interpreter::Register(register_index), val); graph_->osr_values().push_back(val); } return; } // TODO(leszeks): Don't emit if not needed. ValueNode* undefined_value = GetRootConstant(RootIndex::kUndefinedValue); if (new_target_or_generator_register.is_valid()) { int new_target_index = new_target_or_generator_register.index(); for (; register_index < new_target_index; register_index++) { current_interpreter_frame_.set(interpreter::Register(register_index), undefined_value); } current_interpreter_frame_.set( new_target_or_generator_register, new_target ? new_target : GetRegisterInput(kJavaScriptCallNewTargetRegister)); register_index++; } for (; register_index < register_count(); register_index++) { InitializeRegister(interpreter::Register(register_index), undefined_value); } } void MaglevGraphBuilder::BuildMergeStates() { auto offset_and_info = bytecode_analysis().GetLoopInfos().begin(); auto end = bytecode_analysis().GetLoopInfos().end(); while (offset_and_info != end && offset_and_info->loop_start() < entrypoint_) { ++offset_and_info; } for (; offset_and_info != end; ++offset_and_info) { const compiler::LoopInfo& loop_info = *offset_and_info; int offset = loop_info.loop_start(); if (loop_headers_to_peel_.Contains(offset)) { // Peeled loops are treated like normal merges at first. We will construct // the proper loop header merge state when reaching the `JumpLoop` of the // peeled iteration. continue; } const compiler::BytecodeLivenessState* liveness = GetInLivenessFor(offset); DCHECK_NULL(merge_states_[offset]); TRACE("- Creating loop merge state at @" << offset); merge_states_[offset] = MergePointInterpreterFrameState::NewForLoop( current_interpreter_frame_, this, *compilation_unit_, offset, predecessor_count(offset), liveness, &loop_info); } if (bytecode().handler_table_size() > 0) { HandlerTable table(*bytecode().object()); for (int i = 0; i < table.NumberOfRangeEntries(); i++) { const int offset = table.GetRangeHandler(i); const bool was_used = table.HandlerWasUsed(i); const interpreter::Register context_reg(table.GetRangeData(i)); const compiler::BytecodeLivenessState* liveness = GetInLivenessFor(offset); DCHECK_EQ(predecessor_count(offset), 0); DCHECK_NULL(merge_states_[offset]); TRACE("- Creating exception merge state at @" << offset << (was_used ? "" : " (never used)") << ", context register r" << context_reg.index()); merge_states_[offset] = MergePointInterpreterFrameState::NewForCatchBlock( *compilation_unit_, liveness, offset, was_used, context_reg, graph_); } } } namespace { template <int index, interpreter::OperandType... operands> struct GetResultLocationAndSizeHelper; // Terminal cases template <int index> struct GetResultLocationAndSizeHelper<index> { static std::pair<interpreter::Register, int> GetResultLocationAndSize( const interpreter::BytecodeArrayIterator& iterator) { // TODO(leszeks): This should probably actually be "UNREACHABLE" but we have // lazy deopt info for interrupt budget updates at returns, not for actual // lazy deopts, but just for stack iteration purposes. return {interpreter::Register::invalid_value(), 0}; } static bool HasOutputRegisterOperand() { return false; } }; template <int index, interpreter::OperandType... operands> struct GetResultLocationAndSizeHelper<index, interpreter::OperandType::kRegOut, operands...> { static std::pair<interpreter::Register, int> GetResultLocationAndSize( const interpreter::BytecodeArrayIterator& iterator) { // We shouldn't have any other output operands than this one. return {iterator.GetRegisterOperand(index), 1}; } static bool HasOutputRegisterOperand() { return true; } }; template <int index, interpreter::OperandType... operands> struct GetResultLocationAndSizeHelper< index, interpreter::OperandType::kRegOutPair, operands...> { static std::pair<interpreter::Register, int> GetResultLocationAndSize( const interpreter::BytecodeArrayIterator& iterator) { // We shouldn't have any other output operands than this one. return {iterator.GetRegisterOperand(index), 2}; } static bool HasOutputRegisterOperand() { return true; } }; template <int index, interpreter::OperandType... operands> struct GetResultLocationAndSizeHelper< index, interpreter::OperandType::kRegOutTriple, operands...> { static std::pair<interpreter::Register, int> GetResultLocationAndSize( const interpreter::BytecodeArrayIterator& iterator) { // We shouldn't have any other output operands than this one. DCHECK(!(GetResultLocationAndSizeHelper< index + 1, operands...>::HasOutputRegisterOperand())); return {iterator.GetRegisterOperand(index), 3}; } static bool HasOutputRegisterOperand() { return true; } }; // We don't support RegOutList for lazy deopts. template <int index, interpreter::OperandType... operands> struct GetResultLocationAndSizeHelper< index, interpreter::OperandType::kRegOutList, operands...> { static std::pair<interpreter::Register, int> GetResultLocationAndSize( const interpreter::BytecodeArrayIterator& iterator) { interpreter::RegisterList list = iterator.GetRegisterListOperand(index); return {list.first_register(), list.register_count()}; } static bool HasOutputRegisterOperand() { return true; } }; // Induction case. template <int index, interpreter::OperandType operand, interpreter::OperandType... operands> struct GetResultLocationAndSizeHelper<index, operand, operands...> { static std::pair<interpreter::Register, int> GetResultLocationAndSize( const interpreter::BytecodeArrayIterator& iterator) { return GetResultLocationAndSizeHelper< index + 1, operands...>::GetResultLocationAndSize(iterator); } static bool HasOutputRegisterOperand() { return GetResultLocationAndSizeHelper< index + 1, operands...>::HasOutputRegisterOperand(); } }; template <interpreter::Bytecode bytecode, interpreter::ImplicitRegisterUse implicit_use, interpreter::OperandType... operands> std::pair<interpreter::Register, int> GetResultLocationAndSizeForBytecode( const interpreter::BytecodeArrayIterator& iterator) { // We don't support output registers for implicit registers. DCHECK(!interpreter::BytecodeOperands::WritesImplicitRegister(implicit_use)); if (interpreter::BytecodeOperands::WritesAccumulator(implicit_use)) { // If we write the accumulator, we shouldn't also write an output register. DCHECK(!(GetResultLocationAndSizeHelper< 0, operands...>::HasOutputRegisterOperand())); return {interpreter::Register::virtual_accumulator(), 1}; } // Use template magic to output a the appropriate GetRegisterOperand call and // size for this bytecode. return GetResultLocationAndSizeHelper< 0, operands...>::GetResultLocationAndSize(iterator); } } // namespace std::pair<interpreter::Register, int> MaglevGraphBuilder::GetResultLocationAndSize() const { using Bytecode = interpreter::Bytecode; using OperandType = interpreter::OperandType; using ImplicitRegisterUse = interpreter::ImplicitRegisterUse; Bytecode bytecode = iterator_.current_bytecode(); // TODO(leszeks): Only emit these cases for bytecodes we know can lazy deopt. switch (bytecode) { #define CASE(Name, ...) \ case Bytecode::k##Name: \ return GetResultLocationAndSizeForBytecode<Bytecode::k##Name, \ __VA_ARGS__>(iterator_); BYTECODE_LIST(CASE, CASE) #undef CASE } UNREACHABLE(); } #ifdef DEBUG bool MaglevGraphBuilder::HasOutputRegister(interpreter::Register reg) const { interpreter::Bytecode bytecode = iterator_.current_bytecode(); if (reg == interpreter::Register::virtual_accumulator()) { return interpreter::Bytecodes::WritesAccumulator(bytecode); } for (int i = 0; i < interpreter::Bytecodes::NumberOfOperands(bytecode); ++i) { if (interpreter::Bytecodes::IsRegisterOutputOperandType( interpreter::Bytecodes::GetOperandType(bytecode, i))) { interpreter::Register operand_reg = iterator_.GetRegisterOperand(i); int operand_range = iterator_.GetRegisterOperandRange(i); if (base::IsInRange(reg.index(), operand_reg.index(), operand_reg.index() + operand_range)) { return true; } } } return false; } #endif DeoptFrame* MaglevGraphBuilder::AddInlinedArgumentsToDeoptFrame( DeoptFrame* deopt_frame, const MaglevCompilationUnit* unit, ValueNode* closure, base::Vector<ValueNode*> args) { // Only create InlinedArgumentsDeoptFrame if we have a mismatch between // formal parameter and arguments count. if (static_cast<int>(args.size()) != unit->parameter_count()) { deopt_frame = zone()->New<InlinedArgumentsDeoptFrame>( *unit, BytecodeOffset(iterator_.current_offset()), closure, args, deopt_frame); AddDeoptUse(closure); for (ValueNode* arg : deopt_frame->as_inlined_arguments().arguments()) { AddDeoptUse(arg); } } return deopt_frame; } DeoptFrame* MaglevGraphBuilder::GetDeoptFrameForEagerCall( const MaglevCompilationUnit* unit, ValueNode* closure, base::Vector<ValueNode*> args) { // The parent resumes after the call, which is roughly equivalent to a lazy // deopt. Use the helper function directly so that we can mark the // accumulator as dead (since it'll be overwritten by this function's // return value anyway). // TODO(leszeks): This is true for our current set of // inlinings/continuations, but there might be cases in the future where it // isn't. We may need to store the relevant overwritten register in // LazyDeoptFrameScope. DCHECK( interpreter::Bytecodes::WritesAccumulator(iterator_.current_bytecode()) || interpreter::Bytecodes::ClobbersAccumulator( iterator_.current_bytecode())); DeoptFrame* deopt_frame = GetDeoptFrameForLazyDeoptHelper(interpreter::Register::invalid_value(), 0, current_lazy_deopt_scope_, true, false); return AddInlinedArgumentsToDeoptFrame(deopt_frame, unit, closure, args); } DeoptFrame* MaglevGraphBuilder::GetCallerDeoptFrame() { if (!is_inline()) return nullptr; return caller_details_->deopt_frame; } namespace { DeoptFrame* RecursivelyWrapDeoptFrameWithContinuations( Zone* zone, const DeoptFrame& frame, const MaglevGraphBuilder::LazyDeoptFrameScope* parent_scope) { if (!parent_scope) { return zone->New<DeoptFrame>(frame); } return zone->New<DeoptFrame>(parent_scope->data(), RecursivelyWrapDeoptFrameWithContinuations( zone, frame, parent_scope->parent())); } } // namespace DeoptFrame* MaglevGraphBuilder::GetLatestCheckpointedFrame() { if (in_prologue_) { return GetDeoptFrameForEntryStackCheck(); } if (!latest_checkpointed_frame_) { current_interpreter_frame_.virtual_objects().Snapshot(); latest_checkpointed_frame_ = zone()->New<InterpretedDeoptFrame>( *compilation_unit_, zone()->New<CompactInterpreterFrameState>( *compilation_unit_, GetInLiveness(), current_interpreter_frame_), GetClosure(), current_interpreter_frame_.virtual_objects().head(), BytecodeOffset(iterator_.current_offset()), GetCurrentSourcePosition(), GetCallerDeoptFrame()); latest_checkpointed_frame_->as_interpreted().frame_state()->ForEachValue( *compilation_unit_, [&](ValueNode* node, interpreter::Register) { AddDeoptUse(node); }); AddDeoptUse(latest_checkpointed_frame_->as_interpreted().closure()); EagerDeoptFrameScope* deopt_scope = current_eager_deopt_scope_; if (deopt_scope != nullptr) { AddDeoptUseToScopeData(deopt_scope->data()); latest_checkpointed_frame_ = zone()->New<DeoptFrame>( deopt_scope->data(), RecursivelyWrapDeoptFrameWithContinuations( zone(), *latest_checkpointed_frame_, deopt_scope->parent())); } } return latest_checkpointed_frame_; } std::tuple<DeoptFrame*, interpreter::Register, int> MaglevGraphBuilder::GetDeoptFrameForLazyDeopt(bool can_throw) { interpreter::Register result_location; int result_size; if (lazy_deopt_result_location_scope_) { result_location = lazy_deopt_result_location_scope_->result_location(); result_size = lazy_deopt_result_location_scope_->result_size(); } else { std::tie(result_location, result_size) = GetResultLocationAndSize(); } return std::make_tuple(GetDeoptFrameForLazyDeoptHelper( result_location, result_size, current_lazy_deopt_scope_, false, can_throw), result_location, result_size); } void MaglevGraphBuilder::AddDeoptUseToScopeData(DeoptFrame::FrameData& data) { switch (data.tag()) { case DeoptFrame::FrameType::kInterpretedFrame: case DeoptFrame::FrameType::kInlinedArgumentsFrame: // These frames are never created as deopt scope. UNREACHABLE(); case DeoptFrame::FrameType::kConstructInvokeStubFrame: AddDeoptUse( data.get<DeoptFrame::ConstructInvokeStubFrameData>().receiver); AddDeoptUse(data.get<DeoptFrame::ConstructInvokeStubFrameData>().context); break; case DeoptFrame::FrameType::kBuiltinContinuationFrame: AddDeoptUse(data.get<DeoptFrame::BuiltinContinuationFrameData>().context); for (ValueNode* node : data.get<DeoptFrame::BuiltinContinuationFrameData>().parameters) { AddDeoptUse(node); } break; } } DeoptFrame* MaglevGraphBuilder::GetDeoptFrameForLazyDeoptHelper( interpreter::Register result_location, int result_size, LazyDeoptFrameScope* scope, bool mark_accumulator_dead, bool can_throw) { if (scope == nullptr) { compiler::BytecodeLivenessState* liveness = zone()->New<compiler::BytecodeLivenessState>(*GetOutLiveness(), zone()); // Remove result locations from liveness. if (result_location == interpreter::Register::virtual_accumulator()) { DCHECK_EQ(result_size, 1); liveness->MarkAccumulatorDead(); mark_accumulator_dead = false; } else { DCHECK(!result_location.is_parameter()); if (can_throw && IsInsideTryBlock()) { // Exception handlers reuse the frame-state of the lazy deopt, so if a // result register is live in the exception handler, keep it alive here. const compiler::BytecodeLivenessState* exception_handler_liveness = GetCatchBlockFrameState()->frame_state().liveness(); for (int i = 0; i < result_size; i++) { if (!exception_handler_liveness->RegisterIsLive( result_location.index() + i)) { liveness->MarkRegisterDead(result_location.index() + i); } } } else { for (int i = 0; i < result_size; i++) { liveness->MarkRegisterDead(result_location.index() + i); } } } // Explicitly drop the accumulator if needed. if (mark_accumulator_dead && liveness->AccumulatorIsLive()) { liveness->MarkAccumulatorDead(); } // Exception handlers reuse the frame-state of the lazy deopt. Make sure // all exception handler registers are live in this liveness. #ifdef DEBUG if (can_throw && IsInsideTryBlock()) { const compiler::BytecodeLivenessState* exception_handler_liveness = GetCatchBlockFrameState()->frame_state().liveness(); for (int i = 0; i < register_count(); i++) { DCHECK_IMPLIES(exception_handler_liveness->RegisterIsLive(i), liveness->RegisterIsLive(i)); } } #endif current_interpreter_frame_.virtual_objects().Snapshot(); InterpretedDeoptFrame* ret = zone()->New<InterpretedDeoptFrame>( *compilation_unit_, zone()->New<CompactInterpreterFrameState>(*compilation_unit_, liveness, current_interpreter_frame_), GetClosure(), current_interpreter_frame_.virtual_objects().head(), BytecodeOffset(iterator_.current_offset()), GetCurrentSourcePosition(), GetCallerDeoptFrame()); ret->frame_state()->ForEachValue( *compilation_unit_, [this](ValueNode* node, interpreter::Register reg) { // Receiver and closure values have to be materialized, even if // they don't otherwise escape. if (reg == interpreter::Register::receiver() || reg == interpreter::Register::function_closure()) { node->add_use(); } else { AddDeoptUse(node); } }); AddDeoptUse(ret->closure()); return ret; } // Currently only support builtin continuations for bytecodes that write to // the accumulator DCHECK(interpreter::Bytecodes::WritesOrClobbersAccumulator( iterator_.current_bytecode())); AddDeoptUseToScopeData(scope->data()); // Mark the accumulator dead in parent frames since we know that the // continuation will write it. return zone()->New<DeoptFrame>( scope->data(), GetDeoptFrameForLazyDeoptHelper( result_location, result_size, scope->parent(), scope->data().tag() == DeoptFrame::FrameType::kBuiltinContinuationFrame, false)); } InterpretedDeoptFrame* MaglevGraphBuilder::GetDeoptFrameForEntryStackCheck() { if (entry_stack_check_frame_) return entry_stack_check_frame_; DCHECK_EQ(iterator_.current_offset(), entrypoint_); DCHECK(!is_inline()); entry_stack_check_frame_ = zone()->New<InterpretedDeoptFrame>( *compilation_unit_, zone()->New<CompactInterpreterFrameState>( *compilation_unit_, GetInLivenessFor(graph_->is_osr() ? bailout_for_entrypoint() : 0), current_interpreter_frame_), GetClosure(), current_interpreter_frame_.virtual_objects().head(), BytecodeOffset(bailout_for_entrypoint()), GetCurrentSourcePosition(), nullptr); entry_stack_check_frame_->frame_state()->ForEachValue( *compilation_unit_, [&](ValueNode* node, interpreter::Register) { AddDeoptUse(node); }); AddDeoptUse(entry_stack_check_frame_->closure()); return entry_stack_check_frame_; } ReduceResult MaglevGraphBuilder::GetSmiValue( ValueNode* value, UseReprHintRecording record_use_repr_hint) { if (V8_LIKELY(record_use_repr_hint == UseReprHintRecording::kRecord)) { value->MaybeRecordUseReprHint(UseRepresentation::kTagged); } NodeInfo* node_info = GetOrCreateInfoFor(value); ValueRepresentation representation = value->properties().value_representation(); if (representation == ValueRepresentation::kTagged) { return BuildCheckSmi(value, !value->Is<Phi>()); } auto& alternative = node_info->alternative(); if (ValueNode* alt = alternative.tagged()) { #ifdef DEBUG if (HoleyFloat64ToTagged* conversion_node = alt->TryCast<HoleyFloat64ToTagged>()) { DCHECK_EQ(conversion_node->conversion_mode(), NumberConversionMode::kCanonicalizeSmi); } #endif // DEBUG return BuildCheckSmi(alt, !value->Is<Phi>()); } switch (representation) { case ValueRepresentation::kInt32: { if (NodeTypeIsSmi(node_info->type())) { return alternative.set_tagged( AddNewNodeNoInputConversion<UnsafeSmiTagInt32>({value})); } return alternative.set_tagged( AddNewNodeNoInputConversion<CheckedSmiTagInt32>({value})); } case ValueRepresentation::kUint32: { if (NodeTypeIsSmi(node_info->type())) { return alternative.set_tagged( AddNewNodeNoInputConversion<UnsafeSmiTagUint32>({value})); } return alternative.set_tagged( AddNewNodeNoInputConversion<CheckedSmiTagUint32>({value})); } case ValueRepresentation::kFloat64: { return alternative.set_tagged( AddNewNodeNoInputConversion<CheckedSmiTagFloat64>({value})); } case ValueRepresentation::kHoleyFloat64: { return alternative.set_tagged( AddNewNodeNoInputConversion<CheckedSmiTagHoleyFloat64>({value})); } case ValueRepresentation::kIntPtr: return alternative.set_tagged( AddNewNodeNoInputConversion<CheckedSmiTagIntPtr>({value})); case ValueRepresentation::kTagged: case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } UNREACHABLE(); } ReduceResult MaglevGraphBuilder::GetSmiValue( ReduceResult value_result, UseReprHintRecording record_use_repr_hint) { ValueNode* value; GET_VALUE_OR_ABORT(value, value_result); return GetSmiValue(value, record_use_repr_hint); } ReduceResult MaglevGraphBuilder::GetInternalizedString( interpreter::Register reg) { ValueNode* node = current_interpreter_frame_.get(reg); NodeType old_type; if (CheckType(node, NodeType::kInternalizedString, &old_type)) return node; NodeInfo* known_info = GetOrCreateInfoFor(node); if (known_info->alternative().checked_value()) { node = known_info->alternative().checked_value(); if (CheckType(node, NodeType::kInternalizedString, &old_type)) return node; } if (!NodeTypeIs(old_type, NodeType::kString)) { if (IsEmptyNodeType(IntersectType(old_type, NodeType::kString))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotAString); } known_info->IntersectType(NodeType::kString); } // This node may unwrap ThinStrings. ValueNode* maybe_unwrapping_node; GET_VALUE_OR_ABORT(maybe_unwrapping_node, AddNewNode<CheckedInternalizedString>( {node}, GetCheckType(old_type, node))); known_info->alternative().set_checked_value(maybe_unwrapping_node); current_interpreter_frame_.set(reg, maybe_unwrapping_node); return maybe_unwrapping_node; } ReduceResult MaglevGraphBuilder::GetTruncatedInt32ForToNumber( ValueNode* value, NodeType allowed_input_type) { return reducer_.GetTruncatedInt32ForToNumber(value, allowed_input_type); } namespace { int32_t ClampToUint8(int32_t value) { if (value < 0) return 0; if (value > 255) return 255; return value; } } // namespace ValueNode* MaglevGraphBuilder::GetUint8ClampedForToNumber(ValueNode* value) { switch (value->value_representation()) { case ValueRepresentation::kIntPtr: // This is not an efficient implementation, but this only happens in // corner cases. return AddNewNodeNoInputConversion<CheckedNumberToUint8Clamped>( {AddNewNodeNoInputConversion<IntPtrToNumber>({value})}); case ValueRepresentation::kTagged: { if (SmiConstant* constant = value->TryCast<SmiConstant>()) { return GetInt32Constant(ClampToUint8(constant->value().value())); } NodeInfo* info = known_node_aspects().TryGetInfoFor(value); if (info && info->alternative().int32()) { return AddNewNodeNoInputConversion<Int32ToUint8Clamped>( {info->alternative().int32()}); } return AddNewNodeNoInputConversion<CheckedNumberToUint8Clamped>({value}); } // HoleyFloat64 is treated like Float64. ToNumber of undefined is anyway a // NaN, so we'll simply truncate away the NaN-ness of the hole, and don't // need to do extra oddball checks (though we'll miss updating the // feedback). case ValueRepresentation::kHoleyFloat64: value = AddNewNodeNoInputConversion<UnsafeHoleyFloat64ToFloat64>({value}); [[fallthrough]]; case ValueRepresentation::kFloat64: // TODO(leszeks): Handle Float64Constant, which requires the correct // rounding for clamping. return AddNewNodeNoInputConversion<Float64ToUint8Clamped>({value}); case ValueRepresentation::kInt32: if (auto constant = TryGetInt32Constant(value)) { return GetInt32Constant(ClampToUint8(*constant)); } return AddNewNodeNoInputConversion<Int32ToUint8Clamped>({value}); case ValueRepresentation::kUint32: return AddNewNodeNoInputConversion<Uint32ToUint8Clamped>({value}); case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } UNREACHABLE(); } namespace { template <Operation kOperation> struct NodeForOperationHelper; #define NODE_FOR_OPERATION_HELPER(Name) \ template <> \ struct NodeForOperationHelper<Operation::k##Name> { \ using generic_type = Generic##Name; \ }; OPERATION_LIST(NODE_FOR_OPERATION_HELPER) #undef NODE_FOR_OPERATION_HELPER template <Operation kOperation> using GenericNodeForOperation = typename NodeForOperationHelper<kOperation>::generic_type; // Bitwise operations reinterprets the numeric input as Int32 bits for a // bitwise operation, which means we want to do slightly different conversions. template <Operation kOperation> consteval bool BinaryOperationIsBitwiseInt32() { switch (kOperation) { case Operation::kBitwiseNot: case Operation::kBitwiseAnd: case Operation::kBitwiseOr: case Operation::kBitwiseXor: case Operation::kShiftLeft: case Operation::kShiftRight: case Operation::kShiftRightLogical: return true; default: return false; } } static constexpr bool IsOperationWithEmbeddedFeedback(Operation op) { #define OP_WITH_EMBEDDED_FEEDBACK_CASE(op) case Operation::k##op: switch (op) { COMPARISON_OPERATION_LIST(OP_WITH_EMBEDDED_FEEDBACK_CASE) return true; default: return false; } } } // namespace namespace { template <Operation kOperation> struct Int32NodeForHelper; #define SPECIALIZATION(op, OpNode, ...) \ template <> \ struct Int32NodeForHelper<Operation::k##op> { \ using type = OpNode; \ }; MAP_UNARY_OPERATION_TO_INT32_NODE(SPECIALIZATION) MAP_BINARY_OPERATION_TO_INT32_NODE(SPECIALIZATION) #undef SPECIALIZATION template <Operation kOperation> using Int32NodeFor = typename Int32NodeForHelper<kOperation>::type; template <Operation kOperation> struct Float64NodeForHelper; #define SPECIALIZATION(op, OpNode, ...) \ template <> \ struct Float64NodeForHelper<Operation::k##op> { \ using type = OpNode; \ }; MAP_OPERATION_TO_FLOAT64_NODE(SPECIALIZATION) #undef SPECIALIZATION template <Operation kOperation> using Float64NodeFor = typename Float64NodeForHelper<kOperation>::type; } // namespace template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildGenericUnaryOperationNode() { FeedbackSlot slot_index = GetSlotOperand(0); ValueNode* value = GetAccumulator(); return SetAccumulator(AddNewNode<GenericNodeForOperation<kOperation>>( {value}, compiler::FeedbackSource{feedback(), slot_index})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildGenericBinaryOperationNode() { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); if constexpr (IsOperationWithEmbeddedFeedback(kOperation)) { return SetAccumulator(AddNewNode<GenericNodeForOperation<kOperation>>( {left, right}, compiler::EmbeddedFeedbackSource{ indirect_handle(iterator_.bytecode_array(), local_isolate()), iterator_.GetEmbeddedFeedbackOffset(1)})); } else { FeedbackSlot slot_index = GetSlotOperand(1); return SetAccumulator(AddNewNode<GenericNodeForOperation<kOperation>>( {left, right}, compiler::FeedbackSource{feedback(), slot_index})); } } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildGenericBinarySmiOperationNode() { ValueNode* left = GetAccumulator(); int constant = iterator_.GetImmediateOperand(0); ValueNode* right = GetSmiConstant(constant); FeedbackSlot slot_index = GetSlotOperand(1); return SetAccumulator(AddNewNode<GenericNodeForOperation<kOperation>>( {left, right}, compiler::FeedbackSource{feedback(), slot_index})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildInt32UnaryOperationNode() { // Use BuildTruncatingInt32BitwiseNotForToNumber with Smi input hint // for truncating operations. static_assert(!BinaryOperationIsBitwiseInt32<kOperation>()); ValueNode* value = GetAccumulator(); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldInt32UnaryOperation<kOperation>(value), SetAccumulator); using OpNodeT = Int32NodeFor<kOperation>; return SetAccumulator(AddNewNode<OpNodeT>({value})); } ReduceResult MaglevGraphBuilder::BuildTruncatingInt32BitwiseNotForToNumber( NodeType allowed_input_type) { ValueNode* value; GET_VALUE_OR_ABORT( value, GetTruncatedInt32ForToNumber( current_interpreter_frame_.accumulator(), allowed_input_type)); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldInt32UnaryOperation<Operation::kBitwiseNot>(value), SetAccumulator); return SetAccumulator(AddNewNode<Int32BitwiseNot>({value})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildInt32BinaryOperationNode() { // Use BuildTruncatingInt32BinaryOperationNodeForToNumber with Smi input hint // for truncating operations. static_assert(!BinaryOperationIsBitwiseInt32<kOperation>()); ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldInt32BinaryOperation<kOperation>(left, right), SetAccumulator); using OpNodeT = Int32NodeFor<kOperation>; return SetAccumulator(AddNewNode<OpNodeT>({left, right})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildTruncatingInt32BinaryOperationNodeForToNumber( NodeType allowed_input_type) { static_assert(BinaryOperationIsBitwiseInt32<kOperation>()); ValueNode* left; ValueNode* right; if (IsRegisterEqualToAccumulator(0)) { GET_VALUE_OR_ABORT( right, GetTruncatedInt32ForToNumber( current_interpreter_frame_.get(iterator_.GetRegisterOperand(0)), allowed_input_type)); left = right; } else { GET_VALUE_OR_ABORT( left, GetTruncatedInt32ForToNumber(current_interpreter_frame_.get( iterator_.GetRegisterOperand(0)), allowed_input_type)); GET_VALUE_OR_ABORT(right, GetTruncatedInt32ForToNumber( current_interpreter_frame_.accumulator(), allowed_input_type)); } PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldInt32BinaryOperation<kOperation>(left, right), SetAccumulator); return SetAccumulator(AddNewNode<Int32NodeFor<kOperation>>({left, right})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildInt32BinarySmiOperationNode() { // Truncating Int32 nodes treat their input as a signed int32 regardless // of whether it's really signed or not, so we allow Uint32 by loading a // TruncatedInt32 value. static_assert(!BinaryOperationIsBitwiseInt32<kOperation>()); ValueNode* left = GetAccumulator(); int32_t constant = iterator_.GetImmediateOperand(0); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldInt32BinaryOperation<kOperation>(left, constant), SetAccumulator); ValueNode* right = GetInt32Constant(constant); using OpNodeT = Int32NodeFor<kOperation>; return SetAccumulator(AddNewNode<OpNodeT>({left, right})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildTruncatingInt32BinarySmiOperationNodeForToNumber( NodeType allowed_input_type) { static_assert(BinaryOperationIsBitwiseInt32<kOperation>()); ValueNode* left; GET_VALUE_OR_ABORT( left, GetTruncatedInt32ForToNumber( current_interpreter_frame_.accumulator(), allowed_input_type)); int32_t constant = iterator_.GetImmediateOperand(0); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldInt32BinaryOperation<kOperation>(left, constant), SetAccumulator); ValueNode* right = GetInt32Constant(constant); return SetAccumulator(AddNewNode<Int32NodeFor<kOperation>>({left, right})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildFloat64BinarySmiOperationNodeForToNumber( NodeType allowed_input_type) { // TODO(v8:7700): Do constant identity folding. Make sure to normalize // HoleyFloat64 nodes if folded. ValueNode* left; GET_VALUE_OR_ABORT(left, GetAccumulatorFloat64ForToNumber(allowed_input_type)); double constant = static_cast<double>(iterator_.GetImmediateOperand(0)); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldFloat64BinaryOperationForToNumber<kOperation>( GetTaggedToFloat64ConversionType(allowed_input_type), left, constant), SetAccumulator); ValueNode* right = GetFloat64Constant(constant); return SetAccumulator(AddNewNode<Float64NodeFor<kOperation>>({left, right})); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildFloat64UnaryOperationNodeForToNumber( NodeType allowed_input_type) { // TODO(v8:7700): Do constant identity folding. Make sure to normalize // HoleyFloat64 nodes if folded. ValueNode* value; GET_VALUE_OR_ABORT(value, GetAccumulatorFloat64ForToNumber(allowed_input_type)); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldFloat64UnaryOperationForToNumber<kOperation>( GetTaggedToFloat64ConversionType(allowed_input_type), value), SetAccumulator); switch (kOperation) { case Operation::kNegate: return SetAccumulator(AddNewNode<Float64Negate>({value})); case Operation::kIncrement: return SetAccumulator( AddNewNode<Float64Add>({value, GetFloat64Constant(1)})); case Operation::kDecrement: return SetAccumulator( AddNewNode<Float64Subtract>({value, GetFloat64Constant(1)})); default: UNREACHABLE(); } } template <Operation kOperation> ReduceResult MaglevGraphBuilder::BuildFloat64BinaryOperationNodeForToNumber( NodeType allowed_input_type) { // TODO(v8:7700): Do constant identity folding. Make sure to normalize // HoleyFloat64 nodes if folded. ValueNode* left; GET_VALUE_OR_ABORT(left, LoadRegisterFloat64ForToNumber(0, allowed_input_type)); ValueNode* right; GET_VALUE_OR_ABORT(right, GetAccumulatorFloat64ForToNumber(allowed_input_type)); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldFloat64BinaryOperationForToNumber<kOperation>( GetTaggedToFloat64ConversionType(allowed_input_type), left, right), SetAccumulator); return SetAccumulator(AddNewNode<Float64NodeFor<kOperation>>({left, right})); } namespace { NodeType BinopHintToNodeTypeAndConversionType(BinaryOperationHint hint) { switch (hint) { case BinaryOperationHint::kSignedSmall: return NodeType::kSmi; case BinaryOperationHint::kSignedSmallInputs: case BinaryOperationHint::kAdditiveSafeInteger: case BinaryOperationHint::kNumber: return NodeType::kNumber; case BinaryOperationHint::kNumberOrOddball: return NodeType::kNumberOrOddball; case BinaryOperationHint::kNone: case BinaryOperationHint::kString: case BinaryOperationHint::kStringOrStringWrapper: case BinaryOperationHint::kBigInt: case BinaryOperationHint::kBigInt64: case BinaryOperationHint::kAny: UNREACHABLE(); } } } // namespace template <Operation kOperation> ReduceResult MaglevGraphBuilder::VisitUnaryOperation() { FeedbackNexus nexus = FeedbackNexusForOperand(0); BinaryOperationHint feedback_hint = nexus.GetBinaryOperationFeedback(); switch (feedback_hint) { case BinaryOperationHint::kNone: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation); case BinaryOperationHint::kSignedSmall: case BinaryOperationHint::kSignedSmallInputs: case BinaryOperationHint::kAdditiveSafeInteger: case BinaryOperationHint::kNumber: case BinaryOperationHint::kNumberOrOddball: { auto allowed_input_type = BinopHintToNodeTypeAndConversionType(feedback_hint); if constexpr (BinaryOperationIsBitwiseInt32<kOperation>()) { static_assert(kOperation == Operation::kBitwiseNot); return BuildTruncatingInt32BitwiseNotForToNumber(allowed_input_type); } else if (feedback_hint == BinaryOperationHint::kSignedSmall) { return BuildInt32UnaryOperationNode<kOperation>(); } return BuildFloat64UnaryOperationNodeForToNumber<kOperation>( allowed_input_type); break; } case BinaryOperationHint::kString: case BinaryOperationHint::kStringOrStringWrapper: case BinaryOperationHint::kBigInt: case BinaryOperationHint::kBigInt64: case BinaryOperationHint::kAny: // Fallback to generic node. break; } return BuildGenericUnaryOperationNode<kOperation>(); } ReduceResult MaglevGraphBuilder::BuildNewConsStringMap(ValueNode* left, ValueNode* right) { struct Result { bool static_map; bool is_two_byte; // The result map if the other map is known to be one byte. ValueNode* result_map; }; // If either is a two byte map, then the result is the kConsTwoByteStringMap. // If both are non-two byte maps, then the result is the // kConsOneByteStringMap. auto GetIsTwoByteAndMap = [&](ValueNode* input) -> Result { if (auto maybe_constant = TryGetConstant<HeapObject>(input)) { bool two_byte = maybe_constant->map(broker()).IsTwoByteStringMap(); return {true, two_byte, GetRootConstant(two_byte ? RootIndex::kConsTwoByteStringMap : RootIndex::kConsOneByteStringMap)}; } switch (input->opcode()) { case Opcode::kNumberToString: return {true, false, GetRootConstant(RootIndex::kConsOneByteStringMap)}; case Opcode::kInlinedAllocation: { VirtualObject* cons = input->Cast<InlinedAllocation>()->object(); if (cons->object_type() != vobj::ObjectType::kConsString) break; ValueNode* map = cons->get(HeapObject::kMapOffset); if (auto cons_map = TryGetConstant<Map>(map)) { return {true, cons_map->IsTwoByteStringMap(), map}; } return {false, false, map}; } default: break; } return {false, false, nullptr}; }; auto left_info = GetIsTwoByteAndMap(left); auto right_info = GetIsTwoByteAndMap(right); if (left_info.static_map) { if (left_info.is_two_byte) { return GetRootConstant(RootIndex::kConsTwoByteStringMap); } // If left is known non-twobyte, then the result only depends on right. if (right_info.static_map) { if (right_info.is_two_byte) { return GetRootConstant(RootIndex::kConsTwoByteStringMap); } else { return GetRootConstant(RootIndex::kConsOneByteStringMap); } } if (right_info.result_map) { return right_info.result_map; } } else if (left_info.result_map) { // Left is not constant, but we have a value for the map. // If right is known non-twobyte, then the result only depends on left. if (right_info.static_map && !right_info.is_two_byte) { return left_info.result_map; } } // Since ConsStringMap only cares about the two-byte-ness of its inputs we // might as well pass the result map instead if we have one. ValueNode* left_map = left_info.result_map; if (!left_map) { GET_VALUE_OR_ABORT(left_map, BuildLoadTaggedField(left, HeapObject::kMapOffset)); } ValueNode* right_map = right_info.result_map; if (!right_map) { GET_VALUE_OR_ABORT(right_map, BuildLoadTaggedField(right, HeapObject::kMapOffset)); } // Sort inputs for CSE. Move constants to the left since the instruction // reuses the lhs input. if (IsConstantNode(right_map->opcode()) || (!IsConstantNode(left_map->opcode()) && left > right)) { std::swap(left, right); } // TODO(olivf): Evaluate if using maglev controlflow to select the map could // be faster here. return AddNewNode<ConsStringMap>({left_map, right_map}); } size_t MaglevGraphBuilder::StringLengthStaticLowerBound(ValueNode* string, int max_depth) { if (auto maybe_constant = TryGetConstant<String>(string)) { if (maybe_constant->IsString()) { return maybe_constant->AsString().length(); } } switch (string->opcode()) { case Opcode::kNumberToString: return 1; case Opcode::kInlinedAllocation: // TODO(olivf): Add a NodeType::kConsString instead of this check. if (string->Cast<InlinedAllocation>()->object()->object_type() == vobj::ObjectType::kConsString) { return ConsString::kMinLength; } break; case Opcode::kNewConsString: return ConsString::kMinLength; case Opcode::kStringConcat: if (max_depth == 0) return 0; return StringLengthStaticLowerBound(string->input(0).node(), max_depth - 1) + StringLengthStaticLowerBound(string->input(1).node(), max_depth - 1); case Opcode::kPhi: { // For the builder pattern where the inputs are cons strings, we will see // a phi from the Select that compares against the empty string. We // can refine the min_length by checking the phi strings. This might // help us elide the Select. if (max_depth == 0) return 0; auto phi = string->Cast<Phi>(); if (phi->input_count() == 0 || (phi->is_loop_phi() && phi->is_unmerged_loop_phi())) { return 0; } size_t overall_min_length = StringLengthStaticLowerBound(phi->input(0).node(), max_depth - 1); for (int i = 1; i < phi->input_count(); ++i) { size_t min = StringLengthStaticLowerBound(phi->input(i).node(), max_depth - 1); if (min < overall_min_length) { overall_min_length = min; } } return overall_min_length; } default: break; } return 0; } MaybeReduceResult MaglevGraphBuilder::TryBuildNewConsString( ValueNode* left, ValueNode* right, AllocationType allocation_type) { if (!v8_flags.maglev_cons_string_elision) { return ReduceResult::Fail(); } DCHECK(NodeTypeIs(GetType(left), NodeType::kString)); DCHECK(NodeTypeIs(GetType(right), NodeType::kString)); size_t left_min_length = StringLengthStaticLowerBound(left); size_t right_min_length = StringLengthStaticLowerBound(right); bool result_is_cons_string = left_min_length + right_min_length >= ConsString::kMinLength; // TODO(olivf): Support the fast case with a non-cons string fallback. if (!result_is_cons_string) { return MaybeReduceResult::Fail(); } ValueNode* left_length; GET_VALUE_OR_ABORT(left_length, BuildLoadStringLength(left)); ValueNode* right_length; GET_VALUE_OR_ABORT(right_length, BuildLoadStringLength(right)); auto BuildConsString = [&]() -> ReduceResult { ValueNode* new_length; MaybeReduceResult folded = reducer_.TryFoldInt32BinaryOperation<Operation::kAdd>(left_length, right_length); if (folded.HasValue()) { new_length = folded.value(); } else { GET_VALUE_OR_ABORT(new_length, AddNewNode<Int32AddWithOverflow>( {left_length, right_length})); } RETURN_IF_ABORT(TryBuildCheckInt32Condition( new_length, GetInt32Constant(String::kMaxLength), AssertCondition::kUnsignedLessThanEqual, DeoptimizeReason::kStringTooLarge)); if (is_turbolev()) { // TODO(dmercadier): This can be removed once the Turbolev escape // analysis can handle VirtualConsStrings. return AddNewNode<NewConsString>({new_length, left, right}); } ValueNode* new_map; GET_VALUE_OR_ABORT(new_map, BuildNewConsStringMap(left, right)); VirtualObject* cons_string = CreateConsString(new_map, new_length, left, right); return BuildInlinedAllocation(cons_string, allocation_type); }; return Select( [&](BranchBuilder& builder) { if (left_min_length > 0) return BranchResult::kAlwaysFalse; return BuildBranchIfInt32Compare(builder, Operation::kEqual, left_length, GetInt32Constant(0)); }, [&] { return right; }, [&] { return Select( [&](BranchBuilder& builder) { if (right_min_length > 0) return BranchResult::kAlwaysFalse; return BuildBranchIfInt32Compare(builder, Operation::kEqual, right_length, GetInt32Constant(0)); }, [&] { return left; }, [&] { return BuildConsString(); }); }); } ReduceResult MaglevGraphBuilder::BuildUnwrapStringWrapper(ValueNode* input) { DCHECK(NodeTypeIs(GetType(input), NodeType::kStringOrStringWrapper)); if (NodeTypeIs(GetType(input), NodeType::kString)) return input; return AddNewNode<UnwrapStringWrapper>({input}); } MaybeReduceResult MaglevGraphBuilder::TryBuildStringConcat(ValueNode* left, ValueNode* right) { NodeType left_type = GetType(left); NodeType right_type = GetType(right); if (IsEmptyNodeType(left_type) || IsEmptyNodeType(right_type)) { // We must be in unreachable code. return BuildAbort(AbortReason::kUnreachable); } bool left_is_string = NodeTypeIs(left_type, NodeType::kString); bool right_is_string = NodeTypeIs(right_type, NodeType::kString); if (!left_is_string && !right_is_string) { return MaybeReduceResult::Fail(); } // Build the necessary ToString conversions. // // JSReceivers need special handling due to ToPrimitive in // https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator // which uses preferredType NUMBER, unlike the same call in ToString, which // uses STRING. For now, pass these on to StringAddConvertLeft/Right builtins. // TODO(jgruber): consider inlining this instead, and properly converting // operands using `ToString(ToPrimitive(op))`. if (!left_is_string) { if (NodeTypeCanBe(left_type, NodeType::kJSReceiver)) { constexpr Builtin kTarget = Builtin::kStringAddConvertLeft; CallBuiltin* result = BuildCallBuiltin<kTarget>({left, right}); SetAccumulator(result); return result; } GET_VALUE_OR_ABORT(left, BuildToString(left, ToString::kThrowOnSymbol)); } else if (!right_is_string) { if (NodeTypeCanBe(right_type, NodeType::kJSReceiver)) { constexpr Builtin kTarget = Builtin::kStringAddConvertRight; CallBuiltin* result = BuildCallBuiltin<kTarget>({left, right}); SetAccumulator(result); return result; } GET_VALUE_OR_ABORT(right, BuildToString(right, ToString::kThrowOnSymbol)); } return BuildStringConcat(left, right); } ReduceResult MaglevGraphBuilder::BuildStringConcat(ValueNode* left, ValueNode* right) { if (RootConstant* root_constant = left->TryCast<RootConstant>()) { if (root_constant->index() == RootIndex::kempty_string) { RETURN_IF_ABORT(BuildCheckString(right)); SetAccumulator(right); return ReduceResult::Done(); } } if (RootConstant* root_constant = right->TryCast<RootConstant>()) { if (root_constant->index() == RootIndex::kempty_string) { RETURN_IF_ABORT(BuildCheckString(left)); SetAccumulator(left); return ReduceResult::Done(); } } auto left_cst = TryGetStringConstant(left); auto right_cst = TryGetStringConstant(right); Handle<String> left_string; Handle<String> right_string; if (left_cst.ToHandle(&left_string) && right_cst.ToHandle(&right_string)) { Handle<String> concatenated; if (compiler::utils::ConcatenateStrings(left_string, right_string, broker()) .ToHandle(&concatenated)) { ValueNode* string_node = graph()->GetConstant(MakeRefAssumeMemoryFence( broker(), broker()->CanonicalPersistentHandle(concatenated))); SetAccumulator(string_node); return ReduceResult::Done(); } } RETURN_IF_ABORT(BuildCheckString(left)); RETURN_IF_ABORT(BuildCheckString(right)); PROCESS_AND_RETURN_IF_DONE(TryBuildNewConsString(left, right), SetAccumulator); return SetAccumulator(AddNewNode<StringConcat>({left, right})); } ReduceResult MaglevGraphBuilder::BuildFloat64SpeculateSafeAdd( ValueNode* left, ValueNode* right) { DCHECK(flags_.can_speculative_additive_safe_int); PROCESS_AND_RETURN_IF_DONE( reducer_.TryFoldFloat64BinaryOperationForToNumber<Operation::kAdd>( TaggedToFloat64ConversionType::kOnlyNumber, left, right), SetAccumulator); ReduceResult result = AddNewNode<Float64SpeculateSafeAdd>({left, right}); if (result.IsDoneWithValue()) { result.value()->set_can_truncate_to_int32(true); } return SetAccumulator(result); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::VisitBinaryOperation() { FeedbackNexus nexus = FeedbackNexusForOperand(1); BinaryOperationHint feedback_hint = nexus.GetBinaryOperationFeedback(); switch (feedback_hint) { case BinaryOperationHint::kNone: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation); case BinaryOperationHint::kAdditiveSafeInteger: if (flags_.can_speculative_additive_safe_int) { DCHECK_EQ(kOperation, Operation::kAdd); ValueNode* left; GET_VALUE_OR_ABORT( left, LoadRegisterFloat64ForToNumber(0, NodeType::kNumber)); ValueNode* right; GET_VALUE_OR_ABORT(right, GetAccumulatorFloat64ForToNumber(NodeType::kNumber)); return BuildFloat64SpeculateSafeAdd(left, right); } [[fallthrough]]; case BinaryOperationHint::kSignedSmall: case BinaryOperationHint::kSignedSmallInputs: case BinaryOperationHint::kNumber: case BinaryOperationHint::kNumberOrOddball: { NodeType allowed_input_type = BinopHintToNodeTypeAndConversionType(feedback_hint); if constexpr (BinaryOperationIsBitwiseInt32<kOperation>()) { return BuildTruncatingInt32BinaryOperationNodeForToNumber<kOperation>( allowed_input_type); } else if (feedback_hint == BinaryOperationHint::kSignedSmall) { if constexpr (kOperation == Operation::kExponentiate) { // Exponentiate never updates the feedback to be a Smi. UNREACHABLE(); } else { return BuildInt32BinaryOperationNode<kOperation>(); } } else { return BuildFloat64BinaryOperationNodeForToNumber<kOperation>( allowed_input_type); } break; } case BinaryOperationHint::kString: if constexpr (kOperation == Operation::kAdd) { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); return BuildStringConcat(left, right); } break; case BinaryOperationHint::kStringOrStringWrapper: if constexpr (kOperation == Operation::kAdd) { if (broker() ->dependencies() ->DependOnStringWrapperToPrimitiveProtector()) { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_ABORT(BuildCheckStringOrStringWrapper(left)); RETURN_IF_ABORT(BuildCheckStringOrStringWrapper(right)); GET_VALUE_OR_ABORT(left, BuildUnwrapStringWrapper(left)); GET_VALUE_OR_ABORT(right, BuildUnwrapStringWrapper(right)); return BuildStringConcat(left, right); } } [[fallthrough]]; case BinaryOperationHint::kBigInt: case BinaryOperationHint::kBigInt64: case BinaryOperationHint::kAny: // Fallback to generic node. break; } if constexpr (kOperation == Operation::kAdd) { // Since feedback describes both operands, it is only set to a value if // both operands fulfill the condition (e.g. if both are strings). // But we can do better for strings, since only one operand must be a // string to result in a string-add operation. DCHECK_NE(feedback_hint, BinaryOperationHint::kString); ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_DONE(TryBuildStringConcat(left, right)); } return BuildGenericBinaryOperationNode<kOperation>(); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::VisitBinarySmiOperation() { FeedbackNexus nexus = FeedbackNexusForOperand(1); BinaryOperationHint feedback_hint = nexus.GetBinaryOperationFeedback(); switch (feedback_hint) { case BinaryOperationHint::kNone: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation); case BinaryOperationHint::kAdditiveSafeInteger: if (flags_.can_speculative_additive_safe_int) { DCHECK_EQ(kOperation, Operation::kAdd); ValueNode* left; GET_VALUE_OR_ABORT(left, GetAccumulatorFloat64ForToNumber(NodeType::kNumber)); double constant = static_cast<double>(iterator_.GetImmediateOperand(0)); return BuildFloat64SpeculateSafeAdd(left, GetFloat64Constant(constant)); } [[fallthrough]]; case BinaryOperationHint::kSignedSmall: case BinaryOperationHint::kSignedSmallInputs: case BinaryOperationHint::kNumber: case BinaryOperationHint::kNumberOrOddball: { NodeType allowed_input_type = BinopHintToNodeTypeAndConversionType(feedback_hint); if constexpr (BinaryOperationIsBitwiseInt32<kOperation>()) { return BuildTruncatingInt32BinarySmiOperationNodeForToNumber< kOperation>(allowed_input_type); } else if (feedback_hint == BinaryOperationHint::kSignedSmall) { if constexpr (kOperation == Operation::kExponentiate) { // Exponentiate never updates the feedback to be a Smi. UNREACHABLE(); } else { return BuildInt32BinarySmiOperationNode<kOperation>(); } } else { return BuildFloat64BinarySmiOperationNodeForToNumber<kOperation>( allowed_input_type); } break; } case BinaryOperationHint::kString: case BinaryOperationHint::kStringOrStringWrapper: case BinaryOperationHint::kBigInt: case BinaryOperationHint::kBigInt64: case BinaryOperationHint::kAny: // Fallback to generic node. break; } return BuildGenericBinarySmiOperationNode<kOperation>(); } template <Operation kOperation, typename type> bool OperationValue(type left, type right) { switch (kOperation) { case Operation::kEqual: case Operation::kStrictEqual: return left == right; case Operation::kLessThan: return left < right; case Operation::kLessThanOrEqual: return left <= right; case Operation::kGreaterThan: return left > right; case Operation::kGreaterThanOrEqual: return left >= right; } } template <Operation kOperation> MaybeReduceResult MaglevGraphBuilder::TryReduceCompareEqualAgainstConstant() { if (kOperation != Operation::kStrictEqual && kOperation != Operation::kEqual) { return {}; } ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); ValueNode* other = right; compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(left); if (!maybe_constant) { maybe_constant = TryGetConstant<HeapObject>(right); other = left; } if (!maybe_constant) return {}; if (CheckType(other, NodeType::kBoolean)) { auto CompareOtherWith = [&](bool constant) { compiler::OptionalHeapObjectRef const_other = TryGetConstant<HeapObject>(other); if (const_other) { auto bool_other = const_other->TryGetBooleanValue(broker()); if (bool_other.has_value()) { SetAccumulator(GetBooleanConstant(constant == *bool_other)); return ReduceResult::Done(); } } if (constant) { SetAccumulator(other); return ReduceResult::Done(); } else { return SetAccumulator(AddNewNode<LogicalNot>({other})); } }; if (maybe_constant.equals(broker_->true_value())) { return CompareOtherWith(true); } else if (maybe_constant.equals(broker_->false_value())) { return CompareOtherWith(false); } else if (kOperation == Operation::kEqual) { // For `bool == num` we can convert the actual comparison `ToNumber(bool) // == num` into `(num == 1) ? bool : ((num == 0) ? !bool : false)`, std::optional<double> val = {}; if (maybe_constant.value().IsSmi()) { val = maybe_constant.value().AsSmi(); } else if (maybe_constant.value().IsHeapNumber()) { val = maybe_constant.value().AsHeapNumber().value(); } if (val) { if (*val == 0) { return CompareOtherWith(false); } else if (*val == 1) { return CompareOtherWith(true); } else { // The constant number is neither equal to `ToNumber(true)` nor // `ToNumber(false)`. SetAccumulator(GetBooleanConstant(false)); return ReduceResult::Done(); } } } } if (kOperation != Operation::kStrictEqual) return {}; InstanceType type = maybe_constant.value().map(broker()).instance_type(); if (!InstanceTypeChecker::IsReferenceComparable(type)) { return {}; } // If the constant is the undefined value, we can compare it // against holey floats. if (maybe_constant->IsUndefined()) { ValueNode* holey_float = nullptr; if (left->is_holey_float64()) { holey_float = left; } else if (right->is_holey_float64()) { holey_float = right; } if (holey_float) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE SetAccumulator(AddNewNodeNoInputConversion<HoleyFloat64IsUndefinedOrHole>( {holey_float})); #else SetAccumulator( AddNewNodeNoInputConversion<HoleyFloat64IsHole>({holey_float})); #endif // V8_ENABLE_UNDEFINED_DOUBLE return ReduceResult::Done(); } } if (left->properties().value_representation() != ValueRepresentation::kTagged || right->properties().value_representation() != ValueRepresentation::kTagged) { SetAccumulator(GetBooleanConstant(false)); return ReduceResult::Done(); } return SetAccumulator(BuildTaggedEqual(left, right)); } template <Operation kOperation> ReduceResult MaglevGraphBuilder::VisitCompareOperation() { RETURN_IF_DONE(TryReduceCompareEqualAgainstConstant<kOperation>()); // Compare opcodes are not always commutative. We sort the ones which are for // better CSE coverage. auto SortCommute = [](ValueNode*& left, ValueNode*& right) { if (!v8_flags.maglev_cse) return; if (kOperation != Operation::kEqual && kOperation != Operation::kStrictEqual) { return; } if (left > right) { std::swap(left, right); } }; auto TryConstantFoldInt32 = [&](ValueNode* left, ValueNode* right) { if (left->Is<Int32Constant>() && right->Is<Int32Constant>()) { int left_value = left->Cast<Int32Constant>()->value(); int right_value = right->Cast<Int32Constant>()->value(); SetAccumulator(GetBooleanConstant( OperationValue<kOperation>(left_value, right_value))); return true; } return false; }; auto TryConstantFoldUint32ComparedToZero = [&](ValueNode* left, ValueNode* right) { if (left->is_uint32()) { auto right_constant = TryGetInt32Constant(right); if ((right_constant && *right_constant <= 0)) { if (kOperation == Operation::kGreaterThanOrEqual) { SetAccumulator(GetBooleanConstant(true)); return true; } else if (kOperation == Operation::kLessThan) { SetAccumulator(GetBooleanConstant(false)); return true; } } } return false; }; auto TryConstantFoldEqual = [&](ValueNode* left, ValueNode* right) { if (left == right) { SetAccumulator( GetBooleanConstant(kOperation == Operation::kEqual || kOperation == Operation::kStrictEqual || kOperation == Operation::kLessThanOrEqual || kOperation == Operation::kGreaterThanOrEqual)); return true; } return false; }; auto MaybeOddballs = [&]() { auto MaybeOddball = [&](ValueNode* value) { ValueRepresentation rep = value->value_representation(); switch (rep) { case ValueRepresentation::kInt32: case ValueRepresentation::kUint32: case ValueRepresentation::kFloat64: return false; default: break; } return !CheckType(value, NodeType::kNumber); }; return MaybeOddball(LoadRegister(0)) || MaybeOddball(GetAccumulator()); }; auto GetConversionType = [](CompareOperationHint hint) { switch (hint) { case CompareOperationHint::kNumber: return NodeType::kNumber; case CompareOperationHint::kNumberOrBoolean: return NodeType::kNumberOrBoolean; case CompareOperationHint::kNumberOrOddball: return NodeType::kNumberOrOddball; default: UNREACHABLE(); } }; CompareOperationHint hint = iterator_.GetEmbeddedCompareOperationHint(); switch (hint) { case CompareOperationHint::kNone: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation); case CompareOperationHint::kSignedSmall: { // TODO(victorgomes): Add a smart equality operator, that compares for // constants in different representations. ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); if (TryConstantFoldUint32ComparedToZero(left, right)) { return ReduceResult::Done(); } GET_VALUE_OR_ABORT(left, GetInt32(left)); GET_VALUE_OR_ABORT(right, GetInt32(right)); if (TryConstantFoldEqual(left, right)) return ReduceResult::Done(); if (TryConstantFoldInt32(left, right)) return ReduceResult::Done(); SortCommute(left, right); SetAccumulator( AddNewNodeNoInputConversion<Int32Compare>({left, right}, kOperation)); return ReduceResult::Done(); } case CompareOperationHint::kNumberOrOddball: // Equality and strict equality don't perform ToNumber conversions on // Oddballs. if ((kOperation == Operation::kEqual || kOperation == Operation::kStrictEqual) && MaybeOddballs()) { break; } [[fallthrough]]; case CompareOperationHint::kNumberOrBoolean: if (kOperation == Operation::kStrictEqual && MaybeOddballs()) { break; } [[fallthrough]]; case CompareOperationHint::kNumber: { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); if (left->is_int32() && right->is_int32()) { if (TryConstantFoldEqual(left, right)) return ReduceResult::Done(); if (TryConstantFoldInt32(left, right)) return ReduceResult::Done(); SortCommute(left, right); SetAccumulator(AddNewNodeNoInputConversion<Int32Compare>({left, right}, kOperation)); return ReduceResult::Done(); } if (TryConstantFoldUint32ComparedToZero(left, right)) { return ReduceResult::Done(); } auto allowed_input_type = GetConversionType(hint); GET_VALUE_OR_ABORT(left, GetFloat64ForToNumber(left, allowed_input_type)); GET_VALUE_OR_ABORT(right, GetFloat64ForToNumber(right, allowed_input_type)); if (left->Is<Float64Constant>() && right->Is<Float64Constant>()) { double left_value = left->Cast<Float64Constant>()->value().get_scalar(); double right_value = right->Cast<Float64Constant>()->value().get_scalar(); SetAccumulator(GetBooleanConstant( OperationValue<kOperation>(left_value, right_value))); return ReduceResult::Done(); } SortCommute(left, right); SetAccumulator(AddNewNodeNoInputConversion<Float64Compare>({left, right}, kOperation)); return ReduceResult::Done(); } case CompareOperationHint::kInternalizedString: { DCHECK(kOperation == Operation::kEqual || kOperation == Operation::kStrictEqual); ValueNode *left, *right; if (IsRegisterEqualToAccumulator(0)) { GET_VALUE_OR_ABORT( left, GetInternalizedString(iterator_.GetRegisterOperand(0))); right = left; SetAccumulator(GetRootConstant(RootIndex::kTrueValue)); return ReduceResult::Done(); } GET_VALUE_OR_ABORT( left, GetInternalizedString(iterator_.GetRegisterOperand(0))); GET_VALUE_OR_ABORT( right, GetInternalizedString(interpreter::Register::virtual_accumulator())); if (TryConstantFoldEqual(left, right)) return ReduceResult::Done(); return SetAccumulator(BuildTaggedEqual(left, right)); } case CompareOperationHint::kSymbol: { DCHECK(kOperation == Operation::kEqual || kOperation == Operation::kStrictEqual); ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_ABORT(BuildCheckSymbol(left)); RETURN_IF_ABORT(BuildCheckSymbol(right)); if (TryConstantFoldEqual(left, right)) return ReduceResult::Done(); return SetAccumulator(BuildTaggedEqual(left, right)); } case CompareOperationHint::kString: { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_ABORT(BuildCheckString(left)); RETURN_IF_ABORT(BuildCheckString(right)); ReduceResult result = ReduceResult::Done(); if (TryConstantFoldEqual(left, right)) return ReduceResult::Done(); switch (kOperation) { case Operation::kEqual: case Operation::kStrictEqual: { result = AddNewNode<StringEqual>({left, right}, StringEqualInputMode::kOnlyStrings); break; } case Operation::kLessThan: result = BuildCallBuiltinWithTaggedInputs<Builtin::kStringLessThan>( {left, right}); break; case Operation::kLessThanOrEqual: result = BuildCallBuiltinWithTaggedInputs<Builtin::kStringLessThanOrEqual>( {left, right}); break; case Operation::kGreaterThan: result = BuildCallBuiltinWithTaggedInputs<Builtin::kStringGreaterThan>( {left, right}); break; case Operation::kGreaterThanOrEqual: result = BuildCallBuiltinWithTaggedInputs< Builtin::kStringGreaterThanOrEqual>({left, right}); break; } return SetAccumulator(result); } case CompareOperationHint::kStringOrOddball: { if (kOperation == Operation::kStrictEqual) { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_ABORT(BuildCheckStringOrOddball(left)); RETURN_IF_ABORT(BuildCheckStringOrOddball(right)); if (TryConstantFoldEqual(left, right)) return ReduceResult::Done(); // TODO(marja): If one of the sides is constant, we can generate better // code (e.g., don't need to check its type at run time). return SetAccumulator(AddNewNode<StringEqual>( {left, right}, StringEqualInputMode::kStringsOrOddballs)); } break; } case CompareOperationHint::kAny: case CompareOperationHint::kBigInt64: case CompareOperationHint::kBigInt: break; case CompareOperationHint::kReceiverOrNullOrUndefined: { if (kOperation == Operation::kEqual) { break; } DCHECK_EQ(kOperation, Operation::kStrictEqual); ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_ABORT(BuildCheckJSReceiverOrNullOrUndefined(left)); RETURN_IF_ABORT(BuildCheckJSReceiverOrNullOrUndefined(right)); return SetAccumulator(BuildTaggedEqual(left, right)); } case CompareOperationHint::kReceiver: { DCHECK(kOperation == Operation::kEqual || kOperation == Operation::kStrictEqual); ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); RETURN_IF_ABORT(BuildCheckJSReceiver(left)); RETURN_IF_ABORT(BuildCheckJSReceiver(right)); return SetAccumulator(BuildTaggedEqual(left, right)); } } return BuildGenericBinaryOperationNode<kOperation>(); } ReduceResult MaglevGraphBuilder::VisitLdar() { MoveNodeBetweenRegisters(iterator_.GetRegisterOperand(0), interpreter::Register::virtual_accumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaZero() { SetAccumulator(GetSmiConstant(0)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaSmi() { int constant = iterator_.GetImmediateOperand(0); SetAccumulator(GetSmiConstant(constant)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaUndefined() { SetAccumulator(GetRootConstant(RootIndex::kUndefinedValue)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaNull() { SetAccumulator(GetRootConstant(RootIndex::kNullValue)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaTheHole() { SetAccumulator(GetRootConstant(RootIndex::kTheHoleValue)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaTrue() { SetAccumulator(GetRootConstant(RootIndex::kTrueValue)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaFalse() { SetAccumulator(GetRootConstant(RootIndex::kFalseValue)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaConstant() { SetAccumulator(GetConstant(GetRefOperand<HeapObject>(0))); return ReduceResult::Done(); } bool MaglevGraphBuilder::TrySpecializeLoadContextSlotToFunctionContext( ValueNode* context, int slot_index, ContextSlotMutability slot_mutability) { DCHECK(compilation_unit_->info()->specialize_to_function_context()); if (slot_mutability == kMutable) return false; auto context_ref = TryGetConstant<Context>(context); if (!context_ref) return false; compiler::OptionalObjectRef maybe_slot_value = context_ref->get(broker(), slot_index); if (!maybe_slot_value.has_value()) return false; compiler::ObjectRef slot_value = maybe_slot_value.value(); if (slot_value.IsHeapObject()) { // Even though the context slot is immutable, the context might have escaped // before the function to which it belongs has initialized the slot. We // must be conservative and check if the value in the slot is currently the // hole or undefined. Only if it is neither of these, can we be sure that it // won't change anymore. // // See also: JSContextSpecialization::ReduceJSLoadContext. if (slot_value.IsTheHole()) return false; if (slot_value.IsUndefined()) return false; } // Fold the load of the immutable slot. SetAccumulator(GetConstant(slot_value)); return true; } ValueNode* MaglevGraphBuilder::TrySpecializeLoadContextSlot( ValueNode* context_node, int index) { if (!context_node->Is<Constant>()) return {}; compiler::ContextRef context = context_node->Cast<Constant>()->ref().AsContext(); auto maybe_value = context.get(broker(), index); if (!maybe_value || maybe_value->IsTheHole() || maybe_value->IsUndefinedContextCell()) { return {}; } int offset = Context::OffsetOfElementAt(index); if (!maybe_value->IsContextCell()) { // No need to check for context cells anymore. RETURN_VALUE( AddNewNode<LoadContextSlotNoCells>({context_node}, offset, false)); } compiler::ContextCellRef slot_ref = maybe_value->AsContextCell(); ContextCell::State state = slot_ref.state(); switch (state) { case ContextCell::kConst: { auto constant = slot_ref.tagged_value(broker()); if (!constant.has_value()) { RETURN_VALUE( AddNewNode<LoadContextSlotNoCells>({context_node}, offset, false)); } broker()->dependencies()->DependOnContextCell(slot_ref, state); return GetConstant(*constant); } case ContextCell::kSmi: { broker()->dependencies()->DependOnContextCell(slot_ref, state); RETURN_VALUE(BuildLoadTaggedField(GetConstant(slot_ref), offsetof(ContextCell, tagged_value_), LoadType::kSmi)); } case ContextCell::kInt32: broker()->dependencies()->DependOnContextCell(slot_ref, state); return AddNewNodeNoInputConversion<LoadInt32>( {GetConstant(slot_ref)}, static_cast<int>(offsetof(ContextCell, double_value_))); case ContextCell::kFloat64: broker()->dependencies()->DependOnContextCell(slot_ref, state); return AddNewNodeNoInputConversion<LoadFloat64>( {GetConstant(slot_ref)}, static_cast<int>(offsetof(ContextCell, double_value_))); case ContextCell::kDetached: { RETURN_VALUE( AddNewNode<LoadContextSlotNoCells>({context_node}, offset, false)); } } UNREACHABLE(); } ValueNode* MaglevGraphBuilder::LoadAndCacheContextSlot( ValueNode* context, int index, ContextSlotMutability slot_mutability, ContextMode context_mode) { int offset = Context::OffsetOfElementAt(index); ValueNode*& cached_value = known_node_aspects().GetContextCachedValue( context, offset, slot_mutability); if (cached_value) { TRACE(" * Reusing cached context slot " << PrintNodeLabel(context) << "[" << offset << "]: " << PrintNode(cached_value)); return cached_value; } if (slot_mutability == kMutable && !known_node_aspects().IsContextCacheEmpty(slot_mutability)) { known_node_aspects().UpdateMayHaveAliasingContexts( broker(), local_isolate(), context); } if (context_mode == ContextMode::kHasContextCells && (v8_flags.script_context_cells || v8_flags.function_context_cells) && slot_mutability == kMutable) { // We collect feedback only for mutable context slots. cached_value = TrySpecializeLoadContextSlot(context, index); if (cached_value) return cached_value; GET_VALUE(cached_value, AddNewNode<LoadContextSlot>( {context}, offset, slot_mutability == ContextSlotMutability::kImmutable)); return cached_value; } GET_VALUE(cached_value, AddNewNode<LoadContextSlotNoCells>( {context}, offset, slot_mutability == ContextSlotMutability::kImmutable)); return cached_value; } bool MaglevGraphBuilder::ContextMayAlias( ValueNode* context, compiler::OptionalScopeInfoRef scope_info) { // Distinguishing contexts by their scope info only works if scope infos are // guaranteed to be unique. // TODO(crbug.com/401059828): reenable when crashes are gone. if ((true) || !v8_flags.reuse_scope_infos) return true; if (!scope_info.has_value()) { return true; } auto other = graph()->TryGetScopeInfo(context); if (!other.has_value()) { return true; } return scope_info->equals(*other); } MaybeReduceResult MaglevGraphBuilder::TrySpecializeStoreContextSlot( ValueNode* context, int index, ValueNode* value) { DCHECK(v8_flags.script_context_cells || v8_flags.function_context_cells); if (!context->Is<Constant>()) { return AddNewNode<StoreContextSlotWithWriteBarrier>({context, value}, index); } if (IsEmptyNodeType(GetType(value))) { return EmitUnconditionalDeopt(DeoptimizeReason::kWrongValue); } compiler::ContextRef context_ref = context->Cast<Constant>()->ref().AsContext(); auto maybe_value = context_ref.get(broker(), index); if (!maybe_value || maybe_value->IsTheHole() || maybe_value->IsUndefinedContextCell()) { return AddNewNode<StoreContextSlotWithWriteBarrier>({context, value}, index); } int offset = Context::OffsetOfElementAt(index); if (!maybe_value->IsContextCell()) { return BuildStoreTaggedField(context, value, offset, StoreTaggedMode::kDefaultToContext); } compiler::ContextCellRef slot_ref = maybe_value->AsContextCell(); ContextCell::State state = slot_ref.state(); switch (state) { case ContextCell::kConst: { auto constant = slot_ref.tagged_value(broker()); if (!constant.has_value() || (constant->IsString() && !constant->IsInternalizedString())) { return AddNewNode<StoreContextSlotWithWriteBarrier>({context, value}, index); } broker()->dependencies()->DependOnContextCell(slot_ref, state); return BuildCheckNumericalValueOrByReference( value, *constant, DeoptimizeReason::kStoreToConstant); } case ContextCell::kSmi: // HoleyFloat64ToTagged does not canonicalize Smis by default, use // GetSmiValue to force canonicalization for the value if necessary. // TODO(454485895): Consider removing this workaround since // HoleyFloat64ToTagged now canonicalizes by default. RETURN_IF_ABORT(GetSmiValue(value)); broker()->dependencies()->DependOnContextCell(slot_ref, state); return AddNewNode<StoreSmiContextCell>({GetConstant(slot_ref), value}, context_ref, offset); case ContextCell::kInt32: RETURN_IF_ABORT(EnsureInt32(value, true)); broker()->dependencies()->DependOnContextCell(slot_ref, state); return AddNewNode<StoreInt32ContextCell>({GetConstant(slot_ref), value}, context_ref, offset); case ContextCell::kFloat64: RETURN_IF_ABORT(BuildCheckNumber(value)); broker()->dependencies()->DependOnContextCell(slot_ref, state); return AddNewNode<StoreFloat64ContextCell>({GetConstant(slot_ref), value}, context_ref, offset); case ContextCell::kDetached: return BuildStoreTaggedField(context, value, offset, StoreTaggedMode::kDefaultToContext); } UNREACHABLE(); } ReduceResult MaglevGraphBuilder::StoreAndCacheContextSlot( ValueNode* context, int index, ValueNode* value, ContextMode context_mode) { int offset = Context::OffsetOfElementAt(index); DCHECK(!known_node_aspects().HasContextCacheValue( context, offset, ContextSlotMutability::kImmutable)); Node* store = nullptr; if ((v8_flags.script_context_cells || v8_flags.function_context_cells) && context_mode == ContextMode::kHasContextCells) { MaybeReduceResult result = TrySpecializeStoreContextSlot(context, index, value); RETURN_IF_ABORT(result); if (result.IsDoneWithoutPayload()) { // If we didn't need to emit any store, there is nothing to cache. return result.Checked(); } DCHECK(result.IsDoneWithPayload()); store = result.node(); } else { GET_NODE_OR_ABORT( store, BuildStoreTaggedField(context, value, offset, StoreTaggedMode::kDefaultToContext)); } TRACE(" * Recording context slot store " << PrintNodeLabel(context) << "[" << offset << "]: " << PrintNode(value)); auto aliased_slots = known_node_aspects().ClearAliasedContextSlotsFor( graph(), context, offset, value); bool added_to_cache = known_node_aspects().SetContextCachedValue(context, offset, value); // Update loop effect state. KnownNodeAspects::LoadedContextSlotsKey key{context, offset}; if (is_loop_effect_tracking()) { for (auto slot : aliased_slots) { loop_effects_->context_slot_written.insert(slot); loop_effects_->may_have_aliasing_contexts = true; } loop_effects_->context_slot_written.insert(key); } // Update last context store cache and kill the previous store. // TODO(victorgomes): Port this store-store elimination to the optimizer. if (added_to_cache) { unobserved_context_slot_stores_[key] = store; } else { if (known_node_aspects().may_have_aliasing_contexts() != KnownNodeAspects::ContextSlotLoadsAlias::kYes) { auto last_store = unobserved_context_slot_stores_.find(key); if (last_store != unobserved_context_slot_stores_.end()) { MarkNodeDead(last_store->second); last_store->second = store; } else { unobserved_context_slot_stores_[key] = store; } } } return ReduceResult::Done(); } void MaglevGraphBuilder::BuildLoadContextSlot( ValueNode* context, size_t depth, int slot_index, ContextSlotMutability slot_mutability, ContextMode context_mode) { context = GetContextAtDepth(context, depth); if (compilation_unit_->info()->specialize_to_function_context() && TrySpecializeLoadContextSlotToFunctionContext(context, slot_index, slot_mutability)) { return; // Our work here is done. } // Always load the slot here as if it were mutable. Immutable slots have a // narrow range of mutability if the context escapes before the slot is // initialized, so we can't safely assume that the load can be cached in case // it's a load before initialization (e.g. var a = a + 42). current_interpreter_frame_.set_accumulator( LoadAndCacheContextSlot(context, slot_index, kMutable, context_mode)); } ReduceResult MaglevGraphBuilder::BuildStoreContextSlot( ValueNode* context, size_t depth, int slot_index, ValueNode* value, ContextMode context_mode) { context = GetContextAtDepth(context, depth); return StoreAndCacheContextSlot(context, slot_index, value, context_mode); } ReduceResult MaglevGraphBuilder::VisitLdaContextSlotNoCell() { ValueNode* context = LoadRegister(0); int slot_index = iterator_.GetContextSlotOperand(1); size_t depth = iterator_.GetUnsignedImmediateOperand(2); BuildLoadContextSlot(context, depth, slot_index, kMutable, ContextMode::kNoContextCells); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaContextSlot() { ValueNode* context = LoadRegister(0); int slot_index = iterator_.GetContextSlotOperand(1); size_t depth = iterator_.GetUnsignedImmediateOperand(2); BuildLoadContextSlot(context, depth, slot_index, kMutable, ContextMode::kHasContextCells); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaImmutableContextSlot() { ValueNode* context = LoadRegister(0); int slot_index = iterator_.GetContextSlotOperand(1); size_t depth = iterator_.GetUnsignedImmediateOperand(2); BuildLoadContextSlot(context, depth, slot_index, kImmutable, ContextMode::kNoContextCells); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaCurrentContextSlotNoCell() { ValueNode* context = GetContext(); int slot_index = iterator_.GetContextSlotOperand(0); BuildLoadContextSlot(context, 0, slot_index, kMutable, ContextMode::kNoContextCells); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaCurrentContextSlot() { ValueNode* context = GetContext(); int slot_index = iterator_.GetContextSlotOperand(0); BuildLoadContextSlot(context, 0, slot_index, kMutable, ContextMode::kHasContextCells); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaImmutableCurrentContextSlot() { ValueNode* context = GetContext(); int slot_index = iterator_.GetContextSlotOperand(0); BuildLoadContextSlot(context, 0, slot_index, kImmutable, ContextMode::kNoContextCells); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitStaContextSlotNoCell() { ValueNode* context = LoadRegister(0); int slot_index = iterator_.GetContextSlotOperand(1); size_t depth = iterator_.GetUnsignedImmediateOperand(2); return BuildStoreContextSlot(context, depth, slot_index, GetAccumulator(), ContextMode::kNoContextCells); } ReduceResult MaglevGraphBuilder::VisitStaCurrentContextSlotNoCell() { ValueNode* context = GetContext(); int slot_index = iterator_.GetContextSlotOperand(0); return BuildStoreContextSlot(context, 0, slot_index, GetAccumulator(), ContextMode::kNoContextCells); } ReduceResult MaglevGraphBuilder::VisitStaContextSlot() { ValueNode* context = LoadRegister(0); int slot_index = iterator_.GetContextSlotOperand(1); size_t depth = iterator_.GetUnsignedImmediateOperand(2); return BuildStoreContextSlot(context, depth, slot_index, GetAccumulator(), ContextMode::kHasContextCells); } ReduceResult MaglevGraphBuilder::VisitStaCurrentContextSlot() { ValueNode* context = GetContext(); int slot_index = iterator_.GetContextSlotOperand(0); return BuildStoreContextSlot(context, 0, slot_index, GetAccumulator(), ContextMode::kHasContextCells); } ReduceResult MaglevGraphBuilder::VisitStar() { MoveNodeBetweenRegisters(interpreter::Register::virtual_accumulator(), iterator_.GetRegisterOperand(0)); return ReduceResult::Done(); } #define SHORT_STAR_VISITOR(Name, ...) \ ReduceResult MaglevGraphBuilder::Visit##Name() { \ MoveNodeBetweenRegisters( \ interpreter::Register::virtual_accumulator(), \ interpreter::Register::FromShortStar(interpreter::Bytecode::k##Name)); \ return ReduceResult::Done(); \ } SHORT_STAR_BYTECODE_LIST(SHORT_STAR_VISITOR) #undef SHORT_STAR_VISITOR ReduceResult MaglevGraphBuilder::VisitMov() { MoveNodeBetweenRegisters(iterator_.GetRegisterOperand(0), iterator_.GetRegisterOperand(1)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitPushContext() { MoveNodeBetweenRegisters(interpreter::Register::current_context(), iterator_.GetRegisterOperand(0)); SetContext(GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitPopContext() { SetContext(LoadRegister(0)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildTaggedEqual(ValueNode* lhs, ValueNode* rhs) { ValueNode* tagged_lhs; GET_VALUE_OR_ABORT(tagged_lhs, GetTaggedValue(lhs)); ValueNode* tagged_rhs; GET_VALUE_OR_ABORT(tagged_rhs, GetTaggedValue(rhs)); if (tagged_lhs == tagged_rhs) { return GetBooleanConstant(true); } if (reducer_.HaveDisjointTypes(tagged_lhs, tagged_rhs)) { return GetBooleanConstant(false); } // TODO(victorgomes): We could retrieve the HeapObjectRef in Constant and // compare them. if (IsConstantNode(tagged_lhs->opcode()) && !tagged_lhs->Is<Constant>() && tagged_lhs->opcode() == tagged_rhs->opcode()) { // Constants nodes are canonicalized, except for the node holding // HeapObjectRef, so equal constants should have been handled above. return GetBooleanConstant(false); } return AddNewNodeNoInputConversion<TaggedEqual>({tagged_lhs, tagged_rhs}); } ReduceResult MaglevGraphBuilder::BuildTaggedEqual(ValueNode* lhs, RootIndex rhs_index) { return BuildTaggedEqual(lhs, GetRootConstant(rhs_index)); } ReduceResult MaglevGraphBuilder::VisitTestReferenceEqual() { ValueNode* lhs = LoadRegister(0); ValueNode* rhs = GetAccumulator(); return SetAccumulator(BuildTaggedEqual(lhs, rhs)); } ReduceResult MaglevGraphBuilder::BuildTestUndetectable(ValueNode* value) { if (value->properties().value_representation() == ValueRepresentation::kHoleyFloat64) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE return AddNewNodeNoInputConversion<HoleyFloat64IsUndefinedOrHole>({value}); #else return AddNewNodeNoInputConversion<HoleyFloat64IsHole>({value}); #endif // V8_ENABLE_UNDEFINED_DOUBLE } else if (value->properties().value_representation() != ValueRepresentation::kTagged) { return GetBooleanConstant(false); } if (auto maybe_constant = TryGetConstant<HeapObject>(value)) { auto map = maybe_constant.value().map(broker()); return GetBooleanConstant(map.is_undetectable()); } NodeType node_type; if (CheckType(value, NodeType::kSmi, &node_type)) { return GetBooleanConstant(false); } if (auto possible_maps = known_node_aspects().TryGetPossibleMaps(value)) { // We check if all the possible maps have the same undetectable bit value. DCHECK_GT(possible_maps->size(), 0); bool first_is_undetectable = possible_maps->at(0).is_undetectable(); bool all_the_same_value = std::all_of(possible_maps->begin(), possible_maps->end(), [first_is_undetectable](compiler::MapRef map) { bool is_undetectable = map.is_undetectable(); return (first_is_undetectable && is_undetectable) || (!first_is_undetectable && !is_undetectable); }); if (all_the_same_value) { return GetBooleanConstant(first_is_undetectable); } } enum CheckType type = GetCheckType(node_type, value); return AddNewNode<TestUndetectable>({value}, type); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfUndetectable( BranchBuilder& builder, ValueNode* value) { ReduceResult test_result = BuildTestUndetectable(value); if (test_result.IsDoneWithAbort()) { return builder.Abort(); } ValueNode* result; GET_VALUE(result, test_result); switch (result->opcode()) { case Opcode::kRootConstant: switch (result->Cast<RootConstant>()->index()) { case RootIndex::kTrueValue: case RootIndex::kUndefinedValue: case RootIndex::kNullValue: return builder.AlwaysTrue(); default: return builder.AlwaysFalse(); } #ifdef V8_ENABLE_UNDEFINED_DOUBLE case Opcode::kHoleyFloat64IsUndefinedOrHole: return BuildBranchIfFloat64IsUndefinedOrHole( builder, result->Cast<HoleyFloat64IsUndefinedOrHole>()->ValueInput().node()); #else case Opcode::kHoleyFloat64IsHole: return BuildBranchIfFloat64IsHole( builder, result->Cast<HoleyFloat64IsHole>()->ValueInput().node()); #endif // V8_ENABLE_UNDEFINED_DOUBLE case Opcode::kTestUndetectable: return builder.Build<BranchIfUndetectable>( {result->Cast<TestUndetectable>()->ValueInput().node()}, result->Cast<TestUndetectable>()->check_type()); default: UNREACHABLE(); } } ReduceResult MaglevGraphBuilder::VisitTestUndetectable() { return SetAccumulator(BuildTestUndetectable(GetAccumulator())); } ReduceResult MaglevGraphBuilder::VisitTestNull() { ValueNode* value = GetAccumulator(); return SetAccumulator(BuildTaggedEqual(value, RootIndex::kNullValue)); } ReduceResult MaglevGraphBuilder::VisitTestUndefined() { ValueNode* value = GetAccumulator(); return SetAccumulator(BuildTaggedEqual(value, RootIndex::kUndefinedValue)); } template <typename Function> MaybeReduceResult MaglevGraphBuilder::TryReduceTypeOf( ValueNode* value, const Function& GetResult) { // Similar to TF, we assume that all undetectable receiver objects are also // callables. In practice, there is only one: document.all. switch (CheckTypes( value, {NodeType::kBoolean, NodeType::kNumber, NodeType::kString, NodeType::kSymbol, NodeType::kCallable, NodeType::kJSArray})) { case NodeType::kBoolean: return GetResult(TypeOfLiteralFlag::kBoolean, RootIndex::kboolean_string); case NodeType::kNumber: return GetResult(TypeOfLiteralFlag::kNumber, RootIndex::knumber_string); case NodeType::kString: return GetResult(TypeOfLiteralFlag::kString, RootIndex::kstring_string); case NodeType::kSymbol: return GetResult(TypeOfLiteralFlag::kSymbol, RootIndex::ksymbol_string); case NodeType::kCallable: return Select( [&](BranchBuilder& builder) { return BuildBranchIfUndetectable(builder, value); }, [&] { return GetResult(TypeOfLiteralFlag::kUndefined, RootIndex::kundefined_string); }, [&] { return GetResult(TypeOfLiteralFlag::kFunction, RootIndex::kfunction_string); }); case NodeType::kJSArray: // TODO(victorgomes): Track JSReceiver, non-callable types in Maglev. return GetResult(TypeOfLiteralFlag::kObject, RootIndex::kobject_string); default: break; } if (value->IsNullValue()) { return GetResult(TypeOfLiteralFlag::kObject, RootIndex::kobject_string); } if (value->IsUndefinedValue()) { return GetResult(TypeOfLiteralFlag::kUndefined, RootIndex::kundefined_string); } return {}; } MaybeReduceResult MaglevGraphBuilder::TryReduceTypeOf(ValueNode* value) { return TryReduceTypeOf(value, [&](TypeOfLiteralFlag _, RootIndex idx) -> ValueNode* { return GetRootConstant(idx); }); } ReduceResult MaglevGraphBuilder::VisitTestTypeOf() { // TODO(v8:7700): Add a branch version of TestTypeOf that does not need to // materialise the boolean value. TypeOfLiteralFlag literal = interpreter::TestTypeOfFlags::Decode(GetFlag8Operand(0)); if (literal == TypeOfLiteralFlag::kOther) { SetAccumulator(GetRootConstant(RootIndex::kFalseValue)); return ReduceResult::Done(); } ValueNode* value = GetAccumulator(); auto GetResult = [&](TypeOfLiteralFlag expected, RootIndex _) { return GetRootConstant(literal == expected ? RootIndex::kTrueValue : RootIndex::kFalseValue); }; PROCESS_AND_RETURN_IF_DONE(TryReduceTypeOf(value, GetResult), SetAccumulator); return SetAccumulator(AddNewNode<TestTypeOf>({value}, literal)); } MaybeReduceResult MaglevGraphBuilder::TryBuildScriptContextStore( const compiler::GlobalAccessFeedback& global_access_feedback) { DCHECK(global_access_feedback.IsScriptContextSlot()); if (global_access_feedback.immutable()) return {}; auto script_context = GetConstant(global_access_feedback.script_context()); return StoreAndCacheContextSlot( script_context, global_access_feedback.slot_index(), GetAccumulator(), ContextMode::kHasContextCells); } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertyCellStore( const compiler::GlobalAccessFeedback& global_access_feedback) { DCHECK(global_access_feedback.IsPropertyCell()); compiler::PropertyCellRef property_cell = global_access_feedback.property_cell(); if (!property_cell.Cache(broker())) return {}; compiler::ObjectRef property_cell_value = property_cell.value(broker()); if (property_cell_value.IsPropertyCellHole()) { // The property cell is no longer valid. return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess); } PropertyDetails property_details = property_cell.property_details(); DCHECK_EQ(PropertyKind::kData, property_details.kind()); if (property_details.IsReadOnly()) { // Don't even bother trying to lower stores to read-only data // properties. // TODO(neis): We could generate code that checks if the new value // equals the old one and then does nothing or deopts, respectively. return {}; } switch (property_details.cell_type()) { case PropertyCellType::kUndefined: return {}; case PropertyCellType::kConstant: { // Record a code dependency on the cell, and just deoptimize if the new // value doesn't match the previous value stored inside the cell. broker()->dependencies()->DependOnGlobalProperty(property_cell); ValueNode* value = GetAccumulator(); return BuildCheckNumericalValueOrByReference( value, property_cell_value, DeoptimizeReason::kStoreToConstant); } case PropertyCellType::kConstantType: { // We rely on stability further below. if (property_cell_value.IsHeapObject() && !property_cell_value.AsHeapObject().map(broker()).is_stable()) { return {}; } // Record a code dependency on the cell, and just deoptimize if the new // value's type doesn't match the type of the previous value in the cell. broker()->dependencies()->DependOnGlobalProperty(property_cell); ValueNode* value = GetAccumulator(); if (property_cell_value.IsHeapObject()) { compiler::MapRef property_cell_value_map = property_cell_value.AsHeapObject().map(broker()); broker()->dependencies()->DependOnStableMap(property_cell_value_map); RETURN_IF_ABORT(BuildCheckHeapObject(value)); RETURN_IF_ABORT( BuildCheckMaps(value, base::VectorOf({property_cell_value_map}))); } else { RETURN_IF_ABORT(GetSmiValue(value)); } ValueNode* property_cell_node = GetConstant(property_cell.AsHeapObject()); return BuildStoreTaggedField(property_cell_node, value, PropertyCell::kValueOffset, StoreTaggedMode::kDefault); } case PropertyCellType::kMutable: { // Record a code dependency on the cell, and just deoptimize if the // property ever becomes read-only. broker()->dependencies()->DependOnGlobalProperty(property_cell); ValueNode* property_cell_node = GetConstant(property_cell.AsHeapObject()); return BuildStoreTaggedField(property_cell_node, GetAccumulator(), PropertyCell::kValueOffset, StoreTaggedMode::kDefault); } case PropertyCellType::kInTransition: UNREACHABLE(); } return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryBuildScriptContextConstantLoad( const compiler::GlobalAccessFeedback& global_access_feedback) { DCHECK(global_access_feedback.IsScriptContextSlot()); if (!global_access_feedback.immutable()) return {}; compiler::OptionalObjectRef maybe_slot_value = global_access_feedback.script_context().get( broker(), global_access_feedback.slot_index()); if (!maybe_slot_value) return {}; return GetConstant(maybe_slot_value.value()); } MaybeReduceResult MaglevGraphBuilder::TryBuildScriptContextLoad( const compiler::GlobalAccessFeedback& global_access_feedback) { DCHECK(global_access_feedback.IsScriptContextSlot()); RETURN_IF_DONE(TryBuildScriptContextConstantLoad(global_access_feedback)); auto script_context = GetConstant(global_access_feedback.script_context()); ContextSlotMutability mutability = global_access_feedback.immutable() ? kImmutable : kMutable; return LoadAndCacheContextSlot(script_context, global_access_feedback.slot_index(), mutability, ContextMode::kHasContextCells); } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertyCellLoad( const compiler::GlobalAccessFeedback& global_access_feedback) { // TODO(leszeks): A bunch of this is copied from // js-native-context-specialization.cc -- I wonder if we can unify it // somehow. DCHECK(global_access_feedback.IsPropertyCell()); compiler::PropertyCellRef property_cell = global_access_feedback.property_cell(); if (!property_cell.Cache(broker())) return {}; compiler::ObjectRef property_cell_value = property_cell.value(broker()); if (property_cell_value.IsPropertyCellHole()) { // The property cell is no longer valid. return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess); } PropertyDetails property_details = property_cell.property_details(); PropertyCellType property_cell_type = property_details.cell_type(); DCHECK_EQ(PropertyKind::kData, property_details.kind()); if (!property_details.IsConfigurable() && property_details.IsReadOnly()) { return GetConstant(property_cell_value); } // Record a code dependency on the cell if we can benefit from the // additional feedback, or the global property is configurable (i.e. // can be deleted or reconfigured to an accessor property). if (property_cell_type != PropertyCellType::kMutable || property_details.IsConfigurable()) { broker()->dependencies()->DependOnGlobalProperty(property_cell); } // Load from constant/undefined global property can be constant-folded. if (property_cell_type == PropertyCellType::kConstant || property_cell_type == PropertyCellType::kUndefined) { return GetConstant(property_cell_value); } ValueNode* property_cell_node = GetConstant(property_cell.AsHeapObject()); return BuildLoadTaggedField(property_cell_node, PropertyCell::kValueOffset); } MaybeReduceResult MaglevGraphBuilder::TryBuildGlobalStore( const compiler::GlobalAccessFeedback& global_access_feedback) { if (global_access_feedback.IsScriptContextSlot()) { return TryBuildScriptContextStore(global_access_feedback); } else if (global_access_feedback.IsPropertyCell()) { return TryBuildPropertyCellStore(global_access_feedback); } else { DCHECK(global_access_feedback.IsMegamorphic()); return {}; } } MaybeReduceResult MaglevGraphBuilder::TryBuildGlobalLoad( const compiler::GlobalAccessFeedback& global_access_feedback) { if (global_access_feedback.IsScriptContextSlot()) { return TryBuildScriptContextLoad(global_access_feedback); } else if (global_access_feedback.IsPropertyCell()) { return TryBuildPropertyCellLoad(global_access_feedback); } else { DCHECK(global_access_feedback.IsMegamorphic()); return {}; } } ReduceResult MaglevGraphBuilder::VisitLdaGlobal() { // LdaGlobal <name_index> <slot> static const int kNameOperandIndex = 0; static const int kSlotOperandIndex = 1; compiler::NameRef name = GetRefOperand<Name>(kNameOperandIndex); FeedbackSlot slot = GetSlotOperand(kSlotOperandIndex); compiler::FeedbackSource feedback_source{feedback(), slot}; return BuildLoadGlobal(name, feedback_source, TypeofMode::kNotInside); } ReduceResult MaglevGraphBuilder::VisitLdaGlobalInsideTypeof() { // LdaGlobalInsideTypeof <name_index> <slot> static const int kNameOperandIndex = 0; static const int kSlotOperandIndex = 1; compiler::NameRef name = GetRefOperand<Name>(kNameOperandIndex); FeedbackSlot slot = GetSlotOperand(kSlotOperandIndex); compiler::FeedbackSource feedback_source{feedback(), slot}; return BuildLoadGlobal(name, feedback_source, TypeofMode::kInside); } ReduceResult MaglevGraphBuilder::VisitStaGlobal() { // StaGlobal <name_index> <slot> FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& access_feedback = broker()->GetFeedbackForGlobalAccess(feedback_source); if (access_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericGlobalAccess); } const compiler::GlobalAccessFeedback& global_access_feedback = access_feedback.AsGlobalAccess(); RETURN_IF_DONE(TryBuildGlobalStore(global_access_feedback)); ValueNode* value = GetAccumulator(); compiler::NameRef name = GetRefOperand<Name>(0); ValueNode* context = GetContext(); return AddNewNode<StoreGlobal>({context, value}, name, feedback_source); } ReduceResult MaglevGraphBuilder::VisitLdaLookupSlot() { // LdaLookupSlot <name_index> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); SetAccumulator(BuildCallRuntime(Runtime::kLoadLookupSlot, {name}).value()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaLookupContextSlotNoCell() { // LdaLookupContextSlot <name_index> <feedback_slot> <depth> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); ValueNode* slot = GetTaggedIndexConstant(iterator_.GetContextSlotOperand(1)); ValueNode* depth = GetTaggedIndexConstant(iterator_.GetUnsignedImmediateOperand(2)); SetAccumulator(BuildCallBuiltin<Builtin::kLookupContextNoCellTrampoline>( {name, depth, slot})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaLookupContextSlot() { // LdaLookupContextSlot <name_index> <feedback_slot> <depth> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); ValueNode* slot = GetTaggedIndexConstant(iterator_.GetContextSlotOperand(1)); ValueNode* depth = GetTaggedIndexConstant(iterator_.GetUnsignedImmediateOperand(2)); SetAccumulator( BuildCallBuiltin<Builtin::kLookupContextTrampoline>({name, depth, slot})); return ReduceResult::Done(); } bool MaglevGraphBuilder::CheckContextExtensions(size_t depth) { compiler::OptionalScopeInfoRef maybe_scope_info = graph()->TryGetScopeInfo(GetContext()); if (!maybe_scope_info.has_value()) return false; compiler::ScopeInfoRef scope_info = maybe_scope_info.value(); for (uint32_t d = 0; d < depth; d++) { CHECK_NE(scope_info.scope_type(), ScopeType::SCRIPT_SCOPE); CHECK_NE(scope_info.scope_type(), ScopeType::REPL_MODE_SCOPE); if (scope_info.HasContextExtensionSlot() && !broker()->dependencies()->DependOnEmptyContextExtension(scope_info)) { // Using EmptyContextExtension dependency is not possible for this // scope_info, so generate dynamic checks. ValueNode* context = GetContextAtDepth(GetContext(), d); // Only support known contexts so that we can check that there's no // extension at compile time. Otherwise we could end up in a deopt loop // once we do get an extension. compiler::OptionalContextRef context_ref = TryGetConstant<Context>(context); if (!context_ref) return false; compiler::OptionalObjectRef extension_ref = context_ref->get(broker(), Context::EXTENSION_INDEX); // The extension may be concurrently installed while we're checking the // context, in which case it may still be uninitialized. This still // means an extension is about to appear, so we should block this // optimization. if (!extension_ref) return false; if (!extension_ref->IsUndefined()) return false; ValueNode* extension = LoadAndCacheContextSlot(context, Context::EXTENSION_INDEX, kMutable, ContextMode::kNoContextCells); AddNewNodeNoInputConversion<CheckValue>( {extension}, broker()->undefined_value(), DeoptimizeReason::kUnexpectedContextExtension); } CHECK_IMPLIES(!scope_info.HasOuterScopeInfo(), d + 1 == depth); if (scope_info.HasOuterScopeInfo()) { scope_info = scope_info.OuterScopeInfo(broker()); } } return true; } ReduceResult MaglevGraphBuilder::VisitLdaLookupGlobalSlot() { // LdaLookupGlobalSlot <name_index> <feedback_slot> <depth> compiler::NameRef name = GetRefOperand<Name>(0); if (CheckContextExtensions(iterator_.GetUnsignedImmediateOperand(2))) { FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; return BuildLoadGlobal(name, feedback_source, TypeofMode::kNotInside); } else { ValueNode* name_node = GetConstant(name); ValueNode* slot = GetTaggedIndexConstant(iterator_.GetFeedbackSlotOperand(1)); ValueNode* depth = GetTaggedIndexConstant(iterator_.GetUnsignedImmediateOperand(2)); ValueNode* result; if (is_inline()) { ValueNode* vector = GetConstant(feedback()); result = BuildCallBuiltin<Builtin::kLookupGlobalIC>( {name_node, depth, slot, vector}); } else { result = BuildCallBuiltin<Builtin::kLookupGlobalICTrampoline>( {name_node, depth, slot}); } SetAccumulator(result); return ReduceResult::Done(); } } ReduceResult MaglevGraphBuilder::VisitLdaLookupSlotInsideTypeof() { // LdaLookupSlotInsideTypeof <name_index> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); SetAccumulator( BuildCallRuntime(Runtime::kLoadLookupSlotInsideTypeof, {name}).value()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaLookupContextSlotNoCellInsideTypeof() { // LdaLookupContextSlotInsideTypeof <name_index> <context_slot> <depth> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); ValueNode* slot = GetTaggedIndexConstant(iterator_.GetContextSlotOperand(1)); ValueNode* depth = GetTaggedIndexConstant(iterator_.GetUnsignedImmediateOperand(2)); SetAccumulator( BuildCallBuiltin<Builtin::kLookupContextNoCellInsideTypeofTrampoline>( {name, depth, slot})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaLookupContextSlotInsideTypeof() { // LdaLookupContextSlotInsideTypeof <name_index> <context_slot> <depth> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); ValueNode* slot = GetTaggedIndexConstant(iterator_.GetContextSlotOperand(1)); ValueNode* depth = GetTaggedIndexConstant(iterator_.GetUnsignedImmediateOperand(2)); SetAccumulator( BuildCallBuiltin<Builtin::kLookupContextInsideTypeofTrampoline>( {name, depth, slot})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitLdaLookupGlobalSlotInsideTypeof() { // LdaLookupGlobalSlotInsideTypeof <name_index> <feedback_slot> <depth> ValueNode* name = GetConstant(GetRefOperand<Name>(0)); ValueNode* slot = GetTaggedIndexConstant(iterator_.GetFeedbackSlotOperand(1)); ValueNode* depth = GetTaggedIndexConstant(iterator_.GetUnsignedImmediateOperand(2)); ValueNode* result; if (is_inline()) { ValueNode* vector = GetConstant(feedback()); result = BuildCallBuiltin<Builtin::kLookupGlobalICInsideTypeof>( {name, depth, slot, vector}); } else { result = BuildCallBuiltin<Builtin::kLookupGlobalICInsideTypeofTrampoline>( {name, depth, slot}); } SetAccumulator(result); return ReduceResult::Done(); } namespace { Runtime::FunctionId StaLookupSlotFunction(uint8_t sta_lookup_slot_flags) { using Flags = interpreter::StoreLookupSlotFlags; switch (Flags::GetLanguageMode(sta_lookup_slot_flags)) { case LanguageMode::kStrict: return Runtime::kStoreLookupSlot_Strict; case LanguageMode::kSloppy: if (Flags::IsLookupHoistingMode(sta_lookup_slot_flags)) { return Runtime::kStoreLookupSlot_SloppyHoisting; } else { return Runtime::kStoreLookupSlot_Sloppy; } } } } // namespace ReduceResult MaglevGraphBuilder::VisitStaLookupSlot() { // StaLookupSlot <name_index> <flags> ValueNode* value = GetAccumulator(); ValueNode* name = GetConstant(GetRefOperand<Name>(0)); uint32_t flags = GetFlag8Operand(1); EscapeContext(); SetAccumulator( BuildCallRuntime(StaLookupSlotFunction(flags), {name, value}).value()); return ReduceResult::Done(); } void MaglevGraphBuilder::SetKnownValue(ValueNode* node, compiler::ObjectRef ref, NodeType new_node_type) { DCHECK(!node->Is<Constant>()); DCHECK(!node->Is<RootConstant>()); NodeInfo* known_info = GetOrCreateInfoFor(node); // ref type should be compatible with type. DCHECK(NodeTypeIs(StaticTypeForConstant(broker(), ref), new_node_type)); if (ref.IsHeapObject()) { DCHECK(IsInstanceOfNodeType(ref.AsHeapObject().map(broker()), known_info->type(), broker())); } else { DCHECK(!NodeTypeIs(known_info->type(), NodeType::kAnyHeapObject)); } known_info->IntersectType(new_node_type); known_info->alternative().set_checked_value(GetConstant(ref)); } ReduceResult MaglevGraphBuilder::BuildCheckSmi(ValueNode* object, bool elidable) { if (object->StaticTypeIs(broker(), NodeType::kSmi) && elidable) return object; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kSmi))) { return EmitUnconditionalDeopt(DeoptimizeReason::kSmi); } if (EnsureType(object, NodeType::kSmi) && elidable) return object; if constexpr (SmiValuesAre31Bits()) { if (Phi* value_as_phi = object->TryCast<Phi>()) { value_as_phi->SetUseRequires31BitValue(); } } // For non-tagged constants, we may be able to skip the runtime check: every // non-tagged arm of the switch below emits a value-range check, which is // exactly what `Smi::IsValid` proves. For tagged inputs the runtime check // (CheckSmi) is a tag-bit check, and value-equivalence (e.g. via the // checked_value alternative, which may hold a HeapNumber constant) does not // imply Smi tagging. if (object->value_representation() != ValueRepresentation::kTagged) { if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) { if (Smi::IsValid(constant_value.value())) return object; } } switch (object->value_representation()) { case ValueRepresentation::kInt32: if (!SmiValuesAre32Bits()) { AddNewNodeNoInputConversion<CheckInt32IsSmi>({object}); } break; case ValueRepresentation::kUint32: AddNewNodeNoInputConversion<CheckUint32IsSmi>({object}); break; case ValueRepresentation::kFloat64: AddNewNodeNoInputConversion<CheckFloat64IsSmi>({object}); break; case ValueRepresentation::kHoleyFloat64: AddNewNodeNoInputConversion<CheckHoleyFloat64IsSmi>({object}); break; case ValueRepresentation::kTagged: AddNewNodeNoInputConversion<CheckSmi>({object}); break; case ValueRepresentation::kIntPtr: AddNewNodeNoInputConversion<CheckIntPtrIsSmi>({object}); break; case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } return object; } ReduceResult MaglevGraphBuilder::BuildCheckHeapObject(ValueNode* object) { // Check for the empty type first so that we catch the case where // GetType(object) is already empty. NodeType initial_type = GetType(object); if (IsEmptyNodeType(IntersectType(initial_type, NodeType::kAnyHeapObject))) { return EmitUnconditionalDeopt(DeoptimizeReason::kSmi); } if (Phi* phi = object->TryCast<Phi>()) { // Through phi untagging and the default float64->tagged canonicalization, a // Phi could currently be a HeapObject but be retagged as a Smi after phi // representation selection. If we request this Phi has a HeapObject, we // record it so that when we retag it we preserve its HeapObjectness. phi->SetUseRequiresHeapObject(); } if (EnsureType(object, NodeType::kAnyHeapObject)) return ReduceResult::Done(); if (object->Is<Phi>() && NodeTypeCanBe(initial_type, NodeType::kSmi)) { // If {initial_type} contains kSmi, then phi untagging could widen this to a // HeapNumber. Since the `EnsureType(.. kAnyHeapObject)` above just removed // `kSmi` from the type, we need to make sure that still don't forget that // HeapNumber is actually still a possibility. // TODO(dmercadier): this is only a small band-aid: actually, any // GetType(phi) could return Smi when the actual type ends up being // HeapNumber. NodeInfo* info = GetOrCreateInfoFor(object); info->UnionType(NodeType::kHeapNumber); } return AddNewNode<CheckHeapObject>({object}); } ReduceResult MaglevGraphBuilder::BuildCheckSeqOneByteString(ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType( IntersectType(GetType(object), NodeType::kSeqOneByteString))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotASeqOneByteString); } if (EnsureType(object, NodeType::kSeqOneByteString, &known_type)) { return ReduceResult::Done(); } return AddNewNode<CheckSeqOneByteString>({object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckString(ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kString))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotAString); } if (EnsureType(object, NodeType::kString, &known_type)) { return ReduceResult::Done(); } return AddNewNode<CheckString>({object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckStringOrStringWrapper( ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType( IntersectType(GetType(object), NodeType::kStringOrStringWrapper))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotAStringOrStringWrapper); } if (EnsureType(object, NodeType::kStringOrStringWrapper, &known_type)) return ReduceResult::Done(); return AddNewNode<CheckStringOrStringWrapper>( {object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckStringOrOddball(ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType( IntersectType(GetType(object), NodeType::kStringOrOddball))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotAStringOrOddball); } if (EnsureType(object, NodeType::kStringOrOddball, &known_type)) { return ReduceResult::Done(); } return AddNewNode<CheckStringOrOddball>({object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckNumber(ValueNode* object) { // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kNumber))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotANumber); } if (EnsureType(object, NodeType::kNumber)) return ReduceResult::Done(); return AddNewNode<CheckNumber>({object}, Object::Conversion::kToNumber); } ReduceResult MaglevGraphBuilder::BuildCheckSymbol(ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kSymbol))) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotASymbol); } if (EnsureType(object, NodeType::kSymbol, &known_type)) return ReduceResult::Done(); return AddNewNode<CheckSymbol>({object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckJSFunction(ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kJSFunction))) { return EmitUnconditionalDeopt(DeoptimizeReason::kWrongInstanceType); } if (EnsureType(object, NodeType::kJSFunction, &known_type)) return ReduceResult::Done(); return AddNewNode<CheckInstanceType>( {object}, GetCheckType(known_type, object), FIRST_JS_FUNCTION_TYPE, LAST_JS_FUNCTION_TYPE); } ReduceResult MaglevGraphBuilder::BuildCheckJSReceiver(ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kJSReceiver))) { return EmitUnconditionalDeopt(DeoptimizeReason::kWrongInstanceType); } if (EnsureType(object, NodeType::kJSReceiver, &known_type)) return ReduceResult::Done(); return AddNewNode<CheckInstanceType>( {object}, GetCheckType(known_type, object), FIRST_JS_RECEIVER_TYPE, LAST_JS_RECEIVER_TYPE); } ReduceResult MaglevGraphBuilder::BuildCheckJSReceiverOrNullOrUndefined( ValueNode* object) { NodeType known_type; // Check for the empty type first so that we catch the case where // GetType(object) is already empty. if (IsEmptyNodeType(IntersectType(GetType(object), NodeType::kJSReceiverOrNullOrUndefined))) { return EmitUnconditionalDeopt( DeoptimizeReason::kNotAJavaScriptObjectOrNullOrUndefined); } if (EnsureType(object, NodeType::kJSReceiverOrNullOrUndefined, &known_type)) { return ReduceResult::Done(); } return AddNewNode<CheckJSReceiverOrNullOrUndefined>( {object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckMaps( ValueNode* object, base::Vector<const compiler::MapRef> maps, std::optional<ValueNode*> map, bool has_deprecated_map_without_migration_target, bool migration_done_outside) { KnownMapsMerger<base::Vector<const compiler::MapRef>> merger(broker(), zone(), maps); RETURN_IF_DONE(reducer_.TryFoldCheckMaps(object, nullptr, maps, merger)); NodeInfo* known_info = GetOrCreateInfoFor(object); // TODO(v8:7700): Check if the {maps} - {known_maps} size is smaller than // {maps} \intersect {known_maps}, we can emit CheckNotMaps instead. // Emit checks. if (merger.emit_check_with_migration() && !migration_done_outside) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithMigration>( {object}, merger.intersect_set(), GetCheckType(known_info->type(), object))); } else if (has_deprecated_map_without_migration_target && !migration_done_outside) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithMigrationAndDeopt>( {object}, merger.intersect_set(), GetCheckType(known_info->type(), object))); } else if (map) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithAlreadyLoadedMap>( {object, *map}, merger.intersect_set())); } else { RETURN_IF_ABORT( AddNewNode<CheckMaps>({object}, merger.intersect_set(), GetCheckType(known_info->type(), object))); } merger.UpdateKnownNodeAspects(object, known_node_aspects()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildTransitionElementsKindOrCheckMap( ValueNode* heap_object, ValueNode* object_map, const ZoneVector<compiler::MapRef>& transition_sources, compiler::MapRef transition_target) { // TODO(marja): Optimizations based on what we know about the intersection of // known maps and transition sources or transition target. // TransitionElementsKind doesn't happen in cases where we'd need to do // CheckMapsWithMigration instead of CheckMaps. CHECK(!transition_target.is_migration_target()); for (const compiler::MapRef transition_source : transition_sources) { CHECK(!transition_source.is_migration_target()); } NodeInfo* known_info = GetOrCreateInfoFor(heap_object); RETURN_IF_ABORT(AddNewNode<TransitionElementsKindOrCheckMap>( {heap_object, object_map}, transition_sources, transition_target)); // After this operation, heap_object's map is transition_target (or we // deopted). known_info->SetPossibleMaps( PossibleMaps{transition_target}, !transition_target.is_stable(), StaticTypeForMap(transition_target, broker()), broker()); DCHECK(transition_target.IsJSReceiverMap()); if (!transition_target.is_stable()) { known_node_aspects().MarkAnyMapForAnyNodeIsUnstable(); } else { broker()->dependencies()->DependOnStableMap(transition_target); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildCompareMaps( ValueNode* object, ValueNode* object_map, base::Vector<const compiler::MapRef> maps, MaglevSubGraphBuilder* sub_graph, std::optional<MaglevSubGraphBuilder::Label>& if_not_matched, std::optional<int> future_bind_offset) { GetOrCreateInfoFor(object); KnownMapsMerger<base::Vector<const compiler::MapRef>> merger(broker(), zone(), maps); merger.IntersectWithKnownNodeAspects(object, known_node_aspects()); if (merger.intersect_set().is_empty()) { return ReduceResult::DoneWithAbort(); } if (!object->is_tagged() && !object->is_holey_float64()) { // TODO(victorgomes): Implement the holey float64 case. auto heap_number_map = MakeRef(broker(), local_isolate()->factory()->heap_number_map()); if (std::find(maps.begin(), maps.end(), heap_number_map) != maps.end()) { return ReduceResult::Done(); } return ReduceResult::DoneWithAbort(); } // TODO(pthier): Support map packing. DCHECK(!V8_MAP_PACKING_BOOL); // TODO(pthier): Handle map migrations. std::optional<MaglevSubGraphBuilder::Label> map_matched; const compiler::ZoneRefSet<Map>& relevant_maps = merger.intersect_set(); if (relevant_maps.size() > 1) { map_matched.emplace(sub_graph, static_cast<int>(relevant_maps.size())); for (size_t map_index = 1; map_index < relevant_maps.size(); map_index++) { RETURN_IF_ABORT(sub_graph->GotoIfTrue<BranchIfReferenceEqual>( &*map_matched, {object_map, GetConstant(relevant_maps.at(map_index))})); } } if (future_bind_offset) { if_not_matched.emplace( MaglevSubGraphBuilder::LabelForTrackingInterpreterFrameState( sub_graph, 1, *future_bind_offset)); } else { if_not_matched.emplace(sub_graph, 1); } RETURN_IF_ABORT(sub_graph->GotoIfFalse<BranchIfReferenceEqual>( &*if_not_matched, {object_map, GetConstant(relevant_maps.at(0))})); if (map_matched.has_value()) { sub_graph->Goto(&*map_matched); sub_graph->Bind(&*map_matched); } merger.UpdateKnownNodeAspects(object, known_node_aspects()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildTransitionElementsKindAndCompareMaps( ValueNode* heap_object, ValueNode* object_map, const ZoneVector<compiler::MapRef>& transition_sources, compiler::MapRef transition_target, MaglevSubGraphBuilder* sub_graph, std::optional<MaglevSubGraphBuilder::Label>& if_not_matched) { DCHECK(!transition_target.is_migration_target()); NodeInfo* known_info = GetOrCreateInfoFor(heap_object); // TODO(pthier): Calculate and use the intersection of known maps with // (transition_sources union transition_target). ValueNode* new_map; GET_VALUE_OR_ABORT(new_map, AddNewNode<TransitionElementsKind>( {heap_object, object_map}, transition_sources, transition_target)); // TODO(pthier): Support map packing. DCHECK(!V8_MAP_PACKING_BOOL); if_not_matched.emplace(sub_graph, 1); RETURN_IF_ABORT(sub_graph->GotoIfFalse<BranchIfReferenceEqual>( &*if_not_matched, {new_map, GetConstant(transition_target)})); // After the branch, object's map is transition_target. DCHECK(transition_target.IsJSReceiverMap()); known_info->SetPossibleMaps( PossibleMaps{transition_target}, !transition_target.is_stable(), StaticTypeForMap(transition_target, broker()), broker()); if (!transition_target.is_stable()) { known_node_aspects().MarkAnyMapForAnyNodeIsUnstable(); } else { broker()->dependencies()->DependOnStableMap(transition_target); } return ReduceResult::Done(); } namespace { AllocationBlock* GetAllocation(ValueNode* object) { if (object->Is<InlinedAllocation>()) { object = object->Cast<InlinedAllocation>()->input(0).node(); } if (object->Is<AllocationBlock>()) { return object->Cast<AllocationBlock>(); } return nullptr; } } // namespace bool MaglevGraphBuilder::CanElideWriteBarrier(ValueNode* object, ValueNode* value) { if (value->Is<RootConstant>() || value->Is<ConsStringMap>()) return true; if (!IsEmptyNodeType(GetType(value)) && CheckType(value, NodeType::kSmi)) { if constexpr (SmiValuesAre31Bits()) { if (Phi* value_as_phi = value->TryCast<Phi>()) { value_as_phi->SetUseRequires31BitValue(); } } return true; } // No need for a write barrier if both object and value are part of the same // folded young allocation. // Turbolev will do this optimization later after allocation folding. Doing it // here could interfere with turboshaft pretenuring. AllocationBlock* allocation = GetAllocation(object); if (!is_turbolev() && allocation != nullptr && current_allocation_block_ == allocation && allocation->allocation_type() == AllocationType::kYoung && allocation == GetAllocation(value)) { allocation->set_elided_write_barriers_depend_on_type(); return true; } // If tagged and not Smi, we cannot elide write barrier. if (value->is_tagged()) return false; // If its alternative conversion node is Smi, {value} will be converted to // a Smi when tagged. NodeInfo* node_info = GetOrCreateInfoFor(value); if (ValueNode* tagged_alt = node_info->alternative().tagged()) { DCHECK(tagged_alt->is_conversion()); return CheckType(tagged_alt, NodeType::kSmi); } return false; } ReduceResult MaglevGraphBuilder::ConvertForField( ValueNode* value, const vobj::Field& desc, AllocationType allocation_type) { switch (desc.type) { case vobj::FieldType::kTagged: { // Subtle: we don't use `NodeTypeIs(...)` since the predicate must NOT // be true for NodeType::kNone. // TODO(jgruber): NodeType::kNone should never reach here. if (GetType(value) == NodeType::kSmi) { // TODO(jgruber): This is needed because HoleyFloat64ToTagged does not // canonicalize smis by default in GetTaggedValue. We rely on // canonicalization though in TryReduceConstructArrayConstructor. // We should make this more robust. // TODO(454485895): Consider removing this workaround since // HoleyFloat64ToTagged now canonicalizes by default. MaybeReduceResult res = GetSmiValue(value); CHECK(res.IsDoneWithValue()); return res.value(); } if (value->Is<Float64Constant>()) { // Note that NodeType::kSmi MUST go through GetSmiValue for proper // canonicalization. If we see a Float64Constant with type kSmi, it has // passed BuildCheckSmi, i.e. the runtime value is guaranteed to be // convertible to smi (we would have deoptimized otherwise). // // TODO(jgruber): We have to allocate a new object since the // object field could contain a mutable HeapNumber and thus cannot // share instances. However, we could specify such mutable fields // through vobj::Field; and then only allocate a new object here // if needed. return BuildInlinedAllocation(CreateHeapNumber(value), allocation_type); } return GetTaggedValue(value); } case vobj::FieldType::kTrustedPointer: DCHECK(value->Is<TrustedConstant>()); return value; case vobj::FieldType::kInt32: // TODO(jgruber): Add conversions here once needed. DCHECK_EQ(value->properties().value_representation(), ValueRepresentation::kInt32); return value; case vobj::FieldType::kFloat64: return GetFloat64(value); case vobj::FieldType::kNone: UNREACHABLE(); } } void MaglevGraphBuilder::BuildInitializeStore(vobj::Field desc, InlinedAllocation* object, AllocationType allocation_type, ValueNode* value, StoreTaggedMode store_mode) { DCHECK_EQ(value->Is<TrustedConstant>(), desc.type == vobj::FieldType::kTrustedPointer); switch (desc.type) { case vobj::FieldType::kTagged: BuildInitializeStore_Tagged(desc, object, allocation_type, value, store_mode); break; case vobj::FieldType::kTrustedPointer: BuildInitializeStore_TrustedPointer(desc, object, allocation_type, value); break; case vobj::FieldType::kInt32: AddNewNodeNoInputConversion<StoreInt32>({object, value}, desc.offset); break; case vobj::FieldType::kFloat64: AddNewNodeNoInputConversion<StoreFloat64>({object, value}, desc.offset); break; case vobj::FieldType::kNone: UNREACHABLE(); } } void MaglevGraphBuilder::BuildInitializeStore_Tagged( vobj::Field desc, InlinedAllocation* object, AllocationType allocation_type, ValueNode* value, StoreTaggedMode store_mode) { DCHECK_EQ(desc.type, vobj::FieldType::kTagged); // Intercept stores of constant map objects here. if (desc.offset == HeapObject::kMapOffset) { if (auto map = TryGetConstant<Map>(value)) { ReduceResult result = BuildStoreMap(object, map.value(), StoreMap::Kind::kInlinedAllocation); CHECK(!result.IsDoneWithAbort()); return; } } DCHECK(value->is_tagged()); if (InlinedAllocation* inlined_value = value->TryCast<InlinedAllocation>()) { // Add to the escape set. auto escape_deps = graph()->allocations_escape_map().find(object); CHECK(escape_deps != graph()->allocations_escape_map().end()); escape_deps->second.push_back(inlined_value); // Add to the elided set. auto& elided_map = graph()->allocations_elide_map(); auto elided_deps = elided_map.try_emplace(inlined_value, zone()).first; elided_deps->second.push_back(object); inlined_value->AddNonEscapingUses(); } // Since `value` is tagged, BuildStoreTaggedField doesn't need to do // input conversions and won't abort. ReduceResult result = BuildStoreTaggedField(object, value, desc.offset, store_mode); CHECK(!result.IsDoneWithAbort()); } void MaglevGraphBuilder::BuildInitializeStore_TrustedPointer( vobj::Field desc, InlinedAllocation* object, AllocationType allocation_type, ValueNode* value) { DCHECK_EQ(desc.type, vobj::FieldType::kTrustedPointer); DCHECK(value->Is<TrustedConstant>()); DCHECK(value->is_tagged()); // Since `value` is tagged, BuildStoreTaggedField doesn't need to do input // conversions and won't abort. ReduceResult result = BuildStoreTrustedPointerField( object, value, desc.offset, value->Cast<TrustedConstant>()->tag(), StoreTaggedMode::kInitializing); CHECK(!result.IsDoneWithAbort()); } namespace { bool IsEscaping(Graph* graph, InlinedAllocation* alloc) { if (alloc->IsEscaping()) return true; auto it = graph->allocations_elide_map().find(alloc); if (it == graph->allocations_elide_map().end()) return false; for (InlinedAllocation* inner_alloc : it->second) { if (IsEscaping(graph, inner_alloc)) { return true; } } return false; } bool VerifyIsNotEscaping(VirtualObjectList vos, InlinedAllocation* alloc) { for (VirtualObject* vo : vos) { if (vo->allocation() == alloc) continue; bool escaped = false; vo->ForEachSlot([&](ValueNode* nested_value, vobj::Field desc) -> bool { if (escaped) return true; if (!nested_value->Is<InlinedAllocation>()) return true; ValueNode* nested_alloc = nested_value->Cast<InlinedAllocation>(); if (nested_alloc == alloc) { if (vo->allocation()->IsEscaping() || !VerifyIsNotEscaping(vos, vo->allocation())) { escaped = true; } } return true; }); if (escaped) return false; } return true; } } // namespace bool MaglevGraphBuilder::CanTrackObjectChanges(ValueNode* receiver, TrackObjectMode mode) { DCHECK(!receiver->Is<VirtualObject>()); if (!v8_flags.maglev_object_tracking) return false; if (!receiver->Is<InlinedAllocation>()) return false; InlinedAllocation* alloc = receiver->Cast<InlinedAllocation>(); if (mode == TrackObjectMode::kStore) { // If we have two objects A and B, such that A points to B (it contains B in // one of its field), we cannot change B without also changing A, even if // both can be elided. For now, we escape both objects instead. if (graph_->allocations_elide_map().find(alloc) != graph_->allocations_elide_map().end()) { return false; } if (alloc->IsEscaping()) return false; // Ensure object is escaped if we are within a try-catch block. This is // crucial because a deoptimization point inside the catch handler could // re-materialize objects differently, depending on whether the throw // occurred before or after this store. We could potentially relax this // requirement by verifying that no throwable nodes have been emitted since // the try-block started, but for now, err on the side of caution and // always escape. if (IsInsideTryBlock()) return false; } else { DCHECK_EQ(mode, TrackObjectMode::kLoad); if (IsEscaping(graph_, alloc)) return false; } // We don't support loop phis inside VirtualObjects, so any access inside a // loop should escape the object, except for objects that were created since // the last loop header. if (IsInsideLoop()) { if (!is_loop_effect_tracking() || !loop_effects_->allocations.contains(alloc)) { return false; } } // Iterate all live objects to be sure that the allocation is not escaping. SLOW_DCHECK( VerifyIsNotEscaping(current_interpreter_frame_.virtual_objects(), alloc)); return true; } VirtualObject* MaglevGraphBuilder::GetObjectFromAllocation( InlinedAllocation* allocation) { VirtualObject* vobject = allocation->object(); // If it hasn't be snapshotted yet, it is the latest created version of this // object, we don't need to search for it. if (vobject->IsSnapshot()) { vobject = current_interpreter_frame_.virtual_objects().FindAllocatedWith( allocation); } return vobject; } VirtualObject* MaglevGraphBuilder::GetModifiableObjectFromAllocation( InlinedAllocation* allocation) { VirtualObject* vobject = allocation->object(); // If it hasn't be snapshotted yet, it is the latest created version of this // object and we can still modify it, we don't need to copy it. if (vobject->IsSnapshot()) { return DeepCopyVirtualObject( current_interpreter_frame_.virtual_objects().FindAllocatedWith( allocation)); } return vobject; } void MaglevGraphBuilder::TryBuildStoreTaggedFieldToAllocation(ValueNode* object, ValueNode* value, int offset) { if (offset == HeapObject::kMapOffset) return; if (!CanTrackObjectChanges(object, TrackObjectMode::kStore)) return; // This avoids loop in the object graph. if (value->Is<InlinedAllocation>()) return; InlinedAllocation* allocation = object->Cast<InlinedAllocation>(); VirtualObject* vobject = GetModifiableObjectFromAllocation(allocation); CHECK_NOT_NULL(vobject); vobject->set(offset, value); AddNonEscapingUses(allocation, 1); if (v8_flags.trace_maglev_object_tracking) { std::cout << " * Setting value in virtual object " << PrintNodeLabel(vobject) << "[" << offset << "]: " << PrintNode(value) << std::endl; } } ReduceResult MaglevGraphBuilder::BuildStoreTaggedField( ValueNode* object, ValueNode* value, int offset, StoreTaggedMode store_mode, PropertyKey property_key) { // The value may be used to initialize a VO, which can leak to IFS. // It should NOT be a conversion node, UNLESS it's an initializing value. // Initializing values are tagged before allocation, since conversion nodes // may allocate, and are not used to set a VO. DCHECK_IMPLIES(!IsInitializing(store_mode), !value->is_conversion()); if (!IsInitializing(store_mode)) { TryBuildStoreTaggedFieldToAllocation(object, value, offset); } if (CanElideWriteBarrier(object, value)) { return AddNewNode<StoreTaggedFieldNoWriteBarrier>({object, value}, offset, store_mode, property_key); } else { // Detect stores that would create old-to-new references and pretenure the // value. if (v8_flags.maglev_pretenure_store_values) { if (auto alloc = object->TryCast<InlinedAllocation>()) { if (alloc->allocation_block()->allocation_type() == AllocationType::kOld) { alloc->allocation_block()->TryPretenure(value); } } } return AddNewNode<StoreTaggedFieldWithWriteBarrier>( {object, value}, offset, store_mode, NodeTypeCanBe(GetType(value), NodeType::kSmi), property_key); } } ReduceResult MaglevGraphBuilder::BuildStoreTaggedFieldNoWriteBarrier( ValueNode* object, ValueNode* value, int offset, StoreTaggedMode store_mode, PropertyKey property_key) { // The value may be used to initialize a VO, which can leak to IFS. // It should NOT be a conversion node, UNLESS it's an initializing value. // Initializing values are tagged before allocation, since conversion nodes // may allocate, and are not used to set a VO. DCHECK_IMPLIES(!IsInitializing(store_mode), !value->is_conversion()); DCHECK(CanElideWriteBarrier(object, value)); if (!IsInitializing(store_mode)) { TryBuildStoreTaggedFieldToAllocation(object, value, offset); } return AddNewNode<StoreTaggedFieldNoWriteBarrier>({object, value}, offset, store_mode, property_key); } ReduceResult MaglevGraphBuilder::BuildStoreTrustedPointerField( ValueNode* object, ValueNode* value, int offset, IndirectPointerTag tag, StoreTaggedMode store_mode) { #ifdef V8_ENABLE_SANDBOX return AddNewNode<StoreTrustedPointerFieldWithWriteBarrier>( {object, value}, offset, tag, store_mode); #else return BuildStoreTaggedField(object, value, offset, store_mode); #endif // V8_ENABLE_SANDBOX } ReduceResult MaglevGraphBuilder::BuildLoadFixedArrayElement(ValueNode* elements, int index, LoadType type) { // We won't try to reason about the type of the elements array and thus also // cannot end up with an empty type for it. DCHECK(!IsEmptyNodeType(GetType(elements))); if (compiler::OptionalFixedArrayRef fixed_array_ref = TryGetConstant<FixedArray>(elements)) { if (index >= 0 && static_cast<uint32_t>(index) < fixed_array_ref->length()) { compiler::OptionalObjectRef maybe_value = fixed_array_ref->TryGet(broker(), index); if (maybe_value) return GetConstant(*maybe_value); } else { return BuildAbort(AbortReason::kUnreachable); } } if (CanTrackObjectChanges(elements, TrackObjectMode::kLoad)) { VirtualObject* vobject = GetObjectFromAllocation(elements->Cast<InlinedAllocation>()); DCHECK(vobject->map()->IsFixedArrayMap()); ValueNode* length_node = vobject->get(offsetof(FixedArray, length_)); if (auto length = TryGetInt32Constant(length_node)) { if (index >= 0 && index < length.value()) { return vobject->get(FixedArray::OffsetOfElementAt(index)); } else { return BuildAbort(AbortReason::kUnreachable); } } } if (index < 0 || index >= FixedArray::kMaxLength) { return BuildAbort(AbortReason::kUnreachable); } return AddNewNodeNoInputConversion<LoadTaggedField>( {elements}, FixedArray::OffsetOfElementAt(index), type, false, PropertyKey::None()); } ReduceResult MaglevGraphBuilder::BuildLoadFixedArrayElement(ValueNode* elements, ValueNode* index, LoadType type) { if (auto constant = TryGetInt32Constant(index)) { return BuildLoadFixedArrayElement(elements, constant.value(), type); } return AddNewNode<LoadFixedArrayElement>({elements, index}, type); } ReduceResult MaglevGraphBuilder::BuildStoreFixedArrayElement( ValueNode* elements, ValueNode* index, ValueNode* value) { // TODO(victorgomes): Support storing element to a virtual object. If we // modify the elements array, we need to modify the original object to point // to the new elements array. if (CanElideWriteBarrier(elements, value)) { return AddNewNode<StoreFixedArrayElementNoWriteBarrier>( {elements, index, value}); } else { return AddNewNode<StoreFixedArrayElementWithWriteBarrier>( {elements, index, value}); } } ReduceResult MaglevGraphBuilder::BuildLoadFixedDoubleArrayElement( ValueNode* elements, int index) { if (index < 0 || index >= FixedArray::kMaxLength) { return BuildAbort(AbortReason::kUnreachable); } // We won't try to reason about the type of the elements array and thus also // cannot end up with an empty type for it. DCHECK(!IsEmptyNodeType(GetType(elements))); if (CanTrackObjectChanges(elements, TrackObjectMode::kLoad)) { VirtualObject* vobject = GetObjectFromAllocation(elements->Cast<InlinedAllocation>()); std::optional<uint32_t> length = TryGetUint32Constant(vobject->get(FixedArrayBase::kLengthOffset)); if (length.has_value()) { if (static_cast<uint32_t>(index) < length.value()) { return vobject->get(FixedDoubleArray::OffsetOfElementAt(index)); } else { return BuildAbort(AbortReason::kUnreachable); } } } return AddNewNodeNoInputConversion<LoadFixedDoubleArrayElement>( {elements, GetInt32Constant(index)}); } ReduceResult MaglevGraphBuilder::BuildLoadFixedDoubleArrayElement( ValueNode* elements, ValueNode* index) { if (auto constant = TryGetInt32Constant(index)) { return BuildLoadFixedDoubleArrayElement(elements, constant.value()); } return AddNewNode<LoadFixedDoubleArrayElement>({elements, index}); } ReduceResult MaglevGraphBuilder::BuildStoreFixedDoubleArrayElement( ElementsKind elements_kind, ValueNode* elements, ValueNode* index, ValueNode* value) { // TODO(victorgomes): Support storing double element to a virtual object. if (value->value_representation() == ValueRepresentation::kHoleyFloat64) { DCHECK_EQ(elements_kind, HOLEY_DOUBLE_ELEMENTS); return AddNewNode<StoreFixedHoleyDoubleArrayElement>( {elements, index, value}); } DCHECK_EQ(value->value_representation(), ValueRepresentation::kFloat64); return AddNewNode<StoreFixedDoubleArrayElement>({elements, index, value}); } ReduceResult MaglevGraphBuilder::BuildLoadHoleyFixedDoubleArrayElement( ValueNode* elements, ValueNode* index, bool convert_hole) { if (convert_hole) { return AddNewNode<LoadHoleyFixedDoubleArrayElement>({elements, index}); } else { #ifdef V8_ENABLE_UNDEFINED_DOUBLE return AddNewNode< LoadHoleyFixedDoubleArrayElementCheckedNotUndefinedOrHole>( {elements, index}); #else return AddNewNode<LoadHoleyFixedDoubleArrayElementCheckedNotHole>( {elements, index}); #endif // V8_ENABLE_UNDEFINED_DOUBLE } } bool MaglevGraphBuilder::CanTreatHoleAsUndefined( base::Vector<const compiler::MapRef> const& receiver_maps) { // Check if all {receiver_maps} have one of the initial Array.prototype // or Object.prototype objects as their prototype (in any of the current // native contexts, as the global Array protector works isolate-wide). for (compiler::MapRef receiver_map : receiver_maps) { compiler::ObjectRef receiver_prototype = receiver_map.prototype(broker()); if (!receiver_prototype.IsJSObject() || !receiver_prototype.AsJSObject().IsArrayOrObjectPrototype(broker())) { return false; } } // Check if the array prototype chain is intact. return broker()->dependencies()->DependOnNoElementsProtector(); } compiler::OptionalObjectRef MaglevGraphBuilder::TryFoldLoadDictPrototypeConstant( compiler::PropertyAccessInfo const& access_info) { DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL); DCHECK(access_info.IsDictionaryProtoDataConstant()); DCHECK(access_info.holder().has_value()); compiler::OptionalObjectRef constant = access_info.holder()->GetOwnDictionaryProperty( broker(), access_info.dictionary_index(), broker()->dependencies()); if (!constant.has_value()) return {}; for (compiler::MapRef map : access_info.lookup_start_object_maps()) { DirectHandle<Map> map_handle = map.object(); // Non-JSReceivers that passed AccessInfoFactory::ComputePropertyAccessInfo // must have different lookup start map. if (!IsJSReceiverMap(*map_handle)) { // Perform the implicit ToObject for primitives here. // Implemented according to ES6 section 7.3.2 GetV (V, P). Tagged<JSFunction> constructor = Map::GetConstructorFunction( *map_handle, *broker()->target_native_context().object()) .value(); // {constructor.initial_map()} is loaded/stored with acquire-release // semantics for constructors. map = MakeRefAssumeMemoryFence(broker(), constructor->initial_map()); DCHECK(IsJSObjectMap(*map.object())); } broker()->dependencies()->DependOnConstantInDictionaryPrototypeChain( map, access_info.name(), constant.value(), PropertyKind::kData); } return constant; } compiler::OptionalJSObjectRef MaglevGraphBuilder::TryGetConstantDataFieldHolder( compiler::PropertyAccessInfo const& access_info, ValueNode* lookup_start_object) { if (!access_info.IsFastDataConstant()) return {}; if (access_info.holder().has_value()) { return access_info.holder(); } if (compiler::OptionalJSObjectRef c = TryGetConstant<JSObject>(lookup_start_object)) { return c.value(); } return {}; } compiler::OptionalObjectRef MaglevGraphBuilder::TryFoldLoadConstantDataField( compiler::JSObjectRef holder, compiler::PropertyAccessInfo const& access_info) { DCHECK(!access_info.field_representation().IsDouble()); return holder.GetOwnFastConstantDataProperty( broker(), access_info.field_representation(), access_info.field_index(), broker()->dependencies()); } std::optional<Float64> MaglevGraphBuilder::TryFoldLoadConstantDoubleField( compiler::JSObjectRef holder, compiler::PropertyAccessInfo const& access_info) { DCHECK(access_info.field_representation().IsDouble()); return holder.GetOwnFastConstantDoubleProperty( broker(), access_info.field_index(), broker()->dependencies()); } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertyGetterCall( compiler::PropertyAccessInfo const& access_info, ValueNode* receiver, ValueNode* lookup_start_object) { compiler::ObjectRef constant = access_info.constant().value(); if (access_info.IsDictionaryProtoAccessorConstant()) { // For fast mode holders we recorded dependencies in BuildPropertyLoad. for (const compiler::MapRef map : access_info.lookup_start_object_maps()) { broker()->dependencies()->DependOnConstantInDictionaryPrototypeChain( map, access_info.name(), constant, PropertyKind::kAccessor); } } // Introduce the call to the getter function. if (constant.IsJSFunction()) { ConvertReceiverMode receiver_mode = receiver == lookup_start_object ? ConvertReceiverMode::kNotNullOrUndefined : ConvertReceiverMode::kAny; CallArguments args(receiver_mode, {receiver}); return TryReduceCallForConstant(constant.AsJSFunction(), args); } else { // Disable optimizations for super ICs using API getters, so that we get // the correct receiver checks. if (receiver != lookup_start_object) return {}; compiler::FunctionTemplateInfoRef templ = constant.AsFunctionTemplateInfo(); CallArguments args(ConvertReceiverMode::kNotNullOrUndefined, {receiver}); return TryReduceCallForApiFunction(templ, {}, args); } } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertySetterCall( compiler::PropertyAccessInfo const& access_info, ValueNode* receiver, ValueNode* lookup_start_object, ValueNode* value) { // Setting super properties shouldn't end up here. DCHECK_EQ(receiver, lookup_start_object); compiler::ObjectRef constant = access_info.constant().value(); if (constant.IsJSFunction()) { CallArguments args(ConvertReceiverMode::kNotNullOrUndefined, {receiver, value}); RETURN_IF_ABORT(TryReduceCallForConstant(constant.AsJSFunction(), args)); } else { compiler::FunctionTemplateInfoRef templ = constant.AsFunctionTemplateInfo(); CallArguments args(ConvertReceiverMode::kNotNullOrUndefined, {receiver, value}); RETURN_IF_ABORT(TryReduceCallForApiFunction(templ, {}, args)); } // Ignore the return value of the setter call. return ReduceResult::Done(); } namespace { bool AccessInfoGuaranteedConst( compiler::PropertyAccessInfo const& access_info) { if (!access_info.IsFastDataConstant() && !access_info.IsStringLength()) { return false; } // Even if we have a constant load, if the map is not stable, we cannot // guarantee that the load is preserved across side-effecting calls. // TODO(v8:7700): It might be possible to track it as const if we know // that we're still on the main transition tree; and if we add a // dependency on the stable end-maps of the entire tree. for (auto& map : access_info.lookup_start_object_maps()) { if (!map.is_stable()) { return false; } } return true; } LoadType FieldRepresentationToLoadType(Representation repr) { switch (repr.kind()) { case Representation::kSmi: return LoadType::kSmi; case Representation::kHeapObject: return LoadType::kAnyHeapObject; case Representation::kDouble: case Representation::kNone: case Representation::kTagged: case Representation::kWasmValue: case Representation::kNumRepresentations: return LoadType::kUnknown; } } } // namespace ReduceResult MaglevGraphBuilder::BuildLoadField( compiler::PropertyAccessInfo const& access_info, ValueNode* lookup_start_object, compiler::NameRef name) { compiler::OptionalJSObjectRef constant_holder = TryGetConstantDataFieldHolder(access_info, lookup_start_object); if (constant_holder) { if (access_info.field_representation().IsDouble()) { std::optional<Float64> constant = TryFoldLoadConstantDoubleField(constant_holder.value(), access_info); if (constant.has_value()) { return GetFloat64Constant(constant.value()); } } else { compiler::OptionalObjectRef constant = TryFoldLoadConstantDataField(constant_holder.value(), access_info); if (constant.has_value()) { return GetConstant(constant.value()); } } } // Resolve property holder. ValueNode* load_source; if (access_info.holder().has_value()) { load_source = GetConstant(access_info.holder().value()); } else { load_source = lookup_start_object; } FieldIndex field_index = access_info.field_index(); if (!field_index.is_inobject()) { // The field is in the property array, first load it from there. GET_VALUE_OR_ABORT( load_source, BuildLoadTaggedField(load_source, JSReceiver::kPropertiesOrHashOffset)); if (is_turbolev()) { // We record a map hint for Turbolev so that LateEscapeAnalysis knows that // we just loaded a property array, which means that it can't alias with // anything that isn't a property array itself. // TODO(dmercadier): also record from which object this property array // came from, since 2 property array maps for objects with different maps // cannot alias. RETURN_IF_ABORT(AddNewNode<AssumeMap>( {load_source}, compiler::ZoneRefSet<Map>(broker()->property_array_map()))); } } // Do the load. if (field_index.is_double()) { ValueNode* heap_number; GET_VALUE_OR_ABORT(heap_number, AddNewNode<LoadTaggedField>( {load_source}, field_index.offset(), LoadType::kHeapNumber, false, PropertyKey::None())); return AddNewNode<LoadFloat64>( {heap_number}, static_cast<int>(offsetof(HeapNumber, value_))); } // TODO(dmercadier): can we use `access_info.type()` to get a more precise // type than what `access_info.field_representation()` gives us? LoadType type = FieldRepresentationToLoadType(access_info.field_representation()); ValueNode* value; GET_VALUE_OR_ABORT(value, BuildLoadTaggedField( load_source, field_index.offset(), type, AccessInfoGuaranteedConst(access_info), name)); // Insert stable field information if present. NodeInfo* known_info = GetOrCreateInfoFor(value); if (access_info.field_representation().IsSmi()) { known_info->IntersectType(NodeType::kSmi); } else if (access_info.field_representation().IsHeapObject()) { if (access_info.field_map().has_value() && access_info.field_map().value().is_stable()) { DCHECK(access_info.field_map().value().IsJSReceiverMap()); auto map = access_info.field_map().value(); known_info->SetPossibleMaps(PossibleMaps{map}, false, StaticTypeForMap(map, broker()), broker()); broker()->dependencies()->DependOnStableMap(map); } else { known_info->IntersectType(NodeType::kAnyHeapObject); } } return value; } ValueNode* MaglevGraphBuilder::BuildLoadFixedArrayLength( ValueNode* fixed_array) { // We won't try to reason about the type of the FixedArray and thus also // cannot end up with an empty type for it. DCHECK(!IsEmptyNodeType(GetType(fixed_array))); ValueNode* length; GET_VALUE(length, BuildLoadTaggedField(fixed_array, offsetof(FixedArray, length_), LoadType::kSmi)); return length; } ReduceResult MaglevGraphBuilder::BuildLoadJSArrayLength(ValueNode* js_array, LoadType length_type) { // TODO(leszeks): JSArray.length is known to be non-constant, don't bother // searching the constant values. MaybeReduceResult known_length = TryReuseKnownPropertyLoad(js_array, broker()->length_string()); if (known_length.IsDone()) { DCHECK(known_length.IsDoneWithValue()); return known_length.value(); } ValueNode* length; GET_VALUE_OR_ABORT(length, BuildLoadTaggedField( js_array, JSArray::kLengthOffset, length_type, false, broker()->length_string())); RecordKnownProperty(js_array, broker()->length_string(), length, false, compiler::AccessMode::kLoad); return length; } ReduceResult MaglevGraphBuilder::BuildLoadJSDataViewByteLength( ValueNode* js_data_view) { // Note: We can't use broker()->byte_length_string() here, because it could // conflict with redefinitions of the ArrayBufferView byteLength property. if (ValueNode* byte_length = known_node_aspects().TryFindLoadedConstantProperty( js_data_view, PropertyKey::ArrayBufferViewByteLength())) { return byte_length; } ValueNode* result; GET_VALUE_OR_ABORT(result, AddNewNode<LoadDataViewByteLength>({js_data_view})); RecordKnownProperty(js_data_view, PropertyKey::ArrayBufferViewByteLength(), result, true, compiler::AccessMode::kLoad); return result; } ReduceResult MaglevGraphBuilder::BuildLoadJSDataViewDataPointer( ValueNode* js_data_view) { if (ValueNode* backing_store = known_node_aspects().TryFindLoadedConstantProperty( js_data_view, PropertyKey::ArrayBufferViewDataPointer())) { return backing_store; } ValueNode* result; GET_VALUE_OR_ABORT(result, AddNewNode<LoadDataViewDataPointer>({js_data_view})); RecordKnownProperty(js_data_view, PropertyKey::ArrayBufferViewDataPointer(), result, true, compiler::AccessMode::kLoad); return result; } ReduceResult MaglevGraphBuilder::BuildLoadJSFunctionFeedbackCell( ValueNode* closure) { DCHECK(NodeTypeIs(GetType(closure), NodeType::kJSFunction)); if (auto constant = TryGetConstant<JSFunction>(closure)) { return GetConstant(constant->raw_feedback_cell(broker())); } if (auto fast_closure = closure->TryCast<FastCreateClosure>()) { return GetConstant(fast_closure->feedback_cell()); } if (auto slow_closure = closure->TryCast<CreateClosure>()) { return GetConstant(slow_closure->feedback_cell()); } return BuildLoadTaggedField(closure, JSFunction::kFeedbackCellOffset); } ReduceResult MaglevGraphBuilder::BuildLoadJSFunctionContext( ValueNode* closure) { DCHECK(NodeTypeIs(GetType(closure), NodeType::kJSFunction)); if (auto constant = TryGetConstant<JSFunction>(closure)) { return GetConstant(constant->context(broker())); } if (auto fast_closure = closure->TryCast<FastCreateClosure>()) { return fast_closure->ContextInput().node(); } if (auto slow_closure = closure->TryCast<CreateClosure>()) { return slow_closure->ContextInput().node(); } return BuildLoadTaggedField(closure, JSFunction::kContextOffset, LoadType::kContext); } ReduceResult MaglevGraphBuilder::BuildStoreMap(ValueNode* object, compiler::MapRef map, StoreMap::Kind kind) { RETURN_IF_ABORT(AddNewNode<StoreMap>({object}, map, kind)); NodeType object_type = StaticTypeForMap(map, broker()); NodeInfo* node_info = GetOrCreateInfoFor(object); if (map.is_stable()) { node_info->SetPossibleMaps(PossibleMaps{map}, false, object_type, broker()); broker()->dependencies()->DependOnStableMap(map); } else { node_info->SetPossibleMaps(PossibleMaps{map}, true, object_type, broker()); known_node_aspects().MarkAnyMapForAnyNodeIsUnstable(); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildExtendPropertiesBackingStore( compiler::MapRef map, ValueNode* receiver, ValueNode* property_array) { int length = map.NextFreePropertyIndex() - map.GetInObjectProperties(); // Under normal circumstances, NextFreePropertyIndex() will always be larger // than GetInObjectProperties(). However, an attacker able to corrupt heap // memory can break this invariant, in which case we'll get confused here, // potentially causing a sandbox violation. This CHECK defends against that. SBXCHECK_GE(length, 0); return AddNewNode<ExtendPropertiesBackingStore>({property_array, receiver}, map, length); } MaybeReduceResult MaglevGraphBuilder::TryBuildStoreField( compiler::PropertyAccessInfo const& access_info, ValueNode* receiver, compiler::AccessMode access_mode, compiler::NameRef name) { FieldIndex field_index = access_info.field_index(); Representation field_representation = access_info.field_representation(); compiler::OptionalMapRef original_map; if (access_info.HasTransitionMap()) { compiler::MapRef transition = access_info.transition_map().value(); auto back_pointer = transition.GetBackPointer(broker()); if (!back_pointer.has_value()) { return ReduceResult::Fail(); } original_map = back_pointer->AsMap(); if (original_map->UnusedPropertyFields() == 0) { DCHECK(!field_index.is_inobject()); } if (!field_index.is_inobject()) { // If slack tracking ends after this compilation started but before it's // finished, then {original_map} could be out-of-sync with {transition}. // In particular, its UnusedPropertyFields could be non-zero, which would // lead us to not extend the property backing store, while the underlying // Map has actually zero UnusedPropertyFields. Thus, we install a // dependency on {orininal_map} now, so that if such a situation happens, // we'll throw away the code. broker()->dependencies()->DependOnNoSlackTrackingChange(*original_map); } } else if (access_info.IsFastDataConstant() && access_mode == compiler::AccessMode::kStore) { return EmitUnconditionalDeopt(DeoptimizeReason::kStoreToConstant); } ValueNode* value = GetAccumulator(); if (IsEmptyNodeType(GetType(value))) { return EmitUnconditionalDeopt(DeoptimizeReason::kWrongValue); } if (field_representation.IsSmi()) { RETURN_IF_ABORT(GetAccumulatorSmi()); } else { if (field_representation.IsHeapObject()) { // Emit a map check for the field type, if needed, otherwise just a // HeapObject check. if (access_info.field_map().has_value()) { RETURN_IF_ABORT(BuildCheckMaps( value, base::VectorOf({access_info.field_map().value()}))); } else { RETURN_IF_ABORT(BuildCheckHeapObject(value)); } } } ValueNode* store_target; if (field_index.is_inobject()) { store_target = receiver; } else { // The field is in the property array, first load it from there. GET_VALUE_OR_ABORT( store_target, BuildLoadTaggedField(receiver, JSReceiver::kPropertiesOrHashOffset)); if (original_map && original_map->UnusedPropertyFields() == 0) { GET_VALUE_OR_ABORT(store_target, BuildExtendPropertiesBackingStore( *original_map, receiver, store_target)); } } if (field_representation.IsDouble()) { if (access_info.HasTransitionMap()) { // Allocate the mutable double box owned by the field. ValueNode* heapnumber_value; GET_VALUE_OR_ABORT(heapnumber_value, AddNewNode<Float64ToHeapNumberForField>({value})); RETURN_IF_ABORT(BuildStoreTaggedField( store_target, heapnumber_value, field_index.offset(), StoreTaggedMode::kTransitioning, name)); return BuildStoreMap(receiver, access_info.transition_map().value(), StoreMap::Kind::kTransitioning); } else { ValueNode* heap_number; GET_VALUE_OR_ABORT( heap_number, AddNewNode<LoadTaggedField>( {store_target}, field_index.offset(), LoadType::kHeapNumber, false, PropertyKey::None())); return AddNewNode<StoreFloat64>( {heap_number, value}, static_cast<int>(offsetof(HeapNumber, value_))); } } StoreTaggedMode store_mode = access_info.HasTransitionMap() ? StoreTaggedMode::kTransitioning : StoreTaggedMode::kDefault; if (field_representation.IsSmi()) { RETURN_IF_ABORT(BuildStoreTaggedFieldNoWriteBarrier( store_target, value, field_index.offset(), store_mode, name)); } else { DCHECK(field_representation.IsHeapObject() || field_representation.IsTagged()); RETURN_IF_ABORT(BuildStoreTaggedField( store_target, value, field_index.offset(), store_mode, name)); } if (access_info.HasTransitionMap()) { RETURN_IF_ABORT(BuildStoreMap(receiver, access_info.transition_map().value(), StoreMap::Kind::kTransitioning)); } return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertyLoad( ValueNode* receiver, ValueNode* lookup_start_object, compiler::NameRef name, compiler::PropertyAccessInfo const& access_info) { if (access_info.holder().has_value() && !access_info.HasDictionaryHolder()) { broker()->dependencies()->DependOnStablePrototypeChains( access_info.lookup_start_object_maps(), kStartAtPrototype, access_info.holder().value()); } switch (access_info.kind()) { case compiler::PropertyAccessInfo::kInvalid: UNREACHABLE(); case compiler::PropertyAccessInfo::kNotFound: return GetRootConstant(RootIndex::kUndefinedValue); case compiler::PropertyAccessInfo::kDataField: case compiler::PropertyAccessInfo::kFastDataConstant: { ValueNode* result; GET_VALUE_OR_ABORT( result, BuildLoadField(access_info, lookup_start_object, name)); RecordKnownProperty(lookup_start_object, name, result, AccessInfoGuaranteedConst(access_info), compiler::AccessMode::kLoad); return result; } case compiler::PropertyAccessInfo::kDictionaryProtoDataConstant: { compiler::OptionalObjectRef constant = TryFoldLoadDictPrototypeConstant(access_info); if (!constant.has_value()) return {}; return GetConstant(constant.value()); } case compiler::PropertyAccessInfo::kFastAccessorConstant: case compiler::PropertyAccessInfo::kDictionaryProtoAccessorConstant: return TryBuildPropertyGetterCall(access_info, receiver, lookup_start_object); case compiler::PropertyAccessInfo::kModuleExport: { ValueNode* cell = GetConstant(access_info.constant().value().AsCell()); return BuildLoadTaggedField(cell, Cell::kValueOffset, LoadType::kUnknown, false, name); } case compiler::PropertyAccessInfo::kStringLength: { DCHECK_EQ(receiver, lookup_start_object); ValueNode* result; GET_VALUE_OR_ABORT(result, BuildLoadStringLength(receiver)); RecordKnownProperty(lookup_start_object, name, result, AccessInfoGuaranteedConst(access_info), compiler::AccessMode::kLoad); return result; } case compiler::PropertyAccessInfo::kStringWrapperLength: { // TODO(dmercadier): update KnownNodeInfo. ValueNode* string; GET_VALUE_OR_ABORT( string, BuildLoadTaggedField(lookup_start_object, JSPrimitiveWrapper::kValueOffset)); return AddNewNodeNoInputConversion<StringLength>({string}); } case compiler::PropertyAccessInfo::kTypedArrayLength: { CHECK(!IsRabGsabTypedArrayElementsKind(access_info.elements_kind())); if (receiver != lookup_start_object) { // We're accessing the TypedArray length via a prototype (a TypedArray // object in the prototype chain, objects below it not having a "length" // property, reading via super.length). That will throw a TypeError. // This should never occur in any realistic code, so we can deopt here // instead of implementing special handling for it. return EmitUnconditionalDeopt(DeoptimizeReason::kWrongMap); } if (!broker()->dependencies()->DependOnArrayBufferDetachingProtector()) { RETURN_IF_ABORT(AddNewNode<CheckTypedArrayValid>( {lookup_start_object}, TypedArrayAccessMode::kRead)); } return BuildLoadTypedArrayLength(lookup_start_object, access_info.elements_kind()); } } } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertyStore( ValueNode* receiver, ValueNode* lookup_start_object, compiler::NameRef name, compiler::PropertyAccessInfo const& access_info, compiler::AccessMode access_mode) { if (access_info.holder().has_value()) { broker()->dependencies()->DependOnStablePrototypeChains( access_info.lookup_start_object_maps(), kStartAtPrototype, access_info.holder().value()); } switch (access_info.kind()) { case compiler::PropertyAccessInfo::kFastAccessorConstant: { return TryBuildPropertySetterCall(access_info, receiver, lookup_start_object, GetAccumulator()); } case compiler::PropertyAccessInfo::kDataField: case compiler::PropertyAccessInfo::kFastDataConstant: { MaybeReduceResult res = TryBuildStoreField(access_info, receiver, access_mode, name); if (res.IsDone()) { RecordKnownProperty( receiver, name, current_interpreter_frame_.accumulator(), AccessInfoGuaranteedConst(access_info), access_mode); return res; } return {}; } case compiler::PropertyAccessInfo::kInvalid: case compiler::PropertyAccessInfo::kNotFound: case compiler::PropertyAccessInfo::kDictionaryProtoDataConstant: case compiler::PropertyAccessInfo::kDictionaryProtoAccessorConstant: case compiler::PropertyAccessInfo::kModuleExport: case compiler::PropertyAccessInfo::kStringLength: case compiler::PropertyAccessInfo::kStringWrapperLength: case compiler::PropertyAccessInfo::kTypedArrayLength: UNREACHABLE(); } } MaybeReduceResult MaglevGraphBuilder::TryBuildPropertyAccess( ValueNode* receiver, ValueNode* lookup_start_object, compiler::NameRef name, compiler::PropertyAccessInfo const& access_info, compiler::AccessMode access_mode) { switch (access_mode) { case compiler::AccessMode::kLoad: return TryBuildPropertyLoad(receiver, lookup_start_object, name, access_info); case compiler::AccessMode::kStore: case compiler::AccessMode::kStoreInLiteral: case compiler::AccessMode::kDefine: DCHECK_EQ(receiver, lookup_start_object); return TryBuildPropertyStore(receiver, lookup_start_object, name, access_info, access_mode); case compiler::AccessMode::kHas: // TODO(victorgomes): BuildPropertyTest. return {}; } } template <typename GenericAccessFunc> MaybeReduceResult MaglevGraphBuilder::TryBuildNamedAccess( ValueNode* receiver, ValueNode* lookup_start_object, compiler::NamedAccessFeedback const& feedback, compiler::FeedbackSource const& feedback_source, compiler::AccessMode access_mode, GenericAccessFunc&& build_generic_access) { compiler::ZoneRefSet<Map> inferred_maps; bool has_deprecated_map_without_migration_target = false; if (compiler::OptionalHeapObjectRef c = TryGetConstant<HeapObject>(lookup_start_object)) { if (c.value().IsTheHole()) return {}; compiler::MapRef constant_map = c.value().map(broker()); if (c.value().IsJSFunction() && feedback.name().equals(broker()->prototype_string())) { compiler::JSFunctionRef function = c.value().AsJSFunction(); if (!constant_map.has_prototype_slot() || !function.has_instance_prototype(broker()) || function.PrototypeRequiresRuntimeLookup(broker()) || access_mode != compiler::AccessMode::kLoad) { return {}; } compiler::HeapObjectRef prototype = broker()->dependencies()->DependOnPrototypeProperty(function); return GetConstant(prototype); } inferred_maps = compiler::ZoneRefSet<Map>(constant_map); } else if (feedback.maps().empty()) { // The IC is megamorphic. // We can't do megamorphic loads for lookups where the lookup start isn't // the receiver (e.g. load from super). if (receiver != lookup_start_object) return {}; // Use known possible maps if we have any. if (auto possible_maps = known_node_aspects().TryGetPossibleMaps(lookup_start_object)) { inferred_maps = *possible_maps; } else { // If we have no known maps, make the access megamorphic. switch (access_mode) { case compiler::AccessMode::kLoad: return BuildCallBuiltinWithTaggedInputs<Builtin::kLoadIC_Megamorphic>( {receiver, GetConstant(feedback.name())}, feedback_source); case compiler::AccessMode::kStore: return BuildCallBuiltinWithTaggedInputs< Builtin::kStoreIC_Megamorphic>( {receiver, GetConstant(feedback.name()), GetAccumulator()}, feedback_source); case compiler::AccessMode::kDefine: return {}; case compiler::AccessMode::kHas: case compiler::AccessMode::kStoreInLiteral: UNREACHABLE(); } } } else { // TODO(leszeks): This is doing duplicate work with BuildCheckMaps, // consider passing the merger into there. KnownMapsMerger<ZoneVector<compiler::MapRef>> merger(broker(), zone(), feedback.maps()); merger.IntersectWithKnownNodeAspects(lookup_start_object, known_node_aspects()); inferred_maps = merger.intersect_set(); has_deprecated_map_without_migration_target = feedback.has_deprecated_map_without_migration_target(); } if (inferred_maps.is_empty()) { return EmitUnconditionalDeopt(DeoptimizeReason::kWrongMap); } ZoneVector<compiler::PropertyAccessInfo> access_infos(zone()); ZoneVector<compiler::PropertyAccessInfo> access_infos_for_feedback(zone()); for (compiler::MapRef map : inferred_maps) { if (map.is_deprecated()) continue; // TODO(v8:12547): Support writing to objects in shared space, which // need a write barrier that calls Object::Share to ensure the RHS is // shared. if (InstanceTypeChecker::IsAlwaysSharedSpaceJSObject(map.instance_type()) && access_mode == compiler::AccessMode::kStore) { return {}; } compiler::PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo(map, feedback.name(), access_mode); access_infos_for_feedback.push_back(access_info); } compiler::AccessInfoFactory access_info_factory(broker(), zone()); if (!access_info_factory.FinalizePropertyAccessInfos( access_infos_for_feedback, access_mode, &access_infos)) { return {}; } // Check for monomorphic case. if (access_infos.size() == 1) { compiler::PropertyAccessInfo const& access_info = access_infos.front(); base::Vector<const compiler::MapRef> maps = base::VectorOf(access_info.lookup_start_object_maps()); if (HasOnlyStringMaps(maps)) { // Check for string maps before checking if we need to do an access // check. Primitive strings always get the prototype from the native // context they're operated on, so they don't need the access check. if (v8_flags.specialize_code_for_one_byte_seq_strings && base::all_of(maps, [](compiler::MapRef map) { return map.IsSeqStringMap() && map.IsOneByteStringMap(); })) { RETURN_IF_ABORT(BuildCheckSeqOneByteString(lookup_start_object)); } else { RETURN_IF_ABORT(BuildCheckString(lookup_start_object)); } } else if (HasOnlyNumberMaps(maps)) { RETURN_IF_ABORT(BuildCheckNumber(lookup_start_object)); } else { RETURN_IF_ABORT( BuildCheckMaps(lookup_start_object, maps, {}, has_deprecated_map_without_migration_target)); } // Generate the actual property return TryBuildPropertyAccess(receiver, lookup_start_object, feedback.name(), access_info, access_mode); } else { // TODO(victorgomes): Unify control flow logic with // TryBuildPolymorphicElementAccess. return TryBuildPolymorphicPropertyAccess( receiver, lookup_start_object, feedback, access_mode, access_infos, build_generic_access); } } ReduceResult MaglevGraphBuilder::GetInt32ElementIndex(ValueNode* object) { object->MaybeRecordUseReprHint(UseRepresentation::kInt32); switch (object->properties().value_representation()) { case ValueRepresentation::kIntPtr: return AddNewNodeNoInputConversion<CheckedIntPtrToInt32>({object}); case ValueRepresentation::kTagged: NodeType old_type; if (SmiConstant* constant = object->TryCast<SmiConstant>()) { return GetInt32Constant(constant->value().value()); } else if (CheckType(object, NodeType::kSmi, &old_type)) { auto& alternative = GetOrCreateInfoFor(object)->alternative(); bool bailout = false; ValueNode* value = alternative.get_or_set_int32([&]() -> ValueNode* { ReduceResult result = BuildSmiUntag(object); if (result.IsDoneWithAbort()) { bailout = true; return nullptr; } return result.value(); }); if (bailout) { return ReduceResult::DoneWithAbort(); } return value; } else { // TODO(leszeks): Cache this knowledge/converted value somehow on // the node info. return AddNewNodeNoInputConversion<CheckedObjectToIndex>( {object}, GetCheckType(old_type, object)); } case ValueRepresentation::kInt32: // Already good. return object; case ValueRepresentation::kUint32: case ValueRepresentation::kFloat64: case ValueRepresentation::kHoleyFloat64: return GetInt32(object); case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } } // TODO(victorgomes): Consider caching the values and adding an // uint32_alternative in node_info. ReduceResult MaglevGraphBuilder::GetUint32ElementIndex(ValueNode* object) { // Don't record a Uint32 Phi use here, since the tagged path goes via // GetInt32ElementIndex, making this an Int32 Phi use. switch (object->properties().value_representation()) { case ValueRepresentation::kTagged: { // TODO(victorgomes): Consider creating a CheckedObjectToUnsignedIndex. if (SmiConstant* constant = object->TryCast<SmiConstant>()) { int32_t value = constant->value().value(); if (value < 0) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotUint32); } return GetUint32Constant(value); } ValueNode* index; GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(object)); return AddNewNodeNoInputConversion<CheckedInt32ToUint32>({index}); } case ValueRepresentation::kInt32: if (auto constant = TryGetInt32Constant(object)) { if (*constant < 0) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotUint32); } return GetUint32Constant(*constant); } return AddNewNodeNoInputConversion<CheckedInt32ToUint32>({object}); case ValueRepresentation::kUint32: return object; case ValueRepresentation::kFloat64: if (auto constant = TryGetFloat64OrHoleyFloat64Constant( UseRepresentation::kFloat64, object, TaggedToFloat64ConversionType::kOnlyNumber)) { uint32_t uint32_value; if (!DoubleToUint32IfEqualToSelf(constant->get_scalar(), &uint32_value)) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotUint32); } if (Smi::IsValid(uint32_value)) { return GetUint32Constant(uint32_value); } } [[fallthrough]]; case ValueRepresentation::kHoleyFloat64: case ValueRepresentation::kIntPtr: case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } } MaybeReduceResult MaglevGraphBuilder::TryBuildElementAccessOnString( ValueNode* object, ValueNode* index_object, const compiler::ElementAccessFeedback& access_feedback, compiler::KeyedAccessMode const& keyed_mode) { // Strings are immutable and `in` cannot be used on strings if (keyed_mode.access_mode() != compiler::AccessMode::kLoad) { return {}; } RETURN_IF_DONE(TryReduceConstantStringAt(object, index_object, StringAtOOBMode::kElement)); // Ensure that {object} is actually a String. bool is_one_byte_seq_string = v8_flags.specialize_code_for_one_byte_seq_strings && base::all_of(access_feedback.transition_groups(), [](const auto& group) { return base::all_of(group, [](compiler::MapRef map) { return map.IsSeqStringMap() && map.IsOneByteStringMap(); }); }); if (is_one_byte_seq_string) { RETURN_IF_ABORT(BuildCheckSeqOneByteString(object)); } else { RETURN_IF_ABORT(BuildCheckString(object)); } ValueNode* length; GET_VALUE_OR_ABORT(length, BuildLoadStringLength(object)); ValueNode* index; GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(index_object)); auto emit_load = [&]() -> ReduceResult { if (is_one_byte_seq_string) { return AddNewNode<SeqOneByteStringAt>({object, index}); } else { return AddNewNode<StringAt>({object, index}); } }; if (LoadModeHandlesOOB(keyed_mode.load_mode()) && broker()->dependencies()->DependOnNoElementsProtector()) { ValueNode* positive_index; GET_VALUE_OR_ABORT(positive_index, GetUint32ElementIndex(index)); ValueNode* uint32_length; GET_VALUE_OR_ABORT(uint32_length, AddNewNode<UnsafeInt32ToUint32>({length})); return Select( [&](BranchBuilder& builder) { return BuildBranchIfUint32Compare(builder, Operation::kLessThan, positive_index, uint32_length); }, emit_load, [&] { return GetRootConstant(RootIndex::kUndefinedValue); }); } else { RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, length, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); return emit_load(); } } namespace { bool CheckConditionIn32(int32_t lhs, int32_t rhs, AssertCondition condition) { switch (condition) { case AssertCondition::kEqual: return lhs == rhs; case AssertCondition::kNotEqual: return lhs != rhs; case AssertCondition::kLessThan: return lhs < rhs; case AssertCondition::kLessThanEqual: return lhs <= rhs; case AssertCondition::kGreaterThan: return lhs > rhs; case AssertCondition::kGreaterThanEqual: return lhs >= rhs; case AssertCondition::kUnsignedLessThan: return static_cast<uint32_t>(lhs) < static_cast<uint32_t>(rhs); case AssertCondition::kUnsignedLessThanEqual: return static_cast<uint32_t>(lhs) <= static_cast<uint32_t>(rhs); case AssertCondition::kUnsignedGreaterThan: return static_cast<uint32_t>(lhs) > static_cast<uint32_t>(rhs); case AssertCondition::kUnsignedGreaterThanEqual: return static_cast<uint32_t>(lhs) >= static_cast<uint32_t>(rhs); } } bool CompareInt32(int32_t lhs, int32_t rhs, Operation operation) { switch (operation) { case Operation::kEqual: case Operation::kStrictEqual: return lhs == rhs; case Operation::kLessThan: return lhs < rhs; case Operation::kLessThanOrEqual: return lhs <= rhs; case Operation::kGreaterThan: return lhs > rhs; case Operation::kGreaterThanOrEqual: return lhs >= rhs; default: UNREACHABLE(); } } bool CompareUint32(uint32_t lhs, uint32_t rhs, Operation operation) { switch (operation) { case Operation::kEqual: case Operation::kStrictEqual: return lhs == rhs; case Operation::kLessThan: return lhs < rhs; case Operation::kLessThanOrEqual: return lhs <= rhs; case Operation::kGreaterThan: return lhs > rhs; case Operation::kGreaterThanOrEqual: return lhs >= rhs; default: UNREACHABLE(); } } } // namespace ReduceResult MaglevGraphBuilder::TryBuildCheckInt32Condition( ValueNode* lhs, ValueNode* rhs, AssertCondition condition, DeoptimizeReason reason) { auto lhs_const = TryGetInt32Constant(lhs); if (lhs_const) { auto rhs_const = TryGetInt32Constant(rhs); if (rhs_const) { if (CheckConditionIn32(lhs_const.value(), rhs_const.value(), condition)) { return ReduceResult::Done(); } return EmitUnconditionalDeopt(reason); } } return AddNewNode<CheckInt32Condition>({lhs, rhs}, condition, reason); } ReduceResult MaglevGraphBuilder::BuildLoadElements( ValueNode* object, std::optional<ElementsKind> kind) { ValueNode* known_elements = known_node_aspects().TryFindLoadedProperty( object, PropertyKey::Elements()); if (known_elements) { TRACE(" * Reusing non-constant [Elements] " << PrintNodeLabel(known_elements) << ": " << PrintNode(known_elements)); return known_elements; } DCHECK_EQ(JSObject::kElementsOffset, JSArray::kElementsOffset); ValueNode* elements; GET_VALUE_OR_ABORT(elements, BuildLoadTaggedField(object, JSObject::kElementsOffset)); RecordKnownProperty(object, PropertyKey::Elements(), elements, false, compiler::AccessMode::kLoad); if (is_turbolev() && kind.has_value()) { // We record a map hint for Turbolev so that LateEscapeAnalysis knows that // we just loaded a FixedArray array, which means that it can't alias with // anything that isn't a property array itself. // TODO(dmercadier): also record from which object this elements array came // from, since 2 elements arrays for objects with different maps cannot // alias. RETURN_IF_ABORT(AddNewNode<AssumeMap>( {elements}, compiler::ZoneRefSet<Map>(IsDoubleElementsKind(kind.value()) ? broker()->fixed_double_array_map() : broker()->fixed_array_map()))); } return elements; } ReduceResult MaglevGraphBuilder::BuildLoadTypedArrayLength( ValueNode* object, ElementsKind elements_kind) { DCHECK(IsTypedArrayOrRabGsabTypedArrayElementsKind(elements_kind)); bool is_variable_length = IsRabGsabTypedArrayElementsKind(elements_kind); if (!is_variable_length) { if (auto const_object = TryGetConstant<JSTypedArray>(object)) { auto const_typed_array = const_object.value(); if (!const_typed_array.is_on_heap() && !IsRabGsabTypedArrayElementsKind( const_typed_array.elements_kind(broker()))) { size_t length = const_typed_array.length(broker()); static_assert(ArrayBuffer::kMaxByteLength <= std::numeric_limits<intptr_t>::max()); return GetIntPtrConstant(static_cast<intptr_t>(length)); } } // Note: We can't use broker()->length_string() here, because it could // conflict with redefinitions of the TypedArray length property. if (ValueNode* length = known_node_aspects().TryFindLoadedConstantProperty( object, PropertyKey::TypedArrayLength())) { return length; } } ValueNode* result; GET_VALUE_OR_ABORT(result, AddNewNode<LoadTypedArrayLength>({object}, elements_kind)); if (!is_variable_length) { RecordKnownProperty(object, PropertyKey::TypedArrayLength(), result, true, compiler::AccessMode::kLoad); } return result; } ReduceResult MaglevGraphBuilder::BuildLoadTypedArrayElement( ValueNode* object, ValueNode* index, ElementsKind elements_kind) { #define BUILD_AND_RETURN_LOAD_TYPED_ARRAY(Type) \ return AddNewNode<Load##Type##TypedArrayElement>({object, index}, \ elements_kind); switch (elements_kind) { case INT8_ELEMENTS: case INT16_ELEMENTS: case INT32_ELEMENTS: BUILD_AND_RETURN_LOAD_TYPED_ARRAY(SignedInt); case UINT8_CLAMPED_ELEMENTS: case UINT8_ELEMENTS: case UINT16_ELEMENTS: case UINT32_ELEMENTS: BUILD_AND_RETURN_LOAD_TYPED_ARRAY(UnsignedInt); case FLOAT32_ELEMENTS: case FLOAT64_ELEMENTS: BUILD_AND_RETURN_LOAD_TYPED_ARRAY(Double); default: UNREACHABLE(); } #undef BUILD_AND_RETURN_LOAD_TYPED_ARRAY } ReduceResult MaglevGraphBuilder::BuildLoadConstantTypedArrayElement( compiler::JSTypedArrayRef typed_array, ValueNode* index, ElementsKind elements_kind) { #define BUILD_AND_RETURN_LOAD_CONSTANT_TYPED_ARRAY(Type) \ return AddNewNode<Load##Type##ConstantTypedArrayElement>( \ {index}, typed_array, elements_kind); switch (elements_kind) { case INT8_ELEMENTS: case INT16_ELEMENTS: case INT32_ELEMENTS: BUILD_AND_RETURN_LOAD_CONSTANT_TYPED_ARRAY(SignedInt); case UINT8_CLAMPED_ELEMENTS: case UINT8_ELEMENTS: case UINT16_ELEMENTS: case UINT32_ELEMENTS: BUILD_AND_RETURN_LOAD_CONSTANT_TYPED_ARRAY(UnsignedInt); case FLOAT32_ELEMENTS: case FLOAT64_ELEMENTS: BUILD_AND_RETURN_LOAD_CONSTANT_TYPED_ARRAY(Double); default: UNREACHABLE(); } #undef BUILD_AND_RETURN_LOAD_CONSTANTTYPED_ARRAY } ReduceResult MaglevGraphBuilder::BuildStoreTypedArrayElement( ValueNode* object, ValueNode* index, ElementsKind elements_kind) { #define BUILD_STORE_TYPED_ARRAY(Type, value) \ RETURN_IF_ABORT(AddNewNode<Store##Type##TypedArrayElement>( \ {object, index, (value)}, elements_kind)); // TODO(leszeks): These operations have a deopt loop when the ToNumber // conversion sees a type other than number or oddball. Turbofan has the same // deopt loop, but ideally we'd avoid it. switch (elements_kind) { case UINT8_CLAMPED_ELEMENTS: { BUILD_STORE_TYPED_ARRAY(Int, GetAccumulatorUint8ClampedForToNumber()) break; } case INT8_ELEMENTS: case INT16_ELEMENTS: case INT32_ELEMENTS: case UINT8_ELEMENTS: case UINT16_ELEMENTS: case UINT32_ELEMENTS: { ValueNode* value; GET_VALUE_OR_ABORT(value, GetAccumulatorTruncatedInt32ForToNumber( NodeType::kNumberOrOddball)); BUILD_STORE_TYPED_ARRAY(Int, value) break; } case FLOAT32_ELEMENTS: case FLOAT64_ELEMENTS: { ValueNode* value; GET_VALUE_OR_ABORT( value, GetAccumulatorFloat64ForToNumber(NodeType::kNumberOrOddball)); BUILD_STORE_TYPED_ARRAY(Double, value) break; } default: UNREACHABLE(); } #undef BUILD_STORE_TYPED_ARRAY return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildStoreConstantTypedArrayElement( compiler::JSTypedArrayRef typed_array, ValueNode* index, ElementsKind elements_kind) { #define BUILD_STORE_CONSTANT_TYPED_ARRAY(Type, value) \ RETURN_IF_ABORT(AddNewNode<Store##Type##ConstantTypedArrayElement>( \ {index, (value)}, typed_array, elements_kind)); // TODO(leszeks): These operations have a deopt loop when the ToNumber // conversion sees a type other than number or oddball. Turbofan has the same // deopt loop, but ideally we'd avoid it. switch (elements_kind) { case UINT8_CLAMPED_ELEMENTS: { BUILD_STORE_CONSTANT_TYPED_ARRAY(Int, GetAccumulatorUint8ClampedForToNumber()) break; } case INT8_ELEMENTS: case INT16_ELEMENTS: case INT32_ELEMENTS: case UINT8_ELEMENTS: case UINT16_ELEMENTS: case UINT32_ELEMENTS: { ValueNode* value; GET_VALUE_OR_ABORT(value, GetAccumulatorTruncatedInt32ForToNumber( NodeType::kNumberOrOddball)); BUILD_STORE_CONSTANT_TYPED_ARRAY(Int, value) break; } case FLOAT32_ELEMENTS: case FLOAT64_ELEMENTS: { ValueNode* value; GET_VALUE_OR_ABORT( value, GetAccumulatorFloat64ForToNumber(NodeType::kNumberOrOddball)); BUILD_STORE_CONSTANT_TYPED_ARRAY(Double, value) break; } default: UNREACHABLE(); } #undef BUILD_STORE_CONSTANT_TYPED_ARRAY return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryBuildElementAccessOnTypedArray( ValueNode* object, ValueNode* index_object, const compiler::ElementAccessInfo& access_info, compiler::KeyedAccessMode const& keyed_mode) { DCHECK(HasOnlyJSTypedArrayMaps( base::VectorOf(access_info.lookup_start_object_maps()))); ElementsKind elements_kind = access_info.elements_kind(); if (elements_kind == FLOAT16_ELEMENTS || elements_kind == BIGUINT64_ELEMENTS || elements_kind == BIGINT64_ELEMENTS) { return {}; } // No point in optimizing access to detached typed arrays. for (compiler::MapRef map : access_info.lookup_start_object_maps()) { if (map.IsJSDetachedTypedArrayMap()) return {}; } if (keyed_mode.access_mode() == compiler::AccessMode::kLoad && LoadModeHandlesOOB(keyed_mode.load_mode())) { // TODO(victorgomes): Handle OOB mode. return {}; } if (keyed_mode.access_mode() == compiler::AccessMode::kStore && StoreModeIgnoresTypeArrayOOB(keyed_mode.store_mode())) { // TODO(victorgomes): Handle OOB mode. return {}; } if (keyed_mode.access_mode() == compiler::AccessMode::kStore && elements_kind == UINT8_CLAMPED_ELEMENTS && !IsSupported(CpuOperation::kFloat64Round)) { // TODO(victorgomes): Technically we still support if value (in the // accumulator) is of type int32. It would be nice to have a roll back // mechanism instead, so that we do not need to check this early. return {}; } if (!broker()->dependencies()->DependOnArrayBufferDetachingProtector()) { // TODO(leszeks): Eliminate this check. RETURN_IF_ABORT(AddNewNode<CheckTypedArrayValid>( {object}, keyed_mode.IsStore() ? TypedArrayAccessMode::kWrite : TypedArrayAccessMode::kRead)); } ValueNode* index; ValueNode* length; GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(index_object)); GET_VALUE_OR_ABORT(length, BuildLoadTypedArrayLength(object, elements_kind)); RETURN_IF_ABORT(AddNewNode<CheckTypedArrayBounds>({index, length})); switch (keyed_mode.access_mode()) { case compiler::AccessMode::kLoad: DCHECK(!LoadModeHandlesOOB(keyed_mode.load_mode())); if (auto constant = object->TryCast<Constant>()) { compiler::HeapObjectRef constant_object = constant->object(); if (constant_object.IsJSTypedArray() && constant_object.AsJSTypedArray().is_off_heap_non_rab_gsab( broker())) { return BuildLoadConstantTypedArrayElement( constant_object.AsJSTypedArray(), index, elements_kind); } } return BuildLoadTypedArrayElement(object, index, elements_kind); case compiler::AccessMode::kStore: DCHECK(StoreModeIsInBounds(keyed_mode.store_mode())); if (auto constant = object->TryCast<Constant>()) { compiler::HeapObjectRef constant_object = constant->object(); if (constant_object.IsJSTypedArray() && constant_object.AsJSTypedArray().is_off_heap_non_rab_gsab( broker())) { return BuildStoreConstantTypedArrayElement( constant_object.AsJSTypedArray(), index, elements_kind); } } return BuildStoreTypedArrayElement(object, index, elements_kind); case compiler::AccessMode::kHas: // TODO(victorgomes): Implement has element access. return {}; case compiler::AccessMode::kStoreInLiteral: case compiler::AccessMode::kDefine: UNREACHABLE(); } } MaybeReduceResult MaglevGraphBuilder::TryBuildElementLoadOnJSArrayOrJSObject( ValueNode* object, ValueNode* index_object, base::Vector<const compiler::MapRef> maps, ElementsKind elements_kind, KeyedAccessLoadMode load_mode) { DCHECK(IsFastElementsKind(elements_kind)); bool is_jsarray = HasOnlyJSArrayMaps(maps); DCHECK(is_jsarray || HasOnlyJSObjectMaps(maps)); ValueNode* elements_array; GET_VALUE_OR_ABORT(elements_array, BuildLoadElements(object, elements_kind)); ValueNode* index; GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(index_object)); ValueNode* length; if (is_jsarray) { GET_VALUE_OR_ABORT(length, GetInt32(BuildLoadJSArrayLength(object))); } else { length = BuildLoadFixedArrayLength(elements_array); } auto emit_load = [&]() -> ReduceResult { ValueNode* result; if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { GET_VALUE_OR_ABORT(result, BuildLoadHoleyFixedDoubleArrayElement( elements_array, index, CanTreatHoleAsUndefined(maps) && LoadModeHandlesHoles(load_mode))); } else if (elements_kind == PACKED_DOUBLE_ELEMENTS) { GET_VALUE_OR_ABORT( result, BuildLoadFixedDoubleArrayElement(elements_array, index)); } else { DCHECK(!IsDoubleElementsKind(elements_kind)); bool is_holey = IsHoleyElementsKind(elements_kind); bool is_holey_and_treat_hole_as_undefined = is_holey && CanTreatHoleAsUndefined(maps) && LoadModeHandlesHoles(load_mode); bool is_smi = elements_kind == PACKED_SMI_ELEMENTS; GET_VALUE_OR_ABORT(result, BuildLoadFixedArrayElement( elements_array, index, is_smi ? LoadType::kSmi : LoadType::kUnknown)); if (is_holey) { if (is_holey_and_treat_hole_as_undefined) { GET_VALUE_OR_ABORT(result, BuildConvertHoleToUndefined(result)); } else { RETURN_IF_ABORT(BuildCheckNotHole(result)); if (IsSmiElementsKind(elements_kind)) { // After the hole-check, we're guaranteed to have a Smi. // TODO(dmercadier): EnsureType doesn't work nicely with the // MaglevOptimizer (because the MaglevOptimizer won't insert an // EnsureType and thus won't realize that we have a Smi). It would // be better to add a new SmiOrHole LoadType, which would be refined // to just Smi after we insert a hole check. This isn't trivial // because the Maglev type system currently doesn't have a "Hole" // type. EnsureType(result, NodeType::kSmi); } } } } return result; }; if (CanTreatHoleAsUndefined(maps) && LoadModeHandlesOOB(load_mode)) { ValueNode* positive_index; GET_VALUE_OR_ABORT(positive_index, GetUint32ElementIndex(index)); ValueNode* uint32_length; GET_VALUE_OR_ABORT(uint32_length, AddNewNode<UnsafeInt32ToUint32>({length})); return Select( [&](BranchBuilder& builder) { return BuildBranchIfUint32Compare(builder, Operation::kLessThan, positive_index, uint32_length); }, emit_load, [&] { return GetRootConstant(RootIndex::kUndefinedValue); }); } else { RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, length, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); return emit_load(); } } ReduceResult MaglevGraphBuilder::ConvertForStoring(ValueNode* value, ElementsKind kind) { if (IsDoubleElementsKind(kind)) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE if (kind == ElementsKind::HOLEY_DOUBLE_ELEMENTS) { value->MaybeRecordUseReprHint(UseRepresentation::kHoleyFloat64); ValueNode* value_hf64; GET_VALUE_OR_ABORT(value_hf64, GetHoleyFloat64(value)); // We still have to convert hole nans to undefined nans for storing. GET_VALUE_OR_ABORT( value, AddNewNode<HoleyFloat64ConvertHoleToUndefined>({value_hf64})); return value; } #endif // V8_ENABLE_UNDEFINED_DOUBLE // Make sure we do not store signalling NaNs into double arrays. // TODO(leszeks): Consider making this a bit on StoreFixedDoubleArrayElement // rather than a separate node. ValueNode* float64_value; GET_VALUE_OR_ABORT(float64_value, GetFloat64(value)); return GetSilencedNaN(float64_value); } if (IsSmiElementsKind(kind)) return GetSmiValue(value); return value; } std::optional<int32_t> MaglevGraphBuilder::CanElideBoundCheckAndResizing( ValueNode* elements_array, ValueNode* index_object, bool is_jsarray, const compiler::KeyedAccessMode& keyed_mode) { if (!is_jsarray) return {}; if (keyed_mode.access_mode() != compiler::AccessMode::kStoreInLiteral) { // We get the length of the array from the vobj, but if the AccessMode is // not kStoreInLiteral, then this store could be after the array has shrunk. // StoreInLiteral is only used for initializing stores, so there shouldn't // be anything in between the allocation and here to shrink the array. return {}; } if (keyed_mode.store_mode() == KeyedAccessStoreMode::kHandleCOW) { // TODO(dmercadier): we could handle this by still eliding the bounds check // but simply calling EnsureWritableElements. However, this currently // doesn't seem to ever happen, but it should happen if we improve load // elimination. return {}; } std::optional<int32_t> int32_index = TryGetInt32Constant(index_object); if (!int32_index.has_value()) return {}; if (!elements_array->Is<InlinedAllocation>()) return {}; VirtualObject* vobj = elements_array->Cast<InlinedAllocation>()->object(); if (!vobj->map() || !vobj->map()->IsJSArrayMap()) return {}; SmiConstant* array_length = vobj->get(JSArray::kLengthOffset)->TryCast<SmiConstant>(); if (!array_length) return {}; if (array_length->value().value() <= *int32_index) return {}; return int32_index; } MaybeReduceResult MaglevGraphBuilder::TryBuildElementStoreOnJSArrayOrJSObject( ValueNode* object, ValueNode* index_object, ValueNode* value, base::Vector<const compiler::MapRef> maps, ElementsKind elements_kind, const compiler::KeyedAccessMode& keyed_mode) { DCHECK(IsFastElementsKind(elements_kind)); const bool is_jsarray = HasOnlyJSArrayMaps(maps); DCHECK(is_jsarray || HasOnlyJSObjectMaps(maps)); // Get the elements array. ValueNode* elements_array; GET_VALUE_OR_ABORT(elements_array, BuildLoadElements(object, elements_kind)); GET_VALUE_OR_ABORT(value, ConvertForStoring(value, elements_kind)); ValueNode* index = nullptr; if (std::optional<int32_t> int32_index = CanElideBoundCheckAndResizing( object, index_object, is_jsarray, keyed_mode)) { index = GetInt32Constant(*int32_index); } else { // Check boundaries. ValueNode* elements_array_length = nullptr; ValueNode* length; if (is_jsarray) { GET_VALUE_OR_ABORT(length, GetInt32(BuildLoadJSArrayLength(object))); } else { length = elements_array_length = BuildLoadFixedArrayLength(elements_array); } GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(index_object)); if (keyed_mode.store_mode() == KeyedAccessStoreMode::kGrowAndHandleCOW) { if (elements_array_length == nullptr) { elements_array_length = BuildLoadFixedArrayLength(elements_array); } // Validate the {index} depending on holeyness: // // For HOLEY_*_ELEMENTS the {index} must not exceed the {elements} // backing store capacity plus the maximum allowed gap, as otherwise // the (potential) backing store growth would normalize and thus // the elements kind of the {receiver} would change to slow mode. // // For JSArray PACKED_*_ELEMENTS the {index} must be within the range // [0,length+1[ to be valid. In case {index} equals {length}, // the {receiver} will be extended, but kept packed. // // Non-JSArray PACKED_*_ELEMENTS always grow by adding holes because they // lack the magical length property, which requires a map transition. // So we can assume that this did not happen if we did not see this map. ValueNode* limit; if (IsHoleyElementsKind(elements_kind)) { GET_VALUE_OR_ABORT(limit, AddNewNode<Int32AddWithOverflow>( {elements_array_length, GetInt32Constant(JSObject::kMaxGap)})); } else if (is_jsarray) { GET_VALUE_OR_ABORT(limit, AddNewNode<Int32AddWithOverflow>( {length, GetInt32Constant(1)})); } else { limit = elements_array_length; } RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, limit, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); // Grow backing store if necessary and handle COW. GET_VALUE_OR_ABORT(elements_array, AddNewNode<MaybeGrowFastElements>( {elements_array, object, index, elements_array_length}, elements_kind)); // If we didn't grow {elements}, it might still be COW, in which case we // copy it now. if (IsSmiOrObjectElementsKind(elements_kind)) { DCHECK_EQ(keyed_mode.store_mode(), KeyedAccessStoreMode::kGrowAndHandleCOW); GET_VALUE_OR_ABORT( elements_array, AddNewNode<EnsureWritableFastElements>({elements_array, object})); } // Update length if necessary. if (is_jsarray) { ValueNode* new_length; GET_VALUE_OR_ABORT(new_length, AddNewNode<UpdateJSArrayLength>( {length, object, index})); RecordKnownProperty(object, broker()->length_string(), new_length, false, compiler::AccessMode::kStore); } } else { RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, length, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); // Handle COW if needed. if (IsSmiOrObjectElementsKind(elements_kind)) { if (keyed_mode.store_mode() == KeyedAccessStoreMode::kHandleCOW) { GET_VALUE_OR_ABORT( elements_array, AddNewNode<EnsureWritableFastElements>({elements_array, object})); } else { // Ensure that this is not a COW FixedArray. RETURN_IF_ABORT(BuildCheckMaps( elements_array, base::VectorOf({broker()->fixed_array_map()}))); } } } } // Do the store. if (IsDoubleElementsKind(elements_kind)) { return BuildStoreFixedDoubleArrayElement(elements_kind, elements_array, index, value); } else { return BuildStoreFixedArrayElement(elements_array, index, value); } } MaybeReduceResult MaglevGraphBuilder::TryBuildElementAccessOnJSArrayOrJSObject( ValueNode* object, ValueNode* index_object, const compiler::ElementAccessInfo& access_info, compiler::KeyedAccessMode const& keyed_mode) { if (!IsFastElementsKind(access_info.elements_kind())) { return {}; } if (access_info.is_proxy_on_prototype()) { // TODO(jkummerow): Implement support. return {}; } switch (keyed_mode.access_mode()) { case compiler::AccessMode::kLoad: return TryBuildElementLoadOnJSArrayOrJSObject( object, index_object, base::VectorOf(access_info.lookup_start_object_maps()), access_info.elements_kind(), keyed_mode.load_mode()); case compiler::AccessMode::kStoreInLiteral: case compiler::AccessMode::kStore: { base::Vector<const compiler::MapRef> maps = base::VectorOf(access_info.lookup_start_object_maps()); ElementsKind elements_kind = access_info.elements_kind(); return TryBuildElementStoreOnJSArrayOrJSObject(object, index_object, GetAccumulator(), maps, elements_kind, keyed_mode); } default: // TODO(victorgomes): Implement more access types. return {}; } } template <typename GenericAccessFunc> MaybeReduceResult MaglevGraphBuilder::TryBuildElementAccess( ValueNode* object, ValueNode* index_object, compiler::ElementAccessFeedback const& feedback, compiler::FeedbackSource const& feedback_source, GenericAccessFunc&& build_generic_access) { const compiler::KeyedAccessMode& keyed_mode = feedback.keyed_mode(); // Check for the megamorphic case. if (feedback.transition_groups().empty()) { if (keyed_mode.access_mode() == compiler::AccessMode::kLoad) { return BuildCallBuiltinWithTaggedInputs< Builtin::kKeyedLoadIC_Megamorphic>({object, index_object}, feedback_source); } else if (keyed_mode.access_mode() == compiler::AccessMode::kStore) { return BuildCallBuiltinWithTaggedInputs< Builtin::kKeyedStoreIC_Megamorphic>( {object, index_object, GetAccumulator()}, feedback_source); } return {}; } auto possible_maps = known_node_aspects().TryGetPossibleMaps(object); compiler::ElementAccessFeedback refined_feedback = possible_maps ? feedback.Refine(broker(), *possible_maps) : feedback; if (refined_feedback.HasOnlyStringMaps(broker())) { return TryBuildElementAccessOnString(object, index_object, refined_feedback, keyed_mode); } compiler::AccessInfoFactory access_info_factory(broker(), zone()); ZoneVector<compiler::ElementAccessInfo> access_infos(zone()); if (!access_info_factory.ComputeElementAccessInfos(refined_feedback, &access_infos) || access_infos.empty()) { return {}; } // TODO(leszeks): This is copied without changes from TurboFan's native // context specialization. We should figure out a way to share this code. // // For holey stores or growing stores, we need to check that the prototype // chain contains no setters for elements, and we need to guard those checks // via code dependencies on the relevant prototype maps. if (keyed_mode.access_mode() == compiler::AccessMode::kStore) { // TODO(v8:7700): We could have a fast path here, that checks for the // common case of Array or Object prototype only and therefore avoids // the zone allocation of this vector. ZoneVector<compiler::MapRef> prototype_maps(zone()); for (compiler::ElementAccessInfo const& access_info : access_infos) { for (compiler::MapRef receiver_map : access_info.lookup_start_object_maps()) { // If the {receiver_map} has a prototype and its elements backing // store is either holey, or we have a potentially growing store, // then we need to check that all prototypes have stable maps with // with no element accessors and no throwing behavior for elements (and // we need to guard against changes to that below). if ((IsHoleyOrDictionaryElementsKind(receiver_map.elements_kind()) || StoreModeCanGrow(refined_feedback.keyed_mode().store_mode())) && !receiver_map.PrototypesElementsDoNotHaveAccessorsOrThrow( broker(), &prototype_maps)) { return {}; } // TODO(v8:12547): Support writing to objects in shared space, which // need a write barrier that calls Object::Share to ensure the RHS is // shared. if (InstanceTypeChecker::IsAlwaysSharedSpaceJSObject( receiver_map.instance_type())) { return {}; } } } for (compiler::MapRef prototype_map : prototype_maps) { broker()->dependencies()->DependOnStableMap(prototype_map); } } // Do not optimize AccessMode::kDefine for typed arrays. if (keyed_mode.access_mode() == compiler::AccessMode::kDefine) { for (const compiler::ElementAccessInfo& access_info : access_infos) { if (IsTypedArrayOrRabGsabTypedArrayElementsKind( access_info.elements_kind())) { return {}; } } } // Check for monomorphic case. if (access_infos.size() == 1) { compiler::ElementAccessInfo const& access_info = access_infos.front(); // TODO(victorgomes): Support RAB/GSAB backed typed arrays. if (IsRabGsabTypedArrayElementsKind(access_info.elements_kind())) { return {}; } if (!access_info.transition_sources().empty()) { compiler::MapRef transition_target = access_info.lookup_start_object_maps().front(); const ZoneVector<compiler::MapRef>& transition_sources = access_info.transition_sources(); // There are no transitions in heap number maps. If `object` is a SMI, we // would anyway fail the transition and deopt later. DCHECK_NE(transition_target.instance_type(), InstanceType::HEAP_NUMBER_TYPE); #ifdef DEBUG for (auto& transition_source : transition_sources) { DCHECK_NE(transition_source.instance_type(), InstanceType::HEAP_NUMBER_TYPE); } #endif // DEBUG RETURN_IF_ABORT(BuildCheckHeapObject(object)); ValueNode* object_map; GET_VALUE_OR_ABORT(object_map, BuildLoadTaggedField(object, HeapObject::kMapOffset)); RETURN_IF_ABORT(BuildTransitionElementsKindOrCheckMap( object, object_map, transition_sources, transition_target)); } else { RETURN_IF_ABORT(BuildCheckMaps( object, base::VectorOf(access_info.lookup_start_object_maps()))); } if (IsTypedArrayElementsKind(access_info.elements_kind())) { return TryBuildElementAccessOnTypedArray(object, index_object, access_info, keyed_mode); } return TryBuildElementAccessOnJSArrayOrJSObject(object, index_object, access_info, keyed_mode); } else { return TryBuildPolymorphicElementAccess(object, index_object, keyed_mode, access_infos, build_generic_access); } } template <typename GenericAccessFunc> MaybeReduceResult MaglevGraphBuilder::TryBuildPolymorphicElementAccess( ValueNode* object, ValueNode* index_object, const compiler::KeyedAccessMode& keyed_mode, const ZoneVector<compiler::ElementAccessInfo>& access_infos, GenericAccessFunc&& build_generic_access) { if (keyed_mode.access_mode() == compiler::AccessMode::kLoad && LoadModeHandlesOOB(keyed_mode.load_mode())) { // TODO(victorgomes): Handle OOB mode. return {}; } const bool is_any_store = compiler::IsAnyStore(keyed_mode.access_mode()); const int access_info_count = static_cast<int>(access_infos.size()); // Stores don't return a value, so we don't need a variable for the result. MaglevSubGraphBuilder sub_graph(this, is_any_store ? 0 : 1); std::optional<MaglevSubGraphBuilder::Variable> ret_val; std::optional<MaglevSubGraphBuilder::Label> done; std::optional<MaglevSubGraphBuilder::Label> generic_access; RETURN_IF_ABORT(BuildCheckHeapObject(object)); ValueNode* object_map; GET_VALUE_OR_ABORT(object_map, BuildLoadTaggedField(object, HeapObject::kMapOffset)); // TODO(pthier): We could do better here than just emitting code for each map, // as many different maps can produce the exact samce code (e.g. TypedArray // access for Uint16/Uint32/Int16/Int32/...). for (int i = 0; i < access_info_count; i++) { compiler::ElementAccessInfo const& access_info = access_infos[i]; std::optional<MaglevSubGraphBuilder::Label> check_next_map; const bool handle_transitions = !access_info.transition_sources().empty(); MaybeReduceResult map_check_result; if (i == access_info_count - 1) { if (handle_transitions) { compiler::MapRef transition_target = access_info.lookup_start_object_maps().front(); map_check_result = BuildTransitionElementsKindOrCheckMap( object, object_map, access_info.transition_sources(), transition_target); } else { map_check_result = BuildCheckMaps( object, base::VectorOf(access_info.lookup_start_object_maps()), object_map); } } else { if (handle_transitions) { compiler::MapRef transition_target = access_info.lookup_start_object_maps().front(); map_check_result = BuildTransitionElementsKindAndCompareMaps( object, object_map, access_info.transition_sources(), transition_target, &sub_graph, check_next_map); } else { map_check_result = BuildCompareMaps( object, object_map, base::VectorOf(access_info.lookup_start_object_maps()), &sub_graph, check_next_map); } } if (map_check_result.IsDoneWithAbort()) { // We know from known possible maps that this branch is not reachable, // so don't emit any code for it. continue; } MaybeReduceResult result; // TODO(victorgomes): Support RAB/GSAB backed typed arrays. if (IsRabGsabTypedArrayElementsKind(access_info.elements_kind())) { result = MaybeReduceResult::Fail(); } else if (IsTypedArrayElementsKind(access_info.elements_kind())) { result = TryBuildElementAccessOnTypedArray(object, index_object, access_info, keyed_mode); } else { result = TryBuildElementAccessOnJSArrayOrJSObject( object, index_object, access_info, keyed_mode); } switch (result.kind()) { case MaybeReduceResult::kDoneWithPayload: case MaybeReduceResult::kDoneWithoutPayload: DCHECK_EQ(result.HasValue(), !is_any_store); if (!done.has_value()) { // We initialize the label {done} lazily on the first possible path. // If no possible path exists, it is guaranteed that BuildCheckMaps // emitted an unconditional deopt and we return DoneWithAbort at the // end. We need one extra predecessor to jump from the generic case. const int possible_predecessors = access_info_count - i + 1; if (is_any_store) { done.emplace(&sub_graph, possible_predecessors); } else { ret_val.emplace(0); done.emplace( &sub_graph, possible_predecessors, std::initializer_list<MaglevSubGraphBuilder::Variable*>{ &*ret_val}); } } if (!is_any_store) { sub_graph.set(*ret_val, result.value()); } sub_graph.Goto(&*done); break; case MaybeReduceResult::kFail: if (!generic_access.has_value()) { // Conservatively assume that all remaining branches can go into the // generic path, as we have to initialize the predecessors upfront. // TODO(pthier): Find a better way to do that. generic_access.emplace(&sub_graph, access_info_count - i); } sub_graph.Goto(&*generic_access); break; case MaybeReduceResult::kDoneWithAbort: break; } if (check_next_map.has_value()) { sub_graph.Bind(&*check_next_map); } else if (i != access_info_count - 1) { // The map check will always succeed, so we do not need to try other // options. DCHECK(map_check_result.IsDone()); DCHECK(!check_next_map.has_value()); // TODO(victorgomes): We should avoid calling the polymorphic builder in // the first place. If we statically know one of the map comparison will // always succeed, then we should call the non-polymorphic build access. break; } } if (generic_access.has_value() && !sub_graph.TrimPredecessorsAndBind(&*generic_access).IsDoneWithAbort()) { ReduceResult generic_result = build_generic_access(); RETURN_IF_ABORT(generic_result); DCHECK_IMPLIES(!is_any_store, generic_result.IsDoneWithValue()); if (!done.has_value()) { return is_any_store ? ReduceResult::Done() : generic_result.value(); } if (!is_any_store) { sub_graph.set(*ret_val, generic_result.value()); } sub_graph.Goto(&*done); } if (done.has_value()) { RETURN_IF_ABORT(sub_graph.TrimPredecessorsAndBind(&*done)); return is_any_store ? ReduceResult::Done() : sub_graph.get(*ret_val); } else { return ReduceResult::DoneWithAbort(); } } template <typename GenericAccessFunc> MaybeReduceResult MaglevGraphBuilder::TryBuildPolymorphicPropertyAccess( ValueNode* receiver, ValueNode* lookup_start_object, compiler::NamedAccessFeedback const& feedback, compiler::AccessMode access_mode, const ZoneVector<compiler::PropertyAccessInfo>& access_infos, GenericAccessFunc&& build_generic_access) { const bool is_any_store = compiler::IsAnyStore(access_mode); const int access_info_count = static_cast<int>(access_infos.size()); int number_map_index_for_smi = -1; bool needs_migration = false; bool has_deprecated_map_without_migration_target = feedback.has_deprecated_map_without_migration_target(); for (int i = 0; i < access_info_count; i++) { compiler::PropertyAccessInfo const& access_info = access_infos[i]; DCHECK(!access_info.IsInvalid()); for (compiler::MapRef map : access_info.lookup_start_object_maps()) { if (map.is_migration_target()) { needs_migration = true; } if (map.IsHeapNumberMap()) { GetOrCreateInfoFor(lookup_start_object); base::SmallVector<compiler::MapRef, 1> known_maps = {map}; KnownMapsMerger<base::SmallVector<compiler::MapRef, 1>> merger( broker(), zone(), known_maps); merger.IntersectWithKnownNodeAspects(lookup_start_object, known_node_aspects()); if (!merger.intersect_set().is_empty() && !IsEmptyNodeType( IntersectType(GetType(lookup_start_object), NodeType::kSmi))) { DCHECK_EQ(number_map_index_for_smi, -1); number_map_index_for_smi = i; } } } } int start_offset = iterator_.current_offset(); SourcePositionTableIterator::IndexAndPositionState start_source_position_iterator_state = source_position_iterator_.GetState(); std::optional<ContinuationOffsets> continuation; if (!is_any_store) { continuation = FindContinuationForPolymorphicPropertyLoad(); } // Stores don't return a value, so we don't need a variable for the result. MaglevSubGraphBuilder sub_graph(this, is_any_store ? 0 : 1); std::optional<MaglevSubGraphBuilder::Variable> ret_val; std::optional<MaglevSubGraphBuilder::Label> done; std::optional<MaglevSubGraphBuilder::Label> is_number; std::optional<MaglevSubGraphBuilder::Label> generic_access; if (number_map_index_for_smi >= 0) { if (continuation) { int future_bind_offset = iterator_.current_offset(); is_number.emplace( MaglevSubGraphBuilder::LabelForTrackingInterpreterFrameState( &sub_graph, 2, future_bind_offset)); } else { is_number.emplace(&sub_graph, 2); } RETURN_IF_ABORT( sub_graph.GotoIfTrue<BranchIfSmi>(&*is_number, {lookup_start_object})); } else { // The lookup start object is a HeapNumber, but we haven't seen the // HeapNumber map in the access infos. if (!lookup_start_object->is_tagged() && !lookup_start_object->is_holey_float64()) { return EmitUnconditionalDeopt(DeoptimizeReason::kWrongMap); } RETURN_IF_ABORT(BuildCheckHeapObject(lookup_start_object)); } ValueNode* lookup_start_object_map; GET_VALUE_OR_ABORT(lookup_start_object_map, BuildLoadMap(lookup_start_object)); if (needs_migration) { // TODO(marja, v8:7700): Try migrating only if all comparisons failed. // TODO(marja, v8:7700): Investigate making polymorphic map comparison (with // migration) a control node (like switch). GET_VALUE_OR_ABORT(lookup_start_object_map, AddNewNode<MigrateMapIfNeeded>( {lookup_start_object_map, lookup_start_object})); } std::optional<MaglevSubGraphBuilder::Label> check_next_map; for (int i = 0; i < access_info_count; i++) { // Reset the state before generating the next polymorphic arm, in case // FindContinuationForPolymorphicPropertyLoad or a continuation in the // previous arm changed it. iterator_.SetOffset(start_offset); source_position_iterator_.RestoreState( start_source_position_iterator_state); if (check_next_map.has_value()) { sub_graph.Bind(&*check_next_map); check_next_map.reset(); } compiler::PropertyAccessInfo const& access_info = access_infos[i]; MaybeReduceResult map_check_result; const auto& maps = access_info.lookup_start_object_maps(); if (i == access_info_count - 1) { map_check_result = BuildCheckMaps( lookup_start_object, base::VectorOf(maps), {}, has_deprecated_map_without_migration_target, needs_migration); } else { std::optional<int> future_bind_offset; if (continuation) future_bind_offset = iterator_.current_offset(); map_check_result = BuildCompareMaps( lookup_start_object, lookup_start_object_map, base::VectorOf(maps), &sub_graph, check_next_map, future_bind_offset); } if (map_check_result.IsDoneWithAbort()) { DCHECK_NE(i, number_map_index_for_smi); // We know from known possible maps that this branch is not reachable, // so don't emit any code for it. if (continuation) { AdvanceThroughContinuationForPolymorphicPropertyLoad(*continuation); } continue; } if (i == number_map_index_for_smi) { DCHECK(is_number.has_value()); sub_graph.Goto(&*is_number); sub_graph.Bind(&*is_number); } MaybeReduceResult result; if (is_any_store) { result = TryBuildPropertyStore(receiver, lookup_start_object, feedback.name(), access_info, access_mode); } else { result = TryBuildPropertyLoad(receiver, lookup_start_object, feedback.name(), access_info); } switch (result.kind()) { case MaybeReduceResult::kDoneWithPayload: case MaybeReduceResult::kDoneWithoutPayload: { DCHECK_EQ(result.HasValue(), !is_any_store); if (!done.has_value()) { // We initialize the label {done} lazily on the first possible path. // If no possible path exists, it is guaranteed that BuildCheckMaps // emitted an unconditional deopt and we return DoneWithAbort at the // end. We need one extra predecessor to jump from the generic case. const int possible_predecessors = access_info_count - i + 1; if (is_any_store) { done.emplace(&sub_graph, possible_predecessors); } else { ret_val.emplace(0); if (continuation) { int future_bind_offset = continuation->last_continuation; done.emplace( MaglevSubGraphBuilder::LabelForTrackingInterpreterFrameState( &sub_graph, possible_predecessors, std::initializer_list<MaglevSubGraphBuilder::Variable*>{ &*ret_val}, future_bind_offset)); } else { done.emplace( &sub_graph, possible_predecessors, std::initializer_list<MaglevSubGraphBuilder::Variable*>{ &*ret_val}); } } } if (is_any_store) { sub_graph.Goto(&*done); } else if (continuation) { // Generate code for further bytecodes inside the polymorphic // branch. // First save the result of the property load in the accumulator. SetAccumulator(result.value()); if (!BuildContinuationForPolymorphicPropertyLoad(*continuation) .IsDoneWithAbort()) { // The continuation stored its value into the accumulator. Take // that value as our value. sub_graph.set(*ret_val, current_interpreter_frame_.get( interpreter::Register::virtual_accumulator())); sub_graph.Goto(&*done); } } else { sub_graph.set(*ret_val, result.value()); sub_graph.Goto(&*done); } break; } case MaybeReduceResult::kDoneWithAbort: if (continuation) { AdvanceThroughContinuationForPolymorphicPropertyLoad(*continuation); } break; case MaybeReduceResult::kFail: if (!generic_access.has_value()) { // Conservatively assume that all remaining branches can go into the // generic path, as we have to initialize the predecessors upfront. // TODO(pthier): Find a better way to do that. if (continuation) { int future_bind_offset = iterator_.current_offset(); generic_access.emplace( MaglevSubGraphBuilder::LabelForTrackingInterpreterFrameState( &sub_graph, access_info_count - i, future_bind_offset)); } else { generic_access.emplace(&sub_graph, access_info_count - i); } } sub_graph.Goto(&*generic_access); break; default: UNREACHABLE(); } if (!check_next_map.has_value() && i != access_info_count - 1) { // The map check will always succeed, so we do not need to try other // options. DCHECK(map_check_result.IsDone()); // TODO(victorgomes): We should avoid calling the polymorphic builder in // the first place. If we statically know one of the map comparison will // always succeed, then we should call the non-polymorphic build access. break; } } if (generic_access.has_value() && !sub_graph.TrimPredecessorsAndBind(&*generic_access).IsDoneWithAbort()) { MaybeReduceResult generic_result = build_generic_access(); DCHECK(generic_result.IsDone()); DCHECK_EQ(generic_result.IsDoneWithValue(), !is_any_store); if (continuation) { DCHECK(!is_any_store); SetAccumulator(generic_result.value()); ReduceResult continuation_result = BuildContinuationForPolymorphicPropertyLoad(*continuation); if (!done.has_value()) { return current_interpreter_frame_.get( interpreter::Register::virtual_accumulator()); } if (!continuation_result.IsDoneWithAbort()) { sub_graph.set(*ret_val, current_interpreter_frame_.get( interpreter::Register::virtual_accumulator())); } } else { if (!done.has_value()) { return is_any_store ? ReduceResult::Done() : generic_result.value(); } if (!is_any_store) { sub_graph.set(*ret_val, generic_result.value()); } } sub_graph.Goto(&*done); } if (done.has_value()) { if (continuation) { DCHECK_EQ(iterator_.current_offset(), continuation->last_continuation); } RETURN_IF_ABORT(sub_graph.TrimPredecessorsAndBind(&*done)); return is_any_store ? ReduceResult::Done() : sub_graph.get(*ret_val); } else { iterator_.SetOffset(start_offset); source_position_iterator_.RestoreState( start_source_position_iterator_state); return ReduceResult::DoneWithAbort(); } } std::optional<MaglevGraphBuilder::ContinuationOffsets> MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoad() { if (!v8_flags.maglev_poly_calls) { return {}; } if (iterator_.current_bytecode() != interpreter::Bytecode::kGetNamedProperty) { return {}; } int start_offset = iterator_.current_offset(); #ifdef DEBUG SourcePositionTableIterator::IndexAndPositionState start_source_position_iterator_state = source_position_iterator_.GetState(); #endif std::optional<ContinuationOffsets> continuation = FindContinuationForPolymorphicPropertyLoadImpl(); iterator_.SetOffset(start_offset); DCHECK_EQ(start_source_position_iterator_state, source_position_iterator_.GetState()); return continuation; } std::optional<MaglevGraphBuilder::ContinuationOffsets> MaglevGraphBuilder::FindContinuationForPolymorphicPropertyLoadImpl() { interpreter::Register loaded_property_register = interpreter::Register::virtual_accumulator(); // We generate continuations for this pattern: // GetNamedProperty ... // Sta-REG // <Allowed bytecodes> // CallProperty REG ... // Where <allowed bytecodes> are: // - not affecting control flow // - not storing into REG // - not the start or end of a try block // and the continuation is limited in length. // Try-block starts are not visible as control flow or basic blocks, so detect // them using the bytecode offset. int next_handler_change = kMaxInt; HandlerTable table(*bytecode().object()); if (next_handler_table_index_ < table.NumberOfRangeEntries()) { next_handler_change = table.GetRangeStart(next_handler_table_index_); } // Try-block ends are detected via the top end offset in the current handler // stack. if (IsInsideTryBlock()) { const HandlerTableEntry& entry = catch_block_stack_.top(); next_handler_change = std::min(next_handler_change, entry.end); } auto IsOffsetAPolymorphicContinuationInterrupt = [this, next_handler_change](int offset) { // We can't continue a polymorphic load over a merge, since the // other side of the merge will observe the call without the load. // // TODO(leszeks): I guess we could split that merge if we wanted to, // introducing a new merge that has the polymorphic loads+calls on one // side and the generic call on the other. if (IsOffsetAMergePoint(offset)) return true; // We currently can't continue a polymorphic load across a peeled // loop header -- not because of any actual semantic reason, a peeled // loop should be just like straightline code, but just because this // iteration isn't compatible with the PeelLoop iteration. // // TODO(leszeks): We could probably make loop peeling work happen on the // JumpLoop rather than loop header, and then this continuation code // would work. Only for the first peeled iteration though, not for // speeling. if (loop_headers_to_peel_.Contains(offset)) return true; // Loop peeling should be the only reason there was no merge point for a // loop header. DCHECK(!bytecode_analysis_.IsLoopHeader(offset)); // We can't currently continue a polymorphic load over a try-catch // start/end -- again, not for any semantic reason, but just because // this iteration doesn't consider the catch handler stack. // // TODO(leszeks): If this saved/restore the handler stack, it would // probably work, but we'd need to confirm that later phases don't need // strict nesting of handlers (since the first polymorphic call would // be inside the handler range, but the second polymorphic load after it // in linear scan order would be outside of the handler range). if (offset >= next_handler_change) return true; return false; }; // Skip GetNamedProperty. iterator_.Advance(); if (IsOffsetAPolymorphicContinuationInterrupt(iterator_.current_offset())) { return {}; } // Handle Sta-REG and read REG into loaded_property_register. switch (iterator_.current_bytecode()) { #define CASE(Name, ...) \ case interpreter::Bytecode::k##Name: \ loaded_property_register = \ interpreter::Register::FromShortStar(interpreter::Bytecode::k##Name); \ break; SHORT_STAR_BYTECODE_LIST(CASE) #undef CASE default: return {}; } // TODO(marja): Find out a good limit. int limit = 20; while (--limit > 0) { iterator_.Advance(); if (IsOffsetAPolymorphicContinuationInterrupt(iterator_.current_offset())) { return {}; } switch (iterator_.current_bytecode()) { #define CALL_CASE(Name, ...) \ case interpreter::Bytecode::k##Name: \ if (iterator_.GetRegisterOperand(0) == loaded_property_register) { \ return ContinuationOffsets{iterator_.current_offset(), \ iterator_.next_offset()}; \ } \ break; // Call bytecodes (if they call the loaded property) end the continuation. CALL_PROPERTY_BYTECODES(CALL_CASE) #undef CALL_CASE #define STA_CASE(Name, ...) \ case interpreter::Bytecode::k##Name: \ if (interpreter::Register::FromShortStar( \ interpreter::Bytecode::k##Name) == loaded_property_register) { \ return {}; \ } \ break; // Sta bytecodes might clobber the register we loaded into. SHORT_STAR_BYTECODE_LIST(STA_CASE) #undef STA_CASE case interpreter::Bytecode::kStar: if (iterator_.GetRegisterOperand(0) == loaded_property_register) { return {}; } break; #define CONTROL_NODE_CASE(Name, ...) \ case interpreter::Bytecode::k##Name: \ return {}; // The continuation cannot extend beyond control flow altering // bytecodes. JUMP_BYTECODE_LIST(CONTROL_NODE_CASE) RETURN_BYTECODE_LIST(CONTROL_NODE_CASE) UNCONDITIONAL_THROW_BYTECODE_LIST(CONTROL_NODE_CASE) #undef CONTROL_NODE_CASE default: break; } } return {}; // TODO(marja): Add other possible continuations. // Restriction: the bytecodes which can end the continuation must write // their result in the accumulator. // TODO(marja): Remove this restriction. To do that, VisitGetNamedProperty // can't assume it should call SetAccumulator after TryBuildLoadNamedProperty. } ReduceResult MaglevGraphBuilder::BuildContinuationForPolymorphicPropertyLoad( const ContinuationOffsets& continuation) { ScopedModification<bool> only_inline_small_scope(&only_inline_small_, true); while (iterator_.current_offset() < continuation.last_continuation) { iterator_.Advance(); if (VisitSingleBytecode().IsDoneWithAbort()) { AdvanceThroughContinuationForPolymorphicPropertyLoad(continuation); return ReduceResult::DoneWithAbort(); } } return ReduceResult::Done(); } void MaglevGraphBuilder::AdvanceThroughContinuationForPolymorphicPropertyLoad( const ContinuationOffsets& continuation) { while (iterator_.current_offset() < continuation.last_continuation) { iterator_.Advance(); UpdateSourceAndBytecodePosition(iterator_.current_offset()); } } void MaglevGraphBuilder::RecordKnownProperty(ValueNode* lookup_start_object, PropertyKey key, ValueNode* value, bool is_const, compiler::AccessMode access_mode) { DCHECK(!value->is_conversion()); auto& props_for_key = known_node_aspects().GetLoadedPropertiesForKey(zone(), is_const, key); if (!is_const && IsAnyStore(access_mode)) { if (is_loop_effect_tracking()) { loop_effects_->keys_cleared.insert(key); } // We don't do any aliasing analysis, so stores clobber all other cached // loads of a property with that key. We only need to do this for // non-constant properties, since constant properties are known not to // change and therefore can't be clobbered. // TODO(leszeks): Do some light aliasing analysis here, e.g. checking // whether there's an intersection of known maps. if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { std::cout << " * Removing all non-constant cached properties with " << key << std::endl; } props_for_key.clear(); } if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { std::cout << " * Recording " << (is_const ? "constant" : "non-constant") << " known property " << PrintNodeLabel(lookup_start_object) << ": " << PrintNode(lookup_start_object) << " [" << key << "] = " << PrintNodeLabel(value) << ": " << PrintNode(value) << std::endl; } if (IsAnyStore(access_mode) && !is_const && is_loop_effect_tracking()) { auto updated = props_for_key.emplace(lookup_start_object, value); if (updated.second) { loop_effects_->objects_written.insert(lookup_start_object); } else if (updated.first->second != value) { updated.first->second = value; loop_effects_->objects_written.insert(lookup_start_object); } } else { props_for_key[lookup_start_object] = value; } } MaybeReduceResult MaglevGraphBuilder::TryReuseKnownPropertyLoad( ValueNode* lookup_start_object, compiler::NameRef name) { if (ValueNode* property = known_node_aspects().TryFindLoadedProperty( lookup_start_object, name)) { TRACE(" * Reusing non-constant loaded property " << PrintNodeLabel(property) << ": " << PrintNode(property)); return property; } if (ValueNode* property = known_node_aspects().TryFindLoadedConstantProperty( lookup_start_object, name)) { TRACE(" * Reusing constant loaded property " << PrintNodeLabel(property) << ": " << PrintNode(property)); return property; } return {}; } ReduceResult MaglevGraphBuilder::BuildLoadStringLength(ValueNode* string) { NodeType node_type = GetType(string); if (node_type == NodeType::kNone) { return BuildAbort(AbortReason::kUnreachable); } DCHECK(NodeTypeIs(node_type, NodeType::kString)); if (auto vo_string = string->TryCast<InlinedAllocation>()) { VirtualObject* vobj = vo_string->object(); if (vobj->object_type() == vobj::ObjectType::kConsString) { return vobj->get(offsetof(String, length_)); } } if (auto const_string = TryGetConstant<String>(string)) { return GetInt32Constant(const_string->length()); } if (ValueNode* const_length = known_node_aspects().TryFindLoadedConstantProperty( string, PropertyKey::StringLength())) { TRACE(" * Reusing constant [String length]" << PrintNodeLabel(const_length) << ": " << PrintNode(const_length)); return const_length; } ValueNode* result; GET_VALUE_OR_ABORT(result, AddNewNode<StringLength>({string})); RecordKnownProperty(string, PropertyKey::StringLength(), result, true, compiler::AccessMode::kLoad); return result; } template <typename GenericAccessFunc> MaybeReduceResult MaglevGraphBuilder::TryBuildLoadNamedProperty( ValueNode* receiver, ValueNode* lookup_start_object, compiler::NameRef name, compiler::FeedbackSource& feedback_source, GenericAccessFunc&& build_generic_access) { const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess(feedback_source, compiler::AccessMode::kLoad, name); switch (processed_feedback.kind()) { case compiler::ProcessedFeedback::kInsufficient: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess); case compiler::ProcessedFeedback::kNamedAccess: { RETURN_IF_DONE(TryReuseKnownPropertyLoad(lookup_start_object, name)); return TryBuildNamedAccess( receiver, lookup_start_object, processed_feedback.AsNamedAccess(), feedback_source, compiler::AccessMode::kLoad, build_generic_access); } default: return {}; } } MaybeReduceResult MaglevGraphBuilder::TryBuildLoadNamedProperty( ValueNode* receiver, compiler::NameRef name, compiler::FeedbackSource& feedback_source) { auto build_generic_access = [this, &receiver, &name, &feedback_source]() { ValueNode* context = GetContext(); return AddNewNode<LoadNamedGeneric>({context, receiver}, name, feedback_source); }; return TryBuildLoadNamedProperty(receiver, receiver, name, feedback_source, build_generic_access); } ReduceResult MaglevGraphBuilder::VisitGetNamedProperty() { // GetNamedProperty <object> <name_index> <slot> ValueNode* object = LoadRegister(0); compiler::NameRef name = GetRefOperand<Name>(1); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; PROCESS_AND_RETURN_IF_DONE( TryBuildLoadNamedProperty(object, name, feedback_source), SetAccumulator); // Create a generic load in the fallthrough. ValueNode* context = GetContext(); return SetAccumulator( AddNewNode<LoadNamedGeneric>({context, object}, name, feedback_source)); } MaybeReduceResult MaglevGraphBuilder::GetConstantSingleCharacterStringFromCode( uint16_t code) { // Only handle the one-byte character case, which accesses roots. if (code <= String::kMaxOneByteCharCode) { return GetRootConstant(RootsTable::SingleCharacterStringIndex(code)); } return {}; } ReduceResult MaglevGraphBuilder::VisitGetNamedPropertyFromSuper() { // GetNamedPropertyFromSuper <receiver> <name_index> <slot> ValueNode* receiver = LoadRegister(0); ValueNode* home_object = GetAccumulator(); compiler::NameRef name = GetRefOperand<Name>(1); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; // {home_object} is guaranteed to be a HeapObject. ValueNode* home_object_map; GET_VALUE_OR_ABORT(home_object_map, BuildLoadTaggedField(home_object, HeapObject::kMapOffset)); ValueNode* lookup_start_object; GET_VALUE_OR_ABORT( lookup_start_object, BuildLoadTaggedField(home_object_map, Map::kPrototypeOffset)); auto build_generic_access = [this, &receiver, &lookup_start_object, &name, &feedback_source]() { ValueNode* context = GetContext(); return AddNewNode<LoadNamedFromSuperGeneric>( {context, receiver, lookup_start_object}, name, feedback_source); }; PROCESS_AND_RETURN_IF_DONE( TryBuildLoadNamedProperty(receiver, lookup_start_object, name, feedback_source, build_generic_access), SetAccumulator); // Create a generic load. return SetAccumulator(build_generic_access()); } MaybeReduceResult MaglevGraphBuilder::TryBuildGetKeyedPropertyWithEnumeratedKey( ValueNode* object, const compiler::FeedbackSource& feedback_source, const compiler::ProcessedFeedback& processed_feedback) { if (current_for_in_state.index != nullptr && current_for_in_state.enum_cache_indices != nullptr && current_for_in_state.key == current_interpreter_frame_.accumulator()) { bool speculating_receiver_map_matches = false; if (current_for_in_state.receiver != object) { // When the feedback is uninitialized, it is either a keyed load which // always hits the enum cache, or a keyed load that had never been // reached. In either case, we can check the map of the receiver and use // the enum cache if the map match the {cache_type}. if (processed_feedback.kind() != compiler::ProcessedFeedback::kInsufficient) { return MaybeReduceResult::Fail(); } if (BuildCheckHeapObject(object).IsDoneWithAbort()) { return ReduceResult::DoneWithAbort(); } speculating_receiver_map_matches = true; } if (current_for_in_state.receiver_needs_map_check || speculating_receiver_map_matches) { ValueNode* receiver_map; GET_VALUE_OR_ABORT(receiver_map, BuildLoadTaggedField(object, HeapObject::kMapOffset)); RETURN_IF_ABORT(AddNewNode<CheckDynamicValue>( {receiver_map, current_for_in_state.cache_type}, DeoptimizeReason::kWrongMapDynamic)); if (current_for_in_state.receiver == object) { current_for_in_state.receiver_needs_map_check = false; } } // TODO(leszeks): Cache the field index per iteration. ValueNode* field_index; GET_VALUE_OR_ABORT(field_index, BuildLoadFixedArrayElement( current_for_in_state.enum_cache_indices, current_for_in_state.index)); return SetAccumulator( AddNewNode<LoadTaggedFieldByFieldIndex>({object, field_index})); } return MaybeReduceResult::Fail(); } ReduceResult MaglevGraphBuilder::BuildGetKeyedProperty( ValueNode* object, const compiler::FeedbackSource& feedback_source, const compiler::ProcessedFeedback& processed_feedback) { RETURN_IF_DONE(TryBuildGetKeyedPropertyWithEnumeratedKey( object, feedback_source, processed_feedback)); auto build_generic_access = [this, object, &feedback_source]() { ValueNode* context = GetContext(); ValueNode* key = GetAccumulator(); return AddNewNode<GetKeyedGeneric>({context, object, key}, feedback_source); }; switch (processed_feedback.kind()) { case compiler::ProcessedFeedback::kInsufficient: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess); case compiler::ProcessedFeedback::kElementAccess: { // Get the accumulator without conversion. TryBuildElementAccess // will try to pick the best representation. ValueNode* index = current_interpreter_frame_.accumulator(); MaybeReduceResult result = TryBuildElementAccess( object, index, processed_feedback.AsElementAccess(), feedback_source, build_generic_access); PROCESS_AND_RETURN_IF_DONE(result, SetAccumulator); break; } case compiler::ProcessedFeedback::kNamedAccess: { ValueNode* key = GetAccumulator(); RETURN_IF_ABORT(BuildCheckInternalizedStringValueOrByReference( key, processed_feedback.AsNamedAccess().original_name_maybe_thin(), DeoptimizeReason::kKeyedAccessChanged)); compiler::NameRef name = processed_feedback.AsNamedAccess().name(); MaybeReduceResult result = TryReuseKnownPropertyLoad(object, name); PROCESS_AND_RETURN_IF_DONE(result, SetAccumulator); result = TryBuildNamedAccess( object, object, processed_feedback.AsNamedAccess(), feedback_source, compiler::AccessMode::kLoad, build_generic_access); PROCESS_AND_RETURN_IF_DONE(result, SetAccumulator); break; } default: break; } // Create a generic load in the fallthrough. return SetAccumulator(build_generic_access()); } ReduceResult MaglevGraphBuilder::VisitGetKeyedProperty() { // GetKeyedProperty <object> <slot> ValueNode* object = LoadRegister(0); // TODO(leszeks): We don't need to tag the key if it's an Int32 and a simple // monomorphic element load. FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback* processed_feedback = &broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kLoad, std::nullopt); if (processed_feedback->kind() == compiler::ProcessedFeedback::kElementAccess && processed_feedback->AsElementAccess().transition_groups().empty()) { if (auto constant = TryGetConstant<Name>(GetAccumulator())) { compiler::NameRef name = constant.value(); // IsArrayIndex requires IsUniqueName, i.e. thin strings must be // unpacked. Subtle: Refine() still takes the original `name`. compiler::NameRef unpacked_name = name.UnpackIfThin(broker()); if (unpacked_name.IsUniqueName() && !unpacked_name.object()->IsArrayIndex()) { processed_feedback = &processed_feedback->AsElementAccess().Refine(broker(), name); } } } return BuildGetKeyedProperty(object, feedback_source, *processed_feedback); } ReduceResult MaglevGraphBuilder::VisitGetEnumeratedKeyedProperty() { // GetEnumeratedKeyedProperty <object> <enum_index> <cache_type> <slot> ValueNode* object = LoadRegister(0); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kLoad, std::nullopt); return BuildGetKeyedProperty(object, feedback_source, processed_feedback); } ReduceResult MaglevGraphBuilder::VisitLdaModuleVariable() { // LdaModuleVariable <cell_index> <depth> int cell_index = iterator_.GetImmediateOperand(0); size_t depth = iterator_.GetUnsignedImmediateOperand(1); ValueNode* context = GetContextAtDepth(GetContext(), depth); ValueNode* module = LoadAndCacheContextSlot(context, Context::EXTENSION_INDEX, kImmutable, ContextMode::kNoContextCells); ValueNode* exports_or_imports; if (cell_index > 0) { GET_VALUE( exports_or_imports, BuildLoadTaggedField(module, SourceTextModule::kRegularExportsOffset)); // The actual array index is (cell_index - 1). cell_index -= 1; } else { GET_VALUE( exports_or_imports, BuildLoadTaggedField(module, SourceTextModule::kRegularImportsOffset)); // The actual array index is (-cell_index - 1). cell_index = -cell_index - 1; } ValueNode* cell; GET_VALUE(cell, BuildLoadFixedArrayElement(exports_or_imports, cell_index)); ValueNode* value; GET_VALUE(value, BuildLoadTaggedField(cell, Cell::kValueOffset)); SetAccumulator(value); return ReduceResult::Done(); } ValueNode* MaglevGraphBuilder::GetContextAtDepth(ValueNode* context, size_t depth) { MinimizeContextChainDepth(&context, &depth); if (compilation_unit_->info()->specialize_to_function_context()) { compiler::OptionalContextRef maybe_ref = FunctionContextSpecialization::TryToRef(compilation_unit_, context, &depth); if (maybe_ref.has_value()) { context = GetConstant(maybe_ref.value()); } } for (size_t i = 0; i < depth; i++) { context = LoadAndCacheContextSlot(context, Context::PREVIOUS_INDEX, kImmutable, ContextMode::kNoContextCells); EnsureType(context, NodeType::kContext); // The internal consistency of the bytecode guarantees that we cannot end up // with empty types for objects we think are Contexts. DCHECK(!IsEmptyNodeType(GetType(context))); } return context; } ReduceResult MaglevGraphBuilder::VisitStaModuleVariable() { // StaModuleVariable <cell_index> <depth> int cell_index = iterator_.GetImmediateOperand(0); if (V8_UNLIKELY(cell_index < 0)) { // TODO(verwaest): Make this fail as well. return BuildCallRuntime(Runtime::kAbort, {GetSmiConstant(static_cast<int>( AbortReason::kUnsupportedModuleOperation))}); } size_t depth = iterator_.GetUnsignedImmediateOperand(1); ValueNode* context = GetContextAtDepth(GetContext(), depth); ValueNode* module = LoadAndCacheContextSlot(context, Context::EXTENSION_INDEX, kImmutable, ContextMode::kNoContextCells); ValueNode* exports; GET_VALUE(exports, BuildLoadTaggedField( module, SourceTextModule::kRegularExportsOffset)); // The actual array index is (cell_index - 1). cell_index -= 1; ValueNode* cell; GET_VALUE(cell, BuildLoadFixedArrayElement(exports, cell_index)); return BuildStoreTaggedField(cell, GetAccumulator(), Cell::kValueOffset, StoreTaggedMode::kDefault); } ReduceResult MaglevGraphBuilder::BuildLoadGlobal( compiler::NameRef name, compiler::FeedbackSource& feedback_source, TypeofMode typeof_mode) { const compiler::ProcessedFeedback& access_feedback = broker()->GetFeedbackForGlobalAccess(feedback_source); if (access_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericGlobalAccess); } const compiler::GlobalAccessFeedback& global_access_feedback = access_feedback.AsGlobalAccess(); PROCESS_AND_RETURN_IF_DONE(TryBuildGlobalLoad(global_access_feedback), SetAccumulator); ValueNode* context = GetContext(); return SetAccumulator( AddNewNode<LoadGlobal>({context}, name, feedback_source, typeof_mode)); } ReduceResult MaglevGraphBuilder::VisitSetNamedProperty() { // SetNamedProperty <object> <name_index> <slot> ValueNode* object = LoadRegister(0); compiler::NameRef name = GetRefOperand<Name>(1); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kStore, name); auto build_generic_access = [this, object, &name, &feedback_source]() { ValueNode* context = GetContext(); ValueNode* value = GetAccumulator(); return AddNewNode<SetNamedGeneric>({context, object, value}, name, feedback_source); }; switch (processed_feedback.kind()) { case compiler::ProcessedFeedback::kInsufficient: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess); case compiler::ProcessedFeedback::kNamedAccess: RETURN_IF_DONE(TryBuildNamedAccess( object, object, processed_feedback.AsNamedAccess(), feedback_source, compiler::AccessMode::kStore, build_generic_access)); break; default: break; } // Create a generic store in the fallthrough. return build_generic_access(); } ReduceResult MaglevGraphBuilder::VisitSetPrototypeProperties() { // VisitSetPrototypeProperties <name_index> ValueNode* acc = GetAccumulator(); ValueNode* index = GetConstant(GetRefOperand<ObjectBoilerplateDescription>(0)); ValueNode* slot = GetSmiConstant(GetSlotOperand(1).ToInt()); ValueNode* tagged_acc; GET_VALUE_OR_ABORT(tagged_acc, GetTaggedValue(acc)); return BuildCallRuntime( Runtime::kSetPrototypeProperties, {// The object (in accumulator) upon whose prototype boilerplate shall be // applied tagged_acc, // Index of the ObjectBoilerplateDescription whose properties will be // merged in to the above object index, // Array of feedback cells. Needed to instantiate ShareFunctionInfo(s) // from the boilerplate GetConstant(feedback().GetClosureFeedbackCellArrayRef(broker())), // Index of the feedback cell of the first ShareFunctionInfo. We may // assume all other SFI to be tightly packed. slot}); } ReduceResult MaglevGraphBuilder::VisitDefineNamedOwnProperty() { // DefineNamedOwnProperty <object> <name_index> <slot> ValueNode* object = LoadRegister(0); compiler::NameRef name = GetRefOperand<Name>(1); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kStore, name); auto build_generic_access = [this, object, &name, &feedback_source]() { ValueNode* context = GetContext(); ValueNode* value = GetAccumulator(); return AddNewNode<DefineNamedOwnGeneric>({context, object, value}, name, feedback_source); }; switch (processed_feedback.kind()) { case compiler::ProcessedFeedback::kInsufficient: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess); case compiler::ProcessedFeedback::kNamedAccess: RETURN_IF_DONE(TryBuildNamedAccess( object, object, processed_feedback.AsNamedAccess(), feedback_source, compiler::AccessMode::kDefine, build_generic_access)); break; default: break; } // Create a generic store in the fallthrough. return build_generic_access(); } ReduceResult MaglevGraphBuilder::BuildSetKeyedProperty( ValueNode* object, ValueNode* index, compiler::AccessMode access_mode, const compiler::FeedbackSource& feedback_source, const compiler::ProcessedFeedback& processed_feedback, base::FunctionRef<ReduceResult()> generic_setter) { switch (processed_feedback.kind()) { case compiler::ProcessedFeedback::kInsufficient: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess); case compiler::ProcessedFeedback::kNamedAccess: { RETURN_IF_ABORT(BuildCheckInternalizedStringValueOrByReference( index, processed_feedback.AsNamedAccess().original_name_maybe_thin(), DeoptimizeReason::kKeyedAccessChanged)); RETURN_IF_DONE(TryBuildNamedAccess( object, object, processed_feedback.AsNamedAccess(), feedback_source, access_mode, generic_setter)); break; } case compiler::ProcessedFeedback::kElementAccess: { RETURN_IF_DONE(TryBuildElementAccess(object, index, processed_feedback.AsElementAccess(), feedback_source, generic_setter)); break; } default: break; } // Create a generic store in the fallthrough. return generic_setter(); } ReduceResult MaglevGraphBuilder::VisitSetKeyedProperty() { // SetKeyedProperty <object> <key> <slot> ValueNode* object = LoadRegister(0); ValueNode* key = LoadRegister(1); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kStore, std::nullopt); auto build_generic_access = [this, object, key, &feedback_source]() { ValueNode* context = GetContext(); ValueNode* value = GetAccumulator(); return AddNewNode<SetKeyedGeneric>({context, object, key, value}, feedback_source); }; return BuildSetKeyedProperty(object, key, compiler::AccessMode::kStore, feedback_source, processed_feedback, build_generic_access); } ReduceResult MaglevGraphBuilder::VisitDefineKeyedOwnProperty() { // DefineKeyedOwnProperty <object> <key> <flags> <slot> ValueNode* object = LoadRegister(0); ValueNode* key = LoadRegister(1); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kDefine, std::nullopt); auto build_generic_access = [this, object, key, &feedback_source]() { ValueNode* context = GetContext(); ValueNode* value = GetAccumulator(); ValueNode* flags = GetSmiConstant(GetFlag8Operand(2)); return AddNewNode<DefineKeyedOwnGeneric>( {context, object, key, value, flags}, feedback_source); }; return BuildSetKeyedProperty(object, key, compiler::AccessMode::kDefine, feedback_source, processed_feedback, build_generic_access); } ReduceResult MaglevGraphBuilder::VisitStaInArrayLiteral() { // StaInArrayLiteral <object> <index> <slot> ValueNode* object = LoadRegister(0); ValueNode* index = LoadRegister(1); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForPropertyAccess( feedback_source, compiler::AccessMode::kStoreInLiteral, std::nullopt); auto build_generic_access = [this, object, index, &feedback_source]() { ValueNode* context = GetContext(); ValueNode* value = GetAccumulator(); return AddNewNode<StoreInArrayLiteralGeneric>( {context, object, index, value}, feedback_source); }; switch (processed_feedback.kind()) { case compiler::ProcessedFeedback::kInsufficient: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess); case compiler::ProcessedFeedback::kElementAccess: { RETURN_IF_DONE(TryBuildElementAccess( object, index, processed_feedback.AsElementAccess(), feedback_source, build_generic_access)); break; } default: break; } // Create a generic store in the fallthrough. return build_generic_access(); } ReduceResult MaglevGraphBuilder::VisitDefineKeyedOwnPropertyInLiteral() { ValueNode* object = LoadRegister(0); ValueNode* name = LoadRegister(1); ValueNode* value = GetAccumulator(); ValueNode* flags = GetSmiConstant(GetFlag8Operand(2)); ValueNode* slot = GetTaggedIndexConstant(GetSlotOperand(3).ToInt()); ValueNode* feedback_vector = GetConstant(feedback()); return BuildCallRuntime(Runtime::kDefineKeyedOwnPropertyInLiteral, {object, name, value, flags, feedback_vector, slot}); } ReduceResult MaglevGraphBuilder::VisitAdd() { return VisitBinaryOperation<Operation::kAdd>(); } ReduceResult MaglevGraphBuilder::VisitAdd_StringConstant_Internalize() { ValueNode* left = LoadRegister(0); ValueNode* right = GetAccumulator(); FeedbackNexus nexus = FeedbackNexusForOperand(1); ValueNode* slot = GetSmiConstant(nexus.slot().ToInt()); using ASVariant = AddStringConstantAndInternalizeVariant; uint8_t flags = GetFlag8Operand(2); CHECK(flags == static_cast<uint8_t>(ASVariant::kLhsIsStringConstant) || flags == static_cast<uint8_t>(ASVariant::kRhsIsStringConstant)); const ASVariant as_variant = static_cast<ASVariant>(flags); ReduceResult result = ReduceResult::Done(); if (is_inline()) { static constexpr auto kTargetL = Builtin::kAddLhsIsStringConstantInternalizeWithVector; static constexpr auto kTargetR = Builtin::kAddRhsIsStringConstantInternalizeWithVector; ValueNode* vector = GetConstant(feedback()); result = as_variant == ASVariant::kLhsIsStringConstant ? BuildCallBuiltinWithTaggedInputs<kTargetL>( {left, right, slot, vector}) : BuildCallBuiltinWithTaggedInputs<kTargetR>( {left, right, slot, vector}); } else { static constexpr auto kTargetL = Builtin::kAddLhsIsStringConstantInternalizeTrampoline; static constexpr auto kTargetR = Builtin::kAddRhsIsStringConstantInternalizeTrampoline; result = as_variant == ASVariant::kLhsIsStringConstant ? BuildCallBuiltinWithTaggedInputs<kTargetL>({left, right, slot}) : BuildCallBuiltinWithTaggedInputs<kTargetR>({left, right, slot}); } return SetAccumulator(result); } ReduceResult MaglevGraphBuilder::VisitSub() { return VisitBinaryOperation<Operation::kSubtract>(); } ReduceResult MaglevGraphBuilder::VisitMul() { return VisitBinaryOperation<Operation::kMultiply>(); } ReduceResult MaglevGraphBuilder::VisitDiv() { return VisitBinaryOperation<Operation::kDivide>(); } ReduceResult MaglevGraphBuilder::VisitMod() { return VisitBinaryOperation<Operation::kModulus>(); } ReduceResult MaglevGraphBuilder::VisitExp() { return VisitBinaryOperation<Operation::kExponentiate>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseOr() { return VisitBinaryOperation<Operation::kBitwiseOr>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseXor() { return VisitBinaryOperation<Operation::kBitwiseXor>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseAnd() { return VisitBinaryOperation<Operation::kBitwiseAnd>(); } ReduceResult MaglevGraphBuilder::VisitShiftLeft() { return VisitBinaryOperation<Operation::kShiftLeft>(); } ReduceResult MaglevGraphBuilder::VisitShiftRight() { return VisitBinaryOperation<Operation::kShiftRight>(); } ReduceResult MaglevGraphBuilder::VisitShiftRightLogical() { return VisitBinaryOperation<Operation::kShiftRightLogical>(); } ReduceResult MaglevGraphBuilder::VisitAddSmi() { return VisitBinarySmiOperation<Operation::kAdd>(); } ReduceResult MaglevGraphBuilder::VisitSubSmi() { return VisitBinarySmiOperation<Operation::kSubtract>(); } ReduceResult MaglevGraphBuilder::VisitMulSmi() { return VisitBinarySmiOperation<Operation::kMultiply>(); } ReduceResult MaglevGraphBuilder::VisitDivSmi() { return VisitBinarySmiOperation<Operation::kDivide>(); } ReduceResult MaglevGraphBuilder::VisitModSmi() { return VisitBinarySmiOperation<Operation::kModulus>(); } ReduceResult MaglevGraphBuilder::VisitExpSmi() { return VisitBinarySmiOperation<Operation::kExponentiate>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseOrSmi() { return VisitBinarySmiOperation<Operation::kBitwiseOr>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseXorSmi() { return VisitBinarySmiOperation<Operation::kBitwiseXor>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseAndSmi() { return VisitBinarySmiOperation<Operation::kBitwiseAnd>(); } ReduceResult MaglevGraphBuilder::VisitShiftLeftSmi() { return VisitBinarySmiOperation<Operation::kShiftLeft>(); } ReduceResult MaglevGraphBuilder::VisitShiftRightSmi() { return VisitBinarySmiOperation<Operation::kShiftRight>(); } ReduceResult MaglevGraphBuilder::VisitShiftRightLogicalSmi() { return VisitBinarySmiOperation<Operation::kShiftRightLogical>(); } ReduceResult MaglevGraphBuilder::VisitInc() { return VisitUnaryOperation<Operation::kIncrement>(); } ReduceResult MaglevGraphBuilder::VisitDec() { return VisitUnaryOperation<Operation::kDecrement>(); } ReduceResult MaglevGraphBuilder::VisitNegate() { return VisitUnaryOperation<Operation::kNegate>(); } ReduceResult MaglevGraphBuilder::VisitBitwiseNot() { return VisitUnaryOperation<Operation::kBitwiseNot>(); } ReduceResult MaglevGraphBuilder::VisitToBooleanLogicalNot() { return SetAccumulator(BuildToBoolean</* flip */ true>(GetAccumulator())); } ReduceResult MaglevGraphBuilder::BuildLogicalNot(ValueNode* value) { RETURN_IF_DONE(reducer_.TryFoldLogicalNot(value)); return AddNewNode<LogicalNot>({value}); } ReduceResult MaglevGraphBuilder::VisitLogicalNot() { // Invariant: accumulator must already be a boolean value. return SetAccumulator(BuildLogicalNot(GetAccumulator())); } ReduceResult MaglevGraphBuilder::VisitTypeOf() { ValueNode* value = GetAccumulator(); PROCESS_AND_RETURN_IF_DONE(TryReduceTypeOf(value), SetAccumulator); FeedbackNexus nexus = FeedbackNexusForOperand(0); TypeOfFeedback::Result feedback = nexus.GetTypeOfFeedback(); switch (feedback) { case TypeOfFeedback::kNone: return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForTypeOf); case TypeOfFeedback::kSmi: RETURN_IF_ABORT(BuildCheckSmi(value)); SetAccumulator(GetRootConstant(RootIndex::knumber_string)); return ReduceResult::Done(); case TypeOfFeedback::kNumber: RETURN_IF_ABORT(BuildCheckNumber(value)); SetAccumulator(GetRootConstant(RootIndex::knumber_string)); return ReduceResult::Done(); case TypeOfFeedback::kString: RETURN_IF_ABORT(BuildCheckString(value)); SetAccumulator(GetRootConstant(RootIndex::kstring_string)); return ReduceResult::Done(); case TypeOfFeedback::kFunction: RETURN_IF_ABORT(AddNewNode<CheckDetectableCallable>( {value}, GetCheckType(GetType(value), value))); EnsureType(value, NodeType::kCallable); SetAccumulator(GetRootConstant(RootIndex::kfunction_string)); return ReduceResult::Done(); default: break; } return SetAccumulator( BuildCallBuiltinWithTaggedInputs<Builtin::kTypeof>({value})); } ReduceResult MaglevGraphBuilder::VisitDeletePropertyStrict() { ValueNode* object = LoadRegister(0); ValueNode* key = GetAccumulator(); ValueNode* context = GetContext(); return SetAccumulator(AddNewNode<DeleteProperty>({context, object, key}, LanguageMode::kStrict)); } ReduceResult MaglevGraphBuilder::VisitDeletePropertySloppy() { ValueNode* object = LoadRegister(0); ValueNode* key = GetAccumulator(); ValueNode* context = GetContext(); return SetAccumulator(AddNewNode<DeleteProperty>({context, object, key}, LanguageMode::kSloppy)); } ReduceResult MaglevGraphBuilder::VisitGetSuperConstructor() { ValueNode* active_function = GetAccumulator(); // TODO(victorgomes): Maybe BuildLoadTaggedField should support constants // instead. if (compiler::OptionalHeapObjectRef constant = TryGetConstant<HeapObject>(active_function)) { compiler::MapRef map = constant->map(broker()); if (map.is_stable()) { broker()->dependencies()->DependOnStableMap(map); ValueNode* map_proto = GetConstant(map.prototype(broker())); StoreRegister(iterator_.GetRegisterOperand(0), map_proto); return ReduceResult::Done(); } } ValueNode* map; GET_VALUE(map, BuildLoadTaggedField(active_function, HeapObject::kMapOffset)); ValueNode* map_proto; GET_VALUE(map_proto, BuildLoadTaggedField(map, Map::kPrototypeOffset)); StoreRegister(iterator_.GetRegisterOperand(0), map_proto); return ReduceResult::Done(); } bool MaglevGraphBuilder::HasValidInitialMap( compiler::JSFunctionRef new_target, compiler::JSFunctionRef constructor) { if (!new_target.map(broker()).has_prototype_slot()) return false; if (!new_target.has_initial_map(broker())) return false; compiler::MapRef initial_map = new_target.initial_map(broker()); compiler::OptionalObjectRef ctor = initial_map.GetConstructor(broker()); return ctor.has_value() && ctor->equals(constructor); } MaybeReduceResult MaglevGraphBuilder::TryBuildFindNonDefaultConstructorOrConstruct( ValueNode* this_function, ValueNode* new_target, std::pair<interpreter::Register, interpreter::Register> result) { // See also: // JSNativeContextSpecialization::ReduceJSFindNonDefaultConstructorOrConstruct compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(this_function); if (!maybe_constant) return {}; compiler::MapRef function_map = maybe_constant->map(broker()); compiler::HeapObjectRef current = function_map.prototype(broker()); // TODO(v8:13091): Don't produce incomplete stack traces when debug is active. // We already deopt when a breakpoint is set. But it would be even nicer to // avoid producting incomplete stack traces when when debug is active, even if // there are no breakpoints - then a user inspecting stack traces via Dev // Tools would always see the full stack trace. while (true) { if (!current.IsJSFunction()) return {}; compiler::JSFunctionRef current_function = current.AsJSFunction(); // If there are class fields, bail out. TODO(v8:13091): Handle them here. if (current_function.shared(broker()) .requires_instance_members_initializer()) { return {}; } // If there are private methods, bail out. TODO(v8:13091): Handle them here. if (current_function.context(broker()) .scope_info(broker()) .ClassScopeHasPrivateBrand()) { return {}; } FunctionKind kind = current_function.shared(broker()).kind(); if (kind != FunctionKind::kDefaultDerivedConstructor) { // The hierarchy walk will end here; this is the last change to bail out // before creating new nodes. if (!broker()->dependencies()->DependOnArrayIteratorProtector()) { return {}; } if (kind == FunctionKind::kDefaultBaseConstructor) { // Store the result register first, so that a lazy deopt in // `FastNewObject` writes `true` to this register. StoreRegister(result.first, GetBooleanConstant(true)); ValueNode* object; compiler::OptionalJSFunctionRef new_target_function = TryGetConstant<JSFunction>(new_target); if (new_target_function && HasValidInitialMap(new_target_function.value(), current_function)) { GET_VALUE(object, BuildInlinedAllocation( CreateJSConstructor(new_target_function.value()), AllocationType::kYoung)); } else { // We've already stored "true" into result.first, so a deopt here just // has to store result.second. LazyDeoptResultLocationScope new_location(this, result.second, 1); GET_VALUE_OR_ABORT( object, BuildCallBuiltinWithTaggedInputs<Builtin::kFastNewObject>( {GetConstant(current_function), new_target})); } StoreRegister(result.second, object); } else { StoreRegister(result.first, GetBooleanConstant(false)); StoreRegister(result.second, GetConstant(current)); } broker()->dependencies()->DependOnStablePrototypeChain( function_map, WhereToStart::kStartAtReceiver, current_function); return ReduceResult::Done(); } // Keep walking up the class tree. current = current_function.map(broker()).prototype(broker()); } } ReduceResult MaglevGraphBuilder::VisitFindNonDefaultConstructorOrConstruct() { ValueNode* this_function = LoadRegister(0); ValueNode* new_target = LoadRegister(1); auto register_pair = iterator_.GetRegisterPairOperand(2); RETURN_IF_DONE(TryBuildFindNonDefaultConstructorOrConstruct( this_function, new_target, register_pair)); CallBuiltin* result; GET_VALUE_OR_ABORT(result, BuildCallBuiltinWithTaggedInputs< Builtin::kFindNonDefaultConstructorOrConstruct>( {this_function, new_target})); StoreRegisterPair(register_pair, result); return ReduceResult::Done(); } namespace { void ForceEscapeIfAllocation(ValueNode* value) { if (InlinedAllocation* alloc = value->TryCast<InlinedAllocation>()) { alloc->ForceEscaping(); } } } // namespace ReduceResult MaglevGraphBuilder::BuildInlineFunction( SourcePosition call_site_position, ValueNode* context, ValueNode* function, ValueNode* new_target) { DCHECK(is_inline()); DCHECK_GT(caller_details_->arguments.size(), 0); compiler::SharedFunctionInfoRef shared = compilation_unit_->shared_function_info(); compiler::BytecodeArrayRef bytecode = compilation_unit_->bytecode(); compiler::FeedbackVectorRef feedback = compilation_unit_->feedback(); // TODO(dmercadier): when inlining a resumable function, we're really just // inlining the initialization part, since the rest of the function is only // executed when calling .next() on the generator. We're currently relying on // optimizations to detect this statically and remove the whole graph except // for the initialization of the generator object, but it would make sense to // only just inling the initialization part from the start. if (is_tracing_enabled()) { if (v8_flags.maglev_print_inlined && v8_flags.maglev_print_bytecode) { std::cout << "\n----- Inlining " << Brief(*shared.object()) << " with bytecode -----" << std::endl; BytecodeArray::Disassemble(bytecode.object(), std::cout); if (v8_flags.maglev_print_feedback) { i::Print(*feedback.object(), std::cout); } } else if (v8_flags.trace_maglev_graph_building || v8_flags.trace_maglev_inlining) { std::cout << "== Inlining " << shared.object() << std::endl; } } graph()->inlined_functions().push_back( OptimizedCompilationInfo::InlinedFunctionHolder( shared.object(), bytecode.object(), call_site_position)); if (feedback.object()->invocation_count_before_stable(kRelaxedLoad) > v8_flags.invocation_count_for_early_optimization) { compilation_unit_->info()->set_could_not_inline_all_candidates(); } inlining_id_ = static_cast<int>(graph()->inlined_functions().size() - 1); if (should_abort_compilation_) { // We will abort the compilation at the end. return BuildAbort(AbortReason::kMaglevGraphBuildingFailed); } DCHECK_NE(inlining_id_, SourcePosition::kNotInlined); reducer_.SetBytecodeOffset(entrypoint_); reducer_.SetStartSourcePosition(inlining_id_); // Manually create the prologue of the inner function graph, so that we // can manually set up the arguments. DCHECK_NOT_NULL(current_block()); // Set receiver. SetArgument(0, caller_details_->arguments[0]); // The inlined function could call a builtin that iterates the frame, the // receiver needs to have been materialized. // TODO(victorgomes): Can we relax this requirement? Maybe we can allocate the // object lazily? This is also only required if the inlined function is not a // leaf (ie. it calls other functions). ForceEscapeIfAllocation(caller_details_->arguments[0]); // Set remaining arguments. RootConstant* undefined_constant = GetRootConstant(RootIndex::kUndefinedValue); int args_count = static_cast<int>(caller_details_->arguments.size()) - 1; int formal_parameter_count = compilation_unit_->parameter_count() - 1; for (int i = 0; i < formal_parameter_count; i++) { ValueNode* arg_value = i < args_count ? caller_details_->arguments[i + 1] : undefined_constant; SetArgument(i + 1, arg_value); } inlined_new_target_ = new_target; BuildRegisterFrameInitialization(context, function, new_target); BuildMergeStates(); EndPrologue(); in_prologue_ = false; // Build the inlined function body. BuildBody(); // All returns in the inlined body jump to a merge point one past the bytecode // length (i.e. at offset bytecode.length()). If there isn't one already, // create a block at this fake offset and have it jump out of the inlined // function, into a new block that we create which resumes execution of the // outer function. if (!current_block()) { // If we don't have a merge state at the inline_exit_offset, then there is // no control flow that reaches the end of the inlined function, either // because of infinite loops or deopts if (merge_states_[inline_exit_offset()] == nullptr) { TRACE("== Finished inlining (abort) " << shared.object()); return ReduceResult::DoneWithAbort(); } ProcessMergePoint(inline_exit_offset(), /*preserve_kna*/ false); StartNewBlock(inline_exit_offset(), /*predecessor*/ nullptr); } TRACE("== Finished inlining " << shared.object()); // Pull the returned accumulator value out of the inlined function's final // merged return state. return current_interpreter_frame_.accumulator(); } #define TRACE_INLINING(...) \ do { \ if (V8_UNLIKELY(flags_.trace_inlining && is_tracing_enabled())) \ StdoutStream{} << __VA_ARGS__ << std::endl; \ } while (false) #define TRACE_CANNOT_INLINE(...) \ TRACE_INLINING(" cannot inline " << shared << ": " << __VA_ARGS__) bool MaglevGraphBuilder::CanInlineCall(compiler::SharedFunctionInfoRef shared, float call_frequency) { if (static_cast<int>(graph()->inlined_functions().size()) >= SourcePosition::MaxInliningId()) { compilation_unit_->info()->set_could_not_inline_all_candidates(); TRACE_CANNOT_INLINE("maximum inlining ids"); return false; } if (compilation_unit_->shared_function_info().equals(shared)) { TRACE_CANNOT_INLINE("direct recursion"); return false; } SharedFunctionInfo::Inlineability inlineability = shared.GetInlineability(CodeKind::MAGLEV, broker()); if (inlineability != SharedFunctionInfo::Inlineability::kIsInlineable) { TRACE_CANNOT_INLINE(inlineability); return false; } compiler::BytecodeArrayRef bytecode = shared.GetBytecodeArray(broker()); if (call_frequency < flags_.min_inlining_frequency) { TRACE_CANNOT_INLINE("call frequency (" << call_frequency << ") < minimum threshold (" << flags_.min_inlining_frequency << ")"); return false; } if (bytecode.length() > flags_.max_inlined_bytecode_size) { TRACE_CANNOT_INLINE("big function, size (" << bytecode.length() << ") >= max-size (" << flags_.max_inlined_bytecode_size << ")"); return false; } return true; } bool MaglevGraphBuilder::IsFunctionCandidateForEagerInlining( compiler::SharedFunctionInfoRef shared, CallArguments& args) { compiler::BytecodeArrayRef bytecode = shared.GetBytecodeArray(broker()); if (bytecode.length() < flags_.max_eager_inlined_bytecode) { TRACE_INLINING(" greedy inlining " << shared << ": small function, skipping max-depth"); return true; } // Small-ish functions that have float64 inputs are considered small. // TODO(victorgomes): Evaluate why this is not worth for Maglev, it regresses // crypto benchmarks. if (is_turbolev() && inlining_depth() <= flags_.max_inline_depth && bytecode.length() < flags_.max_inlined_bytecode_size_small_with_heapnum_in_out && args.mode() == CallArguments::kDefault) { bool has_float_arg = false; for (size_t i = 1; i < args.count_with_receiver(); i++) { if (args[i] && args[i]->is_float64_or_holey_float64()) { has_float_arg = true; break; } } if (has_float_arg) { TRACE_INLINING(" greedy inlining " << shared << ": small function with heap number inputs"); return true; } } return false; } bool MaglevGraphBuilder::ShouldEagerInlineCall( compiler::SharedFunctionInfoRef shared, CallArguments& args) { if (!IsFunctionCandidateForEagerInlining(shared, args)) { // Functions that aren't small aren't greedily inlined. return false; } if (graph()->total_inlined_bytecode_size_small() >= flags_.max_inlined_bytecode_size_small_total) { compilation_unit_->info()->set_could_not_inline_all_candidates(); TRACE_CANNOT_INLINE("maximum inlined bytecode size for small functions"); return false; } // TODO(olivf): This is a temporary stopgap to prevent infinite recursion when // inlining, because we currently exempt small functions from some of the // negative heuristics. We should refactor these heuristics and make sure they // make sense in the presence of (mutually) recursive inlining. Please do // *not* return true before this check. if (inlining_depth() > flags_.max_inline_depth_small) { TRACE_CANNOT_INLINE("inlining depth (" << inlining_depth() << ") > max_inline_depth_small (" << flags_.max_inline_depth_small << ")"); return false; } return true; } MaybeReduceResult MaglevGraphBuilder::TryBuildInlineCall( ValueNode* context, ValueNode* function, ValueNode* new_target, JSDispatchHandle dispatch_handle, compiler::SharedFunctionInfoRef shared, compiler::FeedbackCellRef feedback_cell, CallArguments& args, const compiler::FeedbackSource& feedback_source) { DCHECK_EQ(args.mode(), CallArguments::kDefault); if (!feedback_cell.feedback_vector(broker())) { // TODO(verwaest): Soft deopt instead? TRACE_CANNOT_INLINE("it has not been compiled/run with feedback yet"); return {}; } float feedback_frequency = 0.0f; if (feedback_source.IsValid()) { compiler::ProcessedFeedback const& feedback = broker()->GetFeedbackForCall(feedback_source); feedback_frequency = feedback.IsInsufficient() ? 0.0f : feedback.AsCall().frequency(); } else if (IsAccessorFunction(shared.kind()) && is_turbolev()) { // Accessors don't have feedback since there's no CallIC. This allows // inlining them in turbolev. feedback_frequency = 1.0f; } float call_frequency = feedback_frequency * GetCurrentCallFrequency(); if (!CanInlineCall(shared, call_frequency)) return {}; compiler::BytecodeArrayRef bytecode = shared.GetBytecodeArray(broker()); if (ShouldEagerInlineCall(shared, args)) { graph()->add_inlined_bytecode_size_small(bytecode.length()); return BuildEagerInlineCall(context, function, new_target, shared, feedback_cell, args, call_frequency); } if (!is_turbolev() && only_inline_small_) { return {}; } if (inlining_depth() > flags_.max_inline_depth) { TRACE_CANNOT_INLINE("inlining depth (" << inlining_depth() << ") > max_inline_depth (" << flags_.max_inline_depth << ")"); return {}; } if (flags_.max_inline_depth == 1) { if (compiler::OptionalJSFunctionRef target_function = TryGetConstant<JSFunction>(function)) { if (compiler::OptionalCodeRef code = target_function->code(broker())) { if (code->GetInlinedBytecodeSize()) { return {}; } } } } if (!flags_.is_non_eager_inlining_enabled) { if (graph()->total_inlined_bytecode_size() >= flags_.max_inlined_bytecode_size_cumulative) { compilation_unit_->info()->set_could_not_inline_all_candidates(); TRACE_CANNOT_INLINE("maximum inlined bytecode size"); return {}; } graph()->add_inlined_bytecode_size(bytecode.length()); return BuildEagerInlineCall(context, function, new_target, shared, feedback_cell, args, call_frequency); } TRACE_INLINING(" considering " << shared << " for inlining"); auto [result, arguments] = GetArgumentsAsArrayOfValueNodes(shared, args); RETURN_IF_ABORT(result); // Creating the CallKnownJSFUnction node will conservatively clear the // unstable maps from the KNA. So, we save them before doing this, since this // could clear maps that don't need to be cleared. KnownNodeAspects* call_aspects = known_node_aspects().Clone(zone()); CallKnownJSFunction* generic_call; GET_VALUE_OR_ABORT(generic_call, BuildCallKnownJSFunction( context, function, new_target, dispatch_handle, shared, arguments)); // Note: We point to the generic call exception handler instead of // jump_targets_ because the former contains a BasicBlockRef that is // guaranteed to be updated correctly upon exception block creation. // BuildLoopForPeeling might reset the BasicBlockRef in jump_targets_. If this // happens, inlined calls within the peeled loop would incorrectly point to // the loop's exception handler instead of the original call's. CatchBlockDetails catch_details = GetTryCatchBlockForNonEagerInlining( generic_call->exception_handler_info()); catch_details.deopt_frame_distance++; float score = (call_frequency / bytecode.length()) * (IsInsideLoop() ? 1.5 : 1.0); MaglevCallSiteInfo* call_site = zone()->New<MaglevCallSiteInfo>( MaglevCallerDetails{ arguments, &generic_call->lazy_deopt_info()->top_frame(), call_aspects, loop_effects_, unobserved_context_slot_stores_, catch_details, GetLoopDepth(), peeled_iteration_count_, /* is_eager_inline */ false, /* is_small_function */ false, call_frequency, current_inlining_tree_debug_info_}, generic_call, feedback_cell, score, bytecode.length()); graph()->inlineable_calls().push(call_site); return generic_call; } ReduceResult MaglevGraphBuilder::BuildEagerInlineCall( ValueNode* context, ValueNode* function, ValueNode* new_target, compiler::SharedFunctionInfoRef shared, compiler::FeedbackCellRef feedback_cell, CallArguments& args, float call_frequency) { DCHECK_EQ(args.mode(), CallArguments::kDefault); // Merge catch block state if needed. CatchBlockDetails catch_block_details = GetCurrentTryCatchBlock(); if (catch_block_details.ref && catch_block_details.exception_handler_was_used) { if (IsInsideTryBlock()) { // Merge the current state into the handler state. GetCatchBlockFrameState()->MergeThrow( this, compilation_unit_, *current_interpreter_frame_.known_node_aspects()); } catch_block_details.deopt_frame_distance++; } // Create a new compilation unit. MaglevCompilationUnit* inner_unit = MaglevCompilationUnit::NewInner( zone(), compilation_unit_, shared, feedback_cell); // Propagate details. auto [arguments_result, arguments_vector] = GetArgumentsAsArrayOfValueNodes(shared, args); RETURN_IF_ABORT(arguments_result); DeoptFrame* deopt_frame = GetDeoptFrameForEagerCall(inner_unit, function, arguments_vector); MaglevCallerDetails* caller_details = zone()->New<MaglevCallerDetails>( arguments_vector, deopt_frame, current_interpreter_frame_.known_node_aspects(), loop_effects_, unobserved_context_slot_stores_, catch_block_details, GetLoopDepth(), peeled_iteration_count_, /* is_eager_inline */ true, /* is_small_function */ true, call_frequency, current_inlining_tree_debug_info_); // Create a new graph builder for the inlined function. MaglevGraphBuilder inner_graph_builder(local_isolate_, inner_unit, graph_, caller_details); // Set the inner graph builder to build in the current block. reducer_.FlushNodesToBlock(); inner_graph_builder.set_current_block(current_block()); // Build inline function. ReduceResult result = inner_graph_builder.BuildInlineFunction( GetCurrentSourcePosition(), context, function, new_target); // Propagate back (or reset) builder state. unobserved_context_slot_stores_ = inner_graph_builder.unobserved_context_slot_stores_; latest_checkpointed_frame_ = nullptr; ClearCurrentAllocationBlock(); if (result.IsDoneWithAbort()) { DCHECK_NULL(inner_graph_builder.current_block()); set_current_block(nullptr); should_abort_compilation_ = inner_graph_builder.should_abort_compilation(); return ReduceResult::DoneWithAbort(); } // Propagate frame information back to the caller. current_interpreter_frame_.set_known_node_aspects( inner_graph_builder.current_interpreter_frame_.known_node_aspects()); current_for_in_state.receiver_needs_map_check = inner_graph_builder.current_for_in_state.receiver_needs_map_check; // Resume execution using the final block of the inner builder. inner_graph_builder.reducer_.FlushNodesToBlock(); set_current_block(inner_graph_builder.current_block()); DCHECK(result.IsDoneWithValue()); return result; } namespace { bool CanInlineArrayIteratingBuiltin(compiler::JSHeapBroker* broker, const PossibleMaps& maps, ElementsKind* kind_return) { DCHECK_NE(0, maps.size()); *kind_return = maps.at(0).elements_kind(); for (compiler::MapRef map : maps) { if (!map.supports_fast_array_iteration(broker) || !UnionElementsKindUptoSize(kind_return, map.elements_kind())) { return false; } } return true; } } // namespace MaybeReduceResult MaglevGraphBuilder::TryReduceArrayIsArray( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) return GetBooleanConstant(false); ValueNode* node = args[0]; if (CheckType(node, NodeType::kJSArray)) { return GetBooleanConstant(true); } if (auto possible_maps = known_node_aspects().TryGetPossibleMaps(node)) { bool has_array_map = false; bool has_proxy_map = false; bool has_other_map = false; for (compiler::MapRef map : *possible_maps) { InstanceType type = map.instance_type(); if (InstanceTypeChecker::IsJSArray(type)) { has_array_map = true; } else if (InstanceTypeChecker::IsJSProxy(type)) { has_proxy_map = true; } else { has_other_map = true; } } if ((has_array_map ^ has_other_map) && !has_proxy_map) { if (has_array_map) { if (auto node_info = known_node_aspects().TryGetInfoFor(node)) { node_info->IntersectType(NodeType::kJSArray); } } return GetBooleanConstant(has_array_map); } } if (is_turbolev()) { return AddNewNode<ObjectIsArray>({node}); } // TODO(dmercadier): consider supporting ObjectIsArray in Maglev. return {}; } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayForEach( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = args.receiver(); if (!receiver) return {}; if (args.count() < 1) { FAIL(" to reduce Array.prototype.forEach - not enough arguments"); } auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_maps) { FAIL(" to reduce Array.prototype.forEach - receiver map is unknown"); } ElementsKind elements_kind; if (!CanInlineArrayIteratingBuiltin(broker(), *possible_maps, &elements_kind)) { FAIL( " to reduce Array.prototype.forEach - doesn't support fast array " "iteration or incompatible maps"); } // TODO(leszeks): May only be needed for holey elements kinds. if (!broker()->dependencies()->DependOnNoElementsProtector()) { FAIL( " to reduce Array.prototype.forEach - invalidated no elements " "protector"); } ValueNode* callback = args[0]; if (!callback->is_tagged()) { FAIL(" to reduce Array.prototype.forEach - callback is untagged value") } auto get_lazy_deopt_scope = [this](compiler::JSFunctionRef target, ValueNode* receiver, ValueNode* callback, ValueNode* this_arg, ValueNode* index_int32, ValueNode* next_index_int32, ValueNode* original_length) { return LazyDeoptFrameScope( this, Builtin::kArrayForEachLoopLazyDeoptContinuation, target, base::VectorOf<ValueNode*>({receiver, callback, this_arg, next_index_int32, original_length})); }; auto get_eager_deopt_scope = [this](compiler::JSFunctionRef target, ValueNode* receiver, ValueNode* callback, ValueNode* this_arg, ValueNode* index_int32, ValueNode* next_index_int32, ValueNode* original_length) { return EagerDeoptFrameScope( this, Builtin::kArrayForEachLoopEagerDeoptContinuation, target, base::VectorOf<ValueNode*>({receiver, callback, this_arg, next_index_int32, original_length})); }; MaybeReduceResult builtin_result = TryReduceArrayIteratingBuiltin( "Array.prototype.forEach", target, args, get_eager_deopt_scope, get_lazy_deopt_scope); if (builtin_result.IsFail() || builtin_result.IsDoneWithAbort()) { return builtin_result; } DCHECK(builtin_result.IsDoneWithoutPayload()); return GetRootConstant(RootIndex::kUndefinedValue); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayMap( compiler::JSFunctionRef target, CallArguments& args) { if (!is_turbolev()) { return {}; } if (!broker()->dependencies()->DependOnArraySpeciesProtector()) { FAIL( " to reduce Array.prototype.map - invalidated array species " "protector"); } compiler::NativeContextRef native_context = broker()->target_native_context(); compiler::MapRef holey_smi_map = native_context.GetInitialJSArrayMap(broker(), HOLEY_SMI_ELEMENTS); compiler::MapRef holey_map = native_context.GetInitialJSArrayMap(broker(), HOLEY_ELEMENTS); compiler::MapRef holey_double_map = native_context.GetInitialJSArrayMap(broker(), HOLEY_DOUBLE_ELEMENTS); ValueNode* result_array = nullptr; // We don't need to check the "array constructor inlining" protector (probably // Turbofan wouldn't need either for the Array.p.map case, but it does // anyway). CanInlineArrayIteratingBuiltin allows only fast mode maps, and if // at runtime we encounter a dictionary mode map, we will deopt. If the // feedback contains dictionary mode maps to start with, we won't even lower // Array.prototype.map here, so there's no risk for a deopt loop. // We always inline the Array ctor here, even if Turbofan doesn't. Since the // top frame cannot be deopted because of the allocation, we don't need a // DeoptFrameScope here. auto initial_callback = [this, &result_array, holey_smi_map](ValueNode* length_smi) { ValueNode* elements; GET_VALUE_OR_ABORT(elements, AddNewNode<CreateFastArrayElements>( {length_smi}, AllocationType::kYoung)); VirtualObject* array; GET_VALUE_OR_ABORT( array, CreateJSArray(holey_smi_map, holey_smi_map.instance_size(), length_smi)); array->set(JSArray::kElementsOffset, elements); GET_VALUE_OR_ABORT(result_array, BuildInlinedAllocation(array, AllocationType::kYoung)); return ReduceResult::Done(); }; auto process_element_callback = [this, &holey_map, &holey_double_map, &result_array](ValueNode* index_int32, ValueNode* element) { return AddNewNode<TransitionAndStoreArrayElement>( {result_array, index_int32, element}, holey_map, holey_double_map); }; auto get_lazy_deopt_scope = [this, &result_array]( compiler::JSFunctionRef target, ValueNode* receiver, ValueNode* callback, ValueNode* this_arg, ValueNode* index_int32, ValueNode* next_index_int32, ValueNode* original_length) { DCHECK_NOT_NULL(result_array); return LazyDeoptFrameScope( this, Builtin::kArrayMapLoopLazyDeoptContinuation, target, base::VectorOf<ValueNode*>({receiver, callback, this_arg, result_array, index_int32, original_length})); }; auto get_eager_deopt_scope = [this, &result_array]( compiler::JSFunctionRef target, ValueNode* receiver, ValueNode* callback, ValueNode* this_arg, ValueNode* index_int32, ValueNode* next_index_int32, ValueNode* original_length) { DCHECK_NOT_NULL(result_array); return EagerDeoptFrameScope( this, Builtin::kArrayMapLoopEagerDeoptContinuation, target, base::VectorOf<ValueNode*>({receiver, callback, this_arg, result_array, next_index_int32, original_length})); }; MaybeReduceResult builtin_result = TryReduceArrayIteratingBuiltin( "Array.prototype.map", target, args, get_eager_deopt_scope, get_lazy_deopt_scope, initial_callback, process_element_callback); if (builtin_result.IsFail() || builtin_result.IsDoneWithAbort()) { return builtin_result; } DCHECK(builtin_result.IsDoneWithoutPayload()); // If the result was not fail or abort, the initial callback has successfully // created the array which we can return now. DCHECK(result_array); return result_array; } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayIteratingBuiltin( const char* name, compiler::JSFunctionRef target, CallArguments& args, GetEagerDeoptScopeCallback get_eager_deopt_scope, GetLazyDeoptScopeCallback get_lazy_deopt_scope, const std::optional<InitialCallback>& initial_callback, const std::optional<ProcessElementCallback>& process_element_callback) { DCHECK_EQ(initial_callback.has_value(), process_element_callback.has_value()); if (!CanSpeculateCall()) return {}; ValueNode* receiver = args.receiver(); if (!receiver) return {}; if (args.count() < 1) { FAIL(" to reduce " << name << " - not enough arguments"); } auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_maps) { FAIL(" to reduce " << name << " - receiver map is unknown"); } ElementsKind elements_kind; if (!CanInlineArrayIteratingBuiltin(broker(), *possible_maps, &elements_kind)) { FAIL(" to reduce " << name << " - doesn't support fast array iteration or incompatible" << " maps"); } // TODO(leszeks): May only be needed for holey elements kinds. if (!broker()->dependencies()->DependOnNoElementsProtector()) { FAIL(" to reduce " << name << " - invalidated no elements protector"); } ValueNode* callback = args[0]; if (!callback->is_tagged()) { FAIL(" to reduce " << name << " - callback is untagged value"); } ValueNode* this_arg = args.count() > 1 ? args[1] : GetRootConstant(RootIndex::kUndefinedValue); ValueNode* original_length; GET_VALUE_OR_ABORT(original_length, BuildLoadJSArrayLength(receiver)); if (initial_callback) { RETURN_IF_ABORT((*initial_callback)(original_length)); } // Elide the callable check if the node is known callable. EnsureType(callback, NodeType::kCallable, [&](NodeType old_type) { // ThrowIfNotCallable is wrapped in a lazy_deopt_scope to make sure the // exception has the right call stack. const LazyDeoptFrameScope& lazy_deopt_scope = get_lazy_deopt_scope( target, receiver, callback, this_arg, GetSmiConstant(0), GetSmiConstant(0), original_length); AddNewNodeNoInputConversion<ThrowIfNotCallable>({callback}); }); ValueNode* original_length_int32; GET_VALUE_OR_ABORT(original_length_int32, GetInt32(original_length)); // Remember the receiver map set before entering the loop the call. bool receiver_maps_were_unstable = false; auto node_info = known_node_aspects().TryGetInfoFor(receiver); if (node_info) { receiver_maps_were_unstable = node_info->possible_maps_are_unstable(); } PossibleMaps receiver_maps_before_loop(*possible_maps); // Create a sub graph builder with two variables (index and length). MaglevSubGraphBuilder sub_builder(this, 2); MaglevSubGraphBuilder::Variable var_index(0); MaglevSubGraphBuilder::Variable var_length(1); MaglevSubGraphBuilder::Label loop_end(&sub_builder, 1); // ``` // index = 0 // bind loop_header // ``` sub_builder.set(var_index, GetSmiConstant(0)); sub_builder.set(var_length, original_length); MaglevSubGraphBuilder::LoopLabel loop_header = sub_builder.BeginLoop({&var_index, &var_length}); // Reset known state that is cleared by BeginLoop, but is known to be true on // the first iteration, and will be re-checked at the end of the loop. // Reset the known receiver maps if necessary. if (receiver_maps_were_unstable) { DCHECK(node_info); node_info->SetPossibleMaps(receiver_maps_before_loop, receiver_maps_were_unstable, // Node type is monotonic, no need to reset it. NodeType::kUnknown, broker()); known_node_aspects().MarkAnyMapForAnyNodeIsUnstable(); } else { if (node_info) { DCHECK_EQ(node_info->possible_maps().size(), receiver_maps_before_loop.size()); } } // Reset the cached loaded array length to the length var. RecordKnownProperty(receiver, broker()->length_string(), sub_builder.get(var_length), false, compiler::AccessMode::kLoad); // ``` // if (index_int32 < length_int32) // fallthrough // else // goto end // ``` Phi* index_tagged = sub_builder.get(var_index)->Cast<Phi>(); EnsureType(index_tagged, NodeType::kSmi); ValueNode* index_int32; GET_VALUE_OR_ABORT(index_int32, GetInt32(index_tagged)); RETURN_IF_ABORT(sub_builder.GotoIfFalse<BranchIfInt32Compare>( &loop_end, {index_int32, original_length_int32}, Operation::kLessThan)); // ``` // next_index = index + 1 // ``` ValueNode* next_index_int32 = nullptr; { // Eager deopt scope for index increment overflow. // TODO(pthier): In practice this increment can never overflow, as the max // possible array length is less than int32 max value. Add a new // Int32Increment that asserts no overflow instead of deopting. EagerDeoptFrameScope eager_deopt_scope = get_eager_deopt_scope(target, receiver, callback, this_arg, index_int32, index_int32, original_length); GET_VALUE_OR_ABORT(next_index_int32, AddNewNode<Int32IncrementWithOverflow>({index_int32})); EnsureType(next_index_int32, NodeType::kSmi); } // TODO(leszeks): Assert Smi. // ``` // element = array.elements[index] // ``` ValueNode* elements; GET_VALUE_OR_ABORT(elements, BuildLoadElements(receiver, elements_kind)); ValueNode* element; if (elements_kind == PACKED_DOUBLE_ELEMENTS) { GET_VALUE_OR_ABORT(element, BuildLoadFixedDoubleArrayElement(elements, index_int32)); } else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { GET_VALUE_OR_ABORT(element, BuildLoadHoleyFixedDoubleArrayElement( elements, index_int32, true)); } else { GET_VALUE_OR_ABORT(element, BuildLoadFixedArrayElement(elements, index_int32)); } std::optional<MaglevSubGraphBuilder::Label> skip_call; if (IsHoleyElementsKind(elements_kind)) { // ``` // if (element is hole) goto skip_call // ``` skip_call.emplace( &sub_builder, 2, std::initializer_list<MaglevSubGraphBuilder::Variable*>{&var_length}); if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { RETURN_IF_ABORT(sub_builder.GotoIfTrue<BranchIfFloat64IsHole>(&*skip_call, {element})); } else { RETURN_IF_ABORT(sub_builder.GotoIfTrue<BranchIfRootConstant>( &*skip_call, {element}, RootIndex::kTheHoleValue)); } } // ``` // callback(this_arg, element, array) // ``` MaybeReduceResult result; { const LazyDeoptFrameScope& lazy_deopt_scope = get_lazy_deopt_scope(target, receiver, callback, this_arg, index_int32, next_index_int32, original_length); CallArguments call_args = args.count() < 2 ? CallArguments(ConvertReceiverMode::kNullOrUndefined, {element, index_tagged, receiver}) : CallArguments(ConvertReceiverMode::kAny, {this_arg, element, index_tagged, receiver}); SaveCallSpeculationScope saved(this); result = ReduceCall(callback, call_args, saved.value()); } // ``` // index = next_index // jump loop_header // ``` DCHECK_IMPLIES(result.IsDoneWithAbort(), current_block() == nullptr); // No need to finish the loop if this code is unreachable. if (!result.IsDoneWithAbort()) { if (process_element_callback) { ValueNode* value = result.value(); RETURN_IF_ABORT((*process_element_callback)(index_int32, value)); } // If any of the receiver's maps were unstable maps, we have to re-check the // maps on each iteration, in case the callback changed them. That said, we // know that the maps are valid on the first iteration, so we can rotate the // check to _after_ the callback, and then elide it if the receiver maps are // still known to be valid (i.e. the known maps after the call are contained // inside the known maps before the call). bool recheck_maps_after_call = receiver_maps_were_unstable; if (recheck_maps_after_call) { // No need to recheck maps if there are known maps... if (auto receiver_info_after_call = known_node_aspects().TryGetInfoFor(receiver)) { // ... and those known maps are equal to, or a subset of, the maps // before the call. if (receiver_info_after_call && receiver_info_after_call->possible_maps_are_known()) { recheck_maps_after_call = !receiver_maps_before_loop.contains( receiver_info_after_call->possible_maps()); } } } // Make sure to finish the loop if we eager deopt in the map check or index // check. const EagerDeoptFrameScope& eager_deopt_scope = get_eager_deopt_scope(target, receiver, callback, this_arg, index_int32, next_index_int32, original_length); if (recheck_maps_after_call) { // Build the CheckMap manually, since we're doing it with already known // maps rather than feedback, and we don't need to update known node // aspects or types since we're at the end of the loop anyway. bool emit_check_with_migration = std::any_of( receiver_maps_before_loop.begin(), receiver_maps_before_loop.end(), [](compiler::MapRef map) { return map.is_migration_target(); }); if (emit_check_with_migration) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithMigration>( {receiver}, receiver_maps_before_loop, CheckType::kOmitHeapObjectCheck)); } else { RETURN_IF_ABORT(AddNewNode<CheckMaps>({receiver}, receiver_maps_before_loop, CheckType::kOmitHeapObjectCheck)); } } // Check if the index is still in bounds, in case the callback changed the // length. ValueNode* current_length; GET_VALUE_OR_ABORT(current_length, BuildLoadJSArrayLength(receiver)); sub_builder.set(var_length, current_length); // Reference compare the loaded length against the original length. If this // is the same value node, then we didn't have any side effects and didn't // clear the cached length. if (current_length != original_length) { RETURN_IF_ABORT( TryBuildCheckInt32Condition(original_length_int32, current_length, AssertCondition::kUnsignedLessThanEqual, DeoptimizeReason::kArrayLengthChanged)); } } if (skip_call.has_value()) { sub_builder.GotoOrTrim(&*skip_call); sub_builder.Bind(&*skip_call); } sub_builder.set(var_index, next_index_int32); sub_builder.EndLoop(&loop_header); // ``` // bind end // ``` sub_builder.Bind(&loop_end); return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayIteratorPrototypeNext( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = args.receiver(); if (!receiver) return {}; if (!receiver->Is<InlinedAllocation>()) return {}; VirtualObject* iterator = receiver->Cast<InlinedAllocation>()->object(); if (!iterator->map() || !iterator->map()->IsJSArrayIteratorMap()) { FAIL("iterator is not a JS array iterator object"); } ValueNode* iterated_object = iterator->get(JSArrayIterator::kIteratedObjectOffset); ElementsKind elements_kind; base::SmallVector<compiler::MapRef, 4> maps; if (iterated_object->Is<InlinedAllocation>()) { VirtualObject* array = iterated_object->Cast<InlinedAllocation>()->object(); // TODO(victorgomes): Remove this once we track changes in the inlined // allocated object. if (iterated_object->Cast<InlinedAllocation>()->IsEscaping()) { FAIL("allocation is escaping, map could have been changed"); } // TODO(victorgomes): This effectively disable the optimization for `for-of` // loops. We need to figure it out a way to re-enable this. if (IsInsideLoop()) { FAIL("we're inside a loop, iterated object map could change"); } auto map = *array->map(); if (!map.supports_fast_array_iteration(broker())) { FAIL("no fast array iteration support"); } elements_kind = map.elements_kind(); maps.push_back(map); } else { auto possible_maps = known_node_aspects().TryGetPossibleMaps(iterated_object); if (!possible_maps) { FAIL("iterated object is unknown"); } if (!CanInlineArrayIteratingBuiltin(broker(), *possible_maps, &elements_kind)) { FAIL("no fast array iteration support or incompatible maps"); } for (auto map : *possible_maps) { maps.push_back(map); } } // TODO(victorgomes): Support typed arrays. if (IsTypedArrayElementsKind(elements_kind)) { FAIL("no typed arrays support"); } if (IsHoleyElementsKind(elements_kind) && !broker()->dependencies()->DependOnNoElementsProtector()) { FAIL("no elements protector"); } // Load the [[NextIndex]] from the {iterator}. // We can assume index and length fit in Uint32. ValueNode* index; GET_VALUE_OR_ABORT( index, BuildLoadTaggedField(receiver, JSArrayIterator::kNextIndexOffset)); ValueNode* uint32_index; GET_VALUE_OR_ABORT(uint32_index, GetUint32ElementIndex(index)); ValueNode* length; GET_VALUE_OR_ABORT( length, BuildLoadJSArrayLength(iterated_object, IsFastElementsKind(elements_kind) ? LoadType::kSmi : LoadType::kNumber)); ValueNode* uint32_length; GET_VALUE_OR_ABORT(uint32_length, GetUint32ElementIndex(length)); // Check next index is below length MaglevSubGraphBuilder subgraph(this, 2); MaglevSubGraphBuilder::Variable is_done(0); MaglevSubGraphBuilder::Variable ret_value(1); RETURN_IF_ABORT(subgraph.Branch( {&is_done, &ret_value}, [&](BranchBuilder& builder) { return BuildBranchIfUint32Compare(builder, Operation::kLessThan, uint32_index, uint32_length); }, [&] { ValueNode* int32_index; GET_VALUE_OR_ABORT(int32_index, GetInt32(uint32_index)); subgraph.set(is_done, GetBooleanConstant(false)); DCHECK( iterator->get(JSArrayIterator::kKindOffset)->Is<Int32Constant>()); IterationKind iteration_kind = static_cast<IterationKind>( iterator->get(JSArrayIterator::kKindOffset) ->Cast<Int32Constant>() ->value()); if (iteration_kind == IterationKind::kKeys) { subgraph.set(ret_value, index); } else { ValueNode* value; GET_VALUE_OR_ABORT( value, TryBuildElementLoadOnJSArrayOrJSObject( iterated_object, int32_index, base::VectorOf(maps), elements_kind, KeyedAccessLoadMode::kHandleOOBAndHoles)); if (iteration_kind == IterationKind::kEntries) { ValueNode* key_value_array; GET_VALUE_OR_ABORT(key_value_array, BuildAndAllocateKeyValueArray(index, value)); subgraph.set(ret_value, key_value_array); } else { subgraph.set(ret_value, value); } } // Add 1 to index ValueNode* next_index; GET_VALUE_OR_ABORT(next_index, AddNewNode<Int32AddWithOverflow>( {int32_index, GetInt32Constant(1)})); EnsureType(next_index, NodeType::kSmi); // Update [[NextIndex]] return BuildStoreTaggedFieldNoWriteBarrier( receiver, next_index, JSArrayIterator::kNextIndexOffset, StoreTaggedMode::kDefault); }, [&] { // Index is greater or equal than length. subgraph.set(is_done, GetBooleanConstant(true)); subgraph.set(ret_value, GetRootConstant(RootIndex::kUndefinedValue)); if (!IsTypedArrayElementsKind(elements_kind)) { // Mark the {iterator} as exhausted by setting the [[NextIndex]] to a // value that will never pass the length check again (aka the maximum // value possible for the specific iterated object). Note that this is // different from what the specification says, which is changing the // [[IteratedObject]] field to undefined, but that makes it difficult // to eliminate the map checks and "length" accesses in for..of loops. // // This is not necessary for JSTypedArray's, since the length of those // cannot change later and so if we were ever out of bounds for them // we will stay out-of-bounds forever. return BuildStoreTaggedField( receiver, GetRootConstant(RootIndex::kMaxUInt32), JSArrayIterator::kNextIndexOffset, StoreTaggedMode::kDefault); } return ReduceResult::Done(); })); // Allocate result object and return. compiler::MapRef map = broker()->target_native_context().iterator_result_map(broker()); VirtualObject* iter_result = CreateJSIteratorResult( map, subgraph.get(ret_value), subgraph.get(is_done)); return BuildInlinedAllocation(iter_result, AllocationType::kYoung); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypeAt( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; if (!broker()->dependencies()->DependOnNoElementsProtector()) { if (v8_flags.trace_maglev_graph_building) { std::cout << " ! Failed to reduce Array.prototype.at - " "NoElementsProtector invalidated"; } return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); ElementsKind elements_kind = NO_ELEMENTS; // TODO(42204525): Support polymorphism. I.e., DOUBLE_ELEMENTS and ELEMENTS // together. if (!possible_maps || !CanInlineArrayIteratingBuiltin( broker(), *possible_maps, &elements_kind)) { return {}; } ValueNode* length; GET_VALUE_OR_ABORT(length, BuildLoadJSArrayLength(receiver)); ValueNode* index = nullptr; if (args.count() == 0) { // Index is the undefined object. ToIntegerOrInfinity(undefined) = 0. index = GetInt32Constant(0); } else { GET_VALUE_OR_ABORT(index, Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare( builder, Operation::kLessThan, args[0], GetInt32Constant(0)); }, [&]() -> ReduceResult { return AddNewNode<Int32Add>({args[0], length}); }, [&]() -> ReduceResult { return args[0]; })); } ValueNode* elements; GET_VALUE_OR_ABORT(elements, BuildLoadElements(receiver, elements_kind)); return Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare(builder, Operation::kGreaterThanOrEqual, index, GetInt32Constant(0)); }, [&]() { return Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare(builder, Operation::kLessThan, index, length); }, [&]() -> ReduceResult { ValueNode* element; if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { GET_VALUE_OR_ABORT(element, AddNewNode<LoadHoleyFixedDoubleArrayElement>( {elements, index})); } else if (elements_kind == PACKED_DOUBLE_ELEMENTS) { GET_VALUE_OR_ABORT( element, BuildLoadFixedDoubleArrayElement(elements, index)); } else { LoadType type = elements_kind == PACKED_SMI_ELEMENTS ? LoadType::kSmi : LoadType::kUnknown; GET_VALUE_OR_ABORT( element, BuildLoadFixedArrayElement(elements, index, type)); } if (IsHoleyElementsKind(elements_kind)) { GET_VALUE_OR_ABORT( element, AddNewNode<ConvertHoleToUndefined>({element})); } return element; }, [&]() -> ReduceResult { return GetRootConstant(RootIndex::kUndefinedValue); }); }, [&]() -> ReduceResult { return GetRootConstant(RootIndex::kUndefinedValue); }); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypeEntries( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = GetValueOrUndefined(args.receiver()); if (!CheckType(receiver, NodeType::kJSReceiver)) { return {}; } return BuildAndAllocateJSArrayIterator(receiver, IterationKind::kEntries); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypeSlice( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = GetValueOrUndefined(args.receiver()); ValueNode* start = GetValueOrUndefined(args[0]); ValueNode* end = GetValueOrUndefined(args[1]); if (auto start_value = TryGetInt32Constant(start)) { if (*start_value != 0) return {}; } else if (!start->IsUndefinedValue()) { return {}; } if (!end->IsUndefinedValue()) { return {}; } if (!CheckType(receiver, NodeType::kJSReceiver)) { return {}; } auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_maps) { return {}; } bool can_be_holey = false; for (compiler::MapRef map : *possible_maps) { if (!map.supports_fast_array_iteration(broker())) { return {}; } if (IsHoleyElementsKind(map.elements_kind())) { can_be_holey = true; } } if (!broker()->dependencies()->DependOnArraySpeciesProtector()) { return {}; } if (can_be_holey && !broker()->dependencies()->DependOnNoElementsProtector()) { return {}; } // TODO(maglev): We can do even better here, either adding a CloneArray // simplified operator, whose output type indicates that it's an Array, // saving subsequent checks, or yet better, by introducing new operators // CopySmiOrObjectElements / CopyDoubleElements and inlining the JSArray // allocation in here. That way we'd even get escape analysis and scalar // replacement to help in some cases. return BuildCallBuiltin<Builtin::kCloneFastJSArray>({receiver}); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypeKeys( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = GetValueOrUndefined(args.receiver()); if (!CheckType(receiver, NodeType::kJSReceiver)) { return {}; } return BuildAndAllocateJSArrayIterator(receiver, IterationKind::kKeys); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypeValues( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = GetValueOrUndefined(args.receiver()); if (!CheckType(receiver, NodeType::kJSReceiver)) { return {}; } return BuildAndAllocateJSArrayIterator(receiver, IterationKind::kValues); } MaybeReduceResult MaglevGraphBuilder::TryReduceStringFromCharCode( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; if (args.count() != 1) return {}; ValueNode* value; GET_VALUE_OR_ABORT( value, GetTruncatedInt32ForToNumber(args[0], NodeType::kNumberOrOddball)); return AddNewNode<BuiltinStringFromCharCode>({value}); } MaybeReduceResult MaglevGraphBuilder::TryReduceConstantStringAt( ValueNode* receiver, ValueNode* index, StringAtOOBMode oob_mode) { auto constant_receiver = TryGetConstant<HeapObject>(receiver); if (!constant_receiver) return {}; if (!constant_receiver->IsString()) { return EmitUnconditionalDeopt(DeoptimizeReason::kNotAString); } compiler::StringRef string = constant_receiver->AsString(); auto maybe_constant_index = TryGetInt32Constant(index); if (!maybe_constant_index) return {}; int32_t constant_index = *maybe_constant_index; if (static_cast<uint32_t>(constant_index) >= string.length()) { switch (oob_mode) { case StringAtOOBMode::kElement: // For element access, a negative index triggers a named lookup rather // than an element lookup; when this is the case, we shouldn't be trying // to optimize an elements access at all, so deopt. if (constant_index < 0) { return EmitUnconditionalDeopt(DeoptimizeReason::kOutOfBounds); } // Otherwise, this is hole-like access, so guard against elements on the // prototype to return undefined. if (broker()->dependencies()->DependOnNoElementsProtector()) { return GetRootConstant(RootIndex::kUndefinedValue); } // If the no elements protector is invalidated, unconditionally deopt. // This shouldn't trigger a deopt look because the feedback should // transition to megamorphic. return EmitUnconditionalDeopt(DeoptimizeReason::kOutOfBounds); case StringAtOOBMode::kCharAt: { // OOB for charAt is always the empty string. return GetRootConstant(RootIndex::kempty_string); } } UNREACHABLE(); } if (std::optional<uint16_t> value = string.GetChar(broker(), constant_index)) { return GetConstantSingleCharacterStringFromCode(*value); } return {}; } MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeCharAt( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall({SpeculationMode::kDisallowBoundsCheckSpeculation})) { return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); ValueNode* index; if (args.count() == 0) { // Index is the undefined object. ToIntegerOrInfinity(undefined) = 0. index = GetInt32Constant(0); } else { GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(args[0])); } // Any other argument is ignored. RETURN_IF_DONE( TryReduceConstantStringAt(receiver, index, StringAtOOBMode::kCharAt)); // Ensure that {receiver} is actually a String. RETURN_IF_ABORT(BuildCheckString(receiver)); // And index is below length. ValueNode* length; GET_VALUE_OR_ABORT(length, BuildLoadStringLength(receiver)); auto GetCharAt = [&]() -> ReduceResult { bool is_seq_one_byte = v8_flags.specialize_code_for_one_byte_seq_strings && NodeTypeIs(GetType(receiver), NodeType::kSeqOneByteString); if (is_seq_one_byte) { return AddNewNode<SeqOneByteStringAt>({receiver, index}); } else { return AddNewNode<StringAt>({receiver, index}); } }; if (current_speculation_mode_ == SpeculationMode::kDisallowBoundsCheckSpeculation) { return Select( [&](BranchBuilder& builder) { // Do unsafe conversions of length and index into uint32, to do an // unsigned comparison. The index might actually be a negative signed // value, but this "unsafe" cast will still work, converting it into a // large unsigned value which compares greater than the length. return BuildBranchIfUint32Compare( builder, Operation::kLessThan, // 'index' and 'length' are both int32, so no input conversion is // needed. AddNewNodeNoInputConversion<UnsafeInt32ToUint32>({index}), AddNewNodeNoInputConversion<UnsafeInt32ToUint32>({length})); }, [&]() -> ReduceResult { return GetCharAt(); }, [&]() -> ReduceResult { return GetRootConstant(RootIndex::kempty_string); }); } DCHECK_EQ(current_speculation_mode_, SpeculationMode::kAllowSpeculation); RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, length, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); return GetCharAt(); } MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeCharCodeAt( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall({SpeculationMode::kDisallowBoundsCheckSpeculation})) { return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); ValueNode* index; if (args.count() == 0) { // Index is the undefined object. ToIntegerOrInfinity(undefined) = 0. index = GetInt32Constant(0); } else { GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(args[0])); } // Any other argument is ignored. // Try to constant-fold if receiver and index are constant if (auto cst = TryGetConstant<String>(receiver)) { if (index->Is<Int32Constant>()) { int idx = index->Cast<Int32Constant>()->value(); if (idx >= 0 && static_cast<uint32_t>(idx) < cst->length()) { if (std::optional<uint16_t> value = cst->GetChar(broker(), idx)) { return GetSmiConstant(*value); } } } } // Ensure that {receiver} is actually a String. RETURN_IF_ABORT(BuildCheckString(receiver)); // And index is below length. ValueNode* length; GET_VALUE_OR_ABORT(length, BuildLoadStringLength(receiver)); if (current_speculation_mode_ == SpeculationMode::kDisallowBoundsCheckSpeculation) { return Select( [&](BranchBuilder& builder) { // Do unsafe conversions of length and index into uint32, to do an // unsigned comparison. The index might actually be a negative signed // value, but this "unsafe" cast will still work, converting it into a // large unsigned value which compares greater than the length. return BuildBranchIfUint32Compare( builder, Operation::kLessThan, // 'index' and 'length' are both int32, so no input conversion is // needed. AddNewNodeNoInputConversion<UnsafeInt32ToUint32>({index}), AddNewNodeNoInputConversion<UnsafeInt32ToUint32>({length})); }, [&]() -> ReduceResult { return BuildGetCharCodeAt(receiver, index); }, [&]() -> ReduceResult { return GetRootConstant(RootIndex::kNanValue); }); } RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, length, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); return BuildGetCharCodeAt(receiver, index); } MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeCodePointAt( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall({SpeculationMode::kDisallowBoundsCheckSpeculation})) { return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); ValueNode* index; if (args.count() == 0) { // Index is the undefined object. ToIntegerOrInfinity(undefined) = 0. index = GetInt32Constant(0); } else { GET_VALUE_OR_ABORT(index, GetInt32ElementIndex(args[0])); } // Any other argument is ignored. // Ensure that {receiver} is actually a String. RETURN_IF_ABORT(BuildCheckString(receiver)); // And index is below length. ValueNode* length; GET_VALUE_OR_ABORT(length, BuildLoadStringLength(receiver)); auto GetCodePointAt = [&]() -> ReduceResult { bool is_seq_one_byte = v8_flags.specialize_code_for_one_byte_seq_strings && NodeTypeIs(GetType(receiver), NodeType::kSeqOneByteString); if (is_seq_one_byte) { // For one-byte strings, codePointAt == charCodeAt, since there are no // surrogate pairs. return AddNewNode<BuiltinSeqOneByteStringCharCodeAt>({receiver, index}); } else { return AddNewNode<BuiltinStringPrototypeCharCodeOrCodePointAt>( {receiver, index}, BuiltinStringPrototypeCharCodeOrCodePointAt::kCodePointAt); } }; if (current_speculation_mode_ == SpeculationMode::kDisallowBoundsCheckSpeculation) { return Select( [&](BranchBuilder& builder) { // Do unsafe conversions of length and index into uint32, to do an // unsigned comparison. The index might actually be a negative signed // value, but this "unsafe" cast will still work, converting it into a // large unsigned value which compares greater than the length. return BuildBranchIfUint32Compare( builder, Operation::kLessThan, // 'index' and 'length' are both int32, so no input conversion is // needed. AddNewNodeNoInputConversion<UnsafeInt32ToUint32>({index}), AddNewNodeNoInputConversion<UnsafeInt32ToUint32>({length})); }, [&]() -> ReduceResult { return GetCodePointAt(); }, [&]() -> ReduceResult { return GetRootConstant(RootIndex::kUndefinedValue); }); } RETURN_IF_ABORT(TryBuildCheckInt32Condition( index, length, AssertCondition::kUnsignedLessThan, DeoptimizeReason::kOutOfBounds)); return GetCodePointAt(); } MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeSlice( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; if (args.count() == 1) { // Reduce slice(-1). ValueNode* index = args[0]; std::optional<int32_t> index_const = TryGetInt32Constant(index); if (!index_const || *index_const != -1) return {}; // Ensure that {receiver} is actually a String. ValueNode* receiver = GetValueOrUndefined(args.receiver()); RETURN_IF_ABORT(BuildCheckString(receiver)); ValueNode* receiver_length; GET_VALUE_OR_ABORT(receiver_length, BuildLoadStringLength(receiver)); return Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare( builder, Operation::kEqual, receiver_length, GetInt32Constant(0)); }, [&] { return GetRootConstant(RootIndex::kempty_string); }, [&] { ValueNode* index_last; // TODO(marja): Consider TryReduceConstantStringAt. GET_VALUE_OR_ABORT(index_last, AddNewNode<Int32Subtract>( {receiver_length, GetInt32Constant(1)})); return AddNewNode<StringAt>({receiver, index_last}); }); } else if (args.count() == 2) { // These will deopt if the argument is not an Int32; CanSpeculateCall above // is needed for avoiding deopt loops. ValueNode* start_index; GET_VALUE_OR_ABORT(start_index, GetInt32(args[0])); if (!start_index) return {}; ValueNode* end_index; GET_VALUE_OR_ABORT(end_index, GetInt32(args[1])); if (!end_index) return {}; ValueNode* receiver = GetValueOrUndefined(args.receiver()); RETURN_IF_ABORT(BuildCheckString(receiver)); return AddNewNode<StringSlice>({receiver, start_index, end_index}); } return {}; } MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeStartsWith( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* search_element = args[0] ? args[0] : GetRootConstant(RootIndex::kundefined_string); ValueNode* receiver = GetValueOrUndefined(args.receiver()); if (!NodeTypeIs(GetType(search_element), NodeType::kString)) return {}; ValueNode* start_arg = GetValueOrUndefined(args[1]); ValueNode* start = start_arg->IsUndefinedValue() ? GetInt32Constant(0) : start_arg; RETURN_IF_ABORT(BuildCheckString(receiver)); RETURN_IF_ABORT(BuildCheckSmi(start)); ValueNode* receiver_length; GET_VALUE_OR_ABORT(receiver_length, BuildLoadStringLength(receiver)); // min(max(start, 0), receiver_length) ValueNode* max_value; GET_VALUE_OR_ABORT(max_value, BuildInt32Max(start, GetInt32Constant(0))); ValueNode* min_value; GET_VALUE_OR_ABORT(min_value, BuildInt32Min(max_value, receiver_length)); ValueNode* clamped_start; GET_VALUE_OR_ABORT(clamped_start, GetInt32(min_value)); ValueNode* search_length; GET_VALUE_OR_ABORT(search_length, BuildLoadStringLength(search_element)); // TODO(all): Introduce a ForInt32 helper. MaglevSubGraphBuilder sub_graph(this, 2); MaglevSubGraphBuilder::Variable ret_val(0); MaglevSubGraphBuilder::Variable var_i(1); MaglevSubGraphBuilder::Label done(&sub_graph, 2, {&ret_val}); MaglevSubGraphBuilder::Label return_false(&sub_graph, 2); // receiver_length - clamped_start < search_length ValueNode* remaining; GET_VALUE_OR_ABORT( remaining, AddNewNode<Int32Subtract>({receiver_length, clamped_start})); RETURN_IF_ABORT(sub_graph.GotoIfTrue<BranchIfInt32Compare>( &return_false, {remaining, search_length}, Operation::kLessThan)); // i = 0 sub_graph.set(var_i, GetInt32Constant(0)); MaglevSubGraphBuilder::LoopLabel loop_header = sub_graph.BeginLoop({&var_i}); MaglevSubGraphBuilder::Label return_true(&sub_graph, 1); ValueNode* index_int32 = sub_graph.get(var_i); // if (i < search_length) continue; else exit loop RETURN_IF_ABORT(sub_graph.GotoIfFalse<BranchIfInt32Compare>( &return_true, {index_int32, search_length}, Operation::kLessThan)); // pos = clamped_start + i ValueNode* pos; GET_VALUE_OR_ABORT(pos, AddNewNode<Int32Add>({clamped_start, index_int32})); // TODO(dmercadier): without static knowledge about maps shapes (which we // probably don't have), BuildGetCharCodeAt generates fairly expensive code // since it needs to handle all string shapes. It would be more efficient to // have a pre-processing before the loop that computes a single linear buffer // for each string (+ maybe an index, for SlicedString in particular), that's // then accessed directly in the loop (cf StringPrepareForGetCodeUnit in // Turboshaft). Here in particular, the loop doesn't contain anything that can // trigger a GC, so this should be safe. ValueNode* lhs_ch; GET_VALUE_OR_ABORT(lhs_ch, BuildGetCharCodeAt(receiver, pos)); ValueNode* rhs_ch; GET_VALUE_OR_ABORT(rhs_ch, BuildGetCharCodeAt(search_element, index_int32)); ValueNode* is_equal; GET_VALUE_OR_ABORT(is_equal, BuildTaggedEqual(lhs_ch, rhs_ch)); // If chars are not equal, return false. RETURN_IF_ABORT(sub_graph.GotoIfFalse<BranchIfRootConstant>( &return_false, {is_equal}, RootIndex::kTrueValue)); // i++; goto loop_header ValueNode* next_index_int32; GET_VALUE_OR_ABORT( next_index_int32, AddNewNode<Int32Add>({sub_graph.get(var_i), GetInt32Constant(1)})); sub_graph.set(var_i, next_index_int32); sub_graph.EndLoop(&loop_header); sub_graph.Bind(&return_true); sub_graph.set(ret_val, GetRootConstant(RootIndex::kTrueValue)); sub_graph.Goto(&done); sub_graph.Bind(&return_false); sub_graph.set(ret_val, GetRootConstant(RootIndex::kFalseValue)); sub_graph.Goto(&done); sub_graph.Bind(&done); return sub_graph.get(ret_val); } MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeIterator( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; ValueNode* receiver = GetValueOrUndefined(args.receiver()); // Ensure that {receiver} is actually a String. RETURN_IF_ABORT(BuildCheckString(receiver)); compiler::MapRef map = broker()->target_native_context().initial_string_iterator_map(broker()); VirtualObject* string_iterator = CreateJSStringIterator(map, receiver); return BuildInlinedAllocation(string_iterator, AllocationType::kYoung); } #ifdef V8_INTL_SUPPORT MaybeReduceResult MaglevGraphBuilder::TryReduceStringPrototypeLocaleCompareIntl( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() < 1 || args.count() > 3) return {}; LocalFactory* factory = local_isolate()->factory(); compiler::ObjectRef undefined_ref = broker()->undefined_value(); DirectHandle<Object> locales_handle; ValueNode* locales_node = nullptr; if (args.count() > 1) { compiler::OptionalHeapObjectRef maybe_locales = TryGetConstant<HeapObject>(args[1]); if (!maybe_locales) return {}; compiler::HeapObjectRef locales = maybe_locales.value(); if (locales.equals(undefined_ref)) { locales_handle = factory->undefined_value(); locales_node = GetRootConstant(RootIndex::kUndefinedValue); } else { if (!locales.IsString()) return {}; compiler::StringRef sref = locales.AsString(); std::optional<Handle<String>> maybe_locales_handle = sref.ObjectIfContentAccessible(broker()); if (!maybe_locales_handle) return {}; locales_handle = *maybe_locales_handle; locales_node = args[1]; } } else { locales_handle = factory->undefined_value(); locales_node = GetRootConstant(RootIndex::kUndefinedValue); } if (args.count() > 2) { compiler::OptionalHeapObjectRef maybe_options = TryGetConstant<HeapObject>(args[2]); if (!maybe_options) return {}; if (!maybe_options.value().equals(undefined_ref)) return {}; } DCHECK(!locales_handle.is_null()); DCHECK_NOT_NULL(locales_node); if (Intl::CompareStringsOptionsFor(local_isolate(), locales_handle, factory->undefined_value()) != Intl::CompareStringsOptions::kTryFastPath) { return {}; } return BuildCallBuiltinWithTaggedInputs<Builtin::kStringFastLocaleCompare>( {GetConstant(target), GetValueOrUndefined(args.receiver()), args[0], locales_node}); } #endif // V8_INTL_SUPPORT #ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA MaybeReduceResult MaglevGraphBuilder::TryReduceGetContinuationPreservedEmbedderData( compiler::JSFunctionRef target, CallArguments& args) { return AddNewNode<GetContinuationPreservedEmbedderData>({}); } MaybeReduceResult MaglevGraphBuilder::TryReduceSetContinuationPreservedEmbedderData( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) return {}; RETURN_IF_ABORT(AddNewNode<SetContinuationPreservedEmbedderData>({args[0]})); return GetRootConstant(RootIndex::kUndefinedValue); } #endif // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA template <typename LoadNode> MaybeReduceResult MaglevGraphBuilder::TryBuildLoadDataView( const CallArguments& args, ExternalArrayType type) { if (!CanSpeculateCall()) return {}; if (!broker()->dependencies()->DependOnArrayBufferDetachingProtector()) { // TODO(victorgomes): Add checks whether the array has been detached or is // immutable. return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); if (!EnsureType(receiver, NodeType::kJSDataView)) { RETURN_IF_ABORT( AddNewNode<CheckInstanceType>({receiver}, CheckType::kCheckHeapObject, JS_DATA_VIEW_TYPE, JS_DATA_VIEW_TYPE)); } // TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too. ValueNode* offset; if (args[0]) { GET_VALUE_OR_ABORT(offset, GetInt32ElementIndex(args[0])); } else { offset = GetInt32Constant(0); } ValueNode* byte_length; GET_VALUE_OR_ABORT(byte_length, BuildLoadJSDataViewByteLength(receiver)); RETURN_IF_ABORT( AddNewNode<CheckJSDataViewBounds>({offset, byte_length}, type)); ValueNode* data_pointer; GET_VALUE_OR_ABORT(data_pointer, BuildLoadJSDataViewDataPointer(receiver)); ValueNode* is_little_endian = args[1] ? args[1] : GetBooleanConstant(false); return AddNewNode<LoadNode>( {receiver, data_pointer, offset, is_little_endian}, type); } template <typename StoreNode, typename Function> MaybeReduceResult MaglevGraphBuilder::TryBuildStoreDataView( const CallArguments& args, ExternalArrayType type, Function&& getValue) { if (!CanSpeculateCall()) return {}; if (!broker()->dependencies()->DependOnArrayBufferDetachingProtector()) { // TODO(victorgomes): Add checks whether the array has been detached. return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); if (!EnsureType(receiver, NodeType::kJSDataView)) { RETURN_IF_ABORT( AddNewNode<CheckInstanceType>({receiver}, CheckType::kCheckHeapObject, JS_DATA_VIEW_TYPE, JS_DATA_VIEW_TYPE)); } // TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too. ValueNode* offset; if (args[0]) { GET_VALUE_OR_ABORT(offset, GetInt32ElementIndex(args[0])); } else { offset = GetInt32Constant(0); } ValueNode* byte_length; GET_VALUE_OR_ABORT(byte_length, BuildLoadJSDataViewByteLength(receiver)); RETURN_IF_ABORT( AddNewNode<CheckJSDataViewBounds>({offset, byte_length}, type)); ValueNode* data_pointer; GET_VALUE_OR_ABORT(data_pointer, BuildLoadJSDataViewDataPointer(receiver)); ValueNode* value; GET_VALUE_OR_ABORT(value, getValue(args[1])); ValueNode* is_little_endian = args[2] ? args[2] : GetBooleanConstant(false); RETURN_IF_ABORT(AddNewNode<StoreNode>( {receiver, data_pointer, offset, value, is_little_endian}, type)); return GetRootConstant(RootIndex::kUndefinedValue); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeGetByteLength( compiler::JSFunctionRef target, CallArguments& args) { // We cannot check CanSpeculateCall here, since this is a getter. if (!broker()->dependencies()->DependOnArrayBufferDetachingProtector()) { // TODO(victorgomes): Add checks whether the array has been detached. return {}; } // TODO(victorgomes): Add data view to known types. ValueNode* receiver = GetValueOrUndefined(args.receiver()); auto possible_receiver_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_receiver_maps) { FAIL(" to reduce DataView.get byteLength - unknown receiver map"); } // Don't optimize if any of the known maps is something else than a DataView // map. for (compiler::MapRef map : *possible_receiver_maps) { // TODO(v8:11111): Optimize for JS_RAB_GSAB_DATA_VIEW_TYPE too. if (map.instance_type() != JS_DATA_VIEW_TYPE) { return {}; } } return BuildLoadJSDataViewByteLength(receiver); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeGetInt8( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildLoadDataView<LoadSignedIntDataViewElement>( args, ExternalArrayType::kExternalInt8Array); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeSetInt8( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildStoreDataView<StoreSignedIntDataViewElement>( args, ExternalArrayType::kExternalInt8Array, [&](ValueNode* value) { return value ? value : GetInt32Constant(0); }); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeGetInt16( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildLoadDataView<LoadSignedIntDataViewElement>( args, ExternalArrayType::kExternalInt16Array); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeSetInt16( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildStoreDataView<StoreSignedIntDataViewElement>( args, ExternalArrayType::kExternalInt16Array, [&](ValueNode* value) { return value ? value : GetInt32Constant(0); }); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeGetInt32( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildLoadDataView<LoadSignedIntDataViewElement>( args, ExternalArrayType::kExternalInt32Array); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeSetInt32( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildStoreDataView<StoreSignedIntDataViewElement>( args, ExternalArrayType::kExternalInt32Array, [&](ValueNode* value) { return value ? value : GetInt32Constant(0); }); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeGetFloat64( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildLoadDataView<LoadDoubleDataViewElement>( args, ExternalArrayType::kExternalFloat64Array); } MaybeReduceResult MaglevGraphBuilder::TryReduceDataViewPrototypeSetFloat64( compiler::JSFunctionRef target, CallArguments& args) { return TryBuildStoreDataView<StoreDoubleDataViewElement>( args, ExternalArrayType::kExternalFloat64Array, [&](ValueNode* value) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE // Produce the same bit pattern we would get through computation. auto ud = Float64::FromBits(kUndefinedNanInt64); const double silenced_nan = ud.to_quiet_nan().get_scalar(); #else const double silenced_nan = std::numeric_limits<double>::quiet_NaN(); #endif // V8_ENABLE_UNDEFINED_DOUBLE return value ? GetFloat64ForToNumber(value, NodeType::kNumberOrOddball) : GetFloat64Constant(silenced_nan); }); } namespace { bool AllOfInstanceTypesUnsafe(const PossibleMaps& maps, std::function<bool(InstanceType)> f) { auto instance_type = [f](compiler::MapRef map) { return f(map.instance_type()); }; return std::all_of(maps.begin(), maps.end(), instance_type); } bool AllOfInstanceTypesAre(const PossibleMaps& maps, InstanceType type) { CHECK(!InstanceTypeChecker::IsString(type)); return AllOfInstanceTypesUnsafe( maps, [type](InstanceType other) { return type == other; }); } } // namespace MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetFieldPrologue( compiler::JSFunctionRef target, CallArguments& args) { if (!v8_flags.maglev_inline_date_accessors) return {}; if (!CanSpeculateCall()) return {}; if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { FAIL(" to reduce Date.prototype.GetXXX - no receiver"); } ValueNode* receiver = GetValueOrUndefined(args.receiver()); // If the map set is not found, then we don't know anything about the map of // the receiver, so bail. auto possible_receiver_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_receiver_maps) { FAIL(" to reduce Date.prototype.GetXXX - unknown receiver map"); } // If the set of possible maps is empty, then there's no possible map for this // receiver, therefore this path is unreachable at runtime. We're unlikely to // ever hit this case, BuildCheckMaps should already unconditionally deopt, // but check it in case another checking operation fails to statically // unconditionally deopt. if (possible_receiver_maps->is_empty()) { // TODO(leszeks): Add an unreachable assert here. return ReduceResult::DoneWithAbort(); } if (!AllOfInstanceTypesAre(*possible_receiver_maps, JS_DATE_TYPE)) { FAIL(" to reduce Date.prototype.GetXXX - wrong receiver maps "); } if (!broker() ->dependencies() ->DependOnNoDateTimeConfigurationChangeProtector()) { FAIL( " to reduce Date.prototype.GetXXX - " "NoDateTimeConfigurationChangeProtector invalidated"); } return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetField( compiler::JSFunctionRef target, CallArguments& args, JSDate::FieldIndex field_index) { DCHECK_LT(field_index, JSDate::kFirstUncachedField); auto prologue_result = TryReduceDatePrototypeGetFieldPrologue(target, args); if (!prologue_result.IsDoneWithoutPayload()) return prologue_result; ValueNode* receiver = args.receiver(); DCHECK_NOT_NULL(receiver); int field_offset = JSDate::kYearOffset + field_index * kTaggedSize; ValueNode* field_value; GET_VALUE_OR_ABORT(field_value, BuildLoadTaggedField(receiver, field_offset)); // All cached JSDate fields are Smi|NaN. RETURN_IF_ABORT(BuildCheckSmi(field_value)); // TODO(jgruber): Presumably NaN time values are rare, so we simply deopt when // encountering such an object. Alternatively, to avoid the deopt we could // fall back to Number: EnsureType(field_value, NodeType::kNumber); return field_value; } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetTime( compiler::JSFunctionRef target, CallArguments& args) { auto prologue_result = TryReduceDatePrototypeGetFieldPrologue(target, args); if (!prologue_result.IsDoneWithoutPayload()) return prologue_result; ValueNode* receiver = args.receiver(); DCHECK_NOT_NULL(receiver); return AddNewNode<LoadFloat64>({receiver}, JSDate::kValueOffset); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetFullYear( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kYear); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetMonth( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kMonth); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetDate( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kDay); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetDay( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kWeekday); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetHours( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kHour); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetMinutes( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kMinute); } MaybeReduceResult MaglevGraphBuilder::TryReduceDatePrototypeGetSeconds( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceDatePrototypeGetField(target, args, JSDate::kSecond); } MaybeReduceResult MaglevGraphBuilder::TryReduceFunctionPrototypeCall( compiler::JSFunctionRef target, CallArguments& args) { // We can't reduce Function#call when there is no receiver function. if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { return {}; } ValueNode* receiver = GetValueOrUndefined(args.receiver()); args.PopReceiver(ConvertReceiverMode::kAny); SaveCallSpeculationScope saved(this); return ReduceCall(receiver, args, saved.value()); } MaybeReduceResult MaglevGraphBuilder::TryReduceFunctionPrototypeApply( compiler::JSFunctionRef target, CallArguments& args) { compiler::OptionalHeapObjectRef maybe_receiver; if (current_speculation_feedback().IsValid()) { const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForCall(current_speculation_feedback()); DCHECK_EQ(processed_feedback.kind(), compiler::ProcessedFeedback::kCall); const compiler::CallFeedback& call_feedback = processed_feedback.AsCall(); if (call_feedback.call_feedback_content() == CallFeedbackContent::kReceiver) { maybe_receiver = call_feedback.target(); } } return TryReduceFunctionPrototypeApplyCallWithReceiver( maybe_receiver, args, current_speculation_feedback()); } namespace { template <size_t MaxKindCount, typename KindsToIndexFunc> bool CanInlineArrayResizingBuiltin( compiler::JSHeapBroker* broker, const PossibleMaps& possible_maps, std::array<SmallZoneVector<compiler::MapRef, 2>, MaxKindCount>& map_kinds, KindsToIndexFunc&& elements_kind_to_index, int* unique_kind_count, bool is_loading) { uint8_t kind_bitmap = 0; for (compiler::MapRef map : possible_maps) { if (!map.supports_fast_array_resize(broker)) { return false; } ElementsKind kind = map.elements_kind(); if (is_loading && kind == HOLEY_DOUBLE_ELEMENTS) { return false; } // Group maps by elements kind, using the provided function to translate // elements kinds to indices. // kind_bitmap is used to get the unique kinds (predecessor count for the // next block). uint8_t kind_index = elements_kind_to_index(kind); kind_bitmap |= 1 << kind_index; map_kinds[kind_index].push_back(map); } *unique_kind_count = base::bits::CountPopulation(kind_bitmap); DCHECK_GE(*unique_kind_count, 1); return true; } } // namespace template <typename MapKindsT, typename IndexToElementsKindFunc, typename BuildKindSpecificFunc> MaybeReduceResult MaglevGraphBuilder::BuildJSArrayBuiltinMapSwitchOnElementsKind( ValueNode* receiver, const MapKindsT& map_kinds, MaglevSubGraphBuilder& sub_graph, std::optional<MaglevSubGraphBuilder::Label>& do_return, int unique_kind_count, IndexToElementsKindFunc&& index_to_elements_kind, BuildKindSpecificFunc&& build_kind_specific, bool make_smi_fallthrough_to_object) { // TODO(pthier): Support map packing. DCHECK(!V8_MAP_PACKING_BOOL); ValueNode* receiver_map; GET_VALUE_OR_ABORT(receiver_map, BuildLoadTaggedField(receiver, HeapObject::kMapOffset)); int emitted_kind_checks = 0; bool any_successful = false; std::optional<MaglevSubGraphBuilder::Label> object_case; if (make_smi_fallthrough_to_object) { object_case.emplace(&sub_graph, 2); } for (size_t kind_index = 0; kind_index < map_kinds.size(); kind_index++) { const auto& maps = map_kinds[kind_index]; // Skip kinds we haven't observed. if (maps.empty()) continue; ElementsKind kind = index_to_elements_kind(kind_index); // Create branches for all but the last elements kind. We don't need // to check the maps of the last kind, as all possible maps have already // been checked when the property (builtin name) was loaded. if (++emitted_kind_checks < unique_kind_count) { MaglevSubGraphBuilder::Label check_next_map(&sub_graph, 1); std::optional<MaglevSubGraphBuilder::Label> do_kind_specific_action; if (maps.size() > 1) { do_kind_specific_action.emplace(&sub_graph, static_cast<int>(maps.size())); for (size_t map_index = 1; map_index < maps.size(); map_index++) { RETURN_IF_ABORT(sub_graph.GotoIfTrue<BranchIfReferenceEqual>( &*do_kind_specific_action, {receiver_map, GetConstant(maps[map_index])})); } } RETURN_IF_ABORT(sub_graph.GotoIfFalse<BranchIfReferenceEqual>( &check_next_map, {receiver_map, GetConstant(maps[0])})); if (do_kind_specific_action.has_value()) { sub_graph.Goto(&*do_kind_specific_action); sub_graph.Bind(&*do_kind_specific_action); } if (IsObjectElementsKind(kind) && make_smi_fallthrough_to_object) { DCHECK(object_case.has_value()); sub_graph.GotoOrTrim(&*object_case); sub_graph.Bind(&*object_case); } any_successful |= (!build_kind_specific(kind).IsDoneWithAbort()); if (IsSmiElementsKind(kind) && make_smi_fallthrough_to_object) { // After building the SMI specific parts (see build_kind_specific call // above), jump to the beginning of the case for the object elements // kind. DCHECK(object_case.has_value()); sub_graph.GotoOrTrim(&*object_case); } else { DCHECK(do_return.has_value()); sub_graph.GotoOrTrim(&*do_return); } sub_graph.Bind(&check_next_map); } else { DCHECK_IMPLIES(make_smi_fallthrough_to_object, !IsSmiElementsKind(kind)); if (IsObjectElementsKind(kind) && make_smi_fallthrough_to_object) { DCHECK(object_case.has_value()); sub_graph.GotoOrTrim(&*object_case); sub_graph.Bind(&*object_case); } any_successful |= (!build_kind_specific(kind).IsDoneWithAbort()); if (do_return.has_value()) { sub_graph.GotoOrTrim(&*do_return); } } } DCHECK_IMPLIES(!any_successful, !current_block()); return any_successful ? ReduceResult::Done() : ReduceResult::DoneWithAbort(); } MaybeReduceResult MaglevGraphBuilder::TryReduceMapPrototypeGet( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; if (!is_turbolev()) { // TODO(dmercadier): consider also doing this lowering for Maglev. This is // currently not done, because to be efficient, this lowering would need to // inline FindOrderedHashMapEntryInt32Key (cf // turboshaft/machine-lowering-reducer-inl.h), which might be a bit too // low-level for Maglev. return {}; } if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { FAIL(" to reduce Map.prototype.Get - no receiver"); } if (args.count() != 1) { FAIL(" to reduce Map.prototype.Get - invalid argument count"); } ValueNode* receiver = GetValueOrUndefined(args.receiver()); auto possible_receiver_maps = known_node_aspects().TryGetPossibleMaps(receiver); // If the map set is not found, then we don't know anything about the map of // the receiver, so bail. if (!possible_receiver_maps) { FAIL(" to reduce Map.prototype.Get - unknown receiver map"); } // If the set of possible maps is empty, then there's no possible map for this // receiver, therefore this path is unreachable at runtime. We're unlikely to // ever hit this case, BuildCheckMaps should already unconditionally deopt, // but check it in case another checking operation fails to statically // unconditionally deopt. if (possible_receiver_maps->is_empty()) { // TODO(leszeks): Add an unreachable assert here. return ReduceResult::DoneWithAbort(); } if (!AllOfInstanceTypesAre(*possible_receiver_maps, JS_MAP_TYPE)) { FAIL(" to reduce Map.prototype.Get - wrong receiver maps"); } ValueNode* key = args[0]; ValueNode* table; GET_VALUE_OR_ABORT( table, BuildLoadTaggedField(receiver, JSCollection::kTableOffset)); ValueNode* entry; auto key_info = known_node_aspects().TryGetInfoFor(key); if (key_info && key_info->alternative().int32()) { GET_VALUE_OR_ABORT(entry, AddNewNode<MapPrototypeGetInt32Key>( {table, key_info->alternative().int32()})); } else { GET_VALUE_OR_ABORT(entry, AddNewNode<MapPrototypeGet>({table, key})); } return entry; } MaybeReduceResult MaglevGraphBuilder::TryReduceSetPrototypeHas( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; if (!is_turbolev()) { // See the comment in TryReduceMapPrototypeGet. return {}; } ValueNode* receiver = args.receiver(); if (!receiver) return {}; if (args.count() != 1) { FAIL(" to reduce Set.prototype.has - invalid argument count"); } auto possible_receiver_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_receiver_maps) { FAIL(" to reduce Set.prototype.has - receiver map is unknown"); } // If the set of possible maps is empty, then there's no possible map for this // receiver, therefore this path is unreachable at runtime. We're unlikely to // ever hit this case, BuildCheckMaps should already unconditionally deopt, // but check it in case another checking operation fails to statically // unconditionally deopt. if (possible_receiver_maps->is_empty()) { // TODO(leszeks): Add an unreachable assert here. return ReduceResult::DoneWithAbort(); } if (!AllOfInstanceTypesAre(*possible_receiver_maps, JS_SET_TYPE)) { FAIL(" to reduce Set.prototype.has - wrong receiver maps"); } ValueNode* key = args[0]; ValueNode* table; GET_VALUE_OR_ABORT( table, BuildLoadTaggedField(receiver, JSCollection::kTableOffset)); return AddNewNode<SetPrototypeHas>({table, key}); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypePush( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; // We can't reduce Function#call when there is no receiver function. if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { FAIL(" to reduce Array.prototype.push - no receiver"); } if (args.count() == 0) { FAIL(" to reduce Array.prototype.push - no argument"); } ValueNode* receiver = GetValueOrUndefined(args.receiver()); auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); // If the map set is not found, then we don't know anything about the map of // the receiver, so bail. if (!possible_maps) { FAIL(" to reduce Array.prototype.push - unknown receiver map"); } // If the set of possible maps is empty, then there's no possible map for this // receiver, therefore this path is unreachable at runtime. We're unlikely to // ever hit this case, BuildCheckMaps should already unconditionally deopt, // but check it in case another checking operation fails to statically // unconditionally deopt. if (possible_maps->is_empty()) { // TODO(leszeks): Add an unreachable assert here. return ReduceResult::DoneWithAbort(); } if (!broker()->dependencies()->DependOnNoElementsProtector()) { FAIL(" to reduce Array.prototype.push - NoElementsProtector invalidated"); } // Check that inlining resizing array builtins is supported and group maps // by elements kind. std::array<SmallZoneVector<compiler::MapRef, 2>, 3> map_kinds = { SmallZoneVector<compiler::MapRef, 2>(zone()), SmallZoneVector<compiler::MapRef, 2>(zone()), SmallZoneVector<compiler::MapRef, 2>(zone())}; // Function to group maps by elements kind, ignoring packedness. Packedness // doesn't matter for push(). // Kinds we care about are all paired in the first 6 values of ElementsKind, // so we can use integer division to truncate holeyness. auto elements_kind_to_index = [&](ElementsKind kind) { static_assert(kFastElementsKindCount <= 6); static_assert(kFastElementsKindPackedToHoley == 1); return static_cast<uint8_t>(kind) / 2; }; auto index_to_elements_kind = [&](uint8_t kind_index) { return static_cast<ElementsKind>(kind_index * 2); }; int unique_kind_count; if (!CanInlineArrayResizingBuiltin(broker(), *possible_maps, map_kinds, elements_kind_to_index, &unique_kind_count, false)) { FAIL(" to reduce Array.prototype.push - Map doesn't support fast resizing"); } // If we have maps for both PACKED_SMI_ELEMENTS / HOLEY_SMI_ELEMENTS and // PACKED_ELEMENTS / HOLEY_ELEMENTS, we can make the smi case fallthrough to // the object case. int smi_index = elements_kind_to_index(PACKED_SMI_ELEMENTS); DCHECK_EQ(smi_index, elements_kind_to_index(HOLEY_SMI_ELEMENTS)); int object_index = elements_kind_to_index(PACKED_ELEMENTS); DCHECK_EQ(object_index, elements_kind_to_index(HOLEY_ELEMENTS)); bool make_smi_fallthrough_to_object = !map_kinds[smi_index].empty() && !map_kinds[object_index].empty(); MaglevSubGraphBuilder sub_graph(this, 0); std::optional<MaglevSubGraphBuilder::Label> do_return; if ((!make_smi_fallthrough_to_object && unique_kind_count > 1) || unique_kind_count > 2) { do_return.emplace(&sub_graph, unique_kind_count - (make_smi_fallthrough_to_object ? 1 : 0)); } ValueNode* old_array_length_smi; GET_VALUE_OR_ABORT(old_array_length_smi, GetSmiValue(BuildLoadJSArrayLength(receiver))); ValueNode* old_array_length; GET_VALUE_OR_ABORT(old_array_length, AddNewNode<UnsafeSmiUntag>({old_array_length_smi})); // TODO(dmercadier): consider computing on Smis directly rather than untagging // and then retagging. ValueNode* new_array_length; GET_VALUE_OR_ABORT(new_array_length, AddNewNode<Int32AddWithOverflow>( {old_array_length, GetInt32Constant(static_cast<int>(args.count()))})); ValueNode* last_index; GET_VALUE_OR_ABORT(last_index, AddNewNode<Int32Decrement>({new_array_length})); ValueNode* elements_array; GET_VALUE_OR_ABORT(elements_array, BuildLoadElements(receiver)); ValueNode* elements_array_length = BuildLoadFixedArrayLength(elements_array); auto build_array_push = [&](ElementsKind kind) { // We first make sure that all inputs have the expected type, so that if we // need to deopt, we'll do so before writing anything to the array. if (make_smi_fallthrough_to_object && IsSmiElementsKind(kind)) { // Usually, we rely on ConvertForStoring below to ensure that the inputs // have the right type. However, when make_smi_fallthrough_to_object is // true, we'll share the Object and Smi storing loop, and // ConvertForStoring will be a no-op since we'll just be storing AnyTagged // objects. Thus, we have to insert CheckSmis here, before falling through // to the Object case. for (ValueNode*& arg : args) { RETURN_IF_ABORT(BuildCheckSmi(arg)); } return ReduceResult::Done(); } base::SmallVector<ValueNode*, 16> args_to_store; args_to_store.reserve(args.count()); for (ValueNode* arg : args) { ValueNode* value; GET_VALUE_OR_ABORT(value, ConvertForStoring(arg, kind)); args_to_store.push_back(value); // Storing to object kinds don't add conversions; no extra code will be // added to the smi case if it falls through the object case. DCHECK_IMPLIES(IsObjectElementsKind(kind), value == arg); } DCHECK_EQ(args_to_store.size(), args.count()); // All inputs have the right type, we can now extend the array and write. ValueNode* writable_elements_array; GET_VALUE_OR_ABORT( writable_elements_array, AddNewNode<MaybeGrowFastElements>( {elements_array, receiver, last_index, elements_array_length}, kind)); // If {MaybeGrowFastElements} doesn't deopt, then we're guaranteed that the // new length fits in a Smi. EnsureType(new_array_length, NodeType::kSmi); ValueNode* new_array_length_smi; GET_VALUE_OR_ABORT(new_array_length_smi, AddNewNode<UnsafeSmiTagInt32>({new_array_length})); RETURN_IF_ABORT(AddNewNode<StoreTaggedFieldNoWriteBarrier>( {receiver, new_array_length_smi}, JSArray::kLengthOffset, StoreTaggedMode::kDefault)); for (int index = 0; index < static_cast<int>(args_to_store.size()); index++) { ValueNode* store_index; GET_VALUE_OR_ABORT( store_index, AddNewNode<Int32Add>({old_array_length, GetInt32Constant(index)})); ValueNode* value = args_to_store[index]; // Do the store if (IsDoubleElementsKind(kind)) { RETURN_IF_ABORT(BuildStoreFixedDoubleArrayElement( kind, writable_elements_array, store_index, value)); } else { DCHECK(IsSmiElementsKind(kind) || IsObjectElementsKind(kind)); RETURN_IF_ABORT(BuildStoreFixedArrayElement(writable_elements_array, store_index, value)); } } return ReduceResult::Done(); }; RETURN_IF_ABORT(BuildJSArrayBuiltinMapSwitchOnElementsKind( receiver, map_kinds, sub_graph, do_return, unique_kind_count, index_to_elements_kind, build_array_push, make_smi_fallthrough_to_object)); if (do_return.has_value()) { sub_graph.Bind(&*do_return); } RecordKnownProperty(receiver, broker()->length_string(), new_array_length, false, compiler::AccessMode::kStore); return new_array_length; } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayPrototypePop( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; // We can't reduce Function#call when there is no receiver function. if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { FAIL(" to reduce Array.prototype.pop - no receiver"); } ValueNode* receiver = GetValueOrUndefined(args.receiver()); auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); // If the map set is not found, then we don't know anything about the map of // the receiver, so bail. if (!possible_maps) { FAIL(" to reduce Array.prototype.pop - unknown receiver map"); } // If the set of possible maps is empty, then there's no possible map for this // receiver, therefore this path is unreachable at runtime. We're unlikely to // ever hit this case, BuildCheckMaps should already unconditionally deopt, // but check it in case another checking operation fails to statically // unconditionally deopt. if (possible_maps->is_empty()) { // TODO(leszeks): Add an unreachable assert here. return ReduceResult::DoneWithAbort(); } if (!broker()->dependencies()->DependOnNoElementsProtector()) { FAIL(" to reduce Array.prototype.pop - NoElementsProtector invalidated"); } constexpr int max_kind_count = 4; std::array<SmallZoneVector<compiler::MapRef, 2>, max_kind_count> map_kinds = { SmallZoneVector<compiler::MapRef, 2>(zone()), SmallZoneVector<compiler::MapRef, 2>(zone()), SmallZoneVector<compiler::MapRef, 2>(zone()), SmallZoneVector<compiler::MapRef, 2>(zone())}; // Smi and Object elements kinds are treated as identical for pop, so we can // group them together without differentiation. // ElementsKind is mapped to an index in the 4 element array using: // - Bit 2 (Only set for double in the fast element range) is mapped to bit // 1) // - Bit 0 (packedness) // The complete mapping: // +-------+----------------------------------------------+ // | Index | ElementsKinds | // +-------+----------------------------------------------+ // | 0 | PACKED_SMI_ELEMENTS and PACKED_ELEMENTS | // | 1 | HOLEY_SMI_ELEMENETS and HOLEY_ELEMENTS | // | 2 | PACKED_DOUBLE_ELEMENTS | // | 3 | HOLEY_DOUBLE_ELEMENTS | // +-------+----------------------------------------------+ auto elements_kind_to_index = [&](ElementsKind kind) { uint8_t kind_int = static_cast<uint8_t>(kind); uint8_t kind_index = ((kind_int & 0x4) >> 1) | (kind_int & 0x1); DCHECK_LT(kind_index, max_kind_count); return kind_index; }; auto index_to_elements_kind = [&](uint8_t kind_index) { uint8_t kind_int; kind_int = ((kind_index & 0x2) << 1) | (kind_index & 0x1); return static_cast<ElementsKind>(kind_int); }; int unique_kind_count; if (!CanInlineArrayResizingBuiltin(broker(), *possible_maps, map_kinds, elements_kind_to_index, &unique_kind_count, true)) { FAIL(" to reduce Array.prototype.pop - Map doesn't support fast resizing"); } MaglevSubGraphBuilder sub_graph(this, 2); MaglevSubGraphBuilder::Variable var_value(0); MaglevSubGraphBuilder::Variable var_new_array_length(1); std::optional<MaglevSubGraphBuilder::Label> do_return = std::make_optional<MaglevSubGraphBuilder::Label>( &sub_graph, unique_kind_count + 1, std::initializer_list<MaglevSubGraphBuilder::Variable*>{ &var_value, &var_new_array_length}); MaglevSubGraphBuilder::Label empty_array(&sub_graph, 1); ValueNode* old_array_length_smi; GET_VALUE_OR_ABORT(old_array_length_smi, GetSmiValue(BuildLoadJSArrayLength(receiver))); // If the array is empty, skip the pop and return undefined. RETURN_IF_ABORT(sub_graph.GotoIfTrue<BranchIfReferenceEqual>( &empty_array, {old_array_length_smi, GetSmiConstant(0)})); ValueNode* elements_array; GET_VALUE_OR_ABORT(elements_array, BuildLoadElements(receiver)); ValueNode* new_array_length_smi; GET_VALUE_OR_ABORT(new_array_length_smi, AddNewNode<CheckedSmiDecrement>({old_array_length_smi})); ValueNode* new_array_length; GET_VALUE_OR_ABORT(new_array_length, AddNewNode<UnsafeSmiUntag>({new_array_length_smi})); sub_graph.set(var_new_array_length, new_array_length_smi); auto build_array_pop = [&](ElementsKind kind) { // Handle COW if needed. ValueNode* writable_elements_array; if (IsSmiOrObjectElementsKind(kind)) { GET_VALUE_OR_ABORT( writable_elements_array, AddNewNode<EnsureWritableFastElements>({elements_array, receiver})); } else { writable_elements_array = elements_array; } // Store new length. RETURN_IF_ABORT(AddNewNode<StoreTaggedFieldNoWriteBarrier>( {receiver, new_array_length_smi}, JSArray::kLengthOffset, StoreTaggedMode::kDefault, broker()->length_string())); // Load the value and store the hole in it's place. ValueNode* value; if (IsDoubleElementsKind(kind)) { GET_VALUE_OR_ABORT(value, BuildLoadFixedDoubleArrayElement( writable_elements_array, new_array_length)); RETURN_IF_ABORT(BuildStoreFixedDoubleArrayElement( kind, writable_elements_array, new_array_length, GetFloat64Constant(Float64::FromBits(kHoleNanInt64)))); } else { DCHECK(IsSmiElementsKind(kind) || IsObjectElementsKind(kind)); GET_VALUE_OR_ABORT(value, BuildLoadFixedArrayElement( writable_elements_array, new_array_length)); RETURN_IF_ABORT(BuildStoreFixedArrayElement( writable_elements_array, new_array_length, GetRootConstant(RootIndex::kTheHoleValue))); } if (IsHoleyElementsKind(kind)) { GET_VALUE_OR_ABORT(value, AddNewNode<ConvertHoleToUndefined>({value})); } sub_graph.set(var_value, value); return ReduceResult::Done(); }; RETURN_IF_ABORT(BuildJSArrayBuiltinMapSwitchOnElementsKind( receiver, map_kinds, sub_graph, do_return, unique_kind_count, index_to_elements_kind, build_array_pop)); sub_graph.Bind(&empty_array); sub_graph.set(var_new_array_length, GetSmiConstant(0)); sub_graph.set(var_value, GetRootConstant(RootIndex::kUndefinedValue)); sub_graph.Goto(&*do_return); sub_graph.Bind(&*do_return); RecordKnownProperty(receiver, broker()->length_string(), sub_graph.get(var_new_array_length), false, compiler::AccessMode::kStore); return sub_graph.get(var_value); } MaybeReduceResult MaglevGraphBuilder::TryReduceFunctionPrototypeHasInstance( compiler::JSFunctionRef target, CallArguments& args) { // We can't reduce Function#hasInstance when there is no receiver function. if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { return {}; } if (args.count() != 1) { return {}; } compiler::OptionalJSObjectRef maybe_receiver_constant = TryGetConstant<JSObject>(args.receiver()); if (!maybe_receiver_constant) { return {}; } if (!maybe_receiver_constant->map(broker()).is_callable()) { return {}; } return BuildOrdinaryHasInstance(args[0], maybe_receiver_constant.value(), nullptr); } MaybeReduceResult MaglevGraphBuilder::TryReduceObjectPrototypeHasOwnProperty( compiler::JSFunctionRef target, CallArguments& args) { if (!CanSpeculateCall()) return {}; if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { return {}; } // We can constant-fold the {receiver.hasOwnProperty(name)} builtin call to // the {True} node in this case: // for (name in receiver) { // if (receiver.hasOwnProperty(name)) { // ... // } // } if (args.count() != 1 || args[0] != current_for_in_state.key) { return {}; } ValueNode* receiver = args.receiver(); if (receiver == current_for_in_state.receiver) { if (current_for_in_state.receiver_needs_map_check) { ValueNode* receiver_map; GET_VALUE_OR_ABORT( receiver_map, BuildLoadTaggedField(receiver, HeapObject::kMapOffset)); RETURN_IF_ABORT(AddNewNode<CheckDynamicValue>( {receiver_map, current_for_in_state.cache_type}, DeoptimizeReason::kWrongMapDynamic)); current_for_in_state.receiver_needs_map_check = false; } return GetRootConstant(RootIndex::kTrueValue); } // We can also optimize for this case below: // receiver(is a heap constant with fast map) // ^ // | object(all keys are enumerable) // | ^ // | | // | JSForInNext // | ^ // +----+ | // | | // JSCall[hasOwnProperty] // We can replace the {JSCall} with several internalized string // comparisons. compiler::OptionalMapRef maybe_receiver_map; compiler::OptionalHeapObjectRef receiver_ref = TryGetConstant<HeapObject>(receiver); if (receiver_ref.has_value()) { compiler::HeapObjectRef receiver_object = receiver_ref.value(); compiler::MapRef receiver_map = receiver_object.map(broker()); maybe_receiver_map = receiver_map; } else { NodeInfo* known_info = GetOrCreateInfoFor(receiver); if (known_info->possible_maps_are_known()) { compiler::ZoneRefSet<Map> possible_maps = known_info->possible_maps(); if (possible_maps.size() == 1) { compiler::MapRef receiver_map = *(possible_maps.begin()); maybe_receiver_map = receiver_map; } } } if (!maybe_receiver_map.has_value()) return {}; compiler::MapRef receiver_map = maybe_receiver_map.value(); InstanceType instance_type = receiver_map.instance_type(); int const nof = receiver_map.NumberOfOwnDescriptors(); // We set a heuristic value to limit the compare instructions number. if (nof > 4 || IsSpecialReceiverInstanceType(instance_type) || receiver_map.is_dictionary_map()) { return {}; } RETURN_IF_ABORT(BuildCheckMaps(receiver, base::VectorOf({receiver_map}))); // Replace builtin call with several internalized string comparisons. MaglevSubGraphBuilder sub_graph(this, 1); MaglevSubGraphBuilder::Variable var_result(0); MaglevSubGraphBuilder::Label done( &sub_graph, nof + 1, std::initializer_list<MaglevSubGraphBuilder::Variable*>{&var_result}); const compiler::DescriptorArrayRef descriptor_array = receiver_map.instance_descriptors(broker()); for (InternalIndex key_index : InternalIndex::Range(nof)) { compiler::NameRef receiver_key = descriptor_array.GetPropertyKey(broker(), key_index); ValueNode* lhs = GetConstant(receiver_key); sub_graph.set(var_result, GetRootConstant(RootIndex::kTrueValue)); RETURN_IF_ABORT( sub_graph.GotoIfTrue<BranchIfReferenceEqual>(&done, {lhs, args[0]})); } sub_graph.set(var_result, GetRootConstant(RootIndex::kFalseValue)); sub_graph.Goto(&done); sub_graph.Bind(&done); return sub_graph.get(var_result); } MaybeReduceResult MaglevGraphBuilder::TryReduceGetProto(ValueNode* object) { auto possible_maps = known_node_aspects().TryGetPossibleMaps(object); if (!possible_maps) { return {}; } if (possible_maps->is_empty()) { return ReduceResult::DoneWithAbort(); } auto it = possible_maps->begin(); compiler::MapRef map = *it; if (IsSpecialReceiverInstanceType(map.instance_type())) { return {}; } DCHECK(!map.IsPrimitiveMap() && map.IsJSReceiverMap()); compiler::HeapObjectRef proto = map.prototype(broker()); ++it; for (; it != possible_maps->end(); ++it) { map = *it; if (IsSpecialReceiverInstanceType(map.instance_type()) || !proto.equals(map.prototype(broker()))) { return {}; } DCHECK(!map.IsPrimitiveMap() && map.IsJSReceiverMap()); } return GetConstant(proto); } MaybeReduceResult MaglevGraphBuilder::TryReduceObjectPrototypeGetProto( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() != 0) return {}; if (!args.receiver()) return {}; return TryReduceGetProto(args.receiver()); } MaybeReduceResult MaglevGraphBuilder::TryReduceObjectGetPrototypeOf( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() != 1) return {}; return TryReduceGetProto(args[0]); } MaybeReduceResult MaglevGraphBuilder::TryReduceReflectGetPrototypeOf( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceObjectGetPrototypeOf(target, args); } MaybeReduceResult MaglevGraphBuilder::TryReduceMathRound( compiler::JSFunctionRef target, CallArguments& args) { return DoTryReduceMathRound(args, Float64Round::Kind::kNearest); } MaybeReduceResult MaglevGraphBuilder::TryReduceNumberParseInt( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) { return GetRootConstant(RootIndex::kNanValue); } if (args.count() != 1) { if (RootConstant* root_cst = args[1]->TryCast<RootConstant>()) { if (root_cst->index() != RootIndex::kUndefinedValue) { return {}; } } else if (SmiConstant* smi_cst = args[1]->TryCast<SmiConstant>()) { if (smi_cst->value().value() != 10 && smi_cst->value().value() != 0) { return {}; } } else { return {}; } } ValueNode* arg = args[0]; switch (arg->value_representation()) { case ValueRepresentation::kUint32: case ValueRepresentation::kInt32: case ValueRepresentation::kIntPtr: return arg; case ValueRepresentation::kTagged: switch (CheckTypes(arg, {NodeType::kSmi})) { case NodeType::kSmi: return arg; default: // TODO(verwaest): Support actually parsing strings, converting // doubles to ints, ... return {}; } case ValueRepresentation::kFloat64: case ValueRepresentation::kHoleyFloat64: return {}; case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } } MaybeReduceResult MaglevGraphBuilder::TryReduceMathAbs( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) { return GetRootConstant(RootIndex::kNanValue); } ValueNode* arg = args[0]; switch (arg->value_representation()) { case ValueRepresentation::kUint32: case ValueRepresentation::kIntPtr: // TODO(388844115): Rename IntPtr to make it clear it's non-negative. return arg; case ValueRepresentation::kInt32: if (!CanSpeculateCall()) return {}; return AddNewNode<Int32AbsWithOverflow>({arg}); case ValueRepresentation::kTagged: switch (CheckTypes(arg, {NodeType::kSmi, NodeType::kNumberOrOddball})) { case NodeType::kSmi: if (!CanSpeculateCall()) return {}; return AddNewNode<Int32AbsWithOverflow>({arg}); case NodeType::kNumberOrOddball: { ValueNode* float64_value; GET_VALUE_OR_ABORT( float64_value, GetFloat64ForToNumber(arg, NodeType::kNumberOrOddball)); return AddNewNode<Float64Abs>({float64_value}); } // TODO(verwaest): Add support for ToNumberOrNumeric and deopt. default: break; } break; case ValueRepresentation::kHoleyFloat64: arg = AddNewNodeNoInputConversion<UnsafeHoleyFloat64ToFloat64>({arg}); [[fallthrough]]; case ValueRepresentation::kFloat64: return AddNewNode<Float64Abs>({arg}); case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } return {}; } MaybeReduceResult MaglevGraphBuilder::TryReduceMathFloor( compiler::JSFunctionRef target, CallArguments& args) { return DoTryReduceMathRound(args, Float64Round::Kind::kFloor); } MaybeReduceResult MaglevGraphBuilder::TryReduceMathCeil( compiler::JSFunctionRef target, CallArguments& args) { return DoTryReduceMathRound(args, Float64Round::Kind::kCeil); } MaybeReduceResult MaglevGraphBuilder::DoTryReduceMathRound( CallArguments& args, Float64Round::Kind kind) { if (args.count() == 0) { return GetRootConstant(RootIndex::kNanValue); } ValueNode* arg = args[0]; auto arg_repr = arg->value_representation(); if (arg_repr == ValueRepresentation::kInt32 || arg_repr == ValueRepresentation::kUint32 || arg_repr == ValueRepresentation::kIntPtr) { return arg; } if (CheckType(arg, NodeType::kSmi)) return arg; if (!IsSupported(CpuOperation::kFloat64Round)) { return {}; } if (arg_repr == ValueRepresentation::kFloat64 || arg_repr == ValueRepresentation::kHoleyFloat64) { if (arg_repr == ValueRepresentation::kHoleyFloat64) { arg = AddNewNodeNoInputConversion<UnsafeHoleyFloat64ToFloat64>({arg}); } return AddNewNode<Float64Round>({arg}, kind); } DCHECK_EQ(arg_repr, ValueRepresentation::kTagged); if (CheckType(arg, NodeType::kNumberOrOddball)) { ValueNode* float64_value; GET_VALUE_OR_ABORT(float64_value, GetFloat64ForToNumber(arg, NodeType::kNumberOrOddball)); return AddNewNode<Float64Round>({float64_value}, kind); } if (!CanSpeculateCall()) return {}; LazyDeoptFrameScope continuation_scope(this, Float64Round::continuation(kind)); ToNumberOrNumeric* conversion; GET_VALUE_OR_ABORT(conversion, AddNewNode<ToNumberOrNumeric>( {arg}, Object::Conversion::kToNumber)); // TODO(victorgomes): rely on automatic input conversion here rather than // calling UncheckedNumberToFloat64 manually. ValueNode* float64_value; GET_VALUE_OR_ABORT(float64_value, AddNewNode<UnsafeNumberToFloat64>({conversion})); return AddNewNode<Float64Round>({float64_value}, kind); } MaybeReduceResult MaglevGraphBuilder::TryReduceMathMin( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) { return GetConstant(broker()->infinity_value()); } return TryReduceMathMinMax( args, [&](ValueNode* v1, ValueNode* v2) -> ReduceResult { return BuildInt32Min(v1, v2); }, [&](ValueNode* v1, ValueNode* v2) -> ReduceResult { ValueNode* v1_float; GET_VALUE_OR_ABORT(v1_float, reducer_.GetFloat64ForToNumber( v1, NodeType::kNumberOrOddball)); ValueNode* v2_float; GET_VALUE_OR_ABORT(v2_float, reducer_.GetFloat64ForToNumber( v2, NodeType::kNumberOrOddball)); RETURN_IF_DONE(reducer_.TryFoldFloat64Min(v1_float, v2_float)); return AddNewNode<Float64Min>({v1_float, v2_float}); }); } MaybeReduceResult MaglevGraphBuilder::TryReduceMathMax( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) { return GetConstant(broker()->minus_infinity_value()); } return TryReduceMathMinMax( args, [&](ValueNode* v1, ValueNode* v2) -> ReduceResult { return BuildInt32Max(v1, v2); }, [&](ValueNode* v1, ValueNode* v2) -> ReduceResult { ValueNode* v1_float; GET_VALUE_OR_ABORT(v1_float, reducer_.GetFloat64ForToNumber( v1, NodeType::kNumberOrOddball)); ValueNode* v2_float; GET_VALUE_OR_ABORT(v2_float, reducer_.GetFloat64ForToNumber( v2, NodeType::kNumberOrOddball)); RETURN_IF_DONE(reducer_.TryFoldFloat64Max(v1_float, v2_float)); return AddNewNode<Float64Max>({v1_float, v2_float}); }); } template <typename Int32Binop, typename Float64Binop> MaybeReduceResult MaglevGraphBuilder::TryReduceMathMinMax( CallArguments& args, Int32Binop&& int32_case, Float64Binop&& float64_case) { bool all_args_are_int32_or_smi = std::all_of(args.begin(), args.end(), [&](ValueNode* arg) { return GetType(arg) == NodeType::kSmi || arg->properties().value_representation() == ValueRepresentation::kInt32; }); if (all_args_are_int32_or_smi) { // TODO(C++23): Use std::ranges::fold_left_first. // int32_case will convert the parameters to Int32. return std::reduce( args.begin() + 1, args.end(), ReduceResult(*args.begin()), [&](ReduceResult lhs_result, ReduceResult rhs_result) -> ReduceResult { ValueNode* lhs; GET_VALUE_OR_ABORT(lhs, lhs_result); ValueNode* rhs; GET_VALUE_OR_ABORT(rhs, rhs_result); return int32_case(lhs, rhs); }); } // TODO(marja): Investigate whether a non-speculative Float64 case helps. if (!CanSpeculateCall()) return {}; // float64_case will convert the parameters to Float64. Only the first one // has to be converted explicitly. ValueNode* first_result; GET_VALUE_OR_ABORT(first_result, reducer_.GetFloat64ForToNumber( args[0], NodeType::kNumberOrOddball)); return std::reduce( args.begin() + 1, args.end(), ReduceResult(first_result->Unwrap()), [&](ReduceResult lhs_result, ReduceResult rhs_result) -> ReduceResult { ValueNode* lhs; GET_VALUE_OR_ABORT(lhs, lhs_result); ValueNode* rhs; GET_VALUE_OR_ABORT(rhs, rhs_result); return float64_case(lhs, rhs); }); } MaybeReduceResult MaglevGraphBuilder::TryReduceArrayConstructor( compiler::JSFunctionRef target, CallArguments& args) { return TryReduceConstructArrayConstructor(target, GetConstant(target), args); } MaybeReduceResult MaglevGraphBuilder::TryReduceMathClz32( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() < 1) { return GetInt32Constant(32); } if (!IsSupported(CpuOperation::kMathClz32)) { return {}; } ValueNode* arg = args[0]; auto arg_repr = arg->value_representation(); if (arg_repr == ValueRepresentation::kInt32 || arg_repr == ValueRepresentation::kUint32 || arg_repr == ValueRepresentation::kIntPtr) { RETURN_IF_DONE(reducer_.TryFoldInt32CountLeadingZeros(arg)); return AddNewNode<Int32CountLeadingZeros>({arg}); } if (arg_repr == ValueRepresentation::kFloat64 || arg_repr == ValueRepresentation::kHoleyFloat64) { RETURN_IF_DONE(reducer_.TryFoldFloat64CountLeadingZeros(arg)); if (IsSupported(CpuOperation::kFloat64Round)) { if (arg_repr == ValueRepresentation::kHoleyFloat64) { GET_VALUE_OR_ABORT( arg, GetFloat64ForToNumber(arg, NodeType::kNumberOrOddball)); } return AddNewNode<Float64CountLeadingZeros>({arg}); } return {}; } DCHECK_EQ(arg_repr, ValueRepresentation::kTagged); if (CheckType(arg, NodeType::kNumber)) { return AddNewNode<TaggedCountLeadingZeros>({arg}); } if (!CanSpeculateCall()) { return {}; } LazyDeoptFrameScope continuation_scope( this, Float64CountLeadingZeros::continuation()); ToNumberOrNumeric* conversion; GET_VALUE_OR_ABORT(conversion, AddNewNode<ToNumberOrNumeric>( {arg}, Object::Conversion::kToNumber)); // TODO(victorgomes): rely on automatic input conversion here rather than // calling UnsafeNumberToFloat64 manually. ValueNode* float64_value; GET_VALUE_OR_ABORT(float64_value, AddNewNode<UnsafeNumberToFloat64>({conversion})); return AddNewNode<Float64CountLeadingZeros>({float64_value}); } MaybeReduceResult MaglevGraphBuilder::TryReduceStringConstructor( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() == 0) { return GetRootConstant(RootIndex::kempty_string); } return BuildToString(args[0], ToString::kConvertSymbol); } #define MATH_UNARY_IEEE_BUILTIN_REDUCER(MathName, ExtName, EnumName) \ MaybeReduceResult MaglevGraphBuilder::TryReduce##MathName( \ compiler::JSFunctionRef target, CallArguments& args) { \ if (args.count() < 1) { \ return GetRootConstant(RootIndex::kNanValue); \ } \ RETURN_IF_DONE(reducer_.TryFoldFloat64Ieee754Unary( \ Float64Ieee754Unary::Ieee754Function::k##EnumName, args[0])); \ if (!CanSpeculateCall() && !CheckType(args[0], NodeType::kNumber)) { \ return {}; \ } \ ValueNode* value; \ GET_VALUE_OR_ABORT(value, \ GetFloat64ForToNumber(args[0], NodeType::kNumber)); \ return AddNewNode<Float64Ieee754Unary>( \ {value}, Float64Ieee754Unary::Ieee754Function::k##EnumName); \ } IEEE_754_UNARY_LIST(MATH_UNARY_IEEE_BUILTIN_REDUCER) #undef MATH_UNARY_IEEE_BUILTIN_REDUCER #define MATH_BINARY_IEEE_BUILTIN_REDUCER(MathName, ExtName, EnumName) \ MaybeReduceResult MaglevGraphBuilder::TryReduce##MathName( \ compiler::JSFunctionRef target, CallArguments& args) { \ if (args.count() < 2) { \ if (args.count() == 1 && !CheckType(args[0], NodeType::kNumber)) { \ return {}; \ } \ return GetRootConstant(RootIndex::kNanValue); \ } \ RETURN_IF_DONE(reducer_.TryFoldFloat64Ieee754Binary( \ Float64Ieee754Binary::Ieee754Function::k##EnumName, args[0], \ args[1])); \ if (!CanSpeculateCall() && (!CheckType(args[0], NodeType::kNumber) || \ !CheckType(args[1], NodeType::kNumber))) { \ return {}; \ } \ ValueNode* lhs; \ GET_VALUE_OR_ABORT(lhs, \ GetFloat64ForToNumber(args[0], NodeType::kNumber)); \ ValueNode* rhs; \ GET_VALUE_OR_ABORT(rhs, \ GetFloat64ForToNumber(args[1], NodeType::kNumber)); \ return AddNewNode<Float64Ieee754Binary>( \ {lhs, rhs}, Float64Ieee754Binary::Ieee754Function::k##EnumName); \ } IEEE_754_BINARY_LIST(MATH_BINARY_IEEE_BUILTIN_REDUCER) #undef MATH_BINARY_IEEE_BUILTIN_REDUCER MaybeReduceResult MaglevGraphBuilder::TryReduceMathSqrt( compiler::JSFunctionRef target, CallArguments& args) { if (args.count() < 1) { return GetRootConstant(RootIndex::kNanValue); } if (!CanSpeculateCall() && args[0]->is_tagged()) { return {}; } ValueNode* value; GET_VALUE_OR_ABORT( value, GetFloat64ForToNumber(args[0], NodeType::kNumberOrOddball)); return AddNewNode<Float64Sqrt>({value}); } MaybeReduceResult MaglevGraphBuilder::TryReduceBuiltin( compiler::JSFunctionRef target, compiler::SharedFunctionInfoRef shared, CallArguments& args, const compiler::FeedbackSource& feedback_source) { if (args.mode() != CallArguments::kDefault) { // TODO(victorgomes): Maybe inline the spread stub? Or call known function // directly if arguments list is an array. return {}; } SaveCallSpeculationScope speculate(this, feedback_source); if (!shared.HasBuiltinId()) return {}; TRACE(" ! Trying to reduce builtin " << Builtins::name(shared.builtin_id())); switch (shared.builtin_id()) { #define CASE(Name, ...) \ case Builtin::k##Name: \ return TryReduce##Name(target, args); MAGLEV_REDUCED_BUILTIN(CASE) #undef CASE default: // TODO(v8:7700): Inline more builtins. return {}; } } ReduceResult MaglevGraphBuilder::GetConvertReceiver( compiler::SharedFunctionInfoRef shared, const CallArguments& args) { DCHECK(!IsTheHoleConstant(args.receiver())); if (shared.native() || shared.language_mode() == LanguageMode::kStrict) { if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { return GetRootConstant(RootIndex::kUndefinedValue); } else { return args.receiver(); } } if (args.receiver_mode() == ConvertReceiverMode::kNullOrUndefined) { return GetConstant( broker()->target_native_context().global_proxy_object(broker())); } ValueNode* receiver = args.receiver(); if (CheckType(receiver, NodeType::kJSReceiver)) return receiver; if (compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(receiver)) { compiler::HeapObjectRef constant = maybe_constant.value(); if (constant.IsNullOrUndefined()) { return GetConstant( broker()->target_native_context().global_proxy_object(broker())); } } return AddNewNode<ConvertReceiver>( {receiver}, broker()->target_native_context(), args.receiver_mode()); } std::pair<ReduceResult, base::Vector<ValueNode*>> MaglevGraphBuilder::GetArgumentsAsArrayOfValueNodes( compiler::SharedFunctionInfoRef shared, const CallArguments& args) { // TODO(victorgomes): Investigate if we can avoid this copy. int arg_count = static_cast<int>(args.count()); auto arguments = zone()->AllocateVector<ValueNode*>(arg_count + 1); if (IsTheHoleConstant(args.receiver())) { arguments[0] = args.receiver(); } else { ReduceResult result = GetConvertReceiver(shared, args); if (result.IsDoneWithAbort()) { return std::make_pair(ReduceResult::DoneWithAbort(), arguments); } GET_VALUE(arguments[0], result.value()); } for (int i = 0; i < arg_count; i++) { arguments[i + 1] = args[i]; } return std::make_pair(ReduceResult::Done(), arguments); } template <typename CallNode, typename... Args> ReduceResult MaglevGraphBuilder::AddNewCallNode(const CallArguments& args, Args&&... extra_args) { size_t input_count = args.count_with_receiver() + CallNode::kFixedInputCount; return AddNewNode<CallNode>( input_count, [&](CallNode* call) { int arg_index = 0; ValueNode* tagged_receiver; GET_VALUE_OR_ABORT( tagged_receiver, GetTaggedValue(GetValueOrUndefined(args.receiver()))); call->set_arg(arg_index++, tagged_receiver); for (size_t i = 0; i < args.count(); ++i) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call->set_arg(arg_index++, tagged_arg); } return ReduceResult::Done(); }, std::forward<Args>(extra_args)...); } ReduceResult MaglevGraphBuilder::BuildGenericCall(ValueNode* target, Call::TargetType target_type, const CallArguments& args) { // TODO(victorgomes): We do not collect call feedback from optimized/inlined // calls. In order to be consistent, we don't pass the feedback_source to the // IR, so that we avoid collecting for generic calls as well. We might want to // revisit this in the future. ValueNode* tagged_target; GET_VALUE_OR_ABORT(tagged_target, GetTaggedValue(target)); ValueNode* context; GET_VALUE(context, GetTaggedValue(GetContext())); switch (args.mode()) { case CallArguments::kDefault: return AddNewCallNode<Call>(args, args.receiver_mode(), target_type, tagged_target, context); case CallArguments::kWithSpread: DCHECK_EQ(args.receiver_mode(), ConvertReceiverMode::kAny); return AddNewCallNode<CallWithSpread>(args, tagged_target, context); case CallArguments::kWithArrayLike: DCHECK_EQ(args.receiver_mode(), ConvertReceiverMode::kAny); // We don't use AddNewCallNode here, because the number of required // arguments is known statically. return AddNewNode<CallWithArrayLike>( {target, GetValueOrUndefined(args.receiver()), args[0], GetContext()}); } } MaybeReduceResult MaglevGraphBuilder::BuildCallSelf( ValueNode* context, ValueNode* function, ValueNode* new_target, compiler::SharedFunctionInfoRef shared, CallArguments& args) { if (IsTheHoleConstant(args.receiver())) return {}; ValueNode* receiver; GET_VALUE_OR_ABORT(receiver, GetConvertReceiver(shared, args)); size_t input_count = args.count() + CallSelf::kFixedInputCount; graph()->set_has_recursive_calls(true); DCHECK_EQ( compilation_unit_->info()->toplevel_compilation_unit()->parameter_count(), shared.internal_formal_parameter_count_with_receiver_deprecated()); ValueNode* tagged_function; GET_VALUE_OR_ABORT(tagged_function, GetTaggedValue(function)); ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(context)); ValueNode* tagged_receiver; GET_VALUE_OR_ABORT(tagged_receiver, GetTaggedValue(receiver)); ValueNode* tagged_new_target; GET_VALUE(tagged_new_target, GetTaggedValue(new_target)); return AddNewNode<CallSelf>( input_count, [&](CallSelf* call) { for (int i = 0; i < static_cast<int>(args.count()); i++) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call->set_arg(i, tagged_arg); } return ReduceResult::Done(); }, compilation_unit_->info()->toplevel_compilation_unit()->parameter_count(), tagged_function, tagged_context, tagged_receiver, tagged_new_target); } bool MaglevGraphBuilder::TargetIsCurrentCompilingUnit( compiler::JSFunctionRef target) { if (compilation_unit_->info()->specialize_to_function_context()) { return target.object().equals( compilation_unit_->info()->toplevel_function()); } return target.object()->shared() == compilation_unit_->info()->toplevel_function()->shared(); } MaybeReduceResult MaglevGraphBuilder::TryReduceCallForApiFunction( compiler::FunctionTemplateInfoRef api_callback, compiler::OptionalSharedFunctionInfoRef maybe_shared, CallArguments& args) { if (IsTheHoleConstant(args.receiver())) { // The receiver may be the_hole when inlining derived constructors and // construct_as_builtin constructors. // TODO(jgruber): Support this case. return {}; } if (args.mode() != CallArguments::kDefault) { // TODO(victorgomes): Maybe inline the spread stub? Or call known function // directly if arguments list is an array. return {}; } // Check if the function has an associated C++ code to execute. compiler::OptionalObjectRef maybe_callback_data = api_callback.callback_data(broker()); if (!maybe_callback_data.has_value()) { // TODO(ishell): consider generating "return undefined" for empty function // instead of failing. return {}; } size_t input_count = args.count() + CallKnownApiFunction::kFixedInputCount; ValueNode* receiver; if (maybe_shared.has_value()) { GET_VALUE_OR_ABORT(receiver, GetConvertReceiver(maybe_shared.value(), args)); } else { receiver = args.receiver(); CHECK_NOT_NULL(receiver); } CallKnownApiFunction::Mode mode = broker()->dependencies()->DependOnNoProfilingProtector() ? (flags_.is_inline_api_calls_enabled ? CallKnownApiFunction::kNoProfilingInlined : CallKnownApiFunction::kNoProfiling) : CallKnownApiFunction::kGeneric; ValueNode* tagged_receiver; GET_VALUE_OR_ABORT(tagged_receiver, GetTaggedValue(receiver)); return AddNewNode<CallKnownApiFunction>( input_count, [&](CallKnownApiFunction* call) { for (int i = 0; i < static_cast<int>(args.count()); i++) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call->set_arg(i, tagged_arg); } return ReduceResult::Done(); }, mode, api_callback, tagged_receiver); } MaybeReduceResult MaglevGraphBuilder::TryBuildCallKnownApiFunction( compiler::JSFunctionRef function, compiler::SharedFunctionInfoRef shared, CallArguments& args) { compiler::OptionalFunctionTemplateInfoRef maybe_function_template_info = shared.function_template_info(broker()); if (!maybe_function_template_info.has_value()) { // Not an Api function. return {}; } if (IsTheHoleConstant(args.receiver())) { // Not supported by CallFunctionTemplate. return {}; } // See if we can optimize this API call. compiler::FunctionTemplateInfoRef function_template_info = maybe_function_template_info.value(); compiler::HolderLookupResult api_holder; if (function_template_info.accept_any_receiver() && function_template_info.is_signature_undefined(broker())) { // We might be able to optimize the API call depending on the // {function_template_info}. // If the API function accepts any kind of {receiver}, we only need to // ensure that the {receiver} is actually a JSReceiver at this point, // and also pass that as the {holder}. There are two independent bits // here: // // a. When the "accept any receiver" bit is set, it means we don't // need to perform access checks, even if the {receiver}'s map // has the "needs access check" bit set. // b. When the {function_template_info} has no signature, we don't // need to do the compatible receiver check, since all receivers // are considered compatible at that point, and the {receiver} // will be pass as the {holder}. api_holder = compiler::HolderLookupResult{CallOptimization::kHolderIsReceiver}; } else if (args.receiver()) { // Try to infer API holder from the known aspects of the {receiver}. api_holder = TryInferApiHolderValue(function_template_info, args.receiver()); } switch (api_holder.lookup) { case CallOptimization::kHolderIsReceiver: case CallOptimization::kHolderFound: return TryReduceCallForApiFunction(function_template_info, shared, args); case CallOptimization::kHolderNotFound: break; } // We don't have enough information to eliminate the access check // and/or the compatible receiver check, so use the generic builtin // that does those checks dynamically. This is still significantly // faster than the generic call sequence. Builtin builtin_name; // TODO(ishell): create no-profiling versions of kCallFunctionTemplate // builtins and use them here based on DependOnNoProfilingProtector() // dependency state. if (function_template_info.accept_any_receiver()) { DCHECK(!function_template_info.is_signature_undefined(broker())); builtin_name = Builtin::kCallFunctionTemplate_CheckCompatibleReceiver; } else if (function_template_info.is_signature_undefined(broker())) { builtin_name = Builtin::kCallFunctionTemplate_CheckAccess; } else { builtin_name = Builtin::kCallFunctionTemplate_CheckAccessAndCompatibleReceiver; } // The CallFunctionTemplate builtin requires the {receiver} to be // an actual JSReceiver, so make sure we do the proper conversion // first if necessary. ValueNode* receiver; GET_VALUE_OR_ABORT(receiver, GetConvertReceiver(shared, args)); int kContext = 1; int kFunctionTemplateInfo = 1; int kArgc = 1; ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(GetContext())); return AddNewNode<CallBuiltin>( kFunctionTemplateInfo + kArgc + kContext + args.count_with_receiver(), [&](CallBuiltin* call_builtin) { int arg_index = 0; call_builtin->set_arg(arg_index++, GetConstant(function_template_info)); call_builtin->set_arg( arg_index++, GetInt32Constant(JSParameterCount(static_cast<int>(args.count())))); ValueNode* tagged_receiver; GET_VALUE_OR_ABORT(tagged_receiver, GetTaggedValue(receiver)); call_builtin->set_arg(arg_index++, tagged_receiver); for (int i = 0; i < static_cast<int>(args.count()); i++) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call_builtin->set_arg(arg_index++, tagged_arg); } return ReduceResult::Done(); }, builtin_name, tagged_context); } bool MaglevGraphBuilder::IsTheHoleConstant(ValueNode* node) { if (node != nullptr) { if (compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(node)) { return maybe_constant->IsTheHole(); } } return false; } MaybeReduceResult MaglevGraphBuilder::TryBuildCallKnownJSFunction( compiler::JSFunctionRef function, ValueNode* new_target, CallArguments& args, const compiler::FeedbackSource& feedback_source) { // Don't inline CallFunction stub across native contexts. if (function.native_context(broker()) != broker()->target_native_context()) { return {}; } compiler::SharedFunctionInfoRef shared = function.shared(broker()); RETURN_IF_DONE(TryBuildCallKnownApiFunction(function, shared, args)); ValueNode* closure = GetConstant(function); compiler::ContextRef context = function.context(broker()); ValueNode* context_node = GetConstant(context); if (V8_UNLIKELY(MaglevIsTopTier()) && TargetIsCurrentCompilingUnit(function) && !graph_->is_osr()) { DCHECK(!shared.HasBuiltinId()); RETURN_IF_DONE( BuildCallSelf(context_node, closure, new_target, shared, args)); } return TryBuildCallKnownJSFunction( context_node, closure, new_target, function.dispatch_handle(), shared, function.raw_feedback_cell(broker()), args, feedback_source); } ReduceResult MaglevGraphBuilder::BuildCallKnownJSFunction( ValueNode* context, ValueNode* function, ValueNode* new_target, JSDispatchHandle dispatch_handle, compiler::SharedFunctionInfoRef shared, compiler::FeedbackCellRef feedback_cell, CallArguments& args, const compiler::FeedbackSource& feedback_source) { ValueNode* receiver = args.receiver(); if (!IsTheHoleConstant(receiver)) { // The receiver may be the_hole when inlining derived constructors and // construct_as_builtin constructors. Only insert conversions when that is // not the case. GET_VALUE_OR_ABORT(receiver, GetConvertReceiver(shared, args)); } size_t input_count = args.count() + CallKnownJSFunction::kFixedInputCount; ValueNode* tagged_function; GET_VALUE_OR_ABORT(tagged_function, GetTaggedValue(function)); ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(context)); ValueNode* tagged_receiver; GET_VALUE_OR_ABORT(tagged_receiver, GetTaggedValue(receiver)); ValueNode* tagged_new_target; GET_VALUE(tagged_new_target, GetTaggedValue(new_target)); return AddNewNode<CallKnownJSFunction>( input_count, [&](CallKnownJSFunction* call) { for (int i = 0; i < static_cast<int>(args.count()); i++) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call->set_arg(i, tagged_arg); } return ReduceResult::Done(); }, dispatch_handle, shared, tagged_function, tagged_context, tagged_receiver, tagged_new_target, feedback_source); } ReduceResult MaglevGraphBuilder::BuildCallKnownJSFunction( ValueNode* context, ValueNode* function, ValueNode* new_target, JSDispatchHandle dispatch_handle, compiler::SharedFunctionInfoRef shared, base::Vector<ValueNode*> arguments) { DCHECK_GT(arguments.size(), 0); constexpr int kSkipReceiver = 1; int argcount_without_receiver = static_cast<int>(arguments.size()) - kSkipReceiver; size_t input_count = argcount_without_receiver + CallKnownJSFunction::kFixedInputCount; ValueNode* tagged_function; GET_VALUE_OR_ABORT(tagged_function, GetTaggedValue(function)); ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(context)); ValueNode* tagged_receiver; GET_VALUE_OR_ABORT(tagged_receiver, GetTaggedValue(arguments[0])); ValueNode* tagged_new_target; GET_VALUE(tagged_new_target, GetTaggedValue(new_target)); return AddNewNode<CallKnownJSFunction>( input_count, [&](CallKnownJSFunction* call) { for (int i = 0; i < argcount_without_receiver; i++) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(arguments[i + kSkipReceiver])); call->set_arg(i, tagged_arg); } return ReduceResult::Done(); }, dispatch_handle, shared, tagged_function, tagged_context, tagged_receiver, tagged_new_target, compiler::FeedbackSource{}); } MaybeReduceResult MaglevGraphBuilder::TryBuildCallKnownJSFunction( ValueNode* context, ValueNode* function, ValueNode* new_target, JSDispatchHandle dispatch_handle, compiler::SharedFunctionInfoRef shared, compiler::FeedbackCellRef feedback_cell, CallArguments& args, const compiler::FeedbackSource& feedback_source) { // Truncate args when they are unreachable. if (args.mode() == CallArguments::kDefault && shared.object()->CanOnlyAccessFixedFormalParameters()) { auto parameter_count = local_isolate_->js_dispatch_table().GetParameterCount(dispatch_handle); if (args.count() > parameter_count - kJSArgcReceiverSlots) { args.ResizeDefaultArguments(parameter_count - kJSArgcReceiverSlots); } } if (v8_flags.maglev_inlining) { RETURN_IF_DONE(TryBuildInlineCall(context, function, new_target, dispatch_handle, shared, feedback_cell, args, feedback_source)); } return BuildCallKnownJSFunction(context, function, new_target, dispatch_handle, shared, feedback_cell, args, feedback_source); } ReduceResult MaglevGraphBuilder::BuildCheckValueByReference( ValueNode* node, compiler::HeapObjectRef ref, DeoptimizeReason reason) { DCHECK(!ref.IsSmi()); DCHECK(!ref.IsHeapNumber()); if (!IsInstanceOfNodeType(ref.map(broker()), GetType(node), broker())) { return EmitUnconditionalDeopt(reason); } if (compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(node)) { if (maybe_constant.value().equals(ref)) { return ReduceResult::Done(); } return EmitUnconditionalDeopt(reason); } RETURN_IF_ABORT(AddNewNode<CheckValue>({node}, ref, reason)); SetKnownValue(node, ref, StaticTypeForConstant(broker(), ref)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildCheckNumericalValueOrByReference( ValueNode* node, compiler::ObjectRef ref, DeoptimizeReason reason) { if (ref.IsHeapObject() && !ref.IsHeapNumber()) { return BuildCheckValueByReference(node, ref.AsHeapObject(), reason); } return BuildCheckNumericalValue(node, ref, reason); } ReduceResult MaglevGraphBuilder::BuildCheckInternalizedStringValueOrByReference( ValueNode* node, compiler::HeapObjectRef ref, DeoptimizeReason reason) { if (!TryGetConstant<HeapObject>(node) && ref.IsInternalizedString()) { if (!IsInstanceOfNodeType(ref.map(broker()), GetType(node), broker())) { return EmitUnconditionalDeopt(reason); } RETURN_IF_ABORT(AddNewNode<CheckValueEqualsString>( {node}, ref.AsInternalizedString(), reason)); SetKnownValue(node, ref, NodeType::kString); return ReduceResult::Done(); } return BuildCheckValueByReference(node, ref, reason); } ReduceResult MaglevGraphBuilder::BuildCheckNumericalValue( ValueNode* node, compiler::ObjectRef ref, DeoptimizeReason reason) { DCHECK(ref.IsSmi() || ref.IsHeapNumber()); if (ref.IsSmi()) { int ref_value = ref.AsSmi(); if (std::optional<int32_t> cst = TryGetInt32Constant(node)) { if (cst.value() == ref_value) { return ReduceResult::Done(); } else { return EmitUnconditionalDeopt(reason); } } if (TryGetFloat64OrHoleyFloat64Constant( UseRepresentation::kFloat64, node, TaggedToFloat64ConversionType::kOnlyNumber)) { // This a non-smi float64 constant ==> deopting. return EmitUnconditionalDeopt(reason); } if (NodeTypeIs(GetType(node), NodeType::kAnyHeapObject)) { return EmitUnconditionalDeopt(reason); } RETURN_IF_ABORT( AddNewNode<CheckValueEqualsInt32>({node}, ref_value, reason)); } else { DCHECK(ref.IsHeapNumber()); Float64 ref_value = Float64::FromBits(ref.AsHeapNumber().value_as_bits()); if (std::optional<Float64> cst = TryGetFloat64OrHoleyFloat64Constant( UseRepresentation::kFloat64, node, TaggedToFloat64ConversionType::kOnlyNumber)) { if (cst->is_nan() && ref_value.is_nan()) { return ReduceResult::Done(); } // For non-NaN values, we need to make sure to distinguish 0 and -0. if (cst->get_scalar() == ref_value.get_scalar() && std::signbit(cst->get_scalar()) == std::signbit(ref_value.get_scalar())) { return ReduceResult::Done(); } return EmitUnconditionalDeopt(reason); } if (!NodeTypeIs(NodeType::kNumber, GetType(node))) { return EmitUnconditionalDeopt(reason); } RETURN_IF_ABORT( AddNewNode<CheckFloat64SameValue>({node}, ref_value, reason)); } SetKnownValue(node, ref, NodeType::kNumber); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildConvertHoleToUndefined(ValueNode* node) { if (!node->is_tagged()) return node; compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(node); if (maybe_constant) { return maybe_constant.value().IsTheHole() ? GetRootConstant(RootIndex::kUndefinedValue) : node; } return AddNewNode<ConvertHoleToUndefined>({node}); } ReduceResult MaglevGraphBuilder::BuildCheckNotHole(ValueNode* node) { if (!node->is_tagged()) return ReduceResult::Done(); compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(node); if (maybe_constant) { if (maybe_constant.value().IsTheHole()) { return EmitUnconditionalDeopt(DeoptimizeReason::kHole); } return ReduceResult::Done(); } return AddNewNode<CheckNotHole>({node}); } MaybeReduceResult MaglevGraphBuilder::TryReduceCallForConstant( compiler::JSFunctionRef target, CallArguments& args, const compiler::FeedbackSource& feedback_source) { if (args.mode() != CallArguments::kDefault) { // TODO(victorgomes): Maybe inline the spread stub? Or call known function // directly if arguments list is an array. return {}; } compiler::SharedFunctionInfoRef shared = target.shared(broker()); ValueNode* target_node = GetConstant(target); // Do not reduce calls to functions with break points. if (!shared.HasBreakInfo(broker())) { if (IsClassConstructor(shared.kind())) { // If we have a class constructor, we should raise an exception. return BuildThrow(Throw::kThrowConstructorNonCallableError, target_node); } DCHECK(IsCallable(*target.object())); RETURN_IF_DONE(TryReduceBuiltin(target, shared, args, feedback_source)); RETURN_IF_DONE(TryBuildCallKnownJSFunction( target, GetRootConstant(RootIndex::kUndefinedValue), args, feedback_source)); } return BuildGenericCall(target_node, Call::TargetType::kJSFunction, args); } compiler::HolderLookupResult MaglevGraphBuilder::TryInferApiHolderValue( compiler::FunctionTemplateInfoRef function_template_info, ValueNode* receiver) { const compiler::HolderLookupResult not_found; auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); if (!possible_maps) { // No info about receiver, can't infer API holder. return not_found; } DCHECK(!possible_maps->is_empty()); compiler::MapRef first_receiver_map = possible_maps->at(0); // See if we can constant-fold the compatible receiver checks. compiler::HolderLookupResult api_holder = function_template_info.LookupHolderOfExpectedType(broker(), first_receiver_map); if (api_holder.lookup == CallOptimization::kHolderNotFound) { // Can't infer API holder. return not_found; } // Check that all {receiver_maps} are actually JSReceiver maps and // that the {function_template_info} accepts them without access // checks (even if "access check needed" is set for {receiver}). // // API holder might be a receivers's hidden prototype (i.e. the receiver is // a global proxy), so in this case the map check or stability dependency on // the receiver guard us from detaching a global object from global proxy. CHECK(first_receiver_map.IsJSReceiverMap()); CHECK(!first_receiver_map.is_access_check_needed() || function_template_info.accept_any_receiver()); for (compiler::MapRef receiver_map : *possible_maps) { compiler::HolderLookupResult holder_i = function_template_info.LookupHolderOfExpectedType(broker(), receiver_map); if (api_holder.lookup != holder_i.lookup) { // Different API holders, dynamic lookup is required. return not_found; } DCHECK(holder_i.lookup == CallOptimization::kHolderFound || holder_i.lookup == CallOptimization::kHolderIsReceiver); if (holder_i.lookup == CallOptimization::kHolderFound) { DCHECK(api_holder.holder.has_value() && holder_i.holder.has_value()); if (!api_holder.holder->equals(*holder_i.holder)) { // Different API holders, dynamic lookup is required. return not_found; } } CHECK(receiver_map.IsJSReceiverMap()); CHECK(!receiver_map.is_access_check_needed() || function_template_info.accept_any_receiver()); } return api_holder; } MaybeReduceResult MaglevGraphBuilder::TryReduceCallForTarget( ValueNode* target_node, compiler::JSFunctionRef target, CallArguments& args, const compiler::FeedbackSource& feedback_source) { RETURN_IF_ABORT(BuildCheckValueByReference( target_node, target, DeoptimizeReason::kWrongCallTarget)); return TryReduceCallForConstant(target, args, feedback_source); } MaybeReduceResult MaglevGraphBuilder::TryReduceCallForNewClosure( ValueNode* target_node, ValueNode* target_context, JSDispatchHandle dispatch_handle, compiler::SharedFunctionInfoRef shared, compiler::FeedbackCellRef feedback_cell, CallArguments& args, const compiler::FeedbackSource& feedback_source) { // Do not reduce calls to functions with break points. if (args.mode() != CallArguments::kDefault) { // TODO(victorgomes): Maybe inline the spread stub? Or call known function // directly if arguments list is an array. return {}; } if (!shared.HasBreakInfo(broker())) { if (IsClassConstructor(shared.kind())) { // If we have a class constructor, we should raise an exception. return BuildThrow(Throw::kThrowConstructorNonCallableError, target_node); } RETURN_IF_DONE(TryBuildCallKnownJSFunction( target_context, target_node, GetRootConstant(RootIndex::kUndefinedValue), dispatch_handle, shared, feedback_cell, args, feedback_source)); } return BuildGenericCall(target_node, Call::TargetType::kJSFunction, args); } MaybeReduceResult MaglevGraphBuilder::TryReduceFunctionPrototypeApplyCallWithReceiver( compiler::OptionalHeapObjectRef maybe_receiver, CallArguments& args, const compiler::FeedbackSource& feedback_source) { if (args.mode() != CallArguments::kDefault) return {}; ValueNode* function = GetValueOrUndefined(args.receiver()); if (maybe_receiver.has_value()) { RETURN_IF_ABORT(BuildCheckValueByReference( function, maybe_receiver.value(), DeoptimizeReason::kWrongCallTarget)); function = GetConstant(maybe_receiver.value()); } SaveCallSpeculationScope saved(this); if (args.count() == 0) { CallArguments empty_args(ConvertReceiverMode::kNullOrUndefined); return ReduceCall(function, empty_args, feedback_source); } auto build_call_only_with_new_receiver = [&] { CallArguments new_args(ConvertReceiverMode::kAny, {args[0]}); return ReduceCall(function, new_args, feedback_source); }; if (args.count() == 1 || args[1]->IsNullValue() || args[1]->IsUndefinedValue()) { return build_call_only_with_new_receiver(); } auto build_call_with_array_like = [&] { CallArguments new_args(ConvertReceiverMode::kAny, {args[0], args[1]}, CallArguments::kWithArrayLike); return ReduceCallWithArrayLike(function, new_args, feedback_source); }; if (!known_node_aspects().MayBeNullOrUndefined(broker(), args[1])) { return build_call_with_array_like(); } return Select( [&](BranchBuilder& builder) { return BuildBranchIfUndefinedOrNull(builder, args[1]); }, build_call_only_with_new_receiver, build_call_with_array_like); } ReduceResult MaglevGraphBuilder::BuildCallWithFeedback( ValueNode* target_node, CallArguments& args, const compiler::FeedbackSource& feedback_source) { const compiler::ProcessedFeedback& processed_feedback = broker()->GetFeedbackForCall(feedback_source); if (processed_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForCall); } DCHECK_EQ(processed_feedback.kind(), compiler::ProcessedFeedback::kCall); const compiler::CallFeedback& call_feedback = processed_feedback.AsCall(); if (call_feedback.target().has_value()) { if (call_feedback.target()->IsJSFunction()) { CallFeedbackContent content = call_feedback.call_feedback_content(); compiler::JSFunctionRef feedback_target = call_feedback.target()->AsJSFunction(); if (content == CallFeedbackContent::kReceiver) { compiler::NativeContextRef native_context = broker()->target_native_context(); compiler::JSFunctionRef apply_function = native_context.function_prototype_apply(broker()); RETURN_IF_ABORT(BuildCheckValueByReference( target_node, apply_function, DeoptimizeReason::kWrongCallTarget)); PROCESS_AND_RETURN_IF_DONE( TryReduceFunctionPrototypeApplyCallWithReceiver( feedback_target, args, feedback_source), SetAccumulator); feedback_target = apply_function; } else { DCHECK_EQ(CallFeedbackContent::kTarget, content); } RETURN_IF_ABORT(BuildCheckValueByReference( target_node, feedback_target, DeoptimizeReason::kWrongCallTarget)); } else if (call_feedback.target()->IsFeedbackCell() && args.mode() == CallArguments::kDefault) { compiler::FeedbackCellRef feedback_cell = call_feedback.target()->AsFeedbackCell(); compiler::OptionalSharedFunctionInfoRef shared = feedback_cell.shared_function_info(broker()); if (shared.has_value() && !shared->HasBreakInfo(broker())) { RETURN_IF_ABORT(BuildCheckJSFunction(target_node)); ValueNode* target_feedback_cell; GET_VALUE_OR_ABORT(target_feedback_cell, BuildLoadJSFunctionFeedbackCell(target_node)); RETURN_IF_ABORT( BuildCheckValueByReference(target_feedback_cell, feedback_cell, DeoptimizeReason::kWrongFeedbackCell)); if (IsClassConstructor(shared->kind())) { // If we have a class constructor, we should raise an exception. return BuildThrow(Throw::kThrowConstructorNonCallableError, target_node); } ValueNode* context; GET_VALUE_OR_ABORT(context, BuildLoadJSFunctionContext(target_node)); compiler::ScopeInfoRef scope_info = shared->scope_info(broker()); if (scope_info.HasOuterScopeInfo()) { scope_info = scope_info.OuterScopeInfo(broker()); CHECK(scope_info.HasContext()); graph()->record_scope_info(context, scope_info); } PROCESS_AND_RETURN_IF_DONE( TryBuildCallKnownJSFunction( context, target_node, GetRootConstant(RootIndex::kUndefinedValue), feedback_cell.dispatch_handle(), shared.value(), feedback_cell, args, feedback_source), SetAccumulator); UNREACHABLE(); } } } PROCESS_AND_RETURN_IF_DONE(ReduceCall(target_node, args, feedback_source), SetAccumulator); UNREACHABLE(); } template <typename CallNode, typename... Args> ReduceResult MaglevGraphBuilder::BuildCallForwardArgumentsElements( ValueNode* target_node, CallArguments& args, ArgumentsElements* elements, Args&&... extra_arg) { Call::TargetType target_type = Call::TargetType::kAny; if (compiler::OptionalJSFunctionRef maybe_constant = TryGetConstant<JSFunction>(target_node)) { compiler::SharedFunctionInfoRef shared = maybe_constant->shared(broker()); if (!IsClassConstructor(shared.kind())) { target_type = Call::TargetType::kJSFunction; } } int start_index = 0; if (elements->create_arguments_type() == CreateArgumentsType::kRestParameter) { start_index = elements->formal_parameter_count(); } ValueNode* tagged_target; GET_VALUE_OR_ABORT(tagged_target, GetTaggedValue(target_node)); ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(GetContext())); return AddNewCallNode<CallNode>(args, tagged_target, std::forward<Args>(extra_arg)..., tagged_context, start_index, target_type); } ReduceResult MaglevGraphBuilder::ReduceCallWithArrayLikeForArgumentsObject( ValueNode* target_node, CallArguments& args, VirtualObject* arguments_object, const compiler::FeedbackSource& feedback_source) { DCHECK_EQ(args.mode(), CallArguments::kWithArrayLike); DCHECK(arguments_object->map()->IsJSArgumentsObjectMap() || arguments_object->map()->IsJSArrayMap()); args.PopArrayLikeArgument(); ValueNode* elements_value = arguments_object->get(JSArgumentsObject::kElementsOffset); if (ArgumentsElements* arguments_elements = elements_value->TryCast<ArgumentsElements>()) { return BuildCallForwardArgumentsElements<CallForwardVarargs>( target_node, args, arguments_elements); } if (elements_value->Is<RootConstant>()) { // It is a RootConstant, Elements can only be the empty fixed array. DCHECK_EQ(elements_value->Cast<RootConstant>()->index(), RootIndex::kEmptyFixedArray); CallArguments new_args(ConvertReceiverMode::kAny, {args.receiver()}); return ReduceCall(target_node, new_args, feedback_source); } if (Constant* constant_value = elements_value->TryCast<Constant>()) { DCHECK(constant_value->object().IsFixedArray()); compiler::FixedArrayRef elements = constant_value->object().AsFixedArray(); base::SmallVector<ValueNode*, 8> arg_list; DCHECK_NOT_NULL(args.receiver()); arg_list.push_back(args.receiver()); for (int i = 0; i < static_cast<int>(args.count()); i++) { arg_list.push_back(args[i]); } for (uint32_t i = 0; i < elements.length(); i++) { arg_list.push_back(GetConstant(*elements.TryGet(broker(), i))); } CallArguments new_args(ConvertReceiverMode::kAny, std::move(arg_list)); return ReduceCall(target_node, new_args, feedback_source); } DCHECK(elements_value->Is<InlinedAllocation>()); InlinedAllocation* allocation = elements_value->Cast<InlinedAllocation>(); VirtualObject* elements = allocation->object(); base::SmallVector<ValueNode*, 8> arg_list; DCHECK_NOT_NULL(args.receiver()); arg_list.push_back(args.receiver()); for (int i = 0; i < static_cast<int>(args.count()); i++) { arg_list.push_back(args[i]); } DCHECK(elements->get(offsetof(FixedArray, length_))->Is<Int32Constant>()); int length = elements->get(offsetof(FixedArray, length_)) ->Cast<Int32Constant>() ->value(); for (int i = 0; i < length; i++) { arg_list.push_back(elements->get(FixedArray::OffsetOfElementAt(i))); } CallArguments new_args(ConvertReceiverMode::kAny, std::move(arg_list)); return ReduceCall(target_node, new_args, feedback_source); } MaybeReduceResult MaglevGraphBuilder::TryReduceConstructWithSpreadForArgumentsObject( ValueNode* target, ValueNode* new_target, CallArguments& args, VirtualObject* arguments_object, const compiler::FeedbackSource& feedback_source) { DCHECK_EQ(args.mode(), CallArguments::kWithSpread); DCHECK(arguments_object->map()->IsJSArgumentsObjectMap() || arguments_object->map()->IsJSArrayMap()); ValueNode* elements_value = arguments_object->get(JSArgumentsObject::kElementsOffset); if (ArgumentsElements* arguments_elements = elements_value->TryCast<ArgumentsElements>()) { // For call/construct with spread, we need to also install a code // dependency on the array iterator lookup protector cell to ensure // that no one messed with the %ArrayIteratorPrototype%.next method. if (!broker()->dependencies()->DependOnArrayIteratorProtector()) return {}; // Remove spread. args.PopSpread(); ValueNode* tagged_new_target; GET_VALUE_OR_ABORT(tagged_new_target, GetTaggedValue(new_target)); return BuildCallForwardArgumentsElements<ConstructForwardVarargs>( target, args, arguments_elements, tagged_new_target); } return {}; } namespace { bool IsSloppyMappedArgumentsObject(compiler::JSHeapBroker* broker, compiler::MapRef map) { return broker->target_native_context() .fast_aliased_arguments_map(broker) .equals(map); } } // namespace std::optional<VirtualObject*> MaglevGraphBuilder::TryGetNonEscapingArgumentsObject(ValueNode* value) { if (!value->Is<InlinedAllocation>()) return {}; InlinedAllocation* alloc = value->Cast<InlinedAllocation>(); // Although the arguments object has not been changed so far, since it is not // escaping, it could be modified after this bytecode if it is inside a loop. if (IsInsideLoop()) { if (!is_loop_effect_tracking() || !loop_effects_->allocations.contains(alloc)) { return {}; } } // TODO(victorgomes): We can probably loosen the IsNotEscaping requirement if // we keep track of the arguments object changes so far. if (alloc->IsEscaping()) return {}; VirtualObject* object = alloc->object(); if (!object->has_static_map()) return {}; // TODO(victorgomes): Support simple JSArray forwarding. compiler::MapRef map = *object->map(); // It is a rest parameter, if it is an array with ArgumentsElements node as // the elements array. if (map.IsJSArrayMap() && object->get(JSArgumentsObject::kElementsOffset) ->Is<ArgumentsElements>()) { return object; } // TODO(victorgomes): We can loosen the IsSloppyMappedArgumentsObject // requirement if there is no stores to the mapped arguments. if (map.IsJSArgumentsObjectMap() && !IsSloppyMappedArgumentsObject(broker(), map)) { return object; } return {}; } ReduceResult MaglevGraphBuilder::ReduceCallWithArrayLike( ValueNode* target_node, CallArguments& args, const compiler::FeedbackSource& feedback_source) { DCHECK_EQ(args.mode(), CallArguments::kWithArrayLike); // TODO(victorgomes): Add the case for JSArrays and Rest parameter. if (std::optional<VirtualObject*> arguments_object = TryGetNonEscapingArgumentsObject(args.array_like_argument())) { RETURN_IF_DONE(ReduceCallWithArrayLikeForArgumentsObject( target_node, args, *arguments_object, feedback_source)); } // On fallthrough, create a generic call. return BuildGenericCall(target_node, Call::TargetType::kAny, args); } ReduceResult MaglevGraphBuilder::ReduceCall( ValueNode* target_node, CallArguments& args, const compiler::FeedbackSource& feedback_source) { if (compiler::OptionalJSFunctionRef maybe_constant = TryGetConstant<JSFunction>(target_node)) { MaybeReduceResult result = TryReduceCallForTarget( target_node, maybe_constant.value(), args, feedback_source); RETURN_IF_DONE(result); } // If the implementation here becomes more complex, we could probably // deduplicate the code for FastCreateClosure and CreateClosure by using // templates or giving them a shared base class. if (FastCreateClosure* fast_create_closure = target_node->TryCast<FastCreateClosure>()) { MaybeReduceResult result = TryReduceCallForNewClosure( fast_create_closure, fast_create_closure->ContextInput().node(), fast_create_closure->feedback_cell().dispatch_handle(), fast_create_closure->shared_function_info(), fast_create_closure->feedback_cell(), args, feedback_source); RETURN_IF_DONE(result); } else if (CreateClosure* create_closure = target_node->TryCast<CreateClosure>()) { MaybeReduceResult result = TryReduceCallForNewClosure( create_closure, create_closure->ContextInput().node(), create_closure->feedback_cell().dispatch_handle(), create_closure->shared_function_info(), create_closure->feedback_cell(), args, feedback_source); RETURN_IF_DONE(result); } // On fallthrough, create a generic call. return BuildGenericCall(target_node, Call::TargetType::kAny, args); } ReduceResult MaglevGraphBuilder::BuildCallFromRegisterList( ConvertReceiverMode receiver_mode) { ValueNode* target = LoadRegister(0); interpreter::RegisterList reg_list = iterator_.GetRegisterListOperand(1); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source(feedback(), slot); CallArguments args(receiver_mode, reg_list, current_interpreter_frame_); return BuildCallWithFeedback(target, args, feedback_source); } ReduceResult MaglevGraphBuilder::BuildCallFromRegisters( int arg_count, ConvertReceiverMode receiver_mode) { ValueNode* target = LoadRegister(0); const int receiver_count = (receiver_mode == ConvertReceiverMode::kNullOrUndefined) ? 0 : 1; const int reg_count = arg_count + receiver_count; FeedbackSlot slot = GetSlotOperand(reg_count + 1); compiler::FeedbackSource feedback_source(feedback(), slot); switch (reg_count) { case 0: { DCHECK_EQ(receiver_mode, ConvertReceiverMode::kNullOrUndefined); CallArguments args(receiver_mode); return BuildCallWithFeedback(target, args, feedback_source); } case 1: { CallArguments args(receiver_mode, {LoadRegister(1)}); return BuildCallWithFeedback(target, args, feedback_source); } case 2: { CallArguments args(receiver_mode, {LoadRegister(1), LoadRegister(2)}); return BuildCallWithFeedback(target, args, feedback_source); } case 3: { CallArguments args(receiver_mode, {LoadRegister(1), LoadRegister(2), LoadRegister(3)}); return BuildCallWithFeedback(target, args, feedback_source); } default: UNREACHABLE(); } } ReduceResult MaglevGraphBuilder::VisitCallAnyReceiver() { return BuildCallFromRegisterList(ConvertReceiverMode::kAny); } ReduceResult MaglevGraphBuilder::VisitCallProperty() { return BuildCallFromRegisterList(ConvertReceiverMode::kNotNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallProperty0() { return BuildCallFromRegisters(0, ConvertReceiverMode::kNotNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallProperty1() { return BuildCallFromRegisters(1, ConvertReceiverMode::kNotNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallProperty2() { return BuildCallFromRegisters(2, ConvertReceiverMode::kNotNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallUndefinedReceiver() { return BuildCallFromRegisterList(ConvertReceiverMode::kNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallUndefinedReceiver0() { return BuildCallFromRegisters(0, ConvertReceiverMode::kNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallUndefinedReceiver1() { return BuildCallFromRegisters(1, ConvertReceiverMode::kNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallUndefinedReceiver2() { return BuildCallFromRegisters(2, ConvertReceiverMode::kNullOrUndefined); } ReduceResult MaglevGraphBuilder::VisitCallWithSpread() { ValueNode* function = LoadRegister(0); interpreter::RegisterList reglist = iterator_.GetRegisterListOperand(1); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source(feedback(), slot); CallArguments args(ConvertReceiverMode::kAny, reglist, current_interpreter_frame_, CallArguments::kWithSpread); return BuildCallWithFeedback(function, args, feedback_source); } ReduceResult MaglevGraphBuilder::VisitCallRuntime() { Runtime::FunctionId function_id = iterator_.GetRuntimeIdOperand(0); interpreter::RegisterList args = iterator_.GetRegisterListOperand(1); switch (function_id) { case Runtime::kMajorGCForCompilerTesting: RETURN_IF_ABORT(AddNewNode<MajorGCForCompilerTesting>({})); SetAccumulator(GetRootConstant(RootIndex::kUndefinedValue)); return ReduceResult::Done(); case Runtime::kTurbofanStaticAssert: if (!is_turbolev()) break; RETURN_IF_ABORT(AddNewNode<TurbofanStaticAssert>( {current_interpreter_frame_.get(args[0])})); SetAccumulator(GetRootConstant(RootIndex::kUndefinedValue)); return ReduceResult::Done(); default: break; } ValueNode* context = GetContext(); size_t input_count = args.register_count() + CallRuntime::kFixedInputCount; CallRuntime* call_runtime; GET_VALUE_OR_ABORT(call_runtime, AddNewNode<CallRuntime>( input_count, [&](CallRuntime* call_runtime) { for (int i = 0; i < args.register_count(); ++i) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call_runtime->set_arg(i, tagged_arg); } return ReduceResult::Done(); }, function_id, context)); SetAccumulator(call_runtime); if (RuntimeFunctionWillThrow(function_id)) { return BuildAbort(AbortReason::kUnexpectedReturnFromThrow); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCallJSRuntime() { // Get the function to call from the native context. compiler::NativeContextRef native_context = broker()->target_native_context(); ValueNode* context = GetConstant(native_context); uint32_t slot = iterator_.GetNativeContextIndexOperand(0); ValueNode* callee = LoadAndCacheContextSlot(context, slot, kMutable, ContextMode::kNoContextCells); // Call the function. interpreter::RegisterList reglist = iterator_.GetRegisterListOperand(1); CallArguments args(ConvertReceiverMode::kNullOrUndefined, reglist, current_interpreter_frame_); return SetAccumulator( BuildGenericCall(callee, Call::TargetType::kJSFunction, args)); } ReduceResult MaglevGraphBuilder::VisitCallRuntimeForPair() { Runtime::FunctionId function_id = iterator_.GetRuntimeIdOperand(0); interpreter::RegisterList args = iterator_.GetRegisterListOperand(1); ValueNode* context = GetContext(); size_t input_count = args.register_count() + CallRuntime::kFixedInputCount; CallRuntime* call_runtime; GET_VALUE_OR_ABORT(call_runtime, AddNewNode<CallRuntime>( input_count, [&](CallRuntime* call_runtime) { for (int i = 0; i < args.register_count(); ++i) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call_runtime->set_arg(i, tagged_arg); } return ReduceResult::Done(); }, function_id, context)); auto result = iterator_.GetRegisterPairOperand(3); StoreRegisterPair(result, call_runtime); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitInvokeIntrinsic() { // InvokeIntrinsic <function_id> <first_arg> <arg_count> Runtime::FunctionId intrinsic_id = iterator_.GetIntrinsicIdOperand(0); interpreter::RegisterList args = iterator_.GetRegisterListOperand(1); switch (intrinsic_id) { #define CASE(Name, _, arg_count) \ case Runtime::kInline##Name: \ DCHECK_IMPLIES(arg_count != -1, arg_count == args.register_count()); \ return VisitIntrinsic##Name(args); INTRINSICS_LIST(CASE) #undef CASE default: UNREACHABLE(); } } ReduceResult MaglevGraphBuilder::VisitIntrinsicCopyDataProperties( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* tagged_arg_0; GET_VALUE_OR_ABORT(tagged_arg_0, GetTaggedValue(args[0])); ValueNode* tagged_arg_1; GET_VALUE_OR_ABORT(tagged_arg_1, GetTaggedValue(args[1])); SetAccumulator(BuildCallBuiltin<Builtin::kCopyDataProperties>( {tagged_arg_0, tagged_arg_1})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder:: VisitIntrinsicCopyDataPropertiesWithExcludedPropertiesOnStack( interpreter::RegisterList args) { SmiConstant* excluded_property_count = GetSmiConstant(args.register_count() - 1); int kContext = 1; int kExcludedPropertyCount = 1; CallBuiltin* call_builtin; ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(GetContext())); GET_VALUE_OR_ABORT( call_builtin, AddNewNode<CallBuiltin>( args.register_count() + kContext + kExcludedPropertyCount, [&](CallBuiltin* call_builtin) { int arg_index = 0; ValueNode* tagged_receiver; GET_VALUE_OR_ABORT(tagged_receiver, GetTaggedValue(args[0])); call_builtin->set_arg(arg_index++, tagged_receiver); call_builtin->set_arg(arg_index++, excluded_property_count); for (int i = 1; i < args.register_count(); i++) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[i])); call_builtin->set_arg(arg_index++, tagged_arg); } return ReduceResult::Done(); }, Builtin::kCopyDataPropertiesWithExcludedProperties, tagged_context)); SetAccumulator(call_builtin); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicCreateIterResultObject( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* value = current_interpreter_frame_.get(args[0]); ValueNode* done = current_interpreter_frame_.get(args[1]); compiler::MapRef map = broker()->target_native_context().iterator_result_map(broker()); VirtualObject* iter_result = CreateJSIteratorResult(map, value, done); return SetAccumulator( BuildInlinedAllocation(iter_result, AllocationType::kYoung)); } ReduceResult MaglevGraphBuilder::VisitIntrinsicCreateAsyncFromSyncIterator( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 1); ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(args[0])); SetAccumulator( BuildCallBuiltin<Builtin::kCreateAsyncFromSyncIteratorBaseline>( {tagged_arg})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicCreateJSGeneratorObject( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* closure = current_interpreter_frame_.get(args[0]); ValueNode* receiver = current_interpreter_frame_.get(args[1]); PROCESS_AND_RETURN_IF_DONE( TryBuildAndAllocateJSGeneratorObject(closure, receiver), SetAccumulator); return SetAccumulator( BuildCallBuiltinWithTaggedInputs<Builtin::kCreateGeneratorObject>( {closure, receiver})); } ReduceResult MaglevGraphBuilder::VisitIntrinsicGeneratorGetResumeMode( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 1); ValueNode* generator = current_interpreter_frame_.get(args[0]); return SetAccumulator( BuildLoadTaggedField(generator, JSGeneratorObject::kResumeModeOffset)); } ReduceResult MaglevGraphBuilder::VisitIntrinsicGeneratorClose( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 1); ValueNode* generator = current_interpreter_frame_.get(args[0]); ValueNode* value = GetSmiConstant(JSGeneratorObject::kGeneratorClosed); RETURN_IF_ABORT(BuildStoreTaggedFieldNoWriteBarrier( generator, value, JSGeneratorObject::kContinuationOffset, StoreTaggedMode::kDefault)); SetAccumulator(GetRootConstant(RootIndex::kUndefinedValue)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicGetImportMetaObject( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 0); SetAccumulator(BuildCallRuntime(Runtime::kGetImportMetaObject, {}).value()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncFunctionAwait( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* tagged_arg_0; GET_VALUE_OR_ABORT(tagged_arg_0, GetTaggedValue(args[0])); ValueNode* tagged_arg_1; GET_VALUE_OR_ABORT(tagged_arg_1, GetTaggedValue(args[1])); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncFunctionAwait>( {tagged_arg_0, tagged_arg_1})); return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryReduceAsyncFunctionEnter( ValueNode* closure, ValueNode* receiver) { if (!broker()->dependencies()->DependOnPromiseHookProtector()) return {}; // We check that we'll be able to allocate the register file here at the start // rather than later so that we don't allocate the promise object for nothing. int register_count = bytecode().parameter_count_without_receiver() + bytecode().register_count(); bool can_allocate_array = FixedArray::SizeFor(register_count) <= kMaxRegularHeapObjectSize; if (!can_allocate_array) return {}; // Creating the Promise object. VirtualObject* promise = CreateJSPromiseObject(); // Creating the AsyncFunction object. // Create the register file. auto undefined = GetRootConstant(RootIndex::kUndefinedValue); base::SmallVector<ValueNode*, 16> values(register_count, undefined); VirtualObject* register_file = CreateFixedArray(base::VectorOf(values)); VirtualObject* async_function = CreateJSAsyncFunctionObject( GetContext(), closure, receiver, register_file, promise); return BuildInlinedAllocation(async_function, AllocationType::kYoung); } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncFunctionEnter( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* closure; GET_VALUE_OR_ABORT(closure, GetTaggedValue(args[0])); ValueNode* receiver; GET_VALUE_OR_ABORT(receiver, GetTaggedValue(args[1])); PROCESS_AND_RETURN_IF_DONE(TryReduceAsyncFunctionEnter(closure, receiver), SetAccumulator); SetAccumulator( BuildCallBuiltin<Builtin::kAsyncFunctionEnter>({closure, receiver})); return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryReduceAsyncFunctionReject( ValueNode* async_function_object, ValueNode* reason) { if (!broker()->dependencies()->DependOnPromiseHookProtector()) return {}; ValueNode* promise; GET_VALUE_OR_ABORT( promise, BuildLoadTaggedField(async_function_object, JSAsyncFunctionObject::kPromiseOffset)); // Create a nested frame state inside the current method's most-recent // {frame_state} that will ensure that lazy deoptimizations at this // point will still return the {promise} instead of the result of the // JSRejectPromise operation (which yields undefined). LazyDeoptFrameScope deopt_continuation( this, Builtin::kAsyncFunctionLazyDeoptContinuation, {}, base::VectorOf<ValueNode*>({promise})); // Disable the additional debug event for the rejection since a // debug event already happened for the exception that got us here. ValueNode* debug_event = GetRootConstant(RootIndex::kFalseValue); BuildCallBuiltin<Builtin::kRejectPromise>({promise, reason, debug_event}); return promise; } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncFunctionReject( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* async_function_object; GET_VALUE_OR_ABORT(async_function_object, GetTaggedValue(args[0])); ValueNode* reason; GET_VALUE_OR_ABORT(reason, GetTaggedValue(args[1])); PROCESS_AND_RETURN_IF_DONE( TryReduceAsyncFunctionReject(async_function_object, reason), SetAccumulator); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncFunctionReject>( {async_function_object, reason})); return ReduceResult::Done(); } MaybeReduceResult MaglevGraphBuilder::TryReduceAsyncFunctionResolve( ValueNode* async_function_object, ValueNode* value) { if (!broker()->dependencies()->DependOnPromiseHookProtector()) return {}; ValueNode* promise; GET_VALUE_OR_ABORT( promise, BuildLoadTaggedField(async_function_object, JSAsyncFunctionObject::kPromiseOffset)); if (NodeTypeIs(GetType(value), NodeType::kJSPrimitive)) { // We can strength-reduce JSResolvePromise to JSFulfillPromise if the // {resolution} is known to be a primitive, as in that case we don't perform // the implicit chaining (via "then"). AddNewNodeNoInputConversion<FulfillPromise>({promise, value}); } else { // Create a nested frame state inside the current method's most-recent // {frame_state} that will ensure that lazy deoptimizations at this // point will still return the {promise} instead of the result of the // JSResolvePromise operation (which yields undefined). LazyDeoptFrameScope deopt_continuation( this, Builtin::kAsyncFunctionLazyDeoptContinuation, {}, base::VectorOf<ValueNode*>({promise})); // TODO(dmercadier): optimize-away to FulfillPromise if possible; cf // JSNativeContextSpecialization::ReduceJSResolvePromise in Turboshaft. BuildCallBuiltin<Builtin::kResolvePromise>({promise, value}); } return promise; } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncFunctionResolve( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* async_function_object; GET_VALUE_OR_ABORT(async_function_object, GetTaggedValue(args[0])); ValueNode* value; GET_VALUE_OR_ABORT(value, GetTaggedValue(args[1])); PROCESS_AND_RETURN_IF_DONE( TryReduceAsyncFunctionResolve(async_function_object, value), SetAccumulator); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncFunctionResolve>( {async_function_object, value})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncGeneratorAwait( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* tagged_arg_0; GET_VALUE_OR_ABORT(tagged_arg_0, GetTaggedValue(args[0])); ValueNode* tagged_arg_1; GET_VALUE_OR_ABORT(tagged_arg_1, GetTaggedValue(args[1])); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncGeneratorAwait>( {tagged_arg_0, tagged_arg_1})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncGeneratorReject( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* tagged_arg_0; GET_VALUE_OR_ABORT(tagged_arg_0, GetTaggedValue(args[0])); ValueNode* tagged_arg_1; GET_VALUE_OR_ABORT(tagged_arg_1, GetTaggedValue(args[1])); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncGeneratorReject>( {tagged_arg_0, tagged_arg_1})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncGeneratorResolve( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 3); ValueNode* tagged_arg_0; GET_VALUE_OR_ABORT(tagged_arg_0, GetTaggedValue(args[0])); ValueNode* tagged_arg_1; GET_VALUE_OR_ABORT(tagged_arg_1, GetTaggedValue(args[1])); ValueNode* tagged_arg_2; GET_VALUE_OR_ABORT(tagged_arg_2, GetTaggedValue(args[2])); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncGeneratorResolve>( {tagged_arg_0, tagged_arg_1, tagged_arg_2})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitIntrinsicAsyncGeneratorYieldWithAwait( interpreter::RegisterList args) { DCHECK_EQ(args.register_count(), 2); ValueNode* tagged_arg_0; GET_VALUE_OR_ABORT(tagged_arg_0, GetTaggedValue(args[0])); ValueNode* tagged_arg_1; GET_VALUE_OR_ABORT(tagged_arg_1, GetTaggedValue(args[1])); SetAccumulator(BuildCallBuiltin<Builtin::kAsyncGeneratorYieldWithAwait>( {tagged_arg_0, tagged_arg_1})); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildGenericConstruct( ValueNode* target, ValueNode* new_target, ValueNode* context, const CallArguments& args, const compiler::FeedbackSource& feedback_source) { DCHECK_EQ(args.receiver_mode(), ConvertReceiverMode::kNullOrUndefined); ValueNode* tagged_target; GET_VALUE_OR_ABORT(tagged_target, GetTaggedValue(target)); ValueNode* tagged_new_target; GET_VALUE(tagged_new_target, GetTaggedValue(new_target)); ValueNode* tagged_context; GET_VALUE(tagged_context, GetTaggedValue(context)); switch (args.mode()) { case CallArguments::kDefault: return AddNewCallNode<Construct>(args, feedback_source, tagged_target, tagged_new_target, tagged_context); case CallArguments::kWithSpread: return AddNewCallNode<ConstructWithSpread>( args, feedback_source, tagged_target, tagged_new_target, tagged_context); case CallArguments::kWithArrayLike: UNREACHABLE(); } } ReduceResult MaglevGraphBuilder::BuildAndAllocateKeyValueArray( ValueNode* key, ValueNode* value) { VirtualObject* elements = CreateFixedArray(base::VectorOf({key, value})); compiler::MapRef map = broker()->target_native_context().js_array_packed_elements_map(broker()); VirtualObject* array; GET_VALUE_OR_ABORT( array, CreateJSArray(map, map.instance_size(), GetInt32Constant(2))); array->set(JSArray::kElementsOffset, elements); return BuildInlinedAllocation(array, AllocationType::kYoung); } ReduceResult MaglevGraphBuilder::BuildAndAllocateJSArray( compiler::MapRef map, ValueNode* length, ValueNode* elements, const compiler::SlackTrackingPrediction& slack_tracking_prediction, AllocationType allocation_type) { VirtualObject* array; GET_VALUE_OR_ABORT( array, CreateJSArray(map, slack_tracking_prediction.instance_size(), length)); array->set(JSArray::kElementsOffset, elements); for (int i = 0; i < slack_tracking_prediction.inobject_property_count(); i++) { array->set(map.GetInObjectPropertyOffset(i), GetRootConstant(RootIndex::kUndefinedValue)); } return BuildInlinedAllocation(array, allocation_type); } ReduceResult MaglevGraphBuilder::BuildAndAllocateJSArrayIterator( ValueNode* array, IterationKind iteration_kind) { compiler::MapRef map = broker()->target_native_context().initial_array_iterator_map(broker()); VirtualObject* iterator = CreateJSArrayIterator(map, array, iteration_kind); return BuildInlinedAllocation(iterator, AllocationType::kYoung); } MaybeReduceResult MaglevGraphBuilder::TryBuildAndAllocateJSGeneratorObject( ValueNode* closure, ValueNode* receiver) { compiler::OptionalJSFunctionRef maybe_constant = TryGetConstant<JSFunction>(closure); if (!maybe_constant.has_value()) return {}; compiler::JSFunctionRef function = maybe_constant.value(); if (!function.has_initial_map(broker())) return {}; // The shared function info of this JSFunction should be the same as the // current one we are compiling. DCHECK_EQ(compilation_unit()->shared_function_info(), function.shared(broker())); // Create the register file. int length = parameter_count_without_receiver() + register_count(); if (FixedArray::SizeFor(length) > kMaxRegularHeapObjectSize) { return {}; } auto undefined = GetRootConstant(RootIndex::kUndefinedValue); base::SmallVector<ValueNode*, 16> values(length, undefined); VirtualObject* register_file = CreateFixedArray(base::VectorOf(values)); // Create the JS[Async]GeneratorObject instance. compiler::SlackTrackingPrediction slack_tracking_prediction = broker()->dependencies()->DependOnInitialMapInstanceSizePrediction( function); compiler::MapRef initial_map = function.initial_map(broker()); VirtualObject* generator = CreateJSGeneratorObject( initial_map, slack_tracking_prediction.instance_size(), GetContext(), closure, receiver, register_file); // Handle in-object properties. for (int i = 0; i < slack_tracking_prediction.inobject_property_count(); i++) { generator->set(initial_map.GetInObjectPropertyOffset(i), undefined); } return BuildInlinedAllocation(generator, AllocationType::kYoung); } namespace { enum class InlineArrayCtorVariant { kZeroArgs, kOneArg_InterpretAsLength, kOneArg_InterpretAsElementValue, kMultipleArgs, }; // See also: JSFunction::GetDerivedMap. compiler::OptionalMapRef TryGetDerivedMap(compiler::JSHeapBroker* broker, compiler::JSFunctionRef target, compiler::JSFunctionRef new_target) { if (!new_target.map(broker).has_prototype_slot() || !new_target.has_initial_map(broker)) { return {}; } compiler::MapRef initial_map = new_target.initial_map(broker); compiler::OptionalObjectRef ctor = initial_map.GetConstructor(broker); if (!ctor.has_value() || !ctor->equals(target)) { return {}; } DCHECK(target.map(broker).is_constructor()); DCHECK(new_target.map(broker).is_constructor()); return initial_map; } } // namespace ValueNode* MaglevGraphBuilder::BuildElementsArray(ElementsKind elements_kind, int length) { DCHECK_GE(length, 0); DCHECK(IsFastElementsKind(elements_kind)); if (length == 0) { return GetRootConstant(RootIndex::kEmptyFixedArray); } // This DCHECK is slightly hacky since it relies on callers passing // kPreallocatedArrayElements for length 0 arrays. The intent is to make sure // that callers use a holey elements kind for the `Array(length)` 1-argument // constructor. DCHECK(IsHoleyElementsKind(elements_kind) || length == JSArray::kPreallocatedArrayElements); ValueNode* the_hole_value; if (IsDoubleElementsKind(elements_kind)) { the_hole_value = GetFloat64Constant(Float64::hole_nan()); } else { the_hole_value = GetRootConstant(RootIndex::kTheHoleValue); } base::SmallVector<ValueNode*, 16> values(length, the_hole_value); return BuildElementsArray(elements_kind, base::VectorOf(values)); } ValueNode* MaglevGraphBuilder::BuildElementsArray( ElementsKind elements_kind, base::Vector<ValueNode*> values) { DCHECK(IsFastElementsKind(elements_kind)); DCHECK_GT(static_cast<int>(values.size()), 0); return IsDoubleElementsKind(elements_kind) ? CreateFixedDoubleArray(values) : CreateFixedArray(values); } MaybeReduceResult MaglevGraphBuilder::TryReduceConstructArrayConstructor( compiler::JSFunctionRef target_function, ValueNode* new_target, CallArguments& args, compiler::OptionalAllocationSiteRef maybe_allocation_site) { const int arity = static_cast<int>(args.count()); InlineArrayCtorVariant variant; if (arity == 0) { variant = InlineArrayCtorVariant::kZeroArgs; } else if (arity == 1) { // TODO(jgruber): Also handle kOneArg_InterpretAsElementValue, for when the // single argument is not a number. That case, once recognized, could be // merged with kMultipleArgs. variant = InlineArrayCtorVariant::kOneArg_InterpretAsLength; } else { variant = InlineArrayCtorVariant::kMultipleArgs; } std::optional<int> maybe_length; if (variant == InlineArrayCtorVariant::kOneArg_InterpretAsLength) { maybe_length = TryGetInt32Constant(args[0]); if (maybe_length.has_value()) { if (*maybe_length < 0) return {}; if (*maybe_length >= JSArray::kInitialMaxFastElementArray) return {}; static_assert(JSArray::kInitialMaxFastElementArray < JSArray::kMaxFastArrayLength); } } compiler::OptionalJSFunctionRef new_target_constant = TryGetConstant<JSFunction>(new_target); if (!new_target_constant.has_value()) { return {}; } compiler::JSFunctionRef new_target_function = new_target_constant.value(); compiler::OptionalMapRef maybe_initial_map = TryGetDerivedMap(broker(), target_function, new_target_function); if (!maybe_initial_map.has_value()) return {}; ElementsKind elements_kind = maybe_allocation_site.has_value() ? maybe_allocation_site->GetElementsKind() : maybe_initial_map->elements_kind(); DCHECK(IsFastElementsKind(elements_kind)); if (variant == InlineArrayCtorVariant::kOneArg_InterpretAsLength) { // Constructing an Array via new Array(N) where N is an unsigned // integer, always creates a holey backing store. elements_kind = GetHoleyElementsKind(elements_kind); } maybe_initial_map = maybe_initial_map->AsElementsKind(broker(), elements_kind); if (!maybe_initial_map.has_value()) return {}; compiler::MapRef initial_map = maybe_initial_map.value(); compiler::SlackTrackingPrediction slack_tracking_prediction = broker()->dependencies()->DependOnInitialMapInstanceSizePrediction( new_target_function); // Tells whether we are protected by either the {site} or a call speculation // bit to do certain speculative optimizations. This mechanism protects // against deopt loops. bool can_speculate_call; AllocationType allocation_type = AllocationType::kYoung; if (maybe_allocation_site) { can_speculate_call = !maybe_allocation_site->IsSpeculationDisabled(); allocation_type = broker()->dependencies()->DependOnPretenureMode(*maybe_allocation_site); broker()->dependencies()->DependOnElementsKind(*maybe_allocation_site); } else { can_speculate_call = CanSpeculateCall(); } // Arity 0, `new Array()`. if (variant == InlineArrayCtorVariant::kZeroArgs) { return BuildAndAllocateJSArray( initial_map, GetSmiConstant(0), BuildElementsArray(elements_kind, JSArray::kPreallocatedArrayElements), slack_tracking_prediction, allocation_type); } // Arity 1, `new Array(maybe_length)`. if (variant == InlineArrayCtorVariant::kOneArg_InterpretAsLength) { if (maybe_length.has_value()) { DCHECK_GE(*maybe_length, 0); DCHECK_LT(*maybe_length, JSArray::kInitialMaxFastElementArray); return BuildAndAllocateJSArray( initial_map, GetSmiConstant(*maybe_length), BuildElementsArray(elements_kind, *maybe_length), slack_tracking_prediction, allocation_type); } // We don't know anything about the length, so we rely on the allocation // site to avoid deopt loops. if (!can_speculate_call) return {}; return Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare(builder, Operation::kGreaterThanOrEqual, args[0], GetInt32Constant(0)); }, [&] { ValueNode* elements; GET_VALUE_OR_ABORT(elements, AddNewNode<AllocateElementsArray>( {args[0]}, elements_kind, allocation_type)); return BuildAndAllocateJSArray(initial_map, args[0], elements, slack_tracking_prediction, allocation_type); }, [&] { ValueNode* error = GetSmiConstant( static_cast<int>(MessageTemplate::kInvalidArrayLength)); return BuildThrow(Throw::kThrowRangeError, error); }); } if (arity > JSArray::kInitialMaxFastElementArray) return {}; // Arity > 1, `new Array(x0, x1, ...)`. DCHECK_GT(arity, 1); DCHECK_EQ(variant, InlineArrayCtorVariant::kMultipleArgs); // Gather the values to store into the newly created array, and remember // sufficient information about node types so we can select a suitable // elements_kind below. bool values_all_smis = true, values_all_numbers = true, values_any_nonnumber = false; base::SmallVector<ValueNode*, 16> values; values.reserve(arity); for (ValueNode* v : args) { NodeType node_type = GetType(v); if (!NodeTypeIs(node_type, NodeType::kSmi)) { values_all_smis = false; if (!NodeTypeIs(node_type, NodeType::kNumber)) { values_all_numbers = false; if (!NodeTypeCanBe(node_type, NodeType::kNumber)) { values_any_nonnumber = true; } } } values.push_back(v); } if (values_all_smis) { // Smis can be stored with any elements kind. } else if (values_all_numbers) { elements_kind = GetMoreGeneralElementsKind( elements_kind, IsHoleyElementsKind(elements_kind) ? HOLEY_DOUBLE_ELEMENTS : PACKED_DOUBLE_ELEMENTS); } else if (values_any_nonnumber) { // We statically know that at least one value is not a number. elements_kind = GetMoreGeneralElementsKind( elements_kind, IsHoleyElementsKind(elements_kind) ? HOLEY_ELEMENTS : PACKED_ELEMENTS); } else if (!can_speculate_call) { // We cannot precisely determine the elements_kind based on static types, // and speculation has already been disabled via feedback. return {}; } // Update the initial map based on our potentially changed elements_kind. { compiler::OptionalMapRef maybe_updated_map = initial_map.AsElementsKind(broker(), elements_kind); if (!maybe_updated_map.has_value()) return {}; initial_map = maybe_updated_map.value(); } // Insert type checks as necessary. if (IsSmiElementsKind(elements_kind)) { for (ValueNode* v : args) { if (NodeTypeIs(GetType(v), NodeType::kSmi)) continue; RETURN_IF_ABORT(BuildCheckSmi(v)); // Note that BuildCheckSmi doesn't actually convert the value; it just // checks that it *can* be canonicalized. The conversion happens later, // see ConvertForField. } } else if (IsDoubleElementsKind(elements_kind)) { for (ValueNode*& v : values) { if (!NodeTypeIs(GetType(v), NodeType::kNumber)) { RETURN_IF_ABORT(BuildCheckNumber(v)); } DCHECK(NodeTypeIs(GetType(v), NodeType::kNumber)); ValueNode* float64_value; GET_VALUE_OR_ABORT(float64_value, GetFloat64(v)); GET_VALUE_OR_ABORT(v, GetSilencedNaN(float64_value)); } } return BuildAndAllocateJSArray( initial_map, GetSmiConstant(arity), BuildElementsArray(elements_kind, base::VectorOf(values)), slack_tracking_prediction, allocation_type); } MaybeReduceResult MaglevGraphBuilder::TryReduceConstructBuiltin( compiler::JSFunctionRef target_function, compiler::SharedFunctionInfoRef target_sfi, ValueNode* target, ValueNode* new_target, CallArguments& args) { DCHECK_EQ(target_function, TryGetConstant<HeapObject>(target).value()); DCHECK_EQ(target_sfi, target_function.shared(broker())); switch (target_sfi.builtin_id()) { case Builtin::kArrayConstructor: { RETURN_IF_DONE(TryReduceConstructArrayConstructor(target_function, new_target, args)); break; } case Builtin::kObjectConstructor: { if (target != new_target) return {}; // If no value is passed, we can immediately lower to a simple // constructor. if (args.count() == 0) { return BuildInlinedAllocation(CreateJSConstructor(target_function), AllocationType::kYoung); } break; } case Builtin::kStringConstructor: { if (target != new_target) return {}; ValueNode* value; if (args.count() == 0) { value = GetRootConstant(RootIndex::kempty_string); } else { LazyDeoptFrameScope deopt_continuation( this, Builtin::kStringCreateLazyDeoptContinuation, target_function, base::VectorOf<ValueNode*>( {GetRootConstant(RootIndex::kTheHoleValue)})); GET_VALUE_OR_ABORT(value, BuildToString(args[0], ToString::kThrowOnSymbol)); } return BuildInlinedAllocation(CreateJSStringWrapper(value), AllocationType::kYoung); } case Builtin::kPromiseConstructor: case Builtin::kTypedArrayConstructor: // TODO(victorgomes): specialize more known constants builtin targets. break; default: break; } return {}; } MaybeReduceResult MaglevGraphBuilder::TryReduceJSConstructStub( compiler::JSFunctionRef target_constant, compiler::SharedFunctionInfoRef shared_function_info, ValueNode* target, ValueNode* new_target, CallArguments& args, compiler::FeedbackSource& feedback_source) { DCHECK_EQ(target_constant, TryGetConstant<HeapObject>(target).value()); // Emit an inlined version of the construct stub. This can be either // JSConstructStubGeneric, or JSBuiltinsConstructStub if construct_as_builtin // is set. The latter is a simplified version of the former: the receiver // is always the_hole, and the returned value is unchecked. const bool construct_as_builtin = shared_function_info.construct_as_builtin(); if (IsDerivedConstructor(shared_function_info.kind())) { ValueNode* implicit_receiver = GetRootConstant(RootIndex::kTheHoleValue); args.set_receiver(implicit_receiver); ValueNode* call_result; { LazyDeoptFrameScope construct(this, implicit_receiver); MaybeReduceResult result = TryBuildCallKnownJSFunction( target_constant, new_target, args, feedback_source); RETURN_IF_ABORT(result); if (construct_as_builtin) { // The invariant of such builtin targets is that the return value is a // JSReceiver. Set the type accordingly here. EnsureType(result.value(), NodeType::kJSReceiver); return result; } call_result = result.value(); } if (CheckType(call_result, NodeType::kJSReceiver)) return call_result; ValueNode* constant_node; if (compiler::OptionalJSReceiverRef maybe_constant = TryGetConstant<JSReceiver>(call_result, &constant_node)) { return constant_node; } if (!call_result->properties().is_tagged()) { return BuildThrow(Throw::kThrowConstructorReturnedNonObject); } return AddNewNode<CheckDerivedConstructResult>({call_result}); } ValueNode* implicit_receiver = nullptr; if (construct_as_builtin) { implicit_receiver = GetRootConstant(RootIndex::kTheHoleValue); } else { // We do not create a construct stub lazy deopt frame, since // FastNewObject cannot fail if target is a JSFunction. if (compiler::OptionalJSFunctionRef new_target_function = TryGetConstant<JSFunction>(new_target)) { if (compiler::OptionalMapRef map = TryGetDerivedMap( broker(), target_constant, new_target_function.value())) { GET_VALUE_OR_ABORT(implicit_receiver, BuildInlinedAllocation( CreateJSConstructor(new_target_function.value()), AllocationType::kYoung)); } } if (implicit_receiver == nullptr) { // TODO(jgruber): Support ConstructStubCreate for deoptimizing during // object construction; for these cases, we have to resume before the // `target` call. if (target != new_target) return {}; GET_VALUE_OR_ABORT( implicit_receiver, BuildCallBuiltinWithTaggedInputs<Builtin::kFastNewObject>( {target, new_target})); } EnsureType(implicit_receiver, NodeType::kJSReceiver); } args.set_receiver(implicit_receiver); ValueNode* call_result; { LazyDeoptFrameScope construct(this, implicit_receiver); MaybeReduceResult result = TryBuildCallKnownJSFunction( target_constant, new_target, args, feedback_source); RETURN_IF_ABORT(result); if (construct_as_builtin) { // The invariant of such builtin targets is that the return value is a // JSReceiver. Set the type accordingly here. EnsureType(result.value(), NodeType::kJSReceiver); return result; } call_result = result.value(); } if (CheckType(call_result, NodeType::kJSReceiver)) return call_result; if (!call_result->properties().is_tagged()) return implicit_receiver; ValueNode* constant_node; if (compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(call_result, &constant_node)) { compiler::HeapObjectRef constant = maybe_constant.value(); DCHECK(CheckType(implicit_receiver, NodeType::kJSReceiver)); if (constant.IsJSReceiver()) return constant_node; return implicit_receiver; } return AddNewNode<CheckConstructResult>({call_result, implicit_receiver}); } MaybeReduceResult MaglevGraphBuilder::TryReduceConstruct( compiler::HeapObjectRef target_constant, ValueNode* target, ValueNode* new_target, CallArguments& args, compiler::FeedbackSource& feedback_source) { // If we have AllocationSite feedback, we take a different path that calls // TryReduceConstructArrayConstructor directly. DCHECK(!target_constant.IsAllocationSite()); DCHECK_EQ(target_constant, TryGetConstant<HeapObject>(target).value()); if (!target_constant.map(broker()).is_constructor()) { // Non-constructor targets throw, let the generic path handle this. // See also Runtime::kThrowConstructedNonConstructable. return {}; } // TODO(v8:7700): Add fast paths for other callables. if (!target_constant.IsJSFunction()) return {}; compiler::JSFunctionRef target_function = target_constant.AsJSFunction(); // Do not inline constructors with break points. compiler::SharedFunctionInfoRef target_sfi = target_function.shared(broker()); if (target_sfi.HasBreakInfo(broker())) { return {}; } // Do not inline cross-native-context. if (target_function.native_context(broker()) != broker()->target_native_context()) { return {}; } if (args.mode() != CallArguments::kDefault) { // TODO(victorgomes): Maybe inline the spread stub? Or call known // target_function directly if arguments list is an array. return {}; } if (target_sfi.HasBuiltinId()) { RETURN_IF_DONE(TryReduceConstructBuiltin(target_function, target_sfi, target, new_target, args)); } return TryReduceJSConstructStub(target_function, target_sfi, target, new_target, args, feedback_source); } ReduceResult MaglevGraphBuilder::BuildConstruct( ValueNode* target, ValueNode* new_target, CallArguments& args, compiler::FeedbackSource& feedback_source) { compiler::ProcessedFeedback const& processed_feedback = broker()->GetFeedbackForCall(feedback_source); if (processed_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForConstruct); } // Enable feedback-based speculation. SaveCallSpeculationScope saved(this, feedback_source); // TODO(victorgomes): Add the case for Rest parameter. if (args.mode() == CallArguments::kWithSpread) { if (std::optional<VirtualObject*> arguments_object = TryGetNonEscapingArgumentsObject(args.spread())) { PROCESS_AND_RETURN_IF_DONE( TryReduceConstructWithSpreadForArgumentsObject( target, new_target, args, *arguments_object, feedback_source), SetAccumulator); } return SetAccumulator(BuildGenericConstruct( target, new_target, GetContext(), args, feedback_source)); } // Note that for Construct nodes, the `CallFeedback::target` actually refers // to new_target. compiler::OptionalHeapObjectRef new_target_feedback = processed_feedback.AsCall().target(); if (new_target_feedback.has_value() && new_target_feedback->IsAllocationSite()) { // The feedback is an AllocationSite, which means we have called the // Array function and collected transition (and pretenuring) feedback // for the resulting arrays. compiler::JSFunctionRef array_function = broker()->target_native_context().array_function(broker()); RETURN_IF_ABORT(BuildCheckValueByReference( target, array_function, DeoptimizeReason::kWrongConstructor)); PROCESS_AND_RETURN_IF_DONE( TryReduceConstructArrayConstructor( array_function, GetConstant(array_function), args, new_target_feedback->AsAllocationSite()), SetAccumulator); return SetAccumulator(BuildGenericConstruct( target, new_target, GetContext(), args, feedback_source)); } // Specialize to new_target_feedback if possible. compiler::OptionalHeapObjectRef new_target_constant = TryGetConstant<HeapObject>(new_target); if (new_target_feedback.has_value() && !new_target_constant.has_value() && new_target_feedback->map(broker()).is_constructor()) { RETURN_IF_ABORT( BuildCheckValueByReference(new_target, new_target_feedback.value(), DeoptimizeReason::kWrongCallTarget)); ValueNode* new_target_constant_node = GetConstant(new_target_feedback.value()); if (target == new_target) { target = new_target_constant_node; } new_target = new_target_constant_node; } if (compiler::OptionalHeapObjectRef target_constant = TryGetConstant<HeapObject>(target)) { PROCESS_AND_RETURN_IF_DONE( TryReduceConstruct(target_constant.value(), target, new_target, args, feedback_source), SetAccumulator); } return SetAccumulator(BuildGenericConstruct(target, new_target, GetContext(), args, feedback_source)); } ReduceResult MaglevGraphBuilder::VisitConstruct() { ValueNode* new_target = GetAccumulator(); ValueNode* target = LoadRegister(0); interpreter::RegisterList reg_list = iterator_.GetRegisterListOperand(1); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source{feedback(), slot}; CallArguments args(ConvertReceiverMode::kNullOrUndefined, reg_list, current_interpreter_frame_); return BuildConstruct(target, new_target, args, feedback_source); } ReduceResult MaglevGraphBuilder::VisitConstructWithSpread() { ValueNode* new_target = GetAccumulator(); ValueNode* target = LoadRegister(0); interpreter::RegisterList reglist = iterator_.GetRegisterListOperand(1); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source(feedback(), slot); CallArguments args(ConvertReceiverMode::kNullOrUndefined, reglist, current_interpreter_frame_, CallArguments::kWithSpread); return BuildConstruct(target, new_target, args, feedback_source); } ReduceResult MaglevGraphBuilder::VisitConstructForwardAllArgs() { ValueNode* new_target = GetAccumulator(); ValueNode* target = LoadRegister(0); FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; if (is_inline()) { constexpr int kSkipReceiver = 1; base::SmallVector<ValueNode*, 8> forwarded_args(argument_count() - kSkipReceiver); for (int i = 1 /* skip receiver */; i < argument_count(); ++i) { forwarded_args[i - kSkipReceiver] = GetInlinedArgument(i); } CallArguments args(ConvertReceiverMode::kNullOrUndefined, std::move(forwarded_args)); return BuildConstruct(target, new_target, args, feedback_source); } else { // TODO(syg): Add ConstructForwardAllArgs reductions and support inlining. return SetAccumulator(BuildCallBuiltinWithTaggedInputs< Builtin::kConstructForwardAllArgs_WithFeedback>( {target, new_target}, feedback_source)); } } ReduceResult MaglevGraphBuilder::VisitTestEqual() { return VisitCompareOperation<Operation::kEqual>(); } ReduceResult MaglevGraphBuilder::VisitTestEqualStrict() { return VisitCompareOperation<Operation::kStrictEqual>(); } ReduceResult MaglevGraphBuilder::VisitTestLessThan() { return VisitCompareOperation<Operation::kLessThan>(); } ReduceResult MaglevGraphBuilder::VisitTestLessThanOrEqual() { return VisitCompareOperation<Operation::kLessThanOrEqual>(); } ReduceResult MaglevGraphBuilder::VisitTestGreaterThan() { return VisitCompareOperation<Operation::kGreaterThan>(); } ReduceResult MaglevGraphBuilder::VisitTestGreaterThanOrEqual() { return VisitCompareOperation<Operation::kGreaterThanOrEqual>(); } MaglevGraphBuilder::InferHasInPrototypeChainResult MaglevGraphBuilder::InferHasInPrototypeChain( ValueNode* receiver, compiler::HeapObjectRef prototype) { auto possible_maps = known_node_aspects().TryGetPossibleMaps(receiver); // If the map set is not found, then we don't know anything about the map of // the receiver, so bail. if (!possible_maps) { return kMayBeInPrototypeChain; } // If the set of possible maps is empty, then there's no possible map for this // receiver, therefore this path is unreachable at runtime. We're unlikely to // ever hit this case, BuildCheckMaps should already unconditionally deopt, // but check it in case another checking operation fails to statically // unconditionally deopt. if (possible_maps->is_empty()) { // TODO(leszeks): Add an unreachable assert here. return kIsNotInPrototypeChain; } ZoneVector<compiler::MapRef> receiver_map_refs(zone()); // Try to determine either that all of the {receiver_maps} have the given // {prototype} in their chain, or that none do. If we can't tell, return // kMayBeInPrototypeChain. bool all = true; bool none = true; for (compiler::MapRef map : *possible_maps) { receiver_map_refs.push_back(map); while (true) { if (IsSpecialReceiverInstanceType(map.instance_type())) { return kMayBeInPrototypeChain; } if (!map.IsJSObjectMap()) { all = false; break; } compiler::HeapObjectRef map_prototype = map.prototype(broker()); if (map_prototype.equals(prototype)) { none = false; break; } map = map_prototype.map(broker()); // TODO(v8:11457) Support dictionary mode protoypes here. if (!map.is_stable() || map.is_dictionary_map()) { return kMayBeInPrototypeChain; } if (map.oddball_type(broker()) == compiler::OddballType::kNull) { all = false; break; } } } DCHECK(!receiver_map_refs.empty()); DCHECK_IMPLIES(all, !none); if (!all && !none) return kMayBeInPrototypeChain; { compiler::OptionalJSObjectRef last_prototype; if (all) { // We don't need to protect the full chain if we found the prototype, we // can stop at {prototype}. In fact we could stop at the one before // {prototype} but since we're dealing with multiple receiver maps this // might be a different object each time, so it's much simpler to include // {prototype}. That does, however, mean that we must check {prototype}'s // map stability. if (!prototype.IsJSObject() || !prototype.map(broker()).is_stable()) { return kMayBeInPrototypeChain; } last_prototype = prototype.AsJSObject(); } broker()->dependencies()->DependOnStablePrototypeChains( receiver_map_refs, kStartAtPrototype, last_prototype); } DCHECK_EQ(all, !none); return all ? kIsInPrototypeChain : kIsNotInPrototypeChain; } MaybeReduceResult MaglevGraphBuilder::TryBuildFastHasInPrototypeChain( ValueNode* object, compiler::HeapObjectRef prototype) { auto in_prototype_chain = InferHasInPrototypeChain(object, prototype); if (in_prototype_chain == kMayBeInPrototypeChain) return {}; return GetBooleanConstant(in_prototype_chain == kIsInPrototypeChain); } ReduceResult MaglevGraphBuilder::BuildHasInPrototypeChain( ValueNode* object, compiler::HeapObjectRef prototype) { RETURN_IF_DONE(TryBuildFastHasInPrototypeChain(object, prototype)); return AddNewNode<HasInPrototypeChain>({object}, prototype); } MaybeReduceResult MaglevGraphBuilder::TryBuildFastOrdinaryHasInstance( ValueNode* object, compiler::JSObjectRef callable, ValueNode* callable_node_if_not_constant) { const bool is_constant = callable_node_if_not_constant == nullptr; if (!is_constant) return {}; if (callable.IsJSBoundFunction()) { // OrdinaryHasInstance on bound functions turns into a recursive // invocation of the instanceof operator again. compiler::JSBoundFunctionRef function = callable.AsJSBoundFunction(); compiler::JSReceiverRef bound_target_function = function.bound_target_function(broker()); if (bound_target_function.IsJSObject()) { RETURN_IF_DONE(TryBuildFastInstanceOf( object, bound_target_function.AsJSObject(), nullptr)); } // If we can't build a fast instance-of, build a slow one with the // partial optimisation of using the bound target function constant. return BuildCallBuiltinWithTaggedInputs<Builtin::kInstanceOf>( {object, GetConstant(bound_target_function)}); } if (callable.IsJSFunction()) { // Optimize if we currently know the "prototype" property. compiler::JSFunctionRef function = callable.AsJSFunction(); // TODO(v8:7700): Remove the has_prototype_slot condition once the broker // is always enabled. if (!function.map(broker()).has_prototype_slot() || !function.has_instance_prototype(broker()) || function.PrototypeRequiresRuntimeLookup(broker())) { return {}; } compiler::HeapObjectRef prototype = broker()->dependencies()->DependOnPrototypeProperty(function); return BuildHasInPrototypeChain(object, prototype); } return {}; } ReduceResult MaglevGraphBuilder::BuildOrdinaryHasInstance( ValueNode* object, compiler::JSObjectRef callable, ValueNode* callable_node_if_not_constant) { RETURN_IF_DONE(TryBuildFastOrdinaryHasInstance( object, callable, callable_node_if_not_constant)); return BuildCallBuiltinWithTaggedInputs<Builtin::kOrdinaryHasInstance>( {callable_node_if_not_constant ? callable_node_if_not_constant : GetConstant(callable), object}); } MaybeReduceResult MaglevGraphBuilder::TryBuildFastInstanceOf( ValueNode* object, compiler::JSObjectRef callable, ValueNode* callable_node_if_not_constant) { compiler::MapRef receiver_map = callable.map(broker()); compiler::NameRef name = broker()->has_instance_symbol(); compiler::PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo( receiver_map, name, compiler::AccessMode::kLoad); // TODO(v8:11457) Support dictionary mode holders here. if (access_info.IsInvalid() || access_info.HasDictionaryHolder()) { return {}; } access_info.RecordDependencies(broker()->dependencies()); if (access_info.IsNotFound()) { // If there's no @@hasInstance handler, the OrdinaryHasInstance operation // takes over, but that requires the constructor to be callable. if (!receiver_map.is_callable()) return {}; broker()->dependencies()->DependOnStablePrototypeChains( access_info.lookup_start_object_maps(), kStartAtPrototype); // Monomorphic property access. if (callable_node_if_not_constant) { RETURN_IF_ABORT(BuildCheckMaps( callable_node_if_not_constant, base::VectorOf(access_info.lookup_start_object_maps()))); } else { // Even if we have a constant receiver, we still have to make sure its // map is correct, in case it migrates. if (receiver_map.is_stable()) { broker()->dependencies()->DependOnStableMap(receiver_map); } else { RETURN_IF_ABORT(BuildCheckMaps( GetConstant(callable), base::VectorOf(access_info.lookup_start_object_maps()))); } } return BuildOrdinaryHasInstance(object, callable, callable_node_if_not_constant); } if (access_info.IsFastDataConstant()) { compiler::OptionalJSObjectRef holder = access_info.holder(); bool found_on_proto = holder.has_value(); compiler::JSObjectRef holder_ref = found_on_proto ? holder.value() : callable; if (access_info.field_representation().IsDouble()) return {}; compiler::OptionalObjectRef has_instance_field = holder_ref.GetOwnFastConstantDataProperty( broker(), access_info.field_representation(), access_info.field_index(), broker()->dependencies()); if (!has_instance_field.has_value() || !has_instance_field->IsHeapObject() || !has_instance_field->AsHeapObject().map(broker()).is_callable()) { return {}; } if (found_on_proto) { broker()->dependencies()->DependOnStablePrototypeChains( access_info.lookup_start_object_maps(), kStartAtPrototype, holder.value()); } ValueNode* callable_node; if (callable_node_if_not_constant) { // Check that {callable_node_if_not_constant} is actually {callable}. RETURN_IF_ABORT( BuildCheckValueByReference(callable_node_if_not_constant, callable, DeoptimizeReason::kWrongValue)); callable_node = callable_node_if_not_constant; } else { callable_node = GetConstant(callable); } RETURN_IF_ABORT(BuildCheckMaps( callable_node, base::VectorOf(access_info.lookup_start_object_maps()))); // Special case the common case, where @@hasInstance is // Function.p.hasInstance. In this case we don't need to call ToBoolean (or // use the continuation), since OrdinaryHasInstance is guaranteed to return // a boolean. if (has_instance_field->IsJSFunction()) { compiler::SharedFunctionInfoRef shared = has_instance_field->AsJSFunction().shared(broker()); if (shared.HasBuiltinId() && shared.builtin_id() == Builtin::kFunctionPrototypeHasInstance) { return BuildOrdinaryHasInstance(object, callable, callable_node_if_not_constant); } } // Call @@hasInstance CallArguments args(ConvertReceiverMode::kNotNullOrUndefined, {callable_node, object}); ValueNode* call_result; { // Make sure that a lazy deopt after the @@hasInstance call also performs // ToBoolean before returning to the interpreter. LazyDeoptFrameScope continuation_scope( this, Builtin::kToBooleanLazyDeoptContinuation); if (has_instance_field->IsJSFunction()) { SaveCallSpeculationScope saved(this); GET_VALUE_OR_ABORT( call_result, TryReduceCallForConstant(has_instance_field->AsJSFunction(), args)); } else { GET_VALUE_OR_ABORT(call_result, BuildGenericCall(GetConstant(*has_instance_field), Call::TargetType::kAny, args)); } // TODO(victorgomes): Propagate the case if we need to soft deopt. } return BuildToBoolean(call_result); } return {}; } template <bool flip> ReduceResult MaglevGraphBuilder::BuildToBoolean(ValueNode* value) { if (IsConstantNode(value->opcode())) { return GetBooleanConstant(FromConstantToBool(local_isolate(), value) ^ flip); } switch (value->value_representation()) { case ValueRepresentation::kHoleyFloat64: value = AddNewNodeNoInputConversion<UnsafeHoleyFloat64ToFloat64>({value}); [[fallthrough]]; case ValueRepresentation::kFloat64: // The ToBoolean of both the_hole and NaN is false, so we can use the // same operation for HoleyFloat64 and Float64. return AddNewNodeNoInputConversion<Float64ToBoolean>({value}, flip); case ValueRepresentation::kUint32: // Uint32 has the same logic as Int32 when converting ToBoolean, namely // comparison against zero, so we can cast it and ignore the signedness. value = AddNewNodeNoInputConversion<TruncateUint32ToInt32>({value}); [[fallthrough]]; case ValueRepresentation::kInt32: return AddNewNodeNoInputConversion<Int32ToBoolean>({value}, flip); case ValueRepresentation::kIntPtr: return AddNewNodeNoInputConversion<IntPtrToBoolean>({value}, flip); case ValueRepresentation::kTagged: break; case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } NodeInfo* node_info = known_node_aspects().TryGetInfoFor(value); if (node_info) { if (ValueNode* as_int32 = node_info->alternative().int32()) { return AddNewNodeNoInputConversion<Int32ToBoolean>({as_int32}, flip); } if (ValueNode* as_float64 = node_info->alternative().float64()) { return AddNewNodeNoInputConversion<Float64ToBoolean>({as_float64}, flip); } } NodeType value_type; if (CheckType(value, NodeType::kJSReceiver, &value_type)) { ValueNode* result; GET_VALUE_OR_ABORT(result, BuildTestUndetectable(value)); // TODO(victorgomes): Check if it is worth to create // TestUndetectableLogicalNot or to remove ToBooleanLogicalNot, since we // already optimize LogicalNots by swapping the branches. if constexpr (!flip) { GET_VALUE_OR_ABORT(result, BuildLogicalNot(result)); } return result; } ValueNode* falsy_value = nullptr; if (CheckType(value, NodeType::kString)) { falsy_value = GetRootConstant(RootIndex::kempty_string); } else if (CheckType(value, NodeType::kSmi)) { falsy_value = GetSmiConstant(0); } if (falsy_value != nullptr) { return AddNewNode<std::conditional_t<flip, TaggedEqual, TaggedNotEqual>>( {value, falsy_value}); } if (CheckType(value, NodeType::kBoolean)) { if constexpr (flip) { GET_VALUE_OR_ABORT(value, BuildLogicalNot(value)); } return value; } return AddNewNode<std::conditional_t<flip, ToBooleanLogicalNot, ToBoolean>>( {value}, GetCheckType(value_type, value)); } MaybeReduceResult MaglevGraphBuilder::TryBuildFastInstanceOfWithFeedback( ValueNode* object, ValueNode* callable, compiler::FeedbackSource feedback_source) { compiler::ProcessedFeedback const& feedback = broker()->GetFeedbackForInstanceOf(feedback_source); if (feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForInstanceOf); } // Check if the right hand side is a known receiver, or // we have feedback from the InstanceOfIC. if (compiler::OptionalJSObjectRef maybe_constant = TryGetConstant<JSObject>(callable)) { return TryBuildFastInstanceOf(object, maybe_constant.value(), nullptr); } if (feedback_source.IsValid()) { compiler::OptionalJSObjectRef callable_from_feedback = feedback.AsInstanceOf().value(); if (callable_from_feedback) { return TryBuildFastInstanceOf(object, *callable_from_feedback, callable); } } return {}; } ReduceResult MaglevGraphBuilder::VisitTestInstanceOf() { // TestInstanceOf <src> <feedback_slot> ValueNode* object = LoadRegister(0); ValueNode* callable = GetAccumulator(); FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; MaybeReduceResult result = TryBuildFastInstanceOfWithFeedback(object, callable, feedback_source); PROCESS_AND_RETURN_IF_DONE(result, SetAccumulator); ValueNode* context = GetContext(); return SetAccumulator( AddNewNode<TestInstanceOf>({context, object, callable}, feedback_source)); } ReduceResult MaglevGraphBuilder::VisitTestIn() { // TestIn <src> <feedback_slot> ValueNode* object = GetAccumulator(); ValueNode* name = LoadRegister(0); FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; // TODO(victorgomes): Create fast path using feedback. USE(feedback_source); return SetAccumulator(BuildCallBuiltinWithTaggedInputs<Builtin::kKeyedHasIC>( {object, name}, feedback_source)); } ReduceResult MaglevGraphBuilder::VisitToName() { // ToObject <dst> if (!CheckType(GetAccumulator(), NodeType::kName)) { return SetAccumulator(AddNewNode<ToName>({GetContext(), GetAccumulator()})); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::BuildToString(ValueNode* value, ToString::ConversionMode mode) { if (CheckType(value, NodeType::kString)) return value; RETURN_IF_DONE(reducer_.TryFoldNumberToString(value)); // TODO(victorgomes): Add fast path for constant primitives. if (CheckType(value, NodeType::kSmi)) { ValueNode* smi_value; GET_VALUE_OR_ABORT(smi_value, GetSmiValue(value)); return AddNewNode<SmiToString>({smi_value}); } if (CheckType(value, NodeType::kNumber)) { if (ValueNode* int32_value = reducer_.TryGetInt32(value)) { return AddNewNode<Int32ToString>({int32_value}); } if (ValueNode* float64_value = reducer_.TryGetFloat64(value)) { return AddNewNode<Float64ToString>({float64_value}); } // TODO(victorgomes): Maybe we should have a notion of weak tagged use, // meaning, we usually emit a tagged version of the node, but we can at // zero-cost change it to a non-tagged equivalent. value->MaybeRecordUseReprHint(UseRepresentation::kTaggedForNumberToString); return AddNewNode<NumberToString>({value}); } return AddNewNode<ToString>({GetContext(), value}, mode); } ReduceResult MaglevGraphBuilder::BuildToNumberOrToNumeric( Object::Conversion mode) { ValueNode* value = GetAccumulator(); switch (value->value_representation()) { case ValueRepresentation::kInt32: case ValueRepresentation::kUint32: case ValueRepresentation::kFloat64: case ValueRepresentation::kIntPtr: return ReduceResult::Done(); case ValueRepresentation::kHoleyFloat64: { return SetAccumulator(AddNewNode<HoleyFloat64ToSilencedFloat64>({value})); } case ValueRepresentation::kTagged: // We'll insert the required checks depending on the feedback. break; case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } FeedbackSlot slot = GetSlotOperand(0); switch (broker()->GetFeedbackForBinaryOperation( compiler::FeedbackSource(feedback(), slot))) { case BinaryOperationHint::kSignedSmall: RETURN_IF_ABORT(BuildCheckSmi(value)); break; case BinaryOperationHint::kSignedSmallInputs: case BinaryOperationHint::kAdditiveSafeInteger: UNREACHABLE(); case BinaryOperationHint::kNumber: case BinaryOperationHint::kBigInt: case BinaryOperationHint::kBigInt64: if (mode == Object::Conversion::kToNumber && EnsureType(value, NodeType::kNumber)) { return ReduceResult::Done(); } RETURN_IF_ABORT(AddNewNode<CheckNumber>({value}, mode)); break; case BinaryOperationHint::kNone: // TODO(leszeks): Faster ToNumber for kNumberOrOddball case BinaryOperationHint::kNumberOrOddball: case BinaryOperationHint::kString: case BinaryOperationHint::kStringOrStringWrapper: case BinaryOperationHint::kAny: if (CheckType(value, NodeType::kNumber)) return ReduceResult::Done(); return SetAccumulator(AddNewNode<ToNumberOrNumeric>({value}, mode)); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitToNumber() { return BuildToNumberOrToNumeric(Object::Conversion::kToNumber); } ReduceResult MaglevGraphBuilder::VisitToNumeric() { return BuildToNumberOrToNumeric(Object::Conversion::kToNumeric); } ReduceResult MaglevGraphBuilder::VisitToObject() { // ToObject <dst> ValueNode* value = GetAccumulator(); interpreter::Register destination = iterator_.GetRegisterOperand(0); NodeType old_type; if (CheckType(value, NodeType::kJSReceiver, &old_type)) { MoveNodeBetweenRegisters(interpreter::Register::virtual_accumulator(), destination); } else { ValueNode* object; GET_VALUE_OR_ABORT(object, AddNewNode<ToObject>({GetContext(), value}, GetCheckType(old_type, value))); StoreRegister(destination, object); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitToString() { // ToString return SetAccumulator( BuildToString(GetAccumulator(), ToString::kThrowOnSymbol)); } ReduceResult MaglevGraphBuilder::VisitToBoolean() { return SetAccumulator(BuildToBoolean(GetAccumulator())); } ReduceResult MaglevGraphBuilder::VisitCreateRegExpLiteral() { // CreateRegExpLiteral <pattern_idx> <literal_idx> <flags> compiler::StringRef pattern = GetRefOperand<String>(0); FeedbackSlot slot = GetSlotOperand(1); uint32_t flags = GetFlag16Operand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; compiler::ProcessedFeedback const& processed_feedback = broker()->GetFeedbackForRegExpLiteral(feedback_source); if (!processed_feedback.IsInsufficient()) { compiler::RegExpBoilerplateDescriptionRef literal = processed_feedback.AsRegExpLiteral().value(); compiler::NativeContextRef native_context = broker()->target_native_context(); compiler::MapRef map = native_context.regexp_function(broker()).initial_map(broker()); return SetAccumulator(BuildInlinedAllocation( CreateRegExpLiteralObject(map, literal), AllocationType::kYoung)); } // Fallback. return SetAccumulator( AddNewNode<CreateRegExpLiteral>({}, pattern, feedback_source, flags)); } ReduceResult MaglevGraphBuilder::VisitCreateArrayLiteral() { compiler::HeapObjectRef constant_elements = GetRefOperand<HeapObject>(0); FeedbackSlot slot_index = GetSlotOperand(1); int bytecode_flags = GetFlag8Operand(2); int literal_flags = interpreter::CreateArrayLiteralFlags::FlagsBits::decode(bytecode_flags); compiler::FeedbackSource feedback_source(feedback(), slot_index); compiler::ProcessedFeedback const& processed_feedback = broker()->GetFeedbackForArrayOrObjectLiteral(feedback_source); if (processed_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForArrayLiteral); } MaybeReduceResult result = TryBuildFastCreateObjectOrArrayLiteral(processed_feedback.AsLiteral()); PROCESS_AND_RETURN_IF_DONE(result, SetAccumulator); if (interpreter::CreateArrayLiteralFlags::FastCloneSupportedBit::decode( bytecode_flags)) { // TODO(victorgomes): CreateShallowArrayLiteral should not need the // boilerplate descriptor. However the current builtin checks that the // feedback exists and fallsback to CreateArrayLiteral if it doesn't. return SetAccumulator(AddNewNode<CreateShallowArrayLiteral>( {}, constant_elements, feedback_source, literal_flags)); } else { return SetAccumulator(AddNewNode<CreateArrayLiteral>( {}, constant_elements, feedback_source, literal_flags)); } } ReduceResult MaglevGraphBuilder::VisitCreateArrayFromIterable() { ValueNode* iterable = GetAccumulator(); return SetAccumulator(BuildCallBuiltinWithTaggedInputs< Builtin::kIterableToListWithSymbolLookup>({iterable})); } ReduceResult MaglevGraphBuilder::VisitCreateEmptyArrayLiteral() { FeedbackSlot slot_index = GetSlotOperand(0); compiler::FeedbackSource feedback_source(feedback(), slot_index); compiler::ProcessedFeedback const& processed_feedback = broker()->GetFeedbackForArrayOrObjectLiteral(feedback_source); if (processed_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForArrayLiteral); } compiler::AllocationSiteRef site = processed_feedback.AsLiteral().value(); broker()->dependencies()->DependOnElementsKind(site); ElementsKind kind = site.GetElementsKind(); compiler::NativeContextRef native_context = broker()->target_native_context(); compiler::MapRef map = native_context.GetInitialJSArrayMap(broker(), kind); // Initial JSArray map shouldn't have any in-object properties. SBXCHECK_EQ(map.GetInObjectProperties(), 0); VirtualObject* array; GET_VALUE_OR_ABORT( array, CreateJSArray(map, map.instance_size(), GetSmiConstant(0))); return SetAccumulator(BuildInlinedAllocation(array, AllocationType::kYoung)); } std::optional<VirtualObject*> MaglevGraphBuilder::TryReadBoilerplateForFastLiteral( compiler::JSObjectRef boilerplate, AllocationType allocation, int max_depth, int* max_properties) { DCHECK_GE(max_depth, 0); DCHECK_GE(*max_properties, 0); if (max_depth == 0) return {}; // Prevent concurrent migrations of boilerplate objects. compiler::JSHeapBroker::BoilerplateMigrationGuardIfNeeded boilerplate_access_guard(broker()); // Now that we hold the migration lock, get the current map. compiler::MapRef boilerplate_map = boilerplate.map(broker()); // Protect against concurrent changes to the boilerplate object by checking // for an identical value at the end of the compilation. broker()->dependencies()->DependOnObjectSlotValue( boilerplate, HeapObject::kMapOffset, boilerplate_map); { compiler::OptionalMapRef current_boilerplate_map = boilerplate.map_direct_read(broker()); if (!current_boilerplate_map.has_value() || !current_boilerplate_map->equals(boilerplate_map)) { // TODO(leszeks): Emit an eager deopt for this case, so that we can // re-learn the boilerplate. This will be easier once we get rid of the // two-pass approach, since we'll be able to create the eager deopt here // and return a ReduceResult::DoneWithAbort(). return {}; } } // Bail out if the boilerplate map has been deprecated. The map could of // course be deprecated at some point after the line below, but it's not a // correctness issue -- it only means the literal won't be created with the // most up to date map(s). if (boilerplate_map.is_deprecated()) return {}; // We currently only support in-object properties. if (boilerplate.map(broker()).elements_kind() == DICTIONARY_ELEMENTS || boilerplate.map(broker()).is_dictionary_map() || !boilerplate.raw_properties_or_hash(broker()).has_value()) { return {}; } { compiler::ObjectRef properties = *boilerplate.raw_properties_or_hash(broker()); bool const empty = properties.IsSmi() || properties.equals(MakeRef( broker(), local_isolate()->factory()->empty_fixed_array())) || properties.equals(MakeRef( broker(), Cast<Object>(local_isolate()->factory()->empty_property_array()))); if (!empty) return {}; } compiler::OptionalFixedArrayBaseRef maybe_elements = boilerplate.elements(broker(), kRelaxedLoad); if (!maybe_elements.has_value()) return {}; compiler::FixedArrayBaseRef boilerplate_elements = maybe_elements.value(); broker()->dependencies()->DependOnObjectSlotValue( boilerplate, JSObject::kElementsOffset, boilerplate_elements); const uint32_t elements_length = boilerplate_elements.length(); VirtualObject* fast_literal; if (boilerplate_map.IsJSArrayMap()) { MaybeReduceResult fast_array = CreateJSArray( boilerplate_map, boilerplate_map.instance_size(), GetConstant(boilerplate.AsJSArray().GetBoilerplateLength(broker()))); CHECK(fast_array.HasValue()); fast_literal = fast_array.value()->Cast<VirtualObject>(); } else { fast_literal = CreateJSObject(boilerplate_map); } int inobject_properties = boilerplate_map.GetInObjectProperties(); // Compute the in-object properties to store first. int index = 0; for (InternalIndex i : InternalIndex::Range(boilerplate_map.NumberOfOwnDescriptors())) { PropertyDetails const property_details = boilerplate_map.GetPropertyDetails(broker(), i); if (property_details.location() != PropertyLocation::kField) continue; DCHECK_EQ(PropertyKind::kData, property_details.kind()); if ((*max_properties)-- == 0) return {}; int offset = boilerplate_map.GetInObjectPropertyOffset(index); #ifdef DEBUG FieldIndex field_index = FieldIndex::ForDetails(*boilerplate_map.object(), property_details); DCHECK(field_index.is_inobject()); DCHECK_EQ(index, field_index.property_index()); DCHECK_EQ(field_index.offset(), offset); #endif // The index is derived from the in-sandbox `NumberOfOwnDescriptors` value, // but the access is out-of-sandbox fast_literal fields. SBXCHECK_LT(index, inobject_properties); // Note: the use of RawInobjectPropertyAt (vs. the higher-level // GetOwnFastConstantDataProperty) here is necessary, since the underlying // value may be `uninitialized`, which the latter explicitly does not // support. compiler::OptionalObjectRef maybe_boilerplate_value = boilerplate.RawInobjectPropertyAt( broker(), FieldIndex::ForInObjectOffset(offset, FieldIndex::kTagged)); if (!maybe_boilerplate_value.has_value()) return {}; // Note: We don't need to take a compilation dependency verifying the value // of `boilerplate_value`, since boilerplate properties are constant after // initialization modulo map migration. We protect against concurrent map // migrations (other than elements kind transition, which don't affect us) // via the boilerplate_migration_access lock. compiler::ObjectRef boilerplate_value = maybe_boilerplate_value.value(); if (IsUninitializedHole(*boilerplate_value.object())) { // It's fine to store the 'uninitialized' Hole into a Smi field since // it will get overwritten anyway. fast_literal->set(offset, GetConstant(boilerplate_value)); } else if (boilerplate_value.IsJSObject()) { compiler::JSObjectRef boilerplate_object = boilerplate_value.AsJSObject(); std::optional<VirtualObject*> maybe_object_value = TryReadBoilerplateForFastLiteral(boilerplate_object, allocation, max_depth - 1, max_properties); if (!maybe_object_value.has_value()) return {}; fast_literal->set(offset, maybe_object_value.value()); } else if (property_details.representation().IsDouble()) { fast_literal->set( offset, CreateHeapNumber(GetFloat64Constant(Float64::FromBits( boilerplate_value.AsHeapNumber().value_as_bits())))); } else { fast_literal->set(offset, GetConstant(boilerplate_value)); } index++; } // Fill inobject_properties with undefined. for (; index < inobject_properties; ++index) { DCHECK(!V8_MAP_PACKING_BOOL); // TODO(wenyuzhao): Fix incorrect MachineType when V8_MAP_PACKING is // enabled. int offset = boilerplate_map.GetInObjectPropertyOffset(index); fast_literal->set(offset, GetRootConstant(RootIndex::kUndefinedValue)); } DCHECK_EQ(JSObject::kElementsOffset, JSArray::kElementsOffset); // Empty or copy-on-write elements just store a constant. compiler::MapRef elements_map = boilerplate_elements.map(broker()); // Protect against concurrent changes to the boilerplate object by checking // for an identical value at the end of the compilation. broker()->dependencies()->DependOnObjectSlotValue( boilerplate_elements, HeapObject::kMapOffset, elements_map); if (boilerplate_elements.length() == 0 || elements_map.IsFixedCowArrayMap(broker())) { if (allocation == AllocationType::kOld && !boilerplate.IsElementsTenured(boilerplate_elements)) { return {}; } fast_literal->set(JSObject::kElementsOffset, GetConstant(boilerplate_elements)); } else { // Compute the elements to store first (might have effects). if (boilerplate_elements.IsFixedDoubleArray()) { int const size = FixedDoubleArray::SizeFor(elements_length); if (size > kMaxRegularHeapObjectSize) return {}; compiler::FixedDoubleArrayRef boilerplate_elements_as_fda = boilerplate_elements.AsFixedDoubleArray(); base::SmallVector<ValueNode*, 16> values; values.reserve(elements_length); for (uint32_t i = 0; i < elements_length; i++) { values.push_back(GetFloat64Constant( boilerplate_elements_as_fda.GetFromImmutableFixedDoubleArray(i))); } fast_literal->set(JSObject::kElementsOffset, CreateFixedDoubleArray(base::VectorOf(values))); } else { int const size = FixedArray::SizeFor(elements_length); if (size > kMaxRegularHeapObjectSize) return {}; base::SmallVector<ValueNode*, 16> values( elements_length, GetRootConstant(RootIndex::kUndefinedValue)); compiler::FixedArrayRef boilerplate_elements_as_fixed_array = boilerplate_elements.AsFixedArray(); for (uint32_t i = 0; i < elements_length; ++i) { if ((*max_properties)-- == 0) return {}; compiler::OptionalObjectRef element_value = boilerplate_elements_as_fixed_array.TryGet(broker(), i); if (!element_value.has_value()) return {}; if (element_value->IsTheHole()) { values[i] = GetConstant(*element_value); } else if (element_value->IsJSObject()) { std::optional<VirtualObject*> object = TryReadBoilerplateForFastLiteral(element_value->AsJSObject(), allocation, max_depth - 1, max_properties); if (!object.has_value()) return {}; values[i] = *object; } else { values[i] = GetConstant(*element_value); } } VirtualObject* elements = CreateFixedArray(base::VectorOf(values)); fast_literal->set(JSObject::kElementsOffset, elements); } } return fast_literal; } VirtualObject* MaglevGraphBuilder::DeepCopyVirtualObject(VirtualObject* old) { VirtualObject* vobject = old->Clone(NewObjectId(), zone()); current_interpreter_frame_.add_object(vobject); old->allocation()->UpdateObject(vobject); return vobject; } VirtualObject* MaglevGraphBuilder::CreateHeapNumber(ValueNode* value) { using Shape = VirtualHeapNumberShape; int slot_count = Shape::header_slot_count; SBXCHECK_EQ(slot_count, 2); compiler::MapRef map = broker()->heap_number_map(); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(offsetof(HeapNumber, value_), value); return vobj; } VirtualObject* MaglevGraphBuilder::CreateConsString(ValueNode* map, ValueNode* length, ValueNode* first, ValueNode* second) { using Shape = VirtualConsStringShape; int slot_count = Shape::header_slot_count; SBXCHECK_EQ(slot_count, 5); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, compiler::OptionalMapRef{}, slot_count); vobj->set(HeapObject::kMapOffset, map); vobj->set(offsetof(ConsString, raw_hash_field_), GetInt32Constant(Name::kEmptyHashField)); vobj->set(offsetof(ConsString, length_), length); vobj->set(offsetof(ConsString, first_), first); vobj->set(offsetof(ConsString, second_), second); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSObject(compiler::MapRef map) { using Shape = VirtualJSObjectShape; DCHECK(!map.is_dictionary_map()); DCHECK(!map.IsInobjectSlackTrackingInProgress()); int slot_count = map.instance_size() / kTaggedSize; SBXCHECK_GE(slot_count, 3); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSObject::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSObject::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); // Initialize all in-object property slots to undefined. if (map.GetInObjectProperties() > 0) { ValueNode* undefined = GetRootConstant(RootIndex::kUndefinedValue); for (int i = 0; i < map.GetInObjectProperties(); i++) { vobj->set(map.GetInObjectPropertyOffset(i), undefined); } } return vobj; } ReduceResult MaglevGraphBuilder::CreateJSArray(compiler::MapRef map, int instance_size, ValueNode* length) { using Shape = VirtualJSArrayShape; int slot_count = instance_size / kTaggedSize; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSArray::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); // Either the value is a Smi already, or we force a conversion to Smi and // cache the value in its alternative representation node. // TODO(454485895): Consider removing this workaround since // HoleyFloat64ToTagged now canonicalizes by default. RETURN_IF_ABORT(GetSmiValue(length)); vobj->set(JSArray::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSArray::kLengthOffset, length); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSStringWrapper(ValueNode* value) { using Shape = VirtualJSPrimitiveWrapperShape; compiler::MapRef map = broker()->target_native_context().string_function(broker()).initial_map( broker()); int slot_count = Shape::header_slot_count; SBXCHECK_EQ(slot_count, 4); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSObject::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSObject::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSPrimitiveWrapper::kValueOffset, value); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSArrayIterator( compiler::MapRef map, ValueNode* iterated_object, IterationKind kind) { using Shape = VirtualJSArrayIteratorShape; int slot_count = Shape::header_slot_count; SBXCHECK_EQ(slot_count, 6); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSArrayIterator::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSArrayIterator::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSArrayIterator::kIteratedObjectOffset, iterated_object); vobj->set(JSArrayIterator::kNextIndexOffset, GetInt32Constant(0)); vobj->set(JSArrayIterator::kKindOffset, GetInt32Constant(static_cast<int>(kind))); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSConstructor( compiler::JSFunctionRef constructor) { DCHECK(constructor.has_initial_map(broker())); using Shape = VirtualJSObjectShape; // TODO(jgruber): SlackTrackingPrediction should store the initial_map. compiler::SlackTrackingPrediction prediction = broker()->dependencies()->DependOnInitialMapInstanceSizePrediction( constructor); compiler::MapRef map = constructor.initial_map(broker()); int slot_count = prediction.instance_size() / kTaggedSize; SBXCHECK_GE(slot_count, 3); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSObject::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSObject::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); if (prediction.inobject_property_count() != 0) { ValueNode* undefined = GetRootConstant(RootIndex::kUndefinedValue); for (int i = 0; i < prediction.inobject_property_count(); i++) { vobj->set(map.GetInObjectPropertyOffset(i), undefined); } } return vobj; } VirtualObject* MaglevGraphBuilder::CreateFixedArray( base::Vector<ValueNode* const> values) { const compiler::MapRef& map = broker()->fixed_array_map(); using Shape = VirtualFixedArrayShape; int length = values.length(); DCHECK_NE(length, 0); // Use kEmptyFixedArray instead. DCHECK_EQ(FixedArray::SizeFor(length) % FieldSizeOf(Shape::kBodyFieldType), 0); DCHECK_EQ(Shape::header_slot_count + length, FixedArray::SizeFor(length) / FieldSizeOf(Shape::kBodyFieldType)); int slot_count = Shape::header_slot_count + length; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); // TODO(jgruber): This is a smi field. Is there some advantage to creating an // int32 constant first? vobj->set(FixedArrayBase::kLengthOffset, GetInt32Constant(length)); for (int i = 0; i < length; i++) { DCHECK_NOT_NULL(values[i]); vobj->set(FixedArray::OffsetOfElementAt(i), values[i]); } return vobj; } VirtualObject* MaglevGraphBuilder::CreateFixedDoubleArray( base::Vector<ValueNode* const> values) { compiler::MapRef map = broker()->fixed_double_array_map(); using T = FixedDoubleArray; using Shape = VirtualFixedDoubleArrayShape; uint32_t length = values.length(); DCHECK_NE(length, 0); // Use kEmptyFixedArray instead. int slot_count = Shape::header_slot_count + length; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); DCHECK_EQ(vobj->size(), T::SizeFor(length)); DCHECK_EQ(Shape::header_slot_count, 2); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(FixedArrayBase::kLengthOffset, GetInt32Constant(length)); for (uint32_t i = 0; i < length; i++) { vobj->set(T::OffsetOfElementAt(i), values[i]); } return vobj; } VirtualObject* MaglevGraphBuilder::CreateContext( compiler::MapRef map, int length, compiler::ScopeInfoRef scope_info, ValueNode* previous_context, std::optional<ValueNode*> extension) { using Shape = VirtualFixedArrayShape; SBXCHECK_GE(length, Context::MIN_CONTEXT_SLOTS); DCHECK_EQ(FixedArray::SizeFor(length) % FieldSizeOf(Shape::kBodyFieldType), 0); DCHECK_EQ(Shape::header_slot_count + length, FixedArray::SizeFor(length) / FieldSizeOf(Shape::kBodyFieldType)); int slot_count = Shape::header_slot_count + length; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(Context::kLengthOffset, GetInt32Constant(length)); vobj->set(Context::OffsetOfElementAt(Context::SCOPE_INFO_INDEX), GetConstant(scope_info)); vobj->set(Context::OffsetOfElementAt(Context::PREVIOUS_INDEX), previous_context); int index = Context::PREVIOUS_INDEX + 1; if (extension.has_value()) { SBXCHECK_GE(length, Context::MIN_CONTEXT_EXTENDED_SLOTS); vobj->set(Context::OffsetOfElementAt(Context::EXTENSION_INDEX), extension.value()); index++; } for (; index < length; index++) { vobj->set(Context::OffsetOfElementAt(index), GetRootConstant(RootIndex::kUndefinedValue)); } EnsureType(vobj, NodeType::kContext); return vobj; } VirtualObject* MaglevGraphBuilder::CreateArgumentsObject( compiler::MapRef map, ValueNode* length, ValueNode* elements, std::optional<ValueNode*> callee) { using Shape = VirtualJSObjectShape; DCHECK_EQ(JSSloppyArgumentsObject::kLengthOffset, JSArray::kLengthOffset); DCHECK_EQ(JSStrictArgumentsObject::kLengthOffset, JSArray::kLengthOffset); int slot_count = map.instance_size() / kTaggedSize; SBXCHECK_EQ(slot_count, callee.has_value() ? 5 : 4); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSArray::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSArray::kElementsOffset, elements); CHECK(length->Is<Int32Constant>() || length->Is<ArgumentsLength>() || length->Is<RestLength>()); vobj->set(JSArray::kLengthOffset, length); if (callee.has_value()) { vobj->set(JSSloppyArgumentsObject::kCalleeOffset, callee.value()); } DCHECK(vobj->map()->IsJSArgumentsObjectMap() || vobj->map()->IsJSArrayMap()); return vobj; } VirtualObject* MaglevGraphBuilder::CreateMappedArgumentsElements( compiler::MapRef map, int mapped_count, ValueNode* context, ValueNode* unmapped_elements) { using Shape = VirtualSloppyArgumentsElementsShape; int slot_count = SloppyArgumentsElements::SizeFor(mapped_count) / kTaggedSize; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(offsetof(SloppyArgumentsElements, length_), GetInt32Constant(mapped_count)); vobj->set(offsetof(SloppyArgumentsElements, context_), context); vobj->set(offsetof(SloppyArgumentsElements, arguments_), unmapped_elements); return vobj; } VirtualObject* MaglevGraphBuilder::CreateRegExpLiteralObject( compiler::MapRef map, compiler::RegExpBoilerplateDescriptionRef literal) { using Shape = VirtualJSRegExpShape; DCHECK_EQ(JSRegExp::Size(), JSRegExp::kLastIndexOffset + kTaggedSize); int slot_count = Shape::header_slot_count + JSRegExp::kInObjectFieldCount; SBXCHECK_EQ(slot_count, 7); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSRegExp::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSRegExp::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSRegExp::kDataOffset, GetTrustedConstant(literal.data(broker()), kRegExpDataIndirectPointerTag)); vobj->set(JSRegExp::kSourceOffset, GetConstant(literal.source(broker()))); vobj->set(JSRegExp::kFlagsOffset, GetInt32Constant(literal.flags())); vobj->set(JSRegExp::kLastIndexOffset, GetInt32Constant(JSRegExp::kInitialLastIndexValue)); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSGeneratorObject( compiler::MapRef map, int instance_size, ValueNode* context, ValueNode* closure, ValueNode* receiver, ValueNode* register_file) { InstanceType instance_type = map.instance_type(); DCHECK(instance_type == JS_GENERATOR_OBJECT_TYPE || instance_type == JS_ASYNC_GENERATOR_OBJECT_TYPE); const bool is_async = instance_type == JS_ASYNC_GENERATOR_OBJECT_TYPE; const vobj::ObjectLayout* object_layout = is_async ? &VirtualJSAsyncGeneratorObjectShape::kObjectLayout : &VirtualJSGeneratorObjectShape::kObjectLayout; int slot_count = instance_size / kTaggedSize; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, object_layout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSGeneratorObject::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSGeneratorObject::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSGeneratorObject::kContextOffset, context); vobj->set(JSGeneratorObject::kFunctionOffset, closure); vobj->set(JSGeneratorObject::kReceiverOffset, receiver); vobj->set(JSGeneratorObject::kInputOrDebugPosOffset, GetRootConstant(RootIndex::kUndefinedValue)); vobj->set(JSGeneratorObject::kResumeModeOffset, GetInt32Constant(JSGeneratorObject::kNext)); vobj->set(JSGeneratorObject::kContinuationOffset, GetInt32Constant(JSGeneratorObject::kGeneratorExecuting)); vobj->set(JSGeneratorObject::kParametersAndRegistersOffset, register_file); if (is_async) { vobj->set(JSAsyncGeneratorObject::kQueueOffset, GetRootConstant(RootIndex::kUndefinedValue)); vobj->set(JSAsyncGeneratorObject::kIsAwaitingOffset, GetInt32Constant(0)); } return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSAsyncFunctionObject( ValueNode* context, ValueNode* closure, ValueNode* receiver, ValueNode* register_file, ValueNode* promise) { compiler::MapRef map = broker()->target_native_context().async_function_object_map(broker()); const vobj::ObjectLayout* object_layout = &VirtualJSAsyncFunctionObjectShape::kObjectLayout; constexpr int slot_count = JSAsyncFunctionObject::kHeaderSize / kTaggedSize; static_assert(slot_count == 13, "If the number of slots in JSAsyncFunctionObject changes, then " "the additional slots need to be initialized below"); VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, object_layout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSAsyncFunctionObject::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSAsyncFunctionObject::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSAsyncFunctionObject::kContextOffset, context); vobj->set(JSAsyncFunctionObject::kFunctionOffset, closure); vobj->set(JSAsyncFunctionObject::kReceiverOffset, receiver); vobj->set(JSAsyncFunctionObject::kInputOrDebugPosOffset, GetRootConstant(RootIndex::kUndefinedValue)); vobj->set(JSAsyncFunctionObject::kResumeModeOffset, GetInt32Constant(JSGeneratorObject::kNext)); vobj->set(JSAsyncFunctionObject::kContinuationOffset, GetInt32Constant(JSGeneratorObject::kGeneratorExecuting)); vobj->set(JSAsyncFunctionObject::kParametersAndRegistersOffset, register_file); vobj->set(JSAsyncFunctionObject::kPromiseOffset, promise); vobj->set(JSAsyncFunctionObject::kAwaitResolveClosureOffset, GetRootConstant(RootIndex::kUndefinedValue)); vobj->set(JSAsyncFunctionObject::kAwaitRejectClosureOffset, GetRootConstant(RootIndex::kUndefinedValue)); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSPromiseObject() { compiler::MapRef promise_map = broker()->target_native_context().promise_function(broker()).initial_map( broker()); int instance_size = promise_map.instance_size(); int slot_count = instance_size / kTaggedSize; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &VirtualJSPromiseObjectShape::kObjectLayout, promise_map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(promise_map)); vobj->set(JSPromise::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSPromise::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSPromise::kReactionsOrResultOffset, GetSmiConstant(0)); static_assert(v8::Promise::kPending == 0); vobj->set(JSPromise::kFlagsOffset, GetSmiConstant(0)); static_assert(JSPromise::kHeaderSize == 5 * kTaggedSize); for (int offset = JSPromise::kHeaderSize; offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) { vobj->set(offset, GetSmiConstant(0)); } return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSIteratorResult(compiler::MapRef map, ValueNode* value, ValueNode* done) { using Shape = VirtualJSIteratorResultShape; static_assert(JSIteratorResult::kSize == 5 * kTaggedSize); int slot_count = Shape::header_slot_count; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSIteratorResult::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSIteratorResult::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSIteratorResult::kValueOffset, value); vobj->set(JSIteratorResult::kDoneOffset, done); return vobj; } VirtualObject* MaglevGraphBuilder::CreateJSStringIterator(compiler::MapRef map, ValueNode* string) { using Shape = VirtualJSStringIteratorShape; static_assert(JSStringIterator::kHeaderSize == 5 * kTaggedSize); int slot_count = Shape::header_slot_count; VirtualObject* vobj = NodeBase::New<VirtualObject>( zone(), 0, NewObjectId(), this, &Shape::kObjectLayout, map, slot_count); vobj->set(HeapObject::kMapOffset, GetConstant(map)); vobj->set(JSStringIterator::kPropertiesOrHashOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSStringIterator::kElementsOffset, GetRootConstant(RootIndex::kEmptyFixedArray)); vobj->set(JSStringIterator::kStringOffset, string); vobj->set(JSStringIterator::kIndexOffset, GetInt32Constant(0)); return vobj; } InlinedAllocation* MaglevGraphBuilder::ExtendOrReallocateCurrentAllocationBlock( AllocationType allocation_type, VirtualObject* vobject) { DCHECK_LE(vobject->size(), kMaxRegularHeapObjectSize); if (!current_allocation_block_ || v8_flags.maglev_allocation_folding == 0 || current_allocation_block_->allocation_type() != allocation_type || !v8_flags.inline_new || is_turbolev()) { current_allocation_block_ = AddNewNodeNoInputConversion<AllocationBlock>({}, allocation_type); } int current_size = current_allocation_block_->size(); if (current_size + vobject->size() > kMaxRegularHeapObjectSize) { current_allocation_block_ = AddNewNodeNoInputConversion<AllocationBlock>({}, allocation_type); } DCHECK_GE(current_size, 0); InlinedAllocation* allocation = AddNewNodeNoInputConversion<InlinedAllocation>( {current_allocation_block_}, vobject); graph()->allocations_escape_map().emplace(allocation, zone()); current_allocation_block_->Add(allocation); vobject->set_allocation(allocation); return allocation; } void MaglevGraphBuilder::ClearCurrentAllocationBlock() { current_allocation_block_ = nullptr; } void MaglevGraphBuilder::AddNonEscapingUses(InlinedAllocation* allocation, int use_count) { if (!v8_flags.maglev_escape_analysis) return; allocation->AddNonEscapingUses(use_count); } void MaglevGraphBuilder::AddDeoptUse(VirtualObject* vobject) { vobject->AddDeoptUse(current_interpreter_frame_.virtual_objects()); } ReduceResult MaglevGraphBuilder::BuildInlinedAllocation( VirtualObject* vobject, AllocationType allocation_type) { current_interpreter_frame_.add_object(vobject); InlinedAllocation* allocation; using ValueAndDesc = std::pair<ValueNode*, vobj::Field>; SmallZoneVector<ValueAndDesc, 8> values(zone()); bool result = vobject->ForEachSlot([&](ValueNode* node, vobj::Field desc) -> bool { CHECK_NE(node, VirtualObject::kUninitializedSlotValue); if (node->Is<VirtualObject>()) { VirtualObject* nested = node->Cast<VirtualObject>(); ReduceResult result = BuildInlinedAllocation(nested, allocation_type); if (result.IsDoneWithAbort()) { return false; } GET_VALUE(node, result); // Update the vobject's slot value. vobject->set(desc.offset, node); } ReduceResult result = ConvertForField(node, desc, allocation_type); if (result.IsDoneWithAbort()) { return false; } GET_VALUE(node, result); values.push_back({node, desc}); return true; }); if (!result) { return ReduceResult::DoneWithAbort(); } allocation = ExtendOrReallocateCurrentAllocationBlock(allocation_type, vobject); AddNonEscapingUses(allocation, static_cast<int>(values.size())); StoreTaggedMode store_mode = vobject->has_static_map() && vobject->map()->IsContextMap() ? StoreTaggedMode::kInitializingToContext : StoreTaggedMode::kInitializing; for (uint32_t i = 0; i < values.size(); i++) { const auto [value, desc] = values[i]; BuildInitializeStore(desc, allocation, allocation_type, value, store_mode); } if (is_loop_effect_tracking()) { loop_effects_->allocations.insert(allocation); } if (v8_flags.maglev_allocation_folding < 2) { ClearCurrentAllocationBlock(); } return allocation; } ValueNode* MaglevGraphBuilder::BuildInlinedArgumentsElements(int start_index, int length) { DCHECK(is_inline()); if (length == 0) { return GetRootConstant(RootIndex::kEmptyFixedArray); } return CreateFixedArray( base::VectorOf(&caller_details_->arguments[start_index + 1], length)); } ValueNode* MaglevGraphBuilder::BuildInlinedUnmappedArgumentsElements( int mapped_count) { int length = argument_count_without_receiver(); if (length == 0) { return GetRootConstant(RootIndex::kEmptyFixedArray); } base::SmallVector<ValueNode*, 16> values( length, GetRootConstant(RootIndex::kTheHoleValue)); for (int i = mapped_count; i < length; i++) { values[i] = caller_details_->arguments[i + 1]; } return CreateFixedArray(base::VectorOf(values)); } template <CreateArgumentsType type> VirtualObject* MaglevGraphBuilder::BuildVirtualArgumentsObject() { switch (type) { case CreateArgumentsType::kMappedArguments: if (parameter_count_without_receiver() == 0) { // If there is no aliasing, the arguments object elements are not // special in any way, we can just return an unmapped backing store. if (is_inline()) { int length = argument_count_without_receiver(); ValueNode* elements = BuildInlinedArgumentsElements(0, length); return CreateArgumentsObject( broker()->target_native_context().sloppy_arguments_map(broker()), GetInt32Constant(length), elements, GetClosure()); } else { ArgumentsLength* length = AddNewNodeNoInputConversion<ArgumentsLength>({}); EnsureType(length, NodeType::kSmi); ValueNode* length_tagged; GET_VALUE(length_tagged, GetTaggedValue(length)); ArgumentsElements* elements = AddNewNodeNoInputConversion<ArgumentsElements>( {length_tagged}, CreateArgumentsType::kUnmappedArguments, parameter_count_without_receiver()); return CreateArgumentsObject( broker()->target_native_context().sloppy_arguments_map(broker()), length, elements, GetClosure()); } } else { // If the parameter count is zero, we should have used the unmapped // backing store. int param_count = parameter_count_without_receiver(); DCHECK_GT(param_count, 0); DCHECK(CanAllocateSloppyArgumentElements()); int param_idx_in_ctxt = compilation_unit_->shared_function_info() .context_parameters_start() + param_count - 1; // The {unmapped_elements} correspond to the extra arguments // (overapplication) that do not need be "mapped" to the actual // arguments. Mapped arguments are accessed via the context, whereas // unmapped arguments are simply accessed via this fixed array. See // SloppyArgumentsElements in src/object/arguments.h. if (is_inline()) { int length = argument_count_without_receiver(); int mapped_count = std::min(param_count, length); ValueNode* unmapped_elements = BuildInlinedUnmappedArgumentsElements(mapped_count); VirtualObject* elements = CreateMappedArgumentsElements( broker()->sloppy_arguments_elements_map(), mapped_count, GetContext(), unmapped_elements); for (int i = 0; i < mapped_count; i++, param_idx_in_ctxt--) { elements->set(SloppyArgumentsElements::OffsetOfElementAt(i), GetInt32Constant(param_idx_in_ctxt)); } return CreateArgumentsObject( broker()->target_native_context().fast_aliased_arguments_map( broker()), GetInt32Constant(length), elements, GetClosure()); } else { ArgumentsLength* length = AddNewNodeNoInputConversion<ArgumentsLength>({}); EnsureType(length, NodeType::kSmi); ValueNode* length_tagged; GET_VALUE(length_tagged, GetTaggedValue(length)); ArgumentsElements* unmapped_elements = AddNewNodeNoInputConversion<ArgumentsElements>( {length_tagged}, CreateArgumentsType::kMappedArguments, param_count); VirtualObject* elements = CreateMappedArgumentsElements( broker()->sloppy_arguments_elements_map(), param_count, GetContext(), unmapped_elements); ValueNode* the_hole_value = GetConstant(broker()->the_hole_value()); for (int i = 0; i < param_count; i++, param_idx_in_ctxt--) { ValueNode* value; ReduceResult result = Select( [&](BranchBuilder& builder) { return BuildBranchIfInt32Compare(builder, Operation::kLessThan, GetInt32Constant(i), length); }, [&] { return GetSmiConstant(param_idx_in_ctxt); }, [&] { return the_hole_value; }); // Cannot bail out, since we're comparing Int32Cosntant with // ArgumentsLength, so no input conversion can go wrong. GET_VALUE(value, result); elements->set(SloppyArgumentsElements::OffsetOfElementAt(i), value); } return CreateArgumentsObject( broker()->target_native_context().fast_aliased_arguments_map( broker()), length, elements, GetClosure()); } } case CreateArgumentsType::kUnmappedArguments: if (is_inline()) { int length = argument_count_without_receiver(); ValueNode* elements = BuildInlinedArgumentsElements(0, length); return CreateArgumentsObject( broker()->target_native_context().strict_arguments_map(broker()), GetInt32Constant(length), elements); } else { ArgumentsLength* length = AddNewNodeNoInputConversion<ArgumentsLength>({}); EnsureType(length, NodeType::kSmi); ValueNode* length_tagged; GET_VALUE(length_tagged, GetTaggedValue(length)); ArgumentsElements* elements = AddNewNodeNoInputConversion<ArgumentsElements>( {length_tagged}, CreateArgumentsType::kUnmappedArguments, parameter_count_without_receiver()); return CreateArgumentsObject( broker()->target_native_context().strict_arguments_map(broker()), length, elements); } case CreateArgumentsType::kRestParameter: if (is_inline()) { int start_index = parameter_count_without_receiver(); int length = std::max(0, argument_count_without_receiver() - start_index); ValueNode* elements = BuildInlinedArgumentsElements(start_index, length); return CreateArgumentsObject( broker()->target_native_context().js_array_packed_elements_map( broker()), GetInt32Constant(length), elements); } else { ArgumentsLength* length = AddNewNodeNoInputConversion<ArgumentsLength>({}); EnsureType(length, NodeType::kSmi); ValueNode* length_tagged; GET_VALUE(length_tagged, GetTaggedValue(length)); ArgumentsElements* elements = AddNewNodeNoInputConversion<ArgumentsElements>( {length_tagged}, CreateArgumentsType::kRestParameter, parameter_count_without_receiver()); RestLength* rest_length = AddNewNodeNoInputConversion<RestLength>( {}, parameter_count_without_receiver()); return CreateArgumentsObject( broker()->target_native_context().js_array_packed_elements_map( broker()), rest_length, elements); } } } template <CreateArgumentsType type> ValueNode* MaglevGraphBuilder::BuildAndAllocateArgumentsObject() { auto arguments = BuildVirtualArgumentsObject<type>(); ValueNode* allocation; GET_VALUE(allocation, BuildInlinedAllocation(arguments, AllocationType::kYoung)); return allocation; } MaybeReduceResult MaglevGraphBuilder::TryBuildFastCreateObjectOrArrayLiteral( const compiler::LiteralFeedback& feedback) { compiler::AllocationSiteRef site = feedback.value(); if (!site.boilerplate(broker()).has_value()) return {}; AllocationType allocation_type = broker()->dependencies()->DependOnPretenureMode(site); // First try to extract out the shape and values of the boilerplate, bailing // out on complex boilerplates. int max_properties = compiler::kMaxFastLiteralProperties; std::optional<VirtualObject*> maybe_value = TryReadBoilerplateForFastLiteral( *site.boilerplate(broker()), allocation_type, compiler::kMaxFastLiteralDepth, &max_properties); if (!maybe_value.has_value()) return {}; // Then, use the collected information to actually create nodes in the graph. // TODO(leszeks): Add support for unwinding graph modifications, so that we // can get rid of this two pass approach. broker()->dependencies()->DependOnElementsKinds(site); return BuildInlinedAllocation(*maybe_value, allocation_type); } ReduceResult MaglevGraphBuilder::VisitCreateObjectLiteral() { compiler::ObjectBoilerplateDescriptionRef boilerplate_desc = GetRefOperand<ObjectBoilerplateDescription>(0); FeedbackSlot slot_index = GetSlotOperand(1); int bytecode_flags = GetFlag8Operand(2); int literal_flags = interpreter::CreateObjectLiteralFlags::FlagsBits::decode(bytecode_flags); compiler::FeedbackSource feedback_source(feedback(), slot_index); compiler::ProcessedFeedback const& processed_feedback = broker()->GetFeedbackForArrayOrObjectLiteral(feedback_source); if (processed_feedback.IsInsufficient()) { return EmitUnconditionalDeopt( DeoptimizeReason::kInsufficientTypeFeedbackForObjectLiteral); } MaybeReduceResult result = TryBuildFastCreateObjectOrArrayLiteral(processed_feedback.AsLiteral()); PROCESS_AND_RETURN_IF_DONE(result, SetAccumulator); if (interpreter::CreateObjectLiteralFlags::FastCloneSupportedBit::decode( bytecode_flags)) { // TODO(victorgomes): CreateShallowObjectLiteral should not need the // boilerplate descriptor. However the current builtin checks that the // feedback exists and fallsback to CreateObjectLiteral if it doesn't. return SetAccumulator(AddNewNode<CreateShallowObjectLiteral>( {}, boilerplate_desc, feedback_source, literal_flags)); } else { return SetAccumulator(AddNewNode<CreateObjectLiteral>( {}, boilerplate_desc, feedback_source, literal_flags)); } } ReduceResult MaglevGraphBuilder::VisitCreateEmptyObjectLiteral() { compiler::NativeContextRef native_context = broker()->target_native_context(); compiler::MapRef map = native_context.object_function(broker()).initial_map(broker()); DCHECK(!map.is_dictionary_map()); DCHECK(!map.IsInobjectSlackTrackingInProgress()); return SetAccumulator( BuildInlinedAllocation(CreateJSObject(map), AllocationType::kYoung)); } ReduceResult MaglevGraphBuilder::VisitCloneObject() { // CloneObject <source_idx> <flags> <feedback_slot> ValueNode* source = LoadRegister(0); ValueNode* flags = GetSmiConstant(interpreter::CreateObjectLiteralFlags::FlagsBits::decode( GetFlag8Operand(1))); FeedbackSlot slot = GetSlotOperand(2); compiler::FeedbackSource feedback_source{feedback(), slot}; return SetAccumulator( BuildCallBuiltinWithTaggedInputs<Builtin::kCloneObjectIC>( {source, flags}, feedback_source)); } ReduceResult MaglevGraphBuilder::VisitGetTemplateObject() { // GetTemplateObject <descriptor_idx> <literal_idx> compiler::SharedFunctionInfoRef shared_function_info = compilation_unit_->shared_function_info(); ValueNode* description = GetConstant(GetRefOperand<HeapObject>(0)); FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; const compiler::ProcessedFeedback& feedback = broker()->GetFeedbackForTemplateObject(feedback_source); if (feedback.IsInsufficient()) { return SetAccumulator(AddNewNode<GetTemplateObject>( {description}, shared_function_info, feedback_source)); } compiler::JSArrayRef template_object = feedback.AsTemplateObject().value(); SetAccumulator(GetConstant(template_object)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateClosure() { compiler::SharedFunctionInfoRef shared_function_info = GetRefOperand<SharedFunctionInfo>(0); compiler::FeedbackCellRef feedback_cell = feedback().GetClosureFeedbackCell( broker(), iterator_.GetFeedbackSlotOperand(1)); uint32_t flags = GetFlag8Operand(2); if (interpreter::CreateClosureFlags::FastNewClosureBit::decode(flags)) { return SetAccumulator(AddNewNode<FastCreateClosure>( {GetContext()}, shared_function_info, feedback_cell)); } else { bool pretenured = interpreter::CreateClosureFlags::PretenuredBit::decode(flags); return SetAccumulator(AddNewNode<CreateClosure>( {GetContext()}, shared_function_info, feedback_cell, pretenured)); } } MaybeReduceResult MaglevGraphBuilder::TryBuildInlinedAllocatedContext( compiler::MapRef map, compiler::ScopeInfoRef scope, int context_length) { const int kContextAllocationLimit = 16; if (context_length > kContextAllocationLimit) return {}; DCHECK_GE(context_length, Context::MIN_CONTEXT_SLOTS); auto context = CreateContext(map, context_length, scope, GetContext()); return BuildInlinedAllocation(context, AllocationType::kYoung); } ReduceResult MaglevGraphBuilder::VisitCreateBlockContext() { // CreateBlockContext <scope_info_idx> compiler::ScopeInfoRef scope_info = GetRefOperand<ScopeInfo>(0); compiler::MapRef map = broker()->target_native_context().block_context_map(broker()); auto done = [&](ValueNode* res) { graph()->record_scope_info(res, scope_info); SetAccumulator(res); }; PROCESS_AND_RETURN_IF_DONE(TryBuildInlinedAllocatedContext( map, scope_info, scope_info.ContextLength()), done); // Fallback. done(BuildCallRuntime(Runtime::kPushBlockContext, {GetConstant(scope_info)}) .value()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateCatchContext() { // CreateCatchContext <exception> <scope_info_idx> ValueNode* exception = LoadRegister(0); compiler::ScopeInfoRef scope_info = GetRefOperand<ScopeInfo>(1); auto context = CreateContext( broker()->target_native_context().catch_context_map(broker()), Context::MIN_CONTEXT_EXTENDED_SLOTS, scope_info, GetContext(), exception); RETURN_IF_ABORT( SetAccumulator(BuildInlinedAllocation(context, AllocationType::kYoung))); graph()->record_scope_info(GetAccumulator(), scope_info); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateFunctionContext() { compiler::ScopeInfoRef info = GetRefOperand<ScopeInfo>(0); uint32_t slot_count = iterator_.GetUnsignedImmediateOperand(1); compiler::MapRef map = broker()->target_native_context().function_context_map(broker()); auto done = [&](ValueNode* res) { graph()->record_scope_info(res, info); SetAccumulator(res); }; PROCESS_AND_RETURN_IF_DONE( TryBuildInlinedAllocatedContext(map, info, slot_count + Context::MIN_CONTEXT_SLOTS), done); // Fallback. done(AddNewNodeNoInputConversion<CreateFunctionContext>( {GetContext()}, info, slot_count, ScopeType::FUNCTION_SCOPE)); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateFunctionContextWithCells() { return VisitCreateFunctionContext(); } ReduceResult MaglevGraphBuilder::VisitCreateEvalContext() { compiler::ScopeInfoRef info = GetRefOperand<ScopeInfo>(0); uint32_t slot_count = iterator_.GetUnsignedImmediateOperand(1); compiler::MapRef map = broker()->target_native_context().eval_context_map(broker()); auto done = [&](ValueNode* res) { graph()->record_scope_info(res, info); SetAccumulator(res); }; PROCESS_AND_RETURN_IF_DONE( TryBuildInlinedAllocatedContext(map, info, slot_count + Context::MIN_CONTEXT_SLOTS), done); if (slot_count <= static_cast<uint32_t>( ConstructorBuiltins::MaximumFunctionContextSlots())) { done(AddNewNodeNoInputConversion<CreateFunctionContext>( {GetContext()}, info, slot_count, ScopeType::EVAL_SCOPE)); } else { done(BuildCallRuntime(Runtime::kNewFunctionContext, {GetConstant(info)}) .value()); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateWithContext() { // CreateWithContext <register> <scope_info_idx> ValueNode* object = LoadRegister(0); compiler::ScopeInfoRef scope_info = GetRefOperand<ScopeInfo>(1); auto context = CreateContext( broker()->target_native_context().with_context_map(broker()), Context::MIN_CONTEXT_EXTENDED_SLOTS, scope_info, GetContext(), object); RETURN_IF_ABORT( SetAccumulator(BuildInlinedAllocation(context, AllocationType::kYoung))); graph()->record_scope_info(GetAccumulator(), scope_info); return ReduceResult::Done(); } bool MaglevGraphBuilder::CanAllocateSloppyArgumentElements() { return SloppyArgumentsElements::SizeFor(parameter_count()) <= kMaxRegularHeapObjectSize; } bool MaglevGraphBuilder::CanAllocateInlinedArgumentElements() { DCHECK(is_inline()); return FixedArray::SizeFor(argument_count_without_receiver()) <= kMaxRegularHeapObjectSize; } ReduceResult MaglevGraphBuilder::VisitCreateMappedArguments() { compiler::SharedFunctionInfoRef shared = compilation_unit_->shared_function_info(); if (!shared.object()->has_duplicate_parameters()) { if (((is_inline() && CanAllocateInlinedArgumentElements()) || (!is_inline() && CanAllocateSloppyArgumentElements()))) { SetAccumulator(BuildAndAllocateArgumentsObject< CreateArgumentsType::kMappedArguments>()); return ReduceResult::Done(); } else if (!is_inline()) { SetAccumulator( BuildCallBuiltin<Builtin::kFastNewSloppyArguments>({GetClosure()})); return ReduceResult::Done(); } } // Generic fallback. SetAccumulator( BuildCallRuntime(Runtime::kNewSloppyArguments, {GetClosure()}).value()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateUnmappedArguments() { if (!is_inline() || CanAllocateInlinedArgumentElements()) { SetAccumulator(BuildAndAllocateArgumentsObject< CreateArgumentsType::kUnmappedArguments>()); return ReduceResult::Done(); } // Generic fallback. SetAccumulator( BuildCallRuntime(Runtime::kNewStrictArguments, {GetClosure()}).value()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitCreateRestParameter() { if (!is_inline() || CanAllocateInlinedArgumentElements()) { SetAccumulator( BuildAndAllocateArgumentsObject<CreateArgumentsType::kRestParameter>()); return ReduceResult::Done(); } // Generic fallback. SetAccumulator( BuildCallRuntime(Runtime::kNewRestParameter, {GetClosure()}).value()); return ReduceResult::Done(); } void MaglevGraphBuilder::PeelLoop() { int loop_header = iterator_.current_offset(); DCHECK(loop_headers_to_peel_.Contains(loop_header)); DCHECK(!in_peeled_iteration()); peeled_iteration_count_ = v8_flags.maglev_optimistic_peeled_loops ? 2 : 1; any_peeled_loop_ = true; allow_loop_peeling_ = false; TRACE(" * Begin loop peeling...."); while (in_peeled_iteration()) { BuildLoopForPeeling(); } // Emit the actual (not peeled) loop if needed. if (loop_header == iterator_.current_offset()) { BuildLoopForPeeling(); } allow_loop_peeling_ = true; } BasicBlock* MaglevGraphBuilder::FinishInlinedBlockForCaller( ControlNode* control_node, ZoneVector<Node*> rem_nodes_in_call_block) { BasicBlock* result = current_block(); reducer_.FlushNodesToBlock(); set_current_block(nullptr); for (Node* n : rem_nodes_in_call_block) { if (n == nullptr) continue; n->set_owner(result); result->nodes().push_back(n); } control_node->set_owner(result); CHECK_NULL(result->control_node()); result->set_control_node(control_node); // Add the final block to the graph. graph_->Add(result); return result; } void MaglevGraphBuilder::BuildLoopForPeeling() { int loop_header = iterator_.current_offset(); DCHECK(loop_headers_to_peel_.Contains(loop_header)); // Since peeled loops do not start with a loop merge state, we need to // explicitly enter e loop effect tracking scope for the peeled iteration. bool track_peeled_effects = v8_flags.maglev_optimistic_peeled_loops && peeled_iteration_count_ == 2; if (track_peeled_effects) { BeginLoopEffects(loop_header); } #ifdef DEBUG bool was_in_peeled_iteration = in_peeled_iteration(); #endif // DEBUG while (iterator_.current_bytecode() != interpreter::Bytecode::kJumpLoop) { local_isolate_->heap()->Safepoint(); // TODO(marja): Simplify this. Figure out when / how stop iterating, instead // of marking the bytecode dead. Remove decremented_predecessor_offsets_ // handling. if (VisitSingleBytecode().IsDoneWithAbort()) { MarkBytecodeDead(); } iterator_.Advance(); } // VisitJumpLoop if (VisitSingleBytecode().IsDoneWithAbort()) { MarkBytecodeDead(); } DCHECK_EQ(was_in_peeled_iteration, in_peeled_iteration()); if (!in_peeled_iteration()) { return; } // In case the peeled iteration was mergeable (see TryMergeLoop) or the // JumpLoop was dead, we are done. if (!current_block()) { decremented_predecessor_offsets_.clear(); KillPeeledLoopTargets(peeled_iteration_count_); peeled_iteration_count_ = 0; if (track_peeled_effects) { EndLoopEffects(loop_header); } return; } peeled_iteration_count_--; // After processing the peeled iteration and reaching the `JumpLoop`, we // re-process the loop body. For this, we need to reset the graph building // state roughly as if we didn't process it yet. // Reset position in exception handler table to before the loop. HandlerTable table(*bytecode().object()); while (next_handler_table_index_ > 0) { next_handler_table_index_--; int start = table.GetRangeStart(next_handler_table_index_); if (start < loop_header) break; } // Re-create catch handler merge states. for (int offset = loop_header; offset <= iterator_.current_offset(); ++offset) { if (auto& merge_state = merge_states_[offset]) { if (merge_state->is_exception_handler()) { merge_state = MergePointInterpreterFrameState::NewForCatchBlock( *compilation_unit_, merge_state->frame_state().liveness(), offset, merge_state->exception_handler_was_used(), merge_state->catch_block_context_register(), graph_); } else { // We only peel innermost loops. DCHECK(!merge_state->is_loop()); merge_state = nullptr; } } new (&jump_targets_[offset]) BasicBlockRef(); } // Reset predecessors as if the loop body had not been visited. for (int offset : decremented_predecessor_offsets_) { DCHECK_GE(offset, loop_header); if (offset <= iterator_.current_offset()) { UpdatePredecessorCount(offset, 1); } } decremented_predecessor_offsets_.clear(); DCHECK(current_block()); // After resetting, the actual loop header always has exactly 2 // predecessors: the two copies of `JumpLoop`. InitializePredecessorCount(loop_header, 2); merge_states_[loop_header] = MergePointInterpreterFrameState::NewForLoop( current_interpreter_frame_, this, *compilation_unit_, loop_header, 2, GetInLivenessFor(loop_header), &bytecode_analysis_.GetLoopInfoFor(loop_header), /* has_been_peeled */ true); BasicBlock* block = FinishBlockNoAbort<Jump>({}, &jump_targets_[loop_header]); // If we ever want more peelings, we should ensure that only the last one // creates a loop header. DCHECK_LE(peeled_iteration_count_, 1); DCHECK_IMPLIES(in_peeled_iteration(), v8_flags.maglev_optimistic_peeled_loops); merge_states_[loop_header]->InitializeLoop( this, *compilation_unit_, current_interpreter_frame_, block, in_peeled_iteration(), loop_effects_); if (track_peeled_effects) { EndLoopEffects(loop_header); } DCHECK_NE(iterator_.current_offset(), loop_header); iterator_.SetOffset(loop_header); } void MaglevGraphBuilder::OsrAnalyzePrequel() { DCHECK_EQ(compilation_unit_->info()->toplevel_compilation_unit(), compilation_unit_); // TODO(olivf) We might want to start collecting known_node_aspects_ here. for (iterator_.SetOffset(0); iterator_.current_offset() != entrypoint_; iterator_.Advance()) { switch (iterator_.current_bytecode()) { case interpreter::Bytecode::kPushContext: { graph()->record_scope_info(GetContext(), {}); // Nothing left to analyze... return; } default: continue; } } } void MaglevGraphBuilder::BeginLoopEffects(int loop_header) { loop_effects_stack_.push_back(zone()->New<LoopEffects>(loop_header, zone())); loop_effects_ = loop_effects_stack_.back(); } void MaglevGraphBuilder::EndLoopEffects(int loop_header) { DCHECK_EQ(loop_effects_, loop_effects_stack_.back()); DCHECK_EQ(loop_effects_->loop_header, loop_header); // TODO(olivf): Update merge states dominated by the loop header with // information we know to be unaffected by the loop. if (merge_states_[loop_header] && merge_states_[loop_header]->is_loop()) { merge_states_[loop_header]->set_loop_effects(loop_effects_); } if (loop_effects_stack_.size() > 1) { LoopEffects* inner_effects = loop_effects_; loop_effects_ = *(loop_effects_stack_.end() - 2); loop_effects_->Merge(inner_effects); } else { loop_effects_ = nullptr; } loop_effects_stack_.pop_back(); } ReduceResult MaglevGraphBuilder::VisitJumpLoop() { const uint32_t relative_jump_bytecode_offset = iterator_.GetUnsignedImmediateOperand(0); const int32_t loop_offset = iterator_.GetImmediateOperand(1); const FeedbackSlot feedback_slot = iterator_.GetSlotOperand(2); int target = iterator_.GetJumpTargetOffset(); if (ShouldEmitInterruptBudgetChecks()) { int reduction = relative_jump_bytecode_offset * v8_flags.osr_from_maglev_interrupt_scale_factor; RETURN_IF_ABORT(AddNewNode<ReduceInterruptBudgetForLoop>( {GetFeedbackCell()}, reduction > 0 ? reduction : 1)); } else { RETURN_IF_ABORT(AddNewNode<HandleNoHeapWritesInterrupt>({})); } if (ShouldEmitOsrInterruptBudgetChecks()) { RETURN_IF_ABORT(AddNewNode<TryOnStackReplacement>( {GetClosure()}, loop_offset, feedback_slot, BytecodeOffset(iterator_.current_offset()), compilation_unit_)); } bool is_peeled_loop = loop_headers_to_peel_.Contains(target); auto FinishLoopBlock = [&]() { return FinishBlockNoAbort<JumpLoop>({}, jump_targets_[target].block_ptr()); }; if (is_peeled_loop && in_peeled_iteration()) { ClobberAccumulator(); if (in_optimistic_peeling_iteration()) { // Let's see if we can finish this loop without peeling it. if (!merge_states_[target]->TryMergeLoop(this, current_interpreter_frame_, FinishLoopBlock)) { merge_states_[target]->MergeDeadLoop(*compilation_unit()); } if (is_loop_effect_tracking_enabled()) { EndLoopEffects(target); } } } else { BasicBlock* block = FinishLoopBlock(); merge_states_[target]->MergeLoop(this, current_interpreter_frame_, block); block->set_predecessor_id(merge_states_[target]->predecessor_count() - 1); if (is_peeled_loop) { DCHECK(!in_peeled_iteration()); } if (is_loop_effect_tracking_enabled()) { EndLoopEffects(target); } } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJump() { BasicBlock* block = FinishBlockNoAbort<Jump>( {}, &jump_targets_[iterator_.GetJumpTargetOffset()]); MergeIntoFrameState(block, iterator_.GetJumpTargetOffset()); DCHECK_EQ(current_block(), nullptr); DCHECK_LT(next_offset(), bytecode().length()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpConstant() { return VisitJump(); } ReduceResult MaglevGraphBuilder::VisitJumpIfNullConstant() { return VisitJumpIfNull(); } ReduceResult MaglevGraphBuilder::VisitJumpIfNotNullConstant() { return VisitJumpIfNotNull(); } ReduceResult MaglevGraphBuilder::VisitJumpIfUndefinedConstant() { return VisitJumpIfUndefined(); } ReduceResult MaglevGraphBuilder::VisitJumpIfNotUndefinedConstant() { return VisitJumpIfNotUndefined(); } ReduceResult MaglevGraphBuilder::VisitJumpIfUndefinedOrNullConstant() { return VisitJumpIfUndefinedOrNull(); } ReduceResult MaglevGraphBuilder::VisitJumpIfTrueConstant() { return VisitJumpIfTrue(); } ReduceResult MaglevGraphBuilder::VisitJumpIfFalseConstant() { return VisitJumpIfFalse(); } ReduceResult MaglevGraphBuilder::VisitJumpIfJSReceiverConstant() { return VisitJumpIfJSReceiver(); } ReduceResult MaglevGraphBuilder::VisitJumpIfForInDoneConstant() { return VisitJumpIfForInDone(); } ReduceResult MaglevGraphBuilder::VisitJumpIfToBooleanTrueConstant() { return VisitJumpIfToBooleanTrue(); } ReduceResult MaglevGraphBuilder::VisitJumpIfToBooleanFalseConstant() { return VisitJumpIfToBooleanFalse(); } void MaglevGraphBuilder::MergeIntoFrameState(BasicBlock* predecessor, int target) { if (merge_states_[target] == nullptr) { bool jumping_to_peeled_iteration = bytecode_analysis().IsLoopHeader(target); DCHECK_EQ(jumping_to_peeled_iteration, loop_headers_to_peel_.Contains(target)); const compiler::BytecodeLivenessState* liveness = GetInLivenessFor(target); if (jumping_to_peeled_iteration) { // The peeled iteration is missing the backedge. DecrementDeadPredecessorAndAccountForPeeling(target); } // If there's no target frame state, allocate a new one. merge_states_[target] = MergePointInterpreterFrameState::New( *compilation_unit_, current_interpreter_frame_, target, predecessor_count(target), predecessor, liveness); } else { // If there already is a frame state, merge. merge_states_[target]->Merge(this, current_interpreter_frame_, predecessor); } } void MaglevGraphBuilder::MergeDeadIntoFrameState(int target) { // If there already is a frame state, merge. if (merge_states_[target]) { DCHECK_EQ(merge_states_[target]->predecessor_count(), predecessor_count(target)); merge_states_[target]->MergeDead(*compilation_unit_); // If this merge is the last one which kills a loop merge, remove that // merge state. if (merge_states_[target]->is_unmerged_unreachable_loop()) { TRACE("! Killing loop merge state at @" << target); merge_states_[target] = nullptr; } } // If there is no merge state yet, don't create one, but just reduce the // number of possible predecessors to zero. DecrementDeadPredecessorAndAccountForPeeling(target); } void MaglevGraphBuilder::MergeDeadLoopIntoFrameState(int target) { // Check if the Loop entry is dead already (e.g. an outer loop from OSR). if (V8_UNLIKELY(!merge_states_[target]) && predecessor_count(target) == 0) { static_assert(kLoopsMustBeEnteredThroughHeader); return; } // If there already is a frame state, merge. if (V8_LIKELY(merge_states_[target])) { DCHECK_EQ(merge_states_[target]->predecessor_count(), predecessor_count(target)); if (is_loop_effect_tracking_enabled() && !merge_states_[target]->is_unmerged_unreachable_loop()) { EndLoopEffects(target); } merge_states_[target]->MergeDeadLoop(*compilation_unit_); } // If there is no merge state yet, don't create one, but just reduce the // number of possible predecessors to zero. DecrementDeadPredecessorAndAccountForPeeling(target); } void MaglevGraphBuilder::MergeIntoInlinedReturnFrameStateForReturn( BasicBlock* predecessor) { int target = inline_exit_offset(); if (merge_states_[target] == nullptr) { // All returns should have the same liveness, which is that only the // accumulator is live. const compiler::BytecodeLivenessState* liveness = GetInLiveness(); DCHECK(liveness->AccumulatorIsLive()); DCHECK_EQ(liveness->live_value_count(), 1); // If there's no target frame state, allocate a new one. merge_states_[target] = MergePointInterpreterFrameState::New( *compilation_unit_, current_interpreter_frame_, target, predecessor_count(target), predecessor, liveness); } else { // Again, all returns should have the same liveness, so double check this. DCHECK(GetInLiveness()->Equals( *merge_states_[target]->frame_state().liveness())); merge_states_[target]->Merge(this, current_interpreter_frame_, predecessor); } } void MaglevGraphBuilder::MergeIntoInlinedReturnFrameStateForSuspendGenerator( BasicBlock* predecessor) { int target = inline_exit_offset(); if (merge_states_[target] == nullptr) { // All returns should have the same liveness, which is that only the // accumulator is live. const compiler::BytecodeLivenessState* liveness = GetInLiveness(); DCHECK(liveness->AccumulatorIsLive()); // SuspendGenerator can have multiple inputs, and thus {liveness} can have // multiple live_value_count. That being said, we don't need to merge any // of them at {target}. We thus create a fake liveness that only contains // the accumulator live, which will be used for the merge at {target}. DCHECK_EQ(iterator_.current_bytecode(), interpreter::Bytecode::kSuspendGenerator); compiler::BytecodeLivenessState* fake_liveness = zone()->New<compiler::BytecodeLivenessState>(liveness->register_count(), zone()); fake_liveness->MarkAccumulatorLive(); liveness = fake_liveness; // If there's no target frame state, allocate a new one. merge_states_[target] = MergePointInterpreterFrameState::New( *compilation_unit_, current_interpreter_frame_, target, predecessor_count(target), predecessor, liveness); } else { // Just checking that the accumulator is currently live, since this the // only thing that we've set in our fake liveness above. DCHECK(GetInLiveness()->AccumulatorIsLive()); DCHECK( merge_states_[target]->frame_state().liveness()->AccumulatorIsLive()); DCHECK_EQ( merge_states_[target]->frame_state().liveness()->live_value_count(), 1); merge_states_[target]->Merge(this, current_interpreter_frame_, predecessor); } } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfReferenceEqual(BranchBuilder& builder, ValueNode* lhs, ValueNode* rhs) { if (RootConstant* root_constant = rhs->TryCast<RootConstant>()) { return builder.Build<BranchIfRootConstant>({lhs}, root_constant->index()); } if (RootConstant* root_constant = lhs->TryCast<RootConstant>()) { return builder.Build<BranchIfRootConstant>({rhs}, root_constant->index()); } if (InlinedAllocation* alloc_lhs = lhs->TryCast<InlinedAllocation>()) { if (InlinedAllocation* alloc_rhs = rhs->TryCast<InlinedAllocation>()) { return builder.FromBool(alloc_lhs == alloc_rhs); } } return builder.Build<BranchIfReferenceEqual>({lhs, rhs}); } void MaglevGraphBuilder::MarkBranchDeadAndJumpIfNeeded(bool is_jump_taken) { int jump_offset = iterator_.GetJumpTargetOffset(); if (is_jump_taken) { BasicBlock* block = FinishBlockNoAbort<Jump>({}, &jump_targets_[jump_offset]); MergeDeadIntoFrameState(next_offset()); MergeIntoFrameState(block, jump_offset); } else { MergeDeadIntoFrameState(jump_offset); } } #ifdef DEBUG namespace { bool IsNumberRootConstant(RootIndex root_index) { switch (root_index) { #define CASE(type, name, label) case RootIndex::k##label: SMI_ROOT_LIST(CASE) STRONG_READ_ONLY_HEAP_NUMBER_ROOT_LIST(CASE) return true; default: return false; } #undef CASE } } // namespace #endif MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfRootConstant( BranchBuilder& builder, ValueNode* node, RootIndex root_index) { // We assume that Maglev never emits a comparison to a root number. DCHECK(!IsNumberRootConstant(root_index)); // If the node we're checking is in the accumulator, swap it in the branch // with the checked value. Cache whether we want to swap, since after we've // swapped the accumulator isn't the original node anymore. BranchBuilder::PatchAccumulatorInBranchScope scope(builder, node, root_index); if (node->is_holey_float64()) { if (root_index == RootIndex::kUndefinedValue) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE return builder.Build<BranchIfFloat64IsUndefinedOrHole>({node}); #else return builder.Build<BranchIfFloat64IsHole>({node}); #endif // V8_ENABLE_UNDEFINED_DOUBLE } return builder.AlwaysFalse(); } if (GetType(node) == NodeType::kNone) { // `node` is dead, it doesn't matter what we return. // TODO(428667907): Ideally we would BuildAbort here. return builder.AlwaysFalse(); } if (CheckType(node, NodeType::kNumber)) { return builder.AlwaysFalse(); } CHECK(node->is_tagged()); if (root_index != RootIndex::kTrueValue && root_index != RootIndex::kFalseValue && CheckType(node, NodeType::kBoolean)) { return builder.AlwaysFalse(); } while (LogicalNot* logical_not = node->TryCast<LogicalNot>()) { // Bypassing logical not(s) on the input and swapping true/false // destinations. node = logical_not->ValueInput().node(); builder.SwapTargets(); } if (RootConstant* constant = node->TryCast<RootConstant>()) { return builder.FromBool(constant->index() == root_index); } if (root_index == RootIndex::kUndefinedValue) { if (Constant* constant = node->TryCast<Constant>()) { return builder.FromBool(constant->object().IsUndefined()); } } if (root_index != RootIndex::kTrueValue && root_index != RootIndex::kFalseValue) { return builder.Build<BranchIfRootConstant>({node}, root_index); } if (root_index == RootIndex::kFalseValue) { builder.SwapTargets(); } switch (node->opcode()) { case Opcode::kTaggedEqual: return BuildBranchIfReferenceEqual( builder, node->Cast<TaggedEqual>()->LeftInput().node(), node->Cast<TaggedEqual>()->RightInput().node()); case Opcode::kTaggedNotEqual: // Swapped true and false targets. builder.SwapTargets(); return BuildBranchIfReferenceEqual( builder, node->Cast<TaggedNotEqual>()->LeftInput().node(), node->Cast<TaggedNotEqual>()->RightInput().node()); case Opcode::kInt32Compare: return builder.Build<BranchIfInt32Compare>( {node->Cast<Int32Compare>()->LeftInput().node(), node->Cast<Int32Compare>()->RightInput().node()}, node->Cast<Int32Compare>()->operation()); case Opcode::kFloat64Compare: return builder.Build<BranchIfFloat64Compare>( {node->Cast<Float64Compare>()->LeftInput().node(), node->Cast<Float64Compare>()->RightInput().node()}, node->Cast<Float64Compare>()->operation()); case Opcode::kInt32ToBoolean: if (node->Cast<Int32ToBoolean>()->flip()) { builder.SwapTargets(); } return builder.Build<BranchIfInt32ToBooleanTrue>( {node->Cast<Int32ToBoolean>()->ValueInput().node()}); case Opcode::kIntPtrToBoolean: if (node->Cast<IntPtrToBoolean>()->flip()) { builder.SwapTargets(); } return builder.Build<BranchIfIntPtrToBooleanTrue>( {node->Cast<IntPtrToBoolean>()->ValueInput().node()}); case Opcode::kFloat64ToBoolean: if (node->Cast<Float64ToBoolean>()->flip()) { builder.SwapTargets(); } return builder.Build<BranchIfFloat64ToBooleanTrue>( {node->Cast<Float64ToBoolean>()->ValueInput().node()}); case Opcode::kTestUndetectable: return builder.Build<BranchIfUndetectable>( {node->Cast<TestUndetectable>()->ValueInput().node()}, node->Cast<TestUndetectable>()->check_type()); #ifdef V8_ENABLE_UNDEFINED_DOUBLE case Opcode::kHoleyFloat64IsUndefinedOrHole: return builder.Build<BranchIfFloat64IsUndefinedOrHole>( {node->Cast<HoleyFloat64IsUndefinedOrHole>()->ValueInput().node()}); #else case Opcode::kHoleyFloat64IsHole: return builder.Build<BranchIfFloat64IsHole>( {node->Cast<HoleyFloat64IsHole>()->ValueInput().node()}); #endif // V8_ENABLE_UNDEFINED_DOUBLE default: return builder.Build<BranchIfRootConstant>({node}, RootIndex::kTrueValue); } } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfTrue( BranchBuilder& builder, ValueNode* node) { builder.SetBranchSpecializationMode(BranchSpecializationMode::kAlwaysBoolean); return BuildBranchIfRootConstant(builder, node, RootIndex::kTrueValue); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfNull( BranchBuilder& builder, ValueNode* node) { return BuildBranchIfRootConstant(builder, node, RootIndex::kNullValue); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfUndefined( BranchBuilder& builder, ValueNode* node) { return BuildBranchIfRootConstant(builder, node, RootIndex::kUndefinedValue); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfUndefinedOrNull(BranchBuilder& builder, ValueNode* node) { compiler::OptionalHeapObjectRef maybe_constant = TryGetConstant<HeapObject>(node); if (maybe_constant.has_value()) { return builder.FromBool(maybe_constant->IsNullOrUndefined()); } if (!node->is_tagged()) { if (node->is_holey_float64()) { #ifdef V8_ENABLE_UNDEFINED_DOUBLE return BuildBranchIfFloat64IsUndefinedOrHole(builder, node); #else return BuildBranchIfFloat64IsHole(builder, node); #endif // V8_ENABLE_UNDEFINED_DOUBLE } return builder.AlwaysFalse(); } if (HasDisjointType(node, NodeType::kOddball)) { return builder.AlwaysFalse(); } return builder.Build<BranchIfUndefinedOrNull>({node}); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfToBooleanTrue( BranchBuilder& builder, ValueNode* node) { // If this is a known boolean, use the non-ToBoolean version. if (CheckType(node, NodeType::kBoolean)) { return BuildBranchIfTrue(builder, node); } // There shouldn't be any LogicalNots here, for swapping true/false, since // these are known to be boolean and should have gone throught the // non-ToBoolean path. DCHECK(!node->Is<LogicalNot>()); bool known_to_boolean_value = false; bool direction_is_true = true; if (IsConstantNode(node->opcode())) { known_to_boolean_value = true; direction_is_true = FromConstantToBool(local_isolate(), node); } else { // TODO(victorgomes): Unify this with TestUndetectable? // JSReceivers are true iff they are not marked as undetectable. Check if // all maps have the same detectability, and if yes, the boolean value is // known. if (NodeInfo* node_info = known_node_aspects().TryGetInfoFor(node)) { if (node_info->type() == NodeType::kNone) { // `node` is dead, it doesn't matter what we return. // TODO(428667907): Ideally we would BuildAbort here. return builder.AlwaysFalse(); } if (NodeTypeIs(node_info->type(), NodeType::kJSReceiver) && node_info->possible_maps_are_known()) { bool all_detectable = true; bool all_undetectable = true; for (compiler::MapRef map : node_info->possible_maps()) { bool is_undetectable = map.is_undetectable(); all_detectable &= !is_undetectable; all_undetectable &= is_undetectable; } if (all_detectable || all_undetectable) { known_to_boolean_value = true; direction_is_true = all_detectable; } } } else if (auto alloc = node->TryCast<InlinedAllocation>()) { if (alloc->object()->has_static_map()) { if (alloc->object()->map()->IsJSReceiverMap()) { known_to_boolean_value = true; direction_is_true = !alloc->object()->map()->is_undetectable(); } } } } if (known_to_boolean_value) { return builder.FromBool(direction_is_true); } switch (node->value_representation()) { // The ToBoolean of both the_hole and NaN is false, so we can use the // same operation for HoleyFloat64 and Float64. case ValueRepresentation::kFloat64: return BuildBranchIfFloat64ToBooleanTrue(builder, node); case ValueRepresentation::kHoleyFloat64: return BuildBranchIfHoleyFloat64ToBooleanTrue(builder, node); case ValueRepresentation::kUint32: // Uint32 has the same logic as Int32 when converting ToBoolean, namely // comparison against zero, so we can cast it and ignore the signedness. node = AddNewNodeNoInputConversion<TruncateUint32ToInt32>({node}); [[fallthrough]]; case ValueRepresentation::kInt32: return BuildBranchIfInt32ToBooleanTrue(builder, node); case ValueRepresentation::kIntPtr: return BuildBranchIfIntPtrToBooleanTrue(builder, node); case ValueRepresentation::kTagged: break; case ValueRepresentation::kRawPtr: case ValueRepresentation::kNone: UNREACHABLE(); } NodeInfo* node_info = known_node_aspects().TryGetInfoFor(node); if (node_info) { if (ValueNode* as_int32 = node_info->alternative().int32()) { return BuildBranchIfInt32ToBooleanTrue(builder, as_int32); } if (ValueNode* as_float64 = node_info->alternative().float64()) { return BuildBranchIfFloat64ToBooleanTrue(builder, as_float64); } if (ValueNode* as_holey_float64 = node_info->alternative().holey_float64()) { return BuildBranchIfHoleyFloat64ToBooleanTrue(builder, as_holey_float64); } } NodeType old_type; if (CheckType(node, NodeType::kBoolean, &old_type)) { return builder.Build<BranchIfRootConstant>({node}, RootIndex::kTrueValue); } if (CheckType(node, NodeType::kSmi)) { builder.SwapTargets(); return builder.Build<BranchIfReferenceEqual>({node, GetSmiConstant(0)}); } if (CheckType(node, NodeType::kString)) { builder.SwapTargets(); return builder.Build<BranchIfRootConstant>({node}, RootIndex::kempty_string); } // TODO(verwaest): Number or oddball. return builder.Build<BranchIfToBooleanTrue>({node}, GetCheckType(old_type, node)); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfInt32ToBooleanTrue(BranchBuilder& builder, ValueNode* node) { // TODO(victorgomes): Optimize. return builder.Build<BranchIfInt32ToBooleanTrue>({node}); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfIntPtrToBooleanTrue(BranchBuilder& builder, ValueNode* node) { // TODO(victorgomes): Optimize. return builder.Build<BranchIfIntPtrToBooleanTrue>({node}); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfFloat64ToBooleanTrue(BranchBuilder& builder, ValueNode* node) { // TODO(victorgomes): Optimize. return builder.Build<BranchIfFloat64ToBooleanTrue>({node}); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfHoleyFloat64ToBooleanTrue( BranchBuilder& builder, ValueNode* node) { // TODO(victorgomes): Optimize. return builder.Build<BranchIfHoleyFloat64ToBooleanTrue>({node}); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfFloat64IsHole( BranchBuilder& builder, ValueNode* node) { // TODO(victorgomes): Optimize. return builder.Build<BranchIfFloat64IsHole>({node}); } #ifdef V8_ENABLE_UNDEFINED_DOUBLE MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfFloat64IsUndefinedOrHole( BranchBuilder& builder, ValueNode* node) { // TODO(victorgomes): Optimize. return builder.Build<BranchIfFloat64IsUndefinedOrHole>({node}); } #endif // V8_ENABLE_UNDEFINED_DOUBLE ReduceResult MaglevGraphBuilder::VisitJumpIfToBooleanTrue() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfTrue); BuildBranchIfToBooleanTrue(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfToBooleanFalse() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfFalse); BuildBranchIfToBooleanTrue(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfTrue() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfTrue); BuildBranchIfTrue(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfFalse() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfFalse); BuildBranchIfTrue(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfNull() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfTrue); BuildBranchIfNull(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfNotNull() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfFalse); BuildBranchIfNull(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfUndefined() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfTrue); BuildBranchIfUndefined(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfNotUndefined() { auto branch_builder = CreateBranchBuilder(BranchType::kBranchIfFalse); BuildBranchIfUndefined(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfUndefinedOrNull() { auto branch_builder = CreateBranchBuilder(); BuildBranchIfUndefinedOrNull(branch_builder, GetAccumulator()); return ReduceResult::Done(); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfJSReceiver( BranchBuilder& builder, ValueNode* value) { if (!value->is_tagged() && !value->is_holey_float64()) { return builder.AlwaysFalse(); } if (CheckType(value, NodeType::kJSReceiver)) { return builder.AlwaysTrue(); } else if (HasDisjointType(value, NodeType::kJSReceiver)) { return builder.AlwaysFalse(); } return builder.Build<BranchIfJSReceiver>({value}); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfInt32Compare( BranchBuilder& builder, Operation op, ValueNode* lhs, ValueNode* rhs) { auto lhs_const = TryGetInt32Constant(lhs); if (lhs_const) { auto rhs_const = TryGetInt32Constant(rhs); if (rhs_const) { return builder.FromBool( CompareInt32(lhs_const.value(), rhs_const.value(), op)); } } return builder.Build<BranchIfInt32Compare>({lhs, rhs}, op); } MaglevGraphBuilder::BranchResult MaglevGraphBuilder::BuildBranchIfUint32Compare( BranchBuilder& builder, Operation op, ValueNode* lhs, ValueNode* rhs) { auto lhs_const = TryGetUint32Constant(lhs); if (lhs_const) { auto rhs_const = TryGetUint32Constant(rhs); if (rhs_const) { return builder.FromBool( CompareUint32(lhs_const.value(), rhs_const.value(), op)); } } return builder.Build<BranchIfUint32Compare>({lhs, rhs}, op); } ReduceResult MaglevGraphBuilder::VisitJumpIfJSReceiver() { auto branch_builder = CreateBranchBuilder(); BuildBranchIfJSReceiver(branch_builder, GetAccumulator()); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitJumpIfForInDone() { // JumpIfForInDone <target> <index> <cache_length> ValueNode* index = LoadRegister(1); ValueNode* cache_length = LoadRegister(2); auto branch_builder = CreateBranchBuilder(); BuildBranchIfInt32Compare(branch_builder, Operation::kEqual, index, cache_length); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitSwitchOnSmiNoFeedback() { // SwitchOnSmiNoFeedback <table_start> <table_length> <case_value_base> interpreter::JumpTableTargetOffsets offsets = iterator_.GetJumpTableTargetOffsets(); if (offsets.size() == 0) return ReduceResult::Done(); int case_value_base = (*offsets.begin()).case_value; BasicBlockRef* targets = zone()->AllocateArray<BasicBlockRef>(offsets.size()); for (interpreter::JumpTableTargetOffset offset : offsets) { BasicBlockRef* ref = &targets[offset.case_value - case_value_base]; new (ref) BasicBlockRef(&jump_targets_[offset.target_offset]); } ValueNode* case_value = GetAccumulator(); BasicBlock* block; GET_BLOCK_OR_ABORT(block, FinishBlock<Switch>({case_value}, case_value_base, targets, offsets.size(), &jump_targets_[next_offset()])); for (interpreter::JumpTableTargetOffset offset : offsets) { MergeIntoFrameState(block, offset.target_offset); } StartFallthroughBlock(next_offset(), block); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitForInEnumerate() { // ForInEnumerate <receiver> ValueNode* receiver = LoadRegister(0); // Pass receiver to ForInPrepare. current_for_in_state.receiver = receiver; return SetAccumulator( BuildCallBuiltinWithTaggedInputs<Builtin::kForInEnumerate>({receiver})); } ReduceResult MaglevGraphBuilder::VisitForInPrepare() { // ForInPrepare <cache_info_triple> ValueNode* enumerator = GetAccumulator(); // Catch the receiver value passed from ForInEnumerate. ValueNode* receiver = current_for_in_state.receiver; FeedbackSlot slot = GetSlotOperand(1); compiler::FeedbackSource feedback_source{feedback(), slot}; // TODO(v8:7700): Use feedback and create fast path. ValueNode* context = GetContext(); interpreter::Register cache_type_reg = iterator_.GetRegisterOperand(0); interpreter::Register cache_array_reg{cache_type_reg.index() + 1}; interpreter::Register cache_length_reg{cache_type_reg.index() + 2}; ForInHint hint = broker()->GetFeedbackForForIn(feedback_source); current_for_in_state = ForInState(); switch (hint) { case ForInHint::kNone: case ForInHint::kEnumCacheKeysAndIndices: case ForInHint::kEnumCacheKeys: { // Check that the {enumerator} is a Map. // The direct IsMap check requires reading of an instance type, so in // order to avoid additional load we compare the {enumerator} against // receiver's Map instead (by definition, the {enumerator} is either // the receiver's Map or a FixedArray). ValueNode* receiver_map; GET_VALUE_OR_ABORT( receiver_map, BuildLoadTaggedField(receiver, HeapObject::kMapOffset)); RETURN_IF_ABORT(AddNewNode<CheckDynamicValue>( {receiver_map, enumerator}, DeoptimizeReason::kWrongMapDynamic)); ValueNode* descriptor_array; GET_VALUE_OR_ABORT( descriptor_array, BuildLoadTaggedField(enumerator, Map::kInstanceDescriptorsOffset)); ValueNode* enum_cache; GET_VALUE(enum_cache, BuildLoadTaggedField(descriptor_array, DescriptorArray::kEnumCacheOffset)); ValueNode* cache_array; GET_VALUE(cache_array, BuildLoadTaggedField(enum_cache, offsetof(EnumCache, keys_))); ValueNode* cache_length; GET_VALUE_OR_ABORT(cache_length, AddNewNode<LoadEnumCacheLength>({enumerator})); if (hint == ForInHint::kEnumCacheKeysAndIndices) { ValueNode* cache_indices; GET_VALUE( cache_indices, BuildLoadTaggedField(enum_cache, offsetof(EnumCache, indices_))); current_for_in_state.enum_cache_indices = cache_indices; RETURN_IF_ABORT(AddNewNode<CheckCacheIndicesNotCleared>( {cache_indices, cache_length})); } else { current_for_in_state.enum_cache_indices = nullptr; } MoveNodeBetweenRegisters(interpreter::Register::virtual_accumulator(), cache_type_reg); StoreRegister(cache_array_reg, cache_array); StoreRegister(cache_length_reg, cache_length); break; } case ForInHint::kAny: { // The result of the bytecode is output in registers |cache_info_triple| // to |cache_info_triple + 2|, with the registers holding cache_type, // cache_array, and cache_length respectively. // // We set the cache type first (to the accumulator value), and write // the other two with a ForInPrepare builtin call. This can lazy deopt, // which will write to cache_array and cache_length, with cache_type // already set on the translation frame. // This move needs to happen before ForInPrepare to avoid lazy deopt // extending the lifetime of the {cache_type} register. MoveNodeBetweenRegisters(interpreter::Register::virtual_accumulator(), cache_type_reg); ForInPrepare* result; GET_VALUE_OR_ABORT(result, AddNewNode<ForInPrepare>({context, enumerator}, feedback_source)); StoreRegisterPair({cache_array_reg, cache_length_reg}, result); // Force a conversion to Int32 for the cache length value. RETURN_IF_ABORT(EnsureInt32(cache_length_reg)); break; } } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitForInNext() { // ForInNext <receiver> <index> <cache_info_pair> ValueNode* receiver = LoadRegister(0); interpreter::Register cache_type_reg, cache_array_reg; std::tie(cache_type_reg, cache_array_reg) = iterator_.GetRegisterPairOperand(2); ValueNode* cache_type = current_interpreter_frame_.get(cache_type_reg); ValueNode* cache_array = current_interpreter_frame_.get(cache_array_reg); FeedbackSlot slot = GetSlotOperand(3); compiler::FeedbackSource feedback_source{feedback(), slot}; ForInHint hint = broker()->GetFeedbackForForIn(feedback_source); switch (hint) { case ForInHint::kNone: case ForInHint::kEnumCacheKeysAndIndices: case ForInHint::kEnumCacheKeys: { ValueNode* index = LoadRegister(1); // Ensure that the expected map still matches that of the {receiver}. ValueNode* receiver_map; GET_VALUE_OR_ABORT( receiver_map, BuildLoadTaggedField(receiver, HeapObject::kMapOffset)); RETURN_IF_ABORT(AddNewNode<CheckDynamicValue>( {receiver_map, cache_type}, DeoptimizeReason::kWrongMapDynamic)); ValueNode* key; GET_VALUE_OR_ABORT( key, BuildLoadFixedArrayElement(cache_array, index, LoadType::kInternalizedString)); SetAccumulator(key); current_for_in_state.receiver = receiver; if (ToObject* to_object = current_for_in_state.receiver->TryCast<ToObject>()) { current_for_in_state.receiver = to_object->ValueInput().node(); } current_for_in_state.receiver_needs_map_check = false; current_for_in_state.cache_type = cache_type; current_for_in_state.key = key; if (hint == ForInHint::kEnumCacheKeysAndIndices) { current_for_in_state.index = index; } // We know that the enum cache entry is not undefined, so skip over the // next JumpIfUndefined. DCHECK(iterator_.next_bytecode() == interpreter::Bytecode::kJumpIfUndefined || iterator_.next_bytecode() == interpreter::Bytecode::kJumpIfUndefinedConstant); iterator_.Advance(); MergeDeadIntoFrameState(iterator_.GetJumpTargetOffset()); break; } case ForInHint::kAny: { ValueNode* index = LoadRegister(1); ValueNode* context = GetContext(); return SetAccumulator(AddNewNode<ForInNext>( {context, receiver, cache_array, cache_type, index}, feedback_source)); }; } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitForInStep() { interpreter::Register index_reg = iterator_.GetRegisterOperand(0); ValueNode* index = current_interpreter_frame_.get(index_reg); ValueNode* value; GET_VALUE_OR_ABORT(value, AddNewNode<Int32NodeFor<Operation::kIncrement>>({index})); StoreRegister(index_reg, value); if (!in_peeled_iteration()) { // With loop peeling, only the `ForInStep` in the non-peeled loop body marks // the end of for-in. current_for_in_state = ForInState(); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitSetPendingMessage() { ValueNode* message = GetAccumulator(); return SetAccumulator(AddNewNode<SetPendingMessage>({message})); } ReduceResult MaglevGraphBuilder::VisitThrow() { ValueNode* exception = GetAccumulator(); return BuildThrow(Throw::kThrow, exception); } ReduceResult MaglevGraphBuilder::VisitReThrow() { ValueNode* exception = GetAccumulator(); return BuildThrow(Throw::kReThrow, exception); } ReduceResult MaglevGraphBuilder::VisitReturn() { // See also: InterpreterAssembler::UpdateInterruptBudgetOnReturn. const uint32_t relative_jump_bytecode_offset = iterator_.current_offset(); if (ShouldEmitInterruptBudgetChecks() && relative_jump_bytecode_offset > 0) { RETURN_IF_ABORT(AddNewNode<ReduceInterruptBudgetForReturn>( {GetFeedbackCell()}, relative_jump_bytecode_offset)); } if (is_inline()) { // All inlined function returns instead jump to one past the end of the // bytecode, where we'll later create a final basic block which resumes // execution of the caller. If there is only one return, at the end of the // function, we can elide this jump and just continue in the same basic // block. if (iterator_.next_offset() != inline_exit_offset() || predecessor_count(inline_exit_offset()) > 1) { BasicBlock* block = FinishBlockNoAbort<Jump>({}, &jump_targets_[inline_exit_offset()]); // The context is dead by now, set it to optimized out to avoid creating // unnecessary phis. SetContext(GetRootConstant(RootIndex::kOptimizedOut)); MergeIntoInlinedReturnFrameStateForReturn(block); } return ReduceResult::Done(); } return FinishBlock<Return>({GetAccumulator()}).has_value() ? ReduceResult::Done() : ReduceResult::DoneWithAbort(); } ReduceResult MaglevGraphBuilder::VisitThrowReferenceErrorIfHole() { // ThrowReferenceErrorIfHole <variable_name> compiler::NameRef name = GetRefOperand<Name>(0); ValueNode* value = GetAccumulator(); switch (value->IsTheHole()) { case Tribool::kTrue: return BuildThrow(Throw::kThrowAccessedUninitializedVariable, GetConstant(name)); case Tribool::kFalse: return ReduceResult::Done(); case Tribool::kMaybe: DCHECK(value->is_tagged()); return AddNewNode<ThrowReferenceErrorIfHole>({value}, name); } } ReduceResult MaglevGraphBuilder::VisitThrowSuperNotCalledIfHole() { // ThrowSuperNotCalledIfHole ValueNode* value = GetAccumulator(); if (CheckType(value, NodeType::kJSReceiver)) return ReduceResult::Done(); switch (value->IsTheHole()) { case Tribool::kTrue: return BuildThrow(Throw::kThrowSuperNotCalled); case Tribool::kFalse: return ReduceResult::Done(); case Tribool::kMaybe: DCHECK(value->is_tagged()); return AddNewNode<ThrowSuperNotCalledIfHole>({value}); } } ReduceResult MaglevGraphBuilder::VisitThrowSuperAlreadyCalledIfNotHole() { // ThrowSuperAlreadyCalledIfNotHole ValueNode* value = GetAccumulator(); switch (value->IsTheHole()) { case Tribool::kTrue: return ReduceResult::Done(); case Tribool::kFalse: return BuildThrow(Throw::kThrowSuperAlreadyCalledError); case Tribool::kMaybe: DCHECK(value->is_tagged()); return AddNewNode<ThrowSuperAlreadyCalledIfNotHole>({value}); } } ReduceResult MaglevGraphBuilder::VisitThrowIfNotSuperConstructor() { // ThrowIfNotSuperConstructor <constructor> ValueNode* constructor = LoadRegister(0); ValueNode* function = GetClosure(); if (auto const_constructor = TryGetConstant<HeapObject>(constructor)) { if (const_constructor->map(broker()).is_constructor()) { return ReduceResult::Done(); } } return AddNewNode<ThrowIfNotSuperConstructor>({constructor, function}); } ReduceResult MaglevGraphBuilder::VisitSwitchOnGeneratorState() { // SwitchOnGeneratorState <generator> <table_start> <table_length> // It should be the first bytecode in the bytecode array. DCHECK_EQ(iterator_.current_offset(), 0); int generator_prologue_block_offset = 1; DCHECK_LT(generator_prologue_block_offset, next_offset()); interpreter::JumpTableTargetOffsets offsets = iterator_.GetJumpTableTargetOffsets(); // If there are no jump offsets, then this generator is not resumable, which // means we can skip checking for it and switching on its state. if (offsets.size() == 0) return ReduceResult::Done(); if (is_inline()) { // When inlining a generator function, we're really just inlining the // initialization part of the generator rather than the whole // yielding/resuming part. For instance, consider // // function* MyGen() { ... } // // ... // let gen = MyGen(); // <--- inlining MyGen here // gen.next(); // // In this example, the only call to `MyGen` that can be inlined is when // creating the generator. The parts that access the generator call // `.next()` and this is not something that we inline. Hence, when we inline // a generator function, the only non-dead part of what we inline is the // initialization of the generator object. // // We thus mark every resume point (the content of {offsets}) as dead, and // don't bother emitting the initial generator switch: we just fallthrough // into the next block, which is where the initialization of the generator // happens. for (interpreter::JumpTableTargetOffset offset : offsets) { MergeDeadIntoFrameState(offset.target_offset); } return ReduceResult::Done(); } graph()->set_has_resumable_generator(); // We create an initial block that checks if the generator is undefined. ValueNode* maybe_generator = LoadRegister(0); // Neither the true nor the false path jump over any bytecode BasicBlock* block_is_generator_undefined; GET_BLOCK_OR_ABORT(block_is_generator_undefined, FinishBlock<BranchIfRootConstant>( {maybe_generator}, RootIndex::kUndefinedValue, &jump_targets_[next_offset()], &jump_targets_[generator_prologue_block_offset])); MergeIntoFrameState(block_is_generator_undefined, next_offset()); // We create the generator prologue block. StartNewBlock(generator_prologue_block_offset, block_is_generator_undefined); // Generator prologue. ValueNode* generator = maybe_generator; ValueNode* state; GET_VALUE_OR_ABORT( state, BuildLoadTaggedField(generator, JSGeneratorObject::kContinuationOffset)); ValueNode* new_state = GetSmiConstant(JSGeneratorObject::kGeneratorExecuting); RETURN_IF_ABORT(BuildStoreTaggedFieldNoWriteBarrier( generator, new_state, JSGeneratorObject::kContinuationOffset, StoreTaggedMode::kDefault)); ValueNode* context; GET_VALUE_OR_ABORT(context, BuildLoadTaggedField( generator, JSGeneratorObject::kContextOffset, LoadType::kContext)); graph()->record_scope_info(context, {}); SetContext(context); // Guarantee that we have something in the accumulator. MoveNodeBetweenRegisters(iterator_.GetRegisterOperand(0), interpreter::Register::virtual_accumulator()); // Switch on generator state. int case_value_base = (*offsets.begin()).case_value; BasicBlockRef* targets = zone()->AllocateArray<BasicBlockRef>(offsets.size()); for (interpreter::JumpTableTargetOffset offset : offsets) { BasicBlockRef* ref = &targets[offset.case_value - case_value_base]; new (ref) BasicBlockRef(&jump_targets_[offset.target_offset]); } ValueNode* case_value; if (state->is_tagged()) { GET_VALUE_OR_ABORT(case_value, AddNewNode<UnsafeSmiUntag>({state})); } else { case_value = state; } BasicBlock* generator_prologue_block; GET_BLOCK_OR_ABORT(generator_prologue_block, FinishBlock<Switch>({case_value}, case_value_base, targets, offsets.size())); for (interpreter::JumpTableTargetOffset offset : offsets) { MergeIntoFrameState(generator_prologue_block, offset.target_offset); } return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitSuspendGenerator() { // SuspendGenerator <generator> <first input register> <register count> // <suspend_id> ValueNode* generator = LoadRegister(0); ValueNode* context = GetContext(); last_suspend_scope_info_ = graph()->TryGetScopeInfo(context, /* for suspend */ true); interpreter::RegisterList args = iterator_.GetRegisterListOperand(1); uint32_t suspend_id = iterator_.GetUnsignedImmediateOperand(3); int input_count = parameter_count_without_receiver() + args.register_count() + GeneratorStore::kFixedInputCount; if (input_count > Node::kMaxInputs) { should_abort_compilation_ = true; return BuildAbort(AbortReason::kUnreachable); } int debug_pos_offset = iterator_.current_offset() + (BytecodeArray::kHeaderSize - kHeapObjectTag); RETURN_IF_ABORT(AddNewNode<GeneratorStore>( input_count, [&](GeneratorStore* node) { int arg_index = 0; for (int i = 1 /* skip receiver */; i < parameter_count(); ++i) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(GetArgument(i))); node->set_parameters_and_registers(arg_index++, tagged_arg); } const compiler::BytecodeLivenessState* liveness = GetOutLiveness(); for (int i = 0; i < args.register_count(); ++i) { ValueNode* value; if (liveness->RegisterIsLive(args[i].index())) { GET_VALUE_OR_ABORT(value, GetTaggedValue(args[i])); } else { value = GetRootConstant(RootIndex::kOptimizedOut); } node->set_parameters_and_registers(arg_index++, value); } return ReduceResult::Done(); }, context, generator, suspend_id, debug_pos_offset)); if (is_inline()) { // All inlined function returns instead jump to one past the end of the // bytecode, where we'll later create a final basic block which resumes // execution of the caller. If there is only one return, at the end of the // function, we can elide this jump and just continue in the same basic // block. if (iterator_.next_offset() != inline_exit_offset() || predecessor_count(inline_exit_offset()) > 1) { BasicBlock* block = FinishBlockNoAbort<Jump>({}, &jump_targets_[inline_exit_offset()]); // The context is dead by now, set it to optimized out to avoid creating // unnecessary phis. SetContext(GetRootConstant(RootIndex::kOptimizedOut)); MergeIntoInlinedReturnFrameStateForSuspendGenerator(block); } return ReduceResult::Done(); } return FinishBlock<Return>({GetAccumulator()}).has_value() ? ReduceResult::Done() : ReduceResult::DoneWithAbort(); } ReduceResult MaglevGraphBuilder::VisitResumeGenerator() { // ResumeGenerator <generator> <first output register> <register count> ValueNode* generator = LoadRegister(0); ValueNode* array; GET_VALUE_OR_ABORT( array, BuildLoadTaggedField( generator, JSGeneratorObject::kParametersAndRegistersOffset)); interpreter::RegisterList registers = iterator_.GetRegisterListOperand(1); if (v8_flags.maglev_assert) { // Check if register count is invalid, that is, larger than the // register file length. ValueNode* array_length = BuildLoadFixedArrayLength(array); ValueNode* register_size = GetInt32Constant( parameter_count_without_receiver() + registers.register_count()); RETURN_IF_ABORT(AddNewNode<AssertInt32>( {register_size, array_length}, AssertCondition::kLessThanEqual, AbortReason::kInvalidParametersAndRegistersInGenerator)); } const compiler::BytecodeLivenessState* liveness = GetOutLivenessFor(next_offset()); RootConstant* stale = GetRootConstant(RootIndex::kStaleRegister); for (int i = 0; i < registers.register_count(); ++i) { interpreter::Register reg = registers[i]; if (liveness->RegisterIsLive(reg.index())) { int array_index = parameter_count_without_receiver() + i; ValueNode* value; GET_VALUE_OR_ABORT(value, AddNewNode<GeneratorRestoreRegister>( {array, stale}, array_index)); StoreRegister(registers[i], value); if (reg == interpreter::Register::current_context()) { EnsureType(value, NodeType::kContext); } } } graph()->record_scope_info(GetContext(), last_suspend_scope_info_); last_suspend_scope_info_ = {}; return SetAccumulator(BuildLoadTaggedField( generator, JSGeneratorObject::kInputOrDebugPosOffset)); } MaybeReduceResult MaglevGraphBuilder::TryReduceGetIterator( ValueNode* receiver, int load_slot_index, int call_slot_index) { // Load iterator method property. FeedbackSlot load_slot = FeedbackVector::ToSlot(load_slot_index); compiler::FeedbackSource load_feedback{feedback(), load_slot}; compiler::NameRef iterator_symbol = broker()->iterator_symbol(); ValueNode* iterator_method; { // TryBuildLoadNamedProperty can eager deopt on e.g. a failed map check -- // this shouldn't pick up this lazy deopt frame. LazyDeoptFrameScope deopt_continuation( this, Builtin::kGetIteratorWithFeedbackLazyDeoptContinuation, {}, base::VectorOf<ValueNode*>({receiver, GetSmiConstant(call_slot_index), GetConstant(feedback())})); MaybeReduceResult result_load = TryBuildLoadNamedProperty(receiver, iterator_symbol, load_feedback); if (result_load.IsDoneWithAbort() || result_load.IsFail()) { return result_load; } DCHECK(result_load.IsDoneWithValue()); iterator_method = result_load.value(); } auto throw_iterator_error = [&] { return BuildThrow(Throw::kThrowIteratorError, receiver); }; if (!iterator_method->is_tagged()) { return throw_iterator_error(); } auto throw_symbol_iterator_invalid = [&] { return BuildThrow(Throw::kThrowSymbolIteratorInvalid); }; auto call_iterator_method = [&]() -> ReduceResult { // If the call eager deopts (e.g. because of incorrect speculation of the // call target), we need to do the full call in the continuation. EagerDeoptFrameScope eager_deopt_continuation( this, Builtin::kCallIteratorWithFeedback, {}, base::VectorOf<ValueNode*>({receiver, iterator_method, GetSmiConstant(call_slot_index), GetConstant(feedback())})); // If the call lazy deopts, we can assume the call itself happened and the // continuation only has to do the post-call checks. LazyDeoptFrameScope deopt_continuation( this, Builtin::kCallIteratorWithFeedbackLazyDeoptContinuation); FeedbackSlot call_slot = FeedbackVector::ToSlot(call_slot_index); compiler::FeedbackSource call_feedback{feedback(), call_slot}; CallArguments args(ConvertReceiverMode::kAny, {receiver}); ReduceResult result_call = BuildCallWithFeedback(iterator_method, args, call_feedback); if (result_call.IsDoneWithAbort()) return result_call; DCHECK(result_call.IsDoneWithValue()); return Select( [&](BranchBuilder& builder) { return BuildBranchIfJSReceiver(builder, result_call.value()); }, [&] { return result_call; }, throw_symbol_iterator_invalid); }; // Check if the iterator_method is undefined and call the method otherwise. return Select( [&](BranchBuilder& builder) { return BuildBranchIfUndefined(builder, iterator_method); }, throw_iterator_error, call_iterator_method); } ReduceResult MaglevGraphBuilder::VisitGetIterator() { // GetIterator <object> ValueNode* receiver = LoadRegister(0); int load_slot = iterator_.GetFeedbackSlotOperand(1); int call_slot = iterator_.GetFeedbackSlotOperand(2); PROCESS_AND_RETURN_IF_DONE( TryReduceGetIterator(receiver, load_slot, call_slot), SetAccumulator); // Fallback to the builtin. ValueNode* context = GetContext(); return SetAccumulator(AddNewNode<GetIterator>({context, receiver}, load_slot, call_slot, feedback())); } ReduceResult MaglevGraphBuilder::VisitForOfNext() { // ForOfNext <iterator> <next> <value_done_out> <call_slot> ValueNode* iterator = LoadRegister(0); ValueNode* next_method = LoadRegister(1); auto register_pair = iterator_.GetRegisterPairOperand(2); int call_slot = iterator_.GetFeedbackSlotOperand(3); CallBuiltin* result_struct; GET_VALUE_OR_ABORT(result_struct, BuildCallBuiltinWithTaggedInputs<Builtin::kForOfNext>( {iterator, next_method, GetConstant(feedback()), GetSmiConstant(call_slot)})); StoreRegisterPair(register_pair, result_struct); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitDebugger() { return BuildCallRuntime(Runtime::kHandleDebuggerStatement, {}); } ReduceResult MaglevGraphBuilder::VisitIncBlockCounter() { ValueNode* closure = GetClosure(); ValueNode* tagged_closure; GET_VALUE_OR_ABORT(tagged_closure, GetTaggedValue(closure)); ValueNode* coverage_array_slot = GetSmiConstant(iterator_.GetCoverageSlotOperand(0)); BuildCallBuiltin<Builtin::kIncBlockCounter>( {tagged_closure, coverage_array_slot}); return ReduceResult::Done(); } ReduceResult MaglevGraphBuilder::VisitAbort() { AbortReason reason = static_cast<AbortReason>(GetFlag8Operand(0)); return BuildAbort(reason); } ReduceResult MaglevGraphBuilder::VisitWide() { UNREACHABLE(); } ReduceResult MaglevGraphBuilder::VisitExtraWide() { UNREACHABLE(); } #define DEBUG_BREAK(Name, ...) \ ReduceResult MaglevGraphBuilder::Visit##Name() { UNREACHABLE(); } DEBUG_BREAK_BYTECODE_LIST(DEBUG_BREAK) #undef DEBUG_BREAK ReduceResult MaglevGraphBuilder::VisitIllegal() { UNREACHABLE(); } bool MaglevGraphBuilder::Build() { DCHECK(!is_inline()); if (should_abort_compilation_) return false; DCHECK_EQ(inlining_id_, SourcePosition::kNotInlined); reducer_.SetBytecodeOffset(entrypoint_); reducer_.SetStartSourcePosition(inlining_id_); StartPrologue(); for (int i = 0; i < parameter_count(); i++) { // TODO(v8:7700): Consider creating InitialValue nodes lazily. InitialValue* v = AddNewNodeNoInputConversion<InitialValue>( {}, interpreter::Register::FromParameterIndex(i)); DCHECK_EQ(graph()->parameters().size(), static_cast<size_t>(i)); graph()->parameters().push_back(v); SetArgument(i, v); if (i == 0) { // This is the receiver (the "this" register). if (is_sloppy( compilation_unit_->shared_function_info().language_mode())) { DCHECK(compilation_unit_->shared_function_info().IsUserJavaScript()); EnsureType(v, NodeType::kJSReceiver); } // In strict mode, the receiver can be pretty much anything (ie, not just // a JSReceiver but also a Smi or undefined, and in derived constructors // it can even be the_hole). The type would be NodeType::kUnknown, so we // just don't set it. } } BuildRegisterFrameInitialization(); // Don't use the AddNewNode helper for the function entry stack check, so // that we can set a custom deopt frame on it. FunctionEntryStackCheck* function_entry_stack_check = NodeBase::New<FunctionEntryStackCheck>(zone(), 0); new (function_entry_stack_check->lazy_deopt_info()) LazyDeoptInfo( zone(), GetDeoptFrameForEntryStackCheck(), interpreter::Register::invalid_value(), 0, compiler::FeedbackSource()); reducer_.AddInitializedNodeToGraph(function_entry_stack_check); BuildMergeStates(); EndPrologue(); in_prologue_ = false; compiler::ScopeInfoRef scope_info = compilation_unit_->shared_function_info().scope_info(broker()); if (scope_info.HasOuterScopeInfo()) { scope_info = scope_info.OuterScopeInfo(broker()); CHECK(scope_info.HasContext()); graph()->record_scope_info(GetContext(), scope_info); } if (compilation_unit_->is_osr()) { OsrAnalyzePrequel(); } BuildBody(); return !should_abort_compilation_; } void MaglevGraphBuilder::BuildBody() { int position = 0; while (!source_position_iterator_.done() && source_position_iterator_.code_offset() < entrypoint_) { position = source_position_iterator_.source_position().ScriptOffset(); source_position_iterator_.Advance(); } reducer_.SetSourcePosition(position, inlining_id_); for (iterator_.SetOffset(entrypoint_); !iterator_.done(); iterator_.Advance()) { local_isolate_->heap()->Safepoint(); if (V8_UNLIKELY( loop_headers_to_peel_.Contains(iterator_.current_offset()))) { PeelLoop(); DCHECK_EQ(iterator_.current_bytecode(), interpreter::Bytecode::kJumpLoop); continue; } if (VisitSingleBytecode().IsDoneWithAbort()) { MarkBytecodeDead(); } } DCHECK_EQ(loop_effects_stack_.size(), is_inline() && caller_details_->loop_effects ? 1 : 0); } SourcePosition MaglevGraphBuilder::GetCurrentSourcePosition() const { return reducer_.current_provenance().position; } BasicBlock* MaglevGraphBuilder::current_block() const { return reducer_.current_block(); } void MaglevGraphBuilder::set_current_block(BasicBlock* block) { reducer_.set_current_block(block); } compiler::FeedbackSource MaglevGraphBuilder::current_speculation_feedback() { return reducer_.current_speculation_feedback(); } bool MaglevGraphBuilder::ShouldEmitInterruptBudgetChecks() { if (is_inline()) { return false; } if (is_turbolev()) { // As the top-tier compiler, Turboshaft doesn't need interrupt budget // checks. return false; } return v8_flags.force_emit_interrupt_budget_checks || v8_flags.turbofan; } bool MaglevGraphBuilder::ShouldEmitOsrInterruptBudgetChecks() { if (!v8_flags.turbofan || !v8_flags.use_osr || !v8_flags.osr_from_maglev) return false; if (!graph_->is_osr() && !v8_flags.always_osr_from_maglev) { return false; } // TODO(olivf) OSR from maglev requires lazy recompilation (see // CompileOptimizedOSRFromMaglev for details). Without this we end up in // deopt loops, e.g., in chromium content_unittests. if (!OptimizingCompileDispatcher::Enabled()) { return false; } // TODO(olivf) OSR'ing from inlined loops is something we might want, but // can't with our current osr-from-maglev implementation. The reason is that // we OSR up by first going down to the interpreter. For inlined loops this // means we would deoptimize to the caller and then probably end up in the // same maglev osr code again, before reaching the turbofan OSR code in the // callee. The solution is to support osr from maglev without // deoptimization. return !is_inline(); } BasicBlock* MaglevGraphBuilder::CreateEdgeSplitBlock( BasicBlockRef& jump_targets, BasicBlock* predecessor) { if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { std::cout << "== New empty block ==" << std::endl; PrintVirtualObjects(); known_node_aspects().PrintLoadedProperties(); } DCHECK_NULL(current_block()); set_current_block(zone()->New<BasicBlock>(nullptr, zone())); BasicBlock* result = FinishBlockNoAbort<Jump>({}, &jump_targets); result->set_edge_split_block(predecessor); #ifdef DEBUG reducer_.StartNewPeriod(); #endif // DEBUG return result; } void MaglevGraphBuilder::ProcessMergePointAtExceptionHandlerStart(int offset) { DCHECK_EQ(current_allocation_block_, nullptr); MergePointInterpreterFrameState& merge_state = *merge_states_[offset]; DCHECK_EQ(merge_state.predecessor_count(), 0); // Copy state. current_interpreter_frame_.CopyFrom(*compilation_unit_, merge_state); // Expressions would have to be explicitly preserved across exceptions. // However, at this point we do not know which ones might be used. current_interpreter_frame_.known_node_aspects()->ClearAvailableExpressions(); // Merges aren't simple fallthroughs, so we should reset the checkpoint // validity. ResetBuilderCachedState(); // Register exception phis. if (has_graph_labeller()) { for (Phi* phi : *merge_states_[offset]->phis()) { graph_labeller()->RegisterNode(phi, compilation_unit_, BytecodeOffset(offset), GetCurrentSourcePosition()); TRACE(" " << phi << " " << PrintNodeLabel(phi) << ": " << PrintNode(phi)); } } } void MaglevGraphBuilder::ProcessMergePoint(int offset, bool preserve_known_node_aspects) { // First copy the merge state to be the current state. MergePointInterpreterFrameState& merge_state = *merge_states_[offset]; current_interpreter_frame_.CopyFrom(*compilation_unit_, merge_state, preserve_known_node_aspects, zone()); ProcessMergePointPredecessors(merge_state, jump_targets_[offset]); } void MaglevGraphBuilder::ProcessMergePointPredecessors( MergePointInterpreterFrameState& merge_state, BasicBlockRef& jump_targets) { // TODO(olivf): Support allocation folding across control flow. DCHECK_EQ(current_allocation_block_, nullptr); // Merges aren't simple fallthroughs, so we should reset state which is // cached directly on the builder instead of on the merge states. ResetBuilderCachedState(); if (merge_state.is_loop()) { DCHECK_EQ(merge_state.predecessors_so_far(), merge_state.predecessor_count() - 1); } else { DCHECK_EQ(merge_state.predecessors_so_far(), merge_state.predecessor_count()); } if (merge_state.predecessor_count() == 1) return; // Set up edge-split. int predecessor_index = merge_state.predecessor_count() - 1; if (merge_state.is_loop()) { // For loops, the JumpLoop block hasn't been generated yet, and so isn't // in the list of jump targets. IT's the last predecessor, so drop the // index by one. DCHECK(merge_state.is_unmerged_loop()); predecessor_index--; } BasicBlockRef* old_jump_targets = jump_targets.Reset(); while (old_jump_targets != nullptr) { BasicBlock* predecessor = merge_state.predecessor_at(predecessor_index); CHECK(predecessor); ControlNode* control = predecessor->control_node(); if (control->Is<ConditionalControlNode>()) { // CreateEmptyBlock automatically registers itself with the offset. predecessor = CreateEdgeSplitBlock(jump_targets, predecessor); // Set the old predecessor's (the conditional block) reference to // point to the new empty predecessor block. old_jump_targets = old_jump_targets->SetToBlockAndReturnNext(predecessor); merge_state.set_predecessor_at(predecessor_index, predecessor); } else { // Re-register the block in the offset's ref list. old_jump_targets = old_jump_targets->MoveToRefList(&jump_targets); } // We only set the predecessor id after splitting critical edges, to make // sure the edge split blocks pick up the correct predecessor index. predecessor->set_predecessor_id(predecessor_index--); } DCHECK_EQ(predecessor_index, -1); RegisterPhisWithGraphLabeller(merge_state); } void MaglevGraphBuilder::RegisterPhisWithGraphLabeller( MergePointInterpreterFrameState& merge_state) { if (!has_graph_labeller()) return; for (Phi* phi : *merge_state.phis()) { reducer_.RegisterNode(phi); TRACE(" " << phi << " " << PrintNodeLabel(phi) << ": " << PrintNode(phi)); } } // Called when a block is killed by an unconditional eager deopt. ReduceResult MaglevGraphBuilder::EmitUnconditionalDeopt( DeoptimizeReason reason) { current_block()->set_deferred(true); FinishBlockNoAbort<Deopt>({}, reason); return ReduceResult::DoneWithAbort(); } void MaglevGraphBuilder::KillPeeledLoopTargets(int peelings) { DCHECK_EQ(iterator_.current_bytecode(), interpreter::Bytecode::kJumpLoop); int target = iterator_.GetJumpTargetOffset(); // Since we ended up not peeling we must kill all the doubly accounted // jumps out of the loop. interpreter::BytecodeArrayIterator iterator(bytecode().object()); for (iterator.SetOffset(target); iterator.current_offset() < iterator_.current_offset(); iterator.Advance()) { interpreter::Bytecode bc = iterator.current_bytecode(); DCHECK_NE(bc, interpreter::Bytecode::kJumpLoop); int kill = -1; if (interpreter::Bytecodes::IsJump(bc) && iterator.GetJumpTargetOffset() > iterator_.current_offset()) { kill = iterator.GetJumpTargetOffset(); } else if (is_inline() && interpreter::Bytecodes::Returns(bc)) { kill = inline_exit_offset(); } if (kill != -1) { if (merge_states_[kill]) { for (int i = 0; i < peelings; ++i) { merge_states_[kill]->MergeDead(*compilation_unit_); } } UpdatePredecessorCount(kill, -peelings); } } } void MaglevGraphBuilder::MarkBytecodeDead() { DCHECK_NULL(current_block()); if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { std::cout << "== Dead ==\n" << std::setw(4) << iterator_.current_offset() << " : "; iterator_.PrintCurrentBytecodeTo(std::cout); std::cout << std::endl; } // If the current bytecode is a jump to elsewhere, then this jump is // also dead and we should make sure to merge it as a dead predecessor. interpreter::Bytecode bytecode = iterator_.current_bytecode(); if (interpreter::Bytecodes::IsForwardJump(bytecode)) { // Jumps merge into their target, and conditional jumps also merge into // the fallthrough. MergeDeadIntoFrameState(iterator_.GetJumpTargetOffset()); if (interpreter::Bytecodes::IsConditionalJump(bytecode)) { MergeDeadIntoFrameState(iterator_.next_offset()); } } else if (bytecode == interpreter::Bytecode::kJumpLoop) { // JumpLoop merges into its loop header, which has to be treated // specially by the merge. if (!in_peeled_iteration() || in_optimistic_peeling_iteration()) { MergeDeadLoopIntoFrameState(iterator_.GetJumpTargetOffset()); } } else if (interpreter::Bytecodes::IsSwitch(bytecode)) { // Switches merge into their targets, and into the fallthrough. for (auto offset : iterator_.GetJumpTableTargetOffsets()) { MergeDeadIntoFrameState(offset.target_offset); } MergeDeadIntoFrameState(iterator_.next_offset()); } else if (!interpreter::Bytecodes::Returns(bytecode) && !interpreter::Bytecodes::UnconditionallyThrows(bytecode)) { // Any other bytecode that doesn't return or throw will merge into the // fallthrough. MergeDeadIntoFrameState(iterator_.next_offset()); } else if (interpreter::Bytecodes::Returns(bytecode) && is_inline()) { MergeDeadIntoFrameState(inline_exit_offset()); } // TODO(leszeks): We could now continue iterating the bytecode } void MaglevGraphBuilder::UpdateSourceAndBytecodePosition(int offset) { reducer_.SetBytecodeOffset(offset); if (source_position_iterator_.done()) return; if (source_position_iterator_.code_offset() == offset) { reducer_.SetSourcePosition( source_position_iterator_.source_position().ScriptOffset(), inlining_id_); source_position_iterator_.Advance(); } else { DCHECK_GT(source_position_iterator_.code_offset(), offset); } } void MaglevGraphBuilder::PrintVirtualObjects() { if (V8_LIKELY(!v8_flags.trace_maglev_graph_building)) return; if (V8_LIKELY(!is_tracing_enabled())) return; current_interpreter_frame_.virtual_objects().Print( std::cout, "* VOs (Interpreter Frame State): "); } ReduceResult MaglevGraphBuilder::VisitSingleBytecode() { if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { std::cout << std::setw(4) << iterator_.current_offset() << " : "; iterator_.PrintCurrentBytecodeTo(std::cout); std::cout << std::endl; } int offset = iterator_.current_offset(); UpdateSourceAndBytecodePosition(offset); MergePointInterpreterFrameState* merge_state = merge_states_[offset]; if (V8_UNLIKELY(merge_state != nullptr)) { bool preserve_known_node_aspects = in_optimistic_peeling_iteration() && loop_headers_to_peel_.Contains(offset); if (merge_state->is_resumable_loop()) { current_for_in_state.enum_cache_indices = nullptr; } if (current_block() != nullptr) { DCHECK(!preserve_known_node_aspects); // TODO(leszeks): Re-evaluate this DCHECK, we might hit it if the only // bytecodes in this basic block were only register juggling. // DCHECK(!node_buffer().empty()); BasicBlock* predecessor; if (merge_state->is_loop() && !merge_state->is_resumable_loop() && need_checkpointed_loop_entry()) { predecessor = FinishBlockNoAbort<CheckpointedJump>({}, &jump_targets_[offset]); } else { predecessor = FinishBlockNoAbort<Jump>({}, &jump_targets_[offset]); } merge_state->Merge(this, *compilation_unit_, current_interpreter_frame_, predecessor); } if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { auto detail = merge_state->is_exception_handler() ? "exception handler" : merge_state->is_loop() ? "loop header" : "merge"; std::cout << "== New block (" << detail << " @" << merge_state << ") at " << compilation_unit()->shared_function_info().object() << "==" << std::endl; PrintVirtualObjects(); known_node_aspects().PrintLoadedProperties(); } if (V8_UNLIKELY(merge_state->is_exception_handler())) { CHECK_EQ(predecessor_count(offset), 0); // If we have no reference to this block, then the exception handler is // dead. if (!jump_targets_[offset].has_ref() || !merge_state->exception_handler_was_used()) { return ReduceResult::DoneWithAbort(); } ProcessMergePointAtExceptionHandlerStart(offset); } else if (merge_state->is_unmerged_unreachable_loop()) { // We encountered a loop header that is only reachable by the JumpLoop // back-edge, but the bytecode_analysis didn't notice upfront. This can // e.g. be a loop that is entered on a dead fall-through. static_assert(kLoopsMustBeEnteredThroughHeader); return ReduceResult::DoneWithAbort(); } else { ProcessMergePoint(offset, preserve_known_node_aspects); } if (is_loop_effect_tracking_enabled() && merge_state->is_loop()) { BeginLoopEffects(offset); } // We pass nullptr for the `predecessor` argument of StartNewBlock because // this block is guaranteed to have a merge_state_, and hence to not have // a `predecessor_` field. StartNewBlock(offset, /*predecessor*/ nullptr); } else if (V8_UNLIKELY(current_block() == nullptr)) { // If we don't have a current block, the bytecode must be dead (because of // some earlier deopt). Mark this bytecode dead too and return. // TODO(leszeks): Merge these two conditions by marking dead states with // a sentinel value. if (predecessor_count(offset) == 1) { CHECK_NULL(merge_state); CHECK(bytecode_analysis().IsLoopHeader(offset)); } else { CHECK_EQ(predecessor_count(offset), 0); } return ReduceResult::DoneWithAbort(); } // Handle exceptions if we have a table. if (bytecode().handler_table_size() > 0) { // Pop all entries where offset >= end. while (IsInsideTryBlock()) { HandlerTableEntry& entry = catch_block_stack_.top(); if (offset < entry.end) break; catch_block_stack_.pop(); } // Push new entries from interpreter handler table where offset >= start // && offset < end. HandlerTable table(*bytecode().object()); while (next_handler_table_index_ < table.NumberOfRangeEntries()) { int start = table.GetRangeStart(next_handler_table_index_); if (offset < start) break; int end = table.GetRangeEnd(next_handler_table_index_); if (offset >= end) { next_handler_table_index_++; continue; } int handler = table.GetRangeHandler(next_handler_table_index_); catch_block_stack_.push({end, handler}); DCHECK_NOT_NULL(merge_states_[handler]); next_handler_table_index_++; } } DCHECK_NOT_NULL(current_block()); #ifdef DEBUG // Clear new nodes for the next VisitFoo reducer_.StartNewPeriod(); #endif // DEBUG if (iterator_.current_bytecode() == interpreter::Bytecode::kJumpLoop && iterator_.GetJumpTargetOffset() < entrypoint_) { static_assert(kLoopsMustBeEnteredThroughHeader); CHECK(EmitUnconditionalDeopt(DeoptimizeReason::kOSREarlyExit) .IsDoneWithAbort()); return ReduceResult::DoneWithAbort(); } switch (iterator_.current_bytecode()) { #define BYTECODE_CASE(name, ...) \ case interpreter::Bytecode::k##name: \ return Visit##name(); BYTECODE_LIST(BYTECODE_CASE, BYTECODE_CASE) #undef BYTECODE_CASE } UNREACHABLE(); } // Add a new node with a dynamic set of inputs which are initialized by the // `post_create_input_initializer` function before the node is added to the // graph. template <typename NodeT, typename Function, typename... Args> ReduceResult MaglevGraphBuilder::AddNewNode( size_t input_count, Function&& post_create_input_initializer, Args&&... args) { static_assert(!std::is_base_of_v<ControlNode, NodeT>, "Use FinishBlock instead of AddNewNode to add control nodes"); return reducer_.AddNewNode<NodeT>( input_count, std::forward<Function>(post_create_input_initializer), std::forward<Args>(args)...); } // Add a new node with a static set of inputs. template <typename NodeT, typename... Args> ReduceResult MaglevGraphBuilder::AddNewNode( std::initializer_list<ValueNode*> inputs, Args&&... args) { static_assert(!std::is_base_of_v<ControlNode, NodeT>, "Use FinishBlock instead of AddNewNode to add control nodes"); return reducer_.AddNewNode<NodeT>(inputs, std::forward<Args>(args)...); } template <typename NodeT, typename... Args> NodeT* MaglevGraphBuilder::AddNewNodeNoInputConversion( std::initializer_list<ValueNode*> inputs, Args&&... args) { static_assert(!std::is_base_of_v<ControlNode, NodeT>, "Use FinishBlock instead of AddNewNode to add control nodes"); return reducer_.AddNewNodeNoInputConversion<NodeT>( inputs, std::forward<Args>(args)...); } CatchBlockDetails MaglevGraphBuilder::GetCurrentTryCatchBlock() { if (IsInsideTryBlock()) { // Inside a try-block. int offset = catch_block_stack_.top().handler; return {&jump_targets_[offset], merge_states_[offset]->exception_handler_was_used(), false, 0}; } if (!is_inline()) { return CatchBlockDetails{}; } return caller_details_->catch_block; } CatchBlockDetails MaglevGraphBuilder::GetTryCatchBlockForNonEagerInlining( ExceptionHandlerInfo* info) { if (!info->HasExceptionHandler()) return CatchBlockDetails{}; int deopt_frame_distance = is_inline() && !IsInsideTryBlock() ? caller_details_->catch_block.deopt_frame_distance : 0; // Since this CatchBlockDetails is stored in a non-eager call site, // the catch block will already exist by the time inlining is attempted. return {info->catch_block_ref_address(), !info->ShouldLazyDeopt(), true, deopt_frame_distance}; } ReduceResult MaglevGraphBuilder::GetTaggedValue( ValueNode* value, UseReprHintRecording record_use_repr_hint) { return reducer_.GetTaggedValue(value, record_use_repr_hint); } ReduceResult MaglevGraphBuilder::GetTaggedValue( interpreter::Register reg, UseReprHintRecording record_use_repr_hint) { ValueNode* value = current_interpreter_frame_.get(reg); return GetTaggedValue(value, record_use_repr_hint); } ReduceResult MaglevGraphBuilder::GetInt32(ValueNode* value, bool can_be_heap_number) { return reducer_.GetInt32(value, can_be_heap_number); } ReduceResult MaglevGraphBuilder::GetInt32(ReduceResult value_result, bool can_be_heap_number) { ValueNode* value; GET_VALUE_OR_ABORT(value, value_result); return reducer_.GetInt32(value, can_be_heap_number); } ReduceResult MaglevGraphBuilder::EnsureInt32(ValueNode* value, bool can_be_heap_number) { return reducer_.EnsureInt32(value, can_be_heap_number); } ReduceResult MaglevGraphBuilder::EnsureInt32(interpreter::Register reg) { return EnsureInt32(current_interpreter_frame_.get(reg)); } ReduceResult MaglevGraphBuilder::GetFloat64(ValueNode* value) { return reducer_.GetFloat64(value); } ReduceResult MaglevGraphBuilder::GetFloat64(interpreter::Register reg) { return GetFloat64(current_interpreter_frame_.get(reg)); } ReduceResult MaglevGraphBuilder::GetFloat64ForToNumber( ValueNode* value, NodeType allowed_input_type) { return reducer_.GetFloat64ForToNumber(value, allowed_input_type); } ReduceResult MaglevGraphBuilder::GetFloat64ForToNumber( interpreter::Register reg, NodeType allowed_input_type) { return GetFloat64ForToNumber(current_interpreter_frame_.get(reg), allowed_input_type); } ReduceResult MaglevGraphBuilder::GetHoleyFloat64(ValueNode* value) { return reducer_.GetHoleyFloat64(value); } std::optional<int32_t> MaglevGraphBuilder::TryGetInt32Constant( ValueNode* value) { return reducer_.TryGetInt32Constant(value); } std::optional<uint32_t> MaglevGraphBuilder::TryGetUint32Constant( ValueNode* value) { return reducer_.TryGetUint32Constant(value); } std::optional<Float64> MaglevGraphBuilder::TryGetFloat64OrHoleyFloat64Constant( UseRepresentation use_repr, ValueNode* value, TaggedToFloat64ConversionType conversion_type) { return reducer_.TryGetFloat64OrHoleyFloat64Constant(use_repr, value, conversion_type); } MaybeHandle<String> MaglevGraphBuilder::TryGetStringConstant(ValueNode* value) { if (Constant* constant = value->TryCast<Constant>()) { if (constant->object().IsString()) { return handle(Cast<String>(*constant->object().object()), local_isolate()); } } else if (RootConstant* root = value->TryCast<RootConstant>()) { Handle<Object> handle = local_isolate_->root_handle(root->index()); if (IsString(*handle)) { return Cast<String>(handle); } } return {}; } template <Builtin kBuiltin> ReduceResult MaglevGraphBuilder::BuildCallBuiltinWithTaggedInputs( std::initializer_list<ValueNode*> inputs) { using Descriptor = typename CallInterfaceDescriptorFor<kBuiltin>::type; if constexpr (Descriptor::HasContextParameter()) { return AddNewNode<CallBuiltin>( inputs.size() + 1, [&](CallBuiltin* call_builtin) { int arg_index = 0; for (auto* input : inputs) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(input)); call_builtin->set_arg(arg_index++, tagged_arg); } return ReduceResult::Done(); }, kBuiltin, GetContext()); } else { return AddNewNode<CallBuiltin>( inputs.size(), [&](CallBuiltin* call_builtin) { int arg_index = 0; for (auto* input : inputs) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(input)); call_builtin->set_arg(arg_index++, tagged_arg); } return ReduceResult::Done(); }, kBuiltin); } } template <Builtin kBuiltin> CallBuiltin* MaglevGraphBuilder::BuildCallBuiltin( std::initializer_list<ValueNode*> inputs) { using Descriptor = typename CallInterfaceDescriptorFor<kBuiltin>::type; if constexpr (Descriptor::HasContextParameter()) { ReduceResult result = AddNewNode<CallBuiltin>( inputs.size() + 1, [&](CallBuiltin* call_builtin) { int arg_index = 0; for (auto* input : inputs) { call_builtin->set_arg(arg_index++, input); } return ReduceResult::Done(); }, kBuiltin, GetContext()); CHECK(result.IsDoneWithValue()); return result.value()->Cast<CallBuiltin>(); } else { ReduceResult result = AddNewNode<CallBuiltin>( inputs.size(), [&](CallBuiltin* call_builtin) { int arg_index = 0; for (auto* input : inputs) { call_builtin->set_arg(arg_index++, input); } return ReduceResult::Done(); }, kBuiltin); CHECK(result.IsDoneWithValue()); return result.value()->Cast<CallBuiltin>(); } } template <Builtin kBuiltin> CallBuiltin* MaglevGraphBuilder::BuildCallBuiltin( std::initializer_list<ValueNode*> inputs, compiler::FeedbackSource const& feedback, CallBuiltin::FeedbackSlotType slot_type) { CallBuiltin* call_builtin = BuildCallBuiltin<kBuiltin>(inputs); call_builtin->set_feedback(feedback, slot_type); #ifdef DEBUG // Check that the last parameters are kSlot and kVector. using Descriptor = typename CallInterfaceDescriptorFor<kBuiltin>::type; int slot_index = call_builtin->InputCountWithoutContext(); int vector_index = slot_index + 1; DCHECK_EQ(slot_index, Descriptor::kSlot); // TODO(victorgomes): Rename all kFeedbackVector parameters in the builtins // to kVector. DCHECK_EQ(vector_index, Descriptor::kVector); #endif // DEBUG return call_builtin; } template <Builtin kBuiltin> ReduceResult MaglevGraphBuilder::BuildCallBuiltinWithTaggedInputs( std::initializer_list<ValueNode*> inputs, compiler::FeedbackSource const& feedback, CallBuiltin::FeedbackSlotType slot_type) { CallBuiltin* call_builtin; GET_VALUE_OR_ABORT(call_builtin, BuildCallBuiltinWithTaggedInputs<kBuiltin>(inputs)); call_builtin->set_feedback(feedback, slot_type); #ifdef DEBUG // Check that the last parameters are kSlot and kVector. using Descriptor = typename CallInterfaceDescriptorFor<kBuiltin>::type; int slot_index = call_builtin->InputCountWithoutContext(); int vector_index = slot_index + 1; DCHECK_EQ(slot_index, Descriptor::kSlot); // TODO(victorgomes): Rename all kFeedbackVector parameters in the builtins // to kVector. DCHECK_EQ(vector_index, Descriptor::kVector); #endif // DEBUG return call_builtin; } ReduceResult MaglevGraphBuilder::BuildCallRuntime( Runtime::FunctionId function_id, std::initializer_list<ValueNode*> inputs) { CallRuntime* result; GET_VALUE_OR_ABORT( result, AddNewNode<CallRuntime>( inputs.size() + CallRuntime::kFixedInputCount, [&](CallRuntime* call_runtime) { int arg_index = 0; for (auto* input : inputs) { ValueNode* tagged_arg; GET_VALUE_OR_ABORT(tagged_arg, GetTaggedValue(input)); call_runtime->set_arg(arg_index++, tagged_arg); } return ReduceResult::Done(); }, function_id, GetContext())); if (RuntimeFunctionWillThrow(function_id)) { return BuildAbort(AbortReason::kUnexpectedReturnFromThrow); } return result; } ReduceResult MaglevGraphBuilder::BuildThrow(Throw::Function function, ValueNode* input) { bool has_input; if (input == nullptr) { has_input = false; // To avoid a nullptr input, we use Smi(0) as dummy input. input = GetSmiConstant(0); } else { has_input = true; } FinishBlockNoAbort<Throw>({input}, function, has_input); return ReduceResult::DoneWithAbort(); } ReduceResult MaglevGraphBuilder::BuildAbort(AbortReason reason) { FinishBlockNoAbort<Abort>({}, reason); return ReduceResult::DoneWithAbort(); } // TODO(victorgomes): Should register inputs be like constants? Ie, // registered/allocated directly in the graph. ValueNode* MaglevGraphBuilder::GetRegisterInput(Register reg) { DCHECK(!graph_->register_inputs().has(reg)); graph_->register_inputs().set(reg); return AddNewNodeNoInputConversion<RegisterInput>({}, reg); } void MaglevGraphBuilder::Print(const char* str) { Handle<String> string_handle = local_isolate()->factory()->NewStringFromAsciiChecked( str, AllocationType::kOld); ValueNode* string_node = graph()->GetConstant(MakeRefAssumeMemoryFence( broker(), broker()->CanonicalPersistentHandle(string_handle))); CHECK(BuildCallRuntime(Runtime::kGlobalPrint, {string_node}).IsDone()); } void MaglevGraphBuilder::Print(ValueNode* value) { CHECK(BuildCallRuntime(Runtime::kDebugPrint, {value}).IsDone()); } void MaglevGraphBuilder::Print(const char* str, ValueNode* value) { Print(str); Print(value); } ReduceResult MaglevGraphBuilder::GetSilencedNaN(ValueNode* value) { DCHECK_EQ(value->properties().value_representation(), ValueRepresentation::kFloat64); // We only need to check for silenced NaN in non-conversion nodes or // conversion from tagged, since they can't be signalling NaNs. if (value->is_conversion()) { // A conversion node should have at least one input. DCHECK_GE(value->input_count(), 1); // If the conversion node is tagged, we could be reading a fabricated sNaN // value (built using a BufferArray for example). if (!value->input(0).node()->properties().is_tagged()) { return value; } } // Special case constants, since we know what they are. Float64Constant* constant = value->TryCast<Float64Constant>(); if (constant) { constexpr double quiet_NaN = std::numeric_limits<double>::quiet_NaN(); if (!constant->value().is_nan()) return constant; return GetFloat64Constant(quiet_NaN); } // Silence all other values. return AddNewNode<Float64ToSilencedFloat64>({value}); } ValueNode* MaglevGraphBuilder::GetSecondValue(ValueNode* result) { // GetSecondReturnedValue must be added just after a node that calls a // builtin that expects 2 returned values. It simply binds kReturnRegister1 // to a value node. Since the previous node must have been a builtin // call, the register is available in the register allocator. No gap moves // would be emitted between these two nodes. if (result->opcode() == Opcode::kCallRuntime) { DCHECK_EQ(result->Cast<CallRuntime>()->ReturnCount(), 2); } else if (result->opcode() == Opcode::kCallBuiltin) { DCHECK_EQ(result->Cast<CallBuiltin>()->ReturnCount(), 2); } else { DCHECK_EQ(result->opcode(), Opcode::kForInPrepare); } // {result} must be the last node in the current block. DCHECK_EQ(reducer_.GetLastNewNodeInCurrentBlockPosition(), result); return AddNewNodeNoInputConversion<GetSecondReturnedValue>({}); } #ifdef DEBUG bool MaglevGraphBuilder::IsNodeCreatedForThisBytecode(ValueNode* node) const { return reducer_.WasNodeCreatedDuringCurrentPeriod(node); } bool MaglevGraphBuilder::MayNeedContextPhis() const { if (graph()->is_osr()) return true; if (IsResumableFunction(compilation_unit_->GetTopLevelCompilationUnit() ->shared_function_info() .kind())) { // Top level function is resumable. return true; } if (IsResumableFunction(compilation_unit_->shared_function_info().kind())) { // Currently inlining a resumable function. return true; } // Checking if we've already inlined a resumable function. for (auto fun : graph()->inlined_functions()) { if (IsResumableFunction(fun.shared_info->kind())) return true; } return false; } #endif // DEBUG template <typename NodeT> void MaglevGraphBuilder::StoreRegisterPair( std::pair<interpreter::Register, interpreter::Register> target, NodeT* value) { const interpreter::Register target0 = target.first; const interpreter::Register target1 = target.second; DCHECK_EQ(interpreter::Register(target0.index() + 1), target1); DCHECK_EQ(value->ReturnCount(), 2); if (!v8_flags.maglev_cse) { // TODO(olivf): CSE might deduplicate this value and the one below. DCHECK(IsNodeCreatedForThisBytecode(value)); } DCHECK(HasOutputRegister(target0)); current_interpreter_frame_.set(target0, value); ValueNode* second_value = GetSecondValue(value); if (!v8_flags.maglev_cse) { DCHECK(IsNodeCreatedForThisBytecode(second_value)); } DCHECK(HasOutputRegister(target1)); current_interpreter_frame_.set(target1, second_value); // Make sure the lazy deopt info of this value, if any, is registered as // mutating these registers. DCHECK_IMPLIES(value->properties().can_lazy_deopt() && IsNodeCreatedForThisBytecode(value), value->lazy_deopt_info()->IsResultRegister(target0)); DCHECK_IMPLIES(value->properties().can_lazy_deopt() && IsNodeCreatedForThisBytecode(value), value->lazy_deopt_info()->IsResultRegister(target1)); } void MaglevGraphBuilder::AttachExceptionHandlerInfo(NodeBase* node) { CatchBlockDetails catch_block = GetCurrentTryCatchBlock(); if (catch_block.ref) { if (!catch_block.exception_handler_was_used) { // Attach an empty live exception handler to mark that there's a // matching catch but we'll lazy deopt if we ever throw. new (node->exception_handler_info()) ExceptionHandlerInfo(ExceptionHandlerInfo::kLazyDeopt); DCHECK(node->exception_handler_info()->HasExceptionHandler()); DCHECK(node->exception_handler_info()->ShouldLazyDeopt()); if (node->Is<CallKnownJSFunction>()) { if (flags_.is_non_eager_inlining_enabled) { // Ensure that we always have the handler of inline call // candidates. current_block()->AddExceptionHandler(node->exception_handler_info()); } } return; } DCHECK_IMPLIES(!IsInsideTryBlock(), is_inline()); if (catch_block.block_already_exists) { DCHECK(!IsInsideTryBlock()); // If we are inlining a function non-eagerly and we are not inside a // try block, then the catch block already exists. new (node->exception_handler_info()) ExceptionHandlerInfo( catch_block.ref->block_ptr(), catch_block.deopt_frame_distance); } else { // If we are inside a try block for the current builder or if we are // inside an eager inlined call inside a try block, the catch basic // block doesn't exist yet, use the ref-list mechanism. new (node->exception_handler_info()) ExceptionHandlerInfo( catch_block.ref, catch_block.deopt_frame_distance); } DCHECK(node->exception_handler_info()->HasExceptionHandler()); DCHECK(!node->exception_handler_info()->ShouldLazyDeopt()); current_block()->AddExceptionHandler(node->exception_handler_info()); if (IsInsideTryBlock()) { // Merge the current state into the handler state. auto state = GetCatchBlockFrameState(); DCHECK_NOT_NULL(state); state->MergeThrow(this, compilation_unit_, *current_interpreter_frame_.known_node_aspects()); } } else { // Patch no exception handler marker. // TODO(victorgomes): Avoid allocating exception handler data in this // case. new (node->exception_handler_info()) ExceptionHandlerInfo(); DCHECK(!node->exception_handler_info()->HasExceptionHandler()); if (node->Is<CallKnownJSFunction>()) { if (flags_.is_non_eager_inlining_enabled) { // Ensure that we always have the handler of inline call candidates. current_block()->AddExceptionHandler(node->exception_handler_info()); } } } } void MaglevGraphBuilder::StartNewBlock(int offset, BasicBlock* predecessor) { StartNewBlock(predecessor, merge_states_[offset], jump_targets_[offset]); } void MaglevGraphBuilder::StartNewBlock( BasicBlock* predecessor, MergePointInterpreterFrameState* merge_state, BasicBlockRef& refs_to_block) { DCHECK_NULL(current_block()); set_current_block(zone()->New<BasicBlock>(merge_state, zone())); if (merge_state == nullptr) { DCHECK_NOT_NULL(predecessor); current_block()->set_predecessor(predecessor); } else { merge_state->InitializeWithBasicBlock(current_block()); } refs_to_block.Bind(current_block()); } template <typename ControlNodeT, typename... Args> std::optional<BasicBlock*> MaglevGraphBuilder::FinishBlock( std::initializer_list<ValueNode*> control_inputs, Args&&... args) { // Returning from inlined functions needs special care and shouldn't even emit // a Return, cf VisitReturn and VisitSuspendGenerator. DCHECK_IMPLIES((std::is_same_v<Return, ControlNodeT>), !is_inline()); ReduceResult result = reducer_.AddNewControlNode<ControlNodeT>( control_inputs, std::forward<Args>(args)...); if (result.IsDoneWithAbort()) { return {}; } // Clear unobserved context slot stores when there is any controlflow. // TODO(olivf): More precision could be achieved by tracking dominating // stores within known_node_aspects. For this we could use a stack of // stores, which we push on split and pop on merge. unobserved_context_slot_stores_.clear(); // TODO(olivf): Support allocation folding across control flow. ClearCurrentAllocationBlock(); current_interpreter_frame_.virtual_objects().Snapshot(); BasicBlock* block = current_block(); reducer_.FlushNodesToBlock(); set_current_block(nullptr); graph()->Add(block); return block; } template <typename ControlNodeT, typename... Args> BasicBlock* MaglevGraphBuilder::FinishBlockNoAbort( std::initializer_list<ValueNode*> control_inputs, Args&&... args) { auto result = FinishBlock<ControlNodeT>(control_inputs, std::forward<Args>(args)...); CHECK(result); return *result; } void MaglevGraphBuilder::StartFallthroughBlock(int next_block_offset, BasicBlock* predecessor) { // Start a new block for the fallthrough path, unless it's a merge point, in // which case we merge our state into it. That merge-point could also be a // loop header, in which case the merge state might not exist yet (if the // only predecessors are this path and the JumpLoop). DCHECK_NULL(current_block()); if (predecessor_count(next_block_offset) == 1) { if (V8_UNLIKELY(v8_flags.trace_maglev_graph_building && is_tracing_enabled())) { std::cout << "== New block (single fallthrough) at " << *compilation_unit_->shared_function_info().object() << "==" << std::endl; PrintVirtualObjects(); known_node_aspects().PrintLoadedProperties(); } StartNewBlock(next_block_offset, predecessor); } else { MergeIntoFrameState(predecessor, next_block_offset); } } ReduceResult MaglevGraphBuilder::BuildLoadMap(ValueNode* object) { if (!object->is_tagged() && !object->is_holey_float64()) { // TODO(victorgomes): Implement the holey float64 case. return ReduceResult::Done(GetConstant(broker()->heap_number_map())); } return BuildLoadTaggedField(object, HeapObject::kMapOffset); } ReduceResult MaglevGraphBuilder::BuildLoadTaggedField(ValueNode* object, uint32_t offset, LoadType type, bool is_const, PropertyKey key) { // TODO(jgruber): The VirtualObject now stores map slots, so theoretically we // could let the path below handle map loads as well. But, maglev currently // doesn't like this at all - doing so creates problems like OOB vobject field // loads, and missed JSArray elements kind transitions. We should understand // whether this is an issue with --maglev-object-tracking. if (offset == HeapObject::kMapOffset || !CanTrackObjectChanges(object, TrackObjectMode::kLoad)) { return AddNewNode<LoadTaggedField>({object}, offset, type, is_const, key); } VirtualObject* vobject = GetObjectFromAllocation(object->Cast<InlinedAllocation>()); ValueNode* value; CHECK_EQ(vobject->FieldForOffset(offset).type, vobj::FieldType::kTagged); value = vobject->get(offset); if (v8_flags.trace_maglev_object_tracking) { std::cout << " * Reusing value in virtual object " << PrintNodeLabel(vobject) << "[" << offset << "]: " << PrintNode(value) << std::endl; } return value; } void MaglevGraphBuilder::AddDeoptUse(ValueNode* node) { if (node == nullptr) return; node->AddDeoptUse(current_interpreter_frame().virtual_objects()); } void MaglevGraphBuilder::CalculatePredecessorCounts() { // Add 1 after the end of the bytecode so we can always write to the offset // after the last bytecode. uint32_t array_length = bytecode().length() + 1; predecessor_count_ = zone()->AllocateArray<uint32_t>(array_length); Memset(predecessor_count_, 0, entrypoint_); Memset(predecessor_count_ + entrypoint_, 1, array_length - entrypoint_); const int max_peelings = v8_flags.maglev_optimistic_peeled_loops ? 2 : 1; // We count jumps from peeled loops to outside of the loop twice. bool is_loop_peeling_iteration = false; std::optional<int> peeled_loop_end; interpreter::BytecodeArrayIterator iterator(bytecode().object()); for (iterator.AdvanceTo(entrypoint_); !iterator.done(); iterator.Advance()) { interpreter::Bytecode bytecode = iterator.current_bytecode(); if (allow_loop_peeling_ && bytecode_analysis().IsLoopHeader(iterator.current_offset())) { const compiler::LoopInfo& loop_info = bytecode_analysis().GetLoopInfoFor(iterator.current_offset()); // Generators use irreducible control flow, which makes loop peeling too // complicated. int size = loop_info.loop_end() - loop_info.loop_start(); if (loop_info.innermost() && !loop_info.resumable() && iterator.next_offset() < loop_info.loop_end() && size < v8_flags.maglev_loop_peeling_max_size && size + graph_->total_peeled_bytecode_size() < v8_flags.maglev_loop_peeling_max_size_cumulative) { DCHECK(!is_loop_peeling_iteration); graph_->add_peeled_bytecode_size(size); is_loop_peeling_iteration = true; loop_headers_to_peel_.Add(iterator.current_offset()); peeled_loop_end = bytecode_analysis().GetLoopEndOffsetForInnermost( iterator.current_offset()); } } if (interpreter::Bytecodes::IsJump(bytecode)) { if (is_loop_peeling_iteration && bytecode == interpreter::Bytecode::kJumpLoop) { DCHECK_EQ(iterator.next_offset(), peeled_loop_end); is_loop_peeling_iteration = false; peeled_loop_end = {}; } if (iterator.GetJumpTargetOffset() < entrypoint_) { static_assert(kLoopsMustBeEnteredThroughHeader); if (predecessor_count(iterator.GetJumpTargetOffset()) == 1) { // We encountered a JumpLoop whose loop header is not reachable // otherwise. This loop is either dead or the JumpLoop will bail // with DeoptimizeReason::kOSREarlyExit. InitializePredecessorCount(iterator.GetJumpTargetOffset(), 0); } } else { UpdatePredecessorCount(iterator.GetJumpTargetOffset(), 1); } if (is_loop_peeling_iteration && iterator.GetJumpTargetOffset() >= *peeled_loop_end) { // Jumps from within the peeled loop to outside need to be counted // twice, once for the peeled and once for the regular loop body. UpdatePredecessorCount(iterator.GetJumpTargetOffset(), max_peelings); } if (!interpreter::Bytecodes::IsConditionalJump(bytecode)) { UpdatePredecessorCount(iterator.next_offset(), -1); } } else if (interpreter::Bytecodes::IsSwitch(bytecode)) { for (auto offset : iterator.GetJumpTableTargetOffsets()) { UpdatePredecessorCount(offset.target_offset, 1); } } else if (interpreter::Bytecodes::Returns(bytecode) || interpreter::Bytecodes::UnconditionallyThrows(bytecode)) { UpdatePredecessorCount(iterator.next_offset(), -1); // Collect inline return jumps in the slot after the last bytecode. if (is_inline() && interpreter::Bytecodes::Returns(bytecode)) { UpdatePredecessorCount(array_length - 1, 1); if (is_loop_peeling_iteration) { UpdatePredecessorCount(array_length - 1, max_peelings); } } } // TODO(leszeks): Also consider handler entries (the bytecode analysis) // will do this automatically I guess if we merge this into that. } if (!is_inline()) { DCHECK_EQ(0, predecessor_count(bytecode().length())); } } bool MaglevGraphBuilder::IsInsideLoopInTheCurrentFunction() const { int loop_header_offset = bytecode_analysis().GetLoopOffsetFor(iterator_.current_offset()); if (loop_header_offset != -1) { const compiler::LoopInfo& loop_info = bytecode_analysis().GetLoopInfoFor(loop_header_offset); if (loop_info.parent_offset() == -1) { // This is the outmost loop, if we're actually inside the peel, we are // not really in a loop. return !in_peeled_iteration() || in_optimistic_peeling_iteration(); } return true; } return false; } bool MaglevGraphBuilder::IsInsideLoop() const { if (is_inline() && caller_details()->loop_depth > 0) return true; return IsInsideLoopInTheCurrentFunction(); } int MaglevGraphBuilder::GetLoopDepth() const { return (is_inline() ? caller_details()->loop_depth : 0) + IsInsideLoopInTheCurrentFunction(); } ReduceResult MaglevGraphBuilder::BuildSmiUntag(ValueNode* node) { return reducer_.BuildSmiUntag(node); } ReduceResult MaglevGraphBuilder::BuildGetCharCodeAt(ValueNode* string, ValueNode* index) { bool is_seq_one_byte = v8_flags.specialize_code_for_one_byte_seq_strings && NodeTypeIs(GetType(string), NodeType::kSeqOneByteString); if (is_seq_one_byte) { return AddNewNode<BuiltinSeqOneByteStringCharCodeAt>({string, index}); } else { return AddNewNode<BuiltinStringPrototypeCharCodeOrCodePointAt>( {string, index}, BuiltinStringPrototypeCharCodeOrCodePointAt::kCharCodeAt); } } } // namespace v8::internal::maglev