Refactor OneShotInterceptorData to prepare for modular codegen

Change-Id: I52d47c28978c0d935d07b438520728a304d311d6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102365
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart
index 213a74d..3d6e2ca 100644
--- a/pkg/compiler/lib/src/js_backend/backend.dart
+++ b/pkg/compiler/lib/src/js_backend/backend.dart
@@ -663,7 +663,9 @@
   CodegenInputs onCodegenStart(JClosedWorld closedWorld) {
     RuntimeTypeTags rtiTags = const RuntimeTypeTags();
     OneShotInterceptorData oneShotInterceptorData = new OneShotInterceptorData(
-        closedWorld.interceptorData, closedWorld.commonElements);
+        closedWorld.interceptorData,
+        closedWorld.commonElements,
+        closedWorld.nativeData);
     Tracer tracer = new Tracer(closedWorld, compiler.outputProvider);
     _rtiEncoder = new RuntimeTypesEncoderImpl(
         rtiTags,
diff --git a/pkg/compiler/lib/src/js_backend/interceptor_data.dart b/pkg/compiler/lib/src/js_backend/interceptor_data.dart
index 1d604c9..b63dae7 100644
--- a/pkg/compiler/lib/src/js_backend/interceptor_data.dart
+++ b/pkg/compiler/lib/src/js_backend/interceptor_data.dart
@@ -14,7 +14,7 @@
 import '../serialization/serialization.dart';
 import '../universe/selector.dart';
 import '../world.dart' show JClosedWorld;
-import 'namer.dart';
+import 'namer.dart' show ModularNamer, suffixForGetInterceptor;
 import 'native_data.dart';
 
 abstract class InterceptorData {
@@ -354,55 +354,79 @@
 class OneShotInterceptorData {
   final InterceptorData _interceptorData;
   final CommonElements _commonElements;
+  final NativeData _nativeData;
 
-  OneShotInterceptorData(this._interceptorData, this._commonElements);
+  OneShotInterceptorData(
+      this._interceptorData, this._commonElements, this._nativeData);
 
-  /// A collection of selectors that must have a one shot interceptor generated.
-  final Map<jsAst.Name, Selector> _oneShotInterceptors =
-      <jsAst.Name, Selector>{};
+  Iterable<OneShotInterceptor> get oneShotInterceptors {
+    List<OneShotInterceptor> interceptors = [];
+    for (var map in _oneShotInterceptors.values) {
+      interceptors.addAll(map.values);
+    }
+    return interceptors;
+  }
 
-  Selector getOneShotInterceptorSelector(jsAst.Name name) =>
-      _oneShotInterceptors[name];
+  Map<Selector, Map<String, OneShotInterceptor>> _oneShotInterceptors = {};
 
-  Iterable<jsAst.Name> get oneShotInterceptorNames =>
-      _oneShotInterceptors.keys.toList()..sort();
-
-  /// A map of specialized versions of the [getInterceptorMethod].
+  /// A set of specialized versions of the [getInterceptorMethod].
   ///
   /// Since [getInterceptorMethod] is a hot method at runtime, we're always
   /// specializing it based on the incoming type. The keys in the map are the
   /// names of these specialized versions. Note that the generic version that
   /// contains all possible type checks is also stored in this map.
-  final Map<jsAst.Name, Set<ClassEntity>> _specializedGetInterceptors =
-      <jsAst.Name, Set<ClassEntity>>{};
+  Iterable<SpecializedGetInterceptor> get specializedGetInterceptors =>
+      _specializedGetInterceptors.values;
 
-  Iterable<jsAst.Name> get specializedGetInterceptorNames =>
-      _specializedGetInterceptors.keys.toList()..sort();
-
-  Set<ClassEntity> getSpecializedGetInterceptorsFor(jsAst.Name name) =>
-      _specializedGetInterceptors[name];
+  Map<String, SpecializedGetInterceptor> _specializedGetInterceptors = {};
 
   jsAst.Name registerOneShotInterceptor(
-      Selector selector, Namer namer, JClosedWorld closedWorld) {
+      Selector selector, ModularNamer namer, JClosedWorld closedWorld) {
+    selector = selector.toNormalized();
     Set<ClassEntity> classes =
         _interceptorData.getInterceptedClassesOn(selector.name, closedWorld);
-    jsAst.Name name = namer.nameForGetOneShotInterceptor(selector, classes);
-    if (!_oneShotInterceptors.containsKey(name)) {
-      registerSpecializedGetInterceptor(classes, namer);
-      _oneShotInterceptors[name] = selector;
-    }
-    return name;
+    String key = suffixForGetInterceptor(_commonElements, _nativeData, classes);
+    Map<String, OneShotInterceptor> interceptors =
+        _oneShotInterceptors[selector] ??= {};
+    OneShotInterceptor interceptor = interceptors.putIfAbsent(
+        key, () => new OneShotInterceptor(key, selector));
+    interceptor.classes.addAll(classes);
+    registerSpecializedGetInterceptor(classes, namer);
+    return namer.nameForGetOneShotInterceptor(selector, classes);
   }
 
   void registerSpecializedGetInterceptor(
-      Set<ClassEntity> classes, Namer namer) {
-    jsAst.Name name = namer.nameForGetInterceptor(classes);
+      Set<ClassEntity> classes, ModularNamer namer) {
     if (classes.contains(_commonElements.jsInterceptorClass)) {
       // We can't use a specialized [getInterceptorMethod], so we make
       // sure we emit the one with all checks.
-      _specializedGetInterceptors[name] = _interceptorData.interceptedClasses;
-    } else {
-      _specializedGetInterceptors[name] = classes;
+      classes = _interceptorData.interceptedClasses;
     }
+    String key = suffixForGetInterceptor(_commonElements, _nativeData, classes);
+    SpecializedGetInterceptor interceptor =
+        _specializedGetInterceptors[key] ??= new SpecializedGetInterceptor(key);
+    interceptor.classes.addAll(classes);
   }
 }
+
+class OneShotInterceptor {
+  final Selector selector;
+  final String key;
+  final Set<ClassEntity> classes = {};
+
+  OneShotInterceptor(this.key, this.selector);
+
+  @override
+  String toString() =>
+      'OneShotInterceptor(selector=$selector,key=$key,classes=$classes)';
+}
+
+class SpecializedGetInterceptor {
+  final String key;
+  final Set<ClassEntity> classes = {};
+
+  SpecializedGetInterceptor(this.key);
+
+  @override
+  String toString() => 'SpecializedGetInterceptor(key=$key,classes=$classes)';
+}
diff --git a/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart
index eeecd6ff..495613f 100644
--- a/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/interceptor_stub_generator.dart
@@ -31,7 +31,6 @@
   final Emitter _emitter;
   final NativeCodegenEnqueuer _nativeCodegenEnqueuer;
   final Namer _namer;
-  final OneShotInterceptorData _oneShotInterceptorData;
   final CustomElementsCodegenAnalysis _customElementsCodegenAnalysis;
   final CodegenWorld _codegenWorld;
   final JClosedWorld _closedWorld;
@@ -42,7 +41,6 @@
       this._emitter,
       this._nativeCodegenEnqueuer,
       this._namer,
-      this._oneShotInterceptorData,
       this._customElementsCodegenAnalysis,
       this._codegenWorld,
       this._closedWorld);
@@ -51,7 +49,10 @@
 
   InterceptorData get _interceptorData => _closedWorld.interceptorData;
 
-  jsAst.Expression generateGetInterceptorMethod(Set<ClassEntity> classes) {
+  jsAst.Expression generateGetInterceptorMethod(
+      SpecializedGetInterceptor interceptor) {
+    Set<ClassEntity> classes = interceptor.classes;
+
     jsAst.Expression interceptorFor(ClassEntity cls) {
       return _emitter.interceptorPrototypeAccess(cls);
     }
@@ -374,11 +375,9 @@
     return null;
   }
 
-  jsAst.Expression generateOneShotInterceptor(jsAst.Name name) {
-    Selector selector =
-        _oneShotInterceptorData.getOneShotInterceptorSelector(name);
-    Set<ClassEntity> classes =
-        _interceptorData.getInterceptedClassesOn(selector.name, _closedWorld);
+  jsAst.Expression generateOneShotInterceptor(OneShotInterceptor interceptor) {
+    Selector selector = interceptor.selector;
+    Set<ClassEntity> classes = interceptor.classes;
     jsAst.Name getInterceptorName = _namer.nameForGetInterceptor(classes);
 
     List<String> parameterNames = <String>[];
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart b/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart
index b2c2743..7b49849f 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/collector.dart
@@ -85,10 +85,9 @@
     // Go over specialized interceptors and then constants to know which
     // interceptors are needed.
     Set<ClassEntity> needed = new Set<ClassEntity>();
-    for (js.Name name
-        in _oneShotInterceptorData.specializedGetInterceptorNames) {
-      needed.addAll(
-          _oneShotInterceptorData.getSpecializedGetInterceptorsFor(name));
+    for (SpecializedGetInterceptor interceptor
+        in _oneShotInterceptorData.specializedGetInterceptors) {
+      needed.addAll(interceptor.classes);
     }
 
     // Add interceptors referenced by constants.
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index 0ce8bde..10ebf1d 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -345,7 +345,6 @@
         _task.emitter,
         _nativeCodegenEnqueuer,
         _namer,
-        _oneShotInterceptorData,
         _customElementsCodegenAnalysis,
         _codegenWorld,
         _closedWorld);
@@ -1002,11 +1001,10 @@
   // We must evaluate these classes eagerly so that the prototype is
   // accessible.
   void _markEagerInterceptorClasses() {
-    Iterable<js.Name> names =
-        _oneShotInterceptorData.specializedGetInterceptorNames;
-    for (js.Name name in names) {
-      for (ClassEntity element
-          in _oneShotInterceptorData.getSpecializedGetInterceptorsFor(name)) {
+    Iterable<SpecializedGetInterceptor> interceptors =
+        _oneShotInterceptorData.specializedGetInterceptors;
+    for (SpecializedGetInterceptor interceptor in interceptors) {
+      for (ClassEntity element in interceptor.classes) {
         Class cls = _classes[element];
         if (cls != null) cls.isEager = true;
       }
@@ -1020,7 +1018,6 @@
         _task.emitter,
         _nativeCodegenEnqueuer,
         _namer,
-        _oneShotInterceptorData,
         _customElementsCodegenAnalysis,
         _codegenWorld,
         _closedWorld);
@@ -1030,13 +1027,23 @@
     // TODO(floitsch): we shouldn't update the registry in the middle of
     // generating the interceptor methods.
     Holder holder = _registry.registerHolder(holderName);
-
-    Iterable<js.Name> names =
-        _oneShotInterceptorData.specializedGetInterceptorNames;
+    List<js.Name> names = [];
+    Map<js.Name, SpecializedGetInterceptor> interceptorMap = {};
+    for (SpecializedGetInterceptor interceptor
+        in _oneShotInterceptorData.specializedGetInterceptors) {
+      js.Name name = _namer.nameForGetInterceptor(interceptor.classes);
+      names.add(name);
+      assert(
+          !interceptorMap.containsKey(name),
+          "Duplicate specialized get interceptor for $name: Existing: "
+          "${interceptorMap[name]}, new ${interceptor}.");
+      interceptorMap[name] = interceptor;
+    }
+    names.sort();
     return names.map((js.Name name) {
-      Set<ClassEntity> classes =
-          _oneShotInterceptorData.getSpecializedGetInterceptorsFor(name);
-      js.Expression code = stubGenerator.generateGetInterceptorMethod(classes);
+      SpecializedGetInterceptor interceptor = interceptorMap[name];
+      js.Expression code =
+          stubGenerator.generateGetInterceptorMethod(interceptor);
       return new StaticStubMethod(name, holder, code);
     });
   }
@@ -1116,7 +1123,6 @@
         _task.emitter,
         _nativeCodegenEnqueuer,
         _namer,
-        _oneShotInterceptorData,
         _customElementsCodegenAnalysis,
         _codegenWorld,
         _closedWorld);
@@ -1126,10 +1132,24 @@
     // TODO(floitsch): we shouldn't update the registry in the middle of
     // generating the interceptor methods.
     Holder holder = _registry.registerHolder(holderName);
-
-    List<js.Name> names = _oneShotInterceptorData.oneShotInterceptorNames;
+    List<js.Name> names = [];
+    Map<js.Name, OneShotInterceptor> interceptorMap = {};
+    for (OneShotInterceptor interceptor
+        in _oneShotInterceptorData.oneShotInterceptors) {
+      js.Name name = _namer.nameForGetOneShotInterceptor(
+          interceptor.selector, interceptor.classes);
+      names.add(name);
+      assert(
+          !interceptorMap.containsKey(name),
+          "Duplicate specialized get interceptor for $name: Existing: "
+          "${interceptorMap[name]}, new ${interceptor}.");
+      interceptorMap[name] = interceptor;
+    }
+    names.sort();
     return names.map((js.Name name) {
-      js.Expression code = stubGenerator.generateOneShotInterceptor(name);
+      OneShotInterceptor interceptor = interceptorMap[name];
+      js.Expression code =
+          stubGenerator.generateOneShotInterceptor(interceptor);
       return new StaticStubMethod(name, holder, code);
     });
   }
diff --git a/pkg/compiler/lib/src/universe/call_structure.dart b/pkg/compiler/lib/src/universe/call_structure.dart
index 8c148c8..5410034 100644
--- a/pkg/compiler/lib/src/universe/call_structure.dart
+++ b/pkg/compiler/lib/src/universe/call_structure.dart
@@ -67,6 +67,15 @@
     sink.end(tag);
   }
 
+  /// Returns `true` if this call structure is normalized, that is, its named
+  /// arguments are sorted.
+  bool get isNormalized => true;
+
+  /// Returns the normalized version of this call structure.
+  ///
+  /// A [CallStructure] is normalized if its named arguments are sorted.
+  CallStructure toNormalized() => this;
+
   CallStructure withTypeArgumentCount(int typeArgumentCount) =>
       new CallStructure(argumentCount, namedArguments, typeArgumentCount);
 
@@ -185,17 +194,21 @@
   }
 }
 
-///
+/// Call structure with named arguments.
 class NamedCallStructure extends CallStructure {
   @override
   final List<String> namedArguments;
-  final List<String> _orderedNamedArguments = <String>[];
+  final List<String> _orderedNamedArguments;
 
   NamedCallStructure(
-      int argumentCount, this.namedArguments, int typeArgumentCount)
-      : super.unnamed(argumentCount, typeArgumentCount) {
-    assert(namedArguments.isNotEmpty);
-  }
+      int argumentCount, List<String> namedArguments, int typeArgumentCount)
+      : this.internal(
+            argumentCount, namedArguments, typeArgumentCount, <String>[]);
+
+  NamedCallStructure.internal(int argumentCount, this.namedArguments,
+      int typeArgumentCount, this._orderedNamedArguments)
+      : assert(namedArguments.isNotEmpty),
+        super.unnamed(argumentCount, typeArgumentCount);
 
   @override
   bool get isNamed => true;
@@ -210,6 +223,16 @@
   int get positionalArgumentCount => argumentCount - namedArgumentCount;
 
   @override
+  bool get isNormalized => namedArguments == _orderedNamedArguments;
+
+  @override
+  CallStructure toNormalized() => new NamedCallStructure.internal(
+      argumentCount,
+      getOrderedNamedArguments(),
+      typeArgumentCount,
+      getOrderedNamedArguments());
+
+  @override
   List<String> getOrderedNamedArguments() {
     if (!_orderedNamedArguments.isEmpty) return _orderedNamedArguments;
 
diff --git a/pkg/compiler/lib/src/universe/selector.dart b/pkg/compiler/lib/src/universe/selector.dart
index 2f70e16..20f8391 100644
--- a/pkg/compiler/lib/src/universe/selector.dart
+++ b/pkg/compiler/lib/src/universe/selector.dart
@@ -315,6 +315,15 @@
     return 'Selector($kind, $name, ${callStructure.structureToString()})';
   }
 
+  /// Returns the normalized version of this selector.
+  ///
+  /// A selector is normalized if its call structure is normalized.
+  // TODO(johnniwinther): Use normalized selectors as much as possible,
+  // especially where selectors are used in sets or as keys in maps.
+  Selector toNormalized() => callStructure.isNormalized
+      ? this
+      : new Selector(kind, memberName, callStructure.toNormalized());
+
   Selector toCallSelector() => new Selector.callClosureFrom(this);
 
   /// Returns the non-generic [Selector] corresponding to this selector.