// 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));
}
// 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));
}
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:
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),
}
// 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);
}
}
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.
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);
}
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) {
};
struct HandlerInfo {
+ bool caught_locally;
Label* handler;
int pc_offset;
};
}
+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();
}
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) \
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()
}
+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( // --
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,
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();
// 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));
}
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;
// 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));
}
// 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));
}
// 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));
}
// 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));
}
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);
}
// 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;
}
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";
}
}
// [ 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));
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));
}
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; }
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> {};
};
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)
2008792749, 2045320228, std::numeric_limits<int32_t>::max()};
-const BranchHint kHints[] = {BranchHint::kNone, BranchHint::kTrue,
- BranchHint::kFalse};
+const BranchHint kBranchHints[] = {BranchHint::kNone, BranchHint::kTrue,
+ BranchHint::kFalse};
} // namespace
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());
}
+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);
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());
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);
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]);
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);
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);
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);