Version 2.12.0-133.2.beta

* Cherry-pick refs/changes/41/176141/2 to beta
diff --git a/pkg/dev_compiler/lib/src/compiler/module_containers.dart b/pkg/dev_compiler/lib/src/compiler/module_containers.dart
deleted file mode 100644
index 6c76688..0000000
--- a/pkg/dev_compiler/lib/src/compiler/module_containers.dart
+++ /dev/null
@@ -1,259 +0,0 @@
-// Copyright (c) 2020, 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.
-
-// @dart = 2.9
-
-import '../compiler/js_names.dart' as js_ast;
-import '../js_ast/js_ast.dart' as js_ast;
-import '../js_ast/js_ast.dart' show js;
-
-/// Represents a top-level property hoisted to a top-level object.
-class ModuleItemData {
-  /// The container that holds this module item in the emitted JS.
-  js_ast.Identifier id;
-
-  /// This module item's key in the emitted JS.
-  ///
-  /// A LiteralString if this object is backed by a JS Object/Map.
-  /// A LiteralNumber if this object is backed by a JS Array.
-  js_ast.Literal jsKey;
-
-  /// This module item's value in the emitted JS.
-  js_ast.Expression jsValue;
-
-  ModuleItemData(this.id, this.jsKey, this.jsValue);
-}
-
-/// Holds variables emitted during code gen.
-///
-/// Associates a [K] with a container-unique JS key and arbitrary JS value.
-/// The container is emitted as a single object:
-/// ```
-/// var C = {
-///   jsKey: jsValue,
-///   ...
-/// };
-/// ```
-class ModuleItemContainer<K> {
-  /// Name of the container in the emitted JS.
-  String name;
-
-  /// If null, this container will be automatically renamed based on [name].
-  js_ast.Identifier containerId;
-
-  final Map<K, ModuleItemData> moduleItems = {};
-
-  /// Holds keys that will not be emitted when calling [emit].
-  final Set<K> _noEmit = {};
-
-  ModuleItemContainer._(this.name, this.containerId);
-
-  /// Creates an automatically sharding container backed by JS Objects.
-  factory ModuleItemContainer.asObject(String name,
-      {String Function(K) keyToString}) {
-    return ModuleItemObjectContainer<K>(name, keyToString);
-  }
-
-  /// Creates a container backed by a JS Array.
-  factory ModuleItemContainer.asArray(String name) {
-    return ModuleItemArrayContainer<K>(name);
-  }
-
-  bool get isNotEmpty => moduleItems.isNotEmpty;
-
-  Iterable<K> get keys => moduleItems.keys;
-
-  int get length => moduleItems.keys.length;
-
-  js_ast.Expression operator [](K key) => moduleItems[key]?.jsValue;
-
-  void operator []=(K key, js_ast.Expression value) {
-    if (moduleItems.containsKey(key)) {
-      moduleItems[key].jsValue = value;
-      return;
-    }
-    var fieldString = '$key';
-    // Avoid shadowing common JS properties.
-    if (js_ast.objectProperties.contains(fieldString)) {
-      fieldString += '\$';
-    }
-    moduleItems[key] = ModuleItemData(
-        containerId, js_ast.LiteralString("'$fieldString'"), value);
-  }
-
-  /// Returns the expression that retrieves [key]'s corresponding JS value via
-  /// a property access through its container.
-  js_ast.Expression access(K key) {
-    return js.call('#.#', [containerId, moduleItems[key].jsKey]);
-  }
-
-  /// Emit the container declaration/initializer.
-  ///
-  /// May be multiple statements if the container is automatically sharded.
-  List<js_ast.Statement> emit() {
-    var properties = <js_ast.Property>[];
-    moduleItems.forEach((k, v) {
-      if (!_noEmit.contains(k)) return;
-      properties.add(js_ast.Property(v.jsKey, v.jsValue));
-    });
-    var containerObject =
-        js_ast.ObjectInitializer(properties, multiline: properties.length > 1);
-    return [
-      js.statement('var # = Object.create(#)', [containerId, containerObject])
-    ];
-  }
-
-  bool contains(K key) => moduleItems.containsKey(key);
-
-  bool canEmit(K key) => !_noEmit.contains(key);
-
-  /// Indicates that [K] should be treated as if it weren't hoisted.
-  ///
-  /// Used when we are managing the variable declarations manually (such as
-  /// unhoisting specific symbols for performance reasons).
-  void setNoEmit(K key) {
-    _noEmit.add(key);
-  }
-}
-
-/// Associates a [K] with a container-unique JS key and arbitrary JS value.
-///
-/// Emitted as a series of JS Objects, splitting them into groups of 500 for
-/// JS optimization purposes:
-/// ```
-/// var C = {
-///   jsKey: jsValue,
-///   ...
-/// };
-/// var C$1 = { ... };
-/// ```
-class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
-  /// Holds the TemporaryId for the current container shard.
-  js_ast.Identifier _currentContainerId;
-
-  /// Tracks how often JS emitted field names appear.
-  ///
-  /// [keyToString] may resolve multiple unique keys to the same JS string.
-  /// When this occurs, the resolved JS string will automatically be renamed.
-  final Map<String, int> _nameFrequencies = {};
-
-  /// Transforms a [K] into a valid name for a JS object property key.
-  ///
-  /// Non-unique generated strings are automatically renamed.
-  String Function(K) keyToString;
-
-  ModuleItemObjectContainer(String name, this.keyToString)
-      : super._(name, null);
-
-  @override
-  void operator []=(K key, js_ast.Expression value) {
-    if (this.contains(key)) {
-      moduleItems[key].jsValue = value;
-      return;
-    }
-    if (length % 500 == 0) _currentContainerId = js_ast.TemporaryId(name);
-    // Create a unique name for K when emitted as a JS field.
-    var fieldString = keyToString(key);
-    _nameFrequencies.update(fieldString, (v) {
-      fieldString += '\$${v + 1}';
-      return v + 1;
-    }, ifAbsent: () {
-      // Avoid shadowing common JS properties.
-      if (js_ast.objectProperties.contains(fieldString)) {
-        fieldString += '\$';
-      }
-      return 0;
-    });
-    moduleItems[key] = ModuleItemData(
-        _currentContainerId, js_ast.LiteralString("'$fieldString'"), value);
-  }
-
-  @override
-  js_ast.Expression access(K key) {
-    return js.call('#.#', [moduleItems[key].id, moduleItems[key].jsKey]);
-  }
-
-  @override
-  List<js_ast.Statement> emit() {
-    var containersToProperties = <js_ast.Identifier, List<js_ast.Property>>{};
-    moduleItems.forEach((k, v) {
-      if (_noEmit.contains(k)) return;
-      if (!containersToProperties.containsKey(v.id)) {
-        containersToProperties[v.id] = <js_ast.Property>[];
-      }
-      containersToProperties[v.id].add(js_ast.Property(v.jsKey, v.jsValue));
-    });
-
-    var statements = <js_ast.Statement>[];
-    containersToProperties.forEach((containerId, properties) {
-      var containerObject = js_ast.ObjectInitializer(properties,
-          multiline: properties.length > 1);
-      statements.add(js.statement(
-          'var # = Object.create(#)', [containerId, containerObject]));
-    });
-    return statements;
-  }
-}
-
-/// Associates a unique [K] with an arbitrary JS value.
-///
-/// Emitted as a JS Array:
-/// ```
-/// var C = [
-///   jsValue,
-///   ...
-/// ];
-/// ```
-class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
-  ModuleItemArrayContainer(String name)
-      : super._(name, js_ast.TemporaryId(name));
-
-  @override
-  void operator []=(K key, js_ast.Expression value) {
-    if (moduleItems.containsKey(key)) {
-      moduleItems[key].jsValue = value;
-      return;
-    }
-    moduleItems[key] =
-        ModuleItemData(containerId, js_ast.LiteralNumber('$length'), value);
-  }
-
-  @override
-  js_ast.Expression access(K key) {
-    return js.call('#[#]', [containerId, moduleItems[key].jsKey]);
-  }
-
-  @override
-  List<js_ast.Statement> emit() {
-    if (moduleItems.isEmpty) return [];
-    var properties = List<js_ast.Expression>.filled(length, null);
-
-    // If the entire array holds just one value, generate a short initializer.
-    var valueSet = <js_ast.Expression>{};
-    moduleItems.forEach((k, v) {
-      if (_noEmit.contains(k)) return;
-      valueSet.add(v.jsValue);
-      properties[int.parse((v.jsKey as js_ast.LiteralNumber).value)] =
-          v.jsValue;
-    });
-
-    if (valueSet.length == 1 && moduleItems.length > 1) {
-      return [
-        js.statement('var # = Array(#).fill(#)', [
-          containerId,
-          js_ast.LiteralNumber('${properties.length}'),
-          valueSet.first
-        ])
-      ];
-    }
-    // Array containers are not sharded, as we do not expect to hit V8's
-    // dictionary-mode limit of 99999 elements.
-    return [
-      js.statement('var # = #', [
-        containerId,
-        js_ast.ArrayInitializer(properties, multiline: properties.length > 1)
-      ])
-    ];
-  }
-}
diff --git a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
index 2fcad3e..e9527d9 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
@@ -8,7 +8,6 @@
 import 'package:meta/meta.dart';
 
 import '../compiler/js_names.dart' as js_ast;
-import '../compiler/module_containers.dart' show ModuleItemContainer;
 import '../js_ast/js_ast.dart' as js_ast;
 import '../js_ast/js_ast.dart' show js;
 
@@ -26,10 +25,6 @@
   /// Private member names in this module, organized by their library.
   final _privateNames = HashMap<Library, HashMap<String, js_ast.TemporaryId>>();
 
-  /// Holds all top-level JS symbols (used for caching or indexing fields).
-  final _symbolContainer = ModuleItemContainer<js_ast.Identifier>.asObject('S',
-      keyToString: (js_ast.Identifier i) => '${i.name}');
-
   /// Extension member symbols for adding Dart members to JS types.
   ///
   /// These are added to the [extensionSymbolsModule]; see that field for more
@@ -56,18 +51,11 @@
   /// Whether we're currently building the SDK, which may require special
   /// bootstrapping logic.
   ///
-  /// This is initialized by [emitModule], which must be called before
+  /// This is initialized by [startModule], which must be called before
   /// accessing this field.
   @protected
   bool isBuildingSdk;
 
-  /// Whether or not to move top level symbols into top-level containers.
-  ///
-  /// This is set in both [emitModule] and [emitLibrary].
-  /// Depends on [isBuildingSdk].
-  @protected
-  bool containerizeSymbols;
-
   /// The temporary variable that stores named arguments (these are passed via a
   /// JS object literal, to match JS conventions).
   @protected
@@ -262,16 +250,10 @@
       var idName = name.endsWith('=') ? name.replaceAll('=', '_') : name;
       idName = idName.replaceAll(js_ast.invalidCharInIdentifier, '_');
       id ??= js_ast.TemporaryId(idName);
-      addSymbol(
-          id,
-          js.call('#.privateName(#, #)',
-              [runtimeModule, emitLibraryName(library), js.string(name)]));
-      if (!containerizeSymbols) {
-        // TODO(vsm): Change back to `const`.
-        // See https://github.com/dart-lang/sdk/issues/40380.
-        moduleItems.add(js.statement('var # = #.privateName(#, #)',
-            [id, runtimeModule, emitLibraryName(library), js.string(name)]));
-      }
+      // TODO(vsm): Change back to `const`.
+      // See https://github.com/dart-lang/sdk/issues/40380.
+      moduleItems.add(js.statement('var # = #.privateName(#, #)',
+          [id, runtimeModule, emitLibraryName(library), js.string(name)]));
       return id;
     }
 
@@ -356,13 +338,9 @@
     var name = js.escapedString(symbolName, "'");
     js_ast.Expression result;
     if (last.startsWith('_')) {
-      var nativeSymbolAccessor =
-          getSymbol(emitPrivateNameSymbol(currentLibrary, last));
-      result = js.call('new #.new(#, #)', [
-        emitConstructorAccess(privateSymbolType),
-        name,
-        nativeSymbolAccessor
-      ]);
+      var nativeSymbol = emitPrivateNameSymbol(currentLibrary, last);
+      result = js.call('new #.new(#, #)',
+          [emitConstructorAccess(privateSymbolType), name, nativeSymbol]);
     } else {
       result = js.call(
           'new #.new(#)', [emitConstructorAccess(internalSymbolType), name]);
@@ -388,11 +366,12 @@
   /// 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: [runtimeModule],
-  /// [extensionSymbolsModule], and the [_libraries] map needed by
+  /// This also initializes several fields: [isBuildingSdk], [runtimeModule],
+  /// [extensionSymbolsModule], as well as the [_libraries] map needed by
   /// [emitLibraryName].
   @protected
   List<js_ast.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.
@@ -526,13 +505,9 @@
       if (isBuildingSdk) {
         value = js.call('# = Symbol(#)', [value, js.string('dartx.$name')]);
       }
-      if (!_symbolContainer.canEmit(id)) {
-        // Extension symbols marked with noEmit are managed manually.
-        // TODO(vsm): Change back to `const`.
-        // See https://github.com/dart-lang/sdk/issues/40380.
-        items.add(js.statement('var # = #;', [id, value]));
-      }
-      _symbolContainer[id] = value;
+      // TODO(vsm): Change back to `const`.
+      // See https://github.com/dart-lang/sdk/issues/40380.
+      items.add(js.statement('var # = #;', [id, value]));
     });
   }
 
@@ -559,28 +534,6 @@
         [runtimeModule, js.string(name), module, partMap]));
   }
 
-  /// Returns an accessor for [id] via the symbol container.
-  /// E.g., transforms $sym to S$5.$sym.
-  ///
-  /// A symbol lookup on an id marked no emit omits the symbol accessor.
-  js_ast.Expression getSymbol(js_ast.Identifier id) {
-    return _symbolContainer.canEmit(id) ? _symbolContainer.access(id) : id;
-  }
-
-  /// Returns the raw JS value associated with [id].
-  js_ast.Expression getSymbolValue(js_ast.Identifier id) {
-    return _symbolContainer[id];
-  }
-
-  /// Inserts a symbol into the symbol table.
-  js_ast.Expression addSymbol(js_ast.Identifier id, js_ast.Expression symbol) {
-    _symbolContainer[id] = symbol;
-    if (!containerizeSymbols) {
-      _symbolContainer.setNoEmit(id);
-    }
-    return _symbolContainer[id];
-  }
-
   /// Finishes the module created by [startModule], by combining the preable
   /// [items] with the [moduleItems] that have been emitted.
   ///
@@ -599,9 +552,6 @@
     // code between `startModule` and `finishModule` is very similar in both.
     _emitDebuggerExtensionInfo(moduleName);
 
-    // Emit all top-level JS symbol containers.
-    items.addAll(_symbolContainer.emit());
-
     // Add the module's code (produced by visiting compilation units, above)
     _copyAndFlattenBlocks(items, moduleItems);
     moduleItems.clear();
@@ -632,13 +582,10 @@
   /// handle the many details involved in naming.
   @protected
   js_ast.TemporaryId getExtensionSymbolInternal(String name) {
-    if (!_extensionSymbols.containsKey(name)) {
-      var id = js_ast.TemporaryId(
-          '\$${js_ast.friendlyNameForDartOperator[name] ?? name}');
-      _extensionSymbols[name] = id;
-      addSymbol(id, id);
-    }
-    return _extensionSymbols[name];
+    return _extensionSymbols.putIfAbsent(
+        name,
+        () => js_ast.TemporaryId(
+            '\$${js_ast.friendlyNameForDartOperator[name] ?? name}'));
   }
 
   /// Shorthand for identifier-like property names.
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 8800d4c..db85b79 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -22,7 +22,6 @@
 import '../compiler/js_utils.dart' as js_ast;
 import '../compiler/module_builder.dart'
     show isSdkInternalRuntimeUri, libraryUriToJsIdentifier, pathToJSIdentifier;
-import '../compiler/module_containers.dart' show ModuleItemContainer;
 import '../compiler/shared_command.dart' show SharedCompilerOptions;
 import '../compiler/shared_compiler.dart';
 import '../js_ast/js_ast.dart' as js_ast;
@@ -70,14 +69,11 @@
   /// Let variables collected for the given function.
   List<js_ast.TemporaryId> _letVariables;
 
-  final _constTable = js_ast.TemporaryId('CT');
+  final _constTable = _emitTemporaryId('CT');
 
-  /// Constant getters used to populate the constant table.
+  // Constant getters used to populate the constant table.
   final _constLazyAccessors = <js_ast.Method>[];
 
-  /// Container for holding the results of lazily-evaluated constants.
-  final _constTableCache = ModuleItemContainer<String>.asArray('C');
-
   /// Tracks the index in [moduleItems] where the const table must be inserted.
   /// Required for SDK builds due to internal circular dependencies.
   /// E.g., dart.constList depends on JSArray.
@@ -220,7 +216,7 @@
   final constAliasCache = HashMap<Constant, js_ast.Expression>();
 
   /// Maps uri strings in asserts and elsewhere to hoisted identifiers.
-  final _uriContainer = ModuleItemContainer<String>.asArray('I');
+  final _uriMap = HashMap<String, js_ast.Identifier>();
 
   final Class _jsArrayClass;
   final Class _privateSymbolClass;
@@ -327,32 +323,7 @@
 
     var libraries = component.libraries;
 
-    // Initialize library variables.
-    isBuildingSdk = libraries.any(isSdkInternalRuntime);
-
-    // For runtime performance reasons, we only containerize SDK symbols in web
-    // libraries. Otherwise, we use a 600-member cutoff before a module is
-    // containerized. This is somewhat arbitrary but works promisingly for the
-    // SDK and Flutter Web.
-    if (!isBuildingSdk) {
-      // The number of DDC top-level symbols scales with the number of
-      // non-static class members across an entire module.
-      var uniqueNames = HashSet<String>();
-      libraries.forEach((Library l) {
-        l.classes.forEach((Class c) {
-          c.members.forEach((m) {
-            var isStatic =
-                m is Field ? m.isStatic : (m is Procedure ? m.isStatic : false);
-            if (isStatic) return;
-            var name = js_ast.toJSIdentifier(
-                m.name.text.replaceAll(js_ast.invalidCharInIdentifier, '_'));
-            uniqueNames.add(name);
-          });
-        });
-      });
-      containerizeSymbols = uniqueNames.length > 600;
-    }
-
+    // Initialize our library variables.
     var items = startModule(libraries);
     _nullableInference.allowNotNullDeclarations = isBuildingSdk;
     _typeTable = TypeTable(runtimeModule);
@@ -364,12 +335,10 @@
       _pendingClasses.addAll(l.classes);
     }
 
-    // Record a safe index after the declaration of type generators and
-    // top-level symbols but before the declaration of any functions.
-    // Various preliminary data structures must be inserted here prior before
-    // referenced by the rest of the module.
-    var safeDeclarationIndex = moduleItems.length;
-    _constTableInsertionIndex = safeDeclarationIndex;
+    // TODO(markzipan): Don't emit this when compiling the SDK.
+    moduleItems
+        .add(js.statement('const # = Object.create(null);', [_constTable]));
+    _constTableInsertionIndex = moduleItems.length;
 
     // Add implicit dart:core dependency so it is first.
     emitLibraryName(_coreTypes.coreLibrary);
@@ -381,25 +350,22 @@
     // This is done by forward declaring items.
     libraries.forEach(_emitLibrary);
 
-    // Emit hoisted assert strings
-    moduleItems.insertAll(safeDeclarationIndex, _uriContainer.emit());
-
-    if (_constTableCache.isNotEmpty) {
-      moduleItems.insertAll(safeDeclarationIndex, _constTableCache.emit());
-    }
-
     // This can cause problems if it's ever true during the SDK build, as it's
     // emitted before dart.defineLazy.
     if (_constLazyAccessors.isNotEmpty) {
-      var constTableDeclaration =
-          js.statement('const # = Object.create(null);', [_constTable]);
       var constTableBody = runtimeStatement(
           'defineLazy(#, { # }, false)', [_constTable, _constLazyAccessors]);
-      moduleItems.insertAll(
-          _constTableInsertionIndex, [constTableDeclaration, constTableBody]);
+      moduleItems.insert(_constTableInsertionIndex, constTableBody);
       _constLazyAccessors.clear();
     }
 
+    // Add assert locations
+    _uriMap.forEach((location, id) {
+      var value = location == null ? 'null' : js.escapedString(location);
+      moduleItems.insert(
+          _constTableInsertionIndex, js.statement('var # = #;', [id, value]));
+    });
+
     moduleItems.addAll(afterClassDefItems);
     afterClassDefItems.clear();
 
@@ -409,8 +375,9 @@
     // Declare imports and extension symbols
     emitImportsAndExtensionSymbols(items);
 
-    // Emit the hoisted type table cache variables
-    items.addAll(_typeTable.dischargeBoundTypes());
+    // Discharge the type table cache variables and
+    // hoisted definitions.
+    items.addAll(_typeTable.discharge());
 
     return finishModule(items, _options.moduleName);
   }
@@ -475,10 +442,6 @@
     _currentLibrary = library;
     _staticTypeContext.enterLibrary(_currentLibrary);
 
-    if (isBuildingSdk) {
-      containerizeSymbols = _isWebLibrary(library.importUri);
-    }
-
     if (isSdkInternalRuntime(library)) {
       // `dart:_runtime` uses a different order for bootstrapping.
       //
@@ -603,29 +566,10 @@
     _classProperties =
         ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, c);
 
-    var body = <js_ast.Statement>[];
-
-    // ClassPropertyModel.build introduces symbols for virtual field accessors.
-    _classProperties.virtualFields.forEach((field, virtualField) {
-      // TODO(vsm): Clean up this logic.
-      //
-      // Typically, [emitClassPrivateNameSymbol] creates a new symbol.  If it
-      // is called multiple times, that symbol is cached.  If the former,
-      // assign directly to [virtualField].  If the latter, copy the old
-      // variable to [virtualField].
-      var symbol = emitClassPrivateNameSymbol(c.enclosingLibrary,
-          getLocalClassName(c), field.name.text, virtualField);
-      if (symbol != virtualField) {
-        addSymbol(virtualField, getSymbolValue(symbol));
-        if (!containerizeSymbols) {
-          body.add(js.statement('const # = #;', [virtualField, symbol]));
-        }
-      }
-    });
-
     var jsCtors = _defineConstructors(c, className);
     var jsMethods = _emitClassMethods(c);
 
+    var body = <js_ast.Statement>[];
     _emitSuperHelperSymbols(body);
     // Deferred supertypes must be evaluated lazily while emitting classes to
     // prevent evaluating a JS expression for a deferred type from influencing
@@ -649,6 +593,7 @@
     // Attach caches on all canonicalized types.
     body.add(runtimeStatement('addTypeCaches(#)', [className]));
 
+    _emitVirtualFieldSymbols(c, body);
     _emitClassSignature(c, className, body);
     _initExtensionSymbols(c);
     if (!c.isMixinDeclaration) {
@@ -699,7 +644,7 @@
 
     var typeConstructor = js.call('(#) => { #; #; return #; }', [
       jsFormals,
-      _typeTable.dischargeFreeTypes(formals),
+      _typeTable.discharge(formals),
       body,
       className ?? _emitIdentifier(name)
     ]);
@@ -1580,13 +1525,7 @@
 
     var body = <js_ast.Statement>[];
     void emitFieldInit(Field f, Expression initializer, TreeNode hoverInfo) {
-      var virtualField = _classProperties.virtualFields[f];
-
-      // Avoid calling getSymbol on _declareMemberName since _declareMemberName
-      // calls _emitMemberName downstream, which already invokes getSymbol.
-      var access = virtualField == null
-          ? _declareMemberName(f)
-          : getSymbol(virtualField);
+      var access = _classProperties.virtualFields[f] ?? _declareMemberName(f);
       var jsInit = _visitInitializer(initializer, f.annotations);
       body.add(jsInit
           .toAssignExpression(js.call('this.#', [access])
@@ -1973,16 +1912,14 @@
   /// wrong behavior if a new field was declared.
   List<js_ast.Method> _emitVirtualFieldAccessor(Field field) {
     var virtualField = _classProperties.virtualFields[field];
-    var virtualFieldSymbol = getSymbol(virtualField);
     var name = _declareMemberName(field);
 
-    var getter = js.fun('function() { return this[#]; }', [virtualFieldSymbol]);
+    var getter = js.fun('function() { return this[#]; }', [virtualField]);
     var jsGetter = js_ast.Method(name, getter, isGetter: true)
       ..sourceInformation = _nodeStart(field);
 
-    var args = field.isFinal
-        ? [js_ast.Super(), name]
-        : [js_ast.This(), virtualFieldSymbol];
+    var args =
+        field.isFinal ? [js_ast.Super(), name] : [js_ast.This(), virtualField];
 
     js_ast.Expression value = _emitIdentifier('value');
     if (!field.isFinal && isCovariantField(field)) {
@@ -2298,13 +2235,13 @@
       var memberLibrary = member?.name?.library ??
           memberClass?.enclosingLibrary ??
           _currentLibrary;
-      return getSymbol(emitPrivateNameSymbol(memberLibrary, name));
+      return emitPrivateNameSymbol(memberLibrary, name);
     }
 
     useExtension ??= _isSymbolizedMember(memberClass, name);
     name = js_ast.memberNameForDartMember(name, _isExternal(member));
     if (useExtension) {
-      return getSymbol(getExtensionSymbolInternal(name));
+      return getExtensionSymbolInternal(name);
     }
     return propertyName(name);
   }
@@ -2921,7 +2858,7 @@
 
       js_ast.Expression addTypeFormalsAsParameters(
           List<js_ast.Expression> elements) {
-        var names = _typeTable.dischargeFreeTypes(typeFormals);
+        var names = _typeTable.discharge(typeFormals);
         return names.isEmpty
             ? js.call('(#) => [#]', [tf, elements])
             : js.call('(#) => {#; return [#];}', [tf, names, elements]);
@@ -3069,8 +3006,9 @@
     // Issue: https://github.com/dart-lang/sdk/issues/43288
     var fun = _emitFunction(functionNode, name);
 
-    var types = _typeTable?.dischargeFreeTypes();
+    var types = _typeTable.discharge();
     var constants = _dischargeConstTable();
+
     var body = js_ast.Block([...?types, ...?constants, ...fun.body.statements]);
     return js_ast.Fun(fun.params, body);
   }
@@ -3164,6 +3102,22 @@
     return result;
   }
 
+  void _emitVirtualFieldSymbols(Class c, List<js_ast.Statement> body) {
+    _classProperties.virtualFields.forEach((field, virtualField) {
+      // TODO(vsm): Clean up this logic.  See comments on the following method.
+      //
+      // Typically, [emitClassPrivateNameSymbol] creates a new symbol.  If it
+      // is called multiple times, that symbol is cached.  If the former,
+      // assign directly to [virtualField].  If the latter, copy the old
+      // variable to [virtualField].
+      var symbol = emitClassPrivateNameSymbol(c.enclosingLibrary,
+          getLocalClassName(c), field.name.text, virtualField);
+      if (symbol != virtualField) {
+        body.add(js.statement('const # = #;', [virtualField, symbol]));
+      }
+    });
+  }
+
   List<js_ast.Identifier> _emitTypeFormals(List<TypeParameter> typeFormals) {
     return typeFormals
         .map((t) => _emitIdentifier(getTypeParameterName(t)))
@@ -3690,11 +3644,14 @@
 
   // Replace a string `uri` literal with a cached top-level variable containing
   // the value to reduce overall code size.
-  js_ast.Expression _cacheUri(String uri) {
-    if (!_uriContainer.contains(uri)) {
-      _uriContainer[uri] = js_ast.LiteralString('"$uri"');
+  js_ast.Identifier _cacheUri(String uri) {
+    var id = _uriMap[uri];
+    if (id == null) {
+      var name = 'L${_uriMap.length}';
+      id = js_ast.TemporaryId(name);
+      _uriMap[uri] = id;
     }
-    return _uriContainer.access(uri);
+    return id;
   }
 
   @override
@@ -5047,7 +5004,7 @@
           return _emitType(firstArg.type);
         }
         if (name == 'extensionSymbol' && firstArg is StringLiteral) {
-          return getSymbol(getExtensionSymbolInternal(firstArg.value));
+          return getExtensionSymbolInternal(firstArg.value);
         }
 
         if (name == 'compileTimeFlag' && firstArg is StringLiteral) {
@@ -5923,19 +5880,19 @@
     }
     var constAliasString = 'C${constAliasCache.length}';
     var constAliasProperty = propertyName(constAliasString);
-
-    _constTableCache[constAliasString] = js.call('void 0');
-    var constAliasAccessor = _constTableCache.access(constAliasString);
-
-    var constAccessor = js.call(
-        '# || #.#', [constAliasAccessor, _constTable, constAliasProperty]);
+    var constAliasId = _emitTemporaryId(constAliasString);
+    var constAccessor =
+        js.call('# || #.#', [constAliasId, _constTable, constAliasProperty]);
     constAliasCache[node] = constAccessor;
     var constJs = super.visitConstant(node);
+    // TODO(vsm): Change back to `let`.
+    // See https://github.com/dart-lang/sdk/issues/40380.
+    moduleItems.add(js.statement('var #;', [constAliasId]));
 
     var func = js_ast.Fun(
         [],
         js_ast.Block([
-          js.statement('return # = #;', [constAliasAccessor, constJs])
+          js.statement('return # = #;', [constAliasId, constJs])
         ]));
     var accessor = js_ast.Method(constAliasProperty, func, isGetter: true);
     _constLazyAccessors.add(accessor);
@@ -6021,8 +5978,8 @@
       // was overridden.
       var symbol = cls.isEnum
           ? _emitMemberName(member.name.text, member: member)
-          : getSymbol(emitClassPrivateNameSymbol(
-              cls.enclosingLibrary, getLocalClassName(cls), member.name.text));
+          : emitClassPrivateNameSymbol(
+              cls.enclosingLibrary, getLocalClassName(cls), member.name.text);
       return js_ast.Property(symbol, constant);
     }
 
diff --git a/pkg/dev_compiler/lib/src/kernel/type_table.dart b/pkg/dev_compiler/lib/src/kernel/type_table.dart
index b9b73fd..ddaf27f 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_table.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_table.dart
@@ -4,17 +4,13 @@
 
 // @dart = 2.9
 
-import 'dart:collection';
-
 import 'package:kernel/kernel.dart';
 
 import '../compiler/js_names.dart' as js_ast;
-import '../compiler/module_containers.dart' show ModuleItemContainer;
 import '../js_ast/js_ast.dart' as js_ast;
 import '../js_ast/js_ast.dart' show js;
 import 'kernel_helpers.dart';
 
-/// Returns all non-locally defined type parameters referred to by [t].
 Set<TypeParameter> freeTypeParameters(DartType t) {
   assert(isKnownDartTypeImplementor(t));
   var result = <TypeParameter>{};
@@ -40,144 +36,160 @@
   return result;
 }
 
-/// A name for a type made of JS identifier safe characters.
-///
-/// 'L' and 'N' are prepended to a type name to represent a legacy or nullable
-/// flavor of a type.
-String _typeString(DartType type, {bool flat = false}) {
-  var nullability = type.declaredNullability == Nullability.legacy
-      ? 'L'
-      : type.declaredNullability == Nullability.nullable
-          ? 'N'
-          : '';
-  assert(isKnownDartTypeImplementor(type));
-  if (type is InterfaceType) {
-    var name = '${type.classNode.name}$nullability';
-    var typeArgs = type.typeArguments;
-    if (typeArgs == null) return name;
-    if (typeArgs.every((p) => p == const DynamicType())) return name;
-    return "${name}Of${typeArgs.map(_typeString).join("\$")}";
-  }
-  if (type is FutureOrType) {
-    var name = 'FutureOr$nullability';
-    if (type.typeArgument == const DynamicType()) return name;
-    return '${name}Of${_typeString(type.typeArgument)}';
-  }
-  if (type is TypedefType) {
-    var name = '${type.typedefNode.name}$nullability';
-    var typeArgs = type.typeArguments;
-    if (typeArgs == null) return name;
-    if (typeArgs.every((p) => p == const DynamicType())) return name;
-    return "${name}Of${typeArgs.map(_typeString).join("\$")}";
-  }
-  if (type is FunctionType) {
-    if (flat) return 'Fn';
-    var rType = _typeString(type.returnType, flat: true);
-    var params = type.positionalParameters
-        .take(3)
-        .map((p) => _typeString(p, flat: true));
-    var paramList = params.join('And');
-    var count = type.positionalParameters.length;
-    if (count > 3 || type.namedParameters.isNotEmpty) {
-      paramList = '${paramList}__';
-    } else if (count == 0) {
-      paramList = 'Void';
+/// _CacheTable tracks cache variables for variables that
+/// are emitted in place with a hoisted variable for a cache.
+class _CacheTable {
+  /// Mapping from types to their canonical names.
+  // Use a LinkedHashMap to maintain key insertion order so the generated code
+  // is stable under slight perturbation.  (If this is not good enough we could
+  // sort by name to canonicalize order.)
+  final _names = <DartType, js_ast.TemporaryId>{};
+  Iterable<DartType> get keys => _names.keys.toList();
+
+  js_ast.Statement _dischargeType(DartType type) {
+    var name = _names.remove(type);
+    if (name != null) {
+      return js.statement('let #;', [name]);
     }
-    return '${paramList}To$nullability$rType';
+    return null;
   }
-  if (type is TypeParameterType) return '${type.parameter.name}$nullability';
-  if (type is DynamicType) return 'dynamic';
-  if (type is VoidType) return 'void';
-  if (type is NeverType) return 'Never$nullability';
-  if (type is BottomType) return 'bottom';
-  if (type is NullType) return 'Null';
-  return 'invalid';
+
+  /// Emit a list of statements declaring the cache variables for
+  /// types tracked by this table.  If [typeFilter] is given,
+  /// only emit the types listed in the filter.
+  List<js_ast.Statement> discharge([Iterable<DartType> typeFilter]) {
+    var decls = <js_ast.Statement>[];
+    var types = typeFilter ?? keys;
+    for (var t in types) {
+      var stmt = _dischargeType(t);
+      if (stmt != null) decls.add(stmt);
+    }
+    return decls;
+  }
+
+  bool isNamed(DartType type) => _names.containsKey(type);
+
+  /// A name for a type made of JS identifier safe characters.
+  ///
+  /// 'L' and 'N' are prepended to a type name to represent a legacy or nullable
+  /// flavor of a type.
+  String _typeString(DartType type, {bool flat = false}) {
+    var nullability = type.declaredNullability == Nullability.legacy
+        ? 'L'
+        : type.declaredNullability == Nullability.nullable
+            ? 'N'
+            : '';
+    assert(isKnownDartTypeImplementor(type));
+    if (type is InterfaceType) {
+      var name = '${type.classNode.name}$nullability';
+      var typeArgs = type.typeArguments;
+      if (typeArgs == null) return name;
+      if (typeArgs.every((p) => p == const DynamicType())) return name;
+      return "${name}Of${typeArgs.map(_typeString).join("\$")}";
+    }
+    if (type is FutureOrType) {
+      var name = 'FutureOr$nullability';
+      if (type.typeArgument == const DynamicType()) return name;
+      return '${name}Of${_typeString(type.typeArgument)}';
+    }
+    if (type is TypedefType) {
+      var name = '${type.typedefNode.name}$nullability';
+      var typeArgs = type.typeArguments;
+      if (typeArgs == null) return name;
+      if (typeArgs.every((p) => p == const DynamicType())) return name;
+      return "${name}Of${typeArgs.map(_typeString).join("\$")}";
+    }
+    if (type is FunctionType) {
+      if (flat) return 'Fn';
+      var rType = _typeString(type.returnType, flat: true);
+      var params = type.positionalParameters
+          .take(3)
+          .map((p) => _typeString(p, flat: true));
+      var paramList = params.join('And');
+      var count = type.positionalParameters.length;
+      if (count > 3 || type.namedParameters.isNotEmpty) {
+        paramList = '${paramList}__';
+      } else if (count == 0) {
+        paramList = 'Void';
+      }
+      return '${paramList}To$nullability$rType';
+    }
+    if (type is TypeParameterType) return '${type.parameter.name}$nullability';
+    if (type is DynamicType) return 'dynamic';
+    if (type is VoidType) return 'void';
+    if (type is NeverType) return 'Never$nullability';
+    if (type is BottomType) return 'bottom';
+    if (type is NullType) return 'Null';
+    return 'invalid';
+  }
+
+  /// Heuristically choose a good name for the cache and generator
+  /// variables.
+  js_ast.TemporaryId chooseTypeName(DartType type) {
+    return js_ast.TemporaryId(escapeIdentifier(_typeString(type)));
+  }
+}
+
+/// _GeneratorTable tracks types which have been
+/// named and hoisted.
+class _GeneratorTable extends _CacheTable {
+  final _defs = <DartType, js_ast.Expression>{};
+
+  final js_ast.Identifier _runtimeModule;
+
+  _GeneratorTable(this._runtimeModule);
+
+  @override
+  js_ast.Statement _dischargeType(DartType t) {
+    var name = _names.remove(t);
+    if (name != null) {
+      var init = _defs.remove(t);
+      assert(init != null);
+      // TODO(vsm): Change back to `let`.
+      // See https://github.com/dart-lang/sdk/issues/40380.
+      return js.statement('var # = () => ((# = #.constFn(#))());',
+          [name, name, _runtimeModule, init]);
+    }
+    return null;
+  }
+
+  /// If [type] does not already have a generator name chosen for it,
+  /// assign it one, using [typeRep] as the initializer for it.
+  /// Emit the generator name.
+  js_ast.TemporaryId _nameType(DartType type, js_ast.Expression typeRep) {
+    var temp = _names[type];
+    if (temp == null) {
+      _names[type] = temp = chooseTypeName(type);
+      _defs[type] = typeRep;
+    }
+    return temp;
+  }
 }
 
 class TypeTable {
+  /// Generator variable names for hoisted types.
+  final _GeneratorTable _generators;
+
   /// Mapping from type parameters to the types which must have their
   /// cache/generator variables discharged at the binding site for the
   /// type variable since the type definition depends on the type
   /// parameter.
   final _scopeDependencies = <TypeParameter, List<DartType>>{};
 
-  /// Contains types with any free type parameters and maps them to a unique
-  /// JS identifier.
-  ///
-  /// Used to reference types hoisted to the top of a generic class or generic
-  /// function (as opposed to the top of the entire module).
-  final _unboundTypeIds = HashMap<DartType, js_ast.Identifier>();
-
-  /// Holds JS type generators keyed by their underlying DartType.
-  final typeContainer = ModuleItemContainer<DartType>.asObject('T',
-      keyToString: (DartType t) => escapeIdentifier(_typeString(t)));
-
-  final js_ast.Identifier _runtimeModule;
-
-  TypeTable(this._runtimeModule);
-
-  /// Returns true if [type] is already recorded in the table.
-  bool _isNamed(DartType type) =>
-      typeContainer.contains(type) || _unboundTypeIds.containsKey(type);
-
-  /// Emit the initializer statements for the type container, which contains
-  /// all named types with fully bound type parameters.
-  List<js_ast.Statement> dischargeBoundTypes() {
-    for (var t in typeContainer.keys) {
-      typeContainer[t] = js.call('() => ((# = #.constFn(#))())',
-          [typeContainer.access(t), _runtimeModule, typeContainer[t]]);
-    }
-    return typeContainer.emit();
-  }
-
-  js_ast.Statement _dischargeFreeType(DartType type) {
-    typeContainer.setNoEmit(type);
-    var init = typeContainer[type];
-    var id = _unboundTypeIds[type];
-    // TODO(vsm): Change back to `let`.
-    // See https://github.com/dart-lang/sdk/issues/40380.
-    return js.statement('var # = () => ((# = #.constFn(#))());',
-        [id, id, _runtimeModule, init]);
-  }
+  TypeTable(js_ast.Identifier runtime) : _generators = _GeneratorTable(runtime);
 
   /// Emit a list of statements declaring the cache variables and generator
-  /// definitions tracked by the table so far.
-  ///
-  /// If [formals] is present, only emit the definitions which depend on the
-  /// formals.
-  List<js_ast.Statement> dischargeFreeTypes([Iterable<TypeParameter> formals]) {
-    var decls = <js_ast.Statement>[];
-    var types = formals == null
-        ? typeContainer.keys.where((p) => freeTypeParameters(p).isNotEmpty)
-        : formals.expand((p) => _scopeDependencies[p] ?? <DartType>[]).toSet();
-
-    for (var t in types) {
-      var stmt = _dischargeFreeType(t);
-      if (stmt != null) decls.add(stmt);
-    }
-    return decls;
+  /// definitions tracked by the table.  If [formals] is present, only
+  /// emit the definitions which depend on the formals.
+  List<js_ast.Statement> discharge([List<TypeParameter> formals]) {
+    var filter = formals?.expand((p) => _scopeDependencies[p] ?? <DartType>[]);
+    var stmts = _generators.discharge(filter);
+    formals?.forEach(_scopeDependencies.remove);
+    return stmts;
   }
 
-  /// Emit a JS expression that evaluates to the generator for [type].
-  ///
-  /// If [type] does not already have a generator name chosen for it,
-  /// assign it one, using [typeRep] as its initializer.
-  js_ast.Expression _nameType(DartType type, js_ast.Expression typeRep) {
-    if (!typeContainer.contains(type)) {
-      typeContainer[type] = typeRep;
-    }
-    return _unboundTypeIds[type] ?? typeContainer.access(type);
-  }
-
-  /// Record the dependencies of the type on its free variables.
-  ///
-  /// Returns true if [type] is a free type parameter (but not a bound) and so
-  /// is not locally hoisted.
+  /// Record the dependencies of the type on its free variables
   bool recordScopeDependencies(DartType type) {
-    if (_isNamed(type)) {
-      return false;
-    }
-
     var freeVariables = freeTypeParameters(type);
     // TODO(leafp): This is a hack to avoid trying to hoist out of
     // generic functions and generic function types.  This often degrades
@@ -188,17 +200,6 @@
       return true;
     }
 
-    // This is only reached when [type] is itself a bound that depends on a
-    // free type parameter.
-    // TODO(markzipan): Bounds are locally hoisted to their own JS identifiers,
-    // but we don't do this this for other types that depend on free variables,
-    // resulting in some duplicated runtime code. We may get some performance
-    // wins if we just locally hoist everything.
-    if (freeVariables.isNotEmpty) {
-      _unboundTypeIds[type] =
-          js_ast.TemporaryId(escapeIdentifier(_typeString(type)));
-    }
-
     for (var free in freeVariables) {
       // If `free` is a promoted type parameter, get the original one so we can
       // find it in our map.
@@ -211,10 +212,10 @@
   /// add the type and its representation to the table, returning an
   /// expression which implements the type (but which caches the value).
   js_ast.Expression nameType(DartType type, js_ast.Expression typeRep) {
-    if (recordScopeDependencies(type)) {
+    if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
       return typeRep;
     }
-    var name = _nameType(type, typeRep);
+    var name = _generators._nameType(type, typeRep);
     return js.call('#()', [name]);
   }
 
@@ -227,10 +228,10 @@
   js_ast.Expression nameFunctionType(
       FunctionType type, js_ast.Expression typeRep,
       {bool lazy = false}) {
-    if (recordScopeDependencies(type)) {
+    if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
       return lazy ? js_ast.ArrowFun([], typeRep) : typeRep;
     }
-    var name = _nameType(type, typeRep);
+    var name = _generators._nameType(type, typeRep);
     return lazy ? name : js.call('#()', [name]);
   }
 }
diff --git a/tools/VERSION b/tools/VERSION
index 4bb1af7..028a34a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -28,4 +28,4 @@
 MINOR 12
 PATCH 0
 PRERELEASE 133
-PRERELEASE_PATCH 1
\ No newline at end of file
+PRERELEASE_PATCH 2
\ No newline at end of file