[debugger] use handler table on unoptimized code for exception prediction.

R=mstarzinger@chromium.org

Review-Url: https://codereview.chromium.org/2197183002
Cr-Commit-Position: refs/heads/master@{#38247}
This commit is contained in:
yangguo 2016-08-02 06:46:54 -07:00 committed by Commit bot
parent 524abd76b7
commit 1515ddd8f1
7 changed files with 228 additions and 106 deletions

View File

@ -982,12 +982,10 @@ int JavaScriptFrame::LookupExceptionHandlerInTable(
int* stack_depth, HandlerTable::CatchPrediction* prediction) {
Code* code = LookupCode();
DCHECK(!code->is_optimized_code());
HandlerTable* table = HandlerTable::cast(code->handler_table());
int pc_offset = static_cast<int>(pc() - code->entry());
return table->LookupRange(pc_offset, stack_depth, prediction);
return code->LookupRangeInHandlerTable(pc_offset, stack_depth, prediction);
}
void JavaScriptFrame::PrintFunctionAndOffset(JSFunction* function, Code* code,
Address pc, FILE* file,
bool print_line_number) {
@ -1236,11 +1234,15 @@ void OptimizedFrame::Summarize(List<FrameSummary>* frames,
int OptimizedFrame::LookupExceptionHandlerInTable(
int* stack_slots, HandlerTable::CatchPrediction* prediction) {
// We cannot perform exception prediction on optimized code. Instead, we need
// to use FrameSummary to find the corresponding code offset in unoptimized
// code to perform prediction there.
DCHECK_NULL(prediction);
Code* code = LookupCode();
HandlerTable* table = HandlerTable::cast(code->handler_table());
int pc_offset = static_cast<int>(pc() - code->entry());
if (stack_slots) *stack_slots = code->stack_slots();
return table->LookupReturn(pc_offset, prediction);
return table->LookupReturn(pc_offset);
}
@ -1337,9 +1339,8 @@ Object* OptimizedFrame::StackSlotAt(int index) const {
int InterpretedFrame::LookupExceptionHandlerInTable(
int* context_register, HandlerTable::CatchPrediction* prediction) {
BytecodeArray* bytecode = function()->shared()->bytecode_array();
HandlerTable* table = HandlerTable::cast(bytecode->handler_table());
int pc_offset = GetBytecodeOffset() + 1; // Point after current bytecode.
return table->LookupRange(pc_offset, context_register, prediction);
return bytecode->LookupRangeInHandlerTable(GetBytecodeOffset(),
context_register, prediction);
}
int InterpretedFrame::GetBytecodeOffset() const {

View File

@ -710,11 +710,11 @@ class FrameSummary BASE_EMBEDDED {
static FrameSummary GetFirst(JavaScriptFrame* frame);
Handle<Object> receiver() { return receiver_; }
Handle<JSFunction> function() { return function_; }
Handle<AbstractCode> abstract_code() { return abstract_code_; }
int code_offset() { return code_offset_; }
bool is_constructor() { return is_constructor_; }
Handle<Object> receiver() const { return receiver_; }
Handle<JSFunction> function() const { return function_; }
Handle<AbstractCode> abstract_code() const { return abstract_code_; }
int code_offset() const { return code_offset_; }
bool is_constructor() const { return is_constructor_; }
void Print();

View File

@ -1296,6 +1296,31 @@ Object* Isolate::UnwindAndFindHandler() {
return exception;
}
namespace {
HandlerTable::CatchPrediction PredictException(JavaScriptFrame* frame) {
HandlerTable::CatchPrediction prediction;
if (frame->is_optimized()) {
if (frame->LookupExceptionHandlerInTable(nullptr, nullptr) > 0) {
// This optimized frame will catch. It's handler table does not include
// exception prediction, and we need to use the corresponding handler
// tables on the unoptimized code objects.
List<FrameSummary> summaries;
frame->Summarize(&summaries);
for (const FrameSummary& summary : summaries) {
int code_offset = summary.code_offset();
int index = summary.abstract_code()->LookupRangeInHandlerTable(
code_offset, nullptr, &prediction);
if (index <= 0) continue;
if (prediction == HandlerTable::UNCAUGHT) continue;
return prediction;
}
}
} else if (frame->LookupExceptionHandlerInTable(nullptr, &prediction) > 0) {
return prediction;
}
return HandlerTable::UNCAUGHT;
}
} // anonymous namespace
Isolate::CatchType Isolate::PredictExceptionCatcher() {
Address external_handler = thread_local_top()->try_catch_handler_address();
@ -1314,11 +1339,8 @@ Isolate::CatchType Isolate::PredictExceptionCatcher() {
// For JavaScript frames we perform a lookup in the handler table.
if (frame->is_java_script()) {
JavaScriptFrame* js_frame = static_cast<JavaScriptFrame*>(frame);
HandlerTable::CatchPrediction prediction;
if (js_frame->LookupExceptionHandlerInTable(nullptr, &prediction) > 0) {
// We are conservative with our prediction: try-finally is considered
// to always rethrow, to meet the expectation of the debugger.
if (prediction != HandlerTable::UNCAUGHT) return CAUGHT_BY_JAVASCRIPT;
if (PredictException(js_frame) != HandlerTable::UNCAUGHT) {
return CAUGHT_BY_JAVASCRIPT;
}
}
@ -1730,15 +1752,13 @@ Handle<Object> Isolate::GetPromiseOnStackOnThrow() {
// Find the top-most try-catch or try-finally handler.
if (PredictExceptionCatcher() != CAUGHT_BY_JAVASCRIPT) return undefined;
for (JavaScriptFrameIterator it(this); !it.done(); it.Advance()) {
JavaScriptFrame* frame = it.frame();
HandlerTable::CatchPrediction prediction;
if (frame->LookupExceptionHandlerInTable(nullptr, &prediction) > 0) {
// Throwing inside a Promise only leads to a reject if not caught by an
// inner try-catch or try-finally.
if (prediction == HandlerTable::PROMISE) {
switch (PredictException(it.frame())) {
case HandlerTable::UNCAUGHT:
break;
case HandlerTable::CAUGHT:
return undefined;
case HandlerTable::PROMISE:
return tltop->promise_on_stack_->promise();
}
return undefined;
}
}
return undefined;

View File

@ -5335,6 +5335,16 @@ ByteArray* AbstractCode::source_position_table() {
}
}
int AbstractCode::LookupRangeInHandlerTable(
int code_offset, int* data, HandlerTable::CatchPrediction* prediction) {
if (IsCode()) {
return GetCode()->LookupRangeInHandlerTable(code_offset, data, prediction);
} else {
return GetBytecodeArray()->LookupRangeInHandlerTable(code_offset, data,
prediction);
}
}
int AbstractCode::SizeIncludingMetadata() {
if (IsCode()) {
return GetCode()->SizeIncludingMetadata();

View File

@ -10229,14 +10229,11 @@ int HandlerTable::LookupRange(int pc_offset, int* data_out,
// TODO(turbofan): Make sure table is sorted and use binary search.
int HandlerTable::LookupReturn(int pc_offset, CatchPrediction* prediction_out) {
int HandlerTable::LookupReturn(int pc_offset) {
for (int i = 0; i < length(); i += kReturnEntrySize) {
int return_offset = Smi::cast(get(i + kReturnOffsetIndex))->value();
int handler_field = Smi::cast(get(i + kReturnHandlerIndex))->value();
if (pc_offset == return_offset) {
if (prediction_out) {
*prediction_out = HandlerPredictionField::decode(handler_field);
}
return HandlerOffsetField::decode(handler_field);
}
}
@ -13723,6 +13720,12 @@ uint32_t Code::TranslateAstIdToPcOffset(BailoutId ast_id) {
return 0;
}
int Code::LookupRangeInHandlerTable(int code_offset, int* data,
HandlerTable::CatchPrediction* prediction) {
DCHECK(!is_optimized_code());
HandlerTable* table = HandlerTable::cast(handler_table());
return table->LookupRange(code_offset, data, prediction);
}
void Code::MakeCodeAgeSequenceYoung(byte* sequence, Isolate* isolate) {
PatchPlatformCodeAge(isolate, sequence, kNoAgeCodeAge, NO_MARKING_PARITY);
@ -14477,6 +14480,13 @@ void BytecodeArray::CopyBytecodesTo(BytecodeArray* to) {
from->length());
}
int BytecodeArray::LookupRangeInHandlerTable(
int code_offset, int* data, HandlerTable::CatchPrediction* prediction) {
HandlerTable* table = HandlerTable::cast(handler_table());
code_offset++; // Point after current bytecode.
return table->LookupRange(code_offset, data, prediction);
}
// static
void JSArray::Initialize(Handle<JSArray> array, int capacity, int length) {
DCHECK(capacity >= 0);

View File

@ -4448,6 +4448,82 @@ class NormalizedMapCache: public FixedArray {
void set(int index, Object* value);
};
// HandlerTable is a fixed array containing entries for exception handlers in
// the code object it is associated with. The tables comes in two flavors:
// 1) Based on ranges: Used for unoptimized code. Contains one entry per
// exception handler and a range representing the try-block covered by that
// handler. Layout looks as follows:
// [ range-start , range-end , handler-offset , handler-data ]
// 2) Based on return addresses: Used for turbofanned code. Contains one entry
// per call-site that could throw an exception. Layout looks as follows:
// [ 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, // the handler will (likely) rethrow the exception.
CAUGHT, // the exception will be caught by the handler.
PROMISE // the exception will be caught and cause a promise rejection.
};
// Getters for handler table based on ranges.
inline int GetRangeStart(int index) const;
inline int GetRangeEnd(int index) const;
inline int GetRangeHandler(int index) const;
inline int GetRangeData(int index) const;
// Setters for handler table based on ranges.
inline void SetRangeStart(int index, int value);
inline void SetRangeEnd(int index, int value);
inline void SetRangeHandler(int index, int offset, CatchPrediction pred);
inline void SetRangeData(int index, int value);
// Setters for handler table based on return addresses.
inline void SetReturnOffset(int index, int value);
inline void SetReturnHandler(int index, int offset, CatchPrediction pred);
// Lookup handler in a table based on ranges.
int LookupRange(int pc_offset, int* data, CatchPrediction* prediction);
// Lookup handler in a table based on return addresses.
int LookupReturn(int pc_offset);
// Returns the conservative catch predication.
inline CatchPrediction GetRangePrediction(int index) const;
// Returns the number of entries in the table.
inline int NumberOfRangeEntries() const;
// Returns the required length of the underlying fixed array.
static int LengthForRange(int entries) { return entries * kRangeEntrySize; }
static int LengthForReturn(int entries) { return entries * kReturnEntrySize; }
DECLARE_CAST(HandlerTable)
#ifdef ENABLE_DISASSEMBLER
void HandlerTableRangePrint(std::ostream& os); // NOLINT
void HandlerTableReturnPrint(std::ostream& os); // NOLINT
#endif
private:
// Layout description for handler table based on ranges.
static const int kRangeStartIndex = 0;
static const int kRangeEndIndex = 1;
static const int kRangeHandlerIndex = 2;
static const int kRangeDataIndex = 3;
static const int kRangeEntrySize = 4;
// Layout description for handler table based on return addresses.
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, 2> {};
class HandlerOffsetField : public BitField<int, 2, 30> {};
};
// ByteArray represents fixed sized byte arrays. Used for the relocation info
// that is attached to code objects.
@ -4571,6 +4647,9 @@ class BytecodeArray : public FixedArrayBase {
void CopyBytecodesTo(BytecodeArray* to);
int LookupRangeInHandlerTable(int code_offset, int* data,
HandlerTable::CatchPrediction* prediction);
// Layout description.
static const int kConstantPoolOffset = FixedArrayBase::kHeaderSize;
static const int kHandlerTableOffset = kConstantPoolOffset + kPointerSize;
@ -4878,83 +4957,6 @@ class LiteralsArray : public FixedArray {
};
// HandlerTable is a fixed array containing entries for exception handlers in
// the code object it is associated with. The tables comes in two flavors:
// 1) Based on ranges: Used for unoptimized code. Contains one entry per
// exception handler and a range representing the try-block covered by that
// handler. Layout looks as follows:
// [ range-start , range-end , handler-offset , handler-data ]
// 2) Based on return addresses: Used for turbofanned code. Contains one entry
// per call-site that could throw an exception. Layout looks as follows:
// [ 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, // the handler will (likely) rethrow the exception.
CAUGHT, // the exception will be caught by the handler.
PROMISE // the exception will be caught and cause a promise rejection.
};
// Getters for handler table based on ranges.
inline int GetRangeStart(int index) const;
inline int GetRangeEnd(int index) const;
inline int GetRangeHandler(int index) const;
inline int GetRangeData(int index) const;
// Setters for handler table based on ranges.
inline void SetRangeStart(int index, int value);
inline void SetRangeEnd(int index, int value);
inline void SetRangeHandler(int index, int offset, CatchPrediction pred);
inline void SetRangeData(int index, int value);
// Setters for handler table based on return addresses.
inline void SetReturnOffset(int index, int value);
inline void SetReturnHandler(int index, int offset, CatchPrediction pred);
// Lookup handler in a table based on ranges.
int LookupRange(int pc_offset, int* data, CatchPrediction* prediction);
// Lookup handler in a table based on return addresses.
int LookupReturn(int pc_offset, CatchPrediction* prediction);
// Returns the conservative catch predication.
inline CatchPrediction GetRangePrediction(int index) const;
// Returns the number of entries in the table.
inline int NumberOfRangeEntries() const;
// Returns the required length of the underlying fixed array.
static int LengthForRange(int entries) { return entries * kRangeEntrySize; }
static int LengthForReturn(int entries) { return entries * kReturnEntrySize; }
DECLARE_CAST(HandlerTable)
#ifdef ENABLE_DISASSEMBLER
void HandlerTableRangePrint(std::ostream& os); // NOLINT
void HandlerTableReturnPrint(std::ostream& os); // NOLINT
#endif
private:
// Layout description for handler table based on ranges.
static const int kRangeStartIndex = 0;
static const int kRangeEndIndex = 1;
static const int kRangeHandlerIndex = 2;
static const int kRangeDataIndex = 3;
static const int kRangeEntrySize = 4;
// Layout description for handler table based on return addresses.
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, 2> {};
class HandlerOffsetField : public BitField<int, 2, 30> {};
};
class TemplateList : public FixedArray {
public:
static Handle<TemplateList> New(Isolate* isolate, int size);
@ -5341,6 +5343,9 @@ class Code: public HeapObject {
BailoutId TranslatePcOffsetToAstId(uint32_t pc_offset);
uint32_t TranslateAstIdToPcOffset(BailoutId ast_id);
int LookupRangeInHandlerTable(int code_offset, int* data,
HandlerTable::CatchPrediction* prediction);
#define DECLARE_CODE_AGE_ENUM(X) k##X##CodeAge,
enum Age {
kToBeExecutedOnceCodeAge = -3,
@ -5554,6 +5559,10 @@ class AbstractCode : public HeapObject {
// Return the source position table.
inline ByteArray* source_position_table();
// Return the exception handler table.
inline int LookupRangeInHandlerTable(
int code_offset, int* data, HandlerTable::CatchPrediction* prediction);
// Returns the size of instructions and the metadata.
inline int SizeIncludingMetadata();

View File

@ -0,0 +1,72 @@
// 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: --expose-debug-as debug --allow-natives-syntax --promise-extra
// Test debug events when we listen to all exceptions and
// there is a catch handler for the exception thrown in a Promise, first
// caught by a try-finally, and immediately rethrown.
// We expect a normal Exception debug event to be triggered.
Debug = debug.Debug;
var expected_events = 1;
var log = [];
var p = new Promise(function(resolve, reject) {
log.push("resolve");
resolve();
});
var q = p.chain(
function() {
log.push("throw");
try {
throw new Error("caught");
} finally {
}
});
q.catch(
function(e) {
assertEquals("caught", e.message);
});
function listener(event, exec_state, event_data, data) {
try {
if (event == Debug.DebugEvent.Exception) {
expected_events--;
assertTrue(expected_events >= 0);
assertEquals("caught", event_data.exception().message);
assertSame(q, event_data.promise());
assertFalse(event_data.uncaught());
}
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
Debug.setBreakOnException();
Debug.setListener(listener);
log.push("end main");
function testDone(iteration) {
function checkResult() {
try {
assertTrue(iteration < 10);
if (expected_events === 0) {
assertEquals(["resolve", "end main", "throw"], log);
} else {
testDone(iteration + 1);
}
} catch (e) {
%AbortJS(e + "\n" + e.stack);
}
}
%EnqueueMicrotask(checkResult);
}
testDone(0);