llvm.org GIT mirror llvm / 94497ce
[utils] Add utils/update_cc_test_checks.py A utility to update LLVM IR in C/C++ FileCheck test files. Example RUN lines in .c/.cc test files: // RUN: %clang -S -Os -DXX %s -o - | FileCheck %s // RUN: %clangxx -S -Os %s -o - | FileCheck -check-prefix=IR %s Usage: % utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc % utils/update_cc_test_checks.py --c-index-test=release/bin/c-index-test --clang=release/bin/clang /tmp/c/a.cc // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py // RUN: %clang -emit-llvm -S -Os -DXX %s -o - | FileCheck -check-prefix=AA %s // RUN: %clangxx -emit-llvm -S -Os %s -o - | FileCheck -check-prefix=BB %s using T = #ifdef XX int __attribute__((vector_size(16))) #else short __attribute__((vector_size(16))) #endif ; // AA-LABEL: _Z3fooDv4_i: // AA: entry: // AA-NEXT: %add = shl <4 x i32> %a, <i32 1, i32 1, i32 1, i32 1> // AA-NEXT: ret <4 x i32> %add // // BB-LABEL: _Z3fooDv8_s: // BB: entry: // BB-NEXT: %add = shl <8 x i16> %a, <i16 1, i16 1, i16 1, i16 1, i16 1, i16 1, i16 1, i16 1> // BB-NEXT: ret <8 x i16> %add T foo(T a) { return a + a; } Differential Revision: https://reviews.llvm.org/D42712 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@326591 91177308-0d34-0410-b5e6-96231b3b80d8 Fangrui Song 2 years ago
3 changed file(s) with 251 addition(s) and 3 deletion(s). Raw diff Collapse all Expand all
9393 asm = SCRUB_X86_RIP_RE.sub(r'{{.*}}(%rip)', asm)
9494 # Generically match a LCP symbol.
9595 asm = SCRUB_X86_LCP_RE.sub(r'{{\.LCPI.*}}', asm)
96 if args.x86_extra_scrub:
96 if getattr(args, 'x86_extra_scrub', False):
9797 # Avoid generating different checks for 32- and 64-bit because of 'retl' vs 'retq'.
9898 asm = SCRUB_X86_RET_RE.sub(r'ret{{[l|q]}}', asm)
9999 # Strip kill operands inserted into the asm.
2828 # Invoke the tool that is being tested.
2929 def invoke_tool(exe, cmd_args, ir):
3030 with open(ir) as ir_file:
31 stdout = subprocess.check_output(exe + ' ' + cmd_args,
32 shell=True, stdin=ir_file)
31 # TODO Remove the str form which is used by update_test_checks.py and
32 # update_llc_test_checks.py
33 # The safer list form is used by update_cc_test_checks.py
34 if isinstance(cmd_args, list):
35 stdout = subprocess.check_output([exe] + cmd_args, stdin=ir_file)
36 else:
37 stdout = subprocess.check_output(exe + ' ' + cmd_args,
38 shell=True, stdin=ir_file)
3339 if sys.version_info[0] > 2:
3440 stdout = stdout.decode()
3541 # Fix line endings to unix CR style.
0 #!/usr/bin/env python3
1 '''A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.
2
3 Example RUN lines in .c/.cc test files:
4
5 // RUN: %clang -emit-llvm -S %s -o - -O2 | FileCheck %s
6 // RUN: %clangxx -emit-llvm -S %s -o - -O2 | FileCheck -check-prefix=CHECK-A %s
7
8 Usage:
9
10 % utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc
11 % utils/update_cc_test_checks.py --c-index-test=release/bin/c-index-test \
12 --clang=release/bin/clang /tmp/c/a.cc
13 '''
14
15 import argparse
16 import collections
17 import distutils.spawn
18 import os
19 import shlex
20 import string
21 import subprocess
22 import sys
23 import re
24 import tempfile
25
26 from UpdateTestChecks import asm, common
27
28 ADVERT = '// NOTE: Assertions have been autogenerated by '
29
30 CHECK_RE = re.compile(r'^\s*//\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:')
31 RUN_LINE_RE = re.compile('^//\s*RUN:\s*(.*)$')
32
33 SUBST = {
34 '%clang': [],
35 '%clang_cc1': ['-cc1'],
36 '%clangxx': ['--driver-mode=g++'],
37 }
38
39 def get_line2spell_and_mangled(args, clang_args):
40 ret = {}
41 with tempfile.NamedTemporaryFile() as f:
42 # TODO Make c-index-test print mangled names without circumventing through precompiled headers
43 status = subprocess.run([args.c_index_test, '-write-pch', f.name, *clang_args],
44 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
45 if status.returncode:
46 sys.stderr.write(status.stdout.decode())
47 sys.exit(2)
48 output = subprocess.check_output([args.c_index_test,
49 '-test-print-mangle', f.name])
50 if sys.version_info[0] > 2:
51 output = output.decode()
52
53 RE = re.compile(r'^FunctionDecl=(\w+):(\d+):\d+ \(Definition\) \[mangled=([^]]+)\]')
54 for line in output.splitlines():
55 m = RE.match(line)
56 if not m: continue
57 spell, line, mangled = m.groups()
58 if mangled == '_' + spell:
59 # HACK for MacOS (where the mangled name includes an _ for C but the IR won't):
60 mangled = spell
61 # Note -test-print-mangle does not print file names so if #include is used,
62 # the line number may come from an included file.
63 ret[int(line)-1] = (spell, mangled)
64 if args.verbose:
65 for line, func_name in sorted(ret.items()):
66 print('line {}: found function {}'.format(line+1, func_name), file=sys.stderr)
67 return ret
68
69
70 def config():
71 parser = argparse.ArgumentParser(
72 description=__doc__,
73 formatter_class=argparse.RawTextHelpFormatter)
74 parser.add_argument('-v', '--verbose', action='store_true')
75 parser.add_argument('--llvm-bin', help='llvm $prefix/bin path')
76 parser.add_argument('--clang',
77 help='"clang" executable, defaults to $llvm_bin/clang')
78 parser.add_argument('--clang-args',
79 help='Space-separated extra args to clang, e.g. --clang-args=-v')
80 parser.add_argument('--c-index-test',
81 help='"c-index-test" executable, defaults to $llvm_bin/c-index-test')
82 parser.add_argument(
83 '--functions', nargs='+', help='A list of function name regexes. '
84 'If specified, update CHECK lines for functions matching at least one regex')
85 parser.add_argument(
86 '--x86_extra_scrub', action='store_true',
87 help='Use more regex for x86 matching to reduce diffs between various subtargets')
88 parser.add_argument('tests', nargs='+')
89 args = parser.parse_args()
90 args.clang_args = shlex.split(args.clang_args or '')
91
92 if args.clang is None:
93 if args.llvm_bin is None:
94 args.clang = 'clang'
95 else:
96 args.clang = os.path.join(args.llvm_bin, 'clang')
97 if not distutils.spawn.find_executable(args.clang):
98 print('Please specify --llvm-bin or --clang', file=sys.stderr)
99 sys.exit(1)
100 if args.c_index_test is None:
101 if args.llvm_bin is None:
102 args.c_index_test = 'c-index-test'
103 else:
104 args.c_index_test = os.path.join(args.llvm_bin, 'c-index-test')
105 if not distutils.spawn.find_executable(args.c_index_test):
106 print('Please specify --llvm-bin or --c-index-test', file=sys.stderr)
107 sys.exit(1)
108
109 return args
110
111
112 def get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict):
113 # TODO Clean up duplication of asm/common build_function_body_dictionary
114 # Invoke external tool and extract function bodies.
115 raw_tool_output = common.invoke_tool(args.clang, clang_args, filename)
116 if '-emit-llvm' in clang_args:
117 common.build_function_body_dictionary(
118 common.OPT_FUNCTION_RE, common.scrub_body, [],
119 raw_tool_output, prefixes, func_dict, args.verbose)
120 else:
121 print('The clang command line should include -emit-llvm as asm tests '
122 'are discouraged in Clang testsuite.', file=sys.stderr)
123 sys.exit(1)
124
125
126 def main():
127 args = config()
128 autogenerated_note = (ADVERT + 'utils/' + os.path.basename(__file__))
129
130 for filename in args.tests:
131 with open(filename) as f:
132 input_lines = [l.rstrip() for l in f]
133
134 # Extract RUN lines.
135 raw_lines = [m.group(1)
136 for m in [RUN_LINE_RE.match(l) for l in input_lines] if m]
137 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
138 for l in raw_lines[1:]:
139 if run_lines[-1].endswith("\\"):
140 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
141 else:
142 run_lines.append(l)
143
144 if args.verbose:
145 print('Found {} RUN lines:'.format(len(run_lines)), file=sys.stderr)
146 for l in run_lines:
147 print(' RUN: ' + l, file=sys.stderr)
148
149 # Build a list of clang command lines and check prefixes from RUN lines.
150 run_list = []
151 line2spell_and_mangled_list = collections.defaultdict(list)
152 for l in run_lines:
153 commands = [cmd.strip() for cmd in l.split('|', 1)]
154
155 triple_in_cmd = None
156 m = common.TRIPLE_ARG_RE.search(commands[0])
157 if m:
158 triple_in_cmd = m.groups()[0]
159
160 # Apply %clang substitution rule, replace %s by `filename`, and append args.clang_args
161 clang_args = shlex.split(commands[0])
162 if clang_args[0] not in SUBST:
163 print('WARNING: Skipping non-clang RUN line: ' + l, file=sys.stderr)
164 continue
165 clang_args[0:1] = SUBST[clang_args[0]]
166 clang_args = [filename if i == '%s' else i for i in clang_args] + args.clang_args
167
168 # Extract -check-prefix in FileCheck args
169 filecheck_cmd = commands[-1]
170 if not filecheck_cmd.startswith('FileCheck '):
171 print('WARNING: Skipping non-FileChecked RUN line: ' + l, file=sys.stderr)
172 continue
173 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
174 for item in m.group(1).split(',')]
175 if not check_prefixes:
176 check_prefixes = ['CHECK']
177 run_list.append((check_prefixes, clang_args, triple_in_cmd))
178
179 # Strip CHECK lines which are in `prefix_set`, update test file.
180 prefix_set = set([prefix for p in run_list for prefix in p[0]])
181 input_lines = []
182 with open(filename, 'r+') as f:
183 for line in f:
184 m = CHECK_RE.match(line)
185 if not (m and m.group(1) in prefix_set) and line != '//\n':
186 input_lines.append(line)
187 f.seek(0)
188 f.writelines(input_lines)
189 f.truncate()
190
191 # Execute clang, generate LLVM IR, and extract functions.
192 func_dict = {}
193 for p in run_list:
194 prefixes = p[0]
195 for prefix in prefixes:
196 func_dict.update({prefix: dict()})
197 for prefixes, clang_args, triple_in_cmd in run_list:
198 if args.verbose:
199 print('Extracted clang cmd: clang {}'.format(clang_args), file=sys.stderr)
200 print('Extracted FileCheck prefixes: {}'.format(prefixes), file=sys.stderr)
201
202 get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict)
203
204 # Invoke c-index-test to get mapping from start lines to mangled names.
205 # Forward all clang args for now.
206 for k, v in get_line2spell_and_mangled(args, clang_args).items():
207 line2spell_and_mangled_list[k].append(v)
208
209 output_lines = [autogenerated_note]
210 for idx, line in enumerate(input_lines):
211 # Discard any previous script advertising.
212 if line.startswith(ADVERT):
213 continue
214 if idx in line2spell_and_mangled_list:
215 added = set()
216 for spell, mangled in line2spell_and_mangled_list[idx]:
217 # One line may contain multiple function declarations.
218 # Skip if the mangled name has been added before.
219 # The line number may come from an included file,
220 # we simply require the spelling name to appear on the line
221 # to exclude functions from other files.
222 if mangled in added or spell not in line:
223 continue
224 if args.functions is None or any(re.search(regex, spell) for regex in args.functions):
225 if added:
226 output_lines.append('//')
227 added.add(mangled)
228 # This is also used for adding IR CHECK lines.
229 asm.add_asm_checks(output_lines, '//', run_list, func_dict, mangled)
230 output_lines.append(line.rstrip('\n'))
231
232 # Update the test file.
233 with open(filename, 'w') as f:
234 for line in output_lines:
235 f.write(line + '\n')
236
237 return 0
238
239
240 if __name__ == '__main__':
241 sys.exit(main())