From 91d6303b960ba6b974ba39bd1f9d74a1ff95b7d9 Mon Sep 17 00:00:00 2001 From: Ryan Sturgell Date: Tue, 8 Apr 2014 15:17:36 -0700 Subject: [PATCH 01/24] Add Ryan Sturgell to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 7994f80e26ba4..48a47f75f6418 100644 --- a/AUTHORS +++ b/AUTHORS @@ -138,3 +138,4 @@ a license to everyone to use it as detailed in LICENSE.) * Guillaume Blanc * Usagi Ito * Camilo Polymeris +* Ryan Sturgell (copyright owned by Google, Inc.) From 932106193279f333a849653daa284069e3583f55 Mon Sep 17 00:00:00 2001 From: Ryan Sturgell Date: Tue, 8 Apr 2014 14:49:28 -0700 Subject: [PATCH 02/24] Extract helper functions from main link loop. This enables future changes which will re-scan archives for objects (either grouped with -Wl,--start-group, -Wl,--end-group or some new emcc specific flag). This is mostly moving lines around and adding some comments. The only functional change is that if you specify the same archive multiple times (a workaround for the lack of grouping flags), we will now only extract it once instead of multiple times. But it will still be scanned multiple times. --- tools/shared.py | 134 +++++++++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 46 deletions(-) diff --git a/tools/shared.py b/tools/shared.py index 826baa832caba..63367c30fdcef 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1145,62 +1145,104 @@ def link(files, target, force_archive_contents=False): resolved_symbols = set() temp_dirs = [] files = map(os.path.abspath, files) + # Paths of already included object files from archives. + added_contents = set() + # Map of archive name to list of extracted object file paths. + ar_contents = {} has_ar = False for f in files: has_ar = has_ar or Building.is_ar(f) + + # If we have only one archive or the force_archive_contents flag is set, + # then we will add every object file we see, regardless of whether it + # resolves any undefined symbols. + force_add_all = len(files) == 1 or force_archive_contents + + # Considers an object file for inclusion in the link. The object is included + # if force_add=True or if the object provides a currently undefined symbol. + # If the object is included, the symbol tables are updated and the function + # returns True. + def consider_object(f, force_add=False): + new_symbols = Building.llvm_nm(f) + do_add = force_add or not unresolved_symbols.isdisjoint(new_symbols.defs) + if do_add: + #print >> sys.stderr, ' adding object', content, '\n' + # Update resolved_symbols table with newly resolved symbols + resolved_symbols.update(new_symbols.defs) + # Update unresolved_symbols table by adding newly unresolved symbols and + # removing newly resolved symbols. + unresolved_symbols.update(new_symbols.undefs.difference(resolved_symbols)) + unresolved_symbols.difference_update(new_symbols.defs) + #print >> sys.stderr, ' undef are now ', unresolved_symbols, '\n' + actual_files.append(f) + return do_add + + def get_archive_contents(f): + if f in ar_contents: + return ar_contents[f] + + cwd = os.getcwd() + try: + temp_dir = os.path.join(EMSCRIPTEN_TEMP_DIR, 'ar_output_' + str(os.getpid()) + '_' + str(len(temp_dirs))) + temp_dirs.append(temp_dir) + safe_ensure_dirs(temp_dir) + os.chdir(temp_dir) + contents = filter(lambda x: len(x) > 0, Popen([LLVM_AR, 't', f], stdout=PIPE).communicate()[0].split('\n')) + #print >> sys.stderr, ' considering archive', f, ':', contents + if len(contents) == 0: + logging.debug('Archive %s appears to be empty (recommendation: link an .so instead of .a)' % f) + else: + for content in contents: # ar will silently fail if the directory for the file does not exist, so make all the necessary directories + dirname = os.path.dirname(content) + if dirname: + safe_ensure_dirs(dirname) + Popen([LLVM_AR, 'xo', f], stdout=PIPE).communicate() # if absolute paths, files will appear there. otherwise, in this directory + contents = map(lambda content: os.path.join(temp_dir, content), contents) + contents = filter(os.path.exists, map(os.path.abspath, contents)) + contents = filter(Building.is_bitcode, contents) + ar_contents[f] = contents + finally: + os.chdir(cwd) + + return contents + + # Traverse a single archive. The object files are repeatedly scanned for + # newly satisfied symbols until no new symbols are found. Returns true if + # any object files were added to the link. + def consider_archive(f): + added_any_objects = False + loop_again = True + #print >> sys.stderr, ' initial undef are now ', unresolved_symbols, '\n' + contents = get_archive_contents(f) + while loop_again: # repeatedly traverse until we have everything we need + #print >> sys.stderr, ' running loop of archive including for', f + loop_again = False + for content in contents: + if content in added_contents: continue + # 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 consider_object(content, force_add=force_add_all): + added_contents.add(content) + loop_again = True + added_any_objects = True + #print >> sys.stderr, ' done running loop of archive including for', f + return added_any_objects + for f in files: if not Building.is_ar(f): if Building.is_bitcode(f): if has_ar: - new_symbols = Building.llvm_nm(f) - resolved_symbols = resolved_symbols.union(new_symbols.defs) - unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs) - actual_files.append(f) + consider_object(f, force_add=True) + else: + # If there are no archives then we can simply link all valid bitcode + # files and skip the symbol table stuff. + actual_files.append(f) else: # Extract object files from ar archives, and link according to gnu ld semantics # (link in an entire .o from the archive if it supplies symbols still unresolved) - cwd = os.getcwd() - try: - temp_dir = os.path.join(EMSCRIPTEN_TEMP_DIR, 'ar_output_' + str(os.getpid()) + '_' + str(len(temp_dirs))) - temp_dirs.append(temp_dir) - safe_ensure_dirs(temp_dir) - os.chdir(temp_dir) - contents = filter(lambda x: len(x) > 0, Popen([LLVM_AR, 't', f], stdout=PIPE).communicate()[0].split('\n')) - #print >> sys.stderr, ' considering archive', f, ':', contents - if len(contents) == 0: - logging.debug('Archive %s appears to be empty (recommendation: link an .so instead of .a)' % f) - else: - for content in contents: # ar will silently fail if the directory for the file does not exist, so make all the necessary directories - dirname = os.path.dirname(content) - if dirname: - safe_ensure_dirs(dirname) - Popen([LLVM_AR, 'xo', f], stdout=PIPE).communicate() # if absolute paths, files will appear there. otherwise, in this directory - contents = map(lambda content: os.path.join(temp_dir, content), contents) - contents = filter(os.path.exists, map(os.path.abspath, contents)) - added_contents = set() - added = True - #print >> sys.stderr, ' initial undef are now ', unresolved_symbols, '\n' - while added: # recursively traverse until we have everything we need - #print >> sys.stderr, ' running loop of archive including for', f - added = False - for content in contents: - if content in added_contents: continue - new_symbols = Building.llvm_nm(content) - # 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 or force_archive_contents: - if Building.is_bitcode(content): - #print >> sys.stderr, ' adding object', content, '\n' - resolved_symbols = resolved_symbols.union(new_symbols.defs) - unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs) - #print >> sys.stderr, ' undef are now ', unresolved_symbols, '\n' - actual_files.append(content) - added_contents.add(content) - added = True - #print >> sys.stderr, ' done running loop of archive including for', f - finally: - os.chdir(cwd) + consider_archive(f) + try_delete(target) # Finish link From 6556a69f282ec534512c3e6a119328fe98886a97 Mon Sep 17 00:00:00 2001 From: Ryan Sturgell Date: Tue, 8 Apr 2014 16:26:32 -0700 Subject: [PATCH 03/24] Track original flag positions in emcc. Source files, libs, and linker flags all need to be separately processed. This allows them to be correctly shuffled back together for further link processing. --- emcc | 97 +++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/emcc b/emcc index 1629f5c74fa78..6941170fb343d 100755 --- a/emcc +++ b/emcc @@ -1098,12 +1098,24 @@ try: # Find input files + # These three arrays are used to store arguments of different types for + # type-specific processing. In order to shuffle the arguments back together + # after processing, all of these arrays hold tuples (original_index, value). + # Note that the index part of the tuple can have a fractional part for input + # arguments that expand into multiple processed arguments, as in -Wl,-f1,-f2. input_files = [] + libs = [] + link_flags = [] + + # All of the above arg lists entries contain indexes into the full argument + # list. In order to add extra implicit args (embind.cc, etc) below, we keep a + # counter for the next index that should be used. + next_arg_index = len(newargs) + has_source_inputs = False has_header_inputs = False lib_dirs = [shared.path_from_root('system', 'local', 'lib'), shared.path_from_root('system', 'lib')] - libs = [] for i in range(len(newargs)): # find input files XXX this a simple heuristic. we should really analyze based on a full understanding of gcc params, # right now we just assume that what is left contains no more |-x OPT| things arg = newargs[i] @@ -1124,13 +1136,13 @@ try: if arg_ending.endswith(SOURCE_ENDINGS + BITCODE_ENDINGS + DYNAMICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS) or shared.Building.is_ar(arg): # we already removed -o , so all these should be inputs newargs[i] = '' if arg_ending.endswith(SOURCE_ENDINGS): - input_files.append(arg) + input_files.append((i, arg)) has_source_inputs = True elif arg_ending.endswith(HEADER_ENDINGS): - input_files.append(arg) + input_files.append((i, arg)) has_header_inputs = True elif arg_ending.endswith(ASSEMBLY_ENDINGS) or shared.Building.is_bitcode(arg): # this should be bitcode, make sure it is valid - input_files.append(arg) + input_files.append((i, arg)) elif arg_ending.endswith(STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS): # if it's not, and it's a library, just add it to libs to find later l = unsuffixed_basename(arg) @@ -1139,7 +1151,7 @@ try: if l.startswith(prefix): l = l[len(prefix):] break - libs.append(l) + libs.append((i, l)) newargs[i] = '' else: logging.warning(arg + ' is not valid LLVM bitcode') @@ -1157,7 +1169,15 @@ try: lib_dirs.append(arg[2:]) newargs[i] = '' elif arg.startswith('-l'): - libs.append(arg[2:]) + libs.append((i, arg[2:])) + newargs[i] = '' + elif arg.startswith('-Wl,'): + # Multiple comma separated link flags can be specified. Create fake + # fractional indices for these: -Wl,a,b,c,d at index 4 becomes: + # (4, a), (4.25, b), (4.5, c), (4.75, d) + link_flags_to_add = arg.split(',')[1:] + for flag_index, flag in enumerate(link_flags_to_add): + link_flags.append((i + float(flag_index) / len(link_flags_to_add), flag)) newargs[i] = '' original_input_files = input_files[:] @@ -1173,7 +1193,7 @@ try: final_ending = ('.' + final_suffix) if len(final_suffix) > 0 else '' # Find library files - for lib in libs: + for i, lib in libs: logging.debug('looking for library "%s"', lib) found = False for prefix in LIB_PREFIXES: @@ -1183,7 +1203,7 @@ try: path = os.path.join(lib_dir, name) if os.path.exists(path): logging.debug('found library "%s" at %s', lib, path) - input_files.append(path) + input_files.append((i, path)) found = True break if found: break @@ -1199,7 +1219,7 @@ try: return False else: return True - input_files = [input_file for input_file in input_files if check(input_file)] + input_files = [(i, input_file) for (i, input_file) in input_files if check(input_file)] if len(input_files) == 0: logging.error('no input files\nnote that input files without a known suffix are ignored, make sure your input files end with one of: ' + str(SOURCE_ENDINGS + BITCODE_ENDINGS + DYNAMICLIB_ENDINGS + STATICLIB_ENDINGS + ASSEMBLY_ENDINGS + HEADER_ENDINGS)) @@ -1211,7 +1231,8 @@ try: # If we are using embind and generating JS, now is the time to link in bind.cpp if bind and final_suffix in JS_CONTAINING_SUFFIXES: - input_files.append(shared.path_from_root('system', 'lib', 'embind', 'bind.cpp')) + input_files.append((next_arg_index, shared.path_from_root('system', 'lib', 'embind', 'bind.cpp'))) + next_arg_index += 1 # Apply optimization level settings shared.Settings.apply_opt_level(opt_level, noisy=True) @@ -1329,7 +1350,8 @@ try: logging.warning('ALIASING_FUNCTION_POINTERS is on, function pointer comparisons may be invalid across types') if shared.Settings.STB_IMAGE and final_suffix in JS_CONTAINING_SUFFIXES: - input_files.append(shared.path_from_root('third_party', 'stb_image.c')) + input_files.append((next_arg_index, shared.path_from_root('third_party', 'stb_image.c'))) + next_arg_index += 1 shared.Settings.EXPORTED_FUNCTIONS += ['_stbi_load', '_stbi_load_from_memory', '_stbi_image_free'] if type(shared.Settings.EXPORTED_FUNCTIONS) in (list, tuple): @@ -1363,12 +1385,13 @@ try: # Precompiled headers support if has_header_inputs: - for header in input_files: - assert header.endswith(HEADER_ENDINGS), 'if you have one header input, we assume you want to precompile headers, and cannot have source files or other inputs as well: ' + str(input_files) + ' : ' + header - args = newargs + shared.EMSDK_CXX_OPTS + input_files + headers = [header for _, header in input_files] + for header in headers: + assert header.endswith(HEADER_ENDINGS), 'if you have one header input, we assume you want to precompile headers, and cannot have source files or other inputs as well: ' + str(headers) + ' : ' + header + args = newargs + shared.EMSDK_CXX_OPTS + headers if specified_target: args += ['-o', specified_target] - logging.debug("running (for precompiled headers): " + call + ' ' + ' '.join(args)) + logging.debug("running (for precompiled headers: " + call + ' ' + ' '.join(args)) execute([call] + args) # let compiler frontend print directly, so colors are saved (PIPE kills that) sys.exit(0) @@ -1386,12 +1409,12 @@ try: return in_temp(unsuffixed(uniquename(input_file)) + default_object_extension) # First, generate LLVM bitcode. For each input file, we get base.o with bitcode - for input_file in input_files: + for i, input_file in input_files: file_ending = filename_type_ending(input_file) if file_ending.endswith(SOURCE_ENDINGS): logging.debug('compiling source file: ' + input_file) output_file = get_bitcode_file(input_file) - temp_files.append(output_file) + temp_files.append((i, output_file)) args = newargs + ['-emit-llvm', '-c', input_file, '-o', output_file] if file_ending.endswith(CXX_ENDINGS): args += shared.EMSDK_CXX_OPTS @@ -1405,18 +1428,18 @@ try: logging.debug('copying bitcode file: ' + input_file) temp_file = in_temp(unsuffixed(uniquename(input_file)) + '.o') shutil.copyfile(input_file, temp_file) - temp_files.append(temp_file) + temp_files.append((i, temp_file)) elif file_ending.endswith(DYNAMICLIB_ENDINGS) or shared.Building.is_ar(input_file): logging.debug('copying library file: ' + input_file) temp_file = in_temp(uniquename(input_file)) shutil.copyfile(input_file, temp_file) - temp_files.append(temp_file) + temp_files.append((i, temp_file)) elif file_ending.endswith(ASSEMBLY_ENDINGS): if not LEAVE_INPUTS_RAW: logging.debug('assembling assembly file: ' + input_file) temp_file = in_temp(unsuffixed(uniquename(input_file)) + '.o') shared.Building.llvm_as(input_file, temp_file) - temp_files.append(temp_file) + temp_files.append((i, temp_file)) else: logging.error(input_file + ': Unknown file suffix when compiling to LLVM bitcode!') sys.exit(1) @@ -1428,10 +1451,10 @@ try: # Optimize source files if llvm_opts > 0: - for i, input_file in enumerate(input_files): + for pos, (_, input_file) in enumerate(input_files): file_ending = filename_type_ending(input_file) if file_ending.endswith(SOURCE_ENDINGS): - temp_file = temp_files[i] + (_, temp_file) = temp_files[pos] logging.debug('optimizing %s', input_file) #if DEBUG: shutil.copyfile(temp_file, os.path.join(TEMP_DIR, 'to_opt.bc') # useful when LLVM opt aborts shared.Building.llvm_opt(temp_file, llvm_opts) @@ -1439,26 +1462,30 @@ try: # If we were just asked to generate bitcode, stop there if final_suffix not in JS_CONTAINING_SUFFIXES: if not specified_target: - for input_file in input_files: + for _, input_file in input_files: safe_move(get_bitcode_file(input_file), unsuffixed_basename(input_file) + final_ending) else: if len(input_files) == 1: - safe_move(temp_files[0], specified_target if specified_target else unsuffixed_basename(input_file) + final_ending) - temp_output_base = unsuffixed(temp_files[0]) + _, input_file = input_files[0] + _, temp_file = temp_files[0] + safe_move(temp_file, specified_target if specified_target else unsuffixed_basename(input_file) + final_ending) + temp_output_base = unsuffixed(temp_file) if os.path.exists(temp_output_base + '.d'): # There was a .d file generated, from -MD or -MMD and friends, save a copy of it to where the output resides, # adjusting the target name away from the temporary file name to the specified target. # It will be deleted with the rest of the temporary directory. deps = open(temp_output_base + '.d').read() deps = deps.replace(temp_output_base + default_object_extension, specified_target) - with open(os.path.join(os.path.dirname(specified_target), os.path.basename(unsuffixed(input_files[0]) + '.d')), "w") as out_dep: + with open(os.path.join(os.path.dirname(specified_target), os.path.basename(unsuffixed(input_file) + '.d')), "w") as out_dep: out_dep.write(deps) else: assert len(original_input_files) == 1 or not has_dash_c, 'fatal error: cannot specify -o with -c with multiple files' + str(sys.argv) + ':' + str(original_input_files) # We have a specified target (-o ), which is not JavaScript or HTML, and # we have multiple files: Link them logging.debug('link: ' + str(temp_files) + specified_target) - shared.Building.link(temp_files, specified_target) + # Sort arg tuples and pass the extracted values to link. + link_args = [f for (i, f) in sorted(temp_files)] + shared.Building.link(link_args, specified_target) logging.debug('stopping at bitcode') exit(0) @@ -1471,7 +1498,7 @@ try: if not LEAVE_INPUTS_RAW and \ not shared.Settings.BUILD_AS_SHARED_LIB and \ not shared.Settings.SIDE_MODULE: # shared libraries/side modules link no C libraries, need them in parent - extra_files_to_link = system_libs.calculate(temp_files, in_temp, stdout, stderr) + extra_files_to_link = system_libs.calculate([f for _, f in sorted(temp_files)], in_temp, stdout, stderr) else: extra_files_to_link = [] @@ -1479,18 +1506,20 @@ try: # 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 \ - (not LEAVE_INPUTS_RAW and not (suffix(temp_files[0]) in BITCODE_ENDINGS or suffix(temp_files[0]) in DYNAMICLIB_ENDINGS) and shared.Building.is_ar(temp_files[0])): - linker_inputs = temp_files + extra_files_to_link + (not LEAVE_INPUTS_RAW and not (suffix(temp_files[0][1]) in BITCODE_ENDINGS or suffix(temp_files[0][1]) in DYNAMICLIB_ENDINGS) and shared.Building.is_ar(temp_files[0][1])): + linker_inputs = [val for _, val in sorted(temp_files + link_flags)] + extra_files_to_link logging.debug('linking: ' + str(linker_inputs)) - shared.Building.link(linker_inputs, in_temp(target_basename + '.bc'), force_archive_contents=len([temp for temp in temp_files if not temp.endswith(STATICLIB_ENDINGS)]) == 0) + shared.Building.link(linker_inputs, in_temp(target_basename + '.bc'), force_archive_contents=len([temp for i, temp in temp_files if not temp.endswith(STATICLIB_ENDINGS)]) == 0) final = in_temp(target_basename + '.bc') else: if not LEAVE_INPUTS_RAW: - shutil.move(temp_files[0], in_temp(target_basename + '.bc')) + _, temp_file = temp_files[0] + shutil.move(temp_file, in_temp(target_basename + '.bc')) final = in_temp(target_basename + '.bc') else: - final = in_temp(input_files[0]) - shutil.copyfile(input_files[0], final) + _, input_file = input_files[0] + final = in_temp(input_file) + shutil.copyfile(input_file, final) log_time('link') From 2fdc694d1a1bec91578cfcdbb0d829dcc58d1957 Mon Sep 17 00:00:00 2001 From: Ryan Sturgell Date: Thu, 1 May 2014 12:06:40 -0700 Subject: [PATCH 04/24] Add support for archive groups. emcc now understands -Wl,--start-group, -Wl,--end-group to group static libraries. Static libraries in a group need not be specified in dependent to dependency order and can have circular dependencies. --- tests/test_other.py | 77 ++++++++++++++++++++++++++++++++++++++++++++- tools/shared.py | 33 +++++++++++++++++-- 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/tests/test_other.py b/tests/test_other.py index 137a83b136727..966d99f4a17d6 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1027,10 +1027,85 @@ def test_multiply_defined_libsymbols_2(self): Building.emar('cr', lib_name, [a_name + '.o', b_name + '.o']) # libLIB.a with a and b # a is in the lib AND in an .o, so should be ignored in the lib. We do still need b from the lib though - Building.emcc(main_name, ['-L.', '-lLIB', a_name+'.o', c_name + '.o'], output_filename='a.out.js') + Building.emcc(main_name, [a_name+'.o', c_name + '.o', '-L.', '-lLIB'], output_filename='a.out.js') self.assertContained('result: 62', run_js(os.path.join(self.get_dir(), 'a.out.js'))) + def test_link_group_asserts(self): + lib_src_name = os.path.join(self.get_dir(), 'lib.c') + open(lib_src_name, 'w').write('int x() { return 42; }') + + main_name = os.path.join(self.get_dir(), 'main.c') + open(main_name, 'w').write(r''' + #include + int x(); + int main() { + printf("result: %d\n", x()); + return 0; + } + ''') + + Building.emcc(lib_src_name) # lib.c.o + lib_name = os.path.join(self.get_dir(), 'libLIB.a') + Building.emar('cr', lib_name, [lib_src_name + '.o']) # libLIB.a with lib.c.o + + def test(lib_args, err_expected): + output = Popen([PYTHON, EMCC, main_name, '-o', 'a.out.js'] + lib_args, stdout=PIPE, stderr=PIPE).communicate() + if err_expected: + self.assertContained(err_expected, output[1]) + else: + out_js = os.path.join(self.get_dir(), 'a.out.js') + assert os.path.exists(out_js), '\n'.join(output) + self.assertContained('result: 42', run_js(out_js)) + + test(['-Wl,--start-group', lib_name], '--start-group without matching --end-group') + test(['-Wl,--start-group', lib_name, '-Wl,--start-group'], 'Nested --start-group, missing --end-group?') + test(['-Wl,--end-group', lib_name, '-Wl,--start-group'], '--end-group without --start-group') + test(['-Wl,--start-group', lib_name, '-Wl,--end-group'], None) + + def test_circular_libs(self): + def tmp_source(name, code): + file_name = os.path.join(self.get_dir(), name) + open(file_name, 'w').write(code) + return file_name + + a = tmp_source('a.c', 'int z(); int x() { return z(); }') + b = tmp_source('b.c', 'int x(); int y() { return x(); } int z() { return 42; }') + c = tmp_source('c.c', 'int q() { return 0; }') + main = tmp_source('main.c', r''' + #include + int y(); + int main() { + printf("result: %d\n", y()); + return 0; + } + ''') + + Building.emcc(a) # a.c.o + Building.emcc(b) # b.c.o + Building.emcc(c) # c.c.o + lib_a = os.path.join(self.get_dir(), 'libA.a') + Building.emar('cr', lib_a, [a + '.o', c + '.o']) # libA.a with a.c.o,c.c.o + lib_b = os.path.join(self.get_dir(), 'libB.a') + Building.emar('cr', lib_b, [b + '.o', c + '.o']) # libB.a with b.c.o,c.c.o + + args = ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1', main, '-o', 'a.out.js'] + libs = [lib_a, lib_b] + + # lib_a does not satisfy any symbols from main, so it will not be included, + # and there will be an unresolved symbol. + output = Popen([PYTHON, EMCC] + args + libs, stdout=PIPE, stderr=PIPE).communicate() + self.assertContained('error: unresolved symbol: x', output[1]) + + # -Wl,--start-group and -Wl,--end-group around the libs will cause a rescan + # of lib_a after lib_b adds undefined symbol "x", so a.c.o will now be + # included (and the link will succeed). + libs = ['-Wl,--start-group'] + libs + ['-Wl,--end-group'] + output = Popen([PYTHON, EMCC] + args + libs, stdout=PIPE, stderr=PIPE).communicate() + out_js = os.path.join(self.get_dir(), 'a.out.js') + assert os.path.exists(out_js), '\n'.join(output) + self.assertContained('result: 42', run_js(out_js)) + def test_redundant_link(self): lib = "int mult() { return 1; }" lib_name = os.path.join(self.get_dir(), 'libA.c') diff --git a/tools/shared.py b/tools/shared.py index 63367c30fdcef..8b98ac0da887a 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1144,14 +1144,20 @@ def link(files, target, force_archive_contents=False): unresolved_symbols = set([func[1:] for func in Settings.EXPORTED_FUNCTIONS]) resolved_symbols = set() temp_dirs = [] - files = map(os.path.abspath, files) + def make_paths_absolute(f): + if f.startswith('-'): # skip flags + return f + else: + return os.path.abspath(f) + files = map(make_paths_absolute, files) # Paths of already included object files from archives. added_contents = set() # Map of archive name to list of extracted object file paths. ar_contents = {} has_ar = False for f in files: - has_ar = has_ar or Building.is_ar(f) + if not f.startswith('-'): + has_ar = has_ar or Building.is_ar(f) # If we have only one archive or the force_archive_contents flag is set, # then we will add every object file we see, regardless of whether it @@ -1229,8 +1235,26 @@ def consider_archive(f): #print >> sys.stderr, ' done running loop of archive including for', f return added_any_objects + current_archive_group = None for f in files: - if not Building.is_ar(f): + if f.startswith('-'): + if f in ['--start-group', '-(']: + assert current_archive_group is None, 'Nested --start-group, missing --end-group?' + current_archive_group = [] + elif f in ['--end-group', '-)']: + assert current_archive_group is not None, '--end-group without --start-group' + # rescan the archives in the group until we don't find any more + # objects to link. + loop_again = True + while loop_again: + loop_again = False + for archive in current_archive_group: + if consider_archive(archive): + loop_again = True + current_archive_group = None + else: + logging.debug('Ignoring unsupported link flag: %s' % f) + elif not Building.is_ar(f): if Building.is_bitcode(f): if has_ar: consider_object(f, force_add=True) @@ -1242,6 +1266,9 @@ def consider_archive(f): # Extract object files from ar archives, and link according to gnu ld semantics # (link in an entire .o from the archive if it supplies symbols still unresolved) consider_archive(f) + if current_archive_group is not None: + current_archive_group.append(f) + assert current_archive_group is None, '--start-group without matching --end-group' try_delete(target) From dc79f4645bb306bd8defd4d6d5014f9611e142dd Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 11 May 2014 08:41:37 +0200 Subject: [PATCH 05/24] Optimize ccall and cwrap --- src/preamble.js | 190 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 128 insertions(+), 62 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index 2aec94c6a9293..1859c1342de34 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -129,7 +129,7 @@ function SAFE_HEAP_LOAD(dest, type, unsigned, ignore) { #if SAFE_HEAP_LOG Module.print('SAFE_HEAP load: ' + [dest, type, getValue(dest, type, 1), ignore]); -#endif +#endif #if USE_TYPED_ARRAYS == 2 // Check alignment @@ -310,28 +310,6 @@ function assert(condition, text) { var globalScope = this; -// C calling interface. A convenient way to call C functions (in C files, or -// defined with extern "C"). -// -// Note: LLVM optimizations can inline and remove functions, after which you will not be -// able to call them. Closure can also do so. To avoid that, add your function to -// the exports using something like -// -// -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]' -// -// @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C") -// @param returnType The return type of the function, one of the JS types 'number', 'string' or 'array' (use 'number' for any C pointer, and -// 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit). -// @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType, -// except that 'array' is not possible (there is no way for us to know the length of the array) -// @param args An array of the arguments to the function, as native JS values (as in returnType) -// Note that string arguments will be stored on the stack (the JS string will become a C string on the stack). -// @return The return value, as a native JS value (as in returnType) -function ccall(ident, returnType, argTypes, args) { - return ccallFunc(getCFunc(ident), returnType, argTypes, args); -} -Module["ccall"] = ccall; - // Returns the C function with a specified identifier (for C++, you need to do manual name mangling) function getCFunc(ident) { try { @@ -343,53 +321,141 @@ function getCFunc(ident) { return func; } -// Internal function that does a C call using a function, not an identifier -function ccallFunc(func, returnType, argTypes, args) { +var cwrap, ccall; +(function(){ var stack = 0; - function toC(value, type) { - if (type == 'string') { - if (value === null || value === undefined || value === 0) return 0; // null string - value = intArrayFromString(value); - type = 'array'; - } - if (type == 'array') { - if (!stack) stack = Runtime.stackSave(); - var ret = Runtime.stackAlloc(value.length); - writeArrayToMemory(value, ret); + var JSfuncs = { + 'stackSave' : function() { + stack = Runtime.stackSave(); + }, + 'stackRestore' : function() { + Runtime.stackRestore(stack); + }, + // type conversion from js to c + 'arrayToC' : function(arr) { + var ret = Runtime.stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + 'stringToC' : function(str) { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { // null string + ret = Runtime.stackAlloc(str.length + 1); // +1 for the trailing '\0' + writeStringToMemory(str, ret); + } return ret; } - return value; - } - function fromC(value, type) { - if (type == 'string') { - return Pointer_stringify(value); + }; + // For fast lookup of conversion functions + var toC = {'string' : JSfuncs['stringToC'], 'array' : JSfuncs['arrayToC']}; + + // C calling interface. A convenient way to call C functions (in C files, or + // defined with extern "C"). + // + // Note: LLVM optimizations can inline and remove functions, after which you will not be + // able to call them. Closure can also do so. To avoid that, add your function to + // the exports using something like + // + // -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]' + // + // @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C") + // @param returnType The return type of the function, one of the JS types 'number', 'string' or 'array' (use 'number' for any C pointer, and + // 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit). + // @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType, + // except that 'array' is not possible (there is no way for us to know the length of the array) + // @param args An array of the arguments to the function, as native JS values (as in returnType) + // Note that string arguments will be stored on the stack (the JS string will become a C string on the stack). + // @return The return value, as a native JS value (as in returnType) + ccall = function ccallFunc(ident, returnType, argTypes, args) { + var func = getCFunc(ident); + var cArgs = []; +#if ASSERTIONS + assert(returnType !== 'array', 'Return type should not be "array".'); +#endif + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = Runtime.stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } } - assert(type != 'array'); - return value; + var ret = func.apply(null, cArgs); + if (returnType === 'string') ret = Pointer_stringify(ret); + if (stack !== 0) Runtime.stackRestore(stack); + return ret; } - var i = 0; - var cArgs = args ? args.map(function(arg) { - return toC(arg, argTypes[i++]); - }) : []; - var ret = fromC(func.apply(null, cArgs), returnType); - if (stack) Runtime.stackRestore(stack); - return ret; -} -// Returns a native JS wrapper for a C function. This is similar to ccall, but -// returns a function you can call repeatedly in a normal way. For example: -// -// var my_function = cwrap('my_c_function', 'number', ['number', 'number']); -// alert(my_function(5, 22)); -// alert(my_function(99, 12)); -// -function cwrap(ident, returnType, argTypes) { - var func = getCFunc(ident); - return function() { - return ccallFunc(func, returnType, argTypes, Array.prototype.slice.call(arguments)); + var sourceRegex = /^function \((.*)\)\s*{\s*([^]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/; + function parseJSFunc(jsfunc) { + // Match the body and the return value of a javascript function source + var parsed = jsfunc.toString().match(sourceRegex).slice(1); + return {arguments : parsed[0], body : parsed[1], returnValue: parsed[2]} } -} + var JSsource = {}; + for (var fun in JSfuncs) { + if (JSfuncs.hasOwnProperty(fun)) { + // Elements of toCsource are arrays of three items: + // the code, and the return value + JSsource[fun] = parseJSFunc(JSfuncs[fun]); + } + } + // Returns a native JS wrapper for a C function. This is similar to ccall, but + // returns a function you can call repeatedly in a normal way. For example: + // + // var my_function = cwrap('my_c_function', 'number', ['number', 'number']); + // alert(my_function(5, 22)); + // alert(my_function(99, 12)); + // + cwrap = function cwrap(ident, returnType, argTypes) { + var cfunc = getCFunc(ident); + // When the function takes numbers and returns a number, we can just return + // the original function + var numericArgs = argTypes.every(function(type){ return type === 'number'}); + var numericRet = (returnType !== 'string'); + if ( numericRet && numericArgs) { + return cfunc; + } + // Creation of the arguments list (["$1","$2",...,"$nargs"]) + var argNames = argTypes.map(function(x,i){return '$'+i}); + var funcstr = "(function(" + argNames.join(',') + ") {"; + var nargs = argTypes.length; + if (!numericArgs) { + // Generate the code needed to convert the arguments from javascript + // values to pointers + funcstr += JSsource['stackSave'].body + ';'; + for (var i = 0; i < nargs; i++) { + var arg = argNames[i], type = argTypes[i]; + if (type === 'number') continue; + var convertCode = JSsource[type + 'ToC']; // [code, return] + funcstr += 'var ' + convertCode.arguments + ' = ' + arg + ';'; + funcstr += convertCode.body + ';'; + funcstr += arg + '=' + convertCode.returnValue + ';'; + } + } + + // When the code is compressed, the name of cfunc is not litterally 'cfunc' anymore + var cfuncname = parseJSFunc(function(){return cfunc}).returnValue; + // Call the function + funcstr += 'var ret = ' + cfuncname + '(' + argNames.join(',') + ');'; + if (!numericRet) { // Return type can only by 'string' or 'number' + // Convert the result to a string + var strgfy = parseJSFunc(function(){return Pointer_stringify}).returnValue; + funcstr += 'ret = ' + strgfy + '(ret);'; + } + if (!numericArgs) { + // If we had a stack, restore it + funcstr += JSsource['stackRestore'].body + ';'; + } + funcstr += 'return ret})'; + return eval(funcstr); + }; +})(); Module["cwrap"] = cwrap; +Module["ccall"] = ccall; // Sets a value in memory in a dynamic way at run-time. Uses the // type data. This is the same as makeSetValue, except that From e25a2b096f09be2e8f37e49c17d9062b816eaa02 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 11 May 2014 08:44:22 +0200 Subject: [PATCH 06/24] Remove useless spaces --- src/preamble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preamble.js b/src/preamble.js index 1859c1342de34..c2ee63dcb4628 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -129,7 +129,7 @@ function SAFE_HEAP_LOAD(dest, type, unsigned, ignore) { #if SAFE_HEAP_LOG Module.print('SAFE_HEAP load: ' + [dest, type, getValue(dest, type, 1), ignore]); -#endif +#endif #if USE_TYPED_ARRAYS == 2 // Check alignment From f0489213abd2235a7fc81ab558b5a234ceeee9e3 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 11 May 2014 13:16:07 +0200 Subject: [PATCH 07/24] Fix typo: 'litterally' -> 'literally' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In French, it's spelled 'littéralement', with 2 't' ;) --- src/preamble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preamble.js b/src/preamble.js index c2ee63dcb4628..0e0101f220a2d 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -437,7 +437,7 @@ var cwrap, ccall; } } - // When the code is compressed, the name of cfunc is not litterally 'cfunc' anymore + // When the code is compressed, the name of cfunc is not literally 'cfunc' anymore var cfuncname = parseJSFunc(function(){return cfunc}).returnValue; // Call the function funcstr += 'var ret = ' + cfuncname + '(' + argNames.join(',') + ');'; From deb10f42434a8992b67b72d0dc3bf993b5fe410f Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 18 May 2014 01:13:22 +0200 Subject: [PATCH 08/24] Correct indentation --- src/preamble.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/preamble.js b/src/preamble.js index 0e0101f220a2d..4fa9ee064ded8 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -391,9 +391,9 @@ var cwrap, ccall; var sourceRegex = /^function \((.*)\)\s*{\s*([^]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/; function parseJSFunc(jsfunc) { - // Match the body and the return value of a javascript function source - var parsed = jsfunc.toString().match(sourceRegex).slice(1); - return {arguments : parsed[0], body : parsed[1], returnValue: parsed[2]} + // Match the body and the return value of a javascript function source + var parsed = jsfunc.toString().match(sourceRegex).slice(1); + return {arguments : parsed[0], body : parsed[1], returnValue: parsed[2]} } var JSsource = {}; for (var fun in JSfuncs) { From ba00e71ac68819df214fdfc57dda71c5bb58e887 Mon Sep 17 00:00:00 2001 From: Ophir LOJKINE Date: Sun, 18 May 2014 01:20:13 +0200 Subject: [PATCH 09/24] Use JSfuncs.stackrestore instead of duplicating its code --- src/preamble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preamble.js b/src/preamble.js index 4fa9ee064ded8..f46119c723fb2 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -385,7 +385,7 @@ var cwrap, ccall; } var ret = func.apply(null, cArgs); if (returnType === 'string') ret = Pointer_stringify(ret); - if (stack !== 0) Runtime.stackRestore(stack); + if (stack !== 0) JSfuncs['stackRestore'](); return ret; } From a4ae7a1c29e7873f77ea4168e1c8c2c66a7cb34a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2014 10:45:29 -0700 Subject: [PATCH 10/24] do not move around loop variable incrementations when the helper is used outside the loop --- tools/eliminator/asm-eliminator-test-output.js | 14 ++++++++++++++ tools/eliminator/asm-eliminator-test.js | 17 ++++++++++++++++- tools/js-optimizer.js | 4 +++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js index d530a90cad87b..ab4c13cc7506b 100644 --- a/tools/eliminator/asm-eliminator-test-output.js +++ b/tools/eliminator/asm-eliminator-test-output.js @@ -954,4 +954,18 @@ function loopVarWithContinue() { } } } +function helperExtraUse() { + var i = 0, i$looptemp = 0; + i = 0; + while (1) { + i$looptemp = i; + i = i + 1; + work(i$looptemp); + work(i); + if (check()) { + break; + } + } + return i; +} diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js index 8c469964a51d8..7b949c44d6948 100644 --- a/tools/eliminator/asm-eliminator-test.js +++ b/tools/eliminator/asm-eliminator-test.js @@ -1208,5 +1208,20 @@ function loopVarWithContinue() { } } } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVarStillUsedSE", "elimOneLoopVar5"] +function helperExtraUse() { + var i = 0, inc = 0; + i = 0; + while (1) { + inc = i + 1; + work(i); + work(inc); + if (check()) { + break; + } else { + i = inc; + } + } + return inc; +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVarStillUsedSE", "elimOneLoopVar5", "helperExtraUse"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index c0096df420653..0cd27aa87a316 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3704,9 +3704,11 @@ function eliminate(ast, memSafe) { } if (firstLooperUsage >= 0) { // the looper is used, we cannot simply merge the two variables - if ((firstHelperUsage < 0 || firstHelperUsage > lastLooperUsage) && lastLooperUsage+1 < stats.length && triviallySafeToMove(stats[found], asmData)) { + if ((firstHelperUsage < 0 || firstHelperUsage > lastLooperUsage) && lastLooperUsage+1 < stats.length && triviallySafeToMove(stats[found], asmData) && + seenUses[helper] === namings[helper]) { // the helper is not used, or it is used after the last use of the looper, so they do not overlap, // and the last looper usage is not on the last line (where we could not append after it), and the + // helper is not used outside of the loop. // just move the looper definition to after the looper's last use stats.splice(lastLooperUsage+1, 0, stats[found]); stats.splice(found, 1); From 4ed0af723b235ae2504c65b6aaeb4b7c57fa435a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2014 11:14:43 -0700 Subject: [PATCH 11/24] add line bisector tool --- tools/bisect_pair_lines.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tools/bisect_pair_lines.py diff --git a/tools/bisect_pair_lines.py b/tools/bisect_pair_lines.py new file mode 100644 index 0000000000000..f698ef2a74756 --- /dev/null +++ b/tools/bisect_pair_lines.py @@ -0,0 +1,63 @@ +''' +Given two similar files, for example one with an additional optimization pass, +and with different results, will bisect between them to find the smallest +diff that makes the outputs different. +Unlike bisect_pairs, this uses lines instead of diffs. We replace line by line. This assumes +the programs differ on each line but lines have not been added or removed +''' + +import os, sys, shutil +from subprocess import Popen, PIPE, STDOUT + +__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +def path_from_root(*pathelems): + return os.path.join(__rootpath__, *pathelems) +exec(open(path_from_root('tools', 'shared.py'), 'r').read()) + +file1 = open(sys.argv[1]).read() +file2 = open(sys.argv[2]).read() + +leftf = open('left', 'w') +leftf.write(file1) +leftf.close() + +rightf = open('right', 'w') +rightf.write(file2) +rightf.close() + +def run_code(name): + ret = run_js(name, stderr=PIPE, full_output=True) + # fix stack traces + ret = filter(lambda line: not line.startswith(' at ') and not name in line, ret.split('\n')) + return '\n'.join(ret) + +print 'running files' +left_result = run_code('left') +right_result = run_code('right') # right as in left-right, not as in correct +assert left_result != right_result + +low = 0 +high = file1.count('\n') + +print 'beginning bisection, %d lines' % high + +left_lines = file1.split('\n') +right_lines = file2.split('\n') + +while True: + mid = int((low + high)/2) + print low, high, ' current: %d' % mid, + open('middle', 'w').write('\n'.join(left_lines[:mid] + right_lines[mid:])) + shutil.copyfile('middle', 'middle' + str(mid)) + result = run_code('middle') + print result == left_result, result == right_result#, 'XXX', left_result, 'YYY', result, 'ZZZ', right_result + if mid == low or mid == high: break + if result == right_result: + low = mid + elif result == left_result: + high = mid + else: + raise Exception('new result!?!?') + +print 'middle%d is like left, middle%d is like right' % (mid+1, mid) + From 4de2914fe17f3b3e169329ad35022e3ee124114a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2014 11:51:06 -0700 Subject: [PATCH 12/24] fix emitsBoolean handling of conditional, and add handling of num --- tools/js-optimizer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 0cd27aa87a316..d89145d5a7adc 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -750,9 +750,12 @@ function simplifyExpressions(ast) { } function emitsBoolean(node) { + if (node[0] === 'num') { + return node[1] === 0 || node[1] === 1; + } if (node[0] === 'binary') return node[1] in COMPARE_OPS; if (node[0] === 'unary-prefix') return node[1] === '!'; - if (node[0] === 'conditional') return true; + if (node[0] === 'conditional') return emitsBoolean(node[2]) && emitsBoolean(node[3]); return false; } From a89d3e95a760f8ed931a762a60d3103cb526d7dc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2014 11:51:53 -0700 Subject: [PATCH 13/24] add conditional to hasSideEffects --- tools/js-optimizer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d89145d5a7adc..088c4f0f3684f 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1033,6 +1033,7 @@ function hasSideEffects(node) { // this is 99% incomplete! } return false; } + case 'conditional': return hasSideEffects(node[1]) || hasSideEffects(node[2]) || hasSideEffects(node[3]); default: return true; } } From 5338d4b9c548e4949ef4ee01cc6085a8de3bbf45 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 19 May 2014 11:52:45 -0700 Subject: [PATCH 14/24] add testcases for last few commits --- tools/test-js-optimizer-asm-pre-output.js | 4 ++++ tools/test-js-optimizer-asm-pre.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index 31b9cfd70572c..5281b87c99f57 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -596,6 +596,10 @@ function conditionalizeMe() { if (x > 10 | HEAP[20] + 2 > 5) { b(); } + print(((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? 1 : $el) | $cheap > 0); + print(((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? 1 : -1) | $cheap > 0); + print($cheap > 0 ? 1 : (HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? 1 : 0); + print(((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? -1 : 1) | $cheap > 0); return (((((Math_imul(i6 + 1, i7) | 0) + 17 | 0) % 5 | 0) == 0 ? 1 : ((((Math_imul(i7 + 1, i7) | 0) + 11 | 0) >>> 0) % 3 | 0) == 0) | 0) == 0; } diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index 2a6ea4a9be883..d48d736e66f13 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -608,6 +608,10 @@ function conditionalizeMe() { if (x > 10 | (HEAP[20] + 2) > 5) { b(); } + print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? 1 : $el) | ($cheap > 0) ); // conditional does not always emit boolean + print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? 1 : -1) | ($cheap > 0) ); + print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? 1 : 0) | ($cheap > 0) ); // this one is safe! + print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? -1 : 1) | ($cheap > 0) ); return ((((Math_imul(i6+1, i7) | 0) + 17 | 0) % 5 | 0 | 0) == 0 | ((((Math_imul(i7+1, i7) | 0) + 11 | 0) >>> 0) % 3 | 0 | 0) == 0 | 0) == 0; } // EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp", "conditionalizeMe"] From f1be27928f95ada02836e0b955b03997be85a70a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 19 May 2014 01:31:34 +0300 Subject: [PATCH 15/24] Implement Web Audio API playback backend to SDL Mix_* interface. This fixes https://github.com/kripken/emscripten/commit/5b41371bb5897a49e1fdcad18d053f775105c846 when Web Audio API is available, and works around the Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=654787. --- src/library_sdl.js | 273 ++++++++++++++++++++++++------------------ tests/sdl_audio_mix.c | 2 + 2 files changed, 160 insertions(+), 115 deletions(-) diff --git a/src/library_sdl.js b/src/library_sdl.js index eabfe3e5eb817..d9639907b852e 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -943,11 +943,67 @@ var LibrarySDL = { var ret = info.volume * 128; // MIX_MAX_VOLUME if (volume != -1) { info.volume = volume / 128; - if (info.audio) info.audio.volume = info.volume; + if (info.audio) { + info.audio.volume = info.volume; // For