Version 2.13.0-9.0.dev

Merge commit '531a3a4ffdd9fde2dcc01f155c5d143d77636410' into 'dev'
diff --git a/pkg/compiler/lib/src/backend_strategy.dart b/pkg/compiler/lib/src/backend_strategy.dart
index b5307e1..92966bc 100644
--- a/pkg/compiler/lib/src/backend_strategy.dart
+++ b/pkg/compiler/lib/src/backend_strategy.dart
@@ -16,6 +16,7 @@
 import 'js_backend/enqueuer.dart';
 import 'js_backend/inferred_data.dart';
 import 'js_emitter/code_emitter_task.dart';
+import 'js_model/locals.dart';
 import 'serialization/serialization.dart';
 import 'ssa/ssa.dart';
 import 'universe/codegen_world_builder.dart';
@@ -65,8 +66,8 @@
   SourceSpan spanFromSpannable(Spannable spannable, Entity currentElement);
 
   /// Creates the [TypesInferrer] used by this strategy.
-  TypesInferrer createTypesInferrer(
-      JClosedWorld closedWorld, InferredDataBuilder inferredDataBuilder);
+  TypesInferrer createTypesInferrer(JClosedWorld closedWorld,
+      GlobalLocalsMap globalLocalsMap, InferredDataBuilder inferredDataBuilder);
 
   /// Calls [f] for every member that needs to be serialized for modular code
   /// generation and returns an [EntityWriter] for encoding these members in
diff --git a/pkg/compiler/lib/src/closure.dart b/pkg/compiler/lib/src/closure.dart
index 89edda7..6f26060 100644
--- a/pkg/compiler/lib/src/closure.dart
+++ b/pkg/compiler/lib/src/closure.dart
@@ -35,6 +35,14 @@
   /// Accessor to the information about scopes that closures capture. Used by
   /// the SSA builder.
   CapturedScope getCapturedScope(MemberEntity entity);
+
+  /// If [entity] is a closure call method or closure signature method, the
+  /// original enclosing member is returned. Otherwise [entity] is returned.
+  ///
+  /// A member and its nested closure share the underlying AST, we need to
+  /// ensure that locals are shared between them. We therefore store the
+  /// locals in the global locals map using the enclosing member as key.
+  MemberEntity getEnclosingMember(MemberEntity entity);
 }
 
 /// Enum used for identifying [ScopeInfo] subclasses in serialization.
@@ -68,7 +76,7 @@
       case ScopeInfoKind.capturedLoopScope:
         return new JsCapturedLoopScope.readFromDataSource(source);
       case ScopeInfoKind.closureRepresentationInfo:
-        return new KernelClosureClassInfo.readFromDataSource(source);
+        return new JsClosureClassInfo.readFromDataSource(source);
     }
     throw new UnsupportedError('Unexpected ScopeInfoKind $kind');
   }
@@ -92,7 +100,8 @@
   /// Also parameters to a `sync*` generator must be boxed, because of the way
   /// we rewrite sync* functions. See also comments in
   /// [ClosureClassMap.useLocal].
-  bool localIsUsedInTryOrSync(Local variable) => false;
+  bool localIsUsedInTryOrSync(KernelToLocalsMap localsMap, Local variable) =>
+      false;
 
   /// Loop through each variable that has been defined in this scope, modified
   /// anywhere (this scope or another scope) and used in another scope. Because
@@ -104,10 +113,11 @@
   /// In the case of loops, this is the set of iteration variables (or any
   /// variables declared in the for loop expression (`for (...here...)`) that
   /// need to be boxed to snapshot their value.
-  void forEachBoxedVariable(f(Local local, FieldEntity field)) {}
+  void forEachBoxedVariable(
+      KernelToLocalsMap localsMap, f(Local local, FieldEntity field)) {}
 
   /// True if [variable] has been mutated and is also used in another scope.
-  bool isBoxedVariable(Local variable) => false;
+  bool isBoxedVariable(KernelToLocalsMap localsMap, Local variable) => false;
 }
 
 /// Class representing the usage of a scope that has been captured in the
@@ -141,7 +151,7 @@
   /// executed. This will encapsulate the value of any variables that have been
   /// scoped into this context from outside. This is an accessor to the
   /// contextBox that [requiresContextBox] is testing is required.
-  Local get context => null;
+  Local get contextBox => null;
 }
 
 /// Class that describes the actual mechanics of how values of variables
@@ -195,7 +205,8 @@
   ///
   /// `i` would be a part of the boxedLoopVariables AND boxedVariables, but b
   /// would only be a part of boxedVariables.
-  List<Local> get boxedLoopVariables => const <Local>[];
+  List<Local> getBoxedLoopVariables(KernelToLocalsMap localsMap) =>
+      const <Local>[];
 }
 
 /// Class that describes the actual mechanics of how the converted, rewritten
@@ -239,7 +250,7 @@
         throw new UnsupportedError(
             'Unexpected ClosureRepresentationInfo kind $kind');
       case ScopeInfoKind.closureRepresentationInfo:
-        return new KernelClosureClassInfo.readFromDataSource(source);
+        return new JsClosureClassInfo.readFromDataSource(source);
     }
     throw new UnsupportedError('Unexpected ScopeInfoKind $kind');
   }
@@ -247,7 +258,7 @@
   /// The original local function before any translation.
   ///
   /// Will be null for methods.
-  Local get closureEntity => null;
+  Local getClosureEntity(KernelToLocalsMap localsMap) => null;
 
   /// The entity for the class used to represent the rewritten closure in the
   /// emitted JavaScript.
@@ -266,13 +277,14 @@
   /// List of locals that this closure class has created corresponding field
   /// entities for.
   @deprecated
-  List<Local> get createdFieldEntities => const <Local>[];
+  List<Local> getCreatedFieldEntities(KernelToLocalsMap localsMap) =>
+      const <Local>[];
 
   /// As shown in the example in the comments at the top of this class, we
   /// create fields in the closure class for each captured variable. This is an
   /// accessor the [local] for which [field] was created.
   /// Returns the [local] for which [field] was created.
-  Local getLocalForField(FieldEntity field) {
+  Local getLocalForField(KernelToLocalsMap localsMap, FieldEntity field) {
     failedAt(field, "No local for $field.");
     return null;
   }
@@ -289,12 +301,14 @@
   /// strictly variables defined in this closure, unlike the behavior in
   /// the superclass ScopeInfo.
   @override
-  void forEachBoxedVariable(f(Local local, FieldEntity field)) {}
+  void forEachBoxedVariable(
+      KernelToLocalsMap localsMap, f(Local local, FieldEntity field)) {}
 
   /// Loop through each free variable in this closure. Free variables are the
   /// variables that have been captured *just* in this closure, not in nested
   /// scopes.
-  void forEachFreeVariable(f(Local variable, FieldEntity field)) {}
+  void forEachFreeVariable(
+      KernelToLocalsMap localsMap, f(Local variable, FieldEntity field)) {}
 
   // TODO(efortuna): Remove this method. The old system was using
   // ClosureClassMaps for situations other than closure class maps, and that's
diff --git a/pkg/compiler/lib/src/compiler.dart b/pkg/compiler/lib/src/compiler.dart
index 6faa855..e921146 100644
--- a/pkg/compiler/lib/src/compiler.dart
+++ b/pkg/compiler/lib/src/compiler.dart
@@ -39,6 +39,7 @@
 import 'js_backend/inferred_data.dart';
 import 'js_model/js_strategy.dart';
 import 'js_model/js_world.dart';
+import 'js_model/locals.dart';
 import 'kernel/kernel_strategy.dart';
 import 'kernel/loader.dart' show KernelLoaderTask, KernelResult;
 import 'null_compiler_output.dart' show NullCompilerOutput;
@@ -401,10 +402,12 @@
       JClosedWorld closedWorld) {
     FunctionEntity mainFunction = closedWorld.elementEnvironment.mainFunction;
     reporter.log('Performing global type inference');
+    GlobalLocalsMap globalLocalsMap =
+        new GlobalLocalsMap(closedWorld.closureDataLookup.getEnclosingMember);
     InferredDataBuilder inferredDataBuilder =
         new InferredDataBuilderImpl(closedWorld.annotationsData);
     return globalInference.runGlobalTypeInference(
-        mainFunction, closedWorld, inferredDataBuilder);
+        mainFunction, closedWorld, globalLocalsMap, inferredDataBuilder);
   }
 
   void runCodegenEnqueuer(CodegenResults codegenResults) {
diff --git a/pkg/compiler/lib/src/dump_info.dart b/pkg/compiler/lib/src/dump_info.dart
index 691151c..882895c 100644
--- a/pkg/compiler/lib/src/dump_info.dart
+++ b/pkg/compiler/lib/src/dump_info.dart
@@ -271,7 +271,7 @@
     List<String> inferredParameterTypes = <String>[];
 
     closedWorld.elementEnvironment.forEachParameterAsLocal(
-        closedWorld.globalLocalsMap, function, (parameter) {
+        _globalInferenceResults.globalLocalsMap, function, (parameter) {
       inferredParameterTypes.add('${_resultOfParameter(parameter)}');
     });
     int parameterIndex = 0;
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index 9793cc5..337c547 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -218,7 +218,7 @@
     // each update, and reading them yields the type that was found in a
     // previous analysis of [outermostElement].
     ScopeInfo scopeInfo = _closureDataLookup.getScopeInfo(_analyzedMember);
-    scopeInfo.forEachBoxedVariable((variable, field) {
+    scopeInfo.forEachBoxedVariable(_localsMap, (variable, field) {
       _capturedAndBoxed[variable] = field;
     });
 
@@ -1722,8 +1722,8 @@
     // Record the types of captured non-boxed variables. Types of
     // these variables may already be there, because of an analysis of
     // a previous closure.
-    info.forEachFreeVariable((Local variable, FieldEntity field) {
-      if (!info.isBoxedVariable(variable)) {
+    info.forEachFreeVariable(_localsMap, (Local variable, FieldEntity field) {
+      if (!info.isBoxedVariable(_localsMap, variable)) {
         if (variable == info.thisLocal) {
           _inferrer.recordTypeOfField(field, thisType);
         }
diff --git a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
index 129b30b..39c1a06 100644
--- a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
+++ b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
@@ -64,6 +64,7 @@
 
   final TypeSystem types;
   final Map<ir.TreeNode, TypeInformation> concreteTypes = {};
+  final GlobalLocalsMap globalLocalsMap;
   final InferredDataBuilder inferredDataBuilder;
 
   final FunctionEntity mainElement;
@@ -114,9 +115,10 @@
       this._compilerOutput,
       this.closedWorld,
       this.mainElement,
+      this.globalLocalsMap,
       this.inferredDataBuilder)
-      : this.types = new TypeSystem(
-            closedWorld, new KernelTypeSystemStrategy(closedWorld));
+      : this.types = new TypeSystem(closedWorld,
+            new KernelTypeSystemStrategy(closedWorld, globalLocalsMap));
 
   /// Applies [f] to all elements in the universe that match [selector] and
   /// [mask]. If [f] returns false, aborts the iteration.
@@ -634,7 +636,7 @@
         this,
         member,
         body,
-        closedWorld.globalLocalsMap.getLocalsMap(member),
+        globalLocalsMap.getLocalsMap(member),
         closedWorld.elementMap.getStaticTypeProvider(member));
     return visitor.run();
   }
@@ -1224,8 +1226,9 @@
 
 class KernelTypeSystemStrategy implements TypeSystemStrategy {
   final JsClosedWorld _closedWorld;
+  final GlobalLocalsMap _globalLocalsMap;
 
-  KernelTypeSystemStrategy(this._closedWorld);
+  KernelTypeSystemStrategy(this._closedWorld, this._globalLocalsMap);
 
   JElementEnvironment get _elementEnvironment =>
       _closedWorld.elementEnvironment;
@@ -1252,8 +1255,8 @@
   @override
   void forEachParameter(FunctionEntity function, void f(Local parameter)) {
     forEachOrderedParameterAsLocal(
-        _closedWorld.globalLocalsMap, _closedWorld.elementMap, function,
-        (Local parameter, {bool isElided}) {
+        _globalLocalsMap, _closedWorld.elementMap, function, (Local parameter,
+            {bool isElided}) {
       f(parameter);
     });
   }
@@ -1264,8 +1267,7 @@
       covariant JLocal parameter,
       TypeSystem types) {
     MemberEntity context = parameter.memberContext;
-    KernelToLocalsMap localsMap =
-        _closedWorld.globalLocalsMap.getLocalsMap(context);
+    KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(context);
     ir.FunctionNode functionNode =
         localsMap.getFunctionNodeForParameter(parameter);
     DartType type = localsMap.getLocalType(_closedWorld.elementMap, parameter);
diff --git a/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart b/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
index d1e4239..f770d9b 100644
--- a/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
+++ b/pkg/compiler/lib/src/inferrer/type_graph_inferrer.dart
@@ -13,6 +13,7 @@
 import '../elements/entities.dart';
 import '../js_backend/inferred_data.dart';
 import '../js_model/elements.dart' show JClosureCallMethod;
+import '../js_model/locals.dart';
 import '../world.dart';
 import 'abstract_value_domain.dart';
 import 'inferrer_engine.dart';
@@ -53,11 +54,12 @@
   final JClosedWorld closedWorld;
 
   final Compiler _compiler;
+  final GlobalLocalsMap _globalLocalsMap;
   final InferredDataBuilder _inferredDataBuilder;
   Metrics /*?*/ _metrics;
 
-  TypeGraphInferrer(
-      this._compiler, this.closedWorld, this._inferredDataBuilder);
+  TypeGraphInferrer(this._compiler, this.closedWorld, this._globalLocalsMap,
+      this._inferredDataBuilder);
 
   String get name => 'Graph inferrer';
 
@@ -82,6 +84,7 @@
         _compiler.outputProvider,
         closedWorld,
         main,
+        _globalLocalsMap,
         _inferredDataBuilder);
   }
 
@@ -134,7 +137,8 @@
       if (member is JClosureCallMethod) {
         ClosureRepresentationInfo info =
             closedWorld.closureDataLookup.getScopeInfo(member);
-        info.forEachFreeVariable((Local from, FieldEntity to) {
+        info.forEachFreeVariable(_globalLocalsMap.getLocalsMap(member),
+            (Local from, FieldEntity to) {
           freeVariables.add(to);
         });
       }
@@ -167,6 +171,7 @@
 
     GlobalTypeInferenceResults results = new GlobalTypeInferenceResultsImpl(
         closedWorld,
+        _globalLocalsMap,
         _inferredDataBuilder.close(closedWorld),
         memberResults,
         parameterResults,
diff --git a/pkg/compiler/lib/src/inferrer/types.dart b/pkg/compiler/lib/src/inferrer/types.dart
index 66d551c..2345d53 100644
--- a/pkg/compiler/lib/src/inferrer/types.dart
+++ b/pkg/compiler/lib/src/inferrer/types.dart
@@ -13,6 +13,8 @@
 import '../elements/entities.dart';
 import '../js_backend/inferred_data.dart';
 import '../js_model/element_map.dart';
+import '../js_model/js_world.dart';
+import '../js_model/locals.dart';
 import '../inferrer/type_graph_inferrer.dart' show TypeGraphInferrer;
 import '../serialization/serialization.dart';
 import '../universe/selector.dart' show Selector;
@@ -111,13 +113,15 @@
       DataSource source,
       JsToElementMap elementMap,
       JClosedWorld closedWorld,
+      GlobalLocalsMap globalLocalsMap,
       InferredData inferredData) {
     bool isTrivial = source.readBool();
     if (isTrivial) {
-      return new TrivialGlobalTypeInferenceResults(closedWorld);
+      return new TrivialGlobalTypeInferenceResults(
+          closedWorld, globalLocalsMap);
     }
     return new GlobalTypeInferenceResultsImpl.readFromDataSource(
-        source, elementMap, closedWorld, inferredData);
+        source, elementMap, closedWorld, globalLocalsMap, inferredData);
   }
 
   /// Serializes this [GlobalTypeInferenceResults] to [sink].
@@ -125,6 +129,8 @@
 
   JClosedWorld get closedWorld;
 
+  GlobalLocalsMap get globalLocalsMap;
+
   InferredData get inferredData;
 
   GlobalTypeInferenceMemberResult resultOfMember(MemberEntity member);
@@ -171,16 +177,20 @@
   Metrics get metrics => _metrics;
 
   /// Runs the global type-inference algorithm once.
-  GlobalTypeInferenceResults runGlobalTypeInference(FunctionEntity mainElement,
-      JClosedWorld closedWorld, InferredDataBuilder inferredDataBuilder) {
+  GlobalTypeInferenceResults runGlobalTypeInference(
+      FunctionEntity mainElement,
+      JClosedWorld closedWorld,
+      GlobalLocalsMap globalLocalsMap,
+      InferredDataBuilder inferredDataBuilder) {
     return measure(() {
       GlobalTypeInferenceResults results;
       if (compiler.disableTypeInference) {
-        results = new TrivialGlobalTypeInferenceResults(closedWorld);
+        results =
+            new TrivialGlobalTypeInferenceResults(closedWorld, globalLocalsMap);
         _metrics = Metrics.none();
       } else {
-        typesInferrerInternal ??= compiler.backendStrategy
-            .createTypesInferrer(closedWorld, inferredDataBuilder);
+        typesInferrerInternal ??= compiler.backendStrategy.createTypesInferrer(
+            closedWorld, globalLocalsMap, inferredDataBuilder);
         results = typesInferrerInternal.analyzeMain(mainElement);
         _metrics = typesInferrerInternal.metrics;
       }
@@ -201,6 +211,8 @@
   @override
   final JClosedWorld closedWorld;
   @override
+  final GlobalLocalsMap globalLocalsMap;
+  @override
   final InferredData inferredData;
   final GlobalTypeInferenceMemberResult _deadFieldResult;
   final GlobalTypeInferenceMemberResult _deadMethodResult;
@@ -214,6 +226,7 @@
 
   GlobalTypeInferenceResultsImpl(
       this.closedWorld,
+      this.globalLocalsMap,
       this.inferredData,
       this.memberResults,
       this.parameterResults,
@@ -230,7 +243,10 @@
       DataSource source,
       JsToElementMap elementMap,
       JClosedWorld closedWorld,
+      GlobalLocalsMap globalLocalsMap,
       InferredData inferredData) {
+    source.registerLocalLookup(new LocalLookupImpl(globalLocalsMap));
+
     source.begin(tag);
     Map<MemberEntity, GlobalTypeInferenceMemberResult> memberResults =
         source.readMemberMap((MemberEntity member) =>
@@ -250,6 +266,7 @@
     source.end(tag);
     return new GlobalTypeInferenceResultsImpl(
         closedWorld,
+        globalLocalsMap,
         inferredData,
         memberResults,
         parameterResults,
@@ -472,8 +489,10 @@
   final AbstractValue _trivialParameterResult;
   @override
   final InferredData inferredData = new TrivialInferredData();
+  @override
+  final GlobalLocalsMap globalLocalsMap;
 
-  TrivialGlobalTypeInferenceResults(this.closedWorld)
+  TrivialGlobalTypeInferenceResults(this.closedWorld, this.globalLocalsMap)
       : _trivialMemberResult = new TrivialGlobalTypeInferenceMemberResult(
             closedWorld.abstractValueDomain.dynamicType),
         _trivialParameterResult = closedWorld.abstractValueDomain.dynamicType;
diff --git a/pkg/compiler/lib/src/ir/closure.dart b/pkg/compiler/lib/src/ir/closure.dart
index 8d0c0c1..a9d5604 100644
--- a/pkg/compiler/lib/src/ir/closure.dart
+++ b/pkg/compiler/lib/src/ir/closure.dart
@@ -343,7 +343,7 @@
 /// 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.TreeNode context;
   final ir.TypeParameterType type;
   final TypeVariableKind kind;
   final ir.TreeNode typeDeclaration;
diff --git a/pkg/compiler/lib/src/js_model/closure.dart b/pkg/compiler/lib/src/js_model/closure.dart
index 570da8a..cfdee0b 100644
--- a/pkg/compiler/lib/src/js_model/closure.dart
+++ b/pkg/compiler/lib/src/js_model/closure.dart
@@ -21,7 +21,6 @@
 import '../universe/selector.dart';
 import 'elements.dart';
 import 'js_world_builder.dart' show JsClosedWorldBuilder;
-import 'locals.dart';
 
 class ClosureDataImpl implements ClosureData {
   /// Tag used for identifying serialized [ClosureData] objects in a
@@ -40,8 +39,15 @@
   final Map<ir.LocalFunction, ClosureRepresentationInfo>
       _localClosureRepresentationMap;
 
-  ClosureDataImpl(this._elementMap, this._scopeMap, this._capturedScopesMap,
-      this._capturedScopeForSignatureMap, this._localClosureRepresentationMap);
+  final Map<MemberEntity, MemberEntity> _enclosingMembers;
+
+  ClosureDataImpl(
+      this._elementMap,
+      this._scopeMap,
+      this._capturedScopesMap,
+      this._capturedScopeForSignatureMap,
+      this._localClosureRepresentationMap,
+      this._enclosingMembers);
 
   /// Deserializes a [ClosureData] object from [source].
   factory ClosureDataImpl.readFromDataSource(
@@ -58,9 +64,16 @@
     Map<ir.LocalFunction, ClosureRepresentationInfo>
         localClosureRepresentationMap = source.readTreeNodeMap(
             () => new ClosureRepresentationInfo.readFromDataSource(source));
+    Map<MemberEntity, MemberEntity> enclosingMembers =
+        source.readMemberMap((member) => source.readMember());
     source.end(tag);
-    return new ClosureDataImpl(elementMap, scopeMap, capturedScopesMap,
-        capturedScopeForSignatureMap, localClosureRepresentationMap);
+    return new ClosureDataImpl(
+        elementMap,
+        scopeMap,
+        capturedScopesMap,
+        capturedScopeForSignatureMap,
+        localClosureRepresentationMap,
+        enclosingMembers);
   }
 
   /// Serializes this [ClosureData] to [sink].
@@ -80,6 +93,10 @@
         (ClosureRepresentationInfo info) {
       info.writeToDataSink(sink);
     });
+    sink.writeMemberMap(_enclosingMembers,
+        (MemberEntity member, MemberEntity value) {
+      sink.writeMember(value);
+    });
     sink.end(tag);
   }
 
@@ -132,6 +149,11 @@
         "Closures found for ${_localClosureRepresentationMap.keys}");
     return closure;
   }
+
+  @override
+  MemberEntity getEnclosingMember(MemberEntity member) {
+    return _enclosingMembers[member] ?? member;
+  }
 }
 
 /// Closure conversion code using our new Entity model. Closure conversion is
@@ -149,7 +171,6 @@
 
 class ClosureDataBuilder {
   final JsToElementMap _elementMap;
-  final GlobalLocalsMap _globalLocalsMap;
   final AnnotationsData _annotationsData;
 
   /// Map of the scoping information that corresponds to a particular entity.
@@ -162,8 +183,9 @@
   Map<ir.LocalFunction, ClosureRepresentationInfo>
       _localClosureRepresentationMap = {};
 
-  ClosureDataBuilder(
-      this._elementMap, this._globalLocalsMap, this._annotationsData);
+  Map<MemberEntity, MemberEntity> _enclosingMembers = {};
+
+  ClosureDataBuilder(this._elementMap, this._annotationsData);
 
   void _updateScopeBasedOnRtiNeed(KernelScopeInfo scope, ClosureRtiNeed rtiNeed,
       MemberEntity outermostEntity) {
@@ -302,24 +324,23 @@
       ClosureRtiNeed rtiNeed,
       List<FunctionEntity> callMethods) {
     closureModels.forEach((MemberEntity member, ClosureScopeModel model) {
-      KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(member);
-      Map<Local, JRecordField> allBoxedVariables =
-          _elementMap.makeRecordContainer(model.scopeInfo, member, localsMap);
+      Map<ir.VariableDeclaration, JRecordField> allBoxedVariables =
+          _elementMap.makeRecordContainer(model.scopeInfo, member);
       _scopeMap[member] = new JsScopeInfo.from(
-          allBoxedVariables, model.scopeInfo, localsMap, _elementMap);
+          allBoxedVariables, model.scopeInfo, member.enclosingClass);
 
       model.capturedScopesMap
           .forEach((ir.Node node, KernelCapturedScope scope) {
-        Map<Local, JRecordField> boxedVariables =
-            _elementMap.makeRecordContainer(scope, member, localsMap);
+        Map<ir.VariableDeclaration, JRecordField> boxedVariables =
+            _elementMap.makeRecordContainer(scope, member);
         _updateScopeBasedOnRtiNeed(scope, rtiNeed, member);
 
         if (scope is KernelCapturedLoopScope) {
           _capturedScopesMap[node] = new JsCapturedLoopScope.from(
-              boxedVariables, scope, localsMap, _elementMap);
+              boxedVariables, scope, member.enclosingClass);
         } else {
           _capturedScopesMap[node] = new JsCapturedScope.from(
-              boxedVariables, scope, localsMap, _elementMap);
+              boxedVariables, scope, member.enclosingClass);
         }
         allBoxedVariables.addAll(boxedVariables);
       });
@@ -328,7 +349,7 @@
           model.closuresToGenerate;
       for (ir.LocalFunction node in closuresToGenerate.keys) {
         ir.FunctionNode functionNode = node.function;
-        KernelClosureClassInfo closureClassInfo = _produceSyntheticElements(
+        JsClosureClassInfo closureClassInfo = _produceSyntheticElements(
             closedWorldBuilder,
             member,
             functionNode,
@@ -355,14 +376,19 @@
             _updateScopeBasedOnRtiNeed(signatureCapturedScope, rtiNeed, member);
             _capturedScopeForSignatureMap[closureClassInfo.signatureMethod] =
                 new JsCapturedScope.from(
-                    {}, signatureCapturedScope, localsMap, _elementMap);
+                    {}, signatureCapturedScope, member.enclosingClass);
           }
         }
         callMethods.add(closureClassInfo.callMethod);
       }
     });
-    return new ClosureDataImpl(_elementMap, _scopeMap, _capturedScopesMap,
-        _capturedScopeForSignatureMap, _localClosureRepresentationMap);
+    return new ClosureDataImpl(
+        _elementMap,
+        _scopeMap,
+        _capturedScopesMap,
+        _capturedScopeForSignatureMap,
+        _localClosureRepresentationMap,
+        _enclosingMembers);
   }
 
   /// Given what variables are captured at each point, construct closure classes
@@ -371,27 +397,24 @@
   /// 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(
+  JsClosureClassInfo _produceSyntheticElements(
       JsClosedWorldBuilder closedWorldBuilder,
       MemberEntity member,
       ir.FunctionNode node,
       KernelScopeInfo info,
-      Map<Local, JRecordField> boxedVariables,
+      Map<ir.VariableDeclaration, 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);
+    JsClosureClassInfo closureClassInfo = closedWorldBuilder.buildClosureClass(
+        member, node, member.library, boxedVariables, info,
+        createSignatureMethod: createSignatureMethod);
 
     // We want the original declaration where that function is used to point
     // to the correct closure class.
-    _globalLocalsMap.setLocalsMap(closureClassInfo.callMethod, localsMap);
+    _enclosingMembers[closureClassInfo.callMethod] = member;
     if (closureClassInfo.signatureMethod != null) {
-      _globalLocalsMap.setLocalsMap(
-          closureClassInfo.signatureMethod, localsMap);
+      _enclosingMembers[closureClassInfo.signatureMethod] = member;
     }
     if (node.parent is ir.Member) {
       assert(_elementMap.getMember(node.parent) == member);
@@ -403,98 +426,102 @@
   }
 }
 
-/// Helper method to get or create a Local variable out of a variable
-/// declaration or type parameter.
-Local _getLocal(
-    ir.Node variable, KernelToLocalsMap localsMap, JsToElementMap 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 {
   /// Tag used for identifying serialized [JsScopeInfo] objects in a
   /// debugging data stream.
   static const String tag = 'scope-info';
 
-  final Iterable<Local> localsUsedInTryOrSync;
+  final Iterable<ir.VariableDeclaration> _localsUsedInTryOrSync;
+
+  Set<Local> _localsUsedInTryOrSyncCache;
+
   @override
   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;
+  final Map<ir.VariableDeclaration, JRecordField> _boxedVariables;
 
-  JsScopeInfo.internal(this.localsUsedInTryOrSync, this.thisLocal,
-      this.boxedVariables, this.freeVariables);
+  Map<Local, JRecordField> _boxedVariablesCache;
 
-  JsScopeInfo.from(this.boxedVariables, KernelScopeInfo info,
-      KernelToLocalsMap localsMap, JsToElementMap elementMap)
-      : this.thisLocal = info.hasThisLocal
-            ? new ThisLocal(localsMap.currentMember.enclosingClass)
-            : 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);
+  JsScopeInfo.internal(
+      this._localsUsedInTryOrSync, this.thisLocal, this._boxedVariables);
+
+  JsScopeInfo.from(
+      this._boxedVariables, KernelScopeInfo info, ClassEntity enclosingClass)
+      : this.thisLocal =
+            info.hasThisLocal ? new ThisLocal(enclosingClass) : null,
+        this._localsUsedInTryOrSync = info.localsUsedInTryOrSync;
+
+  void _ensureBoxedVariableCache(KernelToLocalsMap localsMap) {
+    if (_boxedVariablesCache == null) {
+      if (_boxedVariables.isEmpty) {
+        _boxedVariablesCache = const {};
+      } else {
+        _boxedVariablesCache = {};
+        _boxedVariables
+            .forEach((ir.VariableDeclaration node, JRecordField field) {
+          _boxedVariablesCache[localsMap.getLocalVariable(node)] = field;
+        });
+      }
     }
   }
 
   @override
-  void forEachBoxedVariable(f(Local local, FieldEntity field)) {
-    boxedVariables.forEach((Local l, JRecordField box) {
-      f(l, box);
-    });
+  void forEachBoxedVariable(
+      KernelToLocalsMap localsMap, f(Local local, FieldEntity field)) {
+    _ensureBoxedVariableCache(localsMap);
+    _boxedVariablesCache.forEach(f);
   }
 
   @override
-  bool localIsUsedInTryOrSync(Local variable) =>
-      localsUsedInTryOrSync.contains(variable);
+  bool localIsUsedInTryOrSync(KernelToLocalsMap localsMap, Local variable) {
+    if (_localsUsedInTryOrSyncCache == null) {
+      if (_localsUsedInTryOrSync.isEmpty) {
+        _localsUsedInTryOrSyncCache = const {};
+      } else {
+        _localsUsedInTryOrSyncCache = {};
+        for (ir.VariableDeclaration node in _localsUsedInTryOrSync) {
+          _localsUsedInTryOrSyncCache.add(localsMap.getLocalVariable(node));
+        }
+      }
+    }
+    return _localsUsedInTryOrSyncCache.contains(variable);
+  }
 
   @override
   String toString() {
     StringBuffer sb = new StringBuffer();
     sb.write('this=$thisLocal,');
-    sb.write('localsUsedInTryOrSync={${localsUsedInTryOrSync.join(', ')}}');
+    sb.write('localsUsedInTryOrSync={${_localsUsedInTryOrSync.join(', ')}}');
     return sb.toString();
   }
 
   @override
-  bool isBoxedVariable(Local variable) => boxedVariables.containsKey(variable);
+  bool isBoxedVariable(KernelToLocalsMap localsMap, Local variable) {
+    _ensureBoxedVariableCache(localsMap);
+    return _boxedVariablesCache.containsKey(variable);
+  }
 
   factory JsScopeInfo.readFromDataSource(DataSource source) {
     source.begin(tag);
-    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Iterable<ir.VariableDeclaration> localsUsedInTryOrSync =
+        source.readTreeNodes<ir.VariableDeclaration>();
     Local thisLocal = source.readLocalOrNull();
-    Map<Local, JRecordField> boxedVariables =
-        source.readLocalMap<Local, JRecordField>(() => source.readMember());
-    Set<Local> freeVariables = source.readLocals().toSet();
+    Map<ir.VariableDeclaration, JRecordField> boxedVariables =
+        source.readTreeNodeMap<ir.VariableDeclaration, JRecordField>(
+            () => source.readMember());
     source.end(tag);
     if (boxedVariables.isEmpty) boxedVariables = const {};
-    if (freeVariables.isEmpty) freeVariables = const {};
     return JsScopeInfo.internal(
-        localsUsedInTryOrSync, thisLocal, boxedVariables, freeVariables);
+        localsUsedInTryOrSync, thisLocal, boxedVariables);
   }
 
   @override
   void writeToDataSink(DataSink sink) {
     sink.writeEnum(ScopeInfoKind.scopeInfo);
     sink.begin(tag);
-    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeTreeNodes(_localsUsedInTryOrSync);
     sink.writeLocalOrNull(thisLocal);
-    sink.writeLocalMap(boxedVariables, sink.writeMember);
-    sink.writeLocals(freeVariables);
+    sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
     sink.end(tag);
   }
 }
@@ -505,51 +532,46 @@
   static const String tag = 'captured-scope';
 
   @override
-  final Local context;
+  final Local contextBox;
 
   JsCapturedScope.internal(
-      Iterable<Local> localsUsedInTryOrSync,
+      Iterable<ir.VariableDeclaration> localsUsedInTryOrSync,
       Local thisLocal,
-      Map<Local, JRecordField> boxedVariables,
-      Set<Local> freeVariables,
-      this.context)
-      : super.internal(
-            localsUsedInTryOrSync, thisLocal, boxedVariables, freeVariables);
+      Map<ir.VariableDeclaration, JRecordField> boxedVariables,
+      this.contextBox)
+      : super.internal(localsUsedInTryOrSync, thisLocal, boxedVariables);
 
-  JsCapturedScope.from(
-      Map<Local, JRecordField> boxedVariables,
-      KernelCapturedScope capturedScope,
-      KernelToLocalsMap localsMap,
-      JsToElementMap elementMap)
-      : this.context =
+  JsCapturedScope.from(Map<ir.VariableDeclaration, JRecordField> boxedVariables,
+      KernelCapturedScope capturedScope, ClassEntity enclosingClass)
+      : this.contextBox =
             boxedVariables.isNotEmpty ? boxedVariables.values.first.box : null,
-        super.from(boxedVariables, capturedScope, localsMap, elementMap);
+        super.from(boxedVariables, capturedScope, enclosingClass);
 
   @override
-  bool get requiresContextBox => boxedVariables.isNotEmpty;
+  bool get requiresContextBox => _boxedVariables.isNotEmpty;
 
   factory JsCapturedScope.readFromDataSource(DataSource source) {
     source.begin(tag);
-    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Iterable<ir.VariableDeclaration> localsUsedInTryOrSync =
+        source.readTreeNodes<ir.VariableDeclaration>();
     Local thisLocal = source.readLocalOrNull();
-    Map<Local, JRecordField> boxedVariables =
-        source.readLocalMap<Local, JRecordField>(() => source.readMember());
-    Set<Local> freeVariables = source.readLocals().toSet();
+    Map<ir.VariableDeclaration, JRecordField> boxedVariables =
+        source.readTreeNodeMap<ir.VariableDeclaration, JRecordField>(
+            () => source.readMember());
     Local context = source.readLocalOrNull();
     source.end(tag);
-    return new JsCapturedScope.internal(localsUsedInTryOrSync, thisLocal,
-        boxedVariables, freeVariables, context);
+    return new JsCapturedScope.internal(
+        localsUsedInTryOrSync, thisLocal, boxedVariables, context);
   }
 
   @override
   void writeToDataSink(DataSink sink) {
     sink.writeEnum(ScopeInfoKind.capturedScope);
     sink.begin(tag);
-    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeTreeNodes(_localsUsedInTryOrSync);
     sink.writeLocalOrNull(thisLocal);
-    sink.writeLocalMap(boxedVariables, sink.writeMember);
-    sink.writeLocals(freeVariables);
-    sink.writeLocalOrNull(context);
+    sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
+    sink.writeLocalOrNull(contextBox);
     sink.end(tag);
   }
 }
@@ -559,65 +581,69 @@
   /// debugging data stream.
   static const String tag = 'captured-loop-scope';
 
-  @override
-  final List<Local> boxedLoopVariables;
+  final List<ir.VariableDeclaration> _boxedLoopVariables;
 
   JsCapturedLoopScope.internal(
-      Iterable<Local> localsUsedInTryOrSync,
+      Iterable<ir.VariableDeclaration> localsUsedInTryOrSync,
       Local thisLocal,
-      Map<Local, JRecordField> boxedVariables,
-      Set<Local> freeVariables,
+      Map<ir.VariableDeclaration, JRecordField> boxedVariables,
       Local context,
-      this.boxedLoopVariables)
-      : super.internal(localsUsedInTryOrSync, thisLocal, boxedVariables,
-            freeVariables, context);
+      this._boxedLoopVariables)
+      : super.internal(
+            localsUsedInTryOrSync, thisLocal, boxedVariables, context);
 
   JsCapturedLoopScope.from(
-      Map<Local, JRecordField> boxedVariables,
+      Map<ir.VariableDeclaration, JRecordField> boxedVariables,
       KernelCapturedLoopScope capturedScope,
-      KernelToLocalsMap localsMap,
-      JsToElementMap elementMap)
-      : this.boxedLoopVariables = capturedScope.boxedLoopVariables
-            .map(localsMap.getLocalVariable)
-            .toList(),
-        super.from(boxedVariables, capturedScope, localsMap, elementMap);
+      ClassEntity enclosingClass)
+      : this._boxedLoopVariables = capturedScope.boxedLoopVariables,
+        super.from(boxedVariables, capturedScope, enclosingClass);
 
   @override
-  bool get hasBoxedLoopVariables => boxedLoopVariables.isNotEmpty;
+  bool get hasBoxedLoopVariables => _boxedLoopVariables.isNotEmpty;
 
   factory JsCapturedLoopScope.readFromDataSource(DataSource source) {
     source.begin(tag);
-    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Iterable<ir.VariableDeclaration> localsUsedInTryOrSync =
+        source.readTreeNodes<ir.VariableDeclaration>();
     Local thisLocal = source.readLocalOrNull();
-    Map<Local, JRecordField> boxedVariables =
-        source.readLocalMap<Local, JRecordField>(() => source.readMember());
-    Set<Local> freeVariables = source.readLocals().toSet();
+    Map<ir.VariableDeclaration, JRecordField> boxedVariables =
+        source.readTreeNodeMap<ir.VariableDeclaration, JRecordField>(
+            () => source.readMember());
     Local context = source.readLocalOrNull();
-    List<Local> boxedLoopVariables = source.readLocals();
+    List<ir.VariableDeclaration> boxedLoopVariables =
+        source.readTreeNodes<ir.VariableDeclaration>();
     source.end(tag);
     return new JsCapturedLoopScope.internal(localsUsedInTryOrSync, thisLocal,
-        boxedVariables, freeVariables, context, boxedLoopVariables);
+        boxedVariables, context, boxedLoopVariables);
   }
 
   @override
   void writeToDataSink(DataSink sink) {
     sink.writeEnum(ScopeInfoKind.capturedLoopScope);
     sink.begin(tag);
-    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeTreeNodes(_localsUsedInTryOrSync);
     sink.writeLocalOrNull(thisLocal);
-    sink.writeLocalMap(boxedVariables, sink.writeMember);
-    sink.writeLocals(freeVariables);
-    sink.writeLocalOrNull(context);
-    sink.writeLocals(boxedLoopVariables);
+    sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
+    sink.writeLocalOrNull(contextBox);
+    sink.writeTreeNodes(_boxedLoopVariables);
     sink.end(tag);
   }
+
+  @override
+  List<Local> getBoxedLoopVariables(KernelToLocalsMap localsMap) {
+    List<Local> locals = [];
+    for (ir.VariableDeclaration boxedLoopVariable in _boxedLoopVariables) {
+      locals.add(localsMap.getLocalVariable(boxedLoopVariable));
+    }
+    return locals;
+  }
 }
 
-// TODO(johnniwinther): Rename this class.
 // TODO(johnniwinther): Add unittest for the computed [ClosureClass].
-class KernelClosureClassInfo extends JsScopeInfo
+class JsClosureClassInfo extends JsScopeInfo
     implements ClosureRepresentationInfo {
-  /// Tag used for identifying serialized [KernelClosureClassInfo] objects in a
+  /// Tag used for identifying serialized [JsClosureClassInfo] objects in a
   /// debugging data stream.
   static const String tag = 'closure-representation-info';
 
@@ -625,110 +651,194 @@
   JFunction callMethod;
   @override
   JSignatureMethod signatureMethod;
-  @override
-  final Local closureEntity;
+
+  /// The local used for this closure, if it is an anonymous closure, i.e.
+  /// an `ir.FunctionExpression`.
+  final Local _closureEntity;
+
+  /// The local variable that defines this closure, if it is a local function
+  /// declaration.
+  final ir.VariableDeclaration _closureEntityVariable;
+
   @override
   final Local thisLocal;
+
   @override
   final JClass closureClassEntity;
 
-  final Map<Local, JField> localToFieldMap;
+  final Map<ir.VariableDeclaration, JField> _variableToFieldMap;
+  final Map<JTypeVariable, JField> _typeVariableToFieldMap;
+  final Map<Local, JField> _localToFieldMap;
+  Map<JField, Local> _fieldToLocalsMap;
 
-  KernelClosureClassInfo.internal(
-      Iterable<Local> localsUsedInTryOrSync,
+  JsClosureClassInfo.internal(
+      Iterable<ir.VariableDeclaration> localsUsedInTryOrSync,
       this.thisLocal,
-      Map<Local, JRecordField> boxedVariables,
-      Set<Local> freeVariables,
+      Map<ir.VariableDeclaration, JRecordField> boxedVariables,
       this.callMethod,
       this.signatureMethod,
-      this.closureEntity,
+      this._closureEntity,
+      this._closureEntityVariable,
       this.closureClassEntity,
-      this.localToFieldMap)
-      : super.internal(
-            localsUsedInTryOrSync, thisLocal, boxedVariables, freeVariables);
+      this._variableToFieldMap,
+      this._typeVariableToFieldMap,
+      this._localToFieldMap)
+      : super.internal(localsUsedInTryOrSync, thisLocal, boxedVariables);
 
-  KernelClosureClassInfo.fromScopeInfo(
+  JsClosureClassInfo.fromScopeInfo(
       this.closureClassEntity,
       ir.FunctionNode closureSourceNode,
-      Map<Local, JRecordField> boxedVariables,
+      Map<ir.VariableDeclaration, JRecordField> boxedVariables,
       KernelScopeInfo info,
-      KernelToLocalsMap localsMap,
-      this.closureEntity,
-      this.thisLocal,
-      JsToElementMap elementMap)
-      : localToFieldMap = new Map<Local, JField>(),
-        super.from(boxedVariables, info, localsMap, elementMap);
+      ClassEntity enclosingClass,
+      this._closureEntity,
+      this._closureEntityVariable,
+      this.thisLocal)
+      : _variableToFieldMap = {},
+        _typeVariableToFieldMap = {},
+        _localToFieldMap = {},
+        super.from(boxedVariables, info, enclosingClass);
 
-  factory KernelClosureClassInfo.readFromDataSource(DataSource source) {
+  factory JsClosureClassInfo.readFromDataSource(DataSource source) {
     source.begin(tag);
-    Iterable<Local> localsUsedInTryOrSync = source.readLocals();
+    Iterable<ir.VariableDeclaration> localsUsedInTryOrSync =
+        source.readTreeNodes<ir.VariableDeclaration>();
     Local thisLocal = source.readLocalOrNull();
-    Map<Local, JRecordField> boxedVariables =
-        source.readLocalMap<Local, JRecordField>(() => source.readMember());
-    Set<Local> freeVariables = source.readLocals().toSet();
+    Map<ir.VariableDeclaration, JRecordField> boxedVariables =
+        source.readTreeNodeMap<ir.VariableDeclaration, JRecordField>(
+            () => source.readMember());
     JFunction callMethod = source.readMember();
     JSignatureMethod signatureMethod = source.readMemberOrNull();
     Local closureEntity = source.readLocalOrNull();
+    ir.VariableDeclaration closureEntityVariable = source.readTreeNodeOrNull();
     JClass closureClassEntity = source.readClass();
-    Map<Local, JField> localToFieldMap =
+    Map<ir.VariableDeclaration, JField> localToFieldMap =
+        source.readTreeNodeMap<ir.VariableDeclaration, JField>(
+            () => source.readMember());
+    Map<JTypeVariable, JField> typeVariableToFieldMap = source
+        .readTypeVariableMap<JTypeVariable, JField>(() => source.readMember());
+    Map<Local, JField> thisLocalToFieldMap =
         source.readLocalMap(() => source.readMember());
     source.end(tag);
     if (boxedVariables.isEmpty) boxedVariables = const {};
-    if (freeVariables.isEmpty) freeVariables = const {};
     if (localToFieldMap.isEmpty) localToFieldMap = const {};
-    return KernelClosureClassInfo.internal(
+    return JsClosureClassInfo.internal(
         localsUsedInTryOrSync,
         thisLocal,
         boxedVariables,
-        freeVariables,
         callMethod,
         signatureMethod,
         closureEntity,
+        closureEntityVariable,
         closureClassEntity,
-        localToFieldMap);
+        localToFieldMap,
+        typeVariableToFieldMap,
+        thisLocalToFieldMap);
   }
 
   @override
   void writeToDataSink(DataSink sink) {
     sink.writeEnum(ScopeInfoKind.closureRepresentationInfo);
     sink.begin(tag);
-    sink.writeLocals(localsUsedInTryOrSync);
+    sink.writeTreeNodes(_localsUsedInTryOrSync);
     sink.writeLocalOrNull(thisLocal);
-    sink.writeLocalMap(boxedVariables, sink.writeMember);
-    sink.writeLocals(freeVariables);
+    sink.writeTreeNodeMap(_boxedVariables, sink.writeMember);
     sink.writeMember(callMethod);
     sink.writeMemberOrNull(signatureMethod);
-    sink.writeLocalOrNull(closureEntity);
+    sink.writeLocalOrNull(_closureEntity);
+    sink.writeTreeNodeOrNull(_closureEntityVariable);
     sink.writeClass(closureClassEntity);
-    sink.writeLocalMap(localToFieldMap, sink.writeMember);
+    sink.writeTreeNodeMap(_variableToFieldMap, sink.writeMember);
+    sink.writeTypeVariableMap(_typeVariableToFieldMap, sink.writeMember);
+    sink.writeLocalMap(_localToFieldMap, sink.writeMember);
     sink.end(tag);
   }
 
-  @override
-  List<Local> get createdFieldEntities => localToFieldMap.keys.toList();
+  bool hasFieldForLocal(Local local) => _localToFieldMap.containsKey(local);
 
-  @override
-  Local getLocalForField(FieldEntity field) {
-    for (Local local in localToFieldMap.keys) {
-      if (localToFieldMap[local] == field) {
-        return local;
+  void registerFieldForLocal(Local local, JField field) {
+    assert(_fieldToLocalsMap == null);
+    _localToFieldMap[local] = field;
+  }
+
+  void registerFieldForVariable(ir.VariableDeclaration node, JField field) {
+    assert(_fieldToLocalsMap == null);
+    _variableToFieldMap[node] = field;
+  }
+
+  bool hasFieldForTypeVariable(JTypeVariable typeVariable) =>
+      _typeVariableToFieldMap.containsKey(typeVariable);
+
+  void registerFieldForTypeVariable(JTypeVariable typeVariable, JField field) {
+    assert(_fieldToLocalsMap == null);
+    _typeVariableToFieldMap[typeVariable] = field;
+  }
+
+  void registerFieldForBoxedVariable(
+      ir.VariableDeclaration node, JField field) {
+    assert(_boxedVariablesCache == null);
+    _boxedVariables[node] = field;
+  }
+
+  void _ensureFieldToLocalsMap(KernelToLocalsMap localsMap) {
+    if (_fieldToLocalsMap == null) {
+      _fieldToLocalsMap = {};
+      _variableToFieldMap.forEach((ir.VariableDeclaration node, JField field) {
+        _fieldToLocalsMap[field] = localsMap.getLocalVariable(node);
+      });
+      _typeVariableToFieldMap
+          .forEach((TypeVariableEntity typeVariable, JField field) {
+        _fieldToLocalsMap[field] =
+            localsMap.getLocalTypeVariableEntity(typeVariable);
+      });
+      _localToFieldMap.forEach((Local local, JField field) {
+        _fieldToLocalsMap[field] = local;
+      });
+      if (_fieldToLocalsMap.isEmpty) {
+        _fieldToLocalsMap = const {};
       }
     }
-    failedAt(field, "No local for $field. Options: $localToFieldMap");
-    return null;
   }
 
   @override
-  FieldEntity get thisFieldEntity => localToFieldMap[thisLocal];
+  List<Local> getCreatedFieldEntities(KernelToLocalsMap localsMap) {
+    _ensureFieldToLocalsMap(localsMap);
+    return _fieldToLocalsMap.values.toList();
+  }
 
   @override
-  void forEachFreeVariable(f(Local variable, JField field)) {
-    localToFieldMap.forEach(f);
-    boxedVariables.forEach(f);
+  Local getLocalForField(KernelToLocalsMap localsMap, FieldEntity field) {
+    _ensureFieldToLocalsMap(localsMap);
+    Local local = _fieldToLocalsMap[field];
+    if (local == null) {
+      failedAt(field, "No local for $field. Options: $_fieldToLocalsMap");
+    }
+    return local;
+  }
+
+  @override
+  FieldEntity get thisFieldEntity => _localToFieldMap[thisLocal];
+
+  @override
+  void forEachFreeVariable(
+      KernelToLocalsMap localsMap, f(Local variable, JField field)) {
+    _ensureFieldToLocalsMap(localsMap);
+    _ensureBoxedVariableCache(localsMap);
+    _fieldToLocalsMap.forEach((JField field, Local local) {
+      f(local, field);
+    });
+    _boxedVariablesCache.forEach(f);
   }
 
   @override
   bool get isClosure => true;
+
+  @override
+  Local getClosureEntity(KernelToLocalsMap localsMap) {
+    return _closureEntityVariable != null
+        ? localsMap.getLocalVariable(_closureEntityVariable)
+        : _closureEntity;
+  }
 }
 
 class JClosureClass extends JClass {
@@ -795,7 +905,7 @@
   final String declaredName;
 
   JClosureField(
-      String name, KernelClosureClassInfo containingClass, String declaredName,
+      String name, JsClosureClassInfo containingClass, String declaredName,
       {bool isConst, bool isAssignable})
       : this.internal(
             containingClass.closureClassEntity.library,
diff --git a/pkg/compiler/lib/src/js_model/element_map.dart b/pkg/compiler/lib/src/js_model/element_map.dart
index 622ee68..4d6febd 100644
--- a/pkg/compiler/lib/src/js_model/element_map.dart
+++ b/pkg/compiler/lib/src/js_model/element_map.dart
@@ -150,8 +150,8 @@
 
   /// Make a record to ensure variables that are are declared in one scope and
   /// modified in another get their values updated correctly.
-  Map<Local, JRecordField> makeRecordContainer(
-      KernelScopeInfo info, MemberEntity member, KernelToLocalsMap localsMap);
+  Map<ir.VariableDeclaration, JRecordField> makeRecordContainer(
+      KernelScopeInfo info, MemberEntity member);
 
   /// Returns a provider for static types for [member].
   StaticTypeProvider getStaticTypeProvider(MemberEntity member);
@@ -217,8 +217,8 @@
   /// Returns the [Local] for [node].
   Local getLocalVariable(ir.VariableDeclaration node);
 
-  Local getLocalTypeVariable(
-      ir.TypeParameterType node, JsToElementMap elementMap);
+  /// Returns the [Local] for the [typeVariable].
+  Local getLocalTypeVariableEntity(TypeVariableEntity typeVariable);
 
   /// Returns the [ir.FunctionNode] that declared [parameter].
   ir.FunctionNode getFunctionNodeForParameter(Local parameter);
diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart
index ec6d67f..1f4cd96 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -1725,11 +1725,9 @@
       InterfaceType memberThisType,
       ir.VariableDeclaration variable,
       BoxLocal boxLocal,
-      Map<String, MemberEntity> memberMap,
-      KernelToLocalsMap localsMap) {
-    Local local = localsMap.getLocalVariable(variable);
+      Map<String, MemberEntity> memberMap) {
     JRecordField boxedField =
-        new JRecordField(local.name, boxLocal, isConst: variable.isConst);
+        new JRecordField(variable.name, boxLocal, isConst: variable.isConst);
     members.register(
         boxedField,
         new ClosureFieldData(
@@ -1745,9 +1743,9 @@
   /// are accessed in different scopes. This function creates the container
   /// and returns a map of locals to the corresponding records created.
   @override
-  Map<Local, JRecordField> makeRecordContainer(
-      KernelScopeInfo info, MemberEntity member, KernelToLocalsMap localsMap) {
-    Map<Local, JRecordField> boxedFields = {};
+  Map<ir.VariableDeclaration, JRecordField> makeRecordContainer(
+      KernelScopeInfo info, MemberEntity member) {
+    Map<ir.VariableDeclaration, JRecordField> boxedFields = {};
     if (info.boxedVariables.isNotEmpty) {
       NodeBox box = info.capturedVariablesAccessor;
 
@@ -1768,18 +1766,13 @@
           ? elementEnvironment.getThisType(member.enclosingClass)
           : null;
       for (ir.VariableDeclaration variable in info.boxedVariables) {
-        boxedFields[localsMap.getLocalVariable(variable)] =
-            _constructRecordFieldEntry(
-                memberThisType, variable, boxLocal, memberMap, localsMap);
+        boxedFields[variable] = _constructRecordFieldEntry(
+            memberThisType, variable, boxLocal, memberMap);
       }
     }
     return boxedFields;
   }
 
-  bool _isInRecord(
-          Local local, Map<Local, JRecordField> recordFieldsVisibleInScope) =>
-      recordFieldsVisibleInScope.containsKey(local);
-
   ParameterStructure _getParameterStructureFromFunctionNode(
       ir.FunctionNode node) {
     int requiredPositionalParameters = node.requiredParameterCount;
@@ -1802,13 +1795,12 @@
         typeParameters);
   }
 
-  KernelClosureClassInfo constructClosureClass(
+  JsClosureClassInfo constructClosureClass(
       MemberEntity member,
       ir.FunctionNode node,
       JLibrary enclosingLibrary,
-      Map<Local, JRecordField> recordFieldsVisibleInScope,
+      Map<ir.VariableDeclaration, JRecordField> recordFieldsVisibleInScope,
       KernelScopeInfo info,
-      KernelToLocalsMap localsMap,
       InterfaceType supertype,
       {bool createSignatureMethod}) {
     InterfaceType memberThisType = member.enclosingClass != null
@@ -1839,9 +1831,10 @@
     classes.register(classEntity, closureData, new ClosureClassEnv(memberMap));
 
     Local closureEntity;
+    ir.VariableDeclaration closureEntityNode;
     if (node.parent is ir.FunctionDeclaration) {
       ir.FunctionDeclaration parent = node.parent;
-      closureEntity = localsMap.getLocalVariable(parent.variable);
+      closureEntityNode = parent.variable;
     } else if (node.parent is ir.FunctionExpression) {
       closureEntity = new AnonymousClosureLocal(classEntity);
     }
@@ -1862,20 +1855,17 @@
       index++;
     }
 
-    KernelClosureClassInfo closureClassInfo =
-        new KernelClosureClassInfo.fromScopeInfo(
-            classEntity,
-            node,
-            <Local, JRecordField>{},
-            info,
-            localsMap,
-            closureEntity,
-            info.hasThisLocal
-                ? new ThisLocal(localsMap.currentMember.enclosingClass)
-                : null,
-            this);
+    JsClosureClassInfo closureClassInfo = new JsClosureClassInfo.fromScopeInfo(
+        classEntity,
+        node,
+        <ir.VariableDeclaration, JRecordField>{},
+        info,
+        member.enclosingClass,
+        closureEntity,
+        closureEntityNode,
+        info.hasThisLocal ? new ThisLocal(member.enclosingClass) : null);
     _buildClosureClassFields(closureClassInfo, member, memberThisType, info,
-        localsMap, recordFieldsVisibleInScope, memberMap);
+        recordFieldsVisibleInScope, memberMap);
 
     if (createSignatureMethod) {
       _constructSignatureMethod(closureClassInfo, memberMap, node,
@@ -1898,12 +1888,11 @@
   }
 
   void _buildClosureClassFields(
-      KernelClosureClassInfo closureClassInfo,
+      JsClosureClassInfo closureClassInfo,
       MemberEntity member,
       InterfaceType memberThisType,
       KernelScopeInfo info,
-      KernelToLocalsMap localsMap,
-      Map<Local, JRecordField> recordFieldsVisibleInScope,
+      Map<ir.VariableDeclaration, JRecordField> recordFieldsVisibleInScope,
       Map<String, MemberEntity> memberMap) {
     // TODO(efortuna): Limit field number usage to when we need to distinguish
     // between two variables with the same name from different scopes.
@@ -1917,10 +1906,9 @@
 
     for (ir.Node variable in info.freeVariables) {
       if (variable is ir.VariableDeclaration) {
-        Local capturedLocal = localsMap.getLocalVariable(variable);
-        if (_isInRecord(capturedLocal, recordFieldsVisibleInScope)) {
+        if (recordFieldsVisibleInScope.containsKey(variable)) {
           bool constructedField = _constructClosureFieldForRecord(
-              capturedLocal,
+              variable,
               closureClassInfo,
               memberThisType,
               memberMap,
@@ -1934,15 +1922,17 @@
 
     // Add a field for the captured 'this'.
     if (info.thisUsedAsFreeVariable) {
-      _constructClosureField(
+      closureClassInfo.registerFieldForLocal(
           closureClassInfo.thisLocal,
-          closureClassInfo,
-          memberThisType,
-          memberMap,
-          getClassDefinition(member.enclosingClass).node,
-          true,
-          false,
-          fieldNumber);
+          _constructClosureField(
+              closureClassInfo.thisLocal.name,
+              closureClassInfo,
+              memberThisType,
+              memberMap,
+              getClassDefinition(member.enclosingClass).node,
+              true,
+              false,
+              fieldNumber));
       fieldNumber++;
     }
 
@@ -1950,29 +1940,40 @@
       // Make a corresponding field entity in this closure class for the
       // free variables in the KernelScopeInfo.freeVariable.
       if (variable is ir.VariableDeclaration) {
-        Local capturedLocal = localsMap.getLocalVariable(variable);
-        if (!_isInRecord(capturedLocal, recordFieldsVisibleInScope)) {
-          _constructClosureField(
-              capturedLocal,
-              closureClassInfo,
-              memberThisType,
-              memberMap,
+        if (!recordFieldsVisibleInScope.containsKey(variable)) {
+          closureClassInfo.registerFieldForVariable(
               variable,
-              variable.isConst,
-              false, // Closure field is never assigned (only box fields).
-              fieldNumber);
+              _constructClosureField(
+                  variable.name,
+                  closureClassInfo,
+                  memberThisType,
+                  memberMap,
+                  variable,
+                  variable.isConst,
+                  false, // Closure field is never assigned (only box fields).
+                  fieldNumber));
           fieldNumber++;
         }
       } else if (variable is TypeVariableTypeWithContext) {
-        Local capturedLocal =
-            localsMap.getLocalTypeVariable(variable.type, this);
+        TypeVariableEntity typeVariable =
+            getTypeVariable(variable.type.parameter);
         // We can have distinct TypeVariableTypeWithContexts that have the same
         // local variable but with different nullabilities. We only want to
         // construct a closure field once for each local variable.
-        if (closureClassInfo.localToFieldMap.containsKey(capturedLocal))
+        if (closureClassInfo.hasFieldForTypeVariable(typeVariable)) {
           continue;
-        _constructClosureField(capturedLocal, closureClassInfo, memberThisType,
-            memberMap, variable.type.parameter, true, false, fieldNumber);
+        }
+        closureClassInfo.registerFieldForTypeVariable(
+            typeVariable,
+            _constructClosureField(
+                variable.type.parameter.name,
+                closureClassInfo,
+                memberThisType,
+                memberMap,
+                variable.type.parameter,
+                true,
+                false,
+                fieldNumber));
         fieldNumber++;
       } else {
         throw new UnsupportedError("Unexpected field node type: $variable");
@@ -1988,19 +1989,20 @@
   /// locals they contain may be). Returns `true` if we constructed a new field
   /// in the closure class.
   bool _constructClosureFieldForRecord(
-      Local capturedLocal,
-      KernelClosureClassInfo closureClassInfo,
+      ir.VariableDeclaration capturedLocal,
+      JsClosureClassInfo closureClassInfo,
       InterfaceType memberThisType,
       Map<String, MemberEntity> memberMap,
       ir.TreeNode sourceNode,
-      Map<Local, JRecordField> recordFieldsVisibleInScope,
+      Map<ir.VariableDeclaration, JRecordField> recordFieldsVisibleInScope,
       int fieldNumber) {
     JRecordField recordField = recordFieldsVisibleInScope[capturedLocal];
 
     // Don't construct a new field if the box that holds this local already has
     // a field in the closure class.
-    if (closureClassInfo.localToFieldMap.containsKey(recordField.box)) {
-      closureClassInfo.boxedVariables[capturedLocal] = recordField;
+    if (closureClassInfo.hasFieldForLocal(recordField.box)) {
+      closureClassInfo.registerFieldForBoxedVariable(
+          capturedLocal, recordField);
       return false;
     }
 
@@ -2017,13 +2019,13 @@
                 sourceNode),
             memberThisType));
     memberMap[closureField.name] = closureField;
-    closureClassInfo.localToFieldMap[recordField.box] = closureField;
-    closureClassInfo.boxedVariables[capturedLocal] = recordField;
+    closureClassInfo.registerFieldForLocal(recordField.box, closureField);
+    closureClassInfo.registerFieldForBoxedVariable(capturedLocal, recordField);
     return true;
   }
 
   void _constructSignatureMethod(
-      KernelClosureClassInfo closureClassInfo,
+      JsClosureClassInfo closureClassInfo,
       Map<String, MemberEntity> memberMap,
       ir.FunctionNode closureSourceNode,
       InterfaceType memberThisType,
@@ -2043,21 +2045,18 @@
         closureClassInfo.signatureMethod = signatureMethod;
   }
 
-  void _constructClosureField(
-      Local capturedLocal,
-      KernelClosureClassInfo closureClassInfo,
+  JField _constructClosureField(
+      String name,
+      JsClosureClassInfo closureClassInfo,
       InterfaceType memberThisType,
       Map<String, MemberEntity> memberMap,
       ir.TreeNode sourceNode,
       bool isConst,
       bool isAssignable,
       int fieldNumber) {
-    FieldEntity closureField = new JClosureField(
-        _getClosureVariableName(capturedLocal.name, fieldNumber),
-        closureClassInfo,
-        capturedLocal.name,
-        isConst: isConst,
-        isAssignable: isAssignable);
+    JField closureField = new JClosureField(
+        _getClosureVariableName(name, fieldNumber), closureClassInfo, name,
+        isConst: isConst, isAssignable: isAssignable);
 
     members.register<IndexedField, JFieldData>(
         closureField,
@@ -2068,7 +2067,7 @@
                 sourceNode),
             memberThisType));
     memberMap[closureField.name] = closureField;
-    closureClassInfo.localToFieldMap[capturedLocal] = closureField;
+    return closureField;
   }
 
   // Returns a non-unique name for the given closure element.
diff --git a/pkg/compiler/lib/src/js_model/js_strategy.dart b/pkg/compiler/lib/src/js_model/js_strategy.dart
index bb9ed2b..d432e33 100644
--- a/pkg/compiler/lib/src/js_model/js_strategy.dart
+++ b/pkg/compiler/lib/src/js_model/js_strategy.dart
@@ -169,12 +169,10 @@
         strategy.elementMap,
         closedWorld.liveMemberUsage,
         closedWorld.annotationsData);
-    GlobalLocalsMap _globalLocalsMap = new GlobalLocalsMap();
-    ClosureDataBuilder closureDataBuilder = new ClosureDataBuilder(
-        _elementMap, _globalLocalsMap, closedWorld.annotationsData);
+    ClosureDataBuilder closureDataBuilder =
+        new ClosureDataBuilder(_elementMap, closedWorld.annotationsData);
     JsClosedWorldBuilder closedWorldBuilder = new JsClosedWorldBuilder(
         _elementMap,
-        _globalLocalsMap,
         closureDataBuilder,
         _compiler.options,
         _compiler.abstractValueStrategy);
@@ -383,8 +381,11 @@
 
   @override
   TypesInferrer createTypesInferrer(
-      JClosedWorld closedWorld, InferredDataBuilder inferredDataBuilder) {
-    return new TypeGraphInferrer(_compiler, closedWorld, inferredDataBuilder);
+      JClosedWorld closedWorld,
+      GlobalLocalsMap globalLocalsMap,
+      InferredDataBuilder inferredDataBuilder) {
+    return new TypeGraphInferrer(
+        _compiler, closedWorld, globalLocalsMap, inferredDataBuilder);
   }
 
   @override
diff --git a/pkg/compiler/lib/src/js_model/js_world.dart b/pkg/compiler/lib/src/js_model/js_world.dart
index 2b5263a..868ef6f 100644
--- a/pkg/compiler/lib/src/js_model/js_world.dart
+++ b/pkg/compiler/lib/src/js_model/js_world.dart
@@ -90,8 +90,6 @@
   @override
   final AnnotationsData annotationsData;
   @override
-  final GlobalLocalsMap globalLocalsMap;
-  @override
   final ClosureData closureDataLookup;
   @override
   final OutputUnitData outputUnitData;
@@ -118,7 +116,6 @@
       this.classHierarchy,
       AbstractValueStrategy abstractValueStrategy,
       this.annotationsData,
-      this.globalLocalsMap,
       this.closureDataLookup,
       this.outputUnitData,
       this.memberAccess) {
@@ -138,9 +135,6 @@
     JsKernelToElementMap elementMap =
         new JsKernelToElementMap.readFromDataSource(
             options, reporter, environment, component, source);
-    GlobalLocalsMap globalLocalsMap =
-        new GlobalLocalsMap.readFromDataSource(source);
-    source.registerLocalLookup(new LocalLookupImpl(globalLocalsMap));
     ClassHierarchy classHierarchy = new ClassHierarchy.readFromDataSource(
         source, elementMap.commonElements);
     NativeData nativeData = new NativeData.readFromDataSource(
@@ -203,7 +197,6 @@
         classHierarchy,
         abstractValueStrategy,
         annotationsData,
-        globalLocalsMap,
         closureData,
         outputUnitData,
         memberAccess);
@@ -213,8 +206,6 @@
   void writeToDataSink(DataSink sink) {
     sink.begin(tag);
     elementMap.writeToDataSink(sink);
-    globalLocalsMap.writeToDataSink(sink);
-
     classHierarchy.writeToDataSink(sink);
     nativeData.writeToDataSink(sink);
     interceptorData.writeToDataSink(sink);
diff --git a/pkg/compiler/lib/src/js_model/js_world_builder.dart b/pkg/compiler/lib/src/js_model/js_world_builder.dart
index 07086db..f2fdbe6 100644
--- a/pkg/compiler/lib/src/js_model/js_world_builder.dart
+++ b/pkg/compiler/lib/src/js_model/js_world_builder.dart
@@ -34,23 +34,20 @@
 import '../world.dart';
 import 'closure.dart';
 import 'elements.dart';
-import 'element_map.dart';
 import 'element_map_impl.dart';
 import 'js_world.dart';
-import 'locals.dart';
 
 class JsClosedWorldBuilder {
   final JsKernelToElementMap _elementMap;
   final Map<ClassEntity, ClassHierarchyNode> _classHierarchyNodes =
       new ClassHierarchyNodesMap();
   final Map<ClassEntity, ClassSet> _classSets = <ClassEntity, ClassSet>{};
-  final GlobalLocalsMap _globalLocalsMap;
   final ClosureDataBuilder _closureDataBuilder;
   final CompilerOptions _options;
   final AbstractValueStrategy _abstractValueStrategy;
 
-  JsClosedWorldBuilder(this._elementMap, this._globalLocalsMap,
-      this._closureDataBuilder, this._options, this._abstractValueStrategy);
+  JsClosedWorldBuilder(this._elementMap, this._closureDataBuilder,
+      this._options, this._abstractValueStrategy);
 
   ElementEnvironment get _elementEnvironment => _elementMap.elementEnvironment;
   CommonElements get _commonElements => _elementMap.commonElements;
@@ -236,7 +233,6 @@
             _elementMap.commonElements, _classHierarchyNodes, _classSets),
         _abstractValueStrategy,
         annotationsData,
-        _globalLocalsMap,
         closureData,
         outputUnitData,
         memberAccess);
@@ -414,23 +410,21 @@
 
   /// Construct a closure class and set up the necessary class inference
   /// hierarchy.
-  KernelClosureClassInfo buildClosureClass(
+  JsClosureClassInfo buildClosureClass(
       MemberEntity member,
       ir.FunctionNode originalClosureFunctionNode,
       JLibrary enclosingLibrary,
-      Map<Local, JRecordField> boxedVariables,
+      Map<ir.VariableDeclaration, JRecordField> boxedVariables,
       KernelScopeInfo info,
-      KernelToLocalsMap localsMap,
       {bool createSignatureMethod}) {
     ClassEntity superclass = _commonElements.closureClass;
 
-    KernelClosureClassInfo closureClassInfo = _elementMap.constructClosureClass(
+    JsClosureClassInfo closureClassInfo = _elementMap.constructClosureClass(
         member,
         originalClosureFunctionNode,
         enclosingLibrary,
         boxedVariables,
         info,
-        localsMap,
         _dartTypes.interfaceType(superclass, const []),
         createSignatureMethod: createSignatureMethod);
 
diff --git a/pkg/compiler/lib/src/js_model/locals.dart b/pkg/compiler/lib/src/js_model/locals.dart
index c783b25..1d5cd00 100644
--- a/pkg/compiler/lib/src/js_model/locals.dart
+++ b/pkg/compiler/lib/src/js_model/locals.dart
@@ -22,14 +22,23 @@
   /// debugging data stream.
   static const String tag = 'global-locals-map';
 
+  /// Lookup up the key used to store a LocalsMap for a member.
+  ///
+  /// While procedures are keyed by their own entity, closures use the
+  /// enclosing member as a key. This ensures that the member and all
+  /// nested closures share the same local map.
+  MemberEntity Function(MemberEntity) _localMapKeyLookup;
+
   final Map<MemberEntity, KernelToLocalsMap> _localsMaps;
 
-  GlobalLocalsMap() : _localsMaps = {};
+  GlobalLocalsMap(this._localMapKeyLookup) : _localsMaps = {};
 
-  GlobalLocalsMap.internal(this._localsMaps);
+  GlobalLocalsMap.internal(this._localMapKeyLookup, this._localsMaps);
 
   /// Deserializes a [GlobalLocalsMap] object from [source].
-  factory GlobalLocalsMap.readFromDataSource(DataSource source) {
+  factory GlobalLocalsMap.readFromDataSource(
+      MemberEntity Function(MemberEntity) localMapKeyLookup,
+      DataSource source) {
     source.begin(tag);
     Map<MemberEntity, KernelToLocalsMap> _localsMaps = {};
     int mapCount = source.readInt();
@@ -42,7 +51,7 @@
       }
     }
     source.end(tag);
-    return new GlobalLocalsMap.internal(_localsMaps);
+    return new GlobalLocalsMap.internal(localMapKeyLookup, _localsMaps);
   }
 
   /// Serializes this [GlobalLocalsMap] to [sink].
@@ -68,26 +77,19 @@
 
   /// Returns the [KernelToLocalsMap] for [member].
   KernelToLocalsMap getLocalsMap(MemberEntity member) {
-    // If element is a ConstructorBodyEntity, its localsMap is the same as for
+    // If [member] is a closure call method or closure signature method, its
+    // localsMap is the same as for the enclosing member since the locals are
+    // derived from the same kernel AST.
+    MemberEntity key = _localMapKeyLookup(member);
+    // If [member] is a ConstructorBodyEntity, its localsMap is the same as for
     // ConstructorEntity, because both of these entities came from the same
     // constructor node. The entities are two separate parts because JS does not
     // have the concept of an initializer list, so the constructor (initializer
     // list) and the constructor body are implemented as two separate
     // constructor steps.
-    MemberEntity entity = member;
-    if (entity is ConstructorBodyEntity) member = entity.constructor;
-    return _localsMaps.putIfAbsent(
-        member, () => new KernelToLocalsMapImpl(member));
-  }
-
-  /// Associates [localsMap] with [member].
-  ///
-  /// Use this for sharing maps between members that share IR nodes.
-  void setLocalsMap(MemberEntity member, KernelToLocalsMap localsMap) {
-    assert(member != null, "No member provided.");
-    assert(!_localsMaps.containsKey(member),
-        "Locals map already created for $member.");
-    _localsMaps[member] = localsMap;
+    MemberEntity entity = key;
+    if (entity is ConstructorBodyEntity) key = entity.constructor;
+    return _localsMaps.putIfAbsent(key, () => new KernelToLocalsMapImpl(key));
   }
 }
 
@@ -272,12 +274,11 @@
   }
 
   @override
-  Local getLocalTypeVariable(
-      ir.TypeParameterType node, JsToElementMap elementMap) {
+  Local getLocalTypeVariableEntity(TypeVariableEntity typeVariable) {
     // TODO(efortuna, johnniwinther): We're not registering the type variables
     // like we are for the variable declarations. Is that okay or do we need to
     // make TypeVariableLocal a JLocal?
-    return TypeVariableLocal(elementMap.getTypeVariableType(node).element);
+    return TypeVariableLocal(typeVariable);
   }
 
   @override
diff --git a/pkg/compiler/lib/src/serialization/mixins.dart b/pkg/compiler/lib/src/serialization/mixins.dart
index d7d5d87..0b48464 100644
--- a/pkg/compiler/lib/src/serialization/mixins.dart
+++ b/pkg/compiler/lib/src/serialization/mixins.dart
@@ -185,6 +185,20 @@
   }
 
   @override
+  Map<K, V> readTypeVariableMap<K extends IndexedTypeVariable, V>(V f(),
+      {bool emptyAsNull: false}) {
+    int count = readInt();
+    if (count == 0 && emptyAsNull) return null;
+    Map<K, V> map = {};
+    for (int i = 0; i < count; i++) {
+      IndexedTypeVariable node = readTypeVariable();
+      V value = f();
+      map[node] = value;
+    }
+    return map;
+  }
+
+  @override
   List<E> readLocals<E extends Local>({bool emptyAsNull: false}) {
     int count = readInt();
     if (count == 0 && emptyAsNull) return null;
@@ -606,6 +620,21 @@
   }
 
   @override
+  void writeTypeVariableMap<V>(Map<IndexedTypeVariable, V> map, void f(V value),
+      {bool allowNull: false}) {
+    if (map == null) {
+      assert(allowNull);
+      writeInt(0);
+    } else {
+      writeInt(map.length);
+      map.forEach((IndexedTypeVariable key, V value) {
+        writeTypeVariable(key);
+        f(value);
+      });
+    }
+  }
+
+  @override
   void writeList<E>(Iterable<E> values, void f(E value),
       {bool allowNull: false}) {
     if (values == null) {
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index 6cc9a51..14fb2f7 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -329,6 +329,15 @@
   /// Writes a reference to the indexed type variable [value] to this data sink.
   void writeTypeVariable(IndexedTypeVariable value);
 
+  /// Writes the [map] from references to indexed type variables to [V] values
+  /// to this data sink, calling [f] to write each value to the data sink. If
+  /// [allowNull] is `true`, [map] is allowed to be `null`.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSource.readTypeVariableMap].
+  void writeTypeVariableMap<V>(Map<IndexedTypeVariable, V> map, void f(V value),
+      {bool allowNull: false});
+
   /// Writes a reference to the local [value] to this data sink.
   void writeLocal(Local local);
 
@@ -742,6 +751,15 @@
   /// Reads a reference to an indexed type variable from this data source.
   IndexedTypeVariable readTypeVariable();
 
+  /// Reads a map from indexed type variable to [V] values from this data
+  /// source, calling [f] to read each value from the data source. If
+  /// [emptyAsNull] is `true`, `null` is returned instead of an empty map.
+  ///
+  /// This is a convenience method to be used together with
+  /// [DataSink.writeTypeVariableMap].
+  Map<K, V> readTypeVariableMap<K extends IndexedTypeVariable, V>(V f(),
+      {bool emptyAsNull: false});
+
   /// Reads a reference to a local from this data source.
   Local readLocal();
 
diff --git a/pkg/compiler/lib/src/serialization/task.dart b/pkg/compiler/lib/src/serialization/task.dart
index 707def0..3ac7e86 100644
--- a/pkg/compiler/lib/src/serialization/task.dart
+++ b/pkg/compiler/lib/src/serialization/task.dart
@@ -19,6 +19,7 @@
 import '../js_backend/backend.dart';
 import '../js_backend/inferred_data.dart';
 import '../js_model/js_world.dart';
+import '../js_model/locals.dart';
 import '../options.dart';
 import '../util/sink_adapter.dart';
 import '../world.dart';
@@ -27,8 +28,10 @@
 void serializeGlobalTypeInferenceResultsToSink(
     GlobalTypeInferenceResults results, DataSink sink) {
   JsClosedWorld closedWorld = results.closedWorld;
+  GlobalLocalsMap globalLocalsMap = results.globalLocalsMap;
   InferredData inferredData = results.inferredData;
   closedWorld.writeToDataSink(sink);
+  globalLocalsMap.writeToDataSink(sink);
   inferredData.writeToDataSink(sink);
   results.writeToDataSink(sink, closedWorld.elementMap);
   sink.close();
@@ -42,10 +45,16 @@
     ir.Component component,
     JsClosedWorld newClosedWorld,
     DataSource source) {
+  GlobalLocalsMap newGlobalLocalsMap = GlobalLocalsMap.readFromDataSource(
+      newClosedWorld.closureDataLookup.getEnclosingMember, source);
   InferredData newInferredData =
       InferredData.readFromDataSource(source, newClosedWorld);
   return GlobalTypeInferenceResults.readFromDataSource(
-      source, newClosedWorld.elementMap, newClosedWorld, newInferredData);
+      source,
+      newClosedWorld.elementMap,
+      newClosedWorld,
+      newGlobalLocalsMap,
+      newInferredData);
 }
 
 GlobalTypeInferenceResults deserializeGlobalTypeInferenceResultsFromSource(
@@ -57,10 +66,16 @@
     DataSource source) {
   JsClosedWorld newClosedWorld = new JsClosedWorld.readFromDataSource(
       options, reporter, environment, abstractValueStrategy, component, source);
+  GlobalLocalsMap newGlobalLocalsMap = GlobalLocalsMap.readFromDataSource(
+      newClosedWorld.closureDataLookup.getEnclosingMember, source);
   InferredData newInferredData =
       new InferredData.readFromDataSource(source, newClosedWorld);
   return new GlobalTypeInferenceResults.readFromDataSource(
-      source, newClosedWorld.elementMap, newClosedWorld, newInferredData);
+      source,
+      newClosedWorld.elementMap,
+      newClosedWorld,
+      newGlobalLocalsMap,
+      newInferredData);
 }
 
 void serializeClosedWorldToSink(JsClosedWorld closedWorld, DataSink sink) {
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 20e835b..4bd37c8 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -34,7 +34,7 @@
 import '../js_backend/native_data.dart';
 import '../js_backend/runtime_types_resolution.dart';
 import '../js_emitter/code_emitter_task.dart' show ModularEmitter;
-import '../js_model/locals.dart' show JumpVisitor;
+import '../js_model/locals.dart' show GlobalLocalsMap, JumpVisitor;
 import '../js_model/elements.dart' show JGeneratorBody;
 import '../js_model/element_map.dart';
 import '../js_model/js_strategy.dart';
@@ -212,6 +212,9 @@
 
   RuntimeTypesNeed get _rtiNeed => closedWorld.rtiNeed;
 
+  GlobalLocalsMap get _globalLocalsMap =>
+      globalInferenceResults.globalLocalsMap;
+
   InferredData get _inferredData => globalInferenceResults.inferredData;
 
   DartTypes get dartTypes => closedWorld.dartTypes;
@@ -408,7 +411,7 @@
         _currentFrame,
         member,
         asyncMarker,
-        closedWorld.globalLocalsMap.getLocalsMap(member),
+        _globalLocalsMap.getLocalsMap(member),
         {},
         new KernelToTypeInferenceMapImpl(member, globalInferenceResults),
         _currentFrame != null
@@ -862,7 +865,7 @@
         CapturedScope scopeData =
             _closureDataLookup.getCapturedScope(constructorBody);
         if (scopeData.requiresContextBox) {
-          bodyCallInputs.add(localsHandler.readLocal(scopeData.context));
+          bodyCallInputs.add(localsHandler.readLocal(scopeData.contextBox));
         }
 
         // Pass type arguments.
@@ -1166,7 +1169,7 @@
     var index = 0;
 
     ConstructorEntity element = _elementMap.getConstructor(constructor);
-    ScopeInfo oldScopeInfo = localsHandler.scopeInfo;
+    MemberEntity oldScopeMember = localsHandler.scopeMember;
 
     _inlinedFrom(
         element, _sourceInformationBuilder.buildCall(initializer, initializer),
@@ -1190,13 +1193,12 @@
           constructorData, element.enclosingClass);
 
       // Set the locals handler state as if we were inlining the constructor.
-      ScopeInfo newScopeInfo = _closureDataLookup.getScopeInfo(element);
-      localsHandler.scopeInfo = newScopeInfo;
+      localsHandler.setupScope(element);
       localsHandler.enterScope(_closureDataLookup.getCapturedScope(element),
           _sourceInformationBuilder.buildDeclaration(element));
       _buildInitializers(constructor, constructorData);
     });
-    localsHandler.scopeInfo = oldScopeInfo;
+    localsHandler.setupScope(oldScopeMember);
   }
 
   /// Constructs a special signature function for a closure.
@@ -1391,7 +1393,7 @@
       if (nodeIsConstructorBody &&
           _closureDataLookup
               .getCapturedScope(targetElement)
-              .isBoxedVariable(local)) {
+              .isBoxedVariable(_localsMap, local)) {
         // If local is boxed, then `variable` will be a field inside the box
         // passed as the last parameter, so no need to update our locals
         // handler or check types at this point.
@@ -1430,9 +1432,10 @@
         closedWorld.annotationsData.getParameterCheckPolicy(method).isEmitted) {
       ir.FunctionNode function = getFunctionNode(_elementMap, method);
       for (ir.TypeParameter typeParameter in function.typeParameters) {
-        Local local = _localsMap.getLocalTypeVariable(
-            new ir.TypeParameterType(typeParameter, ir.Nullability.nonNullable),
-            _elementMap);
+        Local local = _localsMap.getLocalTypeVariableEntity(_elementMap
+            .getTypeVariableType(new ir.TypeParameterType(
+                typeParameter, ir.Nullability.nonNullable))
+            .element);
         HInstruction newParameter = localsHandler.directLocals[local];
         DartType bound = _getDartTypeIfValid(typeParameter.bound);
         if (!dartTypes.isTopType(bound)) {
@@ -1629,12 +1632,7 @@
     close(new HGoto(_abstractValueDomain)).addSuccessor(block);
     open(block);
 
-    localsHandler.startFunction(
-        targetElement,
-        _closureDataLookup.getScopeInfo(targetElement),
-        _closureDataLookup.getCapturedScope(targetElement),
-        parameterMap,
-        elidedParameterSet,
+    localsHandler.startFunction(targetElement, parameterMap, elidedParameterSet,
         _sourceInformationBuilder.buildDeclaration(targetElement),
         isGenerativeConstructorBody: targetElement is ConstructorBodyEntity);
 
@@ -5113,8 +5111,8 @@
     _elementEnvironment.forEachInstanceField(closureClassEntity,
         (_, FieldEntity field) {
       if (_fieldAnalysis.getFieldData(field).isElided) return;
-      capturedVariables
-          .add(localsHandler.readLocal(closureInfo.getLocalForField(field)));
+      capturedVariables.add(localsHandler
+          .readLocal(closureInfo.getLocalForField(_localsMap, field)));
     });
 
     AbstractValue type =
@@ -6017,7 +6015,7 @@
         instanceType ?? _elementMap.getMemberThisType(function),
         _nativeData,
         _interceptorData);
-    localsHandler.scopeInfo = _closureDataLookup.getScopeInfo(function);
+    localsHandler.setupScope(function);
 
     CapturedScope scopeData = _closureDataLookup.getCapturedScope(function);
     bool forGenerativeConstructorBody = function is ConstructorBodyEntity;
@@ -6029,14 +6027,13 @@
 
     int argumentIndex = 0;
     if (function.isInstanceMember) {
-      localsHandler.updateLocal(localsHandler.scopeInfo.thisLocal,
-          compiledArguments[argumentIndex++]);
+      localsHandler.updateLocal(
+          localsHandler.thisLocal, compiledArguments[argumentIndex++]);
     }
 
     ir.Member memberContextNode = _elementMap.getMemberContextNode(function);
     bool hasBox = false;
-    KernelToLocalsMap localsMap =
-        closedWorld.globalLocalsMap.getLocalsMap(function);
+    KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(function);
     forEachOrderedParameter(_elementMap, function,
         (ir.VariableDeclaration variable, {bool isElided}) {
       Local local = localsMap.getLocalVariable(variable);
@@ -6045,7 +6042,8 @@
             local, _defaultValueForParameter(memberContextNode, variable));
         return;
       }
-      if (forGenerativeConstructorBody && scopeData.isBoxedVariable(local)) {
+      if (forGenerativeConstructorBody &&
+          scopeData.isBoxedVariable(_localsMap, local)) {
         // The parameter will be a field in the box passed as the last
         // parameter. So no need to have it.
         hasBox = true;
@@ -6202,8 +6200,7 @@
       _checkTypeVariableBounds(function);
     }
 
-    KernelToLocalsMap localsMap =
-        closedWorld.globalLocalsMap.getLocalsMap(function);
+    KernelToLocalsMap localsMap = _globalLocalsMap.getLocalsMap(function);
     forEachOrderedParameter(_elementMap, function,
         (ir.VariableDeclaration variable, {bool isElided}) {
       Local parameter = localsMap.getLocalVariable(variable);
diff --git a/pkg/compiler/lib/src/ssa/locals_handler.dart b/pkg/compiler/lib/src/ssa/locals_handler.dart
index 3798e19..ea01915 100644
--- a/pkg/compiler/lib/src/ssa/locals_handler.dart
+++ b/pkg/compiler/lib/src/ssa/locals_handler.dart
@@ -2,6 +2,8 @@
 // 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:compiler/src/js_model/element_map.dart';
+
 import '../closure.dart';
 import '../common.dart';
 import '../elements/entities.dart';
@@ -12,7 +14,7 @@
 import '../js_backend/native_data.dart';
 import '../js_backend/interceptor_data.dart';
 import '../js_model/closure.dart' show JRecordField, JClosureField;
-import '../js_model/locals.dart' show JLocal;
+import '../js_model/locals.dart' show GlobalLocalsMap, JLocal;
 import '../world.dart' show JClosedWorld;
 
 import 'builder_kernel.dart';
@@ -33,7 +35,11 @@
   Map<Local, HInstruction> directLocals = new Map<Local, HInstruction>();
   Map<Local, FieldEntity> redirectionMapping = new Map<Local, FieldEntity>();
   final KernelSsaGraphBuilder builder;
-  ScopeInfo scopeInfo;
+
+  MemberEntity _scopeInfoMember;
+  ScopeInfo _scopeInfo;
+  KernelToLocalsMap _localsMap;
+
   Map<TypeVariableEntity, TypeVariableLocal> typeVariableLocals =
       new Map<TypeVariableEntity, TypeVariableLocal>();
   final Entity executableContext;
@@ -73,6 +79,9 @@
   GlobalTypeInferenceResults get _globalInferenceResults =>
       builder.globalInferenceResults;
 
+  GlobalLocalsMap get _globalLocalsMap =>
+      _globalInferenceResults.globalLocalsMap;
+
   /// Substituted type variables occurring in [type] into the context of
   /// [contextClass].
   DartType substInContext(DartType type) {
@@ -103,13 +112,33 @@
         memberContext = other.memberContext,
         instanceType = other.instanceType,
         builder = other.builder,
-        scopeInfo = other.scopeInfo,
+        _scopeInfo = other._scopeInfo,
+        _scopeInfoMember = other._scopeInfoMember,
+        _localsMap = other._localsMap,
         _nativeData = other._nativeData,
         _interceptorData = other._interceptorData,
         activationVariables = other.activationVariables,
         cachedTypeOfThis = other.cachedTypeOfThis,
         cachedTypesOfCapturedVariables = other.cachedTypesOfCapturedVariables;
 
+  /// Sets up the scope to use the scope and locals from [member].
+  void setupScope(MemberEntity member) {
+    _scopeInfoMember = member;
+    if (member != null) {
+      _scopeInfo = _closedWorld.closureDataLookup.getScopeInfo(member);
+      _localsMap = _globalLocalsMap.getLocalsMap(member);
+    } else {
+      _scopeInfo = null;
+      _localsMap = null;
+    }
+  }
+
+  /// Returns the member that currently defines the scope as setup in
+  /// [setupScope].
+  MemberEntity get scopeMember => _scopeInfoMember;
+
+  Local get thisLocal => _scopeInfo.thisLocal;
+
   /// Redirects accesses from element [from] to element [to]. The [to] element
   /// must be a boxed variable or a variable that is stored in a closure-field.
   void redirectElement(Local from, FieldEntity to) {
@@ -139,15 +168,15 @@
       // The box is passed as a parameter to a generative constructor body.
       box = inlinedBox ??
           builder.addParameter(
-              closureInfo.context, _abstractValueDomain.nonNullType);
+              closureInfo.contextBox, _abstractValueDomain.nonNullType);
     } else {
       box = createBox(sourceInformation);
     }
     // Add the box to the known locals.
-    directLocals[closureInfo.context] = box;
+    directLocals[closureInfo.contextBox] = box;
     // Make sure that accesses to the boxed locals go into the box. We also
     // need to make sure that parameters are copied into the box if necessary.
-    closureInfo.forEachBoxedVariable((Local from, FieldEntity to) {
+    closureInfo.forEachBoxedVariable(_localsMap, (Local from, FieldEntity to) {
       // The [from] can only be a parameter for function-scopes and not
       // loop scopes.
       JLocal jFrom = from;
@@ -192,19 +221,17 @@
   /// Documentation wanted -- johnniwinther
   ///
   /// Invariant: [function] must be an implementation element.
-  void startFunction(
-      MemberEntity element,
-      ScopeInfo scopeInfo,
-      CapturedScope scopeData,
-      Map<Local, AbstractValue> parameters,
-      Set<Local> elidedParameters,
-      SourceInformation sourceInformation,
+  void startFunction(MemberEntity element, Map<Local, AbstractValue> parameters,
+      Set<Local> elidedParameters, SourceInformation sourceInformation,
       {bool isGenerativeConstructorBody}) {
-    this.scopeInfo = scopeInfo;
+    setupScope(element);
+
+    CapturedScope scopeData =
+        _closedWorld.closureDataLookup.getCapturedScope(element);
 
     parameters.forEach((Local local, AbstractValue typeMask) {
       if (isGenerativeConstructorBody) {
-        if (scopeData.isBoxedVariable(local)) {
+        if (scopeData.isBoxedVariable(_localsMap, local)) {
           // The parameter will be a field in the box passed as the
           // last parameter. So no need to have it.
           return;
@@ -223,12 +250,13 @@
     // When we remove the element model, we can just use the first check
     // (because the underlying elements won't all be *both* ScopeInfos and
     // ClosureRepresentationInfos).
+    ScopeInfo scopeInfo = _scopeInfo;
     if (scopeInfo is ClosureRepresentationInfo && scopeInfo.isClosure) {
       ClosureRepresentationInfo closureData = scopeInfo;
       // If the freeVariableMapping is not empty, then this function was a
       // nested closure that captures variables. Redirect the captured
       // variables to fields in the closure.
-      closureData.forEachFreeVariable((Local from, FieldEntity to) {
+      closureData.forEachFreeVariable(_localsMap, (Local from, FieldEntity to) {
         redirectElement(from, to);
       });
       // Inside closure redirect references to itself to [:this:].
@@ -236,7 +264,7 @@
           new HThis(closureData.thisLocal, _abstractValueDomain.nonNullType);
       builder.graph.thisInstruction = thisInstruction;
       builder.graph.entry.addAtEntry(thisInstruction);
-      updateLocal(closureData.closureEntity, thisInstruction);
+      updateLocal(closureData.getClosureEntity(_localsMap), thisInstruction);
     } else if (element.isInstanceMember) {
       // Once closures have been mapped to classes their instance members might
       // not have any thisElement if the closure was created inside a static
@@ -294,13 +322,13 @@
   bool isAccessedDirectly(Local local) {
     assert(local != null);
     return !redirectionMapping.containsKey(local) &&
-        !scopeInfo.localIsUsedInTryOrSync(local);
+        !_scopeInfo.localIsUsedInTryOrSync(_localsMap, local);
   }
 
   bool isStoredInClosureField(Local local) {
     assert(local != null);
     if (isAccessedDirectly(local)) return false;
-    if (scopeInfo is! ClosureRepresentationInfo) return false;
+    if (_scopeInfo is! ClosureRepresentationInfo) return false;
     FieldEntity redirectTarget = redirectionMapping[local];
     if (redirectTarget == null) return false;
     return redirectTarget is JClosureField;
@@ -313,7 +341,7 @@
   }
 
   bool _isUsedInTryOrGenerator(Local local) {
-    return scopeInfo.localIsUsedInTryOrSync(local);
+    return _scopeInfo.localIsUsedInTryOrSync(_localsMap, local);
   }
 
   /// Returns an [HInstruction] for the given element. If the element is
@@ -341,9 +369,10 @@
       }
       return value;
     } else if (isStoredInClosureField(local)) {
-      ClosureRepresentationInfo closureData = scopeInfo;
+      ClosureRepresentationInfo closureData = _scopeInfo;
       FieldEntity redirect = redirectionMapping[local];
-      HInstruction receiver = readLocal(closureData.closureEntity);
+      HInstruction receiver =
+          readLocal(closureData.getClosureEntity(_localsMap));
       AbstractValue type = local is BoxLocal
           ? _abstractValueDomain.nonNullType
           : getTypeOfCapturedVariable(redirect);
@@ -381,7 +410,7 @@
 
   HInstruction readThis({SourceInformation sourceInformation}) {
     HInstruction res =
-        readLocal(scopeInfo.thisLocal, sourceInformation: sourceInformation);
+        readLocal(_scopeInfo.thisLocal, sourceInformation: sourceInformation);
     if (res.instructionType == null) {
       res.instructionType = getTypeOfThis();
     }
@@ -514,7 +543,7 @@
     savedDirectLocals.forEach((Local local, HInstruction instruction) {
       if (isAccessedDirectly(local)) {
         // We know 'this' cannot be modified.
-        if (local != scopeInfo.thisLocal) {
+        if (local != _scopeInfo.thisLocal) {
           HPhi phi = new HPhi.singleInput(
               local, instruction, _abstractValueDomain.dynamicType);
           loopEntry.addPhi(phi);
@@ -543,8 +572,8 @@
     // In all other cases a new box will be created when entering the body of
     // the next iteration.
     if (loopInfo.hasBoxedLoopVariables) {
-      updateCaptureBox(
-          loopInfo.context, loopInfo.boxedLoopVariables, sourceInformation);
+      updateCaptureBox(loopInfo.contextBox,
+          loopInfo.getBoxedLoopVariables(_localsMap), sourceInformation);
     }
   }
 
@@ -574,7 +603,7 @@
     Map<Local, HInstruction> joinedLocals = new Map<Local, HInstruction>();
     otherLocals.directLocals.forEach((Local local, HInstruction instruction) {
       // We know 'this' cannot be modified.
-      if (local == scopeInfo.thisLocal) {
+      if (local == _scopeInfo.thisLocal) {
         assert(directLocals[local] == instruction);
         joinedLocals[local] = instruction;
       } else {
@@ -607,7 +636,7 @@
     Map<Local, HInstruction> joinedLocals = new Map<Local, HInstruction>();
     HInstruction thisValue = null;
     directLocals.forEach((Local local, HInstruction instruction) {
-      if (local != scopeInfo.thisLocal) {
+      if (local != _scopeInfo.thisLocal) {
         HPhi phi = new HPhi.noInputs(local, _abstractValueDomain.dynamicType);
         joinedLocals[local] = phi;
         joinBlock.addPhi(phi);
@@ -628,13 +657,13 @@
     }
     if (thisValue != null) {
       // If there was a "this" for the scope, add it to the new locals.
-      joinedLocals[scopeInfo.thisLocal] = thisValue;
+      joinedLocals[_scopeInfo.thisLocal] = thisValue;
     }
 
     // Remove locals that are not in all handlers.
     directLocals = new Map<Local, HInstruction>();
     joinedLocals.forEach((Local local, HInstruction instruction) {
-      if (local != scopeInfo.thisLocal &&
+      if (local != _scopeInfo.thisLocal &&
           instruction.inputs.length != localsHandlers.length) {
         joinBlock.removePhi(instruction);
       } else {
@@ -649,7 +678,7 @@
   AbstractValue getTypeOfThis() {
     AbstractValue result = cachedTypeOfThis;
     if (result == null) {
-      ThisLocal local = scopeInfo.thisLocal;
+      ThisLocal local = _scopeInfo.thisLocal;
       ClassEntity cls = local.enclosingClass;
       if (_closedWorld.isUsedAsMixin(cls)) {
         // If the enclosing class is used as a mixin, [:this:] can be
diff --git a/pkg/compiler/lib/src/world.dart b/pkg/compiler/lib/src/world.dart
index fc7f564..68effb3 100644
--- a/pkg/compiler/lib/src/world.dart
+++ b/pkg/compiler/lib/src/world.dart
@@ -26,7 +26,6 @@
 import 'js_backend/native_data.dart' show NativeData;
 import 'js_backend/no_such_method_registry.dart' show NoSuchMethodData;
 import 'js_backend/runtime_types_resolution.dart' show RuntimeTypesNeed;
-import 'js_model/locals.dart';
 import 'js_emitter/sorter.dart';
 import 'universe/class_hierarchy.dart';
 import 'universe/member_usage.dart';
@@ -78,7 +77,6 @@
 
   AnnotationsData get annotationsData;
 
-  GlobalLocalsMap get globalLocalsMap;
   ClosureData get closureDataLookup;
 
   OutputUnitData get outputUnitData;
diff --git a/pkg/compiler/test/closure/closure_test.dart b/pkg/compiler/test/closure/closure_test.dart
index 0701d19..231025d 100644
--- a/pkg/compiler/test/closure/closure_test.dart
+++ b/pkg/compiler/test/closure/closure_test.dart
@@ -38,7 +38,8 @@
       {bool verbose: false}) {
     JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
     JsToElementMap elementMap = closedWorld.elementMap;
-    GlobalLocalsMap localsMap = closedWorld.globalLocalsMap;
+    GlobalLocalsMap localsMap =
+        compiler.globalInference.resultsForTesting.globalLocalsMap;
     ClosureData closureDataLookup = closedWorld.closureDataLookup;
     MemberDefinition definition = elementMap.getMemberDefinition(member);
     assert(
@@ -158,7 +159,7 @@
     capturedScopeStack =
         capturedScopeStack.prepend(closureDataLookup.getCapturedScope(member));
     if (capturedScope.requiresContextBox) {
-      boxNames[capturedScope.context] = 'box${boxNames.length}';
+      boxNames[capturedScope.contextBox] = 'box${boxNames.length}';
     }
     dump(member);
   }
@@ -174,7 +175,7 @@
     capturedScopeStack = capturedScopeStack
         .prepend(closureDataLookup.getCapturedLoopScope(node));
     if (capturedScope.requiresContextBox) {
-      boxNames[capturedScope.context] = 'box${boxNames.length}';
+      boxNames[capturedScope.contextBox] = 'box${boxNames.length}';
     }
     dump(node);
   }
@@ -199,37 +200,38 @@
     print('object: $object');
     if (object is MemberEntity) {
       print(' capturedScope (${capturedScope.runtimeType})');
-      capturedScope.forEachBoxedVariable((a, b) => print('  boxed: $a->$b'));
+      capturedScope.forEachBoxedVariable(
+          _localsMap, (a, b) => print('  boxed: $a->$b'));
     }
     print(
         ' closureRepresentationInfo (${closureRepresentationInfo.runtimeType})');
-    closureRepresentationInfo
-        ?.forEachFreeVariable((a, b) => print('  free: $a->$b'));
-    closureRepresentationInfo
-        ?.forEachBoxedVariable((a, b) => print('  boxed: $a->$b'));
+    closureRepresentationInfo?.forEachFreeVariable(
+        _localsMap, (a, b) => print('  free: $a->$b'));
+    closureRepresentationInfo?.forEachBoxedVariable(
+        _localsMap, (a, b) => print('  boxed: $a->$b'));
   }
 
   /// Compute a string representation of the data stored for [local] in [info].
   String computeLocalValue(Local local) {
     Features features = new Features();
-    if (scopeInfo.localIsUsedInTryOrSync(local)) {
+    if (scopeInfo.localIsUsedInTryOrSync(_localsMap, local)) {
       features.add('inTry');
       // TODO(johnniwinther,efortuna): Should this be enabled and checked?
       //Expect.isTrue(capturedScope.localIsUsedInTryOrSync(local));
     } else {
       //Expect.isFalse(capturedScope.localIsUsedInTryOrSync(local));
     }
-    if (capturedScope.isBoxedVariable(local)) {
+    if (capturedScope.isBoxedVariable(_localsMap, local)) {
       features.add('boxed');
     }
-    if (capturedScope.context == local) {
+    if (capturedScope.contextBox == local) {
       // TODO(johnniwinther): This shouldn't happen! Remove branch/throw error
       // when we verify it can't happen.
       features.add('error-box');
     }
     if (capturedScope is CapturedLoopScope) {
       CapturedLoopScope loopScope = capturedScope;
-      if (loopScope.boxedLoopVariables.contains(local)) {
+      if (loopScope.getBoxedLoopVariables(_localsMap).contains(local)) {
         features.add('loop');
       }
     }
@@ -240,9 +242,10 @@
   String computeObjectValue(MemberEntity member) {
     Features features = new Features();
 
-    void addLocals(String name, forEach(f(Local local, _))) {
+    void addLocals(
+        String name, forEach(KernelToLocalsMap localsMap, f(Local local, _))) {
       List<String> names = <String>[];
-      forEach((Local local, _) {
+      forEach(_localsMap, (Local local, _) {
         if (local is BoxLocal) {
           names.add(boxNames[local]);
         } else {
@@ -263,7 +266,7 @@
     if (capturedScope.requiresContextBox) {
       var keyword = 'boxed';
       addLocals(keyword, capturedScope.forEachBoxedVariable);
-      features['box'] = '(${boxNames[capturedScope.context]} which holds '
+      features['box'] = '(${boxNames[capturedScope.contextBox]} which holds '
           '${features[keyword]})';
       features.remove(keyword);
     }
@@ -271,12 +274,13 @@
     if (closureRepresentationInfo != null) {
       addLocals('free', closureRepresentationInfo.forEachFreeVariable);
       if (closureRepresentationInfo.closureClassEntity != null) {
-        addLocals('fields', (f(Local local, _)) {
+        addLocals('fields', (KernelToLocalsMap localsMap, f(Local local, _)) {
           _closedWorld.elementEnvironment.forEachInstanceField(
               closureRepresentationInfo.closureClassEntity,
               (_, FieldEntity field) {
             if (_closedWorld.fieldAnalysis.getFieldData(field).isElided) return;
-            f(closureRepresentationInfo.getLocalForField(field), field);
+            f(closureRepresentationInfo.getLocalForField(localsMap, field),
+                field);
           });
         });
       }
diff --git a/pkg/compiler/test/codegen/expect_annotations_test.dart b/pkg/compiler/test/codegen/expect_annotations_test.dart
index 9ed3517..a3df294 100644
--- a/pkg/compiler/test/codegen/expect_annotations_test.dart
+++ b/pkg/compiler/test/codegen/expect_annotations_test.dart
@@ -55,7 +55,7 @@
       AbstractValue expectedReturnType,
       GlobalTypeInferenceResults results) {
     closedWorld.elementEnvironment.forEachParameterAsLocal(
-        closedWorld.globalLocalsMap, function, (Local parameter) {
+        results.globalLocalsMap, function, (Local parameter) {
       AbstractValue type = results.resultOfParameter(parameter);
       Expect.equals(
           expectedParameterType, simplify(type, commonMasks), "$parameter");
diff --git a/pkg/compiler/test/codegen/type_inference8_test.dart b/pkg/compiler/test/codegen/type_inference8_test.dart
index 382ef35..252f2e4 100644
--- a/pkg/compiler/test/codegen/type_inference8_test.dart
+++ b/pkg/compiler/test/codegen/type_inference8_test.dart
@@ -62,7 +62,7 @@
   // the argument to 'bar' is always false
   MemberEntity bar = elementEnvironment.lookupLibraryMember(
       elementEnvironment.mainLibrary, 'bar');
-  elementEnvironment.forEachParameterAsLocal(closedWorld.globalLocalsMap, bar,
+  elementEnvironment.forEachParameterAsLocal(results.globalLocalsMap, bar,
       (barArg) {
     AbstractValue barArgMask = results.resultOfParameter(barArg);
     Expect.equals(falseType, barArgMask);
@@ -110,7 +110,7 @@
   Expect.identical(commonMasks.boolType, mask);
   MemberEntity bar = elementEnvironment.lookupLibraryMember(
       elementEnvironment.mainLibrary, 'bar');
-  elementEnvironment.forEachParameterAsLocal(closedWorld.globalLocalsMap, bar,
+  elementEnvironment.forEachParameterAsLocal(results.globalLocalsMap, bar,
       (barArg) {
     AbstractValue barArgMask = results.resultOfParameter(barArg);
     // The argument to bar should have the same type as the return type of foo
diff --git a/pkg/compiler/test/inference/inference_test_helper.dart b/pkg/compiler/test/inference/inference_test_helper.dart
index fa5feb0..d049559 100644
--- a/pkg/compiler/test/inference/inference_test_helper.dart
+++ b/pkg/compiler/test/inference/inference_test_helper.dart
@@ -52,7 +52,9 @@
       {bool verbose: false}) {
     JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
     JsToElementMap elementMap = closedWorld.elementMap;
-    GlobalLocalsMap localsMap = closedWorld.globalLocalsMap;
+    GlobalTypeInferenceResults results =
+        compiler.globalInference.resultsForTesting;
+    GlobalLocalsMap localsMap = results.globalLocalsMap;
     MemberDefinition definition = elementMap.getMemberDefinition(member);
     new TypeMaskIrComputer(
             compiler.reporter,
@@ -60,7 +62,7 @@
             elementMap,
             member,
             localsMap.getLocalsMap(member),
-            compiler.globalInference.resultsForTesting,
+            results,
             closedWorld.closureDataLookup)
         .run(definition.node);
   }
diff --git a/pkg/compiler/test/inference/load_deferred_library_test.dart b/pkg/compiler/test/inference/load_deferred_library_test.dart
index aad3724..8c6e15e 100644
--- a/pkg/compiler/test/inference/load_deferred_library_test.dart
+++ b/pkg/compiler/test/inference/load_deferred_library_test.dart
@@ -51,8 +51,9 @@
       helperLibrary, 'loadDeferredLibrary');
   TypeMask typeMask;
 
-  KernelToLocalsMap localsMap =
-      closedWorld.globalLocalsMap.getLocalsMap(loadDeferredLibrary);
+  KernelToLocalsMap localsMap = compiler
+      .globalInference.resultsForTesting.globalLocalsMap
+      .getLocalsMap(loadDeferredLibrary);
   MemberDefinition definition =
       closedWorld.elementMap.getMemberDefinition(loadDeferredLibrary);
   ir.Procedure procedure = definition.node;
diff --git a/pkg/compiler/test/jumps/jump_test.dart b/pkg/compiler/test/jumps/jump_test.dart
index 35b67d1..b848bd7 100644
--- a/pkg/compiler/test/jumps/jump_test.dart
+++ b/pkg/compiler/test/jumps/jump_test.dart
@@ -39,7 +39,8 @@
       {bool verbose: false}) {
     JsClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
     JsToElementMap elementMap = closedWorld.elementMap;
-    GlobalLocalsMap localsMap = closedWorld.globalLocalsMap;
+    GlobalLocalsMap localsMap =
+        compiler.globalInference.resultsForTesting.globalLocalsMap;
     MemberDefinition definition = elementMap.getMemberDefinition(member);
     new JumpsIrChecker(
             compiler.reporter, actualMap, localsMap.getLocalsMap(member))
diff --git a/tests/web/regress/scope_info_field_loop_test.dart b/tests/web/regress/scope_info_field_loop_test.dart
new file mode 100644
index 0000000..cf4c44b
--- /dev/null
+++ b/tests/web/regress/scope_info_field_loop_test.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2021, 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.
+
+// Regression test for failure caused by refactoring of handling of scope info
+// locals map in the ssa locals handler.
+
+class Class {
+  static const list = [];
+  var field = {for (final key in list) key: null};
+
+  Class(parameter) {
+    parameter;
+  }
+}
+
+main() {
+  new Class(null);
+}
diff --git a/tests/web_2/regress/scope_info_field_loop_test.dart b/tests/web_2/regress/scope_info_field_loop_test.dart
new file mode 100644
index 0000000..c756dfa
--- /dev/null
+++ b/tests/web_2/regress/scope_info_field_loop_test.dart
@@ -0,0 +1,21 @@
+// Copyright (c) 2021, 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.
+
+// Regression test for failure caused by refactoring of handling of scope info
+// locals map in the ssa locals handler.
+
+// @dart=2.9
+
+class Class {
+  static const list = [];
+  var field = {for (final key in list) key: null};
+
+  Class(parameter) {
+    parameter;
+  }
+}
+
+main() {
+  new Class(null);
+}
diff --git a/tools/VERSION b/tools/VERSION
index 7ebd6a0..4da2654 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 13
 PATCH 0
-PRERELEASE 8
+PRERELEASE 9
 PRERELEASE_PATCH 0
\ No newline at end of file