| // Copyright 2014 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-generic-lowering.h" |
| |
| #include "src/ast/ast.h" |
| #include "src/builtins/builtins-constructor.h" |
| #include "src/codegen/code-factory.h" |
| #include "src/compiler/access-builder.h" |
| #include "src/compiler/common-operator.h" |
| #include "src/compiler/js-graph.h" |
| #include "src/compiler/js-heap-broker.h" |
| #include "src/compiler/machine-operator.h" |
| #include "src/compiler/node-matchers.h" |
| #include "src/compiler/node-properties.h" |
| #include "src/compiler/operator-properties.h" |
| #include "src/compiler/processed-feedback.h" |
| #include "src/compiler/simplified-operator.h" |
| #include "src/objects/feedback-cell.h" |
| #include "src/objects/feedback-vector.h" |
| #include "src/objects/scope-info.h" |
| #include "src/objects/template-objects-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| namespace { |
| |
| CallDescriptor::Flags FrameStateFlagForCall(Node* node) { |
| return OperatorProperties::HasFrameStateInput(node->op()) |
| ? CallDescriptor::kNeedsFrameState |
| : CallDescriptor::kNoFlags; |
| } |
| |
| } // namespace |
| |
| JSGenericLowering::JSGenericLowering(JSGraph* jsgraph, Editor* editor, |
| JSHeapBroker* broker) |
| : AdvancedReducer(editor), jsgraph_(jsgraph), broker_(broker) {} |
| |
| JSGenericLowering::~JSGenericLowering() = default; |
| |
| |
| Reduction JSGenericLowering::Reduce(Node* node) { |
| switch (node->opcode()) { |
| #define DECLARE_CASE(x, ...) \ |
| case IrOpcode::k##x: \ |
| Lower##x(node); \ |
| break; |
| JS_OP_LIST(DECLARE_CASE) |
| #undef DECLARE_CASE |
| default: |
| // Nothing to see. |
| return NoChange(); |
| } |
| return Changed(node); |
| } |
| |
| #define REPLACE_STUB_CALL(Name) \ |
| void JSGenericLowering::LowerJS##Name(Node* node) { \ |
| ReplaceWithBuiltinCall(node, Builtins::k##Name); \ |
| } |
| REPLACE_STUB_CALL(ToLength) |
| REPLACE_STUB_CALL(ToNumber) |
| REPLACE_STUB_CALL(ToNumberConvertBigInt) |
| REPLACE_STUB_CALL(ToNumeric) |
| REPLACE_STUB_CALL(ToName) |
| REPLACE_STUB_CALL(ToObject) |
| REPLACE_STUB_CALL(ToString) |
| REPLACE_STUB_CALL(ForInEnumerate) |
| REPLACE_STUB_CALL(AsyncFunctionEnter) |
| REPLACE_STUB_CALL(AsyncFunctionReject) |
| REPLACE_STUB_CALL(AsyncFunctionResolve) |
| REPLACE_STUB_CALL(FulfillPromise) |
| REPLACE_STUB_CALL(PerformPromiseThen) |
| REPLACE_STUB_CALL(PromiseResolve) |
| REPLACE_STUB_CALL(RejectPromise) |
| REPLACE_STUB_CALL(ResolvePromise) |
| #undef REPLACE_STUB_CALL |
| |
| void JSGenericLowering::ReplaceWithBuiltinCall(Node* node, |
| Builtins::Name builtin) { |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| Callable callable = Builtins::CallableFor(isolate(), builtin); |
| ReplaceWithBuiltinCall(node, callable, flags); |
| } |
| |
| void JSGenericLowering::ReplaceWithBuiltinCall(Node* node, Callable callable, |
| CallDescriptor::Flags flags) { |
| ReplaceWithBuiltinCall(node, callable, flags, node->op()->properties()); |
| } |
| |
| void JSGenericLowering::ReplaceWithBuiltinCall( |
| Node* node, Callable callable, CallDescriptor::Flags flags, |
| Operator::Properties properties) { |
| const CallInterfaceDescriptor& descriptor = callable.descriptor(); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), descriptor, descriptor.GetStackParameterCount(), flags, |
| properties); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| node->InsertInput(zone(), 0, stub_code); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| |
| void JSGenericLowering::ReplaceWithRuntimeCall(Node* node, |
| Runtime::FunctionId f, |
| int nargs_override) { |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| Operator::Properties properties = node->op()->properties(); |
| const Runtime::Function* fun = Runtime::FunctionForId(f); |
| int nargs = (nargs_override < 0) ? fun->nargs : nargs_override; |
| auto call_descriptor = |
| Linkage::GetRuntimeCallDescriptor(zone(), f, nargs, properties, flags); |
| Node* ref = jsgraph()->ExternalConstant(ExternalReference::Create(f)); |
| Node* arity = jsgraph()->Int32Constant(nargs); |
| node->InsertInput(zone(), 0, jsgraph()->CEntryStubConstant(fun->result_size)); |
| node->InsertInput(zone(), nargs + 1, ref); |
| node->InsertInput(zone(), nargs + 2, arity); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| |
| void JSGenericLowering::ReplaceUnaryOpWithBuiltinCall( |
| Node* node, Builtins::Name builtin_without_feedback, |
| Builtins::Name builtin_with_feedback) { |
| DCHECK(JSOperator::IsUnaryWithFeedback(node->opcode())); |
| const FeedbackParameter& p = FeedbackParameterOf(node->op()); |
| if (CollectFeedbackInGenericLowering() && p.feedback().IsValid()) { |
| Callable callable = Builtins::CallableFor(isolate(), builtin_with_feedback); |
| Node* slot = jsgraph()->UintPtrConstant(p.feedback().slot.ToInt()); |
| const CallInterfaceDescriptor& descriptor = callable.descriptor(); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), descriptor, descriptor.GetStackParameterCount(), flags, |
| node->op()->properties()); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| STATIC_ASSERT(JSUnaryOpNode::ValueIndex() == 0); |
| STATIC_ASSERT(JSUnaryOpNode::FeedbackVectorIndex() == 1); |
| DCHECK_EQ(node->op()->ValueInputCount(), 2); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 2, slot); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| node->RemoveInput(JSUnaryOpNode::FeedbackVectorIndex()); |
| ReplaceWithBuiltinCall(node, builtin_without_feedback); |
| } |
| } |
| |
| #define DEF_UNARY_LOWERING(Name) \ |
| void JSGenericLowering::LowerJS##Name(Node* node) { \ |
| ReplaceUnaryOpWithBuiltinCall(node, Builtins::k##Name, \ |
| Builtins::k##Name##_WithFeedback); \ |
| } |
| DEF_UNARY_LOWERING(BitwiseNot) |
| DEF_UNARY_LOWERING(Decrement) |
| DEF_UNARY_LOWERING(Increment) |
| DEF_UNARY_LOWERING(Negate) |
| #undef DEF_UNARY_LOWERING |
| |
| void JSGenericLowering::ReplaceBinaryOpWithBuiltinCall( |
| Node* node, Builtins::Name builtin_without_feedback, |
| Builtins::Name builtin_with_feedback) { |
| DCHECK(JSOperator::IsBinaryWithFeedback(node->opcode())); |
| Builtins::Name builtin_id; |
| const FeedbackParameter& p = FeedbackParameterOf(node->op()); |
| if (CollectFeedbackInGenericLowering() && p.feedback().IsValid()) { |
| Node* slot = jsgraph()->UintPtrConstant(p.feedback().slot.ToInt()); |
| STATIC_ASSERT(JSBinaryOpNode::LeftIndex() == 0); |
| STATIC_ASSERT(JSBinaryOpNode::RightIndex() == 1); |
| STATIC_ASSERT(JSBinaryOpNode::FeedbackVectorIndex() == 2); |
| DCHECK_EQ(node->op()->ValueInputCount(), 3); |
| node->InsertInput(zone(), 2, slot); |
| builtin_id = builtin_with_feedback; |
| } else { |
| node->RemoveInput(JSBinaryOpNode::FeedbackVectorIndex()); |
| builtin_id = builtin_without_feedback; |
| } |
| |
| ReplaceWithBuiltinCall(node, builtin_id); |
| } |
| |
| #define DEF_BINARY_LOWERING(Name) \ |
| void JSGenericLowering::LowerJS##Name(Node* node) { \ |
| ReplaceBinaryOpWithBuiltinCall(node, Builtins::k##Name, \ |
| Builtins::k##Name##_WithFeedback); \ |
| } |
| // Binary ops. |
| DEF_BINARY_LOWERING(Add) |
| DEF_BINARY_LOWERING(BitwiseAnd) |
| DEF_BINARY_LOWERING(BitwiseOr) |
| DEF_BINARY_LOWERING(BitwiseXor) |
| DEF_BINARY_LOWERING(Divide) |
| DEF_BINARY_LOWERING(Exponentiate) |
| DEF_BINARY_LOWERING(Modulus) |
| DEF_BINARY_LOWERING(Multiply) |
| DEF_BINARY_LOWERING(ShiftLeft) |
| DEF_BINARY_LOWERING(ShiftRight) |
| DEF_BINARY_LOWERING(ShiftRightLogical) |
| DEF_BINARY_LOWERING(Subtract) |
| // Compare ops. |
| DEF_BINARY_LOWERING(Equal) |
| DEF_BINARY_LOWERING(GreaterThan) |
| DEF_BINARY_LOWERING(GreaterThanOrEqual) |
| DEF_BINARY_LOWERING(InstanceOf) |
| DEF_BINARY_LOWERING(LessThan) |
| DEF_BINARY_LOWERING(LessThanOrEqual) |
| #undef DEF_BINARY_LOWERING |
| |
| void JSGenericLowering::LowerJSStrictEqual(Node* node) { |
| // The === operator doesn't need the current context. |
| NodeProperties::ReplaceContextInput(node, jsgraph()->NoContextConstant()); |
| DCHECK_EQ(node->op()->ControlInputCount(), 1); |
| node->RemoveInput(NodeProperties::FirstControlIndex(node)); |
| |
| Builtins::Name builtin_id; |
| const FeedbackParameter& p = FeedbackParameterOf(node->op()); |
| if (CollectFeedbackInGenericLowering() && p.feedback().IsValid()) { |
| Node* slot = jsgraph()->UintPtrConstant(p.feedback().slot.ToInt()); |
| STATIC_ASSERT(JSStrictEqualNode::LeftIndex() == 0); |
| STATIC_ASSERT(JSStrictEqualNode::RightIndex() == 1); |
| STATIC_ASSERT(JSStrictEqualNode::FeedbackVectorIndex() == 2); |
| DCHECK_EQ(node->op()->ValueInputCount(), 3); |
| node->InsertInput(zone(), 2, slot); |
| builtin_id = Builtins::kStrictEqual_WithFeedback; |
| } else { |
| node->RemoveInput(JSStrictEqualNode::FeedbackVectorIndex()); |
| builtin_id = Builtins::kStrictEqual; |
| } |
| |
| Callable callable = Builtins::CallableFor(isolate(), builtin_id); |
| ReplaceWithBuiltinCall(node, callable, CallDescriptor::kNoFlags, |
| Operator::kEliminatable); |
| } |
| |
| namespace { |
| bool ShouldUseMegamorphicLoadBuiltin(FeedbackSource const& source, |
| JSHeapBroker* broker) { |
| ProcessedFeedback const& feedback = broker->GetFeedback(source); |
| |
| if (feedback.kind() == ProcessedFeedback::kElementAccess) { |
| return feedback.AsElementAccess().transition_groups().empty(); |
| } else if (feedback.kind() == ProcessedFeedback::kNamedAccess) { |
| return feedback.AsNamedAccess().maps().empty(); |
| } else if (feedback.kind() == ProcessedFeedback::kInsufficient) { |
| return false; |
| } |
| UNREACHABLE(); |
| } |
| } // namespace |
| |
| void JSGenericLowering::LowerJSHasProperty(Node* node) { |
| JSHasPropertyNode n(node); |
| const PropertyAccess& p = n.Parameters(); |
| if (!p.feedback().IsValid()) { |
| node->RemoveInput(JSHasPropertyNode::FeedbackVectorIndex()); |
| ReplaceWithBuiltinCall(node, Builtins::kHasProperty); |
| } else { |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 2); |
| n->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kKeyedHasIC); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSLoadProperty(Node* node) { |
| JSLoadPropertyNode n(node); |
| const PropertyAccess& p = n.Parameters(); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 2); |
| if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| n->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall( |
| node, ShouldUseMegamorphicLoadBuiltin(p.feedback(), broker()) |
| ? Builtins::kKeyedLoadICTrampoline_Megamorphic |
| : Builtins::kKeyedLoadICTrampoline); |
| } else { |
| n->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall( |
| node, ShouldUseMegamorphicLoadBuiltin(p.feedback(), broker()) |
| ? Builtins::kKeyedLoadIC_Megamorphic |
| : Builtins::kKeyedLoadIC); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSLoadNamed(Node* node) { |
| JSLoadNamedNode n(node); |
| NamedAccess const& p = n.Parameters(); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 1); |
| if (!p.feedback().IsValid()) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| ReplaceWithBuiltinCall(node, Builtins::kGetProperty); |
| } else if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall( |
| node, ShouldUseMegamorphicLoadBuiltin(p.feedback(), broker()) |
| ? Builtins::kLoadICTrampoline_Megamorphic |
| : Builtins::kLoadICTrampoline); |
| } else { |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall( |
| node, ShouldUseMegamorphicLoadBuiltin(p.feedback(), broker()) |
| ? Builtins::kLoadIC_Megamorphic |
| : Builtins::kLoadIC); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSLoadNamedFromSuper(Node* node) { |
| // TODO(marja, v8:9237): Call a builtin which collects feedback. |
| JSLoadNamedFromSuperNode n(node); |
| NamedAccess const& p = n.Parameters(); |
| node->RemoveInput(2); // Feedback vector |
| node->InsertInput(zone(), 2, jsgraph()->HeapConstant(p.name())); |
| ReplaceWithRuntimeCall(node, Runtime::kLoadFromSuper); |
| } |
| |
| void JSGenericLowering::LowerJSLoadGlobal(Node* node) { |
| JSLoadGlobalNode n(node); |
| const LoadGlobalParameters& p = n.Parameters(); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 0); |
| if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 1, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| Callable callable = CodeFactory::LoadGlobalIC(isolate(), p.typeof_mode()); |
| ReplaceWithBuiltinCall(node, callable, flags); |
| } else { |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 1, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| Callable callable = |
| CodeFactory::LoadGlobalICInOptimizedCode(isolate(), p.typeof_mode()); |
| ReplaceWithBuiltinCall(node, callable, flags); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSGetIterator(Node* node) { |
| // TODO(v8:9625): Currently, the GetIterator operator is desugared in the |
| // native context specialization phase. Thus, the following generic lowering |
| // is not reachable unless that phase is disabled (e.g. for |
| // native-context-independent code). |
| // We can add a check in native context specialization to avoid desugaring |
| // the GetIterator operator when feedback is megamorphic. This would reduce |
| // the size of the compiled code as it would insert 1 call to the builtin |
| // instead of 2 calls resulting from the generic lowering of the LoadNamed |
| // and Call operators. |
| |
| JSGetIteratorNode n(node); |
| GetIteratorParameters const& p = n.Parameters(); |
| Node* load_slot = |
| jsgraph()->TaggedIndexConstant(p.loadFeedback().slot.ToInt()); |
| Node* call_slot = |
| jsgraph()->TaggedIndexConstant(p.callFeedback().slot.ToInt()); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 1); |
| node->InsertInput(zone(), 1, load_slot); |
| node->InsertInput(zone(), 2, call_slot); |
| |
| ReplaceWithBuiltinCall(node, Builtins::kGetIteratorWithFeedback); |
| } |
| |
| void JSGenericLowering::LowerJSStoreProperty(Node* node) { |
| JSStorePropertyNode n(node); |
| const PropertyAccess& p = n.Parameters(); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 3); |
| if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kKeyedStoreICTrampoline); |
| } else { |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kKeyedStoreIC); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSStoreNamed(Node* node) { |
| JSStoreNamedNode n(node); |
| NamedAccess const& p = n.Parameters(); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 2); |
| if (!p.feedback().IsValid()) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| ReplaceWithRuntimeCall(node, Runtime::kSetNamedProperty); |
| } else if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kStoreICTrampoline); |
| } else { |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kStoreIC); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSStoreNamedOwn(Node* node) { |
| JSStoreNamedOwnNode n(node); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| StoreNamedOwnParameters const& p = n.Parameters(); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 2); |
| if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| Callable callable = CodeFactory::StoreOwnIC(isolate()); |
| ReplaceWithBuiltinCall(node, callable, flags); |
| } else { |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| Callable callable = CodeFactory::StoreOwnICInOptimizedCode(isolate()); |
| ReplaceWithBuiltinCall(node, callable, flags); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSStoreGlobal(Node* node) { |
| JSStoreGlobalNode n(node); |
| const StoreGlobalParameters& p = n.Parameters(); |
| FrameState frame_state = n.frame_state(); |
| FrameState outer_state = frame_state.outer_frame_state(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 1); |
| if (outer_state->opcode() != IrOpcode::kFrameState) { |
| n->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kStoreGlobalICTrampoline); |
| } else { |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(p.name())); |
| node->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kStoreGlobalIC); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSStoreDataPropertyInLiteral(Node* node) { |
| JSStoreDataPropertyInLiteralNode n(node); |
| FeedbackParameter const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 4); |
| RelaxControls(node); |
| node->InsertInput(zone(), 5, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithRuntimeCall(node, Runtime::kDefineDataPropertyInLiteral); |
| } |
| |
| void JSGenericLowering::LowerJSStoreInArrayLiteral(Node* node) { |
| JSStoreInArrayLiteralNode n(node); |
| FeedbackParameter const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 3); |
| RelaxControls(node); |
| node->InsertInput(zone(), 3, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kStoreInArrayLiteralIC); |
| } |
| |
| void JSGenericLowering::LowerJSDeleteProperty(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kDeleteProperty); |
| } |
| |
| void JSGenericLowering::LowerJSGetSuperConstructor(Node* node) { |
| Node* active_function = NodeProperties::GetValueInput(node, 0); |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| Node* function_map = effect = graph()->NewNode( |
| jsgraph()->simplified()->LoadField(AccessBuilder::ForMap()), |
| active_function, effect, control); |
| |
| RelaxControls(node); |
| node->ReplaceInput(0, function_map); |
| node->ReplaceInput(1, effect); |
| node->ReplaceInput(2, control); |
| node->TrimInputCount(3); |
| NodeProperties::ChangeOp(node, jsgraph()->simplified()->LoadField( |
| AccessBuilder::ForMapPrototype())); |
| } |
| |
| void JSGenericLowering::LowerJSHasInPrototypeChain(Node* node) { |
| ReplaceWithRuntimeCall(node, Runtime::kHasInPrototypeChain); |
| } |
| |
| void JSGenericLowering::LowerJSOrdinaryHasInstance(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kOrdinaryHasInstance); |
| } |
| |
| void JSGenericLowering::LowerJSHasContextExtension(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSLoadContext(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| |
| void JSGenericLowering::LowerJSStoreContext(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| |
| void JSGenericLowering::LowerJSCreate(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kFastNewObject); |
| } |
| |
| |
| void JSGenericLowering::LowerJSCreateArguments(Node* node) { |
| CreateArgumentsType const type = CreateArgumentsTypeOf(node->op()); |
| switch (type) { |
| case CreateArgumentsType::kMappedArguments: |
| ReplaceWithRuntimeCall(node, Runtime::kNewSloppyArguments); |
| break; |
| case CreateArgumentsType::kUnmappedArguments: |
| ReplaceWithRuntimeCall(node, Runtime::kNewStrictArguments); |
| break; |
| case CreateArgumentsType::kRestParameter: |
| ReplaceWithRuntimeCall(node, Runtime::kNewRestParameter); |
| break; |
| } |
| } |
| |
| |
| void JSGenericLowering::LowerJSCreateArray(Node* node) { |
| CreateArrayParameters const& p = CreateArrayParametersOf(node->op()); |
| int const arity = static_cast<int>(p.arity()); |
| auto interface_descriptor = ArrayConstructorDescriptor{}; |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), interface_descriptor, arity + 1, CallDescriptor::kNeedsFrameState, |
| node->op()->properties()); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(interface_descriptor.GetStackParameterCount(), 0); |
| Node* stub_code = jsgraph()->ArrayConstructorStubConstant(); |
| Node* stub_arity = jsgraph()->Int32Constant(arity); |
| MaybeHandle<AllocationSite> const maybe_site = p.site(); |
| Handle<AllocationSite> site; |
| DCHECK_IMPLIES(broker()->is_native_context_independent(), |
| maybe_site.is_null()); |
| Node* type_info = maybe_site.ToHandle(&site) ? jsgraph()->HeapConstant(site) |
| : jsgraph()->UndefinedConstant(); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, stub_arity); |
| node->InsertInput(zone(), 4, type_info); |
| node->InsertInput(zone(), 5, receiver); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| |
| void JSGenericLowering::LowerJSCreateArrayIterator(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreateAsyncFunctionObject(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreateCollectionIterator(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreateBoundFunction(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSObjectIsArray(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreateObject(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kCreateObjectWithoutProperties); |
| } |
| |
| void JSGenericLowering::LowerJSParseInt(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kParseInt); |
| } |
| |
| void JSGenericLowering::LowerJSRegExpTest(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kRegExpPrototypeTestFast); |
| } |
| |
| void JSGenericLowering::LowerJSCreateClosure(Node* node) { |
| JSCreateClosureNode n(node); |
| CreateClosureParameters const& p = n.Parameters(); |
| Handle<SharedFunctionInfo> const shared_info = p.shared_info(); |
| STATIC_ASSERT(n.FeedbackCellIndex() == 0); |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(shared_info)); |
| node->RemoveInput(4); // control |
| |
| // Use the FastNewClosure builtin only for functions allocated in new space. |
| if (p.allocation() == AllocationType::kYoung) { |
| ReplaceWithBuiltinCall(node, Builtins::kFastNewClosure); |
| } else { |
| ReplaceWithRuntimeCall(node, Runtime::kNewClosure_Tenured); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSCreateFunctionContext(Node* node) { |
| const CreateFunctionContextParameters& parameters = |
| CreateFunctionContextParametersOf(node->op()); |
| Handle<ScopeInfo> scope_info = parameters.scope_info(); |
| int slot_count = parameters.slot_count(); |
| ScopeType scope_type = parameters.scope_type(); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| |
| if (slot_count <= ConstructorBuiltins::MaximumFunctionContextSlots()) { |
| Callable callable = |
| CodeFactory::FastNewFunctionContext(isolate(), scope_type); |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(scope_info)); |
| node->InsertInput(zone(), 1, jsgraph()->Int32Constant(slot_count)); |
| ReplaceWithBuiltinCall(node, callable, flags); |
| } else { |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(scope_info)); |
| ReplaceWithRuntimeCall(node, Runtime::kNewFunctionContext); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSCreateGeneratorObject(Node* node) { |
| node->RemoveInput(4); // control |
| ReplaceWithBuiltinCall(node, Builtins::kCreateGeneratorObject); |
| } |
| |
| void JSGenericLowering::LowerJSCreateIterResultObject(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kCreateIterResultObject); |
| } |
| |
| void JSGenericLowering::LowerJSCreateStringIterator(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreateKeyValueArray(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreatePromise(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSCreateTypedArray(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kCreateTypedArray); |
| } |
| |
| void JSGenericLowering::LowerJSCreateLiteralArray(Node* node) { |
| JSCreateLiteralArrayNode n(node); |
| CreateLiteralParameters const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 0); |
| node->InsertInput(zone(), 1, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| node->InsertInput(zone(), 2, jsgraph()->HeapConstant(p.constant())); |
| |
| // Use the CreateShallowArrayLiteral builtin only for shallow boilerplates |
| // without properties up to the number of elements that the stubs can handle. |
| if ((p.flags() & AggregateLiteral::kIsShallow) != 0 && |
| p.length() < ConstructorBuiltins::kMaximumClonedShallowArrayElements) { |
| ReplaceWithBuiltinCall(node, Builtins::kCreateShallowArrayLiteral); |
| } else { |
| node->InsertInput(zone(), 3, jsgraph()->SmiConstant(p.flags())); |
| ReplaceWithRuntimeCall(node, Runtime::kCreateArrayLiteral); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSGetTemplateObject(Node* node) { |
| JSGetTemplateObjectNode n(node); |
| GetTemplateObjectParameters const& p = n.Parameters(); |
| SharedFunctionInfoRef shared(broker(), p.shared()); |
| TemplateObjectDescriptionRef description(broker(), p.description()); |
| |
| DCHECK_EQ(node->op()->ControlInputCount(), 1); |
| node->RemoveInput(NodeProperties::FirstControlIndex(node)); |
| |
| STATIC_ASSERT(JSGetTemplateObjectNode::FeedbackVectorIndex() == 0); |
| node->InsertInput(zone(), 0, jsgraph()->Constant(shared)); |
| node->InsertInput(zone(), 1, jsgraph()->Constant(description)); |
| node->InsertInput(zone(), 2, |
| jsgraph()->UintPtrConstant(p.feedback().index())); |
| |
| ReplaceWithBuiltinCall(node, Builtins::kGetTemplateObject); |
| } |
| |
| void JSGenericLowering::LowerJSCreateEmptyLiteralArray(Node* node) { |
| JSCreateEmptyLiteralArrayNode n(node); |
| FeedbackParameter const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 0); |
| node->InsertInput(zone(), 1, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| node->RemoveInput(4); // control |
| ReplaceWithBuiltinCall(node, Builtins::kCreateEmptyArrayLiteral); |
| } |
| |
| void JSGenericLowering::LowerJSCreateArrayFromIterable(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kIterableToListWithSymbolLookup); |
| } |
| |
| void JSGenericLowering::LowerJSCreateLiteralObject(Node* node) { |
| JSCreateLiteralObjectNode n(node); |
| CreateLiteralParameters const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 0); |
| node->InsertInput(zone(), 1, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| node->InsertInput(zone(), 2, jsgraph()->HeapConstant(p.constant())); |
| node->InsertInput(zone(), 3, jsgraph()->SmiConstant(p.flags())); |
| |
| // Use the CreateShallowObjectLiteratal builtin only for shallow boilerplates |
| // without elements up to the number of properties that the stubs can handle. |
| if ((p.flags() & AggregateLiteral::kIsShallow) != 0 && |
| p.length() <= |
| ConstructorBuiltins::kMaximumClonedShallowObjectProperties) { |
| ReplaceWithBuiltinCall(node, Builtins::kCreateShallowObjectLiteral); |
| } else { |
| ReplaceWithRuntimeCall(node, Runtime::kCreateObjectLiteral); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSCloneObject(Node* node) { |
| JSCloneObjectNode n(node); |
| CloneObjectParameters const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 1); |
| node->InsertInput(zone(), 1, jsgraph()->SmiConstant(p.flags())); |
| node->InsertInput(zone(), 2, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| ReplaceWithBuiltinCall(node, Builtins::kCloneObjectIC); |
| } |
| |
| void JSGenericLowering::LowerJSCreateEmptyLiteralObject(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kCreateEmptyLiteralObject); |
| } |
| |
| void JSGenericLowering::LowerJSCreateLiteralRegExp(Node* node) { |
| JSCreateLiteralRegExpNode n(node); |
| CreateLiteralParameters const& p = n.Parameters(); |
| STATIC_ASSERT(n.FeedbackVectorIndex() == 0); |
| node->InsertInput(zone(), 1, |
| jsgraph()->TaggedIndexConstant(p.feedback().index())); |
| node->InsertInput(zone(), 2, jsgraph()->HeapConstant(p.constant())); |
| node->InsertInput(zone(), 3, jsgraph()->SmiConstant(p.flags())); |
| ReplaceWithBuiltinCall(node, Builtins::kCreateRegExpLiteral); |
| } |
| |
| |
| void JSGenericLowering::LowerJSCreateCatchContext(Node* node) { |
| Handle<ScopeInfo> scope_info = ScopeInfoOf(node->op()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(scope_info)); |
| ReplaceWithRuntimeCall(node, Runtime::kPushCatchContext); |
| } |
| |
| void JSGenericLowering::LowerJSCreateWithContext(Node* node) { |
| Handle<ScopeInfo> scope_info = ScopeInfoOf(node->op()); |
| node->InsertInput(zone(), 1, jsgraph()->HeapConstant(scope_info)); |
| ReplaceWithRuntimeCall(node, Runtime::kPushWithContext); |
| } |
| |
| void JSGenericLowering::LowerJSCreateBlockContext(Node* node) { |
| Handle<ScopeInfo> scope_info = ScopeInfoOf(node->op()); |
| node->InsertInput(zone(), 0, jsgraph()->HeapConstant(scope_info)); |
| ReplaceWithRuntimeCall(node, Runtime::kPushBlockContext); |
| } |
| |
| namespace { |
| |
| bool CollectCallAndConstructFeedback(JSHeapBroker* broker) { |
| // Call and construct feedback is a special case. Besides shape feedback, we |
| // also increment the call count, which is later used to make inlining |
| // decisions. The call count is only comparable/reliable if it is incremented |
| // for all calls inside a function. This is not the case in default turbofan |
| // mode, in which many calls may be inlined and will thus never reach generic |
| // lowering (where we insert the feedback-collecting builtin call). |
| // Therefore it should only be collected in native context independent code, |
| // where we 1. know every call will reach generic lowering, and 2. we must |
| // collect full feedback to properly tier up later. |
| return broker->is_native_context_independent(); |
| } |
| |
| } // namespace |
| |
| // TODO(jgruber,v8:8888): Should this collect feedback? |
| void JSGenericLowering::LowerJSConstructForwardVarargs(Node* node) { |
| ConstructForwardVarargsParameters p = |
| ConstructForwardVarargsParametersOf(node->op()); |
| int const arg_count = static_cast<int>(p.arity() - 2); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| Callable callable = CodeFactory::ConstructForwardVarargs(isolate()); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), 0); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), arg_count + 1, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count); |
| Node* start_index = jsgraph()->Uint32Constant(p.start_index()); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, stub_arity); |
| node->InsertInput(zone(), 4, start_index); |
| node->InsertInput(zone(), 5, receiver); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| |
| void JSGenericLowering::LowerJSConstruct(Node* node) { |
| JSConstructNode n(node); |
| ConstructParameters const& p = n.Parameters(); |
| int const arg_count = p.arity_without_implicit_args(); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| |
| static constexpr int kReceiver = 1; |
| static constexpr int kMaybeFeedbackVector = 1; |
| |
| if (CollectFeedbackInGenericLowering() && |
| CollectCallAndConstructFeedback(broker()) && p.feedback().IsValid()) { |
| const int stack_argument_count = |
| arg_count + kReceiver + kMaybeFeedbackVector; |
| Callable callable = |
| Builtins::CallableFor(isolate(), Builtins::kConstruct_WithFeedback); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), |
| kMaybeFeedbackVector); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count); |
| Node* slot = jsgraph()->Int32Constant(p.feedback().index()); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| Node* feedback_vector = node->RemoveInput(n.FeedbackVectorIndex()); |
| // Register argument inputs are followed by stack argument inputs (such as |
| // feedback_vector). Both are listed in ascending order. Note that |
| // the receiver is implicitly placed on the stack and is thus inserted |
| // between explicitly-specified register and stack arguments. |
| // TODO(jgruber): Implement a simpler way to specify these mutations. |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, stub_arity); |
| node->InsertInput(zone(), 4, slot); |
| node->InsertInput(zone(), 5, feedback_vector); |
| node->InsertInput(zone(), 6, receiver); |
| // After: {code, target, new_target, arity, slot, vector, receiver, |
| // ...args}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| const int stack_argument_count = arg_count + kReceiver; |
| Callable callable = Builtins::CallableFor(isolate(), Builtins::kConstruct); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| node->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, stub_arity); |
| node->InsertInput(zone(), 4, receiver); |
| |
| // After: {code, target, new_target, arity, receiver, ...args}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSConstructWithArrayLike(Node* node) { |
| JSConstructWithArrayLikeNode n(node); |
| ConstructParameters const& p = n.Parameters(); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| const int arg_count = p.arity_without_implicit_args(); |
| DCHECK_EQ(arg_count, 1); |
| |
| static constexpr int kReceiver = 1; |
| static constexpr int kArgumentList = 1; |
| static constexpr int kMaybeFeedbackVector = 1; |
| |
| if (CollectFeedbackInGenericLowering() && |
| CollectCallAndConstructFeedback(broker()) && p.feedback().IsValid()) { |
| const int stack_argument_count = |
| arg_count - kArgumentList + kReceiver + kMaybeFeedbackVector; |
| Callable callable = Builtins::CallableFor( |
| isolate(), Builtins::kConstructWithArrayLike_WithFeedback); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), |
| kMaybeFeedbackVector); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| Node* slot = jsgraph()->Int32Constant(p.feedback().index()); |
| Node* feedback_vector = node->RemoveInput(n.FeedbackVectorIndex()); |
| // Register argument inputs are followed by stack argument inputs (such as |
| // feedback_vector). Both are listed in ascending order. Note that |
| // the receiver is implicitly placed on the stack and is thus inserted |
| // between explicitly-specified register and stack arguments. |
| // TODO(jgruber): Implement a simpler way to specify these mutations. |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 4, slot); |
| node->InsertInput(zone(), 5, feedback_vector); |
| node->InsertInput(zone(), 6, receiver); |
| // After: {code, target, new_target, arguments_list, slot, vector, |
| // receiver}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| const int stack_argument_count = arg_count - kArgumentList + kReceiver; |
| Callable callable = |
| Builtins::CallableFor(isolate(), Builtins::kConstructWithArrayLike); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), 0); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| node->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 4, receiver); |
| |
| // After: {code, target, new_target, arguments_list, receiver}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSConstructWithSpread(Node* node) { |
| JSConstructWithSpreadNode n(node); |
| ConstructParameters const& p = n.Parameters(); |
| int const arg_count = p.arity_without_implicit_args(); |
| DCHECK_GE(arg_count, 1); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| |
| static constexpr int kReceiver = 1; |
| static constexpr int kTheSpread = 1; // Included in `arg_count`. |
| static constexpr int kMaybeFeedbackVector = 1; |
| |
| if (CollectFeedbackInGenericLowering() && |
| CollectCallAndConstructFeedback(broker()) && p.feedback().IsValid()) { |
| const int stack_argument_count = |
| arg_count + kReceiver + kMaybeFeedbackVector; |
| Callable callable = Builtins::CallableFor( |
| isolate(), Builtins::kConstructWithSpread_WithFeedback); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), |
| kTheSpread + kMaybeFeedbackVector); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* slot = jsgraph()->Int32Constant(p.feedback().index()); |
| |
| // The single available register is needed for `slot`, thus `spread` remains |
| // on the stack here. |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count - kTheSpread); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| Node* feedback_vector = node->RemoveInput(n.FeedbackVectorIndex()); |
| Node* spread = node->RemoveInput(n.LastArgumentIndex()); |
| |
| // Register argument inputs are followed by stack argument inputs (such as |
| // feedback_vector). Both are listed in ascending order. Note that |
| // the receiver is implicitly placed on the stack and is thus inserted |
| // between explicitly-specified register and stack arguments. |
| // TODO(jgruber): Implement a simpler way to specify these mutations. |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, stub_arity); |
| node->InsertInput(zone(), 4, slot); |
| node->InsertInput(zone(), 5, spread); |
| node->InsertInput(zone(), 6, feedback_vector); |
| node->InsertInput(zone(), 7, receiver); |
| // After: {code, target, new_target, arity, slot, spread, vector, receiver, |
| // ...args}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| const int stack_argument_count = arg_count + kReceiver - kTheSpread; |
| Callable callable = CodeFactory::ConstructWithSpread(isolate()); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), 0); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| |
| // We pass the spread in a register, not on the stack. |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count - kTheSpread); |
| Node* receiver = jsgraph()->UndefinedConstant(); |
| DCHECK(n.FeedbackVectorIndex() > n.LastArgumentIndex()); |
| node->RemoveInput(n.FeedbackVectorIndex()); |
| Node* spread = node->RemoveInput(n.LastArgumentIndex()); |
| |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, stub_arity); |
| node->InsertInput(zone(), 4, spread); |
| node->InsertInput(zone(), 5, receiver); |
| |
| // After: {code, target, new_target, arity, spread, receiver, ...args}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| } |
| |
| // TODO(jgruber,v8:8888): Should this collect feedback? |
| void JSGenericLowering::LowerJSCallForwardVarargs(Node* node) { |
| CallForwardVarargsParameters p = CallForwardVarargsParametersOf(node->op()); |
| int const arg_count = static_cast<int>(p.arity() - 2); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| Callable callable = CodeFactory::CallForwardVarargs(isolate()); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), arg_count + 1, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count); |
| Node* start_index = jsgraph()->Uint32Constant(p.start_index()); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 2, stub_arity); |
| node->InsertInput(zone(), 3, start_index); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| |
| void JSGenericLowering::LowerJSCall(Node* node) { |
| JSCallNode n(node); |
| CallParameters const& p = n.Parameters(); |
| int const arg_count = p.arity_without_implicit_args(); |
| ConvertReceiverMode const mode = p.convert_mode(); |
| |
| Node* feedback_vector = n.feedback_vector(); |
| node->RemoveInput(n.FeedbackVectorIndex()); |
| |
| if (CollectFeedbackInGenericLowering() && |
| CollectCallAndConstructFeedback(broker()) && p.feedback().IsValid()) { |
| Callable callable = CodeFactory::Call_WithFeedback(isolate(), mode); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), arg_count + 1, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count); |
| Node* slot = jsgraph()->Int32Constant(p.feedback().index()); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 2, stub_arity); |
| node->InsertInput(zone(), 3, slot); |
| node->InsertInput(zone(), 4, feedback_vector); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| Callable callable = CodeFactory::Call(isolate(), mode); |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), arg_count + 1, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 2, stub_arity); |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSCallWithArrayLike(Node* node) { |
| JSCallWithArrayLikeNode n(node); |
| CallParameters const& p = n.Parameters(); |
| const int arg_count = p.arity_without_implicit_args(); |
| DCHECK_EQ(arg_count, 1); // The arraylike object. |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| |
| static constexpr int kArgumentsList = 1; |
| static constexpr int kReceiver = 1; |
| |
| if (CollectFeedbackInGenericLowering() && |
| CollectCallAndConstructFeedback(broker()) && p.feedback().IsValid()) { |
| const int stack_argument_count = arg_count - kArgumentsList + kReceiver; |
| Callable callable = Builtins::CallableFor( |
| isolate(), Builtins::kCallWithArrayLike_WithFeedback); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* receiver = n.receiver(); |
| Node* arguments_list = n.Argument(0); |
| Node* feedback_vector = n.feedback_vector(); |
| Node* slot = jsgraph()->Int32Constant(p.feedback().index()); |
| |
| // Shuffling inputs. |
| // Before: {target, receiver, arguments_list, vector}. |
| |
| node->ReplaceInput(1, arguments_list); |
| node->ReplaceInput(2, feedback_vector); |
| node->ReplaceInput(3, receiver); |
| |
| // Now: {target, arguments_list, vector, receiver}. |
| |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 3, slot); |
| |
| // After: {code, target, arguments_list, slot, vector, receiver}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| const int stack_argument_count = arg_count - kArgumentsList + kReceiver; |
| Callable callable = CodeFactory::CallWithArrayLike(isolate()); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* receiver = n.receiver(); |
| Node* arguments_list = n.Argument(0); |
| |
| // Shuffling inputs. |
| // Before: {target, receiver, arguments_list, vector}. |
| |
| node->RemoveInput(n.FeedbackVectorIndex()); |
| node->InsertInput(zone(), 0, stub_code); |
| node->ReplaceInput(2, arguments_list); |
| node->ReplaceInput(3, receiver); |
| |
| // After: {code, target, arguments_list, receiver}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSCallWithSpread(Node* node) { |
| JSCallWithSpreadNode n(node); |
| CallParameters const& p = n.Parameters(); |
| int const arg_count = p.arity_without_implicit_args(); |
| DCHECK_GE(arg_count, 1); // At least the spread. |
| CallDescriptor::Flags flags = FrameStateFlagForCall(node); |
| |
| static constexpr int kReceiver = 1; |
| static constexpr int kTheSpread = 1; |
| static constexpr int kMaybeFeedbackVector = 1; |
| |
| if (CollectFeedbackInGenericLowering() && |
| CollectCallAndConstructFeedback(broker()) && p.feedback().IsValid()) { |
| const int stack_argument_count = |
| arg_count - kTheSpread + kReceiver + kMaybeFeedbackVector; |
| Callable callable = Builtins::CallableFor( |
| isolate(), Builtins::kCallWithSpread_WithFeedback); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), |
| kMaybeFeedbackVector); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| Node* slot = jsgraph()->Int32Constant(p.feedback().index()); |
| |
| // We pass the spread in a register, not on the stack. |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count - kTheSpread); |
| |
| // Register argument inputs are followed by stack argument inputs (such as |
| // feedback_vector). Both are listed in ascending order. Note that |
| // the receiver is implicitly placed on the stack and is thus inserted |
| // between explicitly-specified register and stack arguments. |
| // TODO(jgruber): Implement a simpler way to specify these mutations. |
| |
| // Shuffling inputs. |
| // Before: {target, receiver, ...args, spread, vector}. |
| Node* feedback_vector = node->RemoveInput(n.FeedbackVectorIndex()); |
| Node* spread = node->RemoveInput(n.LastArgumentIndex()); |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 2, stub_arity); |
| node->InsertInput(zone(), 3, spread); |
| node->InsertInput(zone(), 4, slot); |
| node->InsertInput(zone(), 5, feedback_vector); |
| // After: {code, target, arity, spread, slot, vector, receiver, ...args}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } else { |
| const int stack_argument_count = arg_count - kTheSpread + kReceiver; |
| Callable callable = CodeFactory::CallWithSpread(isolate()); |
| // If this fails, we might need to update the parameter reordering code |
| // to ensure that the additional arguments passed via stack are pushed |
| // between top of stack and JS arguments. |
| DCHECK_EQ(callable.descriptor().GetStackParameterCount(), 0); |
| auto call_descriptor = Linkage::GetStubCallDescriptor( |
| zone(), callable.descriptor(), stack_argument_count, flags); |
| Node* stub_code = jsgraph()->HeapConstant(callable.code()); |
| |
| // We pass the spread in a register, not on the stack. |
| Node* stub_arity = jsgraph()->Int32Constant(arg_count - kTheSpread); |
| |
| // Shuffling inputs. |
| // Before: {target, receiver, ...args, spread, vector}. |
| |
| node->RemoveInput(n.FeedbackVectorIndex()); |
| Node* spread = node->RemoveInput(n.LastArgumentIndex()); |
| |
| node->InsertInput(zone(), 0, stub_code); |
| node->InsertInput(zone(), 2, stub_arity); |
| node->InsertInput(zone(), 3, spread); |
| |
| // After: {code, target, arity, spread, receiver, ...args}. |
| |
| NodeProperties::ChangeOp(node, common()->Call(call_descriptor)); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSCallRuntime(Node* node) { |
| const CallRuntimeParameters& p = CallRuntimeParametersOf(node->op()); |
| ReplaceWithRuntimeCall(node, p.id(), static_cast<int>(p.arity())); |
| } |
| |
| void JSGenericLowering::LowerJSForInPrepare(Node* node) { |
| JSForInPrepareNode n(node); |
| Effect effect(node); // {node} is kept in the effect chain. |
| Control control = n.control(); // .. but not in the control chain. |
| Node* enumerator = n.enumerator(); |
| Node* slot = |
| jsgraph()->UintPtrConstant(n.Parameters().feedback().slot.ToInt()); |
| |
| std::vector<Edge> use_edges; |
| for (Edge edge : node->use_edges()) use_edges.push_back(edge); |
| |
| // {node} will be changed to a builtin call (see below). The returned value |
| // is a fixed array containing {cache_array} and {cache_length}. |
| // TODO(jgruber): This is awkward; what we really want is two return values, |
| // the {cache_array} and {cache_length}, or better yet three return values |
| // s.t. we can avoid the graph rewrites below. Builtin support for multiple |
| // return types is unclear though. |
| |
| Node* result_fixed_array = node; |
| Node* cache_type = enumerator; // Just to clarify the rename. |
| Node* cache_array; |
| Node* cache_length; |
| |
| cache_array = effect = graph()->NewNode( |
| machine()->Load(MachineType::AnyTagged()), result_fixed_array, |
| jsgraph()->IntPtrConstant(FixedArray::OffsetOfElementAt(0) - |
| kHeapObjectTag), |
| effect, control); |
| cache_length = effect = graph()->NewNode( |
| machine()->Load(MachineType::AnyTagged()), result_fixed_array, |
| jsgraph()->IntPtrConstant(FixedArray::OffsetOfElementAt(1) - |
| kHeapObjectTag), |
| effect, control); |
| |
| // Update the uses of {node}. |
| for (Edge edge : use_edges) { |
| Node* const user = edge.from(); |
| if (NodeProperties::IsEffectEdge(edge)) { |
| edge.UpdateTo(effect); |
| } else if (NodeProperties::IsControlEdge(edge)) { |
| edge.UpdateTo(control); |
| } else { |
| DCHECK(NodeProperties::IsValueEdge(edge)); |
| switch (ProjectionIndexOf(user->op())) { |
| case 0: |
| Replace(user, cache_type); |
| break; |
| case 1: |
| Replace(user, cache_array); |
| break; |
| case 2: |
| Replace(user, cache_length); |
| break; |
| default: |
| UNREACHABLE(); |
| } |
| } |
| } |
| |
| // Finally, change the original node into a builtin call. This happens here, |
| // after graph rewrites, since the Call does not have a control output and |
| // thus must not have any control uses. Any previously existing control |
| // outputs have been replaced by the graph rewrite above. |
| node->InsertInput(zone(), n.FeedbackVectorIndex(), slot); |
| ReplaceWithBuiltinCall(node, Builtins::kForInPrepare); |
| } |
| |
| void JSGenericLowering::LowerJSForInNext(Node* node) { |
| JSForInNextNode n(node); |
| node->InsertInput( |
| zone(), 0, |
| jsgraph()->UintPtrConstant(n.Parameters().feedback().slot.ToInt())); |
| ReplaceWithBuiltinCall(node, Builtins::kForInNext); |
| } |
| |
| void JSGenericLowering::LowerJSLoadMessage(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| |
| void JSGenericLowering::LowerJSStoreMessage(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSLoadModule(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSStoreModule(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSGetImportMeta(Node* node) { |
| ReplaceWithRuntimeCall(node, Runtime::kGetImportMetaObject); |
| } |
| |
| void JSGenericLowering::LowerJSGeneratorStore(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSGeneratorRestoreContinuation(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSGeneratorRestoreContext(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSGeneratorRestoreInputOrDebugPos(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| void JSGenericLowering::LowerJSGeneratorRestoreRegister(Node* node) { |
| UNREACHABLE(); // Eliminated in typed lowering. |
| } |
| |
| namespace { |
| |
| StackCheckKind StackCheckKindOfJSStackCheck(const Operator* op) { |
| DCHECK(op->opcode() == IrOpcode::kJSStackCheck); |
| return OpParameter<StackCheckKind>(op); |
| } |
| |
| } // namespace |
| |
| void JSGenericLowering::LowerJSStackCheck(Node* node) { |
| Node* effect = NodeProperties::GetEffectInput(node); |
| Node* control = NodeProperties::GetControlInput(node); |
| |
| Node* limit = effect = |
| graph()->NewNode(machine()->Load(MachineType::Pointer()), |
| jsgraph()->ExternalConstant( |
| ExternalReference::address_of_jslimit(isolate())), |
| jsgraph()->IntPtrConstant(0), effect, control); |
| |
| StackCheckKind stack_check_kind = StackCheckKindOfJSStackCheck(node->op()); |
| Node* check = effect = graph()->NewNode( |
| machine()->StackPointerGreaterThan(stack_check_kind), limit, effect); |
| Node* branch = |
| graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control); |
| |
| Node* if_true = graph()->NewNode(common()->IfTrue(), branch); |
| Node* etrue = effect; |
| |
| Node* if_false = graph()->NewNode(common()->IfFalse(), branch); |
| NodeProperties::ReplaceControlInput(node, if_false); |
| NodeProperties::ReplaceEffectInput(node, effect); |
| Node* efalse = if_false = node; |
| |
| Node* merge = graph()->NewNode(common()->Merge(2), if_true, if_false); |
| Node* ephi = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, merge); |
| |
| // Wire the new diamond into the graph, {node} can still throw. |
| NodeProperties::ReplaceUses(node, node, ephi, merge, merge); |
| NodeProperties::ReplaceControlInput(merge, if_false, 1); |
| NodeProperties::ReplaceEffectInput(ephi, efalse, 1); |
| |
| // This iteration cuts out potential {IfSuccess} or {IfException} projection |
| // uses of the original node and places them inside the diamond, so that we |
| // can change the original {node} into the slow-path runtime call. |
| for (Edge edge : merge->use_edges()) { |
| if (!NodeProperties::IsControlEdge(edge)) continue; |
| if (edge.from()->opcode() == IrOpcode::kIfSuccess) { |
| NodeProperties::ReplaceUses(edge.from(), nullptr, nullptr, merge); |
| NodeProperties::ReplaceControlInput(merge, edge.from(), 1); |
| edge.UpdateTo(node); |
| } |
| if (edge.from()->opcode() == IrOpcode::kIfException) { |
| NodeProperties::ReplaceEffectInput(edge.from(), node); |
| edge.UpdateTo(node); |
| } |
| } |
| |
| // Turn the stack check into a runtime call. At function entry, the runtime |
| // function takes an offset argument which is subtracted from the stack |
| // pointer prior to the stack check (i.e. the check is `sp - offset >= |
| // limit`). |
| if (stack_check_kind == StackCheckKind::kJSFunctionEntry) { |
| node->InsertInput(zone(), 0, |
| graph()->NewNode(machine()->LoadStackCheckOffset())); |
| ReplaceWithRuntimeCall(node, Runtime::kStackGuardWithGap); |
| } else { |
| ReplaceWithRuntimeCall(node, Runtime::kStackGuard); |
| } |
| } |
| |
| void JSGenericLowering::LowerJSDebugger(Node* node) { |
| ReplaceWithBuiltinCall(node, Builtins::kHandleDebuggerStatement); |
| } |
| |
| Zone* JSGenericLowering::zone() const { return graph()->zone(); } |
| |
| |
| Isolate* JSGenericLowering::isolate() const { return jsgraph()->isolate(); } |
| |
| |
| Graph* JSGenericLowering::graph() const { return jsgraph()->graph(); } |
| |
| |
| CommonOperatorBuilder* JSGenericLowering::common() const { |
| return jsgraph()->common(); |
| } |
| |
| |
| MachineOperatorBuilder* JSGenericLowering::machine() const { |
| return jsgraph()->machine(); |
| } |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |