[dynamic_modules] Add support for deduplicating library prefixes to dart2wasm
Change-Id: I325e4e1e3aff25c4e894bc2f3f23fcf02f15da16 Fixes: https://github.com/dart-lang/sdk/issues/62828 Tested: Dynamic module tests. Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/485980 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Auto-Submit: Nate Biggs <natebiggs@google.com>
This commit is contained in:
@@ -15,7 +15,7 @@ import 'package:front_end/src/api_unstable/vm.dart'
|
||||
parseExperimentalArguments,
|
||||
parseExperimentalFlags,
|
||||
resolveInputUri;
|
||||
import 'package:kernel/ast.dart' show Component, Library, Source;
|
||||
import 'package:kernel/ast.dart' show Component;
|
||||
import 'package:vm/kernel_front_end.dart'
|
||||
show
|
||||
badUsageExitCode,
|
||||
@@ -30,6 +30,8 @@ import 'package:vm/kernel_front_end.dart'
|
||||
parseCommandLineDefines,
|
||||
successExitCode,
|
||||
writeDepfile;
|
||||
import 'package:vm/transformations/prefix_library_uris.dart'
|
||||
as prefix_library_uris;
|
||||
|
||||
import 'bytecode_serialization.dart' show BytecodeSizeStatistics;
|
||||
import 'bytecode_generator.dart' show generateBytecode;
|
||||
@@ -260,8 +262,8 @@ Future<int> runCompilerWithOptions({
|
||||
if (errorDetector.hasCompilationErrors || component == null) {
|
||||
return compileTimeErrorExitCode;
|
||||
}
|
||||
component =
|
||||
prefixLibraryUris(component, results.loadedLibraries, libraryUrisPrefix);
|
||||
component = prefix_library_uris.prefixLibraryUris(
|
||||
component, results.loadedLibraries, libraryUrisPrefix);
|
||||
if (bytecodeOptions.showBytecodeSizeStatistics) {
|
||||
BytecodeSizeStatistics.reset();
|
||||
}
|
||||
@@ -291,57 +293,3 @@ Future<int> runCompilerWithOptions({
|
||||
|
||||
return successExitCode;
|
||||
}
|
||||
|
||||
Component prefixLibraryUris(Component component, Set<Library> loadedLibraries,
|
||||
String libraryUrisPrefix) {
|
||||
if (libraryUrisPrefix.isEmpty) {
|
||||
return component;
|
||||
}
|
||||
final prefixSegments = libraryUrisPrefix.split('/');
|
||||
final importUriReplacements = <Uri, Uri>{};
|
||||
|
||||
for (final lib in component.libraries) {
|
||||
// Skip libraries that come from the host app or the SDK.
|
||||
if (loadedLibraries.contains(lib)) {
|
||||
continue;
|
||||
}
|
||||
final newImportUri = prefixUri(lib.importUri, prefixSegments);
|
||||
importUriReplacements[lib.importUri] = newImportUri;
|
||||
lib.importUri = newImportUri;
|
||||
}
|
||||
|
||||
// Update import uris in sources.
|
||||
final allSourceFileUris = component.uriToSource.keys.toSet();
|
||||
for (final fileUri in allSourceFileUris) {
|
||||
final source = component.uriToSource[fileUri]!;
|
||||
final importUriReplacement = importUriReplacements[source.importUri];
|
||||
if (importUriReplacement == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rewrite the source with the new import URI.
|
||||
component.uriToSource[fileUri] = Source(
|
||||
source.lineStarts,
|
||||
source.source,
|
||||
importUriReplacement,
|
||||
source.fileUri,
|
||||
)
|
||||
..cachedText = source.cachedText
|
||||
..constantCoverageConstructors = source.constantCoverageConstructors;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
Uri prefixUri(Uri uri, List<String> prefixSegments) {
|
||||
if (uri.scheme == 'package') {
|
||||
// For package URIs, the first segment is dot-separated package path, so
|
||||
// we prepend the prefix to the first segment.
|
||||
final pathSegments = uri.pathSegments.toList();
|
||||
pathSegments[0] = [...prefixSegments, pathSegments.first].join('.');
|
||||
return uri.replace(pathSegments: pathSegments);
|
||||
}
|
||||
|
||||
// For other schemes, we just prepend the prefix to the path segments.
|
||||
return uri.replace(pathSegments: prefixSegments.followedBy(uri.pathSegments));
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ class WasmCompilerOptions {
|
||||
Uri? loadsIdsUri;
|
||||
Uri? programSplitConstraintsUri;
|
||||
bool validateDynamicModules = true;
|
||||
String? dynamicModuleLibraryPrefix;
|
||||
Map<String, String> environment = {};
|
||||
Map<fe.ExperimentalFlag, bool> feExperimentalFlags = const {};
|
||||
String? multiRootScheme;
|
||||
|
||||
@@ -115,8 +115,8 @@ final List<Option> options = [
|
||||
(o, value) => o.translatorOptions.enableDeferredLoading = value,
|
||||
defaultsTo: _d.translatorOptions.enableDeferredLoading),
|
||||
UriOption("load-ids", (o, value) => o.loadsIdsUri = value),
|
||||
UriOption("read-program-split",
|
||||
(o, value) => o.programSplitConstraintsUri = value),
|
||||
UriOption(
|
||||
"read-program-split", (o, value) => o.programSplitConstraintsUri = value),
|
||||
Flag("enable-multi-module-stress-test-mode",
|
||||
(o, value) => o.translatorOptions.enableMultiModuleStressTestMode = value,
|
||||
defaultsTo: _d.translatorOptions.enableMultiModuleStressTestMode),
|
||||
@@ -147,6 +147,9 @@ final List<Option> options = [
|
||||
Flag("validate-dynamic-modules",
|
||||
(o, value) => o.validateDynamicModules = value,
|
||||
defaultsTo: true, negatable: true),
|
||||
StringOption("dynamic-module-library-prefix",
|
||||
(o, value) => o.dynamicModuleLibraryPrefix = value),
|
||||
|
||||
UriOption("wasm-opt", (o, value) => o.wasmOptPath = value),
|
||||
// The maximum number of concurrent wasm-opt processes to run. Defaults to the
|
||||
// number of processors on the machine. Use -1 to run with no limit.
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:vm/transformations/devirtualization.dart';
|
||||
import 'package:vm/transformations/dynamic_interface_annotator.dart'
|
||||
as dynamic_interface_annotator;
|
||||
import 'package:vm/transformations/pragma.dart';
|
||||
import 'package:vm/transformations/prefix_library_uris.dart'
|
||||
as library_prefix_uris;
|
||||
import 'package:vm/transformations/type_flow/table_selector_assigner.dart';
|
||||
import 'package:wasm_builder/wasm_builder.dart' as w;
|
||||
|
||||
@@ -255,6 +257,16 @@ class DynamicSubmoduleStrategy extends ModuleStrategy {
|
||||
}
|
||||
|
||||
void _registerLibraries() {
|
||||
final recordLibrary = component.libraries
|
||||
.firstWhere((l) => '${l.importUri}' == dynamicModulesRecordsLibraryUri);
|
||||
if (options.dynamicModuleLibraryPrefix case final uriPrefix?) {
|
||||
library_prefix_uris.prefixLibraryUris(
|
||||
component,
|
||||
component.libraries
|
||||
.where((l) => l.isFromMainModule(coreTypes))
|
||||
.toSet(),
|
||||
uriPrefix);
|
||||
}
|
||||
// Register each library with the SDK. This will ensure no duplicate
|
||||
// libraries are included across dynamic modules.
|
||||
final registerLibraryUris = coreTypes.index
|
||||
@@ -263,7 +275,7 @@ class DynamicSubmoduleStrategy extends ModuleStrategy {
|
||||
final libraryUris = ListLiteral([
|
||||
...component
|
||||
.getDynamicSubmoduleLibraries(coreTypes)
|
||||
.where((l) => '${l.importUri}' != dynamicModulesRecordsLibraryUri)
|
||||
.where((l) => l != recordLibrary)
|
||||
.map((l) => StringLiteral(l.importUri.toString()))
|
||||
], typeArgument: coreTypes.stringNonNullableRawType);
|
||||
entryPoint.function.body = Block([
|
||||
|
||||
@@ -56,12 +56,13 @@ class Dart2wasmExecutor implements TargetExecutor {
|
||||
}
|
||||
|
||||
Future _compile(
|
||||
String testName,
|
||||
DynamicModuleTest test,
|
||||
String name,
|
||||
String source,
|
||||
Uri sourceDir,
|
||||
bool isMain,
|
||||
) async {
|
||||
var testDir = _tmp.uri.resolve(testName).toFilePath();
|
||||
var testDir = _tmp.uri.resolve(test.name).toFilePath();
|
||||
var args = [
|
||||
'--compiler-asserts',
|
||||
'--packages=${repoRoot.toFilePath()}/.dart_tool/package_config.json',
|
||||
@@ -73,9 +74,10 @@ class Dart2wasmExecutor implements TargetExecutor {
|
||||
'--extra-compiler-option=--dynamic-module-type=${isMain ? "main" : "submodule"}',
|
||||
'--extra-compiler-option=--dynamic-module-main=main.dart.dill',
|
||||
'--extra-compiler-option=--dynamic-module-interface='
|
||||
'$rootScheme:/data/$testName/dynamic_interface.yaml',
|
||||
'$rootScheme:/data/${test.name}/dynamic_interface.yaml',
|
||||
'--extra-compiler-option=--minify',
|
||||
'$rootScheme:/data/$testName/$source',
|
||||
'--dynamic-module-library-prefix=${test.name.endsWith('_prefixed') ? name : 'import/prefix'}',
|
||||
'$rootScheme:/data/${test.name}/$source',
|
||||
'$source.wasm',
|
||||
];
|
||||
await runProcess(
|
||||
@@ -83,7 +85,7 @@ class Dart2wasmExecutor implements TargetExecutor {
|
||||
args,
|
||||
testDir,
|
||||
_logger,
|
||||
'compile $testName/$source',
|
||||
'compile ${test.name}/$source',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,14 +93,14 @@ class Dart2wasmExecutor implements TargetExecutor {
|
||||
Future compileApplication(DynamicModuleTest test) async {
|
||||
_ensureDirectory(test.name);
|
||||
_logger.info('Compile ${test.name} app');
|
||||
await _compile(test.name, test.main, test.folder, true);
|
||||
await _compile(test, test.name, test.main, test.folder, true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future compileDynamicModule(DynamicModuleTest test, String name) async {
|
||||
_logger.info('Compile module ${test.name}.$name');
|
||||
_ensureDirectory(test.name);
|
||||
await _compile(test.name, test.dynamicModules[name]!, test.folder, false);
|
||||
await _compile(test, name, test.dynamicModules[name]!, test.folder, false);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2026, 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';
|
||||
|
||||
Component prefixLibraryUris(
|
||||
Component component,
|
||||
Set<Library> loadedLibraries,
|
||||
String libraryUrisPrefix,
|
||||
) {
|
||||
if (libraryUrisPrefix.isEmpty) {
|
||||
return component;
|
||||
}
|
||||
final prefixSegments = libraryUrisPrefix.split('/');
|
||||
final importUriReplacements = <Uri, Uri>{};
|
||||
|
||||
for (final lib in component.libraries) {
|
||||
// Skip libraries that come from the host app or the SDK.
|
||||
if (loadedLibraries.contains(lib)) {
|
||||
continue;
|
||||
}
|
||||
final newImportUri = prefixUri(lib.importUri, prefixSegments);
|
||||
importUriReplacements[lib.importUri] = newImportUri;
|
||||
lib.importUri = newImportUri;
|
||||
}
|
||||
|
||||
// Update import uris in sources.
|
||||
final allSourceFileUris = component.uriToSource.keys.toSet();
|
||||
for (final fileUri in allSourceFileUris) {
|
||||
final source = component.uriToSource[fileUri]!;
|
||||
final importUriReplacement = importUriReplacements[source.importUri];
|
||||
if (importUriReplacement == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rewrite the source with the new import URI.
|
||||
component.uriToSource[fileUri] =
|
||||
Source(
|
||||
source.lineStarts,
|
||||
source.source,
|
||||
importUriReplacement,
|
||||
source.fileUri,
|
||||
)
|
||||
..cachedText = source.cachedText
|
||||
..constantCoverageConstructors = source.constantCoverageConstructors;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
Uri prefixUri(Uri uri, List<String> prefixSegments) {
|
||||
if (uri.scheme == 'package') {
|
||||
// For package URIs, the first segment is dot-separated package path, so
|
||||
// we prepend the prefix to the first segment.
|
||||
final pathSegments = uri.pathSegments.toList();
|
||||
pathSegments[0] = [...prefixSegments, pathSegments.first].join('.');
|
||||
return uri.replace(pathSegments: pathSegments);
|
||||
}
|
||||
|
||||
// For other schemes, we just prepend the prefix to the path segments.
|
||||
return uri.replace(pathSegments: prefixSegments.followedBy(uri.pathSegments));
|
||||
}
|
||||
+10
-5
@@ -1,18 +1,23 @@
|
||||
// Copyright (c) 2026, 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:dart2bytecode/dart2bytecode.dart' show prefixUri;
|
||||
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm/transformations/prefix_library_uris.dart' show prefixUri;
|
||||
|
||||
void main() {
|
||||
group('prefixUri', () {
|
||||
test('prefixes file URIs', () {
|
||||
expect(prefixUri(Uri.parse('file:///foo/bar.dart'), ['pre', 'fix']),
|
||||
Uri.parse('file:///pre/fix/foo/bar.dart'));
|
||||
expect(
|
||||
prefixUri(Uri.parse('file:///foo/bar.dart'), ['pre', 'fix']),
|
||||
Uri.parse('file:///pre/fix/foo/bar.dart'),
|
||||
);
|
||||
});
|
||||
test('prefixes package URIs', () {
|
||||
expect(prefixUri(Uri.parse('package:foo/bar.dart'), ['pre', 'fix']),
|
||||
Uri.parse('package:pre.fix.foo/bar.dart'));
|
||||
expect(
|
||||
prefixUri(Uri.parse('package:foo/bar.dart'), ['pre', 'fix']),
|
||||
Uri.parse('package:pre.fix.foo/bar.dart'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user