1#!/usr/bin/env python3
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 -passes='print<cost-model>' -disable-output 2>&1 | 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
32from __future__ import print_function
33
34import argparse
35import os         # Used to advertise this file's name ("autogenerated_note").
36import sys
37import re
38
39from UpdateTestChecks import common
40
41def main():
42  from argparse import RawTextHelpFormatter
43  parser = argparse.ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
44  parser.add_argument('--opt-binary', default='opt',
45                      help='The opt binary used to generate the test case')
46  parser.add_argument(
47      '--function', help='The function in the test file to update')
48  parser.add_argument('tests', nargs='+')
49  initial_args = common.parse_commandline_args(parser)
50
51  script_name = os.path.basename(__file__)
52
53  opt_basename = os.path.basename(initial_args.opt_binary)
54  if (opt_basename != "opt"):
55    common.error('Unexpected opt name: ' + opt_basename)
56    sys.exit(1)
57
58  for ti in common.itertests(initial_args.tests, parser,
59                             script_name='utils/' + script_name):
60    triple_in_ir = None
61    for l in ti.input_lines:
62      m = common.TRIPLE_IR_RE.match(l)
63      if m:
64        triple_in_ir = m.groups()[0]
65        break
66
67    prefix_list = []
68    for l in ti.run_lines:
69      if '|' not in l:
70        common.warn('Skipping unparseable RUN line: ' + l)
71        continue
72
73      (tool_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split('|', 1)])
74      common.verify_filecheck_prefixes(filecheck_cmd)
75
76      if not tool_cmd.startswith(opt_basename + ' '):
77        common.warn('WSkipping non-%s RUN line: %s' % (opt_basename, l))
78        continue
79
80      if not filecheck_cmd.startswith('FileCheck '):
81        common.warn('Skipping non-FileChecked RUN line: ' + l)
82        continue
83
84      tool_cmd_args = tool_cmd[len(opt_basename):].strip()
85      tool_cmd_args = tool_cmd_args.replace('< %s', '').replace('%s', '').strip()
86
87      check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
88                               for item in m.group(1).split(',')]
89      if not check_prefixes:
90        check_prefixes = ['CHECK']
91
92      # FIXME: We should use multiple check prefixes to common check lines. For
93      # now, we just ignore all but the last.
94      prefix_list.append((check_prefixes, tool_cmd_args))
95
96    builder = common.FunctionTestBuilder(
97      run_list = prefix_list,
98      flags = type('', (object,), {
99            'verbose': ti.args.verbose,
100            'filters': ti.args.filters,
101            'function_signature': False,
102            'check_attributes': False,
103            'replace_value_regex': []}),
104      scrubber_args = [],
105      path=ti.path)
106
107    for prefixes, opt_args in prefix_list:
108      common.debug('Extracted opt cmd:', opt_basename, opt_args, file=sys.stderr)
109      common.debug('Extracted FileCheck prefixes:', str(prefixes), file=sys.stderr)
110
111      raw_tool_outputs = common.invoke_tool(ti.args.opt_binary, opt_args, ti.path)
112
113      if re.search(r'Printing analysis ', raw_tool_outputs) is not None:
114        # Split analysis outputs by "Printing analysis " declarations.
115        for raw_tool_output in re.split(r'Printing analysis ', raw_tool_outputs):
116          builder.process_run_line(common.ANALYZE_FUNCTION_RE, common.scrub_body,
117                                  raw_tool_output, prefixes, False)
118      elif re.search(r'LV: Checking a loop in ', raw_tool_outputs) is not None:
119        # Split analysis outputs by "Printing analysis " declarations.
120        for raw_tool_output in re.split(r'LV: Checking a loop in ', raw_tool_outputs):
121          builder.process_run_line(common.LV_DEBUG_RE, common.scrub_body,
122                                  raw_tool_output, prefixes, False)
123      else:
124        common.warn('Don\'t know how to deal with this output')
125        continue
126
127      builder.processed_prefixes(prefixes)
128
129    func_dict = builder.finish_and_get_func_dict()
130    is_in_function = False
131    is_in_function_start = False
132    prefix_set = set([prefix for prefixes, _ in prefix_list for prefix in prefixes])
133    common.debug('Rewriting FileCheck prefixes:', str(prefix_set), file=sys.stderr)
134    output_lines = []
135
136    for input_info in ti.iterlines(output_lines):
137      input_line = input_info.line
138      args = input_info.args
139      if is_in_function_start:
140        if input_line == '':
141          continue
142        if input_line.lstrip().startswith(';'):
143          m = common.CHECK_RE.match(input_line)
144          if not m or m.group(1) not in prefix_set:
145            output_lines.append(input_line)
146            continue
147
148        # Print out the various check lines here.
149        common.add_analyze_checks(output_lines, ';', prefix_list, func_dict, func_name,
150                                  is_filtered=builder.is_filtered())
151        is_in_function_start = False
152
153      if is_in_function:
154        if common.should_add_line_to_output(input_line, prefix_set):
155          # This input line of the function body will go as-is into the output.
156          # Except make leading whitespace uniform: 2 spaces.
157          input_line = common.SCRUB_LEADING_WHITESPACE_RE.sub(r'  ', input_line)
158          output_lines.append(input_line)
159        else:
160          continue
161        if input_line.strip() == '}':
162          is_in_function = False
163        continue
164
165      # If it's outside a function, it just gets copied to the output.
166      output_lines.append(input_line)
167
168      m = common.IR_FUNCTION_RE.match(input_line)
169      if not m:
170        continue
171      func_name = m.group(1)
172      if ti.args.function is not None and func_name != ti.args.function:
173        # When filtering on a specific function, skip all others.
174        continue
175      is_in_function = is_in_function_start = True
176
177    common.debug('Writing %d lines to %s...' % (len(output_lines), ti.path))
178
179    with open(ti.path, 'wb') as f:
180      f.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines])
181
182
183if __name__ == '__main__':
184  main()
185