05c2f3b0b1
When dynamic modules are enabled, standalone VM can now run bytecode binaries directly. Also, if --interpreter flag is specified, Dart source is compiled to bytecode and interpreter is used to run it. This will allow us to test VM service capabilities including debugging and hot reload against the interpreter. TEST=manual Change-Id: Ibb5a67f4844485c4ed90b8a7568dc42fa552fcac Cq-Include-Trybots: luci.dart.try:vm-aot-dyn-linux-debug-x64-try,vm-aot-dyn-linux-product-x64-try,vm-dyn-linux-debug-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/436421 Reviewed-by: Ryan Macnak <rmacnak@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
1430 lines
44 KiB
Dart
1430 lines
44 KiB
Dart
// 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=pkg/vm/bin/kernel_service.dart ...
|
|
///
|
|
/// invocation or it is invoked as a standalone script to perform training for
|
|
/// the app-jit snapshot
|
|
///
|
|
/// dart pkg/vm/bin/kernel_service.dart --train <source-file>
|
|
///
|
|
///
|
|
library;
|
|
|
|
import 'dart:async' show Future, ZoneSpecification, runZoned;
|
|
import 'dart:collection' show UnmodifiableMapBase;
|
|
import 'dart:convert' show utf8;
|
|
import 'dart:io' show Directory, File, Platform, stderr, stdout;
|
|
import 'dart:isolate';
|
|
import 'dart:typed_data' show Uint8List;
|
|
|
|
import 'package:build_integration/file_system/multi_root.dart';
|
|
import 'package:front_end/src/api_prototype/front_end.dart'
|
|
as fe
|
|
show CompilerResult;
|
|
import 'package:front_end/src/api_prototype/memory_file_system.dart';
|
|
import 'package:front_end/src/api_unstable/vm.dart';
|
|
import 'package:kernel/ast.dart' show Component, Library, Procedure;
|
|
import 'package:kernel/binary/ast_from_binary.dart'
|
|
show BinaryBuilderWithMetadata;
|
|
import 'package:kernel/binary/ast_to_binary.dart';
|
|
import 'package:kernel/class_hierarchy.dart' show ClassHierarchy;
|
|
import 'package:kernel/core_types.dart' show CoreTypes;
|
|
import 'package:kernel/target/targets.dart' show Target, TargetFlags;
|
|
import 'package:vm/http_filesystem.dart';
|
|
import 'package:vm/incremental_compiler.dart';
|
|
import 'package:vm/kernel_front_end.dart'
|
|
show createLoadedLibrariesSet, ErrorDetector;
|
|
import 'package:vm/modular/target/vm.dart' show VmTarget;
|
|
import 'package:vm/native_assets/diagnostic_message.dart';
|
|
import 'package:vm/native_assets/synthesizer.dart';
|
|
|
|
final bool verbose = new bool.fromEnvironment('DFE_VERBOSE');
|
|
final bool dumpKernel = new bool.fromEnvironment('DFE_DUMP_KERNEL');
|
|
const String platformKernelFile = 'virtual_platform_kernel.dill';
|
|
const String packageConfigFile = '.dart_tool/package_config.json';
|
|
|
|
// NOTE: Any changes to these tags need to be reflected in kernel_isolate.cc
|
|
// Tags used to indicate different requests to the dart frontend.
|
|
//
|
|
// Current tags include the following:
|
|
// 0 - Perform normal compilation.
|
|
// 1 - Update in-memory file system with in-memory sources (used by tests).
|
|
// 2 - Accept last compilation result.
|
|
// 3 - APP JIT snapshot training run for kernel_service.
|
|
// 4 - Compile an individual expression in some context (for debugging
|
|
// purposes).
|
|
// 5 - List program dependencies (for creating depfiles)
|
|
// 6 - Isolate shutdown that potentially should result in compiler cleanup.
|
|
// 7 - Reject last compilation result.
|
|
const int kCompileTag = 0;
|
|
const int kUpdateSourcesTag = 1;
|
|
const int kAcceptTag = 2;
|
|
const int kTrainTag = 3;
|
|
const int kCompileExpressionTag = 4;
|
|
const int kListDependenciesTag = 5;
|
|
const int kNotifyIsolateShutdownTag = 6;
|
|
const int kRejectTag = 7;
|
|
|
|
bool allowDartInternalImport = false;
|
|
|
|
// Bytecode generator, optionally injected in
|
|
// pkg/dart2bytecode/bin/kernel_service.dart.
|
|
Uint8List Function(
|
|
Component component,
|
|
List<Library> libraries,
|
|
CoreTypes coreTypes,
|
|
ClassHierarchy hierarchy,
|
|
Target target,
|
|
bool enableAsserts,
|
|
)?
|
|
bytecodeGenerator;
|
|
|
|
CompilerOptions setupCompilerOptions(
|
|
FileSystem fileSystem,
|
|
Uri? platformKernelPath,
|
|
bool enableAsserts,
|
|
bool embedSources,
|
|
List<String>? experimentalFlags,
|
|
Uri? packagesUri,
|
|
List<String> errorsPlain,
|
|
List<String> errorsColorized,
|
|
String invocationModes,
|
|
String verbosityLevel,
|
|
bool enableMirrors,
|
|
) {
|
|
final expFlags = <String>[];
|
|
if (experimentalFlags != null) {
|
|
for (String flag in experimentalFlags) {
|
|
expFlags.addAll(flag.split(","));
|
|
}
|
|
}
|
|
|
|
Verbosity verbosity = Verbosity.parseArgument(verbosityLevel);
|
|
Target target = new VmTarget(new TargetFlags(supportMirrors: enableMirrors));
|
|
return new CompilerOptions()
|
|
..fileSystem = fileSystem
|
|
..target = target
|
|
..packagesFileUri = packagesUri
|
|
..sdkSummary = platformKernelPath
|
|
..embedSourceText = embedSources
|
|
..verbose = verbose
|
|
..omitPlatform =
|
|
false // so that compilation results can be rejected,
|
|
// which potentially is only relevant for
|
|
// incremental, rather than single-shot compilter
|
|
..explicitExperimentalFlags = parseExperimentalFlags(
|
|
parseExperimentalArguments(expFlags),
|
|
onError: (msg) {
|
|
errorsPlain.add(msg);
|
|
errorsColorized.add(msg);
|
|
},
|
|
)
|
|
..environmentDefines = new EnvironmentMap()
|
|
..onDiagnostic = (DiagnosticMessage message) {
|
|
bool printToStdErr = false;
|
|
bool printToStdOut = false;
|
|
switch (message.severity) {
|
|
case Severity.error:
|
|
case Severity.internalProblem:
|
|
// TODO(sigmund): support emitting code with errors as long as they
|
|
// are handled in the generated code.
|
|
printToStdErr = false; // errors are printed by VM
|
|
errorsPlain.addAll(message.plainTextFormatted);
|
|
errorsColorized.addAll(message.ansiFormatted);
|
|
break;
|
|
case Severity.warning:
|
|
printToStdErr = true;
|
|
break;
|
|
case Severity.info:
|
|
printToStdErr = true;
|
|
break;
|
|
case Severity.context:
|
|
case Severity.ignored:
|
|
throw "Unexpected severity: ${message.severity}";
|
|
}
|
|
if (Verbosity.shouldPrint(verbosity, message)) {
|
|
if (printToStdErr) {
|
|
printDiagnosticMessage(message, stderr.writeln);
|
|
}
|
|
// ignore: dead_code
|
|
else if (printToStdOut) {
|
|
printDiagnosticMessage(message, stdout.writeln);
|
|
}
|
|
}
|
|
}
|
|
..invocationModes = InvocationMode.parseArguments(invocationModes)
|
|
..verbosity = verbosity;
|
|
}
|
|
|
|
abstract class Compiler {
|
|
final int isolateGroupId;
|
|
final FileSystem fileSystem;
|
|
final Uri? platformKernelPath;
|
|
final bool enableAsserts;
|
|
final bool embedSources;
|
|
final List<String>? experimentalFlags;
|
|
final String? packageConfig;
|
|
final String invocationModes;
|
|
final String verbosityLevel;
|
|
final bool enableMirrors;
|
|
final bool generateBytecode;
|
|
|
|
// Code coverage and hot reload are only supported by incremental compiler,
|
|
// which is used if vm-service is enabled.
|
|
final bool supportCodeCoverage;
|
|
final bool supportHotReload;
|
|
|
|
final List<String> errorsPlain = <String>[];
|
|
final List<String> errorsColorized = <String>[];
|
|
|
|
late final CompilerOptions options;
|
|
|
|
Compiler(
|
|
this.isolateGroupId,
|
|
this.fileSystem,
|
|
this.platformKernelPath, {
|
|
this.enableAsserts = false,
|
|
this.embedSources = true,
|
|
this.experimentalFlags = null,
|
|
this.supportCodeCoverage = false,
|
|
this.supportHotReload = false,
|
|
this.packageConfig = null,
|
|
this.invocationModes = '',
|
|
this.verbosityLevel = Verbosity.defaultValue,
|
|
required this.enableMirrors,
|
|
required this.generateBytecode,
|
|
}) {
|
|
Uri? packagesUri = null;
|
|
final packageConfig = this.packageConfig ?? Platform.packageConfig;
|
|
if (packageConfig != null) {
|
|
packagesUri = resolveInputUri(packageConfig);
|
|
}
|
|
|
|
if (verbose) {
|
|
print("DFE: Platform.packageConfig: ${Platform.packageConfig}");
|
|
print("DFE: packagesUri: ${packagesUri}");
|
|
print("DFE: Platform.resolvedExecutable: ${Platform.resolvedExecutable}");
|
|
print("DFE: platformKernelPath: ${platformKernelPath}");
|
|
}
|
|
|
|
options = setupCompilerOptions(
|
|
fileSystem,
|
|
platformKernelPath,
|
|
enableAsserts,
|
|
embedSources,
|
|
experimentalFlags,
|
|
packagesUri,
|
|
errorsPlain,
|
|
errorsColorized,
|
|
invocationModes,
|
|
verbosityLevel,
|
|
enableMirrors,
|
|
);
|
|
}
|
|
|
|
Future<CompilerResult> compile(Uri script) {
|
|
return runWithPrintToStderr(() async {
|
|
final CompilerResult compilerResult = await compileInternal(script);
|
|
final Component? component = compilerResult.component;
|
|
|
|
if (errorsPlain.isEmpty) {
|
|
// Record dependencies only if compilation was error free.
|
|
_recordDependencies(isolateGroupId, component, options.packagesFileUri);
|
|
}
|
|
|
|
return compilerResult;
|
|
});
|
|
}
|
|
|
|
Future<CompilerResult> compileInternal(Uri script);
|
|
}
|
|
|
|
class CompilerResult {
|
|
final Component? component;
|
|
|
|
/// Set of libraries loaded from .dill, with or without the SDK depending on
|
|
/// the compilation settings.
|
|
final Set<Library> loadedLibraries;
|
|
final ClassHierarchy? classHierarchy;
|
|
final CoreTypes? coreTypes;
|
|
|
|
CompilerResult(
|
|
this.component,
|
|
this.loadedLibraries,
|
|
this.classHierarchy,
|
|
this.coreTypes,
|
|
);
|
|
}
|
|
|
|
// Environment map which looks up environment defines in the VM environment
|
|
// at runtime.
|
|
// TODO(askesc): This is a temporary hack to get hold of the environment during
|
|
// JIT compilation. We use a lazy map accessing the VM runtime environment using
|
|
// new String.fromEnvironment, since the VM currently does not support providing
|
|
// the full (isolate specific) environment as a finite, static map.
|
|
class EnvironmentMap extends UnmodifiableMapBase<String, String> {
|
|
@override
|
|
bool containsKey(Object? key) {
|
|
return key is String && new bool.hasEnvironment(key);
|
|
}
|
|
|
|
@override
|
|
String? operator [](Object? key) {
|
|
// The fromEnvironment constructor is specified to throw when called using
|
|
// new. However, the VM implementation actually looks up the given name in
|
|
// the environment.
|
|
if (containsKey(key)) {
|
|
return new String.fromEnvironment(key as String);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
get keys => throw "Environment map iteration not supported";
|
|
}
|
|
|
|
class FileSink implements Sink<List<int>> {
|
|
MemoryFileSystemEntity entityForUri;
|
|
List<int> bytes = <int>[];
|
|
|
|
FileSink(this.entityForUri);
|
|
|
|
@override
|
|
void add(List<int> data) {
|
|
bytes.addAll(data);
|
|
}
|
|
|
|
@override
|
|
void close() {
|
|
this.entityForUri.writeAsBytesSync(bytes);
|
|
}
|
|
}
|
|
|
|
class IncrementalCompilerWrapper extends Compiler {
|
|
IncrementalCompiler? generator;
|
|
|
|
IncrementalCompilerWrapper(
|
|
int isolateGroupId,
|
|
FileSystem fileSystem,
|
|
Uri? platformKernelPath, {
|
|
bool enableAsserts = false,
|
|
List<String>? experimentalFlags,
|
|
String? packageConfig,
|
|
String invocationModes = '',
|
|
String verbosityLevel = Verbosity.defaultValue,
|
|
required bool enableMirrors,
|
|
required super.generateBytecode,
|
|
}) : super(
|
|
isolateGroupId,
|
|
fileSystem,
|
|
platformKernelPath,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
supportHotReload: true,
|
|
supportCodeCoverage: true,
|
|
packageConfig: packageConfig,
|
|
invocationModes: invocationModes,
|
|
verbosityLevel: verbosityLevel,
|
|
enableMirrors: enableMirrors,
|
|
);
|
|
|
|
factory IncrementalCompilerWrapper.forExpressionCompilationOnly(
|
|
Component component,
|
|
int isolateGroupId,
|
|
FileSystem fileSystem,
|
|
Uri? platformKernelPath, {
|
|
bool enableAsserts = false,
|
|
List<String>? experimentalFlags,
|
|
String? packageConfig,
|
|
String invocationModes = '',
|
|
required bool enableMirrors,
|
|
required bool generateBytecode,
|
|
}) {
|
|
IncrementalCompilerWrapper result = IncrementalCompilerWrapper(
|
|
isolateGroupId,
|
|
fileSystem,
|
|
platformKernelPath,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfig,
|
|
invocationModes: invocationModes,
|
|
enableMirrors: enableMirrors,
|
|
generateBytecode: generateBytecode,
|
|
);
|
|
result.generator = new IncrementalCompiler.forExpressionCompilationOnly(
|
|
component,
|
|
result.options,
|
|
[component.mainMethod!.enclosingLibrary.fileUri],
|
|
);
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
Future<CompilerResult> compileInternal(Uri script) async {
|
|
final generator = this.generator ??= IncrementalCompiler(options, [script]);
|
|
errorsPlain.clear();
|
|
errorsColorized.clear();
|
|
final compilerResult = await generator.compile(entryPoints: [script]);
|
|
final component = compilerResult.component;
|
|
return new CompilerResult(
|
|
component,
|
|
const {},
|
|
compilerResult.classHierarchy,
|
|
compilerResult.coreTypes,
|
|
);
|
|
}
|
|
|
|
void accept() => generator!.accept();
|
|
Future<void> reject() async => generator!.reject();
|
|
void invalidate(Uri uri) => generator!.invalidate(uri);
|
|
|
|
Future<IncrementalCompilerWrapper> clone(int isolateGroupId) async {
|
|
IncrementalCompilerWrapper clone = IncrementalCompilerWrapper(
|
|
isolateGroupId,
|
|
fileSystem,
|
|
platformKernelPath,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfig,
|
|
invocationModes: invocationModes,
|
|
enableMirrors: enableMirrors,
|
|
generateBytecode: generateBytecode,
|
|
);
|
|
final generator = this.generator!;
|
|
// TODO(VM TEAM): This does not seem safe. What if cloning while having
|
|
// pending deltas for instance?
|
|
generator.resetDeltaState();
|
|
IncrementalCompilerResult compilerResult = await generator.compile();
|
|
Component fullComponent = compilerResult.component;
|
|
|
|
// Assume fileSystem is HybridFileSystem because that is the setup where
|
|
// clone should be used for.
|
|
MemoryFileSystem memoryFileSystem = (fileSystem as HybridFileSystem).memory;
|
|
|
|
String filename = 'full-component-$isolateGroupId.dill';
|
|
Sink<List<int>> sink = FileSink(
|
|
memoryFileSystem.entityForUri(Uri.file(filename)),
|
|
);
|
|
new BinaryPrinter(sink).writeComponentFile(fullComponent);
|
|
sink.close();
|
|
|
|
clone.generator = new IncrementalCompiler(
|
|
options,
|
|
generator.entryPoints,
|
|
initializeFromDillUri: Uri.file(filename),
|
|
);
|
|
return clone;
|
|
}
|
|
}
|
|
|
|
class SingleShotCompilerWrapper extends Compiler {
|
|
final bool requireMain;
|
|
|
|
SingleShotCompilerWrapper(
|
|
int isolateGroupId,
|
|
FileSystem fileSystem,
|
|
Uri platformKernelPath, {
|
|
this.requireMain = false,
|
|
bool enableAsserts = false,
|
|
bool embedSources = true,
|
|
List<String>? experimentalFlags,
|
|
String? packageConfig,
|
|
String invocationModes = '',
|
|
String verbosityLevel = Verbosity.defaultValue,
|
|
required bool enableMirrors,
|
|
required super.generateBytecode,
|
|
}) : super(
|
|
isolateGroupId,
|
|
fileSystem,
|
|
platformKernelPath,
|
|
enableAsserts: enableAsserts,
|
|
embedSources: embedSources,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfig,
|
|
invocationModes: invocationModes,
|
|
verbosityLevel: verbosityLevel,
|
|
enableMirrors: enableMirrors,
|
|
);
|
|
|
|
@override
|
|
Future<CompilerResult> compileInternal(Uri script) async {
|
|
final fe.CompilerResult? compilerResult =
|
|
requireMain
|
|
? await kernelForProgram(script, options)
|
|
: await kernelForModule([script], options);
|
|
if (compilerResult == null) {
|
|
return CompilerResult(null, const {}, null, null);
|
|
}
|
|
|
|
Set<Library> loadedLibraries = createLoadedLibrariesSet(
|
|
compilerResult.loadedComponents,
|
|
compilerResult.sdkComponent,
|
|
includePlatform: false,
|
|
);
|
|
|
|
return new CompilerResult(
|
|
compilerResult.component,
|
|
loadedLibraries,
|
|
compilerResult.classHierarchy,
|
|
compilerResult.coreTypes,
|
|
);
|
|
}
|
|
}
|
|
|
|
final Map<int, IncrementalCompilerWrapper> isolateCompilers = {};
|
|
final Map<int, List<Uri>> isolateDependencies = {};
|
|
final Map<int, _ExpressionCompilationFromDillSettings> isolateLoadNotifies = {};
|
|
|
|
IncrementalCompilerWrapper? lookupIncrementalCompiler(int isolateGroupId) {
|
|
return isolateCompilers[isolateGroupId];
|
|
}
|
|
|
|
Future<Compiler> lookupOrBuildNewIncrementalCompiler(
|
|
int isolateGroupId,
|
|
List sourceFiles,
|
|
Uri platformKernelPath,
|
|
List<int>? platformKernel, {
|
|
bool enableAsserts = false,
|
|
List<String>? experimentalFlags,
|
|
String? packageConfig,
|
|
String? multirootFilepaths,
|
|
String? multirootScheme,
|
|
String invocationModes = '',
|
|
String verbosityLevel = Verbosity.defaultValue,
|
|
required bool enableMirrors,
|
|
required bool generateBytecode,
|
|
}) async {
|
|
IncrementalCompilerWrapper? compiler = lookupIncrementalCompiler(
|
|
isolateGroupId,
|
|
);
|
|
if (compiler != null) {
|
|
updateSources(compiler, sourceFiles);
|
|
invalidateSources(compiler, sourceFiles);
|
|
} else {
|
|
// This is how identify scenario where child isolate hot reload requests
|
|
// requires setting up actual compiler first: non-empty sourceFiles list has
|
|
// no actual content specified for the source file.
|
|
if (sourceFiles.isNotEmpty && sourceFiles[1] == null) {
|
|
// Just use first compiler that should represent main isolate as a source for cloning.
|
|
var source = isolateCompilers.entries.first;
|
|
compiler = await source.value.clone(isolateGroupId);
|
|
} else {
|
|
FileSystem fileSystem = _buildFileSystem(
|
|
sourceFiles,
|
|
platformKernel,
|
|
multirootFilepaths,
|
|
multirootScheme,
|
|
);
|
|
|
|
// TODO(aam): IncrementalCompilerWrapper instance created below have to be
|
|
// destroyed when corresponding isolate is shut down. To achieve that kernel
|
|
// isolate needs to receive a message indicating that particular
|
|
// isolate was shut down. Message should be handled here in this script.
|
|
compiler = new IncrementalCompilerWrapper(
|
|
isolateGroupId,
|
|
fileSystem,
|
|
platformKernelPath,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfig,
|
|
invocationModes: invocationModes,
|
|
verbosityLevel: verbosityLevel,
|
|
enableMirrors: enableMirrors,
|
|
generateBytecode: generateBytecode,
|
|
);
|
|
}
|
|
isolateCompilers[isolateGroupId] = compiler;
|
|
}
|
|
return compiler;
|
|
}
|
|
|
|
void updateSources(IncrementalCompilerWrapper compiler, List sourceFiles) {
|
|
final bool hasMemoryFS = compiler.fileSystem is HybridFileSystem;
|
|
if (sourceFiles.isNotEmpty) {
|
|
final FileSystem fs = compiler.fileSystem;
|
|
for (int i = 0; i < sourceFiles.length ~/ 2; i++) {
|
|
Uri uri = Uri.parse(sourceFiles[i * 2]);
|
|
List<int>? source = sourceFiles[i * 2 + 1];
|
|
// The source is only provided by unit tests and is normally empty.
|
|
// Don't add an entry for the uri so the compiler will fallback to the
|
|
// real file system for the updated source.
|
|
if (hasMemoryFS && source != null) {
|
|
(fs as HybridFileSystem).memory
|
|
.entityForUri(uri)
|
|
.writeAsBytesSync(source);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void invalidateSources(IncrementalCompilerWrapper compiler, List sourceFiles) {
|
|
if (sourceFiles.isNotEmpty) {
|
|
for (int i = 0; i < sourceFiles.length ~/ 2; i++) {
|
|
compiler.invalidate(Uri.parse(sourceFiles[i * 2]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process a request from the runtime. See KernelIsolate::CompileToKernel in
|
|
// kernel_isolate.cc and Loader::SendKernelRequest in loader.cc.
|
|
Future _processExpressionCompilationRequest(request) async {
|
|
final SendPort port = request[1];
|
|
final int isolateGroupId = request[2];
|
|
final dynamic dartPlatformKernel = request[3];
|
|
final String expression = request[4];
|
|
final List<String> definitions = request[5].cast<String>();
|
|
final List<String> definitionTypes = request[6].cast<String>();
|
|
final List<String> typeDefinitions = request[7].cast<String>();
|
|
final List<String> typeBounds = request[8].cast<String>();
|
|
final List<String> typeDefaults = request[9].cast<String>();
|
|
final String libraryUri = request[10];
|
|
final String? klass = request[11];
|
|
final String? method = request[12];
|
|
final bool isStatic = request[13];
|
|
final int offset = request[14];
|
|
final String? scriptUri = request[15];
|
|
final List<Uint8List> dillData = request[16].cast<Uint8List>();
|
|
final int blobLoadCount = request[17];
|
|
final bool enableAsserts = request[18];
|
|
final List<String>? experimentalFlags =
|
|
request[19] != null ? request[19].cast<String>() : null;
|
|
final bool enableMirrors = request[20];
|
|
final bool generateBytecode = request[21];
|
|
|
|
IncrementalCompilerWrapper? compiler = isolateCompilers[isolateGroupId];
|
|
|
|
_ExpressionCompilationFromDillSettings? isolateLoadDillData =
|
|
isolateLoadNotifies[isolateGroupId];
|
|
if (isolateLoadDillData != null) {
|
|
// Check if we can reuse the compiler.
|
|
if (isolateLoadDillData.blobLoadCount != blobLoadCount ||
|
|
isolateLoadDillData.prevDillCount != dillData.length) {
|
|
isolateCompilers.remove(isolateGroupId);
|
|
compiler = null;
|
|
}
|
|
}
|
|
|
|
if (compiler == null) {
|
|
if (dillData.isNotEmpty) {
|
|
if (verbose) {
|
|
print("DFE: Initializing compiler from ${dillData.length} dill files");
|
|
}
|
|
isolateLoadNotifies[isolateGroupId] =
|
|
new _ExpressionCompilationFromDillSettings(
|
|
blobLoadCount,
|
|
dillData.length,
|
|
);
|
|
|
|
// Create Component initialized from the bytes.
|
|
Component component = new Component();
|
|
|
|
// First try to just load all "dillData". This *might* include the
|
|
// platform (and we might have the (same) platform both here and in
|
|
// dart_platform_kernel).
|
|
for (Uint8List bytes in dillData) {
|
|
// TODO(jensj): There might be an issue if main has changed.
|
|
new BinaryBuilderWithMetadata(
|
|
bytes,
|
|
alwaysCreateNewNamedNodes: true,
|
|
).readComponent(component);
|
|
}
|
|
|
|
// Check if the loaded component has the platform.
|
|
// If it does not, try to load from dart_platform_kernel or from file.
|
|
bool foundDartCore = false;
|
|
for (Library library in component.libraries) {
|
|
if (library.importUri.isScheme("dart") &&
|
|
library.importUri.path == "core" &&
|
|
!library.isSynthetic) {
|
|
foundDartCore = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundDartCore) {
|
|
Uint8List platformKernel;
|
|
if (dartPlatformKernel is Uint8List) {
|
|
platformKernel = dartPlatformKernel;
|
|
} else {
|
|
final Uri platformUri = computePlatformBinariesLocation().resolve(
|
|
'vm_platform.dill',
|
|
);
|
|
final File platformFile = new File.fromUri(platformUri);
|
|
if (platformFile.existsSync()) {
|
|
platformKernel = platformFile.readAsBytesSync();
|
|
} else {
|
|
port.send(
|
|
new CompilationResult.errors([
|
|
"No platform found to initialize incremental compiler.",
|
|
]).toResponse(),
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
new BinaryBuilderWithMetadata(
|
|
platformKernel,
|
|
alwaysCreateNewNamedNodes: true,
|
|
).readComponent(component);
|
|
}
|
|
|
|
FileSystem fileSystem = _buildFileSystem(
|
|
[packageConfigFile, <int>[]],
|
|
null,
|
|
null,
|
|
null,
|
|
);
|
|
|
|
// TODO(aam): IncrementalCompilerWrapper instance created below have to be
|
|
// destroyed when corresponding isolate is shut down. To achieve that
|
|
// kernel isolate needs to receive a message indicating that particular
|
|
// isolate was shut down. Message should be handled here in this script.
|
|
try {
|
|
compiler = new IncrementalCompilerWrapper.forExpressionCompilationOnly(
|
|
component,
|
|
isolateGroupId,
|
|
fileSystem,
|
|
null,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfigFile,
|
|
enableMirrors: enableMirrors,
|
|
generateBytecode: generateBytecode,
|
|
);
|
|
isolateCompilers[isolateGroupId] = compiler;
|
|
await compiler.compile(
|
|
component.mainMethod?.enclosingLibrary.importUri ??
|
|
component.libraries.last.importUri,
|
|
);
|
|
} catch (e) {
|
|
port.send(
|
|
new CompilationResult.errors([
|
|
"Error when trying to create a compiler for expression compilation: "
|
|
"'$e'.",
|
|
]).toResponse(),
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (compiler == null) {
|
|
port.send(
|
|
new CompilationResult.errors([
|
|
"No incremental compiler available for this isolate.",
|
|
]).toResponse(),
|
|
);
|
|
return;
|
|
}
|
|
|
|
compiler.errorsPlain.clear();
|
|
compiler.errorsColorized.clear();
|
|
|
|
CompilationResult result;
|
|
try {
|
|
Procedure? procedure = await compiler.generator!.compileExpression(
|
|
expression,
|
|
definitions,
|
|
definitionTypes,
|
|
typeDefinitions,
|
|
typeBounds,
|
|
typeDefaults,
|
|
libraryUri,
|
|
klass,
|
|
method,
|
|
offset,
|
|
scriptUri,
|
|
isStatic,
|
|
);
|
|
|
|
if (procedure == null) {
|
|
port.send(new CompilationResult.errors(["Invalid scope."]).toResponse());
|
|
return;
|
|
}
|
|
|
|
assert(compiler.errorsPlain.length == compiler.errorsColorized.length);
|
|
// Any error will be printed verbatim in observatory, so we always use the
|
|
// plain version (i.e. the one without ANSI escape codes in it).
|
|
if (compiler.errorsPlain.isNotEmpty) {
|
|
// TODO(sigmund): the compiler prints errors to the console, so we
|
|
// shouldn't print those messages again here.
|
|
result = new CompilationResult.errors(compiler.errorsPlain);
|
|
} else {
|
|
Component component = createExpressionEvaluationComponent(procedure);
|
|
Uint8List bytes;
|
|
if (compiler.generateBytecode) {
|
|
bytes = bytecodeGenerator!.call(
|
|
component,
|
|
component.libraries,
|
|
compiler.generator!.lastKnownGoodResult!.coreTypes,
|
|
compiler.generator!.lastKnownGoodResult!.classHierarchy,
|
|
compiler.options.target!,
|
|
compiler.enableAsserts,
|
|
);
|
|
} else {
|
|
bytes = serializeComponent(component);
|
|
}
|
|
result = new CompilationResult.ok(bytes);
|
|
}
|
|
} catch (error, stack) {
|
|
result = new CompilationResult.crash(error, stack);
|
|
}
|
|
|
|
port.send(result.toResponse());
|
|
}
|
|
|
|
void _recordDependencies(
|
|
int isolateGroupId,
|
|
Component? component,
|
|
Uri? packageConfig,
|
|
) {
|
|
final dependencies = isolateDependencies[isolateGroupId] ??= <Uri>[];
|
|
|
|
if (component != null) {
|
|
for (var lib in component.libraries) {
|
|
if (lib.importUri.isScheme("dart")) continue;
|
|
|
|
dependencies.add(lib.fileUri);
|
|
for (var part in lib.parts) {
|
|
final fileUri = lib.fileUri.resolve(part.partUri);
|
|
if (fileUri.hasScheme && !fileUri.isScheme("file")) {
|
|
// E.g. part 'package:foo/foo.dart';
|
|
// Maybe the front end should resolve this?
|
|
continue;
|
|
}
|
|
dependencies.add(fileUri);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (packageConfig != null) {
|
|
dependencies.add(packageConfig);
|
|
}
|
|
}
|
|
|
|
String _escapeDependency(Uri uri) {
|
|
return uri.toFilePath().replaceAll("\\", "\\\\").replaceAll(" ", "\\ ");
|
|
}
|
|
|
|
Uint8List _serializeDependencies(List<Uri> uris) {
|
|
return utf8.encode(uris.map(_escapeDependency).join(" "));
|
|
}
|
|
|
|
Future _processListDependenciesRequest(
|
|
SendPort port,
|
|
int isolateGroupId,
|
|
) async {
|
|
final List<Uri> dependencies = isolateDependencies[isolateGroupId] ?? <Uri>[];
|
|
|
|
CompilationResult result;
|
|
try {
|
|
result = new CompilationResult.ok(_serializeDependencies(dependencies));
|
|
} catch (error, stack) {
|
|
result = new CompilationResult.crash(error, stack);
|
|
}
|
|
|
|
port.send(result.toResponse());
|
|
}
|
|
|
|
Future _processIsolateShutdownNotification(request) async {
|
|
final int isolateGroupId = request[1];
|
|
isolateCompilers.remove(isolateGroupId);
|
|
isolateDependencies.remove(isolateGroupId);
|
|
isolateLoadNotifies.remove(isolateGroupId);
|
|
}
|
|
|
|
Future _processLoadRequest(request) async {
|
|
if (verbose) {
|
|
for (int i = 0; i < request.length; i++) {
|
|
var part = request[i];
|
|
String partToString;
|
|
if (part is List && part.isNotEmpty) {
|
|
// Assume this is large and printing all of it takes a lot of time.
|
|
StringBuffer sb = new StringBuffer();
|
|
String prepend = "[";
|
|
for (int j = 0; j < part.length; j++) {
|
|
sb.write(prepend);
|
|
sb.write(part[j]);
|
|
prepend = ", ";
|
|
if (sb.length > 256) break;
|
|
}
|
|
sb.write("]");
|
|
partToString = sb.toString();
|
|
} else {
|
|
partToString = part.toString();
|
|
}
|
|
if (partToString.length > 256) {
|
|
partToString = partToString.substring(0, 255) + "...";
|
|
}
|
|
print("DFE: request[$i]: $partToString");
|
|
}
|
|
}
|
|
|
|
int tag = request[0];
|
|
|
|
if (tag == kCompileExpressionTag) {
|
|
await _processExpressionCompilationRequest(request);
|
|
return;
|
|
}
|
|
|
|
if (tag == kNotifyIsolateShutdownTag) {
|
|
await _processIsolateShutdownNotification(request);
|
|
return;
|
|
}
|
|
|
|
final SendPort port = request[1];
|
|
final int isolateGroupId = request[7];
|
|
if (tag == kListDependenciesTag) {
|
|
await _processListDependenciesRequest(port, isolateGroupId);
|
|
return;
|
|
}
|
|
|
|
final String? inputFileUri = request[2];
|
|
final Uri? script =
|
|
inputFileUri != null ? Uri.base.resolve(inputFileUri) : null;
|
|
final bool incremental = request[4];
|
|
final bool forSnapshot = request[5];
|
|
final bool embedSources = request[6];
|
|
final List sourceFiles = request[8];
|
|
final bool enableAsserts = request[9];
|
|
final List<String>? experimentalFlags =
|
|
request[10] != null ? request[10].cast<String>() : null;
|
|
final String? packageConfig = request[11];
|
|
final String? multirootFilepaths = request[12];
|
|
final String? multirootScheme = request[13];
|
|
final String verbosityLevel = request[14];
|
|
final bool enableMirrors = request[15];
|
|
final bool generateBytecode = request[16];
|
|
Uri platformKernelPath;
|
|
List<int>? platformKernel = null;
|
|
if (request[3] is String) {
|
|
platformKernelPath = Uri.base.resolveUri(new Uri.file(request[3]));
|
|
} else if (request[3] is List<int>) {
|
|
platformKernelPath = Uri.parse(platformKernelFile);
|
|
platformKernel = request[3];
|
|
} else {
|
|
platformKernelPath = computePlatformBinariesLocation().resolve(
|
|
'vm_platform.dill',
|
|
);
|
|
}
|
|
|
|
final String invocationModes = forSnapshot ? 'compile' : '';
|
|
|
|
Compiler? compiler;
|
|
|
|
// Update the in-memory file system with the provided sources. Currently, only
|
|
// unit tests compile sources that are not on the file system, so this can only
|
|
// happen during unit tests.
|
|
if (tag == kUpdateSourcesTag) {
|
|
assert(
|
|
incremental,
|
|
"Incremental compiler required for use of 'kUpdateSourcesTag'",
|
|
);
|
|
compiler = lookupIncrementalCompiler(isolateGroupId);
|
|
if (compiler == null) {
|
|
port.send(
|
|
new CompilationResult.errors([
|
|
"No incremental compiler available for this isolate.",
|
|
]).toResponse(),
|
|
);
|
|
return;
|
|
}
|
|
updateSources(compiler as IncrementalCompilerWrapper, sourceFiles);
|
|
port.send(new CompilationResult.ok(null).toResponse());
|
|
return;
|
|
} else if (tag == kAcceptTag || tag == kRejectTag) {
|
|
assert(
|
|
incremental,
|
|
"Incremental compiler required for use of 'kAcceptTag' or "
|
|
"'kRejectTag",
|
|
);
|
|
compiler = lookupIncrementalCompiler(isolateGroupId);
|
|
// There are unit tests that invoke the IncrementalCompiler directly and
|
|
// request a reload, meaning that we won't have a compiler for this isolate.
|
|
if (compiler != null) {
|
|
final wrapper = compiler as IncrementalCompilerWrapper;
|
|
try {
|
|
if (tag == kAcceptTag) {
|
|
wrapper.accept();
|
|
} else {
|
|
await wrapper.reject();
|
|
}
|
|
} catch (e, st) {
|
|
port.send(CompilationResult.crash(e, st).toResponse());
|
|
return;
|
|
}
|
|
}
|
|
port.send(new CompilationResult.ok(null).toResponse());
|
|
return;
|
|
}
|
|
|
|
// script should only be null for kUpdateSourcesTag.
|
|
assert(script != null);
|
|
|
|
// TODO(aam): There should be no need to have an option to choose
|
|
// one compiler or another. We should always use an incremental
|
|
// compiler as its functionality is a super set of the other one. We need to
|
|
// watch the performance though.
|
|
FileSystem fileSystem;
|
|
if (incremental) {
|
|
compiler = await lookupOrBuildNewIncrementalCompiler(
|
|
isolateGroupId,
|
|
sourceFiles,
|
|
platformKernelPath,
|
|
platformKernel,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfig,
|
|
multirootFilepaths: multirootFilepaths,
|
|
multirootScheme: multirootScheme,
|
|
invocationModes: invocationModes,
|
|
verbosityLevel: verbosityLevel,
|
|
enableMirrors: enableMirrors,
|
|
generateBytecode: generateBytecode,
|
|
);
|
|
fileSystem = compiler.fileSystem;
|
|
} else {
|
|
fileSystem = _buildFileSystem(
|
|
sourceFiles,
|
|
platformKernel,
|
|
multirootFilepaths,
|
|
multirootScheme,
|
|
);
|
|
compiler = new SingleShotCompilerWrapper(
|
|
isolateGroupId,
|
|
fileSystem,
|
|
platformKernelPath,
|
|
requireMain: false,
|
|
embedSources: embedSources,
|
|
enableAsserts: enableAsserts,
|
|
experimentalFlags: experimentalFlags,
|
|
packageConfig: packageConfig,
|
|
invocationModes: invocationModes,
|
|
verbosityLevel: verbosityLevel,
|
|
enableMirrors: enableMirrors,
|
|
generateBytecode: generateBytecode,
|
|
);
|
|
}
|
|
|
|
CompilationResult result;
|
|
try {
|
|
if (verbose) {
|
|
print("DFE: scriptUri: ${script}");
|
|
}
|
|
|
|
CompilerResult compilerResult = await compiler.compile(script!);
|
|
Set<Library> loadedLibraries = compilerResult.loadedLibraries;
|
|
|
|
final String? nativeAssets = await findNativeAssets(
|
|
packagesFileUri:
|
|
packageConfig != null ? resolveInputUri(packageConfig) : null,
|
|
script: script,
|
|
fileSystem: fileSystem,
|
|
);
|
|
Component? nativeAssetsComponent;
|
|
final nativeAssetsErrors = <NativeAssetsDiagnosticMessage>[];
|
|
if (nativeAssets != null) {
|
|
final errorDetector = ErrorDetector(
|
|
previousErrorHandler:
|
|
(message) => nativeAssetsErrors.add(
|
|
message as NativeAssetsDiagnosticMessage,
|
|
),
|
|
);
|
|
final nativeAssetsLibrary =
|
|
await NativeAssetsSynthesizer.synthesizeLibraryFromYamlString(
|
|
nativeAssets,
|
|
errorDetector,
|
|
pragmaClass: compilerResult.coreTypes?.pragmaClass,
|
|
);
|
|
if (nativeAssetsLibrary != null) {
|
|
nativeAssetsComponent = Component(libraries: [nativeAssetsLibrary]);
|
|
}
|
|
}
|
|
|
|
assert(compiler.errorsPlain.length == compiler.errorsColorized.length);
|
|
// http://dartbug.com/45137
|
|
// enableColors calls `stdout.supportsAnsiEscapes` which - on Windows -
|
|
// does something with line endings. To avoid this when no error
|
|
// messages are do be printed anyway, we are careful not to call it unless
|
|
// necessary.
|
|
if (compiler.errorsColorized.isNotEmpty || nativeAssetsErrors.isNotEmpty) {
|
|
final List<String> errors = [
|
|
...(enableColors) ? compiler.errorsColorized : compiler.errorsPlain,
|
|
...nativeAssetsErrors.map((e) => e.message),
|
|
];
|
|
result = new CompilationResult.errors(errors);
|
|
} else {
|
|
// We serialize the component excluding vm_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 exclude.
|
|
Uint8List bytes;
|
|
if (compiler.generateBytecode) {
|
|
final generator = bytecodeGenerator;
|
|
if (generator == null) {
|
|
throw 'Cannot generate bytecode as dynamic modules are disabled.';
|
|
}
|
|
bytes = generator(
|
|
compilerResult.component!,
|
|
compilerResult.component!.libraries
|
|
.where((lib) => !loadedLibraries.contains(lib))
|
|
.toList(),
|
|
compilerResult.coreTypes!,
|
|
compilerResult.classHierarchy!,
|
|
compiler.options.target!,
|
|
compiler.enableAsserts,
|
|
);
|
|
} else {
|
|
bytes = serializeComponent(
|
|
compilerResult.component!,
|
|
filter: (lib) => !loadedLibraries.contains(lib),
|
|
nativeAssetsComponent: nativeAssetsComponent,
|
|
);
|
|
}
|
|
result = new CompilationResult.ok(bytes);
|
|
}
|
|
} catch (error, stack) {
|
|
result = new CompilationResult.crash(error, stack);
|
|
}
|
|
|
|
if (verbose) print("DFE:> ${result}");
|
|
|
|
if (tag == kTrainTag) {
|
|
// In training mode make sure to read the sdk a few more times...
|
|
ProcessedOptions p = new ProcessedOptions(options: compiler.options);
|
|
final bytes = (await p.loadSdkSummaryBytes())!;
|
|
for (int i = 0; i < 5; i++) {
|
|
p.loadComponent(bytes, null);
|
|
}
|
|
|
|
if (result.status != Status.ok) {
|
|
tag = -tag;
|
|
}
|
|
port.send([tag, inputFileUri, inputFileUri, null, result.payload]);
|
|
} else if (tag == kCompileTag) {
|
|
port.send(result.toResponse());
|
|
} else {
|
|
port.send([
|
|
-tag,
|
|
inputFileUri,
|
|
inputFileUri,
|
|
null,
|
|
new CompilationResult.errors(<String>["unknown tag"]).payload,
|
|
]);
|
|
}
|
|
}
|
|
|
|
/// Returns the contents of the `native_assets.yaml` for the host os in JIT,
|
|
/// if it exists.
|
|
///
|
|
/// Order or priority:
|
|
/// 1. If a `package_config.json` is picked by the kernel service, look to see
|
|
/// if there is a `native_assets.yaml` next to it.
|
|
/// 2. If no `package_config.json` is picked by the kernel service, walk up
|
|
/// folder hierarchy to find one and look next to it.
|
|
Future<String?> findNativeAssets({
|
|
Uri? packagesFileUri,
|
|
Uri? script,
|
|
required FileSystem fileSystem,
|
|
}) async {
|
|
if (packagesFileUri != null &&
|
|
(packagesFileUri.scheme == '' || packagesFileUri.scheme == 'file')) {
|
|
final nativeAssetsUri = packagesFileUri.resolve('native_assets.yaml');
|
|
final nativeAssetsEntity = fileSystem.entityForUri(nativeAssetsUri);
|
|
if (await nativeAssetsEntity.exists()) {
|
|
return await nativeAssetsEntity.readAsString();
|
|
}
|
|
return null;
|
|
}
|
|
if (script != null && (script.scheme == '' || script.scheme == 'file')) {
|
|
Future<String?> tryLoadNativeAssetsYaml(Uri uri) async {
|
|
final nativeAssetsUri = uri.resolve('.dart_tool/native_assets.yaml');
|
|
final nativeAssetsEntity = fileSystem.entityForUri(nativeAssetsUri);
|
|
if (await nativeAssetsEntity.exists()) {
|
|
return await nativeAssetsEntity.readAsString();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Uri folderUri = script.resolve('.');
|
|
while (true) {
|
|
final found = await tryLoadNativeAssetsYaml(folderUri);
|
|
if (found != null) {
|
|
return found;
|
|
}
|
|
final parentUri = folderUri.resolve('..');
|
|
if (parentUri.path == folderUri.path) {
|
|
return null;
|
|
}
|
|
folderUri = parentUri;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Uint8List serializeComponent(
|
|
Component component, {
|
|
bool Function(Library library)? filter,
|
|
Component? nativeAssetsComponent,
|
|
}) {
|
|
final byteSink = new BytesSink();
|
|
BinaryPrinter printer = new BinaryPrinter(byteSink, libraryFilter: filter);
|
|
printer.writeComponentFile(component);
|
|
if (nativeAssetsComponent != null) {
|
|
BinaryPrinter printer = new BinaryPrinter(byteSink);
|
|
printer.writeComponentFile(nativeAssetsComponent);
|
|
}
|
|
return byteSink.builder.takeBytes();
|
|
}
|
|
|
|
/// Creates a file system containing the files specified in [sourceFiles] and
|
|
/// that delegates to the underlying file system for any other file request.
|
|
/// The [sourceFiles] list interleaves file name string and
|
|
/// raw file content Uint8List.
|
|
///
|
|
/// The result can be used instead of StandardFileSystem.instance by the
|
|
/// frontend.
|
|
FileSystem _buildFileSystem(
|
|
List sourceFiles,
|
|
List<int>? platformKernel,
|
|
String? multirootFilepaths,
|
|
String? multirootScheme,
|
|
) {
|
|
FileSystem fileSystem = new HttpAwareFileSystem(StandardFileSystem.instance);
|
|
|
|
if (sourceFiles.isNotEmpty || platformKernel != null) {
|
|
MemoryFileSystem memoryFileSystem = new MemoryFileSystem(
|
|
Uri.parse('file:///'),
|
|
);
|
|
for (int i = 0; i < sourceFiles.length ~/ 2; i++) {
|
|
memoryFileSystem
|
|
.entityForUri(Uri.parse(sourceFiles[i * 2]))
|
|
.writeAsBytesSync(sourceFiles[i * 2 + 1]);
|
|
}
|
|
if (platformKernel != null) {
|
|
memoryFileSystem
|
|
.entityForUri(Uri.parse(platformKernelFile))
|
|
.writeAsBytesSync(platformKernel);
|
|
}
|
|
fileSystem = new HybridFileSystem(memoryFileSystem, fileSystem);
|
|
}
|
|
|
|
if (multirootFilepaths != null) {
|
|
List<Uri> list =
|
|
multirootFilepaths
|
|
.split(',')
|
|
.map((String s) => Uri.base.resolveUri(new Uri.file(s)))
|
|
.toList();
|
|
fileSystem = new MultiRootFileSystem(
|
|
multirootScheme ?? "org-dartlang-root",
|
|
list,
|
|
fileSystem,
|
|
);
|
|
}
|
|
return fileSystem;
|
|
}
|
|
|
|
train(String scriptUri, String? platformKernelPath) async {
|
|
// Train on program asked to train on.
|
|
await trainInternal(scriptUri, platformKernelPath);
|
|
|
|
// Also train a few times on a hello-world program to make sure we exercise
|
|
// the startup sequence.
|
|
Directory tmpDir = Directory.systemTemp.createTempSync(
|
|
"kernel_service_train",
|
|
);
|
|
File helloDart = new File.fromUri(tmpDir.uri.resolve("hello.dart"));
|
|
helloDart.writeAsStringSync("""
|
|
main() {
|
|
print("Hello, World!");
|
|
}
|
|
""");
|
|
try {
|
|
for (int i = 0; i < 10; i++) {
|
|
await trainInternal(helloDart.uri.toString(), platformKernelPath);
|
|
}
|
|
} finally {
|
|
tmpDir.deleteSync(recursive: true);
|
|
}
|
|
}
|
|
|
|
Future trainInternal(String scriptUri, String? platformKernelPath) async {
|
|
var tag = kTrainTag;
|
|
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,
|
|
platformKernelPath,
|
|
false /* incremental */,
|
|
false /* for_snapshot */,
|
|
true /* embed_sources */,
|
|
1 /* isolateGroupId chosen randomly */,
|
|
[] /* source files */,
|
|
false /* enable asserts */,
|
|
null /* experimental_flags */,
|
|
null /* package_config */,
|
|
null /* multirootFilepaths */,
|
|
null /* multirootScheme */,
|
|
'all' /* CFE logging mode */,
|
|
true /* enableMirrors */,
|
|
false /* generateBytecode */,
|
|
];
|
|
await _processLoadRequest(request);
|
|
}
|
|
|
|
main([args]) {
|
|
if ((args?.length ?? 0) > 1 && args[0] == '--train') {
|
|
// This entry point is used when creating an app snapshot.
|
|
// It takes the following extra arguments:
|
|
// 1) Script to compile.
|
|
// 2) Optional platform kernel path.
|
|
int argIndex = 1;
|
|
final String script = args[argIndex++];
|
|
final String? platform = (argIndex < args.length) ? args[argIndex] : null;
|
|
train(script, platform);
|
|
} 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._() {
|
|
if (dumpKernel) {
|
|
final bytes = this.bytes;
|
|
if (bytes != null) {
|
|
_debugDumpKernel(bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Status get status => Status.ok;
|
|
|
|
@override
|
|
get payload => bytes;
|
|
|
|
String toString() => "_CompilationOk(${bytes?.length ?? 0} 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.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})";
|
|
}
|
|
|
|
Future<T> runWithPrintToStderr<T>(Future<T> f()) {
|
|
return runZoned(
|
|
() => new Future<T>(f),
|
|
zoneSpecification: new ZoneSpecification(
|
|
// ignore: non_constant_identifier_names
|
|
print: (_1, _2, _3, String line) => stderr.writeln(line),
|
|
),
|
|
);
|
|
}
|
|
|
|
int _debugDumpCounter = 0;
|
|
void _debugDumpKernel(Uint8List bytes) {
|
|
new File(
|
|
'kernel_service.tmp${_debugDumpCounter++}.dill',
|
|
).writeAsBytesSync(bytes);
|
|
}
|
|
|
|
class _ExpressionCompilationFromDillSettings {
|
|
int blobLoadCount;
|
|
int prevDillCount;
|
|
|
|
_ExpressionCompilationFromDillSettings(
|
|
this.blobLoadCount,
|
|
this.prevDillCount,
|
|
);
|
|
}
|