Files
sdk/pkg/vm/lib/bytecode/dbc.dart
T
Tess Strickland 787446213c [vm/compiler] Move argument shape (not type) checks out of closures.
This CL performs the following checks in the invoke field dispatcher for
dynamic closure calls when lazy dispatchers are enabled:

* The provided function type arguments vector (if any) has the correct
  length.

* No function type arguments should be provided if the closure has
  delayed type arguments.

* All required arguments (positional in all modes, named in appropriate
  null safety modes) have been provided by the caller.

* If there are optional positional arguments, an appropriate number
  has been provided.

* If there are optional named arguments, their names are valid.

Since the runtime already handles checking the argument shapes when lazy
dispatchers are disabled, these checks are now completely removed from
closure bodies in all cases. Thus, the only remaining checks in closure
bodies are the type checks performed by AssertSubtype and
AssertAssignable when lazy dispatchers are enabled.

Changes in the Flutter Gallery:

* ARM7, release: -3.61% instructions, -2.19% total
* ARM7, sizeopt: -3.62% instructions, -2.55% total
* ARM8, release: -3.66% instructions, -1.98% total
* ARM8, sizeopt: -3.65% instructions, -2.37% total

Most of these changes are already exercised by existing tests such as
(but not limited to):

* corelib{,_2}/dynamic_nosuchmethod_test
* language{,_2}/call/call_test
* language{,_2}/closure/tearoff_dynamic_test
* language{,_2}/generic/function_bounds_test
* language{,_2}/parameter/named_with_conversions_test
* language{,_2}/vm/no_such_args_error_message_vm_test

I've added one test to specifically check the interaction between
dynamic calls and required named parameters. There is some coverage in
other NNBD tests, but those are not directly focused on testing this
specifically.

Other changes:

* Adds initial cached ranges for certain BinarySmiOp and ShiftIntegerOp
  instructions when the RHS is a constant, to avoid false negatives for
  deoptimization and throw checks prior to range analysis.

* Adds new slots for various Function fields.

* Adds the ability to define unboxed native slots, which are always
  unboxed after retrieval even in unoptimized code. In the first
  iteration, the backend only handles loads from Uint32 unboxed native
  slots. Part of https://github.com/dart-lang/sdk/issues/42793.

* Removed the special handling for loading from non-nullable int fields
  in AOT compilation. Instead, their treatment is unified with the
  treatment of the new unboxed native fields, since the source field is
  always unboxed and the result of the load is also always unboxed, as
  code involving them is always optimized.

Bug: https://github.com/dart-lang/sdk/issues/40813
Change-Id: Ia02aa3e872c1fefd906fd67b55021ea1797556e4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155604
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
2020-08-17 08:58:08 +00:00

678 lines
21 KiB
Dart

// Copyright (c) 2018, 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 vm.bytecode.dbc;
/// Version of bytecode format, produced by default.
/// Before bumping current bytecode version format, make sure that
/// all users have switched to a VM which is able to consume new
/// version of bytecode.
const int currentBytecodeFormatVersion = 28;
enum Opcode {
kUnusedOpcode000,
kUnusedOpcode001,
kUnusedOpcode002,
kUnusedOpcode003,
kUnusedOpcode004,
kUnusedOpcode005,
kUnusedOpcode006,
kUnusedOpcode007,
kUnusedOpcode008,
kUnusedOpcode009,
kUnusedOpcode010,
kUnusedOpcode011,
kUnusedOpcode012,
kUnusedOpcode013,
kUnusedOpcode014,
kUnusedOpcode015,
kUnusedOpcode016,
kUnusedOpcode017,
kUnusedOpcode018,
kUnusedOpcode019,
kUnusedOpcode020,
kUnusedOpcode021,
kUnusedOpcode022,
kUnusedOpcode023,
kUnusedOpcode024,
kUnusedOpcode025,
kUnusedOpcode026,
kUnusedOpcode027,
kUnusedOpcode028,
kUnusedOpcode029,
kUnusedOpcode030,
kUnusedOpcode031,
kUnusedOpcode032,
kUnusedOpcode033,
kUnusedOpcode034,
kUnusedOpcode035,
kUnusedOpcode036,
kUnusedOpcode037,
kUnusedOpcode038,
kUnusedOpcode039,
kUnusedOpcode040,
kUnusedOpcode041,
kUnusedOpcode042,
kUnusedOpcode043,
kUnusedOpcode044,
kUnusedOpcode045,
kUnusedOpcode046,
kUnusedOpcode047,
kUnusedOpcode048,
kUnusedOpcode049,
kUnusedOpcode050,
kUnusedOpcode051,
kUnusedOpcode052,
kUnusedOpcode053,
kUnusedOpcode054,
kUnusedOpcode055,
kUnusedOpcode056,
kUnusedOpcode057,
kUnusedOpcode058,
kUnusedOpcode059,
kUnusedOpcode060,
kUnusedOpcode061,
kUnusedOpcode062,
kUnusedOpcode063,
kUnusedOpcode064,
kUnusedOpcode065,
kUnusedOpcode066,
kUnusedOpcode067,
kUnusedOpcode068,
kUnusedOpcode069,
kUnusedOpcode070,
kUnusedOpcode071,
kUnusedOpcode072,
kUnusedOpcode073,
kUnusedOpcode074,
kUnusedOpcode075,
kUnusedOpcode076,
kUnusedOpcode077,
kUnusedOpcode078,
kUnusedOpcode079,
kUnusedOpcode080,
kUnusedOpcode081,
// Late variables.
kJumpIfInitialized,
kJumpIfInitialized_Wide,
kPushUninitializedSentinel,
kTrap,
// Prologue and stack management.
kEntry,
kEntry_Wide,
// TODO(alexmarkov): cleanup now unused EntryFixed instruction.
kEntryFixed,
kEntryFixed_Wide,
kEntryOptional,
kUnused00, // Reserved for EntryNoLocals.
kLoadConstant,
kLoadConstant_Wide,
kFrame,
kFrame_Wide,
kCheckFunctionTypeArgs,
kCheckFunctionTypeArgs_Wide,
kCheckStack,
kDebugCheck,
kJumpIfUnchecked,
kJumpIfUnchecked_Wide,
// Object allocation.
kAllocate,
kAllocate_Wide,
kAllocateT,
kCreateArrayTOS,
kAllocateClosure,
kAllocateClosure_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 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,
// 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,
kNativeCall,
kNativeCall_Wide,
kReturnTOS,
kUnused29,
// Types and type checks.
kAssertAssignable,
kAssertAssignable_Wide,
kUnused30, // Reserved for AsSimpleType
kUnused31, // Reserved for AsSimpleType_Wide
kAssertBoolean,
kAssertSubtype,
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,
}
/// 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;
}
throw 'Unexpected instruction encoding $encoding';
}
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.kEntryFixed: const Format(
Encoding.kAE, const [Operand.imm, Operand.imm, Operand.none]),
Opcode.kEntryOptional: 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.kInitLateField: const Format(
Encoding.kD, const [Operand.lit, Operand.none, Operand.none]),
Opcode.kPushUninitializedSentinel: 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.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.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.kNativeCall: 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.kAssertBoolean: const Format(
Encoding.kA, const [Operand.imm, Operand.none, 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.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]),
};
// 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;
}
throw 'Unexpected instruction encoding $encoding';
}
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:
case Opcode.kNativeCall:
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 {}