Bug 1653367 [wpt PR 24632] - Update interfaces/streams.idl, a=testonly
[gecko.git] / config / check_macroassembler_style.py
blob525e92db2947a42ec7907c0974b340f6cb9ec0df
1 # vim: set ts=8 sts=4 et sw=4 tw=99:
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 # ----------------------------------------------------------------------------
7 # This script checks that SpiderMonkey MacroAssembler methods are properly
8 # annotated.
10 # The MacroAssembler has one interface for all platforms, but it might have one
11 # definition per platform. The code of the MacroAssembler use a macro to
12 # annotate the method declarations, in order to delete the function if it is not
13 # present on the current platform, and also to locate the files in which the
14 # methods are defined.
16 # This script scans the MacroAssembler.h header, for method declarations.
17 # It also scans MacroAssembler-/arch/.cpp, MacroAssembler-/arch/-inl.h, and
18 # MacroAssembler-inl.h for method definitions. The result of both scans are
19 # uniformized, and compared, to determine if the MacroAssembler.h header as
20 # proper methods annotations.
21 # ----------------------------------------------------------------------------
23 from __future__ import absolute_import
24 from __future__ import print_function
26 import difflib
27 import os
28 import re
29 import sys
31 architecture_independent = set(['generic'])
32 all_unsupported_architectures_names = set(['mips32', 'mips64', 'mips_shared'])
33 all_architecture_names = set(['x86', 'x64', 'arm', 'arm64'])
34 all_shared_architecture_names = set(['x86_shared', 'arm', 'arm64'])
36 reBeforeArg = "(?<=[(,\s])"
37 reArgType = "(?P<type>[\w\s:*&]+)"
38 reArgName = "(?P<name>\s\w+)"
39 reArgDefault = "(?P<default>(?:\s=[^,)]+)?)"
40 reAfterArg = "(?=[,)])"
41 reMatchArg = re.compile(reBeforeArg + reArgType +
42 reArgName + reArgDefault + reAfterArg)
45 def get_normalized_signatures(signature, fileAnnot=None):
46 # Remove static
47 signature = signature.replace('static', '')
48 # Remove semicolon.
49 signature = signature.replace(';', ' ')
50 # Normalize spaces.
51 signature = re.sub(r'\s+', ' ', signature).strip()
52 # Remove new-line induced spaces after opening braces.
53 signature = re.sub(r'\(\s+', '(', signature).strip()
54 # Match arguments, and keep only the type.
55 signature = reMatchArg.sub('\g<type>', signature)
56 # Remove class name
57 signature = signature.replace('MacroAssembler::', '')
59 # Extract list of architectures
60 archs = ['generic']
61 if fileAnnot:
62 archs = [fileAnnot['arch']]
64 if 'DEFINED_ON(' in signature:
65 archs = re.sub(
66 r'.*DEFINED_ON\((?P<archs>[^()]*)\).*', '\g<archs>', signature).split(',')
67 archs = [a.strip() for a in archs]
68 signature = re.sub(r'\s+DEFINED_ON\([^()]*\)', '', signature)
70 elif 'PER_ARCH' in signature:
71 archs = all_architecture_names
72 signature = re.sub(r'\s+PER_ARCH', '', signature)
74 elif 'PER_SHARED_ARCH' in signature:
75 archs = all_shared_architecture_names
76 signature = re.sub(r'\s+PER_SHARED_ARCH', '', signature)
78 elif 'OOL_IN_HEADER' in signature:
79 assert archs == ['generic']
80 signature = re.sub(r'\s+OOL_IN_HEADER', '', signature)
82 else:
83 # No signature annotation, the list of architectures remains unchanged.
84 pass
86 # Extract inline annotation
87 inline = False
88 if fileAnnot:
89 inline = fileAnnot['inline']
91 if 'inline ' in signature:
92 signature = re.sub(r'inline\s+', '', signature)
93 inline = True
95 inlinePrefx = ''
96 if inline:
97 inlinePrefx = 'inline '
98 signatures = [
99 {'arch': a, 'sig': inlinePrefx + signature}
100 for a in archs
103 return signatures
106 file_suffixes = set([
107 a.replace('_', '-') for a in
108 all_architecture_names.union(all_shared_architecture_names)
109 .union(all_unsupported_architectures_names)
113 def get_file_annotation(filename):
114 origFilename = filename
115 filename = filename.split('/')[-1]
117 inline = False
118 if filename.endswith('.cpp'):
119 filename = filename[:-len('.cpp')]
120 elif filename.endswith('-inl.h'):
121 inline = True
122 filename = filename[:-len('-inl.h')]
123 elif filename.endswith('.h'):
124 # This allows the definitions block in MacroAssembler.h to be
125 # style-checked.
126 inline = True
127 filename = filename[:-len('.h')]
128 else:
129 raise Exception('unknown file name', origFilename)
131 arch = 'generic'
132 for suffix in file_suffixes:
133 if filename == 'MacroAssembler-' + suffix:
134 arch = suffix
135 break
137 return {
138 'inline': inline,
139 'arch': arch.replace('-', '_')
143 def get_macroassembler_definitions(filename):
144 try:
145 fileAnnot = get_file_annotation(filename)
146 except Exception:
147 return []
149 style_section = False
150 lines = ''
151 signatures = []
152 with open(filename) as f:
153 for line in f:
154 if '//{{{ check_macroassembler_style' in line:
155 if style_section:
156 raise 'check_macroassembler_style section already opened.'
157 style_section = True
158 braces_depth = 0
159 elif '//}}} check_macroassembler_style' in line:
160 style_section = False
161 if not style_section:
162 continue
164 # Ignore preprocessor directives.
165 if line.startswith('#'):
166 continue
168 # Remove comments from the processed line.
169 line = re.sub(r'//.*', '', line)
171 # Locate and count curly braces.
172 open_curly_brace = line.find('{')
173 was_braces_depth = braces_depth
174 braces_depth = braces_depth + line.count('{') - line.count('}')
176 # Raise an error if the check_macroassembler_style macro is used
177 # across namespaces / classes scopes.
178 if braces_depth < 0:
179 raise 'check_macroassembler_style annotations are not well scoped.'
181 # If the current line contains an opening curly brace, check if
182 # this line combines with the previous one can be identified as a
183 # MacroAssembler function signature.
184 if open_curly_brace != -1 and was_braces_depth == 0:
185 lines = lines + line[:open_curly_brace]
186 if 'MacroAssembler::' in lines:
187 signatures.extend(
188 get_normalized_signatures(lines, fileAnnot))
189 lines = ''
190 continue
192 # We do not aggregate any lines if we are scanning lines which are
193 # in-between a set of curly braces.
194 if braces_depth > 0:
195 continue
196 if was_braces_depth != 0:
197 line = line[line.rfind('}') + 1:]
199 # This logic is used to remove template instantiation, static
200 # variable definitions and function declaration from the next
201 # function definition.
202 last_semi_colon = line.rfind(';')
203 if last_semi_colon != -1:
204 lines = ''
205 line = line[last_semi_colon + 1:]
207 # Aggregate lines of non-braced text, which corresponds to the space
208 # where we are expecting to find function definitions.
209 lines = lines + line
211 return signatures
214 def get_macroassembler_declaration(filename):
215 style_section = False
216 lines = ''
217 signatures = []
218 with open(filename) as f:
219 for line in f:
220 if '//{{{ check_macroassembler_decl_style' in line:
221 style_section = True
222 elif '//}}} check_macroassembler_decl_style' in line:
223 style_section = False
224 if not style_section:
225 continue
227 # Ignore preprocessor directives.
228 if line.startswith('#'):
229 continue
231 line = re.sub(r'//.*', '', line)
232 if len(line.strip()) == 0 or 'public:' in line or 'private:' in line:
233 lines = ''
234 continue
236 lines = lines + line
238 # Continue until we have a complete declaration
239 if ';' not in lines:
240 continue
242 # Skip member declarations: which are lines ending with a
243 # semi-colon without any list of arguments.
244 if ')' not in lines:
245 lines = ''
246 continue
248 signatures.extend(get_normalized_signatures(lines))
249 lines = ''
251 return signatures
254 def append_signatures(d, sigs):
255 for s in sigs:
256 if s['sig'] not in d:
257 d[s['sig']] = []
258 d[s['sig']].append(s['arch'])
259 return d
262 def generate_file_content(signatures):
263 output = []
264 for s in sorted(signatures.keys()):
265 archs = set(sorted(signatures[s]))
266 archs -= all_unsupported_architectures_names
267 if len(archs.symmetric_difference(architecture_independent)) == 0:
268 output.append(s + ';\n')
269 if s.startswith('inline'):
270 # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
271 # functions. (Such annotation is already removed by the time
272 # this function sees the signature here.)
273 output.append(' is defined in MacroAssembler-inl.h\n')
274 else:
275 output.append(' is defined in MacroAssembler.cpp\n')
276 else:
277 if len(archs.symmetric_difference(all_architecture_names)) == 0:
278 output.append(s + ' PER_ARCH;\n')
279 elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
280 output.append(s + ' PER_SHARED_ARCH;\n')
281 else:
282 output.append(
283 s + ' DEFINED_ON(' + ', '.join(sorted(archs)) + ');\n')
284 for a in sorted(archs):
285 a = a.replace('_', '-')
286 masm = '%s/MacroAssembler-%s' % (a, a)
287 if s.startswith('inline'):
288 output.append(' is defined in %s-inl.h\n' % masm)
289 else:
290 output.append(' is defined in %s.cpp\n' % masm)
291 return output
294 def check_style():
295 # We read from the header file the signature of each function.
296 decls = dict() # type: dict(signature => ['x86', 'x64'])
298 # We infer from each file the signature of each MacroAssembler function.
299 defs = dict() # type: dict(signature => ['x86', 'x64'])
301 root_dir = os.path.join('js', 'src', 'jit')
302 for dirpath, dirnames, filenames in os.walk(root_dir):
303 for filename in filenames:
304 if 'MacroAssembler' not in filename:
305 continue
307 filepath = os.path.join(dirpath, filename).replace('\\', '/')
309 if filepath.endswith('MacroAssembler.h'):
310 decls = append_signatures(
311 decls, get_macroassembler_declaration(filepath))
312 defs = append_signatures(
313 defs, get_macroassembler_definitions(filepath))
315 if not decls or not defs:
316 raise Exception("Did not find any definitions or declarations")
318 # Compare declarations and definitions output.
319 difflines = difflib.unified_diff(generate_file_content(decls),
320 generate_file_content(defs),
321 fromfile='check_macroassembler_style.py declared syntax',
322 tofile='check_macroassembler_style.py found definitions')
323 ok = True
324 for diffline in difflines:
325 ok = False
326 print(diffline, end='')
328 return ok
331 def main():
332 ok = check_style()
334 if ok:
335 print('TEST-PASS | check_macroassembler_style.py | ok')
336 else:
337 print('TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above') # noqa: E501
339 sys.exit(0 if ok else 1)
342 if __name__ == '__main__':
343 main()