blob: 89d4ec0087b9a0b03a4e4658aa5fb6171dea93d3 [file] [edit]
// Copyright (c) 2025, 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.
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'compiler_options.dart';
import 'reference_extensions.dart';
import 'target.dart';
import 'util.dart';
Library? _enclosingLibraryForReference(Reference reference) {
TreeNode? current = reference.node;
// References generated for constants will not have a node attached.
if (reference.node == null) return null;
while (current != null) {
if (current is Library) return current;
current = current.parent;
}
throw ArgumentError('Could not find enclosing library for ${reference.node}');
}
class ModuleMetadataBuilder {
int _counter = WasmCompilerOptions.mainModuleId;
final WasmCompilerOptions options;
ModuleMetadataBuilder(this.options);
ModuleMetadata buildModuleMetadata({
bool emitAsMain = false,
bool skipEmit = false,
}) {
final id = _counter++;
final moduleImportName = options.translatorOptions.minify
? intToMinString(id)
: 'module$id';
return ModuleMetadata._(
moduleImportName,
options.moduleNameForId(options.outputFile, id, emitAsMain: emitAsMain),
skipEmit: skipEmit,
isMain: id == WasmCompilerOptions.mainModuleId,
);
}
}
/// Deferred loading metadata for a single dart2wasm output module.
///
/// Each [ModuleMetadata] will map to a single wasm module emitted by the
/// compiler. The separation of modules is guided by the deferred imports
/// defined in the source code.
///
/// A module may contain code at any level of granularity. Code may be grouped
/// by library, by class or neither. [containsReference] should be used to
/// determine if a module contains a given class/member reference.
class ModuleMetadata {
final bool isMain;
/// The name used to import and export this module.
final String moduleImportName;
/// The name added to the wasm output file for this module.
final String moduleName;
/// Whether or not a wasm file should be emitted for this module.
final bool skipEmit;
ModuleMetadata._(
this.moduleImportName,
this.moduleName, {
this.skipEmit = false,
this.isMain = false,
});
@override
String toString() => moduleImportName;
}
/// Data needed to create deferred modules.
class ModuleOutputData {
/// All [ModuleMetadata]s generated for the program.
final List<ModuleMetadata> modules;
/// Maps the [Reference] to the corresponding [ModuleMetadata].
final Map<Reference, ModuleMetadata>? referenceToModuleMetadata;
/// Maps the [Constant] to the corresponding [ModuleMetadata].
final Map<Constant, ModuleMetadata>? constantToModuleMetadata;
/// Maps the [Library] to the corresponding [ModuleMetadata].
final Map<Library, ModuleMetadata>? libraryToModuleMetadata;
/// Module for any unassigned reference.
final ModuleMetadata? defaultModule;
ModuleOutputData.fineGrainedSplit(
this.modules,
this.referenceToModuleMetadata,
this.constantToModuleMetadata,
this.defaultModule,
) : libraryToModuleMetadata = null,
assert(modules[0].isMain);
ModuleOutputData.librarySplit(
this.modules,
this.libraryToModuleMetadata,
this.defaultModule,
) : referenceToModuleMetadata = null,
constantToModuleMetadata = null,
assert(modules[0].isMain);
ModuleOutputData.monolithic(ModuleMetadata module)
: modules = [module],
libraryToModuleMetadata = null,
referenceToModuleMetadata = null,
constantToModuleMetadata = null,
defaultModule = module,
assert(module.isMain);
ModuleMetadata get mainModule => modules[0];
Iterable<ModuleMetadata> get deferredModules => modules.skip(1);
bool get hasMultipleModules => modules.length > 1;
/// Returns the module that contains [reference].
ModuleMetadata moduleForReference(Reference reference) {
// Turn artificial [Reference]s used in dart2wasm to the normal Kernel AST
// [Reference]s.
final node = reference.node;
if (node is Field) {
if (reference.isGetter) {
reference = node.getterReference;
} else if (reference.isSetter) {
reference = node.setterReference!;
} else if (reference.isStaticFieldInitializer) {
assert(node.isStatic);
reference = node.getterReference;
} else {
assert(reference == node.fieldReference);
}
} else if (node is Constructor) {
if (reference.isInitializerReference ||
reference.isConstructorBodyReference) {
reference = node.reference;
} else {
assert(reference == node.reference);
}
} else {
node as Procedure;
if (reference.isCheckedEntryReference ||
reference.isUncheckedEntryReference ||
reference.isBodyReference ||
reference.isTearOffReference) {
reference = reference.asMember.reference;
} else {
assert(reference == reference.asMember.reference);
}
}
// We may have fine-grained partitioning of the application.
if (referenceToModuleMetadata != null) {
return referenceToModuleMetadata![reference] ?? defaultModule!;
}
// We may have coarse-grained library-based partitioning of the application.
if (libraryToModuleMetadata != null) {
final library = _enclosingLibraryForReference(reference);
return libraryToModuleMetadata![library] ?? defaultModule!;
}
// We put the entire application into the same wasm module.
return defaultModule!;
}
ModuleMetadata? moduleForConstant(Constant constant) {
return constantToModuleMetadata?[constant];
}
}
/// Module strategy that puts all libraries into a single module.
class DefaultModuleStrategy extends ModuleStrategy {
final CoreTypes coreTypes;
final Component component;
final WasmCompilerOptions options;
DefaultModuleStrategy(this.coreTypes, this.component, this.options);
@override
ModuleOutputData buildModuleOutputData() {
// If deferred loading is not enabled then put every library in the main
// module.
final builder = ModuleMetadataBuilder(options);
final mainModule = builder.buildModuleMetadata(emitAsMain: true);
return ModuleOutputData.monolithic(mainModule);
}
@override
void addEntryPoints() {}
@override
void prepareComponent() {}
@override
Future<void> processComponentAfterTfa(
DeferredModuleLoadingMap loadingMap,
) async {}
}
bool containsWasmExport(CoreTypes coreTypes, Library lib) {
if (lib.members.any((m) => hasWasmExportPragma(coreTypes, m))) {
return true;
}
return lib.classes.any(
(c) => c.members.any((m) => hasWasmExportPragma(coreTypes, m)),
);
}
abstract class ModuleStrategy {
void addEntryPoints();
void prepareComponent();
Future<void> processComponentAfterTfa(DeferredModuleLoadingMap loadingMap);
ModuleOutputData buildModuleOutputData();
}
Set<Library> getReachableLibraries(
Library entryPoint,
CoreTypes coreTypes,
WasmTarget kernelTarget,
) {
final List<Library> queue = [entryPoint];
final Set<Library> reachable = {entryPoint};
while (queue.isNotEmpty) {
final current = queue.removeLast();
for (final dep in current.dependencies) {
final importedLib = dep.targetLibrary;
if (reachable.add(importedLib)) {
queue.add(importedLib);
}
}
}
return reachable;
}
class DeferredModuleLoadingMap {
// Maps each (library, deferred import) to a unique id.
final Map<(Library, String), int> loadIds;
// Maps the unique load id to the deferred import.
final List<LibraryDependency> loadIdToDeferredImport;
// Maps (library, import-name)-id to list of needed modules.
//
// NOTE: The load lists are mutable and may get pruned by the compiler after
// code generation to avoid emitting & loading empty modules.
final List<List<ModuleMetadata>> moduleMap;
DeferredModuleLoadingMap._(
this.loadIds,
this.moduleMap,
this.loadIdToDeferredImport,
);
factory DeferredModuleLoadingMap.fromComponent(Component c) {
int nextLoadId = 0;
final loadIds = <(Library, String), int>{};
final loadIdToDeferredImport = <LibraryDependency>[];
final moduleMap = <List<ModuleMetadata>>[];
for (final library in c.libraries) {
for (final dep in library.dependencies) {
if (!dep.isDeferred) continue;
final name = dep.name!;
loadIds[(library, name)] = nextLoadId++;
loadIdToDeferredImport.add(dep);
moduleMap.add([]);
}
}
return DeferredModuleLoadingMap._(
loadIds,
moduleMap,
loadIdToDeferredImport,
);
}
void addModuleToLibraryImport(
Library lib,
String importName,
List<ModuleMetadata> modules,
) {
moduleMap[loadIds[(lib, importName)]!].addAll(modules);
}
}