blob: cea8f86fbebf17a60ded5898dcdb164f2eb60bed [file] [log] [blame]
// Copyright (c) 2016, 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.
/// This is an interface to the Dart Kernel parser and Kernel binary generator.
///
/// It is used by the kernel-isolate to load Dart source code and generate
/// Kernel binary format.
///
/// This is either invoked as the root script of the Kernel isolate when used
/// as a part of
///
/// dart --dfe=utils/kernel-service/kernel-service.dart ...
///
/// invocation or it is invoked as a standalone script to perform training for
/// the app-jit snapshot
///
/// dart utils/kernel-service/kernel-service.dart --train <source-file>
///
///
library runtime.tools.kernel_service;
import 'dart:async' show Future;
import 'dart:io' show File, Platform hide FileSystemEntity;
import 'dart:isolate';
import 'dart:typed_data' show Uint8List;
import 'package:front_end/file_system.dart';
import 'package:front_end/front_end.dart';
import 'package:front_end/memory_file_system.dart';
import 'package:front_end/physical_file_system.dart';
import 'package:front_end/src/fasta/kernel/utils.dart';
import 'package:front_end/src/testing/hybrid_file_system.dart';
import 'package:kernel/kernel.dart' show Program;
import 'package:kernel/target/targets.dart' show TargetFlags;
import 'package:kernel/target/vm_fasta.dart' show VmFastaTarget;
const bool verbose = const bool.fromEnvironment('DFE_VERBOSE');
const bool strongMode = const bool.fromEnvironment('DFE_STRONG_MODE');
Future<CompilationResult> _parseScriptInFileSystem(
Uri script, FileSystem fileSystem,
{bool verbose: false, bool strongMode: false}) async {
final Uri packagesUri = (Platform.packageConfig != null)
? Uri.parse(Platform.packageConfig)
: await _findPackagesFile(fileSystem, script);
if (packagesUri == null) {
throw "Could not find .packages";
}
final Uri patchedSdk = Uri.base
.resolveUri(new Uri.file(Platform.resolvedExecutable))
.resolveUri(new Uri.directory("patched_sdk"));
if (verbose) {
print("""DFE: Requesting compilation {
scriptUri: ${script}
packagesUri: ${packagesUri}
patchedSdk: ${patchedSdk}
}""");
}
try {
var errors = <String>[];
var options = new CompilerOptions()
..strongMode = strongMode
..fileSystem = fileSystem
..target = new VmFastaTarget(new TargetFlags(strongMode: strongMode))
..packagesFileUri = packagesUri
// TODO(sigmund): use outline.dill when the mixin transformer is modular.
..sdkSummary = patchedSdk.resolve('platform.dill')
..verbose = verbose
..onError = (CompilationError e) => errors.add(e.message);
Program program = await kernelForProgram(script, options);
if (errors.isNotEmpty) return new CompilationResult.errors(errors);
// We serialize the program excluding platform.dill because the VM has these
// sources built-in. Everything loaded as a summary in [kernelForProgram] is
// marked `external`, so we can use that bit to decide what to excluce.
// TODO(sigmund): remove the following line (Issue #30111)
program.libraries.forEach((e) => e.isExternal = false);
return new CompilationResult.ok(
serializeProgram(program, filter: (lib) => !lib.isExternal));
} catch (err, stack) {
return new CompilationResult.crash(err, stack);
}
}
/// This duplicates functionality from the Loader which we can't easily
/// access from here.
// TODO(sigmund): delete, this should be supported by the default options in
// package:front_end.
Future<Uri> _findPackagesFile(FileSystem fileSystem, Uri base) async {
var dir = new File.fromUri(base).parent;
while (true) {
final packagesFile = dir.uri.resolve(".packages");
if (await fileSystem.entityForUri(packagesFile).exists()) {
return packagesFile;
}
if (dir.parent.path == dir.path) {
break;
}
dir = dir.parent;
}
return null;
}
Future<CompilationResult> _processLoadRequestImpl(
String inputFilePathOrUri, FileSystem fileSystem) {
Uri scriptUri = Uri.parse(inputFilePathOrUri);
// Because we serve both Loader and bootstrapping requests we need to
// duplicate the logic from _resolveScriptUri(...) here and attempt to
// resolve schemaless uris using current working directory.
if (!scriptUri.hasScheme) {
// Script does not have a scheme, assume that it is a path,
// resolve it against the working directory.
scriptUri = Uri.base.resolveUri(new Uri.file(inputFilePathOrUri));
}
if (!scriptUri.isScheme('file')) {
// TODO(vegorov): Reuse loader code to support other schemes.
return new Future<CompilationResult>.value(new CompilationResult.errors(
["Expected 'file' scheme for a script uri: got ${scriptUri.scheme}"]));
}
return _parseScriptInFileSystem(scriptUri, fileSystem,
verbose: verbose, strongMode: strongMode);
}
// Process a request from the runtime. See KernelIsolate::CompileToKernel in
// kernel_isolate.cc and Loader::SendKernelRequest in loader.cc.
Future _processLoadRequest(request) async {
if (verbose) {
print("DFE: request: $request");
print("DFE: Platform.packageConfig: ${Platform.packageConfig}");
print("DFE: Platform.resolvedExecutable: ${Platform.resolvedExecutable}");
}
int tag = request[0];
final SendPort port = request[1];
final String inputFileUrl = request[2];
FileSystem fileSystem = request.length > 3
? _buildFileSystem(request[3])
: PhysicalFileSystem.instance;
CompilationResult result;
try {
result = await _processLoadRequestImpl(inputFileUrl, fileSystem);
} catch (error, stack) {
result = new CompilationResult.crash(error, stack);
}
if (verbose) {
print("DFE:> ${result}");
}
// Check whether this is a Loader request or a bootstrapping request from
// KernelIsolate::CompileToKernel.
final isBootstrapRequest = tag == null;
if (isBootstrapRequest) {
port.send(result.toResponse());
} else {
// See loader.cc for the code that handles these replies.
if (result.status != Status.ok) {
tag = -tag;
}
port.send([tag, inputFileUrl, inputFileUrl, null, result.payload]);
}
}
/// Creates a file system containing the files specified in [namedSources] and
/// that delegates to the underlying file system for any other file request.
/// The [namedSources] list interleaves file name string and
/// raw file content Uint8List.
///
/// The result can be used instead of PhysicalFileSystem.instance by the
/// frontend.
FileSystem _buildFileSystem(List namedSources) {
MemoryFileSystem fileSystem = new MemoryFileSystem(Uri.parse('file:///'));
for (int i = 0; i < namedSources.length ~/ 2; i++) {
fileSystem
.entityForUri(Uri.parse(namedSources[i * 2]))
.writeAsBytesSync(namedSources[i * 2 + 1]);
}
return new HybridFileSystem(fileSystem);
}
train(String scriptUri) {
// TODO(28532): Enable on Windows.
if (Platform.isWindows) return;
var tag = 1;
var responsePort = new RawReceivePort();
responsePort.handler = (response) {
if (response[0] == tag) {
// Success.
responsePort.close();
} else if (response[0] == -tag) {
// Compilation error.
throw response[4];
} else {
throw "Unexpected response: $response";
}
};
var request = [tag, responsePort.sendPort, scriptUri];
_processLoadRequest(request);
}
main([args]) {
if (args?.length == 2 && args[0] == '--train') {
// This entry point is used when creating an app snapshot. The argument
// provides a script to compile to warm-up generated code.
train(args[1]);
} else {
// Entry point for the Kernel isolate.
return new RawReceivePort()..handler = _processLoadRequest;
}
}
/// Compilation status codes.
///
/// Note: The [index] property of these constants must match
/// `Dart_KernelCompilationStatus` in
/// [dart_api.h](../../../../runtime/include/dart_api.h).
enum Status {
/// Compilation was successful.
ok,
/// Compilation failed with a compile time error.
error,
/// Compiler crashed.
crash,
}
abstract class CompilationResult {
CompilationResult._();
factory CompilationResult.ok(Uint8List bytes) = _CompilationOk;
factory CompilationResult.errors(List<String> errors) = _CompilationError;
factory CompilationResult.crash(Object exception, StackTrace stack) =
_CompilationCrash;
Status get status;
get payload;
List toResponse() => [status.index, payload];
}
class _CompilationOk extends CompilationResult {
final Uint8List bytes;
_CompilationOk(this.bytes) : super._();
@override
Status get status => Status.ok;
@override
get payload => bytes;
String toString() => "_CompilationOk(${bytes.length} bytes)";
}
abstract class _CompilationFail extends CompilationResult {
_CompilationFail() : super._();
String get errorString;
@override
get payload => errorString;
}
class _CompilationError extends _CompilationFail {
final List<String> errors;
_CompilationError(this.errors);
@override
Status get status => Status.error;
@override
String get errorString => errors.take(10).join('\n');
String toString() => "_CompilationError(${errorString})";
}
class _CompilationCrash extends _CompilationFail {
final Object exception;
final StackTrace stack;
_CompilationCrash(this.exception, this.stack);
@override
Status get status => Status.crash;
@override
String get errorString => "${exception}\n${stack}";
String toString() => "_CompilationCrash(${errorString})";
}