Files
sdk/pkg/vm/lib/bytecode/local_variable_table.dart
T
Alexander Markov 631e547c2f [vm/bytecode] Setup initial scope before the first CheckStack
Sometimes we need to stop at the first CheckStack and scan call stack in
the debugger, so debugger needs to know initial context level on function
entry, even before setting up initial context.

This change moves the first scope declaration before the first CheckStack.
Parameters which are not captured are also declared in the initial scope.
After creating an initial context we may add the 2nd scope, along with
captured parameters.

Fixes flaky crashes due to context level mismatch in
service/async_generator_breakpoint_test/1 with bytecode in debug mode.

Change-Id: I11ce1407ee58954b167e51a25302272b05365386
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/117296
Reviewed-by: Régis Crelier <regis@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2019-09-16 21:21:54 +00:00

243 lines
7.0 KiB
Dart

// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library vm.bytecode.local_variable_table;
import 'bytecode_serialization.dart';
enum LocalVariableEntryKind {
invalid,
scope,
variableDeclaration,
contextVariable,
}
abstract class LocalVariableEntry {
static const KindMask = 0x0F;
static const IsCapturedFlag = 1 << 4;
LocalVariableEntryKind get kind;
int get flags => 0;
final int startPC;
void writeContents(BufferedWriter writer);
LocalVariableEntry(this.startPC);
}
class Scope extends LocalVariableEntry {
int endPC;
int contextLevel;
int position;
int endPosition;
final List<VariableDeclaration> variables = <VariableDeclaration>[];
Scope(int startPC, this.endPC, this.contextLevel, this.position,
this.endPosition)
: super(startPC);
@override
LocalVariableEntryKind get kind => LocalVariableEntryKind.scope;
@override
void writeContents(BufferedWriter writer) {
if (endPC == null) {
throw '$this is not closed';
}
writer.writePackedUInt30(endPC - startPC);
writer.writeSLEB128(contextLevel);
writer.writePackedUInt30(position + 1);
writer.writePackedUInt30(endPosition + 1);
}
Scope.readContents(BufferedReader reader, int startPC) : super(startPC) {
endPC = startPC + reader.readPackedUInt30();
contextLevel = reader.readSLEB128();
position = reader.readPackedUInt30() - 1;
endPosition = reader.readPackedUInt30() - 1;
}
@override
String toString() =>
'scope (pc $startPC-$endPC pos $position-$endPosition context-level $contextLevel)';
}
class VariableDeclaration extends LocalVariableEntry {
bool isCaptured;
int index;
int name;
int type;
int position;
int initializedPosition;
VariableDeclaration(int startPC, this.isCaptured, this.index, this.name,
this.type, this.position, this.initializedPosition)
: super(startPC);
@override
LocalVariableEntryKind get kind => LocalVariableEntryKind.variableDeclaration;
@override
int get flags => (isCaptured ? LocalVariableEntry.IsCapturedFlag : 0);
@override
void writeContents(BufferedWriter writer) {
writer.writeSLEB128(index);
writer.writePackedUInt30(name);
writer.writePackedUInt30(type);
writer.writePackedUInt30(position + 1);
writer.writePackedUInt30(initializedPosition + 1);
}
VariableDeclaration.readContents(
BufferedReader reader, int startPC, int flags)
: super(startPC) {
isCaptured = (flags & LocalVariableEntry.IsCapturedFlag) != 0;
index = reader.readSLEB128();
name = reader.readPackedUInt30();
type = reader.readPackedUInt30();
position = reader.readPackedUInt30() - 1;
initializedPosition = reader.readPackedUInt30() - 1;
}
@override
String toString() =>
'variable $index${isCaptured ? ' (captured)' : ''} pc $startPC, name CP#$name, type CP#$type, pos $position, init-pos $initializedPosition';
}
class ContextVariable extends LocalVariableEntry {
int index;
ContextVariable(int startPC, this.index) : super(startPC);
@override
LocalVariableEntryKind get kind => LocalVariableEntryKind.contextVariable;
@override
void writeContents(BufferedWriter writer) {
writer.writeSLEB128(index);
}
ContextVariable.readContents(BufferedReader reader, int startPC)
: super(startPC) {
index = reader.readSLEB128();
}
@override
String toString() => 'context variable $index';
}
/// Keeps information about declared local variables.
class LocalVariableTable {
final scopes = <Scope>[];
final activeScopes = <Scope>[];
ContextVariable contextVariable;
LocalVariableTable();
void enterScope(int pc, int contextLevel, int position) {
final scope = new Scope(pc, null, contextLevel, position, null);
activeScopes.add(scope);
scopes.add(scope);
}
void declareVariable(int pc, bool isCaptured, int index, int nameCpIndex,
int typeCpIndex, int position, int initializedPosition) {
final variable = new VariableDeclaration(pc, isCaptured, index, nameCpIndex,
typeCpIndex, position, initializedPosition);
activeScopes.last.variables.add(variable);
}
void leaveScope(int pc, int endPosition) {
final scope = activeScopes.removeLast();
scope.endPC = pc;
scope.endPosition = endPosition;
if (scope.variables.isEmpty &&
activeScopes.isNotEmpty &&
scope.contextLevel == activeScopes.last.contextLevel) {
scopes.remove(scope);
}
}
void leaveAllScopes(int pc, int endPosition) {
while (activeScopes.isNotEmpty) {
leaveScope(pc, endPosition);
}
}
void recordContextVariable(int pc, int index) {
assert(contextVariable == null);
contextVariable = new ContextVariable(pc, index);
}
bool get isEmpty => scopes.isEmpty && contextVariable == null;
bool get isNotEmpty => !isEmpty;
bool get hasActiveScopes => activeScopes.isNotEmpty;
List<LocalVariableEntry> getEntries() {
final entries = <LocalVariableEntry>[];
if (contextVariable != null) {
entries.add(contextVariable);
}
for (Scope scope in scopes) {
entries.add(scope);
entries.addAll(scope.variables);
}
return entries;
}
void write(BufferedWriter writer) {
final entries = getEntries();
writer.writePackedUInt30(entries.length);
final encodeStartPC = new SLEB128DeltaEncoder();
for (var entry in entries) {
writer.writeByte(entry.kind.index | entry.flags);
encodeStartPC.write(writer, entry.startPC);
entry.writeContents(writer);
}
}
LocalVariableTable.read(BufferedReader reader) {
final int numEntries = reader.readPackedUInt30();
final decodeStartPC = new SLEB128DeltaDecoder();
Scope scope;
for (int i = 0; i < numEntries; ++i) {
final int kindAndFlags = reader.readByte();
final LocalVariableEntryKind kind = LocalVariableEntryKind
.values[kindAndFlags & LocalVariableEntry.KindMask];
final int flags = kindAndFlags & ~LocalVariableEntry.KindMask;
final int startPC = decodeStartPC.read(reader);
switch (kind) {
case LocalVariableEntryKind.scope:
scope = new Scope.readContents(reader, startPC);
scopes.add(scope);
break;
case LocalVariableEntryKind.variableDeclaration:
scope.variables.add(
new VariableDeclaration.readContents(reader, startPC, flags));
break;
case LocalVariableEntryKind.contextVariable:
contextVariable = new ContextVariable.readContents(reader, startPC);
break;
default:
throw 'Unexpected entry kind ${kind}';
}
}
}
Map<int, String> getBytecodeAnnotations() {
final map = <int, String>{};
for (var entry in getEntries()) {
final pc = entry.startPC;
if (map[pc] == null) {
map[pc] = entry.toString();
} else {
map[pc] = "${map[pc]}; $entry";
}
}
return map;
}
}