# vim: set ts=8 sts=4 et sw=4 tw=99:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# ----------------------------------------------------------------------------
# This script checks that SpiderMonkey MacroAssembler methods are properly
# annotated.
#
# The MacroAssembler has one interface for all platforms, but it might have one
# definition per platform. The code of the MacroAssembler use a macro to
# annotate the method declarations, in order to delete the function if it is not
# present on the current platform, and also to locate the files in which the
# methods are defined.
#
# This script scans the MacroAssembler.h header, for method declarations.
# It also scans MacroAssembler-/arch/.cpp, MacroAssembler-/arch/-inl.h, and
# MacroAssembler-inl.h for method definitions. The result of both scans are
# uniformized, and compared, to determine if the MacroAssembler.h header as
# proper methods annotations.
# ----------------------------------------------------------------------------
import difflib
import os
import re
import sys
architecture_independent = set([
"generic"])
all_unsupported_architectures_names = set([
"mips32",
"mips64",
"mips_shared"])
all_architecture_names = set(
[
"x86",
"x64",
"arm",
"arm64",
"loong64",
"riscv64",
"wasm32"]
)
all_shared_architecture_names = set(
[
"x86_shared",
"arm",
"arm64",
"loong64",
"riscv64",
"wasm32"]
)
reBeforeArg = r
"(?<=[(,\s])"
reArgType = r
"(?P[\w\s:*&<>]+)"
reArgName = r
"(?P\s\w+)"
reArgDefault = r
"(?P(?:\s=(?:(?:\s[\w:]+\(\))|[^,)]+))?)"
reAfterArg =
"(?=[,)])"
reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
def get_normalized_signatures(signature, fileAnnot=
None):
# Remove static
signature = signature.replace(
"static",
"")
# Remove semicolon.
signature = signature.replace(
";",
" ")
# Normalize spaces.
signature = re.sub(r
"\s+",
" ", signature).strip()
# Remove new-line induced spaces after opening braces.
signature = re.sub(r
"\(\s+",
"(", signature).strip()
# Match arguments, and keep only the type.
signature = reMatchArg.sub(r
"\g", signature)
# Remove class name
signature = signature.replace(
"MacroAssembler::",
"")
# Extract list of architectures
archs = [
"generic"]
if fileAnnot:
archs = [fileAnnot[
"arch"]]
if "DEFINED_ON(" in signature:
archs = re.sub(
r
".*DEFINED_ON\((?P[^()]*)\).*", r
"\g", signature
).split(
",")
archs = [a.strip()
for a
in archs]
signature = re.sub(r
"\s+DEFINED_ON\([^()]*\)",
"", signature)
elif "PER_ARCH" in signature:
archs = all_architecture_names
signature = re.sub(r
"\s+PER_ARCH",
"", signature)
elif "PER_SHARED_ARCH" in signature:
archs = all_shared_architecture_names
signature = re.sub(r
"\s+PER_SHARED_ARCH",
"", signature)
elif "OOL_IN_HEADER" in signature:
assert archs == [
"generic"]
signature = re.sub(r
"\s+OOL_IN_HEADER",
"", signature)
else:
# No signature annotation, the list of architectures remains unchanged.
pass
# Extract inline annotation
inline =
False
if fileAnnot:
inline = fileAnnot[
"inline"]
if "inline " in signature:
signature = re.sub(r
"inline\s+",
"", signature)
inline =
True
inlinePrefx =
""
if inline:
inlinePrefx =
"inline "
signatures = [{
"arch": a,
"sig": inlinePrefx + signature}
for a
in archs]
return signatures
file_suffixes = set(
[
a.replace(
"_",
"-")
for a
in all_architecture_names.union(all_shared_architecture_names).union(
all_unsupported_architectures_names
)
]
)
def get_file_annotation(filename):
origFilename = filename
filename = filename.split(
"/")[-1]
inline =
False
if filename.endswith(
".cpp"):
filename = filename[: -len(
".cpp")]
elif filename.endswith(
"-inl.h"):
inline =
True
filename = filename[: -len(
"-inl.h")]
elif filename.endswith(
".h"):
# This allows the definitions block in MacroAssembler.h to be
# style-checked.
inline =
True
filename = filename[: -len(
".h")]
else:
raise Exception(
"unknown file name", origFilename)
arch =
"generic"
for suffix
in file_suffixes:
if filename ==
"MacroAssembler-" + suffix:
arch = suffix
break
return {
"inline": inline,
"arch": arch.replace(
"-",
"_")}
def get_macroassembler_definitions(filename):
try:
fileAnnot = get_file_annotation(filename)
except Exception:
return []
style_section =
False
lines =
""
signatures = []
with open(filename, encoding=
"utf-8")
as f:
for line
in f:
if "//{{{ check_macroassembler_style" in line:
if style_section:
raise "check_macroassembler_style section already opened."
style_section =
True
braces_depth = 0
elif "//}}} check_macroassembler_style" in line:
style_section =
False
if not style_section:
continue
# Ignore preprocessor directives.
if line.startswith(
"#"):
continue
# Remove comments from the processed line.
line = re.sub(r
"//.*",
"", line)
# Locate and count curly braces.
open_curly_brace = line.find(
"{")
was_braces_depth = braces_depth
braces_depth = braces_depth + line.count(
"{") - line.count(
"}")
# Raise an error if the check_macroassembler_style macro is used
# across namespaces / classes scopes.
if braces_depth < 0:
raise "check_macroassembler_style annotations are not well scoped."
# If the current line contains an opening curly brace, check if
# this line combines with the previous one can be identified as a
# MacroAssembler function signature.
if open_curly_brace != -1
and was_braces_depth == 0:
lines = lines + line[:open_curly_brace]
if "MacroAssembler::" in lines:
signatures.extend(get_normalized_signatures(lines, fileAnnot))
lines =
""
continue
# We do not aggregate any lines if we are scanning lines which are
# in-between a set of curly braces.
if braces_depth > 0:
continue
if was_braces_depth != 0:
line = line[line.rfind(
"}") + 1 :]
# This logic is used to remove template instantiation, static
# variable definitions and function declaration from the next
# function definition.
last_semi_colon = line.rfind(
";")
if last_semi_colon != -1:
lines =
""
line = line[last_semi_colon + 1 :]
# Aggregate lines of non-braced text, which corresponds to the space
# where we are expecting to find function definitions.
lines = lines + line
return signatures
def get_macroassembler_declaration(filename):
style_section =
False
lines =
""
signatures = []
with open(filename, encoding=
"utf-8")
as f:
for line
in f:
if "//{{{ check_macroassembler_decl_style" in line:
style_section =
True
elif "//}}} check_macroassembler_decl_style" in line:
style_section =
False
if not style_section:
continue
# Ignore preprocessor directives.
if line.startswith(
"#"):
continue
line = re.sub(r
"//.*",
"", line)
if len(line.strip()) == 0
or "public:" in line
or "private:" in line:
lines =
""
continue
lines = lines + line
# Continue until we have a complete declaration
if ";" not in lines:
continue
# Skip member declarations: which are lines ending with a
# semi-colon without any list of arguments.
if ")" not in lines:
lines =
""
continue
signatures.extend(get_normalized_signatures(lines))
lines =
""
return signatures
def append_signatures(d, sigs):
for s
in sigs:
if s[
"sig"]
not in d:
d[s[
"sig"]] = []
d[s[
"sig"]].append(s[
"arch"])
return d
def generate_file_content(signatures):
output = []
for s
in sorted(signatures.keys()):
archs = set(sorted(signatures[s]))
archs -= all_unsupported_architectures_names
if len(archs.symmetric_difference(architecture_independent)) == 0:
output.append(s +
";\n")
if s.startswith(
"inline"):
# TODO, bug 1432600: This is mistaken for OOL_IN_HEADER
# functions. (Such annotation is already removed by the time
# this function sees the signature here.)
output.append(
" is defined in MacroAssembler-inl.h\n")
else:
output.append(
" is defined in MacroAssembler.cpp\n")
else:
if len(archs.symmetric_difference(all_architecture_names)) == 0:
output.append(s +
" PER_ARCH;\n")
elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
output.append(s +
" PER_SHARED_ARCH;\n")
else:
output.append(s +
" DEFINED_ON(" +
", ".join(sorted(archs)) +
");\n")
for a
in sorted(archs):
a = a.replace(
"_",
"-")
masm =
"%s/MacroAssembler-%s" % (a, a)
if s.startswith(
"inline"):
output.append(
" is defined in %s-inl.h\n" % masm)
else:
output.append(
" is defined in %s.cpp\n" % masm)
return output
def check_style():
# We read from the header file the signature of each function.
decls = dict()
# type: dict(signature => ['x86', 'x64'])
# We infer from each file the signature of each MacroAssembler function.
defs = dict()
# type: dict(signature => ['x86', 'x64'])
root_dir = os.path.join(
"js",
"src",
"jit")
for dirpath, dirnames, filenames
in os.walk(root_dir):
for filename
in filenames:
if "MacroAssembler" not in filename:
continue
filepath = os.path.join(dirpath, filename).replace(
"\\",
"/")
if filepath.endswith(
"MacroAssembler.h"):
decls = append_signatures(
decls, get_macroassembler_declaration(filepath)
)
defs = append_signatures(defs, get_macroassembler_definitions(filepath))
if not decls
or not defs:
raise Exception(
"Did not find any definitions or declarations")
# Compare declarations and definitions output.
difflines = difflib.unified_diff(
generate_file_content(decls),
generate_file_content(defs),
fromfile=
"check_macroassembler_style.py declared syntax",
tofile=
"check_macroassembler_style.py found definitions",
)
ok =
True
for diffline
in difflines:
ok =
False
print(diffline, end=
"")
return ok
def main():
ok = check_style()
if ok:
print(
"TEST-PASS | check_macroassembler_style.py | ok")
else:
print(
"TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above" # noqa: E501
)
sys.exit(0
if ok
else 1)
if __name__ ==
"__main__":
main()