[dart2wasm] Introduce phases to dart2wasm.
To support internal compilations, dart2wasm must be able to run in phases. There are a few reasons for this: 1) Kernel transforms are run on the program after the CFE has run. We must emit a dill that frameworks can transform and then pass the transformed dill back to dart2wasm. 2) This allows us to avoid forge limits by running each phase of the compiler in separate blaze actions. TFA has the chance of running long on large programs and so it might be beneficial to run it as its own action. This implementation currently supports 3 phases: "cfe", "tfa", "codegen" They can be run collectively or in any consecutive combination. Phases are specified via a '--phases' multi-option. Any data that needs to be passed between the phases is encoded directly into the serialized dill. This also opens up the opportunity to make "opt" its own phase that runs wasm-opt on the wasm emitted from the codegen phase. Change-Id: Ide830763f7063c7ab880e8e54dc47bd32fd4e7cd Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/455280 Commit-Queue: Nate Biggs <natebiggs@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
@@ -11,7 +11,7 @@ import 'filesystem_io.dart' if (dart.library.js_interop) 'filesystem_js.dart';
|
||||
Future main(List<String> args) async {
|
||||
final sw = Stopwatch()..start();
|
||||
final fileSystem = WasmCompilerFileSystem();
|
||||
final result = await compile(
|
||||
final result = await compileBenchmark(
|
||||
fileSystem, 'pkg/dart2wasm/benchmark/self_compile_benchmark.dart');
|
||||
print('Dart2WasmSelfCompile(RunTimeRaw): ${sw.elapsed.inMilliseconds} ms.');
|
||||
|
||||
@@ -22,7 +22,7 @@ Future main(List<String> args) async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<CompilationSuccess> compile(
|
||||
Future<CodegenResult> compileBenchmark(
|
||||
WasmCompilerFileSystem fileSystem, String mainFile) 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
|
||||
@@ -35,12 +35,12 @@ Future<CompilationSuccess> compile(
|
||||
options.librariesSpecPath =
|
||||
Uri.file('${fileSystem.sdkRoot}/sdk/lib/libraries.json');
|
||||
|
||||
final result = await compileToModule(
|
||||
final result = await compile(
|
||||
options, fileSystem, (mod) => Uri.parse('$mod.maps'), (diag) {
|
||||
print('Diagnostics: ${diag.severity} ${diag.plainTextFormatted}');
|
||||
});
|
||||
if (result is! CompilationSuccess) {
|
||||
throw 'Compilation Failed: $result';
|
||||
}
|
||||
return result;
|
||||
return result as CodegenResult;
|
||||
}
|
||||
|
||||
+313
-65
@@ -16,9 +16,12 @@ 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;
|
||||
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';
|
||||
@@ -41,6 +44,7 @@ import 'deferred_loading.dart';
|
||||
import 'dry_run.dart';
|
||||
import 'dynamic_module_kernel_metadata.dart';
|
||||
import 'dynamic_modules.dart';
|
||||
import 'js/method_collector.dart' show JSMethods;
|
||||
import 'js/runtime_generator.dart' as js;
|
||||
import 'modules.dart';
|
||||
import 'record_class_generator.dart';
|
||||
@@ -48,6 +52,7 @@ import 'records.dart';
|
||||
import 'target.dart' as wasm show Mode;
|
||||
import 'target.dart' hide Mode;
|
||||
import 'translator.dart';
|
||||
import 'util.dart';
|
||||
|
||||
sealed class CompilationResult {}
|
||||
|
||||
@@ -57,12 +62,40 @@ class CompilationDryRunError extends CompilationDryRunResult {}
|
||||
|
||||
class CompilationDryRunSuccess extends CompilationDryRunResult {}
|
||||
|
||||
class CompilationSuccess extends CompilationResult {
|
||||
sealed class CompilationSuccess extends CompilationResult {}
|
||||
|
||||
class CfeResult extends CompilationSuccess {
|
||||
final Component component;
|
||||
final CoreTypes coreTypes;
|
||||
|
||||
CfeResult(this.component, this.coreTypes);
|
||||
}
|
||||
|
||||
class TfaResult extends CompilationSuccess {
|
||||
final Component component;
|
||||
final CoreTypes coreTypes;
|
||||
final LibraryIndex libraryIndex;
|
||||
final ModuleStrategy moduleStrategy;
|
||||
final MainModuleMetadata mainModuleMetadata;
|
||||
final JSMethods jsInteropMethods;
|
||||
final Map<RecordShape, Class> recordClasses;
|
||||
|
||||
TfaResult(
|
||||
this.component,
|
||||
this.coreTypes,
|
||||
this.libraryIndex,
|
||||
this.moduleStrategy,
|
||||
this.mainModuleMetadata,
|
||||
this.jsInteropMethods,
|
||||
this.recordClasses);
|
||||
}
|
||||
|
||||
class CodegenResult extends CompilationSuccess {
|
||||
final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules;
|
||||
final String jsRuntime;
|
||||
final String supportJs;
|
||||
|
||||
CompilationSuccess(this.wasmModules, this.jsRuntime, this.supportJs);
|
||||
CodegenResult(this.wasmModules, this.jsRuntime, this.supportJs);
|
||||
}
|
||||
|
||||
abstract class CompilationError extends CompilationResult {}
|
||||
@@ -124,20 +157,12 @@ const List<String> _librariesToIndex = [
|
||||
/// This value will be added to the Wasm module in `sourceMappingURL` section.
|
||||
/// When this argument is null the code generator does not generate source
|
||||
/// mappings.
|
||||
Future<CompilationResult> compileToModule(
|
||||
Future<CompilationResult> compile(
|
||||
compiler.WasmCompilerOptions options,
|
||||
FileSystem fileSystem,
|
||||
Uri Function(String moduleName)? sourceMapUrlGenerator,
|
||||
void Function(CfeDiagnosticMessage) handleDiagnosticMessage,
|
||||
{void Function(String, String)? writeFile}) async {
|
||||
var hadCompileTimeError = false;
|
||||
void diagnosticMessageHandler(CfeDiagnosticMessage message) {
|
||||
if (message.severity == CfeSeverity.error) {
|
||||
hadCompileTimeError = true;
|
||||
}
|
||||
handleDiagnosticMessage(message);
|
||||
}
|
||||
|
||||
final wasm.Mode mode;
|
||||
if (options.translatorOptions.jsCompatibility) {
|
||||
mode = wasm.Mode.jsCompatibility;
|
||||
@@ -150,6 +175,68 @@ Future<CompilationResult> compileToModule(
|
||||
options.translatorOptions.enableExperimentalWasmInterop,
|
||||
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;
|
||||
CompilationResult? lastResult;
|
||||
|
||||
for (final phase in options.phases) {
|
||||
switch (phase) {
|
||||
case compiler.CompilerPhase.cfe:
|
||||
lastResult = await _runCfePhase(
|
||||
options, target, fileSystem, handleDiagnosticMessage);
|
||||
if (lastResult is! CfeResult) return lastResult;
|
||||
cfeResult = lastResult;
|
||||
|
||||
case compiler.CompilerPhase.tfa:
|
||||
lastResult = await _runTfaPhase(
|
||||
cfeResult ?? await _loadCfeResult(options),
|
||||
options,
|
||||
target,
|
||||
fileSystem);
|
||||
if (lastResult is! TfaResult) return lastResult;
|
||||
tfaResult = lastResult;
|
||||
|
||||
case compiler.CompilerPhase.codegen:
|
||||
lastResult = await _runCodegenPhase(
|
||||
tfaResult ?? await _loadTfaResult(options, target, fileSystem),
|
||||
options,
|
||||
fileSystem,
|
||||
sourceMapUrlGenerator);
|
||||
}
|
||||
}
|
||||
|
||||
return lastResult!;
|
||||
}
|
||||
|
||||
Future<CfeResult> _loadCfeResult(compiler.WasmCompilerOptions options) async {
|
||||
final component =
|
||||
loadComponentFromBytes(await File.fromUri(options.mainUri).readAsBytes());
|
||||
final coreTypes = CoreTypes(component);
|
||||
return CfeResult(component, coreTypes);
|
||||
}
|
||||
|
||||
Future<CompilationResult> _runCfePhase(
|
||||
compiler.WasmCompilerOptions options,
|
||||
WasmTarget target,
|
||||
FileSystem fileSystem,
|
||||
void Function(CfeDiagnosticMessage) handleDiagnosticMessage,
|
||||
{void Function(String, String)? writeFile}) async {
|
||||
var hadCompileTimeError = false;
|
||||
void diagnosticMessageHandler(CfeDiagnosticMessage message) {
|
||||
if (message.severity == CfeSeverity.error) {
|
||||
hadCompileTimeError = true;
|
||||
}
|
||||
handleDiagnosticMessage(message);
|
||||
}
|
||||
|
||||
CompilerOptions compilerOptions = CompilerOptions()
|
||||
..target = target
|
||||
// This is a dummy directory that always exists. This option should be
|
||||
@@ -169,21 +256,6 @@ Future<CompilationResult> compileToModule(
|
||||
..verbose = false
|
||||
..onDiagnostic = diagnosticMessageHandler
|
||||
..fileSystem = fileSystem;
|
||||
if (options.multiRootScheme != null) {
|
||||
compilerOptions.fileSystem = MultiRootFileSystem(
|
||||
options.multiRootScheme!,
|
||||
options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots,
|
||||
compilerOptions.fileSystem);
|
||||
}
|
||||
|
||||
Future<Uri?> resolveUri(Uri? uri) async {
|
||||
if (uri == null) return null;
|
||||
var fileSystemEntity = compilerOptions.fileSystem.entityForUri(uri);
|
||||
if (fileSystemEntity is MultiRootFileSystemEntity) {
|
||||
fileSystemEntity = await fileSystemEntity.delegate;
|
||||
}
|
||||
return fileSystemEntity.uri;
|
||||
}
|
||||
|
||||
if (options.platformPath != null) {
|
||||
compilerOptions.sdkSummary = options.platformPath;
|
||||
@@ -191,10 +263,8 @@ Future<CompilationResult> compileToModule(
|
||||
compilerOptions.compileSdk = true;
|
||||
}
|
||||
|
||||
final dynamicMainModuleUri = await resolveUri(options.dynamicMainModuleUri);
|
||||
final dynamicInterfaceUri = await resolveUri(options.dynamicInterfaceUri);
|
||||
final isDynamicMainModule =
|
||||
options.dynamicModuleType == DynamicModuleType.main;
|
||||
final dynamicMainModuleUri =
|
||||
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
|
||||
final isDynamicSubmodule =
|
||||
options.dynamicModuleType == DynamicModuleType.submodule;
|
||||
if (isDynamicSubmodule) {
|
||||
@@ -227,19 +297,72 @@ Future<CompilationResult> compileToModule(
|
||||
if (hadCompileTimeError) {
|
||||
return CFECompileTimeErrors(compilerResult?.component);
|
||||
}
|
||||
assert(compilerResult != null);
|
||||
|
||||
Component component = compilerResult!.component!;
|
||||
CoreTypes coreTypes = compilerResult.coreTypes!;
|
||||
|
||||
ClosedWorldClassHierarchy classHierarchy =
|
||||
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy;
|
||||
LibraryIndex libraryIndex = LibraryIndex(component, _librariesToIndex);
|
||||
final component = compilerResult!.component!;
|
||||
|
||||
if (options.dumpKernelAfterCfe != null && writeFile != null) {
|
||||
writeFile(options.dumpKernelAfterCfe!, writeComponentToString(component));
|
||||
}
|
||||
|
||||
return CfeResult(component, compilerResult.coreTypes!);
|
||||
}
|
||||
|
||||
Future<TfaResult> _loadTfaResult(compiler.WasmCompilerOptions options,
|
||||
WasmTarget target, FileSystem fileSystem) async {
|
||||
final component = createEmptyComponent();
|
||||
final recordClassesRepository = _RecordClassesRepository();
|
||||
final interopMethodsRepository = _InteropMethodsRepository();
|
||||
component.addMetadataRepository(recordClassesRepository);
|
||||
component.addMetadataRepository(interopMethodsRepository);
|
||||
|
||||
BinaryBuilderWithMetadata(await File.fromUri(options.mainUri).readAsBytes())
|
||||
.readComponent(component);
|
||||
final coreTypes = CoreTypes(component);
|
||||
final libraryIndex = LibraryIndex(component, _librariesToIndex);
|
||||
final classHierarchy = ClassHierarchy(component, coreTypes);
|
||||
final dynamicMainModuleUri =
|
||||
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
|
||||
final dynamicInterfaceUri =
|
||||
await _resolveUri(fileSystem, options.dynamicInterfaceUri);
|
||||
|
||||
final moduleStrategy = _createModuleStrategy(options, component, coreTypes,
|
||||
target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri);
|
||||
|
||||
final recordClasses = <RecordShape, Class>{};
|
||||
recordClassesRepository.mapping.forEach((cls, shape) {
|
||||
recordClasses[shape] = cls;
|
||||
});
|
||||
|
||||
final isDynamicMainModule =
|
||||
options.dynamicModuleType == DynamicModuleType.main;
|
||||
final isDynamicSubmodule =
|
||||
options.dynamicModuleType == DynamicModuleType.submodule;
|
||||
MainModuleMetadata mainModuleMetadata =
|
||||
MainModuleMetadata.empty(options.translatorOptions, options.environment);
|
||||
|
||||
if (isDynamicSubmodule) {
|
||||
mainModuleMetadata =
|
||||
await deserializeMainModuleMetadata(component, options);
|
||||
mainModuleMetadata.verifyDynamicSubmoduleOptions(options);
|
||||
} else if (isDynamicMainModule) {
|
||||
MainModuleMetadata.verifyMainModuleOptions(options);
|
||||
}
|
||||
|
||||
return TfaResult(component, coreTypes, libraryIndex, moduleStrategy,
|
||||
mainModuleMetadata, interopMethodsRepository.mapping, recordClasses);
|
||||
}
|
||||
|
||||
Future<CompilationResult> _runTfaPhase(
|
||||
CfeResult cfeResult,
|
||||
compiler.WasmCompilerOptions options,
|
||||
WasmTarget target,
|
||||
FileSystem fileSystem,
|
||||
{void Function(String, String)? writeFile}) async {
|
||||
var CfeResult(:component, :coreTypes) = cfeResult;
|
||||
|
||||
ClosedWorldClassHierarchy classHierarchy =
|
||||
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy;
|
||||
LibraryIndex libraryIndex = LibraryIndex(component, _librariesToIndex);
|
||||
|
||||
if (options.deleteToStringPackageUri.isNotEmpty) {
|
||||
to_string_transformer.transformComponent(
|
||||
component, options.deleteToStringPackageUri);
|
||||
@@ -250,6 +373,15 @@ Future<CompilationResult> compileToModule(
|
||||
coreTypes,
|
||||
classHierarchy);
|
||||
|
||||
final dynamicMainModuleUri =
|
||||
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
|
||||
final dynamicInterfaceUri =
|
||||
await _resolveUri(fileSystem, options.dynamicInterfaceUri);
|
||||
final isDynamicMainModule =
|
||||
options.dynamicModuleType == DynamicModuleType.main;
|
||||
final isDynamicSubmodule =
|
||||
options.dynamicModuleType == DynamicModuleType.submodule;
|
||||
|
||||
if (isDynamicSubmodule) {
|
||||
// Join the submodule libraries with the TFAed component from the main
|
||||
// module compilation. JS interop transformer must be run before this since
|
||||
@@ -280,26 +412,8 @@ Future<CompilationResult> compileToModule(
|
||||
writeFile(options.dumpKernelBeforeTfa!, writeComponentToString(component));
|
||||
}
|
||||
|
||||
ModuleStrategy moduleStrategy;
|
||||
if (options.translatorOptions.enableDeferredLoading) {
|
||||
moduleStrategy =
|
||||
DeferredLoadingModuleStrategy(component, options, target, coreTypes);
|
||||
} else if (options.translatorOptions.enableMultiModuleStressTestMode) {
|
||||
moduleStrategy = StressTestModuleStrategy(
|
||||
component, coreTypes, options, target, classHierarchy);
|
||||
} else if (isDynamicMainModule) {
|
||||
moduleStrategy = DynamicMainModuleStrategy(
|
||||
component,
|
||||
coreTypes,
|
||||
options,
|
||||
File.fromUri(dynamicInterfaceUri!).readAsStringSync(),
|
||||
options.dynamicInterfaceUri!);
|
||||
} else if (isDynamicSubmodule) {
|
||||
moduleStrategy = DynamicSubmoduleStrategy(
|
||||
component, options, target, coreTypes, dynamicMainModuleUri!);
|
||||
} else {
|
||||
moduleStrategy = DefaultModuleStrategy(component, options);
|
||||
}
|
||||
final moduleStrategy = _createModuleStrategy(options, component, coreTypes,
|
||||
target, classHierarchy, dynamicMainModuleUri, dynamicInterfaceUri);
|
||||
|
||||
// DynamicMainModuleStrategy.prepareComponent() includes
|
||||
// dynamic_interface_annotator transformation which annotates AST nodes with
|
||||
@@ -337,9 +451,19 @@ Future<CompilationResult> compileToModule(
|
||||
useRapidTypeAnalysis: false);
|
||||
}
|
||||
|
||||
if (options.dumpKernelAfterTfa != null) {
|
||||
writeComponentToText(component,
|
||||
path: options.dumpKernelAfterTfa!, showMetadata: true);
|
||||
if (options.phases.last == compiler.CompilerPhase.tfa) {
|
||||
// Store metadata needed for codegen so that it can be serialized.
|
||||
final recordClassesRepo = _RecordClassesRepository();
|
||||
recordClasses.forEach((shape, cls) {
|
||||
recordClassesRepo.mapping[cls] = shape;
|
||||
});
|
||||
component.addMetadataRepository(recordClassesRepo);
|
||||
|
||||
final interopMethodsRepo = _InteropMethodsRepository();
|
||||
jsInteropMethods.forEach((method, info) {
|
||||
interopMethodsRepo.mapping[method] = info;
|
||||
});
|
||||
component.addMetadataRepository(interopMethodsRepo);
|
||||
}
|
||||
|
||||
assert(() {
|
||||
@@ -348,6 +472,29 @@ Future<CompilationResult> compileToModule(
|
||||
return true;
|
||||
}());
|
||||
|
||||
if (options.dumpKernelAfterTfa != null) {
|
||||
writeComponentToText(component,
|
||||
path: options.dumpKernelAfterTfa!, showMetadata: true);
|
||||
}
|
||||
|
||||
return TfaResult(component, coreTypes, libraryIndex, moduleStrategy,
|
||||
mainModuleMetadata, jsInteropMethods, recordClasses);
|
||||
}
|
||||
|
||||
Future<CompilationResult> _runCodegenPhase(
|
||||
TfaResult tfaSuccess,
|
||||
compiler.WasmCompilerOptions options,
|
||||
FileSystem fileSystem,
|
||||
Uri Function(String moduleName)? sourceMapUrlGenerator) async {
|
||||
final TfaResult(
|
||||
:component,
|
||||
:coreTypes,
|
||||
:moduleStrategy,
|
||||
:libraryIndex,
|
||||
:recordClasses,
|
||||
:mainModuleMetadata,
|
||||
:jsInteropMethods
|
||||
) = tfaSuccess;
|
||||
await moduleStrategy.processComponentAfterTfa();
|
||||
|
||||
final moduleOutputData = moduleStrategy.buildModuleOutputData();
|
||||
@@ -359,8 +506,8 @@ Future<CompilationResult> compileToModule(
|
||||
|
||||
String? depFile = options.depFile;
|
||||
if (depFile != null) {
|
||||
writeDepfile(compilerOptions.fileSystem, component.uriToSource.keys,
|
||||
options.outputFile, depFile);
|
||||
writeDepfile(
|
||||
fileSystem, component.uriToSource.keys, options.outputFile, depFile);
|
||||
}
|
||||
|
||||
final generateSourceMaps = options.translatorOptions.generateSourceMaps;
|
||||
@@ -380,6 +527,13 @@ Future<CompilationResult> compileToModule(
|
||||
|
||||
final jsRuntimeFinalizer = js.RuntimeFinalizer(jsInteropMethods);
|
||||
|
||||
final dynamicMainModuleUri =
|
||||
await _resolveUri(fileSystem, options.dynamicMainModuleUri);
|
||||
final isDynamicMainModule =
|
||||
options.dynamicModuleType == DynamicModuleType.main;
|
||||
final isDynamicSubmodule =
|
||||
options.dynamicModuleType == DynamicModuleType.submodule;
|
||||
|
||||
final jsRuntime = isDynamicSubmodule
|
||||
? jsRuntimeFinalizer.generateDynamicSubmodule(
|
||||
translator.functions.translatedProcedures,
|
||||
@@ -400,7 +554,38 @@ Future<CompilationResult> compileToModule(
|
||||
optimized: true);
|
||||
}
|
||||
|
||||
return CompilationSuccess(wasmModules, jsRuntime, supportJs);
|
||||
return CodegenResult(wasmModules, jsRuntime, supportJs);
|
||||
}
|
||||
|
||||
ModuleStrategy _createModuleStrategy(
|
||||
compiler.WasmCompilerOptions options,
|
||||
Component component,
|
||||
CoreTypes coreTypes,
|
||||
WasmTarget target,
|
||||
ClassHierarchy classHierarchy,
|
||||
Uri? dynamicMainModuleUri,
|
||||
Uri? dynamicInterfaceUri) {
|
||||
final isDynamicMainModule =
|
||||
options.dynamicModuleType == DynamicModuleType.main;
|
||||
final isDynamicSubmodule =
|
||||
options.dynamicModuleType == DynamicModuleType.submodule;
|
||||
if (options.translatorOptions.enableDeferredLoading) {
|
||||
return DeferredLoadingModuleStrategy(component, options, target, coreTypes);
|
||||
} else if (options.translatorOptions.enableMultiModuleStressTestMode) {
|
||||
return StressTestModuleStrategy(
|
||||
component, coreTypes, options, target, classHierarchy);
|
||||
} else if (isDynamicMainModule) {
|
||||
return DynamicMainModuleStrategy(
|
||||
component,
|
||||
coreTypes,
|
||||
options,
|
||||
File.fromUri(dynamicInterfaceUri!).readAsStringSync(),
|
||||
options.dynamicInterfaceUri!);
|
||||
} else if (isDynamicSubmodule) {
|
||||
return DynamicSubmoduleStrategy(
|
||||
component, options, target, coreTypes, dynamicMainModuleUri!);
|
||||
}
|
||||
return DefaultModuleStrategy(component, options);
|
||||
}
|
||||
|
||||
// Patches `dart:_internal`s `mainTearOff{0,1,2}` getters.
|
||||
@@ -434,6 +619,69 @@ 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
|
||||
final Map<Class, RecordShape> mapping = {};
|
||||
|
||||
@override
|
||||
RecordShape readFromBinary(Node node, BinarySource source) {
|
||||
final positionals = source.readUInt30();
|
||||
final namesLength = source.readUInt30();
|
||||
final names = namesLength == 0 ? const <String>[] : <String>[];
|
||||
for (int i = 0; i < namesLength; i++) {
|
||||
names.add(source.readStringReference());
|
||||
}
|
||||
return RecordShape(positionals, names);
|
||||
}
|
||||
|
||||
@override
|
||||
String get tag => _tag;
|
||||
|
||||
@override
|
||||
void writeToBinary(RecordShape metadata, Node node, BinarySink sink) {
|
||||
sink.writeUInt30(metadata.positionals);
|
||||
sink.writeUInt30(metadata.names.length);
|
||||
for (final name in metadata.names) {
|
||||
sink.writeStringReference(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _InteropMethodsRepository
|
||||
extends MetadataRepository<({String importName, String jsCode})> {
|
||||
static const String _tag = 'dart2wasm.interopMethods';
|
||||
@override
|
||||
final Map<Procedure, ({String importName, String jsCode})> mapping = {};
|
||||
|
||||
@override
|
||||
({String importName, String jsCode}) readFromBinary(
|
||||
Node node, BinarySource source) {
|
||||
final importName = source.readStringReference();
|
||||
final jsCode = source.readStringReference();
|
||||
return (importName: importName, jsCode: jsCode);
|
||||
}
|
||||
|
||||
@override
|
||||
String get tag => _tag;
|
||||
|
||||
@override
|
||||
void writeToBinary(({String importName, String jsCode}) metadata, Node node,
|
||||
BinarySink sink) {
|
||||
sink.writeStringReference(metadata.importName);
|
||||
sink.writeStringReference(metadata.jsCode);
|
||||
}
|
||||
}
|
||||
|
||||
String _generateSupportJs(TranslatorOptions options) {
|
||||
// Copied from
|
||||
// https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/gc/index.js
|
||||
|
||||
@@ -3,10 +3,33 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'package:front_end/src/api_unstable/vm.dart' as fe;
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'dynamic_modules.dart' show DynamicModuleType;
|
||||
import 'translator.dart';
|
||||
|
||||
/// Represents a discrete phase of dart2wasm's compilation process.
|
||||
///
|
||||
/// cfe: Runs the common frontend and applies any modular transforms as part of
|
||||
/// that process.
|
||||
///
|
||||
/// tfa: Runs global transforms on kernel including, but not limited to, TFA.
|
||||
///
|
||||
/// codegen: Runs the main dart2wasm translation process converting the kernel
|
||||
/// into WASM modules.
|
||||
enum CompilerPhase {
|
||||
cfe,
|
||||
tfa,
|
||||
codegen;
|
||||
|
||||
static CompilerPhase parse(String name) {
|
||||
for (final phase in values) {
|
||||
if (phase.name == name) return phase;
|
||||
}
|
||||
throw ArgumentError('Invalid compiler phase name: $name');
|
||||
}
|
||||
}
|
||||
|
||||
class WasmCompilerOptions {
|
||||
final TranslatorOptions translatorOptions = TranslatorOptions();
|
||||
|
||||
@@ -31,6 +54,11 @@ class WasmCompilerOptions {
|
||||
String? dumpKernelBeforeTfa;
|
||||
String? dumpKernelAfterTfa;
|
||||
bool dryRun = false;
|
||||
List<CompilerPhase> phases = const [
|
||||
CompilerPhase.cfe,
|
||||
CompilerPhase.tfa,
|
||||
CompilerPhase.codegen
|
||||
];
|
||||
|
||||
factory WasmCompilerOptions.defaultOptions() =>
|
||||
WasmCompilerOptions(mainUri: Uri(), outputFile: '');
|
||||
@@ -64,5 +92,57 @@ class WasmCompilerOptions {
|
||||
"compiling dynamic modules.");
|
||||
}
|
||||
}
|
||||
|
||||
_validatePhases();
|
||||
}
|
||||
|
||||
void _validatePhases() {
|
||||
if (phases.isEmpty) {
|
||||
throw ArgumentError('--phases must contain at least one phase.');
|
||||
}
|
||||
|
||||
CompilerPhase? previousPhase;
|
||||
for (final phase in phases) {
|
||||
// Ensure phases are consecutive
|
||||
if (previousPhase != null && previousPhase.index != phase.index - 1) {
|
||||
throw ArgumentError('--phases must contain consecutive phases.');
|
||||
}
|
||||
previousPhase = phase;
|
||||
}
|
||||
|
||||
// Ensure correct input file type
|
||||
final inputExtension = path.extension(mainUri.path);
|
||||
switch (phases.first) {
|
||||
case CompilerPhase.cfe:
|
||||
if (inputExtension != '.dart') {
|
||||
throw ArgumentError('Input to cfe phase must be a .dart file.');
|
||||
}
|
||||
case CompilerPhase.tfa:
|
||||
if (inputExtension != '.dill') {
|
||||
throw ArgumentError('Input to tfa phase must be a .dill file.');
|
||||
}
|
||||
case CompilerPhase.codegen:
|
||||
if (inputExtension != '.dill') {
|
||||
throw ArgumentError('Input to codegen phase must be a .dill file.');
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure correct output file type
|
||||
final outputExtension = path.extension(outputFile);
|
||||
switch (phases.last) {
|
||||
case CompilerPhase.cfe:
|
||||
if (outputExtension != '.dill') {
|
||||
throw ArgumentError('Output from cfe phase must be a .dill file.');
|
||||
}
|
||||
case CompilerPhase.tfa:
|
||||
if (outputExtension != '.dill') {
|
||||
throw ArgumentError('Output from tfa phase must be a .dill file.');
|
||||
}
|
||||
case CompilerPhase.codegen:
|
||||
if (outputExtension != '.wasm') {
|
||||
throw ArgumentError(
|
||||
'Output from codegen phase must be a .wasm file.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ final List<Option> options = [
|
||||
Flag("minify", (o, value) => o.translatorOptions.minify = value,
|
||||
defaultsTo: _d.translatorOptions.minify),
|
||||
Flag("dry-run", (o, value) => o.dryRun = value, defaultsTo: _d.dryRun),
|
||||
StringMultiOption(
|
||||
"phases",
|
||||
(o, values) => o.phases = [...values.map(CompilerPhase.parse)]
|
||||
..sort((a, b) => a.index.compareTo(b.index))),
|
||||
Flag("polymorphic-specialization",
|
||||
(o, value) => o.translatorOptions.polymorphicSpecialization = value,
|
||||
defaultsTo: _d.translatorOptions.polymorphicSpecialization),
|
||||
|
||||
@@ -13,15 +13,6 @@ import 'package:kernel/kernel.dart'
|
||||
show writeComponentToBinary, writeComponentToBytes;
|
||||
import 'package:kernel/library_index.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:vm/metadata/direct_call.dart' show DirectCallMetadataRepository;
|
||||
import 'package:vm/metadata/inferred_type.dart'
|
||||
show
|
||||
InferredArgTypeMetadataRepository,
|
||||
InferredReturnTypeMetadataRepository,
|
||||
InferredTypeMetadataRepository;
|
||||
import 'package:vm/metadata/procedure_attributes.dart'
|
||||
show ProcedureAttributesMetadataRepository;
|
||||
import 'package:vm/metadata/table_selector.dart';
|
||||
|
||||
import 'class_info.dart';
|
||||
import 'compiler_options.dart';
|
||||
@@ -30,6 +21,7 @@ import 'dynamic_modules.dart';
|
||||
import 'js/method_collector.dart' show JSMethods;
|
||||
import 'serialization.dart';
|
||||
import 'translator.dart';
|
||||
import 'util.dart';
|
||||
|
||||
const String dynamicMainModuleProcedureAttributeMetadataTag =
|
||||
'dynMod:procedureAttributes';
|
||||
@@ -494,15 +486,9 @@ Future<(Component, JSMethods)> generateDynamicSubmoduleComponent(
|
||||
concatenatedComponentBytes.setAll(0, optimizedMainComponentBytes);
|
||||
concatenatedComponentBytes.setAll(
|
||||
optimizedMainComponentBytes.length, submoduleComponentBytes);
|
||||
final newComponent = Component()
|
||||
final newComponent = createEmptyComponent()
|
||||
..addMetadataRepository(DynamicModuleGlobalIdRepository())
|
||||
..addMetadataRepository(DynamicModuleConstantRepository())
|
||||
..addMetadataRepository(ProcedureAttributesMetadataRepository())
|
||||
..addMetadataRepository(TableSelectorMetadataRepository())
|
||||
..addMetadataRepository(DirectCallMetadataRepository())
|
||||
..addMetadataRepository(InferredTypeMetadataRepository())
|
||||
..addMetadataRepository(InferredReturnTypeMetadataRepository())
|
||||
..addMetadataRepository(InferredArgTypeMetadataRepository());
|
||||
..addMetadataRepository(DynamicModuleConstantRepository());
|
||||
BinaryBuilderWithMetadata(concatenatedComponentBytes)
|
||||
.readComponent(newComponent);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
@@ -18,6 +19,7 @@ typedef PrintError = void Function(String error);
|
||||
|
||||
Future<int> generateWasm(WasmCompilerOptions options,
|
||||
{PrintError errorPrinter = print}) async {
|
||||
options.validate();
|
||||
final translatorOptions = options.translatorOptions;
|
||||
if (translatorOptions.verbose) {
|
||||
print('Running dart compile wasm...');
|
||||
@@ -75,7 +77,7 @@ Future<int> generateWasm(WasmCompilerOptions options,
|
||||
? moduleNameToRelativeSourceMapUri
|
||||
: null;
|
||||
|
||||
CompilationResult result = await compileToModule(
|
||||
CompilationResult result = await compile(
|
||||
options, StandardFileSystem.instance, relativeSourceMapUrlMapper,
|
||||
(message) {
|
||||
if (!options.dryRun) printDiagnosticMessage(message, errorPrinter);
|
||||
@@ -109,26 +111,35 @@ Future<int> generateWasm(WasmCompilerOptions options,
|
||||
return 255;
|
||||
}
|
||||
|
||||
final writeFutures = <Future>[];
|
||||
result.wasmModules.forEach((moduleName, moduleInfo) {
|
||||
final (:moduleBytes, :sourceMap) = moduleInfo;
|
||||
final File outFile = File(moduleNameToWasmOutputFile(moduleName));
|
||||
outFile.parent.createSync(recursive: true);
|
||||
writeFutures.add(outFile.writeAsBytes(moduleBytes));
|
||||
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);
|
||||
if (sourceMap != null) {
|
||||
writeFutures.add(File(moduleNameToSourceMapFile(moduleName))
|
||||
.writeAsString(sourceMap));
|
||||
}
|
||||
});
|
||||
await Future.wait(writeFutures);
|
||||
|
||||
final jsFile = path.setExtension(options.outputFile, '.mjs');
|
||||
final jsRuntime = result.jsRuntime;
|
||||
await File(jsFile).writeAsString(jsRuntime);
|
||||
final jsFile = path.setExtension(options.outputFile, '.mjs');
|
||||
await File(jsFile).writeAsString(jsRuntime);
|
||||
|
||||
final supportJsFile = path.setExtension(options.outputFile, '.support.js');
|
||||
await File(supportJsFile).writeAsString(result.supportJs);
|
||||
final supportJsFile =
|
||||
path.setExtension(options.outputFile, '.support.js');
|
||||
await File(supportJsFile).writeAsString(supportJs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,16 @@ import 'dart:convert';
|
||||
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:vm/metadata/direct_call.dart' show DirectCallMetadataRepository;
|
||||
import 'package:vm/metadata/inferred_type.dart'
|
||||
show
|
||||
InferredTypeMetadataRepository,
|
||||
InferredReturnTypeMetadataRepository,
|
||||
InferredArgTypeMetadataRepository;
|
||||
import 'package:vm/metadata/procedure_attributes.dart'
|
||||
show ProcedureAttributesMetadataRepository;
|
||||
import 'package:vm/metadata/table_selector.dart'
|
||||
show TableSelectorMetadataRepository;
|
||||
|
||||
bool hasPragma(CoreTypes coreTypes, Annotatable node, String name) {
|
||||
return getPragma(coreTypes, node, name, defaultValue: '') != null;
|
||||
@@ -67,3 +77,13 @@ List<int> _intToLittleEndianBytes(int i) {
|
||||
}
|
||||
|
||||
String intToBase64(int i) => base64.encode(_intToLittleEndianBytes(i));
|
||||
|
||||
Component createEmptyComponent() {
|
||||
return Component()
|
||||
..addMetadataRepository(ProcedureAttributesMetadataRepository())
|
||||
..addMetadataRepository(TableSelectorMetadataRepository())
|
||||
..addMetadataRepository(DirectCallMetadataRepository())
|
||||
..addMetadataRepository(InferredTypeMetadataRepository())
|
||||
..addMetadataRepository(InferredReturnTypeMetadataRepository())
|
||||
..addMetadataRepository(InferredArgTypeMetadataRepository());
|
||||
}
|
||||
|
||||
@@ -9,19 +9,11 @@ import 'package:args/args.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
const String helperJsLoadIdLookupToken = 'LOAD_ID_LOOKUP';
|
||||
const String helperJsModuleDirToken = 'MODULE_DIR';
|
||||
|
||||
final dartAotExecutable = Uri.parse(Platform.resolvedExecutable)
|
||||
.resolve('dartaotruntime')
|
||||
.toFilePath();
|
||||
final dart2wasmSnapshot = Uri.parse(Platform.resolvedExecutable)
|
||||
.resolve('snapshots/dart2wasm_product.snapshot')
|
||||
.toFilePath();
|
||||
final platformDill = Uri.parse(Platform.resolvedExecutable)
|
||||
.resolve('../lib/_internal/dart2wasm_platform.dill')
|
||||
.toFilePath();
|
||||
|
||||
final String goldenPath =
|
||||
'${path.dirname(Platform.script.path)}/data/deferred_load_ids.golden.json';
|
||||
final String mainDart = '${path.dirname(Platform.script.path)}/data/main.dart';
|
||||
@@ -33,10 +25,10 @@ Future<void> main(List<String> args) async {
|
||||
|
||||
final argsResult = parser.parse(args);
|
||||
|
||||
final tmpDir = await Directory.systemTemp.createTemp('wasm-load-ids');
|
||||
final loadIdsUri = tmpDir.uri.resolve('deferred_load_ids.json');
|
||||
final outFilename = '${tmpDir.path}/out.wasm';
|
||||
try {
|
||||
await withTempDir((tmpDirPath) async {
|
||||
final tmpDir = File(tmpDirPath);
|
||||
final loadIdsUri = tmpDir.uri.resolve('deferred_load_ids.json');
|
||||
final outFilename = '${tmpDir.path}/out.wasm';
|
||||
// Compile the test
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
@@ -82,18 +74,5 @@ Future<void> main(List<String> args) async {
|
||||
// Load the helper JS and run the compiled code
|
||||
await run(
|
||||
['pkg/dart2wasm/tool/run_benchmark', helperFile.path, outFilename]);
|
||||
} finally {
|
||||
await tmpDir.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> run(List<String> command) async {
|
||||
print('Running: ${command.join(' ')}');
|
||||
final result = await Process.run(command.first, command.skip(1).toList());
|
||||
if (result.exitCode != 0) {
|
||||
print('-> Failed with exit code ${result.exitCode}');
|
||||
print('-> stdout:\n${result.stdout}');
|
||||
print('-> stderr:\n${result.stderr}');
|
||||
throw 'Subprocess failed';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:wasm_builder/src/ir/ir.dart';
|
||||
import 'package:wasm_builder/src/serialize/deserializer.dart';
|
||||
import 'package:wasm_builder/src/serialize/printer.dart';
|
||||
|
||||
import 'self_compile_test.dart' show withTempDir;
|
||||
import 'util.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
final result = argParser.parse(args);
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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.
|
||||
|
||||
void main() {
|
||||
print('hello world');
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
// 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';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../util.dart';
|
||||
|
||||
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';
|
||||
|
||||
Future<void> main() async {
|
||||
await testSuccessCases();
|
||||
await testFailureCases();
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// Run CFE and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe',
|
||||
mainDart,
|
||||
cfeDill.path,
|
||||
]);
|
||||
Expect.isTrue(await cfeDill.exists());
|
||||
Expect.isTrue((await cfeDill.stat()).size > 0);
|
||||
|
||||
// Run TFA and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=tfa',
|
||||
cfeDill.path,
|
||||
tfaDill.path,
|
||||
]);
|
||||
Expect.isTrue(await tfaDill.exists());
|
||||
Expect.isTrue((await tfaDill.stat()).size > 0);
|
||||
|
||||
// Run codegen and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=codegen',
|
||||
tfaDill.path,
|
||||
wasmOut.path,
|
||||
]);
|
||||
Expect.isTrue(await wasmOut.exists());
|
||||
Expect.isTrue((await wasmOut.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));
|
||||
|
||||
// Run CFE & TFA and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe,tfa',
|
||||
mainDart,
|
||||
tfaDill.path,
|
||||
]);
|
||||
Expect.isTrue(await tfaDill.exists());
|
||||
Expect.isTrue((await tfaDill.stat()).size > 0);
|
||||
|
||||
// Run codegen and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=codegen',
|
||||
tfaDill.path,
|
||||
wasmOut.path,
|
||||
]);
|
||||
Expect.isTrue(await wasmOut.exists());
|
||||
Expect.isTrue((await wasmOut.stat()).size > 0);
|
||||
});
|
||||
|
||||
await withTempDir((tmpDirPath) async {
|
||||
final tmpDir = File(tmpDirPath);
|
||||
|
||||
final cfeDill = File.fromUri(tmpDir.uri.resolve(tfaDillName));
|
||||
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
|
||||
|
||||
// Run CFE and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe',
|
||||
mainDart,
|
||||
cfeDill.path,
|
||||
]);
|
||||
Expect.isTrue(await cfeDill.exists());
|
||||
Expect.isTrue((await cfeDill.stat()).size > 0);
|
||||
|
||||
// Run TFA & codegen and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=tfa,codegen',
|
||||
cfeDill.path,
|
||||
wasmOut.path,
|
||||
]);
|
||||
Expect.isTrue(await wasmOut.exists());
|
||||
Expect.isTrue((await wasmOut.stat()).size > 0);
|
||||
});
|
||||
|
||||
await withTempDir((tmpDirPath) async {
|
||||
final tmpDir = File(tmpDirPath);
|
||||
|
||||
final wasmOut = File.fromUri(tmpDir.uri.resolve(wasmOutName));
|
||||
|
||||
// Run CFE & TFA & codegen and expect output
|
||||
await run([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe,tfa,codegen',
|
||||
mainDart,
|
||||
wasmOut.path,
|
||||
]);
|
||||
Expect.isTrue(await wasmOut.exists());
|
||||
Expect.isTrue((await wasmOut.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));
|
||||
|
||||
// CFE checks
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe',
|
||||
tfaDill.path,
|
||||
cfeDill.path
|
||||
], 'Input to cfe phase must be a .dart file');
|
||||
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe',
|
||||
mainDart,
|
||||
wasmOut.path
|
||||
], 'Output from cfe phase must be a .dill file');
|
||||
|
||||
// TFA checks
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--platform=$platformDill',
|
||||
'--phases=tfa',
|
||||
mainDart,
|
||||
tfaDill.path
|
||||
], 'Input to tfa phase must be a .dill file');
|
||||
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=tfa',
|
||||
cfeDill.path,
|
||||
wasmOut.path
|
||||
], 'Output from tfa phase must be a .dill file');
|
||||
|
||||
// Codegen checks
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--platform=$platformDill',
|
||||
'--phases=codegen',
|
||||
mainDart,
|
||||
wasmOut.path
|
||||
], 'Input to codegen phase must be a .dill file');
|
||||
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=codegen',
|
||||
tfaDill.path,
|
||||
cfeDill.path
|
||||
], 'Output from codegen phase must be a .wasm file');
|
||||
|
||||
// Other checks
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=cfe,codegen',
|
||||
mainDart,
|
||||
wasmOut.path
|
||||
], 'must contain consecutive phases');
|
||||
|
||||
await expectFailedRun([
|
||||
dartAotExecutable,
|
||||
dart2wasmSnapshot,
|
||||
'--platform=$platformDill',
|
||||
'--phases=notReal',
|
||||
mainDart,
|
||||
cfeDill.path
|
||||
], 'Invalid compiler phase name');
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> expectFailedRun(
|
||||
List<String> command, String expectedSubstring) async {
|
||||
try {
|
||||
await run(command, throwOutputOnFailure: true);
|
||||
Expect.fail('Expected dart2wasm error.');
|
||||
} catch (e) {
|
||||
Expect.contains(expectedSubstring, '$e');
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
Future main() async {
|
||||
if (!Platform.isLinux && !Platform.isMacOS) return;
|
||||
|
||||
@@ -44,17 +46,6 @@ Future main() async {
|
||||
});
|
||||
}
|
||||
|
||||
Future run(List<String> command) async {
|
||||
print('Running: ${command.join(' ')}');
|
||||
final result = await Process.run(command.first, command.skip(1).toList());
|
||||
if (result.exitCode != 0) {
|
||||
print('-> Failed with exit code ${result.exitCode}');
|
||||
print('-> stdout:\n${result.stdout}');
|
||||
print('-> stderr:\n${result.stderr}');
|
||||
throw 'Subprocess failed';
|
||||
}
|
||||
}
|
||||
|
||||
void expectEqualBytes(Uint8List a, Uint8List b) {
|
||||
if (a.length != b.length) {
|
||||
throw 'Mismatch in length ${a.length} vs ${b.length}';
|
||||
@@ -65,18 +56,3 @@ void expectEqualBytes(Uint8List a, Uint8List b) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future withTempDir(Future Function(String directory) fun) async {
|
||||
final dir = Directory.systemTemp.createTempSync('dart2wasm_self_compile');
|
||||
try {
|
||||
print('Running with temporary directory: ${dir.path}');
|
||||
return await fun(dir.path);
|
||||
} finally {
|
||||
if (!keepTemporaryDirectory) {
|
||||
dir.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final bool keepTemporaryDirectory =
|
||||
(Platform.environment['KEEP_TEMPORARY_DIRECTORIES'] ?? 'false') != 'false';
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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';
|
||||
|
||||
final dartAotExecutable = Uri.parse(Platform.resolvedExecutable)
|
||||
.resolve('dartaotruntime')
|
||||
.toFilePath();
|
||||
final dart2wasmSnapshot = Uri.parse(Platform.resolvedExecutable)
|
||||
.resolve('snapshots/dart2wasm_product.snapshot')
|
||||
.toFilePath();
|
||||
final platformDill = Uri.parse(Platform.resolvedExecutable)
|
||||
.resolve('../lib/_internal/dart2wasm_platform.dill')
|
||||
.toFilePath();
|
||||
|
||||
Future<void> run(List<String> command,
|
||||
{bool throwOutputOnFailure = false}) async {
|
||||
print('Running: ${command.join(' ')}');
|
||||
final result = await Process.run(command.first, command.skip(1).toList());
|
||||
if (result.exitCode != 0) {
|
||||
if (throwOutputOnFailure) {
|
||||
throw '${result.stdout}\n${result.stderr}';
|
||||
}
|
||||
|
||||
print('-> Failed with exit code ${result.exitCode}');
|
||||
print('-> stdout:\n${result.stdout}');
|
||||
print('-> stderr:\n${result.stderr}');
|
||||
throw 'Subprocess failed';
|
||||
}
|
||||
}
|
||||
|
||||
Future withTempDir(Future Function(String directory) fun) async {
|
||||
final dir = Directory.systemTemp.createTempSync('dart2wasm_self_compile');
|
||||
try {
|
||||
print('Running with temporary directory: ${dir.path}');
|
||||
return await fun(dir.path);
|
||||
} finally {
|
||||
if (!keepTemporaryDirectory) {
|
||||
dir.deleteSync(recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final bool keepTemporaryDirectory =
|
||||
(Platform.environment['KEEP_TEMPORARY_DIRECTORIES'] ?? 'false') != 'false';
|
||||
@@ -8,7 +8,7 @@ import 'dart:typed_data';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:wasm_builder/wasm_builder.dart';
|
||||
|
||||
import 'self_compile_test.dart' show withTempDir, run;
|
||||
import 'util.dart';
|
||||
|
||||
Future main() async {
|
||||
if (!Platform.isLinux && !Platform.isMacOS) return;
|
||||
|
||||
@@ -8,7 +8,8 @@ import 'dart:typed_data';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:wasm_builder/wasm_builder.dart';
|
||||
|
||||
import 'self_compile_test.dart' show withTempDir, run, expectEqualBytes;
|
||||
import 'self_compile_test.dart' show expectEqualBytes;
|
||||
import 'util.dart';
|
||||
|
||||
Future main() async {
|
||||
if (!Platform.isLinux && !Platform.isMacOS) return;
|
||||
|
||||
@@ -81,7 +81,7 @@ VM_ARGS=()
|
||||
DART2WASM_ARGS=("--require-js-string-builtin")
|
||||
ADDITIONAL_BINARYEN_FLAGS=()
|
||||
DART_FILE=""
|
||||
WASM_FILE=""
|
||||
OUTPUT_FILE=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--compile-benchmark=*)
|
||||
@@ -176,7 +176,7 @@ while [ $# -gt 0 ]; do
|
||||
|
||||
-o)
|
||||
shift
|
||||
WASM_FILE="$1"
|
||||
OUTPUT_FILE="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
@@ -190,8 +190,8 @@ while [ $# -gt 0 ]; do
|
||||
DART_FILE="$1"
|
||||
shift
|
||||
else
|
||||
if [ -z "$WASM_FILE" ]; then
|
||||
WASM_FILE="$1"
|
||||
if [ -z "$OUTPUT_FILE" ]; then
|
||||
OUTPUT_FILE="$1"
|
||||
shift
|
||||
else
|
||||
echo "Unexpected argument $1"
|
||||
@@ -203,10 +203,10 @@ while [ $# -gt 0 ]; do
|
||||
done
|
||||
|
||||
if [ $GENERATE_SOURCE_MAP -eq 1 ]; then
|
||||
BINARYEN_FLAGS+=("-ism" "${WASM_FILE}.map" "-osm" "${WASM_FILE}.map")
|
||||
BINARYEN_FLAGS+=("-ism" "${OUTPUT_FILE}.map" "-osm" "${OUTPUT_FILE}.map")
|
||||
fi
|
||||
|
||||
if [ -z "$DART_FILE" -o -z "$WASM_FILE" ]; then
|
||||
if [ -z "$DART_FILE" -o -z "$OUTPUT_FILE" ]; then
|
||||
echo "Expected <file.dart> <file.wasm>"
|
||||
exit 1
|
||||
fi
|
||||
@@ -237,7 +237,7 @@ function measure_size() {
|
||||
}
|
||||
|
||||
function run_if_binaryen_enabled() {
|
||||
if [ $RUN_BINARYEN -eq 1 ]; then
|
||||
if [ $RUN_BINARYEN -eq 1 ] && [[ $OUTPUT_FILE == *.wasm ]]; then
|
||||
$@
|
||||
fi
|
||||
}
|
||||
@@ -247,9 +247,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" "$WASM_FILE")
|
||||
dart2wasm_command=("$DART" "${VM_ARGS[@]}" "$DART2WASM_SRC" "$LIBRARIES_JSON_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$OUTPUT_FILE")
|
||||
else
|
||||
dart2wasm_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$WASM_FILE")
|
||||
dart2wasm_command=("$DART_AOT_RUNTIME" "${VM_ARGS[@]}" "$DART2WASM_AOT_SNAPSHOT" "$PLATFORM_ARG" "${DART2WASM_ARGS[@]}" "$DART_FILE" "$OUTPUT_FILE")
|
||||
fi
|
||||
|
||||
if [ -n "$COMPILE_BENCHMARK_BASE_NAME" ]; then
|
||||
@@ -257,18 +257,18 @@ function run_compiler() {
|
||||
COMPILER_TIME=$TIME
|
||||
COMPILER_MEMORY=$MEMORY
|
||||
|
||||
measure_size ${WASM_FILE%.wasm}.mjs
|
||||
measure_size ${OUTPUT_FILE%.wasm}.mjs
|
||||
MJS_SIZE=$SIZE
|
||||
MJS_GZIP_SIZE=$GZIP_SIZE
|
||||
|
||||
if [ $MULTI_MODULE -eq 1 ]; then
|
||||
for WASM_FILE in "${WASM_FILE%.wasm}"*.wasm; do
|
||||
measure_size $WASM_FILE
|
||||
for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do
|
||||
measure_size $OUTPUT_FILE
|
||||
(( COMPILER_SIZE+=$SIZE ))
|
||||
(( COMPILER_GZIP_SIZE+=$GZIP_SIZE ))
|
||||
done
|
||||
else
|
||||
measure_size $WASM_FILE
|
||||
measure_size $OUTPUT_FILE
|
||||
COMPILER_SIZE=$SIZE
|
||||
COMPILER_GZIP_SIZE=$GZIP_SIZE
|
||||
fi
|
||||
@@ -283,13 +283,13 @@ BINARYEN_SIZE=0
|
||||
BINARYEN_GZIP_SIZE=0
|
||||
|
||||
function run_binaryen_single() {
|
||||
binaryen_command=("$BINARYEN" "$@" "${ADDITIONAL_BINARYEN_FLAGS[@]}" "$WASM_FILE" -o "$WASM_FILE")
|
||||
binaryen_command=("$BINARYEN" "$@" "${ADDITIONAL_BINARYEN_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[@]}
|
||||
BINARYEN_TIME=$(echo "$BINARYEN_TIME + $TIME" | bc)
|
||||
BINARYEN_MEMORY=$(($BINARYEN_MEMORY > $MEMORY ? $BINARYEN_MEMORY : $MEMORY ))
|
||||
measure_size $WASM_FILE
|
||||
measure_size $OUTPUT_FILE
|
||||
BINARYEN_SIZE=$(echo "$BINARYEN_SIZE + $SIZE" | bc)
|
||||
BINARYEN_GZIP_SIZE=$(echo "$BINARYEN_GZIP_SIZE + $GZIP_SIZE" | bc)
|
||||
else
|
||||
@@ -301,7 +301,7 @@ function run_binaryen() {
|
||||
if [ $MULTI_MODULE -eq 1 ]; then
|
||||
# Iterate over all matching wasm files and optimize them concurrently in
|
||||
# different processes.
|
||||
for WASM_FILE in "${WASM_FILE%.wasm}"*.wasm; do
|
||||
for OUTPUT_FILE in "${OUTPUT_FILE%.wasm}"*.wasm; do
|
||||
run_binaryen_single "${BINARYEN_FLAGS_DEFERRED_LOADING[@]}"
|
||||
done
|
||||
else
|
||||
|
||||
@@ -949,13 +949,7 @@ class CompileWasmCommand extends CompileSubcommandCommand {
|
||||
outputFile = '$inputWithoutDart.wasm';
|
||||
}
|
||||
|
||||
if (!outputFile.endsWith('.wasm')) {
|
||||
log.stderr(
|
||||
'Error: The output file "$outputFile" does not end with ".wasm"');
|
||||
return 255;
|
||||
}
|
||||
final outputFileBasename =
|
||||
outputFile.substring(0, outputFile.length - '.wasm'.length);
|
||||
final outputFileBasename = path.withoutExtension(outputFile);
|
||||
|
||||
final packages = args.option(packagesOption.flag);
|
||||
final defines = args.multiOption(defineOption.flag);
|
||||
@@ -974,7 +968,9 @@ class CompileWasmCommand extends CompileSubcommandCommand {
|
||||
extraCompilerOptions
|
||||
.any((e) => e.contains('enable-multi-module-stress-test'));
|
||||
final optimizationLevel = int.parse(args.option('optimization-level')!);
|
||||
final runWasmOpt = optimizationLevel >= 1;
|
||||
|
||||
final runWasmOpt =
|
||||
optimizationLevel >= 1 && path.extension(outputFile) == '.wasm';
|
||||
|
||||
if (runWasmOpt && !checkArtifactExists(sdk.wasmOpt)) {
|
||||
return 255;
|
||||
|
||||
@@ -815,24 +815,6 @@ void main() {}
|
||||
);
|
||||
}, skip: isRunningOnIA32);
|
||||
|
||||
test('Compile wasm with wrong output filename', () async {
|
||||
final p = project(mainSrc: 'void main() {}');
|
||||
final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
|
||||
final result = await p.run(
|
||||
[
|
||||
'compile',
|
||||
'wasm',
|
||||
'-o',
|
||||
'foo',
|
||||
inFile,
|
||||
],
|
||||
);
|
||||
|
||||
expect(result.stderr,
|
||||
contains('Error: The output file "foo" does not end with ".wasm"'));
|
||||
expect(result.exitCode, genericErrorExitCode);
|
||||
}, skip: isRunningOnIA32);
|
||||
|
||||
test('Compile wasm with error', () async {
|
||||
final p = project(mainSrc: '''
|
||||
void main() {
|
||||
|
||||
Reference in New Issue
Block a user