xref: /llvm-project/llvm/utils/update_llc_test_checks.py (revision 34388f986af1289492d043b56b6cba17468d1f8d)
1#!/usr/bin/env python3
2
3"""A test case update script.
4
5This script is a utility to update LLVM 'llc' based test cases with new
6FileCheck patterns. It can either update all of the tests in the file or
7a single test function.
8"""
9
10from __future__ import print_function
11
12import argparse
13import os  # Used to advertise this file's name ("autogenerated_note").
14
15from UpdateTestChecks import common
16
17# llc is the only llc-like in the LLVM tree but downstream forks can add
18# additional ones here if they have them.
19LLC_LIKE_TOOLS = [
20    "llc",
21]
22
23
24def main():
25    parser = argparse.ArgumentParser(description=__doc__)
26    parser.add_argument(
27        "--llc-binary",
28        default=None,
29        help='The "llc" binary to use to generate the test case',
30    )
31    parser.add_argument("--function", help="The function in the test file to update")
32    parser.add_argument(
33        "--extra_scrub",
34        action="store_true",
35        help="Always use additional regex to further reduce diffs between various subtargets",
36    )
37    parser.add_argument(
38        "--x86_scrub_sp",
39        action="store_true",
40        default=True,
41        help="Use regex for x86 sp matching to reduce diffs between various subtargets",
42    )
43    parser.add_argument("--no_x86_scrub_sp", action="store_false", dest="x86_scrub_sp")
44    parser.add_argument(
45        "--x86_scrub_rip",
46        action="store_true",
47        default=False,
48        help="Use more regex for x86 rip matching to reduce diffs between various subtargets",
49    )
50    parser.add_argument(
51        "--no_x86_scrub_rip", action="store_false", dest="x86_scrub_rip"
52    )
53    parser.add_argument(
54        "--no_x86_scrub_mem_shuffle",
55        action="store_true",
56        default=False,
57        help="Reduce scrubbing shuffles with memory operands",
58    )
59    parser.add_argument(
60        "--tool",
61        default=None,
62        help="Treat the given tool name as an llc-like tool for which check lines should be generated",
63    )
64    parser.add_argument(
65        "--default-march",
66        default=None,
67        help="Set a default -march for when neither triple nor arch are found in a RUN line",
68    )
69    parser.add_argument("tests", nargs="+")
70    initial_args = common.parse_commandline_args(parser)
71
72    script_name = os.path.basename(__file__)
73
74    for ti in common.itertests(
75        initial_args.tests, parser, script_name="utils/" + script_name
76    ):
77        triple_in_ir = None
78        for l in ti.input_lines:
79            m = common.TRIPLE_IR_RE.match(l)
80            if m:
81                triple_in_ir = m.groups()[0]
82                break
83
84        run_list = []
85        for l in ti.run_lines:
86            if "|" not in l:
87                common.warn("Skipping unparsable RUN line: " + l)
88                continue
89
90            commands = [cmd.strip() for cmd in l.split("|")]
91            assert len(commands) >= 2
92            preprocess_cmd = None
93            if len(commands) > 2:
94                preprocess_cmd = " | ".join(commands[:-2])
95            llc_cmd = commands[-2]
96            filecheck_cmd = commands[-1]
97            llc_tool = llc_cmd.split(" ")[0]
98
99            triple_in_cmd = None
100            m = common.TRIPLE_ARG_RE.search(llc_cmd)
101            if m:
102                triple_in_cmd = m.groups()[0]
103
104            march_in_cmd = ti.args.default_march
105            m = common.MARCH_ARG_RE.search(llc_cmd)
106            if m:
107                march_in_cmd = m.groups()[0]
108
109            m = common.DEBUG_ONLY_ARG_RE.search(llc_cmd)
110            if m and m.groups()[0] == "isel":
111                from UpdateTestChecks import isel as output_type
112            else:
113                from UpdateTestChecks import asm as output_type
114
115            common.verify_filecheck_prefixes(filecheck_cmd)
116
117            llc_like_tools = LLC_LIKE_TOOLS[:]
118            if ti.args.tool:
119                llc_like_tools.append(ti.args.tool)
120            if llc_tool not in llc_like_tools:
121                common.warn("Skipping non-llc RUN line: " + l)
122                continue
123
124            if not filecheck_cmd.startswith("FileCheck "):
125                common.warn("Skipping non-FileChecked RUN line: " + l)
126                continue
127
128            llc_cmd_args = llc_cmd[len(llc_tool) :].strip()
129            llc_cmd_args = llc_cmd_args.replace("< %s", "").replace("%s", "").strip()
130            if ti.path.endswith(".mir"):
131                llc_cmd_args += " -x mir"
132            check_prefixes = common.get_check_prefixes(filecheck_cmd)
133
134            # FIXME: We should use multiple check prefixes to common check lines. For
135            # now, we just ignore all but the last.
136            run_list.append(
137                (
138                    check_prefixes,
139                    llc_tool,
140                    llc_cmd_args,
141                    preprocess_cmd,
142                    triple_in_cmd,
143                    march_in_cmd,
144                )
145            )
146
147        if ti.path.endswith(".mir"):
148            check_indent = "  "
149        else:
150            check_indent = ""
151
152        ginfo = common.make_asm_generalizer(version=1)
153        builder = common.FunctionTestBuilder(
154            run_list=run_list,
155            flags=type(
156                "",
157                (object,),
158                {
159                    "verbose": ti.args.verbose,
160                    "filters": ti.args.filters,
161                    "function_signature": False,
162                    "check_attributes": False,
163                    "replace_value_regex": [],
164                },
165            ),
166            scrubber_args=[ti.args],
167            path=ti.path,
168            ginfo=ginfo,
169        )
170
171        for (
172            prefixes,
173            llc_tool,
174            llc_args,
175            preprocess_cmd,
176            triple_in_cmd,
177            march_in_cmd,
178        ) in run_list:
179            common.debug("Extracted LLC cmd:", llc_tool, llc_args)
180            common.debug("Extracted FileCheck prefixes:", str(prefixes))
181
182            raw_tool_output = common.invoke_tool(
183                ti.args.llc_binary or llc_tool,
184                llc_args,
185                ti.path,
186                preprocess_cmd,
187                verbose=ti.args.verbose,
188            )
189            triple = triple_in_cmd or triple_in_ir
190            if not triple:
191                triple = common.get_triple_from_march(march_in_cmd)
192
193            scrubber, function_re = output_type.get_run_handler(triple)
194            builder.process_run_line(function_re, scrubber, raw_tool_output, prefixes)
195            builder.processed_prefixes(prefixes)
196
197        func_dict = builder.finish_and_get_func_dict()
198        global_vars_seen_dict = {}
199
200        is_in_function = False
201        is_in_function_start = False
202        func_name = None
203        prefix_set = set([prefix for p in run_list for prefix in p[0]])
204        common.debug("Rewriting FileCheck prefixes:", str(prefix_set))
205        output_lines = []
206
207        include_generated_funcs = common.find_arg_in_test(
208            ti,
209            lambda args: ti.args.include_generated_funcs,
210            "--include-generated-funcs",
211            True,
212        )
213
214        generated_prefixes = []
215        if include_generated_funcs:
216            # Generate the appropriate checks for each function.  We need to emit
217            # these in the order according to the generated output so that CHECK-LABEL
218            # works properly.  func_order provides that.
219
220            # We can't predict where various passes might insert functions so we can't
221            # be sure the input function order is maintained.  Therefore, first spit
222            # out all the source lines.
223            common.dump_input_lines(output_lines, ti, prefix_set, ";")
224
225            # Now generate all the checks.
226            generated_prefixes = common.add_checks_at_end(
227                output_lines,
228                run_list,
229                builder.func_order(),
230                check_indent + ";",
231                lambda my_output_lines, prefixes, func: output_type.add_checks(
232                    my_output_lines,
233                    check_indent + ";",
234                    prefixes,
235                    func_dict,
236                    func,
237                    ginfo,
238                    global_vars_seen_dict,
239                    is_filtered=builder.is_filtered(),
240                ),
241            )
242        else:
243            for input_info in ti.iterlines(output_lines):
244                input_line = input_info.line
245                args = input_info.args
246                if is_in_function_start:
247                    if input_line == "":
248                        continue
249                    if input_line.lstrip().startswith(";"):
250                        m = common.CHECK_RE.match(input_line)
251                        if not m or m.group(1) not in prefix_set:
252                            output_lines.append(input_line)
253                            continue
254
255                    # Print out the various check lines here.
256                    generated_prefixes.extend(
257                        output_type.add_checks(
258                            output_lines,
259                            check_indent + ";",
260                            run_list,
261                            func_dict,
262                            func_name,
263                            ginfo,
264                            global_vars_seen_dict,
265                            is_filtered=builder.is_filtered(),
266                        )
267                    )
268                    is_in_function_start = False
269
270                if is_in_function:
271                    if common.should_add_line_to_output(input_line, prefix_set):
272                        # This input line of the function body will go as-is into the output.
273                        output_lines.append(input_line)
274                    else:
275                        continue
276                    if input_line.strip() == "}":
277                        is_in_function = False
278                    continue
279
280                # If it's outside a function, it just gets copied to the output.
281                output_lines.append(input_line)
282
283                m = common.IR_FUNCTION_RE.match(input_line)
284                if not m:
285                    continue
286                func_name = m.group(1)
287                if args.function is not None and func_name != args.function:
288                    # When filtering on a specific function, skip all others.
289                    continue
290                is_in_function = is_in_function_start = True
291
292        if ti.args.gen_unused_prefix_body:
293            output_lines.extend(
294                ti.get_checks_for_unused_prefixes(run_list, generated_prefixes)
295            )
296
297        common.debug("Writing %d lines to %s..." % (len(output_lines), ti.path))
298        with open(ti.path, "wb") as f:
299            f.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines])
300
301
302if __name__ == "__main__":
303    main()
304