[dart2js] Add closed world proto shaking to Dart2js.

Reachability of proto fields is based on the VM's TFA protobuf_handler:
https://github.com/dart-lang/sdk/blob/main/pkg/vm/lib/transformations/type_flow/protobuf_handler.dart

In summary:
Usages of messages' field accessors are detected and registered. If no accessors for a specific field are used then the field is considered unreachable. In this case the metadata initializer for the field is replaced with a placeholder. Any protobuf messages only reachable from erased metadata initializers are also considered unreachable and are erased.

Note: The protobuf library exposes some reflective accessors which this algorithm does not detect usages of. If a field is only accessed reflectively this algorithm will still erase it and therefore users of these reflective accessors should not enable this protobuf shaking.

Protobuf shaking is disabled by default so this change will have no immediate effect on compilations.

Change-Id: I5ddf749bac4ebdcaf364de1cc9a16395a0ad8050
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/349760
Reviewed-by: Mayank Patke <fishythefish@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Nate Biggs <natebiggs@google.com>
This commit is contained in:
Nate Biggs 2024-02-09 17:43:09 +00:00 committed by Commit Queue
parent 6bd1df749c
commit 69e2b2473d
18 changed files with 477 additions and 34 deletions

View file

@ -77,6 +77,8 @@ class Flags {
static const String omitAsCasts = '--omit-as-casts';
static const String laxRuntimeTypeToString = '--lax-runtime-type-to-string';
static const String enableProtoShaking = '--enable-proto-shaking';
static const String platformBinaries = '--platform-binaries=.+';
static const String minify = '--minify';

View file

@ -532,6 +532,10 @@ class Compiler {
closedWorld = computeClosedWorld(component, rootLibraryUri, libraries);
if (stage == Dart2JSStage.closedWorld && closedWorld != null) {
serializationTask.serializeClosedWorld(closedWorld, indices);
if (options.producesModifiedDill) {
serializationTask.serializeComponent(component,
includeSourceBytes: false);
}
} else if (options.testMode && closedWorld != null) {
closedWorld = closedWorldTestMode(closedWorld);
backendStrategy.registerJClosedWorld(closedWorld);

View file

@ -502,6 +502,7 @@ Future<api.CompilationResult> compile(List<String> argv,
_OneOption(Flags.omitImplicitChecks, passThrough),
_OneOption(Flags.omitAsCasts, passThrough),
_OneOption(Flags.laxRuntimeTypeToString, passThrough),
_OneOption(Flags.enableProtoShaking, passThrough),
_OneOption(Flags.benchmarkingProduction, passThrough),
_OneOption(Flags.benchmarkingExperiment, passThrough),
_OneOption(Flags.soundNullSafety, setNullSafetyMode),
@ -831,11 +832,17 @@ Future<api.CompilationResult> compile(List<String> argv,
processName = 'Serialized';
outputName = 'bytes data';
outputSize = outputProvider.totalDataWritten;
final producesDill = compilerOptions.producesModifiedDill;
String dataOutput = fe.relativizeUri(
Uri.base,
compilerOptions.dataOutputUriForStage(compilerOptions.stage),
Platform.isWindows);
summary += 'serialized to data: ${dataOutput}.';
String summaryLine = dataOutput;
if (producesDill) {
summaryLine += ' and ';
summaryLine += fe.relativizeUri(Uri.base, out!, Platform.isWindows);
}
summary += 'serialized to data: $summaryLine.';
break;
case Dart2JSStage.deferredLoadIds:
processName = 'Serialized';

View file

@ -10,7 +10,8 @@ import 'common/work.dart' show WorkItem;
import 'constants/values.dart';
import 'elements/entities.dart';
import 'elements/types.dart';
import 'universe/use.dart' show ConstantUse, DynamicUse, StaticUse, TypeUse;
import 'universe/use.dart'
show ConditionalUse, ConstantUse, DynamicUse, StaticUse, TypeUse;
import 'universe/world_impact.dart' show WorldImpact;
abstract class EnqueuerListener {
@ -35,10 +36,17 @@ abstract class EnqueuerListener {
/// specific [WorldImpact] of this is returned.
WorldImpact registerClosurizedMember(FunctionEntity function);
/// Called to register that [element] is statically known to be used. Any
/// Called to register that [member] is statically known to be used. Any
/// backend specific [WorldImpact] of this is returned.
WorldImpact registerUsedElement(MemberEntity member);
/// Called to register that [uses] are conditionally applied if [member] is
/// used. [member] should only ever be a pending entity (i.e. it is not known
/// to be live yet). Entities that are already known to be live should have
/// their impacts applied immediately.
void registerPendingConditionalUses(
MemberEntity member, List<ConditionalUse> uses);
/// Called to register that [value] is statically known to be used. Any
/// backend specific [WorldImpact] of this is returned.
WorldImpact registerUsedConstant(ConstantValue value);
@ -94,6 +102,7 @@ abstract class Enqueuer {
worldImpact.forEachDynamicUse((_, use) => processDynamicUse(use));
worldImpact.forEachTypeUse(processTypeUse);
worldImpact.forEachConstantUse((_, use) => processConstantUse(use));
processConditionalUses(worldImpact.conditionalUses);
}
bool checkNoEnqueuedInvokedInstanceMethods(
@ -113,6 +122,8 @@ abstract class Enqueuer {
void processTypeUse(MemberEntity? member, TypeUse typeUse);
void processDynamicUse(DynamicUse dynamicUse);
void processConstantUse(ConstantUse constantUse);
void processConditionalUses(
Map<MemberEntity, List<ConditionalUse>> conditionalUses);
EnqueuerListener get listener;
void open(FunctionEntity? mainMethod, Iterable<Uri> libraries) {

View file

@ -178,6 +178,8 @@ abstract class ImpactRegistry {
void registerExternalProcedureNode(ir.Procedure node);
void registerForeignStaticInvocationNode(ir.StaticInvocation node);
void registerConstSymbolConstructorInvocationNode();
void registerConditionalImpacts(
ir.Member condition, Iterable<ImpactData> impactData);
}
class ImpactBuilderData {

View file

@ -21,41 +21,85 @@ import '../serialization/serialization.dart';
import '../util/enumset.dart';
import 'constants.dart';
import 'impact.dart';
import 'protobuf_impacts.dart';
import 'runtime_type_analysis.dart';
import 'util.dart';
/// Checks [node] against available [ConditionalImpactHandler] to see if any
/// are applicable to it. Returns null if there is no matching handler.
ConditionalImpactHandler? _getConditionalImpactHandler(
KernelToElementMap elementMap, ir.Member node) {
return ProtobufImpactHandler.createIfApplicable(elementMap, node);
}
abstract class ConditionalImpactHandler {
/// Invoked before children of [node] are analyzed. Returns a temporary
/// [ImpactData] if one should be used for the scope of [node] or null
/// otherwise.
ImpactData? beforeInstanceInvocation(ir.InstanceInvocation node);
/// Invoked after children of [node] are analyzed. Takes [currentData] which
/// should be the [ImpactData] prior to visiting [node].
void afterInstanceInvocation(
ir.InstanceInvocation node, ImpactData currentData);
}
class _ConditionalImpactBuilder extends ImpactBuilder {
final ConditionalImpactHandler _conditionalHandler;
_ConditionalImpactBuilder._(
super.elementMap, super.node, this._conditionalHandler)
: super._();
@override
void visitInstanceInvocation(ir.InstanceInvocation node) {
final oldData = _data;
_data = _conditionalHandler.beforeInstanceInvocation(node) ?? _data;
super.visitInstanceInvocation(node);
_conditionalHandler.afterInstanceInvocation(node, oldData);
_data = oldData;
}
}
/// Visitor that builds an [ImpactData] object for the world impact.
class ImpactBuilder extends ir.RecursiveVisitor implements ImpactRegistry {
final ImpactData _data = ImpactData();
ImpactData _data = ImpactData();
final ir.Member node;
final KernelToElementMap _elementMap;
final ir.ClassHierarchy classHierarchy;
final ir.StaticTypeContext staticTypeContext;
final ir.TypeEnvironment typeEnvironment;
ImpactBuilder(this._elementMap, this.staticTypeContext, this.classHierarchy,
this.typeEnvironment);
factory ImpactBuilder(KernelToElementMap elementMap, ir.Member node) {
final conditionalHandler = _getConditionalImpactHandler(elementMap, node);
return conditionalHandler != null
? _ConditionalImpactBuilder._(elementMap, node, conditionalHandler)
: ImpactBuilder._(elementMap, node);
}
ImpactBuilder._(this._elementMap, this.node)
: staticTypeContext =
ir.StaticTypeContext(node, _elementMap.typeEnvironment);
CommonElements get _commonElements => _elementMap.commonElements;
DiagnosticReporter get _reporter => _elementMap.reporter;
ir.ClassHierarchy get classHierarchy => _elementMap.classHierarchy;
ir.TypeEnvironment get typeEnvironment => _elementMap.typeEnvironment;
CompilerOptions get _options => _elementMap.options;
String _typeToString(DartType type) =>
type.toStructuredText(_elementMap.types, _elementMap.options);
CompilerOptions get _options => _elementMap.options;
/// Return the named arguments names as a list of strings.
List<String> _getNamedArguments(ir.Arguments arguments) =>
arguments.named.map((n) => n.name).toList();
ImpactBuilderData computeImpact(ir.Member node) {
ImpactBuilderData computeImpact() {
node.accept(this);
return ImpactBuilderData(node, impactData);
return ImpactBuilderData(node, _data);
}
ImpactData get impactData => _data;
@override
void visitBlock(ir.Block node) {
assert(_pendingRuntimeTypeUseData.isEmpty,
@ -1109,6 +1153,13 @@ class ImpactBuilder extends ir.RecursiveVisitor implements ImpactRegistry {
void registerConstSymbolConstructorInvocationNode() {
_data._hasConstSymbolConstructorInvocation = true;
}
@override
void registerConditionalImpacts(
ir.Member condition, Iterable<ImpactData> impactData) {
// Ensure conditional impact is registered on parent impact, `_data`.
((_data.conditionalImpacts ??= {})[condition] ??= []).addAll(impactData);
}
}
/// Data object that contains the world impact data derived purely from kernel.
@ -1151,6 +1202,9 @@ class ImpactData {
List<_RecordLiteral>? _recordLiterals;
List<_RuntimeTypeUse>? _runtimeTypeUses;
List<_ForInData>? _forInData;
Map<ir.Member, List<ImpactData>>? conditionalImpacts;
ir.TreeNode? conditionalSource;
ir.TreeNode? conditionalReplacement;
// TODO(johnniwinther): Remove these when CFE provides constants.
List<ir.Constructor>? _externalConstructorNodes;
@ -1595,6 +1649,8 @@ class ImpactData {
}
}
conditionalImpacts?.forEach(registry.registerConditionalImpacts);
// TODO(johnniwinther): Remove these when CFE provides constants.
if (_externalConstructorNodes != null) {
for (ir.Constructor data in _externalConstructorNodes!) {

View file

@ -78,13 +78,8 @@ ModularMemberData computeModularMemberData(
ir.Member node,
ScopeModel scopeModel,
EnumSet<PragmaAnnotation> annotations) {
var impactBuilderData = ImpactBuilder(
elementMap,
ir.StaticTypeContext(node, elementMap.typeEnvironment),
elementMap.classHierarchy,
elementMap.typeEnvironment)
.computeImpact(node);
return ModularMemberData(scopeModel, impactBuilderData);
return ModularMemberData(
scopeModel, ImpactBuilder(elementMap, node).computeImpact());
}
class ModularCore {

View file

@ -0,0 +1,207 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/type_algebra.dart' as ir;
import '../kernel/element_map.dart';
import 'impact_data.dart';
/// Handles conditional impact creation for protobuf metadata.
///
/// Consider the following protobuf message:
/// ```
/// message Name {
/// optional string value;
/// }
///
/// message Person {
/// optional Name name;
/// }
/// ```
///
/// which roughly translates to this generated code:
///
/// ```
/// class Name extends $pb.GeneratedMessage {
/// static final $pb.BuilderInfo _i = $pb.BuilderInfo('Name')
/// ..oS(1, 'value');
///
/// static Name create() => Name._();
///
/// // Accessors for value //
/// @$pb.TagNumber(1)
/// String get value => $_getSZ(0);
/// @$pb.TagNumber(1)
/// set value(String v) { $_setString(0, v); }
/// @$pb.TagNumber(1)
/// $core.bool hasValue() => $_has(0);
/// @$pb.TagNumber(1)
/// void clearValue() => clearField(1);
/// }
///
/// class Person extends $pb.GeneratedMessage {
/// static final $pb.BuilderInfo _i = $pb.BuilderInfo('Person')
/// ..aOM<Name>(1, 'name', subBuilder: Name.create); // name metadata initalizer
///
/// static Person create() => Person._();
///
/// // Accessors for name //
/// @$pb.TagNumber(1)
/// Name get name => $_getN(0);
/// @$pb.TagNumber(1)
/// set name(Name v) { setField(0, v); }
/// @$pb.TagNumber(1)
/// $core.bool hasName() => $_has(0);
/// @$pb.TagNumber(1)
/// void clearName() => clearField(1);
/// }
/// ```
///
/// We refer to `..aOM<Name>(1, 'name', subBuilder: Name.create)` as the
/// metadata initializer for `name`. We consider `name` unreachable if none of
/// the accessors for `name` are invoked anywhere in the program.
///
/// If `name` is unreachable then we can replace the metadata initializer for
/// `name` with a placeholder. This would remove the reference to `Name.create`
/// so we consider the usage of `Name.create` to be conditional on any of the
/// `name` accessors being invoked. Furthermore, if `Name` is only instantiated
/// from an unreachable metadata initializer, the `Name` class itself is also
/// unreachable and can be pruned.
class ProtobufImpactHandler implements ConditionalImpactHandler {
static const String protobufLibraryUri = 'package:protobuf/protobuf.dart';
static ProtobufImpactHandler? createIfApplicable(
KernelToElementMap elementMap, ir.Member node) {
if (!elementMap.options.enableProtoShaking) return null;
// Not all programs will include the protobuf library. Ideally those
// programs wouldn't enable proto shaking but we can be conservative and not
// assume the library exists.
final protobufGeneratedMessageClass = elementMap.env.libraryIndex
.tryGetClass(protobufLibraryUri, 'GeneratedMessage');
// Only applicable if the member is in a subclass of GeneratedMessage and
// the name matches the static metadata field.
if (protobufGeneratedMessageClass != null &&
node.enclosingClass?.superclass == protobufGeneratedMessageClass &&
node.name.text == metadataFieldName) {
return ProtobufImpactHandler._(elementMap, node.enclosingClass!);
}
return null;
}
final KernelToElementMap _elementMap;
final ir.Class _messageClass;
ImpactData? _impactData;
ProtobufImpactHandler._(this._elementMap, this._messageClass);
late final ir.Class _builderInfoClass =
_elementMap.env.libraryIndex.getClass(protobufLibraryUri, 'BuilderInfo');
late final ir.Class _tagNumberClass =
_elementMap.env.libraryIndex.getClass(protobufLibraryUri, 'TagNumber');
late final ir.Field _tagNumberField = _elementMap.env.libraryIndex
.getField(protobufLibraryUri, 'TagNumber', 'tagNumber');
late final ir.Procedure _builderInfoAddMethod = _elementMap.env.libraryIndex
.getProcedure(protobufLibraryUri, 'BuilderInfo', 'add');
late final ir.FunctionType _typeOfBuilderInfoAddOfNull =
ir.FunctionTypeInstantiator.instantiate(
_builderInfoAddMethod.getterType as ir.FunctionType,
const <ir.DartType>[ir.NullType()]);
static const String metadataFieldName = '_i';
// All of those methods have the dart field name as second positional
// parameter.
// Method names are defined in:
// https://github.com/google/protobuf.dart/blob/master/protobuf/lib/src/protobuf/builder_info.dart
// The code is generated by:
// https://github.com/google/protobuf.dart/blob/master/protoc_plugin/lib/src/protobuf_field.dart
static const Set<String> metadataInitializers = const <String>{
'a',
'aOM',
'aOS',
'aQM',
'pPS',
'aQS',
'aInt64',
'aOB',
'e',
'p',
'pc',
'm',
};
ir.InstanceInvocation _buildProtobufMetadataPlaceholder(
ir.InstanceInvocation node) {
return ir.InstanceInvocation(
ir.InstanceAccessKind.Instance,
node.receiver,
_builderInfoAddMethod.name,
ir.Arguments(
<ir.Expression>[
ir.IntLiteral(0), // tagNumber
ir.NullLiteral(), // name
ir.NullLiteral(), // fieldType
ir.NullLiteral(), // defaultOrMaker
ir.NullLiteral(), // subBuilder
ir.NullLiteral(), // valueOf
ir.NullLiteral(), // enumValues
],
types: <ir.DartType>[const ir.NullType()],
),
interfaceTarget: _builderInfoAddMethod,
functionType: _typeOfBuilderInfoAddOfNull)
..fileOffset = node.fileOffset;
}
@override
ImpactData? beforeInstanceInvocation(ir.InstanceInvocation node) {
final interfaceTarget = node.interfaceTarget;
// Check if this is a metadata initializer. If so its impacts are
// conditional on the associated field being reachable.
return _impactData = interfaceTarget.enclosingClass == _builderInfoClass &&
metadataInitializers.contains(node.name.text)
? (ImpactData()
..conditionalSource = node
..conditionalReplacement = _buildProtobufMetadataPlaceholder(node))
: null;
}
@override
void afterInstanceInvocation(
ir.InstanceInvocation node, ImpactData currentData) {
// This instance invocation is not a metadata initializer.
if (_impactData == null) return;
// The tag number is always the first argument in a metadata initializer.
final tagNumber = ((node.arguments.positional[0] as ir.ConstantExpression)
.constant as ir.DoubleConstant)
.value
.toInt();
// Iterate through all the accessors and find ones which are annotated
// with a matching tag number. These are the accessors that the current
// metadata initializer is conditional on.
for (final procedure in _messageClass.procedures) {
for (final annotation in procedure.annotations) {
final constant = (annotation as ir.ConstantExpression).constant;
if (constant is ir.InstanceConstant &&
constant.classReference == _tagNumberClass.reference) {
final procedureTagNumber =
(constant.fieldValues[_tagNumberField.fieldReference]
as ir.DoubleConstant)
.value
.toInt();
if (tagNumber == procedureTagNumber) {
((currentData.conditionalImpacts ??= {})[procedure] ??= [])
.add(_impactData!);
}
}
}
}
}
}

View file

@ -17,7 +17,7 @@ import '../native/enqueue.dart';
import '../options.dart';
import '../universe/call_structure.dart' show CallStructure;
import '../universe/codegen_world_builder.dart';
import '../universe/use.dart' show StaticUse, TypeUse;
import '../universe/use.dart' show ConditionalUse, StaticUse, TypeUse;
import '../universe/world_impact.dart'
show WorldImpact, WorldImpactBuilder, WorldImpactBuilderImpl;
import 'backend_impact.dart';
@ -355,4 +355,11 @@ class CodegenEnqueuerListener extends EnqueuerListener {
void logSummary(void log(String message)) {
_nativeEnqueuer.logSummary(log);
}
@override
void registerPendingConditionalUses(
MemberEntity member, List<ConditionalUse> uses) {
throw UnsupportedError(
'Codegen enqueuer does not support conditional impacts.');
}
}

View file

@ -18,6 +18,7 @@ import '../universe/codegen_world_builder.dart';
import '../universe/member_usage.dart';
import '../universe/use.dart'
show
ConditionalUse,
ConstantUse,
DynamicUse,
StaticUse,
@ -308,4 +309,8 @@ class CodegenEnqueuer extends Enqueuer {
@override
Iterable<MemberEntity> get processedEntities =>
worldBuilder.processedEntities;
@override
void processConditionalUses(
Map<MemberEntity, List<ConditionalUse>> conditionalUses) {}
}

View file

@ -15,7 +15,7 @@ import '../js_model/elements.dart' show JClass;
import '../native/enqueue.dart';
import '../options.dart' show CompilerOptions;
import '../universe/call_structure.dart' show CallStructure;
import '../universe/use.dart' show StaticUse, TypeUse;
import '../universe/use.dart' show ConditionalUse, StaticUse, TypeUse;
import '../universe/world_impact.dart'
show WorldImpact, WorldImpactBuilder, WorldImpactBuilderImpl;
import 'field_analysis.dart';
@ -46,6 +46,11 @@ class ResolutionEnqueuerListener extends EnqueuerListener {
final NativeResolutionEnqueuer _nativeEnqueuer;
final KFieldAnalysis _fieldAnalysis;
/// Contains conditional uses for members that have not been marked as live
/// yet. Any entries remaining in here after the queue is finished are
/// considered unreachable.
final Map<MemberEntity, List<ConditionalUse>> _pendingConditionalUses = {};
ResolutionEnqueuerListener(
this._options,
this._elementEnvironment,
@ -184,7 +189,18 @@ class ResolutionEnqueuerListener extends EnqueuerListener {
}
@override
void onQueueClosed() {}
void onQueueClosed() {
// Update the Kernel for any unused conditional impacts.
_pendingConditionalUses.forEach((_, uses) {
for (final use in uses) {
final source = use.source;
if (source?.parent != null) {
// Make sure the source node is still in the AST.
source?.replaceWith(use.replacement!);
}
}
});
}
/// Adds the impact of [constant] to [impactBuilder].
void _computeImpactForCompileTimeConstant(
@ -244,6 +260,12 @@ class ResolutionEnqueuerListener extends EnqueuerListener {
WorldImpact registerUsedElement(MemberEntity member) {
WorldImpactBuilderImpl worldImpact = WorldImpactBuilderImpl();
_customElementsAnalysis.registerStaticUse(member);
final conditionalUses = _pendingConditionalUses.remove(member);
if (conditionalUses != null) {
for (final conditionalUse in conditionalUses) {
worldImpact.addImpact(conditionalUse.impact);
}
}
if (member.isFunction) {
FunctionEntity function = member as FunctionEntity;
@ -470,4 +492,10 @@ class ResolutionEnqueuerListener extends EnqueuerListener {
void logSummary(void log(String message)) {
_nativeEnqueuer.logSummary(log);
}
@override
void registerPendingConditionalUses(
MemberEntity member, List<ConditionalUse> uses) {
(_pendingConditionalUses[member] ??= []).addAll(uses);
}
}

View file

@ -6,6 +6,7 @@ library dart2js.kernel.env;
import 'package:js_shared/variance.dart';
import 'package:kernel/ast.dart' as ir;
import 'package:kernel/library_index.dart' as ir;
import 'package:collection/collection.dart' show mergeSort; // a stable sort.
import '../common.dart';
@ -32,6 +33,8 @@ class KProgramEnv {
library.importUri: KLibraryEnv(library),
};
late final ir.LibraryIndex libraryIndex = ir.LibraryIndex.all(mainComponent);
/// TODO(johnniwinther): Handle arbitrary load order if needed.
ir.Member? get mainMethod => mainComponent.mainMethod;

View file

@ -818,4 +818,31 @@ class KernelImpactConverter implements ImpactRegistry {
impactData.apply(this);
return impactBuilder;
}
@override
void registerConditionalImpacts(
ir.Member condition, Iterable<ImpactData> impacts) {
final conditionalUses = impacts.map((impactData) => ConditionalUse(
source: impactData.conditionalSource,
replacement: impactData.conditionalReplacement,
// TODO(natebiggs): Make KernelImpactConverter stateless so that we
// don't need one per impact.
impact: KernelImpactConverter(
elementMap,
currentMember,
reporter,
_options,
_constantValuefier,
staticTypeContext,
_impacts,
_nativeResolutionEnqueuer,
_backendUsageBuilder,
_customElementsResolutionAnalysis,
_rtiNeedBuilder,
_annotationsData)
.convert(impactData)));
impactBuilder.registerConditionalUses(
elementMap.getMember(condition), conditionalUses);
}
}

View file

@ -37,7 +37,7 @@ enum Dart2JSStage {
emitsKernel: false,
emitsJs: false),
closedWorld('closed-world',
dataOutputName: 'world.data', emitsKernel: false, emitsJs: false),
dataOutputName: 'world.data', emitsKernel: true, emitsJs: false),
globalInference('global-inference',
dataOutputName: 'global.data', emitsKernel: false, emitsJs: false),
codegenAndJsEmitter('codegen-emit-js', emitsKernel: false, emitsJs: true),
@ -56,8 +56,6 @@ enum Dart2JSStage {
final String? dataOutputName;
final bool emitsKernel;
final bool emitsJs;
String? get outputExtension =>
emitsJs ? '.js' : (emitsKernel ? '.dill' : null);
bool get shouldOnlyComputeDill =>
this == Dart2JSStage.cfe || this == Dart2JSStage.cfeFromDill;
@ -719,6 +717,11 @@ class CompilerOptions implements DiagnosticOptions {
// Whether or not to disable byte cache for sources loaded from Kernel dill.
bool disableDiagnosticByteCache = false;
bool enableProtoShaking = false;
bool get producesModifiedDill =>
stage == Dart2JSStage.closedWorld && enableProtoShaking;
late final Dart2JSStage stage = _calculateStage();
Dart2JSStage _calculateStage() => _stageFlag != null
@ -730,6 +733,25 @@ class CompilerOptions implements DiagnosticOptions {
String get _outputFilename => _outputUri?.pathSegments.last ?? '';
String? get _outputExtension {
switch (stage) {
case Dart2JSStage.all:
case Dart2JSStage.allFromDill:
case Dart2JSStage.jsEmitter:
case Dart2JSStage.codegenAndJsEmitter:
return '.js';
case Dart2JSStage.cfe:
case Dart2JSStage.cfeFromDill:
return '.dill';
case Dart2JSStage.closedWorld:
if (producesModifiedDill) return '.dill';
case Dart2JSStage.deferredLoadIds:
case Dart2JSStage.globalInference:
case Dart2JSStage.codegenSharded:
}
return null;
}
/// Output prefix specified by the user via the `--out` flag. The prefix is
/// calculated from the final segment of the user provided URI. If the
/// extension does not match the expected extension for the current [stage]
@ -737,7 +759,7 @@ class CompilerOptions implements DiagnosticOptions {
/// specified.
late final String _outputPrefix = (() {
if (_stageFlag == null) return '';
final extension = stage.outputExtension;
final extension = _outputExtension;
return (extension != null && _outputFilename.endsWith(extension))
? ''
@ -755,7 +777,7 @@ class CompilerOptions implements DiagnosticOptions {
/// Computes a resolved output URI based on value provided via the `--out`
/// flag. Updates [outputUri] based on the result and returns the value.
Uri? setResolvedOutputUri() {
final extension = stage.outputExtension;
final extension = _outputExtension;
if (extension == null) return null;
if (_stageFlag == null) {
@ -935,6 +957,7 @@ class CompilerOptions implements DiagnosticOptions {
..omitAsCasts = _hasOption(options, Flags.omitAsCasts)
..laxRuntimeTypeToString =
_hasOption(options, Flags.laxRuntimeTypeToString)
..enableProtoShaking = _hasOption(options, Flags.enableProtoShaking)
..testMode = _hasOption(options, Flags.testMode)
..trustPrimitives = _hasOption(options, Flags.trustPrimitives)
..useFrequencyNamer =

View file

@ -16,6 +16,7 @@ import '../universe/member_usage.dart';
import '../universe/resolution_world_builder.dart' show ResolutionWorldBuilder;
import '../universe/use.dart'
show
ConditionalUse,
ConstantUse,
DynamicUse,
StaticUse,
@ -98,9 +99,9 @@ class ResolutionEnqueuer extends Enqueuer {
}
/// Callback for applying the use of a [member].
void _applyMemberUse(Entity member, EnumSet<MemberUse> useSet) {
void _applyMemberUse(MemberEntity member, EnumSet<MemberUse> useSet) {
if (useSet.contains(MemberUse.NORMAL)) {
_addToWorkList(member as MemberEntity);
_addToWorkList(member);
}
if (useSet.contains(MemberUse.CLOSURIZE_INSTANCE)) {
_registerClosurizedMember(member as FunctionEntity);
@ -333,4 +334,22 @@ class ResolutionEnqueuer extends Enqueuer {
bool _onQueueEmpty(Iterable<ClassEntity> recentClasses) {
return listener.onQueueEmpty(this, recentClasses);
}
@override
void processConditionalUses(
Map<MemberEntity, List<ConditionalUse>> conditionalUses) {
conditionalUses.forEach((condition, uses) {
// Only register a conditional use as pending if the condition member is
// not live. If it is already live apply the attached impacts immediately.
// [worldBuilder.isInstanceMemberLive] checks against the set of members
// that have been registered as used by the enqueuer.
if (worldBuilder.isInstanceMemberLive(condition)) {
for (final use in uses) {
applyImpact(use.impact);
}
} else {
listener.registerPendingConditionalUses(condition, uses);
}
});
}
}

View file

@ -881,6 +881,12 @@ class ResolutionWorldBuilder extends WorldBuilder implements World {
}
}
bool isInstanceMemberLive(MemberEntity element) {
return element.isAbstract
? _liveAbstractInstanceMembers.contains(element)
: _liveInstanceMembers.contains(element);
}
Map<ClassEntity, Set<ClassEntity>> populateHierarchyNodes() {
Map<ClassEntity, Set<ClassEntity>> typesImplementedBySubclasses = {};

View file

@ -16,6 +16,8 @@
/// program.
library dart2js.universe.use;
import 'package:kernel/ast.dart' as ir;
import '../common.dart';
import '../constants/values.dart';
import '../elements/types.dart';
@ -26,6 +28,7 @@ import '../js_model/closure.dart' show JContextField;
import '../util/util.dart' show equalElements, Hashing;
import 'call_structure.dart' show CallStructure;
import 'selector.dart' show Selector;
import 'world_impact.dart';
enum DynamicUseKind {
INVOKE,
@ -1041,3 +1044,16 @@ class ConstantUse {
@override
String toString() => 'ConstantUse(${value.toStructuredText(null)})';
}
/// Conditional impact and Kernel nodes for replacement if it isn't applied.
///
/// If one of source or replacement is provided, the other must also be
/// provided.
class ConditionalUse {
final ir.TreeNode? source;
final ir.TreeNode? replacement;
final WorldImpact impact;
ConditionalUse({this.source, this.replacement, required this.impact})
: assert((source == null) == (replacement == null));
}

View file

@ -39,6 +39,8 @@ class WorldImpact {
Iterable<ConstantUse> get constantUses => const [];
Map<MemberEntity, List<ConditionalUse>> get conditionalUses => const {};
void _forEach<U>(
Iterable<U> uses, void Function(MemberEntity?, U) visitUse) =>
uses.forEach((use) => visitUse(member, use));
@ -85,6 +87,8 @@ abstract class WorldImpactBuilder extends WorldImpact {
void registerTypeUse(TypeUse typeUse);
void registerStaticUse(StaticUse staticUse);
void registerConstantUse(ConstantUse constantUse);
void registerConditionalUses(
MemberEntity condition, Iterable<ConditionalUse> conditionalUses);
}
class WorldImpactBuilderImpl extends WorldImpactBuilder {
@ -98,6 +102,7 @@ class WorldImpactBuilderImpl extends WorldImpactBuilder {
Set<StaticUse>? _staticUses;
Set<TypeUse>? _typeUses;
Set<ConstantUse>? _constantUses;
Map<MemberEntity, List<ConditionalUse>>? _conditionalUses;
WorldImpactBuilderImpl([this.member]);
@ -110,7 +115,8 @@ class WorldImpactBuilderImpl extends WorldImpactBuilder {
_dynamicUses == null &&
_staticUses == null &&
_typeUses == null &&
_constantUses == null;
_constantUses == null &&
_conditionalUses == null;
/// Copy uses in [impact] to this impact builder.
void addImpact(WorldImpact impact) {
@ -119,6 +125,7 @@ class WorldImpactBuilderImpl extends WorldImpactBuilder {
impact.staticUses.forEach(registerStaticUse);
impact.typeUses.forEach(registerTypeUse);
impact.constantUses.forEach(registerConstantUse);
impact.conditionalUses.forEach(registerConditionalUses);
}
@override
@ -160,6 +167,17 @@ class WorldImpactBuilderImpl extends WorldImpactBuilder {
Iterable<ConstantUse> get constantUses {
return _constantUses ?? const [];
}
@override
void registerConditionalUses(
MemberEntity condition, Iterable<ConditionalUse> impacts) {
((_conditionalUses ??= {})[condition] ??= []).addAll(impacts);
}
@override
Map<MemberEntity, List<ConditionalUse>> get conditionalUses {
return _conditionalUses ?? const {};
}
}
/// Mutable implementation of [WorldImpact] used to transform
@ -230,6 +248,13 @@ class TransformedWorldImpact extends WorldImpactBuilder {
_constantUses!.add(constantUse);
}
@override
void registerConditionalUses(
MemberEntity condition, Iterable<ConditionalUse> conditionalUses) {}
@override
Map<MemberEntity, List<ConditionalUse>> get conditionalUses => const {};
@override
String toString() {
StringBuffer sb = StringBuffer();