|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +A test update script. This script is a utility to update LLVM 'llvm-mc' based test cases with new FileCheck patterns. |
| 4 | +""" |
| 5 | + |
| 6 | +from __future__ import print_function |
| 7 | + |
| 8 | +import argparse |
| 9 | +import os # Used to advertise this file's name ("autogenerated_note"). |
| 10 | + |
| 11 | +from UpdateTestChecks import common |
| 12 | + |
| 13 | +import subprocess |
| 14 | +import re |
| 15 | + |
| 16 | +mc_LIKE_TOOLS = [ |
| 17 | + "llvm-mc", |
| 18 | +] |
| 19 | + |
| 20 | +ERROR_RE = re.compile(r"(warning|error): .*") |
| 21 | +ERROR_CHECK_RE = re.compile(r"# COM: .*") |
| 22 | +OUTPUT_SKIPPED_RE = re.compile(r"(.text)") |
| 23 | +COMMENT = { |
| 24 | + "asm" : "//", |
| 25 | + "dasm" : "#" |
| 26 | + } |
| 27 | + |
| 28 | + |
| 29 | +def invoke_tool(exe, cmd_args, testline, verbose=False): |
| 30 | + if isinstance(cmd_args, list): |
| 31 | + args = [applySubstitutions(a, substitutions) for a in cmd_args] |
| 32 | + else: |
| 33 | + args = cmd_args |
| 34 | + |
| 35 | + cmd = "echo \"" + testline + "\" | " + exe + " " + args |
| 36 | + if verbose: |
| 37 | + print("Command: ", cmd) |
| 38 | + out = subprocess.check_output(cmd, shell=True) |
| 39 | + # Fix line endings to unix CR style. |
| 40 | + return out.decode().replace("\r\n", "\n") |
| 41 | + |
| 42 | + |
| 43 | +# create tests line-by-line, here we just filter out the check lines and comments |
| 44 | +# and treat all others as tests |
| 45 | +def isTestLine(input_line, mc_mode): |
| 46 | + # Skip comment lines |
| 47 | + if input_line.strip(' \t\r').startswith(COMMENT[mc_mode]): |
| 48 | + return False |
| 49 | + elif input_line.strip(' \t\r') == '': |
| 50 | + return False |
| 51 | + # skip any CHECK lines. |
| 52 | + elif common.CHECK_RE.match(input_line): |
| 53 | + return False |
| 54 | + return True |
| 55 | + |
| 56 | +def hasErr(err): |
| 57 | + if err is None or len(err) == 0: |
| 58 | + return False |
| 59 | + if ERROR_RE.search(err): |
| 60 | + return True |
| 61 | + return False |
| 62 | + |
| 63 | +def getErrString(err): |
| 64 | + if err is None or len(err) == 0: |
| 65 | + return "" |
| 66 | + |
| 67 | + lines = err.split('\n') |
| 68 | + # take the first match |
| 69 | + for line in lines: |
| 70 | + s = ERROR_RE.search(line) |
| 71 | + if s: |
| 72 | + return s.group(0) |
| 73 | + return "" |
| 74 | + |
| 75 | +def getOutputString(out): |
| 76 | + if out is None or len(out) == 0: |
| 77 | + return "" |
| 78 | + lines = out.split('\n') |
| 79 | + output = "" |
| 80 | + |
| 81 | + for line in lines: |
| 82 | + if OUTPUT_SKIPPED_RE.search(line): |
| 83 | + continue |
| 84 | + if line.strip('\t ') == '': |
| 85 | + continue |
| 86 | + output += line.lstrip('\t ') |
| 87 | + return output |
| 88 | + |
| 89 | +def should_add_line_to_output(input_line, prefix_set, mc_mode): |
| 90 | + # special check line |
| 91 | + if mc_mode == 'dasm' and ERROR_CHECK_RE.search(input_line): |
| 92 | + return False |
| 93 | + else: |
| 94 | + return common.should_add_line_to_output(input_line, prefix_set, comment_marker=COMMENT[mc_mode]) |
| 95 | + |
| 96 | + |
| 97 | +def getStdCheckLine(prefix, output, mc_mode): |
| 98 | + lines = output.split('\n') |
| 99 | + output = "" |
| 100 | + for line in lines: |
| 101 | + output += COMMENT[mc_mode] + ' ' + prefix + ": " + line + '\n' |
| 102 | + return output |
| 103 | + |
| 104 | +def getErrCheckLine(prefix, output, mc_mode): |
| 105 | + if mc_mode == 'asm': |
| 106 | + return COMMENT[mc_mode] + ' ' + prefix + ": " + output + '\n' |
| 107 | + elif mc_mode == 'dasm': |
| 108 | + return COMMENT[mc_mode] + ' COM: ' + prefix + ": " + output + '\n' |
| 109 | + |
| 110 | +def main(): |
| 111 | + parser = argparse.ArgumentParser(description=__doc__) |
| 112 | + parser.add_argument( |
| 113 | + "--mc-binary", |
| 114 | + default=None, |
| 115 | + help='The "mc" binary to use to generate the test case', |
| 116 | + ) |
| 117 | + parser.add_argument( |
| 118 | + "--tool", |
| 119 | + default=None, |
| 120 | + help="Treat the given tool name as an mc-like tool for which check lines should be generated", |
| 121 | + ) |
| 122 | + parser.add_argument( |
| 123 | + "--default-march", |
| 124 | + default=None, |
| 125 | + help="Set a default -march for when neither triple nor arch are found in a RUN line", |
| 126 | + ) |
| 127 | + parser.add_argument("tests", nargs="+") |
| 128 | + initial_args = common.parse_commandline_args(parser) |
| 129 | + |
| 130 | + script_name = os.path.basename(__file__) |
| 131 | + |
| 132 | + for ti in common.itertests( |
| 133 | + initial_args.tests, parser, script_name="utils/" + script_name |
| 134 | + ): |
| 135 | + if ti.path.endswith('.s'): |
| 136 | + mc_mode = "asm" |
| 137 | + elif ti.path.endswith('.txt'): |
| 138 | + mc_mode = "dasm" |
| 139 | + else: |
| 140 | + common.warn("Expected .s and .txt, Skipping file : ", ti.path) |
| 141 | + continue |
| 142 | + |
| 143 | + triple_in_ir = None |
| 144 | + for l in ti.input_lines: |
| 145 | + m = common.TRIPLE_IR_RE.match(l) |
| 146 | + if m: |
| 147 | + triple_in_ir = m.groups()[0] |
| 148 | + break |
| 149 | + |
| 150 | + run_list = [] |
| 151 | + for l in ti.run_lines: |
| 152 | + if "|" not in l: |
| 153 | + common.warn("Skipping unparsable RUN line: " + l) |
| 154 | + continue |
| 155 | + |
| 156 | + commands = [cmd.strip() for cmd in l.split("|")] |
| 157 | + assert len(commands) >= 2 |
| 158 | + mc_cmd = " | ".join(commands[:-1]) |
| 159 | + filecheck_cmd = commands[-1] |
| 160 | + mc_tool = mc_cmd.split(" ")[0] |
| 161 | + |
| 162 | + triple_in_cmd = None |
| 163 | + m = common.TRIPLE_ARG_RE.search(mc_cmd) |
| 164 | + if m: |
| 165 | + triple_in_cmd = m.groups()[0] |
| 166 | + |
| 167 | + march_in_cmd = ti.args.default_march |
| 168 | + m = common.MARCH_ARG_RE.search(mc_cmd) |
| 169 | + if m: |
| 170 | + march_in_cmd = m.groups()[0] |
| 171 | + |
| 172 | + common.verify_filecheck_prefixes(filecheck_cmd) |
| 173 | + |
| 174 | + mc_like_tools = mc_LIKE_TOOLS[:] |
| 175 | + if ti.args.tool: |
| 176 | + mc_like_tools.append(ti.args.tool) |
| 177 | + if mc_tool not in mc_like_tools: |
| 178 | + common.warn("Skipping non-mc RUN line: " + l) |
| 179 | + continue |
| 180 | + |
| 181 | + if not filecheck_cmd.startswith("FileCheck "): |
| 182 | + common.warn("Skipping non-FileChecked RUN line: " + l) |
| 183 | + continue |
| 184 | + |
| 185 | + mc_cmd_args = mc_cmd[len(mc_tool) :].strip() |
| 186 | + mc_cmd_args = mc_cmd_args.replace("< %s", "").replace("%s", "").strip() |
| 187 | + check_prefixes = common.get_check_prefixes(filecheck_cmd) |
| 188 | + |
| 189 | + run_list.append( |
| 190 | + ( |
| 191 | + check_prefixes, |
| 192 | + mc_tool, |
| 193 | + mc_cmd_args, |
| 194 | + triple_in_cmd, |
| 195 | + march_in_cmd, |
| 196 | + ) |
| 197 | + ) |
| 198 | + |
| 199 | + |
| 200 | + # find all test line from input |
| 201 | + testlines = [l for l in ti.input_lines if isTestLine(l, mc_mode)] |
| 202 | + run_list_size = len(run_list) |
| 203 | + testnum = len(testlines) |
| 204 | + |
| 205 | + raw_output = [] |
| 206 | + raw_prefixes = [] |
| 207 | + for ( |
| 208 | + prefixes, |
| 209 | + mc_tool, |
| 210 | + mc_args, |
| 211 | + triple_in_cmd, |
| 212 | + march_in_cmd, |
| 213 | + ) in run_list: |
| 214 | + common.debug("Extracted mc cmd:", mc_tool, mc_args) |
| 215 | + common.debug("Extracted FileCheck prefixes:", str(prefixes)) |
| 216 | + common.debug("Extracted triple :", str(triple_in_cmd)) |
| 217 | + common.debug("Extracted march:", str(march_in_cmd)) |
| 218 | + |
| 219 | + triple = triple_in_cmd or triple_in_ir |
| 220 | + if not triple: |
| 221 | + triple = common.get_triple_from_march(march_in_cmd) |
| 222 | + |
| 223 | + raw_output.append([]) |
| 224 | + for line in testlines: |
| 225 | + # get output for each testline |
| 226 | + out = invoke_tool( |
| 227 | + ti.args.mc_binary or mc_tool, |
| 228 | + mc_args, |
| 229 | + line, |
| 230 | + verbose=ti.args.verbose, |
| 231 | + ) |
| 232 | + raw_output[-1].append(out) |
| 233 | + |
| 234 | + common.debug("Collect raw tool lines:", str(len(raw_output[-1]))) |
| 235 | + |
| 236 | + raw_prefixes.append(prefixes) |
| 237 | + |
| 238 | + output_lines = [] |
| 239 | + generated_prefixes = [] |
| 240 | + used_prefixes = set() |
| 241 | + prefix_set = set([prefix for p in run_list for prefix in p[0]]) |
| 242 | + common.debug("Rewriting FileCheck prefixes:", str(prefix_set)) |
| 243 | + |
| 244 | + for test_id in range(testnum): |
| 245 | + input_line = testlines[test_id] |
| 246 | + |
| 247 | + # a {prefix : output, [runid] } dict |
| 248 | + # insert output to a prefix-key dict, and do a max sorting |
| 249 | + # to select the most-used prefix which share the same output string |
| 250 | + p_dict = {} |
| 251 | + for run_id in range(run_list_size): |
| 252 | + out = raw_output[run_id][test_id] |
| 253 | + |
| 254 | + if hasErr(out): |
| 255 | + o = getErrString(out) |
| 256 | + else: |
| 257 | + o = getOutputString(out) |
| 258 | + |
| 259 | + prefixes = raw_prefixes[run_id] |
| 260 | + |
| 261 | + for p in prefixes: |
| 262 | + if p not in p_dict: |
| 263 | + p_dict[p] = o, [run_id] |
| 264 | + else: |
| 265 | + if p_dict[p] == (None, []): |
| 266 | + continue |
| 267 | + |
| 268 | + prev_o, run_ids = p_dict[p] |
| 269 | + if o == prev_o: |
| 270 | + run_ids.append(run_id) |
| 271 | + p_dict[p] = o, run_ids |
| 272 | + else: |
| 273 | + # conflict, discard |
| 274 | + p_dict[p] = None, [] |
| 275 | + |
| 276 | + p_dict_sorted = dict(sorted(p_dict.items(), key=lambda item: -len(item[1][1]))) |
| 277 | + |
| 278 | + # prefix is selected and generated with most shared output lines |
| 279 | + # each run_id can only be used once |
| 280 | + gen_prefix = "" |
| 281 | + used_runid = set() |
| 282 | + for prefix, tup in p_dict_sorted.items(): |
| 283 | + o, run_ids = tup |
| 284 | + |
| 285 | + if len(run_ids) == 0: |
| 286 | + continue |
| 287 | + |
| 288 | + skip = False |
| 289 | + for i in run_ids: |
| 290 | + if i in used_runid: |
| 291 | + skip = True |
| 292 | + else: |
| 293 | + used_runid.add(i) |
| 294 | + if not skip: |
| 295 | + used_prefixes.add(prefix) |
| 296 | + |
| 297 | + if hasErr(o): |
| 298 | + gen_prefix += getErrCheckLine(prefix, o, mc_mode) |
| 299 | + else: |
| 300 | + gen_prefix += getStdCheckLine(prefix, o, mc_mode) |
| 301 | + |
| 302 | + generated_prefixes.append(gen_prefix.rstrip('\n')) |
| 303 | + |
| 304 | + # write output |
| 305 | + prefix_id = 0 |
| 306 | + for input_info in ti.iterlines(output_lines): |
| 307 | + input_line = input_info.line |
| 308 | + if isTestLine(input_line, mc_mode): |
| 309 | + output_lines.append(generated_prefixes[prefix_id]) |
| 310 | + output_lines.append(input_line) |
| 311 | + prefix_id += 1 |
| 312 | + |
| 313 | + elif should_add_line_to_output(input_line, prefix_set, mc_mode): |
| 314 | + output_lines.append(input_line) |
| 315 | + |
| 316 | + elif input_line in ti.run_lines or input_line == "": |
| 317 | + output_lines.append(input_line) |
| 318 | + |
| 319 | + if ti.args.gen_unused_prefix_body: |
| 320 | + output_lines.extend( |
| 321 | + ti.get_checks_for_unused_prefixes(run_list, used_prefixes) |
| 322 | + ) |
| 323 | + |
| 324 | + common.debug("Writing %d lines to %s..." % (len(output_lines), ti.path)) |
| 325 | + with open(ti.path, "wb") as f: |
| 326 | + f.writelines(["{}\n".format(l).encode("utf-8") for l in output_lines]) |
| 327 | + |
| 328 | + |
| 329 | +if __name__ == "__main__": |
| 330 | + main() |
0 commit comments