diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/closure-externs.js | 60 | ||||
-rw-r--r-- | src/library.js | 22 | ||||
-rw-r--r-- | src/library_browser.js | 55 | ||||
-rw-r--r-- | src/library_egl.js | 6 | ||||
-rw-r--r-- | src/library_glfw.js | 2 | ||||
-rw-r--r-- | src/library_html5.js | 61 | ||||
-rw-r--r-- | src/library_openal.js | 27 | ||||
-rw-r--r-- | src/library_sdl.js | 13 | ||||
-rw-r--r-- | src/postamble.js | 8 | ||||
-rw-r--r-- | src/preamble.js | 17 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 53 | ||||
-rw-r--r-- | src/relooper/Relooper.h | 8 | ||||
-rw-r--r-- | src/relooper/test.cpp | 92 | ||||
-rw-r--r-- | src/relooper/test.txt | 47 | ||||
-rw-r--r-- | src/runtime.js | 5 | ||||
-rw-r--r-- | src/settings.js | 1 | ||||
-rw-r--r-- | src/shell.js | 4 |
17 files changed, 427 insertions, 54 deletions
diff --git a/src/closure-externs.js b/src/closure-externs.js index a82aa669..fe6d84aa 100644 --- a/src/closure-externs.js +++ b/src/closure-externs.js @@ -108,3 +108,63 @@ var flags = {}; flags.binary; +/** + * @fileoverview Definitions for W3C's Gamepad specification. + * @see http://www.w3.org/TR/gamepad/ + * @externs + */ + +/** + * @typedef {{id: string, index: number, timestamp: number, axes: Array.<number>, buttons: Array.<number>}} + */ +var Gamepad; + +/** +* @type {Array.<number>} +*/ +Gamepad.buttons; + +/** +* @type {Array.<number>} +*/ +Gamepad.axes; + +/** +* @type {number} +*/ +Gamepad.index; + +/** +* @type {string} +*/ +Gamepad.id; + +/** +* @type {number} +*/ +Gamepad.timestamp; + +/** + * @return {Array.<Gamepad>} + */ +navigator.getGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.webkitGetGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.webkitGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.mozGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.gamepads = function() {}; diff --git a/src/library.js b/src/library.js index 2c3d5e03..e711ee98 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 // ========================================================================== @@ -8915,10 +8922,19 @@ LibraryManager.library = { emscripten_get_callstack_js: function(flags) { var err = new Error(); if (!err.stack) { - Runtime.warnOnce('emscripten_get_callstack_js is not supported on this browser!'); - return ''; + // IE10+ special cases: It does have callstack info, but it is only populated if an Error object is thrown, + // so try that as a special-case. + try { + throw new Error(0); + } catch(e) { + err = e; + } + if (!err.stack) { + Runtime.warnOnce('emscripten_get_callstack_js is not supported on this browser!'); + return ''; + } } - var callstack = new Error().stack.toString(); + var callstack = err.stack.toString(); // Find the symbols in the callstack that corresponds to the functions that report callstack information, and remove everyhing up to these from the output. var iThisFunc = callstack.lastIndexOf('_emscripten_log'); diff --git a/src/library_browser.js b/src/library_browser.js index a280d1b2..0808b9f0 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -723,8 +723,59 @@ mergeInto(LibraryManager.library, { // PROGRESS http.onprogress = function http_onprogress(e) { - var percentComplete = (e.position / e.totalSize)*100; - if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]); + if (e.lengthComputable || (e.lengthComputable === undefined && e.totalSize != 0)) { + var percentComplete = (e.position / e.totalSize)*100; + if (onprogress) Runtime.dynCall('vii', onprogress, [arg, percentComplete]); + } + }; + + // Useful because the browser can limit the number of redirection + try { + if (http.channel instanceof Ci.nsIHttpChannel) + http.channel.redirectionLimit = 0; + } catch (ex) { /* whatever */ } + + if (_request == "POST") { + //Send the proper header information along with the request + http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + http.setRequestHeader("Content-length", _param.length); + http.setRequestHeader("Connection", "close"); + http.send(_param); + } else { + http.send(null); + } + }, + + emscripten_async_wget2_data: function(url, request, param, arg, free, onload, onerror, onprogress) { + var _url = Pointer_stringify(url); + var _request = Pointer_stringify(request); + var _param = Pointer_stringify(param); + + var http = new XMLHttpRequest(); + http.open(_request, _url, true); + http.responseType = 'arraybuffer'; + + // LOAD + http.onload = function http_onload(e) { + if (http.status == 200 || _url.substr(0,4).toLowerCase() != "http") { + var byteArray = new Uint8Array(http.response); + var buffer = _malloc(byteArray.length); + HEAPU8.set(byteArray, buffer); + if (onload) Runtime.dynCall('viii', onload, [arg, buffer, byteArray.length]); + if (free) _free(buffer); + } else { + if (onerror) Runtime.dynCall('viii', onerror, [arg, http.status, http.statusText]); + } + }; + + // ERROR + http.onerror = function http_onerror(e) { + if (onerror) Runtime.dynCall('viii', onerror, [arg, http.status, http.statusText]); + }; + + // PROGRESS + http.onprogress = function http_onprogress(e) { + if (onprogress) Runtime.dynCall('viii', onprogress, [arg, e.loaded, e.lengthComputable || e.lengthComputable === undefined ? e.total : 0]); }; // Useful because the browser can limit the number of redirection 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_glfw.js b/src/library_glfw.js index b54205ad..e5782900 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -51,7 +51,7 @@ var LibraryGLFW = { DOMToGLFWKeyCode: function(keycode) { switch (keycode) { case 0x09: return 295 ; //DOM_VK_TAB -> GLFW_KEY_TAB - case 0x1B: return 255 ; //DOM_VK_ESCAPE -> GLFW_KEY_ESC + case 0x1B: return 257 ; //DOM_VK_ESCAPE -> GLFW_KEY_ESC case 0x6A: return 313 ; //DOM_VK_MULTIPLY -> GLFW_KEY_KP_MULTIPLY case 0x6B: return 315 ; //DOM_VK_ADD -> GLFW_KEY_KP_ADD case 0x6D: return 314 ; //DOM_VK_SUBTRACT -> GLFW_KEY_KP_SUBTRACT diff --git a/src/library_html5.js b/src/library_html5.js index b17a0d4d..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, @@ -118,8 +123,11 @@ var LibraryJSEvents = { // Stores objects representing each currently registered JS event handler. eventHandlers: [], + 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); }, @@ -139,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(); @@ -177,11 +186,9 @@ var LibraryJSEvents = { } }; - var isInternetExplorer = (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0); - var eventHandler = { target: JSEvents.findEventTarget(target), - allowsDeferredCalls: 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) { @@ -230,6 +239,8 @@ var LibraryJSEvents = { handlerFunc: handlerFunc, useCapture: useCapture }; + // In IE, mousedown events don't either allow deferred calls to be run! + if (JSEvents.isInternetExplorer() && eventTypeString == 'mousedown') eventHandler.allowsDeferredCalls = false; JSEvents.registerOrRemoveHandler(eventHandler); }, @@ -237,13 +248,27 @@ 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') }}}; - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'e.deltaY', 'double') }}}; - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, 'e.deltaZ', 'double') }}}; - {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, 'e.deltaMode', 'i32') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaX, 'e["deltaX"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaY, 'e["deltaY"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaZ, 'e["deltaZ"]', 'double') }}}; + {{{ makeSetValue('JSEvents.wheelEvent', C_STRUCTS.EmscriptenWheelEvent.deltaMode, 'e["deltaMode"]', 'i32') }}}; + var shouldCancel = Runtime.dynCall('iiii', callbackfunc, [eventTypeId, JSEvents.wheelEvent, userData]); + if (shouldCancel) { + 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(); @@ -255,7 +280,7 @@ var LibraryJSEvents = { allowsDeferredCalls: true, eventTypeString: eventTypeString, callbackfunc: callbackfunc, - handlerFunc: handlerFunc, + handlerFunc: (eventTypeString == 'wheel') ? wheelHandlerFunc : mouseWheelHandlerFunc, useCapture: useCapture }; JSEvents.registerOrRemoveHandler(eventHandler); @@ -918,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 654d60f8..2606bafc 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -1765,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!'; @@ -1810,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. @@ -1883,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/postamble.js b/src/postamble.js index 603b330c..b90049bc 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -48,10 +48,6 @@ Module['callMain'] = Module.callMain = function callMain(args) { args = args || []; - if (ENVIRONMENT_IS_WEB && preloadStartTime !== null) { - Module.printErr('preload time: ' + (Date.now() - preloadStartTime) + ' ms'); - } - ensureInitRuntime(); var argc = args.length+1; @@ -130,6 +126,10 @@ function run(args) { preMain(); + if (ENVIRONMENT_IS_WEB && preloadStartTime !== null) { + Module.printErr('pre-main prep time: ' + (Date.now() - preloadStartTime) + ' ms'); + } + if (Module['_main'] && shouldRunNow) { Module['callMain'](args); } diff --git a/src/preamble.js b/src/preamble.js index 28a1dcbc..89ab5026 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -187,16 +187,16 @@ function SAFE_HEAP_STORE(dest, value, bytes, isFloat) { Module.print('SAFE_HEAP store: ' + [dest, value, bytes, isFloat]); #endif assert(dest > 0, 'segmentation fault'); - assert(dest % bytes === 0); - assert(dest < Math.max(DYNAMICTOP, STATICTOP)); + assert(dest % bytes === 0, 'alignment error'); + assert(dest < Math.max(DYNAMICTOP, STATICTOP), 'segmentation fault (high)'); assert(DYNAMICTOP <= TOTAL_MEMORY); setValue(dest, value, getSafeHeapType(bytes, isFloat), 1); } function SAFE_HEAP_LOAD(dest, bytes, isFloat, unsigned) { assert(dest > 0, 'segmentation fault'); - assert(dest % bytes === 0); - assert(dest < Math.max(DYNAMICTOP, STATICTOP)); + assert(dest % bytes === 0, 'alignment error'); + assert(dest < Math.max(DYNAMICTOP, STATICTOP), 'segmentation fault (high)'); assert(DYNAMICTOP <= TOTAL_MEMORY); var type = getSafeHeapType(bytes, isFloat); var ret = getValue(dest, type, 1); @@ -805,7 +805,14 @@ function demangle(func) { } } if (!allowVoid && list.length === 1 && list[0] === 'void') list = []; // avoid (void) - return rawList ? list : ret + flushList(); + if (rawList) { + if (ret) { + list.push(ret + '?'); + } + return list; + } else { + return ret + flushList(); + } } try { // Special-case the entry point, since its name differs from other name mangling. 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..78de9e64 100644 --- a/src/relooper/test.cpp +++ b/src/relooper/test.cpp @@ -312,7 +312,97 @@ 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()); } } diff --git a/src/relooper/test.txt b/src/relooper/test.txt index 82b02ad7..7d79e037 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,49 @@ } break; } + case 3: { + // block C + break; + } + } + } + + + +-- If chain (optimized) -- + + // block A + if (a == 10) { + // block B + } + // block C + + + +-- If chain |