emscripten/emscripten.py

1044 строки
45 KiB
Python
Executable File

#!/usr/bin/env python2
'''
You should normally never use this! Use emcc instead.
This is a small wrapper script around the core JS compiler. This calls that
compiler with the settings given to it. It can also read data from C/C++
header files (so that the JS compiler can see the constants in those
headers, for the libc implementation in JS).
'''
import os, sys, json, optparse, subprocess, re, time, string, logging
from tools import shared
from tools import jsrun, cache as cache_module, tempfiles
from tools.response_file import read_response_file
from tools.shared import WINDOWS
__rootpath__ = os.path.abspath(os.path.dirname(__file__))
def path_from_root(*pathelems):
"""Returns the absolute path for which the given path elements are
relative to the emscripten root.
"""
return os.path.join(__rootpath__, *pathelems)
def get_configuration():
if hasattr(get_configuration, 'configuration'):
return get_configuration.configuration
configuration = shared.Configuration(environ=os.environ)
get_configuration.configuration = configuration
return configuration
STDERR_FILE = os.environ.get('EMCC_STDERR_FILE')
if STDERR_FILE:
STDERR_FILE = os.path.abspath(STDERR_FILE)
logging.info('logging stderr in js compiler phase into %s' % STDERR_FILE)
STDERR_FILE = open(STDERR_FILE, 'w')
def emscript(infile, settings, outfile, libraries=[], compiler_engine=None,
temp_files=None, DEBUG=None, DEBUG_CACHE=None):
"""Runs the emscripten LLVM-to-JS compiler.
Args:
infile: The path to the input LLVM assembly file.
settings: JSON-formatted settings that override the values
defined in src/settings.js.
outfile: The file where the output is written.
"""
assert settings['ASM_JS'], 'fastcomp is asm.js-only (mode 1 or 2)'
success = False
try:
# Overview:
# * Run LLVM backend to emit JS. JS includes function bodies, memory initializer,
# and various metadata
# * Run compiler.js on the metadata to emit the shell js code, pre/post-ambles,
# JS library dependencies, etc.
temp_js = temp_files.get('.4.js').name
backend_compiler = os.path.join(shared.LLVM_ROOT, 'llc')
backend_args = [backend_compiler, infile, '-march=js', '-filetype=asm', '-o', temp_js]
if settings['PRECISE_F32']:
backend_args += ['-emscripten-precise-f32']
if settings['WARN_UNALIGNED']:
backend_args += ['-emscripten-warn-unaligned']
if settings['RESERVED_FUNCTION_POINTERS'] > 0:
backend_args += ['-emscripten-reserved-function-pointers=%d' % settings['RESERVED_FUNCTION_POINTERS']]
if settings['ASSERTIONS'] > 0:
backend_args += ['-emscripten-assertions=%d' % settings['ASSERTIONS']]
if settings['ALIASING_FUNCTION_POINTERS'] == 0:
backend_args += ['-emscripten-no-aliasing-function-pointers']
if settings['EMULATED_FUNCTION_POINTERS']:
backend_args += ['-emscripten-emulated-function-pointers']
if settings['RELOCATABLE']:
backend_args += ['-emscripten-relocatable']
backend_args += ['-emscripten-global-base=0']
elif settings['GLOBAL_BASE'] >= 0:
backend_args += ['-emscripten-global-base=%d' % settings['GLOBAL_BASE']]
backend_args += ['-O' + str(settings['OPT_LEVEL'])]
if DEBUG:
logging.debug('emscript: llvm backend: ' + ' '.join(backend_args))
t = time.time()
shared.jsrun.timeout_run(subprocess.Popen(backend_args, stdout=subprocess.PIPE))
if DEBUG:
logging.debug(' emscript: llvm backend took %s seconds' % (time.time() - t))
t = time.time()
# Split up output
backend_output = open(temp_js).read()
#if DEBUG: print >> sys.stderr, backend_output
start_funcs_marker = '// EMSCRIPTEN_START_FUNCTIONS'
end_funcs_marker = '// EMSCRIPTEN_END_FUNCTIONS'
metadata_split_marker = '// EMSCRIPTEN_METADATA'
start_funcs = backend_output.index(start_funcs_marker)
end_funcs = backend_output.rindex(end_funcs_marker)
metadata_split = backend_output.rindex(metadata_split_marker)
funcs = backend_output[start_funcs+len(start_funcs_marker):end_funcs]
metadata_raw = backend_output[metadata_split+len(metadata_split_marker):]
#if DEBUG: print >> sys.stderr, "METAraw", metadata_raw
try:
metadata = json.loads(metadata_raw)
except Exception, e:
logging.error('emscript: failure to parse metadata output from compiler backend. raw output is: \n' + metadata_raw)
raise e
mem_init = backend_output[end_funcs+len(end_funcs_marker):metadata_split]
#if DEBUG: print >> sys.stderr, "FUNCS", funcs
#if DEBUG: print >> sys.stderr, "META", metadata
#if DEBUG: print >> sys.stderr, "meminit", mem_init
# if emulating pointer casts, force all tables to the size of the largest
if settings['EMULATE_FUNCTION_POINTER_CASTS']:
max_size = 0
for k, v in metadata['tables'].iteritems():
max_size = max(max_size, v.count(',')+1)
for k, v in metadata['tables'].iteritems():
curr = v.count(',')+1
if curr < max_size:
metadata['tables'][k] = v.replace(']', (',0'*(max_size - curr)) + ']')
if settings['SIDE_MODULE']:
for k in metadata['tables'].keys():
metadata['tables'][k] = metadata['tables'][k].replace('var FUNCTION_TABLE_', 'var SIDE_FUNCTION_TABLE_')
# function table masks
table_sizes = {}
for k, v in metadata['tables'].iteritems():
table_sizes[k] = str(v.count(',')) # undercounts by one, but that is what we want
#if settings['ASSERTIONS'] >= 2 and table_sizes[k] == 0:
# print >> sys.stderr, 'warning: no function pointers with signature ' + k + ', but there is a call, which will abort if it occurs (this can result from undefined behavior, check for compiler warnings on your source files and consider -Werror)'
funcs = re.sub(r"#FM_(\w+)#", lambda m: table_sizes[m.groups(0)[0]], funcs)
# fix +float into float.0, if not running js opts
if not settings['RUNNING_JS_OPTS']:
def fix_dot_zero(m):
num = m.group(3)
# TODO: handle 0x floats?
if num.find('.') < 0:
e = num.find('e');
if e < 0:
num += '.0'
else:
num = num[:e] + '.0' + num[e:]
return m.group(1) + m.group(2) + num
funcs = re.sub(r'([(=,+\-*/%<>:?] *)\+(-?)((0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)', lambda m: fix_dot_zero(m), funcs)
# js compiler
if DEBUG: logging.debug('emscript: js compiler glue')
# Settings changes
i64_funcs = ['i64Add', 'i64Subtract', '__muldi3', '__divdi3', '__udivdi3', '__remdi3', '__uremdi3']
for i64_func in i64_funcs:
if i64_func in metadata['declares']:
settings['PRECISE_I64_MATH'] = 2
break
metadata['declares'] = filter(lambda i64_func: i64_func not in ['getHigh32', 'setHigh32', '__muldi3', '__divdi3', '__remdi3', '__udivdi3', '__uremdi3'], metadata['declares']) # FIXME: do these one by one as normal js lib funcs
# Integrate info from backend
if settings['SIDE_MODULE']:
settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] = [] # we don't need any JS library contents in side modules
settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] = list(
set(settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] + map(shared.JS.to_nice_ident, metadata['declares'])).difference(
map(lambda x: x[1:], metadata['implementedFunctions'])
)
) + map(lambda x: x[1:], metadata['externs'])
if metadata['simd']:
settings['SIMD'] = 1
if metadata['cantValidate'] and settings['ASM_JS'] != 2:
logging.warning('disabling asm.js validation due to use of non-supported features: ' + metadata['cantValidate'])
settings['ASM_JS'] = 2
# Save settings to a file to work around v8 issue 1579
settings_file = temp_files.get('.txt').name
def save_settings():
global settings_text
settings_text = json.dumps(settings, sort_keys=True)
s = open(settings_file, 'w')
s.write(settings_text)
s.close()
save_settings()
# Call js compiler
if DEBUG: t = time.time()
out = jsrun.run_js(path_from_root('src', 'compiler.js'), compiler_engine,
[settings_file] + libraries, stdout=subprocess.PIPE, stderr=STDERR_FILE,
cwd=path_from_root('src'), error_limit=300)
assert '//FORWARDED_DATA:' in out, 'Did not receive forwarded data in pre output - process failed?'
glue, forwarded_data = out.split('//FORWARDED_DATA:')
if DEBUG:
logging.debug(' emscript: glue took %s seconds' % (time.time() - t))
t = time.time()
last_forwarded_json = forwarded_json = json.loads(forwarded_data)
# merge in information from llvm backend
last_forwarded_json['Functions']['tables'] = metadata['tables']
pre, post = glue.split('// EMSCRIPTEN_END_FUNCS')
#print >> sys.stderr, 'glue:', pre, '\n\n||||||||||||||||\n\n', post, '...............'
# memory and global initializers
global_initializers = str(', '.join(map(lambda i: '{ func: function() { %s() } }' % i, metadata['initializers'])))
if settings['SIMD'] == 1:
pre = open(path_from_root(os.path.join('src', 'ecmascript_simd.js'))).read() + '\n\n' + pre
staticbump = mem_init.count(',')+1
while staticbump % 16 != 0: staticbump += 1
pre = pre.replace('STATICTOP = STATIC_BASE + 0;', '''STATICTOP = STATIC_BASE + %d;
/* global initializers */ __ATINIT__.push(%s);
%s''' % (staticbump, global_initializers, mem_init)) # XXX wrong size calculation!
if settings['SIDE_MODULE']:
pre = pre.replace('Runtime.GLOBAL_BASE', 'gb').replace('{{{ STATIC_BUMP }}}', str(staticbump))
funcs_js = [funcs]
parts = pre.split('// ASM_LIBRARY FUNCTIONS\n')
if len(parts) > 1:
pre = parts[0]
funcs_js.append(parts[1])
# merge forwarded data
settings['EXPORTED_FUNCTIONS'] = forwarded_json['EXPORTED_FUNCTIONS']
all_exported_functions = set(settings['EXPORTED_FUNCTIONS']) # both asm.js and otherwise
for additional_export in settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE']: # additional functions to export from asm, if they are implemented
all_exported_functions.add('_' + additional_export)
if settings['EXPORT_FUNCTION_TABLES']:
for table in last_forwarded_json['Functions']['tables'].values():
for func in table.split('[')[1].split(']')[0].split(','):
if func[0] == '_':
all_exported_functions.add(func)
exported_implemented_functions = set(metadata['exports'])
export_bindings = settings['EXPORT_BINDINGS']
export_all = settings['EXPORT_ALL']
all_implemented = metadata['implementedFunctions'] + forwarded_json['Functions']['implementedFunctions'].keys() # XXX perf?
for key in all_implemented:
if key in all_exported_functions or export_all or (export_bindings and key.startswith('_emscripten_bind')):
exported_implemented_functions.add(key)
implemented_functions = set(metadata['implementedFunctions'])
if settings['ASSERTIONS'] and settings.get('ORIGINAL_EXPORTED_FUNCTIONS'):
original_exports = settings['ORIGINAL_EXPORTED_FUNCTIONS']
if original_exports[0] == '@': original_exports = json.loads(open(original_exports[1:]).read())
for requested in original_exports:
if requested not in all_implemented and \
requested != '_malloc': # special-case malloc, EXPORTED by default for internal use, but we bake in a trivial allocator and warn at runtime if used in ASSERTIONS
logging.warning('function requested to be exported, but not implemented: "%s"', requested)
asm_consts = [0]*len(metadata['asmConsts'])
for k, v in metadata['asmConsts'].iteritems():
const = v.encode('utf-8')
if const[0] == '"' and const[-1] == '"':
const = const[1:-1]
const = '{ ' + const + ' }'
i = 0
args = []
while ('$' + str(i)) in const:
args.append('$' + str(i))
i += 1
const = 'function(' + ', '.join(args ) + ') ' + const
asm_consts[int(k)] = const
asm_const_funcs = []
for arity in metadata['asmConstArities']:
forwarded_json['Functions']['libraryFunctions']['_emscripten_asm_const_%d' % arity] = 1
args = ['a%d' % i for i in range(arity)]
all_args = ['code'] + args
asm_const_funcs.append(r'''
function _emscripten_asm_const_%d(%s) {
return ASM_CONSTS[code](%s) | 0;
}''' % (arity, ', '.join(all_args), ', '.join(args)))
pre = pre.replace('// === Body ===', '// === Body ===\n' + '\nvar ASM_CONSTS = [' + ', '.join(asm_consts) + '];\n' + '\n'.join(asm_const_funcs) + '\n')
#if DEBUG: outfile.write('// pre\n')
outfile.write(pre)
pre = None
#if DEBUG: outfile.write('// funcs\n')
# when emulating function pointer casts, we need to know what is the target of each pointer
if settings['EMULATE_FUNCTION_POINTER_CASTS']:
function_pointer_targets = {}
for sig, table in last_forwarded_json['Functions']['tables'].iteritems():
start = table.index('[')
end = table.rindex(']')
body = table[start+1:end].split(',')
parsed = map(lambda x: x.strip(), body)
for i in range(len(parsed)):
if parsed[i] != '0':
assert i not in function_pointer_targets
function_pointer_targets[i] = [sig, str(parsed[i])]
# Move preAsms to their right place
def move_preasm(m):
contents = m.groups(0)[0]
outfile.write(contents + '\n')
return ''
if not settings['BOOTSTRAPPING_STRUCT_INFO'] and len(funcs_js) > 1:
funcs_js[1] = re.sub(r'/\* PRE_ASM \*/(.*)\n', lambda m: move_preasm(m), funcs_js[1])
class Counter:
i = 0
j = 0
if 'pre' in last_forwarded_json['Functions']['tables']:
pre_tables = last_forwarded_json['Functions']['tables']['pre']
del last_forwarded_json['Functions']['tables']['pre']
else:
pre_tables = ''
def unfloat(s):
return 'd' if s == 'f' else s # lower float to double for ffis
if settings['ASSERTIONS'] >= 2:
debug_tables = {}
def make_params(sig): return ','.join(['p%d' % p for p in range(len(sig)-1)])
def make_coerced_params(sig): return ','.join([shared.JS.make_coercion('p%d', unfloat(sig[p+1]), settings) % p for p in range(len(sig)-1)])
def make_coercions(sig): return ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';'
def make_func(name, code, params, coercions): return 'function %s(%s) { %s %s }' % (name, params, coercions, code)
in_table = set()
def make_table(sig, raw):
params = make_params(sig)
coerced_params = make_coerced_params(sig)
coercions = make_coercions(sig)
def make_bad(target=None):
i = Counter.i
Counter.i += 1
if target is None: target = i
name = 'b' + str(i)
if not settings['ASSERTIONS']:
code = 'abort(%s);' % target
else:
code = 'nullFunc_' + sig + '(%d);' % target
if sig[0] != 'v':
code += 'return %s' % shared.JS.make_initializer(sig[0], settings) + ';'
return name, make_func(name, code, params, coercions)
bad, bad_func = make_bad() # the default bad func
if settings['ASSERTIONS'] <= 1:
Counter.pre = [bad_func]
else:
Counter.pre = []
start = raw.index('[')
end = raw.rindex(']')
body = raw[start+1:end].split(',')
if settings['EMULATED_FUNCTION_POINTERS']:
def receive(item):
if item == '0':
return item
else:
if item in all_implemented:
in_table.add(item)
return "asm['" + item + "']"
else:
return item # this is not implemented; it would normally be wrapped, but with emulation, we just use it directly outside
body = map(receive, body)
for j in range(settings['RESERVED_FUNCTION_POINTERS']):
curr = 'jsCall_%s_%s' % (sig, j)
body[settings['FUNCTION_POINTER_ALIGNMENT'] * (1 + j)] = curr
implemented_functions.add(curr)
Counter.j = 0
def fix_item(item):
j = Counter.j
Counter.j += 1
newline = Counter.j % 30 == 29
if item == '0':
if j > 0 and settings['EMULATE_FUNCTION_POINTER_CASTS'] and j in function_pointer_targets: # emulate all non-null pointer calls, if asked to
proper_sig, proper_target = function_pointer_targets[j]
if settings['EMULATED_FUNCTION_POINTERS']:
if proper_target in all_implemented:
proper_target = "asm['" + proper_target + "']"
def make_emulated_param(i):
if i >= len(sig): return shared.JS.make_initializer(proper_sig[i], settings) # extra param, just send a zero
return shared.JS.make_coercion('p%d' % (i-1), proper_sig[i], settings, convert_from=sig[i])
proper_code = proper_target + '(' + ','.join(map(lambda i: make_emulated_param(i+1), range(len(proper_sig)-1))) + ')'
if proper_sig[0] != 'v':
# proper sig has a return, which the wrapper may or may not use
proper_code = shared.JS.make_coercion(proper_code, proper_sig[0], settings)
if proper_sig[0] != sig[0]:
# first coercion ensured we call the target ok; this one ensures we return the right type in the wrapper
proper_code = shared.JS.make_coercion(proper_code, sig[0], settings, convert_from=proper_sig[0])
if sig[0] != 'v':
proper_code = 'return ' + proper_code
else:
# proper sig has no return, we may need a fake return
if sig[0] != 'v':
proper_code = 'return ' + shared.JS.make_initializer(sig[0], settings)
name = 'fpemu_%s_%d' % (sig, j)
wrapper = make_func(name, proper_code, params, coercions)
Counter.pre.append(wrapper)
return name if not newline else (name + '\n')
if settings['ASSERTIONS'] <= 1:
return bad if not newline else (bad + '\n')
else:
specific_bad, specific_bad_func = make_bad(j)
Counter.pre.append(specific_bad_func)
return specific_bad if not newline else (specific_bad + '\n')
if item not in implemented_functions and not settings['EMULATED_FUNCTION_POINTERS']: # when emulating function pointers, we don't need wrappers
# this is imported into asm, we must wrap it
call_ident = item
if call_ident in metadata['redirects']: call_ident = metadata['redirects'][call_ident]
if not call_ident.startswith('_') and not call_ident.startswith('Math_'): call_ident = '_' + call_ident
code = call_ident + '(' + coerced_params + ')'
if sig[0] != 'v':
# ffis cannot return float
if sig[0] == 'f': code = '+' + code
code = 'return ' + shared.JS.make_coercion(code, sig[0], settings)
code += ';'
Counter.pre.append(make_func(item + '__wrapper', code, params, coercions))
return item + '__wrapper'
return item if not newline else (item + '\n')
if settings['ASSERTIONS'] >= 2:
debug_tables[sig] = body
body = ','.join(map(fix_item, body))
return ('\n'.join(Counter.pre), ''.join([raw[:start+1], body, raw[end:]]))
infos = [make_table(sig, raw) for sig, raw in last_forwarded_json['Functions']['tables'].iteritems()]
Counter.pre = []
function_tables_defs = '\n'.join([info[0] for info in infos]) + '\n'
if not settings['EMULATED_FUNCTION_POINTERS']:
function_tables_defs += '\n// EMSCRIPTEN_END_FUNCS\n'
function_tables_defs += '\n'.join([info[1] for info in infos])
asm_setup = ''
if settings['ASSERTIONS'] >= 2:
for sig in last_forwarded_json['Functions']['tables']:
asm_setup += '\nvar debug_table_' + sig + ' = ' + json.dumps(debug_tables[sig]) + ';'
maths = ['Math.' + func for func in ['floor', 'abs', 'sqrt', 'pow', 'cos', 'sin', 'tan', 'acos', 'asin', 'atan', 'atan2', 'exp', 'log', 'ceil', 'imul', 'min', 'clz32']]
simdfloattypes = ['float32x4']
simdinttypes = ['int32x4']
simdtypes = simdfloattypes + simdinttypes
simdfuncs = ['check', 'add', 'sub', 'neg', 'mul',
'equal', 'lessThan', 'greaterThan',
'notEqual', 'lessThanOrEqual', 'greaterThanOrEqual',
'select', 'and', 'or', 'xor', 'not',
'splat', 'swizzle', 'shuffle',
'withX', 'withY', 'withZ', 'withW',
'load', 'store', 'loadX', 'storeX', 'loadXY', 'storeXY', 'loadXYZ', 'storeXYZ']
simdfloatfuncs = simdfuncs + ['div', 'min', 'max', 'minNum', 'maxNum', 'sqrt',
'abs', 'fromInt32x4', 'fromInt32x4Bits',
'reciprocalApproximation', 'reciprocalSqrtApproximation'];
simdintfuncs = simdfuncs + ['fromFloat32x4', 'fromFloat32x4Bits',
'shiftRightArithmeticByScalar',
'shiftRightLogicalByScalar',
'shiftLeftByScalar'];
fundamentals = ['Math', 'Int8Array', 'Int16Array', 'Int32Array', 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Float32Array', 'Float64Array', 'NaN', 'Infinity']
if metadata['simd']:
fundamentals += ['SIMD']
if settings['ALLOW_MEMORY_GROWTH']: fundamentals.append('byteLength')
math_envs = []
provide_fround = settings['PRECISE_F32'] or settings['SIMD']
if provide_fround: maths += ['Math.fround']
def get_function_pointer_error(sig):
if settings['ASSERTIONS'] <= 1:
extra = ' Module["printErr"]("Build with ASSERTIONS=2 for more info.");'
pointer = ' '
else:
pointer = ' \'" + x + "\' '
extra = ' Module["printErr"]("This pointer might make sense in another type signature: '
# sort signatures, attempting to show most likely related ones first
sigs = last_forwarded_json['Functions']['tables'].keys()
def keyfunc(other):
ret = 0
minlen = min(len(other), len(sig))
maxlen = min(len(other), len(sig))
if other.startswith(sig) or sig.startswith(other): ret -= 1000 # prioritize prefixes, could be dropped params
ret -= 133*difflib.SequenceMatcher(a=other, b=sig).ratio() # prioritize on diff similarity
ret += 15*abs(len(other) - len(sig))/float(maxlen) # deprioritize the bigger the length difference is
for i in range(minlen):
if other[i] == sig[i]: ret -= 5/float(maxlen) # prioritize on identically-placed params
ret += 20*len(other) # deprioritize on length
return ret
sigs.sort(key=keyfunc)
for other in sigs:
if other != sig:
extra += other + ': " + debug_table_' + other + '[x] + " '
extra += '"); '
return 'Module["printErr"]("Invalid function pointer' + pointer + 'called with signature \'' + sig + '\'. ' + \
'Perhaps this is an invalid value (e.g. caused by calling a virtual method on a NULL pointer)? ' + \
'Or calling a function with an incorrect type, which will fail? ' + \
'(it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this)' + \
'"); ' + extra
basic_funcs = ['abort', 'assert'] + [m.replace('.', '_') for m in math_envs]
if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_FT_MASK']
if settings['ASSERTIONS']:
if settings['ASSERTIONS'] >= 2: import difflib
for sig in last_forwarded_json['Functions']['tables'].iterkeys():
basic_funcs += ['nullFunc_' + sig]
asm_setup += '\nfunction nullFunc_' + sig + '(x) { ' + get_function_pointer_error(sig) + 'abort(x) }\n'
basic_vars = ['STACKTOP', 'STACK_MAX', 'tempDoublePtr', 'ABORT']
basic_float_vars = []
if metadata.get('preciseI64MathUsed'):
basic_vars += ['cttz_i8']
else:
if forwarded_json['Functions']['libraryFunctions'].get('_llvm_cttz_i32'):
basic_vars += ['cttz_i8']
if settings['RELOCATABLE']:
basic_vars += ['gb', 'fb']
if not settings['SIDE_MODULE']:
asm_setup += 'var gb = Runtime.GLOBAL_BASE, fb = 0;\n'
asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew']
if not settings['RELOCATABLE']:
asm_runtime_funcs += ['setTempRet0', 'getTempRet0']
else:
basic_funcs += ['setTempRet0', 'getTempRet0']
asm_setup += 'var setTempRet0 = Runtime.setTempRet0, getTempRet0 = Runtime.getTempRet0;\n'
# See if we need ASYNCIFY functions
# We might not need them even if ASYNCIFY is enabled
need_asyncify = '_emscripten_alloc_async_context' in exported_implemented_functions
if need_asyncify:
basic_vars += ['___async', '___async_unwind', '___async_retval', '___async_cur_frame']
asm_runtime_funcs += ['setAsync']
if settings.get('EMTERPRETIFY'):
asm_runtime_funcs += ['emterpret']
if settings.get('EMTERPRETIFY_ASYNC'):
asm_runtime_funcs += ['setAsyncState', 'emtStackSave']
# function tables
if not settings['EMULATED_FUNCTION_POINTERS']:
function_tables = ['dynCall_' + table for table in last_forwarded_json['Functions']['tables']]
else:
function_tables = []
function_tables_impls = []
for sig in last_forwarded_json['Functions']['tables'].iterkeys():
args = ','.join(['a' + str(i) for i in range(1, len(sig))])
arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i], settings) + ';' for i in range(1, len(sig))])
coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings) for i in range(1, len(sig))])
ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0], settings)
if not settings['EMULATED_FUNCTION_POINTERS']:
function_tables_impls.append('''
function dynCall_%s(index%s%s) {
index = index|0;
%s
%s;
}
''' % (sig, ',' if len(sig) > 1 else '', args, arg_coercions, ret))
else:
function_tables_impls.append('''
var dynCall_%s = ftCall_%s;
''' % (sig, sig))
ffi_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings, ffi_arg=True) for i in range(1, len(sig))])
for i in range(settings['RESERVED_FUNCTION_POINTERS']):
jsret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('jsCall_%s(%d%s%s)' % (sig, i, ',' if ffi_args else '', ffi_args), sig[0], settings, ffi_result=True)
function_tables_impls.append('''
function jsCall_%s_%s(%s) {
%s
%s;
}
''' % (sig, i, args, arg_coercions, jsret))
shared.Settings.copy(settings)
asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n'
basic_funcs.append('invoke_%s' % sig)
if settings.get('RESERVED_FUNCTION_POINTERS'):
asm_setup += '\n' + shared.JS.make_jscall(sig) + '\n'
basic_funcs.append('jsCall_%s' % sig)
if settings.get('EMULATED_FUNCTION_POINTERS'):
args = ['a%d' % i for i in range(len(sig)-1)]
full_args = ['x'] + args
table_access = 'FUNCTION_TABLE_' + sig
if settings['SIDE_MODULE']:
table_access = 'parentModule["' + table_access + '"]' # side module tables were merged into the parent, we need to access the global one
prelude = '''
if (x < 0 || x >= %s.length) { Module.printErr("Function table mask error (out of range)"); %s ; abort(x) }''' % (table_access, get_function_pointer_error(sig))
asm_setup += '''
function ftCall_%s(%s) {%s
return %s[x](%s);
}
''' % (sig, ', '.join(full_args), prelude, table_access, ', '.join(args))
basic_funcs.append('ftCall_%s' % sig)
def quote(prop):
if settings['CLOSURE_COMPILER'] == 2:
return "'" + prop + "'"
else:
return prop
def access_quote(prop):
if settings['CLOSURE_COMPILER'] == 2:
return "['" + prop + "']"
else:
return '.' + prop
# calculate exports
exported_implemented_functions = list(exported_implemented_functions) + metadata['initializers']
exported_implemented_functions.append('runPostSets')
if settings['ALLOW_MEMORY_GROWTH']:
exported_implemented_functions.append('_emscripten_replace_memory')
all_exported = exported_implemented_functions + asm_runtime_funcs + function_tables
exported_implemented_functions = list(set(exported_implemented_functions))
if settings['EMULATED_FUNCTION_POINTERS']:
all_exported = list(set(all_exported).union(in_table))
exports = []
for export in all_exported:
exports.append(quote(export) + ": " + export)
exports = '{ ' + ', '.join(exports) + ' }'
# calculate globals
try:
del forwarded_json['Variables']['globals']['_llvm_global_ctors'] # not a true variable
except:
pass
if not settings['RELOCATABLE']:
global_vars = metadata['externs']
else:
global_vars = [] # linkable code accesses globals through function calls
global_funcs = list(set([key for key, value in forwarded_json['Functions']['libraryFunctions'].iteritems() if value != 2]).difference(set(global_vars)).difference(implemented_functions))
if settings['RELOCATABLE']:
global_funcs += ['g$' + extern for extern in metadata['externs']]
side = 'parent' if settings['SIDE_MODULE'] else ''
def check(extern):
if settings['ASSERTIONS']: return 'assert(' + side + 'Module["' + extern + '"]);'
return ''
for extern in metadata['externs']:
asm_setup += 'var g$' + extern + ' = function() { ' + check(extern) + ' return ' + side + 'Module["' + extern + '"] };\n'
def math_fix(g):
return g if not g.startswith('Math_') else g.split('_')[1]
asm_global_funcs = ''.join([' var ' + g.replace('.', '_') + '=global' + access_quote(g) + ';\n' for g in maths]);
asm_global_funcs += ''.join([' var ' + g + '=env' + access_quote(math_fix(g)) + ';\n' for g in basic_funcs + global_funcs])
if metadata['simd']:
asm_global_funcs += ''.join([' var SIMD_' + ty + '=global' + access_quote('SIMD') + access_quote(ty) + ';\n' for ty in simdtypes])
asm_global_funcs += ''.join([' var SIMD_' + ty + '_' + g + '=SIMD_' + ty + access_quote(g) + ';\n' for ty in simdinttypes for g in simdintfuncs])
asm_global_funcs += ''.join([' var SIMD_' + ty + '_' + g + '=SIMD_' + ty + access_quote(g) + ';\n' for ty in simdfloattypes for g in simdfloatfuncs])
asm_global_vars = ''.join([' var ' + g + '=env' + access_quote(g) + '|0;\n' for g in basic_vars + global_vars])
# sent data
the_global = '{ ' + ', '.join(['"' + math_fix(s) + '": ' + s for s in fundamentals]) + ' }'
sending = '{ ' + ', '.join(['"' + math_fix(s) + '": ' + s for s in basic_funcs + global_funcs + basic_vars + basic_float_vars + global_vars]) + ' }'
# received
receiving = ''
if settings['ASSERTIONS']:
# assert on the runtime being in a valid state when calling into compiled code. The only exceptions are
# some support code like malloc TODO: verify that malloc is actually safe to use that way
receiving = '\n'.join(['var real_' + s + ' = asm["' + s + '"]; asm["' + s + '''"] = function() {
assert(runtimeInitialized, 'you need to wait for the runtime to be ready (e.g. wait for main() to be called)');
assert(!runtimeExited, 'the runtime was exited (use NO_EXIT_RUNTIME to keep it alive after main() exits)');
return real_''' + s + '''.apply(null, arguments);
};
''' for s in exported_implemented_functions if s not in ['_malloc', '_free', '_memcpy', '_memset', 'runPostSets']])
if not settings['SWAPPABLE_ASM_MODULE']:
receiving += ';\n'.join(['var ' + s + ' = Module["' + s + '"] = asm["' + s + '"]' for s in exported_implemented_functions + function_tables])
else:
receiving += 'Module["asm"] = asm;\n' + ';\n'.join(['var ' + s + ' = Module["' + s + '"] = function() { return Module["asm"]["' + s + '"].apply(null, arguments) }' for s in exported_implemented_functions + function_tables])
receiving += ';\n'
if settings['EXPORT_FUNCTION_TABLES']:
for table in last_forwarded_json['Functions']['tables'].values():
tableName = table.split()[1]
table = table.replace('var ' + tableName, 'var ' + tableName + ' = Module["' + tableName + '"]')
receiving += table + '\n'
# finalize
if DEBUG: logging.debug('asm text sizes' + str([map(len, funcs_js), len(asm_setup), len(asm_global_vars), len(asm_global_funcs), len(pre_tables), len('\n'.join(function_tables_impls)), len(function_tables_defs.replace('\n', '\n ')), len(exports), len(the_global), len(sending), len(receiving)]))
if not settings.get('EMULATED_FUNCTION_POINTERS'):
final_function_tables = '\n'.join(function_tables_impls) + '\n' + function_tables_defs
else:
asm_setup += '\n' + '\n'.join(function_tables_impls) + '\n'
receiving += '\n' + function_tables_defs + '\n' + ''.join(['Module["dynCall_%s"] = dynCall_%s\n' % (sig, sig) for sig in last_forwarded_json['Functions']['tables']])
for sig in last_forwarded_json['Functions']['tables'].keys():
name = 'FUNCTION_TABLE_' + sig
fullname = name if not settings['SIDE_MODULE'] else ('SIDE_' + name)
receiving += 'Module["' + name + '"] = ' + fullname + ';\n'
final_function_tables = '\n// EMSCRIPTEN_END_FUNCS\n'
if settings['RELOCATABLE']:
receiving += '''
var NAMED_GLOBALS = { %s };
for (var named in NAMED_GLOBALS) {
Module['_' + named] = gb + NAMED_GLOBALS[named];
}
Module['NAMED_GLOBALS'] = NAMED_GLOBALS;
''' % ', '.join('"' + k + '": ' + str(v) for k, v in metadata['namedGlobals'].iteritems())
receiving += ''.join(["Module['%s'] = Module['%s']\n" % (k, v) for k, v in metadata['aliases'].iteritems()])
funcs_js = ['''
%s
Module%s = %s;
Module%s = %s;
// EMSCRIPTEN_START_ASM
var asm = (function(global, env, buffer) {
%s
%s
''' % (asm_setup,
access_quote('asmGlobalArg'), the_global,
access_quote('asmLibraryArg'), sending,
"'use asm';" if not metadata.get('hasInlineJS') and settings['ASM_JS'] == 1 else "'almost asm';", '''
var HEAP8 = new global%s(buffer);
var HEAP16 = new global%s(buffer);
var HEAP32 = new global%s(buffer);
var HEAPU8 = new global%s(buffer);
var HEAPU16 = new global%s(buffer);
var HEAPU32 = new global%s(buffer);
var HEAPF32 = new global%s(buffer);
var HEAPF64 = new global%s(buffer);
''' % (access_quote('Int8Array'),
access_quote('Int16Array'),
access_quote('Int32Array'),
access_quote('Uint8Array'),
access_quote('Uint16Array'),
access_quote('Uint32Array'),
access_quote('Float32Array'),
access_quote('Float64Array')) if not settings['ALLOW_MEMORY_GROWTH'] else '''
var Int8View = global%s;
var Int16View = global%s;
var Int32View = global%s;
var Uint8View = global%s;
var Uint16View = global%s;
var Uint32View = global%s;
var Float32View = global%s;
var Float64View = global%s;
var HEAP8 = new Int8View(buffer);
var HEAP16 = new Int16View(buffer);
var HEAP32 = new Int32View(buffer);
var HEAPU8 = new Uint8View(buffer);
var HEAPU16 = new Uint16View(buffer);
var HEAPU32 = new Uint32View(buffer);
var HEAPF32 = new Float32View(buffer);
var HEAPF64 = new Float64View(buffer);
var byteLength = global.byteLength;
''' % (access_quote('Int8Array'),
access_quote('Int16Array'),
access_quote('Int32Array'),
access_quote('Uint8Array'),
access_quote('Uint16Array'),
access_quote('Uint32Array'),
access_quote('Float32Array'),
access_quote('Float64Array'))) + '\n' + asm_global_vars + ('''
var __THREW__ = 0;
var threwValue = 0;
var setjmpId = 0;
var undef = 0;
var nan = global%s, inf = global%s;
var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0;
''' % (access_quote('NaN'), access_quote('Infinity'))) + ''.join(['''
var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + \
[' var tempFloat = %s;\n' % ('Math_fround(0)' if provide_fround else '0.0')] + \
[' var asyncState = 0;\n' if settings.get('EMTERPRETIFY_ASYNC') else ''] + \
([' const f0 = Math_fround(0);\n'] if provide_fround else []) + \
['' if not settings['ALLOW_MEMORY_GROWTH'] else '''
function _emscripten_replace_memory(newBuffer) {
if ((byteLength(newBuffer) & 0xffffff || byteLength(newBuffer) <= 0xffffff) || byteLength(newBuffer) > 0x80000000) return false;
HEAP8 = new Int8View(newBuffer);
HEAP16 = new Int16View(newBuffer);
HEAP32 = new Int32View(newBuffer);
HEAPU8 = new Uint8View(newBuffer);
HEAPU16 = new Uint16View(newBuffer);
HEAPU32 = new Uint32View(newBuffer);
HEAPF32 = new Float32View(newBuffer);
HEAPF64 = new Float64View(newBuffer);
buffer = newBuffer;
return true;
}
'''] + ['''
// EMSCRIPTEN_START_FUNCS
function stackAlloc(size) {
size = size|0;
var ret = 0;
ret = STACKTOP;
STACKTOP = (STACKTOP + size)|0;
STACKTOP = (STACKTOP + 15)&-16;
''' + ('if ((STACKTOP|0) >= (STACK_MAX|0)) abort();\n' if settings['ASSERTIONS'] else '') + '''
return ret|0;
}
function stackSave() {
return STACKTOP|0;
}
function stackRestore(top) {
top = top|0;
STACKTOP = top;
}
''' + ('''
function setAsync() {
___async = 1;
}''' if need_asyncify else '') + ('''
function emterpret(pc) { // this will be replaced when the emterpreter code is generated; adding it here allows validation until then
pc = pc | 0;
assert(0);
}
''' if settings['EMTERPRETIFY'] else '') + ('''
function setAsyncState(x) {
x = x | 0;
asyncState = x;
}
function emtStackSave() {
return EMTSTACKTOP|0;
}
''' if settings['EMTERPRETIFY_ASYNC'] else '') + '''
function setThrew(threw, value) {
threw = threw|0;
value = value|0;
if ((__THREW__|0) == 0) {
__THREW__ = threw;
threwValue = value;
}
}
function copyTempFloat(ptr) {
ptr = ptr|0;
HEAP8[tempDoublePtr>>0] = HEAP8[ptr>>0];
HEAP8[tempDoublePtr+1>>0] = HEAP8[ptr+1>>0];
HEAP8[tempDoublePtr+2>>0] = HEAP8[ptr+2>>0];
HEAP8[tempDoublePtr+3>>0] = HEAP8[ptr+3>>0];
}
function copyTempDouble(ptr) {
ptr = ptr|0;
HEAP8[tempDoublePtr>>0] = HEAP8[ptr>>0];
HEAP8[tempDoublePtr+1>>0] = HEAP8[ptr+1>>0];
HEAP8[tempDoublePtr+2>>0] = HEAP8[ptr+2>>0];
HEAP8[tempDoublePtr+3>>0] = HEAP8[ptr+3>>0];
HEAP8[tempDoublePtr+4>>0] = HEAP8[ptr+4>>0];
HEAP8[tempDoublePtr+5>>0] = HEAP8[ptr+5>>0];
HEAP8[tempDoublePtr+6>>0] = HEAP8[ptr+6>>0];
HEAP8[tempDoublePtr+7>>0] = HEAP8[ptr+7>>0];
}
'''] + ['''
function setTempRet0(value) {
value = value|0;
tempRet0 = value;
}
function getTempRet0() {
return tempRet0|0;
}
''' if not settings['RELOCATABLE'] else ''] + funcs_js + ['''
%s
return %s;
})
// EMSCRIPTEN_END_ASM
(%s, %s, buffer);
%s;
''' % (pre_tables + final_function_tables, exports,
'Module' + access_quote('asmGlobalArg'),
'Module' + access_quote('asmLibraryArg'),
receiving)]
if not settings.get('SIDE_MODULE'):
funcs_js.append('''
Runtime.stackAlloc = asm['stackAlloc'];
Runtime.stackSave = asm['stackSave'];
Runtime.stackRestore = asm['stackRestore'];
''')
if not settings['RELOCATABLE']:
funcs_js.append('''
Runtime.setTempRet0 = asm['setTempRet0'];
Runtime.getTempRet0 = asm['getTempRet0'];
''')
# Set function table masks
masks = {}
max_mask = 0
for sig, table in last_forwarded_json['Functions']['tables'].iteritems():
mask = table.count(',')
masks[sig] = str(mask)
max_mask = max(mask, max_mask)
def function_table_maskize(js, masks):
def fix(m):
sig = m.groups(0)[0]
return masks[sig]
return re.sub(r'{{{ FTM_([\w\d_$]+) }}}', lambda m: fix(m), js) # masks[m.groups(0)[0]]
funcs_js = map(lambda js: function_table_maskize(js, masks), funcs_js)
if settings['SIDE_MODULE']:
funcs_js.append('''
Runtime.registerFunctions(%(sigs)s, Module);
''' % { 'sigs': str(map(str, last_forwarded_json['Functions']['tables'].keys())) })
for i in range(len(funcs_js)): # do this loop carefully to save memory
if WINDOWS: funcs_js[i] = funcs_js[i].replace('\r\n', '\n') # Normalize to UNIX line endings, otherwise writing to text file will duplicate \r\n to \r\r\n!
outfile.write(funcs_js[i])
funcs_js = None
if WINDOWS: post = post.replace('\r\n', '\n') # Normalize to UNIX line endings, otherwise writing to text file will duplicate \r\n to \r\r\n!
outfile.write(post)
outfile.close()
if DEBUG: logging.debug(' emscript: final python processing took %s seconds' % (time.time() - t))
success = True
finally:
if not success:
outfile.close()
shared.try_delete(outfile.name) # remove partial output
if os.environ.get('EMCC_FAST_COMPILER') == '0':
logging.critical('Non-fastcomp compiler is no longer available, please use fastcomp or an older version of emscripten')
sys.exit(1)
def main(args, compiler_engine, cache, temp_files, DEBUG, DEBUG_CACHE):
# Prepare settings for serialization to JSON.
settings = {}
for setting in args.settings:
name, value = setting.strip().split('=', 1)
settings[name] = json.loads(value)
# libraries
libraries = args.libraries[0].split(',') if len(args.libraries) > 0 else []
settings.setdefault('STRUCT_INFO', cache.get_path('struct_info.compiled.json'))
struct_info = settings.get('STRUCT_INFO')
if not os.path.exists(struct_info) and not settings.get('BOOTSTRAPPING_STRUCT_INFO'):
if DEBUG: logging.debug(' emscript: bootstrapping struct info...')
shared.Building.ensure_struct_info(struct_info)
if DEBUG: logging.debug(' emscript: bootstrapping struct info complete')
emscript(args.infile, settings, args.outfile, libraries, compiler_engine=compiler_engine,
temp_files=temp_files, DEBUG=DEBUG, DEBUG_CACHE=DEBUG_CACHE)
def _main(environ):
response_file = True
while response_file:
response_file = None
for index in range(1, len(sys.argv)):
if sys.argv[index][0] == '@':
# found one, loop again next time
response_file = True
response_file_args = read_response_file(sys.argv[index])
# slice in extra_args in place of the response file arg
sys.argv[index:index+1] = response_file_args
break
parser = optparse.OptionParser(
usage='usage: %prog [-h] [-H HEADERS] [-o OUTFILE] [-c COMPILER_ENGINE] [-s FOO=BAR]* infile',
description=('You should normally never use this! Use emcc instead. '
'This is a wrapper around the JS compiler, converting .ll to .js.'),
epilog='')
parser.add_option('-H', '--headers',
default=[],
action='append',
help='System headers (comma separated) whose #defines should be exposed to the compiled code.')
parser.add_option('-L', '--libraries',
default=[],
action='append',
help='Library files (comma separated) to use in addition to those in emscripten src/library_*.')
parser.add_option('-o', '--outfile',
default=sys.stdout,
help='Where to write the output; defaults to stdout.')
parser.add_option('-c', '--compiler',
default=None,
help='Which JS engine to use to run the compiler; defaults to the one in ~/.emscripten.')
parser.add_option('-s', '--setting',
dest='settings',
default=[],
action='append',
metavar='FOO=BAR',
help=('Overrides for settings defined in settings.js. '
'May occur multiple times.'))
parser.add_option('-T', '--temp-dir',
default=None,
help=('Where to create temporary files.'))
parser.add_option('-v', '--verbose',
action='store_true',
dest='verbose',
help='Displays debug output')
parser.add_option('-q', '--quiet',
action='store_false',
dest='verbose',
help='Hides debug output')
parser.add_option('--suppressUsageWarning',
action='store_true',
default=environ.get('EMSCRIPTEN_SUPPRESS_USAGE_WARNING'),
help=('Suppress usage warning'))
# Convert to the same format that argparse would have produced.
keywords, positional = parser.parse_args()
if not keywords.suppressUsageWarning:
logging.warning('''
==============================================================
WARNING: You should normally never use this! Use emcc instead.
==============================================================
''')
if len(positional) != 1:
raise RuntimeError('Must provide exactly one positional argument. Got ' + str(len(positional)) + ': "' + '", "'.join(positional) + '"')
keywords.infile = os.path.abspath(positional[0])
if isinstance(keywords.outfile, basestring):
keywords.outfile = open(keywords.outfile, 'w')
if keywords.temp_dir is None:
temp_files = get_configuration().get_temp_files()
temp_dir = get_configuration().TEMP_DIR
else:
temp_dir = os.path.abspath(keywords.temp_dir)
if not os.path.exists(temp_dir):
os.makedirs(temp_dir)
temp_files = tempfiles.TempFiles(temp_dir)
if keywords.compiler is None:
keywords.compiler = shared.COMPILER_ENGINE
if keywords.verbose is None:
DEBUG = get_configuration().DEBUG
DEBUG_CACHE = get_configuration().DEBUG_CACHE
else:
DEBUG = keywords.verbose
DEBUG_CACHE = keywords.verbose
cache = cache_module.Cache()
temp_files.run_and_clean(lambda: main(
keywords,
compiler_engine=keywords.compiler,
cache=cache,
temp_files=temp_files,
DEBUG=DEBUG,
DEBUG_CACHE=DEBUG_CACHE,
))
if __name__ == '__main__':
_main(environ=os.environ)