[dart2wasm] Save load IDs on serialized component.

Allows load ID assignments to be preserved when dart2wasm is being used
with phases.

Change-Id: I610fb56c46e0b52c1b73293f1d3fb8932f0d9434
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/456220
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/deferred_loading.dart b/pkg/dart2wasm/lib/deferred_loading.dart
index b8f9a68..3e68215 100644
--- a/pkg/dart2wasm/lib/deferred_loading.dart
+++ b/pkg/dart2wasm/lib/deferred_loading.dart
@@ -86,7 +86,6 @@
   final WasmCompilerOptions options;
   final WasmTarget kernelTarget;
   final CoreTypes coreTypes;
-  final Map<String, Map<String, String>> loadIdMap = {};
   late final ModuleOutputData moduleOutputData;
 
   DeferredLoadingModuleStrategy(
@@ -95,7 +94,7 @@
   @override
   void prepareComponent() {
     if (options.loadsIdsUri != null) {
-      component.accept(_DeferredLoadingLoadIdTransformer(coreTypes, loadIdMap));
+      component.accept(_DeferredLoadingLoadIdTransformer(component, coreTypes));
     }
   }
 
@@ -142,8 +141,7 @@
       moduleMap[enclosingLibrary] = outputMapping;
     });
 
-    await _initDeferredImports(
-        component, coreTypes, options, loadIdMap, moduleMap);
+    await _initDeferredImports(component, coreTypes, options, moduleMap);
 
     moduleOutputData =
         ModuleOutputData([mainModule, ...rootSetToModule.values]);
@@ -229,7 +227,6 @@
   final WasmTarget kernelTarget;
   final ClassHierarchy classHierarchy;
   final WasmCompilerOptions options;
-  final Map<String, Map<String, String>> loadIdMap = {};
   late final ModuleOutputData moduleOutputData;
 
   /// We load all 'dart:*' libraries since just doing the deferred load of modules
@@ -287,7 +284,7 @@
         [invokeMain.enclosingLibrary], classHierarchy, coreTypes);
 
     if (options.loadsIdsUri != null) {
-      component.accept(_DeferredLoadingLoadIdTransformer(coreTypes, loadIdMap));
+      component.accept(_DeferredLoadingLoadIdTransformer(component, coreTypes));
     }
   }
 
@@ -310,7 +307,7 @@
       importMap[importName] = [module];
     }
 
-    await _initDeferredImports(component, coreTypes, options, loadIdMap,
+    await _initDeferredImports(component, coreTypes, options,
         {coreTypes.index.getLibrary('dart:_internal'): importMap});
 
     moduleOutputData = ModuleOutputData([mainModule, ...modules]);
@@ -324,13 +321,11 @@
     Component component,
     CoreTypes coreTypes,
     WasmCompilerOptions options,
-    Map<String, Map<String, String>> loadIdMap,
     Map<Library, Map<String, List<ModuleMetadata>>> moduleMap) async {
   if (options.loadsIdsUri == null) {
     _initDeferredImportsPrefix(component, coreTypes, options, moduleMap);
   } else {
-    await _initDeferredImportsLoadIds(
-        component, coreTypes, options, loadIdMap, moduleMap);
+    await _initDeferredImportsLoadIds(component, coreTypes, options, moduleMap);
   }
 }
 
@@ -377,34 +372,38 @@
     Component component,
     CoreTypes coreTypes,
     WasmCompilerOptions options,
-    Map<String, Map<String, String>> loadIdMap,
     Map<Library, Map<String, List<ModuleMetadata>>> moduleMap) async {
-  await (File.fromUri(options.loadsIdsUri!)..createSync()).writeAsString(
-      _generateDeferredMapJson(component.mainMethod!.enclosingLibrary.importUri,
-          moduleMap, loadIdMap));
+  final file = File.fromUri(options.loadsIdsUri!);
+  await file.create(recursive: true);
+  await file.writeAsString(
+    _generateDeferredMapJson(
+        component, component.mainMethod!.enclosingLibrary.importUri, moduleMap),
+  );
 }
 
-String _generateDeferredMapJson(
-    Uri rootLibraryUri,
-    Map<Library, Map<String, List<ModuleMetadata>>> moduleMap,
-    Map<String, Map<String, String>> loadIds) {
+String _generateDeferredMapJson(Component component, Uri rootLibraryUri,
+    Map<Library, Map<String, List<ModuleMetadata>>> moduleMap) {
+  final loadIdRepo =
+      component.metadata[LoadIdRepository._tag] as LoadIdRepository;
   final output = <String, dynamic>{};
-  moduleMap.forEach((library, imports) {
+  loadIdRepo.mapping.forEach((dep, loadId) {
+    final loadIdStr = '$loadId';
+    final prefix = dep.name!;
+    final library = dep.enclosingLibrary;
+    final modules = moduleMap[library]?[prefix];
+
+    // Can be null if the library or import was tree-shaken by TFA.
+    if (modules == null) return;
+
     final libOutput =
-        output[relativizeUri(rootLibraryUri, library.importUri, false)] = {};
-    libOutput['name'] = library.name ?? '<unnamed>';
-    final libImports = libOutput['imports'] = <String, List<String>>{};
-    final libPrefixMappings =
-        libOutput['importPrefixToLoadId'] = <String, String>{};
-    final prefixLoadIds = loadIds['${library.importUri}']!;
-    imports.forEach((prefix, modules) {
-      final loadId = prefixLoadIds[prefix]!;
-      final prefixImports = libImports[loadId] = <String>[];
-      libPrefixMappings[prefix] = loadId;
-      for (final module in modules) {
-        prefixImports.add(module.moduleName);
-      }
-    });
+        output[relativizeUri(rootLibraryUri, library.importUri, false)] ??= {
+      'name': library.name ?? '<unnamed>',
+      'imports': <String, List<String>>{},
+      'importPrefixToLoadId': <String, String>{},
+    };
+
+    libOutput['imports']![loadIdStr] = [...modules.map((m) => m.moduleName)];
+    libOutput['importPrefixToLoadId'][prefix] = loadIdStr;
   });
 
   return const JsonEncoder.withIndent('  ').convert(output);
@@ -415,10 +414,11 @@
   final Procedure? _checkLibraryIsLoaded;
   final Procedure _loadLibraryFromLoadId;
   final Procedure _checkLibraryIsLoadedFromLoadId;
-  final Map<String, Map<String, String>> loadIdCollector;
-  int loadIdCounter = 1;
+  final LoadIdRepository _loadIdRepository = LoadIdRepository();
+  Map<String, int> _libraryLoadIds = {};
+  int _loadIdCounter = 1;
 
-  _DeferredLoadingLoadIdTransformer(CoreTypes coreTypes, this.loadIdCollector)
+  _DeferredLoadingLoadIdTransformer(Component component, CoreTypes coreTypes)
       : _loadLibrary = coreTypes.index.tryGetProcedure(
             'dart:_internal', LibraryIndex.topLevel, 'loadLibrary'),
         _checkLibraryIsLoaded = coreTypes.index.tryGetProcedure(
@@ -426,17 +426,32 @@
         _loadLibraryFromLoadId = coreTypes.index
             .getTopLevelProcedure('dart:_internal', 'loadLibraryFromLoadId'),
         _checkLibraryIsLoadedFromLoadId = coreTypes.index.getTopLevelProcedure(
-            'dart:_internal', 'checkLibraryIsLoadedFromLoadId');
+            'dart:_internal', 'checkLibraryIsLoadedFromLoadId') {
+    component.addMetadataRepository(_loadIdRepository);
+  }
+
+  @override
+  TreeNode visitLibrary(Library node) {
+    // Assign a load ID to each deferred import.
+    _libraryLoadIds = {};
+    for (final dep in node.dependencies) {
+      if (!dep.isDeferred) continue;
+      final loadId = _loadIdCounter++;
+      _loadIdRepository.mapping[dep] = loadId;
+      _libraryLoadIds[dep.name!] = loadId;
+    }
+
+    // Don't visit this library if there are no deferred imports.
+    return _libraryLoadIds.isEmpty ? node : super.visitLibrary(node);
+  }
 
   @override
   TreeNode visitStaticInvocation(StaticInvocation node) {
     if (node.target != _loadLibrary && node.target != _checkLibraryIsLoaded) {
       return super.visitStaticInvocation(node);
     }
-    final [libraryName as StringLiteral, importPrefix as StringLiteral] =
-        node.arguments.positional;
-    final loadId = (loadIdCollector[libraryName.value] ??=
-        {})[importPrefix.value] ??= '${loadIdCounter++}';
+    final [_, importPrefix as StringLiteral] = node.arguments.positional;
+    final loadId = '${_libraryLoadIds[importPrefix.value]!}';
     return StaticInvocation(
         node.target == _loadLibrary
             ? _loadLibraryFromLoadId
@@ -444,3 +459,24 @@
         Arguments([StringLiteral(loadId)]));
   }
 }
+
+/// Contains load IDs for each deferred [LibraryDependency] in the component.
+class LoadIdRepository extends MetadataRepository<int> {
+  static const String _tag = 'dart2wasm.loadIds';
+
+  @override
+  final Map<LibraryDependency, int> mapping = {};
+
+  @override
+  int readFromBinary(Node node, BinarySource source) {
+    return source.readUInt30();
+  }
+
+  @override
+  String get tag => _tag;
+
+  @override
+  void writeToBinary(int metadata, Node node, BinarySink sink) {
+    sink.writeUInt30(metadata);
+  }
+}
diff --git a/pkg/dart2wasm/lib/util.dart b/pkg/dart2wasm/lib/util.dart
index 88ea5ff..915e876 100644
--- a/pkg/dart2wasm/lib/util.dart
+++ b/pkg/dart2wasm/lib/util.dart
@@ -17,6 +17,8 @@
 import 'package:vm/metadata/table_selector.dart'
     show TableSelectorMetadataRepository;
 
+import 'deferred_loading.dart' show LoadIdRepository;
+
 bool hasPragma(CoreTypes coreTypes, Annotatable node, String name) {
   return getPragma(coreTypes, node, name, defaultValue: '') != null;
 }
@@ -80,6 +82,7 @@
 
 Component createEmptyComponent() {
   return Component()
+    ..addMetadataRepository(LoadIdRepository())
     ..addMetadataRepository(ProcedureAttributesMetadataRepository())
     ..addMetadataRepository(TableSelectorMetadataRepository())
     ..addMetadataRepository(DirectCallMetadataRepository())