680 строки
27 KiB
Python
680 строки
27 KiB
Python
import shutil, time, os, sys, json, tempfile
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
|
|
__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
|
def path_from_root(*pathelems):
|
|
return os.path.join(__rootpath__, *pathelems)
|
|
|
|
# Config file
|
|
|
|
CONFIG_FILE = os.path.expanduser('~/.emscripten')
|
|
if not os.path.exists(CONFIG_FILE):
|
|
shutil.copy(path_from_root('settings.py'), CONFIG_FILE)
|
|
print >> sys.stderr, '''
|
|
==============================================================================
|
|
Welcome to Emscripten!
|
|
|
|
This is the first time any of the Emscripten tools has been run.
|
|
|
|
A settings file has been copied to ~/.emscripten, at absolute path: %s
|
|
|
|
Please edit that file and change the paths to fit your system. Specifically,
|
|
make sure LLVM_ROOT and NODE_JS are correct.
|
|
|
|
This command will now exit. When you are done editing those paths, re-run it.
|
|
==============================================================================
|
|
''' % CONFIG_FILE
|
|
sys.exit(0)
|
|
try:
|
|
exec(open(CONFIG_FILE, 'r').read())
|
|
except Exception, e:
|
|
print >> sys.stderr, 'Error in evaluating ~/.emscripten (at %s): %s' % (CONFIG_FILE, str(e))
|
|
sys.exit(1)
|
|
|
|
# Check that basic stuff we need (a JS engine to compile, Node.js, and Clang and LLVM)
|
|
# exists.
|
|
# The test runner always does this check (through |force|). emcc does this less frequently,
|
|
# only when ~/.emscripten_sanity does not exist or is older than ~/.emscripten (so,
|
|
# we re-check sanity when the settings are changed)
|
|
def check_sanity(force=False):
|
|
try:
|
|
if not force:
|
|
settings_mtime = os.stat(CONFIG_FILE).st_mtime
|
|
sanity_file = CONFIG_FILE + '_sanity'
|
|
try:
|
|
sanity_mtime = os.stat(sanity_file).st_mtime
|
|
if sanity_mtime > settings_mtime:
|
|
return # sanity has been checked
|
|
except:
|
|
pass
|
|
|
|
print >> sys.stderr, '(Emscripten: Running sanity checks)'
|
|
|
|
if not check_engine(COMPILER_ENGINE):
|
|
print >> sys.stderr, 'FATAL: The JavaScript shell used for compiling (%s) does not seem to work, check the paths in ~/.emscripten' % COMPILER_ENGINE
|
|
sys.exit(0)
|
|
|
|
if NODE_JS != COMPILER_ENGINE:
|
|
if not check_engine(NODE_JS):
|
|
print >> sys.stderr, 'FATAL: Node.js (%s) does not seem to work, check the paths in ~/.emscripten' % NODE_JS
|
|
sys.exit(0)
|
|
|
|
for cmd in [CLANG, LLVM_DIS]:
|
|
if not os.path.exists(cmd) and not os.path.exists(cmd + '.exe'): # .exe extension required for Windows
|
|
print >> sys.stderr, 'FATAL: Cannot find %s, check the paths in ~/.emscripten' % cmd
|
|
sys.exit(0)
|
|
|
|
if not os.path.exists(CLOSURE_COMPILER):
|
|
print >> sys.stderr, 'WARNING: Closure compiler (%s) does not exist, check the paths in ~/.emscripten. -O2 and above will fail' % CLOSURE_COMPILER
|
|
|
|
# Sanity check passed!
|
|
|
|
if not force:
|
|
# Only create/update this file if the sanity check succeeded, i.e., we got here
|
|
f = open(sanity_file, 'w')
|
|
f.write('certified\n')
|
|
f.close()
|
|
|
|
except Exception, e:
|
|
# Any error here is not worth failing on
|
|
print 'WARNING: sanity check failed to run', e
|
|
|
|
# Tools/paths
|
|
|
|
CLANG_CC=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang'))
|
|
CLANG_CPP=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang++'))
|
|
CLANG=CLANG_CPP
|
|
LLVM_LINK=os.path.join(LLVM_ROOT, 'llvm-link')
|
|
LLVM_LD=os.path.join(LLVM_ROOT, 'llvm-ld')
|
|
LLVM_OPT=os.path.expanduser(os.path.join(LLVM_ROOT, 'opt'))
|
|
LLVM_AS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-as'))
|
|
LLVM_DIS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-dis'))
|
|
LLVM_NM=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-nm'))
|
|
LLVM_DIS_OPTS = ['-show-annotations'] # For LLVM 2.8+. For 2.7, you may need to do just []
|
|
LLVM_INTERPRETER=os.path.expanduser(os.path.join(LLVM_ROOT, 'lli'))
|
|
LLVM_COMPILER=os.path.expanduser(os.path.join(LLVM_ROOT, 'llc'))
|
|
COFFEESCRIPT = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee')
|
|
|
|
EMSCRIPTEN = path_from_root('emscripten.py')
|
|
DEMANGLER = path_from_root('third_party', 'demangler.py')
|
|
NAMESPACER = path_from_root('tools', 'namespacer.py')
|
|
EMCC = path_from_root('emcc')
|
|
EMXX = path_from_root('em++')
|
|
EMAR = path_from_root('emar')
|
|
EMLD = path_from_root('emld')
|
|
EMRANLIB = path_from_root('emranlib')
|
|
EMLIBTOOL = path_from_root('emlibtool')
|
|
EMMAKEN = path_from_root('tools', 'emmaken.py')
|
|
AUTODEBUGGER = path_from_root('tools', 'autodebugger.py')
|
|
DFE = path_from_root('tools', 'dead_function_eliminator.py')
|
|
BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py')
|
|
EXEC_LLVM = path_from_root('tools', 'exec_llvm.py')
|
|
VARIABLE_ELIMINATOR = path_from_root('tools', 'eliminator', 'eliminator.coffee')
|
|
JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js')
|
|
|
|
# Temp dir
|
|
|
|
try:
|
|
EMSCRIPTEN_TEMP_DIR = os.path.join(TEMP_DIR, 'emscripten_temp')
|
|
if not os.path.exists(EMSCRIPTEN_TEMP_DIR):
|
|
try:
|
|
os.makedirs(EMSCRIPTEN_TEMP_DIR)
|
|
except Exception, e:
|
|
print >> sys.stderr, 'Warning: Could not create temp dir (%s): %s' % (EMSCRIPTEN_TEMP_DIR, str(e))
|
|
except:
|
|
EMSCRIPTEN_TEMP_DIR = tempfile.mkdtemp(prefix='emscripten_temp_')
|
|
print >> sys.stderr, 'Warning: TEMP_DIR not defined in ~/.emscripten, using %s' % EMSCRIPTEN_TEMP_DIR
|
|
|
|
# ~/.emscripten stuff
|
|
|
|
try:
|
|
JS_ENGINES
|
|
except:
|
|
try:
|
|
JS_ENGINES = [JS_ENGINE]
|
|
except Exception, e:
|
|
print 'ERROR: ~/.emscripten does not seem to have JS_ENGINES or JS_ENGINE set up'
|
|
raise
|
|
|
|
# Additional compiler options
|
|
|
|
try:
|
|
COMPILER_OPTS # Can be set in ~/.emscripten, optionally
|
|
except:
|
|
COMPILER_OPTS = []
|
|
# Force a simple, standard target as much as possible: target 32-bit linux, and disable various flags that hint at other platforms
|
|
COMPILER_OPTS = COMPILER_OPTS + ['-m32', '-U__i386__', '-U__x86_64__', '-U__i386', '-U__x86_64', '-U__SSE__', '-U__SSE2__', '-U__MMX__',
|
|
'-UX87_DOUBLE_ROUNDING', '-UHAVE_GCC_ASM_FOR_X87', '-DEMSCRIPTEN', '-U__STRICT_ANSI__', '-U__CYGWIN__',
|
|
'-D__STDC__', '-Xclang', '-triple=i386-pc-linux-gnu']
|
|
|
|
|
|
USE_EMSDK = not os.environ.get('EMMAKEN_NO_SDK')
|
|
|
|
if USE_EMSDK:
|
|
# Disable system C and C++ include directories, and add our own (using -idirafter so they are last, like system dirs, which
|
|
# allows projects to override them)
|
|
EMSDK_OPTS = ['-nostdinc', '-nostdinc++', '-Xclang', '-nobuiltininc', '-Xclang', '-nostdinc++', '-Xclang', '-nostdsysteminc',
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include'),
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include', 'bsd'), # posix stuff
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include', 'libc'),
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include', 'libcxx'),
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include', 'gfx'),
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include', 'net'),
|
|
'-Xclang', '-isystem' + path_from_root('system', 'include', 'SDL'),
|
|
] + [
|
|
'-U__APPLE__'
|
|
]
|
|
COMPILER_OPTS += EMSDK_OPTS
|
|
else:
|
|
EMSDK_OPTS = []
|
|
|
|
# Engine tweaks
|
|
|
|
#if 'strict' not in str(SPIDERMONKEY_ENGINE): # XXX temporarily disable strict mode until we sort out some stuff
|
|
# SPIDERMONKEY_ENGINE += ['-e', "options('strict')"] # Strict mode in SpiderMonkey. With V8 we check that fallback to non-strict works too
|
|
|
|
if 'gcparam' not in str(SPIDERMONKEY_ENGINE):
|
|
SPIDERMONKEY_ENGINE += ['-e', "gcparam('maxBytes', 1024*1024*1024);"] # Our very large files need lots of gc heap
|
|
|
|
# Temp file utilities
|
|
|
|
def try_delete(filename):
|
|
try:
|
|
os.unlink(filename)
|
|
except:
|
|
pass
|
|
|
|
class TempFiles:
|
|
def __init__(self):
|
|
self.to_clean = []
|
|
|
|
def note(self, filename):
|
|
self.to_clean.append(filename)
|
|
|
|
def get(self, suffix):
|
|
"""Returns a named temp file with the given prefix."""
|
|
named_file = tempfile.NamedTemporaryFile(dir=TEMP_DIR, suffix=suffix, delete=False)
|
|
self.note(named_file.name)
|
|
return named_file
|
|
|
|
def clean(self):
|
|
for filename in self.to_clean:
|
|
try_delete(filename)
|
|
self.to_clean = []
|
|
|
|
def run_and_clean(self, func):
|
|
try:
|
|
func()
|
|
finally:
|
|
self.clean()
|
|
|
|
# Utilities
|
|
|
|
def check_engine(engine):
|
|
# TODO: we call this several times, perhaps cache the results?
|
|
try:
|
|
return 'hello, world!' in run_js(path_from_root('tests', 'hello_world.js'), engine)
|
|
except Exception, e:
|
|
print 'Checking JS engine %s failed. Check ~/.emscripten. Details: %s' % (str(engine), str(e))
|
|
return False
|
|
|
|
def timeout_run(proc, timeout, note):
|
|
start = time.time()
|
|
if timeout is not None:
|
|
while time.time() - start < timeout and proc.poll() is None:
|
|
time.sleep(0.1)
|
|
if proc.poll() is None:
|
|
proc.kill() # XXX bug: killing emscripten.py does not kill it's child process!
|
|
raise Exception("Timed out: " + note)
|
|
return proc.communicate()[0]
|
|
|
|
def run_js(filename, engine=None, args=[], check_timeout=False, stdout=PIPE, stderr=None, cwd=None):
|
|
if engine is None: engine = JS_ENGINES[0]
|
|
if type(engine) is not list: engine = [engine]
|
|
return timeout_run(Popen(engine + [filename] + (['--'] if 'd8' in engine[0] else []) + args,
|
|
stdout=stdout, stderr=stderr, cwd=cwd), 15*60 if check_timeout else None, 'Execution')
|
|
|
|
def to_cc(cxx):
|
|
# By default, LLVM_GCC and CLANG are really the C++ versions. This gets an explicit C version
|
|
return cxx.replace('clang++', 'clang').replace('g++', 'gcc')
|
|
|
|
def line_splitter(data):
|
|
"""Silly little tool to split JSON arrays over many lines."""
|
|
|
|
out = ''
|
|
counter = 0
|
|
|
|
for i in range(len(data)):
|
|
out += data[i]
|
|
if data[i] == ' ' and counter > 60:
|
|
out += '\n'
|
|
counter = 0
|
|
else:
|
|
counter += 1
|
|
|
|
return out
|
|
|
|
def limit_size(string, MAX=80*20):
|
|
if len(string) < MAX: return string
|
|
return string[0:MAX/2] + '\n[..]\n' + string[-MAX/2:]
|
|
|
|
def read_pgo_data(filename):
|
|
'''
|
|
Reads the output of PGO and generates proper information for CORRECT_* == 2 's *_LINES options
|
|
'''
|
|
signs_lines = []
|
|
overflows_lines = []
|
|
|
|
for line in open(filename, 'r'):
|
|
try:
|
|
if line.rstrip() == '': continue
|
|
if '%0 failures' in line: continue
|
|
left, right = line.split(' : ')
|
|
signature = left.split('|')[1]
|
|
if 'Sign' in left:
|
|
signs_lines.append(signature)
|
|
elif 'Overflow' in left:
|
|
overflows_lines.append(signature)
|
|
except:
|
|
pass
|
|
|
|
return {
|
|
'signs_lines': signs_lines,
|
|
'overflows_lines': overflows_lines
|
|
}
|
|
|
|
# Settings. A global singleton. Not pretty, but nicer than passing |, settings| everywhere
|
|
|
|
class Settings:
|
|
@classmethod
|
|
def reset(self):
|
|
class Settings2:
|
|
QUANTUM_SIZE = 4
|
|
reset = Settings.reset
|
|
|
|
# Given some emcc-type args (-O3, -s X=Y, etc.), fill Settings with the right settings
|
|
@classmethod
|
|
def load(self, args=[]):
|
|
# Load the JS defaults into python
|
|
settings = open(path_from_root('src', 'settings.js')).read().replace('var ', 'Settings.').replace('//', '#')
|
|
exec settings in globals()
|
|
|
|
# Apply additional settings. First -O, then -s
|
|
for i in range(len(args)):
|
|
if args[i].startswith('-O'):
|
|
level = eval(args[i][2])
|
|
Settings.apply_opt_level(level)
|
|
for i in range(len(args)):
|
|
if args[i] == '-s':
|
|
exec 'Settings.' + args[i+1] in globals() # execute the setting
|
|
|
|
# Transforms the Settings information into emcc-compatible args (-s X=Y, etc.). Basically
|
|
# the reverse of load_settings, except for -Ox which is relevant there but not here
|
|
@classmethod
|
|
def serialize(self):
|
|
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
|
|
ret += ['-s', key + '=' + json.dumps(value)]
|
|
return ret
|
|
|
|
@classmethod
|
|
def apply_opt_level(self, opt_level, noisy=False):
|
|
if opt_level >= 1:
|
|
Settings.ASSERTIONS = 0
|
|
Settings.DISABLE_EXCEPTION_CATCHING = 1
|
|
if opt_level >= 2:
|
|
Settings.RELOOP = 1
|
|
if opt_level >= 3:
|
|
Settings.CORRECT_SIGNS = 0
|
|
Settings.CORRECT_OVERFLOWS = 0
|
|
Settings.CORRECT_ROUNDINGS = 0
|
|
Settings.I64_MODE = 0
|
|
Settings.DOUBLE_MODE = 0
|
|
if noisy: print >> sys.stderr, 'Warning: Applying some potentially unsafe optimizations! (Use -O2 if this fails.)'
|
|
|
|
global Settings
|
|
Settings = Settings2
|
|
Settings.load() # load defaults
|
|
|
|
Settings.reset()
|
|
|
|
# Building
|
|
|
|
class Building:
|
|
COMPILER = CLANG
|
|
LLVM_OPTS = False
|
|
COMPILER_TEST_OPTS = [] # For use of the test runner
|
|
|
|
@staticmethod
|
|
def get_building_env():
|
|
env = os.environ.copy()
|
|
env['CC'] = EMCC
|
|
env['CXX'] = EMXX
|
|
env['AR'] = EMAR
|
|
env['RANLIB'] = EMRANLIB
|
|
env['LIBTOOL'] = EMLIBTOOL
|
|
env['EMMAKEN_COMPILER'] = Building.COMPILER
|
|
env['EMSCRIPTEN_TOOLS'] = path_from_root('tools')
|
|
env['CFLAGS'] = env['EMMAKEN_CFLAGS'] = ' '.join(Building.COMPILER_TEST_OPTS)
|
|
env['HOST_CC'] = CLANG_CC
|
|
env['HOST_CXX'] = CLANG_CPP
|
|
env['HOST_CFLAGS'] = "-W" #if set to nothing, CFLAGS is used, which we don't want
|
|
env['HOST_CXXFLAGS'] = "-W" #if set to nothing, CXXFLAGS is used, which we don't want
|
|
return env
|
|
|
|
@staticmethod
|
|
def configure(args, stdout=None, stderr=None, env=None):
|
|
if env is None:
|
|
env = Building.get_building_env()
|
|
env['EMMAKEN_JUST_CONFIGURE'] = '1'
|
|
Popen(args, stdout=stdout, stderr=stderr, env=env).communicate()[0]
|
|
del env['EMMAKEN_JUST_CONFIGURE']
|
|
|
|
@staticmethod
|
|
def make(args, stdout=None, stderr=None, env=None):
|
|
if env is None:
|
|
env = Building.get_building_env()
|
|
Popen(args, stdout=stdout, stderr=stderr, env=env).communicate()[0]
|
|
|
|
@staticmethod
|
|
def build_library(name, build_dir, output_dir, generated_libs, configure=['./configure'], configure_args=[], make=['make'], make_args=['-j', '2'], cache=None, cache_name=None, copy_project=False, env_init={}):
|
|
''' Build a library into a .bc file. We build the .bc file once and cache it for all our tests. (We cache in
|
|
memory since the test directory is destroyed and recreated for each test. Note that we cache separately
|
|
for different compilers) '''
|
|
|
|
if type(generated_libs) is not list: generated_libs = [generated_libs]
|
|
|
|
temp_dir = build_dir
|
|
if copy_project:
|
|
project_dir = os.path.join(temp_dir, name)
|
|
if os.path.exists(project_dir):
|
|
shutil.rmtree(project_dir)
|
|
shutil.copytree(path_from_root('tests', name), project_dir) # Useful in debugging sometimes to comment this out, and two lines above
|
|
else:
|
|
project_dir = build_dir
|
|
try:
|
|
old_dir = os.getcwd()
|
|
except:
|
|
old_dir = None
|
|
os.chdir(project_dir)
|
|
generated_libs = map(lambda lib: os.path.join(project_dir, lib), generated_libs)
|
|
#for lib in generated_libs:
|
|
# try:
|
|
# os.unlink(lib) # make sure compilation completed successfully
|
|
# except:
|
|
# pass
|
|
env = Building.get_building_env()
|
|
for k, v in env_init.iteritems():
|
|
env[k] = v
|
|
if configure: # Useful in debugging sometimes to comment this out (and the lines below up to and including the |link| call)
|
|
Building.configure(configure + configure_args, stdout=open(os.path.join(output_dir, 'configure_'), 'w'),
|
|
stderr=open(os.path.join(output_dir, 'configure_err'), 'w'), env=env)
|
|
Building.make(make + make_args, stdout=open(os.path.join(output_dir, 'make_'), 'w'),
|
|
stderr=open(os.path.join(output_dir, 'make_err'), 'w'), env=env)
|
|
bc_file = os.path.join(project_dir, 'bc.bc')
|
|
Building.link(generated_libs, bc_file)
|
|
if cache is not None:
|
|
cache[cache_name] = open(bc_file, 'rb').read()
|
|
if old_dir:
|
|
os.chdir(old_dir)
|
|
return bc_file
|
|
|
|
@staticmethod
|
|
def link(files, target):
|
|
output = Popen([LLVM_LINK] + files + ['-o', target], stdout=PIPE).communicate()[0]
|
|
assert os.path.exists(target) and (output is None or 'Could not open input file' not in output), 'Linking error: ' + output
|
|
|
|
# Emscripten optimizations that we run on the .ll file
|
|
@staticmethod
|
|
def ll_opts(filename):
|
|
## Remove target info. This helps LLVM opts, if we run them later
|
|
#cleaned = filter(lambda line: not line.startswith('target datalayout = ') and not line.startswith('target triple = '),
|
|
# open(filename + '.o.ll', 'r').readlines())
|
|
#os.unlink(filename + '.o.ll')
|
|
#open(filename + '.o.ll.orig', 'w').write(''.join(cleaned))
|
|
shutil.move(filename + '.o.ll', filename + '.o.ll.orig')
|
|
output = Popen(['python', DFE, filename + '.o.ll.orig', filename + '.o.ll'], stdout=PIPE).communicate()[0]
|
|
assert os.path.exists(filename + '.o.ll'), 'Failed to run ll optimizations'
|
|
|
|
# Optional LLVM optimizations
|
|
@staticmethod
|
|
def llvm_opt(filename, level, safe=True):
|
|
output = Popen([LLVM_OPT, filename] + Building.pick_llvm_opts(level, safe) + ['-o=' + filename + '.opt.bc'], stdout=PIPE).communicate()[0]
|
|
assert os.path.exists(filename + '.opt.bc'), 'Failed to run llvm optimizations: ' + output
|
|
shutil.move(filename + '.opt.bc', filename)
|
|
|
|
@staticmethod
|
|
def llvm_opts(filename): # deprecated version, only for test runner. TODO: remove
|
|
if Building.LLVM_OPTS:
|
|
shutil.move(filename + '.o', filename + '.o.pre')
|
|
output = Popen([LLVM_OPT, filename + '.o.pre'] + Building.LLVM_OPT_OPTS + ['-o=' + filename + '.o'], stdout=PIPE).communicate()[0]
|
|
assert os.path.exists(filename + '.o'), 'Failed to run llvm optimizations: ' + output
|
|
#if Building.LLVM_OPTS == 2:
|
|
# print 'Unsafe LD!'
|
|
# shutil.move(filename + '.o', filename + '.o.pre')
|
|
# output = Popen([LLVM_LD, filename + '.o.pre', '-o=' + filename + '.tmp'], stdout=PIPE).communicate()[0]
|
|
# assert os.path.exists(filename + '.tmp.bc'), 'Failed to run llvm optimizations: ' + output
|
|
# shutil.move(filename + '.tmp.bc', filename + '.o')
|
|
|
|
@staticmethod
|
|
def llvm_dis(input_filename, output_filename=None):
|
|
# LLVM binary ==> LLVM assembly
|
|
if output_filename is None:
|
|
# use test runner conventions
|
|
output_filename = input_filename + '.o.ll'
|
|
input_filename = input_filename + '.o'
|
|
try_delete(output_filename)
|
|
output = Popen([LLVM_DIS, input_filename ] + LLVM_DIS_OPTS + ['-o=' + output_filename], stdout=PIPE).communicate()[0]
|
|
assert os.path.exists(output_filename), 'Could not create .ll file: ' + output
|
|
return output_filename
|
|
|
|
@staticmethod
|
|
def llvm_as(input_filename, output_filename=None):
|
|
# LLVM assembly ==> LLVM binary
|
|
if output_filename is None:
|
|
# use test runner conventions
|
|
output_filename = input_filename + '.o'
|
|
input_filename = input_filename + '.o.ll'
|
|
try_delete(output_filename)
|
|
output = Popen([LLVM_AS, input_filename, '-o=' + output_filename], stdout=PIPE).communicate()[0]
|
|
assert os.path.exists(output_filename), 'Could not create bc file: ' + output
|
|
return output_filename
|
|
|
|
@staticmethod
|
|
def llvm_nm(filename, stdout=PIPE, stderr=None):
|
|
# LLVM binary ==> list of symbols
|
|
output = Popen([LLVM_NM, filename], stdout=stdout, stderr=stderr).communicate()[0]
|
|
class ret:
|
|
defs = []
|
|
undefs = []
|
|
for line in output.split('\n'):
|
|
if len(line) == 0: continue
|
|
status, symbol = filter(lambda seg: len(seg) > 0, line.split(' '))
|
|
if status == 'U':
|
|
ret.undefs.append(symbol)
|
|
else:
|
|
ret.defs.append(symbol)
|
|
return ret
|
|
|
|
@staticmethod
|
|
def emcc(filename, args=[], output_filename=None, stdout=None, stderr=None, env=None):
|
|
if output_filename is None:
|
|
output_filename = filename + '.o'
|
|
try_delete(output_filename)
|
|
Popen([EMCC, filename] + args + ['-o', output_filename], stdout=stdout, stderr=stderr, env=env).communicate()
|
|
assert os.path.exists(output_filename), 'emcc could not create output file'
|
|
|
|
@staticmethod
|
|
def emscripten(filename, append_ext=True, extra_args=[]):
|
|
# Run Emscripten
|
|
settings = Settings.serialize()
|
|
compiler_output = timeout_run(Popen(['python', EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE), None, 'Compiling')
|
|
#print compiler_output
|
|
|
|
# Detect compilation crashes and errors
|
|
if compiler_output is not None and 'Traceback' in compiler_output and 'in test_' in compiler_output: print compiler_output; assert 0
|
|
assert os.path.exists(filename + '.o.js') and len(open(filename + '.o.js', 'r').read()) > 0, 'Emscripten failed to generate .js: ' + str(compiler_output)
|
|
|
|
return filename + '.o.js'
|
|
|
|
@staticmethod
|
|
def pick_llvm_opts(optimization_level, safe=True):
|
|
'''
|
|
It may be safe to use nonportable optimizations (like -OX) if we remove the platform info from the .ll
|
|
(which we do in do_ll_opts) - but even there we have issues (even in TA2) with instruction combining
|
|
into i64s. In any case, the handpicked ones here should be safe and portable. They are also tuned for
|
|
things that look useful.
|
|
'''
|
|
opts = []
|
|
if optimization_level > 0:
|
|
#opts.append('-disable-inlining') # we prefer to let closure compiler do our inlining
|
|
if not safe:
|
|
#opts.append('-O%d' % optimization_level)
|
|
opts.append('-std-compile-opts')
|
|
opts.append('-std-link-opts')
|
|
print 'Unsafe:', opts,
|
|
else:
|
|
allow_nonportable = not safe
|
|
optimize_size = True
|
|
use_aa = not safe
|
|
|
|
# PassManagerBuilder::populateModulePassManager
|
|
if allow_nonportable and use_aa: # ammo.js results indicate this can be nonportable
|
|
opts.append('-tbaa')
|
|
opts.append('-basicaa') # makes fannkuch slow but primes fast
|
|
|
|
opts.append('-globalopt')
|
|
opts.append('-ipsccp')
|
|
opts.append('-deadargelim')
|
|
if allow_nonportable: opts.append('-instcombine')
|
|
opts.append('-simplifycfg')
|
|
|
|
opts.append('-prune-eh')
|
|
if not optimize_size: opts.append('-inline') # The condition here is a difference with LLVM's createStandardAliasAnalysisPasses
|
|
opts.append('-functionattrs')
|
|
if optimization_level > 2:
|
|
opts.append('-argpromotion')
|
|
|
|
# XXX Danger: Can turn a memcpy into something that violates the
|
|
# load-store consistency hypothesis. See hashnum() in Lua.
|
|
# Note: this opt is of great importance for raytrace...
|
|
if allow_nonportable: opts.append('-scalarrepl')
|
|
|
|
if allow_nonportable: opts.append('-early-cse') # ?
|
|
opts.append('-simplify-libcalls')
|
|
opts.append('-jump-threading')
|
|
if allow_nonportable: opts.append('-correlated-propagation') # ?
|
|
opts.append('-simplifycfg')
|
|
if allow_nonportable: opts.append('-instcombine')
|
|
|
|
opts.append('-tailcallelim')
|
|
opts.append('-simplifycfg')
|
|
opts.append('-reassociate')
|
|
opts.append('-loop-rotate')
|
|
opts.append('-licm')
|
|
opts.append('-loop-unswitch') # XXX should depend on optimize_size
|
|
if allow_nonportable: opts.append('-instcombine')
|
|
if Settings.QUANTUM_SIZE == 4: opts.append('-indvars') # XXX this infinite-loops raytrace on q1 (loop in |new node_t[count]| has 68 hardcoded ¬ fixed)
|
|
if allow_nonportable: opts.append('-loop-idiom') # ?
|
|
opts.append('-loop-deletion')
|
|
opts.append('-loop-unroll')
|
|
|
|
##### not in llvm-3.0. but have | #addExtensionsToPM(EP_LoopOptimizerEnd, MPM);| if allow_nonportable: opts.append('-instcombine')
|
|
|
|
# XXX Danger: Messes up Lua output for unknown reasons
|
|
# Note: this opt is of minor importance for raytrace...
|
|
if optimization_level > 1 and allow_nonportable: opts.append('-gvn')
|
|
|
|
opts.append('-memcpyopt') # Danger?
|
|
opts.append('-sccp')
|
|
|
|
if allow_nonportable: opts.append('-instcombine')
|
|
opts.append('-jump-threading')
|
|
opts.append('-correlated-propagation')
|
|
opts.append('-dse')
|
|
#addExtensionsToPM(EP_ScalarOptimizerLate, MPM);
|
|
|
|
opts.append('-adce')
|
|
opts.append('-simplifycfg')
|
|
if allow_nonportable: opts.append('-instcombine')
|
|
|
|
opts.append('-strip-dead-prototypes')
|
|
|
|
if optimization_level > 2: opts.append('-globaldce')
|
|
|
|
if optimization_level > 1: opts.append('-constmerge')
|
|
|
|
Building.LLVM_OPT_OPTS = opts
|
|
return opts
|
|
|
|
@staticmethod
|
|
def js_optimizer(filename, passes):
|
|
if not check_engine(NODE_JS):
|
|
raise Exception('Node.js appears to be missing or broken, looked at: ' + str(NODE_JS))
|
|
|
|
if type(passes) == str:
|
|
passes = [passes]
|
|
input = open(filename, 'r').read()
|
|
output, err = Popen([NODE_JS, JS_OPTIMIZER] + passes, stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(input)
|
|
assert len(output) > 0 and not output.startswith('Assertion failed'), 'Error in js optimizer: ' + err + '\n\n' + output
|
|
filename += '.jo.js'
|
|
f = open(filename, 'w')
|
|
f.write(output)
|
|
f.close()
|
|
return filename
|
|
|
|
@staticmethod
|
|
def eliminator(filename):
|
|
if not check_engine(NODE_JS):
|
|
raise Exception('Node.js appears to be missing or broken, looked at: ' + str(NODE_JS))
|
|
|
|
coffee = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee')
|
|
eliminator = path_from_root('tools', 'eliminator', 'eliminator.coffee')
|
|
input = open(filename, 'r').read()
|
|
output, err = Popen([coffee, eliminator], stdin=PIPE, stdout=PIPE, stderr=PIPE).communicate(input)
|
|
assert len(output) > 0, 'Error in eliminator: ' + err + '\n\n' + output
|
|
filename += '.el.js'
|
|
f = open(filename, 'w')
|
|
f.write(output)
|
|
f.close()
|
|
return filename
|
|
|
|
@staticmethod
|
|
def closure_compiler(filename):
|
|
if not os.path.exists(CLOSURE_COMPILER):
|
|
raise Exception('Closure compiler appears to be missing, looked at: ' + str(CLOSURE_COMPILER))
|
|
|
|
# Something like this (adjust memory as needed):
|
|
# java -Xmx1024m -jar CLOSURE_COMPILER --compilation_level ADVANCED_OPTIMIZATIONS --variable_map_output_file src.cpp.o.js.vars --js src.cpp.o.js --js_output_file src.cpp.o.cc.js
|
|
cc_output = Popen(['java', '-jar', CLOSURE_COMPILER,
|
|
'--compilation_level', 'ADVANCED_OPTIMIZATIONS',
|
|
#'--formatting', 'PRETTY_PRINT',
|
|
#'--variable_map_output_file', filename + '.vars',
|
|
'--js', filename, '--js_output_file', filename + '.cc.js'], stdout=PIPE, stderr=STDOUT).communicate()[0]
|
|
if 'ERROR' in cc_output or not os.path.exists(filename + '.cc.js'):
|
|
raise Exception('closure compiler error: ' + cc_output)
|
|
|
|
return filename + '.cc.js'
|
|
|
|
@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)
|
|
assert len(defs.defs + defs.undefs) > 0
|
|
except:
|
|
return False
|
|
# look for magic signature
|
|
b = open(filename, 'r').read(4)
|
|
if b[0] == 'B' and b[1] == 'C':
|
|
return True
|
|
# on OS X, there is a 20-byte prefix
|
|
elif ord(b[0]) == 222 and ord(b[1]) == 192 and ord(b[2]) == 23 and ord(b[3]) == 11:
|
|
b = open(filename, 'r').read(24)
|
|
return b[20] == 'B' and b[21] == 'C'
|
|
|
|
return False
|
|
|