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
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 # ----------------------------------------------------------------------------
28 architecture_independent
= set(["generic"])
29 all_unsupported_architectures_names
= set(["mips32", "mips64", "mips_shared"])
30 all_architecture_names
= set(
31 ["x86", "x64", "arm", "arm64", "loong64", "riscv64", "wasm32"]
33 all_shared_architecture_names
= set(
34 ["x86_shared", "arm", "arm64", "loong64", "riscv64", "wasm32"]
37 reBeforeArg
= "(?<=[(,\s])"
38 reArgType
= "(?P<type>[\w\s:*&<>]+)"
39 reArgName
= "(?P<name>\s\w+)"
40 reArgDefault
= "(?P<default>(?:\s=(?:(?:\s[\w:]+\(\))|[^,)]+))?)"
41 reAfterArg
= "(?=[,)])"
42 reMatchArg
= re
.compile(reBeforeArg
+ reArgType
+ reArgName
+ reArgDefault
+ reAfterArg
)
45 def get_normalized_signatures(signature
, fileAnnot
=None):
47 signature
= signature
.replace("static", "")
49 signature
= signature
.replace(";", " ")
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
)
57 signature
= signature
.replace("MacroAssembler::", "")
59 # Extract list of architectures
62 archs
= [fileAnnot
["arch"]]
64 if "DEFINED_ON(" in signature
:
66 r
".*DEFINED_ON\((?P<archs>[^()]*)\).*", "\g<archs>", signature
68 archs
= [a
.strip() for a
in archs
]
69 signature
= re
.sub(r
"\s+DEFINED_ON\([^()]*\)", "", signature
)
71 elif "PER_ARCH" in signature
:
72 archs
= all_architecture_names
73 signature
= re
.sub(r
"\s+PER_ARCH", "", signature
)
75 elif "PER_SHARED_ARCH" in signature
:
76 archs
= all_shared_architecture_names
77 signature
= re
.sub(r
"\s+PER_SHARED_ARCH", "", signature
)
79 elif "OOL_IN_HEADER" in signature
:
80 assert archs
== ["generic"]
81 signature
= re
.sub(r
"\s+OOL_IN_HEADER", "", signature
)
84 # No signature annotation, the list of architectures remains unchanged.
87 # Extract inline annotation
90 inline
= fileAnnot
["inline"]
92 if "inline " in signature
:
93 signature
= re
.sub(r
"inline\s+", "", signature
)
98 inlinePrefx
= "inline "
99 signatures
= [{"arch": a
, "sig": inlinePrefx
+ signature
} for a
in archs
]
107 for a
in all_architecture_names
.union(all_shared_architecture_names
).union(
108 all_unsupported_architectures_names
114 def get_file_annotation(filename
):
115 origFilename
= filename
116 filename
= filename
.split("/")[-1]
119 if filename
.endswith(".cpp"):
120 filename
= filename
[: -len(".cpp")]
121 elif filename
.endswith("-inl.h"):
123 filename
= filename
[: -len("-inl.h")]
124 elif filename
.endswith(".h"):
125 # This allows the definitions block in MacroAssembler.h to be
128 filename
= filename
[: -len(".h")]
130 raise Exception("unknown file name", origFilename
)
133 for suffix
in file_suffixes
:
134 if filename
== "MacroAssembler-" + suffix
:
138 return {"inline": inline
, "arch": arch
.replace("-", "_")}
141 def get_macroassembler_definitions(filename
):
143 fileAnnot
= get_file_annotation(filename
)
147 style_section
= False
150 with
open(filename
, encoding
="utf-8") as f
:
152 if "//{{{ check_macroassembler_style" in line
:
154 raise "check_macroassembler_style section already opened."
157 elif "//}}} check_macroassembler_style" in line
:
158 style_section
= False
159 if not style_section
:
162 # Ignore preprocessor directives.
163 if line
.startswith("#"):
166 # Remove comments from the processed line.
167 line
= re
.sub(r
"//.*", "", line
)
169 # Locate and count curly braces.
170 open_curly_brace
= line
.find("{")
171 was_braces_depth
= braces_depth
172 braces_depth
= braces_depth
+ line
.count("{") - line
.count("}")
174 # Raise an error if the check_macroassembler_style macro is used
175 # across namespaces / classes scopes.
177 raise "check_macroassembler_style annotations are not well scoped."
179 # If the current line contains an opening curly brace, check if
180 # this line combines with the previous one can be identified as a
181 # MacroAssembler function signature.
182 if open_curly_brace
!= -1 and was_braces_depth
== 0:
183 lines
= lines
+ line
[:open_curly_brace
]
184 if "MacroAssembler::" in lines
:
185 signatures
.extend(get_normalized_signatures(lines
, fileAnnot
))
189 # We do not aggregate any lines if we are scanning lines which are
190 # in-between a set of curly braces.
193 if was_braces_depth
!= 0:
194 line
= line
[line
.rfind("}") + 1 :]
196 # This logic is used to remove template instantiation, static
197 # variable definitions and function declaration from the next
198 # function definition.
199 last_semi_colon
= line
.rfind(";")
200 if last_semi_colon
!= -1:
202 line
= line
[last_semi_colon
+ 1 :]
204 # Aggregate lines of non-braced text, which corresponds to the space
205 # where we are expecting to find function definitions.
211 def get_macroassembler_declaration(filename
):
212 style_section
= False
215 with
open(filename
, encoding
="utf-8") as f
:
217 if "//{{{ check_macroassembler_decl_style" in line
:
219 elif "//}}} check_macroassembler_decl_style" in line
:
220 style_section
= False
221 if not style_section
:
224 # Ignore preprocessor directives.
225 if line
.startswith("#"):
228 line
= re
.sub(r
"//.*", "", line
)
229 if len(line
.strip()) == 0 or "public:" in line
or "private:" in line
:
235 # Continue until we have a complete declaration
239 # Skip member declarations: which are lines ending with a
240 # semi-colon without any list of arguments.
245 signatures
.extend(get_normalized_signatures(lines
))
251 def append_signatures(d
, sigs
):
253 if s
["sig"] not in d
:
255 d
[s
["sig"]].append(s
["arch"])
259 def generate_file_content(signatures
):
261 for s
in sorted(signatures
.keys()):
262 archs
= set(sorted(signatures
[s
]))
263 archs
-= all_unsupported_architectures_names
264 if len(archs
.symmetric_difference(architecture_independent
)) == 0:
265 output
.append(s
+ ";\n")
266 if s
.startswith("inline"):
267 # TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
268 # functions. (Such annotation is already removed by the time
269 # this function sees the signature here.)
270 output
.append(" is defined in MacroAssembler-inl.h\n")
272 output
.append(" is defined in MacroAssembler.cpp\n")
274 if len(archs
.symmetric_difference(all_architecture_names
)) == 0:
275 output
.append(s
+ " PER_ARCH;\n")
276 elif len(archs
.symmetric_difference(all_shared_architecture_names
)) == 0:
277 output
.append(s
+ " PER_SHARED_ARCH;\n")
279 output
.append(s
+ " DEFINED_ON(" + ", ".join(sorted(archs
)) + ");\n")
280 for a
in sorted(archs
):
281 a
= a
.replace("_", "-")
282 masm
= "%s/MacroAssembler-%s" % (a
, a
)
283 if s
.startswith("inline"):
284 output
.append(" is defined in %s-inl.h\n" % masm
)
286 output
.append(" is defined in %s.cpp\n" % masm
)
291 # We read from the header file the signature of each function.
292 decls
= dict() # type: dict(signature => ['x86', 'x64'])
294 # We infer from each file the signature of each MacroAssembler function.
295 defs
= dict() # type: dict(signature => ['x86', 'x64'])
297 root_dir
= os
.path
.join("js", "src", "jit")
298 for dirpath
, dirnames
, filenames
in os
.walk(root_dir
):
299 for filename
in filenames
:
300 if "MacroAssembler" not in filename
:
303 filepath
= os
.path
.join(dirpath
, filename
).replace("\\", "/")
305 if filepath
.endswith("MacroAssembler.h"):
306 decls
= append_signatures(
307 decls
, get_macroassembler_declaration(filepath
)
309 defs
= append_signatures(defs
, get_macroassembler_definitions(filepath
))
311 if not decls
or not defs
:
312 raise Exception("Did not find any definitions or declarations")
314 # Compare declarations and definitions output.
315 difflines
= difflib
.unified_diff(
316 generate_file_content(decls
),
317 generate_file_content(defs
),
318 fromfile
="check_macroassembler_style.py declared syntax",
319 tofile
="check_macroassembler_style.py found definitions",
322 for diffline
in difflines
:
324 print(diffline
, end
="")
332 print("TEST-PASS | check_macroassembler_style.py | ok")
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__":