summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xemcc16
-rw-r--r--emscripten-version.txt2
-rw-r--r--src/library.js11
-rw-r--r--src/library_egl.js6
-rw-r--r--src/library_html5.js47
-rw-r--r--src/library_openal.js27
-rw-r--r--src/library_sdl.js36
-rw-r--r--src/library_sockfs.js38
-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/settings.js19
-rw-r--r--system/include/emscripten/html5.h10
-rw-r--r--tests/openal_playback.cpp2
-rw-r--r--tests/test_benchmark.py55
-rw-r--r--tests/test_browser.py5
-rw-r--r--tests/test_html5_mouse.c159
-rw-r--r--tests/test_interactive.py3
-rw-r--r--tests/test_other.py97
-rw-r--r--tests/test_sockets.py53
-rw-r--r--tools/js-optimizer.js229
-rw-r--r--tools/test-js-optimizer-asm-outline1-output.js39
-rw-r--r--tools/test-js-optimizer-si-output.js197
-rw-r--r--tools/test-js-optimizer-si.js258
25 files changed, 1462 insertions, 96 deletions
diff --git a/emcc b/emcc
index e28ec767..4ba63f21 100755
--- a/emcc
+++ b/emcc
@@ -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"]