[ARM] Fix for MVE VPT block pass
[llvm-complete.git] / utils / update_mir_test_checks.py
blobb42a38bd55a654176d0956a8fe884fb17d313261
1 #!/usr/bin/env python
3 """Updates FileCheck checks in MIR tests.
5 This script is a utility to update MIR based tests with new FileCheck
6 patterns.
8 The checks added by this script will cover the entire body of each
9 function it handles. Virtual registers used are given names via
10 FileCheck patterns, so if you do want to check a subset of the body it
11 should be straightforward to trim out the irrelevant parts. None of
12 the YAML metadata will be checked, other than function names.
14 If there are multiple llc commands in a test, the full set of checks
15 will be repeated for each different check pattern. Checks for patterns
16 that are common between different commands will be left as-is by
17 default, or removed if the --remove-common-prefixes flag is provided.
18 """
20 from __future__ import print_function
22 import argparse
23 import collections
24 import glob
25 import os
26 import re
27 import subprocess
28 import sys
30 from UpdateTestChecks import common
32 MIR_FUNC_NAME_RE = re.compile(r' *name: *(?P<func>[A-Za-z0-9_.-]+)')
33 MIR_BODY_BEGIN_RE = re.compile(r' *body: *\|')
34 MIR_BASIC_BLOCK_RE = re.compile(r' *bb\.[0-9]+.*:$')
35 VREG_RE = re.compile(r'(%[0-9]+)(?::[a-z0-9_]+)?(?:\([<>a-z0-9 ]+\))?')
36 VREG_DEF_RE = re.compile(
37 r'^ *(?P<vregs>{0}(?:, {0})*) '
38 r'= (?P<opcode>[A-Zt][A-Za-z0-9_]+)'.format(VREG_RE.pattern))
39 MIR_PREFIX_DATA_RE = re.compile(r'^ *(;|bb.[0-9].*: *$|[a-z]+:( |$)|$)')
41 IR_FUNC_NAME_RE = re.compile(
42 r'^\s*define\s+(?:internal\s+)?[^@]*@(?P<func>[A-Za-z0-9_.]+)\s*\(')
43 IR_PREFIX_DATA_RE = re.compile(r'^ *(;|$)')
45 MIR_FUNC_RE = re.compile(
46 r'^---$'
47 r'\n'
48 r'^ *name: *(?P<func>[A-Za-z0-9_.-]+)$'
49 r'.*?'
50 r'^ *body: *\|\n'
51 r'(?P<body>.*?)\n'
52 r'^\.\.\.$',
53 flags=(re.M | re.S))
56 class LLC:
57 def __init__(self, bin):
58 self.bin = bin
60 def __call__(self, args, ir):
61 if ir.endswith('.mir'):
62 args = '{} -x mir'.format(args)
63 with open(ir) as ir_file:
64 stdout = subprocess.check_output('{} {}'.format(self.bin, args),
65 shell=True, stdin=ir_file)
66 if sys.version_info[0] > 2:
67 stdout = stdout.decode()
68 # Fix line endings to unix CR style.
69 stdout = stdout.replace('\r\n', '\n')
70 return stdout
73 class Run:
74 def __init__(self, prefixes, cmd_args, triple):
75 self.prefixes = prefixes
76 self.cmd_args = cmd_args
77 self.triple = triple
79 def __getitem__(self, index):
80 return [self.prefixes, self.cmd_args, self.triple][index]
83 def log(msg, verbose=True):
84 if verbose:
85 print(msg, file=sys.stderr)
88 def warn(msg, test_file=None):
89 if test_file:
90 msg = '{}: {}'.format(test_file, msg)
91 print('WARNING: {}'.format(msg), file=sys.stderr)
94 def find_triple_in_ir(lines, verbose=False):
95 for l in lines:
96 m = common.TRIPLE_IR_RE.match(l)
97 if m:
98 return m.group(1)
99 return None
102 def find_run_lines(test, lines, verbose=False):
103 raw_lines = [m.group(1)
104 for m in [common.RUN_LINE_RE.match(l) for l in lines] if m]
105 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
106 for l in raw_lines[1:]:
107 if run_lines[-1].endswith("\\"):
108 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
109 else:
110 run_lines.append(l)
111 if verbose:
112 log('Found {} RUN lines:'.format(len(run_lines)))
113 for l in run_lines:
114 log(' RUN: {}'.format(l))
115 return run_lines
118 def build_run_list(test, run_lines, verbose=False):
119 run_list = []
120 all_prefixes = []
121 for l in run_lines:
122 commands = [cmd.strip() for cmd in l.split('|', 1)]
123 llc_cmd = commands[0]
124 filecheck_cmd = commands[1] if len(commands) > 1 else ''
126 if not llc_cmd.startswith('llc '):
127 warn('Skipping non-llc RUN line: {}'.format(l), test_file=test)
128 continue
129 if not filecheck_cmd.startswith('FileCheck '):
130 warn('Skipping non-FileChecked RUN line: {}'.format(l),
131 test_file=test)
132 continue
134 triple = None
135 m = common.TRIPLE_ARG_RE.search(llc_cmd)
136 if m:
137 triple = m.group(1)
138 # If we find -march but not -mtriple, use that.
139 m = common.MARCH_ARG_RE.search(llc_cmd)
140 if m and not triple:
141 triple = '{}--'.format(m.group(1))
143 cmd_args = llc_cmd[len('llc'):].strip()
144 cmd_args = cmd_args.replace('< %s', '').replace('%s', '').strip()
146 check_prefixes = [
147 item
148 for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
149 for item in m.group(1).split(',')]
150 if not check_prefixes:
151 check_prefixes = ['CHECK']
152 all_prefixes += check_prefixes
154 run_list.append(Run(check_prefixes, cmd_args, triple))
156 # Remove any common prefixes. We'll just leave those entirely alone.
157 common_prefixes = set([prefix for prefix in all_prefixes
158 if all_prefixes.count(prefix) > 1])
159 for run in run_list:
160 run.prefixes = [p for p in run.prefixes if p not in common_prefixes]
162 return run_list, common_prefixes
165 def find_functions_with_one_bb(lines, verbose=False):
166 result = []
167 cur_func = None
168 bbs = 0
169 for line in lines:
170 m = MIR_FUNC_NAME_RE.match(line)
171 if m:
172 if bbs == 1:
173 result.append(cur_func)
174 cur_func = m.group('func')
175 bbs = 0
176 m = MIR_BASIC_BLOCK_RE.match(line)
177 if m:
178 bbs += 1
179 if bbs == 1:
180 result.append(cur_func)
181 return result
184 def build_function_body_dictionary(test, raw_tool_output, triple, prefixes,
185 func_dict, verbose):
186 for m in MIR_FUNC_RE.finditer(raw_tool_output):
187 func = m.group('func')
188 body = m.group('body')
189 if verbose:
190 log('Processing function: {}'.format(func))
191 for l in body.splitlines():
192 log(' {}'.format(l))
193 for prefix in prefixes:
194 if func in func_dict[prefix] and func_dict[prefix][func] != body:
195 warn('Found conflicting asm for prefix: {}'.format(prefix),
196 test_file=test)
197 func_dict[prefix][func] = body
200 def add_checks_for_function(test, output_lines, run_list, func_dict, func_name,
201 single_bb, verbose=False):
202 printed_prefixes = set()
203 for run in run_list:
204 for prefix in run.prefixes:
205 if prefix in printed_prefixes:
206 continue
207 if not func_dict[prefix][func_name]:
208 continue
209 # if printed_prefixes:
210 # # Add some space between different check prefixes.
211 # output_lines.append('')
212 printed_prefixes.add(prefix)
213 log('Adding {} lines for {}'.format(prefix, func_name), verbose)
214 add_check_lines(test, output_lines, prefix, func_name, single_bb,
215 func_dict[prefix][func_name].splitlines())
216 break
217 return output_lines
220 def add_check_lines(test, output_lines, prefix, func_name, single_bb,
221 func_body):
222 if single_bb:
223 # Don't bother checking the basic block label for a single BB
224 func_body.pop(0)
226 if not func_body:
227 warn('Function has no instructions to check: {}'.format(func_name),
228 test_file=test)
229 return
231 first_line = func_body[0]
232 indent = len(first_line) - len(first_line.lstrip(' '))
233 # A check comment, indented the appropriate amount
234 check = '{:>{}}; {}'.format('', indent, prefix)
236 output_lines.append('{}-LABEL: name: {}'.format(check, func_name))
238 vreg_map = {}
239 for func_line in func_body:
240 if not func_line.strip():
241 continue
242 m = VREG_DEF_RE.match(func_line)
243 if m:
244 for vreg in VREG_RE.finditer(m.group('vregs')):
245 name = mangle_vreg(m.group('opcode'), vreg_map.values())
246 vreg_map[vreg.group(1)] = name
247 func_line = func_line.replace(
248 vreg.group(1), '[[{}:%[0-9]+]]'.format(name), 1)
249 for number, name in vreg_map.items():
250 func_line = re.sub(r'{}\b'.format(number), '[[{}]]'.format(name),
251 func_line)
252 check_line = '{}: {}'.format(check, func_line[indent:]).rstrip()
253 output_lines.append(check_line)
256 def mangle_vreg(opcode, current_names):
257 base = opcode
258 # Simplify some common prefixes and suffixes
259 if opcode.startswith('G_'):
260 base = base[len('G_'):]
261 if opcode.endswith('_PSEUDO'):
262 base = base[:len('_PSEUDO')]
263 # Shorten some common opcodes with long-ish names
264 base = dict(IMPLICIT_DEF='DEF',
265 GLOBAL_VALUE='GV',
266 CONSTANT='C',
267 FCONSTANT='C',
268 MERGE_VALUES='MV',
269 UNMERGE_VALUES='UV',
270 INTRINSIC='INT',
271 INTRINSIC_W_SIDE_EFFECTS='INT',
272 INSERT_VECTOR_ELT='IVEC',
273 EXTRACT_VECTOR_ELT='EVEC',
274 SHUFFLE_VECTOR='SHUF').get(base, base)
275 # Avoid ambiguity when opcodes end in numbers
276 if len(base.rstrip('0123456789')) < len(base):
277 base += '_'
279 i = 0
280 for name in current_names:
281 if name.rstrip('0123456789') == base:
282 i += 1
283 if i:
284 return '{}{}'.format(base, i)
285 return base
288 def should_add_line_to_output(input_line, prefix_set):
289 # Skip any check lines that we're handling.
290 m = common.CHECK_RE.match(input_line)
291 if m and m.group(1) in prefix_set:
292 return False
293 return True
296 def update_test_file(llc, test, remove_common_prefixes=False, verbose=False):
297 log('Scanning for RUN lines in test file: {}'.format(test), verbose)
298 with open(test) as fd:
299 input_lines = [l.rstrip() for l in fd]
301 triple_in_ir = find_triple_in_ir(input_lines, verbose)
302 run_lines = find_run_lines(test, input_lines, verbose)
303 run_list, common_prefixes = build_run_list(test, run_lines, verbose)
305 simple_functions = find_functions_with_one_bb(input_lines, verbose)
307 func_dict = {}
308 for run in run_list:
309 for prefix in run.prefixes:
310 func_dict.update({prefix: dict()})
311 for prefixes, llc_args, triple_in_cmd in run_list:
312 log('Extracted LLC cmd: llc {}'.format(llc_args), verbose)
313 log('Extracted FileCheck prefixes: {}'.format(prefixes), verbose)
315 raw_tool_output = llc(llc_args, test)
316 if not triple_in_cmd and not triple_in_ir:
317 warn('No triple found: skipping file', test_file=test)
318 return
320 build_function_body_dictionary(test, raw_tool_output,
321 triple_in_cmd or triple_in_ir,
322 prefixes, func_dict, verbose)
324 state = 'toplevel'
325 func_name = None
326 prefix_set = set([prefix for run in run_list for prefix in run.prefixes])
327 log('Rewriting FileCheck prefixes: {}'.format(prefix_set), verbose)
329 if remove_common_prefixes:
330 prefix_set.update(common_prefixes)
331 elif common_prefixes:
332 warn('Ignoring common prefixes: {}'.format(common_prefixes),
333 test_file=test)
335 comment_char = '#' if test.endswith('.mir') else ';'
336 autogenerated_note = ('{} NOTE: Assertions have been autogenerated by '
337 'utils/{}'.format(comment_char,
338 os.path.basename(__file__)))
339 output_lines = []
340 output_lines.append(autogenerated_note)
342 for input_line in input_lines:
343 if input_line == autogenerated_note:
344 continue
346 if state == 'toplevel':
347 m = IR_FUNC_NAME_RE.match(input_line)
348 if m:
349 state = 'ir function prefix'
350 func_name = m.group('func')
351 if input_line.rstrip('| \r\n') == '---':
352 state = 'document'
353 output_lines.append(input_line)
354 elif state == 'document':
355 m = MIR_FUNC_NAME_RE.match(input_line)
356 if m:
357 state = 'mir function metadata'
358 func_name = m.group('func')
359 if input_line.strip() == '...':
360 state = 'toplevel'
361 func_name = None
362 if should_add_line_to_output(input_line, prefix_set):
363 output_lines.append(input_line)
364 elif state == 'mir function metadata':
365 if should_add_line_to_output(input_line, prefix_set):
366 output_lines.append(input_line)
367 m = MIR_BODY_BEGIN_RE.match(input_line)
368 if m:
369 if func_name in simple_functions:
370 # If there's only one block, put the checks inside it
371 state = 'mir function prefix'
372 continue
373 state = 'mir function body'
374 add_checks_for_function(test, output_lines, run_list,
375 func_dict, func_name, single_bb=False,
376 verbose=verbose)
377 elif state == 'mir function prefix':
378 m = MIR_PREFIX_DATA_RE.match(input_line)
379 if not m:
380 state = 'mir function body'
381 add_checks_for_function(test, output_lines, run_list,
382 func_dict, func_name, single_bb=True,
383 verbose=verbose)
385 if should_add_line_to_output(input_line, prefix_set):
386 output_lines.append(input_line)
387 elif state == 'mir function body':
388 if input_line.strip() == '...':
389 state = 'toplevel'
390 func_name = None
391 if should_add_line_to_output(input_line, prefix_set):
392 output_lines.append(input_line)
393 elif state == 'ir function prefix':
394 m = IR_PREFIX_DATA_RE.match(input_line)
395 if not m:
396 state = 'ir function body'
397 add_checks_for_function(test, output_lines, run_list,
398 func_dict, func_name, single_bb=False,
399 verbose=verbose)
401 if should_add_line_to_output(input_line, prefix_set):
402 output_lines.append(input_line)
403 elif state == 'ir function body':
404 if input_line.strip() == '}':
405 state = 'toplevel'
406 func_name = None
407 if should_add_line_to_output(input_line, prefix_set):
408 output_lines.append(input_line)
411 log('Writing {} lines to {}...'.format(len(output_lines), test), verbose)
413 with open(test, 'wb') as fd:
414 fd.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines])
417 def main():
418 parser = argparse.ArgumentParser(
419 description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
420 parser.add_argument('-v', '--verbose', action='store_true',
421 help='Show verbose output')
422 parser.add_argument('--llc-binary', dest='llc', default='llc', type=LLC,
423 help='The "llc" binary to generate the test case with')
424 parser.add_argument('--remove-common-prefixes', action='store_true',
425 help='Remove existing check lines whose prefixes are '
426 'shared between multiple commands')
427 parser.add_argument('tests', nargs='+')
428 args = parser.parse_args()
430 test_paths = [test for pattern in args.tests for test in glob.glob(pattern)]
431 for test in test_paths:
432 try:
433 update_test_file(args.llc, test, args.remove_common_prefixes,
434 verbose=args.verbose)
435 except Exception:
436 warn('Error processing file', test_file=test)
437 raise
440 if __name__ == '__main__':
441 main()