First version of incremental DDC mode for expression compilation
- Support evaluate() calls from VM service in expression compiler
- emit all accessed symbols, types, constants, extension symbols,
and imports as part of synthetic evaluation function
- Note: this fixes missing symbol issues in evaluateInFrame()
as well
- update expression evaluation tests
- fix expression compilation broken after hot reload
See widget inspector layout explorer, a result of evaluate() call:
https://drive.google.com/file/d/16UdSE5_V1ZRXAf2KeBxNwYNHMfo1RbnT/view?usp=sharing&resourcekey=0-HZcPm68VbsVzrZ672CApvA
Closes: https://github.com/dart-lang/sdk/issues/41480
Closes: https://github.com/dart-lang/sdk/issues/44979
Closes: https://github.com/dart-lang/sdk/issues/44713
Closes: https://github.com/dart-lang/sdk/issues/44933
Closes: https://github.com/dart-lang/sdk/issues/44813
Closes: https://github.com/dart-lang/sdk/issues/44686
Change-Id: I96c74578c51503adbc4bfe6d6e6112319addc959
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/188400
Commit-Queue: Anna Gringauze <annagrin@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
This commit is contained in:
committed by
commit-bot@chromium.org
parent
9f6e64a06f
commit
14eeb7507f
@@ -96,6 +96,27 @@ Program transformModuleFormat(ModuleFormat format, Program module) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms an ES6 [function] into a given module [format].
|
||||
///
|
||||
/// If the format is [ModuleFormat.es6] this will return [function] unchanged.
|
||||
///
|
||||
/// Because JS ASTs are immutable the resulting function will share as much
|
||||
/// structure as possible with the original. The transformation is a shallow one
|
||||
/// that affects the [ImportDeclaration]s from [items].
|
||||
///
|
||||
/// Returns a new function that combines all statements from tranformed imports
|
||||
/// from [items] and the body of the [function].
|
||||
Fun transformFunctionModuleFormat(
|
||||
List<ModuleItem> items, Fun function, ModuleFormat format) {
|
||||
switch (format) {
|
||||
case ModuleFormat.amd:
|
||||
return AmdModuleBuilder().buildFunctionWithImports(items, function);
|
||||
default:
|
||||
throw UnsupportedError(
|
||||
'Incremental build does not support $format module format');
|
||||
}
|
||||
}
|
||||
|
||||
/// Base class for compiling ES6 modules into various ES5 module patterns.
|
||||
///
|
||||
/// This is a helper class for utilities and state that is shared by several
|
||||
@@ -107,11 +128,16 @@ abstract class _ModuleBuilder {
|
||||
final statements = <Statement>[];
|
||||
|
||||
/// Collect [imports], [exports] and [statements] from the ES6 [module].
|
||||
void visitProgram(Program module) {
|
||||
visitModuleItems(module.body);
|
||||
}
|
||||
|
||||
/// Collect [imports], [exports] and [statements] from the ES6 [items].
|
||||
///
|
||||
/// For exports, this will also add their body to [statements] in the
|
||||
/// appropriate position.
|
||||
void visitProgram(Program module) {
|
||||
for (var item in module.body) {
|
||||
void visitModuleItems(List<ModuleItem> items) {
|
||||
for (var item in items) {
|
||||
if (item is ImportDeclaration) {
|
||||
visitImportDeclaration(item);
|
||||
} else if (item is ExportDeclaration) {
|
||||
@@ -137,6 +163,12 @@ abstract class _ModuleBuilder {
|
||||
void visitStatement(Statement node) {
|
||||
statements.add(node);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
imports.clear();
|
||||
exports.clear();
|
||||
statements.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates modules for with our DDC `dart_library.js` loading mechanism.
|
||||
@@ -260,31 +292,43 @@ class CommonJSModuleBuilder extends _ModuleBuilder {
|
||||
class AmdModuleBuilder extends _ModuleBuilder {
|
||||
AmdModuleBuilder();
|
||||
|
||||
Program build(Program module) {
|
||||
var importStatements = <Statement>[];
|
||||
/// Build a module variable definition for [import].
|
||||
static Statement buildLoadModule(
|
||||
Identifier moduleVar, ImportDeclaration import) =>
|
||||
js.statement('const # = require(#);', [moduleVar, import.from]);
|
||||
|
||||
// Collect imports/exports/statements.
|
||||
visitProgram(module);
|
||||
/// Build library variable definitions for all libraries from [import].
|
||||
static List<Statement> buildImports(
|
||||
Identifier moduleVar, ImportDeclaration import) {
|
||||
var items = <Statement>[];
|
||||
|
||||
var dependencies = <LiteralString>[];
|
||||
var fnParams = <Parameter>[];
|
||||
for (var importName in import.namedImports) {
|
||||
// import * is not emitted by the compiler, so we don't handle it here.
|
||||
assert(!importName.isStar);
|
||||
var asName = importName.asName ?? importName.name;
|
||||
items.add(js.statement(
|
||||
'const # = #.#', [asName, moduleVar, importName.name.name]));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/// Group libraries from [imports] by modules.
|
||||
static Map<Identifier, ImportDeclaration> _collectModuleImports(
|
||||
List<ImportDeclaration> imports) {
|
||||
var result = <Identifier, ImportDeclaration>{};
|
||||
for (var import in imports) {
|
||||
// TODO(jmesserly): we could use destructuring once Atom supports it.
|
||||
var moduleVar =
|
||||
TemporaryId(pathToJSIdentifier(import.from.valueWithoutQuotes));
|
||||
fnParams.add(moduleVar);
|
||||
dependencies.add(import.from);
|
||||
|
||||
// TODO(jmesserly): optimize for the common case of a single import.
|
||||
for (var importName in import.namedImports) {
|
||||
// import * is not emitted by the compiler, so we don't handle it here.
|
||||
assert(!importName.isStar);
|
||||
var asName = importName.asName ?? importName.name;
|
||||
importStatements.add(js.statement(
|
||||
'const # = #.#', [asName, moduleVar, importName.name.name]));
|
||||
}
|
||||
result[moduleVar] = import;
|
||||
}
|
||||
statements.insertAll(0, importStatements);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Build statements for [exports].
|
||||
static List<Statement> buildExports(List<ExportDeclaration> exports) {
|
||||
var items = <Statement>[];
|
||||
|
||||
if (exports.isNotEmpty) {
|
||||
var exportedProps = <Property>[];
|
||||
@@ -297,9 +341,59 @@ class AmdModuleBuilder extends _ModuleBuilder {
|
||||
exportedProps.add(Property(js.string(alias.name), name.name));
|
||||
}
|
||||
}
|
||||
statements.add(js.comment('Exports:'));
|
||||
statements.add(Return(ObjectInitializer(exportedProps, multiline: true)));
|
||||
items.add(js.comment('Exports:'));
|
||||
items.add(Return(ObjectInitializer(exportedProps, multiline: true)));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/// Build function body with all necessary imports included.
|
||||
///
|
||||
/// Used for the top level syntetic function generated during expression
|
||||
/// compilation, in order to include all the context needed for evaluation
|
||||
/// inside it.
|
||||
///
|
||||
/// Returns a new function that combines all statements from tranformed
|
||||
/// imports from [items] and the body of the [function].
|
||||
Fun buildFunctionWithImports(List<ModuleItem> items, Fun function) {
|
||||
clear();
|
||||
visitModuleItems(items);
|
||||
|
||||
var moduleImports = _collectModuleImports(imports);
|
||||
var importStatements = <Statement>[];
|
||||
|
||||
moduleImports.forEach((moduleVar, import) {
|
||||
importStatements.add(buildLoadModule(moduleVar, import));
|
||||
importStatements.addAll(buildImports(moduleVar, import));
|
||||
});
|
||||
|
||||
return Fun(
|
||||
function.params,
|
||||
Block([...importStatements, ...statements, ...function.body.statements]),
|
||||
);
|
||||
}
|
||||
|
||||
Program build(Program module) {
|
||||
// Collect imports/exports/statements.
|
||||
visitProgram(module);
|
||||
|
||||
var moduleImports = _collectModuleImports(imports);
|
||||
var importStatements = <Statement>[];
|
||||
|
||||
var fnParams = moduleImports.keys.toList();
|
||||
var dependencies =
|
||||
moduleImports.values.map((import) => import.from).toList();
|
||||
|
||||
moduleImports.forEach((moduleVar, import) {
|
||||
importStatements.addAll(buildImports(moduleVar, import));
|
||||
});
|
||||
|
||||
// Prepend import statetements.
|
||||
statements.insertAll(0, importStatements);
|
||||
|
||||
// Append export statements.
|
||||
statements.addAll(buildExports(exports));
|
||||
|
||||
var resultModule = NamedFunction(
|
||||
loadFunctionIdentifier(module.name),
|
||||
js.fun("function(#) { 'use strict'; #; }", [fnParams, statements]),
|
||||
|
||||
@@ -8,6 +8,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;
|
||||
|
||||
/// 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.
|
||||
@@ -39,34 +42,39 @@ abstract class ModuleItemContainer<K> {
|
||||
/// Name of the container in the emitted JS.
|
||||
String name;
|
||||
|
||||
/// Indicates if this table is being used in an incremental context (such as
|
||||
/// during expression evaluation).
|
||||
///
|
||||
/// Set by `emitFunctionIncremental` in kernel/compiler.dart.
|
||||
bool incrementalMode = false;
|
||||
|
||||
/// Refers to the latest container if this container is sharded.
|
||||
js_ast.Identifier containerId;
|
||||
|
||||
/// Refers to the aggregated entrypoint into this container.
|
||||
///
|
||||
/// Should only be accessed during expression evaluation since lookups are
|
||||
/// deoptimized in V8..
|
||||
js_ast.Identifier aggregatedContainerId;
|
||||
|
||||
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, and incremental ID used for
|
||||
/// expression evaluation.
|
||||
///
|
||||
/// If [aggregatedId] is null, the container is not sharded, so the
|
||||
/// containerId is safe to use during eval.
|
||||
ModuleItemContainer._(
|
||||
this.name, this.containerId, js_ast.Identifier aggregatedId)
|
||||
: aggregatedContainerId = aggregatedId ?? containerId;
|
||||
/// 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,
|
||||
@@ -107,15 +115,17 @@ abstract class ModuleItemContainer<K> {
|
||||
_noEmit.add(key);
|
||||
}
|
||||
|
||||
void setEmitIfIncremental(K key) {
|
||||
if (incrementalMode) {
|
||||
incrementalModuleItems.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit the container declaration/initializer, using multiple statements if
|
||||
/// necessary.
|
||||
List<js_ast.Statement> emit();
|
||||
|
||||
/// Emit the container declaration/initializer incrementally.
|
||||
///
|
||||
/// Used during expression evaluation. Appends all newly added types to the
|
||||
/// aggregated container.
|
||||
List<js_ast.Statement> emitIncremental();
|
||||
/// 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.
|
||||
@@ -142,8 +152,7 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
|
||||
String Function(K) keyToString;
|
||||
|
||||
ModuleItemObjectContainer(String name, this.keyToString)
|
||||
: super._(
|
||||
name, js_ast.TemporaryId(name), js_ast.Identifier('${name}\$Eval'));
|
||||
: super._(name, js_ast.TemporaryId(name));
|
||||
|
||||
@override
|
||||
void operator []=(K key, js_ast.Expression value) {
|
||||
@@ -170,53 +179,33 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
|
||||
|
||||
@override
|
||||
js_ast.Expression access(K key) {
|
||||
var id = incrementalMode ? aggregatedContainerId : moduleItems[key].id;
|
||||
var id = moduleItems[key].id;
|
||||
return js.call('#.#', [id, moduleItems[key].jsKey]);
|
||||
}
|
||||
|
||||
@override
|
||||
List<js_ast.Statement> emit() {
|
||||
List<js_ast.Statement> emit({_emitValue<K> emitValue}) {
|
||||
var containersToProperties = <js_ast.Identifier, List<js_ast.Property>>{};
|
||||
moduleItems.forEach((k, v) {
|
||||
if (_noEmit.contains(k)) return;
|
||||
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, v.jsValue));
|
||||
containersToProperties[v.id].add(js_ast.Property(
|
||||
v.jsKey, emitValue == null ? v.jsValue : emitValue(k, v)));
|
||||
});
|
||||
|
||||
// Emit a self-reference for the next container so V8 does not optimize it
|
||||
// away. Required for expression evaluation.
|
||||
if (containersToProperties[containerId] == null) {
|
||||
containersToProperties[containerId] = [
|
||||
js_ast.Property(
|
||||
js_ast.LiteralString('_'), js.call('() => #', [containerId]))
|
||||
];
|
||||
}
|
||||
if (containersToProperties.isEmpty) return [];
|
||||
|
||||
var statements = <js_ast.Statement>[];
|
||||
var aggregatedContainers = <js_ast.Expression>[];
|
||||
containersToProperties.forEach((containerId, properties) {
|
||||
var containerObject = js_ast.ObjectInitializer(properties,
|
||||
multiline: properties.length > 1);
|
||||
statements.add(js.statement('var # = #', [containerId, containerObject]));
|
||||
aggregatedContainers.add(js.call('#', [containerId]));
|
||||
});
|
||||
// Create an aggregated access point over all containers for eval.
|
||||
statements.add(js.statement('var # = Object.assign({_ : () => #}, #)',
|
||||
[aggregatedContainerId, aggregatedContainerId, aggregatedContainers]));
|
||||
return statements;
|
||||
}
|
||||
|
||||
/// Appends all newly added types to the most recent container.
|
||||
@override
|
||||
List<js_ast.Statement> emitIncremental() {
|
||||
assert(incrementalMode);
|
||||
var statements = <js_ast.Statement>[];
|
||||
moduleItems.forEach((k, v) {
|
||||
if (_noEmit.contains(k)) return;
|
||||
statements.add(js
|
||||
.statement('#[#] = #', [aggregatedContainerId, v.jsKey, v.jsValue]));
|
||||
});
|
||||
return statements;
|
||||
}
|
||||
}
|
||||
@@ -232,7 +221,7 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
|
||||
/// ```
|
||||
class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
|
||||
ModuleItemArrayContainer(String name)
|
||||
: super._(name, js_ast.TemporaryId(name), null);
|
||||
: super._(name, js_ast.TemporaryId(name));
|
||||
|
||||
@override
|
||||
void operator []=(K key, js_ast.Expression value) {
|
||||
@@ -246,23 +235,26 @@ class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
|
||||
|
||||
@override
|
||||
js_ast.Expression access(K key) {
|
||||
var id = incrementalMode ? aggregatedContainerId : containerId;
|
||||
var id = containerId;
|
||||
return js.call('#[#]', [id, moduleItems[key].jsKey]);
|
||||
}
|
||||
|
||||
@override
|
||||
List<js_ast.Statement> emit() {
|
||||
List<js_ast.Statement> emit({_emitValue<K> emitValue}) {
|
||||
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;
|
||||
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)] =
|
||||
v.jsValue;
|
||||
emitValue == null ? v.jsValue : emitValue(k, v);
|
||||
});
|
||||
|
||||
if (valueSet.isEmpty) return [];
|
||||
|
||||
if (valueSet.length == 1 && moduleItems.length > 1) {
|
||||
return [
|
||||
js.statement('var # = Array(#).fill(#)', [
|
||||
@@ -281,16 +273,4 @@ class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
List<js_ast.Statement> emitIncremental() {
|
||||
assert(incrementalMode);
|
||||
var statements = <js_ast.Statement>[];
|
||||
moduleItems.forEach((k, v) {
|
||||
if (_noEmit.contains(k)) return;
|
||||
statements.add(js
|
||||
.statement('#[#] = #', [aggregatedContainerId, v.jsKey, v.jsValue]));
|
||||
});
|
||||
return statements;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
final _symbolContainer = ModuleItemContainer<js_ast.Identifier>.asObject('S',
|
||||
keyToString: (js_ast.Identifier i) => '${i.name}');
|
||||
|
||||
ModuleItemContainer<js_ast.Identifier> get symbolContainer =>
|
||||
_symbolContainer;
|
||||
|
||||
/// Extension member symbols for adding Dart members to JS types.
|
||||
///
|
||||
/// These are added to the [extensionSymbolsModule]; see that field for more
|
||||
@@ -43,6 +46,47 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
/// Imported libraries, and the temporaries used to refer to them.
|
||||
final _imports = <Library, js_ast.TemporaryId>{};
|
||||
|
||||
/// Incremental mode for expression compilation.
|
||||
///
|
||||
/// If set to true, triggers emitting tall used ypes, symbols, libraries,
|
||||
/// constants, urs inside the generated function.
|
||||
bool _incrementalMode = false;
|
||||
|
||||
@protected
|
||||
bool get incrementalMode => _incrementalMode;
|
||||
|
||||
/// Set incremental mode for one expression compilation.
|
||||
///
|
||||
/// Sets all tables and internal structures to incremental mode so
|
||||
/// only referenced items will be emitted in a generated function.
|
||||
///
|
||||
/// Note: the compiler cannot revert to non-incremental mode.
|
||||
@protected
|
||||
void setIncrementalMode() {
|
||||
incrementalModules.clear();
|
||||
_privateNames.clear();
|
||||
symbolContainer.setIncrementalMode();
|
||||
_incrementalMode = true;
|
||||
}
|
||||
|
||||
/// Modules and libraries accessed during compilation in incremental mode.
|
||||
@protected
|
||||
final Map<String, Set<String>> incrementalModules = {};
|
||||
|
||||
@protected
|
||||
void setEmitIfIncrementalLibrary(Library library) {
|
||||
if (incrementalMode && library != null) {
|
||||
setEmitIfIncremental(libraryToModule(library), jsLibraryName(library));
|
||||
}
|
||||
}
|
||||
|
||||
@protected
|
||||
void setEmitIfIncremental(String module, String library) {
|
||||
if (incrementalMode && library != null) {
|
||||
incrementalModules.putIfAbsent(module, () => {}).add(library);
|
||||
}
|
||||
}
|
||||
|
||||
/// The identifier used to reference DDC's core "dart:_runtime" library from
|
||||
/// generated JS code, typically called "dart" e.g. `dart.dcall`.
|
||||
js_ast.Identifier runtimeModule;
|
||||
@@ -220,8 +264,10 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
/// dart.asInt(<expr>)
|
||||
///
|
||||
@protected
|
||||
js_ast.Expression runtimeCall(String code, [List<Object> args]) =>
|
||||
js.call('#.$code', <Object>[runtimeModule, ...?args]);
|
||||
js_ast.Expression runtimeCall(String code, [List<Object> args]) {
|
||||
setEmitIfIncremental(libraryToModule(coreLibrary), runtimeModule.name);
|
||||
return js.call('#.$code', <Object>[runtimeModule, ...?args]);
|
||||
}
|
||||
|
||||
/// Calls [runtimeCall] and uses `toStatement()` to convert the resulting
|
||||
/// expression into a statement.
|
||||
@@ -276,7 +322,13 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
}
|
||||
|
||||
var privateNames = _privateNames.putIfAbsent(library, () => HashMap());
|
||||
return privateNames.putIfAbsent(name, initPrivateNameSymbol);
|
||||
var symbolId = privateNames.putIfAbsent(name, initPrivateNameSymbol);
|
||||
|
||||
setEmitIfIncrementalLibrary(library);
|
||||
setEmitIfIncremental(libraryToModule(coreLibrary), runtimeModule.name);
|
||||
_symbolContainer.setEmitIfIncremental(symbolId);
|
||||
|
||||
return symbolId;
|
||||
}
|
||||
|
||||
/// Emits a private name JS Symbol for [memberName] unique to a Dart
|
||||
@@ -445,7 +497,6 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
.statement('var # = Object.create(#.library)', [id, runtimeModule]));
|
||||
exports.add(js_ast.NameSpecifier(id));
|
||||
}
|
||||
|
||||
items.add(js_ast.ExportDeclaration(js_ast.ExportClause(exports)));
|
||||
|
||||
if (isBuildingSdk) {
|
||||
@@ -469,6 +520,8 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
|
||||
/// Returns the canonical name to refer to the Dart library.
|
||||
js_ast.Identifier emitLibraryName(Library library) {
|
||||
setEmitIfIncrementalLibrary(library);
|
||||
|
||||
// Avoid adding the dart:_runtime to _imports when our runtime unit tests
|
||||
// import it explicitly. It will always be implicitly imported.
|
||||
if (isSdkInternalRuntime(library)) return runtimeModule;
|
||||
@@ -479,12 +532,10 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
library, () => js_ast.TemporaryId(jsLibraryName(library)));
|
||||
}
|
||||
|
||||
/// Emits imports and extension methods into [items].
|
||||
/// Emits imports into [items].
|
||||
@protected
|
||||
void emitImportsAndExtensionSymbols(List<js_ast.ModuleItem> items,
|
||||
{bool forceExtensionSymbols = false}) {
|
||||
void emitImports(List<js_ast.ModuleItem> items) {
|
||||
var modules = <String, List<Library>>{};
|
||||
|
||||
for (var import in _imports.keys) {
|
||||
modules.putIfAbsent(libraryToModule(import), () => []).add(import);
|
||||
}
|
||||
@@ -493,33 +544,57 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
if (!_libraries.containsKey(coreLibrary)) {
|
||||
coreModuleName = libraryToModule(coreLibrary);
|
||||
}
|
||||
|
||||
modules.forEach((module, libraries) {
|
||||
// Generate import directives.
|
||||
//
|
||||
// Our import variables are temps and can get renamed. Since our renaming
|
||||
// is integrated into js_ast, it is aware of this possibility and will
|
||||
// generate an "as" if needed. For example:
|
||||
//
|
||||
// import {foo} from 'foo'; // if no rename needed
|
||||
// import {foo as foo$} from 'foo'; // if rename was needed
|
||||
//
|
||||
var imports = libraries.map((library) {
|
||||
var alias = jsLibraryAlias(library);
|
||||
if (alias != null) {
|
||||
var aliasId = js_ast.TemporaryId(alias);
|
||||
return js_ast.NameSpecifier(aliasId, asName: _imports[library]);
|
||||
if (!incrementalMode || incrementalModules.containsKey(module)) {
|
||||
var usedLibraries = incrementalModules[module];
|
||||
|
||||
// Generate import directives.
|
||||
//
|
||||
// Our import variables are temps and can get renamed. Since our renaming
|
||||
// is integrated into js_ast, it is aware of this possibility and will
|
||||
// generate an "as" if needed. For example:
|
||||
//
|
||||
// import {foo} from 'foo'; // if no rename needed
|
||||
// import {foo as foo$} from 'foo'; // if rename was needed
|
||||
//
|
||||
var imports = <js_ast.NameSpecifier>[];
|
||||
for (var library in libraries) {
|
||||
if (!incrementalMode ||
|
||||
usedLibraries.contains(jsLibraryName(library))) {
|
||||
var alias = jsLibraryAlias(library);
|
||||
if (alias != null) {
|
||||
var aliasId = js_ast.TemporaryId(alias);
|
||||
imports.add(
|
||||
js_ast.NameSpecifier(aliasId, asName: _imports[library]));
|
||||
} else {
|
||||
imports.add(js_ast.NameSpecifier(_imports[library]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (module == coreModuleName) {
|
||||
if (!incrementalMode || usedLibraries.contains(runtimeModule.name)) {
|
||||
imports.add(js_ast.NameSpecifier(runtimeModule));
|
||||
}
|
||||
if (!incrementalMode ||
|
||||
usedLibraries.contains(extensionSymbolsModule.name)) {
|
||||
imports.add(js_ast.NameSpecifier(extensionSymbolsModule));
|
||||
}
|
||||
}
|
||||
|
||||
if (!incrementalMode || imports.isNotEmpty) {
|
||||
items.add(js_ast.ImportDeclaration(
|
||||
namedImports: imports, from: js.string(module, "'")));
|
||||
}
|
||||
return js_ast.NameSpecifier(_imports[library]);
|
||||
}).toList();
|
||||
if (module == coreModuleName) {
|
||||
imports.add(js_ast.NameSpecifier(runtimeModule));
|
||||
imports.add(js_ast.NameSpecifier(extensionSymbolsModule));
|
||||
}
|
||||
|
||||
items.add(js_ast.ImportDeclaration(
|
||||
namedImports: imports, from: js.string(module, "'")));
|
||||
});
|
||||
}
|
||||
|
||||
/// Emits extension methods into [items].
|
||||
@protected
|
||||
void emitExtensionSymbols(List<js_ast.ModuleItem> items,
|
||||
{bool forceExtensionSymbols = false}) {
|
||||
// Initialize extension symbols
|
||||
_extensionSymbols.forEach((name, id) {
|
||||
js_ast.Expression value =
|
||||
@@ -530,16 +605,84 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
value = js.call(
|
||||
'# || (# = Symbol(#))', [value, 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]));
|
||||
// Emit hoisted extension symbols that are marked as noEmit in regular as
|
||||
// well as incremental mode (if needed) since they are going to be
|
||||
// referenced as such in the generated expression.
|
||||
if (!incrementalMode ||
|
||||
_symbolContainer.incrementalModuleItems.contains(id)) {
|
||||
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]));
|
||||
}
|
||||
}
|
||||
if (_symbolContainer.incrementalModuleItems.contains(id)) {
|
||||
setEmitIfIncremental(
|
||||
libraryToModule(coreLibrary), extensionSymbolsModule.name);
|
||||
}
|
||||
_symbolContainer[id] = value;
|
||||
});
|
||||
}
|
||||
|
||||
/// Emits exports as imports into [items].
|
||||
///
|
||||
/// Use information from exports to re-define library variables referenced
|
||||
/// inside compiled expressions in incremental mode. That matches importing
|
||||
/// a current module into the symbol used to represent the library during
|
||||
/// original compilation in [ProgramCompiler.emitModule].
|
||||
///
|
||||
/// Example of exports emitted to JavaScript during emitModule:
|
||||
///
|
||||
/// ```
|
||||
/// dart.trackLibraries("web/main", { ... });
|
||||
/// // Exports:
|
||||
/// return {
|
||||
/// web__main: main
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// The transformation to imports during expression compilation converts the
|
||||
/// exports above to:
|
||||
///
|
||||
/// ```
|
||||
/// const web__main = require('web/main');
|
||||
/// const main = web__main.web__main;
|
||||
/// ```
|
||||
///
|
||||
/// Where the compiled expression references `main`.
|
||||
@protected
|
||||
void emitExportsAsImports(List<js_ast.ModuleItem> items, Library current) {
|
||||
var exports = <js_ast.NameSpecifier>[];
|
||||
assert(incrementalMode);
|
||||
assert(!isBuildingSdk);
|
||||
|
||||
var module = libraryToModule(current);
|
||||
var usedLibraries = incrementalModules[module] ?? {};
|
||||
|
||||
if (usedLibraries.isNotEmpty) {
|
||||
_libraries.forEach((library, libraryId) {
|
||||
if (usedLibraries.contains(jsLibraryName(library))) {
|
||||
var alias = jsLibraryAlias(library);
|
||||
var aliasId = alias == null ? libraryId : js_ast.TemporaryId(alias);
|
||||
var asName = alias == null ? null : libraryId;
|
||||
exports.add(js_ast.NameSpecifier(aliasId, asName: asName));
|
||||
}
|
||||
});
|
||||
|
||||
items.add(js_ast.ImportDeclaration(
|
||||
namedImports: exports, from: js.string(module, "'")));
|
||||
}
|
||||
}
|
||||
|
||||
/// Emits imports and extension methods into [items].
|
||||
@protected
|
||||
void emitImportsAndExtensionSymbols(List<js_ast.ModuleItem> items,
|
||||
{bool forceExtensionSymbols = false}) {
|
||||
emitImports(items);
|
||||
emitExtensionSymbols(items, forceExtensionSymbols: forceExtensionSymbols);
|
||||
}
|
||||
|
||||
void _emitDebuggerExtensionInfo(String name) {
|
||||
var properties = <js_ast.Property>[];
|
||||
var parts = <js_ast.Property>[];
|
||||
@@ -568,27 +711,26 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
///
|
||||
/// A symbol lookup on an id marked no emit omits the symbol accessor.
|
||||
js_ast.Expression getSymbol(js_ast.Identifier id) {
|
||||
_symbolContainer.setEmitIfIncremental(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) {
|
||||
_symbolContainer.setEmitIfIncremental(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;
|
||||
_symbolContainer.setEmitIfIncremental(id);
|
||||
if (!containerizeSymbols) {
|
||||
_symbolContainer.setNoEmit(id);
|
||||
}
|
||||
return _symbolContainer[id];
|
||||
}
|
||||
|
||||
void setSymbolContainerIncrementalMode(bool setting) {
|
||||
_symbolContainer.incrementalMode = setting;
|
||||
}
|
||||
|
||||
/// Finishes the module created by [startModule], by combining the preable
|
||||
/// [items] with the [moduleItems] that have been emitted.
|
||||
///
|
||||
@@ -646,7 +788,9 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
|
||||
_extensionSymbols[name] = id;
|
||||
addSymbol(id, id);
|
||||
}
|
||||
return _extensionSymbols[name];
|
||||
var symbolId = _extensionSymbols[name];
|
||||
_symbolContainer.setEmitIfIncremental(symbolId);
|
||||
return symbolId;
|
||||
}
|
||||
|
||||
/// Shorthand for identifier-like property names.
|
||||
|
||||
@@ -26,7 +26,7 @@ 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;
|
||||
import '../js_ast/js_ast.dart' show js;
|
||||
import '../js_ast/js_ast.dart' show ModuleItem, js;
|
||||
import '../js_ast/source_map_printer.dart' show NodeEnd, NodeSpan, HoverComment;
|
||||
import 'constants.dart';
|
||||
import 'js_interop.dart';
|
||||
@@ -77,7 +77,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
final _constLazyAccessors = <js_ast.Method>[];
|
||||
|
||||
/// Container for holding the results of lazily-evaluated constants.
|
||||
final _constTableCache = ModuleItemContainer<String>.asArray('C');
|
||||
var _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.
|
||||
@@ -221,7 +221,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
final constAliasCache = HashMap<Constant, js_ast.Expression>();
|
||||
|
||||
/// Maps uri strings in asserts and elsewhere to hoisted identifiers.
|
||||
final _uriContainer = ModuleItemContainer<String>.asArray('I');
|
||||
var _uriContainer = ModuleItemContainer<String>.asArray('I');
|
||||
|
||||
final Class _jsArrayClass;
|
||||
final Class _privateSymbolClass;
|
||||
@@ -242,6 +242,8 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
|
||||
final NullableInference _nullableInference;
|
||||
|
||||
bool _moduleEmitted = false;
|
||||
|
||||
factory ProgramCompiler(
|
||||
Component component,
|
||||
ClassHierarchy hierarchy,
|
||||
@@ -320,8 +322,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
InterfaceType get internalSymbolType =>
|
||||
_coreTypes.legacyRawType(_coreTypes.internalSymbolClass);
|
||||
|
||||
/// Module can be emitted only once, and the compiler can be reused after
|
||||
/// only in incremental mode, for expression compilation only.
|
||||
js_ast.Program emitModule(Component component) {
|
||||
if (moduleItems.isNotEmpty) {
|
||||
if (_moduleEmitted) {
|
||||
throw StateError('Can only call emitModule once.');
|
||||
}
|
||||
_component = component;
|
||||
@@ -443,7 +447,12 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
// Emit the hoisted type table cache variables
|
||||
items.addAll(_typeTable.dischargeBoundTypes());
|
||||
|
||||
return finishModule(items, _options.moduleName);
|
||||
var module = finishModule(items, _options.moduleName);
|
||||
|
||||
// Mark as finished for incremental mode, so it is safe to
|
||||
// switch to the incremental mode for expression compilation.
|
||||
_moduleEmitted = true;
|
||||
return module;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -2530,8 +2539,14 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
return libraryJSName != null ? '$libraryJSName.$jsName' : jsName;
|
||||
}
|
||||
|
||||
String _emitJsNameWithoutGlobal(NamedNode n) {
|
||||
if (!usesJSInterop(n)) return null;
|
||||
setEmitIfIncrementalLibrary(getLibrary(n));
|
||||
return _jsNameWithoutGlobal(n);
|
||||
}
|
||||
|
||||
js_ast.PropertyAccess _emitJSInterop(NamedNode n) {
|
||||
var jsName = _jsNameWithoutGlobal(n);
|
||||
var jsName = _emitJsNameWithoutGlobal(n);
|
||||
if (jsName == null) return null;
|
||||
return _emitJSInteropForGlobal(jsName);
|
||||
}
|
||||
@@ -2762,7 +2777,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
typeRep = runtimeCall(
|
||||
'anonymousJSType(#)', [js.escapedString(getLocalClassName(c))]);
|
||||
} else {
|
||||
var jsName = _jsNameWithoutGlobal(c);
|
||||
var jsName = _emitJsNameWithoutGlobal(c);
|
||||
if (jsName != null) {
|
||||
typeRep = runtimeCall('lazyJSType(() => #, #)',
|
||||
[_emitJSInteropForGlobal(jsName), js.escapedString(jsName)]);
|
||||
@@ -3072,6 +3087,30 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
js_ast.Expression visitTypedefType(TypedefType type) =>
|
||||
visitFunctionType(type.unalias as FunctionType);
|
||||
|
||||
/// Set incremental mode for expression compilation.
|
||||
///
|
||||
/// Called for each expression compilation to set the intremental mode
|
||||
/// and clear referenced items.
|
||||
///
|
||||
/// The compiler cannot revert to non-incremental mode, and requires the
|
||||
/// original module to be already emitted by the same compiler instance.
|
||||
@override
|
||||
void setIncrementalMode() {
|
||||
if (!_moduleEmitted) {
|
||||
throw StateError(
|
||||
'Cannot run in incremental mode before module completion');
|
||||
}
|
||||
super.setIncrementalMode();
|
||||
|
||||
_constTableCache = ModuleItemContainer<String>.asArray('C');
|
||||
_constLazyAccessors.clear();
|
||||
constAliasCache.clear();
|
||||
|
||||
_uriContainer = ModuleItemContainer<String>.asArray('I');
|
||||
|
||||
_typeTable.typeContainer.setIncrementalMode();
|
||||
}
|
||||
|
||||
/// Emits function after initial compilation.
|
||||
///
|
||||
/// Emits function from kernel [functionNode] with name [name] in the context
|
||||
@@ -3079,68 +3118,67 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
/// finished. For example, this happens in expression compilation during
|
||||
/// expression evaluation initiated by the user from the IDE and coordinated
|
||||
/// by the debugger.
|
||||
js_ast.Fun emitFunctionIncremental(
|
||||
Library library, Class cls, FunctionNode functionNode, String name) {
|
||||
// setup context
|
||||
/// Triggers incremental mode, which only emits symbols, types, constants,
|
||||
/// libraries, and uris referenced in the expression compilation result.
|
||||
js_ast.Fun emitFunctionIncremental(List<ModuleItem> items, Library library,
|
||||
Class cls, FunctionNode functionNode, String name) {
|
||||
// Setup context.
|
||||
_currentLibrary = library;
|
||||
_staticTypeContext.enterLibrary(_currentLibrary);
|
||||
_currentClass = cls;
|
||||
|
||||
// Keep all symbols in containers.
|
||||
containerizeSymbols = true;
|
||||
|
||||
// Set all tables to incremental mode, so we can only emit elements that
|
||||
// were referenced the compiled code for the expression.
|
||||
setIncrementalMode();
|
||||
|
||||
// Do not add formal parameter checks for the top-level synthetic function
|
||||
// generated for expression evaluation, as those parameters are a set of
|
||||
// variables from the current scope, and should alredy be checked in the
|
||||
// variables from the current scope, and should already be checked in the
|
||||
// original code.
|
||||
_checkParameters = false;
|
||||
|
||||
// Set module item containers to incremental mode.
|
||||
setSymbolContainerIncrementalMode(true);
|
||||
_typeTable.typeContainer.incrementalMode = true;
|
||||
_constTableCache.incrementalMode = true;
|
||||
|
||||
// Emit function with additional information, such as types that are used
|
||||
// in the expression.
|
||||
// Emit function while recoding elements accessed from tables.
|
||||
var fun = _emitFunction(functionNode, name);
|
||||
|
||||
var types = _typeTable.dischargeBoundTypes();
|
||||
var constants = _dischargeConstTable();
|
||||
var extensionSymbols = <js_ast.Statement>[];
|
||||
emitExtensionSymbols(extensionSymbols);
|
||||
|
||||
// Add all elements from tables accessed in the function
|
||||
var body = js_ast.Block([
|
||||
...extensionSymbols,
|
||||
..._typeTable.dischargeBoundTypes(),
|
||||
...symbolContainer.emit(),
|
||||
..._emitConstTable(),
|
||||
..._uriContainer.emit(),
|
||||
...fun.body.statements
|
||||
]);
|
||||
|
||||
// Import all necessary libraries, including libraries accessed from the
|
||||
// current module and libraries accessed from the type table.
|
||||
for (var library in _typeTable.incrementalLibraries()) {
|
||||
setEmitIfIncrementalLibrary(library);
|
||||
}
|
||||
emitImports(items);
|
||||
emitExportsAsImports(items, _currentLibrary);
|
||||
|
||||
var body = js_ast.Block([...?types, ...?constants, ...fun.body.statements]);
|
||||
return js_ast.Fun(fun.params, body);
|
||||
}
|
||||
|
||||
/// Emit all collected const symbols
|
||||
///
|
||||
/// This is similar to how constants are emitted during
|
||||
/// initial compilation in emitModule
|
||||
///
|
||||
/// TODO: unify the code with emitModule.
|
||||
List<js_ast.Statement> _dischargeConstTable() {
|
||||
var items = <js_ast.Statement>[];
|
||||
|
||||
List<js_ast.Statement> _emitConstTable() {
|
||||
var constTable = <js_ast.Statement>[];
|
||||
if (_constLazyAccessors.isNotEmpty) {
|
||||
var constTableBody = runtimeStatement(
|
||||
'defineLazy(#, { # }, false)', [_constTable, _constLazyAccessors]);
|
||||
items.add(constTableBody);
|
||||
_constLazyAccessors.clear();
|
||||
}
|
||||
constTable
|
||||
.add(js.statement('const # = Object.create(null);', [_constTable]));
|
||||
|
||||
_copyAndFlattenBlocks(items, moduleItems);
|
||||
moduleItems.clear();
|
||||
return items;
|
||||
}
|
||||
constTable.add(runtimeStatement(
|
||||
'defineLazy(#, { # }, false)', [_constTable, _constLazyAccessors]));
|
||||
|
||||
/// Flattens blocks in [items] to a single list.
|
||||
///
|
||||
/// This will not flatten blocks that are marked as being scopes.
|
||||
void _copyAndFlattenBlocks(
|
||||
List<js_ast.Statement> result, Iterable<js_ast.ModuleItem> items) {
|
||||
for (var item in items) {
|
||||
if (item is js_ast.Block && !item.isScope) {
|
||||
_copyAndFlattenBlocks(result, item.statements);
|
||||
} else {
|
||||
result.add(item as js_ast.Statement);
|
||||
}
|
||||
constTable.addAll(_constTableCache.emit());
|
||||
}
|
||||
return constTable;
|
||||
}
|
||||
|
||||
js_ast.Fun _emitFunction(FunctionNode f, String name) {
|
||||
@@ -3729,6 +3767,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
|
||||
if (!_uriContainer.contains(uri)) {
|
||||
_uriContainer[uri] = js_ast.LiteralString('"$uri"');
|
||||
}
|
||||
_uriContainer.setEmitIfIncremental(uri);
|
||||
return _uriContainer.access(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:_fe_analyzer_shared/src/messages/codes.dart'
|
||||
show Code, Message, PlainAndColorizedString;
|
||||
|
||||
import 'package:dev_compiler/dev_compiler.dart';
|
||||
import 'package:dev_compiler/src/compiler/js_names.dart' as js_ast;
|
||||
import 'package:dev_compiler/src/compiler/module_builder.dart';
|
||||
import 'package:dev_compiler/src/js_ast/js_ast.dart' as js_ast;
|
||||
import 'package:dev_compiler/src/kernel/compiler.dart';
|
||||
|
||||
@@ -31,8 +33,6 @@ import 'package:kernel/ast.dart'
|
||||
Member,
|
||||
Node,
|
||||
Procedure,
|
||||
PropertyGet,
|
||||
PropertySet,
|
||||
RedirectingFactoryConstructor,
|
||||
TreeNode,
|
||||
TypeParameter,
|
||||
@@ -114,7 +114,7 @@ class DartScopeBuilder extends Visitor<void> with VisitorVoidMixin {
|
||||
}
|
||||
|
||||
DartScope build() {
|
||||
if (_offset == null || _library == null || _member == null) return null;
|
||||
if (_offset == null || _library == null) return null;
|
||||
|
||||
return DartScope(_library, _cls, _member, _definitions, _typeParameters);
|
||||
}
|
||||
@@ -127,7 +127,10 @@ class DartScopeBuilder extends Visitor<void> with VisitorVoidMixin {
|
||||
@override
|
||||
void visitLibrary(Library library) {
|
||||
_library = library;
|
||||
_offset = _component.getOffset(_library.fileUri, _line, _column);
|
||||
_offset = 0;
|
||||
if (_line > 0) {
|
||||
_offset = _component.getOffset(_library.fileUri, _line, _column);
|
||||
}
|
||||
|
||||
// Exit early if the evaluation offset is not found.
|
||||
// Note: the complete scope is not found in this case,
|
||||
@@ -261,53 +264,6 @@ class FileEndOffsetCalculator extends Visitor<int> with VisitorNullMixin<int> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect private fields and libraries used in expression.
|
||||
///
|
||||
/// Used during expression evaluation to find symbols
|
||||
/// for private fields. The symbols are used in the ddc
|
||||
/// compilation of the expression, are not always avalable
|
||||
/// in the JavaScript scope, so we need to redefine them.
|
||||
///
|
||||
/// See [_addSymbolDefinitions]
|
||||
class PrivateFieldsVisitor extends Visitor<void> with VisitorVoidMixin {
|
||||
final Map<String, Library> privateFields = {};
|
||||
|
||||
@override
|
||||
void defaultNode(Node node) {
|
||||
node.visitChildren(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFieldReference(Field node) {
|
||||
if (node.name.isPrivate && !node.isStatic) {
|
||||
privateFields[node.name.text] = node.enclosingLibrary;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitField(Field node) {
|
||||
if (node.name.isPrivate && !node.isStatic) {
|
||||
privateFields[node.name.text] = node.enclosingLibrary;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPropertyGet(PropertyGet node) {
|
||||
var member = node.interfaceTarget;
|
||||
if (node.name.isPrivate && member != null && member.isInstanceMember) {
|
||||
privateFields[node.name.text] = node.interfaceTarget?.enclosingLibrary;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitPropertySet(PropertySet node) {
|
||||
var member = node.interfaceTarget;
|
||||
if (node.name.isPrivate && member != null && member.isInstanceMember) {
|
||||
privateFields[node.name.text] = node.interfaceTarget?.enclosingLibrary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionCompiler {
|
||||
static final String debugProcedureName = '\$dartEval';
|
||||
|
||||
@@ -317,6 +273,7 @@ class ExpressionCompiler {
|
||||
final IncrementalCompiler _compiler;
|
||||
final ProgramCompiler _kernel2jsCompiler;
|
||||
final Component _component;
|
||||
final ModuleFormat _moduleFormat;
|
||||
|
||||
DiagnosticMessageHandler onDiagnostic;
|
||||
|
||||
@@ -328,6 +285,7 @@ class ExpressionCompiler {
|
||||
|
||||
ExpressionCompiler(
|
||||
this._options,
|
||||
this._moduleFormat,
|
||||
this.errors,
|
||||
this._compiler,
|
||||
this._kernel2jsCompiler,
|
||||
@@ -494,81 +452,27 @@ class ExpressionCompiler {
|
||||
|
||||
// TODO: make this code clear and assumptions enforceable
|
||||
// https://github.com/dart-lang/sdk/issues/43273
|
||||
//
|
||||
// We assume here that ExpressionCompiler is always created using
|
||||
// onDisgnostic method that adds to the error list that is passed
|
||||
// to the same invocation of the ExpressionCompiler constructor.
|
||||
// We only use the error list once - below, to detect if the frontend
|
||||
// compilation of the expression has failed.
|
||||
if (errors.isNotEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var jsFun = _kernel2jsCompiler.emitFunctionIncremental(
|
||||
var imports = <js_ast.ModuleItem>[];
|
||||
var jsFun = _kernel2jsCompiler.emitFunctionIncremental(imports,
|
||||
scope.library, scope.cls, procedure.function, '$debugProcedureName');
|
||||
|
||||
_log('Generated JavaScript for expression');
|
||||
|
||||
var jsFunModified = _addSymbolDefinitions(procedure, jsFun, scope);
|
||||
|
||||
_log('Added symbol definitions to JavaScript');
|
||||
|
||||
// print JS ast to string for evaluation
|
||||
|
||||
var context = js_ast.SimpleJavaScriptPrintingContext();
|
||||
var opts =
|
||||
js_ast.JavaScriptPrintingOptions(allowKeywordsInProperties: true);
|
||||
|
||||
jsFunModified.accept(js_ast.Printer(opts, context));
|
||||
_log('Performed JavaScript adjustments for expression');
|
||||
var tree = transformFunctionModuleFormat(imports, jsFun, _moduleFormat);
|
||||
tree.accept(
|
||||
js_ast.Printer(opts, context, localNamer: js_ast.TemporaryNamer(tree)));
|
||||
|
||||
_log('Added imports and renamed variables for expression');
|
||||
|
||||
return context.getText();
|
||||
}
|
||||
|
||||
/// Add symbol definitions for all symbols in compiled expression
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// compilation of this._field from library 'main'
|
||||
///
|
||||
/// Symbol definition:
|
||||
///
|
||||
/// let _f = dart.privateName(main, "_f");
|
||||
///
|
||||
/// Expression generated by ddc:
|
||||
///
|
||||
/// this[_f]
|
||||
///
|
||||
/// TODO: this is a temporary workaround to make JavaScript produced
|
||||
/// by the ProgramCompiler self-contained.
|
||||
/// Issue: https://github.com/dart-lang/sdk/issues/41480
|
||||
js_ast.Fun _addSymbolDefinitions(
|
||||
Procedure procedure, js_ast.Fun jsFun, DartScope scope) {
|
||||
// get private fields accessed by the evaluated expression
|
||||
var fieldsCollector = PrivateFieldsVisitor();
|
||||
procedure.accept(fieldsCollector);
|
||||
var privateFields = fieldsCollector.privateFields;
|
||||
|
||||
// collect library names where private symbols are defined
|
||||
var libraryForField = privateFields.map((field, library) =>
|
||||
MapEntry(field, _kernel2jsCompiler.emitLibraryName(library).name));
|
||||
|
||||
var body = js_ast.Block([
|
||||
// re-create private field accessors
|
||||
...libraryForField.keys.map(
|
||||
(String field) => _createPrivateField(field, libraryForField[field])),
|
||||
// statements generated by the FE
|
||||
...jsFun.body.statements
|
||||
]);
|
||||
return js_ast.Fun(jsFun.params, body);
|
||||
}
|
||||
|
||||
/// Creates a private symbol definition
|
||||
///
|
||||
/// example:
|
||||
/// let _f = dart.privateName(main, "_f");
|
||||
js_ast.Statement _createPrivateField(String field, String library) {
|
||||
return js_ast.js.statement('let # = dart.privateName(#, #)',
|
||||
[field, library, js_ast.js.string(field)]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:build_integration/file_system/multi_root.dart';
|
||||
import 'package:dev_compiler/dev_compiler.dart';
|
||||
import 'package:dev_compiler/src/compiler/module_builder.dart';
|
||||
import 'package:front_end/src/api_prototype/file_system.dart';
|
||||
import 'package:front_end/src/api_unstable/ddc.dart';
|
||||
import 'package:kernel/ast.dart' show Component, Library;
|
||||
@@ -82,11 +84,13 @@ class ExpressionCompilerWorker {
|
||||
|
||||
final ProcessedOptions _processedOptions;
|
||||
final CompilerOptions _compilerOptions;
|
||||
final ModuleFormat _moduleFormat;
|
||||
final Component _sdkComponent;
|
||||
|
||||
ExpressionCompilerWorker._(
|
||||
this._processedOptions,
|
||||
this._compilerOptions,
|
||||
this._moduleFormat,
|
||||
this._sdkComponent,
|
||||
this.requestStream,
|
||||
this.sendResponse,
|
||||
@@ -120,6 +124,9 @@ class ExpressionCompilerWorker {
|
||||
parseExperimentalArguments(
|
||||
parsedArgs['enable-experiment'] as List<String>),
|
||||
onError: (e) => throw e);
|
||||
|
||||
var moduleFormat = parseModuleFormat(parsedArgs['module-format'] as String);
|
||||
|
||||
return create(
|
||||
librariesSpecificationUri:
|
||||
_argToUri(parsedArgs['libraries-file'] as String),
|
||||
@@ -131,6 +138,7 @@ class ExpressionCompilerWorker {
|
||||
sdkRoot: _argToUri(parsedArgs['sdk-root'] as String),
|
||||
trackWidgetCreation: parsedArgs['track-widget-creation'] as bool,
|
||||
soundNullSafety: parsedArgs['sound-null-safety'] as bool,
|
||||
moduleFormat: moduleFormat,
|
||||
verbose: parsedArgs['verbose'] as bool,
|
||||
requestStream: requestStream,
|
||||
sendResponse: sendResponse,
|
||||
@@ -152,6 +160,7 @@ class ExpressionCompilerWorker {
|
||||
Uri sdkRoot,
|
||||
bool trackWidgetCreation = false,
|
||||
bool soundNullSafety = false,
|
||||
ModuleFormat moduleFormat = ModuleFormat.amd,
|
||||
bool verbose = false,
|
||||
Stream<Map<String, dynamic>> requestStream, // Defaults to read from stdin
|
||||
void Function(Map<String, dynamic>)
|
||||
@@ -185,7 +194,7 @@ class ExpressionCompilerWorker {
|
||||
});
|
||||
|
||||
return ExpressionCompilerWorker._(processedOptions, compilerOptions,
|
||||
sdkComponent, requestStream, sendResponse)
|
||||
moduleFormat, sdkComponent, requestStream, sendResponse)
|
||||
.._updateCache(sdkComponent, dartSdkModule, true);
|
||||
}
|
||||
|
||||
@@ -309,6 +318,7 @@ class ExpressionCompilerWorker {
|
||||
sourceMap: true,
|
||||
summarizeApi: false,
|
||||
moduleName: moduleName,
|
||||
soundNullSafety: _compilerOptions.nnbdMode == NnbdMode.Strong,
|
||||
// Disable asserts due to failures to load source and
|
||||
// locations on kernel loaded from dill files in DDC.
|
||||
// https://github.com/dart-lang/sdk/issues/43986
|
||||
@@ -342,6 +352,7 @@ class ExpressionCompilerWorker {
|
||||
|
||||
var expressionCompiler = ExpressionCompiler(
|
||||
_compilerOptions,
|
||||
_moduleFormat,
|
||||
errors,
|
||||
incrementalCompiler,
|
||||
kernel2jsCompiler,
|
||||
@@ -673,6 +684,7 @@ final argParser = ArgParser()
|
||||
..addOption('sdk-root')
|
||||
..addOption('asset-server-address')
|
||||
..addOption('asset-server-port')
|
||||
..addOption('module-format')
|
||||
..addFlag('track-widget-creation', defaultsTo: false)
|
||||
..addFlag('sound-null-safety', defaultsTo: false)
|
||||
..addFlag('verbose', defaultsTo: false);
|
||||
|
||||
@@ -9,7 +9,8 @@ import 'dart:collection';
|
||||
import 'package:kernel/kernel.dart';
|
||||
|
||||
import '../compiler/js_names.dart' as js_ast;
|
||||
import '../compiler/module_containers.dart' show ModuleItemContainer;
|
||||
import '../compiler/module_containers.dart'
|
||||
show ModuleItemContainer, ModuleItemData;
|
||||
import '../js_ast/js_ast.dart' as js_ast;
|
||||
import '../js_ast/js_ast.dart' show js;
|
||||
import 'kernel_helpers.dart';
|
||||
@@ -119,16 +120,26 @@ class TypeTable {
|
||||
bool _isNamed(DartType type) =>
|
||||
typeContainer.contains(type) || _unboundTypeIds.containsKey(type);
|
||||
|
||||
Set<Library> incrementalLibraries() {
|
||||
var libraries = <Library>{};
|
||||
for (var t in typeContainer.incrementalModuleItems) {
|
||||
if (t is InterfaceType) {
|
||||
libraries.add(t.classNode.enclosingLibrary);
|
||||
}
|
||||
}
|
||||
return libraries;
|
||||
}
|
||||
|
||||
/// 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]]);
|
||||
js_ast.Expression emitValue(DartType t, ModuleItemData data) {
|
||||
var access = js.call('#.#', [data.id, data.jsKey]);
|
||||
return js.call('() => ((# = #.constFn(#))())',
|
||||
[access, _runtimeModule, data.jsValue]);
|
||||
}
|
||||
var boundTypes = typeContainer.incrementalMode
|
||||
? typeContainer.emitIncremental()
|
||||
: typeContainer.emit();
|
||||
|
||||
var boundTypes = typeContainer.emit(emitValue: emitValue);
|
||||
// Bound types should only be emitted once (even across multiple evals).
|
||||
for (var t in typeContainer.keys) {
|
||||
typeContainer.setNoEmit(t);
|
||||
@@ -172,6 +183,7 @@ class TypeTable {
|
||||
if (!typeContainer.contains(type)) {
|
||||
typeContainer[type] = typeRep;
|
||||
}
|
||||
typeContainer.setEmitIfIncremental(type);
|
||||
return _unboundTypeIds[type] ?? typeContainer.access(type);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ import 'dart:io' hide FileSystemEntity;
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:dev_compiler/dev_compiler.dart'
|
||||
show DevCompilerTarget, ExpressionCompiler;
|
||||
show DevCompilerTarget, ExpressionCompiler, parseModuleFormat;
|
||||
|
||||
// front_end/src imports below that require lint `ignore_for_file`
|
||||
// are a temporary state of things until frontend team builds better api
|
||||
@@ -605,6 +605,7 @@ class FrontendCompiler implements CompilerInterface {
|
||||
String filename, String fileSystemScheme, String moduleFormat) async {
|
||||
var packageConfig = await loadPackageConfigUri(
|
||||
_compilerOptions.packagesFileUri ?? File('.packages').absolute.uri);
|
||||
var soundNullSafety = _compilerOptions.nnbdMode == NnbdMode.Strong;
|
||||
final Component component = results.component;
|
||||
// Compute strongly connected components.
|
||||
final strongComponents = StrongComponents(component,
|
||||
@@ -623,7 +624,8 @@ class FrontendCompiler implements CompilerInterface {
|
||||
component, strongComponents, fileSystemScheme, packageConfig,
|
||||
useDebuggerModuleNames: useDebuggerModuleNames,
|
||||
emitDebugMetadata: emitDebugMetadata,
|
||||
moduleFormat: moduleFormat);
|
||||
moduleFormat: moduleFormat,
|
||||
soundNullSafety: soundNullSafety);
|
||||
final sourceFileSink = sourceFile.openWrite();
|
||||
final manifestFileSink = manifestFile.openWrite();
|
||||
final sourceMapsFileSink = sourceMapsFile.openWrite();
|
||||
@@ -839,6 +841,7 @@ class FrontendCompiler implements CompilerInterface {
|
||||
|
||||
var expressionCompiler = new ExpressionCompiler(
|
||||
_compilerOptions,
|
||||
parseModuleFormat(_options['dartdevc-module-format'] as String),
|
||||
errors,
|
||||
_generator.generator,
|
||||
kernel2jsCompiler,
|
||||
|
||||
@@ -27,6 +27,7 @@ class JavaScriptBundler {
|
||||
this._fileSystemScheme, this._packageConfig,
|
||||
{this.useDebuggerModuleNames = false,
|
||||
this.emitDebugMetadata = false,
|
||||
this.soundNullSafety = false,
|
||||
String moduleFormat})
|
||||
: compilers = <String, ProgramCompiler>{},
|
||||
_moduleFormat = parseModuleFormat(moduleFormat ?? 'amd') {
|
||||
@@ -65,6 +66,7 @@ class JavaScriptBundler {
|
||||
final bool emitDebugMetadata;
|
||||
final Map<String, ProgramCompiler> compilers;
|
||||
final ModuleFormat _moduleFormat;
|
||||
final bool soundNullSafety;
|
||||
|
||||
List<Component> _summaries;
|
||||
List<Uri> _summaryUris;
|
||||
@@ -135,6 +137,7 @@ class JavaScriptBundler {
|
||||
summarizeApi: false,
|
||||
emitDebugMetadata: emitDebugMetadata,
|
||||
moduleName: moduleName,
|
||||
soundNullSafety: soundNullSafety,
|
||||
),
|
||||
importToSummary,
|
||||
summaryToModule,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2019, 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.
|
||||
|
||||
// Tests that evaluation containers aren't renamed by DDC.
|
||||
|
||||
import 'dart:_foreign_helper' as helper show JS;
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
class T {
|
||||
final int T$Eval = 0;
|
||||
final int S$Eval = 0;
|
||||
String get realT$Eval => helper.JS<String>('', 'T\$Eval.toString()');
|
||||
String get realS$Eval => helper.JS<String>('', 'S\$Eval.toString()');
|
||||
}
|
||||
|
||||
class T$Eval {}
|
||||
|
||||
void main() {
|
||||
var T$Eval = T();
|
||||
var S$Eval = T$Eval;
|
||||
|
||||
var container1 = helper.JS<String>('', 'T\$Eval.toString()');
|
||||
var container2 = helper.JS<String>('', 'S\$Eval.toString()');
|
||||
|
||||
// Evaluation containers are JS Objects. Ensure they aren't shadowed by JS
|
||||
// symbols or Dart constructs.
|
||||
Expect.equals('[object Object]', '$container1');
|
||||
Expect.equals('[object Object]', '$container2');
|
||||
|
||||
Expect.equals("Instance of 'T'", T$Eval.toString());
|
||||
Expect.equals(T$Eval.T$Eval, 0);
|
||||
Expect.equals(T$Eval.S$Eval, 0);
|
||||
Expect.notEquals(T$Eval.toString(), container1);
|
||||
Expect.equals(T$Eval.realT$Eval, container1);
|
||||
Expect.equals(T$Eval.realS$Eval, container2);
|
||||
}
|
||||
Reference in New Issue
Block a user