From d76259d08d6e93bed247c2158fcb3740ed4c2dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 12 May 2014 15:24:48 +0300 Subject: [PATCH 01/19] Remove the hardcoded alert message on WebGL context loss event in library_browser.js, since it prevents applications from benignly handling WebGL context loss events by reloading GL assets from disk. --- src/library_browser.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/library_browser.js b/src/library_browser.js index 44e8c47331c94..9c3d54bb6786c 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -322,11 +322,6 @@ mergeInto(LibraryManager.library, { #endif // Set the background of the WebGL canvas to black canvas.style.backgroundColor = "black"; - - // Warn on context loss - canvas.addEventListener('webglcontextlost', function(event) { - alert('WebGL context lost. You will need to reload the page.'); - }, false); } if (setInModule) { GLctx = Module.ctx = ctx; From 7fa0bdd051b174ed2487181812c5fc34a001d7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 12 May 2014 15:26:34 +0300 Subject: [PATCH 02/19] Add new function emscripten_is_webgl_context_lost() that allows code to directly query to confirm the WebGL context loss status. --- src/library_html5.js | 6 ++++++ system/include/emscripten/html5.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/src/library_html5.js b/src/library_html5.js index d9376c2ad5a13..d5d0cd6637a9c 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -1307,6 +1307,12 @@ var LibraryJSEvents = { JSEvents.registerWebGlEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WEBGLCONTEXTRESTORED') }}}, "webglcontextrestored"); return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; }, + + emscripten_is_webgl_context_lost: function(target) { + // TODO: In the future if multiple GL contexts are supported, use the 'target' parameter to find the canvas to query. + if (!Module['ctx']) return true; // No context ~> lost context. + return Module['ctx'].isContextLost(); + } }; autoAddDeps(LibraryJSEvents, '$JSEvents'); diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index db81725a35cb3..74c32a99bd44f 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -636,6 +636,12 @@ extern EMSCRIPTEN_RESULT emscripten_set_beforeunload_callback(void *userData, co extern EMSCRIPTEN_RESULT emscripten_set_webglcontextlost_callback(const char *target, void *userData, int useCapture, EM_BOOL (*func)(int eventType, const void *reserved, void *userData)); extern EMSCRIPTEN_RESULT emscripten_set_webglcontextrestored_callback(const char *target, void *userData, int useCapture, EM_BOOL (*func)(int eventType, const void *reserved, void *userData)); +/* + * Queries the given canvas element for whether its WebGL context is in a lost state. + * target: Reserved for future use, pass in 0. + */ +extern EM_BOOL emscripten_is_webgl_context_lost(const char *target); + #ifdef __cplusplus } // ~extern "C" #endif From 9a515a27c158b487839beea9c5d79c0f7957e83a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 12 May 2014 14:46:48 -0700 Subject: [PATCH 03/19] commented closure in benchmarks --- tests/test_benchmark.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 735f0feb06538..821caa6b1e08d 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -112,6 +112,7 @@ def process(filename): '--memory-init-file', '0', '--js-transform', 'python hardcode.py', '-s', 'TOTAL_MEMORY=128*1024*1024', #'-profiling', + #'--closure', '1', '-o', final] + shared_args + emcc_args + self.extra_args, stdout=PIPE, stderr=PIPE, env=self.env).communicate() assert os.path.exists(final), 'Failed to compile file: ' + output[0] self.filename = final From 87d810fc5e78473fc73eb09258c41f40830b7602 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 12 May 2014 18:05:16 -0700 Subject: [PATCH 04/19] testcases for issue #2350 --- tests/cases/i1tof_ta2.ll | 71 +++++++++++++++++++++++++++++++++++++++ tests/cases/i1tof_ta2.txt | 3 ++ 2 files changed, 74 insertions(+) create mode 100644 tests/cases/i1tof_ta2.ll create mode 100644 tests/cases/i1tof_ta2.txt diff --git a/tests/cases/i1tof_ta2.ll b/tests/cases/i1tof_ta2.ll new file mode 100644 index 0000000000000..129409075c848 --- /dev/null +++ b/tests/cases/i1tof_ta2.ll @@ -0,0 +1,71 @@ +; ModuleID = 'bad/emcc-0-basebc.bc' +target datalayout = "e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128" +target triple = "asmjs-unknown-emscripten" + +@.str = private unnamed_addr constant [7 x i8] c"%0.1f\0A\00", align 1 +@.str2 = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 + +; Function Attrs: noinline +define float @_Z2v1v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FECCCCCC0000000, float 0.000000e+00 + ret float %cond +} + +define double @emscripten_get_now() #0 { + ret double 1.0 +} + +; Function Attrs: noinline +define float @_Z2v2v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FD99999A0000000, float 0.000000e+00 + ret float %cond +} + +; Function Attrs: noinline +define float @_Z2v3v() #0 { +entry: + %call = tail call double @emscripten_get_now() + %cmp = fcmp oge double %call, 0.000000e+00 + %cond = select i1 %cmp, float 0x3FB99999A0000000, float 0.000000e+00 + ret float %cond +} + +define i32 @main() #1 { +entry: + %call = tail call float @_Z2v1v() + %call1 = tail call float @_Z2v2v() + %call2 = tail call float @_Z2v3v() + %sub = fsub float %call1, %call + %cmp.i = fcmp ogt float %sub, 0.000000e+00 + br i1 %cmp.i, label %_ZL5signff.exit, label %cond.false.i + +cond.false.i: ; preds = %entry + %cmp1.i = fcmp olt float %sub, 0.000000e+00 + %phitmp = sitofp i1 %cmp1.i to float + %phitmpd = fpext float %phitmp to double + %call1115a = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), double %phitmpd) + %phitmpi = sext i1 %cmp1.i to i32 + %call1115b = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str2, i32 0, i32 0), i32 %phitmpi) + br label %_ZL5signff.exit + +_ZL5signff.exit: ; preds = %cond.false.i, %entry + %cond2.i = phi float [ %phitmp, %cond.false.i ], [ 1.000000e+00, %entry ] + %mul = fmul float %call2, %cond2.i + %add = fadd float %call, %mul + %conv4 = fpext float %add to double + %call5 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([7 x i8]* @.str, i32 0, i32 0), double %conv4) + ret i32 0 +} + +; Function Attrs: nounwind +declare i32 @printf(i8* nocapture, ...) #2 + +attributes #0 = { noinline "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #2 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf"="true" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "unsafe-fp-math"="false" "use-soft-float"="false" } diff --git a/tests/cases/i1tof_ta2.txt b/tests/cases/i1tof_ta2.txt new file mode 100644 index 0000000000000..5d3943cda3cdd --- /dev/null +++ b/tests/cases/i1tof_ta2.txt @@ -0,0 +1,3 @@ +-1.0 +-1 +0.8 From 04d60d0d89cd06b1924e1257b28049cf43733388 Mon Sep 17 00:00:00 2001 From: mhenschel Date: Tue, 13 May 2014 15:20:19 +0200 Subject: [PATCH 05/19] fix progress events for non mozilla browsers Progress events didn't work for me in Chrome or IE. This change fixes it for me. --- src/library_browser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library_browser.js b/src/library_browser.js index 44e8c47331c94..c164d4b716a4e 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -740,8 +740,8 @@ mergeInto(LibraryManager.library, { // PROGRESS http.onprogress = function http_onprogress(e) { - if (e.lengthComputable || (e.lengthComputable === undefined && e.totalSize != 0)) { - var percentComplete = (e.position / e.totalSize)*100; + if (e.lengthComputable || (e.lengthComputable === undefined && e.total != 0)) { + var percentComplete = (e.loaded / e.total)*100; if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]); } }; From e0a636a2963266bb80469f90103362fc79eb1727 Mon Sep 17 00:00:00 2001 From: mhenschel Date: Tue, 13 May 2014 15:28:19 +0200 Subject: [PATCH 06/19] Add myself to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 7994f80e26ba4..137efb000e3cc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -138,3 +138,4 @@ a license to everyone to use it as detailed in LICENSE.) * Guillaume Blanc * Usagi Ito * Camilo Polymeris +* Markus Henschel From 6701e2d57e27e68520b2c1dcd04893336884eccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Tue, 13 May 2014 22:08:17 +0300 Subject: [PATCH 07/19] Move the default WebGL context lost message to the default shell.html file so that default applications still show the error message, and applications can easily override the behavior if/when they see fit. --- src/shell.html | 11 ++++++++++- src/shell_minimal.html | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/shell.html b/src/shell.html index 1e213024b3dab..d204bfa6eb277 100644 --- a/src/shell.html +++ b/src/shell.html @@ -1245,7 +1245,16 @@ console.error(text); } }, - canvas: document.getElementById('canvas'), + canvas: (function() { + var canvas = document.getElementById('canvas'); + + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), setStatus: function(text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; if (text === Module.setStatus.text) return; diff --git a/src/shell_minimal.html b/src/shell_minimal.html index 7dd67929447f0..b67c6fdd9d615 100644 --- a/src/shell_minimal.html +++ b/src/shell_minimal.html @@ -101,7 +101,16 @@ console.error(text); } }, - canvas: document.getElementById('canvas'), + canvas: (function() { + var canvas = document.getElementById('canvas'); + + // As a default initial behavior, pop up an alert when webgl context is lost. To make your + // application robust, you may want to override this behavior before shipping! + // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 + canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + + return canvas; + })(), setStatus: function(text) { if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; if (text === Module.setStatus.text) return; From a2440907aeb89069a6f2a13bdaff888984cd3c62 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 May 2014 13:49:59 -0700 Subject: [PATCH 08/19] conditionalize when it can avoid costly side-effect free code --- tools/js-optimizer.js | 65 +++++++++++++++++++++++ tools/test-js-optimizer-asm-pre-output.js | 59 ++++++++++++++++++++ tools/test-js-optimizer-asm-pre.js | 61 ++++++++++++++++++++- 3 files changed, 184 insertions(+), 1 deletion(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 2914b6e849e33..678ba0d5f5e20 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -749,11 +749,64 @@ function simplifyExpressions(ast) { }); } + // expensive | expensive can be turned into expensive ? 1 : expensive, and + // expensive | cheap can be turned into cheap ? 1 : expensive, + // so that we can avoid the expensive computation, if it has no side effects. + function conditionalize(ast) { + var MIN_COST = 4; + traverse(ast, function(node, type) { + if (type === 'if' || type === 'while') { + var cond = node[1]; + if (cond[0] === 'binary' && (cond[1] === '|' || cond[1] === '&') && cond[3][0] !== 'num' && cond[2][0] !== 'num') { + // logical operator on two non-numerical values, all inside a condition + var left = cond[2]; + var right = cond[3]; + var leftEffects = hasSideEffects(left); + var rightEffects = hasSideEffects(right); + if (leftEffects && rightEffects) return; // both must execute + // canonicalize with side effects, if any, happening on the left + if (rightEffects) { + if (measureCost(left) < MIN_COST) return; // avoidable code is too cheap + var temp = left; + left = right; + right = temp; + } else if (leftEffects) { + if (measureCost(right) < MIN_COST) return; // avoidable code is too cheap + } else { + // no side effects, reorder based on cost estimation + var leftCost = measureCost(left); + var rightCost = measureCost(right); + if (Math.max(leftCost, rightCost) < MIN_COST) return; // avoidable code is too cheap + // canonicalize with expensive code on the right + if (leftCost > rightCost) { + var temp = left; + left = right; + right = temp; + } + } + // worth it, perform conditionalization + if (cond[1] === '|') { + node[1] = ['conditional', left, ['num', 1], right]; + } else { // & + node[1] = ['conditional', left, right, ['num', 0]]; + } + if (left[0] === 'unary-prefix' && left[1] === '!') { + node[1][1] = flipCondition(left); + var temp = node[1][2]; + node[1][2] = node[1][3]; + node[1][3] = temp; + } + } + } + }); + } + traverseGeneratedFunctions(ast, function(func) { simplifyIntegerConversions(func); simplifyBitops(func); joinAdditions(func); simplifyNotComps(func); + conditionalize(func); // simplifyZeroComp(func); TODO: investigate performance }); } @@ -3921,6 +3974,18 @@ function measureSize(ast) { return size; } +function measureCost(ast) { + var size = 0; + traverse(ast, function(node, type) { + if (type === 'num' || type === 'unary-prefix') size--; + else if (type === 'binary' && node[3][0] === 'num' && node[3][1] === 0) size--; + else if (type === 'call' && !callHasSideEffects(node)) size -= 2; + else if (type === 'sub') size++; + size++; + }); + return size; +} + function aggressiveVariableEliminationInternal(func, asmData) { // This removes as many variables as possible. This is often not the best thing because it increases // code size, but it is far preferable to the risk of split functions needing to do more spilling, so diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index c9746e785cf58..ce45645c38eb2 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -538,4 +538,63 @@ function fcomp() { if (5 >= ($b | 0)) return 5; if (5 >= 5) return 5; } +function conditionalizeMe() { + if (x > 1 ? x + y + z + w > 12 : 0) { + b(); + } + if (a() > 1 ? x + y + z + w > 12 : 0) { + b(); + } + if (x > 1 & x + y + z + k() > 12) { + b(); + } + if (a() > 1 & x + y + z + k() > 12) { + b(); + } + if (x > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (a() > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (x > 1 | x + y + z + k() > 12) { + b(); + } + if (a() > 1 | x + y + z + k() > 12) { + b(); + } + if (x > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (a() > 1 ? 1 : x + y + z + w > 12) { + b(); + } + if (x + y + z + k() > 12 | x > 1) { + b(); + } + if (x + y + z + k() > 12 | a() > 1) { + b(); + } + while (x > 1 ? x + y + z + w > 12 : 0) { + b(); + } + while (a() > 1 ? x + y + z + w > 12 : 0) { + b(); + } + while (x > 1 & x + y + z + k() > 12) { + b(); + } + while (a() > 1 & x + y + z + k() > 12) { + b(); + } + if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(+0))) { + b(); + } + if ($sub$i480 >= Math_fround(+0) ? !($sub4$i483 >= Math_fround(HEAPF32[x + y | 0])) : 1) { + b(); + } + if (x > 10 ? 1 : HEAP[20] > 5) { + b(); + } +} diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index 00ebd7d7e380b..b9773bb5a070f 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -550,4 +550,63 @@ function fcomp() { if (!(5 < ($b|0))) return 5; if (!(5 < 5)) return 5; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp"] +function conditionalizeMe() { + if ((x > 1) & (x+y+z+w > 12)) { + b(); + } + if ((a() > 1) & (x+y+z+w > 12)) { + b(); + } + if ((x > 1) & (x+y+z+k() > 12)) { + b(); + } + if ((a() > 1) & (x+y+z+k() > 12)) { + b(); + } + if ((x > 1) | (x+y+z+w > 12)) { + b(); + } + if ((a() > 1) | (x+y+z+w > 12)) { + b(); + } + if ((x > 1) | (x+y+z+k() > 12)) { + b(); + } + if ((a() > 1) | (x+y+z+k() > 12)) { + b(); + } + if ((x+y+z+w > 12) | (x > 1)) { + b(); + } + if ((x+y+z+w > 12) | (a() > 1)) { + b(); + } + if ((x+y+z+k() > 12) | (x > 1)) { + b(); + } + if ((x+y+z+k() > 12) | (a() > 1)) { + b(); + } + while ((x > 1) & (x+y+z+w > 12)) { + b(); + } + while ((a() > 1) & (x+y+z+w > 12)) { + b(); + } + while ((x > 1) & (x+y+z+k() > 12)) { + b(); + } + while ((a() > 1) & (x+y+z+k() > 12)) { + b(); + } + if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(+0))) { + b(); + } + if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(HEAPF32[x+y|0]))) { + b(); + } + if (x > 10 | HEAP[20] > 5) { + b(); + } +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp", "conditionalizeMe"] From eef1e214a698ae6e4f8b2c375536b268f0a95dd2 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 May 2014 14:14:29 -0700 Subject: [PATCH 09/19] update heuristic --- tools/js-optimizer.js | 2 +- tools/test-js-optimizer-asm-pre-output.js | 2 +- tools/test-js-optimizer-asm-pre.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 678ba0d5f5e20..d79e8f238af02 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -753,7 +753,7 @@ function simplifyExpressions(ast) { // expensive | cheap can be turned into cheap ? 1 : expensive, // so that we can avoid the expensive computation, if it has no side effects. function conditionalize(ast) { - var MIN_COST = 4; + var MIN_COST = 7; traverse(ast, function(node, type) { if (type === 'if' || type === 'while') { var cond = node[1]; diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index ce45645c38eb2..d6a7a04b6e0c5 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -593,7 +593,7 @@ function conditionalizeMe() { if ($sub$i480 >= Math_fround(+0) ? !($sub4$i483 >= Math_fround(HEAPF32[x + y | 0])) : 1) { b(); } - if (x > 10 ? 1 : HEAP[20] > 5) { + if (x > 10 | HEAP[20] + 2 > 5) { b(); } } diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index b9773bb5a070f..f930fc132d2d0 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -605,7 +605,7 @@ function conditionalizeMe() { if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(HEAPF32[x+y|0]))) { b(); } - if (x > 10 | HEAP[20] > 5) { + if (x > 10 | (HEAP[20] + 2) > 5) { b(); } } From 17ba5c2b0712b8c871a260f3e1ce00527c5a4d1d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Tue, 13 May 2014 15:00:34 -0700 Subject: [PATCH 10/19] conditionalize in all boolean-emitting expressions --- tests/test_benchmark.py | 36 ++++++++++ tools/js-optimizer.js | 86 +++++++++++++---------- tools/test-js-optimizer-asm-pre-output.js | 1 + tools/test-js-optimizer-asm-pre.js | 1 + 4 files changed, 86 insertions(+), 38 deletions(-) diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 821caa6b1e08d..16abbfdd8070f 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -408,6 +408,42 @@ def test_ifs(self): ''' self.do_benchmark('ifs', src, 'ok', reps=TEST_REPS*5) + def test_conditionals(self): + src = r''' + #include + #include + + int main(int argc, char *argv[]) { + int arg = argc > 1 ? argv[1][0] - '0' : 3; + switch(arg) { + case 0: return 0; break; + case 1: arg = 3*75; break; + case 2: arg = 3*625; break; + case 3: arg = 3*1250; break; + case 4: arg = 3*5*1250; break; + case 5: arg = 3*10*1250; break; + default: printf("error: %d\\n", arg); return -1; + } + + int x = 0; + + for (int j = 0; j < 27000; j++) { + for (int i = 0; i < arg; i++) { + if (((x*x+11) % 3 == 0) | ((x*(x+2)+17) % 5 == 0)) { + x += 2; + } else { + x++; + } + } + } + + printf("ok %d\n", x); + + return x; + } + ''' + self.do_benchmark('conditionals', src, 'ok', reps=TEST_REPS*5) + def test_fannkuch(self): src = open(path_from_root('tests', 'fannkuch.cpp'), 'r').read().replace( 'int n = argc > 1 ? atoi(argv[1]) : 0;', diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d79e8f238af02..f38ff08c55f0a 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -749,54 +749,61 @@ function simplifyExpressions(ast) { }); } + function emitsBoolean(node) { + if (node[0] === 'binary') return node[1] in COMPARE_OPS; + if (node[0] === 'unary-prefix') return node[1] === '!'; + if (node[0] === 'conditional') return true; + return false; + } + // expensive | expensive can be turned into expensive ? 1 : expensive, and // expensive | cheap can be turned into cheap ? 1 : expensive, // so that we can avoid the expensive computation, if it has no side effects. function conditionalize(ast) { var MIN_COST = 7; traverse(ast, function(node, type) { - if (type === 'if' || type === 'while') { - var cond = node[1]; - if (cond[0] === 'binary' && (cond[1] === '|' || cond[1] === '&') && cond[3][0] !== 'num' && cond[2][0] !== 'num') { - // logical operator on two non-numerical values, all inside a condition - var left = cond[2]; - var right = cond[3]; - var leftEffects = hasSideEffects(left); - var rightEffects = hasSideEffects(right); - if (leftEffects && rightEffects) return; // both must execute - // canonicalize with side effects, if any, happening on the left - if (rightEffects) { - if (measureCost(left) < MIN_COST) return; // avoidable code is too cheap + if (node[0] === 'binary' && (node[1] === '|' || node[1] === '&') && node[3][0] !== 'num' && node[2][0] !== 'num') { + // logical operator on two non-numerical values + var left = node[2]; + var right = node[3]; + if (!emitsBoolean(left) || !emitsBoolean(right)) return; + var leftEffects = hasSideEffects(left); + var rightEffects = hasSideEffects(right); + if (leftEffects && rightEffects) return; // both must execute + // canonicalize with side effects, if any, happening on the left + if (rightEffects) { + if (measureCost(left) < MIN_COST) return; // avoidable code is too cheap + var temp = left; + left = right; + right = temp; + } else if (leftEffects) { + if (measureCost(right) < MIN_COST) return; // avoidable code is too cheap + } else { + // no side effects, reorder based on cost estimation + var leftCost = measureCost(left); + var rightCost = measureCost(right); + if (Math.max(leftCost, rightCost) < MIN_COST) return; // avoidable code is too cheap + // canonicalize with expensive code on the right + if (leftCost > rightCost) { var temp = left; left = right; right = temp; - } else if (leftEffects) { - if (measureCost(right) < MIN_COST) return; // avoidable code is too cheap - } else { - // no side effects, reorder based on cost estimation - var leftCost = measureCost(left); - var rightCost = measureCost(right); - if (Math.max(leftCost, rightCost) < MIN_COST) return; // avoidable code is too cheap - // canonicalize with expensive code on the right - if (leftCost > rightCost) { - var temp = left; - left = right; - right = temp; - } - } - // worth it, perform conditionalization - if (cond[1] === '|') { - node[1] = ['conditional', left, ['num', 1], right]; - } else { // & - node[1] = ['conditional', left, right, ['num', 0]]; - } - if (left[0] === 'unary-prefix' && left[1] === '!') { - node[1][1] = flipCondition(left); - var temp = node[1][2]; - node[1][2] = node[1][3]; - node[1][3] = temp; } } + // worth it, perform conditionalization + var ret; + if (node[1] === '|') { + ret = ['conditional', left, ['num', 1], right]; + } else { // & + ret = ['conditional', left, right, ['num', 0]]; + } + if (left[0] === 'unary-prefix' && left[1] === '!') { + ret[1] = flipCondition(left); + var temp = ret[2]; + ret[2] = ret[3]; + ret[3] = temp; + } + return ret; } }); } @@ -3978,7 +3985,10 @@ function measureCost(ast) { var size = 0; traverse(ast, function(node, type) { if (type === 'num' || type === 'unary-prefix') size--; - else if (type === 'binary' && node[3][0] === 'num' && node[3][1] === 0) size--; + else if (type === 'binary') { + if (node[3][0] === 'num' && node[3][1] === 0) size--; + else if (node[1] === '/' || node[1] === '%') size += 2; + } else if (type === 'call' && !callHasSideEffects(node)) size -= 2; else if (type === 'sub') size++; size++; diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index d6a7a04b6e0c5..31b9cfd70572c 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -596,5 +596,6 @@ function conditionalizeMe() { if (x > 10 | HEAP[20] + 2 > 5) { b(); } + return (((((Math_imul(i6 + 1, i7) | 0) + 17 | 0) % 5 | 0) == 0 ? 1 : ((((Math_imul(i7 + 1, i7) | 0) + 11 | 0) >>> 0) % 3 | 0) == 0) | 0) == 0; } diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index f930fc132d2d0..2a6ea4a9be883 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -608,5 +608,6 @@ function conditionalizeMe() { if (x > 10 | (HEAP[20] + 2) > 5) { b(); } + return ((((Math_imul(i6+1, i7) | 0) + 17 | 0) % 5 | 0 | 0) == 0 | ((((Math_imul(i7+1, i7) | 0) + 11 | 0) >>> 0) % 3 | 0 | 0) == 0 | 0) == 0; } // EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp", "conditionalizeMe"] From d470387f096d6427720a9c8a61776fa3d276683e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 14 May 2014 13:17:13 -0700 Subject: [PATCH 11/19] allow overriding functions with the same name in the parent in webidl binder --- tests/webidl/output.txt | 1 + tests/webidl/post.js | 2 ++ tests/webidl/test.h | 2 ++ tests/webidl/test.idl | 2 ++ third_party/WebIDL.py | 12 +++++++----- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/webidl/output.txt b/tests/webidl/output.txt index b874d928e26a7..af3b00874650e 100644 --- a/tests/webidl/output.txt +++ b/tests/webidl/output.txt @@ -10,6 +10,7 @@ Child1:7 588 14 28 +Child1::parentFunc(90) c1 v2 Parent:16 Child1:15 diff --git a/tests/webidl/post.js b/tests/webidl/post.js index 444efcd1c208e..8056a5ff71e93 100644 --- a/tests/webidl/post.js +++ b/tests/webidl/post.js @@ -5,6 +5,7 @@ var sme = new Module.Parent(42); sme.mulVal(2); Module.print('*') Module.print(sme.getVal()); +sme.parentFunc(90); Module.print('c1'); @@ -16,6 +17,7 @@ Module.print(c1.getValSqr()); Module.print(c1.getValSqr(3)); Module.print(c1.getValTimes()); // default argument should be 1 Module.print(c1.getValTimes(2)); +c1.parentFunc(90); Module.print('c1 v2'); diff --git a/tests/webidl/test.h b/tests/webidl/test.h index 903f8f78d6e18..d8eb0fbc2ba95 100644 --- a/tests/webidl/test.h +++ b/tests/webidl/test.h @@ -10,6 +10,7 @@ class Parent { Parent(Parent *p, Parent *q); // overload constructor int getVal() { return value; }; // inline should work just fine here, unlike Way 1 before void mulVal(int mul); + void parentFunc() {} }; class Child1 : public Parent { @@ -19,6 +20,7 @@ class Child1 : public Parent { int getValSqr() { return value*value; } int getValSqr(int more) { return value*value*more; } int getValTimes(int times=1) { return value*times; } + void parentFunc(int x) { printf("Child1::parentFunc(%d)\n", x); } }; // Child2 has vtable, parent does not. Checks we cast child->parent properly - (Parent*)child is not a no-op, must offset diff --git a/tests/webidl/test.idl b/tests/webidl/test.idl index 98ab50703938e..6d87d5e3eaff5 100644 --- a/tests/webidl/test.idl +++ b/tests/webidl/test.idl @@ -5,12 +5,14 @@ interface Parent { void Parent(long val); long getVal(); void mulVal(long mul); + void parentFunc(); }; interface Child1 { void Child1(optional long val); long getValSqr(optional long more); long getValTimes(optional long times=1); + void parentFunc(long x); // redefinition, name collides with parent }; Child1 implements Parent; diff --git a/third_party/WebIDL.py b/third_party/WebIDL.py index 867a7cbc3dfd0..1ce44e066eecd 100644 --- a/third_party/WebIDL.py +++ b/third_party/WebIDL.py @@ -649,13 +649,15 @@ def finish(self, scope): # Flag the interface as being someone's consequential interface iface.setIsConsequentialInterfaceOf(self) additionalMembers = iface.originalMembers; - for additionalMember in additionalMembers: + for additionalMember in additionalMembers[:]: for member in self.members: if additionalMember.identifier.name == member.identifier.name: - raise WebIDLError( - "Multiple definitions of %s on %s coming from 'implements' statements" % - (member.identifier.name, self), - [additionalMember.location, member.location]) + # XXX emscripten: allow such name collisions, ignore parent + additionalMembers.remove(additionalMember) + #raise WebIDLError( + # "Multiple definitions of %s on %s coming from 'implements' statements" % + # (member.identifier.name, self), + # [additionalMember.location, member.location]) self.members.extend(additionalMembers) iface.interfacesImplementingSelf.add(self) From afed8b5259db9f5a7e9a417c13669f29d8ae3d15 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 May 2014 12:32:27 -0700 Subject: [PATCH 12/19] support const methods, not just attributes, in webidl binder --- tests/webidl/output.txt | 1 + tests/webidl/post.js | 1 + tests/webidl/test.h | 1 + tests/webidl/test.idl | 1 + third_party/WebIDL.py | 1 + tools/webidl_binder.py | 3 ++- 6 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/webidl/output.txt b/tests/webidl/output.txt index af3b00874650e..c029935fcb035 100644 --- a/tests/webidl/output.txt +++ b/tests/webidl/output.txt @@ -1,6 +1,7 @@ Parent:42 * 84 +object c1 Parent:7 Child1:7 diff --git a/tests/webidl/post.js b/tests/webidl/post.js index 8056a5ff71e93..5376f27b0e1ed 100644 --- a/tests/webidl/post.js +++ b/tests/webidl/post.js @@ -6,6 +6,7 @@ sme.mulVal(2); Module.print('*') Module.print(sme.getVal()); sme.parentFunc(90); +Module.print(typeof sme.getAsConst()); Module.print('c1'); diff --git a/tests/webidl/test.h b/tests/webidl/test.h index d8eb0fbc2ba95..5d13846c8e4bf 100644 --- a/tests/webidl/test.h +++ b/tests/webidl/test.h @@ -11,6 +11,7 @@ class Parent { int getVal() { return value; }; // inline should work just fine here, unlike Way 1 before void mulVal(int mul); void parentFunc() {} + const Parent *getAsConst() { return NULL; } }; class Child1 : public Parent { diff --git a/tests/webidl/test.idl b/tests/webidl/test.idl index 6d87d5e3eaff5..8ee82b767e729 100644 --- a/tests/webidl/test.idl +++ b/tests/webidl/test.idl @@ -6,6 +6,7 @@ interface Parent { long getVal(); void mulVal(long mul); void parentFunc(); + [Const] Parent getAsConst(); }; interface Child1 { diff --git a/third_party/WebIDL.py b/third_party/WebIDL.py index 1ce44e066eecd..a94cb5281e2dd 100644 --- a/third_party/WebIDL.py +++ b/third_party/WebIDL.py @@ -3443,6 +3443,7 @@ def handleExtendedAttribute(self, attr): identifier == "Ref" or identifier == "Value" or identifier == "Operator" or + identifier == "Const" or identifier == "WebGLHandlesContextLoss"): # Known attributes that we don't need to do anything with here pass diff --git a/tools/webidl_binder.py b/tools/webidl_binder.py index 0507cc7811938..168460fe04189 100644 --- a/tools/webidl_binder.py +++ b/tools/webidl_binder.py @@ -359,7 +359,8 @@ def render_function(class_name, func_name, sigs, return_type, non_pointer, copy, m.getExtendedAttribute('Value'), (m.getExtendedAttribute('Operator') or [None])[0], constructor, - func_scope=m.parentScope.identifier.name) + func_scope=m.parentScope.identifier.name, + const=m.getExtendedAttribute('Const')) mid_js += [';\n'] if constructor: emit_constructor(name) From cf38e24d7eef9ccf6364a570e321698c675a9c60 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 15 May 2014 12:55:32 -0700 Subject: [PATCH 13/19] test for #2358 --- tests/core/test_double_varargs.c | 34 ++++++++++++++++++++++++++++++ tests/core/test_double_varargs.out | 2 ++ tests/test_core.py | 5 +++++ 3 files changed, 41 insertions(+) create mode 100644 tests/core/test_double_varargs.c create mode 100644 tests/core/test_double_varargs.out diff --git a/tests/core/test_double_varargs.c b/tests/core/test_double_varargs.c new file mode 100644 index 0000000000000..d3eb3eede15ec --- /dev/null +++ b/tests/core/test_double_varargs.c @@ -0,0 +1,34 @@ +#include +#include + +double func_int_double_1(int unused1, ...) +{ + int i; + double d; + va_list vl; + va_start(vl, unused1); + i = va_arg(vl, int); + d = va_arg(vl, double); + va_end(vl); + return i+d; +} + +double func_int_double_2(int unused1, int unused2, ...) +{ + int i; + double d; + va_list vl; + va_start(vl, unused2); + i = va_arg(vl, int); + d = va_arg(vl, double); + va_end(vl); + return i+d; +} + +int main() { + double ret = func_int_double_1(0, 5, 10.0); + printf("%f\n", ret); // Expects to print 15 + ret = func_int_double_2(0, 0, 5, 10.0); + printf("%f\n", ret); // Expects to print 15 +} + diff --git a/tests/core/test_double_varargs.out b/tests/core/test_double_varargs.out new file mode 100644 index 0000000000000..c907deceaaba4 --- /dev/null +++ b/tests/core/test_double_varargs.out @@ -0,0 +1,2 @@ +15.000000 +15.000000 diff --git a/tests/test_core.py b/tests/test_core.py index 6a920a7b27f66..ec5bb9fe2fb65 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -425,6 +425,11 @@ def test_i64_varargs(self): self.do_run_from_file(src, output, 'waka fleefl asdfasdfasdfasdf'.split(' ')) + def test_double_varargs(self): + test_path = path_from_root('tests', 'core', 'test_double_varargs') + src, output = (test_path + s for s in ('.c', '.out')) + self.do_run_from_file(src, output) + def test_i32_mul_precise(self): if self.emcc_args == None: return self.skip('needs ta2') From b312ce10210b22db941c64502b87c032360a0f70 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 May 2014 11:02:03 -0700 Subject: [PATCH 14/19] optimize loop vars even if there is some code (like a phi) in the if block where the loop break is, if it does not interfere --- .../eliminator/asm-eliminator-test-output.js | 35 +++++++++++++------ tools/eliminator/asm-eliminator-test.js | 18 +++++++++- tools/js-optimizer.js | 15 ++++++-- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js index 1a5dca552bd04..bf4b111fd6b46 100644 --- a/tools/eliminator/asm-eliminator-test-output.js +++ b/tools/eliminator/asm-eliminator-test-output.js @@ -24,7 +24,7 @@ function __Z11printResultPiS_j($needle, $haystack, $len) { } function _segment_holding($addr) { $addr = $addr | 0; - var $sp_0 = 0, $3 = 0, $12 = 0, $_0 = 0, label = 0; + var $sp_0 = 0, $3 = 0, $_0 = 0, label = 0; $sp_0 = __gm_ + 444 | 0; while (1) { $3 = HEAP32[(($sp_0 | 0) & 16777215) >> 2] | 0; @@ -35,13 +35,11 @@ function _segment_holding($addr) { break; } } - $12 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0; - if (($12 | 0) == 0) { + $sp_0 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0; + if (($sp_0 | 0) == 0) { $_0 = 0; label = 1659; break; - } else { - $sp_0 = $12; } } if (label == 1659) { @@ -818,7 +816,7 @@ function selfAssign() { function elimOneLoopVar($argc, $argv) { $argc = $argc | 0; $argv = $argv | 0; - var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $inc = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0; + var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0, $j$010$looptemp = 0; $curri$012 = 2; $primes$011 = 0; while (1) { @@ -827,14 +825,13 @@ function elimOneLoopVar($argc, $argv) { if ($call10 > Math_fround(+2)) { $j$010 = 2; while (1) { - $inc = $j$010 + 1 | 0; - if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) { + $j$010$looptemp = $j$010; + $j$010 = $j$010 + 1 | 0; + if ((($curri$012 | 0) % ($j$010$looptemp | 0) & -1 | 0) == 0) { $ok$0 = 0; break L15; } - if (Math_fround($inc | 0) < $call10) { - $j$010 = $inc; - } else { + if (!(Math_fround($j$010 | 0) < $call10)) { $ok$0 = 1; break; } @@ -911,4 +908,20 @@ function elimOneLoopVarStillUsed() { } return $retval$0 | 0; } +function elimOneLoopVar5() { + var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0; + $storemerge3$neg9 = -1; + while (1) { + $25 = $jp + ($26 << 2) | 0; + HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0; + $30 = $26 + 1 | 0; + if (($30 | 0) == 63) { + f($30); + break; + } else { + $storemerge3$neg9 = $18 ^ -1; + $26 = $30; + } + } +} diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js index d5bbd8d520bf8..5bec050704131 100644 --- a/tools/eliminator/asm-eliminator-test.js +++ b/tools/eliminator/asm-eliminator-test.js @@ -1152,5 +1152,21 @@ function elimOneLoopVarStillUsed() { } return $retval$0 | 0; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed"] +function elimOneLoopVar5() { + var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0; + $storemerge3$neg9 = -1; + while (1) { + $25 = $jp + ($26 << 2) | 0; + HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0; + $30 = $26 + 1 | 0; + if (($30 | 0) == 63) { + f($30); // loop var used here, so cannot be easily optimized + break; + } else { + $storemerge3$neg9 = $18 ^ -1; + $26 = $30; + } + } +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVar5"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index f38ff08c55f0a..699ea280fb8b5 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3585,13 +3585,13 @@ function eliminate(ast, memSafe) { clearEmptyNodes(ifTrue[1]); clearEmptyNodes(ifFalse[1]); var flip = false; - if (ifFalse[1][0] && ifFalse[1][0][0] === 'break') { // canonicalize break in the if + if (ifFalse[1][0] && ifFalse[1][ifFalse[1].length-1][0] === 'break') { // canonicalize break in the if-true var temp = ifFalse; ifFalse = ifTrue; ifTrue = temp; flip = true; } - if (ifTrue[1][0] && ifTrue[1][0][0] === 'break') { + if (ifTrue[1][0] && ifTrue[1][ifTrue[1].length-1][0] === 'break') { var assigns = ifFalse[1]; clearEmptyNodes(assigns); var loopers = [], helpers = []; @@ -3628,6 +3628,17 @@ function eliminate(ast, memSafe) { } } } + // remove loop vars that are used in the if + traverse(ifTrue, function(node, type) { + if (type === 'name') { + var index = loopers.indexOf(node[1]); + if (index < 0) index = helpers.indexOf(node[1]); + if (index >= 0) { + loopers.splice(index, 1); + helpers.splice(index, 1); + } + } + }); if (loopers.length === 0) return; for (var l = 0; l < loopers.length; l++) { var looper = loopers[l]; From 398cded47b7e08fbb753fb7b4788da29a22195bb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 16 May 2014 14:16:40 -0700 Subject: [PATCH 15/19] merge loop and helper variables when their use ranges do not overlap --- .../eliminator/asm-eliminator-test-output.js | 22 ++++-- tools/eliminator/asm-eliminator-test.js | 23 ++++++- tools/js-optimizer.js | 69 ++++++++++++++++--- 3 files changed, 99 insertions(+), 15 deletions(-) diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js index bf4b111fd6b46..50bd8f49a47d8 100644 --- a/tools/eliminator/asm-eliminator-test-output.js +++ b/tools/eliminator/asm-eliminator-test-output.js @@ -816,7 +816,7 @@ function selfAssign() { function elimOneLoopVar($argc, $argv) { $argc = $argc | 0; $argv = $argv | 0; - var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0, $j$010$looptemp = 0; + var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0; $curri$012 = 2; $primes$011 = 0; while (1) { @@ -825,12 +825,11 @@ function elimOneLoopVar($argc, $argv) { if ($call10 > Math_fround(+2)) { $j$010 = 2; while (1) { - $j$010$looptemp = $j$010; - $j$010 = $j$010 + 1 | 0; - if ((($curri$012 | 0) % ($j$010$looptemp | 0) & -1 | 0) == 0) { + if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) { $ok$0 = 0; break L15; } + $j$010 = $j$010 + 1 | 0; if (!(Math_fround($j$010 | 0) < $call10)) { $ok$0 = 1; break; @@ -895,10 +894,23 @@ function elimOneLoopVar4() { } } function elimOneLoopVarStillUsed() { + var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0; + while (1) { + if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) { + break; + } + $j$010 = $j$010 + 1 | 0; + if (!(Math_fround($j$010 | 0) < $call10)) { + break; + } + } + return $retval$0 | 0; +} +function elimOneLoopVarStillUsedSE() { var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0, $j$010$looptemp = 0; while (1) { $j$010$looptemp = $j$010; - $j$010 = $j$010 + 1 | 0; + $j$010 = $j$010 + sideeffect() | 0; if ((($curri$012 | 0) % ($j$010$looptemp | 0) & -1 | 0) == 0) { break; } diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js index 5bec050704131..2613b3564d718 100644 --- a/tools/eliminator/asm-eliminator-test.js +++ b/tools/eliminator/asm-eliminator-test.js @@ -1152,6 +1152,27 @@ function elimOneLoopVarStillUsed() { } return $retval$0 | 0; } +function elimOneLoopVarStillUsedSE() { + var $0 = 0, $1 = 0, $arg$0 = 0, $arrayidx = 0, $call10 = Math_fround(0), $cmp = 0, $cmp11 = 0, $cmp119 = 0, $cmp12 = 0, $cmp7 = 0, $conv = 0, $conv8 = Math_fround(0), $conv9 = Math_fround(0), $curri$012 = 0, $inc = 0, $inc14$primes$0 = 0, $inc16 = 0, $j$010 = 0, $ok$0 = 0; + var $primes$011 = 0, $rem = 0, $retval$0 = 0, $sub = 0, $vararg_buffer1 = 0, label = 0, sp = 0; + while (1) { + $rem = ($curri$012 | 0) % ($j$010 | 0) & -1; + $cmp12 = ($rem | 0) == 0; + $inc = $j$010 + sideeffect() | 0; // side effect! + if ($cmp12) { + $ok$0 = 0; + break; + } + $conv8 = Math_fround($inc | 0); + $cmp11 = $conv8 < $call10; + if ($cmp11) { + $j$010 = $inc; + } else { + break; + } + } + return $retval$0 | 0; +} function elimOneLoopVar5() { var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0; $storemerge3$neg9 = -1; @@ -1168,5 +1189,5 @@ function elimOneLoopVar5() { } } } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVar5"] +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVarStillUsedSE", "elimOneLoopVar5"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 699ea280fb8b5..fe00254adde84 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1034,6 +1034,27 @@ function hasSideEffects(node) { // this is 99% incomplete! } } +// checks if a node has just basic operations, nothing with side effects nor that can notice side effects, which +// implies we can move it around in the code +function triviallySafeToMove(node, asmData) { + var ok = true; + traverse(node, function(node, type) { + switch (type) { + case 'stat': case 'binary': case 'unary-prefix': case 'assign': case 'num': + break; + case 'name': + if (!(node[1] in asmData.vars) && !(node[1] in asmData.params)) ok = false; + break; + case 'call': + if (callHasSideEffects(node)) ok = false; + break; + default: + ok = false; + } + }); + return ok; +} + // Clear out empty ifs and blocks, and redundant blocks/stats and so forth // Operates on generated functions only function vacuum(ast) { @@ -3662,21 +3683,51 @@ function eliminate(ast, memSafe) { // if a loop variable is used after we assigned to the helper, we must save its value and use that. // (note that this can happen due to elimination, if we eliminate an expression containing the // loop var far down, past the assignment!) - var temp = looper + '$looptemp'; - var looperUsed = false; - assert(!(temp in asmData.vars)); + // first, see if the looper and helper overlap + var firstLooperUsage = -1; + var lastLooperUsage = -1; + var firstHelperUsage = -1; + var lastHelperUsage = -1; for (var i = found+1; i < stats.length; i++) { var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition traverse(curr, function(node, type) { - if (type === 'name' && node[1] === looper) { - node[1] = temp; - looperUsed = true; + if (type === 'name') { + if (node[1] === looper) { + if (firstLooperUsage < 0) firstLooperUsage = i; + lastLooperUsage = i; + } else if (node[1] === helper) { + if (firstHelperUsage < 0) firstHelperUsage = i; + lastHelperUsage = i; + } } }); } - if (looperUsed) { - asmData.vars[temp] = asmData.vars[looper]; - stats.splice(found, 0, ['stat', ['assign', true, ['name', temp], ['name', looper]]]); + if (firstLooperUsage >= 0) { + // the looper is used, we cannot simply merge the two variables + if ((firstHelperUsage < 0 || firstHelperUsage > lastLooperUsage) && lastLooperUsage+1 < stats.length && triviallySafeToMove(stats[found], asmData)) { + // the helper is not used, or it is used after the last use of the looper, so they do not overlap, + // and the last looper usage is not on the last line (where we could not append after it), and the + // just move the looper definition to after the looper's last use + stats.splice(lastLooperUsage+1, 0, stats[found]); + stats.splice(found, 1); + } else { + // they overlap, we can still proceed with the loop optimization, but we must introduce a + // loop temp helper variable + var temp = looper + '$looptemp'; + assert(!(temp in asmData.vars)); + for (var i = firstLooperUsage; i <= lastLooperUsage; i++) { + var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition + traverse(curr, function(node, type) { + if (type === 'name') { + if (node[1] === looper) { + node[1] = temp; + } + } + }); + } + asmData.vars[temp] = asmData.vars[looper]; + stats.splice(found, 0, ['stat', ['assign', true, ['name', temp], ['name', looper]]]); + } } } for (var l = 0; l < helpers.length; l++) { From b41adadcf60a25c14f4b139d0ee664521f76a620 Mon Sep 17 00:00:00 2001 From: Chad Austin Date: Fri, 16 May 2014 23:34:47 -0700 Subject: [PATCH 16/19] expose typeof via emscripten::val --- src/embind/emval.js | 5 +++++ system/include/emscripten/val.h | 6 +++++- tests/embind/embind.test.js | 10 ++++++++++ tests/embind/embind_test.cpp | 8 ++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/embind/emval.js b/src/embind/emval.js index 4007701a8f5c7..ebec3881cacc5 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -286,3 +286,8 @@ function __emval_has_function(handle, name) { name = getStringOrSymbol(name); return handle[name] instanceof Function; } + +function __emval_typeof(handle) { + handle = requireHandle(handle); + return __emval_register(typeof handle); +} diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index e217c9593ec4c..bfd8610aeb60e 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -62,6 +62,7 @@ namespace emscripten { bool _emval_has_function( EM_VAL value, const char* methodName); + EM_VAL _emval_typeof(EM_VAL value); } template @@ -254,7 +255,6 @@ namespace emscripten { // * delete // * in // * instanceof - // * typeof // * ! ~ - + ++ -- // * * / % // * + - @@ -411,6 +411,10 @@ namespace emscripten { return fromGenericWireType(result); } + val typeof() const { + return val(_emval_typeof(handle)); + } + private: // takes ownership, assumes handle already incref'd explicit val(internal::EM_VAL handle) diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 3ded811a91590..53d3988aee90a 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -2030,6 +2030,16 @@ module({ assert.equal(65538, instance.c); }); }); + + BaseFixture.extend("typeof", function() { + test("typeof", function() { + assert.equal("object", cm.getTypeOfVal(null)); + assert.equal("object", cm.getTypeOfVal({})); + assert.equal("function", cm.getTypeOfVal(function(){})); + assert.equal("number", cm.getTypeOfVal(1)); + assert.equal("string", cm.getTypeOfVal("hi")); + }); + }); }); /* global run_all_tests */ diff --git a/tests/embind/embind_test.cpp b/tests/embind/embind_test.cpp index 5a83903a83b87..52103bbbbeedb 100644 --- a/tests/embind/embind_test.cpp +++ b/tests/embind/embind_test.cpp @@ -2368,3 +2368,11 @@ EMSCRIPTEN_BINDINGS(val_new_) { function("construct_with_memory_view", &construct_with_memory_view); function("construct_with_ints_and_float", &construct_with_ints_and_float); } + +std::string getTypeOfVal(const val& v) { + return v.typeof().as(); +} + +EMSCRIPTEN_BINDINGS(typeof) { + function("getTypeOfVal", &getTypeOfVal); +} From 57360800512dc1b23532e1c125c8e0627ccdbcfa Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Sat, 17 May 2014 20:53:41 -0700 Subject: [PATCH 17/19] fix looptemp replacements when there is a continue with a phi of the loop variable --- .../eliminator/asm-eliminator-test-output.js | 18 ++++++++++++++++++ tools/eliminator/asm-eliminator-test.js | 19 +++++++++++++++++++ tools/js-optimizer.js | 6 +++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js index 50bd8f49a47d8..d530a90cad87b 100644 --- a/tools/eliminator/asm-eliminator-test-output.js +++ b/tools/eliminator/asm-eliminator-test-output.js @@ -936,4 +936,22 @@ function elimOneLoopVar5() { } } } +function loopVarWithContinue() { + var i = 0, i$looptemp = 0; + i = 0; + while (1) { + i$looptemp = i; + i = i + 1; + if (check()) { + i = i$looptemp + 1; + continue; + } + work(i); + work(i$looptemp); + work(i); + if (check()) { + break; + } + } +} diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js index 2613b3564d718..8c469964a51d8 100644 --- a/tools/eliminator/asm-eliminator-test.js +++ b/tools/eliminator/asm-eliminator-test.js @@ -1189,5 +1189,24 @@ function elimOneLoopVar5() { } } } +function loopVarWithContinue() { + var i = 0, inc = 0; + i = 0; + while (1) { + inc = i + 1; + if (check()) { + i = i + 1; + continue; + } + work(inc); + work(i); + work(inc); + if (check()) { + break; + } else { + i = inc; + } + } +} // EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVarStillUsedSE", "elimOneLoopVar5"] diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index fe00254adde84..c0096df420653 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3717,11 +3717,15 @@ function eliminate(ast, memSafe) { assert(!(temp in asmData.vars)); for (var i = firstLooperUsage; i <= lastLooperUsage; i++) { var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition - traverse(curr, function(node, type) { + traverse(curr, function looperToLooptemp(node, type) { if (type === 'name') { if (node[1] === looper) { node[1] = temp; } + } else if (type === 'assign' && node[2][0] === 'name') { + // do not traverse the assignment target, phi assignments to the loop variable must remain + traverse(node[3], looperToLooptemp); + return null; } }); } From f0d152957de6729c1a418294155cb950991487bd Mon Sep 17 00:00:00 2001 From: "Michael J. Bishop" Date: Wed, 23 Oct 2013 16:31:01 -0400 Subject: [PATCH 18/19] Adds support for SDL Window Events with event ids: - `SDL_WINDOWEVENT_FOCUS_GAINED` - `SDL_WINDOWEVENT_FOCUS_LOST` - `SDL_WINDOWEVENT_SHOWN` - `SDL_WINDOWEVENT_HIDDEN` Includes a fix for FFOS phones that prevent the `blur` event when audio is playing (Bugzilla 910340) Conflicts: src/library_sdl.js --- src/library_sdl.js | 62 +++++++++++++++++++++++++++++++++++++------- src/struct_info.json | 10 +++++++ 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/library_sdl.js b/src/library_sdl.js index ae384d22307d6..eabfe3e5eb817 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -430,6 +430,15 @@ var LibrarySDL = { savedKeydown: null, receiveEvent: function(event) { + function unpressAllPressedKeys() { + // Un-press all pressed keys: TODO + for (var code in SDL.keyboardMap) { + SDL.events.push({ + type: 'keyup', + keyCode: SDL.keyboardMap[code] + }); + } + }; switch(event.type) { case 'touchstart': case 'touchmove': { event.preventDefault(); @@ -654,18 +663,23 @@ var LibrarySDL = { } event.preventDefault(); break; + case 'focus': + SDL.events.push(event); + event.preventDefault(); + break; case 'blur': - case 'visibilitychange': { - // Un-press all pressed keys: TODO - for (var code in SDL.keyboardMap) { - SDL.events.push({ - type: 'keyup', - keyCode: SDL.keyboardMap[code] - }); - } + SDL.events.push(event); + unpressAllPressedKeys(); + event.preventDefault(); + break; + case 'visibilitychange': + SDL.events.push({ + type: 'visibilitychange', + visible: !document.hidden + }); + unpressAllPressedKeys(); event.preventDefault(); break; - } case 'unload': if (Browser.mainLoop.runner) { SDL.events.push(event); @@ -865,6 +879,29 @@ var LibrarySDL = { {{{ makeSetValue('ptr', C_STRUCTS.SDL_JoyAxisEvent.value, 'SDL.joystickAxisValueConversion(event.value)', 'i32') }}}; break; } + case 'focus': { + var SDL_WINDOWEVENT_FOCUS_GAINED = 12 /* SDL_WINDOWEVENT_FOCUS_GAINED */; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_GAINED', 'i8') }}}; + break; + } + case 'blur': { + var SDL_WINDOWEVENT_FOCUS_LOST = 13 /* SDL_WINDOWEVENT_FOCUS_LOST */; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, '0', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'SDL_WINDOWEVENT_FOCUS_LOST', 'i8') }}}; + break; + } + case 'visibilitychange': { + var SDL_WINDOWEVENT_SHOWN = 1 /* SDL_WINDOWEVENT_SHOWN */; + var SDL_WINDOWEVENT_HIDDEN = 2 /* SDL_WINDOWEVENT_HIDDEN */; + var visibilityEventID = event.visible ? SDL_WINDOWEVENT_SHOWN : SDL_WINDOWEVENT_HIDDEN; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.type, 'SDL.DOMEventToSDLEvent[event.type]', 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.windowID, 0, 'i32') }}}; + {{{ makeSetValue('ptr', C_STRUCTS.SDL_WindowEvent.event, 'visibilityEventID' , 'i8') }}}; + break; + } default: throw 'Unhandled SDL event: ' + event.type; } }, @@ -1068,6 +1105,7 @@ var LibrarySDL = { document.addEventListener("keydown", SDL.receiveEvent); document.addEventListener("keyup", SDL.receiveEvent); document.addEventListener("keypress", SDL.receiveEvent); + window.addEventListener("focus", SDL.receiveEvent); window.addEventListener("blur", SDL.receiveEvent); document.addEventListener("visibilitychange", SDL.receiveEvent); } @@ -1096,6 +1134,10 @@ var LibrarySDL = { SDL.DOMEventToSDLEvent['touchmove'] = 0x702 /* SDL_FINGERMOTION */; SDL.DOMEventToSDLEvent['unload'] = 0x100 /* SDL_QUIT */; SDL.DOMEventToSDLEvent['resize'] = 0x7001 /* SDL_VIDEORESIZE/SDL_EVENT_COMPAT2 */; + SDL.DOMEventToSDLEvent['visibilitychange'] = 0x200 /* SDL_WINDOWEVENT */; + SDL.DOMEventToSDLEvent['focus'] = 0x200 /* SDL_WINDOWEVENT */; + SDL.DOMEventToSDLEvent['blur'] = 0x200 /* SDL_WINDOWEVENT */; + // These are not technically DOM events; the HTML gamepad API is poll-based. // However, we define them here, as the rest of the SDL code assumes that // all SDL events originate as DOM events. @@ -2231,6 +2273,7 @@ var LibrarySDL = { var url = URL.createObjectURL(blob); audio = new Audio(); audio.src = url; + audio.mozAudioChannelType = 'content'; // bugzilla 910340 } var id = SDL.audios.length; @@ -2244,6 +2287,7 @@ var LibrarySDL = { Mix_QuickLoad_RAW: function(mem, len) { var audio = new Audio(); + audio.mozAudioChannelType = 'content'; // bugzilla 910340 // Record the number of channels and frequency for later usage audio.numChannels = SDL.mixerNumChannels; audio.frequency = SDL.mixerFrequency; diff --git a/src/struct_info.json b/src/struct_info.json index 9aeeb28329132..54c89fd7d29ad 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -932,6 +932,16 @@ "file": "SDL/SDL_events.h", "defines": [], "structs": { + "SDL_WindowEvent": [ + "type", + "windowID", + "event", + "padding1", + "padding2", + "padding3", + "data1", + "data2" + ], "SDL_KeyboardEvent": [ "type", "windowID", From c4541009012b441e99403faaf2ad55942dbc0ba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Mon, 19 May 2014 14:35:53 +0300 Subject: [PATCH 19/19] v1.18.2 --- emscripten-version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emscripten-version.txt b/emscripten-version.txt index 6cee04e09ce61..1992f2ad5b1af 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.18.1 +1.18.2