[vm/bytecode] Consolidate bytecode generation options

As number of bytecode generation options grows, it becomes cumbersome
to add them and propagate from the place where they are parsed to
the place where they are used. In order to make it easier to
change and add new bytecode generation options, this CL introduces
BytecodeOptions class which consolidates all options for bytecode
generation. Also, command line options --emit-bytecode-*** are gathered
into a single multi-option --bytecode-options=opt1,opt2,...

Also, unused --use-future-bytecode-format option is cleaned up. If needed,
it could be easily re-introduced in the new BytecodeOptions.

Change-Id: I637bf28ceb4233ead2562afe7ad51c69a99f2d60
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/106965
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/pkg/test_runner/lib/src/compiler_configuration.dart b/pkg/test_runner/lib/src/compiler_configuration.dart
index 8a5ee37..8a881ab 100644
--- a/pkg/test_runner/lib/src/compiler_configuration.dart
+++ b/pkg/test_runner/lib/src/compiler_configuration.dart
@@ -1176,8 +1176,7 @@
     if (_configuration.useKernelBytecode) {
       args.add('--gen-bytecode');
       args.add('--drop-ast');
-      args.add('--emit-bytecode-source-positions');
-      args.add('--emit-bytecode-local-var-info');
+      args.add('--bytecode-options=source-positions,local-var-info');
     }
 
     return Command.vmKernelCompilation(dillFile, true, bootstrapDependencies(),
diff --git a/pkg/vm/bin/kernel_service.dart b/pkg/vm/bin/kernel_service.dart
index fc07655..7f93669 100644
--- a/pkg/vm/bin/kernel_service.dart
+++ b/pkg/vm/bin/kernel_service.dart
@@ -34,6 +34,7 @@
 import 'package:kernel/kernel.dart' show Component, Procedure;
 import 'package:kernel/target/targets.dart' show TargetFlags;
 import 'package:vm/bytecode/gen_bytecode.dart' show generateBytecode;
+import 'package:vm/bytecode/options.dart' show BytecodeOptions;
 import 'package:vm/incremental_compiler.dart';
 import 'package:vm/kernel_front_end.dart' show runWithFrontEndCompilerContext;
 import 'package:vm/http_filesystem.dart';
@@ -147,11 +148,14 @@
         await runWithFrontEndCompilerContext(script, options, component, () {
           // TODO(alexmarkov): disable source positions, local variables info
           //  and source files in VM PRODUCT mode.
+          // TODO(alexmarkov): disable asserts if they are not enabled in VM.
           generateBytecode(component,
-              environmentDefines: options.environmentDefines,
-              emitSourcePositions: true,
-              emitLocalVarInfo: true,
-              emitSourceFiles: true);
+              options: new BytecodeOptions(
+                  enableAsserts: true,
+                  environmentDefines: options.environmentDefines,
+                  emitSourcePositions: true,
+                  emitLocalVarInfo: true,
+                  emitSourceFiles: true));
         });
       }
 
diff --git a/pkg/vm/lib/bytecode/dbc.dart b/pkg/vm/lib/bytecode/dbc.dart
index e89f666..6cb296f 100644
--- a/pkg/vm/lib/bytecode/dbc.dart
+++ b/pkg/vm/lib/bytecode/dbc.dart
@@ -12,11 +12,6 @@
 /// version of bytecode.
 const int currentBytecodeFormatVersion = 11;
 
-/// Version of experimental / bleeding edge bytecode format.
-/// Produced by bytecode generator when --use-future-bytecode-format
-/// option is enabled.
-const int futureBytecodeFormatVersion = currentBytecodeFormatVersion + 1;
-
 enum Opcode {
   // Old instructions, used before bytecode v7.
   // TODO(alexmarkov): remove
diff --git a/pkg/vm/lib/bytecode/declarations.dart b/pkg/vm/lib/bytecode/declarations.dart
index 2519397..ceb0cd8 100644
--- a/pkg/vm/lib/bytecode/declarations.dart
+++ b/pkg/vm/lib/bytecode/declarations.dart
@@ -8,8 +8,7 @@
 import 'bytecode_serialization.dart'
     show BufferedWriter, BufferedReader, BytecodeSizeStatistics, StringTable;
 import 'constant_pool.dart' show ConstantPool;
-import 'dbc.dart'
-    show currentBytecodeFormatVersion, futureBytecodeFormatVersion;
+import 'dbc.dart' show currentBytecodeFormatVersion;
 import 'disassembler.dart' show BytecodeDisassembler;
 import 'exceptions.dart' show ExceptionsTable;
 import 'local_variable_table.dart' show LocalVariableTable;
@@ -1440,8 +1439,6 @@
     sb.write("Bytecode (version: ");
     if (version == currentBytecodeFormatVersion) {
       sb.write("stable");
-    } else if (version == futureBytecodeFormatVersion) {
-      sb.write("future");
     } else {
       sb.write("v$version");
     }
diff --git a/pkg/vm/lib/bytecode/gen_bytecode.dart b/pkg/vm/lib/bytecode/gen_bytecode.dart
index 1e80cc7..343915a 100644
--- a/pkg/vm/lib/bytecode/gen_bytecode.dart
+++ b/pkg/vm/lib/bytecode/gen_bytecode.dart
@@ -42,6 +42,7 @@
 import 'nullability_detector.dart' show NullabilityDetector;
 import 'object_table.dart'
     show ObjectHandle, ObjectTable, NameAndType, topLevelClassName;
+import 'options.dart' show BytecodeOptions;
 import 'recognized_methods.dart' show RecognizedMethods;
 import 'recursive_types_validator.dart' show IllegalRecursiveTypeException;
 import 'source_positions.dart' show LineStarts, SourcePositions;
@@ -58,19 +59,12 @@
 
 void generateBytecode(
   ast.Component component, {
-  bool enableAsserts: true,
-  bool causalAsyncStacks,
-  bool emitSourcePositions: false,
-  bool emitSourceFiles: false,
-  bool emitLocalVarInfo: false,
-  bool emitAnnotations: false,
-  bool omitAssertSourcePositions: false,
-  bool useFutureBytecodeFormat: false,
-  Map<String, String> environmentDefines: const <String, String>{},
+  BytecodeOptions options,
   ErrorReporter errorReporter,
   List<Library> libraries,
   ClassHierarchy hierarchy,
 }) {
+  options ??= new BytecodeOptions();
   verifyBytecodeInstructionDeclarations();
   final coreTypes = new CoreTypes(component);
   void ignoreAmbiguousSupertypes(Class cls, Supertype a, Supertype b) {}
@@ -80,25 +74,9 @@
   final constantsBackend = new VmConstantsBackend(coreTypes);
   final errorReporter = new ForwardConstantEvaluationErrors();
   libraries ??= component.libraries;
-  causalAsyncStacks ??=
-      environmentDefines['dart.developer.causal_async_stacks'] == 'true';
   try {
-    final bytecodeGenerator = new BytecodeGenerator(
-        component,
-        coreTypes,
-        hierarchy,
-        typeEnvironment,
-        constantsBackend,
-        environmentDefines,
-        enableAsserts,
-        causalAsyncStacks,
-        emitSourcePositions,
-        emitSourceFiles,
-        emitLocalVarInfo,
-        emitAnnotations,
-        omitAssertSourcePositions,
-        useFutureBytecodeFormat,
-        errorReporter);
+    final bytecodeGenerator = new BytecodeGenerator(component, coreTypes,
+        hierarchy, typeEnvironment, constantsBackend, options, errorReporter);
     for (var library in libraries) {
       bytecodeGenerator.visitLibrary(library);
     }
@@ -114,15 +92,7 @@
   final ClassHierarchy hierarchy;
   final TypeEnvironment typeEnvironment;
   final ConstantsBackend constantsBackend;
-  final Map<String, String> environmentDefines;
-  final bool enableAsserts;
-  final bool causalAsyncStacks;
-  final bool emitSourcePositions;
-  final bool emitSourceFiles;
-  final bool emitLocalVarInfo;
-  final bool emitAnnotations;
-  final bool omitAssertSourcePositions;
-  final bool useFutureBytecodeFormat;
+  final BytecodeOptions options;
   final ErrorReporter errorReporter;
   final BytecodeMetadataRepository metadata = new BytecodeMetadataRepository();
   final RecognizedMethods recognizedMethods;
@@ -170,20 +140,10 @@
       this.hierarchy,
       this.typeEnvironment,
       this.constantsBackend,
-      this.environmentDefines,
-      this.enableAsserts,
-      this.causalAsyncStacks,
-      this.emitSourcePositions,
-      this.emitSourceFiles,
-      this.emitLocalVarInfo,
-      this.emitAnnotations,
-      this.omitAssertSourcePositions,
-      this.useFutureBytecodeFormat,
+      this.options,
       this.errorReporter)
       : recognizedMethods = new RecognizedMethods(typeEnvironment),
-        formatVersion = useFutureBytecodeFormat
-            ? futureBytecodeFormatVersion
-            : currentBytecodeFormatVersion,
+        formatVersion = currentBytecodeFormatVersion,
         astUriToSource = component.uriToSource {
     nullabilityDetector = new NullabilityDetector(recognizedMethods);
     component.addMetadataRepository(metadata);
@@ -249,7 +209,8 @@
 
   ObjectHandle getScript(Uri uri, bool includeSource) {
     SourceFile source;
-    if (includeSource && (emitSourceFiles || emitSourcePositions)) {
+    if (includeSource &&
+        (options.emitSourceFiles || options.emitSourcePositions)) {
       source = bytecodeComponent.uriToSource[uri];
       if (source == null) {
         final astSource = astUriToSource[uri];
@@ -257,12 +218,12 @@
           final importUri =
               objectTable.getNameHandle(null, astSource.importUri.toString());
           LineStarts lineStarts;
-          if (emitSourcePositions) {
+          if (options.emitSourcePositions) {
             lineStarts = new LineStarts(astSource.lineStarts);
             bytecodeComponent.lineStarts.add(lineStarts);
           }
           String text = '';
-          if (emitSourceFiles) {
+          if (options.emitSourceFiles) {
             text = astSource.cachedText ??
                 utf8.decode(astSource.source, allowMalformed: true);
           }
@@ -323,7 +284,7 @@
     }
     int position = TreeNode.noOffset;
     int endPosition = TreeNode.noOffset;
-    if (emitSourcePositions && cls.fileOffset != TreeNode.noOffset) {
+    if (options.emitSourcePositions && cls.fileOffset != TreeNode.noOffset) {
       flags |= ClassDeclaration.hasSourcePositionsFlag;
       position = cls.fileOffset;
       endPosition = cls.fileEndOffset;
@@ -363,7 +324,8 @@
       Library library, Members members) {
     int flags = 0;
     int position = TreeNode.noOffset;
-    if (emitSourcePositions && library.fileOffset != TreeNode.noOffset) {
+    if (options.emitSourcePositions &&
+        library.fileOffset != TreeNode.noOffset) {
       flags |= ClassDeclaration.hasSourcePositionsFlag;
       position = library.fileOffset;
     }
@@ -404,14 +366,18 @@
     }
     final savedConstantEvaluator = constantEvaluator;
     if (constantEvaluator == null) {
-      constantEvaluator = new ConstantEvaluator(constantsBackend,
-          environmentDefines, typeEnvironment, enableAsserts, errorReporter)
+      constantEvaluator = new ConstantEvaluator(
+          constantsBackend,
+          options.environmentDefines,
+          typeEnvironment,
+          options.enableAsserts,
+          errorReporter)
         ..env = new EvaluationEnvironment();
     }
     List<Constant> constants = nodes.map(_evaluateConstantExpression).toList();
     constantEvaluator = savedConstantEvaluator;
     bool hasPragma = constants.any(_isPragma);
-    if (!emitAnnotations) {
+    if (!options.emitAnnotations) {
       if (hasPragma) {
         constants = constants.where(_isPragma).toList();
       } else {
@@ -469,7 +435,7 @@
     }
     int position = TreeNode.noOffset;
     int endPosition = TreeNode.noOffset;
-    if (emitSourcePositions && field.fileOffset != TreeNode.noOffset) {
+    if (options.emitSourcePositions && field.fileOffset != TreeNode.noOffset) {
       flags |= FieldDeclaration.hasSourcePositionsFlag;
       position = field.fileOffset;
       endPosition = field.fileEndOffset;
@@ -573,7 +539,7 @@
     }
     int position = TreeNode.noOffset;
     int endPosition = TreeNode.noOffset;
-    if (emitSourcePositions && member.fileOffset != TreeNode.noOffset) {
+    if (options.emitSourcePositions && member.fileOffset != TreeNode.noOffset) {
       flags |= FunctionDeclaration.hasSourcePositionsFlag;
       position = member.fileOffset;
       endPosition = member.fileEndOffset;
@@ -872,7 +838,7 @@
       _dartFfiLibrary ??= libraryIndex.tryGetLibrary('dart:ffi');
 
   void _recordSourcePosition(int fileOffset) {
-    if (emitSourcePositions) {
+    if (options.emitSourcePositions) {
       asm.currentSourcePosition = fileOffset;
     }
     maxSourcePosition = math.max(maxSourcePosition, fileOffset);
@@ -991,7 +957,7 @@
   }
 
   void _genReturnTOS() {
-    if (causalAsyncStacks &&
+    if (options.causalAsyncStacks &&
         parentFunction != null &&
         (parentFunction.dartAsyncMarker == AsyncMarker.Async ||
             parentFunction.dartAsyncMarker == AsyncMarker.AsyncStar)) {
@@ -1345,8 +1311,12 @@
       functionTypeParametersSet = functionTypeParameters.toSet();
     }
     // TODO(alexmarkov): improve caching in ConstantEvaluator and reuse it
-    constantEvaluator = new ConstantEvaluator(constantsBackend,
-        environmentDefines, typeEnvironment, enableAsserts, errorReporter)
+    constantEvaluator = new ConstantEvaluator(
+        constantsBackend,
+        options.environmentDefines,
+        typeEnvironment,
+        options.enableAsserts,
+        errorReporter)
       ..env = new EvaluationEnvironment();
 
     if (node.isAbstract || node is Field && !hasInitializerCode(node)) {
@@ -1369,7 +1339,7 @@
     savedMaxSourcePositions = <int>[];
     maxSourcePosition = node.fileOffset;
 
-    locals = new LocalVariables(node, enableAsserts, causalAsyncStacks);
+    locals = new LocalVariables(node, options);
     locals.enterScope(node);
     assert(!locals.isSyncYieldingFrame);
 
@@ -1405,7 +1375,7 @@
     if (!hasErrors) {
       Code code;
       if (hasCode) {
-        if (emitLocalVarInfo && node.function != null) {
+        if (options.emitLocalVarInfo && node.function != null) {
           // Leave the scope which was entered in _setupInitialContext.
           asm.localVariableTable
               .leaveScope(asm.offset, node.function.fileEndOffset);
@@ -1617,7 +1587,7 @@
   void _setupInitialContext(FunctionNode function) {
     _allocateContextIfNeeded();
 
-    if (emitLocalVarInfo && function != null) {
+    if (options.emitLocalVarInfo && function != null) {
       // Open scope after allocating context.
       asm.localVariableTable.enterScope(
           asm.offset, locals.currentContextLevel, function.fileOffset);
@@ -1915,7 +1885,7 @@
     _recordSourcePosition(function.fileOffset);
     _genPrologue(node, function);
 
-    if (causalAsyncStacks &&
+    if (options.causalAsyncStacks &&
         parentFunction != null &&
         (parentFunction.dartAsyncMarker == AsyncMarker.Async ||
             parentFunction.dartAsyncMarker == AsyncMarker.AsyncStar)) {
@@ -1950,7 +1920,7 @@
           function, continuationSwitchLabel, continuationSwitchVar);
     }
 
-    if (emitLocalVarInfo) {
+    if (options.emitLocalVarInfo) {
       // Leave the scope which was entered in _setupInitialContext.
       asm.localVariableTable.leaveScope(asm.offset, function.fileEndOffset);
     }
@@ -1985,7 +1955,7 @@
     int flags = 0;
     int position = TreeNode.noOffset;
     int endPosition = TreeNode.noOffset;
-    if (emitSourcePositions) {
+    if (options.emitSourcePositions) {
       position = (node is ast.FunctionDeclaration)
           ? node.fileOffset
           : function.fileOffset;
@@ -2139,7 +2109,7 @@
   void _enterScope(TreeNode node) {
     locals.enterScope(node);
     _allocateContextIfNeeded();
-    if (emitLocalVarInfo) {
+    if (options.emitLocalVarInfo) {
       asm.localVariableTable
           .enterScope(asm.offset, locals.currentContextLevel, node.fileOffset);
       _startRecordingMaxPosition(node.fileOffset);
@@ -2147,7 +2117,7 @@
   }
 
   void _leaveScope() {
-    if (emitLocalVarInfo) {
+    if (options.emitLocalVarInfo) {
       asm.localVariableTable.leaveScope(asm.offset, _endRecordingMaxPosition());
     }
     if (locals.currentContextSize > 0) {
@@ -3042,7 +3012,7 @@
 
   @override
   visitAssertStatement(AssertStatement node) {
-    if (!enableAsserts) {
+    if (!options.enableAsserts) {
       return;
     }
 
@@ -3051,8 +3021,10 @@
 
     _genConditionAndJumpIf(node.condition, true, done);
 
-    _genPushInt(omitAssertSourcePositions ? 0 : node.conditionStartOffset);
-    _genPushInt(omitAssertSourcePositions ? 0 : node.conditionEndOffset);
+    _genPushInt(
+        options.omitAssertSourcePositions ? 0 : node.conditionStartOffset);
+    _genPushInt(
+        options.omitAssertSourcePositions ? 0 : node.conditionEndOffset);
 
     if (node.message != null) {
       _generateNode(node.message);
@@ -3075,7 +3047,7 @@
 
   @override
   visitAssertBlock(AssertBlock node) {
-    if (!enableAsserts) {
+    if (!options.enableAsserts) {
       return;
     }
 
@@ -3635,7 +3607,7 @@
       } else {
         asm.emitPushNull();
       }
-      if (emitLocalVarInfo && !asm.isUnreachable && node.name != null) {
+      if (options.emitLocalVarInfo && !asm.isUnreachable && node.name != null) {
         _declareLocalVariable(node, maxInitializerPosition + 1);
       }
       _genStoreVar(node);
diff --git a/pkg/vm/lib/bytecode/local_vars.dart b/pkg/vm/lib/bytecode/local_vars.dart
index 14c5025..401a528 100644
--- a/pkg/vm/lib/bytecode/local_vars.dart
+++ b/pkg/vm/lib/bytecode/local_vars.dart
@@ -9,7 +9,9 @@
 import 'package:kernel/ast.dart';
 import 'package:kernel/transformations/continuation.dart'
     show ContinuationVariables;
+
 import 'dbc.dart';
+import 'options.dart' show BytecodeOptions;
 
 class LocalVariables {
   final Map<TreeNode, Scope> _scopes = <TreeNode, Scope>{};
@@ -24,8 +26,7 @@
       <TreeNode, VariableDeclaration>{};
   final Map<ForInStatement, VariableDeclaration> _capturedIteratorVars =
       <ForInStatement, VariableDeclaration>{};
-  final bool enableAsserts;
-  final bool causalAsyncStacks;
+  final BytecodeOptions options;
 
   Scope _currentScope;
   Frame _currentFrame;
@@ -139,7 +140,7 @@
   }
 
   VariableDeclaration get asyncStackTraceVar {
-    assert(causalAsyncStacks);
+    assert(options.causalAsyncStacks);
     assert(_currentFrame.isSyncYielding);
     return _currentFrame.parent
         .getSyntheticVar(ContinuationVariables.asyncStackTraceVar);
@@ -184,7 +185,7 @@
   List<VariableDeclaration> get sortedNamedParameters =>
       _currentFrame.sortedNamedParameters;
 
-  LocalVariables(Member node, this.enableAsserts, this.causalAsyncStacks) {
+  LocalVariables(Member node, this.options) {
     final scopeBuilder = new _ScopeBuilder(this);
     node.accept(scopeBuilder);
 
@@ -372,7 +373,7 @@
         _useVariable(_currentFrame.parent
             .getSyntheticVar(ContinuationVariables.awaitContextVar));
 
-        if (locals.causalAsyncStacks &&
+        if (locals.options.causalAsyncStacks &&
             (_currentFrame.parent.dartAsyncMarker == AsyncMarker.Async ||
                 _currentFrame.parent.dartAsyncMarker ==
                     AsyncMarker.AsyncStar)) {
@@ -627,7 +628,7 @@
 
   @override
   visitAssertStatement(AssertStatement node) {
-    if (!locals.enableAsserts) {
+    if (!locals.options.enableAsserts) {
       return;
     }
     super.visitAssertStatement(node);
@@ -635,7 +636,7 @@
 
   @override
   visitAssertBlock(AssertBlock node) {
-    if (!locals.enableAsserts) {
+    if (!locals.options.enableAsserts) {
       return;
     }
     _visitWithScope(node);
@@ -1089,7 +1090,7 @@
 
   @override
   visitAssertStatement(AssertStatement node) {
-    if (!locals.enableAsserts) {
+    if (!locals.options.enableAsserts) {
       return;
     }
     super.visitAssertStatement(node);
@@ -1097,7 +1098,7 @@
 
   @override
   visitAssertBlock(AssertBlock node) {
-    if (!locals.enableAsserts) {
+    if (!locals.options.enableAsserts) {
       return;
     }
     _visit(node, scope: true);
diff --git a/pkg/vm/lib/bytecode/options.dart b/pkg/vm/lib/bytecode/options.dart
new file mode 100644
index 0000000..516cf57
--- /dev/null
+++ b/pkg/vm/lib/bytecode/options.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2019, 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.
+
+library vm.bytecode.options;
+
+/// Collection of options for bytecode generator.
+class BytecodeOptions {
+  static Map<String, String> commandLineFlags = {
+    'annotations': 'Emit Dart annotations',
+    'local-var-info': 'Emit debug information about local variables',
+    'show-bytecode-size-stat': 'Show bytecode size breakdown',
+    'source-positions': 'Emit source positions',
+  };
+
+  bool enableAsserts;
+  bool causalAsyncStacks;
+  bool emitSourcePositions;
+  bool emitSourceFiles;
+  bool emitLocalVarInfo;
+  bool emitAnnotations;
+  bool omitAssertSourcePositions;
+  bool showBytecodeSizeStatistics;
+  Map<String, String> environmentDefines;
+
+  BytecodeOptions(
+      {this.enableAsserts = false,
+      this.causalAsyncStacks,
+      this.emitSourcePositions = false,
+      this.emitSourceFiles = false,
+      this.emitLocalVarInfo = false,
+      this.emitAnnotations = false,
+      this.omitAssertSourcePositions = false,
+      this.showBytecodeSizeStatistics = false,
+      this.environmentDefines = const <String, String>{}}) {
+    causalAsyncStacks ??=
+        environmentDefines['dart.developer.causal_async_stacks'] == 'true';
+  }
+
+  void parseCommandLineFlags(List<String> flags) {
+    if (flags == null) {
+      return;
+    }
+    for (String flag in flags) {
+      switch (flag) {
+        case 'source-positions':
+          emitSourcePositions = true;
+          break;
+        case 'local-var-info':
+          emitLocalVarInfo = true;
+          break;
+        case 'annotations':
+          emitAnnotations = true;
+          break;
+        case 'show-bytecode-size-stat':
+          showBytecodeSizeStatistics = true;
+          break;
+        default:
+          throw 'Unexpected bytecode flag $flag';
+      }
+    }
+  }
+}
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index 8402ef8..78a233a 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -48,6 +48,7 @@
 import 'bytecode/bytecode_serialization.dart' show BytecodeSizeStatistics;
 import 'bytecode/gen_bytecode.dart'
     show generateBytecode, createFreshComponentWithBytecode;
+import 'bytecode/options.dart' show BytecodeOptions;
 
 import 'constants_error_reporter.dart' show ForwardConstantEvaluationErrors;
 import 'target/install.dart' show installAdditionalTargets;
@@ -111,19 +112,13 @@
           'Split resulting kernel file into multiple files (one per package).',
       defaultsTo: false);
   args.addFlag('gen-bytecode', help: 'Generate bytecode', defaultsTo: false);
-  args.addFlag('emit-bytecode-source-positions',
-      help: 'Emit source positions in bytecode', defaultsTo: false);
-  args.addFlag('emit-bytecode-local-var-info',
-      help: 'Emit information about local variables in bytecode',
-      defaultsTo: false);
-  args.addFlag('emit-bytecode-annotations',
-      help: 'Emit Dart annotations in bytecode', defaultsTo: false);
+  args.addMultiOption('bytecode-options',
+      help: 'Specify options for bytecode generation:',
+      valueHelp: 'opt1,opt2,...',
+      allowed: BytecodeOptions.commandLineFlags.keys,
+      allowedHelp: BytecodeOptions.commandLineFlags);
   args.addFlag('drop-ast',
-      help: 'Drop AST for members with bytecode', defaultsTo: false);
-  args.addFlag('show-bytecode-size-stat',
-      help: 'Show bytecode size breakdown.', defaultsTo: false);
-  args.addFlag('use-future-bytecode-format',
-      help: 'Generate bytecode in the bleeding edge format', defaultsTo: false);
+      help: 'Include only bytecode into the output file', defaultsTo: false);
   args.addMultiOption('enable-experiment',
       help: 'Comma separated list of experimental features to enable.');
   args.addFlag('help',
@@ -166,18 +161,13 @@
   final bool aot = options['aot'];
   final bool tfa = options['tfa'];
   final bool linkPlatform = options['link-platform'];
+  final bool embedSources = options['embed-sources'];
   final bool genBytecode = options['gen-bytecode'];
-  final bool emitBytecodeSourcePositions =
-      options['emit-bytecode-source-positions'];
-  final bool emitBytecodeLocalVarInfo = options['emit-bytecode-local-var-info'];
-  final bool emitBytecodeAnnotations = options['emit-bytecode-annotations'];
   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 bool useProtobufTreeShaker = options['protobuf-tree-shaker'];
   final bool splitOutputByPackages = options['split-output-by-packages'];
-  final bool showBytecodeSizeStat = options['show-bytecode-size-stat'];
   final List<String> experimentalFlags = options['enable-experiment'];
   final Map<String, String> environmentDefines = {};
 
@@ -185,6 +175,12 @@
     return badUsageExitCode;
   }
 
+  final BytecodeOptions bytecodeOptions = new BytecodeOptions(
+      enableAsserts: enableAsserts,
+      emitSourceFiles: embedSources,
+      environmentDefines: environmentDefines)
+    ..parseCommandLineFlags(options['bytecode-options']);
+
   final target = createFrontEndTarget(targetName);
   if (target == null) {
     print('Failed to create front-end target $targetName.');
@@ -222,18 +218,15 @@
     ..onDiagnostic = (DiagnosticMessage m) {
       errorDetector(m);
     }
-    ..embedSourceText = options['embed-sources'];
+    ..embedSourceText = embedSources;
 
   final component = await compileToKernel(mainUri, compilerOptions,
       aot: aot,
       useGlobalTypeFlowAnalysis: tfa,
       environmentDefines: environmentDefines,
       genBytecode: genBytecode,
-      emitBytecodeSourcePositions: emitBytecodeSourcePositions,
-      emitBytecodeLocalVarInfo: emitBytecodeLocalVarInfo,
-      emitBytecodeAnnotations: emitBytecodeAnnotations,
+      bytecodeOptions: bytecodeOptions,
       dropAST: dropAST && !splitOutputByPackages,
-      useFutureBytecodeFormat: useFutureBytecodeFormat,
       enableAsserts: enableAsserts,
       enableConstantEvaluation: enableConstantEvaluation,
       useProtobufTreeShaker: useProtobufTreeShaker);
@@ -244,7 +237,7 @@
     return compileTimeErrorExitCode;
   }
 
-  if (showBytecodeSizeStat && !splitOutputByPackages) {
+  if (bytecodeOptions.showBytecodeSizeStatistics && !splitOutputByPackages) {
     BytecodeSizeStatistics.reset();
   }
 
@@ -253,7 +246,7 @@
   printer.writeComponentFile(component);
   await sink.close();
 
-  if (showBytecodeSizeStat && !splitOutputByPackages) {
+  if (bytecodeOptions.showBytecodeSizeStatistics && !splitOutputByPackages) {
     BytecodeSizeStatistics.dump();
   }
 
@@ -267,15 +260,9 @@
       compilerOptions,
       component,
       outputFileName,
-      environmentDefines: environmentDefines,
       genBytecode: genBytecode,
-      enableAsserts: enableAsserts,
-      emitBytecodeSourcePositions: emitBytecodeSourcePositions,
-      emitBytecodeLocalVarInfo: emitBytecodeLocalVarInfo,
-      emitBytecodeAnnotations: emitBytecodeAnnotations,
+      bytecodeOptions: bytecodeOptions,
       dropAST: dropAST,
-      showBytecodeSizeStat: showBytecodeSizeStat,
-      useFutureBytecodeFormat: useFutureBytecodeFormat,
     );
   }
 
@@ -292,11 +279,8 @@
     bool useGlobalTypeFlowAnalysis: false,
     Map<String, String> environmentDefines,
     bool genBytecode: false,
-    bool emitBytecodeSourcePositions: false,
-    bool emitBytecodeLocalVarInfo: false,
-    bool emitBytecodeAnnotations: false,
+    BytecodeOptions bytecodeOptions,
     bool dropAST: false,
-    bool useFutureBytecodeFormat: false,
     bool enableAsserts: false,
     bool enableConstantEvaluation: true,
     bool useProtobufTreeShaker: false}) async {
@@ -324,14 +308,7 @@
 
   if (genBytecode && !errorDetector.hasCompilationErrors && component != null) {
     await runWithFrontEndCompilerContext(source, options, component, () {
-      generateBytecode(component,
-          enableAsserts: enableAsserts,
-          emitSourcePositions: emitBytecodeSourcePositions,
-          emitSourceFiles: options.embedSourceText,
-          emitLocalVarInfo: emitBytecodeLocalVarInfo,
-          emitAnnotations: emitBytecodeAnnotations,
-          useFutureBytecodeFormat: useFutureBytecodeFormat,
-          environmentDefines: environmentDefines);
+      generateBytecode(component, options: bytecodeOptions);
     });
 
     if (dropAST) {
@@ -661,15 +638,9 @@
   CompilerOptions compilerOptions,
   Component component,
   String outputFileName, {
-  Map<String, String> environmentDefines,
   bool genBytecode: false,
-  bool enableAsserts: true,
-  bool emitBytecodeSourcePositions: false,
-  bool emitBytecodeLocalVarInfo: false,
-  bool emitBytecodeAnnotations: false,
+  BytecodeOptions bytecodeOptions,
   bool dropAST: false,
-  bool showBytecodeSizeStat: false,
-  bool useFutureBytecodeFormat: false,
 }) async {
   // Package sharing: make the encoding not depend on the order in which parts
   // of a package are loaded.
@@ -693,7 +664,7 @@
   final List<String> packages = packagesSet.toList();
   packages.add('main'); // Make sure main package is last.
 
-  if (showBytecodeSizeStat) {
+  if (bytecodeOptions.showBytecodeSizeStatistics) {
     BytecodeSizeStatistics.reset();
   }
 
@@ -724,15 +695,9 @@
             .where((lib) => packageFor(lib) == package)
             .toList();
         generateBytecode(component,
+            options: bytecodeOptions,
             libraries: libraries,
-            hierarchy: hierarchy,
-            enableAsserts: enableAsserts,
-            emitSourcePositions: emitBytecodeSourcePositions,
-            emitSourceFiles: compilerOptions.embedSourceText,
-            emitLocalVarInfo: emitBytecodeLocalVarInfo,
-            emitAnnotations: emitBytecodeAnnotations,
-            useFutureBytecodeFormat: useFutureBytecodeFormat,
-            environmentDefines: environmentDefines);
+            hierarchy: hierarchy);
 
         if (dropAST) {
           partComponent = createFreshComponentWithBytecode(component);
@@ -750,7 +715,7 @@
     }
   });
 
-  if (showBytecodeSizeStat) {
+  if (bytecodeOptions.showBytecodeSizeStatistics) {
     BytecodeSizeStatistics.dump();
   }
 
diff --git a/pkg/vm/test/bytecode/gen_bytecode_test.dart b/pkg/vm/test/bytecode/gen_bytecode_test.dart
index 887a8a0..673a16e 100644
--- a/pkg/vm/test/bytecode/gen_bytecode_test.dart
+++ b/pkg/vm/test/bytecode/gen_bytecode_test.dart
@@ -10,6 +10,7 @@
 import 'package:kernel/kernel.dart';
 import 'package:test/test.dart';
 import 'package:vm/bytecode/gen_bytecode.dart' show generateBytecode;
+import 'package:vm/bytecode/options.dart' show BytecodeOptions;
 import 'package:vm/kernel_front_end.dart' show runWithFrontEndCompilerContext;
 
 import '../common_test_utils.dart';
@@ -34,7 +35,9 @@
     // Need to omit source positions from bytecode as they are different on
     // Linux and Windows (due to differences in newline characters).
     generateBytecode(component,
-        omitAssertSourcePositions: true, libraries: [mainLibrary]);
+        options: new BytecodeOptions(
+            enableAsserts: true, omitAssertSourcePositions: true),
+        libraries: [mainLibrary]);
   });
 
   component.libraries.removeWhere((lib) => lib != mainLibrary);