diff --git a/AUTHORS b/AUTHORS index f51f5aac9e54a..66458cb7e9941 100644 --- a/AUTHORS +++ b/AUTHORS @@ -292,4 +292,7 @@ a license to everyone to use it as detailed in LICENSE.) * Ryan Speets * Fumiya Chiba * Ryan C. Gordon +* Inseok Lee +* Yair Levinson (copyright owned by Autodesk, Inc.) +* Matjaž Drolc diff --git a/cmake/Modules/Platform/Emscripten.cmake b/cmake/Modules/Platform/Emscripten.cmake index b53cc713d70ce..1bc4fc7c02d3d 100644 --- a/cmake/Modules/Platform/Emscripten.cmake +++ b/cmake/Modules/Platform/Emscripten.cmake @@ -107,7 +107,7 @@ if (EMSCRIPTEN_FORCE_COMPILERS) if (NOT _cmake_compiler_result EQUAL 0) message(FATAL_ERROR "Failed to fetch compiler version information with command \"'${CMAKE_C_COMPILER}' -v\"! Process returned with error code ${_cmake_compiler_result}.") endif() - if (NOT "${_cmake_compiler_output}" MATCHES "Emscripten") + if (NOT "${_cmake_compiler_output}" MATCHES "[Ee]mscripten") message(FATAL_ERROR "System LLVM compiler cannot be used to build with Emscripten! Check Emscripten's LLVM toolchain location in .emscripten configuration file, and make sure to point CMAKE_C_COMPILER to where emcc is located. (was pointing to \"${CMAKE_C_COMPILER}\")") endif() string(REGEX MATCH "clang version ([0-9\.]+)" _dummy_unused "${_cmake_compiler_output}") diff --git a/emcc.py b/emcc.py index eb6d7198f07f6..af468e87fc451 100755 --- a/emcc.py +++ b/emcc.py @@ -90,17 +90,199 @@ # dlmalloc makes it hard to compare native and js builds EMCC_CFLAGS = os.environ.get('EMCC_CFLAGS') # Additional compiler flags that we treat as if they were passed to us on the commandline +emscripten_temp_dir = shared.get_emscripten_temp_dir() + # Target options final = None -target = None -script_src = None # if set, we have a script to load with a src attribute -script_inline = None # if set, we have the contents of a script to write inline in a script + + +class Intermediate: + counter = 0 +def save_intermediate(name=None, suffix='js'): + name = os.path.join(emscripten_temp_dir, 'emcc-%d%s.%s' % (Intermediate.counter, '' if name is None else '-' + name, suffix)) + if type(final) != str: + logging.debug('(not saving intermediate %s because deferring linking)' % name) + return + shutil.copyfile(final, name) + Intermediate.counter += 1 + + +class TimeLogger: + last = time.time() + + @staticmethod + def update(): + TimeLogger.last = time.time() + +def log_time(name): + """Log out times for emcc stages""" + if DEBUG: + now = time.time() + logging.debug('emcc step "%s" took %.2f seconds', name, now - TimeLogger.last) + TimeLogger.update() + + +class EmccOptions: + def __init__(self): + self.opt_level = 0 + self.debug_level = 0 + self.shrink_level = 0 + self.requested_debug = '' + self.profiling = False + self.profiling_funcs = False + self.tracing = False + self.emit_symbol_map = False + self.js_opts = None + self.force_js_opts = False + self.llvm_opts = None + self.llvm_lto = None + self.default_cxx_std = '-std=c++03' # Enforce a consistent C++ standard when compiling .cpp files, if user does not specify one on the cmdline. + self.use_closure_compiler = None + self.js_transform = None + self.pre_js = '' # before all js + self.post_module = '' # in js, after Module exists + self.post_js = '' # after all js + self.preload_files = [] + self.embed_files = [] + self.exclude_files = [] + self.ignore_dynamic_linking = False + self.shell_path = shared.path_from_root('src', 'shell.html') + self.js_libraries = [] + self.bind = False + self.emrun = False + self.cpu_profiler = False + self.thread_profiler = False + self.memory_profiler = False + self.save_bc = False + self.memory_init_file = None + self.use_preload_cache = False + self.no_heap_copy = False + self.use_preload_plugins = False + self.proxy_to_worker = False + self.default_object_extension = '.o' + self.valid_abspaths = [] + self.separate_asm = False + self.cfi = False + # Specifies the line ending format to use for all generated text files. + # Defaults to using the native EOL on each platform (\r\n on Windows, \n on Linux&OSX) + self.output_eol = os.linesep + + +class JSOptimizer: + def __init__(self, target, options, misc_temp_files, js_transform_tempfiles): + self.queue = [] + self.extra_info = {} + self.queue_history = [] + self.blacklist = (os.environ.get('EMCC_JSOPT_BLACKLIST') or '').split(',') + self.minify_whitespace = False + self.cleanup_shell = False + + self.target = target + self.opt_level = options.opt_level + self.debug_level = options.debug_level + self.emit_symbol_map = options.emit_symbol_map + self.profiling_funcs = options.profiling_funcs + self.use_closure_compiler = options.use_closure_compiler + + self.misc_temp_files = misc_temp_files + self.js_transform_tempfiles = js_transform_tempfiles + + def flush(self, title='js_opts'): + self.queue = filter(lambda p: p not in self.blacklist, self.queue) + + assert not shared.Settings.WASM_BACKEND, 'JSOptimizer should not run with pure wasm output' + + if self.extra_info is not None and len(self.extra_info) == 0: + self.extra_info = None + + if len(self.queue) > 0 and not(not shared.Settings.ASM_JS and len(self.queue) == 1 and self.queue[0] == 'last'): + passes = self.queue[:] + + if DEBUG != '2' or len(passes) < 2: + # by assumption, our input is JS, and our output is JS. If a pass is going to run in the native optimizer in C++, then we + # must give it JSON and receive from it JSON + chunks = [] + curr = [] + for p in passes: + if len(curr) == 0: + curr.append(p) + else: + native = shared.js_optimizer.use_native(p, source_map=self.debug_level >= 4) + last_native = shared.js_optimizer.use_native(curr[-1], source_map=self.debug_level >= 4) + if native == last_native: + curr.append(p) + else: + curr.append('emitJSON') + chunks.append(curr) + curr = ['receiveJSON', p] + if len(curr) > 0: + chunks.append(curr) + if len(chunks) == 1: + self.run_passes(chunks[0], title, just_split=False, just_concat=False) + else: + for i in range(len(chunks)): + self.run_passes(chunks[i], 'js_opts_' + str(i), just_split='receiveJSON' in chunks[i], just_concat='emitJSON' in chunks[i]) + else: + # DEBUG 2, run each pass separately + extra_info = self.extra_info + for p in passes: + self.queue = [p] + self.flush(p) + self.extra_info = extra_info # flush wipes it + log_time('part of js opts') + self.queue_history += self.queue + self.queue = [] + self.extra_info = {} + + def run_passes(self, passes, title, just_split, just_concat): + global final + passes = ['asm'] + passes + if shared.Settings.PRECISE_F32: + passes = ['asmPreciseF32'] + passes + if (self.emit_symbol_map or shared.Settings.CYBERDWARF) and 'minifyNames' in passes: + passes += ['symbolMap=' + self.target + '.symbols'] + if self.profiling_funcs and 'minifyNames' in passes: + passes += ['profilingFuncs'] + if self.minify_whitespace and 'last' in passes: + passes += ['minifyWhitespace'] + if self.cleanup_shell and 'last' in passes: + passes += ['cleanup'] + logging.debug('applying js optimization passes: %s', ' '.join(passes)) + self.misc_temp_files.note(final) + final = shared.Building.js_optimizer(final, passes, self.debug_level >= 4, + self.extra_info, just_split=just_split, + just_concat=just_concat) + self.misc_temp_files.note(final) + self.js_transform_tempfiles.append(final) + if DEBUG: save_intermediate(title, suffix='js' if 'emitJSON' not in passes else 'json') + + def do_minify(self): + """minifies the code. + + this is also when we do certain optimizations that must be done right before or after minification + """ + if shared.Settings.SPLIT_MEMORY: + # must be done before minification + self.queue += ['splitMemory', 'simplifyExpressions'] + + if self.opt_level >= 2: + if self.debug_level < 2 and not self.use_closure_compiler == 2: + self.queue += ['minifyNames'] + if self.debug_level == 0: + self.minify_whitespace = True + + if self.use_closure_compiler == 1: + self.queue += ['closure'] + elif self.debug_level <= 2 and shared.Settings.FINALIZE_ASM_JS and not self.use_closure_compiler: + self.cleanup_shell = True + # # Main run() function # def run(): - global final, target, script_src, script_inline + global final + target = None if DEBUG: logging.warning('invocation: ' + ' '.join(sys.argv) + (' + ' + EMCC_CFLAGS if EMCC_CFLAGS else '') + ' (in ' + os.getcwd() + ')') if EMCC_CFLAGS: sys.argv.extend(shlex.split(EMCC_CFLAGS)) @@ -402,20 +584,6 @@ def filename_type_ending(filename): suffix = filename_type_suffix(filename) return '' if not suffix else ('.' + suffix) - # Log out times for emcc stages - class TimeLogger: - last = time.time() - - @staticmethod - def update(): - TimeLogger.last = time.time() - - def log_time(name): - if DEBUG: - now = time.time() - logging.debug('emcc step "%s" took %.2f seconds', name, now - TimeLogger.last) - TimeLogger.update() - use_cxx = True try: @@ -424,62 +592,6 @@ def log_time(name): newargs = sys.argv[1:] - opt_level = 0 - debug_level = 0 - shrink_level = 0 - requested_debug = '' - profiling = False - profiling_funcs = False - tracing = False - emit_symbol_map = False - js_opts = None - force_js_opts = False - llvm_opts = None - llvm_lto = None - default_cxx_std = '-std=c++03' # Enforce a consistent C++ standard when compiling .cpp files, if user does not specify one on the cmdline. - use_closure_compiler = None - js_transform = None - pre_js = '' # before all js - post_module = '' # in js, after Module exists - post_js = '' # after all js - preload_files = [] - embed_files = [] - exclude_files = [] - ignore_dynamic_linking = False - shell_path = shared.path_from_root('src', 'shell.html') - js_libraries = [] - bind = False - emrun = False - cpu_profiler = False - thread_profiler = False - memory_profiler = False - save_bc = False - memory_init_file = None - use_preload_cache = False - no_heap_copy = False - use_preload_plugins = False - proxy_to_worker = False - default_object_extension = '.o' - valid_abspaths = [] - separate_asm = False - cfi = False - # Specifies the line ending format to use for all generated text files. - # Defaults to using the native EOL on each platform (\r\n on Windows, \n on Linux&OSX) - output_eol = os.linesep - - def is_valid_abspath(path_name): - # Any path that is underneath the emscripten repository root must be ok. - if shared.path_from_root().replace('\\', '/') in path_name.replace('\\', '/'): - return True - - for valid_abspath in valid_abspaths: - if in_directory(valid_abspath, path_name): - return True - return False - - def check_bad_eq(arg): - assert '=' not in arg, 'Invalid parameter (do not use "=" with "--" options)' - # Scan and strip emscripten specific cmdline warning flags # This needs to run before other cmdline flags have been parsed, so that warnings are properly printed during arg parse newargs = shared.WarningManager.capture_warnings(newargs) @@ -490,16 +602,6 @@ def check_bad_eq(arg): newargs[i] += newargs[i+1] newargs[i+1] = '' - settings_changes = [] - - def validate_arg_level(level_string, max_level, err_msg): - try: - level = int(level_string) - assert 0 <= level <= max_level - except: - raise Exception(err_msg) - return level - def detect_fixed_language_mode(args): check_next = False for item in args: @@ -518,247 +620,8 @@ def detect_fixed_language_mode(args): return False has_fixed_language_mode = detect_fixed_language_mode(newargs) - should_exit = False - - for i in range(len(newargs)): - newargs[i] = newargs[i].strip() # On Windows Vista (and possibly others), excessive spaces in the command line leak into the items in this array, so trim e.g. 'foo.cpp ' -> 'foo.cpp' - if newargs[i].startswith('-O'): - # Let -O default to -O2, which is what gcc does. - requested_level = newargs[i][2:] or '2' - if requested_level == 's': - llvm_opts = ['-Os'] - requested_level = 2 - shrink_level = 1 - settings_changes.append('INLINING_LIMIT=50') - elif requested_level == 'z': - llvm_opts = ['-Oz'] - requested_level = 2 - shrink_level = 2 - settings_changes.append('INLINING_LIMIT=25') - opt_level = validate_arg_level(requested_level, 3, 'Invalid optimization level: ' + newargs[i]) - elif newargs[i].startswith('--js-opts'): - check_bad_eq(newargs[i]) - js_opts = eval(newargs[i+1]) - if js_opts: force_js_opts = True - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--llvm-opts'): - check_bad_eq(newargs[i]) - llvm_opts = eval(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--llvm-lto'): - check_bad_eq(newargs[i]) - llvm_lto = eval(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--closure'): - check_bad_eq(newargs[i]) - use_closure_compiler = int(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--js-transform'): - check_bad_eq(newargs[i]) - js_transform = newargs[i+1] - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--pre-js'): - check_bad_eq(newargs[i]) - pre_js += open(newargs[i+1]).read() + '\n' - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--post-js'): - check_bad_eq(newargs[i]) - post_js += open(newargs[i+1]).read() + '\n' - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--minify'): - check_bad_eq(newargs[i]) - assert newargs[i+1] == '0', '0 is the only supported option for --minify; 1 has been deprecated' - debug_level = max(1, debug_level) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('-g'): - requested_level = newargs[i][2:] or '3' - debug_level = validate_arg_level(requested_level, 4, 'Invalid debug level: ' + newargs[i]) - requested_debug = newargs[i] - newargs[i] = '' - elif newargs[i] == '-profiling' or newargs[i] == '--profiling': - debug_level = 2 - profiling = True - newargs[i] = '' - elif newargs[i] == '-profiling-funcs' or newargs[i] == '--profiling-funcs': - profiling_funcs = True - newargs[i] = '' - elif newargs[i] == '--tracing' or newargs[i] == '--memoryprofiler': - if newargs[i] == '--memoryprofiler': - memory_profiler = True - tracing = True - newargs[i] = '' - newargs.append('-D__EMSCRIPTEN_TRACING__=1') - settings_changes.append("EMSCRIPTEN_TRACING=1") - js_libraries.append(shared.path_from_root('src', 'library_trace.js')) - elif newargs[i] == '--emit-symbol-map': - emit_symbol_map = True - newargs[i] = '' - elif newargs[i] == '--bind': - bind = True - newargs[i] = '' - js_libraries.append(shared.path_from_root('src', 'embind', 'emval.js')) - js_libraries.append(shared.path_from_root('src', 'embind', 'embind.js')) - if default_cxx_std: - default_cxx_std = '-std=c++11' # Force C++11 for embind code, but only if user has not explicitly overridden a standard. - elif newargs[i].startswith('-std=') or newargs[i].startswith('--std='): - default_cxx_std = '' # User specified a standard to use, clear Emscripten from specifying it. - elif newargs[i].startswith('--embed-file'): - check_bad_eq(newargs[i]) - embed_files.append(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--preload-file'): - check_bad_eq(newargs[i]) - preload_files.append(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--exclude-file'): - check_bad_eq(newargs[i]) - exclude_files.append(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--use-preload-cache'): - use_preload_cache = True - newargs[i] = '' - elif newargs[i].startswith('--no-heap-copy'): - no_heap_copy = True - newargs[i] = '' - elif newargs[i].startswith('--use-preload-plugins'): - use_preload_plugins = True - newargs[i] = '' - elif newargs[i] == '--ignore-dynamic-linking': - ignore_dynamic_linking = True - newargs[i] = '' - elif newargs[i] == '-v': - shared.COMPILER_OPTS += ['-v'] - shared.check_sanity(force=True) - newargs[i] = '' - elif newargs[i].startswith('--shell-file'): - check_bad_eq(newargs[i]) - shell_path = newargs[i+1] - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i].startswith('--js-library'): - check_bad_eq(newargs[i]) - js_libraries.append(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i] == '--remove-duplicates': - logging.warning('--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase') - newargs[i] = '' - elif newargs[i] == '--jcache': - logging.error('jcache is no longer supported') - newargs[i] = '' - elif newargs[i] == '--cache': - check_bad_eq(newargs[i]) - os.environ['EM_CACHE'] = newargs[i+1] - shared.reconfigure_cache() - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i] == '--clear-cache': - logging.info('clearing cache as requested by --clear-cache') - shared.Cache.erase() - shared.check_sanity(force=True) # this is a good time for a sanity check - should_exit = True - elif newargs[i] == '--clear-ports': - logging.info('clearing ports and cache as requested by --clear-ports') - system_libs.Ports.erase() - shared.Cache.erase() - shared.check_sanity(force=True) # this is a good time for a sanity check - should_exit = True - elif newargs[i] == '--show-ports': - system_libs.show_ports() - should_exit = True - elif newargs[i] == '--save-bc': - check_bad_eq(newargs[i]) - save_bc = newargs[i+1] - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i] == '--memory-init-file': - check_bad_eq(newargs[i]) - memory_init_file = int(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i] == '--proxy-to-worker': - proxy_to_worker = True - newargs[i] = '' - elif newargs[i] == '--valid-abspath': - valid_abspaths.append(newargs[i+1]) - newargs[i] = '' - newargs[i+1] = '' - elif newargs[i] == '--separate-asm': - separate_asm = True - newargs[i] = '' - elif newargs[i].startswith(('-I', '-L')): - path_name = newargs[i][2:] - if os.path.isabs(path_name) and not is_valid_abspath(path_name): - shared.WarningManager.warn('ABSOLUTE_PATHS', '-I or -L of an absolute path "' + newargs[i] + '" encountered. If this is to a local system header/library, it may cause problems (local system files make sense for compiling natively on your system, but not necessarily to JavaScript).') # Of course an absolute path to a non-system-specific library or header is fine, and you can ignore this warning. The danger are system headers that are e.g. x86 specific and nonportable. The emscripten bundled headers are modified to be portable, local system ones are generally not - elif newargs[i] == '--emrun': - emrun = True - newargs[i] = '' - elif newargs[i] == '--cpuprofiler': - cpu_profiler = True - newargs[i] = '' - elif newargs[i] == '--threadprofiler': - thread_profiler = True - settings_changes.append('PTHREADS_PROFILING=1') - newargs[i] = '' - elif newargs[i] == '--default-obj-ext': - newargs[i] = '' - default_object_extension = newargs[i+1] - if not default_object_extension.startswith('.'): - default_object_extension = '.' + default_object_extension - newargs[i+1] = '' - elif newargs[i] == '-msse': - newargs.append('-D__SSE__=1') - newargs[i] = '' - elif newargs[i] == '-msse2': - newargs.append('-D__SSE__=1') - newargs.append('-D__SSE2__=1') - newargs[i] = '' - elif newargs[i] == '-msse3': - newargs.append('-D__SSE__=1') - newargs.append('-D__SSE2__=1') - newargs.append('-D__SSE3__=1') - newargs[i] = '' - elif newargs[i] == '-mssse3': - newargs.append('-D__SSE__=1') - newargs.append('-D__SSE2__=1') - newargs.append('-D__SSE3__=1') - newargs.append('-D__SSSE3__=1') - newargs[i] = '' - elif newargs[i] == '-msse4.1': - newargs.append('-D__SSE__=1') - newargs.append('-D__SSE2__=1') - newargs.append('-D__SSE3__=1') - newargs.append('-D__SSSE3__=1') - newargs.append('-D__SSE4_1__=1') - newargs[i] = '' - elif newargs[i].startswith("-fsanitize=cfi"): - cfi = True - elif newargs[i] == "--output_eol": - if newargs[i+1].lower() == 'windows': - output_eol = '\r\n' - elif newargs[i+1].lower() == 'linux': - output_eol = '\n' - else: - logging.error('Invalid value "' + newargs[i+1] + '" to --output_eol!') - exit(1) - newargs[i] = '' - newargs[i+1] = '' - if should_exit: - sys.exit(0) - - newargs = [arg for arg in newargs if arg is not ''] + options, settings_changes, newargs = parse_args(newargs) for i in range(0, len(newargs)): arg = newargs[i] @@ -773,35 +636,38 @@ def detect_fixed_language_mode(args): use_cxx = False if not use_cxx: - default_cxx_std = '' # Compiling C code with .c files, don't enforce a default C++ std. + options.default_cxx_std = '' # Compiling C code with .c files, don't enforce a default C++ std. call = CXX if use_cxx else CC # If user did not specify a default -std for C++ code, specify the emscripten default. - if default_cxx_std: - newargs = newargs + [default_cxx_std] + if options.default_cxx_std: + newargs = newargs + [options.default_cxx_std] - if emrun: - pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n' - post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n' + if options.emrun: + options.pre_js += open(shared.path_from_root('src', 'emrun_prejs.js')).read() + '\n' + options.post_js += open(shared.path_from_root('src', 'emrun_postjs.js')).read() + '\n' - if cpu_profiler: - post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n' + if options.cpu_profiler: + options.post_js += open(shared.path_from_root('src', 'cpuprofiler.js')).read() + '\n' - if memory_profiler: - post_js += open(shared.path_from_root('src', 'memoryprofiler.js')).read() + '\n' + if options.memory_profiler: + options.post_js += open(shared.path_from_root('src', 'memoryprofiler.js')).read() + '\n' - if thread_profiler: - post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n' + if options.thread_profiler: + options.post_js += open(shared.path_from_root('src', 'threadprofiler.js')).read() + '\n' - if js_opts is None: js_opts = opt_level >= 2 - if llvm_opts is None: llvm_opts = LLVM_OPT_LEVEL[opt_level] - if memory_init_file is None: memory_init_file = opt_level >= 2 + if options.js_opts is None: + options.js_opts = options.opt_level >= 2 + if options.llvm_opts is None: + options.llvm_opts = LLVM_OPT_LEVEL[options.opt_level] + if options.memory_init_file is None: + options.memory_init_file = options.opt_level >= 2 # TODO: support source maps with js_transform - if js_transform and debug_level >= 4: + if options.js_transform and options.debug_level >= 4: logging.warning('disabling source maps because a js transform is being done') - debug_level = 3 + options.debug_level = 3 if DEBUG: start_time = time.time() # done after parsing arguments, which might affect debug state @@ -931,10 +797,10 @@ def detect_fixed_language_mode(args): wasm_text_target = asm_target.replace('.asm.js', '.wast') # ditto, might not be used wasm_binary_target = asm_target.replace('.asm.js', '.wasm') # ditto, might not be used - if final_suffix == 'html' and not separate_asm and ('PRECISE_F32=2' in settings_changes or 'USE_PTHREADS=2' in settings_changes): - separate_asm = True + if final_suffix == 'html' and not options.separate_asm and ('PRECISE_F32=2' in settings_changes or 'USE_PTHREADS=2' in settings_changes): + options.separate_asm = True logging.warning('forcing separate asm output (--separate-asm), because -s PRECISE_F32=2 or -s USE_PTHREADS=2 was passed.') - if separate_asm: + if options.separate_asm: shared.Settings.SEPARATE_ASM = os.path.basename(asm_target) if 'EMCC_STRICT' in os.environ: @@ -958,38 +824,14 @@ def get_last_setting_change(setting): if error_on_missing_libraries_cmdline: shared.Settings.ERROR_ON_MISSING_LIBRARIES = int(error_on_missing_libraries_cmdline[len('ERROR_ON_MISSING_LIBRARIES='):]) - system_js_libraries = [] - - # Find library files - for i, lib in libs: - logging.debug('looking for library "%s"', lib) - found = False - for prefix in LIB_PREFIXES: - for suff in STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS: - name = prefix + lib + suff - for lib_dir in lib_dirs: - path = os.path.join(lib_dir, name) - if os.path.exists(path): - logging.debug('found library "%s" at %s', lib, path) - input_files.append((i, path)) - found = True - break - if found: break - if found: break - if not found: - system_js_libraries += shared.Building.path_to_system_js_libraries(lib) - - # Certain linker flags imply some link libraries to be pulled in by default. - system_js_libraries += shared.Building.path_to_system_js_libraries_for_settings(settings_changes) - - settings_changes.append('SYSTEM_JS_LIBRARIES="' + ','.join(system_js_libraries) + '"') + settings_changes.append(system_js_libraries_setting_str(libs, lib_dirs, settings_changes, input_files)) # If not compiling to JS, then we are compiling to an intermediate bitcode objects or library, so # ignore dynamic linking, since multiple dynamic linkings can interfere with each other - if not filename_type_suffix(target) in JS_CONTAINING_SUFFIXES or ignore_dynamic_linking: + if not filename_type_suffix(target) in JS_CONTAINING_SUFFIXES or options.ignore_dynamic_linking: def check(input_file): if filename_type_ending(input_file) in DYNAMICLIB_ENDINGS: - if not ignore_dynamic_linking: logging.warning('ignoring dynamic library %s because not compiling to JS or HTML, remember to link it when compiling to JS or HTML at the end', os.path.basename(input_file)) + if not options.ignore_dynamic_linking: logging.warning('ignoring dynamic library %s because not compiling to JS or HTML, remember to link it when compiling to JS or HTML at the end', os.path.basename(input_file)) return False else: return True @@ -1001,23 +843,23 @@ def check(input_file): newargs = CC_ADDITIONAL_ARGS + newargs - if separate_asm and final_suffix != 'html': + if options.separate_asm and final_suffix != 'html': shared.WarningManager.warn('SEPARATE_ASM') # 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: + if options.bind and final_suffix in JS_CONTAINING_SUFFIXES: 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=opt_level, shrink_level=shrink_level, noisy=True) + shared.Settings.apply_opt_level(opt_level=options.opt_level, shrink_level=options.shrink_level, noisy=True) if os.environ.get('EMCC_FAST_COMPILER') == '0': logging.critical('Non-fastcomp compiler is no longer available, please use fastcomp or an older version of emscripten') sys.exit(1) # Set ASM_JS default here so that we can override it from the command line. - shared.Settings.ASM_JS = 1 if opt_level > 0 else 2 + shared.Settings.ASM_JS = 1 if options.opt_level > 0 else 2 # Apply -s settings in newargs here (after optimization levels, so they can override them) for change in settings_changes: @@ -1076,23 +918,23 @@ def check(input_file): assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' if shared.Settings.SAFE_HEAP: - if not js_opts: + if not options.js_opts: logging.debug('enabling js opts for SAFE_HEAP') - js_opts = True - force_js_opts = True + options.js_opts = True + options.force_js_opts = True - if debug_level > 1 and use_closure_compiler: + if options.debug_level > 1 and options.use_closure_compiler: logging.warning('disabling closure because debug info was requested') - use_closure_compiler = False + options.use_closure_compiler = False - assert not (shared.Settings.NO_DYNAMIC_EXECUTION and use_closure_compiler), 'cannot have both NO_DYNAMIC_EXECUTION and closure compiler enabled at the same time' + assert not (shared.Settings.NO_DYNAMIC_EXECUTION and options.use_closure_compiler), 'cannot have both NO_DYNAMIC_EXECUTION and closure compiler enabled at the same time' - if use_closure_compiler: - shared.Settings.USE_CLOSURE_COMPILER = use_closure_compiler + if options.use_closure_compiler: + shared.Settings.USE_CLOSURE_COMPILER = options.use_closure_compiler if not shared.check_closure_compiler(): logging.error('fatal: closure compiler is not configured correctly') sys.exit(1) - if use_closure_compiler == 2 and shared.Settings.ASM_JS == 1: + if options.use_closure_compiler == 2 and shared.Settings.ASM_JS == 1: shared.WarningManager.warn('ALMOST_ASM', 'not all asm.js optimizations are possible with --closure 2, disabling those - your code will be run more slowly') shared.Settings.ASM_JS = 2 @@ -1102,7 +944,7 @@ def check(input_file): shared.Settings.INCLUDE_FULL_LIBRARY = 1 elif shared.Settings.SIDE_MODULE: assert not shared.Settings.MAIN_MODULE - memory_init_file = False # memory init file is not supported with asm.js side modules, must be executable synchronously (for dlopen) + options.memory_init_file = False # memory init file is not supported with asm.js side modules, must be executable synchronously (for dlopen) if shared.Settings.MAIN_MODULE or shared.Settings.SIDE_MODULE: assert shared.Settings.ASM_JS, 'module linking requires asm.js output (-s ASM_JS=1)' @@ -1110,7 +952,7 @@ def check(input_file): shared.Settings.LINKABLE = 1 shared.Settings.RELOCATABLE = 1 shared.Settings.PRECISE_I64_MATH = 1 # other might use precise math, we need to be able to print it - assert not use_closure_compiler, 'cannot use closure compiler on shared modules' + assert not options.use_closure_compiler, 'cannot use closure compiler on shared modules' assert not shared.Settings.ALLOW_MEMORY_GROWTH, 'memory growth is not supported with shared modules yet' if shared.Settings.EMULATE_FUNCTION_POINTER_CASTS: @@ -1127,12 +969,12 @@ def check(input_file): shared.Settings.SAFE_SPLIT_MEMORY = 1 # we use our own infrastructure assert not shared.Settings.RELOCATABLE, 'no SPLIT_MEMORY with RELOCATABLE' assert not shared.Settings.USE_PTHREADS, 'no SPLIT_MEMORY with pthreads' - if not js_opts: - js_opts = True + if not options.js_opts: + options.js_opts = True logging.debug('enabling js opts for SPLIT_MEMORY') - force_js_opts = True - if use_closure_compiler: - use_closure_compiler = False + options.force_js_opts = True + if options.use_closure_compiler: + options.use_closure_compiler = False logging.warning('cannot use closure compiler on split memory, for now, disabling') if shared.Settings.STB_IMAGE and final_suffix in JS_CONTAINING_SUFFIXES: @@ -1155,7 +997,7 @@ def check(input_file): if shared.Settings.FETCH and final_suffix in JS_CONTAINING_SUFFIXES: input_files.append((next_arg_index, shared.path_from_root('system', 'lib', 'fetch', 'emscripten_fetch.cpp'))) next_arg_index += 1 - js_libraries.append(shared.path_from_root('src', 'library_fetch.js')) + options.js_libraries.append(shared.path_from_root('src', 'library_fetch.js')) forced_stdlibs = [] if shared.Settings.DEMANGLE_SUPPORT: @@ -1171,7 +1013,7 @@ def check(input_file): else: logging.debug('using response file for EXPORTED_FUNCTIONS, make sure it includes _malloc and _free') - assert not (bind and shared.Settings.NO_DYNAMIC_EXECUTION), 'NO_DYNAMIC_EXECUTION disallows embind' + assert not (options.bind and shared.Settings.NO_DYNAMIC_EXECUTION), 'NO_DYNAMIC_EXECUTION disallows embind' assert not (shared.Settings.NO_DYNAMIC_EXECUTION and shared.Settings.RELOCATABLE), 'cannot have both NO_DYNAMIC_EXECUTION and RELOCATABLE enabled at the same time, since RELOCATABLE needs to eval()' @@ -1188,26 +1030,26 @@ def check(input_file): #shared.Settings.GLOBAL_BASE = 8*256 # keep enough space at the bottom for a full stack frame, for z-interpreter shared.Settings.SIMPLIFY_IFS = 0 # this is just harmful for emterpreting shared.Settings.EXPORTED_FUNCTIONS += ['emterpret'] - if not js_opts: + if not options.js_opts: logging.debug('enabling js opts for EMTERPRETIFY') - js_opts = True - force_js_opts = True - assert use_closure_compiler is not 2, 'EMTERPRETIFY requires valid asm.js, and is incompatible with closure 2 which disables that' + options.js_opts = True + options.force_js_opts = True + assert options.use_closure_compiler is not 2, 'EMTERPRETIFY requires valid asm.js, and is incompatible with closure 2 which disables that' if shared.Settings.DEAD_FUNCTIONS: - if not js_opts: + if not options.js_opts: logging.debug('enabling js opts for DEAD_FUNCTIONS') - js_opts = True - force_js_opts = True + options.js_opts = True + options.force_js_opts = True - if proxy_to_worker: + if options.proxy_to_worker: shared.Settings.PROXY_TO_WORKER = 1 - if use_preload_plugins or len(preload_files) > 0 or len(embed_files) > 0: + if options.use_preload_plugins or len(options.preload_files) > 0 or len(options.embed_files) > 0: # if we include any files, or intend to use preload plugins, then we definitely need filesystem support shared.Settings.FORCE_FILESYSTEM = 1 - if proxy_to_worker or use_preload_plugins: + if options.proxy_to_worker or options.use_preload_plugins: shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['$Browser'] if not shared.Settings.NO_FILESYSTEM and not shared.Settings.ONLY_MY_CODE: @@ -1219,11 +1061,11 @@ def check(input_file): if shared.Settings.USE_PTHREADS: if not any(s.startswith('PTHREAD_POOL_SIZE=') for s in settings_changes): settings_changes.append('PTHREAD_POOL_SIZE=0') - js_libraries.append(shared.path_from_root('src', 'library_pthread.js')) + options.js_libraries.append(shared.path_from_root('src', 'library_pthread.js')) newargs.append('-D__EMSCRIPTEN_PTHREADS__=1') shared.Settings.FORCE_FILESYSTEM = 1 # proxying of utime requires the filesystem else: - js_libraries.append(shared.path_from_root('src', 'library_pthread_stub.js')) + options.js_libraries.append(shared.path_from_root('src', 'library_pthread_stub.js')) if shared.Settings.USE_PTHREADS: if shared.Settings.LINKABLE: @@ -1240,10 +1082,10 @@ def check(input_file): exit(1) if shared.Settings.OUTLINING_LIMIT: - if not js_opts: + if not options.js_opts: logging.debug('enabling js opts as optional functionality implemented as a js opt was requested') - js_opts = True - force_js_opts = True + options.js_opts = True + options.force_js_opts = True if shared.Settings.WASM: shared.Settings.BINARYEN = 1 # these are synonyms @@ -1263,7 +1105,7 @@ def check(input_file): assert shared.Settings.BINARYEN_MEM_MAX == -1 or shared.Settings.BINARYEN_MEM_MAX % 65536 == 0, 'BINARYEN_MEM_MAX must be a multiple of 64KB, was ' + str(shared.Settings.BINARYEN_MEM_MAX) if shared.Settings.WASM_BACKEND: - js_opts = None + options.js_opts = None shared.Settings.BINARYEN = 1 # Static linking is tricky with LLVM, since e.g. memset might not be used from libc, # but be used as an intrinsic, and codegen will generate a libc call from that intrinsic @@ -1284,6 +1126,9 @@ def check(input_file): shared.Settings.GLOBAL_BASE = 1024 # leave some room for mapping global vars assert not shared.Settings.SPLIT_MEMORY, 'WebAssembly does not support split memory' assert not shared.Settings.USE_PTHREADS, 'WebAssembly does not support pthreads' + if shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS: + logging.warning('for wasm there is no need to set ELIMINATE_DUPLICATE_FUNCTIONS, the binaryen optimizer does it automatically') + shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS = 0 # if root was not specified in -s, it might be fixed in ~/.emscripten, copy from there if not shared.Settings.BINARYEN_ROOT: try: @@ -1294,18 +1139,14 @@ def check(input_file): # also always use f32s when asm.js is not in the picture if ('PRECISE_F32=0' not in settings_changes and 'PRECISE_F32=2' not in settings_changes) or 'asmjs' not in shared.Settings.BINARYEN_METHOD: shared.Settings.PRECISE_F32 = 1 - if js_opts and not force_js_opts and 'asmjs' not in shared.Settings.BINARYEN_METHOD: - js_opts = None + if options.js_opts and not options.force_js_opts and 'asmjs' not in shared.Settings.BINARYEN_METHOD: + options.js_opts = None logging.debug('asm.js opts not forced by user or an option that depends them, and we do not intend to run the asm.js, so disabling and leaving opts to the binaryen optimizer') - if use_closure_compiler: - logging.warning('closure compiler is known to have issues with binaryen (FIXME)') + assert not options.use_closure_compiler == 2, 'closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm' # for simplicity, we always have a mem init file, which may also be imported into the wasm module. # * if we also supported js mem inits we'd have 4 modes # * and js mem inits are useful for avoiding a side file, but the wasm module avoids that anyhow - memory_init_file = True - if shared.Building.is_wasm_only() and shared.Settings.EVAL_CTORS: - logging.debug('disabling EVAL_CTORS, as in wasm-only mode it hurts more than it helps. TODO: a wasm version of it') - shared.Settings.EVAL_CTORS = 0 + options.memory_init_file = True # async compilation requires wasm-only mode, and also not interpreting (the interpreter needs sync input) if shared.Settings.BINARYEN_ASYNC_COMPILATION == 1 and shared.Building.is_wasm_only() and 'interpret' not in shared.Settings.BINARYEN_METHOD: # async compilation requires a swappable module - we swap it in when it's ready @@ -1324,9 +1165,18 @@ def check(input_file): sys.exit(1) if shared.Settings.EVAL_CTORS: - # this option is not a js optimizer pass, but does run the js optimizer internally, so - # we need to generate proper code for that - shared.Settings.RUNNING_JS_OPTS = 1 + if not shared.Settings.BINARYEN: + # for asm.js: this option is not a js optimizer pass, but does run the js optimizer internally, so + # we need to generate proper code for that (for wasm, we run a binaryen tool for this) + shared.Settings.RUNNING_JS_OPTS = 1 + else: + if 'interpret' in shared.Settings.BINARYEN_METHOD: + logging.warning('disabling EVAL_CTORS as the bundled interpreter confuses the ctor tool') + shared.Settings.EVAL_CTORS = 0 + else: + # for wasm, we really want no-exit-runtime, so that atexits don't stop us + if not shared.Settings.NO_EXIT_RUNTIME: + logging.warning('you should enable -s NO_EXIT_RUNTIME=1 so that EVAL_CTORS can work at full efficiency (it gets rid of atexit calls which might disrupt EVAL_CTORS)') if shared.Settings.ALLOW_MEMORY_GROWTH and shared.Settings.ASM_JS == 1: # this is an issue in asm.js, but not wasm @@ -1334,22 +1184,22 @@ def check(input_file): shared.WarningManager.warn('ALMOST_ASM') shared.Settings.ASM_JS = 2 # memory growth does not validate as asm.js http://discourse.wicg.io/t/request-for-comments-switching-resizing-heaps-in-asm-js/641/23 - if js_opts: + if options.js_opts: shared.Settings.RUNNING_JS_OPTS = 1 if shared.Settings.CYBERDWARF: newargs.append('-g') shared.Settings.BUNDLED_CD_DEBUG_FILE = target + ".cd" - js_libraries.append(shared.path_from_root('src', 'library_cyberdwarf.js')) - js_libraries.append(shared.path_from_root('src', 'library_debugger_toolkit.js')) + options.js_libraries.append(shared.path_from_root('src', 'library_cyberdwarf.js')) + options.js_libraries.append(shared.path_from_root('src', 'library_debugger_toolkit.js')) - if tracing: + if options.tracing: if shared.Settings.ALLOW_MEMORY_GROWTH: shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE += ['emscripten_trace_report_memory_layout'] if shared.Settings.ONLY_MY_CODE: shared.Settings.DEFAULT_LIBRARY_FUNCS_TO_INCLUDE = [] - separate_asm = True + options.separate_asm = True shared.Settings.FINALIZE_ASM_JS = False if shared.Settings.GLOBAL_BASE < 0: @@ -1362,13 +1212,16 @@ def check(input_file): # We leave the -O option in place so that the clang front-end runs in that # optimization mode, but we disable the actual optimization passes, as we'll # run them separately. - if opt_level > 0: + if options.opt_level > 0: newargs.append('-mllvm') newargs.append('-disable-llvm-optzns') + if not shared.Settings.LEGALIZE_JS_FFI: + assert shared.Building.is_wasm_only(), 'LEGALIZE_JS_FFI incompatible with RUNNING_JS_OPTS and non-wasm BINARYEN_METHOD.' + shared.Settings.EMSCRIPTEN_VERSION = shared.EMSCRIPTEN_VERSION - shared.Settings.OPT_LEVEL = opt_level - shared.Settings.DEBUG_LEVEL = debug_level + shared.Settings.OPT_LEVEL = options.opt_level + shared.Settings.DEBUG_LEVEL = options.debug_level ## Compile source code to bitcode @@ -1400,18 +1253,19 @@ def get_bitcode_file(input_file): # can just emit directly to the target if specified_target: if specified_target.endswith('/') or specified_target.endswith('\\') or os.path.isdir(specified_target): - return os.path.join(specified_target, os.path.basename(unsuffixed(input_file))) + default_object_extension + return os.path.join(specified_target, os.path.basename(unsuffixed(input_file))) + options.default_object_extension return specified_target return unsuffixed(input_file) + final_ending else: - if has_dash_c: return unsuffixed(input_file) + default_object_extension - return in_temp(unsuffixed(uniquename(input_file)) + default_object_extension) + if has_dash_c: return unsuffixed(input_file) + options.default_object_extension + return in_temp(unsuffixed(uniquename(input_file)) + options.default_object_extension) # Request LLVM debug info if explicitly specified, or building bitcode with -g, or if building a source all the way to JS with -g - if debug_level >= 4 or ((final_suffix not in JS_CONTAINING_SUFFIXES or (has_source_inputs and final_suffix in JS_CONTAINING_SUFFIXES)) and requested_debug == '-g'): - if debug_level == 4 or not (final_suffix in JS_CONTAINING_SUFFIXES and js_opts): # do not save llvm debug info if js optimizer will wipe it out anyhow (but if source maps are used, keep it) + if options.debug_level >= 4 or ((final_suffix not in JS_CONTAINING_SUFFIXES or (has_source_inputs and final_suffix in JS_CONTAINING_SUFFIXES)) and options.requested_debug == '-g'): + # do not save llvm debug info if js optimizer will wipe it out anyhow (but if source maps are used, keep it) + if options.debug_level == 4 or not (final_suffix in JS_CONTAINING_SUFFIXES and options.js_opts): newargs.append('-g') # preserve LLVM debug info - debug_level = 4 + options.debug_level = 4 # Bitcode args generation code def get_bitcode_args(input_files): @@ -1478,7 +1332,7 @@ def compile_source_file(i, input_file): assert len(temp_files) == len(input_files) # Optimize source files - if llvm_opts > 0: + if options.llvm_opts > 0: for pos, (_, input_file) in enumerate(input_files): file_ending = filename_type_ending(input_file) if file_ending.endswith(SOURCE_ENDINGS): @@ -1486,7 +1340,7 @@ def compile_source_file(i, input_file): logging.debug('optimizing %s', input_file) #if DEBUG: shutil.copyfile(temp_file, os.path.join(shared.configuration.CANONICAL_TEMP_DIR, 'to_opt.bc')) # useful when LLVM opt aborts new_temp_file = in_temp(unsuffixed(uniquename(temp_file)) + '.o') - shared.Building.llvm_opt(temp_file, llvm_opts, new_temp_file) + shared.Building.llvm_opt(temp_file, options.llvm_opts, new_temp_file) temp_files[pos] = (temp_files[pos][0], new_temp_file) # Decide what we will link @@ -1513,7 +1367,7 @@ def compile_source_file(i, input_file): # 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) + deps = deps.replace(temp_output_base + options.default_object_extension, specified_target) 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: @@ -1592,24 +1446,13 @@ def get_final(): with ToolchainProfiler.profile_block('post-link'): if DEBUG: - emscripten_temp_dir = shared.get_emscripten_temp_dir() logging.debug('saving intermediate processing steps to %s', emscripten_temp_dir) - - class Intermediate: - counter = 0 - def save_intermediate(name=None, suffix='js'): - name = os.path.join(emscripten_temp_dir, 'emcc-%d%s.%s' % (Intermediate.counter, '' if name is None else '-' + name, suffix)) - if type(final) != str: - logging.debug('(not saving intermediate %s because deferring linking)' % name) - return - shutil.copyfile(final, name) - Intermediate.counter += 1 - if not LEAVE_INPUTS_RAW: save_intermediate('basebc', 'bc') # Optimize, if asked to if not LEAVE_INPUTS_RAW: - link_opts = [] if debug_level >= 4 or shared.Settings.CYBERDWARF else ['-strip-debug'] # remove LLVM debug if we are not asked for it + # remove LLVM debug if we are not asked for it + link_opts = [] if options.debug_level >= 4 or shared.Settings.CYBERDWARF else ['-strip-debug'] if not shared.Settings.ASSERTIONS: link_opts += ['-disable-verify'] else: @@ -1618,13 +1461,13 @@ def save_intermediate(name=None, suffix='js'): # something similar, which we can do with a param to opt link_opts += ['-disable-debug-info-type-map'] - if llvm_lto >= 2 and llvm_opts > 0: + if options.llvm_lto >= 2 and options.llvm_opts > 0: logging.debug('running LLVM opts as pre-LTO') - final = shared.Building.llvm_opt(final, llvm_opts, DEFAULT_FINAL) + final = shared.Building.llvm_opt(final, options.llvm_opts, DEFAULT_FINAL) if DEBUG: save_intermediate('opt', 'bc') # If we can LTO, do it before dce, since it opens up dce opportunities - if shared.Building.can_build_standalone() and llvm_lto and llvm_lto != 2: + if shared.Building.can_build_standalone() and options.llvm_lto and options.llvm_lto != 2: if not shared.Building.can_inline(): link_opts.append('-disable-inlining') # add a manual internalize with the proper things we need to be kept alive during lto link_opts += shared.Building.get_safe_internalize() + ['-std-link-opts'] @@ -1636,7 +1479,7 @@ def save_intermediate(name=None, suffix='js'): # At minimum remove dead functions etc., this potentially saves a lot in the size of the generated code (and the time to compile it) link_opts += shared.Building.get_safe_internalize() + ['-globaldce'] - if cfi: + if options.cfi: if use_cxx: link_opts.append("-wholeprogramdevirt") link_opts.append("-lowertypetests") @@ -1650,13 +1493,13 @@ def save_intermediate(name=None, suffix='js'): if len(link_opts) > 0: final = shared.Building.llvm_opt(final, link_opts, DEFAULT_FINAL) if DEBUG: save_intermediate('linktime', 'bc') - if save_bc: - shutil.copyfile(final, save_bc) + if options.save_bc: + shutil.copyfile(final, options.save_bc) # Prepare .ll for Emscripten if LEAVE_INPUTS_RAW: assert len(input_files) == 1 - if DEBUG and save_bc: save_intermediate('ll', 'll') + if DEBUG and options.save_bc: save_intermediate('ll', 'll') if AUTODEBUG: logging.debug('autodebug') @@ -1673,8 +1516,10 @@ def save_intermediate(name=None, suffix='js'): with ToolchainProfiler.profile_block('emscript'): # Emscripten logging.debug('LLVM => JS') - extra_args = [] if not js_libraries else ['--libraries', ','.join(map(os.path.abspath, js_libraries))] - if memory_init_file: + extra_args = [] + if options.js_libraries: + extra_args = ['--libraries', ','.join(map(os.path.abspath, options.js_libraries))] + if options.memory_init_file: shared.Settings.MEM_INIT_METHOD = 1 else: assert shared.Settings.MEM_INIT_METHOD != 1 @@ -1699,58 +1544,60 @@ def save_intermediate(name=None, suffix='js'): with ToolchainProfiler.profile_block('source transforms'): # Embed and preload files if shared.Settings.SPLIT_MEMORY: - no_heap_copy = True # copying into the heap is risky when split - the chunks might be too small for the file package! + options.no_heap_copy = True # copying into the heap is risky when split - the chunks might be too small for the file package! - if len(preload_files) + len(embed_files) > 0: + if len(options.preload_files) + len(options.embed_files) > 0: logging.debug('setting up files') file_args = [] - if len(preload_files) > 0: + if len(options.preload_files) > 0: file_args.append('--preload') - file_args += preload_files - if len(embed_files) > 0: + file_args += options.preload_files + if len(options.embed_files) > 0: file_args.append('--embed') - file_args += embed_files - if len(exclude_files) > 0: + file_args += options.embed_files + if len(options.exclude_files) > 0: file_args.append('--exclude') - file_args += exclude_files - if use_preload_cache: + file_args += options.exclude_files + if options.use_preload_cache: file_args.append('--use-preload-cache') - if no_heap_copy: + if options.no_heap_copy: file_args.append('--no-heap-copy') - if not use_closure_compiler: + if not options.use_closure_compiler: file_args.append('--no-closure') if shared.Settings.LZ4: file_args.append('--lz4') - if use_preload_plugins: + if options.use_preload_plugins: file_args.append('--use-preload-plugins') file_code = execute([shared.PYTHON, shared.FILE_PACKAGER, unsuffixed(target) + '.data'] + file_args, stdout=PIPE)[0] - pre_js = file_code + pre_js + options.pre_js = file_code + options.pre_js # Apply pre and postjs files - if pre_js or post_module or post_js: + if options.pre_js or options.post_module or options.post_js: logging.debug('applying pre/postjses') src = open(final).read() - if post_module: - src = src.replace('// {{PREAMBLE_ADDITIONS}}', post_module + '\n// {{PREAMBLE_ADDITIONS}}') + if options.post_module: + src = src.replace('// {{PREAMBLE_ADDITIONS}}', options.post_module + '\n// {{PREAMBLE_ADDITIONS}}') final += '.pp.js' if WINDOWS: # Avoid duplicating \r\n to \r\r\n when writing out. - if pre_js: pre_js = pre_js.replace('\r\n', '\n') - if post_js: post_js = post_js.replace('\r\n', '\n') + if options.pre_js: + options.pre_js = options.pre_js.replace('\r\n', '\n') + if options.post_js: + options.post_js = options.post_js.replace('\r\n', '\n') outfile = open(final, 'w') - outfile.write(pre_js) + outfile.write(options.pre_js) outfile.write(src) # this may be large, don't join it to others - outfile.write(post_js) + outfile.write(options.post_js) outfile.close() - pre_js = src = post_js = None + options.pre_js = src = options.post_js = None if DEBUG: save_intermediate('pre-post') # Apply a source code transformation, if requested - if js_transform: + if options.js_transform: shutil.copyfile(final, final + '.tr.js') final += '.tr.js' posix = True if not shared.WINDOWS else False - logging.debug('applying transform: %s', js_transform) - subprocess.check_call(shlex.split(js_transform, posix=posix) + [os.path.abspath(final)]) + logging.debug('applying transform: %s', options.js_transform) + subprocess.check_call(shlex.split(options.js_transform, posix=posix) + [os.path.abspath(final)]) if DEBUG: save_intermediate('transformed') js_transform_tempfiles = [final] @@ -1759,6 +1606,7 @@ def save_intermediate(name=None, suffix='js'): log_time('source transforms') with ToolchainProfiler.profile_block('memory initializer'): + memfile = None if shared.Settings.MEM_INIT_METHOD > 0: memfile = target + '.mem' shared.try_delete(memfile) @@ -1770,7 +1618,7 @@ def repl(m): while membytes and membytes[-1] == 0: membytes.pop() if not membytes: return '' - if not memory_init_file: + if not options.memory_init_file: # memory initializer in a string literal return "memoryInitializer = '%s';" % shared.JS.generate_string_initializer(list(membytes)) open(memfile, 'wb').write(''.join(map(chr, membytes))) @@ -1781,7 +1629,9 @@ def repl(m): return 'memoryInitializer = "%s";' % os.path.basename(memfile) else: # with wasm, we may have the mem init file in the wasm binary already - return 'memoryInitializer = Module["wasmJSMethod"].indexOf("asmjs") >= 0 || Module["wasmJSMethod"].indexOf("interpret-asm2wasm") >= 0 ? "%s" : null;' % os.path.basename(memfile) + return ('memoryInitializer = Module["wasmJSMethod"].indexOf("asmjs") >= 0 || ' + 'Module["wasmJSMethod"].indexOf("interpret-asm2wasm") >= 0 ? "%s" : null;' + % os.path.basename(memfile)) src = re.sub(shared.JS.memory_initializer_pattern, repl, open(final).read(), count=1) open(final + '.mem.js', 'w').write(src) final += '.mem.js' @@ -1793,7 +1643,7 @@ def repl(m): logging.debug('wrote memory initialization to %s', memfile) else: logging.debug('did not see memory initialization') - elif not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE and debug_level < 4: + elif not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE and options.debug_level < 4: # not writing a binary init, but we can at least optimize them by splitting them up src = open(final).read() src = shared.JS.optimize_initializer(src) @@ -1804,7 +1654,9 @@ def repl(m): src = None if shared.Settings.USE_PTHREADS: - shutil.copyfile(shared.path_from_root('src', 'pthread-main.js'), os.path.join(os.path.dirname(os.path.abspath(target)), 'pthread-main.js')) + target_dir = os.path.dirname(os.path.abspath(target)) + shutil.copyfile(shared.path_from_root('src', 'pthread-main.js'), + os.path.join(target_dir, 'pthread-main.js')) # Generate the fetch-worker.js script for multithreaded emscripten_fetch() support if targeting pthreads. if shared.Settings.FETCH and shared.Settings.USE_PTHREADS: @@ -1813,95 +1665,25 @@ def repl(m): # exit block 'memory initializer' log_time('memory initializer') + optimizer = JSOptimizer( + target=target, + options=options, + misc_temp_files=misc_temp_files, + js_transform_tempfiles=js_transform_tempfiles, + ) with ToolchainProfiler.profile_block('js opts'): # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing - class JSOptimizer: - queue = [] - extra_info = {} - queue_history = [] - blacklist = (os.environ.get('EMCC_JSOPT_BLACKLIST') or '').split(',') - minify_whitespace = False - cleanup_shell = False - - @staticmethod - def flush(title='js_opts'): - JSOptimizer.queue = filter(lambda p: p not in JSOptimizer.blacklist, JSOptimizer.queue) - - assert not shared.Settings.WASM_BACKEND, 'JSOptimizer should not run with pure wasm output' - - if JSOptimizer.extra_info is not None and len(JSOptimizer.extra_info) == 0: - JSOptimizer.extra_info = None - - if len(JSOptimizer.queue) > 0 and not(not shared.Settings.ASM_JS and len(JSOptimizer.queue) == 1 and JSOptimizer.queue[0] == 'last'): - - def run_passes(passes, title, just_split, just_concat): - global final, target - passes = ['asm'] + passes - if shared.Settings.PRECISE_F32: - passes = ['asmPreciseF32'] + passes - if (emit_symbol_map or shared.Settings.CYBERDWARF) and 'minifyNames' in passes: - passes += ['symbolMap=' + target + '.symbols'] - if profiling_funcs and 'minifyNames' in passes: - passes += ['profilingFuncs'] - if JSOptimizer.minify_whitespace and 'last' in passes: - passes += ['minifyWhitespace'] - if JSOptimizer.cleanup_shell and 'last' in passes: - passes += ['cleanup'] - logging.debug('applying js optimization passes: %s', ' '.join(passes)) - misc_temp_files.note(final) - final = shared.Building.js_optimizer(final, passes, debug_level >= 4, JSOptimizer.extra_info, just_split=just_split, just_concat=just_concat) - misc_temp_files.note(final) - js_transform_tempfiles.append(final) - if DEBUG: save_intermediate(title, suffix='js' if 'emitJSON' not in passes else 'json') - - passes = JSOptimizer.queue[:] - - if DEBUG != '2' or len(passes) < 2: - # by assumption, our input is JS, and our output is JS. If a pass is going to run in the native optimizer in C++, then we - # must give it JSON and receive from it JSON - chunks = [] - curr = [] - for p in passes: - if len(curr) == 0: - curr.append(p) - else: - native = shared.js_optimizer.use_native(p, source_map=debug_level >= 4) - last_native = shared.js_optimizer.use_native(curr[-1], source_map=debug_level >= 4) - if native == last_native: - curr.append(p) - else: - curr.append('emitJSON') - chunks.append(curr) - curr = ['receiveJSON', p] - if len(curr) > 0: - chunks.append(curr) - if len(chunks) == 1: - run_passes(chunks[0], title, just_split=False, just_concat=False) - else: - for i in range(len(chunks)): - run_passes(chunks[i], 'js_opts_' + str(i), just_split='receiveJSON' in chunks[i], just_concat='emitJSON' in chunks[i]) - else: - # DEBUG 2, run each pass separately - extra_info = JSOptimizer.extra_info - for p in passes: - JSOptimizer.queue = [p] - JSOptimizer.flush(p) - JSOptimizer.extra_info = extra_info # flush wipes it - log_time('part of js opts') - JSOptimizer.queue_history += JSOptimizer.queue - JSOptimizer.queue = [] - JSOptimizer.extra_info = {} if shared.Settings.DEAD_FUNCTIONS: - JSOptimizer.queue += ['eliminateDeadFuncs'] - JSOptimizer.extra_info['dead_functions'] = shared.Settings.DEAD_FUNCTIONS + optimizer.queue += ['eliminateDeadFuncs'] + optimizer.extra_info['dead_functions'] = shared.Settings.DEAD_FUNCTIONS - if opt_level >= 1 and js_opts: + if options.opt_level >= 1 and options.js_opts: logging.debug('running js post-opts') if DEBUG == '2': # Clean up the syntax a bit - JSOptimizer.queue += ['noop'] + optimizer.queue += ['noop'] def get_eliminate(): if shared.Settings.ALLOW_MEMORY_GROWTH: @@ -1909,87 +1691,76 @@ def get_eliminate(): else: return 'eliminate' - if opt_level >= 2: - JSOptimizer.queue += [get_eliminate()] + if options.opt_level >= 2: + optimizer.queue += [get_eliminate()] if shared.Settings.AGGRESSIVE_VARIABLE_ELIMINATION: # note that this happens before registerize/minification, which can obfuscate the name of 'label', which is tricky - JSOptimizer.queue += ['aggressiveVariableElimination'] + optimizer.queue += ['aggressiveVariableElimination'] - JSOptimizer.queue += ['simplifyExpressions'] + optimizer.queue += ['simplifyExpressions'] if shared.Settings.EMTERPRETIFY: # emterpreter code will not run through a JS optimizing JIT, do more work ourselves - JSOptimizer.queue += ['localCSE'] + optimizer.queue += ['localCSE'] if shared.Settings.EMTERPRETIFY: # add explicit label setting, as we will run aggressiveVariableElimination late, *after* 'label' is no longer notable by name - JSOptimizer.queue += ['safeLabelSetting'] + optimizer.queue += ['safeLabelSetting'] - if opt_level >= 1 and js_opts: - if opt_level >= 2: + if options.opt_level >= 1 and options.js_opts: + if options.opt_level >= 2: # simplify ifs if it is ok to make the code somewhat unreadable, and unless outlining (simplified ifs # with commaified code breaks late aggressive variable elimination) - if shared.Settings.SIMPLIFY_IFS and (debug_level == 0 or profiling) and shared.Settings.OUTLINING_LIMIT == 0: JSOptimizer.queue += ['simplifyIfs'] - - if shared.Settings.PRECISE_F32: JSOptimizer.queue += ['optimizeFrounds'] - - def do_minify(): # minifies the code. this is also when we do certain optimizations that must be done right before or after minification - if shared.Settings.SPLIT_MEMORY: JSOptimizer.queue += ['splitMemory', 'simplifyExpressions'] # must be done before minification + # do not do this with binaryen, as commaifying confuses binaryen call type detection (FIXME, in theory, but unimportant) + debugging = options.debug_level == 0 or options.profiling + if shared.Settings.SIMPLIFY_IFS and debugging and shared.Settings.OUTLINING_LIMIT == 0 and not shared.Settings.BINARYEN: + optimizer.queue += ['simplifyIfs'] - if opt_level >= 2: - if debug_level < 2 and not use_closure_compiler == 2: - JSOptimizer.queue += ['minifyNames'] - if debug_level == 0: - JSOptimizer.minify_whitespace = True + if shared.Settings.PRECISE_F32: optimizer.queue += ['optimizeFrounds'] - if use_closure_compiler == 1: - JSOptimizer.queue += ['closure'] - elif debug_level <= 2 and shared.Settings.FINALIZE_ASM_JS and not use_closure_compiler: - JSOptimizer.cleanup_shell = True - - if js_opts: - if shared.Settings.SAFE_HEAP: JSOptimizer.queue += ['safeHeap'] + if options.js_opts: + if shared.Settings.SAFE_HEAP: optimizer.queue += ['safeHeap'] if shared.Settings.OUTLINING_LIMIT > 0: - JSOptimizer.queue += ['outline'] - JSOptimizer.extra_info['sizeToOutline'] = shared.Settings.OUTLINING_LIMIT + optimizer.queue += ['outline'] + optimizer.extra_info['sizeToOutline'] = shared.Settings.OUTLINING_LIMIT - if opt_level >= 2 and debug_level < 3: - if opt_level >= 3 or shrink_level > 0: - JSOptimizer.queue += ['registerizeHarder'] + if options.opt_level >= 2 and options.debug_level < 3: + if options.opt_level >= 3 or options.shrink_level > 0: + optimizer.queue += ['registerizeHarder'] else: - JSOptimizer.queue += ['registerize'] + optimizer.queue += ['registerize'] # NOTE: Important that this comes after registerize/registerizeHarder - if shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS and opt_level >= 2: - JSOptimizer.flush() + if shared.Settings.ELIMINATE_DUPLICATE_FUNCTIONS and options.opt_level >= 2: + optimizer.flush() shared.Building.eliminate_duplicate_funcs(final) - if shared.Settings.EVAL_CTORS and memory_init_file and debug_level < 4: - JSOptimizer.flush() + if shared.Settings.EVAL_CTORS and options.memory_init_file and options.debug_level < 4 and not shared.Settings.BINARYEN: + optimizer.flush() shared.Building.eval_ctors(final, memfile) if DEBUG: save_intermediate('eval-ctors', 'js') - if js_opts: + if options.js_opts: # some compilation modes require us to minify later or not at all if not shared.Settings.EMTERPRETIFY and not shared.Settings.BINARYEN: - do_minify() + optimizer.do_minify() - if opt_level >= 2: - JSOptimizer.queue += ['asmLastOpts'] + if options.opt_level >= 2: + optimizer.queue += ['asmLastOpts'] - if shared.Settings.FINALIZE_ASM_JS: JSOptimizer.queue += ['last'] + if shared.Settings.FINALIZE_ASM_JS: optimizer.queue += ['last'] - JSOptimizer.flush() + optimizer.flush() - if use_closure_compiler == 2: - JSOptimizer.flush() + if options.use_closure_compiler == 2: + optimizer.flush() logging.debug('running closure') # no need to add this to js_transform_tempfiles, because closure and # debug_level > 0 are never simultaneously true - final = shared.Building.closure_compiler(final, pretty=debug_level >= 1) + final = shared.Building.closure_compiler(final, pretty=options.debug_level >= 1) if DEBUG: save_intermediate('closure') log_time('js opts') @@ -1997,56 +1768,7 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati with ToolchainProfiler.profile_block('final emitting'): if shared.Settings.EMTERPRETIFY: - JSOptimizer.flush() - logging.debug('emterpretifying') - import json - try: - # move temp js to final position, alongside its mem init file - shutil.move(final, js_target) - args = [shared.PYTHON, shared.path_from_root('tools', 'emterpretify.py'), js_target, final + '.em.js', json.dumps(shared.Settings.EMTERPRETIFY_BLACKLIST), json.dumps(shared.Settings.EMTERPRETIFY_WHITELIST), '', str(shared.Settings.SWAPPABLE_ASM_MODULE)] - if shared.Settings.EMTERPRETIFY_ASYNC: - args += ['ASYNC=1'] - if shared.Settings.EMTERPRETIFY_ADVISE: - args += ['ADVISE=1'] - if profiling or profiling_funcs: - args += ['PROFILING=1'] - if shared.Settings.ASSERTIONS: - args += ['ASSERTIONS=1'] - if shared.Settings.PRECISE_F32: - args += ['FROUND=1'] - if shared.Settings.ALLOW_MEMORY_GROWTH: - args += ['MEMORY_SAFE=1'] - if shared.Settings.EMTERPRETIFY_FILE: - args += ['FILE="' + shared.Settings.EMTERPRETIFY_FILE + '"'] - execute(args) - final = final + '.em.js' - finally: - shared.try_delete(js_target) - - if shared.Settings.EMTERPRETIFY_ADVISE: - logging.warning('halting compilation due to EMTERPRETIFY_ADVISE') - sys.exit(0) - - # minify (if requested) after emterpreter processing, and finalize output - logging.debug('finalizing emterpreted code') - shared.Settings.FINALIZE_ASM_JS = 1 - if not shared.Settings.BINARYEN: - do_minify() - JSOptimizer.queue += ['last'] - JSOptimizer.flush() - - # finalize the original as well, if we will be swapping it in (TODO: add specific option for this) - if shared.Settings.SWAPPABLE_ASM_MODULE: - real = final - original = js_target + '.orig.js' # the emterpretify tool saves the original here - final = original - logging.debug('finalizing original (non-emterpreted) code at ' + final) - if not shared.Settings.BINARYEN: - do_minify() - JSOptimizer.queue += ['last'] - JSOptimizer.flush() - safe_move(final, original) - final = real + emterpretify(js_target, optimizer, options) # Remove some trivial whitespace # TODO: do not run when compress has already been done on all parts of the code #src = open(final).read() @@ -2057,182 +1779,23 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati if shared.Settings.CYBERDWARF: execute([shared.PYTHON, shared.path_from_root('tools', 'emdebug_cd_merger.py'), target + '.cd', target+'.symbols']) - # Emit source maps, if needed - if debug_level >= 4: - logging.debug('generating source maps') - jsrun.run_js(shared.path_from_root('tools', 'source-maps', 'sourcemapper.js'), - shared.NODE_JS, js_transform_tempfiles + - ['--sourceRoot', os.getcwd(), - '--mapFileBaseName', target, - '--offset', str(0)]) + if options.debug_level >= 4: + emit_source_maps(target, optimizer.js_transform_tempfiles) # track files that will need native eols generated_text_files_with_native_eols = [] - # Separate out the asm.js code, if asked. Or, if necessary for another option - if (separate_asm or shared.Settings.BINARYEN) and not shared.Settings.WASM_BACKEND: - logging.debug('separating asm') - subprocess.check_call([shared.PYTHON, shared.path_from_root('tools', 'separate_asm.py'), final, asm_target, final]) + if (options.separate_asm or shared.Settings.BINARYEN) and not shared.Settings.WASM_BACKEND: + separate_asm_js(final, asm_target) generated_text_files_with_native_eols += [asm_target] - # extra only-my-code logic - if shared.Settings.ONLY_MY_CODE: - temp = asm_target + '.only.js' - print jsrun.run_js(shared.path_from_root('tools', 'js-optimizer.js'), shared.NODE_JS, args=[asm_target, 'eliminateDeadGlobals', 'last', 'asm'], stdout=open(temp, 'w')) - shutil.move(temp, asm_target) - - if shared.Settings.BINARYEN_METHOD: - methods = shared.Settings.BINARYEN_METHOD.split(',') - valid_methods = ['asmjs', 'native-wasm', 'interpret-s-expr', 'interpret-binary', 'interpret-asm2wasm'] - for m in methods: - if not m.strip() in valid_methods: - logging.error('Unrecognized BINARYEN_METHOD "' + m.strip() + '" specified! Please pass a comma-delimited list containing one or more of: ' + ','.join(valid_methods)) - sys.exit(1) - + binaryen_method_sanity_check() if shared.Settings.BINARYEN: - logging.debug('using binaryen, with method: ' + shared.Settings.BINARYEN_METHOD) - binaryen_bin = os.path.join(shared.Settings.BINARYEN_ROOT, 'bin') - # Emit wasm.js at the top of the js. This is *not* optimized with the rest of the code, since - # (1) it contains asm.js, whose validation would be broken, and (2) it's very large so it would - # be slow in cleanup/JSDCE etc. - # TODO: for html, it could be a separate script tag - # We need wasm.js if there is a chance the polyfill will be used. If the user sets - # BINARYEN_METHOD with something that doesn't use the polyfill, then we don't need it. - if not shared.Settings.BINARYEN_METHOD or 'interpret' in shared.Settings.BINARYEN_METHOD: - logging.debug('integrating wasm.js polyfill interpreter') - wasm_js = open(os.path.join(binaryen_bin, 'wasm.js')).read() - wasm_js = wasm_js.replace('EMSCRIPTEN_', 'emscripten_') # do not confuse the markers - js = open(final).read() - combined = open(final, 'w') - combined.write(wasm_js) - combined.write('\n//^wasm.js\n') - combined.write(js) - combined.close() - # normally we emit binary, but for debug info, we might emit text first - wrote_wasm_text = False - # finish compiling to WebAssembly, using asm2wasm, if we didn't already emit WebAssembly directly using the wasm backend. - if not shared.Settings.WASM_BACKEND: - if DEBUG: - # save the asm.js input - shutil.copyfile(asm_target, os.path.join(emscripten_temp_dir, os.path.basename(asm_target))) - cmd = [os.path.join(binaryen_bin, 'asm2wasm'), asm_target, '--total-memory=' + str(shared.Settings.TOTAL_MEMORY)] - if shared.Settings.BINARYEN_TRAP_MODE == 'js': - cmd += ['--emit-jsified-potential-traps'] - elif shared.Settings.BINARYEN_TRAP_MODE == 'clamp': - cmd += ['--emit-clamped-potential-traps'] - elif shared.Settings.BINARYEN_TRAP_MODE == 'allow': - cmd += ['--emit-potential-traps'] - else: - logging.error('invalid BINARYEN_TRAP_MODE value: ' + shared.Settings.BINARYEN_TRAP_MODE + ' (should be js/clamp/allow)') - sys.exit(1) - if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS: - cmd += ['--ignore-implicit-traps'] - # pass optimization level to asm2wasm (if not optimizing, or which passes we should run was overridden, do not optimize) - if opt_level > 0 and not shared.Settings.BINARYEN_PASSES: - cmd.append(shared.Building.opt_level_to_str(opt_level, shrink_level)) - # import mem init file if it exists, and if we will not be using asm.js as a binaryen method (as it needs the mem init file, of course) - import_mem_init = memory_init_file and os.path.exists(memfile) and 'asmjs' not in shared.Settings.BINARYEN_METHOD and 'interpret-asm2wasm' not in shared.Settings.BINARYEN_METHOD - if import_mem_init: - cmd += ['--mem-init=' + memfile] - if not shared.Settings.RELOCATABLE: - cmd += ['--mem-base=' + str(shared.Settings.GLOBAL_BASE)] - # various options imply that the imported table may not be the exact size as the wasm module's own table segments - if shared.Settings.RELOCATABLE or shared.Settings.RESERVED_FUNCTION_POINTERS > 0 or shared.Settings.EMULATED_FUNCTION_POINTERS: - cmd += ['--table-max=-1'] - if shared.Settings.SIDE_MODULE: - cmd += ['--mem-max=-1'] - elif shared.Settings.BINARYEN_MEM_MAX >= 0: - cmd += ['--mem-max=' + str(shared.Settings.BINARYEN_MEM_MAX)] - if shared.Building.is_wasm_only(): - cmd += ['--wasm-only'] # this asm.js is code not intended to run as asm.js, it is only ever going to be wasm, an can contain special fastcomp-wasm support - if debug_level >= 2 or profiling_funcs: - cmd += ['-g'] - if emit_symbol_map or shared.Settings.CYBERDWARF: - cmd += ['--symbolmap=' + target + '.symbols'] - # we prefer to emit a binary, as it is more efficient. however, when we - # want full debug info support (not just function names), then we must - # emit text (at least until wasm gains support for debug info in binaries) - target_binary = debug_level < 3 - if target_binary: - cmd += ['-o', wasm_binary_target] - else: - cmd += ['-o', wasm_text_target, '-S'] - wrote_wasm_text = True - logging.debug('asm2wasm (asm.js => WebAssembly): ' + ' '.join(cmd)) - TimeLogger.update() - subprocess.check_call(cmd) - - if not target_binary: - cmd = [os.path.join(binaryen_bin, 'wasm-as'), wasm_text_target, '-o', wasm_binary_target] - if debug_level >= 2 or profiling_funcs: - cmd += ['-g'] - logging.debug('wasm-as (text => binary): ' + ' '.join(cmd)) - subprocess.check_call(cmd) - if import_mem_init: - # remove and forget about the mem init file in later processing; it does not need to be prefetched in the html, etc. - os.unlink(memfile) - memory_init_file = False - log_time('asm2wasm') - if shared.Settings.BINARYEN_PASSES: - shutil.move(wasm_binary_target, wasm_binary_target + '.pre') - cmd = [os.path.join(binaryen_bin, 'wasm-opt'), wasm_binary_target + '.pre', '-o', wasm_binary_target] + map(lambda p: '--' + p, shared.Settings.BINARYEN_PASSES.split(',')) - logging.debug('wasm-opt on BINARYEN_PASSES: ' + ' '.join(cmd)) - subprocess.check_call(cmd) - if not wrote_wasm_text and 'interpret-s-expr' in shared.Settings.BINARYEN_METHOD: - cmd = [os.path.join(binaryen_bin, 'wasm-dis'), wasm_binary_target, '-o', wasm_text_target] - logging.debug('wasm-dis (binary => text): ' + ' '.join(cmd)) - subprocess.check_call(cmd) - if shared.Settings.BINARYEN_SCRIPTS: - binaryen_scripts = os.path.join(shared.Settings.BINARYEN_ROOT, 'scripts') - script_env = os.environ.copy() - root_dir = os.path.abspath(os.path.dirname(__file__)) - if script_env.get('PYTHONPATH'): - script_env['PYTHONPATH'] += ':' + root_dir - else: - script_env['PYTHONPATH'] = root_dir - for script in shared.Settings.BINARYEN_SCRIPTS.split(','): - logging.debug('running binaryen script: ' + script) - subprocess.check_call([shared.PYTHON, os.path.join(binaryen_scripts, script), final, wasm_text_target], env=script_env) - # after generating the wasm, do some final operations - if not shared.Settings.WASM_BACKEND: - if shared.Settings.SIDE_MODULE: - wso = shared.WebAssembly.make_shared_library(final, wasm_binary_target) - # replace the wasm binary output with the dynamic library. TODO: use a specific suffix for such files? - shutil.move(wso, wasm_binary_target) - if not DEBUG: - os.unlink(asm_target) # we don't need the asm.js, it can just confuse - sys.exit(0) # and we are done. - if opt_level >= 2: - # minify the JS - do_minify() # calculate how to minify - if JSOptimizer.cleanup_shell or JSOptimizer.minify_whitespace or use_closure_compiler: - misc_temp_files.note(final) - if DEBUG: save_intermediate('preclean', 'js') - if use_closure_compiler: - logging.debug('running closure on shell code') - final = shared.Building.closure_compiler(final, pretty=not JSOptimizer.minify_whitespace) - else: - assert JSOptimizer.cleanup_shell - logging.debug('running cleanup on shell code') - final = shared.Building.js_optimizer_no_asmjs(final, ['noPrintMetadata', 'JSDCE', 'last'] + (['minifyWhitespace'] if JSOptimizer.minify_whitespace else [])) - if DEBUG: save_intermediate('postclean', 'js') + final = do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, + wasm_text_target, misc_temp_files, optimizer) if shared.Settings.MODULARIZE: - logging.debug('Modularizing, assigning to var ' + shared.Settings.EXPORT_NAME) - src = open(final).read() - final = final + '.modular.js' - f = open(final, 'w') - f.write('var ' + shared.Settings.EXPORT_NAME + ' = function(' + shared.Settings.EXPORT_NAME + ') {\n') - f.write(' ' + shared.Settings.EXPORT_NAME + ' = ' + shared.Settings.EXPORT_NAME + ' || {};\n') - f.write(' var Module = ' + shared.Settings.EXPORT_NAME + ';\n') # included code may refer to Module (e.g. from file packager), so alias it - f.write('\n') - f.write(src) - f.write('\n') - f.write(' return ' + shared.Settings.EXPORT_NAME + ';\n') - f.write('};\n') - f.close() - src = None - if DEBUG: save_intermediate('modularized', 'js') + final = modularize(final) # The JS is now final. Move it to its final location shutil.move(final, js_target) @@ -2241,29 +1804,565 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati # If we were asked to also generate HTML, do that if final_suffix == 'html': - logging.debug('generating HTML') - shell = open(shell_path).read() - assert '{{{ SCRIPT }}}' in shell, 'HTML shell must contain {{{ SCRIPT }}} , see src/shell.html for an example' - base_js_target = os.path.basename(js_target) - - def un_src(): # use this if you want to modify the script and need it to be inline - global script_src, script_inline - if script_src is None: return - script_inline = ''' - var script = document.createElement('script'); - script.src = "%s"; - document.body.appendChild(script); -''' % script_src - script_src = None + generate_html(target, options, js_target, target_basename, + asm_target, wasm_binary_target, + memfile, optimizer) + else: + if options.proxy_to_worker: + generate_worker_js(target, js_target, target_basename) - asm_mods = [] + for f in generated_text_files_with_native_eols: + tools.line_endings.convert_line_endings_in_file(f, os.linesep, options.output_eol) + log_time('final emitting') + # exit block 'final emitting' - if proxy_to_worker: - child_js = shared.Settings.PROXY_TO_WORKER_FILENAME or target_basename - script_inline = ''' + if DEBUG: logging.debug('total time: %.2f seconds', (time.time() - start_time)) + + finally: + if not TEMP_DIR: + try: + shutil.rmtree(temp_dir) + except: + pass + else: + logging.info('emcc saved files are in:' + temp_dir) + + +def parse_args(newargs): + options = EmccOptions() + settings_changes = [] + should_exit = False + + for i in range(len(newargs)): + # On Windows Vista (and possibly others), excessive spaces in the command line + # leak into the items in this array, so trim e.g. 'foo.cpp ' -> 'foo.cpp' + newargs[i] = newargs[i].strip() + if newargs[i].startswith('-O'): + # Let -O default to -O2, which is what gcc does. + options.requested_level = newargs[i][2:] or '2' + if options.requested_level == 's': + options.llvm_opts = ['-Os'] + options.requested_level = 2 + options.shrink_level = 1 + settings_changes.append('INLINING_LIMIT=50') + elif options.requested_level == 'z': + options.llvm_opts = ['-Oz'] + options.requested_level = 2 + options.shrink_level = 2 + settings_changes.append('INLINING_LIMIT=25') + options.opt_level = validate_arg_level(options.requested_level, 3, 'Invalid optimization level: ' + newargs[i]) + elif newargs[i].startswith('--js-opts'): + check_bad_eq(newargs[i]) + options.js_opts = eval(newargs[i+1]) + if options.js_opts: + options.force_js_opts = True + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--llvm-opts'): + check_bad_eq(newargs[i]) + options.llvm_opts = eval(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--llvm-lto'): + check_bad_eq(newargs[i]) + options.llvm_lto = eval(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--closure'): + check_bad_eq(newargs[i]) + options.use_closure_compiler = int(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--js-transform'): + check_bad_eq(newargs[i]) + options.js_transform = newargs[i+1] + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--pre-js'): + check_bad_eq(newargs[i]) + options.pre_js += open(newargs[i+1]).read() + '\n' + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--post-js'): + check_bad_eq(newargs[i]) + options.post_js += open(newargs[i+1]).read() + '\n' + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--minify'): + check_bad_eq(newargs[i]) + assert newargs[i+1] == '0', '0 is the only supported option for --minify; 1 has been deprecated' + options.debug_level = max(1, options.debug_level) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('-g'): + requested_level = newargs[i][2:] or '3' + options.debug_level = validate_arg_level(requested_level, 4, 'Invalid debug level: ' + newargs[i]) + options.requested_debug = newargs[i] + newargs[i] = '' + elif newargs[i] == '-profiling' or newargs[i] == '--profiling': + options.debug_level = 2 + options.profiling = True + newargs[i] = '' + elif newargs[i] == '-profiling-funcs' or newargs[i] == '--profiling-funcs': + options.profiling_funcs = True + newargs[i] = '' + elif newargs[i] == '--tracing' or newargs[i] == '--memoryprofiler': + if newargs[i] == '--memoryprofiler': + options.memory_profiler = True + options.tracing = True + newargs[i] = '' + newargs.append('-D__EMSCRIPTEN_TRACING__=1') + settings_changes.append("EMSCRIPTEN_TRACING=1") + options.js_libraries.append(shared.path_from_root('src', 'library_trace.js')) + elif newargs[i] == '--emit-symbol-map': + options.emit_symbol_map = True + newargs[i] = '' + elif newargs[i] == '--bind': + options.bind = True + newargs[i] = '' + options.js_libraries.append(shared.path_from_root('src', 'embind', 'emval.js')) + options.js_libraries.append(shared.path_from_root('src', 'embind', 'embind.js')) + if options.default_cxx_std: + # Force C++11 for embind code, but only if user has not explicitly overridden a standard. + options.default_cxx_std = '-std=c++11' + elif newargs[i].startswith('-std=') or newargs[i].startswith('--std='): + # User specified a standard to use, clear Emscripten from specifying it. + options.default_cxx_std = '' + elif newargs[i].startswith('--embed-file'): + check_bad_eq(newargs[i]) + options.embed_files.append(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--preload-file'): + check_bad_eq(newargs[i]) + options.preload_files.append(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--exclude-file'): + check_bad_eq(newargs[i]) + options.exclude_files.append(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--use-preload-cache'): + options.use_preload_cache = True + newargs[i] = '' + elif newargs[i].startswith('--no-heap-copy'): + options.no_heap_copy = True + newargs[i] = '' + elif newargs[i].startswith('--use-preload-plugins'): + options.use_preload_plugins = True + newargs[i] = '' + elif newargs[i] == '--ignore-dynamic-linking': + options.ignore_dynamic_linking = True + newargs[i] = '' + elif newargs[i] == '-v': + shared.COMPILER_OPTS += ['-v'] + shared.check_sanity(force=True) + newargs[i] = '' + elif newargs[i].startswith('--shell-file'): + check_bad_eq(newargs[i]) + options.shell_path = newargs[i+1] + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i].startswith('--js-library'): + check_bad_eq(newargs[i]) + options.js_libraries.append(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i] == '--remove-duplicates': + logging.warning('--remove-duplicates is deprecated as it is no longer needed. If you cannot link without it, file a bug with a testcase') + newargs[i] = '' + elif newargs[i] == '--jcache': + logging.error('jcache is no longer supported') + newargs[i] = '' + elif newargs[i] == '--cache': + check_bad_eq(newargs[i]) + os.environ['EM_CACHE'] = newargs[i+1] + shared.reconfigure_cache() + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i] == '--clear-cache': + logging.info('clearing cache as requested by --clear-cache') + shared.Cache.erase() + shared.check_sanity(force=True) # this is a good time for a sanity check + should_exit = True + elif newargs[i] == '--clear-ports': + logging.info('clearing ports and cache as requested by --clear-ports') + system_libs.Ports.erase() + shared.Cache.erase() + shared.check_sanity(force=True) # this is a good time for a sanity check + should_exit = True + elif newargs[i] == '--show-ports': + system_libs.show_ports() + should_exit = True + elif newargs[i] == '--save-bc': + check_bad_eq(newargs[i]) + options.save_bc = newargs[i+1] + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i] == '--memory-init-file': + check_bad_eq(newargs[i]) + options.memory_init_file = int(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i] == '--proxy-to-worker': + options.proxy_to_worker = True + newargs[i] = '' + elif newargs[i] == '--valid-abspath': + options.valid_abspaths.append(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' + elif newargs[i] == '--separate-asm': + options.separate_asm = True + newargs[i] = '' + elif newargs[i].startswith(('-I', '-L')): + options.path_name = newargs[i][2:] + if os.path.isabs(options.path_name) and not is_valid_abspath(options, options.path_name): + # Of course an absolute path to a non-system-specific library or header + # is fine, and you can ignore this warning. The danger are system headers + # that are e.g. x86 specific and nonportable. The emscripten bundled + # headers are modified to be portable, local system ones are generally not. + shared.WarningManager.warn( + 'ABSOLUTE_PATHS', '-I or -L of an absolute path "' + newargs[i] + + '" encountered. If this is to a local system header/library, it may ' + 'cause problems (local system files make sense for compiling natively ' + 'on your system, but not necessarily to JavaScript).') + elif newargs[i] == '--emrun': + options.emrun = True + newargs[i] = '' + elif newargs[i] == '--cpuprofiler': + options.cpu_profiler = True + newargs[i] = '' + elif newargs[i] == '--threadprofiler': + options.thread_profiler = True + settings_changes.append('PTHREADS_PROFILING=1') + newargs[i] = '' + elif newargs[i] == '--default-obj-ext': + newargs[i] = '' + options.default_object_extension = newargs[i+1] + if not options.default_object_extension.startswith('.'): + options.default_object_extension = '.' + options.default_object_extension + newargs[i+1] = '' + elif newargs[i] == '-msse': + newargs.append('-D__SSE__=1') + newargs[i] = '' + elif newargs[i] == '-msse2': + newargs.append('-D__SSE__=1') + newargs.append('-D__SSE2__=1') + newargs[i] = '' + elif newargs[i] == '-msse3': + newargs.append('-D__SSE__=1') + newargs.append('-D__SSE2__=1') + newargs.append('-D__SSE3__=1') + newargs[i] = '' + elif newargs[i] == '-mssse3': + newargs.append('-D__SSE__=1') + newargs.append('-D__SSE2__=1') + newargs.append('-D__SSE3__=1') + newargs.append('-D__SSSE3__=1') + newargs[i] = '' + elif newargs[i] == '-msse4.1': + newargs.append('-D__SSE__=1') + newargs.append('-D__SSE2__=1') + newargs.append('-D__SSE3__=1') + newargs.append('-D__SSSE3__=1') + newargs.append('-D__SSE4_1__=1') + newargs[i] = '' + elif newargs[i].startswith("-fsanitize=cfi"): + options.cfi = True + elif newargs[i] == "--output_eol": + if newargs[i+1].lower() == 'windows': + options.output_eol = '\r\n' + elif newargs[i+1].lower() == 'linux': + options.output_eol = '\n' + else: + logging.error('Invalid value "' + newargs[i+1] + '" to --output_eol!') + exit(1) + newargs[i] = '' + newargs[i+1] = '' + + if should_exit: + sys.exit(0) + + newargs = [arg for arg in newargs if arg is not ''] + return options, settings_changes, newargs + + +def emterpretify(js_target, optimizer, options): + global final + optimizer.flush() + logging.debug('emterpretifying') + import json + try: + # move temp js to final position, alongside its mem init file + shutil.move(final, js_target) + args = [shared.PYTHON, + shared.path_from_root('tools', 'emterpretify.py'), + js_target, + final + '.em.js', + json.dumps(shared.Settings.EMTERPRETIFY_BLACKLIST), + json.dumps(shared.Settings.EMTERPRETIFY_WHITELIST), + '', + str(shared.Settings.SWAPPABLE_ASM_MODULE), + ] + if shared.Settings.EMTERPRETIFY_ASYNC: + args += ['ASYNC=1'] + if shared.Settings.EMTERPRETIFY_ADVISE: + args += ['ADVISE=1'] + if options.profiling or options.profiling_funcs: + args += ['PROFILING=1'] + if shared.Settings.ASSERTIONS: + args += ['ASSERTIONS=1'] + if shared.Settings.PRECISE_F32: + args += ['FROUND=1'] + if shared.Settings.ALLOW_MEMORY_GROWTH: + args += ['MEMORY_SAFE=1'] + if shared.Settings.EMTERPRETIFY_FILE: + args += ['FILE="' + shared.Settings.EMTERPRETIFY_FILE + '"'] + execute(args) + final = final + '.em.js' + finally: + shared.try_delete(js_target) + + if shared.Settings.EMTERPRETIFY_ADVISE: + logging.warning('halting compilation due to EMTERPRETIFY_ADVISE') + sys.exit(0) + + # minify (if requested) after emterpreter processing, and finalize output + logging.debug('finalizing emterpreted code') + shared.Settings.FINALIZE_ASM_JS = 1 + if not shared.Settings.BINARYEN: + optimizer.do_minify() + optimizer.queue += ['last'] + optimizer.flush() + + # finalize the original as well, if we will be swapping it in (TODO: add specific option for this) + if shared.Settings.SWAPPABLE_ASM_MODULE: + real = final + original = js_target + '.orig.js' # the emterpretify tool saves the original here + final = original + logging.debug('finalizing original (non-emterpreted) code at ' + final) + if not shared.Settings.BINARYEN: + optimizer.do_minify() + optimizer.queue += ['last'] + optimizer.flush() + safe_move(final, original) + final = real + + +def emit_source_maps(target, js_transform_tempfiles): + logging.debug('generating source maps') + jsrun.run_js(shared.path_from_root('tools', 'source-maps', 'sourcemapper.js'), + shared.NODE_JS, js_transform_tempfiles + + ['--sourceRoot', os.getcwd(), + '--mapFileBaseName', target, + '--offset', str(0)]) + + +def separate_asm_js(final, asm_target): + """Separate out the asm.js code, if asked. Or, if necessary for another option""" + logging.debug('separating asm') + subprocess.check_call([shared.PYTHON, shared.path_from_root('tools', 'separate_asm.py'), final, asm_target, final]) + + # extra only-my-code logic + if shared.Settings.ONLY_MY_CODE: + temp = asm_target + '.only.js' + print jsrun.run_js(shared.path_from_root('tools', 'js-optimizer.js'), shared.NODE_JS, args=[asm_target, 'eliminateDeadGlobals', 'last', 'asm'], stdout=open(temp, 'w')) + shutil.move(temp, asm_target) + + +def binaryen_method_sanity_check(): + if shared.Settings.BINARYEN_METHOD: + methods = shared.Settings.BINARYEN_METHOD.split(',') + valid_methods = ['asmjs', 'native-wasm', 'interpret-s-expr', 'interpret-binary', 'interpret-asm2wasm'] + for m in methods: + if not m.strip() in valid_methods: + logging.error('Unrecognized BINARYEN_METHOD "' + m.strip() + '" specified! Please pass a comma-delimited list containing one or more of: ' + ','.join(valid_methods)) + sys.exit(1) + + +def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, + wasm_text_target, misc_temp_files, optimizer): + logging.debug('using binaryen, with method: ' + shared.Settings.BINARYEN_METHOD) + binaryen_bin = os.path.join(shared.Settings.BINARYEN_ROOT, 'bin') + # Emit wasm.js at the top of the js. This is *not* optimized with the rest of the code, since + # (1) it contains asm.js, whose validation would be broken, and (2) it's very large so it would + # be slow in cleanup/JSDCE etc. + # TODO: for html, it could be a separate script tag + # We need wasm.js if there is a chance the polyfill will be used. If the user sets + # BINARYEN_METHOD with something that doesn't use the polyfill, then we don't need it. + if not shared.Settings.BINARYEN_METHOD or 'interpret' in shared.Settings.BINARYEN_METHOD: + logging.debug('integrating wasm.js polyfill interpreter') + wasm_js = open(os.path.join(binaryen_bin, 'wasm.js')).read() + wasm_js = wasm_js.replace('EMSCRIPTEN_', 'emscripten_') # do not confuse the markers + js = open(final).read() + combined = open(final, 'w') + combined.write(wasm_js) + combined.write('\n//^wasm.js\n') + combined.write(js) + combined.close() + # normally we emit binary, but for debug info, we might emit text first + wrote_wasm_text = False + # finish compiling to WebAssembly, using asm2wasm, if we didn't already emit WebAssembly directly using the wasm backend. + if not shared.Settings.WASM_BACKEND: + if DEBUG: + # save the asm.js input + shared.safe_copy(asm_target, os.path.join(emscripten_temp_dir, os.path.basename(asm_target))) + cmd = [os.path.join(binaryen_bin, 'asm2wasm'), asm_target, '--total-memory=' + str(shared.Settings.TOTAL_MEMORY)] + if shared.Settings.BINARYEN_TRAP_MODE == 'js': + cmd += ['--emit-jsified-potential-traps'] + elif shared.Settings.BINARYEN_TRAP_MODE == 'clamp': + cmd += ['--emit-clamped-potential-traps'] + elif shared.Settings.BINARYEN_TRAP_MODE == 'allow': + cmd += ['--emit-potential-traps'] + else: + logging.error('invalid BINARYEN_TRAP_MODE value: ' + shared.Settings.BINARYEN_TRAP_MODE + ' (should be js/clamp/allow)') + sys.exit(1) + if shared.Settings.BINARYEN_IGNORE_IMPLICIT_TRAPS: + cmd += ['--ignore-implicit-traps'] + # pass optimization level to asm2wasm (if not optimizing, or which passes we should run was overridden, do not optimize) + if options.opt_level > 0 and not shared.Settings.BINARYEN_PASSES: + cmd.append(shared.Building.opt_level_to_str(options.opt_level, options.shrink_level)) + # import mem init file if it exists, and if we will not be using asm.js as a binaryen method (as it needs the mem init file, of course) + mem_file_exists = options.memory_init_file and os.path.exists(memfile) + ok_binaryen_method = 'asmjs' not in shared.Settings.BINARYEN_METHOD and 'interpret-asm2wasm' not in shared.Settings.BINARYEN_METHOD + import_mem_init = mem_file_exists and ok_binaryen_method + if import_mem_init: + cmd += ['--mem-init=' + memfile] + if not shared.Settings.RELOCATABLE: + cmd += ['--mem-base=' + str(shared.Settings.GLOBAL_BASE)] + # various options imply that the imported table may not be the exact size as the wasm module's own table segments + if shared.Settings.RELOCATABLE or shared.Settings.RESERVED_FUNCTION_POINTERS > 0 or shared.Settings.EMULATED_FUNCTION_POINTERS: + cmd += ['--table-max=-1'] + if shared.Settings.SIDE_MODULE: + cmd += ['--mem-max=-1'] + elif shared.Settings.BINARYEN_MEM_MAX >= 0: + cmd += ['--mem-max=' + str(shared.Settings.BINARYEN_MEM_MAX)] + if shared.Settings.LEGALIZE_JS_FFI != 1: + cmd += ['--no-legalize-javascript-ffi'] + if shared.Building.is_wasm_only(): + cmd += ['--wasm-only'] # this asm.js is code not intended to run as asm.js, it is only ever going to be wasm, an can contain special fastcomp-wasm support + if options.debug_level >= 2 or options.profiling_funcs: + cmd += ['-g'] + if options.emit_symbol_map or shared.Settings.CYBERDWARF: + cmd += ['--symbolmap=' + target + '.symbols'] + # we prefer to emit a binary, as it is more efficient. however, when we + # want full debug info support (not just function names), then we must + # emit text (at least until wasm gains support for debug info in binaries) + target_binary = options.debug_level < 3 + if target_binary: + cmd += ['-o', wasm_binary_target] + else: + cmd += ['-o', wasm_text_target, '-S'] + wrote_wasm_text = True + logging.debug('asm2wasm (asm.js => WebAssembly): ' + ' '.join(cmd)) + TimeLogger.update() + subprocess.check_call(cmd) + + if not target_binary: + cmd = [os.path.join(binaryen_bin, 'wasm-as'), wasm_text_target, '-o', wasm_binary_target] + if options.debug_level >= 2 or options.profiling_funcs: + cmd += ['-g'] + logging.debug('wasm-as (text => binary): ' + ' '.join(cmd)) + subprocess.check_call(cmd) + if import_mem_init: + # remove and forget about the mem init file in later processing; it does not need to be prefetched in the html, etc. + if DEBUG: + safe_move(memfile, os.path.join(emscripten_temp_dir, os.path.basename(memfile))) + else: + os.unlink(memfile) + options.memory_init_file = False + log_time('asm2wasm') + if shared.Settings.BINARYEN_PASSES: + shutil.move(wasm_binary_target, wasm_binary_target + '.pre') + # BINARYEN_PASSES is comma-separated, and we support both '-'-prefixed and unprefixed pass names + passes = map(lambda p: ('--' + p) if p[0] != '-' else p, shared.Settings.BINARYEN_PASSES.split(',')) + cmd = [os.path.join(binaryen_bin, 'wasm-opt'), wasm_binary_target + '.pre', '-o', wasm_binary_target] + passes + logging.debug('wasm-opt on BINARYEN_PASSES: ' + ' '.join(cmd)) + subprocess.check_call(cmd) + if not wrote_wasm_text and 'interpret-s-expr' in shared.Settings.BINARYEN_METHOD: + cmd = [os.path.join(binaryen_bin, 'wasm-dis'), wasm_binary_target, '-o', wasm_text_target] + logging.debug('wasm-dis (binary => text): ' + ' '.join(cmd)) + subprocess.check_call(cmd) + if shared.Settings.BINARYEN_SCRIPTS: + binaryen_scripts = os.path.join(shared.Settings.BINARYEN_ROOT, 'scripts') + script_env = os.environ.copy() + root_dir = os.path.abspath(os.path.dirname(__file__)) + if script_env.get('PYTHONPATH'): + script_env['PYTHONPATH'] += ':' + root_dir + else: + script_env['PYTHONPATH'] = root_dir + for script in shared.Settings.BINARYEN_SCRIPTS.split(','): + logging.debug('running binaryen script: ' + script) + subprocess.check_call([shared.PYTHON, os.path.join(binaryen_scripts, script), final, wasm_text_target], env=script_env) + if shared.Settings.EVAL_CTORS: + shared.Building.eval_ctors(final, wasm_binary_target, binaryen_bin) + # after generating the wasm, do some final operations + if not shared.Settings.WASM_BACKEND: + if shared.Settings.SIDE_MODULE: + wso = shared.WebAssembly.make_shared_library(final, wasm_binary_target) + # replace the wasm binary output with the dynamic library. TODO: use a specific suffix for such files? + shutil.move(wso, wasm_binary_target) + if not DEBUG: + os.unlink(asm_target) # we don't need the asm.js, it can just confuse + sys.exit(0) # and we are done. + if options.opt_level >= 2: + # minify the JS + optimizer.do_minify() # calculate how to minify + if optimizer.cleanup_shell or optimizer.minify_whitespace or options.use_closure_compiler: + misc_temp_files.note(final) + if DEBUG: save_intermediate('preclean', 'js') + if options.use_closure_compiler: + logging.debug('running closure on shell code') + final = shared.Building.closure_compiler(final, pretty=not optimizer.minify_whitespace) + else: + assert optimizer.cleanup_shell + logging.debug('running cleanup on shell code') + passes = ['noPrintMetadata', 'JSDCE', 'last'] + if optimizer.minify_whitespace: + passes.append('minifyWhitespace') + final = shared.Building.js_optimizer_no_asmjs(final, passes) + if DEBUG: save_intermediate('postclean', 'js') + return final + + +def modularize(final): + logging.debug('Modularizing, assigning to var ' + shared.Settings.EXPORT_NAME) + src = open(final).read() + final = final + '.modular.js' + f = open(final, 'w') + f.write('var ' + shared.Settings.EXPORT_NAME + ' = function(' + shared.Settings.EXPORT_NAME + ') {\n') + f.write(' ' + shared.Settings.EXPORT_NAME + ' = ' + shared.Settings.EXPORT_NAME + ' || {};\n') + f.write(' var Module = ' + shared.Settings.EXPORT_NAME + ';\n') # included code may refer to Module (e.g. from file packager), so alias it + f.write('\n') + f.write(src) + f.write('\n') + f.write(' return ' + shared.Settings.EXPORT_NAME + ';\n') + f.write('};\n') + f.close() + if DEBUG: save_intermediate('modularized', 'js') + return final + + +def generate_html(target, options, js_target, target_basename, + asm_target, wasm_binary_target, + memfile, optimizer): + script = ScriptSource() + + logging.debug('generating HTML') + shell = open(options.shell_path).read() + assert '{{{ SCRIPT }}}' in shell, 'HTML shell must contain {{{ SCRIPT }}} , see src/shell.html for an example' + base_js_target = os.path.basename(js_target) + + asm_mods = [] + + if options.proxy_to_worker: + proxy_worker_filename = shared.Settings.PROXY_TO_WORKER_FILENAME or target_basename + worker_js = worker_js_script(proxy_worker_filename) + script.inline = ''' if ((',' + window.location.search.substr(1) + ',').indexOf(',noProxy,') < 0) { console.log('running code in a web worker'); -''' + open(shared.path_from_root('src', 'webGLClient.js')).read() + '\n' + shared.read_and_preprocess(shared.path_from_root('src', 'proxyClient.js')).replace('{{{ filename }}}', child_js).replace('{{{ IDBStore.js }}}', open(shared.path_from_root('src', 'IDBStore.js')).read()) + ''' +''' + worker_js + ''' } else { // note: no support for code mods (PRECISE_F32==2) console.log('running code on the main thread'); @@ -2271,18 +2370,20 @@ def un_src(): # use this if you want to modify the script and need it to be inli script.src = "%s.js"; document.body.appendChild(script); } -''' % child_js - else: - # Normal code generation path - script_src = base_js_target - - from tools import client_mods - asm_mods = client_mods.get_mods(shared.Settings, minified = 'minifyNames' in JSOptimizer.queue_history, separate_asm = separate_asm) - - if shared.Settings.EMTERPRETIFY_FILE: - # We need to load the emterpreter file before anything else, it has to be synchronously ready - un_src() - script_inline = ''' +''' % proxy_worker_filename + else: + # Normal code generation path + script.src = base_js_target + + from tools import client_mods + asm_mods = client_mods.get_mods(shared.Settings, + minified = 'minifyNames' in optimizer.queue_history, + separate_asm = options.separate_asm) + + if shared.Settings.EMTERPRETIFY_FILE: + # We need to load the emterpreter file before anything else, it has to be synchronously ready + script.un_src() + script.inline = ''' var emterpretXHR = new XMLHttpRequest(); emterpretXHR.open('GET', '%s', true); emterpretXHR.responseType = 'arraybuffer'; @@ -2291,12 +2392,12 @@ def un_src(): # use this if you want to modify the script and need it to be inli %s }; emterpretXHR.send(null); -''' % (shared.Settings.EMTERPRETIFY_FILE, script_inline) +''' % (shared.Settings.EMTERPRETIFY_FILE, script.inline) - if memory_init_file: - # start to load the memory init file in the HTML, in parallel with the JS - un_src() - script_inline = (''' + if options.memory_init_file: + # start to load the memory init file in the HTML, in parallel with the JS + script.un_src() + script.inline = (''' (function() { var memoryInitializer = '%s'; if (typeof Module['locateFile'] === 'function') { @@ -2309,15 +2410,17 @@ def un_src(): # use this if you want to modify the script and need it to be inli meminitXHR.responseType = 'arraybuffer'; meminitXHR.send(null); })(); -''' % os.path.basename(memfile)) + script_inline - - # Download .asm.js if --separate-asm was passed in an asm.js build, or if 'asmjs' is one - # of the wasm run methods. - if separate_asm and (not shared.Settings.BINARYEN or 'asmjs' in shared.Settings.BINARYEN_METHOD): - un_src() - if len(asm_mods) == 0: - # just load the asm, then load the rest - script_inline = ''' +''' % os.path.basename(memfile)) + script.inline + + # Download .asm.js if --separate-asm was passed in an asm.js build, or if 'asmjs' is one + # of the wasm run methods. + if not options.separate_asm or (shared.Settings.BINARYEN and 'asmjs' not in shared.Settings.BINARYEN_METHOD): + assert len(asm_mods) == 0, 'no --separate-asm means no client code mods are possible' + else: + script.un_src() + if len(asm_mods) == 0: + # just load the asm, then load the rest + script.inline = ''' var script = document.createElement('script'); script.src = "%s"; script.onload = function() { @@ -2326,10 +2429,10 @@ def un_src(): # use this if you want to modify the script and need it to be inli }, 1); // delaying even 1ms is enough to allow compilation memory to be reclaimed }; document.body.appendChild(script); -''' % (os.path.basename(asm_target), script_inline) - else: - # may need to modify the asm code, load it as text, modify, and load asynchronously - script_inline = ''' +''' % (os.path.basename(asm_target), script.inline) + else: + # may need to modify the asm code, load it as text, modify, and load asynchronously + script.inline = ''' var codeXHR = new XMLHttpRequest(); codeXHR.open('GET', '%s', true); codeXHR.onload = function() { @@ -2349,14 +2452,12 @@ def un_src(): # use this if you want to modify the script and need it to be inli document.body.appendChild(script); }; codeXHR.send(null); -''' % (os.path.basename(asm_target), '\n'.join(asm_mods), script_inline) - else: - assert len(asm_mods) == 0, 'no --separate-asm means no client code mods are possible' +''' % (os.path.basename(asm_target), '\n'.join(asm_mods), script.inline) - if shared.Settings.BINARYEN and not shared.Settings.BINARYEN_ASYNC_COMPILATION: - # We need to load the wasm file before anything else, it has to be synchronously ready TODO: optimize - un_src() - script_inline = ''' + if shared.Settings.BINARYEN and not shared.Settings.BINARYEN_ASYNC_COMPILATION: + # We need to load the wasm file before anything else, it has to be synchronously ready TODO: optimize + script.un_src() + script.inline = ''' var wasmXHR = new XMLHttpRequest(); wasmXHR.open('GET', '%s', true); wasmXHR.responseType = 'arraybuffer'; @@ -2365,40 +2466,110 @@ def un_src(): # use this if you want to modify the script and need it to be inli %s }; wasmXHR.send(null); -''' % (os.path.basename(wasm_binary_target), script_inline) +''' % (os.path.basename(wasm_binary_target), script.inline) + + html = open(target, 'wb') + html_contents = shell.replace('{{{ SCRIPT }}}', script.replacement()) + html_contents = tools.line_endings.convert_line_endings(html_contents, '\n', options.output_eol) + html.write(html_contents) + html.close() + + +def generate_worker_js(target, js_target, target_basename): + shutil.move(js_target, js_target[:-3] + '.worker.js') # compiler output goes in .worker.js file + worker_target_basename = target_basename + '.worker' + proxy_worker_filename = shared.Settings.PROXY_TO_WORKER_FILENAME or worker_target_basename + target_contents = worker_js_script(proxy_worker_filename) + open(target, 'w').write(target_contents) + + +def worker_js_script(proxy_worker_filename): + web_gl_client_src = open(shared.path_from_root('src', 'webGLClient.js')).read() + idb_store_src = open(shared.path_from_root('src', 'IDBStore.js')).read() + proxy_client_src = ( + open(shared.path_from_root('src', 'proxyClient.js')).read() + .replace('{{{ filename }}}', proxy_worker_filename) + .replace('{{{ IDBStore.js }}}', idb_store_src) + ) + + return web_gl_client_src + '\n' + proxy_client_src + + +def system_js_libraries_setting_str(libs, lib_dirs, settings_changes, input_files): + libraries = [] + + # Find library files + for i, lib in libs: + logging.debug('looking for library "%s"', lib) + found = False + for prefix in LIB_PREFIXES: + for suff in STATICLIB_ENDINGS + DYNAMICLIB_ENDINGS: + name = prefix + lib + suff + for lib_dir in lib_dirs: + path = os.path.join(lib_dir, name) + if os.path.exists(path): + logging.debug('found library "%s" at %s', lib, path) + input_files.append((i, path)) + found = True + break + if found: break + if found: break + if not found: + libraries += shared.Building.path_to_system_js_libraries(lib) + + # Certain linker flags imply some link libraries to be pulled in by default. + libraries += shared.Building.path_to_system_js_libraries_for_settings(settings_changes) + return 'SYSTEM_JS_LIBRARIES="' + ','.join(libraries) + '"' + + +class ScriptSource: + def __init__(self): + self.src = None # if set, we have a script to load with a src attribute + self.inline = None # if set, we have the contents of a script to write inline in a script + + def un_src(self): + """Use this if you want to modify the script and need it to be inline.""" + if self.src is None: + return + self.inline = ''' + var script = document.createElement('script'); + script.src = "%s"; + document.body.appendChild(script); +''' % self.src + self.src = None + + def replacement(self): + """Returns the script tag to replace the {{{ SCRIPT }}} tag in the target""" + assert (self.src or self.inline) and not (self.src and self.inline) + if self.src: + return '' % urllib.quote(self.src) + else: + return '' % self.inline - html = open(target, 'wb') - assert (script_src or script_inline) and not (script_src and script_inline) - if script_src: - script_replacement = '' % urllib.quote(script_src) - else: - script_replacement = '' % script_inline - html_contents = shell.replace('{{{ SCRIPT }}}', script_replacement) - html_contents = tools.line_endings.convert_line_endings(html_contents, '\n', output_eol) - html.write(html_contents) - html.close() - else: # final_suffix != html - if proxy_to_worker: - shutil.move(js_target, js_target[:-3] + '.worker.js') # compiler output goes in .worker.js file - worker_target_basename = target_basename + '.worker' - target_contents = open(shared.path_from_root('src', 'webGLClient.js')).read() + '\n' + open(shared.path_from_root('src', 'proxyClient.js')).read().replace('{{{ filename }}}', shared.Settings.PROXY_TO_WORKER_FILENAME or worker_target_basename).replace('{{{ IDBStore.js }}}', open(shared.path_from_root('src', 'IDBStore.js')).read()) - open(target, 'w').write(target_contents) - for f in generated_text_files_with_native_eols: - tools.line_endings.convert_line_endings_in_file(f, os.linesep, output_eol) - log_time('final emitting') - # exit block 'final emitting' +def is_valid_abspath(options, path_name): + # Any path that is underneath the emscripten repository root must be ok. + if shared.path_from_root().replace('\\', '/') in path_name.replace('\\', '/'): + return True - if DEBUG: logging.debug('total time: %.2f seconds', (time.time() - start_time)) + for valid_abspath in options.valid_abspaths: + if in_directory(valid_abspath, path_name): + return True + return False + + +def check_bad_eq(arg): + assert '=' not in arg, 'Invalid parameter (do not use "=" with "--" options)' + + +def validate_arg_level(level_string, max_level, err_msg): + try: + level = int(level_string) + assert 0 <= level <= max_level + except: + raise Exception(err_msg) + return level - finally: - if not TEMP_DIR: - try: - shutil.rmtree(temp_dir) - except: - pass - else: - logging.info('emcc saved files are in:' + temp_dir) if __name__ == '__main__': run() diff --git a/emrun b/emrun index c697d64177e9b..8b325eea2e75c 100755 --- a/emrun +++ b/emrun @@ -4,7 +4,7 @@ # Usage: emrun filename.html # See emrun --help for more information -import os, platform, optparse, logging, re, pprint, atexit, urlparse, subprocess, sys, SocketServer, BaseHTTPServer, SimpleHTTPServer, time, string, struct, socket, cgi, tempfile, stat, shutil, json, uuid +import os, platform, optparse, logging, re, pprint, atexit, urlparse, subprocess, sys, SocketServer, BaseHTTPServer, SimpleHTTPServer, time, string, struct, socket, cgi, tempfile, stat, shutil, json, uuid, shlex from operator import itemgetter from urllib import unquote from Queue import PriorityQueue @@ -51,16 +51,6 @@ LINUX = False OSX = False if os.name == 'nt': WINDOWS = True - try: - import shlex - import win32api, _winreg - from win32com.client import GetObject - from win32api import GetFileVersionInfo, LOWORD, HIWORD - from _winreg import HKEY_CURRENT_USER, OpenKey, QueryValue - except Exception, e: - print >> sys.stderr, str(e) - print >> sys.stderr, "Importing Python win32 modules failed! This most likely occurs if you do not have PyWin32 installed! Get it from http://sourceforge.net/projects/pywin32/" - sys.exit(1) elif platform.system() == 'Linux': LINUX = True elif platform.mac_ver()[0] != '': @@ -72,6 +62,21 @@ elif platform.mac_ver()[0] != '': if not WINDOWS and not LINUX and not OSX: raise Exception("Unknown OS!") +import_win32api_modules_warned_once = False + +def import_win32api_modules(): + try: + global win32api, _winreg, GetObject + import win32api, _winreg + from win32com.client import GetObject + except Exception, e: + global import_win32api_modules_warned_once + if not import_win32api_modules_warned_once: + print >> sys.stderr, str(e) + print >> sys.stderr, "Importing Python win32 modules failed! This most likely occurs if you do not have PyWin32 installed! Get it from http://sourceforge.net/projects/pywin32/" + import_win32api_modules_warned_once = True + raise + # Returns wallclock time in seconds. def tick(): # Would like to return time.clock() since it's apparently better for precision, but it is broken on OSX 10.10 and Python 2.7.8. @@ -253,11 +258,13 @@ user_pref("toolkit.telemetry.enabled", false); user_pref("toolkit.telemetry.unified", false); user_pref("datareporting.policy.dataSubmissionEnabled", false); user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true); -// Allow window.dump("foo\n") to print directly to console +// Allow window.dump() to print directly to console user_pref("browser.dom.window.dump.enabled", true); // Disable background add-ons related update & information check pings user_pref("extensions.update.enabled", false); user_pref("extensions.getAddons.cache.enabled", false); +// Enable wasm +user_pref("javascript.options.wasm", true); ''') f.close() logv('create_emrun_safe_firefox_profile: Created new Firefox profile "' + temp_firefox_profile_dir + '"') @@ -518,7 +525,10 @@ class HTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): have_received_messages = True elif path == '/system_info': system_info = json.loads(get_system_info(format_json=True)) - browser_info = json.loads(get_browser_info(browser_exe, format_json=True)) + try: + browser_info = json.loads(get_browser_info(browser_exe, format_json=True)) + except: + browser_info = '' data = { 'system': system_info, 'browser': browser_info } self.send_response(200) self.send_header("Content-type", "application/json") @@ -578,6 +588,7 @@ def get_cpu_info(): frequency = 0 try: if WINDOWS: + import_win32api_modules() root_winmgmts = GetObject("winmgmts:root\cimv2") cpus = root_winmgmts.ExecQuery("Select * from Win32_Processor") cpu_name = cpus[0].Name + ', ' + platform.processor() @@ -629,6 +640,11 @@ def win_get_gpu_info(): if gpu['model'] == model: return gpu return None + try: + import_win32api_modules() + except: + return [] + for i in range(0, 16): try: hHardwareReg = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "HARDWARE") @@ -728,10 +744,10 @@ def get_gpu_info(): def get_executable_version(filename): try: if WINDOWS: - info = GetFileVersionInfo(filename, "\\") + info = win32api.GetFileVersionInfo(filename, "\\") ms = info['FileVersionMS'] ls = info['FileVersionLS'] - version = HIWORD (ms), LOWORD (ms), HIWORD (ls), LOWORD (ls) + version = win32api.HIWORD(ms), win32api.LOWORD(ms), win32api.HIWORD(ls), win32api.LOWORD(ls) return '.'.join(map(str, version)) elif OSX: plistfile = filename[0:filename.find('MacOS')] + 'Info.plist' @@ -794,6 +810,7 @@ def win_get_file_properties(fname): props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None} try: + import win32api # backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc fixedInfo = win32api.GetFileVersionInfo(fname, '\\') props['FixedFileInfo'] = fixedInfo @@ -869,6 +886,7 @@ def get_os_version(): bitness = ' (64bit)' if platform.machine() in ['AMD64', 'x86_64'] else ' (32bit)' try: if WINDOWS: + import_win32api_modules() versionHandle = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion") productName = _winreg.QueryValueEx(versionHandle, "ProductName") @@ -935,11 +953,12 @@ def which(program): return None def win_get_default_browser(): + import_win32api_modules() # Look in the registry for the default system browser on Windows without relying on # 'start %1' since that method has an issue, see comment below. try: - with OpenKey(HKEY_CURRENT_USER, r"Software\Classes\http\shell\open\command") as key: - cmd = QueryValue(key, None) + with _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, r"Software\Classes\http\shell\open\command") as key: + cmd = _winreg.QueryValue(key, None) if cmd: parts = shlex.split(cmd) if len(parts) > 0: @@ -1059,6 +1078,10 @@ def list_android_browsers(): browsers += ['chrome'] if line.endswith('=com.chrome.beta'): browsers += ['chrome_beta'] + if line.endswith('=com.chrome.dev'): + browsers += ['chrome_dev'] + if line.endswith('=com.chrome.canary'): + browsers += ['chrome_canary'] if line.endswith('=com.opera.browser'): browsers += ['opera'] if line.endswith('=com.opera.mini.android'): @@ -1093,11 +1116,13 @@ def browser_display_name(browser): return 'Google Chrome' if 'firefox' in b: # Try to identify firefox flavor explicitly, to help show issues where emrun would launch the wrong browser. - product_name = win_get_file_properties(browser)['StringFileInfo']['ProductName'] if WINDOWS else 'firefox' - if product_name.lower() != 'firefox': - return 'Mozilla Firefox ' + product_name - else: - return 'Mozilla Firefox' + try: + product_name = win_get_file_properties(browser)['StringFileInfo']['ProductName'] if WINDOWS else 'firefox' + if product_name.lower() != 'firefox': + return 'Mozilla Firefox ' + product_name + except Exception, e: + pass + return 'Mozilla Firefox' if 'opera' in b: return 'Opera' if 'safari' in b: @@ -1109,6 +1134,7 @@ def subprocess_env(): # https://bugzilla.mozilla.org/show_bug.cgi?id=745154 e['MOZ_DISABLE_AUTO_SAFE_MODE'] = '1' e['MOZ_DISABLE_SAFE_MODE_KEY'] = '1' # https://bugzilla.mozilla.org/show_bug.cgi?id=653410#c9 + e['JIT_OPTION_asmJSAtomicsEnable'] = 'true' # https://bugzilla.mozilla.org/show_bug.cgi?id=1299359#c0 return e # Removes a directory tree even if it was readonly, and doesn't throw exception on failure. @@ -1326,7 +1352,7 @@ def main(): logv('Web server root directory: ' + os.path.abspath('.')) if options.android: - if not options.no_browser: + if not options.no_browser or options.browser_info: if not options.browser: loge("Running on Android requires that you explicitly specify the browser to run with --browser . Run emrun --android --list_browsers to obtain a list of installed browsers you can use.") return 1 @@ -1339,9 +1365,13 @@ def main(): elif options.browser == 'firefox_nightly' or options.browser == 'fennec': browser_app = 'org.mozilla.fennec/.App' elif options.browser == 'chrome': - browser_app = 'com.android.chrome/.Main' - elif options.browser == 'chrome_beta' or options.browser == 'chrome_canary': # There is no Chrome Canary for Android, but Play store has 'Chrome Beta' instead. - browser_app = 'com.chrome.beta/com.android.chrome.Main' + browser_app = 'com.android.chrome/com.google.android.apps.chrome.Main' + elif options.browser == 'chrome_beta': + browser_app = 'com.chrome.beta/com.google.android.apps.chrome.Main' + elif options.browser == 'chrome_dev': + browser_app = 'com.chrome.dev/com.google.android.apps.chrome.Main' + elif options.browser == 'chrome_canary': + browser_app = 'com.chrome.canary/com.google.android.apps.chrome.Main' elif options.browser == 'opera': browser_app = 'com.opera.browser/com.opera.Opera' elif options.browser == 'opera_mini': # Launching the URL works, but page seems to never load (Fails with 'Network problem' even when other browsers work) @@ -1358,9 +1388,9 @@ def main(): # 4. Type 'aapt d xmltree .apk AndroidManifest.xml > manifest.txt' to extract the manifest from the package. # 5. Locate the name of the main activity for the browser in manifest.txt and add an entry to above list in form 'appname/mainactivityname' - if WINDOWS: - url = url.replace('&', '\\&') + url = url.replace('&', '\\&') browser = [ADB, 'shell', 'am', 'start', '-a', 'android.intent.action.VIEW', '-n', browser_app, '-d', url] + print(str(browser)) processname_killed_atexit = browser_app[:browser_app.find('/')] else: #Launching a web page on local system. if options.browser: @@ -1368,48 +1398,64 @@ def main(): options.browser = options.browser.strip() if (options.browser.startswith('"') and options.browser.endswith('"')) or (options.browser.startswith("'") and options.browser.endswith("'")): options.browser = options.browser[1:-1].strip() - browser = find_browser(str(options.browser)) - if not browser: - loge('Unable to find browser "' + str(options.browser) + '"! Check the correctness of the passed --browser=xxx parameter!') - return 1 - browser_exe = browser[0] - browser_args = [] - - if 'safari' in browser_exe.lower(): - # Safari has a bug that a command line 'Safari http://page.com' does not launch that page, - # but instead launches 'file:///http://page.com'. To remedy this, must use the open -a command - # to run Safari, but unfortunately this will end up spawning Safari process detached from emrun. - if OSX: - browser = ['open', '-a', 'Safari'] + (browser[1:] if len(browser) > 1 else []) - - processname_killed_atexit = 'Safari' - elif 'chrome' in browser_exe.lower(): - processname_killed_atexit = 'chrome' - browser_args = ['--incognito', '--enable-nacl', '--enable-pnacl', '--disable-restore-session-state', '--enable-webgl', '--no-default-browser-check', '--no-first-run', '--allow-file-access-from-files'] - # if options.no_server: - # browser_args += ['--disable-web-security'] - elif 'firefox' in browser_exe.lower(): - processname_killed_atexit = 'firefox' - elif 'iexplore' in browser_exe.lower(): - processname_killed_atexit = 'iexplore' - browser_args = ['-private'] - elif 'opera' in browser_exe.lower(): - processname_killed_atexit = 'opera' - - # In Windows cmdline, & character delimits multiple commmands, so must use ^ to escape them. - if browser_exe == 'cmd': - url = url.replace('&', '^&') - browser += browser_args + [url] + + if not options.no_browser or options.browser_info: + browser = find_browser(str(options.browser)) + if not browser: + loge('Unable to find browser "' + str(options.browser) + '"! Check the correctness of the passed --browser=xxx parameter!') + return 1 + browser_exe = browser[0] + browser_args = [] + + if 'safari' in browser_exe.lower(): + # Safari has a bug that a command line 'Safari http://page.com' does not launch that page, + # but instead launches 'file:///http://page.com'. To remedy this, must use the open -a command + # to run Safari, but unfortunately this will end up spawning Safari process detached from emrun. + if OSX: + browser = ['open', '-a', 'Safari'] + (browser[1:] if len(browser) > 1 else []) + + processname_killed_atexit = 'Safari' + elif 'chrome' in browser_exe.lower(): + processname_killed_atexit = 'chrome' + browser_args = ['--incognito', '--enable-nacl', '--enable-pnacl', '--disable-restore-session-state', '--enable-webgl', '--no-default-browser-check', '--no-first-run', '--allow-file-access-from-files'] + # if options.no_server: + # browser_args += ['--disable-web-security'] + elif 'firefox' in browser_exe.lower(): + processname_killed_atexit = 'firefox' + elif 'iexplore' in browser_exe.lower(): + processname_killed_atexit = 'iexplore' + browser_args = ['-private'] + elif 'opera' in browser_exe.lower(): + processname_killed_atexit = 'opera' + + # In Windows cmdline, & character delimits multiple commmands, so must use ^ to escape them. + if browser_exe == 'cmd': + url = url.replace('&', '^&') + browser += browser_args + [url] if options.kill_on_start: pname = processname_killed_atexit kill_browser_process() processname_killed_atexit = pname + + # Copy the profile over to Android. + if options.android and options.safe_firefox_profile: + profile_dir = create_emrun_safe_firefox_profile() + def run(cmd): + print str(cmd) + subprocess.call(cmd) + + run(['adb', 'shell', 'rm', '-rf', '/mnt/sdcard/safe_firefox_profile']) + run(['adb', 'shell', 'mkdir', '/mnt/sdcard/safe_firefox_profile']) + run(['adb', 'push', os.path.join(profile_dir, 'prefs.js'), '/mnt/sdcard/safe_firefox_profile/prefs.js']) + browser += ['--es', 'args', '"--profile /mnt/sdcard/safe_firefox_profile"'] + # Create temporary Firefox profile to run the page with. This is important to run after kill_browser_process()/kill_on_start op above, since that # cleans up the temporary profile if one exists. - if processname_killed_atexit == 'firefox' and options.safe_firefox_profile and not options.no_browser: + if processname_killed_atexit == 'firefox' and options.safe_firefox_profile and not options.no_browser and not options.android: profile_dir = create_emrun_safe_firefox_profile() + browser += ['-no-remote', '-profile', profile_dir.replace('\\', '/')] if options.system_info: @@ -1418,7 +1464,7 @@ def main(): if options.browser_info: if options.android: - if option.json: + if options.json: print json.dumps({ 'browser': 'Android ' + browser_app }, indent=2) @@ -1445,7 +1491,7 @@ def main(): httpd = HTTPWebServer(('', options.port), HTTPHandler) if not options.no_browser: - logv("Executing %s" % ' '.join(browser)) + logi("Executing %s" % ' '.join(browser)) if browser[0] == 'cmd': serve_forever = True # Workaround an issue where passing 'cmd /C start' is not able to detect when the user closes the page. browser_process = subprocess.Popen(browser, env=subprocess_env()) diff --git a/emscripten-version.txt b/emscripten-version.txt index 101fdd6ffe543..39fb75d831693 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1 +1 @@ -"1.37.12" +"1.37.13" diff --git a/emscripten.py b/emscripten.py index 6cc22c3a171d9..253f1a7604f55 100755 --- a/emscripten.py +++ b/emscripten.py @@ -98,16 +98,10 @@ def emscript(infile, settings, outfile, libraries=None, compiler_engine=None, glue, forwarded_data = compiler_glue(metadata, settings, libraries, compiler_engine, temp_files, DEBUG) with ToolchainProfiler.profile_block('function_tables_and_exports'): - (post, funcs_js, sending, receiving, asm_setup, the_global, asm_global_vars, - asm_global_funcs, pre_tables, final_function_tables, exports, function_table_data) = ( + (post, function_table_data, bundled_args) = ( function_tables_and_exports(funcs, metadata, mem_init, glue, forwarded_data, settings, outfile, DEBUG)) with ToolchainProfiler.profile_block('write_output_file'): - function_table_sigs = function_table_data.keys() - module = create_module(funcs_js, asm_setup, the_global, sending, receiving, asm_global_vars, - asm_global_funcs, pre_tables, final_function_tables, function_table_sigs, - exports, metadata, settings) - write_output_file(metadata, post, module, function_table_data, settings, outfile, DEBUG) - + finalize_output(outfile, post, function_table_data, bundled_args, metadata, settings, DEBUG) success = True finally: @@ -337,13 +331,31 @@ def move_preasm(m): len(exports), len(the_global), len(sending), len(receiving)])) logging.debug(' emscript: python processing: function tables and exports took %s seconds' % (time.time() - t)) - return (post, funcs_js, sending, receiving, asm_setup, the_global, asm_global_vars, - asm_global_funcs, pre_tables, final_function_tables, exports, function_table_data) + bundled_args = (funcs_js, asm_setup, the_global, sending, receiving, asm_global_vars, + asm_global_funcs, pre_tables, final_function_tables, exports) + return (post, function_table_data, bundled_args) + + +def finalize_output(outfile, post, function_table_data, bundled_args, metadata, settings, DEBUG): + function_table_sigs = function_table_data.keys() + module = create_module(function_table_sigs, metadata, settings, *bundled_args) + + if DEBUG: + logging.debug('emscript: python processing: finalize') + t = time.time() + + write_output_file(outfile, post, module) + module = None + + if DEBUG: + logging.debug(' emscript: python processing: finalize took %s seconds' % (time.time() - t)) + + write_cyberdwarf_data(outfile, metadata, settings) -def create_module(funcs_js, asm_setup, the_global, sending, receiving, asm_global_vars, - asm_global_funcs, pre_tables, final_function_tables, function_table_sigs, - exports, metadata, settings): +def create_module(function_table_sigs, metadata, settings, + funcs_js, asm_setup, the_global, sending, receiving, asm_global_vars, + asm_global_funcs, pre_tables, final_function_tables, exports): receiving += create_named_globals(metadata, settings) runtime_funcs = create_runtime_funcs(exports, settings) @@ -382,22 +394,16 @@ def create_module(funcs_js, asm_setup, the_global, sending, receiving, asm_globa return module -def write_output_file(metadata, post, module, function_table_data, settings, outfile, DEBUG): - if DEBUG: - logging.debug('emscript: python processing: finalize') - t = time.time() - +def write_output_file(outfile, post, module): for i in range(len(module)): # do this loop carefully to save memory - if WINDOWS: module[i] = module[i].replace('\r\n', '\n') # Normalize to UNIX line endings, otherwise writing to text file will duplicate \r\n to \r\r\n! + module[i] = normalize_line_endings(module[i]) outfile.write(module[i]) - module = None - if WINDOWS: post = post.replace('\r\n', '\n') # Normalize to UNIX line endings, otherwise writing to text file will duplicate \r\n to \r\r\n! + post = normalize_line_endings(post) outfile.write(post) - if DEBUG: - logging.debug(' emscript: python processing: finalize took %s seconds' % (time.time() - t)) +def write_cyberdwarf_data(outfile, metadata, settings): if settings['CYBERDWARF']: assert('cyberdwarf_data' in metadata) cd_file_name = outfile.name + ".cd" @@ -434,6 +440,8 @@ def create_backend_args(infile, temp_js, settings): args += ['-emscripten-global-base=%d' % settings['GLOBAL_BASE']] if settings['SIDE_MODULE']: args += ['-emscripten-side-module'] + if settings['LEGALIZE_JS_FFI'] != 1: + args += ['-emscripten-legalize-javascript-ffi=0'] if settings['DISABLE_EXCEPTION_CATCHING'] != 1: args += ['-enable-emscripten-cpp-exceptions'] if settings['DISABLE_EXCEPTION_CATCHING'] == 2: @@ -534,13 +542,21 @@ def memory_and_global_initializers(pre, metadata, mem_init, settings): staticbump = metadata['staticBump'] while staticbump % 16 != 0: staticbump += 1 - pre = pre.replace('STATICTOP = STATIC_BASE + 0;', '''STATICTOP = STATIC_BASE + %d;%s -/* global initializers */ %s __ATINIT__.push(%s); -%s''' % (staticbump, - 'assert(STATICTOP < SPLIT_MEMORY, "SPLIT_MEMORY size must be big enough so the entire static memory, need " + STATICTOP);' if settings['SPLIT_MEMORY'] else '', - 'if (!ENVIRONMENT_IS_PTHREAD)' if settings['USE_PTHREADS'] else '', - global_initializers, - mem_init)) + split_memory = '' + if settings['SPLIT_MEMORY']: + split_memory = ('assert(STATICTOP < SPLIT_MEMORY, "SPLIT_MEMORY size must be big enough so the ' + 'entire static memory, need " + STATICTOP);') + pthread = '' + if settings['USE_PTHREADS']: + pthread = 'if (!ENVIRONMENT_IS_PTHREAD)' + pre = pre.replace('STATICTOP = STATIC_BASE + 0;', + '''STATICTOP = STATIC_BASE + {staticbump};{split_memory} +/* global initializers */ {pthread} __ATINIT__.push({global_initializers}); +{mem_init}'''.format(staticbump=staticbump, + split_memory=split_memory, + pthread=pthread, + global_initializers=global_initializers, + mem_init=mem_init)) if settings['SIDE_MODULE']: pre = pre.replace('Runtime.GLOBAL_BASE', 'gb') @@ -1718,14 +1734,9 @@ def emscript_wasm_backend(infile, settings, outfile, libraries=None, compiler_en # finalize module = create_module_wasm(sending, receiving, invoke_funcs, settings) - for i in range(len(module)): # do this loop carefully to save memory - if WINDOWS: module[i] = module[i].replace('\r\n', '\n') # Normalize to UNIX line endings, otherwise writing to text file will duplicate \r\n to \r\r\n! - outfile.write(module[i]) + write_output_file(outfile, post, module) module = None - if WINDOWS: post = post.replace('\r\n', '\n') # Normalize to UNIX line endings, otherwise writing to text file will duplicate \r\n to \r\r\n! - outfile.write(post) - outfile.close() @@ -2050,6 +2061,16 @@ def asmjs_mangle(name): sys.exit(1) +def normalize_line_endings(text): + """Normalize to UNIX line endings. + + On Windows, writing to text file will duplicate \r\n to \r\r\n otherwise. + """ + if WINDOWS: + return text.replace('\r\n', '\n') + return text + + def main(args, compiler_engine, cache, temp_files, DEBUG): # Prepare settings for serialization to JSON. settings = {} diff --git a/src/closure-externs.js b/src/closure-externs.js index 370ddd9684a89..3e170e34f0e7f 100644 --- a/src/closure-externs.js +++ b/src/closure-externs.js @@ -849,6 +849,64 @@ SIMD.Bool16x8.xor = function() {}; SIMD.Bool32x4.xor = function() {}; SIMD.Bool64x2.xor = function() {}; +SIMD.Float32x4.load1 = function() {}; +SIMD.Float32x4.load2 = function() {}; +SIMD.Float32x4.load3 = function() {}; +SIMD.Float32x4.load4 = function() {}; +SIMD.Float32x4.store1 = function() {}; +SIMD.Float32x4.store2 = function() {}; +SIMD.Float32x4.store3 = function() {}; +SIMD.Float32x4.store4 = function() {}; + +SIMD.Int32x4.load1 = function() {}; +SIMD.Int32x4.load2 = function() {}; +SIMD.Int32x4.load3 = function() {}; +SIMD.Int32x4.load4 = function() {}; +SIMD.Int32x4.store1 = function() {}; +SIMD.Int32x4.store2 = function() {}; +SIMD.Int32x4.store3 = function() {}; +SIMD.Int32x4.store4 = function() {}; + +SIMD.Uint32x4.load1 = function() {}; +SIMD.Uint32x4.load2 = function() {}; +SIMD.Uint32x4.load3 = function() {}; +SIMD.Uint32x4.load4 = function() {}; +SIMD.Uint32x4.store1 = function() {}; +SIMD.Uint32x4.store2 = function() {}; +SIMD.Uint32x4.store3 = function() {}; +SIMD.Uint32x4.store4 = function() {}; + +SIMD.bool64x2.anyTrue = function() {}; +SIMD.bool32x4.anyTrue = function() {}; +SIMD.bool16x8.anyTrue = function() {}; +SIMD.bool8x16.anyTrue = function() {}; + +SIMD.Float32x4.fromBool64x2Bits = function() {}; +SIMD.Float64x2.fromBool64x2Bits = function() {}; +SIMD.Int8x16.fromBool64x2Bits = function() {}; +SIMD.Int16x8.fromBool64x2Bits = function() {}; +SIMD.Int32x4.fromBool64x2Bits = function() {}; +SIMD.Uint8x16.fromBool64x2Bits = function() {}; +SIMD.Uint16x8.fromBool64x2Bits = function() {}; +SIMD.Uint32x4.fromBool64x2Bits = function() {}; +SIMD.Bool8x16.fromBool64x2Bits = function() {}; +SIMD.Bool16x8.fromBool64x2Bits = function() {}; +SIMD.Bool32x4.fromBool64x2Bits = function() {}; +SIMD.Bool64x2.fromBool64x2Bits = function() {}; + +SIMD.Float32x4.fromFloat64x2 = function() {}; +SIMD.Float64x2.fromFloat64x2 = function() {}; +SIMD.Int8x16.fromFloat64x2 = function() {}; +SIMD.Int16x8.fromFloat64x2 = function() {}; +SIMD.Int32x4.fromFloat64x2 = function() {}; +SIMD.Uint8x16.fromFloat64x2 = function() {}; +SIMD.Uint16x8.fromFloat64x2 = function() {}; +SIMD.Uint32x4.fromFloat64x2 = function() {}; +SIMD.Bool8x16.fromFloat64x2 = function() {}; +SIMD.Bool16x8.fromFloat64x2 = function() {}; +SIMD.Bool32x4.fromFloat64x2 = function() {}; +SIMD.Bool64x2.fromFloat64x2 = function() {}; + var GLctx = {}; /** diff --git a/src/library_cyberdwarf.js b/src/library_cyberdwarf.js index 30c06936c376d..0e7b64ab9d741 100644 --- a/src/library_cyberdwarf.js +++ b/src/library_cyberdwarf.js @@ -1,9 +1,9 @@ -var LibraryCyberdwarf = { - // These are empty, designed to be replaced when a debugger is invoked - metadata_llvm_dbg_value_constant: function(a,b,c,d) { - }, - metadata_llvm_dbg_value_local: function(a,b,c,d) { - } -}; - -mergeInto(LibraryManager.library, LibraryCyberdwarf); +var LibraryCyberdwarf = { + // These are empty, designed to be replaced when a debugger is invoked + metadata_llvm_dbg_value_constant: function(a,b,c,d) { + }, + metadata_llvm_dbg_value_local: function(a,b,c,d) { + } +}; + +mergeInto(LibraryManager.library, LibraryCyberdwarf); diff --git a/src/library_gl.js b/src/library_gl.js index ba74301a43b2e..e1d893b8ad412 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -445,7 +445,7 @@ var LibraryGL = { if (webGLContextAttributes['majorVersion'] == 1 && webGLContextAttributes['minorVersion'] == 0) { ctx = canvas.getContext("webgl", webGLContextAttributes) || canvas.getContext("experimental-webgl", webGLContextAttributes); } else if (webGLContextAttributes['majorVersion'] == 2 && webGLContextAttributes['minorVersion'] == 0) { - ctx = canvas.getContext("webgl2", webGLContextAttributes) || canvas.getContext("experimental-webgl2", webGLContextAttributes); + ctx = canvas.getContext("webgl2", webGLContextAttributes); } else { throw 'Unsupported WebGL context version ' + majorVersion + '.' + minorVersion + '!' } @@ -2633,8 +2633,8 @@ var LibraryGL = { } else { for (var i = 0; i < data.length; i++) { switch (type) { - case 'Integer': {{{ makeSetValue('params', 'i', 'data[i]', 'i32') }}}; break; - case 'Float': {{{ makeSetValue('params', 'i', 'data[i]', 'float') }}}; break; + case 'Integer': {{{ makeSetValue('params', 'i*4', 'data[i]', 'i32') }}}; break; + case 'Float': {{{ makeSetValue('params', 'i*4', 'data[i]', 'float') }}}; break; default: throw 'internal emscriptenWebGLGetUniform() error, bad type: ' + type; } } @@ -2731,9 +2731,9 @@ var LibraryGL = { } else { for (var i = 0; i < data.length; i++) { switch (type) { - case 'Integer': {{{ makeSetValue('params', 'i', 'data[i]', 'i32') }}}; break; - case 'Float': {{{ makeSetValue('params', 'i', 'data[i]', 'float') }}}; break; - case 'FloatToInteger': {{{ makeSetValue('params', 'i', 'Math.fround(data[i])', 'i32') }}}; break; + case 'Integer': {{{ makeSetValue('params', 'i*4', 'data[i]', 'i32') }}}; break; + case 'Float': {{{ makeSetValue('params', 'i*4', 'data[i]', 'float') }}}; break; + case 'FloatToInteger': {{{ makeSetValue('params', 'i*4', 'Math.fround(data[i])', 'i32') }}}; break; default: throw 'internal emscriptenWebGLGetVertexAttrib() error, bad type: ' + type; } } @@ -7128,6 +7128,10 @@ var LibraryGL = { }, glPopMatrix: function() { + if (GLImmediate.matrixStack[GLImmediate.currentMatrix].length == 0) { + GL.recordError(0x0504/*GL_STACK_UNDERFLOW*/); + return; + } GLImmediate.matricesModified = true; GLImmediate.matrixVersion[GLImmediate.currentMatrix] = (GLImmediate.matrixVersion[GLImmediate.currentMatrix] + 1)|0; GLImmediate.matrix[GLImmediate.currentMatrix] = GLImmediate.matrixStack[GLImmediate.currentMatrix].pop(); diff --git a/src/library_glfw.js b/src/library_glfw.js index b71bab38e491a..fd81f2da59ca5 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -13,7 +13,6 @@ * What it does not but should probably do: * - Transmit events when glfwPollEvents, glfwWaitEvents or glfwSwapBuffers is * called. Events callbacks are called as soon as event are received. - * - Joystick support. * - Input modes. * - Gamma ramps. * - Video modes. @@ -81,6 +80,7 @@ var LibraryGLFW = { return GLFW.windows[id - 1]; }, + joystickFunc: null, // GLFWjoystickfun errorFunc: null, // GLFWerrorfun monitorFunc: null, // GLFWmonitorfun active: null, // active window @@ -382,6 +382,14 @@ var LibraryGLFW = { #endif }, + onGamepadConnected: function(event) { + GLFW.refreshJoysticks(); + }, + + onGamepadDisconnected: function(event) { + GLFW.refreshJoysticks(); + }, + onKeydown: function(event) { GLFW.onKeyChanged(event.keyCode, 1); // GLFW_PRESS or GLFW_REPEAT @@ -641,6 +649,68 @@ var LibraryGLFW = { } }, + setJoystickCallback: function(cbfun) { + GLFW.joystickFunc = cbfun; + GLFW.refreshJoysticks(); + }, + + joys: {}, // glfw joystick data + lastGamepadState: null, + lastGamepadStateFrame: null, // The integer value of Browser.mainLoop.currentFrameNumber of when the last gamepad state was produced. + + refreshJoysticks: function() { + // Produce a new Gamepad API sample if we are ticking a new game frame, or if not using emscripten_set_main_loop() at all to drive animation. + if (Browser.mainLoop.currentFrameNumber !== GLFW.lastGamepadStateFrame || !Browser.mainLoop.currentFrameNumber) { + GLFW.lastGamepadState = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : null); + GLFW.lastGamepadStateFrame = Browser.mainLoop.currentFrameNumber; + + for (var joy = 0; joy < GLFW.lastGamepadState.length; ++joy) { + var gamepad = GLFW.lastGamepadState[joy]; + + if (gamepad) { + if (!GLFW.joys[joy]) { + console.log('glfw joystick connected:',joy); + GLFW.joys[joy] = { + id: allocate(intArrayFromString(gamepad.id), 'i8', ALLOC_NORMAL), + buttonsCount: gamepad.buttons.length, + axesCount: gamepad.axes.length, + buttons: allocate(new Array(gamepad.buttons.length), 'i8', ALLOC_NORMAL), + axes: allocate(new Array(gamepad.axes.length*4), 'float', ALLOC_NORMAL) + }; + + if (GLFW.joystickFunc) { + Module['dynCall_vii'](GLFW.joystickFunc, joy, 0x00040001); // GLFW_CONNECTED + } + } + + var data = GLFW.joys[joy]; + + for (var i = 0; i < gamepad.buttons.length; ++i) { + setValue(data.buttons + i, gamepad.buttons[i].pressed, 'i8'); + } + + for (var i = 0; i < gamepad.axes.length; ++i) { + setValue(data.axes + i*4, gamepad.axes[i], 'float'); + } + } else { + if (GLFW.joys[joy]) { + console.log('glfw joystick disconnected',joy); + + if (GLFW.joystickFunc) { + Module['dynCall_vii'](GLFW.joystickFunc, joy, 0x00040002); // GLFW_DISCONNECTED + } + + _free(GLFW.joys[joy].id); + _free(GLFW.joys[joy].buttons); + _free(GLFW.joys[joy].axes); + + delete GLFW.joys[joy]; + } + } + } + } + }, + setKeyCallback: function(winid, cbfun) { var win = GLFW.WindowFromId(winid); if (!win) return; @@ -954,6 +1024,8 @@ var LibraryGLFW = { GLFW.windows = new Array() GLFW.active = null; + window.addEventListener("gamepadconnected", GLFW.onGamepadConnected, true); + window.addEventListener("gamepaddisconnected", GLFW.onGamepadDisconnected, true); window.addEventListener("keydown", GLFW.onKeydown, true); window.addEventListener("keypress", GLFW.onKeyPress, true); window.addEventListener("keyup", GLFW.onKeyup, true); @@ -973,6 +1045,8 @@ var LibraryGLFW = { }, glfwTerminate: function() { + window.removeEventListener("gamepadconnected", GLFW.onGamepadConnected, true); + window.removeEventListener("gamepaddisconnected", GLFW.onGamepadDisconnected, true); window.removeEventListener("keydown", GLFW.onKeydown, true); window.removeEventListener("keypress", GLFW.onKeyPress, true); window.removeEventListener("keyup", GLFW.onKeyup, true); @@ -1354,15 +1428,49 @@ var LibraryGLFW = { glfwCreateWindowSurface: function(instance, winid, allocator, surface) { throw "glfwCreateWindowSurface is not implemented."; }, - glfwSetJoystickCallback: function(cbfun) { throw "glfwSetJoystickCallback is not implemented."; }, + glfwJoystickPresent: function(joy) { + GLFW.refreshJoysticks(); - glfwJoystickPresent: function(joy) { throw "glfwJoystickPresent is not implemented."; }, + return GLFW.joys[joy] !== undefined; + }, - glfwGetJoystickAxes: function(joy, count) { throw "glfwGetJoystickAxes is not implemented."; }, + glfwGetJoystickAxes: function(joy, count) { + GLFW.refreshJoysticks(); + + var state = GLFW.joys[joy]; + if (!state || !state.axes) { + setValue(count, 0, 'i32'); + return; + } + + setValue(count, state.axesCount, 'i32'); + return state.axes; + }, - glfwGetJoystickButtons: function(joy, count) { throw "glfwGetJoystickButtons is not implemented."; }, + glfwGetJoystickButtons: function(joy, count) { + GLFW.refreshJoysticks(); - glfwGetJoystickName: function(joy) { throw "glfwGetJoystickName is not implemented."; }, + var state = GLFW.joys[joy]; + if (!state || !state.buttons) { + setValue(count, 0, 'i32'); + return; + } + + setValue(count, state.buttonsCount, 'i32'); + return state.buttons; + }, + + glfwGetJoystickName: function(joy) { + if (GLFW.joys[joy]) { + return GLFW.joys[joy].id; + } else { + return 0; + } + }, + + glfwSetJoystickCallback: function(cbfun) { + GLFW.setJoystickCallback(cbfun); + }, glfwSetClipboardString: function(win, string) {}, diff --git a/src/library_sdl.js b/src/library_sdl.js index 2df6ea3136f74..96851be1befbd 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -2216,6 +2216,20 @@ var LibrarySDL = { data[destPtr++] = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}}; data[destPtr++] = 255; } + } else if (raw.bpp == 2) { + // grayscale + alpha + var pixels = raw.size; + var data = imageData.data; + var sourcePtr = raw.data; + var destPtr = 0; + for (var i = 0; i < pixels; i++) { + var gray = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}}; + var alpha = {{{ makeGetValue('sourcePtr++', 0, 'i8', null, 1) }}}; + data[destPtr++] = gray; + data[destPtr++] = gray; + data[destPtr++] = gray; + data[destPtr++] = alpha; + } } else if (raw.bpp == 1) { // grayscale var pixels = raw.size; diff --git a/src/settings.js b/src/settings.js index 0d74dfbff8e6c..c4c668e78c7e3 100644 --- a/src/settings.js +++ b/src/settings.js @@ -721,6 +721,13 @@ var BINARYEN_ASYNC_COMPILATION = 1; // Whether to compile the wasm asynchronousl var BINARYEN_ROOT = ""; // Directory where we can find Binaryen. Will be automatically set for you, // but you can set it to override if you are a Binaryen developer. +var LEGALIZE_JS_FFI = 1; // Whether to legalize the JS FFI interfaces (imports/exports) by wrapping + // them to automatically demote i64 to i32 and promote f32 to f64. This is + // necessary in order to interface with JavaScript, both for asm.js and wasm. + // For non-web/non-JS embeddings, setting this to 0 may be desirable. + // LEGALIZE_JS_FFI=0 is incompatible with RUNNING_JS_OPTS and using + // non-wasm BINARYEN_METHOD settings. + var WASM = 0; // Alias for BINARYEN, the two are identical. Both make us compile code to WebAssembly. var WASM_BACKEND = 0; // Whether to use the WebAssembly backend that is in development in LLVM. diff --git a/src/webGLClient.js b/src/webGLClient.js index 6eda55deec636..722870503e341 100644 --- a/src/webGLClient.js +++ b/src/webGLClient.js @@ -316,7 +316,11 @@ WebGLClient.prefetch = function() { // Create a fake temporary GL context var canvas = document.createElement('canvas'); var ctx = canvas.getContext('webgl-experimental') || canvas.getContext('webgl'); - if (!ctx) return; + if (!ctx) { + // If we have no webGL support, we still notify that prefetching is done, as the app blocks on that + worker.postMessage({ target: 'gl', op: 'setPrefetched', preMain: true }); + return; + } // Fetch the parameters and proxy them var parameters = {}; diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 63e03a768dc6e..66d9e58d6ae36 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -25,6 +25,7 @@ namespace emscripten { typedef long GenericEnumValue; typedef void* GenericFunction; + typedef void (*VoidFunctionPtr)(void); // Implemented in JavaScript. Don't call these directly. extern "C" { @@ -1012,12 +1013,12 @@ namespace emscripten { } template - static GenericFunction getUpcaster() { + static VoidFunctionPtr getUpcaster() { return nullptr; } template - static GenericFunction getDowncaster() { + static VoidFunctionPtr getDowncaster() { return nullptr; } }; @@ -1116,13 +1117,12 @@ namespace emscripten { EMSCRIPTEN_ALWAYS_INLINE explicit class_(const char* name) { using namespace internal; - typedef void (*VoidFunctionPtr)(void); BaseSpecifier::template verify(); auto _getActualType = &getActualType; - VoidFunctionPtr upcast = (VoidFunctionPtr)BaseSpecifier::template getUpcaster(); - VoidFunctionPtr downcast = (VoidFunctionPtr)BaseSpecifier::template getDowncaster(); + auto upcast = BaseSpecifier::template getUpcaster(); + auto downcast = BaseSpecifier::template getDowncaster(); auto destructor = &raw_destructor; _embind_register_class( diff --git a/system/lib/embind/bind.cpp b/system/lib/embind/bind.cpp index ebd041ca0fe9f..0a47e7bd5eca3 100644 --- a/system/lib/embind/bind.cpp +++ b/system/lib/embind/bind.cpp @@ -1,149 +1,149 @@ -#include -#ifdef USE_CXA_DEMANGLE -#include <../lib/libcxxabi/include/cxxabi.h> -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace emscripten; - -extern "C" { - const char* __attribute__((used)) __getTypeName(const std::type_info* ti) { - if (has_unbound_type_names) { -#ifdef USE_CXA_DEMANGLE - int stat; - char* demangled = abi::__cxa_demangle(ti->name(), NULL, NULL, &stat); - if (stat == 0 && demangled) { - return demangled; - } - - switch (stat) { - case -1: - return strdup(""); - case -2: - return strdup(""); - case -3: - return strdup(""); - default: - return strdup(""); - } -#else - return strdup(ti->name()); -#endif - } else { - char str[80]; - sprintf(str, "%p", reinterpret_cast(ti)); - return strdup(str); - } - } -} - -namespace { - template - static void register_integer(const char* name) { - using namespace internal; - _embind_register_integer(TypeID::get(), name, sizeof(T), std::numeric_limits::min(), std::numeric_limits::max()); - } - - template - static void register_float(const char* name) { - using namespace internal; - _embind_register_float(TypeID::get(), name, sizeof(T)); - } - - - // matches typeMapping in embind.js - enum TypedArrayIndex { - Int8Array, - Uint8Array, - Int16Array, - Uint16Array, - Int32Array, - Uint32Array, - Float32Array, - Float64Array, - }; - - template - constexpr TypedArrayIndex getTypedArrayIndex() { - static_assert(internal::typeSupportsMemoryView(), - "type does not map to a typed array"); - return std::is_floating_point::value - ? (sizeof(T) == 4 - ? Float32Array - : Float64Array) - : (sizeof(T) == 1 - ? (std::is_signed::value ? Int8Array : Uint8Array) - : (sizeof(T) == 2 - ? (std::is_signed::value ? Int16Array : Uint16Array) - : (std::is_signed::value ? Int32Array : Uint32Array))); - } - - template - static void register_memory_view(const char* name) { - using namespace internal; - _embind_register_memory_view(TypeID>::get(), getTypedArrayIndex(), name); - } -} - -EMSCRIPTEN_BINDINGS(native_and_builtin_types) { - using namespace emscripten::internal; - - _embind_register_void(TypeID::get(), "void"); - - _embind_register_bool(TypeID::get(), "bool", sizeof(bool), true, false); - - register_integer("char"); - register_integer("signed char"); - register_integer("unsigned char"); - register_integer("short"); - register_integer("unsigned short"); - register_integer("int"); - register_integer("unsigned int"); - register_integer("long"); - register_integer("unsigned long"); - - register_float("float"); - register_float("double"); - - _embind_register_std_string(TypeID::get(), "std::string"); - _embind_register_std_string(TypeID >::get(), "std::basic_string"); - _embind_register_std_wstring(TypeID::get(), sizeof(wchar_t), "std::wstring"); - _embind_register_emval(TypeID::get(), "emscripten::val"); - - // Some of these types are aliases for each other. Luckily, - // embind.js's _embind_register_memory_view ignores duplicate - // registrations rather than asserting, so the first - // register_memory_view call for a particular type will take - // precedence. - - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); - - register_memory_view("emscripten::memory_view"); - register_memory_view("emscripten::memory_view"); -#if __SIZEOF_LONG_DOUBLE__ == __SIZEOF_DOUBLE__ - register_memory_view("emscripten::memory_view"); -#endif -} +#include +#ifdef USE_CXA_DEMANGLE +#include <../lib/libcxxabi/include/cxxabi.h> +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace emscripten; + +extern "C" { + const char* __attribute__((used)) __getTypeName(const std::type_info* ti) { + if (has_unbound_type_names) { +#ifdef USE_CXA_DEMANGLE + int stat; + char* demangled = abi::__cxa_demangle(ti->name(), NULL, NULL, &stat); + if (stat == 0 && demangled) { + return demangled; + } + + switch (stat) { + case -1: + return strdup(""); + case -2: + return strdup(""); + case -3: + return strdup(""); + default: + return strdup(""); + } +#else + return strdup(ti->name()); +#endif + } else { + char str[80]; + sprintf(str, "%p", reinterpret_cast(ti)); + return strdup(str); + } + } +} + +namespace { + template + static void register_integer(const char* name) { + using namespace internal; + _embind_register_integer(TypeID::get(), name, sizeof(T), std::numeric_limits::min(), std::numeric_limits::max()); + } + + template + static void register_float(const char* name) { + using namespace internal; + _embind_register_float(TypeID::get(), name, sizeof(T)); + } + + + // matches typeMapping in embind.js + enum TypedArrayIndex { + Int8Array, + Uint8Array, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array, + }; + + template + constexpr TypedArrayIndex getTypedArrayIndex() { + static_assert(internal::typeSupportsMemoryView(), + "type does not map to a typed array"); + return std::is_floating_point::value + ? (sizeof(T) == 4 + ? Float32Array + : Float64Array) + : (sizeof(T) == 1 + ? (std::is_signed::value ? Int8Array : Uint8Array) + : (sizeof(T) == 2 + ? (std::is_signed::value ? Int16Array : Uint16Array) + : (std::is_signed::value ? Int32Array : Uint32Array))); + } + + template + static void register_memory_view(const char* name) { + using namespace internal; + _embind_register_memory_view(TypeID>::get(), getTypedArrayIndex(), name); + } +} + +EMSCRIPTEN_BINDINGS(native_and_builtin_types) { + using namespace emscripten::internal; + + _embind_register_void(TypeID::get(), "void"); + + _embind_register_bool(TypeID::get(), "bool", sizeof(bool), true, false); + + register_integer("char"); + register_integer("signed char"); + register_integer("unsigned char"); + register_integer("short"); + register_integer("unsigned short"); + register_integer("int"); + register_integer("unsigned int"); + register_integer("long"); + register_integer("unsigned long"); + + register_float("float"); + register_float("double"); + + _embind_register_std_string(TypeID::get(), "std::string"); + _embind_register_std_string(TypeID >::get(), "std::basic_string"); + _embind_register_std_wstring(TypeID::get(), sizeof(wchar_t), "std::wstring"); + _embind_register_emval(TypeID::get(), "emscripten::val"); + + // Some of these types are aliases for each other. Luckily, + // embind.js's _embind_register_memory_view ignores duplicate + // registrations rather than asserting, so the first + // register_memory_view call for a particular type will take + // precedence. + + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); + + register_memory_view("emscripten::memory_view"); + register_memory_view("emscripten::memory_view"); +#if __SIZEOF_LONG_DOUBLE__ == __SIZEOF_DOUBLE__ + register_memory_view("emscripten::memory_view"); +#endif +} diff --git a/tests/core/test_embind_5.cpp b/tests/core/test_embind_5.cpp index 539bb4875be57..8f06104053cd6 100644 --- a/tests/core/test_embind_5.cpp +++ b/tests/core/test_embind_5.cpp @@ -2,29 +2,50 @@ #include using namespace emscripten; + + class MyFoo { public: - MyFoo() { - EM_ASM({print("constructing my foo");}); - } - void doit() { - EM_ASM({print("doing it");}); - } + MyFoo() { + EM_ASM({Module.print("constructing my foo");}); + } + virtual void doit() { + EM_ASM({Module.print("doing it");}); + } +}; + +class MyBar : public MyFoo { +public: + MyBar() { + EM_ASM({Module.print("constructing my bar");}); + } + void doit() override { + EM_ASM({Module.print("doing something else");}); + } }; + + EMSCRIPTEN_BINDINGS(my_module) { - class_("MyFoo") - .constructor<>() - .function("doit", &MyFoo::doit) - ; + class_("MyFoo") + .constructor<>() + .function("doit", &MyFoo::doit) + ; + class_>("MyBar") + .constructor<>() + ; } + + int main(int argc, char **argv) { - EM_ASM( - try { - var foo = new Module.MyFoo(); - foo.doit(); - } catch(e) { - Module.print(e); - } - ); - return 0; + EM_ASM( + try { + var foo = new Module.MyFoo(); + foo.doit(); + var bar = new Module.MyBar(); + bar.doit(); + } catch(e) { + Module.print(e); + } + ); + return 0; } diff --git a/tests/core/test_embind_5.out b/tests/core/test_embind_5.out index 1227c93ffe3e0..3d9881af0a054 100644 --- a/tests/core/test_embind_5.out +++ b/tests/core/test_embind_5.out @@ -1,2 +1,5 @@ constructing my foo -doing it \ No newline at end of file +doing it +constructing my foo +constructing my bar +doing something else diff --git a/tests/full_es2_sdlproc.c b/tests/full_es2_sdlproc.c index 3f72f2bf6e03c..18648dc7b0f22 100644 --- a/tests/full_es2_sdlproc.c +++ b/tests/full_es2_sdlproc.c @@ -48,6 +48,7 @@ #include #include #include +#include #ifndef HAVE_BUILTIN_SINCOS #include "sincos.h" @@ -698,6 +699,14 @@ gears_init(void) /* Set the LightSourcePosition uniform which is constant throught the program */ glUniform4fv(LightSourcePosition_location, 1, LightSourcePosition); + { + GLfloat LightSourcePosition_copy[4]; + glGetUniformfv(program, LightSourcePosition_location, LightSourcePosition_copy); + for (uint32_t i = 0; i < 4; ++i) { + assert(LightSourcePosition[i] == LightSourcePosition_copy[i]); + } + } + /* make the gears */ gear1 = create_gear(1.0, 4.0, 1.0, 20, 0.7); gear2 = create_gear(0.5, 2.0, 2.0, 10, 0.7); diff --git a/tests/gl_error.c b/tests/gl_error.c new file mode 100644 index 0000000000000..e06a587b6c2d6 --- /dev/null +++ b/tests/gl_error.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include +#include +#include "SDL/SDL.h" + +int main() +{ + SDL_Surface *screen; + + assert(SDL_Init(SDL_INIT_VIDEO) == 0); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + screen = SDL_SetVideoMode( 256, 256, 16, SDL_OPENGL ); + assert(screen); + + // pop from empty stack + glPopMatrix(); + assert(glGetError() == GL_STACK_UNDERFLOW); + + int result = 1; + REPORT_RESULT(); + return 0; +} diff --git a/tests/glfw_joystick.c b/tests/glfw_joystick.c new file mode 100644 index 0000000000000..eb73ca86bd211 --- /dev/null +++ b/tests/glfw_joystick.c @@ -0,0 +1,105 @@ +#include +#include +#include +#ifdef __EMSCRIPTEN__ +#include +#endif +#define GLFW_INCLUDE_ES2 +#include + +int result = 1; + +GLFWwindow* g_window; + +void render(); +void error_callback(int error, const char* description); + +void render() { + glClearColor(0.7f, 0.7f, 0.7f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + for (int j = GLFW_JOYSTICK_1; j < GLFW_JOYSTICK_16; ++j) { + int joy = GLFW_JOYSTICK_1 + j; + if (!glfwJoystickPresent(joy)) continue; + + static struct { + int axes_count; + float axes[16]; + int button_count; + unsigned char buttons[16]; + } last_gamepad_state[16] = {0}; + + const char *name = glfwGetJoystickName(joy); + + int axes_count = 0; + const float *axes = glfwGetJoystickAxes(joy, &axes_count); + + int button_count = 0; + const unsigned char *buttons = glfwGetJoystickButtons(joy, &button_count); + + last_gamepad_state[joy].axes_count = axes_count; + for (int i = 0; i < axes_count; ++i) { + if (last_gamepad_state[joy].axes[i] != axes[i]) { + printf("(%d %s) axis %d = %f\n", joy, name, i, axes[i]); + } + + last_gamepad_state[joy].axes[i] = axes[i]; + } + + last_gamepad_state[joy].button_count = button_count; + for (int i = 0; i < button_count; ++i) { + if (last_gamepad_state[joy].buttons[i] != buttons[i]) { + printf("(%d %s) button %d = %d\n", joy, name, i, buttons[i]); + } + + last_gamepad_state[joy].buttons[i] = buttons[i]; + } + } +} + +void joystick_callback(int joy, int event) +{ + if (event == GLFW_CONNECTED) { + printf("Joystick %d was connected: %s\n", joy, glfwGetJoystickName(joy)); + } else if (event == GLFW_DISCONNECTED) { + printf("Joystick %d was disconnected\n", joy); + } +} + +int main() { + if (!glfwInit()) + { + result = 0; + printf("Could not create window. Test failed.\n"); +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif + return -1; + } + glfwWindowHint(GLFW_RESIZABLE , 1); + g_window = glfwCreateWindow(600, 450, "GLFW joystick test", NULL, NULL); + if (!g_window) + { + result = 0; + printf("Could not create window. Test failed.\n"); +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(g_window); + glfwSetJoystickCallback(joystick_callback); + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(render, 0, 1); +#else + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } +#endif + + glfwTerminate(); + + return 0; +} diff --git a/tests/optimizer/test-js-optimizer-asm-regs-harder-output.js b/tests/optimizer/test-js-optimizer-asm-regs-harder-output.js index bf478c59a99bf..1843e0e23b3b2 100644 --- a/tests/optimizer/test-js-optimizer-asm-regs-harder-output.js +++ b/tests/optimizer/test-js-optimizer-asm-regs-harder-output.js @@ -138,4 +138,17 @@ function b1() { abort(1); return SIMD_Float32x4(0, 0, 0, 0); } +function __emscripten_dceable_type_decls() { + ___cxa_throw(0, 0, 0); + ___cxa_rethrow(); + ___cxa_end_catch(); + ___cxa_pure_virtual(); + _getenv(0) | 0; + _pthread_mutex_lock(0) | 0; + _pthread_cond_broadcast(0) | 0; + _pthread_mutex_unlock(0) | 0; + _pthread_cond_wait(0, 0) | 0; + _strftime(0, 0, 0, 0) | 0; + +_fabs(+0); +} diff --git a/tests/optimizer/test-js-optimizer-asm-regs-harder-output2.js b/tests/optimizer/test-js-optimizer-asm-regs-harder-output2.js index 66c6b2f6c1740..9bb9d49d067b5 100644 --- a/tests/optimizer/test-js-optimizer-asm-regs-harder-output2.js +++ b/tests/optimizer/test-js-optimizer-asm-regs-harder-output2.js @@ -138,4 +138,17 @@ function b1() { abort(1); return SIMD_Float32x4(0, 0, 0, 0); } +function __emscripten_dceable_type_decls() { + ___cxa_throw(0, 0, 0); + ___cxa_rethrow(); + ___cxa_end_catch(); + ___cxa_pure_virtual(); + _getenv(0) | 0; + _pthread_mutex_lock(0) | 0; + _pthread_cond_broadcast(0) | 0; + _pthread_mutex_unlock(0) | 0; + _pthread_cond_wait(0, 0) | 0; + _strftime(0, 0, 0, 0) | 0; + +_fabs(+0); +} diff --git a/tests/optimizer/test-js-optimizer-asm-regs-harder-output3.js b/tests/optimizer/test-js-optimizer-asm-regs-harder-output3.js index 6d01521b7b82f..311f131082123 100644 --- a/tests/optimizer/test-js-optimizer-asm-regs-harder-output3.js +++ b/tests/optimizer/test-js-optimizer-asm-regs-harder-output3.js @@ -138,4 +138,17 @@ function b1() { abort(1); return SIMD_Float32x4(0, 0, 0, 0); } +function __emscripten_dceable_type_decls() { + ___cxa_throw(0, 0, 0); + ___cxa_rethrow(); + ___cxa_end_catch(); + ___cxa_pure_virtual(); + _getenv(0) | 0; + _pthread_mutex_lock(0) | 0; + _pthread_cond_broadcast(0) | 0; + _pthread_mutex_unlock(0) | 0; + _pthread_cond_wait(0, 0) | 0; + _strftime(0, 0, 0, 0) | 0; + +_fabs(+0); +} diff --git a/tests/optimizer/test-js-optimizer-asm-regs-harder.js b/tests/optimizer/test-js-optimizer-asm-regs-harder.js index ca22b11505128..cffb6f0e8db72 100644 --- a/tests/optimizer/test-js-optimizer-asm-regs-harder.js +++ b/tests/optimizer/test-js-optimizer-asm-regs-harder.js @@ -159,5 +159,19 @@ function b1() { abort(1); return SIMD_Float32x4_check(SIMD_Float32x4(0, 0, 0, 0)); } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "stackRestore", "switchey", "switchey2", "iffey", "labelledJump", "linkedVars", "deadCondExpr", "b1"] +// this should be kept alive at all costs +function __emscripten_dceable_type_decls() { + ___cxa_throw(0, 0, 0); + ___cxa_rethrow(); + ___cxa_end_catch(); + ___cxa_pure_virtual(); + _getenv(0) | 0; + _pthread_mutex_lock(0) | 0; + _pthread_cond_broadcast(0) | 0; + _pthread_mutex_unlock(0) | 0; + _pthread_cond_wait(0, 0) | 0; + _strftime(0, 0, 0, 0) | 0; + +_fabs(+0); +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "stackRestore", "switchey", "switchey2", "iffey", "labelledJump", "linkedVars", "deadCondExpr", "b1", "__emscripten_dceable_type_decls"] diff --git a/tests/optimizer/test-js-optimizer-shiftsAggressive-output.js b/tests/optimizer/test-js-optimizer-shiftsAggressive-output.js index 5b42978622d0b..25826166a8dde 100644 --- a/tests/optimizer/test-js-optimizer-shiftsAggressive-output.js +++ b/tests/optimizer/test-js-optimizer-shiftsAggressive-output.js @@ -3,9 +3,11 @@ function __ZNSt3__111__call_onceERVmPvPFvS2_E($flag, $arg, $func) { $arg = $arg | 0; $func = $func | 0; var $2 = 0; + 0; $2 = cheez(); whee1($flag + 1 | 0); whee2($flag + 1 | 0); whee3($flag + 1 | 0); + whee4(1 + 0); } diff --git a/tests/optimizer/test-js-optimizer-shiftsAggressive.js b/tests/optimizer/test-js-optimizer-shiftsAggressive.js index 4218d04cbdcbe..5e00f03d7af56 100644 --- a/tests/optimizer/test-js-optimizer-shiftsAggressive.js +++ b/tests/optimizer/test-js-optimizer-shiftsAggressive.js @@ -2,12 +2,13 @@ function __ZNSt3__111__call_onceERVmPvPFvS2_E($flag,$arg,$func){ $flag=($flag)|0; $arg=($arg)|0; $func=($func)|0; - var $1=0,$2=0,$3=0; + var $1=0,$2=0,$3=0,$initializedAndNotReassigned=0; $1; $2 = cheez(); $3 = $flag + 1 | 0; whee1($3); whee2($3); whee3($3); + whee4(1 + $initializedAndNotReassigned); } // EMSCRIPTEN_GENERATED_FUNCTIONS: ["__ZNSt3__111__call_onceERVmPvPFvS2_E"] diff --git a/tests/other/ffi.c b/tests/other/ffi.c new file mode 100644 index 0000000000000..80f44fd71fa04 --- /dev/null +++ b/tests/other/ffi.c @@ -0,0 +1,26 @@ +#include +#include + +float add_f (float a, float b) { + return a + b; +} + +long long add_ll (long long a, long long b) { + return a + b; +} + +extern float import_f(float x); + +extern long long import_ll(long long x); + +int main () { + float a = 1.2, + b = import_f((float)3.4), + c; + c = add_f(a, b); + + long long d = 1, + e = import_ll((long long)2), + f; + f = add_ll(d, e); +} diff --git a/tests/sdl-stb-bpp1.png b/tests/sdl-stb-bpp1.png new file mode 100644 index 0000000000000..21e8ee97624df Binary files /dev/null and b/tests/sdl-stb-bpp1.png differ diff --git a/tests/sdl-stb-bpp2.png b/tests/sdl-stb-bpp2.png new file mode 100644 index 0000000000000..14b194a0b356f Binary files /dev/null and b/tests/sdl-stb-bpp2.png differ diff --git a/tests/sdl-stb-bpp3.png b/tests/sdl-stb-bpp3.png new file mode 100644 index 0000000000000..e51a14f572a5d Binary files /dev/null and b/tests/sdl-stb-bpp3.png differ diff --git a/tests/sdl-stb-bpp4.png b/tests/sdl-stb-bpp4.png new file mode 100644 index 0000000000000..cd081ba25f3aa Binary files /dev/null and b/tests/sdl-stb-bpp4.png differ diff --git a/tests/test_browser.py b/tests/test_browser.py index e69b0a1abd48e..2aa698ed2ff5d 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -627,6 +627,27 @@ def test_sdl_stb_image(self): shutil.copyfile(path_from_root('tests', 'screenshot.jpg'), os.path.join(self.get_dir(), 'screenshot.not')) self.btest('sdl_stb_image.c', reference='screenshot.jpg', args=['-s', 'STB_IMAGE=1', '--preload-file', 'screenshot.not', '-lSDL', '-lGL']) + def test_sdl_stb_image_bpp(self): + # load grayscale image without alpha + self.clear() + shutil.copyfile(path_from_root('tests', 'sdl-stb-bpp1.png'), os.path.join(self.get_dir(), 'screenshot.not')) + self.btest('sdl_stb_image.c', reference='sdl-stb-bpp1.png', args=['-s', 'STB_IMAGE=1', '--preload-file', 'screenshot.not', '-lSDL', '-lGL']) + + # load grayscale image with alpha + self.clear() + shutil.copyfile(path_from_root('tests', 'sdl-stb-bpp2.png'), os.path.join(self.get_dir(), 'screenshot.not')) + self.btest('sdl_stb_image.c', reference='sdl-stb-bpp2.png', args=['-s', 'STB_IMAGE=1', '--preload-file', 'screenshot.not', '-lSDL', '-lGL']) + + # load RGB image + self.clear() + shutil.copyfile(path_from_root('tests', 'sdl-stb-bpp3.png'), os.path.join(self.get_dir(), 'screenshot.not')) + self.btest('sdl_stb_image.c', reference='sdl-stb-bpp3.png', args=['-s', 'STB_IMAGE=1', '--preload-file', 'screenshot.not', '-lSDL', '-lGL']) + + # load RGBA image + self.clear() + shutil.copyfile(path_from_root('tests', 'sdl-stb-bpp4.png'), os.path.join(self.get_dir(), 'screenshot.not')) + self.btest('sdl_stb_image.c', reference='sdl-stb-bpp4.png', args=['-s', 'STB_IMAGE=1', '--preload-file', 'screenshot.not', '-lSDL', '-lGL']) + def test_sdl_stb_image_data(self): # load an image file, get pixel data. shutil.copyfile(path_from_root('tests', 'screenshot.jpg'), os.path.join(self.get_dir(), 'screenshot.not')) @@ -1076,6 +1097,53 @@ def test_sdl_joystick_2(self): Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'sdl_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js', '-lSDL', '-lGL']).communicate() self.run_browser('page.html', '', '/report_result?2') + def test_glfw_joystick(self): + # Generates events corresponding to the Editor's Draft of the HTML5 Gamepad API. + # https://dvcs.w3.org/hg/gamepad/raw-file/default/gamepad.html#idl-def-Gamepad + open(os.path.join(self.get_dir(), 'pre.js'), 'w').write(''' + var gamepads = []; + // Spoof this function. + navigator['getGamepads'] = function() { + return gamepads; + }; + window['addNewGamepad'] = function(id, numAxes, numButtons) { + var index = gamepads.length; + var gamepad = { + axes: new Array(numAxes), + buttons: new Array(numButtons), + id: id, + index: index + }; + gamepads.push(gamepad) + var i; + for (i = 0; i < numAxes; i++) gamepads[index].axes[i] = 0; + // Buttons are objects + for (i = 0; i < numButtons; i++) gamepads[index].buttons[i] = { pressed: false, value: 0 }; + + // Dispatch event (required for glfw joystick; note not used in SDL test) + var event = new Event('gamepadconnected'); + event.gamepad = gamepad; + window.dispatchEvent(event); + }; + // FF mutates the original objects. + window['simulateGamepadButtonDown'] = function (index, button) { + gamepads[index].buttons[button].pressed = true; + gamepads[index].buttons[button].value = 1; + }; + window['simulateGamepadButtonUp'] = function (index, button) { + gamepads[index].buttons[button].pressed = false; + gamepads[index].buttons[button].value = 0; + }; + window['simulateAxisMotion'] = function (index, axis, value) { + gamepads[index].axes[axis] = value; + }; + ''') + open(os.path.join(self.get_dir(), 'test_glfw_joystick.c'), 'w').write(self.with_report_result(open(path_from_root('tests', 'test_glfw_joystick.c')).read())) + + Popen([PYTHON, EMCC, os.path.join(self.get_dir(), 'test_glfw_joystick.c'), '-O2', '--minify', '0', '-o', 'page.html', '--pre-js', 'pre.js', '-lGL', '-lglfw3', '-s', 'USE_GLFW=3']).communicate() + self.run_browser('page.html', '', '/report_result?2') + + def test_webgl_context_attributes(self): # Javascript code to check the attributes support we want to test in the WebGL implementation # (request the attribute, create a context and check its value afterwards in the context attributes). @@ -1883,6 +1951,9 @@ def test_subdata(self): def test_perspective(self): self.btest('perspective.c', reference='perspective.png', args=['-s', 'LEGACY_GL_EMULATION=1', '-lGL', '-lSDL']) + def test_glerror(self): + self.btest('gl_error.c', expected='1', args=['-s', 'LEGACY_GL_EMULATION=1', '-lGL']) + def test_runtimelink(self): main, supp = self.setup_runtimelink_test() open('supp.cpp', 'w').write(supp) diff --git a/tests/test_core.py b/tests/test_core.py index ee6b7068945a3..8bb094b9b2f2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -41,7 +41,8 @@ def decorated(f): # Async wasm compilation can't work in some tests, they are set up synchronously def sync(f): def decorated(self): - self.emcc_args += ['-s', 'BINARYEN_ASYNC_COMPILATION=0'] # test is set up synchronously + if self.is_wasm(): + self.emcc_args += ['-s', 'BINARYEN_ASYNC_COMPILATION=0'] # test is set up synchronously f(self) return decorated @@ -874,7 +875,7 @@ def test_exceptions(self): Settings.EXCEPTION_DEBUG = 1 Settings.DISABLE_EXCEPTION_CATCHING = 0 - if '-O2' in self.emcc_args and not self.is_wasm(): + if '-O2' in self.emcc_args: self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage src = ''' @@ -3509,6 +3510,39 @@ def test_dylink_many_postSets(self): } ''', expected=['simple.\nsimple.\nsimple.\nsimple.\n']) + def test_dylink_postSets_chunking(self): + self.dylink_test(header=r''' + extern int global_var; + ''', main=r''' + #include + #include "header.h" + + // prepare 99 global variable with local initializer + static int p = 1; + #define P(x) __attribute__((used)) int *padding##x = &p; + P(01) P(02) P(03) P(04) P(05) P(06) P(07) P(08) P(09) P(10) + P(11) P(12) P(13) P(14) P(15) P(16) P(17) P(18) P(19) P(20) + P(21) P(22) P(23) P(24) P(25) P(26) P(27) P(28) P(29) P(30) + P(31) P(32) P(33) P(34) P(35) P(36) P(37) P(38) P(39) P(40) + P(41) P(42) P(43) P(44) P(45) P(46) P(47) P(48) P(49) P(50) + P(51) P(52) P(53) P(54) P(55) P(56) P(57) P(58) P(59) P(60) + P(61) P(62) P(63) P(64) P(65) P(66) P(67) P(68) P(69) P(70) + P(71) P(72) P(73) P(74) P(75) P(76) P(77) P(78) P(79) P(80) + P(81) P(82) P(83) P(84) P(85) P(86) P(87) P(88) P(89) P(90) + P(91) P(92) P(93) P(94) P(95) P(96) P(97) P(98) P(99) + + // prepare global variable with global initializer + int *ptr = &global_var; + + int main(int argc, char *argv[]) { + printf("%d\n", *ptr); + } + ''', side=r''' + #include "header.h" + + int global_var = 12345; + ''', expected=['12345\n']) + @no_wasm # todo def test_dylink_syslibs(self): # one module uses libcxx, need to force its inclusion when it isn't the main if not self.can_dlfcn(): return @@ -3988,7 +4022,7 @@ def test_langinfo(self): def test_files(self): self.banned_js_engines = [SPIDERMONKEY_ENGINE] # closure can generate variables called 'gc', which pick up js shell stuff - if '-O2' in self.emcc_args and not self.is_wasm(): + if '-O2' in self.emcc_args: self.emcc_args += ['--closure', '1'] # Use closure here, to test we don't break FS stuff self.emcc_args = filter(lambda x: x != '-g', self.emcc_args) # ensure we test --closure 1 --memory-init-file 1 (-g would disable closure) elif '-O3' in self.emcc_args and not self.is_wasm(): @@ -4941,6 +4975,9 @@ def test_sse1(self): orig_args = self.emcc_args for mode in [[], ['-s', 'SIMD=1']]: self.emcc_args = orig_args + mode + ['-msse'] + if '-O2' in self.emcc_args: + self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.do_run(open(path_from_root('tests', 'test_sse1.cpp'), 'r').read(), 'Success!') # ignore nans in some simd tests due to an LLVM regression still being investigated, @@ -4961,6 +4998,9 @@ def test_sse1_full(self): orig_args = self.emcc_args for mode in [[], ['-s', 'SIMD=1']]: self.emcc_args = orig_args + mode + ['-I' + path_from_root('tests'), '-msse'] + if '-O2' in self.emcc_args: + self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.do_run(open(path_from_root('tests', 'test_sse1_full.cpp'), 'r').read(), self.ignore_nans(native_result), output_nicerizer=self.ignore_nans) # Tests the full SSE2 API. @@ -4981,6 +5021,9 @@ def test_sse2_full(self): orig_args = self.emcc_args for mode in [[], ['-s', 'SIMD=1']]: self.emcc_args = orig_args + mode + ['-I' + path_from_root('tests'), '-msse2'] + args + if '-O2' in self.emcc_args: + self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.do_run(open(path_from_root('tests', 'test_sse2_full.cpp'), 'r').read(), self.ignore_nans(native_result), output_nicerizer=self.ignore_nans) # Tests the full SSE3 API. @@ -5283,7 +5326,7 @@ def test_sqlite(self): force_c=True) def test_zlib(self): - if '-O2' in self.emcc_args and 'ASM_JS=0' not in self.emcc_args and not self.is_wasm(): # without asm, closure minifies Math.imul badly + if '-O2' in self.emcc_args and 'ASM_JS=0' not in self.emcc_args: # without asm, closure minifies Math.imul badly self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage assert 'asm2g' in test_modes @@ -5397,21 +5440,20 @@ def process(filename): #, build_ll_hook=self.do_autodebug) test() - num_original_funcs = self.count_funcs('src.cpp.o.js') - # Run with duplicate function elimination turned on - dfe_supported_opt_levels = ['-O2', '-O3', '-Oz', '-Os'] - - for opt_level in dfe_supported_opt_levels: - if opt_level in self.emcc_args: - print >> sys.stderr, "Testing poppler with ELIMINATE_DUPLICATE_FUNCTIONS set to 1" - Settings.ELIMINATE_DUPLICATE_FUNCTIONS = 1 - test() - - # Make sure that DFE ends up eliminating more than 200 functions (if we can view source) - if not self.is_wasm(): + if not self.is_wasm(): # wasm does this all the time + # Run with duplicate function elimination turned on + dfe_supported_opt_levels = ['-O2', '-O3', '-Oz', '-Os'] + + for opt_level in dfe_supported_opt_levels: + if opt_level in self.emcc_args: + print >> sys.stderr, "Testing poppler with ELIMINATE_DUPLICATE_FUNCTIONS set to 1" + num_original_funcs = self.count_funcs('src.cpp.o.js') + Settings.ELIMINATE_DUPLICATE_FUNCTIONS = 1 + test() + # Make sure that DFE ends up eliminating more than 200 functions (if we can view source) assert (num_original_funcs - self.count_funcs('src.cpp.o.js')) > 200 - break + break @sync def test_openjpeg(self): @@ -6113,10 +6155,13 @@ def test_tracing(self): self.do_run_in_out_file_test('tests', 'core', 'test_tracing') - @no_wasm # TODO: ctor evaller in binaryen + @no_wasm_backend('TODO') def test_eval_ctors(self): if '-O2' not in str(self.emcc_args) or '-O1' in str(self.emcc_args): return self.skip('need js optimizations') + if self.is_wasm(): + self.emcc_args += ['-s', 'NO_EXIT_RUNTIME=1'] + orig_args = self.emcc_args[:] + ['-s', 'EVAL_CTORS=0'] print 'leave printf in ctor' @@ -6143,13 +6188,15 @@ def do_test(test): code_size = os.stat(code_file).st_size if self.uses_memory_init_file(): mem_size = os.stat('src.cpp.o.js.mem').st_size - # if we are wasm, then eval-ctors disables wasm-only, losing i64 opts, increasing size + # if we are wasm, then the mem init is inside the wasm too, so the total change in code+data may grow *or* shrink code_size_should_shrink = not self.is_wasm() print code_size, ' => ', ec_code_size, ', are we testing code size?', code_size_should_shrink if self.uses_memory_init_file(): print mem_size, ' => ', ec_mem_size if code_size_should_shrink: assert ec_code_size < code_size + else: + assert ec_code_size != code_size, 'should at least change' if self.uses_memory_init_file(): assert ec_mem_size > mem_size @@ -6348,7 +6395,7 @@ def process(filename): ''' # XXX disable due to possible v8 bug -- self.do_run(src, '*166*\n*ok*', post_build=post) - if '-O2' in self.emcc_args and 'ASM_JS=0' not in self.emcc_args and not self.is_wasm(): # without asm, closure minifies Math.imul badly + if '-O2' in self.emcc_args and 'ASM_JS=0' not in self.emcc_args: # without asm, closure minifies Math.imul badly self.emcc_args += ['--closure', '1'] # Use closure here, to test we export things right # Way 2: use CppHeaderParser diff --git a/tests/test_glfw_joystick.c b/tests/test_glfw_joystick.c new file mode 100644 index 0000000000000..3adccfd031fab --- /dev/null +++ b/tests/test_glfw_joystick.c @@ -0,0 +1,138 @@ +#include +#include +#include +#ifdef __EMSCRIPTEN__ +#include +#endif +#define GLFW_INCLUDE_ES2 +#include + +int result = 1; + +GLFWwindow* g_window; + +void error_callback(int error, const char* description); + +int joy_connected = -1; + +void joystick_callback(int joy, int event) +{ + if (event == GLFW_CONNECTED) { + printf("Joystick %d was connected: %s\n", joy, glfwGetJoystickName(joy)); + joy_connected = joy; // use the most recently connected joystick + } else if (event == GLFW_DISCONNECTED) { + printf("Joystick %d was disconnected\n", joy); + if (joy == joy_connected) joy_connected = -1; + } +} + +void main_2(void *arg) { + printf("Testing adding new gamepads\n"); + emscripten_run_script("window.addNewGamepad('Pad Thai', 4, 16)"); + emscripten_run_script("window.addNewGamepad('Pad Kee Mao', 0, 4)"); + // Check that the joysticks exist properly. + assert(glfwJoystickPresent(GLFW_JOYSTICK_1)); + assert(glfwJoystickPresent(GLFW_JOYSTICK_2)); + + assert(strcmp(glfwGetJoystickName(GLFW_JOYSTICK_1), "Pad Thai") == 0); + assert(strcmp(glfwGetJoystickName(GLFW_JOYSTICK_2), "Pad Kee Mao") == 0); + + int axes_count = 0; + int buttons_count = 0; + + glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + assert(axes_count == 4); + assert(buttons_count == 16); + + glfwGetJoystickAxes(GLFW_JOYSTICK_2, &axes_count); + glfwGetJoystickButtons(GLFW_JOYSTICK_2, &buttons_count); + assert(axes_count == 0); + assert(buttons_count == 4); + + // Buttons + printf("Testing buttons\n"); + const unsigned char *buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + assert(buttons_count == 16); + assert(buttons[0] == 0); + emscripten_run_script("window.simulateGamepadButtonDown(0, 1)"); + buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + assert(buttons_count == 16); + assert(buttons[1] == 1); + + emscripten_run_script("window.simulateGamepadButtonUp(0, 1)"); + buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count); + assert(buttons_count == 16); + assert(buttons[1] == 0); + + + emscripten_run_script("window.simulateGamepadButtonDown(1, 0)"); + buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_2, &buttons_count); + assert(buttons_count == 4); + assert(buttons[0] == 1); + + emscripten_run_script("window.simulateGamepadButtonUp(1, 0)"); + buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_2, &buttons_count); + assert(buttons_count == 4); + assert(buttons[1] == 0); + + // Joystick wiggling + printf("Testing joystick axes\n"); + emscripten_run_script("window.simulateAxisMotion(0, 0, 1)"); + const float *axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + assert(axes_count == 4); + assert(axes[0] == 1); + + emscripten_run_script("window.simulateAxisMotion(0, 0, 0)"); + axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + assert(axes_count == 4); + assert(axes[0] == 0); + + emscripten_run_script("window.simulateAxisMotion(0, 1, -1)"); + axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count); + assert(axes_count == 4); + assert(axes[1] == -1); + + // End test. + result = 2; + printf("Test passed!\n"); + REPORT_RESULT(); +} + +int main() { + if (!glfwInit()) + { + result = 0; + printf("Could not create window. Test failed.\n"); +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif + return -1; + } + glfwWindowHint(GLFW_RESIZABLE , 1); + g_window = glfwCreateWindow(600, 450, "GLFW joystick test", NULL, NULL); + if (!g_window) + { + result = 0; + printf("Could not create window. Test failed.\n"); +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif + glfwTerminate(); + return -1; + } + glfwMakeContextCurrent(g_window); + glfwSetJoystickCallback(joystick_callback); + + emscripten_async_call(main_2, NULL, 3000); // avoid startup delays and intermittent errors + +#ifndef __EMSCRIPTEN__ + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } +#endif + + glfwTerminate(); + + return 0; +} diff --git a/tests/test_interactive.py b/tests/test_interactive.py index a08e6be1c5e4c..b6ea1c80b216a 100644 --- a/tests/test_interactive.py +++ b/tests/test_interactive.py @@ -140,6 +140,9 @@ def test_glfw_fullscreen(self): def test_glfw_get_key_stuck(self): self.btest('test_glfw_get_key_stuck.c', expected='1', args=['-s', 'NO_EXIT_RUNTIME=1', '-s', 'USE_GLFW=3']) + def test_glfw_joystick(self): + self.btest('glfw_joystick.c', expected='1', args=['-s', 'NO_EXIT_RUNTIME=1', '-s', 'USE_GLFW=3']) + def test_glfw_pointerlock(self): self.btest('test_glfw_pointerlock.c', expected='1', args=['-s', 'NO_EXIT_RUNTIME=1', '-s', 'USE_GLFW=3']) diff --git a/tests/test_other.py b/tests/test_other.py index 8bbe47da46bc6..18bf7c256a1ba 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -520,8 +520,12 @@ def check_makefile(configuration, dirname): # Run Cmake cmd = [emconfigure, 'cmake'] + cmake_args + ['-G', generator, cmakelistsdir] + env = os.environ.copy() + # https://github.com/kripken/emscripten/pull/5145: Check that CMake works even if EMCC_SKIP_SANITY_CHECK=1 is passed. + if test_dir == 'target_html': + env['EMCC_SKIP_SANITY_CHECK'] = '1' print str(cmd) - ret = Popen(cmd, stdout=None if EM_BUILD_VERBOSE_LEVEL >= 2 else PIPE, stderr=None if EM_BUILD_VERBOSE_LEVEL >= 1 else PIPE).communicate() + ret = Popen(cmd, env=env, stdout=None if EM_BUILD_VERBOSE_LEVEL >= 2 else PIPE, stderr=None if EM_BUILD_VERBOSE_LEVEL >= 1 else PIPE).communicate() if len(ret) > 1 and ret[1] != None and len(ret[1].strip()) > 0: logging.error(ret[1]) # If there were any errors, print them directly to console for diagnostics. if len(ret) > 1 and ret[1] != None and 'error' in ret[1].lower(): @@ -2293,7 +2297,7 @@ def test_embind(self): for tf in testFiles: f.write(open(tf, 'rb').read()) - output = run_js(self.in_dir('a.out.js'), stdout=PIPE, stderr=PIPE, full_output=True, assert_returncode=0) + output = run_js(self.in_dir('a.out.js'), stdout=PIPE, stderr=PIPE, full_output=True, assert_returncode=0, engine=NODE_JS) assert "FAIL" not in output, output def test_llvm_nativizer(self): @@ -5342,49 +5346,50 @@ def test_emconfigure_js_o(self): del os.environ['EMCONFIGURE_JS'] def test_emcc_c_multi(self): - def test(args, llvm_opts=None): - print args - lib = r''' - int mult() { return 1; } - ''' - - lib_name = 'libA.c' - open(lib_name, 'w').write(lib) - main = r''' - #include - int mult(); - int main() { - printf("result: %d\n", mult()); - return 0; - } - ''' - main_name = 'main.c' - open(main_name, 'w').write(main) + with clean_write_access_to_canonical_temp_dir(): + def test(args, llvm_opts=None): + print args + lib = r''' + int mult() { return 1; } + ''' + + lib_name = 'libA.c' + open(lib_name, 'w').write(lib) + main = r''' + #include + int mult(); + int main() { + printf("result: %d\n", mult()); + return 0; + } + ''' + main_name = 'main.c' + open(main_name, 'w').write(main) - if os.environ.get('EMCC_DEBUG'): return self.skip('cannot run in debug mode') - try: - os.environ['EMCC_DEBUG'] = '1' - out, err = Popen([PYTHON, EMCC, '-c', main_name, lib_name] + args, stderr=PIPE).communicate() - finally: - del os.environ['EMCC_DEBUG'] + if os.environ.get('EMCC_DEBUG'): return self.skip('cannot run in debug mode') + try: + os.environ['EMCC_DEBUG'] = '1' + out, err = Popen([PYTHON, EMCC, '-c', main_name, lib_name] + args, stderr=PIPE).communicate() + finally: + del os.environ['EMCC_DEBUG'] - VECTORIZE = '-disable-loop-vectorization' + VECTORIZE = '-disable-loop-vectorization' - if args: - assert err.count(VECTORIZE) == 2, err # specified twice, once per file + if args: + assert err.count(VECTORIZE) == 2, err # specified twice, once per file - assert err.count('emcc: LLVM opts: ' + llvm_opts + ' ' + VECTORIZE) == 2, err # exactly once per invocation of optimizer - else: - assert err.count(VECTORIZE) == 0, err # no optimizations + assert err.count('emcc: LLVM opts: ' + llvm_opts + ' ' + VECTORIZE) == 2, err # exactly once per invocation of optimizer + else: + assert err.count(VECTORIZE) == 0, err # no optimizations - Popen([PYTHON, EMCC, main_name.replace('.c', '.o'), lib_name.replace('.c', '.o')]).communicate() + Popen([PYTHON, EMCC, main_name.replace('.c', '.o'), lib_name.replace('.c', '.o')]).communicate() - self.assertContained('result: 1', run_js(os.path.join(self.get_dir(), 'a.out.js'))) + self.assertContained('result: 1', run_js(os.path.join(self.get_dir(), 'a.out.js'))) - test([]) - test(['-O2'], '-O3') - test(['-Oz'], '-Oz') - test(['-Os'], '-Os') + test([]) + test(['-O2'], '-O3') + test(['-Oz'], '-Oz') + test(['-Os'], '-Os') def test_export_all_3142(self): open('src.cpp', 'w').write(r''' @@ -7503,6 +7508,52 @@ def test_binaryen_ignore_implicit_traps(self): print 'sizes:', sizes assert sizes[1] < sizes[0], 'ignoring implicit traps must reduce code size' + # test disabling of JS FFI legalization + def test_legalize_js_ffi(self): + with clean_write_access_to_canonical_temp_dir(): + for (args,js_ffi) in [ + (['-s', 'LEGALIZE_JS_FFI=1', '-s', 'SIDE_MODULE=1', '-O2', ], True), + (['-s', 'LEGALIZE_JS_FFI=0', '-s', 'SIDE_MODULE=1', '-O2', ], False), + (['-s', 'LEGALIZE_JS_FFI=0', '-s', 'SIDE_MODULE=1', '-O0', ], False), + (['-s', 'LEGALIZE_JS_FFI=0', '-s', 'SIDE_MODULE=0', '-O0'], False), + ]: + print args + try_delete('a.out.wasm') + try_delete('a.out.wast') + cmd = [PYTHON, EMCC, path_from_root('tests', 'other', 'ffi.c'), '-s', 'WASM=1', '-g', '-o', 'a.out.js'] + args + print ' '.join(cmd) + proc = Popen(cmd, stdout=PIPE, stderr=PIPE) + output, err = proc.communicate() + assert proc.returncode == 0 + text = open('a.out.wast').read() + #print "text: %s" % text + e_add_f32 = re.search('func \$_add_f \(param \$*. f32\) \(param \$*. f32\) \(result f32\)', text) + i_i64_i32 = re.search('import .*"_import_ll" .*\(param i32 i32\) \(result i32\)', text) + i_f32_f64 = re.search('import .*"_import_f" .*\(param f64\) \(result f64\)', text) + i_i64_i64 = re.search('import .*"_import_ll" .*\(param i64\) \(result i64\)', text) + i_f32_f32 = re.search('import .*"_import_f" .*\(param f32\) \(result f32\)', text) + e_i64_i32 = re.search('func \$_add_ll \(param \$*. i32\) \(param \$*. i32\) \(param \$*. i32\) \(param \$*. i32\) \(result i32\)', text) + e_f32_f64 = re.search('func \$legalstub\$_add_f \(param \$*. f64\) \(param \$*. f64\) \(result f64\)', text) + e_i64_i64 = re.search('func \$_add_ll \(param \$*. i64\) \(param \$*. i64\) \(result i64\)', text) + #print e_add_f32, i_i64_i32, i_f32_f64, i_i64_i64, i_f32_f32, e_i64_i32, e_f32_f64, e_i64_i64 + assert e_add_f32, 'add_f export missing' + if js_ffi: + assert i_i64_i32, 'i64 not converted to i32 in imports' + assert i_f32_f64, 'f32 not converted to f64 in imports' + assert not i_i64_i64, 'i64 not converted to i32 in imports' + assert not i_f32_f32, 'f32 not converted to f64 in imports' + assert e_i64_i32, 'i64 not converted to i32 in exports' + assert e_f32_f64, 'f32 not converted to f64 in exports' + assert not e_i64_i64, 'i64 not converted to i32 in exports' + else: + assert not i_i64_i32, 'i64 converted to i32 in imports' + assert not i_f32_f64, 'f32 converted to f64 in imports' + assert i_i64_i64, 'i64 converted to i32 in imports' + assert i_f32_f32, 'f32 converted to f64 in imports' + assert not e_i64_i32, 'i64 converted to i32 in exports' + assert not e_f32_f64, 'f32 converted to f64 in exports' + assert e_i64_i64, 'i64 converted to i32 in exports' + def test_sysconf_phys_pages(self): for args, expected in [ ([], 1024), diff --git a/tools/ctor_evaller.py b/tools/ctor_evaller.py index 03b3aef2acad1..892bcad52fbd4 100644 --- a/tools/ctor_evaller.py +++ b/tools/ctor_evaller.py @@ -9,10 +9,13 @@ from tempfiles import try_delete js_file = sys.argv[1] -mem_init_file = sys.argv[2] +binary_file = sys.argv[2] # mem init for js, wasm binary for wasm total_memory = int(sys.argv[3]) total_stack = int(sys.argv[4]) global_base = int(sys.argv[5]) +binaryen_bin = sys.argv[6] + +wasm = not not binaryen_bin assert global_base > 0 @@ -32,7 +35,17 @@ def find_ctors(js): ctors_end += 3 return (ctors_start, ctors_end) -def eval_ctors(js, mem_init, num): +def find_ctors_data(js, num): + ctors_start, ctors_end = find_ctors(js) + assert ctors_start > 0 + ctors_text = js[ctors_start:ctors_end] + all_ctors = filter(lambda ctor: ctor.endswith('()') and not ctor == 'function()' and '.' not in ctor, ctors_text.split(' ')) + all_ctors = map(lambda ctor: ctor.replace('()', ''), all_ctors) + assert len(all_ctors) > 0 + ctors = all_ctors[:num] + return ctors_start, ctors_end, all_ctors, ctors + +def eval_ctors_js(js, mem_init, num): def kill_func(asm, name): before = len(asm) @@ -48,14 +61,7 @@ def add_func(asm, func): return asm # Find the global ctors - ctors_start, ctors_end = find_ctors(js) - assert ctors_start > 0 - ctors_text = js[ctors_start:ctors_end] - all_ctors = filter(lambda ctor: ctor.endswith('()') and not ctor == 'function()' and '.' not in ctor, ctors_text.split(' ')) - all_ctors = map(lambda ctor: ctor.replace('()', ''), all_ctors) - total_ctors = len(all_ctors) - assert total_ctors > 0 - ctors = all_ctors[:num] + ctors_start, ctors_end, all_ctors, ctors = find_ctors_data(js, num) shared.logging.debug('trying to eval ctors: ' + ', '.join(ctors)) # Find the asm module, and receive the mem init. asm = get_asm(js) @@ -257,6 +263,7 @@ def read_and_delete(filename): # out contains the new mem init and other info num_successful, mem_init_raw, atexits = json.loads(out_result) mem_init = ''.join(map(chr, mem_init_raw)) + total_ctors = len(all_ctors) if num_successful < total_ctors: shared.logging.debug('not all ctors could be evalled, something was used that was not safe (and therefore was not defined, and caused an error):\n========\n' + err_result + '========') # Remove the evalled ctors, add a new one for atexits if needed, and write that out @@ -272,6 +279,23 @@ def read_and_delete(filename): js = js[:ctors_start] + new_ctors + js[ctors_end:] return (num_successful, js, mem_init, ctors) +def eval_ctors_wasm(js, wasm_file, num): + ctors_start, ctors_end, all_ctors, ctors = find_ctors_data(js, num) + cmd = [os.path.join(binaryen_bin, 'wasm-ctor-eval'), wasm_file, '-o', wasm_file, '--ctors=' + ','.join(ctors)] + shared.logging.debug('wasm ctor cmd: ' + str(cmd)) + out, err = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + num_successful = err.count('success on') + shared.logging.debug(err) + if len(ctors) == len(all_ctors): + new_ctors = '' + else: + elements = [] + for ctor in all_ctors[num:]: + elements.append('{ func: function() { %s() } }' % ctor) + new_ctors = '__ATINIT__.push(' + ', '.join(elements) + ');' + js = js[:ctors_start] + new_ctors + js[ctors_end:] + return num_successful, js + # main if __name__ == '__main__': js = open(js_file).read() @@ -288,50 +312,63 @@ def read_and_delete(filename): num_ctors = ctors_text.count('function()') shared.logging.debug('ctor_evaller: %d ctors, from |%s|' % (num_ctors, ctors_text)) - if os.path.exists(mem_init_file): - mem_init = json.dumps(map(ord, open(mem_init_file, 'rb').read())) + if not wasm: + # js path + mem_init_file = binary_file + if os.path.exists(mem_init_file): + mem_init = json.dumps(map(ord, open(mem_init_file, 'rb').read())) + else: + mem_init = [] + + # find how many ctors we can remove, by bisection (if there are hundreds, running them sequentially is silly slow) + + shared.logging.debug('ctor_evaller: trying to eval %d global constructors' % num_ctors) + num_successful, new_js, new_mem_init, removed = eval_ctors_js(js, mem_init, num_ctors) + if num_successful == 0: + shared.logging.debug('ctor_evaller: not successful') + sys.exit(0) + + shared.logging.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) + if num_successful == num_ctors: + js = new_js + mem_init = new_mem_init + else: + shared.logging.debug('ctor_evaller: final execution') + check, js, mem_init, removed = eval_ctors_js(js, mem_init, num_successful) + assert check == num_successful + open(js_file, 'w').write(js) + open(mem_init_file, 'wb').write(mem_init) + + # Dead function elimination can help us + + shared.logging.debug('ctor_evaller: eliminate no longer needed functions after ctor elimination') + # find exports + asm = get_asm(open(js_file).read()) + exports_start = asm.find('return {') + exports_end = asm.find('};', exports_start) + exports_text = asm[asm.find('{', exports_start) + 1 : exports_end] + exports = map(lambda x: x.split(':')[1].strip(), exports_text.replace(' ', '').split(',')) + for r in removed: + assert r in exports, 'global ctors were exported' + exports = filter(lambda e: e not in removed, exports) + # fix up the exports + js = open(js_file).read() + absolute_exports_start = js.find(exports_text) + js = js[:absolute_exports_start] + ', '.join(map(lambda e: e + ': ' + e, exports)) + js[absolute_exports_start + len(exports_text):] + open(js_file, 'w').write(js) + # find unreachable methods and remove them + reachable = shared.Building.calculate_reachable_functions(js_file, exports, can_reach=False)['reachable'] + for r in removed: + assert r not in reachable, 'removed ctors must NOT be reachable' + shared.Building.js_optimizer(js_file, ['removeFuncs'], extra_info={ 'keep': reachable }, output_filename=js_file) else: - mem_init = [] - - # find how many ctors we can remove, by bisection (if there are hundreds, running them sequentially is silly slow) - - shared.logging.debug('ctor_evaller: trying to eval %d global constructors' % num_ctors) - num_successful, new_js, new_mem_init, removed = eval_ctors(js, mem_init, num_ctors) - if num_successful == 0: - shared.logging.debug('ctor_evaller: not successful') - sys.exit(0) - - shared.logging.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) - if num_successful == num_ctors: - js = new_js - mem_init = new_mem_init - else: - shared.logging.debug('ctor_evaller: final execution') - check, js, mem_init, removed = eval_ctors(js, mem_init, num_successful) - assert check == num_successful - open(js_file, 'w').write(js) - open(mem_init_file, 'wb').write(mem_init) - - # Dead function elimination can help us - - shared.logging.debug('ctor_evaller: eliminate no longer needed functions after ctor elimination') - # find exports - asm = get_asm(open(js_file).read()) - exports_start = asm.find('return {') - exports_end = asm.find('};', exports_start) - exports_text = asm[asm.find('{', exports_start) + 1 : exports_end] - exports = map(lambda x: x.split(':')[1].strip(), exports_text.replace(' ', '').split(',')) - for r in removed: - assert r in exports, 'global ctors were exported' - exports = filter(lambda e: e not in removed, exports) - # fix up the exports - js = open(js_file).read() - absolute_exports_start = js.find(exports_text) - js = js[:absolute_exports_start] + ', '.join(map(lambda e: e + ': ' + e, exports)) + js[absolute_exports_start + len(exports_text):] - open(js_file, 'w').write(js) - # find unreachable methods and remove them - reachable = shared.Building.calculate_reachable_functions(js_file, exports, can_reach=False)['reachable'] - for r in removed: - assert r not in reachable, 'removed ctors must NOT be reachable' - shared.Building.js_optimizer(js_file, ['removeFuncs'], extra_info={ 'keep': reachable }, output_filename=js_file) + # wasm path + wasm_file = binary_file + shared.logging.debug('ctor_evaller (wasm): trying to eval %d global constructors' % num_ctors) + num_successful, new_js = eval_ctors_wasm(js, wasm_file, num_ctors) + if num_successful == 0: + shared.logging.debug('ctor_evaller: not successful') + sys.exit(0) + shared.logging.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) + open(js_file, 'w').write(new_js) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d4bb540183f84..adf048e8033a1 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -2606,6 +2606,10 @@ function registerizeHarder(ast) { }); if (abort) return; + // Do not process the dceable helper function for wasm, which declares + // types, we need to alive for asm2wasm + if (fun[1] == '__emscripten_dceable_type_decls') return; + var asmData = normalizeAsm(fun); var localVars = asmData.vars; @@ -4843,6 +4847,11 @@ function aggressiveVariableEliminationInternal(func, asmData) { } values[name] = node; } + // 'def' is non-null only if the variable was explicitly re-assigned after its definition. + // If it wasn't, the initial value should be used, which is supposed to always be zero. + else if (name in asmData.vars) { + values[name] = makeAsmCoercedZero(asmData.vars[name]) + } return node; } @@ -6649,7 +6658,9 @@ function emterpretify(ast) { if (type === ASM_INT) { opcode = 'ADD'; tryNumSymmetrical(); - } else if (type === ASM_DOUBLE) opcode = 'ADDD'; + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'ADDD'; + } break; } } @@ -6657,14 +6668,18 @@ function emterpretify(ast) { if (type === ASM_INT) { opcode = 'SUB'; tryNumAsymmetrical(); - } else if (type === ASM_DOUBLE) opcode = 'SUBD'; + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'SUBD'; + } break; } case '*': { if (type === ASM_INT) { opcode = 'MUL'; tryNumSymmetrical(); - } else if (type === ASM_DOUBLE) opcode = 'MULD'; + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'MULD'; + } break; } case '/': { @@ -6673,8 +6688,9 @@ function emterpretify(ast) { if (sign === ASM_SIGNED) opcode = 'SDIV'; else opcode = 'UDIV'; tryNumAsymmetrical(sign === ASM_UNSIGNED); + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'DIVD'; } - else if (type === ASM_DOUBLE) opcode = 'DIVD'; break; } case '%': { @@ -6683,8 +6699,9 @@ function emterpretify(ast) { if (sign === ASM_SIGNED) opcode = 'SMOD'; else opcode = 'UMOD'; tryNumAsymmetrical(sign === ASM_UNSIGNED); + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'MODD'; } - else if (type === ASM_DOUBLE) opcode = 'MODD'; break; } case '<': { @@ -6693,8 +6710,9 @@ function emterpretify(ast) { if (sign === ASM_SIGNED) opcode = 'SLT'; else opcode = 'ULT'; tryNumAsymmetrical(sign === ASM_UNSIGNED); + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'LTD'; } - else if (type === ASM_DOUBLE) opcode = 'LTD'; break; } case '<=': { @@ -6703,17 +6721,18 @@ function emterpretify(ast) { if (sign === ASM_SIGNED) opcode = 'SLE'; else opcode = 'ULE'; tryNumAsymmetrical(sign === ASM_UNSIGNED); + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'LED'; } - else if (type === ASM_DOUBLE) opcode = 'LED'; break; } case '>': { - assert(type === ASM_DOUBLE); + assert(type === ASM_DOUBLE || type === ASM_FLOAT); opcode = 'GTD'; break; } case '>=': { - assert(type === ASM_DOUBLE); + assert(type === ASM_DOUBLE || type === ASM_FLOAT); opcode = 'GED'; break; } @@ -6721,14 +6740,18 @@ function emterpretify(ast) { if (type === ASM_INT) { opcode = 'EQ'; tryNumSymmetrical(); - } else if (type === ASM_DOUBLE) opcode = 'EQD'; + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'EQD'; + } break; } case '!=': { if (type === ASM_INT) { opcode = 'NE'; tryNumSymmetrical(); - } else if (type === ASM_DOUBLE) opcode = 'NED'; + } else if (type === ASM_DOUBLE || type === ASM_FLOAT) { + opcode = 'NED'; + } break; } case '&': opcode = 'AND'; tryNumSymmetrical(); break; @@ -6739,7 +6762,7 @@ function emterpretify(ast) { case '>>>': opcode = 'LSHR'; tryNumAsymmetrical(true); break; default: throw 'bad ' + node[1]; } - if (!opcode) assert(0, JSON.stringify([node, type, sign])); + assert(opcode, 'failed to find the proper opcode in makeBinary: ' + JSON.stringify([node, type, sign])); var x, y, z; var usingNumValue = numValue !== null && ((!numValueUnsigned && ((numValue << 24 >> 24) === numValue)) || ( numValueUnsigned && ((numValue & 255) === numValue))); diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index b6260af20eb9e..80e4bfabd91c2 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -631,6 +631,9 @@ StringSet BREAK_CAPTURERS("do while for switch"), CONTINUE_CAPTURERS("do while for"), FUNCTIONS_THAT_ALWAYS_THROW("abort ___resumeException ___cxa_throw ___cxa_rethrow"); +IString DCEABLE_TYPE_DECLS("__emscripten_dceable_type_decls"); + + bool isFunctionTable(const char *name) { static const char *functionTable = "FUNCTION_TABLE"; static unsigned size = strlen(functionTable); @@ -2415,6 +2418,10 @@ void registerizeHarder(Ref ast) { }); if (abort) return; + // Do not process the dceable helper function for wasm, which declares + // types, we need to alive for asm2wasm + if (fun[1] == DCEABLE_TYPE_DECLS) return; + AsmData asmData(fun); #ifdef PROFILING diff --git a/tools/ports/binaryen.py b/tools/ports/binaryen.py index d50875ddc7cdc..8c5bab5d7ba6a 100644 --- a/tools/ports/binaryen.py +++ b/tools/ports/binaryen.py @@ -1,6 +1,6 @@ import os, shutil, logging -TAG = 'version_32' +TAG = 'version_33' def needed(settings, shared, ports): if not settings.BINARYEN: return False diff --git a/tools/ports/cocos2d.py b/tools/ports/cocos2d.py index a3354d1213809..9564b246b1894 100644 --- a/tools/ports/cocos2d.py +++ b/tools/ports/cocos2d.py @@ -3,18 +3,17 @@ import logging import re -TAG = 'version_3_1e' +TAG = 'version_3_2' def get(ports, settings, shared): if settings.USE_COCOS2D == 3: ports.fetch_project( - 'Cocos2d', 'https://github.com/emscripten-ports/Cocos2d/archive/version_3_1e.zip', 'Cocos2d-' + TAG) + 'Cocos2d', 'https://github.com/emscripten-ports/Cocos2d/archive/' + TAG + '.zip', 'Cocos2d-' + TAG) def create(): logging.info('building port: Cocos2d v3') logging.warn('Cocos2d: library is experimental, do not expect that it will work out of the box') - logging.warn('Cocos2d: please use STB_IMAGE=1 or --use-preload-plugin to work with images') cocos2d_build = os.path.join(ports.get_dir(), 'Cocos2d') cocos2d_root = os.path.join(cocos2d_build, 'Cocos2d-' + TAG) @@ -89,48 +88,62 @@ def show(): def make_source_list(cocos2d_root, cocos2dx_root): - makefile = os.path.join(cocos2dx_root, 'proj.emscripten', 'Makefile') sources = [] - with open(makefile) as infile: - add_next = False - for line in infile: - if line.startswith('SOURCES'): - sources.append( - re.search('=\s*(.*?)(\s*\\\\$|\s*$)', line, re.IGNORECASE).group(1)) - add_next = line.endswith('\\\n') - continue - if add_next: - sources.append( - re.search('\s*(.*?)(\s*\\\\$|\s*$)', line, re.IGNORECASE).group(1)) - add_next = line.endswith('\\\n') - + + def add_makefile(makefile): + with open(makefile) as infile: + add_next = False + for line in infile: + if line.startswith('SOURCES'): + file = re.search('=\s*(.*?)(\s*\\\\$|\s*$)', line, re.IGNORECASE).group(1) + absfile = os.path.abspath(os.path.join(os.path.dirname(makefile), file)) + sources.append(absfile) + add_next = line.endswith('\\\n') + continue + if add_next: + file = re.search('\s*(.*?)(\s*\\\\$|\s*$)', line, re.IGNORECASE).group(1) + absfile = os.path.abspath(os.path.join(os.path.dirname(makefile), file)) + sources.append(absfile) + add_next = line.endswith('\\\n') + + # core + add_makefile(os.path.join(cocos2dx_root, 'proj.emscripten', 'Makefile')) + # extensions + add_makefile(os.path.join(cocos2d_root, 'extensions', 'proj.emscripten', 'Makefile')) + # external + add_makefile(os.path.join(cocos2d_root, 'external', 'Box2D', 'proj.emscripten', 'Makefile')) + add_makefile(os.path.join(cocos2d_root, 'external', 'chipmunk', 'proj.emscripten', 'Makefile')) + add_makefile(os.path.join(cocos2dx_root, 'platform', 'third_party', 'Makefile')) + # misc sources.append(os.path.join(cocos2d_root, 'CocosDenshion', 'emscripten', 'SimpleAudioEngine.cpp')) + sources.append(os.path.join(cocos2dx_root, 'CCDeprecated.cpp')) # subset of cocos2d v2 return sources def make_includes(cocos2d_root, cocos2dx_root): return [os.path.join(cocos2d_root, 'CocosDenshion', 'include'), os.path.join(cocos2d_root, 'extensions'), - os.path.join(cocos2d_root, 'extensions'), + os.path.join(cocos2d_root, 'extensions', 'AssetsManager'), + os.path.join(cocos2d_root, 'extensions', 'CCArmature'), os.path.join(cocos2d_root, 'extensions', 'CCBReader'), - os.path.join(cocos2d_root, 'extensions', - 'GUI', 'CCControlExtension'), + os.path.join(cocos2d_root, 'extensions', 'GUI', 'CCControlExtension'), + os.path.join(cocos2d_root, 'extensions', 'GUI', 'CCEditBox'), + os.path.join(cocos2d_root, 'extensions', 'GUI', 'CCScrollView'), os.path.join(cocos2d_root, 'extensions', 'network'), os.path.join(cocos2d_root, 'extensions', 'Components'), - os.path.join(cocos2d_root, 'external', - 'chipmunk', 'include', 'chipmunk'), + os.path.join(cocos2d_root, 'extensions', 'LocalStorage'), + os.path.join(cocos2d_root, 'extensions', 'physics_nodes'), + os.path.join(cocos2d_root, 'extensions', 'spine'), + os.path.join(cocos2d_root, 'external'), + os.path.join(cocos2d_root, 'external', 'chipmunk', 'include', 'chipmunk'), cocos2dx_root, os.path.join(cocos2dx_root, 'cocoa'), os.path.join(cocos2dx_root, 'include'), os.path.join(cocos2dx_root, 'kazmath', 'include'), os.path.join(cocos2dx_root, 'platform'), os.path.join(cocos2dx_root, 'platform', 'emscripten'), - os.path.join(cocos2dx_root, 'platform', - 'third_party', 'linux', 'libfreetype2'), - os.path.join(cocos2dx_root, 'platform', - 'third_party', 'common', 'etc'), - os.path.join(cocos2dx_root, 'platform', 'third_party', - 'emscripten', 'libtiff', 'include'), - os.path.join(cocos2dx_root, 'platform', - 'third_party', 'emscripten', 'libjpeg'), + os.path.join(cocos2dx_root, 'platform', 'third_party', 'linux', 'libfreetype2'), + os.path.join(cocos2dx_root, 'platform', 'third_party', 'common', 'etc'), + os.path.join(cocos2dx_root, 'platform', 'third_party', 'emscripten', 'libtiff', 'include'), + os.path.join(cocos2dx_root, 'platform', 'third_party', 'emscripten', 'libjpeg'), os.path.join(cocos2dx_root, 'platform', 'third_party', 'emscripten', 'libwebp')] diff --git a/tools/shared.py b/tools/shared.py index d5a48c1907de1..e56cb31897a04 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1186,7 +1186,7 @@ def apply_opt_level(self, opt_level, shrink_level=0, noisy=False): self.attrs['ASSERTIONS'] = 0 self.attrs['DISABLE_EXCEPTION_CATCHING'] = 1 self.attrs['ALIASING_FUNCTION_POINTERS'] = 1 - if shrink_level >= 2: + if shrink_level >= 2 and not self.attrs['BINARYEN']: self.attrs['EVAL_CTORS'] = 1 def __getattr__(self, attr): @@ -1981,7 +1981,7 @@ def is_wasm_only(): # fastcomp can emit wasm-only code. # also disable this mode if it depends on special optimizations that are not yet # compatible with it. - return 'asmjs' not in Settings.BINARYEN_METHOD and 'interpret-asm2wasm' not in Settings.BINARYEN_METHOD and not Settings.RUNNING_JS_OPTS and not Settings.EMULATED_FUNCTION_POINTERS and not Settings.EMULATE_FUNCTION_POINTER_CASTS + return ('asmjs' not in Settings.BINARYEN_METHOD and 'interpret-asm2wasm' not in Settings.BINARYEN_METHOD and not Settings.RUNNING_JS_OPTS and not Settings.EMULATED_FUNCTION_POINTERS and not Settings.EMULATE_FUNCTION_POINTER_CASTS) or not Settings.LEGALIZE_JS_FFI @staticmethod def get_safe_internalize(): @@ -2056,9 +2056,10 @@ def js_optimizer_no_asmjs(filename, passes): subprocess.check_call(NODE_JS + [js_optimizer.JS_OPTIMIZER, filename] + passes, stdout=open(next, 'w')) return next + # evals ctors. if binaryen_bin is provided, it is the dir of the binaryen tool for this, and we are in wasm mode @staticmethod - def eval_ctors(js_file, mem_init_file): - subprocess.check_call([PYTHON, path_from_root('tools', 'ctor_evaller.py'), js_file, mem_init_file, str(Settings.TOTAL_MEMORY), str(Settings.TOTAL_STACK), str(Settings.GLOBAL_BASE)]) + def eval_ctors(js_file, binary_file, binaryen_bin=''): + subprocess.check_call([PYTHON, path_from_root('tools', 'ctor_evaller.py'), js_file, binary_file, str(Settings.TOTAL_MEMORY), str(Settings.TOTAL_STACK), str(Settings.GLOBAL_BASE), binaryen_bin]) @staticmethod def eliminate_duplicate_funcs(filename):