1from __future__ import print_function 2 3import argparse 4import copy 5import glob 6import itertools 7import os 8import re 9import subprocess 10import sys 11import shlex 12 13from typing import List 14 15##### Common utilities for update_*test_checks.py 16 17 18_verbose = False 19_prefix_filecheck_ir_name = "" 20 21""" 22Version changelog: 23 241: Initial version, used by tests that don't specify --version explicitly. 252: --function-signature is now enabled by default and also checks return 26 type/attributes. 273: Opening parenthesis of function args is kept on the first LABEL line 28 in case arguments are split to a separate SAME line. 294: --check-globals now has a third option ('smart'). The others are now called 30 'none' and 'all'. 'smart' is the default. 31""" 32DEFAULT_VERSION = 4 33 34 35SUPPORTED_ANALYSES = { 36 "Branch Probability Analysis", 37 "Cost Model Analysis", 38 "Loop Access Analysis", 39 "Scalar Evolution Analysis", 40} 41 42 43class Regex(object): 44 """Wrap a compiled regular expression object to allow deep copy of a regexp. 45 This is required for the deep copy done in do_scrub. 46 47 """ 48 49 def __init__(self, regex): 50 self.regex = regex 51 52 def __deepcopy__(self, memo): 53 result = copy.copy(self) 54 result.regex = self.regex 55 return result 56 57 def search(self, line): 58 return self.regex.search(line) 59 60 def sub(self, repl, line): 61 return self.regex.sub(repl, line) 62 63 def pattern(self): 64 return self.regex.pattern 65 66 def flags(self): 67 return self.regex.flags 68 69 70class Filter(Regex): 71 """Augment a Regex object with a flag indicating whether a match should be 72 added (!is_filter_out) or removed (is_filter_out) from the generated checks. 73 74 """ 75 76 def __init__(self, regex, is_filter_out): 77 super(Filter, self).__init__(regex) 78 self.is_filter_out = is_filter_out 79 80 def __deepcopy__(self, memo): 81 result = copy.deepcopy(super(Filter, self), memo) 82 result.is_filter_out = copy.deepcopy(self.is_filter_out, memo) 83 return result 84 85 86def parse_commandline_args(parser): 87 class RegexAction(argparse.Action): 88 """Add a regular expression option value to a list of regular expressions. 89 This compiles the expression, wraps it in a Regex and adds it to the option 90 value list.""" 91 92 def __init__(self, option_strings, dest, nargs=None, **kwargs): 93 if nargs is not None: 94 raise ValueError("nargs not allowed") 95 super(RegexAction, self).__init__(option_strings, dest, **kwargs) 96 97 def do_call(self, namespace, values, flags): 98 value_list = getattr(namespace, self.dest) 99 if value_list is None: 100 value_list = [] 101 102 try: 103 value_list.append(Regex(re.compile(values, flags))) 104 except re.error as error: 105 raise ValueError( 106 "{}: Invalid regular expression '{}' ({})".format( 107 option_string, error.pattern, error.msg 108 ) 109 ) 110 111 setattr(namespace, self.dest, value_list) 112 113 def __call__(self, parser, namespace, values, option_string=None): 114 self.do_call(namespace, values, 0) 115 116 class FilterAction(RegexAction): 117 """Add a filter to a list of filter option values.""" 118 119 def __init__(self, option_strings, dest, nargs=None, **kwargs): 120 super(FilterAction, self).__init__(option_strings, dest, nargs, **kwargs) 121 122 def __call__(self, parser, namespace, values, option_string=None): 123 super(FilterAction, self).__call__(parser, namespace, values, option_string) 124 125 value_list = getattr(namespace, self.dest) 126 127 is_filter_out = option_string == "--filter-out" 128 129 value_list[-1] = Filter(value_list[-1].regex, is_filter_out) 130 131 setattr(namespace, self.dest, value_list) 132 133 filter_group = parser.add_argument_group( 134 "filtering", 135 """Filters are applied to each output line according to the order given. The 136 first matching filter terminates filter processing for that current line.""", 137 ) 138 139 filter_group.add_argument( 140 "--filter", 141 action=FilterAction, 142 dest="filters", 143 metavar="REGEX", 144 help="Only include lines matching REGEX (may be specified multiple times)", 145 ) 146 filter_group.add_argument( 147 "--filter-out", 148 action=FilterAction, 149 dest="filters", 150 metavar="REGEX", 151 help="Exclude lines matching REGEX", 152 ) 153 154 parser.add_argument( 155 "--include-generated-funcs", 156 action="store_true", 157 help="Output checks for functions not in source", 158 ) 159 parser.add_argument( 160 "-v", "--verbose", action="store_true", help="Show verbose output" 161 ) 162 parser.add_argument( 163 "-u", 164 "--update-only", 165 action="store_true", 166 help="Only update test if it was already autogened", 167 ) 168 parser.add_argument( 169 "--force-update", 170 action="store_true", 171 help="Update test even if it was autogened by a different script", 172 ) 173 parser.add_argument( 174 "--enable", 175 action="store_true", 176 dest="enabled", 177 default=True, 178 help="Activate CHECK line generation from this point forward", 179 ) 180 parser.add_argument( 181 "--disable", 182 action="store_false", 183 dest="enabled", 184 help="Deactivate CHECK line generation from this point forward", 185 ) 186 parser.add_argument( 187 "--replace-value-regex", 188 nargs="+", 189 default=[], 190 help="List of regular expressions to replace matching value names", 191 ) 192 parser.add_argument( 193 "--prefix-filecheck-ir-name", 194 default="", 195 help="Add a prefix to FileCheck IR value names to avoid conflicts with scripted names", 196 ) 197 parser.add_argument( 198 "--global-value-regex", 199 nargs="+", 200 default=[], 201 help="List of regular expressions that a global value declaration must match to generate a check (has no effect if checking globals is not enabled)", 202 ) 203 parser.add_argument( 204 "--global-hex-value-regex", 205 nargs="+", 206 default=[], 207 help="List of regular expressions such that, for matching global value declarations, literal integer values should be encoded in hex in the associated FileCheck directives", 208 ) 209 # FIXME: in 3.9, we can use argparse.BooleanOptionalAction. At that point, 210 # we need to rename the flag to just -generate-body-for-unused-prefixes. 211 parser.add_argument( 212 "--no-generate-body-for-unused-prefixes", 213 action="store_false", 214 dest="gen_unused_prefix_body", 215 default=True, 216 help="Generate a function body that always matches for unused prefixes. This is useful when unused prefixes are desired, and it avoids needing to annotate each FileCheck as allowing them.", 217 ) 218 # This is the default when regenerating existing tests. The default when 219 # generating new tests is determined by DEFAULT_VERSION. 220 parser.add_argument( 221 "--version", type=int, default=1, help="The version of output format" 222 ) 223 args = parser.parse_args() 224 # TODO: This should not be handled differently from the other options 225 global _verbose, _global_value_regex, _global_hex_value_regex 226 _verbose = args.verbose 227 _global_value_regex = args.global_value_regex 228 _global_hex_value_regex = args.global_hex_value_regex 229 return args 230 231 232def parse_args(parser, argv): 233 args = parser.parse_args(argv) 234 if args.version >= 2: 235 args.function_signature = True 236 # TODO: This should not be handled differently from the other options 237 global _verbose, _global_value_regex, _global_hex_value_regex 238 _verbose = args.verbose 239 _global_value_regex = args.global_value_regex 240 _global_hex_value_regex = args.global_hex_value_regex 241 if "check_globals" in args and args.check_globals == "default": 242 args.check_globals = "none" if args.version < 4 else "smart" 243 return args 244 245 246class InputLineInfo(object): 247 def __init__(self, line, line_number, args, argv): 248 self.line = line 249 self.line_number = line_number 250 self.args = args 251 self.argv = argv 252 253 254class TestInfo(object): 255 def __init__( 256 self, 257 test, 258 parser, 259 script_name, 260 input_lines, 261 args, 262 argv, 263 comment_prefix, 264 argparse_callback, 265 ): 266 self.parser = parser 267 self.argparse_callback = argparse_callback 268 self.path = test 269 self.args = args 270 if args.prefix_filecheck_ir_name: 271 global _prefix_filecheck_ir_name 272 _prefix_filecheck_ir_name = args.prefix_filecheck_ir_name 273 self.argv = argv 274 self.input_lines = input_lines 275 self.run_lines = find_run_lines(test, self.input_lines) 276 self.comment_prefix = comment_prefix 277 if self.comment_prefix is None: 278 if self.path.endswith(".mir"): 279 self.comment_prefix = "#" 280 else: 281 self.comment_prefix = ";" 282 self.autogenerated_note_prefix = self.comment_prefix + " " + UTC_ADVERT 283 self.test_autogenerated_note = self.autogenerated_note_prefix + script_name 284 self.test_autogenerated_note += get_autogennote_suffix(parser, self.args) 285 self.test_unused_note = ( 286 self.comment_prefix + self.comment_prefix + " " + UNUSED_NOTE 287 ) 288 289 def ro_iterlines(self): 290 for line_num, input_line in enumerate(self.input_lines): 291 args, argv = check_for_command( 292 input_line, self.parser, self.args, self.argv, self.argparse_callback 293 ) 294 yield InputLineInfo(input_line, line_num, args, argv) 295 296 def iterlines(self, output_lines): 297 output_lines.append(self.test_autogenerated_note) 298 for line_info in self.ro_iterlines(): 299 input_line = line_info.line 300 # Discard any previous script advertising. 301 if input_line.startswith(self.autogenerated_note_prefix): 302 continue 303 self.args = line_info.args 304 self.argv = line_info.argv 305 if not self.args.enabled: 306 output_lines.append(input_line) 307 continue 308 yield line_info 309 310 def get_checks_for_unused_prefixes( 311 self, run_list, used_prefixes: List[str] 312 ) -> List[str]: 313 run_list = [element for element in run_list if element[0] is not None] 314 unused_prefixes = set( 315 [prefix for sublist in run_list for prefix in sublist[0]] 316 ).difference(set(used_prefixes)) 317 318 ret = [] 319 if not unused_prefixes: 320 return ret 321 ret.append(self.test_unused_note) 322 for unused in sorted(unused_prefixes): 323 ret.append( 324 "{comment} {prefix}: {match_everything}".format( 325 comment=self.comment_prefix, 326 prefix=unused, 327 match_everything=r"""{{.*}}""", 328 ) 329 ) 330 return ret 331 332 333def itertests( 334 test_patterns, parser, script_name, comment_prefix=None, argparse_callback=None 335): 336 for pattern in test_patterns: 337 # On Windows we must expand the patterns ourselves. 338 tests_list = glob.glob(pattern) 339 if not tests_list: 340 warn("Test file pattern '%s' was not found. Ignoring it." % (pattern,)) 341 continue 342 for test in tests_list: 343 with open(test) as f: 344 input_lines = [l.rstrip() for l in f] 345 first_line = input_lines[0] if input_lines else "" 346 if UTC_AVOID in first_line: 347 warn("Skipping test that must not be autogenerated: " + test) 348 continue 349 is_regenerate = UTC_ADVERT in first_line 350 351 # If we're generating a new test, set the default version to the latest. 352 argv = sys.argv[:] 353 if not is_regenerate: 354 argv.insert(1, "--version=" + str(DEFAULT_VERSION)) 355 356 args = parse_args(parser, argv[1:]) 357 if argparse_callback is not None: 358 argparse_callback(args) 359 if is_regenerate: 360 if script_name not in first_line and not args.force_update: 361 warn( 362 "Skipping test which wasn't autogenerated by " + script_name, 363 test, 364 ) 365 continue 366 args, argv = check_for_command( 367 first_line, parser, args, argv, argparse_callback 368 ) 369 elif args.update_only: 370 assert UTC_ADVERT not in first_line 371 warn("Skipping test which isn't autogenerated: " + test) 372 continue 373 final_input_lines = [] 374 for l in input_lines: 375 if UNUSED_NOTE in l: 376 break 377 final_input_lines.append(l) 378 yield TestInfo( 379 test, 380 parser, 381 script_name, 382 final_input_lines, 383 args, 384 argv, 385 comment_prefix, 386 argparse_callback, 387 ) 388 389 390def should_add_line_to_output( 391 input_line, 392 prefix_set, 393 *, 394 skip_global_checks=False, 395 skip_same_checks=False, 396 comment_marker=";", 397): 398 # Skip any blank comment lines in the IR. 399 if not skip_global_checks and input_line.strip() == comment_marker: 400 return False 401 # Skip a special double comment line we use as a separator. 402 if input_line.strip() == comment_marker + SEPARATOR: 403 return False 404 # Skip any blank lines in the IR. 405 # if input_line.strip() == '': 406 # return False 407 # And skip any CHECK lines. We're building our own. 408 m = CHECK_RE.match(input_line) 409 if m and m.group(1) in prefix_set: 410 if skip_same_checks and CHECK_SAME_RE.match(input_line): 411 # The previous CHECK line was removed, so don't leave this dangling 412 return False 413 if skip_global_checks: 414 # Skip checks only if they are of global value definitions 415 global_ir_value_re = re.compile(r"(\[\[|@)", flags=(re.M)) 416 is_global = global_ir_value_re.search(input_line) 417 return not is_global 418 return False 419 420 return True 421 422 423# Perform lit-like substitutions 424def getSubstitutions(sourcepath): 425 sourcedir = os.path.dirname(sourcepath) 426 return [ 427 ("%s", sourcepath), 428 ("%S", sourcedir), 429 ("%p", sourcedir), 430 ("%{pathsep}", os.pathsep), 431 ] 432 433 434def applySubstitutions(s, substitutions): 435 for a, b in substitutions: 436 s = s.replace(a, b) 437 return s 438 439 440# Invoke the tool that is being tested. 441def invoke_tool(exe, cmd_args, ir, preprocess_cmd=None, verbose=False): 442 with open(ir) as ir_file: 443 substitutions = getSubstitutions(ir) 444 445 # TODO Remove the str form which is used by update_test_checks.py and 446 # update_llc_test_checks.py 447 # The safer list form is used by update_cc_test_checks.py 448 if preprocess_cmd: 449 # Allow pre-processing the IR file (e.g. using sed): 450 assert isinstance( 451 preprocess_cmd, str 452 ) # TODO: use a list instead of using shell 453 preprocess_cmd = applySubstitutions(preprocess_cmd, substitutions).strip() 454 if verbose: 455 print( 456 "Pre-processing input file: ", 457 ir, 458 " with command '", 459 preprocess_cmd, 460 "'", 461 sep="", 462 file=sys.stderr, 463 ) 464 # Python 2.7 doesn't have subprocess.DEVNULL: 465 with open(os.devnull, "w") as devnull: 466 pp = subprocess.Popen( 467 preprocess_cmd, shell=True, stdin=devnull, stdout=subprocess.PIPE 468 ) 469 ir_file = pp.stdout 470 471 if isinstance(cmd_args, list): 472 args = [applySubstitutions(a, substitutions) for a in cmd_args] 473 stdout = subprocess.check_output([exe] + args, stdin=ir_file) 474 else: 475 stdout = subprocess.check_output( 476 exe + " " + applySubstitutions(cmd_args, substitutions), 477 shell=True, 478 stdin=ir_file, 479 ) 480 if sys.version_info[0] > 2: 481 # FYI, if you crashed here with a decode error, your run line probably 482 # results in bitcode or other binary format being written to the pipe. 483 # For an opt test, you probably want to add -S or -disable-output. 484 stdout = stdout.decode() 485 # Fix line endings to unix CR style. 486 return stdout.replace("\r\n", "\n") 487 488 489##### LLVM IR parser 490RUN_LINE_RE = re.compile(r"^\s*(?://|[;#])\s*RUN:\s*(.*)$") 491CHECK_PREFIX_RE = re.compile(r"--?check-prefix(?:es)?[= ](\S+)") 492PREFIX_RE = re.compile("^[a-zA-Z0-9_-]+$") 493CHECK_RE = re.compile( 494 r"^\s*(?://|[;#])\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL|-SAME|-EMPTY)?:" 495) 496CHECK_SAME_RE = re.compile(r"^\s*(?://|[;#])\s*([^:]+?)(?:-SAME)?:") 497 498UTC_ARGS_KEY = "UTC_ARGS:" 499UTC_ARGS_CMD = re.compile(r".*" + UTC_ARGS_KEY + r"\s*(?P<cmd>.*)\s*$") 500UTC_ADVERT = "NOTE: Assertions have been autogenerated by " 501UTC_AVOID = "NOTE: Do not autogenerate" 502UNUSED_NOTE = "NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:" 503 504OPT_FUNCTION_RE = re.compile( 505 r"^(\s*;\s*Function\sAttrs:\s(?P<attrs>[\w\s():,]+?))?\s*define\s+(?P<funcdef_attrs_and_ret>[^@]*)@(?P<func>[\w.$-]+?)\s*" 506 r"(?P<args_and_sig>\((\)|(.*?[\w.-]+?)\))[^{]*\{)\n(?P<body>.*?)^\}$", 507 flags=(re.M | re.S), 508) 509 510ANALYZE_FUNCTION_RE = re.compile( 511 r"^\s*\'(?P<analysis>[\w\s-]+?)\'\s+for\s+function\s+\'(?P<func>[\w.$-]+?)\':" 512 r"\s*\n(?P<body>.*)$", 513 flags=(re.X | re.S), 514) 515 516LV_DEBUG_RE = re.compile( 517 r"^\s*\'(?P<func>[\w.$-]+?)\'[^\n]*" r"\s*\n(?P<body>.*)$", flags=(re.X | re.S) 518) 519 520IR_FUNCTION_RE = re.compile(r'^\s*define\s+(?:internal\s+)?[^@]*@"?([\w.$-]+)"?\s*\(') 521TRIPLE_IR_RE = re.compile(r'^\s*target\s+triple\s*=\s*"([^"]+)"$') 522TRIPLE_ARG_RE = re.compile(r"-mtriple[= ]([^ ]+)") 523MARCH_ARG_RE = re.compile(r"-march[= ]([^ ]+)") 524DEBUG_ONLY_ARG_RE = re.compile(r"-debug-only[= ]([^ ]+)") 525 526SCRUB_LEADING_WHITESPACE_RE = re.compile(r"^(\s+)") 527SCRUB_WHITESPACE_RE = re.compile(r"(?!^(| \w))[ \t]+", flags=re.M) 528SCRUB_PRESERVE_LEADING_WHITESPACE_RE = re.compile(r"((?!^)[ \t]*(\S))[ \t]+") 529SCRUB_TRAILING_WHITESPACE_RE = re.compile(r"[ \t]+$", flags=re.M) 530SCRUB_TRAILING_WHITESPACE_TEST_RE = SCRUB_TRAILING_WHITESPACE_RE 531SCRUB_TRAILING_WHITESPACE_AND_ATTRIBUTES_RE = re.compile( 532 r"([ \t]|(#[0-9]+))+$", flags=re.M 533) 534SCRUB_KILL_COMMENT_RE = re.compile(r"^ *#+ +kill:.*\n") 535SCRUB_LOOP_COMMENT_RE = re.compile( 536 r"# =>This Inner Loop Header:.*|# in Loop:.*", flags=re.M 537) 538SCRUB_TAILING_COMMENT_TOKEN_RE = re.compile(r"(?<=\S)+[ \t]*#$", flags=re.M) 539 540SEPARATOR = "." 541 542 543def error(msg, test_file=None): 544 if test_file: 545 msg = "{}: {}".format(msg, test_file) 546 print("ERROR: {}".format(msg), file=sys.stderr) 547 548 549def warn(msg, test_file=None): 550 if test_file: 551 msg = "{}: {}".format(msg, test_file) 552 print("WARNING: {}".format(msg), file=sys.stderr) 553 554 555def debug(*args, **kwargs): 556 # Python2 does not allow def debug(*args, file=sys.stderr, **kwargs): 557 if "file" not in kwargs: 558 kwargs["file"] = sys.stderr 559 if _verbose: 560 print(*args, **kwargs) 561 562 563def find_run_lines(test, lines): 564 debug("Scanning for RUN lines in test file:", test) 565 raw_lines = [m.group(1) for m in [RUN_LINE_RE.match(l) for l in lines] if m] 566 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else [] 567 for l in raw_lines[1:]: 568 if run_lines[-1].endswith("\\"): 569 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l 570 else: 571 run_lines.append(l) 572 debug("Found {} RUN lines in {}:".format(len(run_lines), test)) 573 for l in run_lines: 574 debug(" RUN: {}".format(l)) 575 return run_lines 576 577 578def get_triple_from_march(march): 579 triples = { 580 "amdgcn": "amdgcn", 581 "r600": "r600", 582 "mips": "mips", 583 "sparc": "sparc", 584 "hexagon": "hexagon", 585 "ve": "ve", 586 } 587 for prefix, triple in triples.items(): 588 if march.startswith(prefix): 589 return triple 590 print("Cannot find a triple. Assume 'x86'", file=sys.stderr) 591 return "x86" 592 593 594def apply_filters(line, filters): 595 has_filter = False 596 for f in filters: 597 if not f.is_filter_out: 598 has_filter = True 599 if f.search(line): 600 return False if f.is_filter_out else True 601 # If we only used filter-out, keep the line, otherwise discard it since no 602 # filter matched. 603 return False if has_filter else True 604 605 606def do_filter(body, filters): 607 return ( 608 body 609 if not filters 610 else "\n".join( 611 filter(lambda line: apply_filters(line, filters), body.splitlines()) 612 ) 613 ) 614 615 616def scrub_body(body): 617 # Scrub runs of whitespace out of the assembly, but leave the leading 618 # whitespace in place. 619 body = SCRUB_PRESERVE_LEADING_WHITESPACE_RE.sub(lambda m: m.group(2) + " ", body) 620 621 # Expand the tabs used for indentation. 622 body = str.expandtabs(body, 2) 623 # Strip trailing whitespace. 624 body = SCRUB_TRAILING_WHITESPACE_TEST_RE.sub(r"", body) 625 return body 626 627 628def do_scrub(body, scrubber, scrubber_args, extra): 629 if scrubber_args: 630 local_args = copy.deepcopy(scrubber_args) 631 local_args[0].extra_scrub = extra 632 return scrubber(body, *local_args) 633 return scrubber(body, *scrubber_args) 634 635 636# Build up a dictionary of all the function bodies. 637class function_body(object): 638 def __init__( 639 self, 640 string, 641 extra, 642 funcdef_attrs_and_ret, 643 args_and_sig, 644 attrs, 645 func_name_separator, 646 ): 647 self.scrub = string 648 self.extrascrub = extra 649 self.funcdef_attrs_and_ret = funcdef_attrs_and_ret 650 self.args_and_sig = args_and_sig 651 self.attrs = attrs 652 self.func_name_separator = func_name_separator 653 654 def is_same_except_arg_names( 655 self, extrascrub, funcdef_attrs_and_ret, args_and_sig, attrs, is_backend 656 ): 657 arg_names = set() 658 659 def drop_arg_names(match): 660 arg_names.add(match.group(variable_group_in_ir_value_match)) 661 if match.group(attribute_group_in_ir_value_match): 662 attr = match.group(attribute_group_in_ir_value_match) 663 else: 664 attr = "" 665 return match.group(1) + attr + match.group(match.lastindex) 666 667 def repl_arg_names(match): 668 if ( 669 match.group(variable_group_in_ir_value_match) is not None 670 and match.group(variable_group_in_ir_value_match) in arg_names 671 ): 672 return match.group(1) + match.group(match.lastindex) 673 return match.group(1) + match.group(2) + match.group(match.lastindex) 674 675 if self.funcdef_attrs_and_ret != funcdef_attrs_and_ret: 676 return False 677 if self.attrs != attrs: 678 return False 679 ans0 = IR_VALUE_RE.sub(drop_arg_names, self.args_and_sig) 680 ans1 = IR_VALUE_RE.sub(drop_arg_names, args_and_sig) 681 if ans0 != ans1: 682 return False 683 if is_backend: 684 # Check without replacements, the replacements are not applied to the 685 # body for backend checks. 686 return self.extrascrub == extrascrub 687 688 es0 = IR_VALUE_RE.sub(repl_arg_names, self.extrascrub) 689 es1 = IR_VALUE_RE.sub(repl_arg_names, extrascrub) 690 es0 = SCRUB_IR_COMMENT_RE.sub(r"", es0) 691 es1 = SCRUB_IR_COMMENT_RE.sub(r"", es1) 692 return es0 == es1 693 694 def __str__(self): 695 return self.scrub 696 697 698class FunctionTestBuilder: 699 def __init__(self, run_list, flags, scrubber_args, path): 700 self._verbose = flags.verbose 701 self._record_args = flags.function_signature 702 self._check_attributes = flags.check_attributes 703 # Strip double-quotes if input was read by UTC_ARGS 704 self._filters = ( 705 list( 706 map( 707 lambda f: Filter( 708 re.compile(f.pattern().strip('"'), f.flags()), f.is_filter_out 709 ), 710 flags.filters, 711 ) 712 ) 713 if flags.filters 714 else [] 715 ) 716 self._scrubber_args = scrubber_args 717 self._path = path 718 # Strip double-quotes if input was read by UTC_ARGS 719 self._replace_value_regex = list( 720 map(lambda x: x.strip('"'), flags.replace_value_regex) 721 ) 722 self._func_dict = {} 723 self._func_order = {} 724 self._global_var_dict = {} 725 self._processed_prefixes = set() 726 for tuple in run_list: 727 for prefix in tuple[0]: 728 self._func_dict.update({prefix: dict()}) 729 self._func_order.update({prefix: []}) 730 self._global_var_dict.update({prefix: dict()}) 731 732 def finish_and_get_func_dict(self): 733 for prefix in self.get_failed_prefixes(): 734 warn( 735 "Prefix %s had conflicting output from different RUN lines for all functions in test %s" 736 % ( 737 prefix, 738 self._path, 739 ) 740 ) 741 return self._func_dict 742 743 def func_order(self): 744 return self._func_order 745 746 def global_var_dict(self): 747 return self._global_var_dict 748 749 def is_filtered(self): 750 return bool(self._filters) 751 752 def process_run_line( 753 self, function_re, scrubber, raw_tool_output, prefixes, is_backend 754 ): 755 build_global_values_dictionary(self._global_var_dict, raw_tool_output, prefixes) 756 for m in function_re.finditer(raw_tool_output): 757 if not m: 758 continue 759 func = m.group("func") 760 body = m.group("body") 761 # func_name_separator is the string that is placed right after function name at the 762 # beginning of assembly function definition. In most assemblies, that is just a 763 # colon: `foo:`. But, for example, in nvptx it is a brace: `foo(`. If is_backend is 764 # False, just assume that separator is an empty string. 765 if is_backend: 766 # Use ':' as default separator. 767 func_name_separator = ( 768 m.group("func_name_separator") 769 if "func_name_separator" in m.groupdict() 770 else ":" 771 ) 772 else: 773 func_name_separator = "" 774 attrs = m.group("attrs") if self._check_attributes else "" 775 funcdef_attrs_and_ret = ( 776 m.group("funcdef_attrs_and_ret") if self._record_args else "" 777 ) 778 # Determine if we print arguments, the opening brace, or nothing after the 779 # function name 780 if self._record_args and "args_and_sig" in m.groupdict(): 781 args_and_sig = scrub_body(m.group("args_and_sig").strip()) 782 elif "args_and_sig" in m.groupdict(): 783 args_and_sig = "(" 784 else: 785 args_and_sig = "" 786 filtered_body = do_filter(body, self._filters) 787 scrubbed_body = do_scrub( 788 filtered_body, scrubber, self._scrubber_args, extra=False 789 ) 790 scrubbed_extra = do_scrub( 791 filtered_body, scrubber, self._scrubber_args, extra=True 792 ) 793 if "analysis" in m.groupdict(): 794 analysis = m.group("analysis") 795 if analysis not in SUPPORTED_ANALYSES: 796 warn("Unsupported analysis mode: %r!" % (analysis,)) 797 if func.startswith("stress"): 798 # We only use the last line of the function body for stress tests. 799 scrubbed_body = "\n".join(scrubbed_body.splitlines()[-1:]) 800 if self._verbose: 801 print("Processing function: " + func, file=sys.stderr) 802 for l in scrubbed_body.splitlines(): 803 print(" " + l, file=sys.stderr) 804 for prefix in prefixes: 805 # Replace function names matching the regex. 806 for regex in self._replace_value_regex: 807 # Pattern that matches capture groups in the regex in leftmost order. 808 group_regex = re.compile(r"\(.*?\)") 809 # Replace function name with regex. 810 match = re.match(regex, func) 811 if match: 812 func_repl = regex 813 # Replace any capture groups with their matched strings. 814 for g in match.groups(): 815 func_repl = group_regex.sub( 816 re.escape(g), func_repl, count=1 817 ) 818 func = re.sub(func_repl, "{{" + func_repl + "}}", func) 819 820 # Replace all calls to regex matching functions. 821 matches = re.finditer(regex, scrubbed_body) 822 for match in matches: 823 func_repl = regex 824 # Replace any capture groups with their matched strings. 825 for g in match.groups(): 826 func_repl = group_regex.sub( 827 re.escape(g), func_repl, count=1 828 ) 829 # Substitute function call names that match the regex with the same 830 # capture groups set. 831 scrubbed_body = re.sub( 832 func_repl, "{{" + func_repl + "}}", scrubbed_body 833 ) 834 835 if func in self._func_dict[prefix]: 836 if self._func_dict[prefix][func] is not None and ( 837 str(self._func_dict[prefix][func]) != scrubbed_body 838 or self._func_dict[prefix][func].args_and_sig != args_and_sig 839 or self._func_dict[prefix][func].attrs != attrs 840 or self._func_dict[prefix][func].funcdef_attrs_and_ret 841 != funcdef_attrs_and_ret 842 ): 843 if self._func_dict[prefix][func].is_same_except_arg_names( 844 scrubbed_extra, 845 funcdef_attrs_and_ret, 846 args_and_sig, 847 attrs, 848 is_backend, 849 ): 850 self._func_dict[prefix][func].scrub = scrubbed_extra 851 self._func_dict[prefix][func].args_and_sig = args_and_sig 852 else: 853 # This means a previous RUN line produced a body for this function 854 # that is different from the one produced by this current RUN line, 855 # so the body can't be common across RUN lines. We use None to 856 # indicate that. 857 self._func_dict[prefix][func] = None 858 else: 859 if prefix not in self._processed_prefixes: 860 self._func_dict[prefix][func] = function_body( 861 scrubbed_body, 862 scrubbed_extra, 863 funcdef_attrs_and_ret, 864 args_and_sig, 865 attrs, 866 func_name_separator, 867 ) 868 self._func_order[prefix].append(func) 869 else: 870 # An earlier RUN line used this check prefixes but didn't produce 871 # a body for this function. This happens in Clang tests that use 872 # preprocesser directives to exclude individual functions from some 873 # RUN lines. 874 self._func_dict[prefix][func] = None 875 876 def processed_prefixes(self, prefixes): 877 """ 878 Mark a set of prefixes as having had at least one applicable RUN line fully 879 processed. This is used to filter out function bodies that don't have 880 outputs for all RUN lines. 881 """ 882 self._processed_prefixes.update(prefixes) 883 884 def get_failed_prefixes(self): 885 # This returns the list of those prefixes that failed to match any function, 886 # because there were conflicting bodies produced by different RUN lines, in 887 # all instances of the prefix. 888 for prefix in self._func_dict: 889 if self._func_dict[prefix] and ( 890 not [ 891 fct 892 for fct in self._func_dict[prefix] 893 if self._func_dict[prefix][fct] is not None 894 ] 895 ): 896 yield prefix 897 898 899##### Generator of LLVM IR CHECK lines 900 901SCRUB_IR_COMMENT_RE = re.compile(r"\s*;.*") 902 903# TODO: We should also derive check lines for global, debug, loop declarations, etc.. 904 905 906class NamelessValue: 907 def __init__( 908 self, 909 check_prefix, 910 check_key, 911 ir_prefix, 912 ir_regexp, 913 global_ir_rhs_regexp, 914 *, 915 is_before_functions=False, 916 is_number=False, 917 replace_number_with_counter=False, 918 match_literally=False, 919 interlaced_with_previous=False 920 ): 921 self.check_prefix = check_prefix 922 self.check_key = check_key 923 self.ir_prefix = ir_prefix 924 self.ir_regexp = ir_regexp 925 self.global_ir_rhs_regexp = global_ir_rhs_regexp 926 self.is_before_functions = is_before_functions 927 self.is_number = is_number 928 # Some variable numbers (e.g. MCINST1234) will change based on unrelated 929 # modifications to LLVM, replace those with an incrementing counter. 930 self.replace_number_with_counter = replace_number_with_counter 931 self.match_literally = match_literally 932 self.interlaced_with_previous = interlaced_with_previous 933 self.variable_mapping = {} 934 935 # Return true if this kind of IR value is "local", basically if it matches '%{{.*}}'. 936 def is_local_def_ir_value_match(self, match): 937 return self.ir_prefix == "%" 938 939 # Return true if this kind of IR value is "global", basically if it matches '#{{.*}}'. 940 def is_global_scope_ir_value_match(self, match): 941 return self.global_ir_rhs_regexp is not None 942 943 # Return the IR prefix and check prefix we use for this kind or IR value, 944 # e.g., (%, TMP) for locals. If the IR prefix is a regex, return the prefix 945 # used in the IR output 946 def get_ir_prefix_from_ir_value_match(self, match): 947 return re.search(self.ir_prefix, match[0])[0], self.check_prefix 948 949 # Return the IR regexp we use for this kind or IR value, e.g., [\w.-]+? for locals 950 def get_ir_regex_from_ir_value_re_match(self, match): 951 # for backwards compatibility we check locals with '.*' 952 if self.is_local_def_ir_value_match(match): 953 return ".*" 954 return self.ir_regexp 955 956 # Create a FileCheck variable name based on an IR name. 957 def get_value_name(self, var: str, check_prefix: str): 958 var = var.replace("!", "") 959 if self.replace_number_with_counter: 960 assert var 961 replacement = self.variable_mapping.get(var, None) 962 if replacement is None: 963 # Replace variable with an incrementing counter 964 replacement = str(len(self.variable_mapping) + 1) 965 self.variable_mapping[var] = replacement 966 var = replacement 967 # This is a nameless value, prepend check_prefix. 968 if var.isdigit(): 969 var = check_prefix + var 970 else: 971 # This is a named value that clashes with the check_prefix, prepend with 972 # _prefix_filecheck_ir_name, if it has been defined. 973 if ( 974 may_clash_with_default_check_prefix_name(check_prefix, var) 975 and _prefix_filecheck_ir_name 976 ): 977 var = _prefix_filecheck_ir_name + var 978 var = var.replace(".", "_") 979 var = var.replace("-", "_") 980 return var.upper() 981 982 # Create a FileCheck variable from regex. 983 def get_value_definition(self, var, match): 984 # for backwards compatibility we check locals with '.*' 985 varname = self.get_value_name(var, self.check_prefix) 986 prefix = self.get_ir_prefix_from_ir_value_match(match)[0] 987 if self.is_number: 988 regex = "" # always capture a number in the default format 989 capture_start = "[[#" 990 else: 991 regex = self.get_ir_regex_from_ir_value_re_match(match) 992 capture_start = "[[" 993 if self.is_local_def_ir_value_match(match): 994 return capture_start + varname + ":" + prefix + regex + "]]" 995 return prefix + capture_start + varname + ":" + regex + "]]" 996 997 # Use a FileCheck variable. 998 def get_value_use(self, var, match, var_prefix=None): 999 if var_prefix is None: 1000 var_prefix = self.check_prefix 1001 capture_start = "[[#" if self.is_number else "[[" 1002 if self.is_local_def_ir_value_match(match): 1003 return capture_start + self.get_value_name(var, var_prefix) + "]]" 1004 prefix = self.get_ir_prefix_from_ir_value_match(match)[0] 1005 return prefix + capture_start + self.get_value_name(var, var_prefix) + "]]" 1006 1007 1008# Description of the different "unnamed" values we match in the IR, e.g., 1009# (local) ssa values, (debug) metadata, etc. 1010ir_nameless_values = [ 1011 # check_prefix check_key ir_prefix ir_regexp global_ir_rhs_regexp 1012 NamelessValue(r"TMP", "%", r"%", r"[\w$.-]+?", None), 1013 NamelessValue(r"ATTR", "#", r"#", r"[0-9]+", None), 1014 NamelessValue(r"ATTR", "#", r"attributes #", r"[0-9]+", r"{[^}]*}"), 1015 NamelessValue(r"GLOB", "@", r"@", r"[0-9]+", None), 1016 NamelessValue(r"GLOB", "@", r"@", r"[0-9]+", r".+", is_before_functions=True), 1017 NamelessValue( 1018 r"GLOBNAMED", 1019 "@", 1020 r"@", 1021 r"[a-zA-Z0-9_$\"\\.-]*[a-zA-Z_$\"\\.-][a-zA-Z0-9_$\"\\.-]*", 1022 r".+", 1023 is_before_functions=True, 1024 match_literally=True, 1025 interlaced_with_previous=True, 1026 ), 1027 NamelessValue(r"DBG", "!", r"!dbg ", r"![0-9]+", None), 1028 NamelessValue(r"DIASSIGNID", "!", r"!DIAssignID ", r"![0-9]+", None), 1029 NamelessValue(r"PROF", "!", r"!prof ", r"![0-9]+", None), 1030 NamelessValue(r"TBAA", "!", r"!tbaa ", r"![0-9]+", None), 1031 NamelessValue(r"TBAA_STRUCT", "!", r"!tbaa.struct ", r"![0-9]+", None), 1032 NamelessValue(r"RNG", "!", r"!range ", r"![0-9]+", None), 1033 NamelessValue(r"LOOP", "!", r"!llvm.loop ", r"![0-9]+", None), 1034 NamelessValue(r"META", "!", r"metadata ", r"![0-9]+", None), 1035 NamelessValue(r"META", "!", r"", r"![0-9]+", r"(?:distinct |)!.*"), 1036 NamelessValue(r"ACC_GRP", "!", r"!llvm.access.group ", r"![0-9]+", None), 1037 NamelessValue(r"META", "!", r"![a-z.]+ ", r"![0-9]+", None), 1038] 1039 1040global_nameless_values = [ 1041 nameless_value 1042 for nameless_value in ir_nameless_values 1043 if nameless_value.global_ir_rhs_regexp is not None 1044] 1045# global variable names should be matched literally 1046global_nameless_values_w_unstable_ids = [ 1047 nameless_value 1048 for nameless_value in global_nameless_values 1049 if not nameless_value.match_literally 1050] 1051 1052asm_nameless_values = [ 1053 NamelessValue( 1054 r"MCINST", 1055 "Inst#", 1056 "<MCInst #", 1057 r"\d+", 1058 r".+", 1059 is_number=True, 1060 replace_number_with_counter=True, 1061 ), 1062 NamelessValue( 1063 r"MCREG", 1064 "Reg:", 1065 "<MCOperand Reg:", 1066 r"\d+", 1067 r".+", 1068 is_number=True, 1069 replace_number_with_counter=True, 1070 ), 1071] 1072 1073analyze_nameless_values = [ 1074 NamelessValue( 1075 r"GRP", 1076 "#", 1077 r"", 1078 r"0x[0-9a-f]+", 1079 None, 1080 replace_number_with_counter=True, 1081 ), 1082] 1083 1084 1085def createOrRegexp(old, new): 1086 if not old: 1087 return new 1088 if not new: 1089 return old 1090 return old + "|" + new 1091 1092 1093def createPrefixMatch(prefix_str, prefix_re): 1094 return "(?:" + prefix_str + "(" + prefix_re + "))" 1095 1096 1097# Build the regexp that matches an "IR value". This can be a local variable, 1098# argument, global, or metadata, anything that is "named". It is important that 1099# the PREFIX and SUFFIX below only contain a single group, if that changes 1100# other locations will need adjustment as well. 1101IR_VALUE_REGEXP_PREFIX = r"(\s*)" 1102IR_VALUE_REGEXP_STRING = r"" 1103for nameless_value in ir_nameless_values: 1104 match = createPrefixMatch(nameless_value.ir_prefix, nameless_value.ir_regexp) 1105 if nameless_value.global_ir_rhs_regexp is not None: 1106 match = "^" + match 1107 IR_VALUE_REGEXP_STRING = createOrRegexp(IR_VALUE_REGEXP_STRING, match) 1108IR_VALUE_REGEXP_SUFFIX = r"([,\s\(\)\}]|\Z)" 1109IR_VALUE_RE = re.compile( 1110 IR_VALUE_REGEXP_PREFIX 1111 + r"(" 1112 + IR_VALUE_REGEXP_STRING 1113 + r")" 1114 + IR_VALUE_REGEXP_SUFFIX 1115) 1116 1117GLOBAL_VALUE_REGEXP_STRING = r"" 1118for nameless_value in global_nameless_values_w_unstable_ids: 1119 match = createPrefixMatch(nameless_value.ir_prefix, nameless_value.ir_regexp) 1120 GLOBAL_VALUE_REGEXP_STRING = createOrRegexp(GLOBAL_VALUE_REGEXP_STRING, match) 1121GLOBAL_VALUE_RE = re.compile( 1122 IR_VALUE_REGEXP_PREFIX 1123 + r"(" 1124 + GLOBAL_VALUE_REGEXP_STRING 1125 + r")" 1126 + IR_VALUE_REGEXP_SUFFIX 1127) 1128 1129# Build the regexp that matches an "ASM value" (currently only for --asm-show-inst comments). 1130ASM_VALUE_REGEXP_STRING = "" 1131for nameless_value in asm_nameless_values: 1132 match = createPrefixMatch(nameless_value.ir_prefix, nameless_value.ir_regexp) 1133 ASM_VALUE_REGEXP_STRING = createOrRegexp(ASM_VALUE_REGEXP_STRING, match) 1134ASM_VALUE_REGEXP_SUFFIX = r"([>\s]|\Z)" 1135ASM_VALUE_RE = re.compile( 1136 r"((?:#|//)\s*)" + "(" + ASM_VALUE_REGEXP_STRING + ")" + ASM_VALUE_REGEXP_SUFFIX 1137) 1138 1139ANALYZE_VALUE_REGEXP_PREFIX = r"(\s*)" 1140ANALYZE_VALUE_REGEXP_STRING = r"" 1141for nameless_value in analyze_nameless_values: 1142 match = createPrefixMatch(nameless_value.ir_prefix, nameless_value.ir_regexp) 1143 ANALYZE_VALUE_REGEXP_STRING = createOrRegexp(ANALYZE_VALUE_REGEXP_STRING, match) 1144ANALYZE_VALUE_REGEXP_SUFFIX = r"(\)?:)" 1145ANALYZE_VALUE_RE = re.compile( 1146 ANALYZE_VALUE_REGEXP_PREFIX 1147 + r"(" 1148 + ANALYZE_VALUE_REGEXP_STRING 1149 + r")" 1150 + ANALYZE_VALUE_REGEXP_SUFFIX 1151) 1152 1153# The entire match is group 0, the prefix has one group (=1), the entire 1154# IR_VALUE_REGEXP_STRING is one group (=2), and then the nameless values start. 1155first_nameless_group_in_ir_value_match = 3 1156 1157# constants for the group id of special matches 1158variable_group_in_ir_value_match = 3 1159attribute_group_in_ir_value_match = 4 1160 1161 1162# Check a match for IR_VALUE_RE and inspect it to determine if it was a local 1163# value, %..., global @..., debug number !dbg !..., etc. See the PREFIXES above. 1164def get_idx_from_ir_value_match(match): 1165 for i in range(first_nameless_group_in_ir_value_match, match.lastindex): 1166 if match.group(i) is not None: 1167 return i - first_nameless_group_in_ir_value_match 1168 error("Unable to identify the kind of IR value from the match!") 1169 return 0 1170 1171 1172# See get_idx_from_ir_value_match 1173def get_name_from_ir_value_match(match): 1174 return match.group( 1175 get_idx_from_ir_value_match(match) + first_nameless_group_in_ir_value_match 1176 ) 1177 1178 1179def get_nameless_value_from_match(match, nameless_values) -> NamelessValue: 1180 return nameless_values[get_idx_from_ir_value_match(match)] 1181 1182 1183# Return true if var clashes with the scripted FileCheck check_prefix. 1184def may_clash_with_default_check_prefix_name(check_prefix, var): 1185 return check_prefix and re.match( 1186 r"^" + check_prefix + r"[0-9]+?$", var, re.IGNORECASE 1187 ) 1188 1189 1190def generalize_check_lines_common( 1191 lines, 1192 is_analyze, 1193 vars_seen, 1194 global_vars_seen, 1195 nameless_values, 1196 nameless_value_regex, 1197 is_asm, 1198 preserve_names, 1199): 1200 # This gets called for each match that occurs in 1201 # a line. We transform variables we haven't seen 1202 # into defs, and variables we have seen into uses. 1203 def transform_line_vars(match): 1204 var = get_name_from_ir_value_match(match) 1205 nameless_value = get_nameless_value_from_match(match, nameless_values) 1206 if may_clash_with_default_check_prefix_name(nameless_value.check_prefix, var): 1207 warn( 1208 "Change IR value name '%s' or use --prefix-filecheck-ir-name to prevent possible conflict" 1209 " with scripted FileCheck name." % (var,) 1210 ) 1211 key = (var, nameless_value.check_key) 1212 is_local_def = nameless_value.is_local_def_ir_value_match(match) 1213 if is_local_def and key in vars_seen: 1214 rv = nameless_value.get_value_use(var, match) 1215 elif not is_local_def and key in global_vars_seen: 1216 # We could have seen a different prefix for the global variables first, 1217 # ensure we use that one instead of the prefix for the current match. 1218 rv = nameless_value.get_value_use(var, match, global_vars_seen[key]) 1219 else: 1220 if is_local_def: 1221 vars_seen.add(key) 1222 else: 1223 global_vars_seen[key] = nameless_value.check_prefix 1224 rv = nameless_value.get_value_definition(var, match) 1225 # re.sub replaces the entire regex match 1226 # with whatever you return, so we have 1227 # to make sure to hand it back everything 1228 # including the commas and spaces. 1229 return match.group(1) + rv + match.group(match.lastindex) 1230 1231 lines_with_def = [] 1232 multiple_braces_re = re.compile(r"({{+)|(}}+)") 1233 def escape_braces(match_obj): 1234 return '{{' + re.escape(match_obj.group(0)) + '}}' 1235 1236 for i, line in enumerate(lines): 1237 if not is_asm and not is_analyze: 1238 # An IR variable named '%.' matches the FileCheck regex string. 1239 line = line.replace("%.", "%dot") 1240 for regex in _global_hex_value_regex: 1241 if re.match("^@" + regex + " = ", line): 1242 line = re.sub( 1243 r"\bi([0-9]+) ([0-9]+)", 1244 lambda m: "i" 1245 + m.group(1) 1246 + " [[#" 1247 + hex(int(m.group(2))) 1248 + "]]", 1249 line, 1250 ) 1251 break 1252 # Ignore any comments, since the check lines will too. 1253 scrubbed_line = SCRUB_IR_COMMENT_RE.sub(r"", line) 1254 lines[i] = scrubbed_line 1255 if not preserve_names: 1256 # It can happen that two matches are back-to-back and for some reason sub 1257 # will not replace both of them. For now we work around this by 1258 # substituting until there is no more match. 1259 changed = True 1260 while changed: 1261 (lines[i], changed) = nameless_value_regex.subn( 1262 transform_line_vars, lines[i], count=1 1263 ) 1264 if is_analyze: 1265 # Escape multiple {{ or }} as {{}} denotes a FileCheck regex. 1266 scrubbed_line = multiple_braces_re.sub(escape_braces, lines[i]) 1267 lines[i] = scrubbed_line 1268 return lines 1269 1270 1271# Replace IR value defs and uses with FileCheck variables. 1272def generalize_check_lines( 1273 lines, is_analyze, vars_seen, global_vars_seen, preserve_names 1274): 1275 return generalize_check_lines_common( 1276 lines, 1277 is_analyze, 1278 vars_seen, 1279 global_vars_seen, 1280 ir_nameless_values, 1281 IR_VALUE_RE, 1282 False, 1283 preserve_names, 1284 ) 1285 1286 1287def generalize_global_check_line(line, preserve_names, global_vars_seen): 1288 [new_line] = generalize_check_lines_common( 1289 [line], 1290 False, 1291 set(), 1292 global_vars_seen, 1293 global_nameless_values_w_unstable_ids, 1294 GLOBAL_VALUE_RE, 1295 False, 1296 preserve_names, 1297 ) 1298 return new_line 1299 1300 1301def generalize_asm_check_lines(lines, vars_seen, global_vars_seen): 1302 return generalize_check_lines_common( 1303 lines, 1304 False, 1305 vars_seen, 1306 global_vars_seen, 1307 asm_nameless_values, 1308 ASM_VALUE_RE, 1309 True, 1310 False, 1311 ) 1312 1313 1314def generalize_analyze_check_lines(lines, vars_seen, global_vars_seen): 1315 return generalize_check_lines_common( 1316 lines, 1317 True, 1318 vars_seen, 1319 global_vars_seen, 1320 analyze_nameless_values, 1321 ANALYZE_VALUE_RE, 1322 False, 1323 False, 1324 ) 1325 1326 1327def add_checks( 1328 output_lines, 1329 comment_marker, 1330 prefix_list, 1331 func_dict, 1332 func_name, 1333 check_label_format, 1334 is_backend, 1335 is_analyze, 1336 version, 1337 global_vars_seen_dict, 1338 is_filtered, 1339 preserve_names=False, 1340): 1341 # prefix_exclusions are prefixes we cannot use to print the function because it doesn't exist in run lines that use these prefixes as well. 1342 prefix_exclusions = set() 1343 printed_prefixes = [] 1344 for p in prefix_list: 1345 checkprefixes = p[0] 1346 # If not all checkprefixes of this run line produced the function we cannot check for it as it does not 1347 # exist for this run line. A subset of the check prefixes might know about the function but only because 1348 # other run lines created it. 1349 if any( 1350 map( 1351 lambda checkprefix: func_name not in func_dict[checkprefix], 1352 checkprefixes, 1353 ) 1354 ): 1355 prefix_exclusions |= set(checkprefixes) 1356 continue 1357 1358 # prefix_exclusions is constructed, we can now emit the output 1359 for p in prefix_list: 1360 global_vars_seen = {} 1361 checkprefixes = p[0] 1362 for checkprefix in checkprefixes: 1363 if checkprefix in global_vars_seen_dict: 1364 global_vars_seen.update(global_vars_seen_dict[checkprefix]) 1365 else: 1366 global_vars_seen_dict[checkprefix] = {} 1367 if checkprefix in printed_prefixes: 1368 break 1369 1370 # Check if the prefix is excluded. 1371 if checkprefix in prefix_exclusions: 1372 continue 1373 1374 # If we do not have output for this prefix we skip it. 1375 if not func_dict[checkprefix][func_name]: 1376 continue 1377 1378 # Add some space between different check prefixes, but not after the last 1379 # check line (before the test code). 1380 if is_backend: 1381 if len(printed_prefixes) != 0: 1382 output_lines.append(comment_marker) 1383 1384 if checkprefix not in global_vars_seen_dict: 1385 global_vars_seen_dict[checkprefix] = {} 1386 1387 global_vars_seen_before = [key for key in global_vars_seen.keys()] 1388 1389 vars_seen = set() 1390 printed_prefixes.append(checkprefix) 1391 attrs = str(func_dict[checkprefix][func_name].attrs) 1392 attrs = "" if attrs == "None" else attrs 1393 if version > 1: 1394 funcdef_attrs_and_ret = func_dict[checkprefix][ 1395 func_name 1396 ].funcdef_attrs_and_ret 1397 else: 1398 funcdef_attrs_and_ret = "" 1399 1400 if attrs: 1401 output_lines.append( 1402 "%s %s: Function Attrs: %s" % (comment_marker, checkprefix, attrs) 1403 ) 1404 args_and_sig = str(func_dict[checkprefix][func_name].args_and_sig) 1405 if args_and_sig: 1406 args_and_sig = generalize_check_lines( 1407 [args_and_sig], 1408 is_analyze, 1409 vars_seen, 1410 global_vars_seen, 1411 preserve_names, 1412 )[0] 1413 func_name_separator = func_dict[checkprefix][func_name].func_name_separator 1414 if "[[" in args_and_sig: 1415 # Captures in label lines are not supported, thus split into a -LABEL 1416 # and a separate -SAME line that contains the arguments with captures. 1417 args_and_sig_prefix = "" 1418 if version >= 3 and args_and_sig.startswith("("): 1419 # Ensure the "(" separating function name and arguments is in the 1420 # label line. This is required in case of function names that are 1421 # prefixes of each other. Otherwise, the label line for "foo" might 1422 # incorrectly match on "foo.specialized". 1423 args_and_sig_prefix = args_and_sig[0] 1424 args_and_sig = args_and_sig[1:] 1425 1426 # Removing args_and_sig from the label match line requires 1427 # func_name_separator to be empty. Otherwise, the match will not work. 1428 assert func_name_separator == "" 1429 output_lines.append( 1430 check_label_format 1431 % ( 1432 checkprefix, 1433 funcdef_attrs_and_ret, 1434 func_name, 1435 args_and_sig_prefix, 1436 func_name_separator, 1437 ) 1438 ) 1439 output_lines.append( 1440 "%s %s-SAME: %s" % (comment_marker, checkprefix, args_and_sig) 1441 ) 1442 else: 1443 output_lines.append( 1444 check_label_format 1445 % ( 1446 checkprefix, 1447 funcdef_attrs_and_ret, 1448 func_name, 1449 args_and_sig, 1450 func_name_separator, 1451 ) 1452 ) 1453 func_body = str(func_dict[checkprefix][func_name]).splitlines() 1454 if not func_body: 1455 # We have filtered everything. 1456 continue 1457 1458 # For ASM output, just emit the check lines. 1459 if is_backend: 1460 body_start = 1 1461 if is_filtered: 1462 # For filtered output we don't add "-NEXT" so don't add extra spaces 1463 # before the first line. 1464 body_start = 0 1465 else: 1466 output_lines.append( 1467 "%s %s: %s" % (comment_marker, checkprefix, func_body[0]) 1468 ) 1469 func_lines = generalize_asm_check_lines( 1470 func_body[body_start:], vars_seen, global_vars_seen 1471 ) 1472 for func_line in func_lines: 1473 if func_line.strip() == "": 1474 output_lines.append( 1475 "%s %s-EMPTY:" % (comment_marker, checkprefix) 1476 ) 1477 else: 1478 check_suffix = "-NEXT" if not is_filtered else "" 1479 output_lines.append( 1480 "%s %s%s: %s" 1481 % (comment_marker, checkprefix, check_suffix, func_line) 1482 ) 1483 # Remember new global variables we have not seen before 1484 for key in global_vars_seen: 1485 if key not in global_vars_seen_before: 1486 global_vars_seen_dict[checkprefix][key] = global_vars_seen[key] 1487 break 1488 # For analyze output, generalize the output, and emit CHECK-EMPTY lines as well. 1489 elif is_analyze: 1490 func_body = generalize_analyze_check_lines( 1491 func_body, vars_seen, global_vars_seen 1492 ) 1493 for func_line in func_body: 1494 if func_line.strip() == "": 1495 output_lines.append( 1496 "{} {}-EMPTY:".format(comment_marker, checkprefix) 1497 ) 1498 else: 1499 check_suffix = "-NEXT" if not is_filtered else "" 1500 output_lines.append( 1501 "{} {}{}: {}".format( 1502 comment_marker, checkprefix, check_suffix, func_line 1503 ) 1504 ) 1505 1506 # Add space between different check prefixes and also before the first 1507 # line of code in the test function. 1508 output_lines.append(comment_marker) 1509 1510 # Remember new global variables we have not seen before 1511 for key in global_vars_seen: 1512 if key not in global_vars_seen_before: 1513 global_vars_seen_dict[checkprefix][key] = global_vars_seen[key] 1514 break 1515 # For IR output, change all defs to FileCheck variables, so we're immune 1516 # to variable naming fashions. 1517 else: 1518 func_body = generalize_check_lines( 1519 func_body, False, vars_seen, global_vars_seen, preserve_names 1520 ) 1521 1522 # This could be selectively enabled with an optional invocation argument. 1523 # Disabled for now: better to check everything. Be safe rather than sorry. 1524 1525 # Handle the first line of the function body as a special case because 1526 # it's often just noise (a useless asm comment or entry label). 1527 # if func_body[0].startswith("#") or func_body[0].startswith("entry:"): 1528 # is_blank_line = True 1529 # else: 1530 # output_lines.append('%s %s: %s' % (comment_marker, checkprefix, func_body[0])) 1531 # is_blank_line = False 1532 1533 is_blank_line = False 1534 1535 for func_line in func_body: 1536 if func_line.strip() == "": 1537 is_blank_line = True 1538 continue 1539 # Do not waste time checking IR comments. 1540 func_line = SCRUB_IR_COMMENT_RE.sub(r"", func_line) 1541 1542 # Skip blank lines instead of checking them. 1543 if is_blank_line: 1544 output_lines.append( 1545 "{} {}: {}".format( 1546 comment_marker, checkprefix, func_line 1547 ) 1548 ) 1549 else: 1550 check_suffix = "-NEXT" if not is_filtered else "" 1551 output_lines.append( 1552 "{} {}{}: {}".format( 1553 comment_marker, checkprefix, check_suffix, func_line 1554 ) 1555 ) 1556 is_blank_line = False 1557 1558 # Add space between different check prefixes and also before the first 1559 # line of code in the test function. 1560 output_lines.append(comment_marker) 1561 1562 # Remember new global variables we have not seen before 1563 for key in global_vars_seen: 1564 if key not in global_vars_seen_before: 1565 global_vars_seen_dict[checkprefix][key] = global_vars_seen[key] 1566 break 1567 return printed_prefixes 1568 1569 1570def add_ir_checks( 1571 output_lines, 1572 comment_marker, 1573 prefix_list, 1574 func_dict, 1575 func_name, 1576 preserve_names, 1577 function_sig, 1578 version, 1579 global_vars_seen_dict, 1580 is_filtered, 1581): 1582 # Label format is based on IR string. 1583 if function_sig and version > 1: 1584 function_def_regex = "define %s" 1585 elif function_sig: 1586 function_def_regex = "define {{[^@]+}}%s" 1587 else: 1588 function_def_regex = "%s" 1589 check_label_format = "{} %s-LABEL: {}@%s%s%s".format( 1590 comment_marker, function_def_regex 1591 ) 1592 return add_checks( 1593 output_lines, 1594 comment_marker, 1595 prefix_list, 1596 func_dict, 1597 func_name, 1598 check_label_format, 1599 False, 1600 False, 1601 version, 1602 global_vars_seen_dict, 1603 is_filtered, 1604 preserve_names, 1605 ) 1606 1607 1608def add_analyze_checks( 1609 output_lines, comment_marker, prefix_list, func_dict, func_name, is_filtered 1610): 1611 check_label_format = "{} %s-LABEL: '%s%s%s%s'".format(comment_marker) 1612 global_vars_seen_dict = {} 1613 return add_checks( 1614 output_lines, 1615 comment_marker, 1616 prefix_list, 1617 func_dict, 1618 func_name, 1619 check_label_format, 1620 False, 1621 True, 1622 1, 1623 global_vars_seen_dict, 1624 is_filtered, 1625 ) 1626 1627 1628def build_global_values_dictionary(glob_val_dict, raw_tool_output, prefixes): 1629 for nameless_value in itertools.chain(global_nameless_values, asm_nameless_values): 1630 if nameless_value.global_ir_rhs_regexp is None: 1631 continue 1632 1633 lhs_re_str = nameless_value.ir_prefix + nameless_value.ir_regexp 1634 rhs_re_str = nameless_value.global_ir_rhs_regexp 1635 1636 global_ir_value_re_str = r"^" + lhs_re_str + r"\s=\s" + rhs_re_str + r"$" 1637 global_ir_value_re = re.compile(global_ir_value_re_str, flags=(re.M)) 1638 lines = [] 1639 for m in global_ir_value_re.finditer(raw_tool_output): 1640 # Attach the substring's start index so that CHECK lines 1641 # can be sorted properly even if they are matched by different nameless values. 1642 # This is relevant for GLOB and GLOBNAMED since they may appear interlaced. 1643 lines.append((m.start(), m.group(0))) 1644 1645 for prefix in prefixes: 1646 if glob_val_dict[prefix] is None: 1647 continue 1648 if nameless_value.check_prefix in glob_val_dict[prefix]: 1649 if lines == glob_val_dict[prefix][nameless_value.check_prefix]: 1650 continue 1651 if prefix == prefixes[-1]: 1652 warn("Found conflicting asm under the same prefix: %r!" % (prefix,)) 1653 else: 1654 glob_val_dict[prefix][nameless_value.check_prefix] = None 1655 continue 1656 glob_val_dict[prefix][nameless_value.check_prefix] = lines 1657 1658 1659def filter_globals_according_to_preference( 1660 global_val_lines_w_index, global_vars_seen, nameless_value, global_check_setting 1661): 1662 if global_check_setting == "none": 1663 return [] 1664 if global_check_setting == "all": 1665 return global_val_lines_w_index 1666 assert global_check_setting == "smart" 1667 1668 if nameless_value.check_key == "#": 1669 # attribute sets are usually better checked by --check-attributes 1670 return [] 1671 1672 def extract(line, nv): 1673 p = ( 1674 "^" 1675 + nv.ir_prefix 1676 + "(" 1677 + nv.ir_regexp 1678 + ") = (" 1679 + nv.global_ir_rhs_regexp 1680 + ")" 1681 ) 1682 match = re.match(p, line) 1683 return (match.group(1), re.findall(nv.ir_regexp, match.group(2))) 1684 1685 transitively_visible = set() 1686 contains_refs_to = {} 1687 1688 def add(var): 1689 nonlocal transitively_visible 1690 nonlocal contains_refs_to 1691 if var in transitively_visible: 1692 return 1693 transitively_visible.add(var) 1694 if not var in contains_refs_to: 1695 return 1696 for x in contains_refs_to[var]: 1697 add(x) 1698 1699 for i, line in global_val_lines_w_index: 1700 (var, refs) = extract(line, nameless_value) 1701 contains_refs_to[var] = refs 1702 for var, check_key in global_vars_seen: 1703 if check_key != nameless_value.check_key: 1704 continue 1705 add(var) 1706 return [ 1707 (i, line) 1708 for i, line in global_val_lines_w_index 1709 if extract(line, nameless_value)[0] in transitively_visible 1710 ] 1711 1712 1713METADATA_FILTERS = [ 1714 ( 1715 r"(?<=\")(.+ )?(\w+ version )[\d.]+(?:[^\" ]*)(?: \([^)]+\))?", 1716 r"{{.*}}\2{{.*}}", 1717 ), # preface with glob also, to capture optional CLANG_VENDOR 1718 (r'(!DIFile\(filename: ".+", directory: )".+"', r"\1{{.*}}"), 1719] 1720METADATA_FILTERS_RE = [(re.compile(f), r) for (f, r) in METADATA_FILTERS] 1721 1722 1723def filter_unstable_metadata(line): 1724 for f, replacement in METADATA_FILTERS_RE: 1725 line = f.sub(replacement, line) 1726 return line 1727 1728 1729def flush_current_checks(output_lines, new_lines_w_index, comment_marker): 1730 if not new_lines_w_index: 1731 return 1732 output_lines.append(comment_marker + SEPARATOR) 1733 new_lines_w_index.sort() 1734 for _, line in new_lines_w_index: 1735 output_lines.append(line) 1736 new_lines_w_index.clear() 1737 1738 1739def add_global_checks( 1740 glob_val_dict, 1741 comment_marker, 1742 prefix_list, 1743 output_lines, 1744 global_vars_seen_dict, 1745 preserve_names, 1746 is_before_functions, 1747 global_check_setting, 1748): 1749 printed_prefixes = set() 1750 output_lines_loc = {} # Allows GLOB and GLOBNAMED to be sorted correctly 1751 for nameless_value in global_nameless_values: 1752 if nameless_value.is_before_functions != is_before_functions: 1753 continue 1754 for p in prefix_list: 1755 global_vars_seen = {} 1756 checkprefixes = p[0] 1757 if checkprefixes is None: 1758 continue 1759 for checkprefix in checkprefixes: 1760 if checkprefix in global_vars_seen_dict: 1761 global_vars_seen.update(global_vars_seen_dict[checkprefix]) 1762 else: 1763 global_vars_seen_dict[checkprefix] = {} 1764 if (checkprefix, nameless_value.check_prefix) in printed_prefixes: 1765 break 1766 if not glob_val_dict[checkprefix]: 1767 continue 1768 if nameless_value.check_prefix not in glob_val_dict[checkprefix]: 1769 continue 1770 if not glob_val_dict[checkprefix][nameless_value.check_prefix]: 1771 continue 1772 1773 check_lines = [] 1774 global_vars_seen_before = [key for key in global_vars_seen.keys()] 1775 lines_w_index = glob_val_dict[checkprefix][nameless_value.check_prefix] 1776 lines_w_index = filter_globals_according_to_preference( 1777 lines_w_index, 1778 global_vars_seen_before, 1779 nameless_value, 1780 global_check_setting, 1781 ) 1782 for i, line in lines_w_index: 1783 if _global_value_regex: 1784 matched = False 1785 for regex in _global_value_regex: 1786 if re.match("^@" + regex + " = ", line) or re.match( 1787 "^!" + regex + " = ", line 1788 ): 1789 matched = True 1790 break 1791 if not matched: 1792 continue 1793 new_line = generalize_global_check_line( 1794 line, preserve_names, global_vars_seen 1795 ) 1796 new_line = filter_unstable_metadata(new_line) 1797 check_line = "%s %s: %s" % (comment_marker, checkprefix, new_line) 1798 check_lines.append((i, check_line)) 1799 if not check_lines: 1800 continue 1801 1802 if not checkprefix in output_lines_loc: 1803 output_lines_loc[checkprefix] = [] 1804 if not nameless_value.interlaced_with_previous: 1805 flush_current_checks( 1806 output_lines, output_lines_loc[checkprefix], comment_marker 1807 ) 1808 for check_line in check_lines: 1809 output_lines_loc[checkprefix].append(check_line) 1810 1811 printed_prefixes.add((checkprefix, nameless_value.check_prefix)) 1812 1813 # Remembe new global variables we have not seen before 1814 for key in global_vars_seen: 1815 if key not in global_vars_seen_before: 1816 global_vars_seen_dict[checkprefix][key] = global_vars_seen[key] 1817 break 1818 1819 if printed_prefixes: 1820 for p in prefix_list: 1821 if p[0] is None: 1822 continue 1823 for checkprefix in p[0]: 1824 if checkprefix not in output_lines_loc: 1825 continue 1826 flush_current_checks( 1827 output_lines, output_lines_loc[checkprefix], comment_marker 1828 ) 1829 break 1830 output_lines.append(comment_marker + SEPARATOR) 1831 return printed_prefixes 1832 1833 1834def check_prefix(prefix): 1835 if not PREFIX_RE.match(prefix): 1836 hint = "" 1837 if "," in prefix: 1838 hint = " Did you mean '--check-prefixes=" + prefix + "'?" 1839 warn( 1840 ( 1841 "Supplied prefix '%s' is invalid. Prefix must contain only alphanumeric characters, hyphens and underscores." 1842 + hint 1843 ) 1844 % (prefix) 1845 ) 1846 1847 1848def get_check_prefixes(filecheck_cmd): 1849 check_prefixes = [ 1850 item 1851 for m in CHECK_PREFIX_RE.finditer(filecheck_cmd) 1852 for item in m.group(1).split(",") 1853 ] 1854 if not check_prefixes: 1855 check_prefixes = ["CHECK"] 1856 return check_prefixes 1857 1858 1859def verify_filecheck_prefixes(fc_cmd): 1860 fc_cmd_parts = fc_cmd.split() 1861 for part in fc_cmd_parts: 1862 if "check-prefix=" in part: 1863 prefix = part.split("=", 1)[1] 1864 check_prefix(prefix) 1865 elif "check-prefixes=" in part: 1866 prefixes = part.split("=", 1)[1].split(",") 1867 for prefix in prefixes: 1868 check_prefix(prefix) 1869 if prefixes.count(prefix) > 1: 1870 warn( 1871 "Supplied prefix '%s' is not unique in the prefix list." 1872 % (prefix,) 1873 ) 1874 1875 1876def get_autogennote_suffix(parser, args): 1877 autogenerated_note_args = "" 1878 for action in parser._actions: 1879 if not hasattr(args, action.dest): 1880 continue # Ignore options such as --help that aren't included in args 1881 # Ignore parameters such as paths to the binary or the list of tests 1882 if action.dest in ( 1883 "tests", 1884 "update_only", 1885 "tool_binary", 1886 "opt_binary", 1887 "llc_binary", 1888 "clang", 1889 "opt", 1890 "llvm_bin", 1891 "verbose", 1892 "force_update", 1893 ): 1894 continue 1895 value = getattr(args, action.dest) 1896 if action.dest == "check_globals": 1897 default_value = "none" if args.version < 4 else "smart" 1898 if value == default_value: 1899 continue 1900 autogenerated_note_args += action.option_strings[0] + " " 1901 if args.version < 4 and value == "all": 1902 continue 1903 autogenerated_note_args += "%s " % value 1904 continue 1905 if action.const is not None: # action stores a constant (usually True/False) 1906 # Skip actions with different constant values (this happens with boolean 1907 # --foo/--no-foo options) 1908 if value != action.const: 1909 continue 1910 if parser.get_default(action.dest) == value: 1911 continue # Don't add default values 1912 if action.dest == "function_signature" and args.version >= 2: 1913 continue # Enabled by default in version 2 1914 if action.dest == "filters": 1915 # Create a separate option for each filter element. The value is a list 1916 # of Filter objects. 1917 for elem in value: 1918 opt_name = "filter-out" if elem.is_filter_out else "filter" 1919 opt_value = elem.pattern() 1920 new_arg = '--%s "%s" ' % (opt_name, opt_value.strip('"')) 1921 if new_arg not in autogenerated_note_args: 1922 autogenerated_note_args += new_arg 1923 else: 1924 autogenerated_note_args += action.option_strings[0] + " " 1925 if action.const is None: # action takes a parameter 1926 if action.nargs == "+": 1927 value = " ".join(map(lambda v: '"' + v.strip('"') + '"', value)) 1928 autogenerated_note_args += "%s " % value 1929 if autogenerated_note_args: 1930 autogenerated_note_args = " %s %s" % ( 1931 UTC_ARGS_KEY, 1932 autogenerated_note_args[:-1], 1933 ) 1934 return autogenerated_note_args 1935 1936 1937def check_for_command(line, parser, args, argv, argparse_callback): 1938 cmd_m = UTC_ARGS_CMD.match(line) 1939 if cmd_m: 1940 for option in shlex.split(cmd_m.group("cmd").strip()): 1941 if option: 1942 argv.append(option) 1943 args = parse_args(parser, filter(lambda arg: arg not in args.tests, argv)) 1944 if argparse_callback is not None: 1945 argparse_callback(args) 1946 return args, argv 1947 1948 1949def find_arg_in_test(test_info, get_arg_to_check, arg_string, is_global): 1950 result = get_arg_to_check(test_info.args) 1951 if not result and is_global: 1952 # See if this has been specified via UTC_ARGS. This is a "global" option 1953 # that affects the entire generation of test checks. If it exists anywhere 1954 # in the test, apply it to everything. 1955 saw_line = False 1956 for line_info in test_info.ro_iterlines(): 1957 line = line_info.line 1958 if not line.startswith(";") and line.strip() != "": 1959 saw_line = True 1960 result = get_arg_to_check(line_info.args) 1961 if result: 1962 if warn and saw_line: 1963 # We saw the option after already reading some test input lines. 1964 # Warn about it. 1965 print( 1966 "WARNING: Found {} in line following test start: ".format( 1967 arg_string 1968 ) 1969 + line, 1970 file=sys.stderr, 1971 ) 1972 print( 1973 "WARNING: Consider moving {} to top of file".format(arg_string), 1974 file=sys.stderr, 1975 ) 1976 break 1977 return result 1978 1979 1980def dump_input_lines(output_lines, test_info, prefix_set, comment_string): 1981 for input_line_info in test_info.iterlines(output_lines): 1982 line = input_line_info.line 1983 args = input_line_info.args 1984 if line.strip() == comment_string: 1985 continue 1986 if line.strip() == comment_string + SEPARATOR: 1987 continue 1988 if line.lstrip().startswith(comment_string): 1989 m = CHECK_RE.match(line) 1990 if m and m.group(1) in prefix_set: 1991 continue 1992 output_lines.append(line.rstrip("\n")) 1993 1994 1995def add_checks_at_end( 1996 output_lines, prefix_list, func_order, comment_string, check_generator 1997): 1998 added = set() 1999 generated_prefixes = set() 2000 for prefix in prefix_list: 2001 prefixes = prefix[0] 2002 tool_args = prefix[1] 2003 for prefix in prefixes: 2004 for func in func_order[prefix]: 2005 # The func order can contain the same functions multiple times. 2006 # If we see one again we are done. 2007 if (func, prefix) in added: 2008 continue 2009 if added: 2010 output_lines.append(comment_string) 2011 2012 # The add_*_checks routines expect a run list whose items are 2013 # tuples that have a list of prefixes as their first element and 2014 # tool command args string as their second element. They output 2015 # checks for each prefix in the list of prefixes. By doing so, it 2016 # implicitly assumes that for each function every run line will 2017 # generate something for that function. That is not the case for 2018 # generated functions as some run lines might not generate them 2019 # (e.g. -fopenmp vs. no -fopenmp). 2020 # 2021 # Therefore, pass just the prefix we're interested in. This has 2022 # the effect of generating all of the checks for functions of a 2023 # single prefix before moving on to the next prefix. So checks 2024 # are ordered by prefix instead of by function as in "normal" 2025 # mode. 2026 for generated_prefix in check_generator( 2027 output_lines, [([prefix], tool_args)], func 2028 ): 2029 added.add((func, generated_prefix)) 2030 generated_prefixes.add(generated_prefix) 2031 return generated_prefixes 2032