[dart2js] Remove need for individual closures in Deferrable serialized data.

This new approach uses static tear-offs where possible to avoid allocating a new Closure for each Deferrable.

Note: This doesn't scale to every case but as of today the one case not covered is a singleton and so only 1 closure should be allocated there regardless.

Data from a fairly large app:
Before:
Total => 3.8GB
Closures => 85.6MB
Closure Context => 71.0MB

After:
Total => 3.7GB
Closures => 28.9MB
Closure Context => 49.8MB

Diff:
Closures => 56.7MB
Closure Context => 21.2MB

Change-Id: If233f958df2822708b51c0d14c7439b5d3a5a07b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/313340
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Nate Biggs 2023-07-14 01:29:36 +00:00 committed by Commit Queue
parent 72f1c288bc
commit 25a1d01084
7 changed files with 158 additions and 49 deletions

View file

@ -271,26 +271,26 @@ class GlobalTypeInferenceResultsImpl implements GlobalTypeInferenceResults {
source.begin(tag);
Deferrable<Map<MemberEntity, GlobalTypeInferenceMemberResult>>
memberResults = source.readDeferrable(() => source.readMemberMap(
memberResults = source.readDeferrable((source) => source.readMemberMap(
(MemberEntity member) =>
GlobalTypeInferenceMemberResult.readFromDataSource(
source,
elementMap.getMemberContextNode(member),
closedWorld.abstractValueDomain)));
Deferrable<Map<Local, AbstractValue>> parameterResults =
source.readDeferrable(() => source.readLocalMap(() => closedWorld
source.readDeferrable((source) => source.readLocalMap(() => closedWorld
.abstractValueDomain
.readAbstractValueFromDataSource(source)));
Set<Selector> returnsListElementTypeSet =
source.readList(() => Selector.readFromDataSource(source)).toSet();
Deferrable<Map<ir.TreeNode, AbstractValue>> allocatedLists =
source.readDeferrable(() => source.readTreeNodeMap(() => closedWorld
.abstractValueDomain
.readAbstractValueFromDataSource(source)));
source.readDeferrable((source) => source.readTreeNodeMap(() =>
closedWorld.abstractValueDomain
.readAbstractValueFromDataSource(source)));
Deferrable<Map<ir.TreeNode, AbstractValue>> allocatedRecords =
source.readDeferrable(() => source.readTreeNodeMap(() => closedWorld
.abstractValueDomain
.readAbstractValueFromDataSource(source)));
source.readDeferrable((source) => source.readTreeNodeMap(() =>
closedWorld.abstractValueDomain
.readAbstractValueFromDataSource(source)));
source.end(tag);
return GlobalTypeInferenceResultsImpl._deserialized(
closedWorld,

View file

@ -66,21 +66,40 @@ class ClosureDataImpl implements ClosureData {
this._localClosureRepresentationMap,
this._enclosingMembers);
static Map<MemberEntity, ScopeInfo> _readScopeMap(DataSourceReader source) {
return source.readMemberMap(
(MemberEntity member) => ScopeInfo.readFromDataSource(source));
}
static Map<ir.TreeNode, CapturedScope> _readCapturedScopesMap(
DataSourceReader source) {
return source
.readTreeNodeMap(() => CapturedScope.readFromDataSource(source));
}
static Map<MemberEntity, CapturedScope> _readCapturedScopeForSignatureMap(
DataSourceReader source) {
return source
.readMemberMap((_) => CapturedScope.readFromDataSource(source));
}
static Map<ir.LocalFunction, ClosureRepresentationInfo>
_readLocalClosureRepresentationMap(DataSourceReader source) {
return source.readTreeNodeMap<ir.LocalFunction, ClosureRepresentationInfo>(
() => ClosureRepresentationInfo.readFromDataSource(source));
}
/// Deserializes a [ClosureData] object from [source].
factory ClosureDataImpl.readFromDataSource(
JsToElementMap elementMap, DataSourceReader source) {
source.begin(tag);
// TODO(johnniwinther): Support shared [ScopeInfo].
final scopeMap = source.readDeferrable(() => source.readMemberMap(
(MemberEntity member) => ScopeInfo.readFromDataSource(source)));
final capturedScopesMap = source.readDeferrable(() =>
source.readTreeNodeMap(() => CapturedScope.readFromDataSource(source)));
final capturedScopeForSignatureMap = source.readDeferrable(() =>
source.readMemberMap(
(MemberEntity member) => CapturedScope.readFromDataSource(source)));
final localClosureRepresentationMap = source.readDeferrable(() =>
source.readTreeNodeMap<ir.LocalFunction, ClosureRepresentationInfo>(
() => ClosureRepresentationInfo.readFromDataSource(source)));
final scopeMap = source.readDeferrable(_readScopeMap);
final capturedScopesMap = source.readDeferrable(_readCapturedScopesMap);
final capturedScopeForSignatureMap =
source.readDeferrable(_readCapturedScopeForSignatureMap);
final localClosureRepresentationMap =
source.readDeferrable(_readLocalClosureRepresentationMap);
Map<MemberEntity, MemberEntity> enclosingMembers =
source.readMemberMap((member) => source.readMember());
source.end(tag);
@ -1205,6 +1224,10 @@ class ClosureFunctionData extends ClosureMemberData
ClosureFunctionData._deserialized(super.definition, super.memberThisType,
this.functionType, this._functionNode, this.classTypeVariableAccess);
static ir.FunctionNode _readFunctionNode(DataSourceReader source) {
return source.readTreeNode() as ir.FunctionNode;
}
factory ClosureFunctionData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
ClosureMemberDefinition definition =
@ -1213,7 +1236,7 @@ class ClosureFunctionData extends ClosureMemberData
source.readDartTypeOrNull() as InterfaceType?;
FunctionType functionType = source.readDartType() as FunctionType;
Deferrable<ir.FunctionNode> functionNode =
source.readDeferrable(() => source.readTreeNode() as ir.FunctionNode);
source.readDeferrable(_readFunctionNode);
ClassTypeVariableAccess classTypeVariableAccess =
source.readEnum(ClassTypeVariableAccess.values);
source.end(tag);

View file

@ -450,11 +450,13 @@ class SpecialMemberDefinition implements MemberDefinition {
SpecialMemberDefinition._deserialized(this._node, this.kind);
static ir.TreeNode _readNode(DataSourceReader source) =>
source.readTreeNode();
factory SpecialMemberDefinition.readFromDataSource(
DataSourceReader source, MemberKind kind) {
source.begin(tag);
Deferrable<ir.TreeNode> node =
source.readDeferrable(() => source.readTreeNode());
Deferrable<ir.TreeNode> node = source.readDeferrable(_readNode);
source.end(tag);
return SpecialMemberDefinition._deserialized(node, kind);
}
@ -497,12 +499,14 @@ class ClosureMemberDefinition implements MemberDefinition {
: assert(
kind == MemberKind.closureCall || kind == MemberKind.closureField);
static ir.TreeNode _readNode(DataSourceReader source) =>
source.readTreeNode();
factory ClosureMemberDefinition.readFromDataSource(
DataSourceReader source, MemberKind kind) {
source.begin(tag);
SourceSpan location = source.readSourceSpan();
Deferrable<ir.TreeNode> node =
source.readDeferrable(() => source.readTreeNode());
Deferrable<ir.TreeNode> node = source.readDeferrable(_readNode);
source.end(tag);
return ClosureMemberDefinition._deserialized(location, kind, node);
}

View file

@ -639,6 +639,11 @@ abstract class JMemberDataImpl implements JMemberData {
JMemberDataImpl._deserialized(this.node, this.definition, this._staticTypes);
static StaticTypeCache _readStaticTypeCache(
DataSourceReader source, ir.Member node) {
return StaticTypeCache.readFromDataSource(source, node);
}
@override
InterfaceType? getMemberThisType(JsToElementMap elementMap) {
MemberEntity member = elementMap.getMember(node);
@ -770,8 +775,8 @@ class FunctionDataImpl extends JMemberDataImpl
"Unexpected member node $node (${node.runtimeType}).");
}
MemberDefinition definition = MemberDefinition.readFromDataSource(source);
Deferrable<StaticTypeCache> staticTypes = source
.readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
Deferrable<StaticTypeCache> staticTypes = source.readDeferrableWithArg(
JMemberDataImpl._readStaticTypeCache, node);
source.end(tag);
return FunctionDataImpl._deserialized(
node, functionNode, definition, staticTypes);
@ -822,13 +827,18 @@ class SignatureFunctionData implements FunctionData {
SignatureFunctionData._deserialized(this.definition, this.memberThisType,
this._typeParameters, this.classTypeVariableAccess);
static List<ir.TypeParameter> _readTypeParameterNodes(
DataSourceReader source) {
return source.readTypeParameterNodes();
}
factory SignatureFunctionData.readFromDataSource(DataSourceReader source) {
source.begin(tag);
MemberDefinition definition = MemberDefinition.readFromDataSource(source);
InterfaceType? memberThisType =
source.readDartTypeOrNull() as InterfaceType?;
Deferrable<List<ir.TypeParameter>> typeParameters =
source.readDeferrable(() => source.readTypeParameterNodes());
source.readDeferrable(_readTypeParameterNodes);
ClassTypeVariableAccess classTypeVariableAccess =
source.readEnum(ClassTypeVariableAccess.values);
source.end(tag);
@ -975,8 +985,8 @@ class JConstructorData extends FunctionDataImpl {
"Unexpected member node $node (${node.runtimeType}).");
}
MemberDefinition definition = MemberDefinition.readFromDataSource(source);
Deferrable<StaticTypeCache> staticTypes = source
.readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
Deferrable<StaticTypeCache> staticTypes = source.readDeferrableWithArg(
JMemberDataImpl._readStaticTypeCache, node);
source.end(tag);
return JConstructorData._deserialized(
node, functionNode, definition, staticTypes);
@ -1023,8 +1033,8 @@ class ConstructorBodyDataImpl extends FunctionDataImpl {
"Unexpected member node $node (${node.runtimeType}).");
}
MemberDefinition definition = MemberDefinition.readFromDataSource(source);
Deferrable<StaticTypeCache> staticTypes = source
.readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
Deferrable<StaticTypeCache> staticTypes = source.readDeferrableWithArg(
JMemberDataImpl._readStaticTypeCache, node);
source.end(tag);
return ConstructorBodyDataImpl._deserialized(
node, functionNode, definition, staticTypes);
@ -1067,8 +1077,8 @@ class JFieldDataImpl extends JMemberDataImpl implements JFieldData {
source.begin(tag);
ir.Member node = source.readMemberNode();
MemberDefinition definition = MemberDefinition.readFromDataSource(source);
Deferrable<StaticTypeCache> staticTypes = source
.readDeferrable(() => StaticTypeCache.readFromDataSource(source, node));
Deferrable<StaticTypeCache> staticTypes = source.readDeferrableWithArg(
JMemberDataImpl._readStaticTypeCache, node);
source.end(tag);
return JFieldDataImpl._deserialized(
node as ir.Field, definition, staticTypes);

View file

@ -44,8 +44,8 @@ class GlobalLocalsMap {
Map<MemberEntity, Deferrable<KernelToLocalsMap>> _localsMaps = {};
int mapCount = source.readInt();
for (int i = 0; i < mapCount; i++) {
Deferrable<KernelToLocalsMap> localsMap = source.readDeferrable(
() => KernelToLocalsMapImpl.readFromDataSource(source));
Deferrable<KernelToLocalsMap> localsMap =
source.readDeferrable(KernelToLocalsMapImpl.readFromDataSource);
List<MemberEntity> members = source.readMembers();
for (MemberEntity member in members) {
_localsMaps[member] = localsMap;

View file

@ -19,6 +19,10 @@ import 'package:compiler/src/serialization/serialization.dart';
/// `Deferrable<E>` to initialize `_m1`. This internal constructor should be
/// called from the readFromSource method/factory to create the instance
/// of `C`. `d` should be obtained using [DataSourceReader.readDeferrable].
/// Note: [DataSourceReader.readDeferrable] passes the correct
/// [DataSourceReader] to the reader function so that the [DataSourceReader]
/// does not have to be closed over. If necessary a static tear-off should be
/// used as the argument so a closure isn't created for every read.
/// 3) Any existing constructors of `C` should maintain the same signature
/// and initialize `_m1` passing `m` to [Deferrable.eager] where m is the value
/// previously used to initialize `m0`.
@ -31,11 +35,14 @@ import 'package:compiler/src/serialization/serialization.dart';
///
/// class Foo {
/// final Bar bar;
/// final String name;
///
/// Foo(this.bar);
/// Foo(this.bar, this.name);
///
/// factory Foo.readFromSource(DataSourceReader reader) {
/// return Foo(Bar.readFromSource(reader));
/// final bar = Bar.readFromSource(reader);
/// final name = reader.readString();
/// return Foo(bar, name);
/// }
/// }
///
@ -45,22 +52,36 @@ import 'package:compiler/src/serialization/serialization.dart';
/// Bar get bar => _bar.loaded();
/// final Deferrable<Bar> _bar;
///
/// Foo(Bar bar) : _bar = Deferrable.eager(bar);
/// Foo._deserialized(this._bar);
/// String get name => _name.loaded();
/// final Deferrable<String> _name;
///
/// Foo(Bar bar, String name) :
/// _bar = Deferrable.eager(bar), _name = Deferrable.eager(name);
/// Foo._deserialized(this._bar, this._name);
///
/// static String readName(DataSourceReader reader) => reader.readString();
///
/// factory Foo.readFromSource(DataSourceReader reader) {
/// return Foo._deserialized(
/// reader.readDeferrable(() => Bar.readFromSource(reader)));
/// final bar = reader.readDeferrable(Bar.readFromSource);
/// final name = reader.readDeferrable(readName);
/// return Foo._deserialized(bar, name);
/// }
/// }
abstract class Deferrable<E> {
E loaded();
factory Deferrable.deferred(DataSourceReader reader, E f(), int offset,
factory Deferrable.deferred(
DataSourceReader reader, E f(DataSourceReader source), int offset,
{bool cacheData = true}) =>
cacheData
? _DeferredCache(reader, f, offset)
: _Deferred(reader, f, offset);
static Deferrable<E> deferredWithArg<E, A>(DataSourceReader reader,
E f(DataSourceReader source, A arg), A arg, int offset,
{bool cacheData = true}) =>
cacheData
? _DeferredCacheWithArg(reader, f, arg, offset)
: _DeferredWithArg(reader, f, arg, offset);
const factory Deferrable.eager(E data) = _Eager;
const Deferrable();
@ -73,12 +94,48 @@ class _Eager<E> extends Deferrable<E> {
const _Eager(this._data);
}
class _Deferred<E> extends Deferrable<E> {
class _DeferredWithArg<E, A> extends Deferrable<E> {
final DataSourceReader _reader;
final E Function() _dataLoader;
final E Function(DataSourceReader source, A arg) _dataLoader;
final A _arg;
final int _dataOffset;
@override
E loaded() => _reader.readWithOffset(_dataOffset, _dataLoader);
E loaded() =>
_reader.readWithOffset(_dataOffset, () => _dataLoader(_reader, _arg));
_DeferredWithArg(this._reader, this._dataLoader, this._arg, this._dataOffset);
}
class _DeferredCacheWithArg<E, A> extends Deferrable<E> {
final int _dataOffset;
// Below fields are nullable so they can be cleared after loading.
DataSourceReader? _reader;
E Function(DataSourceReader source, A arg)? _dataLoader;
A? _arg;
late final E _data = _loadData();
@override
E loaded() => _data;
E _loadData() {
final reader = _reader!;
final dataLoader = _dataLoader!;
final arg = _arg!;
_reader = null;
_dataLoader = null;
_arg = null;
return reader.readWithOffset(_dataOffset, () => dataLoader(reader, arg));
}
_DeferredCacheWithArg(
this._reader, this._dataLoader, this._arg, this._dataOffset);
}
class _Deferred<E> extends Deferrable<E> {
final DataSourceReader _reader;
final E Function(DataSourceReader source) _dataLoader;
final int _dataOffset;
@override
E loaded() => _reader.readWithOffset(_dataOffset, () => _dataLoader(_reader));
_Deferred(this._reader, this._dataLoader, this._dataOffset);
}
@ -86,7 +143,7 @@ class _DeferredCache<E> extends Deferrable<E> {
final int _dataOffset;
// Below fields are nullable so they can be cleared after loading.
DataSourceReader? _reader;
E Function()? _dataLoader;
E Function(DataSourceReader source)? _dataLoader;
late final E _data = _loadData();
@override
@ -97,7 +154,7 @@ class _DeferredCache<E> extends Deferrable<E> {
final dataLoader = _dataLoader!;
_reader = null;
_dataLoader = null;
return reader.readWithOffset(_dataOffset, dataLoader);
return reader.readWithOffset(_dataOffset, () => dataLoader(reader));
}
_DeferredCache(this._reader, this._dataLoader, this._dataOffset);

View file

@ -287,13 +287,28 @@ class DataSourceReader {
return _sourceReader.readAtOffset(offset, f);
}
Deferrable<E> readDeferrable<E>(E f(), {bool cacheData = true}) {
Deferrable<E> readDeferrable<E>(E f(DataSourceReader source),
{bool cacheData = true}) {
return enableDeferredStrategy
? (useDeferredStrategy
? Deferrable<E>.deferred(this, f, _sourceReader.readDeferred(),
cacheData: cacheData)
: Deferrable<E>.eager(_sourceReader.readDeferredAsEager(f)))
: Deferrable<E>.eager(f());
: Deferrable<E>.eager(
_sourceReader.readDeferredAsEager(() => f(this))))
: Deferrable<E>.eager(f(this));
}
Deferrable<E> readDeferrableWithArg<E, A>(
E f(DataSourceReader source, A arg), A arg,
{bool cacheData = true}) {
return enableDeferredStrategy
? (useDeferredStrategy
? Deferrable.deferredWithArg<E, A>(
this, f, arg, _sourceReader.readDeferred(),
cacheData: cacheData)
: Deferrable<E>.eager(
_sourceReader.readDeferredAsEager(() => f(this, arg))))
: Deferrable<E>.eager(f(this, arg));
}
/// Invoke [f] in the context of [member]. This sets up support for