[llvm-exegesis][NFC] Improve parsing of the YAML files
[llvm-core.git] / utils / update_cc_test_checks.py
blob865ebf7ff57be4dac8cd0bda20757071357a291d
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('tests', nargs='+')
90 args = parser.parse_args()
91 args.clang_args = shlex.split(args.clang_args or '')
93 if args.clang is None:
94 if args.llvm_bin is None:
95 args.clang = 'clang'
96 else:
97 args.clang = os.path.join(args.llvm_bin, 'clang')
98 if not distutils.spawn.find_executable(args.clang):
99 print('Please specify --llvm-bin or --clang', file=sys.stderr)
100 sys.exit(1)
101 if args.c_index_test is None:
102 if args.llvm_bin is None:
103 args.c_index_test = 'c-index-test'
104 else:
105 args.c_index_test = os.path.join(args.llvm_bin, 'c-index-test')
106 if not distutils.spawn.find_executable(args.c_index_test):
107 print('Please specify --llvm-bin or --c-index-test', file=sys.stderr)
108 sys.exit(1)
110 return args
113 def get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict):
114 # TODO Clean up duplication of asm/common build_function_body_dictionary
115 # Invoke external tool and extract function bodies.
116 raw_tool_output = common.invoke_tool(args.clang, clang_args, filename)
117 if '-emit-llvm' in clang_args:
118 common.build_function_body_dictionary(
119 common.OPT_FUNCTION_RE, common.scrub_body, [],
120 raw_tool_output, prefixes, func_dict, args.verbose)
121 else:
122 print('The clang command line should include -emit-llvm as asm tests '
123 'are discouraged in Clang testsuite.', file=sys.stderr)
124 sys.exit(1)
127 def main():
128 args = config()
129 autogenerated_note = (ADVERT + 'utils/' + os.path.basename(__file__))
131 for filename in args.tests:
132 with open(filename) as f:
133 input_lines = [l.rstrip() for l in f]
135 # Extract RUN lines.
136 raw_lines = [m.group(1)
137 for m in [RUN_LINE_RE.match(l) for l in input_lines] if m]
138 run_lines = [raw_lines[0]] if len(raw_lines) > 0 else []
139 for l in raw_lines[1:]:
140 if run_lines[-1].endswith("\\"):
141 run_lines[-1] = run_lines[-1].rstrip("\\") + " " + l
142 else:
143 run_lines.append(l)
145 if args.verbose:
146 print('Found {} RUN lines:'.format(len(run_lines)), file=sys.stderr)
147 for l in run_lines:
148 print(' RUN: ' + l, file=sys.stderr)
150 # Build a list of clang command lines and check prefixes from RUN lines.
151 run_list = []
152 line2spell_and_mangled_list = collections.defaultdict(list)
153 for l in run_lines:
154 commands = [cmd.strip() for cmd in l.split('|', 1)]
156 triple_in_cmd = None
157 m = common.TRIPLE_ARG_RE.search(commands[0])
158 if m:
159 triple_in_cmd = m.groups()[0]
161 # Apply %clang substitution rule, replace %s by `filename`, and append args.clang_args
162 clang_args = shlex.split(commands[0])
163 if clang_args[0] not in SUBST:
164 print('WARNING: Skipping non-clang RUN line: ' + l, file=sys.stderr)
165 continue
166 clang_args[0:1] = SUBST[clang_args[0]]
167 clang_args = [filename if i == '%s' else i for i in clang_args] + args.clang_args
169 # Extract -check-prefix in FileCheck args
170 filecheck_cmd = commands[-1]
171 if not filecheck_cmd.startswith('FileCheck '):
172 print('WARNING: Skipping non-FileChecked RUN line: ' + l, file=sys.stderr)
173 continue
174 check_prefixes = [item for m in common.CHECK_PREFIX_RE.finditer(filecheck_cmd)
175 for item in m.group(1).split(',')]
176 if not check_prefixes:
177 check_prefixes = ['CHECK']
178 run_list.append((check_prefixes, clang_args, triple_in_cmd))
180 # Strip CHECK lines which are in `prefix_set`, update test file.
181 prefix_set = set([prefix for p in run_list for prefix in p[0]])
182 input_lines = []
183 with open(filename, 'r+') as f:
184 for line in f:
185 m = CHECK_RE.match(line)
186 if not (m and m.group(1) in prefix_set) and line != '//\n':
187 input_lines.append(line)
188 f.seek(0)
189 f.writelines(input_lines)
190 f.truncate()
192 # Execute clang, generate LLVM IR, and extract functions.
193 func_dict = {}
194 for p in run_list:
195 prefixes = p[0]
196 for prefix in prefixes:
197 func_dict.update({prefix: dict()})
198 for prefixes, clang_args, triple_in_cmd in run_list:
199 if args.verbose:
200 print('Extracted clang cmd: clang {}'.format(clang_args), file=sys.stderr)
201 print('Extracted FileCheck prefixes: {}'.format(prefixes), file=sys.stderr)
203 get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict)
205 # Invoke c-index-test to get mapping from start lines to mangled names.
206 # Forward all clang args for now.
207 for k, v in get_line2spell_and_mangled(args, clang_args).items():
208 line2spell_and_mangled_list[k].append(v)
210 output_lines = [autogenerated_note]
211 for idx, line in enumerate(input_lines):
212 # Discard any previous script advertising.
213 if line.startswith(ADVERT):
214 continue
215 if idx in line2spell_and_mangled_list:
216 added = set()
217 for spell, mangled in line2spell_and_mangled_list[idx]:
218 # One line may contain multiple function declarations.
219 # Skip if the mangled name has been added before.
220 # The line number may come from an included file,
221 # we simply require the spelling name to appear on the line
222 # to exclude functions from other files.
223 if mangled in added or spell not in line:
224 continue
225 if args.functions is None or any(re.search(regex, spell) for regex in args.functions):
226 if added:
227 output_lines.append('//')
228 added.add(mangled)
229 common.add_ir_checks(output_lines, '//', run_list, func_dict, mangled)
230 output_lines.append(line.rstrip('\n'))
232 # Update the test file.
233 with open(filename, 'w') as f:
234 for line in output_lines:
235 f.write(line + '\n')
237 return 0
240 if __name__ == '__main__':
241 sys.exit(main())