From 08307b13afe17bdb4ce9606bf4458d8461e4109c Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 11 Feb 2025 09:55:39 +0100 Subject: [PATCH 1/3] Use dart2wasm support expression --- build_web_compilers/CHANGELOG.md | 4 +++ .../lib/src/dart2wasm_bootstrap.dart | 25 +++++++++++--- .../lib/src/web_entrypoint_builder.dart | 33 ++++++++++--------- build_web_compilers/pubspec.yaml | 2 +- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/build_web_compilers/CHANGELOG.md b/build_web_compilers/CHANGELOG.md index ca0c97ae9..b94834919 100644 --- a/build_web_compilers/CHANGELOG.md +++ b/build_web_compilers/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.1.2-dev + +- Use support-detection scripts emitted by `dart2wasm`. + ## 4.1.1 - Support 3.8.0 pre-release sdks. diff --git a/build_web_compilers/lib/src/dart2wasm_bootstrap.dart b/build_web_compilers/lib/src/dart2wasm_bootstrap.dart index ef84f5c04..ea8a8290f 100644 --- a/build_web_compilers/lib/src/dart2wasm_bootstrap.dart +++ b/build_web_compilers/lib/src/dart2wasm_bootstrap.dart @@ -17,20 +17,28 @@ import 'web_entrypoint_builder.dart'; final _resourcePool = Pool(maxWorkersPerTask); +/// Result of invoking the `dart2wasm` compiler. +/// +/// The `supportExpression` is an optional JavaScript expressions emitted by +/// newer versions of the compiler. It can be evaluated as part of a loader +/// script to determine whether the current JavaScript environment supports +/// `dart2wasm`. +typedef Dart2WasmBootstrapResult = ({String? supportExpression}); + /// Invokes `dart compile wasm` to compile the primary input of [buildStep]. /// /// This only emits the `.wasm` and `.mjs` files produced by `dart2wasm`. An /// entrypoint loader needs to be emitted separately. -Future bootstrapDart2Wasm( +Future bootstrapDart2Wasm( BuildStep buildStep, List additionalArguments, String javaScriptModuleExtension, ) async { - await _resourcePool.withResource(() => _bootstrapDart2Wasm( + return await _resourcePool.withResource(() => _bootstrapDart2Wasm( buildStep, additionalArguments, javaScriptModuleExtension)); } -Future _bootstrapDart2Wasm( +Future _bootstrapDart2Wasm( BuildStep buildStep, List additionalArguments, String javaScriptModuleExtension, @@ -61,7 +69,7 @@ $librariesString https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings '''); - return; + return null; } var scratchSpace = await buildStep.fetchResource(scratchSpaceResource); @@ -124,4 +132,13 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski log.severe('ExitCode:${result.exitCode}\nStdOut:\n${result.stdout}\n' 'StdErr:\n${result.stderr}'); } + + var supportFile = + scratchSpace.fileFor(dartEntrypointId.changeExtension('support.js')); + String? supportExpression; + if (await supportFile.exists()) { + supportExpression = await supportFile.readAsString(); + } + + return (supportExpression: supportExpression); } diff --git a/build_web_compilers/lib/src/web_entrypoint_builder.dart b/build_web_compilers/lib/src/web_entrypoint_builder.dart index ddcc6aa64..9d97f5e8c 100644 --- a/build_web_compilers/lib/src/web_entrypoint_builder.dart +++ b/build_web_compilers/lib/src/web_entrypoint_builder.dart @@ -290,6 +290,7 @@ class WebEntrypointBuilder implements Builder { if (!isAppEntrypoint) return; final compilationSteps = []; + Dart2WasmBootstrapResult? dart2WasmResult; for (final compiler in options.compilers) { switch (compiler.compiler) { @@ -311,20 +312,26 @@ class WebEntrypointBuilder implements Builder { entrypointExtension: compiler.extension, )); case WebCompiler.Dart2Wasm: - compilationSteps.add(bootstrapDart2Wasm( - buildStep, compiler.compilerArguments, compiler.extension)); + compilationSteps.add(Future(() async { + dart2WasmResult = await bootstrapDart2Wasm( + buildStep, compiler.compilerArguments, compiler.extension); + })); } } await Future.wait(compilationSteps); - if (_generateLoader(buildStep.inputId) case (var id, var loader)?) { + if (_generateLoader(buildStep.inputId, dart2WasmResult) + case (var id, var loader)?) { await buildStep.writeAsString(id, loader); } } - (AssetId, String)? _generateLoader(AssetId input) { + (AssetId, String)? _generateLoader( + AssetId input, Dart2WasmBootstrapResult? dart2WasmResult) { var loaderExtension = options.loaderExtension; var wasmCompiler = options.optionsFor(WebCompiler.Dart2Wasm); - if (loaderExtension == null || wasmCompiler == null) { + if (loaderExtension == null || + wasmCompiler == null || + dart2WasmResult == null) { // Generating the loader has been disabled or no loader is necessary. return null; } @@ -350,17 +357,13 @@ function relativeURL(ref) { // If we're compiling to JS, start a feature detection to prefer wasm but // fall back to JS if necessary. if (jsCompiler != null) { - loaderResult.writeln(''' -function supportsWasmGC() { - // This attempts to instantiate a wasm module that only will validate if the - // final WasmGC spec is implemented in the browser. - // - // Copied from https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/gc/index.js - const bytes = [0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 95, 1, 120, 0]; - return 'WebAssembly' in self && WebAssembly.validate(new Uint8Array(bytes)); -} + final supportCheck = dart2WasmResult.supportExpression ?? + "'WebAssembly' in self && " + 'WebAssembly.validate(new Uint8Array(' + '[0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 95, 1, 120, 0]));'; -if (supportsWasmGC()) { + loaderResult.writeln(''' +if ($supportCheck) { '''); } diff --git a/build_web_compilers/pubspec.yaml b/build_web_compilers/pubspec.yaml index 050e1f2c2..dedeef876 100644 --- a/build_web_compilers/pubspec.yaml +++ b/build_web_compilers/pubspec.yaml @@ -1,5 +1,5 @@ name: build_web_compilers -version: 4.1.1 +version: 4.1.2-dev description: Builder implementations wrapping the dart2js and DDC compilers. repository: https://github.com/dart-lang/build/tree/master/build_web_compilers resolution: workspace From 8adc365a311a061fd19602ee7af14f6dc9dbfb18 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 11 Feb 2025 10:00:37 +0100 Subject: [PATCH 2/3] Fix looking up support file --- build_web_compilers/lib/src/dart2wasm_bootstrap.dart | 2 +- build_web_compilers/test/dart2wasm_bootstrap_test.dart | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build_web_compilers/lib/src/dart2wasm_bootstrap.dart b/build_web_compilers/lib/src/dart2wasm_bootstrap.dart index ea8a8290f..728c01380 100644 --- a/build_web_compilers/lib/src/dart2wasm_bootstrap.dart +++ b/build_web_compilers/lib/src/dart2wasm_bootstrap.dart @@ -134,7 +134,7 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski } var supportFile = - scratchSpace.fileFor(dartEntrypointId.changeExtension('support.js')); + scratchSpace.fileFor(dartEntrypointId.changeExtension('.support.js')); String? supportExpression; if (await supportFile.exists()) { supportExpression = await supportFile.readAsString(); diff --git a/build_web_compilers/test/dart2wasm_bootstrap_test.dart b/build_web_compilers/test/dart2wasm_bootstrap_test.dart index 53e03c24c..004d48518 100644 --- a/build_web_compilers/test/dart2wasm_bootstrap_test.dart +++ b/build_web_compilers/test/dart2wasm_bootstrap_test.dart @@ -69,9 +69,11 @@ void main() { 'a|web/index.dart.js': decodedMatches( stringContainsInOrder( [ - 'if (supportsWasmGC())', + 'if', + 'WebAssembly.validate', + '{', 'compileStreaming', - 'else', + '} else {', 'scriptTag.src = relativeURL("./index.dart2js.js");' ], ), From 465e562b22164f904f4504c86779fd62eada2710 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 11 Feb 2025 11:12:09 +0100 Subject: [PATCH 3/3] Review feedback --- build_web_compilers/CHANGELOG.md | 2 +- .../lib/src/dart2wasm_bootstrap.dart | 35 +++++++++++++------ .../lib/src/web_entrypoint_builder.dart | 6 ++-- build_web_compilers/pubspec.yaml | 2 +- .../test/dart2wasm_bootstrap_test.dart | 6 ++++ 5 files changed, 35 insertions(+), 16 deletions(-) diff --git a/build_web_compilers/CHANGELOG.md b/build_web_compilers/CHANGELOG.md index b94834919..eed23eab7 100644 --- a/build_web_compilers/CHANGELOG.md +++ b/build_web_compilers/CHANGELOG.md @@ -1,4 +1,4 @@ -## 4.1.2-dev +## 4.1.2-wip - Use support-detection scripts emitted by `dart2wasm`. diff --git a/build_web_compilers/lib/src/dart2wasm_bootstrap.dart b/build_web_compilers/lib/src/dart2wasm_bootstrap.dart index 728c01380..5a5248a0d 100644 --- a/build_web_compilers/lib/src/dart2wasm_bootstrap.dart +++ b/build_web_compilers/lib/src/dart2wasm_bootstrap.dart @@ -18,18 +18,32 @@ import 'web_entrypoint_builder.dart'; final _resourcePool = Pool(maxWorkersPerTask); /// Result of invoking the `dart2wasm` compiler. -/// -/// The `supportExpression` is an optional JavaScript expressions emitted by -/// newer versions of the compiler. It can be evaluated as part of a loader -/// script to determine whether the current JavaScript environment supports -/// `dart2wasm`. -typedef Dart2WasmBootstrapResult = ({String? supportExpression}); +final class Dart2WasmBootstrapResult { + /// Whether `dart2wasm` did compile the Dart program. + /// + /// This is typically false when the program transitively imports an + /// unsupported library, or if the compiler fails for another reason. + final bool didCompile; + + /// An optional JavaScript expression that might be emitted by `dart2wasm`. + /// The expression evaluates to `true` if the current JavaScript environment + /// supports all the features expected by the generated WebAssembly module. + final String? supportExpression; + + const Dart2WasmBootstrapResult.didNotCompile() + : didCompile = false, + supportExpression = null; + + Dart2WasmBootstrapResult({ + required this.supportExpression, + }) : didCompile = true; +} /// Invokes `dart compile wasm` to compile the primary input of [buildStep]. /// /// This only emits the `.wasm` and `.mjs` files produced by `dart2wasm`. An /// entrypoint loader needs to be emitted separately. -Future bootstrapDart2Wasm( +Future bootstrapDart2Wasm( BuildStep buildStep, List additionalArguments, String javaScriptModuleExtension, @@ -38,7 +52,7 @@ Future bootstrapDart2Wasm( buildStep, additionalArguments, javaScriptModuleExtension)); } -Future _bootstrapDart2Wasm( +Future _bootstrapDart2Wasm( BuildStep buildStep, List additionalArguments, String javaScriptModuleExtension, @@ -69,7 +83,7 @@ $librariesString https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings '''); - return null; + return const Dart2WasmBootstrapResult.didNotCompile(); } var scratchSpace = await buildStep.fetchResource(scratchSpaceResource); @@ -131,6 +145,7 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski } else { log.severe('ExitCode:${result.exitCode}\nStdOut:\n${result.stdout}\n' 'StdErr:\n${result.stderr}'); + return const Dart2WasmBootstrapResult.didNotCompile(); } var supportFile = @@ -140,5 +155,5 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski supportExpression = await supportFile.readAsString(); } - return (supportExpression: supportExpression); + return Dart2WasmBootstrapResult(supportExpression: supportExpression); } diff --git a/build_web_compilers/lib/src/web_entrypoint_builder.dart b/build_web_compilers/lib/src/web_entrypoint_builder.dart index 9d97f5e8c..5783cfd4f 100644 --- a/build_web_compilers/lib/src/web_entrypoint_builder.dart +++ b/build_web_compilers/lib/src/web_entrypoint_builder.dart @@ -329,9 +329,7 @@ class WebEntrypointBuilder implements Builder { AssetId input, Dart2WasmBootstrapResult? dart2WasmResult) { var loaderExtension = options.loaderExtension; var wasmCompiler = options.optionsFor(WebCompiler.Dart2Wasm); - if (loaderExtension == null || - wasmCompiler == null || - dart2WasmResult == null) { + if (loaderExtension == null || wasmCompiler == null) { // Generating the loader has been disabled or no loader is necessary. return null; } @@ -357,7 +355,7 @@ function relativeURL(ref) { // If we're compiling to JS, start a feature detection to prefer wasm but // fall back to JS if necessary. if (jsCompiler != null) { - final supportCheck = dart2WasmResult.supportExpression ?? + final supportCheck = dart2WasmResult?.supportExpression ?? "'WebAssembly' in self && " 'WebAssembly.validate(new Uint8Array(' '[0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 95, 1, 120, 0]));'; diff --git a/build_web_compilers/pubspec.yaml b/build_web_compilers/pubspec.yaml index dedeef876..1e24cd00f 100644 --- a/build_web_compilers/pubspec.yaml +++ b/build_web_compilers/pubspec.yaml @@ -1,5 +1,5 @@ name: build_web_compilers -version: 4.1.2-dev +version: 4.1.2-wip description: Builder implementations wrapping the dart2js and DDC compilers. repository: https://github.com/dart-lang/build/tree/master/build_web_compilers resolution: workspace diff --git a/build_web_compilers/test/dart2wasm_bootstrap_test.dart b/build_web_compilers/test/dart2wasm_bootstrap_test.dart index 004d48518..6282d9f43 100644 --- a/build_web_compilers/test/dart2wasm_bootstrap_test.dart +++ b/build_web_compilers/test/dart2wasm_bootstrap_test.dart @@ -70,6 +70,12 @@ void main() { stringContainsInOrder( [ 'if', + // Depending on whether dart2wasm emitted a .support.js file, + // this check either comes from dart2wasm or from our own script + // doing its own feature detection as a fallback. Which one is + // used depends on the SDK version, we can only assume that the + // check is guaranteed to include WebAssembly.validate to check + // for WASM features. 'WebAssembly.validate', '{', 'compileStreaming',