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