blob: ec026022fc2f702e4bad45dd2e6d9c0d85088397 [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')
David Bolvansky7169ea32019-08-07 14:44:50 +000065 parser.add_argument('-u', '--update-only', action='store_true',
Philip Reames9bf59382019-08-05 18:25:08 +000066 help='Only update test if it was already autogened')
David Greenea14ffc72019-10-07 14:37:20 +000067 parser.add_argument('-p', '--preserve-names', action='store_true',
68 help='Do not scrub IR names')
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000069 parser.add_argument('tests', nargs='+')
70 args = parser.parse_args()
71
David Bolvansky7169ea32019-08-07 14:44:50 +000072 script_name = os.path.basename(__file__)
73 autogenerated_note = (ADVERT + 'utils/' + script_name)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000074
Sanjay Patelcae64a02017-06-12 17:44:30 +000075 opt_basename = os.path.basename(args.opt_binary)
Fangrui Song91ab86f2019-05-12 04:55:09 +000076 if not re.match(r'^opt(-\d+)?$', opt_basename):
David Bolvansky7169ea32019-08-07 14:44:50 +000077 common.error('Unexpected opt name: ' + opt_basename)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000078 sys.exit(1)
Fangrui Song91ab86f2019-05-12 04:55:09 +000079 opt_basename = 'opt'
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000080
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000081 for test in args.tests:
82 if not glob.glob(test):
Simon Pilgrim34cccf02019-09-27 10:04:16 +000083 common.warn("Test file pattern '%s' was not found. Ignoring it." % (test,))
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000084 continue
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000085
Simon Pilgrim34cccf02019-09-27 10:04:16 +000086 # On Windows we must expand the patterns ourselves.
87 test_paths = [test for pattern in args.tests for test in glob.glob(pattern)]
Simon Pilgrimf509fe42019-03-05 10:44:37 +000088 for test in test_paths:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000089 if args.verbose:
Fangrui Song1eb84c42019-08-06 09:44:18 +000090 print('Scanning for RUN lines in test file: ' + test, file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000091 with open(test) as f:
92 input_lines = [l.rstrip() for l in f]
93
David Bolvansky7169ea32019-08-07 14:44:50 +000094 first_line = input_lines[0] if input_lines else ""
95 if 'autogenerated' in first_line and script_name not in first_line:
96 common.warn("Skipping test which wasn't autogenerated by " + script_name, test)
97 continue
98
Philip Reames9bf59382019-08-05 18:25:08 +000099 if args.update_only:
David Bolvansky7169ea32019-08-07 14:44:50 +0000100 if not first_line or 'autogenerated' not in first_line:
101 common.warn("Skipping test which isn't autogenerated: " + test)
Fangrui Songd24e6d72019-08-06 09:42:00 +0000102 continue
Philip Reames9bf59382019-08-05 18:25:08 +0000103
Bryant Wong291264b2016-12-29 19:32:34 +0000104 raw_lines = [m.group(1)
Fangrui Songee4e2e72018-01-30 00:40:05 +0000105 for m in [common.RUN_LINE_RE.match(l) for l in input_lines] if m]
Bryant Wong291264b2016-12-29 19:32:34 +0000106 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
107 for l in raw_lines[1:]:
Fangrui Songd24e6d72019-08-06 09:42:00 +0000108 if run_lines[-1].endswith('\\'):
109 run_lines[-1] = run_lines[-1].rstrip('\\') + ' ' + l
Bryant Wong507256b2016-12-29 20:05:51 +0000110 else:
111 run_lines.append(l)
Bryant Wong291264b2016-12-29 19:32:34 +0000112
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000113 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000114 print('Found %d RUN lines:' % (len(run_lines),), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000115 for l in run_lines:
Serge Guelton4a274782019-01-03 14:11:33 +0000116 print(' RUN: ' + l, file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000117
118 prefix_list = []
119 for l in run_lines:
Philip Reames9bf59382019-08-05 18:25:08 +0000120 if '|' not in l:
David Bolvansky7169ea32019-08-07 14:44:50 +0000121 common.warn('Skipping unparseable RUN line: ' + l)
Philip Reames9bf59382019-08-05 18:25:08 +0000122 continue
Fangrui Songd24e6d72019-08-06 09:42:00 +0000123
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000124 (tool_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split('|', 1)])
David Bolvansky45be5e42019-07-29 17:41:00 +0000125 common.verify_filecheck_prefixes(filecheck_cmd)
Sanjay Patelcae64a02017-06-12 17:44:30 +0000126 if not tool_cmd.startswith(opt_basename + ' '):
David Bolvansky7169ea32019-08-07 14:44:50 +0000127 common.warn('Skipping non-%s RUN line: %s' % (opt_basename, l))
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000128 continue
129
130 if not filecheck_cmd.startswith('FileCheck '):
David Bolvansky7169ea32019-08-07 14:44:50 +0000131 common.warn('Skipping non-FileChecked RUN line: ' + l)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000132 continue
133
Sanjay Patelcae64a02017-06-12 17:44:30 +0000134 tool_cmd_args = tool_cmd[len(opt_basename):].strip()
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000135 tool_cmd_args = tool_cmd_args.replace('< %s', '').replace('%s', '').strip()
136
Fangrui Songee4e2e72018-01-30 00:40:05 +0000137 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
Nikolai Bozhenov33ee40e2017-01-14 09:39:35 +0000138 for item in m.group(1).split(',')]
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000139 if not check_prefixes:
140 check_prefixes = ['CHECK']
141
142 # FIXME: We should use multiple check prefixes to common check lines. For
143 # now, we just ignore all but the last.
144 prefix_list.append((check_prefixes, tool_cmd_args))
145
146 func_dict = {}
147 for prefixes, _ in prefix_list:
148 for prefix in prefixes:
149 func_dict.update({prefix: dict()})
Sanjay Patelcae64a02017-06-12 17:44:30 +0000150 for prefixes, opt_args in prefix_list:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000151 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000152 print('Extracted opt cmd: ' + opt_basename + ' ' + opt_args, file=sys.stderr)
153 print('Extracted FileCheck prefixes: ' + str(prefixes), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000154
Fangrui Songee4e2e72018-01-30 00:40:05 +0000155 raw_tool_output = common.invoke_tool(args.opt_binary, opt_args, test)
156 common.build_function_body_dictionary(
Fangrui Song4f0f4262018-02-10 05:01:33 +0000157 common.OPT_FUNCTION_RE, common.scrub_body, [],
158 raw_tool_output, prefixes, func_dict, args.verbose)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000159
160 is_in_function = False
161 is_in_function_start = False
162 prefix_set = set([prefix for prefixes, _ in prefix_list for prefix in prefixes])
163 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000164 print('Rewriting FileCheck prefixes: %s' % (prefix_set,), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000165 output_lines = []
166 output_lines.append(autogenerated_note)
167
168 for input_line in input_lines:
169 if is_in_function_start:
170 if input_line == '':
171 continue
172 if input_line.lstrip().startswith(';'):
Fangrui Songee4e2e72018-01-30 00:40:05 +0000173 m = common.CHECK_RE.match(input_line)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000174 if not m or m.group(1) not in prefix_set:
175 output_lines.append(input_line)
176 continue
177
178 # Print out the various check lines here.
David Greenea14ffc72019-10-07 14:37:20 +0000179 common.add_ir_checks(output_lines, ';', prefix_list, func_dict,
180 func_name, args.preserve_names)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000181 is_in_function_start = False
182
183 if is_in_function:
Fangrui Songee4e2e72018-01-30 00:40:05 +0000184 if common.should_add_line_to_output(input_line, prefix_set):
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000185 # This input line of the function body will go as-is into the output.
Sanjay Pateld8592712016-03-27 20:43:02 +0000186 # Except make leading whitespace uniform: 2 spaces.
Fangrui Songee4e2e72018-01-30 00:40:05 +0000187 input_line = common.SCRUB_LEADING_WHITESPACE_RE.sub(r' ', input_line)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000188 output_lines.append(input_line)
189 else:
190 continue
191 if input_line.strip() == '}':
192 is_in_function = False
193 continue
194
Sanjay Patel16be4df92016-04-05 19:50:21 +0000195 # Discard any previous script advertising.
196 if input_line.startswith(ADVERT):
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000197 continue
198
199 # If it's outside a function, it just gets copied to the output.
200 output_lines.append(input_line)
201
202 m = IR_FUNCTION_RE.match(input_line)
203 if not m:
204 continue
Fangrui Songee4e2e72018-01-30 00:40:05 +0000205 func_name = m.group(1)
206 if args.function is not None and func_name != args.function:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000207 # When filtering on a specific function, skip all others.
208 continue
209 is_in_function = is_in_function_start = True
210
211 if args.verbose:
Serge Guelton4a274782019-01-03 14:11:33 +0000212 print('Writing %d lines to %s...' % (len(output_lines), test), file=sys.stderr)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000213
214 with open(test, 'wb') as f:
Simon Pilgrimd82bd4d2019-01-30 16:15:59 +0000215 f.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines])
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000216
217
218if __name__ == '__main__':
219 main()