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
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(
48 r
'^ *name: *(?P<func>[A-Za-z0-9_.-]+)$'
57 def __init__(self
, 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')
74 def __init__(self
, prefixes
, cmd_args
, triple
):
75 self
.prefixes
= prefixes
76 self
.cmd_args
= cmd_args
79 def __getitem__(self
, index
):
80 return [self
.prefixes
, self
.cmd_args
, self
.triple
][index
]
83 def log(msg
, verbose
=True):
85 print(msg
, file=sys
.stderr
)
88 def warn(msg
, test_file
=None):
90 msg
= '{}: {}'.format(test_file
, msg
)
91 print('WARNING: {}'.format(msg
), file=sys
.stderr
)
94 def find_triple_in_ir(lines
, verbose
=False):
96 m
= common
.TRIPLE_IR_RE
.match(l
)
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
112 log('Found {} RUN lines:'.format(len(run_lines
)))
114 log(' RUN: {}'.format(l
))
118 def build_run_list(test
, run_lines
, verbose
=False):
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 ''
125 common
.verify_filecheck_prefixes(filecheck_cmd
)
127 if not llc_cmd
.startswith('llc '):
128 warn('Skipping non-llc RUN line: {}'.format(l
), test_file
=test
)
130 if not filecheck_cmd
.startswith('FileCheck '):
131 warn('Skipping non-FileChecked RUN line: {}'.format(l
),
136 m
= common
.TRIPLE_ARG_RE
.search(llc_cmd
)
139 # If we find -march but not -mtriple, use that.
140 m
= common
.MARCH_ARG_RE
.search(llc_cmd
)
142 triple
= '{}--'.format(m
.group(1))
144 cmd_args
= llc_cmd
[len('llc'):].strip()
145 cmd_args
= cmd_args
.replace('< %s', '').replace('%s', '').strip()
149 for m
in common
.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
201 def add_checks_for_function(test
, output_lines
, run_list
, func_dict
, func_name
,
202 single_bb
, verbose
=False):
203 printed_prefixes
= set()
205 for prefix
in run
.prefixes
:
206 if prefix
in printed_prefixes
:
208 if not func_dict
[prefix
][func_name
]:
210 # if printed_prefixes:
211 # # Add some space between different check prefixes.
212 # output_lines.append('')
213 printed_prefixes
.add(prefix
)
214 log('Adding {} lines for {}'.format(prefix
, func_name
), verbose
)
215 add_check_lines(test
, output_lines
, prefix
, func_name
, single_bb
,
216 func_dict
[prefix
][func_name
].splitlines())
221 def add_check_lines(test
, output_lines
, prefix
, func_name
, single_bb
,
224 # Don't bother checking the basic block label for a single BB
228 warn('Function has no instructions to check: {}'.format(func_name
),
232 first_line
= func_body
[0]
233 indent
= len(first_line
) - len(first_line
.lstrip(' '))
234 # A check comment, indented the appropriate amount
235 check
= '{:>{}}; {}'.format('', indent
, prefix
)
237 output_lines
.append('{}-LABEL: name: {}'.format(check
, func_name
))
240 for func_line
in func_body
:
241 if not func_line
.strip():
243 m
= VREG_DEF_RE
.match(func_line
)
245 for vreg
in VREG_RE
.finditer(m
.group('vregs')):
246 name
= mangle_vreg(m
.group('opcode'), vreg_map
.values())
247 vreg_map
[vreg
.group(1)] = name
248 func_line
= func_line
.replace(
249 vreg
.group(1), '[[{}:%[0-9]+]]'.format(name
), 1)
250 for number
, name
in vreg_map
.items():
251 func_line
= re
.sub(r
'{}\b'.format(number
), '[[{}]]'.format(name
),
253 check_line
= '{}: {}'.format(check
, func_line
[indent
:]).rstrip()
254 output_lines
.append(check_line
)
257 def mangle_vreg(opcode
, current_names
):
259 # Simplify some common prefixes and suffixes
260 if opcode
.startswith('G_'):
261 base
= base
[len('G_'):]
262 if opcode
.endswith('_PSEUDO'):
263 base
= base
[:len('_PSEUDO')]
264 # Shorten some common opcodes with long-ish names
265 base
= dict(IMPLICIT_DEF
='DEF',
272 INTRINSIC_W_SIDE_EFFECTS
='INT',
273 INSERT_VECTOR_ELT
='IVEC',
274 EXTRACT_VECTOR_ELT
='EVEC',
275 SHUFFLE_VECTOR
='SHUF').get(base
, base
)
276 # Avoid ambiguity when opcodes end in numbers
277 if len(base
.rstrip('0123456789')) < len(base
):
281 for name
in current_names
:
282 if name
.rstrip('0123456789') == base
:
285 return '{}{}'.format(base
, i
)
289 def should_add_line_to_output(input_line
, prefix_set
):
290 # Skip any check lines that we're handling.
291 m
= common
.CHECK_RE
.match(input_line
)
292 if m
and m
.group(1) in prefix_set
:
297 def update_test_file(llc
, test
, remove_common_prefixes
=False, verbose
=False):
298 log('Scanning for RUN lines in test file: {}'.format(test
), verbose
)
299 with
open(test
) as fd
:
300 input_lines
= [l
.rstrip() for l
in fd
]
302 triple_in_ir
= find_triple_in_ir(input_lines
, verbose
)
303 run_lines
= find_run_lines(test
, input_lines
, verbose
)
304 run_list
, common_prefixes
= build_run_list(test
, run_lines
, verbose
)
306 simple_functions
= find_functions_with_one_bb(input_lines
, verbose
)
310 for prefix
in run
.prefixes
:
311 func_dict
.update({prefix
: dict()})
312 for prefixes
, llc_args
, triple_in_cmd
in run_list
:
313 log('Extracted LLC cmd: llc {}'.format(llc_args
), verbose
)
314 log('Extracted FileCheck prefixes: {}'.format(prefixes
), verbose
)
316 raw_tool_output
= llc(llc_args
, test
)
317 if not triple_in_cmd
and not triple_in_ir
:
318 warn('No triple found: skipping file', test_file
=test
)
321 build_function_body_dictionary(test
, raw_tool_output
,
322 triple_in_cmd
or triple_in_ir
,
323 prefixes
, func_dict
, verbose
)
327 prefix_set
= set([prefix
for run
in run_list
for prefix
in run
.prefixes
])
328 log('Rewriting FileCheck prefixes: {}'.format(prefix_set
), verbose
)
330 if remove_common_prefixes
:
331 prefix_set
.update(common_prefixes
)
332 elif common_prefixes
:
333 warn('Ignoring common prefixes: {}'.format(common_prefixes
),
336 comment_char
= '#' if test
.endswith('.mir') else ';'
337 autogenerated_note
= ('{} NOTE: Assertions have been autogenerated by '
338 'utils/{}'.format(comment_char
,
339 os
.path
.basename(__file__
)))
341 output_lines
.append(autogenerated_note
)
343 for input_line
in input_lines
:
344 if input_line
== autogenerated_note
:
347 if state
== 'toplevel':
348 m
= IR_FUNC_NAME_RE
.match(input_line
)
350 state
= 'ir function prefix'
351 func_name
= m
.group('func')
352 if input_line
.rstrip('| \r\n') == '---':
354 output_lines
.append(input_line
)
355 elif state
== 'document':
356 m
= MIR_FUNC_NAME_RE
.match(input_line
)
358 state
= 'mir function metadata'
359 func_name
= m
.group('func')
360 if input_line
.strip() == '...':
363 if should_add_line_to_output(input_line
, prefix_set
):
364 output_lines
.append(input_line
)
365 elif state
== 'mir function metadata':
366 if should_add_line_to_output(input_line
, prefix_set
):
367 output_lines
.append(input_line
)
368 m
= MIR_BODY_BEGIN_RE
.match(input_line
)
370 if func_name
in simple_functions
:
371 # If there's only one block, put the checks inside it
372 state
= 'mir function prefix'
374 state
= 'mir function body'
375 add_checks_for_function(test
, output_lines
, run_list
,
376 func_dict
, func_name
, single_bb
=False,
378 elif state
== 'mir function prefix':
379 m
= MIR_PREFIX_DATA_RE
.match(input_line
)
381 state
= 'mir function body'
382 add_checks_for_function(test
, output_lines
, run_list
,
383 func_dict
, func_name
, single_bb
=True,
386 if should_add_line_to_output(input_line
, prefix_set
):
387 output_lines
.append(input_line
)
388 elif state
== 'mir function body':
389 if input_line
.strip() == '...':
392 if should_add_line_to_output(input_line
, prefix_set
):
393 output_lines
.append(input_line
)
394 elif state
== 'ir function prefix':
395 m
= IR_PREFIX_DATA_RE
.match(input_line
)
397 state
= 'ir function body'
398 add_checks_for_function(test
, output_lines
, run_list
,
399 func_dict
, func_name
, single_bb
=False,
402 if should_add_line_to_output(input_line
, prefix_set
):
403 output_lines
.append(input_line
)
404 elif state
== 'ir function body':
405 if input_line
.strip() == '}':
408 if should_add_line_to_output(input_line
, prefix_set
):
409 output_lines
.append(input_line
)
412 log('Writing {} lines to {}...'.format(len(output_lines
), test
), verbose
)
414 with
open(test
, 'wb') as fd
:
415 fd
.writelines(['{}\n'.format(l
).encode('utf-8') for l
in output_lines
])
419 parser
= argparse
.ArgumentParser(
420 description
=__doc__
, formatter_class
=argparse
.RawTextHelpFormatter
)
421 parser
.add_argument('-v', '--verbose', action
='store_true',
422 help='Show verbose output')
423 parser
.add_argument('--llc-binary', dest
='llc', default
='llc', type=LLC
,
424 help='The "llc" binary to generate the test case with')
425 parser
.add_argument('--remove-common-prefixes', action
='store_true',
426 help='Remove existing check lines whose prefixes are '
427 'shared between multiple commands')
428 parser
.add_argument('tests', nargs
='+')
429 args
= parser
.parse_args()
431 test_paths
= [test
for pattern
in args
.tests
for test
in glob
.glob(pattern
)]
432 for test
in test_paths
:
434 update_test_file(args
.llc
, test
, args
.remove_common_prefixes
,
435 verbose
=args
.verbose
)
437 warn('Error processing file', test_file
=test
)
441 if __name__
== '__main__':