[vm,dynamic_modules] Add RecordCoverage instruction.

The RecordCoverage instruction has an A/E encoding. The A argument
is the type of coverage being recorded, whereas the E argument is
the logical index into the coverage array for updating whether that
source position has been hit.

Also adds new metadata to the bytecode component for the coverage
arrays associated with bytecode containing RecordCoverage instructions
and a new runtime entry for lazily allocate the coverage array for
an interpreted function when needed.

The type of coverage is encoded in the RecordCoverage instruction,
despite being redundant with the information in the coverage array, so that checking whether that type of coverage is currently enabled at
runtime doesn't require either accessing the coverage array (which may
be lazily allocated), forcing allocation of the coverage array just to
discover that type of coverage is currently disabled, or reading the
serialized bytecode component to avoid that forced allocation.

------

Other changes:

Source reporting now treats unexecuted interpreted functions when
not forcing compilation as if they were uncompiled native functions,
so that the source report from running the same code gives the same
result whether using the interpreter or the native compiler.

Bytecode closures are no longer skipped in source reports. Previously
any closure without a context scope was skipped, but bytecode closures
don't have those.

TEST=vm/cc/SourceReport_Coverage

Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-aot-dyn-linux-debug-x64-try,vm-aot-dyn-linux-product-x64-try
Change-Id: I7557e5dd4c98331c7ca2f5c867dd5f6d03e9d756
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/501520
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Tess Strickland
2026-05-19 04:27:39 -07:00
parent d2b5c1a81e
commit 52cfd29cbb
21 changed files with 1915 additions and 1437 deletions
+49 -3
View File
@@ -47,7 +47,7 @@ which reside in different sections such as libraries, classes, members, code, et
```
type BytecodeFile {
UInt32 magic = 0x44424333; // 'DBC3'
UInt32 formatVersion = 2;
UInt32 formatVersion = 3;
// Descriptors of the sections below.
// Each section has a fixed index in the descriptors array.
@@ -69,6 +69,7 @@ type BytecodeFile {
SourceFile[] sourceFiles;
LineStarts[] lineStarts;
LocalVariables[] localVariables;
RecordedCoverageArray[] recordedCoverage;
PackedObject[] annotations;
}
@@ -604,7 +605,8 @@ Code section contains bodies of members (including field initializers).
type Code {
UInt flags = (hasExceptionsTable, hasSourcePositions, hasNullableFields,
hasClosures, hasParameterFlags, hasForwardingStubTarget,
hasDefaultFunctionTypeArgs, hasLocalVariables)
hasDefaultFunctionTypeArgs, hasLocalVariables,
hasRecordedCoverage)
if hasParameterFlags
// For all parameters: (isCovariant, isCovariantByClass)
@@ -635,6 +637,11 @@ type Code {
// Offset of LocalVariables in localVariables section of BytecodeFile.
UInt localVariablesOffset;
if hasRecordedCoverage
// Offset of RecordedCoverageArray in recordedCoverage section of
// BytecodeFile.
Uint recordedCoverageOffset;
if hasNullableFields
List<PackedObject> nullableFields;
@@ -681,7 +688,8 @@ type ClosureDeclaration {
type ClosureCode {
UInt flags = (hasExceptionsTable, hasSourcePositions, hasLocalVariables,
capturesOnlyFinalNotLateVars, hasLocalFunctionId)
capturesOnlyFinalNotLateVars, hasLocalFunctionId,
hasRecordedCoverage)
if hasLocalFunctionId
UInt localFunctionId;
@@ -699,6 +707,11 @@ type ClosureCode {
if hasLocalVariables
// Offset of LocalVariables in localVariables section of BytecodeFile.
UInt localVariablesOffset;
if hasRecordedCoverage
// Offset of RecordedCoverageArray in recordedCoverage section of
// BytecodeFile.
UInt recordedCoverageOffset;
}
```
@@ -968,6 +981,28 @@ type ContextVariable extends LocalVariableEntry {
}
```
### Recorded coverage
```
type RecordedCoverageArray {
UInt numEntries;
// Unordered encoded list of entries.
RecordedCoverageEntry[numEntries] entries;
}
type RecordedCoverageEntry = {
// The index of the entry's type in the RecordedEntryType enum.
UInt type;
// Delta-encoded file offset.
SLEB128 fileOffset;
}
enum RecordedCoverageType = {
regular = 0,
branchTarget = 1;
}
```
## Bytecode instructions
### Execution state
@@ -1445,3 +1480,14 @@ Store object SP[0] into the element [D] of closure SP[-1].
No-op. Provides a unique PC offset to ensure an emitted source position is not
overwritten by a different source position emitted by the next instruction.
#### RecordCoverage A, E
Records coverage. [A] is the index in the RecordedCoverageType enum for the type
of coverage being recorded, and [E] is the index of the entry in
the RecordedCoverageArray.
The information in [A] is redundant, but allows the interpreter to check to see
if the isolate group is currently recorded that type of coverage without needing
either to iterate over the serialized RecordedCoverageArray or to index into the
bytecode's coverage array, which may be lazily allocated.
+19 -2
View File
@@ -10,7 +10,8 @@ import 'dbc.dart';
import 'exceptions.dart' show ExceptionsTable;
import 'local_variable_table.dart' show LocalVariableTable;
import 'options.dart';
import 'source_positions.dart' show SourcePositions;
import 'source_positions.dart'
show RecordedCoverageArray, RecordedCoverageType, SourcePositions;
class Label {
final bool allowsBackwardJumps;
@@ -60,13 +61,17 @@ class BytecodeAssembler {
final ExceptionsTable exceptionsTable = new ExceptionsTable();
final LocalVariableTable localVariableTable = new LocalVariableTable();
final SourcePositions sourcePositions = new SourcePositions();
final RecordedCoverageArray recordedCoverageArray =
new RecordedCoverageArray();
final bool _emitSourcePositions;
final bool _recordCoverage;
bool isUnreachable = false;
int currentSourcePosition = TreeNode.noOffset;
int currentSourcePositionFlags = 0;
BytecodeAssembler(BytecodeOptions options)
: _emitSourcePositions = options.emitSourcePositions;
: _emitSourcePositions = options.emitSourcePositions,
_recordCoverage = options.recordCoverage;
int get offset => _length;
@@ -834,4 +839,16 @@ class BytecodeAssembler {
void emitLoadRecordField(int rd) {
_emitInstructionD(Opcode.kLoadRecordField, rd);
}
@pragma('vm:prefer-inline')
void recordCoverage(RecordedCoverageType type, int fileOffset) {
if (!_recordCoverage) return;
final index = recordedCoverageArray.add(type, fileOffset);
_emitRecordCoverage(type.index, index);
}
@pragma('vm:prefer-inline')
void _emitRecordCoverage(int ra, int re) {
_emitInstructionAE(Opcode.kRecordCoverage, ra, re);
}
}
+59 -2
View File
@@ -52,7 +52,12 @@ import 'object_table.dart'
topLevelClassName;
import 'options.dart' show BytecodeOptions;
import 'recognized_methods.dart' show RecognizedMethods;
import 'source_positions.dart' show LineStarts, SourcePositions;
import 'source_positions.dart'
show
LineStarts,
RecordedCoverageArray,
RecordedCoverageType,
SourcePositions;
// This symbol is used as the name in assert assignable's to indicate it comes
// from an explicit 'as' check. This will cause the runtime to throw the right
@@ -1238,6 +1243,22 @@ class BytecodeGenerator extends RecursiveVisitor {
maxSourcePosition = math.max(maxSourcePosition, fileOffset);
}
void _recordCoverage(TreeNode? call) {
if (call == null) return;
final offset = call is AssertStatement
? call.conditionStartOffset
: call.fileOffset;
// If there is no source position, coverage cannot be recorded.
if (offset == TreeNode.noOffset) return;
asm.recordCoverage(RecordedCoverageType.regular, offset);
}
void _recordBranchTargetCoverage(TreeNode target) {
// If there is no source position, coverage cannot be recorded.
if (target.fileOffset == TreeNode.noOffset) return;
asm.recordCoverage(RecordedCoverageType.branchTarget, target.fileOffset);
}
void _generateNode(TreeNode? node) {
if (node == null) {
return;
@@ -1470,6 +1491,10 @@ class BytecodeGenerator extends RecursiveVisitor {
if (totalArgCount >= argumentsLimit) {
throw 'Too many arguments';
}
// Only record coverage for non-synthetic calls.
if ((asm.currentSourcePositionFlags & SourcePositions.syntheticFlag) == 0) {
_recordCoverage(node);
}
if (isUnchecked) {
asm.emitUncheckedDirectCall(cpIndex, totalArgCount);
} else {
@@ -2066,6 +2091,7 @@ class BytecodeGenerator extends RecursiveVisitor {
asm.exceptionsTable,
finalizeSourcePositions(),
finalizeLocalVariables(),
finalizeRecordedCoverage(),
nullableFields,
closures ?? const <ClosureDeclaration>[],
parameterFlags,
@@ -2123,6 +2149,14 @@ class BytecodeGenerator extends RecursiveVisitor {
return localVariables;
}
RecordedCoverageArray? finalizeRecordedCoverage() {
if (asm.recordedCoverageArray.isEmpty) {
return null;
}
bytecodeComponent.recordedCoverage.add(asm.recordedCoverageArray);
return asm.recordedCoverageArray;
}
void _genPrologue(TreeNode node, FunctionNode? function) {
if (locals.makesCopyOfParameters) {
final int numOptionalPositional =
@@ -2813,6 +2847,7 @@ class BytecodeGenerator extends RecursiveVisitor {
asm.exceptionsTable,
finalizeSourcePositions(),
finalizeLocalVariables(),
finalizeRecordedCoverage(),
capturesOnlyFinalNotLateVars,
node.id.toInt(),
);
@@ -3527,6 +3562,7 @@ class BytecodeGenerator extends RecursiveVisitor {
targetName,
argDesc,
);
_recordCoverage(node);
if (isDynamic) {
assert(!isUnchecked);
asm.emitDynamicCall(callCpIndex, totalArgCount);
@@ -3628,6 +3664,7 @@ class BytecodeGenerator extends RecursiveVisitor {
// Duplicate receiver (closure) for UncheckedClosureCall.
asm.emitPush(receiverTemp);
final argDescCpIndex = cp.addArgDescByArguments(args, hasReceiver: true);
_recordCoverage(node);
asm.emitUncheckedClosureCall(argDescCpIndex, totalArgCount);
return;
}
@@ -3670,6 +3707,7 @@ class BytecodeGenerator extends RecursiveVisitor {
// Duplicate receiver (closure) for UncheckedClosureCall.
_genLoadVar(node.variable);
final argDescCpIndex = cp.addArgDescByArguments(args, hasReceiver: true);
_recordCoverage(node);
asm.emitUncheckedClosureCall(argDescCpIndex, totalArgCount);
}
@@ -4254,7 +4292,16 @@ class BytecodeGenerator extends RecursiveVisitor {
final Label done = new Label();
asm.emitJumpIfNoAsserts(done);
_genConditionAndJumpIf(node.condition, true, done);
// To only introduce one RecordCoverage instruction for each assert,
// insert it between evaluating the condition and the jump.
final negated = _genCondition(node.condition);
_recordCoverage(node);
asm.emitSourcePosition();
if (negated) {
asm.emitJumpIfFalse(done);
} else {
asm.emitJumpIfTrue(done);
}
final fileUri = node.location!.file;
final source = node.enclosingComponent!.uriToSource[fileUri]!;
@@ -4356,6 +4403,7 @@ class BytecodeGenerator extends RecursiveVisitor {
asm.emitCheckStack(++currentLoopDepth);
_recordBranchTargetCoverage(node.body);
_generateNode(node.body);
_genConditionAndJumpIf(node.condition, true, join);
@@ -4406,6 +4454,7 @@ class BytecodeGenerator extends RecursiveVisitor {
_genConditionAndJumpIf(condition, false, done);
}
_recordBranchTargetCoverage(node.body);
_generateNode(node.body);
if (locals.currentContextSize > 0) {
@@ -4445,12 +4494,14 @@ class BytecodeGenerator extends RecursiveVisitor {
_genConditionAndJumpIf(node.condition, false, otherwisePart);
_recordBranchTargetCoverage(node.then);
_generateNode(node.then);
if (node.otherwise != null) {
final Label done = new Label();
asm.emitJump(done);
asm.bind(otherwisePart);
_recordBranchTargetCoverage(node.otherwise!);
_generateNode(node.otherwise);
asm.bind(done);
} else {
@@ -4563,6 +4614,7 @@ class BytecodeGenerator extends RecursiveVisitor {
final Label caseLabel = caseLabels[i];
asm.bind(caseLabel);
_recordBranchTargetCoverage(switchCase.body);
_generateNode(switchCase.body);
// Front-end issues a compile-time error if there is a fallthrough
@@ -4705,6 +4757,7 @@ class BytecodeGenerator extends RecursiveVisitor {
final tryCatches = this.tryCatches ??= <TryCatch, TryBlock>{};
tryCatches[node] = tryBlock; // Used by rethrow.
_recordBranchTargetCoverage(node.body);
_generateNode(node.body);
asm.emitJump(done);
@@ -4753,6 +4806,7 @@ class BytecodeGenerator extends RecursiveVisitor {
_genStoreVar(stackTraceVar);
}
_recordBranchTargetCoverage(catchClause.body);
_generateNode(catchClause.body);
_leaveScope();
@@ -4785,6 +4839,7 @@ class BytecodeGenerator extends RecursiveVisitor {
<TryFinally, List<FinallyBlock>>{};
finallyBlocks[node] = <FinallyBlock>[];
_recordBranchTargetCoverage(node.body);
_generateNode(node.body);
if (!asm.isUnreachable) {
@@ -4807,6 +4862,7 @@ class BytecodeGenerator extends RecursiveVisitor {
for (var finallyBlock in finallyBlocks[node]!) {
asm.bind(finallyBlock.entry);
_restoreContextForTryBlock(node);
_recordBranchTargetCoverage(node.finalizer);
_generateNode(node.finalizer);
finallyBlock.generateContinuation();
}
@@ -4903,6 +4959,7 @@ class BytecodeGenerator extends RecursiveVisitor {
_genConditionAndJumpIf(node.condition, false, done);
_recordBranchTargetCoverage(node.body);
_generateNode(node.body);
asm.emitJump(join);
@@ -643,6 +643,7 @@ class BytecodeSizeStatistics {
static int sourceFilesSize = 0;
static int lineStartsSize = 0;
static int localVariablesSize = 0;
static int recordedCoverageSize = 0;
static int annotationsSize = 0;
static int constantPoolSize = 0;
static int instructionsSize = 0;
@@ -663,6 +664,7 @@ class BytecodeSizeStatistics {
sourceFilesSize = 0;
lineStartsSize = 0;
localVariablesSize = 0;
recordedCoverageSize = 0;
annotationsSize = 0;
constantPoolSize = 0;
instructionsSize = 0;
@@ -687,6 +689,7 @@ class BytecodeSizeStatistics {
print(" Source files: $sourceFilesSize");
print(" Line starts: $lineStartsSize");
print(" Local variables: $localVariablesSize");
print(" Recorded coverage: $recordedCoverageSize");
print(" Annotations: $annotationsSize");
print(" Code: $codeSize");
print(" - constant pool: $constantPoolSize");
+10 -1
View File
@@ -7,7 +7,7 @@ library;
/// Version of bytecode format
/// (should match runtime/vm/constants_kbc.h).
const int bytecodeFormatVersion = 2;
const int bytecodeFormatVersion = 3;
enum Opcode {
kTrap,
@@ -222,6 +222,10 @@ enum Opcode {
// FFI
kFfiCall,
kFfiCall_Wide,
// Coverage
kRecordCoverage,
kRecordCoverage_Wide,
}
/// Compact variants of opcodes are always even.
@@ -773,6 +777,11 @@ const Map<Opcode, Format> BytecodeFormats = const {
Operand.none,
Operand.none,
]),
Opcode.kRecordCoverage: const Format(Encoding.kAE, const [
Operand.imm,
Operand.imm,
Operand.none,
]),
};
// Should match constant in runtime/vm/stack_frame_kbc.h.
+70 -15
View File
@@ -25,7 +25,8 @@ import 'object_table.dart'
NameAndType,
ParameterFlags,
TypeParameterDeclaration;
import 'source_positions.dart' show LineStarts, SourcePositions;
import 'source_positions.dart'
show LineStarts, RecordedCoverageArray, SourcePositions;
class LibraryDeclaration extends BytecodeDeclaration {
static const usesDartMirrorsFlag = 1 << 0;
@@ -873,12 +874,14 @@ class Code extends BytecodeDeclaration {
static const hasForwardingStubTargetFlag = 1 << 5;
static const hasDefaultFunctionTypeArgsFlag = 1 << 6;
static const hasLocalVariablesFlag = 1 << 7;
static const hasRecordedCoverageFlag = 1 << 8;
final ConstantPool constantPool;
final Uint8List bytecodes;
final ExceptionsTable exceptionsTable;
final SourcePositions? sourcePositions;
final LocalVariableTable? localVariables;
final RecordedCoverageArray? recordedCoverage;
final List<ObjectHandle> nullableFields;
final List<ClosureDeclaration> closures;
// Covariant and CovariantByClass flags for all parameters.
@@ -889,6 +892,7 @@ class Code extends BytecodeDeclaration {
bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
bool get hasSourcePositions => sourcePositions?.isNotEmpty ?? false;
bool get hasLocalVariables => localVariables?.isNotEmpty ?? false;
bool get hasRecordedCoverage => recordedCoverage?.isNotEmpty ?? false;
bool get hasNullableFields => nullableFields.isNotEmpty;
bool get hasClosures => closures.isNotEmpty;
@@ -902,7 +906,8 @@ class Code extends BytecodeDeclaration {
(defaultFunctionTypeArgsCpIndex != null
? hasDefaultFunctionTypeArgsFlag
: 0) |
(hasLocalVariables ? hasLocalVariablesFlag : 0);
(hasLocalVariables ? hasLocalVariablesFlag : 0) |
(hasRecordedCoverage ? hasRecordedCoverageFlag : 0);
Code(
this.constantPool,
@@ -910,6 +915,7 @@ class Code extends BytecodeDeclaration {
this.exceptionsTable,
this.sourcePositions,
this.localVariables,
this.recordedCoverage,
this.nullableFields,
this.closures,
this.parameterFlags,
@@ -948,6 +954,9 @@ class Code extends BytecodeDeclaration {
if (hasLocalVariables) {
writer.writeLinkOffset(localVariables!);
}
if (hasRecordedCoverage) {
writer.writeLinkOffset(recordedCoverage!);
}
if (hasNullableFields) {
writer.writePackedList(nullableFields);
}
@@ -990,6 +999,9 @@ class Code extends BytecodeDeclaration {
final localVariables = ((flags & hasLocalVariablesFlag) != 0)
? reader.readLinkOffset<LocalVariableTable>()
: null;
final recordedCoverage = ((flags & hasRecordedCoverageFlag) != 0)
? reader.readLinkOffset<RecordedCoverageArray>()
: null;
final List<ObjectHandle> nullableFields =
((flags & hasNullableFieldsFlag) != 0)
? reader.readPackedList<ObjectHandle>()
@@ -1003,6 +1015,7 @@ class Code extends BytecodeDeclaration {
exceptionsTable,
sourcePositions,
localVariables,
recordedCoverage,
nullableFields,
closures,
parameterFlags,
@@ -1017,6 +1030,7 @@ class Code extends BytecodeDeclaration {
"Bytecode {\n"
"${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable, annotations: [hasSourcePositions ? sourcePositions!.getBytecodeAnnotations() : const <int, String>{}, hasLocalVariables ? localVariables!.getBytecodeAnnotations() : const <int, String>{}])}}\n"
"$exceptionsTable"
"${recordedCoverage == null ? '' : 'Coverage array: $recordedCoverage\n'}"
"${nullableFields.isEmpty ? '' : 'Nullable fields: $nullableFields\n'}"
"${parameterFlags == null ? '' : 'Parameter flags: $parameterFlags\n'}"
"${forwardingStubTargetCpIndex == null ? '' : 'Forwarding stub target: CP#$forwardingStubTargetCpIndex\n'}"
@@ -1237,17 +1251,19 @@ class ClosureCode {
static const hasLocalVariablesFlag = 1 << 2;
static const capturesOnlyFinalNotLateVarsFlag = 1 << 3;
static const hasLocalFunctionIdFlag = 1 << 4;
static const hasRecordedCoverageFlag = 1 << 5;
final Uint8List bytecodes;
final ExceptionsTable exceptionsTable;
final SourcePositions? sourcePositions;
final LocalVariableTable? localVariables;
final RecordedCoverageArray? recordedCoverage;
final bool capturesOnlyFinalNotLateVars;
final int localFunctionId;
bool get hasExceptionsTable => exceptionsTable.blocks.isNotEmpty;
bool get hasSourcePositions => sourcePositions?.isNotEmpty ?? false;
bool get hasLocalVariables => localVariables?.isNotEmpty ?? false;
bool get hasRecordedCoverage => recordedCoverage?.isNotEmpty ?? false;
bool get hasLocalFunctionId => localFunctionId > 0;
int get flags =>
@@ -1255,13 +1271,15 @@ class ClosureCode {
(hasSourcePositions ? hasSourcePositionsFlag : 0) |
(hasLocalVariables ? hasLocalVariablesFlag : 0) |
(capturesOnlyFinalNotLateVars ? capturesOnlyFinalNotLateVarsFlag : 0) |
(hasLocalFunctionId ? hasLocalFunctionIdFlag : 0);
(hasLocalFunctionId ? hasLocalFunctionIdFlag : 0) |
(hasRecordedCoverage ? hasRecordedCoverageFlag : 0);
ClosureCode(
this.bytecodes,
this.exceptionsTable,
this.sourcePositions,
this.localVariables,
this.recordedCoverage,
this.capturesOnlyFinalNotLateVars,
this.localFunctionId,
);
@@ -1281,6 +1299,9 @@ class ClosureCode {
if (hasLocalVariables) {
writer.writeLinkOffset(localVariables!);
}
if (hasRecordedCoverage) {
writer.writeLinkOffset(recordedCoverage!);
}
}
factory ClosureCode.read(BufferedReader reader) {
@@ -1298,6 +1319,9 @@ class ClosureCode {
final localVariables = ((flags & hasLocalVariablesFlag) != 0)
? reader.readLinkOffset<LocalVariableTable>()
: null;
final recordedCoverage = ((flags & hasRecordedCoverageFlag) != 0)
? reader.readLinkOffset<RecordedCoverageArray>()
: null;
final capturesOnlyFinalNotLateVars =
(flags & capturesOnlyFinalNotLateVarsFlag) != 0;
@@ -1306,6 +1330,7 @@ class ClosureCode {
exceptionsTable,
sourcePositions,
localVariables,
recordedCoverage,
capturesOnlyFinalNotLateVars,
localFunctionId,
);
@@ -1329,6 +1354,9 @@ class ClosureCode {
],
),
);
if (hasRecordedCoverage) {
sb.writeln('Coverage array: $recordedCoverage');
}
sb.writeln('}');
return sb.toString();
}
@@ -1363,7 +1391,7 @@ class _Section {
class Component {
static const int magicValue = 0x44424333; // 'DBC3'
static const int numSections = 13;
static const int numSections = 14;
static const int sectionAlignment = 4;
// UInt32 magic, version, numSections x (numItems, offset)
@@ -1380,6 +1408,8 @@ class Component {
final List<SourceFile> sourceFiles = <SourceFile>[];
final Map<Uri, SourceFile> uriToSource = <Uri, SourceFile>{};
final List<LocalVariableTable> localVariables = <LocalVariableTable>[];
final List<RecordedCoverageArray> recordedCoverage =
<RecordedCoverageArray>[];
final List<AnnotationsDeclaration> annotations = <AnnotationsDeclaration>[];
ObjectHandle? dynModuleEntryPoint;
@@ -1400,6 +1430,14 @@ class Component {
}
BytecodeSizeStatistics.annotationsSize += annotationsWriter.offset;
final recordedCoverageWriter = new BufferedWriter.fromWriter(writer);
for (var rc in recordedCoverage) {
writer.linkWriter.put(rc, recordedCoverageWriter.offset);
rc.write(recordedCoverageWriter);
}
BytecodeSizeStatistics.recordedCoverageSize +=
recordedCoverageWriter.offset;
final localVariablesWriter = new BufferedWriter.fromWriter(writer);
for (var lv in localVariables) {
writer.linkWriter.put(lv, localVariablesWriter.offset);
@@ -1484,9 +1522,14 @@ class Component {
new _Section(sourceFiles.length, sourceFilesWriter),
new _Section(lineStarts.length, lineStartsWriter),
new _Section(localVariables.length, localVariablesWriter),
new _Section(recordedCoverage.length, recordedCoverageWriter),
new _Section(annotations.length, annotationsWriter),
];
assert(sections.length == numSections);
if (sections.length != numSections) {
throw StateError(
'Expected $numSections sections, got ${sections.length}',
);
}
int offset = headerSize;
for (var section in sections) {
@@ -1567,6 +1610,9 @@ class Component {
final localVariablesNum = reader.readUInt32();
final localVariablesOffset = reader.readUInt32();
final recordedCoverageNum = reader.readUInt32();
final recordedCoverageOffset = reader.readUInt32();
final annotationsNum = reader.readUInt32();
final annotationsOffset = reader.readUInt32();
@@ -1590,6 +1636,24 @@ class Component {
annotations.add(annot);
}
final recordedCoverageStart = start + recordedCoverageOffset;
reader.offset = recordedCoverageStart;
for (int i = 0; i < recordedCoverageNum; ++i) {
int offset = reader.offset - recordedCoverageStart;
RecordedCoverageArray rc = new RecordedCoverageArray.read(reader);
reader.linkReader.setOffset(rc, offset);
recordedCoverage.add(rc);
}
final localVariablesStart = start + localVariablesOffset;
reader.offset = localVariablesStart;
for (int i = 0; i < localVariablesNum; ++i) {
int offset = reader.offset - localVariablesStart;
LocalVariableTable lv = new LocalVariableTable.read(reader);
reader.linkReader.setOffset(lv, offset);
localVariables.add(lv);
}
final lineStartsStart = start + lineStartsOffset;
reader.offset = lineStartsStart;
for (int i = 0; i < lineStartsNum; ++i) {
@@ -1617,15 +1681,6 @@ class Component {
sourcePositions.add(sp);
}
final localVariablesStart = start + localVariablesOffset;
reader.offset = localVariablesStart;
for (int i = 0; i < localVariablesNum; ++i) {
int offset = reader.offset - localVariablesStart;
LocalVariableTable lv = new LocalVariableTable.read(reader);
reader.linkReader.setOffset(lv, offset);
localVariables.add(lv);
}
final codesStart = start + codesOffset;
reader.offset = codesStart;
for (int i = 0; i < codesNum; ++i) {
@@ -159,3 +159,64 @@ class LineStarts extends BytecodeDeclaration {
@override
String toString() => 'Line starts: $lineStarts';
}
enum RecordedCoverageType {
// Used for most types of coverage.
regular,
// Used when recording that a branch reached a particular target.
branchTarget,
}
/// Keeps types and file offsets of coverage information recorded
/// by RecordCoverage instructions.
///
/// RecordCoverage instructions use indices into the list of types
/// and file offsets collected during generation, and the bytecode reader
/// generates an appropriate coverage array from it at load time.
class RecordedCoverageArray extends BytecodeDeclaration {
final _recordedCoverageMap = <(RecordedCoverageType, int), int>{};
final _recordedCoverageList = <(RecordedCoverageType, int)>[];
RecordedCoverageArray();
bool get isEmpty => _recordedCoverageList.isEmpty;
bool get isNotEmpty => !isEmpty;
// Adds the type and file offset to the list of types and file offsets
// recorded for RecordCoverage instructions. Returns the index into
// the list for use as the argument to the RecordCoverage instruction.
int add(RecordedCoverageType type, int fileOffset) {
final key = (type, fileOffset);
int? index = _recordedCoverageMap[key];
if (index == null) {
index = _recordedCoverageList.length;
_recordedCoverageList.add(key);
_recordedCoverageMap[key] = index;
}
return index;
}
void write(BufferedWriter writer) {
writer.writePackedUInt30(_recordedCoverageList.length);
final encodeFileOffsets = new SLEB128DeltaEncoder();
for (final (type, fileOffset) in _recordedCoverageList) {
writer.writePackedUInt30(type.index);
encodeFileOffsets.write(writer, fileOffset);
}
}
RecordedCoverageArray.read(BufferedReader reader) {
final decodeFileOffsets = new SLEB128DeltaDecoder();
final length = reader.readPackedUInt30();
for (int i = 0; i < length; i++) {
final type = RecordedCoverageType.values[(reader.readPackedUInt30())];
final fileOffset = decodeFileOffsets.read(reader);
final key = (type, fileOffset);
_recordedCoverageList.add(key);
_recordedCoverageMap[key] = i;
}
}
@override
String toString() => _recordedCoverageList.toString();
}
@@ -976,11 +976,11 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_AllocateArray_entry_point_offset => 0x2e8;
@override
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x708;
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x710;
@override
int get Thread_active_exception_offset => 0x6c8;
int get Thread_active_exception_offset => 0x6d0;
@override
int get Thread_active_stacktrace_offset => 0x6d0;
int get Thread_active_stacktrace_offset => 0x6d8;
@override
int get Thread_array_write_barrier_entry_point_offset => 0x200;
@override
@@ -998,7 +998,7 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_allocate_object_slow_entry_point_offset => 0x230;
@override
int get Thread_api_top_scope_offset => 0x888;
int get Thread_api_top_scope_offset => 0x890;
@override
int get Thread_async_exception_handler_stub_offset => 0x160;
@override
@@ -1014,15 +1014,15 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_call_to_runtime_stub_offset => 0xd8;
@override
int get Thread_dart_stream_offset => 0x8e0;
int get Thread_dart_stream_offset => 0x8e8;
@override
int get Thread_dispatch_table_array_offset => 0x68;
@override
int get Thread_double_truncate_round_supported_offset => 0x890;
int get Thread_double_truncate_round_supported_offset => 0x898;
@override
int get Thread_service_extension_stream_offset => 0x8e8;
int get Thread_service_extension_stream_offset => 0x8f0;
@override
int get Thread_thread_locals_offset => 0x8f0;
int get Thread_thread_locals_offset => 0x8f8;
@override
int get Thread_optimize_entry_offset => 0x258;
@override
@@ -1040,7 +1040,7 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_enter_safepoint_stub_offset => 0x1e0;
@override
int get Thread_execution_state_offset => 0x6f0;
int get Thread_execution_state_offset => 0x6f8;
@override
int get Thread_exit_safepoint_stub_offset => 0x1e8;
@override
@@ -1060,7 +1060,7 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_float_zerow_address_offset => 0x2e0;
@override
int get Thread_global_object_pool_offset => 0x6d8;
int get Thread_global_object_pool_offset => 0x6e0;
@override
int get Thread_interpret_call_entry_point_offset => 0x2a0;
@override
@@ -1068,11 +1068,11 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_invoke_dart_code_stub_offset => 0xc8;
@override
int get Thread_exit_through_ffi_offset => 0x700;
int get Thread_exit_through_ffi_offset => 0x708;
@override
int get Thread_isolate_offset => 0x678;
int get Thread_isolate_offset => 0x680;
@override
int get Thread_isolate_group_offset => 0x680;
int get Thread_isolate_group_offset => 0x688;
@override
int get Thread_field_table_values_offset => 0x70;
@override
@@ -1082,9 +1082,9 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_lazy_specialize_type_test_stub_offset => 0x1d8;
@override
int get Thread_old_marking_stack_block_offset => 0x6a8;
int get Thread_old_marking_stack_block_offset => 0x6b0;
@override
int get Thread_new_marking_stack_block_offset => 0x6b0;
int get Thread_new_marking_stack_block_offset => 0x6b8;
@override
int get Thread_megamorphic_call_checked_entry_offset => 0x248;
@override
@@ -1140,15 +1140,15 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_resume_interpreter_adjusted_entry_point_offset => 0x280;
@override
int get Thread_resume_pc_offset => 0x6e0;
int get Thread_resume_pc_offset => 0x6e8;
@override
int get Thread_saved_shadow_call_stack_offset => 0x6e8;
int get Thread_saved_shadow_call_stack_offset => 0x6f0;
@override
int get Thread_safepoint_state_offset => 0x6f8;
int get Thread_safepoint_state_offset => 0x700;
@override
int get Thread_shared_field_table_values_offset => 0x78;
@override
int get Thread_single_step_offset => 0x8c0;
int get Thread_single_step_offset => 0x8c8;
@override
int get Thread_slow_type_test_stub_offset => 0x1d0;
@override
@@ -1156,9 +1156,9 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_stack_limit_offset => 0x48;
@override
int get Thread_saved_stack_limit_offset => 0x688;
int get Thread_saved_stack_limit_offset => 0x690;
@override
int get Thread_stack_overflow_flags_offset => 0x690;
int get Thread_stack_overflow_flags_offset => 0x698;
@override
int get Thread_stack_overflow_shared_with_fpu_regs_entry_point_offset =>
0x240;
@@ -1170,60 +1170,60 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Thread_stack_overflow_shared_without_fpu_regs_stub_offset => 0x188;
@override
int get Thread_store_buffer_block_offset => 0x6a0;
int get Thread_store_buffer_block_offset => 0x6a8;
@override
int get Thread_suspend_state_await_entry_point_offset => 0x628;
int get Thread_suspend_state_await_entry_point_offset => 0x630;
@override
int get Thread_suspend_state_await_with_type_check_entry_point_offset =>
0x630;
0x638;
@override
int get Thread_suspend_state_init_async_entry_point_offset => 0x620;
int get Thread_suspend_state_init_async_entry_point_offset => 0x628;
@override
int get Thread_suspend_state_return_async_entry_point_offset => 0x638;
int get Thread_suspend_state_return_async_entry_point_offset => 0x640;
@override
int get Thread_suspend_state_return_async_not_future_entry_point_offset =>
0x640;
0x648;
@override
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x648;
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x650;
@override
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x650;
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x658;
@override
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x658;
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x660;
@override
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x660;
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x668;
@override
int get Thread_suspend_state_suspend_sync_star_at_start_entry_point_offset =>
0x668;
0x670;
@override
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x670;
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x678;
@override
int get Thread_top_exit_frame_info_offset => 0x698;
int get Thread_top_exit_frame_info_offset => 0x6a0;
@override
int get Thread_top_offset => 0x58;
@override
int get Thread_top_resource_offset => 0x20;
@override
int get Thread_unboxed_runtime_arg_offset => 0x898;
int get Thread_unboxed_runtime_arg_offset => 0x8a0;
@override
int get Thread_vm_tag_offset => 0x6c0;
int get Thread_vm_tag_offset => 0x6c8;
@override
int get Thread_write_barrier_entry_point_offset => 0x1f8;
@override
int get Thread_write_barrier_mask_offset => 0x50;
@override
int get Thread_next_task_id_offset => 0x8a8;
int get Thread_next_task_id_offset => 0x8b0;
@override
int get Thread_random_offset => 0x8b0;
int get Thread_random_offset => 0x8b8;
@override
int get Thread_jump_to_frame_entry_point_offset => 0x270;
@override
int get Thread_tsan_utils_offset => 0x8b8;
int get Thread_tsan_utils_offset => 0x8c0;
@override
int get Thread_current_tag_offset => 0x8d0;
int get Thread_current_tag_offset => 0x8d8;
@override
int get Thread_default_tag_offset => 0x8d8;
int get Thread_default_tag_offset => 0x8e0;
@override
int get Thread_user_tag_offset => 0x8c8;
int get Thread_user_tag_offset => 0x8d0;
@override
int get TsanUtils_setjmp_function_offset => 0x0;
@override
@@ -1326,7 +1326,6 @@ final class Arm64VMOffsets extends VMOffsets {
List<int> get Code_entry_point_offset => [0x8, 0x18, 0x10, 0x20];
@override
List<int> get Thread_write_barrier_wrappers_thread_offset => [
0x580,
0x588,
0x590,
0x598,
@@ -1341,17 +1340,18 @@ final class Arm64VMOffsets extends VMOffsets {
0x5e0,
0x5e8,
0x5f0,
-1,
-1,
-1,
-1,
0x5f8,
-1,
-1,
-1,
-1,
0x600,
-1,
-1,
0x608,
-1,
-1,
0x610,
0x618,
0x620,
-1,
-1,
-1,
@@ -1368,7 +1368,7 @@ final class Arm64VMOffsets extends VMOffsets {
@override
int get Bool_InstanceSize => 0x10;
@override
int get Bytecode_InstanceSize => 0x60;
int get Bytecode_InstanceSize => 0x70;
@override
int get Capability_InstanceSize => 0x10;
@override
@@ -1939,11 +1939,11 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_AllocateArray_entry_point_offset => 0x2e8;
@override
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x708;
int get Thread_DeoptimizeCopyFrame_entry_point_offset => 0x710;
@override
int get Thread_active_exception_offset => 0x6c8;
int get Thread_active_exception_offset => 0x6d0;
@override
int get Thread_active_stacktrace_offset => 0x6d0;
int get Thread_active_stacktrace_offset => 0x6d8;
@override
int get Thread_array_write_barrier_entry_point_offset => 0x200;
@override
@@ -1961,7 +1961,7 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_allocate_object_slow_entry_point_offset => 0x230;
@override
int get Thread_api_top_scope_offset => 0x888;
int get Thread_api_top_scope_offset => 0x890;
@override
int get Thread_async_exception_handler_stub_offset => 0x160;
@override
@@ -1977,15 +1977,15 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_call_to_runtime_stub_offset => 0xd8;
@override
int get Thread_dart_stream_offset => 0x8e0;
int get Thread_dart_stream_offset => 0x8e8;
@override
int get Thread_dispatch_table_array_offset => 0x68;
@override
int get Thread_double_truncate_round_supported_offset => 0x890;
int get Thread_double_truncate_round_supported_offset => 0x898;
@override
int get Thread_service_extension_stream_offset => 0x8e8;
int get Thread_service_extension_stream_offset => 0x8f0;
@override
int get Thread_thread_locals_offset => 0x8f0;
int get Thread_thread_locals_offset => 0x8f8;
@override
int get Thread_optimize_entry_offset => 0x258;
@override
@@ -2003,7 +2003,7 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_enter_safepoint_stub_offset => 0x1e0;
@override
int get Thread_execution_state_offset => 0x6f0;
int get Thread_execution_state_offset => 0x6f8;
@override
int get Thread_exit_safepoint_stub_offset => 0x1e8;
@override
@@ -2023,7 +2023,7 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_float_zerow_address_offset => 0x2e0;
@override
int get Thread_global_object_pool_offset => 0x6d8;
int get Thread_global_object_pool_offset => 0x6e0;
@override
int get Thread_interpret_call_entry_point_offset => 0x2a0;
@override
@@ -2031,11 +2031,11 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_invoke_dart_code_stub_offset => 0xc8;
@override
int get Thread_exit_through_ffi_offset => 0x700;
int get Thread_exit_through_ffi_offset => 0x708;
@override
int get Thread_isolate_offset => 0x678;
int get Thread_isolate_offset => 0x680;
@override
int get Thread_isolate_group_offset => 0x680;
int get Thread_isolate_group_offset => 0x688;
@override
int get Thread_field_table_values_offset => 0x70;
@override
@@ -2045,9 +2045,9 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_lazy_specialize_type_test_stub_offset => 0x1d8;
@override
int get Thread_old_marking_stack_block_offset => 0x6a8;
int get Thread_old_marking_stack_block_offset => 0x6b0;
@override
int get Thread_new_marking_stack_block_offset => 0x6b0;
int get Thread_new_marking_stack_block_offset => 0x6b8;
@override
int get Thread_megamorphic_call_checked_entry_offset => 0x248;
@override
@@ -2103,11 +2103,11 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_resume_interpreter_adjusted_entry_point_offset => 0x280;
@override
int get Thread_resume_pc_offset => 0x6e0;
int get Thread_resume_pc_offset => 0x6e8;
@override
int get Thread_saved_shadow_call_stack_offset => 0x6e8;
int get Thread_saved_shadow_call_stack_offset => 0x6f0;
@override
int get Thread_safepoint_state_offset => 0x6f8;
int get Thread_safepoint_state_offset => 0x700;
@override
int get Thread_shared_field_table_values_offset => 0x78;
@override
@@ -2117,9 +2117,9 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_stack_limit_offset => 0x48;
@override
int get Thread_saved_stack_limit_offset => 0x688;
int get Thread_saved_stack_limit_offset => 0x690;
@override
int get Thread_stack_overflow_flags_offset => 0x690;
int get Thread_stack_overflow_flags_offset => 0x698;
@override
int get Thread_stack_overflow_shared_with_fpu_regs_entry_point_offset =>
0x240;
@@ -2131,60 +2131,60 @@ final class Arm64ProductVMOffsets extends VMOffsets {
@override
int get Thread_stack_overflow_shared_without_fpu_regs_stub_offset => 0x188;
@override
int get Thread_store_buffer_block_offset => 0x6a0;
int get Thread_store_buffer_block_offset => 0x6a8;
@override
int get Thread_suspend_state_await_entry_point_offset => 0x628;
int get Thread_suspend_state_await_entry_point_offset => 0x630;
@override
int get Thread_suspend_state_await_with_type_check_entry_point_offset =>
0x630;
0x638;
@override
int get Thread_suspend_state_init_async_entry_point_offset => 0x620;
int get Thread_suspend_state_init_async_entry_point_offset => 0x628;
@override
int get Thread_suspend_state_return_async_entry_point_offset => 0x638;
int get Thread_suspend_state_return_async_entry_point_offset => 0x640;
@override
int get Thread_suspend_state_return_async_not_future_entry_point_offset =>
0x640;
0x648;
@override
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x648;
int get Thread_suspend_state_init_async_star_entry_point_offset => 0x650;
@override
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x650;
int get Thread_suspend_state_yield_async_star_entry_point_offset => 0x658;
@override
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x658;
int get Thread_suspend_state_return_async_star_entry_point_offset => 0x660;
@override
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x660;
int get Thread_suspend_state_init_sync_star_entry_point_offset => 0x668;
@override
int get Thread_suspend_state_suspend_sync_star_at_start_entry_point_offset =>
0x668;
0x670;
@override
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x670;
int get Thread_suspend_state_handle_exception_entry_point_offset => 0x678;
@override
int get Thread_top_exit_frame_info_offset => 0x698;
int get Thread_top_exit_frame_info_offset => 0x6a0;
@override
int get Thread_top_offset => 0x58;
@override
int get Thread_top_resource_offset => 0x20;
@override
int get Thread_unboxed_runtime_arg_offset => 0x898;
int get Thread_unboxed_runtime_arg_offset => 0x8a0;
@override
int get Thread_vm_tag_offset => 0x6c0;
int get Thread_vm_tag_offset => 0x6c8;
@override
int get Thread_write_barrier_entry_point_offset => 0x1f8;
@override
int get Thread_write_barrier_mask_offset => 0x50;
@override
int get Thread_next_task_id_offset => 0x8a8;
int get Thread_next_task_id_offset => 0x8b0;
@override
int get Thread_random_offset => 0x8b0;
int get Thread_random_offset => 0x8b8;
@override
int get Thread_jump_to_frame_entry_point_offset => 0x270;
@override
int get Thread_tsan_utils_offset => 0x8b8;
int get Thread_tsan_utils_offset => 0x8c0;
@override
int get Thread_current_tag_offset => 0x8d0;
int get Thread_current_tag_offset => 0x8d8;
@override
int get Thread_default_tag_offset => 0x8d8;
int get Thread_default_tag_offset => 0x8e0;
@override
int get Thread_user_tag_offset => 0x8c8;
int get Thread_user_tag_offset => 0x8d0;
@override
int get TsanUtils_setjmp_function_offset => 0x0;
@override
@@ -2287,7 +2287,6 @@ final class Arm64ProductVMOffsets extends VMOffsets {
List<int> get Code_entry_point_offset => [0x8, 0x18, 0x10, 0x20];
@override
List<int> get Thread_write_barrier_wrappers_thread_offset => [
0x580,
0x588,
0x590,
0x598,
@@ -2302,17 +2301,18 @@ final class Arm64ProductVMOffsets extends VMOffsets {
0x5e0,
0x5e8,
0x5f0,
-1,
-1,
-1,
-1,
0x5f8,
-1,
-1,
-1,
-1,
0x600,
-1,
-1,
0x608,
-1,
-1,
0x610,
0x618,
0x620,
-1,
-1,
-1,
@@ -2736,6 +2736,7 @@ enum RuntimeEntry {
InitializeSharedField,
FatalError,
EnsureDeeplyImmutable,
AllocateBytecodeCoverageArray,
}
enum LeafRuntimeEntry {
+44 -8
View File
@@ -19,6 +19,7 @@
#include "vm/dart_entry.h"
#include "vm/flags.h"
#include "vm/hash.h"
#include "vm/hash_map.h"
#include "vm/hash_table.h"
#include "vm/longjump.h"
#include "vm/object.h"
@@ -227,6 +228,8 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
(flags & Code::kHasForwardingStubTargetFlag) != 0;
const bool has_default_function_type_args =
(flags & Code::kHasDefaultFunctionTypeArgsFlag) != 0;
const bool has_recorded_coverage =
(flags & Code::kHasRecordedCoverageFlag) != 0;
if (has_parameter_flags) {
intptr_t num_flags = reader_.ReadUInt();
@@ -266,6 +269,8 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
ReadLocalVariables(bytecode, has_local_variables);
ReadRecordedCoverage(bytecode, has_recorded_coverage);
if (FLAG_dump_kernel_bytecode) {
if (ShouldPrint(function)) {
KernelBytecodeDisassembler::Disassemble(function);
@@ -306,6 +311,8 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
(flags & ClosureCode::kHasLocalVariablesFlag) != 0;
const bool captures_only_final_not_late_vars =
(flags & ClosureCode::kCapturesOnlyFinalNotLateVarsFlag) != 0;
const bool has_recorded_coverage =
(flags & ClosureCode::kHasRecordedCoverageFlag) != 0;
intptr_t local_function_id = -1;
if ((flags & ClosureCode::kHasLocalFunctionIdFlag) != 0) {
@@ -326,6 +333,8 @@ void BytecodeReaderHelper::ReadCode(const Function& function,
ReadLocalVariables(closure_bytecode, has_local_variables);
ReadRecordedCoverage(closure_bytecode, has_recorded_coverage);
if (FLAG_dump_kernel_bytecode) {
if (ShouldPrint(closure)) {
KernelBytecodeDisassembler::Disassemble(closure);
@@ -914,6 +923,21 @@ void BytecodeReaderHelper::ReadLocalVariables(const Bytecode& bytecode,
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}
void BytecodeReaderHelper::ReadRecordedCoverage(const Bytecode& bytecode,
bool has_recorded_coverage) {
if (!has_recorded_coverage) {
return;
}
const intptr_t offset = reader_.ReadUInt();
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
bytecode.set_recorded_coverage_binary_offset(
bytecode_component_->GetRecordedCoverageOffset() + offset);
#else
USE(offset);
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
}
ArrayPtr BytecodeReaderHelper::ReadBytecodeComponent() {
AlternativeReadingScope alt(&reader_, 0);
@@ -968,6 +992,9 @@ ArrayPtr BytecodeReaderHelper::ReadBytecodeComponent() {
reader_.ReadUInt32(); // Skip localVariables.numItems
const intptr_t local_variables_offset = start_offset + reader_.ReadUInt32();
reader_.ReadUInt32(); // Skip recordedCoverage.numItems
const intptr_t recorded_coverage_offset = start_offset + reader_.ReadUInt32();
reader_.ReadUInt32(); // Skip annotations.numItems
const intptr_t annotations_offset = start_offset + reader_.ReadUInt32();
@@ -989,14 +1016,15 @@ ArrayPtr BytecodeReaderHelper::ReadBytecodeComponent() {
reader_.set_offset(object_offsets_offset);
auto& bytecode_component_array = Array::Handle(
Z, BytecodeComponentData::New(
Z, *(reader_.typed_data()), version, num_objects,
string_table_offset, strings_contents_offset,
object_offsets_offset, objects_contents_offset, main_offset,
num_libraries, library_index_offset, libraries_offset, num_classes,
classes_offset, members_offset, num_codes, codes_offset,
source_positions_offset, source_files_offset, line_starts_offset,
local_variables_offset, annotations_offset, Heap::kOld));
Z,
BytecodeComponentData::New(
Z, *(reader_.typed_data()), version, num_objects, string_table_offset,
strings_contents_offset, object_offsets_offset,
objects_contents_offset, main_offset, num_libraries,
library_index_offset, libraries_offset, num_classes, classes_offset,
members_offset, num_codes, codes_offset, source_positions_offset,
source_files_offset, line_starts_offset, local_variables_offset,
recorded_coverage_offset, annotations_offset, Heap::kOld));
BytecodeComponentData bytecode_component(bytecode_component_array);
@@ -2734,6 +2762,10 @@ intptr_t BytecodeComponentData::GetLocalVariablesOffset() const {
return Smi::Value(Smi::RawCast(data_.At(kLocalVariablesOffset)));
}
intptr_t BytecodeComponentData::GetRecordedCoverageOffset() const {
return Smi::Value(Smi::RawCast(data_.At(kRecordedCoverageOffset)));
}
intptr_t BytecodeComponentData::GetAnnotationsOffset() const {
return Smi::Value(Smi::RawCast(data_.At(kAnnotationsOffset)));
}
@@ -2767,6 +2799,7 @@ ArrayPtr BytecodeComponentData::New(Zone* zone,
intptr_t source_files_offset,
intptr_t line_starts_offset,
intptr_t local_variables_offset,
intptr_t recorded_coverage_offset,
intptr_t annotations_offset,
Heap::Space space) {
const Array& data =
@@ -2832,6 +2865,9 @@ ArrayPtr BytecodeComponentData::New(Zone* zone,
smi_handle = Smi::New(local_variables_offset);
data.SetAt(kLocalVariablesOffset, smi_handle);
smi_handle = Smi::New(recorded_coverage_offset);
data.SetAt(kRecordedCoverageOffset, smi_handle);
smi_handle = Smi::New(annotations_offset);
data.SetAt(kAnnotationsOffset, smi_handle);
+54
View File
@@ -300,6 +300,7 @@ class BytecodeReaderHelper : public ValueObject {
static const int kHasForwardingStubTargetFlag = 1 << 5;
static const int kHasDefaultFunctionTypeArgsFlag = 1 << 6;
static const int kHasLocalVariablesFlag = 1 << 7;
static const int kHasRecordedCoverageFlag = 1 << 8;
};
// Closure code flags, must be in sync with ClosureCode constants in
@@ -310,6 +311,7 @@ class BytecodeReaderHelper : public ValueObject {
static const int kHasLocalVariablesFlag = 1 << 2;
static const int kCapturesOnlyFinalNotLateVarsFlag = 1 << 3;
static const int kHasLocalFunctionIdFlag = 1 << 4;
static const int kHasRecordedCoverageFlag = 1 << 5;
};
// Parameter flags, must be in sync with ParameterFlags constants in
@@ -390,6 +392,8 @@ class BytecodeReaderHelper : public ValueObject {
bool has_exceptions_table);
void ReadSourcePositions(const Bytecode& bytecode, bool has_source_positions);
void ReadLocalVariables(const Bytecode& bytecode, bool has_local_variables);
void ReadRecordedCoverage(const Bytecode& bytecode,
bool has_recorded_coverage);
StringPtr ConstructorName(const Class& cls, const String& name);
ObjectPtr ReadObjectContents(uint32_t header);
@@ -451,6 +455,7 @@ class BytecodeComponentData : ValueObject {
kSourceFilesOffset,
kLineStartsOffset,
kLocalVariablesOffset,
kRecordedCoverageOffset,
kAnnotationsOffset,
kNumFields
};
@@ -477,6 +482,7 @@ class BytecodeComponentData : ValueObject {
intptr_t GetSourceFilesOffset() const;
intptr_t GetLineStartsOffset() const;
intptr_t GetLocalVariablesOffset() const;
intptr_t GetRecordedCoverageOffset() const;
intptr_t GetAnnotationsOffset() const;
void SetObject(intptr_t index, const Object& obj) const;
ObjectPtr GetObject(intptr_t index) const;
@@ -504,6 +510,7 @@ class BytecodeComponentData : ValueObject {
intptr_t source_files_offset,
intptr_t line_starts_offset,
intptr_t local_variables_offset,
intptr_t recorded_coverage_offset,
intptr_t annotations_offset,
Heap::Space space);
@@ -714,6 +721,53 @@ class BytecodeLocalVariablesIterator : ValueObject {
TokenPosition cur_end_token_pos_ = TokenPosition::kNoSource;
};
// Types of recorded coverage, keep in sync with the RecordedCoverageType
// enum in pkg/dart2bytecode/lib/source_positions.dart.
enum class RecordedCoverageType { kRegular, kBranchTarget };
class BytecodeRecordedCoverageIterator : ValueObject {
public:
BytecodeRecordedCoverageIterator(Zone* zone, const Bytecode& bytecode)
: reader_(TypedDataBase::Handle(zone, bytecode.binary())) {
ASSERT(bytecode.HasRecordedCoverage());
reader_.set_offset(bytecode.recorded_coverage_binary_offset());
pairs_remaining_ = num_entries_ = reader_.ReadUInt();
}
intptr_t NumEntries() const { return num_entries_; }
bool MoveNext() {
if (pairs_remaining_ == 0) {
return false;
}
ASSERT(pairs_remaining_ > 0);
--pairs_remaining_;
cur_type_ = static_cast<RecordedCoverageType>(reader_.ReadUInt());
cur_position_ += reader_.ReadSLEB128();
return true;
}
intptr_t EncodedCoveragePosition() const {
return TokenPos().EncodeCoveragePosition(IsBranch());
}
private:
TokenPosition TokenPos() const {
ASSERT(cur_position_ >= 0);
return TokenPosition::Deserialize(cur_position_);
}
bool IsBranch() const {
return cur_type_ == RecordedCoverageType::kBranchTarget;
}
Reader reader_;
intptr_t num_entries_ = 0;
intptr_t pairs_remaining_ = 0;
RecordedCoverageType cur_type_ = RecordedCoverageType::kRegular;
intptr_t cur_position_ = 0;
};
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
} // namespace bytecode
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -207,6 +207,8 @@ namespace dart {
V(LoadRecordField_Wide, D, WIDE, num, ___, ___) \
V(FfiCall, D, ORDN, lit, ___, ___) \
V(FfiCall_Wide, D, WIDE, lit, ___, ___) \
V(RecordCoverage, A_E, ORDN, num, num, ___) \
V(RecordCoverage_Wide, A_E, WIDE, num, num, ___) \
// These bytecodes are only generated within the VM. Reassigning their
// opcodes is not a breaking change.
@@ -270,7 +272,7 @@ class KernelBytecode {
static const intptr_t kMagicValue = 0x44424333; // 'DBC3'
// Bytecode format version supported by the VM
// (should match pkg/dart2bytecode/lib/dbc.dart).
static const intptr_t kBytecodeFormatVersion = 2;
static const intptr_t kBytecodeFormatVersion = 3;
enum Opcode {
#define DECLARE_BYTECODE(name, encoding, kind, op1, op2, op3) k##name,
+52
View File
@@ -3557,6 +3557,58 @@ SwitchDispatchNoSingleStep:
DISPATCH();
}
{
BYTECODE(RecordCoverage, A_E);
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
// rA contains the type of the recorded coverage so the runtime can check
// if it is enabled even if the coverage array has not yet been allocated.
const bool is_branch = static_cast<bytecode::RecordedCoverageType>(rA) ==
bytecode::RecordedCoverageType::kBranchTarget;
const bool coverage_enabled =
is_branch ? thread->isolate_group()->branch_coverage()
: thread->isolate_group()->coverage();
if (coverage_enabled) {
ArrayPtr coverage_array =
Function::GetBytecode(FrameFunction(FP))->untag()->coverage_array();
if (coverage_array == Array::null()) [[unlikely]] {
SP[1] = Object::null(); // Allocate stack space for result.
SP[2] = Function::GetBytecode(FrameFunction(FP));
Exit(thread, FP, SP + 3, pc);
INVOKE_RUNTIME(DRT_AllocateBytecodeCoverageArray,
NativeArguments(thread, 1, SP + 2, SP + 1));
ASSERT(Bytecode::RawCast(SP[2])->untag()->coverage_array() ==
Array::RawCast(SP[1]));
coverage_array = Array::RawCast(SP[1]);
}
ASSERT(coverage_array != Array::null());
// The index in rE is a logical index into the (position, count) pairs.
ASSERT(Smi::Value(coverage_array->untag()->length()) % 2 == 0);
const intptr_t position_index = 2 * rE;
const intptr_t count_index = position_index + 1;
#if defined(DEBUG)
// Double-check that the coverage type in the instruction is a branch
// target iff the encoded position is a branch target.
bool is_encoded_branch = false;
const intptr_t encoded = Smi::Value(
Smi::RawCast(coverage_array->untag()->element(position_index)));
TokenPosition::DecodeCoveragePosition(encoded, &is_encoded_branch);
ASSERT_EQUAL(is_branch, is_encoded_branch);
#else
USE(position_index);
#endif
coverage_array->untag()->set_element(count_index, Smi::New(1));
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
DISPATCH();
}
{
BYTECODE_ENTRY_LABEL(Trap);
+39
View File
@@ -11547,6 +11547,16 @@ void Function::RestoreICDataMap(
}
ArrayPtr Function::GetCoverageArray() const {
#if defined(DART_DYNAMIC_MODULES)
if (HasBytecode()) {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
const auto& bytecode = Bytecode::Handle(GetBytecode());
return bytecode.coverage_array();
#else
return Array::null();
#endif
}
#endif
const Array& arr = Array::Handle(ic_data_array());
if (arr.IsNull()) {
return Array::null();
@@ -19079,6 +19089,35 @@ LocalVarDescriptorsPtr Bytecode::GetLocalVarDescriptors() const {
UNREACHABLE();
#endif
}
ArrayPtr Bytecode::EnsureCoverageArray(Thread* thread) const {
#if defined(DART_DYNAMIC_MODULES)
// Should only be called for bytecode with RecordCoverage instructions.
ASSERT(HasRecordedCoverage());
if (coverage_array() == Array::null()) {
Zone* const zone = thread->zone();
bytecode::BytecodeRecordedCoverageIterator it(zone, *this);
const auto& array =
Array::Handle(zone, Array::New(2 * it.NumEntries(), Heap::kOld));
auto& smi = Smi::Handle(zone);
// The coverage array has two consecutive entries for each logical
// index: the encoded coverage position and the hit count.
for (intptr_t i = 0; it.MoveNext(); i += 2) {
smi = Smi::New(it.EncodedCoveragePosition());
array.SetAt(i, smi);
smi = Smi::New(0);
array.SetAt(i + 1, smi);
}
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
if (coverage_array() == Array::null()) {
untag()->set_coverage_array(array.ptr());
}
}
return coverage_array();
#else
UNREACHABLE();
#endif
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
const char* Bytecode::ToCString() const {
+18
View File
@@ -7619,6 +7619,16 @@ class Bytecode : public Object {
// Will compute local var descriptors if necessary.
LocalVarDescriptorsPtr GetLocalVarDescriptors() const;
intptr_t recorded_coverage_binary_offset() const {
return untag()->recorded_coverage_binary_offset_;
}
void set_recorded_coverage_binary_offset(intptr_t value) const {
StoreNonPointer(&untag()->recorded_coverage_binary_offset_, value);
}
ArrayPtr coverage_array() const { return untag()->coverage_array(); }
ArrayPtr EnsureCoverageArray(Thread* thread) const;
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
bool HasLocalVariablesInfo() const {
@@ -7629,6 +7639,14 @@ class Bytecode : public Object {
#endif
}
bool HasRecordedCoverage() const {
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
return (recorded_coverage_binary_offset() != 0);
#else
return false;
#endif
}
const char* Name() const;
const char* QualifiedName() const;
const char* FullyQualifiedName() const;
+2
View File
@@ -2118,6 +2118,7 @@ class UntaggedBytecode : public UntaggedObject {
COMPRESSED_POINTER_FIELD(ExceptionHandlersPtr, exception_handlers);
COMPRESSED_POINTER_FIELD(PcDescriptorsPtr, pc_descriptors);
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
COMPRESSED_POINTER_FIELD(ArrayPtr, coverage_array);
COMPRESSED_POINTER_FIELD(LocalVarDescriptorsPtr, var_descriptors);
VISIT_TO(var_descriptors);
#else
@@ -2145,6 +2146,7 @@ class UntaggedBytecode : public UntaggedObject {
int32_t source_positions_binary_offset_;
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
int32_t local_variables_binary_offset_;
int32_t recorded_coverage_binary_offset_;
#endif
static bool ContainsPC(ObjectPtr raw_obj, uword pc);
+16
View File
@@ -4935,6 +4935,22 @@ DEFINE_RUNTIME_ENTRY(ResumeInterpreter, 3) {
#endif // defined(DART_DYNAMIC_MODULES)
}
// Lazily allocates a coverage array for bytecode prior to recording coverage.
//
// Arg0: Bytecode object that needs an allocated coverage array.
DEFINE_RUNTIME_ENTRY(AllocateBytecodeCoverageArray, 1) {
#if defined(DART_DYNAMIC_MODULES) && !defined(PRODUCT) && \
!defined(DART_PRECOMPILED_RUNTIME)
const auto& bytecode = Bytecode::CheckedHandle(zone, arguments.ArgAt(0));
const auto& coverage_array =
Array::Handle(zone, bytecode.EnsureCoverageArray(thread));
arguments.SetReturn(coverage_array);
#else
UNREACHABLE();
#endif // defined(DART_DYNAMIC_MODULES) && !defined(PRODUCT) &&
// !defined(DART_PRECOMPILED_RUNTIME)
}
DEFINE_RUNTIME_ENTRY(FatalError, 1) {
const String& message = String::CheckedHandle(zone, arguments.ArgAt(0));
FATAL("%s", message.ToCString());
+2 -1
View File
@@ -90,7 +90,8 @@ namespace dart {
V(ResumeInterpreter) \
V(InitializeSharedField) \
V(FatalError) \
V(EnsureDeeplyImmutable)
V(EnsureDeeplyImmutable) \
V(AllocateBytecodeCoverageArray)
// Note: Leaf runtime function have C linkage, so they cannot pass C++ struct
// values like ObjectPtr.
+55 -54
View File
@@ -137,7 +137,7 @@ bool SourceReport::ShouldSkipFunction(const Function& func) {
func.is_synthetic() || func.IsRedirectingFactory()) {
return true;
}
if (func.IsNonImplicitClosureFunction() &&
if (func.IsNonImplicitClosureFunction() && !func.HasBytecode() &&
(func.context_scope() == ContextScope::null())) {
// TODO(iposva): This can arise if we attempt to compile an inner function
// before we have compiled its enclosing function or if the enclosing
@@ -332,14 +332,13 @@ intptr_t SourceReport::GetTokenPosOrLine(const Script& script,
}
void SourceReport::PrintCoverageData(JSONObject* jsobj,
const Script& script,
intptr_t script_index,
const Function& function,
bool report_branch_coverage) {
const TokenPosition& begin_pos = function.token_pos();
const TokenPosition& end_pos = function.end_token_pos();
const Script& script = Script::Handle(zone(), function.script());
bool const_constructor_hit = false;
if (function.IsFunction() && function.is_const()) {
for (TokenPosition hit :
@@ -424,6 +423,7 @@ void SourceReport::PrintCoverageData(JSONObject* jsobj,
}
void SourceReport::PrintPossibleBreakpointsData(JSONObject* jsobj,
const Script& script,
const Function& func,
const Code& code) {
const TokenPosition& begin_pos = func.token_pos();
@@ -431,7 +431,6 @@ void SourceReport::PrintPossibleBreakpointsData(JSONObject* jsobj,
intptr_t func_length = func.SourceSize() + 1;
BitVector possible(zone(), func_length);
const Script& script = Script::Handle(zone(), func.script());
if (func.HasBytecode()) {
#if defined(DART_DYNAMIC_MODULES)
@@ -539,6 +538,37 @@ void SourceReport::PrintScriptTable(JSONArray* scripts) {
}
}
void SourceReport::VisitCodeOrBytecode(JSONObject* jsobj,
const Script& script,
intptr_t script_index,
const Function& func,
const Code& code,
CompileMode compile_mode) {
ASSERT(!code.IsNull() || func.HasBytecode());
// TODO(sstrickl): Handle call site data for bytecode.
if (IsReportRequested(kCallSites) && !code.IsNull()) {
PrintCallSitesData(jsobj, func, code);
}
if (IsReportRequested(kCoverage)) {
PrintCoverageData(jsobj, script, script_index, func,
/* report_branch_coverage */ false);
}
if (IsReportRequested(kBranchCoverage)) {
PrintCoverageData(jsobj, script, script_index, func,
/* report_branch_coverage */ true);
}
if (IsReportRequested(kPossibleBreakpoints)) {
PrintPossibleBreakpointsData(jsobj, script, func, code);
}
if (IsReportRequested(kProfile)) {
if (auto* const profile_function = profile_.FindFunction(func)) {
if (profile_function->NumSourcePositions() > 0) {
PrintProfileData(jsobj, profile_function);
}
}
}
}
void SourceReport::VisitFunction(JSONArray* jsarr,
const Function& func,
CompileMode compile_mode) {
@@ -555,64 +585,35 @@ void SourceReport::VisitFunction(JSONArray* jsarr,
return;
}
auto& code = Code::Handle(zone());
if (!func.HasBytecode()) {
code = func.unoptimized_code();
if (code.IsNull()) {
if (func.HasCode() || (compile_mode == kForceCompile)) {
const Error& err =
Error::Handle(Compiler::EnsureUnoptimizedCode(thread(), func));
if (!err.IsNull()) {
// Emit an uncompiled range for this function with error information.
JSONObject range(jsarr);
range.AddProperty("scriptIndex", script_index);
range.AddProperty("startPos", begin_pos);
range.AddProperty("endPos", end_pos);
range.AddProperty("compiled", false);
range.AddProperty("error", err);
return;
}
code = func.unoptimized_code();
} else {
// This function has not been compiled yet.
JSONObject range(jsarr);
range.AddProperty("scriptIndex", script_index);
range.AddProperty("startPos", begin_pos);
range.AddProperty("endPos", end_pos);
range.AddProperty("compiled", false);
return;
}
auto& code = Code::Handle(zone(), func.unoptimized_code());
auto& err = Error::Handle(zone());
bool is_compiled = !code.IsNull();
if (func.HasBytecode()) {
ASSERT(code.IsNull());
// We treat unexecuted bytecode as "uncompiled" unless force compilation was
// requested, to match the reports for compiled code.
is_compiled = func.WasExecuted() || compile_mode == kForceCompile;
} else if (code.IsNull() &&
(func.HasCode() || (compile_mode == kForceCompile))) {
err = Compiler::EnsureUnoptimizedCode(thread(), func);
if (err.IsNull()) {
is_compiled = true;
code = func.unoptimized_code();
} else {
// Emit an uncompiled range for this function with error information.
}
}
ASSERT(!code.IsNull() || func.HasBytecode());
JSONObject range(jsarr);
range.AddProperty("scriptIndex", script_index);
range.AddProperty("startPos", begin_pos);
range.AddProperty("endPos", end_pos);
range.AddProperty("compiled", true);
// TODO(sstrickl): Handle call site data for bytecode.
if (IsReportRequested(kCallSites) && !code.IsNull()) {
PrintCallSitesData(&range, func, code);
range.AddProperty("compiled", is_compiled);
if (!err.IsNull()) {
range.AddProperty("error", err);
}
if (IsReportRequested(kCoverage)) {
PrintCoverageData(&range, script_index, func,
/* report_branch_coverage */ false);
}
if (IsReportRequested(kBranchCoverage)) {
PrintCoverageData(&range, script_index, func,
/* report_branch_coverage */ true);
}
if (IsReportRequested(kPossibleBreakpoints)) {
PrintPossibleBreakpointsData(&range, func, code);
}
if (IsReportRequested(kProfile)) {
ProfileFunction* profile_function = profile_.FindFunction(func);
if ((profile_function != nullptr) &&
(profile_function->NumSourcePositions() > 0)) {
PrintProfileData(&range, profile_function);
}
if (is_compiled) {
VisitCodeOrBytecode(&range, script, script_index, func, code, compile_mode);
}
}
+8
View File
@@ -88,10 +88,12 @@ class SourceReport {
const Function& func,
const Code& code);
void PrintCoverageData(JSONObject* jsobj,
const Script& script,
intptr_t script_index,
const Function& func,
bool report_branch_coverage);
void PrintPossibleBreakpointsData(JSONObject* jsobj,
const Script& script,
const Function& func,
const Code& code);
void PrintProfileData(JSONObject* jsobj, ProfileFunction* profile_function);
@@ -103,6 +105,12 @@ class SourceReport {
void VisitFunction(JSONArray* jsarr,
const Function& func,
CompileMode compile_mode);
void VisitCodeOrBytecode(JSONObject* jsobj,
const Script& script,
intptr_t script_index,
const Function& func,
const Code& code,
CompileMode compile_mode);
void VisitField(JSONArray* jsarr,
const Field& field,
CompileMode compile_mode);
+1 -1
View File
@@ -937,7 +937,7 @@ ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue47017_Assert) {
ISOLATE_UNIT_TEST_CASE(SourceReport_Coverage_Issue47021_StaticOnlyClasses) {
// WARNING: This MUST be big enough for the serialized JSON string.
const int kBufferSize = 2048;
const int kBufferSize = 4096;
char buffer[kBufferSize];
const char* kScript =
"abstract class AllStatic {\n"