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 ''
126 if not llc_cmd
.startswith('llc '):
127 warn('Skipping non-llc RUN line: {}'.format(l
), test_file
=test
)
129 if not filecheck_cmd
.startswith('FileCheck '):
130 warn('Skipping non-FileChecked RUN line: {}'.format(l
),
135 m
= common
.TRIPLE_ARG_RE
.search(llc_cmd
)
138 # If we find -march but not -mtriple, use that.
139 m
= common
.MARCH_ARG_RE
.search(llc_cmd
)
141 triple
= '{}--'.format(m
.group(1))
143 cmd_args
= llc_cmd
[len('llc'):].strip()
144 cmd_args
= cmd_args
.replace('< %s', '').replace('%s', '').strip()
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])
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):
170 m
= MIR_FUNC_NAME_RE
.match(line
)
173 result
.append(cur_func
)
174 cur_func
= m
.group('func')
176 m
= MIR_BASIC_BLOCK_RE
.match(line
)
180 result
.append(cur_func
)
184 def build_function_body_dictionary(test
, raw_tool_output
, triple
, prefixes
,
186 for m
in MIR_FUNC_RE
.finditer(raw_tool_output
):
187 func
= m
.group('func')
188 body
= m
.group('body')
190 log('Processing function: {}'.format(func
))
191 for l
in body
.splitlines():
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
),
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()
204 for prefix
in run
.prefixes
:
205 if prefix
in printed_prefixes
:
207 if not func_dict
[prefix
][func_name
]:
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())
220 def add_check_lines(test
, output_lines
, prefix
, func_name
, single_bb
,
223 # Don't bother checking the basic block label for a single BB
227 warn('Function has no instructions to check: {}'.format(func_name
),
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
))
239 for func_line
in func_body
:
240 if not func_line
.strip():
242 m
= VREG_DEF_RE
.match(func_line
)
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
),
252 check_line
= '{}: {}'.format(check
, func_line
[indent
:]).rstrip()
253 output_lines
.append(check_line
)
256 def mangle_vreg(opcode
, current_names
):
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',
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
):
280 for name
in current_names
:
281 if name
.rstrip('0123456789') == base
:
284 return '{}{}'.format(base
, i
)
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
:
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
)
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
)
320 build_function_body_dictionary(test
, raw_tool_output
,
321 triple_in_cmd
or triple_in_ir
,
322 prefixes
, func_dict
, verbose
)
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
),
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__
)))
340 output_lines
.append(autogenerated_note
)
342 for input_line
in input_lines
:
343 if input_line
== autogenerated_note
:
346 if state
== 'toplevel':
347 m
= IR_FUNC_NAME_RE
.match(input_line
)
349 state
= 'ir function prefix'
350 func_name
= m
.group('func')
351 if input_line
.rstrip('| \r\n') == '---':
353 output_lines
.append(input_line
)
354 elif state
== 'document':
355 m
= MIR_FUNC_NAME_RE
.match(input_line
)
357 state
= 'mir function metadata'
358 func_name
= m
.group('func')
359 if input_line
.strip() == '...':
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
)
369 if func_name
in simple_functions
:
370 # If there's only one block, put the checks inside it
371 state
= 'mir function prefix'
373 state
= 'mir function body'
374 add_checks_for_function(test
, output_lines
, run_list
,
375 func_dict
, func_name
, single_bb
=False,
377 elif state
== 'mir function prefix':
378 m
= MIR_PREFIX_DATA_RE
.match(input_line
)
380 state
= 'mir function body'
381 add_checks_for_function(test
, output_lines
, run_list
,
382 func_dict
, func_name
, single_bb
=True,
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() == '...':
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
)
396 state
= 'ir function body'
397 add_checks_for_function(test
, output_lines
, run_list
,
398 func_dict
, func_name
, single_bb
=False,
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() == '}':
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
])
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
:
433 update_test_file(args
.llc
, test
, args
.remove_common_prefixes
,
434 verbose
=args
.verbose
)
436 warn('Error processing file', test_file
=test
)
440 if __name__
== '__main__':