summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xemcc60
-rw-r--r--emlink.py293
-rwxr-xr-xemscripten.py40
-rw-r--r--src/intertyper.js2
-rw-r--r--src/jsifier.js69
-rw-r--r--src/library.js4
-rw-r--r--src/library_gl.js112
-rw-r--r--src/modules.js17
-rw-r--r--src/parseTools.js25
-rw-r--r--src/preamble.js5
-rw-r--r--src/settings.js6
-rwxr-xr-xtests/runner.py251
-rw-r--r--tools/js-optimizer.js32
-rw-r--r--tools/js_optimizer.py31
-rw-r--r--tools/shared.py40
-rw-r--r--tools/test-js-optimizer-asm-pre-output.js10
-rw-r--r--tools/test-js-optimizer-asm-pre.js12
-rw-r--r--tools/test-js-optimizer-asm-relocate-output.js8
-rw-r--r--tools/test-js-optimizer-asm-relocate.js10
19 files changed, 849 insertions, 178 deletions
diff --git a/emcc b/emcc
index 75750e1e..90a2ea71 100755
--- a/emcc
+++ b/emcc
@@ -1066,6 +1066,17 @@ try:
logging.warning('disabling LLVM optimizations, need typed arrays mode 2 for them')
llvm_opts = 0
+ if shared.Settings.MAIN_MODULE:
+ assert not shared.Settings.SIDE_MODULE
+ shared.Settings.INCLUDE_FULL_LIBRARY = 1
+ elif shared.Settings.SIDE_MODULE:
+ assert not shared.Settings.MAIN_MODULE
+
+ if shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE:
+ assert not memory_init_file, 'memory init file is not supported with module linking'
+ shared.Settings.LINKABLE = 1 # TODO: add FORCE_DCE option for the brave people that do want to dce here and in side modules
+ debug_level = max(debug_level, 2)
+
## Compile source code to bitcode
logging.debug('compiling to bitcode')
@@ -1140,7 +1151,9 @@ try:
extra_files_to_link = []
if not LEAVE_INPUTS_RAW and not AUTODEBUG and \
- not shared.Settings.BUILD_AS_SHARED_LIB == 2: # shared lib 2 use the library in the parent
+ not shared.Settings.BUILD_AS_SHARED_LIB == 2 and \
+ not shared.Settings.SIDE_MODULE: # shared libraries/side modules link no C libraries, need them in parent
+
# Check if we need to include some libraries that we compile. (We implement libc ourselves in js, but
# compile a malloc implementation and stdlibc++.)
@@ -1352,7 +1365,9 @@ try:
return 'SDL_Init' in all_needed and ('malloc' not in all_needed or 'free' not in all_needed)
# Settings this in the environment will avoid checking dependencies and make building big projects a little faster
+ # 1 means include everything; otherwise it can be the name of a lib (libcxx, etc.)
force = os.environ.get('EMCC_FORCE_STDLIBS')
+ force_all = force == '1'
# Scan symbols
all_needed = set()
@@ -1370,7 +1385,8 @@ try:
('libcxxabi', create_libcxxabi, apply_libcxxabi, libcxxabi_symbols),
('sdl', create_sdl, apply_sdl, sdl_symbols),
('libc', create_libc, apply_libc, libc_symbols)]:
- if not force:
+ force_this = force_all or force == name
+ if not force_this:
need = set()
has = set()
for symbols in symbolses:
@@ -1383,12 +1399,13 @@ try:
if haz in need:
need.remove(haz)
if shared.Settings.VERBOSE: logging.debug('considering %s: we need %s and have %s' % (name, str(need), str(has)))
- if (force or len(need) > 0) and apply_(need):
- # We need to build and link the library in
- logging.debug('including %s' % name)
- libfile = shared.Cache.get(name, create)
- extra_files_to_link.append(libfile)
- force = True
+ if force_this or len(need) > 0:
+ force_all = True
+ if apply_(need):
+ # We need to build and link the library in
+ logging.debug('including %s' % name)
+ libfile = shared.Cache.get(name, create)
+ extra_files_to_link.append(libfile)
# First, combine the bitcode files if there are several. We must also link if we have a singleton .a
if len(input_files) + len(extra_files_to_link) > 1 or \
@@ -1396,7 +1413,7 @@ try:
linker_inputs = temp_files + extra_files_to_link
logging.debug('linking: ' + str(linker_inputs))
t0 = time.time()
- shared.Building.link(linker_inputs, in_temp(target_basename + '.bc'))
+ shared.Building.link(linker_inputs, in_temp(target_basename + '.bc'), force_archive_contents = len(filter(lambda temp: not temp.endswith(STATICLIB_SUFFIXES), temp_files)) == 0)
t1 = time.time()
logging.debug(' linking took %.2f seconds' % (t1 - t0))
final = in_temp(target_basename + '.bc')
@@ -1433,17 +1450,16 @@ try:
shared.Building.llvm_opt(in_temp(target_basename + '.bc'), ['-O3'])
if DEBUG: save_intermediate('opt', 'bc')
- if shared.Building.can_build_standalone():
- # If we can LTO, do it before dce, since it opens up dce opportunities
- if llvm_lto and llvm_lto != 2 and shared.Building.can_use_unsafe_opts():
- if not shared.Building.can_inline(): link_opts.append('-disable-inlining')
- # do not internalize in std-link-opts - it ignores internalize-public-api-list - and add a manual internalize
- link_opts += ['-disable-internalize'] + shared.Building.get_safe_internalize() + ['-std-link-opts']
- else:
- # At minimum remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it)
- link_opts += shared.Building.get_safe_internalize() + ['-globaldce']
- shared.Building.llvm_opt(in_temp(target_basename + '.bc'), link_opts)
- if DEBUG: save_intermediate('linktime', 'bc')
+ # If we can LTO, do it before dce, since it opens up dce opportunities
+ if shared.Building.can_build_standalone() and llvm_lto and llvm_lto != 2 and shared.Building.can_use_unsafe_opts():
+ if not shared.Building.can_inline(): link_opts.append('-disable-inlining')
+ # do not internalize in std-link-opts - it ignores internalize-public-api-list - and add a manual internalize
+ link_opts += ['-disable-internalize'] + shared.Building.get_safe_internalize() + ['-std-link-opts']
+ else:
+ # At minimum remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it)
+ link_opts += shared.Building.get_safe_internalize() + ['-globaldce']
+ shared.Building.llvm_opt(in_temp(target_basename + '.bc'), link_opts)
+ if DEBUG: save_intermediate('linktime', 'bc')
if save_bc:
shutil.copyfile(final, save_bc)
@@ -1584,7 +1600,7 @@ try:
if closure and shared.Settings.ASM_JS:
js_optimizer_queue += ['closure']
- js_optimizer_queue += ['last']
+ if not shared.Settings.SIDE_MODULE: js_optimizer_queue += ['last'] # side modules are not finalized until after relocation
flush_js_optimizer_queue()
@@ -1611,7 +1627,7 @@ try:
if os.path.abspath(memfile) != os.path.abspath(memfile):
shutil.copyfile(memfile, temp_memfile)
return 'loadMemoryInitializer("%s");' % os.path.basename(memfile)
- src = re.sub('/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, Runtime\.GLOBAL_BASE\)', repl, src, count=1)
+ src = re.sub(shared.JS.memory_initializer_pattern, repl, src, count=1)
open(final + '.mem.js', 'w').write(src)
final += '.mem.js'
js_transform_tempfiles[-1] = final # simple text substitution preserves comment line number mappings
diff --git a/emlink.py b/emlink.py
new file mode 100644
index 00000000..0a209cc7
--- /dev/null
+++ b/emlink.py
@@ -0,0 +1,293 @@
+#!/usr/bin/env python2
+
+'''
+Fast static linker for emscripten outputs. Specifically this links asm.js modules.
+
+See https://github.com/kripken/emscripten/wiki/Linking
+'''
+
+import os, subprocess, sys, re
+from tools import shared
+from tools import js_optimizer
+
+try:
+ me, main, side, out = sys.argv[:4]
+except:
+ print >> sys.stderr, 'usage: emlink.py [main module] [side module] [output name]'
+ sys.exit(1)
+
+print 'Main module:', main
+print 'Side module:', side
+print 'Output:', out
+
+shared.try_delete(out)
+
+class AsmModule():
+ def __init__(self, filename):
+ self.filename = filename
+ self.js = open(filename).read()
+
+ self.start_asm = self.js.find(js_optimizer.start_asm_marker)
+ self.start_funcs = self.js.find(js_optimizer.start_funcs_marker)
+ self.end_funcs = self.js.rfind(js_optimizer.end_funcs_marker)
+ self.end_asm = self.js.rfind(js_optimizer.end_asm_marker)
+
+ # pre
+ self.pre_js = self.js[:self.start_asm]
+
+ # heap initializer
+ self.staticbump = int(re.search(shared.JS.memory_staticbump_pattern, self.pre_js).group(1))
+ if self.staticbump:
+ self.mem_init_js = re.search(shared.JS.memory_initializer_pattern, self.pre_js).group(0)
+
+ # global initializers
+ global_inits = re.search(shared.JS.global_initializers_pattern, self.pre_js)
+ if global_inits:
+ self.global_inits_js = global_inits.group(0)
+ self.global_inits = map(lambda init: init.split('{')[2][1:].split('(')[0], global_inits.groups(0)[0].split(','))
+ else:
+ self.global_inits_js = ''
+ self.global_inits = []
+
+ # imports (and global variables)
+ first_var = self.js.find('var ', self.js.find('var ', self.start_asm)+4)
+ self.pre_imports_js = self.js[self.start_asm:first_var]
+ self.imports_js = self.js[first_var:self.start_funcs]
+ self.imports = {}
+ for imp in js_optimizer.import_sig.finditer(self.imports_js):
+ key, value = imp.group(0).split('var ')[1][:-1].split('=', 1)
+ self.imports[key] = value
+ #print >> sys.stderr, 'imports', self.imports
+
+ # funcs
+ self.funcs_js = self.js[self.start_funcs:self.end_funcs]
+ self.funcs = set([m.group(2) for m in js_optimizer.func_sig.finditer(self.funcs_js)])
+ #print 'funcs', self.funcs
+
+ # tables and exports
+ post_js = self.js[self.end_funcs:self.end_asm]
+ ret = post_js.find('return')
+ self.tables_js = post_js[:ret]
+ self.exports_js = post_js[ret:]
+ self.tables = self.parse_tables(self.tables_js)
+ self.exports = set([export.strip() for export in self.exports_js[self.exports_js.find('{')+1:self.exports_js.find('}')].split(',')])
+
+ # post
+ self.post_js = self.js[self.end_asm:]
+ self.sendings = {}
+ for sending in [sending.strip() for sending in self.post_js[self.post_js.find('}, { ')+5:self.post_js.find(' }, buffer);')].split(',')]:
+ colon = sending.find(':')
+ self.sendings[sending[:colon].replace('"', '')] = sending[colon+1:].strip()
+ self.module_defs = set(re.findall('var [\w\d_$]+ = Module\["[\w\d_$]+"\] = asm\["[\w\d_$]+"\];\n', self.post_js))
+
+ def relocate_into(self, main):
+ # heap initializer
+ if self.staticbump > 0:
+ new_mem_init = self.mem_init_js[:self.mem_init_js.rfind(', ')] + ', Runtime.GLOBAL_BASE+%d)' % main.staticbump
+ main.pre_js = re.sub(shared.JS.memory_staticbump_pattern, 'STATICTOP = STATIC_BASE + %d;\n' % (main.staticbump + side.staticbump) + new_mem_init, main.pre_js, count=1)
+
+ # Find function name replacements TODO: do not rename duplicate names with duplicate contents, just merge them
+ replacements = {}
+ for func in self.funcs:
+ rep = func
+ while rep in main.funcs:
+ rep += '_'
+ replacements[func] = rep
+ #print >> sys.stderr, 'replacements:', replacements
+
+ # sendings: add invokes for new tables
+ all_sendings = main.sendings
+ added_sending = False
+ for table in self.tables:
+ if table not in main.tables:
+ sig = table[table.rfind('_')+1:]
+ all_sendings['invoke_%s' % sig] = shared.JS.make_invoke(sig, named=False)
+ added_sending = True
+
+ # imports
+ all_imports = main.imports
+ for key, value in self.imports.iteritems():
+ if key in self.funcs or key in main.funcs: continue # external function in one module, implemented in the other
+ value_concrete = '.' not in value # env.key means it is an import, an external value, and not a concrete one
+ main_value = main.imports.get(key)
+ main_value_concrete = main_value and '.' not in main_value
+ if value_concrete and main_value_concrete: continue # standard global var
+ if not main_value or value_concrete:
+ if '+' in value:
+ # relocate
+ value = value.replace('(', '').replace(')', '').replace('| 0', '').replace('|0', '').replace(' ', '')
+ left, right = value.split('+')
+ assert left == 'H_BASE'
+ value = str(main.staticbump + int(right))
+ all_imports[key] = value
+ if (value_concrete or main_value_concrete) and key in all_sendings:
+ del all_sendings[key] # import of external value no longer needed
+ main.imports_js = '\n'.join(['var %s = %s;' % (key, value) for key, value in all_imports.iteritems()]) + '\n'
+
+ if added_sending:
+ sendings_js = ', '.join(['%s: %s' % (key, value) for key, value in all_sendings.iteritems()])
+ sendings_start = main.post_js.find('}, { ')+5
+ sendings_end = main.post_js.find(' }, buffer);')
+ main.post_js = main.post_js[:sendings_start] + sendings_js + main.post_js[sendings_end:]
+
+ # check for undefined references to global variables
+ def check_import(key, value):
+ if value.startswith('+') or value.endswith('|0'): # ignore functions
+ if key not in all_sendings:
+ print >> sys.stderr, 'external variable %s is still not defined after linking' % key
+ for key, value in all_imports.iteritems(): check_import(key, value)
+
+ # tables
+ f_bases = {}
+ f_sizes = {}
+ for table, data in self.tables.iteritems():
+ main.tables[table] = self.merge_tables(table, main.tables.get(table), data, replacements, f_bases, f_sizes)
+ main.combine_tables()
+ #print >> sys.stderr, 'f bases', f_bases
+
+ # relocate
+ temp = shared.Building.js_optimizer(self.filename, ['asm', 'relocate', 'last'], extra_info={
+ 'replacements': replacements,
+ 'fBases': f_bases,
+ 'hBase': main.staticbump
+ })
+ #print >> sys.stderr, 'relocated side into', temp
+ relocated_funcs = AsmModule(temp)
+ shared.try_delete(temp)
+ main.extra_funcs_js = relocated_funcs.funcs_js.replace(js_optimizer.start_funcs_marker, '\n')
+
+ # update function table uses
+ ft_marker = 'FUNCTION_TABLE_'
+
+ def update_fts(what):
+ updates = []
+ i = 1 # avoid seeing marker in recursion
+ while 1:
+ i = what.find(ft_marker, i)
+ if i < 0: break;
+ start = i
+ end = what.find('[', start)
+ table = what[i:end]
+ if table not in f_sizes:
+ # table was not modified
+ i += len(ft_marker)
+ continue
+ nesting = 1
+ while nesting > 0:
+ next = what.find(']', end+1)
+ nesting -= 1
+ nesting += what.count('[', end+1, next)
+ end = next
+ assert end > 0
+ mask = what.rfind('&', start, end)
+ assert mask > 0 and end - mask <= 13
+ fixed = update_fts(what[start:mask+1] + str(f_sizes[table]-1) + ']')
+ updates.append((start, end, fixed))
+ i = end # additional function table uses were done by recursion
+ # apply updates
+ if len(updates) == 0: return what
+ parts = []
+ so_far = 0
+ for i in range(len(updates)):
+ start, end, fixed = updates[i]
+ parts.append(what[so_far:start])
+ parts.append(fixed)
+ so_far = end+1
+ parts.append(what[so_far:])
+ return ''.join(parts)
+
+ main.funcs_js = update_fts(main.funcs_js)
+ main.extra_funcs_js = update_fts(main.extra_funcs_js)
+
+ # global initializers
+ if self.global_inits:
+ my_global_inits = map(lambda init: replacements[init] if init in replacements else init, self.global_inits)
+ all_global_inits = map(lambda init: '{ func: function() { %s() } }' % init, main.global_inits + my_global_inits)
+ all_global_inits_js = '/* global initializers */ __ATINIT__.push(' + ','.join(all_global_inits) + ');'
+ if main.global_inits:
+ target = main.global_inits_js
+ else:
+ target = '// === Body ===\n'
+ all_global_inits_js = target + all_global_inits_js
+ main.pre_js = main.pre_js.replace(target, all_global_inits_js)
+
+ # exports
+ def rep_exp(export):
+ key, value = export.split(':')
+ if key in replacements:
+ repped = replacements[key]
+ return repped + ': ' + repped
+ return export
+ my_exports = map(rep_exp, self.exports)
+ exports = main.exports.union(my_exports)
+ main.exports_js = 'return {' + ','.join(list(exports)) + '};\n})\n'
+
+ # post
+ def rep_def(deff):
+ key = deff.split(' ')[1]
+ if key in replacements:
+ rep = replacements[key]
+ return 'var %s = Module["%s"] = asm["%s"];\n' % (rep, rep, rep)
+ return deff
+ my_module_defs = map(rep_def, self.module_defs)
+ new_module_defs = set(my_module_defs).difference(main.module_defs)
+ if len(new_module_defs) > 0:
+ position = main.post_js.find('Runtime.') # Runtime is the start of the hardcoded ones
+ main.post_js = main.post_js[:position] + ''.join(list(new_module_defs)) + '\n' + main.post_js[position:]
+
+ def write(self, out):
+ f = open(out, 'w')
+ f.write(self.pre_js)
+ f.write(self.pre_imports_js)
+ f.write(self.imports_js)
+ f.write(self.funcs_js)
+ f.write(self.extra_funcs_js)
+ f.write(self.tables_js)
+ f.write(self.exports_js)
+ f.write(self.post_js)
+ f.close()
+
+ # Utilities
+
+ def parse_tables(self, js):
+ tables = {}
+ parts = js.split(';')
+ for part in parts:
+ if '=' not in part: continue
+ part = part.split('var ')[1]
+ name, data = part.split(' = ')
+ tables[name] = data
+ return tables
+
+ def merge_tables(self, table, main, side, replacements, f_bases, f_sizes):
+ sig = table.split('_')[-1]
+ side = side[1:-1].split(',')
+ side = map(lambda f: replacements[f] if f in replacements else f, side)
+ if not main:
+ f_bases[sig] = 0
+ f_sizes[table] = len(side)
+ return '[' + ','.join(side) + ']'
+ main = main[1:-1].split(',')
+ # TODO: handle non-aliasing case too
+ assert len(main) % 2 == 0
+ f_bases[sig] = len(main)
+ ret = main + side
+ size = 2
+ while size < len(ret): size *= 2
+ aborter = ret[1] # we can assume odd indexes have an aborting function with the right signature
+ ret = ret + [aborter]*(size - len(ret))
+ assert len(ret) == size
+ f_sizes[table] = size
+ return '[' + ','.join(ret) + ']'
+
+ def combine_tables(self):
+ self.tables_js = '// EMSCRIPTEN_END_FUNCS\n'
+ for table, data in self.tables.iteritems():
+ self.tables_js += 'var %s = %s;\n' % (table, data)
+
+main = AsmModule(main)
+side = AsmModule(side)
+
+side.relocate_into(main)
+main.write(out)
+
diff --git a/emscripten.py b/emscripten.py
index 56f59273..df0587f9 100755
--- a/emscripten.py
+++ b/emscripten.py
@@ -11,6 +11,7 @@ headers, for the libc implementation in JS).
import os, sys, json, optparse, subprocess, re, time, multiprocessing, functools
+from tools import shared
from tools import jsrun, cache as cache_module, tempfiles
from tools.response_file import read_response_file
@@ -25,7 +26,6 @@ def get_configuration():
if hasattr(get_configuration, 'configuration'):
return get_configuration.configuration
- from tools import shared
configuration = shared.Configuration(environ=os.environ)
get_configuration.configuration = configuration
return configuration
@@ -341,8 +341,18 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None,
ret = re.sub(r'"?{{{ BA_([\w\d_$]+)\|([\w\d_$]+) }}}"?,0,0,0', lambda m: split_32(blockaddrs[m.groups(0)[0]][m.groups(0)[1]]), js)
return re.sub(r'"?{{{ BA_([\w\d_$]+)\|([\w\d_$]+) }}}"?', lambda m: str(blockaddrs[m.groups(0)[0]][m.groups(0)[1]]), ret)
+ pre = blockaddrsize(indexize(pre))
+
+ if settings.get('ASM_JS'):
+ # move postsets into the asm module
+ class PostSets: js = ''
+ def handle_post_sets(m):
+ PostSets.js = m.group(0)
+ return '\n'
+ pre = re.sub(r'function runPostSets[^}]+}', handle_post_sets, pre)
+
#if DEBUG: outfile.write('// pre\n')
- outfile.write(blockaddrsize(indexize(pre)))
+ outfile.write(pre)
pre = None
#if DEBUG: outfile.write('// funcs\n')
@@ -453,23 +463,12 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None,
}
''' % (sig, i, args, arg_coercions, jsret))
- args = ','.join(['a' + str(i) for i in range(1, len(sig))])
- args = 'index' + (',' if args else '') + args
- # C++ exceptions are numbers, and longjmp is a string 'longjmp'
- asm_setup += '''
-function invoke_%s(%s) {
- try {
- %sModule["dynCall_%s"](%s);
- } catch(e) {
- if (typeof e !== 'number' && e !== 'longjmp') throw e;
- asm["setThrew"](1, 0);
- }
-}
-''' % (sig, args, 'return ' if sig[0] != 'v' else '', sig, args)
+ asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n'
basic_funcs.append('invoke_%s' % sig)
# calculate exports
exported_implemented_functions = list(exported_implemented_functions)
+ exported_implemented_functions.append('runPostSets')
exports = []
if not simple:
for export in exported_implemented_functions + asm_runtime_funcs + function_tables:
@@ -491,6 +490,15 @@ function invoke_%s(%s) {
''.join([' var ' + g + '=env.' + math_fix(g) + ';\n' for g in basic_funcs + global_funcs])
asm_global_vars = ''.join([' var ' + g + '=env.' + g + '|0;\n' for g in basic_vars + global_vars]) + \
''.join([' var ' + g + '=+env.' + g + ';\n' for g in basic_float_vars])
+ # In linkable modules, we need to add some explicit globals for global variables that can be linked and used across modules
+ if settings.get('MAIN_MODULE') or settings.get('SIDE_MODULE'):
+ assert settings.get('TARGET_LE32'), 'TODO: support x86 target when linking modules (needs offset of 4 and not 8 here)'
+ for key, value in forwarded_json['Variables']['globals'].iteritems():
+ if value.get('linkable'):
+ init = forwarded_json['Variables']['indexedGlobals'][key] + 8 # 8 is Runtime.GLOBAL_BASE / STATIC_BASE
+ if settings.get('SIDE_MODULE'): init = '(H_BASE+' + str(init) + ')|0'
+ asm_global_vars += ' var %s=%s;\n' % (key, str(init))
+
# 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]) + ' }'
@@ -578,7 +586,7 @@ var asm = (function(global, env, buffer) {
value = value|0;
tempRet%d = value;
}
-''' % (i, i) for i in range(10)])] + funcs_js + ['''
+''' % (i, i) for i in range(10)])] + [PostSets.js + '\n'] + funcs_js + ['''
%s
return %s;
diff --git a/src/intertyper.js b/src/intertyper.js
index 6da30ae8..94d937e1 100644
--- a/src/intertyper.js
+++ b/src/intertyper.js
@@ -502,7 +502,7 @@ function intertyper(data, sidePass, baseLineNums) {
} else {
// variable
var ident = item.tokens[0].text;
- var private_ = findTokenText(item, 'private') >= 0;
+ var private_ = findTokenText(item, 'private') >= 0 || findTokenText(item, 'internal') >= 0;
cleanOutTokens(LLVM.GLOBAL_MODIFIERS, item.tokens, [2, 3]);
var external = false;
if (item.tokens[2].text === 'external') {
diff --git a/src/jsifier.js b/src/jsifier.js
index 885fbc30..3f8c184c 100644
--- a/src/jsifier.js
+++ b/src/jsifier.js
@@ -264,9 +264,9 @@ function JSify(data, functionsOnly, givenFunctions) {
assert(!item.lines); // FIXME remove this, after we are sure it isn't needed
var ret = [item];
if (item.ident == '_llvm_global_ctors') {
- item.JS = '\n__ATINIT__ = __ATINIT__.concat([\n' +
- item.ctors.map(function(ctor) { return ' { func: function() { ' + ctor + '() } }' }).join(',\n') +
- '\n]);\n';
+ item.JS = '\n/* global initializers */ __ATINIT__.push(' +
+ item.ctors.map(function(ctor) { return '{ func: function() { ' + ctor + '() } }' }).join(',') +
+ ');\n';
return ret;
}
@@ -286,6 +286,11 @@ function JSify(data, functionsOnly, givenFunctions) {
allocator = 'ALLOC_NONE';
}
+ if (ASM_JS && (MAIN_MODULE || SIDE_MODULE) && !item.private_ && !NAMED_GLOBALS && isIndexableGlobal(item.ident)) {
+ // We need this to be named (and it normally would not be), so that it can be linked to and used from other modules
+ Variables.globals[item.ident].linkable = 1;
+ }
+
if (isBSS(item)) {
var length = calcAllocatedSize(item.type);
length = Runtime.alignMemory(length);
@@ -374,22 +379,11 @@ function JSify(data, functionsOnly, givenFunctions) {
// Set the actual value in a postset, since it may be a global variable. We also order by dependencies there
Variables.globals[item.ident].targetIdent = item.value.ident;
var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value);
- var fix = '';
- if (BUILD_AS_SHARED_LIB == 2 && !item.private_) {
- var target = item.ident;
- if (isFunctionType(item.type)) {
- target = item.value.ident; // the other side does not know this is an alias/function table index. So make it the alias target.
- var varData = Variables.globals[target];
- assert(!varData, 'multi-level aliasing does not work yet in shared lib 2 exports');
- }
- fix = '\nif (globalScope) { assert(!globalScope["' + item.ident + '"]); globalScope["' + item.ident + '"] = ' + target + ' }'
+ if ((MAIN_MODULE || SIDE_MODULE) && isFunctionType(item.type)) {
+ var target = item.value.ident;
+ if (!Functions.aliases[target]) Functions.aliases[target] = [];
+ Functions.aliases[target].push(item.ident);
}
- ret.push({
- intertype: 'GlobalVariablePostSet',
- ident: item.ident,
- dependencies: set([value]),
- JS: item.ident + ' = ' + value + ';' + fix
- });
return ret;
}
});
@@ -847,6 +841,19 @@ function JSify(data, functionsOnly, givenFunctions) {
}
func.JS = func.JS.replace(/\n *;/g, '\n'); // remove unneeded lines
+
+ if (MAIN_MODULE || SIDE_MODULE) {
+ // Clone the function for each of its aliases. We do not know which name it will be used by in another module,
+ // and we do not have a heavyweight metadata system to resolve aliases during linking
+ var aliases = Functions.aliases[func.ident];
+ if (aliases) {
+ var body = func.JS.substr(func.JS.indexOf('('));
+ aliases.forEach(function(alias) {
+ func.JS += '\n' + 'function ' + alias + body;
+ });
+ }
+ }
+
return func;
}
});
@@ -1254,7 +1261,7 @@ function JSify(data, functionsOnly, givenFunctions) {
case 'xchg': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, param2, type, null, null, null, null, ',') + ',tempValue)';
case 'cmpxchg': {
var param3 = finalizeLLVMParameter(item.params[2]);
- return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==(' + param2 + '|0) ? ' + makeSetValue(param1, 0, param3, type, null, null, null, null, ',') + ' : 0),tempValue)';
+ return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==(' + param2 + '|0) ? ' + asmCoercion(makeSetValue(param1, 0, param3, type, null, null, null, null, ','), 'i32') + ' : 0),tempValue)';
}
default: throw 'unhandled atomic op: ' + item.op;
}
@@ -1502,7 +1509,7 @@ function JSify(data, functionsOnly, givenFunctions) {
// This is a call through an invoke_*, either a forced one, or a setjmp-required one
// note: no need to update argsTypes at this point
if (byPointerForced) Functions.unimplementedFunctions[callIdent] = sig;
- args.unshift(byPointerForced ? Functions.getIndex(callIdent) : asmCoercion(callIdent, 'i32'));
+ args.unshift(byPointerForced ? Functions.getIndex(callIdent, undefined, sig) : asmCoercion(callIdent, 'i32'));
callIdent = 'invoke_' + sig;
}
} else if (SAFE_DYNCALLS) {
@@ -1582,6 +1589,7 @@ function JSify(data, functionsOnly, givenFunctions) {
if (phase == 'pre' && !Variables.generatedGlobalBase) {
Variables.generatedGlobalBase = true;
// Globals are done, here is the rest of static memory
+ assert((TARGET_LE32 && Runtime.GLOBAL_BASE == 8) || (TARGET_X86 && Runtime.GLOBAL_BASE == 4)); // this is assumed in e.g. relocations for linkable modules
print('STATIC_BASE = ' + Runtime.GLOBAL_BASE + ';\n');
print('STATICTOP = STATIC_BASE + ' + Runtime.alignMemory(Variables.nextIndexedOffset) + ';\n');
}
@@ -1615,11 +1623,11 @@ function JSify(data, functionsOnly, givenFunctions) {
print('/* no memory initializer */'); // test purposes
}
- // Run postsets right before main, and after the memory initializer has been set up
+ // Define postsets. These will be run in ATINIT, right before global initializers (which might need the postsets). We cannot
+ // run them now because the memory initializer might not have been applied yet.
print('function runPostSets() {\n');
print(itemsDict.GlobalVariablePostSet.map(function(item) { return item.JS }).join('\n'));
print('}\n');
- print('if (!awaitingMemoryInitializer) runPostSets();\n'); // if we load the memory initializer, this is done later
if (USE_TYPED_ARRAYS == 2) {
print('var tempDoublePtr = Runtime.alignMemory(allocate(12, "i8", ALLOC_STATIC), 8);\n');
@@ -1729,8 +1737,8 @@ function JSify(data, functionsOnly, givenFunctions) {
}
}
});
- print(read('fastLong.js'));
}
+ print(read('fastLong.js'));
print('// EMSCRIPTEN_END_FUNCS\n');
print(read('long.js'));
} else {
@@ -1792,6 +1800,21 @@ function JSify(data, functionsOnly, givenFunctions) {
substrate.addItems(data.functionStubs, 'FunctionStub');
assert(data.functions.length == 0);
} else {
+ if (phase == 'pre') {
+ // ensure there is a global ctors, for runPostSets
+ if ('_llvm_global_ctors' in data.globalVariables) {
+ data.globalVariables._llvm_global_ctors.ctors.unshift('runPostSets'); // run postsets right before global initializers
+ hasCtors = true;
+ } else {
+ substrate.addItems([{
+ intertype: 'GlobalVariableStub',
+ ident: '_llvm_global_ctors',
+ type: '[1 x { i32, void ()* }]',
+ ctors: ["runPostSets"],
+ }], 'GlobalVariable');
+ }
+ }
+
substrate.addItems(sortGlobals(data.globalVariables), 'GlobalVariable');
substrate.addItems(data.aliass, 'Alias');
substrate.addItems(data.functions, 'FunctionSplitter');
diff --git a/src/library.js b/src/library.js
index 822f4319..bf969fd7 100644
--- a/src/library.js
+++ b/src/library.js
@@ -8271,4 +8271,8 @@ function autoAddDeps(object, name) {
}
}
+// Add aborting stubs for various libc stuff needed by libc++
+['pthread_cond_signal', 'pthread_equal', 'wcstol', 'wcstoll', 'wcstoul', 'wcstoull', 'wcstof', 'wcstod', 'wcstold', 'swprintf', 'pthread_join', 'pthread_detach', 'strcoll_l', 'strxfrm_l', 'wcscoll_l', 'toupper_l', 'tolower_l', 'iswspace_l', 'iswprint_l', 'iswcntrl_l', 'iswupper_l', 'iswlower_l', 'iswalpha_l', 'iswdigit_l', 'iswpunct_l', 'iswxdigit_l', 'iswblank_l', 'wcsxfrm_l', 'towupper_l', 'towlower_l'].forEach(function(aborter) {
+ LibraryManager.library[aborter] = function() { throw 'TODO: ' + aborter };
+});
diff --git a/src/library_gl.js b/src/library_gl.js
index 1fa0cc9c..e59492cf 100644
--- a/src/library_gl.js
+++ b/src/library_gl.js
@@ -1260,6 +1260,8 @@ var LibraryGL = {
return Module.ctx.isFramebuffer(fb);
},
+#if DISABLE_GL_EMULATION == 0
+
// GL emulation: provides misc. functionality not present in OpenGL ES 2.0 or WebGL
$GLEmulation__postset: 'GLEmulation.init();',
@@ -4139,6 +4141,44 @@ var LibraryGL = {
},
glRotatef: 'glRotated',
+ glDrawBuffer: function() { throw 'glDrawBuffer: TODO' },
+ glReadBuffer: function() { throw 'glReadBuffer: TODO' },
+
+ glLightfv: function() { throw 'glLightfv: TODO' },
+ glLightModelfv: function() { throw 'glLightModelfv: TODO' },
+ glMaterialfv: function() { throw 'glMaterialfv: TODO' },
+
+ glTexGeni: function() { throw 'glTexGeni: TODO' },
+ glTexGenfv: function() { throw 'glTexGenfv: TODO' },
+ glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') },
+ glTexEnvf: function() { Runtime.warnOnce('glTexEnvf: TODO') },
+ glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') },
+
+ glTexImage1D: function() { throw 'glTexImage1D: TODO' },
+ glTexCoord3f: function() { throw 'glTexCoord3f: TODO' },
+ glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' },
+
+ glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') },
+
+ // Open GLES1.1 compatibility
+
+ glGenFramebuffersOES : 'glGenFramebuffers',
+ glGenRenderbuffersOES : 'glGenRenderbuffers',
+ glBindFramebufferOES : 'glBindFramebuffer',
+ glBindRenderbufferOES : 'glBindRenderbuffer',
+ glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv',
+ glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer',
+ glRenderbufferStorageOES : 'glRenderbufferStorage',
+ glCheckFramebufferStatusOES : 'glCheckFramebufferStatus',
+ glDeleteFramebuffersOES : 'glDeleteFramebuffers',
+ glDeleteRenderbuffersOES : 'glDeleteRenderbuffers',
+ glGenVertexArraysOES: 'glGenVertexArrays',
+ glDeleteVertexArraysOES: 'glDeleteVertexArrays',
+ glBindVertexArrayOES: 'glBindVertexArray',
+ glFramebufferTexture2DOES: 'glFramebufferTexture2D',
+
+#endif // DISABLE_GL_EMULATION == 0
+
// GLU
gluPerspective: function(fov, aspect, near, far) {
@@ -4205,25 +4245,6 @@ var LibraryGL = {
_glOrtho(left, right, bottom, top, -1, 1);
},
- glDrawBuffer: function() { throw 'glDrawBuffer: TODO' },
- glReadBuffer: function() { throw 'glReadBuffer: TODO' },
-
- glLightfv: function() { throw 'glLightfv: TODO' },
- glLightModelfv: function() { throw 'glLightModelfv: TODO' },
- glMaterialfv: function() { throw 'glMaterialfv: TODO' },
-
- glTexGeni: function() { throw 'glTexGeni: TODO' },
- glTexGenfv: function() { throw 'glTexGenfv: TODO' },
- glTexEnvi: function() { Runtime.warnOnce('glTexEnvi: TODO') },
- glTexEnvf: function() { Runtime.warnOnce('glTexEnvf: TODO') },
- glTexEnvfv: function() { Runtime.warnOnce('glTexEnvfv: TODO') },
-
- glTexImage1D: function() { throw 'glTexImage1D: TODO' },
- glTexCoord3f: function() { throw 'glTexCoord3f: TODO' },
- glGetTexLevelParameteriv: function() { throw 'glGetTexLevelParameteriv: TODO' },
-
- glShadeModel: function() { Runtime.warnOnce('TODO: glShadeModel') },
-
// GLES2 emulation
glVertexAttribPointer__sig: 'viiiiii',
@@ -4351,23 +4372,6 @@ var LibraryGL = {
glGetError__sig: 'i',
glFrontFace__sig: 'vi',
glSampleCoverage__sig: 'vi',
-
- // Open GLES1.1 compatibility
-
- glGenFramebuffersOES : 'glGenFramebuffers',
- glGenRenderbuffersOES : 'glGenRenderbuffers',
- glBindFramebufferOES : 'glBindFramebuffer',
- glBindRenderbufferOES : 'glBindRenderbuffer',
- glGetRenderbufferParameterivOES : 'glGetRenderbufferParameteriv',
- glFramebufferRenderbufferOES : 'glFramebufferRenderbuffer',
- glRenderbufferStorageOES : 'glRenderbufferStorage',
- glCheckFramebufferStatusOES : 'glCheckFramebufferStatus',
- glDeleteFramebuffersOES : 'glDeleteFramebuffers',
- glDeleteRenderbuffersOES : 'glDeleteRenderbuffers',
- glGenVertexArraysOES: 'glGenVertexArrays',
- glDeleteVertexArraysOES: 'glDeleteVertexArrays',
- glBindVertexArrayOES: 'glBindVertexArray',
- glFramebufferTexture2DOES: 'glFramebufferTexture2D',
};
@@ -4409,25 +4413,27 @@ var LibraryGL = {
autoAddDeps(LibraryGL, '$GL');
-// Emulation requires everything else, potentially
-LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.slice(0); // the __deps object is shared
-var glFuncs = [];
-for (var item in LibraryGL) {
- if (item != '$GLEmulation' && item.substr(-6) != '__deps' && item.substr(-9) != '__postset' && item.substr(-5) != '__sig' && item.substr(0, 2) == 'gl') {
- glFuncs.push(item);
- }
-}
-LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs);
-LibraryGL.$GLEmulation__deps.push(function() {
- for (var func in Functions.getIndex.tentative) {
- Functions.getIndex(func);
- Functions.unimplementedFunctions[func] = LibraryGL[func.substr(1) + '__sig'];
+if (!DISABLE_GL_EMULATION) {
+ // Emulation requires everything else, potentially
+ LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.slice(0); // the __deps object is shared
+ var glFuncs = [];
+ for (var item in LibraryGL) {
+ if (item != '$GLEmulation' && item.substr(-6) != '__deps' && item.substr(-9) != '__postset' && item.substr(-5) != '__sig' && item.substr(0, 2) == 'gl') {
+ glFuncs.push(item);
+ }
}
-});
+ LibraryGL.$GLEmulation__deps = LibraryGL.$GLEmulation__deps.concat(glFuncs);
+ LibraryGL.$GLEmulation__deps.push(function() {
+ for (var func in Functions.getIndex.tentative) {
+ Functions.getIndex(func);
+ Functions.unimplementedFunctions[func] = LibraryGL[func.substr(1) + '__sig'];
+ }
+ });
-if (FORCE_GL_EMULATION) {
- LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation');
- LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation');
+ if (FORCE_GL_EMULATION) {
+ LibraryGL.glDrawElements__deps = LibraryGL.glDrawElements__deps.concat('$GLEmulation');
+ LibraryGL.glDrawArrays__deps = LibraryGL.glDrawArrays__deps.concat('$GLEmulation');
+ }
}
mergeInto(LibraryManager.library, LibraryGL);
diff --git a/src/modules.js b/src/modules.js
index d26acbd5..a6aa2644 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -245,6 +245,8 @@ var Functions = {
blockAddresses: {}, // maps functions to a map of block labels to label ids
+ aliases: {}, // in shared modules (MAIN_MODULE or SHARED_MODULE), a list of aliases for functions that have them
+
getSignature: function(returnType, argTypes, hasVarArgs) {
var sig = returnType == 'void' ? 'v' : (isIntImplemented(returnType) ? 'i' : 'f');
for (var i = 0; i < argTypes.length; i++) {
@@ -257,25 +259,32 @@ var Functions = {
},
// Mark a function as needing indexing. Python will coordinate them all
- getIndex: function(ident, doNotCreate) {
+ getIndex: function(ident, doNotCreate, sig) {
if (doNotCreate && !(ident in this.indexedFunctions)) {
if (!Functions.getIndex.tentative) Functions.getIndex.tentative = {}; // only used by GL emulation; TODO: generalize when needed
Functions.getIndex.tentative[ident] = 0;
}
+ var ret;
if (phase != 'post' && singlePhase) {
if (!doNotCreate) this.indexedFunctions[ident] = 0; // tell python we need this indexized
- return "'{{ FI_" + toNiceIdent(ident) + " }}'"; // something python will replace later
+ ret = "'{{ FI_" + toNiceIdent(ident) + " }}'"; // something python will replace later
} else {
if (!singlePhase) return 'NO_INDEX'; // Should not index functions in post
- var ret = this.indexedFunctions[ident];
+ ret = this.indexedFunctions[ident];
if (!ret) {
if (doNotCreate) return '0';
ret = this.nextIndex;
this.nextIndex += 2; // Need to have indexes be even numbers, see |polymorph| test
this.indexedFunctions[ident] = ret;
}
- return ret.toString();
+ ret = ret.toString();
+ }
+ if (SIDE_MODULE && sig) { // sig can be undefined for the GL library functions
+ ret = '((F_BASE_' + sig + ' + ' + ret + ')|0)';
+ } else if (BUILD_AS_SHARED_LIB) {
+ ret = '(FUNCTION_TABLE_OFFSET + ' + ret + ')';
}
+ return ret;
},
getTable: function(sig) {
diff --git a/src/parseTools.js b/src/parseTools.js
index 6bc0b7ea..dfd4b7ed 100644
--- a/src/parseTools.js
+++ b/src/parseTools.js
@@ -492,7 +492,9 @@ function makeGlobalUse(ident) {
UNINDEXABLE_GLOBALS[ident] = 1;
return ident;
}
- return (Runtime.GLOBAL_BASE + index).toString();
+ var ret = (Runtime.GLOBAL_BASE + index).toString();
+ if (SIDE_MODULE) ret = '(H_BASE+' + ret + ')';
+ return ret;
}
return ident;
}
@@ -1227,15 +1229,11 @@ function indexizeFunctions(value, type) {
var out = {};
if (type && isFunctionType(type, out) && value[0] === '_') { // checking for _ differentiates from $ (local vars)
// add signature to library functions that we now know need indexing
- if (!(value in Functions.implementedFunctions) && !(value in Functions.unimplementedFunctions)) {
- Functions.unimplementedFunctions[value] = Functions.getSignature(out.returnType, out.segments ? out.segments.map(function(segment) { return segment[0].text }) : []);
- }
-
- if (BUILD_AS_SHARED_LIB) {
- return '(FUNCTION_TABLE_OFFSET + ' + Functions.getIndex(value) + ')';
- } else {
- return Functions.getIndex(value);
+ var sig = Functions.implementedFunctions[value] || Functions.unimplementedFunctions[value];
+ if (!sig) {
+ sig = Functions.unimplementedFunctions[value] = Functions.getSignature(out.returnType, out.segments ? out.segments.map(function(segment) { return segment[0].text }) : []);
}
+ return Functions.getIndex(value, undefined, sig);
}
return value;
}
@@ -1627,6 +1625,9 @@ function makePointer(slab, pos, allocator, type, ptr, finalMemoryInitialization)
// writing out into memory, without a normal allocation. We put all of these into a single big chunk.
assert(typeof slab == 'object');
assert(slab.length % QUANTUM_SIZE == 0, slab.length); // must be aligned already
+ if (SIDE_MODULE && typeof ptr == 'string') {
+ ptr = parseInt(ptr.substring(ptr.indexOf('+'), ptr.length-1)); // parse into (H_BASE+X)
+ }
var offset = ptr - Runtime.GLOBAL_BASE;
for (var i = 0; i < slab.length; i++) {
memoryInitialization[offset + i] = slab[i];
@@ -2146,7 +2147,11 @@ function processMathop(item) {
}
case 'select': return idents[0] + ' ? ' + makeCopyI64(idents[1]) + ' : ' + makeCopyI64(idents[2]);
case 'ptrtoint': return makeI64(idents[0], 0);
- case 'inttoptr': return '(' + idents[0] + '[0])'; // just directly truncate the i64 to a 'pointer', which is an i32
+ case 'inttoptr': {
+ var m = /\(?\[(\d+),\d+\]\)?/.exec(idents[0]);
+ if (m) return m[1]; // constant, can just parse it right now
+ return '(' + idents[0] + '[0])'; // just directly truncate the i64 to a 'pointer', which is an i32
+ }
// Dangerous, rounded operations. TODO: Fully emulate
case 'add': {
if (PRECISE_I64_MATH) {
diff --git a/src/preamble.js b/src/preamble.js
index 55a8a3a5..ed148d9e 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -887,8 +887,6 @@ __ATEXIT__.push({ func: function() { PGOMonitor.dump() } });
addPreRun(function() { addRunDependency('pgo') });
#endif
-var awaitingMemoryInitializer = false;
-
function loadMemoryInitializer(filename) {
function applyData(data) {
#if USE_TYPED_ARRAYS == 2
@@ -896,7 +894,6 @@ function loadMemoryInitializer(filename) {
#else
allocate(data, 'i8', ALLOC_NONE, STATIC_BASE);
#endif
- runPostSets();
}
// always do this asynchronously, to keep shell and web as similar as possible
@@ -911,8 +908,6 @@ function loadMemoryInitializer(filename) {
});
}
});
-
- awaitingMemoryInitializer = false;
}
// === Body ===
diff --git a/src/settings.js b/src/settings.js
index dff52adf..7f9dca3b 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -186,6 +186,8 @@ var GL_MAX_TEMP_BUFFER_SIZE = 2097152; // How large GL emulation temp buffers ar
var GL_UNSAFE_OPTS = 1; // Enables some potentially-unsafe optimizations in GL emulation code
var FULL_ES2 = 0; // Forces support for all GLES2 features, not just the WebGL-friendly subset.
var FORCE_GL_EMULATION = 0; // Forces inclusion of full GL emulation code.
+var DISABLE_GL_EMULATION = 0; // Disable inclusion of full GL emulation code. Useful when you don't want emulation
+ // but do need INCLUDE_FULL_LIBRARY or MAIN_MODULE.
var DISABLE_EXCEPTION_CATCHING = 0; // Disables generating code to actually catch exceptions. If the code you
// are compiling does not actually rely on catching exceptions (but the
@@ -296,6 +298,10 @@ var SHOW_LABELS = 0; // Show labels in the generated code
var PRINT_SPLIT_FILE_MARKER = 0; // Prints markers in Javascript generation to split the file later on. See emcc --split option.
+var MAIN_MODULE = 0; // A main module is a file compiled in a way that allows us to link it to
+ // a side module using emlink.py.
+var SIDE_MODULE = 0; // Corresponds to MAIN_MODULE
+
var BUILD_AS_SHARED_LIB = 0; // Whether to build the code as a shared library
// 0 here means this is not a shared lib: It is a main file.
// All shared library options (1 and 2) are currently deprecated XXX
diff --git a/tests/runner.py b/tests/runner.py
index 2ce72240..51745180 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -16,7 +16,7 @@ so you may prefer to use fewer cores here.
'''
from subprocess import Popen, PIPE, STDOUT
-import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, tempfile, re, difflib, webbrowser, hashlib, threading, platform, BaseHTTPServer, multiprocessing, functools, stat
+import os, unittest, tempfile, shutil, time, inspect, sys, math, glob, re, difflib, webbrowser, hashlib, threading, platform, BaseHTTPServer, multiprocessing, functools, stat
if len(sys.argv) == 1:
print '''
@@ -265,6 +265,12 @@ process(sys.argv[1])
else:
assert 'memory initializer */' in open(filename + '.o.js').read()
+ def validate_asmjs(self, err):
+ if 'uccessfully compiled asm.js code' in err and 'asm.js link error' not in err:
+ print >> sys.stderr, "[was asm.js'ified]"
+ elif 'asm.js' in err: # if no asm.js error, then not an odin build
+ raise Exception("did NOT asm.js'ify")
+
def run_generated_code(self, engine, filename, args=[], check_timeout=True, output_nicerizer=None):
stdout = os.path.join(self.get_dir(), 'stdout') # use files, as PIPE can get too full and hang us
stderr = os.path.join(self.get_dir(), 'stderr')
@@ -279,10 +285,7 @@ process(sys.argv[1])
out = open(stdout, 'r').read()
err = open(stderr, 'r').read()
if engine == SPIDERMONKEY_ENGINE and Settings.ASM_JS:
- if 'uccessfully compiled asm.js code' in err and 'asm.js link error' not in err:
- print >> sys.stderr, "[was asm.js'ified]"
- elif 'asm.js' in err: # if no asm.js error, then not an odin build
- raise Exception("did NOT asm.js'ify")
+ self.validate_asmjs(err)
if output_nicerizer:
ret = output_nicerizer(out, err)
else:
@@ -8582,7 +8585,7 @@ def process(filename):
del os.environ['EMCC_DEBUG']
for debug in [1,2]:
def clean(text):
- return text.replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n')
+ return text.replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('\n\n', '\n').replace('{\n}', '{}')
self.assertIdentical(clean(open('release.js').read()), clean(open('debug%d.js' % debug).read())) # EMCC_DEBUG=1 mode must not generate different code!
print >> sys.stderr, 'debug check %d passed too' % debug
@@ -9637,7 +9640,9 @@ def process(filename):
# objects when generating source maps, so we want to make sure the
# optimizer can deal with both types.
out_file = re.sub(' *//@.*$', '', out_file, flags=re.MULTILINE)
- self.assertIdentical(no_maps_file, out_file)
+ def clean(code):
+ return code.replace('{\n}', '{}')
+ self.assertIdentical(clean(no_maps_file), clean(out_file))
map_filename = out_filename + '.map'
data = json.load(open(map_filename, 'r'))
self.assertIdentical(out_filename, data['file'])
@@ -10606,6 +10611,215 @@ f.close()
self.assertContained('hello from lib', run_js(os.path.join(self.get_dir(), 'a.out.js')))
assert not os.path.exists('a.out') and not os.path.exists('a.exe'), 'Must not leave unneeded linker stubs'
+ def test_static_link(self):
+ def test(name, header, main, side, expected, args=[], suffix='cpp', first=True):
+ print name
+ #t = main ; main = side ; side = t
+ original_main = main
+ original_side = side
+ if header: open(os.path.join(self.get_dir(), 'header.h'), 'w').write(header)
+ if type(main) == str:
+ open(os.path.join(self.get_dir(), 'main.' + suffix), 'w').write(main)
+ main = ['main.' + suffix]
+ if type(side) == str:
+ open(os.path.join(self.get_dir(), 'side.' + suffix), 'w').write(side)
+ side = ['side.' + suffix]
+ Popen([PYTHON, EMCC] + side + ['-o', 'side.js', '-s', 'SIDE_MODULE=1', '-O2'] + args).communicate()
+ # TODO: test with and without DISABLE_GL_EMULATION, check that file sizes change
+ Popen([PYTHON, EMCC] + main + ['-o', 'main.js', '-s', 'MAIN_MODULE=1', '-O2', '-s', 'DISABLE_GL_EMULATION=1'] + args).communicate()
+ Popen([PYTHON, EMLINK, 'main.js', 'side.js', 'together.js'], stdout=PIPE).communicate()
+ assert os.path.exists('together.js')
+ out = run_js('together.js', engine=SPIDERMONKEY_ENGINE, stderr=PIPE, full_output=True)
+ self.assertContained(expected, out)
+ self.validate_asmjs(out)
+ if first:
+ shutil.copyfile('together.js', 'first.js')
+ test(name + ' (reverse)', header, original_side, original_main, expected, args, suffix, False) # test reverse order
+
+ # test a simple call from one module to another. only one has a string (and constant memory initialization for it)
+ test('basics', '', '''
+ #include <stdio.h>
+ extern int sidey();
+ int main() {
+ printf("other says %d.", sidey());
+ return 0;
+ }
+ ''', '''
+ int sidey() { return 11; }
+ ''', 'other says 11.')
+
+ # finalization of float variables should pass asm.js validation
+ test('floats', '', '''
+ #include <stdio.h>
+ extern float sidey();
+ int main() {
+ printf("other says %.2f.", sidey()+1);
+ return 0;
+ }
+ ''', '''
+ float sidey() { return 11.5; }
+ ''', 'other says 12.50')
+
+ # memory initialization in both
+ test('multiple memory inits', '', r'''
+ #include <stdio.h>
+ extern void sidey();
+ int main() {
+ printf("hello from main\n");
+ sidey();
+ return 0;
+ }
+ ''', r'''
+ #include <stdio.h>
+ void sidey() { printf("hello from side\n"); }
+ ''', 'hello from main\nhello from side\n')
+
+ # function pointers
+ test('fp1', 'typedef void (*voidfunc)();', r'''
+ #include <stdio.h>
+ #include "header.h"
+ voidfunc sidey(voidfunc f);
+ void a() { printf("hello from funcptr\n"); }
+ int main() {
+ sidey(a)();
+ return 0;
+ }
+ ''', '''
+ #include "header.h"
+ voidfunc sidey(voidfunc f) { return f; }
+ ''', 'hello from funcptr\n')
+
+ # Global initializer
+ test('global init', '', r'''
+ #include <stdio.h>
+ struct Class {
+ Class() { printf("a new Class\n"); }
+ };
+ static Class c;
+ int main() {
+ return 0;
+ }
+ ''', r'''
+ void nothing() {}
+ ''', 'a new Class\n')
+
+ # Multiple global initializers (LLVM generates overlapping names for them)
+ test('global inits', r'''
+ #include <stdio.h>
+ struct Class {
+ Class(const char *name) { printf("new %s\n", name); }
+ };
+ ''', r'''
+ #include "header.h"
+ static Class c("main");
+ int main() {
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ static Class c("side");
+ ''', ['new main\nnew side\n', 'new side\nnew main\n'])
+
+ # Class code used across modules
+ test('codecall', r'''
+ #include <stdio.h>
+ struct Class {
+ Class(const char *name);
+ };
+ ''', r'''
+ #include "header.h"
+ int main() {
+ Class c("main");
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ Class::Class(const char *name) { printf("new %s\n", name); }
+ ''', ['new main\n'])
+
+ # malloc usage in both modules
+ test('malloc', r'''
+ #include <stdlib.h>
+ #include <string.h>
+ char *side(const char *data);
+ ''', r'''
+ #include <stdio.h>
+ #include "header.h"
+ int main() {
+ char *temp = side("hello through side\n");
+ char *ret = (char*)malloc(strlen(temp)+1);
+ strcpy(ret, temp);
+ temp[1] = 'x';
+ puts(ret);
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ char *side(const char *data) {
+ char *ret = (char*)malloc(strlen(data)+1);
+ strcpy(ret, data);
+ return ret;
+ }
+ ''', ['hello through side\n'])
+
+ # libc usage in one modules. must force libc inclusion in the main module if that isn't the one using mallinfo()
+ try:
+ os.environ['EMCC_FORCE_STDLIBS'] = 'libc'
+ test('malloc-1', r'''
+ #include <string.h>
+ int side();
+ ''', r'''
+ #include <stdio.h>
+ #include "header.h"
+ int main() {
+ printf("|%d|\n", side());
+ return 0;
+ }
+ ''', r'''
+ #include <stdlib.h>
+ #include <malloc.h>
+ #include "header.h"
+ int side() {
+ struct mallinfo m = mallinfo();
+ return m.arena > 1;
+ }
+ ''', ['|1|\n'])
+ finally:
+ del os.environ['EMCC_FORCE_STDLIBS']
+
+ # iostream usage in one and std::string in both
+ test('iostream', r'''
+ #include <iostream>
+ #include <string>
+ std::string side();
+ ''', r'''
+ #include "header.h"
+ int main() {
+ std::cout << "hello from main " << side() << std::endl;
+ return 0;
+ }
+ ''', r'''
+ #include "header.h"
+ std::string side() { return "and hello from side"; }
+ ''', ['hello from main and hello from side\n'])
+
+ # zlib compression library. tests function pointers in initializers and many other things
+ test('zlib', '', open(path_from_root('tests', 'zlib', 'example.c'), 'r').read(),
+ self.get_library('zlib', os.path.join('libz.a'), make_args=['libz.a']),
+ open(path_from_root('tests', 'zlib', 'ref.txt'), 'r').read(),
+ args=['-I' + path_from_root('tests', 'zlib')], suffix='c')
+
+ # bullet physics engine. tests all the things
+ test('bullet', '', open(path_from_root('tests', 'bullet', 'Demos', 'HelloWorld', 'HelloWorld.cpp'), 'r').read(),
+ self.get_library('bullet', [os.path.join('src', '.libs', 'libBulletDynamics.a'),
+ os.path.join('src', '.libs', 'libBulletCollision.a'),
+ os.path.join('src', '.libs', 'libLinearMath.a')]),
+ [open(path_from_root('tests', 'bullet', 'output.txt'), 'r').read(), # different roundings
+ open(path_from_root('tests', 'bullet', 'output2.txt'), 'r').read(),
+ open(path_from_root('tests', 'bullet', 'output3.txt'), 'r').read()],
+ args=['-I' + path_from_root('tests', 'bullet', 'src')])
+
+
def test_symlink(self):
if os.name == 'nt':
return self.skip('Windows FS does not need to be tested for symlinks support, since it does not have them.')
@@ -13893,7 +14107,22 @@ elif 'sanity' in str(sys.argv):
def test_firstrun(self):
for command in commands:
wipe()
- output = self.do(command)
+
+ def make_executable(name):
+ with open(os.path.join(temp_bin, name), 'w') as f:
+ os.fchmod(f.fileno(), stat.S_IRWXU)
+
+ try:
+ temp_bin = tempfile.mkdtemp()
+ old_environ_path = os.environ['PATH']
+ os.environ['PATH'] = temp_bin + os.pathsep + old_environ_path
+ make_executable('llvm-dis')
+ make_executable('node')
+ make_executable('python2')
+ output = self.do(command)
+ finally:
+ os.environ['PATH'] = old_environ_path
+ shutil.rmtree(temp_bin)
self.assertContained('Welcome to Emscripten!', output)
self.assertContained('This is the first time any of the Emscripten tools has been run.', output)
@@ -13901,6 +14130,12 @@ elif 'sanity' in str(sys.argv):
self.assertContained('It contains our best guesses for the important paths, which are:', output)
self.assertContained('LLVM_ROOT', output)
self.assertContained('NODE_JS', output)
+ self.assertContained('PYTHON', output)
+ if platform.system() is not 'Windows':
+ # os.chmod can't make files executable on Windows
+ self.assertIdentical(temp_bin, re.search("^ *LLVM_ROOT *= (.*)$", output, re.M).group(1))
+ self.assertIdentical(os.path.join(temp_bin, 'node'), re.search("^ *NODE_JS *= (.*)$", output, re.M).group(1))
+ self.assertIdentical(os.path.join(temp_bin, 'python2'), re.search("^ *PYTHON *= (.*)$", output, re.M).group(1))
self.assertContained('Please edit the file if any of those are incorrect', output)
self.assertContained('This command will now exit. When you are done editing those paths, re-run it.', output)
assert output.split()[-1].endswith('===='), 'We should have stopped: ' + output
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index d04807a7..1de06e4c 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -405,13 +405,16 @@ function removeUnneededLabelSettings(ast) {
// Various expression simplifications. Pre run before closure (where we still have metadata), Post run after.
var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^');
+var COMPARE_OPS = set('<', '<=', '>', '>=', '==', '===', '!=');
function simplifyExpressionsPre(ast) {
- // Look for (x&A)<<B>>B and replace it with X&A if possible.
- function simplifySignExtends(ast) {
+ // Simplify common expressions used to perform integer conversion operations
+ // in cases where no conversion is needed.
+ function simplifyIntegerConversions(ast) {
traverse(ast, function(node, type) {
if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' &&
node[2][0] === 'binary' && node[2][1] === '<<' && node[2][3][0] === 'num' && node[3][1] === node[2][3][1]) {
+ // Transform (x&A)<<B>>B to X&A.
var innerNode = node[2][2];
var shifts = node[3][1];
if (innerNode[0] === 'binary' && innerNode[1] === '&' && innerNode[3][0] === 'num') {
@@ -420,6 +423,15 @@ function simplifyExpressionsPre(ast) {
return innerNode;
}
}
+ } else if (type === 'binary' && node[1] === '&' && node[3][0] === 'num') {
+ // Rewrite (X < Y) & 1 to (X < Y)|0. (Subsequent passes will eliminate
+ // the |0 if possible.)
+ var input = node[2];
+ var amount = node[3][1];
+ if (input[0] === 'binary' && (input[1] in COMPARE_OPS) && amount == 1) {
+ node[1] = '|';
+ node[3][1] = 0;
+ }
}
});
}
@@ -763,7 +775,7 @@ function simplifyExpressionsPre(ast) {
}
traverseGeneratedFunctions(ast, function(func) {
- simplifySignExtends(func);
+ simplifyIntegerConversions(func);
simplifyBitops(func);
joinAdditions(func);
// simplifyZeroComp(func); TODO: investigate performance
@@ -2730,8 +2742,9 @@ function relocate(ast) {
assert(asm); // we also assume we are normalized
var replacements = extraInfo.replacements;
- var fBase = extraInfo.fBase;
+ var fBases = extraInfo.fBases;
var hBase = extraInfo.hBase;
+ var m;
traverse(ast, function(node, type) {
switch(type) {
@@ -2743,13 +2756,14 @@ function relocate(ast) {
case 'binary': {
if (node[1] == '+' && node[2][0] == 'name') {
var base = null;
- if (node[2][1] == 'F_BASE') {
- base = fBase;
- } else if (node[2][1] == 'H_BASE') {
+ if (node[2][1] == 'H_BASE') {
base = hBase;
+ } else if (m = /^F_BASE_(\w+)$/.exec(node[2][1])) {
+ base = fBases[m[1]] || 0; // 0 if the parent has no function table for this, but the child does. so no relocation needed
}
- if (base) {
+ if (base !== null) {
var other = node[3];
+ if (base === 0) return other;
if (other[0] == 'num') {
other[1] += base;
return other;
@@ -2887,7 +2901,7 @@ arguments_.slice(1).forEach(function(arg) {
passes[arg](ast);
});
if (asm && last) {
- asmLoopOptimizer(ast);
+ asmLoopOptimizer(ast); // TODO: move out of last, to make last faster when done later (as in side modules)
prepDotZero(ast);
}
var js = astToSrc(ast, minifyWhitespace), old;
diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py
index 905ae835..a4e1ca6c 100644
--- a/tools/js_optimizer.py
+++ b/tools/js_optimizer.py
@@ -20,6 +20,7 @@ WINDOWS = sys.platform.startswith('win')
DEBUG = os.environ.get('EMCC_DEBUG')
func_sig = re.compile('( *)function ([_\w$]+)\(')
+import_sig = re.compile('var ([_\w$]+) *=[^;]+;')
class Minifier:
'''
@@ -95,6 +96,11 @@ class Minifier:
'globals': self.globs
})
+start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n'
+end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n'
+start_asm_marker = '// EMSCRIPTEN_START_ASM\n'
+end_asm_marker = '// EMSCRIPTEN_END_ASM\n'
+
def run_on_chunk(command):
filename = command[2] # XXX hackish
#print >> sys.stderr, 'running js optimizer command', ' '.join(command), '""""', open(filename).read()
@@ -107,7 +113,7 @@ def run_on_chunk(command):
if DEBUG and not shared.WINDOWS: print >> sys.stderr, '.' # Skip debug progress indicator on Windows, since it doesn't buffer well with multiple threads printing to console.
return filename
-def run_on_js(filename, passes, js_engine, jcache, source_map=False):
+def run_on_js(filename, passes, js_engine, jcache, source_map=False, extra_info=None):
if isinstance(jcache, bool) and jcache: jcache = shared.JCache
if jcache: shared.JCache.ensure()
@@ -129,17 +135,14 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False):
generated = set(eval(suffix[len(suffix_marker)+1:]))
# Find markers
- start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n'
- end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n'
start_funcs = js.find(start_funcs_marker)
end_funcs = js.rfind(end_funcs_marker)
- #assert (start_funcs >= 0) == (end_funcs >= 0) == (not not suffix)
+
+ know_generated = suffix or start_funcs >= 0
minify_globals = 'registerizeAndMinify' in passes and 'asm' in passes
if minify_globals:
passes = map(lambda p: p if p != 'registerizeAndMinify' else 'registerize', passes)
- start_asm_marker = '// EMSCRIPTEN_START_ASM\n'
- end_asm_marker = '// EMSCRIPTEN_END_ASM\n'
start_asm = js.find(start_asm_marker)
end_asm = js.rfind(end_asm_marker)
assert (start_asm >= 0) == (end_asm >= 0)
@@ -148,14 +151,14 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False):
if closure:
passes = filter(lambda p: p != 'closure', passes) # we will do it manually
- if not suffix and jcache:
+ if not know_generated and jcache:
# JCache cannot be used without metadata, since it might reorder stuff, and that's dangerous since only generated can be reordered
# This means jcache does not work after closure compiler runs, for example. But you won't get much benefit from jcache with closure
# anyhow (since closure is likely the longest part of the build).
if DEBUG: print >>sys.stderr, 'js optimizer: no metadata, so disabling jcache'
jcache = False
- if suffix:
+ if know_generated:
if not minify_globals:
pre = js[:start_funcs + len(start_funcs_marker)]
post = js[end_funcs + len(end_funcs_marker):]
@@ -189,7 +192,7 @@ EMSCRIPTEN_FUNCS();
minify_info = minifier.serialize()
#if DEBUG: print >> sys.stderr, 'minify info:', minify_info
# remove suffix if no longer needed
- if 'last' in passes:
+ if suffix and 'last' in passes:
suffix_start = post.find(suffix_marker)
suffix_end = post.find('\n', suffix_start)
post = post[:suffix_start] + post[suffix_end:]
@@ -209,7 +212,7 @@ EMSCRIPTEN_FUNCS();
if m:
ident = m.group(2)
else:
- if suffix: continue # ignore whitespace
+ if know_generated: continue # ignore whitespace
ident = 'anon_%d' % i
assert ident
funcs.append((ident, func))
@@ -249,8 +252,12 @@ EMSCRIPTEN_FUNCS();
f.write(chunk)
f.write(suffix_marker)
if minify_globals:
+ assert not extra_info
f.write('\n')
f.write('// EXTRA_INFO:' + minify_info)
+ elif extra_info:
+ f.write('\n')
+ f.write('// EXTRA_INFO:' + json.dumps(extra_info))
f.close()
return temp_file
filenames = [write_chunk(chunks[i], i) for i in range(len(chunks))]
@@ -329,6 +336,6 @@ EMSCRIPTEN_FUNCS();
return filename
-def run(filename, passes, js_engine, jcache, source_map=False):
- return temp_files.run_and_clean(lambda: run_on_js(filename, passes, js_engine, jcache, source_map))
+def run(filename, passes, js_engine, jcache, source_map=False, extra_info=None):
+ return temp_files.run_and_clean(lambda: run_on_js(filename, passes, js_engine, jcache, source_map, extra_info))
diff --git a/tools/shared.py b/tools/shared.py
index 776001cd..d35924c6 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -205,7 +205,7 @@ else:
config_file = '\n'.join(config_file)
# autodetect some default paths
config_file = config_file.replace('{{{ EMSCRIPTEN_ROOT }}}', __rootpath__)
- llvm_root = find_executable('llvm-dis') or '/usr/bin'
+ llvm_root = os.path.dirname(find_executable('llvm-dis') or '/usr/bin/llvm-dis')
config_file = config_file.replace('{{{ LLVM_ROOT }}}', llvm_root)
node = find_executable('node') or find_executable('nodejs') or 'node'
config_file = config_file.replace('{{{ NODE }}}', node)
@@ -400,6 +400,7 @@ EMAR = path_from_root('emar')
EMRANLIB = path_from_root('emranlib')
EMLIBTOOL = path_from_root('emlibtool')
EMCONFIG = path_from_root('em-config')
+EMLINK = path_from_root('emlink.py')
EMMAKEN = path_from_root('tools', 'emmaken.py')
AUTODEBUGGER = path_from_root('tools', 'autodebugger.py')
BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py')
@@ -866,7 +867,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
return generated_libs
@staticmethod
- def link(files, target):
+ def link(files, target, force_archive_contents=False):
actual_files = []
unresolved_symbols = set(['main']) # tracking unresolveds is necessary for .a linking, see below. (and main is always a necessary symbol)
resolved_symbols = set()
@@ -917,7 +918,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
# Link in the .o if it provides symbols, *or* this is a singleton archive (which is apparently an exception in gcc ld)
#print >> sys.stderr, 'need', content, '?', unresolved_symbols, 'and we can supply', new_symbols.defs
#print >> sys.stderr, content, 'DEF', new_symbols.defs, '\n'
- if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1:
+ if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1 or force_archive_contents:
if Building.is_bitcode(content):
#print >> sys.stderr, ' adding object', content, '\n'
resolved_symbols = resolved_symbols.union(new_symbols.defs)
@@ -1100,6 +1101,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
@staticmethod
def get_safe_internalize():
+ if not Building.can_build_standalone(): return [] # do not internalize anything
exps = expand_response(Settings.EXPORTED_FUNCTIONS)
if '_malloc' not in exps: exps.append('_malloc') # needed internally, even if user did not add to EXPORTED_FUNCTIONS
exports = ','.join(map(lambda exp: exp[1:], exps))
@@ -1209,8 +1211,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e
return opts
@staticmethod
- def js_optimizer(filename, passes, jcache, debug):
- return js_optimizer.run(filename, passes, listify(NODE_JS), jcache, debug)
+ def js_optimizer(filename, passes, jcache=False, debug=False, extra_info=None):
+ return js_optimizer.run(filename, passes, listify(NODE_JS), jcache, debug, extra_info)
@staticmethod
def closure_compiler(filename, pretty=True):
@@ -1350,9 +1352,35 @@ JCache = cache.JCache(Cache)
chunkify = cache.chunkify
class JS:
+ memory_initializer_pattern = '/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, ([\dRuntime\.GLOBAL_BASE+]+)\)'
+ no_memory_initializer_pattern = '/\* no memory initializer \*/'
+
+ memory_staticbump_pattern = 'STATICTOP = STATIC_BASE \+ (\d+);'
+
+ global_initializers_pattern = '/\* global initializers \*/ __ATINIT__.push\((.+)\);'
+
@staticmethod
def to_nice_ident(ident): # limited version of the JS function toNiceIdent
- return ident.replace('%', '$').replace('@', '_');
+ return ident.replace('%', '$').replace('@', '_')
+
+ @staticmethod
+ def make_invoke(sig, named=True):
+ args = ','.join(['a' + str(i) for i in range(1, len(sig))])
+ args = 'index' + (',' if args else '') + args
+ # C++ exceptions are numbers, and longjmp is a string 'longjmp'
+ return '''function%s(%s) {
+ try {
+ %sModule["dynCall_%s"](%s);
+ } catch(e) {
+ if (typeof e !== 'number' && e !== 'longjmp') throw e;
+ asm["setThrew"](1, 0);
+ }
+}''' % ((' invoke_' + sig) if named else '', args, 'return ' if sig[0] != 'v' else '', sig, args)
+
+ @staticmethod
+ def align(x, by):
+ while x % by != 0: x += 1
+ return x
# Compression of code and data for smaller downloads
class Compression:
diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js
index 301a2ec8..59a42010 100644
--- a/tools/test-js-optimizer-asm-pre-output.js
+++ b/tools/test-js-optimizer-asm-pre-output.js
@@ -119,6 +119,16 @@ function sign_extension_simplification() {
print(5);
}
}
+function compare_result_simplification() {
+ HEAP32[$4] = HEAP32[$5] < HEAP32[$6];
+ HEAP32[$4] = HEAP32[$5] > HEAP32[$6];
+ HEAP32[$4] = HEAP32[$5] <= HEAP32[$6];
+ HEAP32[$4] = HEAP32[$5] <= HEAP32[$6];
+ HEAP32[$4] = HEAP32[$5] == HEAP32[$6];
+ HEAP32[$4] = HEAP32[$5] === HEAP32[$6];
+ HEAP32[$4] = HEAP32[$5] != HEAP32[$6];
+ var x = HEAP32[$5] != HEAP32[$6] | 0;
+}
function tempDoublePtr($45, $14, $28, $42) {
$45 = $45 | 0;
$14 = $14 | 0;
diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js
index c7c92124..7ca941fa 100644
--- a/tools/test-js-optimizer-asm-pre.js
+++ b/tools/test-js-optimizer-asm-pre.js
@@ -122,6 +122,18 @@ function sign_extension_simplification() {
print(5);
}
}
+function compare_result_simplification() {
+ // Eliminate these '&1's.
+ HEAP32[$4] = (HEAP32[$5] < HEAP32[$6]) & 1;
+ HEAP32[$4] = (HEAP32[$5] > HEAP32[$6]) & 1;
+ HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1;
+ HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1;
+ HEAP32[$4] = (HEAP32[$5] == HEAP32[$6]) & 1;
+ HEAP32[$4] = (HEAP32[$5] === HEAP32[$6]) & 1;
+ HEAP32[$4] = (HEAP32[$5] != HEAP32[$6]) & 1;
+ // Convert the &1 to |0 here, since we don't get an implicit coersion.
+ var x = (HEAP32[$5] != HEAP32[$6]) & 1;
+}
function tempDoublePtr($45, $14, $28, $42) {
$45 = $45 | 0;
$14 = $14 | 0;
diff --git a/tools/test-js-optimizer-asm-relocate-output.js b/tools/test-js-optimizer-asm-relocate-output.js
index 6a197e81..2f8294c5 100644
--- a/tools/test-js-optimizer-asm-relocate-output.js
+++ b/tools/test-js-optimizer-asm-relocate-output.js
@@ -1,9 +1,9 @@
function leaveMeAlone(c) {}
function fixed(a, b) {}
function a(x, y) {
- fixed(34, 12);
- fixed(34 | 0, 12 | 0);
- leaveMeAlone(10 + x, 33 + y);
- leaveMeAlone(10 + x | 0, 33 + y | 0);
+ fixed(34, 4);
+ fixed(34 | 0, 102 | 0);
+ leaveMeAlone(2 + x, 33 + y);
+ leaveMeAlone(x | 0, 33 + y | 0);
}
diff --git a/tools/test-js-optimizer-asm-relocate.js b/tools/test-js-optimizer-asm-relocate.js
index a45bc2f0..5402c9f6 100644
--- a/tools/test-js-optimizer-asm-relocate.js
+++ b/tools/test-js-optimizer-asm-relocate.js
@@ -3,10 +3,10 @@ function leaveMeAlone(c) {
function replaceMe(a, b) {
}
function a(x, y) {
- replaceMe(H_BASE + 1, F_BASE + 2);
- replaceMe(H_BASE + 1 | 0, F_BASE + 2 | 0);
- leaveMeAlone(F_BASE + x, H_BASE + y);
- leaveMeAlone(F_BASE + x | 0, H_BASE + y | 0);
+ replaceMe(H_BASE + 1, F_BASE_vii + 2);
+ replaceMe(H_BASE + 1 | 0, F_BASE_vi + 2 | 0);
+ leaveMeAlone(F_BASE_vii + x, H_BASE + y);
+ leaveMeAlone(F_BASE_vUNKNOWN + x | 0, H_BASE + y | 0);
}
// EMSCRIPTEN_GENERATED_FUNCTIONS
-// EXTRA_INFO: { "replacements": { "replaceMe": "fixed" }, "hBase": 33, "fBase": 10 }
+// EXTRA_INFO: { "replacements": { "replaceMe": "fixed" }, "hBase": 33, "fBases": { "vii": 2, "vi": 100, "v": 20 } }