From addbdcbd5973b42b0d6ed17c16deaddde1c83688 Mon Sep 17 00:00:00 2001 From: Nate Biggs Date: Wed, 2 Jul 2025 09:44:43 -0700 Subject: [PATCH] [dart2wasm] Add --dry-run flag to dart2wasm. This new flag will run the CFE to create a kernel and then run a series of checks over the resulting kernel to look for errors that could block a wasm migration. The compiler will then exit before actually starting the wasm compilation process. This means no output file will be emitted so callers must be aware of this. This first CL is not meant to cover every check we could add here. It adds some initial checks and we can expand on this to include more in follow-up changes. One of the checks implemented here is also provided by a lint. While ideally we would share code between lints and these checks, the delta in the CFE vs analyzer model makes that infeasible today. Sample output: ``` Found incompatibilities with WebAssembly. package:dryrun/test.dart 5:15 - Cannot test a JS value against String (3) package:dryrun/test.dart 6:7 - JS interop class 'B' cannot extend Dart class 'A'. (2) ``` Bug: https://github.com/dart-lang/sdk/issues/60050 Change-Id: Ib2c8e3501cc42d57b86ebaa749359ce6c5dba974 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437960 Commit-Queue: Nate Biggs Reviewed-by: Martin Kustermann --- pkg/dart2wasm/lib/compile.dart | 24 +- pkg/dart2wasm/lib/compiler_options.dart | 1 + pkg/dart2wasm/lib/dart2wasm.dart | 1 + pkg/dart2wasm/lib/dry_run.dart | 227 +++++++++++++ pkg/dart2wasm/lib/generate_wasm.dart | 10 +- pkg/dart2wasm/pubspec.yaml | 2 + pkg/dart2wasm/test/dry_run/dry_run_test.dart | 299 ++++++++++++++++++ .../dry_run/testcases/analysis_options.yaml | 8 + .../test/dry_run/testcases/import_ffi.dart | 8 + .../test/dry_run/testcases/import_html.dart | 8 + .../test/dry_run/testcases/import_js.dart | 8 + .../dry_run/testcases/import_package_js.dart | 8 + .../test/dry_run/testcases/malformed_is.dart | 34 ++ .../testcases/valid_interop_import.dart | 14 + pkg/dartdev/lib/src/commands/compile.dart | 5 + tools/bots/test_matrix.json | 9 + 16 files changed, 663 insertions(+), 3 deletions(-) create mode 100644 pkg/dart2wasm/lib/dry_run.dart create mode 100644 pkg/dart2wasm/test/dry_run/dry_run_test.dart create mode 100644 pkg/dart2wasm/test/dry_run/testcases/analysis_options.yaml create mode 100644 pkg/dart2wasm/test/dry_run/testcases/import_ffi.dart create mode 100644 pkg/dart2wasm/test/dry_run/testcases/import_html.dart create mode 100644 pkg/dart2wasm/test/dry_run/testcases/import_js.dart create mode 100644 pkg/dart2wasm/test/dry_run/testcases/import_package_js.dart create mode 100644 pkg/dart2wasm/test/dry_run/testcases/malformed_is.dart create mode 100644 pkg/dart2wasm/test/dry_run/testcases/valid_interop_import.dart diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart index b4a2cfb53bf..c835c7949ca 100644 --- a/pkg/dart2wasm/lib/compile.dart +++ b/pkg/dart2wasm/lib/compile.dart @@ -37,6 +37,7 @@ import 'package:wasm_builder/wasm_builder.dart' show Serializer; import 'compiler_options.dart' as compiler; import 'constant_evaluator.dart'; import 'deferred_loading.dart'; +import 'dry_run.dart'; import 'dynamic_module_kernel_metadata.dart'; import 'dynamic_modules.dart'; import 'js/runtime_generator.dart' as js; @@ -49,6 +50,12 @@ import 'translator.dart'; sealed class CompilationResult {} +abstract class CompilationDryRunResult extends CompilationResult {} + +class CompilationDryRunError extends CompilationDryRunResult {} + +class CompilationDryRunSuccess extends CompilationDryRunResult {} + class CompilationSuccess extends CompilationResult { final Map wasmModules; final String jsRuntime; @@ -79,7 +86,9 @@ class CFECrashError extends CompilationError { /// (We print them as soon as they are reported by CFE. i.e. we stream errors /// instead of accumulating/batching all of them and reporting at the end.) class CFECompileTimeErrors extends CompilationError { - CFECompileTimeErrors(); + final Component? component; + + CFECompileTimeErrors(this.component); } const List _librariesToIndex = [ @@ -199,7 +208,18 @@ Future compileToModule( } catch (e, s) { return CFECrashError(e, s); } - if (hadCompileTimeError) return CFECompileTimeErrors(); + if (options.dryRun) { + final component = compilerResult?.component; + if (component == null) { + return CompilationDryRunError(); + } + final summarizer = DryRunSummarizer(component); + final hasErrors = summarizer.summarize(); + return hasErrors ? CompilationDryRunError() : CompilationDryRunSuccess(); + } + if (hadCompileTimeError) { + return CFECompileTimeErrors(compilerResult?.component); + } assert(compilerResult != null); Component component = compilerResult!.component!; diff --git a/pkg/dart2wasm/lib/compiler_options.dart b/pkg/dart2wasm/lib/compiler_options.dart index 6d120e7ab7b..47cfd42f27e 100644 --- a/pkg/dart2wasm/lib/compiler_options.dart +++ b/pkg/dart2wasm/lib/compiler_options.dart @@ -29,6 +29,7 @@ class WasmCompilerOptions { String? dumpKernelAfterCfe; String? dumpKernelBeforeTfa; String? dumpKernelAfterTfa; + bool dryRun = false; factory WasmCompilerOptions.defaultOptions() => WasmCompilerOptions(mainUri: Uri(), outputFile: ''); diff --git a/pkg/dart2wasm/lib/dart2wasm.dart b/pkg/dart2wasm/lib/dart2wasm.dart index 55a56bbeaff..dab5155258c 100644 --- a/pkg/dart2wasm/lib/dart2wasm.dart +++ b/pkg/dart2wasm/lib/dart2wasm.dart @@ -26,6 +26,7 @@ final List