[InstCombine] Signed saturation tests. NFC
[llvm-complete.git] / utils / update_cc_test_checks.py
blobee8f641c3cab2894be9a61a520c7d6be752bd6f8
1 #!/usr/bin/env python3
2 '''A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.
4 Example RUN lines in .c/.cc test files:
6 // RUN: %clang -emit-llvm -S %s -o - -O2 | FileCheck %s
7 // RUN: %clangxx -emit-llvm -S %s -o - -O2 | FileCheck -check-prefix=CHECK-A %s
9 Usage:
11 % utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc
12 % utils/update_cc_test_checks.py --c-index-test=release/bin/c-index-test \
13 --clang=release/bin/clang /tmp/c/a.cc
14 '''
16 import argparse
17 import collections
18 import distutils.spawn
19 import os
20 import shlex
21 import string
22 import subprocess
23 import sys
24 import re
25 import tempfile
27 from UpdateTestChecks import asm, common
29 ADVERT = '// NOTE: Assertions have been autogenerated by '
31 CHECK_RE = re.compile(r'^\s*//\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:')
32 RUN_LINE_RE = re.compile('^//\s*RUN:\s*(.*)$')
34 SUBST = {
35 '%clang': [],
36 '%clang_cc1': ['-cc1'],
37 '%clangxx': ['--driver-mode=g++'],
40 def get_line2spell_and_mangled(args, clang_args):
41 ret = {}
42 with tempfile.NamedTemporaryFile() as f:
43 # TODO Make c-index-test print mangled names without circumventing through precompiled headers
44 status = subprocess.run([args.c_index_test, '-write-pch', f.name, *clang_args],
45 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
46 if status.returncode:
47 sys.stderr.write(status.stdout.decode())
48 sys.exit(2)
49 output = subprocess.check_output([args.c_index_test,
50 '-test-print-mangle', f.name])
51 if sys.version_info[0] > 2:
52 output = output.decode()
53 DeclRE = re.compile(r'^FunctionDecl=(\w+):(\d+):\d+ \(Definition\)')
54 MangleRE = re.compile(r'.*\[mangled=([^]]+)\]')
55 MatchedDecl = False
56 for line in output.splitlines():
57 # Get the function source name, line number and mangled name. Sometimes
58 # c-index-test outputs the mangled name on a separate line (this can happen
59 # with block comments in front of functions). Keep scanning until we see
60 # the mangled name.
61 decl_m = DeclRE.match(line)
62 mangle_m = MangleRE.match(line)
64 if decl_m:
65 MatchedDecl = True
66 spell, lineno = decl_m.groups()
67 if MatchedDecl and mangle_m:
68 mangled = mangle_m.group(1)
69 MatchedDecl = False
70 else:
71 continue
73 if mangled == '_' + spell:
74 # HACK for MacOS (where the mangled name includes an _ for C but the IR won't):
75 mangled = spell
76 # Note -test-print-mangle does not print file names so if #include is used,
77 # the line number may come from an included file.
78 ret[int(lineno)-1] = (spell, mangled)
79 if args.verbose:
80 for line, func_name in sorted(ret.items()):
81 print('line {}: found function {}'.format(line+1, func_name), file=sys.stderr)
82 return ret
85 def config():
86 parser = argparse.ArgumentParser(
87 description=__doc__,
88 formatter_class=argparse.RawTextHelpFormatter)
89 parser.add_argument('-v', '--verbose', action='store_true')
90 parser.add_argument('--llvm-bin', help='llvm $prefix/bin path')
91 parser.add_argument('--clang',
92 help='"clang" executable, defaults to $llvm_bin/clang')
93 parser.add_argument('--clang-args',
94 help='Space-separated extra args to clang, e.g. --clang-args=-v')
95 parser.add_argument('--c-index-test',
96 help='"c-index-test" executable, defaults to $llvm_bin/c-index-test')
97 parser.add_argument('--opt',
98 help='"opt" executable, defaults to $llvm_bin/opt')
99 parser.add_argument(
100 '--functions', nargs='+', help='A list of function name regexes. '
101 'If specified, update CHECK lines for functions matching at least one regex')
102 parser.add_argument(
103 '--x86_extra_scrub', action='store_true',
104 help='Use more regex for x86 matching to reduce diffs between various subtargets')
105 parser.add_argument('-u', '--update-only', action='store_true',
106 help='Only update test if it was already autogened')
107 parser.add_argument('tests', nargs='+')
108 args = parser.parse_args()
109 args.clang_args = shlex.split(args.clang_args or '')
111 if args.clang is None:
112 if args.llvm_bin is None:
113 args.clang = 'clang'
114 else:
115 args.clang = os.path.join(args.llvm_bin, 'clang')
116 if not distutils.spawn.find_executable(args.clang):
117 print('Please specify --llvm-bin or --clang', file=sys.stderr)
118 sys.exit(1)
120 if args.opt is None:
121 if args.llvm_bin is None:
122 args.opt = 'opt'
123 else:
124 args.opt = os.path.join(args.llvm_bin, 'opt')
125 if not distutils.spawn.find_executable(args.opt):
126 # Many uses of this tool will not need an opt binary, because it's only
127 # needed for updating a test that runs clang | opt | FileCheck. So we
128 # defer this error message until we find that opt is actually needed.
129 args.opt = None
131 if args.c_index_test is None:
132 if args.llvm_bin is None:
133 args.c_index_test = 'c-index-test'
134 else:
135 args.c_index_test = os.path.join(args.llvm_bin, 'c-index-test')
136 if not distutils.spawn.find_executable(args.c_index_test):
137 print('Please specify --llvm-bin or --c-index-test', file=sys.stderr)
138 sys.exit(1)
140 return args
143 def get_function_body(args, filename, clang_args, extra_commands, prefixes, triple_in_cmd, func_dict):
144 # TODO Clean up duplication of asm/common build_function_body_dictionary
145 # Invoke external tool and extract function bodies.
146 raw_tool_output = common.invoke_tool(args.clang, clang_args, filename)
147 for extra_command in extra_commands:
148 extra_args = shlex.split(extra_command)
149 with tempfile.NamedTemporaryFile() as f:
150 f.write(raw_tool_output.encode())
151 f.flush()
152 if extra_args[0] == 'opt':
153 if args.opt is None:
154 print(filename, 'needs to run opt. '
155 'Please specify --llvm-bin or --opt', file=sys.stderr)
156 sys.exit(1)
157 extra_args[0] = args.opt
158 raw_tool_output = common.invoke_tool(extra_args[0],
159 extra_args[1:], f.name)
160 if '-emit-llvm' in clang_args:
161 common.build_function_body_dictionary(
162 common.OPT_FUNCTION_RE, common.scrub_body, [],
163 raw_tool_output, prefixes, func_dict, args.verbose)
164 else:
165 print('The clang command line should include -emit-llvm as asm tests '
166 'are discouraged in Clang testsuite.', file=sys.stderr)
167 sys.exit(1)
170 def main():
171 args = config()
172 script_name = os.path.basename(__file__)
173 autogenerated_note = (ADVERT + 'utils/' + script_name)
175 for filename in args.tests:
176 with open(filename) as f:
177 input_lines = [l.rstrip() for l in f]
179 first_line = input_lines[0] if input_lines else ""
180 if 'autogenerated' in first_line and script_name not in first_line:
181 common.warn("Skipping test which wasn't autogenerated by " + script_name, filename)
182 continue
184 if args.update_only:
185 if not first_line or 'autogenerated' not in first_line:
186 common.warn("Skipping test which isn't autogenerated: " + filename)
187 continue
189 # Extract RUN lines.
190 raw_lines = [m.group(1)
191 for m in [RUN_LINE_RE.match(l) for l in input_lines] if m]
192 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
193 for l in raw_lines[1:]:
194 if run_lines[-1].endswith("\\"):
195 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
196 else:
197 run_lines.append(l)
199 if args.verbose:
200 print('Found {} RUN lines:'.format(len(run_lines)), file=sys.stderr)
201 for l in run_lines:
202 print(' RUN: ' + l, file=sys.stderr)
204 # Build a list of clang command lines and check prefixes from RUN lines.
205 run_list = []
206 line2spell_and_mangled_list = collections.defaultdict(list)
207 for l in run_lines:
208 commands = [cmd.strip() for cmd in l.split('|')]
210 triple_in_cmd = None
211 m = common.TRIPLE_ARG_RE.search(commands[0])
212 if m:
213 triple_in_cmd = m.groups()[0]
215 # Apply %clang substitution rule, replace %s by `filename`, and append args.clang_args
216 clang_args = shlex.split(commands[0])
217 if clang_args[0] not in SUBST:
218 print('WARNING: Skipping non-clang RUN line: ' + l, file=sys.stderr)
219 continue
220 clang_args[0:1] = SUBST[clang_args[0]]
221 clang_args = [filename if i == '%s' else i for i in clang_args] + args.clang_args
223 # Permit piping the output through opt
224 if not (len(commands) == 2 or
225 (len(commands) == 3 and commands[1].startswith('opt'))):
226 print('WARNING: Skipping non-clang RUN line: ' + l, file=sys.stderr)
228 # Extract -check-prefix in FileCheck args
229 filecheck_cmd = commands[-1]
230 common.verify_filecheck_prefixes(filecheck_cmd)
231 if not filecheck_cmd.startswith('FileCheck '):
232 print('WARNING: Skipping non-FileChecked RUN line: ' + l, file=sys.stderr)
233 continue
234 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
235 for item in m.group(1).split(',')]
236 if not check_prefixes:
237 check_prefixes = ['CHECK']
238 run_list.append((check_prefixes, clang_args, commands[1:-1], triple_in_cmd))
240 # Strip CHECK lines which are in `prefix_set`, update test file.
241 prefix_set = set([prefix for p in run_list for prefix in p[0]])
242 input_lines = []
243 with open(filename, 'r+') as f:
244 for line in f:
245 m = CHECK_RE.match(line)
246 if not (m and m.group(1) in prefix_set) and line != '//\n':
247 input_lines.append(line)
248 f.seek(0)
249 f.writelines(input_lines)
250 f.truncate()
252 # Execute clang, generate LLVM IR, and extract functions.
253 func_dict = {}
254 for p in run_list:
255 prefixes = p[0]
256 for prefix in prefixes:
257 func_dict.update({prefix: dict()})
258 for prefixes, clang_args, extra_commands, triple_in_cmd in run_list:
259 if args.verbose:
260 print('Extracted clang cmd: clang {}'.format(clang_args), file=sys.stderr)
261 print('Extracted FileCheck prefixes: {}'.format(prefixes), file=sys.stderr)
263 get_function_body(args, filename, clang_args, extra_commands, prefixes, triple_in_cmd, func_dict)
265 # Invoke c-index-test to get mapping from start lines to mangled names.
266 # Forward all clang args for now.
267 for k, v in get_line2spell_and_mangled(args, clang_args).items():
268 line2spell_and_mangled_list[k].append(v)
270 output_lines = [autogenerated_note]
271 for idx, line in enumerate(input_lines):
272 # Discard any previous script advertising.
273 if line.startswith(ADVERT):
274 continue
275 if idx in line2spell_and_mangled_list:
276 added = set()
277 for spell, mangled in line2spell_and_mangled_list[idx]:
278 # One line may contain multiple function declarations.
279 # Skip if the mangled name has been added before.
280 # The line number may come from an included file,
281 # we simply require the spelling name to appear on the line
282 # to exclude functions from other files.
283 if mangled in added or spell not in line:
284 continue
285 if args.functions is None or any(re.search(regex, spell) for regex in args.functions):
286 if added:
287 output_lines.append('//')
288 added.add(mangled)
289 common.add_ir_checks(output_lines, '//', run_list, func_dict, mangled, False)
290 output_lines.append(line.rstrip('\n'))
292 # Update the test file.
293 with open(filename, 'w') as f:
294 for line in output_lines:
295 f.write(line + '\n')
297 return 0
300 if __name__ == '__main__':
301 sys.exit(main())