Skip to content

Use dart2wasm support-detection scripts #3845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build_web_compilers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 4.1.2-wip

- Use support-detection scripts emitted by `dart2wasm`.

## 4.1.1

- Support 3.8.0 pre-release sdks.
Expand Down
40 changes: 36 additions & 4 deletions build_web_compilers/lib/src/dart2wasm_bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,42 @@ import 'web_entrypoint_builder.dart';

final _resourcePool = Pool(maxWorkersPerTask);

/// Result of invoking the `dart2wasm` compiler.
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<void> bootstrapDart2Wasm(
Future<Dart2WasmBootstrapResult> bootstrapDart2Wasm(
BuildStep buildStep,
List<String> additionalArguments,
String javaScriptModuleExtension,
) async {
await _resourcePool.withResource(() => _bootstrapDart2Wasm(
return await _resourcePool.withResource(() => _bootstrapDart2Wasm(
buildStep, additionalArguments, javaScriptModuleExtension));
}

Future<void> _bootstrapDart2Wasm(
Future<Dart2WasmBootstrapResult> _bootstrapDart2Wasm(
BuildStep buildStep,
List<String> additionalArguments,
String javaScriptModuleExtension,
Expand Down Expand Up @@ -61,7 +83,7 @@ $librariesString

https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-skipped-compiling-warnings
''');
return;
return const Dart2WasmBootstrapResult.didNotCompile();
}

var scratchSpace = await buildStep.fetchResource(scratchSpaceResource);
Expand Down Expand Up @@ -123,5 +145,15 @@ 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 =
scratchSpace.fileFor(dartEntrypointId.changeExtension('.support.js'));
String? supportExpression;
if (await supportFile.exists()) {
supportExpression = await supportFile.readAsString();
}

return Dart2WasmBootstrapResult(supportExpression: supportExpression);
}
29 changes: 15 additions & 14 deletions build_web_compilers/lib/src/web_entrypoint_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ class WebEntrypointBuilder implements Builder {
if (!isAppEntrypoint) return;

final compilationSteps = <Future>[];
Dart2WasmBootstrapResult? dart2WasmResult;

for (final compiler in options.compilers) {
switch (compiler.compiler) {
Expand All @@ -311,17 +312,21 @@ 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) {
Expand Down Expand Up @@ -350,17 +355,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) {
''');
}

Expand Down
2 changes: 1 addition & 1 deletion build_web_compilers/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: build_web_compilers
version: 4.1.1
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
Expand Down
12 changes: 10 additions & 2 deletions build_web_compilers/test/dart2wasm_bootstrap_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,17 @@ void main() {
'a|web/index.dart.js': decodedMatches(
stringContainsInOrder(
[
'if (supportsWasmGC())',
'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',
'else',
'} else {',
'scriptTag.src = relativeURL("./index.dart2js.js");'
],
),
Expand Down
Loading