Bug 1776056 - Switch to the tab an animation is running and make sure the animation...
[gecko.git] / config / check_macroassembler_style.py
blob02a1cdfe321281801860a14def8474c871d34306
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 import difflib
24 import os
25 import re
26 import sys
28 architecture_independent = set(["generic"])
29 all_unsupported_architectures_names = set(["mips32", "mips64", "mips_shared"])
30 all_architecture_names = set(["x86", "x64", "arm", "arm64", "loong64", "wasm32"])
31 all_shared_architecture_names = set(["x86_shared", "arm", "arm64", "loong64", "wasm32"])
33 reBeforeArg = "(?<=[(,\s])"
34 reArgType = "(?P<type>[\w\s:*&<>]+)"
35 reArgName = "(?P<name>\s\w+)"
36 reArgDefault = "(?P<default>(?:\s=(?:(?:\s[\w:]+\(\))|[^,)]+))?)"
37 reAfterArg = "(?=[,)])"
38 reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
41 def get_normalized_signatures(signature, fileAnnot=None):
42 # Remove static
43 signature = signature.replace("static", "")
44 # Remove semicolon.
45 signature = signature.replace(";", " ")
46 # Normalize spaces.
47 signature = re.sub(r"\s+", " ", signature).strip()
48 # Remove new-line induced spaces after opening braces.
49 signature = re.sub(r"\(\s+", "(", signature).strip()
50 # Match arguments, and keep only the type.
51 signature = reMatchArg.sub("\g<type>", signature)
52 # Remove class name
53 signature = signature.replace("MacroAssembler::", "")
55 # Extract list of architectures
56 archs = ["generic"]
57 if fileAnnot:
58 archs = [fileAnnot["arch"]]
60 if "DEFINED_ON(" in signature:
61 archs = re.sub(
62 r".*DEFINED_ON\((?P<archs>[^()]*)\).*", "\g<archs>", signature
63 ).split(",")
64 archs = [a.strip() for a in archs]
65 signature = re.sub(r"\s+DEFINED_ON\([^()]*\)", "", signature)
67 elif "PER_ARCH" in signature:
68 archs = all_architecture_names
69 signature = re.sub(r"\s+PER_ARCH", "", signature)
71 elif "PER_SHARED_ARCH" in signature:
72 archs = all_shared_architecture_names
73 signature = re.sub(r"\s+PER_SHARED_ARCH", "", signature)
75 elif "OOL_IN_HEADER" in signature:
76 assert archs == ["generic"]
77 signature = re.sub(r"\s+OOL_IN_HEADER", "", signature)
79 else:
80 # No signature annotation, the list of architectures remains unchanged.
81 pass
83 # Extract inline annotation
84 inline = False
85 if fileAnnot:
86 inline = fileAnnot["inline"]
88 if "inline " in signature:
89 signature = re.sub(r"inline\s+", "", signature)
90 inline = True
92 inlinePrefx = ""
93 if inline:
94 inlinePrefx = "inline "
95 signatures = [{"arch": a, "sig": inlinePrefx + signature} for a in archs]
97 return signatures
100 file_suffixes = set(
102 a.replace("_", "-")
103 for a in all_architecture_names.union(all_shared_architecture_names).union(
104 all_unsupported_architectures_names
110 def get_file_annotation(filename):
111 origFilename = filename
112 filename = filename.split("/")[-1]
114 inline = False
115 if filename.endswith(".cpp"):
116 filename = filename[: -len(".cpp")]
117 elif filename.endswith("-inl.h"):
118 inline = True
119 filename = filename[: -len("-inl.h")]
120 elif filename.endswith(".h"):
121 # This allows the definitions block in MacroAssembler.h to be
122 # style-checked.
123 inline = True
124 filename = filename[: -len(".h")]
125 else:
126 raise Exception("unknown file name", origFilename)
128 arch = "generic"
129 for suffix in file_suffixes:
130 if filename == "MacroAssembler-" + suffix:
131 arch = suffix
132 break
134 return {"inline": inline, "arch": arch.replace("-", "_")}
137 def get_macroassembler_definitions(filename):
138 try:
139 fileAnnot = get_file_annotation(filename)
140 except Exception:
141 return []
143 style_section = False
144 lines = ""
145 signatures = []
146 with open(filename, encoding="utf-8") as f:
147 for line in f:
148 if "//{{{ check_macroassembler_style" in line:
149 if style_section:
150 raise "check_macroassembler_style section already opened."
151 style_section = True
152 braces_depth = 0
153 elif "//}}} check_macroassembler_style" in line:
154 style_section = False
155 if not style_section:
156 continue
158 # Ignore preprocessor directives.
159 if line.startswith("#"):
160 continue
162 # Remove comments from the processed line.
163 line = re.sub(r"//.*", "", line)
165 # Locate and count curly braces.
166 open_curly_brace = line.find("{")
167 was_braces_depth = braces_depth
168 braces_depth = braces_depth + line.count("{") - line.count("}")
170 # Raise an error if the check_macroassembler_style macro is used
171 # across namespaces / classes scopes.
172 if braces_depth < 0:
173 raise "check_macroassembler_style annotations are not well scoped."
175 # If the current line contains an opening curly brace, check if
176 # this line combines with the previous one can be identified as a
177 # MacroAssembler function signature.
178 if open_curly_brace != -1 and was_braces_depth == 0:
179 lines = lines + line[:open_curly_brace]
180 if "MacroAssembler::" in lines:
181 signatures.extend(get_normalized_signatures(lines, fileAnnot))
182 lines = ""
183 continue
185 # We do not aggregate any lines if we are scanning lines which are
186 # in-between a set of curly braces.
187 if braces_depth > 0:
188 continue
189 if was_braces_depth != 0:
190 line = line[line.rfind("}") + 1 :]
192 # This logic is used to remove template instantiation, static
193 # variable definitions and function declaration from the next
194 # function definition.
195 last_semi_colon = line.rfind(";")
196 if last_semi_colon != -1:
197 lines = ""
198 line = line[last_semi_colon + 1 :]
200 # Aggregate lines of non-braced text, which corresponds to the space
201 # where we are expecting to find function definitions.
202 lines = lines + line
204 return signatures
207 def get_macroassembler_declaration(filename):
208 style_section = False
209 lines = ""
210 signatures = []
211 with open(filename, encoding="utf-8") as f:
212 for line in f:
213 if "//{{{ check_macroassembler_decl_style" in line:
214 style_section = True
215 elif "//}}} check_macroassembler_decl_style" in line:
216 style_section = False
217 if not style_section:
218 continue
220 # Ignore preprocessor directives.
221 if line.startswith("#"):
222 continue
224 line = re.sub(r"//.*", "", line)
225 if len(line.strip()) == 0 or "public:" in line or "private:" in line:
226 lines = ""
227 continue
229 lines = lines + line
231 # Continue until we have a complete declaration
232 if ";" not in lines:
233 continue
235 # Skip member declarations: which are lines ending with a
236 # semi-colon without any list of arguments.
237 if ")" not in lines:
238 lines = ""
239 continue
241 signatures.extend(get_normalized_signatures(lines))
242 lines = ""
244 return signatures
247 def append_signatures(d, sigs):
248 for s in sigs:
249 if s["sig"] not in d:
250 d[s["sig"]] = []
251 d[s["sig"]].append(s["arch"])
252 return d
255 def generate_file_content(signatures):
256 output = []
257 for s in sorted(signatures.keys()):
258 archs = set(sorted(signatures[s]))
259 archs -= all_unsupported_architectures_names
260 if len(archs.symmetric_difference(architecture_independent)) == 0:
261 output.append(s + ";\n")
262 if s.startswith("inline"):
263 # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
264 # functions. (Such annotation is already removed by the time
265 # this function sees the signature here.)
266 output.append(" is defined in MacroAssembler-inl.h\n")
267 else:
268 output.append(" is defined in MacroAssembler.cpp\n")
269 else:
270 if len(archs.symmetric_difference(all_architecture_names)) == 0:
271 output.append(s + " PER_ARCH;\n")
272 elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
273 output.append(s + " PER_SHARED_ARCH;\n")
274 else:
275 output.append(s + " DEFINED_ON(" + ", ".join(sorted(archs)) + ");\n")
276 for a in sorted(archs):
277 a = a.replace("_", "-")
278 masm = "%s/MacroAssembler-%s" % (a, a)
279 if s.startswith("inline"):
280 output.append(" is defined in %s-inl.h\n" % masm)
281 else:
282 output.append(" is defined in %s.cpp\n" % masm)
283 return output
286 def check_style():
287 # We read from the header file the signature of each function.
288 decls = dict() # type: dict(signature => ['x86', 'x64'])
290 # We infer from each file the signature of each MacroAssembler function.
291 defs = dict() # type: dict(signature => ['x86', 'x64'])
293 root_dir = os.path.join("js", "src", "jit")
294 for dirpath, dirnames, filenames in os.walk(root_dir):
295 for filename in filenames:
296 if "MacroAssembler" not in filename:
297 continue
299 filepath = os.path.join(dirpath, filename).replace("\\", "/")
301 if filepath.endswith("MacroAssembler.h"):
302 decls = append_signatures(
303 decls, get_macroassembler_declaration(filepath)
305 defs = append_signatures(defs, get_macroassembler_definitions(filepath))
307 if not decls or not defs:
308 raise Exception("Did not find any definitions or declarations")
310 # Compare declarations and definitions output.
311 difflines = difflib.unified_diff(
312 generate_file_content(decls),
313 generate_file_content(defs),
314 fromfile="check_macroassembler_style.py declared syntax",
315 tofile="check_macroassembler_style.py found definitions",
317 ok = True
318 for diffline in difflines:
319 ok = False
320 print(diffline, end="")
322 return ok
325 def main():
326 ok = check_style()
328 if ok:
329 print("TEST-PASS | check_macroassembler_style.py | ok")
330 else:
331 print(
332 "TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above" # noqa: E501
335 sys.exit(0 if ok else 1)
338 if __name__ == "__main__":
339 main()