check_macroassembler_style.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. #----------------------------------------------------------------------------
  5. # This script checks that SpiderMonkey MacroAssembler methods are properly
  6. # annotated.
  7. #
  8. # The MacroAssembler has one interface for all platforms, but it might have one
  9. # definition per platform. The code of the MacroAssembler use a macro to
  10. # annotate the method declarations, in order to delete the function if it is not
  11. # present on the current platform, and also to locate the files in which the
  12. # methods are defined.
  13. #
  14. # This script scans the MacroAssembler.h header, for method declarations.
  15. # It also scans MacroAssembler-/arch/.cpp, MacroAssembler-/arch/-inl.h, and
  16. # MacroAssembler-inl.h for method definitions. The result of both scans are
  17. # uniformized, and compared, to determine if the MacroAssembler.h header as
  18. # proper methods annotations.
  19. #----------------------------------------------------------------------------
  20. from __future__ import print_function
  21. import difflib
  22. import os
  23. import re
  24. import sys
  25. from mozversioncontrol import get_repository_from_env
  26. architecture_independent = set([ 'generic' ])
  27. all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64', 'mips32', 'mips64' ])
  28. all_shared_architecture_names = set([ 'x86_shared', 'mips_shared', 'arm', 'arm64' ])
  29. reBeforeArg = "(?<=[(,\s])"
  30. reArgType = "(?P<type>[\w\s:*&]+)"
  31. reArgName = "(?P<name>\s\w+)"
  32. reArgDefault = "(?P<default>(?:\s=[^,)]+)?)"
  33. reAfterArg = "(?=[,)])"
  34. reMatchArg = re.compile(reBeforeArg + reArgType + reArgName + reArgDefault + reAfterArg)
  35. def get_normalized_signatures(signature, fileAnnot = None):
  36. # Remove static
  37. signature = signature.replace('static', '')
  38. # Remove semicolon.
  39. signature = signature.replace(';', ' ')
  40. # Normalize spaces.
  41. signature = re.sub(r'\s+', ' ', signature).strip()
  42. # Match arguments, and keep only the type.
  43. signature = reMatchArg.sub('\g<type>', signature)
  44. # Remove class name
  45. signature = signature.replace('MacroAssembler::', '')
  46. # Extract list of architectures
  47. archs = ['generic']
  48. if fileAnnot:
  49. archs = [fileAnnot['arch']]
  50. if 'DEFINED_ON(' in signature:
  51. archs = re.sub(r'.*DEFINED_ON\((?P<archs>[^()]*)\).*', '\g<archs>', signature).split(',')
  52. archs = [a.strip() for a in archs]
  53. signature = re.sub(r'\s+DEFINED_ON\([^()]*\)', '', signature)
  54. elif 'PER_ARCH' in signature:
  55. archs = all_architecture_names
  56. signature = re.sub(r'\s+PER_ARCH', '', signature)
  57. elif 'PER_SHARED_ARCH' in signature:
  58. archs = all_shared_architecture_names
  59. signature = re.sub(r'\s+PER_SHARED_ARCH', '', signature)
  60. else:
  61. # No signature annotation, the list of architectures remains unchanged.
  62. pass
  63. # Extract inline annotation
  64. inline = False
  65. if fileAnnot:
  66. inline = fileAnnot['inline']
  67. if 'inline ' in signature:
  68. signature = re.sub(r'inline\s+', '', signature)
  69. inline = True
  70. inlinePrefx = ''
  71. if inline:
  72. inlinePrefx = 'inline '
  73. signatures = [
  74. { 'arch': a, 'sig': inlinePrefx + signature }
  75. for a in archs
  76. ]
  77. return signatures
  78. file_suffixes = set([
  79. a.replace('_', '-') for a in
  80. all_architecture_names.union(all_shared_architecture_names)
  81. ])
  82. def get_file_annotation(filename):
  83. origFilename = filename
  84. filename = filename.split('/')[-1]
  85. inline = False
  86. if filename.endswith('.cpp'):
  87. filename = filename[:-len('.cpp')]
  88. elif filename.endswith('-inl.h'):
  89. inline = True
  90. filename = filename[:-len('-inl.h')]
  91. else:
  92. raise Exception('unknown file name', origFilename)
  93. arch = 'generic'
  94. for suffix in file_suffixes:
  95. if filename == 'MacroAssembler-' + suffix:
  96. arch = suffix
  97. break
  98. return {
  99. 'inline': inline,
  100. 'arch': arch.replace('-', '_')
  101. }
  102. def get_macroassembler_definitions(filename):
  103. try:
  104. fileAnnot = get_file_annotation(filename)
  105. except:
  106. return []
  107. style_section = False
  108. code_section = False
  109. lines = ''
  110. signatures = []
  111. with open(filename) as f:
  112. for line in f:
  113. if '//{{{ check_macroassembler_style' in line:
  114. style_section = True
  115. elif '//}}} check_macroassembler_style' in line:
  116. style_section = False
  117. if not style_section:
  118. continue
  119. line = re.sub(r'//.*', '', line)
  120. if line.startswith('{'):
  121. if 'MacroAssembler::' in lines:
  122. signatures.extend(get_normalized_signatures(lines, fileAnnot))
  123. code_section = True
  124. continue
  125. if line.startswith('}'):
  126. code_section = False
  127. lines = ''
  128. continue
  129. if code_section:
  130. continue
  131. if len(line.strip()) == 0:
  132. lines = ''
  133. continue
  134. lines = lines + line
  135. # Continue until we have a complete declaration
  136. if '{' not in lines:
  137. continue
  138. # Skip variable declarations
  139. if ')' not in lines:
  140. lines = ''
  141. continue
  142. return signatures
  143. def get_macroassembler_declaration(filename):
  144. style_section = False
  145. lines = ''
  146. signatures = []
  147. with open(filename) as f:
  148. for line in f:
  149. if '//{{{ check_macroassembler_style' in line:
  150. style_section = True
  151. elif '//}}} check_macroassembler_style' in line:
  152. style_section = False
  153. if not style_section:
  154. continue
  155. line = re.sub(r'//.*', '', line)
  156. if len(line.strip()) == 0:
  157. lines = ''
  158. continue
  159. lines = lines + line
  160. # Continue until we have a complete declaration
  161. if ';' not in lines:
  162. continue
  163. # Skip variable declarations
  164. if ')' not in lines:
  165. lines = ''
  166. continue
  167. signatures.extend(get_normalized_signatures(lines))
  168. lines = ''
  169. return signatures
  170. def append_signatures(d, sigs):
  171. for s in sigs:
  172. if s['sig'] not in d:
  173. d[s['sig']] = []
  174. d[s['sig']].append(s['arch']);
  175. return d
  176. def generate_file_content(signatures):
  177. output = []
  178. for s in sorted(signatures.keys()):
  179. archs = set(sorted(signatures[s]))
  180. if len(archs.symmetric_difference(architecture_independent)) == 0:
  181. output.append(s + ';\n')
  182. if s.startswith('inline'):
  183. output.append(' is defined in MacroAssembler-inl.h\n')
  184. else:
  185. output.append(' is defined in MacroAssembler.cpp\n')
  186. else:
  187. if len(archs.symmetric_difference(all_architecture_names)) == 0:
  188. output.append(s + ' PER_ARCH;\n')
  189. elif len(archs.symmetric_difference(all_shared_architecture_names)) == 0:
  190. output.append(s + ' PER_SHARED_ARCH;\n')
  191. else:
  192. output.append(s + ' DEFINED_ON(' + ', '.join(archs) + ');\n')
  193. for a in archs:
  194. a = a.replace('_', '-')
  195. masm = '%s/MacroAssembler-%s' % (a, a)
  196. if s.startswith('inline'):
  197. output.append(' is defined in %s-inl.h\n' % masm)
  198. else:
  199. output.append(' is defined in %s.cpp\n' % masm)
  200. return output
  201. def check_style():
  202. # We read from the header file the signature of each function.
  203. decls = dict() # type: dict(signature => ['x86', 'x64'])
  204. # We infer from each file the signature of each MacroAssembler function.
  205. defs = dict() # type: dict(signature => ['x86', 'x64'])
  206. repo = get_repository_from_env()
  207. # Select the appropriate files.
  208. for filename in repo.get_files_in_working_directory():
  209. if not filename.startswith('js/src/jit/'):
  210. continue
  211. if 'MacroAssembler' not in filename:
  212. continue
  213. filename = os.path.join(repo.path, filename)
  214. if filename.endswith('MacroAssembler.h'):
  215. decls = append_signatures(decls, get_macroassembler_declaration(filename))
  216. else:
  217. defs = append_signatures(defs, get_macroassembler_definitions(filename))
  218. # Compare declarations and definitions output.
  219. difflines = difflib.unified_diff(generate_file_content(decls),
  220. generate_file_content(defs),
  221. fromfile='check_macroassembler_style.py declared syntax',
  222. tofile='check_macroassembler_style.py found definitions')
  223. ok = True
  224. for diffline in difflines:
  225. ok = False
  226. print(diffline, end='')
  227. return ok
  228. def main():
  229. ok = check_style()
  230. if ok:
  231. print('TEST-PASS | check_macroassembler_style.py | ok')
  232. else:
  233. print('TEST-UNEXPECTED-FAIL | check_macroassembler_style.py | actual output does not match expected output; diff is above')
  234. sys.exit(0 if ok else 1)
  235. if __name__ == '__main__':
  236. main()