OSDN Git Service

gn build: Merge r366361.
[android-x86/external-llvm.git] / utils / update_cc_test_checks.py
1 #!/usr/bin/env python3
2 '''A utility to update LLVM IR CHECK lines in C/C++ FileCheck test files.
3
4 Example RUN lines in .c/.cc test files:
5
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
8
9 Usage:
10
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 '''
15
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
26
27 from UpdateTestChecks import asm, common
28
29 ADVERT = '// NOTE: Assertions have been autogenerated by '
30
31 CHECK_RE = re.compile(r'^\s*//\s*([^:]+?)(?:-NEXT|-NOT|-DAG|-LABEL)?:')
32 RUN_LINE_RE = re.compile('^//\s*RUN:\s*(.*)$')
33
34 SUBST = {
35     '%clang': [],
36     '%clang_cc1': ['-cc1'],
37     '%clangxx': ['--driver-mode=g++'],
38 }
39
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()
53
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
69
70
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 '')
92
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)
109
110   return args
111
112
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)
125
126
127 def main():
128   args = config()
129   autogenerated_note = (ADVERT + 'utils/' + os.path.basename(__file__))
130
131   for filename in args.tests:
132     with open(filename) as f:
133       input_lines = [l.rstrip() for l in f]
134
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)
144
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)
149
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)]
155
156       triple_in_cmd = None
157       m = common.TRIPLE_ARG_RE.search(commands[0])
158       if m:
159         triple_in_cmd = m.groups()[0]
160
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
168
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))
179
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()
191
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)
202
203       get_function_body(args, filename, clang_args, prefixes, triple_in_cmd, func_dict)
204
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)
209
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'))
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())