| // 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 'package:kernel/ast.dart' as ir; |
| |
| import '../closure.dart'; |
| import '../common.dart'; |
| import '../common/tasks.dart'; |
| import '../constants/expressions.dart'; |
| import '../constants/values.dart'; |
| import '../elements/entities.dart'; |
| import '../elements/names.dart' show Name; |
| import '../elements/types.dart'; |
| import '../kernel/element_map.dart'; |
| import '../kernel/env.dart'; |
| import '../options.dart'; |
| import '../ssa/type_builder.dart'; |
| import '../universe/selector.dart'; |
| import 'elements.dart'; |
| import 'closure_visitors.dart'; |
| import 'locals.dart'; |
| import 'js_strategy.dart' show JsClosedWorldBuilder; |
| |
| class KernelClosureAnalysis { |
| /// Inspect members and mark if those members capture any state that needs to |
| /// be marked as free variables. |
| static ScopeModel computeScopeModel( |
| MemberEntity entity, ir.Member node, CompilerOptions options) { |
| if (entity.isAbstract) return null; |
| if (entity.isField && !entity.isInstanceMember) { |
| ir.Field field = node; |
| // Skip top-level/static fields without an initializer. |
| if (field.initializer == null) return null; |
| } |
| |
| bool hasThisLocal = false; |
| if (entity.isInstanceMember) { |
| hasThisLocal = true; |
| } else if (entity.isConstructor) { |
| ConstructorEntity constructor = entity; |
| hasThisLocal = !constructor.isFactoryConstructor; |
| } |
| ScopeModel model = new ScopeModel(); |
| CapturedScopeBuilder translator = |
| new CapturedScopeBuilder(model, options, hasThisLocal: hasThisLocal); |
| if (entity.isField) { |
| if (node is ir.Field && node.initializer != null) { |
| node.accept(translator); |
| } else { |
| assert(entity.isInstanceMember); |
| model.scopeInfo = new KernelScopeInfo(true); |
| } |
| } else { |
| assert(node is ir.Procedure || node is ir.Constructor); |
| node.accept(translator); |
| } |
| return model; |
| } |
| } |
| |
| /// Closure conversion code using our new Entity model. Closure conversion is |
| /// necessary because the semantics of closures are slightly different in Dart |
| /// than JavaScript. Closure conversion is separated out into two phases: |
| /// generation of a new (temporary) representation to store where variables need |
| /// to be hoisted/captured up at another level to re-write the closure, and then |
| /// the code generation phase where we generate elements and/or instructions to |
| /// represent this new code path. |
| /// |
| /// For a general explanation of how closure conversion works at a high level, |
| /// check out: |
| /// http://siek.blogspot.com/2012/07/essence-of-closure-conversion.html or |
| /// http://matt.might.net/articles/closure-conversion/. |
| // TODO(efortuna): Change inheritance hierarchy so that the |
| // ClosureConversionTask doesn't inherit from ClosureTask because it's just a |
| // glorified timer. |
| class KernelClosureConversionTask extends ClosureConversionTask { |
| final KernelToElementMapForBuilding _elementMap; |
| final GlobalLocalsMap _globalLocalsMap; |
| final CompilerOptions _options; |
| |
| /// Map of the scoping information that corresponds to a particular entity. |
| Map<MemberEntity, ScopeInfo> _scopeMap = <MemberEntity, ScopeInfo>{}; |
| Map<ir.Node, CapturedScope> _capturedScopesMap = <ir.Node, CapturedScope>{}; |
| // Indicates the type variables (if any) that are captured in a given |
| // Signature function. |
| Map<MemberEntity, CapturedScope> _capturedScopeForSignatureMap = |
| <MemberEntity, CapturedScope>{}; |
| |
| Map<MemberEntity, ClosureRepresentationInfo> _memberClosureRepresentationMap = |
| <MemberEntity, ClosureRepresentationInfo>{}; |
| |
| // The key is either a [ir.FunctionDeclaration] or [ir.FunctionExpression]. |
| Map<ir.TreeNode, ClosureRepresentationInfo> _localClosureRepresentationMap = |
| <ir.TreeNode, ClosureRepresentationInfo>{}; |
| |
| KernelClosureConversionTask( |
| Measurer measurer, this._elementMap, this._globalLocalsMap, this._options) |
| : super(measurer); |
| |
| void _updateScopeBasedOnRtiNeed(KernelScopeInfo scope, ClosureRtiNeed rtiNeed, |
| MemberEntity outermostEntity) { |
| bool includeForRti(Set<VariableUse> useSet) { |
| for (VariableUse usage in useSet) { |
| switch (usage.kind) { |
| case VariableUseKind.explicit: |
| return true; |
| break; |
| case VariableUseKind.implicitCast: |
| if (_options.implicitDowncastCheckPolicy.isEmitted) { |
| return true; |
| } |
| break; |
| case VariableUseKind.localType: |
| if (_options.assignmentCheckPolicy.isEmitted) { |
| return true; |
| } |
| break; |
| |
| case VariableUseKind.constructorTypeArgument: |
| ConstructorEntity constructor = |
| _elementMap.getConstructor(usage.member); |
| if (rtiNeed.classNeedsTypeArguments(constructor.enclosingClass)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.staticTypeArgument: |
| FunctionEntity method = _elementMap.getMethod(usage.member); |
| if (rtiNeed.methodNeedsTypeArguments(method)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.instanceTypeArgument: |
| Selector selector = _elementMap.getSelector(usage.invocation); |
| if (rtiNeed.selectorNeedsTypeArguments(selector)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.localTypeArgument: |
| // TODO(johnniwinther): We should be able to track direct local |
| // function invocations and not have to use the selector here. |
| Selector selector = _elementMap.getSelector(usage.invocation); |
| if (rtiNeed.localFunctionNeedsTypeArguments(usage.localFunction) || |
| rtiNeed.selectorNeedsTypeArguments(selector)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.memberParameter: |
| if (_options.parameterCheckPolicy.isEmitted) { |
| return true; |
| } else { |
| FunctionEntity method = _elementMap.getMethod(usage.member); |
| if (rtiNeed.methodNeedsSignature(method)) { |
| return true; |
| } |
| } |
| break; |
| case VariableUseKind.localParameter: |
| if (_options.parameterCheckPolicy.isEmitted) { |
| return true; |
| } else if (rtiNeed |
| .localFunctionNeedsSignature(usage.localFunction)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.memberReturnType: |
| if (_options.assignmentCheckPolicy.isEmitted) { |
| return true; |
| } else { |
| FunctionEntity method = _elementMap.getMethod(usage.member); |
| if (rtiNeed.methodNeedsSignature(method)) { |
| return true; |
| } |
| } |
| break; |
| case VariableUseKind.localReturnType: |
| if (_options.assignmentCheckPolicy.isEmitted) { |
| return true; |
| } else if (rtiNeed |
| .localFunctionNeedsSignature(usage.localFunction)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.fieldType: |
| if (_options.assignmentCheckPolicy.isEmitted || |
| _options.parameterCheckPolicy.isEmitted) { |
| return true; |
| } |
| break; |
| case VariableUseKind.listLiteral: |
| if (rtiNeed.classNeedsTypeArguments( |
| _elementMap.commonElements.jsArrayClass)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.mapLiteral: |
| if (rtiNeed.classNeedsTypeArguments( |
| _elementMap.commonElements.mapLiteralClass)) { |
| return true; |
| } |
| break; |
| case VariableUseKind.instantiationTypeArgument: |
| // TODO(johnniwinther): Use the static type of the expression. |
| if (rtiNeed.instantiationNeedsTypeArguments( |
| null, usage.instantiation.typeArguments.length)) { |
| return true; |
| } |
| break; |
| } |
| } |
| return false; |
| } |
| |
| if (includeForRti(scope.thisUsedAsFreeVariableIfNeedsRti)) { |
| scope.thisUsedAsFreeVariable = true; |
| } |
| scope.freeVariablesForRti.forEach( |
| (TypeVariableTypeWithContext typeVariable, Set<VariableUse> useSet) { |
| if (includeForRti(useSet)) { |
| scope.freeVariables.add(typeVariable); |
| } |
| }); |
| } |
| |
| Iterable<FunctionEntity> createClosureEntities( |
| JsClosedWorldBuilder closedWorldBuilder, |
| Map<MemberEntity, ScopeModel> closureModels, |
| ClosureRtiNeed rtiNeed) { |
| List<FunctionEntity> callMethods = <FunctionEntity>[]; |
| closureModels.forEach((MemberEntity member, ScopeModel model) { |
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member); |
| Map<Local, JRecordField> allBoxedVariables = |
| _elementMap.makeRecordContainer(model.scopeInfo, member, localsMap); |
| _scopeMap[member] = new JsScopeInfo.from( |
| allBoxedVariables, model.scopeInfo, localsMap, _elementMap); |
| |
| model.capturedScopesMap |
| .forEach((ir.Node node, KernelCapturedScope scope) { |
| Map<Local, JRecordField> boxedVariables = |
| _elementMap.makeRecordContainer(scope, member, localsMap); |
| _updateScopeBasedOnRtiNeed(scope, rtiNeed, member); |
| |
| if (scope is KernelCapturedLoopScope) { |
| _capturedScopesMap[node] = new JsCapturedLoopScope.from( |
| boxedVariables, scope, localsMap, _elementMap); |
| } else { |
| _capturedScopesMap[node] = new JsCapturedScope.from( |
| boxedVariables, scope, localsMap, _elementMap); |
| } |
| allBoxedVariables.addAll(boxedVariables); |
| }); |
| |
| Map<ir.TreeNode, KernelScopeInfo> closuresToGenerate = |
| model.closuresToGenerate; |
| for (ir.TreeNode node in closuresToGenerate.keys) { |
| ir.FunctionNode functionNode; |
| if (node is ir.FunctionDeclaration) { |
| functionNode = node.function; |
| } else if (node is ir.FunctionExpression) { |
| functionNode = node.function; |
| } else { |
| failedAt(member, "Unexpected closure node ${node}"); |
| } |
| KernelClosureClassInfo closureClassInfo = _produceSyntheticElements( |
| closedWorldBuilder, |
| member, |
| functionNode, |
| closuresToGenerate[node], |
| allBoxedVariables, |
| rtiNeed, |
| createSignatureMethod: |
| rtiNeed.localFunctionNeedsSignature(functionNode.parent)); |
| // Add also for the call method. |
| _scopeMap[closureClassInfo.callMethod] = closureClassInfo; |
| _scopeMap[closureClassInfo.signatureMethod] = closureClassInfo; |
| |
| // Set up capturedScope for signature method. This is distinct from |
| // _capturedScopesMap because there is no corresponding ir.Node for the |
| // signature. |
| if (rtiNeed.localFunctionNeedsSignature(functionNode.parent) && |
| model.capturedScopesMap[functionNode] != null) { |
| KernelCapturedScope capturedScope = |
| model.capturedScopesMap[functionNode]; |
| assert(capturedScope is! KernelCapturedLoopScope); |
| KernelCapturedScope signatureCapturedScope = |
| new KernelCapturedScope.forSignature(capturedScope); |
| _updateScopeBasedOnRtiNeed(signatureCapturedScope, rtiNeed, member); |
| _capturedScopeForSignatureMap[closureClassInfo.signatureMethod] = |
| new JsCapturedScope.from( |
| {}, signatureCapturedScope, localsMap, _elementMap); |
| } |
| callMethods.add(closureClassInfo.callMethod); |
| } |
| }); |
| return callMethods; |
| } |
| |
| /// Given what variables are captured at each point, construct closure classes |
| /// with fields containing the captured variables to replicate the Dart |
| /// closure semantics in JS. If this closure captures any variables (meaning |
| /// the closure accesses a variable that gets accessed at some point), then |
| /// boxForCapturedVariables stores the local context for those variables. |
| /// If no variables are captured, this parameter is null. |
| KernelClosureClassInfo _produceSyntheticElements( |
| JsClosedWorldBuilder closedWorldBuilder, |
| MemberEntity member, |
| ir.FunctionNode node, |
| KernelScopeInfo info, |
| Map<Local, JRecordField> boxedVariables, |
| ClosureRtiNeed rtiNeed, |
| {bool createSignatureMethod}) { |
| _updateScopeBasedOnRtiNeed(info, rtiNeed, member); |
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member); |
| KernelClosureClassInfo closureClassInfo = |
| closedWorldBuilder.buildClosureClass( |
| member, node, member.library, boxedVariables, info, localsMap, |
| createSignatureMethod: createSignatureMethod); |
| |
| // We want the original declaration where that function is used to point |
| // to the correct closure class. |
| _memberClosureRepresentationMap[closureClassInfo.callMethod] = |
| closureClassInfo; |
| _memberClosureRepresentationMap[closureClassInfo.signatureMethod] = |
| closureClassInfo; |
| _globalLocalsMap.setLocalsMap(closureClassInfo.callMethod, localsMap); |
| if (createSignatureMethod) { |
| _globalLocalsMap.setLocalsMap( |
| closureClassInfo.signatureMethod, localsMap); |
| } |
| if (node.parent is ir.Member) { |
| assert(_elementMap.getMember(node.parent) == member); |
| _memberClosureRepresentationMap[member] = closureClassInfo; |
| } else { |
| assert(node.parent is ir.FunctionExpression || |
| node.parent is ir.FunctionDeclaration); |
| _localClosureRepresentationMap[node.parent] = closureClassInfo; |
| } |
| return closureClassInfo; |
| } |
| |
| @override |
| ScopeInfo getScopeInfo(MemberEntity entity) { |
| // TODO(johnniwinther): Remove this check when constructor bodies a created |
| // eagerly with the J-model; a constructor body should have it's own |
| // [ClosureRepresentationInfo]. |
| if (entity is ConstructorBodyEntity) { |
| ConstructorBodyEntity constructorBody = entity; |
| entity = constructorBody.constructor; |
| } |
| |
| ScopeInfo scopeInfo = _scopeMap[entity]; |
| assert( |
| scopeInfo != null, failedAt(entity, "Missing scope info for $entity.")); |
| return scopeInfo; |
| } |
| |
| // TODO(efortuna): Eventually capturedScopesMap[node] should always |
| // be non-null, and we should just test that with an assert. |
| @override |
| CapturedScope getCapturedScope(MemberEntity entity) { |
| MemberDefinition definition = _elementMap.getMemberDefinition(entity); |
| switch (definition.kind) { |
| case MemberKind.regular: |
| case MemberKind.constructor: |
| case MemberKind.constructorBody: |
| case MemberKind.closureCall: |
| return _capturedScopesMap[definition.node] ?? const CapturedScope(); |
| case MemberKind.signature: |
| return _capturedScopeForSignatureMap[entity] ?? const CapturedScope(); |
| default: |
| throw failedAt(entity, "Unexpected member definition $definition"); |
| } |
| } |
| |
| @override |
| // TODO(efortuna): Eventually capturedScopesMap[node] should always |
| // be non-null, and we should just test that with an assert. |
| CapturedLoopScope getCapturedLoopScope(ir.Node loopNode) => |
| _capturedScopesMap[loopNode] ?? const CapturedLoopScope(); |
| |
| @override |
| ClosureRepresentationInfo getClosureInfo(ir.Node node) { |
| assert(node is ir.FunctionExpression || node is ir.FunctionDeclaration); |
| var closure = _localClosureRepresentationMap[node]; |
| assert( |
| closure != null, |
| "Corresponding closure class not found for $node. " |
| "Closures found for ${_localClosureRepresentationMap.keys}"); |
| return closure; |
| } |
| } |
| |
| enum VariableUseKind { |
| /// An explicit variable use. |
| /// |
| /// For type variable this is an explicit as-cast, an is-test or a type |
| /// literal. |
| explicit, |
| |
| /// A type variable used in the type of a local variable. |
| localType, |
| |
| /// A type variable used in an implicit cast. |
| implicitCast, |
| |
| /// A type variable passed as the type argument of a list literal. |
| listLiteral, |
| |
| /// A type variable passed as the type argument of a map literal. |
| mapLiteral, |
| |
| /// A type variable passed as a type argument to a constructor. |
| constructorTypeArgument, |
| |
| /// A type variable passed as a type argument to a static method. |
| staticTypeArgument, |
| |
| /// A type variable passed as a type argument to an instance method. |
| instanceTypeArgument, |
| |
| /// A type variable passed as a type argument to a local function. |
| localTypeArgument, |
| |
| /// A type variable in a parameter type of a member. |
| memberParameter, |
| |
| /// A type variable in a parameter type of a local function. |
| localParameter, |
| |
| /// A type variable used in a return type of a member. |
| memberReturnType, |
| |
| /// A type variable used in a return type of a local function. |
| localReturnType, |
| |
| /// A type variable in a field type. |
| fieldType, |
| |
| /// A type argument of an generic instantiation. |
| instantiationTypeArgument, |
| } |
| |
| class VariableUse { |
| final VariableUseKind kind; |
| final ir.Member member; |
| final ir.TreeNode /*ir.FunctionDeclaration|ir.FunctionExpression*/ |
| localFunction; |
| final ir.MethodInvocation invocation; |
| final ir.Instantiation instantiation; |
| |
| const VariableUse._simple(this.kind) |
| : this.member = null, |
| this.localFunction = null, |
| this.invocation = null, |
| this.instantiation = null; |
| |
| VariableUse.memberParameter(this.member) |
| : this.kind = VariableUseKind.memberParameter, |
| this.localFunction = null, |
| this.invocation = null, |
| this.instantiation = null; |
| |
| VariableUse.localParameter(this.localFunction) |
| : this.kind = VariableUseKind.localParameter, |
| this.member = null, |
| this.invocation = null, |
| this.instantiation = null { |
| assert(localFunction is ir.FunctionDeclaration || |
| localFunction is ir.FunctionExpression); |
| } |
| |
| VariableUse.memberReturnType(this.member) |
| : this.kind = VariableUseKind.memberReturnType, |
| this.localFunction = null, |
| this.invocation = null, |
| this.instantiation = null; |
| |
| VariableUse.localReturnType(this.localFunction) |
| : this.kind = VariableUseKind.localReturnType, |
| this.member = null, |
| this.invocation = null, |
| this.instantiation = null { |
| assert(localFunction is ir.FunctionDeclaration || |
| localFunction is ir.FunctionExpression); |
| } |
| |
| VariableUse.constructorTypeArgument(this.member) |
| : this.kind = VariableUseKind.constructorTypeArgument, |
| this.localFunction = null, |
| this.invocation = null, |
| this.instantiation = null; |
| |
| VariableUse.staticTypeArgument(this.member) |
| : this.kind = VariableUseKind.staticTypeArgument, |
| this.localFunction = null, |
| this.invocation = null, |
| this.instantiation = null; |
| |
| VariableUse.instanceTypeArgument(this.invocation) |
| : this.kind = VariableUseKind.instanceTypeArgument, |
| this.member = null, |
| this.localFunction = null, |
| this.instantiation = null; |
| |
| VariableUse.localTypeArgument(this.localFunction, this.invocation) |
| : this.kind = VariableUseKind.localTypeArgument, |
| this.member = null, |
| this.instantiation = null { |
| assert(localFunction is ir.FunctionDeclaration || |
| localFunction is ir.FunctionExpression); |
| } |
| |
| VariableUse.instantiationTypeArgument(this.instantiation) |
| : this.kind = VariableUseKind.instantiationTypeArgument, |
| this.member = null, |
| this.localFunction = null, |
| this.invocation = null; |
| |
| static const VariableUse explicit = |
| const VariableUse._simple(VariableUseKind.explicit); |
| |
| static const VariableUse localType = |
| const VariableUse._simple(VariableUseKind.localType); |
| |
| static const VariableUse implicitCast = |
| const VariableUse._simple(VariableUseKind.implicitCast); |
| |
| static const VariableUse listLiteral = |
| const VariableUse._simple(VariableUseKind.listLiteral); |
| |
| static const VariableUse mapLiteral = |
| const VariableUse._simple(VariableUseKind.mapLiteral); |
| |
| static const VariableUse fieldType = |
| const VariableUse._simple(VariableUseKind.fieldType); |
| |
| int get hashCode => |
| kind.hashCode * 11 + |
| member.hashCode * 13 + |
| localFunction.hashCode * 17 + |
| invocation.hashCode * 19 + |
| instantiation.hashCode * 23; |
| |
| bool operator ==(other) { |
| if (identical(this, other)) return true; |
| if (other is! VariableUse) return false; |
| return kind == other.kind && |
| member == other.member && |
| localFunction == other.localFunction && |
| invocation == other.invocation && |
| instantiation == other.instantiation; |
| } |
| |
| String toString() => 'VariableUse(kind=$kind,member=$member,' |
| 'localFunction=$localFunction,invocation=$invocation,' |
| 'instantiation=$instantiation)'; |
| } |
| |
| class KernelScopeInfo { |
| final Set<ir.VariableDeclaration> localsUsedInTryOrSync; |
| final bool hasThisLocal; |
| final Set<ir.VariableDeclaration> boxedVariables; |
| // If boxedVariables is empty, this will be null, because no variables will |
| // need to be boxed. |
| final NodeBox capturedVariablesAccessor; |
| |
| /// The set of variables that were defined in another scope, but are used in |
| /// this scope. The items in this set are either of type VariableDeclaration |
| /// or TypeParameterTypeWithContext. |
| Set<ir.Node /* VariableDeclaration | TypeParameterTypeWithContext */ > |
| freeVariables = new Set<ir.Node>(); |
| |
| /// A set of type parameters that are defined in another scope and are only |
| /// used if runtime type information is checked. If runtime type information |
| /// needs to be retained, all of these type variables will be added ot the |
| /// freeVariables set. Whether these variables are actually used as |
| /// freeVariables will be set by the time this structure is converted to a |
| /// JsScopeInfo, so JsScopeInfo does not need to use them. |
| Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti = |
| <TypeVariableTypeWithContext, Set<VariableUse>>{}; |
| |
| /// If true, `this` is used as a free variable, in this scope. It is stored |
| /// separately from [freeVariables] because there is no single |
| /// `VariableDeclaration` node that represents `this`. |
| bool thisUsedAsFreeVariable = false; |
| |
| /// If true, `this` is used as a free variable, in this scope if we are also |
| /// performing runtime type checks. It is stored |
| /// separately from [thisUsedAsFreeVariable] because we don't know at this |
| /// stage if we will be needing type checks for this scope. |
| Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti = new Set<VariableUse>(); |
| |
| KernelScopeInfo(this.hasThisLocal) |
| : localsUsedInTryOrSync = new Set<ir.VariableDeclaration>(), |
| boxedVariables = new Set<ir.VariableDeclaration>(), |
| capturedVariablesAccessor = null; |
| |
| KernelScopeInfo.from(this.hasThisLocal, KernelScopeInfo info) |
| : localsUsedInTryOrSync = info.localsUsedInTryOrSync, |
| boxedVariables = info.boxedVariables, |
| capturedVariablesAccessor = null; |
| |
| KernelScopeInfo.withBoxedVariables( |
| this.boxedVariables, |
| this.capturedVariablesAccessor, |
| this.localsUsedInTryOrSync, |
| this.freeVariables, |
| this.freeVariablesForRti, |
| this.thisUsedAsFreeVariable, |
| this.thisUsedAsFreeVariableIfNeedsRti, |
| this.hasThisLocal); |
| |
| String toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.write('KernelScopeInfo(this=$hasThisLocal,'); |
| sb.write('freeVriables=$freeVariables,'); |
| sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}'); |
| String comma = ''; |
| sb.write('freeVariablesForRti={'); |
| freeVariablesForRti.forEach((key, value) { |
| sb.write('$comma$key:$value'); |
| comma = ','; |
| }); |
| sb.write('})'); |
| return sb.toString(); |
| } |
| } |
| |
| /// Helper method to get or create a Local variable out of a variable |
| /// declaration or type parameter. |
| Local _getLocal(ir.Node variable, KernelToLocalsMap localsMap, |
| KernelToElementMap elementMap) { |
| assert(variable is ir.VariableDeclaration || |
| variable is TypeVariableTypeWithContext); |
| if (variable is ir.VariableDeclaration) { |
| return localsMap.getLocalVariable(variable); |
| } else if (variable is TypeVariableTypeWithContext) { |
| return localsMap.getLocalTypeVariable(variable.type, elementMap); |
| } |
| throw new ArgumentError('Only know how to get/create locals for ' |
| 'VariableDeclarations or TypeParameterTypeWithContext. Recieved ' |
| '${variable.runtimeType}'); |
| } |
| |
| class JsScopeInfo extends ScopeInfo { |
| final Set<Local> localsUsedInTryOrSync; |
| final Local thisLocal; |
| final Map<Local, JRecordField> boxedVariables; |
| |
| /// The set of variables that were defined in another scope, but are used in |
| /// this scope. |
| final Set<Local> freeVariables; |
| |
| JsScopeInfo.from(this.boxedVariables, KernelScopeInfo info, |
| KernelToLocalsMap localsMap, KernelToElementMap elementMap) |
| : this.thisLocal = |
| info.hasThisLocal ? new ThisLocal(localsMap.currentMember) : null, |
| this.localsUsedInTryOrSync = |
| info.localsUsedInTryOrSync.map(localsMap.getLocalVariable).toSet(), |
| this.freeVariables = info.freeVariables |
| .map((ir.Node node) => _getLocal(node, localsMap, elementMap)) |
| .toSet() { |
| if (info.thisUsedAsFreeVariable) { |
| this.freeVariables.add(this.thisLocal); |
| } |
| } |
| |
| void forEachBoxedVariable(f(Local local, FieldEntity field)) { |
| boxedVariables.forEach((Local l, JRecordField box) { |
| f(l, box); |
| }); |
| } |
| |
| bool localIsUsedInTryOrSync(Local variable) => |
| localsUsedInTryOrSync.contains(variable); |
| |
| String toString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.write('this=$thisLocal,'); |
| sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}'); |
| return sb.toString(); |
| } |
| |
| bool isBoxed(Local variable) => boxedVariables.containsKey(variable); |
| } |
| |
| class KernelCapturedScope extends KernelScopeInfo { |
| KernelCapturedScope( |
| Set<ir.VariableDeclaration> boxedVariables, |
| NodeBox capturedVariablesAccessor, |
| Set<ir.VariableDeclaration> localsUsedInTryOrSync, |
| Set<ir.Node /* VariableDeclaration | TypeVariableTypeWithContext */ > |
| freeVariables, |
| Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti, |
| bool thisUsedAsFreeVariable, |
| Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti, |
| bool hasThisLocal) |
| : super.withBoxedVariables( |
| boxedVariables, |
| capturedVariablesAccessor, |
| localsUsedInTryOrSync, |
| freeVariables, |
| freeVariablesForRti, |
| thisUsedAsFreeVariable, |
| thisUsedAsFreeVariableIfNeedsRti, |
| hasThisLocal); |
| |
| // Loops through the free variables of an existing KernelCapturedScope and |
| // creates a new KernelCapturedScope that only captures type variables. |
| KernelCapturedScope.forSignature(KernelCapturedScope scope) |
| : this( |
| _empty, |
| null, |
| _empty, |
| scope.freeVariables.where( |
| (ir.Node variable) => variable is TypeVariableTypeWithContext), |
| scope.freeVariablesForRti, |
| scope.thisUsedAsFreeVariable, |
| scope.thisUsedAsFreeVariableIfNeedsRti, |
| scope.hasThisLocal); |
| |
| // Silly hack because we don't have const sets. |
| static final Set<ir.VariableDeclaration> _empty = new Set(); |
| |
| bool get requiresContextBox => boxedVariables.isNotEmpty; |
| } |
| |
| class JsCapturedScope extends JsScopeInfo implements CapturedScope { |
| final Local context; |
| |
| JsCapturedScope.from( |
| Map<Local, JRecordField> boxedVariables, |
| KernelCapturedScope capturedScope, |
| KernelToLocalsMap localsMap, |
| KernelToElementMap elementMap) |
| : this.context = |
| boxedVariables.isNotEmpty ? boxedVariables.values.first.box : null, |
| super.from(boxedVariables, capturedScope, localsMap, elementMap); |
| |
| bool get requiresContextBox => boxedVariables.isNotEmpty; |
| } |
| |
| class KernelCapturedLoopScope extends KernelCapturedScope { |
| final List<ir.VariableDeclaration> boxedLoopVariables; |
| |
| KernelCapturedLoopScope( |
| Set<ir.VariableDeclaration> boxedVariables, |
| NodeBox capturedVariablesAccessor, |
| this.boxedLoopVariables, |
| Set<ir.VariableDeclaration> localsUsedInTryOrSync, |
| Set<ir.Node /* VariableDeclaration | TypeVariableTypeWithContext */ > |
| freeVariables, |
| Map<TypeVariableTypeWithContext, Set<VariableUse>> freeVariablesForRti, |
| bool thisUsedAsFreeVariable, |
| Set<VariableUse> thisUsedAsFreeVariableIfNeedsRti, |
| bool hasThisLocal) |
| : super( |
| boxedVariables, |
| capturedVariablesAccessor, |
| localsUsedInTryOrSync, |
| freeVariables, |
| freeVariablesForRti, |
| thisUsedAsFreeVariable, |
| thisUsedAsFreeVariableIfNeedsRti, |
| hasThisLocal); |
| |
| bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty; |
| } |
| |
| class JsCapturedLoopScope extends JsCapturedScope implements CapturedLoopScope { |
| final List<Local> boxedLoopVariables; |
| |
| JsCapturedLoopScope.from( |
| Map<Local, JRecordField> boxedVariables, |
| KernelCapturedLoopScope capturedScope, |
| KernelToLocalsMap localsMap, |
| KernelToElementMap elementMap) |
| : this.boxedLoopVariables = capturedScope.boxedLoopVariables |
| .map(localsMap.getLocalVariable) |
| .toList(), |
| super.from(boxedVariables, capturedScope, localsMap, elementMap); |
| |
| bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty; |
| } |
| |
| // TODO(johnniwinther): Add unittest for the computed [ClosureClass]. |
| class KernelClosureClassInfo extends JsScopeInfo |
| implements ClosureRepresentationInfo { |
| JFunction callMethod; |
| JSignatureMethod signatureMethod; |
| final Local closureEntity; |
| final Local thisLocal; |
| final JClass closureClassEntity; |
| |
| final Map<Local, JField> localToFieldMap = new Map<Local, JField>(); |
| |
| KernelClosureClassInfo.fromScopeInfo( |
| this.closureClassEntity, |
| ir.FunctionNode closureSourceNode, |
| Map<Local, JRecordField> boxedVariables, |
| KernelScopeInfo info, |
| KernelToLocalsMap localsMap, |
| this.closureEntity, |
| this.thisLocal, |
| KernelToElementMap elementMap) |
| : super.from(boxedVariables, info, localsMap, elementMap); |
| |
| List<Local> get createdFieldEntities => localToFieldMap.keys.toList(); |
| |
| @override |
| Local getLocalForField(FieldEntity field) { |
| for (Local local in localToFieldMap.keys) { |
| if (localToFieldMap[local] == field) { |
| return local; |
| } |
| } |
| failedAt(field, "No local for $field. Options: $localToFieldMap"); |
| return null; |
| } |
| |
| FieldEntity get thisFieldEntity => localToFieldMap[thisLocal]; |
| |
| @override |
| void forEachBoxedVariable(f(Local local, JField field)) { |
| boxedVariables.forEach(f); |
| } |
| |
| void forEachFreeVariable(f(Local variable, JField field)) { |
| localToFieldMap.forEach(f); |
| boxedVariables.forEach(f); |
| } |
| |
| bool isVariableBoxed(Local variable) => |
| boxedVariables.keys.contains(variable); |
| |
| bool get isClosure => true; |
| } |
| |
| /// A local variable to disambiguate between a variable that has been captured |
| /// from one scope to another. This is the ir.Node version that corresponds to |
| /// [BoxLocal]. |
| class NodeBox { |
| final String name; |
| final ir.TreeNode executableContext; |
| NodeBox(this.name, this.executableContext); |
| } |
| |
| class JClosureClass extends JClass { |
| JClosureClass(JLibrary library, String name) |
| : super(library, name, isAbstract: false); |
| |
| @override |
| bool get isClosure => true; |
| |
| String toString() => '${jsElementPrefix}closure_class($name)'; |
| } |
| |
| class JClosureField extends JField implements PrivatelyNamedJSEntity { |
| final Local _declaredEntity; |
| JClosureField(String name, KernelClosureClassInfo containingClass, |
| bool isConst, bool isAssignable, this._declaredEntity) |
| : super( |
| containingClass.closureClassEntity.library, |
| containingClass.closureClassEntity, |
| new Name(name, containingClass.closureClassEntity.library), |
| isAssignable: isAssignable, |
| isConst: isConst, |
| isStatic: false); |
| |
| @override |
| Local get declaredEntity => _declaredEntity; |
| |
| @override |
| Entity get rootOfScope => enclosingClass; |
| } |
| |
| /// A container for variables declared in a particular scope that are accessed |
| /// elsewhere. |
| // TODO(efortuna, johnniwinther): Don't implement JClass. This isn't actually a |
| // class. |
| class JRecord extends JClass { |
| JRecord(LibraryEntity library, String name) |
| : super(library, name, isAbstract: false); |
| |
| bool get isClosure => false; |
| |
| String toString() => '${jsElementPrefix}record_container($name)'; |
| } |
| |
| /// A variable that has been "boxed" to prevent name shadowing with the |
| /// original variable and ensure that this variable is updated/read with the |
| /// most recent value. |
| /// This corresponds to BoxFieldElement; we reuse BoxLocal from the original |
| /// algorithm to correspond to the actual name of the variable. |
| class JRecordField extends JField { |
| final BoxLocal box; |
| JRecordField(String name, this.box, JClass containingClass, bool isConst) |
| : super(containingClass.library, containingClass, |
| new Name(name, containingClass.library), |
| isStatic: false, isAssignable: true, isConst: isConst); |
| |
| @override |
| bool get isInstanceMember => false; |
| } |
| |
| class ClosureClassDefinition implements ClassDefinition { |
| final ClassEntity cls; |
| final SourceSpan location; |
| |
| ClosureClassDefinition(this.cls, this.location); |
| |
| ClassKind get kind => ClassKind.closure; |
| |
| ir.Node get node => |
| throw new UnsupportedError('ClosureClassDefinition.node for $cls'); |
| |
| String toString() => |
| 'ClosureClassDefinition(kind:$kind,cls:$cls,location:$location)'; |
| } |
| |
| abstract class ClosureMemberData implements MemberData { |
| final MemberDefinition definition; |
| final InterfaceType memberThisType; |
| |
| ClosureMemberData(this.definition, this.memberThisType); |
| |
| @override |
| Iterable<ConstantValue> getMetadata(KernelToElementMap elementMap) { |
| return const <ConstantValue>[]; |
| } |
| |
| @override |
| InterfaceType getMemberThisType(KernelToElementMapForBuilding elementMap) { |
| return memberThisType; |
| } |
| } |
| |
| class ClosureFunctionData extends ClosureMemberData |
| with FunctionDataMixin |
| implements FunctionData { |
| final FunctionType functionType; |
| final ir.FunctionNode functionNode; |
| final ClassTypeVariableAccess classTypeVariableAccess; |
| |
| ClosureFunctionData( |
| ClosureMemberDefinition definition, |
| InterfaceType memberThisType, |
| this.functionType, |
| this.functionNode, |
| this.classTypeVariableAccess) |
| : super(definition, memberThisType); |
| |
| void forEachParameter(KernelToElementMapForBuilding elementMap, |
| void f(DartType type, String name, ConstantValue defaultValue)) { |
| void handleParameter(ir.VariableDeclaration node, {bool isOptional: true}) { |
| DartType type = elementMap.getDartType(node.type); |
| String name = node.name; |
| ConstantValue defaultValue; |
| if (isOptional) { |
| if (node.initializer != null) { |
| defaultValue = elementMap.getConstantValue(node.initializer); |
| } else { |
| defaultValue = new NullConstantValue(); |
| } |
| } |
| f(type, name, defaultValue); |
| } |
| |
| for (int i = 0; i < functionNode.positionalParameters.length; i++) { |
| handleParameter(functionNode.positionalParameters[i], |
| isOptional: i >= functionNode.requiredParameterCount); |
| } |
| functionNode.namedParameters.toList() |
| ..sort(namedOrdering) |
| ..forEach(handleParameter); |
| } |
| |
| @override |
| FunctionType getFunctionType(KernelToElementMap elementMap) { |
| return functionType; |
| } |
| } |
| |
| class ClosureFieldData extends ClosureMemberData implements FieldData { |
| DartType _type; |
| ClosureFieldData(MemberDefinition definition, InterfaceType memberThisType) |
| : super(definition, memberThisType); |
| |
| @override |
| DartType getFieldType(KernelToElementMap elementMap) { |
| if (_type != null) return _type; |
| ir.TreeNode sourceNode = definition.node; |
| ir.DartType type; |
| if (sourceNode is ir.Class) { |
| type = sourceNode.thisType; |
| } else if (sourceNode is ir.VariableDeclaration) { |
| type = sourceNode.type; |
| } else if (sourceNode is ir.Field) { |
| type = sourceNode.type; |
| } else if (sourceNode is ir.TypeLiteral) { |
| type = sourceNode.type; |
| } else if (sourceNode is ir.Typedef) { |
| type = sourceNode.type; |
| } else if (sourceNode is ir.TypeParameter) { |
| type = sourceNode.bound; |
| } else { |
| failedAt( |
| definition.member, |
| 'Unexpected node type ${sourceNode} in ' |
| 'ClosureFieldData.getFieldType'); |
| } |
| return _type = elementMap.getDartType(type); |
| } |
| |
| @override |
| ConstantExpression getFieldConstantExpression(KernelToElementMap elementMap) { |
| failedAt( |
| definition.member, |
| "Unexpected field ${definition.member} in " |
| "ClosureFieldData.getFieldConstantExpression"); |
| return null; |
| } |
| |
| @override |
| ConstantValue getConstantFieldInitializer(KernelToElementMap elementMap) { |
| failedAt( |
| definition.member, |
| "Unexpected field ${definition.member} in " |
| "ClosureFieldData.getConstantFieldInitializer"); |
| return null; |
| } |
| |
| @override |
| bool hasConstantFieldInitializer(KernelToElementMap elementMap) { |
| return false; |
| } |
| |
| @override |
| ConstantValue getFieldConstantValue(KernelToElementMap elementMap) { |
| return null; |
| } |
| |
| @override |
| ClassTypeVariableAccess get classTypeVariableAccess => |
| ClassTypeVariableAccess.none; |
| } |
| |
| class ClosureMemberDefinition implements MemberDefinition { |
| final MemberEntity member; |
| final SourceSpan location; |
| final MemberKind kind; |
| final ir.Node node; |
| |
| ClosureMemberDefinition(this.member, this.location, this.kind, this.node); |
| |
| String toString() => |
| 'ClosureMemberDefinition(kind:$kind,member:$member,location:$location)'; |
| } |
| |
| class RecordContainerDefinition implements ClassDefinition { |
| final ClassEntity cls; |
| final SourceSpan location; |
| |
| RecordContainerDefinition(this.cls, this.location); |
| |
| ClassKind get kind => ClassKind.record; |
| |
| ir.Node get node => |
| throw new UnsupportedError('RecordContainerDefinition.node for $cls'); |
| |
| String toString() => |
| 'RecordContainerDefinition(kind:$kind,cls:$cls,location:$location)'; |
| } |
| |
| /// Collection of scope data collected for a single member. |
| class ScopeModel { |
| /// Collection [ScopeInfo] data for the member. |
| KernelScopeInfo scopeInfo; |
| |
| /// Collected [CapturedScope] data for nodes. |
| Map<ir.Node, KernelCapturedScope> capturedScopesMap = |
| <ir.Node, KernelCapturedScope>{}; |
| |
| /// Collected [ScopeInfo] data for nodes. |
| Map<ir.TreeNode, KernelScopeInfo> closuresToGenerate = |
| <ir.TreeNode, KernelScopeInfo>{}; |
| |
| String toString() { |
| return '$scopeInfo\n$capturedScopesMap\n$closuresToGenerate'; |
| } |
| } |
| |
| enum TypeVariableKind { cls, method, local, function } |
| |
| /// A fake ir.Node that holds the TypeParameterType as well as the context in |
| /// which it occurs. |
| class TypeVariableTypeWithContext implements ir.Node { |
| final ir.Node context; |
| final ir.TypeParameterType type; |
| final TypeVariableKind kind; |
| final ir.TreeNode typeDeclaration; |
| |
| /// [context] can be either an ir.Member or a ir.FunctionDeclaration or |
| /// ir.FunctionExpression. |
| factory TypeVariableTypeWithContext( |
| ir.TypeParameterType type, ir.TreeNode context) { |
| TypeVariableKind kind; |
| ir.TreeNode typeDeclaration = type.parameter.parent; |
| if (typeDeclaration == null) { |
| // We have a function type variable, like `T` in `void Function<T>(int)`. |
| kind = TypeVariableKind.function; |
| } else if (typeDeclaration is ir.Class) { |
| // We have a class type variable, like `T` in `class Class<T> { ... }`. |
| kind = TypeVariableKind.cls; |
| } else if (typeDeclaration.parent is ir.Member) { |
| ir.Member member = typeDeclaration.parent; |
| if (member is ir.Constructor || |
| (member is ir.Procedure && member.isFactory)) { |
| // We have a synthesized generic method type variable for a class type |
| // variable. |
| // TODO(johnniwinther): Handle constructor/factory type variables as |
| // method type variables. |
| kind = TypeVariableKind.cls; |
| typeDeclaration = member.enclosingClass; |
| } else { |
| // We have a generic method type variable, like `T` in |
| // `m<T>() { ... }`. |
| kind = TypeVariableKind.method; |
| typeDeclaration = typeDeclaration.parent; |
| context = typeDeclaration; |
| } |
| } else { |
| // We have a generic local function type variable, like `T` in |
| // `m() { local<T>() { ... } ... }`. |
| assert( |
| typeDeclaration.parent is ir.FunctionExpression || |
| typeDeclaration.parent is ir.FunctionDeclaration, |
| "Unexpected type declaration: $typeDeclaration"); |
| kind = TypeVariableKind.local; |
| typeDeclaration = typeDeclaration.parent; |
| context = typeDeclaration; |
| } |
| return new TypeVariableTypeWithContext.internal( |
| type, context, kind, typeDeclaration); |
| } |
| |
| TypeVariableTypeWithContext.internal( |
| this.type, this.context, this.kind, this.typeDeclaration); |
| |
| accept(ir.Visitor v) { |
| throw new UnsupportedError('TypeVariableTypeWithContext.accept'); |
| } |
| |
| visitChildren(ir.Visitor v) { |
| throw new UnsupportedError('TypeVariableTypeWithContext.visitChildren'); |
| } |
| |
| int get hashCode => type.hashCode; |
| |
| bool operator ==(other) { |
| if (other is! TypeVariableTypeWithContext) return false; |
| return type == other.type && context == other.context; |
| } |
| |
| String toString() => |
| 'TypeVariableTypeWithContext(type=$type,context=$context,' |
| 'kind=$kind,typeDeclaration=$typeDeclaration)'; |
| } |
| |
| abstract class ClosureRtiNeed { |
| bool classNeedsTypeArguments(ClassEntity cls); |
| |
| bool methodNeedsTypeArguments(FunctionEntity method); |
| |
| bool methodNeedsSignature(MemberEntity method); |
| |
| bool localFunctionNeedsTypeArguments(ir.Node node); |
| |
| bool localFunctionNeedsSignature(ir.Node node); |
| |
| bool selectorNeedsTypeArguments(Selector selector); |
| |
| bool instantiationNeedsTypeArguments( |
| DartType functionType, int typeArgumentCount); |
| } |