[vm/bytecode] Add an option to print bytecode size statistics

This CL adds '--show-bytecode-size-stat' option to gen_kernel and
Fuchsia kernel compiler to print breakdown of size of generated bytecode.

Change-Id: I0637738b0a1e9c635cb687bbfd482a9bdae32183
Reviewed-on: https://dart-review.googlesource.com/c/90322
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Régis Crelier <regis@google.com>
Auto-Submit: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Régis Crelier <regis@google.com>
diff --git a/pkg/vm/lib/bytecode/bytecode_serialization.dart b/pkg/vm/lib/bytecode/bytecode_serialization.dart
index a592981..bce0817 100644
--- a/pkg/vm/lib/bytecode/bytecode_serialization.dart
+++ b/pkg/vm/lib/bytecode/bytecode_serialization.dart
@@ -277,6 +277,7 @@
   }
 
   void write(BufferedWriter writer) {
+    final start = writer.offset;
     writer.writeUInt32(_oneByteStrings.length);
     writer.writeUInt32(_twoByteStrings.length);
     int endOffset = 0;
@@ -301,6 +302,7 @@
       }
     }
     _written = true;
+    BytecodeSizeStatistics.stringTableSize += (writer.offset - start);
   }
 
   StringTable.read(BufferedReader reader) {
@@ -358,3 +360,48 @@
     return sb.toString();
   }
 }
+
+class ConstantPoolEntryStatistics {
+  final String name;
+  int size = 0;
+  int count = 0;
+
+  ConstantPoolEntryStatistics(this.name);
+}
+
+class BytecodeSizeStatistics {
+  static int componentSize = 0;
+  static int objectTableSize = 0;
+  static int stringTableSize = 0;
+  static int membersSize = 0;
+  static int constantPoolSize = 0;
+  static int instructionsSize = 0;
+  static List<ConstantPoolEntryStatistics> constantPoolStats =
+      <ConstantPoolEntryStatistics>[];
+
+  static void reset() {
+    componentSize = 0;
+    objectTableSize = 0;
+    stringTableSize = 0;
+    membersSize = 0;
+    constantPoolSize = 0;
+    instructionsSize = 0;
+    constantPoolStats = <ConstantPoolEntryStatistics>[];
+  }
+
+  static void dump() {
+    print("Bytecode size statistics:");
+    print("  Bytecode component:  $componentSize");
+    print("   - object table:     $objectTableSize");
+    print("   - string table:     $stringTableSize");
+    print("  Bytecode members:    $membersSize");
+    print("   - constant pool:    $constantPoolSize");
+    for (var cpStat in constantPoolStats) {
+      final name = cpStat.name.padRight(40);
+      final size = cpStat.size.toString().padLeft(10);
+      final count = cpStat.count.toString().padLeft(8);
+      print("       - $name:    $size  (count: $count)");
+    }
+    print("   - instructions:     $instructionsSize");
+  }
+}
diff --git a/pkg/vm/lib/bytecode/constant_pool.dart b/pkg/vm/lib/bytecode/constant_pool.dart
index e4c77c1..a81f5f0 100644
--- a/pkg/vm/lib/bytecode/constant_pool.dart
+++ b/pkg/vm/lib/bytecode/constant_pool.dart
@@ -10,7 +10,12 @@
 
 import 'dbc.dart' show constantPoolIndexLimit, BytecodeLimitExceededException;
 import 'bytecode_serialization.dart'
-    show BufferedWriter, BufferedReader, StringTable;
+    show
+        BufferedWriter,
+        BufferedReader,
+        BytecodeSizeStatistics,
+        ConstantPoolEntryStatistics,
+        StringTable;
 import 'object_table.dart' show ObjectHandle, ObjectTable;
 
 /*
@@ -210,6 +215,9 @@
   kInterfaceCall,
 }
 
+String constantTagToString(ConstantTag tag) =>
+    tag.toString().substring('ConstantTag.k'.length);
+
 abstract class ConstantPoolEntry {
   const ConstantPoolEntry();
 
@@ -1303,13 +1311,28 @@
   }
 
   void write(BufferedWriter writer) {
+    final start = writer.offset;
+    if (BytecodeSizeStatistics.constantPoolStats.isEmpty) {
+      for (var tag in ConstantTag.values) {
+        BytecodeSizeStatistics.constantPoolStats
+            .add(new ConstantPoolEntryStatistics(constantTagToString(tag)));
+      }
+    }
     writer.writePackedUInt30(entries.length);
     entries.forEach((e) {
       if (e is _ReservedConstantPoolEntry) {
         return;
       }
+
+      final entryStart = writer.offset;
+
       e.write(writer);
+
+      final entryStat = BytecodeSizeStatistics.constantPoolStats[e.tag.index];
+      entryStat.size += (writer.offset - entryStart);
+      ++entryStat.count;
     });
+    BytecodeSizeStatistics.constantPoolSize += (writer.offset - start);
   }
 
   ConstantPool.read(BufferedReader reader)
diff --git a/pkg/vm/lib/bytecode/object_table.dart b/pkg/vm/lib/bytecode/object_table.dart
index 4b17c60..baa1460 100644
--- a/pkg/vm/lib/bytecode/object_table.dart
+++ b/pkg/vm/lib/bytecode/object_table.dart
@@ -12,6 +12,7 @@
         BufferedWriter,
         BufferedReader,
         BytecodeObject,
+        BytecodeSizeStatistics,
         ObjectReader,
         ObjectWriter,
         StringWriter;
@@ -1022,6 +1023,7 @@
   void write(BufferedWriter writer) {
     assert(writer.objectWriter == this);
     assert(_indexTable != null);
+    final start = writer.offset;
 
     BufferedWriter contentsWriter = new BufferedWriter.fromWriter(writer);
     List<int> offsets = new List<int>(_indexTable.length);
@@ -1045,6 +1047,7 @@
         obj.indexStrings(writer.stringWriter);
       }
     }
+    BytecodeSizeStatistics.objectTableSize += (writer.offset - start);
   }
 
   ObjectTable.read(BufferedReader reader) {
diff --git a/pkg/vm/lib/kernel_front_end.dart b/pkg/vm/lib/kernel_front_end.dart
index 6b50c79..eb56144 100644
--- a/pkg/vm/lib/kernel_front_end.dart
+++ b/pkg/vm/lib/kernel_front_end.dart
@@ -43,6 +43,7 @@
 import 'package:kernel/vm/constants_native_effects.dart' as vm_constants;
 
 import 'bytecode/ast_remover.dart' show ASTRemover;
+import 'bytecode/bytecode_serialization.dart' show BytecodeSizeStatistics;
 import 'bytecode/gen_bytecode.dart' show generateBytecode;
 
 import 'constants_error_reporter.dart' show ForwardConstantEvaluationErrors;
@@ -106,6 +107,8 @@
       help: 'Emit source positions in bytecode', defaultsTo: false);
   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);
   args.addMultiOption('enable-experiment',
@@ -151,6 +154,7 @@
   final bool enableAsserts = options['enable-asserts'];
   final bool enableConstantEvaluation = options['enable-constant-evaluation'];
   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 = {};
 
@@ -214,11 +218,19 @@
     return compileTimeErrorExitCode;
   }
 
+  if (showBytecodeSizeStat && !splitOutputByPackages) {
+    BytecodeSizeStatistics.reset();
+  }
+
   final IOSink sink = new File(outputFileName).openWrite();
   final BinaryPrinter printer = new BinaryPrinter(sink);
   printer.writeComponentFile(component);
   await sink.close();
 
+  if (showBytecodeSizeStat && !splitOutputByPackages) {
+    BytecodeSizeStatistics.dump();
+  }
+
   if (depfile != null) {
     await writeDepfile(fileSystem, component, outputFileName, depfile);
   }
@@ -233,6 +245,7 @@
       genBytecode: genBytecode,
       emitBytecodeSourcePositions: emitBytecodeSourcePositions,
       dropAST: dropAST,
+      showBytecodeSizeStat: showBytecodeSizeStat,
     );
   }
 
@@ -587,6 +600,7 @@
   bool genBytecode: false,
   bool emitBytecodeSourcePositions: false,
   bool dropAST: false,
+  bool showBytecodeSizeStat: false,
   bool useFutureBytecodeFormat: false,
 }) async {
   // Package sharing: make the encoding not depend on the order in which parts
@@ -611,6 +625,10 @@
   final List<String> packages = packagesSet.toList();
   packages.add('main'); // Make sure main package is last.
 
+  if (showBytecodeSizeStat) {
+    BytecodeSizeStatistics.reset();
+  }
+
   await runWithFrontEndCompilerContext(source, compilerOptions, component,
       () async {
     for (String package in packages) {
@@ -653,6 +671,10 @@
     }
   });
 
+  if (showBytecodeSizeStat) {
+    BytecodeSizeStatistics.dump();
+  }
+
   final IOSink packagesList = new File('$outputFileName-packages').openWrite();
   for (String package in packages) {
     packagesList.writeln(package);
diff --git a/pkg/vm/lib/metadata/bytecode.dart b/pkg/vm/lib/metadata/bytecode.dart
index 7a86c70..cd9f65e 100644
--- a/pkg/vm/lib/metadata/bytecode.dart
+++ b/pkg/vm/lib/metadata/bytecode.dart
@@ -6,7 +6,7 @@
 
 import 'package:kernel/ast.dart';
 import '../bytecode/bytecode_serialization.dart'
-    show BufferedWriter, BufferedReader, StringTable;
+    show BufferedWriter, BufferedReader, BytecodeSizeStatistics, StringTable;
 import '../bytecode/constant_pool.dart' show ConstantPool;
 import '../bytecode/dbc.dart'
     show
@@ -121,6 +121,7 @@
 
   @override
   void write(BufferedWriter writer) {
+    final start = writer.offset;
     writer.writePackedUInt30(flags);
     if (hasClosures) {
       writer.writePackedUInt30(closures.length);
@@ -140,6 +141,7 @@
     if (hasClosures) {
       closures.forEach((c) => c.bytecode.write(writer));
     }
+    BytecodeSizeStatistics.membersSize += (writer.offset - start);
   }
 
   factory MemberBytecode.read(BufferedReader reader) {
@@ -368,6 +370,7 @@
 
   @override
   void write(BufferedWriter writer) {
+    final start = writer.offset;
     objectTable.allocateIndexTable();
 
     // Writing object table may add new strings to strings table,
@@ -384,6 +387,7 @@
 
     writer.writeBytes(stringsWriter.takeBytes());
     writer.writeBytes(objectsWriter.takeBytes());
+    BytecodeSizeStatistics.componentSize += (writer.offset - start);
   }
 
   BytecodeComponent.read(BufferedReader reader) {
@@ -461,6 +465,7 @@
   writer.writePackedUInt30(bytecodes.length);
   writer.align(bytecodeInstructionsAlignment);
   writer.writeBytes(bytecodes);
+  BytecodeSizeStatistics.instructionsSize += bytecodes.length;
 }
 
 List<int> _readBytecodeInstructions(BufferedReader reader) {