Reimplement member usage tracking

We now track member usage in terms of static, dynamic and super access
for reads, writes and invocations. The information collected during
closed world computation is now the basis for the potential member usage
in codegen, thus ensuring that we cannot conclude in codegen that for
instance a field is read dynamically when the closed world knows that
it is never read dynamically.

Closes #36516

Change-Id: I3a1cb87c71268c34bcd67e14a035d9d1be324ab0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/100840
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Johnni Winther 2019-05-01 08:58:34 +00:00 committed by commit-bot@chromium.org
parent b36734f4da
commit 8d4816b7ff
19 changed files with 1044 additions and 762 deletions

View file

@ -296,7 +296,7 @@ abstract class DeferredLoadTask extends CompilerTask {
_collectTypeArgumentDependencies(
staticUse.type.typeArguments, dependencies);
break;
case StaticUseKind.INVOKE:
case StaticUseKind.STATIC_INVOKE:
case StaticUseKind.CLOSURE_CALL:
case StaticUseKind.DIRECT_INVOKE:
// TODO(johnniwinther): Use rti need data to skip unneeded type

View file

@ -207,7 +207,7 @@ abstract class EnqueuerImpl extends Enqueuer {
/// Check enqueuer consistency after the queue has been closed.
bool checkEnqueuerConsistency(ElementEnvironment elementEnvironment) {
task.measure(() {
task.measureSubtask('resolution.check', () {
// Run through the classes and see if we need to enqueue more methods.
for (ClassEntity classElement
in worldBuilder.directlyInstantiatedClasses) {
@ -284,7 +284,7 @@ class ResolutionEnqueuer extends EnqueuerImpl {
{ConstructorEntity constructor,
bool nativeUsage: false,
bool globalDependency: false}) {
task.measure(() {
task.measureSubtask('resolution.typeUse', () {
_worldBuilder.registerTypeInstantiation(type, _applyClassUse,
constructor: constructor);
listener.registerInstantiatedType(type,
@ -307,7 +307,7 @@ class ResolutionEnqueuer extends EnqueuerImpl {
_reporter.internalError(member,
'Unenqueued use of $member: ${useSet.iterable(MemberUse.values)}');
}
}, dryRun: true);
}, checkEnqueuerConsistency: true);
}
/// Callback for applying the use of a [member].
@ -343,14 +343,14 @@ class ResolutionEnqueuer extends EnqueuerImpl {
@override
void processDynamicUse(DynamicUse dynamicUse) {
task.measure(() {
task.measureSubtask('resolution.dynamicUse', () {
_worldBuilder.registerDynamicUse(dynamicUse, _applyMemberUse);
});
}
@override
void processConstantUse(ConstantUse constantUse) {
task.measure(() {
task.measureSubtask('resolution.constantUse', () {
if (_worldBuilder.registerConstantUse(constantUse)) {
applyImpact(listener.registerUsedConstant(constantUse.value),
impactSource: 'constant use');
@ -361,18 +361,20 @@ class ResolutionEnqueuer extends EnqueuerImpl {
@override
void processStaticUse(StaticUse staticUse) {
_worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
// TODO(johnniwinther): Add `ResolutionWorldBuilder.registerConstructorUse`
// for these:
switch (staticUse.kind) {
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
_registerInstantiatedType(staticUse.type,
constructor: staticUse.element, globalDependency: false);
break;
default:
break;
}
task.measureSubtask('resolution.staticUse', () {
_worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
// TODO(johnniwinther): Add `ResolutionWorldBuilder.registerConstructorUse`
// for these:
switch (staticUse.kind) {
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
_registerInstantiatedType(staticUse.type,
constructor: staticUse.element, globalDependency: false);
break;
default:
break;
}
});
}
@override

View file

@ -107,7 +107,7 @@ class CodegenEnqueuer extends EnqueuerImpl {
void _registerInstantiatedType(InterfaceType type,
{bool nativeUsage: false}) {
task.measure(() {
task.measureSubtask('codegen.typeUse', () {
_worldBuilder.registerTypeInstantiation(type, _applyClassUse);
listener.registerInstantiatedType(type, nativeUsage: nativeUsage);
});
@ -127,7 +127,7 @@ class CodegenEnqueuer extends EnqueuerImpl {
failedAt(member,
'Unenqueued use of $member: ${useSet.iterable(MemberUse.values)}');
}
}, dryRun: true);
}, checkEnqueuerConsistency: true);
}
/// Callback for applying the use of a [cls].
@ -160,26 +160,28 @@ class CodegenEnqueuer extends EnqueuerImpl {
@override
void processDynamicUse(DynamicUse dynamicUse) {
task.measure(() {
task.measureSubtask('codegen.dynamicUse', () {
_worldBuilder.registerDynamicUse(dynamicUse, _applyMemberUse);
});
}
@override
void processStaticUse(StaticUse staticUse) {
_worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
switch (staticUse.kind) {
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
processTypeUse(new TypeUse.instantiation(staticUse.type));
break;
case StaticUseKind.INLINING:
// TODO(johnniwinther): Should this be tracked with _MemberUsage ?
listener.registerUsedElement(staticUse.element);
break;
default:
break;
}
task.measureSubtask('codegen.staticUse', () {
_worldBuilder.registerStaticUse(staticUse, _applyMemberUse);
switch (staticUse.kind) {
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
processTypeUse(new TypeUse.instantiation(staticUse.type));
break;
case StaticUseKind.INLINING:
// TODO(johnniwinther): Should this be tracked with _MemberUsage ?
listener.registerUsedElement(staticUse.element);
break;
default:
break;
}
});
}
@override
@ -229,7 +231,7 @@ class CodegenEnqueuer extends EnqueuerImpl {
@override
void processConstantUse(ConstantUse constantUse) {
task.measure(() {
task.measureSubtask('codegen.constantUse', () {
if (_worldBuilder.registerConstantUse(constantUse)) {
applyImpact(listener.registerUsedConstant(constantUse.value));
_recentConstants = true;

View file

@ -53,7 +53,7 @@ class ParameterStubGenerator {
_closedWorld.elementEnvironment;
bool needsSuperGetter(FunctionEntity element) =>
_codegenWorld.methodsNeedingSuperGetter.contains(element);
_codegenWorld.methodsNeedsSuperGetter(element);
/// Generates stubs to fill in missing optional named or positional arguments
/// and missing type arguments. Returns `null` if no stub is needed.

View file

@ -477,7 +477,7 @@ class ProgramBuilder {
void _addJsInteropStubs(LibrariesMap librariesMap) {
if (_classes.containsKey(_commonElements.objectClass)) {
var toStringInvocation = _namer.invocationName(Selectors.toString_);
js.Name toStringInvocation = _namer.invocationName(Selectors.toString_);
// TODO(jacobr): register toString as used so that it is always accessible
// from JavaScript.
_classes[_commonElements.objectClass].callStubs.add(_buildStubMethod(
@ -492,22 +492,23 @@ class ProgramBuilder {
// a regular getter that returns a JavaScript function and tearing off
// a method in the case where there exist multiple JavaScript classes
// that conflict on whether the member is a getter or a method.
var interceptorClass = _classes[_commonElements.jsJavaScriptObjectClass];
var stubNames = new Set<String>();
Class interceptorClass = _classes[_commonElements.jsJavaScriptObjectClass];
Set<String> stubNames = {};
librariesMap
.forEach((LibraryEntity library, List<ClassEntity> classElements, _) {
for (ClassEntity cls in classElements) {
if (_nativeData.isJsInteropClass(cls)) {
_elementEnvironment.forEachLocalClassMember(cls,
(MemberEntity member) {
var jsName = _nativeData.computeUnescapedJSInteropName(member.name);
String jsName =
_nativeData.computeUnescapedJSInteropName(member.name);
if (!member.isInstanceMember) return;
if (member.isGetter || member.isField || member.isFunction) {
var selectors =
Iterable<Selector> selectors =
_codegenWorld.getterInvocationsByName(member.name);
if (selectors != null && !selectors.isEmpty) {
for (var selector in selectors.keys) {
var stubName = _namer.invocationName(selector);
for (Selector selector in selectors) {
js.Name stubName = _namer.invocationName(selector);
if (stubNames.add(stubName.key)) {
interceptorClass.callStubs.add(_buildStubMethod(stubName,
js.js('function(obj) { return obj.# }', [jsName]),
@ -518,7 +519,7 @@ class ProgramBuilder {
}
if (member.isSetter || (member.isField && !member.isConst)) {
var selectors =
Iterable<Selector> selectors =
_codegenWorld.setterInvocationsByName(member.name);
if (selectors != null && !selectors.isEmpty) {
var stubName = _namer.setterForMember(member);
@ -765,11 +766,11 @@ class ProgramBuilder {
assert(!field.needsUncheckedSetter);
FieldEntity element = field.element;
js.Expression code = _generatedCode[element];
assert(code != null, "No setter code for field: $field");
if (code == null) {
// TODO(johnniwinther): Static types are not honoured in the dynamic
// uses created in codegen, leading to dead code, as known by the
// closed world computation, being triggered by the codegen
// enqueuer. We cautiously generate an empty function for this case.
// This should never occur because codegen member usage is now
// limited by closed world member usage. In the case we've missed a
// spot we cautiously generate an empty function.
code = js.js("function() {}");
}
js.Name name = _namer.deriveSetterName(field.accessorName);
@ -901,9 +902,8 @@ class ProgramBuilder {
isClosureCallMethod = true;
} else {
// Careful with operators.
canTearOff = _codegenWorld.hasInvokedGetter(element);
assert(canTearOff ||
!_codegenWorld.methodsNeedingSuperGetter.contains(element));
canTearOff = _codegenWorld.hasInvokedGetter(element) ||
_codegenWorld.methodsNeedsSuperGetter(element);
tearOffName = _namer.getterForElement(element);
}
}

View file

@ -1084,11 +1084,12 @@ class FragmentEmitter {
js.Expression code;
if (field.isElided) {
ConstantValue constantValue = field.constantValue;
assert(
constantValue != null, "No constant value for elided field: $field");
if (constantValue == null) {
// TODO(johnniwinther): Static types are not honoured in the dynamic
// uses created in codegen, leading to dead code, as known by the closed
// world computation, being triggered by the codegen enqueuer. We
// cautiously generate a null constant for this case.
// This should never occur because codegen member usage is now limited
// by closed world member usage. In the case we've missed a spot we
// cautiously generate a null constant.
constantValue = new NullConstantValue();
}
code = js.js(

View file

@ -32,6 +32,7 @@ import '../serialization/serialization.dart';
import '../universe/class_hierarchy.dart';
import '../universe/class_set.dart';
import '../universe/function_set.dart' show FunctionSet;
import '../universe/member_usage.dart';
import '../universe/selector.dart';
import '../world.dart';
import 'element_map.dart';
@ -93,6 +94,8 @@ class JsClosedWorld implements JClosedWorld {
final OutputUnitData outputUnitData;
Sorter _sorter;
final Map<MemberEntity, MemberAccess> memberAccess;
JsClosedWorld(
this.elementMap,
this.nativeData,
@ -113,7 +116,8 @@ class JsClosedWorld implements JClosedWorld {
this.annotationsData,
this.globalLocalsMap,
this.closureDataLookup,
this.outputUnitData) {
this.outputUnitData,
this.memberAccess) {
_abstractValueDomain = abstractValueStrategy.createDomain(this);
}
@ -167,6 +171,9 @@ class JsClosedWorld implements JClosedWorld {
OutputUnitData outputUnitData =
new OutputUnitData.readFromDataSource(source);
Map<MemberEntity, MemberAccess> memberAccess =
source.readMemberMap(() => new MemberAccess.readFromDataSource(source));
source.end(tag);
return new JsClosedWorld(
@ -189,7 +196,8 @@ class JsClosedWorld implements JClosedWorld {
annotationsData,
globalLocalsMap,
closureData,
outputUnitData);
outputUnitData,
memberAccess);
}
/// Serializes this [JsClosedWorld] to [sink].
@ -217,6 +225,8 @@ class JsClosedWorld implements JClosedWorld {
annotationsData.writeToDataSink(sink);
closureDataLookup.writeToDataSink(sink);
outputUnitData.writeToDataSink(sink);
sink.writeMemberMap(
memberAccess, (MemberAccess access) => access.writeToDataSink(sink));
sink.end(tag);
}
@ -539,6 +549,11 @@ class JsClosedWorld implements JClosedWorld {
return elementEnvironment.isMixinApplication(cls) &&
!elementEnvironment.isUnnamedMixinApplication(cls);
}
@override
MemberAccess getMemberAccess(MemberEntity member) {
return memberAccess[member];
}
}
class KernelSorter implements Sorter {

View file

@ -29,6 +29,7 @@ import '../options.dart';
import '../universe/class_hierarchy.dart';
import '../universe/class_set.dart';
import '../universe/feature.dart';
import '../universe/member_usage.dart';
import '../universe/selector.dart';
import '../world.dart';
import 'closure.dart';
@ -202,6 +203,11 @@ class JsClosedWorldBuilder {
OutputUnitData outputUnitData =
_convertOutputUnitData(map, kOutputUnitData, closureData);
Map<MemberEntity, MemberAccess> memberAccess = map.toBackendMemberMap(
closedWorld.liveMemberUsage,
(MemberUsage usage) =>
new MemberAccess(usage.reads, usage.writes, usage.invokes));
return new JsClosedWorld(
_elementMap,
nativeData,
@ -226,7 +232,8 @@ class JsClosedWorldBuilder {
annotationsData,
_globalLocalsMap,
closureData,
outputUnitData);
outputUnitData,
memberAccess);
}
BackendUsage _convertBackendUsage(
@ -487,7 +494,7 @@ class JsClosedWorldBuilder {
map.toBackendLibrary,
convertClassMap,
convertMemberMap,
(m) => convertMap<ConstantValue, OutputUnit>(
(m) => convertMap<ConstantValue, OutputUnit, OutputUnit>(
m, map.toBackendConstant, (v) => v));
}
}
@ -633,20 +640,20 @@ abstract class JsToFrontendMap {
return convertMap(map, toBackendClass, convert);
}
Map<MemberEntity, V> toBackendMemberMap<V>(
Map<MemberEntity, V> map, V convert(V value)) {
Map<MemberEntity, V2> toBackendMemberMap<V1, V2>(
Map<MemberEntity, V1> map, V2 convert(V1 value)) {
return convertMap(map, toBackendMember, convert);
}
}
E identity<E>(E element) => element;
Map<K, V> convertMap<K, V>(
Map<K, V> map, K convertKey(K key), V convertValue(V value)) {
Map<K, V> newMap = <K, V>{};
map.forEach((K key, V value) {
Map<K, V2> convertMap<K, V1, V2>(
Map<K, V1> map, K convertKey(K key), V2 convertValue(V1 value)) {
Map<K, V2> newMap = <K, V2>{};
map.forEach((K key, V1 value) {
K newKey = convertKey(key);
V newValue = convertValue(value);
V2 newValue = convertValue(value);
if (newKey != null && newValue != null) {
// Entities that are not used don't have a corresponding backend entity.
newMap[newKey] = newValue;

View file

@ -1979,6 +1979,8 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
// for this to work.
assert(selector.applies(target),
failedAt(node, '$selector does not apply to $target'));
assert(!selector.isGetter && !selector.isSetter,
"Unexpected direct invocation selector: $selector.");
_registry.registerStaticUse(new StaticUse.directInvoke(
target, selector.callStructure, node.typeArguments));
} else {

View file

@ -46,9 +46,9 @@ abstract class CodegenWorld extends BuiltWorld {
Map<Selector, SelectorConstraints> invocationsByName(String name);
Map<Selector, SelectorConstraints> getterInvocationsByName(String name);
Iterable<Selector> getterInvocationsByName(String name);
Map<Selector, SelectorConstraints> setterInvocationsByName(String name);
Iterable<Selector> setterInvocationsByName(String name);
void forEachInvokedName(
f(String name, Map<Selector, SelectorConstraints> selectors));
@ -68,7 +68,7 @@ abstract class CodegenWorld extends BuiltWorld {
/// All directly or indirectly instantiated classes.
Iterable<ClassEntity> get instantiatedClasses;
Iterable<FunctionEntity> get methodsNeedingSuperGetter;
bool methodsNeedsSuperGetter(FunctionEntity function);
/// The calls [f] for all static fields.
void forEachStaticField(void Function(FieldEntity) f);
@ -111,8 +111,6 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
/// Classes implemented by directly instantiated classes.
final Set<ClassEntity> _implementedClasses = new Set<ClassEntity>();
final Set<FunctionEntity> _methodsNeedingSuperGetter =
new Set<FunctionEntity>();
final Map<String, Map<Selector, SelectorConstraints>> _invokedNames =
<String, Map<Selector, SelectorConstraints>>{};
final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters =
@ -129,14 +127,22 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
final Map<MemberEntity, MemberUsage> _memberUsage =
<MemberEntity, MemberUsage>{};
/// Map containing instance members of live classes that are not yet live
/// themselves.
final Map<String, Set<MemberUsage>> _instanceMembersByName =
/// Map containing instance members of live classes that have not yet been
/// fully invoked dynamically.
///
/// A method is fully invoked if all is optional parameter have been passed
/// in some invocation.
final Map<String, Set<MemberUsage>> _invokableInstanceMembersByName =
<String, Set<MemberUsage>>{};
/// Map containing instance methods of live classes that are not yet
/// closurized.
final Map<String, Set<MemberUsage>> _instanceFunctionsByName =
/// Map containing instance members of live classes that have not yet been
/// read from dynamically.
final Map<String, Set<MemberUsage>> _readableInstanceMembersByName =
<String, Set<MemberUsage>>{};
/// Map containing instance members of live classes that have not yet been
/// written to dynamically.
final Map<String, Set<MemberUsage>> _writableInstanceMembersByName =
<String, Set<MemberUsage>>{};
final Set<DartType> _isChecks = new Set<DartType>();
@ -208,35 +214,51 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
return false;
}
bool hasInvocation(MemberEntity member) {
return _hasMatchingSelector(
_invokedNames[member.name], member, _closedWorld);
Iterable<CallStructure> _getMatchingCallStructures(
Map<Selector, SelectorConstraints> selectors, MemberEntity member) {
if (selectors == null) return const <CallStructure>[];
Set<CallStructure> callStructures;
for (Selector selector in selectors.keys) {
if (selector.appliesUnnamed(member)) {
SelectorConstraints masks = selectors[selector];
if (masks.canHit(member, selector.memberName, _closedWorld)) {
callStructures ??= new Set<CallStructure>();
callStructures.add(selector.callStructure);
}
}
}
return callStructures ?? const <CallStructure>[];
}
bool hasInvokedGetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedGetters[member.name], member, _closedWorld) ||
member.isFunction && _methodsNeedingSuperGetter.contains(member);
Iterable<CallStructure> _getInvocationCallStructures(MemberEntity member) {
return _getMatchingCallStructures(_invokedNames[member.name], member);
}
bool hasInvokedSetter(MemberEntity member) {
bool _hasInvokedGetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedGetters[member.name], member, _closedWorld);
}
bool _hasInvokedSetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedSetters[member.name], member, _closedWorld);
}
bool registerDynamicUse(
void registerDynamicUse(
DynamicUse dynamicUse, MemberUsedCallback memberUsed) {
Selector selector = dynamicUse.selector;
String methodName = selector.name;
void _process(Map<String, Set<MemberUsage>> memberMap,
EnumSet<MemberUse> action(MemberUsage usage)) {
void _process(
Map<String, Set<MemberUsage>> memberMap,
EnumSet<MemberUse> action(MemberUsage usage),
bool shouldBeRemoved(MemberUsage usage)) {
_processSet(memberMap, methodName, (MemberUsage usage) {
if (selector.appliesUnnamed(usage.entity) &&
_selectorConstraintsStrategy.appliedUnnamed(
dynamicUse, usage.entity, _closedWorld)) {
memberUsed(usage.entity, action(usage));
return true;
return shouldBeRemoved(usage);
}
return false;
});
@ -247,27 +269,31 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
registerDynamicInvocation(
dynamicUse.selector, dynamicUse.typeArguments);
if (_registerNewSelector(dynamicUse, _invokedNames)) {
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structure.
_process(_instanceMembersByName, (m) => m.invoke(null));
return true;
_process(
_invokableInstanceMembersByName,
(m) => m.invoke(Accesses.dynamicAccess, selector.callStructure),
// If not all optional parameters have been passed in invocations
// we must keep the member in [_invokableInstanceMembersByName].
(u) => !u.hasPendingDynamicInvoke);
}
break;
case DynamicUseKind.GET:
if (_registerNewSelector(dynamicUse, _invokedGetters)) {
_process(_instanceMembersByName, (m) => m.read());
_process(_instanceFunctionsByName, (m) => m.read());
return true;
_process(
_readableInstanceMembersByName,
(m) => m.read(Accesses.dynamicAccess),
(u) => !u.hasPendingDynamicRead);
}
break;
case DynamicUseKind.SET:
if (_registerNewSelector(dynamicUse, _invokedSetters)) {
_process(_instanceMembersByName, (m) => m.write());
return true;
_process(
_writableInstanceMembersByName,
(m) => m.write(Accesses.dynamicAccess),
(u) => !u.hasPendingDynamicWrite);
}
break;
}
return false;
}
bool _registerNewSelector(DynamicUse dynamicUse,
@ -296,32 +322,43 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
MemberUsage usage = _getMemberUsage(element, useSet);
switch (staticUse.kind) {
case StaticUseKind.STATIC_TEAR_OFF:
useSet.addAll(usage.read());
useSet.addAll(usage.read(Accesses.staticAccess));
break;
case StaticUseKind.FIELD_GET:
case StaticUseKind.FIELD_SET:
case StaticUseKind.INSTANCE_FIELD_GET:
case StaticUseKind.INSTANCE_FIELD_SET:
case StaticUseKind.CALL_METHOD:
// TODO(johnniwinther): Avoid this. Currently [FIELD_GET] and
// [FIELD_SET] contains [BoxFieldElement]s which we cannot enqueue.
// Also [CLOSURE] contains [LocalFunctionElement] which we cannot
// enqueue.
break;
case StaticUseKind.INVOKE:
case StaticUseKind.SUPER_INVOKE:
registerStaticInvocation(staticUse);
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structure.
useSet.addAll(usage.invoke(null));
useSet.addAll(
usage.invoke(Accesses.superAccess, staticUse.callStructure));
break;
case StaticUseKind.STATIC_INVOKE:
registerStaticInvocation(staticUse);
useSet.addAll(
usage.invoke(Accesses.staticAccess, staticUse.callStructure));
break;
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.SET:
useSet.addAll(usage.write());
useSet.addAll(usage.write(Accesses.superAccess));
break;
case StaticUseKind.SUPER_SETTER_SET:
useSet.addAll(usage.write(Accesses.superAccess));
break;
case StaticUseKind.STATIC_SET:
useSet.addAll(usage.write(Accesses.staticAccess));
break;
case StaticUseKind.SUPER_TEAR_OFF:
_methodsNeedingSuperGetter.add(element);
useSet.addAll(usage.read());
useSet.addAll(usage.read(Accesses.superAccess));
break;
case StaticUseKind.GET:
useSet.addAll(usage.read());
case StaticUseKind.SUPER_GET:
useSet.addAll(usage.read(Accesses.superAccess));
break;
case StaticUseKind.STATIC_GET:
useSet.addAll(usage.read(Accesses.staticAccess));
break;
case StaticUseKind.FIELD_INIT:
useSet.addAll(usage.init());
@ -333,14 +370,15 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structure.
useSet.addAll(usage.invoke(null));
useSet.addAll(
usage.invoke(Accesses.staticAccess, staticUse.callStructure));
break;
case StaticUseKind.DIRECT_INVOKE:
MemberEntity member = staticUse.element;
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structure.
useSet.addAll(usage.invoke(null));
_instanceMembersByName[usage.entity.name]?.remove(usage);
useSet.addAll(
usage.invoke(Accesses.staticAccess, staticUse.callStructure));
if (staticUse.typeArguments?.isNotEmpty ?? false) {
registerDynamicInvocation(
new Selector.call(member.memberName, staticUse.callStructure),
@ -361,85 +399,99 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
}
void processClassMembers(ClassEntity cls, MemberUsedCallback memberUsed,
{bool dryRun: false}) {
{bool checkEnqueuerConsistency: false}) {
_elementEnvironment.forEachClassMember(cls,
(ClassEntity cls, MemberEntity member) {
_processInstantiatedClassMember(cls, member, memberUsed, dryRun: dryRun);
_processInstantiatedClassMember(cls, member, memberUsed,
checkEnqueuerConsistency: checkEnqueuerConsistency);
});
}
void _processInstantiatedClassMember(
ClassEntity cls, MemberEntity member, MemberUsedCallback memberUsed,
{bool dryRun: false}) {
{bool checkEnqueuerConsistency: false}) {
if (!member.isInstanceMember) return;
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
_getMemberUsage(member, useSet);
MemberUsage usage = _getMemberUsage(member, useSet);
if (useSet.isNotEmpty) {
memberUsed(member, useSet);
if (checkEnqueuerConsistency) {
throw new SpannableAssertionFailure(member,
'Unenqueued usage of $member: \nbefore: <none>\nafter : $usage');
} else {
memberUsed(member, useSet);
}
}
}
MemberUsage _getMemberUsage(MemberEntity member, EnumSet<MemberUse> useSet,
{bool dryRun: false}) {
{bool checkEnqueuerConsistency: false}) {
// TODO(johnniwinther): Change [TypeMask] to not apply to a superclass
// member unless the class has been instantiated. Similar to
// [StrongModeConstraint].
MemberUsage usage = _memberUsage[member];
if (usage == null) {
MemberAccess potentialAccess = _closedWorld.getMemberAccess(member);
if (member.isInstanceMember) {
String memberName = member.name;
ClassEntity cls = member.enclosingClass;
bool isNative = _nativeBasicData.isNativeClass(cls);
usage = new MemberUsage(member);
usage = new MemberUsage(member, potentialAccess: potentialAccess);
if (member.isField && !isNative) {
useSet.addAll(usage.init());
}
if (member is JSignatureMethod) {
// We mark signature methods as "always used" to prevent them from being
// optimized away.
// We mark signature methods as "always used" to prevent them from
// being optimized away.
// TODO(johnniwinther): Make this a part of the regular enqueueing.
useSet.addAll(usage.invoke(CallStructure.NO_ARGS));
useSet.addAll(
usage.invoke(Accesses.dynamicAccess, CallStructure.NO_ARGS));
}
if (!usage.hasRead && hasInvokedGetter(member)) {
useSet.addAll(usage.read());
if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
useSet.addAll(usage.read(Accesses.dynamicAccess));
}
if (!usage.hasWrite && hasInvokedSetter(member)) {
useSet.addAll(usage.write());
if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
useSet.addAll(usage.write(Accesses.dynamicAccess));
}
if (!usage.hasInvoke && hasInvocation(member)) {
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structures.
useSet.addAll(usage.invoke(null));
if (usage.hasPendingDynamicInvoke) {
Iterable<CallStructure> callStructures =
_getInvocationCallStructures(member);
for (CallStructure callStructure in callStructures) {
useSet.addAll(usage.invoke(Accesses.dynamicAccess, callStructure));
if (!usage.hasPendingDynamicInvoke) {
break;
}
}
}
if (!dryRun) {
if (usage.hasPendingClosurizationUse) {
// Store the member in [instanceFunctionsByName] to catch
// getters on the function.
_instanceFunctionsByName
.putIfAbsent(usage.entity.name, () => new Set<MemberUsage>())
if (!checkEnqueuerConsistency) {
if (usage.hasPendingDynamicInvoke) {
_invokableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
if (usage.hasPendingNormalUse) {
// The element is not yet used. Add it to the list of instance
// members to still be processed.
_instanceMembersByName
.putIfAbsent(memberName, () => new Set<MemberUsage>())
if (usage.hasPendingDynamicRead) {
_readableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
if (usage.hasPendingDynamicWrite) {
_writableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
}
} else {
usage = new MemberUsage(member);
usage = new MemberUsage(member, potentialAccess: potentialAccess);
if (member.isField) {
useSet.addAll(usage.init());
}
}
if (!dryRun) {
if (!checkEnqueuerConsistency) {
_memberUsage[member] = usage;
}
} else {
if (dryRun) {
if (checkEnqueuerConsistency) {
usage = usage.clone();
}
}
@ -521,7 +573,6 @@ class CodegenWorldBuilderImpl extends WorldBuilderBase
constTypeLiterals: _constTypeLiterals,
directlyInstantiatedClasses: directlyInstantiatedClasses,
typeVariableTypeLiterals: typeVariableTypeLiterals,
methodsNeedingSuperGetter: _methodsNeedingSuperGetter,
instantiatedClasses: instantiatedClasses,
isChecks: _isChecks,
instantiatedTypes: _instantiatedTypes,
@ -549,9 +600,6 @@ class CodegenWorldImpl implements CodegenWorld {
@override
final Iterable<TypeVariableType> typeVariableTypeLiterals;
@override
final Iterable<FunctionEntity> methodsNeedingSuperGetter;
@override
final Iterable<ClassEntity> instantiatedClasses;
@ -580,7 +628,6 @@ class CodegenWorldImpl implements CodegenWorld {
{this.constTypeLiterals,
this.directlyInstantiatedClasses,
this.typeVariableTypeLiterals,
this.methodsNeedingSuperGetter,
this.instantiatedClasses,
this.isChecks,
this.instantiatedTypes,
@ -754,15 +801,23 @@ class CodegenWorldImpl implements CodegenWorld {
@override
bool hasInvokedGetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedGetters[member.name], member, _closedWorld) ||
member.isFunction && methodsNeedingSuperGetter.contains(member);
MemberUsage memberUsage = _liveMemberUsage[member];
if (memberUsage == null) return false;
return memberUsage.reads.contains(Access.dynamicAccess);
}
@override
bool methodsNeedsSuperGetter(FunctionEntity function) {
MemberUsage memberUsage = _liveMemberUsage[function];
if (memberUsage == null) return false;
return memberUsage.reads.contains(Access.superAccess);
}
@override
bool hasInvokedSetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedSetters[member.name], member, _closedWorld);
MemberUsage memberUsage = _liveMemberUsage[member];
if (memberUsage == null) return false;
return memberUsage.writes.contains(Access.dynamicAccess);
}
Map<Selector, SelectorConstraints> _asUnmodifiable(
@ -777,13 +832,13 @@ class CodegenWorldImpl implements CodegenWorld {
}
@override
Map<Selector, SelectorConstraints> getterInvocationsByName(String name) {
return _asUnmodifiable(_invokedGetters[name]);
Iterable<Selector> getterInvocationsByName(String name) {
return _invokedGetters[name]?.keys;
}
@override
Map<Selector, SelectorConstraints> setterInvocationsByName(String name) {
return _asUnmodifiable(_invokedSetters[name]);
Iterable<Selector> setterInvocationsByName(String name) {
return _invokedSetters[name]?.keys;
}
@override

File diff suppressed because it is too large Load diff

View file

@ -66,8 +66,12 @@ abstract class ResolutionEnqueuerWorldBuilder extends ResolutionWorldBuilder {
/// Computes usage for all members declared by [cls]. Calls [membersUsed] with
/// the usage changes for each member.
///
/// If [checkEnqueuerConsistency] is `true` we check that no new member
/// usage can be found. This check is performed without changing the already
/// collected member usage.
void processClassMembers(ClassEntity cls, MemberUsedCallback memberUsed,
{bool dryRun: false});
{bool checkEnqueuerConsistency: false});
/// Applies the [dynamicUse] to applicable instance members. Calls
/// [membersUsed] with the usage changes for each member.
@ -288,14 +292,22 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
Map<MemberEntity, MemberUsage> get memberUsageForTesting => _memberUsage;
/// Map containing instance members of live classes that are not yet fully
/// live themselves.
final Map<String, Set<MemberUsage>> _instanceMembersByName =
/// Map containing instance members of live classes that have not yet been
/// fully invoked dynamically.
///
/// A method is fully invoked if all is optional parameter have been passed
/// in some invocation.
final Map<String, Set<MemberUsage>> _invokableInstanceMembersByName =
<String, Set<MemberUsage>>{};
/// Map containing instance methods of live classes that are not yet
/// closurized.
final Map<String, Set<MemberUsage>> _instanceFunctionsByName =
/// Map containing instance members of live classes that have not yet been
/// read from dynamically.
final Map<String, Set<MemberUsage>> _readableInstanceMembersByName =
<String, Set<MemberUsage>>{};
/// Map containing instance members of live classes that have not yet been
/// written to dynamically.
final Map<String, Set<MemberUsage>> _writableInstanceMembersByName =
<String, Set<MemberUsage>>{};
final Set<FieldEntity> _fieldSetters = new Set<FieldEntity>();
@ -490,8 +502,7 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
}
bool _hasInvokedGetter(MemberEntity member) {
return _hasMatchingSelector(_invokedGetters[member.name], member) ||
member.isFunction && _methodsNeedingSuperGetter.contains(member);
return _hasMatchingSelector(_invokedGetters[member.name], member);
}
bool _hasInvokedSetter(MemberEntity member) {
@ -525,23 +536,36 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
dynamicUse.selector, dynamicUse.typeArguments);
if (_registerNewSelector(dynamicUse, _invokedNames)) {
_process(
_instanceMembersByName,
(m) => m.invoke(dynamicUse.selector.callStructure),
(u) => !u.hasPendingNormalUse);
_invokableInstanceMembersByName,
(m) => m.invoke(
Accesses.dynamicAccess, dynamicUse.selector.callStructure),
// If not all optional parameters have been passed in invocations
// we must keep the member in [_invokableInstanceMembersByName].
// TODO(johnniwinther): Also remove from
// [_readableInstanceMembersByName] in case of getters/setters.
(u) => !u.hasPendingDynamicInvoke);
}
break;
case DynamicUseKind.GET:
if (_registerNewSelector(dynamicUse, _invokedGetters)) {
_process(_instanceMembersByName, (m) => m.read(),
(u) => !u.hasPendingNormalUse);
_process(_instanceFunctionsByName, (m) => m.read(),
(u) => !u.hasPendingClosurizationUse);
_process(
_readableInstanceMembersByName,
(m) => m.read(Accesses.dynamicAccess),
// TODO(johnniwinther): Members cannot be partially read so
// we should always remove them.
// TODO(johnniwinther): Also remove from
// [_invokableInstanceMembersByName] in case of methods.
(u) => !u.hasPendingDynamicRead);
}
break;
case DynamicUseKind.SET:
if (_registerNewSelector(dynamicUse, _invokedSetters)) {
_process(_instanceMembersByName, (m) => m.write(),
(u) => !u.hasPendingNormalUse);
_process(
_writableInstanceMembersByName,
(m) => m.write(Accesses.dynamicAccess),
// TODO(johnniwinther): Members cannot be partially written so
// we should always remove them.
(u) => !u.hasPendingDynamicWrite);
}
break;
}
@ -604,10 +628,11 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
// [FIELD_SET] contains [BoxFieldElement]s which we cannot enqueue.
// Also [CLOSURE] contains [LocalFunctionElement] which we cannot
// enqueue.
switch (staticUse.kind) {
case StaticUseKind.FIELD_GET:
case StaticUseKind.INSTANCE_FIELD_GET:
break;
case StaticUseKind.FIELD_SET:
case StaticUseKind.INSTANCE_FIELD_SET:
_fieldSetters.add(staticUse.element);
break;
case StaticUseKind.CLOSURE:
@ -615,21 +640,27 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
// Already handled above.
break;
case StaticUseKind.SUPER_TEAR_OFF:
useSet.addAll(usage.read());
useSet.addAll(usage.read(Accesses.superAccess));
_methodsNeedingSuperGetter.add(staticUse.element);
break;
case StaticUseKind.SUPER_FIELD_SET:
_fieldSetters.add(staticUse.element);
useSet.addAll(usage.write());
useSet.addAll(usage.write(Accesses.superAccess));
break;
case StaticUseKind.GET:
useSet.addAll(usage.read());
case StaticUseKind.SUPER_GET:
useSet.addAll(usage.read(Accesses.superAccess));
break;
case StaticUseKind.STATIC_GET:
useSet.addAll(usage.read(Accesses.staticAccess));
break;
case StaticUseKind.STATIC_TEAR_OFF:
useSet.addAll(usage.read());
useSet.addAll(usage.read(Accesses.staticAccess));
break;
case StaticUseKind.SET:
useSet.addAll(usage.write());
case StaticUseKind.SUPER_SETTER_SET:
useSet.addAll(usage.write(Accesses.superAccess));
break;
case StaticUseKind.STATIC_SET:
useSet.addAll(usage.write(Accesses.staticAccess));
break;
case StaticUseKind.FIELD_INIT:
useSet.addAll(usage.init());
@ -637,13 +668,20 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
case StaticUseKind.FIELD_CONSTANT_INIT:
useSet.addAll(usage.constantInit(staticUse.constant));
break;
case StaticUseKind.INVOKE:
case StaticUseKind.SUPER_INVOKE:
registerStaticInvocation(staticUse);
useSet.addAll(usage.invoke(staticUse.callStructure));
useSet.addAll(
usage.invoke(Accesses.superAccess, staticUse.callStructure));
break;
case StaticUseKind.STATIC_INVOKE:
registerStaticInvocation(staticUse);
useSet.addAll(
usage.invoke(Accesses.staticAccess, staticUse.callStructure));
break;
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
useSet.addAll(usage.invoke(staticUse.callStructure));
useSet.addAll(
usage.invoke(Accesses.staticAccess, staticUse.callStructure));
break;
case StaticUseKind.DIRECT_INVOKE:
failedAt(element, 'Direct static use is not supported for resolution.');
@ -692,10 +730,11 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
@override
void processClassMembers(ClassEntity cls, MemberUsedCallback memberUsed,
{bool dryRun: false}) {
{bool checkEnqueuerConsistency: false}) {
_elementEnvironment.forEachClassMember(cls,
(ClassEntity cls, MemberEntity member) {
_processInstantiatedClassMember(cls, member, memberUsed, dryRun: dryRun);
_processInstantiatedClassMember(cls, member, memberUsed,
checkEnqueuerConsistency: checkEnqueuerConsistency);
});
}
@ -720,7 +759,7 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
}
MemberUsage _getMemberUsage(MemberEntity member, EnumSet<MemberUse> useSet,
{bool dryRun: false}) {
{bool checkEnqueuerConsistency: false}) {
MemberUsage usage = _memberUsage[member];
if (usage == null) {
if (member.isInstanceMember) {
@ -736,11 +775,11 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
// Note: this assumes that there are no non-native fields on native
// classes, which may not be the case when a native class is subclassed.
bool isNative = _nativeBasicData.isNativeClass(cls);
usage = new MemberUsage(member, trackParameters: true);
usage = new MemberUsage(member);
if (member.isField && !isNative) {
useSet.addAll(usage.init());
}
if (!dryRun) {
if (!checkEnqueuerConsistency) {
if (member.isField && isNative) {
registerUsedElement(member);
}
@ -751,100 +790,114 @@ class ResolutionWorldBuilderImpl extends WorldBuilderBase
}
}
if (!usage.hasRead && _hasInvokedGetter(member)) {
useSet.addAll(usage.read());
if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
useSet.addAll(usage.read(Accesses.dynamicAccess));
}
if (!usage.isFullyInvoked) {
if (usage.hasPendingDynamicInvoke) {
Iterable<CallStructure> callStructures =
_getInvocationCallStructures(member);
for (CallStructure callStructure in callStructures) {
useSet.addAll(usage.invoke(callStructure));
if (usage.isFullyInvoked) {
useSet.addAll(usage.invoke(Accesses.dynamicAccess, callStructure));
if (!usage.hasPendingDynamicInvoke) {
break;
}
}
}
if (!usage.hasWrite && _hasInvokedSetter(member)) {
useSet.addAll(usage.write());
if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
useSet.addAll(usage.write(Accesses.dynamicAccess));
}
if (!dryRun) {
if (usage.hasPendingNormalUse) {
// The element is not yet used. Add it to the list of instance
// members to still be processed.
_instanceMembersByName
.putIfAbsent(memberName, () => new Set<MemberUsage>())
if (!checkEnqueuerConsistency) {
if (usage.hasPendingDynamicInvoke) {
_invokableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
if (usage.hasPendingClosurizationUse) {
// Store the member in [instanceFunctionsByName] to catch
// getters on the function.
_instanceFunctionsByName
.putIfAbsent(memberName, () => new Set<MemberUsage>())
if (usage.hasPendingDynamicRead) {
_readableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
if (usage.hasPendingDynamicWrite) {
_writableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
}
} else {
usage = new MemberUsage(member, trackParameters: true);
usage = new MemberUsage(member);
if (member.isField) {
useSet.addAll(usage.init());
}
}
if (!dryRun) {
if (!checkEnqueuerConsistency) {
_memberUsage[member] = usage;
}
}
return usage;
}
void _processInstantiatedClassMember(ClassEntity cls,
covariant MemberEntity member, MemberUsedCallback memberUsed,
{bool dryRun: false}) {
void _processInstantiatedClassMember(
ClassEntity cls, MemberEntity member, MemberUsedCallback memberUsed,
{bool checkEnqueuerConsistency: false}) {
if (!member.isInstanceMember) return;
String memberName = member.name;
MemberUsage usage = _memberUsage[member];
if (usage == null) {
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
usage = _getMemberUsage(member, useSet, dryRun: dryRun);
memberUsed(usage.entity, useSet);
usage = _getMemberUsage(member, useSet,
checkEnqueuerConsistency: checkEnqueuerConsistency);
if (useSet.isNotEmpty) {
if (checkEnqueuerConsistency) {
throw new SpannableAssertionFailure(member,
'Unenqueued usage of $member: \nbefore: <none>\nafter : $usage');
} else {
memberUsed(usage.entity, useSet);
}
}
} else {
MemberUsage original = usage;
if (dryRun) {
if (checkEnqueuerConsistency) {
usage = usage.clone();
}
if (!usage.fullyUsed) {
if (usage.hasPendingDynamicUse) {
EnumSet<MemberUse> useSet = new EnumSet<MemberUse>();
if (!usage.hasRead && _hasInvokedGetter(member)) {
useSet.addAll(usage.read());
if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
useSet.addAll(usage.read(Accesses.dynamicAccess));
}
if (!usage.isFullyInvoked) {
if (usage.hasPendingDynamicInvoke) {
Iterable<CallStructure> callStructures =
_getInvocationCallStructures(member);
for (CallStructure callStructure in callStructures) {
useSet.addAll(usage.invoke(callStructure));
if (usage.isFullyInvoked) {
useSet.addAll(usage.invoke(Accesses.dynamicAccess, callStructure));
if (!usage.hasPendingDynamicInvoke) {
break;
}
}
}
if (!usage.hasWrite && _hasInvokedSetter(member)) {
useSet.addAll(usage.write());
if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
useSet.addAll(usage.write(Accesses.dynamicAccess));
}
if (!dryRun) {
if (!usage.hasPendingNormalUse) {
_instanceMembersByName[memberName]?.remove(usage);
if (!checkEnqueuerConsistency) {
if (!usage.hasPendingDynamicRead) {
_readableInstanceMembersByName[memberName]?.remove(usage);
}
if (!usage.hasPendingClosurizationUse) {
_instanceFunctionsByName[memberName]?.remove(usage);
if (!usage.hasPendingDynamicInvoke) {
_invokableInstanceMembersByName[memberName]?.remove(usage);
}
if (!usage.hasPendingDynamicWrite) {
_writableInstanceMembersByName[memberName]?.remove(usage);
}
}
if (checkEnqueuerConsistency && !original.dataEquals(usage)) {
_elementMap.reporter.internalError(
member,
'Unenqueued usage of $member: \n'
'before: $original\nafter : $usage');
}
memberUsed(usage.entity, useSet);
}
if (dryRun && !original.dataEquals(usage)) {
_elementMap.reporter.internalError(member,
'Unenqueued usage of $member: before: $original, after: $usage');
}
}
}

View file

@ -147,8 +147,11 @@ enum StaticUseKind {
STATIC_TEAR_OFF,
SUPER_TEAR_OFF,
SUPER_FIELD_SET,
FIELD_GET,
FIELD_SET,
SUPER_GET,
SUPER_SETTER_SET,
SUPER_INVOKE,
INSTANCE_FIELD_GET,
INSTANCE_FIELD_SET,
CLOSURE,
CLOSURE_CALL,
CALL_METHOD,
@ -156,9 +159,9 @@ enum StaticUseKind {
CONST_CONSTRUCTOR_INVOKE,
DIRECT_INVOKE,
INLINING,
INVOKE,
GET,
SET,
STATIC_INVOKE,
STATIC_GET,
STATIC_SET,
FIELD_INIT,
FIELD_CONSTANT_INIT,
}
@ -197,9 +200,10 @@ class StaticUse {
String get shortText {
StringBuffer sb = new StringBuffer();
switch (kind) {
case StaticUseKind.FIELD_SET:
case StaticUseKind.INSTANCE_FIELD_SET:
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.SET:
case StaticUseKind.SUPER_SETTER_SET:
case StaticUseKind.STATIC_SET:
sb.write('set:');
break;
case StaticUseKind.FIELD_INIT:
@ -268,8 +272,8 @@ class StaticUse {
failedAt(element,
"Not CallStructure for static invocation of element $element."));
return new GenericStaticUse(element, StaticUseKind.INVOKE, callStructure,
typeArguments, deferredImport);
return new GenericStaticUse(element, StaticUseKind.STATIC_INVOKE,
callStructure, typeArguments, deferredImport);
}
/// Closurization of a static or top-level function [element].
@ -300,7 +304,7 @@ class StaticUse {
element.isField || element.isGetter,
failedAt(element,
"Static get element $element must be a field or a getter."));
return new StaticUse.internal(element, StaticUseKind.GET,
return new StaticUse.internal(element, StaticUseKind.STATIC_GET,
deferredImport: deferredImport);
}
@ -317,7 +321,7 @@ class StaticUse {
(element.isField && element.isAssignable) || element.isSetter,
failedAt(element,
"Static set element $element must be a field or a setter."));
return new StaticUse.internal(element, StaticUseKind.SET,
return new StaticUse.internal(element, StaticUseKind.STATIC_SET,
deferredImport: deferredImport);
}
@ -348,7 +352,7 @@ class StaticUse {
failedAt(element,
"Not CallStructure for super invocation of element $element."));
return new GenericStaticUse(
element, StaticUseKind.INVOKE, callStructure, typeArguments);
element, StaticUseKind.SUPER_INVOKE, callStructure, typeArguments);
}
/// Read access of a super field or getter [element].
@ -361,7 +365,7 @@ class StaticUse {
element.isField || element.isGetter,
failedAt(element,
"Super get element $element must be a field or a getter."));
return new StaticUse.internal(element, StaticUseKind.GET);
return new StaticUse.internal(element, StaticUseKind.SUPER_GET);
}
/// Write access of a super field [element].
@ -383,7 +387,7 @@ class StaticUse {
element, "Super set element $element must be an instance method."));
assert(element.isSetter,
failedAt(element, "Super set element $element must be a setter."));
return new StaticUse.internal(element, StaticUseKind.SET);
return new StaticUse.internal(element, StaticUseKind.SUPER_SETTER_SET);
}
/// Closurization of a super method [element].
@ -411,7 +415,7 @@ class StaticUse {
element,
"Not CallStructure for super constructor invocation of element "
"$element."));
return new StaticUse.internal(element, StaticUseKind.INVOKE,
return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
callStructure: callStructure);
}
@ -425,14 +429,14 @@ class StaticUse {
element,
"Not CallStructure for constructor body invocation of element "
"$element."));
return new StaticUse.internal(element, StaticUseKind.INVOKE,
return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
callStructure: callStructure);
}
/// Direct invocation of a generator (body) [element], as a static call or
/// through a this or super constructor call.
factory StaticUse.generatorBodyInvoke(FunctionEntity element) {
return new StaticUse.internal(element, StaticUseKind.INVOKE,
return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
callStructure: CallStructure.NO_ARGS);
}
@ -459,7 +463,7 @@ class StaticUse {
element.isField || element.isGetter,
failedAt(element,
"Direct get element $element must be a field or a getter."));
return new StaticUse.internal(element, StaticUseKind.GET);
return new StaticUse.internal(element, StaticUseKind.STATIC_GET);
}
/// Direct write access of a field [element].
@ -470,7 +474,7 @@ class StaticUse {
"Direct set element $element must be an instance member."));
assert(element.isField,
failedAt(element, "Direct set element $element must be a field."));
return new StaticUse.internal(element, StaticUseKind.SET);
return new StaticUse.internal(element, StaticUseKind.STATIC_SET);
}
/// Constructor invocation of [element] with the given [callStructure].
@ -486,7 +490,7 @@ class StaticUse {
element,
"Not CallStructure for constructor invocation of element "
"$element."));
return new StaticUse.internal(element, StaticUseKind.INVOKE,
return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
callStructure: callStructure);
}
@ -559,7 +563,7 @@ class StaticUse {
element.isInstanceMember || element is JRecordField,
failedAt(element,
"Field init element $element must be an instance or boxed field."));
return new StaticUse.internal(element, StaticUseKind.FIELD_GET);
return new StaticUse.internal(element, StaticUseKind.INSTANCE_FIELD_GET);
}
/// Write access of an instance field or boxed field [element].
@ -568,7 +572,7 @@ class StaticUse {
element.isInstanceMember || element is JRecordField,
failedAt(element,
"Field init element $element must be an instance or boxed field."));
return new StaticUse.internal(element, StaticUseKind.FIELD_SET);
return new StaticUse.internal(element, StaticUseKind.INSTANCE_FIELD_SET);
}
/// Read of a local function [element].
@ -592,7 +596,7 @@ class StaticUse {
/// Implicit method/constructor invocation of [element] created by the
/// backend.
factory StaticUse.implicitInvoke(FunctionEntity element) {
return new StaticUse.internal(element, StaticUseKind.INVOKE,
return new StaticUse.internal(element, StaticUseKind.STATIC_INVOKE,
callStructure: element.parameterStructure.callStructure);
}

View file

@ -36,14 +36,16 @@ abstract class EnumSet<E> {
/// value indices.
void set value(int mask);
/// Adds [enumValue] to this set.
void add(E enumValue);
/// Adds [enumValue] to this set. Returns `true` if the set was changed by
/// this action.
bool add(E enumValue);
/// Adds all enum values in [set] to this set.
void addAll(EnumSet<E> set);
/// Removes [enumValue] from this set.
void remove(E enumValue);
/// Removes [enumValue] from this set. Returns `true` if the set was changed
/// by this action.
bool remove(E enumValue);
/// Removes all enum values in [set] from this set. The set of removed values
/// is returned.
@ -149,8 +151,10 @@ class _EnumSet<E> extends EnumSet<E> {
}
@override
void add(E enumValue) {
bool add(E enumValue) {
int before = _value;
_value |= 1 << (enumValue as dynamic).index;
return _value != before;
}
@override
@ -159,8 +163,10 @@ class _EnumSet<E> extends EnumSet<E> {
}
@override
void remove(E enumValue) {
bool remove(E enumValue) {
int before = _value;
_value &= ~(1 << (enumValue as dynamic).index);
return _value != before;
}
@override
@ -201,7 +207,7 @@ class _ConstEnumSet<E> extends EnumSet<E> {
}
@override
void add(E enumValue) {
bool add(E enumValue) {
throw new UnsupportedError('EnumSet.add');
}
@ -212,16 +218,36 @@ class _ConstEnumSet<E> extends EnumSet<E> {
@override
void clear() {
throw new UnsupportedError('EnumSet.clear');
if (isEmpty) {
// We allow this no-op operation on an immutable set to support using a
// constant empty set together with mutable sets where applicable.
} else {
throw new UnsupportedError('EnumSet.clear');
}
}
@override
void remove(E enumValue) {
bool remove(E enumValue) {
if (isEmpty) {
// We allow this no-op operation on an immutable set to support using a
// constant empty set together with mutable sets where applicable.
return false;
}
throw new UnsupportedError('EnumSet.remove');
}
@override
EnumSet<E> removeAll(EnumSet<E> set) {
if (isEmpty) {
// We allow this no-op operation on an immutable set to support using a
// constant empty set together with mutable sets where applicable.
return this;
}
if (set.isEmpty) {
// We allow this no-op operation on an immutable set to support using a
// constant empty set together with mutable sets where applicable.
return set.clone();
}
throw new UnsupportedError('EnumSet.removeAll');
}
}

View file

@ -198,6 +198,10 @@ abstract class JClosedWorld implements World {
/// Returns the single [MemberEntity] that matches a call to [selector] on the
/// [receiver]. If multiple targets exist, `null` is returned.
MemberEntity locateSingleMember(Selector selector, AbstractValue receiver);
/// Returns the set of read, write, and invocation accesses found on [member]
/// during the closed world computation.
MemberAccess getMemberAccess(MemberEntity member);
}
abstract class OpenWorld implements World {

View file

@ -11,6 +11,9 @@ var field1b;
/*element: field1c:init,read,write*/
var field1c;
/*element: field1d:init,read*/
var field1d;
/*element: field2a:read*/
get field2a => 42;
@ -27,6 +30,11 @@ get field2c => 42;
/*element: field2c=:write*/
set field2c(_) {}
/*element: field2d:read*/
get field2d => 42;
set field2d(_) {}
class Class {
/*element: Class.field1a:init,read*/
var field1a;
@ -37,6 +45,9 @@ class Class {
/*element: Class.field1c:init,read,write*/
var field1c;
/*element: Class.field1d:init,invoke,read=static*/
var field1d;
/*element: Class.field2a:read*/
get field2a => 42;
@ -53,6 +64,11 @@ class Class {
/*element: Class.field2c=:write*/
set field2c(_) {}
/*element: Class.field2d:invoke,read=static*/
get field2d => null;
set field2d(_) {}
/*element: Class.field3a:init*/
var field3a = 0;
@ -67,10 +83,12 @@ class Class {
field1a;
field1b = 42;
field1c = field1c;
field1d();
field2a;
field2b = 42;
field2c = field2c;
field2d();
}
}
@ -79,10 +97,12 @@ main() {
field1a;
field1b = 42;
field1c = field1c;
field1d();
field2a;
field2b = 42;
field2c = field2c;
field2d();
new Class().test();
}

View file

@ -165,7 +165,7 @@ class Class1b {
/*element: Class2.:invoke*/
class Class2 {
/*element: Class2.c:init,read*/
/*element: Class2.c:init,invoke,read=static*/
Class1a c;
}

View file

@ -0,0 +1,122 @@
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
class Super {
/*element: Super.field1:init,read=super*/
var field1;
/*element: Super.field2:init,write=super*/
var field2;
/*element: Super.field3:init,read=super*/
var field3;
/*element: Super.field4:init,read=super*/
final field4;
/*element: Super.field5:init,read=super*/
final field5;
/*element: Super.constructor1:invoke*/
Super.constructor1(this.field4, this.field5);
/*element: Super.constructor2:invoke=(0)*/
Super.constructor2([this.field4, this.field5]);
/*element: Super.constructor3:invoke=(1)*/
Super.constructor3([this.field4, this.field5]);
/*element: Super.method1:invoke=(1):super*/
method1([a, b]) {}
/*element: Super.method2:invoke,read=super*/
method2([a, b]) {}
/*element: Super.getter1:read=super*/
get getter1 => null;
/*element: Super.getter2:read=super*/
get getter2 => null;
/*element: Super.setter1=:write=super*/
set setter1(_) {}
/*element: Super.call:invoke=(0,a,b,c)*/
void call({a, b, c, d}) {}
}
class Sub extends Super {
/*element: Sub.constructor1:invoke=(1)*/
Sub.constructor1([field4, field5]) : super.constructor1(field4, field5);
/*element: Sub.constructor2:invoke*/
Sub.constructor2() : super.constructor2();
/*element: Sub.readSuperField:invoke*/
readSuperField() {
return super.field1;
}
/*element: Sub.writeSuperField:invoke*/
writeSuperField() {
super.field2 = null;
}
/*element: Sub.invokeSuperField:invoke*/
invokeSuperField() {
super.field3(a: 0);
}
/*element: Sub.readSuperFinalField:invoke*/
readSuperFinalField() {
return super.field4;
}
/*element: Sub.invokeSuperFinalField:invoke*/
invokeSuperFinalField() {
super.field5(b: 0);
}
/*element: Sub.invokeSuperMethod:invoke*/
invokeSuperMethod() {
super.method1(0);
}
/*element: Sub.readSuperMethod:invoke*/
readSuperMethod() {
return super.method2;
}
/*element: Sub.readSuperGetter:invoke*/
readSuperGetter() {
return super.getter1;
}
/*element: Sub.invokeSuperGetter:invoke*/
invokeSuperGetter() {
return super.getter2(c: 0);
}
/*element: Sub.writeSuperSetter:invoke*/
writeSuperSetter() {
super.setter1 = null;
}
}
/*element: main:invoke*/
void main() {
new Super.constructor3(null);
new Sub.constructor1(null);
new Sub.constructor2()
..readSuperField()
..writeSuperField()
..invokeSuperField()
..readSuperFinalField()
..invokeSuperFinalField()
..invokeSuperMethod()
..readSuperMethod()
..readSuperGetter()
..invokeSuperGetter()
..writeSuperSetter();
}

View file

@ -11,6 +11,7 @@ import 'package:compiler/src/ir/util.dart';
import 'package:compiler/src/kernel/kernel_strategy.dart';
import 'package:compiler/src/universe/member_usage.dart';
import 'package:compiler/src/universe/resolution_world_builder.dart';
import 'package:compiler/src/util/enumset.dart';
import 'package:compiler/src/util/features.dart';
import 'package:kernel/ast.dart' as ir;
import '../equivalence/id_equivalence.dart';
@ -55,6 +56,31 @@ class ClosedWorldDataComputer extends DataComputer<Features> {
Enqueuer.skipEnqueuerCheckForTesting = skipEnqueuerCheck;
}
/// Compute a short textual representation of [access] on member.
///
/// Dynamic access on instance members and static access on non-instance
/// members is implicit, so we only annotate super access and static access
/// not implied by dynamic or super access.
String computeAccessText(MemberEntity member, EnumSet<Access> access,
[String prefix]) {
StringBuffer sb = new StringBuffer();
String delimiter = '';
if (prefix != null) {
sb.write(prefix);
delimiter = ':';
}
if (access.contains(Access.superAccess)) {
sb.write(delimiter);
sb.write('super');
} else if (member.isInstanceMember &&
access.contains(Access.staticAccess) &&
!access.contains(Access.dynamicAccess)) {
sb.write(delimiter);
sb.write('static');
}
return sb.toString();
}
@override
void computeMemberData(Compiler compiler, MemberEntity member,
Map<Id, ActualData<Features>> actualMap,
@ -71,15 +97,20 @@ class ClosedWorldDataComputer extends DataComputer<Features> {
features.add(Tags.init);
}
if (memberUsage.hasRead) {
features.add(Tags.read);
features[Tags.read] = computeAccessText(member, memberUsage.reads);
}
if (memberUsage.hasWrite) {
features.add(Tags.write);
features[Tags.write] = computeAccessText(member, memberUsage.writes);
}
if (memberUsage.isFullyInvoked) {
features.add(Tags.invoke);
} else if (memberUsage.hasInvoke) {
features[Tags.invoke] = memberUsage.invokedParameters.shortText;
if (memberUsage.hasInvoke) {
if (memberUsage is MethodUsage &&
!memberUsage.parameterUsage.isFullyUsed) {
features[Tags.invoke] = computeAccessText(member, memberUsage.invokes,
memberUsage.invokedParameters.shortText);
} else {
features[Tags.invoke] =
computeAccessText(member, memberUsage.invokes);
}
}
}
Id id = computeEntityId(node);