mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-29 14:36:07 +00:00

This reverts commit 8a3fdf7b908978625e9a7e57fbb443e4e6f98976 as it is broken. See https://github.com/llvm/llvm-project/issues/63746. Effectively fixes: https://github.com/llvm/llvm-project/issues/63746
559 lines
21 KiB
Python
Executable File
559 lines
21 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.
|
|
|
|
Example RUN lines in .c/.cc test files:
|
|
|
|
// RUN: %clang -emit-llvm -S %s -o - -O2 | FileCheck %s
|
|
// RUN: %clangxx -emit-llvm -S %s -o - -O2 | FileCheck -check-prefix=CHECK-A %s
|
|
|
|
Usage:
|
|
|
|
% utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc
|
|
% utils/update_cc_test_checks.py --clang=release/bin/clang /tmp/c/a.cc
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import collections
|
|
import json
|
|
import os
|
|
import re
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
from UpdateTestChecks import common
|
|
|
|
SUBST = {
|
|
"%clang": [],
|
|
"%clang_cc1": ["-cc1"],
|
|
"%clangxx": ["--driver-mode=g++"],
|
|
}
|
|
|
|
|
|
def get_line2func_list(args, clang_args):
|
|
ret = collections.defaultdict(list)
|
|
# Use clang's JSON AST dump to get the mangled name
|
|
json_dump_args = [args.clang] + clang_args + ["-fsyntax-only", "-o", "-"]
|
|
if "-cc1" not in json_dump_args:
|
|
# For tests that invoke %clang instead if %clang_cc1 we have to use
|
|
# -Xclang -ast-dump=json instead:
|
|
json_dump_args.append("-Xclang")
|
|
json_dump_args.append("-ast-dump=json")
|
|
common.debug("Running", " ".join(json_dump_args))
|
|
|
|
popen = subprocess.Popen(
|
|
json_dump_args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
universal_newlines=True,
|
|
)
|
|
stdout, stderr = popen.communicate()
|
|
if popen.returncode != 0:
|
|
sys.stderr.write("Failed to run " + " ".join(json_dump_args) + "\n")
|
|
sys.stderr.write(stderr)
|
|
sys.stderr.write(stdout)
|
|
sys.exit(2)
|
|
|
|
# Parse the clang JSON and add all children of type FunctionDecl.
|
|
# TODO: Should we add checks for global variables being emitted?
|
|
def parse_clang_ast_json(node, loc, search):
|
|
node_kind = node["kind"]
|
|
# Recurse for the following nodes that can contain nested function decls:
|
|
if node_kind in (
|
|
"NamespaceDecl",
|
|
"LinkageSpecDecl",
|
|
"TranslationUnitDecl",
|
|
"CXXRecordDecl",
|
|
"ClassTemplateSpecializationDecl",
|
|
):
|
|
# Specializations must use the loc from the specialization, not the
|
|
# template, and search for the class's spelling as the specialization
|
|
# does not mention the method names in the source.
|
|
if node_kind == "ClassTemplateSpecializationDecl":
|
|
inner_loc = node["loc"]
|
|
inner_search = node["name"]
|
|
else:
|
|
inner_loc = None
|
|
inner_search = None
|
|
if "inner" in node:
|
|
for inner in node["inner"]:
|
|
parse_clang_ast_json(inner, inner_loc, inner_search)
|
|
# Otherwise we ignore everything except functions:
|
|
if node_kind not in (
|
|
"FunctionDecl",
|
|
"CXXMethodDecl",
|
|
"CXXConstructorDecl",
|
|
"CXXDestructorDecl",
|
|
"CXXConversionDecl",
|
|
):
|
|
return
|
|
if loc is None:
|
|
loc = node["loc"]
|
|
if node.get("isImplicit") is True and node.get("storageClass") == "extern":
|
|
common.debug("Skipping builtin function:", node["name"], "@", loc)
|
|
return
|
|
common.debug("Found function:", node["kind"], node["name"], "@", loc)
|
|
line = loc.get("line")
|
|
# If there is no line it is probably a builtin function -> skip
|
|
if line is None:
|
|
common.debug(
|
|
"Skipping function without line number:", node["name"], "@", loc
|
|
)
|
|
return
|
|
|
|
# If there is no 'inner' object, it is a function declaration and we can
|
|
# skip it. However, function declarations may also contain an 'inner' list,
|
|
# but in that case it will only contains ParmVarDecls. If we find an entry
|
|
# that is not a ParmVarDecl, we know that this is a function definition.
|
|
has_body = False
|
|
if "inner" in node:
|
|
for i in node["inner"]:
|
|
if i.get("kind", "ParmVarDecl") != "ParmVarDecl":
|
|
has_body = True
|
|
break
|
|
if not has_body:
|
|
common.debug("Skipping function without body:", node["name"], "@", loc)
|
|
return
|
|
spell = node["name"]
|
|
if search is None:
|
|
search = spell
|
|
mangled = node.get("mangledName", spell)
|
|
ret[int(line) - 1].append((spell, mangled, search))
|
|
|
|
ast = json.loads(stdout)
|
|
if ast["kind"] != "TranslationUnitDecl":
|
|
common.error("Clang AST dump JSON format changed?")
|
|
sys.exit(2)
|
|
parse_clang_ast_json(ast, None, None)
|
|
|
|
for line, funcs in sorted(ret.items()):
|
|
for func in funcs:
|
|
common.debug(
|
|
"line {}: found function {}".format(line + 1, func), file=sys.stderr
|
|
)
|
|
if not ret:
|
|
common.warn("Did not find any functions using", " ".join(json_dump_args))
|
|
return ret
|
|
|
|
|
|
def str_to_commandline(value):
|
|
if not value:
|
|
return []
|
|
return shlex.split(value)
|
|
|
|
|
|
def infer_dependent_args(args):
|
|
if not args.clang:
|
|
if not args.llvm_bin:
|
|
args.clang = "clang"
|
|
else:
|
|
args.clang = os.path.join(args.llvm_bin, "clang")
|
|
if not args.opt:
|
|
if not args.llvm_bin:
|
|
args.opt = "opt"
|
|
else:
|
|
args.opt = os.path.join(args.llvm_bin, "opt")
|
|
|
|
|
|
def find_executable(executable):
|
|
_, ext = os.path.splitext(executable)
|
|
if sys.platform == "win32" and ext != ".exe":
|
|
executable = executable + ".exe"
|
|
|
|
return shutil.which(executable)
|
|
|
|
|
|
def config():
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__, formatter_class=argparse.RawTextHelpFormatter
|
|
)
|
|
parser.add_argument("--llvm-bin", help="llvm $prefix/bin path")
|
|
parser.add_argument(
|
|
"--clang", help='"clang" executable, defaults to $llvm_bin/clang'
|
|
)
|
|
parser.add_argument(
|
|
"--clang-args",
|
|
default=[],
|
|
type=str_to_commandline,
|
|
help="Space-separated extra args to clang, e.g. --clang-args=-v",
|
|
)
|
|
parser.add_argument("--opt", help='"opt" executable, defaults to $llvm_bin/opt')
|
|
parser.add_argument(
|
|
"--functions",
|
|
nargs="+",
|
|
help="A list of function name regexes. "
|
|
"If specified, update CHECK lines for functions matching at least one regex",
|
|
)
|
|
parser.add_argument(
|
|
"--x86_extra_scrub",
|
|
action="store_true",
|
|
help="Use more regex for x86 matching to reduce diffs between various subtargets",
|
|
)
|
|
parser.add_argument(
|
|
"--function-signature",
|
|
action="store_true",
|
|
help="Keep function signature information around for the check line",
|
|
)
|
|
parser.add_argument(
|
|
"--check-attributes",
|
|
action="store_true",
|
|
help='Check "Function Attributes" for functions',
|
|
)
|
|
parser.add_argument(
|
|
"--check-globals",
|
|
action="store_true",
|
|
help="Check global entries (global variables, metadata, attribute sets, ...) for functions",
|
|
)
|
|
parser.add_argument("tests", nargs="+")
|
|
args = common.parse_commandline_args(parser)
|
|
infer_dependent_args(args)
|
|
|
|
if not find_executable(args.clang):
|
|
print("Please specify --llvm-bin or --clang", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Determine the builtin includes directory so that we can update tests that
|
|
# depend on the builtin headers. See get_clang_builtin_include_dir() and
|
|
# use_clang() in llvm/utils/lit/lit/llvm/config.py.
|
|
try:
|
|
builtin_include_dir = (
|
|
subprocess.check_output([args.clang, "-print-file-name=include"])
|
|
.decode()
|
|
.strip()
|
|
)
|
|
SUBST["%clang_cc1"] = [
|
|
"-cc1",
|
|
"-internal-isystem",
|
|
builtin_include_dir,
|
|
"-nostdsysteminc",
|
|
]
|
|
except subprocess.CalledProcessError:
|
|
common.warn(
|
|
"Could not determine clang builtins directory, some tests "
|
|
"might not update correctly."
|
|
)
|
|
|
|
if not find_executable(args.opt):
|
|
# Many uses of this tool will not need an opt binary, because it's only
|
|
# needed for updating a test that runs clang | opt | FileCheck. So we
|
|
# defer this error message until we find that opt is actually needed.
|
|
args.opt = None
|
|
|
|
return args, parser
|
|
|
|
|
|
def get_function_body(builder, args, filename, clang_args, extra_commands, prefixes):
|
|
# TODO Clean up duplication of asm/common build_function_body_dictionary
|
|
# Invoke external tool and extract function bodies.
|
|
raw_tool_output = common.invoke_tool(args.clang, clang_args, filename)
|
|
for extra_command in extra_commands:
|
|
extra_args = shlex.split(extra_command)
|
|
with tempfile.NamedTemporaryFile() as f:
|
|
f.write(raw_tool_output.encode())
|
|
f.flush()
|
|
if extra_args[0] == "opt":
|
|
if args.opt is None:
|
|
print(
|
|
filename,
|
|
"needs to run opt. " "Please specify --llvm-bin or --opt",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
extra_args[0] = args.opt
|
|
raw_tool_output = common.invoke_tool(extra_args[0], extra_args[1:], f.name)
|
|
if "-emit-llvm" in clang_args:
|
|
builder.process_run_line(
|
|
common.OPT_FUNCTION_RE, common.scrub_body, raw_tool_output, prefixes, False
|
|
)
|
|
builder.processed_prefixes(prefixes)
|
|
else:
|
|
print(
|
|
"The clang command line should include -emit-llvm as asm tests "
|
|
"are discouraged in Clang testsuite.",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
|
|
def exec_run_line(exe):
|
|
popen = subprocess.Popen(
|
|
exe, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
|
|
)
|
|
stdout, stderr = popen.communicate()
|
|
if popen.returncode != 0:
|
|
sys.stderr.write("Failed to run " + " ".join(exe) + "\n")
|
|
sys.stderr.write(stderr)
|
|
sys.stderr.write(stdout)
|
|
sys.exit(3)
|
|
|
|
|
|
def main():
|
|
initial_args, parser = config()
|
|
script_name = os.path.basename(__file__)
|
|
|
|
for ti in common.itertests(
|
|
initial_args.tests,
|
|
parser,
|
|
"utils/" + script_name,
|
|
comment_prefix="//",
|
|
argparse_callback=infer_dependent_args,
|
|
):
|
|
# Build a list of filechecked and non-filechecked RUN lines.
|
|
run_list = []
|
|
line2func_list = collections.defaultdict(list)
|
|
|
|
subs = {
|
|
"%s": ti.path,
|
|
"%t": tempfile.NamedTemporaryFile().name,
|
|
"%S": os.path.dirname(ti.path),
|
|
}
|
|
|
|
for l in ti.run_lines:
|
|
commands = [cmd.strip() for cmd in l.split("|")]
|
|
|
|
triple_in_cmd = None
|
|
m = common.TRIPLE_ARG_RE.search(commands[0])
|
|
if m:
|
|
triple_in_cmd = m.groups()[0]
|
|
|
|
# Parse executable args.
|
|
exec_args = shlex.split(commands[0])
|
|
# Execute non-clang runline.
|
|
if exec_args[0] not in SUBST:
|
|
# Do lit-like substitutions.
|
|
for s in subs:
|
|
exec_args = [
|
|
i.replace(s, subs[s]) if s in i else i for i in exec_args
|
|
]
|
|
run_list.append((None, exec_args, None, None))
|
|
continue
|
|
# This is a clang runline, apply %clang substitution rule, do lit-like substitutions,
|
|
# and append args.clang_args
|
|
clang_args = exec_args
|
|
clang_args[0:1] = SUBST[clang_args[0]]
|
|
for s in subs:
|
|
clang_args = [
|
|
i.replace(s, subs[s]) if s in i else i for i in clang_args
|
|
]
|
|
clang_args += ti.args.clang_args
|
|
|
|
# Extract -check-prefix in FileCheck args
|
|
filecheck_cmd = commands[-1]
|
|
common.verify_filecheck_prefixes(filecheck_cmd)
|
|
if not filecheck_cmd.startswith("FileCheck "):
|
|
# Execute non-filechecked clang runline.
|
|
exe = [ti.args.clang] + clang_args
|
|
run_list.append((None, exe, None, None))
|
|
continue
|
|
|
|
check_prefixes = common.get_check_prefixes(filecheck_cmd)
|
|
run_list.append((check_prefixes, clang_args, commands[1:-1], triple_in_cmd))
|
|
|
|
# Execute clang, generate LLVM IR, and extract functions.
|
|
|
|
# Store only filechecked runlines.
|
|
filecheck_run_list = [i for i in run_list if i[0]]
|
|
builder = common.FunctionTestBuilder(
|
|
run_list=filecheck_run_list, flags=ti.args, scrubber_args=[], path=ti.path
|
|
)
|
|
|
|
for prefixes, args, extra_commands, triple_in_cmd in run_list:
|
|
# Execute non-filechecked runline.
|
|
if not prefixes:
|
|
print(
|
|
"NOTE: Executing non-FileChecked RUN line: " + " ".join(args),
|
|
file=sys.stderr,
|
|
)
|
|
exec_run_line(args)
|
|
continue
|
|
|
|
clang_args = args
|
|
common.debug("Extracted clang cmd: clang {}".format(clang_args))
|
|
common.debug("Extracted FileCheck prefixes: {}".format(prefixes))
|
|
|
|
get_function_body(
|
|
builder, ti.args, ti.path, clang_args, extra_commands, prefixes
|
|
)
|
|
|
|
# Invoke clang -Xclang -ast-dump=json to get mapping from start lines to
|
|
# mangled names. Forward all clang args for now.
|
|
for k, v in get_line2func_list(ti.args, clang_args).items():
|
|
line2func_list[k].extend(v)
|
|
|
|
func_dict = builder.finish_and_get_func_dict()
|
|
global_vars_seen_dict = {}
|
|
prefix_set = set([prefix for p in filecheck_run_list for prefix in p[0]])
|
|
output_lines = []
|
|
has_checked_pre_function_globals = False
|
|
|
|
include_generated_funcs = common.find_arg_in_test(
|
|
ti,
|
|
lambda args: ti.args.include_generated_funcs,
|
|
"--include-generated-funcs",
|
|
True,
|
|
)
|
|
generated_prefixes = []
|
|
if include_generated_funcs:
|
|
# Generate the appropriate checks for each function. We need to emit
|
|
# these in the order according to the generated output so that CHECK-LABEL
|
|
# works properly. func_order provides that.
|
|
|
|
# It turns out that when clang generates functions (for example, with
|
|
# -fopenmp), it can sometimes cause functions to be re-ordered in the
|
|
# output, even functions that exist in the source file. Therefore we
|
|
# can't insert check lines before each source function and instead have to
|
|
# put them at the end. So the first thing to do is dump out the source
|
|
# lines.
|
|
common.dump_input_lines(output_lines, ti, prefix_set, "//")
|
|
|
|
# Now generate all the checks.
|
|
def check_generator(my_output_lines, prefixes, func):
|
|
if "-emit-llvm" in clang_args:
|
|
return common.add_ir_checks(
|
|
my_output_lines,
|
|
"//",
|
|
prefixes,
|
|
func_dict,
|
|
func,
|
|
False,
|
|
ti.args.function_signature,
|
|
ti.args.version,
|
|
global_vars_seen_dict,
|
|
is_filtered=builder.is_filtered(),
|
|
)
|
|
else:
|
|
return asm.add_checks(
|
|
my_output_lines,
|
|
"//",
|
|
prefixes,
|
|
func_dict,
|
|
func,
|
|
global_vars_seen_dict,
|
|
is_filtered=builder.is_filtered(),
|
|
)
|
|
|
|
if ti.args.check_globals:
|
|
generated_prefixes.extend(
|
|
common.add_global_checks(
|
|
builder.global_var_dict(),
|
|
"//",
|
|
run_list,
|
|
output_lines,
|
|
global_vars_seen_dict,
|
|
True,
|
|
True,
|
|
)
|
|
)
|
|
generated_prefixes.extend(
|
|
common.add_checks_at_end(
|
|
output_lines,
|
|
filecheck_run_list,
|
|
builder.func_order(),
|
|
"//",
|
|
lambda my_output_lines, prefixes, func: check_generator(
|
|
my_output_lines, prefixes, func
|
|
),
|
|
)
|
|
)
|
|
else:
|
|
# Normal mode. Put checks before each source function.
|
|
for line_info in ti.iterlines(output_lines):
|
|
idx = line_info.line_number
|
|
line = line_info.line
|
|
args = line_info.args
|
|
include_line = True
|
|
m = common.CHECK_RE.match(line)
|
|
if m and m.group(1) in prefix_set:
|
|
continue # Don't append the existing CHECK lines
|
|
# Skip special separator comments added by commmon.add_global_checks.
|
|
if line.strip() == "//" + common.SEPARATOR:
|
|
continue
|
|
if idx in line2func_list:
|
|
added = set()
|
|
for spell, mangled, search in line2func_list[idx]:
|
|
# One line may contain multiple function declarations.
|
|
# Skip if the mangled name has been added before.
|
|
# The line number may come from an included file, we simply require
|
|
# the search string (normally the function's spelling name, but is
|
|
# the class's spelling name for class specializations) to appear on
|
|
# the line to exclude functions from other files.
|
|
if mangled in added or search not in line:
|
|
continue
|
|
if args.functions is None or any(
|
|
re.search(regex, spell) for regex in args.functions
|
|
):
|
|
last_line = output_lines[-1].strip()
|
|
while last_line == "//":
|
|
# Remove the comment line since we will generate a new comment
|
|
# line as part of common.add_ir_checks()
|
|
output_lines.pop()
|
|
last_line = output_lines[-1].strip()
|
|
if (
|
|
ti.args.check_globals
|
|
and not has_checked_pre_function_globals
|
|
):
|
|
generated_prefixes.extend(
|
|
common.add_global_checks(
|
|
builder.global_var_dict(),
|
|
"//",
|
|
run_list,
|
|
output_lines,
|
|
global_vars_seen_dict,
|
|
True,
|
|
True,
|
|
)
|
|
)
|
|
has_checked_pre_function_globals = True
|
|
if added:
|
|
output_lines.append("//")
|
|
added.add(mangled)
|
|
generated_prefixes.extend(
|
|
common.add_ir_checks(
|
|
output_lines,
|
|
"//",
|
|
filecheck_run_list,
|
|
func_dict,
|
|
mangled,
|
|
False,
|
|
args.function_signature,
|
|
args.version,
|
|
global_vars_seen_dict,
|
|
is_filtered=builder.is_filtered(),
|
|
)
|
|
)
|
|
if line.rstrip("\n") == "//":
|
|
include_line = False
|
|
|
|
if include_line:
|
|
output_lines.append(line.rstrip("\n"))
|
|
|
|
if ti.args.check_globals:
|
|
generated_prefixes.extend(
|
|
common.add_global_checks(
|
|
builder.global_var_dict(),
|
|
"//",
|
|
run_list,
|
|
output_lines,
|
|
global_vars_seen_dict,
|
|
True,
|
|
False,
|
|
)
|
|
)
|
|
if ti.args.gen_unused_prefix_body:
|
|
output_lines.extend(
|
|
ti.get_checks_for_unused_prefixes(run_list, generated_prefixes)
|
|
)
|
|
common.debug("Writing %d lines to %s..." % (len(output_lines), ti.path))
|
|
with open(ti.path, "wb") as f:
|
|
f.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines])
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|