blob: 727c3db76865d42f4d652344e24b61cbedb20d33 [file] [log] [blame] [edit]
// 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:typed_data';
import 'package:build_integration/file_system/multi_root.dart'
show MultiRootFileSystem;
import 'package:front_end/src/api_prototype/macros.dart' as macros
show isMacroLibraryUri;
import 'package:front_end/src/api_prototype/standard_file_system.dart'
show StandardFileSystem;
import 'package:front_end/src/api_unstable/vm.dart'
show
CompilerOptions,
CompilerResult,
DiagnosticMessage,
kernelForProgram,
NnbdMode,
Severity;
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart' show writeComponentToText;
import 'package:kernel/library_index.dart';
import 'package:kernel/verifier.dart';
import 'package:vm/kernel_front_end.dart' show writeDepfile;
import 'package:vm/transformations/mixin_deduplication.dart'
as mixin_deduplication show transformComponent;
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/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' as deferred_loading;
import 'js/runtime_generator.dart' as js;
import 'record_class_generator.dart';
import 'records.dart';
import 'target.dart' as wasm show Mode;
import 'target.dart' hide Mode;
import 'translator.dart';
sealed class CompilationResult {}
class CompilationSuccess extends CompilationResult {
final Map<String, ({Uint8List moduleBytes, String? sourceMap})> wasmModules;
final String jsRuntime;
CompilationSuccess(this.wasmModules, this.jsRuntime);
}
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);
}
/// 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 {
CFECompileTimeErrors();
}
/// 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> compileToModule(
compiler.WasmCompilerOptions options,
Uri Function(String moduleName)? sourceMapUrlGenerator,
void Function(DiagnosticMessage) handleDiagnosticMessage) async {
var hadCompileTimeError = false;
void diagnosticMessageHandler(DiagnosticMessage message) {
if (message.severity == Severity.error) {
hadCompileTimeError = true;
}
handleDiagnosticMessage(message);
}
final wasm.Mode mode;
if (options.translatorOptions.jsCompatibility) {
mode = wasm.Mode.jsCompatibility;
} else {
mode = wasm.Mode.regular;
}
final WasmTarget target = WasmTarget(
enableExperimentalFfi: options.translatorOptions.enableExperimentalFfi,
enableExperimentalWasmInterop:
options.translatorOptions.enableExperimentalWasmInterop,
removeAsserts: !options.translatorOptions.enableAsserts,
mode: mode);
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',
...options.environment,
}
..explicitExperimentalFlags = options.feExperimentalFlags
..verbose = false
..onDiagnostic = diagnosticMessageHandler
..nnbdMode = NnbdMode.Strong;
if (options.multiRootScheme != null) {
compilerOptions.fileSystem = MultiRootFileSystem(
options.multiRootScheme!,
options.multiRoots.isEmpty ? [Uri.base] : options.multiRoots,
StandardFileSystem.instance);
}
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 (hadCompileTimeError) return CFECompileTimeErrors();
assert(compilerResult != null);
Component component = compilerResult!.component!;
CoreTypes coreTypes = compilerResult.coreTypes!;
ClassHierarchy classHierarchy = compilerResult.classHierarchy!;
LibraryIndex libraryIndex = LibraryIndex(component, [
"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 (options.dumpKernelAfterCfe != null) {
writeComponentToText(component, path: options.dumpKernelAfterCfe!);
}
if (options.deleteToStringPackageUri.isNotEmpty) {
to_string_transformer.transformComponent(
component, options.deleteToStringPackageUri);
}
if (options.translatorOptions.enableMultiModuleStressTestMode) {
deferred_loading.transformComponentForTestMode(
component, classHierarchy, coreTypes, target);
}
ConstantEvaluator constantEvaluator = ConstantEvaluator(
options, target, component, coreTypes, classHierarchy, libraryIndex);
unreachable_code_elimination.transformComponent(target, component,
constantEvaluator, options.translatorOptions.enableAsserts);
js.RuntimeFinalizer jsRuntimeFinalizer =
js.createRuntimeFinalizer(component, coreTypes, classHierarchy);
final Map<RecordShape, Class> recordClasses =
generateRecordClasses(component, coreTypes);
target.recordClasses = recordClasses;
if (options.dumpKernelBeforeTfa != null) {
writeComponentToText(component, path: options.dumpKernelBeforeTfa!);
}
mixin_deduplication.transformComponent(component);
// Patch `dart:_internal`s `mainTearOff` getter.
final internalLib = component.libraries
.singleWhere((lib) => lib.importUri.toString() == 'dart:_internal');
final mainTearOff = internalLib.procedures
.singleWhere((procedure) => procedure.name.text == 'mainTearOff');
mainTearOff.isExternal = false;
mainTearOff.function.body = ReturnStatement(
ConstantExpression(StaticTearOffConstant(component.mainMethod!)));
// Keep the flags in-sync with
// pkg/vm/test/transformations/type_flow/transformer_test.dart
globalTypeFlow.transformComponent(target, coreTypes, component,
useRapidTypeAnalysis: false);
if (options.dumpKernelAfterTfa != null) {
writeComponentToText(component,
path: options.dumpKernelAfterTfa!, showMetadata: true);
}
assert(() {
verifyComponent(
target, VerificationStage.afterGlobalTransformations, component);
return true;
}());
final moduleOutputData = deferred_loading.modulesForComponent(
component, options, target, coreTypes);
var translator = Translator(component, coreTypes, libraryIndex, recordClasses,
moduleOutputData, options.translatorOptions);
String? depFile = options.depFile;
if (depFile != null) {
writeDepfile(
compilerOptions.fileSystem,
// TODO(https://dartbug.com/55246): track macro deps when available.
component.uriToSource.keys
.where((uri) => !macros.isMacroLibraryUri(uri)),
options.outputFile,
depFile);
}
final generateSourceMaps = options.translatorOptions.generateSourceMaps;
final modules = translator.translate(sourceMapUrlGenerator);
final wasmModules = <String, ({Uint8List moduleBytes, String? sourceMap})>{};
modules.forEach((moduleOutput, module) {
final serializer = Serializer();
module.serialize(serializer);
final wasmModuleSerialized = serializer.data;
final sourceMap =
generateSourceMaps ? serializer.sourceMapSerializer.serialize() : null;
wasmModules[moduleOutput.moduleName] =
(moduleBytes: wasmModuleSerialized, sourceMap: sourceMap);
});
String jsRuntime = jsRuntimeFinalizer.generate(
translator.functions.translatedProcedures,
translator.internalizedStringsForJSRuntime,
mode);
return CompilationSuccess(wasmModules, jsRuntime);
}