Files
sdk/pkg/dart2wasm/lib/compiler_options.dart
T
Martin Kustermann 5cd42e6b21 [dart2wasm] Start emitting binaryen.inline custom section
Binaryen introduced `binaryen.inline` which allows us to tell it
inlining hints now, including "never inline" hint (see [0])

This allows us to remove the ugly mangling of wasm function names with
`<noInline>` postfix.

We also now pass `--strip-toolchain-annotations`: The annotations
occupy size in the wasm binary and wasm runtimes ignore them (they are
for `wams-opt` only).

Except for IR tests: Here we want to see the annotations, so we keep
them there.

We also rename the package:wasm_builder classes to clearly indicate
those are binaryen specific sections.

We also make the ir_test.dart put it's options first, allowing the IR
tests to override options if needed.

[0] https://github.com/WebAssembly/binaryen/commit/3c25487214600a9

Change-Id: I96688bfaeba403a39cd5e7376f8d2889bcbae030
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/510000
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
2026-06-10 15:51:47 -07:00

249 lines
7.7 KiB
Dart

// Copyright (c) 2022, 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:front_end/src/api_unstable/vm.dart' as fe;
import 'package:path/path.dart' as path;
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,
opt;
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 {
static const int mainModuleId = 0;
final TranslatorOptions translatorOptions = TranslatorOptions();
Uri? platformPath;
Uri? librariesSpecPath;
Uri? packagesPath;
Uri mainUri;
String outputFile;
String? depFile;
Uri? deferredMapUri;
bool useLoadIds = false;
Uri? programSplitConstraintsUri;
Map<String, String> environment = {};
Map<fe.ExperimentalFlag, bool> feExperimentalFlags = const {};
String? multiRootScheme;
List<Uri> multiRoots = const [];
List<String> deleteToStringPackageUri = const [];
String? dumpKernelAfterCfe;
String? dumpKernelBeforeTfa;
String? dumpKernelAfterTfa;
bool dryRun = false;
Uri? wasmOptPath;
bool stripToolchainAnnotations = true;
int maxActiveWasmOptProcesses = _defaultMaxActiveWasmOptProcesses();
bool saveUnopt = false;
Set<int> moduleIdsToOptimize = const {};
bool stripWasm = true;
Uri? recordedUsesFile;
List<CompilerPhase> phases = const [
CompilerPhase.cfe,
CompilerPhase.tfa,
CompilerPhase.codegen,
];
factory WasmCompilerOptions.defaultOptions() =>
WasmCompilerOptions(mainUri: Uri(), outputFile: '');
WasmCompilerOptions({required this.mainUri, required this.outputFile});
bool get useMultiModuleOpt =>
translatorOptions.enableDeferredLoading ||
translatorOptions.enableMultiModuleStressTestMode;
static String moduleNameForId(
String filePath,
int id, {
bool emitAsMain = false,
}) {
final basename = path.basename(filePath);
if (emitAsMain || id == mainModuleId) {
return basename;
}
return '${deferredModuleFilenamePrefix(basename)}$id.wasm';
}
static int? idForModuleName(String mainWasmFilename, String moduleFilename) {
assert(
mainWasmFilename.endsWith('.wasm') &&
!mainWasmFilename.contains(path.separator),
);
if (mainWasmFilename == moduleFilename) return mainModuleId;
final prefix = '${path.basenameWithoutExtension(mainWasmFilename)}_module';
if (!moduleFilename.startsWith(prefix) ||
!moduleFilename.endsWith('.wasm')) {
return null;
}
return idFromDeferredModuleFilename(moduleFilename);
}
/// Given a deferred module filename returns the module id.
///
/// (i.e. returns `<id>` for `test_module<id>.wasm`).
static int idFromDeferredModuleFilename(String moduleName) {
// The name has pattern: "..._module<moduleId>.wasm"
assert(moduleName.endsWith('.wasm') && moduleName.contains('_module'));
final offset = moduleName.lastIndexOf('_module') + '_module'.length;
return int.parse(
moduleName.substring(offset, moduleName.length - '.wasm'.length),
);
}
/// The prefix of all deferred module filenames.
///
/// (i.e. returns `test_module` for main module `test.wasm` and
/// deferred modules `test_module<id>.wasm`).
static String deferredModuleFilenamePrefix(String mainModuleFilename) =>
path.basename(path.setExtension(mainModuleFilename, '_module'));
static int _defaultMaxActiveWasmOptProcesses() {
try {
return Platform.numberOfProcessors;
} catch (e) {
// Fallback to 8 if numberOfProcessors is not available (e.g. if this is
// running in a web context).
return 8;
}
}
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) {
throw ArgumentError(
"--shared-memory-max-pages must be specified if "
"--import-shared-memory is used.",
);
}
if (!translatorOptions.enableDeferredLoading) {
if (deferredMapUri != null) {
throw ArgumentError(
"--deferred-map can only be used with "
"--enable-deferred-loading",
);
}
if (useLoadIds) {
throw ArgumentError(
"--use-load-ids can only be used with "
"--enable-deferred-loading",
);
}
} else if (useLoadIds && deferredMapUri == null) {
throw ArgumentError("--use-load-ids requires --deferred-map");
}
if (translatorOptions.standalone) {
void handleUnsupportedOption(bool enabled, String name) {
if (enabled) {
throw ArgumentError('$name is not supported with --standalone');
}
}
handleUnsupportedOption(
translatorOptions.enableDeferredLoading,
'--enable-deferred-loading',
);
}
_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.');
}
case CompilerPhase.opt:
if (inputExtension != '.wasm') {
throw ArgumentError('Input to opt phase must be a .wasm 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.',
);
}
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',
);
}
}
}