Bug 1509898 [wpt PR 14236] - Simplify interpolation of 2-D matrix transforms., a...
[gecko.git] / config / check_macroassembler_style.py
blob1b2dcb2fe599cd647276f6afa0a98ad6824538b1
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 print_function
25 import difflib
26 import os
27 import re
28 import sys
30 architecture_independent = set(['generic'])
31 all_unsupported_architectures_names = set(['mips32', 'mips64', 'mips_shared'])
32 all_architecture_names = set(['x86', 'x64', 'arm', 'arm64'])
33 all_shared_architecture_names = set(['x86_shared', 'arm', 'arm64'])
35 reBeforeArg = "(?<=[(,\s])"
36 reArgType = "(?P<type>[\w\s:*&]+)"
37 reArgName = "(?P<name>\s\w+)"
38 reArgDefault = "(?P<default>(?:\s=[^,)]+)?)"
39 reAfterArg = "(?=[,)])"
40 reMatchArg = re.compile(reBeforeArg + reArgType +
41 reArgName + reArgDefault + reAfterArg)
44 def get_normalized_signatures(signature, fileAnnot=None):
45 # Remove static
46 signature = signature.replace('static', '')
47 # Remove semicolon.
48 signature = signature.replace(';', ' ')
49 # Normalize spaces.
50 signature = re.sub(r'\s+', ' ', signature).strip()
51 # Remove new-line induced spaces after opening braces.
52 signature = re.sub(r'\(\s+', '(', signature).strip()
53 # Match arguments, and keep only the type.
54 signature = reMatchArg.sub('\g<type>', signature)
55 # Remove class name
56 signature = signature.replace('MacroAssembler::', '')
58 # Extract list of architectures
59 archs = ['generic']
60 if fileAnnot:
61 archs = [fileAnnot['arch']]
63 if 'DEFINED_ON(' in signature:
64 archs = re.sub(
65 r'.*DEFINED_ON\((?P<archs>[^()]*)\).*', '\g<archs>', signature).split(',')
66 archs = [a.strip() for a in archs]
67 signature = re.sub(r'\s+DEFINED_ON\([^()]*\)', '', signature)
69 elif 'PER_ARCH' in signature:
70 archs = all_architecture_names
71 signature = re.sub(r'\s+PER_ARCH', '', signature)
73 elif 'PER_SHARED_ARCH' in signature:
74 archs = all_shared_architecture_names
75 signature = re.sub(r'\s+PER_SHARED_ARCH', '', signature)
77 elif 'OOL_IN_HEADER' in signature:
78 assert archs == ['generic']
79 signature = re.sub(r'\s+OOL_IN_HEADER', '', signature)
81 else:
82 # No signature annotation, the list of architectures remains unchanged.
83 pass
85 # Extract inline annotation
86 inline = False
87 if fileAnnot:
88 inline = fileAnnot['inline']
90 if 'inline ' in signature:
91 signature = re.sub(r'inline\s+', '', signature)
92 inline = True
94 inlinePrefx = ''
95 if inline:
96 inlinePrefx = 'inline '
97 signatures = [
98 {'arch': a, 'sig': inlinePrefx + signature}
99 for a in archs
102 return signatures
105 file_suffixes = set([
106 a.replace('_', '-') for a in
107 all_architecture_names.union(all_shared_architecture_names)
108 .union(all_unsupported_architectures_names)
112 def get_file_annotation(filename):
113 origFilename = filename
114 filename = filename.split('/')[-1]
116 inline = False
117 if filename.endswith('.cpp'):
118 filename = filename[:-len('.cpp')]
119 elif filename.endswith('-inl.h'):
120 inline = True
121 filename = filename[:-len('-inl.h')]
122 elif filename.endswith('.h'):
123 # This allows the definitions block in MacroAssembler.h to be
124 # style-checked.
125 inline = True
126 filename = filename[:-len('.h')]
127 else:
128 raise Exception('unknown file name', origFilename)
130 arch = 'generic'
131 for suffix in file_suffixes:
132 if filename == 'MacroAssembler-' + suffix:
133 arch = suffix
134 break
136 return {
137 'inline': inline,
138 'arch': arch.replace('-', '_')
142 def get_macroassembler_definitions(filename):
143 try:
144 fileAnnot = get_file_annotation(filename)
145 except Exception:
146 return []
148 style_section = False
149 lines = ''
150 signatures = []
151 with open(filename) as f:
152 for line in f:
153 if '//{{{ check_macroassembler_style' in line:
154 if style_section:
155 raise 'check_macroassembler_style section already opened.'
156 style_section = True
157 braces_depth = 0
158 elif '//}}} check_macroassembler_style' in line:
159 style_section = False
160 if not style_section:
161 continue
163 # Remove comments from the processed line.
164 line = re.sub(r'//.*', '', line)
166 # Locate and count curly braces.
167 open_curly_brace = line.find('{')
168 was_braces_depth = braces_depth
169 braces_depth = braces_depth + line.count('{') - line.count('}')
171 # Raise an error if the check_macroassembler_style macro is used
172 # across namespaces / classes scopes.
173 if braces_depth < 0:
174 raise 'check_macroassembler_style annotations are not well scoped.'
176 # If the current line contains an opening curly brace, check if
177 # this line combines with the previous one can be identified as a
178 # MacroAssembler function signature.
179 if open_curly_brace != -1 and was_braces_depth == 0:
180 lines = lines + line[:open_curly_brace]
181 if 'MacroAssembler::' in lines:
182 signatures.extend(
183 get_normalized_signatures(lines, fileAnnot))
184 lines = ''
185 continue
187 # We do not aggregate any lines if we are scanning lines which are
188 # in-between a set of curly braces.
189 if braces_depth > 0:
190 continue
191 if was_braces_depth != 0:
192 line = line[line.rfind('}') + 1:]
194 # This logic is used to remove template instantiation, static
195 # variable definitions and function declaration from the next
196 # function definition.
197 last_semi_colon = line.rfind(';')
198 if last_semi_colon != -1:
199 lines = ''
200 line = line[last_semi_colon + 1:]
202 # Aggregate lines of non-braced text, which corresponds to the space
203 # where we are expecting to find function definitions.
204 lines = lines + line
206 return signatures
209 def get_macroassembler_declaration(filename):
210 style_section = False
211 lines = ''
212 signatures = []
213 with open(filename) as f:
214 for line in f:
215 if '//{{{ check_macroassembler_decl_style' in line:
216 style_section = True
217 elif '//}}} check_macroassembler_decl_style' in line:
218 style_section = False
219 if not style_section:
220 continue
222 line = re.sub(r'//.*', '', line)
223 if len(line.strip()) == 0 or 'public:' in line or 'private:' in line:
224 lines = ''
225 continue
226 lines = lines + line
228 # Continue until we have a complete declaration
229 if ';' not in lines:
230 continue
232 # Skip member declarations: which are lines ending with a
233 # semi-colon without any list of arguments.
234 if ')' not in lines:
235 lines = ''
236 continue
238 signatures.extend(get_normalized_signatures(lines))
239 lines = ''
241 return signatures
244 def append_signatures(d, sigs):
245 for s in sigs:
246 if s['sig'] not in d:
247 d[s['sig']] = []
248 d[s['sig']].append(s['arch'])
249 return d
252 def generate_file_content(signatures):
253 output = []
254 for s in sorted(signatures.keys()):
255 archs = set(sorted(signatures[s]))
256 archs -= all_unsupported_architectures_names
257 if len(archs.symmetric_difference(architecture_independent)) == 0:
258 output.append(s + ';\n')
259 if s.startswith('inline'):
260 # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
261 # functions. (Such annotation is already removed by the time
262 # this function sees the signature here.)
263 output.append(' is defined in MacroAssembler-inl.h\n')
264 else:
265 output.append(' is defined in MacroAssembler.cpp\n')
266 else:
267 if len(archs.symmetric_difference(all_architecture_names)) == 0:
268 output.append(s + ' PER_ARCH;\n')
269 elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
270 output.append(s + ' PER_SHARED_ARCH;\n')
271 else:
272 output.append(
273 s + ' DEFINED_ON(' + ', '.join(sorted(archs)) + ');\n')
274 for a in sorted(archs):
275 a = a.replace('_', '-')
276 masm = '%s/MacroAssembler-%s' % (a, a)
277 if s.startswith('inline'):
278 output.append(' is defined in %s-inl.h\n' % masm)
279 else:
280 output.append(' is defined in %s.cpp\n' % masm)
281 return output
284 def check_style():
285 # We read from the header file the signature of each function.
286 decls = dict() # type: dict(signature => ['x86', 'x64'])
288 # We infer from each file the signature of each MacroAssembler function.
289 defs = dict() # type: dict(signature => ['x86', 'x64'])
291 root_dir = os.path.join('js', 'src', 'jit')
292 for dirpath, dirnames, filenames in os.walk(root_dir):
293 for filename in filenames:
294 if 'MacroAssembler' not in filename:
295 continue
297 filepath = os.path.join(dirpath, filename).replace('\\', '/')
299 if filepath.endswith('MacroAssembler.h'):
300 decls = append_signatures(
301 decls, get_macroassembler_declaration(filepath))
302 defs = append_signatures(
303 defs, get_macroassembler_definitions(filepath))
305 if not decls or not defs:
306 raise Exception("Did not find any definitions or declarations")
308 # Compare declarations and definitions output.
309 difflines = difflib.unified_diff(generate_file_content(decls),
310 generate_file_content(defs),
311 fromfile='check_macroassembler_style.py declared syntax',
312 tofile='check_macroassembler_style.py found definitions')
313 ok = True
314 for diffline in difflines:
315 ok = False
316 print(diffline, end='')
318 return ok
321 def main():
322 ok = check_style()
324 if ok:
325 print('TEST-PASS | check_macroassembler_style.py | ok')
326 else:
327 print('TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above') # noqa: E501
329 sys.exit(0 if ok else 1)
332 if __name__ == '__main__':
333 main()