[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:
parent
524abd76b7
commit
1515ddd8f1
@ -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 {
|
||||
|
10
src/frames.h
10
src/frames.h
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
163
src/objects.h
163
src/objects.h
@ -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();
|
||||
|
||||
|
72
test/mjsunit/es6/debug-promises/throw-finally-caught-all.js
Normal file
72
test/mjsunit/es6/debug-promises/throw-finally-caught-all.js
Normal 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);
|
Loading…
Reference in New Issue
Block a user