- The '.cfg' output is now created on target.
- Arch-specific checker tests can be created by inserting a
suffix. For example:
/// CHECK-START-ARM64: int Main.foo(int) register (after)
/// CHECK-DAG: <<Arg:i\d+>> ParameterValue
Change-Id: I55cdb37f8e806c7ffdde6b676c8f44ac30b59051
int64_t offset = instr->ImmLSUnsigned() << instr->SizeLS();
std::ostringstream tmp_stream;
Thread::DumpThreadOffset<8>(tmp_stream, static_cast<uint32_t>(offset));
- AppendToOutput(" (%s)", tmp_stream.str().c_str());
+ AppendToOutput(" ; %s", tmp_stream.str().c_str());
}
}
--- /dev/null
+Test allocation of caller and callee saved registers.
--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+
+ public static void assertIntEquals(int expected, int result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+
+ static boolean doThrow = false;
+
+ // This function always returns 1.
+ // We use 'throw' to prevent the function from being inlined.
+ public static int $opt$noinline$function_call(int arg) {
+ if (doThrow) throw new Error();
+ return 1 % arg;
+ }
+
+ // | registers available to | regexp
+ // | the register allocator |
+ // ------------------------------|------------------------|-----------------
+ // ARM64 callee-saved registers | [x20-x29] | x2[0-9]
+ // ARM callee-saved registers | [r5-r8,r10,r11] | r([5-8]|10|11)
+
+ /**
+ * Check that a value live across a function call is allocated in a callee
+ * saved register.
+ */
+
+ /// CHECK-START-ARM: int Main.$opt$LiveInCall(int) register (after)
+ /// CHECK-DAG: <<Arg:i\d+>> ParameterValue
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1
+ /// CHECK: <<t1:i\d+>> Add [<<Arg>>,<<Const1>>] {{.*->r([5-8]|10|11)}}
+ /// CHECK: <<t2:i\d+>> InvokeStaticOrDirect
+ /// CHECK: Sub [<<t1>>,<<t2>>]
+ /// CHECK: Return
+
+ /// CHECK-START-ARM64: int Main.$opt$LiveInCall(int) register (after)
+ /// CHECK-DAG: <<Arg:i\d+>> ParameterValue
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1
+ /// CHECK: <<t1:i\d+>> Add [<<Arg>>,<<Const1>>] {{.*->x2[0-9]}}
+ /// CHECK: <<t2:i\d+>> InvokeStaticOrDirect
+ /// CHECK: Sub [<<t1>>,<<t2>>]
+ /// CHECK: Return
+
+ // TODO: Add tests for other architectures.
+
+ public static int $opt$LiveInCall(int arg) {
+ int t1 = arg + 1;
+ int t2 = $opt$noinline$function_call(arg);
+ return t1 - t2;
+ }
+
+ public static void main(String[] args) {
+ int arg = 123;
+ assertIntEquals($opt$LiveInCall(arg), arg);
+ }
+}
# on a particular DEX output, keep building them with dx for now (b/19467889).
USE_JACK="false"
- if [ "$runtime" = "art" -a "$image_suffix" = "-optimizing" -a "$target_mode" = "no" -a "$debuggable" = "no" ]; then
+ if [ "$runtime" = "art" -a "$image_suffix" = "-optimizing" -a "$debuggable" = "no" ]; then
# In no-prebuild mode, the compiler is only invoked if both dex2oat and
# patchoat are available. Disable Checker otherwise (b/22552692).
if [ "$prebuild_mode" = "yes" ] || [ "$have_patchoat" = "yes" -a "$have_dex2oat" = "yes" ]; then
run_checker="yes"
- run_args="${run_args} -Xcompiler-option --dump-cfg=$tmp_dir/$cfg_output \
+ if [ "$target_mode" = "no" ]; then
+ cfg_output_dir="$tmp_dir"
+ checker_arch_option=
+ else
+ cfg_output_dir="$DEX_LOCATION"
+ checker_arch_option="--arch=${target_arch_name^^}"
+ fi
+ run_args="${run_args} -Xcompiler-option --dump-cfg=$cfg_output_dir/$cfg_output \
-Xcompiler-option -j1"
fi
fi
build_file_size_limit=5120
run_file_size_limit=5120
fi
+if [ "$run_checker" = "yes" -a "$target_mode" = "yes" ]; then
+ # We will need to `adb pull` the .cfg output from the target onto the host to
+ # run checker on it. This file can be big.
+ build_file_size_limit=16384
+ run_file_size_limit=16384
+fi
if [ ${USE_JACK} = "false" ]; then
# Set ulimit if we build with dx only, Jack can generate big temp files.
if ! ulimit -S "$build_file_size_limit"; then
if [ "$run_exit" = "0" ]; then
if [ "$run_checker" = "yes" ]; then
- "$checker" "$cfg_output" "$tmp_dir" 2>&1
+ if [ "$target_mode" = "yes" ]; then
+ adb pull $cfg_output_dir/$cfg_output &> /dev/null
+ fi
+ "$checker" $checker_arch_option "$cfg_output" "$tmp_dir" 2>&1
checker_exit="$?"
if [ "$checker_exit" = "0" ]; then
good="yes"
echo "${test_dir}: running..." 1>&2
"./${run}" $run_args "$@" >"$output" 2>&1
if [ "$run_checker" = "yes" ]; then
- "$checker" -q "$cfg_output" "$tmp_dir" >> "$output" 2>&1
+ if [ "$target_mode" = "yes" ]; then
+ adb pull $cfg_output_dir/$cfg_output &> /dev/null
+ fi
+ "$checker" -q $checker_arch_option "$cfg_output" "$tmp_dir" >> "$output" 2>&1
fi
sed -e 's/[[:cntrl:]]$//g' < "$output" >"${td_expected}"
good="yes"
echo "run exit status: $run_exit" 1>&2
good_run="no"
elif [ "$run_checker" = "yes" ]; then
- "$checker" -q "$cfg_output" "$tmp_dir" >> "$output" 2>&1
+ if [ "$target_mode" = "yes" ]; then
+ adb pull $cfg_output_dir/$cfg_output &> /dev/null
+ fi
+ "$checker" -q $checker_arch_option "$cfg_output" "$tmp_dir" >> "$output" 2>&1
checker_exit="$?"
if [ "$checker_exit" != "0" ]; then
echo "checker exit status: $checker_exit" 1>&2
The engine will attempt to match the check lines against the output of the
group named on the first line. Together they verify that the CFG after
constant folding returns an integer constant with value either 11 or 22.
+
+A group of check lines can be made architecture-specific by inserting '-<arch>'
+after the 'CHECK-START' keyword. The previous example can be updated to run for
+arm64 only with:
+
+ // CHECK-START-ARM64: int MyClass.MyMethod() constant_folding (after)
+ // CHECK: <<ID:i\d+>> IntConstant {{11|22}}
+ // CHECK: Return [<<ID>>]
import argparse
import os
+from common.archs import archs_list
from common.logger import Logger
from file_format.c1visualizer.parser import ParseC1visualizerStream
from file_format.checker.parser import ParseCheckerStream
help="print a list of all passes found in the tested file")
parser.add_argument("--dump-pass", dest="dump_pass", metavar="PASS",
help="print a compiler pass dump")
+ parser.add_argument("--arch", dest="arch", choices=archs_list,
+ help="Run the tests for the specified target architecture.")
parser.add_argument("-q", "--quiet", action="store_true",
help="print only errors")
return parser.parse_args()
Logger.fail("Source path \"" + path + "\" not found")
-def RunTests(checkPrefix, checkPath, outputFilename):
+def RunTests(checkPrefix, checkPath, outputFilename, targetArch):
c1File = ParseC1visualizerStream(os.path.basename(outputFilename), open(outputFilename, "r"))
for checkFilename in FindCheckerFiles(checkPath):
checkerFile = ParseCheckerStream(os.path.basename(checkFilename),
checkPrefix,
open(checkFilename, "r"))
- MatchFiles(checkerFile, c1File)
+ MatchFiles(checkerFile, c1File, targetArch)
if __name__ == "__main__":
elif args.dump_pass:
DumpPass(args.tested_file, args.dump_pass)
else:
- RunTests(args.check_prefix, args.source_path, args.tested_file)
+ RunTests(args.check_prefix, args.source_path, args.tested_file, args.arch)
--- /dev/null
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+archs_list = ['ARM', 'ARM64', 'MIPS64', 'X86', 'X86_64']
def __parseC1Line(line, lineNo, state, fileName):
""" This function is invoked on each line of the output file and returns
- a pair which instructs the parser how the line should be handled. If the
+ a triplet which instructs the parser how the line should be handled. If the
line is to be included in the current group, it is returned in the first
value. If the line starts a new output group, the name of the group is
- returned in the second value.
+ returned in the second value. The third value is only here to make the
+ function prototype compatible with `SplitStream` and is always set to
+ `None` here.
"""
if state.currentState == C1ParserState.StartingCfgBlock:
# Previous line started a new 'cfg' block which means that this one must
# Extract the pass name, prepend it with the name of the method and
# return as the beginning of a new group.
state.currentState = C1ParserState.InsideCfgBlock
- return (None, state.lastMethodName + " " + line.split("\"")[1])
+ return (None, state.lastMethodName + " " + line.split("\"")[1], None)
else:
Logger.fail("Expected output group name", fileName, lineNo)
elif state.currentState == C1ParserState.InsideCfgBlock:
if line == "end_cfg":
state.currentState = C1ParserState.OutsideBlock
- return (None, None)
+ return (None, None, None)
else:
- return (line, None)
+ return (line, None, None)
elif state.currentState == C1ParserState.InsideCompilationBlock:
# Search for the method's name. Format: method "<name>"
state.lastMethodName = methodName
elif line == "end_compilation":
state.currentState = C1ParserState.OutsideBlock
- return (None, None)
+ return (None, None, None)
else:
assert state.currentState == C1ParserState.OutsideBlock
if state.lastMethodName is None:
Logger.fail("Expected method header", fileName, lineNo)
state.currentState = C1ParserState.StartingCfgBlock
- return (None, None)
+ return (None, None, None)
elif line == "begin_compilation":
state.currentState = C1ParserState.InsideCompilationBlock
- return (None, None)
+ return (None, None, None)
else:
Logger.fail("C1visualizer line not inside a group", fileName, lineNo)
fnProcessLine = lambda line, lineNo: __parseC1Line(line, lineNo, state, fileName)
fnLineOutsideChunk = lambda line, lineNo: \
Logger.fail("C1visualizer line not inside a group", fileName, lineNo)
- for passName, passLines, startLineNo in SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
+ for passName, passLines, startLineNo, testArch in \
+ SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
C1visualizerPass(c1File, passName, passLines, startLineNo + 1)
return c1File
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.archs import archs_list
from common.logger import Logger
from file_format.common import SplitStream
from file_format.checker.struct import CheckerFile, TestCase, TestAssertion, RegexExpression
def __isCheckerLine(line):
return line.startswith("///") or line.startswith("##")
-def __extractLine(prefix, line):
+def __extractLine(prefix, line, arch = None):
""" Attempts to parse a check line. The regex searches for a comment symbol
followed by the CHECK keyword, given attribute and a colon at the very
beginning of the line. Whitespaces are ignored.
"""
rIgnoreWhitespace = r"\s*"
rCommentSymbols = [r"///", r"##"]
+ arch_specifier = r"-%s" % arch if arch is not None else r""
regexPrefix = rIgnoreWhitespace + \
r"(" + r"|".join(rCommentSymbols) + r")" + \
rIgnoreWhitespace + \
- prefix + r":"
+ prefix + arch_specifier + r":"
# The 'match' function succeeds only if the pattern is matched at the
# beginning of the line.
return None
def __processLine(line, lineNo, prefix, fileName):
- """ This function is invoked on each line of the check file and returns a pair
+ """ This function is invoked on each line of the check file and returns a triplet
which instructs the parser how the line should be handled. If the line is
to be included in the current check group, it is returned in the first
value. If the line starts a new check group, the name of the group is
- returned in the second value.
+ returned in the second value. The third value indicates whether the line
+ contained an architecture-specific suffix.
"""
if not __isCheckerLine(line):
- return None, None
+ return None, None, None
# Lines beginning with 'CHECK-START' start a new test case.
- startLine = __extractLine(prefix + "-START", line)
- if startLine is not None:
- return None, startLine
+ # We currently only consider the architecture suffix in "CHECK-START" lines.
+ for arch in [None] + archs_list:
+ startLine = __extractLine(prefix + "-START", line, arch)
+ if startLine is not None:
+ return None, startLine, arch
# Lines starting only with 'CHECK' are matched in order.
plainLine = __extractLine(prefix, line)
if plainLine is not None:
- return (plainLine, TestAssertion.Variant.InOrder, lineNo), None
+ return (plainLine, TestAssertion.Variant.InOrder, lineNo), None, None
# 'CHECK-NEXT' lines are in-order but must match the very next line.
nextLine = __extractLine(prefix + "-NEXT", line)
if nextLine is not None:
- return (nextLine, TestAssertion.Variant.NextLine, lineNo), None
+ return (nextLine, TestAssertion.Variant.NextLine, lineNo), None, None
# 'CHECK-DAG' lines are no-order assertions.
dagLine = __extractLine(prefix + "-DAG", line)
if dagLine is not None:
- return (dagLine, TestAssertion.Variant.DAG, lineNo), None
+ return (dagLine, TestAssertion.Variant.DAG, lineNo), None, None
# 'CHECK-NOT' lines are no-order negative assertions.
notLine = __extractLine(prefix + "-NOT", line)
if notLine is not None:
- return (notLine, TestAssertion.Variant.Not, lineNo), None
+ return (notLine, TestAssertion.Variant.Not, lineNo), None, None
Logger.fail("Checker assertion could not be parsed: '" + line + "'", fileName, lineNo)
fnProcessLine = lambda line, lineNo: __processLine(line, lineNo, prefix, fileName)
fnLineOutsideChunk = lambda line, lineNo: \
Logger.fail("Checker line not inside a group", fileName, lineNo)
- for caseName, caseLines, startLineNo in SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
- testCase = TestCase(checkerFile, caseName, startLineNo)
+ for caseName, caseLines, startLineNo, testArch in \
+ SplitStream(stream, fnProcessLine, fnLineOutsideChunk):
+ testCase = TestCase(checkerFile, caseName, startLineNo, testArch)
for caseLine in caseLines:
ParseCheckerAssertion(testCase, caseLine[0], caseLine[1], caseLine[2])
return checkerFile
def addTestCase(self, new_test_case):
self.testCases.append(new_test_case)
+ def testCasesForArch(self, targetArch):
+ return [t for t in self.testCases if t.testArch == targetArch]
+
def __eq__(self, other):
return isinstance(other, self.__class__) \
and self.testCases == other.testCases
class TestCase(PrintableMixin):
- def __init__(self, parent, name, startLineNo):
+ def __init__(self, parent, name, startLineNo, testArch = None):
assert isinstance(parent, CheckerFile)
self.parent = parent
self.name = name
self.assertions = []
self.startLineNo = startLineNo
+ self.testArch = testArch
if not self.name:
Logger.fail("Test case does not have a name", self.fileName, self.startLineNo)
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.archs import archs_list
from common.testing import ToUnicode
from file_format.checker.parser import ParseCheckerStream
from file_format.checker.struct import CheckerFile, TestCase, TestAssertion, RegexExpression
/// CHECK-START: Example Group
/// CHECK-NEXT: bar
""")
+
+
+class CheckerParser_ArchTests(unittest.TestCase):
+
+ noarch_block = """
+ /// CHECK-START: Group
+ /// CHECK: foo
+ /// CHECK-NEXT: bar
+ /// CHECK-NOT: baz
+ /// CHECK-DAG: yoyo
+ """
+
+ arch_block = """
+ /// CHECK-START-{test_arch}: Group
+ /// CHECK: foo
+ /// CHECK-NEXT: bar
+ /// CHECK-NOT: baz
+ /// CHECK-DAG: yoyo
+ """
+
+ def test_NonArchTests(self):
+ for arch in [None] + archs_list:
+ checkerFile = ParseCheckerStream("<test-file>",
+ "CHECK",
+ io.StringIO(ToUnicode(self.noarch_block)))
+ self.assertEqual(len(checkerFile.testCases), 1)
+ self.assertEqual(len(checkerFile.testCases[0].assertions), 4)
+
+ def test_IgnoreNonTargetArch(self):
+ for targetArch in archs_list:
+ for testArch in [a for a in archs_list if a != targetArch]:
+ checkerText = self.arch_block.format(test_arch = testArch)
+ checkerFile = ParseCheckerStream("<test-file>",
+ "CHECK",
+ io.StringIO(ToUnicode(checkerText)))
+ self.assertEqual(len(checkerFile.testCases), 1)
+ self.assertEqual(len(checkerFile.testCasesForArch(testArch)), 1)
+ self.assertEqual(len(checkerFile.testCasesForArch(targetArch)), 0)
+
+ def test_Arch(self):
+ for arch in archs_list:
+ checkerText = self.arch_block.format(test_arch = arch)
+ checkerFile = ParseCheckerStream("<test-file>",
+ "CHECK",
+ io.StringIO(ToUnicode(checkerText)))
+ self.assertEqual(len(checkerFile.testCases), 1)
+ self.assertEqual(len(checkerFile.testCasesForArch(arch)), 1)
+ self.assertEqual(len(checkerFile.testCases[0].assertions), 4)
Arguments:
- fnProcessLine: Called on each line with the text and line number. Must
- return a pair, name of the chunk started on this line and data extracted
- from this line (or None in both cases).
+ return a triplet, composed of the name of the chunk started on this line,
+ the data extracted, and the name of the architecture this test applies to
+ (or None to indicate that all architectures should run this test).
- fnLineOutsideChunk: Called on attempt to attach data prior to creating
a chunk.
"""
# Let the child class process the line and return information about it.
# The _processLine method can modify the content of the line (or delete it
# entirely) and specify whether it starts a new group.
- processedLine, newChunkName = fnProcessLine(line, lineNo)
+ processedLine, newChunkName, testArch = fnProcessLine(line, lineNo)
+ # Currently, only a full chunk can be specified as architecture-specific.
+ assert testArch is None or newChunkName is not None
if newChunkName is not None:
- currentChunk = (newChunkName, [], lineNo)
+ currentChunk = (newChunkName, [], lineNo, testArch)
allChunks.append(currentChunk)
if processedLine is not None:
if currentChunk is not None:
matchFrom = match.scope.end + 1
variables = match.variables
-def MatchFiles(checkerFile, c1File):
+def MatchFiles(checkerFile, c1File, targetArch):
for testCase in checkerFile.testCases:
+ if testCase.testArch not in [None, targetArch]:
+ continue
# TODO: Currently does not handle multiple occurrences of the same group
# name, e.g. when a pass is run multiple times. It will always try to
# match a check group against the first output group of the same name.
from file_format.c1visualizer.test import C1visualizerParser_Test
from file_format.checker.test import CheckerParser_PrefixTest, \
CheckerParser_RegexExpressionTest, \
- CheckerParser_FileLayoutTest
+ CheckerParser_FileLayoutTest, \
+ CheckerParser_ArchTests
from match.test import MatchLines_Test, \
MatchFiles_Test