[dart2wasm] Introduce an opt phase to dart2wasm.

To help facilitate this we move all IO into a separate helper
library/class. This makes it easier to have symmetric read/write
functions and to do IO within compile.dart where necessary.

Adding the new `opt` phase allows us to remove the duplicated code
between dartdev and compile_benchmark simplifying those two files a lot.
It will also allow us to more easily invoke wasm-opt within our internal
build pipeline.

For compile_benchmark we still run the opt phase independently (but
through dart2wasm) to keep the benchmark data as consistent as possible.

Change-Id: Iaa855dbc3a05abfedbc3eea4af32e3ba27e84600
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/464640
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Nate Biggs
2025-12-01 21:50:29 -08:00
committed by Commit Queue
parent 836465deaf
commit fdeb9dc575
14 changed files with 606 additions and 542 deletions
@@ -2,41 +2,70 @@
// 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:typed_data';
import 'package:_fe_analyzer_shared/src/util/colors.dart' as colors;
import 'package:dart2wasm/compile.dart';
import 'package:dart2wasm/compiler_options.dart';
import 'package:dart2wasm/io_util.dart';
import 'filesystem_io.dart' if (dart.library.js_interop) 'filesystem_js.dart';
class _BenchmarkIOManager extends CompilerPhaseInputOutputManager {
final WasmCompilerFileSystem benchmarkFileSystem;
Uint8List? moduleBytes;
_BenchmarkIOManager(this.benchmarkFileSystem, WasmCompilerOptions options)
: super(benchmarkFileSystem, options);
@override
Future<void> writeWasmModule(Uint8List wasmModule, String moduleName) async {
moduleBytes = wasmModule;
}
@override
Future<void> writeWasmSourceMap(String sourceMap, String moduleName) async {}
@override
Future<void> writeJsRuntime(String jsRuntime) async {}
@override
Future<void> writeSupportJs(String supportJs) async {}
void flushWasmModules(String wasmFile) {
benchmarkFileSystem.writeBytesSync(wasmFile, moduleBytes!);
}
}
Future main(List<String> args) async {
final sw = Stopwatch()..start();
final fileSystem = WasmCompilerFileSystem();
final result = await compileBenchmark(
fileSystem, 'pkg/dart2wasm/benchmark/self_compile_benchmark.dart');
final mainFile = 'pkg/dart2wasm/benchmark/self_compile_benchmark.dart';
final main = Uri.file('${fileSystem.sdkRoot}/$mainFile');
final options = WasmCompilerOptions(mainUri: main, outputFile: 'out.wasm');
final ioManager = _BenchmarkIOManager(fileSystem, options);
options.librariesSpecPath =
Uri.file('${fileSystem.sdkRoot}/sdk/lib/libraries.json');
await compileBenchmark(options, ioManager);
print('Dart2WasmSelfCompile(RunTimeRaw): ${sw.elapsed.inMilliseconds} ms.');
if (args.isNotEmpty) {
final module = result.wasmModules.values.single;
final wasmFile = args.single;
fileSystem.writeBytesSync(wasmFile, module.moduleBytes);
ioManager.flushWasmModules(wasmFile);
}
}
Future<CodegenResult> compileBenchmark(
WasmCompilerFileSystem fileSystem, String mainFile) async {
Future<CodegenResult> compileBenchmark(WasmCompilerOptions options,
CompilerPhaseInputOutputManager ioManager) async {
// Avoid CFE self-detecting whether `stdout`/`stderr` is terminal and supports
// colors (as we don't have `dart:io` available when we run dart2wasm in a
// wasm runtime).
colors.enableColors = false;
final main = Uri.file('${fileSystem.sdkRoot}/$mainFile');
final options = WasmCompilerOptions(mainUri: main, outputFile: 'out.wasm');
options.librariesSpecPath =
Uri.file('${fileSystem.sdkRoot}/sdk/lib/libraries.json');
final result = await compile(
options, fileSystem, (mod) => Uri.parse('$mod.maps'), (diag) {
final result = await compile(options, ioManager, (diag) {
print('Diagnostics: ${diag.severity} ${diag.plainTextFormatted}');
});
if (result is! CompilationSuccess) {
+165 -96
View File
@@ -2,11 +2,6 @@
// 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:io' show File;
import 'dart:typed_data';
import 'package:build_integration/file_system/multi_root.dart'
show MultiRootFileSystem, MultiRootFileSystemEntity;
import 'package:front_end/src/api_prototype/dynamic_module_validator.dart'
show DynamicInterfaceYamlFile;
import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem;
@@ -18,14 +13,9 @@ import 'package:front_end/src/api_unstable/vm.dart'
kernelForProgram,
CfeSeverity;
import 'package:kernel/ast.dart';
import 'package:kernel/binary/ast_from_binary.dart'
show BinaryBuilderWithMetadata;
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart'
show writeComponentToText, loadComponentFromBytes;
import 'package:kernel/library_index.dart';
import 'package:kernel/text/ast_to_text.dart';
import 'package:kernel/type_environment.dart';
import 'package:kernel/verifier.dart';
import 'package:vm/kernel_front_end.dart' show writeDepfile;
@@ -46,6 +36,7 @@ import 'deferred_loading.dart';
import 'dry_run.dart';
import 'dynamic_module_kernel_metadata.dart';
import 'dynamic_modules.dart';
import 'io_util.dart';
import 'js/method_collector.dart' show JSMethods;
import 'js/runtime_generator.dart' as js;
import 'modules.dart';
@@ -93,11 +84,17 @@ class TfaResult extends CompilationSuccess {
}
class CodegenResult extends CompilationSuccess {
final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules;
final String jsRuntime;
final String supportJs;
final String mainWasmFile;
final int numModules;
CodegenResult(this.wasmModules, this.jsRuntime, this.supportJs);
CodegenResult(this.mainWasmFile, this.numModules);
}
class OptResult extends CompilationSuccess {
final String mainWasmFile;
final int numModules;
OptResult(this.mainWasmFile, this.numModules);
}
abstract class CompilationError extends CompilationResult {}
@@ -148,6 +145,43 @@ const List<String> _librariesToIndex = [
"dart:typed_data",
];
const List<String> _binaryenFlags = [
'--enable-gc',
'--enable-reference-types',
'--enable-multivalue',
'--enable-exception-handling',
'--enable-nontrapping-float-to-int',
'--enable-sign-ext',
'--enable-bulk-memory',
'--enable-threads',
'--no-inline=*<noInline>*',
'--closed-world',
'--traps-never-happen',
'--type-unfinalizing',
'-Os',
'--type-ssa',
'--gufa',
'-Os',
'--type-merging',
'-Os',
'--type-finalizing',
'--minimize-rec-groups',
];
const List<String> _binaryenFlagsMultiModule = [
'--enable-gc',
'--enable-reference-types',
'--enable-multivalue',
'--enable-exception-handling',
'--enable-nontrapping-float-to-int',
'--enable-sign-ext',
'--enable-bulk-memory',
'--enable-threads',
'--no-inline=*<noInline>*',
'--traps-never-happen',
'-Os',
];
/// Compile a Dart file into a Wasm module.
///
/// Returns `null` if an error occurred during compilation. The
@@ -161,8 +195,7 @@ const List<String> _librariesToIndex = [
/// mappings.
Future<CompilationResult> compile(
compiler.WasmCompilerOptions options,
FileSystem fileSystem,
Uri Function(String moduleName)? sourceMapUrlGenerator,
CompilerPhaseInputOutputManager ioManager,
void Function(CfeDiagnosticMessage) handleDiagnosticMessage) async {
final wasm.Mode mode;
if (options.translatorOptions.jsCompatibility) {
@@ -177,15 +210,9 @@ Future<CompilationResult> compile(
removeAsserts: !options.translatorOptions.enableAsserts,
mode: mode);
if (options.multiRootScheme != null) {
fileSystem = MultiRootFileSystem(
options.multiRootScheme!,
options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots,
fileSystem);
}
CfeResult? cfeResult;
TfaResult? tfaResult;
CodegenResult? codegenResult;
CompilationResult? lastResult;
for (final phase in options.phases) {
@@ -194,7 +221,8 @@ Future<CompilationResult> compile(
lastResult = await _runCfePhase(
options,
target,
fileSystem,
ioManager.fileSystem,
ioManager,
handleDiagnosticMessage,
);
if (lastResult is! CfeResult) return lastResult;
@@ -202,31 +230,40 @@ Future<CompilationResult> compile(
case compiler.CompilerPhase.tfa:
lastResult = await _runTfaPhase(
cfeResult ?? await _loadCfeResult(options, fileSystem),
cfeResult ?? await _loadCfeResult(options, ioManager),
options,
target,
fileSystem,
ioManager,
);
if (lastResult is! TfaResult) return lastResult;
tfaResult = lastResult;
case compiler.CompilerPhase.codegen:
lastResult = await _runCodegenPhase(
tfaResult ?? await _loadTfaResult(options, target, fileSystem),
tfaResult ?? await _loadTfaResult(options, target, ioManager),
options,
fileSystem,
sourceMapUrlGenerator);
ioManager);
if (lastResult is! CodegenResult) return lastResult;
codegenResult = lastResult;
case compiler.CompilerPhase.opt:
lastResult = await _runOptPhase(
codegenResult ?? await _loadCodegenResult(options, ioManager),
options,
ioManager);
if (lastResult is! OptResult) return lastResult;
}
}
return lastResult!;
}
Future<CfeResult> _loadCfeResult(
compiler.WasmCompilerOptions options, FileSystem fileSystem) async {
final component = loadComponentFromBytes(
await File.fromUri((await _resolveUri(fileSystem, options.mainUri))!)
.readAsBytes());
Future<CfeResult> _loadCfeResult(compiler.WasmCompilerOptions options,
CompilerPhaseInputOutputManager ioManager) async {
final component = Component();
await ioManager.readComponent(options.mainUri, component);
final coreTypes = CoreTypes(component);
return CfeResult(component, coreTypes);
}
@@ -235,6 +272,7 @@ Future<CompilationResult> _runCfePhase(
compiler.WasmCompilerOptions options,
WasmTarget target,
FileSystem fileSystem,
CompilerPhaseInputOutputManager ioManager,
void Function(CfeDiagnosticMessage) handleDiagnosticMessage) async {
var hadCompileTimeError = false;
void diagnosticMessageHandler(CfeDiagnosticMessage message) {
@@ -276,10 +314,7 @@ Future<CompilationResult> _runCfePhase(
if (isDynamicMainModule) {
final dynamicInterfaceUri = options.dynamicInterfaceUri;
if (dynamicInterfaceUri != null) {
final resolvedDynamicInterfaceUri =
await _resolveUri(fileSystem, dynamicInterfaceUri);
final contents =
File.fromUri(resolvedDynamicInterfaceUri!).readAsStringSync();
final contents = await ioManager.readString(dynamicInterfaceUri);
final dynamicInterfaceYamlFile = DynamicInterfaceYamlFile(contents);
additionalSources = dynamicInterfaceYamlFile
.getUserLibraryUris(dynamicInterfaceUri)
@@ -288,7 +323,7 @@ Future<CompilationResult> _runCfePhase(
}
final dynamicMainModuleUri =
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
await ioManager.resolveUri(options.dynamicMainModuleUri);
final isDynamicSubmodule =
options.dynamicModuleType == DynamicModuleType.submodule;
if (isDynamicSubmodule) {
@@ -324,35 +359,43 @@ Future<CompilationResult> _runCfePhase(
final component = compilerResult!.component!;
if (options.dumpKernelAfterCfe != null) {
writeComponentToText(component,
path: options.dumpKernelAfterCfe!, showMetadata: true);
ioManager.writeComponentAsText(component, options.dumpKernelAfterCfe!);
}
if (options.emitCfe) {
await ioManager.writeComponent(component, options.outputFile);
}
return CfeResult(component, compilerResult.coreTypes!);
}
Future<TfaResult> _loadTfaResult(compiler.WasmCompilerOptions options,
WasmTarget target, FileSystem fileSystem) async {
WasmTarget target, CompilerPhaseInputOutputManager ioManager) async {
final component = createEmptyComponent();
final recordClassesRepository = _RecordClassesRepository();
final interopMethodsRepository = _InteropMethodsRepository();
component.addMetadataRepository(recordClassesRepository);
component.addMetadataRepository(interopMethodsRepository);
BinaryBuilderWithMetadata(
await File.fromUri((await _resolveUri(fileSystem, options.mainUri))!)
.readAsBytes())
.readComponent(component);
await ioManager.readComponent(options.mainUri, component);
final coreTypes = CoreTypes(component);
final libraryIndex = LibraryIndex(component, _librariesToIndex);
final classHierarchy = ClassHierarchy(component, coreTypes);
final dynamicMainModuleUri =
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
await ioManager.resolveUri(options.dynamicMainModuleUri);
final dynamicInterfaceUri =
await _resolveUri(fileSystem, options.dynamicInterfaceUri);
await ioManager.resolveUri(options.dynamicInterfaceUri);
final moduleStrategy = _createModuleStrategy(options, component, coreTypes,
target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri);
final moduleStrategy = await _createModuleStrategy(
options,
ioManager,
component,
coreTypes,
target,
classHierarchy,
dynamicMainModuleUri,
dynamicInterfaceUri);
final recordClasses = <RecordShape, Class>{};
recordClassesRepository.mapping.forEach((cls, shape) {
@@ -368,7 +411,7 @@ Future<TfaResult> _loadTfaResult(compiler.WasmCompilerOptions options,
if (isDynamicSubmodule) {
mainModuleMetadata =
await deserializeMainModuleMetadata(component, options);
await deserializeMainModuleMetadata(component, ioManager);
mainModuleMetadata.verifyDynamicSubmoduleOptions(options);
} else if (isDynamicMainModule) {
MainModuleMetadata.verifyMainModuleOptions(options);
@@ -382,7 +425,7 @@ Future<CompilationResult> _runTfaPhase(
CfeResult cfeResult,
compiler.WasmCompilerOptions options,
WasmTarget target,
FileSystem fileSystem) async {
CompilerPhaseInputOutputManager ioManager) async {
var CfeResult(:component, :coreTypes) = cfeResult;
ClosedWorldClassHierarchy classHierarchy =
@@ -400,9 +443,9 @@ Future<CompilationResult> _runTfaPhase(
classHierarchy);
final dynamicMainModuleUri =
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
await ioManager.resolveUri(options.dynamicMainModuleUri);
final dynamicInterfaceUri =
await _resolveUri(fileSystem, options.dynamicInterfaceUri);
await ioManager.resolveUri(options.dynamicInterfaceUri);
final isDynamicMainModule =
options.dynamicModuleType == DynamicModuleType.main;
final isDynamicSubmodule =
@@ -435,12 +478,18 @@ Future<CompilationResult> _runTfaPhase(
target.recordClasses = recordClasses;
if (options.dumpKernelBeforeTfa != null) {
writeComponentToText(component,
path: options.dumpKernelBeforeTfa!, showMetadata: true);
ioManager.writeComponentAsText(component, options.dumpKernelBeforeTfa!);
}
final moduleStrategy = _createModuleStrategy(options, component, coreTypes,
target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri);
final moduleStrategy = await _createModuleStrategy(
options,
ioManager,
component,
coreTypes,
target,
classHierarchy,
dynamicMainModuleUri,
dynamicInterfaceUri);
// Ensure we annotate AST nodes as entry points prior to other transformations
// looking at pragmas (such as mixin_deduplication and TFA).
@@ -469,11 +518,12 @@ Future<CompilationResult> _runTfaPhase(
if (isDynamicSubmodule) {
mainModuleMetadata =
await deserializeMainModuleMetadata(component, options);
await deserializeMainModuleMetadata(component, ioManager);
mainModuleMetadata.verifyDynamicSubmoduleOptions(options);
} else if (isDynamicMainModule) {
MainModuleMetadata.verifyMainModuleOptions(options);
await serializeMainModuleComponent(component, dynamicMainModuleUri!,
await serializeMainModuleComponent(
ioManager, component, dynamicMainModuleUri!,
optimized: false);
}
@@ -498,7 +548,7 @@ Future<CompilationResult> _runTfaPhase(
libraryIndex = LibraryIndex(component, _librariesToIndex);
}
if (options.phases.last == compiler.CompilerPhase.tfa) {
if (options.emitTfa) {
// Store metadata needed for codegen so that it can be serialized.
final recordClassesRepo = _RecordClassesRepository();
recordClasses.forEach((shape, cls) {
@@ -520,19 +570,27 @@ Future<CompilationResult> _runTfaPhase(
}());
if (options.dumpKernelAfterTfa != null) {
writeComponentToText(component,
path: options.dumpKernelAfterTfa!, showMetadata: true);
ioManager.writeComponentAsText(component, options.dumpKernelAfterTfa!);
}
if (options.emitTfa) {
await ioManager.writeComponent(component, options.outputFile);
}
return TfaResult(component, coreTypes, libraryIndex, moduleStrategy,
mainModuleMetadata, jsInteropMethods, recordClasses);
}
Future<CodegenResult> _loadCodegenResult(compiler.WasmCompilerOptions options,
CompilerPhaseInputOutputManager ioManager) async {
return CodegenResult(options.mainUri.toFilePath(),
await ioManager.getModuleCount(options.mainUri));
}
Future<CompilationResult> _runCodegenPhase(
TfaResult tfaSuccess,
compiler.WasmCompilerOptions options,
FileSystem fileSystem,
Uri Function(String moduleName)? sourceMapUrlGenerator) async {
CompilerPhaseInputOutputManager ioManager) async {
final TfaResult(
:component,
:coreTypes,
@@ -558,29 +616,32 @@ Future<CompilationResult> _runCodegenPhase(
String? depFile = options.depFile;
if (depFile != null) {
writeDepfile(
fileSystem, component.uriToSource.keys, options.outputFile, depFile);
writeDepfile(ioManager.fileSystem, component.uriToSource.keys,
options.outputFile, depFile);
}
final generateSourceMaps = options.translatorOptions.generateSourceMaps;
final modules = translator.translate(sourceMapUrlGenerator);
final wasmModules = <String, ({Uint8List moduleBytes, String? sourceMap})>{};
final modules = translator.translate(ioManager.sourceMapUrlGenerator);
final writeFutures = <Future<void>>[];
modules.forEach((moduleOutput, module) {
if (moduleOutput.skipEmit) return;
final serializer = Serializer();
module.serialize(serializer);
final wasmModuleSerialized = serializer.data;
writeFutures.add(
ioManager.writeWasmModule(serializer.data, moduleOutput.moduleName));
final sourceMap =
generateSourceMaps ? serializer.sourceMapSerializer.serialize() : null;
wasmModules[moduleOutput.moduleName] =
(moduleBytes: wasmModuleSerialized, sourceMap: sourceMap);
if (generateSourceMaps) {
final sourceMap = serializer.sourceMapSerializer.serialize();
writeFutures.add(
ioManager.writeWasmSourceMap(sourceMap, moduleOutput.moduleName));
}
});
await Future.wait(writeFutures);
final jsRuntimeFinalizer = js.RuntimeFinalizer(jsInteropMethods);
final dynamicMainModuleUri =
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
await ioManager.resolveUri(options.dynamicMainModuleUri);
final isDynamicMainModule =
options.dynamicModuleType == DynamicModuleType.main;
final isDynamicSubmodule =
@@ -602,8 +663,9 @@ Future<CompilationResult> _runCodegenPhase(
final supportJs = _generateSupportJs(options.translatorOptions);
if (isDynamicMainModule) {
await serializeMainModuleMetadata(component, translator, options);
await serializeMainModuleComponent(component, dynamicMainModuleUri!,
await serializeMainModuleMetadata(component, translator, ioManager);
await serializeMainModuleComponent(
ioManager, component, dynamicMainModuleUri!,
optimized: true);
}
@@ -612,17 +674,39 @@ Future<CompilationResult> _runCodegenPhase(
await writeLoadIdsFile(component, coreTypes, options, loadingMap);
}
return CodegenResult(wasmModules, jsRuntime, supportJs);
await ioManager.writeJsRuntime(jsRuntime);
await ioManager.writeSupportJs(supportJs);
return CodegenResult(options.outputFile, modules.length);
}
ModuleStrategy _createModuleStrategy(
Future<CompilationResult> _runOptPhase(
CodegenResult codegenResult,
compiler.WasmCompilerOptions options,
CompilerPhaseInputOutputManager ioManager) async {
final futures = <Future<void>>[];
final numModules = codegenResult.numModules;
for (int i = 0; i < numModules; i++) {
futures.add(ioManager.runWasmOpt(
codegenResult.mainWasmFile,
i,
options.useMultiModuleOpt
? _binaryenFlagsMultiModule
: _binaryenFlags));
}
await Future.wait(futures);
return OptResult(options.outputFile, numModules);
}
Future<ModuleStrategy> _createModuleStrategy(
compiler.WasmCompilerOptions options,
CompilerPhaseInputOutputManager ioManager,
Component component,
CoreTypes coreTypes,
WasmTarget target,
ClassHierarchy classHierarchy,
Uri? dynamicMainModuleUri,
Uri? dynamicInterfaceUri) {
Uri? dynamicInterfaceUri) async {
final isDynamicMainModule =
options.dynamicModuleType == DynamicModuleType.main;
final isDynamicSubmodule =
@@ -637,7 +721,7 @@ ModuleStrategy _createModuleStrategy(
component,
coreTypes,
options,
File.fromUri(dynamicInterfaceUri!).readAsStringSync(),
await ioManager.readString(dynamicInterfaceUri!),
options.dynamicInterfaceUri!);
} else if (isDynamicSubmodule) {
return DynamicSubmoduleStrategy(
@@ -677,15 +761,6 @@ void _patchMainTearOffs(CoreTypes coreTypes, Component component) {
if (mainHasType(mainArg0Type)) return patchToReturnMainTearOff(mainTearOff0);
}
Future<Uri?> _resolveUri(FileSystem fileSystem, Uri? uri) async {
if (uri == null) return null;
var fileSystemEntity = fileSystem.entityForUri(uri);
if (fileSystemEntity is MultiRootFileSystemEntity) {
fileSystemEntity = await fileSystemEntity.delegate;
}
return fileSystemEntity.uri;
}
class _RecordClassesRepository extends MetadataRepository<RecordShape> {
static const String _tag = 'dart2wasm.recordClasses';
@override
@@ -780,9 +855,3 @@ String _generateSupportJs(TranslatorOptions options) {
];
return '(${requiredFeatures.join('&&')})';
}
String writeComponentToString(Component component) {
final buffer = StringBuffer();
Printer(buffer).writeComponentFile(component);
return '$buffer';
}
+35 -1
View File
@@ -20,7 +20,8 @@ import 'translator.dart';
enum CompilerPhase {
cfe,
tfa,
codegen;
codegen,
opt;
static CompilerPhase parse(String name) {
for (final phase in values) {
@@ -31,6 +32,7 @@ enum CompilerPhase {
}
class WasmCompilerOptions {
static const int mainModuleId = 0;
final TranslatorOptions translatorOptions = TranslatorOptions();
Uri? platformPath;
@@ -54,6 +56,9 @@ class WasmCompilerOptions {
String? dumpKernelBeforeTfa;
String? dumpKernelAfterTfa;
bool dryRun = false;
Uri? wasmOptPath;
bool saveUnopt = false;
bool stripWasm = true;
List<CompilerPhase> phases = const [
CompilerPhase.cfe,
CompilerPhase.tfa,
@@ -67,6 +72,21 @@ class WasmCompilerOptions {
bool get enableDynamicModules => dynamicModuleType != null;
bool get useMultiModuleOpt =>
translatorOptions.enableDeferredLoading ||
translatorOptions.enableMultiModuleStressTestMode ||
enableDynamicModules;
String moduleNameForId(String filePath, int id, {bool emitAsMain = false}) =>
emitAsMain || id == mainModuleId
? path.basename(filePath)
: path.basename(path.setExtension(filePath, '_module$id.wasm'));
bool get emitCfe => phases.last == CompilerPhase.cfe;
bool get emitTfa => phases.last == CompilerPhase.tfa;
bool get emitCodegen => phases.last == CompilerPhase.tfa;
bool get readCodegen => phases.first == CompilerPhase.codegen;
void validate() {
if (translatorOptions.importSharedMemory &&
translatorOptions.sharedMemoryMaxPages == null) {
@@ -125,6 +145,10 @@ class WasmCompilerOptions {
if (inputExtension != '.dill') {
throw ArgumentError('Input to codegen phase must be a .dill file.');
}
case CompilerPhase.opt:
if (inputExtension != '.wasm') {
throw ArgumentError('Input to opt phase must be a .wasm file.');
}
}
// Ensure correct output file type
@@ -143,6 +167,16 @@ class WasmCompilerOptions {
throw ArgumentError(
'Output from codegen phase must be a .wasm file.');
}
case CompilerPhase.opt:
if (outputExtension != '.wasm') {
throw ArgumentError('Output from opt phase must be a .wasm file.');
}
}
if (phases.contains(CompilerPhase.opt) &&
translatorOptions.optimizationLevel == 0) {
throw ArgumentError(
'Cannot specify "opt" phase with optimization level 0');
}
}
}
+10 -6
View File
@@ -19,13 +19,14 @@ final WasmCompilerOptions _d = WasmCompilerOptions.defaultOptions();
final List<Option> options = [
Flag("help", (o, _) {}, abbr: "h", negatable: false, defaultsTo: false),
IntOption("optimization-level",
(o, value) => o.translatorOptions.optimizationLevel = value,
abbr: "O"),
Flag("import-shared-memory",
(o, value) => o.translatorOptions.importSharedMemory = value,
defaultsTo: _d.translatorOptions.importSharedMemory),
Flag("inlining", (o, value) => o.translatorOptions.inlining = value,
defaultsTo: _d.translatorOptions.inlining),
Flag("minify", (o, value) => o.translatorOptions.minify = value,
defaultsTo: _d.translatorOptions.minify),
Flag("inlining", (o, value) => o.translatorOptions.inliningOverride = value),
Flag("minify", (o, value) => o.translatorOptions.minifyOverride = value),
Flag("dry-run", (o, value) => o.dryRun = value, defaultsTo: _d.dryRun),
StringMultiOption(
"phases",
@@ -49,10 +50,10 @@ final List<Option> options = [
(o, value) => o.translatorOptions.omitExplicitTypeChecks = value,
defaultsTo: _d.translatorOptions.omitExplicitTypeChecks),
Flag("omit-implicit-checks",
(o, value) => o.translatorOptions.omitImplicitTypeChecks = value,
(o, value) => o.translatorOptions.omitImplicitTypeChecksOverride = value,
defaultsTo: _d.translatorOptions.omitImplicitTypeChecks),
Flag("omit-bounds-checks", (o, value) {
o.translatorOptions.omitBoundsChecks = value;
o.translatorOptions.omitBoundsChecksOverride = value;
}, defaultsTo: _d.translatorOptions.omitBoundsChecks),
Flag("verbose", (o, value) => o.translatorOptions.verbose = value,
defaultsTo: _d.translatorOptions.verbose),
@@ -141,6 +142,9 @@ final List<Option> options = [
Flag("validate-dynamic-modules",
(o, value) => o.validateDynamicModules = value,
defaultsTo: true, negatable: true),
UriOption("wasm-opt", (o, value) => o.wasmOptPath = value),
Flag("save-unopt", (o, value) => o.saveUnopt = value),
Flag("strip-wasm", (o, value) => o.stripWasm = value, negatable: true),
];
Map<fe.ExperimentalFlag, bool> processFeExperimentalFlags(
@@ -9,15 +9,14 @@ import 'package:kernel/ast.dart';
import 'package:kernel/binary/ast_from_binary.dart'
show BinaryBuilderWithMetadata;
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart'
show writeComponentToBinary, writeComponentToBytes;
import 'package:kernel/kernel.dart' show writeComponentToBytes;
import 'package:kernel/library_index.dart';
import 'package:path/path.dart' as path;
import 'class_info.dart';
import 'compiler_options.dart';
import 'dispatch_table.dart';
import 'dynamic_modules.dart';
import 'io_util.dart';
import 'js/method_collector.dart' show JSMethods;
import 'serialization.dart';
import 'translator.dart';
@@ -460,11 +459,13 @@ String _makeOptDillPath(String path) =>
'${path.substring(0, path.length - '.dill'.length)}.opt.dill';
Future<void> serializeMainModuleComponent(
Component component, Uri dynamicModuleMainUri,
CompilerPhaseInputOutputManager ioManager,
Component component,
Uri dynamicModuleMainUri,
{required bool optimized}) async {
// TODO(natebiggs): Serialize as a summary and filter to only necessary
// libraries.
await writeComponentToBinary(
await ioManager.writeComponent(
component,
optimized
? _makeOptDillPath(dynamicModuleMainUri.path)
@@ -505,21 +506,15 @@ Future<(Component, JSMethods)> generateDynamicSubmoduleComponent(
}
Future<MainModuleMetadata> deserializeMainModuleMetadata(
Component component, WasmCompilerOptions options) async {
final filename = options.dynamicModuleMetadataFile ??
Uri.parse(path.setExtension(
options.dynamicMainModuleUri!.toFilePath(), '.dyndata'));
final dynamicModuleMetadataBytes = await File.fromUri(filename).readAsBytes();
final source = DataDeserializer(dynamicModuleMetadataBytes, component);
Component component, CompilerPhaseInputOutputManager ioManager) async {
final source = DataDeserializer(
await ioManager.readMainDynModuleMetadataBytes(), component);
return MainModuleMetadata.deserialize(source);
}
Future<void> serializeMainModuleMetadata(Component component,
Translator translator, WasmCompilerOptions options) async {
final filename = options.dynamicModuleMetadataFile ??
Uri.parse(path.setExtension(
options.dynamicMainModuleUri!.toFilePath(), '.dyndata'));
Translator translator, CompilerPhaseInputOutputManager ioManager) async {
final serializer = DataSerializer(component);
translator.dynamicModuleInfo!.metadata.serialize(serializer, translator);
await File.fromUri(filename).writeAsBytes(serializer.takeBytes());
await ioManager.writeMainDynModuleMetadataBytes(serializer.takeBytes());
}
+3 -52
View File
@@ -2,16 +2,13 @@
// 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:io';
import 'package:front_end/src/api_prototype/standard_file_system.dart'
show StandardFileSystem;
import 'package:front_end/src/api_unstable/vm.dart' show printDiagnosticMessage;
import 'package:kernel/kernel.dart';
import 'package:path/path.dart' as path;
import 'compile.dart';
import 'compiler_options.dart';
import 'io_util.dart';
export 'package:dart2wasm/compiler_options.dart';
@@ -61,25 +58,9 @@ Future<int> generateWasm(WasmCompilerOptions options,
' - watch points = [${translatorOptions.watchPoints.map((p) => p.toString()).join(',')}]');
}
String moduleNameToWasmOutputFile(String moduleName) {
return path.join(path.dirname(options.outputFile), moduleName);
}
String moduleNameToSourceMapFile(String moduleName) {
return '${moduleNameToWasmOutputFile(moduleName)}.map';
}
Uri moduleNameToRelativeSourceMapUri(String moduleName) {
return Uri.file(path.basename(moduleNameToSourceMapFile(moduleName)));
}
final relativeSourceMapUrlMapper = translatorOptions.generateSourceMaps
? moduleNameToRelativeSourceMapUri
: null;
final fileSystem = StandardFileSystem.instance;
CompilationResult result = await compile(
options, StandardFileSystem.instance, relativeSourceMapUrlMapper,
(message) {
options, CompilerPhaseInputOutputManager(fileSystem, options), (message) {
if (!options.dryRun) printDiagnosticMessage(message, errorPrinter);
});
@@ -109,35 +90,5 @@ Future<int> generateWasm(WasmCompilerOptions options,
return 255;
}
switch (result) {
case CfeResult(:final component):
await File(options.outputFile)
.writeAsBytes(writeComponentToBytes(component));
case TfaResult(:final component):
await File(options.outputFile)
.writeAsBytes(writeComponentToBytes(component));
case CodegenResult(:final wasmModules, :final jsRuntime, :final supportJs):
final writeFutures = <Future>[];
wasmModules.forEach((moduleName, moduleInfo) {
final (:moduleBytes, :sourceMap) = moduleInfo;
final File outFile = File(moduleNameToWasmOutputFile(moduleName));
outFile.parent.createSync(recursive: true);
writeFutures.add(outFile.writeAsBytes(moduleBytes));
if (sourceMap != null) {
writeFutures.add(File(moduleNameToSourceMapFile(moduleName))
.writeAsString(sourceMap));
}
});
await Future.wait(writeFutures);
final jsFile = path.setExtension(options.outputFile, '.mjs');
await File(jsFile).writeAsString(jsRuntime);
final supportJsFile =
path.setExtension(options.outputFile, '.support.js');
await File(supportJsFile).writeAsString(supportJs);
}
return 0;
}
+184
View File
@@ -0,0 +1,184 @@
// 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 'dart:io' show File, Directory, Process, ProcessResult;
import 'dart:typed_data';
import 'package:build_integration/file_system/multi_root.dart'
show MultiRootFileSystemEntity, MultiRootFileSystem;
import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem;
import 'package:kernel/ast.dart' show Component;
import 'package:kernel/binary/ast_from_binary.dart'
show BinaryBuilderWithMetadata;
import 'package:kernel/kernel.dart'
show writeComponentToBinary, writeComponentToText;
import 'package:path/path.dart' as path;
import 'compiler_options.dart';
class CompilerPhaseInputOutputManager {
final FileSystem fileSystem;
final WasmCompilerOptions options;
CompilerPhaseInputOutputManager(FileSystem fileSystem, this.options)
: fileSystem = options.multiRootScheme != null
? MultiRootFileSystem(
options.multiRootScheme!,
options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots,
fileSystem)
: fileSystem;
String _moduleNameToWasmFile(String prefix, String moduleName) {
return path.join(path.dirname(prefix), moduleName);
}
String _moduleNameToSourceMapFile(String prefix, String moduleName) {
return '${_moduleNameToWasmFile(prefix, moduleName)}.map';
}
Uri _moduleNameToRelativeSourceMapUri(String moduleName) {
return Uri.file(path
.basename(_moduleNameToSourceMapFile(options.outputFile, moduleName)));
}
Uri Function(String)? get sourceMapUrlGenerator =>
options.translatorOptions.generateSourceMaps
? _moduleNameToRelativeSourceMapUri
: null;
Future<String> readString(Uri uri) async {
return await File.fromUri((await resolveUri(uri))!).readAsString();
}
Future<List<int>> readBytes(Uri uri) async {
return await File.fromUri((await resolveUri(uri))!).readAsBytes();
}
Future<void> readComponent(Uri componentUri, Component component) async {
BinaryBuilderWithMetadata(
await File.fromUri((await resolveUri(componentUri))!).readAsBytes())
.readComponent(component);
}
Future<void> writeComponent(Component component, String path,
{bool includeSource = true}) {
return writeComponentToBinary(component, path,
includeSource: includeSource);
}
void writeComponentAsText(Component component, String path) {
writeComponentToText(component, path: path, showMetadata: true);
}
Future<void> writeWasmModule(Uint8List wasmModule, String moduleName) {
final wasmFileName = _moduleNameToWasmFile(options.outputFile, moduleName);
final Directory dir = Directory(path.dirname(wasmFileName));
// Do this synchronously to make sure it happens before subsequent async
// operations.
if (!dir.existsSync()) {
dir.createSync(recursive: true);
}
return File(wasmFileName).writeAsBytes(wasmModule);
}
Future<void> writeWasmSourceMap(String sourceMap, String moduleName) {
return File(_moduleNameToSourceMapFile(options.outputFile, moduleName))
.writeAsString(sourceMap);
}
Future<void> writeJsRuntime(String jsRuntime) {
return File(path.setExtension(options.outputFile, '.mjs'))
.writeAsString(jsRuntime);
}
Future<void> writeSupportJs(String supportJs) {
return File(path.setExtension(options.outputFile, '.support.js'))
.writeAsString(supportJs);
}
Future<void> runWasmOpt(
String mainWasmModule, int moduleId, List<String> flags) async {
final inputModuleName = options.moduleNameForId(mainWasmModule, moduleId);
final outputModuleName =
options.moduleNameForId(options.outputFile, moduleId);
final wasmOutName =
_moduleNameToWasmFile(options.outputFile, outputModuleName);
final wasmInName = _moduleNameToWasmFile(mainWasmModule, inputModuleName);
final args = [
...flags,
wasmInName,
'-o',
wasmOutName,
if (options.translatorOptions.generateSourceMaps) ...[
'-ism',
_moduleNameToSourceMapFile(mainWasmModule, inputModuleName),
'-osm',
_moduleNameToSourceMapFile(options.outputFile, outputModuleName),
],
if (!options.stripWasm) '-g',
];
if (options.saveUnopt) {
await File(wasmInName)
.copy(path.setExtension(wasmOutName, '.unopt.wasm'));
}
final wasmOptPath = options.wasmOptPath?.toFilePath() ?? 'wasm-opt';
final result = await _runProcess(wasmOptPath, args);
if (result.exitCode != 0) {
throw Exception('wasm-opt failed with exit code ${result.exitCode}:'
'\n${result.stdout}\n${result.stderr}');
}
}
Future<ProcessResult> _runProcess(
String executable, List<String> args) async {
return await Process.run(executable, args);
}
Future<int> getModuleCount(Uri mainWasmFile) async {
final mainPath = (await resolveUri(mainWasmFile))!.toFilePath();
final files = (await Directory(path.dirname(mainPath)).list().toList());
final prefix = path.basenameWithoutExtension(mainPath);
bool isMultiModule = false;
int maxModuleId = 0;
for (final file in files) {
if (file is! File) continue;
final fileBase = path.basename(file.path);
if (!fileBase.startsWith(prefix)) continue;
if (path.extension(fileBase) != '.wasm') continue;
final fileSuffix =
path.setExtension(fileBase, '').substring(prefix.length);
if (!fileSuffix.startsWith('_module')) continue;
isMultiModule = true;
final moduleId = int.tryParse(fileSuffix.substring('_module'.length));
if (moduleId == null) continue;
maxModuleId = moduleId > maxModuleId ? moduleId : maxModuleId;
}
return isMultiModule ? maxModuleId + 1 : 1;
}
Future<Uint8List> readMainDynModuleMetadataBytes() async {
final filename = options.dynamicModuleMetadataFile ??
Uri.parse(path.setExtension(
options.dynamicMainModuleUri!.toFilePath(), '.dyndata'));
return await File.fromUri(filename).readAsBytes();
}
Future<void> writeMainDynModuleMetadataBytes(Uint8List bytes) async {
final filename = options.dynamicModuleMetadataFile ??
Uri.parse(path.setExtension(
options.dynamicMainModuleUri!.toFilePath(), '.dyndata'));
await File.fromUri(filename).writeAsBytes(bytes);
}
Future<Uri?> resolveUri(Uri? uri) async {
if (uri == null) return null;
var fileSystemEntity = fileSystem.entityForUri(uri);
if (fileSystemEntity is MultiRootFileSystemEntity) {
fileSystemEntity = await fileSystemEntity.delegate;
}
return fileSystemEntity.uri;
}
}
+4 -12
View File
@@ -4,14 +4,11 @@
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart';
import 'package:path/path.dart' as path;
import 'compiler_options.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.
@@ -24,7 +21,7 @@ Library? _enclosingLibraryForReference(Reference reference) {
}
class ModuleMetadataBuilder {
int _counter = _mainModuleId;
int _counter = WasmCompilerOptions.mainModuleId;
final WasmCompilerOptions options;
ModuleMetadataBuilder(this.options);
@@ -34,14 +31,9 @@ class ModuleMetadataBuilder {
final id = _counter++;
final moduleImportName =
options.translatorOptions.minify ? intToMinString(id) : 'module$id';
return ModuleMetadata._(
moduleImportName,
emitAsMain || id == _mainModuleId
? path.basename(options.outputFile)
: path.basename(
path.setExtension(options.outputFile, '_module$id.wasm')),
skipEmit: skipEmit,
isMain: id == _mainModuleId);
return ModuleMetadata._(moduleImportName,
options.moduleNameForId(options.outputFile, id, emitAsMain: emitAsMain),
skipEmit: skipEmit, isMain: id == WasmCompilerOptions.mainModuleId);
}
}
+8 -4
View File
@@ -44,17 +44,21 @@ class ValueOption<T> extends Option<T> {
void Function(WasmCompilerOptions o, T v) applyToOptions,
T Function(dynamic v) converter,
{String? defaultsTo,
String? abbr,
bool hide = false})
: super(name, (a) => a.addOption(name, defaultsTo: defaultsTo),
applyToOptions, converter);
: super(
name,
(a) => a.addOption(name, defaultsTo: defaultsTo, abbr: abbr),
applyToOptions,
converter);
}
class IntOption extends ValueOption<int> {
IntOption(
String name, void Function(WasmCompilerOptions o, int v) applyToOptions,
{String? defaultsTo})
{String? defaultsTo, String? abbr})
: super(name, applyToOptions, (v) => int.parse(v),
defaultsTo: defaultsTo);
defaultsTo: defaultsTo, abbr: abbr);
}
class StringOption extends ValueOption<String> {
+23 -12
View File
@@ -44,15 +44,16 @@ import 'util.dart' as util;
class TranslatorOptions {
bool enableAsserts = false;
bool importSharedMemory = false;
bool inlining = true;
int optimizationLevel = 1;
bool? inliningOverride;
bool jsCompatibility = false;
bool omitImplicitTypeChecks = false;
bool? omitImplicitTypeChecksOverride;
bool omitExplicitTypeChecks = false;
bool omitBoundsChecks = false;
bool? omitBoundsChecksOverride;
bool polymorphicSpecialization = false;
bool printKernel = false;
bool printWasm = false;
bool minify = false;
bool? minifyOverride;
bool verifyTypeChecks = false;
bool verbose = false;
bool enableExperimentalFfi = false;
@@ -67,18 +68,26 @@ class TranslatorOptions {
bool requireJsStringBuiltin = false;
List<int> watchPoints = [];
bool get inlining => inliningOverride ?? optimizationLevel >= 1;
bool get minify => minifyOverride ?? optimizationLevel >= 2;
bool get omitImplicitTypeChecks =>
omitImplicitTypeChecksOverride ?? optimizationLevel >= 3;
bool get omitBoundsChecks =>
omitBoundsChecksOverride ?? optimizationLevel >= 4;
void serialize(DataSerializer sink) {
sink.writeBool(enableAsserts);
sink.writeBool(importSharedMemory);
sink.writeBool(inlining);
sink.writeInt(optimizationLevel);
sink.writeNullable(inliningOverride, sink.writeBool);
sink.writeBool(jsCompatibility);
sink.writeBool(omitImplicitTypeChecks);
sink.writeNullable(omitImplicitTypeChecksOverride, sink.writeBool);
sink.writeBool(omitExplicitTypeChecks);
sink.writeBool(omitBoundsChecks);
sink.writeNullable(omitBoundsChecksOverride, sink.writeBool);
sink.writeBool(polymorphicSpecialization);
sink.writeBool(printKernel);
sink.writeBool(printWasm);
sink.writeBool(minify);
sink.writeNullable(minifyOverride, sink.writeBool);
sink.writeBool(verifyTypeChecks);
sink.writeBool(verbose);
sink.writeBool(enableExperimentalFfi);
@@ -97,15 +106,17 @@ class TranslatorOptions {
final TranslatorOptions options = TranslatorOptions();
options.enableAsserts = source.readBool();
options.importSharedMemory = source.readBool();
options.inlining = source.readBool();
options.optimizationLevel = source.readInt();
options.inliningOverride = source.readNullable(source.readBool);
options.jsCompatibility = source.readBool();
options.omitImplicitTypeChecks = source.readBool();
options.omitImplicitTypeChecksOverride =
source.readNullable(source.readBool);
options.omitExplicitTypeChecks = source.readBool();
options.omitBoundsChecks = source.readBool();
options.omitBoundsChecksOverride = source.readNullable(source.readBool);
options.polymorphicSpecialization = source.readBool();
options.printKernel = source.readBool();
options.printWasm = source.readBool();
options.minify = source.readBool();
options.minifyOverride = source.readNullable(source.readBool);
options.verifyTypeChecks = source.readBool();
options.verbose = source.readBool();
options.enableExperimentalFfi = source.readBool();
+73 -23
View File
@@ -13,6 +13,7 @@ final String mainDart = '${path.dirname(Platform.script.path)}/data/main.dart';
final String cfeDillName = 'main.cfe.dill';
final String tfaDillName = 'main.tfa.dill';
final String wasmOutName = 'main.wasm';
final String wasmOptOutName = 'main.opt.wasm';
Future<void> main() async {
await testSuccessCases();
@@ -21,11 +22,10 @@ Future<void> main() async {
Future<void> testSuccessCases() async {
await withTempDir((tmpDirPath) async {
final tmpDir = File(tmpDirPath);
final cfeDill = File.fromUri(tmpDir.uri.resolve(cfeDillName));
final tfaDill = File.fromUri(tmpDir.uri.resolve(tfaDillName));
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
final cfeDill = File(path.join(tmpDirPath, cfeDillName));
final tfaDill = File(path.join(tmpDirPath, tfaDillName));
final wasmOut = File(path.join(tmpDirPath, wasmOutName));
final wasmOptOut = File(path.join(tmpDirPath, wasmOptOutName));
// Run CFE and expect output
await run([
@@ -62,13 +62,24 @@ Future<void> testSuccessCases() async {
]);
Expect.isTrue(await wasmOut.exists());
Expect.isTrue((await wasmOut.stat()).size > 0);
// Run opt and expect output
await run([
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--phases=opt',
'--wasm-opt=$wasmOptExecutable',
wasmOut.path,
wasmOptOut.path,
]);
Expect.isTrue(await wasmOptOut.exists());
Expect.isTrue((await wasmOptOut.stat()).size > 0);
});
await withTempDir((tmpDirPath) async {
final tmpDir = File(tmpDirPath);
final tfaDill = File.fromUri(tmpDir.uri.resolve(tfaDillName));
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
final tfaDill = File(path.join(tmpDirPath, tfaDillName));
final wasmOut = File(path.join(tmpDirPath, wasmOutName));
// Run CFE & TFA and expect output
await run([
@@ -96,10 +107,8 @@ Future<void> testSuccessCases() async {
});
await withTempDir((tmpDirPath) async {
final tmpDir = File(tmpDirPath);
final cfeDill = File.fromUri(tmpDir.uri.resolve(tfaDillName));
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
final cfeDill = File(path.join(tmpDirPath, cfeDillName));
final wasmOut = File(path.join(tmpDirPath, wasmOutName));
// Run CFE and expect output
await run([
@@ -127,9 +136,7 @@ Future<void> testSuccessCases() async {
});
await withTempDir((tmpDirPath) async {
final tmpDir = File(tmpDirPath);
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
final wasmOut = File(path.join(tmpDirPath, wasmOutName));
// Run CFE & TFA & codegen and expect output
await run([
@@ -143,15 +150,31 @@ Future<void> testSuccessCases() async {
Expect.isTrue(await wasmOut.exists());
Expect.isTrue((await wasmOut.stat()).size > 0);
});
await withTempDir((tmpDirPath) async {
final wasmOptOut = File(path.join(tmpDirPath, wasmOptOutName));
// Run CFE & TFA & codegen & opt and expect output
await run([
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--phases=cfe,tfa,codegen,opt',
'--wasm-opt=$wasmOptExecutable',
mainDart,
wasmOptOut.path,
]);
Expect.isTrue(await wasmOptOut.exists());
Expect.isTrue((await wasmOptOut.stat()).size > 0);
});
}
Future<void> testFailureCases() async {
await withTempDir((tmpDirPath) async {
final tmpDir = File(tmpDirPath);
final cfeDill = File.fromUri(tmpDir.uri.resolve(cfeDillName));
final tfaDill = File.fromUri(tmpDir.uri.resolve(tfaDillName));
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
final cfeDill = File(path.join(tmpDirPath, cfeDillName));
final tfaDill = File(path.join(tmpDirPath, tfaDillName));
final wasmOut = File(path.join(tmpDirPath, wasmOutName));
final wasmOptOut = File(path.join(tmpDirPath, wasmOptOutName));
// CFE checks
await expectFailedRun([
@@ -177,7 +200,6 @@ Future<void> testFailureCases() async {
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--platform=$platformDill',
'--phases=tfa',
mainDart,
tfaDill.path
@@ -197,7 +219,6 @@ Future<void> testFailureCases() async {
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--platform=$platformDill',
'--phases=codegen',
mainDart,
wasmOut.path
@@ -212,6 +233,25 @@ Future<void> testFailureCases() async {
cfeDill.path
], 'Output from codegen phase must be a .wasm file');
// Opt checks
await expectFailedRun([
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--phases=opt',
mainDart,
wasmOptOut.path
], 'Input to opt phase must be a .wasm file');
await expectFailedRun([
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--phases=opt',
wasmOut.path,
cfeDill.path
], 'Output from opt phase must be a .wasm file');
// Other checks
await expectFailedRun([
dartAotExecutable,
@@ -230,6 +270,16 @@ Future<void> testFailureCases() async {
mainDart,
cfeDill.path
], 'Invalid compiler phase name');
await expectFailedRun([
dartAotExecutable,
dart2wasmSnapshot,
'--platform=$platformDill',
'--phases=opt',
'-O0',
wasmOut.path,
wasmOptOut.path
], 'Cannot specify "opt" phase with optimization level 0');
});
}
+3
View File
@@ -10,6 +10,9 @@ final dartAotExecutable = Uri.parse(Platform.resolvedExecutable)
final dart2wasmSnapshot = Uri.parse(Platform.resolvedExecutable)
.resolve('snapshots/dart2wasm_product.snapshot')
.toFilePath();
final wasmOptExecutable = Uri.parse(Platform.resolvedExecutable)
.resolve('utils/wasm-opt')
.toFilePath();
final platformDill = Uri.parse(Platform.resolvedExecutable)
.resolve('../lib/_internal/dart2wasm_platform.dill')
.toFilePath();
+15 -91
View File
@@ -58,17 +58,8 @@ LIBRARIES_JSON_ARG="--libraries-spec=$SDK_DIR/sdk/lib/libraries.json"
function find_flags {
echo -en "$(sed -n "/$1 =/,/end of $1/ p" $SDK_DIR/pkg/dartdev/lib/src/commands/compile.dart | sed '1d' | sed '$d' | tr '\n' ' ' | sed 's#\s\+# #g' | sed 's#^\s\+##' | sed 's#\s\+$##')"
}
# Use same flags as `dart compile exe`
BINARYEN_FLAGS=($(find_flags 'binaryenFlags'))
BINARYEN_FLAGS_DEFERRED_LOADING=($(find_flags 'binaryenFlagsDeferredLoading'))
OPT_FLAGS_L0=($(find_flags 'optimizationLevel0Flags'))
OPT_FLAGS_L1=($(find_flags 'optimizationLevel1Flags'))
OPT_FLAGS_L2=($(find_flags 'optimizationLevel2Flags'))
OPT_FLAGS_L3=($(find_flags 'optimizationLevel3Flags'))
OPT_FLAGS_L4=($(find_flags 'optimizationLevel4Flags'))
RUN_BINARYEN=1
MULTI_MODULE=0
RUN_SRC=0
GENERATE_SOURCE_MAP=1
COMPILE_BENCHMARK_BASE_NAME=""
@@ -79,7 +70,6 @@ SNAPSHOT_NAME="dart2wasm"
# flags.
VM_ARGS=()
DART2WASM_ARGS=("--require-js-string-builtin")
ADDITIONAL_BINARYEN_FLAGS=()
DART_FILE=""
OUTPUT_FILE=""
while [ $# -gt 0 ]; do
@@ -100,11 +90,6 @@ while [ $# -gt 0 ]; do
shift
;;
-g | --no-strip-wasm)
ADDITIONAL_BINARYEN_FLAGS+=("-g")
shift
;;
--compiler-asserts)
SNAPSHOT_NAME="dart2wasm_asserts"
VM_ARGS+=("--enable-asserts")
@@ -112,52 +97,16 @@ while [ $# -gt 0 ]; do
;;
-O0 | --optimization-level=0)
DART2WASM_ARGS+=(${OPT_FLAGS_L0[@]})
DART2WASM_ARGS+=("-O0")
RUN_BINARYEN=0
shift
;;
-O1 | --optimization-level=1)
DART2WASM_ARGS+=(${OPT_FLAGS_L1[@]})
RUN_BINARYEN=1
shift
;;
-O2 | --optimization-level=2)
DART2WASM_ARGS+=(${OPT_FLAGS_L2[@]})
RUN_BINARYEN=1
shift
;;
-O3 | --optimization-level=3)
DART2WASM_ARGS+=(${OPT_FLAGS_L3[@]})
RUN_BINARYEN=1
shift
;;
-O4 | --optimization-level=4)
DART2WASM_ARGS+=(${OPT_FLAGS_L4[@]})
RUN_BINARYEN=1
shift
;;
--extra-compiler-option=--platform=*)
PLATFORM_FILENAME="${1#--extra-compiler-option=--platform=}"
shift
;;
--enable-deferred-loading | --extra-compiler-option=--enable-deferred-loading)
MULTI_MODULE=1
DART2WASM_ARGS+=("--enable-deferred-loading")
shift
;;
--extra-compiler-option=--enable-multi-module-stress-test-mode)
MULTI_MODULE=1
DART2WASM_ARGS+=("--enable-multi-module-stress-test-mode")
shift
;;
--extra-compiler-option=--no-js-compatibility)
PLATFORM_FILENAME="$BIN_DIR/dart2wasm_platform.dill"
DART2WASM_ARGS+=("--no-js-compatibility")
@@ -243,9 +192,9 @@ COMPILER_GZIP_SIZE=0
function run_compiler() {
if [ $RUN_SRC -eq 1 ]; then
dart2wasm_command=("$DART" "${VM_ARGS[@]}" "$DART2WASM_SRC" "$LIBRARIES_JSON_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$OUTPUT_FILE")
dart2wasm_command=("$DART" "${VM_ARGS[@]}" "$DART2WASM_SRC" "$LIBRARIES_JSON_ARG" "${DART2WASM_ARGS[@]}" "--phases=cfe,tfa,codegen" "$DART_FILE" "$OUTPUT_FILE")
else
dart2wasm_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$OUTPUT_FILE")
dart2wasm_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "--phases=cfe,tfa,codegen" "$DART_FILE" "$OUTPUT_FILE")
fi
if [ -n "$COMPILE_BENCHMARK_BASE_NAME" ]; then
@@ -257,17 +206,11 @@ function run_compiler() {
MJS_SIZE=$SIZE
MJS_GZIP_SIZE=$GZIP_SIZE
if [ $MULTI_MODULE -eq 1 ]; then
for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do
measure_size $OUTPUT_FILE
(( COMPILER_SIZE+=$SIZE ))
(( COMPILER_GZIP_SIZE+=$GZIP_SIZE ))
done
else
for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do
measure_size $OUTPUT_FILE
COMPILER_SIZE=$SIZE
COMPILER_GZIP_SIZE=$GZIP_SIZE
fi
(( COMPILER_SIZE+=$SIZE ))
(( COMPILER_GZIP_SIZE+=$GZIP_SIZE ))
done
else
"${dart2wasm_command[@]}"
fi
@@ -278,43 +221,24 @@ BINARYEN_MEMORY=0
BINARYEN_SIZE=0
BINARYEN_GZIP_SIZE=0
function run_binaryen_single() {
OUTPUT_FILE="$1"
shift
if [ $GENERATE_SOURCE_MAP -eq 1 ]; then
SOURCE_MAP_FLAGS=("-ism" "${OUTPUT_FILE}.map" "-osm" "${OUTPUT_FILE}.map")
function run_binaryen() {
if [ $RUN_SRC -eq 1 ]; then
opt_command=("$DART" "${VM_ARGS[@]}" "$DART2WASM_SRC" "$LIBRARIES_JSON_ARG" "${DART2WASM_ARGS[@]}" "--phases=opt" "--wasm-opt=$BINARYEN" "$OUTPUT_FILE" "$OUTPUT_FILE")
else
opt_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "--phases=opt" "--wasm-opt=$BINARYEN" "$OUTPUT_FILE" "$OUTPUT_FILE")
fi
binaryen_command=("$BINARYEN" "${ADDITIONAL_BINARYEN_FLAGS[@]}" "$@" "${SOURCE_MAP_FLAGS[@]}" "$OUTPUT_FILE" -o "$OUTPUT_FILE")
if [ -n "$COMPILE_BENCHMARK_BASE_NAME" ]; then
# If we're measuring run each binaryen command sequentially.
measure ${binaryen_command[@]}
measure "${opt_command[@]}"
BINARYEN_TIME=$(echo "$BINARYEN_TIME + $TIME" | bc)
BINARYEN_MEMORY=$(($BINARYEN_MEMORY > $MEMORY ? $BINARYEN_MEMORY : $MEMORY ))
measure_size $OUTPUT_FILE
BINARYEN_SIZE=$(echo "$BINARYEN_SIZE + $SIZE" | bc)
BINARYEN_GZIP_SIZE=$(echo "$BINARYEN_GZIP_SIZE + $GZIP_SIZE" | bc)
else
${binaryen_command[@]} &
fi
}
function run_binaryen() {
if [ $MULTI_MODULE -eq 1 ]; then
# Iterate over all matching wasm files and optimize them concurrently in
# different processes.
for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do
run_binaryen_single "$OUTPUT_FILE" "${BINARYEN_FLAGS_DEFERRED_LOADING[@]}"
done
else
run_binaryen_single "$OUTPUT_FILE" "${BINARYEN_FLAGS[@]}"
fi
wait
if [ -n "$COMPILE_BENCHMARK_BASE_NAME" ]; then
MAX_MEMORY=$(($COMPILER_MEMORY > $BINARYEN_MEMORY ? $COMPILER_MEMORY : $BINARYEN_MEMORY ))
TOTAL_TIME=$(echo "$COMPILER_TIME + $BINARYEN_TIME" | bc)
else
"${opt_command[@]}"
fi
}
+29 -215
View File
@@ -723,86 +723,6 @@ class CompileWasmCommand extends CompileSubcommandCommand {
static const String commandName = 'wasm';
static const String help = 'Compile Dart to a WebAssembly/WasmGC module.';
// The unique place where we store various flags for dart2wasm & binaryen.
//
// Other uses (e.g. pkg/dart2wasm/tool/compile_benchmark) will grep in this
// file for the flags. So please keep the formatting.
final List<String> binaryenFlags = _flagList('''
--enable-gc
--enable-reference-types
--enable-multivalue
--enable-exception-handling
--enable-nontrapping-float-to-int
--enable-sign-ext
--enable-bulk-memory
--enable-threads
--no-inline=*<noInline>*
--closed-world
--traps-never-happen
--type-unfinalizing
-Os
--type-ssa
--gufa
-Os
--type-merging
-Os
--type-finalizing
--minimize-rec-groups
'''); // end of binaryenFlags
final List<String> binaryenFlagsDeferredLoading = _flagList('''
--enable-gc
--enable-reference-types
--enable-multivalue
--enable-exception-handling
--enable-nontrapping-float-to-int
--enable-sign-ext
--enable-bulk-memory
--enable-threads
--no-inline=*<noInline>*
--traps-never-happen
-Os
'''); // end of binaryenFlagsDeferredLoading
final List<String> optimizationLevel0Flags = _flagList('''
--no-inlining
--no-minify
'''); // end of optimizationLevel0Flags
final List<String> optimizationLevel1Flags = _flagList('''
--inlining
--no-minify
'''); // end of optimizationLevel1Flags
final List<String> optimizationLevel2Flags = _flagList('''
--inlining
--minify
'''); // end of optimizationLevel2Flags
final List<String> optimizationLevel3Flags = _flagList('''
--inlining
--minify
--omit-implicit-checks
'''); // end of optimizationLevel3Flags
final List<String> optimizationLevel4Flags = _flagList('''
--inlining
--minify
--omit-implicit-checks
--omit-bounds-checks
'''); // end of optimizationLevel4Flags
static List<String> _flagList(String lines) => lines
.split('\n')
.map((line) => line.trim())
.where((line) => line.isNotEmpty)
.toList();
CompileWasmCommand({bool verbose = false})
: super(commandName, help, verbose) {
argParser
@@ -857,6 +777,14 @@ class CompileWasmCommand extends CompileSubcommandCommand {
valueHelp: 'page count',
hide: !verbose,
)
..addMultiOption('phases',
help: 'Specifies which phases of the dart2wasm compiler to run. Each '
'phase will emit a partial result that is then the input to the '
'next phase.',
allowed: ['cfe', 'tfa', 'codegen', 'opt'],
defaultsTo: ['cfe', 'tfa', 'codegen', 'opt'],
hide: !verbose,
splitCommas: true)
..addMultiOption(
'extra-compiler-option',
abbr: 'E',
@@ -872,13 +800,6 @@ class CompileWasmCommand extends CompileSubcommandCommand {
allowed: ['0', '1', '2', '3', '4'],
defaultsTo: '1',
valueHelp: 'level',
allowedHelp: {
'0': optimizationLevel0Flags.join(' '),
'1': optimizationLevel1Flags.join(' '),
'2': optimizationLevel2Flags.join(' '),
'3': optimizationLevel3Flags.join(' '),
'4': optimizationLevel4Flags.join(' '),
},
hide: !verbose,
)
..addFlag(
@@ -964,43 +885,23 @@ class CompileWasmCommand extends CompileSubcommandCommand {
}
}
final isDeferredLoading = args.flag('enable-deferred-loading');
// Used in testing to force multiple modules.
final isMultiStressTestMode = extraCompilerOptions
.any((e) => e.contains('enable-multi-module-stress-test'));
final isMultiModule = isDeferredLoading || isMultiStressTestMode;
final optimizationLevel = int.parse(args.option('optimization-level')!);
final runWasmOpt =
optimizationLevel >= 1 && path.extension(outputFile) == '.wasm';
if (runWasmOpt && !checkArtifactExists(sdk.wasmOpt)) {
return 255;
int? optimizationLevel;
List<String> phases = args.multiOption('phases');
if (args.wasParsed('phases')) {}
if (args.option('optimization-level') != null) {
optimizationLevel = int.tryParse(args.option('optimization-level')!);
if (optimizationLevel == null) {
usageException(
'Error: The --optimization-level flag must specify a number!');
}
if (optimizationLevel == 0) {
if (!args.wasParsed('phases')) {
// Don't add the opt phase.
phases.removeLast();
}
}
}
void handleOverride(List<String> flags, String name, bool? value) {
// If no override provided, default to what -O implies.
if (value == null) return;
flags.removeWhere((option) => option == '--no-$name');
flags.removeWhere((option) => option == '--$name');
// Explicitly use the flag value, irrespective of -O settings.
value ? flags.add('--$name') : flags.add('--no-$name');
}
final optimizationFlags = (switch (optimizationLevel) {
0 => optimizationLevel0Flags,
1 => optimizationLevel1Flags,
2 => optimizationLevel2Flags,
3 => optimizationLevel3Flags,
4 => optimizationLevel4Flags,
_ => throw 'unreachable',
})
.toList();
handleOverride(optimizationFlags, 'minify',
args.wasParsed('minify') ? args.flag('minify') : null);
final generateSourceMap = args.flag('source-maps');
final enabledExperiments = args.enabledExperiments;
final dart2wasmCommand = [
@@ -1013,20 +914,19 @@ class CompileWasmCommand extends CompileSubcommandCommand {
if (args.flag('print-kernel')) '--print-kernel',
if (args.flag(enableAssertsOption.flag)) '--${enableAssertsOption.flag}',
if (!generateSourceMap) '--no-source-maps',
if (isDeferredLoading) '--enable-deferred-loading',
if (optimizationLevel != null) '--optimization-level=$optimizationLevel',
if (args.flag('minify')) '--minify',
if (args.flag('strip-wasm')) '--strip-wasm',
if (args.flag('enable-deferred-loading')) '--enable-deferred-loading',
for (final define in defines) '-D$define',
if (maxPages != null) ...[
'--import-shared-memory',
'--shared-memory-max-pages=$maxPages',
],
'--phases=${phases.join(",")}',
'--wasm-opt=${sdk.wasmOpt}',
...enabledExperiments.map((e) => '--enable-experiment=$e'),
// First we pass flags based on the optimization level.
...optimizationFlags,
// Then we pass any extra compiler flags through.
...extraCompilerOptions,
sourcePath,
outputFile,
];
@@ -1042,102 +942,16 @@ class CompileWasmCommand extends CompileSubcommandCommand {
return compileErrorExitCode;
}
final bool strip = args.flag('strip-wasm');
// When running in dry run mode there will not be any file emitted.
final isDryRun = extraCompilerOptions.any((e) => e.contains('dry-run'));
if (isDryRun) return 0;
if (runWasmOpt) {
if (isMultiModule) {
// Iterate over all matching wasm files and optimize them concurrently
// in different processes.
final outputFiles = await _listMultiWasmModules(
path.dirname(outputFile), outputFileBasename);
final futures = <Future<int>>[];
for (final f in outputFiles) {
final baseFileName = path.setExtension(f.path, '');
futures.add(optimize(baseFileName, f.path,
deferredLoadingEnabled: true,
generateSourceMap: generateSourceMap,
strip: strip));
}
final exitCode = (await Future.wait(futures))
.firstWhere((r) => r != 0, orElse: () => 0);
if (exitCode != 0) return exitCode;
} else {
final exitCode = await optimize(outputFileBasename, outputFile,
deferredLoadingEnabled: false,
generateSourceMap: generateSourceMap,
strip: strip);
if (exitCode != 0) return exitCode;
}
}
final mjsFile = '$outputFileBasename.mjs';
log.stdout(
"Generated wasm module '$outputFile', and JS init file '$mjsFile'.");
return 0;
}
Future<List<File>> _listMultiWasmModules(
String outputDir, String outputFileBasename) async {
final files = <File>[];
final outputFiles = await Directory(outputDir).list().toList();
// When multiple modules are produced from wasm (e.g. with deferred
// loading), the compiler emits files:
// - basename.wasm (main module)
// - basename_module{1...N}.wasm (extra modules)
for (final f in outputFiles) {
if (f is! File) continue;
if (!path.split(f.path).last.startsWith(outputFileBasename)) continue;
if (path.extension(f.path) != '.wasm') continue;
files.add(f);
}
return files;
}
Future<int> optimize(String outputFileBasename, String outputFile,
{required bool deferredLoadingEnabled,
required bool generateSourceMap,
required bool strip}) async {
final unoptFile = '$outputFileBasename.unopt.wasm';
File(outputFile).renameSync(unoptFile);
final unoptSourceMapFile = '$outputFileBasename.unopt.wasm.map';
if (generateSourceMap) {
File('$outputFile.map').renameSync(unoptSourceMapFile);
}
final flags = [
...(deferredLoadingEnabled
? binaryenFlagsDeferredLoading
: binaryenFlags),
if (!strip) '-g',
if (generateSourceMap) ...[
'-ism',
unoptSourceMapFile,
'-osm',
'$outputFile.map'
]
];
if (verbose) {
log.stdout('Optimizing output with: ${sdk.wasmOpt} $flags');
}
final processResult = Process.runSync(
sdk.wasmOpt,
[...flags, '-o', outputFile, unoptFile],
);
if (processResult.exitCode != 0) {
log.stderr('Error: Wasm compilation failed while optimizing output');
log.stderr(processResult.stderr);
return compileErrorExitCode;
}
return 0;
}
}
abstract class CompileSubcommandCommand extends DartdevCommand {