| // 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 emitDup() { | |
| _emitInstruction0(Opcode.kDup); | |
| } | |
| @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 emitResolveNativeFunction(int rd) { | |
| _emitInstructionD(Opcode.kResolveNativeFunction, 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 explicitly created by the bytecode generator in only one case | |
| // (calls to dart:developer::debugger). Otherwise, they are introduced 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); | |
| } | |
| } |