598 lines · python
1#!/usr/bin/env python32"""A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.3 4Example RUN lines in .c/.cc test files:5 6// RUN: %clang -emit-llvm -S %s -o - -O2 | FileCheck %s7// RUN: %clangxx -emit-llvm -S %s -o - -O2 | FileCheck -check-prefix=CHECK-A %s8 9Usage:10 11% utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc12% utils/update_cc_test_checks.py --clang=release/bin/clang /tmp/c/a.cc13"""14 15from __future__ import print_function16 17from sys import stderr18from traceback import print_exc19import argparse20import collections21import json22import os23import re24import shlex25import shutil26import subprocess27import sys28import tempfile29 30from UpdateTestChecks import common31 32SUBST = {33 "%clang": [],34 "%clang_cc1": ["-cc1"],35 "%clangxx": ["--driver-mode=g++"],36}37 38 39def get_line2func_list(args, clang_args, globals_name_prefix):40 ret = collections.defaultdict(list)41 # Use clang's JSON AST dump to get the mangled name42 json_dump_args = [args.clang] + clang_args + ["-fsyntax-only", "-o", "-"]43 if "-cc1" not in json_dump_args:44 # For tests that invoke %clang instead if %clang_cc1 we have to use45 # -Xclang -ast-dump=json instead:46 json_dump_args.append("-Xclang")47 json_dump_args.append("-ast-dump=json")48 common.debug("Running", " ".join(json_dump_args))49 50 popen = subprocess.Popen(51 json_dump_args,52 stdout=subprocess.PIPE,53 stderr=subprocess.PIPE,54 universal_newlines=True,55 )56 stdout, stderr = popen.communicate()57 if popen.returncode != 0:58 sys.stderr.write("Failed to run " + " ".join(json_dump_args) + "\n")59 sys.stderr.write(stderr)60 sys.stderr.write(stdout)61 sys.exit(2)62 63 # Parse the clang JSON and add all children of type FunctionDecl.64 # TODO: Should we add checks for global variables being emitted?65 def parse_clang_ast_json(node, loc, search):66 node_kind = node["kind"]67 # Recurse for the following nodes that can contain nested function decls:68 if node_kind in (69 "NamespaceDecl",70 "LinkageSpecDecl",71 "TranslationUnitDecl",72 "CXXRecordDecl",73 "ClassTemplateSpecializationDecl",74 ):75 # Specializations must use the loc from the specialization, not the76 # template, and search for the class's spelling as the specialization77 # does not mention the method names in the source.78 if node_kind == "ClassTemplateSpecializationDecl":79 inner_loc = node["loc"]80 inner_search = node["name"]81 else:82 inner_loc = None83 inner_search = None84 if "inner" in node:85 for inner in node["inner"]:86 parse_clang_ast_json(inner, inner_loc, inner_search)87 # Otherwise we ignore everything except functions:88 if node_kind not in (89 "FunctionDecl",90 "CXXMethodDecl",91 "CXXConstructorDecl",92 "CXXDestructorDecl",93 "CXXConversionDecl",94 ):95 return96 if loc is None:97 loc = node["loc"]98 if node.get("isImplicit") is True and node.get("storageClass") == "extern":99 common.debug("Skipping builtin function:", node["name"], "@", loc)100 return101 common.debug("Found function:", node["kind"], node["name"], "@", loc)102 line = loc.get("line")103 # If there is no line it is probably a builtin function -> skip104 if line is None:105 common.debug(106 "Skipping function without line number:", node["name"], "@", loc107 )108 return109 110 # If there is no 'inner' object, it is a function declaration and we can111 # skip it. However, function declarations may also contain an 'inner' list,112 # but in that case it will only contains ParmVarDecls. If we find an entry113 # that is not a ParmVarDecl, we know that this is a function definition.114 has_body = False115 if "inner" in node:116 for i in node["inner"]:117 if i.get("kind", "ParmVarDecl") != "ParmVarDecl":118 has_body = True119 break120 if not has_body:121 common.debug("Skipping function without body:", node["name"], "@", loc)122 return123 spell = node["name"]124 if search is None:125 search = spell126 mangled = node.get("mangledName", spell)127 # Clang's AST dump includes the globals prefix, but when Clang emits128 # LLVM IR this is not included and instead added as part of the asm129 # output. Strip it from the mangled name of globals when needed130 # (see DataLayout::getGlobalPrefix()).131 if globals_name_prefix:132 storage = node.get("storageClass", None)133 if storage != "static" and mangled[0] == globals_name_prefix:134 mangled = mangled[1:]135 ret[int(line) - 1].append((spell, mangled, search))136 137 ast = json.loads(stdout)138 if ast["kind"] != "TranslationUnitDecl":139 common.error("Clang AST dump JSON format changed?")140 sys.exit(2)141 parse_clang_ast_json(ast, None, None)142 143 for line, funcs in sorted(ret.items()):144 for func in funcs:145 common.debug(146 "line {}: found function {}".format(line + 1, func), file=sys.stderr147 )148 if not ret:149 common.warn("Did not find any functions using", " ".join(json_dump_args))150 return ret151 152 153def str_to_commandline(value):154 if not value:155 return []156 return shlex.split(value)157 158 159def infer_dependent_args(args):160 if not args.clang:161 if not args.llvm_bin:162 args.clang = "clang"163 else:164 args.clang = os.path.join(args.llvm_bin, "clang")165 if not args.opt:166 if not args.llvm_bin:167 args.opt = "opt"168 else:169 args.opt = os.path.join(args.llvm_bin, "opt")170 171 172def find_executable(executable):173 _, ext = os.path.splitext(executable)174 if sys.platform == "win32" and ext != ".exe":175 executable = executable + ".exe"176 177 return shutil.which(executable)178 179 180def config():181 parser = argparse.ArgumentParser(182 description=__doc__, formatter_class=argparse.RawTextHelpFormatter183 )184 parser.add_argument("--llvm-bin", help="llvm $prefix/bin path")185 parser.add_argument(186 "--clang", help='"clang" executable, defaults to $llvm_bin/clang'187 )188 parser.add_argument(189 "--clang-args",190 default=[],191 type=str_to_commandline,192 help="Space-separated extra args to clang, e.g. --clang-args=-v",193 )194 parser.add_argument("--opt", help='"opt" executable, defaults to $llvm_bin/opt')195 parser.add_argument(196 "--functions",197 nargs="+",198 help="A list of function name regexes. "199 "If specified, update CHECK lines for functions matching at least one regex",200 )201 parser.add_argument(202 "--x86_extra_scrub",203 action="store_true",204 help="Use more regex for x86 matching to reduce diffs between various subtargets",205 )206 parser.add_argument(207 "--function-signature",208 action="store_true",209 help="Keep function signature information around for the check line",210 )211 parser.add_argument(212 "--check-attributes",213 action="store_true",214 help='Check "Function Attributes" for functions',215 )216 parser.add_argument(217 "--check-globals",218 nargs="?",219 const="all",220 default="default",221 choices=["none", "smart", "all"],222 help="Check global entries (global variables, metadata, attribute sets, ...) for functions",223 )224 parser.add_argument("tests", nargs="+")225 args = common.parse_commandline_args(parser)226 infer_dependent_args(args)227 228 if not find_executable(args.clang):229 print("Please specify --llvm-bin or --clang", file=sys.stderr)230 sys.exit(1)231 232 # Determine the builtin includes directory so that we can update tests that233 # depend on the builtin headers. See get_clang_builtin_include_dir() and234 # use_clang() in llvm/utils/lit/lit/llvm/config.py.235 try:236 builtin_include_dir = (237 subprocess.check_output([args.clang, "-print-file-name=include"])238 .decode()239 .strip()240 )241 SUBST["%clang_cc1"] = [242 "-cc1",243 "-internal-isystem",244 builtin_include_dir,245 "-nostdsysteminc",246 ]247 except subprocess.CalledProcessError:248 common.warn(249 "Could not determine clang builtins directory, some tests "250 "might not update correctly."251 )252 253 if not find_executable(args.opt):254 # Many uses of this tool will not need an opt binary, because it's only255 # needed for updating a test that runs clang | opt | FileCheck. So we256 # defer this error message until we find that opt is actually needed.257 args.opt = None258 259 return args, parser260 261 262def get_function_body(263 builder, args, filename, clang_args, extra_commands, prefixes, raw_tool_output264):265 # TODO Clean up duplication of asm/common build_function_body_dictionary266 for extra_command in extra_commands:267 extra_args = shlex.split(extra_command)268 with tempfile.NamedTemporaryFile() as f:269 f.write(raw_tool_output.encode())270 f.flush()271 if extra_args[0] == "opt":272 if args.opt is None:273 print(274 filename,275 "needs to run opt. " "Please specify --llvm-bin or --opt",276 file=sys.stderr,277 )278 sys.exit(1)279 extra_args[0] = args.opt280 raw_tool_output = common.invoke_tool(extra_args[0], extra_args[1:], f.name)281 if "-emit-llvm" in clang_args:282 builder.process_run_line(283 common.OPT_FUNCTION_RE, common.scrub_body, raw_tool_output, prefixes284 )285 builder.processed_prefixes(prefixes)286 else:287 print(288 "The clang command line should include -emit-llvm as asm tests "289 "are discouraged in Clang testsuite.",290 file=sys.stderr,291 )292 sys.exit(1)293 294 295def exec_run_line(exe):296 popen = subprocess.Popen(297 exe, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True298 )299 stdout, stderr = popen.communicate()300 if popen.returncode != 0:301 sys.stderr.write("Failed to run " + " ".join(exe) + "\n")302 sys.stderr.write(stderr)303 sys.stderr.write(stdout)304 sys.exit(3)305 306 307def update_test(ti: common.TestInfo):308 # Build a list of filechecked and non-filechecked RUN lines.309 run_list = []310 line2func_list = collections.defaultdict(list)311 312 subs = {313 "%s": ti.path,314 "%t": tempfile.NamedTemporaryFile().name,315 "%S": os.path.dirname(ti.path),316 }317 318 for l in ti.run_lines:319 commands = [cmd.strip() for cmd in l.split("|")]320 321 triple_in_cmd = None322 m = common.TRIPLE_ARG_RE.search(commands[0])323 if m:324 triple_in_cmd = m.groups()[0]325 326 # Parse executable args.327 exec_args = shlex.split(commands[0])328 # Execute non-clang runline.329 if exec_args[0] not in SUBST:330 # Do lit-like substitutions.331 for s in subs:332 exec_args = [i.replace(s, subs[s]) if s in i else i for i in exec_args]333 run_list.append((None, exec_args, None, None))334 continue335 # This is a clang runline, apply %clang substitution rule, do lit-like substitutions,336 # and append args.clang_args337 clang_args = exec_args338 clang_args[0:1] = SUBST[clang_args[0]]339 for s in subs:340 clang_args = [i.replace(s, subs[s]) if s in i else i for i in clang_args]341 clang_args += ti.args.clang_args342 343 # Extract -check-prefix in FileCheck args344 filecheck_cmd = commands[-1]345 common.verify_filecheck_prefixes(filecheck_cmd)346 if not filecheck_cmd.startswith("FileCheck "):347 # Execute non-filechecked clang runline.348 exe = [ti.args.clang] + clang_args349 run_list.append((None, exe, None, None))350 continue351 352 check_prefixes = common.get_check_prefixes(filecheck_cmd)353 run_list.append((check_prefixes, clang_args, commands[1:-1], triple_in_cmd))354 355 # Execute clang, generate LLVM IR, and extract functions.356 357 # Store only filechecked runlines.358 filecheck_run_list = [i for i in run_list if i[0]]359 ginfo = common.make_ir_generalizer(ti.args.version, ti.args.check_globals == "none")360 builder = common.FunctionTestBuilder(361 run_list=filecheck_run_list,362 flags=ti.args,363 scrubber_args=[],364 path=ti.path,365 ginfo=ginfo,366 )367 368 global_tbaa_records_for_prefixes = {}369 for prefixes, args, extra_commands, triple_in_cmd in run_list:370 # Execute non-filechecked runline.371 if not prefixes:372 print(373 "NOTE: Executing non-FileChecked RUN line: " + " ".join(args),374 file=sys.stderr,375 )376 exec_run_line(args)377 continue378 379 clang_args = args380 common.debug("Extracted clang cmd: clang {}".format(clang_args))381 common.debug("Extracted FileCheck prefixes: {}".format(prefixes))382 383 # Invoke external tool and extract function bodies.384 raw_tool_output = common.invoke_tool(ti.args.clang, clang_args, ti.path)385 get_function_body(386 builder,387 ti.args,388 ti.path,389 clang_args,390 extra_commands,391 prefixes,392 raw_tool_output,393 )394 395 # Extract TBAA metadata for later usage in check lines.396 tbaa_map = common.get_tbaa_records(ti.args.version, raw_tool_output)397 global_tbaa_records_for_prefixes[tuple(prefixes)] = tbaa_map398 399 # Invoke clang -Xclang -ast-dump=json to get mapping from start lines to400 # mangled names. Forward all clang args for now.401 for k, v in get_line2func_list(402 ti.args, clang_args, common.get_globals_name_prefix(raw_tool_output)403 ).items():404 line2func_list[k].extend(v)405 406 func_dict = builder.finish_and_get_func_dict()407 global_vars_seen_dict = {}408 prefix_set = set([prefix for p in filecheck_run_list for prefix in p[0]])409 output_lines = []410 has_checked_pre_function_globals = False411 412 include_generated_funcs = common.find_arg_in_test(413 ti,414 lambda args: ti.args.include_generated_funcs,415 "--include-generated-funcs",416 True,417 )418 generated_prefixes = []419 if include_generated_funcs:420 # Generate the appropriate checks for each function. We need to emit421 # these in the order according to the generated output so that CHECK-LABEL422 # works properly. func_order provides that.423 424 # It turns out that when clang generates functions (for example, with425 # -fopenmp), it can sometimes cause functions to be re-ordered in the426 # output, even functions that exist in the source file. Therefore we427 # can't insert check lines before each source function and instead have to428 # put them at the end. So the first thing to do is dump out the source429 # lines.430 common.dump_input_lines(output_lines, ti, prefix_set, "//")431 432 # Now generate all the checks.433 def check_generator(my_output_lines, prefixes, func):434 return common.add_ir_checks(435 my_output_lines,436 "//",437 prefixes,438 func_dict,439 func,440 False,441 ti.args.function_signature,442 ginfo,443 global_vars_seen_dict,444 global_tbaa_records_for_prefixes,445 is_filtered=builder.is_filtered(),446 )447 448 if ti.args.check_globals != "none":449 generated_prefixes.extend(450 common.add_global_checks(451 builder.global_var_dict(),452 "//",453 run_list,454 output_lines,455 ginfo,456 global_vars_seen_dict,457 global_tbaa_records_for_prefixes,458 False,459 True,460 ti.args.check_globals,461 )462 )463 generated_prefixes.extend(464 common.add_checks_at_end(465 output_lines,466 filecheck_run_list,467 builder.func_order(),468 "//",469 lambda my_output_lines, prefixes, func: check_generator(470 my_output_lines, prefixes, func471 ),472 )473 )474 else:475 # Normal mode. Put checks before each source function.476 for line_info in ti.iterlines(output_lines):477 idx = line_info.line_number478 line = line_info.line479 args = line_info.args480 include_line = True481 m = common.CHECK_RE.match(line)482 if m and m.group(1) in prefix_set:483 continue # Don't append the existing CHECK lines484 # Skip special separator comments added by commmon.add_global_checks.485 if line.strip() == "//" + common.SEPARATOR:486 continue487 if idx in line2func_list:488 added = set()489 for spell, mangled, search in line2func_list[idx]:490 # One line may contain multiple function declarations.491 # Skip if the mangled name has been added before.492 # The line number may come from an included file, we simply require493 # the search string (normally the function's spelling name, but is494 # the class's spelling name for class specializations) to appear on495 # the line to exclude functions from other files.496 if mangled in added or search not in line:497 continue498 if args.functions is None or any(499 re.search(regex, spell) for regex in args.functions500 ):501 last_line = output_lines[-1].strip()502 while last_line == "//":503 # Remove the comment line since we will generate a new comment504 # line as part of common.add_ir_checks()505 output_lines.pop()506 last_line = output_lines[-1].strip()507 if (508 ti.args.check_globals != "none"509 and not has_checked_pre_function_globals510 ):511 generated_prefixes.extend(512 common.add_global_checks(513 builder.global_var_dict(),514 "//",515 run_list,516 output_lines,517 ginfo,518 global_vars_seen_dict,519 global_tbaa_records_for_prefixes,520 False,521 True,522 ti.args.check_globals,523 )524 )525 has_checked_pre_function_globals = True526 if added:527 output_lines.append("//")528 added.add(mangled)529 generated_prefixes.extend(530 common.add_ir_checks(531 output_lines,532 "//",533 filecheck_run_list,534 func_dict,535 mangled,536 False,537 args.function_signature,538 ginfo,539 global_vars_seen_dict,540 global_tbaa_records_for_prefixes,541 is_filtered=builder.is_filtered(),542 )543 )544 if line.rstrip("\n") == "//":545 include_line = False546 547 if include_line:548 output_lines.append(line.rstrip("\n"))549 550 if ti.args.check_globals != "none":551 generated_prefixes.extend(552 common.add_global_checks(553 builder.global_var_dict(),554 "//",555 run_list,556 output_lines,557 ginfo,558 global_vars_seen_dict,559 global_tbaa_records_for_prefixes,560 False,561 False,562 ti.args.check_globals,563 )564 )565 if ti.args.gen_unused_prefix_body:566 output_lines.extend(567 ti.get_checks_for_unused_prefixes(run_list, generated_prefixes)568 )569 common.debug("Writing %d lines to %s..." % (len(output_lines), ti.path))570 with open(ti.path, "wb") as f:571 f.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines])572 573 574def main():575 initial_args, parser = config()576 script_name = os.path.basename(__file__)577 578 returncode = 0579 for ti in common.itertests(580 initial_args.tests,581 parser,582 "utils/" + script_name,583 comment_prefix="//",584 argparse_callback=infer_dependent_args,585 ):586 try:587 update_test(ti)588 except Exception:589 stderr.write(f"Error: Failed to update test {ti.path}\n")590 print_exc()591 returncode = 1592 593 return returncode594 595 596if __name__ == "__main__":597 sys.exit(main())598