blob: f084b998190532b4087bc83cadbdadabdbf5483e [file] [log] [blame]
// 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.
/// Command line entry point for Dart Development Compiler (known as ddc,
/// dartdevc, dev compiler), used to compile a collection of dart libraries into
/// a single JS module.
library;
import 'dart:async';
import 'dart:convert';
import 'dart:developer' show Service, debugger;
import 'dart:io';
import 'dart:isolate';
import 'package:bazel_worker/bazel_worker.dart';
import 'package:kernel/ast.dart' show clearDummyTreeNodesParentPointer;
import 'src/compiler/shared_command.dart';
import 'src/kernel/command.dart';
import 'src/kernel/expression_compiler_worker.dart';
/// The internal entry point for the Dart Dev Compiler.
///
/// [sendPort] may be passed in when started in an isolate. If provided, it is
/// used for bazel worker communication instead of stdin/stdout.
Future internalMain(List<String> args, [SendPort? sendPort]) async {
// Always returns a new modifiable list.
var parsedArgs = ParsedArguments.from(args);
if (parsedArgs.isWorker) {
var workerConnection = sendPort == null
? StdAsyncWorkerConnection()
: SendPortAsyncWorkerConnection(sendPort);
await _CompilerWorker(parsedArgs, workerConnection).run();
} else if (parsedArgs.isBatch) {
var batch = _BatchHelper();
await batch._runBatch(parsedArgs);
} else if (parsedArgs.isExpressionCompiler) {
await ExpressionCompilerWorker.createAndStart(parsedArgs.rest,
sendPort: sendPort);
} else {
var result = await compile(parsedArgs);
exitCode = result.exitCode;
}
}
/// Runs the compiler worker loop.
class _CompilerWorker extends AsyncWorkerLoop {
/// The original args supplied to the executable.
final ParsedArguments _startupArgs;
_CompilerWorker(this._startupArgs, AsyncWorkerConnection workerConnection)
: super(connection: workerConnection);
/// Keeps track of our last compilation result so it can potentially be
/// re-used in a worker.
CompilerResult? lastResult;
/// Performs each individual work request.
@override
Future<WorkResponse> performRequest(WorkRequest request) async {
var args = _startupArgs.merge(request.arguments);
var output = StringBuffer();
var context = args.reuseResult ? lastResult : null;
/// Build a map of uris to digests.
final inputDigests = <Uri, List<int>>{};
for (var input in request.inputs) {
inputDigests[sourcePathToUri(input.path)] = input.digest;
}
lastResult = await runZoned(
() => compile(args,
compilerState: context?.kernelState, inputDigests: inputDigests),
zoneSpecification:
ZoneSpecification(print: (self, parent, zone, message) {
output.writeln(message.toString());
}));
return WorkResponse()
..exitCode = lastResult!.success ? 0 : 1
..output = output.toString();
}
}
class _BatchHelper {
Stopwatch watch = Stopwatch();
CompilerResult? result;
int totalTests = 0;
int failedTests = 0;
/// One can go into "leak test mode" by doing `export DDC_LEAK_TEST="true"`
/// on the terminal (I think `set DDC_LEAK_TEST="true"` on Windows).
/// Then one could run test.py, say
/// ```
/// python3 tools/test.py -t10000 -c ddc --nnbd weak -m release -r none \
/// --enable-asserts --no-use-sdk -j 1 co19/LanguageFeatures/
/// ```
/// and attach the leak tester via
/// ```
/// out/ReleaseX64/dart \
/// pkg/front_end/test/vm_service_for_leak_detection.dart --dart-leak-test
/// ```
final bool leakTesting = Platform.environment['DDC_LEAK_TEST'] == 'true';
/// Runs DDC in Kernel batch mode for test.dart.
Future _runBatch(ParsedArguments batchArgs) async {
_workaroundForLeakingBug();
if (leakTesting) {
var services =
await Service.controlWebServer(enable: true, silenceOutput: true);
File.fromUri(Directory.systemTemp.uri.resolve('./dart_leak_test_uri'))
.writeAsStringSync(services.serverUri!.toString());
}
watch.start();
print('>>> BATCH START');
String? line;
while ((line = stdin.readLineSync(encoding: utf8))?.isNotEmpty == true) {
await _doIteration(batchArgs, line!);
_iterationDone();
}
var time = watch.elapsedMilliseconds;
print('>>> BATCH END (${totalTests - failedTests})/$totalTests ${time}ms');
}
Future<void> _doIteration(ParsedArguments batchArgs, String line) async {
totalTests++;
var args = batchArgs.merge(line.split(RegExp(r'\s+')));
String outcome;
try {
result = await compile(args, compilerState: result?.kernelState);
outcome = result!.success ? 'PASS' : (result!.crashed ? 'CRASH' : 'FAIL');
} catch (e, s) {
// Clear the cache. It might have been left in a weird state.
result = null;
outcome = 'CRASH';
print('Unhandled exception:');
print(e);
print(s);
}
stderr.writeln('>>> EOF STDERR');
print('>>> TEST $outcome ${watch.elapsedMilliseconds}ms');
}
void _iterationDone() {
if (leakTesting) {
// Dummy tree nodes can (currently) leak though the parent pointer.
// To avoid that (here) (for leak testing) we'll null them out.
clearDummyTreeNodesParentPointer();
print('Will now wait');
debugger();
}
}
/// Workaround for https://github.com/dart-lang/sdk/issues/51317.
void _workaroundForLeakingBug() {
try {
stdin.echoMode;
} catch (e) {/**/}
try {
stdout.writeln();
} catch (e) {/**/}
try {
stderr.writeln();
} catch (e) {/**/}
}
}