[dartdevc] Creating container entrypoints for expression eval.

The expression evaluator should now index/read object containers (types and symbols right now) via accessors like 'A$Eval'.

Fixes #44494

Change-Id: I31d203cfe59a6e2c03eeb22e8c86270c28b5e2fa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/176923
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
This commit is contained in:
Mark Zhou 2021-01-06 00:20:29 +00:00 committed by commit-bot@chromium.org
parent daba9f3626
commit bd30532d82
6 changed files with 100 additions and 26 deletions

View file

@ -39,15 +39,34 @@ 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 = {};
/// Holds keys that will not be emitted when calling [emit].
final Set<K> _noEmit = {};
ModuleItemContainer._(this.name, this.containerId);
/// 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)
: this.aggregatedContainerId = aggregatedId ?? containerId;
/// Creates an automatically sharding container backed by JS Objects.
factory ModuleItemContainer.asObject(String name,
@ -95,7 +114,7 @@ abstract class ModuleItemContainer<K> {
/// Emit the container declaration/initializer incrementally.
///
/// Used during expression evaluation. Appends all newly added types to the
/// most recent container.
/// aggregated container.
List<js_ast.Statement> emitIncremental();
}
@ -123,7 +142,8 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
String Function(K) keyToString;
ModuleItemObjectContainer(String name, this.keyToString)
: super._(name, js_ast.TemporaryId(name));
: super._(
name, js_ast.TemporaryId(name), js_ast.Identifier('${name}\$Eval'));
@override
void operator []=(K key, js_ast.Expression value) {
@ -150,7 +170,8 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
@override
js_ast.Expression access(K key) {
return js.call('#.#', [moduleItems[key].id, moduleItems[key].jsKey]);
var id = incrementalMode ? aggregatedContainerId : moduleItems[key].id;
return js.call('#.#', [id, moduleItems[key].jsKey]);
}
@override
@ -173,23 +194,29 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
];
}
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 # = Object.create(#)', [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('#[#] = #', [containerId, v.jsKey, v.jsValue]));
statements.add(js
.statement('#[#] = #', [aggregatedContainerId, v.jsKey, v.jsValue]));
});
return statements;
}
@ -206,7 +233,7 @@ class ModuleItemObjectContainer<K> extends ModuleItemContainer<K> {
/// ```
class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
ModuleItemArrayContainer(String name)
: super._(name, js_ast.TemporaryId(name));
: super._(name, js_ast.TemporaryId(name), null);
@override
void operator []=(K key, js_ast.Expression value) {
@ -220,7 +247,8 @@ class ModuleItemArrayContainer<K> extends ModuleItemContainer<K> {
@override
js_ast.Expression access(K key) {
return js.call('#[#]', [containerId, moduleItems[key].jsKey]);
var id = incrementalMode ? aggregatedContainerId : containerId;
return js.call('#[#]', [id, moduleItems[key].jsKey]);
}
@override
@ -257,11 +285,12 @@ 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('#[#] = #', [containerId, v.jsKey, v.jsValue]));
statements.add(js
.statement('#[#] = #', [aggregatedContainerId, v.jsKey, v.jsValue]));
});
return statements;
}

View file

@ -581,6 +581,10 @@ abstract class SharedCompiler<Library, Class, InterfaceType, FunctionNode> {
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.
///

View file

@ -70,7 +70,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
/// Let variables collected for the given function.
List<js_ast.TemporaryId> _letVariables;
final _constTable = js_ast.TemporaryId('CT');
final _constTable = js_ast.Identifier('CT');
/// Constant getters used to populate the constant table.
final _constLazyAccessors = <js_ast.Method>[];
@ -3088,6 +3088,10 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// original code.
_checkParameters = false;
// Set module item containers to incremental mode.
setSymbolContainerIncrementalMode(true);
_typeTable.typeContainer.incrementalMode = true;
// Emit function with additional information, such as types that are used
// in the expression. Note that typeTable can be null if this function is
// called from the expression compilation service, since we currently do
@ -3097,7 +3101,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
// Issue: https://github.com/dart-lang/sdk/issues/43288
var fun = _emitFunction(functionNode, name);
var types = _typeTable?.dischargeBoundTypes(incremental: true);
var types = _typeTable?.dischargeBoundTypes();
var constants = _dischargeConstTable();
var body = js_ast.Block([...?types, ...?constants, ...fun.body.statements]);

View file

@ -122,15 +122,14 @@ class TypeTable {
/// Emit the initializer statements for the type container, which contains
/// all named types with fully bound type parameters.
///
/// [incremental] is only used for expression evaluation.
List<js_ast.Statement> dischargeBoundTypes({bool incremental = false}) {
List<js_ast.Statement> dischargeBoundTypes() {
for (var t in typeContainer.keys) {
typeContainer[t] = js.call('() => ((# = #.constFn(#))())',
[typeContainer.access(t), _runtimeModule, typeContainer[t]]);
}
var boundTypes =
incremental ? typeContainer.emitIncremental() : typeContainer.emit();
var boundTypes = typeContainer.incrementalMode
? typeContainer.emitIncremental()
: typeContainer.emit();
// Bound types should only be emitted once (even across multiple evals).
for (var t in typeContainer.keys) {
typeContainer.setNoEmit(t);

View file

@ -450,10 +450,10 @@ void main() {
expression: 'main',
expectedResult: '''
(function(x, y, z) {
T.VoidTodynamic = () => (T.VoidTodynamic = dart.constFn(dart.fnType(dart.dynamic, [])))();
T\$Eval.VoidTodynamic = () => (T\$Eval.VoidTodynamic = dart.constFn(dart.fnType(dart.dynamic, [])))();
dart.defineLazy(CT, {
get C0() {
return C[0] = dart.fn(foo.main, T.VoidTodynamic());
return C[0] = dart.fn(foo.main, T\$Eval.VoidTodynamic());
}
}, false);
return C[0] || CT.C0;
@ -1339,8 +1339,8 @@ void main() {
expression: 'baz(p as String)',
expectedResult: '''
(function(p) {
T.StringL = () => (T.StringL = dart.constFn(dart.legacy(core.String)))();
return foo.baz(T.StringL().as(p));
T\$Eval.StringL = () => (T\$Eval.StringL = dart.constFn(dart.legacy(core.String)))();
return foo.baz(T\$Eval.StringL().as(p));
}(
0
))
@ -1959,8 +1959,8 @@ void main() {
expression: 'a is String',
expectedResult: '''
(function(a, check) {
T.StringL = () => (T.StringL = dart.constFn(dart.legacy(core.String)))();
return T.StringL().is(a);
T\$Eval.StringL = () => (T\$Eval.StringL = dart.constFn(dart.legacy(core.String)))();
return T\$Eval.StringL().is(a);
}(
null,
null
@ -1974,7 +1974,7 @@ void main() {
expression: 'a is int',
expectedResult: '''
(function(a, check) {
return T.intL().is(a);
return T\$Eval.intL().is(a);
}(
null,
null
@ -2182,10 +2182,10 @@ void main() {
expression: 'main',
expectedResult: '''
(function(x, y, z) {
T.VoidTodynamic = () => (T.VoidTodynamic = dart.constFn(dart.fnType(dart.dynamic, [])))();
T\$Eval.VoidTodynamic = () => (T\$Eval.VoidTodynamic = dart.constFn(dart.fnType(dart.dynamic, [])))();
dart.defineLazy(CT, {
get C0() {
return C[0] = dart.fn(foo.main, T.VoidTodynamic());
return C[0] = dart.fn(foo.main, T\$Eval.VoidTodynamic());
}
}, false);
return C[0] || CT.C0;

View file

@ -0,0 +1,38 @@
// 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);
}