diff options
Diffstat (limited to 'config/check_macroassembler_style.py')
-rw-r--r-- | config/check_macroassembler_style.py | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/config/check_macroassembler_style.py b/config/check_macroassembler_style.py new file mode 100644 index 0000000000..02a1cdfe32 --- /dev/null +++ b/config/check_macroassembler_style.py @@ -0,0 +1,339 @@ +# 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", "wasm32"]) +all_shared_architecture_names = set(["x86_shared", "arm", "arm64", "loong64", "wasm32"]) + +reBeforeArg = "(?<=[(,\s])" +reArgType = "(?P<type>[\w\s:*&<>]+)" +reArgName = "(?P<name>\s\w+)" +reArgDefault = "(?P<default>(?:\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("\g<type>", 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<archs>[^()]*)\).*", "\g<archs>", 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() |