// 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 = 3; 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, // Closure allocation and access. kAllocateClosure, kAllocateClosure_Wide, kLoadClosureElement, kLoadClosureElement_Wide, kStoreClosureElement, kStoreClosureElement_Wide, // 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, // Coverage kRecordCoverage, kRecordCoverage_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.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.kD, const [ Operand.lit, Operand.none, Operand.none, ]), Opcode.kLoadClosureElement: const Format(Encoding.kD, const [ Operand.imm, Operand.none, Operand.none, ]), Opcode.kStoreClosureElement: const Format(Encoding.kD, const [ Operand.imm, 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, ]), Opcode.kRecordCoverage: const Format(Encoding.kAE, const [ Operand.imm, Operand.imm, 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 {}