diff options
-rwxr-xr-x | emcc | 39 | ||||
-rw-r--r-- | src/settings.js | 7 | ||||
-rw-r--r-- | tests/codemods.cpp | 21 | ||||
-rw-r--r-- | tests/test_browser.py | 33 |
4 files changed, 91 insertions, 9 deletions
@@ -1962,8 +1962,9 @@ try: # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing js_optimizer_queue = [] js_optimizer_extra_info = {} + js_optimizer_queue_history = [] def flush_js_optimizer_queue(): - global final, js_optimizer_queue, js_optimizer_extra_info + global final, js_optimizer_queue, js_optimizer_extra_info, js_optimizer_queue_history if len(js_optimizer_extra_info) == 0: js_optimizer_extra_info = None if len(js_optimizer_queue) > 0 and not(not shared.Settings.ASM_JS and len(js_optimizer_queue) == 1 and js_optimizer_queue[0] == 'last'): @@ -1990,6 +1991,7 @@ try: final = shared.Building.js_optimizer(final, passes, jcache, debug_level >= 4, js_optimizer_extra_info) js_transform_tempfiles.append(final) save_intermediate(name) + js_optimizer_queue_history += js_optimizer_queue js_optimizer_queue = [] js_optimizer_extra_info = {} @@ -2079,16 +2081,38 @@ try: if debug_level >= 4: generate_source_map(target) shutil.move(final, js_target) - if 1: + need_mods = shared.Settings.PRECISE_F32 == 2 + if not need_mods: # Non-modifiable code, just load the code directly script_tag = '''<script async type="text/javascript" src="%s"></script>''' % base_js_target else: # Potentially-modifiable code, load as text, modify, then execute. This lets you # patch the code on the client machine right before it is executed, perhaps based - # on information about the client. For example, - # code = code.replace(/Math_fround\(/g, '('); - # could be used to remove Math.fround optimization (in an unminified build), if - # the client does not support Math.fround natively. + # on information about the client. + mods = [] + if shared.Settings.PRECISE_F32 == 2: + if 'minifyNames' not in js_optimizer_queue_history: + # simple dumb replace + mods.append('''if (!Math.fround) { console.log('optimizing out Math.fround calls'); code = code.replace(/Math_fround\(/g, '(').replace("'use asm'", "'almost asm'") }''') + else: + # minified, not quite so simple - TODO + mods.append(''' +if (!Math.fround) try { + console.log('optimizing out Math.fround calls'); + var m = /var ([^=]+)=global\.Math\.fround;/.exec(code); + var minified = m[1]; + if (!minified) throw 'fail'; + var startAsm = code.indexOf('// EMSCRIPTEN_START_FUNCS'); + var endAsm = code.indexOf('// EMSCRIPTEN_END_FUNCS'); + var asm = code.substring(startAsm, endAsm); + do { + var moar = false; // we need to re-do, as x(x( will not be fixed + asm = asm.replace(new RegExp('[^a-zA-Z0-9\\\\$\\\\_]' + minified + '\\\\(', 'g'), function(s) { moar = true; return s[0] + '(' }); + } while (moar); + code = code.substring(0, startAsm) + asm + code.substring(endAsm); + code = code.replace("'use asm'", "'almost asm'"); +} catch(e) { console.log('failed to optimize out Math.fround calls ' + e) } +''') code = ''' // Load the code as text, and execute it asynchronously. This keeps everything // async and responsive, while also making it possible to modify the code on @@ -2097,6 +2121,7 @@ var codeXHR = new XMLHttpRequest(); codeXHR.open('GET', '%s', true); codeXHR.onload = function() { var code = codeXHR.responseText; + %s var blob = new Blob([code], { type: 'text/javascript' }); codeXHR = null; var src = URL.createObjectURL(blob); @@ -2108,7 +2133,7 @@ codeXHR.onload = function() { document.body.appendChild(script); }; codeXHR.send(null); -''' % base_js_target +''' % (base_js_target, '\n '.join(mods)) script_tag = '''<script>%s</script>''' % code html.write(shell.replace('{{{ SCRIPT }}}', script_tag)) else: diff --git a/src/settings.js b/src/settings.js index fc54dc5a..79e949fa 100644 --- a/src/settings.js +++ b/src/settings.js @@ -124,8 +124,11 @@ var PRECISE_F32 = 0; // 0: Use JS numbers for floating-point values. These are 6 // 1: Model C++ floats precisely, using Math.fround, polyfilling when necessary. This // can be slow if the polyfill is used on heavy float32 computation. // 2: Model C++ floats precisely using Math.fround if available in the JS engine, otherwise - // use an empty polyfill. This will have less of a speed penalty than using the full - // polyfill in cases where engine support is not present. + // use an empty polyfill. This will have much less of a speed penalty than using the full + // polyfill in cases where engine support is not present. In addition, we can + // remove the empty polyfill calls themselves on the client when generating html, + // which should mean that this gives you the best of both worlds of 0 and 1, and is + // therefore recommended. var SIMD = 0; // Whether to emit SIMD code ( https://github.com/johnmccutchan/ecmascript_simd ) var CLOSURE_ANNOTATIONS = 0; // If set, the generated code will be annotated for the closure diff --git a/tests/codemods.cpp b/tests/codemods.cpp new file mode 100644 index 00000000..26712339 --- /dev/null +++ b/tests/codemods.cpp @@ -0,0 +1,21 @@ +#include <stdio.h> +#include <math.h> +#include <emscripten.h> + +int main() { + volatile int x = 10; + float y = 123456789.123456789; + while (x-- > 0) { + y = (sqrtf(y) + y)/2; + } + double d = y; + double diff = fabs(d - 121376.4609375000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000); + int ok = fabs(diff) < 0.000001; + printf("%.20f : %d\n", diff, ok); + + int result; + if (ok) result = 1; + else result = diff+2; // add two to this >= number to avoid conflicts with 1 + REPORT_RESULT(); +} + diff --git a/tests/test_browser.py b/tests/test_browser.py index f185c211..ef67aa20 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1815,3 +1815,36 @@ Module["preRun"].push(function () { def test_html5(self): self.btest(path_from_root('tests', 'test_html5.c'), expected='0') + + def test_codemods(self): + for opt_level in [0, 2]: + print 'opt level', opt_level + opts = '-O' + str(opt_level) + # sanity checks, building with and without precise float semantics generates different results + self.btest(path_from_root('tests', 'codemods.cpp'), expected='2', args=[opts]) + self.btest(path_from_root('tests', 'codemods.cpp'), expected='1', args=[opts, '-s', 'PRECISE_F32=1']) + self.btest(path_from_root('tests', 'codemods.cpp'), expected='1', args=[opts, '-s', 'PRECISE_F32=2']) # empty polyfill, but browser has support, so semantics are like float + + # now use a shell to remove the browser's fround support + open(self.in_dir('shell.html'), 'w').write(open(path_from_root('src', 'shell.html')).read().replace('var Module = {', ''' + Math.fround = null; + var Module = { + ''')) + self.btest(path_from_root('tests', 'codemods.cpp'), expected='2', args=[opts, '--shell-file', 'shell.html']) + self.btest(path_from_root('tests', 'codemods.cpp'), expected='1', args=[opts, '--shell-file', 'shell.html', '-s', 'PRECISE_F32=1']) + self.btest(path_from_root('tests', 'codemods.cpp'), expected='2', args=[opts, '--shell-file', 'shell.html', '-s', 'PRECISE_F32=2']) # empty polyfill, no browser support, so semantics are like double + + # finally, remove fround, patch up fround as the code executes (after polyfilling etc.), to verify that we got rid of it entirely on the client side + fixer = 'python fix.py' + open('fix.py', 'w').write(r''' +import sys +filename = sys.argv[1] +js = open(filename).read() +replaced = js.replace("var Math_fround = Math.fround;", "var Math_fround = Math.fround = function(x) { return 0; }") +assert js != replaced +open(filename, 'w').write(replaced) + ''') + self.btest(path_from_root('tests', 'codemods.cpp'), expected='2', args=[opts, '--shell-file', 'shell.html', '--js-transform', fixer]) # no fround anyhow + self.btest(path_from_root('tests', 'codemods.cpp'), expected='121378', args=[opts, '--shell-file', 'shell.html', '--js-transform', fixer, '-s', 'PRECISE_F32=1']) # proper polyfill was enstated, then it was replaced by the fix so 0 is returned all the time, hence a different result here + self.btest(path_from_root('tests', 'codemods.cpp'), expected='2', args=[opts, '--shell-file', 'shell.html', '--js-transform', fixer, '-s', 'PRECISE_F32=2']) # we should remove the calls to the polyfill ENTIRELY here, on the clientside, so we should NOT see any calls to fround here, and result should be like double + |