diff options
Diffstat (limited to 'config/check_macroassembler_style.py')
-rw-r--r-- | config/check_macroassembler_style.py | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/config/check_macroassembler_style.py b/config/check_macroassembler_style.py new file mode 100644 index 000000000..00be610db --- /dev/null +++ b/config/check_macroassembler_style.py @@ -0,0 +1,283 @@ +# 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. +#---------------------------------------------------------------------------- + +from __future__ import print_function + +import difflib +import os +import re +import sys + +from mozversioncontrol import get_repository_from_env + + +architecture_independent = set([ 'generic' ]) +all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64', 'mips32', 'mips64' ]) +all_shared_architecture_names = set([ 'x86_shared', 'mips_shared', 'arm', 'arm64' ]) + +reBeforeArg = "(?<=[(,\s])" +reArgType = "(?P<type>[\w\s:*&]+)" +reArgName = "(?P<name>\s\w+)" +reArgDefault = "(?P<default>(?:\s=[^,)]+)?)" +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() + # 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) + + 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) +]) +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')] + 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: + return [] + + style_section = False + code_section = False + lines = '' + signatures = [] + with open(filename) as f: + for line in f: + if '//{{{ check_macroassembler_style' in line: + style_section = True + elif '//}}} check_macroassembler_style' in line: + style_section = False + if not style_section: + continue + + line = re.sub(r'//.*', '', line) + if line.startswith('{'): + if 'MacroAssembler::' in lines: + signatures.extend(get_normalized_signatures(lines, fileAnnot)) + code_section = True + continue + if line.startswith('}'): + code_section = False + lines = '' + continue + if code_section: + continue + + if len(line.strip()) == 0: + lines = '' + continue + lines = lines + line + # Continue until we have a complete declaration + if '{' not in lines: + continue + # Skip variable declarations + if ')' not in lines: + lines = '' + continue + + return signatures + +def get_macroassembler_declaration(filename): + style_section = False + lines = '' + signatures = [] + with open(filename) as f: + for line in f: + if '//{{{ check_macroassembler_style' in line: + style_section = True + elif '//}}} check_macroassembler_style' in line: + style_section = False + if not style_section: + continue + + line = re.sub(r'//.*', '', line) + if len(line.strip()) == 0: + lines = '' + continue + lines = lines + line + # Continue until we have a complete declaration + if ';' not in lines: + continue + # Skip variable declarations + 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])) + if len(archs.symmetric_difference(architecture_independent)) == 0: + output.append(s + ';\n') + if s.startswith('inline'): + 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(archs) + ');\n') + for a in 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']) + + repo = get_repository_from_env() + + # Select the appropriate files. + for filename in repo.get_files_in_working_directory(): + if not filename.startswith('js/src/jit/'): + continue + if 'MacroAssembler' not in filename: + continue + + filename = os.path.join(repo.path, filename) + + if filename.endswith('MacroAssembler.h'): + decls = append_signatures(decls, get_macroassembler_declaration(filename)) + else: + defs = append_signatures(defs, get_macroassembler_definitions(filename)) + + # 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') + + sys.exit(0 if ok else 1) + + +if __name__ == '__main__': + main() |