Commit ab3b3bec authored by jarin's avatar jarin Committed by Commit bot

[turbofan] Unwind and jump to the catch handler in the deoptimizer.

The idea here is to perform the handler lookup in the deoptimizer, and then take the information from the handler table to build the catch handler frame in the deoptimizer. Specifically, we use the pc offset, context location and stack height (in full-code) to tweak the output frame.

Sadly, this still requires nasty voodoo for the liveness analyzer so that it keeps variables alive if they are used in the catch handler.

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

Cr-Commit-Position: refs/heads/master@{#33936}
parent 0e79d8c7
......@@ -431,10 +431,13 @@ class AstGraphBuilder::FrameStateBeforeAndAfter {
DCHECK_EQ(IrOpcode::kDead,
NodeProperties::GetFrameStateInput(node, 0)->opcode());
bool node_has_exception = NodeProperties::IsExceptionalCall(node);
Node* frame_state_after =
id_after == BailoutId::None()
? builder_->jsgraph()->EmptyFrameState()
: builder_->environment()->Checkpoint(id_after, combine);
: builder_->environment()->Checkpoint(id_after, combine,
node_has_exception);
NodeProperties::ReplaceFrameStateInput(node, 0, frame_state_after);
}
......@@ -869,9 +872,9 @@ void AstGraphBuilder::Environment::UpdateStateValuesWithCache(
env_values, static_cast<size_t>(count));
}
Node* AstGraphBuilder::Environment::Checkpoint(
BailoutId ast_id, OutputFrameStateCombine combine) {
Node* AstGraphBuilder::Environment::Checkpoint(BailoutId ast_id,
OutputFrameStateCombine combine,
bool owner_has_exception) {
if (!builder()->info()->is_deoptimization_enabled()) {
return builder()->jsgraph()->EmptyFrameState();
}
......@@ -891,7 +894,15 @@ Node* AstGraphBuilder::Environment::Checkpoint(
DCHECK(IsLivenessBlockConsistent());
if (liveness_block() != nullptr) {
liveness_block()->Checkpoint(result);
// If the owning node has an exception, register the checkpoint to the
// predecessor so that the checkpoint is used for both the normal and the
// exceptional paths. Yes, this is a terrible hack and we might want
// to use an explicit frame state for the exceptional path.
if (owner_has_exception) {
liveness_block()->GetPredecessor()->Checkpoint(result);
} else {
liveness_block()->Checkpoint(result);
}
}
return result;
}
......@@ -4042,8 +4053,10 @@ void AstGraphBuilder::PrepareFrameState(Node* node, BailoutId ast_id,
DCHECK_EQ(IrOpcode::kDead,
NodeProperties::GetFrameStateInput(node, 0)->opcode());
bool node_has_exception = NodeProperties::IsExceptionalCall(node);
NodeProperties::ReplaceFrameStateInput(
node, 0, environment()->Checkpoint(ast_id, combine));
node, 0,
environment()->Checkpoint(ast_id, combine, node_has_exception));
}
}
......
......@@ -518,7 +518,8 @@ class AstGraphBuilder::Environment : public ZoneObject {
// Preserve a checkpoint of the environment for the IR graph. Any
// further mutation of the environment will not affect checkpoints.
Node* Checkpoint(BailoutId ast_id, OutputFrameStateCombine combine =
OutputFrameStateCombine::Ignore());
OutputFrameStateCombine::Ignore(),
bool node_has_exception = false);
// Control dependency tracked by this environment.
Node* GetControlDependency() { return control_dependency_; }
......
......@@ -85,6 +85,10 @@ class LivenessAnalyzerBlock {
void Bind(int var) { entries_.push_back(Entry(Entry::kBind, var)); }
void Checkpoint(Node* node) { entries_.push_back(Entry(node)); }
void AddPredecessor(LivenessAnalyzerBlock* b) { predecessors_.push_back(b); }
LivenessAnalyzerBlock* GetPredecessor() {
DCHECK(predecessors_.size() == 1);
return predecessors_[0];
}
private:
class Entry {
......
......@@ -123,6 +123,7 @@ bool NodeProperties::IsControlEdge(Edge edge) {
// static
bool NodeProperties::IsExceptionalCall(Node* node) {
if (node->op()->HasProperty(Operator::kNoThrow)) return false;
for (Edge const edge : node->use_edges()) {
if (!NodeProperties::IsControlEdge(edge)) continue;
if (edge.from()->opcode() == IrOpcode::kIfException) return true;
......
This diff is collapsed.
......@@ -597,8 +597,8 @@ class Deoptimizer : public Malloced {
void DeleteFrameDescriptions();
void DoComputeOutputFrames();
void DoComputeJSFrame(int frame_index);
void DoComputeInterpretedFrame(int frame_index);
void DoComputeJSFrame(int frame_index, bool goto_catch_handler);
void DoComputeInterpretedFrame(int frame_index, bool goto_catch_handler);
void DoComputeArgumentsAdaptorFrame(int frame_index);
void DoComputeConstructStubFrame(int frame_index);
void DoComputeAccessorStubFrame(int frame_index, bool is_setter_stub_frame);
......@@ -671,6 +671,9 @@ class Deoptimizer : public Malloced {
Address from_;
int fp_to_sp_delta_;
int has_alignment_padding_;
bool deoptimizing_throw_;
int catch_handler_data_;
int catch_handler_pc_offset_;
// Input frame description.
FrameDescription* input_;
......
......@@ -96,6 +96,8 @@ class FullCodeGenerator: public AstVisitor {
#error Unsupported target architecture.
#endif
static Register result_register();
private:
class Breakable;
class Iteration;
......@@ -741,7 +743,6 @@ class FullCodeGenerator: public AstVisitor {
FunctionLiteral* literal() const { return info_->literal(); }
Scope* scope() { return scope_; }
static Register result_register();
static Register context_register();
// Set fields in the stack frame. Offsets are the frame pointer relative
......
......@@ -1112,6 +1112,13 @@ Object* Isolate::UnwindAndFindHandler() {
// Gather information from the frame.
code = frame->LookupCode();
if (code->marked_for_deoptimization()) {
// If the target code is lazy deoptimized, we jump to the original
// return address, but we make a note that we are throwing, so that
// the deoptimizer can do the right thing.
offset = static_cast<int>(frame->pc() - code->entry());
set_deoptimizer_lazy_throw(true);
}
handler_sp = return_sp;
handler_fp = frame->fp();
break;
......@@ -1755,7 +1762,6 @@ void Isolate::ThreadDataTable::RemoveAllThreads(Isolate* isolate) {
#define TRACE_ISOLATE(tag)
#endif
Isolate::Isolate(bool enable_serializer)
: embedder_data_(),
entry_stack_(NULL),
......@@ -1771,6 +1777,7 @@ Isolate::Isolate(bool enable_serializer)
stub_cache_(NULL),
code_aging_helper_(NULL),
deoptimizer_data_(NULL),
deoptimizer_lazy_throw_(false),
materialized_object_store_(NULL),
capture_stack_trace_for_uncaught_exceptions_(false),
stack_trace_for_uncaught_exceptions_frame_limit_(0),
......
......@@ -820,6 +820,10 @@ class Isolate {
StubCache* stub_cache() { return stub_cache_; }
CodeAgingHelper* code_aging_helper() { return code_aging_helper_; }
DeoptimizerData* deoptimizer_data() { return deoptimizer_data_; }
bool deoptimizer_lazy_throw() const { return deoptimizer_lazy_throw_; }
void set_deoptimizer_lazy_throw(bool value) {
deoptimizer_lazy_throw_ = value;
}
ThreadLocalTop* thread_local_top() { return &thread_local_top_; }
MaterializedObjectStore* materialized_object_store() {
return materialized_object_store_;
......@@ -1218,6 +1222,7 @@ class Isolate {
StubCache* stub_cache_;
CodeAgingHelper* code_aging_helper_;
DeoptimizerData* deoptimizer_data_;
bool deoptimizer_lazy_throw_;
MaterializedObjectStore* materialized_object_store_;
ThreadLocalTop thread_local_top_;
bool capture_stack_trace_for_uncaught_exceptions_;
......
// 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: --allow-natives-syntax
(function LazyDeoptFromTryBlock() {
function g(dummy) {
%DeoptimizeFunction(f);
throw 42;
}
function f() {
var a = 1;
try {
var dummy = 2; // perturb the stack height.
g(dummy);
} catch (e) {
return e + a;
}
}
assertEquals(43, f());
assertEquals(43, f());
%NeverOptimizeFunction(g);
%OptimizeFunctionOnNextCall(f);
assertEquals(43, f());
})();
(function LazyDeoptDoublyNestedTryBlock() {
function g(dummy) {
%DeoptimizeFunction(f);
throw 42;
}
function f() {
var b;
try {
var a = 1;
try {
var dummy = 2; // perturb the stack height.
g(dummy);
} catch (e) {
b = e + a;
}
} catch (e) {
return 0;
}
return b;
}
assertEquals(43, f());
assertEquals(43, f());
%NeverOptimizeFunction(g);
%OptimizeFunctionOnNextCall(f);
assertEquals(43, f());
})();
(function LazyDeoptInlinedTry() {
function g(dummy) {
%DeoptimizeFunction(f);
%DeoptimizeFunction(h);
throw 42;
}
function h() {
var a = 1;
try {
var dummy = 2; // perturb the stack height.
g(dummy);
} catch (e) {
b = e + a;
}
return b;
}
function f() {
var c = 1;
return h() + 1;
}
assertEquals(44, f());
assertEquals(44, f());
%NeverOptimizeFunction(g);
%OptimizeFunctionOnNextCall(f);
assertEquals(44, f());
})();
(function LazyDeoptInlinedIntoTry() {
function g(c) {
%DeoptimizeFunction(f);
%DeoptimizeFunction(h);
throw c;
}
function h(c) {
return g(c);
}
function f() {
var a = 1;
try {
var c = 42; // perturb the stack height.
h(c);
} catch (e) {
a += e;
}
return a;
}
assertEquals(43, f());
assertEquals(43, f());
%NeverOptimizeFunction(g);
%OptimizeFunctionOnNextCall(f);
assertEquals(43, f());
})();
(function LazyDeoptTryBlockContextCatch() {
var global = 0;
function g() {
%DeoptimizeFunction(f);
throw "boom!";
}
function f(a) {
var x = a + 23
try {
let y = a + 42;
function capture() { return x + y }
g();
} catch(e) {
global = x;
}
return x;
}
assertEquals(23, f(0));
assertEquals(24, f(1));
%OptimizeFunctionOnNextCall(f);
assertEquals(25, f(2));
assertEquals(25, global);
})();
(function LazyDeoptTryBlockFinally() {
var global = 0;
function g() {
%DeoptimizeFunction(f);
throw "boom!";
}
function f(a) {
var x = a + 23
try {
let y = a + 42;
function capture() { return x + y }
g();
} finally {
global = x;
}
return x;
}
assertThrows(function() { f(0) });
assertThrows(function() { f(1) });
%OptimizeFunctionOnNextCall(f);
assertThrowsEquals(function() { f(2) }, "boom!");
assertEquals(25, global);
})();
(function LazyDeoptTryCatchContextCatch() {
var global = 0;
function g() {
%DeoptimizeFunction(f);
throw 5;
}
function f(a) {
var x = a + 23
try {
try {
throw 1;
} catch(e2) {
function capture() { return x + y }
g();
}
} catch(e) {
global = x + e;
}
return x;
}
assertEquals(23, f(0));
assertEquals(24, f(1));
%OptimizeFunctionOnNextCall(f);
assertEquals(25, f(2));
assertEquals(30, global);
})();
(function LazyDeoptTryWithContextCatch() {
var global = 0;
function g() {
%DeoptimizeFunction(f);
throw 5;
}
function f(a) {
var x = a + 23
try {
with ({ y : a + 42 }) {
function capture() { return x + y }
g();
}
} catch(e) {
global = x + e;
}
return x;
}
assertEquals(23, f(0));
assertEquals(24, f(1));
%OptimizeFunctionOnNextCall(f);
assertEquals(25, f(2));
assertEquals(30, global);
})();
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