| // 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. | |
| import '../js_ast/js_ast.dart' as js_ast; | |
| import '../js_ast/js_ast.dart' show js; | |
| import 'js_names.dart' as js_ast; | |
| /// Defines how to emit a value of a table | |
| typedef EmitValue<K> = js_ast.Expression Function(K, ModuleItemData); | |
| /// 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, | |
| /// ... | |
| /// }; | |
| /// ``` | |
| abstract class ModuleItemContainer<K> { | |
| /// Name of the container in the emitted JS. | |
| String name; | |
| /// Refers to the latest container if this container is sharded. | |
| js_ast.Identifier containerId; | |
| final Map<K, ModuleItemData> moduleItems = {}; | |
| /// Incremental mode used for expression compilation | |
| bool _incrementalMode = false; | |
| /// Items accessed during incremental mode | |
| final Set<K> incrementalModuleItems = {}; | |
| /// Indicates if this table is being used in an incremental context. | |
| /// | |
| /// Used during expression evaluation. | |
| /// Set by `emitFunctionIncremental` in kernel/compiler.dart. | |
| bool get incrementalMode => _incrementalMode; | |
| /// Sets the container to incremental mode. | |
| /// | |
| /// Used during expression evaluating so only referenced items | |
| /// will be emitted in a generated function. | |
| /// | |
| /// Note: the container cannot revert to non-incremental mode. | |
| void setIncrementalMode() { | |
| incrementalModuleItems.clear(); | |
| _incrementalMode = true; | |
| } | |
| /// Holds keys that will not be emitted when calling [emit]. | |
| final Set<K> _noEmit = {}; | |
| /// Creates a container with a name, ID | |
| ModuleItemContainer._(this.name, this.containerId); | |
| /// Creates an automatically sharding container backed by JS Objects. | |
| factory ModuleItemContainer.asObject( | |
| String name, { | |
| required 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; | |
| bool get isEmpty => moduleItems.isEmpty; | |
| js_ast.Expression? operator [](K key) => moduleItems[key]?.jsValue; | |
| void operator []=(K key, js_ast.Expression value); | |
| /// Returns the expression that retrieves [key]'s corresponding JS value via | |
| /// a property access through its container. | |
| js_ast.Expression access(K key); | |
| 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); | |
| } | |
| void setEmitIfIncremental(K key) { | |
| if (incrementalMode) { | |
| incrementalModuleItems.add(key); | |
| } | |
| } | |
| /// Emit the container declaration/initializer, using multiple statements if | |
| /// necessary. | |
| /// | |
| /// Uses [emitValue] to emit the values in the table. | |
| List<js_ast.Statement> emit({EmitValue<K>? emitValue}); | |
| } | |
| /// 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> { | |
| /// 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, js_ast.ScopedId(name)); | |
| @override | |
| void operator []=(K key, js_ast.Expression value) { | |
| if (contains(key)) { | |
| moduleItems[key]!.jsValue = value; | |
| return; | |
| } | |
| // 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( | |
| containerId, | |
| js_ast.LiteralString("'$fieldString'"), | |
| value, | |
| ); | |
| if (length % 500 == 0) containerId = js_ast.ScopedId(name); | |
| } | |
| @override | |
| js_ast.Expression access(K key) { | |
| var id = moduleItems[key]!.id; | |
| return js.call('#.#', [id, moduleItems[key]!.jsKey]); | |
| } | |
| @override | |
| List<js_ast.Statement> emit({EmitValue<K>? emitValue}) { | |
| var containersToProperties = <js_ast.Identifier, List<js_ast.Property>>{}; | |
| moduleItems.forEach((k, v) { | |
| if (!incrementalMode && _noEmit.contains(k)) return; | |
| if (incrementalMode && !incrementalModuleItems.contains(k)) return; | |
| if (!containersToProperties.containsKey(v.id)) { | |
| containersToProperties[v.id] = <js_ast.Property>[]; | |
| } | |
| containersToProperties[v.id]!.add( | |
| js_ast.Property( | |
| v.jsKey, | |
| emitValue == null ? v.jsValue : emitValue(k, v), | |
| ), | |
| ); | |
| }); | |
| if (containersToProperties.isEmpty) return []; | |
| var statements = <js_ast.Statement>[]; | |
| containersToProperties.forEach((containerId, properties) { | |
| var containerObject = js_ast.ObjectInitializer( | |
| properties, | |
| multiline: properties.length > 1, | |
| ); | |
| statements.add(js.statement('var # = #', [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.ScopedId(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) { | |
| var id = containerId; | |
| return js.call('#[#]', [id, moduleItems[key]!.jsKey]); | |
| } | |
| @override | |
| List<js_ast.Statement> emit({EmitValue<K>? emitValue}) { | |
| var dummyExpression = js_ast.ScopedId('dummyExpression'); | |
| var properties = List<js_ast.Expression>.filled(length, dummyExpression); | |
| // If the entire array holds just one value, generate a short initializer. | |
| var valueSet = <js_ast.Expression>{}; | |
| moduleItems.forEach((k, v) { | |
| if (!incrementalMode && _noEmit.contains(k)) return; | |
| if (incrementalMode && !incrementalModuleItems.contains(k)) return; | |
| valueSet.add(v.jsValue); | |
| properties[int.parse((v.jsKey as js_ast.LiteralNumber).value)] = | |
| emitValue == null ? v.jsValue : emitValue(k, v); | |
| }); | |
| if (valueSet.isEmpty) return []; | |
| 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), | |
| ]), | |
| ]; | |
| } | |
| } |