| // 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 '../util/enumset.dart'; | |
| import '../util/util.dart'; | |
| import '../world.dart'; | |
| import 'call_structure.dart'; | |
| import 'class_hierarchy.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 this._parameterStubs, | |
| required this._compiledConstants, | |
| required this._invokedNames, | |
| required this._invokedGetters, | |
| required this._invokedSetters, | |
| required this._staticTypeArgumentDependencies, | |
| required this._dynamicTypeArgumentDependencies, | |
| required this.oneShotInterceptorData, | |
| }) : _reachableLazyMemberBodies = processedEntities | |
| .where((e) => e is JGeneratorBody || e is JConstructorBody) | |
| .toSet(); | |
| @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 []; | |
| } | |
| } |