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