diff options
92 files changed, 3833 insertions, 1242 deletions
@@ -138,4 +138,8 @@ a license to everyone to use it as detailed in LICENSE.) * Guillaume Blanc <guillaumeblanc.sc@gmail.com> * Usagi Ito <usagi@WonderRabbitProject.net> * Camilo Polymeris <cpolymeris@gmail.com> +* Markus Henschel <markus.henschel@yager.de> +* Ophir Lojkine <ophir.lojkine@eleves.ec-nantes.fr> +* Ryan Sturgell <ryan.sturgell@gmail.com> (copyright owned by Google, Inc.) * Jason Green <jason@transgaming.com> (copyright owned by TransGaming, Inc.) + @@ -64,6 +64,7 @@ DYNAMICLIB_ENDINGS = ('.dylib', '.so', '.dll') STATICLIB_ENDINGS = ('.a',) ASSEMBLY_ENDINGS = ('.ll',) HEADER_ENDINGS = ('.h', '.hxx', '.hpp', '.hh', '.H', '.HXX', '.HPP', '.HH') +SUPPORTED_LINKER_FLAGS = ('--start-group', '-(', '--end-group', '-)') LIB_PREFIXES = ('', 'lib') @@ -1098,12 +1099,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 +1137,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 <target>, 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 +1152,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 +1170,20 @@ 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): + # Only keep flags that shared.Building.link knows how to deal with. + # We currently can't handle flags with options (like + # -Wl,-rpath,/bin:/lib, where /bin:/lib is an option for the -rpath + # flag). + if flag in SUPPORTED_LINKER_FLAGS: + link_flags.append((i + float(flag_index) / len(link_flags_to_add), flag)) newargs[i] = '' original_input_files = input_files[:] @@ -1173,7 +1199,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 +1209,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 +1225,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 +1237,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) @@ -1223,6 +1250,8 @@ try: value = '"@' + os.path.abspath(value[1:]) + '"' value = value.replace('\\\\', '/').replace('\\', '/') # Convert backslash paths to forward slashes on Windows as well, since the JS compiler otherwise needs the backslashes escaped (alternative is to escape all input paths passing to JS, which feels clumsier to read) exec('shared.Settings.' + key + ' = ' + value) + if key == 'EXPORTED_FUNCTIONS': + shared.Settings.ORIGINAL_EXPORTED_FUNCTIONS = shared.Settings.EXPORTED_FUNCTIONS[:] # used for warnings in emscripten.py fastcomp = os.environ.get('EMCC_FAST_COMPILER') != '0' @@ -1240,10 +1269,10 @@ try: assert shared.Settings.TARGET_ASMJS_UNKNOWN_EMSCRIPTEN == 1, 'fastcomp requires asmjs-unknown-emscripten' assert shared.Settings.USE_TYPED_ARRAYS == 2, 'fastcomp assumes ta2' assert not split_js_file, '--split-js is deprecated and not supported in fastcomp' - assert shared.Settings.MAX_SETJMPS == 20, 'changing MAX_SETJMPS is not supported in fastcomp yet' assert shared.Settings.INIT_HEAP == 0, 'HEAP_INIT is not supported in fastcomp (and should never be needed except for debugging)' assert not shared.Settings.RUNTIME_TYPE_INFO, 'RUNTIME_TYPE_INFO is not supported in fastcomp' assert not shared.Settings.CORRUPTION_CHECK, 'CORRUPTION_CHECK is not supported in asm.js mode, which is what fastcomp can emit (you can use non-asm.js mode in non-fastcomp)' + assert not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE, 'Linking modules is not supported in fastcomp' except Exception, e: logging.error('Compiler settings are incompatible with fastcomp. You can fall back to the older compiler core, although that is not recommended, see https://github.com/kripken/emscripten/wiki/LLVM-Backend') raise e @@ -1260,8 +1289,8 @@ try: fastcomp_opts += ['-pnacl-abi-simplify-preopt', '-pnacl-abi-simplify-postopt'] if shared.Settings.DISABLE_EXCEPTION_CATCHING != 1: fastcomp_opts += ['-enable-emscripten-cxx-exceptions'] - if len(shared.Settings.EXCEPTION_CATCHING_WHITELIST) > 0: - fastcomp_opts += ['-emscripten-cxx-exceptions-whitelist=' + ','.join(shared.Settings.EXCEPTION_CATCHING_WHITELIST)] + if shared.Settings.DISABLE_EXCEPTION_CATCHING == 2: + fastcomp_opts += ['-emscripten-cxx-exceptions-whitelist=' + ','.join(shared.Settings.EXCEPTION_CATCHING_WHITELIST or ['fake'])] if shared.Settings.ASM_JS: assert opt_level >= 1 or fastcomp, 'asm.js requires -O1 or above' @@ -1329,7 +1358,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,9 +1393,10 @@ 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)) @@ -1386,12 +1417,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 +1436,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 +1459,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 +1470,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 <target>), 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 +1506,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 +1514,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') @@ -1757,8 +1794,11 @@ try: if emit_symbol_map: js_optimizer_queue += ['symbolMap='+target+'.symbols'] if debug_level == 0: js_optimizer_queue += ['minifyWhitespace'] - if closure and shared.Settings.ASM_JS: - js_optimizer_queue += ['closure'] + if shared.Settings.ASM_JS: + if closure: + js_optimizer_queue += ['closure'] + elif debug_level <= 2 and not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE: + js_optimizer_queue += ['cleanup'] if not shared.Settings.SIDE_MODULE: js_optimizer_queue += ['last'] # side modules are not finalized until after relocation diff --git a/emscripten-version.txt b/emscripten-version.txt index a36c9a1e..ca3ca78e 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.18.0 +1.18.4 diff --git a/emscripten.py b/emscripten.py index e2aef648..98877a8e 100755 --- a/emscripten.py +++ b/emscripten.py @@ -726,7 +726,7 @@ Runtime.getTempRet0 = asm['getTempRet0']; def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, jcache=None, temp_files=None, DEBUG=None, DEBUG_CACHE=None): - """Runs the emscripten LLVM-to-JS compiler. We parallelize as much as possible + """Runs the emscripten LLVM-to-JS compiler. Args: infile: The path to the input LLVM assembly file. @@ -737,566 +737,583 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, assert(settings['ASM_JS']) - # Overview: - # * Run LLVM backend to emit JS. JS includes function bodies, memory initializer, - # and various metadata - # * Run compiler.js on the metadata to emit the shell js code, pre/post-ambles, - # JS library dependencies, etc. - - temp_js = temp_files.get('.4.js').name - backend_compiler = os.path.join(shared.LLVM_ROOT, 'llc') - backend_args = [backend_compiler, infile, '-march=js', '-filetype=asm', '-o', temp_js] - if settings['PRECISE_F32']: - backend_args += ['-emscripten-precise-f32'] - if settings['WARN_UNALIGNED']: - backend_args += ['-emscripten-warn-unaligned'] - if settings['RESERVED_FUNCTION_POINTERS'] > 0: - backend_args += ['-emscripten-reserved-function-pointers=%d' % settings['RESERVED_FUNCTION_POINTERS']] - if settings['ASSERTIONS'] > 0: - backend_args += ['-emscripten-assertions=%d' % settings['ASSERTIONS']] - if settings['ALIASING_FUNCTION_POINTERS'] == 0: - backend_args += ['-emscripten-no-aliasing-function-pointers'] - backend_args += ['-O' + str(settings['OPT_LEVEL'])] - if DEBUG: - logging.debug('emscript: llvm backend: ' + ' '.join(backend_args)) - t = time.time() - shared.jsrun.timeout_run(subprocess.Popen(backend_args, stdout=subprocess.PIPE)) - if DEBUG: - logging.debug(' emscript: llvm backend took %s seconds' % (time.time() - t)) - t = time.time() - - # Split up output - backend_output = open(temp_js).read() - #if DEBUG: print >> sys.stderr, backend_output - - start_funcs_marker = '// EMSCRIPTEN_START_FUNCTIONS' - end_funcs_marker = '// EMSCRIPTEN_END_FUNCTIONS' - metadata_split_marker = '// EMSCRIPTEN_METADATA' - - start_funcs = backend_output.index(start_funcs_marker) - end_funcs = backend_output.rindex(end_funcs_marker) - metadata_split = backend_output.rindex(metadata_split_marker) - - funcs = backend_output[start_funcs+len(start_funcs_marker):end_funcs] - metadata_raw = backend_output[metadata_split+len(metadata_split_marker):] - #if DEBUG: print >> sys.stderr, "METAraw", metadata_raw - metadata = json.loads(metadata_raw) - mem_init = backend_output[end_funcs+len(end_funcs_marker):metadata_split] - #if DEBUG: print >> sys.stderr, "FUNCS", funcs - #if DEBUG: print >> sys.stderr, "META", metadata - #if DEBUG: print >> sys.stderr, "meminit", mem_init - - # function table masks - - table_sizes = {} - for k, v in metadata['tables'].iteritems(): - table_sizes[k] = str(v.count(',')) # undercounts by one, but that is what we want - #if settings['ASSERTIONS'] >= 2 and table_sizes[k] == 0: - # print >> sys.stderr, 'warning: no function pointers with signature ' + k + ', but there is a call, which will abort if it occurs (this can result from undefined behavior, check for compiler warnings on your source files and consider -Werror)' - funcs = re.sub(r"#FM_(\w+)#", lambda m: table_sizes[m.groups(0)[0]], funcs) - - # fix +float into float.0, if not running js opts - if not settings['RUNNING_JS_OPTS']: - def fix_dot_zero(m): - num = m.group(3) - # TODO: handle 0x floats? - if num.find('.') < 0: - e = num.find('e'); - if e < 0: - num += '.0' - else: - num = num[:e] + '.0' + num[e:] - return m.group(1) + m.group(2) + num - funcs = re.sub(r'([(=,+\-*/%<>:?] *)\+(-?)((0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)', lambda m: fix_dot_zero(m), funcs) - - # js compiler - - if DEBUG: logging.debug('emscript: js compiler glue') - - # Settings changes - assert settings['TARGET_ASMJS_UNKNOWN_EMSCRIPTEN'] == 1 - settings['TARGET_ASMJS_UNKNOWN_EMSCRIPTEN'] = 2 - i64_funcs = ['i64Add', 'i64Subtract', '__muldi3', '__divdi3', '__udivdi3', '__remdi3', '__uremdi3'] - for i64_func in i64_funcs: - if i64_func in metadata['declares']: - settings['PRECISE_I64_MATH'] = 2 - break + success = False - metadata['declares'] = filter(lambda i64_func: i64_func not in ['getHigh32', 'setHigh32', '__muldi3', '__divdi3', '__remdi3', '__udivdi3', '__uremdi3'], metadata['declares']) # FIXME: do these one by one as normal js lib funcs - - # Integrate info from backend - settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] = list( - set(settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] + map(shared.JS.to_nice_ident, metadata['declares'])).difference( - map(lambda x: x[1:], metadata['implementedFunctions']) - ) - ) + map(lambda x: x[1:], metadata['externs']) - if metadata['simd']: - settings['SIMD'] = 1 - if not metadata['canValidate'] and settings['ASM_JS'] != 2: - logging.warning('disabling asm.js validation due to use of non-supported features') - settings['ASM_JS'] = 2 + try: - # Save settings to a file to work around v8 issue 1579 - settings_file = temp_files.get('.txt').name - def save_settings(): - global settings_text - settings_text = json.dumps(settings, sort_keys=True) - s = open(settings_file, 'w') - s.write(settings_text) - s.close() - save_settings() + # Overview: + # * Run LLVM backend to emit JS. JS includes function bodies, memory initializer, + # and various metadata + # * Run compiler.js on the metadata to emit the shell js code, pre/post-ambles, + # JS library dependencies, etc. + + temp_js = temp_files.get('.4.js').name + backend_compiler = os.path.join(shared.LLVM_ROOT, 'llc') + backend_args = [backend_compiler, infile, '-march=js', '-filetype=asm', '-o', temp_js] + if settings['PRECISE_F32']: + backend_args += ['-emscripten-precise-f32'] + if settings['WARN_UNALIGNED']: + backend_args += ['-emscripten-warn-unaligned'] + if settings['RESERVED_FUNCTION_POINTERS'] > 0: + backend_args += ['-emscripten-reserved-function-pointers=%d' % settings['RESERVED_FUNCTION_POINTERS']] + if settings['ASSERTIONS'] > 0: + backend_args += ['-emscripten-assertions=%d' % settings['ASSERTIONS']] + if settings['ALIASING_FUNCTION_POINTERS'] == 0: + backend_args += ['-emscripten-no-aliasing-function-pointers'] + backend_args += ['-O' + str(settings['OPT_LEVEL'])] + backend_args += ['-emscripten-max-setjmps=%d' % settings['MAX_SETJMPS']] + if DEBUG: + logging.debug('emscript: llvm backend: ' + ' '.join(backend_args)) + t = time.time() + shared.jsrun.timeout_run(subprocess.Popen(backend_args, stdout=subprocess.PIPE)) + if DEBUG: + logging.debug(' emscript: llvm backend took %s seconds' % (time.time() - t)) + t = time.time() + + # Split up output + backend_output = open(temp_js).read() + #if DEBUG: print >> sys.stderr, backend_output + + start_funcs_marker = '// EMSCRIPTEN_START_FUNCTIONS' + end_funcs_marker = '// EMSCRIPTEN_END_FUNCTIONS' + metadata_split_marker = '// EMSCRIPTEN_METADATA' + + start_funcs = backend_output.index(start_funcs_marker) + end_funcs = backend_output.rindex(end_funcs_marker) + metadata_split = backend_output.rindex(metadata_split_marker) + + funcs = backend_output[start_funcs+len(start_funcs_marker):end_funcs] + metadata_raw = backend_output[metadata_split+len(metadata_split_marker):] + #if DEBUG: print >> sys.stderr, "METAraw", metadata_raw + metadata = json.loads(metadata_raw) + mem_init = backend_output[end_funcs+len(end_funcs_marker):metadata_split] + #if DEBUG: print >> sys.stderr, "FUNCS", funcs + #if DEBUG: print >> sys.stderr, "META", metadata + #if DEBUG: print >> sys.stderr, "meminit", mem_init + + # function table masks + + table_sizes = {} + for k, v in metadata['tables'].iteritems(): + table_sizes[k] = str(v.count(',')) # undercounts by one, but that is what we want + #if settings['ASSERTIONS'] >= 2 and table_sizes[k] == 0: + # print >> sys.stderr, 'warning: no function pointers with signature ' + k + ', but there is a call, which will abort if it occurs (this can result from undefined behavior, check for compiler warnings on your source files and consider -Werror)' + funcs = re.sub(r"#FM_(\w+)#", lambda m: table_sizes[m.groups(0)[0]], funcs) + + # fix +float into float.0, if not running js opts + if not settings['RUNNING_JS_OPTS']: + def fix_dot_zero(m): + num = m.group(3) + # TODO: handle 0x floats? + if num.find('.') < 0: + e = num.find('e'); + if e < 0: + num += '.0' + else: + num = num[:e] + '.0' + num[e:] + return m.group(1) + m.group(2) + num + funcs = re.sub(r'([(=,+\-*/%<>:?] *)\+(-?)((0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)', lambda m: fix_dot_zero(m), funcs) - # Call js compiler - if DEBUG: t = time.time() - out = jsrun.run_js(path_from_root('src', 'compiler.js'), compiler_engine, [settings_file, ';', 'glue'] + libraries, stdout=subprocess.PIPE, stderr=STDERR_FILE, - cwd=path_from_root('src')) - assert '//FORWARDED_DATA:' in out, 'Did not receive forwarded data in pre output - process failed?' - glue, forwarded_data = out.split('//FORWARDED_DATA:') + # js compiler - if DEBUG: - logging.debug(' emscript: glue took %s seconds' % (time.time() - t)) - t = time.time() + if DEBUG: logging.debug('emscript: js compiler glue') - last_forwarded_json = forwarded_json = json.loads(forwarded_data) + # Settings changes + assert settings['TARGET_ASMJS_UNKNOWN_EMSCRIPTEN'] == 1 + settings['TARGET_ASMJS_UNKNOWN_EMSCRIPTEN'] = 2 + i64_funcs = ['i64Add', 'i64Subtract', '__muldi3', '__divdi3', '__udivdi3', '__remdi3', '__uremdi3'] + for i64_func in i64_funcs: + if i64_func in metadata['declares']: + settings['PRECISE_I64_MATH'] = 2 + break - # merge in information from llvm backend + metadata['declares'] = filter(lambda i64_func: i64_func not in ['getHigh32', 'setHigh32', '__muldi3', '__divdi3', '__remdi3', '__udivdi3', '__uremdi3'], metadata['declares']) # FIXME: do these one by one as normal js lib funcs - last_forwarded_json['Functions']['tables'] = metadata['tables'] + # Integrate info from backend + settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] = list( + set(settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE'] + map(shared.JS.to_nice_ident, metadata['declares'])).difference( + map(lambda x: x[1:], metadata['implementedFunctions']) + ) + ) + map(lambda x: x[1:], metadata['externs']) + if metadata['simd']: + settings['SIMD'] = 1 + if not metadata['canValidate'] and settings['ASM_JS'] != 2: + logging.warning('disabling asm.js validation due to use of non-supported features') + settings['ASM_JS'] = 2 + + # Save settings to a file to work around v8 issue 1579 + settings_file = temp_files.get('.txt').name + def save_settings(): + global settings_text + settings_text = json.dumps(settings, sort_keys=True) + s = open(settings_file, 'w') + s.write(settings_text) + s.close() + save_settings() - '''indexed_functions = set() - for key in forwarded_json['Functions']['indexedFunctions'].iterkeys(): - indexed_functions.add(key)''' + # Call js compiler + if DEBUG: t = time.time() + out = jsrun.run_js(path_from_root('src', 'compiler.js'), compiler_engine, [settings_file, ';', 'glue'] + libraries, stdout=subprocess.PIPE, stderr=STDERR_FILE, + cwd=path_from_root('src')) + assert '//FORWARDED_DATA:' in out, 'Did not receive forwarded data in pre output - process failed?' + glue, forwarded_data = out.split('//FORWARDED_DATA:') - pre, post = glue.split('// EMSCRIPTEN_END_FUNCS') + if DEBUG: + logging.debug(' emscript: glue took %s seconds' % (time.time() - t)) + t = time.time() - #print >> sys.stderr, 'glue:', pre, '\n\n||||||||||||||||\n\n', post, '...............' + last_forwarded_json = forwarded_json = json.loads(forwarded_data) - # memory and global initializers + # merge in information from llvm backend - global_initializers = ', '.join(map(lambda i: '{ func: function() { %s() } }' % i, metadata['initializers'])) + last_forwarded_json['Functions']['tables'] = metadata['tables'] - pre = pre.replace('STATICTOP = STATIC_BASE + 0;', '''STATICTOP = STATIC_BASE + Runtime.alignMemory(%d); -/* global initializers */ __ATINIT__.push(%s); -%s''' % (mem_init.count(',')+1, global_initializers, mem_init)) # XXX wrong size calculation! + '''indexed_functions = set() + for key in forwarded_json['Functions']['indexedFunctions'].iterkeys(): + indexed_functions.add(key)''' - funcs_js = [funcs] - if settings.get('ASM_JS'): - parts = pre.split('// ASM_LIBRARY FUNCTIONS\n') - if len(parts) > 1: - pre = parts[0] - funcs_js.append(parts[1]) + pre, post = glue.split('// EMSCRIPTEN_END_FUNCS') - # merge forwarded data - assert settings.get('ASM_JS'), 'fastcomp is asm.js only' - settings['EXPORTED_FUNCTIONS'] = forwarded_json['EXPORTED_FUNCTIONS'] - all_exported_functions = set(settings['EXPORTED_FUNCTIONS']) # both asm.js and otherwise - for additional_export in settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE']: # additional functions to export from asm, if they are implemented - all_exported_functions.add('_' + additional_export) - exported_implemented_functions = set(metadata['exports']) - export_bindings = settings['EXPORT_BINDINGS'] - export_all = settings['EXPORT_ALL'] - for key in metadata['implementedFunctions'] + forwarded_json['Functions']['implementedFunctions'].keys(): # XXX perf - if key in all_exported_functions or export_all or (export_bindings and key.startswith('_emscripten_bind')): - exported_implemented_functions.add(key) - implemented_functions = set(metadata['implementedFunctions']) - - # Add named globals - named_globals = '\n'.join(['var %s = %s;' % (k, v) for k, v in metadata['namedGlobals'].iteritems()]) - pre = pre.replace('// === Body ===', '// === Body ===\n' + named_globals + '\n') + #print >> sys.stderr, 'glue:', pre, '\n\n||||||||||||||||\n\n', post, '...............' - #if DEBUG: outfile.write('// pre\n') - outfile.write(pre) - pre = None + # memory and global initializers - #if DEBUG: outfile.write('// funcs\n') + global_initializers = ', '.join(map(lambda i: '{ func: function() { %s() } }' % i, metadata['initializers'])) - if settings.get('ASM_JS'): - # Move preAsms to their right place - def move_preasm(m): - contents = m.groups(0)[0] - outfile.write(contents + '\n') - return '' - funcs_js[1] = re.sub(r'/\* PRE_ASM \*/(.*)\n', lambda m: move_preasm(m), funcs_js[1]) + pre = pre.replace('STATICTOP = STATIC_BASE + 0;', '''STATICTOP = STATIC_BASE + Runtime.alignMemory(%d); + /* global initializers */ __ATINIT__.push(%s); + %s''' % (mem_init.count(',')+1, global_initializers, mem_init)) # XXX wrong size calculation! - funcs_js += ['\n// EMSCRIPTEN_END_FUNCS\n'] + funcs_js = [funcs] + if settings.get('ASM_JS'): + parts = pre.split('// ASM_LIBRARY FUNCTIONS\n') + if len(parts) > 1: + pre = parts[0] + funcs_js.append(parts[1]) - simple = os.environ.get('EMCC_SIMPLE_ASM') - class Counter: - i = 0 - j = 0 - if 'pre' in last_forwarded_json['Functions']['tables']: - pre_tables = last_forwarded_json['Functions']['tables']['pre'] - del last_forwarded_json['Functions']['tables']['pre'] - else: - pre_tables = '' + # merge forwarded data + assert settings.get('ASM_JS'), 'fastcomp is asm.js only' + settings['EXPORTED_FUNCTIONS'] = forwarded_json['EXPORTED_FUNCTIONS'] + all_exported_functions = set(settings['EXPORTED_FUNCTIONS']) # both asm.js and otherwise + for additional_export in settings['DEFAULT_LIBRARY_FUNCS_TO_INCLUDE']: # additional functions to export from asm, if they are implemented + all_exported_functions.add('_' + additional_export) + exported_implemented_functions = set(metadata['exports']) + export_bindings = settings['EXPORT_BINDINGS'] + export_all = settings['EXPORT_ALL'] + all_implemented = metadata['implementedFunctions'] + forwarded_json['Functions']['implementedFunctions'].keys() # XXX perf? + for key in all_implemented: + if key in all_exported_functions or export_all or (export_bindings and key.startswith('_emscripten_bind')): + exported_implemented_functions.add(key) + implemented_functions = set(metadata['implementedFunctions']) + if settings['ASSERTIONS'] and settings.get('ORIGINAL_EXPORTED_FUNCTIONS'): + for requested in settings['ORIGINAL_EXPORTED_FUNCTIONS']: + if requested not in all_implemented: + logging.warning('function requested to be exported, but not implemented: "%s"', requested) + + # Add named globals + named_globals = '\n'.join(['var %s = %s;' % (k, v) for k, v in metadata['namedGlobals'].iteritems()]) + pre = pre.replace('// === Body ===', '// === Body ===\n' + named_globals + '\n') + + #if DEBUG: outfile.write('// pre\n') + outfile.write(pre) + pre = None + + #if DEBUG: outfile.write('// funcs\n') - def unfloat(s): - return 'd' if s == 'f' else s # lower float to double for ffis + if settings.get('ASM_JS'): + # Move preAsms to their right place + def move_preasm(m): + contents = m.groups(0)[0] + outfile.write(contents + '\n') + return '' + funcs_js[1] = re.sub(r'/\* PRE_ASM \*/(.*)\n', lambda m: move_preasm(m), funcs_js[1]) + + funcs_js += ['\n// EMSCRIPTEN_END_FUNCS\n'] + + simple = os.environ.get('EMCC_SIMPLE_ASM') + class Counter: + i = 0 + j = 0 + if 'pre' in last_forwarded_json['Functions']['tables']: + pre_tables = last_forwarded_json['Functions']['tables']['pre'] + del last_forwarded_json['Functions']['tables']['pre'] + else: + pre_tables = '' - if settings['ASSERTIONS'] >= 2: - debug_tables = {} + def unfloat(s): + return 'd' if s == 'f' else s # lower float to double for ffis - def make_table(sig, raw): - params = ','.join(['p%d' % p for p in range(len(sig)-1)]) - coerced_params = ','.join([shared.JS.make_coercion('p%d', unfloat(sig[p+1]), settings) % p for p in range(len(sig)-1)]) - coercions = ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' - def make_func(name, code): - return 'function %s(%s) { %s %s }' % (name, params, coercions, code) - def make_bad(target=None): - i = Counter.i - Counter.i += 1 - if target is None: target = i - name = 'b' + str(i) - if not settings['ASSERTIONS']: - code = 'abort(%s);' % target - else: - code = 'nullFunc_' + sig + '(%d);' % target - if sig[0] != 'v': - code += 'return %s' % shared.JS.make_initializer(sig[0], settings) + ';' - return name, make_func(name, code) - bad, bad_func = make_bad() # the default bad func - if settings['ASSERTIONS'] <= 1: - Counter.pre = [bad_func] - else: - Counter.pre = [] - start = raw.index('[') - end = raw.rindex(']') - body = raw[start+1:end].split(',') - for j in range(settings['RESERVED_FUNCTION_POINTERS']): - curr = 'jsCall_%s_%s' % (sig, j) - body[settings['FUNCTION_POINTER_ALIGNMENT'] * (1 + j)] = curr - implemented_functions.add(curr) - Counter.j = 0 - def fix_item(item): - Counter.j += 1 - newline = Counter.j % 30 == 29 - if item == '0': - if settings['ASSERTIONS'] <= 1: - return bad if not newline else (bad + '\n') + if settings['ASSERTIONS'] >= 2: + debug_tables = {} + + def make_table(sig, raw): + params = ','.join(['p%d' % p for p in range(len(sig)-1)]) + coerced_params = ','.join([shared.JS.make_coercion('p%d', unfloat(sig[p+1]), settings) % p for p in range(len(sig)-1)]) + coercions = ';'.join(['p%d = %s' % (p, shared.JS.make_coercion('p%d' % p, sig[p+1], settings)) for p in range(len(sig)-1)]) + ';' + def make_func(name, code): + return 'function %s(%s) { %s %s }' % (name, params, coercions, code) + def make_bad(target=None): + i = Counter.i + Counter.i += 1 + if target is None: target = i + name = 'b' + str(i) + if not settings['ASSERTIONS']: + code = 'abort(%s);' % target else: - specific_bad, specific_bad_func = make_bad(Counter.j-1) - Counter.pre.append(specific_bad_func) - return specific_bad if not newline else (specific_bad + '\n') - if item not in implemented_functions: - # this is imported into asm, we must wrap it - call_ident = item - if call_ident in metadata['redirects']: call_ident = metadata['redirects'][call_ident] - if not call_ident.startswith('_') and not call_ident.startswith('Math_'): call_ident = '_' + call_ident - code = call_ident + '(' + coerced_params + ')' + code = 'nullFunc_' + sig + '(%d);' % target if sig[0] != 'v': - # ffis cannot return float - if sig[0] == 'f': code = '+' + code - code = 'return ' + shared.JS.make_coercion(code, sig[0], settings) - code += ';' - Counter.pre.append(make_func(item + '__wrapper', code)) - return item + '__wrapper' - return item if not newline else (item + '\n') - if settings['ASSERTIONS'] >= 2: - debug_tables[sig] = body - body = ','.join(map(fix_item, body)) - return ('\n'.join(Counter.pre), ''.join([raw[:start+1], body, raw[end:]])) - - infos = [make_table(sig, raw) for sig, raw in last_forwarded_json['Functions']['tables'].iteritems()] - Counter.pre = [] - - function_tables_defs = '\n'.join([info[0] for info in infos]) + '\n// EMSCRIPTEN_END_FUNCS\n' + '\n'.join([info[1] for info in infos]) - - asm_setup = '' - maths = ['Math.' + func for func in ['floor', 'abs', 'sqrt', 'pow', 'cos', 'sin', 'tan', 'acos', 'asin', 'atan', 'atan2', 'exp', 'log', 'ceil', 'imul']] - fundamentals = ['Math', 'Int8Array', 'Int16Array', 'Int32Array', 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Float32Array', 'Float64Array'] - math_envs = ['Math.min'] # TODO: move min to maths - asm_setup += '\n'.join(['var %s = %s;' % (f.replace('.', '_'), f) for f in math_envs]) - - if settings['PRECISE_F32']: maths += ['Math.fround'] - - basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] - if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') - if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_FT_MASK'] - if settings['CHECK_HEAP_ALIGN']: basic_funcs += ['CHECK_ALIGN_2', 'CHECK_ALIGN_4', 'CHECK_ALIGN_8'] - if settings['ASSERTIONS']: - if settings['ASSERTIONS'] >= 2: import difflib - for sig in last_forwarded_json['Functions']['tables'].iterkeys(): - basic_funcs += ['nullFunc_' + sig] + code += 'return %s' % shared.JS.make_initializer(sig[0], settings) + ';' + return name, make_func(name, code) + bad, bad_func = make_bad() # the default bad func if settings['ASSERTIONS'] <= 1: - extra = ' Module["printErr"]("Build with ASSERTIONS=2 for more info.");' - pointer = ' ' + Counter.pre = [bad_func] else: - pointer = ' \'" + x + "\' ' - asm_setup += '\nvar debug_table_' + sig + ' = ' + json.dumps(debug_tables[sig]) + ';' - extra = ' Module["printErr"]("This pointer might make sense in another type signature: ' - # sort signatures, attempting to show most likely related ones first - sigs = last_forwarded_json['Functions']['tables'].keys() - def keyfunc(other): - ret = 0 - minlen = min(len(other), len(sig)) - maxlen = min(len(other), len(sig)) - if other.startswith(sig) or sig.startswith(other): ret -= 1000 # prioritize prefixes, could be dropped params - ret -= 133*difflib.SequenceMatcher(a=other, b=sig).ratio() # prioritize on diff similarity - ret += 15*abs(len(other) - len(sig))/float(maxlen) # deprioritize the bigger the length difference is - for i in range(minlen): - if other[i] == sig[i]: ret -= 5/float(maxlen) # prioritize on identically-placed params - ret += 20*len(other) # deprioritize on length - return ret - sigs.sort(key=keyfunc) - for other in sigs: - if other != sig: - extra += other + ': " + debug_table_' + other + '[x] + " ' - extra += '"); ' - asm_setup += '\nfunction nullFunc_' + sig + '(x) { Module["printErr"]("Invalid function pointer' + pointer + 'called with signature \'' + sig + '\'. ' + \ - 'Perhaps this is an invalid value (e.g. caused by calling a virtual method on a NULL pointer)? ' + \ - 'Or calling a function with an incorrect type, which will fail? ' + \ - '(it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this)' + \ - '"); ' + extra + ' abort(x) }\n' + Counter.pre = [] + start = raw.index('[') + end = raw.rindex(']') + body = raw[start+1:end].split(',') + for j in range(settings['RESERVED_FUNCTION_POINTERS']): + curr = 'jsCall_%s_%s' % (sig, j) + body[settings['FUNCTION_POINTER_ALIGNMENT'] * (1 + j)] = curr + implemented_functions.add(curr) + Counter.j = 0 + def fix_item(item): + Counter.j += 1 + newline = Counter.j % 30 == 29 + if item == '0': + if settings['ASSERTIONS'] <= 1: + return bad if not newline else (bad + '\n') + else: + specific_bad, specific_bad_func = make_bad(Counter.j-1) + Counter.pre.append(specific_bad_func) + return specific_bad if not newline else (specific_bad + '\n') + if item not in implemented_functions: + # this is imported into asm, we must wrap it + call_ident = item + if call_ident in metadata['redirects']: call_ident = metadata['redirects'][call_ident] + if not call_ident.startswith('_') and not call_ident.startswith('Math_'): call_ident = '_' + call_ident + code = call_ident + '(' + coerced_params + ')' + if sig[0] != 'v': + # ffis cannot return float + if sig[0] == 'f': code = '+' + code + code = 'return ' + shared.JS.make_coercion(code, sig[0], settings) + code += ';' + Counter.pre.append(make_func(item + '__wrapper', code)) + return item + '__wrapper' + return item if not newline else (item + '\n') + if settings['ASSERTIONS'] >= 2: + debug_tables[sig] = body + body = ','.join(map(fix_item, body)) + return ('\n'.join(Counter.pre), ''.join([raw[:start+1], body, raw[end:]])) + + infos = [make_table(sig, raw) for sig, raw in last_forwarded_json['Functions']['tables'].iteritems()] + Counter.pre = [] + + function_tables_defs = '\n'.join([info[0] for info in infos]) + '\n// EMSCRIPTEN_END_FUNCS\n' + '\n'.join([info[1] for info in infos]) + + asm_setup = '' + maths = ['Math.' + func for func in ['floor', 'abs', 'sqrt', 'pow', 'cos', 'sin', 'tan', 'acos', 'asin', 'atan', 'atan2', 'exp', 'log', 'ceil', 'imul']] + fundamentals = ['Math', 'Int8Array', 'Int16Array', 'Int32Array', 'Uint8Array', 'Uint16Array', 'Uint32Array', 'Float32Array', 'Float64Array'] + math_envs = ['Math.min'] # TODO: move min to maths + asm_setup += '\n'.join(['var %s = %s;' % (f.replace('.', '_'), f) for f in math_envs]) + + if settings['PRECISE_F32']: maths += ['Math.fround'] + + basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] + if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') + if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_FT_MASK'] + if settings['CHECK_HEAP_ALIGN']: basic_funcs += ['CHECK_ALIGN_2', 'CHECK_ALIGN_4', 'CHECK_ALIGN_8'] + if settings['ASSERTIONS']: + if settings['ASSERTIONS'] >= 2: import difflib + for sig in last_forwarded_json['Functions']['tables'].iterkeys(): + basic_funcs += ['nullFunc_' + sig] + if settings['ASSERTIONS'] <= 1: + extra = ' Module["printErr"]("Build with ASSERTIONS=2 for more info.");' + pointer = ' ' + else: + pointer = ' \'" + x + "\' ' + asm_setup += '\nvar debug_table_' + sig + ' = ' + json.dumps(debug_tables[sig]) + ';' + extra = ' Module["printErr"]("This pointer might make sense in another type signature: ' + # sort signatures, attempting to show most likely related ones first + sigs = last_forwarded_json['Functions']['tables'].keys() + def keyfunc(other): + ret = 0 + minlen = min(len(other), len(sig)) + maxlen = min(len(other), len(sig)) + if other.startswith(sig) or sig.startswith(other): ret -= 1000 # prioritize prefixes, could be dropped params + ret -= 133*difflib.SequenceMatcher(a=other, b=sig).ratio() # prioritize on diff similarity + ret += 15*abs(len(other) - len(sig))/float(maxlen) # deprioritize the bigger the length difference is + for i in range(minlen): + if other[i] == sig[i]: ret -= 5/float(maxlen) # prioritize on identically-placed params + ret += 20*len(other) # deprioritize on length + return ret + sigs.sort(key=keyfunc) + for other in sigs: + if other != sig: + extra += other + ': " + debug_table_' + other + '[x] + " ' + extra += '"); ' + asm_setup += '\nfunction nullFunc_' + sig + '(x) { Module["printErr"]("Invalid function pointer' + pointer + 'called with signature \'' + sig + '\'. ' + \ + 'Perhaps this is an invalid value (e.g. caused by calling a virtual method on a NULL pointer)? ' + \ + 'Or calling a function with an incorrect type, which will fail? ' + \ + '(it is worth building your source files with -Werror (warnings are errors), as warnings can indicate undefined behavior which can cause this)' + \ + '"); ' + extra + ' abort(x) }\n' + + basic_vars = ['STACKTOP', 'STACK_MAX', 'tempDoublePtr', 'ABORT'] + basic_float_vars = ['NaN', 'Infinity'] + + if metadata.get('preciseI64MathUsed') or \ + forwarded_json['Functions']['libraryFunctions'].get('llvm_cttz_i32') or \ + forwarded_json['Functions']['libraryFunctions'].get('llvm_ctlz_i32'): + basic_vars += ['cttz_i8', 'ctlz_i8'] - basic_vars = ['STACKTOP', 'STACK_MAX', 'tempDoublePtr', 'ABORT'] - basic_float_vars = ['NaN', 'Infinity'] + if settings.get('DLOPEN_SUPPORT'): + for sig in last_forwarded_json['Functions']['tables'].iterkeys(): + basic_vars.append('F_BASE_%s' % sig) + asm_setup += ' var F_BASE_%s = %s;\n' % (sig, 'FUNCTION_TABLE_OFFSET' if settings.get('SIDE_MODULE') else '0') + '\n' - if metadata.get('preciseI64MathUsed') or \ - forwarded_json['Functions']['libraryFunctions'].get('llvm_cttz_i32') or \ - forwarded_json['Functions']['libraryFunctions'].get('llvm_ctlz_i32'): - basic_vars += ['cttz_i8', 'ctlz_i8'] + if '_rand' in exported_implemented_functions or '_srand' in exported_implemented_functions: + basic_vars += ['___rand_seed'] + + asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew', 'setTempRet0', 'getTempRet0'] + # function tables + function_tables = ['dynCall_' + table for table in last_forwarded_json['Functions']['tables']] + function_tables_impls = [] - if settings.get('DLOPEN_SUPPORT'): for sig in last_forwarded_json['Functions']['tables'].iterkeys(): - basic_vars.append('F_BASE_%s' % sig) - asm_setup += ' var F_BASE_%s = %s;\n' % (sig, 'FUNCTION_TABLE_OFFSET' if settings.get('SIDE_MODULE') else '0') + '\n' + args = ','.join(['a' + str(i) for i in range(1, len(sig))]) + arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i], settings) + ';' for i in range(1, len(sig))]) + coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings) for i in range(1, len(sig))]) + ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0], settings) + function_tables_impls.append(''' + function dynCall_%s(index%s%s) { + index = index|0; + %s + %s; + } + ''' % (sig, ',' if len(sig) > 1 else '', args, arg_coercions, ret)) + + ffi_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings, ffi_arg=True) for i in range(1, len(sig))]) + for i in range(settings['RESERVED_FUNCTION_POINTERS']): + jsret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('jsCall(%d%s%s)' % (i, ',' if ffi_args else '', ffi_args), sig[0], settings, ffi_result=True) + function_tables_impls.append(''' + function jsCall_%s_%s(%s) { + %s + %s; + } + + ''' % (sig, i, args, arg_coercions, jsret)) + shared.Settings.copy(settings) + asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' + basic_funcs.append('invoke_%s' % sig) + if settings.get('DLOPEN_SUPPORT'): + asm_setup += '\n' + shared.JS.make_extcall(sig) + '\n' + basic_funcs.append('extCall_%s' % sig) + + # calculate exports + exported_implemented_functions = list(exported_implemented_functions) + metadata['initializers'] + exported_implemented_functions.append('runPostSets') + exports = [] + if not simple: + for export in exported_implemented_functions + asm_runtime_funcs + function_tables: + exports.append("%s: %s" % (export, export)) + exports = '{ ' + ', '.join(exports) + ' }' + else: + exports = '_main' + # calculate globals + try: + del forwarded_json['Variables']['globals']['_llvm_global_ctors'] # not a true variable + except: + pass + # If no named globals, only need externals + global_vars = metadata['externs'] #+ forwarded_json['Variables']['globals'] + global_funcs = list(set(['_' + key for key, value in forwarded_json['Functions']['libraryFunctions'].iteritems() if value != 2]).difference(set(global_vars)).difference(implemented_functions)) + def math_fix(g): + return g if not g.startswith('Math_') else g.split('_')[1] + asm_global_funcs = ''.join([' var ' + g.replace('.', '_') + '=global.' + g + ';\n' for g in maths]) + \ + ''.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]) + # 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_ASMJS_UNKNOWN_EMSCRIPTEN'), '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]) + ' }' + # received + if not simple: + receiving = ';\n'.join(['var ' + s + ' = Module["' + s + '"] = asm["' + s + '"]' for s in exported_implemented_functions + function_tables]) + else: + receiving = 'var _main = Module["_main"] = asm;' - if '_rand' in exported_implemented_functions or '_srand' in exported_implemented_functions: - basic_vars += ['___rand_seed'] + # finalize - asm_runtime_funcs = ['stackAlloc', 'stackSave', 'stackRestore', 'setThrew', 'setTempRet0', 'getTempRet0'] - # function tables - function_tables = ['dynCall_' + table for table in last_forwarded_json['Functions']['tables']] - function_tables_impls = [] + if DEBUG: logging.debug('asm text sizes' + str([map(len, funcs_js), len(asm_setup), len(asm_global_vars), len(asm_global_funcs), len(pre_tables), len('\n'.join(function_tables_impls)), len(function_tables_defs.replace('\n', '\n ')), len(exports), len(the_global), len(sending), len(receiving)])) - for sig in last_forwarded_json['Functions']['tables'].iterkeys(): - args = ','.join(['a' + str(i) for i in range(1, len(sig))]) - arg_coercions = ' '.join(['a' + str(i) + '=' + shared.JS.make_coercion('a' + str(i), sig[i], settings) + ';' for i in range(1, len(sig))]) - coerced_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings) for i in range(1, len(sig))]) - ret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('FUNCTION_TABLE_%s[index&{{{ FTM_%s }}}](%s)' % (sig, sig, coerced_args), sig[0], settings) - function_tables_impls.append(''' - function dynCall_%s(index%s%s) { - index = index|0; - %s - %s; + funcs_js = [''' + %s + function asmPrintInt(x, y) { + Module.print('int ' + x + ',' + y);// + ' ' + new Error().stack); } -''' % (sig, ',' if len(sig) > 1 else '', args, arg_coercions, ret)) - - ffi_args = ','.join([shared.JS.make_coercion('a' + str(i), sig[i], settings, ffi_arg=True) for i in range(1, len(sig))]) - for i in range(settings['RESERVED_FUNCTION_POINTERS']): - jsret = ('return ' if sig[0] != 'v' else '') + shared.JS.make_coercion('jsCall(%d%s%s)' % (i, ',' if ffi_args else '', ffi_args), sig[0], settings, ffi_result=True) - function_tables_impls.append(''' - function jsCall_%s_%s(%s) { + function asmPrintFloat(x, y) { + Module.print('float ' + x + ',' + y);// + ' ' + new Error().stack); + } + // EMSCRIPTEN_START_ASM + var asm = (function(global, env, buffer) { %s - %s; + var HEAP8 = new global.Int8Array(buffer); + var HEAP16 = new global.Int16Array(buffer); + var HEAP32 = new global.Int32Array(buffer); + var HEAPU8 = new global.Uint8Array(buffer); + var HEAPU16 = new global.Uint16Array(buffer); + var HEAPU32 = new global.Uint32Array(buffer); + var HEAPF32 = new global.Float32Array(buffer); + var HEAPF64 = new global.Float64Array(buffer); + ''' % (asm_setup, "'use asm';" if not metadata.get('hasInlineJS') and not settings['SIDE_MODULE'] and settings['ASM_JS'] == 1 else "'almost asm';") + '\n' + asm_global_vars + ''' + var __THREW__ = 0; + var threwValue = 0; + var setjmpId = 0; + var undef = 0; + var nan = +env.NaN, inf = +env.Infinity; + var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; + ''' + ''.join([''' + var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('PRECISE_F32') else '0.0')] + ([' const f0 = Math_fround(0);\n'] if settings.get('PRECISE_F32') else []) + [''' + // EMSCRIPTEN_START_FUNCS + function stackAlloc(size) { + size = size|0; + var ret = 0; + ret = STACKTOP; + STACKTOP = (STACKTOP + size)|0; + ''' + ('STACKTOP = (STACKTOP + 3)&-4;' if settings['TARGET_X86'] else 'STACKTOP = (STACKTOP + 7)&-8;') + ''' + return ret|0; } - -''' % (sig, i, args, arg_coercions, jsret)) - shared.Settings.copy(settings) - asm_setup += '\n' + shared.JS.make_invoke(sig) + '\n' - basic_funcs.append('invoke_%s' % sig) - if settings.get('DLOPEN_SUPPORT'): - asm_setup += '\n' + shared.JS.make_extcall(sig) + '\n' - basic_funcs.append('extCall_%s' % sig) - - # calculate exports - exported_implemented_functions = list(exported_implemented_functions) + metadata['initializers'] - exported_implemented_functions.append('runPostSets') - exports = [] - if not simple: - for export in exported_implemented_functions + asm_runtime_funcs + function_tables: - exports.append("%s: %s" % (export, export)) - exports = '{ ' + ', '.join(exports) + ' }' - else: - exports = '_main' - # calculate globals - try: - del forwarded_json['Variables']['globals']['_llvm_global_ctors'] # not a true variable - except: - pass - # If no named globals, only need externals - global_vars = metadata['externs'] #+ forwarded_json['Variables']['globals'] - global_funcs = list(set(['_' + key for key, value in forwarded_json['Functions']['libraryFunctions'].iteritems() if value != 2]).difference(set(global_vars)).difference(implemented_functions)) - def math_fix(g): - return g if not g.startswith('Math_') else g.split('_')[1] - asm_global_funcs = ''.join([' var ' + g.replace('.', '_') + '=global.' + g + ';\n' for g in maths]) + \ - ''.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]) - # 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_ASMJS_UNKNOWN_EMSCRIPTEN'), '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]) + ' }' - # received - if not simple: - receiving = ';\n'.join(['var ' + s + ' = Module["' + s + '"] = asm["' + s + '"]' for s in exported_implemented_functions + function_tables]) - else: - receiving = 'var _main = Module["_main"] = asm;' - - # finalize - - if DEBUG: logging.debug('asm text sizes' + str([map(len, funcs_js), len(asm_setup), len(asm_global_vars), len(asm_global_funcs), len(pre_tables), len('\n'.join(function_tables_impls)), len(function_tables_defs.replace('\n', '\n ')), len(exports), len(the_global), len(sending), len(receiving)])) - - funcs_js = [''' -%s -function asmPrintInt(x, y) { - Module.print('int ' + x + ',' + y);// + ' ' + new Error().stack); -} -function asmPrintFloat(x, y) { - Module.print('float ' + x + ',' + y);// + ' ' + new Error().stack); -} -// EMSCRIPTEN_START_ASM -var asm = (function(global, env, buffer) { - %s - var HEAP8 = new global.Int8Array(buffer); - var HEAP16 = new global.Int16Array(buffer); - var HEAP32 = new global.Int32Array(buffer); - var HEAPU8 = new global.Uint8Array(buffer); - var HEAPU16 = new global.Uint16Array(buffer); - var HEAPU32 = new global.Uint32Array(buffer); - var HEAPF32 = new global.Float32Array(buffer); - var HEAPF64 = new global.Float64Array(buffer); -''' % (asm_setup, "'use asm';" if not metadata.get('hasInlineJS') and not settings['SIDE_MODULE'] and settings['ASM_JS'] == 1 else "'almost asm';") + '\n' + asm_global_vars + ''' - var __THREW__ = 0; - var threwValue = 0; - var setjmpId = 0; - var undef = 0; - var nan = +env.NaN, inf = +env.Infinity; - var tempInt = 0, tempBigInt = 0, tempBigIntP = 0, tempBigIntS = 0, tempBigIntR = 0.0, tempBigIntI = 0, tempBigIntD = 0, tempValue = 0, tempDouble = 0.0; -''' + ''.join([''' - var tempRet%d = 0;''' % i for i in range(10)]) + '\n' + asm_global_funcs] + [' var tempFloat = %s;\n' % ('Math_fround(0)' if settings.get('PRECISE_F32') else '0.0')] + ([' const f0 = Math_fround(0);\n'] if settings.get('PRECISE_F32') else []) + [''' -// EMSCRIPTEN_START_FUNCS -function stackAlloc(size) { - size = size|0; - var ret = 0; - ret = STACKTOP; - STACKTOP = (STACKTOP + size)|0; -''' + ('STACKTOP = (STACKTOP + 3)&-4;' if settings['TARGET_X86'] else 'STACKTOP = (STACKTOP + 7)&-8;') + ''' - return ret|0; -} -function stackSave() { - return STACKTOP|0; -} -function stackRestore(top) { - top = top|0; - STACKTOP = top; -} -function setThrew(threw, value) { - threw = threw|0; - value = value|0; - if ((__THREW__|0) == 0) { - __THREW__ = threw; - threwValue = value; + function stackSave() { + return STACKTOP|0; } -} -function copyTempFloat(ptr) { - ptr = ptr|0; - HEAP8[tempDoublePtr>>0] = HEAP8[ptr>>0]; - HEAP8[tempDoublePtr+1>>0] = HEAP8[ptr+1>>0]; - HEAP8[tempDoublePtr+2>>0] = HEAP8[ptr+2>>0]; - HEAP8[tempDoublePtr+3>>0] = HEAP8[ptr+3>>0]; -} -function copyTempDouble(ptr) { - ptr = ptr|0; - HEAP8[tempDoublePtr>>0] = HEAP8[ptr>>0]; - HEAP8[tempDoublePtr+1>>0] = HEAP8[ptr+1>>0]; - HEAP8[tempDoublePtr+2>>0] = HEAP8[ptr+2>>0]; - HEAP8[tempDoublePtr+3>>0] = HEAP8[ptr+3>>0]; - HEAP8[tempDoublePtr+4>>0] = HEAP8[ptr+4>>0]; - HEAP8[tempDoublePtr+5>>0] = HEAP8[ptr+5>>0]; - HEAP8[tempDoublePtr+6>>0] = HEAP8[ptr+6>>0]; - HEAP8[tempDoublePtr+7>>0] = HEAP8[ptr+7>>0]; -} -function setTempRet0(value) { - value = value|0; - tempRet0 = value; -} -function getTempRet0() { - return tempRet0|0; -} -'''] + funcs_js + [''' - %s + function stackRestore(top) { + top = top|0; + STACKTOP = top; + } + function setThrew(threw, value) { + threw = threw|0; + value = value|0; + if ((__THREW__|0) == 0) { + __THREW__ = threw; + threwValue = value; + } + } + function copyTempFloat(ptr) { + ptr = ptr|0; + HEAP8[tempDoublePtr>>0] = HEAP8[ptr>>0]; + HEAP8[tempDoublePtr+1>>0] = HEAP8[ptr+1>>0]; + HEAP8[tempDoublePtr+2>>0] = HEAP8[ptr+2>>0]; + HEAP8[tempDoublePtr+3>>0] = HEAP8[ptr+3>>0]; + } + function copyTempDouble(ptr) { + ptr = ptr|0; + HEAP8[tempDoublePtr>>0] = HEAP8[ptr>>0]; + HEAP8[tempDoublePtr+1>>0] = HEAP8[ptr+1>>0]; + HEAP8[tempDoublePtr+2>>0] = HEAP8[ptr+2>>0]; + HEAP8[tempDoublePtr+3>>0] = HEAP8[ptr+3>>0]; + HEAP8[tempDoublePtr+4>>0] = HEAP8[ptr+4>>0]; + HEAP8[tempDoublePtr+5>>0] = HEAP8[ptr+5>>0]; + HEAP8[tempDoublePtr+6>>0] = HEAP8[ptr+6>>0]; + HEAP8[tempDoublePtr+7>>0] = HEAP8[ptr+7>>0]; + } + function setTempRet0(value) { + value = value|0; + tempRet0 = value; + } + function getTempRet0() { + return tempRet0|0; + } + '''] + funcs_js + [''' + %s - return %s; -}) -// EMSCRIPTEN_END_ASM -(%s, %s, buffer); -%s; -''' % (pre_tables + '\n'.join(function_tables_impls) + '\n' + function_tables_defs.replace('\n', '\n '), exports, the_global, sending, receiving)] + return %s; + }) + // EMSCRIPTEN_END_ASM + (%s, %s, buffer); + %s; + ''' % (pre_tables + '\n'.join(function_tables_impls) + '\n' + function_tables_defs.replace('\n', '\n '), exports, the_global, sending, receiving)] + + if not settings.get('SIDE_MODULE'): + funcs_js.append(''' + Runtime.stackAlloc = asm['stackAlloc']; + Runtime.stackSave = asm['stackSave']; + Runtime.stackRestore = asm['stackRestore']; + Runtime.setTempRet0 = asm['setTempRet0']; + Runtime.getTempRet0 = asm['getTempRet0']; + ''') - if not settings.get('SIDE_MODULE'): - funcs_js.append(''' -Runtime.stackAlloc = asm['stackAlloc']; -Runtime.stackSave = asm['stackSave']; -Runtime.stackRestore = asm['stackRestore']; -Runtime.setTempRet0 = asm['setTempRet0']; -Runtime.getTempRet0 = asm['getTempRet0']; -''') + # Set function table masks + masks = {} + max_mask = 0 + for sig, table in last_forwarded_json['Functions']['tables'].iteritems(): + mask = table.count(',') + masks[sig] = str(mask) + max_mask = max(mask, max_mask) + def function_table_maskize(js, masks): + def fix(m): + sig = m.groups(0)[0] + return masks[sig] + return re.sub(r'{{{ FTM_([\w\d_$]+) }}}', lambda m: fix(m), js) # masks[m.groups(0)[0]] + funcs_js = map(lambda js: function_table_maskize(js, masks), funcs_js) - # Set function table masks - masks = {} - max_mask = 0 - for sig, table in last_forwarded_json['Functions']['tables'].iteritems(): - mask = table.count(',') - masks[sig] = str(mask) - max_mask = max(mask, max_mask) - def function_table_maskize(js, masks): - def fix(m): - sig = m.groups(0)[0] - return masks[sig] - return re.sub(r'{{{ FTM_([\w\d_$]+) }}}', lambda m: fix(m), js) # masks[m.groups(0)[0]] - funcs_js = map(lambda js: function_table_maskize(js, masks), funcs_js) + if settings.get('DLOPEN_SUPPORT'): + funcs_js.append(''' + asm.maxFunctionIndex = %(max_mask)d; + DLFCN.registerFunctions(asm, %(max_mask)d+1, %(sigs)s, Module); + Module.SYMBOL_TABLE = SYMBOL_TABLE; + ''' % { 'max_mask': max_mask, 'sigs': str(map(str, last_forwarded_json['Functions']['tables'].keys())) }) + else: + function_tables_defs = '\n'.join([table for table in last_forwarded_json['Functions']['tables'].itervalues()]) + outfile.write(function_tables_defs) + funcs_js = [''' + // EMSCRIPTEN_START_FUNCS + '''] + funcs_js + [''' + // EMSCRIPTEN_END_FUNCS + '''] + + # Create symbol table for self-dlopen if settings.get('DLOPEN_SUPPORT'): - funcs_js.append(''' - asm.maxFunctionIndex = %(max_mask)d; - DLFCN.registerFunctions(asm, %(max_mask)d+1, %(sigs)s, Module); - Module.SYMBOL_TABLE = SYMBOL_TABLE; -''' % { 'max_mask': max_mask, 'sigs': str(map(str, last_forwarded_json['Functions']['tables'].keys())) }) - - else: - function_tables_defs = '\n'.join([table for table in last_forwarded_json['Functions']['tables'].itervalues()]) - outfile.write(function_tables_defs) - funcs_js = [''' -// EMSCRIPTEN_START_FUNCS -'''] + funcs_js + [''' -// EMSCRIPTEN_END_FUNCS -'''] - - # Create symbol table for self-dlopen - if settings.get('DLOPEN_SUPPORT'): - symbol_table = {} - for k, v in forwarded_json['Variables']['indexedGlobals'].iteritems(): - if forwarded_json['Variables']['globals'][k]['named']: - symbol_table[k] = str(v + forwarded_json['Runtime']['GLOBAL_BASE']) - for raw in last_forwarded_json['Functions']['tables'].itervalues(): - if raw == '': continue - table = map(string.strip, raw[raw.find('[')+1:raw.find(']')].split(",")) - for i in range(len(table)): - value = table[i] - if value != '0': - if settings.get('SIDE_MODULE'): - symbol_table[value] = 'FUNCTION_TABLE_OFFSET+' + str(i) - else: - symbol_table[value] = str(i) - outfile.write("var SYMBOL_TABLE = %s;" % json.dumps(symbol_table).replace('"', '')) - - for i in range(len(funcs_js)): # do this loop carefully to save memory - outfile.write(funcs_js[i]) - funcs_js = None - - outfile.write(post) - - outfile.close() - - if DEBUG: logging.debug(' emscript: final python processing took %s seconds' % (time.time() - t)) + symbol_table = {} + for k, v in forwarded_json['Variables']['indexedGlobals'].iteritems(): + if forwarded_json['Variables']['globals'][k]['named']: + symbol_table[k] = str(v + forwarded_json['Runtime']['GLOBAL_BASE']) + for raw in last_forwarded_json['Functions']['tables'].itervalues(): + if raw == '': continue + table = map(string.strip, raw[raw.find('[')+1:raw.find(']')].split(",")) + for i in range(len(table)): + value = table[i] + if value != '0': + if settings.get('SIDE_MODULE'): + symbol_table[value] = 'FUNCTION_TABLE_OFFSET+' + str(i) + else: + symbol_table[value] = str(i) + outfile.write("var SYMBOL_TABLE = %s;" % json.dumps(symbol_table).replace('"', '')) + + for i in range(len(funcs_js)): # do this loop carefully to save memory + outfile.write(funcs_js[i]) + funcs_js = None + + outfile.write(post) + + outfile.close() + + if DEBUG: logging.debug(' emscript: final python processing took %s seconds' % (time.time() - t)) + + success = True + + finally: + if not success: + outfile.close() + shared.try_delete(outfile.name) # remove partial output if os.environ.get('EMCC_FAST_COMPILER') != '0': emscript = emscript_fast diff --git a/src/embind/embind.js b/src/embind/embind.js index 4821c77b..124ea569 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -4,10 +4,12 @@ /*global readLatin1String*/ /*global __emval_register, _emval_handle_array, __emval_decref*/ /*global ___getTypeName*/ +/*global requireHandle*/ /*jslint sub:true*/ /* The symbols 'fromWireType' and 'toWireType' must be accessed via array notation to be closure-safe since craftInvokerFunction crafts functions as strings that can't be closured. */ var InternalError = Module['InternalError'] = extendError(Error, 'InternalError'); var BindingError = Module['BindingError'] = extendError(Error, 'BindingError'); var UnboundTypeError = Module['UnboundTypeError'] = extendError(BindingError, 'UnboundTypeError'); +var PureVirtualError = Module['PureVirtualError'] = extendError(BindingError, 'PureVirtualError'); function throwInternalError(message) { throw new InternalError(message); @@ -151,6 +153,59 @@ function _embind_repr(v) { } } +// raw pointer -> instance +var registeredInstances = {}; + +function getBasestPointer(class_, ptr) { + if (ptr === undefined) { + throwBindingError('ptr should not be undefined'); + } + while (class_.baseClass) { + ptr = class_.upcast(ptr); + class_ = class_.baseClass; + } + return ptr; +} + +function registerInheritedInstance(class_, ptr, instance) { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + throwBindingError('Tried to register registered instance: ' + ptr); + } else { + registeredInstances[ptr] = instance; + } +} + +function unregisterInheritedInstance(class_, ptr) { + ptr = getBasestPointer(class_, ptr); + if (registeredInstances.hasOwnProperty(ptr)) { + delete registeredInstances[ptr]; + } else { + throwBindingError('Tried to unregister unregistered instance: ' + ptr); + } +} + +function getInheritedInstance(class_, ptr) { + ptr = getBasestPointer(class_, ptr); + return registeredInstances[ptr]; +} + +function getInheritedInstanceCount() { + return Object.keys(registeredInstances).length; +} +Module['getInheritedInstanceCount'] = getInheritedInstanceCount; + +function getLiveInheritedInstances() { + var rv = []; + for (var k in registeredInstances) { + if (registeredInstances.hasOwnProperty(k)) { + rv.push(registeredInstances[k]); + } + } + return rv; +} +Module['getLiveInheritedInstances'] = getLiveInheritedInstances; + // typeID -> { toWireType: ..., fromWireType: ... } var registeredTypes = {}; @@ -535,6 +590,9 @@ function __embind_register_emval(rawType, name) { 'argPackAdvance': 8, 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: null, // This type does not need a destructor + + // TODO: do we need a deleteObject here? write a test where + // emval is passed into JS via an interface }); } @@ -630,7 +688,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp var argsList = ""; var argsListWired = ""; - for(var i = 0; i < argCount-2; ++i) { + for(var i = 0; i < argCount - 2; ++i) { argsList += (i!==0?", ":"")+"arg"+i; argsListWired += (i!==0?", ":"")+"arg"+i+"Wired"; } @@ -665,7 +723,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += "var thisWired = classParam.toWireType("+dtorStack+", this);\n"; } - for(var i = 0; i < argCount-2; ++i) { + for(var i = 0; i < argCount - 2; ++i) { invokerFnBody += "var arg"+i+"Wired = argType"+i+".toWireType("+dtorStack+", arg"+i+"); // "+argTypes[i+2].name+"\n"; args1.push("argType"+i); args2.push(argTypes[i+2]); @@ -684,7 +742,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += "runDestructors(destructors);\n"; } else { for(var i = isClassMethodFunc?1:2; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. Also skip class type if not a method. - var paramName = (i === 1 ? "thisWired" : ("arg"+(i-2)+"Wired")); + var paramName = (i === 1 ? "thisWired" : ("arg"+(i - 2)+"Wired")); if (argTypes[i].destructorFunction !== null) { invokerFnBody += paramName+"_dtor("+paramName+"); // "+argTypes[i].name+"\n"; args1.push(paramName+"_dtor"); @@ -1124,14 +1182,14 @@ function RegisteredPointer( } } -RegisteredPointer.prototype.getPointee = function(ptr) { +RegisteredPointer.prototype.getPointee = function getPointee(ptr) { if (this.rawGetPointee) { ptr = this.rawGetPointee(ptr); } return ptr; }; -RegisteredPointer.prototype.destructor = function(ptr) { +RegisteredPointer.prototype.destructor = function destructor(ptr) { if (this.rawDestructor) { this.rawDestructor(ptr); } @@ -1140,7 +1198,13 @@ RegisteredPointer.prototype.destructor = function(ptr) { RegisteredPointer.prototype['argPackAdvance'] = 8; RegisteredPointer.prototype['readValueFromPointer'] = simpleReadValueFromPointer; -RegisteredPointer.prototype['fromWireType'] = function(ptr) { +RegisteredPointer.prototype['deleteObject'] = function deleteObject(handle) { + if (handle !== null) { + handle['delete'](); + } +}; + +RegisteredPointer.prototype['fromWireType'] = function fromWireType(ptr) { // ptr is a raw pointer (or a raw smartpointer) // rawPointer is a maybe-null raw pointer @@ -1150,6 +1214,13 @@ RegisteredPointer.prototype['fromWireType'] = function(ptr) { return null; } + var registeredInstance = getInheritedInstance(this.registeredClass, rawPointer); + if (undefined !== registeredInstance) { + var rv = registeredInstance['clone'](); + this.destructor(ptr); + return rv; + } + function makeDefaultHandle() { if (this.isSmartPointer) { return makeClassHandle(this.registeredClass.instancePrototype, { @@ -1225,7 +1296,7 @@ function getInstanceTypeName(handle) { return handle.$$.ptrType.registeredClass.name; } -ClassHandle.prototype['isAliasOf'] = function(other) { +ClassHandle.prototype['isAliasOf'] = function isAliasOf(other) { if (!(this instanceof ClassHandle)) { return false; } @@ -1255,19 +1326,24 @@ function throwInstanceAlreadyDeleted(obj) { throwBindingError(getInstanceTypeName(obj) + ' instance already deleted'); } -ClassHandle.prototype['clone'] = function() { +ClassHandle.prototype['clone'] = function clone() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - var clone = Object.create(Object.getPrototypeOf(this), { - $$: { - value: shallowCopy(this.$$), - } - }); + if (this.$$.preservePointerOnDelete) { + this.$$.count.value += 1; + return this; + } else { + var clone = Object.create(Object.getPrototypeOf(this), { + $$: { + value: shallowCopy(this.$$), + } + }); - clone.$$.count.value += 1; - return clone; + clone.$$.count.value += 1; + return clone; + } }; function runDestructor(handle) { @@ -1283,16 +1359,20 @@ ClassHandle.prototype['delete'] = function ClassHandle_delete() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - if (this.$$.deleteScheduled) { + + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { throwBindingError('Object already scheduled for deletion'); } this.$$.count.value -= 1; - if (0 === this.$$.count.value) { + var toDelete = 0 === this.$$.count.value; + if (toDelete) { runDestructor(this); } - this.$$.smartPtr = undefined; - this.$$.ptr = undefined; + if (!this.$$.preservePointerOnDelete) { + this.$$.smartPtr = undefined; + this.$$.ptr = undefined; + } }; var deletionQueue = []; @@ -1305,7 +1385,7 @@ ClassHandle.prototype['deleteLater'] = function deleteLater() { if (!this.$$.ptr) { throwInstanceAlreadyDeleted(this); } - if (this.$$.deleteScheduled) { + if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) { throwBindingError('Object already scheduled for deletion'); } deletionQueue.push(this); @@ -1351,12 +1431,15 @@ function RegisteredClass( this.getActualType = getActualType; this.upcast = upcast; this.downcast = downcast; + this.pureVirtualFunctions = []; } function shallowCopy(o) { var rv = {}; for (var k in o) { - rv[k] = o[k]; + if (Object.prototype.hasOwnProperty.call(o, k)) { + rv[k] = o[k]; + } } return rv; } @@ -1491,12 +1574,12 @@ function __embind_register_class_constructor( if (undefined !== classType.registeredClass.constructor_body[argCount - 1]) { throw new BindingError("Cannot register multiple constructors with identical number of parameters (" + (argCount-1) + ") for class '" + classType.name + "'! Overload resolution is currently only performed using the parameter count, not actual type info!"); } - classType.registeredClass.constructor_body[argCount - 1] = function() { + classType.registeredClass.constructor_body[argCount - 1] = function unboundTypeHandler() { throwUnboundTypeError('Cannot construct ' + classType.name + ' due to unbound types', rawArgTypes); }; whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - classType.registeredClass.constructor_body[argCount - 1] = function() { + classType.registeredClass.constructor_body[argCount - 1] = function constructor_body() { if (arguments.length !== argCount - 1) { throwBindingError(humanName + ' called with ' + arguments.length + ' arguments, expected ' + (argCount-1)); } @@ -1569,7 +1652,8 @@ function __embind_register_class_function( rawArgTypesAddr, // [ReturnType, ThisType, Args...] invokerSignature, rawInvoker, - context + context, + isPureVirtual ) { var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); methodName = readLatin1String(methodName); @@ -1579,21 +1663,25 @@ function __embind_register_class_function( classType = classType[0]; var humanName = classType.name + '.' + methodName; - var unboundTypesHandler = function() { + if (isPureVirtual) { + classType.registeredClass.pureVirtualFunctions.push(methodName); + } + + function unboundTypesHandler() { throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; + } var proto = classType.registeredClass.instancePrototype; var method = proto[methodName]; - if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount-2)) { + if (undefined === method || (undefined === method.overloadTable && method.className !== classType.name && method.argCount === argCount - 2)) { // This is the first overload to be registered, OR we are replacing a function in the base class with a function in the derived class. - unboundTypesHandler.argCount = argCount-2; + unboundTypesHandler.argCount = argCount - 2; unboundTypesHandler.className = classType.name; proto[methodName] = unboundTypesHandler; } else { // There was an existing function with the same name registered. Set up a function overload routing table. ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-2] = unboundTypesHandler; + proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler; } whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { @@ -1605,7 +1693,7 @@ function __embind_register_class_function( if (undefined === proto[methodName].overloadTable) { proto[methodName] = memberFunction; } else { - proto[methodName].overloadTable[argCount-2] = memberFunction; + proto[methodName].overloadTable[argCount - 2] = memberFunction; } return []; @@ -1614,53 +1702,6 @@ function __embind_register_class_function( }); } -function __embind_register_class_class_function( - rawClassType, - methodName, - argCount, - rawArgTypesAddr, - invokerSignature, - rawInvoker, - fn -) { - var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); - methodName = readLatin1String(methodName); - rawInvoker = requireFunction(invokerSignature, rawInvoker); - whenDependentTypesAreResolved([], [rawClassType], function(classType) { - classType = classType[0]; - var humanName = classType.name + '.' + methodName; - - var unboundTypesHandler = function() { - throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); - }; - - var proto = classType.registeredClass.constructor; - if (undefined === proto[methodName]) { - // This is the first function to be registered with this name. - unboundTypesHandler.argCount = argCount-1; - proto[methodName] = unboundTypesHandler; - } else { - // There was an existing function with the same name registered. Set up a function overload routing table. - ensureOverloadTable(proto, methodName, humanName); - proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; - } - - whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, - // the function handlers go into an overload table. - var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); - var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); - if (undefined === proto[methodName].overloadTable) { - proto[methodName] = func; - } else { - proto[methodName].overloadTable[argCount-1] = func; - } - return []; - }); - return []; - }); -} - function __embind_register_class_property( classType, fieldName, @@ -1730,6 +1771,112 @@ function __embind_register_class_property( }); } +function __embind_register_class_class_function( + rawClassType, + methodName, + argCount, + rawArgTypesAddr, + invokerSignature, + rawInvoker, + fn +) { + var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr); + methodName = readLatin1String(methodName); + rawInvoker = requireFunction(invokerSignature, rawInvoker); + whenDependentTypesAreResolved([], [rawClassType], function(classType) { + classType = classType[0]; + var humanName = classType.name + '.' + methodName; + + function unboundTypesHandler() { + throwUnboundTypeError('Cannot call ' + humanName + ' due to unbound types', rawArgTypes); + } + + var proto = classType.registeredClass.constructor; + if (undefined === proto[methodName]) { + // This is the first function to be registered with this name. + unboundTypesHandler.argCount = argCount-1; + proto[methodName] = unboundTypesHandler; + } else { + // There was an existing function with the same name registered. Set up a function overload routing table. + ensureOverloadTable(proto, methodName, humanName); + proto[methodName].overloadTable[argCount-1] = unboundTypesHandler; + } + + whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { + // Replace the initial unbound-types-handler stub with the proper function. If multiple overloads are registered, + // the function handlers go into an overload table. + var invokerArgsArray = [argTypes[0] /* return value */, null /* no class 'this'*/].concat(argTypes.slice(1) /* actual params */); + var func = craftInvokerFunction(humanName, invokerArgsArray, null /* no class 'this'*/, rawInvoker, fn); + if (undefined === proto[methodName].overloadTable) { + proto[methodName] = func; + } else { + proto[methodName].overloadTable[argCount-1] = func; + } + return []; + }); + return []; + }); +} + +function __embind_create_inheriting_constructor(constructorName, wrapperType, properties) { + constructorName = readLatin1String(constructorName); + wrapperType = requireRegisteredType(wrapperType, 'wrapper'); + properties = requireHandle(properties); + + var arraySlice = [].slice; + + var registeredClass = wrapperType.registeredClass; + var wrapperPrototype = registeredClass.instancePrototype; + var baseClass = registeredClass.baseClass; + var baseClassPrototype = baseClass.instancePrototype; + var baseConstructor = registeredClass.baseClass.constructor; + var ctor = createNamedFunction(constructorName, function() { + registeredClass.baseClass.pureVirtualFunctions.forEach(function(name) { + if (this[name] === baseClassPrototype[name]) { + throw new PureVirtualError('Pure virtual function ' + name + ' must be implemented in JavaScript'); + } + }.bind(this)); + + Object.defineProperty(this, '__parent', { + value: wrapperPrototype + }); + this.__construct.apply(this, arraySlice.call(arguments)); + }); + + // It's a little nasty that we're modifying the wrapper prototype here. + + wrapperPrototype.__construct = function __construct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __construct"); + } + + var inner = baseConstructor.implement.apply( + undefined, + [this].concat(arraySlice.call(arguments))); + var $$ = inner.$$; + inner.notifyOnDestruction(); + $$.preservePointerOnDelete = true; + Object.defineProperty(this, '$$', { + value: $$ + }); + registerInheritedInstance(registeredClass, $$.ptr, this); + }; + + wrapperPrototype.__destruct = function __destruct() { + if (this === wrapperPrototype) { + throwBindingError("Pass correct 'this' to __destruct"); + } + + unregisterInheritedInstance(registeredClass, this.$$.ptr); + }; + + ctor.prototype = Object.create(wrapperPrototype); + for (var p in properties) { + ctor.prototype[p] = properties[p]; + } + return __emval_register(ctor); +} + var char_0 = '0'.charCodeAt(0); var char_9 = '9'.charCodeAt(0); function makeLegalFunctionName(name) { diff --git a/src/embind/emval.js b/src/embind/emval.js index 4007701a..1661bc02 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -265,7 +265,14 @@ function __emval_get_method_caller(argCount, argTypes) { " args += argType" + i + ".argPackAdvance;\n"; } functionBody += - " var rv = handle[name](" + argsList + ");\n" + + " var rv = handle[name](" + argsList + ");\n"; + for (var i = 0; i < argCount - 1; ++i) { + if (types[i + 1]['deleteObject']) { + functionBody += + " argType" + i + ".deleteObject(arg" + i + ");\n"; + } + } + functionBody += " return retType.toWireType(destructors, rv);\n" + "};\n"; @@ -281,8 +288,16 @@ function __emval_call_method(caller, handle, methodName, destructorsRef, args) { return caller(handle, methodName, allocateDestructors(destructorsRef), args); } -function __emval_has_function(handle, name) { +function __emval_has_function(handle, name, classType) { handle = requireHandle(handle); name = getStringOrSymbol(name); - return handle[name] instanceof Function; + classType = requireRegisteredType(classType, 'class wrapper filter'); + + var filter = classType.registeredClass.instancePrototype[name]; + return (handle[name] instanceof Function) && (filter === undefined || handle[name] !== filter); +} + +function __emval_typeof(handle) { + handle = requireHandle(handle); + return __emval_register(typeof handle); } diff --git a/src/emscripten-source-map.min.js b/src/emscripten-source-map.min.js index 9151400f..44cb1d84 100644 --- a/src/emscripten-source-map.min.js +++ b/src/emscripten-source-map.min.js @@ -4,7 +4,7 @@ var emscripten_sourcemap_xmlHttp = undefined; function emscripten_sourceMapLoaded() { if (emscripten_sourcemap_xmlHttp.readyState === 4) { Module['removeRunDependency']('sourcemap'); - if (emscripten_sourcemap_xmlHttp.status === 200) { + if (emscripten_sourcemap_xmlHttp.status === 200 || emscripten_sourcemap_xmlHttp.status === 0) { emscripten_source_map = new window.sourceMap.SourceMapConsumer(emscripten_sourcemap_xmlHttp.responseText); console.log('Source map data loaded.'); } else { @@ -20,6 +20,7 @@ function emscripten_loadSourceMap() { emscripten_sourcemap_xmlHttp = new XMLHttpRequest(); emscripten_sourcemap_xmlHttp.onreadystatechange = emscripten_sourceMapLoaded; emscripten_sourcemap_xmlHttp.open("GET", url, true); + emscripten_sourcemap_xmlHttp.responseType = "text"; emscripten_sourcemap_xmlHttp.send(null); } diff --git a/src/library.js b/src/library.js index 0d649258..fb1a8998 100644 --- a/src/library.js +++ b/src/library.js @@ -3222,39 +3222,6 @@ LibraryManager.library = { {{{ makeStructuralReturn([makeGetTempDouble(0, 'i32'), makeGetTempDouble(1, 'i32')]) }}}; }, #endif - strtoll__deps: ['_parseInt64'], - strtoll: function(str, endptr, base) { - return __parseInt64(str, endptr, base, '-9223372036854775808', '9223372036854775807'); // LLONG_MIN, LLONG_MAX. - }, - strtoll_l__deps: ['strtoll'], - strtoll_l: function(str, endptr, base) { - return _strtoll(str, endptr, base); // no locale support yet - }, - strtol__deps: ['_parseInt'], - strtol: function(str, endptr, base) { - return __parseInt(str, endptr, base, -2147483648, 2147483647, 32); // LONG_MIN, LONG_MAX. - }, - strtol_l__deps: ['strtol'], - strtol_l: function(str, endptr, base) { - return _strtol(str, endptr, base); // no locale support yet - }, - strtoul__deps: ['_parseInt'], - strtoul: function(str, endptr, base) { - return __parseInt(str, endptr, base, 0, 4294967295, 32, true); // ULONG_MAX. - }, - strtoul_l__deps: ['strtoul'], - strtoul_l: function(str, endptr, base) { - return _strtoul(str, endptr, base); // no locale support yet - }, - strtoull__deps: ['_parseInt64'], - strtoull: function(str, endptr, base) { - return __parseInt64(str, endptr, base, 0, '18446744073709551615', true); // ULONG_MAX. - }, - strtoull_l__deps: ['strtoull'], - strtoull_l: function(str, endptr, base) { - return _strtoull(str, endptr, base); // no locale support yet - }, - environ: 'allocate(1, "i32*", ALLOC_STATIC)', __environ__deps: ['environ'], __environ: '_environ', @@ -3619,28 +3586,6 @@ LibraryManager.library = { return pdest|0; }, - strlwr__deps:['tolower'], - strlwr: function(pstr){ - var i = 0; - while(1) { - var x = {{{ makeGetValue('pstr', 'i', 'i8') }}}; - if (x == 0) break; - {{{ makeSetValue('pstr', 'i', '_tolower(x)', 'i8') }}}; - i++; - } - }, - - strupr__deps:['toupper'], - strupr: function(pstr){ - var i = 0; - while(1) { - var x = {{{ makeGetValue('pstr', 'i', 'i8') }}}; - if (x == 0) break; - {{{ makeSetValue('pstr', 'i', '_toupper(x)', 'i8') }}}; - i++; - } - }, - strcat__asm: true, strcat__sig: 'iii', strcat__deps: ['strlen'], @@ -3681,132 +3626,6 @@ LibraryManager.library = { // ctype.h // ========================================================================== - isascii: function(chr) { - return chr >= 0 && (chr & 0x80) == 0; - }, - toascii: function(chr) { - return chr & 0x7F; - }, - toupper: function(chr) { - if (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) { - return chr - {{{ charCode('a') }}} + {{{ charCode('A') }}}; - } else { - return chr; - } - }, - _toupper: 'toupper', - toupper_l__deps: ['toupper'], - toupper_l: function(str, endptr, base) { - return _toupper(str, endptr, base); // no locale support yet - }, - - tolower__asm: true, - tolower__sig: 'ii', - tolower: function(chr) { - chr = chr|0; - if ((chr|0) < {{{ charCode('A') }}}) return chr|0; - if ((chr|0) > {{{ charCode('Z') }}}) return chr|0; - return (chr - {{{ charCode('A') }}} + {{{ charCode('a') }}})|0; - }, - _tolower: 'tolower', - tolower_l__deps: ['tolower'], - tolower_l: function(chr) { - return _tolower(chr); // no locale support yet - }, - - // The following functions are defined as macros in glibc. - islower: function(chr) { - return chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}; - }, - islower_l__deps: ['islower'], - islower_l: function(chr) { - return _islower(chr); // no locale support yet - }, - isupper: function(chr) { - return chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}; - }, - isupper_l__deps: ['isupper'], - isupper_l: function(chr) { - return _isupper(chr); // no locale support yet - }, - isalpha: function(chr) { - return (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || - (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); - }, - isalpha_l__deps: ['isalpha'], - isalpha_l: function(chr) { - return _isalpha(chr); // no locale support yet - }, - isdigit: function(chr) { - return chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}; - }, - isdigit_l__deps: ['isdigit'], - isdigit_l: function(chr) { - return _isdigit(chr); // no locale support yet - }, - isxdigit: function(chr) { - return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || - (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('f') }}}) || - (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('F') }}}); - }, - isxdigit_l__deps: ['isxdigit'], - isxdigit_l: function(chr) { - return _isxdigit(chr); // no locale support yet - }, - isalnum: function(chr) { - return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || - (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || - (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); - }, - isalnum_l__deps: ['isalnum'], - isalnum_l: function(chr) { - return _isalnum(chr); // no locale support yet - }, - ispunct: function(chr) { - return (chr >= {{{ charCode('!') }}} && chr <= {{{ charCode('/') }}}) || - (chr >= {{{ charCode(':') }}} && chr <= {{{ charCode('@') }}}) || - (chr >= {{{ charCode('[') }}} && chr <= {{{ charCode('`') }}}) || - (chr >= {{{ charCode('{') }}} && chr <= {{{ charCode('~') }}}); - }, - ispunct_l__deps: ['ispunct'], - ispunct_l: function(chr) { - return _ispunct(chr); // no locale support yet - }, - isspace: function(chr) { - return (chr == 32) || (chr >= 9 && chr <= 13); - }, - isspace_l__deps: ['isspace'], - isspace_l: function(chr) { - return _isspace(chr); // no locale support yet - }, - isblank: function(chr) { - return chr == {{{ charCode(' ') }}} || chr == {{{ charCode('\t') }}}; - }, - isblank_l__deps: ['isblank'], - isblank_l: function(chr) { - return _isblank(chr); // no locale support yet - }, - iscntrl: function(chr) { - return (0 <= chr && chr <= 0x1F) || chr === 0x7F; - }, - iscntrl_l__deps: ['iscntrl'], - iscntrl_l: function(chr) { - return _iscntrl(chr); // no locale support yet - }, - isprint: function(chr) { - return 0x1F < chr && chr < 0x7F; - }, - isprint_l__deps: ['isprint'], - isprint_l: function(chr) { - return _isprint(chr); // no locale support yet - }, - isgraph: function(chr) { - return 0x20 < chr && chr < 0x7F; - }, - isgraph_l__deps: ['isgraph'], - isgraph_l: function(chr) { - return _isgraph(chr); // no locale support yet - }, // Lookup tables for glibc ctype implementation. __ctype_b_loc__deps: ['malloc'], __ctype_b_loc: function() { @@ -6104,15 +5923,15 @@ LibraryManager.library = { var i = 0; setjmpId = (setjmpId+1)|0; {{{ makeSetValueAsm('env', '0', 'setjmpId', 'i32') }}}; - while ((i|0) < {{{ 2*MAX_SETJMPS }}}) { - if ({{{ makeGetValueAsm('table', '(i<<2)', 'i32') }}} == 0) { - {{{ makeSetValueAsm('table', '(i<<2)', 'setjmpId', 'i32') }}}; - {{{ makeSetValueAsm('table', '(i<<2)+4', 'label', 'i32') }}}; + while ((i|0) < {{{ MAX_SETJMPS }}}) { + if ({{{ makeGetValueAsm('table', '(i<<3)', 'i32') }}} == 0) { + {{{ makeSetValueAsm('table', '(i<<3)', 'setjmpId', 'i32') }}}; + {{{ makeSetValueAsm('table', '(i<<3)+4', 'label', 'i32') }}}; // prepare next slot - {{{ makeSetValueAsm('table', '(i<<2)+8', '0', 'i32') }}}; + {{{ makeSetValueAsm('table', '(i<<3)+8', '0', 'i32') }}}; return 0; } - i = (i+2)|0; + i = i+1|0; } {{{ makePrintChars('too many setjmps in a function call, build with a higher value for MAX_SETJMPS') }}}; abort(0); @@ -6126,12 +5945,12 @@ LibraryManager.library = { table = table|0; var i = 0, curr = 0; while ((i|0) < {{{ MAX_SETJMPS }}}) { - curr = {{{ makeGetValueAsm('table', '(i<<2)', 'i32') }}}; + curr = {{{ makeGetValueAsm('table', '(i<<3)', 'i32') }}}; if ((curr|0) == 0) break; if ((curr|0) == (id|0)) { - return {{{ makeGetValueAsm('table', '(i<<2)+4', 'i32') }}}; + return {{{ makeGetValueAsm('table', '(i<<3)+4', 'i32') }}}; } - i = (i+2)|0; + i = i+1|0; } return 0; }, diff --git a/src/library_browser.js b/src/library_browser.js index 44e8c473..4ef7c577 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -322,11 +322,6 @@ mergeInto(LibraryManager.library, { #endif // Set the background of the WebGL canvas to black canvas.style.backgroundColor = "black"; - - // Warn on context loss - canvas.addEventListener('webglcontextlost', function(event) { - alert('WebGL context lost. You will need to reload the page.'); - }, false); } if (setInModule) { GLctx = Module.ctx = ctx; @@ -478,7 +473,21 @@ mergeInto(LibraryManager.library, { }, getMouseWheelDelta: function(event) { - return Math.max(-1, Math.min(1, event.type === 'DOMMouseScroll' ? event.detail : -event.wheelDelta)); + var delta = 0; + switch (event.type) { + case 'DOMMouseScroll': + delta = event.detail; + break; + case 'mousewheel': + delta = -event.wheelDelta; + break; + case 'wheel': + delta = event.deltaY; + break; + default: + throw 'unrecognized mouse wheel event: ' + event.type; + } + return Math.max(-1, Math.min(1, delta)); }, mouseX: 0, @@ -688,15 +697,22 @@ mergeInto(LibraryManager.library, { emscripten_async_wget: function(url, file, onload, onerror) { var _url = Pointer_stringify(url); var _file = Pointer_stringify(file); + function doCallback(callback) { + if (callback) { + var stack = Runtime.stackSave(); + Runtime.dynCall('vi', callback, [allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]); + Runtime.stackRestore(stack); + } + } FS.createPreloadedFile( PATH.dirname(_file), PATH.basename(_file), _url, true, true, function() { - if (onload) Runtime.dynCall('vi', onload, [file]); + doCallback(onload); }, function() { - if (onerror) Runtime.dynCall('vi', onerror, [file]); + doCallback(onerror); } ); }, @@ -727,7 +743,11 @@ mergeInto(LibraryManager.library, { http.onload = function http_onload(e) { if (http.status == 200) { FS.createDataFile( _file.substr(0, index), _file.substr(index + 1), new Uint8Array(http.response), true, true); - if (onload) Runtime.dynCall('vii', onload, [arg, file]); + if (onload) { + var stack = Runtime.stackSave(); + Runtime.dynCall('vii', onload, [arg, allocate(intArrayFromString(_file), 'i8', ALLOC_STACK)]); + Runtime.stackRestore(stack); + } } else { if (onerror) Runtime.dynCall('vii', onerror, [arg, http.status]); } @@ -740,8 +760,8 @@ mergeInto(LibraryManager.library, { // PROGRESS http.onprogress = function http_onprogress(e) { - if (e.lengthComputable || (e.lengthComputable === undefined && e.totalSize != 0)) { - var percentComplete = (e.position / e.totalSize)*100; + if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) { + var percentComplete = (e.loaded / e.total)*100; if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]); } }; diff --git a/src/library_html5.js b/src/library_html5.js index d9376c2a..d5d0cd66 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -1307,6 +1307,12 @@ var LibraryJSEvents = { JSEvents.registerWebGlEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED') }}}, "webglcontextrestored"); return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; }, + + emscripten_is_webgl_context_lost: function(target) { + // TODO: In the future if multiple GL contexts are supported, use the 'target' parameter to find the canvas to query. + if (!Module['ctx']) return true; // No context ~> lost context. + return Module['ctx'].isContextLost(); + } }; autoAddDeps(LibraryJSEvents, '$JSEvents'); diff --git a/src/library_sdl.js b/src/library_sdl.js index d5b2b354..a01b3c6c 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -430,6 +430,15 @@ var LibrarySDL = { savedKeydown: null, receiveEvent: function(event) { + function unpressAllPressedKeys() { + // Un-press all pressed keys: TODO + for (var code in SDL.keyboardMap) { + SDL.events.push({ + type: 'keyup', + keyCode: SDL.keyboardMap[code] + }); + } + }; switch(event.type) { case 'touchstart': case 'touchmove': { event.preventDefault(); @@ -532,7 +541,7 @@ var LibrarySDL = { } } // fall through - case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': + case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel': case 'wheel': // If we preventDefault on keydown events, the subsequent keypress events // won't fire. However, it's fine (and in some cases necessary) to // preventDefault for keys that don't generate a character. Otherwise, @@ -541,21 +550,40 @@ var LibrarySDL = { event.preventDefault(); } - if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel') { + if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel' || event.type == 'wheel') { + // Simulate old-style SDL events representing mouse wheel input as buttons var button = Browser.getMouseWheelDelta(event) > 0 ? 4 : 3; - var event2 = { + var event1 = { type: 'mousedown', button: button, pageX: event.pageX, pageY: event.pageY }; - SDL.events.push(event2); - event = { + SDL.events.push(event1); + var event2 = { type: 'mouseup', button: button, pageX: event.pageX, pageY: event.pageY }; + SDL.events.push(event2); + + // Convert DOMMouseScroll events to wheel events for new style SDL events. + if (event.type == 'DOMMouseScroll') { + SDL.events.push({ + type: 'wheel', + deltaX: 0, + deltaY: -event.detail, + }); + break; + } else if (event.type == 'mousewheel') { + SDL.events.push({ + type: 'wheel', + deltaX: 0, + deltaY: event.wheelDelta, + }); + break; + } } else if (event.type == 'mousedown') { SDL.DOMButtons[event.button] = 1; SDL.events.push({ @@ -635,18 +663,23 @@ var LibrarySDL = { } event.preventDefault(); break; + case 'focus': + SDL.events.push(event); + event.preventDefault(); + break; case 'blur': - case 'visibilitychange': { - // Un-press all pressed keys: TODO - for (var code in SDL.keyboardMap) { - SDL.events.push({ - type: 'keyup', - keyCode: SDL.keyboardMap[code] - }); - } + SDL.events.push(event); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'visibilitychange': + SDL.events.push({ + type: 'visibilitychange', + visible: !document.hidden + }); + unpressAllPressedKeys(); event.preventDefault(); break; - } case 'unload': if (Browser.mainLoop.runner) { SDL.events.push(event); @@ -787,6 +820,12 @@ var LibrarySDL = { } break; } + case 'wheel': { + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.x, 'event.deltaX', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_MouseWheelEvent.y, 'event.deltaY', 'i32') }}}; + break; + } case 'touchstart': case 'touchend': case 'touchmove': { var touch = event.touch; if (!Browser.touches[touch.identifier]) break; @@ -840,6 +879,29 @@ var LibrarySDL = { {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; break; } + case 'focus': { + var SDL_WINDOWEVENT_FOCUS_GAINED = 12 /* SDL_WINDOWEVENT_FOCUS_GAINED */; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_GAINED', 'i8') }}}; + break; + } + case 'blur': { + var SDL_WINDOWEVENT_FOCUS_LOST = 13 /* SDL_WINDOWEVENT_FOCUS_LOST */; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_LOST', 'i8') }}}; + break; + } + case 'visibilitychange': { + var SDL_WINDOWEVENT_SHOWN = 1 /* SDL_WINDOWEVENT_SHOWN */; + var SDL_WINDOWEVENT_HIDDEN = 2 /* SDL_WINDOWEVENT_HIDDEN */; + var visibilityEventID = event.visible ? SDL_WINDOWEVENT_SHOWN : SDL_WINDOWEVENT_HIDDEN; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -880,12 +942,80 @@ var LibrarySDL = { if (!info) return 0; var ret = info.volume * 128; // MIX_MAX_VOLUME if (volume != -1) { - info.volume = volume / 128; - if (info.audio) info.audio.volume = info.volume; + info.volume = Math.min(Math.max(volume, 0), 128) / 128; + if (info.audio) { + try { + info.audio.volume = info.volume; // For <audio> element + if (info.audio.webAudioGainNode) info.audio.webAudioGainNode['gain']['value'] = info.volume; // For WebAudio playback + } catch(e) { + Module.printErr('setGetVolume failed to set audio volume: ' + e); + } + } } return ret; }, + // Plays out an SDL audio resource that was loaded with the Mix_Load APIs, when using Web Audio.. + playWebAudio: function(audio) { + if (!audio) return; + if (audio.webAudioNode) return; // This instance is already playing, don't start again. + if (!SDL.webAudioAvailable()) return; + try { + var webAudio = audio.resource.webAudio; + audio.paused = false; + if (!webAudio.decodedBuffer) { + if (webAudio.onDecodeComplete === undefined) abort("Cannot play back audio object that was not loaded"); + webAudio.onDecodeComplete.push(function() { if (!audio.paused) SDL.playWebAudio(audio); }); + return; + } + audio.webAudioNode = SDL.audioContext['createBufferSource'](); + audio.webAudioNode['buffer'] = webAudio.decodedBuffer; + audio.webAudioNode['loop'] = audio.loop; + audio.webAudioNode['onended'] = function() { audio.onended(); } // For <media> element compatibility, route the onended signal to the instance. + + // Add an intermediate gain node to control volume. + audio.webAudioGainNode = SDL.audioContext['createGain'](); + audio.webAudioGainNode['gain']['value'] = audio.volume; + audio.webAudioNode['connect'](audio.webAudioGainNode); + audio.webAudioGainNode['connect'](SDL.audioContext['destination']); + audio.webAudioNode['start'](0, audio.currentPosition); + audio.startTime = SDL.audioContext['currentTime'] - audio.currentPosition; + } catch(e) { + Module.printErr('playWebAudio failed: ' + e); + } + }, + + // Pausea an SDL audio resource that was played with Web Audio.. + pauseWebAudio: function(audio) { + if (!audio) return; + if (audio.webAudioNode) { + try { + // Remember where we left off, so that if/when we resume, we can restart the playback at a proper place. + audio.currentPosition = (SDL.audioContext['currentTime'] - audio.startTime) % audio.resource.webAudio.decodedBuffer.duration; + // Important: When we reach here, the audio playback is stopped by the user. But when calling .stop() below, the Web Audio + // graph will send the onended signal, but we don't want to process that, since pausing should not clear/destroy the audio + // channel. + audio.webAudioNode['onended'] = undefined; + audio.webAudioNode.stop(); + audio.webAudioNode = undefined; + } catch(e) { + Module.printErr('pauseWebAudio failed: ' + e); + } + } + audio.paused = true; + }, + + openAudioContext: function() { + // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, + // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. + if (!SDL.audioContext) { + if (typeof(AudioContext) !== 'undefined') SDL.audioContext = new AudioContext(); + else if (typeof(webkitAudioContext) !== 'undefined') SDL.audioContext = new webkitAudioContext(); + } + }, + + webAudioAvailable: function() { return !!SDL.audioContext; }, + fillWebAudioBufferFromHeap: function(heapPtr, sizeSamplesPerChannel, dstAudioBuffer) { // The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit or 16-bit as // supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data, @@ -1043,6 +1173,7 @@ var LibrarySDL = { document.addEventListener("keydown", SDL.receiveEvent); document.addEventListener("keyup", SDL.receiveEvent); document.addEventListener("keypress", SDL.receiveEvent); + window.addEventListener("focus", SDL.receiveEvent); window.addEventListener("blur", SDL.receiveEvent); document.addEventListener("visibilitychange", SDL.receiveEvent); } @@ -1065,11 +1196,16 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['mousedown'] = 0x401 /* SDL_MOUSEBUTTONDOWN */; SDL.DOMEventToSDLEvent['mouseup'] = 0x402 /* SDL_MOUSEBUTTONUP */; SDL.DOMEventToSDLEvent['mousemove'] = 0x400 /* SDL_MOUSEMOTION */; + SDL.DOMEventToSDLEvent['wheel'] = 0x403 /* SDL_MOUSEWHEEL */; SDL.DOMEventToSDLEvent['touchstart'] = 0x700 /* SDL_FINGERDOWN */; SDL.DOMEventToSDLEvent['touchend'] = 0x701 /* SDL_FINGERUP */; SDL.DOMEventToSDLEvent['touchmove'] = 0x702 /* SDL_FINGERMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; + SDL.DOMEventToSDLEvent['visibilitychange'] = 0x200 /* SDL_WINDOWEVENT */; + SDL.DOMEventToSDLEvent['focus'] = 0x200 /* SDL_WINDOWEVENT */; + SDL.DOMEventToSDLEvent['blur'] = 0x200 /* SDL_WINDOWEVENT */; + // These are not technically DOM events; the HTML gamepad API is poll-based. // However, we define them here, as the rest of the SDL code assumes that // all SDL events originate as DOM events. @@ -1139,7 +1275,7 @@ var LibrarySDL = { }, SDL_SetVideoMode: function(width, height, depth, flags) { - ['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) { + ['touchstart', 'touchend', 'touchmove', 'mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'wheel', 'mouseout'].forEach(function(event) { Module['canvas'].addEventListener(event, SDL.receiveEvent, true); }); @@ -1182,11 +1318,11 @@ var LibrarySDL = { for (var i = 0; i < SDL.numChannels; ++i) { if (SDL.channels[i].audio) { SDL.channels[i].audio.pause(); + SDL.channels[i].audio = undefined; } } - if (SDL.music.audio) { - SDL.music.audio.pause(); - } + if (SDL.music.audio) SDL.music.audio.pause(); + SDL.music.audio = undefined; Module.print('SDL_Quit called (and ignored)'); }, @@ -1936,15 +2072,8 @@ var LibrarySDL = { } else { // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. - if (!SDL.audioContext) { - if (typeof(AudioContext) !== 'undefined') { - SDL.audioContext = new AudioContext(); - } else if (typeof(webkitAudioContext) !== 'undefined') { - SDL.audioContext = new webkitAudioContext(); - } else { - throw 'Web Audio API is not available!'; - } - } + SDL.openAudioContext(); + if (!SDL.audioContext) throw 'Web Audio API is not available!'; SDL.audio.soundSource = new Array(); // Use an array of sound sources as a ring buffer to queue blocks of synthesized audio to Web Audio API. SDL.audio.nextSoundSource = 0; // Index of the next sound buffer in the ring buffer queue to play. SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start. @@ -2127,6 +2256,7 @@ var LibrarySDL = { Mix_Quit: function(){}, Mix_OpenAudio: function(frequency, format, channels, chunksize) { + SDL.openAudioContext(); SDL.allocateChannels(32); // Just record the values for a later call to Mix_QuickLoad_RAW SDL.mixerFrequency = frequency; @@ -2169,6 +2299,7 @@ var LibrarySDL = { var filename = ''; var audio; + var webAudio; var bytes; if (rwops.filename !== undefined) { @@ -2176,7 +2307,7 @@ var LibrarySDL = { var raw = Module["preloadedAudios"][filename]; if (!raw) { if (raw === null) Module.printErr('Trying to reuse preloaded audio, but freePreloadedMediaOnUse is set!'); - Runtime.warnOnce('Cannot find preloaded audio ' + filename); + if (!Module.noAudioDecoding) Runtime.warnOnce('Cannot find preloaded audio ' + filename); // see if we can read the file-contents from the in-memory FS try { @@ -2192,45 +2323,84 @@ var LibrarySDL = { audio = raw; } else if (rwops.bytes !== undefined) { - bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count); + // For Web Audio context buffer decoding, we must make a clone of the audio data, but for <media> element, + // a view to existing data is sufficient. + if (SDL.webAudioAvailable()) bytes = HEAPU8.buffer.slice(rwops.bytes, rwops.bytes + rwops.count); + else bytes = HEAPU8.subarray(rwops.bytes, rwops.bytes + rwops.count); } else { return 0; } - // Here, we didn't find a preloaded audio but we either were passed a filepath for - // which we loaded bytes, or we were passed some bytes - if (audio === undefined && bytes) { + var arrayBuffer = bytes ? bytes.buffer || bytes : bytes; + + // To allow user code to work around browser bugs with audio playback on <audio> elements an Web Audio, enable + // the user code to hook in a callback to decide on a file basis whether each file should use Web Audio or <audio> for decoding and playback. + // In particular, see https://bugzilla.mozilla.org/show_bug.cgi?id=654787 and ?id=1012801 for tradeoffs. + var canPlayWithWebAudio = Module['SDL_canPlayWithWebAudio'] === undefined || Module['SDL_canPlayWithWebAudio'](filename, arrayBuffer); + + if (bytes !== undefined && SDL.webAudioAvailable() && canPlayWithWebAudio) { + audio = undefined; + webAudio = {}; + // The audio decoding process is asynchronous, which gives trouble if user code plays the audio data back immediately + // after loading. Therefore prepare an array of callback handlers to run when this audio decoding is complete, which + // will then start the playback (with some delay). + webAudio.onDecodeComplete = []; // While this member array exists, decoding hasn't finished yet. + function onDecodeComplete(data) { + webAudio.decodedBuffer = data; + // Call all handlers that were waiting for this decode to finish, and clear the handler list. + webAudio.onDecodeComplete.forEach(function(e) { e(); }); + webAudio.onDecodeComplete = undefined; // Don't allow more callback handlers since audio has finished decoding. + } + + SDL.audioContext['decodeAudioData'](arrayBuffer, onDecodeComplete); + } else if (audio === undefined && bytes) { + // Here, we didn't find a preloaded audio but we either were passed a filepath for + // which we loaded bytes, or we were passed some bytes var blob = new Blob([bytes], {type: rwops.mimetype}); var url = URL.createObjectURL(blob); audio = new Audio(); audio.src = url; + audio.mozAudioChannelType = 'content'; // bugzilla 910340 } var id = SDL.audios.length; // Keep the loaded audio in the audio arrays, ready for playback SDL.audios.push({ source: filename, - audio: audio + audio: audio, // Points to the <audio> element, if loaded + webAudio: webAudio // Points to a Web Audio -specific resource object, if loaded }); return id; }, Mix_QuickLoad_RAW: function(mem, len) { - var audio = new Audio(); - // Record the number of channels and frequency for later usage - audio.numChannels = SDL.mixerNumChannels; - audio.frequency = SDL.mixerFrequency; + var audio; + var webAudio; + var numSamples = len >> 1; // len is the length in bytes, and the array contains 16-bit PCM values var buffer = new Float32Array(numSamples); for (var i = 0; i < numSamples; ++i) { buffer[i] = ({{{ makeGetValue('mem', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?) } - // FIXME: doesn't make sense to keep the audio element in the buffer + + if (SDL.webAudioAvailable()) { + webAudio = {}; + webAudio.decodedBuffer = buffer; + } else { + var audio = new Audio(); + audio.mozAudioChannelType = 'content'; // bugzilla 910340 + // Record the number of channels and frequency for later usage + audio.numChannels = SDL.mixerNumChannels; + audio.frequency = SDL.mixerFrequency; + // FIXME: doesn't make sense to keep the audio element in the buffer + } + var id = SDL.audios.length; SDL.audios.push({ source: '', audio: audio, + webAudio: webAudio, buffer: buffer }); return id; @@ -2243,13 +2413,12 @@ var LibrarySDL = { SDL.channelMinimumNumber = num; }, Mix_PlayChannel: function(channel, id, loops) { - // TODO: handle loops + // TODO: handle fixed amount of N loops. Currently loops either 0 or infinite times. // Get the audio element associated with the ID var info = SDL.audios[id]; if (!info) return -1; - var audio = info.audio; - if (!audio) return -1; + if (!info.audio && !info.webAudio) return -1; // If the user asks us to allocate a channel automatically, get the first // free one. @@ -2265,73 +2434,33 @@ var LibrarySDL = { return -1; } } - // We clone the audio node to utilize the preloaded audio buffer, since - // the browser has already preloaded the audio file. var channelInfo = SDL.channels[channel]; - channelInfo.audio = audio = audio.cloneNode(true); - audio.numChannels = info.audio.numChannels; - audio.frequency = info.audio.frequency; - // TODO: handle N loops. Behavior matches Mix_PlayMusic - audio.loop = loops != 0; - audio['onended'] = function SDL_audio_onended() { // TODO: cache these - channelInfo.audio = null; - if (SDL.channelFinished) { - Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); - } - } - // Either play the element, or load the dynamic data into it - if (info.buffer) { - var contextCtor = null; - if (audio && ('mozSetup' in audio)) { // Audio Data API - try { - audio['mozSetup'](audio.numChannels, audio.frequency); - audio["mozWriteAudio"](info.buffer); - } catch (e) { - // Workaround for Firefox bug 783052 - // ignore this exception! - } - /* - } else if (contextCtor = (window.AudioContext || // WebAudio API - window.webkitAudioContext)) { - var currentIndex = 0; - var numChannels = parseInt(audio.numChannels); - var context = new contextCtor(); - var source = context.createBufferSource(); - source.loop = false; - source.buffer = context.createBuffer(numChannels, 1, audio.frequency); - var jsNode = context.createJavaScriptNode(2048, numChannels, numChannels); - jsNode.onaudioprocess = function jsNode_onaudioprocess(event) { - var buffers = new Array(numChannels); - for (var i = 0; i < numChannels; ++i) { - buffers[i] = event.outputBuffer.getChannelData(i); - } - var remaining = info.buffer.length - currentIndex; - if (remaining > 2048) { - remaining = 2048; - } - for (var i = 0; i < remaining;) { - for (var j = 0; j < numChannels; ++j) { - buffers[j][i] = info.buffer[currentIndex + i + j] * audio.volume; - } - i += j; - } - currentIndex += remaining * numChannels; - for (var i = remaining; i < 2048;) { - for (var j = 0; j < numChannels; ++j) { - buffers[j][i] = 0; // silence - } - i += j; - } - }; - source.connect(jsNode); - jsNode.connect(context.destination); - source.noteOn(0); - */ - } + var audio; + if (info.webAudio) { + // Create an instance of the WebAudio object. + audio = {}; + audio.resource = info; // This new object is an instance that refers to this existing resource. + audio.paused = false; + audio.currentPosition = 0; + // Make our instance look similar to the instance of a <media> to make api simple. + audio.play = function() { SDL.playWebAudio(this); } + audio.pause = function() { SDL.pauseWebAudio(this); } } else { - audio.play(); + // We clone the audio node to utilize the preloaded audio buffer, since + // the browser has already preloaded the audio file. + audio = info.audio.cloneNode(true); + audio.numChannels = info.audio.numChannels; + audio.frequency = info.audio.frequency; } + audio['onended'] = function SDL_audio_onended() { // TODO: cache these + if (channelInfo.audio == this) { channelInfo.audio.paused = true; channelInfo.audio = null; } + if (SDL.channelFinished) Runtime.getFuncWrapper(SDL.channelFinished, 'vi')(channel); + } + channelInfo.audio = audio; + // TODO: handle N loops. Behavior matches Mix_PlayMusic + audio.loop = loops != 0; audio.volume = channelInfo.volume; + audio.play(); return channel; }, Mix_PlayChannelTimed: 'Mix_PlayChannel', // XXX ignore Timing @@ -2384,46 +2513,51 @@ var LibrarySDL = { Mix_PlayMusic__deps: ['Mix_HaltMusic'], Mix_PlayMusic: function(id, loops) { - loops = Math.max(loops, 1); - var audio = SDL.audios[id].audio; - if (!audio) return 0; - audio.loop = loops != 0; // TODO: handle N loops for finite N - if (SDL.audios[id].buffer) { - audio["mozWriteAudio"](SDL.audios[id].buffer); - } else { - audio.play(); - } - audio.volume = SDL.music.volume; - audio['onended'] = _Mix_HaltMusic; // will send callback + // Pause old music if it exists. if (SDL.music.audio) { - if (!SDL.music.audio.paused) { - Module.printErr('Music is already playing. ' + SDL.music.source); - } + if (!SDL.music.audio.paused) Module.printErr('Music is already playing. ' + SDL.music.source); SDL.music.audio.pause(); } + var info = SDL.audios[id]; + var audio; + if (info.webAudio) { // Play via Web Audio API + // Create an instance of the WebAudio object. + audio = {}; + audio.resource = info; // This new webAudio object is an instance that refers to this existing resource. + audio.paused = false; + audio.currentPosition = 0; + audio.play = function() { SDL.playWebAudio(this); } + audio.pause = function() { SDL.pauseWebAudio(this); } + } else if (info.audio) { // Play via the <audio> element + audio = info.audio; + } + audio['onended'] = function() { if (SDL.music.audio == this) _Mix_HaltMusic(); } // will send callback + audio.loop = loops != 0; // TODO: handle N loops for finite N + audio.volume = SDL.music.volume; SDL.music.audio = audio; + audio.play(); return 0; }, Mix_PauseMusic: function() { var audio = SDL.music.audio; - if (!audio) return 0; - audio.pause(); + if (audio) audio.pause(); return 0; }, Mix_ResumeMusic: function() { var audio = SDL.music.audio; - if (!audio) return 0; - audio.play(); + if (audio) audio.play(); return 0; }, Mix_HaltMusic: function() { var audio = SDL.music.audio; - if (!audio) return 0; - audio.src = audio.src; // rewind - audio.pause(); + if (audio) { + audio.src = audio.src; // rewind <media> element + audio.currentPosition = 0; // rewind Web Audio graph playback. + audio.pause(); + } SDL.music.audio = null; if (SDL.hookMusicFinished) { Runtime.dynCall('v', SDL.hookMusicFinished); @@ -2500,9 +2634,7 @@ var LibrarySDL = { return; } var info = SDL.channels[channel]; - if (info && info.audio) { - info.audio.play(); - } + if (info && info.audio) info.audio.play(); }, // SDL TTF diff --git a/src/preamble.js b/src/preamble.js index 2aec94c6..58b442ab 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -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,148 @@ 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: ccall/cwrap use the C stack for temporary values. If you pass a string + // then it is only alive until the call is complete. If the code being + // called saves the pointer to be used later, it may point to invalid + // data. If you need a string to live forever, you can create it (and + // must later delete it manually!) using malloc and writeStringToMemory, + // for example. + // + // 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) JSfuncs['stackRestore'](); + 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*([^*]*?)[\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 literally '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 diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index 568dd381..9e469ec4 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -1097,7 +1097,7 @@ void Relooper::Calculate(Block *Entry) { // Remove unneeded breaks and continues. // A flow operation is trivially unneeded if the shape we naturally get to by normal code // execution is the same as the flow forces us to. - void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL, LoopShape *LastLoop=NULL) { + void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL, LoopShape *LastLoop=NULL, unsigned Depth=0) { BlockSet NaturalBlocks; FollowNaturalFlow(Natural, NaturalBlocks); Shape *Next = Root; @@ -1108,7 +1108,7 @@ void Relooper::Calculate(Block *Entry) { if (Simple->Inner->BranchVar) LastLoop = NULL; // a switch clears out the loop (TODO: only for breaks, not continue) if (Simple->Next) { - if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2) { + if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2 && Depth < 20) { // If there is a next block, we already know at Simple creation time to make direct branches, // and we can do nothing more in general. But, we try to optimize the case of a break and // a direct: This would normally be if (break?) { break; } .. but if we @@ -1144,6 +1144,7 @@ void Relooper::Calculate(Block *Entry) { } } } + Depth++; // this optimization increases depth, for us and all our next chain (i.e., until this call returns) } Next = Simple->Next; } else { @@ -1168,11 +1169,11 @@ void Relooper::Calculate(Block *Entry) { } }, { for (IdShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { - RemoveUnneededFlows(iter->second, Multiple->Next, Multiple->Breaks ? NULL : LastLoop); + RemoveUnneededFlows(iter->second, Multiple->Next, Multiple->Breaks ? NULL : LastLoop, Depth+1); } Next = Multiple->Next; }, { - RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop); + RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop, Depth+1); Next = Loop->Next; }); } diff --git a/src/relooper/fuzzer.py b/src/relooper/fuzzer.py index 18db997e..313f4d5e 100644 --- a/src/relooper/fuzzer.py +++ b/src/relooper/fuzzer.py @@ -3,7 +3,7 @@ import random, subprocess, difflib while True: # Random decisions - num = random.randint(2, 250) + num = random.randint(2, 500) density = random.random() * random.random() decisions = [random.randint(1, num*20) for x in range(num*3)] branches = [0]*num @@ -123,14 +123,14 @@ int main() { open('fuzz.slow.js', 'w').write(slow) open('fuzz.cpp', 'w').write(fast) print '_' - slow_out = subprocess.Popen(['mozjs', '-m', '-n', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] + slow_out = subprocess.Popen(['mozjs', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] print '.' subprocess.call(['g++', 'fuzz.cpp', 'Relooper.o', '-o', 'fuzz', '-g']) print '*' subprocess.call(['./fuzz'], stdout=open('fuzz.fast.js', 'w')) print '-' - fast_out = subprocess.Popen(['mozjs', '-m', '-n', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] + fast_out = subprocess.Popen(['mozjs', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] print if slow_out != fast_out: diff --git a/src/relooper/test.cpp b/src/relooper/test.cpp index 9f3ddceb..f319757b 100644 --- a/src/relooper/test.cpp +++ b/src/relooper/test.cpp @@ -1,4 +1,6 @@ +#include <vector> + #include "Relooper.h" int main() { @@ -435,5 +437,34 @@ int main() { puts(r.GetOutputBuffer()); } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- lots of exits to an unwind block, possible nesting --\n\n"); + + const int DEPTH = 40; + + std::vector<Block*> blocks; + for (int i = 0; i < DEPTH; i++) blocks.push_back(new Block("// block\n", NULL)); + Block *last = new Block("// last\nreturn;\n", NULL); + Block *UW = new Block("// UW\nresumeException();\n\n", NULL); + + for (int i = 0; i < DEPTH; i++) { + Block *b = blocks[i]; + b->AddBranchTo(i+1 < DEPTH ? blocks[i+1] : last, "check()", NULL); + b->AddBranchTo(UW, NULL, NULL); + } + + Relooper r; + for (int i = 0; i < DEPTH; i++) r.AddBlock(blocks[i]); + r.AddBlock(last); + r.AddBlock(UW); + + r.Calculate(blocks[0]); + r.Render(); + + puts(r.GetOutputBuffer()); + } } diff --git a/src/relooper/test.txt b/src/relooper/test.txt index d53aeeb1..2ae331c0 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -417,3 +417,156 @@ } // block D + + +-- lots of exits to an unwind block, possible nesting -- + + // block + do { + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // last + return; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } while(0); + // UW + resumeException(); + + diff --git a/src/shell.html b/src/shell.html index 1e213024..d204bfa6 100644 --- a/src/shell.html +++ b/src/shell.html @@ -1245,7 +1245,16 @@ console.error(text); } }, - canvas: document.getElementById('canvas'), + canvas: (function() { + var canvas = document.getElementById('canvas'); + + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), setStatus: function(text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; if (text === Module.setStatus.text) return; diff --git a/src/shell_minimal.html b/src/shell_minimal.html index 7dd67929..b67c6fdd 100644 --- a/src/shell_minimal.html +++ b/src/shell_minimal.html @@ -101,7 +101,16 @@ console.error(text); } }, - canvas: document.getElementById('canvas'), + canvas: (function() { + var canvas = document.getElementById('canvas'); + + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), setStatus: function(text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; if (text === Module.setStatus.text) return; diff --git a/src/struct_info.json b/src/struct_info.json index f762bf2b..54c89fd7 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -932,6 +932,16 @@ "file": "SDL/SDL_events.h", "defines": [], "structs": { + "SDL_WindowEvent": [ + "type", + "windowID", + "event", + "padding1", + "padding2", + "padding3", + "data1", + "data2" + ], "SDL_KeyboardEvent": [ "type", "windowID", @@ -969,6 +979,14 @@ "x", "y" ], + "SDL_MouseWheelEvent": [ + "type", + "timestamp", + "windowID", + "which", + "x", + "y" + ], "SDL_JoyAxisEvent": [ "type", "which", diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index eede0755..7bc28ff6 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -149,7 +149,8 @@ namespace emscripten { TYPEID argTypes[], const char* invokerSignature, GenericFunction invoker, - void* context); + void* context, + unsigned isPureVirtual); void _embind_register_class_property( TYPEID classType, @@ -172,6 +173,11 @@ namespace emscripten { GenericFunction invoker, GenericFunction method); + EM_VAL _embind_create_inheriting_constructor( + const char* constructorName, + TYPEID wrapperType, + EM_VAL properties); + void _embind_register_enum( TYPEID enumType, const char* name, @@ -276,6 +282,33 @@ namespace emscripten { return method; } + namespace internal { + // this should be in <type_traits>, but alas, it's not + template<typename T> struct remove_class; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...)> { using type = R(A...); }; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...) const> { using type = R(A...); }; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...) volatile> { using type = R(A...); }; + template<typename C, typename R, typename... A> + struct remove_class<R(C::*)(A...) const volatile> { using type = R(A...); }; + + template<typename LambdaType> + struct CalculateLambdaSignature { + using type = typename std::add_pointer< + typename remove_class< + decltype(&LambdaType::operator()) + >::type + >::type; + }; + } + + template<typename LambdaType> + typename internal::CalculateLambdaSignature<LambdaType>::type optional_override(const LambdaType& fp) { + return fp; + } + //////////////////////////////////////////////////////////////////////////////// // Invoker //////////////////////////////////////////////////////////////////////////////// @@ -873,40 +906,55 @@ namespace emscripten { }; }; + //////////////////////////////////////////////////////////////////////////////// // CLASSES //////////////////////////////////////////////////////////////////////////////// + namespace internal { + class WrapperBase { + public: + void setNotifyJSOnDestruction(bool notify) { + notifyJSOnDestruction = notify; + } + + protected: + bool notifyJSOnDestruction = false; + }; + } + // abstract classes template<typename T> - class wrapper : public T { + class wrapper : public T, public internal::WrapperBase { public: typedef T class_type; - explicit wrapper(val&& wrapped) - : wrapped(std::forward<val>(wrapped)) + template<typename... Args> + explicit wrapper(val&& wrapped, Args&&... args) + : T(std::forward<Args>(args)...) + , wrapped(std::forward<val>(wrapped)) {} + ~wrapper() { + if (notifyJSOnDestruction) { + call<void>("__destruct"); + } + } + template<typename ReturnType, typename... Args> ReturnType call(const char* name, Args&&... args) const { return wrapped.call<ReturnType>(name, std::forward<Args>(args)...); } - template<typename ReturnType, typename... Args, typename Default> - ReturnType optional_call(const char* name, Default def, Args&&... args) const { - if (wrapped.has_function(name)) { - return call<ReturnType>(name, std::forward<Args>(args)...); - } else { - return def(); - } - } - private: val wrapped; }; -#define EMSCRIPTEN_WRAPPER(T) \ - T(::emscripten::val&& v): wrapper(std::forward<::emscripten::val>(v)) {} +#define EMSCRIPTEN_WRAPPER(T) \ + template<typename... Args> \ + T(::emscripten::val&& v, Args&&... args) \ + : wrapper(std::forward<::emscripten::val>(v), std::forward<Args>(args)...) \ + {} namespace internal { struct NoBaseClass { @@ -987,6 +1035,45 @@ namespace emscripten { SmartPtrIfNeeded(U&, const char*) { } }; + + template<typename WrapperType> + val wrapped_extend(const std::string& name, const val& properties) { + return val::take_ownership(_embind_create_inheriting_constructor( + name.c_str(), + TypeID<WrapperType>::get(), + properties.__get_handle())); + } + }; + + struct pure_virtual { + template<typename InputType, int Index> + struct Transform { + typedef InputType type; + }; + }; + + namespace internal { + template<typename... Policies> + struct isPureVirtual; + + template<typename... Rest> + struct isPureVirtual<pure_virtual, Rest...> { + static constexpr bool value = true; + }; + + template<typename T, typename... Rest> + struct isPureVirtual<T, Rest...> { + static constexpr bool value = isPureVirtual<Rest...>::value; + }; + + template<> + struct isPureVirtual<> { + static constexpr bool value = false; + }; + } + + template<typename... ConstructorArgs> + struct constructor { }; template<typename ClassType, typename BaseSpecifier = internal::NoBaseClass> @@ -1095,18 +1182,38 @@ namespace emscripten { return *this; } - template<typename WrapperType, typename PointerType = WrapperType*> - const class_& allow_subclass(const char* wrapperClassName, const char* pointerName = "<UnknownPointerName>") const { + template<typename WrapperType, typename PointerType = WrapperType*, typename... ConstructorArgs> + const class_& allow_subclass( + const char* wrapperClassName, + const char* pointerName = "<UnknownPointerName>", + ::emscripten::constructor<ConstructorArgs...> = ::emscripten::constructor<ConstructorArgs...>() + ) const { using namespace internal; auto cls = class_<WrapperType, base<ClassType>>(wrapperClassName) + .function("notifyOnDestruction", select_overload<void(WrapperType&)>([](WrapperType& wrapper) { + wrapper.setNotifyJSOnDestruction(true); + })) ; SmartPtrIfNeeded<PointerType> _(cls, pointerName); - return class_function( - "implement", - &wrapped_new<PointerType, WrapperType, val>, - allow_raw_pointer<ret_val>()); + return + class_function( + "implement", + &wrapped_new<PointerType, WrapperType, val, ConstructorArgs...>, + allow_raw_pointer<ret_val>()) + .class_function( + "extend", + &wrapped_extend<WrapperType>) + ; + } + + template<typename WrapperType, typename... ConstructorArgs> + const class_& allow_subclass( + const char* wrapperClassName, + ::emscripten::constructor<ConstructorArgs...> constructor + ) const { + return allow_subclass<WrapperType, WrapperType*>(wrapperClassName, "<UnknownPointerName>", constructor); } template<typename ReturnType, typename... Args, typename... Policies> @@ -1123,7 +1230,8 @@ namespace emscripten { args.types, getSignature(invoker), reinterpret_cast<GenericFunction>(invoker), - getContext(memberFunction)); + getContext(memberFunction), + isPureVirtual<Policies...>::value); return *this; } @@ -1141,7 +1249,8 @@ namespace emscripten { args.types, getSignature(invoker), reinterpret_cast<GenericFunction>(invoker), - getContext(memberFunction)); + getContext(memberFunction), + isPureVirtual<Policies...>::value); return *this; } @@ -1158,7 +1267,8 @@ namespace emscripten { args.types, getSignature(invoke), reinterpret_cast<GenericFunction>(invoke), - getContext(function)); + getContext(function), + false); return *this; } diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index db81725a..74c32a99 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -636,6 +636,12 @@ extern EMSCRIPTEN_RESULT emscripten_set_beforeunload_callback(void *userData, co extern EMSCRIPTEN_RESULT emscripten_set_webglcontextlost_callback(const char *target, void *userData, int useCapture, EM_BOOL (*func)(int eventType, const void *reserved, void *userData)); extern EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback(const char *target, void *userData, int useCapture, EM_BOOL (*func)(int eventType, const void *reserved, void *userData)); +/* + * Queries the given canvas element for whether its WebGL context is in a lost state. + * target: Reserved for future use, pass in 0. + */ +extern EM_BOOL emscripten_is_webgl_context_lost(const char *target); + #ifdef __cplusplus } // ~extern "C" #endif diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index e217c959..31f5923e 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -61,7 +61,9 @@ namespace emscripten { EM_VAR_ARGS argv); bool _emval_has_function( EM_VAL value, - const char* methodName); + const char* methodName, + internal::TYPEID filter); + EM_VAL _emval_typeof(EM_VAL value); } template<const char* address> @@ -254,7 +256,6 @@ namespace emscripten { // * delete // * in // * instanceof - // * typeof // * ! ~ - + ++ -- // * * / % // * + - @@ -392,8 +393,10 @@ namespace emscripten { return MethodCaller<ReturnValue, Args...>::call(handle, name, std::forward<Args>(args)...); } - bool has_function(const char* name) const { - return _emval_has_function(handle, name); + template<typename ClassType> + bool has_implementation_defined_function(const char* name) const { + using namespace internal; + return _emval_has_function(handle, name, TypeID<ClassType>::get()); } template<typename T> @@ -411,6 +414,15 @@ namespace emscripten { return fromGenericWireType<T>(result); } + // private: TODO: use a friend? + internal::EM_VAL __get_handle() const { + return handle; + } + + val typeof() const { + return val(_emval_typeof(handle)); + } + private: // takes ownership, assumes handle already incref'd explicit val(internal::EM_VAL handle) diff --git a/system/lib/libc.symbols b/system/lib/libc.symbols index eb2053ce..d889d509 100644 --- a/system/lib/libc.symbols +++ b/system/lib/libc.symbols @@ -40,6 +40,7 @@ W _Znwj W _ZnwjRKSt9nothrow_t T __floatscan + T __intscan T __overflow T __shgetc T __shlim @@ -57,6 +58,9 @@ W free W independent_calloc W independent_comalloc + T isdigit + T isspace + T isupper W mallinfo W malloc W malloc_footprint @@ -77,6 +81,12 @@ T memcmp T memcpy T strtod + T strtoull + T strtoll + T strtoul + T strtol + T strtoimax + T strtoumax T strcoll T __strcoll_l W strcoll_l @@ -89,4 +99,5 @@ T strtof_l T strtold T strtold_l + T tolower W valloc diff --git a/system/lib/libc/musl/src/compat/readme.txt b/system/lib/libc/musl/src/compat/readme.txt new file mode 100644 index 00000000..c96c6136 --- /dev/null +++ b/system/lib/libc/musl/src/compat/readme.txt @@ -0,0 +1,2 @@ +Files in this directory are not strictly standard musl libc, but implemented here for compatibility for Emscripten purposes. + diff --git a/system/lib/libc/musl/src/compat/strlwr.c b/system/lib/libc/musl/src/compat/strlwr.c new file mode 100644 index 00000000..7c70a948 --- /dev/null +++ b/system/lib/libc/musl/src/compat/strlwr.c @@ -0,0 +1,12 @@ +#include <ctype.h> + +char *strlwr(char *str) +{ + char *ret = str; + while(*str) + { + *str = tolower(*str); + ++str; + } + return ret; +} diff --git a/system/lib/libc/musl/src/compat/strtol_l.c b/system/lib/libc/musl/src/compat/strtol_l.c new file mode 100644 index 00000000..58b46186 --- /dev/null +++ b/system/lib/libc/musl/src/compat/strtol_l.c @@ -0,0 +1,22 @@ +#include <stdlib.h> +#include <ctype.h> + +unsigned long long strtoull_l(const char *restrict s, char **restrict p, int base, locale_t loc) +{ + return strtoull(s, p, base); +} + +long long strtoll_l(const char *restrict s, char **restrict p, int base, locale_t loc) +{ + return strtoll(s, p, base); +} + +unsigned long strtoul_l(const char *restrict s, char **restrict p, int base, locale_t loc) +{ + return strtoul(s, p, base); +} + +long strtol_l(const char *restrict s, char **restrict p, int base, locale_t loc) +{ + return strtol(s, p, base); +} diff --git a/system/lib/libc/musl/src/compat/strupr.c b/system/lib/libc/musl/src/compat/strupr.c new file mode 100644 index 00000000..469ad8b1 --- /dev/null +++ b/system/lib/libc/musl/src/compat/strupr.c @@ -0,0 +1,12 @@ +#include <ctype.h> + +char *strupr(char *str) +{ + char *ret = str; + while(*str) + { + *str = toupper(*str); + ++str; + } + return ret; +} diff --git a/system/lib/libc/musl/src/ctype/isalnum.c b/system/lib/libc/musl/src/ctype/isalnum.c new file mode 100644 index 00000000..e3d2cf0b --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isalnum.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isalnum(int c) +{ + return isalpha(c) || isdigit(c); +} diff --git a/system/lib/libc/musl/src/ctype/isalpha.c b/system/lib/libc/musl/src/ctype/isalpha.c new file mode 100644 index 00000000..53e115c2 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isalpha.c @@ -0,0 +1,7 @@ +#include <ctype.h> +#undef isalpha + +int isalpha(int c) +{ + return ((unsigned)c|32)-'a' < 26; +} diff --git a/system/lib/libc/musl/src/ctype/isascii.c b/system/lib/libc/musl/src/ctype/isascii.c new file mode 100644 index 00000000..3af0a10d --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isascii.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isascii(int c) +{ + return !(c&~0x7f); +} diff --git a/system/lib/libc/musl/src/ctype/isblank.c b/system/lib/libc/musl/src/ctype/isblank.c new file mode 100644 index 00000000..957400b2 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isblank.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isblank(int c) +{ + return (c == ' ' || c == '\t'); +} diff --git a/system/lib/libc/musl/src/ctype/iscntrl.c b/system/lib/libc/musl/src/ctype/iscntrl.c new file mode 100644 index 00000000..92ed7f0e --- /dev/null +++ b/system/lib/libc/musl/src/ctype/iscntrl.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int iscntrl(int c) +{ + return (unsigned)c < 0x20 || c == 0x7f; +} diff --git a/system/lib/libc/musl/src/ctype/isdigit.c b/system/lib/libc/musl/src/ctype/isdigit.c new file mode 100644 index 00000000..0bc82a6d --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isdigit.c @@ -0,0 +1,7 @@ +#include <ctype.h> +#undef isdigit + +int isdigit(int c) +{ + return (unsigned)c-'0' < 10; +} diff --git a/system/lib/libc/musl/src/ctype/isgraph.c b/system/lib/libc/musl/src/ctype/isgraph.c new file mode 100644 index 00000000..98979d1e --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isgraph.c @@ -0,0 +1,4 @@ +int isgraph(int c) +{ + return (unsigned)c-0x21 < 0x5e; +} diff --git a/system/lib/libc/musl/src/ctype/islower.c b/system/lib/libc/musl/src/ctype/islower.c new file mode 100644 index 00000000..d72fb212 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/islower.c @@ -0,0 +1,7 @@ +#include <ctype.h> +#undef islower + +int islower(int c) +{ + return (unsigned)c-'a' < 26; +} diff --git a/system/lib/libc/musl/src/ctype/isprint.c b/system/lib/libc/musl/src/ctype/isprint.c new file mode 100644 index 00000000..504e66ed --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isprint.c @@ -0,0 +1,4 @@ +int isprint(int c) +{ + return (unsigned)c-0x20 < 0x5f; +} diff --git a/system/lib/libc/musl/src/ctype/ispunct.c b/system/lib/libc/musl/src/ctype/ispunct.c new file mode 100644 index 00000000..fc455352 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/ispunct.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int ispunct(int c) +{ + return isgraph(c) && !isalnum(c); +} diff --git a/system/lib/libc/musl/src/ctype/isspace.c b/system/lib/libc/musl/src/ctype/isspace.c new file mode 100644 index 00000000..8e535aa1 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isspace.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isspace(int c) +{ + return c == ' ' || (unsigned)c-'\t' < 5; +} diff --git a/system/lib/libc/musl/src/ctype/isupper.c b/system/lib/libc/musl/src/ctype/isupper.c new file mode 100644 index 00000000..f09d88c5 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isupper.c @@ -0,0 +1,7 @@ +#include <ctype.h> +#undef isupper + +int isupper(int c) +{ + return (unsigned)c-'A' < 26; +} diff --git a/system/lib/libc/musl/src/ctype/isxdigit.c b/system/lib/libc/musl/src/ctype/isxdigit.c new file mode 100644 index 00000000..ae68a3dc --- /dev/null +++ b/system/lib/libc/musl/src/ctype/isxdigit.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isxdigit(int c) +{ + return isdigit(c) || ((unsigned)c|32)-'a' < 6; +} diff --git a/system/lib/libc/musl/src/ctype/toascii.c b/system/lib/libc/musl/src/ctype/toascii.c new file mode 100644 index 00000000..f0e48e8e --- /dev/null +++ b/system/lib/libc/musl/src/ctype/toascii.c @@ -0,0 +1,7 @@ +#include <ctype.h> + +/* nonsense function that should NEVER be used! */ +int toascii(int c) +{ + return c & 0x7f; +} diff --git a/system/lib/libc/musl/src/ctype/tolower.c b/system/lib/libc/musl/src/ctype/tolower.c new file mode 100644 index 00000000..b56f3c50 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/tolower.c @@ -0,0 +1,7 @@ +#include <ctype.h> + +int tolower(int c) +{ + if (isupper(c)) return c | 32; + return c; +} diff --git a/system/lib/libc/musl/src/ctype/toupper.c b/system/lib/libc/musl/src/ctype/toupper.c new file mode 100644 index 00000000..1799f030 --- /dev/null +++ b/system/lib/libc/musl/src/ctype/toupper.c @@ -0,0 +1,7 @@ +#include <ctype.h> + +int toupper(int c) +{ + if (islower(c)) return c & 0x5f; + return c; +} diff --git a/system/lib/libc/musl/src/locale/isalnum_l.c b/system/lib/libc/musl/src/locale/isalnum_l.c new file mode 100644 index 00000000..b8a6eef3 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isalnum_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isalnum_l(int c, locale_t l) +{ + return isalnum(c); +} diff --git a/system/lib/libc/musl/src/locale/isalpha_l.c b/system/lib/libc/musl/src/locale/isalpha_l.c new file mode 100644 index 00000000..2e1205c6 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isalpha_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isalpha_l(int c, locale_t l) +{ + return isalpha(c); +} diff --git a/system/lib/libc/musl/src/locale/isblank_l.c b/system/lib/libc/musl/src/locale/isblank_l.c new file mode 100644 index 00000000..27479aa1 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isblank_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isblank_l(int c, locale_t l) +{ + return isblank(c); +} diff --git a/system/lib/libc/musl/src/locale/iscntrl_l.c b/system/lib/libc/musl/src/locale/iscntrl_l.c new file mode 100644 index 00000000..ca596fa9 --- /dev/null +++ b/system/lib/libc/musl/src/locale/iscntrl_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int iscntrl_l(int c, locale_t l) +{ + return iscntrl(c); +} diff --git a/system/lib/libc/musl/src/locale/isdigit_l.c b/system/lib/libc/musl/src/locale/isdigit_l.c new file mode 100644 index 00000000..c8ae7bd3 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isdigit_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isdigit_l(int c, locale_t l) +{ + return isdigit(c); +} diff --git a/system/lib/libc/musl/src/locale/isgraph_l.c b/system/lib/libc/musl/src/locale/isgraph_l.c new file mode 100644 index 00000000..713a86e6 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isgraph_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isgraph_l(int c, locale_t l) +{ + return isgraph(c); +} diff --git a/system/lib/libc/musl/src/locale/islower_l.c b/system/lib/libc/musl/src/locale/islower_l.c new file mode 100644 index 00000000..25ec97a1 --- /dev/null +++ b/system/lib/libc/musl/src/locale/islower_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int islower_l(int c, locale_t l) +{ + return islower(c); +} diff --git a/system/lib/libc/musl/src/locale/isprint_l.c b/system/lib/libc/musl/src/locale/isprint_l.c new file mode 100644 index 00000000..79ef3514 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isprint_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isprint_l(int c, locale_t l) +{ + return isprint(c); +} diff --git a/system/lib/libc/musl/src/locale/ispunct_l.c b/system/lib/libc/musl/src/locale/ispunct_l.c new file mode 100644 index 00000000..1c0bd046 --- /dev/null +++ b/system/lib/libc/musl/src/locale/ispunct_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int ispunct_l(int c, locale_t l) +{ + return ispunct(c); +} diff --git a/system/lib/libc/musl/src/locale/isspace_l.c b/system/lib/libc/musl/src/locale/isspace_l.c new file mode 100644 index 00000000..e1a0efed --- /dev/null +++ b/system/lib/libc/musl/src/locale/isspace_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isspace_l(int c, locale_t l) +{ + return isspace(c); +} diff --git a/system/lib/libc/musl/src/locale/isupper_l.c b/system/lib/libc/musl/src/locale/isupper_l.c new file mode 100644 index 00000000..11ba7036 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isupper_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isupper_l(int c, locale_t l) +{ + return isupper(c); +} diff --git a/system/lib/libc/musl/src/locale/isxdigit_l.c b/system/lib/libc/musl/src/locale/isxdigit_l.c new file mode 100644 index 00000000..68649d09 --- /dev/null +++ b/system/lib/libc/musl/src/locale/isxdigit_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int isxdigit_l(int c, locale_t l) +{ + return isxdigit(c); +} diff --git a/system/lib/libc/musl/src/locale/tolower_l.c b/system/lib/libc/musl/src/locale/tolower_l.c new file mode 100644 index 00000000..ba277919 --- /dev/null +++ b/system/lib/libc/musl/src/locale/tolower_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int tolower_l(int c, locale_t l) +{ + return tolower(c); +} diff --git a/system/lib/libc/musl/src/locale/toupper_l.c b/system/lib/libc/musl/src/locale/toupper_l.c new file mode 100644 index 00000000..73f2f39b --- /dev/null +++ b/system/lib/libc/musl/src/locale/toupper_l.c @@ -0,0 +1,6 @@ +#include <ctype.h> + +int toupper_l(int c, locale_t l) +{ + return toupper(c); +} diff --git a/system/lib/libc/musl/src/stdlib/strtol.c b/system/lib/libc/musl/src/stdlib/strtol.c new file mode 100644 index 00000000..730bf2d7 --- /dev/null +++ b/system/lib/libc/musl/src/stdlib/strtol.c @@ -0,0 +1,64 @@ +#include "stdio_impl.h" +#include "intscan.h" +#include "shgetc.h" +#include <inttypes.h> +#include <limits.h> +#include <ctype.h> +#include "libc.h" + +static unsigned long long strtox(const char *s, char **p, int base, unsigned long long lim) +{ + /* FIXME: use a helper function or macro to setup the FILE */ + FILE f; + f.flags = 0; + f.buf = f.rpos = (void *)s; + if ((size_t)s > (size_t)-1/2) + f.rend = (void *)-1; + else + f.rend = (unsigned char *)s+(size_t)-1/2; + f.lock = -1; + shlim(&f, 0); + unsigned long long y = __intscan(&f, base, 1, lim); + if (p) { + size_t cnt = shcnt(&f); + *p = (char *)s + cnt; + } + return y; +} + +unsigned long long strtoull(const char *restrict s, char **restrict p, int base) +{ + return strtox(s, p, base, ULLONG_MAX); +} + +long long strtoll(const char *restrict s, char **restrict p, int base) +{ + return strtox(s, p, base, LLONG_MIN); +} + +unsigned long strtoul(const char *restrict s, char **restrict p, int base) +{ + return strtox(s, p, base, ULONG_MAX); +} + +long strtol(const char *restrict s, char **restrict p, int base) +{ + return strtox(s, p, base, 0UL+LONG_MIN); +} + +intmax_t strtoimax(const char *restrict s, char **restrict p, int base) +{ + return strtoll(s, p, base); +} + +uintmax_t strtoumax(const char *restrict s, char **restrict p, int base) +{ + return strtoull(s, p, base); +} + +weak_alias(strtol, __strtol_internal); +weak_alias(strtoul, __strtoul_internal); +weak_alias(strtoll, __strtoll_internal); +weak_alias(strtoull, __strtoull_internal); +weak_alias(strtoimax, __strtoimax_internal); +weak_alias(strtoumax, __strtoumax_internal); diff --git a/system/lib/libcextra.symbols b/system/lib/libcextra.symbols index 64ba670a..17e524e6 100644 --- a/system/lib/libcextra.symbols +++ b/system/lib/libcextra.symbols @@ -1,7 +1,6 @@ T __cos T __cosdf T __fputwc_unlocked - T __intscan W __iswctype_l T __lgamma_r T __lgammaf_r @@ -46,6 +45,27 @@ T ilogbf T ilogbl T index + T isascii + T islower + T islower_l + T isupper_l + T isalpha + T isalpha_l + T isblank_l + T isdigit_l + T isxdigit + T isxdigit_l + T isalnum + T isalnum_l + T ispunct + T ispunct_l + T isspace_l + T iscntrl + T iscntrl_l + T isprint + T isprint_l + T isgraph + T isgraph_l T iswalnum T iswalnum_l T iswalpha @@ -122,6 +142,7 @@ T strfmon_l T strlcat T strlcpy + T strlwr T strncasecmp_l T strncat T strndup @@ -133,6 +154,11 @@ T strstr T strtok T strtok_r + T strtoull_l + T strtoll_l + T strtoul_l + T strtol_l + T strupr T strverscmp T strxfrm W strxfrm_l @@ -140,6 +166,10 @@ T tgamma T tgammaf T tgammal + T toascii + T toupper + T toupper_l + T tolower_l T towctrans T towctrans_l T towlower diff --git a/tests/cases/floatundefinvoke_fastcomp.ll b/tests/cases/floatundefinvoke_fastcomp.ll new file mode 100644 index 00000000..215506ef --- /dev/null +++ b/tests/cases/floatundefinvoke_fastcomp.ll @@ -0,0 +1,30 @@ +; ModuleID = 'a.o' +target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128" +target triple = "asmjs-unknown-emscripten" + +@.str = private unnamed_addr constant [11 x i8] c"float: %f\0A\00", align 1 + +define void @_Z10printFloatf(float %f) #0 { +entry: + %conv = fpext float %f to double + %call = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([11 x i8]* @.str, i32 0, i32 0), double %conv) + ret void +} + +define i32 @main() #1 { +entry: + tail call void @_Z10printFloatf(float 1.000000e+00) + call void @emscripten_preinvoke() + call void @_Z10printFloatf(float undef) + %last = call i32 @emscripten_postinvoke() + %lastf = sitofp i32 %last to float + tail call void @_Z10printFloatf(float %lastf) + ret i32 1 +} + +declare void @emscripten_preinvoke() +declare i32 @emscripten_postinvoke() +declare i32 @printf(i8* nocapture, ...) #1 + +attributes #0 = { noinline nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } diff --git a/tests/cases/floatundefinvoke_fastcomp.txt b/tests/cases/floatundefinvoke_fastcomp.txt new file mode 100644 index 00000000..5e19391e --- /dev/null +++ b/tests/cases/floatundefinvoke_fastcomp.txt @@ -0,0 +1,3 @@ +float: 1.000000 +float: 0.000000 +float: 0.000000 diff --git a/tests/cases/i1tof_ta2.ll b/tests/cases/i1tof_ta2.ll new file mode 100644 index 00000000..12940907 --- /dev/null +++ b/tests/cases/i1tof_ta2.ll @@ -0,0 +1,71 @@ +; ModuleID = 'bad/emcc-0-basebc.bc' +target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128" +target triple = "asmjs-unknown-emscripten" + +@.str = private unnamed_addr constant [7 x i8] c"%0.1f\0A\00", align 1 +@.str2 = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 + +; Function Attrs: noinline +define float @_Z2v1v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FECCCCCC0000000, float 0.000000e+00 + ret float %cond +} + +define double @emscripten_get_now() #0 { + ret double 1.0 +} + +; Function Attrs: noinline +define float @_Z2v2v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FD99999A0000000, float 0.000000e+00 + ret float %cond +} + +; Function Attrs: noinline +define float @_Z2v3v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FB99999A0000000, float 0.000000e+00 + ret float %cond +} + +define i32 @main() #1 { +entry: + %call = tail call float @_Z2v1v() + %call1 = tail call float @_Z2v2v() + %call2 = tail call float @_Z2v3v() + %sub = fsub float %call1, %call + %cmp.i = fcmp ogt float %sub, 0.000000e+00 + br i1 %cmp.i, label %_ZL5signff.exit, label %cond.false.i + +cond.false.i: ; preds = %entry + %cmp1.i = fcmp olt float %sub, 0.000000e+00 + %phitmp = sitofp i1 %cmp1.i to float + %phitmpd = fpext float %phitmp to double + %call1115a = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), double %phitmpd) + %phitmpi = sext i1 %cmp1.i to i32 + %call1115b = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str2, i32 0, i32 0), i32 %phitmpi) + br label %_ZL5signff.exit + +_ZL5signff.exit: ; preds = %cond.false.i, %entry + %cond2.i = phi float [ %phitmp, %cond.false.i ], [ 1.000000e+00, %entry ] + %mul = fmul float %call2, %cond2.i + %add = fadd float %call, %mul + %conv4 = fpext float %add to double + %call5 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), double %conv4) + ret i32 0 +} + +; Function Attrs: nounwind +declare i32 @printf(i8* nocapture, ...) #2 + +attributes #0 = { noinline "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #2 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } diff --git a/tests/cases/i1tof_ta2.txt b/tests/cases/i1tof_ta2.txt new file mode 100644 index 00000000..5d3943cd --- /dev/null +++ b/tests/cases/i1tof_ta2.txt @@ -0,0 +1,3 @@ +-1.0 +-1 +0.8 diff --git a/tests/core/test_double_varargs.c b/tests/core/test_double_varargs.c new file mode 100644 index 00000000..d3eb3eed --- /dev/null +++ b/tests/core/test_double_varargs.c @@ -0,0 +1,34 @@ +#include <stdio.h> +#include <stdarg.h> + +double func_int_double_1(int unused1, ...) +{ + int i; + double d; + va_list vl; + va_start(vl, unused1); + i = va_arg(vl, int); + d = va_arg(vl, double); + va_end(vl); + return i+d; +} + +double func_int_double_2(int unused1, int unused2, ...) +{ + int i; + double d; + va_list vl; + va_start(vl, unused2); + i = va_arg(vl, int); + d = va_arg(vl, double); + va_end(vl); + return i+d; +} + +int main() { + double ret = func_int_double_1(0, 5, 10.0); + printf("%f\n", ret); // Expects to print 15 + ret = func_int_double_2(0, 0, 5, 10.0); + printf("%f\n", ret); // Expects to print 15 +} + diff --git a/tests/core/test_double_varargs.out b/tests/core/test_double_varargs.out new file mode 100644 index 00000000..c907dece --- /dev/null +++ b/tests/core/test_double_varargs.out @@ -0,0 +1,2 @@ +15.000000 +15.000000 diff --git a/tests/core/test_exceptions_white_list_empty.out b/tests/core/test_exceptions_white_list_empty.out new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/core/test_exceptions_white_list_empty.out diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 3ded811a..a6b2e98c 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -1548,7 +1548,7 @@ module({ }); }); - BaseFixture.extend("abstract methods", function() { + BaseFixture.extend("implementing abstract methods with JS objects", function() { test("can call abstract methods", function() { var obj = cm.getAbstractClass(); assert.equal("from concrete", obj.abstractMethod()); @@ -1580,7 +1580,8 @@ module({ }; var impl = cm.AbstractClass.implement(new MyImplementation); - assert.equal(expected, impl.optionalMethod(expected)); + // TODO: remove .implement() as a public API. It interacts poorly with Class.extend. + //assert.equal(expected, impl.optionalMethod(expected)); assert.equal(expected, cm.callOptionalMethod(impl, expected)); impl.delete(); }); @@ -1588,7 +1589,8 @@ module({ test("if not implemented then optional method runs default", function() { var impl = cm.AbstractClass.implement({}); assert.equal("optionalfoo", impl.optionalMethod("foo")); - assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); + // TODO: remove .implement() as a public API. It interacts poorly with Class.extend. + //assert.equal("optionalfoo", cm.callOptionalMethod(impl, "foo")); impl.delete(); }); @@ -1638,6 +1640,366 @@ module({ impl.delete(); }); + + test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { + var derived = cm.embind_test_return_smart_derived_ptr(); + var impl = cm.AbstractClass.implement({ + returnsSharedPtr: function() { + return derived; + } + }); + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + derived.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + }); + + BaseFixture.extend("constructor prototype class inheritance", function() { + var Empty = cm.AbstractClass.extend("Empty", { + abstractMethod: function() { + } + }); + + test("can extend, construct, and delete", function() { + var instance = new Empty; + instance.delete(); + }); + + test("properties set in constructor are externally visible", function() { + var HasProperty = cm.AbstractClass.extend("HasProperty", { + __construct: function(x) { + this.__parent.__construct.call(this); + this.property = x; + }, + abstractMethod: function() { + } + }); + var instance = new HasProperty(10); + assert.equal(10, instance.property); + instance.delete(); + }); + + test("pass derived object to c++", function() { + var Implementation = cm.AbstractClass.extend("Implementation", { + abstractMethod: function() { + return "abc"; + }, + }); + var instance = new Implementation; + var result = cm.callAbstractMethod(instance); + instance.delete(); + assert.equal("abc", result); + }); + + test("properties set in constructor are visible in overridden methods", function() { + var HasProperty = cm.AbstractClass.extend("HasProperty", { + __construct: function(x) { + this.__parent.__construct.call(this); + this.x = x; + }, + abstractMethod: function() { + return this.x; + }, + }); + var instance = new HasProperty("xyz"); + var result = cm.callAbstractMethod(instance); + instance.delete(); + assert.equal("xyz", result); + }); + + test("interface methods are externally visible", function() { + var instance = new Empty; + var result = instance.concreteMethod(); + instance.delete(); + assert.equal("concrete", result); + }); + + test("optional methods are externally visible", function() { + var instance = new Empty; + var result = instance.optionalMethod("_123"); + instance.delete(); + assert.equal("optional_123", result); + }); + + test("optional methods: not defined", function() { + var instance = new Empty; + var result = cm.callOptionalMethod(instance, "_123"); + instance.delete(); + assert.equal("optional_123", result); + }); + + // Calling C++ implementations of optional functions can be + // made to work, but requires an interface change on the C++ + // side, using a technique similar to the one described at + // https://wiki.python.org/moin/boost.python/OverridableVirtualFunctions + // + // The issue is that, in a standard binding, calling + // parent.prototype.optionalMethod invokes the wrapper + // function, which checks that the JS object implements + // 'optionalMethod', which it does. Thus, C++ calls back into + // JS, resulting in an infinite loop. + // + // The solution, for optional methods, is to bind a special + // concrete implementation that specifically calls the base + // class's implementation. See the binding of + // AbstractClass::optionalMethod in embind_test.cpp. + + test("can call parent implementation from within derived implementation", function() { + var parent = cm.AbstractClass; + var ExtendsOptionalMethod = parent.extend("ExtendsOptionalMethod", { + abstractMethod: function() { + }, + optionalMethod: function(s) { + return "optionaljs_" + parent.prototype.optionalMethod.call(this, s); + }, + }); + var instance = new ExtendsOptionalMethod; + var result = cm.callOptionalMethod(instance, "_123"); + instance.delete(); + assert.equal("optionaljs_optional_123", result); + }); + + // TODO: deriving from classes with constructors? + + test("instanceof", function() { + var instance = new Empty; + assert.instanceof(instance, Empty); + assert.instanceof(instance, cm.AbstractClass); + instance.delete(); + }); + + test("returning null shared pointer from interfaces implemented in JS code does not leak", function() { + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return null; + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("returning a new shared pointer from interfaces implemented in JS code does not leak", function() { + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return cm.embind_test_return_smart_derived_ptr().deleteLater(); + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("void methods work", function() { + var saved = {}; + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + differentArguments: function(i, d, f, q, s) { + saved.i = i; + saved.d = d; + saved.f = f; + saved.q = q; + saved.s = s; + } + }); + var impl = new C; + + cm.callDifferentArguments(impl, 1, 2, 3, 4, "foo"); + + assert.deepEqual(saved, { + i: 1, + d: 2, + f: 3, + q: 4, + s: "foo", + }); + + impl.delete(); + }); + + test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { + var derived = cm.embind_test_return_smart_derived_ptr(); + var C = cm.AbstractClass.extend("C", { + abstractMethod: function() { + }, + returnsSharedPtr: function() { + return derived; + } + }); + var impl = new C; + cm.callReturnsSharedPtrMethod(impl); + impl.delete(); + derived.delete(); + // Let the memory leak test superfixture check that no leaks occurred. + }); + + test("calling pure virtual function gives good error message", function() { + var C = cm.AbstractClass.extend("C", {}); + var error = assert.throws(cm.PureVirtualError, function() { + new C; + }); + assert.equal('Pure virtual function abstractMethod must be implemented in JavaScript', error.message); + }); + + test("can extend from C++ class with constructor arguments", function() { + var parent = cm.AbstractClassWithConstructor; + var C = parent.extend("C", { + __construct: function(x) { + this.__parent.__construct.call(this, x); + }, + abstractMethod: function() { + return this.concreteMethod(); + } + }); + + var impl = new C("hi"); + var rv = cm.callAbstractMethod2(impl); + impl.delete(); + + assert.equal("hi", rv); + }); + + test("__destruct is called when object is destroyed", function() { + var parent = cm.HeldAbstractClass; + var calls = []; + var C = parent.extend("C", { + method: function() { + }, + __destruct: function() { + calls.push("__destruct"); + this.__parent.__destruct.call(this); + } + }); + var impl = new C; + var copy = impl.clone(); + impl.delete(); + assert.deepEqual([], calls); + copy.delete(); + assert.deepEqual(["__destruct"], calls); + }); + + test("if JavaScript implementation of interface is returned, don't wrap in new handle", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.delete(); + assert.equal(impl, rv); + rv.delete(); + }); + + test("can instantiate two wrappers with constructors", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + __construct: function() { + this.__parent.__construct.call(this); + }, + method: function() { + } + }); + var a = new C; + var b = new C; + a.delete(); + b.delete(); + }); + + test("incorrectly calling parent is an error", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + __construct: function() { + this.__parent.__construct(); + }, + method: function() { + } + }); + assert.throws(cm.BindingError, function() { + new C; + }); + }); + + test("deleteLater() works for JavaScript implementations", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.deleteLater(); + rv.deleteLater(); + cm.flushPendingDeletes(); + }); + + test("deleteLater() combined with delete() works for JavaScript implementations", function() { + var parent = cm.HeldAbstractClass; + var C = parent.extend("C", { + method: function() { + } + }); + var impl = new C; + var rv = cm.passHeldAbstractClass(impl); + impl.deleteLater(); + rv.delete(); + cm.flushPendingDeletes(); + }); + + test("method arguments with pointer ownership semantics are cleaned up after call", function() { + var parent = cm.AbstractClass; + var C = parent.extend("C", { + abstractMethod: function() { + }, + }); + var impl = new C; + cm.passShared(impl); + impl.delete(); + }); + + test("method arguments with pointer ownership semantics can be cloned", function() { + var parent = cm.AbstractClass; + var owned; + var C = parent.extend("C", { + abstractMethod: function() { + }, + passShared: function(p) { + owned = p.clone(); + } + }); + var impl = new C; + cm.passShared(impl); + impl.delete(); + + assert.equal("Derived", owned.getClassName()); + owned.delete(); + }); + + test("emscripten::val method arguments don't leak", function() { + var parent = cm.AbstractClass; + var got; + var C = parent.extend("C", { + abstractMethod: function() { + }, + passVal: function(g) { + got = g; + } + }); + var impl = new C; + var v = {}; + cm.passVal(impl, v); + impl.delete(); + + assert.equal(v, got); + }); }); BaseFixture.extend("registration order", function() { @@ -1945,19 +2307,6 @@ module({ }); }); - test("returning a cached new shared pointer from interfaces implemented in JS code does not leak", function() { - var derived = cm.embind_test_return_smart_derived_ptr(); - var impl = cm.AbstractClass.implement({ - returnsSharedPtr: function() { - return derived; - } - }); - cm.callReturnsSharedPtrMethod(impl); - impl.delete(); - derived.delete(); - // Let the memory leak test superfixture check that no leaks occurred. - }); - BaseFixture.extend("val::as", function() { test("built-ins", function() { assert.equal(true, cm.val_as_bool(true)); @@ -2030,6 +2379,49 @@ module({ assert.equal(65538, instance.c); }); }); + + BaseFixture.extend("intrusive pointers", function() { + test("can pass intrusive pointers", function() { + var ic = new cm.IntrusiveClass; + var d = cm.passThroughIntrusiveClass(ic); + assert.true(ic.isAliasOf(d)); + ic.delete(); + d.delete(); + }); + + test("can hold intrusive pointers", function() { + var ic = new cm.IntrusiveClass; + var holder = new cm.IntrusiveClassHolder; + holder.set(ic); + ic.delete(); + var d = holder.get(); + d.delete(); + holder.delete(); + }); + + test("can extend from intrusive pointer class and still preserve reference in JavaScript", function() { + var C = cm.IntrusiveClass.extend("C", { + }); + var instance = new C; + var holder = new cm.IntrusiveClassHolder; + holder.set(instance); + instance.delete(); + + var back = holder.get(); + assert.equal(back, instance); + holder.delete(); + }); + }); + + BaseFixture.extend("typeof", function() { + test("typeof", function() { + assert.equal("object", cm.getTypeOfVal(null)); + assert.equal("object", cm.getTypeOfVal({})); + assert.equal("function", cm.getTypeOfVal(function(){})); + assert.equal("number", cm.getTypeOfVal(1)); + assert.equal("string", cm.getTypeOfVal("hi")); + }); + }); }); /* global run_all_tests */ diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 5a83903a..30267994 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -1090,6 +1090,16 @@ public: virtual std::shared_ptr<Derived> returnsSharedPtr() = 0; virtual void differentArguments(int i, double d, unsigned char f, double q, std::string) = 0; + + std::string concreteMethod() const { + return "concrete"; + } + + virtual void passShared(const std::shared_ptr<Derived>&) { + } + + virtual void passVal(const val& v) { + } }; EMSCRIPTEN_SYMBOL(optionalMethod); @@ -1103,9 +1113,10 @@ public: } std::string optionalMethod(std::string s) const { - return optional_call<std::string>(optionalMethod_symbol, [&] { - return AbstractClass::optionalMethod(s); - }, s); + return call<std::string>("optionalMethod", s); + //return optional_call<std::string>(optionalMethod_symbol, [&] { + // return AbstractClass::optionalMethod(s); + //}, s); } std::shared_ptr<Derived> returnsSharedPtr() { @@ -1115,6 +1126,14 @@ public: void differentArguments(int i, double d, unsigned char f, double q, std::string s) { return call<void>("differentArguments", i, d, f, q, s); } + + virtual void passShared(const std::shared_ptr<Derived>& p) override { + return call<void>("passShared", p); + } + + virtual void passVal(const val& v) override { + return call<void>("passVal", v); + } }; class ConcreteClass : public AbstractClass { @@ -1122,7 +1141,6 @@ class ConcreteClass : public AbstractClass { return "from concrete"; } - void differentArguments(int i, double d, unsigned char f, double q, std::string s) { } @@ -1152,12 +1170,74 @@ void callDifferentArguments(AbstractClass& ac, int i, double d, unsigned char f, return ac.differentArguments(i, d, f, q, s); } +struct AbstractClassWithConstructor { + explicit AbstractClassWithConstructor(std::string s) + : s(s) + {} + + virtual std::string abstractMethod() = 0; + std::string concreteMethod() { + return s; + } + + std::string s; +}; + +struct AbstractClassWithConstructorWrapper : public wrapper<AbstractClassWithConstructor> { + EMSCRIPTEN_WRAPPER(AbstractClassWithConstructorWrapper); + + virtual std::string abstractMethod() override { + return call<std::string>("abstractMethod"); + } +}; + +std::string callAbstractMethod2(AbstractClassWithConstructor& ac) { + return ac.abstractMethod(); +} + +struct HeldAbstractClass : public PolyBase, public PolySecondBase { + virtual void method() = 0; +}; +struct HeldAbstractClassWrapper : wrapper<HeldAbstractClass> { + EMSCRIPTEN_WRAPPER(HeldAbstractClassWrapper); + + virtual void method() override { + return call<void>("method"); + } +}; + +std::shared_ptr<PolySecondBase> passHeldAbstractClass(std::shared_ptr<HeldAbstractClass> p) { + return p; +} + +void passShared(AbstractClass& ac) { + auto p = std::make_shared<Derived>(); + ac.passShared(p); +} + +void passVal(AbstractClass& ac, val v) { + return ac.passVal(v); +} + EMSCRIPTEN_BINDINGS(interface_tests) { class_<AbstractClass>("AbstractClass") .smart_ptr<std::shared_ptr<AbstractClass>>("shared_ptr<AbstractClass>") .allow_subclass<AbstractClassWrapper>("AbstractClassWrapper") - .function("abstractMethod", &AbstractClass::abstractMethod) - .function("optionalMethod", &AbstractClass::optionalMethod) + .function("abstractMethod", &AbstractClass::abstractMethod, pure_virtual()) + // The select_overload is necessary because, otherwise, the C++ compiler + // cannot deduce the signature of the lambda function. + .function("optionalMethod", optional_override( + [](AbstractClass& this_, std::string s) { + return this_.AbstractClass::optionalMethod(s); + } + )) + .function("concreteMethod", &AbstractClass::concreteMethod) + .function("passShared", select_overload<void(AbstractClass&, const std::shared_ptr<Derived>&)>([](AbstractClass& self, const std::shared_ptr<Derived>& derived) { + self.AbstractClass::passShared(derived); + })) + .function("passVal", select_overload<void(AbstractClass&, const val&)>([](AbstractClass& self, const val& v) { + self.AbstractClass::passVal(v); + })) ; function("getAbstractClass", &getAbstractClass); @@ -1165,6 +1245,22 @@ EMSCRIPTEN_BINDINGS(interface_tests) { function("callOptionalMethod", &callOptionalMethod); function("callReturnsSharedPtrMethod", &callReturnsSharedPtrMethod); function("callDifferentArguments", &callDifferentArguments); + function("passShared", &passShared); + function("passVal", &passVal); + + class_<AbstractClassWithConstructor>("AbstractClassWithConstructor") + .allow_subclass<AbstractClassWithConstructorWrapper>("AbstractClassWithConstructorWrapper", constructor<std::string>()) + .function("abstractMethod", &AbstractClassWithConstructor::abstractMethod, pure_virtual()) + .function("concreteMethod", &AbstractClassWithConstructor::concreteMethod) + ; + function("callAbstractMethod2", &callAbstractMethod2); + + class_<HeldAbstractClass, base<PolySecondBase>>("HeldAbstractClass") + .smart_ptr<std::shared_ptr<HeldAbstractClass>>("shared_ptr<HeldAbstractClass>") + .allow_subclass<HeldAbstractClassWrapper, std::shared_ptr<HeldAbstractClassWrapper>>("HeldAbstractClassWrapper") + .function("method", &HeldAbstractClass::method, pure_virtual()) + ; + function("passHeldAbstractClass", &passHeldAbstractClass); } template<typename T, size_t sizeOfArray> @@ -2368,3 +2464,180 @@ EMSCRIPTEN_BINDINGS(val_new_) { function("construct_with_memory_view", &construct_with_memory_view); function("construct_with_ints_and_float", &construct_with_ints_and_float); } + +template <typename T> +class intrusive_ptr { +public: + typedef T element_type; + + intrusive_ptr(std::nullptr_t = nullptr) + : px(nullptr) + {} + + template <typename U> + explicit intrusive_ptr(U* px) + : px(px) + { + addRef(px); + } + + intrusive_ptr(const intrusive_ptr& that) + : px(that.px) + { + addRef(px); + } + + template<typename U> + intrusive_ptr(const intrusive_ptr<U>& that) + : px(that.get()) + { + addRef(px); + } + + intrusive_ptr& operator=(const intrusive_ptr& that) { + reset(that.get()); + return *this; + } + + intrusive_ptr& operator=(intrusive_ptr&& that) { + release(px); + px = that.px; + that.px = 0; + return *this; + } + + template<typename U> + intrusive_ptr& operator=(const intrusive_ptr<U>& that) { + reset(that.get()); + return *this; + } + + template<typename U> + intrusive_ptr& operator=(intrusive_ptr<U>&& that) { + release(px); + px = that.px; + that.px = 0; + return *this; + } + + ~intrusive_ptr() { + release(px); + } + + void reset(T* nx = nullptr) { + addRef(nx); + release(px); + px = nx; + } + + T* get() const { + return px; + } + + T& operator*() const { + return *px; + } + + T* operator->() const { + return px; + } + + explicit operator bool() const { + return px != nullptr; + } + + void swap(intrusive_ptr& rhs) { + std::swap(px, rhs.px); + } + +private: + void addRef(T* px) { + if (px) { + ++px->referenceCount; + } + } + + void release(T* px) { + if (--px->referenceCount == 0) { + delete px; + } + } + + T* px; + + template<typename U> + friend class intrusive_ptr; +}; + +namespace emscripten { + template<typename T> + struct smart_ptr_trait<intrusive_ptr<T>> { + typedef intrusive_ptr<T> pointer_type; + typedef T element_type; + + static sharing_policy get_sharing_policy() { + return sharing_policy::INTRUSIVE; + } + + static T* get(const intrusive_ptr<T>& p) { + return p.get(); + } + + static intrusive_ptr<T> share(const intrusive_ptr<T>& r, T* ptr) { + return intrusive_ptr<T>(ptr); + } + + static pointer_type* construct_null() { + return new pointer_type; + } + }; +} + +template<typename T> +intrusive_ptr<T> make_intrusive_ptr() { + return intrusive_ptr<T>(new T); +} + +struct IntrusiveClass { + virtual ~IntrusiveClass() {} + long referenceCount = 0; +}; + +struct IntrusiveClassWrapper : public wrapper<IntrusiveClass> { + EMSCRIPTEN_WRAPPER(IntrusiveClassWrapper); +}; + +template<typename T> +struct Holder { + void set(const T& v) { + value = v; + } + const T& get() const { + return value; + } + T value; +}; + +EMSCRIPTEN_BINDINGS(intrusive_pointers) { + class_<IntrusiveClass>("IntrusiveClass") + .smart_ptr_constructor("intrusive_ptr<IntrusiveClass>", &make_intrusive_ptr<IntrusiveClass>) + .allow_subclass<IntrusiveClassWrapper, intrusive_ptr<IntrusiveClassWrapper>>("IntrusiveClassWrapper") + ; + + typedef Holder<intrusive_ptr<IntrusiveClass>> IntrusiveClassHolder; + class_<IntrusiveClassHolder>("IntrusiveClassHolder") + .constructor<>() + .function("set", &IntrusiveClassHolder::set) + .function("get", &IntrusiveClassHolder::get) + ; + + function("passThroughIntrusiveClass", &passThrough<intrusive_ptr<IntrusiveClass>>); +} + +std::string getTypeOfVal(const val& v) { + return v.typeof().as<std::string>(); +} + +EMSCRIPTEN_BINDINGS(typeof) { + function("getTypeOfVal", &getTypeOfVal); +} diff --git a/tests/emscripten_fs_api_browser.cpp b/tests/emscripten_fs_api_browser.cpp index 0355287a..1410ba3c 100644 --- a/tests/emscripten_fs_api_browser.cpp +++ b/tests/emscripten_fs_api_browser.cpp @@ -107,11 +107,14 @@ int main() { onLoaded, onError); + char name[40]; + strcpy(name, "/tmp/screen_shot.png"); // test for issue #2349, name being free'd emscripten_async_wget( "http://localhost:8888/screenshot.png", - "/tmp/screen_shot.png", + name, onLoaded, onError); + memset(name, 0, 30); emscripten_set_main_loop(wait_wgets, 0, 0); diff --git a/tests/life.c b/tests/life.c index 4ce8d385..263fa0e6 100644 --- a/tests/life.c +++ b/tests/life.c @@ -67,7 +67,9 @@ void game(int w, int h, int i) i--; nudge(univ, w, h); // keep it interesting for benchmark } else { +#if !__EMSCRIPTEN__ usleep(20000); +#endif show(univ, w, h); } } diff --git a/tests/sdl_audio_mix.c b/tests/sdl_audio_mix.c index a1c0485d..422fc122 100644 --- a/tests/sdl_audio_mix.c +++ b/tests/sdl_audio_mix.c @@ -58,7 +58,9 @@ void one_iter() { Mix_HaltChannel(soundChannel); Mix_HaltMusic(); int result = 1; +#ifdef REPORT_RESULT REPORT_RESULT(); +#endif break; }; } diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 735f0feb..16abbfdd 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -112,6 +112,7 @@ process(sys.argv[1]) '--memory-init-file', '0', '--js-transform', 'python hardcode.py', '-s', 'TOTAL_MEMORY=128*1024*1024', #'-profiling', + #'--closure', '1', '-o', final] + shared_args + emcc_args + self.extra_args, stdout=PIPE, stderr=PIPE, env=self.env).communicate() assert os.path.exists(final), 'Failed to compile file: ' + output[0] self.filename = final @@ -407,6 +408,42 @@ class benchmark(RunnerCore): ''' self.do_benchmark('ifs', src, 'ok', reps=TEST_REPS*5) + def test_conditionals(self): + src = r''' + #include <stdio.h> + #include <stdlib.h> + + int main(int argc, char *argv[]) { + int arg = argc > 1 ? argv[1][0] - '0' : 3; + switch(arg) { + case 0: return 0; break; + case 1: arg = 3*75; break; + case 2: arg = 3*625; break; + case 3: arg = 3*1250; break; + case 4: arg = 3*5*1250; break; + case 5: arg = 3*10*1250; break; + default: printf("error: %d\\n", arg); return -1; + } + + int x = 0; + + for (int j = 0; j < 27000; j++) { + for (int i = 0; i < arg; i++) { + if (((x*x+11) % 3 == 0) | ((x*(x+2)+17) % 5 == 0)) { + x += 2; + } else { + x++; + } + } + } + + printf("ok %d\n", x); + + return x; + } + ''' + self.do_benchmark('conditionals', src, 'ok', reps=TEST_REPS*5) + def test_fannkuch(self): src = open(path_from_root('tests', 'fannkuch.cpp'), 'r').read().replace( 'int n = argc > 1 ? atoi(argv[1]) : 0;', diff --git a/tests/test_core.py b/tests/test_core.py index 6a920a7b..62a061e2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -425,6 +425,11 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output, 'waka fleefl asdfasdfasdfasdf'.split(' ')) + def test_double_varargs(self): + test_path = path_from_root('tests', 'core', 'test_double_varargs') + src, output = (test_path + s for s in ('.c', '.out')) + self.do_run_from_file(src, output) + def test_i32_mul_precise(self): if self.emcc_args == None: return self.skip('needs ta2') @@ -966,6 +971,7 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output) def test_strings(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('musl libc needs ta2') test_path = path_from_root('tests', 'core', 'test_strings') src, output = (test_path + s for s in ('.in', '.out')) @@ -1160,7 +1166,6 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output) def test_longjmp_repeat(self): - if os.environ.get('EMCC_FAST_COMPILER') == '0': Settings.MAX_SETJMPS = 1 # todo: do this more strict thing in fastcomp too test_path = path_from_root('tests', 'core', 'test_longjmp_repeat') src, output = (test_path + s for s in ('.in', '.out')) self.do_run_from_file(src, output) @@ -1184,8 +1189,6 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co self.do_run_from_file(src, output) def test_setjmp_many(self): - if os.environ.get('EMCC_FAST_COMPILER') != '0': return self.skip('todo in fastcomp: make MAX_SETJMPS take effect') - src = r''' #include <stdio.h> #include <setjmp.h> @@ -1197,9 +1200,42 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co return 0; } ''' - for num in [Settings.MAX_SETJMPS, Settings.MAX_SETJMPS+1]: - print num - self.do_run(src.replace('NUM', str(num)), '0\n' * num if num <= Settings.MAX_SETJMPS or not Settings.ASM_JS else 'build with a higher value for MAX_SETJMPS') + for maxx in [Settings.MAX_SETJMPS/2, Settings.MAX_SETJMPS, 2*Settings.MAX_SETJMPS]: + Settings.MAX_SETJMPS = maxx + for num in [maxx, maxx+1]: + print maxx, num + self.do_run(src.replace('NUM', str(num)), '0\n' * num if num <= Settings.MAX_SETJMPS or not Settings.ASM_JS else 'build with a higher value for MAX_SETJMPS') + + def test_setjmp_many_2(self): + if os.environ.get('EMCC_FAST_COMPILER') == '0': return self.skip('non-fastcomp do not hit the limit.') + + src = r''' +#include <setjmp.h> +#include <stdio.h> + +jmp_buf env; + +void luaWork(int d){ + int x; + printf("d is at %d\n", d); + + longjmp(env, 1); +} + +int main() +{ + const int ITERATIONS=25; + for(int i = 0; i < ITERATIONS; i++){ + if(!setjmp(env)){ + luaWork(i); + } + } + return 0; +} +''' + + self.do_run(src, r'''d is at 19 +too many setjmps in a function call, build with a higher value for MAX_SETJMPS''') def test_exceptions(self): if Settings.QUANTUM_SIZE == 1: return self.skip("we don't support libcxx in q1") @@ -1311,6 +1347,33 @@ class T(RunnerCore): # Short name, to make it more fun to use manually on the co test_path = path_from_root('tests', 'core', 'test_exceptions_white_list') src, output = (test_path + s for s in ('.in', '.out')) self.do_run_from_file(src, output) + size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'orig.js') + + if os.environ.get('EMCC_FAST_COMPILER') != '0': + # check that an empty whitelist works properly (as in, same as exceptions disabled) + empty_output = path_from_root('tests', 'core', 'test_exceptions_white_list_empty.out') + + Settings.EXCEPTION_CATCHING_WHITELIST = [] + self.do_run_from_file(src, empty_output) + empty_size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'empty.js') + + Settings.EXCEPTION_CATCHING_WHITELIST = ['fake'] + self.do_run_from_file(src, empty_output) + fake_size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'fake.js') + + Settings.DISABLE_EXCEPTION_CATCHING = 1 + self.do_run_from_file(src, empty_output) + disabled_size = len(open('src.cpp.o.js').read()) + shutil.copyfile('src.cpp.o.js', 'disabled.js') + + assert size - empty_size > 2000, [empty_size, size] # big change when we disable entirely + assert size - fake_size > 2000, [fake_size, size] + assert empty_size == fake_size, [empty_size, fake_size] + assert empty_size - disabled_size < 100, [empty_size, disabled_size] # full disable removes a tiny bit more + assert fake_size - disabled_size < 100, [disabled_size, fake_size] def test_exceptions_white_list_2(self): Settings.DISABLE_EXCEPTION_CATCHING = 2 @@ -2032,10 +2095,10 @@ def process(filename): if self.emcc_args and '-O2' in self.emcc_args: # Make sure ALLOW_MEMORY_GROWTH generates different code (should be less optimized) - code_start = 'var TOTAL_MEMORY = ' + code_start = 'var TOTAL_MEMORY' fail = fail[fail.find(code_start):] win = win[win.find(code_start):] - assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + assert len(fail) < len(win), 'failing code - without memory growth on - is more optimized, and smaller' + str([len(fail), len(win)]) def test_ssr(self): # struct self-ref src = ''' @@ -2461,6 +2524,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_hex(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for hex strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_hex') src, output = (test_path + s for s in ('.in', '.out')) @@ -2468,6 +2532,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_dec(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for decimal strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_dec') src, output = (test_path + s for s in ('.in', '.out')) @@ -2475,6 +2540,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_bin(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for binary strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_bin') src, output = (test_path + s for s in ('.in', '.out')) @@ -2482,6 +2548,7 @@ The current type of b is: 9 self.do_run_from_file(src, output) def test_strtol_oct(self): + if self.run_name.startswith('s_'): return self.skip('Needs musl libc.') # tests strtoll for decimal strings (0x...) test_path = path_from_root('tests', 'core', 'test_strtol_oct') src, output = (test_path + s for s in ('.in', '.out')) @@ -3204,7 +3271,7 @@ def process(filename): break else: raise Exception('Could not find symbol table!') - table = table[table.find('{'):table.rfind('}')+1] + table = table[table.find('{'):table.find('}')+1] # ensure there aren't too many globals; we don't want unnamed_addr assert table.count(',') <= 4 @@ -3713,6 +3780,7 @@ int main() self.do_run(src, expected) def test_transtrcase(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('musl libc needs ta2') test_path = path_from_root('tests', 'core', 'test_transtrcase') src, output = (test_path + s for s in ('.in', '.out')) @@ -4608,6 +4676,7 @@ int main(void) { ### 'Medium' tests def test_fannkuch(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('musl libc needs ta2') try: if self.run_name == 'slow2' or self.run_name == 'slow2asm': old_target = os.environ.get('EMCC_LLVM_TARGET') or '' @@ -4652,8 +4721,7 @@ int main(void) { if self.emcc_args is None: self.emcc_args = [] # dlmalloc auto-inclusion is only done if we use emcc self.banned_js_engines = [NODE_JS] # slower, and fail on 64-bit - Settings.CORRECT_SIGNS = 2 - Settings.CORRECT_SIGNS_LINES = ['src.cpp:' + str(i+4) for i in [4816, 4191, 4246, 4199, 4205, 4235, 4227]] + Settings.CORRECT_SIGNS = 1 Settings.TOTAL_MEMORY = 128*1024*1024 # needed with typed arrays src = open(path_from_root('system', 'lib', 'dlmalloc.c'), 'r').read() + '\n\n\n' + open(path_from_root('tests', 'dlmalloc_test.c'), 'r').read() @@ -5412,9 +5480,6 @@ def process(filename): ### Integration tests def test_ccall(self): - if self.emcc_args is not None and '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here, to test we export things right - post = ''' def process(filename): src = \'\'\' @@ -5458,6 +5523,11 @@ def process(filename): self.do_run_from_file(src, output, post_build=post) + if self.emcc_args is not None and '-O2' in self.emcc_args: + print 'with closure' + self.emcc_args += ['--closure', '1'] + self.do_run_from_file(src, output, post_build=post) + def test_pgo(self): if Settings.ASM_JS: return self.skip('PGO does not work in asm mode') diff --git a/tests/test_other.py b/tests/test_other.py index 349a16b4..03859a4e 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -203,6 +203,10 @@ Options that are modified or new in %s include: (['--typed-arrays', '1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), (['--typed-arrays', '2'], lambda generated: 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 selected'), (['--llvm-opts', '1'], lambda generated: '_puts(' in generated, 'llvm opts requested'), + ([], lambda generated: '// The Module object' in generated, 'without opts, comments in shell code'), + (['-O2'], lambda generated: '// The Module object' not in generated, 'with opts, no comments in shell code'), + (['-O2', '-g2'], lambda generated: '// The Module object' not in generated, 'with -g2, no comments in shell code'), + (['-O2', '-g3'], lambda generated: '// The Module object' in generated, 'with -g3, yes comments in shell code'), ]: print params, text self.clear() @@ -1027,10 +1031,93 @@ This pointer might make sense in another type signature: i: 0 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 <stdio.h> + 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 <stdio.h> + 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_list = [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_list, 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_list + ['-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)) + + # -( and -) should also work. + args = ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1', main, '-o', 'a2.out.js'] + libs = ['-Wl,-('] + libs_list + ['-Wl,-)'] + output = Popen([PYTHON, EMCC] + args + libs, stdout=PIPE, stderr=PIPE).communicate() + out_js = os.path.join(self.get_dir(), 'a2.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') @@ -2135,7 +2222,7 @@ void wakaw::Cm::RasterBase<wakaw::watwat::Polocator?>(unsigned int*, unsigned in test_js_closure_0 = open(path_from_root('tests', 'Module-exports', 'test.js')).read() # Check that test.js compiled with --closure 0 contains "module['exports'] = Module;" - assert "module['exports'] = Module;" in test_js_closure_0 + assert ("module['exports'] = Module;" in test_js_closure_0) or ('module["exports"]=Module' in test_js_closure_0) # Check that main.js (which requires test.js) completes successfully when run in node.js # in order to check that the exports are indeed functioning correctly. @@ -2791,3 +2878,14 @@ int main(int argc, char **argv) { assert sizes[0] == 7 # no aliasing, all unique, fat tables assert sizes[1] == 3 # aliased once more + def test_bad_export(self): + for m in ['', ' ']: + self.clear() + cmd = [PYTHON, EMCC, path_from_root('tests', 'hello_world.c'), '-s', 'EXPORTED_FUNCTIONS=["' + m + '_main"]'] + print cmd + stdout, stderr = Popen(cmd, stderr=PIPE).communicate() + if m: + assert 'function requested to be exported, but not implemented: " _main"' in stderr, stderr + else: + self.assertContained('hello, world!', run_js('a.out.js')) + diff --git a/tests/test_sanity.py b/tests/test_sanity.py index 3d3da523..3ebd49b6 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -240,7 +240,7 @@ class sanity(RunnerCore): os.chmod(path_from_root('tests', 'fake', 'bin', 'llc'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) os.chmod(path_from_root('tests', 'fake', 'bin', 'clang++'), stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) try_delete(SANITY_FILE) - output = self.check_working(EMCC, 'did not see a source tree above LLVM_DIR, could not verify version numbers match') + output = self.check_working(EMCC, 'did not see a source tree above the LLVM root directory') VERSION_WARNING = 'Emscripten, llvm and clang versions do not match, this is dangerous' diff --git a/tests/webidl/output.txt b/tests/webidl/output.txt index b874d928..c029935f 100644 --- a/tests/webidl/output.txt +++ b/tests/webidl/output.txt @@ -1,6 +1,7 @@ Parent:42 * 84 +object c1 Parent:7 Child1:7 @@ -10,6 +11,7 @@ Child1:7 588 14 28 +Child1::parentFunc(90) c1 v2 Parent:16 Child1:15 diff --git a/tests/webidl/post.js b/tests/webidl/post.js index 444efcd1..5376f27b 100644 --- a/tests/webidl/post.js +++ b/tests/webidl/post.js @@ -5,6 +5,8 @@ var sme = new Module.Parent(42); sme.mulVal(2); Module.print('*') Module.print(sme.getVal()); +sme.parentFunc(90); +Module.print(typeof sme.getAsConst()); Module.print('c1'); @@ -16,6 +18,7 @@ Module.print(c1.getValSqr()); Module.print(c1.getValSqr(3)); Module.print(c1.getValTimes()); // default argument should be 1 Module.print(c1.getValTimes(2)); +c1.parentFunc(90); Module.print('c1 v2'); diff --git a/tests/webidl/test.h b/tests/webidl/test.h index 903f8f78..5d13846c 100644 --- a/tests/webidl/test.h +++ b/tests/webidl/test.h @@ -10,6 +10,8 @@ public: Parent(Parent *p, Parent *q); // overload constructor int getVal() { return value; }; // inline should work just fine here, unlike Way 1 before void mulVal(int mul); + void parentFunc() {} + const Parent *getAsConst() { return NULL; } }; class Child1 : public Parent { @@ -19,6 +21,7 @@ public: int getValSqr() { return value*value; } int getValSqr(int more) { return value*value*more; } int getValTimes(int times=1) { return value*times; } + void parentFunc(int x) { printf("Child1::parentFunc(%d)\n", x); } }; // Child2 has vtable, parent does not. Checks we cast child->parent properly - (Parent*)child is not a no-op, must offset diff --git a/tests/webidl/test.idl b/tests/webidl/test.idl index 98ab5070..8ee82b76 100644 --- a/tests/webidl/test.idl +++ b/tests/webidl/test.idl @@ -5,12 +5,15 @@ interface Parent { void Parent(long val); long getVal(); void mulVal(long mul); + void parentFunc(); + [Const] Parent getAsConst(); }; interface Child1 { void Child1(optional long val); long getValSqr(optional long more); long getValTimes(optional long times=1); + void parentFunc(long x); // redefinition, name collides with parent }; Child1 implements Parent; diff --git a/third_party/WebIDL.py b/third_party/WebIDL.py index 867a7cbc..a94cb528 100644 --- a/third_party/WebIDL.py +++ b/third_party/WebIDL.py @@ -649,13 +649,15 @@ class IDLInterface(IDLObjectWithScope): # Flag the interface as being someone's consequential interface iface.setIsConsequentialInterfaceOf(self) additionalMembers = iface.originalMembers; - for additionalMember in additionalMembers: + for additionalMember in additionalMembers[:]: for member in self.members: if additionalMember.identifier.name == member.identifier.name: - raise WebIDLError( - "Multiple definitions of %s on %s coming from 'implements' statements" % - (member.identifier.name, self), - [additionalMember.location, member.location]) + # XXX emscripten: allow such name collisions, ignore parent + additionalMembers.remove(additionalMember) + #raise WebIDLError( + # "Multiple definitions of %s on %s coming from 'implements' statements" % + # (member.identifier.name, self), + # [additionalMember.location, member.location]) self.members.extend(additionalMembers) iface.interfacesImplementingSelf.add(self) @@ -3441,6 +3443,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): identifier == "Ref" or identifier == "Value" or identifier == "Operator" or + identifier == "Const" or identifier == "WebGLHandlesContextLoss"): # Known attributes that we don't need to do anything with here pass diff --git a/tools/bisect_pair_lines.py b/tools/bisect_pair_lines.py new file mode 100644 index 00000000..f698ef2a --- /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) + diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js index 1a5dca55..ab4c13cc 100644 --- a/tools/eliminator/asm-eliminator-test-output.js +++ b/tools/eliminator/asm-eliminator-test-output.js @@ -24,7 +24,7 @@ function __Z11printResultPiS_j($needle, $haystack, $len) { } function _segment_holding($addr) { $addr = $addr | 0; - var $sp_0 = 0, $3 = 0, $12 = 0, $_0 = 0, label = 0; + var $sp_0 = 0, $3 = 0, $_0 = 0, label = 0; $sp_0 = __gm_ + 444 | 0; while (1) { $3 = HEAP32[(($sp_0 | 0) & 16777215) >> 2] | 0; @@ -35,13 +35,11 @@ function _segment_holding($addr) { break; } } - $12 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0; - if (($12 | 0) == 0) { + $sp_0 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0; + if (($sp_0 | 0) == 0) { $_0 = 0; label = 1659; break; - } else { - $sp_0 = $12; } } if (label == 1659) { @@ -818,7 +816,7 @@ function selfAssign() { function elimOneLoopVar($argc, $argv) { $argc = $argc | 0; $argv = $argv | 0; - var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $inc = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0; + var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0; $curri$012 = 2; $primes$011 = 0; while (1) { @@ -827,14 +825,12 @@ function elimOneLoopVar($argc, $argv) { if ($call10 > Math_fround(+2)) { $j$010 = 2; while (1) { - $inc = $j$010 + 1 | 0; if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) { $ok$0 = 0; break L15; } - if (Math_fround($inc | 0) < $call10) { - $j$010 = $inc; - } else { + $j$010 = $j$010 + 1 | 0; + if (!(Math_fround($j$010 | 0) < $call10)) { $ok$0 = 1; break; } @@ -898,10 +894,23 @@ function elimOneLoopVar4() { } } function elimOneLoopVarStillUsed() { + var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0; + while (1) { + if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) { + break; + } + $j$010 = $j$010 + 1 | 0; + if (!(Math_fround($j$010 | 0) < $call10)) { + break; + } + } + return $retval$0 | 0; +} +function elimOneLoopVarStillUsedSE() { var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0, $j$010$looptemp = 0; while (1) { $j$010$looptemp = $j$010; - $j$010 = $j$010 + 1 | 0; + $j$010 = $j$010 + sideeffect() | 0; if ((($curri$012 | 0) % ($j$010$looptemp | 0) & -1 | 0) == 0) { break; } @@ -911,4 +920,52 @@ function elimOneLoopVarStillUsed() { } return $retval$0 | 0; } +function elimOneLoopVar5() { + var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0; + $storemerge3$neg9 = -1; + while (1) { + $25 = $jp + ($26 << 2) | 0; + HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0; + $30 = $26 + 1 | 0; + if (($30 | 0) == 63) { + f($30); + break; + } else { + $storemerge3$neg9 = $18 ^ -1; + $26 = $30; + } + } +} +function loopVarWithContinue() { + var i = 0, i$looptemp = 0; + i = 0; + while (1) { + i$looptemp = i; + i = i + 1; + if (check()) { + i = i$looptemp + 1; + continue; + } + work(i); + work(i$looptemp); + work(i); + if (check()) { + break; + } + } +} +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 d5bbd8d5..7b949c44 100644 --- a/tools/eliminator/asm-eliminator-test.js +++ b/tools/eliminator/asm-eliminator-test.js @@ -1152,5 +1152,76 @@ function elimOneLoopVarStillUsed() { } return $retval$0 | 0; } -// 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"] +function elimOneLoopVarStillUsedSE() { + var $0 = 0, $1 = 0, $arg$0 = 0, $arrayidx = 0, $call10 = Math_fround(0), $cmp = 0, $cmp11 = 0, $cmp119 = 0, $cmp12 = 0, $cmp7 = 0, $conv = 0, $conv8 = Math_fround(0), $conv9 = Math_fround(0), $curri$012 = 0, $inc = 0, $inc14$primes$0 = 0, $inc16 = 0, $j$010 = 0, $ok$0 = 0; + var $primes$011 = 0, $rem = 0, $retval$0 = 0, $sub = 0, $vararg_buffer1 = 0, label = 0, sp = 0; + while (1) { + $rem = ($curri$012 | 0) % ($j$010 | 0) & -1; + $cmp12 = ($rem | 0) == 0; + $inc = $j$010 + sideeffect() | 0; // side effect! + if ($cmp12) { + $ok$0 = 0; + break; + } + $conv8 = Math_fround($inc | 0); + $cmp11 = $conv8 < $call10; + if ($cmp11) { + $j$010 = $inc; + } else { + break; + } + } + return $retval$0 | 0; +} +function elimOneLoopVar5() { + var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0; + $storemerge3$neg9 = -1; + while (1) { + $25 = $jp + ($26 << 2) | 0; + HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0; + $30 = $26 + 1 | 0; + if (($30 | 0) == 63) { + f($30); // loop var used here, so cannot be easily optimized + break; + } else { + $storemerge3$neg9 = $18 ^ -1; + $26 = $30; + } + } +} +function loopVarWithContinue() { + var i = 0, inc = 0; + i = 0; + while (1) { + inc = i + 1; + if (check()) { + i = i + 1; + continue; + } + work(inc); + work(i); + work(inc); + if (check()) { + break; + } else { + i = inc; + } + } +} +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 2914b6e8..5b01dae0 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -136,6 +136,7 @@ var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch'); var NAME_OR_NUM = set('name', 'num'); var ASSOCIATIVE_BINARIES = set('+', '*', '|', '&', '^'); var ALTER_FLOW = set('break', 'continue', 'return'); +var BITWISE = set('|', '&', '^'); var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch'); var CONTINUE_CAPTURERS = LOOP; @@ -387,14 +388,18 @@ function simplifyExpressions(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; + } else if (type === 'binary' && (node[1] in BITWISE)) { + for (var i = 2; i <= 3; i++) { + var subNode = node[i]; + if (subNode[0] === 'binary' && subNode[1] === '&' && subNode[3][0] === 'num' && subNode[3][1] == 1) { + // Rewrite (X < Y) & 1 to X < Y , when it is going into a bitwise operator. We could + // remove even more (just replace &1 with |0, then subsequent passes could remove the |0) + // but v8 issue #2513 means the code would then run very slowly in chrome. + var input = subNode[2]; + if (input[0] === 'binary' && (input[1] in COMPARE_OPS)) { + node[i] = input; + } + } } } }); @@ -749,11 +754,74 @@ 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 emitsBoolean(node[2]) && emitsBoolean(node[3]); + return false; + } + + // expensive | expensive can be turned into expensive ? 1 : expensive, and + // expensive | cheap can be turned into cheap ? 1 : expensive, + // so that we can avoid the expensive computation, if it has no side effects. + function conditionalize(ast) { + var MIN_COST = 7; + traverse(ast, function(node, type) { + if (node[0] === 'binary' && (node[1] === '|' || node[1] === '&') && node[3][0] !== 'num' && node[2][0] !== 'num') { + // logical operator on two non-numerical values + var left = node[2]; + var right = node[3]; + if (!emitsBoolean(left) || !emitsBoolean(right)) return; + var leftEffects = hasSideEffects(left); + var rightEffects = hasSideEffects(right); + if (leftEffects && rightEffects) return; // both must execute + // canonicalize with side effects, if any, happening on the left + if (rightEffects) { + if (measureCost(left) < MIN_COST) return; // avoidable code is too cheap + var temp = left; + left = right; + right = temp; + } else if (leftEffects) { + if (measureCost(right) < MIN_COST) return; // avoidable code is too cheap + } else { + // no side effects, reorder based on cost estimation + var leftCost = measureCost(left); + var rightCost = measureCost(right); + if (Math.max(leftCost, rightCost) < MIN_COST) return; // avoidable code is too cheap + // canonicalize with expensive code on the right + if (leftCost > rightCost) { + var temp = left; + left = right; + right = temp; + } + } + // worth it, perform conditionalization + var ret; + if (node[1] === '|') { + ret = ['conditional', left, ['num', 1], right]; + } else { // & + ret = ['conditional', left, right, ['num', 0]]; + } + if (left[0] === 'unary-prefix' && left[1] === '!') { + ret[1] = flipCondition(left); + var temp = ret[2]; + ret[2] = ret[3]; + ret[3] = temp; + } + return ret; + } + }); + } + traverseGeneratedFunctions(ast, function(func) { simplifyIntegerConversions(func); simplifyBitops(func); joinAdditions(func); simplifyNotComps(func); + conditionalize(func); // simplifyZeroComp(func); TODO: investigate performance }); } @@ -970,10 +1038,32 @@ function hasSideEffects(node) { // this is 99% incomplete! } return false; } + case 'conditional': return hasSideEffects(node[1]) || hasSideEffects(node[2]) || hasSideEffects(node[3]); default: return true; } } +// checks if a node has just basic operations, nothing with side effects nor that can notice side effects, which +// implies we can move it around in the code +function triviallySafeToMove(node, asmData) { + var ok = true; + traverse(node, function(node, type) { + switch (type) { + case 'stat': case 'binary': case 'unary-prefix': case 'assign': case 'num': + break; + case 'name': + if (!(node[1] in asmData.vars) && !(node[1] in asmData.params)) ok = false; + break; + case 'call': + if (callHasSideEffects(node)) ok = false; + break; + default: + ok = false; + } + }); + return ok; +} + // Clear out empty ifs and blocks, and redundant blocks/stats and so forth // Operates on generated functions only function vacuum(ast) { @@ -2152,18 +2242,30 @@ function registerizeHarder(ast) { break; case 'conditional': isInExpr++; - buildFlowGraph(node[1]); - var jEnter = markJunction(); - var jExit = addJunction(); - if (node[2]) { - buildFlowGraph(node[2]); - } - joinJunction(jExit); - setJunction(jEnter); - if (node[3]) { - buildFlowGraph(node[3]); + // If the conditional has no side-effects, we can treat it as a single + // block, which might open up opportunities to remove it entirely. + if (!hasSideEffects(node)) { + buildFlowGraph(node[1]); + if (node[2]) { + buildFlowGraph(node[2]); + } + if (node[3]) { + buildFlowGraph(node[3]); + } + } else { + buildFlowGraph(node[1]); + var jEnter = markJunction(); + var jExit = addJunction(); + if (node[2]) { + buildFlowGraph(node[2]); + } + joinJunction(jExit); + setJunction(jEnter); + if (node[3]) { + buildFlowGraph(node[3]); + } + joinJunction(jExit); } - joinJunction(jExit); isInExpr--; break; case 'while': @@ -3525,13 +3627,13 @@ function eliminate(ast, memSafe) { clearEmptyNodes(ifTrue[1]); clearEmptyNodes(ifFalse[1]); var flip = false; - if (ifFalse[1][0] && ifFalse[1][0][0] === 'break') { // canonicalize break in the if + if (ifFalse[1][0] && ifFalse[1][ifFalse[1].length-1][0] === 'break') { // canonicalize break in the if-true var temp = ifFalse; ifFalse = ifTrue; ifTrue = temp; flip = true; } - if (ifTrue[1][0] && ifTrue[1][0][0] === 'break') { + if (ifTrue[1][0] && ifTrue[1][ifTrue[1].length-1][0] === 'break') { var assigns = ifFalse[1]; clearEmptyNodes(assigns); var loopers = [], helpers = []; @@ -3568,6 +3670,17 @@ function eliminate(ast, memSafe) { } } } + // remove loop vars that are used in the if + traverse(ifTrue, function(node, type) { + if (type === 'name') { + var index = loopers.indexOf(node[1]); + if (index < 0) index = helpers.indexOf(node[1]); + if (index >= 0) { + loopers.splice(index, 1); + helpers.splice(index, 1); + } + } + }); if (loopers.length === 0) return; for (var l = 0; l < loopers.length; l++) { var looper = loopers[l]; @@ -3591,21 +3704,57 @@ function eliminate(ast, memSafe) { // if a loop variable is used after we assigned to the helper, we must save its value and use that. // (note that this can happen due to elimination, if we eliminate an expression containing the // loop var far down, past the assignment!) - var temp = looper + '$looptemp'; - var looperUsed = false; - assert(!(temp in asmData.vars)); + // first, see if the looper and helper overlap + var firstLooperUsage = -1; + var lastLooperUsage = -1; + var firstHelperUsage = -1; + var lastHelperUsage = -1; for (var i = found+1; i < stats.length; i++) { var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition traverse(curr, function(node, type) { - if (type === 'name' && node[1] === looper) { - node[1] = temp; - looperUsed = true; + if (type === 'name') { + if (node[1] === looper) { + if (firstLooperUsage < 0) firstLooperUsage = i; + lastLooperUsage = i; + } else if (node[1] === helper) { + if (firstHelperUsage < 0) firstHelperUsage = i; + lastHelperUsage = i; + } } }); } - if (looperUsed) { - asmData.vars[temp] = asmData.vars[looper]; - stats.splice(found, 0, ['stat', ['assign', true, ['name', temp], ['name', looper]]]); + 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) && + 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); + } else { + // they overlap, we can still proceed with the loop optimization, but we must introduce a + // loop temp helper variable + var temp = looper + '$looptemp'; + assert(!(temp in asmData.vars)); + for (var i = firstLooperUsage; i <= lastLooperUsage; i++) { + var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition + traverse(curr, function looperToLooptemp(node, type) { + if (type === 'name') { + if (node[1] === looper) { + node[1] = temp; + } + } else if (type === 'assign' && node[2][0] === 'name') { + // do not traverse the assignment target, phi assignments to the loop variable must remain + traverse(node[3], looperToLooptemp); + return null; + } + }); + } + asmData.vars[temp] = asmData.vars[looper]; + stats.splice(found, 0, ['stat', ['assign', true, ['name', temp], ['name', looper]]]); + } } } for (var l = 0; l < helpers.length; l++) { @@ -3921,6 +4070,21 @@ function measureSize(ast) { return size; } +function measureCost(ast) { + var size = 0; + traverse(ast, function(node, type) { + if (type === 'num' || type === 'unary-prefix') size--; + else if (type === 'binary') { + if (node[3][0] === 'num' && node[3][1] === 0) size--; + else if (node[1] === '/' || node[1] === '%') size += 2; + } + else if (type === 'call' && !callHasSideEffects(node)) size -= 2; + else if (type === 'sub') size++; + size++; + }); + return size; +} + function aggressiveVariableEliminationInternal(func, asmData) { // This removes as many variables as possible. This is often not the best thing because it increases // code size, but it is far preferable to the risk of split functions needing to do more spilling, so diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index d0284528..e06c2d2f 100644 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -137,6 +137,10 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False, extra_info= if closure: passes = filter(lambda p: p != 'closure', passes) # we will do it manually + cleanup = 'cleanup' in passes + if cleanup: + passes = filter(lambda p: p != 'cleanup', passes) # we will do it manually + 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 @@ -291,23 +295,29 @@ EMSCRIPTEN_FUNCS(); for filename in filenames: temp_files.note(filename) - if closure: - # run closure on the shell code, everything but what we js-optimize + if closure or cleanup: + # run on the shell code, everything but what we js-optimize start_asm = '// EMSCRIPTEN_START_ASM\n' end_asm = '// EMSCRIPTEN_END_ASM\n' - closure_sep = 'wakaUnknownBefore(); var asm=wakaUnknownAfter(global,env,buffer)\n' + cl_sep = 'wakaUnknownBefore(); var asm=wakaUnknownAfter(global,env,buffer)\n' - closuree = temp_files.get('.closure.js').name - c = open(closuree, 'w') + cle = temp_files.get('.cl.js').name + c = open(cle, 'w') pre_1, pre_2 = pre.split(start_asm) post_1, post_2 = post.split(end_asm) c.write(pre_1) - c.write(closure_sep) + c.write(cl_sep) c.write(post_2) c.close() - closured = shared.Building.closure_compiler(closuree, pretty='minifyWhitespace' not in passes) - temp_files.note(closured) - coutput = open(closured).read() + if closure: + if DEBUG: print >> sys.stderr, 'running closure on shell code' + cld = shared.Building.closure_compiler(cle, pretty='minifyWhitespace' not in passes) + else: + if DEBUG: print >> sys.stderr, 'running cleanup on shell code' + cld = cle + '.js' + subprocess.Popen(js_engine + [JS_OPTIMIZER, cle, 'noPrintMetadata'] + (['minifyWhitespace'] if 'minifyWhitespace' in passes else []), stdout=open(cld, 'w')).communicate() + temp_files.note(cld) + coutput = open(cld).read() coutput = coutput.replace('wakaUnknownBefore();', '') after = 'wakaUnknownAfter' start = coutput.find(after) diff --git a/tools/shared.py b/tools/shared.py index 5305d46b..7aaa4136 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -326,7 +326,7 @@ def check_fastcomp(): break d = os.path.dirname(d) if not seen: - logging.warning('did not see a source tree above LLVM_DIR, could not verify version numbers match') + logging.warning('did not see a source tree above the LLVM root directory (guessing based on directory of %s), could not verify version numbers match' % LLVM_COMPILER) return True except Exception, e: logging.warning('could not check fastcomp: %s' % str(e)) @@ -1144,63 +1144,131 @@ class Building: 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 + # 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: + logging.debug('adding object %s to link' % (f)) + # 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) + 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')) + 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 + logging.debug('considering archive %s' % (f)) + contents = get_archive_contents(f) + while loop_again: # repeatedly traverse until we have everything we need + 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) + if consider_object(content, force_add=force_add_all): + added_contents.add(content) + loop_again = True + added_any_objects = True + logging.debug('done running loop of archive %s' % (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 + logging.debug('starting archive group loop'); + while loop_again: + loop_again = False + for archive in current_archive_group: + if consider_archive(archive): + loop_again = True + logging.debug('done with archive group loop'); + 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: - 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) + # If we're inside a --start-group/--end-group section, add to the list + # so we can loop back around later. + 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) # Finish link @@ -1362,12 +1430,10 @@ class Building: cmdline = [PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + args if jsrun.TRACK_PROCESS_SPAWNS: logging.info('Executing emscripten.py compiler with cmdline "' + ' '.join(cmdline) + '"') - compiler_output = jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling') - #print compiler_output + jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling') # Detect compilation crashes and errors - if compiler_output is not None and 'Traceback' in compiler_output and 'in test_' in compiler_output: print compiler_output; assert 0 - assert os.path.exists(filename + '.o.js') and len(open(filename + '.o.js', 'r').read()) > 0, 'Emscripten failed to generate .js: ' + str(compiler_output) + assert os.path.exists(filename + '.o.js'), 'Emscripten failed to generate .js' return filename + '.o.js' diff --git a/tools/system_libs.py b/tools/system_libs.py index 50910a8a..bc81a351 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -55,7 +55,14 @@ def calculate(temp_files, in_temp, stdout, stderr): os.path.join('libcxx', 'new.cpp'), ] musl_files = [ + ['ctype', [ + 'isdigit.c', + 'isspace.c', + 'isupper.c', + 'tolower.c', + ]], ['internal', [ + 'intscan.c', 'floatscan.c', 'shgetc.c', ]], @@ -74,6 +81,7 @@ def calculate(temp_files, in_temp, stdout, stderr): 'atoi.c', 'atol.c', 'strtod.c', + 'strtol.c', ]], ['string', [ 'memcmp.c', @@ -103,7 +111,22 @@ def calculate(temp_files, in_temp, stdout, stderr): def create_libcextra(): logging.debug('building libcextra for cache') musl_files = [ + ['compat', [ + 'strlwr.c', + 'strtol_l.c', + 'strupr.c' + ]], ['ctype', [ + 'isalnum.c', + 'isalpha.c', + 'isascii.c', + 'isblank.c', + 'iscntrl.c', + 'isgraph.c', + 'islower.c', + 'isprint.c', + 'ispunct.c', + 'isxdigit.c', 'iswalnum.c', 'iswalpha.c', 'iswblank.c', @@ -117,19 +140,30 @@ def calculate(temp_files, in_temp, stdout, stderr): 'iswspace.c', 'iswupper.c', 'iswxdigit.c', + 'toascii.c', + 'toupper.c', 'towctrans.c', 'wcswidth.c', 'wctrans.c', 'wcwidth.c', ]], - ['internal', [ - 'intscan.c', - ]], ['legacy', [ 'err.c', ]], ['locale', [ 'iconv.c', + 'isalnum_l.c', + 'isalpha_l.c', + 'isblank_l.c', + 'iscntrl_l.c', + 'isdigit_l.c', + 'isgraph_l.c', + 'islower_l.c', + 'isprint_l.c', + 'ispunct_l.c', + 'isspace_l.c', + 'isupper_l.c', + 'isxdigit_l.c', 'iswalnum_l.c', 'iswalpha_l.c', 'iswblank_l.c', @@ -148,6 +182,8 @@ def calculate(temp_files, in_temp, stdout, stderr): 'strfmon.c', 'strncasecmp_l.c', 'strxfrm.c', + 'tolower_l.c', + 'toupper_l.c', 'towctrans_l.c', 'towlower_l.c', 'towupper_l.c', diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index c9746e78..da3ebb4f 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -106,15 +106,11 @@ function sign_extension_simplification() { } } 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]; - HEAP32[$4] = HEAP32[$5] !== HEAP32[$6]; - var x = HEAP32[$5] != HEAP32[$6] | 0; + f((a > b & 1) + 1 | 0); + f(a > b | z); + f(a > b | c > d); + HEAP32[$4] = HEAP32[$5] < HEAP32[$6] & 1; + var x = HEAP32[$5] != HEAP32[$6] & 1; } function tempDoublePtr($45, $14, $28, $42) { $45 = $45 | 0; @@ -538,4 +534,68 @@ function fcomp() { if (5 >= ($b | 0)) return 5; if (5 >= 5) return 5; } +function conditionalizeMe() { + if (x > 1 ? x + y + z + w > 12 : 0) { + b(); + } + if (a() > 1 ? x + y + z + w > 12 : 0) { + b(); + } + if (x > 1 & x + y + z + k() > 12) { + b(); + } + if (a() > 1 & x + y + z + k() > 12) { + b(); + } + if (x > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (a() > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (x > 1 | x + y + z + k() > 12) { + b(); + } + if (a() > 1 | x + y + z + k() > 12) { + b(); + } + if (x > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (a() > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (x + y + z + k() > 12 | x > 1) { + b(); + } + if (x + y + z + k() > 12 | a() > 1) { + b(); + } + while (x > 1 ? x + y + z + w > 12 : 0) { + b(); + } + while (a() > 1 ? x + y + z + w > 12 : 0) { + b(); + } + while (x > 1 & x + y + z + k() > 12) { + b(); + } + while (a() > 1 & x + y + z + k() > 12) { + b(); + } + if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(+0))) { + b(); + } + if ($sub$i480 >= Math_fround(+0) ? !($sub4$i483 >= Math_fround(HEAPF32[x + y | 0])) : 1) { + b(); + } + 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 00ebd7d7..08d8c2d4 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -113,16 +113,11 @@ function sign_extension_simplification() { } } function compare_result_simplification() { - // Eliminate these '&1's. + f(((a > b)&1) + 1 | 0); + f(((a > b)&1) | z); + f(((a > b)&1) | (c > d & 1)); + // Don't 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; - 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) { @@ -550,4 +545,68 @@ function fcomp() { if (!(5 < ($b|0))) return 5; if (!(5 < 5)) return 5; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp"] +function conditionalizeMe() { + if ((x > 1) & (x+y+z+w > 12)) { + b(); + } + if ((a() > 1) & (x+y+z+w > 12)) { + b(); + } + if ((x > 1) & (x+y+z+k() > 12)) { + b(); + } + if ((a() > 1) & (x+y+z+k() > 12)) { + b(); + } + if ((x > 1) | (x+y+z+w > 12)) { + b(); + } + if ((a() > 1) | (x+y+z+w > 12)) { + b(); + } + if ((x > 1) | (x+y+z+k() > 12)) { + b(); + } + if ((a() > 1) | (x+y+z+k() > 12)) { + b(); + } + if ((x+y+z+w > 12) | (x > 1)) { + b(); + } + if ((x+y+z+w > 12) | (a() > 1)) { + b(); + } + if ((x+y+z+k() > 12) | (x > 1)) { + b(); + } + if ((x+y+z+k() > 12) | (a() > 1)) { + b(); + } + while ((x > 1) & (x+y+z+w > 12)) { + b(); + } + while ((a() > 1) & (x+y+z+w > 12)) { + b(); + } + while ((x > 1) & (x+y+z+k() > 12)) { + b(); + } + while ((a() > 1) & (x+y+z+k() > 12)) { + b(); + } + if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(+0))) { + b(); + } + if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(HEAPF32[x+y|0]))) { + b(); + } + 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"] diff --git a/tools/test-js-optimizer-asm-regs-harder-output.js b/tools/test-js-optimizer-asm-regs-harder-output.js index e1df42cb..c3b326f6 100644 --- a/tools/test-js-optimizer-asm-regs-harder-output.js +++ b/tools/test-js-optimizer-asm-regs-harder-output.js @@ -129,4 +129,9 @@ function linkedVars() { } return i2 + i1; } +function deadCondExpr(i2) { + i2 = i2 | 0; + var i1 = 0; + return i1 | 0; +} diff --git a/tools/test-js-optimizer-asm-regs-harder.js b/tools/test-js-optimizer-asm-regs-harder.js index 0231a215..fa72aab8 100644 --- a/tools/test-js-optimizer-asm-regs-harder.js +++ b/tools/test-js-optimizer-asm-regs-harder.js @@ -149,5 +149,11 @@ function linkedVars() { } return outer1 + outer2; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "stackRestore", "switchey", "switchey2", "iffey", "labelledJump", "linkedVars'] +function deadCondExpr(input) { + input = input|0; + var dead = 0, temp = 0; + dead = (!input ? -1 : input)|0; + return temp|0; +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "stackRestore", "switchey", "switchey2", "iffey", "labelledJump", "linkedVars", "deadCondExpr"] diff --git a/tools/webidl_binder.py b/tools/webidl_binder.py index 0507cc78..168460fe 100644 --- a/tools/webidl_binder.py +++ b/tools/webidl_binder.py @@ -359,7 +359,8 @@ for name in names: m.getExtendedAttribute('Value'), (m.getExtendedAttribute('Operator') or [None])[0], constructor, - func_scope=m.parentScope.identifier.name) + func_scope=m.parentScope.identifier.name, + const=m.getExtendedAttribute('Const')) mid_js += [';\n'] if constructor: emit_constructor(name) |