[dart2wasm] Add deferred loading support to dart2wasm (6/X).

Wasm globals serve a few purposes in the compiler such as storing static fields and closure vtables. Sometimes the access of these globals will be from a different module than the ones they're defined in. We need some indirection to be able to access them in these cross-module situations.

This change adds getter and setter functions that can be called via the StaticTable when a global needs to be accessed from a different module.

We use References to track the owning module for each global to determine if we can access it directly or not.

Change-Id: I93191c83dee1b7a47171c5808e64b071479cdeea
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381324
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/dart2wasm/lib/async.dart b/pkg/dart2wasm/lib/async.dart
index 2918d60..7aba7ab 100644
--- a/pkg/dart2wasm/lib/async.dart
+++ b/pkg/dart2wasm/lib/async.dart
@@ -30,7 +30,8 @@
         b.addLocal(w.RefType(asyncSuspendStateInfo.struct, nullable: false));
 
     // AsyncResumeFun _resume
-    b.global_get(translator.makeFunctionRef(resumeFun));
+    translator.globals
+        .readGlobal(b, translator.makeFunctionRef(b.module, resumeFun));
 
     // WasmStructRef? _context
     if (context != null) {
diff --git a/pkg/dart2wasm/lib/closures.dart b/pkg/dart2wasm/lib/closures.dart
index b29405b..690561d 100644
--- a/pkg/dart2wasm/lib/closures.dart
+++ b/pkg/dart2wasm/lib/closures.dart
@@ -31,8 +31,11 @@
   /// The constant global variable pointing to the vtable.
   final w.Global vtable;
 
-  ClosureImplementation(
-      this.representation, this.functions, this.dynamicCallEntry, this.vtable);
+  /// The module this closure is implemented in.
+  final w.ModuleBuilder module;
+
+  ClosureImplementation(this.representation, this.functions,
+      this.dynamicCallEntry, this.vtable, this.module);
 }
 
 /// Describes the representation of closures for a particular function
@@ -58,27 +61,120 @@
   /// The struct type for the context of an instantiated closure.
   final w.StructType? instantiationContextStruct;
 
+  final String exportSuffix;
+
   /// Entry point functions for instantiations of this generic closure.
-  late final List<w.BaseFunction> instantiationTrampolines =
+  late final List<w.BaseFunction> _instantiationTrampolines =
       _instantiationTrampolinesThunk!();
   List<w.BaseFunction> Function()? _instantiationTrampolinesThunk;
+  final Map<w.ModuleBuilder, List<w.BaseFunction>>
+      _instantiationTrampolinesImports = {};
+  late final List<String> _instantiationTrampolineImportNames = List.generate(
+      _instantiationTrampolines.length,
+      (i) => 'instantiationTrampoline-$i-$exportSuffix');
+  List<w.BaseFunction> _instantiationTrampolinesForModule(
+      Translator translator, w.ModuleBuilder module) {
+    if (translator.isMainModule(module)) {
+      return _instantiationTrampolines;
+    }
+    if (_instantiationTrampolinesImports.isEmpty) {
+      for (int i = 0; i < _instantiationTrampolines.length; i++) {
+        final exportName = _instantiationTrampolineImportNames[i];
+
+        translator.mainModule.exports
+            .export(exportName, _instantiationTrampolines[i]);
+      }
+    }
+    return _instantiationTrampolinesImports.putIfAbsent(module, () {
+      final importedTrampolines = <w.BaseFunction>[];
+      for (var i = 0; i < _instantiationTrampolines.length; i++) {
+        final importName = _instantiationTrampolineImportNames[i];
+        final function = _instantiationTrampolines[i];
+        importedTrampolines.add(module.functions.import(
+            translator.nameForModule(translator.mainModule),
+            importName,
+            function.type));
+      }
+      return importedTrampolines;
+    });
+  }
 
   /// The function that instantiates this generic closure.
-  late final w.BaseFunction instantiationFunction =
+  late final w.BaseFunction _instantiationFunction =
       _instantiationFunctionThunk!();
   w.BaseFunction Function()? _instantiationFunctionThunk;
+  final Map<w.ModuleBuilder, w.BaseFunction> _instantiationFunctionImports = {};
+  late final _instantiationFunctionExportName =
+      'instantiationFunction-$exportSuffix';
+  w.BaseFunction instantiationFunctionForModule(
+      Translator translator, w.ModuleBuilder module) {
+    if (translator.isMainModule(module)) {
+      return _instantiationFunction;
+    }
+    if (_instantiationFunctionImports.isEmpty) {
+      translator.mainModule.exports
+          .export(_instantiationFunctionExportName, _instantiationFunction);
+    }
+    return _instantiationFunctionImports.putIfAbsent(module, () {
+      return module.functions.import(
+          translator.nameForModule(translator.mainModule),
+          _instantiationFunctionExportName,
+          _instantiationFunction.type);
+    });
+  }
 
   /// The function that takes instantiation context of this generic closure and
   /// another instantiation context (both as `ref
   /// #InstantiationClosureContextBase`) and compares types in the contexts.
   /// This function is used to implement function equality of instantiations.
-  late final w.BaseFunction instantiationTypeComparisonFunction =
+  late final w.BaseFunction _instantiationTypeComparisonFunction =
       _instantiationTypeComparisonFunctionThunk!();
   w.BaseFunction Function()? _instantiationTypeComparisonFunctionThunk;
+  final Map<w.ModuleBuilder, w.BaseFunction>
+      _instantiationTypeComparisonFunctionImports = {};
+  late final _instantiationTypeComparisonExportName =
+      'instantiationTypeComparison-$exportSuffix';
+  w.BaseFunction instantiationTypeComparisonFunctionForModule(
+      Translator translator, w.ModuleBuilder module) {
+    if (translator.isMainModule(module)) {
+      return _instantiationTypeComparisonFunction;
+    }
+    if (_instantiationTypeComparisonFunctionImports.isEmpty) {
+      translator.mainModule.exports.export(
+          _instantiationTypeComparisonExportName,
+          _instantiationTypeComparisonFunction);
+    }
+    return _instantiationTypeComparisonFunctionImports.putIfAbsent(module, () {
+      return module.functions.import(
+          translator.nameForModule(translator.mainModule),
+          _instantiationTypeComparisonExportName,
+          _instantiationTypeComparisonFunction.type);
+    });
+  }
 
-  late final w.BaseFunction instantiationTypeHashFunction =
+  late final w.BaseFunction _instantiationTypeHashFunction =
       _instantiationTypeHashFunctionThunk!();
   w.BaseFunction Function()? _instantiationTypeHashFunctionThunk;
+  final Map<w.ModuleBuilder, w.BaseFunction>
+      _instantiationTypeHashFunctionImports = {};
+  late final _instantiationTypeHashExportName =
+      'instantiationTypeHash-$exportSuffix';
+  w.BaseFunction instantiationTypeHashFunctionForModule(
+      Translator translator, w.ModuleBuilder module) {
+    if (translator.isMainModule(module)) {
+      return _instantiationTypeHashFunction;
+    }
+    if (_instantiationTypeHashFunctionImports.isEmpty) {
+      translator.mainModule.exports.export(
+          _instantiationTypeHashExportName, _instantiationTypeHashFunction);
+    }
+    return _instantiationTypeHashFunctionImports.putIfAbsent(module, () {
+      return module.functions.import(
+          translator.nameForModule(translator.mainModule),
+          _instantiationTypeHashExportName,
+          _instantiationTypeHashFunction.type);
+    });
+  }
 
   /// The signature of the function that instantiates this generic closure.
   w.FunctionType get instantiationFunctionType {
@@ -90,8 +186,13 @@
   w.FunctionType getVtableFieldType(int index) =>
       (vtableStruct.fields[index].type as w.RefType).heapType as w.FunctionType;
 
-  ClosureRepresentation(this.typeCount, this.vtableStruct, this.closureStruct,
-      this._indexOfCombination, this.instantiationContextStruct);
+  ClosureRepresentation(
+      this.typeCount,
+      this.vtableStruct,
+      this.closureStruct,
+      this._indexOfCombination,
+      this.instantiationContextStruct,
+      this.exportSuffix);
 
   bool get isGeneric => typeCount > 0;
 
@@ -214,7 +315,7 @@
                 mutable: false)),
           superType: vtableBaseStruct);
 
-  /// Type of [ClosureRepresentation.instantiationTypeComparisonFunction].
+  /// Type of [ClosureRepresentation._instantiationTypeComparisonFunction].
   late final w.FunctionType instantiationClosureTypeComparisonFunctionType =
       translator.typesBuilder.defineFunction(
     [
@@ -285,7 +386,6 @@
         superType: superType);
   }
 
-  w.ModuleBuilder get m => translator.m;
   w.ValueType get topType => translator.topInfo.nullableType;
 
   ClosureLayouter(this.translator)
@@ -326,15 +426,16 @@
   /// Get the representation for closures with a specific signature, described
   /// by the number of type parameters, the maximum number of positional
   /// parameters and the names of named parameters.
-  ClosureRepresentation? getClosureRepresentation(
+  ClosureRepresentation? getClosureRepresentation(w.ModuleBuilder module,
       int typeCount, int positionalCount, List<String> names) {
     final representations =
         _representationsForCounts(typeCount, positionalCount);
     if (representations.withoutNamed == null) {
       ClosureRepresentation? parent = positionalCount == 0
           ? null
-          : getClosureRepresentation(typeCount, positionalCount - 1, const [])!;
-      representations.withoutNamed = _createRepresentation(typeCount,
+          : getClosureRepresentation(
+              module, typeCount, positionalCount - 1, const [])!;
+      representations.withoutNamed = _createRepresentation(module, typeCount,
           positionalCount, const [], parent, null, [positionalCount]);
     }
 
@@ -344,6 +445,7 @@
         representations.clusterForNames(names);
     if (cluster == null) return null;
     return cluster.representation ??= _createRepresentation(
+        module,
         typeCount,
         positionalCount,
         names,
@@ -354,6 +456,7 @@
   }
 
   ClosureRepresentation _createRepresentation(
+      w.ModuleBuilder module,
       int typeCount,
       int positionalCount,
       List<String> names,
@@ -375,7 +478,7 @@
     if (typeCount > 0) {
       // Add or set vtable field for the instantiation function.
       instantiatedRepresentation =
-          getClosureRepresentation(0, positionalCount, names)!;
+          getClosureRepresentation(module, 0, positionalCount, names)!;
       w.RefType inputType = w.RefType.def(closureBaseStruct, nullable: false);
       w.RefType outputType = w.RefType.def(
           instantiatedRepresentation.closureStruct,
@@ -428,7 +531,8 @@
         vtableStruct,
         closureStruct,
         indexOfCombination,
-        instantiationContextStruct);
+        instantiationContextStruct,
+        nameTags.join('-'));
 
     if (typeCount > 0) {
       // The instantiation trampolines and the instantiation function can't be
@@ -440,7 +544,7 @@
 
       representation._instantiationTrampolinesThunk = () {
         List<w.BaseFunction> instantiationTrampolines = [
-          ...?parent?.instantiationTrampolines
+          ...?parent?._instantiationTrampolines
         ];
         String instantiationTrampolineFunctionName =
             "${["#Instantiation", ...nameTags].join("-")} trampoline";
@@ -477,6 +581,7 @@
                         (positionalCount + 1) +
                         genericIndex)
                 : translator.globals.getDummyFunction(
+                    translator.mainModule,
                     (instantiatedRepresentation
                             .vtableStruct
                             .fields[vtableBaseIndexNonGeneric +
@@ -493,9 +598,11 @@
         String instantiationFunctionName =
             ["#Instantiation", ...nameTags].join("-");
         return _createInstantiationFunction(
+            module,
             typeCount,
             instantiatedRepresentation!,
-            representation.instantiationTrampolines,
+            representation._instantiationTrampolinesForModule(
+                translator, module),
             representation.instantiationFunctionType,
             instantiationContextStruct!,
             closureStruct,
@@ -531,7 +638,8 @@
     assert(genericFunctionType.inputs.length ==
         instantiatedFunctionType.inputs.length + typeCount);
 
-    final trampoline = m.functions.define(instantiatedFunctionType, name);
+    final trampoline =
+        translator.mainModule.functions.define(instantiatedFunctionType, name);
     final b = trampoline.body;
 
     // Cast context reference to actual context type.
@@ -573,9 +681,9 @@
     return trampoline;
   }
 
-  w.BaseFunction _createInstantiationDynamicCallEntry(
+  w.BaseFunction _createInstantiationDynamicCallEntry(w.ModuleBuilder module,
       int typeCount, w.StructType instantiationContextStruct) {
-    final function = m.functions.define(
+    final function = module.functions.define(
         translator.dynamicCallVtableEntryFunctionType,
         "instantiation dynamic call entry");
     final b = function.body;
@@ -625,6 +733,7 @@
   }
 
   w.BaseFunction _createInstantiationFunction(
+      w.ModuleBuilder module,
       int typeCount,
       ClosureRepresentation instantiatedRepresentation,
       List<w.BaseFunction> instantiationTrampolines,
@@ -641,18 +750,19 @@
     assert(functionType.outputs.single == instantiatedClosureType);
 
     // Create vtable for the instantiated closure, containing the trampolines.
-    final vtable = m.globals.define(w.GlobalType(
+    final vtable = module.globals.define(w.GlobalType(
         w.RefType.def(instantiatedRepresentation.vtableStruct, nullable: false),
         mutable: false));
     final ib = vtable.initializer;
-    ib.ref_func(_createInstantiationDynamicCallEntry(typeCount, contextStruct));
+    ib.ref_func(
+        _createInstantiationDynamicCallEntry(module, typeCount, contextStruct));
     for (w.BaseFunction trampoline in instantiationTrampolines) {
       ib.ref_func(trampoline);
     }
     ib.struct_new(instantiatedRepresentation.vtableStruct);
     ib.end();
 
-    final instantiationFunction = m.functions.define(functionType, name);
+    final instantiationFunction = module.functions.define(functionType, name);
     final b = instantiationFunction.body;
     w.Local preciseClosure = b.addLocal(genericClosureType);
 
@@ -674,7 +784,7 @@
     }
     b.struct_new(contextStruct);
 
-    b.global_get(vtable);
+    translator.globals.readGlobal(b, vtable);
 
     // Construct the type of the instantiated closure, which is the type of the
     // original closure with the type arguments of the instantiation substituted
@@ -703,7 +813,7 @@
   }
 
   w.BaseFunction _createInstantiationTypeComparisonFunction(int numTypes) {
-    final function = m.functions.define(
+    final function = translator.mainModule.functions.define(
         instantiationClosureTypeComparisonFunctionType,
         "#InstantiationTypeComparison-$numTypes");
 
@@ -752,7 +862,7 @@
   }
 
   w.BaseFunction _createInstantiationTypeHashFunction(int numTypes) {
-    final function = m.functions.define(
+    final function = translator.mainModule.functions.define(
         instantiationClosureTypeHashFunctionType,
         "#InstantiationTypeHash-$numTypes");
 
@@ -770,7 +880,7 @@
 
     // Same as `SystemHash.hashN` functions: combine first hash with
     // `_hashSeed`.
-    translator.globals.readGlobal(b, translator.hashSeed);
+    translator.callReference(translator.hashSeed.getterReference, b);
 
     // Field 0 is the instantiated closure. Types start at 1.
     for (int typeFieldIdx = 1; typeFieldIdx <= numTypes; typeFieldIdx += 1) {
@@ -1063,8 +1173,6 @@
         : null;
   }
 
-  w.ModuleBuilder get m => translator.m;
-
   late final w.ValueType typeType =
       translator.classInfo[translator.typeClass]!.nonNullableType;
 
@@ -1153,8 +1261,6 @@
 
   Translator get translator => closures.translator;
 
-  w.ModuleBuilder get m => translator.m;
-
   Source _currentSource;
 
   @override
diff --git a/pkg/dart2wasm/lib/code_generator.dart b/pkg/dart2wasm/lib/code_generator.dart
index 33314a0..cd2ca25 100644
--- a/pkg/dart2wasm/lib/code_generator.dart
+++ b/pkg/dart2wasm/lib/code_generator.dart
@@ -59,7 +59,7 @@
   final Member enclosingMember;
 
   // To be initialized in `generate()`
-  late final w.InstructionsBuilder b;
+  late w.InstructionsBuilder b;
   late final List<w.Local> paramLocals;
   late final w.Label? returnLabel;
 
@@ -1991,12 +1991,7 @@
         intrinsifier.generateStaticGetterIntrinsic(node);
     if (intrinsicResult != null) return intrinsicResult;
 
-    Member target = node.target;
-    if (target is Field) {
-      return translator.globals.readGlobal(b, target);
-    } else {
-      return translator.outputOrVoid(call(target.reference));
-    }
+    return translator.outputOrVoid(call(node.targetReference));
   }
 
   @override
@@ -2010,39 +2005,20 @@
   w.ValueType visitStaticSet(StaticSet node, w.ValueType expectedType) {
     bool preserved = expectedType != voidMarker;
     Member target = node.target;
-    if (target is Field) {
-      w.Global global = translator.globals.getGlobal(target);
-      w.Global? flag = translator.globals.getGlobalInitializedFlag(target);
-      wrap(node.value, global.type.type);
-      b.global_set(global);
-      if (flag != null) {
-        b.i32_const(1); // true
-        b.global_set(flag);
-      }
-      if (preserved) {
-        b.global_get(global);
-        return global.type.type;
-      } else {
-        return voidMarker;
-      }
-    } else {
-      w.FunctionType targetFunctionType =
-          translator.signatureForDirectCall(target.reference);
-      w.ValueType paramType = targetFunctionType.inputs.single;
-      wrap(node.value, paramType);
-      w.Local? temp;
-      if (preserved) {
-        temp = addLocal(paramType);
-        b.local_tee(temp);
-      }
-      call(target.reference);
-      if (preserved) {
-        b.local_get(temp!);
-        return temp.type;
-      } else {
-        return voidMarker;
-      }
+    w.ValueType paramType = target is Field
+        ? translator.globals.getGlobalForStaticField(target).type.type
+        : translator.signatureForDirectCall(target.reference).inputs.single;
+    wrap(node.value, paramType);
+    if (!preserved) {
+      call(node.targetReference);
+      return voidMarker;
     }
+    w.Local temp = addLocal(paramType);
+    b.local_tee(temp);
+
+    call(node.targetReference);
+    b.local_get(temp);
+    return temp.type;
   }
 
   @override
@@ -2352,7 +2328,7 @@
     b.i32_const(info.classId);
     b.i32_const(initialIdentityHash);
     pushContext();
-    b.global_get(closure.vtable);
+    translator.globals.readGlobal(b, closure.vtable);
     types.makeType(this, functionType);
     b.struct_new(struct);
 
@@ -2368,7 +2344,8 @@
         b.ref_as_non_null();
       }
     } else {
-      b.global_get(translator.globals.dummyStructGlobal); // Dummy context
+      translator.globals
+          .readGlobal(b, translator.globals.dummyStructGlobal); // Dummy context
     }
   }
 
@@ -2394,7 +2371,7 @@
     int posArgCount = arguments.positional.length;
     List<String> argNames = arguments.named.map((a) => a.name).toList()..sort();
     ClosureRepresentation? representation = translator.closureLayouter
-        .getClosureRepresentation(typeCount, posArgCount, argNames);
+        .getClosureRepresentation(b.module, typeCount, posArgCount, argNames);
     if (representation == null) {
       // This is a dynamic function call with a signature that matches no
       // functions in the program.
@@ -2466,7 +2443,8 @@
       int posArgCount = type.positionalParameters.length;
       List<String> argNames = type.namedParameters.map((a) => a.name).toList();
       ClosureRepresentation representation = translator.closureLayouter
-          .getClosureRepresentation(typeCount, posArgCount, argNames)!;
+          .getClosureRepresentation(
+              b.module, typeCount, posArgCount, argNames)!;
 
       // Operand closure
       w.RefType closureType =
@@ -3137,6 +3115,10 @@
 
   if (member is Field) {
     if (member.isStatic) {
+      if (reference.isImplicitGetter || reference.isImplicitSetter) {
+        return StaticFieldImplicitAccessorCodeGenerator(
+            translator, functionType, member, reference.isImplicitGetter);
+      }
       return StaticFieldInitializerCodeGenerator(
           translator, functionType, member);
     }
@@ -3218,7 +3200,7 @@
     b.i32_const(info.classId);
     b.i32_const(initialIdentityHash);
     b.local_get(paramLocals[0]); // `this` as context
-    b.global_get(closure.vtable);
+    translator.globals.readGlobal(b, closure.vtable);
     types.makeType(this, functionType);
     b.struct_new(struct);
     b.end();
@@ -3845,7 +3827,7 @@
     closures.collectContexts(field);
     closures.buildContexts();
 
-    w.Global global = translator.globals.getGlobal(field);
+    w.Global global = translator.globals.getGlobalForStaticField(field);
     w.Global? flag = translator.globals.getGlobalInitializedFlag(field);
     wrap(field.initializer!, global.type.type);
     b.global_set(global);
@@ -3860,6 +3842,64 @@
   }
 }
 
+class StaticFieldImplicitAccessorCodeGenerator extends AstCodeGenerator {
+  final Field field;
+  final bool isImplicitGetter;
+
+  StaticFieldImplicitAccessorCodeGenerator(Translator translator,
+      w.FunctionType functionType, this.field, this.isImplicitGetter)
+      : super(translator, functionType, field);
+
+  @override
+  void generateInternal() {
+    final global = translator.globals.getGlobalForStaticField(field);
+    final flag = translator.globals.getGlobalInitializedFlag(field);
+    if (isImplicitGetter) {
+      final initFunction =
+          translator.functions.getExistingFunction(field.fieldReference);
+      _generateGetter(global, flag, initFunction);
+    } else {
+      _generateSetter(global, flag);
+    }
+    b.end();
+  }
+
+  void _generateGetter(
+      w.Global global, w.Global? flag, w.BaseFunction? initFunction) {
+    if (initFunction == null) {
+      // Statically initialized
+      b.global_get(global);
+    } else {
+      if (flag != null) {
+        // Explicit initialization flag
+        assert(global.type.type == initFunction.type.outputs.single);
+        b.global_get(flag);
+        b.if_(const [], [global.type.type]);
+        b.global_get(global);
+        b.else_();
+        b.call(initFunction);
+        b.end();
+      } else {
+        // Null signals uninitialized
+        w.Label block = b.block(const [], [initFunction.type.outputs.single]);
+        b.global_get(global);
+        b.br_on_non_null(block);
+        b.call(initFunction);
+        b.end();
+      }
+    }
+  }
+
+  void _generateSetter(w.Global global, w.Global? flag) {
+    b.local_get(paramLocals.single);
+    b.global_set(global);
+    if (flag != null) {
+      b.i32_const(1); // true
+      b.global_set(flag);
+    }
+  }
+}
+
 class ImplicitFieldAccessorCodeGenerator extends AstCodeGenerator {
   final Field field;
   final bool isImplicitGetter;
diff --git a/pkg/dart2wasm/lib/constants.dart b/pkg/dart2wasm/lib/constants.dart
index 4fa57de..1dc5d58 100644
--- a/pkg/dart2wasm/lib/constants.dart
+++ b/pkg/dart2wasm/lib/constants.dart
@@ -26,6 +26,28 @@
   ConstantInfo(this.constant, this.global, this.function);
 
   bool get isLazy => function != null;
+
+  void _readGlobal(Translator translator, w.InstructionsBuilder b) {
+    translator.globals.readGlobal(b, global, importNameSuffix: 'constant');
+  }
+
+  w.ValueType readConstant(Translator translator, w.InstructionsBuilder b) {
+    final initFunction = function;
+    if (initFunction != null) {
+      // Lazily initialized constant.
+      w.ValueType type = global.type.type.withNullability(false);
+      w.Label done = b.block(const [], [type]);
+      _readGlobal(translator, b);
+      b.br_on_non_null(done);
+
+      translator.callFunction(initFunction, b);
+      b.end();
+      return type;
+    } else {
+      _readGlobal(translator, b);
+      return global.type.type;
+    }
+  }
 }
 
 typedef ConstantCodeGenerator = void Function(w.InstructionsBuilder);
@@ -82,7 +104,7 @@
 
   Constants(this.translator);
 
-  w.ModuleBuilder get m => translator.m;
+  // All constant constructs should go in the main module.
   Types get types => translator.types;
   CoreTypes get coreTypes => translator.coreTypes;
 
@@ -298,7 +320,6 @@
   ConstantInstantiator(this.constants, this.b, this.expectedType);
 
   Translator get translator => constants.translator;
-  w.ModuleBuilder get m => translator.m;
 
   void instantiate(Constant constant) {
     w.ValueType resultType = constant.accept(this);
@@ -319,20 +340,7 @@
   @override
   w.ValueType defaultConstant(Constant constant) {
     ConstantInfo info = ConstantCreator(constants).ensureConstant(constant)!;
-    if (info.isLazy) {
-      // Lazily initialized constant.
-      w.ValueType type = info.global.type.type.withNullability(false);
-      w.Label done = b.block(const [], [type]);
-      b.global_get(info.global);
-      b.br_on_non_null(done);
-      b.call(info.function!);
-      b.end();
-      return type;
-    } else {
-      // Constant initialized eagerly in a global initializer.
-      b.global_get(info.global);
-      return info.global.type.type;
-    }
+    return info.readConstant(translator, b);
   }
 
   @override
@@ -415,7 +423,6 @@
 
   Translator get translator => constants.translator;
   Types get types => translator.types;
-  w.ModuleBuilder get m => constants.m;
 
   Constant get _uninitializedHashBaseIndexConstant =>
       (translator.uninitializedHashBaseIndex.initializer as ConstantExpression)
@@ -443,14 +450,16 @@
       Constant constant, w.RefType type, ConstantCodeGenerator generator,
       {bool lazy = false}) {
     assert(!type.nullable);
+    final mainModule = translator.mainModule;
     if (lazy) {
       // Create uninitialized global and function to initialize it.
-      final global = m.globals.define(w.GlobalType(type.withNullability(true)));
+      final global =
+          mainModule.globals.define(w.GlobalType(type.withNullability(true)));
       global.initializer.ref_null(w.HeapType.none);
       global.initializer.end();
       w.FunctionType ftype =
           translator.typesBuilder.defineFunction(const [], [type]);
-      final function = m.functions.define(ftype, "$constant");
+      final function = mainModule.functions.define(ftype, "$constant");
       final b2 = function.body;
       generator(b2);
       w.Local temp = b2.addLocal(type);
@@ -464,7 +473,8 @@
       // Create global with the constant in its initializer.
       assert(!constants.currentlyCreating);
       constants.currentlyCreating = true;
-      final global = m.globals.define(w.GlobalType(type, mutable: false));
+      final global =
+          mainModule.globals.define(w.GlobalType(type, mutable: false));
       generator(global.initializer);
       global.initializer.end();
       constants.currentlyCreating = false;
@@ -513,7 +523,8 @@
       return createConstant(constant, info.nonNullableType, (b) {
         b.i32_const(info.classId);
         b.i32_const(initialIdentityHash);
-        b.global_get(translator.getInternalizedStringGlobal(constant.value));
+        b.global_get(
+            translator.getInternalizedStringGlobal(b.module, constant.value));
         b.struct_new(info.struct);
       });
     }
@@ -536,11 +547,13 @@
         w.DataSegmentBuilder segment;
         Uint8List bytes;
         if (isOneByte) {
-          segment = constants.oneByteStringSegment ??= m.dataSegments.define();
+          segment = constants.oneByteStringSegment ??=
+              translator.mainModule.dataSegments.define();
           bytes = Uint8List.fromList(constant.value.codeUnits);
         } else {
           assert(Endian.host == Endian.little);
-          segment = constants.twoByteStringSegment ??= m.dataSegments.define();
+          segment = constants.twoByteStringSegment ??=
+              translator.mainModule.dataSegments.define();
           bytes = Uint16List.fromList(constant.value.codeUnits)
               .buffer
               .asUint8List();
@@ -818,18 +831,24 @@
     ClosureImplementation closure = translator.getTearOffClosure(member);
     w.StructType struct = closure.representation.closureStruct;
     w.RefType type = w.RefType.def(struct, nullable: false);
+
+    // The vtable for the target will be stored on a global in the target's
+    // module.
+    final isLazy = !translator
+        .isMainModule(translator.moduleForReference(constant.targetReference));
     return createConstant(constant, type, (b) {
       ClassInfo info = translator.closureInfo;
       translator.functions.recordClassAllocation(info.classId);
 
       b.i32_const(info.classId);
       b.i32_const(initialIdentityHash);
-      b.global_get(translator.globals.dummyStructGlobal); // Dummy context
-      b.global_get(closure.vtable);
+      translator.globals
+          .readGlobal(b, translator.globals.dummyStructGlobal); // Dummy context
+      translator.globals.readGlobal(b, closure.vtable);
       constants.instantiateConstant(
           b, functionTypeConstant, types.nonNullableTypeType);
       b.struct_new(struct);
-    });
+    }, lazy: isLazy);
   }
 
   @override
@@ -840,6 +859,7 @@
         .map((c) => ensureConstant(constants._lowerTypeConstant(c))!)
         .toList();
     Procedure tearOffProcedure = tearOffConstant.targetReference.asProcedure;
+    final module = translator.moduleForReference(tearOffProcedure.reference);
     FunctionType tearOffFunctionType =
         translator.getTearOffType(tearOffProcedure);
     FunctionType instantiatedFunctionType =
@@ -855,16 +875,17 @@
         tearOffConstant.function.namedParameters.map((p) => p.name!).toList();
     ClosureRepresentation instantiationOfTearOffRepresentation = translator
         .closureLayouter
-        .getClosureRepresentation(0, positionalCount, names)!;
+        .getClosureRepresentation(module, 0, positionalCount, names)!;
     ClosureRepresentation tearOffRepresentation = translator.closureLayouter
-        .getClosureRepresentation(types.length, positionalCount, names)!;
+        .getClosureRepresentation(
+            module, types.length, positionalCount, names)!;
     w.StructType struct = instantiationOfTearOffRepresentation.closureStruct;
     w.RefType type = w.RefType.def(struct, nullable: false);
 
     final tearOffConstantInfo = ensureConstant(tearOffConstant)!;
 
     w.BaseFunction makeDynamicCallEntry() {
-      final function = m.functions.define(
+      final function = module.functions.define(
           translator.dynamicCallVtableEntryFunctionType, "dynamic call entry");
 
       final b = function.body;
@@ -898,12 +919,12 @@
           w.FunctionType signature, w.BaseFunction tearOffFunction) {
         assert(tearOffFunction.type.inputs.length ==
             signature.inputs.length + types.length);
-        final function =
-            m.functions.define(signature, "instantiation constant trampoline");
+        final function = module.functions
+            .define(signature, "instantiation constant trampoline");
         final b = function.body;
         b.local_get(function.locals[0]);
         for (ConstantInfo typeInfo in types) {
-          b.global_get(typeInfo.global);
+          typeInfo.readConstant(translator, b);
         }
         for (int i = 1; i < signature.inputs.length; i++) {
           b.local_get(function.locals[i]);
@@ -926,17 +947,20 @@
           //   - non-generic closure / non-generic tear-off definitions
           //   - non-generic callers
           // => We make a dummy entry which is unreachable.
-          function = translator.globals.getDummyFunction(signature);
+          function = translator.globals
+              .getDummyFunction(translator.mainModule, signature);
         } else {
           final int tearOffFieldIndex = tearOffRepresentation
               .fieldIndexForSignature(posArgCount, nameCombination.names);
           w.BaseFunction tearOffFunction = tearOffClosure.functions[
               tearOffFieldIndex - tearOffRepresentation.vtableBaseIndex];
-          if (translator.globals.isDummyFunction(tearOffFunction)) {
+          if (translator.globals
+              .isDummyFunction(translator.mainModule, tearOffFunction)) {
             // This name combination may not exist for the target, but got
             // clustered together with other name combinations that do exist.
             // => We make a dummy entry which is unreachable.
-            function = translator.globals.getDummyFunction(signature);
+            function = translator.globals
+                .getDummyFunction(translator.mainModule, signature);
           } else {
             function = makeTrampoline(signature, tearOffFunction);
           }
@@ -964,9 +988,10 @@
 
       // Context is not used by the vtable functions, but it's needed for
       // closure equality checks to work (`_Closure._equals`).
-      b.global_get(tearOffConstantInfo.global);
+      tearOffConstantInfo.readConstant(translator, b);
+
       for (final ty in types) {
-        b.global_get(ty.global);
+        ty.readConstant(translator, b);
       }
       b.struct_new(tearOffRepresentation.instantiationContextStruct!);
 
diff --git a/pkg/dart2wasm/lib/functions.dart b/pkg/dart2wasm/lib/functions.dart
index a8d6d43..b7cfe85 100644
--- a/pkg/dart2wasm/lib/functions.dart
+++ b/pkg/dart2wasm/lib/functions.dart
@@ -228,12 +228,8 @@
   @override
   w.FunctionType visitField(Field node, Reference target) {
     if (!node.isInstanceMember) {
-      if (target == node.fieldReference) {
-        // Static field initializer function
-        return _makeFunctionType(translator, target, null);
-      }
-      String kind = target == node.setterReference ? "setter" : "getter";
-      throw "No implicit $kind function for static field: $node";
+      // Static field initializer function or implicit getter/setter.
+      return _makeFunctionType(translator, target, null);
     }
     assert(!translator.dispatchTable
         .selectorForTarget(target)
@@ -465,6 +461,19 @@
     {bool isImportOrExport = false}) {
   Member member = target.asMember;
 
+  if (member is Field && !member.isInstanceMember) {
+    final isGetter = target.isImplicitGetter;
+    final isSetter = target.isImplicitSetter;
+    if (isGetter || isSetter) {
+      final global = translator.globals.getGlobalForStaticField(member);
+      final globalType = global.type.type;
+      if (isGetter) {
+        return translator.typesBuilder.defineFunction(const [], [globalType]);
+      }
+      return translator.typesBuilder.defineFunction([globalType], const []);
+    }
+  }
+
   // Translate types differently for imports and exports.
   w.ValueType translateType(DartType type) => isImportOrExport
       ? translator.translateExternalType(type)
@@ -473,6 +482,7 @@
   final List<w.ValueType> inputs = _getInputTypes(
       translator, target, receiverType, isImportOrExport, translateType);
 
+  // Setters don't have an output with the exception of static implicit setters.
   final bool emptyOutputList =
       (member is Field && member.setterReference == target) ||
           (member is Procedure && member.isSetter);
diff --git a/pkg/dart2wasm/lib/globals.dart b/pkg/dart2wasm/lib/globals.dart
index ae97c66..f5f64d2 100644
--- a/pkg/dart2wasm/lib/globals.dart
+++ b/pkg/dart2wasm/lib/globals.dart
@@ -12,15 +12,18 @@
 class Globals {
   final Translator translator;
 
-  final Map<Field, w.Global> _globals = {};
+  final Map<Field, w.GlobalBuilder> _globals = {};
+  final Map<w.Global, w.BaseFunction> _globalGetters = {};
+  final Map<w.Global, w.BaseFunction> _globalSetters = {};
   final Map<Field, w.BaseFunction> _globalInitializers = {};
   final Map<Field, w.Global> _globalInitializedFlag = {};
-  final Map<w.FunctionType, w.BaseFunction> _dummyFunctions = {};
+  final Map<(w.ModuleBuilder, w.FunctionType), w.BaseFunction> _dummyFunctions =
+      {};
   final Map<w.HeapType, w.Global> _dummyValues = {};
+  final Map<w.Global, Map<w.ModuleBuilder, w.ImportedGlobal>> _importedGlobals =
+      {};
   late final w.Global dummyStructGlobal;
 
-  w.ModuleBuilder get m => translator.m;
-
   Globals(this.translator) {
     _initDummyValues();
   }
@@ -29,7 +32,7 @@
     // Create dummy struct for anyref/eqref/structref dummy values
     w.StructType structType =
         translator.typesBuilder.defineStruct("#DummyStruct");
-    final dummyStructGlobalInit = m.globals.define(
+    final dummyStructGlobalInit = translator.mainModule.globals.define(
         w.GlobalType(w.RefType.struct(nullable: false), mutable: false));
     final ib = dummyStructGlobalInit.initializer;
     ib.struct_new(structType);
@@ -42,9 +45,9 @@
 
   /// Provide a dummy function with the given signature. Used for empty entries
   /// in vtables and for dummy values of function reference type.
-  w.BaseFunction getDummyFunction(w.FunctionType type) {
-    return _dummyFunctions.putIfAbsent(type, () {
-      final function = m.functions.define(type, "#dummy function $type");
+  w.BaseFunction getDummyFunction(w.ModuleBuilder module, w.FunctionType type) {
+    return _dummyFunctions.putIfAbsent((module, type), () {
+      final function = module.functions.define(type, "#dummy function $type");
       final b = function.body;
       b.unreachable();
       b.end();
@@ -53,42 +56,46 @@
   }
 
   /// Returns whether the given function was provided by [getDummyFunction].
-  bool isDummyFunction(w.BaseFunction function) {
-    return _dummyFunctions[function.type] == function;
+  bool isDummyFunction(w.ModuleBuilder module, w.BaseFunction function) {
+    return _dummyFunctions[(module, function.type)] == function;
   }
 
-  w.Global? _prepareDummyValue(w.ValueType type) {
+  w.Global? _prepareDummyValue(w.ModuleBuilder module, w.ValueType type) {
     if (type is w.RefType && !type.nullable) {
       w.HeapType heapType = type.heapType;
-      w.Global? foundGlobal = _dummyValues[heapType];
-      if (foundGlobal != null) return foundGlobal;
-      w.GlobalBuilder? global;
-      if (heapType is w.DefType) {
-        if (heapType is w.StructType) {
-          for (w.FieldType field in heapType.fields) {
-            _prepareDummyValue(field.type.unpacked);
+      return _dummyValues.putIfAbsent(heapType, () {
+        if (heapType is w.DefType) {
+          if (heapType is w.StructType) {
+            for (w.FieldType field in heapType.fields) {
+              _prepareDummyValue(module, field.type.unpacked);
+            }
+            final global =
+                module.globals.define(w.GlobalType(type, mutable: false));
+            final ib = global.initializer;
+            for (w.FieldType field in heapType.fields) {
+              instantiateDummyValue(ib, field.type.unpacked);
+            }
+            ib.struct_new(heapType);
+            ib.end();
+            return global;
+          } else if (heapType is w.ArrayType) {
+            final global =
+                module.globals.define(w.GlobalType(type, mutable: false));
+            final ib = global.initializer;
+            ib.array_new_fixed(heapType, 0);
+            ib.end();
+            return global;
+          } else if (heapType is w.FunctionType) {
+            final global =
+                module.globals.define(w.GlobalType(type, mutable: false));
+            final ib = global.initializer;
+            ib.ref_func(getDummyFunction(module, heapType));
+            ib.end();
+            return global;
           }
-          global = m.globals.define(w.GlobalType(type, mutable: false));
-          final ib = global.initializer;
-          for (w.FieldType field in heapType.fields) {
-            instantiateDummyValue(ib, field.type.unpacked);
-          }
-          ib.struct_new(heapType);
-          ib.end();
-        } else if (heapType is w.ArrayType) {
-          global = m.globals.define(w.GlobalType(type, mutable: false));
-          final ib = global.initializer;
-          ib.array_new_fixed(heapType, 0);
-          ib.end();
-        } else if (heapType is w.FunctionType) {
-          global = m.globals.define(w.GlobalType(type, mutable: false));
-          final ib = global.initializer;
-          ib.ref_func(getDummyFunction(heapType));
-          ib.end();
         }
-        _dummyValues[heapType] = global!;
-      }
-      return global;
+        throw 'Unexpected heapType: $heapType';
+      });
     }
 
     return null;
@@ -117,7 +124,7 @@
           if (type.nullable) {
             b.ref_null(heapType.bottomType);
           } else {
-            b.global_get(_prepareDummyValue(type)!);
+            readGlobal(b, _prepareDummyValue(b.module, type)!);
           }
         } else {
           throw "Unsupported global type $type ($type)";
@@ -137,18 +144,100 @@
     return null;
   }
 
+  /// Reads the value of [w.Global] onto the stack in [b].
+  ///
+  /// Takes into account the calling module and the module the global belongs
+  /// to. If they are not the same then accesses the global indirectly, either
+  /// through an import or a getter call.
+  w.ValueType readGlobal(w.InstructionsBuilder b, w.Global global,
+      {String importNameSuffix = ''}) {
+    final owningModule = global.enclosingModule;
+    final callingModule = b.module;
+    if (owningModule == callingModule) {
+      b.global_get(global);
+    } else if (translator.isMainModule(owningModule)) {
+      final importedGlobals = _importedGlobals.putIfAbsent(global, () {
+        final importName = 'global$importNameSuffix${_importedGlobals.length}';
+        owningModule.exports.export(importName, global);
+        return {};
+      });
+      final importedGlobal = importedGlobals.putIfAbsent(callingModule, () {
+        return callingModule.globals.import(
+            translator.nameForModule(owningModule),
+            global.exportedName!,
+            global.type);
+      });
+
+      b.global_get(importedGlobal);
+    } else {
+      final getter = _globalGetters.putIfAbsent(global, () {
+        final getterType =
+            owningModule.types.defineFunction(const [], [global.type.type]);
+        final getterFunction = owningModule.functions.define(getterType);
+        final getterBody = getterFunction.body;
+        getterBody.global_get(global);
+        getterBody.end();
+        return getterFunction;
+      });
+
+      translator.callFunction(getter, b);
+    }
+    return global.type.type;
+  }
+
+  /// Sets the value of [w.Global] in [b].
+  ///
+  /// Takes into account the calling module and the module the global belongs
+  /// to. If they are not the same then sets the global indirectly, either
+  /// through an import or a setter call.
+  void updateGlobal(w.InstructionsBuilder b,
+      void Function(w.InstructionsBuilder b) pushValue, w.Global global) {
+    final owningModule = global.enclosingModule;
+    final callingModule = b.module;
+    if (owningModule == callingModule) {
+      pushValue(b);
+      b.global_set(global);
+    } else if (translator.isMainModule(owningModule)) {
+      final importName = 'global${_importedGlobals.length}';
+      final importedFunctions = _importedGlobals.putIfAbsent(global, () {
+        owningModule.exports.export(importName, global);
+        return {};
+      });
+      final importedGlobal = importedFunctions[b.module] ??= b.module.globals
+          .import(
+              translator.nameForModule(owningModule), importName, global.type);
+      pushValue(b);
+      b.global_set(importedGlobal);
+    } else {
+      final setter = _globalSetters.putIfAbsent(global, () {
+        final setterType =
+            owningModule.types.defineFunction([global.type.type], const []);
+        final setterFunction = owningModule.functions.define(setterType);
+        final setterBody = setterFunction.body;
+        setterBody.local_get(setterBody.locals.single);
+        setterBody.global_set(global);
+        setterBody.end();
+        return setterFunction;
+      });
+
+      pushValue(b);
+      translator.callFunction(setter, b);
+    }
+  }
+
   /// Return (and if needed create) the Wasm global corresponding to a static
   /// field.
-  w.Global getGlobal(Field field) {
+  w.Global getGlobalForStaticField(Field field) {
     assert(!field.isLate);
     return _globals.putIfAbsent(field, () {
       final Constant? init = _getConstantInitializer(field);
       w.ValueType type = translator.translateTypeOfField(field);
+      final module = translator.moduleForReference(field.fieldReference);
       if (init != null &&
           !(translator.constants.ensureConstant(init)?.isLazy ?? false)) {
         // Initialized to a constant
         final global =
-            m.globals.define(w.GlobalType(type, mutable: !field.isFinal));
+            module.globals.define(w.GlobalType(type, mutable: !field.isFinal));
         translator.constants
             .instantiateConstant(global.initializer, init, type);
         global.initializer.end();
@@ -159,13 +248,13 @@
           type = type.withNullability(true);
         } else {
           // Explicit initialization flag
-          final flag = m.globals.define(w.GlobalType(w.NumType.i32));
+          final flag = module.globals.define(w.GlobalType(w.NumType.i32));
           flag.initializer.i32_const(0);
           flag.initializer.end();
           _globalInitializedFlag[field] = flag;
         }
 
-        final global = m.globals.define(w.GlobalType(type));
+        final global = module.globals.define(w.GlobalType(type));
         instantiateDummyValue(global.initializer, type);
         global.initializer.end();
 
@@ -179,38 +268,8 @@
   /// Return the Wasm global containing the flag indicating whether this static
   /// field has been initialized, if such a flag global is needed.
   ///
-  /// Note that [getGlobal] must have been called for the field beforehand.
+  /// Note that [getGlobalForStaticField] must have been called for the field beforehand.
   w.Global? getGlobalInitializedFlag(Field variable) {
     return _globalInitializedFlag[variable];
   }
-
-  /// Emit code to read a static field.
-  w.ValueType readGlobal(w.InstructionsBuilder b, Field variable) {
-    w.Global global = getGlobal(variable);
-    w.BaseFunction? initFunction = _globalInitializers[variable];
-    if (initFunction == null) {
-      // Statically initialized
-      b.global_get(global);
-      return global.type.type;
-    }
-    w.Global? flag = _globalInitializedFlag[variable];
-    if (flag != null) {
-      // Explicit initialization flag
-      assert(global.type.type == initFunction.type.outputs.single);
-      b.global_get(flag);
-      b.if_(const [], [global.type.type]);
-      b.global_get(global);
-      b.else_();
-      b.call(initFunction);
-      b.end();
-    } else {
-      // Null signals uninitialized
-      w.Label block = b.block(const [], [initFunction.type.outputs.single]);
-      b.global_get(global);
-      b.br_on_non_null(block);
-      b.call(initFunction);
-      b.end();
-    }
-    return initFunction.type.outputs.single;
-  }
 }
diff --git a/pkg/dart2wasm/lib/intrinsics.dart b/pkg/dart2wasm/lib/intrinsics.dart
index f074a73..0bba712 100644
--- a/pkg/dart2wasm/lib/intrinsics.dart
+++ b/pkg/dart2wasm/lib/intrinsics.dart
@@ -1061,9 +1061,8 @@
         StaticTearOffConstant func = f.constant as StaticTearOffConstant;
         w.BaseFunction wasmFunction =
             translator.functions.getFunction(func.targetReference);
-        w.Global functionRef = translator.makeFunctionRef(wasmFunction);
-        b.global_get(functionRef);
-        return functionRef.type.type;
+        return translator.globals
+            .readGlobal(b, translator.makeFunctionRef(b.module, wasmFunction));
       }
 
       // Wasm(AnyRef|FuncRef|EqRef|StructRef|I32|I64|F32|F64) constructors
diff --git a/pkg/dart2wasm/lib/sync_star.dart b/pkg/dart2wasm/lib/sync_star.dart
index 70f6c4d..5bc2039 100644
--- a/pkg/dart2wasm/lib/sync_star.dart
+++ b/pkg/dart2wasm/lib/sync_star.dart
@@ -36,7 +36,8 @@
     } else {
       b.ref_null(w.HeapType.struct);
     }
-    b.global_get(translator.makeFunctionRef(resumeFun));
+    translator.globals
+        .readGlobal(b, translator.makeFunctionRef(b.module, resumeFun));
     b.struct_new(syncStarIterableInfo.struct);
     b.return_();
     b.end();
diff --git a/pkg/dart2wasm/lib/translator.dart b/pkg/dart2wasm/lib/translator.dart
index 34676e6..012a4d32 100644
--- a/pkg/dart2wasm/lib/translator.dart
+++ b/pkg/dart2wasm/lib/translator.dart
@@ -129,7 +129,8 @@
 
   /// Internalized strings to move to the JS runtime
   final List<String> internalizedStringsForJSRuntime = [];
-  final Map<String, w.Global> _internalizedStringGlobals = {};
+  final Map<(w.ModuleBuilder, String), w.Global> _internalizedStringGlobals =
+      {};
 
   final Map<w.HeapType, ClassInfo> classForHeapType = {};
   final Map<Field, int> fieldIndex = {};
@@ -568,6 +569,7 @@
     if (type is FunctionType) {
       ClosureRepresentation? representation =
           closureLayouter.getClosureRepresentation(
+              mainModule,
               type.typeParameters.length,
               type.positionalParameters.length,
               type.namedParameters.map((p) => p.name).toList());
@@ -631,9 +633,11 @@
     return w.RefType.any(nullable: true);
   }
 
-  w.Global makeFunctionRef(w.BaseFunction f) {
+  /// Creates a global reference to [f] in [module]. [f] must also be located
+  /// in [module].
+  w.Global makeFunctionRef(w.ModuleBuilder module, w.BaseFunction f) {
     return functionRefCache.putIfAbsent(f, () {
-      final global = m.globals.define(
+      final global = module.globals.define(
           w.GlobalType(w.RefType.def(f.type, nullable: false), mutable: false));
       global.initializer.ref_func(f);
       global.initializer.end();
@@ -652,6 +656,7 @@
 
   ClosureImplementation getClosure(FunctionNode functionNode,
       w.BaseFunction target, ParameterInfo paramInfo, String name) {
+    final targetModule = target.enclosingModule;
     // The target function takes an extra initial parameter if it's a function
     // expression / local function (which takes a context) or a tear-off of an
     // instance method (which takes a receiver).
@@ -673,8 +678,9 @@
             paramInfo.typeParamCount +
             paramInfo.positional.length +
             paramInfo.named.length);
-    ClosureRepresentation representation = closureLayouter
-        .getClosureRepresentation(typeCount, positionalCount, names)!;
+    ClosureRepresentation representation =
+        closureLayouter.getClosureRepresentation(
+            targetModule, typeCount, positionalCount, names)!;
     assert(representation.vtableStruct.fields.length ==
         representation.vtableBaseIndex +
             (1 + positionalCount) +
@@ -728,7 +734,8 @@
 
     w.BaseFunction makeTrampoline(
         w.FunctionType signature, int posArgCount, List<String> argNames) {
-      final trampoline = m.functions.define(signature, "$name trampoline");
+      final trampoline =
+          targetModule.functions.define(signature, "$name trampoline");
       compilationQueue.add(CompilationTask(
           trampoline,
           _ClosureTrampolineGenerator(this, trampoline, target, typeCount,
@@ -737,7 +744,7 @@
     }
 
     w.BaseFunction makeDynamicCallEntry() {
-      final function = m.functions.define(
+      final function = targetModule.functions.define(
           dynamicCallVtableEntryFunctionType, "$name dynamic call entry");
       compilationQueue.add(CompilationTask(
           function,
@@ -754,21 +761,24 @@
       w.FunctionType signature = representation.getVtableFieldType(fieldIndex);
       w.BaseFunction function = canBeCalledWith(posArgCount, argNames)
           ? makeTrampoline(signature, posArgCount, argNames)
-          : globals.getDummyFunction(signature);
+          : globals.getDummyFunction(ib.module, signature);
       functions.add(function);
       ib.ref_func(function);
     }
 
-    final vtable = m.globals.define(w.GlobalType(
+    final vtable = targetModule.globals.define(w.GlobalType(
         w.RefType.def(representation.vtableStruct, nullable: false),
         mutable: false));
     final ib = vtable.initializer;
     final dynamicCallEntry = makeDynamicCallEntry();
     ib.ref_func(dynamicCallEntry);
     if (representation.isGeneric) {
-      ib.ref_func(representation.instantiationTypeComparisonFunction);
-      ib.ref_func(representation.instantiationTypeHashFunction);
-      ib.ref_func(representation.instantiationFunction);
+      ib.ref_func(representation.instantiationTypeComparisonFunctionForModule(
+          this, ib.module));
+      ib.ref_func(representation.instantiationTypeHashFunctionForModule(
+          this, ib.module));
+      ib.ref_func(
+          representation.instantiationFunctionForModule(this, ib.module));
     }
     for (int posArgCount = 0; posArgCount <= positionalCount; posArgCount++) {
       fillVtableEntry(ib, posArgCount, const []);
@@ -780,7 +790,7 @@
     ib.end();
 
     return ClosureImplementation(
-        representation, functions, dynamicCallEntry, vtable);
+        representation, functions, dynamicCallEntry, vtable, targetModule);
   }
 
   w.ValueType outputOrVoid(List<w.ValueType> outputs) {
@@ -1180,15 +1190,15 @@
   ClassInfo getRecordClassInfo(RecordType recordType) =>
       classInfo[recordClasses[RecordShape.fromType(recordType)]!]!;
 
-  w.Global getInternalizedStringGlobal(String s) {
-    w.Global? internalizedString = _internalizedStringGlobals[s];
+  w.Global getInternalizedStringGlobal(w.ModuleBuilder module, String s) {
+    w.Global? internalizedString = _internalizedStringGlobals[(module, s)];
     if (internalizedString != null) {
       return internalizedString;
     }
     final i = internalizedStringsForJSRuntime.length;
-    internalizedString = m.globals.import('s', '$i',
+    internalizedString = module.globals.import('s', '$i',
         w.GlobalType(w.RefType.extern(nullable: true), mutable: false));
-    _internalizedStringGlobals[s] = internalizedString;
+    _internalizedStringGlobals[(module, s)] = internalizedString;
     internalizedStringsForJSRuntime.add(s);
     return internalizedString;
   }
diff --git a/pkg/wasm_builder/lib/src/ir/global.dart b/pkg/wasm_builder/lib/src/ir/global.dart
index f56b9d3..c680809 100644
--- a/pkg/wasm_builder/lib/src/ir/global.dart
+++ b/pkg/wasm_builder/lib/src/ir/global.dart
@@ -10,6 +10,7 @@
   final FinalizableIndex finalizableIndex;
   final GlobalType type;
   final ModuleBuilder enclosingModule;
+  String? exportedName;
 
   /// Name of the global in the names section.
   final String? globalName;
@@ -21,7 +22,11 @@
   String toString() => globalName ?? "$finalizableIndex";
 
   @override
-  Export export(String name) => GlobalExport(name, this);
+  Export export(String name) {
+    assert(exportedName == null);
+    exportedName = name;
+    return GlobalExport(name, this);
+  }
 }
 
 /// A global variable defined in a module.