Files
sdk/pkg/frontend_server/lib/compute_kernel.dart
T
Srujan Gaddam 459d57c8b8 [package:js] Add static interop stub for outlines
Fixes b/235393918

@staticInterop replaces factories with a new named node, a static
method. In order to persist this transformation in modular
compilation, this needs to be done to outlines that can then be
consumed by the source library. In order to allow erasure at the
time of 'performOutlineTransformations', coreTypes is added to that
API.

Change-Id: I90d17fff8bbe143982fcd12cfb06dc3e8d58781a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/247928
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
2022-06-17 19:58:49 +00:00

502 lines
18 KiB
Dart

// Copyright (c) 2021, 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.
/// A library to invoke the CFE to compute kernel summary files.
///
/// Used by `utils/bazel/kernel_worker.dart`.
import 'dart:async';
import 'dart:io';
import 'package:_fe_analyzer_shared/src/macros/executor/isolated_executor.dart'
as isolatedExecutor;
import 'package:_fe_analyzer_shared/src/macros/executor/process_executor.dart'
as processExecutor;
import 'package:_fe_analyzer_shared/src/macros/executor/serialization.dart'
show SerializationMode;
import 'package:args/args.dart';
import 'package:build_integration/file_system/multi_root.dart';
import 'package:compiler/src/kernel/dart2js_target.dart';
import 'package:dev_compiler/src/kernel/target.dart';
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
import 'package:front_end/src/api_unstable/bazel_worker.dart' as fe;
import 'package:front_end/src/fasta/kernel/macro/macro.dart';
import 'package:kernel/ast.dart' show Component, Library, Reference;
import 'package:kernel/target/targets.dart';
import 'package:vm/target/flutter.dart';
import 'package:vm/target/flutter_runner.dart';
import 'package:vm/target/vm.dart';
/// If the last arg starts with `@`, this reads the file it points to and treats
/// each line as an additional arg.
///
/// This is how individual work request args are differentiated from startup
/// args in bazel (individual work request args go in that file).
List<String> preprocessArgs(List<String> args) {
args = new List.from(args);
if (args.isEmpty) {
return args;
}
String lastArg = args.last;
if (lastArg.startsWith('@')) {
File argsFile = new File(lastArg.substring(1));
try {
args.removeLast();
args.addAll(argsFile.readAsLinesSync());
} on FileSystemException catch (e) {
throw new Exception('Failed to read file specified by $lastArg : $e');
}
}
return args;
}
/// An [ArgParser] for generating kernel summaries.
final summaryArgsParser = new ArgParser()
..addFlag('help', negatable: false, abbr: 'h')
..addFlag('exclude-non-sources',
negatable: false,
help: 'Whether source files loaded implicitly should be included as '
'part of the summary.')
..addFlag('summary-only',
defaultsTo: true,
negatable: true,
help: 'Whether to only build summary files.')
..addFlag('summary',
defaultsTo: true,
negatable: true,
help: 'Whether or not to build summary files.')
..addOption('target',
allowed: const [
'vm',
'flutter',
'flutter_runner',
'dart2js',
'dart2js_summary',
'ddc',
],
help: 'Build kernel for the vm, flutter, flutter_runner, dart2js or ddc')
..addOption('dart-sdk-summary')
..addMultiOption('input-summary')
..addMultiOption('input-linked')
..addMultiOption('multi-root')
..addOption('multi-root-scheme', defaultsTo: 'org-dartlang-multi-root')
..addOption('libraries-file')
..addOption('packages-file')
..addMultiOption('source')
..addOption('output')
..addFlag('reuse-compiler-result', defaultsTo: false)
..addFlag('use-incremental-compiler', defaultsTo: false)
..addOption('used-inputs')
..addFlag('track-widget-creation', defaultsTo: false)
..addMultiOption('enable-experiment',
help: 'Enable a language experiment when invoking the CFE.')
..addMultiOption('define', abbr: 'D')
..addFlag('verbose', defaultsTo: false)
..addFlag('sound-null-safety', defaultsTo: false)
..addOption('verbosity',
defaultsTo: fe.Verbosity.defaultValue,
help: 'Sets the verbosity level used for filtering messages during '
'compilation.',
allowed: fe.Verbosity.allowedValues,
allowedHelp: fe.Verbosity.allowedValuesHelp)
..addMultiOption('precompiled-macro',
help: 'Configuration for precompiled macro binaries or kernel files.\n'
'Must be used in combination with --precompiled-macro-format.\n'
'The expected format of this option is as follows: '
'<macro-library-uri>;<absolute-path-to-binary>\nFor example: '
'--precompiled-macro="package:some_macro/some_macro.dart;'
'/path/to/compiled/macro"')
..addOption('precompiled-macro-format',
help: 'The format for precompiled macros.',
allowed: ['aot', 'kernel'],
defaultsTo: 'aot')
..addOption('macro-serialization-mode',
help: 'The serialization mode for communicating with macros.',
allowed: ['bytedata', 'json'],
defaultsTo: 'bytedata');
class ComputeKernelResult {
final bool succeeded;
final fe.InitializedCompilerState? previousState;
ComputeKernelResult(this.succeeded, this.previousState);
}
/// Computes a kernel file based on [args].
///
/// If [isWorker] is true then exit codes will not be set on failure.
///
/// If [outputBuffer] is provided then messages will be written to that buffer
/// instead of printed to the console.
///
/// Returns whether or not the summary was successfully output.
Future<ComputeKernelResult> computeKernel(List<String> args,
{bool isWorker: false,
StringBuffer? outputBuffer,
Map<Uri, List<int>>? inputDigests,
fe.InitializedCompilerState? previousState}) async {
inputDigests ??= <Uri, List<int>>{};
dynamic out = outputBuffer ?? stderr;
bool succeeded = true;
var parsedArgs = summaryArgsParser.parse(args);
if (parsedArgs['help']) {
out.writeln(summaryArgsParser.usage);
if (!isWorker) exit(0);
return new ComputeKernelResult(false, previousState);
}
// Bazel creates an overlay file system where some files may be located in the
// source tree, some in a gendir, and some in a bindir. The multi-root file
// system hides this from the front end.
var multiRoots = parsedArgs['multi-root'].map(Uri.base.resolve).toList();
if (multiRoots.isEmpty) multiRoots.add(Uri.base);
var fileSystem = new MultiRootFileSystem(parsedArgs['multi-root-scheme'],
multiRoots, fe.StandardFileSystem.instance);
var sources = (parsedArgs['source'] as List<String>).map(toUri).toList();
var excludeNonSources = parsedArgs['exclude-non-sources'] as bool;
var nnbdMode = parsedArgs['sound-null-safety'] as bool
? fe.NnbdMode.Strong
: fe.NnbdMode.Weak;
var summaryOnly = parsedArgs['summary-only'] as bool;
var summary = parsedArgs['summary'] as bool;
if (summaryOnly && !summary) {
throw new ArgumentError('--summary-only conflicts with --no-summary');
}
var trackWidgetCreation = parsedArgs['track-widget-creation'] as bool;
// TODO(sigmund,jakemac): make target mandatory. We allow null to be backwards
// compatible while we migrate existing clients of this tool.
var targetName =
(parsedArgs['target'] as String?) ?? (summaryOnly ? 'ddc' : 'vm');
var targetFlags = new TargetFlags(
trackWidgetCreation: trackWidgetCreation,
enableNullSafety: nnbdMode == fe.NnbdMode.Strong);
Target target;
switch (targetName) {
case 'vm':
target = new VmTarget(targetFlags);
if (summaryOnly) {
out.writeln('error: --summary-only not supported for the vm target');
}
break;
case 'flutter':
target = new FlutterTarget(targetFlags);
if (summaryOnly) {
throw new ArgumentError(
'error: --summary-only not supported for the flutter target');
}
break;
case 'flutter_runner':
target = new FlutterRunnerTarget(targetFlags);
if (summaryOnly) {
throw new ArgumentError('error: --summary-only not supported for the '
'flutter_runner target');
}
break;
case 'dart2js':
target = new Dart2jsTarget('dart2js', targetFlags);
if (summaryOnly) {
out.writeln(
'error: --summary-only not supported for the dart2js target');
}
break;
case 'dart2js_summary':
target = new Dart2jsSummaryTarget(
'dart2js', sources, excludeNonSources, targetFlags);
if (!summaryOnly) {
out.writeln(
'error: --no-summary-only not supported for the dart2js summary target');
}
break;
case 'ddc':
// TODO(jakemac):If `generateKernel` changes to return a summary
// component, process the component instead.
target =
new DevCompilerSummaryTarget(sources, excludeNonSources, targetFlags);
if (!summaryOnly) {
out.writeln('error: --no-summary-only not supported for the '
'ddc target');
}
break;
default:
out.writeln('error: unsupported target: $targetName');
return ComputeKernelResult(false, previousState);
}
List<Uri> linkedInputs =
(parsedArgs['input-linked'] as List<String>).map(toUri).toList();
List<Uri> summaryInputs =
(parsedArgs['input-summary'] as List<String>).map(toUri).toList();
fe.InitializedCompilerState state;
bool usingIncrementalCompiler = false;
bool recordUsedInputs = parsedArgs["used-inputs"] != null;
var environmentDefines = _parseEnvironmentDefines(parsedArgs['define']);
var verbose = parsedArgs['verbose'] as bool;
var verbosity = fe.Verbosity.parseArgument(parsedArgs['verbosity']);
Uri? sdkSummaryUri = toUriNullable(parsedArgs['dart-sdk-summary']);
if (parsedArgs['use-incremental-compiler']) {
usingIncrementalCompiler = true;
// If digests weren't given and if not in worker mode, create fake data and
// ensure we don't have a previous state (as that wouldn't be safe with
// fake input digests).
if (!isWorker && inputDigests.isEmpty) {
previousState = null;
if (sdkSummaryUri != null) {
inputDigests[sdkSummaryUri] = const [0];
}
for (Uri uri in summaryInputs) {
inputDigests[uri] = const [0];
}
for (Uri uri in linkedInputs) {
inputDigests[uri] = const [0];
}
}
state = await fe.initializeIncrementalCompiler(
previousState,
{
"target=$targetName",
"trackWidgetCreation=$trackWidgetCreation",
"multiRootScheme=${fileSystem.markerScheme}",
"multiRootRoots=${fileSystem.roots}",
},
sdkSummaryUri,
toUriNullable(parsedArgs['packages-file']),
toUriNullable(parsedArgs['libraries-file']),
[...summaryInputs, ...linkedInputs],
inputDigests,
target,
fileSystem,
(parsedArgs['enable-experiment'] as List<String>),
summaryOnly,
environmentDefines,
trackNeededDillLibraries: recordUsedInputs,
verbose: verbose,
nnbdMode: nnbdMode);
} else {
state = fe.initializeCompiler(
// TODO(sigmund): pass an old state once we can make use of it.
null,
sdkSummaryUri,
toUriNullable(parsedArgs['libraries-file']),
toUriNullable(parsedArgs['packages-file']),
[...summaryInputs, ...linkedInputs],
target,
fileSystem,
parsedArgs['enable-experiment'] as List<String>,
environmentDefines,
verbose: verbose,
nnbdMode: nnbdMode);
}
// Either set up or reset the state for macros based on experiment status.
// TODO: Make this a part of `initializeCompiler`, if/when we want to make it
// more widely supported.
if (state.processedOpts.globalFeatures.macros.isEnabled) {
enableMacros = true;
forceEnableMacros = true;
SerializationMode serializationMode;
switch (parsedArgs['macro-serialization-mode']) {
case 'json':
serializationMode = SerializationMode.jsonServer;
break;
case 'bytedata':
serializationMode = SerializationMode.byteDataServer;
break;
default:
throw ArgumentError('Unrecognized macro serialization mode '
'${parsedArgs['macro-serialization-mode']}');
}
// TODO: Handle invalidation of precompiled macros.
// TODO: Handle multiple macro libraries compiled to a single precompiled
// kernel file.
var macroExecutor = state.processedOpts.macroExecutor;
var format = parsedArgs['precompiled-macro-format'];
for (var parts in (parsedArgs['precompiled-macro'] as List<String>)
.map((arg) => arg.split(';'))) {
var library = Uri.parse(parts[0]);
if (macroExecutor.libraryIsRegistered(library)) {
continue;
}
var programUri = toUri(parts[1]);
switch (format) {
case 'kernel':
macroExecutor.registerExecutorFactory(
() => isolatedExecutor.start(serializationMode, programUri),
{library});
break;
case 'aot':
macroExecutor.registerExecutorFactory(
() => processExecutor.start(
serializationMode,
processExecutor.CommunicationChannel.socket,
programUri.toFilePath()),
{library});
break;
default:
throw ArgumentError('Unrecognized precompiled macro format $format');
}
}
} else {
enableMacros = false;
forceEnableMacros = false;
await state.options.macroExecutor?.close();
state.options.macroExecutor = null;
}
void onDiagnostic(fe.DiagnosticMessage message) {
if (fe.Verbosity.shouldPrint(verbosity, message)) {
fe.printDiagnosticMessage(message, out.writeln);
}
if (message.severity == fe.Severity.error) {
succeeded = false;
}
}
List<int>? kernel;
bool wroteUsedDills = false;
if (usingIncrementalCompiler) {
state.options.onDiagnostic = onDiagnostic;
IncrementalCompilerResult incrementalCompilerResult =
await state.incrementalCompiler!.computeDelta(
entryPoints: sources,
fullComponent: true,
trackNeededDillLibraries: recordUsedInputs);
Component incrementalComponent = incrementalCompilerResult.component;
if (recordUsedInputs) {
Set<Uri> usedOutlines = {};
for (Library lib in incrementalCompilerResult.neededDillLibraries!) {
if (lib.importUri.isScheme("dart")) continue;
Uri? uri = state.libraryToInputDill![lib.importUri];
if (uri == null) {
throw new StateError("Library ${lib.importUri} was recorded as used, "
"but was not in the list of known libraries.");
}
usedOutlines.add(uri);
}
var outputUsedFile = new File(parsedArgs["used-inputs"]);
outputUsedFile.createSync(recursive: true);
outputUsedFile.writeAsStringSync(usedOutlines.join("\n"));
wroteUsedDills = true;
}
kernel = await state.incrementalCompiler!.context.runInContext((_) {
if (summaryOnly) {
incrementalComponent.uriToSource.clear();
incrementalComponent.problemsAsJson = null;
incrementalComponent.setMainMethodAndMode(
null, true, incrementalComponent.mode);
target.performOutlineTransformations(
incrementalComponent, incrementalCompilerResult.coreTypes!);
makeStable(incrementalComponent);
return Future.value(fe.serializeComponent(incrementalComponent,
includeSources: false, includeOffsets: false));
}
makeStable(incrementalComponent);
return Future.value(fe.serializeComponent(incrementalComponent,
filter: excludeNonSources
? (library) => sources.contains(library.importUri)
: null,
includeOffsets: true));
});
} else if (summaryOnly) {
kernel = await fe.compileSummary(state, sources, onDiagnostic,
includeOffsets: false);
} else {
Component? component = await fe
.compileComponent(state, sources, onDiagnostic, buildSummary: summary);
if (component != null) {
kernel = fe.serializeComponent(component,
filter: excludeNonSources
? (library) => sources.contains(library.importUri)
: null,
includeOffsets: true);
}
}
state.options.onDiagnostic = null; // See http://dartbug.com/36983.
if (!wroteUsedDills && recordUsedInputs) {
// The path taken didn't record inputs used: Say we used everything.
var outputUsedFile = new File(parsedArgs["used-inputs"]);
outputUsedFile.createSync(recursive: true);
Set<Uri> allFiles = {...summaryInputs, ...linkedInputs};
outputUsedFile.writeAsStringSync(allFiles.join("\n"));
wroteUsedDills = true;
}
if (kernel != null) {
var outputFile = new File(parsedArgs['output']);
outputFile.createSync(recursive: true);
outputFile.writeAsBytesSync(kernel);
} else {
assert(!succeeded);
}
return new ComputeKernelResult(succeeded, state);
}
/// Make sure the output is stable by sorting libraries and additional exports.
void makeStable(Component c) {
// Make sure the output is stable.
c.libraries.sort((l1, l2) {
return "${l1.fileUri}".compareTo("${l2.fileUri}");
});
c.problemsAsJson?.sort();
c.computeCanonicalNames();
for (Library library in c.libraries) {
library.additionalExports.sort((Reference r1, Reference r2) {
return "${r1.canonicalName}".compareTo("${r2.canonicalName}");
});
library.problemsAsJson?.sort();
}
}
class DevCompilerSummaryTarget extends DevCompilerTarget with SummaryMixin {
final List<Uri> sources;
final bool excludeNonSources;
DevCompilerSummaryTarget(
this.sources, this.excludeNonSources, TargetFlags targetFlags)
: super(targetFlags);
}
Uri? toUriNullable(String? uriString) {
if (uriString == null) return null;
return toUri(uriString);
}
Uri toUri(String uriString) {
// Windows-style paths use '\', so convert them to '/' in case they've been
// concatenated with Unix-style paths.
return Uri.base.resolve(uriString.replaceAll("\\", "/"));
}
Map<String, String> _parseEnvironmentDefines(List<String> args) {
var environment = <String, String>{};
for (var arg in args) {
var eq = arg.indexOf('=');
if (eq <= 0) {
var kind = eq == 0 ? 'name' : 'value';
throw FormatException('no $kind given to -D option `$arg`');
}
var name = arg.substring(0, eq);
var value = arg.substring(eq + 1);
environment[name] = value;
}
return environment;
}