Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python2.7 |
| 2 | |
| 3 | """A test case update script. |
| 4 | |
| 5 | This script is a utility to update LLVM X86 'llc' based test cases with new |
| 6 | FileCheck patterns. It can either update all of the tests in the file or |
| 7 | a single test function. |
| 8 | """ |
| 9 | |
| 10 | import argparse |
| 11 | import itertools |
| 12 | import string |
| 13 | import subprocess |
| 14 | import sys |
| 15 | import tempfile |
| 16 | import re |
| 17 | |
| 18 | |
| 19 | def llc(args, cmd_args, ir): |
| 20 | with open(ir) as ir_file: |
| 21 | stdout = subprocess.check_output(args.llc_binary + ' ' + cmd_args, |
| 22 | shell=True, stdin=ir_file) |
Simon Pilgrim | 6b6dcc4 | 2016-01-27 21:13:18 +0000 | [diff] [blame] | 23 | # Fix line endings to unix CR style. |
| 24 | stdout = stdout.replace('\r\n', '\n') |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 25 | return stdout |
| 26 | |
| 27 | |
| 28 | ASM_SCRUB_WHITESPACE_RE = re.compile(r'(?!^(| \w))[ \t]+', flags=re.M) |
Chandler Carruth | e375095 | 2015-02-04 10:46:48 +0000 | [diff] [blame] | 29 | ASM_SCRUB_TRAILING_WHITESPACE_RE = re.compile(r'[ \t]+$', flags=re.M) |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 30 | ASM_SCRUB_SHUFFLES_RE = ( |
| 31 | re.compile( |
| 32 | r'^(\s*\w+) [^#\n]+#+ ((?:[xyz]mm\d+|mem) = .*)$', |
| 33 | flags=re.M)) |
| 34 | ASM_SCRUB_SP_RE = re.compile(r'\d+\(%(esp|rsp)\)') |
| 35 | ASM_SCRUB_RIP_RE = re.compile(r'[.\w]+\(%rip\)') |
| 36 | ASM_SCRUB_KILL_COMMENT_RE = re.compile(r'^ *#+ +kill:.*\n') |
| 37 | |
| 38 | |
| 39 | def scrub_asm(asm): |
| 40 | # Scrub runs of whitespace out of the assembly, but leave the leading |
| 41 | # whitespace in place. |
| 42 | asm = ASM_SCRUB_WHITESPACE_RE.sub(r' ', asm) |
| 43 | # Expand the tabs used for indentation. |
| 44 | asm = string.expandtabs(asm, 2) |
| 45 | # Detect shuffle asm comments and hide the operands in favor of the comments. |
| 46 | asm = ASM_SCRUB_SHUFFLES_RE.sub(r'\1 {{.*#+}} \2', asm) |
| 47 | # Generically match the stack offset of a memory operand. |
| 48 | asm = ASM_SCRUB_SP_RE.sub(r'{{[0-9]+}}(%\1)', asm) |
| 49 | # Generically match a RIP-relative memory operand. |
| 50 | asm = ASM_SCRUB_RIP_RE.sub(r'{{.*}}(%rip)', asm) |
| 51 | # Strip kill operands inserted into the asm. |
| 52 | asm = ASM_SCRUB_KILL_COMMENT_RE.sub('', asm) |
Chandler Carruth | e375095 | 2015-02-04 10:46:48 +0000 | [diff] [blame] | 53 | # Strip trailing whitespace. |
| 54 | asm = ASM_SCRUB_TRAILING_WHITESPACE_RE.sub(r'', asm) |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 55 | return asm |
| 56 | |
| 57 | |
| 58 | def main(): |
| 59 | parser = argparse.ArgumentParser(description=__doc__) |
| 60 | parser.add_argument('-v', '--verbose', action='store_true', |
| 61 | help='Show verbose output') |
| 62 | parser.add_argument('--llc-binary', default='llc', |
| 63 | help='The "llc" binary to use to generate the test case') |
| 64 | parser.add_argument( |
| 65 | '--function', help='The function in the test file to update') |
| 66 | parser.add_argument('tests', nargs='+') |
| 67 | args = parser.parse_args() |
| 68 | |
| 69 | run_line_re = re.compile('^\s*;\s*RUN:\s*(.*)$') |
| 70 | ir_function_re = re.compile('^\s*define\s+(?:internal\s+)?[^@]*@(\w+)\s*\(') |
| 71 | asm_function_re = re.compile( |
| 72 | r'^_?(?P<f>[^:]+):[ \t]*#+[ \t]*@(?P=f)\n[^:]*?' |
| 73 | r'(?P<body>^##?[ \t]+[^:]+:.*?)\s*' |
Chandler Carruth | 0613751 | 2015-02-15 00:08:01 +0000 | [diff] [blame] | 74 | r'^\s*(?:[^:\n]+?:\s*\n\s*\.size|\.cfi_endproc|\.globl|\.comm|\.(?:sub)?section)', |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 75 | flags=(re.M | re.S)) |
| 76 | check_prefix_re = re.compile('--check-prefix=(\S+)') |
| 77 | check_re = re.compile(r'^\s*;\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:') |
James Y Knight | 7c90506 | 2015-11-23 21:33:58 +0000 | [diff] [blame] | 78 | autogenerated_note = ('; NOTE: Assertions have been autogenerated by ' |
| 79 | 'utils/update_llc_test_checks.py') |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 80 | |
| 81 | for test in args.tests: |
| 82 | if args.verbose: |
| 83 | print >>sys.stderr, 'Scanning for RUN lines in test file: %s' % (test,) |
| 84 | with open(test) as f: |
| 85 | test_lines = [l.rstrip() for l in f] |
| 86 | |
| 87 | run_lines = [m.group(1) |
| 88 | for m in [run_line_re.match(l) for l in test_lines] if m] |
| 89 | if args.verbose: |
| 90 | print >>sys.stderr, 'Found %d RUN lines:' % (len(run_lines),) |
| 91 | for l in run_lines: |
| 92 | print >>sys.stderr, ' RUN: ' + l |
| 93 | |
| 94 | checks = [] |
| 95 | for l in run_lines: |
| 96 | (llc_cmd, filecheck_cmd) = tuple([cmd.strip() for cmd in l.split('|', 1)]) |
| 97 | if not llc_cmd.startswith('llc '): |
| 98 | print >>sys.stderr, 'WARNING: Skipping non-llc RUN line: ' + l |
| 99 | continue |
| 100 | |
| 101 | if not filecheck_cmd.startswith('FileCheck '): |
| 102 | print >>sys.stderr, 'WARNING: Skipping non-FileChecked RUN line: ' + l |
| 103 | continue |
| 104 | |
| 105 | llc_cmd_args = llc_cmd[len('llc'):].strip() |
| 106 | llc_cmd_args = llc_cmd_args.replace('< %s', '').replace('%s', '').strip() |
| 107 | |
| 108 | check_prefixes = [m.group(1) |
| 109 | for m in check_prefix_re.finditer(filecheck_cmd)] |
| 110 | if not check_prefixes: |
| 111 | check_prefixes = ['CHECK'] |
| 112 | |
| 113 | # FIXME: We should use multiple check prefixes to common check lines. For |
| 114 | # now, we just ignore all but the last. |
| 115 | checks.append((check_prefixes, llc_cmd_args)) |
| 116 | |
| 117 | asm = {} |
| 118 | for prefixes, _ in checks: |
| 119 | for prefix in prefixes: |
| 120 | asm.update({prefix: dict()}) |
| 121 | for prefixes, llc_args in checks: |
| 122 | if args.verbose: |
| 123 | print >>sys.stderr, 'Extracted LLC cmd: llc ' + llc_args |
| 124 | print >>sys.stderr, 'Extracted FileCheck prefixes: ' + str(prefixes) |
| 125 | raw_asm = llc(args, llc_args, test) |
| 126 | # Build up a dictionary of all the function bodies. |
| 127 | for m in asm_function_re.finditer(raw_asm): |
| 128 | if not m: |
| 129 | continue |
| 130 | f = m.group('f') |
| 131 | f_asm = scrub_asm(m.group('body')) |
Chandler Carruth | a4a77ed5 | 2015-02-03 21:26:45 +0000 | [diff] [blame] | 132 | if f.startswith('stress'): |
| 133 | # We only use the last line of the asm for stress tests. |
| 134 | f_asm = '\n'.join(f_asm.splitlines()[-1:]) |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 135 | if args.verbose: |
| 136 | print >>sys.stderr, 'Processing asm for function: ' + f |
| 137 | for l in f_asm.splitlines(): |
| 138 | print >>sys.stderr, ' ' + l |
| 139 | for prefix in prefixes: |
| 140 | if f in asm[prefix] and asm[prefix][f] != f_asm: |
| 141 | if prefix == prefixes[-1]: |
| 142 | print >>sys.stderr, ('WARNING: Found conflicting asm under the ' |
James Y Knight | 7c90506 | 2015-11-23 21:33:58 +0000 | [diff] [blame] | 143 | 'same prefix: %r!' % (prefix,)) |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 144 | else: |
| 145 | asm[prefix][f] = None |
| 146 | continue |
| 147 | |
| 148 | asm[prefix][f] = f_asm |
| 149 | |
| 150 | is_in_function = False |
| 151 | is_in_function_start = False |
| 152 | prefix_set = set([prefix for prefixes, _ in checks for prefix in prefixes]) |
| 153 | if args.verbose: |
| 154 | print >>sys.stderr, 'Rewriting FileCheck prefixes: %s' % (prefix_set,) |
| 155 | fixed_lines = [] |
James Y Knight | 7c90506 | 2015-11-23 21:33:58 +0000 | [diff] [blame] | 156 | fixed_lines.append(autogenerated_note) |
| 157 | |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 158 | for l in test_lines: |
| 159 | if is_in_function_start: |
| 160 | if l.lstrip().startswith(';'): |
| 161 | m = check_re.match(l) |
| 162 | if not m or m.group(1) not in prefix_set: |
| 163 | fixed_lines.append(l) |
| 164 | continue |
| 165 | |
| 166 | # Print out the various check lines here |
| 167 | printed_prefixes = [] |
| 168 | for prefixes, _ in checks: |
| 169 | for prefix in prefixes: |
| 170 | if prefix in printed_prefixes: |
| 171 | break |
| 172 | if not asm[prefix][name]: |
| 173 | continue |
| 174 | if len(printed_prefixes) != 0: |
| 175 | fixed_lines.append(';') |
| 176 | printed_prefixes.append(prefix) |
| 177 | fixed_lines.append('; %s-LABEL: %s:' % (prefix, name)) |
| 178 | asm_lines = asm[prefix][name].splitlines() |
| 179 | fixed_lines.append('; %s: %s' % (prefix, asm_lines[0])) |
| 180 | for asm_line in asm_lines[1:]: |
| 181 | fixed_lines.append('; %s-NEXT: %s' % (prefix, asm_line)) |
| 182 | break |
| 183 | is_in_function_start = False |
| 184 | |
| 185 | if is_in_function: |
| 186 | # Skip any blank comment lines in the IR. |
| 187 | if l.strip() == ';': |
| 188 | continue |
| 189 | # And skip any CHECK lines. We'll build our own. |
| 190 | m = check_re.match(l) |
| 191 | if m and m.group(1) in prefix_set: |
| 192 | continue |
| 193 | # Collect the remaining lines in the function body and look for the end |
| 194 | # of the function. |
| 195 | fixed_lines.append(l) |
| 196 | if l.strip() == '}': |
| 197 | is_in_function = False |
| 198 | continue |
| 199 | |
James Y Knight | 7c90506 | 2015-11-23 21:33:58 +0000 | [diff] [blame] | 200 | if l == autogenerated_note: |
| 201 | continue |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 202 | fixed_lines.append(l) |
| 203 | |
| 204 | m = ir_function_re.match(l) |
| 205 | if not m: |
| 206 | continue |
| 207 | name = m.group(1) |
| 208 | if args.function is not None and name != args.function: |
| 209 | # When filtering on a specific function, skip all others. |
| 210 | continue |
| 211 | is_in_function = is_in_function_start = True |
| 212 | |
| 213 | if args.verbose: |
| 214 | print>>sys.stderr, 'Writing %d fixed lines to %s...' % ( |
| 215 | len(fixed_lines), test) |
Simon Pilgrim | 6b6dcc4 | 2016-01-27 21:13:18 +0000 | [diff] [blame] | 216 | with open(test, 'wb') as f: |
Chandler Carruth | 06a5dd6 | 2015-01-12 04:43:18 +0000 | [diff] [blame] | 217 | f.writelines([l + '\n' for l in fixed_lines]) |
| 218 | |
| 219 | |
| 220 | if __name__ == '__main__': |
| 221 | main() |