a4a4ca8a41
Missing from this implementation: - Closure/dynamic calls with differing signatures - Overrides with extra optional parameters - Records with same shape defined in different dynamic modules - Avoiding running TFA on dynamic module. - Recompilation of only updateable functions from main module. - Persist wasm def types from main module. Testing is currently done locally via the dynamic_modules package test suite: dart pkg/dynamic_modules/test/runner/main.dart --runtime=dart2wasm Immediately after this lands we can introduce a new step to one of the wasm test matrix configurations that runs the above test suite (the VM has a similar configuration). Change-Id: I3386d84be11b773842d45f4268a62a54c47e352b Tested: Tested via new tests in dynamic_modules package. Tests run locally but will add to existing config. Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/397721 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Nate Biggs <natebiggs@google.com>
171 lines
5.6 KiB
Dart
171 lines
5.6 KiB
Dart
// 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 'target.dart';
|
|
import 'util.dart';
|
|
|
|
const _mainModuleId = 0;
|
|
|
|
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? enclosingClassForReference(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 Class) return current;
|
|
current = current.parent;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
class ModuleOutputBuilder {
|
|
int _counter = _mainModuleId;
|
|
|
|
ModuleOutput buildModule({bool emitAsMain = false, bool skipEmit = false}) =>
|
|
ModuleOutput._(_counter++, emitAsMain: emitAsMain, skipEmit: skipEmit);
|
|
}
|
|
|
|
/// Deferred loading metadata for a single dart2wasm output module.
|
|
///
|
|
/// Each [ModuleOutput] 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 ModuleOutput {
|
|
/// The ID for the module which will be included in the emitted name.
|
|
final int _id;
|
|
|
|
/// The set of libraries contained in this module.
|
|
final Set<Library> libraries = {};
|
|
|
|
bool get isMain => _id == _mainModuleId;
|
|
|
|
/// The name used to import and export this module.
|
|
String get moduleImportName => 'module$_id';
|
|
|
|
/// 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;
|
|
|
|
ModuleOutput._(this._id, {this.skipEmit = false, bool emitAsMain = false})
|
|
: moduleName = emitAsMain || _id == _mainModuleId ? '' : 'module$_id';
|
|
|
|
/// Whether or not the provided kernel [Reference] is included in this module.
|
|
bool containsReference(Reference reference) {
|
|
final enclosingLibrary = _enclosingLibraryForReference(reference);
|
|
if (enclosingLibrary == null) return false;
|
|
return libraries.contains(enclosingLibrary);
|
|
}
|
|
|
|
@override
|
|
String toString() => '$moduleImportName($libraries)';
|
|
}
|
|
|
|
/// Data needed to create deferred modules.
|
|
class ModuleOutputData {
|
|
/// All [ModuleOutput]s generated for the program.
|
|
final List<ModuleOutput> modules;
|
|
|
|
final Map<Library, Map<String, List<ModuleOutput>>> _importMap;
|
|
|
|
ModuleOutputData(this.modules, this._importMap) : assert(modules[0].isMain);
|
|
|
|
ModuleOutput get mainModule => modules[0];
|
|
Iterable<ModuleOutput> get deferredModules => modules.skip(1);
|
|
|
|
bool get hasMultipleModules => modules.length > 1;
|
|
|
|
/// Mapping from deferred library import to the 'load list' of module names
|
|
/// needed for that import.
|
|
///
|
|
/// If library L is required (either directly or indirectly) by two separate
|
|
/// imports, then L will be in its own module. That module will be included in
|
|
/// the load list for both those imports.
|
|
Map<String, Map<String, List<String>>> generateModuleImportMap() {
|
|
final result = <String, Map<String, List<String>>>{};
|
|
_importMap.forEach((lib, importMapping) {
|
|
final nameMapping = <String, List<String>>{};
|
|
importMapping.forEach((importName, modules) {
|
|
nameMapping[importName] =
|
|
modules.map((o) => o.moduleImportName).toList();
|
|
});
|
|
result[lib.importUri.toString()] = nameMapping;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/// Returns the module that contains [reference].
|
|
ModuleOutput moduleForReference(Reference reference) =>
|
|
modules.firstWhere((e) => e.containsReference(reference));
|
|
}
|
|
|
|
/// Module strategy that puts all libraries into a single module.
|
|
class DefaultModuleStrategy extends ModuleStrategy {
|
|
final Component component;
|
|
|
|
DefaultModuleStrategy(this.component);
|
|
|
|
@override
|
|
ModuleOutputData buildModuleOutputData() {
|
|
// If deferred loading is not enabled then put every library in the main
|
|
// module.
|
|
final mainModule = ModuleOutput._(_mainModuleId);
|
|
mainModule.libraries.addAll(component.libraries);
|
|
return ModuleOutputData([mainModule], const {});
|
|
}
|
|
|
|
@override
|
|
void prepareComponent() {}
|
|
}
|
|
|
|
bool _hasWasmExportPragma(CoreTypes coreTypes, Member m) =>
|
|
hasPragma(coreTypes, m, 'wasm:export');
|
|
|
|
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 prepareComponent();
|
|
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;
|
|
}
|