blob: cf7528f57609708a7867d91e8094789c9cf1c674 [file] [log] [blame]
Justin Bogner7c1bdaf2017-10-18 02:20:31 +00001#!/usr/bin/env python
2
3"""Updates FileCheck checks in MIR tests.
4
5This script is a utility to update MIR based tests with new FileCheck
6patterns.
7
8The checks added by this script will cover the entire body of each
9function it handles. Virtual registers used are given names via
10FileCheck patterns, so if you do want to check a subset of the body it
11should be straightforward to trim out the irrelevant parts. None of
12the YAML metadata will be checked, other than function names.
13
14If there are multiple llc commands in a test, the full set of checks
15will be repeated for each different check pattern. Checks for patterns
16that are common between different commands will be left as-is by
17default, or removed if the --remove-common-prefixes flag is provided.
18"""
19
20from __future__ import print_function
21
22import argparse
23import collections
24import os
25import re
26import subprocess
27import sys
28
Justin Bogner35a9d1b2018-02-28 00:56:24 +000029from UpdateTestChecks import common
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000030
Justin Bogner930a95c2017-12-18 23:31:55 +000031MIR_FUNC_NAME_RE = re.compile(r' *name: *(?P<func>[A-Za-z0-9_.-]+)')
32MIR_BODY_BEGIN_RE = re.compile(r' *body: *\|')
33MIR_BASIC_BLOCK_RE = re.compile(r' *bb\.[0-9]+.*:$')
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000034VREG_RE = re.compile(r'(%[0-9]+)(?::[a-z0-9_]+)?(?:\([<>a-z0-9 ]+\))?')
35VREG_DEF_RE = re.compile(
36 r'^ *(?P<vregs>{0}(?:, {0})*) '
37 r'= (?P<opcode>[A-Zt][A-Za-z0-9_]+)'.format(VREG_RE.pattern))
Justin Bogner930a95c2017-12-18 23:31:55 +000038MIR_PREFIX_DATA_RE = re.compile(r'^ *(;|bb.[0-9].*: *$|[a-z]+:( |$)|$)')
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000039
Justin Bogner4314f3a2017-12-19 00:49:04 +000040IR_FUNC_NAME_RE = re.compile(
Justin Bogner5db11e32018-01-26 22:56:31 +000041 r'^\s*define\s+(?:internal\s+)?[^@]*@(?P<func>[A-Za-z0-9_.]+)\s*\(')
Justin Bogner4314f3a2017-12-19 00:49:04 +000042IR_PREFIX_DATA_RE = re.compile(r'^ *(;|$)')
43
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000044MIR_FUNC_RE = re.compile(
45 r'^---$'
46 r'\n'
Justin Bogner4b1ab942017-10-18 05:52:56 +000047 r'^ *name: *(?P<func>[A-Za-z0-9_.-]+)$'
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000048 r'.*?'
49 r'^ *body: *\|\n'
50 r'(?P<body>.*?)\n'
51 r'^\.\.\.$',
52 flags=(re.M | re.S))
53
Justin Bogner35a9d1b2018-02-28 00:56:24 +000054
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000055class LLC:
56 def __init__(self, bin):
57 self.bin = bin
58
59 def __call__(self, args, ir):
60 if ir.endswith('.mir'):
61 args = '{} -x mir'.format(args)
62 with open(ir) as ir_file:
63 stdout = subprocess.check_output('{} {}'.format(self.bin, args),
64 shell=True, stdin=ir_file)
Simon Pilgrimf436f702019-03-02 11:14:01 +000065 if sys.version_info[0] > 2:
66 stdout = stdout.decode()
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000067 # Fix line endings to unix CR style.
68 stdout = stdout.replace('\r\n', '\n')
69 return stdout
70
71
72class Run:
73 def __init__(self, prefixes, cmd_args, triple):
74 self.prefixes = prefixes
75 self.cmd_args = cmd_args
76 self.triple = triple
77
78 def __getitem__(self, index):
79 return [self.prefixes, self.cmd_args, self.triple][index]
80
81
82def log(msg, verbose=True):
83 if verbose:
84 print(msg, file=sys.stderr)
85
86
87def warn(msg, test_file=None):
88 if test_file:
89 msg = '{}: {}'.format(test_file, msg)
90 print('WARNING: {}'.format(msg), file=sys.stderr)
91
92
93def find_triple_in_ir(lines, verbose=False):
94 for l in lines:
Justin Bogner35a9d1b2018-02-28 00:56:24 +000095 m = common.TRIPLE_IR_RE.match(l)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +000096 if m:
97 return m.group(1)
98 return None
99
100
101def find_run_lines(test, lines, verbose=False):
102 raw_lines = [m.group(1)
Justin Bogner35a9d1b2018-02-28 00:56:24 +0000103 for m in [common.RUN_LINE_RE.match(l) for l in lines] if m]
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000104 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
105 for l in raw_lines[1:]:
106 if run_lines[-1].endswith("\\"):
107 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
108 else:
109 run_lines.append(l)
110 if verbose:
111 log('Found {} RUN lines:'.format(len(run_lines)))
112 for l in run_lines:
113 log(' RUN: {}'.format(l))
114 return run_lines
115
116
117def build_run_list(test, run_lines, verbose=False):
118 run_list = []
119 all_prefixes = []
120 for l in run_lines:
121 commands = [cmd.strip() for cmd in l.split('|', 1)]
122 llc_cmd = commands[0]
123 filecheck_cmd = commands[1] if len(commands) > 1 else ''
124
125 if not llc_cmd.startswith('llc '):
126 warn('Skipping non-llc RUN line: {}'.format(l), test_file=test)
127 continue
128 if not filecheck_cmd.startswith('FileCheck '):
129 warn('Skipping non-FileChecked RUN line: {}'.format(l),
130 test_file=test)
131 continue
132
133 triple = None
Justin Bogner35a9d1b2018-02-28 00:56:24 +0000134 m = common.TRIPLE_ARG_RE.search(llc_cmd)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000135 if m:
136 triple = m.group(1)
137 # If we find -march but not -mtriple, use that.
Justin Bogner35a9d1b2018-02-28 00:56:24 +0000138 m = common.MARCH_ARG_RE.search(llc_cmd)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000139 if m and not triple:
140 triple = '{}--'.format(m.group(1))
141
142 cmd_args = llc_cmd[len('llc'):].strip()
143 cmd_args = cmd_args.replace('< %s', '').replace('%s', '').strip()
144
Justin Bogner35a9d1b2018-02-28 00:56:24 +0000145 check_prefixes = [
146 item
147 for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
148 for item in m.group(1).split(',')]
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000149 if not check_prefixes:
150 check_prefixes = ['CHECK']
151 all_prefixes += check_prefixes
152
153 run_list.append(Run(check_prefixes, cmd_args, triple))
154
155 # Remove any common prefixes. We'll just leave those entirely alone.
156 common_prefixes = set([prefix for prefix in all_prefixes
157 if all_prefixes.count(prefix) > 1])
158 for run in run_list:
159 run.prefixes = [p for p in run.prefixes if p not in common_prefixes]
160
161 return run_list, common_prefixes
162
163
164def find_functions_with_one_bb(lines, verbose=False):
165 result = []
166 cur_func = None
167 bbs = 0
168 for line in lines:
Justin Bogner930a95c2017-12-18 23:31:55 +0000169 m = MIR_FUNC_NAME_RE.match(line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000170 if m:
171 if bbs == 1:
172 result.append(cur_func)
173 cur_func = m.group('func')
174 bbs = 0
Justin Bogner930a95c2017-12-18 23:31:55 +0000175 m = MIR_BASIC_BLOCK_RE.match(line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000176 if m:
177 bbs += 1
178 if bbs == 1:
179 result.append(cur_func)
180 return result
181
182
183def build_function_body_dictionary(test, raw_tool_output, triple, prefixes,
184 func_dict, verbose):
185 for m in MIR_FUNC_RE.finditer(raw_tool_output):
186 func = m.group('func')
187 body = m.group('body')
188 if verbose:
189 log('Processing function: {}'.format(func))
190 for l in body.splitlines():
191 log(' {}'.format(l))
192 for prefix in prefixes:
193 if func in func_dict[prefix] and func_dict[prefix][func] != body:
194 warn('Found conflicting asm for prefix: {}'.format(prefix),
195 test_file=test)
196 func_dict[prefix][func] = body
197
198
199def add_checks_for_function(test, output_lines, run_list, func_dict, func_name,
Justin Bognerd3eccb52018-02-28 00:44:46 +0000200 single_bb, verbose=False):
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000201 printed_prefixes = set()
202 for run in run_list:
203 for prefix in run.prefixes:
204 if prefix in printed_prefixes:
205 continue
206 if not func_dict[prefix][func_name]:
207 continue
208 # if printed_prefixes:
209 # # Add some space between different check prefixes.
210 # output_lines.append('')
211 printed_prefixes.add(prefix)
212 log('Adding {} lines for {}'.format(prefix, func_name), verbose)
213 add_check_lines(test, output_lines, prefix, func_name, single_bb,
Justin Bognerd3eccb52018-02-28 00:44:46 +0000214 func_dict[prefix][func_name].splitlines())
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000215 break
216 return output_lines
217
218
219def add_check_lines(test, output_lines, prefix, func_name, single_bb,
Justin Bognerd3eccb52018-02-28 00:44:46 +0000220 func_body):
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000221 if single_bb:
222 # Don't bother checking the basic block label for a single BB
223 func_body.pop(0)
224
225 if not func_body:
226 warn('Function has no instructions to check: {}'.format(func_name),
227 test_file=test)
228 return
229
230 first_line = func_body[0]
231 indent = len(first_line) - len(first_line.lstrip(' '))
232 # A check comment, indented the appropriate amount
233 check = '{:>{}}; {}'.format('', indent, prefix)
234
235 output_lines.append('{}-LABEL: name: {}'.format(check, func_name))
236
237 vreg_map = {}
238 for func_line in func_body:
239 if not func_line.strip():
240 continue
241 m = VREG_DEF_RE.match(func_line)
242 if m:
243 for vreg in VREG_RE.finditer(m.group('vregs')):
244 name = mangle_vreg(m.group('opcode'), vreg_map.values())
245 vreg_map[vreg.group(1)] = name
246 func_line = func_line.replace(
247 vreg.group(1), '[[{}:%[0-9]+]]'.format(name), 1)
248 for number, name in vreg_map.items():
Justin Bognerf7b10072017-11-06 21:06:09 +0000249 func_line = re.sub(r'{}\b'.format(number), '[[{}]]'.format(name),
250 func_line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000251 check_line = '{}: {}'.format(check, func_line[indent:]).rstrip()
252 output_lines.append(check_line)
253
254
255def mangle_vreg(opcode, current_names):
256 base = opcode
257 # Simplify some common prefixes and suffixes
258 if opcode.startswith('G_'):
259 base = base[len('G_'):]
260 if opcode.endswith('_PSEUDO'):
261 base = base[:len('_PSEUDO')]
262 # Shorten some common opcodes with long-ish names
263 base = dict(IMPLICIT_DEF='DEF',
264 GLOBAL_VALUE='GV',
265 CONSTANT='C',
266 FCONSTANT='C',
267 MERGE_VALUES='MV',
268 UNMERGE_VALUES='UV',
269 INTRINSIC='INT',
270 INTRINSIC_W_SIDE_EFFECTS='INT',
271 INSERT_VECTOR_ELT='IVEC',
272 EXTRACT_VECTOR_ELT='EVEC',
273 SHUFFLE_VECTOR='SHUF').get(base, base)
Justin Bogner1a33cdb2017-10-18 15:37:09 +0000274 # Avoid ambiguity when opcodes end in numbers
275 if len(base.rstrip('0123456789')) < len(base):
276 base += '_'
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000277
278 i = 0
279 for name in current_names:
Justin Bogner1a33cdb2017-10-18 15:37:09 +0000280 if name.rstrip('0123456789') == base:
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000281 i += 1
282 if i:
283 return '{}{}'.format(base, i)
284 return base
285
286
287def should_add_line_to_output(input_line, prefix_set):
288 # Skip any check lines that we're handling.
Justin Bogner35a9d1b2018-02-28 00:56:24 +0000289 m = common.CHECK_RE.match(input_line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000290 if m and m.group(1) in prefix_set:
291 return False
292 return True
293
294
Justin Bognerd3eccb52018-02-28 00:44:46 +0000295def update_test_file(llc, test, remove_common_prefixes=False, verbose=False):
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000296 log('Scanning for RUN lines in test file: {}'.format(test), verbose)
297 with open(test) as fd:
298 input_lines = [l.rstrip() for l in fd]
299
300 triple_in_ir = find_triple_in_ir(input_lines, verbose)
301 run_lines = find_run_lines(test, input_lines, verbose)
302 run_list, common_prefixes = build_run_list(test, run_lines, verbose)
303
304 simple_functions = find_functions_with_one_bb(input_lines, verbose)
305
306 func_dict = {}
307 for run in run_list:
308 for prefix in run.prefixes:
309 func_dict.update({prefix: dict()})
310 for prefixes, llc_args, triple_in_cmd in run_list:
311 log('Extracted LLC cmd: llc {}'.format(llc_args), verbose)
312 log('Extracted FileCheck prefixes: {}'.format(prefixes), verbose)
313
314 raw_tool_output = llc(llc_args, test)
315 if not triple_in_cmd and not triple_in_ir:
316 warn('No triple found: skipping file', test_file=test)
317 return
318
Justin Bognercf30db92017-10-18 05:39:22 +0000319 build_function_body_dictionary(test, raw_tool_output,
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000320 triple_in_cmd or triple_in_ir,
321 prefixes, func_dict, verbose)
322
323 state = 'toplevel'
324 func_name = None
325 prefix_set = set([prefix for run in run_list for prefix in run.prefixes])
326 log('Rewriting FileCheck prefixes: {}'.format(prefix_set), verbose)
327
328 if remove_common_prefixes:
329 prefix_set.update(common_prefixes)
330 elif common_prefixes:
331 warn('Ignoring common prefixes: {}'.format(common_prefixes),
332 test_file=test)
333
Justin Bogner4314f3a2017-12-19 00:49:04 +0000334 comment_char = '#' if test.endswith('.mir') else ';'
335 autogenerated_note = ('{} NOTE: Assertions have been autogenerated by '
336 'utils/{}'.format(comment_char,
337 os.path.basename(__file__)))
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000338 output_lines = []
339 output_lines.append(autogenerated_note)
340
341 for input_line in input_lines:
342 if input_line == autogenerated_note:
343 continue
344
345 if state == 'toplevel':
Justin Bogner4314f3a2017-12-19 00:49:04 +0000346 m = IR_FUNC_NAME_RE.match(input_line)
347 if m:
348 state = 'ir function prefix'
349 func_name = m.group('func')
Justin Bognereaae3052018-01-23 06:39:04 +0000350 if input_line.rstrip('| \r\n') == '---':
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000351 state = 'document'
352 output_lines.append(input_line)
353 elif state == 'document':
Justin Bogner930a95c2017-12-18 23:31:55 +0000354 m = MIR_FUNC_NAME_RE.match(input_line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000355 if m:
Justin Bogner930a95c2017-12-18 23:31:55 +0000356 state = 'mir function metadata'
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000357 func_name = m.group('func')
358 if input_line.strip() == '...':
359 state = 'toplevel'
360 func_name = None
361 if should_add_line_to_output(input_line, prefix_set):
362 output_lines.append(input_line)
Justin Bogner930a95c2017-12-18 23:31:55 +0000363 elif state == 'mir function metadata':
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000364 if should_add_line_to_output(input_line, prefix_set):
365 output_lines.append(input_line)
Justin Bogner930a95c2017-12-18 23:31:55 +0000366 m = MIR_BODY_BEGIN_RE.match(input_line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000367 if m:
368 if func_name in simple_functions:
369 # If there's only one block, put the checks inside it
Justin Bogner930a95c2017-12-18 23:31:55 +0000370 state = 'mir function prefix'
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000371 continue
Justin Bogner930a95c2017-12-18 23:31:55 +0000372 state = 'mir function body'
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000373 add_checks_for_function(test, output_lines, run_list,
Justin Bognerd3eccb52018-02-28 00:44:46 +0000374 func_dict, func_name, single_bb=False,
375 verbose=verbose)
Justin Bogner930a95c2017-12-18 23:31:55 +0000376 elif state == 'mir function prefix':
377 m = MIR_PREFIX_DATA_RE.match(input_line)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000378 if not m:
Justin Bogner930a95c2017-12-18 23:31:55 +0000379 state = 'mir function body'
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000380 add_checks_for_function(test, output_lines, run_list,
Justin Bognerd3eccb52018-02-28 00:44:46 +0000381 func_dict, func_name, single_bb=True,
382 verbose=verbose)
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000383
384 if should_add_line_to_output(input_line, prefix_set):
385 output_lines.append(input_line)
Justin Bogner930a95c2017-12-18 23:31:55 +0000386 elif state == 'mir function body':
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000387 if input_line.strip() == '...':
388 state = 'toplevel'
389 func_name = None
390 if should_add_line_to_output(input_line, prefix_set):
391 output_lines.append(input_line)
Justin Bogner4314f3a2017-12-19 00:49:04 +0000392 elif state == 'ir function prefix':
393 m = IR_PREFIX_DATA_RE.match(input_line)
394 if not m:
395 state = 'ir function body'
396 add_checks_for_function(test, output_lines, run_list,
Justin Bognerd9224822018-03-12 18:06:58 +0000397 func_dict, func_name, single_bb=False,
398 verbose=verbose)
Justin Bogner4314f3a2017-12-19 00:49:04 +0000399
400 if should_add_line_to_output(input_line, prefix_set):
401 output_lines.append(input_line)
402 elif state == 'ir function body':
403 if input_line.strip() == '}':
404 state = 'toplevel'
405 func_name = None
406 if should_add_line_to_output(input_line, prefix_set):
407 output_lines.append(input_line)
408
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000409
410 log('Writing {} lines to {}...'.format(len(output_lines), test), verbose)
411
412 with open(test, 'wb') as fd:
Simon Pilgrimf436f702019-03-02 11:14:01 +0000413 fd.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines])
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000414
415
416def main():
417 parser = argparse.ArgumentParser(
418 description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
419 parser.add_argument('-v', '--verbose', action='store_true',
420 help='Show verbose output')
421 parser.add_argument('--llc-binary', dest='llc', default='llc', type=LLC,
422 help='The "llc" binary to generate the test case with')
423 parser.add_argument('--remove-common-prefixes', action='store_true',
424 help='Remove existing check lines whose prefixes are '
425 'shared between multiple commands')
426 parser.add_argument('tests', nargs='+')
427 args = parser.parse_args()
428
429 for test in args.tests:
Justin Bogner6b55f1f2017-10-18 22:36:08 +0000430 try:
431 update_test_file(args.llc, test, args.remove_common_prefixes,
Justin Bognerd3eccb52018-02-28 00:44:46 +0000432 verbose=args.verbose)
Justin Bogner6b55f1f2017-10-18 22:36:08 +0000433 except Exception:
434 warn('Error processing file', test_file=test)
435 raise
Justin Bogner7c1bdaf2017-10-18 02:20:31 +0000436
437
438if __name__ == '__main__':
439 main()