From 719870b469b2aa12cca7073b8404d4d8337a21d5 Mon Sep 17 00:00:00 2001 From: Fumiya Chiba Date: Sun, 30 Apr 2017 23:33:15 -0700 Subject: [PATCH 01/31] Fix emrun android chrome (#5180) * Fix chrome activity name * Comform to latest release channels --- emrun | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/emrun b/emrun index c697d64177e9b..340526b1935b2 100755 --- a/emrun +++ b/emrun @@ -1059,6 +1059,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'): @@ -1339,9 +1343,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) From 213e97e088cc560db7b0bc7efa81bac5cc8c7d66 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Tue, 2 May 2017 10:12:20 -0700 Subject: [PATCH 02/31] fix debug mode copy of asm.js temp file in test runner (with both EM_SAVE_DIR and EMCC_DEBUG), we may copy the file to its current location, which python throws on normally, so do that safely --- emcc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emcc.py b/emcc.py index eb6d7198f07f6..09993e12dcfac 100755 --- a/emcc.py +++ b/emcc.py @@ -2114,7 +2114,7 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati 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))) + 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'] From 13d36603f05d8fa5706ea894c135cf1ad8ecabea Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Tue, 2 May 2017 15:02:38 -0700 Subject: [PATCH 03/31] make sure to run other.test_embind in node.js, because that's what it's test code assumes, and it's bad to assume node.js is the default js engine in the test runner --- tests/test_other.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_other.py b/tests/test_other.py index 8bbe47da46bc6..78501349ca976 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -2293,7 +2293,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): From edf3e3c9d2c159e0d9bef383c9cafc56b3389b69 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 2 May 2017 16:08:05 -0700 Subject: [PATCH 04/31] Duplicate function elimination fixes (#5186) * ELIMINATE_DUPLICATE_FUNCTIONS is not needed in wasm, as we have the binaryen optimizer do that for us anyhow when optimizing. remove unneeded testing of that option for wasm in test_poppler. also fix testing of that option for asm.js: we were misidentifying ourselves as wasm due to disabling binaryen async compilation (the options string contains BINARYEN), so just add that when in wasm mode --- emcc.py | 3 +++ tests/test_core.py | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/emcc.py b/emcc.py index 09993e12dcfac..6b6ff808ceb33 100755 --- a/emcc.py +++ b/emcc.py @@ -1284,6 +1284,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: diff --git a/tests/test_core.py b/tests/test_core.py index ee6b7068945a3..28e1a5db270c1 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 @@ -5397,21 +5398,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): From 1ab6375b740e7ee2571978ec1a8c36b1ba466f2c Mon Sep 17 00:00:00 2001 From: Jukka Jylanki Date: Wed, 3 May 2017 16:23:07 +0300 Subject: [PATCH 05/31] Update emrun.py to latest version. --- emrun | 158 ++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 60 deletions(-) diff --git a/emrun b/emrun index 340526b1935b2..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: @@ -1097,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: @@ -1113,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. @@ -1330,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 @@ -1366,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: @@ -1376,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: @@ -1426,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) @@ -1453,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()) From 8654549be4f7d8ea8fe8970229bf8767b4a5d154 Mon Sep 17 00:00:00 2001 From: jgravelle-google Date: Wed, 3 May 2017 11:31:59 -0700 Subject: [PATCH 06/31] Fix embinding derived classes (#5193) Embinding derived classes uses template-generated upcast/downcast functions for construction internally. Always converting them to void-returning function pointers causes runtime pointer aliasing errors. The type here should only be void-returning in the case of the NoBaseClass internal struct. --- system/include/emscripten/bind.h | 10 +++--- tests/core/test_embind_5.cpp | 59 ++++++++++++++++++++++---------- tests/core/test_embind_5.out | 5 ++- 3 files changed, 49 insertions(+), 25 deletions(-) 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/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 From 5c0dba9eb836ca9fbba7e341e2714e16f7dbfd1b Mon Sep 17 00:00:00 2001 From: Aleksander Guryanov Date: Thu, 4 May 2017 01:38:37 +0700 Subject: [PATCH 07/31] cocos2d add subset of v2 + extensions (#5188) --- tools/ports/cocos2d.py | 73 +++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 30 deletions(-) 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')] From af4a3b28066d9ad803fd46d80a1364aab948c755 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 3 May 2017 12:46:16 -0700 Subject: [PATCH 08/31] =?UTF-8?q?Emterpreter:=20support=20float=20versions?= =?UTF-8?q?=20of=20binary=20operations,=20which=20is=20necessary=20as=20no?= =?UTF-8?q?=E2=80=A6=20(#5129)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * support float versions of binary operations, which is necessary as now we detect f0 properly (since #5046), so we do notice float operations in more cases, which is why this went unnoticed before. we do all emterpreter math in doubles anyhow, so this just requires us to accept float ops and emit the same double ops * improve emterpreter makeBinary assertion message --- tools/js-optimizer.js | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d4bb540183f84..d4bf0e141973b 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -6649,7 +6649,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 +6659,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 +6679,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 +6690,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 +6701,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 +6712,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 +6731,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 +6753,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))); From 2ee929dc2e480175262ab4a5050d1a7c6215f87c Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Thu, 4 May 2017 09:30:44 -0700 Subject: [PATCH 09/31] don't leak temp files in other.test_emcc_c_multi --- tests/test_other.py | 73 +++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/tests/test_other.py b/tests/test_other.py index 78501349ca976..f0ad1bdfd2862 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -5342,49 +5342,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''' From d1a1fc121b7d6aab9adcb0c418453d48be318754 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 4 May 2017 16:00:43 -0700 Subject: [PATCH 10/31] closure is now considered ok for wasm, we use it in ammo.js and box2d.js wasm versions, and it passes tests (#5197) --- emcc.py | 3 +-- tests/test_core.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/emcc.py b/emcc.py index 6b6ff808ceb33..0087823853545 100755 --- a/emcc.py +++ b/emcc.py @@ -1300,8 +1300,7 @@ def check(input_file): if js_opts and not force_js_opts and 'asmjs' not in shared.Settings.BINARYEN_METHOD: 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 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 diff --git a/tests/test_core.py b/tests/test_core.py index 28e1a5db270c1..653ff5ad3e2d9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -875,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 = ''' @@ -3989,7 +3989,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(): @@ -5284,7 +5284,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 @@ -6348,7 +6348,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 From 909e396f567cf52b556b8d03a4e6380f732aa1d3 Mon Sep 17 00:00:00 2001 From: Jukka Jylanki Date: Fri, 5 May 2017 14:51:47 +0300 Subject: [PATCH 11/31] Fix Emscripten compiler detection in CMake when EMCC_SKIP_SANITY_CHECK=1 is specified, and add a test. Fixes #5145. --- cmake/Modules/Platform/Emscripten.cmake | 2 +- tests/test_other.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmake/Modules/Platform/Emscripten.cmake b/cmake/Modules/Platform/Emscripten.cmake index b53cc713d70ce..c9981e8dda45a 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 "emscripten") 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/tests/test_other.py b/tests/test_other.py index f0ad1bdfd2862..bcba2d1b3e37f 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(): From 3e76dc58403166cb7a9bba897b99c2811594d3c7 Mon Sep 17 00:00:00 2001 From: Gregg Tavares Date: Fri, 5 May 2017 22:57:50 +0900 Subject: [PATCH 12/31] remove experimental-webgl2 There is no such thing as `experimental-webgl2`. Browsers no longer do this because apps get dependent on prefixes --- src/library_gl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_gl.js b/src/library_gl.js index ba74301a43b2e..aa8714979ff8e 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 + '!' } From ccaf4e74fa9abf51cff8d1d4823f0b4d84bf3eab Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 5 May 2017 14:59:49 -0500 Subject: [PATCH 13/31] JS_FFI option to disable JS FFI mangling. (#5168) * LEGALIZE_JS_FFI option to disable JS FFI mangling. For JS/Web platform, calls to JS imports are normally wrapped to convert i64 to i32 and f32 to f64. Likewise calls from JS into exports do the inverse wrapping. This change provides an option to disable that wrapping and use the original types for the call. This is useful for non-web embeddings. The following fastcomp options get enabled by this option: -emscripten-legalize-javascript-ffi=0 -emscripten-only-wasm The following binaryen options get enabled by this option: --no-legalize-javascript-ffi --wasm-only * Add test for LEGALIZE_JS_FFI. The test other.test_legalize_js_ffi compiles tests/core/ffi.c several different ways: LEGALIZE_JS_FFI={0,1}, SIDE_MODULE={0,1}, -O{2,0}. After each build the test scans the results a.out.wast file to make sure that type wrapping/conversions happen (LEGALIZE_JS_FFI=1) or don't happen (LEGALIZE_JS_FFI=0). --- emcc.py | 5 +++++ emscripten.py | 2 ++ src/settings.js | 7 +++++++ tests/other/ffi.c | 26 +++++++++++++++++++++++++ tests/test_other.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ tools/shared.py | 2 +- 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/other/ffi.c diff --git a/emcc.py b/emcc.py index 0087823853545..edd7985916067 100755 --- a/emcc.py +++ b/emcc.py @@ -1368,6 +1368,9 @@ def check(input_file): 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 @@ -2145,6 +2148,8 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati 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 debug_level >= 2 or profiling_funcs: diff --git a/emscripten.py b/emscripten.py index 6cc22c3a171d9..becfcc934f603 100755 --- a/emscripten.py +++ b/emscripten.py @@ -434,6 +434,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: 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/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/test_other.py b/tests/test_other.py index bcba2d1b3e37f..18bf7c256a1ba 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -7508,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/shared.py b/tools/shared.py index d5a48c1907de1..f66b9bd088c92 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -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(): From 89c4737ab72aea84263779819ae495d2d0ac38a4 Mon Sep 17 00:00:00 2001 From: Fumiya Chiba Date: Sun, 7 May 2017 22:24:34 -0700 Subject: [PATCH 14/31] Add test for postSets chunking cf. https://github.com/kripken/emscripten-fastcomp/issues/183 --- tests/test_core.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 653ff5ad3e2d9..fdfb137e9d5e3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3510,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 From 054fab3435de90eb01e0a3af33a4fc20afcb2fe7 Mon Sep 17 00:00:00 2001 From: Inseok Lee Date: Tue, 9 May 2017 03:17:00 +0900 Subject: [PATCH 15/31] Add missing SIMD closure externs (#5177) * Add missing SIMD closure externs * Test closure compiler with SIMD * Update AUTHORS --- AUTHORS | 2 +- src/closure-externs.js | 58 ++++++++++++++++++++++++++++++++++++++++++ tests/test_core.py | 9 +++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index f51f5aac9e54a..1732e2e50dcfa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -292,4 +292,4 @@ a license to everyone to use it as detailed in LICENSE.) * Ryan Speets * Fumiya Chiba * Ryan C. Gordon - +* Inseok Lee 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/tests/test_core.py b/tests/test_core.py index fdfb137e9d5e3..955e889b00bc8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4975,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, @@ -4995,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. @@ -5015,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. From 08ff3c3c1a367a136eddf7af20871f00742b079e Mon Sep 17 00:00:00 2001 From: Jukka Jylanki Date: Tue, 9 May 2017 13:20:40 +0300 Subject: [PATCH 16/31] Accept both 'Emscripten' and 'emscripten' when detecting emcc in CMake. #5203 --- cmake/Modules/Platform/Emscripten.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/Platform/Emscripten.cmake b/cmake/Modules/Platform/Emscripten.cmake index c9981e8dda45a..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}") From 68b42f58b1c37174ced17e0728ec3012553d084f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 9 May 2017 18:10:04 -0700 Subject: [PATCH 17/31] registerizeHarder should not dce out the contents of the dceable helper function for wasm, which declares types. without the types, asm2wasm will fail (#5195) --- .../test-js-optimizer-asm-regs-harder-output.js | 13 +++++++++++++ .../test-js-optimizer-asm-regs-harder-output2.js | 13 +++++++++++++ .../test-js-optimizer-asm-regs-harder-output3.js | 13 +++++++++++++ .../test-js-optimizer-asm-regs-harder.js | 16 +++++++++++++++- tools/js-optimizer.js | 4 ++++ tools/optimizer/optimizer.cpp | 7 +++++++ 6 files changed, 65 insertions(+), 1 deletion(-) 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/tools/js-optimizer.js b/tools/js-optimizer.js index d4bf0e141973b..7e9ea07a447b8 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; 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 From c2f4e65c07dc782bb36a5e963b24a13eaac93b43 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 9 May 2017 18:12:25 -0700 Subject: [PATCH 18/31] use the binaryen ctor-eval tool for wasm instead of the js-based one (#5194) * use the binaryen ctor-eval tool for wasm instead of the js-based ctor-evaller. the binaryen tool is much faster and more efficient (but does not special-case atexit, so it works best with NO_EXIT_RUNTIME; it suggests doing so if not already enabled) * note that when interpreting the wasm, we can't use the ctor evaller, the bundled interpreter confuses the tool * do not simplifyIfs when wasm, as it hits a bug in asm2wasm --- emcc.py | 31 ++++++--- tests/test_core.py | 9 ++- tools/ctor_evaller.py | 147 ++++++++++++++++++++++++++---------------- tools/shared.py | 7 +- 4 files changed, 125 insertions(+), 69 deletions(-) diff --git a/emcc.py b/emcc.py index edd7985916067..586f8b711c36b 100755 --- a/emcc.py +++ b/emcc.py @@ -1305,9 +1305,6 @@ def check(input_file): # * 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 # 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 @@ -1326,9 +1323,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 @@ -1935,7 +1941,9 @@ def get_eliminate(): if 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'] + # do not do this with binaryen, as commaifying confuses binaryen call type detection (FIXME, in theory, but unimportant) + if shared.Settings.SIMPLIFY_IFS and (debug_level == 0 or profiling) and shared.Settings.OUTLINING_LIMIT == 0 and not shared.Settings.BINARYEN: + JSOptimizer.queue += ['simplifyIfs'] if shared.Settings.PRECISE_F32: JSOptimizer.queue += ['optimizeFrounds'] @@ -1971,7 +1979,7 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati JSOptimizer.flush() shared.Building.eliminate_duplicate_funcs(final) - if shared.Settings.EVAL_CTORS and memory_init_file and debug_level < 4: + if shared.Settings.EVAL_CTORS and memory_init_file and debug_level < 4 and not shared.Settings.BINARYEN: JSOptimizer.flush() shared.Building.eval_ctors(final, memfile) if DEBUG: save_intermediate('eval-ctors', 'js') @@ -2177,7 +2185,10 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati 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) + if DEBUG: + safe_move(memfile, os.path.join(emscripten_temp_dir, os.path.basename(memfile))) + else: + os.unlink(memfile) memory_init_file = False log_time('asm2wasm') if shared.Settings.BINARYEN_PASSES: @@ -2200,6 +2211,8 @@ def do_minify(): # minifies the code. this is also when we do certain optimizati 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: diff --git a/tests/test_core.py b/tests/test_core.py index 955e889b00bc8..8bb094b9b2f2d 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6155,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' @@ -6185,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 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/shared.py b/tools/shared.py index f66b9bd088c92..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): @@ -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): From bcb05cb0d7a0b9363f9e0b867e93fdd8c2e52513 Mon Sep 17 00:00:00 2001 From: Fumiya Chiba Date: Wed, 10 May 2017 02:54:50 -0700 Subject: [PATCH 19/31] Add test for stb image decoding Test images are generated by these commands: convert -size 600x450 -depth 8 -background none -fill "rgba(200, 150, 100, 50)" label:bpp4 tests/sdl-stb-bpp4.png convert -size 600x450 -depth 8 -background white -fill "rgb(200, 150, 100)" label:bpp3 -alpha deactivate tests/sdl-stb-bpp3.png convert -size 600x450 -depth 8 -background none -fill "rgba(200, 200, 200, 50)" label:bpp2 tests/sdl-stb-bpp2.png convert -size 600x450 -depth 8 -background white -fill "rgb(200, 200, 200)" label:bpp1 -alpha deactivate tests/sdl-stb-bpp1.png pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB -ow tests/sdl-stb-bpp4.png pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB -ow tests/sdl-stb-bpp3.png pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB -ow tests/sdl-stb-bpp2.png pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB -ow tests/sdl-stb-bpp1.png --- tests/sdl-stb-bpp1.png | Bin 0 -> 4962 bytes tests/sdl-stb-bpp2.png | Bin 0 -> 8238 bytes tests/sdl-stb-bpp3.png | Bin 0 -> 7458 bytes tests/sdl-stb-bpp4.png | Bin 0 -> 9855 bytes tests/test_browser.py | 21 +++++++++++++++++++++ 5 files changed, 21 insertions(+) create mode 100644 tests/sdl-stb-bpp1.png create mode 100644 tests/sdl-stb-bpp2.png create mode 100644 tests/sdl-stb-bpp3.png create mode 100644 tests/sdl-stb-bpp4.png diff --git a/tests/sdl-stb-bpp1.png b/tests/sdl-stb-bpp1.png new file mode 100644 index 0000000000000000000000000000000000000000..21e8ee97624dfc2486e5eed888fd95d94dc0c5a3 GIT binary patch literal 4962 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 literal 0 HcmV?d00001 diff --git a/tests/sdl-stb-bpp2.png b/tests/sdl-stb-bpp2.png new file mode 100644 index 0000000000000000000000000000000000000000..14b194a0b356ff5461e943c2bd8a32db8ad7e563 GIT binary patch literal 8238 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 literal 0 HcmV?d00001 diff --git a/tests/sdl-stb-bpp3.png b/tests/sdl-stb-bpp3.png new file mode 100644 index 0000000000000000000000000000000000000000..e51a14f572a5d16c1cadfcb37028cf3ef133fdb8 GIT binary patch literal 7458 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 WWsartep6S_=sY#hC{lYC_P+piqpP9- literal 0 HcmV?d00001 diff --git a/tests/sdl-stb-bpp4.png b/tests/sdl-stb-bpp4.png new file mode 100644 index 0000000000000000000000000000000000000000..cd081ba25f3aa374ef095060786f244b5f7c8334 GIT binary patch literal 9855 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 Date: Wed, 10 May 2017 04:57:55 -0700 Subject: [PATCH 20/31] Expand image data with bpp = 2 on SDL image load --- src/library_sdl.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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; From 10151e7d8825856404ac6ae2d9d4e51e3b2bb97e Mon Sep 17 00:00:00 2001 From: CHIBA Fumiya Date: Wed, 17 May 2017 01:46:29 +0900 Subject: [PATCH 21/31] Set error flag if trying to pop empty matrix stack (#5218) * Set error flag if trying to pop empty matrix stack cf. https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glPushMatrix.xml > Initially, each of the stacks contains one matrix, an identity matrix. > It is an error to push a full matrix stack or to pop a matrix stack > that contains only a single matrix. In either case, the error flag > is set and no other change is made to GL state. In the current implementation, GLImmediate.matrixStack doesn't have initial identity matrix, so error should be returned when the stack is empty. --- src/library_gl.js | 4 ++++ tests/gl_error.c | 25 +++++++++++++++++++++++++ tests/test_browser.py | 3 +++ 3 files changed, 32 insertions(+) create mode 100644 tests/gl_error.c diff --git a/src/library_gl.js b/src/library_gl.js index aa8714979ff8e..e6b6ccebcdc44 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -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/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/test_browser.py b/tests/test_browser.py index ba4ec5420a840..11b3a2bfbfc91 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1904,6 +1904,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) From ccd7c89118326a241367b64312191e1ba6f914e5 Mon Sep 17 00:00:00 2001 From: ImplOfAnImpl Date: Wed, 17 May 2017 23:47:57 +0300 Subject: [PATCH 22/31] Fix for issue 5226 (#5227) Handle a variable that is never assigned to in aggressiveVariableElimination --- tests/optimizer/test-js-optimizer-shiftsAggressive-output.js | 2 ++ tests/optimizer/test-js-optimizer-shiftsAggressive.js | 3 ++- tools/js-optimizer.js | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) 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/tools/js-optimizer.js b/tools/js-optimizer.js index 7e9ea07a447b8..adf048e8033a1 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -4847,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; } From 6f3ba3917750ab322d9a970eb7a23c52435982f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 18 May 2017 16:20:06 +0300 Subject: [PATCH 23/31] Files system/lib/embind/bind.cpp and src/library_cyberdwarf.js had mismatching DOS \r\n line endings in repository, convert them to Unix \n line endings, which is the convention for this repository. --- src/library_cyberdwarf.js | 18 +-- system/lib/embind/bind.cpp | 298 ++++++++++++++++++------------------- 2 files changed, 158 insertions(+), 158 deletions(-) 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/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 +} From 4f42f6b4abcd9da07b316243434a6b3bd53f34d6 Mon Sep 17 00:00:00 2001 From: satoshinm Date: Thu, 18 May 2017 09:38:49 -0700 Subject: [PATCH 24/31] Implement glfw joystick API (#5175) * Implement glfw joystick API Joystick connected/disconnected callbacks are called in refreshJoysticks(). The gamepadconnected/disconnected events are only used as a hint to refresh the joysticks, whose getGamepads() state is compared to the previous state to determine the new state. --- src/library_glfw.js | 120 ++++++++++++++++++++++++++++++-- tests/glfw_joystick.c | 105 ++++++++++++++++++++++++++++ tests/test_browser.py | 47 +++++++++++++ tests/test_glfw_joystick.c | 138 +++++++++++++++++++++++++++++++++++++ tests/test_interactive.py | 3 + 5 files changed, 407 insertions(+), 6 deletions(-) create mode 100644 tests/glfw_joystick.c create mode 100644 tests/test_glfw_joystick.c 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/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/test_browser.py b/tests/test_browser.py index 11b3a2bfbfc91..2aa698ed2ff5d 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1097,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). 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']) From 70abec2572593ae78ebb622d0262fe31c416922a Mon Sep 17 00:00:00 2001 From: Tanner Rogalsky Date: Mon, 22 May 2017 12:49:44 -0400 Subject: [PATCH 25/31] Fix stride for GL state query functions (#5219) * fix stride for emscriptenWebGLGetUniform and emscriptenWebGLGetVertexAttrib * add a test for gl uniform array querying --- src/library_gl.js | 10 +++++----- tests/full_es2_sdlproc.c | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/library_gl.js b/src/library_gl.js index e6b6ccebcdc44..e1d893b8ad412 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -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; } } 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); From d51469a83719936577a1819bf6372a89a3d49ca4 Mon Sep 17 00:00:00 2001 From: jgravelle-google Date: Mon, 22 May 2017 15:50:22 -0700 Subject: [PATCH 26/31] Refactor emcc py (#5236) * Extract generate_html, JSOptimizer * Fixes for JSOptimizer hoist, hoist more things * Extract generate_worker_js * Dedent generate_html * Decompose long line of code in generate_worker_js * Remove unneeded comment * Extract modularize * Extract do_binaryen * Extract binaryen_method_sanity_check * Make JSOptimizer non-static * Build JSOptimizer object with arguments * Extract separate_asm_js * Extract emit_source_maps * Extract emterpretify * Add missing optimizer argument * Fixes for generate_html * Move lots of command-line options into EmccOptions class * Shorten long length lines * Add intermediate values and format string to statictop initialization * Fix validate_arg_level * Dedup normalizing UNIX line endings comment with function * Forward args from function_tables_and_exports in a tuple, to minimize boilerplate, duplication, line noise * Reuse writing module and post to outfile in wasm backend * Make parse_args-specific functions be declared at module scope * Move global script_src/_inline into generate_html * Encapsulate script_src and script_inline in ScriptSource class * Un-global target * Add line breaks to long args array * Extract system_js_libraries * Break long line with multiple file loads into multiple lines * Extract duplicated logic in proxy to worker js * Invert long if/else * Fix missing argument to system_js_libraries_setting_str * Use optimized js_transform_tempfiles when emitting sourcemaps * Add LEGALIZE_JS_FFI that was dropped via a merge conflict --- emcc.py | 1980 ++++++++++++++++++++++++++----------------------- emscripten.py | 91 ++- 2 files changed, 1119 insertions(+), 952 deletions(-) diff --git a/emcc.py b/emcc.py index 586f8b711c36b..7bba2dde2efe0 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 @@ -1297,14 +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') - assert not use_closure_compiler == 2, 'closure compiler mode 2 assumes the code is asm.js, so not meaningful for wasm' + 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 + 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 @@ -1342,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: @@ -1370,7 +1212,7 @@ 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') @@ -1378,8 +1220,8 @@ def check(input_file): 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 @@ -1411,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): @@ -1489,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): @@ -1497,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 @@ -1524,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: @@ -1603,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: @@ -1629,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'] @@ -1647,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") @@ -1661,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') @@ -1684,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 @@ -1710,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] @@ -1770,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) @@ -1781,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))) @@ -1792,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' @@ -1804,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) @@ -1815,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: @@ -1824,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: @@ -1920,89 +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) # do not do this with binaryen, as commaifying confuses binaryen call type detection (FIXME, in theory, but unimportant) - if shared.Settings.SIMPLIFY_IFS and (debug_level == 0 or profiling) and shared.Settings.OUTLINING_LIMIT == 0 and not shared.Settings.BINARYEN: - 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 + 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 and not shared.Settings.BINARYEN: - 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') @@ -2010,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() @@ -2070,189 +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 - 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 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.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 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. - if DEBUG: - safe_move(memfile, os.path.join(emscripten_temp_dir, os.path.basename(memfile))) - else: - 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) - 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 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) @@ -2261,29 +1804,562 @@ 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) + + 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' - asm_mods = [] + if DEBUG: logging.debug('total time: %.2f seconds', (time.time() - start_time)) - if proxy_to_worker: - child_js = shared.Settings.PROXY_TO_WORKER_FILENAME or target_basename - script_inline = ''' + 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') + options.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 + 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 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) + import_mem_init = 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 + if import_mem_init and ok_binaryen_method: + 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') + 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) + 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'); @@ -2291,18 +2367,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'; @@ -2311,12 +2389,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') { @@ -2329,15 +2407,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() { @@ -2346,10 +2426,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() { @@ -2369,14 +2449,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'; @@ -2385,40 +2463,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/emscripten.py b/emscripten.py index becfcc934f603..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" @@ -536,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') @@ -1720,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() @@ -2052,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 = {} From 3008c7094f6c5fa9caa77591bc972f7f6aa5a7df Mon Sep 17 00:00:00 2001 From: jgravelle-google Date: Tue, 23 May 2017 13:06:00 -0700 Subject: [PATCH 27/31] Fix emcc refactor-induced test regression, browser.test_binaryen_async (#5240) --- emcc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/emcc.py b/emcc.py index 7bba2dde2efe0..938a5873e4844 100755 --- a/emcc.py +++ b/emcc.py @@ -2224,9 +2224,10 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, 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) - import_mem_init = options.memory_init_file and os.path.exists(memfile) + 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 - if import_mem_init and ok_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)] From 0c40cee4676e1b459bfac6b82440d134b6c508ad Mon Sep 17 00:00:00 2001 From: Yair Levinson Date: Wed, 24 May 2017 21:11:22 +0300 Subject: [PATCH 28/31] [proxied-webgl] Remove "gl-prefetch" runDependency if browser does not support webGL (#5238) We still need to notify that prefetching is complete if WebGL is not present, so that app startup is not bocked --- AUTHORS | 1 + src/webGLClient.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1732e2e50dcfa..35e6c1c39a65e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -293,3 +293,4 @@ a license to everyone to use it as detailed in LICENSE.) * Fumiya Chiba * Ryan C. Gordon * Inseok Lee +* Yair Levinson (copyright owned by Autodesk, Inc.) \ No newline at end of file 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 = {}; From ea0754739a0f97d80d845b7a5efe99a9f30c0142 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 24 May 2017 13:22:12 -0700 Subject: [PATCH 29/31] support '-' prefixing of passes in BINARYEN_PASSES list (#5244) * support '-' prefixing of passes in BINARYEN_PASSES list, making it simple to add both --passname and -O2 etc. * also fix a bug in debug mode when in the test runner, we need to be careful about copying those debug files --- emcc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/emcc.py b/emcc.py index 938a5873e4844..78e0ddca223b9 100755 --- a/emcc.py +++ b/emcc.py @@ -2207,7 +2207,7 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, 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))) + 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'] @@ -2275,7 +2275,9 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, 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(',')) + # 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: From 55554cbe500f7b1310944e52cf6dea0716f3c9ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Drolc?= Date: Fri, 26 May 2017 19:33:55 +0200 Subject: [PATCH 30/31] Fixed emcc --clear-ports command (#5248) --- AUTHORS | 4 +++- emcc.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 35e6c1c39a65e..66458cb7e9941 100644 --- a/AUTHORS +++ b/AUTHORS @@ -293,4 +293,6 @@ a license to everyone to use it as detailed in LICENSE.) * Fumiya Chiba * Ryan C. Gordon * Inseok Lee -* Yair Levinson (copyright owned by Autodesk, Inc.) \ No newline at end of file +* Yair Levinson (copyright owned by Autodesk, Inc.) +* Matjaž Drolc + diff --git a/emcc.py b/emcc.py index 78e0ddca223b9..af468e87fc451 100755 --- a/emcc.py +++ b/emcc.py @@ -1988,7 +1988,7 @@ def parse_args(newargs): should_exit = True elif newargs[i] == '--clear-ports': logging.info('clearing ports and cache as requested by --clear-ports') - options.system_libs.Ports.erase() + system_libs.Ports.erase() shared.Cache.erase() shared.check_sanity(force=True) # this is a good time for a sanity check should_exit = True From 7c06dd52cb130928f6002bb03dbeddbbcead38e4 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Fri, 26 May 2017 10:26:37 -0700 Subject: [PATCH 31/31] 1.37.13 ; binaryen version 33 --- emscripten-version.txt | 2 +- tools/ports/binaryen.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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