mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[vm/infra] Nascent AOT IL tests infrastructure.
Our current unit testing infrastructure does not make it possible to test AOT compilation pipeline end-to-end, because it does not run TFA when generating Kernel. This makes it challenging to write regression tests for certain issues. Instead we extend test runner with a capability to perform IL matching when running AOT tests. runtime/docs/infa/il_tests.md provides details on how to write such tests. TEST=manually Change-Id: I6f5220b814f4a5d8c053efacd3711df495dea404 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/214961 Commit-Queue: Slava Egorov <vegorov@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
parent
7ffd140a4e
commit
8740a4f10f
|
@ -725,7 +725,12 @@ class PrecompilerCompilerConfiguration extends CompilerConfiguration
|
|||
tempDir, arguments, environmentOverrides));
|
||||
|
||||
commands.add(
|
||||
computeDartBootstrapCommand(tempDir, arguments, environmentOverrides));
|
||||
computeGenSnapshotCommand(tempDir, arguments, environmentOverrides));
|
||||
|
||||
if (arguments.contains('--print-flow-graph-optimized')) {
|
||||
commands.add(
|
||||
computeILCompareCommand(tempDir, arguments, environmentOverrides));
|
||||
}
|
||||
|
||||
if (!_configuration.keepGeneratedFiles) {
|
||||
commands.add(computeRemoveKernelFileCommand(
|
||||
|
@ -777,7 +782,7 @@ class PrecompilerCompilerConfiguration extends CompilerConfiguration
|
|||
alwaysCompile: !_useSdk);
|
||||
}
|
||||
|
||||
Command computeDartBootstrapCommand(String tempDir, List<String> arguments,
|
||||
Command computeGenSnapshotCommand(String tempDir, List<String> arguments,
|
||||
Map<String, String> environmentOverrides) {
|
||||
var buildDir = _configuration.buildDirectory;
|
||||
var exec = _configuration.genSnapshotPath;
|
||||
|
@ -821,6 +826,8 @@ class PrecompilerCompilerConfiguration extends CompilerConfiguration
|
|||
// The SIMARM precompiler assumes support for integer division, but the
|
||||
// Qemu arm cpus do not support integer division.
|
||||
if (_configuration.useQemu) '--no-use-integer-division',
|
||||
if (arguments.contains('--print-flow-graph-optimized'))
|
||||
'--redirect-isolate-log-to=$tempDir/out.il',
|
||||
..._replaceDartFiles(arguments, tempKernelFile(tempDir)),
|
||||
];
|
||||
|
||||
|
@ -829,6 +836,21 @@ class PrecompilerCompilerConfiguration extends CompilerConfiguration
|
|||
alwaysCompile: !_useSdk);
|
||||
}
|
||||
|
||||
Command computeILCompareCommand(String tempDir, List<String> arguments,
|
||||
Map<String, String> environmentOverrides) {
|
||||
var pkgVmDir = Platform.script.resolve('../../../pkg/vm').toFilePath();
|
||||
var compareIl = '$pkgVmDir/tool/compare_il$shellScriptExtension';
|
||||
|
||||
var args = [
|
||||
arguments.firstWhere((arg) => arg.endsWith('_il_test.dart')),
|
||||
'$tempDir/out.il',
|
||||
];
|
||||
|
||||
return CompilationCommand('compare_il', tempDir, bootstrapDependencies(),
|
||||
compareIl, args, environmentOverrides,
|
||||
alwaysCompile: !_useSdk);
|
||||
}
|
||||
|
||||
static const String ndkPath = "third_party/android_tools/ndk";
|
||||
String get abiTriple => _isArm || _isArmX64
|
||||
? "arm-linux-androideabi"
|
||||
|
@ -943,6 +965,10 @@ class PrecompilerCompilerConfiguration extends CompilerConfiguration
|
|||
List<String> computeCompilerArguments(
|
||||
TestFile testFile, List<String> vmOptions, List<String> args) {
|
||||
return [
|
||||
if (testFile.ilMatches.isNotEmpty) ...[
|
||||
'--print-flow-graph-optimized',
|
||||
'--print-flow-graph-filter=${testFile.ilMatches.join(',')}'
|
||||
],
|
||||
if (_enableAsserts) '--enable_asserts',
|
||||
...filterVmOptions(vmOptions),
|
||||
...testFile.sharedOptions,
|
||||
|
|
|
@ -211,6 +211,11 @@ class TestFile extends _TestFileBase {
|
|||
throw FormatException('Unknown feature "$name" in test $filePath');
|
||||
});
|
||||
|
||||
var ilMatches = filePath.endsWith('_il_test.dart')
|
||||
? _parseStringOption(filePath, contents, r'MatchIL\[AOT\]',
|
||||
allowMultiple: true)
|
||||
: const <String>[];
|
||||
|
||||
// VM options.
|
||||
var vmOptions = <List<String>>[];
|
||||
var matches = _vmOptionsRegExp.allMatches(contents);
|
||||
|
@ -335,7 +340,8 @@ class TestFile extends _TestFileBase {
|
|||
vmOptions: vmOptions,
|
||||
sharedObjects: sharedObjects,
|
||||
otherResources: otherResources,
|
||||
experiments: experiments);
|
||||
experiments: experiments,
|
||||
ilMatches: ilMatches);
|
||||
}
|
||||
|
||||
/// A special fake test file for representing a VM unit test written in C++.
|
||||
|
@ -357,6 +363,7 @@ class TestFile extends _TestFileBase {
|
|||
sharedObjects = [],
|
||||
otherResources = [],
|
||||
experiments = [],
|
||||
ilMatches = [],
|
||||
super(null, null, []);
|
||||
|
||||
TestFile._(Path suiteDirectory, Path path, List<StaticError> expectedErrors,
|
||||
|
@ -376,7 +383,8 @@ class TestFile extends _TestFileBase {
|
|||
this.vmOptions,
|
||||
this.sharedObjects,
|
||||
this.otherResources,
|
||||
this.experiments})
|
||||
this.experiments,
|
||||
this.ilMatches = const <String>[]})
|
||||
: super(suiteDirectory, path, expectedErrors) {
|
||||
assert(!isMultitest || dartOptions.isEmpty);
|
||||
}
|
||||
|
@ -403,6 +411,9 @@ class TestFile extends _TestFileBase {
|
|||
/// requirements, the test is implicitly skipped.
|
||||
final List<Feature> requirements;
|
||||
|
||||
/// List of functions which will have their IL verified (in AOT mode).
|
||||
final List<String> ilMatches;
|
||||
|
||||
final List<String> sharedOptions;
|
||||
final List<String> dartOptions;
|
||||
final List<String> dart2jsOptions;
|
||||
|
@ -482,6 +493,7 @@ class _MultitestFile extends _TestFileBase implements TestFile {
|
|||
String get packages => _origin.packages;
|
||||
|
||||
List<Feature> get requirements => _origin.requirements;
|
||||
List<String> get ilMatches => _origin.ilMatches;
|
||||
List<String> get dart2jsOptions => _origin.dart2jsOptions;
|
||||
List<String> get dartOptions => _origin.dartOptions;
|
||||
List<String> get ddcOptions => _origin.ddcOptions;
|
||||
|
|
189
pkg/vm/bin/compare_il.dart
Normal file
189
pkg/vm/bin/compare_il.dart
Normal file
|
@ -0,0 +1,189 @@
|
|||
// 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.
|
||||
|
||||
// This is a helper script which performs IL matching for AOT IL tests.
|
||||
// See runtime/docs/infra/il_tests.md for more information.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
void main(List<String> args) {
|
||||
if (args.length != 2) {
|
||||
throw 'Usage: compare_il <*_il_test.dart> <output.il>';
|
||||
}
|
||||
|
||||
final testFile = args[0];
|
||||
final ilFile = args[1];
|
||||
|
||||
final graphs = _extractGraphs(ilFile);
|
||||
|
||||
final expectations = _extractExpectations(testFile);
|
||||
|
||||
for (var expectation in expectations.entries) {
|
||||
// Find a graph for this expectation. We expect that function names are
|
||||
// unique enough to identify a specific graph.
|
||||
final graph =
|
||||
graphs.entries.singleWhere((e) => e.key.contains(expectation.key));
|
||||
|
||||
// Extract the list of opcodes, ignoring irrelevant things like
|
||||
// ParallelMove.
|
||||
final gotOpcodesIgnoringMoves = graph.value
|
||||
.where((instr) => instr.opcode != 'ParallelMove')
|
||||
.map((instr) => instr.opcode)
|
||||
.toList();
|
||||
|
||||
// Check that expectations are the prefix of gotOpcodesIgnoringMoves.
|
||||
print('Matching ${graph.key}');
|
||||
for (var i = 0; i < expectation.value.length; i++) {
|
||||
final gotOpcode = gotOpcodesIgnoringMoves[i];
|
||||
final expectedOpcode = expectation.value[i];
|
||||
if (gotOpcode != expectedOpcode) {
|
||||
throw 'Failed to match graph of ${graph.key} to '
|
||||
'expectations for ${expectation.key} at instruction ${i}: '
|
||||
'got ${gotOpcode} expected ${expectedOpcode}';
|
||||
}
|
||||
}
|
||||
print('... ok');
|
||||
}
|
||||
|
||||
exit(0); // Success.
|
||||
}
|
||||
|
||||
// IL instruction extracted from flow graph dump.
|
||||
class Instruction {
|
||||
final String raw;
|
||||
|
||||
Instruction(this.raw);
|
||||
|
||||
String get opcode {
|
||||
final match = instructionPattern.firstMatch(raw)!;
|
||||
final op = match.namedGroup('opcode')!;
|
||||
final blockType = match.namedGroup('block_type');
|
||||
|
||||
// Handle blocks which look like "B%d[%s]".
|
||||
if (blockType != null) {
|
||||
return blockTypes[blockType]!;
|
||||
}
|
||||
|
||||
// Handle parallel moves specially.
|
||||
if (op.startsWith('ParallelMove')) {
|
||||
return 'ParallelMove';
|
||||
}
|
||||
|
||||
// Handle branches.
|
||||
if (op.startsWith(branchIfPrefix)) {
|
||||
return 'Branch(${op.substring(branchIfPrefix.length)})';
|
||||
}
|
||||
|
||||
// Normal instruction.
|
||||
return op;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'Instruction($opcode)';
|
||||
|
||||
static final instructionPattern = RegExp(
|
||||
r'^\s*\d+:\s+(v\d+ <- )?(?<opcode>[^:[(]+(?<block_type>\[[\w ]+\])?)');
|
||||
|
||||
static const blockTypes = {
|
||||
'[join]': 'JoinEntry',
|
||||
'[target]': 'TargetEntry',
|
||||
'[graph]': 'GraphEntry',
|
||||
'[function entry]': 'FunctionEntry'
|
||||
};
|
||||
|
||||
static const branchIfPrefix = 'Branch if ';
|
||||
}
|
||||
|
||||
Map<String, List<Instruction>> _extractGraphs(String ilFile) {
|
||||
final graphs = <String, List<Instruction>>{};
|
||||
|
||||
final reader = LineReader(ilFile);
|
||||
|
||||
var instructions = <Instruction>[];
|
||||
while (reader.hasMore) {
|
||||
if (reader.testNext('*** BEGIN CFG')) {
|
||||
reader.next(); // Skip phase name.
|
||||
final functionName = reader.next();
|
||||
while (!reader.testNext('*** END CFG')) {
|
||||
var curr = reader.next();
|
||||
|
||||
// If instruction line ends with '{' search for a matching '}' (it will
|
||||
// be on its own line).
|
||||
if (curr.endsWith('{')) {
|
||||
do {
|
||||
curr += '\n' + reader.current;
|
||||
} while (reader.next() != '}');
|
||||
}
|
||||
|
||||
instructions.add(Instruction(curr));
|
||||
}
|
||||
|
||||
graphs[functionName] = instructions;
|
||||
instructions = <Instruction>[];
|
||||
} else {
|
||||
reader.next();
|
||||
}
|
||||
}
|
||||
|
||||
return graphs;
|
||||
}
|
||||
|
||||
Map<String, List<String>> _extractExpectations(String testFile) {
|
||||
final expectations = <String, List<String>>{};
|
||||
|
||||
final reader = LineReader(testFile);
|
||||
|
||||
final matchILPattern = RegExp(r'^// MatchIL\[AOT\]=(?<value>.*)$');
|
||||
final matcherPattern = RegExp(r'^// __ (?<value>.*)$');
|
||||
|
||||
var matchers = <String>[];
|
||||
while (reader.hasMore) {
|
||||
var functionName = reader.matchNext(matchILPattern);
|
||||
if (functionName != null) {
|
||||
// Read comment block which follows `// MatchIL[AOT]=...`.
|
||||
while (reader.hasMore && reader.current.startsWith('//')) {
|
||||
final match = matcherPattern.firstMatch(reader.next());
|
||||
if (match != null) {
|
||||
matchers.add(match.namedGroup('value')!);
|
||||
}
|
||||
}
|
||||
expectations[functionName] = matchers;
|
||||
matchers = <String>[];
|
||||
} else {
|
||||
reader.next();
|
||||
}
|
||||
}
|
||||
|
||||
return expectations;
|
||||
}
|
||||
|
||||
class LineReader {
|
||||
final List<String> lines;
|
||||
int lineno = 0;
|
||||
|
||||
LineReader(String path) : lines = File(path).readAsLinesSync();
|
||||
|
||||
String get current => lines[lineno];
|
||||
|
||||
bool get hasMore => lineno < lines.length;
|
||||
|
||||
String next() {
|
||||
final curr = current;
|
||||
lineno++;
|
||||
return curr;
|
||||
}
|
||||
|
||||
bool testNext(String expected) {
|
||||
if (current == expected) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
String? matchNext(RegExp pattern) {
|
||||
final m = pattern.firstMatch(current);
|
||||
return m?.namedGroup('value');
|
||||
}
|
||||
}
|
35
pkg/vm/tool/compare_il
Executable file
35
pkg/vm/tool/compare_il
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
# Script for comparing IL generated from IL tests.
|
||||
|
||||
set -e
|
||||
|
||||
function follow_links() {
|
||||
file="$1"
|
||||
while [ -h "$file" ]; do
|
||||
# On Mac OS, readlink -f doesn't work.
|
||||
file="$(readlink "$file")"
|
||||
done
|
||||
echo "$file"
|
||||
}
|
||||
|
||||
# Unlike $0, $BASH_SOURCE points to the absolute path of this file.
|
||||
PROG_NAME="$(follow_links "$BASH_SOURCE")"
|
||||
|
||||
# Handle the case where dart-sdk/bin has been symlinked to.
|
||||
CUR_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
|
||||
|
||||
SDK_DIR="$CUR_DIR/../../.."
|
||||
|
||||
# TODO(kustermann): For windows as well as for hosts running on arm, our
|
||||
# checked-in dart binaries must be adjusted.
|
||||
if [[ `uname` == 'Darwin' ]]; then
|
||||
DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
|
||||
else
|
||||
DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
|
||||
fi
|
||||
|
||||
exec "$DART" $DART_VM_FLAGS "${SDK_DIR}/pkg/vm/bin/compare_il.dart" $@
|
17
pkg/vm/tool/compare_il.bat
Normal file
17
pkg/vm/tool/compare_il.bat
Normal file
|
@ -0,0 +1,17 @@
|
|||
@echo off
|
||||
REM Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
REM for details. All rights reserved. Use of this source code is governed by a
|
||||
REM BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
REM Script for comparing IL generated from IL tests.
|
||||
|
||||
set SCRIPTPATH=%~dp0
|
||||
|
||||
REM Does the path have a trailing slash? If so, remove it.
|
||||
if %SCRIPTPATH:~-1%==\ set SCRIPTPATH=%SCRIPTPATH:~0,-1%
|
||||
|
||||
set SDK_DIR=%SCRIPTPATH%/../../../
|
||||
|
||||
set DART=%SDK_DIR%/tools/sdks/dart-sdk/bin/dart.exe
|
||||
|
||||
"%DART%" %DART_VM_OPTIONS% "%SDK_DIR%/pkg/vm/bin/compare_il.dart" %*
|
|
@ -29,10 +29,8 @@ SDK_DIR="$CUR_DIR/../../.."
|
|||
# checked-in dart binaries must be adjusted.
|
||||
if [[ `uname` == 'Darwin' ]]; then
|
||||
DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
|
||||
OUT_DIR="$SDK_DIR/xcodebuild"
|
||||
else
|
||||
DART="$SDK_DIR/tools/sdks/dart-sdk/bin/dart"
|
||||
OUT_DIR="$SDK_DIR/out"
|
||||
fi
|
||||
|
||||
exec "$DART" $DART_VM_FLAGS "${SDK_DIR}/pkg/vm/bin/gen_kernel.dart" $@
|
||||
|
|
86
runtime/docs/infra/il_tests.md
Normal file
86
runtime/docs/infra/il_tests.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Writing IL tests for AOT compiler
|
||||
|
||||
Usually optimized IL strongly depends on TFA results and which makes it
|
||||
difficult to test certain AOT optimizations through `run_vm_tests`.
|
||||
|
||||
In such cases you can attempt to write an IL test instead. In these tests
|
||||
test runner will run full AOT pipeline (TFA + `gen_snapshot`), will instruct
|
||||
`gen_snapshot` to dump flow graphs of specific methods and then run
|
||||
`pkg/vm/tool/compare_il` helper script to compare expectations. Here is how you
|
||||
create an IL test.
|
||||
|
||||
IL tests are placed in files ending with `_il_test.dart`.
|
||||
|
||||
Each IL test should contain one or more _IL matching blocks_, which have the
|
||||
following format:
|
||||
|
||||
```dart
|
||||
// MatchIL[AOT]=functionName
|
||||
// comment
|
||||
// __ op
|
||||
// comment
|
||||
// __ op
|
||||
// __ op
|
||||
// __ op
|
||||
```
|
||||
|
||||
Each section starts with a `// MatchIL[AOT]=functionName` line which contains
|
||||
the name (or a substring of a name) of the function for which IL should be
|
||||
matched.
|
||||
|
||||
`// MatchIL[AOT]=...` line is followed by some number of comment lines `//`,
|
||||
where lines starting with `// __ ` specify _an instruction matcher_ and the rest
|
||||
are ignored (they just act as normal comments).
|
||||
|
||||
`gen_snapshot` will be instructed (via `--print-flow-graph-optimized` and
|
||||
`--print-flow-graph-filter=functionName,...` flags) to dump IL for all
|
||||
functions names specified in IL matching blocks.
|
||||
|
||||
After that `pkg/vm/tool/compare_il` script will be used to compare the dumps
|
||||
to actual expectations: by checking that dumped flow graph starts with the
|
||||
expected sequence of commands (ignoring some instructions like `ParallelMove`).
|
||||
|
||||
## Example
|
||||
|
||||
```dart
|
||||
// MatchIL[AOT]=factorial
|
||||
// __ GraphEntry
|
||||
// __ FunctionEntry
|
||||
// __ CheckStackOverflow
|
||||
// __ Branch(EqualityCompare)
|
||||
@pragma('vm:never-inline')
|
||||
int factorial(int value) => value == 1 ? value : value * factorial(value - 1);
|
||||
```
|
||||
|
||||
This test specifies that the graph for `factorial` should start with a sequence
|
||||
`GraphEntry`, `FunctionEntry`, `CheckStackOverflow`, `Branch(EqualityCompare)`.
|
||||
|
||||
If the graph has a different shape the test will fail, e.g. given the graph
|
||||
|
||||
```
|
||||
*** BEGIN CFG
|
||||
After AllocateRegisters
|
||||
==== file:///.../src/dart/sdk/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart_::_factorial (RegularFunction)
|
||||
0: B0[graph]:0 {
|
||||
v3 <- Constant(#1) [1, 1] T{_Smi}
|
||||
v19 <- UnboxedConstant(#1 int64) T{_Smi}
|
||||
}
|
||||
2: B1[function entry]:2 {
|
||||
v2 <- Parameter(0) [-9223372036854775808, 9223372036854775807] T{int}
|
||||
}
|
||||
4: CheckStackOverflow:8(stack=0, loop=0)
|
||||
5: ParallelMove rcx <- S+2
|
||||
6: v17 <- BoxInt64(v2) [-9223372036854775808, 9223372036854775807] T{int}
|
||||
7: ParallelMove rax <- rax
|
||||
8: Branch if StrictCompare(===, v17 T{int}, v3) T{bool} goto (3, 4)
|
||||
```
|
||||
|
||||
we will get:
|
||||
|
||||
```
|
||||
Unhandled exception:
|
||||
Failed to match graph of ==== file:///.../src/dart/sdk/runtime/tests/vm/dart/aot_prefer_equality_comparison_il_test.dart_::_factorial (RegularFunction) to expectations for factorial at instruction 3: got BoxInt64 expected Branch(EqualityCompare)
|
||||
#0 main (file:///.../src/dart/sdk/pkg/vm/bin/compare_il.dart:37:9)
|
||||
#1 _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:285:32)
|
||||
#2 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:187:12)
|
||||
```
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "vm/log.h"
|
||||
|
||||
#include "vm/dart.h"
|
||||
#include "vm/flags.h"
|
||||
#include "vm/isolate.h"
|
||||
#include "vm/thread.h"
|
||||
|
@ -28,8 +29,43 @@ DEFINE_FLAG(charp,
|
|||
"Default: service isolate log messages are suppressed "
|
||||
"(specify 'vm-service' to log them).");
|
||||
|
||||
Log::Log(LogPrinter printer)
|
||||
: printer_(printer), manual_flush_(0), buffer_(0) {}
|
||||
DEFINE_FLAG(charp,
|
||||
redirect_isolate_log_to,
|
||||
nullptr,
|
||||
"Log isolate messages into the given file.");
|
||||
|
||||
namespace {
|
||||
class LogFile {
|
||||
public:
|
||||
static const LogFile& Instance() {
|
||||
static LogFile log_file;
|
||||
return log_file;
|
||||
}
|
||||
|
||||
static void Print(const char* data) {
|
||||
Dart::file_write_callback()(data, strlen(data), Instance().handle_);
|
||||
}
|
||||
|
||||
private:
|
||||
LogFile()
|
||||
: handle_(Dart::file_open_callback()(FLAG_redirect_isolate_log_to,
|
||||
/*write=*/true)) {}
|
||||
|
||||
~LogFile() { Dart::file_close_callback()(handle_); }
|
||||
|
||||
void* handle_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Log::Log(LogPrinter printer) : printer_(printer), manual_flush_(0), buffer_(0) {
|
||||
if (printer_ == nullptr) {
|
||||
if (FLAG_redirect_isolate_log_to == nullptr) {
|
||||
printer_ = [](const char* data) { OS::PrintErr("%s", data); };
|
||||
} else {
|
||||
printer_ = &LogFile::Print;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log::~Log() {
|
||||
// Did someone enable manual flushing and then forgot to Flush?
|
||||
|
@ -108,7 +144,7 @@ void Log::Flush(const intptr_t cursor) {
|
|||
TerminateString();
|
||||
const char* str = &buffer_[cursor];
|
||||
ASSERT(str != nullptr);
|
||||
printer_("%s", str);
|
||||
printer_(str);
|
||||
buffer_.TruncateTo(cursor);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,11 +22,11 @@ class LogBlock;
|
|||
|
||||
#define THR_VPrint(format, args) Log::Current()->VPrint(format, args)
|
||||
|
||||
typedef void (*LogPrinter)(const char* str, ...) PRINTF_ATTRIBUTE(1, 2);
|
||||
typedef void (*LogPrinter)(const char* data);
|
||||
|
||||
class Log {
|
||||
public:
|
||||
explicit Log(LogPrinter printer = OS::PrintErr);
|
||||
explicit Log(LogPrinter printer = nullptr);
|
||||
~Log();
|
||||
|
||||
static Log* Current();
|
||||
|
|
|
@ -18,26 +18,12 @@ namespace dart {
|
|||
|
||||
static const char* test_output_ = NULL;
|
||||
|
||||
PRINTF_ATTRIBUTE(1, 2)
|
||||
static void TestPrinter(const char* format, ...) {
|
||||
// Measure.
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
intptr_t len = Utils::VSNPrint(NULL, 0, format, args);
|
||||
va_end(args);
|
||||
|
||||
// Print string to buffer.
|
||||
char* buffer = reinterpret_cast<char*>(malloc(len + 1));
|
||||
va_list args2;
|
||||
va_start(args2, format);
|
||||
Utils::VSNPrint(buffer, (len + 1), format, args2);
|
||||
va_end(args2);
|
||||
|
||||
static void TestPrinter(const char* buffer) {
|
||||
if (test_output_ != NULL) {
|
||||
free(const_cast<char*>(test_output_));
|
||||
test_output_ = NULL;
|
||||
}
|
||||
test_output_ = buffer;
|
||||
test_output_ = strdup(buffer);
|
||||
|
||||
// Also print to stdout to see the overall result.
|
||||
OS::PrintErr("%s", test_output_);
|
||||
|
|
Loading…
Reference in a new issue