2323from tools .toolchain_profiler import ToolchainProfiler
2424
2525import base64
26+ import glob
27+ import hashlib
2628import json
2729import logging
2830import os
@@ -500,8 +502,7 @@ def ensure_archive_index(archive_file):
500502 run_process ([shared .LLVM_RANLIB , archive_file ])
501503
502504
503- @ToolchainProfiler .profile_block ('JS symbol generation' )
504- def get_all_js_syms ():
505+ def generate_js_symbols ():
505506 # Runs the js compiler to generate a list of all symbols available in the JS
506507 # libraries. This must be done separately for each linker invokation since the
507508 # list of symbols depends on what settings are used.
@@ -516,6 +517,59 @@ def get_all_js_syms():
516517 if shared .is_c_symbol (name ):
517518 name = shared .demangle_c_symbol_name (name )
518519 library_syms .add (name )
520+ return library_syms
521+
522+
523+ @ToolchainProfiler .profile_block ('JS symbol generation' )
524+ def get_all_js_syms ():
525+ # Avoiding using the cache when generating struct info since
526+ # this step is performed while the cache is locked.
527+ if settings .BOOTSTRAPPING_STRUCT_INFO or config .FROZEN_CACHE :
528+ return generate_js_symbols ()
529+
530+ # To avoid the cost of calling generate_js_symbols each time an executable is
531+ # linked we cache symbol lists for the N most recently used configs.
532+ # We define a cache hit as when the settings and `--js-library` contents are
533+ # identical.
534+ input_files = {}
535+ # The JS library files are all absolute paths so can't conflict with this one.
536+ input_files ['settings.json' ] = json .dumps (settings .dict (), sort_keys = True , indent = 2 )
537+ for jslib in sorted (glob .glob (utils .path_from_root ('src' ) + '/library*.js' )):
538+ input_files [jslib ] = read_file (jslib )
539+ for jslib in settings .JS_LIBRARIES :
540+ if not os .path .isabs (jslib ):
541+ jslib = utils .path_from_root ('src' , jslib )
542+ input_files [jslib ] = read_file (jslib )
543+ input_data = []
544+ for name , content in input_files .items ():
545+ content_hash = hashlib .sha1 (content .encode ('utf-8' )).hexdigest ()
546+ input_data .append (f'{ name } : { content_hash } ' )
547+
548+ input_data = '\n ' .join (input_data ) + '\n '
549+ cache_filename = None
550+ num_cache_entries = 20
551+
552+ with cache .lock ('js_symbol_lists' ):
553+ oldest_timestamp = 0
554+ for i in range (num_cache_entries ):
555+ input_file = cache .get_path (f'js_symbol_list_{ i } .inputs' )
556+ list_file = cache .get_path (f'js_symbol_list_{ i } .txt' )
557+ if not os .path .exists (input_file ) or not os .path .exists (list_file ):
558+ cache_filename = list_file
559+ break
560+ timestamp = os .path .getmtime (input_file )
561+ if timestamp < oldest_timestamp or not oldest_timestamp :
562+ oldest_timestamp = timestamp
563+ cache_filename = list_file
564+ if read_file (input_file ) == input_data :
565+ # Cache hit, read the symbol list from the list_file
566+ return read_file (list_file ).splitlines ()
567+
568+ # Cache miss. Generate a new symbol list and write to the the cache
569+ library_syms = generate_js_symbols ()
570+
571+ write_file (cache_filename , '\n ' .join (library_syms ) + '\n ' )
572+ write_file (shared .replace_suffix (cache_filename , '.inputs' ), input_data )
519573
520574 return library_syms
521575
0 commit comments