Version 2.12.0-120.0.dev

Merge commit '37d8c78237e875355ffe36d6b2b33c29606a43fa' into 'dev'
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
index c62864d..6b7be5d 100644
--- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_mini_ast.dart
@@ -199,7 +199,7 @@
   Expression get nonNullAssert => new _NonNullAssert(this);
 
   /// If `this` is an expression `x`, creates the expression `(x)`.
-  Expression get parenthesized => new _WrappedExpression(null, this, null);
+  Expression get parenthesized => new _ParenthesizedExpression(this);
 
   /// If `this` is an expression `x`, creates the statement `x;`.
   Statement get stmt => new _ExpressionStatement(this);
@@ -1225,6 +1225,28 @@
   }
 }
 
+class _ParenthesizedExpression extends Expression {
+  final Expression expr;
+
+  _ParenthesizedExpression(this.expr);
+
+  @override
+  String toString() => '($expr)';
+
+  @override
+  void _preVisit(AssignedVariables<Node, Var> assignedVariables) {
+    expr._preVisit(assignedVariables);
+  }
+
+  @override
+  Type _visit(
+      Harness h, FlowAnalysis<Node, Statement, Expression, Var, Type> flow) {
+    var type = expr._visit(h, flow);
+    flow.parenthesizedExpression(this, expr);
+    return type;
+  }
+}
+
 class _PlaceholderExpression extends Expression {
   final Type type;
 
diff --git a/pkg/dds/CHANGELOG.md b/pkg/dds/CHANGELOG.md
index 953ac0b..c88de17 100644
--- a/pkg/dds/CHANGELOG.md
+++ b/pkg/dds/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 1.6.1
+- Fixed unhandled `StateError` that could be thrown if the VM service disconnected
+  while a request was outstanding.
+
 # 1.6.0
 - Added `errorCode` to `DartDevelopmentServiceException` to communicate the
   underlying reason of the failure.
diff --git a/pkg/dds/lib/src/client.dart b/pkg/dds/lib/src/client.dart
index 2c85aae..9637058 100644
--- a/pkg/dds/lib/src/client.dart
+++ b/pkg/dds/lib/src/client.dart
@@ -240,8 +240,14 @@
 
     // Unless otherwise specified, the request is forwarded to the VM service.
     // NOTE: This must be the last fallback registered.
-    _clientPeer.registerFallback((parameters) async =>
-        await _vmServicePeer.sendRequest(parameters.method, parameters.value));
+    _clientPeer.registerFallback((parameters) async {
+      try {
+        return await _vmServicePeer.sendRequest(
+            parameters.method, parameters.value);
+      } on StateError {
+        await dds.shutdown();
+      }
+    });
   }
 
   static int _idCounter = 0;
diff --git a/pkg/dds/pubspec.yaml b/pkg/dds/pubspec.yaml
index d4132a1..d0e85c6 100644
--- a/pkg/dds/pubspec.yaml
+++ b/pkg/dds/pubspec.yaml
@@ -3,7 +3,7 @@
   A library used to spawn the Dart Developer Service, used to communicate with
   a Dart VM Service instance.
 
-version: 1.6.0
+version: 1.6.1
 
 homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
 
diff --git a/pkg/dds/test/handles_client_disconnect_state_error_test.dart b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
index 2bca092..f500711 100644
--- a/pkg/dds/test/handles_client_disconnect_state_error_test.dart
+++ b/pkg/dds/test/handles_client_disconnect_state_error_test.dart
@@ -24,6 +24,11 @@
         // Notify listeners that this client is closed.
         doneCompleter.complete();
         break;
+      case 'foo':
+        completer.completeError(
+          StateError('The client closed with pending request "foo".'),
+        );
+        break;
       default:
         completer.complete(await super.sendRequest(method, args));
     }
@@ -61,4 +66,28 @@
     await client.close();
     await dds.done;
   });
+
+  test('StateError handled by _DartDevelopmentServiceClient request forwarder',
+      () async {
+    final dds = await DartDevelopmentService.startDartDevelopmentService(
+        Uri(scheme: 'http'));
+    final ws = await WebSocketChannel.connect(dds.uri.replace(scheme: 'ws'));
+
+    // Create a VM service client that connects to DDS.
+    final client = json_rpc.Client(ws.cast<String>());
+    unawaited(client.listen());
+
+    // Make a request that causes the VM service peer to close in the middle of
+    // handling a request. This is meant to mimic a device being disconnected
+    // unexpectedly.
+    try {
+      await client.sendRequest('foo');
+    } on StateError {
+      // This state error is expected. This test is ensuring that DDS exits
+      // gracefully even if the VM service disappears.
+    }
+
+    // DDS should shutdown if the VM service peer disconnects.
+    await dds.done;
+  });
 }
diff --git a/pkg/dev_compiler/lib/src/compiler/module_containers.dart b/pkg/dev_compiler/lib/src/compiler/module_containers.dart
new file mode 100644
index 0000000..82f06b4
--- /dev/null
+++ b/pkg/dev_compiler/lib/src/compiler/module_containers.dart
@@ -0,0 +1,259 @@
+// 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>(length);
+
+    // 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 e9527d9..2fcad3e 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_compiler.dart
@@ -8,6 +8,7 @@
 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;
 
@@ -25,6 +26,10 @@
   /// 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
@@ -51,11 +56,18 @@
   /// Whether we're currently building the SDK, which may require special
   /// bootstrapping logic.
   ///
-  /// This is initialized by [startModule], which must be called before
+  /// This is initialized by [emitModule], 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
@@ -250,10 +262,16 @@
       var idName = name.endsWith('=') ? name.replaceAll('=', '_') : name;
       idName = idName.replaceAll(js_ast.invalidCharInIdentifier, '_');
       id ??= js_ast.TemporaryId(idName);
-      // 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)]));
+      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)]));
+      }
       return id;
     }
 
@@ -338,9 +356,13 @@
     var name = js.escapedString(symbolName, "'");
     js_ast.Expression result;
     if (last.startsWith('_')) {
-      var nativeSymbol = emitPrivateNameSymbol(currentLibrary, last);
-      result = js.call('new #.new(#, #)',
-          [emitConstructorAccess(privateSymbolType), name, nativeSymbol]);
+      var nativeSymbolAccessor =
+          getSymbol(emitPrivateNameSymbol(currentLibrary, last));
+      result = js.call('new #.new(#, #)', [
+        emitConstructorAccess(privateSymbolType),
+        name,
+        nativeSymbolAccessor
+      ]);
     } else {
       result = js.call(
           'new #.new(#)', [emitConstructorAccess(internalSymbolType), name]);
@@ -366,12 +388,11 @@
   /// 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
+  /// This also initializes several fields: [runtimeModule],
+  /// [extensionSymbolsModule], and 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.
@@ -505,9 +526,13 @@
       if (isBuildingSdk) {
         value = js.call('# = Symbol(#)', [value, js.string('dartx.$name')]);
       }
-      // TODO(vsm): Change back to `const`.
-      // See https://github.com/dart-lang/sdk/issues/40380.
-      items.add(js.statement('var # = #;', [id, value]));
+      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;
     });
   }
 
@@ -534,6 +559,28 @@
         [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.
   ///
@@ -552,6 +599,9 @@
     // 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();
@@ -582,10 +632,13 @@
   /// handle the many details involved in naming.
   @protected
   js_ast.TemporaryId getExtensionSymbolInternal(String name) {
-    return _extensionSymbols.putIfAbsent(
-        name,
-        () => js_ast.TemporaryId(
-            '\$${js_ast.friendlyNameForDartOperator[name] ?? name}'));
+    if (!_extensionSymbols.containsKey(name)) {
+      var id = js_ast.TemporaryId(
+          '\$${js_ast.friendlyNameForDartOperator[name] ?? name}');
+      _extensionSymbols[name] = id;
+      addSymbol(id, id);
+    }
+    return _extensionSymbols[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 db85b79..8800d4c 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -22,6 +22,7 @@
 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;
@@ -69,11 +70,14 @@
   /// Let variables collected for the given function.
   List<js_ast.TemporaryId> _letVariables;
 
-  final _constTable = _emitTemporaryId('CT');
+  final _constTable = js_ast.TemporaryId('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.
@@ -216,7 +220,7 @@
   final constAliasCache = HashMap<Constant, js_ast.Expression>();
 
   /// Maps uri strings in asserts and elsewhere to hoisted identifiers.
-  final _uriMap = HashMap<String, js_ast.Identifier>();
+  final _uriContainer = ModuleItemContainer<String>.asArray('I');
 
   final Class _jsArrayClass;
   final Class _privateSymbolClass;
@@ -323,7 +327,32 @@
 
     var libraries = component.libraries;
 
-    // Initialize our library variables.
+    // 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;
+    }
+
     var items = startModule(libraries);
     _nullableInference.allowNotNullDeclarations = isBuildingSdk;
     _typeTable = TypeTable(runtimeModule);
@@ -335,10 +364,12 @@
       _pendingClasses.addAll(l.classes);
     }
 
-    // TODO(markzipan): Don't emit this when compiling the SDK.
-    moduleItems
-        .add(js.statement('const # = Object.create(null);', [_constTable]));
-    _constTableInsertionIndex = moduleItems.length;
+    // 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;
 
     // Add implicit dart:core dependency so it is first.
     emitLibraryName(_coreTypes.coreLibrary);
@@ -350,22 +381,25 @@
     // 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.insert(_constTableInsertionIndex, constTableBody);
+      moduleItems.insertAll(
+          _constTableInsertionIndex, [constTableDeclaration, 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();
 
@@ -375,9 +409,8 @@
     // Declare imports and extension symbols
     emitImportsAndExtensionSymbols(items);
 
-    // Discharge the type table cache variables and
-    // hoisted definitions.
-    items.addAll(_typeTable.discharge());
+    // Emit the hoisted type table cache variables
+    items.addAll(_typeTable.dischargeBoundTypes());
 
     return finishModule(items, _options.moduleName);
   }
@@ -442,6 +475,10 @@
     _currentLibrary = library;
     _staticTypeContext.enterLibrary(_currentLibrary);
 
+    if (isBuildingSdk) {
+      containerizeSymbols = _isWebLibrary(library.importUri);
+    }
+
     if (isSdkInternalRuntime(library)) {
       // `dart:_runtime` uses a different order for bootstrapping.
       //
@@ -566,10 +603,29 @@
     _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
@@ -593,7 +649,6 @@
     // Attach caches on all canonicalized types.
     body.add(runtimeStatement('addTypeCaches(#)', [className]));
 
-    _emitVirtualFieldSymbols(c, body);
     _emitClassSignature(c, className, body);
     _initExtensionSymbols(c);
     if (!c.isMixinDeclaration) {
@@ -644,7 +699,7 @@
 
     var typeConstructor = js.call('(#) => { #; #; return #; }', [
       jsFormals,
-      _typeTable.discharge(formals),
+      _typeTable.dischargeFreeTypes(formals),
       body,
       className ?? _emitIdentifier(name)
     ]);
@@ -1525,7 +1580,13 @@
 
     var body = <js_ast.Statement>[];
     void emitFieldInit(Field f, Expression initializer, TreeNode hoverInfo) {
-      var access = _classProperties.virtualFields[f] ?? _declareMemberName(f);
+      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 jsInit = _visitInitializer(initializer, f.annotations);
       body.add(jsInit
           .toAssignExpression(js.call('this.#', [access])
@@ -1912,14 +1973,16 @@
   /// 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[#]; }', [virtualField]);
+    var getter = js.fun('function() { return this[#]; }', [virtualFieldSymbol]);
     var jsGetter = js_ast.Method(name, getter, isGetter: true)
       ..sourceInformation = _nodeStart(field);
 
-    var args =
-        field.isFinal ? [js_ast.Super(), name] : [js_ast.This(), virtualField];
+    var args = field.isFinal
+        ? [js_ast.Super(), name]
+        : [js_ast.This(), virtualFieldSymbol];
 
     js_ast.Expression value = _emitIdentifier('value');
     if (!field.isFinal && isCovariantField(field)) {
@@ -2235,13 +2298,13 @@
       var memberLibrary = member?.name?.library ??
           memberClass?.enclosingLibrary ??
           _currentLibrary;
-      return emitPrivateNameSymbol(memberLibrary, name);
+      return getSymbol(emitPrivateNameSymbol(memberLibrary, name));
     }
 
     useExtension ??= _isSymbolizedMember(memberClass, name);
     name = js_ast.memberNameForDartMember(name, _isExternal(member));
     if (useExtension) {
-      return getExtensionSymbolInternal(name);
+      return getSymbol(getExtensionSymbolInternal(name));
     }
     return propertyName(name);
   }
@@ -2858,7 +2921,7 @@
 
       js_ast.Expression addTypeFormalsAsParameters(
           List<js_ast.Expression> elements) {
-        var names = _typeTable.discharge(typeFormals);
+        var names = _typeTable.dischargeFreeTypes(typeFormals);
         return names.isEmpty
             ? js.call('(#) => [#]', [tf, elements])
             : js.call('(#) => {#; return [#];}', [tf, names, elements]);
@@ -3006,9 +3069,8 @@
     // Issue: https://github.com/dart-lang/sdk/issues/43288
     var fun = _emitFunction(functionNode, name);
 
-    var types = _typeTable.discharge();
+    var types = _typeTable?.dischargeFreeTypes();
     var constants = _dischargeConstTable();
-
     var body = js_ast.Block([...?types, ...?constants, ...fun.body.statements]);
     return js_ast.Fun(fun.params, body);
   }
@@ -3102,22 +3164,6 @@
     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)))
@@ -3644,14 +3690,11 @@
 
   // Replace a string `uri` literal with a cached top-level variable containing
   // the value to reduce overall code size.
-  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;
+  js_ast.Expression _cacheUri(String uri) {
+    if (!_uriContainer.contains(uri)) {
+      _uriContainer[uri] = js_ast.LiteralString('"$uri"');
     }
-    return id;
+    return _uriContainer.access(uri);
   }
 
   @override
@@ -5004,7 +5047,7 @@
           return _emitType(firstArg.type);
         }
         if (name == 'extensionSymbol' && firstArg is StringLiteral) {
-          return getExtensionSymbolInternal(firstArg.value);
+          return getSymbol(getExtensionSymbolInternal(firstArg.value));
         }
 
         if (name == 'compileTimeFlag' && firstArg is StringLiteral) {
@@ -5880,19 +5923,19 @@
     }
     var constAliasString = 'C${constAliasCache.length}';
     var constAliasProperty = propertyName(constAliasString);
-    var constAliasId = _emitTemporaryId(constAliasString);
-    var constAccessor =
-        js.call('# || #.#', [constAliasId, _constTable, constAliasProperty]);
+
+    _constTableCache[constAliasString] = js.call('void 0');
+    var constAliasAccessor = _constTableCache.access(constAliasString);
+
+    var constAccessor = js.call(
+        '# || #.#', [constAliasAccessor, _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 # = #;', [constAliasId, constJs])
+          js.statement('return # = #;', [constAliasAccessor, constJs])
         ]));
     var accessor = js_ast.Method(constAliasProperty, func, isGetter: true);
     _constLazyAccessors.add(accessor);
@@ -5978,8 +6021,8 @@
       // was overridden.
       var symbol = cls.isEnum
           ? _emitMemberName(member.name.text, member: member)
-          : emitClassPrivateNameSymbol(
-              cls.enclosingLibrary, getLocalClassName(cls), member.name.text);
+          : getSymbol(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 ddaf27f..b9b73fd 100644
--- a/pkg/dev_compiler/lib/src/kernel/type_table.dart
+++ b/pkg/dev_compiler/lib/src/kernel/type_table.dart
@@ -4,13 +4,17 @@
 
 // @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>{};
@@ -36,160 +40,144 @@
   return result;
 }
 
-/// _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 null;
+/// 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("\$")}";
   }
-
-  /// 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;
+  if (type is FutureOrType) {
+    var name = 'FutureOr$nullability';
+    if (type.typeArgument == const DynamicType()) return name;
+    return '${name}Of${_typeString(type.typeArgument)}';
   }
-
-  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';
+  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("\$")}";
   }
-
-  /// 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]);
+  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 null;
+    return '${paramList}To$nullability$rType';
   }
-
-  /// 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;
-  }
+  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';
 }
 
 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>>{};
 
-  TypeTable(js_ast.Identifier runtime) : _generators = _GeneratorTable(runtime);
+  /// 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>();
 
-  /// Emit a list of statements declaring the cache variables and generator
-  /// 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;
+  /// 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();
   }
 
-  /// Record the dependencies of the type on its free variables
+  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]);
+  }
+
+  /// 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;
+  }
+
+  /// 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.
   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
@@ -200,6 +188,17 @@
       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.
@@ -212,10 +211,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 (!_generators.isNamed(type) && recordScopeDependencies(type)) {
+    if (recordScopeDependencies(type)) {
       return typeRep;
     }
-    var name = _generators._nameType(type, typeRep);
+    var name = _nameType(type, typeRep);
     return js.call('#()', [name]);
   }
 
@@ -228,10 +227,10 @@
   js_ast.Expression nameFunctionType(
       FunctionType type, js_ast.Expression typeRep,
       {bool lazy = false}) {
-    if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
+    if (recordScopeDependencies(type)) {
       return lazy ? js_ast.ArrowFun([], typeRep) : typeRep;
     }
-    var name = _generators._nameType(type, typeRep);
+    var name = _nameType(type, typeRep);
     return lazy ? name : js.call('#()', [name]);
   }
 }
diff --git a/tests/lib/lib_dart2js.status b/tests/lib/lib_dart2js.status
index 14a0a1a..d66a2c8 100644
--- a/tests/lib/lib_dart2js.status
+++ b/tests/lib/lib_dart2js.status
@@ -23,6 +23,8 @@
 html/xhr_test: Slow, Pass
 isolate/*: SkipByDesign # No support for dart:isolate in dart4web (http://dartbug.com/30538)
 mirrors/*: SkipByDesign # Mirrors not supported on web in Dart 2.0.
+typed_data/int64_list_load_store_test: SkipByDesign # No support for Int64List
+typed_data/typed_data_hierarchy_int64_test: SkipByDesign # No support for Int64List
 wasm/*: SkipByDesign # dart:wasm not currently supported on web.
 
 [ $compiler != dart2js ]
diff --git a/tests/lib_2/typed_data/int32x4_arithmetic_test.dart b/tests/lib_2/typed_data/int32x4_arithmetic_test.dart
index 6f08626..f9a740e 100644
--- a/tests/lib_2/typed_data/int32x4_arithmetic_test.dart
+++ b/tests/lib_2/typed_data/int32x4_arithmetic_test.dart
@@ -91,16 +91,35 @@
   Expect.equals(1, o.w);
 }
 
+const int53 = 0x20000000000000; // 2^53.
+final usingJavaScriptNumbers = (int53 + 1) == int53;
+
 testTruncation() {
-  var n = 0xAABBCCDD00000001;
-  var x = new Int32x4(n, 0, 0, 0);
-  Expect.equals(x.x, 1);
+  // Check that various bits from bit 32 and up are masked away.
+  var base = usingJavaScriptNumbers ? 0x1BCCDD00000000 : 0xAABBCCDD00000000;
+  var x1 = new Int32x4(base + 1, 0, 0, 0);
+  Expect.equals(1, x1.x);
+
+  // Check that all even bits up to bit 30 are preserved.
+  var x2 = new Int32x4(base + 0x55555555, 0, 0, 0);
+  Expect.equals(0x55555555, x2.x);
+
+  // Check that the odd bits up to bit 31 are preserved, and that
+  // bit 31 is treated as a sign bit.
+  var x3 = new Int32x4(base + 0xAAAAAAAA, 0, 0, 0);
+  const signExtended = -1431655766; // 0xFFFFFFFFAAAAAAAA or 0x3FFFFFAAAAAAAA.
+  Expect.equals(signExtended, x3.x);
+
+  // Check that all bits from bit 32 and up are masked away.
+  var highBase = 0xFFFFFFFF10000000;
+  var x4 = new Int32x4(highBase, 0, 0, 0);
+  Expect.equals(0x10000000, x4.x);
 }
 
 main() {
   for (int i = 0; i < 20; i++) {
     testAdd();
     testSub();
-    testTruncation(); //   //# int64: ok
+    testTruncation();
   }
 }
diff --git a/tools/VERSION b/tools/VERSION
index 39a0b88..e1df069 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 12
 PATCH 0
-PRERELEASE 119
+PRERELEASE 120
 PRERELEASE_PATCH 0
\ No newline at end of file