| Index: pkg/compiler/lib/src/js_model/closure.dart
|
| diff --git a/pkg/compiler/lib/src/js_model/closure.dart b/pkg/compiler/lib/src/js_model/closure.dart
|
| index 1e086df0ee1e031e229e06446932c0be6b491f6b..bf4fad2c23e435e4caea867d47b2f3041d2dd06b 100644
|
| --- a/pkg/compiler/lib/src/js_model/closure.dart
|
| +++ b/pkg/compiler/lib/src/js_model/closure.dart
|
| @@ -96,25 +96,41 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
|
| JsClosedWorld closedWorldRefiner) {
|
| closureModels.forEach((MemberEntity member, ScopeModel model) {
|
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member);
|
| - if (model.scopeInfo != null) {
|
| - _scopeMap[member] = new JsScopeInfo.from(model.scopeInfo, localsMap);
|
| - }
|
| -
|
| + assert(model.scopeInfo != null);
|
| + var boxedVariables =
|
| + _elementMap.makeRecordContainer(model.scopeInfo, member, localsMap);
|
| + _scopeMap[member] =
|
| + new JsScopeInfo.from(boxedVariables, model.scopeInfo, localsMap);
|
| + print('created the following scope info $member ${_scopeMap[member]}');
|
| +
|
| + Set<JsCapturedScope> capturedScopes = new Set<JsCaptuerdScope>();
|
| model.capturedScopesMap
|
| .forEach((ir.Node node, KernelCapturedScope scope) {
|
| + boxedVariables = _elementMap.makeRecordContainer(
|
| + model.scopeInfo,
|
| + _elementMap.getMember(node),
|
| + localsMap); // TODO: (maybe don't need to call this again.)
|
| if (scope is KernelCapturedLoopScope) {
|
| _capturedScopesMap[node] =
|
| - new JsCapturedLoopScope.from(scope, localsMap);
|
| + new JsCapturedLoopScope.from(boxedVariables, scope, localsMap);
|
| } else {
|
| - _capturedScopesMap[node] = new JsCapturedScope.from(scope, localsMap);
|
| + _capturedScopesMap[node] =
|
| + new JsCapturedScope.from(boxedVariables, scope, localsMap);
|
| }
|
| + capturedScopes.add(_capturedScopesMap[node]);
|
| + print('captured scope info $node ${_capturedScopesMap[node]}');
|
| });
|
|
|
| Map<ir.FunctionNode, KernelScopeInfo> closuresToGenerate =
|
| model.closuresToGenerate;
|
| for (ir.FunctionNode node in closuresToGenerate.keys) {
|
| - _produceSyntheticElements(
|
| - member, node, closuresToGenerate[node], closedWorldRefiner);
|
| + KernelClosureClass closureClass = _produceSyntheticElements(member,
|
| + node, closuresToGenerate[node], capturedScopes, closedWorldRefiner);
|
| + // Add one for each call method.
|
| + KernelToLocalsMap localsMap =
|
| + _globalLocalsMap.getLocalsMap(closureClass.callMethod);
|
| + _scopeMap[closureClass.callMethod] = closureClass;
|
| + print('then we made $closureClass starting from ${node.parent}');
|
| }
|
| });
|
| }
|
| @@ -125,11 +141,22 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
|
| /// 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.
|
| - void _produceSyntheticElements(MemberEntity member, ir.FunctionNode node,
|
| - KernelScopeInfo info, JsClosedWorld closedWorldRefiner) {
|
| + KernelClosureClass _produceSyntheticElements(
|
| + MemberEntity member,
|
| + ir.FunctionNode node,
|
| + KernelScopeInfo info,
|
| + Set<JsCaptuerdScope> capturedScopes,
|
| + JsClosedWorld closedWorldRefiner) {
|
| KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member);
|
| +
|
| KernelClosureClass closureClass = closedWorldRefiner.buildClosureClass(
|
| - member, node, member.library, info, node.location, localsMap);
|
| + member,
|
| + node,
|
| + member.library,
|
| + capturedScopes,
|
| + info,
|
| + node.location,
|
| + localsMap);
|
|
|
| // We want the original declaration where that function is used to point
|
| // to the correct closure class.
|
| @@ -142,6 +169,7 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
|
| }
|
| assert(entity != null);
|
| _closureRepresentationMap[entity] = closureClass;
|
| + return closureClass;
|
| }
|
|
|
| @override
|
| @@ -157,8 +185,6 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
|
| return _scopeMap[entity] ?? getClosureRepresentationInfo(entity);
|
| }
|
|
|
| - // 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);
|
| @@ -174,8 +200,6 @@ class KernelClosureConversionTask extends ClosureConversionTask<ir.Node> {
|
| }
|
|
|
| @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();
|
|
|
| @@ -208,6 +232,11 @@ class KernelScopeInfo {
|
| /// this scope.
|
| Set<ir.VariableDeclaration> freeVariables = new Set<ir.VariableDeclaration>();
|
|
|
| + /// The set of scopes that this scope captures. In practice we only populate
|
| + /// it if this scope uses a particular variable that is defined in another
|
| + /// scope, and this information is only really useful for closures.
|
| + final Set<ir.Node> capturedScopes = new Set<ir.Node>();
|
| +
|
| KernelScopeInfo(this.hasThisLocal)
|
| : localsUsedInTryOrSync = new Set<ir.VariableDeclaration>(),
|
| boxedVariables = new Set<ir.VariableDeclaration>(),
|
| @@ -228,7 +257,10 @@ class KernelScopeInfo {
|
| String toString() {
|
| StringBuffer sb = new StringBuffer();
|
| sb.write('this=$hasThisLocal,');
|
| - sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}');
|
| + sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}},');
|
| + sb.write('freeVariables={${freeVariables.join(', ')}},');
|
| + sb.write('boxedVariables={${boxedVariables.join(', ')}},');
|
| + sb.write('capturedVariablesAccessor=$capturedVariablesAccessor');
|
| return sb.toString();
|
| }
|
| }
|
| @@ -236,29 +268,24 @@ class KernelScopeInfo {
|
| class JsScopeInfo extends ScopeInfo {
|
| final Set<Local> localsUsedInTryOrSync;
|
| final Local thisLocal;
|
| - final Set<Local> boxedVariables;
|
| + final Map<Local, JRecord> boxedVariables;
|
|
|
| /// The set of variables that were defined in another scope, but are used in
|
| /// this scope.
|
| final Set<Local> freeVariables;
|
|
|
| - JsScopeInfo(this.thisLocal, this.localsUsedInTryOrSync, this.boxedVariables,
|
| - this.freeVariables);
|
| -
|
| - JsScopeInfo.from(KernelScopeInfo info, KernelToLocalsMap localsMap)
|
| + JsScopeInfo.from(
|
| + this.boxedVariables, KernelScopeInfo info, KernelToLocalsMap localsMap)
|
| : this.thisLocal =
|
| info.hasThisLocal ? new ThisLocal(localsMap.currentMember) : null,
|
| this.localsUsedInTryOrSync =
|
| info.localsUsedInTryOrSync.map(localsMap.getLocalVariable).toSet(),
|
| - this.boxedVariables =
|
| - info.boxedVariables.map(localsMap.getLocalVariable).toSet(),
|
| this.freeVariables =
|
| info.freeVariables.map(localsMap.getLocalVariable).toSet();
|
|
|
| void forEachBoxedVariable(f(Local local, FieldEntity field)) {
|
| - boxedVariables.forEach((Local l) {
|
| - // TODO(efortuna): add FieldEntities as created.
|
| - f(l, null);
|
| + boxedVariables.forEach((Local l, JRecord box) {
|
| + f(l, box);
|
| });
|
| }
|
|
|
| @@ -267,21 +294,21 @@ class JsScopeInfo extends ScopeInfo {
|
|
|
| String toString() {
|
| StringBuffer sb = new StringBuffer();
|
| + sb.write('JsScopeInfo: ');
|
| sb.write('this=$thisLocal,');
|
| sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}');
|
| + sb.write('freeVariables={${freeVariables.join(', ')}}');
|
| + sb.write('boxedVariables={$boxedVariables}');
|
| return sb.toString();
|
| }
|
|
|
| - bool isBoxed(Local variable) => boxedVariables.contains(variable);
|
| + bool isBoxed(Local variable) => boxedVariables.containsKey(variable);
|
| }
|
|
|
| class KernelCapturedScope extends KernelScopeInfo {
|
| - final ir.TreeNode context;
|
| -
|
| KernelCapturedScope(
|
| Set<ir.VariableDeclaration> boxedVariables,
|
| NodeBox capturedVariablesAccessor,
|
| - this.context,
|
| Set<ir.VariableDeclaration> localsUsedInTryOrSync,
|
| Set<ir.VariableDeclaration> freeVariables,
|
| bool hasThisLocal)
|
| @@ -292,14 +319,25 @@ class KernelCapturedScope extends KernelScopeInfo {
|
| }
|
|
|
| class JsCapturedScope extends JsScopeInfo implements CapturedScope {
|
| - final Local context;
|
| + final BoxLocal context;
|
|
|
| - JsCapturedScope.from(
|
| + JsCapturedScope.from(Map<Local, JRecord> boxedVariables,
|
| KernelCapturedScope capturedScope, KernelToLocalsMap localsMap)
|
| - : this.context = localsMap.getLocalVariable(capturedScope.context),
|
| - super.from(capturedScope, localsMap);
|
| + : this.context =
|
| + boxedVariables.isNotEmpty ? boxedVariables.values.first.box : null,
|
| + super.from(boxedVariables, capturedScope, localsMap);
|
|
|
| bool get requiresContextBox => boxedVariables.isNotEmpty;
|
| +
|
| + String toString() {
|
| + StringBuffer sb = new StringBuffer();
|
| + sb.write('this=$thisLocal,');
|
| + sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}');
|
| + sb.write('freeVariables={${freeVariables.join(', ')}}');
|
| + sb.write('boxedVariables={$boxedVariables}');
|
| + sb.write('context={$context}');
|
| + return sb.toString();
|
| + }
|
| }
|
|
|
| class KernelCapturedLoopScope extends KernelCapturedScope {
|
| @@ -309,12 +347,11 @@ class KernelCapturedLoopScope extends KernelCapturedScope {
|
| Set<ir.VariableDeclaration> boxedVariables,
|
| NodeBox capturedVariablesAccessor,
|
| this.boxedLoopVariables,
|
| - ir.TreeNode context,
|
| Set<ir.VariableDeclaration> localsUsedInTryOrSync,
|
| Set<ir.VariableDeclaration> freeVariables,
|
| bool hasThisLocal)
|
| - : super(boxedVariables, capturedVariablesAccessor, context,
|
| - localsUsedInTryOrSync, freeVariables, hasThisLocal);
|
| + : super(boxedVariables, capturedVariablesAccessor, localsUsedInTryOrSync,
|
| + freeVariables, hasThisLocal);
|
|
|
| bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty;
|
| }
|
| @@ -322,36 +359,36 @@ class KernelCapturedLoopScope extends KernelCapturedScope {
|
| class JsCapturedLoopScope extends JsCapturedScope implements CapturedLoopScope {
|
| final List<Local> boxedLoopVariables;
|
|
|
| - JsCapturedLoopScope.from(
|
| + JsCapturedLoopScope.from(Map<Local, JRecord> boxedVariables,
|
| KernelCapturedLoopScope capturedScope, KernelToLocalsMap localsMap)
|
| : this.boxedLoopVariables = capturedScope.boxedLoopVariables
|
| .map(localsMap.getLocalVariable)
|
| .toList(),
|
| - super.from(capturedScope, localsMap);
|
| + super.from(boxedVariables, capturedScope, localsMap);
|
|
|
| bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty;
|
| }
|
|
|
| // TODO(johnniwinther): Add unittest for the computed [ClosureClass].
|
| class KernelClosureClass extends JsScopeInfo
|
| - implements ClosureRepresentationInfo, JClass {
|
| - final String name;
|
| - final JLibrary library;
|
| + implements ClosureRepresentationInfo {
|
| JFunction callMethod;
|
| final Local closureEntity;
|
| final Local thisLocal;
|
| -
|
| - /// Index into the classData, classList and classEnvironment lists where this
|
| - /// entity is stored in [JsToFrontendMapImpl].
|
| - final int classIndex;
|
| + final ClassEntity closureClassEntity;
|
|
|
| final Map<Local, JField> localToFieldMap = new Map<Local, JField>();
|
|
|
| + /// The set of scopes that this scope captures. In practice we only populate
|
| + /// it if this scope uses a particular variable that is defined in another
|
| + /// scope, and this information is only really useful for closures.
|
| + final Set<JsCapturedScope> capturedScopes;
|
| +
|
| KernelClosureClass.fromScopeInfo(
|
| + this.closureClassEntity,
|
| ir.FunctionNode closureSourceNode,
|
| - this.name,
|
| - this.classIndex,
|
| - this.library,
|
| + Map<Local, JRecord> boxedVariables,
|
| + this.capturedScopes,
|
| KernelScopeInfo info,
|
| KernelToLocalsMap localsMap)
|
| : closureEntity = closureSourceNode.parent is ir.Member
|
| @@ -359,9 +396,7 @@ class KernelClosureClass extends JsScopeInfo
|
| : localsMap.getLocalFunction(closureSourceNode.parent),
|
| thisLocal =
|
| info.hasThisLocal ? new ThisLocal(localsMap.currentMember) : null,
|
| - super.from(info, localsMap);
|
| -
|
| - ClassEntity get closureClassEntity => this;
|
| + super.from(boxedVariables, info, localsMap);
|
|
|
| List<Local> get createdFieldEntities => localToFieldMap.keys.toList();
|
|
|
| @@ -374,25 +409,36 @@ class KernelClosureClass extends JsScopeInfo
|
| @override
|
| void forEachBoxedVariable(f(Local local, JField field)) {
|
| for (Local l in localToFieldMap.keys) {
|
| - if (localToFieldMap[l] is JBoxedField) f(l, localToFieldMap[l]);
|
| + if (localToFieldMap[l] is JRecord) f(l, localToFieldMap[l]);
|
| + }
|
| + for (JsCapturedScope scope in capturedScopes) {
|
| + scope.forEachBoxedVariable(f);
|
| }
|
| }
|
|
|
| void forEachFreeVariable(f(Local variable, JField field)) {
|
| for (Local l in localToFieldMap.keys) {
|
| var jField = localToFieldMap[l];
|
| - if (jField is! JBoxedField && jField is! BoxLocal) f(l, jField);
|
| + if (jField is! BoxLocal) f(l, jField);
|
| }
|
| }
|
|
|
| - bool isVariableBoxed(Local variable) =>
|
| - localToFieldMap.keys.contains(variable);
|
| -
|
| - bool get isClosure => true;
|
| + bool isVariableBoxed(Local variable) {
|
| + print(
|
| + 'RRRRRRRRRRR ${localToFieldMap.keys.contains(variable)} ${capturedScopes}');
|
| + return localToFieldMap.keys.contains(variable) ||
|
| + capturedScopes.any((JsCapturedScope scope) => scope.isBoxed(variable));
|
| + }
|
|
|
| - bool get isAbstract => false;
|
| + @override
|
| + bool isBoxed(Local variable) {
|
| + print(
|
| + 'RRRRRRRRRRR ${localToFieldMap.keys.contains(variable)} ${capturedScopes}');
|
| + return localToFieldMap.keys.contains(variable) ||
|
| + capturedScopes.any((JsCapturedScope scope) => scope.isBoxed(variable));
|
| + }
|
|
|
| - String toString() => '${jsElementPrefix}class($name)';
|
| + bool get isClosure => true;
|
| }
|
|
|
| /// A local variable to disambiguate between a variable that has been captured
|
| @@ -404,23 +450,62 @@ class NodeBox {
|
| NodeBox(this.name, this.executableContext);
|
| }
|
|
|
| +class JClosureClass extends JClass {
|
| + // TODO(efortuna): omg this is so horrible.
|
| + final KernelToLocalsMap localsMap;
|
| +
|
| + JClosureClass(this.localsMap, JLibrary library, int classIndex, String name)
|
| + : super(library, classIndex, name, isAbstract: false);
|
| +
|
| + @override
|
| + bool get isClosure => true;
|
| +
|
| + String toString() => '${jsElementPrefix}closure_class($name)';
|
| +}
|
| +
|
| class JClosureField extends JField {
|
| JClosureField(String name, int memberIndex,
|
| KernelClosureClass containingClass, bool isConst, bool isAssignable)
|
| - : super(memberIndex, containingClass.library, containingClass,
|
| - new Name(name, containingClass.library),
|
| - isAssignable: isAssignable, isConst: isConst, isStatic: false);
|
| + : super(
|
| + memberIndex,
|
| + containingClass.closureClassEntity.library,
|
| + containingClass.closureClassEntity,
|
| + new Name(name, containingClass.closureClassEntity.library),
|
| + isAssignable: isAssignable,
|
| + isConst: isConst,
|
| + isStatic: false);
|
| +}
|
| +
|
| +/// 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 JRecordContainer implements JClass {
|
| + final JLibrary library;
|
| + final String name;
|
| +
|
| + /// Index into the classData, classList and classEnvironment lists where this
|
| + /// entity is stored in [JsToFrontendMapImpl].
|
| + final int classIndex;
|
| +
|
| + JRecordContainer(this.library, this.classIndex, this.name);
|
| +
|
| + bool get isAbstract => false;
|
| +
|
| + bool get isClosure => false;
|
| +
|
| + String toString() => '${jsElementPrefix}record_container($name)';
|
| }
|
|
|
| -/// A ClosureField that has been "boxed" to prevent name shadowing with the
|
| +/// 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 JBoxedField extends JField {
|
| +class JRecord extends JField {
|
| final BoxLocal box;
|
| - JBoxedField(String name, int memberIndex, this.box,
|
| - KernelClosureClass containingClass, bool isConst, bool isAssignable)
|
| + JRecord(String name, int memberIndex, this.box, JClass containingClass,
|
| + bool isConst, bool isAssignable)
|
| : super(memberIndex, containingClass.library, containingClass,
|
| new Name(name, containingClass.library),
|
| isAssignable: isAssignable, isConst: isConst);
|
| @@ -434,11 +519,9 @@ class ClosureClassDefinition implements ClassDefinition {
|
|
|
| ClassKind get kind => ClassKind.closure;
|
|
|
| - ir.Node get node =>
|
| - throw new UnsupportedError('ClosureClassDefinition.node for $cls');
|
| + ir.Node get node => throw new UnsupportedError('JRecord.node for $cls');
|
|
|
| - String toString() =>
|
| - 'ClosureClassDefinition(kind:$kind,cls:$cls,location:$location)';
|
| + String toString() => 'JRecord(kind:$kind,cls:$cls,location:$location)';
|
| }
|
|
|
| class ClosureMemberData implements MemberData {
|
| @@ -517,6 +600,21 @@ class ClosureMemberDefinition implements MemberDefinition {
|
| '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.container;
|
| +
|
| + 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.
|
|
|