[dart2js] Eagerly serialize generated impacts so that they can be GCed immediately.

When dump info data is being serialized as part of the codegen + emitter joint phase, all the impacts are considered "generated" and so they must all be serialized with the dump info data. If we wait to serialize them until the end of the phase as we usually do, these impacts cannot be GCed right away. This leads to a much higher memory footprint. By serializing them eagerly we can GC the object and just maintain the much leaner serialized data.

Change-Id: I11606f7291fe8d86af1f72724bd8c68e9666a00a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/359300
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Nate Biggs 2024-03-23 03:58:52 +00:00 committed by Commit Queue
parent 19665111e5
commit 6bc1b01241
7 changed files with 114 additions and 28 deletions

View file

@ -7,7 +7,6 @@ library dart2js.compiler_base;
import 'dart:async' show Future;
import 'dart:convert' show jsonEncode;
import 'package:compiler/src/serialization/indexed_sink_source.dart';
import 'package:compiler/src/universe/use.dart' show StaticUse;
import 'package:front_end/src/api_unstable/dart2js.dart' as fe;
import 'package:kernel/ast.dart' as ir;
@ -656,14 +655,18 @@ class Compiler {
GlobalTypeInferenceResults? globalTypeInferenceResultsForDumpInfo;
AbstractValueDomain? abstractValueDomainForDumpInfo;
OutputUnitData? outputUnitDataForDumpInfo;
if (options.dumpInfoWriteUri != null ||
options.dumpInfoReadUri != null ||
options.dumpInfo) {
DataSinkWriter? sinkForDumpInfo;
if (options.dumpInfoReadUri != null || options.dumpInfo) {
globalTypeInferenceResultsForDumpInfo = globalTypeInferenceResults;
abstractValueDomainForDumpInfo = closedWorld.abstractValueDomain;
outputUnitDataForDumpInfo = closedWorld.outputUnitData;
indicesForDumpInfo = indices;
}
if (options.dumpInfoWriteUri != null) {
sinkForDumpInfo = serializationTask.dataSinkWriterForDumpInfo(
closedWorld.abstractValueDomain, indices);
dumpInfoRegistry.registerDataSinkWriter(sinkForDumpInfo);
}
// Run codegen.
final sourceLookup = SourceLookup(output.component);
@ -691,13 +694,10 @@ class Compiler {
dumpInfoRegistry,
codegenResults,
programSize);
dumpInfoRegistry.clear();
dumpInfoRegistry.close();
if (options.dumpInfoWriteUri != null) {
serializationTask.serializeDumpInfoProgramData(
backendStrategy,
dumpInfoData,
abstractValueDomainForDumpInfo!,
indicesForDumpInfo!);
serializationTask.serializeDumpInfoProgramData(sinkForDumpInfo!,
backendStrategy, dumpInfoData, dumpInfoRegistry);
} else {
await runDumpInfo(codegenResults,
globalTypeInferenceResultsForDumpInfo!, dumpInfoData);

View file

@ -41,7 +41,7 @@ import 'universe/world_impact.dart' show WorldImpact;
///
/// This registry collects data while JS is being emitted and stores it to be
/// processed by and used in the dump info stage. Since it holds references to
/// AST nodes it should be cleared with [DumpInfoJsAstRegistry.clear] as soon
/// AST nodes it should be cleared with [DumpInfoJsAstRegistry.close] as soon
/// as the necessary data for it is extracted.
///
/// See [DumpInfoProgramData.fromEmitterResults] for how this data is processed.
@ -67,6 +67,8 @@ class DumpInfoJsAstRegistry {
final Map<jsAst.Node, ConstantValue> _constantRegistry = {};
final Map<jsAst.Node, List<Entity>> _entityRegistry = {};
final List<CodeSpan> _stack = [];
DataSinkWriter? _dataSinkWriter;
int _impactCount = 0;
DumpInfoJsAstRegistry(this.options)
: _disabled = !options.dumpInfo && options.dumpInfoWriteUri == null;
@ -87,11 +89,23 @@ class DumpInfoJsAstRegistry {
_constantRegistry[code] = constant;
}
void registerDataSinkWriter(DataSinkWriter dataSinkWriter) {
_dataSinkWriter = dataSinkWriter..startDeferrable();
}
void registerImpact(MemberEntity member, CodegenImpact impact,
{required bool isGenerated}) {
if (_disabled) return;
if (isGenerated || options.dumpInfoWriteUri == null) {
_impactRegistry[member] = impact;
if (isGenerated || options.dumpInfo) {
if (options.dumpInfoWriteUri != null) {
// Serialize immediately so that we don't have to hold a reference to
// every impact until the end of the phase.
_dataSinkWriter!.writeMember(member);
impact.writeToDataSink(_dataSinkWriter!);
_impactCount++;
} else {
_impactRegistry[member] = impact;
}
} else {
_serializedImpactMembers.add(member);
}
@ -134,10 +148,11 @@ class DumpInfoJsAstRegistry {
}
}
void clear() {
void close() {
assert(_stack.isEmpty);
assert(_entityRegistry.isEmpty);
assert(_constantRegistry.isEmpty);
_dataSinkWriter?.endDeferrable();
_entityCode.clear();
_constantCode.clear();
_serializedImpactMembers.clear();
@ -258,6 +273,18 @@ class DumpInfoProgramData {
factory DumpInfoProgramData.readFromDataSource(DataSourceReader source,
{required bool includeCodeText}) {
late int impactCount;
final registeredImpactsDeferrable =
source.readDeferrable((DataSourceReader source) {
final impacts = <MemberEntity, CodegenImpact>{};
for (var i = 0; i < impactCount; i++) {
final member = source.readMember();
final impact = CodegenImpact.readFromDataSource(source);
impacts[member] = impact;
}
return impacts;
});
impactCount = source.readInt();
final programSize = source.readInt();
final outputUnitSizesLength = source.readInt();
final outputUnitSizes = <OutputUnit, int>{};
@ -289,8 +316,6 @@ class DumpInfoProgramData {
constantCode[constant] = codeSpan;
}
final serializedImpactMembers = source.readMembers().toSet();
final registeredImpacts = source.readMemberMap<MemberEntity, CodegenImpact>(
(_) => CodegenImpact.readFromDataSource(source));
return DumpInfoProgramData._(
programSize,
outputUnitSizes,
@ -299,12 +324,13 @@ class DumpInfoProgramData {
entityCodeSize,
constantCode,
serializedImpactMembers,
registeredImpacts,
registeredImpactsDeferrable.loaded(),
neededClasses: neededClasses,
neededClassTypes: neededClassTypes);
}
void writeToDataSink(DataSinkWriter sink) {
void writeToDataSink(DataSinkWriter sink, DumpInfoJsAstRegistry registry) {
sink.writeInt(registry._impactCount);
sink.writeInt(programSize);
sink.writeInt(outputUnitSizes.length);
outputUnitSizes.forEach((outputUnit, size) {
@ -332,8 +358,6 @@ class DumpInfoProgramData {
_writeCodeSpan(sink, codeSpan);
});
sink.writeMembers(serializedImpactMembers);
sink.writeMemberMap(registeredImpacts,
(_, CodegenImpact impact) => impact.writeToDataSink(sink));
}
}

View file

@ -70,6 +70,22 @@ class BinaryDataSink implements DataSink {
_deferredOffsetToSize[indexOffset] = _length - dataStartOffset;
}
final List<(int, int)> _deferredOffsets = [];
@override
void startDeferred() {
final indexOffset = _length;
writeInt(0); // Padding so the offset won't collide with a nested write.
final dataStartOffset = _length;
_deferredOffsets.add((indexOffset, dataStartOffset));
}
@override
void endDeferred() {
final (indexOffset, dataStartOffset) = _deferredOffsets.removeLast();
_deferredOffsetToSize[indexOffset] = _length - dataStartOffset;
}
@override
void writeEnum<E extends Enum>(E value) {
writeInt(value.index);

View file

@ -50,6 +50,22 @@ class ObjectDataSink implements DataSink {
_data![sizeIndex] = endIndex - startIndex;
}
final List<(int, int)> _deferredOffsets = [];
@override
void startDeferred() {
final sizeIndex = length;
writeInt(0); // Padding so the offset won't collide with a nested write.
final startIndex = length;
_deferredOffsets.add((sizeIndex, startIndex));
}
@override
void endDeferred() {
final (sizeIndex, startIndex) = _deferredOffsets.removeLast();
_data![sizeIndex] = length - startIndex;
}
@override
void close() {
_data = null;

View file

@ -30,6 +30,18 @@ abstract class DataSink {
/// via an offset read.
void writeDeferred(void writer());
/// Begins a block of data that can later be read as a deferred block.
/// [endDeferred] must eventually be called to end the block. This creates a
/// block similar to [writeDeferred] but does not require the data to be
/// written in a single closure.
void startDeferred();
/// End a block of data that can later be read as a deferred block.
/// [startDeferred] must be called before this to start the block. This
/// creates a block similar to [writeDeferred] but does not require the data
/// to be written in a single closure.
void endDeferred();
/// Closes any underlying data sinks.
void close();
}
@ -131,6 +143,14 @@ class DataSinkWriter {
_sinkWriter.writeDeferred(f);
}
void startDeferrable() {
_sinkWriter.startDeferred();
}
void endDeferrable() {
_sinkWriter.endDeferred();
}
/// Writes a reference to [value] to this data sink. If [value] has not yet
/// been serialized, [f] is called to serialize the value itself. If
/// [identity] is true then the cache is backed by a [Map] created using

View file

@ -25,7 +25,9 @@ abstract class DataSource {
E readEnum<E extends Enum>(List<E> values);
/// Returns the offset for a deferred entity and skips it in the read queue.
/// The offset can later be passed to [readAtOffset] to get the value.
/// The offset can later be passed to [readAtOffset] to get the value. This
/// block can be written with either [DataSink.writeDeferred] or the
/// combination of [DataSink.startDeferred] and [DataSink.endDeferred].
int readDeferred();
/// Eagerly reads and returns the value for a deferred entity.

View file

@ -277,17 +277,22 @@ class SerializationTask extends CompilerTask {
results.addAll(codegenResults);
}
void serializeDumpInfoProgramData(
JsBackendStrategy backendStrategy,
DumpInfoProgramData dumpInfoProgramData,
AbstractValueDomain abstractValueDomain,
SerializationIndices indices) {
DataSinkWriter dataSinkWriterForDumpInfo(
AbstractValueDomain abstractValueDomain, SerializationIndices indices) {
final outputUri = _options.dumpInfoWriteUri!;
api.BinaryOutputSink dataOutput =
_outputProvider.createBinarySink(outputUri);
final sink = DataSinkWriter(BinaryDataSink(dataOutput), _options, indices);
sink.registerAbstractValueDomain(abstractValueDomain);
dumpInfoProgramData.writeToDataSink(sink);
return sink;
}
void serializeDumpInfoProgramData(
DataSinkWriter sink,
JsBackendStrategy backendStrategy,
DumpInfoProgramData dumpInfoProgramData,
DumpInfoJsAstRegistry dumpInfoRegistry) {
dumpInfoProgramData.writeToDataSink(sink, dumpInfoRegistry);
sink.close();
}
@ -302,7 +307,10 @@ class SerializationTask extends CompilerTask {
final source = DataSourceReader(
BinaryDataSource(dataInput.data, stringInterner: _stringInterner),
_options,
indices);
indices,
// This must use a deferred strategy so that we can delay reading the
// registered impacts until we are able to read the count of them.
useDeferredStrategy: true);
backendStrategy.prepareCodegenReader(source);
source.registerAbstractValueDomain(abstractValueDomain);
return DumpInfoProgramData.readFromDataSource(source,