summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Fabio Rinaldi <fabio.rinaldi@linaro.org> 2020-02-12 16:18:50 +0000
committer Roland Levillain <rpl@google.com> 2020-07-22 10:44:31 +0000
commit40b0614be3296e163654c4e293793d00bcf36a5a (patch)
tree5dcd9dd7d93d14b83c82cb90674df9353280189d
parent52fe49e87902fb231201874f52c4993e6fe611e9 (diff)
Checker: Add function isaHasFeature
Developers are now able to use hasIsaFeature("feature_name") to check if an instruction set feature was used at compile time. Checker will retrieve the list of features from the .cfg file. It expects them to be dumped at the beginning of the file as a fake compilation block in the following form: begin_compilation name "isa_features:feature1,-feature2" method "isa_features:feature1,-feature2" date 1580721972 end_compilation Dumping that is optional. hasIsaFeature() will always return False if that pass is not found. Author: Fabio Rinaldi Committer: Artem Serov Bug: 147876827 Test: ./art/tools/checker/run_unit_tests.py Test: test.py --target --optimizing Change-Id: I4ce15d853025f9863d7981b33b761cfc799fed50
-rw-r--r--tools/checker/README20
-rw-r--r--tools/checker/common/logger.py8
-rw-r--r--tools/checker/file_format/c1visualizer/parser.py24
-rw-r--r--tools/checker/file_format/c1visualizer/struct.py12
-rw-r--r--tools/checker/file_format/c1visualizer/test.py60
-rw-r--r--tools/checker/match/file.py7
-rw-r--r--tools/checker/match/line.py1
-rw-r--r--tools/checker/match/test.py62
8 files changed, 163 insertions, 31 deletions
diff --git a/tools/checker/README b/tools/checker/README
index 51be828848..b04b0d8253 100644
--- a/tools/checker/README
+++ b/tools/checker/README
@@ -93,9 +93,23 @@ Branching is possible thanks to the following statements:
- CHECK-FI:
CHECK-IF and CHECK-ELIF take a Python expression as input that will be evaluated by `eval`.
-Like CHECK-EVAL, they support only referencing of variables, defining new variables as part
-of the statement input is not allowed. Any other surrounding text will be passed to Python's `eval`
-as is. CHECK-ELSE and CHECK-FI must not have any input.
+
+A possible use case of branching is to check whether the generated code exploits the instruction
+architecture features enabled at compile time. For that purpose, you can call the custom made
+function isaHasFeature("feature_name").
+
+Example:
+ /// CHECK-START-ARM64: int other.TestByte.testDotProdComplex(byte[], byte[]) disassembly (after)
+ /// CHECK: VecDotProd
+ /// CHECK-IF: isaHasFeature("dotprod")
+ /// CHECK: sdot
+ /// CHECK-ELSE:
+ /// CHECK-NOT: sdot
+ /// CHECK-FI:
+
+Like CHECK-EVAL, CHECK-IF and CHECK-ELIF support only referencing of variables, defining new
+variables as part of the statement input is not allowed. Any other surrounding text will be passed
+to Python's `eval` as is. CHECK-ELSE and CHECK-FI must not have any input.
Example:
/// CHECK-START: int MyClass.MyMethod() constant_folding (after)
diff --git a/tools/checker/common/logger.py b/tools/checker/common/logger.py
index 15eb55c702..aa3a92f56f 100644
--- a/tools/checker/common/logger.py
+++ b/tools/checker/common/logger.py
@@ -44,14 +44,14 @@ class Logger(object):
Verbosity = Level.Info
@staticmethod
- def log(text, level=Level.Info, color=Color.Default, newLine=True, out=sys.stdout):
+ def log(content, level=Level.Info, color=Color.Default, newLine=True, out=sys.stdout):
if level <= Logger.Verbosity:
- text = Logger.Color.terminalCode(color, out) + text + \
+ content = Logger.Color.terminalCode(color, out) + str(content) + \
Logger.Color.terminalCode(Logger.Color.Default, out)
if newLine:
- print(text, file=out)
+ print(content, file=out)
else:
- print(text, end="", file=out)
+ print(content, end="", file=out)
out.flush()
@staticmethod
diff --git a/tools/checker/file_format/c1visualizer/parser.py b/tools/checker/file_format/c1visualizer/parser.py
index bdcde9db51..e16382e3f7 100644
--- a/tools/checker/file_format/c1visualizer/parser.py
+++ b/tools/checker/file_format/c1visualizer/parser.py
@@ -25,7 +25,7 @@ class C1ParserState:
self.currentState = C1ParserState.OutsideBlock
self.lastMethodName = None
-def __parseC1Line(line, lineNo, state, fileName):
+def __parseC1Line(c1File, line, lineNo, state, fileName):
""" This function is invoked on each line of the output 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 group, it is returned in the first
@@ -58,7 +58,25 @@ def __parseC1Line(line, lineNo, state, fileName):
methodName = line.split("\"")[1].strip()
if not methodName:
Logger.fail("Empty method name in output", fileName, lineNo)
- state.lastMethodName = methodName
+
+ m = re.match("isa_features:([\w,-]+)", methodName)
+ if (m):
+ rawFeatures = m.group(1).split(",")
+ # Create a map of features in the form {featureName: isEnabled}.
+ features = {}
+ for rf in rawFeatures:
+ featureName = rf
+ isEnabled = True
+ # A '-' in front of the feature name indicates that the feature wasn't enabled at compile
+ # time.
+ if rf[0] == '-':
+ featureName = rf[1:]
+ isEnabled = False
+ features[featureName] = isEnabled
+
+ c1File.setISAFeatures(features)
+ else:
+ state.lastMethodName = methodName
elif line == "end_compilation":
state.currentState = C1ParserState.OutsideBlock
return (None, None, None)
@@ -81,7 +99,7 @@ def __parseC1Line(line, lineNo, state, fileName):
def ParseC1visualizerStream(fileName, stream):
c1File = C1visualizerFile(fileName)
state = C1ParserState()
- fnProcessLine = lambda line, lineNo: __parseC1Line(line, lineNo, state, fileName)
+ fnProcessLine = lambda line, lineNo: __parseC1Line(c1File, line, lineNo, state, fileName)
fnLineOutsideChunk = lambda line, lineNo: \
Logger.fail("C1visualizer line not inside a group", fileName, lineNo)
for passName, passLines, startLineNo, testArch in \
diff --git a/tools/checker/file_format/c1visualizer/struct.py b/tools/checker/file_format/c1visualizer/struct.py
index 991564eff4..21036da213 100644
--- a/tools/checker/file_format/c1visualizer/struct.py
+++ b/tools/checker/file_format/c1visualizer/struct.py
@@ -12,14 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from common.logger import Logger
-from common.mixins import PrintableMixin
+from common.immutables import ImmutableDict
+from common.logger import Logger
+from common.mixins import PrintableMixin
class C1visualizerFile(PrintableMixin):
def __init__(self, fileName):
self.fileName = fileName
self.passes = []
+ self.instructionSetFeatures = ImmutableDict()
+
+ def setISAFeatures(self, features):
+ self.instructionSetFeatures = ImmutableDict(features)
def addPass(self, new_pass):
self.passes.append(new_pass)
@@ -32,7 +37,8 @@ class C1visualizerFile(PrintableMixin):
def __eq__(self, other):
return isinstance(other, self.__class__) \
- and self.passes == other.passes
+ and self.passes == other.passes \
+ and self.instructionSetFeatures == other.instructionSetFeatures
class C1visualizerPass(PrintableMixin):
diff --git a/tools/checker/file_format/c1visualizer/test.py b/tools/checker/file_format/c1visualizer/test.py
index 812a4cf9ce..11a6f0ef73 100644
--- a/tools/checker/file_format/c1visualizer/test.py
+++ b/tools/checker/file_format/c1visualizer/test.py
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from common.immutables import ImmutableDict
from common.testing import ToUnicode
from file_format.c1visualizer.parser import ParseC1visualizerStream
from file_format.c1visualizer.struct import C1visualizerFile, C1visualizerPass
@@ -23,13 +24,16 @@ import unittest
class C1visualizerParser_Test(unittest.TestCase):
- def createFile(self, passList):
+ def createFile(self, data):
""" Creates an instance of CheckerFile from provided info.
- Data format: [ ( <case-name>, [ ( <text>, <assert-variant> ), ... ] ), ... ]
+ Data format: ( [ <isa-feature>, ... ],
+ [ ( <case-name>, [ ( <text>, <assert-variant> ), ... ] ), ... ]
+ )
"""
c1File = C1visualizerFile("<c1_file>")
- for passEntry in passList:
+ c1File.instructionSetFeatures = data[0]
+ for passEntry in data[1]:
passName = passEntry[0]
passBody = passEntry[1]
c1Pass = C1visualizerPass(c1File, passName, passBody, 0)
@@ -41,7 +45,7 @@ class C1visualizerParser_Test(unittest.TestCase):
return self.assertEqual(expectedFile, actualFile)
def test_EmptyFile(self):
- self.assertParsesTo("", [])
+ self.assertParsesTo("", (ImmutableDict(), []))
def test_SingleGroup(self):
self.assertParsesTo(
@@ -55,7 +59,9 @@ class C1visualizerParser_Test(unittest.TestCase):
bar
end_cfg
""",
- [ ( "MyMethod pass1", [ "foo", "bar" ] ) ])
+ ( ImmutableDict(), [
+ ( "MyMethod pass1", [ "foo", "bar" ] )
+ ]))
def test_MultipleGroups(self):
self.assertParsesTo(
@@ -76,8 +82,10 @@ class C1visualizerParser_Test(unittest.TestCase):
def
end_cfg
""",
- [ ( "MyMethod1 pass1", [ "foo", "bar" ] ),
- ( "MyMethod1 pass2", [ "abc", "def" ] ) ])
+ ( ImmutableDict(), [
+ ( "MyMethod1 pass1", [ "foo", "bar" ] ),
+ ( "MyMethod1 pass2", [ "abc", "def" ] )
+ ]))
self.assertParsesTo(
"""
begin_compilation
@@ -101,5 +109,39 @@ class C1visualizerParser_Test(unittest.TestCase):
def
end_cfg
""",
- [ ( "MyMethod1 pass1", [ "foo", "bar" ] ),
- ( "MyMethod2 pass2", [ "abc", "def" ] ) ])
+ ( ImmutableDict(), [
+ ( "MyMethod1 pass1", [ "foo", "bar" ] ),
+ ( "MyMethod2 pass2", [ "abc", "def" ] )
+ ]))
+
+ def test_InstructionSetFeatures(self):
+ self.assertParsesTo(
+ """
+ begin_compilation
+ name "isa_features:feature1,-feature2"
+ method "isa_features:feature1,-feature2"
+ date 1234
+ end_compilation
+ """,
+ ( ImmutableDict({"feature1": True, "feature2": False}), []))
+ self.assertParsesTo(
+ """
+ begin_compilation
+ name "isa_features:feature1,-feature2"
+ method "isa_features:feature1,-feature2"
+ date 1234
+ end_compilation
+ begin_compilation
+ name "xyz1"
+ method "MyMethod1"
+ date 1234
+ end_compilation
+ begin_cfg
+ name "pass1"
+ foo
+ bar
+ end_cfg
+ """,
+ ( ImmutableDict({"feature1": True, "feature2": False}), [
+ ( "MyMethod1 pass1", [ "foo", "bar" ] )
+ ]))
diff --git a/tools/checker/match/file.py b/tools/checker/match/file.py
index 37d019593c..5c6aed75d5 100644
--- a/tools/checker/match/file.py
+++ b/tools/checker/match/file.py
@@ -311,14 +311,15 @@ class ExecutionState(object):
self.lastVariant = variant
-def MatchTestCase(testCase, c1Pass):
+def MatchTestCase(testCase, c1Pass, instructionSetFeatures):
""" Runs a test case against a C1visualizer graph dump.
Raises MatchFailedException when a statement cannot be satisfied.
"""
assert testCase.name == c1Pass.name
- state = ExecutionState(c1Pass)
+ initialVariables = {"ISA_FEATURES": instructionSetFeatures}
+ state = ExecutionState(c1Pass, initialVariables)
testStatements = testCase.statements + [ None ]
for statement in testStatements:
state.handle(statement)
@@ -342,7 +343,7 @@ def MatchFiles(checkerFile, c1File, targetArch, debuggableMode):
Logger.startTest(testCase.name)
try:
- MatchTestCase(testCase, c1Pass)
+ MatchTestCase(testCase, c1Pass, c1File.instructionSetFeatures)
Logger.testPassed()
except MatchFailedException as e:
lineNo = c1Pass.startLineNo + e.lineNo
diff --git a/tools/checker/match/line.py b/tools/checker/match/line.py
index 49b55dede2..91450a56f7 100644
--- a/tools/checker/match/line.py
+++ b/tools/checker/match/line.py
@@ -111,6 +111,7 @@ def getEvalText(expression, variables, pos):
def EvaluateLine(checkerLine, variables):
assert checkerLine.isEvalContentStatement()
+ hasIsaFeature = lambda feature: variables["ISA_FEATURES"].get(feature, False)
eval_string = "".join(map(lambda expr: getEvalText(expr, variables, checkerLine),
checkerLine.expressions))
return eval(eval_string)
diff --git a/tools/checker/match/test.py b/tools/checker/match/test.py
index 01724f001b..69e515f3d2 100644
--- a/tools/checker/match/test.py
+++ b/tools/checker/match/test.py
@@ -103,12 +103,27 @@ class MatchLines_Test(unittest.TestCase):
class MatchFiles_Test(unittest.TestCase):
- def assertMatches(self, checkerString, c1String):
+ def assertMatches(self, checkerString, c1String, instructionSetFeatures=None):
checkerString = \
"""
/// CHECK-START: MyMethod MyPass
""" + checkerString
- c1String = \
+ featureString = ""
+ if instructionSetFeatures:
+ joinedFeatures = ",".join(map(lambda feature: feature
+ if instructionSetFeatures[feature]
+ else "-" + feature,
+ instructionSetFeatures))
+
+ featureString = \
+ """
+ begin_compilation
+ name "isa_features:%s"
+ method "isa_features:%s"
+ date 1234
+ end_compilation
+ """ % (joinedFeatures, joinedFeatures)
+ c1String = featureString + \
"""
begin_compilation
name "MyMethod"
@@ -125,11 +140,11 @@ class MatchFiles_Test(unittest.TestCase):
c1File = ParseC1visualizerStream("<c1-file>", io.StringIO(ToUnicode(c1String)))
assert len(checkerFile.testCases) == 1
assert len(c1File.passes) == 1
- MatchTestCase(checkerFile.testCases[0], c1File.passes[0])
+ MatchTestCase(checkerFile.testCases[0], c1File.passes[0], c1File.instructionSetFeatures)
- def assertDoesNotMatch(self, checkerString, c1String):
+ def assertDoesNotMatch(self, checkerString, c1String, instructionSetFeatures=None):
with self.assertRaises(MatchFailedException):
- self.assertMatches(checkerString, c1String)
+ self.assertMatches(checkerString, c1String, instructionSetFeatures)
def assertBadStructure(self, checkerString, c1String):
with self.assertRaises(BadStructureException):
@@ -915,4 +930,39 @@ class MatchFiles_Test(unittest.TestCase):
""",
"""
foo
- """) \ No newline at end of file
+ """)
+
+ def test_hasIsaFeature(self):
+ self.assertMatches(
+ """
+ /// CHECK-EVAL: hasIsaFeature('feature1') and not hasIsaFeature('feature2')
+ """,
+ """
+ foo
+ """,
+ ImmutableDict({"feature1": True})
+ )
+ self.assertDoesNotMatch(
+ """
+ /// CHECK-EVAL: not hasIsaFeature('feature1')
+ """,
+ """
+ foo
+ """,
+ ImmutableDict({"feature1": True})
+ )
+ self.assertMatches(
+ """
+ /// CHECK-IF: hasIsaFeature('feature2')
+ /// CHECK: bar1
+ /// CHECK-ELSE:
+ /// CHECK: bar2
+ /// CHECK-FI:
+ """,
+ """
+ foo
+ bar1
+ """,
+ ImmutableDict({"feature1": False, "feature2": True})
+ )
+