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>
885 lines
26 KiB
Dart
885 lines
26 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:convert';
|
|
|
|
import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem;
|
|
import 'package:front_end/src/api_unstable/vm.dart'
|
|
show
|
|
CompilerOptions,
|
|
CompilerResult,
|
|
CfeDiagnosticMessage,
|
|
kernelForProgram,
|
|
CfeSeverity;
|
|
import 'package:kernel/ast.dart';
|
|
import 'package:kernel/class_hierarchy.dart';
|
|
import 'package:kernel/core_types.dart';
|
|
import 'package:kernel/library_index.dart';
|
|
import 'package:kernel/type_environment.dart';
|
|
import 'package:kernel/verifier.dart';
|
|
import 'package:path/path.dart' as path;
|
|
import 'package:pool/pool.dart' as pool;
|
|
import 'package:record_use/record_use.dart' as record_use;
|
|
import 'package:vm/kernel_front_end.dart' show writeDepfile;
|
|
import 'package:vm/transformations/mixin_deduplication.dart'
|
|
as mixin_deduplication
|
|
show transformLibraries;
|
|
import 'package:vm/transformations/record_use/record_use.dart' as record_use;
|
|
import 'package:vm/transformations/to_string_transformer.dart'
|
|
as to_string_transformer;
|
|
import 'package:vm/transformations/type_flow/transformer.dart'
|
|
as globalTypeFlow
|
|
show transformComponent;
|
|
import 'package:vm/transformations/type_flow/utils.dart' as tfa_utils;
|
|
import 'package:vm/transformations/unreachable_code_elimination.dart'
|
|
as unreachable_code_elimination;
|
|
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 'generate_wasm.dart';
|
|
import 'io_util.dart';
|
|
import 'js/runtime_generator.dart' as js;
|
|
import 'modules.dart';
|
|
import 'record_class_generator.dart';
|
|
import 'records.dart';
|
|
import 'source_map_utils.dart';
|
|
import 'target.dart' as wasm show Mode;
|
|
import 'target.dart' hide Mode;
|
|
import 'translator.dart';
|
|
import 'util.dart';
|
|
|
|
sealed class CompilationResult {}
|
|
|
|
abstract class CompilationDryRunResult extends CompilationResult {}
|
|
|
|
class CompilationDryRunError extends CompilationDryRunResult {}
|
|
|
|
class CompilationDryRunSuccess extends CompilationDryRunResult {}
|
|
|
|
sealed class CompilationSuccess extends CompilationResult {}
|
|
|
|
class CfeResult extends CompilationSuccess {
|
|
final Component component;
|
|
final CoreTypes coreTypes;
|
|
|
|
CfeResult(this.component, this.coreTypes);
|
|
}
|
|
|
|
class TfaResult extends CompilationSuccess {
|
|
final Component component;
|
|
final CoreTypes coreTypes;
|
|
final LibraryIndex libraryIndex;
|
|
final ModuleStrategy moduleStrategy;
|
|
final Map<RecordShape, Class> recordClasses;
|
|
|
|
TfaResult(
|
|
this.component,
|
|
this.coreTypes,
|
|
this.libraryIndex,
|
|
this.moduleStrategy,
|
|
this.recordClasses,
|
|
);
|
|
}
|
|
|
|
class CodegenResult extends CompilationSuccess {
|
|
/// The main wasm file of the compiled application.
|
|
final String mainWasmFile;
|
|
|
|
/// The ids of all emitted wasm modules, including the special `0` id
|
|
/// for the main module.
|
|
final Set<int> moduleIds;
|
|
|
|
CodegenResult(this.mainWasmFile, this.moduleIds) {
|
|
assert(moduleIds.contains(compiler.WasmCompilerOptions.mainModuleId));
|
|
}
|
|
}
|
|
|
|
class OptResult extends CompilationSuccess {
|
|
final String mainWasmFile;
|
|
final int numModules;
|
|
|
|
OptResult(this.mainWasmFile, this.numModules);
|
|
}
|
|
|
|
abstract class CompilationError extends CompilationResult {}
|
|
|
|
/// The CFE has crashed with an exception.
|
|
///
|
|
/// This is a CFE bug and should be reported by users.
|
|
class CFECrashError extends CompilationError {
|
|
final Object error;
|
|
final StackTrace stackTrace;
|
|
|
|
CFECrashError(this.error, this.stackTrace);
|
|
|
|
@override
|
|
String toString() => 'CFECrashError($error):\n$stackTrace';
|
|
}
|
|
|
|
/// Compiling the Dart program resulted in compile-time errors.
|
|
///
|
|
/// This is a bug in the dart program (e.g. syntax errors, static type errors,
|
|
/// ...) that's being compiled. Users have to address those errors in their
|
|
/// code for it to compile successfully.
|
|
///
|
|
/// The errors are already printed via the `handleDiagnosticMessage` callback.
|
|
/// (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 {
|
|
final Component? component;
|
|
|
|
CFECompileTimeErrors(this.component);
|
|
}
|
|
|
|
List<String> librariesToIndex(wasm.Mode mode) {
|
|
return [
|
|
"dart:_boxed_bool",
|
|
"dart:_boxed_double",
|
|
"dart:_boxed_int",
|
|
"dart:_compact_hash",
|
|
"dart:_internal",
|
|
"dart:_js_helper",
|
|
"dart:_js_types",
|
|
"dart:_list",
|
|
"dart:_string",
|
|
"dart:_wasm",
|
|
"dart:async",
|
|
"dart:collection",
|
|
"dart:core",
|
|
"dart:ffi",
|
|
"dart:typed_data",
|
|
if (mode == .standalone) "dart:_embedder",
|
|
];
|
|
}
|
|
|
|
const List<String> _binaryenFlags = [
|
|
'--enable-gc',
|
|
'--enable-reference-types',
|
|
'--enable-multivalue',
|
|
'--enable-exception-handling',
|
|
'--enable-nontrapping-float-to-int',
|
|
'--enable-sign-ext',
|
|
'--enable-bulk-memory',
|
|
'--enable-threads',
|
|
'--enable-simd',
|
|
'--closed-world',
|
|
'--traps-never-happen',
|
|
'--type-unfinalizing',
|
|
'-Os',
|
|
'--type-ssa',
|
|
'--gufa',
|
|
'-Os',
|
|
'--type-merging',
|
|
'-Os',
|
|
'--type-finalizing',
|
|
'--minimize-rec-groups',
|
|
];
|
|
|
|
const List<String> _binaryenFlagsMultiModule = [
|
|
'--enable-gc',
|
|
'--enable-reference-types',
|
|
'--enable-multivalue',
|
|
'--enable-exception-handling',
|
|
'--enable-nontrapping-float-to-int',
|
|
'--enable-sign-ext',
|
|
'--enable-bulk-memory',
|
|
'--enable-threads',
|
|
'--enable-simd',
|
|
'--traps-never-happen',
|
|
'-Os',
|
|
'-Os',
|
|
];
|
|
|
|
/// Compile a Dart file into a Wasm module.
|
|
///
|
|
/// Returns `null` if an error occurred during compilation. The
|
|
/// [handleDiagnosticMessage] callback will have received an error message
|
|
/// describing the error.
|
|
///
|
|
/// When generating source maps, `sourceMapUrlGenerator` argument should be
|
|
/// provided which takes the module name and gives the URL of the source map.
|
|
/// This value will be added to the Wasm module in `sourceMappingURL` section.
|
|
/// When this argument is null the code generator does not generate source
|
|
/// mappings.
|
|
Future<CompilationResult> compile(
|
|
compiler.WasmCompilerOptions options,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
void Function(CfeDiagnosticMessage) handleDiagnosticMessage,
|
|
) async {
|
|
final wasm.Mode mode;
|
|
if (options.translatorOptions.jsCompatibility) {
|
|
mode = wasm.Mode.jsCompatibility;
|
|
} else if (options.translatorOptions.standalone) {
|
|
mode = wasm.Mode.standalone;
|
|
} else {
|
|
mode = wasm.Mode.regular;
|
|
}
|
|
final WasmTarget target = WasmTarget(
|
|
enableExperimentalFfi: options.translatorOptions.enableExperimentalFfi,
|
|
enableExperimentalWasmInterop:
|
|
options.translatorOptions.enableExperimentalWasmInterop,
|
|
removeAsserts: !options.translatorOptions.enableAsserts,
|
|
mode: mode,
|
|
);
|
|
|
|
CfeResult? cfeResult;
|
|
TfaResult? tfaResult;
|
|
CodegenResult? codegenResult;
|
|
CompilationResult? lastResult;
|
|
|
|
for (final phase in options.phases) {
|
|
switch (phase) {
|
|
case compiler.CompilerPhase.cfe:
|
|
lastResult = await _runCfePhase(
|
|
options,
|
|
target,
|
|
ioManager.fileSystem,
|
|
ioManager,
|
|
handleDiagnosticMessage,
|
|
);
|
|
if (lastResult is! CfeResult) return lastResult;
|
|
cfeResult = lastResult;
|
|
|
|
case compiler.CompilerPhase.tfa:
|
|
lastResult = await _runTfaPhase(
|
|
cfeResult ?? await _loadCfeResult(options, ioManager),
|
|
options,
|
|
target,
|
|
ioManager,
|
|
);
|
|
if (lastResult is! TfaResult) return lastResult;
|
|
tfaResult = lastResult;
|
|
|
|
case compiler.CompilerPhase.codegen:
|
|
lastResult = await _runCodegenPhase(
|
|
tfaResult ?? await _loadTfaResult(options, target, ioManager),
|
|
options,
|
|
ioManager,
|
|
);
|
|
|
|
if (lastResult is! CodegenResult) return lastResult;
|
|
codegenResult = lastResult;
|
|
|
|
case compiler.CompilerPhase.opt:
|
|
lastResult = await _runOptPhase(
|
|
codegenResult ?? await _loadCodegenResult(options, ioManager),
|
|
options,
|
|
ioManager,
|
|
);
|
|
|
|
if (lastResult is! OptResult) return lastResult;
|
|
}
|
|
}
|
|
|
|
return lastResult!;
|
|
}
|
|
|
|
Future<CfeResult> _loadCfeResult(
|
|
compiler.WasmCompilerOptions options,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
) async {
|
|
final component = Component();
|
|
await ioManager.readComponent(options.mainUri, component);
|
|
final coreTypes = CoreTypes(component);
|
|
return CfeResult(component, coreTypes);
|
|
}
|
|
|
|
Future<CompilationResult> _runCfePhase(
|
|
compiler.WasmCompilerOptions options,
|
|
WasmTarget target,
|
|
FileSystem fileSystem,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
void Function(CfeDiagnosticMessage) handleDiagnosticMessage,
|
|
) async {
|
|
var hadCompileTimeError = false;
|
|
void diagnosticMessageHandler(CfeDiagnosticMessage message) {
|
|
if (message.severity == CfeSeverity.error) {
|
|
hadCompileTimeError = true;
|
|
}
|
|
handleDiagnosticMessage(message);
|
|
}
|
|
|
|
CompilerOptions compilerOptions = CompilerOptions()
|
|
..target = target
|
|
// This is a dummy directory that always exists. This option should be
|
|
// unused as we pass platform.dill or libraries.json, though currently the
|
|
// CFE mandates this option to be there (but doesn't use it).
|
|
// => Remove this once CFE no longer mandates this (or remove option in CFE
|
|
// entirely).
|
|
..sdkRoot = Uri.file('.')
|
|
..librariesSpecificationUri = options.librariesSpecPath
|
|
..packagesFileUri = options.packagesPath
|
|
..environmentDefines = {
|
|
'dart.tool.dart2wasm': 'true',
|
|
'dart.tool.dart2wasm.minify': '${options.translatorOptions.minify}',
|
|
...options.environment,
|
|
}
|
|
..explicitExperimentalFlags = options.feExperimentalFlags
|
|
..verbose = false
|
|
..embedSourceText = options.translatorOptions.enableAsserts
|
|
..onDiagnostic = diagnosticMessageHandler
|
|
..fileSystem = fileSystem;
|
|
|
|
if (options.platformPath != null) {
|
|
compilerOptions.sdkSummary = options.platformPath;
|
|
} else {
|
|
compilerOptions.compileSdk = true;
|
|
}
|
|
|
|
CompilerResult? compilerResult;
|
|
try {
|
|
compilerResult = await kernelForProgram(options.mainUri, compilerOptions);
|
|
} catch (e, s) {
|
|
return CFECrashError(e, s);
|
|
}
|
|
if (options.dryRun) {
|
|
final component = compilerResult?.component;
|
|
if (component == null) {
|
|
return CompilationDryRunError();
|
|
}
|
|
final summarizer = DryRunSummarizer(
|
|
component,
|
|
enableExperimentalFfi: options.translatorOptions.enableExperimentalFfi,
|
|
);
|
|
final hasErrors = await summarizer.summarize();
|
|
return hasErrors ? CompilationDryRunError() : CompilationDryRunSuccess();
|
|
}
|
|
if (hadCompileTimeError) {
|
|
return CFECompileTimeErrors(compilerResult?.component);
|
|
}
|
|
final component = compilerResult!.component!;
|
|
|
|
if (options.dumpKernelAfterCfe != null) {
|
|
ioManager.writeComponentAsText(component, options.dumpKernelAfterCfe!);
|
|
}
|
|
|
|
if (options.emitCfe) {
|
|
await ioManager.writeComponent(component, options.outputFile);
|
|
}
|
|
|
|
return CfeResult(component, compilerResult.coreTypes!);
|
|
}
|
|
|
|
Future<TfaResult> _loadTfaResult(
|
|
compiler.WasmCompilerOptions options,
|
|
WasmTarget target,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
) async {
|
|
final component = createEmptyComponent();
|
|
final recordClassesRepository = _RecordClassesRepository();
|
|
component.addMetadataRepository(recordClassesRepository);
|
|
|
|
await ioManager.readComponent(options.mainUri, component);
|
|
|
|
final coreTypes = CoreTypes(component);
|
|
final libraryIndex = LibraryIndex(component, librariesToIndex(target.mode));
|
|
final classHierarchy = ClassHierarchy(component, coreTypes);
|
|
|
|
final moduleStrategy = await _createModuleStrategy(
|
|
options,
|
|
ioManager,
|
|
component,
|
|
coreTypes,
|
|
target,
|
|
classHierarchy,
|
|
);
|
|
|
|
final recordClasses = <RecordShape, Class>{};
|
|
recordClassesRepository.mapping.forEach((cls, shape) {
|
|
recordClasses[shape] = cls;
|
|
});
|
|
|
|
return TfaResult(
|
|
component,
|
|
coreTypes,
|
|
libraryIndex,
|
|
moduleStrategy,
|
|
recordClasses,
|
|
);
|
|
}
|
|
|
|
Future<CompilationResult> _runTfaPhase(
|
|
CfeResult cfeResult,
|
|
compiler.WasmCompilerOptions options,
|
|
WasmTarget target,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
) async {
|
|
var CfeResult(:component, :coreTypes) = cfeResult;
|
|
|
|
ClosedWorldClassHierarchy classHierarchy =
|
|
ClassHierarchy(component, coreTypes) as ClosedWorldClassHierarchy;
|
|
LibraryIndex libraryIndex = LibraryIndex(
|
|
component,
|
|
librariesToIndex(target.mode),
|
|
);
|
|
|
|
if (options.deleteToStringPackageUri.isNotEmpty) {
|
|
to_string_transformer.transformComponent(
|
|
component,
|
|
options.deleteToStringPackageUri,
|
|
);
|
|
}
|
|
|
|
if (!options.translatorOptions.standalone) {
|
|
js.performJSInteropTransformations(
|
|
component.libraries,
|
|
coreTypes,
|
|
classHierarchy,
|
|
);
|
|
}
|
|
|
|
final librariesToTransform = component.libraries;
|
|
final constantEvaluator = ConstantEvaluator(
|
|
options,
|
|
target,
|
|
component,
|
|
coreTypes,
|
|
classHierarchy,
|
|
libraryIndex,
|
|
);
|
|
unreachable_code_elimination.transformLibraries(
|
|
target,
|
|
librariesToTransform,
|
|
constantEvaluator,
|
|
options.translatorOptions.enableAsserts,
|
|
);
|
|
|
|
final Map<RecordShape, Class> recordClasses = generateRecordClasses(
|
|
component,
|
|
coreTypes,
|
|
);
|
|
target.recordClasses = recordClasses;
|
|
|
|
if (options.dumpKernelBeforeTfa != null) {
|
|
ioManager.writeComponentAsText(component, options.dumpKernelBeforeTfa!);
|
|
}
|
|
|
|
final moduleStrategy = await _createModuleStrategy(
|
|
options,
|
|
ioManager,
|
|
component,
|
|
coreTypes,
|
|
target,
|
|
classHierarchy,
|
|
);
|
|
|
|
// Ensure we annotate AST nodes as entry points prior to other transformations
|
|
// looking at pragmas (such as mixin_deduplication and TFA).
|
|
moduleStrategy.addEntryPoints();
|
|
|
|
mixin_deduplication.transformLibraries(
|
|
librariesToTransform,
|
|
coreTypes,
|
|
target,
|
|
// This puts each canonical mixin application in its own library so that
|
|
// the import graph does not need to add edges to a single library
|
|
// containing all mixin applications.
|
|
useUniqueDeduplicationLibrary:
|
|
options.translatorOptions.enableDeferredLoading,
|
|
);
|
|
|
|
// Ensure this happens after mixin deduplication so that all libraries and
|
|
// classes are present.
|
|
moduleStrategy.prepareComponent();
|
|
|
|
final hasDeferredImports = component.libraries.any(
|
|
(lib) => lib.dependencies.any((d) => d.isDeferred),
|
|
);
|
|
if (hasDeferredImports) {
|
|
DeferredLoadingLowering.markRuntimeFunctionsAsEntrypoints(coreTypes);
|
|
}
|
|
|
|
_patchMainTearOffs(coreTypes, component);
|
|
|
|
// We initialize the [printStats] to `false` to prevent it's field
|
|
// initializer to run (which only works on VM -- but we want our compiler
|
|
// to also run if compiled via dart2js/dart2wasm)
|
|
tfa_utils.printStats = false;
|
|
|
|
// Keep the flags in-sync with
|
|
// pkg/vm/test/transformations/type_flow/transformer_test.dart
|
|
globalTypeFlow.transformComponent(
|
|
target,
|
|
coreTypes,
|
|
component,
|
|
useRapidTypeAnalysis: true,
|
|
treeShakeProtobufs:
|
|
options.translatorOptions.enableProtobufTreeShaker ||
|
|
options.translatorOptions.enableProtobufMixinTreeShaker,
|
|
treeShakeProtobufMixins:
|
|
options.translatorOptions.enableProtobufMixinTreeShaker,
|
|
);
|
|
|
|
// TFA may have tree shaken members that are in the library index cache.
|
|
// To avoid having dangling references in the index, we create a new one.
|
|
libraryIndex = LibraryIndex(component, librariesToIndex(target.mode));
|
|
|
|
if (options.emitTfa) {
|
|
// Store metadata needed for codegen so that it can be serialized.
|
|
final recordClassesRepo = _RecordClassesRepository();
|
|
recordClasses.forEach((shape, cls) {
|
|
recordClassesRepo.mapping[cls] = shape;
|
|
});
|
|
component.addMetadataRepository(recordClassesRepo);
|
|
}
|
|
|
|
assert(() {
|
|
verifyComponent(
|
|
target,
|
|
VerificationStage.afterGlobalTransformations,
|
|
component,
|
|
);
|
|
return true;
|
|
}());
|
|
|
|
if (options.dumpKernelAfterTfa != null) {
|
|
ioManager.writeComponentAsText(component, options.dumpKernelAfterTfa!);
|
|
}
|
|
|
|
if (options.emitTfa) {
|
|
await ioManager.writeComponent(component, options.outputFile);
|
|
}
|
|
|
|
return TfaResult(
|
|
component,
|
|
coreTypes,
|
|
libraryIndex,
|
|
moduleStrategy,
|
|
recordClasses,
|
|
);
|
|
}
|
|
|
|
Future<CodegenResult> _loadCodegenResult(
|
|
compiler.WasmCompilerOptions options,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
) async {
|
|
final mainUri = (await ioManager.resolveUri(options.mainUri))!.toFilePath();
|
|
return CodegenResult(mainUri, await ioManager.getModuleIds(mainUri));
|
|
}
|
|
|
|
Future<CompilationResult> _runCodegenPhase(
|
|
TfaResult tfaSuccess,
|
|
compiler.WasmCompilerOptions options,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
) async {
|
|
final TfaResult(
|
|
:component,
|
|
:coreTypes,
|
|
:moduleStrategy,
|
|
:libraryIndex,
|
|
:recordClasses,
|
|
) = tfaSuccess;
|
|
|
|
final loadingMap = DeferredModuleLoadingMap.fromComponent(component);
|
|
component.accept(DeferredLoadingLowering(coreTypes, loadingMap));
|
|
|
|
// May populate [loadingMap] by creating various [ModuleOutputData].
|
|
await moduleStrategy.processComponentAfterTfa(loadingMap);
|
|
|
|
final moduleOutputData = moduleStrategy.buildModuleOutputData();
|
|
|
|
final translator = Translator(
|
|
component,
|
|
coreTypes,
|
|
libraryIndex,
|
|
recordClasses,
|
|
loadingMap,
|
|
moduleOutputData,
|
|
options.translatorOptions,
|
|
);
|
|
|
|
String? depFile = options.depFile;
|
|
if (depFile != null) {
|
|
writeDepfile(
|
|
ioManager.fileSystem,
|
|
component.uriToSource.keys,
|
|
options.outputFile,
|
|
depFile,
|
|
);
|
|
}
|
|
|
|
final generateSourceMaps = options.translatorOptions.generateSourceMaps;
|
|
final modules = translator.translate(ioManager.sourceMapUrlGenerator);
|
|
final writeFutures = <Future<void>>[];
|
|
|
|
List<String?>? classNames;
|
|
if (generateSourceMaps && options.translatorOptions.minify) {
|
|
classNames = [];
|
|
for (var classId = 0; classId < translator.classes.length; classId += 1) {
|
|
classNames.add(translator.classes[classId].cls?.name);
|
|
}
|
|
}
|
|
|
|
modules.forEach((moduleMetadata, module) {
|
|
if (moduleMetadata.skipEmit) return;
|
|
final serializer = Serializer();
|
|
module.serialize(serializer);
|
|
writeFutures.add(
|
|
ioManager.writeWasmModule(serializer.data, moduleMetadata.moduleName),
|
|
);
|
|
if (generateSourceMaps) {
|
|
final sourceMapJson = serializer.sourceMapSerializer.serializeAsJson();
|
|
if (moduleMetadata.isMain && classNames != null) {
|
|
addMinifiedClassNames(sourceMapJson, classNames);
|
|
}
|
|
writeFutures.add(
|
|
ioManager.writeWasmSourceMap(
|
|
jsonEncode(sourceMapJson),
|
|
moduleMetadata.moduleName,
|
|
),
|
|
);
|
|
}
|
|
});
|
|
await Future.wait(writeFutures);
|
|
|
|
if (!options.translatorOptions.standalone) {
|
|
final jsRuntimeFinalizer = js.RuntimeFinalizer(
|
|
coreTypes,
|
|
translator.interopMemberNamer,
|
|
);
|
|
|
|
final jsRuntime = jsRuntimeFinalizer.generate(
|
|
moduleOutputData.mainModule.moduleImportName,
|
|
translator.functions.translatedProcedures,
|
|
translator.internalizedStringsForJSRuntime,
|
|
translator.options.requireJsStringBuiltin,
|
|
translator.options.enableDeferredLoading ||
|
|
translator.options.enableMultiModuleStressTestMode,
|
|
);
|
|
|
|
final supportJs = _generateSupportJs(options.translatorOptions);
|
|
|
|
final deferredMapFile = options.deferredMapUri;
|
|
if (deferredMapFile != null) {
|
|
await writeDeferredMapFile(component, coreTypes, options, loadingMap);
|
|
}
|
|
|
|
await ioManager.writeJsRuntime(jsRuntime);
|
|
await ioManager.writeSupportJs(supportJs);
|
|
}
|
|
|
|
final wasmOutputFilename = path.basename(options.outputFile);
|
|
final moduleIds = modules.keys
|
|
.map<int>(
|
|
(moduleMetadata) => WasmCompilerOptions.idForModuleName(
|
|
wasmOutputFilename,
|
|
moduleMetadata.moduleName,
|
|
)!,
|
|
)
|
|
.toSet();
|
|
|
|
if (options.recordedUsesFile != null) {
|
|
record_use.LoadingUnit loadingUnitForNode(TreeNode node) {
|
|
while (node is! NamedNode) {
|
|
node = node.parent!;
|
|
}
|
|
assert(node is Member || node is Class);
|
|
final moduleOutput = moduleOutputData.moduleForReference(node.reference);
|
|
if (moduleOutput == moduleOutputData.defaultModule &&
|
|
moduleOutputData.modules.length > 1) {
|
|
// This is an unassigned reference such as a constant class only
|
|
// used for annotations. Assign it to the main module as a placeholder.
|
|
return record_use.LoadingUnit(
|
|
moduleOutputData.mainModule.moduleImportName,
|
|
);
|
|
}
|
|
return record_use.LoadingUnit(moduleOutput.moduleImportName);
|
|
}
|
|
|
|
record_use.transformComponent(
|
|
component,
|
|
options.recordedUsesFile!,
|
|
loadingUnitLookup: loadingUnitForNode,
|
|
);
|
|
}
|
|
|
|
return CodegenResult(options.outputFile, moduleIds);
|
|
}
|
|
|
|
Future<CompilationResult> _runOptPhase(
|
|
CodegenResult codegenResult,
|
|
compiler.WasmCompilerOptions options,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
) async {
|
|
final moduleIdsToOptimize = options.moduleIdsToOptimize.isEmpty
|
|
? codegenResult.moduleIds
|
|
: options.moduleIdsToOptimize;
|
|
|
|
final numModules = moduleIdsToOptimize.length;
|
|
final optPool = pool.Pool(
|
|
options.maxActiveWasmOptProcesses == -1
|
|
? numModules
|
|
: options.maxActiveWasmOptProcesses,
|
|
);
|
|
|
|
final wasmOptFlags = <String>[
|
|
...(options.useMultiModuleOpt ? _binaryenFlagsMultiModule : _binaryenFlags),
|
|
if (options.stripToolchainAnnotations) '--strip-toolchain-annotations',
|
|
];
|
|
|
|
await Future.wait([
|
|
for (final moduleId in moduleIdsToOptimize)
|
|
optPool.withResource(() async {
|
|
await ioManager.runWasmOpt(
|
|
codegenResult.mainWasmFile,
|
|
moduleId,
|
|
wasmOptFlags,
|
|
);
|
|
}),
|
|
]);
|
|
await optPool.close();
|
|
return OptResult(options.outputFile, numModules);
|
|
}
|
|
|
|
Future<ModuleStrategy> _createModuleStrategy(
|
|
compiler.WasmCompilerOptions options,
|
|
CompilerPhaseInputOutputManager ioManager,
|
|
Component component,
|
|
CoreTypes coreTypes,
|
|
WasmTarget target,
|
|
ClassHierarchy classHierarchy,
|
|
) async {
|
|
if (options.translatorOptions.enableDeferredLoading) {
|
|
return DeferredLoadingModuleStrategy(
|
|
component,
|
|
options,
|
|
target,
|
|
coreTypes,
|
|
ioManager,
|
|
);
|
|
} else if (options.translatorOptions.enableMultiModuleStressTestMode) {
|
|
return StressTestModuleStrategy(
|
|
component,
|
|
coreTypes,
|
|
options,
|
|
target,
|
|
classHierarchy,
|
|
);
|
|
}
|
|
return DefaultModuleStrategy(coreTypes, component, options);
|
|
}
|
|
|
|
// Patches `dart:_internal`s `mainTearOff{0,1,2}` getters.
|
|
void _patchMainTearOffs(CoreTypes coreTypes, Component component) {
|
|
final mainMethod = component.mainMethod!;
|
|
final mainMethodType = mainMethod.computeSignatureOrFunctionType();
|
|
|
|
Procedure lookup(String name) {
|
|
return coreTypes.index.getTopLevelProcedure('dart:_internal', name);
|
|
}
|
|
|
|
(Procedure, DartType) lookupWithType(String name) {
|
|
final p = lookup(name);
|
|
return (p, p.function.positionalParameters[1].type);
|
|
}
|
|
|
|
void patchInvokeMainInternal(Procedure p) {
|
|
final invoke = lookup('_invokeMainInternal');
|
|
invoke.isExternal = false;
|
|
invoke.function.body = ReturnStatement(
|
|
StaticInvocation(
|
|
p,
|
|
Arguments([
|
|
VariableGet(invoke.function.positionalParameters.single),
|
|
ConstantExpression(StaticTearOffConstant(mainMethod)),
|
|
]),
|
|
),
|
|
)..parent = invoke.function;
|
|
}
|
|
|
|
final typeEnv = TypeEnvironment(
|
|
coreTypes,
|
|
ClassHierarchy(component, coreTypes),
|
|
);
|
|
bool mainHasType(DartType type) => typeEnv.isSubtypeOf(mainMethodType, type);
|
|
|
|
final (mainInvoke0, mainArg0Type) = lookupWithType('_invokeMainArg0');
|
|
final (mainInvoke1, mainArg1Type) = lookupWithType('_invokeMainArg1');
|
|
final (mainInvoke2, mainArg2Type) = lookupWithType('_invokeMainArg2');
|
|
|
|
if (mainHasType(mainArg2Type)) {
|
|
return patchInvokeMainInternal(mainInvoke2);
|
|
}
|
|
if (mainHasType(mainArg1Type)) {
|
|
return patchInvokeMainInternal(mainInvoke1);
|
|
}
|
|
if (mainHasType(mainArg0Type)) {
|
|
return patchInvokeMainInternal(mainInvoke0);
|
|
}
|
|
throw 'Main method has unexpected type: $mainMethodType';
|
|
}
|
|
|
|
class _RecordClassesRepository extends MetadataRepository<RecordShape> {
|
|
static const String _tag = 'dart2wasm.recordClasses';
|
|
@override
|
|
final Map<Class, RecordShape> mapping = {};
|
|
|
|
@override
|
|
RecordShape readFromBinary(Node node, BinarySource source) {
|
|
final positionals = source.readUInt30();
|
|
final namesLength = source.readUInt30();
|
|
final names = namesLength == 0 ? const <String>[] : <String>[];
|
|
for (int i = 0; i < namesLength; i++) {
|
|
names.add(source.readStringReference());
|
|
}
|
|
return RecordShape(positionals, names);
|
|
}
|
|
|
|
@override
|
|
String get tag => _tag;
|
|
|
|
@override
|
|
void writeToBinary(RecordShape metadata, Node node, BinarySink sink) {
|
|
sink.writeUInt30(metadata.positionals);
|
|
sink.writeUInt30(metadata.names.length);
|
|
for (final name in metadata.names) {
|
|
sink.writeStringReference(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
String _generateSupportJs(TranslatorOptions options) {
|
|
// Copied from
|
|
// https://github.com/GoogleChromeLabs/wasm-feature-detect/blob/main/src/detectors/gc/index.js
|
|
//
|
|
// Uses WasmGC types and will only validate correctly if the engine supports
|
|
// WasmGC:
|
|
// ```
|
|
// (module
|
|
// (type $type0 (struct (field $field0 i8)))
|
|
// )
|
|
// ```
|
|
//
|
|
// NOTE: Once we support more feature detections we may use
|
|
// `package:wasm_builder` to create the module instead of having a fixed one
|
|
// here.
|
|
const String supportsWasmGC =
|
|
'WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,95,1,120,0]))';
|
|
|
|
// Imports a `js-string` builtin spec function *with wrong signature*. An engine
|
|
//
|
|
// * *without* knowledge about `js-string` builtin would accept such an import at
|
|
// validation time.
|
|
//
|
|
// * *with* knowledge about `js-string` would refuse it as the signature
|
|
// used to import the `cast` function is not according to `js-string` spec
|
|
//
|
|
// ```
|
|
// (module
|
|
// (func $wasm:js-string.cast (;0;) (import "wasm:js-string" "cast"))
|
|
// )
|
|
// ```
|
|
const String supportsJsStringBuiltins =
|
|
'!WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,2,23,1,14,119,97,115,109,58,106,115,45,115,116,114,105,110,103,4,99,97,115,116,0,0]),{"builtins":["js-string"]})';
|
|
|
|
final requiredFeatures = [
|
|
supportsWasmGC,
|
|
if (options.requireJsStringBuiltin) supportsJsStringBuiltins,
|
|
];
|
|
return '(${requiredFeatures.join('&&')})';
|
|
}
|