gn build: Merge r372706
[llvm-complete.git] / utils / update_cc_test_checks.py
blobd4eb9f2ca1bb32df763f78cd28751fdb407be576
1 #!/usr/bin/env python3
2 '''A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.
4 Example RUN lines in .c/.cc test files:
6 // RUN: %clang -emit-llvm -S %s -o - -O2 | FileCheck %s
7 // RUN: %clangxx -emit-llvm -S %s -o - -O2 | FileCheck -check-prefix=CHECK-A %s
9 Usage:
11 % utils/update_cc_test_checks.py --llvm-bin=release/bin test/a.cc
12 % utils/update_cc_test_checks.py --c-index-test=release/bin/c-index-test \
13 --clang=release/bin/clang /tmp/c/a.cc
14 '''
16 import argparse
17 import collections
18 import distutils.spawn
19 import os
20 import shlex
21 import string
22 import subprocess
23 import sys
24 import re
25 import tempfile
27 from UpdateTestChecks import asm, common
29 ADVERT = '// NOTE: Assertions have been autogenerated by '
31 CHECK_RE = re.compile(r'^\s*//\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:')
32 RUN_LINE_RE = re.compile('^//\s*RUN:\s*(.*)$')
34 SUBST = {
35 '%clang': [],
36 '%clang_cc1': ['-cc1'],
37 '%clangxx': ['--driver-mode=g++'],
40 def get_line2spell_and_mangled(args, clang_args):
41 ret = {}
42 with tempfile.NamedTemporaryFile() as f:
43 # TODO Make c-index-test print mangled names without circumventing through precompiled headers
44 status = subprocess.run([args.c_index_test, '-write-pch', f.name, *clang_args],
45 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
46 if status.returncode:
47 sys.stderr.write(status.stdout.decode())
48 sys.exit(2)
49 output = subprocess.check_output([args.c_index_test,
50 '-test-print-mangle', f.name])
51 if sys.version_info[0] > 2:
52 output = output.decode()
54 RE = re.compile(r'^FunctionDecl=(\w+):(\d+):\d+ \(Definition\) \[mangled=([^]]+)\]')
55 for line in output.splitlines():
56 m = RE.match(line)
57 if not m: continue
58 spell, line, mangled = m.groups()
59 if mangled == '_' + spell:
60 # HACK for MacOS (where the mangled name includes an _ for C but the IR won't):
61 mangled = spell
62 # Note -test-print-mangle does not print file names so if #include is used,
63 # the line number may come from an included file.
64 ret[int(line)-1] = (spell, mangled)
65 if args.verbose:
66 for line, func_name in sorted(ret.items()):
67 print('line {}: found function {}'.format(line+1, func_name), file=sys.stderr)
68 return ret
71 def config():
72 parser = argparse.ArgumentParser(
73 description=__doc__,
74 formatter_class=argparse.RawTextHelpFormatter)
75 parser.add_argument('-v', '--verbose', action='store_true')
76 parser.add_argument('--llvm-bin', help='llvm $prefix/bin path')
77 parser.add_argument('--clang',
78 help='"clang" executable, defaults to $llvm_bin/clang')
79 parser.add_argument('--clang-args',
80 help='Space-separated extra args to clang, e.g. --clang-args=-v')
81 parser.add_argument('--c-index-test',
82 help='"c-index-test" executable, defaults to $llvm_bin/c-index-test')
83 parser.add_argument(
84 '--functions', nargs='+', help='A list of function name regexes. '
85 'If specified, update CHECK lines for functions matching at least one regex')
86 parser.add_argument(
87 '--x86_extra_scrub', action='store_true',
88 help='Use more regex for x86 matching to reduce diffs between various subtargets')
89 parser.add_argument('-u', '--update-only', action='store_true',
90 help='Only update test if it was already autogened')
91 parser.add_argument('tests', nargs='+')
92 args = parser.parse_args()
93 args.clang_args = shlex.split(args.clang_args or '')
95 if args.clang is None:
96 if args.llvm_bin is None:
97 args.clang = 'clang'
98 else:
99 args.clang = os.path.join(args.llvm_bin, 'clang')
100 if not distutils.spawn.find_executable(args.clang):
101 print('Please specify --llvm-bin or --clang', file=sys.stderr)
102 sys.exit(1)
103 if args.c_index_test is None:
104 if args.llvm_bin is None:
105 args.c_index_test = 'c-index-test'
106 else:
107 args.c_index_test = os.path.join(args.llvm_bin, 'c-index-test')
108 if not distutils.spawn.find_executable(args.c_index_test):
109 print('Please specify --llvm-bin or --c-index-test', file=sys.stderr)
110 sys.exit(1)
112 return args
115 def get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict):
116 # TODO Clean up duplication of asm/common build_function_body_dictionary
117 # Invoke external tool and extract function bodies.
118 raw_tool_output = common.invoke_tool(args.clang, clang_args, filename)
119 if '-emit-llvm' in clang_args:
120 common.build_function_body_dictionary(
121 common.OPT_FUNCTION_RE, common.scrub_body, [],
122 raw_tool_output, prefixes, func_dict, args.verbose)
123 else:
124 print('The clang command line should include -emit-llvm as asm tests '
125 'are discouraged in Clang testsuite.', file=sys.stderr)
126 sys.exit(1)
129 def main():
130 args = config()
131 script_name = os.path.basename(__file__)
132 autogenerated_note = (ADVERT + 'utils/' + script_name)
134 for filename in args.tests:
135 with open(filename) as f:
136 input_lines = [l.rstrip() for l in f]
138 first_line = input_lines[0] if input_lines else ""
139 if 'autogenerated' in first_line and script_name not in first_line:
140 common.warn("Skipping test which wasn't autogenerated by " + script_name, filename)
141 continue
143 if args.update_only:
144 if not first_line or 'autogenerated' not in first_line:
145 common.warn("Skipping test which isn't autogenerated: " + filename)
146 continue
148 # Extract RUN lines.
149 raw_lines = [m.group(1)
150 for m in [RUN_LINE_RE.match(l) for l in input_lines] if m]
151 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
152 for l in raw_lines[1:]:
153 if run_lines[-1].endswith("\\"):
154 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
155 else:
156 run_lines.append(l)
158 if args.verbose:
159 print('Found {} RUN lines:'.format(len(run_lines)), file=sys.stderr)
160 for l in run_lines:
161 print(' RUN: ' + l, file=sys.stderr)
163 # Build a list of clang command lines and check prefixes from RUN lines.
164 run_list = []
165 line2spell_and_mangled_list = collections.defaultdict(list)
166 for l in run_lines:
167 commands = [cmd.strip() for cmd in l.split('|', 1)]
169 triple_in_cmd = None
170 m = common.TRIPLE_ARG_RE.search(commands[0])
171 if m:
172 triple_in_cmd = m.groups()[0]
174 # Apply %clang substitution rule, replace %s by `filename`, and append args.clang_args
175 clang_args = shlex.split(commands[0])
176 if clang_args[0] not in SUBST:
177 print('WARNING: Skipping non-clang RUN line: ' + l, file=sys.stderr)
178 continue
179 clang_args[0:1] = SUBST[clang_args[0]]
180 clang_args = [filename if i == '%s' else i for i in clang_args] + args.clang_args
182 # Extract -check-prefix in FileCheck args
183 filecheck_cmd = commands[-1]
184 common.verify_filecheck_prefixes(filecheck_cmd)
185 if not filecheck_cmd.startswith('FileCheck '):
186 print('WARNING: Skipping non-FileChecked RUN line: ' + l, file=sys.stderr)
187 continue
188 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
189 for item in m.group(1).split(',')]
190 if not check_prefixes:
191 check_prefixes = ['CHECK']
192 run_list.append((check_prefixes, clang_args, triple_in_cmd))
194 # Strip CHECK lines which are in `prefix_set`, update test file.
195 prefix_set = set([prefix for p in run_list for prefix in p[0]])
196 input_lines = []
197 with open(filename, 'r+') as f:
198 for line in f:
199 m = CHECK_RE.match(line)
200 if not (m and m.group(1) in prefix_set) and line != '//\n':
201 input_lines.append(line)
202 f.seek(0)
203 f.writelines(input_lines)
204 f.truncate()
206 # Execute clang, generate LLVM IR, and extract functions.
207 func_dict = {}
208 for p in run_list:
209 prefixes = p[0]
210 for prefix in prefixes:
211 func_dict.update({prefix: dict()})
212 for prefixes, clang_args, triple_in_cmd in run_list:
213 if args.verbose:
214 print('Extracted clang cmd: clang {}'.format(clang_args), file=sys.stderr)
215 print('Extracted FileCheck prefixes: {}'.format(prefixes), file=sys.stderr)
217 get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict)
219 # Invoke c-index-test to get mapping from start lines to mangled names.
220 # Forward all clang args for now.
221 for k, v in get_line2spell_and_mangled(args, clang_args).items():
222 line2spell_and_mangled_list[k].append(v)
224 output_lines = [autogenerated_note]
225 for idx, line in enumerate(input_lines):
226 # Discard any previous script advertising.
227 if line.startswith(ADVERT):
228 continue
229 if idx in line2spell_and_mangled_list:
230 added = set()
231 for spell, mangled in line2spell_and_mangled_list[idx]:
232 # One line may contain multiple function declarations.
233 # Skip if the mangled name has been added before.
234 # The line number may come from an included file,
235 # we simply require the spelling name to appear on the line
236 # to exclude functions from other files.
237 if mangled in added or spell not in line:
238 continue
239 if args.functions is None or any(re.search(regex, spell) for regex in args.functions):
240 if added:
241 output_lines.append('//')
242 added.add(mangled)
243 common.add_ir_checks(output_lines, '//', run_list, func_dict, mangled)
244 output_lines.append(line.rstrip('\n'))
246 # Update the test file.
247 with open(filename, 'w') as f:
248 for line in output_lines:
249 f.write(line + '\n')
251 return 0
254 if __name__ == '__main__':
255 sys.exit(main())