// Copyright 2015 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/compiler/js-call-reducer.h" #include #include "include/v8-fast-api-calls.h" #include "src/api/api-inl.h" #include "src/base/small-vector.h" #include "src/builtins/builtins-promise.h" #include "src/builtins/builtins-utils.h" #include "src/codegen/code-factory.h" #include "src/codegen/tnode.h" #include "src/compiler/access-builder.h" #include "src/compiler/access-info.h" #include "src/compiler/allocation-builder.h" #include "src/compiler/compilation-dependencies.h" #include "src/compiler/feedback-source.h" #include "src/compiler/graph-assembler.h" #include "src/compiler/js-graph.h" #include "src/compiler/linkage.h" #include "src/compiler/map-inference.h" #include "src/compiler/node-matchers.h" #include "src/compiler/property-access-builder.h" #include "src/compiler/simplified-operator.h" #include "src/compiler/type-cache.h" #include "src/ic/call-optimization.h" #include "src/logging/counters.h" #include "src/objects/arguments-inl.h" #include "src/objects/feedback-vector-inl.h" #include "src/objects/js-array-buffer-inl.h" #include "src/objects/js-array-inl.h" #include "src/objects/js-objects.h" #include "src/objects/objects-inl.h" #include "src/objects/ordered-hash-table.h" namespace v8 { namespace internal { namespace compiler { // Shorter lambda declarations with less visual clutter. #define _ [&]() // NOLINT(whitespace/braces) class JSCallReducerAssembler : public JSGraphAssembler { protected: class CatchScope; private: static constexpr bool kMarkLoopExits = true; public: JSCallReducerAssembler(JSGraph* jsgraph, Zone* zone, Node* node) : JSGraphAssembler(jsgraph, zone, nullptr, kMarkLoopExits), node_(node), outermost_catch_scope_(CatchScope::Outermost(zone)), catch_scope_(&outermost_catch_scope_) { InitializeEffectControl(NodeProperties::GetEffectInput(node), NodeProperties::GetControlInput(node)); // Finish initializing the outermost catch scope. bool has_handler = NodeProperties::IsExceptionalCall(node, &outermost_handler_); outermost_catch_scope_.set_has_handler(has_handler); outermost_catch_scope_.set_gasm(this); } TNode ReduceMathUnary(const Operator* op); TNode ReduceMathBinary(const Operator* op); TNode ReduceStringPrototypeSubstring(); TNode ReduceStringPrototypeSlice(); TNode TargetInput() const { return ValueInput(0); } CatchScope* catch_scope() const { return catch_scope_; } Node* outermost_handler() const { return outermost_handler_; } Node* node_ptr() const { return node_; } protected: using NodeGenerator0 = std::function()>; using VoidGenerator0 = std::function; // TODO(jgruber): Currently IfBuilder0 and IfBuilder1 are implemented as // separate classes. If, in the future, we encounter additional use cases that // return more than 1 value, we should merge these back into a single variadic // implementation. class IfBuilder0 final { public: IfBuilder0(JSGraphAssembler* gasm, TNode cond, bool negate_cond) : gasm_(gasm), cond_(cond), negate_cond_(negate_cond), initial_effect_(gasm->effect()), initial_control_(gasm->control()) {} IfBuilder0& ExpectTrue() { DCHECK_EQ(hint_, BranchHint::kNone); hint_ = BranchHint::kTrue; return *this; } IfBuilder0& ExpectFalse() { DCHECK_EQ(hint_, BranchHint::kNone); hint_ = BranchHint::kFalse; return *this; } IfBuilder0& Then(const VoidGenerator0& body) { then_body_ = body; return *this; } IfBuilder0& Else(const VoidGenerator0& body) { else_body_ = body; return *this; } ~IfBuilder0() { // Ensure correct usage: effect/control must not have been modified while // the IfBuilder0 instance is alive. DCHECK_EQ(gasm_->effect(), initial_effect_); DCHECK_EQ(gasm_->control(), initial_control_); // Unlike IfBuilder1, this supports an empty then or else body. This is // possible since the merge does not take any value inputs. DCHECK(then_body_ || else_body_); if (negate_cond_) std::swap(then_body_, else_body_); auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel() : gasm_->MakeLabel(); auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel() : gasm_->MakeLabel(); auto merge = gasm_->MakeLabel(); gasm_->Branch(cond_, &if_true, &if_false); gasm_->Bind(&if_true); if (then_body_) then_body_(); gasm_->Goto(&merge); gasm_->Bind(&if_false); if (else_body_) else_body_(); gasm_->Goto(&merge); gasm_->Bind(&merge); } private: JSGraphAssembler* const gasm_; const TNode cond_; const bool negate_cond_; const Effect initial_effect_; const Control initial_control_; BranchHint hint_ = BranchHint::kNone; VoidGenerator0 then_body_; VoidGenerator0 else_body_; DISALLOW_COPY_AND_ASSIGN(IfBuilder0); }; IfBuilder0 If(TNode cond) { return {this, cond, false}; } IfBuilder0 IfNot(TNode cond) { return {this, cond, true}; } template class IfBuilder1 { using If1BodyFunction = std::function()>; public: IfBuilder1(JSGraphAssembler* gasm, TNode cond) : gasm_(gasm), cond_(cond) {} V8_WARN_UNUSED_RESULT IfBuilder1& ExpectTrue() { DCHECK_EQ(hint_, BranchHint::kNone); hint_ = BranchHint::kTrue; return *this; } V8_WARN_UNUSED_RESULT IfBuilder1& ExpectFalse() { DCHECK_EQ(hint_, BranchHint::kNone); hint_ = BranchHint::kFalse; return *this; } V8_WARN_UNUSED_RESULT IfBuilder1& Then(const If1BodyFunction& body) { then_body_ = body; return *this; } V8_WARN_UNUSED_RESULT IfBuilder1& Else(const If1BodyFunction& body) { else_body_ = body; return *this; } V8_WARN_UNUSED_RESULT TNode Value() { DCHECK(then_body_); DCHECK(else_body_); auto if_true = (hint_ == BranchHint::kFalse) ? gasm_->MakeDeferredLabel() : gasm_->MakeLabel(); auto if_false = (hint_ == BranchHint::kTrue) ? gasm_->MakeDeferredLabel() : gasm_->MakeLabel(); auto merge = gasm_->MakeLabel(kPhiRepresentation); gasm_->Branch(cond_, &if_true, &if_false); gasm_->Bind(&if_true); TNode then_result = then_body_(); gasm_->Goto(&merge, then_result); gasm_->Bind(&if_false); TNode else_result = else_body_(); gasm_->Goto(&merge, else_result); gasm_->Bind(&merge); return merge.PhiAt(0); } private: static constexpr MachineRepresentation kPhiRepresentation = MachineRepresentation::kTagged; JSGraphAssembler* const gasm_; const TNode cond_; BranchHint hint_ = BranchHint::kNone; If1BodyFunction then_body_; If1BodyFunction else_body_; }; template IfBuilder1 SelectIf(TNode cond) { return {this, cond}; } // Simplified operators. TNode SpeculativeToNumber( TNode value, NumberOperationHint hint = NumberOperationHint::kNumberOrOddball); TNode CheckSmi(TNode value); TNode CheckString(TNode value); TNode CheckBounds(TNode value, TNode limit); // Common operators. TNode TypeGuardUnsignedSmall(TNode value); TNode TypeGuardNonInternal(TNode value); TNode TypeGuardFixedArrayLength(TNode value); TNode Call4(const Callable& callable, TNode context, TNode arg0, TNode arg1, TNode arg2, TNode arg3); // Javascript operators. TNode JSCall3(TNode function, TNode this_arg, TNode arg0, TNode arg1, TNode arg2, FrameState frame_state); TNode JSCall4(TNode function, TNode this_arg, TNode arg0, TNode arg1, TNode arg2, TNode arg3, FrameState frame_state); TNode JSCallRuntime2(Runtime::FunctionId function_id, TNode arg0, TNode arg1, FrameState frame_state); // Used in special cases in which we are certain CreateArray does not throw. TNode CreateArrayNoThrow(TNode ctor, TNode size); TNode AllocateEmptyJSArray(ElementsKind kind, const NativeContextRef& native_context); TNode NumberInc(TNode value) { return NumberAdd(value, OneConstant()); } void MaybeInsertMapChecks(MapInference* inference, bool has_stability_dependency) { // TODO(jgruber): Implement MapInference::InsertMapChecks in graph // assembler. if (!has_stability_dependency) { Node* e = effect(); inference->InsertMapChecks(jsgraph(), &e, control(), feedback()); InitializeEffectControl(e, control()); } } // TODO(jgruber): Currently, it's the responsibility of the developer to note // which operations may throw and appropriately wrap these in a call to // MayThrow (see e.g. JSCall3 and CallRuntime2). A more methodical approach // would be good. TNode MayThrow(const NodeGenerator0& body) { TNode result = body(); if (catch_scope()->has_handler()) { // The IfException node is later merged into the outer graph. // Note: AddNode is intentionally not called since effect and control // should not be updated. Node* if_exception = graph()->NewNode(common()->IfException(), effect(), control()); catch_scope()->RegisterIfExceptionNode(if_exception); // Control resumes here. AddNode(graph()->NewNode(common()->IfSuccess(), control())); } return result; } // A catch scope represents a single catch handler. The handler can be // custom catch logic within the reduction itself; or a catch handler in the // outside graph into which the reduction will be integrated (in this case // the scope is called 'outermost'). class CatchScope { private: // Only used to partially construct the outermost scope. explicit CatchScope(Zone* zone) : if_exception_nodes_(zone) {} // For all inner scopes. CatchScope(Zone* zone, JSCallReducerAssembler* gasm) : gasm_(gasm), parent_(gasm->catch_scope_), has_handler_(true), if_exception_nodes_(zone) { gasm_->catch_scope_ = this; } public: ~CatchScope() { gasm_->catch_scope_ = parent_; } static CatchScope Outermost(Zone* zone) { return CatchScope{zone}; } static CatchScope Inner(Zone* zone, JSCallReducerAssembler* gasm) { return {zone, gasm}; } bool has_handler() const { return has_handler_; } bool is_outermost() const { return parent_ == nullptr; } CatchScope* parent() const { return parent_; } // Should only be used to initialize the outermost scope (inner scopes // always have a handler and are passed the gasm pointer at construction). void set_has_handler(bool v) { DCHECK(is_outermost()); has_handler_ = v; } void set_gasm(JSCallReducerAssembler* v) { DCHECK(is_outermost()); gasm_ = v; } bool has_exceptional_control_flow() const { return !if_exception_nodes_.empty(); } void RegisterIfExceptionNode(Node* if_exception) { DCHECK(has_handler()); if_exception_nodes_.push_back(if_exception); } void MergeExceptionalPaths(TNode* exception_out, Effect* effect_out, Control* control_out) { DCHECK(has_handler()); DCHECK(has_exceptional_control_flow()); const int size = static_cast(if_exception_nodes_.size()); if (size == 1) { // No merge needed. Node* e = if_exception_nodes_.at(0); *exception_out = TNode::UncheckedCast(e); *effect_out = Effect(e); *control_out = Control(e); } else { DCHECK_GT(size, 1); Node* merge = gasm_->graph()->NewNode(gasm_->common()->Merge(size), size, if_exception_nodes_.data()); // These phis additionally take {merge} as an input. Temporarily add // it to the list. if_exception_nodes_.push_back(merge); const int size_with_merge = static_cast(if_exception_nodes_.size()); Node* ephi = gasm_->graph()->NewNode(gasm_->common()->EffectPhi(size), size_with_merge, if_exception_nodes_.data()); Node* phi = gasm_->graph()->NewNode( gasm_->common()->Phi(MachineRepresentation::kTagged, size), size_with_merge, if_exception_nodes_.data()); if_exception_nodes_.pop_back(); *exception_out = TNode::UncheckedCast(phi); *effect_out = Effect(ephi); *control_out = Control(merge); } } private: JSCallReducerAssembler* gasm_ = nullptr; CatchScope* const parent_ = nullptr; bool has_handler_ = false; NodeVector if_exception_nodes_; }; class TryCatchBuilder0 { public: using TryFunction = VoidGenerator0; using CatchFunction = std::function)>; TryCatchBuilder0(JSCallReducerAssembler* gasm, const TryFunction& try_body) : gasm_(gasm), try_body_(try_body) {} void Catch(const CatchFunction& catch_body) { TNode handler_exception; Effect handler_effect{nullptr}; Control handler_control{nullptr}; auto continuation = gasm_->MakeLabel(); // Try. { CatchScope catch_scope = CatchScope::Inner(gasm_->temp_zone(), gasm_); try_body_(); gasm_->Goto(&continuation); catch_scope.MergeExceptionalPaths(&handler_exception, &handler_effect, &handler_control); } // Catch. { gasm_->InitializeEffectControl(handler_effect, handler_control); catch_body(handler_exception); gasm_->Goto(&continuation); } gasm_->Bind(&continuation); } private: JSCallReducerAssembler* const gasm_; const VoidGenerator0 try_body_; }; TryCatchBuilder0 Try(const VoidGenerator0& try_body) { return {this, try_body}; } using ConditionFunction1 = std::function(TNode)>; using StepFunction1 = std::function(TNode)>; class ForBuilder0 { using For0BodyFunction = std::function)>; public: ForBuilder0(JSGraphAssembler* gasm, TNode initial_value, const ConditionFunction1& cond, const StepFunction1& step) : gasm_(gasm), initial_value_(initial_value), cond_(cond), step_(step) {} void Do(const For0BodyFunction& body) { auto loop_exit = gasm_->MakeLabel(); { GraphAssembler::LoopScope loop_scope(gasm_); auto loop_header = loop_scope.loop_header_label(); auto loop_body = gasm_->MakeLabel(); gasm_->Goto(loop_header, initial_value_); gasm_->Bind(loop_header); TNode i = loop_header->PhiAt(0); gasm_->BranchWithHint(cond_(i), &loop_body, &loop_exit, BranchHint::kTrue); gasm_->Bind(&loop_body); body(i); gasm_->Goto(loop_header, step_(i)); } gasm_->Bind(&loop_exit); } private: static constexpr MachineRepresentation kPhiRepresentation = MachineRepresentation::kTagged; JSGraphAssembler* const gasm_; const TNode initial_value_; const ConditionFunction1 cond_; const StepFunction1 step_; }; ForBuilder0 ForZeroUntil(TNode excluded_limit) { TNode initial_value = ZeroConstant(); auto cond = [=](TNode i) { return NumberLessThan(i, excluded_limit); }; auto step = [=](TNode i) { return NumberAdd(i, OneConstant()); }; return {this, initial_value, cond, step}; } ForBuilder0 Forever(TNode initial_value, const StepFunction1& step) { return {this, initial_value, [=](TNode) { return TrueConstant(); }, step}; } using For1BodyFunction = std::function, TNode*)>; class ForBuilder1 { public: ForBuilder1(JSGraphAssembler* gasm, TNode initial_value, const ConditionFunction1& cond, const StepFunction1& step, TNode initial_arg0) : gasm_(gasm), initial_value_(initial_value), cond_(cond), step_(step), initial_arg0_(initial_arg0) {} V8_WARN_UNUSED_RESULT ForBuilder1& Do(const For1BodyFunction& body) { body_ = body; return *this; } V8_WARN_UNUSED_RESULT TNode Value() { DCHECK(body_); TNode arg0 = initial_arg0_; auto loop_exit = gasm_->MakeDeferredLabel(kPhiRepresentation); { GraphAssembler::LoopScope loop_scope(gasm_); auto loop_header = loop_scope.loop_header_label(); auto loop_body = gasm_->MakeDeferredLabel(kPhiRepresentation); gasm_->Goto(loop_header, initial_value_, initial_arg0_); gasm_->Bind(loop_header); TNode i = loop_header->PhiAt(0); arg0 = loop_header->PhiAt(1); gasm_->BranchWithHint(cond_(i), &loop_body, &loop_exit, BranchHint::kTrue, arg0); gasm_->Bind(&loop_body); body_(i, &arg0); gasm_->Goto(loop_header, step_(i), arg0); } gasm_->Bind(&loop_exit); return TNode::UncheckedCast(loop_exit.PhiAt(0)); } void ValueIsUnused() { USE(Value()); } private: static constexpr MachineRepresentation kPhiRepresentation = MachineRepresentation::kTagged; JSGraphAssembler* const gasm_; const TNode initial_value_; const ConditionFunction1 cond_; const StepFunction1 step_; For1BodyFunction body_; const TNode initial_arg0_; }; ForBuilder1 For1(TNode initial_value, const ConditionFunction1& cond, const StepFunction1& step, TNode initial_arg0) { return {this, initial_value, cond, step, initial_arg0}; } ForBuilder1 For1ZeroUntil(TNode excluded_limit, TNode initial_arg0) { TNode initial_value = ZeroConstant(); auto cond = [=](TNode i) { return NumberLessThan(i, excluded_limit); }; auto step = [=](TNode i) { return NumberAdd(i, OneConstant()); }; return {this, initial_value, cond, step, initial_arg0}; } void ThrowIfNotCallable(TNode maybe_callable, FrameState frame_state) { IfNot(ObjectIsCallable(maybe_callable)) .Then(_ { JSCallRuntime2(Runtime::kThrowTypeError, NumberConstant(static_cast( MessageTemplate::kCalledNonCallable)), maybe_callable, frame_state); Unreachable(); // The runtime call throws unconditionally. }) .ExpectTrue(); } const FeedbackSource& feedback() const { CallParameters const& p = CallParametersOf(node_ptr()->op()); return p.feedback(); } int ValueInputCount() const { return node_ptr()->op()->ValueInputCount(); } TNode ValueInput(int index) const { return TNode::UncheckedCast( NodeProperties::GetValueInput(node_, index)); } template TNode ValueInputAs(int index) const { return TNode::UncheckedCast(ValueInput(index)); } TNode ValueInputOrNaN(int index) { return TNode::UncheckedCast( ValueInputCount() > index ? NodeProperties::GetValueInput(node_, index) : NaNConstant()); } TNode ValueInputOrUndefined(int index) { return TNode::UncheckedCast( ValueInputCount() > index ? NodeProperties::GetValueInput(node_, index) : UndefinedConstant()); } TNode ValueInputOrZero(int index) { return TNode::UncheckedCast( ValueInputCount() > index ? NodeProperties::GetValueInput(node_, index) : ZeroConstant()); } TNode ContextInput() const { return TNode::UncheckedCast( NodeProperties::GetContextInput(node_)); } FrameState FrameStateInput() const { return FrameState(NodeProperties::GetFrameStateInput(node_)); } JSOperatorBuilder* javascript() const { return jsgraph()->javascript(); } private: Node* const node_; CatchScope outermost_catch_scope_; Node* outermost_handler_; CatchScope* catch_scope_; friend class CatchScope; }; enum class ArrayReduceDirection { kLeft, kRight }; enum class ArrayFindVariant { kFind, kFindIndex }; enum class ArrayEverySomeVariant { kEvery, kSome }; enum class ArrayIndexOfIncludesVariant { kIncludes, kIndexOf }; // This subclass bundles functionality specific to reducing iterating array // builtins. class IteratingArrayBuiltinReducerAssembler : public JSCallReducerAssembler { public: IteratingArrayBuiltinReducerAssembler(JSGraph* jsgraph, Zone* zone, Node* node) : JSCallReducerAssembler(jsgraph, zone, node) { DCHECK(FLAG_turbo_inline_array_builtins); } TNode ReduceArrayPrototypeForEach( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared); TNode ReduceArrayPrototypeReduce(MapInference* inference, const bool has_stability_dependency, ElementsKind kind, ArrayReduceDirection direction, const SharedFunctionInfoRef& shared); TNode ReduceArrayPrototypeMap( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context); TNode ReduceArrayPrototypeFilter( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context); TNode ReduceArrayPrototypeFind(MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context, ArrayFindVariant variant); TNode ReduceArrayPrototypeEverySome( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context, ArrayEverySomeVariant variant); TNode ReduceArrayPrototypeIndexOfIncludes( ElementsKind kind, ArrayIndexOfIncludesVariant variant); private: // Returns {index,value}. Assumes that the map has not changed, but possibly // the length and backing store. std::pair, TNode> SafeLoadElement(ElementsKind kind, TNode o, TNode index) { // Make sure that the access is still in bounds, since the callback could // have changed the array's size. TNode length = LoadJSArrayLength(o, kind); index = CheckBounds(index, length); // Reload the elements pointer before calling the callback, since the // previous callback might have resized the array causing the elements // buffer to be re-allocated. TNode elements = LoadField(AccessBuilder::ForJSObjectElements(), o); TNode value = LoadElement( AccessBuilder::ForFixedArrayElement(kind, LoadSensitivity::kCritical), elements, index); return std::make_pair(index, value); } template TNode MaybeSkipHole( TNode o, ElementsKind kind, GraphAssemblerLabel* continue_label, TNode... vars) { if (!IsHoleyElementsKind(kind)) return o; std::array reps = { MachineRepresentationOf::value...}; auto if_not_hole = MakeLabel(reps, GraphAssemblerLabelType::kNonDeferred); BranchWithHint(HoleCheck(kind, o), continue_label, &if_not_hole, BranchHint::kFalse, vars...); // The contract is that we don't leak "the hole" into "user JavaScript", // so we must rename the {element} here to explicitly exclude "the hole" // from the type of {element}. Bind(&if_not_hole); return TypeGuardNonInternal(o); } TNode LoadJSArrayLength(TNode array, ElementsKind kind) { return LoadField(AccessBuilder::ForJSArrayLength(kind), array); } void StoreJSArrayLength(TNode array, TNode value, ElementsKind kind) { StoreField(AccessBuilder::ForJSArrayLength(kind), array, value); } void StoreFixedArrayBaseElement(TNode o, TNode index, TNode v, ElementsKind kind) { StoreElement(AccessBuilder::ForFixedArrayElement(kind), o, index, v); } TNode LoadElements(TNode o) { return LoadField(AccessBuilder::ForJSObjectElements(), o); } TNode LoadFixedArrayBaseLength(TNode o) { return LoadField(AccessBuilder::ForFixedArrayLength(), o); } TNode HoleCheck(ElementsKind kind, TNode v) { return IsDoubleElementsKind(kind) ? NumberIsFloat64Hole(TNode::UncheckedCast(v)) : IsTheHole(v); } TNode CheckFloat64Hole(TNode value, CheckFloat64HoleMode mode) { return AddNode( graph()->NewNode(simplified()->CheckFloat64Hole(mode, feedback()), value, effect(), control())); } // May deopt for holey double elements. TNode TryConvertHoleToUndefined(TNode value, ElementsKind kind) { DCHECK(IsHoleyElementsKind(kind)); if (kind == HOLEY_DOUBLE_ELEMENTS) { // TODO(7409): avoid deopt if not all uses of value are truncated. TNode number = TNode::UncheckedCast(value); return CheckFloat64Hole(number, CheckFloat64HoleMode::kAllowReturnHole); } return ConvertTaggedHoleToUndefined(value); } }; class PromiseBuiltinReducerAssembler : public JSCallReducerAssembler { public: PromiseBuiltinReducerAssembler(JSGraph* jsgraph, Zone* zone, Node* node, JSHeapBroker* broker) : JSCallReducerAssembler(jsgraph, zone, node), broker_(broker) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); } TNode ReducePromiseConstructor( const NativeContextRef& native_context); int ConstructArity() const { DCHECK_EQ(IrOpcode::kJSConstruct, node_ptr()->opcode()); ConstructParameters const& p = ConstructParametersOf(node_ptr()->op()); return p.arity_without_implicit_args(); } TNode NewTargetInput() const { // new_target is the last input (after target and all other args). static constexpr int kTarget = 1; return TNode::UncheckedCast( NodeProperties::GetValueInput(node_ptr(), ConstructArity() + kTarget)); } private: TNode CreatePromise(TNode context) { return AddNode( graph()->NewNode(javascript()->CreatePromise(), context, effect())); } TNode CreateFunctionContext(const NativeContextRef& native_context, TNode outer_context, int slot_count) { return AddNode(graph()->NewNode( javascript()->CreateFunctionContext( native_context.scope_info().object(), slot_count - Context::MIN_CONTEXT_SLOTS, FUNCTION_SCOPE), outer_context, effect(), control())); } void StoreContextSlot(TNode context, size_t slot_index, TNode value) { StoreField(AccessBuilder::ForContextSlot(slot_index), context, value); } TNode CreateClosureFromBuiltinSharedFunctionInfo( SharedFunctionInfoRef shared, TNode context) { DCHECK(shared.HasBuiltinId()); Callable const callable = Builtins::CallableFor( isolate(), static_cast(shared.builtin_id())); return AddNode(graph()->NewNode( javascript()->CreateClosure(shared.object(), isolate()->factory()->many_closures_cell(), callable.code()), context, effect(), control())); } void CallPromiseExecutor(TNode executor, TNode resolve, TNode reject, FrameState frame_state) { const ConstructParameters& p = ConstructParametersOf(node_ptr()->op()); FeedbackSource no_feedback_source{}; MayThrow(_ { return AddNode(graph()->NewNode( javascript()->Call(2 + kTargetAndReceiver, p.frequency(), no_feedback_source, ConvertReceiverMode::kNullOrUndefined), executor, UndefinedConstant(), resolve, reject, ContextInput(), frame_state, effect(), control())); }); } void CallPromiseReject(TNode reject, TNode exception, FrameState frame_state) { const ConstructParameters& p = ConstructParametersOf(node_ptr()->op()); FeedbackSource no_feedback_source{}; MayThrow(_ { return AddNode(graph()->NewNode( javascript()->Call(1 + kTargetAndReceiver, p.frequency(), no_feedback_source, ConvertReceiverMode::kNullOrUndefined), reject, UndefinedConstant(), exception, ContextInput(), frame_state, effect(), control())); }); } JSHeapBroker* const broker_; }; class FastApiCallReducerAssembler : public JSCallReducerAssembler { public: FastApiCallReducerAssembler(JSGraph* jsgraph, Zone* zone, Node* node, Address c_function, const CFunctionInfo* c_signature) : JSCallReducerAssembler(jsgraph, zone, node), c_function_(c_function), c_signature_(c_signature) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); DCHECK_NE(c_function_, kNullAddress); CHECK_NOT_NULL(c_signature_); } TNode ReduceFastApiCall() { int c_arg_count = c_signature_->ArgumentCount(); Node* function_node = ExternalConstant(ExternalReference::Create(c_function_)); base::SmallVector inputs(0); inputs.emplace_back(function_node); int wrapper_object_index = isolate()->embedder_wrapper_object_index(); CHECK_GE(wrapper_object_index, 0); for (int i = 0; i < c_arg_count; ++i) { if (i + kFunctionArgCount < ValueInputCount()) { inputs.emplace_back(ConvertArgumentIfJSWrapper( c_signature_->ArgumentInfo(i).GetType(), ValueInput(i + kFunctionArgCount), wrapper_object_index)); } else { inputs.emplace_back(UndefinedConstant()); } } inputs.emplace_back(effect()); inputs.emplace_back(control()); return FastApiCall(inputs); } private: static constexpr int kFunctionArgCount = 1; static constexpr int kExtraInputsCount = kFunctionArgCount + 2; // effect, control static constexpr int kInlineSize = 10; TNode FastApiCall( base::SmallVector const& inputs) { return AddNode( graph()->NewNode(simplified()->FastApiCall(c_signature_, feedback()), static_cast(inputs.size()), inputs.begin())); } TNode UnwrapApiObject(TNode node, int wrapper_object_index) { const int offset = Internals::kJSObjectHeaderSize + (Internals::kEmbedderDataSlotSize * wrapper_object_index); FieldAccess access( kTaggedBase, offset, MaybeHandle(), MaybeHandle(), V8_HEAP_SANDBOX_BOOL ? Type::SandboxedExternalPointer() : Type::Any(), MachineType::Pointer(), WriteBarrierKind::kNoWriteBarrier); TNode load = AddNode(graph()->NewNode( simplified()->LoadField(access), node, effect(), control())); return load; } Node* ConvertArgumentIfJSWrapper(CTypeInfo::Type type, TNode node, int wrapper_object_index) { switch (type) { case CTypeInfo::Type::kUnwrappedApiObject: // This call assumes that {node} is a JSObject with an internal field // set to a C pointer. It should fail in all other cases. // TODO(mslekova): Implement instanceOf check for the C pointer type. // TODO(mslekova): Introduce a GraphAssembler primitive for safe cast. return UnwrapApiObject(TNode::UncheckedCast(node), wrapper_object_index); default: return node; } } const Address c_function_; const CFunctionInfo* const c_signature_; }; TNode JSCallReducerAssembler::SpeculativeToNumber( TNode value, NumberOperationHint hint) { return AddNode( graph()->NewNode(simplified()->SpeculativeToNumber(hint, feedback()), value, effect(), control())); } TNode JSCallReducerAssembler::CheckSmi(TNode value) { return AddNode(graph()->NewNode(simplified()->CheckSmi(feedback()), value, effect(), control())); } TNode JSCallReducerAssembler::CheckString(TNode value) { return AddNode(graph()->NewNode(simplified()->CheckString(feedback()), value, effect(), control())); } TNode JSCallReducerAssembler::CheckBounds(TNode value, TNode limit) { return AddNode(graph()->NewNode(simplified()->CheckBounds(feedback()), value, limit, effect(), control())); } TNode JSCallReducerAssembler::TypeGuardUnsignedSmall(TNode value) { return TNode::UncheckedCast(TypeGuard(Type::UnsignedSmall(), value)); } TNode JSCallReducerAssembler::TypeGuardNonInternal( TNode value) { return TNode::UncheckedCast(TypeGuard(Type::NonInternal(), value)); } TNode JSCallReducerAssembler::TypeGuardFixedArrayLength( TNode value) { DCHECK(TypeCache::Get()->kFixedDoubleArrayLengthType.Is( TypeCache::Get()->kFixedArrayLengthType)); return TNode::UncheckedCast( TypeGuard(TypeCache::Get()->kFixedArrayLengthType, value)); } TNode JSCallReducerAssembler::Call4( const Callable& callable, TNode context, TNode arg0, TNode arg1, TNode arg2, TNode arg3) { // TODO(jgruber): Make this more generic. Currently it's fitted to its single // callsite. CallDescriptor* desc = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags, Operator::kEliminatable); return TNode::UncheckedCast(Call(desc, HeapConstant(callable.code()), arg0, arg1, arg2, arg3, context)); } TNode JSCallReducerAssembler::JSCall3( TNode function, TNode this_arg, TNode arg0, TNode arg1, TNode arg2, FrameState frame_state) { CallParameters const& p = CallParametersOf(node_ptr()->op()); return MayThrow(_ { return AddNode(graph()->NewNode( javascript()->Call(3 + kTargetAndReceiver, p.frequency(), p.feedback(), ConvertReceiverMode::kAny, p.speculation_mode(), CallFeedbackRelation::kUnrelated), function, this_arg, arg0, arg1, arg2, ContextInput(), frame_state, effect(), control())); }); } TNode JSCallReducerAssembler::JSCall4( TNode function, TNode this_arg, TNode arg0, TNode arg1, TNode arg2, TNode arg3, FrameState frame_state) { CallParameters const& p = CallParametersOf(node_ptr()->op()); return MayThrow(_ { return AddNode(graph()->NewNode( javascript()->Call(4 + kTargetAndReceiver, p.frequency(), p.feedback(), ConvertReceiverMode::kAny, p.speculation_mode(), CallFeedbackRelation::kUnrelated), function, this_arg, arg0, arg1, arg2, arg3, ContextInput(), frame_state, effect(), control())); }); } TNode JSCallReducerAssembler::JSCallRuntime2( Runtime::FunctionId function_id, TNode arg0, TNode arg1, FrameState frame_state) { return MayThrow(_ { return AddNode( graph()->NewNode(javascript()->CallRuntime(function_id, 2), arg0, arg1, ContextInput(), frame_state, effect(), control())); }); } TNode JSCallReducerAssembler::CreateArrayNoThrow(TNode ctor, TNode size) { return AddNode(graph()->NewNode( javascript()->CreateArray(1, MaybeHandle()), ctor, ctor, size, ContextInput(), FrameStateInput(), effect(), control())); } TNode JSCallReducerAssembler::AllocateEmptyJSArray( ElementsKind kind, const NativeContextRef& native_context) { // TODO(jgruber): Port AllocationBuilder to JSGraphAssembler. MapRef map = native_context.GetInitialJSArrayMap(kind); AllocationBuilder ab(jsgraph(), effect(), control()); ab.Allocate(map.instance_size(), AllocationType::kYoung, Type::Array()); ab.Store(AccessBuilder::ForMap(), map); Node* empty_fixed_array = jsgraph()->EmptyFixedArrayConstant(); ab.Store(AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer(), empty_fixed_array); ab.Store(AccessBuilder::ForJSObjectElements(), empty_fixed_array); ab.Store(AccessBuilder::ForJSArrayLength(kind), jsgraph()->ZeroConstant()); for (int i = 0; i < map.GetInObjectProperties(); ++i) { ab.Store(AccessBuilder::ForJSObjectInObjectProperty(map, i), jsgraph()->UndefinedConstant()); } Node* result = ab.Finish(); InitializeEffectControl(result, control()); return TNode::UncheckedCast(result); } TNode JSCallReducerAssembler::ReduceMathUnary(const Operator* op) { TNode input = ValueInput(2); TNode input_as_number = SpeculativeToNumber(input); return TNode::UncheckedCast(graph()->NewNode(op, input_as_number)); } TNode JSCallReducerAssembler::ReduceMathBinary(const Operator* op) { TNode left = ValueInput(2); TNode right = ValueInputOrNaN(3); TNode left_number = SpeculativeToNumber(left); TNode right_number = SpeculativeToNumber(right); return TNode::UncheckedCast( graph()->NewNode(op, left_number, right_number)); } TNode JSCallReducerAssembler::ReduceStringPrototypeSubstring() { TNode receiver = ValueInput(1); TNode start = ValueInput(2); TNode end = ValueInputOrUndefined(3); TNode receiver_string = CheckString(receiver); TNode start_smi = CheckSmi(start); TNode length = StringLength(receiver_string); TNode end_smi = SelectIf(IsUndefined(end)) .Then(_ { return length; }) .Else(_ { return CheckSmi(end); }) .ExpectFalse() .Value(); TNode zero = TNode::UncheckedCast(ZeroConstant()); TNode finalStart = NumberMin(NumberMax(start_smi, zero), length); TNode finalEnd = NumberMin(NumberMax(end_smi, zero), length); TNode from = NumberMin(finalStart, finalEnd); TNode to = NumberMax(finalStart, finalEnd); return StringSubstring(receiver_string, from, to); } TNode JSCallReducerAssembler::ReduceStringPrototypeSlice() { TNode receiver = ValueInput(1); TNode start = ValueInput(2); TNode end = ValueInputOrUndefined(3); TNode receiver_string = CheckString(receiver); TNode start_smi = CheckSmi(start); TNode length = StringLength(receiver_string); TNode end_smi = SelectIf(IsUndefined(end)) .Then(_ { return length; }) .Else(_ { return CheckSmi(end); }) .ExpectFalse() .Value(); TNode zero = TNode::UncheckedCast(ZeroConstant()); TNode from_untyped = SelectIf(NumberLessThan(start_smi, zero)) .Then(_ { return NumberMax(NumberAdd(length, start_smi), zero); }) .Else(_ { return NumberMin(start_smi, length); }) .ExpectFalse() .Value(); // {from} is always in non-negative Smi range, but our typer cannot figure // that out yet. TNode from = TypeGuardUnsignedSmall(from_untyped); TNode to_untyped = SelectIf(NumberLessThan(end_smi, zero)) .Then(_ { return NumberMax(NumberAdd(length, end_smi), zero); }) .Else(_ { return NumberMin(end_smi, length); }) .ExpectFalse() .Value(); // {to} is always in non-negative Smi range, but our typer cannot figure that // out yet. TNode to = TypeGuardUnsignedSmall(to_untyped); return SelectIf(NumberLessThan(from, to)) .Then(_ { return StringSubstring(receiver_string, from, to); }) .Else(_ { return EmptyStringConstant(); }) .ExpectTrue() .Value(); } namespace { struct ForEachFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; TNode context; TNode target; FrameState outer_frame_state; TNode receiver; TNode callback; TNode this_arg; TNode original_length; }; FrameState ForEachLoopLazyFrameState(const ForEachFrameStateParams& params, TNode k) { Builtins::Name builtin = Builtins::kArrayForEachLoopLazyDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState ForEachLoopEagerFrameState(const ForEachFrameStateParams& params, TNode k) { Builtins::Name builtin = Builtins::kArrayForEachLoopEagerDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeForEach( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared) { FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode receiver = ValueInputAs(1); TNode fncallback = ValueInputOrUndefined(2); TNode this_arg = ValueInputOrUndefined(3); TNode original_length = LoadJSArrayLength(receiver, kind); ForEachFrameStateParams frame_state_params{ jsgraph(), shared, context, target, outer_frame_state, receiver, fncallback, this_arg, original_length}; ThrowIfNotCallable(fncallback, ForEachLoopLazyFrameState(frame_state_params, ZeroConstant())); ForZeroUntil(original_length).Do([&](TNode k) { Checkpoint(ForEachLoopEagerFrameState(frame_state_params, k)); // Deopt if the map has changed during the iteration. MaybeInsertMapChecks(inference, has_stability_dependency); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); auto continue_label = MakeLabel(); element = MaybeSkipHole(element, kind, &continue_label); TNode next_k = NumberAdd(k, OneConstant()); JSCall3(fncallback, this_arg, element, k, receiver, ForEachLoopLazyFrameState(frame_state_params, next_k)); Goto(&continue_label); Bind(&continue_label); }); return UndefinedConstant(); } namespace { struct ReduceFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; ArrayReduceDirection direction; TNode context; TNode target; FrameState outer_frame_state; }; FrameState ReducePreLoopLazyFrameState(const ReduceFrameStateParams& params, TNode receiver, TNode callback, TNode k, TNode original_length) { Builtins::Name builtin = (params.direction == ArrayReduceDirection::kLeft) ? Builtins::kArrayReduceLoopLazyDeoptContinuation : Builtins::kArrayReduceRightLoopLazyDeoptContinuation; Node* checkpoint_params[] = {receiver, callback, k, original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState ReducePreLoopEagerFrameState(const ReduceFrameStateParams& params, TNode receiver, TNode callback, TNode original_length) { Builtins::Name builtin = (params.direction == ArrayReduceDirection::kLeft) ? Builtins::kArrayReducePreLoopEagerDeoptContinuation : Builtins::kArrayReduceRightPreLoopEagerDeoptContinuation; Node* checkpoint_params[] = {receiver, callback, original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } FrameState ReduceLoopLazyFrameState(const ReduceFrameStateParams& params, TNode receiver, TNode callback, TNode k, TNode original_length) { Builtins::Name builtin = (params.direction == ArrayReduceDirection::kLeft) ? Builtins::kArrayReduceLoopLazyDeoptContinuation : Builtins::kArrayReduceRightLoopLazyDeoptContinuation; Node* checkpoint_params[] = {receiver, callback, k, original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState ReduceLoopEagerFrameState(const ReduceFrameStateParams& params, TNode receiver, TNode callback, TNode k, TNode original_length, TNode accumulator) { Builtins::Name builtin = (params.direction == ArrayReduceDirection::kLeft) ? Builtins::kArrayReduceLoopEagerDeoptContinuation : Builtins::kArrayReduceRightLoopEagerDeoptContinuation; Node* checkpoint_params[] = {receiver, callback, k, original_length, accumulator}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeReduce( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, ArrayReduceDirection direction, const SharedFunctionInfoRef& shared) { FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode receiver = ValueInputAs(1); TNode fncallback = ValueInputOrUndefined(2); ReduceFrameStateParams frame_state_params{ jsgraph(), shared, direction, context, target, outer_frame_state}; TNode original_length = LoadJSArrayLength(receiver, kind); // Set up variable behavior depending on the reduction kind (left/right). TNode k; StepFunction1 step; ConditionFunction1 cond; TNode zero = ZeroConstant(); TNode one = OneConstant(); if (direction == ArrayReduceDirection::kLeft) { k = zero; step = [&](TNode i) { return NumberAdd(i, one); }; cond = [&](TNode i) { return NumberLessThan(i, original_length); }; } else { k = NumberSubtract(original_length, one); step = [&](TNode i) { return NumberSubtract(i, one); }; cond = [&](TNode i) { return NumberLessThanOrEqual(zero, i); }; } ThrowIfNotCallable( fncallback, ReducePreLoopLazyFrameState(frame_state_params, receiver, fncallback, k, original_length)); // Set initial accumulator value. TNode accumulator; if (node_ptr()->op()->ValueInputCount() > 3) { accumulator = ValueInput(3); // Initial value specified by the user. } else { // The initial value was not specified by the user. In this case, the first // (or last in the case of reduceRight) non-holey value of the array is // used. Loop until we find it. If not found, trigger a deopt. // TODO(jgruber): The deopt does not seem necessary. Instead we could simply // throw the TypeError here from optimized code. auto found_initial_element = MakeLabel(MachineRepresentation::kTagged, MachineRepresentation::kTagged); Forever(k, step).Do([&](TNode k) { Checkpoint(ReducePreLoopEagerFrameState(frame_state_params, receiver, fncallback, original_length)); CheckIf(cond(k), DeoptimizeReason::kNoInitialElement); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); auto continue_label = MakeLabel(); GotoIf(HoleCheck(kind, element), &continue_label); Goto(&found_initial_element, k, TypeGuardNonInternal(element)); Bind(&continue_label); }); Unreachable(); // The loop is exited either by deopt or a jump to below. InitializeEffectControl(nullptr, nullptr); // TODO(jgruber): This manual fiddling with blocks could be avoided by // implementing a `break` mechanic for loop builders. Bind(&found_initial_element); k = step(found_initial_element.PhiAt(0)); accumulator = found_initial_element.PhiAt(1); } TNode result = For1(k, cond, step, accumulator) .Do([&](TNode k, TNode* accumulator) { Checkpoint(ReduceLoopEagerFrameState(frame_state_params, receiver, fncallback, k, original_length, *accumulator)); // Deopt if the map has changed during the iteration. MaybeInsertMapChecks(inference, has_stability_dependency); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); auto continue_label = MakeLabel(MachineRepresentation::kTagged); element = MaybeSkipHole(element, kind, &continue_label, *accumulator); TNode next_k = step(k); TNode next_accumulator = JSCall4( fncallback, UndefinedConstant(), *accumulator, element, k, receiver, ReduceLoopLazyFrameState(frame_state_params, receiver, fncallback, next_k, original_length)); Goto(&continue_label, next_accumulator); Bind(&continue_label); *accumulator = continue_label.PhiAt(0); }) .Value(); return result; } namespace { struct MapFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; TNode context; TNode target; FrameState outer_frame_state; TNode receiver; TNode callback; TNode this_arg; TNode a; TNode original_length; }; FrameState MapLoopLazyFrameState(const MapFrameStateParams& params, TNode k) { Node* checkpoint_params[] = { params.receiver, params.callback, params.this_arg, params.a, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, Builtins::kArrayMapLoopLazyDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState MapLoopEagerFrameState(const MapFrameStateParams& params, TNode k) { Node* checkpoint_params[] = { params.receiver, params.callback, params.this_arg, params.a, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, Builtins::kArrayMapLoopEagerDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeMap( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context) { FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode receiver = ValueInputAs(1); TNode fncallback = ValueInputOrUndefined(2); TNode this_arg = ValueInputOrUndefined(3); TNode original_length = LoadJSArrayLength(receiver, kind); // If the array length >= kMaxFastArrayLength, then CreateArray // will create a dictionary. We should deopt in this case, and make sure // not to attempt inlining again. original_length = CheckBounds(original_length, NumberConstant(JSArray::kMaxFastArrayLength)); // Even though {JSCreateArray} is not marked as {kNoThrow}, we can elide the // exceptional projections because it cannot throw with the given // parameters. TNode array_ctor = Constant(native_context.GetInitialJSArrayMap(kind).GetConstructor()); TNode a = CreateArrayNoThrow(array_ctor, original_length); MapFrameStateParams frame_state_params{ jsgraph(), shared, context, target, outer_frame_state, receiver, fncallback, this_arg, a, original_length}; ThrowIfNotCallable(fncallback, MapLoopLazyFrameState(frame_state_params, ZeroConstant())); ForZeroUntil(original_length).Do([&](TNode k) { Checkpoint(MapLoopEagerFrameState(frame_state_params, k)); MaybeInsertMapChecks(inference, has_stability_dependency); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); auto continue_label = MakeLabel(); element = MaybeSkipHole(element, kind, &continue_label); TNode v = JSCall3(fncallback, this_arg, element, k, receiver, MapLoopLazyFrameState(frame_state_params, k)); // The array {a} should be HOLEY_SMI_ELEMENTS because we'd only come into // this loop if the input array length is non-zero, and "new Array({x > 0})" // always produces a HOLEY array. MapRef holey_double_map = native_context.GetInitialJSArrayMap(HOLEY_DOUBLE_ELEMENTS); MapRef holey_map = native_context.GetInitialJSArrayMap(HOLEY_ELEMENTS); TransitionAndStoreElement(holey_double_map, holey_map, a, k, v); Goto(&continue_label); Bind(&continue_label); }); return a; } namespace { struct FilterFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; TNode context; TNode target; FrameState outer_frame_state; TNode receiver; TNode callback; TNode this_arg; TNode a; TNode original_length; }; FrameState FilterLoopLazyFrameState(const FilterFrameStateParams& params, TNode k, TNode to, TNode element) { Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, params.a, k, params.original_length, element, to}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, Builtins::kArrayFilterLoopLazyDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState FilterLoopEagerPostCallbackFrameState( const FilterFrameStateParams& params, TNode k, TNode to, TNode element, TNode callback_value) { // Note that we are intentionally reusing the // Builtins::kArrayFilterLoopLazyDeoptContinuation as an *eager* entry point // in this case. This is safe, because re-evaluating a [ToBoolean] coercion is // safe. Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, params.a, k, params.original_length, element, to, callback_value}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, Builtins::kArrayFilterLoopLazyDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } FrameState FilterLoopEagerFrameState(const FilterFrameStateParams& params, TNode k, TNode to) { Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, params.a, k, params.original_length, to}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, Builtins::kArrayFilterLoopEagerDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeFilter( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context) { FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode receiver = ValueInputAs(1); TNode fncallback = ValueInputOrUndefined(2); TNode this_arg = ValueInputOrUndefined(3); // The output array is packed (filter doesn't visit holes). const ElementsKind packed_kind = GetPackedElementsKind(kind); TNode a = AllocateEmptyJSArray(packed_kind, native_context); TNode original_length = LoadJSArrayLength(receiver, kind); FilterFrameStateParams frame_state_params{ jsgraph(), shared, context, target, outer_frame_state, receiver, fncallback, this_arg, a, original_length}; // This frame state doesn't ever call the deopt continuation, it's only // necessary to specify a continuation in order to handle the exceptional // case. We don't have all the values available to completely fill out // the checkpoint parameters yet, but that's okay because it'll never be // called. TNode zero = ZeroConstant(); ThrowIfNotCallable(fncallback, FilterLoopLazyFrameState(frame_state_params, zero, zero, zero)); TNode initial_a_length = zero; For1ZeroUntil(original_length, initial_a_length) .Do([&](TNode k, TNode* a_length_object) { TNode a_length = TNode::UncheckedCast(*a_length_object); Checkpoint(FilterLoopEagerFrameState(frame_state_params, k, a_length)); MaybeInsertMapChecks(inference, has_stability_dependency); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); auto continue_label = MakeLabel(MachineRepresentation::kTaggedSigned); element = MaybeSkipHole(element, kind, &continue_label, a_length); TNode v = JSCall3( fncallback, this_arg, element, k, receiver, FilterLoopLazyFrameState(frame_state_params, k, a_length, element)); // We need an eager frame state for right after the callback function // returned, just in case an attempt to grow the output array fails. Checkpoint(FilterLoopEagerPostCallbackFrameState(frame_state_params, k, a_length, element, v)); GotoIfNot(ToBoolean(v), &continue_label, a_length); // Since the callback returned a trueish value, store the element in a. { TNode a_length1 = TypeGuardFixedArrayLength(a_length); TNode elements = LoadElements(a); elements = MaybeGrowFastElements(kind, FeedbackSource{}, a, elements, a_length1, LoadFixedArrayBaseLength(elements)); TNode new_a_length = NumberInc(a_length1); StoreJSArrayLength(a, new_a_length, kind); StoreFixedArrayBaseElement(elements, a_length1, element, kind); Goto(&continue_label, new_a_length); } Bind(&continue_label); *a_length_object = TNode::UncheckedCast(continue_label.PhiAt(0)); }) .ValueIsUnused(); return a; } namespace { struct FindFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; TNode context; TNode target; FrameState outer_frame_state; TNode receiver; TNode callback; TNode this_arg; TNode original_length; }; FrameState FindLoopLazyFrameState(const FindFrameStateParams& params, TNode k, ArrayFindVariant variant) { Builtins::Name builtin = (variant == ArrayFindVariant::kFind) ? Builtins::kArrayFindLoopLazyDeoptContinuation : Builtins::kArrayFindIndexLoopLazyDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState FindLoopEagerFrameState(const FindFrameStateParams& params, TNode k, ArrayFindVariant variant) { Builtins::Name builtin = (variant == ArrayFindVariant::kFind) ? Builtins::kArrayFindLoopEagerDeoptContinuation : Builtins::kArrayFindIndexLoopEagerDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } FrameState FindLoopAfterCallbackLazyFrameState( const FindFrameStateParams& params, TNode next_k, TNode if_found_value, ArrayFindVariant variant) { Builtins::Name builtin = (variant == ArrayFindVariant::kFind) ? Builtins::kArrayFindLoopAfterCallbackLazyDeoptContinuation : Builtins::kArrayFindIndexLoopAfterCallbackLazyDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, next_k, params.original_length, if_found_value}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeFind( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context, ArrayFindVariant variant) { FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode receiver = ValueInputAs(1); TNode fncallback = ValueInputOrUndefined(2); TNode this_arg = ValueInputOrUndefined(3); TNode original_length = LoadJSArrayLength(receiver, kind); FindFrameStateParams frame_state_params{ jsgraph(), shared, context, target, outer_frame_state, receiver, fncallback, this_arg, original_length}; ThrowIfNotCallable( fncallback, FindLoopLazyFrameState(frame_state_params, ZeroConstant(), variant)); const bool is_find_variant = (variant == ArrayFindVariant::kFind); auto out = MakeLabel(MachineRepresentation::kTagged); ForZeroUntil(original_length).Do([&](TNode k) { Checkpoint(FindLoopEagerFrameState(frame_state_params, k, variant)); MaybeInsertMapChecks(inference, has_stability_dependency); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); if (IsHoleyElementsKind(kind)) { element = TryConvertHoleToUndefined(element, kind); } TNode if_found_value = is_find_variant ? element : k; TNode next_k = NumberInc(k); // The callback result states whether the desired element was found. TNode v = JSCall3(fncallback, this_arg, element, k, receiver, FindLoopAfterCallbackLazyFrameState(frame_state_params, next_k, if_found_value, variant)); GotoIf(ToBoolean(v), &out, if_found_value); }); // If the loop completed, the element was not found. TNode if_not_found_value = is_find_variant ? TNode::UncheckedCast(UndefinedConstant()) : TNode::UncheckedCast(MinusOneConstant()); Goto(&out, if_not_found_value); Bind(&out); return out.PhiAt(0); } namespace { struct EverySomeFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; TNode context; TNode target; FrameState outer_frame_state; TNode receiver; TNode callback; TNode this_arg; TNode original_length; }; FrameState EverySomeLoopLazyFrameState(const EverySomeFrameStateParams& params, TNode k, ArrayEverySomeVariant variant) { Builtins::Name builtin = (variant == ArrayEverySomeVariant::kEvery) ? Builtins::kArrayEveryLoopLazyDeoptContinuation : Builtins::kArraySomeLoopLazyDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState EverySomeLoopEagerFrameState(const EverySomeFrameStateParams& params, TNode k, ArrayEverySomeVariant variant) { Builtins::Name builtin = (variant == ArrayEverySomeVariant::kEvery) ? Builtins::kArrayEveryLoopEagerDeoptContinuation : Builtins::kArraySomeLoopEagerDeoptContinuation; Node* checkpoint_params[] = {params.receiver, params.callback, params.this_arg, k, params.original_length}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, builtin, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), params.outer_frame_state, ContinuationFrameStateMode::EAGER)); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeEverySome( MapInference* inference, const bool has_stability_dependency, ElementsKind kind, const SharedFunctionInfoRef& shared, const NativeContextRef& native_context, ArrayEverySomeVariant variant) { FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode receiver = ValueInputAs(1); TNode fncallback = ValueInputOrUndefined(2); TNode this_arg = ValueInputOrUndefined(3); TNode original_length = LoadJSArrayLength(receiver, kind); EverySomeFrameStateParams frame_state_params{ jsgraph(), shared, context, target, outer_frame_state, receiver, fncallback, this_arg, original_length}; ThrowIfNotCallable( fncallback, EverySomeLoopLazyFrameState(frame_state_params, ZeroConstant(), variant)); auto out = MakeLabel(MachineRepresentation::kTagged); ForZeroUntil(original_length).Do([&](TNode k) { Checkpoint(EverySomeLoopEagerFrameState(frame_state_params, k, variant)); MaybeInsertMapChecks(inference, has_stability_dependency); TNode element; std::tie(k, element) = SafeLoadElement(kind, receiver, k); auto continue_label = MakeLabel(); element = MaybeSkipHole(element, kind, &continue_label); TNode v = JSCall3(fncallback, this_arg, element, k, receiver, EverySomeLoopLazyFrameState(frame_state_params, k, variant)); if (variant == ArrayEverySomeVariant::kEvery) { GotoIfNot(ToBoolean(v), &out, FalseConstant()); } else { DCHECK_EQ(variant, ArrayEverySomeVariant::kSome); GotoIf(ToBoolean(v), &out, TrueConstant()); } Goto(&continue_label); Bind(&continue_label); }); Goto(&out, (variant == ArrayEverySomeVariant::kEvery) ? TrueConstant() : FalseConstant()); Bind(&out); return out.PhiAt(0); } namespace { Callable GetCallableForArrayIndexOfIncludes(ArrayIndexOfIncludesVariant variant, ElementsKind elements_kind, Isolate* isolate) { if (variant == ArrayIndexOfIncludesVariant::kIndexOf) { switch (elements_kind) { case PACKED_SMI_ELEMENTS: case HOLEY_SMI_ELEMENTS: case PACKED_ELEMENTS: case HOLEY_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIndexOfSmiOrObject); case PACKED_DOUBLE_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIndexOfPackedDoubles); default: DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind); return Builtins::CallableFor(isolate, Builtins::kArrayIndexOfHoleyDoubles); } } else { DCHECK_EQ(variant, ArrayIndexOfIncludesVariant::kIncludes); switch (elements_kind) { case PACKED_SMI_ELEMENTS: case HOLEY_SMI_ELEMENTS: case PACKED_ELEMENTS: case HOLEY_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIncludesSmiOrObject); case PACKED_DOUBLE_ELEMENTS: return Builtins::CallableFor(isolate, Builtins::kArrayIncludesPackedDoubles); default: DCHECK_EQ(HOLEY_DOUBLE_ELEMENTS, elements_kind); return Builtins::CallableFor(isolate, Builtins::kArrayIncludesHoleyDoubles); } } UNREACHABLE(); } } // namespace TNode IteratingArrayBuiltinReducerAssembler::ReduceArrayPrototypeIndexOfIncludes( ElementsKind kind, ArrayIndexOfIncludesVariant variant) { TNode context = ContextInput(); TNode receiver = ValueInputAs(1); TNode search_element = ValueInputOrUndefined(2); TNode from_index = ValueInputOrZero(3); // TODO(jgruber): This currently only reduces to a stub call. Create a full // reduction (similar to other higher-order array builtins) instead of // lowering to a builtin call. E.g. Array.p.every and Array.p.some have almost // identical functionality. TNode length = LoadJSArrayLength(receiver, kind); TNode elements = LoadElements(receiver); const bool have_from_index = ValueInputCount() > 3; if (have_from_index) { TNode from_index_smi = CheckSmi(from_index); // If the index is negative, it means the offset from the end and // therefore needs to be added to the length. If the result is still // negative, it needs to be clamped to 0. TNode cond = NumberLessThan(from_index_smi, ZeroConstant()); from_index = SelectIf(cond) .Then(_ { return NumberMax(NumberAdd(length, from_index_smi), ZeroConstant()); }) .Else(_ { return from_index_smi; }) .ExpectFalse() .Value(); } return Call4(GetCallableForArrayIndexOfIncludes(variant, kind, isolate()), context, elements, search_element, length, from_index); } namespace { struct PromiseCtorFrameStateParams { JSGraph* jsgraph; SharedFunctionInfoRef shared; Node* node_ptr; TNode context; TNode target; FrameState outer_frame_state; }; // Remnant of old-style JSCallReducer code. Could be ported to graph assembler, // but probably not worth the effort. Node* CreateArtificialFrameState(Node* node, Node* outer_frame_state, int parameter_count, BailoutId bailout_id, FrameStateType frame_state_type, const SharedFunctionInfoRef& shared, Node* context, CommonOperatorBuilder* common, Graph* graph) { const FrameStateFunctionInfo* state_info = common->CreateFrameStateFunctionInfo( frame_state_type, parameter_count + 1, 0, shared.object()); const Operator* op = common->FrameState( bailout_id, OutputFrameStateCombine::Ignore(), state_info); const Operator* op0 = common->StateValues(0, SparseInputMask::Dense()); Node* node0 = graph->NewNode(op0); static constexpr int kTargetInputIndex = 0; static constexpr int kReceiverInputIndex = 1; const int parameter_count_with_receiver = parameter_count + 1; std::vector params; params.reserve(parameter_count_with_receiver); for (int i = 0; i < parameter_count_with_receiver; i++) { params.push_back(node->InputAt(kReceiverInputIndex + i)); } const Operator* op_param = common->StateValues( static_cast(params.size()), SparseInputMask::Dense()); Node* params_node = graph->NewNode(op_param, static_cast(params.size()), ¶ms.front()); DCHECK(context); return graph->NewNode(op, params_node, node0, node0, context, node->InputAt(kTargetInputIndex), outer_frame_state); } FrameState PromiseConstructorFrameState( const PromiseCtorFrameStateParams& params, CommonOperatorBuilder* common, Graph* graph) { DCHECK_EQ(1, params.shared.internal_formal_parameter_count()); return FrameState(CreateArtificialFrameState( params.node_ptr, params.outer_frame_state, 1, BailoutId::ConstructStubInvoke(), FrameStateType::kConstructStub, params.shared, params.context, common, graph)); } FrameState PromiseConstructorLazyFrameState( const PromiseCtorFrameStateParams& params, FrameState constructor_frame_state) { // The deopt continuation of this frame state is never called; the frame state // is only necessary to obtain the right stack trace. JSGraph* jsgraph = params.jsgraph; Node* checkpoint_params[] = { jsgraph->UndefinedConstant(), /* receiver */ jsgraph->UndefinedConstant(), /* promise */ jsgraph->UndefinedConstant(), /* reject function */ jsgraph->TheHoleConstant() /* exception */ }; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( jsgraph, params.shared, Builtins::kPromiseConstructorLazyDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), constructor_frame_state, ContinuationFrameStateMode::LAZY)); } FrameState PromiseConstructorLazyWithCatchFrameState( const PromiseCtorFrameStateParams& params, FrameState constructor_frame_state, TNode promise, TNode reject) { // This continuation just returns the created promise and takes care of // exceptions thrown by the executor. Node* checkpoint_params[] = { params.jsgraph->UndefinedConstant(), /* receiver */ promise, reject}; return FrameState(CreateJavaScriptBuiltinContinuationFrameState( params.jsgraph, params.shared, Builtins::kPromiseConstructorLazyDeoptContinuation, params.target, params.context, checkpoint_params, arraysize(checkpoint_params), constructor_frame_state, ContinuationFrameStateMode::LAZY_WITH_CATCH)); } } // namespace TNode PromiseBuiltinReducerAssembler::ReducePromiseConstructor( const NativeContextRef& native_context) { DCHECK_GE(ConstructArity(), 1); FrameState outer_frame_state = FrameStateInput(); TNode context = ContextInput(); TNode target = TargetInput(); TNode executor = ValueInput(1); DCHECK_EQ(target, NewTargetInput()); SharedFunctionInfoRef promise_shared = native_context.promise_function().shared(); PromiseCtorFrameStateParams frame_state_params{jsgraph(), promise_shared, node_ptr(), context, target, outer_frame_state}; // Insert a construct stub frame into the chain of frame states. This will // reconstruct the proper frame when deoptimizing within the constructor. // For the frame state, we only provide the executor parameter, even if more // arguments were passed. This is not observable from JS. FrameState constructor_frame_state = PromiseConstructorFrameState(frame_state_params, common(), graph()); ThrowIfNotCallable(executor, PromiseConstructorLazyFrameState(frame_state_params, constructor_frame_state)); TNode promise = CreatePromise(context); // 8. CreatePromiseResolvingFunctions // Allocate a promise context for the closures below. TNode promise_context = CreateFunctionContext( native_context, context, PromiseBuiltins::kPromiseContextLength); StoreContextSlot(promise_context, PromiseBuiltins::kPromiseSlot, promise); StoreContextSlot(promise_context, PromiseBuiltins::kAlreadyResolvedSlot, FalseConstant()); StoreContextSlot(promise_context, PromiseBuiltins::kDebugEventSlot, TrueConstant()); // Allocate closures for the resolve and reject cases. SharedFunctionInfoRef resolve_sfi( broker_, broker_->isolate() ->factory() ->promise_capability_default_resolve_shared_fun()); TNode resolve = CreateClosureFromBuiltinSharedFunctionInfo(resolve_sfi, promise_context); SharedFunctionInfoRef reject_sfi( broker_, broker_->isolate() ->factory() ->promise_capability_default_reject_shared_fun()); TNode reject = CreateClosureFromBuiltinSharedFunctionInfo(reject_sfi, promise_context); FrameState lazy_with_catch_frame_state = PromiseConstructorLazyWithCatchFrameState( frame_state_params, constructor_frame_state, promise, reject); // 9. Call executor with both resolving functions. // 10a. Call reject if the call to executor threw. Try(_ { CallPromiseExecutor(executor, resolve, reject, lazy_with_catch_frame_state); }).Catch([&](TNode exception) { CallPromiseReject(reject, exception, lazy_with_catch_frame_state); }); return promise; } #undef _ bool JSCallReducer::should_disallow_heap_access() const { return broker_->is_concurrent_inlining(); } Reduction JSCallReducer::ReplaceWithSubgraph(JSCallReducerAssembler* gasm, Node* subgraph) { // TODO(jgruber): Consider a less fiddly way of integrating the new subgraph // into the outer graph. For instance, the subgraph could be created in // complete isolation, and then plugged into the outer graph in one go. // Instead of manually tracking IfException nodes, we could iterate the // subgraph. // Replace the Call node with the newly-produced subgraph. ReplaceWithValue(gasm->node_ptr(), subgraph, gasm->effect(), gasm->control()); // Wire exception edges contained in the newly-produced subgraph into the // outer graph. auto catch_scope = gasm->catch_scope(); DCHECK(catch_scope->is_outermost()); if (catch_scope->has_handler() && catch_scope->has_exceptional_control_flow()) { TNode handler_exception; Effect handler_effect{nullptr}; Control handler_control{nullptr}; gasm->catch_scope()->MergeExceptionalPaths( &handler_exception, &handler_effect, &handler_control); ReplaceWithValue(gasm->outermost_handler(), handler_exception, handler_effect, handler_control); } return Replace(subgraph); } Reduction JSCallReducer::ReduceMathUnary(Node* node, const Operator* op) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->NaNConstant(); ReplaceWithValue(node, value); return Replace(value); } JSCallReducerAssembler a(jsgraph(), temp_zone(), node); Node* subgraph = a.ReduceMathUnary(op); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceMathBinary(Node* node, const Operator* op) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->NaNConstant(); ReplaceWithValue(node, value); return Replace(value); } JSCallReducerAssembler a(jsgraph(), temp_zone(), node); Node* subgraph = a.ReduceMathBinary(op); return ReplaceWithSubgraph(&a, subgraph); } // ES6 section 20.2.2.19 Math.imul ( x, y ) Reduction JSCallReducer::ReduceMathImul(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->ZeroConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* left = NodeProperties::GetValueInput(node, 2); Node* right = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->ZeroConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); left = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), left, effect, control); right = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), right, effect, control); left = graph()->NewNode(simplified()->NumberToUint32(), left); right = graph()->NewNode(simplified()->NumberToUint32(), right); Node* value = graph()->NewNode(simplified()->NumberImul(), left, right); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.2.2.11 Math.clz32 ( x ) Reduction JSCallReducer::ReduceMathClz32(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->Constant(32); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); input = graph()->NewNode(simplified()->NumberToUint32(), input); Node* value = graph()->NewNode(simplified()->NumberClz32(), input); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.2.2.24 Math.max ( value1, value2, ...values ) // ES6 section 20.2.2.25 Math.min ( value1, value2, ...values ) Reduction JSCallReducer::ReduceMathMinMax(Node* node, const Operator* op, Node* empty_value) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() <= 2) { ReplaceWithValue(node, empty_value); return Replace(empty_value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* value = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), NodeProperties::GetValueInput(node, 2), effect, control); for (int i = 3; i < node->op()->ValueInputCount(); i++) { Node* input = effect = graph()->NewNode( simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball, p.feedback()), NodeProperties::GetValueInput(node, i), effect, control); value = graph()->NewNode(op, value, input); } ReplaceWithValue(node, value, effect); return Replace(value); } Reduction JSCallReducer::Reduce(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); switch (node->opcode()) { case IrOpcode::kJSConstruct: return ReduceJSConstruct(node); case IrOpcode::kJSConstructWithArrayLike: return ReduceJSConstructWithArrayLike(node); case IrOpcode::kJSConstructWithSpread: return ReduceJSConstructWithSpread(node); case IrOpcode::kJSCall: return ReduceJSCall(node); case IrOpcode::kJSCallWithArrayLike: return ReduceJSCallWithArrayLike(node); case IrOpcode::kJSCallWithSpread: return ReduceJSCallWithSpread(node); default: break; } return NoChange(); } void JSCallReducer::Finalize() { // TODO(turbofan): This is not the best solution; ideally we would be able // to teach the GraphReducer about arbitrary dependencies between different // nodes, even if they don't show up in the use list of the other node. std::set const waitlist = std::move(waitlist_); for (Node* node : waitlist) { if (!node->IsDead()) { Reduction const reduction = Reduce(node); if (reduction.Changed()) { Node* replacement = reduction.replacement(); if (replacement != node) { Replace(node, replacement); } } } } } // ES6 section 22.1.1 The Array Constructor Reduction JSCallReducer::ReduceArrayConstructor(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = NodeProperties::GetValueInput(node, 0); CallParameters const& p = CallParametersOf(node->op()); // Turn the {node} into a {JSCreateArray} call. size_t const arity = p.arity_without_implicit_args(); NodeProperties::ReplaceValueInput(node, target, 0); NodeProperties::ReplaceValueInput(node, target, 1); NodeProperties::ChangeOp( node, javascript()->CreateArray(arity, MaybeHandle())); return Changed(node); } // ES6 section 19.3.1.1 Boolean ( value ) Reduction JSCallReducer::ReduceBooleanConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); // Replace the {node} with a proper {ToBoolean} operator. Node* value = (p.arity_without_implicit_args() == 0) ? jsgraph()->UndefinedConstant() : NodeProperties::GetValueInput(node, 2); value = graph()->NewNode(simplified()->ToBoolean(), value); ReplaceWithValue(node, value); return Replace(value); } // ES section #sec-object-constructor Reduction JSCallReducer::ReduceObjectConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.arity_without_implicit_args() < 1) return NoChange(); Node* value = NodeProperties::GetValueInput(node, 2); Node* effect = NodeProperties::GetEffectInput(node); // We can fold away the Object(x) call if |x| is definitely not a primitive. if (NodeProperties::CanBePrimitive(broker(), value, effect)) { if (!NodeProperties::CanBeNullOrUndefined(broker(), value, effect)) { // Turn the {node} into a {JSToObject} call if we know that // the {value} cannot be null or undefined. NodeProperties::ReplaceValueInputs(node, value); NodeProperties::ChangeOp(node, javascript()->ToObject()); return Changed(node); } } else { ReplaceWithValue(node, value); return Replace(value); } return NoChange(); } // ES6 section 19.2.3.1 Function.prototype.apply ( thisArg, argArray ) Reduction JSCallReducer::ReduceFunctionPrototypeApply(Node* node) { DisallowHeapAccessIf no_heap_acess(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); size_t arity = p.arity_without_implicit_args(); ConvertReceiverMode convert_mode = ConvertReceiverMode::kAny; if (arity == 0) { // Neither thisArg nor argArray was provided. convert_mode = ConvertReceiverMode::kNullOrUndefined; node->ReplaceInput(0, node->InputAt(1)); node->ReplaceInput(1, jsgraph()->UndefinedConstant()); } else if (arity == 1) { // The argArray was not provided, just remove the {target}. node->RemoveInput(0); --arity; } else { Node* target = NodeProperties::GetValueInput(node, 1); Node* this_argument = NodeProperties::GetValueInput(node, 2); Node* arguments_list = NodeProperties::GetValueInput(node, 3); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // If {arguments_list} cannot be null or undefined, we don't need // to expand this {node} to control-flow. if (!NodeProperties::CanBeNullOrUndefined(broker(), arguments_list, effect)) { // Massage the value inputs appropriately. node->ReplaceInput(0, target); node->ReplaceInput(1, this_argument); node->ReplaceInput(2, arguments_list); while (arity-- > 1) node->RemoveInput(3); // Morph the {node} to a {JSCallWithArrayLike}. NodeProperties::ChangeOp( node, javascript()->CallWithArrayLike( p.frequency(), p.feedback(), p.speculation_mode(), CallFeedbackRelation::kUnrelated)); return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node)); } else { // Check whether {arguments_list} is null. Node* check_null = graph()->NewNode(simplified()->ReferenceEqual(), arguments_list, jsgraph()->NullConstant()); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check_null, control); Node* if_null = graph()->NewNode(common()->IfTrue(), control); control = graph()->NewNode(common()->IfFalse(), control); // Check whether {arguments_list} is undefined. Node* check_undefined = graph()->NewNode(simplified()->ReferenceEqual(), arguments_list, jsgraph()->UndefinedConstant()); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check_undefined, control); Node* if_undefined = graph()->NewNode(common()->IfTrue(), control); control = graph()->NewNode(common()->IfFalse(), control); // Lower to {JSCallWithArrayLike} if {arguments_list} is neither null // nor undefined. Node* effect0 = effect; Node* control0 = control; Node* value0 = effect0 = control0 = graph()->NewNode( javascript()->CallWithArrayLike(p.frequency(), p.feedback(), p.speculation_mode(), CallFeedbackRelation::kUnrelated), target, this_argument, arguments_list, context, frame_state, effect0, control0); // Lower to {JSCall} if {arguments_list} is either null or undefined. Node* effect1 = effect; Node* control1 = graph()->NewNode(common()->Merge(2), if_null, if_undefined); Node* value1 = effect1 = control1 = graph()->NewNode( javascript()->Call(0 + kTargetAndReceiver), target, this_argument, context, frame_state, effect1, control1); // Rewire potential exception edges. Node* if_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &if_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* if_exception0 = graph()->NewNode(common()->IfException(), control0, effect0); control0 = graph()->NewNode(common()->IfSuccess(), control0); Node* if_exception1 = graph()->NewNode(common()->IfException(), control1, effect1); control1 = graph()->NewNode(common()->IfSuccess(), control1); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), if_exception0, if_exception1); Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception0, if_exception1, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), if_exception0, if_exception1, merge); ReplaceWithValue(if_exception, phi, ephi, merge); } // Join control paths. control = graph()->NewNode(common()->Merge(2), control0, control1); effect = graph()->NewNode(common()->EffectPhi(2), effect0, effect1, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), value0, value1, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } } // Change {node} to the new {JSCall} operator. NodeProperties::ChangeOp( node, javascript()->Call(arity + kTargetAndReceiver, p.frequency(), p.feedback(), convert_mode, p.speculation_mode(), CallFeedbackRelation::kUnrelated)); // Try to further reduce the JSCall {node}. return Changed(node).FollowedBy(ReduceJSCall(node)); } // ES section #sec-function.prototype.bind Reduction JSCallReducer::ReduceFunctionPrototypeBind(Node* node) { DisallowHeapAccessIf no_heap_acess(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } // Value inputs to the {node} are as follows: // // - target, which is Function.prototype.bind JSFunction // - receiver, which is the [[BoundTargetFunction]] // - bound_this (optional), which is the [[BoundThis]] // - and all the remaining value inputs are [[BoundArguments]] Node* receiver = NodeProperties::GetValueInput(node, 1); Node* bound_this = (node->op()->ValueInputCount() < 3) ? jsgraph()->UndefinedConstant() : NodeProperties::GetValueInput(node, 2); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Ensure that the {receiver} is known to be a JSBoundFunction or // a JSFunction with the same [[Prototype]], and all maps we've // seen for the {receiver} so far indicate that {receiver} is // definitely a constructor or not a constructor. MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); MapRef first_receiver_map(broker(), receiver_maps[0]); bool const is_constructor = first_receiver_map.is_constructor(); if (should_disallow_heap_access() && !first_receiver_map.serialized_prototype()) { TRACE_BROKER_MISSING(broker(), "serialized prototype on map " << first_receiver_map); return inference.NoChange(); } ObjectRef const prototype = first_receiver_map.prototype(); for (Handle const map : receiver_maps) { MapRef receiver_map(broker(), map); if (should_disallow_heap_access() && !receiver_map.serialized_prototype()) { TRACE_BROKER_MISSING(broker(), "serialized prototype on map " << receiver_map); return inference.NoChange(); } // Check for consistency among the {receiver_maps}. STATIC_ASSERT(LAST_TYPE == LAST_FUNCTION_TYPE); if (!receiver_map.prototype().equals(prototype) || receiver_map.is_constructor() != is_constructor || receiver_map.instance_type() < FIRST_FUNCTION_TYPE) { return inference.NoChange(); } // Disallow binding of slow-mode functions. We need to figure out // whether the length and name property are in the original state. if (receiver_map.is_dictionary_map()) return inference.NoChange(); // Check whether the length and name properties are still present // as AccessorInfo objects. In that case, their values can be // recomputed even if the actual value of the object changes. // This mirrors the checks done in builtins-function-gen.cc at // runtime otherwise. int minimum_nof_descriptors = i::Max(JSFunction::kLengthDescriptorIndex, JSFunction::kNameDescriptorIndex) + 1; if (receiver_map.NumberOfOwnDescriptors() < minimum_nof_descriptors) { return inference.NoChange(); } const InternalIndex kLengthIndex(JSFunction::kLengthDescriptorIndex); const InternalIndex kNameIndex(JSFunction::kNameDescriptorIndex); if (!receiver_map.serialized_own_descriptor(kLengthIndex) || !receiver_map.serialized_own_descriptor(kNameIndex)) { TRACE_BROKER_MISSING(broker(), "serialized descriptors on map " << receiver_map); return inference.NoChange(); } ReadOnlyRoots roots(isolate()); StringRef length_string(broker(), roots.length_string_handle()); StringRef name_string(broker(), roots.name_string_handle()); base::Optional length_value( receiver_map.GetStrongValue(kLengthIndex)); base::Optional name_value( receiver_map.GetStrongValue(kNameIndex)); if (!length_value || !name_value) { TRACE_BROKER_MISSING( broker(), "name or length descriptors on map " << receiver_map); return inference.NoChange(); } if (!receiver_map.GetPropertyKey(kLengthIndex).equals(length_string) || !length_value->IsAccessorInfo() || !receiver_map.GetPropertyKey(kNameIndex).equals(name_string) || !name_value->IsAccessorInfo()) { return inference.NoChange(); } } // Choose the map for the resulting JSBoundFunction (but bail out in case of a // custom prototype). MapRef map = is_constructor ? native_context().bound_function_with_constructor_map() : native_context().bound_function_without_constructor_map(); if (!map.prototype().equals(prototype)) return inference.NoChange(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); // Replace the {node} with a JSCreateBoundFunction. static constexpr int kContextEffectAndControl = 3; int const arity = std::max(0, node->op()->ValueInputCount() - kContextEffectAndControl); int const input_count = kTargetAndReceiver + arity + kContextEffectAndControl; Node** inputs = graph()->zone()->NewArray(input_count); inputs[0] = receiver; inputs[1] = bound_this; for (int i = 0; i < arity; ++i) { inputs[kTargetAndReceiver + i] = NodeProperties::GetValueInput(node, 3 + i); } inputs[kTargetAndReceiver + arity + 0] = context; inputs[kTargetAndReceiver + arity + 1] = effect; inputs[kTargetAndReceiver + arity + 2] = control; Node* value = effect = graph()->NewNode(javascript()->CreateBoundFunction(arity, map.object()), input_count, inputs); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 19.2.3.3 Function.prototype.call (thisArg, ...args) Reduction JSCallReducer::ReduceFunctionPrototypeCall(Node* node) { DisallowHeapAccessIf no_heap_acess(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Change context of {node} to the Function.prototype.call context, // to ensure any exception is thrown in the correct context. Node* context; HeapObjectMatcher m(target); if (m.HasValue()) { JSFunctionRef function = m.Ref(broker()).AsJSFunction(); if (should_disallow_heap_access() && !function.serialized()) { TRACE_BROKER_MISSING(broker(), "Serialize call on function " << function); return NoChange(); } context = jsgraph()->Constant(function.context()); } else { context = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSFunctionContext()), target, effect, control); } NodeProperties::ReplaceContextInput(node, context); NodeProperties::ReplaceEffectInput(node, effect); // Remove the target from {node} and use the receiver as target instead, and // the thisArg becomes the new target. If thisArg was not provided, insert // undefined instead. size_t arity = p.arity_without_implicit_args(); ConvertReceiverMode convert_mode; if (arity == 0) { // The thisArg was not provided, use undefined as receiver. convert_mode = ConvertReceiverMode::kNullOrUndefined; node->ReplaceInput(0, node->InputAt(1)); node->ReplaceInput(1, jsgraph()->UndefinedConstant()); } else { // Just remove the target, which is the first value input. convert_mode = ConvertReceiverMode::kAny; node->RemoveInput(0); --arity; } NodeProperties::ChangeOp( node, javascript()->Call(arity + kTargetAndReceiver, p.frequency(), p.feedback(), convert_mode, p.speculation_mode(), CallFeedbackRelation::kUnrelated)); // Try to further reduce the JSCall {node}. return Changed(node).FollowedBy(ReduceJSCall(node)); } // ES6 section 19.2.3.6 Function.prototype [ @@hasInstance ] (V) Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* object = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // TODO(turbofan): If JSOrdinaryToInstance raises an exception, the // stack trace doesn't contain the @@hasInstance call; we have the // corresponding bug in the baseline case. Some massaging of the frame // state would be necessary here. // Morph this {node} into a JSOrdinaryHasInstance node. node->ReplaceInput(0, receiver); node->ReplaceInput(1, object); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance()); return Changed(node); } Reduction JSCallReducer::ReduceObjectGetPrototype(Node* node, Node* object) { Node* effect = NodeProperties::GetEffectInput(node); // Try to determine the {object} map. MapInference inference(broker(), object, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& object_maps = inference.GetMaps(); MapRef candidate_map(broker(), object_maps[0]); if (should_disallow_heap_access() && !candidate_map.serialized_prototype()) { TRACE_BROKER_MISSING(broker(), "prototype for map " << candidate_map); return inference.NoChange(); } ObjectRef candidate_prototype = candidate_map.prototype(); // Check if we can constant-fold the {candidate_prototype}. for (size_t i = 0; i < object_maps.size(); ++i) { MapRef object_map(broker(), object_maps[i]); if (should_disallow_heap_access() && !object_map.serialized_prototype()) { TRACE_BROKER_MISSING(broker(), "prototype for map " << object_map); return inference.NoChange(); } if (IsSpecialReceiverInstanceType(object_map.instance_type()) || !object_map.prototype().equals(candidate_prototype)) { // We exclude special receivers, like JSProxy or API objects that // might require access checks here; we also don't want to deal // with hidden prototypes at this point. return inference.NoChange(); } // The above check also excludes maps for primitive values, which is // important because we are not applying [[ToObject]] here as expected. DCHECK(!object_map.IsPrimitiveMap() && object_map.IsJSReceiverMap()); } if (!inference.RelyOnMapsViaStability(dependencies())) { return inference.NoChange(); } Node* value = jsgraph()->Constant(candidate_prototype); ReplaceWithValue(node, value); return Replace(value); } // ES6 section 19.1.2.11 Object.getPrototypeOf ( O ) Reduction JSCallReducer::ReduceObjectGetPrototypeOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* object = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); return ReduceObjectGetPrototype(node, object); } // ES section #sec-object.is Reduction JSCallReducer::ReduceObjectIs(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& params = CallParametersOf(node->op()); int const argc = params.arity_without_implicit_args(); Node* lhs = (argc >= 1) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* rhs = (argc >= 2) ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* value = graph()->NewNode(simplified()->SameValue(), lhs, rhs); ReplaceWithValue(node, value); return Replace(value); } // ES6 section B.2.2.1.1 get Object.prototype.__proto__ Reduction JSCallReducer::ReduceObjectPrototypeGetProto(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); return ReduceObjectGetPrototype(node, receiver); } // ES #sec-object.prototype.hasownproperty Reduction JSCallReducer::ReduceObjectPrototypeHasOwnProperty(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& params = CallParametersOf(node->op()); int const argc = params.arity_without_implicit_args(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* name = (argc >= 1) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // We can optimize a call to Object.prototype.hasOwnProperty if it's being // used inside a fast-mode for..in, so for code like this: // // for (name in receiver) { // if (receiver.hasOwnProperty(name)) { // ... // } // } // // If the for..in is in fast-mode, we know that the {receiver} has {name} // as own property, otherwise the enumeration wouldn't include it. The graph // constructed by the BytecodeGraphBuilder in this case looks like this: // receiver // ^ ^ // | | // | +-+ // | | // | JSToObject // | ^ // | | // | JSForInNext // | ^ // +----+ | // | | // JSCall[hasOwnProperty] // We can constant-fold the {node} to True in this case, and insert // a (potentially redundant) map check to guard the fact that the // {receiver} map didn't change since the dominating JSForInNext. This // map check is only necessary when TurboFan cannot prove that there // is no observable side effect between the {JSForInNext} and the // {JSCall} to Object.prototype.hasOwnProperty. // // Also note that it's safe to look through the {JSToObject}, since the // Object.prototype.hasOwnProperty does an implicit ToObject anyway, and // these operations are not observable. if (name->opcode() == IrOpcode::kJSForInNext) { ForInMode const mode = ForInModeOf(name->op()); if (mode != ForInMode::kGeneric) { Node* object = NodeProperties::GetValueInput(name, 0); Node* cache_type = NodeProperties::GetValueInput(name, 2); if (object->opcode() == IrOpcode::kJSToObject) { object = NodeProperties::GetValueInput(object, 0); } if (object == receiver) { // No need to repeat the map check if we can prove that there's no // observable side effect between {effect} and {name]. if (!NodeProperties::NoObservableSideEffectBetween(effect, name)) { Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map, cache_type); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongMap), check, effect, control); } Node* value = jsgraph()->TrueConstant(); ReplaceWithValue(node, value, effect, control); return Replace(value); } } } return NoChange(); } // ES #sec-object.prototype.isprototypeof Reduction JSCallReducer::ReduceObjectPrototypeIsPrototypeOf(Node* node) { DisallowHeapAccessIf no_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* value = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); // Ensure that the {receiver} is known to be a JSReceiver (so that // the ToObject step of Object.prototype.isPrototypeOf is a no-op). MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { return NoChange(); } // We don't check whether {value} is a proper JSReceiver here explicitly, // and don't explicitly rule out Primitive {value}s, since all of them // have null as their prototype, so the prototype chain walk inside the // JSHasInPrototypeChain operator immediately aborts and yields false. NodeProperties::ReplaceValueInput(node, value, 0); NodeProperties::ReplaceValueInput(node, receiver, 1); for (int i = node->op()->ValueInputCount(); i-- > 2;) { node->RemoveInput(i); } NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain()); return Changed(node); } // ES6 section 26.1.1 Reflect.apply ( target, thisArgument, argumentsList ) Reduction JSCallReducer::ReduceReflectApply(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); // Massage value inputs appropriately. node->RemoveInput(0); node->RemoveInput(0); while (arity < 3) { node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant()); } while (arity-- > 3) { node->RemoveInput(arity); } NodeProperties::ChangeOp( node, javascript()->CallWithArrayLike(p.frequency(), p.feedback(), p.speculation_mode(), CallFeedbackRelation::kUnrelated)); return Changed(node).FollowedBy(ReduceJSCallWithArrayLike(node)); } // ES6 section 26.1.2 Reflect.construct ( target, argumentsList [, newTarget] ) Reduction JSCallReducer::ReduceReflectConstruct(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); // Massage value inputs appropriately. node->RemoveInput(0); node->RemoveInput(0); while (arity < 2) { node->InsertInput(graph()->zone(), arity++, jsgraph()->UndefinedConstant()); } if (arity < 3) { node->InsertInput(graph()->zone(), arity++, node->InputAt(0)); } while (arity-- > 3) { node->RemoveInput(arity); } NodeProperties::ChangeOp( node, javascript()->ConstructWithArrayLike(p.frequency(), p.feedback())); return Changed(node).FollowedBy(ReduceJSConstructWithArrayLike(node)); } // ES6 section 26.1.7 Reflect.getPrototypeOf ( target ) Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = (node->op()->ValueInputCount() >= 3) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); return ReduceObjectGetPrototype(node, target); } // ES6 section #sec-object.create Object.create(proto, properties) Reduction JSCallReducer::ReduceObjectCreate(Node* node) { int arg_count = node->op()->ValueInputCount(); Node* properties = arg_count >= 4 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); if (properties != jsgraph()->UndefinedConstant()) return NoChange(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* prototype = arg_count >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); node->ReplaceInput(0, prototype); node->ReplaceInput(1, context); node->ReplaceInput(2, frame_state); node->ReplaceInput(3, effect); node->ReplaceInput(4, control); node->TrimInputCount(5); NodeProperties::ChangeOp(node, javascript()->CreateObject()); return Changed(node); } // ES section #sec-reflect.get Reduction JSCallReducer::ReduceReflectGet(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); if (arity != 2) return NoChange(); Node* target = NodeProperties::GetValueInput(node, 2); Node* key = NodeProperties::GetValueInput(node, 3); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check whether {target} is a JSReceiver. Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); // Throw an appropriate TypeError if the {target} is not a JSReceiver. Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; { if_false = efalse = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant( static_cast(MessageTemplate::kCalledOnNonObject)), jsgraph()->HeapConstant(factory()->ReflectGet_string()), context, frame_state, efalse, if_false); } // Otherwise just use the existing GetPropertyStub. Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue; { Callable callable = Builtins::CallableFor(isolate(), Builtins::kGetProperty); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNeedsFrameState, Operator::kNoProperties); Node* stub_code = jsgraph()->HeapConstant(callable.code()); vtrue = etrue = if_true = graph()->NewNode(common()->Call(call_descriptor), stub_code, target, key, context, frame_state, etrue, if_true); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true); if_true = graph()->NewNode(common()->IfSuccess(), if_true); Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false); if_false = graph()->NewNode(common()->IfSuccess(), if_false); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse); Node* ephi = graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), extrue, exfalse, merge); ReplaceWithValue(on_exception, phi, ephi, merge); } // Connect the throwing path to end. if_false = graph()->NewNode(common()->Throw(), efalse, if_false); NodeProperties::MergeControlToEnd(graph(), common(), if_false); // Continue on the regular path. ReplaceWithValue(node, vtrue, etrue, if_true); return Changed(vtrue); } // ES section #sec-reflect.has Reduction JSCallReducer::ReduceReflectHas(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); Node* target = (arity >= 1) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* key = (arity >= 2) ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check whether {target} is a JSReceiver. Node* check = graph()->NewNode(simplified()->ObjectIsReceiver(), target); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); // Throw an appropriate TypeError if the {target} is not a JSReceiver. Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; { if_false = efalse = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant( static_cast(MessageTemplate::kCalledOnNonObject)), jsgraph()->HeapConstant(factory()->ReflectHas_string()), context, frame_state, efalse, if_false); } // Otherwise just use the existing {JSHasProperty} logic. Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue; { // TODO(magardn): collect feedback so this can be optimized vtrue = etrue = if_true = graph()->NewNode(javascript()->HasProperty(FeedbackSource()), target, key, context, frame_state, etrue, if_true); } // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* extrue = graph()->NewNode(common()->IfException(), etrue, if_true); if_true = graph()->NewNode(common()->IfSuccess(), if_true); Node* exfalse = graph()->NewNode(common()->IfException(), efalse, if_false); if_false = graph()->NewNode(common()->IfSuccess(), if_false); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), extrue, exfalse); Node* ephi = graph()->NewNode(common()->EffectPhi(2), extrue, exfalse, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), extrue, exfalse, merge); ReplaceWithValue(on_exception, phi, ephi, merge); } // Connect the throwing path to end. if_false = graph()->NewNode(common()->Throw(), efalse, if_false); NodeProperties::MergeControlToEnd(graph(), common(), if_false); // Continue on the regular path. ReplaceWithValue(node, vtrue, etrue, if_true); return Changed(vtrue); } namespace { bool CanInlineArrayIteratingBuiltin(JSHeapBroker* broker, MapHandles const& receiver_maps, ElementsKind* kind_return) { DCHECK_NE(0, receiver_maps.size()); *kind_return = MapRef(broker, receiver_maps[0]).elements_kind(); for (auto receiver_map : receiver_maps) { MapRef map(broker, receiver_map); if (!map.supports_fast_array_iteration() || !UnionElementsKindUptoSize(kind_return, map.elements_kind())) { return false; } } return true; } bool CanInlineArrayResizingBuiltin(JSHeapBroker* broker, MapHandles const& receiver_maps, std::vector* kinds, bool builtin_is_push = false) { DCHECK_NE(0, receiver_maps.size()); for (auto receiver_map : receiver_maps) { MapRef map(broker, receiver_map); if (!map.supports_fast_array_resize()) return false; // TODO(turbofan): We should also handle fast holey double elements once // we got the hole NaN mess sorted out in TurboFan/V8. if (map.elements_kind() == HOLEY_DOUBLE_ELEMENTS && !builtin_is_push) { return false; } ElementsKind current_kind = map.elements_kind(); auto kind_ptr = kinds->data(); size_t i; for (i = 0; i < kinds->size(); i++, kind_ptr++) { if (UnionElementsKindUptoPackedness(kind_ptr, current_kind)) { break; } } if (i == kinds->size()) kinds->push_back(current_kind); } return true; } // Wraps common setup code for iterating array builtins. class IteratingArrayBuiltinHelper { public: IteratingArrayBuiltinHelper(Node* node, JSHeapBroker* broker, JSGraph* jsgraph, CompilationDependencies* dependencies) : receiver_(NodeProperties::GetValueInput(node, 1)), effect_(NodeProperties::GetEffectInput(node)), control_(NodeProperties::GetControlInput(node)), inference_(broker, receiver_, effect_) { if (!FLAG_turbo_inline_array_builtins) return; DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); const CallParameters& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return; } // Try to determine the {receiver} map. if (!inference_.HaveMaps()) return; MapHandles const& receiver_maps = inference_.GetMaps(); if (!CanInlineArrayIteratingBuiltin(broker, receiver_maps, &elements_kind_)) { return; } // TODO(jgruber): May only be needed for holey elements kinds. if (!dependencies->DependOnNoElementsProtector()) UNREACHABLE(); has_stability_dependency_ = inference_.RelyOnMapsPreferStability( dependencies, jsgraph, &effect_, control_, p.feedback()); can_reduce_ = true; } bool can_reduce() const { return can_reduce_; } bool has_stability_dependency() const { return has_stability_dependency_; } Node* effect() const { return effect_; } Node* control() const { return control_; } MapInference* inference() { return &inference_; } ElementsKind elements_kind() const { return elements_kind_; } private: bool can_reduce_ = false; bool has_stability_dependency_ = false; Node* receiver_; Node* effect_; Node* control_; MapInference inference_; ElementsKind elements_kind_; }; } // namespace Reduction JSCallReducer::ReduceArrayForEach( Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeForEach( h.inference(), h.has_stability_dependency(), h.elements_kind(), shared); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayReduce( Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeReduce( h.inference(), h.has_stability_dependency(), h.elements_kind(), ArrayReduceDirection::kLeft, shared); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayReduceRight( Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeReduce( h.inference(), h.has_stability_dependency(), h.elements_kind(), ArrayReduceDirection::kRight, shared); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayMap(Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); // Calls CreateArray and thus requires this additional protector dependency. if (!dependencies()->DependOnArraySpeciesProtector()) { return h.inference()->NoChange(); } IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeMap(h.inference(), h.has_stability_dependency(), h.elements_kind(), shared, native_context()); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayFilter( Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); // Calls CreateArray and thus requires this additional protector dependency. if (!dependencies()->DependOnArraySpeciesProtector()) { return h.inference()->NoChange(); } IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeFilter(h.inference(), h.has_stability_dependency(), h.elements_kind(), shared, native_context()); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayFind(Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeFind( h.inference(), h.has_stability_dependency(), h.elements_kind(), shared, native_context(), ArrayFindVariant::kFind); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayFindIndex( Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeFind( h.inference(), h.has_stability_dependency(), h.elements_kind(), shared, native_context(), ArrayFindVariant::kFindIndex); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArrayEvery(Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeEverySome( h.inference(), h.has_stability_dependency(), h.elements_kind(), shared, native_context(), ArrayEverySomeVariant::kEvery); return ReplaceWithSubgraph(&a, subgraph); } // ES7 Array.prototype.inludes(searchElement[, fromIndex]) // #sec-array.prototype.includes Reduction JSCallReducer::ReduceArrayIncludes(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeIndexOfIncludes( h.elements_kind(), ArrayIndexOfIncludesVariant::kIncludes); return ReplaceWithSubgraph(&a, subgraph); } // ES6 Array.prototype.indexOf(searchElement[, fromIndex]) // #sec-array.prototype.indexof Reduction JSCallReducer::ReduceArrayIndexOf(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeIndexOfIncludes( h.elements_kind(), ArrayIndexOfIncludesVariant::kIndexOf); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceArraySome(Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); IteratingArrayBuiltinHelper h(node, broker(), jsgraph(), dependencies()); if (!h.can_reduce()) return h.inference()->NoChange(); IteratingArrayBuiltinReducerAssembler a(jsgraph(), temp_zone(), node); a.InitializeEffectControl(h.effect(), h.control()); TNode subgraph = a.ReduceArrayPrototypeEverySome( h.inference(), h.has_stability_dependency(), h.elements_kind(), shared, native_context(), ArrayEverySomeVariant::kSome); return ReplaceWithSubgraph(&a, subgraph); } Reduction JSCallReducer::ReduceCallApiFunction( Node* node, const SharedFunctionInfoRef& shared) { DisallowHeapAccessIf no_heap_acess(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int const argc = p.arity_without_implicit_args(); Node* target = NodeProperties::GetValueInput(node, 0); Node* global_proxy = jsgraph()->Constant(native_context().global_proxy_object()); Node* receiver = (p.convert_mode() == ConvertReceiverMode::kNullOrUndefined) ? global_proxy : NodeProperties::GetValueInput(node, 1); Node* holder; Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); if (!shared.function_template_info().has_value()) { TRACE_BROKER_MISSING( broker(), "FunctionTemplateInfo for function with SFI " << shared); return NoChange(); } // See if we can optimize this API call to {shared}. FunctionTemplateInfoRef function_template_info( shared.function_template_info().value()); if (!function_template_info.has_call_code()) return NoChange(); if (function_template_info.accept_any_receiver() && function_template_info.is_signature_undefined()) { // 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}. // receiver = holder = effect = graph()->NewNode(simplified()->ConvertReceiver(p.convert_mode()), receiver, global_proxy, effect, control); } else { // Try to infer the {receiver} maps from the graph. MapInference inference(broker(), receiver, effect); if (inference.HaveMaps()) { MapHandles const& receiver_maps = inference.GetMaps(); MapRef first_receiver_map(broker(), receiver_maps[0]); // See if we can constant-fold the compatible receiver checks. HolderLookupResult api_holder = function_template_info.LookupHolderOfExpectedType(first_receiver_map); if (api_holder.lookup == CallOptimization::kHolderNotFound) { return inference.NoChange(); } // 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}). // // Note that we don't need to know the concrete {receiver} maps here, // meaning it's fine if the {receiver_maps} are unreliable, and we also // don't need to install any stability dependencies, since the only // relevant information regarding the {receiver} is the Map::constructor // field on the root map (which is different from the JavaScript exposed // "constructor" property) and that field cannot change. // // So if we know that {receiver} had a certain constructor at some point // in the past (i.e. it had a certain map), then this constructor is going // to be the same later, since this information cannot change with map // transitions. // // The same is true for the instance type, e.g. we still know that the // instance type is JSObject even if that information is unreliable, and // the "access check needed" bit, which also cannot change later. CHECK(first_receiver_map.IsJSReceiverMap()); CHECK(!first_receiver_map.is_access_check_needed() || function_template_info.accept_any_receiver()); for (size_t i = 1; i < receiver_maps.size(); ++i) { MapRef receiver_map(broker(), receiver_maps[i]); HolderLookupResult holder_i = function_template_info.LookupHolderOfExpectedType(receiver_map); if (api_holder.lookup != holder_i.lookup) return inference.NoChange(); 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)) { return inference.NoChange(); } } CHECK(receiver_map.IsJSReceiverMap()); CHECK(!receiver_map.is_access_check_needed() || function_template_info.accept_any_receiver()); } if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation && !inference.RelyOnMapsViaStability(dependencies())) { // We were not able to make the receiver maps reliable without map // checks but doing map checks would lead to deopt loops, so give up. return inference.NoChange(); } // TODO(neis): The maps were used in a way that does not actually require // map checks or stability dependencies. inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); // Determine the appropriate holder for the {lookup}. holder = api_holder.lookup == CallOptimization::kHolderFound ? jsgraph()->Constant(*api_holder.holder) : receiver; } else { // 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. Builtins::Name builtin_name; if (function_template_info.accept_any_receiver()) { builtin_name = Builtins::kCallFunctionTemplate_CheckCompatibleReceiver; } else if (function_template_info.is_signature_undefined()) { builtin_name = Builtins::kCallFunctionTemplate_CheckAccess; } else { builtin_name = Builtins::kCallFunctionTemplate_CheckAccessAndCompatibleReceiver; } // The CallFunctionTemplate builtin requires the {receiver} to be // an actual JSReceiver, so make sure we do the proper conversion // first if necessary. receiver = holder = effect = graph()->NewNode(simplified()->ConvertReceiver(p.convert_mode()), receiver, global_proxy, effect, control); Callable callable = Builtins::CallableFor(isolate(), builtin_name); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), argc + 1 /* implicit receiver */, CallDescriptor::kNeedsFrameState); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(callable.code())); node->ReplaceInput(1, jsgraph()->Constant(function_template_info)); node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(argc)); node->ReplaceInput(3, receiver); // Update receiver input. node->ReplaceInput(6 + argc, effect); // Update effect input. NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); return Changed(node); } } // TODO(turbofan): Consider introducing a JSCallApiCallback operator for // this and lower it during JSGenericLowering, and unify this with the // JSNativeContextSpecialization::InlineApiCall method a bit. if (!function_template_info.call_code().has_value()) { TRACE_BROKER_MISSING(broker(), "call code for function template info " << function_template_info); return NoChange(); } Address c_function = function_template_info.c_function(); if (FLAG_turbo_fast_api_calls && c_function != kNullAddress) { const CFunctionInfo* c_signature = function_template_info.c_signature(); FastApiCallReducerAssembler a(jsgraph(), graph()->zone(), node, c_function, c_signature); Node* c_call = a.ReduceFastApiCall(); ReplaceWithSubgraph(&a, c_call); return Replace(c_call); } CallHandlerInfoRef call_handler_info = *function_template_info.call_code(); Callable call_api_callback = CodeFactory::CallApiCallback(isolate()); CallInterfaceDescriptor cid = call_api_callback.descriptor(); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), cid, argc + 1 /* implicit receiver */, CallDescriptor::kNeedsFrameState); ApiFunction api_function(call_handler_info.callback()); ExternalReference function_reference = ExternalReference::Create( &api_function, ExternalReference::DIRECT_API_CALL); Node* continuation_frame_state = CreateGenericLazyDeoptContinuationFrameState( jsgraph(), shared, target, context, receiver, frame_state); node->InsertInput(graph()->zone(), 0, jsgraph()->HeapConstant(call_api_callback.code())); node->ReplaceInput(1, jsgraph()->ExternalConstant(function_reference)); node->InsertInput(graph()->zone(), 2, jsgraph()->Constant(argc)); node->InsertInput(graph()->zone(), 3, jsgraph()->Constant(call_handler_info.data())); node->InsertInput(graph()->zone(), 4, holder); node->ReplaceInput(5, receiver); // Update receiver input. node->ReplaceInput(7 + argc, continuation_frame_state); node->ReplaceInput(8 + argc, effect); // Update effect input. NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); return Changed(node); } namespace { // Check whether elements aren't mutated; we play it extremely safe here by // explicitly checking that {node} is only used by {LoadField} or // {LoadElement}. bool IsSafeArgumentsElements(Node* node) { for (Edge const edge : node->use_edges()) { if (!NodeProperties::IsValueEdge(edge)) continue; if (edge.from()->opcode() != IrOpcode::kLoadField && edge.from()->opcode() != IrOpcode::kLoadElement) { return false; } } return true; } } // namespace Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread( Node* node, int arity, CallFrequency const& frequency, FeedbackSource const& feedback, SpeculationMode speculation_mode, CallFeedbackRelation feedback_relation) { DCHECK(node->opcode() == IrOpcode::kJSCallWithArrayLike || node->opcode() == IrOpcode::kJSCallWithSpread || node->opcode() == IrOpcode::kJSConstructWithArrayLike || node->opcode() == IrOpcode::kJSConstructWithSpread); DCHECK_IMPLIES(speculation_mode == SpeculationMode::kAllowSpeculation, feedback.IsValid()); Node* arguments_list = NodeProperties::GetValueInput(node, arity); if (arguments_list->opcode() != IrOpcode::kJSCreateArguments) { return NoChange(); } // Check if {node} is the only value user of {arguments_list} (except for // value uses in frame states). If not, we give up for now. for (Edge edge : arguments_list->use_edges()) { if (!NodeProperties::IsValueEdge(edge)) continue; Node* const user = edge.from(); switch (user->opcode()) { case IrOpcode::kCheckMaps: case IrOpcode::kFrameState: case IrOpcode::kStateValues: case IrOpcode::kReferenceEqual: case IrOpcode::kReturn: // Ignore safe uses that definitely don't mess with the arguments. continue; case IrOpcode::kLoadField: { DCHECK_EQ(arguments_list, user->InputAt(0)); FieldAccess const& access = FieldAccessOf(user->op()); if (access.offset == JSArray::kLengthOffset) { // Ignore uses for arguments#length. STATIC_ASSERT( static_cast(JSArray::kLengthOffset) == static_cast(JSStrictArgumentsObject::kLengthOffset)); STATIC_ASSERT( static_cast(JSArray::kLengthOffset) == static_cast(JSSloppyArgumentsObject::kLengthOffset)); continue; } else if (access.offset == JSObject::kElementsOffset) { // Ignore safe uses for arguments#elements. if (IsSafeArgumentsElements(user)) continue; } break; } case IrOpcode::kJSCallWithArrayLike: // Ignore uses as argumentsList input to calls with array like. if (user->InputAt(2) == arguments_list) continue; break; case IrOpcode::kJSConstructWithArrayLike: // Ignore uses as argumentsList input to calls with array like. if (user->InputAt(1) == arguments_list) continue; break; case IrOpcode::kJSCallWithSpread: { // Ignore uses as spread input to calls with spread. CallParameters p = CallParametersOf(user->op()); int const arity = p.arity_without_implicit_args(); if (user->InputAt(arity + 1) == arguments_list) continue; break; } case IrOpcode::kJSConstructWithSpread: { // Ignore uses as spread input to construct with spread. ConstructParameters p = ConstructParametersOf(user->op()); int const arity = p.arity_without_implicit_args(); if (user->InputAt(arity) == arguments_list) continue; break; } default: break; } // We cannot currently reduce the {node} to something better than what // it already is, but we might be able to do something about the {node} // later, so put it on the waitlist and try again during finalization. waitlist_.insert(node); return NoChange(); } // Get to the actual frame state from which to extract the arguments; // we can only optimize this in case the {node} was already inlined into // some other function (and same for the {arguments_list}). CreateArgumentsType const type = CreateArgumentsTypeOf(arguments_list->op()); Node* frame_state = NodeProperties::GetFrameStateInput(arguments_list); FrameStateInfo state_info = FrameStateInfoOf(frame_state->op()); int start_index = 0; int formal_parameter_count; { Handle shared; if (!state_info.shared_info().ToHandle(&shared)) return NoChange(); formal_parameter_count = SharedFunctionInfoRef(broker(), shared) .internal_formal_parameter_count(); } if (type == CreateArgumentsType::kMappedArguments) { // Mapped arguments (sloppy mode) that are aliased can only be handled // here if there's no side-effect between the {node} and the {arg_array}. // TODO(turbofan): Further relax this constraint. if (formal_parameter_count != 0) { Node* effect = NodeProperties::GetEffectInput(node); if (!NodeProperties::NoObservableSideEffectBetween(effect, arguments_list)) { return NoChange(); } } } else if (type == CreateArgumentsType::kRestParameter) { start_index = formal_parameter_count; } // 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 (node->opcode() == IrOpcode::kJSCallWithSpread || node->opcode() == IrOpcode::kJSConstructWithSpread) { if (!dependencies()->DependOnArrayIteratorProtector()) return NoChange(); } // Remove the {arguments_list} input from the {node}. node->RemoveInput(arity--); // Check if are spreading to inlined arguments or to the arguments of // the outermost function. Node* outer_state = frame_state->InputAt(kFrameStateOuterStateInput); if (outer_state->opcode() != IrOpcode::kFrameState) { Operator const* op = (node->opcode() == IrOpcode::kJSCallWithArrayLike || node->opcode() == IrOpcode::kJSCallWithSpread) ? javascript()->CallForwardVarargs(arity + 1, start_index) : javascript()->ConstructForwardVarargs(arity + 2, start_index); NodeProperties::ChangeOp(node, op); return Changed(node); } // Get to the actual frame state from which to extract the arguments; // we can only optimize this in case the {node} was already inlined into // some other function (and same for the {arg_array}). FrameStateInfo outer_info = FrameStateInfoOf(outer_state->op()); if (outer_info.type() == FrameStateType::kArgumentsAdaptor) { // Need to take the parameters from the arguments adaptor. frame_state = outer_state; } // Add the actual parameters to the {node}, skipping the receiver. Node* const parameters = frame_state->InputAt(kFrameStateParametersInput); for (int i = start_index + 1; i < parameters->InputCount(); ++i) { node->InsertInput(graph()->zone(), static_cast(++arity), parameters->InputAt(i)); } if (node->opcode() == IrOpcode::kJSCallWithArrayLike || node->opcode() == IrOpcode::kJSCallWithSpread) { NodeProperties::ChangeOp( node, javascript()->Call(arity + 1, frequency, feedback, ConvertReceiverMode::kAny, speculation_mode, CallFeedbackRelation::kUnrelated)); return Changed(node).FollowedBy(ReduceJSCall(node)); } else { NodeProperties::ChangeOp( node, javascript()->Construct(arity + kTargetAndNewTarget, frequency, feedback)); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check whether the given new target value is a constructor function. The // replacement {JSConstruct} operator only checks the passed target value // but relies on the new target value to be implicitly valid. Node* check = graph()->NewNode(simplified()->ObjectIsConstructor(), new_target); Node* check_branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* check_fail = graph()->NewNode(common()->IfFalse(), check_branch); Node* check_throw = check_fail = graph()->NewNode( javascript()->CallRuntime(Runtime::kThrowTypeError, 2), jsgraph()->Constant(static_cast(MessageTemplate::kNotConstructor)), new_target, context, frame_state, effect, check_fail); control = graph()->NewNode(common()->IfTrue(), check_branch); NodeProperties::ReplaceControlInput(node, control); // Rewire potential exception edges. Node* on_exception = nullptr; if (NodeProperties::IsExceptionalCall(node, &on_exception)) { // Create appropriate {IfException} and {IfSuccess} nodes. Node* if_exception = graph()->NewNode(common()->IfException(), check_throw, check_fail); check_fail = graph()->NewNode(common()->IfSuccess(), check_fail); // Join the exception edges. Node* merge = graph()->NewNode(common()->Merge(2), if_exception, on_exception); Node* ephi = graph()->NewNode(common()->EffectPhi(2), if_exception, on_exception, merge); Node* phi = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), if_exception, on_exception, merge); ReplaceWithValue(on_exception, phi, ephi, merge); merge->ReplaceInput(1, on_exception); ephi->ReplaceInput(1, on_exception); phi->ReplaceInput(1, on_exception); } // The above %ThrowTypeError runtime call is an unconditional throw, // making it impossible to return a successful completion in this case. We // simply connect the successful completion to the graph end. Node* throw_node = graph()->NewNode(common()->Throw(), check_throw, check_fail); NodeProperties::MergeControlToEnd(graph(), common(), throw_node); return Changed(node).FollowedBy(ReduceJSConstruct(node)); } } namespace { bool ShouldUseCallICFeedback(Node* node) { HeapObjectMatcher m(node); if (m.HasValue() || m.IsCheckClosure() || m.IsJSCreateClosure()) { // Don't use CallIC feedback when we know the function // being called, i.e. either know the closure itself or // at least the SharedFunctionInfo. return false; } else if (m.IsPhi()) { // Protect against endless loops here. Node* control = NodeProperties::GetControlInput(node); if (control->opcode() == IrOpcode::kLoop) return false; // Check if {node} is a Phi of nodes which shouldn't // use CallIC feedback (not looking through loops). int const value_input_count = m.node()->op()->ValueInputCount(); for (int n = 0; n < value_input_count; ++n) { if (ShouldUseCallICFeedback(node->InputAt(n))) return true; } return false; } return true; } } // namespace bool JSCallReducer::IsBuiltinOrApiFunction(JSFunctionRef function) const { if (should_disallow_heap_access() && !function.serialized()) { TRACE_BROKER_MISSING(broker(), "data for function " << function); return false; } // TODO(neis): Add a way to check if function template info isn't serialized // and add a warning in such cases. Currently we can't tell if function // template info doesn't exist or wasn't serialized. return function.shared().HasBuiltinId() || function.shared().function_template_info().has_value(); } Reduction JSCallReducer::ReduceJSCall(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); Node* control = NodeProperties::GetControlInput(node); Node* effect = NodeProperties::GetEffectInput(node); size_t arity = p.arity_without_implicit_args(); // Try to specialize JSCall {node}s with constant {target}s. HeapObjectMatcher m(target); if (m.HasValue()) { ObjectRef target_ref = m.Ref(broker()); if (target_ref.IsJSFunction()) { JSFunctionRef function = target_ref.AsJSFunction(); if (should_disallow_heap_access() && !function.serialized()) { TRACE_BROKER_MISSING(broker(), "data for function " << function); return NoChange(); } // Don't inline cross native context. if (!function.native_context().equals(native_context())) { return NoChange(); } return ReduceJSCall(node, function.shared()); } else if (target_ref.IsJSBoundFunction()) { JSBoundFunctionRef function = target_ref.AsJSBoundFunction(); if (should_disallow_heap_access() && !function.serialized()) { TRACE_BROKER_MISSING(broker(), "data for function " << function); return NoChange(); } ObjectRef bound_this = function.bound_this(); ConvertReceiverMode const convert_mode = bound_this.IsNullOrUndefined() ? ConvertReceiverMode::kNullOrUndefined : ConvertReceiverMode::kNotNullOrUndefined; // Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]]. NodeProperties::ReplaceValueInput( node, jsgraph()->Constant(function.bound_target_function()), 0); NodeProperties::ReplaceValueInput(node, jsgraph()->Constant(bound_this), 1); // Insert the [[BoundArguments]] for {node}. FixedArrayRef bound_arguments = function.bound_arguments(); for (int i = 0; i < bound_arguments.length(); ++i) { node->InsertInput(graph()->zone(), i + 2, jsgraph()->Constant(bound_arguments.get(i))); arity++; } NodeProperties::ChangeOp( node, javascript()->Call(arity + kTargetAndReceiver, p.frequency(), p.feedback(), convert_mode, p.speculation_mode(), CallFeedbackRelation::kUnrelated)); // Try to further reduce the JSCall {node}. return Changed(node).FollowedBy(ReduceJSCall(node)); } // Don't mess with other {node}s that have a constant {target}. // TODO(bmeurer): Also support proxies here. return NoChange(); } // If {target} is the result of a JSCreateClosure operation, we can // just immediately try to inline based on the SharedFunctionInfo, // since TurboFan generally doesn't inline cross-context, and hence // the {target} must have the same native context as the call site. // Same if the {target} is the result of a CheckClosure operation. if (target->opcode() == IrOpcode::kJSCreateClosure) { CreateClosureParameters const& p = CreateClosureParametersOf(target->op()); return ReduceJSCall(node, SharedFunctionInfoRef(broker(), p.shared_info())); } else if (target->opcode() == IrOpcode::kCheckClosure) { FeedbackCellRef cell(broker(), FeedbackCellOf(target->op())); return ReduceJSCall(node, cell.value().AsFeedbackVector().shared_function_info()); } // If {target} is the result of a JSCreateBoundFunction operation, // we can just fold the construction and call the bound target // function directly instead. if (target->opcode() == IrOpcode::kJSCreateBoundFunction) { Node* bound_target_function = NodeProperties::GetValueInput(target, 0); Node* bound_this = NodeProperties::GetValueInput(target, 1); int const bound_arguments_length = static_cast(CreateBoundFunctionParametersOf(target->op()).arity()); // Patch the {node} to use [[BoundTargetFunction]] and [[BoundThis]]. NodeProperties::ReplaceValueInput(node, bound_target_function, 0); NodeProperties::ReplaceValueInput(node, bound_this, 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments_length; ++i) { Node* value = NodeProperties::GetValueInput(target, 2 + i); node->InsertInput(graph()->zone(), 2 + i, value); arity++; } // Update the JSCall operator on {node}. ConvertReceiverMode const convert_mode = NodeProperties::CanBeNullOrUndefined(broker(), bound_this, effect) ? ConvertReceiverMode::kAny : ConvertReceiverMode::kNotNullOrUndefined; NodeProperties::ChangeOp( node, javascript()->Call(arity + kTargetAndReceiver, p.frequency(), p.feedback(), convert_mode, p.speculation_mode(), CallFeedbackRelation::kUnrelated)); // Try to further reduce the JSCall {node}. return Changed(node).FollowedBy(ReduceJSCall(node)); } if (!ShouldUseCallICFeedback(target) || p.feedback_relation() != CallFeedbackRelation::kRelated || !p.feedback().IsValid()) { return NoChange(); } ProcessedFeedback const& feedback = broker()->GetFeedbackForCall(p.feedback()); if (feedback.IsInsufficient()) { return ReduceForInsufficientFeedback( node, DeoptimizeReason::kInsufficientTypeFeedbackForCall); } base::Optional feedback_target = feedback.AsCall().target(); if (feedback_target.has_value() && feedback_target->map().is_callable()) { Node* target_function = jsgraph()->Constant(*feedback_target); if (FLAG_turboprop) { if (!feedback_target->IsJSFunction()) return NoChange(); if (!IsBuiltinOrApiFunction(feedback_target->AsJSFunction())) { return NoChange(); } } // Check that the {target} is still the {target_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, target_function); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, effect, control); // Specialize the JSCall node to the {target_function}. NodeProperties::ReplaceValueInput(node, target_function, 0); NodeProperties::ReplaceEffectInput(node, effect); // Try to further reduce the JSCall {node}. return Changed(node).FollowedBy(ReduceJSCall(node)); } else if (feedback_target.has_value() && feedback_target->IsFeedbackCell()) { FeedbackCellRef feedback_cell( broker(), feedback_target.value().AsFeedbackCell().object()); if (feedback_cell.value().IsFeedbackVector()) { // Check that {target} is a closure with given {feedback_cell}, // which uniquely identifies a given function inside a native context. FeedbackVectorRef feedback_vector = feedback_cell.value().AsFeedbackVector(); if (!feedback_vector.serialized()) { TRACE_BROKER_MISSING( broker(), "feedback vector, not serialized: " << feedback_vector); return NoChange(); } if (FLAG_turboprop && !feedback_vector.shared_function_info().HasBuiltinId()) { return NoChange(); } Node* target_closure = effect = graph()->NewNode(simplified()->CheckClosure(feedback_cell.object()), target, effect, control); // Specialize the JSCall node to the {target_closure}. NodeProperties::ReplaceValueInput(node, target_closure, 0); NodeProperties::ReplaceEffectInput(node, effect); // Try to further reduce the JSCall {node}. return Changed(node).FollowedBy(ReduceJSCall(node)); } } return NoChange(); } Reduction JSCallReducer::ReduceJSCall(Node* node, const SharedFunctionInfoRef& shared) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* target = NodeProperties::GetValueInput(node, 0); // Do not reduce calls to functions with break points. if (shared.HasBreakInfo()) return NoChange(); // Raise a TypeError if the {target} is a "classConstructor". if (IsClassConstructor(shared.kind())) { NodeProperties::ReplaceValueInputs(node, target); NodeProperties::ChangeOp( node, javascript()->CallRuntime( Runtime::kThrowConstructorNonCallableError, 1)); return Changed(node); } // Check for known builtin functions. int builtin_id = shared.HasBuiltinId() ? shared.builtin_id() : Builtins::kNoBuiltinId; switch (builtin_id) { case Builtins::kArrayConstructor: return ReduceArrayConstructor(node); case Builtins::kBooleanConstructor: return ReduceBooleanConstructor(node); case Builtins::kFunctionPrototypeApply: return ReduceFunctionPrototypeApply(node); case Builtins::kFastFunctionPrototypeBind: return ReduceFunctionPrototypeBind(node); case Builtins::kFunctionPrototypeCall: return ReduceFunctionPrototypeCall(node); case Builtins::kFunctionPrototypeHasInstance: return ReduceFunctionPrototypeHasInstance(node); case Builtins::kObjectConstructor: return ReduceObjectConstructor(node); case Builtins::kObjectCreate: return ReduceObjectCreate(node); case Builtins::kObjectGetPrototypeOf: return ReduceObjectGetPrototypeOf(node); case Builtins::kObjectIs: return ReduceObjectIs(node); case Builtins::kObjectPrototypeGetProto: return ReduceObjectPrototypeGetProto(node); case Builtins::kObjectPrototypeHasOwnProperty: return ReduceObjectPrototypeHasOwnProperty(node); case Builtins::kObjectPrototypeIsPrototypeOf: return ReduceObjectPrototypeIsPrototypeOf(node); case Builtins::kReflectApply: return ReduceReflectApply(node); case Builtins::kReflectConstruct: return ReduceReflectConstruct(node); case Builtins::kReflectGet: return ReduceReflectGet(node); case Builtins::kReflectGetPrototypeOf: return ReduceReflectGetPrototypeOf(node); case Builtins::kReflectHas: return ReduceReflectHas(node); case Builtins::kArrayForEach: return ReduceArrayForEach(node, shared); case Builtins::kArrayMap: return ReduceArrayMap(node, shared); case Builtins::kArrayFilter: return ReduceArrayFilter(node, shared); case Builtins::kArrayReduce: return ReduceArrayReduce(node, shared); case Builtins::kArrayReduceRight: return ReduceArrayReduceRight(node, shared); case Builtins::kArrayPrototypeFind: return ReduceArrayFind(node, shared); case Builtins::kArrayPrototypeFindIndex: return ReduceArrayFindIndex(node, shared); case Builtins::kArrayEvery: return ReduceArrayEvery(node, shared); case Builtins::kArrayIndexOf: return ReduceArrayIndexOf(node); case Builtins::kArrayIncludes: return ReduceArrayIncludes(node); case Builtins::kArraySome: return ReduceArraySome(node, shared); case Builtins::kArrayPrototypePush: return ReduceArrayPrototypePush(node); case Builtins::kArrayPrototypePop: return ReduceArrayPrototypePop(node); case Builtins::kArrayPrototypeShift: return ReduceArrayPrototypeShift(node); case Builtins::kArrayPrototypeSlice: return ReduceArrayPrototypeSlice(node); case Builtins::kArrayPrototypeEntries: return ReduceArrayIterator(node, ArrayIteratorKind::kArrayLike, IterationKind::kEntries); case Builtins::kArrayPrototypeKeys: return ReduceArrayIterator(node, ArrayIteratorKind::kArrayLike, IterationKind::kKeys); case Builtins::kArrayPrototypeValues: return ReduceArrayIterator(node, ArrayIteratorKind::kArrayLike, IterationKind::kValues); case Builtins::kArrayIteratorPrototypeNext: return ReduceArrayIteratorPrototypeNext(node); case Builtins::kArrayIsArray: return ReduceArrayIsArray(node); case Builtins::kArrayBufferIsView: return ReduceArrayBufferIsView(node); case Builtins::kDataViewPrototypeGetByteLength: return ReduceArrayBufferViewAccessor( node, JS_DATA_VIEW_TYPE, AccessBuilder::ForJSArrayBufferViewByteLength()); case Builtins::kDataViewPrototypeGetByteOffset: return ReduceArrayBufferViewAccessor( node, JS_DATA_VIEW_TYPE, AccessBuilder::ForJSArrayBufferViewByteOffset()); case Builtins::kDataViewPrototypeGetUint8: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalUint8Array); case Builtins::kDataViewPrototypeGetInt8: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalInt8Array); case Builtins::kDataViewPrototypeGetUint16: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalUint16Array); case Builtins::kDataViewPrototypeGetInt16: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalInt16Array); case Builtins::kDataViewPrototypeGetUint32: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalUint32Array); case Builtins::kDataViewPrototypeGetInt32: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalInt32Array); case Builtins::kDataViewPrototypeGetFloat32: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalFloat32Array); case Builtins::kDataViewPrototypeGetFloat64: return ReduceDataViewAccess(node, DataViewAccess::kGet, ExternalArrayType::kExternalFloat64Array); case Builtins::kDataViewPrototypeSetUint8: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalUint8Array); case Builtins::kDataViewPrototypeSetInt8: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalInt8Array); case Builtins::kDataViewPrototypeSetUint16: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalUint16Array); case Builtins::kDataViewPrototypeSetInt16: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalInt16Array); case Builtins::kDataViewPrototypeSetUint32: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalUint32Array); case Builtins::kDataViewPrototypeSetInt32: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalInt32Array); case Builtins::kDataViewPrototypeSetFloat32: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalFloat32Array); case Builtins::kDataViewPrototypeSetFloat64: return ReduceDataViewAccess(node, DataViewAccess::kSet, ExternalArrayType::kExternalFloat64Array); case Builtins::kTypedArrayPrototypeByteLength: return ReduceArrayBufferViewAccessor( node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSArrayBufferViewByteLength()); case Builtins::kTypedArrayPrototypeByteOffset: return ReduceArrayBufferViewAccessor( node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSArrayBufferViewByteOffset()); case Builtins::kTypedArrayPrototypeLength: return ReduceArrayBufferViewAccessor( node, JS_TYPED_ARRAY_TYPE, AccessBuilder::ForJSTypedArrayLength()); case Builtins::kTypedArrayPrototypeToStringTag: return ReduceTypedArrayPrototypeToStringTag(node); case Builtins::kMathAbs: return ReduceMathUnary(node, simplified()->NumberAbs()); case Builtins::kMathAcos: return ReduceMathUnary(node, simplified()->NumberAcos()); case Builtins::kMathAcosh: return ReduceMathUnary(node, simplified()->NumberAcosh()); case Builtins::kMathAsin: return ReduceMathUnary(node, simplified()->NumberAsin()); case Builtins::kMathAsinh: return ReduceMathUnary(node, simplified()->NumberAsinh()); case Builtins::kMathAtan: return ReduceMathUnary(node, simplified()->NumberAtan()); case Builtins::kMathAtanh: return ReduceMathUnary(node, simplified()->NumberAtanh()); case Builtins::kMathCbrt: return ReduceMathUnary(node, simplified()->NumberCbrt()); case Builtins::kMathCeil: return ReduceMathUnary(node, simplified()->NumberCeil()); case Builtins::kMathCos: return ReduceMathUnary(node, simplified()->NumberCos()); case Builtins::kMathCosh: return ReduceMathUnary(node, simplified()->NumberCosh()); case Builtins::kMathExp: return ReduceMathUnary(node, simplified()->NumberExp()); case Builtins::kMathExpm1: return ReduceMathUnary(node, simplified()->NumberExpm1()); case Builtins::kMathFloor: return ReduceMathUnary(node, simplified()->NumberFloor()); case Builtins::kMathFround: return ReduceMathUnary(node, simplified()->NumberFround()); case Builtins::kMathLog: return ReduceMathUnary(node, simplified()->NumberLog()); case Builtins::kMathLog1p: return ReduceMathUnary(node, simplified()->NumberLog1p()); case Builtins::kMathLog10: return ReduceMathUnary(node, simplified()->NumberLog10()); case Builtins::kMathLog2: return ReduceMathUnary(node, simplified()->NumberLog2()); case Builtins::kMathRound: return ReduceMathUnary(node, simplified()->NumberRound()); case Builtins::kMathSign: return ReduceMathUnary(node, simplified()->NumberSign()); case Builtins::kMathSin: return ReduceMathUnary(node, simplified()->NumberSin()); case Builtins::kMathSinh: return ReduceMathUnary(node, simplified()->NumberSinh()); case Builtins::kMathSqrt: return ReduceMathUnary(node, simplified()->NumberSqrt()); case Builtins::kMathTan: return ReduceMathUnary(node, simplified()->NumberTan()); case Builtins::kMathTanh: return ReduceMathUnary(node, simplified()->NumberTanh()); case Builtins::kMathTrunc: return ReduceMathUnary(node, simplified()->NumberTrunc()); case Builtins::kMathAtan2: return ReduceMathBinary(node, simplified()->NumberAtan2()); case Builtins::kMathPow: return ReduceMathBinary(node, simplified()->NumberPow()); case Builtins::kMathClz32: return ReduceMathClz32(node); case Builtins::kMathImul: return ReduceMathImul(node); case Builtins::kMathMax: return ReduceMathMinMax(node, simplified()->NumberMax(), jsgraph()->Constant(-V8_INFINITY)); case Builtins::kMathMin: return ReduceMathMinMax(node, simplified()->NumberMin(), jsgraph()->Constant(V8_INFINITY)); case Builtins::kNumberIsFinite: return ReduceNumberIsFinite(node); case Builtins::kNumberIsInteger: return ReduceNumberIsInteger(node); case Builtins::kNumberIsSafeInteger: return ReduceNumberIsSafeInteger(node); case Builtins::kNumberIsNaN: return ReduceNumberIsNaN(node); case Builtins::kNumberParseInt: return ReduceNumberParseInt(node); case Builtins::kGlobalIsFinite: return ReduceGlobalIsFinite(node); case Builtins::kGlobalIsNaN: return ReduceGlobalIsNaN(node); case Builtins::kMapPrototypeGet: return ReduceMapPrototypeGet(node); case Builtins::kMapPrototypeHas: return ReduceMapPrototypeHas(node); case Builtins::kRegExpPrototypeTest: return ReduceRegExpPrototypeTest(node); case Builtins::kReturnReceiver: return ReduceReturnReceiver(node); case Builtins::kStringPrototypeIndexOf: return ReduceStringPrototypeIndexOf(node); case Builtins::kStringPrototypeCharAt: return ReduceStringPrototypeCharAt(node); case Builtins::kStringPrototypeCharCodeAt: return ReduceStringPrototypeStringAt(simplified()->StringCharCodeAt(), node); case Builtins::kStringPrototypeCodePointAt: return ReduceStringPrototypeStringAt(simplified()->StringCodePointAt(), node); case Builtins::kStringPrototypeSubstring: return ReduceStringPrototypeSubstring(node); case Builtins::kStringPrototypeSlice: return ReduceStringPrototypeSlice(node); case Builtins::kStringPrototypeSubstr: return ReduceStringPrototypeSubstr(node); case Builtins::kStringPrototypeStartsWith: return ReduceStringPrototypeStartsWith(node); #ifdef V8_INTL_SUPPORT case Builtins::kStringPrototypeToLowerCaseIntl: return ReduceStringPrototypeToLowerCaseIntl(node); case Builtins::kStringPrototypeToUpperCaseIntl: return ReduceStringPrototypeToUpperCaseIntl(node); #endif // V8_INTL_SUPPORT case Builtins::kStringFromCharCode: return ReduceStringFromCharCode(node); case Builtins::kStringFromCodePoint: return ReduceStringFromCodePoint(node); case Builtins::kStringPrototypeIterator: return ReduceStringPrototypeIterator(node); case Builtins::kStringIteratorPrototypeNext: return ReduceStringIteratorPrototypeNext(node); case Builtins::kStringPrototypeConcat: return ReduceStringPrototypeConcat(node); case Builtins::kTypedArrayPrototypeEntries: return ReduceArrayIterator(node, ArrayIteratorKind::kTypedArray, IterationKind::kEntries); case Builtins::kTypedArrayPrototypeKeys: return ReduceArrayIterator(node, ArrayIteratorKind::kTypedArray, IterationKind::kKeys); case Builtins::kTypedArrayPrototypeValues: return ReduceArrayIterator(node, ArrayIteratorKind::kTypedArray, IterationKind::kValues); case Builtins::kPromisePrototypeCatch: return ReducePromisePrototypeCatch(node); case Builtins::kPromisePrototypeFinally: return ReducePromisePrototypeFinally(node); case Builtins::kPromisePrototypeThen: return ReducePromisePrototypeThen(node); case Builtins::kPromiseResolveTrampoline: return ReducePromiseResolveTrampoline(node); case Builtins::kMapPrototypeEntries: return ReduceCollectionIteration(node, CollectionKind::kMap, IterationKind::kEntries); case Builtins::kMapPrototypeKeys: return ReduceCollectionIteration(node, CollectionKind::kMap, IterationKind::kKeys); case Builtins::kMapPrototypeGetSize: return ReduceCollectionPrototypeSize(node, CollectionKind::kMap); case Builtins::kMapPrototypeValues: return ReduceCollectionIteration(node, CollectionKind::kMap, IterationKind::kValues); case Builtins::kMapIteratorPrototypeNext: return ReduceCollectionIteratorPrototypeNext( node, OrderedHashMap::kEntrySize, factory()->empty_ordered_hash_map(), FIRST_JS_MAP_ITERATOR_TYPE, LAST_JS_MAP_ITERATOR_TYPE); case Builtins::kSetPrototypeEntries: return ReduceCollectionIteration(node, CollectionKind::kSet, IterationKind::kEntries); case Builtins::kSetPrototypeGetSize: return ReduceCollectionPrototypeSize(node, CollectionKind::kSet); case Builtins::kSetPrototypeValues: return ReduceCollectionIteration(node, CollectionKind::kSet, IterationKind::kValues); case Builtins::kSetIteratorPrototypeNext: return ReduceCollectionIteratorPrototypeNext( node, OrderedHashSet::kEntrySize, factory()->empty_ordered_hash_set(), FIRST_JS_SET_ITERATOR_TYPE, LAST_JS_SET_ITERATOR_TYPE); case Builtins::kDatePrototypeGetTime: return ReduceDatePrototypeGetTime(node); case Builtins::kDateNow: return ReduceDateNow(node); case Builtins::kNumberConstructor: return ReduceNumberConstructor(node); case Builtins::kBigIntAsUintN: return ReduceBigIntAsUintN(node); default: break; } if (shared.function_template_info().has_value()) { return ReduceCallApiFunction(node, shared); } return NoChange(); } Reduction JSCallReducer::ReduceJSCallWithArrayLike(Node* node) { DCHECK_EQ(IrOpcode::kJSCallWithArrayLike, node->opcode()); const CallParameters& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); DCHECK_EQ(arity, 0); return ReduceCallOrConstructWithArrayLikeOrSpread( node, arity + kTargetAndReceiver, p.frequency(), p.feedback(), p.speculation_mode(), p.feedback_relation()); } Reduction JSCallReducer::ReduceJSCallWithSpread(Node* node) { DCHECK_EQ(IrOpcode::kJSCallWithSpread, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); DCHECK_GE(p.arity(), 1); CallFrequency frequency = p.frequency(); FeedbackSource feedback = p.feedback(); return ReduceCallOrConstructWithArrayLikeOrSpread( node, arity + kTargetAndReceiver - 1, frequency, feedback, p.speculation_mode(), p.feedback_relation()); } Reduction JSCallReducer::ReduceJSConstruct(Node* node) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); int arity = p.arity_without_implicit_args(); Node* target = NodeProperties::GetValueInput(node, 0); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (p.feedback().IsValid()) { ProcessedFeedback const& feedback = broker()->GetFeedbackForCall(p.feedback()); if (feedback.IsInsufficient()) { return ReduceForInsufficientFeedback( node, DeoptimizeReason::kInsufficientTypeFeedbackForConstruct); } base::Optional feedback_target = feedback.AsCall().target(); if (feedback_target.has_value() && feedback_target->IsAllocationSite()) { // The feedback is an AllocationSite, which means we have called the // Array function and collected transition (and pretenuring) feedback // for the resulting arrays. This has to be kept in sync with the // implementation in Ignition. Node* array_function = jsgraph()->Constant(native_context().array_function()); // Check that the {target} is still the {array_function}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), target, array_function); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, effect, control); // Turn the {node} into a {JSCreateArray} call. NodeProperties::ReplaceEffectInput(node, effect); for (int i = arity; i > 0; --i) { NodeProperties::ReplaceValueInput( node, NodeProperties::GetValueInput(node, i), i + 1); } NodeProperties::ReplaceValueInput(node, array_function, 1); NodeProperties::ChangeOp( node, javascript()->CreateArray( arity, feedback_target->AsAllocationSite().object())); return Changed(node); } else if (feedback_target.has_value() && !HeapObjectMatcher(new_target).HasValue() && feedback_target->map().is_constructor()) { Node* new_target_feedback = jsgraph()->Constant(*feedback_target); // Check that the {new_target} is still the {new_target_feedback}. Node* check = graph()->NewNode(simplified()->ReferenceEqual(), new_target, new_target_feedback); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kWrongCallTarget), check, effect, control); // Specialize the JSConstruct node to the {new_target_feedback}. NodeProperties::ReplaceValueInput(node, new_target_feedback, arity + 1); NodeProperties::ReplaceEffectInput(node, effect); if (target == new_target) { NodeProperties::ReplaceValueInput(node, new_target_feedback, 0); } // Try to further reduce the JSConstruct {node}. return Changed(node).FollowedBy(ReduceJSConstruct(node)); } } // Try to specialize JSConstruct {node}s with constant {target}s. HeapObjectMatcher m(target); if (m.HasValue()) { HeapObjectRef target_ref = m.Ref(broker()); // Raise a TypeError if the {target} is not a constructor. if (!target_ref.map().is_constructor()) { NodeProperties::ReplaceValueInputs(node, target); NodeProperties::ChangeOp(node, javascript()->CallRuntime( Runtime::kThrowConstructedNonConstructable)); return Changed(node); } if (target_ref.IsJSFunction()) { JSFunctionRef function = target_ref.AsJSFunction(); if (should_disallow_heap_access() && !function.serialized()) { TRACE_BROKER_MISSING(broker(), "function, not serialized: " << function); return NoChange(); } // Do not reduce constructors with break points. if (function.shared().HasBreakInfo()) return NoChange(); // Don't inline cross native context. if (!function.native_context().equals(native_context())) { return NoChange(); } // Check for known builtin functions. int builtin_id = function.shared().HasBuiltinId() ? function.shared().builtin_id() : Builtins::kNoBuiltinId; switch (builtin_id) { case Builtins::kArrayConstructor: { // TODO(bmeurer): Deal with Array subclasses here. // Turn the {node} into a {JSCreateArray} call. for (int i = arity; i > 0; --i) { NodeProperties::ReplaceValueInput( node, NodeProperties::GetValueInput(node, i), i + 1); } NodeProperties::ReplaceValueInput(node, new_target, 1); NodeProperties::ChangeOp( node, javascript()->CreateArray(arity, Handle())); return Changed(node); } case Builtins::kObjectConstructor: { // If no value is passed, we can immediately lower to a simple // JSCreate and don't need to do any massaging of the {node}. if (arity == 0) { NodeProperties::ChangeOp(node, javascript()->Create()); return Changed(node); } // If {target} is not the same as {new_target} (i.e. the Object // constructor), {value} will be ignored and therefore we can lower // to {JSCreate}. See https://tc39.es/ecma262/#sec-object-value. HeapObjectMatcher mnew_target(new_target); if (mnew_target.HasValue() && !mnew_target.Ref(broker()).equals(function)) { // Drop the value inputs. for (int i = arity; i > 0; --i) node->RemoveInput(i); NodeProperties::ChangeOp(node, javascript()->Create()); return Changed(node); } break; } case Builtins::kPromiseConstructor: return ReducePromiseConstructor(node); case Builtins::kTypedArrayConstructor: return ReduceTypedArrayConstructor(node, function.shared()); default: break; } } else if (target_ref.IsJSBoundFunction()) { JSBoundFunctionRef function = target_ref.AsJSBoundFunction(); if (should_disallow_heap_access() && !function.serialized()) { TRACE_BROKER_MISSING(broker(), "function, not serialized: " << function); return NoChange(); } ObjectRef bound_target_function = function.bound_target_function(); FixedArrayRef bound_arguments = function.bound_arguments(); // Patch {node} to use [[BoundTargetFunction]]. NodeProperties::ReplaceValueInput( node, jsgraph()->Constant(bound_target_function), 0); // Patch {node} to use [[BoundTargetFunction]] // as new.target if {new_target} equals {target}. NodeProperties::ReplaceValueInput( node, graph()->NewNode(common()->Select(MachineRepresentation::kTagged), graph()->NewNode(simplified()->ReferenceEqual(), target, new_target), jsgraph()->Constant(bound_target_function), new_target), arity + 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments.length(); ++i) { node->InsertInput(graph()->zone(), i + 1, jsgraph()->Constant(bound_arguments.get(i))); arity++; } // Update the JSConstruct operator on {node}. NodeProperties::ChangeOp( node, javascript()->Construct(arity + kTargetAndNewTarget, p.frequency(), FeedbackSource())); // Try to further reduce the JSConstruct {node}. return Changed(node).FollowedBy(ReduceJSConstruct(node)); } // TODO(bmeurer): Also support optimizing proxies here. } // If {target} is the result of a JSCreateBoundFunction operation, // we can just fold the construction and construct the bound target // function directly instead. if (target->opcode() == IrOpcode::kJSCreateBoundFunction) { Node* bound_target_function = NodeProperties::GetValueInput(target, 0); int const bound_arguments_length = static_cast(CreateBoundFunctionParametersOf(target->op()).arity()); // Patch the {node} to use [[BoundTargetFunction]]. NodeProperties::ReplaceValueInput(node, bound_target_function, 0); // Patch {node} to use [[BoundTargetFunction]] // as new.target if {new_target} equals {target}. NodeProperties::ReplaceValueInput( node, graph()->NewNode(common()->Select(MachineRepresentation::kTagged), graph()->NewNode(simplified()->ReferenceEqual(), target, new_target), bound_target_function, new_target), arity + 1); // Insert the [[BoundArguments]] for {node}. for (int i = 0; i < bound_arguments_length; ++i) { Node* value = NodeProperties::GetValueInput(target, 2 + i); node->InsertInput(graph()->zone(), 1 + i, value); arity++; } // Update the JSConstruct operator on {node}. NodeProperties::ChangeOp( node, javascript()->Construct(arity + kTargetAndNewTarget, p.frequency(), FeedbackSource())); // Try to further reduce the JSConstruct {node}. return Changed(node).FollowedBy(ReduceJSConstruct(node)); } return NoChange(); } // ES #sec-string.prototype.indexof Reduction JSCallReducer::ReduceStringPrototypeIndexOf(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (node->op()->ValueInputCount() >= 3) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* new_receiver = effect = graph()->NewNode( simplified()->CheckString(p.feedback()), receiver, effect, control); Node* search_string = NodeProperties::GetValueInput(node, 2); Node* new_search_string = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), search_string, effect, control); Node* new_position = jsgraph()->ZeroConstant(); if (node->op()->ValueInputCount() >= 4) { Node* position = NodeProperties::GetValueInput(node, 3); new_position = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), position, effect, control); } NodeProperties::ReplaceEffectInput(node, effect); RelaxEffectsAndControls(node); node->ReplaceInput(0, new_receiver); node->ReplaceInput(1, new_search_string); node->ReplaceInput(2, new_position); node->TrimInputCount(3); NodeProperties::ChangeOp(node, simplified()->StringIndexOf()); return Changed(node); } return NoChange(); } // ES #sec-string.prototype.substring Reduction JSCallReducer::ReduceStringPrototypeSubstring(Node* node) { if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } JSCallReducerAssembler a(jsgraph(), temp_zone(), node); Node* subgraph = a.ReduceStringPrototypeSubstring(); return ReplaceWithSubgraph(&a, subgraph); } // ES #sec-string.prototype.slice Reduction JSCallReducer::ReduceStringPrototypeSlice(Node* node) { if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } JSCallReducerAssembler a(jsgraph(), temp_zone(), node); Node* subgraph = a.ReduceStringPrototypeSlice(); return ReplaceWithSubgraph(&a, subgraph); } // ES #sec-string.prototype.substr Reduction JSCallReducer::ReduceStringPrototypeSubstr(Node* node) { if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* start = NodeProperties::GetValueInput(node, 2); Node* end = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); start = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), start, effect, control); Node* length = graph()->NewNode(simplified()->StringLength(), receiver); // Replace {end} argument with {length} if it is undefined. { Node* check = graph()->NewNode(simplified()->ReferenceEqual(), end, jsgraph()->UndefinedConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = length; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = efalse = graph()->NewNode( simplified()->CheckSmi(p.feedback()), end, efalse, if_false); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); end = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); } Node* initStart = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kFalse), graph()->NewNode(simplified()->NumberLessThan(), start, jsgraph()->ZeroConstant()), graph()->NewNode( simplified()->NumberMax(), graph()->NewNode(simplified()->NumberAdd(), length, start), jsgraph()->ZeroConstant()), start); // The select above guarantees that initStart is non-negative, but // our typer can't figure that out yet. initStart = effect = graph()->NewNode( common()->TypeGuard(Type::UnsignedSmall()), initStart, effect, control); Node* resultLength = graph()->NewNode( simplified()->NumberMin(), graph()->NewNode(simplified()->NumberMax(), end, jsgraph()->ZeroConstant()), graph()->NewNode(simplified()->NumberSubtract(), length, initStart)); // The the select below uses {resultLength} only if {resultLength > 0}, // but our typer can't figure that out yet. Node* to = effect = graph()->NewNode( common()->TypeGuard(Type::UnsignedSmall()), graph()->NewNode(simplified()->NumberAdd(), initStart, resultLength), effect, control); Node* result_string = nullptr; // Return empty string if {from} is smaller than {to}. { Node* check = graph()->NewNode(simplified()->NumberLessThan(), jsgraph()->ZeroConstant(), resultLength); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = etrue = graph()->NewNode(simplified()->StringSubstring(), receiver, initStart, to, etrue, if_true); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = jsgraph()->EmptyStringConstant(); control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); result_string = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); } ReplaceWithValue(node, result_string, effect, control); return Replace(result_string); } Reduction JSCallReducer::ReduceJSConstructWithArrayLike(Node* node) { DCHECK_EQ(IrOpcode::kJSConstructWithArrayLike, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); const int arity = p.arity_without_implicit_args(); DCHECK_EQ(arity, 1); return ReduceCallOrConstructWithArrayLikeOrSpread( node, arity, p.frequency(), p.feedback(), SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kRelated); } Reduction JSCallReducer::ReduceJSConstructWithSpread(Node* node) { DCHECK_EQ(IrOpcode::kJSConstructWithSpread, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); int arity = p.arity_without_implicit_args(); DCHECK_LE(1u, arity); CallFrequency frequency = p.frequency(); FeedbackSource feedback = p.feedback(); return ReduceCallOrConstructWithArrayLikeOrSpread( node, arity, frequency, feedback, SpeculationMode::kDisallowSpeculation, CallFeedbackRelation::kRelated); } Reduction JSCallReducer::ReduceReturnReceiver(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); ReplaceWithValue(node, receiver); return Replace(receiver); } Reduction JSCallReducer::ReduceForInsufficientFeedback( Node* node, DeoptimizeReason reason) { DCHECK(node->opcode() == IrOpcode::kJSCall || node->opcode() == IrOpcode::kJSConstruct); if (!(flags() & kBailoutOnUninitialized)) return NoChange(); // TODO(mythria): May be add additional flags to specify if we need to deopt // on calls / construct rather than checking for TurboProp here. We may need // it for NativeContextIndependent code too. if (FLAG_turboprop) return NoChange(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* frame_state = NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead()); Node* deoptimize = graph()->NewNode( common()->Deoptimize(DeoptimizeKind::kSoft, reason, FeedbackSource()), frame_state, effect, control); // TODO(bmeurer): This should be on the AdvancedReducer somehow. NodeProperties::MergeControlToEnd(graph(), common(), deoptimize); Revisit(graph()->end()); node->TrimInputCount(0); NodeProperties::ChangeOp(node, common()->Dead()); return Changed(node); } Node* JSCallReducer::LoadReceiverElementsKind(Node* receiver, Node** effect, Node** control) { Node* receiver_map = *effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, *effect, *control); Node* receiver_bit_field2 = *effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map, *effect, *control); Node* receiver_elements_kind = graph()->NewNode( simplified()->NumberShiftRightLogical(), graph()->NewNode( simplified()->NumberBitwiseAnd(), receiver_bit_field2, jsgraph()->Constant(Map::Bits2::ElementsKindBits::kMask)), jsgraph()->Constant(Map::Bits2::ElementsKindBits::kShift)); return receiver_elements_kind; } void JSCallReducer::CheckIfElementsKind(Node* receiver_elements_kind, ElementsKind kind, Node* control, Node** if_true, Node** if_false) { Node* is_packed_kind = graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind, jsgraph()->Constant(GetPackedElementsKind(kind))); Node* packed_branch = graph()->NewNode(common()->Branch(), is_packed_kind, control); Node* if_packed = graph()->NewNode(common()->IfTrue(), packed_branch); if (IsHoleyElementsKind(kind)) { Node* if_not_packed = graph()->NewNode(common()->IfFalse(), packed_branch); Node* is_holey_kind = graph()->NewNode(simplified()->NumberEqual(), receiver_elements_kind, jsgraph()->Constant(GetHoleyElementsKind(kind))); Node* holey_branch = graph()->NewNode(common()->Branch(), is_holey_kind, if_not_packed); Node* if_holey = graph()->NewNode(common()->IfTrue(), holey_branch); Node* if_not_packed_not_holey = graph()->NewNode(common()->IfFalse(), holey_branch); *if_true = graph()->NewNode(common()->Merge(2), if_packed, if_holey); *if_false = if_not_packed_not_holey; } else { *if_true = if_packed; *if_false = graph()->NewNode(common()->IfFalse(), packed_branch); } } // ES6 section 22.1.3.18 Array.prototype.push ( ) Reduction JSCallReducer::ReduceArrayPrototypePush(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } int const num_values = node->op()->ValueInputCount() - 2; Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); std::vector kinds; if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds, true)) { return inference.NoChange(); } if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); std::vector controls_to_merge; std::vector effects_to_merge; std::vector values_to_merge; Node* return_value = jsgraph()->UndefinedConstant(); Node* receiver_elements_kind = LoadReceiverElementsKind(receiver, &effect, &control); Node* next_control = control; Node* next_effect = effect; for (size_t i = 0; i < kinds.size(); i++) { ElementsKind kind = kinds[i]; control = next_control; effect = next_effect; // We do not need branch for the last elements kind. if (i != kinds.size() - 1) { CheckIfElementsKind(receiver_elements_kind, kind, control, &control, &next_control); } // Collect the value inputs to push. std::vector values(num_values); for (int i = 0; i < num_values; ++i) { values[i] = NodeProperties::GetValueInput(node, 2 + i); } for (auto& value : values) { if (IsSmiElementsKind(kind)) { value = effect = graph()->NewNode(simplified()->CheckSmi(p.feedback()), value, effect, control); } else if (IsDoubleElementsKind(kind)) { value = effect = graph()->NewNode( simplified()->CheckNumber(p.feedback()), value, effect, control); // Make sure we do not store signaling NaNs into double arrays. value = graph()->NewNode(simplified()->NumberSilenceNaN(), value); } } // Load the "length" property of the {receiver}. Node* length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); return_value = length; // Check if we have any {values} to push. if (num_values > 0) { // Compute the resulting "length" of the {receiver}. Node* new_length = return_value = graph()->NewNode( simplified()->NumberAdd(), length, jsgraph()->Constant(num_values)); // Load the elements backing store of the {receiver}. Node* elements = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, effect, control); Node* elements_length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForFixedArrayLength()), elements, effect, control); GrowFastElementsMode mode = IsDoubleElementsKind(kind) ? GrowFastElementsMode::kDoubleElements : GrowFastElementsMode::kSmiOrObjectElements; elements = effect = graph()->NewNode( simplified()->MaybeGrowFastElements(mode, p.feedback()), receiver, elements, graph()->NewNode(simplified()->NumberAdd(), length, jsgraph()->Constant(num_values - 1)), elements_length, effect, control); // Update the JSArray::length field. Since this is observable, // there must be no other check after this. effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), receiver, new_length, effect, control); // Append the {values} to the {elements}. for (int i = 0; i < num_values; ++i) { Node* value = values[i]; Node* index = graph()->NewNode(simplified()->NumberAdd(), length, jsgraph()->Constant(i)); effect = graph()->NewNode(simplified()->StoreElement( AccessBuilder::ForFixedArrayElement(kind)), elements, index, value, effect, control); } } controls_to_merge.push_back(control); effects_to_merge.push_back(effect); values_to_merge.push_back(return_value); } if (controls_to_merge.size() > 1) { int const count = static_cast(controls_to_merge.size()); control = graph()->NewNode(common()->Merge(count), count, &controls_to_merge.front()); effects_to_merge.push_back(control); effect = graph()->NewNode(common()->EffectPhi(count), count + 1, &effects_to_merge.front()); values_to_merge.push_back(control); return_value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), count + 1, &values_to_merge.front()); } ReplaceWithValue(node, return_value, effect, control); return Replace(return_value); } // ES6 section 22.1.3.17 Array.prototype.pop ( ) Reduction JSCallReducer::ReduceArrayPrototypePop(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); std::vector kinds; if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds)) { return inference.NoChange(); } if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); std::vector controls_to_merge; std::vector effects_to_merge; std::vector values_to_merge; Node* value = jsgraph()->UndefinedConstant(); Node* receiver_elements_kind = LoadReceiverElementsKind(receiver, &effect, &control); Node* next_control = control; Node* next_effect = effect; for (size_t i = 0; i < kinds.size(); i++) { ElementsKind kind = kinds[i]; control = next_control; effect = next_effect; // We do not need branch for the last elements kind. if (i != kinds.size() - 1) { CheckIfElementsKind(receiver_elements_kind, kind, control, &control, &next_control); } // Load the "length" property of the {receiver}. Node* length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Check if the {receiver} has any elements. Node* check = graph()->NewNode(simplified()->NumberEqual(), length, jsgraph()->ZeroConstant()); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = jsgraph()->UndefinedConstant(); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse; { // TODO(tebbi): We should trim the backing store if the capacity is too // big, as implemented in elements.cc:ElementsAccessorBase::SetLengthImpl. // Load the elements backing store from the {receiver}. Node* elements = efalse = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, efalse, if_false); // Ensure that we aren't popping from a copy-on-write backing store. if (IsSmiOrObjectElementsKind(kind)) { elements = efalse = graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver, elements, efalse, if_false); } // Compute the new {length}. length = graph()->NewNode(simplified()->NumberSubtract(), length, jsgraph()->OneConstant()); // Store the new {length} to the {receiver}. efalse = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), receiver, length, efalse, if_false); // Load the last entry from the {elements}. vfalse = efalse = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement(kind)), elements, length, efalse, if_false); // Store a hole to the element we just removed from the {receiver}. efalse = graph()->NewNode( simplified()->StoreElement( AccessBuilder::ForFixedArrayElement(GetHoleyElementsKind(kind))), elements, length, jsgraph()->TheHoleConstant(), efalse, if_false); } control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); // Convert the hole to undefined. Do this last, so that we can optimize // conversion operator via some smart strength reduction in many cases. if (IsHoleyElementsKind(kind)) { value = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); } controls_to_merge.push_back(control); effects_to_merge.push_back(effect); values_to_merge.push_back(value); } if (controls_to_merge.size() > 1) { int const count = static_cast(controls_to_merge.size()); control = graph()->NewNode(common()->Merge(count), count, &controls_to_merge.front()); effects_to_merge.push_back(control); effect = graph()->NewNode(common()->EffectPhi(count), count + 1, &effects_to_merge.front()); values_to_merge.push_back(control); value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), count + 1, &values_to_merge.front()); } ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 22.1.3.22 Array.prototype.shift ( ) Reduction JSCallReducer::ReduceArrayPrototypeShift(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* target = NodeProperties::GetValueInput(node, 0); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); std::vector kinds; if (!CanInlineArrayResizingBuiltin(broker(), receiver_maps, &kinds)) { return inference.NoChange(); } if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); std::vector controls_to_merge; std::vector effects_to_merge; std::vector values_to_merge; Node* value = jsgraph()->UndefinedConstant(); Node* receiver_elements_kind = LoadReceiverElementsKind(receiver, &effect, &control); Node* next_control = control; Node* next_effect = effect; for (size_t i = 0; i < kinds.size(); i++) { ElementsKind kind = kinds[i]; control = next_control; effect = next_effect; // We do not need branch for the last elements kind. if (i != kinds.size() - 1) { CheckIfElementsKind(receiver_elements_kind, kind, control, &control, &next_control); } // Load length of the {receiver}. Node* length = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayLength(kind)), receiver, effect, control); // Return undefined if {receiver} has no elements. Node* check0 = graph()->NewNode(simplified()->NumberEqual(), length, jsgraph()->ZeroConstant()); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check0, control); Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* etrue0 = effect; Node* vtrue0 = jsgraph()->UndefinedConstant(); Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* efalse0 = effect; Node* vfalse0; { // Check if we should take the fast-path. Node* check1 = graph()->NewNode(simplified()->NumberLessThanOrEqual(), length, jsgraph()->Constant(JSArray::kMaxCopyElements)); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check1, if_false0); Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1); Node* etrue1 = efalse0; Node* vtrue1; { Node* elements = etrue1 = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver, etrue1, if_true1); // Load the first element here, which we return below. vtrue1 = etrue1 = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement(kind)), elements, jsgraph()->ZeroConstant(), etrue1, if_true1); // Ensure that we aren't shifting a copy-on-write backing store. if (IsSmiOrObjectElementsKind(kind)) { elements = etrue1 = graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver, elements, etrue1, if_true1); } // Shift the remaining {elements} by one towards the start. Node* loop = graph()->NewNode(common()->Loop(2), if_true1, if_true1); Node* eloop = graph()->NewNode(common()->EffectPhi(2), etrue1, etrue1, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* index = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), jsgraph()->OneConstant(), jsgraph()->Constant(JSArray::kMaxCopyElements - 1), loop); { Node* check2 = graph()->NewNode(simplified()->NumberLessThan(), index, length); Node* branch2 = graph()->NewNode(common()->Branch(), check2, loop); if_true1 = graph()->NewNode(common()->IfFalse(), branch2); etrue1 = eloop; Node* control = graph()->NewNode(common()->IfTrue(), branch2); Node* effect = etrue1; ElementAccess const access = AccessBuilder::ForFixedArrayElement(kind); Node* value = effect = graph()->NewNode(simplified()->LoadElement(access), elements, index, effect, control); effect = graph()->NewNode( simplified()->StoreElement(access), elements, graph()->NewNode(simplified()->NumberSubtract(), index, jsgraph()->OneConstant()), value, effect, control); loop->ReplaceInput(1, control); eloop->ReplaceInput(1, effect); index->ReplaceInput(1, graph()->NewNode(simplified()->NumberAdd(), index, jsgraph()->OneConstant())); } // Compute the new {length}. length = graph()->NewNode(simplified()->NumberSubtract(), length, jsgraph()->OneConstant()); // Store the new {length} to the {receiver}. etrue1 = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSArrayLength(kind)), receiver, length, etrue1, if_true1); // Store a hole to the element we just removed from the {receiver}. etrue1 = graph()->NewNode( simplified()->StoreElement(AccessBuilder::ForFixedArrayElement( GetHoleyElementsKind(kind))), elements, length, jsgraph()->TheHoleConstant(), etrue1, if_true1); } Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1); Node* efalse1 = efalse0; Node* vfalse1; { // Call the generic C++ implementation. const int builtin_index = Builtins::kArrayShift; auto call_descriptor = Linkage::GetCEntryStubCallDescriptor( graph()->zone(), 1, BuiltinArguments::kNumExtraArgsWithReceiver, Builtins::name(builtin_index), node->op()->properties(), CallDescriptor::kNeedsFrameState); Node* stub_code = jsgraph()->CEntryStubConstant(1, kDontSaveFPRegs, kArgvOnStack, true); Address builtin_entry = Builtins::CppEntryOf(builtin_index); Node* entry = jsgraph()->ExternalConstant( ExternalReference::Create(builtin_entry)); Node* argc = jsgraph()->Constant(BuiltinArguments::kNumExtraArgsWithReceiver); if_false1 = efalse1 = vfalse1 = graph()->NewNode(common()->Call(call_descriptor), stub_code, receiver, jsgraph()->PaddingConstant(), argc, target, jsgraph()->UndefinedConstant(), entry, argc, context, frame_state, efalse1, if_false1); } if_false0 = graph()->NewNode(common()->Merge(2), if_true1, if_false1); efalse0 = graph()->NewNode(common()->EffectPhi(2), etrue1, efalse1, if_false0); vfalse0 = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue1, vfalse1, if_false0); } control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, efalse0, control); value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0, vfalse0, control); // Convert the hole to undefined. Do this last, so that we can optimize // conversion operator via some smart strength reduction in many cases. if (IsHoleyElementsKind(kind)) { value = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(), value); } controls_to_merge.push_back(control); effects_to_merge.push_back(effect); values_to_merge.push_back(value); } if (controls_to_merge.size() > 1) { int const count = static_cast(controls_to_merge.size()); control = graph()->NewNode(common()->Merge(count), count, &controls_to_merge.front()); effects_to_merge.push_back(control); effect = graph()->NewNode(common()->EffectPhi(count), count + 1, &effects_to_merge.front()); values_to_merge.push_back(control); value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), count + 1, &values_to_merge.front()); } ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 22.1.3.23 Array.prototype.slice ( ) Reduction JSCallReducer::ReduceArrayPrototypeSlice(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); if (!FLAG_turbo_inline_array_builtins) return NoChange(); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* start = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* end = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Optimize for the case where we simply clone the {receiver}, // i.e. when the {start} is zero and the {end} is undefined // (meaning it will be set to {receiver}s "length" property). if (!NumberMatcher(start).Is(0) || !HeapObjectMatcher(end).Is(factory()->undefined_value())) { return NoChange(); } MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); // Check that the maps are of JSArray (and more). // TODO(turbofan): Consider adding special case for the common pattern // `slice.call(arguments)`, for example jQuery makes heavy use of that. bool can_be_holey = false; for (Handle map : receiver_maps) { MapRef receiver_map(broker(), map); if (!receiver_map.supports_fast_array_iteration()) return inference.NoChange(); if (IsHoleyElementsKind(receiver_map.elements_kind())) { can_be_holey = true; } } if (!dependencies()->DependOnArraySpeciesProtector()) return inference.NoChange(); if (can_be_holey) { if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); } inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); // TODO(turbofan): 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. Callable callable = Builtins::CallableFor(isolate(), Builtins::kCloneFastJSArray); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags, Operator::kNoThrow | Operator::kNoDeopt); // Calls to Builtins::kCloneFastJSArray produce COW arrays // if the original array is COW Node* clone = effect = graph()->NewNode( common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()), receiver, context, effect, control); ReplaceWithValue(node, clone, effect, control); return Replace(clone); } // ES6 section 22.1.2.2 Array.isArray ( arg ) Reduction JSCallReducer::ReduceArrayIsArray(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); // We certainly know that undefined is not an array. if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* object = NodeProperties::GetValueInput(node, 2); node->ReplaceInput(0, object); node->ReplaceInput(1, context); node->ReplaceInput(2, frame_state); node->ReplaceInput(3, effect); node->ReplaceInput(4, control); node->TrimInputCount(5); NodeProperties::ChangeOp(node, javascript()->ObjectIsArray()); return Changed(node); } Reduction JSCallReducer::ReduceArrayIterator(Node* node, ArrayIteratorKind array_kind, IterationKind iteration_kind) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Check if we know that {receiver} is a valid JSReceiver. MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { return NoChange(); } // TypedArray iteration is stricter: it throws if the receiver is not a typed // array. So don't bother optimizing in that case. if (array_kind == ArrayIteratorKind::kTypedArray && !inference.AllOfInstanceTypesAre(InstanceType::JS_TYPED_ARRAY_TYPE)) { return NoChange(); } if (array_kind == ArrayIteratorKind::kTypedArray) { // Make sure we deopt when the JSArrayBuffer is detached. if (!dependencies()->DependOnArrayBufferDetachingProtector()) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), receiver, effect, control); Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasDetachedBit::kMask)), jsgraph()->ZeroConstant()); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached, p.feedback()), check, effect, control); } } // Morph the {node} into a JSCreateArrayIterator with the given {kind}. RelaxControls(node); node->ReplaceInput(0, receiver); node->ReplaceInput(1, context); node->ReplaceInput(2, effect); node->ReplaceInput(3, control); node->TrimInputCount(4); NodeProperties::ChangeOp(node, javascript()->CreateArrayIterator(iteration_kind)); return Changed(node); } // ES #sec-%arrayiteratorprototype%.next Reduction JSCallReducer::ReduceArrayIteratorPrototypeNext(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* iterator = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (iterator->opcode() != IrOpcode::kJSCreateArrayIterator) return NoChange(); IterationKind const iteration_kind = CreateArrayIteratorParametersOf(iterator->op()).kind(); Node* iterated_object = NodeProperties::GetValueInput(iterator, 0); Node* iterator_effect = NodeProperties::GetEffectInput(iterator); MapInference inference(broker(), iterated_object, iterator_effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& iterated_object_maps = inference.GetMaps(); // Check that various {iterated_object_maps} have compatible elements kinds. ElementsKind elements_kind = MapRef(broker(), iterated_object_maps[0]).elements_kind(); if (IsTypedArrayElementsKind(elements_kind)) { // TurboFan doesn't support loading from BigInt typed arrays yet. if (elements_kind == BIGUINT64_ELEMENTS || elements_kind == BIGINT64_ELEMENTS) { return inference.NoChange(); } for (Handle map : iterated_object_maps) { MapRef iterated_object_map(broker(), map); if (iterated_object_map.elements_kind() != elements_kind) { return inference.NoChange(); } } } else { if (!CanInlineArrayIteratingBuiltin(broker(), iterated_object_maps, &elements_kind)) { return inference.NoChange(); } } if (IsHoleyElementsKind(elements_kind)) { if (!dependencies()->DependOnNoElementsProtector()) UNREACHABLE(); } // Since the map inference was done relative to {iterator_effect} rather than // {effect}, we need to guard the use of the map(s) even when the inference // was reliable. inference.InsertMapChecks(jsgraph(), &effect, control, p.feedback()); if (IsTypedArrayElementsKind(elements_kind)) { // See if we can skip the detaching check. if (!dependencies()->DependOnArrayBufferDetachingProtector()) { // Bail out if the {iterated_object}s JSArrayBuffer was detached. Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), iterated_object, effect, control); Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasDetachedBit::kMask)), jsgraph()->ZeroConstant()); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached, p.feedback()), check, effect, control); } } // Load the [[NextIndex]] from the {iterator} and leverage the fact // that we definitely know that it's in Unsigned32 range since the // {iterated_object} is either a JSArray or a JSTypedArray. For the // latter case we even know that it's a Smi in UnsignedSmall range. FieldAccess index_access = AccessBuilder::ForJSArrayIteratorNextIndex(); if (IsTypedArrayElementsKind(elements_kind)) { index_access.type = TypeCache::Get()->kJSTypedArrayLengthType; } else { index_access.type = TypeCache::Get()->kJSArrayLengthType; } Node* index = effect = graph()->NewNode(simplified()->LoadField(index_access), iterator, effect, control); // Load the elements of the {iterated_object}. While it feels // counter-intuitive to place the elements pointer load before // the condition below, as it might not be needed (if the {index} // is out of bounds for the {iterated_object}), it's better this // way as it allows the LoadElimination to eliminate redundant // reloads of the elements pointer. Node* elements = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSObjectElements()), iterated_object, effect, control); // Load the length of the {iterated_object}. Due to the map checks we // already know something about the length here, which we can leverage // to generate Word32 operations below without additional checking. FieldAccess length_access = IsTypedArrayElementsKind(elements_kind) ? AccessBuilder::ForJSTypedArrayLength() : AccessBuilder::ForJSArrayLength(elements_kind); Node* length = effect = graph()->NewNode( simplified()->LoadField(length_access), iterated_object, effect, control); // Check whether {index} is within the valid range for the {iterated_object}. Node* check = graph()->NewNode(simplified()->NumberLessThan(), index, length); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kNone), check, control); Node* done_true; Node* value_true; Node* etrue = effect; Node* if_true = graph()->NewNode(common()->IfTrue(), branch); { // We know that the {index} is range of the {length} now. index = etrue = graph()->NewNode( common()->TypeGuard( Type::Range(0.0, length_access.type.Max() - 1.0, graph()->zone())), index, etrue, if_true); done_true = jsgraph()->FalseConstant(); if (iteration_kind == IterationKind::kKeys) { // Just return the {index}. value_true = index; } else { DCHECK(iteration_kind == IterationKind::kEntries || iteration_kind == IterationKind::kValues); if (IsTypedArrayElementsKind(elements_kind)) { Node* base_ptr = etrue = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSTypedArrayBasePointer()), iterated_object, etrue, if_true); Node* external_ptr = etrue = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForJSTypedArrayExternalPointer()), iterated_object, etrue, if_true); ExternalArrayType array_type = kExternalInt8Array; switch (elements_kind) { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case TYPE##_ELEMENTS: \ array_type = kExternal##Type##Array; \ break; TYPED_ARRAYS(TYPED_ARRAY_CASE) default: UNREACHABLE(); #undef TYPED_ARRAY_CASE } Node* buffer = etrue = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSArrayBufferViewBuffer()), iterated_object, etrue, if_true); value_true = etrue = graph()->NewNode(simplified()->LoadTypedElement(array_type), buffer, base_ptr, external_ptr, index, etrue, if_true); } else { value_true = etrue = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement(elements_kind)), elements, index, etrue, if_true); // Convert hole to undefined if needed. if (elements_kind == HOLEY_ELEMENTS || elements_kind == HOLEY_SMI_ELEMENTS) { value_true = graph()->NewNode( simplified()->ConvertTaggedHoleToUndefined(), value_true); } else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) { // TODO(6587): avoid deopt if not all uses of value are truncated. CheckFloat64HoleMode mode = CheckFloat64HoleMode::kAllowReturnHole; value_true = etrue = graph()->NewNode( simplified()->CheckFloat64Hole(mode, p.feedback()), value_true, etrue, if_true); } } if (iteration_kind == IterationKind::kEntries) { // Allocate elements for key/value pair value_true = etrue = graph()->NewNode(javascript()->CreateKeyValueArray(), index, value_true, context, etrue); } else { DCHECK_EQ(IterationKind::kValues, iteration_kind); } } // Increment the [[NextIndex]] field in the {iterator}. The TypeGuards // above guarantee that the {next_index} is in the UnsignedSmall range. Node* next_index = graph()->NewNode(simplified()->NumberAdd(), index, jsgraph()->OneConstant()); etrue = graph()->NewNode(simplified()->StoreField(index_access), iterator, next_index, etrue, if_true); } Node* done_false; Node* value_false; Node* efalse = effect; Node* if_false = graph()->NewNode(common()->IfFalse(), branch); { // iterator.[[NextIndex]] >= array.length, stop iterating. done_false = jsgraph()->TrueConstant(); value_false = jsgraph()->UndefinedConstant(); 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. Node* end_index = jsgraph()->Constant(index_access.type.Max()); efalse = graph()->NewNode(simplified()->StoreField(index_access), iterator, end_index, efalse, if_false); } } control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), value_true, value_false, control); Node* done = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), done_true, done_false, control); // Create IteratorResult object. value = effect = graph()->NewNode(javascript()->CreateIterResultObject(), value, done, context, effect); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 21.1.3.2 String.prototype.charCodeAt ( pos ) // ES6 section 21.1.3.3 String.prototype.codePointAt ( pos ) Reduction JSCallReducer::ReduceStringPrototypeStringAt( const Operator* string_access_operator, Node* node) { DCHECK(string_access_operator->opcode() == IrOpcode::kStringCharCodeAt || string_access_operator->opcode() == IrOpcode::kStringCodePointAt); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* index = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Ensure that the {receiver} is actually a String. receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); // Determine the {receiver} length. Node* receiver_length = graph()->NewNode(simplified()->StringLength(), receiver); // Check that the {index} is within range. index = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), index, receiver_length, effect, control); // Return the character from the {receiver} as single character string. Node* masked_index = graph()->NewNode(simplified()->PoisonIndex(), index); Node* value = effect = graph()->NewNode(string_access_operator, receiver, masked_index, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES section 21.1.3.20 // String.prototype.startsWith ( searchString [ , position ] ) Reduction JSCallReducer::ReduceStringPrototypeStartsWith(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (node->op()->ValueInputCount() < 3) { effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value, effect, control); return Replace(value); } Node* search_string = NodeProperties::GetValueInput(node, 2); Node* position = node->op()->ValueInputCount() >= 4 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->ZeroConstant(); HeapObjectMatcher m(search_string); if (m.HasValue()) { ObjectRef target_ref = m.Ref(broker()); if (target_ref.IsString()) { StringRef str = target_ref.AsString(); if (str.length() == 1) { receiver = effect = graph()->NewNode( simplified()->CheckString(p.feedback()), receiver, effect, control); position = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), position, effect, control); Node* string_length = graph()->NewNode(simplified()->StringLength(), receiver); Node* unsigned_position = graph()->NewNode( simplified()->NumberMax(), position, jsgraph()->ZeroConstant()); Node* check = graph()->NewNode(simplified()->NumberLessThan(), unsigned_position, string_length); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kNone), check, control); Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = jsgraph()->FalseConstant(); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue; { Node* masked_position = graph()->NewNode(simplified()->PoisonIndex(), unsigned_position); Node* string_first = etrue = graph()->NewNode(simplified()->StringCharCodeAt(), receiver, masked_position, etrue, if_true); Node* search_first = jsgraph()->Constant(str.GetFirstChar()); vtrue = graph()->NewNode(simplified()->NumberEqual(), string_first, search_first); } control = graph()->NewNode(common()->Merge(2), if_true, if_false); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } } } return NoChange(); } // ES section 21.1.3.1 String.prototype.charAt ( pos ) Reduction JSCallReducer::ReduceStringPrototypeCharAt(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* index = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Ensure that the {receiver} is actually a String. receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), receiver, effect, control); // Determine the {receiver} length. Node* receiver_length = graph()->NewNode(simplified()->StringLength(), receiver); // Check that the {index} is within range. index = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), index, receiver_length, effect, control); // Return the character from the {receiver} as single character string. Node* masked_index = graph()->NewNode(simplified()->PoisonIndex(), index); Node* value = effect = graph()->NewNode(simplified()->StringCharCodeAt(), receiver, masked_index, effect, control); value = graph()->NewNode(simplified()->StringFromSingleCharCode(), value); ReplaceWithValue(node, value, effect, control); return Replace(value); } #ifdef V8_INTL_SUPPORT Reduction JSCallReducer::ReduceStringPrototypeToLowerCaseIntl(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); NodeProperties::ReplaceEffectInput(node, effect); RelaxEffectsAndControls(node); node->ReplaceInput(0, receiver); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->StringToLowerCaseIntl()); NodeProperties::SetType(node, Type::String()); return Changed(node); } Reduction JSCallReducer::ReduceStringPrototypeToUpperCaseIntl(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); NodeProperties::ReplaceEffectInput(node, effect); RelaxEffectsAndControls(node); node->ReplaceInput(0, receiver); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->StringToUpperCaseIntl()); NodeProperties::SetType(node, Type::String()); return Changed(node); } #endif // V8_INTL_SUPPORT // ES #sec-string.fromcharcode Reduction JSCallReducer::ReduceStringFromCharCode(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() == 3) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode( simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(simplified()->StringFromSingleCharCode(), input); ReplaceWithValue(node, value, effect); return Replace(value); } return NoChange(); } // ES #sec-string.fromcodepoint Reduction JSCallReducer::ReduceStringFromCodePoint(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() == 3) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode( simplified()->CheckBounds(p.feedback(), CheckBoundsFlag::kConvertStringAndMinusZero), input, jsgraph()->Constant(0x10FFFF + 1), effect, control); Node* value = graph()->NewNode(simplified()->StringFromSingleCodePoint(), input); ReplaceWithValue(node, value, effect); return Replace(value); } return NoChange(); } Reduction JSCallReducer::ReduceStringPrototypeIterator(Node* node) { // TODO(jgruber): We could reduce here when generating native context // independent code, if LowerJSCreateStringIterator were implemented in // generic lowering. if (broker()->is_native_context_independent()) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); Node* iterator = effect = graph()->NewNode(javascript()->CreateStringIterator(), receiver, jsgraph()->NoContextConstant(), effect); ReplaceWithValue(node, iterator, effect, control); return Replace(iterator); } Reduction JSCallReducer::ReduceStringIteratorPrototypeNext(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_STRING_ITERATOR_TYPE)) { return NoChange(); } Node* string = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSStringIteratorString()), receiver, effect, control); Node* index = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSStringIteratorIndex()), receiver, effect, control); Node* length = graph()->NewNode(simplified()->StringLength(), string); // branch0: if (index < length) Node* check0 = graph()->NewNode(simplified()->NumberLessThan(), index, length); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kNone), check0, control); Node* etrue0 = effect; Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* done_true; Node* vtrue0; { done_true = jsgraph()->FalseConstant(); vtrue0 = etrue0 = graph()->NewNode(simplified()->StringFromCodePointAt(), string, index, etrue0, if_true0); // Update iterator.[[NextIndex]] Node* char_length = graph()->NewNode(simplified()->StringLength(), vtrue0); index = graph()->NewNode(simplified()->NumberAdd(), index, char_length); etrue0 = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSStringIteratorIndex()), receiver, index, etrue0, if_true0); } Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* done_false; Node* vfalse0; { vfalse0 = jsgraph()->UndefinedConstant(); done_false = jsgraph()->TrueConstant(); } control = graph()->NewNode(common()->Merge(2), if_true0, if_false0); effect = graph()->NewNode(common()->EffectPhi(2), etrue0, effect, control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), vtrue0, vfalse0, control); Node* done = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), done_true, done_false, control); value = effect = graph()->NewNode(javascript()->CreateIterResultObject(), value, done, context, effect); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES #sec-string.prototype.concat Reduction JSCallReducer::ReduceStringPrototypeConcat(Node* node) { if (node->op()->ValueInputCount() < 2 || node->op()->ValueInputCount() > 3) { return NoChange(); } CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 1), effect, control); if (node->op()->ValueInputCount() < 3) { ReplaceWithValue(node, receiver, effect, control); return Replace(receiver); } Node* argument = effect = graph()->NewNode(simplified()->CheckString(p.feedback()), NodeProperties::GetValueInput(node, 2), effect, control); Node* receiver_length = graph()->NewNode(simplified()->StringLength(), receiver); Node* argument_length = graph()->NewNode(simplified()->StringLength(), argument); Node* length = graph()->NewNode(simplified()->NumberAdd(), receiver_length, argument_length); length = effect = graph()->NewNode( simplified()->CheckBounds(p.feedback()), length, jsgraph()->Constant(String::kMaxLength + 1), effect, control); Node* value = graph()->NewNode(simplified()->StringConcat(), length, receiver, argument); ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSCallReducer::ReducePromiseConstructor(Node* node) { // TODO(jgruber): We could reduce here when generating native context // independent code, if LowerJSCreatePromise were implemented in generic // lowering. if (broker()->is_native_context_independent()) return NoChange(); DisallowHeapAccessIf no_heap_access(should_disallow_heap_access()); PromiseBuiltinReducerAssembler a(jsgraph(), temp_zone(), node, broker()); // We only inline when we have the executor. if (a.ConstructArity() < 1) return NoChange(); // Only handle builtins Promises, not subclasses. if (a.TargetInput() != a.NewTargetInput()) return NoChange(); if (!dependencies()->DependOnPromiseHookProtector()) return NoChange(); TNode subgraph = a.ReducePromiseConstructor(native_context()); return ReplaceWithSubgraph(&a, subgraph); } bool JSCallReducer::DoPromiseChecks(MapInference* inference) { if (!inference->HaveMaps()) return false; MapHandles const& receiver_maps = inference->GetMaps(); // Check whether all {receiver_maps} are JSPromise maps and // have the initial Promise.prototype as their [[Prototype]]. for (Handle map : receiver_maps) { MapRef receiver_map(broker(), map); if (!receiver_map.IsJSPromiseMap()) return false; if (should_disallow_heap_access() && !receiver_map.serialized_prototype()) { TRACE_BROKER_MISSING(broker(), "prototype for map " << receiver_map); return false; } if (!receiver_map.prototype().equals( native_context().promise_prototype())) { return false; } } return true; } // ES section #sec-promise.prototype.catch Reduction JSCallReducer::ReducePromisePrototypeCatch(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } int arity = p.arity_without_implicit_args(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); MapInference inference(broker(), receiver, effect); if (!DoPromiseChecks(&inference)) return inference.NoChange(); if (!dependencies()->DependOnPromiseThenProtector()) return inference.NoChange(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); // Massage the {node} to call "then" instead by first removing all inputs // following the onRejected parameter, and then filling up the parameters // to two inputs from the left with undefined. Node* target = jsgraph()->Constant(native_context().promise_then()); NodeProperties::ReplaceValueInput(node, target, 0); NodeProperties::ReplaceEffectInput(node, effect); for (; arity > 1; --arity) node->RemoveInput(3); for (; arity < 2; ++arity) { node->InsertInput(graph()->zone(), 2, jsgraph()->UndefinedConstant()); } NodeProperties::ChangeOp( node, javascript()->Call( arity + kTargetAndReceiver, p.frequency(), p.feedback(), ConvertReceiverMode::kNotNullOrUndefined, p.speculation_mode(), CallFeedbackRelation::kUnrelated)); return Changed(node).FollowedBy(ReducePromisePrototypeThen(node)); } Node* JSCallReducer::CreateClosureFromBuiltinSharedFunctionInfo( SharedFunctionInfoRef shared, Node* context, Node* effect, Node* control) { DCHECK(shared.HasBuiltinId()); Callable const callable = Builtins::CallableFor( isolate(), static_cast(shared.builtin_id())); return graph()->NewNode( javascript()->CreateClosure( shared.object(), factory()->many_closures_cell(), callable.code()), context, effect, control); } // ES section #sec-promise.prototype.finally Reduction JSCallReducer::ReducePromisePrototypeFinally(Node* node) { DisallowHeapAccessIf no_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); int arity = p.arity_without_implicit_args(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* on_finally = arity >= 1 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } MapInference inference(broker(), receiver, effect); if (!DoPromiseChecks(&inference)) return inference.NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); if (!dependencies()->DependOnPromiseHookProtector()) return inference.NoChange(); if (!dependencies()->DependOnPromiseThenProtector()) return inference.NoChange(); if (!dependencies()->DependOnPromiseSpeciesProtector()) return inference.NoChange(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); // Check if {on_finally} is callable, and if so wrap it into appropriate // closures that perform the finalization. Node* check = graph()->NewNode(simplified()->ObjectIsCallable(), on_finally); Node* branch = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* catch_true; Node* then_true; { Node* context = jsgraph()->Constant(native_context()); Node* constructor = jsgraph()->Constant(native_context().promise_function()); // Allocate shared context for the closures below. context = etrue = graph()->NewNode(javascript()->CreateFunctionContext( native_context().scope_info().object(), PromiseBuiltins::kPromiseFinallyContextLength - Context::MIN_CONTEXT_SLOTS, FUNCTION_SCOPE), context, etrue, if_true); etrue = graph()->NewNode( simplified()->StoreField( AccessBuilder::ForContextSlot(PromiseBuiltins::kOnFinallySlot)), context, on_finally, etrue, if_true); etrue = graph()->NewNode( simplified()->StoreField( AccessBuilder::ForContextSlot(PromiseBuiltins::kConstructorSlot)), context, constructor, etrue, if_true); // Allocate the closure for the reject case. SharedFunctionInfoRef promise_catch_finally( broker(), factory()->promise_catch_finally_shared_fun()); catch_true = etrue = CreateClosureFromBuiltinSharedFunctionInfo( promise_catch_finally, context, etrue, if_true); // Allocate the closure for the fulfill case. SharedFunctionInfoRef promise_then_finally( broker(), factory()->promise_then_finally_shared_fun()); then_true = etrue = CreateClosureFromBuiltinSharedFunctionInfo( promise_then_finally, context, etrue, if_true); } Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* catch_false = on_finally; Node* then_false = on_finally; control = graph()->NewNode(common()->Merge(2), if_true, if_false); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); Node* catch_finally = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), catch_true, catch_false, control); Node* then_finally = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), then_true, then_false, control); // At this point we definitely know that {receiver} has one of the // {receiver_maps}, so insert a MapGuard as a hint for the lowering // of the call to "then" below. { ZoneHandleSet maps; for (Handle map : receiver_maps) maps.insert(map, graph()->zone()); effect = graph()->NewNode(simplified()->MapGuard(maps), receiver, effect, control); } // Massage the {node} to call "then" instead by first removing all inputs // following the onFinally parameter, and then replacing the only parameter // input with the {on_finally} value. Node* target = jsgraph()->Constant(native_context().promise_then()); NodeProperties::ReplaceValueInput(node, target, 0); NodeProperties::ReplaceEffectInput(node, effect); NodeProperties::ReplaceControlInput(node, control); for (; arity > 2; --arity) node->RemoveInput(2); for (; arity < 2; ++arity) node->InsertInput(graph()->zone(), 2, then_finally); node->ReplaceInput(2, then_finally); node->ReplaceInput(3, catch_finally); NodeProperties::ChangeOp( node, javascript()->Call( arity + kTargetAndReceiver, p.frequency(), p.feedback(), ConvertReceiverMode::kNotNullOrUndefined, p.speculation_mode(), CallFeedbackRelation::kUnrelated)); return Changed(node).FollowedBy(ReducePromisePrototypeThen(node)); } Reduction JSCallReducer::ReducePromisePrototypeThen(Node* node) { DisallowHeapAccessIf no_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* on_fulfilled = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* on_rejected = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); MapInference inference(broker(), receiver, effect); if (!DoPromiseChecks(&inference)) return inference.NoChange(); if (!dependencies()->DependOnPromiseHookProtector()) return inference.NoChange(); if (!dependencies()->DependOnPromiseSpeciesProtector()) return inference.NoChange(); inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); // Check that {on_fulfilled} is callable. on_fulfilled = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), graph()->NewNode(simplified()->ObjectIsCallable(), on_fulfilled), on_fulfilled, jsgraph()->UndefinedConstant()); // Check that {on_rejected} is callable. on_rejected = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), graph()->NewNode(simplified()->ObjectIsCallable(), on_rejected), on_rejected, jsgraph()->UndefinedConstant()); // Create the resulting JSPromise. Node* promise = effect = graph()->NewNode(javascript()->CreatePromise(), context, effect); // Chain {result} onto {receiver}. promise = effect = graph()->NewNode( javascript()->PerformPromiseThen(), receiver, on_fulfilled, on_rejected, promise, context, frame_state, effect, control); // At this point we know that {promise} is going to have the // initial Promise map, since even if {PerformPromiseThen} // above called into the host rejection tracker, the {promise} // doesn't escape to user JavaScript. So bake this information // into the graph such that subsequent passes can use the // information for further optimizations. MapRef promise_map = native_context().promise_function().initial_map(); effect = graph()->NewNode( simplified()->MapGuard(ZoneHandleSet(promise_map.object())), promise, effect, control); ReplaceWithValue(node, promise, effect, control); return Replace(promise); } // ES section #sec-promise.resolve Reduction JSCallReducer::ReducePromiseResolveTrampoline(Node* node) { DisallowHeapAccessIf no_heap_access(should_disallow_heap_access()); DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* value = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Only reduce when the receiver is guaranteed to be a JSReceiver. MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) { return NoChange(); } // Morph the {node} into a JSPromiseResolve operation. node->ReplaceInput(0, receiver); node->ReplaceInput(1, value); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->PromiseResolve()); return Changed(node); } // ES #sec-typedarray-constructors Reduction JSCallReducer::ReduceTypedArrayConstructor( Node* node, const SharedFunctionInfoRef& shared) { DCHECK_EQ(IrOpcode::kJSConstruct, node->opcode()); ConstructParameters const& p = ConstructParametersOf(node->op()); int arity = p.arity_without_implicit_args(); Node* target = NodeProperties::GetValueInput(node, 0); Node* arg1 = (arity >= 1) ? NodeProperties::GetValueInput(node, 1) : jsgraph()->UndefinedConstant(); Node* arg2 = (arity >= 2) ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); Node* arg3 = (arity >= 3) ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); Node* new_target = NodeProperties::GetValueInput(node, arity + 1); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // Insert a construct stub frame into the chain of frame states. This will // reconstruct the proper frame when deoptimizing within the constructor. frame_state = CreateArtificialFrameState( node, frame_state, arity, BailoutId::ConstructStubInvoke(), FrameStateType::kConstructStub, shared, context, common(), graph()); // This continuation just returns the newly created JSTypedArray. We // pass the_hole as the receiver, just like the builtin construct stub // does in this case. Node* const parameters[] = {jsgraph()->TheHoleConstant()}; int const num_parameters = static_cast(arraysize(parameters)); frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared, Builtins::kGenericLazyDeoptContinuation, target, context, parameters, num_parameters, frame_state, ContinuationFrameStateMode::LAZY); Node* result = graph()->NewNode(javascript()->CreateTypedArray(), target, new_target, arg1, arg2, arg3, context, frame_state, effect, control); return Replace(result); } // ES #sec-get-%typedarray%.prototype-@@tostringtag Reduction JSCallReducer::ReduceTypedArrayPrototypeToStringTag(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); NodeVector values(graph()->zone()); NodeVector effects(graph()->zone()); NodeVector controls(graph()->zone()); Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver); control = graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control); values.push_back(jsgraph()->UndefinedConstant()); effects.push_back(effect); controls.push_back(graph()->NewNode(common()->IfTrue(), control)); control = graph()->NewNode(common()->IfFalse(), control); Node* receiver_map = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), receiver, effect, control); Node* receiver_bit_field2 = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForMapBitField2()), receiver_map, effect, control); Node* receiver_elements_kind = graph()->NewNode( simplified()->NumberShiftRightLogical(), graph()->NewNode( simplified()->NumberBitwiseAnd(), receiver_bit_field2, jsgraph()->Constant(Map::Bits2::ElementsKindBits::kMask)), jsgraph()->Constant(Map::Bits2::ElementsKindBits::kShift)); // Offset the elements kind by FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND, // so that the branch cascade below is turned into a simple table // switch by the ControlFlowOptimizer later. receiver_elements_kind = graph()->NewNode( simplified()->NumberSubtract(), receiver_elements_kind, jsgraph()->Constant(FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ do { \ Node* check = graph()->NewNode( \ simplified()->NumberEqual(), receiver_elements_kind, \ jsgraph()->Constant(TYPE##_ELEMENTS - \ FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND)); \ control = graph()->NewNode(common()->Branch(), check, control); \ values.push_back(jsgraph()->Constant( \ broker()->GetTypedArrayStringTag(TYPE##_ELEMENTS))); \ effects.push_back(effect); \ controls.push_back(graph()->NewNode(common()->IfTrue(), control)); \ control = graph()->NewNode(common()->IfFalse(), control); \ } while (false); TYPED_ARRAYS(TYPED_ARRAY_CASE) #undef TYPED_ARRAY_CASE values.push_back(jsgraph()->UndefinedConstant()); effects.push_back(effect); controls.push_back(control); int const count = static_cast(controls.size()); control = graph()->NewNode(common()->Merge(count), count, &controls.front()); effects.push_back(control); effect = graph()->NewNode(common()->EffectPhi(count), count + 1, &effects.front()); values.push_back(control); Node* value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, count), count + 1, &values.front()); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES #sec-number.isfinite Reduction JSCallReducer::ReduceNumberIsFinite(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsFiniteNumber(), input); ReplaceWithValue(node, value); return Replace(value); } // ES #sec-number.isfinite Reduction JSCallReducer::ReduceNumberIsInteger(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsInteger(), input); ReplaceWithValue(node, value); return Replace(value); } // ES #sec-number.issafeinteger Reduction JSCallReducer::ReduceNumberIsSafeInteger(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsSafeInteger(), input); ReplaceWithValue(node, value); return Replace(value); } // ES #sec-number.isnan Reduction JSCallReducer::ReduceNumberIsNaN(Node* node) { if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* input = NodeProperties::GetValueInput(node, 2); Node* value = graph()->NewNode(simplified()->ObjectIsNaN(), input); ReplaceWithValue(node, value); return Replace(value); } Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) { // We only optimize if we have target, receiver and key parameters. if (node->op()->ValueInputCount() != 3) return NoChange(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* key = NodeProperties::GetValueInput(node, 2); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_MAP_TYPE)) { return NoChange(); } Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, effect, control); Node* entry = effect = graph()->NewNode( simplified()->FindOrderedHashMapEntry(), table, key, effect, control); Node* check = graph()->NewNode(simplified()->NumberEqual(), entry, jsgraph()->MinusOneConstant()); Node* branch = graph()->NewNode(common()->Branch(), check, control); // Key not found. Node* if_true = graph()->NewNode(common()->IfTrue(), branch); Node* etrue = effect; Node* vtrue = jsgraph()->UndefinedConstant(); // Key found. Node* if_false = graph()->NewNode(common()->IfFalse(), branch); Node* efalse = effect; Node* vfalse = efalse = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForOrderedHashMapEntryValue()), table, entry, efalse, if_false); control = graph()->NewNode(common()->Merge(2), if_true, if_false); Node* value = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), vtrue, vfalse, control); effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) { // We only optimize if we have target, receiver and key parameters. if (node->op()->ValueInputCount() != 3) return NoChange(); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* key = NodeProperties::GetValueInput(node, 2); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_MAP_TYPE)) { return NoChange(); } Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, effect, control); Node* index = effect = graph()->NewNode( simplified()->FindOrderedHashMapEntry(), table, key, effect, control); Node* value = graph()->NewNode(simplified()->NumberEqual(), index, jsgraph()->MinusOneConstant()); value = graph()->NewNode(simplified()->BooleanNot(), value); ReplaceWithValue(node, value, effect, control); return Replace(value); } namespace { InstanceType InstanceTypeForCollectionKind(CollectionKind kind) { switch (kind) { case CollectionKind::kMap: return JS_MAP_TYPE; case CollectionKind::kSet: return JS_SET_TYPE; } UNREACHABLE(); } } // namespace Reduction JSCallReducer::ReduceCollectionIteration( Node* node, CollectionKind collection_kind, IterationKind iteration_kind) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); InstanceType type = InstanceTypeForCollectionKind(collection_kind); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(type)) { return NoChange(); } Node* js_create_iterator = effect = graph()->NewNode( javascript()->CreateCollectionIterator(collection_kind, iteration_kind), receiver, context, effect, control); ReplaceWithValue(node, js_create_iterator, effect); return Replace(js_create_iterator); } Reduction JSCallReducer::ReduceCollectionPrototypeSize( Node* node, CollectionKind collection_kind) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); InstanceType type = InstanceTypeForCollectionKind(collection_kind); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(type)) { return NoChange(); } Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionTable()), receiver, effect, control); Node* value = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashMapOrSetNumberOfElements()), table, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } Reduction JSCallReducer::ReduceCollectionIteratorPrototypeNext( Node* node, int entry_size, Handle empty_collection, InstanceType collection_iterator_instance_type_first, InstanceType collection_iterator_instance_type_last) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* receiver = NodeProperties::GetValueInput(node, 1); Node* context = NodeProperties::GetContextInput(node); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); // A word of warning to begin with: This whole method might look a bit // strange at times, but that's mostly because it was carefully handcrafted // to allow for full escape analysis and scalar replacement of both the // collection iterator object and the iterator results, including the // key-value arrays in case of Set/Map entry iteration. // // TODO(turbofan): Currently the escape analysis (and the store-load // forwarding) is unable to eliminate the allocations for the key-value // arrays in case of Set/Map entry iteration, and we should investigate // how to update the escape analysis / arrange the graph in a way that // this becomes possible. InstanceType receiver_instance_type; { MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps()) return NoChange(); MapHandles const& receiver_maps = inference.GetMaps(); receiver_instance_type = MapRef(broker(), receiver_maps[0]).instance_type(); for (size_t i = 1; i < receiver_maps.size(); ++i) { if (MapRef(broker(), receiver_maps[i]).instance_type() != receiver_instance_type) { return inference.NoChange(); } } if (receiver_instance_type < collection_iterator_instance_type_first || receiver_instance_type > collection_iterator_instance_type_last) { return inference.NoChange(); } inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); } // Transition the JSCollectionIterator {receiver} if necessary // (i.e. there were certain mutations while we're iterating). { Node* done_loop; Node* done_eloop; Node* loop = control = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = effect = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); // Check if reached the final table of the {receiver}. Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorTable()), receiver, effect, control); Node* next_table = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForOrderedHashMapOrSetNextTable()), table, effect, control); Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), next_table); control = graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); // Abort the {loop} when we reach the final table. done_loop = graph()->NewNode(common()->IfTrue(), control); done_eloop = effect; // Migrate to the {next_table} otherwise. control = graph()->NewNode(common()->IfFalse(), control); // Self-heal the {receiver}s index. Node* index = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorIndex()), receiver, effect, control); Callable const callable = Builtins::CallableFor(isolate(), Builtins::kOrderedHashTableHealIndex); auto call_descriptor = Linkage::GetStubCallDescriptor( graph()->zone(), callable.descriptor(), callable.descriptor().GetStackParameterCount(), CallDescriptor::kNoFlags, Operator::kEliminatable); index = effect = graph()->NewNode(common()->Call(call_descriptor), jsgraph()->HeapConstant(callable.code()), table, index, jsgraph()->NoContextConstant(), effect); index = effect = graph()->NewNode( common()->TypeGuard(TypeCache::Get()->kFixedArrayLengthType), index, effect, control); // Update the {index} and {table} on the {receiver}. effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSCollectionIteratorIndex()), receiver, index, effect, control); effect = graph()->NewNode( simplified()->StoreField(AccessBuilder::ForJSCollectionIteratorTable()), receiver, next_table, effect, control); // Tie the knot. loop->ReplaceInput(1, control); eloop->ReplaceInput(1, effect); control = done_loop; effect = done_eloop; } // Get current index and table from the JSCollectionIterator {receiver}. Node* index = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorIndex()), receiver, effect, control); Node* table = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSCollectionIteratorTable()), receiver, effect, control); // Create the {JSIteratorResult} first to ensure that we always have // a dominating Allocate node for the allocation folding phase. Node* iterator_result = effect = graph()->NewNode( javascript()->CreateIterResultObject(), jsgraph()->UndefinedConstant(), jsgraph()->TrueConstant(), context, effect); // Look for the next non-holey key, starting from {index} in the {table}. Node* controls[2]; Node* effects[3]; { // Compute the currently used capacity. Node* number_of_buckets = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashMapOrSetNumberOfBuckets()), table, effect, control); Node* number_of_elements = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashMapOrSetNumberOfElements()), table, effect, control); Node* number_of_deleted_elements = effect = graph()->NewNode( simplified()->LoadField( AccessBuilder::ForOrderedHashMapOrSetNumberOfDeletedElements()), table, effect, control); Node* used_capacity = graph()->NewNode(simplified()->NumberAdd(), number_of_elements, number_of_deleted_elements); // Skip holes and update the {index}. Node* loop = graph()->NewNode(common()->Loop(2), control, control); Node* eloop = graph()->NewNode(common()->EffectPhi(2), effect, effect, loop); Node* terminate = graph()->NewNode(common()->Terminate(), eloop, loop); NodeProperties::MergeControlToEnd(graph(), common(), terminate); Node* iloop = graph()->NewNode( common()->Phi(MachineRepresentation::kTagged, 2), index, index, loop); Node* index = effect = graph()->NewNode( common()->TypeGuard(TypeCache::Get()->kFixedArrayLengthType), iloop, eloop, control); { Node* check0 = graph()->NewNode(simplified()->NumberLessThan(), index, used_capacity); Node* branch0 = graph()->NewNode(common()->Branch(BranchHint::kTrue), check0, loop); Node* if_false0 = graph()->NewNode(common()->IfFalse(), branch0); Node* efalse0 = effect; { // Mark the {receiver} as exhausted. efalse0 = graph()->NewNode( simplified()->StoreField( AccessBuilder::ForJSCollectionIteratorTable()), receiver, jsgraph()->HeapConstant(empty_collection), efalse0, if_false0); controls[0] = if_false0; effects[0] = efalse0; } Node* if_true0 = graph()->NewNode(common()->IfTrue(), branch0); Node* etrue0 = effect; { // Load the key of the entry. STATIC_ASSERT(OrderedHashMap::HashTableStartIndex() == OrderedHashSet::HashTableStartIndex()); Node* entry_start_position = graph()->NewNode( simplified()->NumberAdd(), graph()->NewNode( simplified()->NumberAdd(), graph()->NewNode(simplified()->NumberMultiply(), index, jsgraph()->Constant(entry_size)), number_of_buckets), jsgraph()->Constant(OrderedHashMap::HashTableStartIndex())); Node* entry_key = etrue0 = graph()->NewNode( simplified()->LoadElement(AccessBuilder::ForFixedArrayElement()), table, entry_start_position, etrue0, if_true0); // Advance the index. index = graph()->NewNode(simplified()->NumberAdd(), index, jsgraph()->OneConstant()); Node* check1 = graph()->NewNode(simplified()->ReferenceEqual(), entry_key, jsgraph()->TheHoleConstant()); Node* branch1 = graph()->NewNode(common()->Branch(BranchHint::kFalse), check1, if_true0); { // Abort loop with resulting value. Node* control = graph()->NewNode(common()->IfFalse(), branch1); Node* effect = etrue0; Node* value = effect = graph()->NewNode(common()->TypeGuard(Type::NonInternal()), entry_key, effect, control); Node* done = jsgraph()->FalseConstant(); // Advance the index on the {receiver}. effect = graph()->NewNode( simplified()->StoreField( AccessBuilder::ForJSCollectionIteratorIndex()), receiver, index, effect, control); // The actual {value} depends on the {receiver} iteration type. switch (receiver_instance_type) { case JS_MAP_KEY_ITERATOR_TYPE: case JS_SET_VALUE_ITERATOR_TYPE: break; case JS_SET_KEY_VALUE_ITERATOR_TYPE: value = effect = graph()->NewNode(javascript()->CreateKeyValueArray(), value, value, context, effect); break; case JS_MAP_VALUE_ITERATOR_TYPE: value = effect = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement()), table, graph()->NewNode( simplified()->NumberAdd(), entry_start_position, jsgraph()->Constant(OrderedHashMap::kValueOffset)), effect, control); break; case JS_MAP_KEY_VALUE_ITERATOR_TYPE: value = effect = graph()->NewNode( simplified()->LoadElement( AccessBuilder::ForFixedArrayElement()), table, graph()->NewNode( simplified()->NumberAdd(), entry_start_position, jsgraph()->Constant(OrderedHashMap::kValueOffset)), effect, control); value = effect = graph()->NewNode(javascript()->CreateKeyValueArray(), entry_key, value, context, effect); break; default: UNREACHABLE(); break; } // Store final {value} and {done} into the {iterator_result}. effect = graph()->NewNode(simplified()->StoreField( AccessBuilder::ForJSIteratorResultValue()), iterator_result, value, effect, control); effect = graph()->NewNode(simplified()->StoreField( AccessBuilder::ForJSIteratorResultDone()), iterator_result, done, effect, control); controls[1] = control; effects[1] = effect; } // Continue with next loop index. loop->ReplaceInput(1, graph()->NewNode(common()->IfTrue(), branch1)); eloop->ReplaceInput(1, etrue0); iloop->ReplaceInput(1, index); } } control = effects[2] = graph()->NewNode(common()->Merge(2), 2, controls); effect = graph()->NewNode(common()->EffectPhi(2), 3, effects); } // Yield the final {iterator_result}. ReplaceWithValue(node, iterator_result, effect, control); return Replace(iterator_result); } Reduction JSCallReducer::ReduceArrayBufferIsView(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); Node* value = node->op()->ValueInputCount() >= 3 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->UndefinedConstant(); RelaxEffectsAndControls(node); node->ReplaceInput(0, value); node->TrimInputCount(1); NodeProperties::ChangeOp(node, simplified()->ObjectIsArrayBufferView()); return Changed(node); } Reduction JSCallReducer::ReduceArrayBufferViewAccessor( Node* node, InstanceType instance_type, FieldAccess const& access) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(instance_type)) { return NoChange(); } // Load the {receiver}s field. Node* value = effect = graph()->NewNode(simplified()->LoadField(access), receiver, effect, control); // See if we can skip the detaching check. if (!dependencies()->DependOnArrayBufferDetachingProtector()) { // Check whether {receiver}s JSArrayBuffer was detached. Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), receiver, effect, control); Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasDetachedBit::kMask)), jsgraph()->ZeroConstant()); // TODO(turbofan): Ideally we would bail out here if the {receiver}s // JSArrayBuffer was detached, but there's no way to guard against // deoptimization loops right now, since the JSCall {node} is usually // created from a LOAD_IC inlining, and so there's no CALL_IC slot // from which we could use the speculation bit. value = graph()->NewNode( common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue), check, value, jsgraph()->ZeroConstant()); } ReplaceWithValue(node, value, effect, control); return Replace(value); } namespace { uint32_t ExternalArrayElementSize(const ExternalArrayType element_type) { switch (element_type) { #define TYPED_ARRAY_CASE(Type, type, TYPE, ctype) \ case kExternal##Type##Array: \ DCHECK_LE(sizeof(ctype), 8); \ return sizeof(ctype); TYPED_ARRAYS(TYPED_ARRAY_CASE) default: UNREACHABLE(); #undef TYPED_ARRAY_CASE } } } // namespace Reduction JSCallReducer::ReduceDataViewAccess(Node* node, DataViewAccess access, ExternalArrayType element_type) { DCHECK_EQ(node->opcode(), IrOpcode::kJSCall); size_t const element_size = ExternalArrayElementSize(element_type); CallParameters const& p = CallParametersOf(node->op()); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* offset = node->op()->ValueInputCount() > 2 ? NodeProperties::GetValueInput(node, 2) : jsgraph()->ZeroConstant(); Node* value = nullptr; if (access == DataViewAccess::kSet) { value = node->op()->ValueInputCount() > 3 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); } const int endian_index = (access == DataViewAccess::kGet ? 3 : 4); Node* is_little_endian = (node->op()->ValueInputCount() > endian_index ? NodeProperties::GetValueInput(node, endian_index) : jsgraph()->FalseConstant()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } // Only do stuff if the {receiver} is really a DataView. MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_DATA_VIEW_TYPE)) { return NoChange(); } // Check that the {offset} is within range for the {receiver}. HeapObjectMatcher m(receiver); if (m.HasValue()) { // We only deal with DataViews here whose [[ByteLength]] is at least // {element_size}, as for all other DataViews it'll be out-of-bounds. JSDataViewRef dataview = m.Ref(broker()).AsJSDataView(); if (dataview.byte_length() < element_size) return NoChange(); // Check that the {offset} is within range of the {byte_length}. Node* byte_length = jsgraph()->Constant(dataview.byte_length() - (element_size - 1)); offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), offset, byte_length, effect, control); } else { // We only deal with DataViews here that have Smi [[ByteLength]]s. Node* byte_length = effect = graph()->NewNode(simplified()->LoadField( AccessBuilder::ForJSArrayBufferViewByteLength()), receiver, effect, control); if (element_size > 1) { // For non-byte accesses we also need to check that the {offset} // plus the {element_size}-1 fits within the given {byte_length}. // So to keep this as a single check on the {offset}, we subtract // the {element_size}-1 from the {byte_length} here (clamped to // positive safe integer range), and perform a check against that // with the {offset} below. byte_length = graph()->NewNode( simplified()->NumberMax(), jsgraph()->ZeroConstant(), graph()->NewNode(simplified()->NumberSubtract(), byte_length, jsgraph()->Constant(element_size - 1))); } // Check that the {offset} is within range of the {byte_length}. offset = effect = graph()->NewNode(simplified()->CheckBounds(p.feedback()), offset, byte_length, effect, control); } // Coerce {is_little_endian} to boolean. is_little_endian = graph()->NewNode(simplified()->ToBoolean(), is_little_endian); // Coerce {value} to Number. if (access == DataViewAccess::kSet) { value = effect = graph()->NewNode( simplified()->SpeculativeToNumber(NumberOperationHint::kNumberOrOddball, p.feedback()), value, effect, control); } // We need to retain either the {receiver} itself or it's backing // JSArrayBuffer to make sure that the GC doesn't collect the raw // memory. We default to {receiver} here, and only use the buffer // if we anyways have to load it (to reduce register pressure). Node* buffer_or_receiver = receiver; if (!dependencies()->DependOnArrayBufferDetachingProtector()) { // Get the underlying buffer and check that it has not been detached. Node* buffer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()), receiver, effect, control); // Bail out if the {buffer} was detached. Node* buffer_bit_field = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()), buffer, effect, control); Node* check = graph()->NewNode( simplified()->NumberEqual(), graph()->NewNode( simplified()->NumberBitwiseAnd(), buffer_bit_field, jsgraph()->Constant(JSArrayBuffer::WasDetachedBit::kMask)), jsgraph()->ZeroConstant()); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached, p.feedback()), check, effect, control); // We can reduce register pressure by holding on to the {buffer} // now to retain the backing store memory. buffer_or_receiver = buffer; } // Load the {receiver}s data pointer. Node* data_pointer = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSDataViewDataPointer()), receiver, effect, control); switch (access) { case DataViewAccess::kGet: // Perform the load. value = effect = graph()->NewNode( simplified()->LoadDataViewElement(element_type), buffer_or_receiver, data_pointer, offset, is_little_endian, effect, control); break; case DataViewAccess::kSet: // Perform the store. effect = graph()->NewNode( simplified()->StoreDataViewElement(element_type), buffer_or_receiver, data_pointer, offset, value, is_little_endian, effect, control); value = jsgraph()->UndefinedConstant(); break; } ReplaceWithValue(node, value, effect, control); return Changed(value); } // ES6 section 18.2.2 isFinite ( number ) Reduction JSCallReducer::ReduceGlobalIsFinite(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->FalseConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(simplified()->NumberIsFinite(), input); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 18.2.3 isNaN ( number ) Reduction JSCallReducer::ReduceGlobalIsNaN(Node* node) { CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->TrueConstant(); ReplaceWithValue(node, value); return Replace(value); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* input = NodeProperties::GetValueInput(node, 2); input = effect = graph()->NewNode(simplified()->SpeculativeToNumber( NumberOperationHint::kNumberOrOddball, p.feedback()), input, effect, control); Node* value = graph()->NewNode(simplified()->NumberIsNaN(), input); ReplaceWithValue(node, value, effect); return Replace(value); } // ES6 section 20.3.4.10 Date.prototype.getTime ( ) Reduction JSCallReducer::ReduceDatePrototypeGetTime(Node* node) { Node* receiver = NodeProperties::GetValueInput(node, 1); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); MapInference inference(broker(), receiver, effect); if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_DATE_TYPE)) { return NoChange(); } Node* value = effect = graph()->NewNode(simplified()->LoadField(AccessBuilder::ForJSDateValue()), receiver, effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 20.3.3.1 Date.now ( ) Reduction JSCallReducer::ReduceDateNow(Node* node) { Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* value = effect = graph()->NewNode(simplified()->DateNow(), effect, control); ReplaceWithValue(node, value, effect, control); return Replace(value); } // ES6 section 20.1.2.13 Number.parseInt ( string, radix ) Reduction JSCallReducer::ReduceNumberParseInt(Node* node) { // We certainly know that undefined is not an array. if (node->op()->ValueInputCount() < 3) { Node* value = jsgraph()->NaNConstant(); ReplaceWithValue(node, value); return Replace(value); } int arg_count = node->op()->ValueInputCount(); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* object = NodeProperties::GetValueInput(node, 2); Node* radix = arg_count >= 4 ? NodeProperties::GetValueInput(node, 3) : jsgraph()->UndefinedConstant(); node->ReplaceInput(0, object); node->ReplaceInput(1, radix); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->ParseInt()); return Changed(node); } Reduction JSCallReducer::ReduceRegExpPrototypeTest(Node* node) { DisallowHeapAccessIf disallow_heap_access(should_disallow_heap_access()); if (FLAG_force_slow_path) return NoChange(); if (node->op()->ValueInputCount() < 3) return NoChange(); CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* regexp = NodeProperties::GetValueInput(node, 1); // Only the initial JSRegExp map is valid here, since the following lastIndex // check as well as the lowered builtin call rely on a known location of the // lastIndex field. Handle regexp_initial_map = native_context().regexp_function().initial_map().object(); MapInference inference(broker(), regexp, effect); if (!inference.Is(regexp_initial_map)) return inference.NoChange(); MapHandles const& regexp_maps = inference.GetMaps(); ZoneVector access_infos(graph()->zone()); AccessInfoFactory access_info_factory(broker(), dependencies(), graph()->zone()); if (should_disallow_heap_access()) { // Obtain precomputed access infos from the broker. for (auto map : regexp_maps) { MapRef map_ref(broker(), map); PropertyAccessInfo access_info = broker()->GetPropertyAccessInfo( map_ref, NameRef(broker(), isolate()->factory()->exec_string()), AccessMode::kLoad); access_infos.push_back(access_info); } } else { // Compute property access info for "exec" on {resolution}. access_info_factory.ComputePropertyAccessInfos( MapHandles(regexp_maps.begin(), regexp_maps.end()), factory()->exec_string(), AccessMode::kLoad, &access_infos); } PropertyAccessInfo ai_exec = access_info_factory.FinalizePropertyAccessInfosAsOne(access_infos, AccessMode::kLoad); if (ai_exec.IsInvalid()) return inference.NoChange(); // If "exec" has been modified on {regexp}, we can't do anything. if (ai_exec.IsDataConstant()) { Handle holder; // Do not reduce if the exec method is not on the prototype chain. if (!ai_exec.holder().ToHandle(&holder)) return inference.NoChange(); JSObjectRef holder_ref(broker(), holder); // Bail out if the exec method is not the original one. base::Optional constant = holder_ref.GetOwnDataProperty( ai_exec.field_representation(), ai_exec.field_index()); if (!constant.has_value() || !constant->equals(native_context().regexp_exec_function())) { return inference.NoChange(); } // Add proper dependencies on the {regexp}s [[Prototype]]s. dependencies()->DependOnStablePrototypeChains( ai_exec.receiver_maps(), kStartAtPrototype, JSObjectRef(broker(), holder)); } else { return inference.NoChange(); } inference.RelyOnMapsPreferStability(dependencies(), jsgraph(), &effect, control, p.feedback()); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); Node* search = NodeProperties::GetValueInput(node, 2); Node* search_string = effect = graph()->NewNode( simplified()->CheckString(p.feedback()), search, effect, control); Node* lastIndex = effect = graph()->NewNode( simplified()->LoadField(AccessBuilder::ForJSRegExpLastIndex()), regexp, effect, control); Node* lastIndexSmi = effect = graph()->NewNode( simplified()->CheckSmi(p.feedback()), lastIndex, effect, control); Node* is_positive = graph()->NewNode(simplified()->NumberLessThanOrEqual(), jsgraph()->ZeroConstant(), lastIndexSmi); effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kNotASmi, p.feedback()), is_positive, effect, control); node->ReplaceInput(0, regexp); node->ReplaceInput(1, search_string); node->ReplaceInput(2, context); node->ReplaceInput(3, frame_state); node->ReplaceInput(4, effect); node->ReplaceInput(5, control); node->TrimInputCount(6); NodeProperties::ChangeOp(node, javascript()->RegExpTest()); return Changed(node); } // ES section #sec-number-constructor Reduction JSCallReducer::ReduceNumberConstructor(Node* node) { DCHECK_EQ(IrOpcode::kJSCall, node->opcode()); CallParameters const& p = CallParametersOf(node->op()); Node* target = NodeProperties::GetValueInput(node, 0); Node* receiver = NodeProperties::GetValueInput(node, 1); Node* value = p.arity() < 3 ? jsgraph()->ZeroConstant() : NodeProperties::GetValueInput(node, 2); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); // Create the artificial frame state in the middle of the Number constructor. SharedFunctionInfoRef shared_info = native_context().number_function().shared(); Node* stack_parameters[] = {receiver}; int stack_parameter_count = arraysize(stack_parameters); Node* continuation_frame_state = CreateJavaScriptBuiltinContinuationFrameState( jsgraph(), shared_info, Builtins::kGenericLazyDeoptContinuation, target, context, stack_parameters, stack_parameter_count, frame_state, ContinuationFrameStateMode::LAZY); // Convert the {value} to a Number. NodeProperties::ReplaceValueInputs(node, value); NodeProperties::ChangeOp(node, javascript()->ToNumberConvertBigInt()); NodeProperties::ReplaceFrameStateInput(node, continuation_frame_state); return Changed(node); } Reduction JSCallReducer::ReduceBigIntAsUintN(Node* node) { if (!jsgraph()->machine()->Is64()) { return NoChange(); } CallParameters const& p = CallParametersOf(node->op()); if (p.speculation_mode() == SpeculationMode::kDisallowSpeculation) { return NoChange(); } if (node->op()->ValueInputCount() < 4) { return NoChange(); } Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); Node* bits = NodeProperties::GetValueInput(node, 2); Node* value = NodeProperties::GetValueInput(node, 3); NumberMatcher matcher(bits); if (matcher.IsInteger() && matcher.IsInRange(0, 64)) { const int bits_value = static_cast(matcher.Value()); value = effect = graph()->NewNode(simplified()->CheckBigInt(p.feedback()), value, effect, control); value = graph()->NewNode(simplified()->BigIntAsUintN(bits_value), value); ReplaceWithValue(node, value, effect); return Replace(value); } return NoChange(); } Graph* JSCallReducer::graph() const { return jsgraph()->graph(); } Isolate* JSCallReducer::isolate() const { return jsgraph()->isolate(); } Factory* JSCallReducer::factory() const { return isolate()->factory(); } NativeContextRef JSCallReducer::native_context() const { return broker()->target_native_context(); } CommonOperatorBuilder* JSCallReducer::common() const { return jsgraph()->common(); } JSOperatorBuilder* JSCallReducer::javascript() const { return jsgraph()->javascript(); } SimplifiedOperatorBuilder* JSCallReducer::simplified() const { return jsgraph()->simplified(); } } // namespace compiler } // namespace internal } // namespace v8