|  | #!/usr/bin/env python3 | 
|  |  | 
|  | """A script to generate FileCheck statements for 'opt' regression tests. | 
|  |  | 
|  | This script is a utility to update LLVM opt test cases with new | 
|  | FileCheck patterns. It can either update all of the tests in the file or | 
|  | a single test function. | 
|  |  | 
|  | Example usage: | 
|  |  | 
|  | # Default to using `opt` as found in your PATH. | 
|  | $ update_test_checks.py test/foo.ll | 
|  |  | 
|  | # Override the path lookup. | 
|  | $ update_test_checks.py --tool-binary=../bin/opt test/foo.ll | 
|  |  | 
|  | # Use a custom tool instead of `opt`. | 
|  | $ update_test_checks.py --tool=yourtool test/foo.ll | 
|  |  | 
|  | Workflow: | 
|  | 1. Make a compiler patch that requires updating some number of FileCheck lines | 
|  | in regression test files. | 
|  | 2. Save the patch and revert it from your local work area. | 
|  | 3. Update the RUN-lines in the affected regression tests to look canonical. | 
|  | Example: "; RUN: opt < %s -instcombine -S | FileCheck %s" | 
|  | 4. Refresh the FileCheck lines for either the entire file or select functions by | 
|  | running this script. | 
|  | 5. Commit the fresh baseline of checks. | 
|  | 6. Apply your patch from step 1 and rebuild your local binaries. | 
|  | 7. Re-run this script on affected regression tests. | 
|  | 8. Check the diffs to ensure the script has done something reasonable. | 
|  | 9. Submit a patch including the regression test diffs for review. | 
|  | """ | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import argparse | 
|  | import os  # Used to advertise this file's name ("autogenerated_note"). | 
|  | import re | 
|  | import sys | 
|  |  | 
|  | from UpdateTestChecks import common | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | from argparse import RawTextHelpFormatter | 
|  | parser = argparse.ArgumentParser(description=__doc__, formatter_class=RawTextHelpFormatter) | 
|  | parser.add_argument('--tool', default='opt', | 
|  | help='The name of the tool used to generate the test case (defaults to "opt")') | 
|  | parser.add_argument('--tool-binary', '--opt-binary', | 
|  | help='The tool binary used to generate the test case') | 
|  | parser.add_argument( | 
|  | '--function', help='The function in the test file to update') | 
|  | parser.add_argument('-p', '--preserve-names', action='store_true', | 
|  | help='Do not scrub IR names') | 
|  | parser.add_argument('--function-signature', action='store_true', | 
|  | help='Keep function signature information around for the check line') | 
|  | parser.add_argument('--scrub-attributes', action='store_true', | 
|  | help='Remove attribute annotations (#0) from the end of check line') | 
|  | parser.add_argument('--check-attributes', action='store_true', | 
|  | help='Check "Function Attributes" for functions') | 
|  | parser.add_argument('--check-globals', action='store_true', | 
|  | help='Check global entries (global variables, metadata, attribute sets, ...) for functions') | 
|  | parser.add_argument('tests', nargs='+') | 
|  | initial_args = common.parse_commandline_args(parser) | 
|  |  | 
|  | script_name = os.path.basename(__file__) | 
|  |  | 
|  | if initial_args.tool_binary: | 
|  | tool_basename = os.path.basename(initial_args.tool_binary) | 
|  | if not re.match(r'^%s(-\d+)?(\.exe)?$' % (initial_args.tool), tool_basename): | 
|  | common.error('Unexpected tool name: ' + tool_basename) | 
|  | sys.exit(1) | 
|  |  | 
|  | for ti in common.itertests(initial_args.tests, parser, | 
|  | script_name='utils/' + script_name): | 
|  | # If requested we scrub trailing attribute annotations, e.g., '#0', together with whitespaces | 
|  | if ti.args.scrub_attributes: | 
|  | common.SCRUB_TRAILING_WHITESPACE_TEST_RE = common.SCRUB_TRAILING_WHITESPACE_AND_ATTRIBUTES_RE | 
|  | else: | 
|  | common.SCRUB_TRAILING_WHITESPACE_TEST_RE = common.SCRUB_TRAILING_WHITESPACE_RE | 
|  |  | 
|  | tool_basename = ti.args.tool | 
|  |  | 
|  | prefix_list = [] | 
|  | for l in ti.run_lines: | 
|  | if '|' not in l: | 
|  | common.warn('Skipping unparsable RUN line: ' + l) | 
|  | continue | 
|  |  | 
|  | commands = [cmd.strip() for cmd in l.split('|')] | 
|  | assert len(commands) >= 2 | 
|  | preprocess_cmd = None | 
|  | if len(commands) > 2: | 
|  | preprocess_cmd = " | ".join(commands[:-2]) | 
|  | tool_cmd = commands[-2] | 
|  | filecheck_cmd = commands[-1] | 
|  | common.verify_filecheck_prefixes(filecheck_cmd) | 
|  | if not tool_cmd.startswith(tool_basename + ' '): | 
|  | common.warn('Skipping non-%s RUN line: %s' % (tool_basename, l)) | 
|  | continue | 
|  |  | 
|  | if not filecheck_cmd.startswith('FileCheck '): | 
|  | common.warn('Skipping non-FileChecked RUN line: ' + l) | 
|  | continue | 
|  |  | 
|  | tool_cmd_args = tool_cmd[len(tool_basename):].strip() | 
|  | tool_cmd_args = tool_cmd_args.replace('< %s', '').replace('%s', '').strip() | 
|  | check_prefixes = common.get_check_prefixes(filecheck_cmd) | 
|  |  | 
|  | # FIXME: We should use multiple check prefixes to common check lines. For | 
|  | # now, we just ignore all but the last. | 
|  | prefix_list.append((check_prefixes, tool_cmd_args, preprocess_cmd)) | 
|  |  | 
|  | global_vars_seen_dict = {} | 
|  | builder = common.FunctionTestBuilder( | 
|  | run_list=prefix_list, | 
|  | flags=ti.args, | 
|  | scrubber_args=[], | 
|  | path=ti.path) | 
|  |  | 
|  | tool_binary = ti.args.tool_binary | 
|  | if not tool_binary: | 
|  | tool_binary = tool_basename | 
|  |  | 
|  | for prefixes, tool_args, preprocess_cmd in prefix_list: | 
|  | common.debug('Extracted tool cmd: ' + tool_basename + ' ' + tool_args) | 
|  | common.debug('Extracted FileCheck prefixes: ' + str(prefixes)) | 
|  |  | 
|  | raw_tool_output = common.invoke_tool(tool_binary, tool_args, | 
|  | ti.path, preprocess_cmd=preprocess_cmd, | 
|  | verbose=ti.args.verbose) | 
|  | builder.process_run_line(common.OPT_FUNCTION_RE, common.scrub_body, | 
|  | raw_tool_output, prefixes, False) | 
|  | builder.processed_prefixes(prefixes) | 
|  |  | 
|  | func_dict = builder.finish_and_get_func_dict() | 
|  | is_in_function = False | 
|  | is_in_function_start = False | 
|  | has_checked_pre_function_globals = False | 
|  | prefix_set = set([prefix for prefixes, _, _ in prefix_list for prefix in prefixes]) | 
|  | common.debug('Rewriting FileCheck prefixes:', str(prefix_set)) | 
|  | output_lines = [] | 
|  |  | 
|  | include_generated_funcs = common.find_arg_in_test(ti, | 
|  | lambda args: ti.args.include_generated_funcs, | 
|  | '--include-generated-funcs', | 
|  | True) | 
|  | generated_prefixes = [] | 
|  | if include_generated_funcs: | 
|  | # Generate the appropriate checks for each function.  We need to emit | 
|  | # these in the order according to the generated output so that CHECK-LABEL | 
|  | # works properly.  func_order provides that. | 
|  |  | 
|  | # We can't predict where various passes might insert functions so we can't | 
|  | # be sure the input function order is maintained.  Therefore, first spit | 
|  | # out all the source lines. | 
|  | common.dump_input_lines(output_lines, ti, prefix_set, ';') | 
|  |  | 
|  | args = ti.args | 
|  | if args.check_globals: | 
|  | generated_prefixes.extend( | 
|  | common.add_global_checks(builder.global_var_dict(), ';', | 
|  | prefix_list, output_lines, | 
|  | global_vars_seen_dict, args.preserve_names, | 
|  | True)) | 
|  |  | 
|  | # Now generate all the checks. | 
|  | generated_prefixes.extend( | 
|  | common.add_checks_at_end( | 
|  | output_lines, prefix_list, builder.func_order(), ';', | 
|  | lambda my_output_lines, prefixes, func: common.add_ir_checks( | 
|  | my_output_lines, | 
|  | ';', | 
|  | prefixes, | 
|  | func_dict, | 
|  | func, | 
|  | False, | 
|  | args.function_signature, | 
|  | args.version, | 
|  | global_vars_seen_dict, | 
|  | is_filtered=builder.is_filtered()))) | 
|  | else: | 
|  | # "Normal" mode. | 
|  | for input_line_info in ti.iterlines(output_lines): | 
|  | input_line = input_line_info.line | 
|  | args = input_line_info.args | 
|  | if is_in_function_start: | 
|  | if input_line == '': | 
|  | continue | 
|  | if input_line.lstrip().startswith(';'): | 
|  | m = common.CHECK_RE.match(input_line) | 
|  | if not m or m.group(1) not in prefix_set: | 
|  | output_lines.append(input_line) | 
|  | continue | 
|  |  | 
|  | # Print out the various check lines here. | 
|  | generated_prefixes.extend( | 
|  | common.add_ir_checks( | 
|  | output_lines, | 
|  | ';', | 
|  | prefix_list, | 
|  | func_dict, | 
|  | func_name, | 
|  | args.preserve_names, | 
|  | args.function_signature, | 
|  | args.version, | 
|  | global_vars_seen_dict, | 
|  | is_filtered=builder.is_filtered())) | 
|  | is_in_function_start = False | 
|  |  | 
|  | m = common.IR_FUNCTION_RE.match(input_line) | 
|  | if m and not has_checked_pre_function_globals: | 
|  | if args.check_globals: | 
|  | generated_prefixes.extend( | 
|  | common.add_global_checks(builder.global_var_dict(), ';', | 
|  | prefix_list, output_lines, | 
|  | global_vars_seen_dict, | 
|  | args.preserve_names, True)) | 
|  | has_checked_pre_function_globals = True | 
|  |  | 
|  | if common.should_add_line_to_output(input_line, prefix_set, not is_in_function): | 
|  | # This input line of the function body will go as-is into the output. | 
|  | # Except make leading whitespace uniform: 2 spaces. | 
|  | input_line = common.SCRUB_LEADING_WHITESPACE_RE.sub(r'  ', input_line) | 
|  | output_lines.append(input_line) | 
|  | if input_line.strip() == '}': | 
|  | is_in_function = False | 
|  | continue | 
|  |  | 
|  | if is_in_function: | 
|  | continue | 
|  |  | 
|  | m = common.IR_FUNCTION_RE.match(input_line) | 
|  | if not m: | 
|  | continue | 
|  | func_name = m.group(1) | 
|  | if args.function is not None and func_name != args.function: | 
|  | # When filtering on a specific function, skip all others. | 
|  | continue | 
|  | is_in_function = is_in_function_start = True | 
|  |  | 
|  | if args.check_globals: | 
|  | generated_prefixes.extend( | 
|  | common.add_global_checks(builder.global_var_dict(), ';', prefix_list, | 
|  | output_lines, global_vars_seen_dict, | 
|  | args.preserve_names, False)) | 
|  | if ti.args.gen_unused_prefix_body: | 
|  | output_lines.extend(ti.get_checks_for_unused_prefixes( | 
|  | prefix_list, generated_prefixes)) | 
|  | common.debug('Writing %d lines to %s...' % (len(output_lines), ti.path)) | 
|  |  | 
|  | with open(ti.path, 'wb') as f: | 
|  | f.writelines(['{}\n'.format(l).encode('utf-8') for l in output_lines]) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |