Commit 11d5d09c authored by mstarzinger's avatar mstarzinger Committed by Commit bot

[turbofan] Initial support for constructor call inlining.

This implements a first version of support for constructor call inlining
in the inlining machinery. For now we can only inline calls where the
actual constructor and the original constructor coincide (i.e. no super
constructor calls). Note that the target of a super constructor call is
loaded with a runtime call, so there is no way for it to be constant
promoted at the moment.

R=bmeurer@chromium.org
BUG=v8:4544
LOG=n

Review URL: https://codereview.chromium.org/1435873002

Cr-Commit-Position: refs/heads/master@{#31954}
parent dd0ba4d1
......@@ -3215,7 +3215,8 @@ Node* AstGraphBuilder::BuildNewTargetVariable(Variable* new_target_var) {
const Operator* op =
javascript()->CallRuntime(Runtime::kGetOriginalConstructor, 0);
Node* object = NewNode(op);
PrepareFrameState(object, BailoutId::None());
// TODO(4544): Bailout id only needed for JavaScriptFrame::Summarize.
PrepareFrameState(object, BailoutId::FunctionContext());
// Assign the object to the {new.target} variable. This should never lazy
// deopt, so it is fine to send invalid bailout id.
......
......@@ -551,6 +551,11 @@ void CodeGenerator::BuildTranslationForFrameStateDescriptor(
shared_info_id,
static_cast<unsigned int>(descriptor->parameters_count()));
break;
case FrameStateType::kConstructStub:
translation->BeginConstructStubFrame(
shared_info_id,
static_cast<unsigned int>(descriptor->parameters_count()));
break;
}
for (size_t i = 0; i < descriptor->GetSize(state_combine); i++) {
......
......@@ -54,6 +54,9 @@ std::ostream& operator<<(std::ostream& os, FrameStateType type) {
case FrameStateType::kArgumentsAdaptor:
os << "ARGUMENTS_ADAPTOR";
break;
case FrameStateType::kConstructStub:
os << "CONSTRUCT_STUB";
break;
}
return os;
}
......
......@@ -77,7 +77,8 @@ class OutputFrameStateCombine {
// The type of stack frame that a FrameState node represents.
enum class FrameStateType {
kJavaScriptFunction, // Represents an unoptimized JavaScriptFrame.
kArgumentsAdaptor // Represents an ArgumentsAdaptorFrame.
kArgumentsAdaptor, // Represents an ArgumentsAdaptorFrame.
kConstructStub // Represents a ConstructStubFrame.
};
......
......@@ -456,7 +456,12 @@ void JSGenericLowering::LowerJSLoadDynamic(Node* node) {
}
void JSGenericLowering::LowerJSCreate(Node* node) { UNIMPLEMENTED(); }
void JSGenericLowering::LowerJSCreate(Node* node) {
// TODO(4544): The duplication of the constructor function is only valid if
// actual constructor and original constructor coincide. Fix this!
node->InsertInput(zone(), 1, node->InputAt(0)); // Duplicate constructor.
ReplaceWithRuntimeCall(node, Runtime::kNewObject);
}
void JSGenericLowering::LowerJSCreateArguments(Node* node) {
......
......@@ -13,7 +13,7 @@ namespace internal {
namespace compiler {
Reduction JSInliningHeuristic::Reduce(Node* node) {
if (node->opcode() != IrOpcode::kJSCallFunction) return NoChange();
if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
// Check if we already saw that {node} before, and if so, just skip it.
if (seen_.find(node->id()) != seen_.end()) return NoChange();
......@@ -26,7 +26,7 @@ Reduction JSInliningHeuristic::Reduce(Node* node) {
// Functions marked with %SetForceInlineFlag are immediately inlined.
if (function->shared()->force_inline()) {
return inliner_.ReduceJSCallFunction(node, function);
return inliner_.ReduceJSCall(node, function);
}
// Handling of special inlining modes right away:
......@@ -36,7 +36,7 @@ Reduction JSInliningHeuristic::Reduce(Node* node) {
case kRestrictedInlining:
return NoChange();
case kStressInlining:
return inliner_.ReduceJSCallFunction(node, function);
return inliner_.ReduceJSCall(node, function);
case kGeneralInlining:
break;
}
......@@ -67,18 +67,21 @@ Reduction JSInliningHeuristic::Reduce(Node* node) {
// Stop inlinining once the maximum allowed level is reached.
int level = 0;
for (Node* frame_state = NodeProperties::GetFrameStateInput(node, 1);
for (Node* frame_state = NodeProperties::GetFrameStateInput(node, 0);
frame_state->opcode() == IrOpcode::kFrameState;
frame_state = NodeProperties::GetFrameStateInput(frame_state, 0)) {
if (++level > FLAG_max_inlining_levels) return NoChange();
}
// Gather feedback on how often this call site has been hit before.
CallFunctionParameters p = CallFunctionParametersOf(node->op());
int calls = -1; // Same default as CallICNexus::ExtractCallCount.
if (p.feedback().IsValid()) {
CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
calls = nexus.ExtractCallCount();
// TODO(turbofan): We also want call counts for constructor calls.
if (node->opcode() == IrOpcode::kJSCallFunction) {
CallFunctionParameters p = CallFunctionParametersOf(node->op());
if (p.feedback().IsValid()) {
CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
calls = nexus.ExtractCallCount();
}
}
// ---------------------------------------------------------------------------
......@@ -99,7 +102,7 @@ void JSInliningHeuristic::Finalize() {
if (cumulative_count_ > FLAG_max_inlined_nodes_cumulative) break;
auto i = candidates_.begin();
Candidate const& candidate = *i;
inliner_.ReduceJSCallFunction(candidate.node, candidate.function);
inliner_.ReduceJSCall(candidate.node, candidate.function);
cumulative_count_ += candidate.function->shared()->ast_node_count();
candidates_.erase(i);
}
......
......@@ -34,36 +34,47 @@ namespace compiler {
} while (false)
// Provides convenience accessors for calls to JS functions.
class JSCallFunctionAccessor {
// Provides convenience accessors for the common layout of nodes having either
// the {JSCallFunction} or the {JSCallConstruct} operator.
class JSCallAccessor {
public:
explicit JSCallFunctionAccessor(Node* call) : call_(call) {
DCHECK_EQ(IrOpcode::kJSCallFunction, call->opcode());
explicit JSCallAccessor(Node* call) : call_(call) {
DCHECK(call->opcode() == IrOpcode::kJSCallFunction ||
call->opcode() == IrOpcode::kJSCallConstruct);
}
Node* jsfunction() { return call_->InputAt(0); }
Node* receiver() { return call_->InputAt(1); }
Node* target() {
// Both, {JSCallFunction} and {JSCallConstruct}, have same layout here.
return call_->InputAt(0);
}
Node* formal_argument(size_t index) {
DCHECK(index < formal_arguments());
return call_->InputAt(static_cast<int>(2 + index));
Node* receiver() {
DCHECK_EQ(IrOpcode::kJSCallFunction, call_->opcode());
return call_->InputAt(1);
}
size_t formal_arguments() {
// {value_inputs} includes jsfunction and receiver.
size_t value_inputs = call_->op()->ValueInputCount();
DCHECK_GE(call_->InputCount(), 2);
return value_inputs - 2;
Node* original_constructor() {
DCHECK_EQ(IrOpcode::kJSCallConstruct, call_->opcode());
return call_->InputAt(formal_arguments() + 1);
}
Node* frame_state_before() {
DCHECK_EQ(IrOpcode::kJSCallFunction, call_->opcode());
return NodeProperties::GetFrameStateInput(call_, 1);
}
Node* frame_state_after() {
// Both, {JSCallFunction} and {JSCallConstruct}, have frame state after.
return NodeProperties::GetFrameStateInput(call_, 0);
}
int formal_arguments() {
// Both, {JSCallFunction} and {JSCallConstruct}, have two extra inputs:
// - JSCallConstruct: Includes target function and original constructor.
// - JSCallFunction: Includes target function and receiver.
return call_->op()->ValueInputCount() - 2;
}
private:
Node* call_;
};
......@@ -227,12 +238,13 @@ Reduction JSInliner::InlineCall(Node* call, Node* context, Node* frame_state,
}
Node* JSInliner::CreateArgumentsAdaptorFrameState(
JSCallFunctionAccessor* call, Handle<SharedFunctionInfo> shared_info) {
Node* JSInliner::CreateArtificialFrameState(Node* node, Node* outer_frame_state,
int parameter_count,
FrameStateType frame_state_type,
Handle<SharedFunctionInfo> shared) {
const FrameStateFunctionInfo* state_info =
jsgraph_->common()->CreateFrameStateFunctionInfo(
FrameStateType::kArgumentsAdaptor,
static_cast<int>(call->formal_arguments()) + 1, 0, shared_info,
frame_state_type, parameter_count + 1, 0, shared,
CALL_MAINTAINS_NATIVE_CONTEXT);
const Operator* op = jsgraph_->common()->FrameState(
......@@ -240,36 +252,37 @@ Node* JSInliner::CreateArgumentsAdaptorFrameState(
const Operator* op0 = jsgraph_->common()->StateValues(0);
Node* node0 = jsgraph_->graph()->NewNode(op0);
NodeVector params(local_zone_);
params.push_back(call->receiver());
for (size_t argument = 0; argument != call->formal_arguments(); ++argument) {
params.push_back(call->formal_argument(argument));
for (int parameter = 0; parameter < parameter_count + 1; ++parameter) {
params.push_back(node->InputAt(1 + parameter));
}
const Operator* op_param =
jsgraph_->common()->StateValues(static_cast<int>(params.size()));
Node* params_node = jsgraph_->graph()->NewNode(
op_param, static_cast<int>(params.size()), &params.front());
return jsgraph_->graph()->NewNode(
op, params_node, node0, node0, jsgraph_->UndefinedConstant(),
call->jsfunction(), call->frame_state_after());
return jsgraph_->graph()->NewNode(op, params_node, node0, node0,
jsgraph_->UndefinedConstant(),
node->InputAt(0), outer_frame_state);
}
Reduction JSInliner::Reduce(Node* node) {
if (node->opcode() != IrOpcode::kJSCallFunction) return NoChange();
if (!IrOpcode::IsInlineeOpcode(node->opcode())) return NoChange();
JSCallFunctionAccessor call(node);
HeapObjectMatcher match(call.jsfunction());
// This reducer can handle both normal function calls as well a constructor
// calls whenever the target is a constant function object, as follows:
// - JSCallFunction(target:constant, receiver, args...)
// - JSCallConstruct(target:constant, args..., new.target)
HeapObjectMatcher match(node->InputAt(0));
if (!match.HasValue() || !match.Value()->IsJSFunction()) return NoChange();
Handle<JSFunction> function = Handle<JSFunction>::cast(match.Value());
return ReduceJSCallFunction(node, function);
return ReduceJSCall(node, function);
}
Reduction JSInliner::ReduceJSCallFunction(Node* node,
Handle<JSFunction> function) {
DCHECK_EQ(IrOpcode::kJSCallFunction, node->opcode());
JSCallFunctionAccessor call(node);
Reduction JSInliner::ReduceJSCall(Node* node, Handle<JSFunction> function) {
DCHECK(IrOpcode::IsInlineeOpcode(node->opcode()));
JSCallAccessor call(node);
if (!function->shared()->IsInlineable()) {
// Function must be inlineable.
......@@ -359,7 +372,7 @@ Reduction JSInliner::ReduceJSCallFunction(Node* node,
// In strong mode, in case of too few arguments we need to throw a TypeError
// so we must not inline this call.
size_t parameter_count = info.literal()->parameter_count();
int parameter_count = info.literal()->parameter_count();
if (is_strong(info.language_mode()) &&
call.formal_arguments() < parameter_count) {
TRACE("Not inlining %s into %s because too few arguments for strong mode\n",
......@@ -425,12 +438,6 @@ Reduction JSInliner::ReduceJSCallFunction(Node* node,
graph_reducer.ReduceGraph();
}
// The inlinee specializes to the context from the JSFunction object.
// TODO(turbofan): We might want to load the context from the JSFunction at
// runtime in case we only know the SharedFunctionInfo once we have dynamic
// type feedback in the compiler.
Node* context = jsgraph_->Constant(handle(function->context()));
CopyVisitor visitor(&graph, jsgraph_->graph(), &zone);
visitor.CopyGraph();
......@@ -438,13 +445,60 @@ Reduction JSInliner::ReduceJSCallFunction(Node* node,
Node* end = visitor.GetCopy(graph.end());
Node* frame_state = call.frame_state_after();
// Insert nodes around the call that model the behavior required for a
// constructor dispatch and turn the constructor call into a regular call.
// This models the behavior usually accomplished by our {JSConstructStub}.
// Note that the context has to be the callers context (input to call node).
if (node->opcode() == IrOpcode::kJSCallConstruct) {
Node* effect = NodeProperties::GetEffectInput(node);
Node* context = NodeProperties::GetContextInput(node);
Node* create = jsgraph_->graph()->NewNode(jsgraph_->javascript()->Create(),
call.target(), context, effect);
NodeProperties::ReplaceEffectInput(node, create);
// TODO(4544): For now {JSCreate} requires the actual constructor to
// coincide with the original constructor. Adapt JSGenericLowering to fix.
// Also Runtime_GetOriginalConstructor depends on this for now. Fix as well!
CHECK_EQ(call.target(), call.original_constructor());
// TODO(4544): For derived constructors we should not allocate an implicit
// receiver and also the return value should not be checked afterwards.
CHECK(!IsClassConstructor(function->shared()->kind()));
// Swizzle the inputs of the {JSCallConstruct} node to look like inputs to
// any {JSCallFunction} node so that the rest of the inlining machinery
// behaves as if we were dealing with a regular function invocation.
node->RemoveInput(call.formal_arguments() + 1); // Drop new.target.
node->InsertInput(jsgraph_->graph()->zone(), 1, create);
// Insert a check of the return value to determine whether the return value
// or the implicit receiver should be selected as a result of the call.
Node* check = jsgraph_->graph()->NewNode(
jsgraph_->javascript()->CallRuntime(Runtime::kInlineIsSpecObject, 1),
node, context, node, start);
Node* select = jsgraph_->graph()->NewNode(
jsgraph_->common()->Select(kMachAnyTagged), check, node, create);
NodeProperties::ReplaceUses(node, select, check, node, node);
NodeProperties::ReplaceValueInput(select, node, 1);
NodeProperties::ReplaceValueInput(check, node, 0);
NodeProperties::ReplaceEffectInput(check, 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, call.formal_arguments(),
FrameStateType::kConstructStub, info.shared_info());
}
// The inlinee specializes to the context from the JSFunction object.
// TODO(turbofan): We might want to load the context from the JSFunction at
// runtime in case we only know the SharedFunctionInfo once we have dynamic
// type feedback in the compiler.
Node* context = jsgraph_->Constant(handle(function->context()));
// Insert a JSConvertReceiver node for sloppy callees. Note that the context
// passed into this node has to be the callees context (loaded above). Note
// that the frame state passed to the JSConvertReceiver must be the frame
// state _before_ the call; it is not necessary to fiddle with the receiver
// in that frame state tho, as the conversion of the receiver can be repeated
// any number of times, it's not observable.
if (is_sloppy(info.language_mode()) && !function->shared()->native()) {
if (node->opcode() == IrOpcode::kJSCallFunction &&
is_sloppy(info.language_mode()) && !function->shared()->native()) {
const CallFunctionParameters& p = CallFunctionParametersOf(node->op());
Node* effect = NodeProperties::GetEffectInput(node);
Node* convert = jsgraph_->graph()->NewNode(
......@@ -457,10 +511,11 @@ Reduction JSInliner::ReduceJSCallFunction(Node* node,
// Insert argument adaptor frame if required. The callees formal parameter
// count (i.e. value outputs of start node minus target, receiver, num args
// and context) have to match the number of arguments passed to the call.
DCHECK_EQ(static_cast<int>(parameter_count),
start->op()->ValueOutputCount() - 4);
DCHECK_EQ(parameter_count, start->op()->ValueOutputCount() - 4);
if (call.formal_arguments() != parameter_count) {
frame_state = CreateArgumentsAdaptorFrameState(&call, info.shared_info());
frame_state = CreateArtificialFrameState(
node, frame_state, call.formal_arguments(),
FrameStateType::kArgumentsAdaptor, info.shared_info());
}
return InlineCall(node, context, frame_state, start, end);
......
......@@ -16,9 +16,6 @@ class CompilationInfo;
namespace compiler {
// Forward declarations.
class JSCallFunctionAccessor;
// The JSInliner provides the core graph inlining machinery. Note that this
// class only deals with the mechanics of how to inline one graph into another,
// heuristics that decide what and how much to inline are beyond its scope.
......@@ -36,15 +33,17 @@ class JSInliner final : public AdvancedReducer {
// Can be used by inlining heuristics or by testing code directly, without
// using the above generic reducer interface of the inlining machinery.
Reduction ReduceJSCallFunction(Node* node, Handle<JSFunction> function);
Reduction ReduceJSCall(Node* node, Handle<JSFunction> function);
private:
Zone* local_zone_;
CompilationInfo* info_;
JSGraph* jsgraph_;
Node* CreateArgumentsAdaptorFrameState(
JSCallFunctionAccessor* call, Handle<SharedFunctionInfo> shared_info);
Node* CreateArtificialFrameState(Node* node, Node* outer_frame_state,
int parameter_count,
FrameStateType frame_state_type,
Handle<SharedFunctionInfo> shared);
Reduction InlineCall(Node* call, Node* context, Node* frame_state,
Node* start, Node* end);
......
......@@ -363,7 +363,7 @@ const CreateClosureParameters& CreateClosureParametersOf(const Operator* op) {
V(ToName, Operator::kNoProperties, 1, 1) \
V(ToObject, Operator::kNoProperties, 1, 1) \
V(Yield, Operator::kNoProperties, 1, 1) \
V(Create, Operator::kEliminatable, 0, 1) \
V(Create, Operator::kEliminatable, 1, 1) \
V(HasProperty, Operator::kNoProperties, 2, 1) \
V(TypeOf, Operator::kEliminatable, 1, 1) \
V(InstanceOf, Operator::kNoProperties, 2, 1) \
......
......@@ -230,6 +230,7 @@ int Linkage::FrameStateInputCount(Runtime::FunctionId function) {
case Runtime::kNewClosure:
case Runtime::kNewClosure_Tenured:
case Runtime::kNewFunctionContext:
case Runtime::kNewObject:
case Runtime::kPushBlockContext:
case Runtime::kPushCatchContext:
case Runtime::kReThrow:
......@@ -244,6 +245,7 @@ int Linkage::FrameStateInputCount(Runtime::FunctionId function) {
case Runtime::kInlineDefaultConstructorCallSuper:
case Runtime::kInlineGetCallerJSFunction:
case Runtime::kInlineGetPrototype:
case Runtime::kInlineIsConstructCall:
case Runtime::kInlineRegExpExec:
case Runtime::kInlineSubString:
case Runtime::kInlineToInteger:
......
......@@ -373,6 +373,11 @@ class IrOpcode {
return kIfTrue <= value && value <= kIfDefault;
}
// Returns true if opcode can be inlined.
static bool IsInlineeOpcode(Value value) {
return value == kJSCallConstruct || value == kJSCallFunction;
}
// Returns true if opcode for comparison operator.
static bool IsComparisonOpcode(Value value) {
return (kJSEqual <= value && value <= kJSGreaterThanOrEqual) ||
......
......@@ -570,10 +570,18 @@ RUNTIME_FUNCTION(Runtime_GetOriginalConstructor) {
DCHECK(args.length() == 0);
JavaScriptFrameIterator it(isolate);
JavaScriptFrame* frame = it.frame();
// Currently we don't inline [[Construct]] calls.
return frame->IsConstructor() && !frame->HasInlinedFrames()
? frame->GetOriginalConstructor()
: isolate->heap()->undefined_value();
// TODO(4544): Currently we never inline any [[Construct]] calls where the
// actual constructor differs from the original constructor. Fix this soon!
if (frame->HasInlinedFrames()) {
HandleScope scope(isolate);
List<FrameSummary> frames(FLAG_max_inlining_levels + 1);
it.frame()->Summarize(&frames);
FrameSummary& summary = frames.last();
return summary.is_constructor() ? Object::cast(*summary.function())
: isolate->heap()->undefined_value();
}
return frame->IsConstructor() ? frame->GetOriginalConstructor()
: isolate->heap()->undefined_value();
}
......@@ -590,11 +598,13 @@ RUNTIME_FUNCTION(Runtime_ConvertReceiver) {
RUNTIME_FUNCTION(Runtime_IsConstructCall) {
SealHandleScope shs(isolate);
HandleScope scope(isolate);
DCHECK(args.length() == 0);
JavaScriptFrameIterator it(isolate);
JavaScriptFrame* frame = it.frame();
return isolate->heap()->ToBoolean(frame->IsConstructor());
List<FrameSummary> frames(FLAG_max_inlining_levels + 1);
it.frame()->Summarize(&frames);
FrameSummary& summary = frames.last();
return isolate->heap()->ToBoolean(summary.is_constructor());
}
......
......@@ -138,8 +138,14 @@
'debug-stepout-scope-part2': [PASS, NO_VARIANTS],
'debug-stepout-scope-part3': [PASS, NO_VARIANTS],
'es6/debug-evaluate-blockscopes': [PASS, NO_VARIANTS],
# issue 4055:
# Issue 4055: Scope chain length observed by debugger is off.
'es6/generators-debug-scopes': [PASS, NO_VARIANTS],
# Issue 4544: Stepping in inlined constructors is borked.
'es6/debug-promises/throw-in-constructor': [PASS, NO_VARIANTS],
'es6/debug-step-into-constructor': [PASS, NO_VARIANTS],
# TODO(4544): Investigate why assumptions about fast properties break.
'fast-prototype': [PASS, NO_VARIANTS],
# TODO(titzer): --always-opt incorrectly disables CrankShaft soft deopt points
'result-table-min': [PASS, NO_VARIANTS],
......
......@@ -78,7 +78,7 @@ const SharedOperator kSharedOperators[] = {
SHARED(ToName, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
SHARED(ToObject, Operator::kNoProperties, 1, 1, 1, 1, 1, 1, 2),
SHARED(Yield, Operator::kNoProperties, 1, 0, 1, 1, 1, 1, 2),
SHARED(Create, Operator::kEliminatable, 0, 0, 1, 0, 1, 1, 0),
SHARED(Create, Operator::kEliminatable, 1, 0, 1, 0, 1, 1, 0),
SHARED(HasProperty, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
SHARED(TypeOf, Operator::kEliminatable, 1, 0, 1, 0, 1, 1, 0),
SHARED(InstanceOf, Operator::kNoProperties, 2, 1, 1, 1, 1, 1, 2),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment