blob: 92ce0535a109f33de660e4a35a0affe18ff6c473 [file] [log] [blame]
Serge Guelton16228bc2019-01-03 15:44:24 +00001#!/usr/bin/env python
Sanjay Patelfff7a3d2016-03-24 23:19:26 +00002
Sanjay Patelcae64a02017-06-12 17:44:30 +00003"""A script to generate FileCheck statements for 'opt' regression tests.
Sanjay Patelfff7a3d2016-03-24 23:19:26 +00004
Sanjay Patelcae64a02017-06-12 17:44:30 +00005This script is a utility to update LLVM opt test cases with new
Sanjay Patelfff7a3d2016-03-24 23:19:26 +00006FileCheck patterns. It can either update all of the tests in the file or
7a single test function.
Sanjay Patel40641582016-04-05 18:00:47 +00008
9Example usage:
Sanjay Patelcae64a02017-06-12 17:44:30 +000010$ update_test_checks.py --opt=../bin/opt test/foo.ll
Sanjay Patel40641582016-04-05 18:00:47 +000011
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 -instcombine -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!
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000030"""
31
Serge Guelton4a274782019-01-03 14:11:33 +000032from __future__ import print_function
33
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000034import argparse
Simon Pilgrimf509fe42019-03-05 10:44:37 +000035import glob
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000036import itertools
37import os # Used to advertise this file's name ("autogenerated_note").
38import string
39import subprocess
40import sys
41import tempfile
42import re
43
Fangrui Songee4e2e72018-01-30 00:40:05 +000044from UpdateTestChecks import common
45
Sanjay Patel16be4df92016-04-05 19:50:21 +000046ADVERT = '; NOTE: Assertions have been autogenerated by '
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000047
48# RegEx: this is where the magic happens.
49
Sanjay Patele54e6f52016-03-25 17:00:12 +000050IR_FUNCTION_RE = re.compile('^\s*define\s+(?:internal\s+)?[^@]*@([\w-]+)\s*\(')
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000051
52
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000053
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000054
55
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000056def main():
Sanjay Patel40641582016-04-05 18:00:47 +000057 from argparse import RawTextHelpFormatter
58 parser = argparse.ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000059 parser.add_argument('-v', '--verbose', action='store_true',
60 help='Show verbose output')
Sanjay Patelcae64a02017-06-12 17:44:30 +000061 parser.add_argument('--opt-binary', default='opt',
62 help='The opt binary used to generate the test case')
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000063 parser.add_argument(
64 '--function', help='The function in the test file to update')
65 parser.add_argument('tests', nargs='+')
66 args = parser.parse_args()
67
Sanjay Patel16be4df92016-04-05 19:50:21 +000068 autogenerated_note = (ADVERT + 'utils/' + os.path.basename(__file__))
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000069
Sanjay Patelcae64a02017-06-12 17:44:30 +000070 opt_basename = os.path.basename(args.opt_binary)
Fangrui Song91ab86f2019-05-12 04:55:09 +000071 if not re.match(r'^opt(-\d+)?$', opt_basename):
Serge Guelton4a274782019-01-03 14:11:33 +000072 print('ERROR: Unexpected opt name: ' + opt_basename, file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000073 sys.exit(1)
Fangrui Song91ab86f2019-05-12 04:55:09 +000074 opt_basename = 'opt'
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000075
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000076 test_paths = []
77 for test in args.tests:
78 if not glob.glob(test):
79 print('WARNING: Test file \'%s\' was not found. Ignoring it.' % (test,), file=sys.stderr)
80 continue
81 test_paths.append(test)
82
Simon Pilgrimf509fe42019-03-05 10:44:37 +000083 for test in test_paths:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000084 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +000085 print('Scanning for RUN lines in test file: %s' % (test,), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000086 with open(test) as f:
87 input_lines = [l.rstrip() for l in f]
88
Bryant Wong291264b2016-12-29 19:32:34 +000089 raw_lines = [m.group(1)
Fangrui Songee4e2e72018-01-30 00:40:05 +000090 for m in [common.RUN_LINE_RE.match(l) for l in input_lines] if m]
Bryant Wong291264b2016-12-29 19:32:34 +000091 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
92 for l in raw_lines[1:]:
Bryant Wong507256b2016-12-29 20:05:51 +000093 if run_lines[-1].endswith("\\"):
94 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
95 else:
96 run_lines.append(l)
Bryant Wong291264b2016-12-29 19:32:34 +000097
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000098 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +000099 print('Found %d RUN lines:' % (len(run_lines),), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000100 for l in run_lines:
Serge Guelton4a274782019-01-03 14:11:33 +0000101 print(' RUN: ' + l, file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000102
103 prefix_list = []
104 for l in run_lines:
105 (tool_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split('|', 1)])
106
Sanjay Patelcae64a02017-06-12 17:44:30 +0000107 if not tool_cmd.startswith(opt_basename + ' '):
Serge Guelton4a274782019-01-03 14:11:33 +0000108 print('WARNING: Skipping non-%s RUN line: %s' % (opt_basename, l), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000109 continue
110
111 if not filecheck_cmd.startswith('FileCheck '):
Serge Guelton4a274782019-01-03 14:11:33 +0000112 print('WARNING: Skipping non-FileChecked RUN line: ' + l, file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000113 continue
114
Sanjay Patelcae64a02017-06-12 17:44:30 +0000115 tool_cmd_args = tool_cmd[len(opt_basename):].strip()
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000116 tool_cmd_args = tool_cmd_args.replace('< %s', '').replace('%s', '').strip()
117
Fangrui Songee4e2e72018-01-30 00:40:05 +0000118 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
Nikolai Bozhenov33ee40e2017-01-14 09:39:35 +0000119 for item in m.group(1).split(',')]
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000120 if not check_prefixes:
121 check_prefixes = ['CHECK']
122
123 # FIXME: We should use multiple check prefixes to common check lines. For
124 # now, we just ignore all but the last.
125 prefix_list.append((check_prefixes, tool_cmd_args))
126
127 func_dict = {}
128 for prefixes, _ in prefix_list:
129 for prefix in prefixes:
130 func_dict.update({prefix: dict()})
Sanjay Patelcae64a02017-06-12 17:44:30 +0000131 for prefixes, opt_args in prefix_list:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000132 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000133 print('Extracted opt cmd: ' + opt_basename + ' ' + opt_args, file=sys.stderr)
134 print('Extracted FileCheck prefixes: ' + str(prefixes), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000135
Fangrui Songee4e2e72018-01-30 00:40:05 +0000136 raw_tool_output = common.invoke_tool(args.opt_binary, opt_args, test)
137 common.build_function_body_dictionary(
Fangrui Song4f0f4262018-02-10 05:01:33 +0000138 common.OPT_FUNCTION_RE, common.scrub_body, [],
139 raw_tool_output, prefixes, func_dict, args.verbose)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000140
141 is_in_function = False
142 is_in_function_start = False
143 prefix_set = set([prefix for prefixes, _ in prefix_list for prefix in prefixes])
144 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000145 print('Rewriting FileCheck prefixes: %s' % (prefix_set,), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000146 output_lines = []
147 output_lines.append(autogenerated_note)
148
149 for input_line in input_lines:
150 if is_in_function_start:
151 if input_line == '':
152 continue
153 if input_line.lstrip().startswith(';'):
Fangrui Songee4e2e72018-01-30 00:40:05 +0000154 m = common.CHECK_RE.match(input_line)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000155 if not m or m.group(1) not in prefix_set:
156 output_lines.append(input_line)
157 continue
158
159 # Print out the various check lines here.
Simon Pilgrimabba0482018-04-05 09:30:42 +0000160 common.add_ir_checks(output_lines, ';', prefix_list, func_dict, func_name)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000161 is_in_function_start = False
162
163 if is_in_function:
Fangrui Songee4e2e72018-01-30 00:40:05 +0000164 if common.should_add_line_to_output(input_line, prefix_set):
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000165 # This input line of the function body will go as-is into the output.
Sanjay Pateld8592712016-03-27 20:43:02 +0000166 # Except make leading whitespace uniform: 2 spaces.
Fangrui Songee4e2e72018-01-30 00:40:05 +0000167 input_line = common.SCRUB_LEADING_WHITESPACE_RE.sub(r' ', input_line)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000168 output_lines.append(input_line)
169 else:
170 continue
171 if input_line.strip() == '}':
172 is_in_function = False
173 continue
174
Sanjay Patel16be4df92016-04-05 19:50:21 +0000175 # Discard any previous script advertising.
176 if input_line.startswith(ADVERT):
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000177 continue
178
179 # If it's outside a function, it just gets copied to the output.
180 output_lines.append(input_line)
181
182 m = IR_FUNCTION_RE.match(input_line)
183 if not m:
184 continue
Fangrui Songee4e2e72018-01-30 00:40:05 +0000185 func_name = m.group(1)
186 if args.function is not None and func_name != args.function:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000187 # When filtering on a specific function, skip all others.
188 continue
189 is_in_function = is_in_function_start = True
190
191 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000192 print('Writing %d lines to %s...' % (len(output_lines), test), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000193
194 with open(test, 'wb') as f:
Simon Pilgrimd82bd4d2019-01-30 16:15:59 +0000195 f.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines])
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000196
197
198if __name__ == '__main__':
199 main()