[vm/bytecode] Add versioning to bytecode format

Change-Id: I35d86aec17aa0f6894a6380e4bb5ac7ca3092fac
Reviewed-on: https://dart-review.googlesource.com/c/80522
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: RĂ©gis Crelier <regis@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/vm/bin/gen_kernel.dart b/pkg/vm/bin/gen_kernel.dart
index e13c4eb..5acfd26 100644
--- a/pkg/vm/bin/gen_kernel.dart
+++ b/pkg/vm/bin/gen_kernel.dart
@@ -45,7 +45,9 @@
       defaultsTo: true)
   ..addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false)
   ..addFlag('drop-ast',
-      help: 'Drop AST for members with bytecode', defaultsTo: false);
+      help: 'Drop AST for members with bytecode', defaultsTo: false)
+  ..addFlag('use-future-bytecode-format',
+      help: 'Generate bytecode in the bleeding edge format', defaultsTo: false);
 
 final String _usage = '''
 Usage: dart pkg/vm/bin/gen_kernel.dart --platform vm_platform_strong.dill [options] input.dart
@@ -82,6 +84,7 @@
   final bool tfa = options['tfa'];
   final bool genBytecode = options['gen-bytecode'];
   final bool dropAST = options['drop-ast'];
+  final bool useFutureBytecodeFormat = options['use-future-bytecode-format'];
   final bool enableAsserts = options['enable-asserts'];
   final bool enableConstantEvaluation = options['enable-constant-evaluation'];
   final Map<String, String> environmentDefines = {};
@@ -114,6 +117,7 @@
       environmentDefines: environmentDefines,
       genBytecode: genBytecode,
       dropAST: dropAST,
+      useFutureBytecodeFormat: useFutureBytecodeFormat,
       enableAsserts: enableAsserts,
       enableConstantEvaluation: enableConstantEvaluation);
 
diff --git a/pkg/vm/lib/bytecode/dbc.dart b/pkg/vm/lib/bytecode/dbc.dart
index 7bb56e6..754bdcd 100644
--- a/pkg/vm/lib/bytecode/dbc.dart
+++ b/pkg/vm/lib/bytecode/dbc.dart
@@ -6,6 +6,19 @@
 
 library vm.bytecode.dbc;
 
+/// Version of stable bytecode format, produced by default.
+/// Before bumping stable bytecode version format, make sure that
+/// all users have switched to a VM which is able to consume next
+/// version of bytecode.
+const int stableBytecodeFormatVersion = 1;
+
+/// Version of bleeding edge bytecode format.
+/// Produced by bytecode generator when --use-future-bytecode-format
+/// option is enabled.
+/// Should match kMaxSupportedBytecodeFormatVersion in
+/// runtime/vm/constants_kbc.h.
+const int futureBytecodeFormatVersion = stableBytecodeFormatVersion + 1;
+
 enum Opcode {
   kTrap,
 
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index b19a687..ad72107 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -39,6 +39,7 @@
 void generateBytecode(Component component,
     {bool dropAST: false,
     bool omitSourcePositions: false,
+    bool useFutureBytecodeFormat: false,
     Map<String, String> environmentDefines,
     ErrorReporter errorReporter}) {
   final coreTypes = new CoreTypes(component);
@@ -50,8 +51,15 @@
   final constantsBackend =
       new VmConstantsBackend(environmentDefines, coreTypes);
   final errorReporter = new ForwardConstantEvaluationErrors(typeEnvironment);
-  new BytecodeGenerator(component, coreTypes, hierarchy, typeEnvironment,
-          constantsBackend, omitSourcePositions, errorReporter)
+  new BytecodeGenerator(
+          component,
+          coreTypes,
+          hierarchy,
+          typeEnvironment,
+          constantsBackend,
+          omitSourcePositions,
+          useFutureBytecodeFormat,
+          errorReporter)
       .visitComponent(component);
   if (dropAST) {
     new DropAST().visitComponent(component);
@@ -65,6 +73,7 @@
   final TypeEnvironment typeEnvironment;
   final ConstantsBackend constantsBackend;
   final bool omitSourcePositions;
+  final bool useFutureBytecodeFormat;
   final ErrorReporter errorReporter;
   final BytecodeMetadataRepository metadata = new BytecodeMetadataRepository();
   final RecognizedMethods recognizedMethods;
@@ -101,6 +110,7 @@
       this.typeEnvironment,
       this.constantsBackend,
       this.omitSourcePositions,
+      this.useFutureBytecodeFormat,
       this.errorReporter)
       : recognizedMethods = new RecognizedMethods(typeEnvironment) {
     component.addMetadataRepository(metadata);
@@ -750,8 +760,11 @@
 
   void end(Member node) {
     if (!hasErrors) {
-      metadata.mapping[node] = new BytecodeMetadata(
-          cp, asm.bytecode, asm.exceptionsTable, nullableFields, closures);
+      final formatVersion = useFutureBytecodeFormat
+          ? futureBytecodeFormatVersion
+          : stableBytecodeFormatVersion;
+      metadata.mapping[node] = new BytecodeMetadata(formatVersion, cp,
+          asm.bytecode, asm.exceptionsTable, nullableFields, closures);
     }
 
     typeEnvironment.thisType = null;
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index cfc523b..31cf8b6 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -49,6 +49,7 @@
     Map<String, String> environmentDefines,
     bool genBytecode: false,
     bool dropAST: false,
+    bool useFutureBytecodeFormat: false,
     bool enableAsserts: false,
     bool enableConstantEvaluation: true}) async {
   // Replace error handler to detect if there are compilation errors.
@@ -91,7 +92,9 @@
   if (genBytecode && !errorDetector.hasCompilationErrors && component != null) {
     await runWithFrontEndCompilerContext(source, options, component, () {
       generateBytecode(component,
-          dropAST: dropAST, environmentDefines: environmentDefines);
+          dropAST: dropAST,
+          useFutureBytecodeFormat: useFutureBytecodeFormat,
+          environmentDefines: environmentDefines);
     });
   }
 
diff --git a/pkg/vm/lib/metadata/bytecode.dart b/pkg/vm/lib/metadata/bytecode.dart
index 40c664c..573bd02 100644
--- a/pkg/vm/lib/metadata/bytecode.dart
+++ b/pkg/vm/lib/metadata/bytecode.dart
@@ -6,6 +6,8 @@
 
 import 'package:kernel/ast.dart';
 import '../bytecode/constant_pool.dart' show ConstantPool;
+import '../bytecode/dbc.dart'
+    show stableBytecodeFormatVersion, futureBytecodeFormatVersion;
 import '../bytecode/disassembler.dart' show BytecodeDisassembler;
 import '../bytecode/exceptions.dart' show ExceptionsTable;
 
@@ -14,6 +16,7 @@
 /// In kernel binary, bytecode metadata is encoded as following:
 ///
 /// type BytecodeMetadata {
+///   UInt bytecodeFormatVersion
 ///   UInt flags (HasExceptionsTable, HasNullableFields, HasClosures)
 ///
 ///   ConstantPool constantPool
@@ -46,6 +49,7 @@
   static const hasNullableFieldsFlag = 1 << 1;
   static const hasClosuresFlag = 1 << 2;
 
+  final int version;
   final ConstantPool constantPool;
   final List<int> bytecodes;
   final ExceptionsTable exceptionsTable;
@@ -61,13 +65,16 @@
       (hasNullableFields ? hasNullableFieldsFlag : 0) |
       (hasClosures ? hasClosuresFlag : 0);
 
-  BytecodeMetadata(this.constantPool, this.bytecodes, this.exceptionsTable,
-      this.nullableFields, this.closures);
+  BytecodeMetadata(this.version, this.constantPool, this.bytecodes,
+      this.exceptionsTable, this.nullableFields, this.closures);
 
   // TODO(alexmarkov): Consider printing constant pool before bytecode.
   @override
   String toString() => "\n"
-      "Bytecode {\n"
+      "Bytecode"
+      " (version: "
+      "${version == stableBytecodeFormatVersion ? 'stable' : version == futureBytecodeFormatVersion ? 'future' : "v$version"}"
+      ") {\n"
       "${new BytecodeDisassembler().disassemble(bytecodes, exceptionsTable)}}\n"
       "$exceptionsTable"
       "${nullableFields.isEmpty ? '' : 'Nullable fields: ${nullableFields.map((ref) => ref.asField).toList()}\n'}"
@@ -121,6 +128,7 @@
 
   @override
   void writeToBinary(BytecodeMetadata metadata, Node node, BinarySink sink) {
+    sink.writeUInt30(metadata.version);
     sink.writeUInt30(metadata.flags);
     metadata.constantPool.writeToBinary(node, sink);
     sink.writeByteList(metadata.bytecodes);
@@ -140,6 +148,11 @@
 
   @override
   BytecodeMetadata readFromBinary(Node node, BinarySource source) {
+    int version = source.readUInt();
+    if (version != stableBytecodeFormatVersion &&
+        version != futureBytecodeFormatVersion) {
+      throw 'Error: unexpected bytecode version $version';
+    }
     int flags = source.readUInt();
     final ConstantPool constantPool =
         new ConstantPool.readFromBinary(node, source);
@@ -158,7 +171,7 @@
             ? new List<ClosureBytecode>.generate(source.readUInt(),
                 (_) => new ClosureBytecode.readFromBinary(source))
             : const <ClosureBytecode>[];
-    return new BytecodeMetadata(
-        constantPool, bytecodes, exceptionsTable, nullableFields, closures);
+    return new BytecodeMetadata(version, constantPool, bytecodes,
+        exceptionsTable, nullableFields, closures);
   }
 }
diff --git a/pkg/vm/testcases/bytecode/asserts.dart.expect b/pkg/vm/testcases/bytecode/asserts.dart.expect
index 2194f84..8c9b60b 100644
--- a/pkg/vm/testcases/bytecode/asserts.dart.expect
+++ b/pkg/vm/testcases/bytecode/asserts.dart.expect
@@ -3,7 +3,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   JumpIfNoAsserts      L1
@@ -28,7 +28,7 @@
   assert(condition);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   JumpIfNoAsserts      L1
@@ -58,7 +58,7 @@
   assert([@vm.call-site-attributes.metadata=receiverType:() → dart.core::bool] condition.call(), [@vm.call-site-attributes.metadata=receiverType:() → dart.core::String] message.call());
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/async.dart.expect b/pkg/vm/testcases/bytecode/async.dart.expect
index b5d1e82..5a5cd62 100644
--- a/pkg/vm/testcases/bytecode/async.dart.expect
+++ b/pkg/vm/testcases/bytecode/async.dart.expect
@@ -4,7 +4,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                3
   CheckStack
   Allocate             CP#19
@@ -276,7 +276,7 @@
   return :async_completer.{asy::Completer::future};
 };
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                7
   CheckStack
   AllocateContext      4
@@ -463,7 +463,7 @@
   return :async_completer.{asy::Completer::future};
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   AllocateContext      11
@@ -746,7 +746,7 @@
   return :async_completer.{asy::Completer::future};
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   AllocateContext      11
@@ -1165,7 +1165,7 @@
   return :async_completer.{asy::Completer::future};
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   AllocateContext      16
@@ -1818,7 +1818,7 @@
   return :async_completer.{asy::Completer::future};
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   AllocateContext      2
@@ -2204,7 +2204,7 @@
   return nested;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   AllocateContext      9
@@ -2460,7 +2460,7 @@
   return :async_completer.{asy::Completer::future};
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/bootstrapping.dart.expect b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
index 1d4b344..5711186 100644
--- a/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
+++ b/pkg/vm/testcases/bytecode/bootstrapping.dart.expect
@@ -7,7 +7,7 @@
 class _ScheduleImmediate extends core::Object {
   static field (() → void) → void _closure = null;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -28,7 +28,7 @@
 class _NamespaceImpl extends core::Object implements self::_Namespace {
   static field self::_NamespaceImpl _cachedNamespace = null;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -46,7 +46,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-6]
@@ -60,7 +60,7 @@
 ]  @_in::ExternalName::•("Namespace_Create")
   external static method _create(self::_NamespaceImpl namespace, dynamic n) → self::_NamespaceImpl;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -73,7 +73,7 @@
 ]  @_in::ExternalName::•("Namespace_GetPointer")
   external static method _getPointer(self::_NamespaceImpl namespace) → core::int;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   NativeCall           CP#0
@@ -85,7 +85,7 @@
 ]  @_in::ExternalName::•("Namespace_GetDefault")
   external static method _getDefault() → core::int;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   Allocate             CP#0
@@ -113,7 +113,7 @@
     self::_NamespaceImpl::_cachedNamespace = self::_NamespaceImpl::_create(new self::_NamespaceImpl::_(), namespace);
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushConstant         CP#0
@@ -156,7 +156,7 @@
     return self::_NamespaceImpl::_cachedNamespace;
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#1
@@ -178,7 +178,7 @@
 }
 class _Namespace extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -196,7 +196,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -214,7 +214,7 @@
     self::_NamespaceImpl::_setupNamespace(namespace);
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#1
@@ -230,7 +230,7 @@
 ]  static get _namespace() → self::_Namespace
     return self::_NamespaceImpl::_namespace;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#1
@@ -259,7 +259,7 @@
   static field dynamic _computeScriptUri = null;
   static field dynamic _cachedScript = null;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -277,7 +277,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   Push                 FP[-5]
@@ -296,7 +296,7 @@
     self::VMLibraryHooks::_cachedScript = null;
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -347,7 +347,7 @@
 static field core::int _stderrFD = 2;
 static field core::String _rawScript;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -360,7 +360,7 @@
 ]@_in::ExternalName::•("Builtin_PrintString")
 external static method _printString(core::String s) → void;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -380,7 +380,7 @@
   self::_printString(arg.{core::Object::toString}());
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -394,7 +394,7 @@
 ]static method _getPrintClosure() → dynamic
   return self::_print;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -409,7 +409,7 @@
   self::_ScheduleImmediate::_closure = closure;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                3
   CheckStack
   Push                 FP[-7]
@@ -432,7 +432,7 @@
   self::_stderrFD = stderr;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushConstant         CP#0
@@ -514,7 +514,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -533,7 +533,7 @@
   self::VMLibraryHooks::platformScript = self::_scriptUri;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/closures.dart.expect b/pkg/vm/testcases/bytecode/closures.dart.expect
index 247038a..e7acb3d 100644
--- a/pkg/vm/testcases/bytecode/closures.dart.expect
+++ b/pkg/vm/testcases/bytecode/closures.dart.expect
@@ -5,7 +5,7 @@
 typedef IntFunc = (core::int) → dynamic;
 class C1 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -25,7 +25,7 @@
 }
 class C2 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -45,7 +45,7 @@
 }
 class C3 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -65,7 +65,7 @@
 }
 class C4 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -85,7 +85,7 @@
 }
 class C5 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -105,7 +105,7 @@
 }
 class C6 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -125,7 +125,7 @@
 }
 class C7 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -145,7 +145,7 @@
 }
 class C8 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -165,7 +165,7 @@
 }
 class A<T1 extends core::Object = dynamic, T2 extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -183,7 +183,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                5
   CheckStack
   CheckFunctionTypeArgs 2, 0
@@ -484,7 +484,7 @@
 class B extends core::Object {
   field core::int foo = null;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -503,7 +503,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                5
   CheckStack
   AllocateContext      4
@@ -772,7 +772,7 @@
 }
 class C extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -790,7 +790,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                5
   CheckStack
   AllocateContext      1
@@ -977,7 +977,7 @@
     }
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                5
   CheckStack
   Push                 FP[-5]
@@ -1078,7 +1078,7 @@
 }
 class D<T extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -1096,7 +1096,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                3
   CheckStack
   AllocateContext      1
@@ -1172,7 +1172,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   AllocateContext      1
@@ -1264,7 +1264,7 @@
   return x;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   CheckFunctionTypeArgs 8, 0
@@ -1349,7 +1349,7 @@
   core::print(<core::Type>[self::callWithArgs::T1, self::callWithArgs::T2, self::callWithArgs::T3, self::callWithArgs::T4, self::callWithArgs::T5, self::callWithArgs::T6, self::callWithArgs::T7, self::callWithArgs::T8]);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -1409,7 +1409,7 @@
   new self::A::•<core::List<self::C1>, core::List<self::C2>>().{self::A::foo}<core::List<self::C3>, core::List<self::C4>>();
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                7
   CheckStack
   Allocate             CP#14
@@ -1529,7 +1529,7 @@
   return intFunc;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/deferred_lib.dart.expect b/pkg/vm/testcases/bytecode/deferred_lib.dart.expect
index cf966e1..d0261e3 100644
--- a/pkg/vm/testcases/bytecode/deferred_lib.dart.expect
+++ b/pkg/vm/testcases/bytecode/deferred_lib.dart.expect
@@ -3,7 +3,7 @@
 import "./hello.dart" as hel;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushNull
@@ -25,7 +25,7 @@
 ]static method callDeferred() → dynamic
   return let final dynamic #t1 = CheckLibraryIsLoaded(lib) in hel::main();
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
@@ -42,7 +42,7 @@
 ]static method testLoadLibrary() → dynamic
   return LoadLibrary(lib);
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/field_initializers.dart.expect b/pkg/vm/testcases/bytecode/field_initializers.dart.expect
index 3842a43..d28770d 100644
--- a/pkg/vm/testcases/bytecode/field_initializers.dart.expect
+++ b/pkg/vm/testcases/bytecode/field_initializers.dart.expect
@@ -9,7 +9,7 @@
   field core::int foo4;
   field core::int foo5 = 43;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-6]
@@ -46,7 +46,7 @@
     : self::A::foo1 = null, self::A::foo4 = foo4, self::A::foo5 = 44, super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-7]
@@ -85,7 +85,7 @@
     : self::A::foo4 = null, self::A::foo1 = x, self::A::foo5 = y.{core::num::+}(1), super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -104,7 +104,7 @@
     : this self::A::•(45)
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-8]
@@ -131,7 +131,7 @@
   static field core::int foo7 = 47;
   static const field core::int foo8 = 48;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -155,7 +155,7 @@
     : super self::A::•(49)
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-7]
@@ -185,7 +185,7 @@
     ;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/hello.dart.expect b/pkg/vm/testcases/bytecode/hello.dart.expect
index b9c0903..db4aaaf 100644
--- a/pkg/vm/testcases/bytecode/hello.dart.expect
+++ b/pkg/vm/testcases/bytecode/hello.dart.expect
@@ -3,7 +3,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
diff --git a/pkg/vm/testcases/bytecode/instance_creation.dart.expect b/pkg/vm/testcases/bytecode/instance_creation.dart.expect
index 5358ccb..1f0cfa2 100644
--- a/pkg/vm/testcases/bytecode/instance_creation.dart.expect
+++ b/pkg/vm/testcases/bytecode/instance_creation.dart.expect
@@ -7,7 +7,7 @@
   generic-covariant-impl field self::Base::T1 t1 = null;
   generic-covariant-impl field self::Base::T2 t2 = null;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -67,7 +67,7 @@
 }
 class A extends self::Base<core::int, core::String> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-6]
@@ -87,7 +87,7 @@
 }
 class B<T extends core::Object = dynamic> extends self::Base<core::List<self::B::T>, core::String> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -133,7 +133,7 @@
 }
 class C extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-6]
@@ -174,7 +174,7 @@
 }
 class E<K extends core::Object = dynamic, V extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -192,7 +192,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -213,7 +213,7 @@
 }
 class F<K extends core::Object = dynamic, V extends core::Object = dynamic> extends self::E<core::String, core::List<self::F::V>> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -231,7 +231,7 @@
     : super self::E::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -252,7 +252,7 @@
 }
 class G<K extends core::Object = dynamic, V extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -270,7 +270,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -298,7 +298,7 @@
 }
 class H<P1 extends core::Object = dynamic, P2 extends core::Object = dynamic, P3 extends core::Object = dynamic> extends self::G<self::H::P2, self::H::P3> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -318,7 +318,7 @@
 }
 class I extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-6]
@@ -336,7 +336,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   EntryOptional        1, 0, 1
   LoadConstant         r1, CP#0
   LoadConstant         r1, CP#1
@@ -365,7 +365,7 @@
 }
 class J extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -380,7 +380,7 @@
 }
 abstract class K<A extends core::Object = dynamic, B extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -405,7 +405,7 @@
 }
 class TestTypeArgReuse<P extends core::Object = dynamic, Q extends core::Object = dynamic> extends self::Base<self::TestTypeArgReuse::P, self::TestTypeArgReuse::Q> implements self::K<self::TestTypeArgReuse::P, self::TestTypeArgReuse::Q> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -424,7 +424,7 @@
     ;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Allocate             CP#0
@@ -447,7 +447,7 @@
 ]static method foo1() → dynamic
   return new self::C::•("hello");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#1
@@ -488,7 +488,7 @@
   new self::B::•<core::int>();
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   CheckFunctionTypeArgs 1, 0
@@ -516,7 +516,7 @@
   new self::B::•<core::List<self::foo3::T>>();
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -535,7 +535,7 @@
   self::G::test_factory<core::int, core::List<core::String>>();
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
@@ -561,7 +561,7 @@
   self::I::test_factory2(param: 42);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -580,7 +580,7 @@
 ]static method foo6() → dynamic
   return core::_GrowableList::•<core::String>(0);
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -599,7 +599,7 @@
 ]static method foo7(core::int n) → dynamic
   return core::_List::•<core::int>(n);
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#1
diff --git a/pkg/vm/testcases/bytecode/literals.dart.expect b/pkg/vm/testcases/bytecode/literals.dart.expect
index ac3a176..0954a1f 100644
--- a/pkg/vm/testcases/bytecode/literals.dart.expect
+++ b/pkg/vm/testcases/bytecode/literals.dart.expect
@@ -7,7 +7,7 @@
   final field core::int index;
   final field core::String _name;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#13
@@ -31,7 +31,7 @@
 }
 ]  static const field core::List<self::A> values = const <self::A>[self::A::elem1, self::A::elem2, self::A::elem3, self::A::elem4];
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -45,7 +45,7 @@
 }
 ]  static const field self::A elem1 = const self::A::•(0, "A.elem1");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -59,7 +59,7 @@
 }
 ]  static const field self::A elem2 = const self::A::•(1, "A.elem2");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -73,7 +73,7 @@
 }
 ]  static const field self::A elem3 = const self::A::•(2, "A.elem3");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -87,7 +87,7 @@
 }
 ]  static const field self::A elem4 = const self::A::•(3, "A.elem4");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-7]
@@ -115,7 +115,7 @@
     : self::A::index = index, self::A::_name = _name, super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -135,7 +135,7 @@
 class B extends core::Object {
   final field core::int i;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-6]
@@ -161,7 +161,7 @@
 class C extends self::B {
   final field core::int j;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-8]
@@ -193,7 +193,7 @@
   final field dynamic x;
   final field dynamic y;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   EntryOptional        2, 1, 0
   LoadConstant         r2, CP#0
   Frame                0
@@ -226,7 +226,7 @@
 }
 class E<T extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -246,7 +246,7 @@
 }
 class F<P extends core::Object = dynamic, Q extends core::Object = dynamic> extends self::E<core::Map<self::F::P, self::F::Q>> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -265,7 +265,7 @@
     ;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -280,7 +280,7 @@
 ]static const field self::A c1 = self::A::elem3;
 static const field core::String c2 = "hello!";
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushInt              6
@@ -290,7 +290,7 @@
 }
 ]static const field core::int c3 = self::c2.{core::String::length};
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -304,7 +304,7 @@
 }
 ]static const field self::C c4 = const self::C::•(1, 2, 3);
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -319,7 +319,7 @@
 ]static const field self::D c5 = const self::D::•(const self::B::•(4));
 static field core::double fieldWithDoubleLiteralInitializer = 1.0;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#3
@@ -372,7 +372,7 @@
   core::print(self::c5);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushInt              42
@@ -446,7 +446,7 @@
   core::print(const self::D::•(const self::C::•(4, 5, 6), const <core::String, core::Object>{"foo": 42, "bar": const self::B::•(self::c2.{core::String::length})}));
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -517,7 +517,7 @@
   core::print(<core::String>["a", a.{core::int::toString}(), "b"]);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   CheckFunctionTypeArgs 1, 0
@@ -633,7 +633,7 @@
   core::print(<self::test_map_literal::T, core::int>{c: 4});
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -659,7 +659,7 @@
   core::print(#_private_symbol);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   CheckFunctionTypeArgs 1, 0
@@ -688,7 +688,7 @@
   core::print(self::test_type_literal::T);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#1
@@ -703,7 +703,7 @@
 ]static method testGenericConstInstance() → dynamic
   return const self::F::•<core::int, core::String>();
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -717,7 +717,7 @@
 ]static method testGenericFunctionTypeLiteral() → dynamic
   return <X extends core::Object = dynamic>(X) → X;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
@@ -732,7 +732,7 @@
 ]static method testFieldWithDoubleLiteralInitializer() → dynamic
   return self::fieldWithDoubleLiteralInitializer;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/loops.dart.expect b/pkg/vm/testcases/bytecode/loops.dart.expect
index fdb0b11..913c397 100644
--- a/pkg/vm/testcases/bytecode/loops.dart.expect
+++ b/pkg/vm/testcases/bytecode/loops.dart.expect
@@ -3,7 +3,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushInt              0
@@ -50,7 +50,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushInt              0
@@ -108,7 +108,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushInt              0
@@ -169,7 +169,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   PushInt              0
@@ -219,7 +219,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushInt              0
@@ -266,7 +266,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                3
   CheckStack
   PushInt              0
@@ -307,7 +307,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
   PushInt              0
@@ -354,7 +354,7 @@
   return sum;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/optional_params.dart.expect b/pkg/vm/testcases/bytecode/optional_params.dart.expect
index 265c758..bd33fe5 100644
--- a/pkg/vm/testcases/bytecode/optional_params.dart.expect
+++ b/pkg/vm/testcases/bytecode/optional_params.dart.expect
@@ -3,7 +3,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   EntryOptional        1, 2, 0
   LoadConstant         r1, CP#0
   LoadConstant         r2, CP#1
@@ -83,7 +83,7 @@
   core::print("b = ${b}");
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   EntryOptional        2, 0, 3
   LoadConstant         r2, CP#0
   LoadConstant         r2, CP#1
@@ -214,7 +214,7 @@
   core::print("c = ${c}");
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   EntryOptional        2, 0, 2
   LoadConstant         r2, CP#0
   LoadConstant         r2, CP#1
@@ -257,7 +257,7 @@
   core::print(b);
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushConstant         CP#0
diff --git a/pkg/vm/testcases/bytecode/super_calls.dart.expect b/pkg/vm/testcases/bytecode/super_calls.dart.expect
index 34e7eb7..7c6b17e 100644
--- a/pkg/vm/testcases/bytecode/super_calls.dart.expect
+++ b/pkg/vm/testcases/bytecode/super_calls.dart.expect
@@ -4,7 +4,7 @@
 
 class Base1 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -22,7 +22,7 @@
     : super core::Object::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   CheckFunctionTypeArgs 1, 0
@@ -33,7 +33,7 @@
 }
 ]  method foo<T extends core::Object = dynamic>(self::Base1::foo::T a1, core::int a2) → void {}
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushInt              42
@@ -46,7 +46,7 @@
 ]  get bar() → dynamic
     return 42;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
@@ -58,7 +58,7 @@
 }
 class A extends self::Base1 {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -76,7 +76,7 @@
     : super self::Base1::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -98,7 +98,7 @@
 ]  method testSuperCall(core::int x) → dynamic
     return super.{self::Base1::foo}<core::String>("a1", 2);
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -115,7 +115,7 @@
 ]  method testSuperTearOff() → dynamic
     return super.{self::Base1::foo};
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -132,7 +132,7 @@
 ]  method testSuperGet() → dynamic
     return super.{self::Base1::bar};
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -156,7 +156,7 @@
 ]  method testSuperCallViaGetter() → dynamic
     return [@vm.call-site-attributes.metadata=receiverType:dynamic] super.{self::Base1::bar}.call<core::int>("param");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -177,7 +177,7 @@
 }
 abstract class Base2 extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -200,7 +200,7 @@
 }
 abstract class B extends self::Base2 {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -218,7 +218,7 @@
     : super self::Base2::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-6]
@@ -272,7 +272,7 @@
 ]  method testSuperCall(core::int x) → dynamic
     return super.{self::Base2::foo}<core::double>("a1", 3.14, 5);
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -307,7 +307,7 @@
 ]  method testSuperTearOff() → dynamic
     return super.{self::Base2::foo};
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -342,7 +342,7 @@
 ]  method testSuperGet() → dynamic
     return super.{self::Base2::bar};
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   PushConstant         CP#0
@@ -384,7 +384,7 @@
 ]  method testSuperCallViaGetter() → dynamic
     return [@vm.call-site-attributes.metadata=receiverType:dynamic] super.{self::Base2::bar}.call<core::int>("param");
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -424,7 +424,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/switch.dart.expect b/pkg/vm/testcases/bytecode/switch.dart.expect
index b26fc38..6fd01d5 100644
--- a/pkg/vm/testcases/bytecode/switch.dart.expect
+++ b/pkg/vm/testcases/bytecode/switch.dart.expect
@@ -3,7 +3,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushNull
@@ -73,7 +73,7 @@
   return y;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushNull
@@ -160,7 +160,7 @@
   return y;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   PushNull
@@ -246,7 +246,7 @@
   return y;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/try_blocks.dart.expect b/pkg/vm/testcases/bytecode/try_blocks.dart.expect
index 78ae07a..ac93137 100644
--- a/pkg/vm/testcases/bytecode/try_blocks.dart.expect
+++ b/pkg/vm/testcases/bytecode/try_blocks.dart.expect
@@ -3,7 +3,7 @@
 import "dart:core" as core;
 
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                4
   CheckStack
 Try #0 start:
@@ -62,7 +62,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                5
   CheckStack
 Try #0 start:
@@ -226,7 +226,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                7
   CheckStack
   AllocateContext      3
@@ -505,7 +505,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                8
   CheckStack
 Try #0 start:
@@ -615,7 +615,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                3
   CheckStack
   PushInt              0
@@ -693,7 +693,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                9
   CheckStack
   AllocateContext      2
@@ -929,7 +929,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                6
   CheckStack
   AllocateContext      1
@@ -1232,7 +1232,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                5
   CheckStack
 Try #0 start:
@@ -1304,7 +1304,7 @@
   }
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/pkg/vm/testcases/bytecode/type_ops.dart.expect b/pkg/vm/testcases/bytecode/type_ops.dart.expect
index 5e1fdac..c718bdb 100644
--- a/pkg/vm/testcases/bytecode/type_ops.dart.expect
+++ b/pkg/vm/testcases/bytecode/type_ops.dart.expect
@@ -4,7 +4,7 @@
 
 class A<T extends core::Object = dynamic> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -24,7 +24,7 @@
 }
 class B extends self::A<core::String> {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -44,7 +44,7 @@
 }
 class C<T1 extends core::Object = dynamic, T2 extends core::Object = dynamic, T3 extends core::Object = dynamic> extends self::B {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -65,7 +65,7 @@
 class D<P extends core::Object = dynamic, Q extends core::Object = dynamic> extends self::C<core::int, self::D::Q, self::D::P> {
   generic-covariant-impl field core::Map<self::D::P, self::D::Q> foo;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-6]
@@ -98,7 +98,7 @@
     : self::D::foo = tt as{TypeError} core::Map<self::D::P, self::D::Q>, super self::C::•()
     ;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -168,7 +168,7 @@
     [@vm.call-site-attributes.metadata=receiverType:#lib::D<#lib::D::P, #lib::D::Q>] this.{self::D::foo} = y as{TypeError} core::Map<self::D::P, self::D::Q>;
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   CheckFunctionTypeArgs 2, 0
@@ -236,7 +236,7 @@
     return (z as core::Map<self::D::foo3::T2, self::D::Q>).{core::Map::values};
   }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                2
   CheckStack
   Push                 FP[-6]
@@ -289,7 +289,7 @@
 }
 class E<P extends core::String = core::String> extends core::Object {
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
@@ -302,7 +302,7 @@
 ]  static factory •<P extends core::String = dynamic>() → self::E<self::E::•::P>
     return null;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   CheckFunctionTypeArgs 2, 0
@@ -334,7 +334,7 @@
 }
 static field core::List<core::Iterable<dynamic>> globalVar;
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   Push                 FP[-5]
@@ -395,7 +395,7 @@
   return x as self::A<core::int>;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                1
   CheckStack
   Push                 FP[-5]
@@ -418,7 +418,7 @@
   self::globalVar = x as{TypeError} core::List<core::Iterable<dynamic>>;
 }
 [@vm.bytecode=
-Bytecode {
+Bytecode (version: stable) {
   Entry                0
   CheckStack
   PushNull
diff --git a/runtime/vm/compiler/frontend/bytecode_reader.cc b/runtime/vm/compiler/frontend/bytecode_reader.cc
index 587eb27..49dec04 100644
--- a/runtime/vm/compiler/frontend/bytecode_reader.cc
+++ b/runtime/vm/compiler/frontend/bytecode_reader.cc
@@ -62,6 +62,17 @@
   AlternativeReadingScope alt(&helper_->reader_, &H.metadata_payloads(),
                               md_offset);
 
+  const intptr_t version = helper_->reader_.ReadUInt();
+  if ((version < KernelBytecode::kMinSupportedBytecodeFormatVersion) ||
+      (version > KernelBytecode::kMaxSupportedBytecodeFormatVersion)) {
+    FATAL3(
+        "Unsupported Dart bytecode format version %" Pd
+        ". This version of Dart VM supports bytecode format versions from %" Pd
+        " to %" Pd ".",
+        version, KernelBytecode::kMinSupportedBytecodeFormatVersion,
+        KernelBytecode::kMaxSupportedBytecodeFormatVersion);
+  }
+
   const int kHasExceptionsTableFlag = 1 << 0;
   const int kHasNullableFieldsFlag = 1 << 1;
   const int kHasClosuresFlag = 1 << 2;
diff --git a/runtime/vm/constants_kbc.h b/runtime/vm/constants_kbc.h
index f03847f..d61ac75 100644
--- a/runtime/vm/constants_kbc.h
+++ b/runtime/vm/constants_kbc.h
@@ -468,6 +468,12 @@
 
 class KernelBytecode {
  public:
+  // Minimum bytecode format version supported by VM.
+  static const intptr_t kMinSupportedBytecodeFormatVersion = 1;
+  // Maximum bytecode format version supported by VM.
+  // Should match futureBytecodeFormatVersion in pkg/vm/lib/bytecode/dbc.dart.
+  static const intptr_t kMaxSupportedBytecodeFormatVersion = 2;
+
   enum Opcode {
 #define DECLARE_BYTECODE(name, encoding, op1, op2, op3) k##name,
     KERNEL_BYTECODES_LIST(DECLARE_BYTECODE)