[dartdevc] fix #36253, kernel backend now emits trackLibraries call

Also fixes DDC's Kernel backend to emit source maps. There's also
some refactoring to move shared code to the shared_compiler, to unblock
hot reload implementation work and other fixes (such as exporting
private names from the module).

Change-Id: I872e221d9f266198fcc220900146c1c9c5503acb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97553
Commit-Queue: Jenny Messerly <jmesserly@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index 47c984c..f08de41 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -44,7 +44,7 @@
 import 'extension_types.dart' show ExtensionTypeSet;
 import 'js_interop.dart';
 import 'js_typerep.dart';
-import 'module_compiler.dart' show CompilerOptions, JSModuleFile;
+import 'module_compiler.dart' show CompilerOptions;
 import 'nullable_type_inference.dart' show NullableTypeInference;
 import 'property_model.dart';
 import 'reify_coercions.dart' show CoercionReifier;
@@ -84,16 +84,6 @@
 
   JSTypeRep jsTypeRep;
 
-  /// The set of libraries we are currently compiling, and the temporaries used
-  /// to refer to them.
-  ///
-  /// We sometimes special case codegen for a single library, as it simplifies
-  /// name scoping requirements.
-  final _libraries = Map<LibraryElement, JS.Identifier>();
-
-  /// Imported libraries, and the temporaries used to refer to them.
-  final _imports = Map<LibraryElement, JS.TemporaryId>();
-
   /// The list of dart:_runtime SDK functions; these are assumed by other code
   /// in the SDK to be generated before anything else.
   final _internalSdkFunctions = <JS.ModuleItem>[];
@@ -118,9 +108,6 @@
 
   final _initializingFormalTemps = HashMap<ParameterElement, JS.TemporaryId>();
 
-  JS.Identifier _extensionSymbolsModule;
-  final _extensionSymbols = Map<String, JS.TemporaryId>();
-
   /// The  type provider from the current Analysis [context].
   final TypeProvider types;
 
@@ -286,48 +273,17 @@
 
     // Transform the AST to make coercions explicit.
     compilationUnits = CoercionReifier.reify(compilationUnits);
-    var items = <JS.ModuleItem>[];
-    var root = JS.Identifier('_root');
-    items.add(js.statement('const # = Object.create(null)', [root]));
+    var libraries = compilationUnits
+        .where((unit) {
+          var library = unit.declaredElement.library;
+          return unit.declaredElement == library.definingCompilationUnit;
+        })
+        .map((unit) => unit.declaredElement.library)
+        .toList();
 
-    var isBuildingSdk = compilationUnits
-        .any((u) => isSdkInternalRuntime(u.declaredElement.library));
-    if (isBuildingSdk) {
-      // Don't allow these to be renamed when we're building the SDK.
-      // There is JS code in dart:* that depends on their names.
-      runtimeModule = JS.Identifier('dart');
-      _extensionSymbolsModule = JS.Identifier('dartx');
-    } else {
-      // Otherwise allow these to be renamed so users can write them.
-      runtimeModule = JS.TemporaryId('dart');
-      _extensionSymbolsModule = JS.TemporaryId('dartx');
-    }
+    var items = startModule(libraries);
     _typeTable = TypeTable(runtimeModule);
 
-    // Initialize our library variables.
-    var exports = <JS.NameSpecifier>[];
-    void emitLibrary(JS.Identifier id) {
-      items.add(js.statement('const # = Object.create(#)', [id, root]));
-      exports.add(JS.NameSpecifier(id));
-    }
-
-    for (var unit in compilationUnits) {
-      var library = unit.declaredElement.library;
-      if (unit.declaredElement != library.definingCompilationUnit) continue;
-
-      var libraryTemp = isSdkInternalRuntime(library)
-          ? runtimeModule
-          : JS.TemporaryId(jsLibraryName(_libraryRoot, library));
-      _libraries[library] = libraryTemp;
-      emitLibrary(libraryTemp);
-    }
-
-    // dart:_runtime has a magic module that holds extension method symbols.
-    // TODO(jmesserly): find a cleaner design for this.
-    if (isBuildingSdk) emitLibrary(_extensionSymbolsModule);
-
-    items.add(JS.ExportDeclaration(JS.ExportClause(exports)));
-
     // Collect all class/type Element -> Node mappings
     // in case we need to forward declare any classes.
     _declarationNodes = HashMap<TypeDefiningElement, AstNode>();
@@ -363,50 +319,15 @@
     // Visit directives (for exports)
     compilationUnits.forEach(_emitExportDirectives);
 
-    // Declare imports
-    _finishImports(items);
-    // Initialize extension symbols
-    _extensionSymbols.forEach((name, id) {
-      JS.Expression value =
-          JS.PropertyAccess(_extensionSymbolsModule, _propertyName(name));
-      if (isBuildingSdk) {
-        value = js.call('# = Symbol(#)', [value, js.string("dartx.$name")]);
-      }
-      items.add(js.statement('const # = #;', [id, value]));
-    });
-
-    _emitDebuggerExtensionInfo(options.moduleName);
+    // Declare imports and extension symbols
+    emitImportsAndExtensionSymbols(items);
 
     // Discharge the type table cache variables and
     // hoisted definitions.
     items.addAll(_typeTable.discharge());
     items.addAll(_internalSdkFunctions);
 
-    // Add the module's code (produced by visiting compilation units, above)
-    _copyAndFlattenBlocks(items, moduleItems);
-
-    // Build the module.
-    return JS.Program(items, name: options.moduleName);
-  }
-
-  void _emitDebuggerExtensionInfo(String name) {
-    var properties = <JS.Property>[];
-    _libraries.forEach((library, value) {
-      // TODO(jacobr): we could specify a short library name instead of the
-      // full library uri if we wanted to save space.
-      properties.add(JS.Property(
-          js.escapedString(jsLibraryDebuggerName(_libraryRoot, library)),
-          value));
-    });
-
-    // Track the module name for each library in the module.
-    // This data is only required for debugging.
-    moduleItems.add(js
-        .statement('#.trackLibraries(#, #, ${JSModuleFile.sourceMapHoleID});', [
-      runtimeModule,
-      js.string(name),
-      JS.ObjectInitializer(properties, multiline: true)
-    ]));
+    return finishModule(items, options.moduleName);
   }
 
   /// If [e] is a property accessor element, this returns the
@@ -494,22 +415,54 @@
     return name;
   }
 
-  /// Flattens blocks in [items] to a single list.
+  /// Choose a canonical name from the [library] element.
   ///
-  /// This will not flatten blocks that are marked as being scopes.
-  void _copyAndFlattenBlocks(
-      List<JS.ModuleItem> result, Iterable<JS.ModuleItem> items) {
-    for (var item in items) {
-      if (item is JS.Block && !item.isScope) {
-        _copyAndFlattenBlocks(result, item.statements);
-      } else if (item != null) {
-        result.add(item);
-      }
+  /// This never uses the library's name (the identifier in the `library`
+  /// declaration) as it doesn't have any meaningful rules enforced.
+  @override
+  String jsLibraryName(LibraryElement library) {
+    var uri = library.source.uri;
+    if (uri.scheme == 'dart') {
+      return uri.path;
     }
+    // TODO(vsm): This is not necessarily unique if '__' appears in a file name.
+    var encodedSeparator = '__';
+    String qualifiedPath;
+    if (uri.scheme == 'package') {
+      // Strip the package name.
+      // TODO(vsm): This is not unique if an escaped '/'appears in a filename.
+      // E.g., "foo/bar.dart" and "foo$47bar.dart" would collide.
+      qualifiedPath = uri.pathSegments.skip(1).join(encodedSeparator);
+    } else {
+      qualifiedPath = path
+          .relative(uri.toFilePath(), from: _libraryRoot)
+          .replaceAll(path.separator, encodedSeparator)
+          .replaceAll('..', encodedSeparator);
+    }
+    return pathToJSIdentifier(qualifiedPath);
   }
 
-  String _libraryToModule(LibraryElement library) {
-    assert(!_libraries.containsKey(library));
+  /// Debugger friendly name for a Dart Library.
+  @override
+  String jsLibraryDebuggerName(LibraryElement library) {
+    var uri = library.source.uri;
+    // For package: and dart: uris show the entire
+    if (uri.scheme == 'dart' || uri.scheme == 'package') return uri.toString();
+
+    var filePath = uri.toFilePath();
+    // Relative path to the library.
+    return path.relative(filePath, from: _libraryRoot);
+  }
+
+  /// Returns true if the library [l] is dart:_runtime.
+  @override
+  bool isSdkInternalRuntime(LibraryElement l) {
+    var uri = l.source.uri;
+    return uri.scheme == 'dart' && uri.path == '_runtime';
+  }
+
+  @override
+  String libraryToModule(LibraryElement library) {
     var source = library.source;
     // TODO(jmesserly): we need to split out HTML.
     if (source.uri.scheme == 'dart') {
@@ -524,39 +477,6 @@
     return moduleName;
   }
 
-  void _finishImports(List<JS.ModuleItem> items) {
-    var modules = Map<String, List<LibraryElement>>();
-
-    for (var import in _imports.keys) {
-      modules.putIfAbsent(_libraryToModule(import), () => []).add(import);
-    }
-
-    String coreModuleName;
-    if (!_libraries.containsKey(coreLibrary)) {
-      coreModuleName = _libraryToModule(coreLibrary);
-    }
-    modules.forEach((module, libraries) {
-      // Generate import directives.
-      //
-      // Our import variables are temps and can get renamed. Since our renaming
-      // is integrated into js_ast, it is aware of this possibility and will
-      // generate an "as" if needed. For example:
-      //
-      //     import {foo} from 'foo';         // if no rename needed
-      //     import {foo as foo$} from 'foo'; // if rename was needed
-      //
-      var imports =
-          libraries.map((l) => JS.NameSpecifier(_imports[l])).toList();
-      if (module == coreModuleName) {
-        imports.add(JS.NameSpecifier(runtimeModule));
-        imports.add(JS.NameSpecifier(_extensionSymbolsModule));
-      }
-
-      items.add(JS.ImportDeclaration(
-          namedImports: imports, from: js.string(module, "'")));
-    });
-  }
-
   /// Called to emit class declarations.
   ///
   /// During the course of emitting one item, we may emit another. For example
@@ -1506,7 +1426,7 @@
       // Dart does not use ES6 constructors.
       // Add an error to catch any invalid usage.
       jsMethods
-          .add(JS.Method(_propertyName('constructor'), js.fun(r'''function() {
+          .add(JS.Method(propertyName('constructor'), js.fun(r'''function() {
                   throw Error("use `new " + #.typeName(#.getReifiedType(this)) +
                       ".new(...)` to create a Dart object");
               }''', [runtimeModule, runtimeModule])));
@@ -1613,7 +1533,7 @@
           if (param.kind == ParameterKind.NAMED) {
             foundNamedParams = true;
 
-            var name = _propertyName(param.name);
+            var name = propertyName(param.name);
             body.add(js.statement('if (# in #) #;', [
               name,
               namedArgumentTemp,
@@ -1743,7 +1663,7 @@
       // Sort the names to match dart2js order.
       var sortedNames = (namedParameterTypes.keys.toList())..sort();
       var named = sortedNames
-          .map((n) => JS.Property(_propertyName(n), JS.Identifier(n)));
+          .map((n) => JS.Property(propertyName(n), JS.Identifier(n)));
       addProperty('namedArguments', JS.ObjectInitializer(named.toList()));
       positionalArgs.removeLast();
     }
@@ -2085,7 +2005,7 @@
       if (extensions.isEmpty) return;
 
       var names = extensions
-          .map((e) => _propertyName(JS.memberNameForDartMember(e)))
+          .map((e) => propertyName(JS.memberNameForDartMember(e)))
           .toList();
       body.add(js.statement('#.#(#, #);', [
         runtimeModule,
@@ -2119,7 +2039,7 @@
         var proto = classElem.type.isObject
             ? js.call('Object.create(null)')
             : runtimeCall('get${name}s(#.__proto__)', [className]);
-        elements.insert(0, JS.Property(_propertyName('__proto__'), proto));
+        elements.insert(0, JS.Property(propertyName('__proto__'), proto));
       }
       body.add(runtimeStatement('set${name}Signature(#, () => #)', [
         className,
@@ -2333,7 +2253,7 @@
   JS.Expression _constructorName(String name) {
     if (name == '') {
       // Default constructors (factory or not) use `new` as their name.
-      return _propertyName('new');
+      return propertyName('new');
     }
     return _emitStaticMemberName(name);
   }
@@ -2718,7 +2638,7 @@
   JS.Method _emitTopLevelProperty(FunctionDeclaration node) {
     var name = node.name.name;
     return JS.Method(
-        _propertyName(name), _emitFunctionExpression(node.functionExpression),
+        propertyName(name), _emitFunctionExpression(node.functionExpression),
         isGetter: node.isGetter, isSetter: node.isSetter)
       ..sourceInformation = _functionEnd(node);
   }
@@ -2890,7 +2810,7 @@
       t = covariantParams.lookup(t) as TypeParameterElement;
       if (t != null) {
         body.add(runtimeStatement('checkTypeBound(#, #, #)',
-            [_emitType(t.type), _emitType(t.bound), _propertyName(t.name)]));
+            [_emitType(t.type), _emitType(t.bound), propertyName(t.name)]));
       }
     }
   }
@@ -3230,7 +3150,7 @@
       {bool cacheType = true}) {
     var properties = <JS.Property>[];
     types.forEach((name, type) {
-      var key = _propertyName(name);
+      var key = propertyName(name);
       var value = _emitType(type, cacheType: cacheType);
       properties.add(JS.Property(key, value));
     });
@@ -3421,7 +3341,7 @@
   /// function does not handle JS interop.
   JS.Expression _emitTopLevelMemberName(Element e, {String suffix = ''}) {
     var name = getJSExportName(e) ?? _getElementName(e);
-    return _propertyName(name + suffix);
+    return propertyName(name + suffix);
   }
 
   @override
@@ -3686,7 +3606,7 @@
         }
       }
       if (e.name == 'extensionSymbol' && firstArg is StringLiteral) {
-        return _getExtensionSymbolInternal(firstArg.stringValue);
+        return getExtensionSymbolInternal(firstArg.stringValue);
       }
     }
 
@@ -4125,7 +4045,7 @@
   JS.Property visitNamedExpression(NamedExpression node) {
     assert(node.parent is ArgumentList);
     return JS.Property(
-        _propertyName(node.name.label.name), _visitExpression(node.expression));
+        propertyName(node.name.label.name), _visitExpression(node.expression));
   }
 
   List<JS.Parameter> _emitParametersForElement(ExecutableElement member) {
@@ -4526,7 +4446,7 @@
         var args = ctor.positionalArguments.map(_emitDartObject).toList();
         var named = <JS.Property>[];
         ctor.namedArguments.forEach((name, value) {
-          named.add(JS.Property(_propertyName(name), _emitDartObject(value)));
+          named.add(JS.Property(propertyName(name), _emitDartObject(value)));
         });
         if (named.isNotEmpty) args.add(JS.ObjectInitializer(named));
         return _emitInstanceCreationExpression(ctor.constructor, type, args,
@@ -6113,11 +6033,11 @@
       var runtimeName = getJSExportName(element);
       if (runtimeName != null) {
         var parts = runtimeName.split('.');
-        if (parts.length < 2) return _propertyName(runtimeName);
+        if (parts.length < 2) return propertyName(runtimeName);
 
         JS.Expression result = JS.Identifier(parts[0]);
         for (int i = 1; i < parts.length; i++) {
-          result = JS.PropertyAccess(result, _propertyName(parts[i]));
+          result = JS.PropertyAccess(result, propertyName(parts[i]));
         }
         return result;
       }
@@ -6132,9 +6052,9 @@
     // actually try to access those JS members via interop.
     name = JS.memberNameForDartMember(name, _isExternal(element));
     if (useExtension) {
-      return _getExtensionSymbolInternal(name);
+      return getExtensionSymbolInternal(name);
     }
-    return _propertyName(name);
+    return propertyName(name);
   }
 
   /// Emits the name of a static member, suitable for use in a JS property
@@ -6173,20 +6093,7 @@
           name += '_';
         }
     }
-    return _propertyName(name);
-  }
-
-  /// This is an internal method used by [_emitMemberName] and the
-  /// optimized `dart:_runtime extensionSymbol` builtin to get the symbol
-  /// for `dartx.<name>`.
-  ///
-  /// Do not call this directly; you want [_emitMemberName], which knows how to
-  /// handle the many details involved in naming.
-  JS.TemporaryId _getExtensionSymbolInternal(String name) {
-    return _extensionSymbols.putIfAbsent(
-        name,
-        () => JS.TemporaryId(
-            '\$${JS.friendlyNameForDartOperator[name] ?? name}'));
+    return propertyName(name);
   }
 
   var _forwardingCache = HashMap<Element, Map<String, Element>>();
@@ -6253,14 +6160,6 @@
     return false;
   }
 
-  /// Returns the canonical name to refer to the Dart library.
-  JS.Identifier emitLibraryName(LibraryElement library) {
-    // It's either one of the libraries in this module, or it's an import.
-    return _libraries[library] ??
-        _imports.putIfAbsent(library,
-            () => JS.TemporaryId(jsLibraryName(_libraryRoot, library)));
-  }
-
   /// Return true if this is one of the methods/properties on all Dart Objects
   /// (toString, hashCode, noSuchMethod, runtimeType, ==).
   bool isObjectMember(String name) {
@@ -6668,49 +6567,6 @@
       _unreachable(node);
 }
 
-/// Choose a canonical name from the [library] element.
-///
-/// This never uses the library's name (the identifier in the `library`
-/// declaration) as it doesn't have any meaningful rules enforced.
-String jsLibraryName(String libraryRoot, LibraryElement library) {
-  var uri = library.source.uri;
-  if (uri.scheme == 'dart') {
-    return uri.path;
-  }
-  // TODO(vsm): This is not necessarily unique if '__' appears in a file name.
-  var encodedSeparator = '__';
-  String qualifiedPath;
-  if (uri.scheme == 'package') {
-    // Strip the package name.
-    // TODO(vsm): This is not unique if an escaped '/'appears in a filename.
-    // E.g., "foo/bar.dart" and "foo$47bar.dart" would collide.
-    qualifiedPath = uri.pathSegments.skip(1).join(encodedSeparator);
-  } else {
-    qualifiedPath = path
-        .relative(uri.toFilePath(), from: libraryRoot)
-        .replaceAll(path.separator, encodedSeparator)
-        .replaceAll('..', encodedSeparator);
-  }
-  return pathToJSIdentifier(qualifiedPath);
-}
-
-/// Debugger friendly name for a Dart Library.
-String jsLibraryDebuggerName(String libraryRoot, LibraryElement library) {
-  var uri = library.source.uri;
-  // For package: and dart: uris show the entire
-  if (uri.scheme == 'dart' || uri.scheme == 'package') return uri.toString();
-
-  var filePath = uri.toFilePath();
-  // Relative path to the library.
-  return path.relative(filePath, from: libraryRoot);
-}
-
-/// Shorthand for identifier-like property names.
-/// For now, we emit them as strings and the printer restores them to
-/// identifiers if it can.
-// TODO(jmesserly): avoid the round tripping through quoted form.
-JS.LiteralString _propertyName(String name) => js.string(name, "'");
-
 // TODO(jacobr): we would like to do something like the following
 // but we don't have summary support yet.
 // bool _supportJsExtensionMethod(AnnotatedNode node) =>
diff --git a/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart b/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart
index 7ad873d..3997597 100644
--- a/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart
@@ -19,6 +19,7 @@
 import '../compiler/module_builder.dart'
     show transformModuleFormat, ModuleFormat;
 import '../compiler/shared_command.dart';
+import '../compiler/shared_compiler.dart';
 import '../js_ast/js_ast.dart' as JS;
 import '../js_ast/js_ast.dart' show js;
 import '../js_ast/source_map_printer.dart' show SourceMapPrintingContext;
@@ -135,9 +136,6 @@
   /// into the output JavaScript module.
   final bool sourceMapComment;
 
-  /// Whether to emit the source mapping file inline as a data url.
-  final bool inlineSourceMap;
-
   /// The file extension for summaries.
   final String summaryExtension;
 
@@ -159,7 +157,6 @@
   CompilerOptions(
       {bool sourceMap = true,
       this.sourceMapComment = true,
-      this.inlineSourceMap = false,
       bool summarizeApi = true,
       this.summaryExtension = 'sum',
       this.unsafeForceCompile = false,
@@ -182,7 +179,6 @@
 
   CompilerOptions.fromArguments(ArgResults args)
       : sourceMapComment = args['source-map-comment'] as bool,
-        inlineSourceMap = args['inline-source-map'] as bool,
         summaryExtension = args['summary-extension'] as String,
         unsafeForceCompile = args['unsafe-force-compile'] as bool,
         summaryOutPath = args['summary-out'] as String,
@@ -203,8 +199,6 @@
               'disable if using X-SourceMap header',
           defaultsTo: true,
           hide: hide)
-      ..addFlag('inline-source-map',
-          help: 'emit source mapping inline', defaultsTo: false, hide: hide)
       ..addFlag('unsafe-force-compile',
           help: 'Compile code even if it has errors. ಠ_ಠ\n'
               'This has undefined behavior!',
@@ -242,13 +236,6 @@
   /// the libraries in this module.
   final List<int> summaryBytes;
 
-  /// Unique identifier indicating hole to inline the source map.
-  ///
-  /// We cannot generate the source map before the script it is for is
-  /// generated so we have generate the script including this id and then
-  /// replace the ID once the source map is generated.
-  static String sourceMapHoleID = 'SourceMap3G5a8h6JVhHfdGuDxZr1EF9GQC8y0e6u';
-
   JSModuleFile(this.errors, this.options, this.moduleTree, this.summaryBytes);
 
   JSModuleFile.invalid(this.errors, this.options)
@@ -303,7 +290,7 @@
     var rawSourceMap = options.inlineSourceMap
         ? js.escapedString(json.encode(builtMap), "'").value
         : 'null';
-    text = text.replaceFirst(sourceMapHoleID, rawSourceMap);
+    text = text.replaceFirst(SharedCompiler.sourceMapLocationID, rawSourceMap);
 
     return JSModuleCode(text, builtMap);
   }
diff --git a/pkg/dev_compiler/lib/src/compiler/shared_command.dart b/pkg/dev_compiler/lib/src/compiler/shared_command.dart
index 0c7c246..50f1405 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_command.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_command.dart
@@ -56,6 +56,10 @@
   /// code.
   final bool sourceMap;
 
+  /// Whether to emit the source mapping file in the program text, so the
+  /// runtime can enable synchronous stack trace deobsfuscation.
+  final bool inlineSourceMap;
+
   /// Whether to emit a summary file containing API signatures.
   ///
   /// This is required for a modular build process.
@@ -95,6 +99,7 @@
 
   SharedCompilerOptions(
       {this.sourceMap = true,
+      this.inlineSourceMap = false,
       this.summarizeApi = true,
       this.emitMetadata = false,
       this.enableAsserts = true,
@@ -109,6 +114,7 @@
       [String moduleRoot, String summaryExtension])
       : this(
             sourceMap: args['source-map'] as bool,
+            inlineSourceMap: args['inline-source-map'] as bool,
             summarizeApi: args['summarize'] as bool,
             emitMetadata: args['emit-metadata'] as bool,
             enableAsserts: args['enable-asserts'] as bool,
@@ -137,6 +143,8 @@
           help: 'emit an API summary file', defaultsTo: true, hide: hide)
       ..addFlag('source-map',
           help: 'emit source mapping', defaultsTo: true, hide: hide)
+      ..addFlag('inline-source-map',
+          help: 'emit source mapping inline', defaultsTo: false, hide: hide)
       ..addFlag('emit-metadata',
           help: 'emit metadata annotations queriable via mirrors', hide: hide)
       ..addFlag('enable-asserts',
diff --git a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
index 7b6aae5..1bfdecd 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
@@ -22,18 +22,46 @@
   /// This lets DDC use the setter method's return value directly.
   final List<JS.Identifier> _operatorSetResultStack = [];
 
+  /// Private member names in this module, organized by their library.
+  final _privateNames = HashMap<Library, HashMap<String, JS.TemporaryId>>();
+
+  /// Extension member symbols for adding Dart members to JS types.
+  ///
+  /// These are added to the [extensionSymbolsModule]; see that field for more
+  /// information.
+  final _extensionSymbols = <String, JS.TemporaryId>{};
+
+  /// The set of libraries we are currently compiling, and the temporaries used
+  /// to refer to them.
+  final _libraries = <Library, JS.Identifier>{};
+
+  /// Imported libraries, and the temporaries used to refer to them.
+  final _imports = <Library, JS.TemporaryId>{};
+
   /// The identifier used to reference DDC's core "dart:_runtime" library from
   /// generated JS code, typically called "dart" e.g. `dart.dcall`.
   @protected
   JS.Identifier runtimeModule;
 
+  /// The identifier used to reference DDC's "extension method" symbols, used to
+  /// safely add Dart-specific member names to JavaScript classes, such as
+  /// primitive types (e.g. String) or DOM types in "dart:html".
+  @protected
+  JS.Identifier extensionSymbolsModule;
+
+  /// Whether we're currently building the SDK, which may require special
+  /// bootstrapping logic.
+  ///
+  /// This is initialized by [startModule], which must be called before
+  /// accessing this field.
+  @protected
+  bool isBuildingSdk;
+
   /// The temporary variable that stores named arguments (these are passed via a
   /// JS object literal, to match JS conventions).
   @protected
   final namedArgumentTemp = JS.TemporaryId('opts');
 
-  final _privateNames = HashMap<Library, HashMap<String, JS.TemporaryId>>();
-
   /// The list of output module items, in the order they need to be emitted in.
   @protected
   final moduleItems = <JS.ModuleItem>[];
@@ -42,6 +70,7 @@
   ///
   /// This is used for deferred supertypes of mutually recursive non-generic
   /// classes.
+  @protected
   final afterClassDefItems = <JS.ModuleItem>[];
 
   /// The type used for private Dart [Symbol]s.
@@ -52,9 +81,14 @@
   @protected
   InterfaceType get internalSymbolType;
 
+  /// The current library being compiled.
   @protected
   Library get currentLibrary;
 
+  /// The library for dart:core in the SDK.
+  @protected
+  Library get coreLibrary;
+
   /// The import URI of current library.
   @protected
   Uri get currentLibraryUri;
@@ -63,6 +97,25 @@
   @protected
   FunctionNode get currentFunction;
 
+  /// Choose a canonical name from the [library] element.
+  ///
+  /// This never uses the library's name (the identifier in the `library`
+  /// declaration) as it doesn't have any meaningful rules enforced.
+  @protected
+  String jsLibraryName(Library library);
+
+  /// Debugger friendly name for a Dart [library].
+  @protected
+  String jsLibraryDebuggerName(Library library);
+
+  /// Gets the module import URI that contains [library].
+  @protected
+  String libraryToModule(Library library);
+
+  /// Returns true if the library [l] is "dart:_runtime".
+  @protected
+  bool isSdkInternalRuntime(Library l);
+
   /// Whether any superclass of [c] defines a static [name].
   @protected
   bool superclassHasStatic(Class c, String name);
@@ -289,6 +342,214 @@
   @protected
   JS.Expression canonicalizeConstObject(JS.Expression expr) =>
       cacheConst(runtimeCall('const(#)', expr));
+
+  /// Emits preamble for the module containing [libraries], and returns the
+  /// list of module items for further items to be added.
+  ///
+  /// The preamble consists of initializing the identifiers for each library,
+  /// that will be used to store their members. It also generates the
+  /// appropriate ES6 `export` declaration to export them from this module.
+  ///
+  /// After the code for all of the library members is emitted,
+  /// [emitImportsAndExtensionSymbols] should be used to emit imports/extension
+  /// symbols into the list returned by this method. Finally, [finishModule]
+  /// can be called to complete the module and return the resulting JS AST.
+  ///
+  /// This also initializes several fields: [isBuildingSdk], [runtimeModule],
+  /// [extensionSymbolsModule], as well as the [_libraries] map needed by
+  /// [emitLibraryName].
+  @protected
+  List<JS.ModuleItem> startModule(Iterable<Library> libraries) {
+    isBuildingSdk = libraries.any(isSdkInternalRuntime);
+    if (isBuildingSdk) {
+      // Don't allow these to be renamed when we're building the SDK.
+      // There is JS code in dart:* that depends on their names.
+      runtimeModule = JS.Identifier('dart');
+      extensionSymbolsModule = JS.Identifier('dartx');
+    } else {
+      // Otherwise allow these to be renamed so users can write them.
+      runtimeModule = JS.TemporaryId('dart');
+      extensionSymbolsModule = JS.TemporaryId('dartx');
+    }
+
+    // Initialize our library variables.
+    var items = <JS.ModuleItem>[];
+    var exports = <JS.NameSpecifier>[];
+
+    if (isBuildingSdk) {
+      // Bootstrap the ability to create Dart library objects.
+      var libraryProto = JS.TemporaryId('_library');
+      items.add(js.statement('const # = Object.create(null)', libraryProto));
+      items.add(js.statement(
+          'const # = Object.create(#)', [runtimeModule, libraryProto]));
+      items.add(js.statement('#.library = #', [runtimeModule, libraryProto]));
+      exports.add(JS.NameSpecifier(runtimeModule));
+    }
+
+    for (var library in libraries) {
+      if (isBuildingSdk && isSdkInternalRuntime(library)) {
+        _libraries[library] = runtimeModule;
+        continue;
+      }
+      var id = JS.TemporaryId(jsLibraryName(library));
+      _libraries[library] = id;
+
+      items.add(js.statement(
+          'const # = Object.create(#.library)', [id, runtimeModule]));
+      exports.add(JS.NameSpecifier(id));
+    }
+
+    // dart:_runtime has a magic module that holds extension method symbols.
+    // TODO(jmesserly): find a cleaner design for this.
+    if (isBuildingSdk) {
+      var id = extensionSymbolsModule;
+      items.add(js.statement(
+          'const # = Object.create(#.library)', [id, runtimeModule]));
+      exports.add(JS.NameSpecifier(id));
+    }
+
+    items.add(JS.ExportDeclaration(JS.ExportClause(exports)));
+    return items;
+  }
+
+  /// Returns the canonical name to refer to the Dart library.
+  @protected
+  JS.Identifier emitLibraryName(Library library) {
+    // It's either one of the libraries in this module, or it's an import.
+    return _libraries[library] ??
+        _imports.putIfAbsent(
+            library, () => JS.TemporaryId(jsLibraryName(library)));
+  }
+
+  /// Emits imports and extension methods into [items].
+  @protected
+  void emitImportsAndExtensionSymbols(List<JS.ModuleItem> items) {
+    var modules = Map<String, List<Library>>();
+
+    for (var import in _imports.keys) {
+      modules.putIfAbsent(libraryToModule(import), () => []).add(import);
+    }
+
+    String coreModuleName;
+    if (!_libraries.containsKey(coreLibrary)) {
+      coreModuleName = libraryToModule(coreLibrary);
+    }
+    modules.forEach((module, libraries) {
+      // Generate import directives.
+      //
+      // Our import variables are temps and can get renamed. Since our renaming
+      // is integrated into js_ast, it is aware of this possibility and will
+      // generate an "as" if needed. For example:
+      //
+      //     import {foo} from 'foo';         // if no rename needed
+      //     import {foo as foo$} from 'foo'; // if rename was needed
+      //
+      var imports =
+          libraries.map((l) => JS.NameSpecifier(_imports[l])).toList();
+      if (module == coreModuleName) {
+        imports.add(JS.NameSpecifier(runtimeModule));
+        imports.add(JS.NameSpecifier(extensionSymbolsModule));
+      }
+
+      items.add(JS.ImportDeclaration(
+          namedImports: imports, from: js.string(module, "'")));
+    });
+
+    // Initialize extension symbols
+    _extensionSymbols.forEach((name, id) {
+      JS.Expression value =
+          JS.PropertyAccess(extensionSymbolsModule, propertyName(name));
+      if (isBuildingSdk) {
+        value = js.call('# = Symbol(#)', [value, js.string("dartx.$name")]);
+      }
+      items.add(js.statement('const # = #;', [id, value]));
+    });
+  }
+
+  void _emitDebuggerExtensionInfo(String name) {
+    var properties = <JS.Property>[];
+    _libraries.forEach((library, value) {
+      // TODO(jacobr): we could specify a short library name instead of the
+      // full library uri if we wanted to save space.
+      properties.add(
+          JS.Property(js.escapedString(jsLibraryDebuggerName(library)), value));
+    });
+    var module = JS.ObjectInitializer(properties, multiline: true);
+
+    // Track the module name for each library in the module.
+    // This data is only required for debugging.
+    moduleItems.add(js.statement(
+        '#.trackLibraries(#, #, $sourceMapLocationID);',
+        [runtimeModule, js.string(name), module]));
+  }
+
+  /// Finishes the module created by [startModule], by combining the preable
+  /// [items] with the [moduleItems] that have been emitted.
+  ///
+  /// The [moduleName] should specify the module's name, and the items should
+  /// be the list resulting from startModule, with additional items added,
+  /// but not including the contents of moduleItems (which will be handled by
+  /// this method itself).
+  ///
+  /// Note, this function mutates the items list and returns it as the `body`
+  /// field of the result.
+  @protected
+  JS.Program finishModule(List<JS.ModuleItem> items, String moduleName) {
+    // TODO(jmesserly): there's probably further consolidation we can do
+    // between DDC's two backends, by moving more code into this method, as the
+    // code between `startModule` and `finishModule` is very similar in both.
+    _emitDebuggerExtensionInfo(moduleName);
+
+    // Add the module's code (produced by visiting compilation units, above)
+    _copyAndFlattenBlocks(items, moduleItems);
+    moduleItems.clear();
+
+    // Build the module.
+    return JS.Program(items, name: moduleName);
+  }
+
+  /// Flattens blocks in [items] to a single list.
+  ///
+  /// This will not flatten blocks that are marked as being scopes.
+  void _copyAndFlattenBlocks(
+      List<JS.ModuleItem> result, Iterable<JS.ModuleItem> items) {
+    for (var item in items) {
+      if (item is JS.Block && !item.isScope) {
+        _copyAndFlattenBlocks(result, item.statements);
+      } else if (item != null) {
+        result.add(item);
+      }
+    }
+  }
+
+  /// This is an internal method used by [_emitMemberName] and the
+  /// optimized `dart:_runtime extensionSymbol` builtin to get the symbol
+  /// for `dartx.<name>`.
+  ///
+  /// Do not call this directly; you want [_emitMemberName], which knows how to
+  /// handle the many details involved in naming.
+  @protected
+  JS.TemporaryId getExtensionSymbolInternal(String name) {
+    return _extensionSymbols.putIfAbsent(
+        name,
+        () => JS.TemporaryId(
+            '\$${JS.friendlyNameForDartOperator[name] ?? name}'));
+  }
+
+  /// Shorthand for identifier-like property names.
+  /// For now, we emit them as strings and the printer restores them to
+  /// identifiers if it can.
+  // TODO(jmesserly): avoid the round tripping through quoted form.
+  @protected
+  JS.LiteralString propertyName(String name) => js.string(name, "'");
+
+  /// Unique identifier indicating the location to inline the source map.
+  ///
+  /// We cannot generate the source map before the script it is for is
+  /// generated so we have generate the script including this identifier in the
+  /// JS AST, and then replace it once the source map is generated.
+  static const String sourceMapLocationID =
+      'SourceMap3G5a8h6JVhHfdGuDxZr1EF9GQC8y0e6u';
 }
 
 /// Whether a variable with [name] is referenced in the [node].
diff --git a/pkg/dev_compiler/lib/src/kernel/command.dart b/pkg/dev_compiler/lib/src/kernel/command.dart
index e7a764c..317ee17 100644
--- a/pkg/dev_compiler/lib/src/kernel/command.dart
+++ b/pkg/dev_compiler/lib/src/kernel/command.dart
@@ -9,7 +9,6 @@
 import 'package:args/args.dart';
 import 'package:build_integration/file_system/multi_root.dart';
 import 'package:cli_util/cli_util.dart' show getSdkPath;
-import 'package:dev_compiler/src/flutter/track_widget_constructor_locations.dart';
 import 'package:front_end/src/api_unstable/ddc.dart' as fe;
 import 'package:kernel/kernel.dart' hide MapEntry;
 import 'package:kernel/text/ast_to_text.dart' as kernel show Printer;
@@ -20,7 +19,10 @@
 import '../compiler/js_names.dart' as JS;
 import '../compiler/module_builder.dart';
 import '../compiler/shared_command.dart';
+import '../compiler/shared_compiler.dart';
+import '../flutter/track_widget_constructor_locations.dart';
 import '../js_ast/js_ast.dart' as JS;
+import '../js_ast/js_ast.dart' show js;
 import '../js_ast/source_map_printer.dart' show SourceMapPrintingContext;
 
 import 'analyzer_to_kernel.dart';
@@ -275,7 +277,8 @@
   // --single-out-file is used, but that option does not appear to be used by
   // any of our build systems.
   var jsCode = jsProgramToCode(jsModule, options.moduleFormats.first,
-      buildSourceMap: argResults['source-map'] as bool,
+      buildSourceMap: options.sourceMap,
+      inlineSourceMap: options.inlineSourceMap,
       jsUrl: path.toUri(output).toString(),
       mapUrl: path.toUri(output + '.map').toString(),
       bazelMapping: options.bazelMapping,
@@ -311,6 +314,7 @@
 
 JSCode jsProgramToCode(JS.Program moduleTree, ModuleFormat format,
     {bool buildSourceMap = false,
+    bool inlineSourceMap = false,
     String jsUrl,
     String mapUrl,
     Map<String, String> bazelMapping,
@@ -344,6 +348,10 @@
   }
 
   var text = printer.getText();
+  var rawSourceMap = inlineSourceMap
+      ? js.escapedString(json.encode(builtMap), "'").value
+      : 'null';
+  text = text.replaceFirst(SharedCompiler.sourceMapLocationID, rawSourceMap);
 
   return JSCode(text, builtMap);
 }
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 36e9d0c..fdd1c6f 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -40,13 +40,6 @@
         ConstantVisitor<JS.Expression> {
   final SharedCompilerOptions options;
 
-  /// The set of libraries we are currently compiling, and the temporaries used
-  /// to refer to them.
-  ///
-  /// We sometimes special case codegen for a single library, as it simplifies
-  /// name scoping requirements.
-  final _libraries = Map<Library, JS.Identifier>.identity();
-
   /// Maps a library URI import, that is not in [_libraries], to the
   /// corresponding Kernel summary module we imported it with.
   final _importToSummary = Map<Library, Component>.identity();
@@ -54,18 +47,12 @@
   /// Maps a summary to the JS import name for the module.
   final _summaryToModule = Map<Component, String>.identity();
 
-  /// Imported libraries, and the temporaries used to refer to them.
-  final _imports = Map<Library, JS.TemporaryId>();
-
   /// The variable for the current catch clause
   VariableDeclaration _rethrowParameter;
 
   /// In an async* function, this represents the stream controller parameter.
   JS.TemporaryId _asyncStarController;
 
-  JS.Identifier _extensionSymbolsModule;
-  final _extensionSymbols = Map<String, JS.TemporaryId>();
-
   Set<Class> _pendingClasses;
 
   /// Temporary variables mapped to their corresponding JavaScript variable.
@@ -238,6 +225,9 @@
   Library get currentLibrary => _currentLibrary;
 
   @override
+  Library get coreLibrary => coreTypes.coreLibrary;
+
+  @override
   FunctionNode get currentFunction => _currentFunction;
 
   @override
@@ -266,47 +256,11 @@
     }
 
     var libraries = component.libraries.where((l) => !l.isExternal);
-    var ddcRuntime =
-        libraries.firstWhere(isSdkInternalRuntime, orElse: () => null);
-    if (ddcRuntime != null) {
-      // Don't allow these to be renamed when we're building the SDK.
-      // There is JS code in dart:* that depends on their names.
-      runtimeModule = JS.Identifier('dart');
-      _extensionSymbolsModule = JS.Identifier('dartx');
-      _nullableInference.allowNotNullDeclarations = true;
-    } else {
-      // Otherwise allow these to be renamed so users can write them.
-      runtimeModule = JS.TemporaryId('dart');
-      _extensionSymbolsModule = JS.TemporaryId('dartx');
-    }
-    _typeTable = TypeTable(runtimeModule);
 
     // Initialize our library variables.
-    var items = <JS.ModuleItem>[];
-    var exports = <JS.NameSpecifier>[];
-    // TODO(jmesserly): this is a performance optimization for V8 to prevent it
-    // from treating our Dart library objects as JS Maps.
-    var root = JS.Identifier('_root');
-    items.add(js.statement('const # = Object.create(null)', [root]));
-
-    void emitLibrary(JS.Identifier id) {
-      items.add(js.statement('const # = Object.create(#)', [id, root]));
-      exports.add(JS.NameSpecifier(id));
-    }
-
-    for (var library in libraries) {
-      var libraryTemp = library == ddcRuntime
-          ? runtimeModule
-          : JS.TemporaryId(jsLibraryName(library));
-      _libraries[library] = libraryTemp;
-      emitLibrary(libraryTemp);
-    }
-
-    // dart:_runtime has a magic module that holds extension method symbols.
-    // TODO(jmesserly): find a cleaner design for this.
-    if (ddcRuntime != null) emitLibrary(_extensionSymbolsModule);
-
-    items.add(JS.ExportDeclaration(JS.ExportClause(exports)));
+    var items = startModule(libraries);
+    _nullableInference.allowNotNullDeclarations = isBuildingSdk;
+    _typeTable = TypeTable(runtimeModule);
 
     // Collect all class/type Element -> Node mappings
     // in case we need to forward declare any classes.
@@ -331,53 +285,54 @@
     // Visit directives (for exports)
     libraries.forEach(_emitExports);
 
-    // Declare imports
-    _finishImports(items);
-    // Initialize extension symbols
-    _extensionSymbols.forEach((name, id) {
-      JS.Expression value =
-          JS.PropertyAccess(_extensionSymbolsModule, _propertyName(name));
-      if (ddcRuntime != null) {
-        value = js.call('# = Symbol(#)', [value, js.string("dartx.$name")]);
-      }
-      items.add(js.statement('const # = #;', [id, value]));
-    });
+    // Declare imports and extension symbols
+    emitImportsAndExtensionSymbols(items);
 
     // Discharge the type table cache variables and
     // hoisted definitions.
     items.addAll(_typeTable.discharge());
 
-    // Add the module's code (produced by visiting compilation units, above)
-    _copyAndFlattenBlocks(items, moduleItems);
-
-    // Build the module.
-    return JS.Program(items, name: options.moduleName);
+    return finishModule(items, options.moduleName);
   }
 
-  /// Flattens blocks in [items] to a single list.
-  ///
-  /// This will not flatten blocks that are marked as being scopes.
-  void _copyAndFlattenBlocks(
-      List<JS.ModuleItem> result, Iterable<JS.ModuleItem> items) {
-    for (var item in items) {
-      if (item is JS.Block && !item.isScope) {
-        _copyAndFlattenBlocks(result, item.statements);
-      } else if (item != null) {
-        result.add(item);
-      }
+  @override
+  String jsLibraryName(Library library) {
+    var uri = library.importUri;
+    if (uri.scheme == 'dart') return uri.path;
+
+    // TODO(vsm): This is not necessarily unique if '__' appears in a file name.
+    Iterable<String> segments;
+    if (uri.scheme == 'package') {
+      // Strip the package name.
+      // TODO(vsm): This is not unique if an escaped '/'appears in a filename.
+      // E.g., "foo/bar.dart" and "foo__bar.dart" would collide.
+      segments = uri.pathSegments.skip(1);
+    } else {
+      // TODO(jmesserly): this is not unique typically.
+      segments = [uri.pathSegments.last];
     }
+
+    var qualifiedPath = segments.map((p) => p == '..' ? '' : p).join('__');
+    return pathToJSIdentifier(qualifiedPath);
   }
 
-  /// Returns the canonical name to refer to the Dart library.
-  JS.Identifier emitLibraryName(Library library) {
-    // It's either one of the libraries in this module, or it's an import.
-    return _libraries[library] ??
-        _imports.putIfAbsent(
-            library, () => JS.TemporaryId(jsLibraryName(library)));
+  @override
+  String jsLibraryDebuggerName(Library library) {
+    var uri = library.importUri;
+    // For package: and dart: uris show the entire
+    if (uri.scheme == 'dart' || uri.scheme == 'package') return uri.toString();
+    // TODO(jmesserly): this is not unique typically.
+    return uri.pathSegments.last;
   }
 
-  String _libraryToModule(Library library) {
-    assert(!_libraries.containsKey(library));
+  @override
+  bool isSdkInternalRuntime(Library l) {
+    var uri = l.importUri;
+    return uri.scheme == 'dart' && uri.path == '_runtime';
+  }
+
+  @override
+  String libraryToModule(Library library) {
     if (library.importUri.scheme == 'dart') {
       // TODO(jmesserly): we need to split out HTML.
       return JS.dartSdkModule;
@@ -391,39 +346,6 @@
     return moduleName;
   }
 
-  void _finishImports(List<JS.ModuleItem> items) {
-    var modules = Map<String, List<Library>>();
-
-    for (var import in _imports.keys) {
-      modules.putIfAbsent(_libraryToModule(import), () => []).add(import);
-    }
-
-    String coreModuleName;
-    if (!_libraries.containsKey(coreTypes.coreLibrary)) {
-      coreModuleName = _libraryToModule(coreTypes.coreLibrary);
-    }
-    modules.forEach((module, libraries) {
-      // Generate import directives.
-      //
-      // Our import variables are temps and can get renamed. Since our renaming
-      // is integrated into js_ast, it is aware of this possibility and will
-      // generate an "as" if needed. For example:
-      //
-      //     import {foo} from 'foo';         // if no rename needed
-      //     import {foo as foo$} from 'foo'; // if rename was needed
-      //
-      var imports =
-          libraries.map((l) => JS.NameSpecifier(_imports[l])).toList();
-      if (module == coreModuleName) {
-        imports.add(JS.NameSpecifier(runtimeModule));
-        imports.add(JS.NameSpecifier(_extensionSymbolsModule));
-      }
-
-      items.add(JS.ImportDeclaration(
-          namedImports: imports, from: js.string(module, "'")));
-    });
-  }
-
   void _emitLibrary(Library library) {
     // NOTE: this method isn't the right place to initialize per-library state.
     // Classes can be visited out of order, so this is only to catch things that
@@ -1203,7 +1125,7 @@
       if (extensions.isEmpty) return;
 
       var names = extensions
-          .map((e) => _propertyName(JS.memberNameForDartMember(e)))
+          .map((e) => propertyName(JS.memberNameForDartMember(e)))
           .toList();
       body.add(js.statement('#.#(#, #);', [
         runtimeModule,
@@ -1239,7 +1161,7 @@
         var proto = c == coreTypes.objectClass
             ? js.call('Object.create(null)')
             : runtimeCall('get${name}s(#.__proto__)', [className]);
-        elements.insert(0, JS.Property(_propertyName('__proto__'), proto));
+        elements.insert(0, JS.Property(propertyName('__proto__'), proto));
       }
       body.add(runtimeStatement('set${name}Signature(#, () => #)', [
         className,
@@ -1480,7 +1402,7 @@
   JS.Expression _constructorName(String name) {
     if (name == '') {
       // Default constructors (factory or not) use `new` as their name.
-      return _propertyName('new');
+      return propertyName('new');
     }
     return _emitStaticMemberName(name);
   }
@@ -1658,7 +1580,7 @@
       // Dart does not use ES6 constructors.
       // Add an error to catch any invalid usage.
       jsMethods
-          .add(JS.Method(_propertyName('constructor'), js.fun(r'''function() {
+          .add(JS.Method(propertyName('constructor'), js.fun(r'''function() {
                   throw Error("use `new " + #.typeName(#.getReifiedType(this)) +
                       ".new(...)` to create a Dart object");
               }''', [runtimeModule, runtimeModule])));
@@ -1864,7 +1786,7 @@
       if (isCovariantParameter(param) &&
           !isCovariantParameter(superMember.function.namedParameters
               .firstWhere((n) => n.name == param.name))) {
-        var name = _propertyName(param.name);
+        var name = propertyName(param.name);
         var paramType = superMethodType.namedParameters
             .firstWhere((n) => n.name == param.name);
         body.add(js.statement('if (# in #) #;', [
@@ -2205,11 +2127,11 @@
       var runtimeName = getJSExportName(member);
       if (runtimeName != null) {
         var parts = runtimeName.split('.');
-        if (parts.length < 2) return _propertyName(runtimeName);
+        if (parts.length < 2) return propertyName(runtimeName);
 
         JS.Expression result = JS.Identifier(parts[0]);
         for (int i = 1; i < parts.length; i++) {
-          result = JS.PropertyAccess(result, _propertyName(parts[i]));
+          result = JS.PropertyAccess(result, propertyName(parts[i]));
         }
         return result;
       }
@@ -2228,22 +2150,9 @@
     name = JS.memberNameForDartMember(
         name, member is Procedure && member.isExternal);
     if (useExtension) {
-      return _getExtensionSymbolInternal(name);
+      return getExtensionSymbolInternal(name);
     }
-    return _propertyName(name);
-  }
-
-  /// This is an internal method used by [_emitMemberName] and the
-  /// optimized `dart:_runtime extensionSymbol` builtin to get the symbol
-  /// for `dartx.<name>`.
-  ///
-  /// Do not call this directly; you want [_emitMemberName], which knows how to
-  /// handle the many details involved in naming.
-  JS.TemporaryId _getExtensionSymbolInternal(String name) {
-    return _extensionSymbols.putIfAbsent(
-        name,
-        () => JS.TemporaryId(
-            '\$${JS.friendlyNameForDartOperator[name] ?? name}'));
+    return propertyName(name);
   }
 
   /// Don't symbolize native members that just forward to the underlying
@@ -2322,7 +2231,7 @@
           name += '_';
         }
     }
-    return _propertyName(name);
+    return propertyName(name);
   }
 
   JS.Expression _emitJSInteropStaticMemberName(NamedNode n) {
@@ -2352,7 +2261,7 @@
   /// function does not handle JS interop.
   JS.Expression _emitTopLevelMemberName(NamedNode n, {String suffix = ''}) {
     var name = getJSExportName(n) ?? getTopLevelName(n);
-    return _propertyName(name + suffix);
+    return propertyName(name + suffix);
   }
 
   String _getJSNameWithoutGlobal(NamedNode n) {
@@ -2405,7 +2314,7 @@
 
     var name = node.name.name;
     var result = JS.Method(
-        _propertyName(name), _emitFunction(node.function, node.name.name),
+        propertyName(name), _emitFunction(node.function, node.name.name),
         isGetter: node.isGetter, isSetter: node.isSetter)
       ..sourceInformation = _nodeEnd(node.fileEndOffset);
 
@@ -2700,7 +2609,7 @@
 
   JS.ObjectInitializer _emitTypeProperties(Iterable<NamedType> types) {
     return JS.ObjectInitializer(types
-        .map((t) => JS.Property(_propertyName(t.name), _emitType(t.type)))
+        .map((t) => JS.Property(propertyName(t.name), _emitType(t.type)))
         .toList());
   }
 
@@ -3020,7 +2929,7 @@
         body.add(runtimeStatement('checkTypeBound(#, #, #)', [
           _emitType(TypeParameterType(t)),
           _emitType(t.bound),
-          _propertyName(t.name)
+          propertyName(t.name)
         ]));
       }
     }
@@ -4400,7 +4309,7 @@
         return _emitType(firstArg.type);
       }
       if (name == 'extensionSymbol' && firstArg is StringLiteral) {
-        return _getExtensionSymbolInternal(firstArg.value);
+        return getExtensionSymbolInternal(firstArg.value);
       }
     }
     if (target == coreTypes.identicalProcedure) {
@@ -4504,7 +4413,7 @@
   }
 
   JS.Property _emitNamedExpression(NamedExpression arg) {
-    return JS.Property(_propertyName(arg.name), _visitExpression(arg.value));
+    return JS.Property(propertyName(arg.name), _visitExpression(arg.value));
   }
 
   /// Emits code for the `JS(...)` macro.
@@ -5168,7 +5077,7 @@
 
     var type = visitInterfaceType(node.getType(types) as InterfaceType);
     var prototype = js.call("#.prototype", [type]);
-    var properties = [JS.Property(_propertyName("__proto__"), prototype)]
+    var properties = [JS.Property(propertyName("__proto__"), prototype)]
       ..addAll(node.fieldValues.entries.map(entryToProperty));
     return canonicalizeConstObject(
         JS.ObjectInitializer(properties, multiline: true));
@@ -5201,39 +5110,6 @@
   }
 }
 
-bool isSdkInternalRuntime(Library l) =>
-    l.importUri.toString() == 'dart:_runtime';
-
-/// Choose a canonical name from the [library] element.
-///
-/// This never uses the library's name (the identifier in the `library`
-/// declaration) as it doesn't have any meaningful rules enforced.
-String jsLibraryName(Library library) {
-  var uri = library.importUri;
-  if (uri.scheme == 'dart') return uri.path;
-
-  // TODO(vsm): This is not necessarily unique if '__' appears in a file name.
-  Iterable<String> segments;
-  if (uri.scheme == 'package') {
-    // Strip the package name.
-    // TODO(vsm): This is not unique if an escaped '/'appears in a filename.
-    // E.g., "foo/bar.dart" and "foo__bar.dart" would collide.
-    segments = uri.pathSegments.skip(1);
-  } else {
-    // TODO(jmesserly): this is not unique typically.
-    segments = [uri.pathSegments.last];
-  }
-
-  var qualifiedPath = segments.map((p) => p == '..' ? '' : p).join('__');
-  return pathToJSIdentifier(qualifiedPath);
-}
-
-/// Shorthand for identifier-like property names.
-/// For now, we emit them as strings and the printer restores them to
-/// identifiers if it can.
-// TODO(jmesserly): avoid the round tripping through quoted form.
-JS.LiteralString _propertyName(String name) => js.string(name, "'");
-
 bool _isInlineJSFunction(Statement body) {
   var block = body;
   if (block is Block) {
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart
index 7b161fb..665aae3 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/runtime.dart
@@ -147,6 +147,22 @@
 
 final JsSymbol = JS('', 'Symbol');
 
+/// The prototype used for all Dart libraries.
+///
+/// This makes it easy to identify Dart library objects, and also improves
+/// performance (JS engines such as V8 tend to assume `Object.create(null)` is
+/// used for a Map, so they don't optimize it as they normally would for
+/// class-like objects).
+///
+/// The `dart.library` field is set by the compiler during SDK bootstrapping
+/// (because it is needed for dart:_runtime itself), so we don't need to
+/// initialize it here. The name `dart.library` is used because it reads nicely,
+/// for example:
+///
+///     const my_library = Object.create(dart.library);
+///
+Object libraryPrototype = JS('', 'dart.library');
+
 // TODO(vsm): Remove once this flag we've removed the ability to
 // whitelist / fallback on the old behavior.
 bool startAsyncSynchronously = true;