Files
Tess Strickland 52cfd29cbb [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>
2026-05-19 04:27:39 -07:00

855 lines
21 KiB
Dart

// Copyright (c) 2024, 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.
import 'dart:typed_data' show Uint8List;
import 'package:kernel/ast.dart' show TreeNode;
import 'dbc.dart';
import 'exceptions.dart' show ExceptionsTable;
import 'local_variable_table.dart' show LocalVariableTable;
import 'options.dart';
import 'source_positions.dart'
show RecordedCoverageArray, RecordedCoverageType, SourcePositions;
class Label {
final bool allowsBackwardJumps;
List<int>? _jumps = <int>[];
int offset = -1;
Label({this.allowsBackwardJumps = false});
bool get isBound => offset >= 0;
int jumpOperand(int jumpOffset) {
if (isBound) {
if (offset <= jumpOffset && !allowsBackwardJumps) {
throw 'Backward jump to this label is not allowed';
}
// Jump instruction takes a relative offset.
return offset - jumpOffset;
}
_jumps!.add(jumpOffset);
return 0;
}
List<int> bind(int offset) {
assert(!isBound);
this.offset = offset;
final jumps = _jumps!;
_jumps = null;
return jumps;
}
}
class BytecodeAssembler {
static const int kByteMask = 0xFF;
static const int kUint32Mask = 0xFFFFFFFF;
static const int kMinInt8 = -0x80;
static const int kMaxInt8 = 0x7F;
static const int kMinInt24 = -0x800000;
static const int kMaxInt24 = 0x7FFFFF;
static const int kMinInt32 = -0x80000000;
static const int kMaxInt32 = 0x7FFFFFFF;
static const int kInitialCapacity = 32;
int _length = 0;
Uint8List _buffer = new Uint8List(kInitialCapacity);
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,
_recordCoverage = options.recordCoverage;
int get offset => _length;
Uint8List get bytecode => new Uint8List.view(_buffer.buffer, 0, _length);
void bind(Label label) {
final List<int> jumps = label.bind(offset);
for (int jumpOffset in jumps) {
_patchJump(jumpOffset, label.jumpOperand(jumpOffset));
}
if (jumps.isNotEmpty || label.allowsBackwardJumps) {
isUnreachable = false;
}
}
@pragma('vm:prefer-inline')
void _emitSourcePosition() {
final position = currentSourcePosition == TreeNode.noOffset
? SourcePositions.noSourcePosition
: currentSourcePosition;
if (_emitSourcePositions && !isUnreachable) {
bool added = sourcePositions.add(
offset,
position,
currentSourcePositionFlags,
);
if (!added) {
// There's already an entry for the current PC, so emit a Nop instruction
// to provide a new pc offset for the requested source position.
_emitNop();
added = sourcePositions.add(
offset,
position,
currentSourcePositionFlags,
);
assert(added);
}
}
}
@pragma('vm:prefer-inline')
void emitSourcePosition() {
if (currentSourcePosition != TreeNode.noOffset) {
_emitSourcePosition();
}
}
// TreeNode.noOffset (-1) source position on calls is used to mark synthetic
// calls without corresponding source position. Debugger uses the absence of
// source position to distinguish these calls and avoid stopping at them
// while single stepping.
@pragma('vm:prefer-inline')
void emitSourcePositionForCall() => _emitSourcePosition();
void _grow() {
final newSize = _buffer.length << 1;
final newBuffer = new Uint8List(newSize);
newBuffer.setRange(0, _buffer.length, _buffer);
_buffer = newBuffer;
}
void _growAndEmitBytes(
int b0, [
int? b1,
int? b2,
int? b3,
int? b4,
int? b5,
]) {
_grow();
assert(_length + 6 < _buffer.length);
_buffer[_length] = b0;
++_length;
if (b1 != null) {
_buffer[_length] = b1;
++_length;
if (b2 != null) {
_buffer[_length] = b2;
++_length;
if (b3 != null) {
_buffer[_length] = b3;
++_length;
if (b4 != null) {
_buffer[_length] = b4;
++_length;
if (b5 != null) {
_buffer[_length] = b5;
++_length;
}
}
}
}
}
}
@pragma('vm:prefer-inline')
void _emitByte(int abyte) {
assert(_isUint8(abyte));
if (_length < _buffer.length) {
_buffer[_length] = abyte;
++_length;
} else {
_growAndEmitBytes(abyte);
}
}
@pragma('vm:prefer-inline')
void _emitBytes2(int b0, int b1) {
assert(_isUint8(b0) && _isUint8(b1));
if (_length + 1 < _buffer.length) {
_buffer[_length] = b0;
_buffer[_length + 1] = b1;
_length += 2;
} else {
_growAndEmitBytes(b0, b1);
}
}
@pragma('vm:prefer-inline')
void _emitBytes3(int b0, int b1, int b2) {
assert(_isUint8(b0) && _isUint8(b1) && _isUint8(b2));
if (_length + 2 < _buffer.length) {
_buffer[_length] = b0;
_buffer[_length + 1] = b1;
_buffer[_length + 2] = b2;
_length += 3;
} else {
_growAndEmitBytes(b0, b1, b2);
}
}
@pragma('vm:prefer-inline')
void _emitBytes4(int b0, int b1, int b2, int b3) {
assert(_isUint8(b0) && _isUint8(b1) && _isUint8(b2) && _isUint8(b3));
if (_length + 3 < _buffer.length) {
_buffer[_length] = b0;
_buffer[_length + 1] = b1;
_buffer[_length + 2] = b2;
_buffer[_length + 3] = b3;
_length += 4;
} else {
_growAndEmitBytes(b0, b1, b2, b3);
}
}
void _emitBytes5(int b0, int b1, int b2, int b3, int b4) {
assert(
_isUint8(b0) &&
_isUint8(b1) &&
_isUint8(b2) &&
_isUint8(b3) &&
_isUint8(b4),
);
if (_length + 4 < _buffer.length) {
_buffer[_length] = b0;
_buffer[_length + 1] = b1;
_buffer[_length + 2] = b2;
_buffer[_length + 3] = b3;
_buffer[_length + 4] = b4;
_length += 5;
} else {
_growAndEmitBytes(b0, b1, b2, b3, b4);
}
}
void _emitBytes6(int b0, int b1, int b2, int b3, int b4, int b5) {
assert(
_isUint8(b0) &&
_isUint8(b1) &&
_isUint8(b2) &&
_isUint8(b3) &&
_isUint8(b4) &&
_isUint8(b5),
);
if (_length + 5 < _buffer.length) {
_buffer[_length] = b0;
_buffer[_length + 1] = b1;
_buffer[_length + 2] = b2;
_buffer[_length + 3] = b3;
_buffer[_length + 4] = b4;
_buffer[_length + 5] = b5;
_length += 6;
} else {
_growAndEmitBytes(b0, b1, b2, b3, b4, b5);
}
}
int _byteAt(int pos) {
return _buffer[pos];
}
void _setByteAt(int pos, int value) {
assert(_isUint8(value));
_buffer[pos] = value;
}
@pragma('vm:prefer-inline')
int _byte0(int v) => v & kByteMask;
@pragma('vm:prefer-inline')
int _byte1(int v) => (v >> 8) & kByteMask;
@pragma('vm:prefer-inline')
int _byte2(int v) => (v >> 16) & kByteMask;
@pragma('vm:prefer-inline')
int _byte3(int v) => (v >> 24) & kByteMask;
@pragma('vm:prefer-inline')
bool _isInt8(int v) => (kMinInt8 <= v) && (v <= kMaxInt8);
@pragma('vm:prefer-inline')
bool _isInt24(int v) => (kMinInt24 <= v) && (v <= kMaxInt24);
@pragma('vm:prefer-inline')
bool _isInt32(int v) => (kMinInt32 <= v) && (v <= kMaxInt32);
@pragma('vm:prefer-inline')
bool _isUint8(int v) => (v & kByteMask) == v;
@pragma('vm:prefer-inline')
bool _isUint32(int v) => (v & kUint32Mask) == v;
@pragma('vm:prefer-inline')
void _emitInstruction0(Opcode opcode) {
if (isUnreachable) {
return;
}
_emitByte(opcode.index);
}
@pragma('vm:prefer-inline')
void _emitInstructionA(Opcode opcode, int ra) {
if (isUnreachable) {
return;
}
_emitBytes2(opcode.index, ra);
}
@pragma('vm:prefer-inline')
void _emitInstructionD(Opcode opcode, int rd) {
if (isUnreachable) {
return;
}
if (_isUint8(rd)) {
_emitBytes2(opcode.index, rd);
} else {
assert(_isUint32(rd));
_emitBytes5(
opcode.index + kWideModifier,
_byte0(rd),
_byte1(rd),
_byte2(rd),
_byte3(rd),
);
}
}
@pragma('vm:prefer-inline')
void _emitInstructionX(Opcode opcode, int rx) {
if (isUnreachable) {
return;
}
if (_isInt8(rx)) {
_emitBytes2(opcode.index, rx & kByteMask);
} else {
assert(_isInt32(rx));
_emitBytes5(
opcode.index + kWideModifier,
_byte0(rx),
_byte1(rx),
_byte2(rx),
_byte3(rx),
);
}
}
@pragma('vm:prefer-inline')
void _emitInstructionAE(Opcode opcode, int ra, int re) {
if (isUnreachable) {
return;
}
if (_isUint8(re)) {
_emitBytes3(opcode.index, ra, re);
} else {
assert(_isUint32(re));
_emitBytes6(
opcode.index + kWideModifier,
ra,
_byte0(re),
_byte1(re),
_byte2(re),
_byte3(re),
);
}
}
@pragma('vm:prefer-inline')
void _emitInstructionAY(Opcode opcode, int ra, int ry) {
if (isUnreachable) {
return;
}
if (_isInt8(ry)) {
_emitBytes3(opcode.index, ra, ry & kByteMask);
} else {
assert(_isInt32(ry));
_emitBytes6(
opcode.index + kWideModifier,
ra,
_byte0(ry),
_byte1(ry),
_byte2(ry),
_byte3(ry),
);
}
}
@pragma('vm:prefer-inline')
void _emitInstructionDF(Opcode opcode, int rd, int rf) {
if (isUnreachable) {
return;
}
if (_isUint8(rd)) {
_emitBytes3(opcode.index, rd, rf);
} else {
assert(_isUint32(rd));
_emitBytes6(
opcode.index + kWideModifier,
_byte0(rd),
_byte1(rd),
_byte2(rd),
_byte3(rd),
rf,
);
}
}
@pragma('vm:prefer-inline')
void _emitInstructionABC(Opcode opcode, int ra, int rb, int rc) {
if (isUnreachable) {
return;
}
_emitBytes4(opcode.index, ra, rb, rc);
}
@pragma('vm:prefer-inline')
void emitSpecializedBytecode(Opcode opcode) {
assert(BytecodeFormats[opcode]!.encoding == Encoding.k0);
emitSourcePosition();
_emitInstruction0(opcode);
}
@pragma('vm:prefer-inline')
void _emitJumpInstruction(Opcode opcode, Label label) {
assert(isJump(opcode));
if (isUnreachable) {
return;
}
final int target = label.jumpOperand(offset);
// Use compact representation only for backwards jumps.
// TODO(alexmarkov): generate compact forward jumps as well.
if (label.isBound && _isInt8(target)) {
_emitBytes2(opcode.index, target & kByteMask);
} else {
assert(_isInt24(target));
_emitBytes4(
opcode.index + kWideModifier,
_byte0(target),
_byte1(target),
_byte2(target),
);
}
}
void _patchJump(int pos, int rt) {
final Opcode opcode = Opcode.values[_byteAt(pos) - kWideModifier];
assert(hasWideVariant(opcode));
assert(isJump(opcode));
assert(_isInt24(rt));
_setByteAt(pos + 1, _byte0(rt));
_setByteAt(pos + 2, _byte1(rt));
_setByteAt(pos + 3, _byte2(rt));
}
void emitTrap() {
_emitInstruction0(Opcode.kTrap);
isUnreachable = true;
}
@pragma('vm:prefer-inline')
void emitDrop1() {
_emitInstruction0(Opcode.kDrop1);
}
@pragma('vm:prefer-inline')
void emitJump(Label label) {
_emitJumpInstruction(Opcode.kJump, label);
isUnreachable = true;
}
@pragma('vm:prefer-inline')
void emitJumpIfNoAsserts(Label label) {
_emitJumpInstruction(Opcode.kJumpIfNoAsserts, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfNotZeroTypeArgs(Label label) {
_emitJumpInstruction(Opcode.kJumpIfNotZeroTypeArgs, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfEqStrict(Label label) {
_emitJumpInstruction(Opcode.kJumpIfEqStrict, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfNeStrict(Label label) {
_emitJumpInstruction(Opcode.kJumpIfNeStrict, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfTrue(Label label) {
_emitJumpInstruction(Opcode.kJumpIfTrue, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfFalse(Label label) {
_emitJumpInstruction(Opcode.kJumpIfFalse, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfNull(Label label) {
_emitJumpInstruction(Opcode.kJumpIfNull, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfNotNull(Label label) {
_emitJumpInstruction(Opcode.kJumpIfNotNull, label);
}
@pragma('vm:prefer-inline')
void emitJumpIfUnchecked(Label label) {
_emitJumpInstruction(Opcode.kJumpIfUnchecked, label);
}
@pragma('vm:prefer-inline')
void emitSuspend(Label label) {
_emitJumpInstruction(Opcode.kSuspend, label);
}
@pragma('vm:prefer-inline')
void emitReturnTOS() {
emitSourcePosition();
_emitInstruction0(Opcode.kReturnTOS);
isUnreachable = true;
}
@pragma('vm:prefer-inline')
void emitPush(int rx) {
_emitInstructionX(Opcode.kPush, rx);
}
@pragma('vm:prefer-inline')
void emitLoadConstant(int ra, int re) {
_emitInstructionAE(Opcode.kLoadConstant, ra, re);
}
@pragma('vm:prefer-inline')
void emitPushConstant(int rd) {
_emitInstructionD(Opcode.kPushConstant, rd);
}
@pragma('vm:prefer-inline')
void emitPushNull() {
_emitInstruction0(Opcode.kPushNull);
}
@pragma('vm:prefer-inline')
void emitPushTrue() {
_emitInstruction0(Opcode.kPushTrue);
}
@pragma('vm:prefer-inline')
void emitPushFalse() {
_emitInstruction0(Opcode.kPushFalse);
}
@pragma('vm:prefer-inline')
void emitPushInt(int rx) {
_emitInstructionX(Opcode.kPushInt, rx);
}
@pragma('vm:prefer-inline')
void emitStoreLocal(int rx) {
_emitInstructionX(Opcode.kStoreLocal, rx);
}
@pragma('vm:prefer-inline')
void emitPopLocal(int rx) {
_emitInstructionX(Opcode.kPopLocal, rx);
}
@pragma('vm:prefer-inline')
void emitDirectCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kDirectCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitUncheckedDirectCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kUncheckedDirectCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitInterfaceCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kInterfaceCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitInstantiatedInterfaceCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kInstantiatedInterfaceCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitUncheckedClosureCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kUncheckedClosureCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitUncheckedInterfaceCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kUncheckedInterfaceCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitDynamicCall(int rd, int rf) {
emitSourcePositionForCall();
_emitInstructionDF(Opcode.kDynamicCall, rd, rf);
}
@pragma('vm:prefer-inline')
void emitExternalCall(int rd) {
_emitInstructionD(Opcode.kExternalCall, rd);
}
@pragma('vm:prefer-inline')
void emitFfiCall(int rd) {
_emitInstructionD(Opcode.kFfiCall, rd);
}
@pragma('vm:prefer-inline')
void emitLoadStatic(int rd) {
_emitInstructionD(Opcode.kLoadStatic, rd);
}
@pragma('vm:prefer-inline')
void emitStoreStaticTOS(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kStoreStaticTOS, rd);
}
@pragma('vm:prefer-inline')
void emitCreateArrayTOS() {
_emitInstruction0(Opcode.kCreateArrayTOS);
}
@pragma('vm:prefer-inline')
void emitAllocate(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kAllocate, rd);
}
@pragma('vm:prefer-inline')
void emitAllocateT() {
emitSourcePosition();
_emitInstruction0(Opcode.kAllocateT);
}
@pragma('vm:prefer-inline')
void emitStoreIndexedTOS() {
_emitInstruction0(Opcode.kStoreIndexedTOS);
}
@pragma('vm:prefer-inline')
void emitStoreFieldTOS(int rd) {
_emitInstructionD(Opcode.kStoreFieldTOS, rd);
}
@pragma('vm:prefer-inline')
void emitStoreContextParent() {
_emitInstruction0(Opcode.kStoreContextParent);
}
@pragma('vm:prefer-inline')
void emitStoreContextVar(int ra, int re) {
_emitInstructionAE(Opcode.kStoreContextVar, ra, re);
}
@pragma('vm:prefer-inline')
void emitLoadFieldTOS(int rd) {
_emitInstructionD(Opcode.kLoadFieldTOS, rd);
}
@pragma('vm:prefer-inline')
void emitLoadTypeArgumentsField(int rd) {
_emitInstructionD(Opcode.kLoadTypeArgumentsField, rd);
}
@pragma('vm:prefer-inline')
void emitLoadContextParent() {
_emitInstruction0(Opcode.kLoadContextParent);
}
@pragma('vm:prefer-inline')
void emitLoadContextVar(int ra, int re) {
_emitInstructionAE(Opcode.kLoadContextVar, ra, re);
}
@pragma('vm:prefer-inline')
void emitBooleanNegateTOS() {
_emitInstruction0(Opcode.kBooleanNegateTOS);
}
@pragma('vm:prefer-inline')
void emitThrow(int ra) {
emitSourcePosition();
_emitInstructionA(Opcode.kThrow, ra);
isUnreachable = true;
}
@pragma('vm:prefer-inline')
void emitEntry(int rd) {
_emitInstructionD(Opcode.kEntry, rd);
}
@pragma('vm:prefer-inline')
void emitFrame(int rd) {
_emitInstructionD(Opcode.kFrame, rd);
}
@pragma('vm:prefer-inline')
void emitSetFrame(int ra) {
_emitInstructionA(Opcode.kSetFrame, ra);
}
@pragma('vm:prefer-inline')
void emitAllocateContext(int ra, int re) {
_emitInstructionAE(Opcode.kAllocateContext, ra, re);
}
@pragma('vm:prefer-inline')
void emitCloneContext(int ra, int re) {
_emitInstructionAE(Opcode.kCloneContext, ra, re);
}
@pragma('vm:prefer-inline')
void emitMoveSpecial(SpecialIndex ra, int ry) {
_emitInstructionAY(Opcode.kMoveSpecial, ra.index, ry);
}
@pragma('vm:prefer-inline')
void emitInstantiateType(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kInstantiateType, rd);
}
@pragma('vm:prefer-inline')
void emitInstantiateTypeArgumentsTOS(int ra, int re) {
emitSourcePosition();
_emitInstructionAE(Opcode.kInstantiateTypeArgumentsTOS, ra, re);
}
@pragma('vm:prefer-inline')
void emitAssertAssignable(int ra, int re) {
emitSourcePosition();
_emitInstructionAE(Opcode.kAssertAssignable, ra, re);
}
@pragma('vm:prefer-inline')
void emitAssertSubtype() {
emitSourcePosition();
_emitInstruction0(Opcode.kAssertSubtype);
}
@pragma('vm:prefer-inline')
void emitCheckStack(int ra) {
emitSourcePosition();
_emitInstructionA(Opcode.kCheckStack, ra);
}
// Nops are never created by the bytecode generator, but rather by
// the assembler in order to provide unique pc offsets for source
// positions.
@pragma('vm:prefer-inline')
void _emitNop() {
_emitInstruction0(Opcode.kNop);
}
@pragma('vm:prefer-inline')
void emitCheckFunctionTypeArgs(int ra, int re) {
emitSourcePosition();
_emitInstructionAE(Opcode.kCheckFunctionTypeArgs, ra, re);
}
@pragma('vm:prefer-inline')
void emitEntryOptional(int ra, int rb, int rc) {
_emitInstructionABC(Opcode.kEntryOptional, ra, rb, rc);
}
@pragma('vm:prefer-inline')
void emitEntrySuspendable(int ra, int rb, int rc) {
_emitInstructionABC(Opcode.kEntrySuspendable, ra, rb, rc);
}
@pragma('vm:prefer-inline')
void emitAllocateClosure(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kAllocateClosure, rd);
}
@pragma('vm:prefer-inline')
void emitLoadClosureElement(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kLoadClosureElement, rd);
}
@pragma('vm:prefer-inline')
void emitStoreClosureElement(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kStoreClosureElement, rd);
}
@pragma('vm:prefer-inline')
void emitNullCheck(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kNullCheck, rd);
}
@pragma('vm:prefer-inline')
void emitInitLateField(int rd) {
emitSourcePosition();
_emitInstructionD(Opcode.kInitLateField, rd);
}
@pragma('vm:prefer-inline')
void emitPushUninitializedSentinel() {
_emitInstruction0(Opcode.kPushUninitializedSentinel);
}
@pragma('vm:prefer-inline')
void emitJumpIfInitialized(Label label) {
_emitJumpInstruction(Opcode.kJumpIfInitialized, label);
}
@pragma('vm:prefer-inline')
void emitAllocateRecord(int rd) {
_emitInstructionD(Opcode.kAllocateRecord, rd);
}
@pragma('vm:prefer-inline')
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);
}
}