regenByteCode.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/usr/bin/env python
  2. #-------------------------------------------------------------------------------------------------------
  3. # Copyright (C) Microsoft. All rights reserved.
  4. # Copyright (c) ChakraCore Project Contributors. All rights reserved.
  5. # Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  6. #-------------------------------------------------------------------------------------------------------
  7. # Regenerate embedded bytecode headers. This script must be run when:
  8. # a) Ahy changes have been made to the javascript in lib/Runtime/Library/InJavascript
  9. # b) Any changes have been made to the bytecode emission process including edits to JnDirectFields.h
  10. # NOTEs:
  11. # 1. this script relies on forcing 64bit CC builds to produce 32bit bytecode - this could break due to future
  12. # changes to CC. If this facility breaks it will need to be fixed or this script will need to be updated to
  13. # use 32 bit builds as well as 64 bit
  14. # 2. Options:
  15. # --skip-build Don't build CC just generate bytecode with already built binaries
  16. # --jit only generate bytecode for CC with jit (default is to do both jit and noJit)
  17. # --noJit only generate bytecode for CC with noJit (default is to do both jit and noJit)
  18. # --verify throw an error if bytecode changes detected - intended for use in CI
  19. # --binary=path provide a path to a binary to use, requires either --jit or --noJit to be set
  20. # --x86 specify that provided binary is an x86 build, will generate x86 bytecode only - requires pre-built binary
  21. # 3. Python version - this script is designed to run on both Python 2.x and 3.x
  22. import subprocess
  23. import sys
  24. import uuid
  25. import os
  26. # Parse provided parameters
  27. verification_mode = False
  28. skip_build = False
  29. noJit = True
  30. jit = True
  31. x86 = False
  32. overide_binary = ""
  33. for param in sys.argv:
  34. if param == '--skip-build':
  35. skip_build = True
  36. elif param == '--verify':
  37. # welcome message for CI
  38. print('######### Verifying generated bytecode #########')
  39. verification_mode = True
  40. elif param == '--noJit':
  41. jit = False
  42. elif param == '--jit':
  43. noJit = False
  44. elif param == '--x86':
  45. x86 = True
  46. elif param[:9] == '--binary=':
  47. overide_binary = param[9:]
  48. skip_build = True
  49. # Detect OS
  50. windows = False
  51. if os.name == 'posix':
  52. print('OS is Linux or macOS')
  53. else:
  54. print('OS is Windows')
  55. windows = True
  56. # If Jit or noJit flag has been give print a message also if both flags given revert to default behaviour
  57. if jit == False:
  58. if noJit == True:
  59. print('Regenerating bytecode for no-jit build only')
  60. else:
  61. noJit = True
  62. jit = True
  63. elif noJit == False:
  64. print('Regenerating bytecode for jit build only')
  65. if x86 == True:
  66. if overide_binary == "":
  67. print('x86 build can only be used when pre-built and provided with the --binary command line parameter')
  68. sys.exit(1)
  69. # Adjust path for running from different locations
  70. base_path = os.path.abspath(os.path.dirname(__file__))
  71. # Compile ChakraCore both noJit and Jit variants (unless disabled by args)
  72. def run_sub(message, commands, error):
  73. print(message)
  74. sub = subprocess.Popen(commands, shell=windows)
  75. sub.wait()
  76. if sub.returncode != 0:
  77. print(error)
  78. sys.exit(1)
  79. if skip_build == False:
  80. # build for linux or macOS with build.sh script - could update to use cmake directly but this works for now
  81. if windows == False:
  82. if noJit == True:
  83. run_sub('Compiling ChakraCore with no Jit',
  84. [base_path + '/../build.sh', '--no-jit', '--debug', '--static', '--target-path=' + base_path + '/../out/noJit', '-j=8'],
  85. 'No Jit build failed - aborting bytecode generation')
  86. if jit == True:
  87. run_sub('Compiling ChakraCore with Jit',
  88. [base_path + '/../build.sh', '--debug', '--static', '--target-path=' + base_path + '/../out/Jit', '-j=8'],
  89. 'Jit build failed - aborting bytecode generation')
  90. # build for windows
  91. else:
  92. if noJit == True:
  93. run_sub('Compiling ChakraCore with no Jit',
  94. ['msbuild', '/P:platform=x64', '/P:configuration=debug', '/M', '/p:BuildJIT=false', base_path+ '/../Build/Chakra.Core.sln'],
  95. 'No Jit build failed - aborting bytecode generation')
  96. if jit == True:
  97. run_sub('Compiling ChakraCore with Jit',
  98. ['msbuild', '/P:platform=x64', '/P:configuration=debug', '/M', base_path + '/../Build/Chakra.Core.sln'],
  99. 'No Jit build failed - aborting bytecode generation')
  100. # Generate the new bytecode checking for changes to each file
  101. # First define variables and methods that will be used then the calls take place below
  102. changes_detected = False
  103. # this header text will be placed at the top of the generated files
  104. header_text = '''//-------------------------------------------------------------------------------------------------------
  105. // Copyright (C) Microsoft. All rights reserved.
  106. // Copyright (c) ChakraCore Project Contributors. All rights reserved.
  107. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  108. //-------------------------------------------------------------------------------------------------------
  109. // Generated Bytecode Header, this file was created by tools/regenByteCode.py
  110. // This file contains:
  111. // a) bytecode for Intl library methods implemented in javascript and
  112. // b) bytecode for other Js library methods, JsBuiltIns, implemented in javascript
  113. #define JsBuiltIns(VALUE)'''
  114. def append_bytecode(header, command, in_path, file_name, error):
  115. command_with_file = command[:]
  116. command_with_file.append(in_path + file_name)
  117. header.write('//Bytecode generated from ' + file_name + '\nconst char Library_Bytecode_')
  118. header.write(file_name[:-3])
  119. header.flush()
  120. job = subprocess.Popen(command_with_file, stdout=header)
  121. job.wait()
  122. if job.returncode != 0:
  123. print(error)
  124. print('Command line: ')
  125. print(*command_with_file)
  126. sys.exit(1)
  127. # Load file and ensure line endings are '\n' if on windows
  128. def load_file(path, mode):
  129. global windows
  130. if windows == True:
  131. if sys.version_info[0] < 3:
  132. return open(path, mode + 'b')
  133. else:
  134. return open(path, mode, newline='\n')
  135. else:
  136. return open(path, mode)
  137. # Regenerate the bytecode
  138. def bytecode_job(out_path, command, in_path, error):
  139. if verification_mode == True:
  140. print('Checking bytecode in file ' + out_path)
  141. else:
  142. print('Generating bytecode in file ' + out_path)
  143. old_version = ''
  144. global changes_detected
  145. if changes_detected == False:
  146. old_version = load_file(out_path, 'r').read()
  147. header = load_file(out_path, 'w')
  148. header.write(header_text)
  149. files = os.listdir(in_path)
  150. files.sort()
  151. filtered_files = []
  152. for file_name in files:
  153. if file_name.endswith('.js'):
  154. if file_name != 'Intl.js':
  155. without_extension = file_name[:-3]
  156. parts = without_extension.split('_')
  157. header.write(' \\\nVALUE(' + parts[0] + ', ' + parts[1] + ', ' + parts[0] + parts[1].title() + ')')
  158. filtered_files.append(file_name)
  159. header.write('\n\nnamespace js\n{\n\n#ifdef ENABLE_JS_BUILTINS\n\n')
  160. # generate bytecode for JsBuiltins
  161. command_with_chakra_lib = command[:]
  162. command_with_chakra_lib.append('-LdChakraLib')
  163. command_with_chakra_lib.append('-JsBuiltIn')
  164. for file_name in filtered_files:
  165. append_bytecode(header, command_with_chakra_lib, in_path, file_name, error)
  166. # generate bytecode for Intl
  167. command.append('-Intl')
  168. header.write('#endif\n\n#ifdef ENABLE_INTL_OBJECT\n\n')
  169. append_bytecode(header, command, in_path, 'Intl.js', error)
  170. header.write('#endif\n\n}\n')
  171. header.close()
  172. if changes_detected == False:
  173. new_version = load_file(out_path, 'r').read()
  174. if new_version != old_version:
  175. changes_detected = True
  176. if verification_mode == True:
  177. new_lines = new_version.split('\n')
  178. old_lines = old_version.split('\n')
  179. max_lines = min(len(new_lines), len(old_lines))
  180. for i in range(0, max_lines):
  181. if new_lines[i] != old_lines[i]:
  182. print('Error found - output on line ' + str(i + 1) + ' is:')
  183. print(new_lines[i].replace('\r', '\\r'))
  184. print('Expected output was:')
  185. print(old_lines[i].replace('\r', '\\r'))
  186. break
  187. # set paths for binaries - default paths based on build seteps above (different for windows to macOS and linux)
  188. # OR overridden path provided on command line
  189. noJitpath = base_path + "/../out/noJit/debug/ch"
  190. jitPath = base_path + "/../out/jit/debug/ch"
  191. if overide_binary != "":
  192. noJitpath = overide_binary
  193. jitPath = overide_binary
  194. if jit == True and noJit == True:
  195. print("Cannot use override binary option without specifying either jit or noJit")
  196. sys.exit(1)
  197. elif windows == True:
  198. noJitpath = base_path + '/../Build/VcBuild.NoJIT/bin/x64_debug/ch.exe'
  199. jitPath = base_path + '/../Build/VcBuild/bin/x64_debug/ch.exe'
  200. # Call the functions above to generate the bytecode
  201. if noJit == True:
  202. commands = [noJitpath, '-GenerateLibraryByteCodeHeader']
  203. if x86 == False:
  204. bytecode_job(base_path + '/../lib/Runtime/Library/InJavascript/JsBuiltIn.nojit.bc.64b.h',
  205. commands, base_path + '/../lib/Runtime/Library/InJavascript/',
  206. 'Failed to generate noJit 64bit Bytecode')
  207. commands.append('-Force32BitByteCode')
  208. bytecode_job(base_path + '/../lib/Runtime/Library/InJavascript/JsBuiltIn.nojit.bc.32b.h',
  209. commands, base_path + '/../lib/Runtime/Library/InJavascript/',
  210. 'Failed to generate noJit 32bit JsBuiltin Bytecode')
  211. if jit == True:
  212. commands = [jitPath, '-GenerateLibraryByteCodeHeader']
  213. if x86 == False:
  214. bytecode_job(base_path + '/../lib/Runtime/Library/InJavascript/JsBuiltIn.bc.64b.h',
  215. commands, base_path + '/../lib/Runtime/Library/InJavascript/',
  216. 'Failed to generate 64bit JsBuiltin Bytecode')
  217. commands.append('-Force32BitByteCode')
  218. bytecode_job(base_path + '/../lib/Runtime/Library/InJavascript/JsBuiltIn.bc.32b.h',
  219. commands, base_path + '/../lib/Runtime/Library/InJavascript/',
  220. 'Failed to generate 32bit JsBuiltin Bytecode')
  221. # Bytecode regeneration complete - assess changes, report result AND if appropriate generate a new GUID
  222. if changes_detected == True:
  223. if verification_mode == True:
  224. print('Bytecode changes detected - the generated bytecode files are not up to date please run tools/regenByteCode.py\n')
  225. sys.exit(1)
  226. if jit == False or noJit == False:
  227. print("Bytecode updated for one variant only - ensure you re-run for both variants before submitting code")
  228. else:
  229. print('Generating new GUID for new bytecode')
  230. guid_header = load_file(base_path + '/../lib/Runtime/Bytecode/ByteCodeCacheReleaseFileVersion.h', 'w')
  231. guid = str(uuid.uuid4())
  232. output_str = '''//-------------------------------------------------------------------------------------------------------
  233. // Copyright (C) Microsoft. All rights reserved.
  234. // Copyright (c) ChakraCore Project Contributors. All rights reserved.
  235. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  236. //-------------------------------------------------------------------------------------------------------
  237. // NOTE: If there is a merge conflict the correct fix is to make a new GUID.
  238. // This file was generated with tools/regenByteCode.py
  239. // {%s}
  240. const GUID byteCodeCacheReleaseFileVersion =
  241. { 0x%s, 0x%s, 0x%s, {0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s } };
  242. ''' % (guid,
  243. guid[:8], guid[9:13], guid[14:18], guid[19:21], guid[21:23], guid[24:26],
  244. guid[26:28], guid[28:30], guid[30:32], guid[32:34], guid[-2:])
  245. guid_header.write(output_str)
  246. print('Bytecode successfully regenerated. Please rebuild ChakraCore to incorporate it.')
  247. else:
  248. if verification_mode == True:
  249. print('Bytecode is up to date\n')
  250. else:
  251. print('Bytecode update was not required')