Commit e17a283f authored by ishell's avatar ishell Committed by Commit bot

[es6] Properly handle the case when an inlined getter/setter/constructor does a tail call.

Deoptimizer is now able to reconstruct topmost accessor and constructor frames.

BUG=chromium:608278, v8:4698
LOG=N
TBR=bmeurer@chromium.org

Review-Url: https://codereview.chromium.org/1936043002
Cr-Commit-Position: refs/heads/master@{#36075}
parent 0dfc1613
......@@ -871,42 +871,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -714,42 +714,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -2127,7 +2127,11 @@ void HEnterInlined::RegisterReturnTarget(HBasicBlock* return_target,
std::ostream& HEnterInlined::PrintDataTo(std::ostream& os) const { // NOLINT
return os << function()->debug_name()->ToCString().get();
os << function()->debug_name()->ToCString().get();
if (syntactic_tail_call_mode() == TailCallMode::kAllow) {
os << ", JSTailCall";
}
return os;
}
......
......@@ -904,42 +904,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -338,7 +338,6 @@ void LChunk::AddInstruction(LInstruction* instr, HBasicBlock* block) {
}
}
LConstantOperand* LChunk::DefineConstantOperand(HConstant* constant) {
return LConstantOperand::Create(constant->id(), zone());
}
......@@ -508,6 +507,57 @@ void LChunkBuilderBase::Retry(BailoutReason reason) {
status_ = ABORTED;
}
void LChunkBuilderBase::CreateLazyBailoutForCall(HBasicBlock* current_block,
LInstruction* instr,
HInstruction* hydrogen_val) {
if (!instr->IsCall()) return;
HEnvironment* hydrogen_env = current_block->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
if (hydrogen_env->frame_type() == JS_FUNCTION) {
// In case an outer frame is a function frame we have to replay
// environment manually because
// 1) it does not contain a result of inlined function yet,
// 2) we can't find the proper simulate that corresponds to the point
// after inlined call to do a ReplayEnvironment() on.
// So we push return value on top of outer environment.
// As for JS_GETTER/JS_SETTER/JS_CONSTRUCT nothing has to be done here,
// the deoptimizer ensures that the result of the callee is correctly
// propagated to result register during deoptimization.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
}
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block);
}
LInstruction* LChunkBuilderBase::AssignEnvironment(LInstruction* instr,
HEnvironment* hydrogen_env) {
int argument_index_accumulator = 0;
......
......@@ -745,6 +745,11 @@ class LChunkBuilderBase BASE_EMBEDDED {
// Will not be moved to a register even if one is freely available.
virtual MUST_USE_RESULT LOperand* UseAny(HValue* value) = 0;
// Constructs proper environment for a lazy bailout point after call, creates
// LLazyBailout instruction and adds it to current block.
void CreateLazyBailoutForCall(HBasicBlock* current_block, LInstruction* instr,
HInstruction* hydrogen_val);
// Assigns given environment to an instruction. An instruction which can
// deoptimize must have an environment.
LInstruction* AssignEnvironment(LInstruction* instr,
......
......@@ -881,42 +881,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -881,42 +881,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -886,42 +886,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -810,42 +810,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
LInstruction* LChunkBuilder::DoPrologue(HPrologue* instr) {
......
......@@ -896,42 +896,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
......@@ -919,42 +919,7 @@ void LChunkBuilder::AddInstruction(LInstruction* instr,
}
chunk_->AddInstruction(instr, current_block_);
if (instr->IsCall()) {
HEnvironment* hydrogen_env = current_block_->last_environment();
HValue* hydrogen_value_for_lazy_bailout = hydrogen_val;
DCHECK_NOT_NULL(hydrogen_env);
if (instr->IsSyntacticTailCall()) {
// If it was a syntactic tail call we need to drop the current frame and
// all the frames on top of it that are either an arguments adaptor frame
// or a tail caller frame.
hydrogen_env = hydrogen_env->outer();
while (hydrogen_env != nullptr &&
(hydrogen_env->frame_type() == ARGUMENTS_ADAPTOR ||
hydrogen_env->frame_type() == TAIL_CALLER_FUNCTION)) {
hydrogen_env = hydrogen_env->outer();
}
if (hydrogen_env != nullptr) {
// Push return value on top of outer environment.
hydrogen_env = hydrogen_env->Copy();
hydrogen_env->Push(hydrogen_val);
} else {
// Although we don't need this lazy bailout for normal execution
// (because when we tail call from the outermost function we should pop
// its frame) we still need it when debugger is on.
hydrogen_env = current_block_->last_environment();
}
} else {
if (hydrogen_val->HasObservableSideEffects()) {
HSimulate* sim = HSimulate::cast(hydrogen_val->next());
sim->ReplayEnvironment(hydrogen_env);
hydrogen_value_for_lazy_bailout = sim;
}
}
LInstruction* bailout = LChunkBuilderBase::AssignEnvironment(
new (zone()) LLazyBailout(), hydrogen_env);
bailout->set_hydrogen_value(hydrogen_value_for_lazy_bailout);
chunk_->AddInstruction(bailout, current_block_);
}
CreateLazyBailoutForCall(current_block_, instr, hydrogen_val);
}
......
This diff is collapsed.
......@@ -22,6 +22,8 @@ function CheckStackTrace(expected) {
assertEquals(expected[i].name, stack[i + 1].getFunctionName());
}
}
%NeverOptimizeFunction(CheckStackTrace);
function f(expected_call_stack, a, b) {
CheckStackTrace(expected_call_stack);
......@@ -223,6 +225,88 @@ function f_153(expected_call_stack, a) {
})();
// Tail calling from getter.
(function() {
function g(v) {
CheckStackTrace([g, test]);
%DeoptimizeFunction(test);
return 153;
}
%NeverOptimizeFunction(g);
function f(v) {
return g();
}
%SetForceInlineFlag(f);
function test() {
var o = {};
o.__defineGetter__('p', f);
assertEquals(153, o.p);
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail calling from setter.
(function() {
function g() {
CheckStackTrace([g, test]);
%DeoptimizeFunction(test);
return 153;
}
%NeverOptimizeFunction(g);
var context = 10;
function f(v) {
return g(context);
}
%SetForceInlineFlag(f);
function test() {
var o = {};
o.__defineSetter__('q', f);
assertEquals(1, o.q = 1);
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail calling from constructor.
(function() {
function g(context) {
CheckStackTrace([g, test]);
%DeoptimizeFunction(test);
return {x: 153};
}
%NeverOptimizeFunction(g);
function A() {
this.x = 42;
return g();
}
function test() {
var o = new A();
%DebugPrint(o);
assertEquals(153, o.x);
}
test();
test();
%OptimizeFunctionOnNextCall(test);
test();
})();
// Tail calling via various expressions.
(function() {
function g1(a) {
......
// Copyright 2016 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.
// Flags: --harmony-tailcalls --allow-natives-syntax
"use strict";
function h() {
var stack = (new Error("boom")).stack;
print(stack);
%DeoptimizeFunction(f1);
%DeoptimizeFunction(f2);
%DeoptimizeFunction(f3);
%DeoptimizeFunction(g);
%DeoptimizeFunction(h);
return 1;
}
%NeverOptimizeFunction(h);
function g(v) {
return h();
}
%SetForceInlineFlag(g);
function f1() {
var o = {};
o.__defineGetter__('p', g);
o.p;
}
f1();
f1();
%OptimizeFunctionOnNextCall(f1);
f1();
function f2() {
var o = {};
o.__defineSetter__('q', g);
o.q = 1;
}
f2();
f2();
%OptimizeFunctionOnNextCall(f2);
f2();
function A() {
return h();
}
function f3() {
new A();
}
f3();
f3();
%OptimizeFunctionOnNextCall(f3);
f3();
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