Skip to content

Commit

Permalink
[vm/infra] Nascent AOT IL tests infrastructure.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
mraleph authored and commit-bot@chromium.org committed Sep 30, 2021
1 parent 7ffd140 commit 8740a4f
Show file tree
Hide file tree
Showing 10 changed files with 412 additions and 27 deletions.
30 changes: 28 additions & 2 deletions pkg/test_runner/lib/src/compiler_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)),
];

Expand All @@ -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"
Expand Down Expand Up @@ -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,
Expand Down
16 changes: 14 additions & 2 deletions pkg/test_runner/lib/src/test_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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++.
Expand All @@ -357,6 +363,7 @@ class TestFile extends _TestFileBase {
sharedObjects = [],
otherResources = [],
experiments = [],
ilMatches = [],
super(null, null, []);

TestFile._(Path suiteDirectory, Path path, List<StaticError> expectedErrors,
Expand All @@ -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);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
189 changes: 189 additions & 0 deletions pkg/vm/bin/compare_il.dart
Original file line number Diff line number Diff line change
@@ -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 changes: 35 additions & 0 deletions pkg/vm/tool/compare_il
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions pkg/vm/tool/compare_il.bat
Original file line number Diff line number Diff line change
@@ -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" %*
2 changes: 0 additions & 2 deletions pkg/vm/tool/gen_kernel
Original file line number Diff line number Diff line change
Expand Up @@ -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" $@
Loading

0 comments on commit 8740a4f

Please sign in to comment.