Several dump-info fixes.

Includes:
 * avoid prettyPrint in dump-info (which removes inconsistencies from shadowing
   when using fast-startup), instead log start and end offsets and record the
   emitted string.
 * fix startup emitter to track a few more things (class names, inherit calls,
   lazy initializers, etc)
Change-Id: I213edf526173212e918129440dc6dec996a9dd55
Reviewed-on: https://dart-review.googlesource.com/c/89084
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/dump_info.dart b/pkg/compiler/lib/src/dump_info.dart
index e2722a0..d3f9c4d 100644
--- a/pkg/compiler/lib/src/dump_info.dart
+++ b/pkg/compiler/lib/src/dump_info.dart
@@ -32,6 +32,7 @@
   final Compiler compiler;
   final JClosedWorld closedWorld;
   final GlobalTypeInferenceResults _globalInferenceResults;
+  final DumpInfoTask dumpInfoTask;
 
   JElementEnvironment get environment => closedWorld.elementEnvironment;
   CodegenWorldBuilder get codegenWorldBuilder => compiler.codegenWorldBuilder;
@@ -41,13 +42,13 @@
   final Map<ConstantValue, Info> _constantToInfo = <ConstantValue, Info>{};
   final Map<OutputUnit, OutputUnitInfo> _outputToInfo = {};
 
-  ElementInfoCollector(
-      this.compiler, this.closedWorld, this._globalInferenceResults);
+  ElementInfoCollector(this.compiler, this.dumpInfoTask, this.closedWorld,
+      this._globalInferenceResults);
 
   void run() {
-    compiler.dumpInfoTask._constantToNode.forEach((constant, node) {
+    dumpInfoTask._constantToNode.forEach((constant, node) {
       // TODO(sigmund): add dependencies on other constants
-      var size = compiler.dumpInfoTask._nodeToSize[node];
+      var size = dumpInfoTask._nodeData[node].length;
       var code = jsAst.prettyPrint(node,
           enableMinification: compiler.options.enableMinification);
       var info = new ConstantInfo(
@@ -64,8 +65,8 @@
   /// output size. Either because it is a function being emitted or inlined,
   /// or because it is an entity that holds dependencies to other entities.
   bool shouldKeep(Entity entity) {
-    return compiler.dumpInfoTask.impacts.containsKey(entity) ||
-        compiler.dumpInfoTask.inlineCount.containsKey(entity);
+    return dumpInfoTask.impacts.containsKey(entity) ||
+        dumpInfoTask.inlineCount.containsKey(entity);
   }
 
   LibraryInfo visitLibrary(LibraryEntity lib) {
@@ -73,7 +74,7 @@
     if (libname.isEmpty) {
       libname = '<unnamed>';
     }
-    int size = compiler.dumpInfoTask.sizeOf(lib);
+    int size = dumpInfoTask.sizeOf(lib);
     LibraryInfo info = new LibraryInfo(libname, lib.canonicalUri, null, size);
     _entityToInfo[lib] = info;
 
@@ -122,8 +123,8 @@
       return null;
     }
 
-    int size = compiler.dumpInfoTask.sizeOf(field);
-    String code = compiler.dumpInfoTask.codeOf(field);
+    int size = dumpInfoTask.sizeOf(field);
+    String code = dumpInfoTask.codeOf(field);
 
     // TODO(het): Why doesn't `size` account for the code size already?
     if (code != null) size += code.length;
@@ -162,7 +163,7 @@
         outputUnit: _unitInfoForClass(clazz));
     _entityToInfo[clazz] = classInfo;
 
-    int size = compiler.dumpInfoTask.sizeOf(clazz);
+    int size = dumpInfoTask.sizeOf(clazz);
     environment.forEachLocalClassMember(clazz, (member) {
       if (member.isFunction || member.isGetter || member.isSetter) {
         FunctionInfo functionInfo = visitFunction(member);
@@ -213,7 +214,7 @@
     ClosureInfo closureInfo = new ClosureInfo(
         name: element.name,
         outputUnit: _unitInfoForClass(element),
-        size: compiler.dumpInfoTask.sizeOf(element));
+        size: dumpInfoTask.sizeOf(element));
     _entityToInfo[element] = closureInfo;
 
     FunctionEntity callMethod = closedWorld.elementEnvironment
@@ -229,7 +230,7 @@
   }
 
   FunctionInfo visitFunction(FunctionEntity function) {
-    int size = compiler.dumpInfoTask.sizeOf(function);
+    int size = dumpInfoTask.sizeOf(function);
     // TODO(sigmund): consider adding a small info to represent unreachable
     // code here.
     if (size == 0 && !shouldKeep(function)) return null;
@@ -260,7 +261,7 @@
           : false,
       isExternal: function.isExternal,
     );
-    String code = compiler.dumpInfoTask.codeOf(function);
+    String code = dumpInfoTask.codeOf(function);
 
     List<ParameterInfo> parameters = <ParameterInfo>[];
     List<String> inferredParameterTypes = <String>[];
@@ -280,7 +281,7 @@
     String sideEffects =
         '${_globalInferenceResults.inferredData.getSideEffectsOfElement(function)}';
 
-    int inlinedCount = compiler.dumpInfoTask.inlineCount[function];
+    int inlinedCount = dumpInfoTask.inlineCount[function];
     if (inlinedCount == null) inlinedCount = 0;
 
     FunctionInfo info = new FunctionInfo(
@@ -400,10 +401,10 @@
   /// The size of the generated output.
   int _programSize;
 
-  // A set of javascript AST nodes that we care about the size of.
-  // This set is automatically populated when registerEntityAst()
-  // is called.
-  final Set<jsAst.Node> _tracking = new Set<jsAst.Node>();
+  /// Data associated with javascript AST nodes. The map only contains keys for
+  /// nodes that we care about.  Keys are automatically added when
+  /// [registerEntityAst] is called.
+  final Map<jsAst.Node, _CodeData> _nodeData = <jsAst.Node, _CodeData>{};
 
   // A mapping from Dart Entities to Javascript AST Nodes.
   final Map<Entity, List<jsAst.Node>> _entityToNodes =
@@ -411,10 +412,6 @@
   final Map<ConstantValue, jsAst.Node> _constantToNode =
       <ConstantValue, jsAst.Node>{};
 
-  // A mapping from Javascript AST Nodes to the size of their
-  // pretty-printed contents.
-  final Map<jsAst.Node, int> _nodeToSize = <jsAst.Node, int>{};
-
   final Map<Entity, int> inlineCount = <Entity, int>{};
 
   // A mapping from an entity to a list of entities that are
@@ -470,24 +467,15 @@
     return selections;
   }
 
-  // Returns true if we care about tracking the size of
-  // this node.
-  bool isTracking(jsAst.Node code) {
-    if (compiler.options.dumpInfo) {
-      return _tracking.contains(code);
-    } else {
-      return false;
-    }
-  }
-
   /// Registers that a javascript AST node [code] was produced by the dart
   /// Entity [entity].
-  void registerEntityAst(Entity entity, jsAst.Node code) {
+  void registerEntityAst(Entity entity, jsAst.Node code,
+      {LibraryEntity library}) {
     if (compiler.options.dumpInfo) {
       _entityToNodes
           .putIfAbsent(entity, () => new List<jsAst.Node>())
           .add(code);
-      _tracking.add(code);
+      _nodeData[code] ??= _CodeData();
     }
   }
 
@@ -496,19 +484,36 @@
       assert(_constantToNode[constant] == null ||
           _constantToNode[constant] == code);
       _constantToNode[constant] = code;
-      _tracking.add(code);
+      _nodeData[code] ??= _CodeData();
     }
   }
 
-  /// Records the size of a dart AST node after it has been pretty-printed into
-  /// the output buffer.
-  void recordAstSize(jsAst.Node node, int size) {
-    if (isTracking(node)) {
-      //TODO: should I be incrementing here instead?
-      _nodeToSize[node] = size;
+  // TODO(sigmund): delete the stack once we stop emitting the source text.
+  List<_CodeData> _stack = [];
+  void enterNode(jsAst.Node node, int start) {
+    var data = _nodeData[node];
+    if (data != null) {
+      _stack.add(data);
+      data.start = start;
     }
   }
 
+  void emit(String string) {
+    // Note: historically we emitted the full body of classes and methods, so
+    // instance methods ended up emitted twice.  Once we use a different
+    // encoding of dump info, we also plan to remove this duplication.
+    _stack.forEach((f) => f.text.write(string));
+  }
+
+  void exitNode(jsAst.Node node, int start, int end, int closing) {
+    var data = _nodeData[node];
+    if (data == null) return;
+    var last = _stack.removeLast();
+    assert(data == last);
+    assert(data.start == start);
+    data.end = end;
+  }
+
   /// Returns the size of the source code that was generated for an entity.
   /// If no source code was produced, return 0.
   int sizeOf(Entity entity) {
@@ -519,7 +524,7 @@
     }
   }
 
-  int sizeOfNode(jsAst.Node node) => _nodeToSize[node] ?? 0;
+  int sizeOfNode(jsAst.Node node) => _nodeData[node].length ?? 0;
 
   String codeOf(Entity entity) {
     List<jsAst.Node> code = _entityToNodes[entity];
@@ -527,8 +532,7 @@
     // Concatenate rendered ASTs.
     StringBuffer sb = new StringBuffer();
     for (jsAst.Node ast in code) {
-      sb.writeln(jsAst.prettyPrint(ast,
-          enableMinification: compiler.options.enableMinification));
+      sb.writeln(_nodeData[ast].text);
     }
     return sb.toString();
   }
@@ -537,7 +541,7 @@
       GlobalTypeInferenceResults globalInferenceResults) {
     measure(() {
       infoCollector = new ElementInfoCollector(
-          compiler, closedWorld, globalInferenceResults)
+          compiler, this, closedWorld, globalInferenceResults)
         ..run();
       StringBuffer jsonBuffer = new StringBuffer();
       dumpInfoJson(jsonBuffer, closedWorld);
@@ -634,3 +638,15 @@
     });
   }
 }
+
+/// Helper class to store what dump-info will show for a piece of code.
+///
+/// Currently we print out the actual text, in the future, we will only emit
+/// start and end offsets.
+class _CodeData {
+  int start;
+  int end;
+  StringBuffer text = new StringBuffer();
+
+  int get length => end - start;
+}
diff --git a/pkg/compiler/lib/src/js/js.dart b/pkg/compiler/lib/src/js/js.dart
index 9893f5b..523cd66 100644
--- a/pkg/compiler/lib/src/js/js.dart
+++ b/pkg/compiler/lib/src/js/js.dart
@@ -69,20 +69,20 @@
 
   @override
   void emit(String string) {
+    monitor?.emit(string);
     outBuffer.add(string);
   }
 
   @override
   void enterNode(Node node, int startPosition) {
+    monitor?.enterNode(node, startPosition);
     codePositionListener.onStartPosition(node, startPosition);
   }
 
   @override
   void exitNode(
       Node node, int startPosition, int endPosition, int closingPosition) {
-    if (monitor != null) {
-      monitor.recordAstSize(node, endPosition - startPosition);
-    }
+    monitor?.exitNode(node, startPosition, endPosition, closingPosition);
     codePositionListener.onPositions(
         node, startPosition, endPosition, closingPosition);
   }
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 5ef3622..e5cabfe 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -597,6 +597,13 @@
     return js.js('#.#', [cls.holder.name, cls.name]);
   }
 
+  void registerEntityAst(Entity entity, js.Node code, {LibraryEntity library}) {
+    compiler.dumpInfoTask.registerEntityAst(entity, code);
+    // TODO(sigmund): stop recoding associations twice, dump-info already
+    // has library to element dependencies to recover this data.
+    if (library != null) compiler.dumpInfoTask.registerEntityAst(library, code);
+  }
+
   js.Statement emitMainFragment(
       Program program, DeferredLoadingState deferredLoadingState) {
     MainFragment fragment = program.fragments.first;
@@ -792,30 +799,28 @@
         .where((Holder holder) => !holder.isStaticStateHolder)
         .toList(growable: false);
 
-    Map<Holder, Map<js.Name, js.Expression>> holderCode = {};
+    Map<Holder, List<js.Property>> holderCode = {};
 
     for (Holder holder in holders) {
-      holderCode[holder] = <js.Name, js.Expression>{};
+      holderCode[holder] = <js.Property>[];
     }
 
     for (Library library in fragment.libraries) {
       for (StaticMethod method in library.statics) {
         assert(!method.holder.isStaticStateHolder);
         var staticMethod = emitStaticMethod(method);
-        if (compiler.options.dumpInfo) {
-          for (var code in staticMethod.values) {
-            compiler.dumpInfoTask.registerEntityAst(method.element, code);
-            compiler.dumpInfoTask.registerEntityAst(library.element, code);
-          }
-        }
-        holderCode[method.holder].addAll(staticMethod);
+        staticMethod.forEach((key, value) {
+          var property = new js.Property(js.quoteName(key), value);
+          holderCode[method.holder].add(property);
+          registerEntityAst(method.element, property, library: library.element);
+        });
       }
       for (Class cls in library.classes) {
         assert(!cls.holder.isStaticStateHolder);
         var constructor = emitConstructor(cls);
-        compiler.dumpInfoTask.registerEntityAst(cls.element, constructor);
-        compiler.dumpInfoTask.registerEntityAst(library.element, constructor);
-        holderCode[cls.holder][cls.name] = constructor;
+        var property = new js.Property(js.quoteName(cls.name), constructor);
+        registerEntityAst(cls.element, property, library: library.element);
+        holderCode[cls.holder].add(property);
       }
     }
 
@@ -823,10 +828,7 @@
     List<Holder> activeHolders = [];
 
     for (Holder holder in holders) {
-      List<js.Property> properties = [];
-      holderCode[holder].forEach((js.Name key, js.Expression value) {
-        properties.add(new js.Property(js.quoteName(key), value));
-      });
+      List<js.Property> properties = holderCode[holder];
       if (properties.isEmpty) {
         holderInitializations.add(new js.VariableInitialization(
             new js.VariableDeclaration(holder.name, allowRename: false),
@@ -1006,8 +1008,7 @@
       var proto = js.js.statement(
           '#.prototype = #;', [classReference(cls), emitPrototype(cls)]);
       ClassEntity element = cls.element;
-      compiler.dumpInfoTask.registerEntityAst(element, proto);
-      compiler.dumpInfoTask.registerEntityAst(element.library, proto);
+      registerEntityAst(element, proto, library: element.library);
       return proto;
     }).toList(growable: false);
 
@@ -1050,7 +1051,7 @@
       emitInstanceMethod(method)
           .forEach((js.Expression name, js.Expression code) {
         var prop = js.Property(name, code);
-        compiler.dumpInfoTask.registerEntityAst(method.element, prop);
+        registerEntityAst(method.element, prop);
         properties.add(prop);
       });
     });
@@ -1216,11 +1217,13 @@
         if (cls.isSoftDeferred != softDeferred) continue;
         collect(cls);
         if (cls.mixinClass != null) {
-          mixinCalls.add(js.js.statement('#(#, #)', [
+          js.Statement statement = js.js.statement('#(#, #)', [
             locals.find('_mixin', 'hunkHelpers.mixin'),
             classReference(cls),
             classReference(cls.mixinClass),
-          ]));
+          ]);
+          registerEntityAst(cls.element, statement, library: library.element);
+          mixinCalls.add(statement);
         }
       }
     }
@@ -1231,13 +1234,27 @@
           ? new js.LiteralNull()
           : classReference(superclass);
       if (list.length == 1) {
-        inheritCalls.add(js.js.statement('#(#, #)', [
+        Class cls = list.single;
+        var statement = js.js.statement('#(#, #)', [
           locals.find('_inherit', 'hunkHelpers.inherit'),
-          classReference(list.single),
+          classReference(cls),
           superclassReference
-        ]));
+        ]);
+        registerEntityAst(cls.element, statement, library: cls.element.library);
+        inheritCalls.add(statement);
       } else {
-        var listElements = list.map(classReference).toList();
+        List<js.Expression> listElements = [];
+        // Since inheritMany shares the superclass reference, we attribute it
+        // only to the first subclass.
+        ClassEntity firstClass = list.first.element;
+        registerEntityAst(firstClass, superclassReference,
+            library: firstClass.library);
+        for (Class cls in list) {
+          js.Expression reference = classReference(cls);
+          registerEntityAst(cls.element, reference,
+              library: cls.element.library);
+          listElements.add(reference);
+        }
         inheritCalls.add(js.js.statement('#(#, #)', [
           locals.find('_inheritMany', 'hunkHelpers.inheritMany'),
           superclassReference,
@@ -1271,14 +1288,18 @@
           if (method.aliasName != null) {
             if (firstAlias) {
               firstAlias = false;
-              assignments.add(js.js.statement(
+              js.Statement statement = js.js.statement(
                   assignments.isEmpty
                       ? 'var _ = #.prototype;'
                       : '_ = #.prototype',
-                  classReference(cls)));
+                  classReference(cls));
+              registerEntityAst(method.element, statement);
+              assignments.add(statement);
             }
-            assignments.add(js.js.statement('_.# = _.#',
-                [js.quoteName(method.aliasName), js.quoteName(method.name)]));
+            js.Statement statement = js.js.statement('_.# = _.#',
+                [js.quoteName(method.aliasName), js.quoteName(method.name)]);
+            registerEntityAst(method.element, statement);
+            assignments.add(statement);
           }
         }
       }
@@ -1489,8 +1510,11 @@
         if (method is StaticDartMethod) {
           if (method.needsTearOff) {
             Holder holder = method.holder;
-            inits.add(
-                emitInstallTearOff(new js.VariableUse(holder.name), method));
+            js.Statement statement =
+                emitInstallTearOff(new js.VariableUse(holder.name), method);
+            registerEntityAst(method.element, statement,
+                library: library.element);
+            inits.add(statement);
           }
         }
       }
@@ -1508,7 +1532,9 @@
           reference = js.js('# = #', [temp, container]);
         }
         for (InstanceMethod method in methods) {
-          inits.add(emitInstallTearOff(reference, method));
+          js.Statement statement = emitInstallTearOff(reference, method);
+          registerEntityAst(method.element, statement);
+          inits.add(statement);
           reference = temp; // Second and subsequent calls use temp.
         }
       }
@@ -1558,8 +1584,11 @@
     //
     Iterable<js.Statement> statements = fields.map((StaticField field) {
       assert(field.holder.isStaticStateHolder);
-      return js.js
+      js.Statement statement = js.js
           .statement("#.# = #;", [field.holder.name, field.name, field.code]);
+      registerEntityAst(field.element, statement,
+          library: field.element.library);
+      return statement;
     });
     return wrapPhase('staticFields', statements.toList());
   }
@@ -1574,13 +1603,17 @@
     LocalAliases locals = LocalAliases();
     for (StaticField field in fields) {
       assert(field.holder.isStaticStateHolder);
-      statements.add(js.js.statement("#(#, #, #, #);", [
+      js.Statement statement = js.js.statement("#(#, #, #, #);", [
         locals.find('_lazy', 'hunkHelpers.lazy'),
         field.holder.name,
         js.quoteName(field.name),
         js.quoteName(field.getterName),
         field.code
-      ]));
+      ]);
+
+      registerEntityAst(field.element, statement,
+          library: field.element.library);
+      statements.add(statement);
     }
 
     if (locals.isNotEmpty) {