236 lines · python
1#!/usr/bin/env python32 3"""A script to generate FileCheck statements for 'opt' analysis tests.4 5This script is a utility to update LLVM opt analysis test cases with new6FileCheck patterns. It can either update all of the tests in the file or7a single test function.8 9Example usage:10$ update_analyze_test_checks.py --opt=../bin/opt test/foo.ll11 12Workflow:131. Make a compiler patch that requires updating some number of FileCheck lines14 in regression test files.152. Save the patch and revert it from your local work area.163. Update the RUN-lines in the affected regression tests to look canonical.17 Example: "; RUN: opt < %s -passes='print<cost-model>' -disable-output 2>&1 | FileCheck %s"184. Refresh the FileCheck lines for either the entire file or select functions by19 running this script.205. Commit the fresh baseline of checks.216. Apply your patch from step 1 and rebuild your local binaries.227. Re-run this script on affected regression tests.238. Check the diffs to ensure the script has done something reasonable.249. Submit a patch including the regression test diffs for review.25 26A common pattern is to have the script insert complete checking of every27instruction. Then, edit it down to only check the relevant instructions.28The script is designed to make adding checks to a test case fast, it is *not*29designed to be authoratitive about what constitutes a good test!30"""31 32from __future__ import print_function33 34from sys import stderr35from traceback import print_exc36import argparse37import os # Used to advertise this file's name ("autogenerated_note").38import sys39import re40 41from UpdateTestChecks import common42 43 44def update_test(opt_basename: str, ti: common.TestInfo):45 triple_in_ir = None46 for l in ti.input_lines:47 m = common.TRIPLE_IR_RE.match(l)48 if m:49 triple_in_ir = m.groups()[0]50 break51 52 prefix_list = []53 for l in ti.run_lines:54 if "|" not in l:55 common.warn("Skipping unparsable RUN line: " + l)56 continue57 58 (tool_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split("|", 1)])59 common.verify_filecheck_prefixes(filecheck_cmd)60 61 if not tool_cmd.startswith(opt_basename + " "):62 common.warn("WSkipping non-%s RUN line: %s" % (opt_basename, l))63 continue64 65 if not filecheck_cmd.startswith("FileCheck "):66 common.warn("Skipping non-FileChecked RUN line: " + l)67 continue68 69 tool_cmd_args = tool_cmd[len(opt_basename) :].strip()70 tool_cmd_args = tool_cmd_args.replace("< %s", "").replace("%s", "").strip()71 check_prefixes = common.get_check_prefixes(filecheck_cmd)72 73 # FIXME: We should use multiple check prefixes to common check lines. For74 # now, we just ignore all but the last.75 prefix_list.append((check_prefixes, tool_cmd_args))76 77 ginfo = common.make_analyze_generalizer(version=1)78 builder = common.FunctionTestBuilder(79 run_list=prefix_list,80 flags=type(81 "",82 (object,),83 {84 "verbose": ti.args.verbose,85 "filters": ti.args.filters,86 "function_signature": False,87 "check_attributes": False,88 "replace_value_regex": [],89 },90 ),91 scrubber_args=[],92 path=ti.path,93 ginfo=ginfo,94 )95 96 for prefixes, opt_args in prefix_list:97 common.debug("Extracted opt cmd:", opt_basename, opt_args, file=sys.stderr)98 common.debug("Extracted FileCheck prefixes:", str(prefixes), file=sys.stderr)99 100 raw_tool_outputs = common.invoke_tool(ti.args.opt_binary, opt_args, ti.path)101 102 if re.search(r"Printing analysis ", raw_tool_outputs) is not None:103 # Split analysis outputs by "Printing analysis " declarations.104 for raw_tool_output in re.split(r"Printing analysis ", raw_tool_outputs):105 builder.process_run_line(106 common.ANALYZE_FUNCTION_RE,107 common.scrub_body,108 raw_tool_output,109 prefixes,110 )111 elif (112 re.search(113 r"(LV|LDist|HashRecognize): Checking a loop in ", raw_tool_outputs114 )115 is not None116 ):117 for raw_tool_output in re.split(118 r"(LV|LDist|HashRecognize): Checking a loop in ", raw_tool_outputs119 ):120 builder.process_run_line(121 common.LOOP_PASS_DEBUG_RE,122 common.scrub_body,123 raw_tool_output,124 prefixes,125 )126 else:127 common.warn("Don't know how to deal with this output")128 continue129 130 builder.processed_prefixes(prefixes)131 132 func_dict = builder.finish_and_get_func_dict()133 is_in_function = False134 is_in_function_start = False135 prefix_set = set([prefix for prefixes, _ in prefix_list for prefix in prefixes])136 common.debug("Rewriting FileCheck prefixes:", str(prefix_set), file=sys.stderr)137 output_lines = []138 139 generated_prefixes = []140 for input_info in ti.iterlines(output_lines):141 input_line = input_info.line142 args = input_info.args143 if is_in_function_start:144 if input_line == "":145 continue146 if input_line.lstrip().startswith(";"):147 m = common.CHECK_RE.match(input_line)148 if not m or m.group(1) not in prefix_set:149 output_lines.append(input_line)150 continue151 152 # Print out the various check lines here.153 generated_prefixes.extend(154 common.add_analyze_checks(155 output_lines,156 ";",157 prefix_list,158 func_dict,159 func_name,160 ginfo,161 is_filtered=builder.is_filtered(),162 )163 )164 is_in_function_start = False165 166 if is_in_function:167 if common.should_add_line_to_output(input_line, prefix_set):168 # This input line of the function body will go as-is into the output.169 output_lines.append(input_line)170 else:171 continue172 if input_line.strip() == "}":173 is_in_function = False174 continue175 176 # If it's outside a function, it just gets copied to the output.177 output_lines.append(input_line)178 179 m = common.IR_FUNCTION_RE.match(input_line)180 if not m:181 continue182 func_name = m.group(1)183 if ti.args.function is not None and func_name != ti.args.function:184 # When filtering on a specific function, skip all others.185 continue186 is_in_function = is_in_function_start = True187 188 if ti.args.gen_unused_prefix_body:189 output_lines.extend(190 ti.get_checks_for_unused_prefixes(prefix_list, generated_prefixes)191 )192 193 common.debug("Writing %d lines to %s..." % (len(output_lines), ti.path))194 195 with open(ti.path, "wb") as f:196 f.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines])197 198 199def main():200 from argparse import RawTextHelpFormatter201 202 parser = argparse.ArgumentParser(203 description=__doc__, formatter_class=RawTextHelpFormatter204 )205 parser.add_argument(206 "--opt-binary",207 default="opt",208 help="The opt binary used to generate the test case",209 )210 parser.add_argument("--function", help="The function in the test file to update")211 parser.add_argument("tests", nargs="+")212 initial_args = common.parse_commandline_args(parser)213 214 script_name = os.path.basename(__file__)215 216 opt_basename = os.path.basename(initial_args.opt_binary)217 if opt_basename != "opt":218 common.error("Unexpected opt name: " + opt_basename)219 sys.exit(1)220 221 returncode = 0222 for ti in common.itertests(223 initial_args.tests, parser, script_name="utils/" + script_name224 ):225 try:226 update_test(opt_basename, ti)227 except Exception:228 stderr.write(f"Error: Failed to update test {ti.path}\n")229 print_exc()230 returncode = 1231 return returncode232 233 234if __name__ == "__main__":235 sys.exit(main())236