383 lines · python
1#!/usr/bin/env python32 3"""A test case update script.4 5This script is a utility to update LLVM 'llc' based test cases with new6FileCheck patterns. It can either update all of the tests in the file or7a single test function.8"""9 10from __future__ import print_function11 12from sys import stderr13from traceback import print_exc14import argparse15import os # Used to advertise this file's name ("autogenerated_note").16import sys17 18from UpdateTestChecks import common, mir19 20# llc is the only llc-like in the LLVM tree but downstream forks can add21# additional ones here if they have them.22LLC_LIKE_TOOLS = [23 "llc",24]25 26 27def update_test(ti: common.TestInfo):28 triple_in_ir = None29 for l in ti.input_lines:30 m = common.TRIPLE_IR_RE.match(l)31 if m:32 triple_in_ir = m.groups()[0]33 break34 35 run_list = []36 mir_run_list = []37 for l in ti.run_lines:38 if "|" not in l:39 common.warn("Skipping unparsable RUN line: " + l)40 continue41 42 commands = [cmd.strip() for cmd in l.split("|")]43 assert len(commands) >= 244 preprocess_cmd = None45 if len(commands) > 2:46 preprocess_cmd = " | ".join(commands[:-2])47 llc_cmd = commands[-2]48 filecheck_cmd = commands[-1]49 llc_tool = llc_cmd.split(" ")[0]50 51 triple_in_cmd = None52 m = common.TRIPLE_ARG_RE.search(llc_cmd)53 if m:54 triple_in_cmd = m.groups()[0]55 56 march_in_cmd = ti.args.default_march57 m = common.MARCH_ARG_RE.search(llc_cmd)58 if m:59 march_in_cmd = m.groups()[0]60 61 target_list = run_list62 m = common.DEBUG_ONLY_ARG_RE.search(llc_cmd)63 if m and m.groups()[0] == "isel":64 from UpdateTestChecks import isel as output_type65 elif not m and common.STOP_PASS_RE.search(llc_cmd):66 # MIR output mode. If -debug-only is present assume67 # the debug output is the main point of interest.68 target_list = mir_run_list69 else:70 from UpdateTestChecks import asm as output_type71 72 common.verify_filecheck_prefixes(filecheck_cmd)73 74 llc_like_tools = LLC_LIKE_TOOLS[:]75 if ti.args.tool:76 llc_like_tools.append(ti.args.tool)77 if llc_tool not in llc_like_tools:78 common.warn("Skipping non-llc RUN line: " + l)79 continue80 81 if not filecheck_cmd.startswith("FileCheck "):82 common.warn("Skipping non-FileChecked RUN line: " + l)83 continue84 85 llc_cmd_args = llc_cmd[len(llc_tool) :].strip()86 llc_cmd_args = llc_cmd_args.replace("< %s", "").replace("%s", "").strip()87 if ti.path.endswith(".mir"):88 llc_cmd_args += " -x mir"89 check_prefixes = common.get_check_prefixes(filecheck_cmd)90 91 # FIXME: We should use multiple check prefixes to common check lines. For92 # now, we just ignore all but the last.93 target_list.append(94 (95 check_prefixes,96 llc_tool,97 llc_cmd_args,98 preprocess_cmd,99 triple_in_cmd,100 march_in_cmd,101 )102 )103 104 if ti.path.endswith(".mir"):105 check_indent = " "106 else:107 check_indent = ""108 109 ginfo = common.make_asm_generalizer(version=1)110 builder = common.FunctionTestBuilder(111 run_list=run_list,112 flags=type(113 "",114 (object,),115 {116 "verbose": ti.args.verbose,117 "filters": ti.args.filters,118 "function_signature": False,119 "check_attributes": False,120 "replace_value_regex": [],121 },122 ),123 scrubber_args=[ti.args],124 path=ti.path,125 ginfo=ginfo,126 )127 128 # Dictionary to store MIR function bodies separately129 mir_func_dict = {}130 for run_tuple, is_mir in [(run, False) for run in run_list] + [131 (run, True) for run in mir_run_list132 ]:133 (134 prefixes,135 llc_tool,136 llc_args,137 preprocess_cmd,138 triple_in_cmd,139 march_in_cmd,140 ) = run_tuple141 142 common.debug("Extracted LLC cmd:", llc_tool, llc_args)143 common.debug("Extracted FileCheck prefixes:", str(prefixes))144 145 raw_tool_output = common.invoke_tool(146 ti.args.llc_binary or llc_tool,147 llc_args,148 ti.path,149 preprocess_cmd,150 verbose=ti.args.verbose,151 )152 triple = triple_in_cmd or triple_in_ir153 if not triple:154 triple = common.get_triple_from_march(march_in_cmd)155 156 if is_mir:157 # MIR output mode158 common.debug("Detected MIR output mode for prefixes:", str(prefixes))159 for prefix in prefixes:160 if prefix not in mir_func_dict:161 mir_func_dict[prefix] = {}162 163 mir.build_function_info_dictionary(164 ti.path,165 raw_tool_output,166 triple,167 prefixes,168 mir_func_dict,169 ti.args.verbose,170 )171 else:172 # ASM output mode173 scrubber, function_re = output_type.get_run_handler(triple)174 if 0 == builder.process_run_line(175 function_re, scrubber, raw_tool_output, prefixes176 ):177 common.warn(178 "Couldn't match any function. Possibly the wrong target triple has been provided"179 )180 builder.processed_prefixes(prefixes)181 182 func_dict = builder.finish_and_get_func_dict()183 184 # Check for conflicts: same prefix used for both ASM and MIR185 conflicting_prefixes = set(func_dict.keys()) & set(mir_func_dict.keys())186 if conflicting_prefixes:187 common.warn(188 "The following prefixes are used for both ASM and MIR output, which will cause FileCheck failures: {}".format(189 ", ".join(sorted(conflicting_prefixes))190 ),191 test_file=ti.path,192 )193 for prefix in conflicting_prefixes:194 mir_func_dict[prefix] = {}195 func_dict[prefix] = {}196 197 global_vars_seen_dict = {}198 199 is_in_function = False200 is_in_function_start = False201 func_name = None202 prefix_set = set([prefix for p in run_list for prefix in p[0]])203 prefix_set.update([prefix for p in mir_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 emit217 # these in the order according to the generated output so that CHECK-LABEL218 # works properly. func_order provides that.219 220 # We can't predict where various passes might insert functions so we can't221 # be sure the input function order is maintained. Therefore, first spit222 # 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.line245 args = input_info.args246 if is_in_function_start:247 if input_line == "":248 continue249 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 continue254 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 269 # Also add MIR checks if we have them for this function270 if mir_run_list and func_name:271 mir.add_mir_checks_for_function(272 ti.path,273 output_lines,274 mir_run_list,275 mir_func_dict,276 func_name,277 single_bb=False, # Don't skip basic block labels.278 print_fixed_stack=False, # Don't print fixed stack (ASM tests don't need it).279 first_check_is_next=False, # First check is LABEL, not NEXT.280 at_the_function_name=False, # Use "name:" not "@name".281 check_indent="", # No indentation for IR files (not MIR files).282 )283 284 is_in_function_start = False285 286 if is_in_function:287 if common.should_add_line_to_output(input_line, prefix_set):288 # This input line of the function body will go as-is into the output.289 output_lines.append(input_line)290 else:291 continue292 if input_line.strip() == "}":293 is_in_function = False294 continue295 296 # If it's outside a function, it just gets copied to the output.297 output_lines.append(input_line)298 299 m = common.IR_FUNCTION_RE.match(input_line)300 if not m:301 continue302 func_name = m.group(1)303 if args.function is not None and func_name != args.function:304 # When filtering on a specific function, skip all others.305 continue306 is_in_function = is_in_function_start = True307 308 if ti.args.gen_unused_prefix_body:309 output_lines.extend(310 ti.get_checks_for_unused_prefixes(run_list, generated_prefixes)311 )312 313 common.debug("Writing %d lines to %s..." % (len(output_lines), ti.path))314 with open(ti.path, "wb") as f:315 f.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines])316 317 318def main():319 parser = argparse.ArgumentParser(description=__doc__)320 parser.add_argument(321 "--llc-binary",322 default=None,323 help='The "llc" binary to use to generate the test case',324 )325 parser.add_argument("--function", help="The function in the test file to update")326 parser.add_argument(327 "--extra_scrub",328 action="store_true",329 help="Always use additional regex to further reduce diffs between various subtargets",330 )331 parser.add_argument(332 "--x86_scrub_sp",333 action="store_true",334 default=True,335 help="Use regex for x86 sp matching to reduce diffs between various subtargets",336 )337 parser.add_argument("--no_x86_scrub_sp", action="store_false", dest="x86_scrub_sp")338 parser.add_argument(339 "--x86_scrub_rip",340 action="store_true",341 default=False,342 help="Use more regex for x86 rip matching to reduce diffs between various subtargets",343 )344 parser.add_argument(345 "--no_x86_scrub_rip", action="store_false", dest="x86_scrub_rip"346 )347 parser.add_argument(348 "--no_x86_scrub_mem_shuffle",349 action="store_true",350 default=False,351 help="Reduce scrubbing shuffles with memory operands",352 )353 parser.add_argument(354 "--tool",355 default=None,356 help="Treat the given tool name as an llc-like tool for which check lines should be generated",357 )358 parser.add_argument(359 "--default-march",360 default=None,361 help="Set a default -march for when neither triple nor arch are found in a RUN line",362 )363 parser.add_argument("tests", nargs="+")364 initial_args = common.parse_commandline_args(parser)365 366 script_name = os.path.basename(__file__)367 368 returncode = 0369 for ti in common.itertests(370 initial_args.tests, parser, script_name="utils/" + script_name371 ):372 try:373 update_test(ti)374 except Exception as e:375 stderr.write(f"Error: Failed to update test {ti.path}\n")376 print_exc()377 returncode = 1378 return returncode379 380 381if __name__ == "__main__":382 sys.exit(main())383