diff options
author | 2020-02-12 16:18:50 +0000 | |
---|---|---|
committer | 2020-07-22 10:44:31 +0000 | |
commit | 40b0614be3296e163654c4e293793d00bcf36a5a (patch) | |
tree | 5dcd9dd7d93d14b83c82cb90674df9353280189d | |
parent | 52fe49e87902fb231201874f52c4993e6fe611e9 (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/README | 20 | ||||
-rw-r--r-- | tools/checker/common/logger.py | 8 | ||||
-rw-r--r-- | tools/checker/file_format/c1visualizer/parser.py | 24 | ||||
-rw-r--r-- | tools/checker/file_format/c1visualizer/struct.py | 12 | ||||
-rw-r--r-- | tools/checker/file_format/c1visualizer/test.py | 60 | ||||
-rw-r--r-- | tools/checker/match/file.py | 7 | ||||
-rw-r--r-- | tools/checker/match/line.py | 1 | ||||
-rw-r--r-- | tools/checker/match/test.py | 62 |
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}) + ) + |