• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python2.7
2
3"""A script to generate FileCheck statements for 'opt' analysis tests.
4
5This script is a utility to update LLVM opt analysis test cases with new
6FileCheck patterns. It can either update all of the tests in the file or
7a single test function.
8
9Example usage:
10$ update_analyze_test_checks.py --opt=../bin/opt test/foo.ll
11
12Workflow:
131. Make a compiler patch that requires updating some number of FileCheck lines
14   in regression test files.
152. Save the patch and revert it from your local work area.
163. Update the RUN-lines in the affected regression tests to look canonical.
17   Example: "; RUN: opt < %s -analyze -cost-model -S | FileCheck %s"
184. Refresh the FileCheck lines for either the entire file or select functions by
19   running this script.
205. Commit the fresh baseline of checks.
216. Apply your patch from step 1 and rebuild your local binaries.
227. Re-run this script on affected regression tests.
238. Check the diffs to ensure the script has done something reasonable.
249. Submit a patch including the regression test diffs for review.
25
26A common pattern is to have the script insert complete checking of every
27instruction. Then, edit it down to only check the relevant instructions.
28The script is designed to make adding checks to a test case fast, it is *not*
29designed to be authoratitive about what constitutes a good test!
30"""
31
32import argparse
33import itertools
34import os         # Used to advertise this file's name ("autogenerated_note").
35import string
36import subprocess
37import sys
38import tempfile
39import re
40
41from UpdateTestChecks import common
42
43ADVERT = '; NOTE: Assertions have been autogenerated by '
44
45# RegEx: this is where the magic happens.
46
47IR_FUNCTION_RE = re.compile('^\s*define\s+(?:internal\s+)?[^@]*@([\w-]+)\s*\(')
48
49
50
51
52
53def main():
54  from argparse import RawTextHelpFormatter
55  parser = argparse.ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
56  parser.add_argument('-v', '--verbose', action='store_true',
57                      help='Show verbose output')
58  parser.add_argument('--opt-binary', default='opt',
59                      help='The opt binary used to generate the test case')
60  parser.add_argument(
61      '--function', help='The function in the test file to update')
62  parser.add_argument('tests', nargs='+')
63  args = parser.parse_args()
64
65  autogenerated_note = (ADVERT + 'utils/' + os.path.basename(__file__))
66
67  opt_basename = os.path.basename(args.opt_binary)
68  if (opt_basename != "opt"):
69    print >>sys.stderr, 'ERROR: Unexpected opt name: ' + opt_basename
70    sys.exit(1)
71
72  for test in args.tests:
73    if args.verbose:
74      print >>sys.stderr, 'Scanning for RUN lines in test file: %s' % (test,)
75    with open(test) as f:
76      input_lines = [l.rstrip() for l in f]
77
78    raw_lines = [m.group(1)
79                 for m in [common.RUN_LINE_RE.match(l) for l in input_lines] if m]
80    run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
81    for l in raw_lines[1:]:
82      if run_lines[-1].endswith("\\"):
83        run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
84      else:
85        run_lines.append(l)
86
87    if args.verbose:
88      print >>sys.stderr, 'Found %d RUN lines:' % (len(run_lines),)
89      for l in run_lines:
90        print >>sys.stderr, '  RUN: ' + l
91
92    prefix_list = []
93    for l in run_lines:
94      (tool_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split('|', 1)])
95
96      if not tool_cmd.startswith(opt_basename + ' '):
97        print >>sys.stderr, 'WARNING: Skipping non-%s RUN line: %s' % (opt_basename, l)
98        continue
99
100      if not filecheck_cmd.startswith('FileCheck '):
101        print >>sys.stderr, 'WARNING: Skipping non-FileChecked RUN line: ' + l
102        continue
103
104      tool_cmd_args = tool_cmd[len(opt_basename):].strip()
105      tool_cmd_args = tool_cmd_args.replace('< %s', '').replace('%s', '').strip()
106
107      check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
108                               for item in m.group(1).split(',')]
109      if not check_prefixes:
110        check_prefixes = ['CHECK']
111
112      # FIXME: We should use multiple check prefixes to common check lines. For
113      # now, we just ignore all but the last.
114      prefix_list.append((check_prefixes, tool_cmd_args))
115
116    func_dict = {}
117    for prefixes, _ in prefix_list:
118      for prefix in prefixes:
119        func_dict.update({prefix: dict()})
120    for prefixes, opt_args in prefix_list:
121      if args.verbose:
122        print >>sys.stderr, 'Extracted opt cmd: ' + opt_basename + ' ' + opt_args
123        print >>sys.stderr, 'Extracted FileCheck prefixes: ' + str(prefixes)
124
125      raw_tool_outputs = common.invoke_tool(args.opt_binary, opt_args, test)
126
127      # Split analysis outputs by "Printing analysis " declarations.
128      for raw_tool_output in re.split(r'Printing analysis ', raw_tool_outputs):
129        common.build_function_body_dictionary(
130          common.ANALYZE_FUNCTION_RE, common.scrub_body, [],
131          raw_tool_output, prefixes, func_dict, args.verbose)
132
133    is_in_function = False
134    is_in_function_start = False
135    prefix_set = set([prefix for prefixes, _ in prefix_list for prefix in prefixes])
136    if args.verbose:
137      print >>sys.stderr, 'Rewriting FileCheck prefixes: %s' % (prefix_set,)
138    output_lines = []
139    output_lines.append(autogenerated_note)
140
141    for input_line in input_lines:
142      if is_in_function_start:
143        if input_line == '':
144          continue
145        if input_line.lstrip().startswith(';'):
146          m = common.CHECK_RE.match(input_line)
147          if not m or m.group(1) not in prefix_set:
148            output_lines.append(input_line)
149            continue
150
151        # Print out the various check lines here.
152        common.add_analyze_checks(output_lines, ';', prefix_list, func_dict, func_name)
153        is_in_function_start = False
154
155      if is_in_function:
156        if common.should_add_line_to_output(input_line, prefix_set):
157          # This input line of the function body will go as-is into the output.
158          # Except make leading whitespace uniform: 2 spaces.
159          input_line = common.SCRUB_LEADING_WHITESPACE_RE.sub(r'  ', input_line)
160          output_lines.append(input_line)
161        else:
162          continue
163        if input_line.strip() == '}':
164          is_in_function = False
165        continue
166
167      # Discard any previous script advertising.
168      if input_line.startswith(ADVERT):
169        continue
170
171      # If it's outside a function, it just gets copied to the output.
172      output_lines.append(input_line)
173
174      m = IR_FUNCTION_RE.match(input_line)
175      if not m:
176        continue
177      func_name = m.group(1)
178      if args.function is not None and func_name != args.function:
179        # When filtering on a specific function, skip all others.
180        continue
181      is_in_function = is_in_function_start = True
182
183    if args.verbose:
184      print>>sys.stderr, 'Writing %d lines to %s...' % (len(output_lines), test)
185
186    with open(test, 'wb') as f:
187      f.writelines([l + '\n' for l in output_lines])
188
189
190if __name__ == '__main__':
191  main()
192