blob: ef36a65c7d28d6edbce46d1ca1db181174375ab2 [file] [log] [blame]
// Copyright (c) 2017, 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 'dart:collection';
import '../common.dart';
import '../common/elements.dart';
import '../common/names.dart' show Identifiers;
import '../constants/values.dart';
import '../elements/entities.dart';
import '../elements/types.dart';
import '../js_backend/annotations.dart' show AnnotationsData;
import '../js_backend/inferred_data.dart';
import '../js_backend/interceptor_data.dart' show OneShotInterceptorData;
import '../js_backend/native_data.dart' show NativeBasicData;
import '../js_model/elements.dart';
import '../js_model/js_world.dart' show JClosedWorld;
import '../universe/class_hierarchy.dart';
import '../util/enumset.dart';
import '../util/util.dart';
import '../world.dart';
import 'call_structure.dart';
import 'member_usage.dart';
import 'selector.dart' show Selector;
import 'use.dart'
show ConstantUse, DynamicUse, DynamicUseKind, StaticUse, StaticUseKind;
import 'world_builder.dart';
// The immutable result of the [CodegenWorldBuilder].
abstract class CodegenWorld extends BuiltWorld {
/// Returns `true` if [member] is a late member visited by the codegen
/// enqueuer and is therefore reachable within the program. After
/// serialization all late members are registered in the closed world. This
/// predicate is used to determine which of these members are actually used.
bool isLateMemberReachable(MemberEntity member);
/// Calls [f] for each generic call method on a live closure class.
void forEachGenericClosureCallMethod(void Function(FunctionEntity) f);
bool hasInvokedGetter(MemberEntity member);
/// Returns `true` if [member] is invoked as a setter.
bool hasInvokedSetter(MemberEntity member);
Map<Selector, SelectorConstraints>? invocationsByName(String name);
Iterable<Selector>? getterInvocationsByName(String name);
Iterable<Selector>? setterInvocationsByName(String name);
void forEachInvokedName(
void Function(String name, Map<Selector, SelectorConstraints> selectors) f,
);
void forEachInvokedGetter(
void Function(String name, Map<Selector, SelectorConstraints> selectors) f,
);
void forEachInvokedSetter(
void Function(String name, Map<Selector, SelectorConstraints> selectors) f,
);
/// All directly instantiated classes, that is, classes with a generative
/// constructor that has been called directly and not only through a
/// super-call.
// TODO(johnniwinther): Improve semantic precision.
Iterable<ClassEntity> get directlyInstantiatedClasses;
Iterable<ClassEntity> get constructorReferences;
/// All directly or indirectly instantiated classes.
Iterable<ClassEntity> get instantiatedClasses;
bool methodsNeedsSuperGetter(FunctionEntity function);
/// The calls [f] for all static fields.
void forEachStaticField(void Function(FieldEntity) f);
/// Returns the types that are live as constant type literals.
Iterable<DartType> get constTypeLiterals;
/// Returns the types that are live as constant type arguments.
Iterable<DartType> get liveTypeArguments;
/// Returns a list of constants topologically sorted so that dependencies
/// appear before the dependent constant.
///
/// [preSortCompare] is a comparator function that gives the constants a
/// consistent order prior to the topological sort which gives the constants
/// an ordering that is less sensitive to perturbations in the source code.
Iterable<ConstantValue> getConstantsForEmission([
Comparator<ConstantValue>? preSortCompare,
]);
/// Returns `true` if [member] is called from a subclass via `super`.
bool isAliasedSuperMember(MemberEntity member);
OneShotInterceptorData get oneShotInterceptorData;
Iterable<JParameterStub> getParameterStubs(FunctionEntity function);
}
class CodegenWorldBuilder extends WorldBuilder {
final JClosedWorld _closedWorld;
final InferredData _inferredData;
final OneShotInterceptorData _oneShotInterceptorData;
/// All declaration elements that have been processed by codegen.
final Set<MemberEntity> processedEntities = {};
/// The set of all directly instantiated classes, that is, classes with a
/// generative constructor that has been called directly and not only through
/// a super-call.
///
/// Invariant: Elements are declaration elements.
// TODO(johnniwinther): [_directlyInstantiatedClasses] and
// [_instantiatedTypes] sets should be merged.
final Set<ClassEntity> _directlyInstantiatedClasses = {};
/// The set of all directly instantiated types, that is, the types of the
/// directly instantiated classes.
///
/// See [_directlyInstantiatedClasses].
final Set<InterfaceType> _instantiatedTypes = {};
/// Classes implemented by directly instantiated classes.
final Set<ClassEntity> _implementedClasses = {};
final Map<String, Map<Selector, SelectorConstraints>> _invokedNames = {};
final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters = {};
final Map<String, Map<Selector, SelectorConstraints>> _invokedSetters = {};
final Map<ClassEntity, ClassUsage> _processedClasses = {};
Map<ClassEntity, ClassUsage> get classUsageForTesting => _processedClasses;
/// Map of registered usage of static and instance members.
final Map<MemberEntity, MemberUsage> _memberUsage = {};
/// 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 = {};
/// Map containing instance members of live classes that have not yet been
/// read from dynamically.
final Map<String, Set<MemberUsage>> _readableInstanceMembersByName = {};
/// Map containing instance members of live classes that have not yet been
/// written to dynamically.
final Map<String, Set<MemberUsage>> _writableInstanceMembersByName = {};
final Set<DartType> _isChecks = {};
final SelectorConstraintsStrategy _selectorConstraintsStrategy;
final Set<ConstantValue> _constantValues = {};
final Set<DartType> _constTypeLiterals = {};
final Set<DartType> _liveTypeArguments = {};
final Set<TypeVariableType> _namedTypeVariables = {};
final Set<ClassEntity> _constructorReferences = {};
final Map<FunctionEntity, List<JParameterStub>> _parameterStubs = {};
CodegenWorldBuilder(
this._closedWorld,
this._inferredData,
this._selectorConstraintsStrategy,
this._oneShotInterceptorData,
);
ElementEnvironment get _elementEnvironment => _closedWorld.elementEnvironment;
NativeBasicData get _nativeBasicData => _closedWorld.nativeData;
Iterable<ClassEntity> get instantiatedClasses => _processedClasses.keys.where(
(cls) => _processedClasses[cls]!.isInstantiated,
);
// TODO(johnniwinther): Improve semantic precision.
Iterable<ClassEntity> get directlyInstantiatedClasses {
return _directlyInstantiatedClasses;
}
/// Register [type] as (directly) instantiated.
// TODO(johnniwinther): Fully enforce the separation between exact, through
// subclass and through subtype instantiated types/classes.
// TODO(johnniwinther): Support unknown type arguments for generic types.
void registerTypeInstantiation(
InterfaceType type,
ClassUsedCallback classUsed,
) {
ClassEntity cls = type.element;
bool isNative = _nativeBasicData.isNativeClass(cls);
_instantiatedTypes.add(type);
// We can't use the closed-world assumption with native abstract
// classes; a native abstract class may have non-abstract subclasses
// not declared to the program. Instances of these classes are
// indistinguishable from the abstract class.
if (!cls.isAbstract || isNative) {
_directlyInstantiatedClasses.add(cls);
_processInstantiatedClass(cls, classUsed);
}
// TODO(johnniwinther): Replace this by separate more specific mappings that
// include the type arguments.
if (_implementedClasses.add(cls)) {
classUsed(cls, _getClassUsage(cls).implement());
_elementEnvironment.forEachSupertype(cls, (InterfaceType supertype) {
if (_implementedClasses.add(supertype.element)) {
classUsed(
supertype.element,
_getClassUsage(supertype.element).implement(),
);
}
});
}
}
bool _hasMatchingSelector(
Map<Selector, SelectorConstraints>? selectors,
MemberEntity member,
JClosedWorld world,
) {
if (selectors == null) return false;
for (Selector selector in selectors.keys) {
if (selector.appliesUnnamed(member)) {
SelectorConstraints masks = selectors[selector]!;
if (masks.canHit(member, selector.memberName, world)) {
return true;
}
}
}
return false;
}
Iterable<CallStructure> _getMatchingCallStructures(
Map<Selector, SelectorConstraints>? selectors,
MemberEntity member,
) {
if (selectors == null) return const [];
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 ??= {};
callStructures.add(selector.callStructure);
}
}
}
return callStructures ?? const [];
}
Iterable<CallStructure> _getInvocationCallStructures(MemberEntity member) {
return _getMatchingCallStructures(_invokedNames[member.name], member);
}
bool _hasInvokedGetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedGetters[member.name],
member,
_closedWorld,
);
}
bool _hasInvokedSetter(MemberEntity member) {
return _hasMatchingSelector(
_invokedSetters[member.name],
member,
_closedWorld,
);
}
void registerDynamicUse(
DynamicUse dynamicUse,
MemberUsedCallback memberUsed,
) {
Selector selector = dynamicUse.selector;
String methodName = selector.name;
void process(
Map<String, Set<MemberUsage>> memberMap,
EnumSet<MemberUse> Function(MemberUsage usage) action,
bool Function(MemberUsage usage) shouldBeRemoved,
) {
_processSet(memberMap, methodName, (MemberUsage usage) {
// Strategy will check both that selector applies and constraint is met.
if (_selectorConstraintsStrategy.appliedUnnamed(
dynamicUse,
usage.entity,
_closedWorld,
)) {
memberUsed(usage.entity, action(usage));
return shouldBeRemoved(usage);
}
return false;
});
}
switch (dynamicUse.kind) {
case DynamicUseKind.invoke:
registerDynamicInvocation(
dynamicUse.selector,
dynamicUse.typeArguments,
);
if (_registerNewSelector(dynamicUse, _invokedNames)) {
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(
_readableInstanceMembersByName,
(m) => m.read(Accesses.dynamicAccess),
(u) => !u.hasPendingDynamicRead,
);
}
break;
case DynamicUseKind.set:
if (_registerNewSelector(dynamicUse, _invokedSetters)) {
process(
_writableInstanceMembersByName,
(m) => m.write(Accesses.dynamicAccess),
(u) => !u.hasPendingDynamicWrite,
);
}
break;
}
}
bool _registerNewSelector(
DynamicUse dynamicUse,
Map<String, Map<Selector, SelectorConstraints>> selectorMap,
) {
Selector selector = dynamicUse.selector;
String name = selector.name;
Object? constraint = dynamicUse.receiverConstraint;
Map<Selector, SelectorConstraints> selectors = selectorMap[name] ??=
Maplet<Selector, SelectorConstraints>();
UniverseSelectorConstraints? constraints =
selectors[selector] as UniverseSelectorConstraints?;
if (constraints == null) {
selectors[selector] = _selectorConstraintsStrategy
.createSelectorConstraints(selector, constraint);
return true;
}
return constraints.addReceiverConstraint(constraint);
}
void registerIsCheck(covariant DartType type) {
_isChecks.add(type);
}
void registerNamedTypeVariable(TypeVariableType type) {
_namedTypeVariables.add(type);
}
void registerStaticUse(StaticUse staticUse, MemberUsedCallback memberUsed) {
MemberEntity element = staticUse.element as MemberEntity;
var (usage, useSet) = _getMemberUsage(element);
switch (staticUse.kind) {
case StaticUseKind.staticTearOff:
useSet = useSet.union(usage.read(Accesses.staticAccess));
break;
case StaticUseKind.instanceFieldGet:
case StaticUseKind.instanceFieldSet:
case StaticUseKind.callMethod:
// 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.superInvoke:
registerStaticInvocation(staticUse);
useSet = useSet.union(
usage.invoke(Accesses.superAccess, staticUse.callStructure!),
);
break;
case StaticUseKind.staticInvoke:
registerStaticInvocation(staticUse);
useSet = useSet.union(
usage.invoke(Accesses.staticAccess, staticUse.callStructure!),
);
break;
case StaticUseKind.superFieldSet:
useSet = useSet.union(usage.write(Accesses.superAccess));
break;
case StaticUseKind.superSetterSet:
useSet = useSet.union(usage.write(Accesses.superAccess));
break;
case StaticUseKind.staticSet:
useSet = useSet.union(usage.write(Accesses.staticAccess));
break;
case StaticUseKind.superTearOff:
useSet = useSet.union(usage.read(Accesses.superAccess));
break;
case StaticUseKind.superGet:
useSet = useSet.union(usage.read(Accesses.superAccess));
break;
case StaticUseKind.staticGet:
useSet = useSet.union(usage.read(Accesses.staticAccess));
break;
case StaticUseKind.fieldInit:
useSet = useSet.union(usage.init());
break;
case StaticUseKind.fieldConstantInit:
useSet = useSet.union(usage.constantInit(staticUse.constant!));
break;
case StaticUseKind.constructorInvoke:
case StaticUseKind.constConstructorInvoke:
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structure.
useSet = useSet.union(
usage.invoke(Accesses.staticAccess, staticUse.callStructure!),
);
break;
case StaticUseKind.directInvoke:
// We don't track parameters in the codegen world builder, so we
// pass `null` instead of the concrete call structure.
useSet = useSet.union(
usage.invoke(Accesses.staticAccess, staticUse.callStructure!),
);
if (staticUse.typeArguments?.isNotEmpty ?? false) {
registerDynamicInvocation(
Selector.call(element.memberName, staticUse.callStructure!),
staticUse.typeArguments!,
);
}
break;
case StaticUseKind.inlining:
registerStaticInvocation(staticUse);
break;
case StaticUseKind.closure:
case StaticUseKind.closureCall:
case StaticUseKind.weakStaticTearOff:
failedAt(
currentElementSpannable,
"Static use ${staticUse.kind} is not supported during codegen.",
);
}
if (useSet.isNotEmpty) {
memberUsed(usage.entity, useSet);
}
}
void processClassMembers(
ClassEntity cls,
MemberUsedCallback memberUsed, {
bool checkEnqueuerConsistency = false,
}) {
_elementEnvironment.forEachClassMember(cls, (
ClassEntity cls,
MemberEntity member,
) {
_processInstantiatedClassMember(
cls,
member,
memberUsed,
checkEnqueuerConsistency: checkEnqueuerConsistency,
);
});
}
void _processInstantiatedClassMember(
ClassEntity cls,
MemberEntity member,
MemberUsedCallback memberUsed, {
bool checkEnqueuerConsistency = false,
}) {
if (!member.isInstanceMember) return;
var (usage, useSet) = _getMemberUsage(member);
if (useSet.isNotEmpty) {
if (checkEnqueuerConsistency) {
throw SpannableAssertionFailure(
member,
'Unenqueued usage of $member: \nbefore: <none>\nafter : $usage',
);
} else {
memberUsed(member, useSet);
}
}
}
(MemberUsage, EnumSet<MemberUse>) _getMemberUsage(
MemberEntity member, {
bool checkEnqueuerConsistency = false,
}) {
EnumSet<MemberUse> useSet = EnumSet.empty();
// 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 = MemberUsage(member, potentialAccess: potentialAccess);
if (member is FieldEntity && !isNative) {
useSet = useSet.union(usage.init());
}
if (member is JSignatureMethod) {
// 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 = useSet.union(
usage.invoke(Accesses.dynamicAccess, CallStructure.noArgs),
);
}
if (usage.hasPendingDynamicRead && _hasInvokedGetter(member)) {
useSet = useSet.union(usage.read(Accesses.dynamicAccess));
}
if (usage.hasPendingDynamicWrite && _hasInvokedSetter(member)) {
useSet = useSet.union(usage.write(Accesses.dynamicAccess));
}
if (usage.hasPendingDynamicInvoke) {
Iterable<CallStructure> callStructures = _getInvocationCallStructures(
member,
);
for (CallStructure callStructure in callStructures) {
useSet = useSet.union(
usage.invoke(Accesses.dynamicAccess, callStructure),
);
if (!usage.hasPendingDynamicInvoke) {
break;
}
}
}
if (!checkEnqueuerConsistency) {
if (usage.hasPendingDynamicInvoke) {
_invokableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
if (usage.hasPendingDynamicRead) {
_readableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
if (usage.hasPendingDynamicWrite) {
_writableInstanceMembersByName
.putIfAbsent(memberName, () => {})
.add(usage);
}
}
} else {
usage = MemberUsage(member, potentialAccess: potentialAccess);
if (member is FieldEntity) {
useSet = useSet.union(usage.init());
}
}
if (!checkEnqueuerConsistency) {
_memberUsage[member] = usage;
}
} else {
if (checkEnqueuerConsistency) {
usage = usage.clone();
}
}
return (usage, useSet);
}
void _processSet(
Map<String, Set<MemberUsage>> map,
String memberName,
bool Function(MemberUsage e) f,
) {
Set<MemberUsage>? members = map[memberName];
if (members == null) return;
// [f] might add elements to [: map[memberName] :] during the loop below
// so we create a new list for [: map[memberName] :] and prepend the
// [remaining] members after the loop.
map[memberName] = {};
Set<MemberUsage> remaining = {};
for (MemberUsage member in members) {
if (!f(member)) remaining.add(member);
}
map[memberName]!.addAll(remaining);
}
/// Return the canonical [ClassUsage] for [cls].
ClassUsage _getClassUsage(ClassEntity cls) {
return _processedClasses.putIfAbsent(cls, () => ClassUsage(cls));
}
void _processInstantiatedClass(ClassEntity cls, ClassUsedCallback classUsed) {
// Registers [superclass] as instantiated. Returns `true` if it wasn't
// already instantiated and we therefore have to process its superclass as
// well.
bool processClass(ClassEntity superclass) {
ClassUsage usage = _getClassUsage(superclass);
if (!usage.isInstantiated) {
classUsed(usage.cls, usage.instantiate());
return true;
}
return false;
}
ClassEntity? current = cls;
while (current != null && processClass(current)) {
current = _elementEnvironment.getSuperClass(current);
}
}
/// Set of all registered compiled constants.
final Set<ConstantValue> _compiledConstants = {};
Iterable<ConstantValue> get compiledConstantsForTesting => _compiledConstants;
void addCompileTimeConstantForEmission(ConstantValue constant) {
_compiledConstants.add(constant);
}
/// Register the constant [use] with this world builder. Returns `true` if
/// the constant use was new to the world.
bool registerConstantUse(ConstantUse use) {
addCompileTimeConstantForEmission(use.value);
return _constantValues.add(use.value);
}
void registerConstTypeLiteral(DartType type) {
_constTypeLiterals.add(type);
}
void registerTypeArgument(DartType type) {
_liveTypeArguments.add(type);
}
void registerConstructorReference(InterfaceType type) {
_constructorReferences.add(type.element);
}
bool _canTearOffFunction(FunctionEntity member, MemberUsage usage) {
if (!member.isFunction) return false;
if (member.isInstanceMember) {
if (member.enclosingClass!.isClosure) return false;
if (usage.reads.contains(Access.superAccess)) return true;
if (_hasInvokedGetter(member)) return true;
return false;
} else {
if (member is ConstructorEntity) return false;
assert(member.isStatic || member.isTopLevel);
return usage.hasRead;
}
}
bool _methodCanBeApplied(FunctionEntity method) {
return _closedWorld.backendUsage.isFunctionApplyUsed &&
_inferredData.getMightBePassedToApply(method);
}
bool _functionNeedsStubs(FunctionEntity member) {
if (member.isAbstract) return false;
if (member is JGeneratorBody) return false;
if (member is ConstructorBodyEntity) return false;
return member.parameterStructure.optionalParameters != 0 ||
member.parameterStructure.typeParameters != 0;
}
List<JParameterStub> generateParameterStubs() {
List<JParameterStub> newStubs = [];
_memberUsage.forEach((member, usage) {
if (member is! FunctionEntity) return;
if (member is JParameterStub) return;
if (!usage.hasUse) return;
if (!_functionNeedsStubs(member)) return;
final Map<Selector, SelectorConstraints> liveSelectors =
// Only instance members (not static methods) need stubs.
(member.isInstanceMember ? _invokedNames[member.name!] : const {}) ??
const {};
final Map<Selector, SelectorConstraints> callSelectors =
(_canTearOffFunction(member, usage)
? _invokedNames[Identifiers.call]
: const {}) ??
const {};
bool canBeApplied = _methodCanBeApplied(member);
int memberTypeParameters = member.parameterStructure.typeParameters;
if (liveSelectors.isEmpty &&
callSelectors.isEmpty &&
// Function.apply might need a stub to default the type parameter.
!(canBeApplied && memberTypeParameters > 0)) {
return;
}
// For every call-selector the corresponding selector with the name of the
// member.
//
// For example, for the call-selector `call(x, y)` the renamed selector
// for member `foo` would be `foo(x, y)`.
Set<Selector> renamedCallSelectors = {};
Set<Selector> stubSelectors = {};
void createStub(
FunctionEntity member,
Selector selector, {
Selector? callSelector,
required bool needsSuper,
}) {
if (_parameterStubs[member]?.any(
(e) =>
e.parameterStructure.callStructure == selector.callStructure,
) ??
false) {
return;
}
final stub = _generateParameterStub(
member,
selector,
callSelector: callSelector,
needsSuper: needsSuper,
);
if (stub == null) return;
newStubs.add(stub);
(_parameterStubs[member] ??= []).add(stub);
if (needsSuper) {
// We need to force the accesses here since the target member may not
// have a pending super invoke. This ensures that the emitter uses the
// aliased name for this super invocation.
usage.invoke(
Accesses.superAccess,
member.parameterStructure.callStructure,
forceAccesses: true,
);
}
}
bool needsSuper = usage.reads.contains(Access.superAccess);
// Start with closure-call selectors, since they imply the generation
// of the non-call version.
if (canBeApplied && memberTypeParameters > 0) {
// Function.apply calls the function with no type arguments, so generic
// methods need the stub to default the type arguments.
// This has to be the first stub.
Selector namedSelector = Selector.fromElement(member).toNonGeneric();
Selector closureSelector = namedSelector.toCallSelector();
renamedCallSelectors.add(namedSelector);
stubSelectors.add(namedSelector);
createStub(
member,
namedSelector,
callSelector: closureSelector,
needsSuper: needsSuper,
);
}
for (Selector selector in callSelectors.keys) {
Selector renamedSelector = Selector.call(
member.memberName,
selector.callStructure,
);
renamedCallSelectors.add(renamedSelector);
if (!renamedSelector.appliesUnnamed(member)) {
continue;
}
if (stubSelectors.add(renamedSelector)) {
createStub(
member,
renamedSelector,
callSelector: selector,
needsSuper: needsSuper,
);
}
// A generic method might need to support `call<T>(x)` for a generic
// instantiation stub without `call<T>(x)` being in [callSelectors].
// [selector] will be `call(x)` (that already passes the appliesUnnamed
// check by defaulting type arguments), and the method will be generic.
//
// This is basically the same logic as above, but with type arguments.
if (selector.callStructure.typeArgumentCount == 0) {
if (memberTypeParameters > 0) {
Selector renamedSelectorWithTypeArguments = Selector.call(
member.memberName,
selector.callStructure.withTypeArgumentCount(
memberTypeParameters,
),
);
renamedCallSelectors.add(renamedSelectorWithTypeArguments);
if (stubSelectors.add(renamedSelectorWithTypeArguments)) {
Selector closureSelector = Selector.callClosureFrom(
renamedSelectorWithTypeArguments,
);
createStub(
member,
renamedSelectorWithTypeArguments,
callSelector: closureSelector,
needsSuper: needsSuper,
);
}
}
}
}
// Now run through the actual member selectors (eg. `foo$2(x, y)` and not
// `call$2(x, y)`). Some of them have already been generated because of the
// call-selectors and they are in the renamedCallSelectors set.
for (Selector selector in liveSelectors.keys) {
if (renamedCallSelectors.contains(selector)) continue;
if (!selector.appliesUnnamed(member)) continue;
if (!liveSelectors[selector]!.canHit(
member,
selector.memberName,
_closedWorld,
)) {
continue;
}
if (stubSelectors.add(selector)) {
createStub(member, selector, needsSuper: needsSuper);
}
}
});
return newStubs;
}
JParameterStub? _generateParameterStub(
FunctionEntity member,
Selector selector, {
Selector? callSelector,
required bool needsSuper,
}) {
ParameterStructure parameterStructure = member.parameterStructure;
final callStructure = selector.callStructure;
int positionalArgumentCount = callStructure.positionalArgumentCount;
if (callStructure.typeArgumentCount == parameterStructure.typeParameters) {
if (positionalArgumentCount == parameterStructure.totalParameters) {
// Positional optional arguments are all provided.
assert(callStructure.isUnnamed);
return null;
}
if (parameterStructure.namedParameters.isNotEmpty &&
callStructure.namedArgumentCount ==
parameterStructure.namedParameters.length) {
// Named optional arguments are all provided.
return null;
}
}
return _closedWorld.elementMap.createParameterStub(
member as JFunction,
selector,
callSelector: callSelector,
needsSuper: needsSuper,
);
}
CodegenWorld close() {
Map<MemberEntity, MemberUsage> liveMemberUsage = {};
_memberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (usage.hasUse) {
liveMemberUsage[member] = usage;
}
});
return CodegenWorldImpl(
_closedWorld,
liveMemberUsage,
processedEntities,
constTypeLiterals: _constTypeLiterals,
constructorReferences: _constructorReferences,
directlyInstantiatedClasses: directlyInstantiatedClasses,
typeVariableTypeLiterals: typeVariableTypeLiterals,
instantiatedClasses: instantiatedClasses,
isChecks: _isChecks,
namedTypeVariables: _namedTypeVariables,
instantiatedTypes: _instantiatedTypes,
liveTypeArguments: _liveTypeArguments,
compiledConstants: _compiledConstants,
invokedNames: _invokedNames,
invokedGetters: _invokedGetters,
invokedSetters: _invokedSetters,
staticTypeArgumentDependencies: staticTypeArgumentDependencies,
dynamicTypeArgumentDependencies: dynamicTypeArgumentDependencies,
oneShotInterceptorData: _oneShotInterceptorData,
parameterStubs: _parameterStubs,
);
}
}
class CodegenWorldImpl implements CodegenWorld {
final JClosedWorld _closedWorld;
final Map<MemberEntity, MemberUsage> _liveMemberUsage;
final Set<MemberEntity> _reachableLazyMemberBodies;
@override
final Iterable<DartType> constTypeLiterals;
@override
final Iterable<ClassEntity> constructorReferences;
@override
final Iterable<ClassEntity> directlyInstantiatedClasses;
@override
final Iterable<TypeVariableType> typeVariableTypeLiterals;
@override
final Iterable<ClassEntity> instantiatedClasses;
@override
final Iterable<DartType> isChecks;
@override
final Set<TypeVariableType> namedTypeVariables;
@override
final Iterable<InterfaceType> instantiatedTypes;
@override
final Iterable<DartType> liveTypeArguments;
final Iterable<ConstantValue> _compiledConstants;
final Map<String, Map<Selector, SelectorConstraints>> _invokedNames;
final Map<String, Map<Selector, SelectorConstraints>> _invokedGetters;
final Map<String, Map<Selector, SelectorConstraints>> _invokedSetters;
final Map<Entity, Set<DartType>> _staticTypeArgumentDependencies;
final Map<Selector, Set<DartType>> _dynamicTypeArgumentDependencies;
final Map<FunctionEntity, List<JParameterStub>> _parameterStubs;
@override
final OneShotInterceptorData oneShotInterceptorData;
CodegenWorldImpl(
this._closedWorld,
this._liveMemberUsage,
Iterable<MemberEntity> processedEntities, {
required this.constTypeLiterals,
required this.constructorReferences,
required this.directlyInstantiatedClasses,
required this.typeVariableTypeLiterals,
required this.instantiatedClasses,
required this.isChecks,
required this.namedTypeVariables,
required this.instantiatedTypes,
required this.liveTypeArguments,
required Map<FunctionEntity, List<JParameterStub>> parameterStubs,
required Iterable<ConstantValue> compiledConstants,
required Map<String, Map<Selector, SelectorConstraints>> invokedNames,
required Map<String, Map<Selector, SelectorConstraints>> invokedGetters,
required Map<String, Map<Selector, SelectorConstraints>> invokedSetters,
required Map<Entity, Set<DartType>> staticTypeArgumentDependencies,
required Map<Selector, Set<DartType>> dynamicTypeArgumentDependencies,
required this.oneShotInterceptorData,
}) : _parameterStubs = parameterStubs,
_reachableLazyMemberBodies = processedEntities
.where((e) => e is JGeneratorBody || e is JConstructorBody)
.toSet(),
_compiledConstants = compiledConstants,
_invokedNames = invokedNames,
_invokedGetters = invokedGetters,
_invokedSetters = invokedSetters,
_staticTypeArgumentDependencies = staticTypeArgumentDependencies,
_dynamicTypeArgumentDependencies = dynamicTypeArgumentDependencies;
@override
AnnotationsData get annotationsData => _closedWorld.annotationsData;
@override
ClassHierarchy get classHierarchy => _closedWorld.classHierarchy;
@override
void forEachStaticField(void Function(FieldEntity) f) {
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (member is FieldEntity && (member.isStatic || member.isTopLevel)) {
f(member);
}
});
}
@override
void forEachGenericMethod(void Function(FunctionEntity e) f) {
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (member is FunctionEntity &&
_closedWorld.elementEnvironment
.getFunctionTypeVariables(member)
.isNotEmpty) {
f(member);
}
});
}
@override
void forEachGenericInstanceMethod(void Function(FunctionEntity e) f) {
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (member is FunctionEntity &&
member.isInstanceMember &&
_closedWorld.elementEnvironment
.getFunctionTypeVariables(member)
.isNotEmpty) {
f(member);
}
});
}
@override
void forEachGenericClosureCallMethod(void Function(FunctionEntity e) f) {
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (member.name == Identifiers.call &&
member.isInstanceMember &&
member.enclosingClass!.isClosure &&
member is FunctionEntity &&
_closedWorld.elementEnvironment
.getFunctionTypeVariables(member)
.isNotEmpty) {
f(member);
}
});
}
@override
late final Iterable<FunctionEntity> userNoSuchMethods = [
for (MemberEntity member in _liveMemberUsage.keys)
if (member is FunctionEntity &&
member.isInstanceMember &&
member.name == Identifiers.noSuchMethod_ &&
!_closedWorld.commonElements.isDefaultNoSuchMethodImplementation(
member,
))
member,
];
@override
Iterable<Local> get genericLocalFunctions => const [];
@override
late final Iterable<FunctionEntity> closurizedMembers = (() {
final result = <FunctionEntity>{};
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if ((member.isFunction || member is JGeneratorBody) &&
member.isInstanceMember &&
usage.hasRead) {
result.add(member as FunctionEntity);
}
});
return result;
})();
@override
late final Iterable<FunctionEntity> closurizedStatics = (() {
final result = <FunctionEntity>{};
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (member.isFunction &&
(member.isStatic || member.isTopLevel) &&
usage.hasRead) {
result.add(member as FunctionEntity);
}
});
return result;
})();
@override
late final Map<MemberEntity, DartType> genericCallableProperties = (() {
final result = <MemberEntity, DartType>{};
_liveMemberUsage.forEach((MemberEntity member, MemberUsage usage) {
if (usage.hasRead) {
DartType? type;
if (member is FieldEntity) {
type = _closedWorld.elementEnvironment.getFieldType(member);
} else if (member.isGetter) {
type = _closedWorld.elementEnvironment
.getFunctionType(member as FunctionEntity)
.returnType;
}
if (type == null) return;
if (_closedWorld.dartTypes.canAssignGenericFunctionTo(type)) {
result[member] = type;
} else {
type = type.withoutNullability;
if (type is InterfaceType) {
FunctionType? callType = _closedWorld.dartTypes.getCallType(type);
if (callType != null &&
_closedWorld.dartTypes.canAssignGenericFunctionTo(callType)) {
result[member] = callType;
}
}
}
}
});
return result;
})();
@override
void forEachStaticTypeArgument(
void Function(Entity function, Set<DartType> typeArguments) f,
) {
_staticTypeArgumentDependencies.forEach(f);
}
@override
void forEachDynamicTypeArgument(
void Function(Selector selector, Set<DartType> typeArguments) f,
) {
_dynamicTypeArgumentDependencies.forEach(f);
}
@override
void forEachInvokedName(
void Function(String name, Map<Selector, SelectorConstraints> selectors) f,
) {
_invokedNames.forEach(f);
}
@override
void forEachInvokedGetter(
void Function(String name, Map<Selector, SelectorConstraints> selectors) f,
) {
_invokedGetters.forEach(f);
}
@override
void forEachInvokedSetter(
void Function(String name, Map<Selector, SelectorConstraints> selectors) f,
) {
_invokedSetters.forEach(f);
}
@override
bool hasInvokedGetter(MemberEntity 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) {
MemberUsage? memberUsage = _liveMemberUsage[member];
if (memberUsage == null) return false;
return memberUsage.writes.contains(Access.dynamicAccess);
}
Map<Selector, SelectorConstraints>? _asUnmodifiable(
Map<Selector, SelectorConstraints>? map,
) {
if (map == null) return null;
return UnmodifiableMapView(map);
}
@override
Map<Selector, SelectorConstraints>? invocationsByName(String name) {
return _asUnmodifiable(_invokedNames[name]);
}
@override
Iterable<Selector>? getterInvocationsByName(String name) {
return _invokedGetters[name]?.keys;
}
@override
Iterable<Selector>? setterInvocationsByName(String name) {
return _invokedSetters[name]?.keys;
}
@override
Iterable<ConstantValue> getConstantsForEmission([
Comparator<ConstantValue>? preSortCompare,
]) {
// We must emit dependencies before their uses.
Set<ConstantValue> seenConstants = {};
List<ConstantValue> result = [];
void addConstant(ConstantValue constant) {
if (!seenConstants.contains(constant)) {
constant.getDependencies().forEach(addConstant);
assert(!seenConstants.contains(constant));
result.add(constant);
seenConstants.add(constant);
}
}
if (preSortCompare != null) {
List<ConstantValue> sorted = _compiledConstants.toList();
sorted.sort(preSortCompare);
sorted.forEach(addConstant);
} else {
_compiledConstants.forEach(addConstant);
}
return result;
}
@override
bool isAliasedSuperMember(MemberEntity member) {
MemberUsage? usage = _liveMemberUsage[member];
if (usage == null) return false;
return usage.invokes.contains(Access.superAccess) ||
usage.writes.contains(Access.superAccess);
}
@override
bool isLateMemberReachable(MemberEntity member) {
return _reachableLazyMemberBodies.contains(member);
}
@override
Iterable<JParameterStub> getParameterStubs(FunctionEntity member) {
return _parameterStubs[member] ?? const [];
}
}