5cd42e6b21
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>
249 lines
7.7 KiB
Dart
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',
|
|
);
|
|
}
|
|
}
|
|
}
|