1 #!/usr/bin/env python2.7
3 """A test case update script.
5 This script is a utility to update LLVM X86 'llc' based test cases with new
6 FileCheck patterns. It can either update all of the tests in the file or
7 a single test function.
11 import os
# Used to advertise this file's name ("autogenerated_note").
17 # Invoke the tool that is being tested.
18 def llc(args
, cmd_args
, ir
):
19 with
open(ir
) as ir_file
:
20 stdout
= subprocess
.check_output(args
.llc_binary
+ ' ' + cmd_args
,
21 shell
=True, stdin
=ir_file
)
22 # Fix line endings to unix CR style.
23 stdout
= stdout
.replace('\r\n', '\n')
27 # RegEx: this is where the magic happens.
29 ASM_FUNCTION_X86_RE
= re
.compile(
30 r
'^_?(?P<func>[^:]+):[ \t]*#+[ \t]*@(?P=func)\n[^:]*?'
31 r
'(?P<body>^##?[ \t]+[^:]+:.*?)\s*'
32 r
'^\s*(?:[^:\n]+?:\s*\n\s*\.size|\.cfi_endproc|\.globl|\.comm|\.(?:sub)?section|#+ -- End function)',
35 ASM_FUNCTION_ARM_RE
= re
.compile(
36 r
'^(?P<func>[0-9a-zA-Z_]+):\n' # f: (name of function)
37 r
'\s+\.fnstart\n' # .fnstart
38 r
'(?P<body>.*?)\n' # (body of the function)
39 r
'.Lfunc_end[0-9]+:', # .Lfunc_end0: or # -- End function
42 ASM_FUNCTION_AARCH64_RE
= re
.compile(
43 r
'^_?(?P<func>[^:]+):[ \t]*\/\/[ \t]*@(?P=func)\n'
44 r
'[ \t]+.cfi_startproc\n'
46 # This list is incomplete
47 r
'.Lfunc_end[0-9]+:\n',
50 ASM_FUNCTION_PPC_RE
= re
.compile(
51 r
'^_?(?P<func>[^:]+):[ \t]*#+[ \t]*@(?P=func)\n'
52 r
'\.Lfunc_begin[0-9]+:\n'
53 r
'[ \t]+.cfi_startproc\n'
54 r
'(?:\.Lfunc_[gl]ep[0-9]+:\n(?:[ \t]+.*?\n)*)*'
56 # This list is incomplete
57 r
'(?:^[ \t]*(?:\.long[ \t]+[^\n]+|\.quad[ \t]+[^\n]+)\n)*'
58 r
'.Lfunc_end[0-9]+:\n',
61 ASM_FUNCTION_SYSTEMZ_RE
= re
.compile(
62 r
'^_?(?P<func>[^:]+):[ \t]*#+[ \t]*@(?P=func)\n'
63 r
'[ \t]+.cfi_startproc\n'
65 r
'.Lfunc_end[0-9]+:\n',
69 SCRUB_WHITESPACE_RE
= re
.compile(r
'(?!^(| \w))[ \t]+', flags
=re
.M
)
70 SCRUB_TRAILING_WHITESPACE_RE
= re
.compile(r
'[ \t]+$', flags
=re
.M
)
71 SCRUB_KILL_COMMENT_RE
= re
.compile(r
'^ *#+ +kill:.*\n')
72 SCRUB_LOOP_COMMENT_RE
= re
.compile(
73 r
'# =>This Inner Loop Header:.*|# in Loop:.*', flags
=re
.M
)
75 SCRUB_X86_SHUFFLES_RE
= (
77 r
'^(\s*\w+) [^#\n]+#+ ((?:[xyz]mm\d+|mem)( \{%k\d+\}( \{z\})?)? = .*)$',
79 SCRUB_X86_SP_RE
= re
.compile(r
'\d+\(%(esp|rsp)\)')
80 SCRUB_X86_RIP_RE
= re
.compile(r
'[.\w]+\(%rip\)')
81 SCRUB_X86_LCP_RE
= re
.compile(r
'\.LCPI[0-9]+_[0-9]+')
83 RUN_LINE_RE
= re
.compile('^\s*;\s*RUN:\s*(.*)$')
84 TRIPLE_ARG_RE
= re
.compile(r
'-mtriple=([^ ]+)')
85 TRIPLE_IR_RE
= re
.compile(r
'^target\s+triple\s*=\s*"([^"]+)"$')
86 IR_FUNCTION_RE
= re
.compile('^\s*define\s+(?:internal\s+)?[^@]*@(\w+)\s*\(')
87 CHECK_PREFIX_RE
= re
.compile('--?check-prefix(?:es)?=(\S+)')
88 CHECK_RE
= re
.compile(r
'^\s*;\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:')
90 def scrub_asm_x86(asm
):
91 # Scrub runs of whitespace out of the assembly, but leave the leading
92 # whitespace in place.
93 asm
= SCRUB_WHITESPACE_RE
.sub(r
' ', asm
)
94 # Expand the tabs used for indentation.
95 asm
= string
.expandtabs(asm
, 2)
96 # Detect shuffle asm comments and hide the operands in favor of the comments.
97 asm
= SCRUB_X86_SHUFFLES_RE
.sub(r
'\1 {{.*#+}} \2', asm
)
98 # Generically match the stack offset of a memory operand.
99 asm
= SCRUB_X86_SP_RE
.sub(r
'{{[0-9]+}}(%\1)', asm
)
100 # Generically match a RIP-relative memory operand.
101 asm
= SCRUB_X86_RIP_RE
.sub(r
'{{.*}}(%rip)', asm
)
102 # Generically match a LCP symbol.
103 asm
= SCRUB_X86_LCP_RE
.sub(r
'{{\.LCPI.*}}', asm
)
104 # Strip kill operands inserted into the asm.
105 asm
= SCRUB_KILL_COMMENT_RE
.sub('', asm
)
106 # Strip trailing whitespace.
107 asm
= SCRUB_TRAILING_WHITESPACE_RE
.sub(r
'', asm
)
110 def scrub_asm_arm_eabi(asm
):
111 # Scrub runs of whitespace out of the assembly, but leave the leading
112 # whitespace in place.
113 asm
= SCRUB_WHITESPACE_RE
.sub(r
' ', asm
)
114 # Expand the tabs used for indentation.
115 asm
= string
.expandtabs(asm
, 2)
116 # Strip kill operands inserted into the asm.
117 asm
= SCRUB_KILL_COMMENT_RE
.sub('', asm
)
118 # Strip trailing whitespace.
119 asm
= SCRUB_TRAILING_WHITESPACE_RE
.sub(r
'', asm
)
122 def scrub_asm_powerpc64le(asm
):
123 # Scrub runs of whitespace out of the assembly, but leave the leading
124 # whitespace in place.
125 asm
= SCRUB_WHITESPACE_RE
.sub(r
' ', asm
)
126 # Expand the tabs used for indentation.
127 asm
= string
.expandtabs(asm
, 2)
128 # Stripe unimportant comments
129 asm
= SCRUB_LOOP_COMMENT_RE
.sub(r
'', asm
)
130 # Strip trailing whitespace.
131 asm
= SCRUB_TRAILING_WHITESPACE_RE
.sub(r
'', asm
)
134 def scrub_asm_systemz(asm
):
135 # Scrub runs of whitespace out of the assembly, but leave the leading
136 # whitespace in place.
137 asm
= SCRUB_WHITESPACE_RE
.sub(r
' ', asm
)
138 # Expand the tabs used for indentation.
139 asm
= string
.expandtabs(asm
, 2)
140 # Strip trailing whitespace.
141 asm
= SCRUB_TRAILING_WHITESPACE_RE
.sub(r
'', asm
)
145 # Build up a dictionary of all the function bodies.
146 def build_function_body_dictionary(raw_tool_output
, triple
, prefixes
, func_dict
,
149 'x86_64': (scrub_asm_x86
, ASM_FUNCTION_X86_RE
),
150 'i686': (scrub_asm_x86
, ASM_FUNCTION_X86_RE
),
151 'x86': (scrub_asm_x86
, ASM_FUNCTION_X86_RE
),
152 'i386': (scrub_asm_x86
, ASM_FUNCTION_X86_RE
),
153 'aarch64': (scrub_asm_arm_eabi
, ASM_FUNCTION_AARCH64_RE
),
154 'arm-eabi': (scrub_asm_arm_eabi
, ASM_FUNCTION_ARM_RE
),
155 'thumb-eabi': (scrub_asm_arm_eabi
, ASM_FUNCTION_ARM_RE
),
156 'thumbv8-eabi': (scrub_asm_arm_eabi
, ASM_FUNCTION_ARM_RE
),
157 'armeb-eabi': (scrub_asm_arm_eabi
, ASM_FUNCTION_ARM_RE
),
158 'powerpc64le': (scrub_asm_powerpc64le
, ASM_FUNCTION_PPC_RE
),
159 's390x': (scrub_asm_systemz
, ASM_FUNCTION_SYSTEMZ_RE
),
162 for prefix
, s
in target_handlers
.items():
163 if triple
.startswith(prefix
):
167 raise KeyError('Triple %r is not supported' % (triple
))
169 scrubber
, function_re
= handlers
170 for m
in function_re
.finditer(raw_tool_output
):
173 func
= m
.group('func')
174 scrubbed_body
= scrubber(m
.group('body'))
175 if func
.startswith('stress'):
176 # We only use the last line of the function body for stress tests.
177 scrubbed_body
= '\n'.join(scrubbed_body
.splitlines()[-1:])
179 print >>sys
.stderr
, 'Processing function: ' + func
180 for l
in scrubbed_body
.splitlines():
181 print >>sys
.stderr
, ' ' + l
182 for prefix
in prefixes
:
183 if func
in func_dict
[prefix
] and func_dict
[prefix
][func
] != scrubbed_body
:
184 if prefix
== prefixes
[-1]:
185 print >>sys
.stderr
, ('WARNING: Found conflicting asm under the '
186 'same prefix: %r!' % (prefix
,))
188 func_dict
[prefix
][func
] = None
191 func_dict
[prefix
][func
] = scrubbed_body
194 def add_checks(output_lines
, run_list
, func_dict
, func_name
):
195 printed_prefixes
= []
198 for checkprefix
in checkprefixes
:
199 if checkprefix
in printed_prefixes
:
201 if not func_dict
[checkprefix
][func_name
]:
203 # Add some space between different check prefixes.
204 if len(printed_prefixes
) != 0:
205 output_lines
.append(';')
206 printed_prefixes
.append(checkprefix
)
207 output_lines
.append('; %s-LABEL: %s:' % (checkprefix
, func_name
))
208 func_body
= func_dict
[checkprefix
][func_name
].splitlines()
209 output_lines
.append('; %s: %s' % (checkprefix
, func_body
[0]))
210 for func_line
in func_body
[1:]:
211 output_lines
.append('; %s-NEXT: %s' % (checkprefix
, func_line
))
212 # Add space between different check prefixes and the first line of code.
213 # output_lines.append(';')
218 def should_add_line_to_output(input_line
, prefix_set
):
219 # Skip any blank comment lines in the IR.
220 if input_line
.strip() == ';':
222 # Skip any blank lines in the IR.
223 #if input_line.strip() == '':
225 # And skip any CHECK lines. We're building our own.
226 m
= CHECK_RE
.match(input_line
)
227 if m
and m
.group(1) in prefix_set
:
234 parser
= argparse
.ArgumentParser(description
=__doc__
)
235 parser
.add_argument('-v', '--verbose', action
='store_true',
236 help='Show verbose output')
237 parser
.add_argument('--llc-binary', default
='llc',
238 help='The "llc" binary to use to generate the test case')
240 '--function', help='The function in the test file to update')
241 parser
.add_argument('tests', nargs
='+')
242 args
= parser
.parse_args()
244 autogenerated_note
= ('; NOTE: Assertions have been autogenerated by '
245 'utils/' + os
.path
.basename(__file__
))
247 for test
in args
.tests
:
249 print >>sys
.stderr
, 'Scanning for RUN lines in test file: %s' % (test
,)
250 with
open(test
) as f
:
251 input_lines
= [l
.rstrip() for l
in f
]
254 for l
in input_lines
:
255 m
= TRIPLE_IR_RE
.match(l
)
257 triple_in_ir
= m
.groups()[0]
260 raw_lines
= [m
.group(1)
261 for m
in [RUN_LINE_RE
.match(l
) for l
in input_lines
] if m
]
262 run_lines
= [raw_lines
[0]] if len(raw_lines
) > 0 else []
263 for l
in raw_lines
[1:]:
264 if run_lines
[-1].endswith("\\"):
265 run_lines
[-1] = run_lines
[-1].rstrip("\\") + " " + l
270 print >>sys
.stderr
, 'Found %d RUN lines:' % (len(run_lines
),)
272 print >>sys
.stderr
, ' RUN: ' + l
276 commands
= [cmd
.strip() for cmd
in l
.split('|', 1)]
277 llc_cmd
= commands
[0]
280 m
= TRIPLE_ARG_RE
.search(llc_cmd
)
282 triple_in_cmd
= m
.groups()[0]
285 if len(commands
) > 1:
286 filecheck_cmd
= commands
[1]
287 if not llc_cmd
.startswith('llc '):
288 print >>sys
.stderr
, 'WARNING: Skipping non-llc RUN line: ' + l
291 if not filecheck_cmd
.startswith('FileCheck '):
292 print >>sys
.stderr
, 'WARNING: Skipping non-FileChecked RUN line: ' + l
295 llc_cmd_args
= llc_cmd
[len('llc'):].strip()
296 llc_cmd_args
= llc_cmd_args
.replace('< %s', '').replace('%s', '').strip()
298 check_prefixes
= [item
for m
in CHECK_PREFIX_RE
.finditer(filecheck_cmd
)
299 for item
in m
.group(1).split(',')]
300 if not check_prefixes
:
301 check_prefixes
= ['CHECK']
303 # FIXME: We should use multiple check prefixes to common check lines. For
304 # now, we just ignore all but the last.
305 run_list
.append((check_prefixes
, llc_cmd_args
, triple_in_cmd
))
310 for prefix
in prefixes
:
311 func_dict
.update({prefix
: dict()})
312 for prefixes
, llc_args
, triple_in_cmd
in run_list
:
314 print >>sys
.stderr
, 'Extracted LLC cmd: llc ' + llc_args
315 print >>sys
.stderr
, 'Extracted FileCheck prefixes: ' + str(prefixes
)
317 raw_tool_output
= llc(args
, llc_args
, test
)
318 if not (triple_in_cmd
or triple_in_ir
):
319 print >>sys
.stderr
, "Cannot find a triple. Assume 'x86'"
321 build_function_body_dictionary(raw_tool_output
,
322 triple_in_cmd
or triple_in_ir
or 'x86', prefixes
, func_dict
, args
.verbose
)
324 is_in_function
= False
325 is_in_function_start
= False
327 prefix_set
= set([prefix
for p
in run_list
for prefix
in p
[0]])
329 print >>sys
.stderr
, 'Rewriting FileCheck prefixes: %s' % (prefix_set
,)
331 output_lines
.append(autogenerated_note
)
333 for input_line
in input_lines
:
334 if is_in_function_start
:
337 if input_line
.lstrip().startswith(';'):
338 m
= CHECK_RE
.match(input_line
)
339 if not m
or m
.group(1) not in prefix_set
:
340 output_lines
.append(input_line
)
343 # Print out the various check lines here.
344 output_lines
= add_checks(output_lines
, run_list
, func_dict
, func_name
)
345 is_in_function_start
= False
348 if should_add_line_to_output(input_line
, prefix_set
) == True:
349 # This input line of the function body will go as-is into the output.
350 output_lines
.append(input_line
)
353 if input_line
.strip() == '}':
354 is_in_function
= False
357 if input_line
== autogenerated_note
:
360 # If it's outside a function, it just gets copied to the output.
361 output_lines
.append(input_line
)
363 m
= IR_FUNCTION_RE
.match(input_line
)
366 func_name
= m
.group(1)
367 if args
.function
is not None and func_name
!= args
.function
:
368 # When filtering on a specific function, skip all others.
370 is_in_function
= is_in_function_start
= True
373 print>>sys
.stderr
, 'Writing %d lines to %s...' % (len(output_lines
), test
)
375 with
open(test
, 'wb') as f
:
376 f
.writelines([l
+ '\n' for l
in output_lines
])
379 if __name__
== '__main__':