lin
2025-07-31 065ea569db06206874bbfa18eb25ff6121aec09b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# Copyright (C) 2014 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.
 
from collections                      import namedtuple
from common.immutables                import ImmutableDict
from common.logger                    import Logger
from file_format.c1visualizer.struct  import C1visualizerFile, C1visualizerPass
from file_format.checker.struct       import CheckerFile, TestCase, TestAssertion
from match.line                       import MatchLines, EvaluateLine
 
MatchScope = namedtuple("MatchScope", ["start", "end"])
MatchInfo = namedtuple("MatchInfo", ["scope", "variables"])
 
class MatchFailedException(Exception):
  def __init__(self, assertion, lineNo, variables):
    self.assertion = assertion
    self.lineNo = lineNo
    self.variables = variables
 
def splitIntoGroups(assertions):
  """ Breaks up a list of assertions, grouping instructions which should be
      tested in the same scope (consecutive DAG and NOT instructions).
   """
  splitAssertions = []
  lastVariant = None
  for assertion in assertions:
    if (assertion.variant == lastVariant and
        assertion.variant in [TestAssertion.Variant.DAG, TestAssertion.Variant.Not]):
      splitAssertions[-1].append(assertion)
    else:
      splitAssertions.append([assertion])
      lastVariant = assertion.variant
  return splitAssertions
 
def findMatchingLine(assertion, c1Pass, scope, variables, excludeLines=[]):
  """ Finds the first line in `c1Pass` which matches `assertion`.
 
  Scan only lines numbered between `scope.start` and `scope.end` and not on the
  `excludeLines` list.
 
  Returns the index of the `c1Pass` line matching the assertion and variables
  values after the match.
 
  Raises MatchFailedException if no such `c1Pass` line can be found.
  """
  for i in range(scope.start, scope.end):
    if i in excludeLines: continue
    newVariables = MatchLines(assertion, c1Pass.body[i], variables)
    if newVariables is not None:
      return MatchInfo(MatchScope(i, i), newVariables)
  raise MatchFailedException(assertion, scope.start, variables)
 
def matchDagGroup(assertions, c1Pass, scope, variables):
  """ Attempts to find matching `c1Pass` lines for a group of DAG assertions.
 
  Assertions are matched in the list order and variable values propagated. Only
  lines in `scope` are scanned and each line can only match one assertion.
 
  Returns the range of `c1Pass` lines covered by this group (min/max of matching
  line numbers) and the variable values after the match of the last assertion.
 
  Raises MatchFailedException when an assertion cannot be satisfied.
  """
  matchedLines = []
  for assertion in assertions:
    assert assertion.variant == TestAssertion.Variant.DAG
    match = findMatchingLine(assertion, c1Pass, scope, variables, matchedLines)
    variables = match.variables
    assert match.scope.start == match.scope.end
    assert match.scope.start not in matchedLines
    matchedLines.append(match.scope.start)
  return MatchInfo(MatchScope(min(matchedLines), max(matchedLines)), variables)
 
def testNotGroup(assertions, c1Pass, scope, variables):
  """ Verifies that none of the given NOT assertions matches a line inside
      the given `scope` of `c1Pass` lines.
 
  Raises MatchFailedException if an assertion matches a line in the scope.
  """
  for i in range(scope.start, scope.end):
    line = c1Pass.body[i]
    for assertion in assertions:
      assert assertion.variant == TestAssertion.Variant.Not
      if MatchLines(assertion, line, variables) is not None:
        raise MatchFailedException(assertion, i, variables)
 
def testEvalGroup(assertions, scope, variables):
  for assertion in assertions:
    if not EvaluateLine(assertion, variables):
      raise MatchFailedException(assertion, scope.start, variables)
 
def MatchTestCase(testCase, c1Pass):
  """ Runs a test case against a C1visualizer graph dump.
 
  Raises MatchFailedException when an assertion cannot be satisfied.
  """
  assert testCase.name == c1Pass.name
 
  matchFrom = 0
  variables = ImmutableDict()
  c1Length = len(c1Pass.body)
 
  # NOT assertions are verified retrospectively, once the scope is known.
  pendingNotAssertions = None
 
  # Prepare assertions by grouping those that are verified in the same scope.
  # We also add None as an EOF assertion that will set scope for NOTs.
  assertionGroups = splitIntoGroups(testCase.assertions)
  assertionGroups.append(None)
 
  for assertionGroup in assertionGroups:
    if assertionGroup is None:
      # EOF marker always matches the last+1 line of c1Pass.
      match = MatchInfo(MatchScope(c1Length, c1Length), None)
    elif assertionGroup[0].variant == TestAssertion.Variant.Not:
      # NOT assertions will be tested together with the next group.
      assert not pendingNotAssertions
      pendingNotAssertions = assertionGroup
      continue
    elif assertionGroup[0].variant == TestAssertion.Variant.InOrder:
      # Single in-order assertion. Find the first line that matches.
      assert len(assertionGroup) == 1
      scope = MatchScope(matchFrom, c1Length)
      match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
    elif assertionGroup[0].variant == TestAssertion.Variant.NextLine:
      # Single next-line assertion. Test if the current line matches.
      assert len(assertionGroup) == 1
      scope = MatchScope(matchFrom, matchFrom + 1)
      match = findMatchingLine(assertionGroup[0], c1Pass, scope, variables)
    elif assertionGroup[0].variant == TestAssertion.Variant.DAG:
      # A group of DAG assertions. Match them all starting from the same point.
      scope = MatchScope(matchFrom, c1Length)
      match = matchDagGroup(assertionGroup, c1Pass, scope, variables)
    else:
      assert assertionGroup[0].variant == TestAssertion.Variant.Eval
      scope = MatchScope(matchFrom, c1Length)
      testEvalGroup(assertionGroup, scope, variables)
      continue
 
    if pendingNotAssertions:
      # Previous group were NOT assertions. Make sure they don't match any lines
      # in the [matchFrom, match.start) scope.
      scope = MatchScope(matchFrom, match.scope.start)
      testNotGroup(pendingNotAssertions, c1Pass, scope, variables)
      pendingNotAssertions = None
 
    # Update state.
    assert matchFrom <= match.scope.end
    matchFrom = match.scope.end + 1
    variables = match.variables
 
def MatchFiles(checkerFile, c1File, targetArch, debuggableMode):
  for testCase in checkerFile.testCases:
    if testCase.testArch not in [None, targetArch]:
      continue
    if testCase.forDebuggable != debuggableMode:
      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.
    c1Pass = c1File.findPass(testCase.name)
    if c1Pass is None:
      Logger.fail("Test case not found in the CFG file",
                  testCase.fileName, testCase.startLineNo, testCase.name)
 
    Logger.startTest(testCase.name)
    try:
      MatchTestCase(testCase, c1Pass)
      Logger.testPassed()
    except MatchFailedException as e:
      lineNo = c1Pass.startLineNo + e.lineNo
      if e.assertion.variant == TestAssertion.Variant.Not:
        msg = "NOT assertion matched line {}"
      else:
        msg = "Assertion could not be matched starting from line {}"
      msg = msg.format(lineNo)
      Logger.testFailed(msg, e.assertion, e.variables)