[vm/bytecode] Add yield point markers to source positions

Issue: https://github.com/dart-lang/sdk/issues/36427
Change-Id: I384161fd27b977e05796666c9a1a1e336d4d6440
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107572
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2019-06-28 17:13:51 +00:00
committed by commit-bot@chromium.org
parent d946b3acf1
commit f91089faaf
7 changed files with 140 additions and 52 deletions
+6
View File
@@ -79,6 +79,12 @@ class BytecodeAssembler {
}
}
void emitYieldPointSourcePosition() {
if (!isUnreachable) {
sourcePositions.addYieldPoint(offset, currentSourcePosition);
}
}
void _emitByte(int abyte) {
assert(_isUint8(abyte));
bytecode.add(abyte);
+2 -2
View File
@@ -760,7 +760,7 @@ class Code {
bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
bool get hasSourcePositions =>
sourcePositions != null && sourcePositions.mapping.isNotEmpty;
sourcePositions != null && sourcePositions.isNotEmpty;
bool get hasLocalVariables =>
localVariables != null && localVariables.isNotEmpty;
bool get hasNullableFields => nullableFields.isNotEmpty;
@@ -1056,7 +1056,7 @@ class ClosureCode {
bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
bool get hasSourcePositions =>
sourcePositions != null && sourcePositions.mapping.isNotEmpty;
sourcePositions != null && sourcePositions.isNotEmpty;
bool get hasLocalVariables =>
localVariables != null && localVariables.isNotEmpty;
+5 -1
View File
@@ -1485,7 +1485,7 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
}
SourcePositions finalizeSourcePositions() {
if (asm.sourcePositions.mapping.isEmpty) {
if (asm.sourcePositions.isEmpty) {
return null;
}
bytecodeComponent.sourcePositions.add(asm.sourcePositions);
@@ -3692,6 +3692,10 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
return;
}
if (options.emitSourcePositions) {
asm.emitYieldPointSourcePosition();
}
// 0 is reserved for normal entry, yield points are counted from 1.
final int yieldIndex = yieldPoints.length + 1;
final Label continuationLabel = new Label(allowsBackwardJumps: true);
+42 -9
View File
@@ -15,7 +15,10 @@ import 'bytecode_serialization.dart'
/// Maintains mapping between bytecode instructions and source positions.
class SourcePositions {
final Map<int, int> mapping = <int, int>{}; // PC -> fileOffset
// Special value of fileOffset which marks yield point.
static const yieldPointMarker = -2;
final List<int> _positions = <int>[]; // Pairs (PC, fileOffset).
int _lastPc = 0;
int _lastOffset = 0;
@@ -25,20 +28,37 @@ class SourcePositions {
assert(pc > _lastPc);
assert(fileOffset >= 0);
if (fileOffset != _lastOffset) {
mapping[pc] = fileOffset;
_positions.add(pc);
_positions.add(fileOffset);
_lastPc = pc;
_lastOffset = fileOffset;
}
}
void addYieldPoint(int pc, int fileOffset) {
assert(pc > _lastPc);
assert(fileOffset >= 0);
_positions.add(pc);
_positions.add(yieldPointMarker);
_positions.add(pc);
_positions.add(fileOffset);
_lastPc = pc;
_lastOffset = fileOffset;
}
bool get isEmpty => _positions.isEmpty;
bool get isNotEmpty => !isEmpty;
void write(BufferedWriter writer) {
writer.writePackedUInt30(mapping.length);
writer.writePackedUInt30(_positions.length ~/ 2);
final encodePC = new PackedUInt30DeltaEncoder();
final encodeOffset = new SLEB128DeltaEncoder();
mapping.forEach((int pc, int fileOffset) {
for (int i = 0; i < _positions.length; i += 2) {
final int pc = _positions[i];
final int fileOffset = _positions[i + 1];
encodePC.write(writer, pc);
encodeOffset.write(writer, fileOffset);
});
}
}
SourcePositions.read(BufferedReader reader) {
@@ -48,16 +68,29 @@ class SourcePositions {
for (int i = 0; i < length; ++i) {
int pc = decodePC.read(reader);
int fileOffset = decodeOffset.read(reader);
add(pc, fileOffset);
_positions.add(pc);
_positions.add(fileOffset);
}
}
@override
String toString() => mapping.toString();
String toString() => _positions.toString();
Map<int, String> getBytecodeAnnotations() {
return mapping.map((int pc, int fileOffset) =>
new MapEntry(pc, 'source position $fileOffset'));
final map = <int, String>{};
for (int i = 0; i < _positions.length; i += 2) {
final int pc = _positions[i];
final int fileOffset = _positions[i + 1];
final entry = (fileOffset == yieldPointMarker)
? 'yield point'
: 'source position $fileOffset';
if (map[pc] == null) {
map[pc] = entry;
} else {
map[pc] = "${map[pc]}; $entry";
}
}
return map;
}
}
@@ -331,6 +331,10 @@ class BytecodeReader : public AllStatic {
class BytecodeSourcePositionsIterator : ValueObject {
public:
// This constant should match corresponding constant in class SourcePositions
// (pkg/vm/lib/bytecode/source_positions.dart).
static const intptr_t kYieldPointMarker = -2;
BytecodeSourcePositionsIterator(Zone* zone, const Bytecode& bytecode)
: reader_(ExternalTypedData::Handle(zone, bytecode.GetBinary(zone))) {
if (bytecode.HasSourcePositions()) {
@@ -347,6 +351,12 @@ class BytecodeSourcePositionsIterator : ValueObject {
--pairs_remaining_;
cur_bci_ += reader_.ReadUInt();
cur_token_pos_ += reader_.ReadSLEB128();
is_yield_point_ = false;
if (cur_token_pos_ == kYieldPointMarker) {
const bool result = MoveNext();
is_yield_point_ = true;
return result;
}
return true;
}
@@ -354,11 +364,14 @@ class BytecodeSourcePositionsIterator : ValueObject {
TokenPosition TokenPos() const { return TokenPosition(cur_token_pos_); }
bool IsYieldPoint() const { return is_yield_point_; }
private:
Reader reader_;
intptr_t pairs_remaining_ = 0;
intptr_t cur_bci_ = 0;
intptr_t cur_token_pos_ = 0;
bool is_yield_point_ = false;
};
#if !defined(PRODUCT)
+71 -40
View File
@@ -948,23 +948,7 @@ bool ActivationFrame::HandlesException(const Instance& exc_obj) {
return false;
}
void ActivationFrame::ExtractTokenPositionFromAsyncClosure() {
// Attempt to determine the token pos and try index from the async closure.
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Script& script = Script::Handle(zone, function().script());
ASSERT(function_.IsAsyncGenClosure() || function_.IsAsyncClosure());
// This should only be called on frames that aren't active on the stack.
ASSERT(fp() == 0);
ASSERT(script.kind() == RawScript::kKernelTag);
const Array& await_to_token_map =
Array::Handle(zone, script.yield_positions());
if (await_to_token_map.IsNull()) {
// No mapping.
return;
}
intptr_t ActivationFrame::GetAwaitJumpVariable() {
GetVarDescriptors();
intptr_t var_desc_len = var_descriptors_.Length();
intptr_t await_jump_var = -1;
@@ -980,6 +964,59 @@ void ActivationFrame::ExtractTokenPositionFromAsyncClosure() {
await_jump_var = Smi::Cast(await_jump_index).Value();
}
}
return await_jump_var;
}
void ActivationFrame::ExtractTokenPositionFromAsyncClosure() {
// Attempt to determine the token pos and try index from the async closure.
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
const Script& script = Script::Handle(zone, function().script());
ASSERT(function_.IsAsyncGenClosure() || function_.IsAsyncClosure());
// This should only be called on frames that aren't active on the stack.
ASSERT(fp() == 0);
if (function_.is_declared_in_bytecode()) {
#if !defined(DART_PRECOMPILED_RUNTIME)
const auto& bytecode = Bytecode::Handle(zone, function_.bytecode());
if (!bytecode.HasSourcePositions()) {
return;
}
const intptr_t await_jump_var = GetAwaitJumpVariable();
if (await_jump_var < 0) {
return;
}
// Yield points are counted from 1 (0 is reserved for normal entry).
intptr_t yield_point_index = 1;
kernel::BytecodeSourcePositionsIterator iter(zone, bytecode);
while (iter.MoveNext()) {
if (iter.IsYieldPoint()) {
if (yield_point_index == await_jump_var) {
token_pos_ = iter.TokenPos();
token_pos_initialized_ = true;
try_index_ = bytecode.GetTryIndexAtPc(bytecode.PayloadStart() +
iter.PcOffset());
return;
}
++yield_point_index;
}
}
return;
#else
UNREACHABLE();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
ASSERT(!IsInterpreted());
ASSERT(script.kind() == RawScript::kKernelTag);
const Array& await_to_token_map =
Array::Handle(zone, script.yield_positions());
if (await_to_token_map.IsNull()) {
// No mapping.
return;
}
const intptr_t await_jump_var = GetAwaitJumpVariable();
if (await_jump_var < 0) {
return;
}
@@ -1011,29 +1048,6 @@ void ActivationFrame::ExtractTokenPositionFromAsyncClosure() {
ASSERT(token_pos.IsSmi());
token_pos_ = TokenPosition(Smi::Cast(token_pos).Value());
token_pos_initialized_ = true;
if (IsInterpreted()) {
#if !defined(DART_PRECOMPILED_RUNTIME)
// In order to determine the try index, we need to map the token position
// to a pc offset, and then a pc offset to the try index.
// TODO(regis): Should we set the token position fields in pc descriptors?
uword pc_offset = kUwordMax;
kernel::BytecodeSourcePositionsIterator iter(zone, bytecode());
while (iter.MoveNext()) {
// PcOffsets are monotonic in source positions, so we get the lowest one.
if (iter.TokenPos() == token_pos_) {
pc_offset = iter.PcOffset();
break;
}
}
if (pc_offset < kUwordMax) {
try_index_ =
bytecode().GetTryIndexAtPc(bytecode().PayloadStart() + pc_offset);
}
#else
UNREACHABLE();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
return;
}
GetPcDescriptors();
PcDescriptors::Iterator iter(pc_desc_, RawPcDescriptors::kAnyKind);
while (iter.MoveNext()) {
@@ -4170,6 +4184,23 @@ bool Debugger::IsAtAsyncJump(ActivationFrame* top_frame) {
if (!closure_or_null.IsNull()) {
ASSERT(closure_or_null.IsInstance());
ASSERT(Instance::Cast(closure_or_null).IsClosure());
if (top_frame->function().is_declared_in_bytecode()) {
#if !defined(DART_PRECOMPILED_RUNTIME)
const auto& bytecode =
Bytecode::Handle(zone, top_frame->function().bytecode());
const TokenPosition token_pos = top_frame->TokenPos();
kernel::BytecodeSourcePositionsIterator iter(zone, bytecode);
while (iter.MoveNext()) {
if (iter.IsYieldPoint() && (iter.TokenPos() == token_pos)) {
return true;
}
}
return false;
#else
UNREACHABLE();
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
ASSERT(!top_frame->IsInterpreted());
const Script& script = Script::Handle(zone, top_frame->SourceScript());
ASSERT(script.kind() == RawScript::kKernelTag);
// Are we at a yield point (previous await)?
+1
View File
@@ -388,6 +388,7 @@ class ActivationFrame : public ZoneAllocated {
RawObject* GetAsyncStreamControllerStream();
RawObject* GetAsyncCompleterAwaiter(const Object& completer);
RawObject* GetAsyncCompleter();
intptr_t GetAwaitJumpVariable();
void ExtractTokenPositionFromAsyncClosure();
bool IsAsyncMachinery() const;