aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/closure-externs.js60
-rw-r--r--src/library.js26
-rw-r--r--src/library_browser.js55
-rw-r--r--src/library_egl.js6
-rw-r--r--src/library_glfw.js2
-rw-r--r--src/library_html5.js61
-rw-r--r--src/library_openal.js27
-rw-r--r--src/library_sdl.js13
-rw-r--r--src/library_sockfs.js38
-rw-r--r--src/postamble.js8
-rw-r--r--src/preamble.js17
-rw-r--r--src/relooper/Relooper.cpp53
-rw-r--r--src/relooper/Relooper.h8
-rw-r--r--src/relooper/test.cpp123
-rw-r--r--src/relooper/test.txt65
-rw-r--r--src/runtime.js5
-rw-r--r--src/settings.js20
-rw-r--r--src/shell.js4
18 files changed, 533 insertions, 58 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..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;
},
@@ -8915,10 +8926,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 ac43f3b4..3d96a693 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -1811,9 +1811,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!';
@@ -1856,7 +1856,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.
@@ -1929,7 +1934,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/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..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);