[turbofan] Introduce prediction for exception handlers.

This introduces a conservative prediction for each exception handler
whether it will locally catch an exception or re-throw it to outside
the code bondaries. It will allow for a more intuitive prediction of
whether an exception is considered "caught" or "uncaught".

R=bmeurer@chromium.org,yangguo@chromium.org
BUG=chromium:492522
LOG=N

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

Cr-Commit-Position: refs/heads/master@{#28681}
This commit is contained in:
mstarzinger 2015-05-28 06:22:48 -07:00 committed by Commit bot
parent 9079b99ad4
commit d8b94f34cc
22 changed files with 166 additions and 30 deletions

View File

@ -1106,6 +1106,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -1276,6 +1276,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler != nullptr) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -335,9 +335,11 @@ class AstGraphBuilder::ControlScopeForCatch : public ControlScope {
ControlScopeForCatch(AstGraphBuilder* owner, TryCatchBuilder* control)
: ControlScope(owner), control_(control) {
builder()->try_nesting_level_++; // Increment nesting.
builder()->try_catch_nesting_level_++;
}
~ControlScopeForCatch() {
builder()->try_nesting_level_--; // Decrement nesting.
builder()->try_catch_nesting_level_--;
}
protected:
@ -437,6 +439,7 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info,
globals_(0, local_zone),
execution_control_(nullptr),
execution_context_(nullptr),
try_catch_nesting_level_(0),
try_nesting_level_(0),
input_buffer_size_(0),
input_buffer_(nullptr),
@ -3563,17 +3566,22 @@ Node* AstGraphBuilder::MakeNode(const Operator* op, int value_input_count,
}
// Add implicit exception continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow) && inside_try_scope) {
// Conservative prediction whether caught locally.
IfExceptionHint hint = try_catch_nesting_level_ > 0
? IfExceptionHint::kLocallyCaught
: IfExceptionHint::kLocallyUncaught;
// Copy the environment for the success continuation.
Environment* success_env = environment()->CopyForConditional();
Node* on_exception = graph()->NewNode(common()->IfException(), result);
const Operator* op = common()->IfException(hint);
Node* on_exception = graph()->NewNode(op, result);
environment_->UpdateControlDependency(on_exception);
execution_control()->ThrowValue(on_exception);
set_environment(success_env);
}
// Add implicit success continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow)) {
Node* on_success = graph()->NewNode(common()->IfSuccess(), result);
const Operator* op = common()->IfSuccess();
Node* on_success = graph()->NewNode(op, result);
environment_->UpdateControlDependency(on_success);
}
}

View File

@ -90,6 +90,7 @@ class AstGraphBuilder : public AstVisitor {
SetOncePointer<Node> function_context_;
// Tracks how many try-blocks are currently entered.
int try_catch_nesting_level_;
int try_nesting_level_;
// Temporary storage for building node input lists.

View File

@ -156,8 +156,12 @@ Handle<Code> CodeGenerator::GenerateCode() {
HandlerTable::LengthForReturn(static_cast<int>(handlers_.size())),
TENURED));
for (size_t i = 0; i < handlers_.size(); ++i) {
int position = handlers_[i].handler->pos();
HandlerTable::CatchPrediction prediction = handlers_[i].caught_locally
? HandlerTable::CAUGHT
: HandlerTable::UNCAUGHT;
table->SetReturnOffset(static_cast<int>(i), handlers_[i].pc_offset);
table->SetReturnHandler(static_cast<int>(i), handlers_[i].handler->pos());
table->SetReturnHandler(static_cast<int>(i), position, prediction);
}
result->set_handler_table(*table);
}
@ -379,9 +383,9 @@ void CodeGenerator::RecordCallPosition(Instruction* instr) {
if (flags & CallDescriptor::kHasExceptionHandler) {
InstructionOperandConverter i(this, instr);
RpoNumber handler_rpo =
i.InputRpo(static_cast<int>(instr->InputCount()) - 1);
handlers_.push_back({GetLabel(handler_rpo), masm()->pc_offset()});
bool caught = flags & CallDescriptor::kHasLocalCatchHandler;
RpoNumber handler_rpo = i.InputRpo(instr->InputCount() - 1);
handlers_.push_back({caught, GetLabel(handler_rpo), masm()->pc_offset()});
}
if (flags & CallDescriptor::kNeedsNopAfterCall) {

View File

@ -160,6 +160,7 @@ class CodeGenerator final : public GapResolver::Assembler {
};
struct HandlerInfo {
bool caught_locally;
Label* handler;
int pc_offset;
};

View File

@ -36,6 +36,21 @@ BranchHint BranchHintOf(const Operator* const op) {
}
size_t hash_value(IfExceptionHint hint) { return static_cast<size_t>(hint); }
std::ostream& operator<<(std::ostream& os, IfExceptionHint hint) {
switch (hint) {
case IfExceptionHint::kLocallyCaught:
return os << "Caught";
case IfExceptionHint::kLocallyUncaught:
return os << "Uncaught";
}
UNREACHABLE();
return os;
}
bool operator==(SelectParameters const& lhs, SelectParameters const& rhs) {
return lhs.type() == rhs.type() && lhs.hint() == rhs.hint();
}
@ -105,7 +120,6 @@ std::ostream& operator<<(std::ostream& os, ParameterInfo const& i) {
V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfException, Operator::kKontrol, 0, 0, 1, 1, 0, 1) \
V(IfDefault, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(Throw, Operator::kKontrol, 1, 1, 1, 0, 0, 1) \
V(Deoptimize, Operator::kNoThrow, 1, 1, 1, 0, 0, 1) \
@ -210,6 +224,18 @@ struct CommonOperatorGlobalCache final {
CACHED_OP_LIST(CACHED)
#undef CACHED
template <IfExceptionHint kCaughtLocally>
struct IfExceptionOperator final : public Operator1<IfExceptionHint> {
IfExceptionOperator()
: Operator1<IfExceptionHint>( // --
IrOpcode::kIfException, Operator::kKontrol, // opcode
"IfException", // name
0, 0, 1, 1, 0, 1, // counts
kCaughtLocally) {} // parameter
};
IfExceptionOperator<IfExceptionHint::kLocallyCaught> kIfExceptionCOperator;
IfExceptionOperator<IfExceptionHint::kLocallyUncaught> kIfExceptionUOperator;
template <size_t kInputCount>
struct EndOperator final : public Operator {
EndOperator()
@ -385,6 +411,18 @@ const Operator* CommonOperatorBuilder::Branch(BranchHint hint) {
}
const Operator* CommonOperatorBuilder::IfException(IfExceptionHint hint) {
switch (hint) {
case IfExceptionHint::kLocallyCaught:
return &cache_.kIfExceptionCOperator;
case IfExceptionHint::kLocallyUncaught:
return &cache_.kIfExceptionUOperator;
}
UNREACHABLE();
return nullptr;
}
const Operator* CommonOperatorBuilder::Switch(size_t control_output_count) {
DCHECK_GE(control_output_count, 3u); // Disallow trivial switches.
return new (zone()) Operator( // --

View File

@ -34,6 +34,14 @@ std::ostream& operator<<(std::ostream&, BranchHint);
BranchHint BranchHintOf(const Operator* const);
// Prediction whether throw-site is surrounded by any local catch-scope.
enum class IfExceptionHint { kLocallyUncaught, kLocallyCaught };
size_t hash_value(IfExceptionHint hint);
std::ostream& operator<<(std::ostream&, IfExceptionHint);
class SelectParameters final {
public:
explicit SelectParameters(MachineType type,
@ -94,7 +102,7 @@ class CommonOperatorBuilder final : public ZoneObject {
const Operator* IfTrue();
const Operator* IfFalse();
const Operator* IfSuccess();
const Operator* IfException();
const Operator* IfException(IfExceptionHint hint);
const Operator* Switch(size_t control_output_count);
const Operator* IfValue(int32_t value);
const Operator* IfDefault();

View File

@ -843,6 +843,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -73,7 +73,8 @@ class CallDescriptor final : public ZoneObject {
kPatchableCallSite = 1u << 1,
kNeedsNopAfterCall = 1u << 2,
kHasExceptionHandler = 1u << 3,
kSupportsTailCalls = 1u << 4,
kHasLocalCatchHandler = 1u << 4,
kSupportsTailCalls = 1u << 5,
kPatchableCallSiteWithNop = kPatchableCallSite | kNeedsNopAfterCall
};
typedef base::Flags<Flag> Flags;

View File

@ -541,6 +541,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -690,6 +690,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -1461,6 +1461,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -1047,6 +1047,11 @@ void InstructionSelector::VisitCall(Node* node, BasicBlock* handler) {
// Pass label of exception handler block.
CallDescriptor::Flags flags = descriptor->flags();
if (handler) {
DCHECK_EQ(IrOpcode::kIfException, handler->front()->opcode());
IfExceptionHint hint = OpParameter<IfExceptionHint>(handler->front());
if (hint == IfExceptionHint::kLocallyCaught) {
flags |= CallDescriptor::kHasLocalCatchHandler;
}
flags |= CallDescriptor::kHasExceptionHandler;
buffer.instruction_args.push_back(g.Label(handler));
}

View File

@ -983,7 +983,8 @@ int OptimizedFrame::LookupExceptionHandlerInTable(int* stack_slots) {
HandlerTable* table = HandlerTable::cast(code->handler_table());
int pc_offset = static_cast<int>(pc() - code->entry());
*stack_slots = code->stack_slots();
return table->LookupReturn(pc_offset);
HandlerTable::CatchPrediction prediction; // TODO(yangguo): For debugger.
return table->LookupReturn(pc_offset, &prediction);
}

View File

@ -8638,11 +8638,14 @@ int HandlerTable::LookupRange(int pc_offset, int* stack_depth_out) {
// TODO(turbofan): Make sure table is sorted and use binary search.
int HandlerTable::LookupReturn(int pc_offset) {
int HandlerTable::LookupReturn(int pc_offset, CatchPrediction* prediction) {
for (int i = 0; i < length(); i += kReturnEntrySize) {
int return_offset = Smi::cast(get(i + kReturnOffsetIndex))->value();
int handler_offset = Smi::cast(get(i + kReturnHandlerIndex))->value();
if (pc_offset == return_offset) return handler_offset;
int handler_field = Smi::cast(get(i + kReturnHandlerIndex))->value();
if (pc_offset == return_offset) {
*prediction = HandlerPredictionField::decode(handler_field);
return HandlerOffsetField::decode(handler_field);
}
}
return -1;
}
@ -11841,12 +11844,14 @@ void HandlerTable::HandlerTableRangePrint(std::ostream& os) {
void HandlerTable::HandlerTableReturnPrint(std::ostream& os) {
os << " off hdlr\n";
os << " off hdlr (c)\n";
for (int i = 0; i < length(); i += kReturnEntrySize) {
int pc_offset = Smi::cast(get(i + kReturnOffsetIndex))->value();
int handler = Smi::cast(get(i + kReturnHandlerIndex))->value();
int handler_field = Smi::cast(get(i + kReturnHandlerIndex))->value();
int handler_offset = HandlerOffsetField::decode(handler_field);
CatchPrediction prediction = HandlerPredictionField::decode(handler_field);
os << " " << std::setw(4) << pc_offset << " -> " << std::setw(4)
<< handler << "\n";
<< handler_offset << " (" << prediction << ")\n";
}
}

View File

@ -5159,6 +5159,11 @@ class DeoptimizationOutputData: public FixedArray {
// [ return-address-offset , handler-offset ]
class HandlerTable : public FixedArray {
public:
// Conservative prediction whether a given handler will locally catch an
// exception or cause a re-throw to outside the code boundary. Since this is
// undecidable it is merely an approximation (e.g. useful for debugger).
enum CatchPrediction { UNCAUGHT, CAUGHT };
// Accessors for handler table based on ranges.
void SetRangeStart(int index, int value) {
set(index * kRangeEntrySize + kRangeStartIndex, Smi::FromInt(value));
@ -5177,7 +5182,9 @@ class HandlerTable : public FixedArray {
void SetReturnOffset(int index, int value) {
set(index * kReturnEntrySize + kReturnOffsetIndex, Smi::FromInt(value));
}
void SetReturnHandler(int index, int value) {
void SetReturnHandler(int index, int offset, CatchPrediction prediction) {
int value = HandlerOffsetField::encode(offset) |
HandlerPredictionField::encode(prediction);
set(index * kReturnEntrySize + kReturnHandlerIndex, Smi::FromInt(value));
}
@ -5185,7 +5192,7 @@ class HandlerTable : public FixedArray {
int LookupRange(int pc_offset, int* stack_depth);
// Lookup handler in a table based on return addresses.
int LookupReturn(int pc_offset);
int LookupReturn(int pc_offset, CatchPrediction* prediction);
// Returns the required length of the underlying fixed array.
static int LengthForRange(int entries) { return entries * kRangeEntrySize; }
@ -5210,6 +5217,10 @@ class HandlerTable : public FixedArray {
static const int kReturnOffsetIndex = 0;
static const int kReturnHandlerIndex = 1;
static const int kReturnEntrySize = 2;
// Encoding of the {handler} field.
class HandlerPredictionField : public BitField<CatchPrediction, 0, 1> {};
class HandlerOffsetField : public BitField<int, 1, 30> {};
};

View File

@ -52,7 +52,6 @@ const SharedOperator kSharedOperators[] = {
SHARED(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(IfException, Operator::kKontrol, 0, 0, 1, 1, 0, 1),
SHARED(Throw, Operator::kKontrol, 1, 1, 1, 0, 0, 1),
SHARED(Return, Operator::kNoThrow, 1, 1, 1, 0, 0, 1),
SHARED(Terminate, Operator::kKontrol, 0, 1, 1, 0, 0, 1)
@ -178,7 +177,7 @@ const int32_t kInt32Values[] = {
2008792749, 2045320228, std::numeric_limits<int32_t>::max()};
const BranchHint kHints[] = {BranchHint::kNone, BranchHint::kTrue,
const BranchHint kBranchHints[] = {BranchHint::kNone, BranchHint::kTrue,
BranchHint::kFalse};
} // namespace
@ -201,7 +200,7 @@ TEST_F(CommonOperatorTest, End) {
TEST_F(CommonOperatorTest, Branch) {
TRACED_FOREACH(BranchHint, hint, kHints) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
const Operator* const op = common()->Branch(hint);
EXPECT_EQ(IrOpcode::kBranch, op->opcode());
EXPECT_EQ(Operator::kKontrol, op->properties());
@ -217,6 +216,24 @@ TEST_F(CommonOperatorTest, Branch) {
}
TEST_F(CommonOperatorTest, IfException) {
static const IfExceptionHint kIfExceptionHints[] = {
IfExceptionHint::kLocallyCaught, IfExceptionHint::kLocallyUncaught};
TRACED_FOREACH(IfExceptionHint, hint, kIfExceptionHints) {
const Operator* const op = common()->IfException(hint);
EXPECT_EQ(IrOpcode::kIfException, op->opcode());
EXPECT_EQ(Operator::kKontrol, op->properties());
EXPECT_EQ(0, op->ValueInputCount());
EXPECT_EQ(0, op->EffectInputCount());
EXPECT_EQ(1, op->ControlInputCount());
EXPECT_EQ(1, OperatorProperties::GetTotalInputCount(op));
EXPECT_EQ(1, op->ValueOutputCount());
EXPECT_EQ(0, op->EffectOutputCount());
EXPECT_EQ(1, op->ControlOutputCount());
}
}
TEST_F(CommonOperatorTest, Switch) {
TRACED_FOREACH(size_t, cases, kCases) {
const Operator* const op = common()->Switch(cases);
@ -256,7 +273,7 @@ TEST_F(CommonOperatorTest, Select) {
kMachInt32, kMachUint32, kMachInt64, kMachUint64,
kMachFloat32, kMachFloat64, kMachAnyTagged};
TRACED_FOREACH(MachineType, type, kTypes) {
TRACED_FOREACH(BranchHint, hint, kHints) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
const Operator* const op = common()->Select(type, hint);
EXPECT_EQ(IrOpcode::kSelect, op->opcode());
EXPECT_EQ(Operator::kPure, op->properties());

View File

@ -478,7 +478,8 @@ TEST_F(LoopPeelingTest, TwoExitLoopWithCall_nope) {
Node* call = graph()->NewNode(&kMockCall, b1.if_true);
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* if_exception = graph()->NewNode(common()->IfException(), call);
Node* if_exception = graph()->NewNode(
common()->IfException(IfExceptionHint::kLocallyUncaught), call);
loop->ReplaceInput(1, if_success);
Node* merge = graph()->NewNode(common()->Merge(2), b1.if_false, if_exception);

View File

@ -104,8 +104,9 @@ TEST_F(NodePropertiesTest, CollectControlProjections_Branch) {
TEST_F(NodePropertiesTest, CollectControlProjections_Call) {
Node* result[2];
CommonOperatorBuilder common(zone());
IfExceptionHint h = IfExceptionHint::kLocallyUncaught;
Node* call = Node::New(zone(), 1, &kMockCallOperator, 0, nullptr, false);
Node* if_ex = Node::New(zone(), 2, common.IfException(), 1, &call, false);
Node* if_ex = Node::New(zone(), 2, common.IfException(h), 1, &call, false);
Node* if_ok = Node::New(zone(), 3, common.IfSuccess(), 1, &call, false);
NodeProperties::CollectControlProjections(call, result, arraysize(result));
EXPECT_EQ(if_ok, result[0]);

View File

@ -1059,10 +1059,12 @@ TARGET_TEST_F(SchedulerTest, CallException) {
Node* p0 = graph()->NewNode(common()->Parameter(0), start);
Node* c1 = graph()->NewNode(&kMockCall, start);
Node* ok1 = graph()->NewNode(common()->IfSuccess(), c1);
Node* ex1 = graph()->NewNode(common()->IfException(), c1);
Node* ex1 = graph()->NewNode(
common()->IfException(IfExceptionHint::kLocallyUncaught), c1);
Node* c2 = graph()->NewNode(&kMockCall, ok1);
Node* ok2 = graph()->NewNode(common()->IfSuccess(), c2);
Node* ex2 = graph()->NewNode(common()->IfException(), c2);
Node* ex2 = graph()->NewNode(
common()->IfException(IfExceptionHint::kLocallyUncaught), c2);
Node* hdl = graph()->NewNode(common()->Merge(2), ex1, ex2);
Node* m = graph()->NewNode(common()->Merge(2), ok2, hdl);
Node* phi = graph()->NewNode(common()->Phi(kMachAnyTagged, 2), c2, p0, m);

View File

@ -59,7 +59,8 @@ TEST_F(TailCallOptimizationTest, CallCodeObject1) {
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* if_exception = graph()->NewNode(common()->IfException(), call);
Node* if_exception = graph()->NewNode(
common()->IfException(IfExceptionHint::kLocallyUncaught), call);
Node* ret = graph()->NewNode(common()->Return(), call, call, if_success);
Node* end = graph()->NewNode(common()->End(1), if_exception);
graph()->SetEnd(end);
@ -124,7 +125,8 @@ TEST_F(TailCallOptimizationTest, CallJSFunction1) {
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* if_exception = graph()->NewNode(common()->IfException(), call);
Node* if_exception = graph()->NewNode(
common()->IfException(IfExceptionHint::kLocallyUncaught), call);
Node* ret = graph()->NewNode(common()->Return(), call, call, if_success);
Node* end = graph()->NewNode(common()->End(1), if_exception);
graph()->SetEnd(end);