aboutsummaryrefslogtreecommitdiff
path: root/tools/shared.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/shared.py')
-rw-r--r--tools/shared.py109
1 files changed, 74 insertions, 35 deletions
diff --git a/tools/shared.py b/tools/shared.py
index 0282fbb1..6f97737e 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -295,6 +295,10 @@ CANONICAL_TEMP_DIR = os.path.join(TEMP_DIR, 'emscripten_temp')
EMSCRIPTEN_TEMP_DIR = None
DEBUG = os.environ.get('EMCC_DEBUG')
+if DEBUG == "0":
+ DEBUG = None
+DEBUG_CACHE = DEBUG and "cache" in DEBUG
+
if DEBUG:
try:
EMSCRIPTEN_TEMP_DIR = CANONICAL_TEMP_DIR
@@ -554,7 +558,7 @@ class Settings:
ret = []
for key, value in Settings.__dict__.iteritems():
if key == key.upper(): # this is a hack. all of our settings are ALL_CAPS, python internals are not
- jsoned = json.dumps(value)
+ jsoned = json.dumps(value, sort_keys=True)
ret += ['-s', key + '=' + jsoned]
return ret
@@ -563,6 +567,7 @@ class Settings:
if opt_level >= 1:
Settings.ASSERTIONS = 0
Settings.DISABLE_EXCEPTION_CATCHING = 1
+ Settings.EMIT_GENERATED_FUNCTIONS = 1
if opt_level >= 2:
Settings.RELOOP = 1
if opt_level >= 3:
@@ -663,6 +668,9 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
def make(args, stdout=None, stderr=None, env=None):
if env is None:
env = Building.get_building_env()
+ if not args:
+ print >> sys.stderr, 'Error: Executable to run not specified.'
+ sys.exit(1)
#args += ['VERBOSE=1']
try:
Popen(args, stdout=stdout, stderr=stderr, env=env).communicate()
@@ -746,12 +754,16 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
resolved_symbols = set()
temp_dirs = []
files = map(os.path.abspath, files)
+ has_ar = False
+ for f in files:
+ has_ar = has_ar or Building.is_ar(f)
for f in files:
if not Building.is_ar(f):
if Building.is_bitcode(f):
- new_symbols = Building.llvm_nm(f)
- resolved_symbols = resolved_symbols.union(new_symbols.defs)
- unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs)
+ if has_ar:
+ new_symbols = Building.llvm_nm(f)
+ resolved_symbols = resolved_symbols.union(new_symbols.defs)
+ unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs)
actual_files.append(f)
else:
# Extract object files from ar archives, and link according to gnu ld semantics
@@ -804,7 +816,26 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
# Finish link
actual_files = unique_ordered(actual_files) # tolerate people trying to link a.so a.so etc.
if DEBUG: print >>sys.stderr, 'emcc: llvm-linking:', actual_files
- output = Popen([LLVM_LINK] + actual_files + ['-o', target], stdout=PIPE).communicate()[0]
+
+ # check for too-long command line
+ link_cmd = [LLVM_LINK] + actual_files + ['-o', target]
+ # 8k is a bit of an arbitrary limit, but a reasonable one
+ # for max command line size before we use a respose file
+ response_file = None
+ if len(' '.join(link_cmd)) > 8192:
+ if DEBUG: print >>sys.stderr, 'using response file for llvm-link'
+ [response_fd, response_file] = mkstemp(suffix='.response', dir=TEMP_DIR)
+ response_fh = os.fdopen(response_fd, 'w')
+ for arg in actual_files:
+ response_fh.write(arg + "\n")
+ response_fh.close()
+ link_cmd = [LLVM_LINK, "@" + response_file, '-o', target]
+
+ output = Popen(link_cmd, stdout=PIPE).communicate()[0]
+
+ if response_file:
+ os.unlink(response_file)
+
assert os.path.exists(target) and (output is None or 'Could not open input file' not in output), 'Linking error: ' + output
for temp_dir in temp_dirs:
try_delete(temp_dir)
@@ -826,6 +857,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
def llvm_opt(filename, opts):
if type(opts) is int:
opts = Building.pick_llvm_opts(opts)
+ #opts += ['-debug-pass=Arguments']
if DEBUG: print >> sys.stderr, 'emcc: LLVM opts:', opts
output = Popen([LLVM_OPT, filename] + opts + ['-o=' + filename + '.opt.bc'], stdout=PIPE).communicate()[0]
assert os.path.exists(filename + '.opt.bc'), 'Failed to run llvm optimizations: ' + output
@@ -862,8 +894,14 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
assert os.path.exists(output_filename), 'Could not create bc file: ' + output
return output_filename
+ nm_cache = {} # cache results of nm - it can be slow to run
+
@staticmethod
def llvm_nm(filename, stdout=PIPE, stderr=None):
+ if filename in Building.nm_cache:
+ #if DEBUG: print >> sys.stderr, 'loading nm results for %s from cache' % filename
+ return Building.nm_cache[filename]
+
# LLVM binary ==> list of symbols
output = Popen([LLVM_NM, filename], stdout=stdout, stderr=stderr).communicate()[0]
class ret:
@@ -884,6 +922,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
ret.defs = set(ret.defs)
ret.undefs = set(ret.undefs)
ret.commons = set(ret.commons)
+ Building.nm_cache[filename] = ret
return ret
@staticmethod
@@ -1084,24 +1123,6 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
@staticmethod
def is_bitcode(filename):
- # checks if a file contains LLVM bitcode
- # if the file doesn't exist or doesn't have valid symbols, it isn't bitcode
- try:
- defs = Building.llvm_nm(filename, stderr=PIPE)
- # If no symbols found, it might just be an empty bitcode file, try to dis it
- if len(defs.defs) + len(defs.undefs) + len(defs.commons) == 0:
- # llvm-nm 3.0 has a bug when reading symbols from ar files
- # so try to see if we're dealing with an ar file, in which
- # case we should try to dis it.
- if not Building.is_ar(filename):
- test_ll = os.path.join(EMSCRIPTEN_TEMP_DIR, 'test.ll')
- Building.llvm_dis(filename, test_ll)
- assert os.path.exists(test_ll)
- try_delete(test_ll)
- except Exception, e:
- if DEBUG: print >> sys.stderr, 'shared.Building.is_bitcode failed to test whether file \'%s\' is a llvm bitcode file! Failed on exception: %s' % (filename, e)
- return False
-
# look for magic signature
b = open(filename, 'r').read(4)
if b[0] == 'B' and b[1] == 'C':
@@ -1177,6 +1198,10 @@ class Cache:
except:
pass
try_delete(RELOOPER)
+ try:
+ open(Cache.dirname + '__last_clear', 'w').write('last clear: ' + time.asctime() + '\n')
+ except:
+ print >> sys.stderr, 'failed to save last clear time'
# Request a cached file. If it isn't in the cache, it will be created with
# the given creator function
@@ -1219,29 +1244,30 @@ class JCache:
# Returns a cached value, if it exists. Make sure the full key matches
@staticmethod
def get(shortkey, keys):
- #if DEBUG: print >> sys.stderr, 'jcache get?', shortkey
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache get?', shortkey
cachename = JCache.get_cachename(shortkey)
if not os.path.exists(cachename):
- #if DEBUG: print >> sys.stderr, 'jcache none at all'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache none at all'
return
data = cPickle.Unpickler(open(cachename, 'rb')).load()
if len(data) != 2:
- #if DEBUG: print >> sys.stderr, 'jcache error in get'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache error in get'
return
oldkeys = data[0]
if len(oldkeys) != len(keys):
- #if DEBUG: print >> sys.stderr, 'jcache collision (a)'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache collision (a)'
return
for i in range(len(oldkeys)):
if oldkeys[i] != keys[i]:
- #if DEBUG: print >> sys.stderr, 'jcache collision (b)'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache collision (b)'
return
- #if DEBUG: print >> sys.stderr, 'jcache win'
+ if DEBUG_CACHE: print >> sys.stderr, 'jcache win'
return data[1]
# Sets the cached value for a key (from get_key)
@staticmethod
def set(shortkey, keys, value):
+ if DEBUG_CACHE: print >> sys.stderr, 'save to cache', shortkey
cachename = JCache.get_cachename(shortkey)
cPickle.Pickler(open(cachename, 'wb')).dump([keys, value])
#if DEBUG:
@@ -1265,28 +1291,36 @@ class JCache:
if os.path.exists(chunking_file):
try:
previous_mapping = cPickle.Unpickler(open(chunking_file, 'rb')).load() # maps a function identifier to the chunk number it will be in
- except:
- pass
+ if DEBUG: print >> sys.stderr, 'jscache previous mapping of size %d loaded from %s' % (len(previous_mapping), chunking_file)
+ except Exception, e:
+ print >> sys.stderr, 'Failed to load and unpickle previous chunking file at %s: ' % chunking_file, e
+ else:
+ print >> sys.stderr, 'Previous chunking file not found at %s' % chunking_file
chunks = []
if previous_mapping:
# initialize with previous chunking
news = []
for func in funcs:
ident, data = func
+ assert ident, 'need names for jcache chunking'
if not ident in previous_mapping:
news.append(func)
else:
n = previous_mapping[ident]
while n >= len(chunks): chunks.append([])
chunks[n].append(func)
+ if DEBUG: print >> sys.stderr, 'jscache not in previous chunking', len(news)
# add news and adjust for new sizes
spilled = news
- for chunk in chunks:
+ for i in range(len(chunks)):
+ chunk = chunks[i]
size = sum([len(func[1]) for func in chunk])
- while size > 1.5*chunk_size and len(chunk) > 0:
+ #if DEBUG: print >> sys.stderr, 'need spilling?', i, size, len(chunk), 'vs', chunk_size, 1.5*chunk_size
+ while size > 1.5*chunk_size and len(chunk) > 1:
spill = chunk.pop()
spilled.append(spill)
size -= len(spill[1])
+ #if DEBUG: print >> sys.stderr, 'jscache new + spilled', len(spilled)
for chunk in chunks:
size = sum([len(func[1]) for func in chunk])
while size < 0.66*chunk_size and len(spilled) > 0:
@@ -1295,6 +1329,7 @@ class JCache:
size += len(spill[1])
chunks = filter(lambda chunk: len(chunk) > 0, chunks) # might have empty ones, eliminate them
funcs = spilled # we will allocate these into chunks as if they were normal inputs
+ #if DEBUG: print >> sys.stderr, 'leftover spills', len(spilled)
# initialize reasonably, the rest of the funcs we need to split out
curr = []
total_size = 0
@@ -1320,15 +1355,19 @@ class JCache:
for i in range(len(chunks)):
chunk = chunks[i]
for ident, data in chunk:
+ assert ident not in new_mapping, 'cannot have duplicate names in jcache chunking'
new_mapping[ident] = i
cPickle.Pickler(open(chunking_file, 'wb')).dump(new_mapping)
+ if DEBUG: print >> sys.stderr, 'jscache mapping of size %d saved to %s' % (len(new_mapping), chunking_file)
#if DEBUG:
+ # for i in range(len(chunks)):
+ # chunk = chunks[i]
+ # print >> sys.stderr, 'final chunk', i, len(chunk)
+ # print >> sys.stderr, 'new mapping:', new_mapping
# if previous_mapping:
# for ident in set(previous_mapping.keys() + new_mapping.keys()):
# if previous_mapping.get(ident) != new_mapping.get(ident):
# print >> sys.stderr, 'mapping inconsistency', ident, previous_mapping.get(ident), new_mapping.get(ident)
- # for key, value in new_mapping.iteritems():
- # print >> sys.stderr, 'mapping:', key, value
return [''.join([func[1] for func in chunk]) for chunk in chunks] # remove function names
class JS: