# 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_unsupported_architectures_names = set([ 'mips32', 'mips64', 'mips_shared' ]) all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64' ]) all_shared_architecture_names = set([ 'x86_shared', 'arm', 'arm64' ]) reBeforeArg = "(?<=[(,\s])" reArgType = "(?P[\w\s:*&]+)" reArgName = "(?P\s\w+)" reArgDefault = "(?P(?:\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', 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[^()]*)\).*', '\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) 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')] 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])) archs -= all_unsupported_architectures_names 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(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']) 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()