|  | #!/usr/bin/env python3 | 
|  |  | 
|  | """Updates FileCheck checks in MIR tests. | 
|  |  | 
|  | This script is a utility to update MIR based tests with new FileCheck | 
|  | patterns. | 
|  |  | 
|  | The checks added by this script will cover the entire body of each | 
|  | function it handles. Virtual registers used are given names via | 
|  | FileCheck patterns, so if you do want to check a subset of the body it | 
|  | should be straightforward to trim out the irrelevant parts. None of | 
|  | the YAML metadata will be checked, other than function names, and fixedStack | 
|  | if the --print-fixed-stack option is used. | 
|  |  | 
|  | If there are multiple llc commands in a test, the full set of checks | 
|  | will be repeated for each different check pattern. Checks for patterns | 
|  | that are common between different commands will be left as-is by | 
|  | default, or removed if the --remove-common-prefixes flag is provided. | 
|  | """ | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import argparse | 
|  | import collections | 
|  | import glob | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  |  | 
|  | from UpdateTestChecks import common | 
|  |  | 
|  | MIR_FUNC_NAME_RE = re.compile(r" *name: *(?P<func>[A-Za-z0-9_.-]+)") | 
|  | MIR_BODY_BEGIN_RE = re.compile(r" *body: *\|") | 
|  | MIR_BASIC_BLOCK_RE = re.compile(r" *bb\.[0-9]+.*:$") | 
|  | VREG_RE = re.compile(r"(%[0-9]+)(?:\.[a-z0-9_]+)?(?::[a-z0-9_]+)?(?:\([<>a-z0-9 ]+\))?") | 
|  | MI_FLAGS_STR = ( | 
|  | r"(frame-setup |frame-destroy |nnan |ninf |nsz |arcp |contract |afn " | 
|  | r"|reassoc |nuw |nsw |exact |nofpexcept |nomerge )*" | 
|  | ) | 
|  | VREG_DEF_FLAGS_STR = r"(?:dead |undef )*" | 
|  | VREG_DEF_RE = re.compile( | 
|  | r"^ *(?P<vregs>{2}{0}(?:, {2}{0})*) = " | 
|  | r"{1}(?P<opcode>[A-Zt][A-Za-z0-9_]+)".format( | 
|  | VREG_RE.pattern, MI_FLAGS_STR, VREG_DEF_FLAGS_STR | 
|  | ) | 
|  | ) | 
|  | MIR_PREFIX_DATA_RE = re.compile(r"^ *(;|bb.[0-9].*: *$|[a-z]+:( |$)|$)") | 
|  |  | 
|  | IR_FUNC_NAME_RE = re.compile( | 
|  | r"^\s*define\s+(?:internal\s+)?[^@]*@(?P<func>[A-Za-z0-9_.]+)\s*\(" | 
|  | ) | 
|  | IR_PREFIX_DATA_RE = re.compile(r"^ *(;|$)") | 
|  |  | 
|  | MIR_FUNC_RE = re.compile( | 
|  | r"^---$" | 
|  | r"\n" | 
|  | r"^ *name: *(?P<func>[A-Za-z0-9_.-]+)$" | 
|  | r".*?" | 
|  | r"(?:^ *fixedStack: *(\[\])? *\n" | 
|  | r"(?P<fixedStack>.*?)\n?" | 
|  | r"^ *stack:" | 
|  | r".*?)?" | 
|  | r"^ *body: *\|\n" | 
|  | r"(?P<body>.*?)\n" | 
|  | r"^\.\.\.$", | 
|  | flags=(re.M | re.S), | 
|  | ) | 
|  |  | 
|  |  | 
|  | class LLC: | 
|  | def __init__(self, bin): | 
|  | self.bin = bin | 
|  |  | 
|  | def __call__(self, args, ir): | 
|  | if ir.endswith(".mir"): | 
|  | args = "{} -x mir".format(args) | 
|  | with open(ir) as ir_file: | 
|  | stdout = subprocess.check_output( | 
|  | "{} {}".format(self.bin, args), shell=True, stdin=ir_file | 
|  | ) | 
|  | if sys.version_info[0] > 2: | 
|  | stdout = stdout.decode() | 
|  | # Fix line endings to unix CR style. | 
|  | stdout = stdout.replace("\r\n", "\n") | 
|  | return stdout | 
|  |  | 
|  |  | 
|  | class Run: | 
|  | def __init__(self, prefixes, cmd_args, triple): | 
|  | self.prefixes = prefixes | 
|  | self.cmd_args = cmd_args | 
|  | self.triple = triple | 
|  |  | 
|  | def __getitem__(self, index): | 
|  | return [self.prefixes, self.cmd_args, self.triple][index] | 
|  |  | 
|  |  | 
|  | def log(msg, verbose=True): | 
|  | if verbose: | 
|  | print(msg, file=sys.stderr) | 
|  |  | 
|  |  | 
|  | def find_triple_in_ir(lines, verbose=False): | 
|  | for l in lines: | 
|  | m = common.TRIPLE_IR_RE.match(l) | 
|  | if m: | 
|  | return m.group(1) | 
|  | return None | 
|  |  | 
|  |  | 
|  | def build_run_list(test, run_lines, verbose=False): | 
|  | run_list = [] | 
|  | all_prefixes = [] | 
|  | for l in run_lines: | 
|  | if "|" not in l: | 
|  | common.warn("Skipping unparsable RUN line: " + l) | 
|  | continue | 
|  |  | 
|  | commands = [cmd.strip() for cmd in l.split("|", 1)] | 
|  | llc_cmd = commands[0] | 
|  | filecheck_cmd = commands[1] if len(commands) > 1 else "" | 
|  | common.verify_filecheck_prefixes(filecheck_cmd) | 
|  |  | 
|  | if not llc_cmd.startswith("llc "): | 
|  | common.warn("Skipping non-llc RUN line: {}".format(l), test_file=test) | 
|  | continue | 
|  | if not filecheck_cmd.startswith("FileCheck "): | 
|  | common.warn( | 
|  | "Skipping non-FileChecked RUN line: {}".format(l), test_file=test | 
|  | ) | 
|  | continue | 
|  |  | 
|  | triple = None | 
|  | m = common.TRIPLE_ARG_RE.search(llc_cmd) | 
|  | if m: | 
|  | triple = m.group(1) | 
|  | # If we find -march but not -mtriple, use that. | 
|  | m = common.MARCH_ARG_RE.search(llc_cmd) | 
|  | if m and not triple: | 
|  | triple = "{}--".format(m.group(1)) | 
|  |  | 
|  | cmd_args = llc_cmd[len("llc") :].strip() | 
|  | cmd_args = cmd_args.replace("< %s", "").replace("%s", "").strip() | 
|  | check_prefixes = common.get_check_prefixes(filecheck_cmd) | 
|  | all_prefixes += check_prefixes | 
|  |  | 
|  | run_list.append(Run(check_prefixes, cmd_args, triple)) | 
|  |  | 
|  | # Sort prefixes that are shared between run lines before unshared prefixes. | 
|  | # This causes us to prefer printing shared prefixes. | 
|  | for run in run_list: | 
|  | run.prefixes.sort(key=lambda prefix: -all_prefixes.count(prefix)) | 
|  |  | 
|  | return run_list | 
|  |  | 
|  |  | 
|  | def find_functions_with_one_bb(lines, verbose=False): | 
|  | result = [] | 
|  | cur_func = None | 
|  | bbs = 0 | 
|  | for line in lines: | 
|  | m = MIR_FUNC_NAME_RE.match(line) | 
|  | if m: | 
|  | if bbs == 1: | 
|  | result.append(cur_func) | 
|  | cur_func = m.group("func") | 
|  | bbs = 0 | 
|  | m = MIR_BASIC_BLOCK_RE.match(line) | 
|  | if m: | 
|  | bbs += 1 | 
|  | if bbs == 1: | 
|  | result.append(cur_func) | 
|  | return result | 
|  |  | 
|  |  | 
|  | class FunctionInfo: | 
|  | def __init__(self, body, fixedStack): | 
|  | self.body = body | 
|  | self.fixedStack = fixedStack | 
|  |  | 
|  | def __eq__(self, other): | 
|  | if not isinstance(other, FunctionInfo): | 
|  | return False | 
|  | return self.body == other.body and self.fixedStack == other.fixedStack | 
|  |  | 
|  |  | 
|  | def build_function_info_dictionary( | 
|  | test, raw_tool_output, triple, prefixes, func_dict, verbose | 
|  | ): | 
|  | for m in MIR_FUNC_RE.finditer(raw_tool_output): | 
|  | func = m.group("func") | 
|  | fixedStack = m.group("fixedStack") | 
|  | body = m.group("body") | 
|  | if verbose: | 
|  | log("Processing function: {}".format(func)) | 
|  | for l in body.splitlines(): | 
|  | log("  {}".format(l)) | 
|  |  | 
|  | # Vreg mangling | 
|  | mangled = [] | 
|  | vreg_map = {} | 
|  | for func_line in body.splitlines(keepends=True): | 
|  | m = VREG_DEF_RE.match(func_line) | 
|  | if m: | 
|  | for vreg in VREG_RE.finditer(m.group("vregs")): | 
|  | if vreg.group(1) in vreg_map: | 
|  | name = vreg_map[vreg.group(1)] | 
|  | else: | 
|  | name = mangle_vreg(m.group("opcode"), vreg_map.values()) | 
|  | vreg_map[vreg.group(1)] = name | 
|  | func_line = func_line.replace( | 
|  | vreg.group(1), "[[{}:%[0-9]+]]".format(name), 1 | 
|  | ) | 
|  | for number, name in vreg_map.items(): | 
|  | func_line = re.sub( | 
|  | r"{}\b".format(number), "[[{}]]".format(name), func_line | 
|  | ) | 
|  | mangled.append(func_line) | 
|  | body = "".join(mangled) | 
|  |  | 
|  | for prefix in prefixes: | 
|  | info = FunctionInfo(body, fixedStack) | 
|  | if func in func_dict[prefix]: | 
|  | if func_dict[prefix][func] != info: | 
|  | func_dict[prefix][func] = None | 
|  | else: | 
|  | func_dict[prefix][func] = info | 
|  |  | 
|  |  | 
|  | def add_checks_for_function( | 
|  | test, output_lines, run_list, func_dict, func_name, single_bb, args | 
|  | ): | 
|  | printed_prefixes = set() | 
|  | for run in run_list: | 
|  | for prefix in run.prefixes: | 
|  | if prefix in printed_prefixes: | 
|  | break | 
|  | if not func_dict[prefix][func_name]: | 
|  | continue | 
|  | if printed_prefixes: | 
|  | # Add some space between different check prefixes. | 
|  | indent = len(output_lines[-1]) - len(output_lines[-1].lstrip(" ")) | 
|  | output_lines.append(" "*indent + ";") | 
|  | printed_prefixes.add(prefix) | 
|  | log("Adding {} lines for {}".format(prefix, func_name), args.verbose) | 
|  | add_check_lines( | 
|  | test, | 
|  | output_lines, | 
|  | prefix, | 
|  | func_name, | 
|  | single_bb, | 
|  | func_dict[prefix][func_name], | 
|  | args, | 
|  | ) | 
|  | break | 
|  | else: | 
|  | common.warn( | 
|  | "Found conflicting asm for function: {}".format(func_name), | 
|  | test_file=test, | 
|  | ) | 
|  | return output_lines | 
|  |  | 
|  |  | 
|  | def add_check_lines( | 
|  | test, output_lines, prefix, func_name, single_bb, func_info: FunctionInfo, args | 
|  | ): | 
|  | func_body = func_info.body.splitlines() | 
|  | if single_bb: | 
|  | # Don't bother checking the basic block label for a single BB | 
|  | func_body.pop(0) | 
|  |  | 
|  | if not func_body: | 
|  | common.warn( | 
|  | "Function has no instructions to check: {}".format(func_name), | 
|  | test_file=test, | 
|  | ) | 
|  | return | 
|  |  | 
|  | first_line = func_body[0] | 
|  | indent = len(first_line) - len(first_line.lstrip(" ")) | 
|  | # A check comment, indented the appropriate amount | 
|  | check = "{:>{}}; {}".format("", indent, prefix) | 
|  |  | 
|  | output_lines.append("{}-LABEL: name: {}".format(check, func_name)) | 
|  |  | 
|  | if args.print_fixed_stack: | 
|  | output_lines.append("{}: fixedStack:".format(check)) | 
|  | for stack_line in func_info.fixedStack.splitlines(): | 
|  | filecheck_directive = check + "-NEXT" | 
|  | output_lines.append("{}: {}".format(filecheck_directive, stack_line)) | 
|  |  | 
|  | first_check = True | 
|  | for func_line in func_body: | 
|  | if not func_line.strip(): | 
|  | # The mir printer prints leading whitespace so we can't use CHECK-EMPTY: | 
|  | output_lines.append(check + "-NEXT: {{" + func_line + "$}}") | 
|  | continue | 
|  | filecheck_directive = check if first_check else check + "-NEXT" | 
|  | first_check = False | 
|  | check_line = "{}: {}".format(filecheck_directive, func_line[indent:]).rstrip() | 
|  | output_lines.append(check_line) | 
|  |  | 
|  |  | 
|  | def mangle_vreg(opcode, current_names): | 
|  | base = opcode | 
|  | # Simplify some common prefixes and suffixes | 
|  | if opcode.startswith("G_"): | 
|  | base = base[len("G_") :] | 
|  | if opcode.endswith("_PSEUDO"): | 
|  | base = base[: len("_PSEUDO")] | 
|  | # Shorten some common opcodes with long-ish names | 
|  | base = dict( | 
|  | IMPLICIT_DEF="DEF", | 
|  | GLOBAL_VALUE="GV", | 
|  | CONSTANT="C", | 
|  | FCONSTANT="C", | 
|  | MERGE_VALUES="MV", | 
|  | UNMERGE_VALUES="UV", | 
|  | INTRINSIC="INT", | 
|  | INTRINSIC_W_SIDE_EFFECTS="INT", | 
|  | INSERT_VECTOR_ELT="IVEC", | 
|  | EXTRACT_VECTOR_ELT="EVEC", | 
|  | SHUFFLE_VECTOR="SHUF", | 
|  | ).get(base, base) | 
|  | # Avoid ambiguity when opcodes end in numbers | 
|  | if len(base.rstrip("0123456789")) < len(base): | 
|  | base += "_" | 
|  |  | 
|  | i = 0 | 
|  | for name in current_names: | 
|  | if name.rstrip("0123456789") == base: | 
|  | i += 1 | 
|  | if i: | 
|  | return "{}{}".format(base, i) | 
|  | return base | 
|  |  | 
|  |  | 
|  | def should_add_line_to_output(input_line, prefix_set): | 
|  | # Skip any check lines that we're handling as well as comments | 
|  | m = common.CHECK_RE.match(input_line) | 
|  | if (m and m.group(1) in prefix_set) or re.search("^[ \t]*;", input_line): | 
|  | return False | 
|  | return True | 
|  |  | 
|  |  | 
|  | def update_test_file(args, test, autogenerated_note): | 
|  | with open(test) as fd: | 
|  | input_lines = [l.rstrip() for l in fd] | 
|  |  | 
|  | triple_in_ir = find_triple_in_ir(input_lines, args.verbose) | 
|  | run_lines = common.find_run_lines(test, input_lines) | 
|  | run_list = build_run_list(test, run_lines, args.verbose) | 
|  |  | 
|  | simple_functions = find_functions_with_one_bb(input_lines, args.verbose) | 
|  |  | 
|  | func_dict = {} | 
|  | for run in run_list: | 
|  | for prefix in run.prefixes: | 
|  | func_dict.update({prefix: dict()}) | 
|  | for prefixes, llc_args, triple_in_cmd in run_list: | 
|  | log("Extracted LLC cmd: llc {}".format(llc_args), args.verbose) | 
|  | log("Extracted FileCheck prefixes: {}".format(prefixes), args.verbose) | 
|  |  | 
|  | raw_tool_output = args.llc_binary(llc_args, test) | 
|  | if not triple_in_cmd and not triple_in_ir: | 
|  | common.warn("No triple found: skipping file", test_file=test) | 
|  | return | 
|  |  | 
|  | build_function_info_dictionary( | 
|  | test, | 
|  | raw_tool_output, | 
|  | triple_in_cmd or triple_in_ir, | 
|  | prefixes, | 
|  | func_dict, | 
|  | args.verbose, | 
|  | ) | 
|  |  | 
|  | state = "toplevel" | 
|  | func_name = None | 
|  | prefix_set = set([prefix for run in run_list for prefix in run.prefixes]) | 
|  | log("Rewriting FileCheck prefixes: {}".format(prefix_set), args.verbose) | 
|  |  | 
|  | output_lines = [] | 
|  | output_lines.append(autogenerated_note) | 
|  |  | 
|  | for input_line in input_lines: | 
|  | if input_line == autogenerated_note: | 
|  | continue | 
|  |  | 
|  | if state == "toplevel": | 
|  | m = IR_FUNC_NAME_RE.match(input_line) | 
|  | if m: | 
|  | state = "ir function prefix" | 
|  | func_name = m.group("func") | 
|  | if input_line.rstrip("| \r\n") == "---": | 
|  | state = "document" | 
|  | output_lines.append(input_line) | 
|  | elif state == "document": | 
|  | m = MIR_FUNC_NAME_RE.match(input_line) | 
|  | if m: | 
|  | state = "mir function metadata" | 
|  | func_name = m.group("func") | 
|  | if input_line.strip() == "...": | 
|  | state = "toplevel" | 
|  | func_name = None | 
|  | if should_add_line_to_output(input_line, prefix_set): | 
|  | output_lines.append(input_line) | 
|  | elif state == "mir function metadata": | 
|  | if should_add_line_to_output(input_line, prefix_set): | 
|  | output_lines.append(input_line) | 
|  | m = MIR_BODY_BEGIN_RE.match(input_line) | 
|  | if m: | 
|  | if func_name in simple_functions: | 
|  | # If there's only one block, put the checks inside it | 
|  | state = "mir function prefix" | 
|  | continue | 
|  | state = "mir function body" | 
|  | add_checks_for_function( | 
|  | test, | 
|  | output_lines, | 
|  | run_list, | 
|  | func_dict, | 
|  | func_name, | 
|  | single_bb=False, | 
|  | args=args, | 
|  | ) | 
|  | elif state == "mir function prefix": | 
|  | m = MIR_PREFIX_DATA_RE.match(input_line) | 
|  | if not m: | 
|  | state = "mir function body" | 
|  | add_checks_for_function( | 
|  | test, | 
|  | output_lines, | 
|  | run_list, | 
|  | func_dict, | 
|  | func_name, | 
|  | single_bb=True, | 
|  | args=args, | 
|  | ) | 
|  |  | 
|  | if should_add_line_to_output(input_line, prefix_set): | 
|  | output_lines.append(input_line) | 
|  | elif state == "mir function body": | 
|  | if input_line.strip() == "...": | 
|  | state = "toplevel" | 
|  | func_name = None | 
|  | if should_add_line_to_output(input_line, prefix_set): | 
|  | output_lines.append(input_line) | 
|  | elif state == "ir function prefix": | 
|  | m = IR_PREFIX_DATA_RE.match(input_line) | 
|  | if not m: | 
|  | state = "ir function body" | 
|  | add_checks_for_function( | 
|  | test, | 
|  | output_lines, | 
|  | run_list, | 
|  | func_dict, | 
|  | func_name, | 
|  | single_bb=False, | 
|  | args=args, | 
|  | ) | 
|  |  | 
|  | if should_add_line_to_output(input_line, prefix_set): | 
|  | output_lines.append(input_line) | 
|  | elif state == "ir function body": | 
|  | if input_line.strip() == "}": | 
|  | state = "toplevel" | 
|  | func_name = None | 
|  | if should_add_line_to_output(input_line, prefix_set): | 
|  | output_lines.append(input_line) | 
|  |  | 
|  | log("Writing {} lines to {}...".format(len(output_lines), test), args.verbose) | 
|  |  | 
|  | with open(test, "wb") as fd: | 
|  | fd.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines]) | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser( | 
|  | description=__doc__, formatter_class=argparse.RawTextHelpFormatter | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--llc-binary", | 
|  | default="llc", | 
|  | type=LLC, | 
|  | help='The "llc" binary to generate the test case with', | 
|  | ) | 
|  | parser.add_argument( | 
|  | "--print-fixed-stack", | 
|  | action="store_true", | 
|  | help="Add check lines for fixedStack", | 
|  | ) | 
|  | parser.add_argument("tests", nargs="+") | 
|  | args = common.parse_commandline_args(parser) | 
|  |  | 
|  | script_name = os.path.basename(__file__) | 
|  | for ti in common.itertests(args.tests, parser, script_name="utils/" + script_name): | 
|  | try: | 
|  | update_test_file(ti.args, ti.path, ti.test_autogenerated_note) | 
|  | except Exception: | 
|  | common.warn("Error processing file", test_file=ti.path) | 
|  | raise | 
|  |  | 
|  |  | 
|  | if __name__ == "__main__": | 
|  | main() |