06d938046f
and TargetFlags.soundNullSafety TEST=existing Change-Id: I5e28d3d187b0f84fa23130c042fd3c55b89c687c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/413460 Reviewed-by: Ömer Ağacan <omersa@google.com> Reviewed-by: Jens Johansen <jensj@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Nate Biggs <natebiggs@google.com>
548 lines
19 KiB
Dart
548 lines
19 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.
|
|
|
|
import 'dart:convert' show JsonEncoder, LineSplitter, jsonDecode, utf8;
|
|
import 'dart:io'
|
|
show File, Platform, ProcessSignal, exit, exitCode, stderr, stdin, stdout;
|
|
|
|
import 'package:_fe_analyzer_shared/src/util/relativize.dart'
|
|
show isWindows, relativizeUri;
|
|
import 'package:front_end/src/api_prototype/compiler_options.dart'
|
|
show CompilerOptions, DiagnosticMessage;
|
|
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
|
|
import 'package:front_end/src/api_prototype/kernel_generator.dart';
|
|
import 'package:front_end/src/base/command_line_options.dart';
|
|
import 'package:front_end/src/base/compiler_context.dart' show CompilerContext;
|
|
import 'package:front_end/src/base/file_system_dependency_tracker.dart';
|
|
import 'package:front_end/src/base/get_dependencies.dart' show getDependencies;
|
|
import 'package:front_end/src/base/incremental_compiler.dart'
|
|
show IncrementalCompiler;
|
|
import 'package:front_end/src/base/processed_options.dart'
|
|
show ProcessedOptions;
|
|
import 'package:front_end/src/codes/cfe_codes.dart'
|
|
show codeInternalProblemVerificationError;
|
|
import 'package:front_end/src/kernel/benchmarker.dart'
|
|
show BenchmarkPhases, Benchmarker;
|
|
import 'package:front_end/src/kernel/utils.dart' show writeComponentToFile;
|
|
import 'package:front_end/src/kernel_generator_impl.dart'
|
|
show generateKernelInternal;
|
|
import 'package:front_end/src/linux_and_intel_specific_perf.dart';
|
|
import 'package:kernel/kernel.dart'
|
|
show Component, Library, RecursiveVisitor, Source;
|
|
import 'package:kernel/src/types.dart' show Types;
|
|
import 'package:kernel/target/targets.dart' show Target, TargetFlags, getTarget;
|
|
import 'package:kernel/verifier.dart';
|
|
|
|
import '../test/coverage_helper.dart';
|
|
import 'additional_targets.dart' show installAdditionalTargets;
|
|
import 'bench_maker.dart' show BenchMaker;
|
|
import 'command_line.dart' show runProtectedFromAbort, withGlobalOptions;
|
|
|
|
const bool benchmark =
|
|
const bool.fromEnvironment("benchmark", defaultValue: false);
|
|
|
|
const bool summary =
|
|
const bool.fromEnvironment("summary", defaultValue: false) || benchmark;
|
|
|
|
const int iterations = const int.fromEnvironment("iterations", defaultValue: 1);
|
|
|
|
Future<void> compileEntryPoint(List<String> arguments) async {
|
|
if (Platform.environment["dart_cfe_intel_pt"] == "true") {
|
|
print("Notice: Locating perf for profiling using intel_pt events.");
|
|
linuxAndIntelSpecificPerf(onlyInitialize: true);
|
|
}
|
|
installAdditionalTargets();
|
|
|
|
// Timing results for each iteration
|
|
List<double> elapsedTimes = <double>[];
|
|
List<Benchmarker> benchmarkers = <Benchmarker>[];
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
if (i > 0) {
|
|
print("\n\n=== Iteration ${i + 1} of $iterations");
|
|
}
|
|
Stopwatch stopwatch = new Stopwatch()..start();
|
|
Benchmarker? benchmarker;
|
|
if (benchmark) {
|
|
benchmarker = new Benchmarker();
|
|
benchmarkers.add(benchmarker);
|
|
}
|
|
await compile(arguments, benchmarker: benchmarker);
|
|
benchmarker?.stop();
|
|
stopwatch.stop();
|
|
|
|
elapsedTimes.add(stopwatch.elapsedMilliseconds.toDouble());
|
|
List<Object>? typeChecks = Types.typeChecksForTesting;
|
|
if (typeChecks?.isNotEmpty ?? false) {
|
|
BenchMaker.writeTypeChecks("type_checks.json", typeChecks!);
|
|
}
|
|
}
|
|
|
|
summarize(elapsedTimes, benchmarkers);
|
|
}
|
|
|
|
Future<void> outlineEntryPoint(List<String> arguments) async {
|
|
installAdditionalTargets();
|
|
|
|
// Timing results for each iteration
|
|
List<double> elapsedTimes = <double>[];
|
|
List<Benchmarker> benchmarkers = <Benchmarker>[];
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
if (i > 0) {
|
|
print("\n\n=== Iteration ${i + 1} of $iterations");
|
|
}
|
|
Stopwatch stopwatch = new Stopwatch()..start();
|
|
Benchmarker? benchmarker;
|
|
if (benchmark) {
|
|
benchmarker = new Benchmarker();
|
|
benchmarkers.add(benchmarker);
|
|
}
|
|
await outline(arguments, benchmarker: benchmarker);
|
|
benchmarker?.stop();
|
|
stopwatch.stop();
|
|
|
|
elapsedTimes.add(stopwatch.elapsedMilliseconds.toDouble());
|
|
}
|
|
|
|
summarize(elapsedTimes, benchmarkers);
|
|
}
|
|
|
|
void summarize(List<double> elapsedTimes, List<Benchmarker> benchmarkers) {
|
|
if (summary) {
|
|
Map<String, dynamic> map = <String, dynamic>{
|
|
'elapsedTimes': elapsedTimes,
|
|
if (benchmarkers.isNotEmpty) 'benchmarkers': benchmarkers
|
|
};
|
|
JsonEncoder encoder = new JsonEncoder.withIndent(" ");
|
|
String json = encoder.convert(map);
|
|
print('\nSummary:\n\n$json\n');
|
|
} else {
|
|
assert(benchmarkers.isEmpty);
|
|
}
|
|
}
|
|
|
|
Future<void> depsEntryPoint(List<String> arguments) async {
|
|
installAdditionalTargets();
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
if (i > 1) {
|
|
print("\n");
|
|
}
|
|
await deps(arguments);
|
|
}
|
|
}
|
|
|
|
Future<void> compilePlatformEntryPoint(List<String> arguments) async {
|
|
installAdditionalTargets();
|
|
for (int i = 0; i < iterations; i++) {
|
|
if (i > 0) {
|
|
print("\n");
|
|
}
|
|
await runProtectedFromAbort<void>(() => compilePlatform(arguments));
|
|
}
|
|
}
|
|
|
|
Future<void> batchEntryPoint(List<String> arguments) async {
|
|
if (shouldCollectCoverage()) {
|
|
tryListenToSignal(ProcessSignal.sigterm,
|
|
() => possiblyCollectCoverage("batch_compiler", doExit: true));
|
|
}
|
|
installAdditionalTargets();
|
|
await new BatchCompiler(
|
|
stdin.transform(utf8.decoder).transform(new LineSplitter()))
|
|
.run();
|
|
}
|
|
|
|
void tryListenToSignal(ProcessSignal signal, void Function() callback) {
|
|
try {
|
|
signal.watch().listen(
|
|
(_) => callback(),
|
|
onError: (_) {
|
|
// swallow.
|
|
},
|
|
);
|
|
} catch (e) {
|
|
// swallow.
|
|
}
|
|
}
|
|
|
|
const String cfeCoverageEnvironmentVariable = "CFE_COVERAGE";
|
|
|
|
bool shouldCollectCoverage() {
|
|
String? coverage = Platform.environment[cfeCoverageEnvironmentVariable];
|
|
if (coverage != null) return true;
|
|
return false;
|
|
}
|
|
|
|
Future<void> possiblyCollectCoverage(String displayNamePrefix,
|
|
{required bool doExit}) async {
|
|
String? coverage = Platform.environment[cfeCoverageEnvironmentVariable];
|
|
|
|
if (coverage != null) {
|
|
assert(shouldCollectCoverage());
|
|
Uri coverageUri = Uri.base.resolveUri(Uri.file(coverage));
|
|
String displayName =
|
|
"${displayNamePrefix}_${DateTime.now().microsecondsSinceEpoch}";
|
|
File f = new File.fromUri(coverageUri.resolve("$displayName.coverage"));
|
|
// Force compiling seems to add something like 1 second to the collection
|
|
// time, but we get rid of uncompiled functions so it seems to be worth it.
|
|
(await collectCoverage(displayName: displayName, forceCompile: true))
|
|
?.writeToFile(f);
|
|
}
|
|
|
|
if (doExit) {
|
|
exit(exitCode);
|
|
}
|
|
}
|
|
|
|
class BatchCompiler {
|
|
final Stream<String>? lines;
|
|
|
|
Uri? platformUri;
|
|
|
|
Component? platformComponent;
|
|
|
|
bool hadVerifyError = false;
|
|
|
|
IncrementalCompiler? _incrementalCompiler;
|
|
|
|
List<DiagnosticMessage> _errors = [];
|
|
|
|
void Function(DiagnosticMessage)? _originalOnDiagnostic;
|
|
|
|
BatchCompiler(this.lines);
|
|
|
|
Future<void> run() async {
|
|
await for (String line in lines!) {
|
|
try {
|
|
if (await batchCompileArguments(
|
|
new List<String>.from(jsonDecode(line)))) {
|
|
stdout.writeln(">>> TEST OK");
|
|
} else {
|
|
stdout.writeln(">>> TEST FAIL");
|
|
}
|
|
} catch (e, trace) {
|
|
stderr.writeln("Unhandled exception:\n $e");
|
|
stderr.writeln(trace);
|
|
stdout.writeln(">>> TEST CRASH");
|
|
_incrementalCompiler = null;
|
|
}
|
|
await stdout.flush();
|
|
stderr.writeln(">>> EOF STDERR");
|
|
await stderr.flush();
|
|
}
|
|
}
|
|
|
|
Future<bool> batchCompileArguments(List<String> arguments) {
|
|
return runProtectedFromAbort<bool>(
|
|
() => withGlobalOptions<bool>(
|
|
"compile",
|
|
[Flags.omitPlatform, ...arguments],
|
|
true,
|
|
(CompilerContext c, _) => batchCompileImpl(c)),
|
|
false);
|
|
}
|
|
|
|
Future<bool> batchCompile(CompilerOptions options, Uri input, Uri output) {
|
|
return CompilerContext.runWithOptions(
|
|
new ProcessedOptions(
|
|
options: options, inputs: <Uri>[input], output: output),
|
|
batchCompileImpl);
|
|
}
|
|
|
|
void _onDiagnostic(DiagnosticMessage message) {
|
|
_errors.add(message);
|
|
if (_originalOnDiagnostic != null) {
|
|
_originalOnDiagnostic!(message);
|
|
}
|
|
}
|
|
|
|
Future<bool> batchCompileImpl(CompilerContext context) async {
|
|
_errors.clear();
|
|
ProcessedOptions options = context.options;
|
|
bool createNewCompiler = false;
|
|
if (_incrementalCompiler == null ||
|
|
!_incrementalCompiler!.context.options.equivalent(options)) {
|
|
createNewCompiler = true;
|
|
}
|
|
|
|
if (platformComponent == null ||
|
|
platformUri != options.sdkSummary ||
|
|
hadVerifyError) {
|
|
createNewCompiler = true;
|
|
platformUri = options.sdkSummary;
|
|
platformComponent = await options.loadSdkSummary(null);
|
|
if (platformComponent == null) {
|
|
throw "platformComponent is null";
|
|
}
|
|
hadVerifyError = false;
|
|
}
|
|
|
|
if (createNewCompiler) {
|
|
platformComponent!.adoptChildren();
|
|
_incrementalCompiler =
|
|
new IncrementalCompiler.fromComponent(context, platformComponent);
|
|
}
|
|
|
|
ProcessedOptions incrementalCompilerOptions =
|
|
_incrementalCompiler!.context.options;
|
|
if (!identical(incrementalCompilerOptions.inputs, options.inputs)) {
|
|
// Invalidating the packages uri causes it to recalculate which packages
|
|
// file to use which is what we want.
|
|
_incrementalCompiler!.invalidate(incrementalCompilerOptions.packagesUri);
|
|
incrementalCompilerOptions.inputs.clear();
|
|
incrementalCompilerOptions.inputs.addAll(options.inputs);
|
|
}
|
|
|
|
_originalOnDiagnostic = options.rawOptionsForTesting.onDiagnostic ??
|
|
options.defaultDiagnosticMessageHandler;
|
|
incrementalCompilerOptions.rawOptionsForTesting.onDiagnostic =
|
|
_onDiagnostic;
|
|
|
|
assert(options.omitPlatform,
|
|
"Platform must be omitted for the batch compiler.");
|
|
assert(!options.hasAdditionalDills,
|
|
"Additional dills are not supported for the batch compiler.");
|
|
IncrementalCompilerResult compilerResult =
|
|
await _incrementalCompiler!.computeDelta(fullComponent: true);
|
|
await _emitComponent(options, compilerResult.component,
|
|
message: "Wrote component to ");
|
|
for (DiagnosticMessage error in _errors) {
|
|
if (error.codeName == codeInternalProblemVerificationError.name) {
|
|
hadVerifyError = true;
|
|
}
|
|
}
|
|
return _errors.isEmpty;
|
|
}
|
|
}
|
|
|
|
Future<void> incrementalEntryPoint(List<String> arguments) async {
|
|
installAdditionalTargets();
|
|
await withGlobalOptions("incremental", arguments, true,
|
|
(CompilerContext c, _) {
|
|
// TODO(ahe): Extend this entry point so it can replace
|
|
// batchEntryPoint.
|
|
new IncrementalCompiler(c);
|
|
return Future<void>.value();
|
|
});
|
|
}
|
|
|
|
Future<void> outline(List<String> arguments, {Benchmarker? benchmarker}) async {
|
|
return await runProtectedFromAbort<void>(() async {
|
|
return await withGlobalOptions("outline", arguments, true,
|
|
(CompilerContext c, _) async {
|
|
if (c.options.verbose) {
|
|
print("Building outlines for ${arguments.join(' ')}");
|
|
}
|
|
CompilerResult compilerResult = await generateKernelInternal(c,
|
|
buildSummary: true,
|
|
serializeIfBuildingSummary: false,
|
|
buildComponent: false,
|
|
benchmarker: benchmarker);
|
|
Component component = compilerResult.component!;
|
|
await _emitComponent(c.options, component,
|
|
benchmarker: benchmarker, message: "Wrote outline to ");
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<Uri> compile(List<String> arguments, {Benchmarker? benchmarker}) async {
|
|
return await runProtectedFromAbort<Uri>(() async {
|
|
return await withGlobalOptions("compile", arguments, true,
|
|
(CompilerContext c, _) async {
|
|
if (c.options.verbose) {
|
|
print("Compiling directly to Kernel: ${arguments.join(' ')}");
|
|
}
|
|
CompilerResult compilerResult =
|
|
await generateKernelInternal(c, benchmarker: benchmarker);
|
|
Component component = compilerResult.component!;
|
|
Uri uri = await _emitComponent(c.options, component,
|
|
benchmarker: benchmarker, message: "Wrote component to ");
|
|
_benchmarkAstVisitor(component, benchmarker);
|
|
return uri;
|
|
});
|
|
});
|
|
}
|
|
|
|
Future<Uri?> deps(List<String> arguments) async {
|
|
return await runProtectedFromAbort<Uri?>(() async {
|
|
FileSystemDependencyTracker tracker = new FileSystemDependencyTracker();
|
|
return await withGlobalOptions("deps", arguments, true, tracker: tracker,
|
|
(CompilerContext c, _) async {
|
|
if (c.options.verbose) {
|
|
print("Computing deps: ${arguments.join(' ')}");
|
|
}
|
|
await generateKernelInternal(
|
|
c,
|
|
buildSummary: true,
|
|
serializeIfBuildingSummary: false,
|
|
);
|
|
return await _emitDeps(tracker, c.options.output);
|
|
});
|
|
});
|
|
}
|
|
|
|
/// Writes the [component] to the URI specified in the compiler options.
|
|
Future<Uri> _emitComponent(ProcessedOptions options, Component component,
|
|
{Benchmarker? benchmarker, required String message}) async {
|
|
Uri uri = options.output!;
|
|
if (options.omitPlatform) {
|
|
benchmarker?.enterPhase(BenchmarkPhases.omitPlatform);
|
|
component.computeCanonicalNames();
|
|
Component userCode = new Component(
|
|
nameRoot: component.root,
|
|
uriToSource: new Map<Uri, Source>.from(component.uriToSource));
|
|
userCode.setMainMethodAndMode(component.mainMethodName, true);
|
|
for (Library library in component.libraries) {
|
|
if (!library.importUri.isScheme("dart")) {
|
|
userCode.libraries.add(library);
|
|
}
|
|
}
|
|
component = userCode;
|
|
}
|
|
if (uri.isScheme("file")) {
|
|
benchmarker?.enterPhase(BenchmarkPhases.writeComponent);
|
|
await writeComponentToFile(component, uri);
|
|
options.ticker.logMs("${message}${uri.toFilePath()}");
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
Future<Uri?> _emitDeps(FileSystemDependencyTracker tracker,
|
|
[Uri? output]) async {
|
|
Uri? dFile;
|
|
if (output != null) {
|
|
dFile = new File(new File.fromUri(output).path + ".d").uri;
|
|
await writeDepsFile(output, dFile, tracker.dependencies);
|
|
}
|
|
return dFile;
|
|
}
|
|
|
|
/// Runs a visitor on [component] for benchmarking.
|
|
void _benchmarkAstVisitor(Component component, Benchmarker? benchmarker) {
|
|
if (benchmarker != null) {
|
|
// When benchmarking also do a recursive visit of the produced component
|
|
// that does nothing other than visiting everything. Do this to produce
|
|
// a reference point for comparing inference time and serialization time.
|
|
benchmarker.enterPhase(BenchmarkPhases.benchmarkAstVisit);
|
|
component.accept(new EmptyRecursiveVisitorForBenchmarking());
|
|
}
|
|
benchmarker?.enterPhase(BenchmarkPhases.unknown);
|
|
}
|
|
|
|
class EmptyRecursiveVisitorForBenchmarking extends RecursiveVisitor {}
|
|
|
|
Future<void> compilePlatform(List<String> arguments) async {
|
|
FileSystemDependencyTracker tracker = new FileSystemDependencyTracker();
|
|
await withGlobalOptions("compile_platform", arguments, false,
|
|
tracker: tracker, (CompilerContext c, List<String> restArguments) {
|
|
c.compilingPlatform = true;
|
|
Uri hostPlatform = Uri.base.resolveUri(new Uri.file(restArguments[2]));
|
|
Uri outlineOutput = Uri.base.resolveUri(new Uri.file(restArguments[4]));
|
|
return compilePlatformInternal(
|
|
c, tracker, c.options.output!, outlineOutput, hostPlatform);
|
|
});
|
|
}
|
|
|
|
Future<void> compilePlatformInternal(
|
|
CompilerContext c,
|
|
FileSystemDependencyTracker tracker,
|
|
Uri fullOutput,
|
|
Uri outlineOutput,
|
|
Uri hostPlatform) async {
|
|
if (c.options.verbose) {
|
|
print("Generating outline of ${c.options.sdkRoot} into $outlineOutput");
|
|
print("Compiling ${c.options.sdkRoot} to $fullOutput");
|
|
}
|
|
|
|
CompilerResult result =
|
|
await generateKernelInternal(c, buildSummary: true, buildComponent: true);
|
|
new File.fromUri(outlineOutput).writeAsBytesSync(result.summary!);
|
|
c.options.ticker.logMs("Wrote outline to ${outlineOutput.toFilePath()}");
|
|
|
|
verifyComponent(c.options.target,
|
|
VerificationStage.afterModularTransformations, result.component!);
|
|
await writeComponentToFile(result.component!, fullOutput);
|
|
|
|
c.options.ticker.logMs("Wrote component to ${fullOutput.toFilePath()}");
|
|
|
|
if (c.options.emitDeps) {
|
|
List<Uri> deps = tracker.dependencies.toList();
|
|
for (Uri dependency in await computeHostDependencies(hostPlatform)) {
|
|
// Add the dependencies of the compiler's own sources.
|
|
if (dependency != outlineOutput) {
|
|
// We're computing the dependencies for [outlineOutput], so we shouldn't
|
|
// include it in the deps file.
|
|
deps.add(dependency);
|
|
}
|
|
}
|
|
await writeDepsFile(fullOutput,
|
|
new File(new File.fromUri(fullOutput).path + ".d").uri, deps);
|
|
}
|
|
}
|
|
|
|
Future<List<Uri>> computeHostDependencies(Uri hostPlatform) {
|
|
// Do not try to parse compile_platform if it was precompiled into a binary.
|
|
if (!Platform.script.toFilePath().endsWith('.dart')) {
|
|
return Future.value([]);
|
|
}
|
|
|
|
// Returns a list of source files that make up the Fasta compiler (the files
|
|
// the Dart VM reads to run Fasta). Until Fasta is self-hosting (in strong
|
|
// mode), this is only an approximation, albeit accurate. Once Fasta is
|
|
// self-hosting, this isn't an approximation. Regardless, strong mode
|
|
// shouldn't affect which files are read.
|
|
Target? hostTarget = getTarget("vm", new TargetFlags());
|
|
return getDependencies(Platform.script,
|
|
platform: hostPlatform, target: hostTarget);
|
|
}
|
|
|
|
Future<void> writeDepsFile(
|
|
Uri output, Uri depsFile, List<Uri> allDependencies) async {
|
|
if (allDependencies.isEmpty) return;
|
|
String toRelativeFilePath(Uri uri) {
|
|
// Ninja expects to find file names relative to the current working
|
|
// directory. We've tried making them relative to the deps file, but that
|
|
// doesn't work for downstream projects. Making them absolute also
|
|
// doesn't work.
|
|
//
|
|
// We can test if it works by running ninja twice, for example:
|
|
//
|
|
// ninja -C xcodebuild/ReleaseX64 -d explain compile_platform
|
|
// ninja -C xcodebuild/ReleaseX64 -d explain compile_platform
|
|
//
|
|
// The second time, ninja should say:
|
|
//
|
|
// ninja: Entering directory `xcodebuild/ReleaseX64'
|
|
// ninja: no work to do.
|
|
//
|
|
// It's broken if it says something like this:
|
|
//
|
|
// ninja explain: expected depfile 'vm_platform.dill.d' to mention \
|
|
// 'vm_platform.dill', got '/.../xcodebuild/ReleaseX64/vm_platform.dill'
|
|
return Uri.parse(relativizeUri(Uri.base, uri, isWindows)).toFilePath();
|
|
}
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
sb.write(toRelativeFilePath(output));
|
|
sb.write(":");
|
|
List<String> paths = new List<String>.generate(
|
|
allDependencies.length, (int i) => toRelativeFilePath(allDependencies[i]),
|
|
growable: false);
|
|
// Sort the relative paths to ease analyzing future changes to this code.
|
|
paths.sort();
|
|
String? previous;
|
|
for (String path in paths) {
|
|
// Check for and omit duplicates.
|
|
if (path != previous) {
|
|
previous = path;
|
|
sb.write(" \\\n ");
|
|
sb.write(path);
|
|
}
|
|
}
|
|
sb.writeln();
|
|
await new File.fromUri(depsFile).writeAsString("$sb");
|
|
}
|