fe6ea5e39d
Normally, bytecodes no longer in use would be renamed to Unused<X>. However, in the case of DebugCheck, its only use was to call the debugger when single stepping, and since its original creation, the interpreter has been changed to call the debugger when single stepping on every instruction. Thus, DebugCheck instructions are effectively no-ops, only used as a distinct PC offset for source positions, and this CL changes their name to reflect this. This CL also changes the bytecode generator to detect uses of debugger() from dart:developer and to add a Nop after it, mimicking how StreamingFlowGraphBuilder recognizes uses of debugger() and adds a DebugStepCheck instruction afterwards. Doing this instead of just using asm.emitSourcePosition() at the end of visitStaticInvocation ensures that the source position isn't overwritten by the next emitted instruction. TEST=pkg/vm_service/test/set_sdk_library_debuggable_test Cq-Include-Trybots: luci.dart.try:vm-dyn-linux-debug-x64-try,vm-dyn-mac-debug-arm64-try Change-Id: Ie24bcea0b5aeb9e41d7765f25b1cd123bb2565b4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/480203 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
603 lines
20 KiB
Dart
603 lines
20 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.
|
|
|
|
/// Dart kernel bytecode instructions (described in runtime/vm/constants_kbc.h).
|
|
library;
|
|
|
|
/// Version of bytecode format
|
|
/// (should match runtime/vm/constants_kbc.h).
|
|
const int bytecodeFormatVersion = 1;
|
|
|
|
enum Opcode {
|
|
kTrap,
|
|
kUnused00,
|
|
|
|
// Prologue and stack management.
|
|
kEntry,
|
|
kEntry_Wide,
|
|
kEntryOptional,
|
|
kEntrySuspendable,
|
|
kLoadConstant,
|
|
kLoadConstant_Wide,
|
|
kFrame,
|
|
kFrame_Wide,
|
|
kCheckFunctionTypeArgs,
|
|
kCheckFunctionTypeArgs_Wide,
|
|
kCheckStack,
|
|
kNop,
|
|
kJumpIfUnchecked,
|
|
kJumpIfUnchecked_Wide,
|
|
|
|
// Object allocation.
|
|
kAllocate,
|
|
kAllocate_Wide,
|
|
kAllocateT,
|
|
kCreateArrayTOS,
|
|
kAllocateClosure,
|
|
kUnused03,
|
|
|
|
// Context allocation and access.
|
|
kAllocateContext,
|
|
kAllocateContext_Wide,
|
|
kCloneContext,
|
|
kCloneContext_Wide,
|
|
kLoadContextParent,
|
|
kStoreContextParent,
|
|
kLoadContextVar,
|
|
kLoadContextVar_Wide,
|
|
kUnused04, // Reserved for LoadContextVar0
|
|
kUnused05,
|
|
kStoreContextVar,
|
|
kStoreContextVar_Wide,
|
|
|
|
// Constants.
|
|
kPushConstant,
|
|
kPushConstant_Wide,
|
|
kUnused06, // Reserved for PushConstant0
|
|
kUnused07,
|
|
kPushTrue,
|
|
kPushFalse,
|
|
kPushInt,
|
|
kPushInt_Wide,
|
|
kUnused08, // Reserved for PushInt0
|
|
kUnused09, // Reserved for PushInt1
|
|
kUnused10, // Reserved for PushInt2
|
|
kUnused11,
|
|
kPushNull,
|
|
|
|
// Locals and expression stack.
|
|
kDrop1,
|
|
kPush,
|
|
kPush_Wide,
|
|
kUnused12, // Reserved for PushLocal0
|
|
kUnused13, // Reserved for PushLocal1
|
|
kUnused14, // Reserved for PushLocal2
|
|
kUnused15, // Reserved for PushLocal3
|
|
kUnused16, // Reserved for PushParamLast0
|
|
kUnused17, // Reserved for PushParamLast1
|
|
kPopLocal,
|
|
kPopLocal_Wide,
|
|
kLoadStatic,
|
|
kLoadStatic_Wide,
|
|
kStoreLocal,
|
|
kStoreLocal_Wide,
|
|
|
|
// Instance fields and arrays.
|
|
kLoadFieldTOS,
|
|
kLoadFieldTOS_Wide,
|
|
kStoreFieldTOS,
|
|
kStoreFieldTOS_Wide,
|
|
kStoreIndexedTOS,
|
|
kUnused20,
|
|
|
|
// Late variables.
|
|
kJumpIfInitialized,
|
|
kJumpIfInitialized_Wide,
|
|
kPushUninitializedSentinel,
|
|
kUnused21,
|
|
|
|
// Late fields.
|
|
kInitLateField,
|
|
kInitLateField_Wide,
|
|
|
|
// Static fields.
|
|
kStoreStaticTOS,
|
|
kStoreStaticTOS_Wide,
|
|
|
|
// Jumps.
|
|
kJump,
|
|
kJump_Wide,
|
|
kJumpIfNoAsserts,
|
|
kJumpIfNoAsserts_Wide,
|
|
kJumpIfNotZeroTypeArgs,
|
|
kJumpIfNotZeroTypeArgs_Wide,
|
|
kJumpIfEqStrict,
|
|
kJumpIfEqStrict_Wide,
|
|
kJumpIfNeStrict,
|
|
kJumpIfNeStrict_Wide,
|
|
kJumpIfTrue,
|
|
kJumpIfTrue_Wide,
|
|
kJumpIfFalse,
|
|
kJumpIfFalse_Wide,
|
|
kJumpIfNull,
|
|
kJumpIfNull_Wide,
|
|
kJumpIfNotNull,
|
|
kJumpIfNotNull_Wide,
|
|
|
|
kSuspend,
|
|
kSuspend_Wide,
|
|
|
|
// Calls.
|
|
kDirectCall,
|
|
kDirectCall_Wide,
|
|
kUncheckedDirectCall,
|
|
kUncheckedDirectCall_Wide,
|
|
kInterfaceCall,
|
|
kInterfaceCall_Wide,
|
|
kExternalCall,
|
|
kExternalCall_Wide,
|
|
kInstantiatedInterfaceCall,
|
|
kInstantiatedInterfaceCall_Wide,
|
|
kUncheckedClosureCall,
|
|
kUncheckedClosureCall_Wide,
|
|
kUncheckedInterfaceCall,
|
|
kUncheckedInterfaceCall_Wide,
|
|
kDynamicCall,
|
|
kDynamicCall_Wide,
|
|
kReturnTOS,
|
|
kUnused25,
|
|
|
|
// Types and type checks.
|
|
kAssertAssignable,
|
|
kAssertAssignable_Wide,
|
|
kAssertSubtype,
|
|
kUnused30,
|
|
kLoadTypeArgumentsField,
|
|
kLoadTypeArgumentsField_Wide,
|
|
kInstantiateType,
|
|
kInstantiateType_Wide,
|
|
kInstantiateTypeArgumentsTOS,
|
|
kInstantiateTypeArgumentsTOS_Wide,
|
|
kUnused32, // Reserved for IsType
|
|
kUnused33, // Reserved for IsType_Wide
|
|
kUnused34, // Reserved for IsSimpleType
|
|
kUnused35, // Reserved for IsSimpleType_Wide
|
|
|
|
// Exception handling.
|
|
kThrow,
|
|
kSetFrame,
|
|
kMoveSpecial,
|
|
kMoveSpecial_Wide,
|
|
|
|
// Bool operations.
|
|
kBooleanNegateTOS,
|
|
|
|
// Null operations.
|
|
kEqualsNull,
|
|
kNullCheck,
|
|
kNullCheck_Wide,
|
|
|
|
// Int operations.
|
|
kNegateInt,
|
|
kAddInt,
|
|
kSubInt,
|
|
kMulInt,
|
|
kTruncDivInt,
|
|
kModInt,
|
|
kBitAndInt,
|
|
kBitOrInt,
|
|
kBitXorInt,
|
|
kShlInt,
|
|
kShrInt,
|
|
kCompareIntEq,
|
|
kCompareIntGt,
|
|
kCompareIntLt,
|
|
kCompareIntGe,
|
|
kCompareIntLe,
|
|
|
|
// Double operations.
|
|
kNegateDouble,
|
|
kAddDouble,
|
|
kSubDouble,
|
|
kMulDouble,
|
|
kDivDouble,
|
|
kCompareDoubleEq,
|
|
kCompareDoubleGt,
|
|
kCompareDoubleLt,
|
|
kCompareDoubleGe,
|
|
kCompareDoubleLe,
|
|
|
|
// Records
|
|
kAllocateRecord,
|
|
kAllocateRecord_Wide,
|
|
kLoadRecordField,
|
|
kLoadRecordField_Wide,
|
|
|
|
// FFI
|
|
kFfiCall,
|
|
kFfiCall_Wide,
|
|
}
|
|
|
|
/// Compact variants of opcodes are always even.
|
|
/// Wide variant = opcode + kWideModifier.
|
|
const int kWideModifier = 1;
|
|
|
|
/// Opcode should fit into 1 byte.
|
|
const int kMaxOpcodes = 256;
|
|
|
|
enum Encoding {
|
|
k0, // No operands.
|
|
kA, // 1 operand: A = 8-bit unsigned.
|
|
kD, // 1 operand: D = 8/32-bit unsigned.
|
|
kX, // 1 operand: X = 8/32-bit signed.
|
|
kT, // 1 operand: T = 8/24-bit signed.
|
|
kAE, // 2 operands: A = 8-bit unsigned, E = 8/32-bit unsigned
|
|
kAY, // 2 operands: A = 8-bit unsigned, Y = 8/32-bit signed
|
|
kDF, // 2 operands: D = 8/32-bit unsigned, F = 8-bit unsigned
|
|
kABC, // 3 operands: A, B, C - 8-bit unsigned.
|
|
}
|
|
|
|
int instructionSize(Encoding encoding, bool isWide) {
|
|
switch (encoding) {
|
|
case Encoding.k0:
|
|
return 1;
|
|
case Encoding.kA:
|
|
return 2;
|
|
case Encoding.kD:
|
|
return isWide ? 5 : 2;
|
|
case Encoding.kX:
|
|
return isWide ? 5 : 2;
|
|
case Encoding.kT:
|
|
return isWide ? 4 : 2;
|
|
case Encoding.kAE:
|
|
return isWide ? 6 : 3;
|
|
case Encoding.kAY:
|
|
return isWide ? 6 : 3;
|
|
case Encoding.kDF:
|
|
return isWide ? 6 : 3;
|
|
case Encoding.kABC:
|
|
return 4;
|
|
}
|
|
}
|
|
|
|
enum Operand {
|
|
none, // ignored / non-existent operand
|
|
imm, // immediate operand
|
|
lit, // constant literal from object pool
|
|
reg, // register (unsigned FP relative local)
|
|
xeg, // x-register (signed FP relative local)
|
|
tgt, // jump target relative to the PC of the current instruction
|
|
spe, // SpecialIndex
|
|
}
|
|
|
|
class Format {
|
|
final Encoding encoding;
|
|
final List<Operand> operands;
|
|
const Format(this.encoding, this.operands);
|
|
}
|
|
|
|
const Map<Opcode, Format> BytecodeFormats = const {
|
|
Opcode.kTrap: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kEntry: const Format(
|
|
Encoding.kD, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kEntryOptional: const Format(
|
|
Encoding.kABC, const [Operand.imm, Operand.imm, Operand.imm]),
|
|
Opcode.kEntrySuspendable: const Format(
|
|
Encoding.kABC, const [Operand.imm, Operand.imm, Operand.imm]),
|
|
Opcode.kLoadConstant: const Format(
|
|
Encoding.kAE, const [Operand.reg, Operand.lit, Operand.none]),
|
|
Opcode.kFrame: const Format(
|
|
Encoding.kD, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kCheckFunctionTypeArgs: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.reg, Operand.none]),
|
|
Opcode.kCheckStack: const Format(
|
|
Encoding.kA, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kNop: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kAllocate: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kAllocateT: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCreateArrayTOS: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kAllocateContext: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.imm, Operand.none]),
|
|
Opcode.kCloneContext: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.imm, Operand.none]),
|
|
Opcode.kLoadContextParent: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kStoreContextParent: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kLoadContextVar: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.imm, Operand.none]),
|
|
Opcode.kStoreContextVar: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.imm, Operand.none]),
|
|
Opcode.kPushConstant: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kPushNull: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kPushTrue: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kPushFalse: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kPushInt: const Format(
|
|
Encoding.kX, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kDrop1: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kPush: const Format(
|
|
Encoding.kX, const [Operand.xeg, Operand.none, Operand.none]),
|
|
Opcode.kPopLocal: const Format(
|
|
Encoding.kX, const [Operand.xeg, Operand.none, Operand.none]),
|
|
Opcode.kStoreLocal: const Format(
|
|
Encoding.kX, const [Operand.xeg, Operand.none, Operand.none]),
|
|
Opcode.kLoadFieldTOS: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kStoreFieldTOS: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kStoreIndexedTOS: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfInitialized: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kPushUninitializedSentinel: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kInitLateField: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kLoadStatic: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kStoreStaticTOS: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kJump: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfNoAsserts: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfNotZeroTypeArgs: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfEqStrict: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfNeStrict: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfTrue: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfFalse: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfNull: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfNotNull: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kJumpIfUnchecked: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kSuspend: const Format(
|
|
Encoding.kT, const [Operand.tgt, Operand.none, Operand.none]),
|
|
Opcode.kInterfaceCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kInstantiatedInterfaceCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kDynamicCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kExternalCall: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kReturnTOS: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kAssertAssignable: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.lit, Operand.none]),
|
|
Opcode.kAssertSubtype: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kLoadTypeArgumentsField: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kInstantiateType: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kInstantiateTypeArgumentsTOS: const Format(
|
|
Encoding.kAE, const [Operand.imm, Operand.lit, Operand.none]),
|
|
Opcode.kThrow: const Format(
|
|
Encoding.kA, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kMoveSpecial: const Format(
|
|
Encoding.kAY, const [Operand.spe, Operand.xeg, Operand.none]),
|
|
Opcode.kSetFrame: const Format(
|
|
Encoding.kA, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kBooleanNegateTOS: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kEqualsNull: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kNullCheck: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kNegateInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kAddInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kSubInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kMulInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kTruncDivInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kModInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kBitAndInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kBitOrInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kBitXorInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kShlInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kShrInt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareIntEq: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareIntGt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareIntLt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareIntGe: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareIntLe: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kDirectCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kUncheckedDirectCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kAllocateClosure: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kUncheckedClosureCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kUncheckedInterfaceCall: const Format(
|
|
Encoding.kDF, const [Operand.lit, Operand.imm, Operand.none]),
|
|
Opcode.kNegateDouble: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kAddDouble: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kSubDouble: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kMulDouble: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kDivDouble: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareDoubleEq: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareDoubleGt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareDoubleLt: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareDoubleGe: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kCompareDoubleLe: const Format(
|
|
Encoding.k0, const [Operand.none, Operand.none, Operand.none]),
|
|
Opcode.kAllocateRecord: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
Opcode.kLoadRecordField: const Format(
|
|
Encoding.kD, const [Operand.imm, Operand.none, Operand.none]),
|
|
Opcode.kFfiCall: const Format(
|
|
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
|
|
};
|
|
|
|
// Should match constant in runtime/vm/stack_frame_kbc.h.
|
|
const int kParamEndSlotFromFp = 4;
|
|
|
|
enum SpecialIndex {
|
|
exception,
|
|
stackTrace,
|
|
}
|
|
|
|
/// Returns [true] if there is a wide variant for the given opcode.
|
|
bool hasWideVariant(Opcode opcode) {
|
|
final encoding = BytecodeFormats[opcode]!.encoding;
|
|
switch (encoding) {
|
|
case Encoding.k0:
|
|
case Encoding.kA:
|
|
case Encoding.kABC:
|
|
return false;
|
|
case Encoding.kD:
|
|
case Encoding.kX:
|
|
case Encoding.kT:
|
|
case Encoding.kAE:
|
|
case Encoding.kAY:
|
|
case Encoding.kDF:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool isWideOpcode(Opcode opcode) {
|
|
return (BytecodeFormats[opcode] == null) &&
|
|
hasWideVariant(Opcode.values[opcode.index - kWideModifier]);
|
|
}
|
|
|
|
Opcode fromWideOpcode(Opcode opcode) {
|
|
assert(isWideOpcode(opcode));
|
|
return Opcode.values[opcode.index - kWideModifier];
|
|
}
|
|
|
|
void verifyBytecodeInstructionDeclarations() {
|
|
const String kWideSuffix = '_Wide';
|
|
for (Opcode opcode in Opcode.values) {
|
|
final format = BytecodeFormats[opcode];
|
|
if (opcode.toString().endsWith(kWideSuffix)) {
|
|
if (format != null) {
|
|
throw 'Bytecode format should not be defined for wide opcode $opcode.';
|
|
}
|
|
final Opcode compact = Opcode.values[opcode.index - kWideModifier];
|
|
if (compact.toString() + kWideSuffix != opcode.toString()) {
|
|
throw 'Wide opcode $opcode should immediately follow its compact opcode (previous opcode is $compact).';
|
|
}
|
|
if (!hasWideVariant(compact)) {
|
|
throw 'Wide opcode $opcode should not be defined for opcode $compact with encoding ${BytecodeFormats[compact]!.encoding}.';
|
|
}
|
|
}
|
|
if (format == null) {
|
|
continue;
|
|
}
|
|
if (hasWideVariant(opcode)) {
|
|
if (Opcode.values[opcode.index + kWideModifier].toString() !=
|
|
opcode.toString() + kWideSuffix) {
|
|
throw 'Opcode $opcode$kWideSuffix should immedialy follow $opcode.';
|
|
}
|
|
if (opcode.index.isOdd) {
|
|
throw 'Opcode $opcode (${format.encoding}) has a wide variant and should be even';
|
|
}
|
|
}
|
|
}
|
|
if (Opcode.values.length > kMaxOpcodes) {
|
|
throw 'Too many opcodes';
|
|
}
|
|
}
|
|
|
|
bool isJump(Opcode opcode) => BytecodeFormats[opcode]!.encoding == Encoding.kT;
|
|
|
|
bool isThrow(Opcode opcode) => opcode == Opcode.kThrow;
|
|
|
|
bool isCall(Opcode opcode) {
|
|
switch (opcode) {
|
|
case Opcode.kDirectCall:
|
|
case Opcode.kUncheckedDirectCall:
|
|
case Opcode.kInterfaceCall:
|
|
case Opcode.kInstantiatedInterfaceCall:
|
|
case Opcode.kUncheckedClosureCall:
|
|
case Opcode.kUncheckedInterfaceCall:
|
|
case Opcode.kDynamicCall:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool isReturn(Opcode opcode) => (opcode == Opcode.kReturnTOS);
|
|
|
|
bool isControlFlow(Opcode opcode) =>
|
|
isJump(opcode) || isThrow(opcode) || isCall(opcode) || isReturn(opcode);
|
|
|
|
bool isPush(Opcode opcode) {
|
|
switch (opcode) {
|
|
case Opcode.kPush:
|
|
case Opcode.kPushConstant:
|
|
case Opcode.kPushNull:
|
|
case Opcode.kPushTrue:
|
|
case Opcode.kPushFalse:
|
|
case Opcode.kPushInt:
|
|
case Opcode.kPushUninitializedSentinel:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Bytecode instructions reference constant pool indices using
|
|
// unsigned 32-bit operands.
|
|
const int constantPoolIndexLimit = 1 << 32;
|
|
|
|
// Local variables are referenced using 32-bit signed operands.
|
|
const int localVariableIndexLimit = 1 << 31;
|
|
|
|
// Captured variables are referenced using 32-bit unsigned operands.
|
|
const int capturedVariableIndexLimit = 1 << 32;
|
|
|
|
// Context IDs are referenced using 8-bit unsigned operands.
|
|
const int contextIdLimit = 1 << 8;
|
|
|
|
// Number of arguments is encoded as 8-bit unsigned operand.
|
|
const int argumentsLimit = 1 << 8;
|
|
|
|
// Base class for exceptions thrown when certain limit of bytecode
|
|
// format is exceeded.
|
|
abstract class BytecodeLimitExceededException {}
|