Bug 1769321 [wpt PR 34062] - Update wpt metadata, a=testonly
[gecko.git] / config / check_macroassembler_style.py
blob20375e9e262e1bd4e1e27b71f6971a932122142c
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", "loong64"])
34 all_shared_architecture_names = set(["x86_shared", "arm", "arm64", "loong64"])
36 reBeforeArg = "(?<=[(,\s])"
37 reArgType = "(?P<type>[\w\s:*&<>]+)"
38 reArgName = "(?P<name>\s\w+)"
39 reArgDefault = "(?P<default>(?:\s=(?:(?:\s[\w:]+\(\))|[^,)]+))?)"
40 reAfterArg = "(?=[,)])"
41 reMatchArg = re.compile(reBeforeArg + reArgType + 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
66 ).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 = [{"arch": a, "sig": inlinePrefx + signature} for a in archs]
100 return signatures
103 file_suffixes = set(
105 a.replace("_", "-")
106 for a in all_architecture_names.union(all_shared_architecture_names).union(
107 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 {"inline": inline, "arch": arch.replace("-", "_")}
140 def get_macroassembler_definitions(filename):
141 try:
142 fileAnnot = get_file_annotation(filename)
143 except Exception:
144 return []
146 style_section = False
147 lines = ""
148 signatures = []
149 with open(filename, encoding="utf-8") as f:
150 for line in f:
151 if "//{{{ check_macroassembler_style" in line:
152 if style_section:
153 raise "check_macroassembler_style section already opened."
154 style_section = True
155 braces_depth = 0
156 elif "//}}} check_macroassembler_style" in line:
157 style_section = False
158 if not style_section:
159 continue
161 # Ignore preprocessor directives.
162 if line.startswith("#"):
163 continue
165 # Remove comments from the processed line.
166 line = re.sub(r"//.*", "", line)
168 # Locate and count curly braces.
169 open_curly_brace = line.find("{")
170 was_braces_depth = braces_depth
171 braces_depth = braces_depth + line.count("{") - line.count("}")
173 # Raise an error if the check_macroassembler_style macro is used
174 # across namespaces / classes scopes.
175 if braces_depth < 0:
176 raise "check_macroassembler_style annotations are not well scoped."
178 # If the current line contains an opening curly brace, check if
179 # this line combines with the previous one can be identified as a
180 # MacroAssembler function signature.
181 if open_curly_brace != -1 and was_braces_depth == 0:
182 lines = lines + line[:open_curly_brace]
183 if "MacroAssembler::" in lines:
184 signatures.extend(get_normalized_signatures(lines, fileAnnot))
185 lines = ""
186 continue
188 # We do not aggregate any lines if we are scanning lines which are
189 # in-between a set of curly braces.
190 if braces_depth > 0:
191 continue
192 if was_braces_depth != 0:
193 line = line[line.rfind("}") + 1 :]
195 # This logic is used to remove template instantiation, static
196 # variable definitions and function declaration from the next
197 # function definition.
198 last_semi_colon = line.rfind(";")
199 if last_semi_colon != -1:
200 lines = ""
201 line = line[last_semi_colon + 1 :]
203 # Aggregate lines of non-braced text, which corresponds to the space
204 # where we are expecting to find function definitions.
205 lines = lines + line
207 return signatures
210 def get_macroassembler_declaration(filename):
211 style_section = False
212 lines = ""
213 signatures = []
214 with open(filename, encoding="utf-8") as f:
215 for line in f:
216 if "//{{{ check_macroassembler_decl_style" in line:
217 style_section = True
218 elif "//}}} check_macroassembler_decl_style" in line:
219 style_section = False
220 if not style_section:
221 continue
223 # Ignore preprocessor directives.
224 if line.startswith("#"):
225 continue
227 line = re.sub(r"//.*", "", line)
228 if len(line.strip()) == 0 or "public:" in line or "private:" in line:
229 lines = ""
230 continue
232 lines = lines + line
234 # Continue until we have a complete declaration
235 if ";" not in lines:
236 continue
238 # Skip member declarations: which are lines ending with a
239 # semi-colon without any list of arguments.
240 if ")" not in lines:
241 lines = ""
242 continue
244 signatures.extend(get_normalized_signatures(lines))
245 lines = ""
247 return signatures
250 def append_signatures(d, sigs):
251 for s in sigs:
252 if s["sig"] not in d:
253 d[s["sig"]] = []
254 d[s["sig"]].append(s["arch"])
255 return d
258 def generate_file_content(signatures):
259 output = []
260 for s in sorted(signatures.keys()):
261 archs = set(sorted(signatures[s]))
262 archs -= all_unsupported_architectures_names
263 if len(archs.symmetric_difference(architecture_independent)) == 0:
264 output.append(s + ";\n")
265 if s.startswith("inline"):
266 # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
267 # functions. (Such annotation is already removed by the time
268 # this function sees the signature here.)
269 output.append(" is defined in MacroAssembler-inl.h\n")
270 else:
271 output.append(" is defined in MacroAssembler.cpp\n")
272 else:
273 if len(archs.symmetric_difference(all_architecture_names)) == 0:
274 output.append(s + " PER_ARCH;\n")
275 elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
276 output.append(s + " PER_SHARED_ARCH;\n")
277 else:
278 output.append(s + " DEFINED_ON(" + ", ".join(sorted(archs)) + ");\n")
279 for a in sorted(archs):
280 a = a.replace("_", "-")
281 masm = "%s/MacroAssembler-%s" % (a, a)
282 if s.startswith("inline"):
283 output.append(" is defined in %s-inl.h\n" % masm)
284 else:
285 output.append(" is defined in %s.cpp\n" % masm)
286 return output
289 def check_style():
290 # We read from the header file the signature of each function.
291 decls = dict() # type: dict(signature => ['x86', 'x64'])
293 # We infer from each file the signature of each MacroAssembler function.
294 defs = dict() # type: dict(signature => ['x86', 'x64'])
296 root_dir = os.path.join("js", "src", "jit")
297 for dirpath, dirnames, filenames in os.walk(root_dir):
298 for filename in filenames:
299 if "MacroAssembler" not in filename:
300 continue
302 filepath = os.path.join(dirpath, filename).replace("\\", "/")
304 if filepath.endswith("MacroAssembler.h"):
305 decls = append_signatures(
306 decls, get_macroassembler_declaration(filepath)
308 defs = append_signatures(defs, get_macroassembler_definitions(filepath))
310 if not decls or not defs:
311 raise Exception("Did not find any definitions or declarations")
313 # Compare declarations and definitions output.
314 difflines = difflib.unified_diff(
315 generate_file_content(decls),
316 generate_file_content(defs),
317 fromfile="check_macroassembler_style.py declared syntax",
318 tofile="check_macroassembler_style.py found definitions",
320 ok = True
321 for diffline in difflines:
322 ok = False
323 print(diffline, end="")
325 return ok
328 def main():
329 ok = check_style()
331 if ok:
332 print("TEST-PASS | check_macroassembler_style.py | ok")
333 else:
334 print(
335 "TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above" # noqa: E501
338 sys.exit(0 if ok else 1)
341 if __name__ == "__main__":
342 main()