mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 02:57:35 +00:00
[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:
parent
6bd1df749c
commit
69e2b2473d
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
207
pkg/compiler/lib/src/ir/protobuf_impacts.dart
Normal file
207
pkg/compiler/lib/src/ir/protobuf_impacts.dart
Normal 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!);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue