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(["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):
43 signature
= signature
.replace("static", "")
45 signature
= signature
.replace(";", " ")
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
)
53 signature
= signature
.replace("MacroAssembler::", "")
55 # Extract list of architectures
58 archs
= [fileAnnot
["arch"]]
60 if "DEFINED_ON(" in signature
:
62 r
".*DEFINED_ON\((?P<archs>[^()]*)\).*", "\g<archs>", signature
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
)
80 # No signature annotation, the list of architectures remains unchanged.
83 # Extract inline annotation
86 inline
= fileAnnot
["inline"]
88 if "inline " in signature
:
89 signature
= re
.sub(r
"inline\s+", "", signature
)
94 inlinePrefx
= "inline "
95 signatures
= [{"arch": a
, "sig": inlinePrefx
+ signature
} for a
in archs
]
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]
115 if filename
.endswith(".cpp"):
116 filename
= filename
[: -len(".cpp")]
117 elif filename
.endswith("-inl.h"):
119 filename
= filename
[: -len("-inl.h")]
120 elif filename
.endswith(".h"):
121 # This allows the definitions block in MacroAssembler.h to be
124 filename
= filename
[: -len(".h")]
126 raise Exception("unknown file name", origFilename
)
129 for suffix
in file_suffixes
:
130 if filename
== "MacroAssembler-" + suffix
:
134 return {"inline": inline
, "arch": arch
.replace("-", "_")}
137 def get_macroassembler_definitions(filename
):
139 fileAnnot
= get_file_annotation(filename
)
143 style_section
= False
146 with
open(filename
, encoding
="utf-8") as f
:
148 if "//{{{ check_macroassembler_style" in line
:
150 raise "check_macroassembler_style section already opened."
153 elif "//}}} check_macroassembler_style" in line
:
154 style_section
= False
155 if not style_section
:
158 # Ignore preprocessor directives.
159 if line
.startswith("#"):
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.
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
))
185 # We do not aggregate any lines if we are scanning lines which are
186 # in-between a set of curly braces.
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:
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.
207 def get_macroassembler_declaration(filename
):
208 style_section
= False
211 with
open(filename
, encoding
="utf-8") as f
:
213 if "//{{{ check_macroassembler_decl_style" in line
:
215 elif "//}}} check_macroassembler_decl_style" in line
:
216 style_section
= False
217 if not style_section
:
220 # Ignore preprocessor directives.
221 if line
.startswith("#"):
224 line
= re
.sub(r
"//.*", "", line
)
225 if len(line
.strip()) == 0 or "public:" in line
or "private:" in line
:
231 # Continue until we have a complete declaration
235 # Skip member declarations: which are lines ending with a
236 # semi-colon without any list of arguments.
241 signatures
.extend(get_normalized_signatures(lines
))
247 def append_signatures(d
, sigs
):
249 if s
["sig"] not in d
:
251 d
[s
["sig"]].append(s
["arch"])
255 def generate_file_content(signatures
):
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")
268 output
.append(" is defined in MacroAssembler.cpp\n")
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")
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
)
282 output
.append(" is defined in %s.cpp\n" % masm
)
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
:
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",
318 for diffline
in difflines
:
320 print(diffline
, end
="")
329 print("TEST-PASS | check_macroassembler_style.py | ok")
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__":