// 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, kDebugCheck, 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, kUnused23, // Reserved for InterfaceCall1 kUnused24, // Reserved for InterfaceCall1_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, } /// 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 operands; const Format(this.encoding, this.operands); } const Map 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.kDebugCheck: 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.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]), }; // 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 {}