diff options
-rwxr-xr-x | emcc | 16 | ||||
-rw-r--r-- | emscripten-version.txt | 2 | ||||
-rw-r--r-- | src/library.js | 11 | ||||
-rw-r--r-- | src/library_egl.js | 6 | ||||
-rw-r--r-- | src/library_html5.js | 47 | ||||
-rw-r--r-- | src/library_openal.js | 27 | ||||
-rw-r--r-- | src/library_sdl.js | 36 | ||||
-rw-r--r-- | src/library_sockfs.js | 38 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 53 | ||||
-rw-r--r-- | src/relooper/Relooper.h | 8 | ||||
-rw-r--r-- | src/relooper/test.cpp | 123 | ||||
-rw-r--r-- | src/relooper/test.txt | 65 | ||||
-rw-r--r-- | src/settings.js | 19 | ||||
-rw-r--r-- | system/include/emscripten/html5.h | 10 | ||||
-rw-r--r-- | tests/openal_playback.cpp | 2 | ||||
-rw-r--r-- | tests/test_benchmark.py | 55 | ||||
-rw-r--r-- | tests/test_browser.py | 5 | ||||
-rw-r--r-- | tests/test_html5_mouse.c | 159 | ||||
-rw-r--r-- | tests/test_interactive.py | 3 | ||||
-rw-r--r-- | tests/test_other.py | 97 | ||||
-rw-r--r-- | tests/test_sockets.py | 53 | ||||
-rw-r--r-- | tools/js-optimizer.js | 229 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-outline1-output.js | 39 | ||||
-rw-r--r-- | tools/test-js-optimizer-si-output.js | 197 | ||||
-rw-r--r-- | tools/test-js-optimizer-si.js | 258 |
25 files changed, 1462 insertions, 96 deletions
@@ -235,6 +235,13 @@ Options that are modified or new in %s include: slower because JS optimization will be limited to 1 core. (default in -O0) + -profiling Use reasonable defaults when emitting JS to + make the build useful for profiling. This + sets -g2 (preserve function names) and may + also enable optimizations that affect + performance and otherwise might not be + performed in -g2. + --typed-arrays <mode> 0: No typed arrays 1: Parallel typed arrays 2: Shared (C-like) typed arrays (default) @@ -770,6 +777,7 @@ try: opt_level = 0 debug_level = 0 + profiling = False js_opts = None llvm_opts = None llvm_lto = None @@ -890,6 +898,10 @@ try: requested_level = newargs[i][2:] or '3' debug_level = validate_arg_level(requested_level, 4, 'Invalid debug level: ' + newargs[i]) newargs[i] = '-g' # we'll need this to get LLVM debug info + elif newargs[i] == '-profiling': + debug_level = 2 + profiling = True + newargs[i] = '' elif newargs[i] == '--bind': bind = True newargs[i] = '' @@ -1677,6 +1689,10 @@ try: js_optimizer_queue += ['simplifyExpressions'] + # 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: js_optimizer_queue += ['simplifyIfs'] + if opt_level >= 3 and shared.Settings.PRECISE_F32: js_optimizer_queue += ['optimizeFrounds'] if closure and not shared.Settings.ASM_JS: diff --git a/emscripten-version.txt b/emscripten-version.txt index 87684ab8..2e46fa6f 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.13.1 +1.13.2 diff --git a/src/library.js b/src/library.js index f7155e9d..8aac2bb2 100644 --- a/src/library.js +++ b/src/library.js @@ -6402,6 +6402,13 @@ LibraryManager.library = { siginterrupt: function() { throw 'siginterrupt not implemented' }, + raise__deps: ['$ERRNO_CODES', '__setErrNo'], + raise: function(sig) { + // TODO: + ___setErrNo(ERRNO_CODES.ENOSYS); + return -1; + }, + // ========================================================================== // sys/wait.h // ========================================================================== @@ -8169,7 +8176,9 @@ LibraryManager.library = { }, setsockopt: function(d, level, optname, optval, optlen) { +#if SOCKET_DEBUG console.log('ignoring setsockopt command'); +#endif return 0; }, @@ -8658,7 +8667,9 @@ LibraryManager.library = { }, setsockopt: function(fd, level, optname, optval, optlen) { +#if SOCKET_DEBUG console.log('ignoring setsockopt command'); +#endif return 0; }, diff --git a/src/library_egl.js b/src/library_egl.js index e2d1df43..46ec939e 100644 --- a/src/library_egl.js +++ b/src/library_egl.js @@ -492,7 +492,11 @@ var LibraryEGL = { EGL.setErrorCode(0x3000 /* EGL_SUCCESS */); return 1; }, - + + + // EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL(void); + eglWaitGL: 'eglWaitClient', + // EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval(EGLDisplay dpy, EGLint interval); eglSwapInterval: function(display, interval) { if (display != 62000 /* Magic ID for Emscripten 'default display' */) { diff --git a/src/library_html5.js b/src/library_html5.js index 5534781d..d9376c2a 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -17,6 +17,11 @@ var LibraryJSEvents = { // so that we can report information about that element in the event message. previousFullscreenElement: null, + // Remember the current mouse coordinates in case we need to emulate movementXY generation for browsers that don't support it. + // Some browsers (e.g. Safari 6.0.5) only give movementXY when Pointerlock is active. + previousScreenX: null, + previousScreenY: null, + // When the C runtime exits via exit(), we unregister all event handlers added by this library to be nice and clean. // Track in this field whether we have yet registered that __ATEXIT__ handler. removeEventListenersRegistered: false, @@ -121,7 +126,8 @@ var LibraryJSEvents = { isInternetExplorer: function() { return navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0; }, _removeHandler: function(i) { - JSEvents.eventHandlers[i].target.removeEventListener(JSEvents.eventHandlers[i].eventTypeString, JSEvents.eventHandlers[i].handlerFunc, true); + var h = JSEvents.eventHandlers[i]; + h.target.removeEventListener(h.eventTypeString, h.eventListenerFunc, h.useCapture); JSEvents.eventHandlers.splice(i, 1); }, @@ -141,6 +147,7 @@ var LibraryJSEvents = { } if (eventHandler.callbackfunc) { + eventHandler.eventListenerFunc = jsEventHandler; eventHandler.target.addEventListener(eventHandler.eventTypeString, jsEventHandler, eventHandler.useCapture); JSEvents.eventHandlers.push(eventHandler); JSEvents.registerRemoveEventListeners(); @@ -181,7 +188,7 @@ var LibraryJSEvents = { var eventHandler = { target: JSEvents.findEventTarget(target), - allowsDeferredCalls: JSEvents.isInternetExplorer ? false : true, // MSIE doesn't allow fullscreen and pointerlock requests from key handlers, others do. + allowsDeferredCalls: JSEvents.isInternetExplorer() ? false : true, // MSIE doesn't allow fullscreen and pointerlock requests from key handlers, others do. eventTypeString: eventTypeString, callbackfunc: callbackfunc, handlerFunc: handlerFunc, @@ -203,10 +210,12 @@ var LibraryJSEvents = { {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.metaKey, 'e.metaKey', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.button, 'e.button', 'i16') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.buttons, 'e.buttons', 'i16') }}}; - {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementX, 'e.movementX || e.mozMovementX || e.webkitMovementX', 'i32') }}}; - {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementY, 'e.movementY || e.mozMovementY || e.webkitMovementY', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementX, 'e["movementX"] || e["mozMovementX"] || e["webkitMovementX"] || (e.screenX-JSEvents.previousScreenX)', 'i32') }}}; + {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.movementY, 'e["movementY"] || e["mozMovementY"] || e["webkitMovementY"] || (e.screenY-JSEvents.previousScreenY)', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.canvasX, 'e.clientX - rect.left', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.canvasY, 'e.clientY - rect.top', 'i32') }}}; + JSEvents.previousScreenX = e.screenX; + JSEvents.previousScreenY = e.screenY; }, registerMouseEventCallback: function(target, userData, useCapture, callbackfunc, eventTypeId, eventTypeString) { @@ -239,7 +248,8 @@ var LibraryJSEvents = { if (!JSEvents.wheelEvent) { JSEvents.wheelEvent = _malloc( {{{ C_STRUCTS.EmscriptenWheelEvent.__size__ }}} ); } - var handlerFunc = function(event) { + // The DOM Level 3 events spec event 'wheel' + var wheelHandlerFunc = function(event) { var e = event || window.event; JSEvents.fillMouseEventData(JSEvents.wheelEvent, e); {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["deltaX"]', 'double') }}}; @@ -251,13 +261,26 @@ var LibraryJSEvents = { e.preventDefault(); } }; + // The 'mousewheel' event as implemented in Safari 6.0.5 + var mouseWheelHandlerFunc = function(event) { + var e = event || window.event; + JSEvents.fillMouseEventData(JSEvents.wheelEvent, e); + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["wheelDeltaX"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, '-e["wheelDeltaY"] /* Invert to unify direction with the DOM Level 3 wheel event. */', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, '0 /* Not available */', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, '0 /* DOM_DELTA_PIXEL */', 'i32') }}}; + var shouldCancel = Runtime.dynCall('iiii', callbackfunc, [eventTypeId, JSEvents.wheelEvent, userData]); + if (shouldCancel) { + e.preventDefault(); + } + }; var eventHandler = { target: JSEvents.findEventTarget(target), allowsDeferredCalls: true, eventTypeString: eventTypeString, callbackfunc: callbackfunc, - handlerFunc: handlerFunc, + handlerFunc: (eventTypeString == 'wheel') ? wheelHandlerFunc : mouseWheelHandlerFunc, useCapture: useCapture }; JSEvents.registerOrRemoveHandler(eventHandler); @@ -920,8 +943,16 @@ var LibraryJSEvents = { }, emscripten_set_wheel_callback: function(target, userData, useCapture, callbackfunc) { - JSEvents.registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "wheel"); - return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; + target = JSEvents.findEventTarget(target); + if (typeof target.onwheel !== 'undefined') { + JSEvents.registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "wheel"); + return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; + } else if (typeof target.onmousewheel !== 'undefined') { + JSEvents.registerWheelEventCallback(target, userData, useCapture, callbackfunc, {{{ cDefine('EMSCRIPTEN_EVENT_WHEEL') }}}, "mousewheel"); + return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; + } else { + return {{{ cDefine('EMSCRIPTEN_RESULT_NOT_SUPPORTED') }}}; + } }, emscripten_set_resize_callback: function(target, userData, useCapture, callbackfunc) { diff --git a/src/library_openal.js b/src/library_openal.js index fd382aa1..58b11607 100644 --- a/src/library_openal.js +++ b/src/library_openal.js @@ -58,9 +58,21 @@ var LibraryOpenAL = { entry.src = AL.currentContext.ctx.createBufferSource(); entry.src.buffer = entry.buffer; entry.src.connect(src.gain); - entry.src.start(startTime, offset); - + if (typeof(entry.src.start) !== 'undefined') { + entry.src.start(startTime, offset); + } else if (typeof(entry.src.noteOn) !== 'undefined') { + entry.src.noteOn(startTime); #if OPENAL_DEBUG + if (offset > 0) { + Runtime.warnOnce('The current browser does not support AudioBufferSourceNode.start(when, offset); method, so cannot play back audio with an offset '+offset+' secs! Audio glitches will occur!'); + } +#endif + } +#if OPENAL_DEBUG + else { + Runtime.warnOnce('Unable to start AudioBufferSourceNode playback! Not supported by the browser?'); + } + console.log('updateSource queuing buffer ' + i + ' for source ' + idx + ' at ' + startTime + ' (offset by ' + offset + ')'); #endif } @@ -204,6 +216,9 @@ var LibraryOpenAL = { } if (ctx) { + // Old Web Audio API (e.g. Safari 6.0.5) had an inconsistently named createGainNode function. + if (typeof(ctx.createGain) === 'undefined') ctx.createGain = ctx.createGainNode; + var gain = ctx.createGain(); gain.connect(ctx.destination); var context = { @@ -1283,16 +1298,16 @@ var LibraryOpenAL = { ret = 'Out of Memory'; break; case 0x1004 /* ALC_DEFAULT_DEVICE_SPECIFIER */: - if (typeof(AudioContext) == "function" || - typeof(webkitAudioContext) == "function") { + if (typeof(AudioContext) !== "undefined" || + typeof(webkitAudioContext) !== "undefined") { ret = 'Device'; } else { return 0; } break; case 0x1005 /* ALC_DEVICE_SPECIFIER */: - if (typeof(AudioContext) == "function" || - typeof(webkitAudioContext) == "function") { + if (typeof(AudioContext) !== "undefined" || + typeof(webkitAudioContext) !== "undefined") { ret = 'Device\0'; } else { ret = '\0'; diff --git a/src/library_sdl.js b/src/library_sdl.js index b50073be..2606bafc 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -91,13 +91,20 @@ var LibrarySDL = { 33: 1099, // pagedup 34: 1102, // pagedown - + + 35: 1101, // end + 36: 1098, // home + + 45: 1097, // insert + 17: 1248, // control (right, or left) 18: 1250, // alt 173: 45, // minus 16: 1249, // shift - 96: 88 | 1<<10, // keypad 0 + 20: 301, // caps lock + + 96: 98 | 1<<10, // keypad 0 97: 89 | 1<<10, // keypad 1 98: 90 | 1<<10, // keypad 2 99: 91 | 1<<10, // keypad 3 @@ -107,6 +114,15 @@ var LibrarySDL = { 103: 95 | 1<<10, // keypad 7 104: 96 | 1<<10, // keypad 8 105: 97 | 1<<10, // keypad 9 + + 106: 85 | 1<<10, // keypad multiply + 107: 87 | 1<<10, // keypad plus + 109: 86 | 1<<10, // keypad minus + 111: 84 | 1<<10, // keypad divide + + 110: 99 | 1<<10, // keypad decimal point + + 144: 83 | 1<<10, // keypad num lock 112: 58 | 1<<10, // F1 113: 59 | 1<<10, // F2 @@ -168,16 +184,19 @@ var LibrarySDL = { 27: 41, // escape 8: 42, // backspace 9: 43, // tab + 301: 57, // caps lock 32: 44, // space 61: 46, // equals 91: 47, // left bracket 93: 48, // right bracket 92: 49, // backslash + 96: 43, // grave 59: 51, // ; 96: 52, // apostrophe 44: 54, // comma 46: 55, // period 47: 56, // slash + 127: 76, // delete 305: 224, // ctrl 308: 226, // alt }, @@ -1746,9 +1765,9 @@ var LibrarySDL = { // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. if (!SDL.audioContext) { - if (typeof(AudioContext) === 'function') { + if (typeof(AudioContext) !== 'undefined') { SDL.audioContext = new AudioContext(); - } else if (typeof(webkitAudioContext) === 'function') { + } else if (typeof(webkitAudioContext) !== 'undefined') { SDL.audioContext = new webkitAudioContext(); } else { throw 'Web Audio API is not available!'; @@ -1791,7 +1810,12 @@ var LibrarySDL = { } #endif var playtime = Math.max(curtime, SDL.audio.nextPlayTime); - SDL.audio.soundSource[SDL.audio.nextSoundSource]['start'](playtime); + var ss = SDL.audio.soundSource[SDL.audio.nextSoundSource]; + if (typeof ss['start'] !== 'undefined') { + ss['start'](playtime); + } else if (typeof ss['noteOn'] !== 'undefined') { + ss['noteOn'](playtime); + } var buffer_duration = sizeSamplesPerChannel / SDL.audio.freq; SDL.audio.nextPlayTime = playtime + buffer_duration; // Timer will be scheduled before the buffer completed playing. @@ -1864,7 +1888,7 @@ var LibrarySDL = { } else if (!SDL.audio.timer && !SDL.audio.scriptProcessorNode) { // If we are using the same sampling frequency as the native sampling rate of the Web Audio graph is using, we can feed our buffers via // Web Audio ScriptProcessorNode, which is a pull-mode API that calls back to our code to get audio data. - if (SDL.audioContext !== undefined && SDL.audio.freq == SDL.audioContext['sampleRate']) { + if (SDL.audioContext !== undefined && SDL.audio.freq == SDL.audioContext['sampleRate'] && typeof SDL.audioContext['createScriptProcessor'] !== 'undefined') { var sizeSamplesPerChannel = SDL.audio.bufferSize / SDL.audio.bytesPerSample / SDL.audio.channels; // How many samples per a single channel fit in the cb buffer? SDL.audio.scriptProcessorNode = SDL.audioContext['createScriptProcessor'](sizeSamplesPerChannel, 0, SDL.audio.channels); SDL.audio.scriptProcessorNode['onaudioprocess'] = function (e) { diff --git a/src/library_sockfs.js b/src/library_sockfs.js index 22fd8761..23641464 100644 --- a/src/library_sockfs.js +++ b/src/library_sockfs.js @@ -133,12 +133,42 @@ mergeInto(LibraryManager.library, { } else { // create the actual websocket object and connect try { - var url = 'ws://' + addr + ':' + port; + // runtimeConfig gets set to true if WebSocket runtime configuration is available. + var runtimeConfig = (Module['websocket'] && ('object' === typeof Module['websocket'])); + + // The default value is 'ws://' the replace is needed because the compiler replaces "//" comments with '#' + // comments without checking context, so we'd end up with ws:#, the replace swaps the "#" for "//" again. + var url = '{{{ WEBSOCKET_URL }}}'.replace('#', '//'); + + if (runtimeConfig) { + if ('string' === typeof Module['websocket']['url']) { + url = Module['websocket']['url']; // Fetch runtime WebSocket URL config. + } + } + + if (url === 'ws://' || url === 'wss://') { // Is the supplied URL config just a prefix, if so complete it. + url = url + addr + ':' + port; + } + + // Make the WebSocket subprotocol (Sec-WebSocket-Protocol) default to binary if no configuration is set. + var subProtocols = '{{{ WEBSOCKET_SUBPROTOCOL }}}'; // The default value is 'binary' + + if (runtimeConfig) { + if ('string' === typeof Module['websocket']['subprotocol']) { + subProtocols = Module['websocket']['subprotocol']; // Fetch runtime WebSocket subprotocol config. + } + } + + // The regex trims the string (removes spaces at the beginning and end, then splits the string by + // <any space>,<any space> into an Array. Whitespace removal is important for Websockify and ws. + subProtocols = subProtocols.replace(/^ +| +$/g,"").split(/ *, */); + + // The node ws library API for specifying optional subprotocol is slightly different than the browser's. + var opts = ENVIRONMENT_IS_NODE ? {'protocol': subProtocols.toString()} : subProtocols; + #if SOCKET_DEBUG - console.log('connect: ' + url); + Module.print('connect: ' + url + ', ' + subProtocols.toString()); #endif - // the node ws library API is slightly different than the browser's - var opts = ENVIRONMENT_IS_NODE ? {headers: {'websocket-protocol': ['binary']}} : ['binary']; // If node we use the ws library. var WebSocket = ENVIRONMENT_IS_NODE ? require('ws') : window['WebSocket']; ws = new WebSocket(url, opts); diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index c11de099..780a6d59 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -122,7 +122,7 @@ void Branch::Render(Block *Target, bool SetLabel) { if (Code) PrintIndented("%s\n", Code); if (SetLabel) PrintIndented("label = %d;\n", Target->Id); if (Ancestor) { - if (Type != Direct) { + if (Type == Break || Type == Continue) { if (Labeled) { PrintIndented("%s L%d;\n", Type == Break ? "break" : "continue", Ancestor->Id); } else { @@ -287,6 +287,11 @@ void Block::Render(bool InLoop) { Details->Render(Target, SetCurrLabel); if (HasFusedContent) { Fused->InnerMap.find(Target)->second->Render(InLoop); + } else if (Details->Type == Branch::Nested) { + // Nest the parent content here, and remove it from showing up afterwards as Next + assert(Parent->Next); + Parent->Next->Render(InLoop); + Parent->Next = NULL; } if (useSwitch && iter != ProcessedBranchesOut.end()) { PrintIndented("break;\n"); @@ -650,7 +655,7 @@ void Relooper::Calculate(Block *Entry) { Block *Curr = *iter; for (BlockBranchMap::iterator iter = Curr->BranchesOut.begin(); iter != Curr->BranchesOut.end(); iter++) { Block *Target = iter->first; - if (!contains(Hoisted, Target) && !contains(NextEntries, Target)) + if (!contains(Hoisted, Target) && !contains(NextEntries, Target)) { // abort this hoisting abort = true; break; @@ -1077,12 +1082,48 @@ void Relooper::Calculate(Block *Entry) { SHAPE_SWITCH(Root, { if (Simple->Inner->BranchVar) LastLoop = NULL; // a switch clears out the loop (TODO: only for breaks, not continue) - // If there is a next block, we already know at Simple creation time to make direct branches, - // and we can do nothing more. If there is no next however, then Natural is where we will - // go to by doing nothing, so we can potentially optimize some branches to direct. if (Simple->Next) { + if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2) { + // If there is a next block, we already know at Simple creation time to make direct branches, + // and we can do nothing more in general. But, we try to optimize the case of a break and + // a direct: This would normally be if (break?) { break; } .. but if we + // make sure to nest the else, we can save the break, if (!break?) { .. } . This is also + // better because the more canonical nested form is easier to further optimize later. The + // downside is more nesting, which adds to size in builds with whitespace. + // Note that we avoid switches, as it complicates control flow and is not relevant + // for the common case we optimize here. + bool Found = false; + bool Abort = false; + for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { + Block *Target = iter->first; + Branch *Details = iter->second; + if (Details->Type == Branch::Break) { + Found = true; + if (!contains(NaturalBlocks, Target)) Abort = true; + } else if (Details->Type != Branch::Direct) { + Abort = true; + } + } + if (Found && !Abort) { + for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { + Block *Target = iter->first; + Branch *Details = iter->second; + if (Details->Type == Branch::Break) { + Details->Type = Branch::Direct; + if (MultipleShape *Multiple = Shape::IsMultiple(Details->Ancestor)) { + Multiple->NeedLoop--; + } + } else { + assert(Details->Type == Branch::Direct); + Details->Type = Branch::Nested; + } + } + } + } Next = Simple->Next; } else { + // If there is no next then Natural is where we will + // go to by doing nothing, so we can potentially optimize some branches to direct. for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { Block *Target = iter->first; Branch *Details = iter->second; @@ -1140,7 +1181,7 @@ void Relooper::Calculate(Block *Entry) { for (BlockBranchMap::iterator iter = Simple->Inner->ProcessedBranchesOut.begin(); iter != Simple->Inner->ProcessedBranchesOut.end(); iter++) { Block *Target = iter->first; Branch *Details = iter->second; - if (Details->Type != Branch::Direct) { + if (Details->Type == Branch::Break || Details->Type == Branch::Continue) { assert(LoopStack.size() > 0); if (Details->Ancestor != LoopStack.top() && Details->Labeled) { LabeledShape *Labeled = Shape::IsLabeled(Details->Ancestor); diff --git a/src/relooper/Relooper.h b/src/relooper/Relooper.h index 85adf359..152bae0e 100644 --- a/src/relooper/Relooper.h +++ b/src/relooper/Relooper.h @@ -24,9 +24,11 @@ struct Shape; // Info about a branching from one block to another struct Branch { enum FlowType { - Direct = 0, // We will directly reach the right location through other means, no need for continue or break + Direct = 0, // We will directly reach the right location through other means, no need for continue or break Break = 1, - Continue = 2 + Continue = 2, + Nested = 3 // This code is directly reached, but we must be careful to ensure it is nested in an if - it is not reached + // unconditionally, other code paths exist alongside it that we need to make sure do not intertwine }; Shape *Ancestor; // If not NULL, this shape is the relevant one for purposes of getting to the target block. We break or continue on it Branch::FlowType Type; // If Ancestor is not NULL, this says whether to break or continue @@ -59,7 +61,7 @@ struct Block { Shape *Parent; // The shape we are directly inside int Id; // A unique identifier, defined when added to relooper. Note that this uniquely identifies a *logical* block - if we split it, the two instances have the same content *and* the same Id const char *Code; // The string representation of the code in this block. Owning pointer (we copy the input) - const char *BranchVar; // If we have more than one branch out, the variable whose value determines where we go + const char *BranchVar; // A variable whose value determines where we go; if this is not NULL, emit a switch on that variable bool IsCheckedMultipleEntry; // If true, we are a multiple entry, so reaching us requires setting the label variable Block(const char *CodeInit, const char *BranchVarInit); diff --git a/src/relooper/test.cpp b/src/relooper/test.cpp index b4ce669c..9f3ddceb 100644 --- a/src/relooper/test.cpp +++ b/src/relooper/test.cpp @@ -312,7 +312,128 @@ int main() { printf("\n\n", "the_var"); r.Render(); - puts(buffer); + puts(r.GetOutputBuffer()); + } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_c, NULL, NULL); + + b_b->AddBranchTo(b_c, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); + } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + Block *b_d = new Block("// block D\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_d, NULL, NULL); + + b_b->AddBranchTo(b_c, "b == 10", NULL); + b_b->AddBranchTo(b_d, NULL, NULL); + + b_c->AddBranchTo(b_d, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + r.AddBlock(b_d); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); + } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized, long) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + Block *b_d = new Block("// block D\n", NULL); + Block *b_e = new Block("// block E\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_e, NULL, NULL); + + b_b->AddBranchTo(b_c, "b == 10", NULL); + b_b->AddBranchTo(b_e, NULL, NULL); + + b_c->AddBranchTo(b_d, "c == 10", NULL); + b_c->AddBranchTo(b_e, NULL, NULL); + + b_d->AddBranchTo(b_e, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + r.AddBlock(b_d); + r.AddBlock(b_e); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); + } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- If chain (optimized, lead to complex) --\n\n"); + + Block *b_a = new Block("// block A\n", NULL); + Block *b_b = new Block("// block B\n", NULL); + Block *b_c = new Block("// block C\n", NULL); + Block *b_d = new Block("// block D\n", NULL); + + b_a->AddBranchTo(b_b, "a == 10", NULL); + b_a->AddBranchTo(b_d, NULL, NULL); + + b_b->AddBranchTo(b_c, "b == 10", NULL); + b_b->AddBranchTo(b_d, NULL, NULL); + + b_c->AddBranchTo(b_c, "loop", NULL); + b_c->AddBranchTo(b_d, NULL, NULL); + + Relooper r; + r.AddBlock(b_a); + r.AddBlock(b_b); + r.AddBlock(b_c); + r.AddBlock(b_d); + + r.Calculate(b_a); + r.Render(); + + puts(r.GetOutputBuffer()); } } diff --git a/src/relooper/test.txt b/src/relooper/test.txt index 82b02ad7..d53aeeb1 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -324,10 +324,6 @@ label = 1; L0: while(1) { switch(label|0) { - case 3: { - // block C - break; - } case 1: { // block A if (check == 10) { @@ -357,6 +353,67 @@ } break; } + case 3: { + // block C + break; + } + } + } + + + +-- If chain (optimized) -- + + // block A + if (a == 10) { + // block B + } + // block C + + + +-- If chain (optimized) -- + + // block A + if (a == 10) { + // block B + if (b == 10) { + // block C + } + } + // block D + + + +-- If chain (optimized, long) -- + + // block A + if (a == 10) { + // block B + if (b == 10) { + // block C + if (c == 10) { + // block D + } + } + } + // block E + + + +-- If chain (optimized, lead to complex) -- + + // block A + if (a == 10) { + // block B + if (b == 10) { + while(1) { + // block C + if (!(loop)) { + break; + } + } } } + // block D diff --git a/src/settings.js b/src/settings.js index 1c41676d..8b046e95 100644 --- a/src/settings.js +++ b/src/settings.js @@ -165,8 +165,14 @@ var OUTLINING_LIMIT = 0; // A function size above which we try to automatically // throughput. It is hard to say what values to start testing // with, but something around 20,000 to 100,000 might make sense. // (The unit size is number of AST nodes.) + // Outlining decreases maximum function size, but does so at the + // cost of increasing overall code size as well as performance + // (outlining itself makes code less optimized, and requires + // emscripten to disable some passes that are incompatible with + // it). var AGGRESSIVE_VARIABLE_ELIMINATION = 0; // Run aggressiveVariableElimination in js-optimizer.js +var SIMPLIFY_IFS = 1; // Whether to simplify ifs in js-optimizer.js // Generated code debugging options var SAFE_HEAP = 0; // Check each write to the heap, for example, this will give a clear @@ -225,6 +231,19 @@ var LIBRARY_DEBUG = 0; // Print out when we enter a library call (library*.js). var SOCKET_DEBUG = 0; // Log out socket/network data transfer. var SOCKET_WEBRTC = 0; // Select socket backend, either webrtc or websockets. +// As well as being configurable at compile time via the "-s" option the WEBSOCKET_URL and WEBSOCKET_SUBPROTOCOL +// settings may configured at run time via the Module object e.g. +// Module['websocket'] = {subprotocol: 'base64, binary, text'}; +// Module['websocket'] = {url: 'wss://', subprotocol: 'base64'}; +// Run time configuration may be useful as it lets an application select multiple different services. +var WEBSOCKET_URL = 'ws://'; // A string containing either a WebSocket URL prefix (ws:// or wss://) or a complete + // RFC 6455 URL - "ws[s]:" "//" host [ ":" port ] path [ "?" query ]. + // In the (default) case of only a prefix being specified the URL will be constructed from + // prefix + addr + ':' + port + // where addr and port are derived from the socket connect/bind/accept calls. +var WEBSOCKET_SUBPROTOCOL = 'binary'; // A string containing a comma separated list of WebSocket subprotocols + // as would be present in the Sec-WebSocket-Protocol header. + var OPENAL_DEBUG = 0; // Print out debugging information from our OpenAL implementation. var GL_ASSERTIONS = 0; // Adds extra checks for error situations in the GL library. Can impact performance. diff --git a/system/include/emscripten/html5.h b/system/include/emscripten/html5.h index 4f74af03..db81725a 100644 --- a/system/include/emscripten/html5.h +++ b/system/include/emscripten/html5.h @@ -123,6 +123,10 @@ extern "C" { /* * The event structure passed in keyboard events keypress, keydown and keyup. * https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3-Events.html#keys + * + * Note that since the DOM Level 3 Events spec is very recent at the time of writing (2014-03), uniform + * support for the different fields in the spec is still in flux. Be sure to check the results in multiple + * browsers. See the unmerged pull request #2222 for an example way on how to interpret the legacy key events. */ typedef struct EmscriptenKeyboardEvent { // The printed representation of the pressed key. @@ -144,14 +148,18 @@ typedef struct EmscriptenKeyboardEvent { EM_UTF8 locale[32]; // The following fields are values from previous versions of the DOM key events specifications. // See https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent?redirectlocale=en-US&redirectslug=DOM%2FKeyboardEvent - // The character representation of the key. + // The character representation of the key. This is the field 'char' from the docs, but renamed to charValue to avoid a C reserved word. + // Warning: This attribute has been dropped from DOM Level 3 events. EM_UTF8 charValue[32]; // The Unicode reference number of the key; this attribute is used only by the keypress event. For keys whose char attribute // contains multiple characters, this is the Unicode value of the first character in that attribute. + // Warning: This attribute is deprecated, you should use the field 'key' instead, if available. unsigned long charCode; // A system and implementation dependent numerical code identifying the unmodified value of the pressed key. + // Warning: This attribute is deprecated, you should use the field 'key' instead, if available. unsigned long keyCode; // A system and implementation dependent numeric code identifying the unmodified value of the pressed key; this is usually the same as keyCode. + // Warning: This attribute is deprecated, you should use the field 'key' instead, if available. unsigned long which; } EmscriptenKeyboardEvent; diff --git a/tests/openal_playback.cpp b/tests/openal_playback.cpp index 880b6906..46c4f8a3 100644 --- a/tests/openal_playback.cpp +++ b/tests/openal_playback.cpp @@ -25,7 +25,7 @@ void playSource(void* arg) alGetSourcei(source, AL_SOURCE_STATE, &state); assert(state == AL_STOPPED); -#ifdef __EMSCRIPTEN__ +#ifdef REPORT_RESULT int result = 1; REPORT_RESULT(); #endif diff --git a/tests/test_benchmark.py b/tests/test_benchmark.py index 2bc34c60..4c39a764 100644 --- a/tests/test_benchmark.py +++ b/tests/test_benchmark.py @@ -20,9 +20,10 @@ class Benchmarker: def __init__(self, name): self.name = name - def bench(self, args, output_parser=None): + def bench(self, args, output_parser=None, reps=TEST_REPS): self.times = [] - for i in range(TEST_REPS): + self.reps = reps + for i in range(reps): start = time.time() output = self.run(args) if not output_parser: @@ -41,7 +42,7 @@ class Benchmarker: sorted_times.sort() median = sum(sorted_times[len(sorted_times)/2 - 1:len(sorted_times)/2 + 1])/2 - print ' %10s: mean: %4.3f (+-%4.3f) secs median: %4.3f range: %4.3f-%4.3f (noise: %4.3f%%) (%d runs)' % (self.name, mean, std, median, min(self.times), max(self.times), 100*std/mean, TEST_REPS), + print ' %10s: mean: %4.3f (+-%4.3f) secs median: %4.3f range: %4.3f-%4.3f (noise: %4.3f%%) (%d runs)' % (self.name, mean, std, median, min(self.times), max(self.times), 100*std/mean, self.reps), if baseline: mean_baseline = sum(baseline.times)/len(baseline.times) @@ -130,6 +131,7 @@ try: #NativeBenchmarker('clang-3.4', os.path.join(LLVM_3_4, 'clang'), os.path.join(LLVM_3_4, 'clang++')), #NativeBenchmarker('gcc', 'gcc', 'g++'), JSBenchmarker('sm-f32', SPIDERMONKEY_ENGINE, ['-s', 'PRECISE_F32=2']), + #JSBenchmarker('sm-f32-si', SPIDERMONKEY_ENGINE, ['-profiling', '-s', 'PRECISE_F32=2', '-s', 'SIMPLIFY_IFS=1']), #JSBenchmarker('sm-f32-aggro', SPIDERMONKEY_ENGINE, ['-s', 'PRECISE_F32=2', '-s', 'AGGRESSIVE_VARIABLE_ELIMINATION=1']), #JSBenchmarker('sm-f32-3.2', SPIDERMONKEY_ENGINE, ['-s', 'PRECISE_F32=2'], env={ 'LLVM': LLVM_3_2 }), #JSBenchmarker('sm-f32-3.3', SPIDERMONKEY_ENGINE, ['-s', 'PRECISE_F32=2'], env={ 'LLVM': LLVM_3_3 }), @@ -192,7 +194,7 @@ class benchmark(RunnerCore): print for b in benchmarkers: b.build(self, filename, args, shared_args, emcc_args, native_args, native_exec, lib_builder) - b.bench(args, output_parser) + b.bench(args, output_parser, reps) b.display(benchmarkers[0]) def test_primes(self): @@ -361,6 +363,51 @@ class benchmark(RunnerCore): ''' self.do_benchmark('copy', src, 'sum:') + def test_ifs(self): + src = r''' + #include <stdio.h> + #include <stdlib.h> + + volatile int x = 0; + + __attribute__ ((noinline)) int calc() { + return (x++) & 16384; + } + + 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 = 75; break; + case 2: arg = 625; break; + case 3: arg = 1250; break; + case 4: arg = 5*1250; break; + case 5: arg = 10*1250; break; + default: printf("error: %d\\n", arg); return -1; + } + + int sum = 0; + + for (int j = 0; j < 27000; j++) { + for (int i = 0; i < arg; i++) { + if (calc() && calc()) { + sum += 17; + } else { + sum += 19; + } + if (calc() || calc()) { + sum += 23; + } + } + } + + printf("ok\n"); + + return sum; + } + ''' + self.do_benchmark('ifs', src, 'ok', reps=TEST_REPS*5) + def test_fannkuch(self): src = open(path_from_root('tests', 'fannkuch.cpp'), 'r').read().replace( 'int n = argc > 1 ? atoi(argv[1]) : 0;', diff --git a/tests/test_browser.py b/tests/test_browser.py index d5949709..f0343669 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1790,6 +1790,11 @@ Module["preRun"].push(function () { print opts self.btest(path_from_root('tests', 'test_html5.c'), args=opts, expected='0') + def test_html5_mouse(self): + for opts in [[], ['-O2', '-g1', '--closure', '1']]: + print opts + self.btest(path_from_root('tests', 'test_html5_mouse.c'), args=opts + ['-DAUTOMATE_SUCCESS=1'], expected='0') + def test_codemods(self): for opt_level in [0, 2]: print 'opt level', opt_level diff --git a/tests/test_html5_mouse.c b/tests/test_html5_mouse.c new file mode 100644 index 00000000..f087a62b --- /dev/null +++ b/tests/test_html5_mouse.c @@ -0,0 +1,159 @@ +#include <stdio.h> +#include <emscripten.h> +#include <string.h> +#include <emscripten/html5.h> + +void report_result(int result) +{ + if (result == 0) { + printf("Test successful!\n"); + } else { + printf("Test failed!\n"); + } +#ifdef REPORT_RESULT + REPORT_RESULT(); +#endif +} + +static inline const char *emscripten_event_type_to_string(int eventType) { + const char *events[] = { "(invalid)", "(none)", "keypress", "keydown", "keyup", "click", "mousedown", "mouseup", "dblclick", "mousemove", "wheel", "resize", + "scroll", "blur", "focus", "focusin", "focusout", "deviceorientation", "devicemotion", "orientationchange", "fullscreenchange", "pointerlockchange", + "visibilitychange", "touchstart", "touchend", "touchmove", "touchcancel", "gamepadconnected", "gamepaddisconnected", "beforeunload", + "batterychargingchange", "batterylevelchange", "webglcontextlost", "webglcontextrestored", "(invalid)" }; + ++eventType; + if (eventType < 0) eventType = 0; + if (eventType >= sizeof(events)/sizeof(events[0])) eventType = sizeof(events)/sizeof(events[0])-1; + return events[eventType]; +} + +const char *emscripten_result_to_string(EMSCRIPTEN_RESULT result) { + if (result == EMSCRIPTEN_RESULT_SUCCESS) return "EMSCRIPTEN_RESULT_SUCCESS"; + if (result == EMSCRIPTEN_RESULT_DEFERRED) return "EMSCRIPTEN_RESULT_DEFERRED"; + if (result == EMSCRIPTEN_RESULT_NOT_SUPPORTED) return "EMSCRIPTEN_RESULT_NOT_SUPPORTED"; + if (result == EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED) return "EMSCRIPTEN_RESULT_FAILED_NOT_DEFERRED"; + if (result == EMSCRIPTEN_RESULT_INVALID_TARGET) return "EMSCRIPTEN_RESULT_INVALID_TARGET"; + if (result == EMSCRIPTEN_RESULT_UNKNOWN_TARGET) return "EMSCRIPTEN_RESULT_UNKNOWN_TARGET"; + if (result == EMSCRIPTEN_RESULT_INVALID_PARAM) return "EMSCRIPTEN_RESULT_INVALID_PARAM"; + if (result == EMSCRIPTEN_RESULT_FAILED) return "EMSCRIPTEN_RESULT_FAILED"; + if (result == EMSCRIPTEN_RESULT_NO_DATA) return "EMSCRIPTEN_RESULT_NO_DATA"; + return "Unknown EMSCRIPTEN_RESULT!"; +} + +#define TEST_RESULT(x) if (ret != EMSCRIPTEN_RESULT_SUCCESS) printf("%s returned %s.\n", #x, emscripten_result_to_string(ret)); + +int gotClick = 0; +int gotMouseDown = 0; +int gotMouseUp = 0; +int gotDblClick = 0; +int gotMouseMove = 0; +int gotWheel = 0; + +void instruction() +{ + if (!gotClick) { printf("Please click on the canvas.\n"); return; } + if (!gotMouseDown) { printf("Please click on the canvas.\n"); return; } + if (!gotMouseUp) { printf("Please click on the canvas.\n"); return; } + if (!gotDblClick) { printf("Please double-click on the canvas.\n"); return; } + if (!gotMouseMove) { printf("Please move the mouse on the canvas.\n"); return; } + if (!gotWheel) { printf("Please scroll the mouse wheel.\n"); return; } + + if (gotClick && gotMouseDown && gotMouseUp && gotDblClick && gotMouseMove && gotWheel) report_result(0); +} + +EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent *e, void *userData) +{ + printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, movement: (%ld,%ld), canvas: (%ld,%ld)\n", + emscripten_event_type_to_string(eventType), e->screenX, e->screenY, e->clientX, e->clientY, + e->ctrlKey ? " CTRL" : "", e->shiftKey ? " SHIFT" : "", e->altKey ? " ALT" : "", e->metaKey ? " META" : "", + e->button, e->buttons, e->movementX, e->movementY, e->canvasX, e->canvasY); + + if (e->screenX != 0 && e->screenY != 0 && e->clientX != 0 && e->clientY != 0 && e->canvasX != 0 && e->canvasY != 0) + { + if (e->buttons != 0) + { + if (eventType == EMSCRIPTEN_EVENT_CLICK) gotClick = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) gotMouseDown = 1; + if (eventType == EMSCRIPTEN_EVENT_DBLCLICK) gotDblClick = 1; + } + if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) gotMouseUp = 1; + if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE && (e->movementX != 0 || e->movementY != 0)) gotMouseMove = 1; + } + + if (eventType == EMSCRIPTEN_EVENT_CLICK && e->screenX == -500000) + { + printf("ERROR! Received an event to a callback that should have been unregistered!\n"); + gotClick = 0; + report_result(1); + } + + instruction(); + return 0; +} + +EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent *e, void *userData) +{ + printf("%s, screen: (%ld,%ld), client: (%ld,%ld),%s%s%s%s button: %hu, buttons: %hu, canvas: (%ld,%ld), delta:(%g,%g,%g), deltaMode:%lu\n", + emscripten_event_type_to_string(eventType), e->mouse.screenX, e->mouse.screenY, e->mouse.clientX, e->mouse.clientY, + e->mouse.ctrlKey ? " CTRL" : "", e->mouse.shiftKey ? " SHIFT" : "", e->mouse.altKey ? " ALT" : "", e->mouse.metaKey ? " META" : "", + e->mouse.button, e->mouse.buttons, e->mouse.canvasX, e->mouse.canvasY, + (float)e->deltaX, (float)e->deltaY, (float)e->deltaZ, e->deltaMode); + + if (e->deltaY > 0.f || e->deltaY < 0.f) + gotWheel = 1; + + instruction(); + return 0; +} + +int main() +{ + EMSCRIPTEN_RESULT ret = emscripten_set_click_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_click_callback); + ret = emscripten_set_mousedown_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mousedown_callback); + ret = emscripten_set_mouseup_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mouseup_callback); + ret = emscripten_set_dblclick_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_dblclick_callback); + ret = emscripten_set_mousemove_callback(0, 0, 1, mouse_callback); + TEST_RESULT(emscripten_set_mousemove_callback); + + ret = emscripten_set_wheel_callback(0, 0, 1, wheel_callback); + TEST_RESULT(emscripten_set_wheel_callback); + +#ifdef AUTOMATE_SUCCESS + EM_ASM( + function sendEvent(type, data) { + var event = document.createEvent('Event'); + event.initEvent(type, true, true); + for(var d in data) event[d] = data[d]; + window.dispatchEvent(event); + } + sendEvent('click', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 1 }); + ); + // Test that unregistering a callback works. Clicks should no longer be received. + ret = emscripten_set_click_callback(0, 0, 1, 0); + TEST_RESULT(emscripten_set_click_callback); + + EM_ASM( + function sendEvent(type, data) { + var event = document.createEvent('Event'); + event.initEvent(type, true, true); + for(var d in data) event[d] = data[d]; + window.dispatchEvent(event); + } + sendEvent('click', { screenX: -500000, screenY: -500000, clientX: -500000, clientY: -500000, button: 0, buttons: 1 }); // Send a dummy event that should not be received. + sendEvent('mousedown', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 1 }); + sendEvent('mouseup', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0 }); + sendEvent('dblclick', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 1 }); + sendEvent('mousemove', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0, movementX: 1, movementY: 1 }); + sendEvent('wheel', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0, deltaX: 1, deltaY: 1, deltaZ: 1, deltaMode: 1 }); + sendEvent('mousewheel', { screenX: 1, screenY: 1, clientX: 1, clientY: 1, button: 0, buttons: 0, wheelDeltaX: 1, wheelDeltaY: 1 }); + ); +#endif + + /* For the events to function, one must either call emscripten_set_main_loop or enable Module.noExitRuntime by some other means. + Otherwise the application will exit after leaving main(), and the atexit handlers will clean up all event hooks (by design). */ + EM_ASM(Module['noExitRuntime'] = true); + return 0; +} diff --git a/tests/test_interactive.py b/tests/test_interactive.py index 715e7d6b..4ac52f55 100644 --- a/tests/test_interactive.py +++ b/tests/test_interactive.py @@ -22,6 +22,9 @@ class interactive(BrowserCore): def test_html5_fullscreen(self): self.btest(path_from_root('tests', 'test_html5_fullscreen.c'), expected='0') + def test_html5_mouse(self): + self.btest(path_from_root('tests', 'test_html5_mouse.c'), expected='0') + def test_sdl_wm_togglefullscreen(self): self.btest('sdl_wm_togglefullscreen.c', expected='1', args=['-s', 'NO_EXIT_RUNTIME=1']) diff --git a/tests/test_other.py b/tests/test_other.py index fcf7e49d..ded45112 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -191,6 +191,7 @@ Options that are modified or new in %s include: (['-O2', '-g1'], lambda generated: 'var b = 0' in generated and not 'function _main' in generated, 'compress is cancelled by -g1'), (['-O2', '-g2'], lambda generated: ('var b = 0' in generated or 'var i1 = 0' in generated) and 'function _main' in generated, 'minify is cancelled by -g2'), (['-O2', '-g3'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'registerize is cancelled by -g3'), + (['-O2', '-profiling'], lambda generated: ('var b = 0' in generated or 'var i1 = 0' in generated) and 'function _main' in generated, 'similar to -g2'), #(['-O2', '-g4'], lambda generated: 'var b=0' not in generated and 'var b = 0' not in generated and 'function _main' in generated, 'same as -g3 for now'), (['-s', 'INLINING_LIMIT=0'], lambda generated: 'function _dump' in generated, 'no inlining without opts'), (['-s', 'USE_TYPED_ARRAYS=0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), @@ -1772,6 +1773,8 @@ This pointer might make sense in another type signature: i: 0 ['simplifyExpressions', 'optimizeShiftsConservative']), (path_from_root('tools', 'test-js-optimizer-t2.js'), open(path_from_root('tools', 'test-js-optimizer-t2-output.js')).read(), ['simplifyExpressions', 'optimizeShiftsAggressive']), + (path_from_root('tools', 'test-js-optimizer-si.js'), open(path_from_root('tools', 'test-js-optimizer-si-output.js')).read(), + ['simplifyIfs']), # Make sure that optimizeShifts handles functions with shift statements. (path_from_root('tools', 'test-js-optimizer-t3.js'), open(path_from_root('tools', 'test-js-optimizer-t3-output.js')).read(), ['optimizeShiftsAggressive']), @@ -2568,3 +2571,97 @@ int main() output = run_js('a.out.js', stderr=PIPE, full_output=True, engine=SPIDERMONKEY_ENGINE) assert 'asm.js' in output, 'spidermonkey should mention asm.js compilation: ' + output + def test_bad_triple(self): + Popen([CLANG, path_from_root('tests', 'hello_world.c'), '-c', '-emit-llvm', '-o', 'a.bc'], stdout=PIPE, stderr=PIPE).communicate() + out, err = Popen([PYTHON, EMCC, 'a.bc'], stdout=PIPE, stderr=PIPE).communicate() + assert 'warning' in err, err + assert 'incorrect target triple' in err, err + + def test_simplify_ifs(self): + def test(src, nums): + open('src.c', 'w').write(src) + for opts, ifs in [ + [['-g2'], nums[0]], + [['-profiling'], nums[1]], + [['-profiling', '-g2'], nums[2]] + ]: + print opts, ifs + try_delete('a.out.js') + Popen([PYTHON, EMCC, 'src.c', '-O2'] + opts, stdout=PIPE).communicate() + src = open('a.out.js').read() + main = src[src.find('function _main'):src.find('\n}', src.find('function _main'))] + actual_ifs = main.count('if (') + assert ifs == actual_ifs, main + ' : ' + str([ifs, actual_ifs]) + #print main + + test(r''' + #include <stdio.h> + #include <string.h> + int main(int argc, char **argv) { + if (argc > 5 && strlen(argv[0]) > 1 && strlen(argv[1]) > 2) printf("halp"); + return 0; + } + ''', [3, 1, 1]) + + test(r''' + #include <stdio.h> + #include <string.h> + int main(int argc, char **argv) { + while (argc % 3 == 0) { + if (argc > 5 && strlen(argv[0]) > 1 && strlen(argv[1]) > 2) { + printf("halp"); + argc++; + } else { + while (argc > 0) { + printf("%d\n", argc--); + } + } + } + return 0; + } + ''', [8, 5, 5]) + + test(r''' + #include <stdio.h> + #include <string.h> + int main(int argc, char **argv) { + while (argc % 17 == 0) argc *= 2; + if (argc > 5 && strlen(argv[0]) > 10 && strlen(argv[1]) > 20) { + printf("halp"); + argc++; + } else { + printf("%d\n", argc--); + } + while (argc % 17 == 0) argc *= 2; + return argc; + } + ''', [6, 3, 3]) + + test(r''' + #include <stdio.h> + #include <stdlib.h> + + int main(int argc, char *argv[]) { + if (getenv("A") && getenv("B")) { + printf("hello world\n"); + } else { + printf("goodnight moon\n"); + } + printf("and that's that\n"); + return 0; + } + ''', [3, 1, 1]) + + test(r''' + #include <stdio.h> + #include <stdlib.h> + + int main(int argc, char *argv[]) { + if (getenv("A") || getenv("B")) { + printf("hello world\n"); + } + printf("and that's that\n"); + return 0; + } + ''', [3, 1, 1]) + diff --git a/tests/test_sockets.py b/tests/test_sockets.py index e3a5573d..d8133b0b 100644 --- a/tests/test_sockets.py +++ b/tests/test_sockets.py @@ -419,21 +419,54 @@ class sockets(BrowserCore): sockets_include = '-I'+path_from_root('tests', 'sockets') - # Websockify-proxied servers can't run dgram tests harnesses = [ - # Websockify doesn't seem to like ws.WebSocket clients TODO check if this is a ws issue or Websockify issue - #(WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 49160), 0), - (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=0'], 49161), 0), - (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=1'], 49162), 1) + (WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 59160), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=0'], 59162), 0), + (CompiledServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include, '-DTEST_DGRAM=1'], 59164), 1) ] + # Basic test of node client against both a Websockified and compiled echo server. for harness, datagram in harnesses: with harness: - Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_sockets_echo_client.c'), '-o', path_from_root('tests', 'sockets', 'client.js'), '-DSOCKK=%d' % harness.listen_port, '-DREPORT_RESULT=int dummy'], stdout=PIPE, stderr=PIPE).communicate() + Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_sockets_echo_client.c'), '-o', 'client.js', '-DSOCKK=%d' % harness.listen_port, '-DTEST_DGRAM=%d' % datagram, '-DREPORT_RESULT=int dummy'], stdout=PIPE, stderr=PIPE).communicate() + + out = run_js('client.js', engine=NODE_JS, full_output=True) + self.assertContained('do_msg_read: read 14 bytes', out) + + # Test against a Websockified server with compile time configured WebSocket subprotocol. We use a Websockified + # server because as long as the subprotocol list contains binary it will configure itself to accept binary + # This test also checks that the connect url contains the correct subprotocols. + print "\nTesting compile time WebSocket configuration.\n" + for harness in [ + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 59166) + ]: + with harness: + Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_sockets_echo_client.c'), '-o', 'client.js', '-s', 'SOCKET_DEBUG=1', '-s', 'WEBSOCKET_SUBPROTOCOL="base64, binary"', '-DSOCKK=59166', '-DREPORT_RESULT=int dummy'], stdout=PIPE, stderr=PIPE).communicate() + + out = run_js('client.js', engine=NODE_JS, full_output=True) + self.assertContained('do_msg_read: read 14 bytes', out) + self.assertContained('connect: ws://127.0.0.1:59166, base64,binary', out) + + # Test against a Websockified server with runtime WebSocket configuration. We specify both url and subprotocol. + # In this test we have *deliberately* used the wrong port '-DSOCKK=12345' to configure the echo_client.c, so + # the connection would fail without us specifying a valid WebSocket URL in the configuration. + print "\nTesting runtime WebSocket configuration.\n" + for harness in [ + WebsockifyServerHarness(os.path.join('sockets', 'test_sockets_echo_server.c'), [sockets_include], 59168) + ]: + with harness: + open(os.path.join(self.get_dir(), 'websocket_pre.js'), 'w').write(''' + var Module = { + websocket: { + url: 'ws://localhost:59168/testA/testB', + subprotocol: 'text, base64, binary', + } + }; + ''') - self.assertContained('do_msg_read: read 14 bytes', run_js(path_from_root('tests', 'sockets', 'client.js'), engine=NODE_JS)) + Popen([PYTHON, EMCC, path_from_root('tests', 'sockets', 'test_sockets_echo_client.c'), '-o', 'client.js', '--pre-js', 'websocket_pre.js', '-s', 'SOCKET_DEBUG=1', '-DSOCKK=12345', '-DREPORT_RESULT=int dummy'], stdout=PIPE, stderr=PIPE).communicate() - # Tidy up files that might have been created by this test. - try_delete(path_from_root('tests', 'sockets', 'client.js')) - try_delete(path_from_root('tests', 'sockets', 'client.js.map')) + out = run_js('client.js', engine=NODE_JS, full_output=True) + self.assertContained('do_msg_read: read 14 bytes', out) + self.assertContained('connect: ws://localhost:59168/testA/testB, text,base64,binary', out) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 240ee2bd..129c493f 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -140,6 +140,8 @@ var ALTER_FLOW = set('break', 'continue', 'return'); var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch'); var CONTINUE_CAPTURERS = LOOP; +var COMMABLE = set('assign', 'binary', 'unary-prefix', 'unary-postfix', 'name', 'num', 'call', 'seq', 'conditional', 'sub'); + var FUNCTIONS_THAT_ALWAYS_THROW = set('abort', '___resumeException', '___cxa_throw', '___cxa_rethrow'); var NULL_NODE = ['name', 'null']; @@ -279,6 +281,38 @@ function clearEmptyNodes(list) { } } +function filterEmptyNodes(list) { // creates a copy and returns it + return list.filter(function(node) { + return !(isEmptyNode(node) || (node[0] === 'stat' && isEmptyNode(node[1]))); + }); +} + +function removeEmptySubNodes(node) { + if (node[0] === 'defun') { + node[3] = filterEmptyNodes(node[3]); + } else if (node[0] === 'block' && node[1]) { + node[1] = filterEmptyNodes(node[1]); + } +/* + var stats = getStatements(node); + if (stats) clearEmptyNodes(stats); +*/ +} + +function removeAllEmptySubNodes(ast) { + traverse(ast, removeEmptySubNodes); +} + +function astCompare(x, y) { + if (!Array.isArray(x)) return x === y; + if (!Array.isArray(y)) return false; + if (x.length !== y.length) return false; + for (var i = 0; i < x.length; i++) { + if (!astCompare(x[i], y[i])) return false; + } + return true; +} + // Passes // Dump the AST. Useful for debugging. For example, @@ -414,7 +448,7 @@ function removeUnneededLabelSettings(ast) { }); } -// Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. +// Various expression simplifications. Happens after elimination, which opens up many of these simplification opportunities. var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^'); var COMPARE_OPS = set('<', '<=', '>', '>=', '==', '===', '!=', '!=='); @@ -797,11 +831,166 @@ function simplifyExpressions(ast) { simplifyIntegerConversions(func); simplifyBitops(func); joinAdditions(func); - // simplifyZeroComp(func); TODO: investigate performance simplifyNotComps(func); + // simplifyZeroComp(func); TODO: investigate performance + }); +} + + +function simplifyIfs(ast) { + traverseGeneratedFunctions(ast, function(func) { + var simplifiedAnElse = false; + + traverse(func, function(node, type) { + // simplify if (x) { if (y) { .. } } to if (x ? y : 0) { .. } + if (type === 'if') { + var body = node[2]; + // recurse to handle chains + while (body[0] === 'block') { + var stats = body[1]; + if (stats.length === 0) break; + var other = stats[stats.length-1]; + if (other[0] !== 'if') { + // our if block does not end with an if. perhaps if have an else we can flip + if (node[3] && node[3][0] === 'block') { + var stats = node[3][1]; + if (stats.length === 0) break; + var other = stats[stats.length-1]; + if (other[0] === 'if') { + // flip node + node[1] = flipCondition(node[1]); + node[2] = node[3]; + node[3] = body; + body = node[2]; + } else break; + } else break; + } + // we can handle elses, but must be fully identical + if (node[3] || other[3]) { + if (!node[3]) break; + if (!astCompare(node[3], other[3])) { + // the elses are different, but perhaps if we flipped a condition we can do better + if (astCompare(node[3], other[2])) { + // flip other. note that other may not have had an else! add one if so; we will eliminate such things later + if (!other[3]) other[3] = ['block', []]; + other[1] = flipCondition(other[1]); + var temp = other[2]; + other[2] = other[3]; + other[3] = temp; + } else break; + } + } + if (stats.length > 1) { + // try to commaify - turn everything between the ifs into a comma operator inside the second if + var ok = true; + for (var i = 0; i < stats.length-1; i++) { + var curr = stats[i]; + if (curr[0] === 'stat') curr = curr[1]; + if (!(curr[0] in COMMABLE)) ok = false; + } + if (!ok) break; + for (var i = stats.length-2; i >= 0; i--) { + var curr = stats[i]; + if (curr[0] === 'stat') curr = curr[1]; + other[1] = ['seq', curr, other[1]]; + } + stats = body[1] = [other]; + } + if (stats.length !== 1) break; + if (node[3]) simplifiedAnElse = true; + node[1] = ['conditional', node[1], other[1], ['num', 0]]; + body = node[2] = other[2]; + } + } + }); + + if (simplifiedAnElse) { + // there may be fusing opportunities + + // we can only fuse if we remove all uses of the label. if there are + // other ones - if the label check can be reached from elsewhere - + // we must leave it + var abort = false; + + var labelAssigns = {}; + traverse(func, function(node, type) { + if (type === 'assign' && node[2][0] === 'name' && node[2][1] === 'label') { + if (node[3][0] === 'num') { + var value = node[3][1]; + labelAssigns[value] = (labelAssigns[value] || 0) + 1; + } else { + // label is assigned a dynamic value (like from indirectbr), we cannot do anything + abort = true; + } + } + }); + if (abort) return; + + var labelChecks = {}; + traverse(func, function(node, type) { + if (type === 'binary' && node[1] === '==' && node[2][0] === 'binary' && node[2][1] === '|' && + node[2][2][0] === 'name' && node[2][2][1] === 'label') { + if (node[3][0] === 'num') { + var value = node[3][1]; + labelChecks[value] = (labelChecks[value] || 0) + 1; + } else { + // label is checked vs a dynamic value (like from indirectbr), we cannot do anything + abort = true; + } + } + }); + if (abort) return; + + var inLoop = 0; // when in a loop, we do not emit label = 0; in the relooper as there is no need + traverse(func, function(node, type) { + if (type === 'while') inLoop++; + var stats = getStatements(node); + if (stats) { + for (var i = 0; i < stats.length-1; i++) { + var pre = stats[i]; + var post = stats[i+1]; + if (pre[0] === 'if' && pre[3] && post[0] === 'if' && !post[3]) { + var postCond = post[1]; + if (postCond[0] === 'binary' && postCond[1] === '==' && + postCond[2][0] === 'binary' && postCond[2][1] === '|' && + postCond[2][2][0] === 'name' && postCond[2][2][1] === 'label' && + postCond[2][3][0] === 'num' && postCond[2][3][1] === 0 && + postCond[3][0] === 'num') { + var postValue = postCond[3][1]; + var preElse = pre[3]; + if (labelAssigns[postValue] === 1 && labelChecks[postValue] === 1 && preElse[0] === 'block' && preElse[1] && preElse[1].length === 1) { + var preStat = preElse[1][0]; + if (preStat[0] === 'stat' && preStat[1][0] === 'assign' && + preStat[1][1] === true && preStat[1][2][0] === 'name' && preStat[1][2][1] === 'label' && + preStat[1][3][0] === 'num' && preStat[1][3][1] === postValue) { + // Conditions match, just need to make sure the post clears label + if (post[2][0] === 'block' && post[2][1] && post[2][1].length > 0) { + var postStat = post[2][1][0]; + var haveClear = + postStat[0] === 'stat' && postStat[1][0] === 'assign' && + postStat[1][1] === true && postStat[1][2][0] === 'name' && postStat[1][2][1] === 'label' && + postStat[1][3][0] === 'num' && postStat[1][3][1] === 0; + if (!inLoop || haveClear) { + // Everything lines up, do it + pre[3] = post[2]; + if (haveClear) pre[3][1].splice(0, 1); // remove the label clearing + stats.splice(i+1, 1); // remove the post entirely + } + } + } + } + } + } + } + } + }, function(node, type) { + if (type === 'while') inLoop--; + }); + } }); } + // In typed arrays mode 2, we can have // HEAP[x >> 2] // very often. We can in some cases do the shift on the variable itself when it is set, @@ -1161,6 +1350,10 @@ function simplifyNotCompsDirect(node) { if (!simplifyNotCompsPass) return node; } +function flipCondition(cond) { + return simplifyNotCompsDirect(['unary-prefix', '!', cond]); +} + var simplifyNotCompsPass = false; function simplifyNotComps(ast) { @@ -1281,6 +1474,7 @@ function vacuum(ast) { traverseGeneratedFunctions(ast, function(node) { vacuumInternal(node); simplifyNotComps(node); + removeEmptySubNodes(node); }); } @@ -1426,7 +1620,7 @@ function hoistMultiples(ast) { var temp = node[3]; node[3] = node[2]; node[2] = temp; - node[1] = simplifyNotCompsDirect(['unary-prefix', '!', node[1]]); + node[1] = flipCondition(node[1]); stat1 = node[2][1]; stat2 = node[3][1]; } @@ -1687,6 +1881,11 @@ function denormalizeAsm(func, data) { if (!isEmptyNode(stats[i])) break; } } + // calculate variable definitions + var varDefs = []; + for (var v in data.vars) { + varDefs.push(makeAsmVarDef(v, data.vars[v])); + } // each param needs a line; reuse emptyNodes as much as we can var numParams = 0; for (var i in data.params) numParams++; @@ -1695,26 +1894,21 @@ function denormalizeAsm(func, data) { if (!isEmptyNode(stats[emptyNodes])) break; emptyNodes++; } - var neededEmptyNodes = numParams + 1; // params plus one big var + var neededEmptyNodes = numParams + (varDefs.length ? 1 : 0); // params plus one big var if there are vars if (neededEmptyNodes > emptyNodes) { var args = [0, 0]; for (var i = 0; i < neededEmptyNodes - emptyNodes; i++) args[i+2] = 0; stats.splice.apply(stats, args); + } else if (neededEmptyNodes < emptyNodes) { + stats.splice(0, emptyNodes - neededEmptyNodes); } // add param coercions var next = 0; func[2].forEach(function(param) { stats[next++] = ['stat', ['assign', true, ['name', param], makeAsmCoercion(['name', param], data.params[param])]]; }); - // add variable definitions - var varDefs = []; - for (var v in data.vars) { - varDefs.push(makeAsmVarDef(v, data.vars[v])); - } if (varDefs.length) { stats[next] = ['var', varDefs]; - } else { - stats[next] = emptyNode(); } if (data.inlines.length > 0) { var i = 0; @@ -3877,6 +4071,8 @@ function eliminate(ast, memSafe) { } new ExpressionOptimizer(ast).run(); } + + removeAllEmptySubNodes(ast); } function eliminateMemSafe(ast) { @@ -4247,6 +4443,8 @@ function aggressiveVariableEliminationInternal(func, asmData) { } } }); + + removeAllEmptySubNodes(func); } function aggressiveVariableElimination(ast) { @@ -5235,6 +5433,7 @@ var passes = { removeAssignsToUndefined: removeAssignsToUndefined, //removeUnneededLabelSettings: removeUnneededLabelSettings, simplifyExpressions: simplifyExpressions, + simplifyIfs: simplifyIfs, optimizeShiftsConservative: optimizeShiftsConservative, optimizeShiftsAggressive: optimizeShiftsAggressive, hoistMultiples: hoistMultiples, @@ -5281,7 +5480,15 @@ if (extraInfoStart > 0) extraInfo = JSON.parse(src.substr(extraInfoStart + 14)); arguments_.slice(1).forEach(function(arg) { + //traverse(ast, function(node) { + // if (node[0] === 'defun' && node[1] === 'copyTempFloat') printErr('pre ' + JSON.stringify(node, null, ' ')); + //}); passes[arg](ast); + //var func; + //traverse(ast, function(node) { + // if (node[0] === 'defun') func = node; + // if (isEmptyNode(node)) throw 'empty node after ' + arg + ', in ' + func[1]; + //}); }); if (asm && last) { asmLastOpts(ast); // TODO: move out of last, to make last faster when done later (as in side modules) diff --git a/tools/test-js-optimizer-asm-outline1-output.js b/tools/test-js-optimizer-asm-outline1-output.js index c4792c51..40c028c6 100644 --- a/tools/test-js-optimizer-asm-outline1-output.js +++ b/tools/test-js-optimizer-asm-outline1-output.js @@ -176,11 +176,12 @@ function vars(x, y) { y = +y; var sp = 0; sp = STACKTOP; - STACKTOP = STACKTOP + 152 | 0; + STACKTOP = STACKTOP + 136 | 0; c(1 + (x + y)); c(2 + y * x); c(3 + (x + y)); c(4 + y * x); + c(5 + (x + y)); HEAP32[sp + 8 >> 2] = x; HEAPF32[sp + 16 >> 2] = y; HEAP32[sp + 24 >> 2] = 0; @@ -213,7 +214,7 @@ function vars3(x, y) { y = +y; var a = 0, sp = 0; sp = STACKTOP; - STACKTOP = STACKTOP + 160 | 0; + STACKTOP = STACKTOP + 144 | 0; a = x + y; a = c(1 + a); a = c(2 + y * x); @@ -256,15 +257,11 @@ function vars_w_stack(x, y) { var a = 0, b = +0, sp = 0; sp = STACKTOP; STACKTOP = STACKTOP + 208 | 0; - HEAP32[sp + 24 >> 2] = x; - HEAPF32[sp + 32 >> 2] = y; - HEAP32[sp + 40 >> 2] = a; - HEAPF32[sp + 48 >> 2] = b; - HEAP32[sp + 72 >> 2] = 0; - HEAP32[sp + 76 >> 2] = 0; - vars_w_stack$1(sp); - a = HEAP32[sp + 40 >> 2] | 0; - b = +HEAPF32[sp + 48 >> 2]; + a = x + y; + b = y * x; + a = c(1 + a); + a = c(2 + a); + a = c(3 + a); HEAP32[sp + 40 >> 2] = a; HEAPF32[sp + 48 >> 2] = b; HEAP32[sp + 64 >> 2] = 0; @@ -585,10 +582,9 @@ function mix$1(sp) { } function vars$0(sp) { sp = sp | 0; - var x = 0, y = +0; + var y = +0, x = 0; x = HEAP32[sp + 8 >> 2] | 0; y = +HEAPF32[sp + 16 >> 2]; - c(5 + (x + y)); c(6 + y * x); c(7 + (x + y)); c(8 + y * x); @@ -632,6 +628,7 @@ function vars_w_stack$0(sp) { var a = 0, b = +0; a = HEAP32[sp + 40 >> 2] | 0; b = +HEAPF32[sp + 48 >> 2]; + a = c(4 + a); a = c(5 + a); a = c(6 + a); b = c(7 + a); @@ -639,22 +636,6 @@ function vars_w_stack$0(sp) { HEAP32[sp + 40 >> 2] = a; HEAPF32[sp + 48 >> 2] = b; } -function vars_w_stack$1(sp) { - sp = sp | 0; - var a = 0, x = 0, y = +0, b = +0; - x = HEAP32[sp + 24 >> 2] | 0; - y = +HEAPF32[sp + 32 >> 2]; - a = HEAP32[sp + 40 >> 2] | 0; - b = +HEAPF32[sp + 48 >> 2]; - a = x + y; - b = y * x; - a = c(1 + a); - a = c(2 + a); - a = c(3 + a); - a = c(4 + a); - HEAP32[sp + 40 >> 2] = a; - HEAPF32[sp + 48 >> 2] = b; -} function chain$0(sp) { sp = sp | 0; var helper$0 = 0; diff --git a/tools/test-js-optimizer-si-output.js b/tools/test-js-optimizer-si-output.js new file mode 100644 index 00000000..9ef5171c --- /dev/null +++ b/tools/test-js-optimizer-si-output.js @@ -0,0 +1,197 @@ +function a() { + if (x ? y : 0) { + g(); + } + if (x) { + if (y) { + g(); + } else { + h(); + } + } + if (x) { + if (y) { + g(); + } + h(); + } + if (x) { + if (y) { + g(); + } + } else { + h(); + } + if (x) { + return; + if (y) { + g(); + } + } + if ((x ? y : 0) ? z : 0) { + g(); + } + if (x) { + return; + if (y ? z : 0) { + g(); + } + } + if (x ? y : 0) { + return; + if (z) { + g(); + } + } + if (x ? y : 0) { + if (z) { + g(); + } + f(); + } + if (x) { + if (y ? z : 0) { + g(); + } + f(); + } + if (x ? (f(), x = x + 2 | 0, y) : 0) { + g(); + } + if (x) { + f(); + x = x + 2 | 0; + return; + if (y) { + g(); + } + } + andNowForElses(); + if (x ? y : 0) { + f(); + } else { + label = 5; + } + if (x) { + if (y) { + f(); + } else { + label = 5; + } + } else { + label = 6; + } + if (x) { + if (y) { + f(); + } else { + label = 5; + } + } + if (x) { + if (y) { + f(); + } + } else { + label = 5; + } + if (x) { + a = 5; + if (y) { + f(); + } + } else { + label = 5; + } + fuseElses(); + if (x ? y : 0) { + f(); + } else { + a(); + } + if (x ? y : 0) { + f(); + } else { + label = 52; + } + if ((label | 0) == 62) { + label = 0; + a(); + } + if (x ? y : 0) { + f(); + } else { + a(); + } + while (1) { + if (x ? y : 0) { + f(); + } else { + label = 953; + } + if ((label | 0) == 953) { + a(); + } + } + if (x ? y : 0) { + label = 54; + } else { + label = 54; + } + if ((label | 0) == 54) { + label = 0; + a(); + } +} +function b() { + if (x) { + a(); + } else { + label = 5; + } + if ((label | 0) == 5) { + label = 0; + a(); + } +} +function c() { + label = x; + if (x ? y : 0) { + f(); + } else { + label = 151; + } + if ((label | 0) == 151) { + label = 0; + a(); + } +} +function d() { + if (x ? y : 0) { + f(); + } else { + label = 251; + } + if ((label | 0) == 251) { + label = 0; + a(); + } + if ((label | 0) == 251) { + a(); + } +} +function e() { + if (x ? y : 0) { + f(); + } else { + label = 351; + } + if ((label | 0) == 351) { + label = 0; + a(); + } + if ((label | 0) == x) { + a(); + } +} + diff --git a/tools/test-js-optimizer-si.js b/tools/test-js-optimizer-si.js new file mode 100644 index 00000000..04ceec4a --- /dev/null +++ b/tools/test-js-optimizer-si.js @@ -0,0 +1,258 @@ +function a() { + if (x) { + if (y) { + g(); + } + } + if (x) { + if (y) { + g(); + } else { + h(); + } + } + if (x) { + if (y) { + g(); + } + h(); + } + if (x) { + if (y) { + g(); + } + } else { + h(); + } + if (x) { + return; + if (y) { + g(); + } + } + if (x) { + if (y) { + if (z) { + g(); + } + } + } + if (x) { + return; + if (y) { + if (z) { + g(); + } + } + } + if (x) { + if (y) { + return; + if (z) { + g(); + } + } + } + if (x) { + if (y) { + if (z) { + g(); + } + f(); + } + } + if (x) { + if (y) { + if (z) { + g(); + } + } + f(); + } + if (x) { + f(); + x = x + 2 | 0; + if (y) { + g(); + } + } + if (x) { + f(); + x = x + 2 | 0; + return; + if (y) { + g(); + } + } + andNowForElses(); + if (x) { + if (y) { + f(); + } else { + label = 5; + } + } else { + label = 5; + } + if (x) { + if (y) { + f(); + } else { + label = 5; + } + } else { + label = 6; + } + if (x) { + if (y) { + f(); + } else { + label = 5; + } + } + if (x) { + if (y) { + f(); + } + } else { + label = 5; + } + if (x) { + a = 5; // do not commify me + if (y) { + f(); + } + } else { + label = 5; + } + fuseElses(); + if (x) { + if (y) { + f(); + } else { + label = 51; + } + } else { + label = 51; + } + if ((label|0) == 51) { + label = 0; + a(); + } + if (x) { + if (y) { + f(); + } else { + label = 52; + } + } else { + label = 52; + } + if ((label|0) == 62) { + label = 0; + a(); + } + if (x) { + if (y) { + f(); + } else { + label = 53; + } + } else { + label = 53; + } + if ((label|0) == 53) { + a(); + } + while (1) { + if (x) { + if (y) { + f(); + } else { + label = 953; + } + } else { + label = 953; + } + if ((label|0) == 953) { + a(); + } + } + if (x) { + if (y) { + label = 54; // extra label setting, cannot fuse here + } else { + label = 54; + } + } else { + label = 54; + } + if ((label|0) == 54) { + label = 0; + a(); + } +} +function b() { + if (x) { // will not be fused, since we did not eliminate with elses + a(); + } else { + label = 5; + } + if ((label|0) == 5) { + label = 0; + a(); + } +} +function c() { + label = x; // dynamic assign to label, suppresses label removal + if (x) { + if (y) { + f(); + } else { + label = 151; + } + } else { + label = 151; + } + if ((label|0) == 151) { + label = 0; + a(); + } +} +function d() { + if (x) { + if (y) { + f(); + } else { + label = 251; + } + } else { + label = 251; + } + if ((label|0) == 251) { + label = 0; + a(); + } + if ((label|0) == 251) { // extra check of label, suppresses label removal + a(); + } +} +function e() { + if (x) { + if (y) { + f(); + } else { + label = 351; + } + } else { + label = 351; + } + if ((label|0) == 351) { + label = 0; + a(); + } + if ((label|0) == x) { // dynamic check of label, suppresses label removal + a(); + } +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "c", "d"] |