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 # ----------------------------------------------------------------------------
23 from __future__
import absolute_import
24 from __future__
import print_function
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):
46 signature
= signature
.replace("static", "")
48 signature
= signature
.replace(";", " ")
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
)
56 signature
= signature
.replace("MacroAssembler::", "")
58 # Extract list of architectures
61 archs
= [fileAnnot
["arch"]]
63 if "DEFINED_ON(" in signature
:
65 r
".*DEFINED_ON\((?P<archs>[^()]*)\).*", "\g<archs>", signature
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
)
83 # No signature annotation, the list of architectures remains unchanged.
86 # Extract inline annotation
89 inline
= fileAnnot
["inline"]
91 if "inline " in signature
:
92 signature
= re
.sub(r
"inline\s+", "", signature
)
97 inlinePrefx
= "inline "
98 signatures
= [{"arch": a
, "sig": inlinePrefx
+ signature
} for a
in archs
]
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]
118 if filename
.endswith(".cpp"):
119 filename
= filename
[: -len(".cpp")]
120 elif filename
.endswith("-inl.h"):
122 filename
= filename
[: -len("-inl.h")]
123 elif filename
.endswith(".h"):
124 # This allows the definitions block in MacroAssembler.h to be
127 filename
= filename
[: -len(".h")]
129 raise Exception("unknown file name", origFilename
)
132 for suffix
in file_suffixes
:
133 if filename
== "MacroAssembler-" + suffix
:
137 return {"inline": inline
, "arch": arch
.replace("-", "_")}
140 def get_macroassembler_definitions(filename
):
142 fileAnnot
= get_file_annotation(filename
)
146 style_section
= False
149 with
open(filename
, encoding
="utf-8") as f
:
151 if "//{{{ check_macroassembler_style" in line
:
153 raise "check_macroassembler_style section already opened."
156 elif "//}}} check_macroassembler_style" in line
:
157 style_section
= False
158 if not style_section
:
161 # Ignore preprocessor directives.
162 if line
.startswith("#"):
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.
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
))
188 # We do not aggregate any lines if we are scanning lines which are
189 # in-between a set of curly braces.
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:
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.
210 def get_macroassembler_declaration(filename
):
211 style_section
= False
214 with
open(filename
, encoding
="utf-8") as f
:
216 if "//{{{ check_macroassembler_decl_style" in line
:
218 elif "//}}} check_macroassembler_decl_style" in line
:
219 style_section
= False
220 if not style_section
:
223 # Ignore preprocessor directives.
224 if line
.startswith("#"):
227 line
= re
.sub(r
"//.*", "", line
)
228 if len(line
.strip()) == 0 or "public:" in line
or "private:" in line
:
234 # Continue until we have a complete declaration
238 # Skip member declarations: which are lines ending with a
239 # semi-colon without any list of arguments.
244 signatures
.extend(get_normalized_signatures(lines
))
250 def append_signatures(d
, sigs
):
252 if s
["sig"] not in d
:
254 d
[s
["sig"]].append(s
["arch"])
258 def generate_file_content(signatures
):
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")
271 output
.append(" is defined in MacroAssembler.cpp\n")
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")
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
)
285 output
.append(" is defined in %s.cpp\n" % masm
)
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
:
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",
321 for diffline
in difflines
:
323 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__":