blob: 3fd8dd7dd7e93ec2be8c8c42c28ef7b58500c3ef [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 Patelcae64a02017-06-12 17:44:30 +000059 parser.add_argument('--opt-binary', default='opt',
60 help='The opt binary used to generate the test case')
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000061 parser.add_argument(
62 '--function', help='The function in the test file to update')
David Greenea14ffc72019-10-07 14:37:20 +000063 parser.add_argument('-p', '--preserve-names', action='store_true',
64 help='Do not scrub IR names')
Johannes Doerfert3598b812019-10-10 12:08:21 -050065 parser.add_argument('--function-signature', action='store_true',
66 help='Keep function signature information around for the check line')
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000067 parser.add_argument('tests', nargs='+')
Alex Richardson61873942019-11-20 13:19:48 +000068 args = common.parse_commandline_args(parser)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000069
David Bolvansky7169ea32019-08-07 14:44:50 +000070 script_name = os.path.basename(__file__)
71 autogenerated_note = (ADVERT + 'utils/' + script_name)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000072
Sanjay Patelcae64a02017-06-12 17:44:30 +000073 opt_basename = os.path.basename(args.opt_binary)
Fangrui Song91ab86f2019-05-12 04:55:09 +000074 if not re.match(r'^opt(-\d+)?$', opt_basename):
David Bolvansky7169ea32019-08-07 14:44:50 +000075 common.error('Unexpected opt name: ' + opt_basename)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000076 sys.exit(1)
Fangrui Song91ab86f2019-05-12 04:55:09 +000077 opt_basename = 'opt'
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000078
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000079 for test in args.tests:
80 if not glob.glob(test):
Simon Pilgrim34cccf02019-09-27 10:04:16 +000081 common.warn("Test file pattern '%s' was not found. Ignoring it." % (test,))
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000082 continue
David Bolvanskyfcffa7c2019-07-11 20:14:22 +000083
Simon Pilgrim34cccf02019-09-27 10:04:16 +000084 # On Windows we must expand the patterns ourselves.
85 test_paths = [test for pattern in args.tests for test in glob.glob(pattern)]
Simon Pilgrimf509fe42019-03-05 10:44:37 +000086 for test in test_paths:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +000087 with open(test) as f:
88 input_lines = [l.rstrip() for l in f]
89
David Bolvansky7169ea32019-08-07 14:44:50 +000090 first_line = input_lines[0] if input_lines else ""
91 if 'autogenerated' in first_line and script_name not in first_line:
92 common.warn("Skipping test which wasn't autogenerated by " + script_name, test)
93 continue
94
Philip Reames9bf59382019-08-05 18:25:08 +000095 if args.update_only:
David Bolvansky7169ea32019-08-07 14:44:50 +000096 if not first_line or 'autogenerated' not in first_line:
97 common.warn("Skipping test which isn't autogenerated: " + test)
Fangrui Songd24e6d72019-08-06 09:42:00 +000098 continue
Philip Reames9bf59382019-08-05 18:25:08 +000099
Alex Richardsond9542db2019-12-02 10:50:23 +0000100 run_lines = common.find_run_lines(test, input_lines)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000101 prefix_list = []
102 for l in run_lines:
Philip Reames9bf59382019-08-05 18:25:08 +0000103 if '|' not in l:
David Bolvansky7169ea32019-08-07 14:44:50 +0000104 common.warn('Skipping unparseable RUN line: ' + l)
Philip Reames9bf59382019-08-05 18:25:08 +0000105 continue
Fangrui Songd24e6d72019-08-06 09:42:00 +0000106
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000107 (tool_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split('|', 1)])
David Bolvansky45be5e42019-07-29 17:41:00 +0000108 common.verify_filecheck_prefixes(filecheck_cmd)
Sanjay Patelcae64a02017-06-12 17:44:30 +0000109 if not tool_cmd.startswith(opt_basename + ' '):
David Bolvansky7169ea32019-08-07 14:44:50 +0000110 common.warn('Skipping non-%s RUN line: %s' % (opt_basename, l))
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000111 continue
112
113 if not filecheck_cmd.startswith('FileCheck '):
David Bolvansky7169ea32019-08-07 14:44:50 +0000114 common.warn('Skipping non-FileChecked RUN line: ' + l)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000115 continue
116
Sanjay Patelcae64a02017-06-12 17:44:30 +0000117 tool_cmd_args = tool_cmd[len(opt_basename):].strip()
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000118 tool_cmd_args = tool_cmd_args.replace('< %s', '').replace('%s', '').strip()
119
Fangrui Songee4e2e72018-01-30 00:40:05 +0000120 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
Nikolai Bozhenov33ee40e2017-01-14 09:39:35 +0000121 for item in m.group(1).split(',')]
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000122 if not check_prefixes:
123 check_prefixes = ['CHECK']
124
125 # FIXME: We should use multiple check prefixes to common check lines. For
126 # now, we just ignore all but the last.
127 prefix_list.append((check_prefixes, tool_cmd_args))
128
129 func_dict = {}
130 for prefixes, _ in prefix_list:
131 for prefix in prefixes:
132 func_dict.update({prefix: dict()})
Sanjay Patelcae64a02017-06-12 17:44:30 +0000133 for prefixes, opt_args in prefix_list:
Alex Richardsond9542db2019-12-02 10:50:23 +0000134 common.debug('Extracted opt cmd: ' + opt_basename + ' ' + opt_args)
135 common.debug('Extracted FileCheck prefixes: ' + str(prefixes))
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000136
Fangrui Songee4e2e72018-01-30 00:40:05 +0000137 raw_tool_output = common.invoke_tool(args.opt_binary, opt_args, test)
138 common.build_function_body_dictionary(
Fangrui Song4f0f4262018-02-10 05:01:33 +0000139 common.OPT_FUNCTION_RE, common.scrub_body, [],
Johannes Doerfert3598b812019-10-10 12:08:21 -0500140 raw_tool_output, prefixes, func_dict, args.verbose,
141 args.function_signature)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000142
143 is_in_function = False
144 is_in_function_start = False
145 prefix_set = set([prefix for prefixes, _ in prefix_list for prefix in prefixes])
Alex Richardsond9542db2019-12-02 10:50:23 +0000146 common.debug('Rewriting FileCheck prefixes:', str(prefix_set))
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000147 output_lines = []
148 output_lines.append(autogenerated_note)
149
150 for input_line in input_lines:
151 if is_in_function_start:
152 if input_line == '':
153 continue
154 if input_line.lstrip().startswith(';'):
Fangrui Songee4e2e72018-01-30 00:40:05 +0000155 m = common.CHECK_RE.match(input_line)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000156 if not m or m.group(1) not in prefix_set:
157 output_lines.append(input_line)
158 continue
159
160 # Print out the various check lines here.
David Greenea14ffc72019-10-07 14:37:20 +0000161 common.add_ir_checks(output_lines, ';', prefix_list, func_dict,
Johannes Doerferte67f6472019-11-01 11:17:27 -0500162 func_name, args.preserve_names, args.function_signature)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000163 is_in_function_start = False
164
165 if is_in_function:
Fangrui Songee4e2e72018-01-30 00:40:05 +0000166 if common.should_add_line_to_output(input_line, prefix_set):
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000167 # This input line of the function body will go as-is into the output.
Sanjay Pateld8592712016-03-27 20:43:02 +0000168 # Except make leading whitespace uniform: 2 spaces.
Fangrui Songee4e2e72018-01-30 00:40:05 +0000169 input_line = common.SCRUB_LEADING_WHITESPACE_RE.sub(r' ', input_line)
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000170 output_lines.append(input_line)
171 else:
172 continue
173 if input_line.strip() == '}':
174 is_in_function = False
175 continue
176
Sanjay Patel16be4df92016-04-05 19:50:21 +0000177 # Discard any previous script advertising.
178 if input_line.startswith(ADVERT):
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000179 continue
180
181 # If it's outside a function, it just gets copied to the output.
182 output_lines.append(input_line)
183
184 m = IR_FUNCTION_RE.match(input_line)
185 if not m:
186 continue
Fangrui Songee4e2e72018-01-30 00:40:05 +0000187 func_name = m.group(1)
188 if args.function is not None and func_name != args.function:
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000189 # When filtering on a specific function, skip all others.
190 continue
191 is_in_function = is_in_function_start = True
192
Alex Richardsond9542db2019-12-02 10:50:23 +0000193 common.debug('Writing %d lines to %s...' % (len(output_lines), test))
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000194
195 with open(test, 'wb') as f:
Simon Pilgrimd82bd4d2019-01-30 16:15:59 +0000196 f.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines])
Sanjay Patelfff7a3d2016-03-24 23:19:26 +0000197
198
199if __name__ == '__main__':
200 main()