3 """Updates FileCheck checks in MIR tests.
5 This script is a utility to update MIR based tests with new FileCheck
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.
20 from __future__ import print_function
29 RUN_LINE_RE = re.compile('^\s*[;#]\s*RUN:\s*(.*)$')
30 TRIPLE_ARG_RE = re.compile(r'-mtriple[= ]([^ ]+)')
31 MARCH_ARG_RE = re.compile(r'-march[= ]([^ ]+)')
32 TRIPLE_IR_RE = re.compile(r'^\s*target\s+triple\s*=\s*"([^"]+)"$')
33 CHECK_PREFIX_RE = re.compile('--?check-prefix(?:es)?[= ](\S+)')
34 CHECK_RE = re.compile(r'^\s*[;#]\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:')
36 MIR_FUNC_NAME_RE = re.compile(r' *name: *(?P<func>[A-Za-z0-9_.-]+)')
37 MIR_BODY_BEGIN_RE = re.compile(r' *body: *\|')
38 MIR_BASIC_BLOCK_RE = re.compile(r' *bb\.[0-9]+.*:$')
39 VREG_RE = re.compile(r'(%[0-9]+)(?::[a-z0-9_]+)?(?:\([<>a-z0-9 ]+\))?')
40 VREG_DEF_RE = re.compile(
41 r'^ *(?P<vregs>{0}(?:, {0})*) '
42 r'= (?P<opcode>[A-Zt][A-Za-z0-9_]+)'.format(VREG_RE.pattern))
43 MIR_PREFIX_DATA_RE = re.compile(r'^ *(;|bb.[0-9].*: *$|[a-z]+:( |$)|$)')
44 VREG_CLASS_RE = re.compile(r'^ *- *{ id: ([0-9]+), class: ([a-z0-9_]+)', re.M)
46 IR_FUNC_NAME_RE = re.compile(
47 r'^\s*define\s+(?:internal\s+)?[^@]*@(?P<func>\w+)\s*\(')
48 IR_PREFIX_DATA_RE = re.compile(r'^ *(;|$)')
50 MIR_FUNC_RE = re.compile(
53 r'^ *name: *(?P<func>[A-Za-z0-9_.-]+)$'
54 r'(?:.*?(?P<vregs>^ *registers: *(?:\n *- {[^\n]+$)*))?'
62 def __init__(self, bin):
65 def __call__(self, args, ir):
66 if ir.endswith('.mir'):
67 args = '{} -x mir'.format(args)
68 with open(ir) as ir_file:
69 stdout = subprocess.check_output('{} {}'.format(self.bin, args),
70 shell=True, stdin=ir_file)
71 # Fix line endings to unix CR style.
72 stdout = stdout.replace('\r\n', '\n')
77 def __init__(self, prefixes, cmd_args, triple):
78 self.prefixes = prefixes
79 self.cmd_args = cmd_args
82 def __getitem__(self, index):
83 return [self.prefixes, self.cmd_args, self.triple][index]
86 def log(msg, verbose=True):
88 print(msg, file=sys.stderr)
91 def warn(msg, test_file=None):
93 msg = '{}: {}'.format(test_file, msg)
94 print('WARNING: {}'.format(msg), file=sys.stderr)
97 def find_triple_in_ir(lines, verbose=False):
99 m = TRIPLE_IR_RE.match(l)
105 def find_run_lines(test, lines, verbose=False):
106 raw_lines = [m.group(1)
107 for m in [RUN_LINE_RE.match(l) for l in lines] if m]
108 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
109 for l in raw_lines[1:]:
110 if run_lines[-1].endswith("\\"):
111 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
115 log('Found {} RUN lines:'.format(len(run_lines)))
117 log(' RUN: {}'.format(l))
121 def build_run_list(test, run_lines, verbose=False):
125 commands = [cmd.strip() for cmd in l.split('|', 1)]
126 llc_cmd = commands[0]
127 filecheck_cmd = commands[1] if len(commands) > 1 else ''
129 if not llc_cmd.startswith('llc '):
130 warn('Skipping non-llc RUN line: {}'.format(l), test_file=test)
132 if not filecheck_cmd.startswith('FileCheck '):
133 warn('Skipping non-FileChecked RUN line: {}'.format(l),
138 m = TRIPLE_ARG_RE.search(llc_cmd)
141 # If we find -march but not -mtriple, use that.
142 m = MARCH_ARG_RE.search(llc_cmd)
144 triple = '{}--'.format(m.group(1))
146 cmd_args = llc_cmd[len('llc'):].strip()
147 cmd_args = cmd_args.replace('< %s', '').replace('%s', '').strip()
149 check_prefixes = [item for m in CHECK_PREFIX_RE.finditer(filecheck_cmd)
150 for item in m.group(1).split(',')]
151 if not check_prefixes:
152 check_prefixes = ['CHECK']
153 all_prefixes += check_prefixes
155 run_list.append(Run(check_prefixes, cmd_args, triple))
157 # Remove any common prefixes. We'll just leave those entirely alone.
158 common_prefixes = set([prefix for prefix in all_prefixes
159 if all_prefixes.count(prefix) > 1])
161 run.prefixes = [p for p in run.prefixes if p not in common_prefixes]
163 return run_list, common_prefixes
166 def find_functions_with_one_bb(lines, verbose=False):
171 m = MIR_FUNC_NAME_RE.match(line)
174 result.append(cur_func)
175 cur_func = m.group('func')
177 m = MIR_BASIC_BLOCK_RE.match(line)
181 result.append(cur_func)
185 def build_function_body_dictionary(test, raw_tool_output, triple, prefixes,
187 for m in MIR_FUNC_RE.finditer(raw_tool_output):
188 func = m.group('func')
189 body = m.group('body')
191 log('Processing function: {}'.format(func))
192 for l in body.splitlines():
194 for prefix in prefixes:
195 if func in func_dict[prefix] and func_dict[prefix][func] != body:
196 warn('Found conflicting asm for prefix: {}'.format(prefix),
198 func_dict[prefix][func] = body
199 func_dict[prefix]['{}:vregs'.format(func)] = m.group('vregs')
202 def add_checks_for_function(test, output_lines, run_list, func_dict, func_name,
203 add_vreg_checks, single_bb, verbose=False):
204 printed_prefixes = set()
206 for prefix in run.prefixes:
207 if prefix in printed_prefixes:
209 if not func_dict[prefix][func_name]:
211 # if printed_prefixes:
212 # # Add some space between different check prefixes.
213 # output_lines.append('')
214 printed_prefixes.add(prefix)
215 log('Adding {} lines for {}'.format(prefix, func_name), verbose)
218 vregs = func_dict[prefix]['{}:vregs'.format(func_name)]
219 add_check_lines(test, output_lines, prefix, func_name, single_bb,
220 func_dict[prefix][func_name].splitlines(), vregs)
225 def add_check_lines(test, output_lines, prefix, func_name, single_bb,
226 func_body, vreg_data):
228 # Don't bother checking the basic block label for a single BB
232 warn('Function has no instructions to check: {}'.format(func_name),
236 first_line = func_body[0]
237 indent = len(first_line) - len(first_line.lstrip(' '))
238 # A check comment, indented the appropriate amount
239 check = '{:>{}}; {}'.format('', indent, prefix)
241 output_lines.append('{}-LABEL: name: {}'.format(check, func_name))
244 output_lines.append('{}: registers:'.format(check))
245 for m in VREG_CLASS_RE.finditer(vreg_data):
246 output_lines.append('{}-NEXT: id: {}, class: {}'.format(
247 check, m.group(1), m.group(2)))
250 for func_line in func_body:
251 if not func_line.strip():
253 m = VREG_DEF_RE.match(func_line)
255 for vreg in VREG_RE.finditer(m.group('vregs')):
256 name = mangle_vreg(m.group('opcode'), vreg_map.values())
257 vreg_map[vreg.group(1)] = name
258 func_line = func_line.replace(
259 vreg.group(1), '[[{}:%[0-9]+]]'.format(name), 1)
260 for number, name in vreg_map.items():
261 func_line = re.sub(r'{}\b'.format(number), '[[{}]]'.format(name),
263 check_line = '{}: {}'.format(check, func_line[indent:]).rstrip()
264 output_lines.append(check_line)
267 def mangle_vreg(opcode, current_names):
269 # Simplify some common prefixes and suffixes
270 if opcode.startswith('G_'):
271 base = base[len('G_'):]
272 if opcode.endswith('_PSEUDO'):
273 base = base[:len('_PSEUDO')]
274 # Shorten some common opcodes with long-ish names
275 base = dict(IMPLICIT_DEF='DEF',
282 INTRINSIC_W_SIDE_EFFECTS='INT',
283 INSERT_VECTOR_ELT='IVEC',
284 EXTRACT_VECTOR_ELT='EVEC',
285 SHUFFLE_VECTOR='SHUF').get(base, base)
286 # Avoid ambiguity when opcodes end in numbers
287 if len(base.rstrip('0123456789')) < len(base):
291 for name in current_names:
292 if name.rstrip('0123456789') == base:
295 return '{}{}'.format(base, i)
299 def should_add_line_to_output(input_line, prefix_set):
300 # Skip any check lines that we're handling.
301 m = CHECK_RE.match(input_line)
302 if m and m.group(1) in prefix_set:
307 def update_test_file(llc, test, remove_common_prefixes=False,
308 add_vreg_checks=False, verbose=False):
309 log('Scanning for RUN lines in test file: {}'.format(test), verbose)
310 with open(test) as fd:
311 input_lines = [l.rstrip() for l in fd]
313 triple_in_ir = find_triple_in_ir(input_lines, verbose)
314 run_lines = find_run_lines(test, input_lines, verbose)
315 run_list, common_prefixes = build_run_list(test, run_lines, verbose)
317 simple_functions = find_functions_with_one_bb(input_lines, verbose)
321 for prefix in run.prefixes:
322 func_dict.update({prefix: dict()})
323 for prefixes, llc_args, triple_in_cmd in run_list:
324 log('Extracted LLC cmd: llc {}'.format(llc_args), verbose)
325 log('Extracted FileCheck prefixes: {}'.format(prefixes), verbose)
327 raw_tool_output = llc(llc_args, test)
328 if not triple_in_cmd and not triple_in_ir:
329 warn('No triple found: skipping file', test_file=test)
332 build_function_body_dictionary(test, raw_tool_output,
333 triple_in_cmd or triple_in_ir,
334 prefixes, func_dict, verbose)
338 prefix_set = set([prefix for run in run_list for prefix in run.prefixes])
339 log('Rewriting FileCheck prefixes: {}'.format(prefix_set), verbose)
341 if remove_common_prefixes:
342 prefix_set.update(common_prefixes)
343 elif common_prefixes:
344 warn('Ignoring common prefixes: {}'.format(common_prefixes),
347 comment_char = '#' if test.endswith('.mir') else ';'
348 autogenerated_note = ('{} NOTE: Assertions have been autogenerated by '
349 'utils/{}'.format(comment_char,
350 os.path.basename(__file__)))
352 output_lines.append(autogenerated_note)
354 for input_line in input_lines:
355 if input_line == autogenerated_note:
358 if state == 'toplevel':
359 m = IR_FUNC_NAME_RE.match(input_line)
361 state = 'ir function prefix'
362 func_name = m.group('func')
363 if input_line.strip() == '---':
365 output_lines.append(input_line)
366 elif state == 'document':
367 m = MIR_FUNC_NAME_RE.match(input_line)
369 state = 'mir function metadata'
370 func_name = m.group('func')
371 if input_line.strip() == '...':
374 if should_add_line_to_output(input_line, prefix_set):
375 output_lines.append(input_line)
376 elif state == 'mir function metadata':
377 if should_add_line_to_output(input_line, prefix_set):
378 output_lines.append(input_line)
379 m = MIR_BODY_BEGIN_RE.match(input_line)
381 if func_name in simple_functions:
382 # If there's only one block, put the checks inside it
383 state = 'mir function prefix'
385 state = 'mir function body'
386 add_checks_for_function(test, output_lines, run_list,
387 func_dict, func_name, add_vreg_checks,
388 single_bb=False, verbose=verbose)
389 elif state == 'mir function prefix':
390 m = MIR_PREFIX_DATA_RE.match(input_line)
392 state = 'mir function body'
393 add_checks_for_function(test, output_lines, run_list,
394 func_dict, func_name, add_vreg_checks,
395 single_bb=True, verbose=verbose)
397 if should_add_line_to_output(input_line, prefix_set):
398 output_lines.append(input_line)
399 elif state == 'mir function body':
400 if input_line.strip() == '...':
403 if should_add_line_to_output(input_line, prefix_set):
404 output_lines.append(input_line)
405 elif state == 'ir function prefix':
406 m = IR_PREFIX_DATA_RE.match(input_line)
408 state = 'ir function body'
409 add_checks_for_function(test, output_lines, run_list,
410 func_dict, func_name, add_vreg_checks,
411 single_bb=False, verbose=verbose)
413 if should_add_line_to_output(input_line, prefix_set):
414 output_lines.append(input_line)
415 elif state == 'ir function body':
416 if input_line.strip() == '}':
419 if should_add_line_to_output(input_line, prefix_set):
420 output_lines.append(input_line)
423 log('Writing {} lines to {}...'.format(len(output_lines), test), verbose)
425 with open(test, 'wb') as fd:
426 fd.writelines([l + '\n' for l in output_lines])
430 parser = argparse.ArgumentParser(
431 description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
432 parser.add_argument('-v', '--verbose', action='store_true',
433 help='Show verbose output')
434 parser.add_argument('--llc-binary', dest='llc', default='llc', type=LLC,
435 help='The "llc" binary to generate the test case with')
436 parser.add_argument('--remove-common-prefixes', action='store_true',
437 help='Remove existing check lines whose prefixes are '
438 'shared between multiple commands')
439 parser.add_argument('--add-vreg-checks', action='store_true',
440 help='Add checks for the "registers:" block')
441 parser.add_argument('tests', nargs='+')
442 args = parser.parse_args()
444 for test in args.tests:
446 update_test_file(args.llc, test, args.remove_common_prefixes,
447 args.add_vreg_checks, verbose=args.verbose)
449 warn('Error processing file', test_file=test)
453 if __name__ == '__main__':