2 '''A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.
4 Example RUN lines in .c/.cc test files:
6 // RUN: %clang -emit-llvm -S %s -o - -O2 | FileCheck %s
7 // RUN: %clangxx -emit-llvm -S %s -o - -O2 | FileCheck -check-prefix=CHECK-A %s
11 % utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc
12 % utils/update_cc_test_checks.py --clang=release/bin/clang /tmp/c/a.cc
15 from __future__
import print_function
19 import distutils
.spawn
28 from UpdateTestChecks
import common
32 '%clang_cc1': ['-cc1'],
33 '%clangxx': ['--driver-mode=g++'],
36 def get_line2func_list(args
, clang_args
):
37 ret
= collections
.defaultdict(list)
38 # Use clang's JSON AST dump to get the mangled name
39 json_dump_args
= [args
.clang
] + clang_args
+ ['-fsyntax-only', '-o', '-']
40 if '-cc1' not in json_dump_args
:
41 # For tests that invoke %clang instead if %clang_cc1 we have to use
42 # -Xclang -ast-dump=json instead:
43 json_dump_args
.append('-Xclang')
44 json_dump_args
.append('-ast-dump=json')
45 common
.debug('Running', ' '.join(json_dump_args
))
47 popen
= subprocess
.Popen(json_dump_args
, stdout
=subprocess
.PIPE
,
48 stderr
=subprocess
.PIPE
, universal_newlines
=True)
49 stdout
, stderr
= popen
.communicate()
50 if popen
.returncode
!= 0:
51 sys
.stderr
.write('Failed to run ' + ' '.join(json_dump_args
) + '\n')
52 sys
.stderr
.write(stderr
)
53 sys
.stderr
.write(stdout
)
56 # Parse the clang JSON and add all children of type FunctionDecl.
57 # TODO: Should we add checks for global variables being emitted?
58 def parse_clang_ast_json(node
, loc
, search
):
59 node_kind
= node
['kind']
60 # Recurse for the following nodes that can contain nested function decls:
61 if node_kind
in ('NamespaceDecl', 'LinkageSpecDecl', 'TranslationUnitDecl',
62 'CXXRecordDecl', 'ClassTemplateSpecializationDecl'):
63 # Specializations must use the loc from the specialization, not the
64 # template, and search for the class's spelling as the specialization
65 # does not mention the method names in the source.
66 if node_kind
== 'ClassTemplateSpecializationDecl':
67 inner_loc
= node
['loc']
68 inner_search
= node
['name']
73 for inner
in node
['inner']:
74 parse_clang_ast_json(inner
, inner_loc
, inner_search
)
75 # Otherwise we ignore everything except functions:
76 if node_kind
not in ('FunctionDecl', 'CXXMethodDecl', 'CXXConstructorDecl',
77 'CXXDestructorDecl', 'CXXConversionDecl'):
81 if node
.get('isImplicit') is True and node
.get('storageClass') == 'extern':
82 common
.debug('Skipping builtin function:', node
['name'], '@', loc
)
84 common
.debug('Found function:', node
['kind'], node
['name'], '@', loc
)
85 line
= loc
.get('line')
86 # If there is no line it is probably a builtin function -> skip
88 common
.debug('Skipping function without line number:', node
['name'], '@', loc
)
91 # If there is no 'inner' object, it is a function declaration and we can
92 # skip it. However, function declarations may also contain an 'inner' list,
93 # but in that case it will only contains ParmVarDecls. If we find an entry
94 # that is not a ParmVarDecl, we know that this is a function definition.
97 for i
in node
['inner']:
98 if i
.get('kind', 'ParmVarDecl') != 'ParmVarDecl':
102 common
.debug('Skipping function without body:', node
['name'], '@', loc
)
107 mangled
= node
.get('mangledName', spell
)
108 ret
[int(line
)-1].append((spell
, mangled
, search
))
110 ast
= json
.loads(stdout
)
111 if ast
['kind'] != 'TranslationUnitDecl':
112 common
.error('Clang AST dump JSON format changed?')
114 parse_clang_ast_json(ast
, None, None)
116 for line
, funcs
in sorted(ret
.items()):
118 common
.debug('line {}: found function {}'.format(line
+1, func
), file=sys
.stderr
)
120 common
.warn('Did not find any functions using', ' '.join(json_dump_args
))
124 def str_to_commandline(value
):
127 return shlex
.split(value
)
130 def infer_dependent_args(args
):
132 if not args
.llvm_bin
:
135 args
.clang
= os
.path
.join(args
.llvm_bin
, 'clang')
137 if not args
.llvm_bin
:
140 args
.opt
= os
.path
.join(args
.llvm_bin
, 'opt')
144 parser
= argparse
.ArgumentParser(
146 formatter_class
=argparse
.RawTextHelpFormatter
)
147 parser
.add_argument('--llvm-bin', help='llvm $prefix/bin path')
148 parser
.add_argument('--clang',
149 help='"clang" executable, defaults to $llvm_bin/clang')
150 parser
.add_argument('--clang-args', default
=[], type=str_to_commandline
,
151 help='Space-separated extra args to clang, e.g. --clang-args=-v')
152 parser
.add_argument('--opt',
153 help='"opt" executable, defaults to $llvm_bin/opt')
155 '--functions', nargs
='+', help='A list of function name regexes. '
156 'If specified, update CHECK lines for functions matching at least one regex')
158 '--x86_extra_scrub', action
='store_true',
159 help='Use more regex for x86 matching to reduce diffs between various subtargets')
160 parser
.add_argument('--function-signature', action
='store_true',
161 help='Keep function signature information around for the check line')
162 parser
.add_argument('--check-attributes', action
='store_true',
163 help='Check "Function Attributes" for functions')
164 parser
.add_argument('--check-globals', action
='store_true',
165 help='Check global entries (global variables, metadata, attribute sets, ...) for functions')
166 parser
.add_argument('tests', nargs
='+')
167 args
= common
.parse_commandline_args(parser
)
168 infer_dependent_args(args
)
170 if not distutils
.spawn
.find_executable(args
.clang
):
171 print('Please specify --llvm-bin or --clang', file=sys
.stderr
)
174 # Determine the builtin includes directory so that we can update tests that
175 # depend on the builtin headers. See get_clang_builtin_include_dir() and
176 # use_clang() in llvm/utils/lit/lit/llvm/config.py.
178 builtin_include_dir
= subprocess
.check_output(
179 [args
.clang
, '-print-file-name=include']).decode().strip()
180 SUBST
['%clang_cc1'] = ['-cc1', '-internal-isystem', builtin_include_dir
,
182 except subprocess
.CalledProcessError
:
183 common
.warn('Could not determine clang builtins directory, some tests '
184 'might not update correctly.')
186 if not distutils
.spawn
.find_executable(args
.opt
):
187 # Many uses of this tool will not need an opt binary, because it's only
188 # needed for updating a test that runs clang | opt | FileCheck. So we
189 # defer this error message until we find that opt is actually needed.
195 def get_function_body(builder
, args
, filename
, clang_args
, extra_commands
,
197 # TODO Clean up duplication of asm/common build_function_body_dictionary
198 # Invoke external tool and extract function bodies.
199 raw_tool_output
= common
.invoke_tool(args
.clang
, clang_args
, filename
)
200 for extra_command
in extra_commands
:
201 extra_args
= shlex
.split(extra_command
)
202 with tempfile
.NamedTemporaryFile() as f
:
203 f
.write(raw_tool_output
.encode())
205 if extra_args
[0] == 'opt':
207 print(filename
, 'needs to run opt. '
208 'Please specify --llvm-bin or --opt', file=sys
.stderr
)
210 extra_args
[0] = args
.opt
211 raw_tool_output
= common
.invoke_tool(extra_args
[0],
212 extra_args
[1:], f
.name
)
213 if '-emit-llvm' in clang_args
:
214 builder
.process_run_line(
215 common
.OPT_FUNCTION_RE
, common
.scrub_body
, raw_tool_output
,
217 builder
.processed_prefixes(prefixes
)
219 print('The clang command line should include -emit-llvm as asm tests '
220 'are discouraged in Clang testsuite.', file=sys
.stderr
)
223 def exec_run_line(exe
):
224 popen
= subprocess
.Popen(exe
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, universal_newlines
=True)
225 stdout
, stderr
= popen
.communicate()
226 if popen
.returncode
!= 0:
227 sys
.stderr
.write('Failed to run ' + ' '.join(exe
) + '\n')
228 sys
.stderr
.write(stderr
)
229 sys
.stderr
.write(stdout
)
233 initial_args
, parser
= config()
234 script_name
= os
.path
.basename(__file__
)
236 for ti
in common
.itertests(initial_args
.tests
, parser
, 'utils/' + script_name
,
237 comment_prefix
='//', argparse_callback
=infer_dependent_args
):
238 # Build a list of filechecked and non-filechecked RUN lines.
240 line2func_list
= collections
.defaultdict(list)
244 '%t' : tempfile
.NamedTemporaryFile().name
,
245 '%S' : os
.path
.dirname(ti
.path
),
248 for l
in ti
.run_lines
:
249 commands
= [cmd
.strip() for cmd
in l
.split('|')]
252 m
= common
.TRIPLE_ARG_RE
.search(commands
[0])
254 triple_in_cmd
= m
.groups()[0]
256 # Parse executable args.
257 exec_args
= shlex
.split(commands
[0])
258 # Execute non-clang runline.
259 if exec_args
[0] not in SUBST
:
260 # Do lit-like substitutions.
262 exec_args
= [i
.replace(s
, subs
[s
]) if s
in i
else i
for i
in exec_args
]
263 run_list
.append((None, exec_args
, None, None))
265 # This is a clang runline, apply %clang substitution rule, do lit-like substitutions,
266 # and append args.clang_args
267 clang_args
= exec_args
268 clang_args
[0:1] = SUBST
[clang_args
[0]]
270 clang_args
= [i
.replace(s
, subs
[s
]) if s
in i
else i
for i
in clang_args
]
271 clang_args
+= ti
.args
.clang_args
273 # Extract -check-prefix in FileCheck args
274 filecheck_cmd
= commands
[-1]
275 common
.verify_filecheck_prefixes(filecheck_cmd
)
276 if not filecheck_cmd
.startswith('FileCheck '):
277 # Execute non-filechecked clang runline.
278 exe
= [ti
.args
.clang
] + clang_args
279 run_list
.append((None, exe
, None, None))
282 check_prefixes
= [item
for m
in common
.CHECK_PREFIX_RE
.finditer(filecheck_cmd
)
283 for item
in m
.group(1).split(',')]
284 if not check_prefixes
:
285 check_prefixes
= ['CHECK']
286 run_list
.append((check_prefixes
, clang_args
, commands
[1:-1], triple_in_cmd
))
288 # Execute clang, generate LLVM IR, and extract functions.
290 # Store only filechecked runlines.
291 filecheck_run_list
= [i
for i
in run_list
if i
[0]]
292 builder
= common
.FunctionTestBuilder(
293 run_list
=filecheck_run_list
,
298 for prefixes
, args
, extra_commands
, triple_in_cmd
in run_list
:
299 # Execute non-filechecked runline.
301 print('NOTE: Executing non-FileChecked RUN line: ' + ' '.join(args
), file=sys
.stderr
)
306 common
.debug('Extracted clang cmd: clang {}'.format(clang_args
))
307 common
.debug('Extracted FileCheck prefixes: {}'.format(prefixes
))
309 get_function_body(builder
, ti
.args
, ti
.path
, clang_args
, extra_commands
,
312 # Invoke clang -Xclang -ast-dump=json to get mapping from start lines to
313 # mangled names. Forward all clang args for now.
314 for k
, v
in get_line2func_list(ti
.args
, clang_args
).items():
315 line2func_list
[k
].extend(v
)
317 func_dict
= builder
.finish_and_get_func_dict()
318 global_vars_seen_dict
= {}
319 prefix_set
= set([prefix
for p
in filecheck_run_list
for prefix
in p
[0]])
321 has_checked_pre_function_globals
= False
323 include_generated_funcs
= common
.find_arg_in_test(ti
,
324 lambda args
: ti
.args
.include_generated_funcs
,
325 '--include-generated-funcs',
328 if include_generated_funcs
:
329 # Generate the appropriate checks for each function. We need to emit
330 # these in the order according to the generated output so that CHECK-LABEL
331 # works properly. func_order provides that.
333 # It turns out that when clang generates functions (for example, with
334 # -fopenmp), it can sometimes cause functions to be re-ordered in the
335 # output, even functions that exist in the source file. Therefore we
336 # can't insert check lines before each source function and instead have to
337 # put them at the end. So the first thing to do is dump out the source
339 common
.dump_input_lines(output_lines
, ti
, prefix_set
, '//')
341 # Now generate all the checks.
342 def check_generator(my_output_lines
, prefixes
, func
):
343 if '-emit-llvm' in clang_args
:
344 return common
.add_ir_checks(my_output_lines
, '//',
346 func_dict
, func
, False,
347 ti
.args
.function_signature
,
348 global_vars_seen_dict
,
349 is_filtered
=builder
.is_filtered())
351 return asm
.add_checks(my_output_lines
, '//',
353 func_dict
, func
, global_vars_seen_dict
,
354 is_filtered
=builder
.is_filtered())
356 if ti
.args
.check_globals
:
357 common
.add_global_checks(builder
.global_var_dict(), '//', run_list
,
358 output_lines
, global_vars_seen_dict
, True,
360 common
.add_checks_at_end(output_lines
, filecheck_run_list
, builder
.func_order(),
361 '//', lambda my_output_lines
, prefixes
, func
:
362 check_generator(my_output_lines
,
365 # Normal mode. Put checks before each source function.
366 for line_info
in ti
.iterlines(output_lines
):
367 idx
= line_info
.line_number
368 line
= line_info
.line
369 args
= line_info
.args
371 m
= common
.CHECK_RE
.match(line
)
372 if m
and m
.group(1) in prefix_set
:
373 continue # Don't append the existing CHECK lines
374 # Skip special separator comments added by commmon.add_global_checks.
375 if line
.strip() == '//' + common
.SEPARATOR
:
377 if idx
in line2func_list
:
379 for spell
, mangled
, search
in line2func_list
[idx
]:
380 # One line may contain multiple function declarations.
381 # Skip if the mangled name has been added before.
382 # The line number may come from an included file, we simply require
383 # the search string (normally the function's spelling name, but is
384 # the class's spelling name for class specializations) to appear on
385 # the line to exclude functions from other files.
386 if mangled
in added
or search
not in line
:
388 if args
.functions
is None or any(re
.search(regex
, spell
) for regex
in args
.functions
):
389 last_line
= output_lines
[-1].strip()
390 while last_line
== '//':
391 # Remove the comment line since we will generate a new comment
392 # line as part of common.add_ir_checks()
394 last_line
= output_lines
[-1].strip()
395 if ti
.args
.check_globals
and not has_checked_pre_function_globals
:
396 common
.add_global_checks(builder
.global_var_dict(), '//',
397 run_list
, output_lines
,
398 global_vars_seen_dict
, True, True)
399 has_checked_pre_function_globals
= True
401 output_lines
.append('//')
403 common
.add_ir_checks(output_lines
, '//', filecheck_run_list
, func_dict
, mangled
,
404 False, args
.function_signature
, global_vars_seen_dict
,
405 is_filtered
=builder
.is_filtered())
406 if line
.rstrip('\n') == '//':
410 output_lines
.append(line
.rstrip('\n'))
412 if ti
.args
.check_globals
:
413 common
.add_global_checks(builder
.global_var_dict(), '//', run_list
,
414 output_lines
, global_vars_seen_dict
, True, False)
415 common
.debug('Writing %d lines to %s...' % (len(output_lines
), ti
.path
))
416 with
open(ti
.path
, 'wb') as f
:
417 f
.writelines(['{}\n'.format(l
).encode('utf-8') for l
in output_lines
])
422 if __name__
== '__main__':