[dart2wasm] Add export name minification to dynamic modules.

Change-Id: Id316930bc9d65566d16a12b494caacc846f899ff
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441989
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Ömer Ağacan <omersa@google.com>
This commit is contained in:
Mayank Patke
2025-08-27 14:33:55 -07:00
committed by Commit Queue
parent 17c6e3cd49
commit 6cbc6df06a
10 changed files with 237 additions and 37 deletions
+30 -13
View File
@@ -498,8 +498,9 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
return info;
}
static String _dynamicModuleConstantExportName(int id) => '#c$id';
static String _dynamicModuleInitFunctionExportName(int id) => '#cf$id';
static String _dynamicModuleConstantExportName(int id) => '#constant$id';
static String _dynamicModuleInitFunctionExportName(int id) =>
'#constantFunction$id';
static int _nextGlobalId = 0;
String _constantName(Constant constant) {
@@ -577,11 +578,15 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
translator.typesBuilder.defineFunction(const [], [type]);
if (mainModuleExportId != null) {
global = targetModule.globals.import(translator.mainModule.moduleName,
_dynamicModuleConstantExportName(mainModuleExportId), globalType);
global = targetModule.globals.import(
translator.mainModule.moduleName,
translator.dynamicModuleExports!
.getConstantName(mainModuleExportId)!,
globalType);
initFunction = targetModule.functions.import(
translator.mainModule.moduleName,
_dynamicModuleInitFunctionExportName(mainModuleExportId),
translator.dynamicModuleExports!
.getConstantInitializerName(mainModuleExportId)!,
ftype);
} else {
final name = _constantName(constant);
@@ -597,10 +602,16 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
final exportId = dynamicModuleConstantIdMap[constant] =
dynamicModuleConstantIdMap.length;
targetModule.exports.export(
_dynamicModuleConstantExportName(exportId), definedGlobal);
targetModule.exports
.export(_dynamicModuleInitFunctionExportName(exportId), function);
translator.exporter.exportConstant(
targetModule,
_dynamicModuleConstantExportName(exportId),
definedGlobal,
exportId);
translator.exporter.exportConstantInitializer(
targetModule,
_dynamicModuleInitFunctionExportName(exportId),
function,
exportId);
}
final b2 = function.body;
generator(b2);
@@ -622,8 +633,11 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
final globalType = w.GlobalType(type, mutable: false);
w.Global global;
if (mainModuleExportId != null) {
global = targetModule.globals.import(translator.mainModule.moduleName,
_dynamicModuleConstantExportName(mainModuleExportId), globalType);
global = targetModule.globals.import(
translator.mainModule.moduleName,
translator.dynamicModuleExports!
.getConstantName(mainModuleExportId)!,
globalType);
} else {
constants.currentlyCreating = true;
final definedGlobal = global =
@@ -636,8 +650,11 @@ class ConstantCreator extends ConstantVisitor<ConstantInfo?>
final exportId = dynamicModuleConstantIdMap[constant] =
dynamicModuleConstantIdMap.length;
targetModule.exports.export(
_dynamicModuleConstantExportName(exportId), definedGlobal);
translator.exporter.exportConstant(
targetModule,
_dynamicModuleConstantExportName(exportId),
definedGlobal,
exportId);
}
}
@@ -27,6 +27,7 @@ import 'class_info.dart';
import 'compiler_options.dart';
import 'dispatch_table.dart';
import 'dynamic_modules.dart';
import 'exports.dart';
import 'js/method_collector.dart' show JSMethods;
import 'serialization.dart';
import 'translator.dart';
@@ -474,6 +475,7 @@ Future<(Component, JSMethods)> generateDynamicSubmoduleComponent(
final newComponent = Component()
..addMetadataRepository(DynamicModuleGlobalIdRepository())
..addMetadataRepository(DynamicModuleConstantRepository())
..addMetadataRepository(DynamicModuleExportRepository())
..addMetadataRepository(ProcedureAttributesMetadataRepository())
..addMetadataRepository(TableSelectorMetadataRepository())
..addMetadataRepository(DirectCallMetadataRepository())
+2
View File
@@ -22,6 +22,7 @@ import 'compiler_options.dart';
import 'constants.dart' show maxArrayNewFixedLength;
import 'dispatch_table.dart';
import 'dynamic_module_kernel_metadata.dart';
import 'exports.dart';
import 'intrinsics.dart' show MemberIntrinsic;
import 'kernel_nodes.dart';
import 'modules.dart';
@@ -129,6 +130,7 @@ class DynamicMainModuleStrategy extends ModuleStrategy with KernelNodes {
component.addMetadataRepository(DynamicModuleConstantRepository());
component.addMetadataRepository(DynamicModuleGlobalIdRepository());
component.addMetadataRepository(DynamicModuleExportRepository());
}
@override
+147
View File
@@ -0,0 +1,147 @@
// 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:wasm_builder/wasm_builder.dart';
import 'util.dart';
class DynamicModuleExportRepository
extends MetadataRepository<DynamicModuleExports> {
static const repositoryTag = 'wasm.dynamic-modules.exports';
@override
final String tag = repositoryTag;
@override
final Map<TreeNode, DynamicModuleExports> mapping = {};
@override
DynamicModuleExports readFromBinary(_, BinarySource source) {
return DynamicModuleExports._readFromBinary(source);
}
@override
void writeToBinary(DynamicModuleExports exports, Node node, BinarySink sink) {
exports._writeToBinary(sink);
}
}
class DynamicModuleExports {
final Map<int, String> _callableNames = {};
final Map<int, String> _constantNames = {};
final Map<int, String> _constantInitializerNames = {};
DynamicModuleExports();
factory DynamicModuleExports._readFromBinary(BinarySource source) {
void readMap(Map<int, String> map) {
final length = source.readUInt30();
for (int i = 0; i < length; i++) {
final id = source.readUInt30();
final name = source.readStringReference();
map[id] = name;
}
}
final exports = DynamicModuleExports();
readMap(exports._callableNames);
readMap(exports._constantNames);
readMap(exports._constantInitializerNames);
return exports;
}
void _writeToBinary(BinarySink sink) {
void writeMap(Map<int, String> map) {
sink.writeUInt30(map.length);
map.forEach((id, name) {
sink.writeUInt30(id);
sink.writeStringReference(name);
});
}
writeMap(_callableNames);
writeMap(_constantNames);
writeMap(_constantInitializerNames);
}
String? getCallableName(int callableReferenceId) =>
_callableNames[callableReferenceId];
String? getConstantName(int constantId) => _constantNames[constantId];
String? getConstantInitializerName(int constantId) =>
_constantInitializerNames[constantId];
}
/// A generator for export names (when minification is enabled).
///
/// For simplicity and readability of generated names, we just use the base64
/// encoding of an integer counter.
class ExportNamer {
final bool minify;
ExportNamer(this.minify);
final Set<String> _reservedNames = {};
/// Mark a name as reserved by the `wasm:export` or `wasm:weak-export`
/// annotations so that it will not be generated as a minified export name.
void reserveName(String name) {
if (!minify) return;
final added = _reservedNames.add(name);
assert(added, "Name '$name' is already reserved");
}
int _nameCounter = 0;
String _getExportName(String name) {
if (!minify) return name;
do {
name = intToBase64(_nameCounter++);
} while (_reservedNames.contains(name));
return name;
}
}
/// Manages exporting entities in a minification-aware way.
///
/// The [Exporter] stores associations between entities exported from a dynamic
/// main module and their export names in [dynamicModuleExports] so that dynamic
/// submodules can import these entities with those names.
///
/// The `export___` methods add relevant associations to [dynamicModuleExports]
/// and add the given exportable to the Wasm exports of the specified module
/// under the computed export name.
class Exporter {
final ExportNamer _namer;
Exporter(this._namer);
final DynamicModuleExports dynamicModuleExports = DynamicModuleExports();
void exportCallable(ModuleBuilder module, String name,
FunctionBuilder function, int callableReferenceId) {
dynamicModuleExports._callableNames[callableReferenceId] =
_export(module, name, function);
}
void exportConstant(ModuleBuilder module, String name, GlobalBuilder constant,
int constantId) {
dynamicModuleExports._constantNames[constantId] =
_export(module, name, constant);
}
void exportConstantInitializer(ModuleBuilder module, String name,
FunctionBuilder initializer, int constantId) {
dynamicModuleExports._constantInitializerNames[constantId] =
_export(module, name, initializer);
}
String _export(ModuleBuilder module, String name, Exportable exportable) {
final exportName = _namer._getExportName(name);
module.exports.export(exportName, exportable);
return exportName;
}
}
+17 -7
View File
@@ -67,11 +67,18 @@ class FunctionCollector {
}
// Ensure any procedures marked as exported are enqueued.
String? exportName =
translator.getPragma(member, "wasm:export", member.name.text);
final text = member.name.text;
String? exportName = translator.getPragma(member, "wasm:export", text);
if (exportName != null) {
getFunction(member.reference);
}
// Whether a procedure is strongly or weakly exported, we must not use its
// name as the export name of a different function.
exportName ??= translator.getPragma(member, "wasm:weak-export", text);
if (exportName != null) {
translator.exportNamer.reserveName(exportName);
}
}
/// If the member with the reference [target] is exported, get the export
@@ -158,9 +165,11 @@ class FunctionCollector {
final callableReferenceId =
translator.dynamicModuleInfo?.metadata.callableReferenceIds[target];
if (callableReferenceId != null) {
translator.mainModule.exports.export(
translator.exporter.exportCallable(
translator.mainModule,
_generateDynamicSubmoduleCallableName(callableReferenceId),
function);
function,
callableReferenceId);
}
}
@@ -170,7 +179,8 @@ class FunctionCollector {
});
}
String _generateDynamicSubmoduleCallableName(int key) => '#dc$key';
String _generateDynamicSubmoduleCallableName(int key) =>
'#dynamicCallable$key';
w.BaseFunction _importFunctionToDynamicSubmodule(Reference target) {
assert(translator.isDynamicSubmodule);
@@ -186,8 +196,8 @@ class FunctionCollector {
}
return translator.dynamicSubmodule.functions.import(
translator.mainModule.moduleName,
_generateDynamicSubmoduleCallableName(
dynamicSubmoduleCallableReferenceId),
translator.dynamicModuleExports!
.getCallableName(dynamicSubmoduleCallableReferenceId)!,
translator.signatureForMainModule(target),
getFunctionName(target));
}
+3 -16
View File
@@ -2,10 +2,10 @@
// 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 'dart:convert';
import 'package:kernel/ast.dart';
import 'util.dart';
class Symbols {
final bool minify;
Symbols(this.minify);
@@ -31,7 +31,7 @@ class Symbols {
final Map<SymbolConstant, int> symbolOrdinals = {};
String getMangledSymbolName(SymbolConstant symbol) {
if (minify) {
return _intToBase64(
return intToBase64(
symbolOrdinals.putIfAbsent(symbol, () => symbolOrdinals.length));
}
@@ -50,16 +50,3 @@ class Symbols {
return '${symbol.name}@${libraryReference.asLibrary.importUri.hashCode}';
}
}
List<int> _intToLittleEndianBytes(int i) {
List<int> bytes = [];
bytes.add(i & 0xFF);
i >>>= 8;
while (i != 0) {
bytes.add(i & 0xFF);
i >>>= 8;
}
return bytes;
}
String _intToBase64(int i) => base64.encode(_intToLittleEndianBytes(i));
+14
View File
@@ -23,6 +23,7 @@ import 'dispatch_table.dart';
import 'dynamic_forwarders.dart';
import 'dynamic_module_kernel_metadata.dart';
import 'dynamic_modules.dart';
import 'exports.dart';
import 'functions.dart';
import 'globals.dart';
import 'kernel_nodes.dart';
@@ -125,6 +126,9 @@ class Translator with KernelNodes {
final Symbols symbols;
final ExportNamer exportNamer;
late final Exporter exporter;
// Kernel input and context.
@override
final Component component;
@@ -177,6 +181,8 @@ class Translator with KernelNodes {
late final CompilationQueue compilationQueue;
late final FunctionCollector functions;
late final DynamicModuleExports? dynamicModuleExports;
// Information about the program used and updated by the various phases.
/// [ClassInfo]s of classes in the compilation unit and the [ClassInfo] for
@@ -452,6 +458,7 @@ class Translator with KernelNodes {
{bool enableDynamicModules = false,
required MainModuleMetadata mainModuleMetadata})
: symbols = Symbols(options.minify),
exportNamer = ExportNamer(options.minify),
libraries = component.libraries,
hierarchy =
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy {
@@ -473,6 +480,13 @@ class Translator with KernelNodes {
functions = FunctionCollector(this);
types = Types(this);
exceptionTag = ExceptionTag(this);
exporter = Exporter(exportNamer);
dynamicModuleExports =
(component.metadata[DynamicModuleExportRepository.repositoryTag]
as DynamicModuleExportRepository?)
?.mapping[component] ??= exporter.dynamicModuleExports;
}
void _initLoadLibraryImportMap() {
+15
View File
@@ -2,6 +2,8 @@
// 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 'dart:convert';
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
@@ -52,3 +54,16 @@ T addPragma<T extends Annotatable>(
coreTypes.pragmaName.fieldReference: StringConstant(pragmaName),
coreTypes.pragmaOptions.fieldReference: value ?? NullConstant(),
})));
List<int> _intToLittleEndianBytes(int i) {
List<int> bytes = [];
bytes.add(i & 0xFF);
i >>>= 8;
while (i != 0) {
bytes.add(i & 0xFF);
i >>>= 8;
}
return bytes;
}
String intToBase64(int i) => base64.encode(_intToLittleEndianBytes(i));
@@ -10,7 +10,12 @@ import 'shared/shared.dart' show Child, Other;
void main() async {
final o1 = Other();
Child();
Expect.equals('Instance of \'Other\'', o1.toString());
// Ensure that the overrides correctly call `super.toString()`, eventually
// ending at `Object.toString`. The class name may be minified, so we don't
// want to make it part of the expectation.
Expect.isTrue(o1.toString().startsWith('Instance of'));
Expect.equals(3, await helper.load('entry1.dart'));
helper.done();
}
@@ -74,6 +74,7 @@ class Dart2wasmExecutor implements TargetExecutor {
'--extra-compiler-option=--dynamic-module-main=main.dart.dill',
'--extra-compiler-option=--dynamic-module-interface='
'$rootScheme:/data/$testName/dynamic_interface.yaml',
'--extra-compiler-option=--minify',
'$rootScheme:/data/$testName/$source',
'$source.wasm',
];