aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAlon Zakai <alonzakai@gmail.com>2013-09-12 14:47:17 -0700
committerAlon Zakai <alonzakai@gmail.com>2013-09-12 14:47:17 -0700
commit6010666be99cd0322babba1174cfbc65c776deb5 (patch)
treedad63b03b751394c169de61fbf2c195f4faf5344 /src
parent38890204ed1f5f8dd34cced7c42fc9cf42dccab5 (diff)
parentf9dff9b3f2e95b2ca8e5b8fd97538f301fd080fe (diff)
Merge branch 'incoming'
Diffstat (limited to 'src')
-rw-r--r--src/intertyper.js4
-rw-r--r--src/jsifier.js9
-rw-r--r--src/library_browser.js10
-rw-r--r--src/library_sdl.js39
-rw-r--r--src/modules.js7
-rw-r--r--src/postamble.js14
-rw-r--r--src/preamble.js72
-rw-r--r--src/proxyClient.js74
-rw-r--r--src/proxyWorker.js143
-rw-r--r--src/runtime.js6
-rw-r--r--src/settings.js3
11 files changed, 357 insertions, 24 deletions
diff --git a/src/intertyper.js b/src/intertyper.js
index ddb93d71..082fd993 100644
--- a/src/intertyper.js
+++ b/src/intertyper.js
@@ -708,13 +708,15 @@ function intertyper(data, sidePass, baseLineNums) {
item.ident = eatLLVMIdent(tokensLeft);
if (item.ident == 'asm') {
if (ASM_JS) {
- warnOnce('inline JS in asm.js mode can cause the code to no longer fall in the asm.js subset of JavaScript');
+ Types.hasInlineJS = true;
+ warnOnce('inline JavaScript (asm, EM_ASM) will cause the code to no longer fall in the asm.js subset of JavaScript, which can reduce performance - consider using emscripten_run_script');
}
assert(TARGET_LE32, 'inline js is only supported in le32');
// Inline assembly is just JavaScript that we paste into the code
item.intertype = 'value';
if (tokensLeft[0].text == 'sideeffect') tokensLeft.splice(0, 1);
item.ident = tokensLeft[0].text.substr(1, tokensLeft[0].text.length-2) || ';'; // use ; for empty inline assembly
+ assert((item.tokens[5].text.match(/=/g) || []).length <= 1, 'we only support at most 1 exported variable from inline js: ' + item.ident);
var i = 0;
var params = [], args = [];
splitTokenList(tokensLeft[3].item.tokens).map(function(element) {
diff --git a/src/jsifier.js b/src/jsifier.js
index 38f3bd5e..49f2c564 100644
--- a/src/jsifier.js
+++ b/src/jsifier.js
@@ -324,11 +324,13 @@ function JSify(data, functionsOnly, givenFunctions) {
assert(typeof constant === 'object');//, [typeof constant, JSON.stringify(constant), item.external]);
// This is a flattened object. We need to find its idents, so they can be assigned to later
+ var structTypes = null;
constant.forEach(function(value, i) {
if (needsPostSet(value)) { // ident, or expression containing an ident
+ if (!structTypes) structTypes = generateStructTypes(item.type);
ret.push({
intertype: 'GlobalVariablePostSet',
- JS: makeSetValue(makeGlobalUse(item.ident), i, value, 'i32', false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors
+ JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors
});
constant[i] = '0';
}
@@ -1831,7 +1833,7 @@ function JSify(data, functionsOnly, givenFunctions) {
}
if (CORRUPTION_CHECK) {
- assert(!ASM_JS); // cannot monkeypatch asm!
+ assert(!ASM_JS, 'corruption checker is not compatible with asm.js');
print(processMacros(read('corruptionCheck.js')));
}
if (HEADLESS) {
@@ -1841,6 +1843,9 @@ function JSify(data, functionsOnly, givenFunctions) {
print(read('headless.js').replace("'%s'", "'http://emscripten.org'").replace("'?%s'", "''").replace("'?%s'", "'/'").replace('%s,', 'null,').replace('%d', '0'));
print('}');
}
+ if (PROXY_TO_WORKER) {
+ print(read('proxyWorker.js'));
+ }
if (RUNTIME_TYPE_INFO) {
Types.cleanForRuntime();
print('Runtime.typeInfo = ' + JSON.stringify(Types.types));
diff --git a/src/library_browser.js b/src/library_browser.js
index 235ccc78..e4966e15 100644
--- a/src/library_browser.js
+++ b/src/library_browser.js
@@ -724,7 +724,15 @@ mergeInto(LibraryManager.library, {
Module['preMainLoop']();
}
- Runtime.dynCall('v', func);
+ try {
+ Runtime.dynCall('v', func);
+ } catch (e) {
+ if (e instanceof ExitStatus) {
+ return;
+ } else {
+ throw e;
+ }
+ }
if (Module['postMainLoop']) {
Module['postMainLoop']();
diff --git a/src/library_sdl.js b/src/library_sdl.js
index 9231f41b..9383834f 100644
--- a/src/library_sdl.js
+++ b/src/library_sdl.js
@@ -442,6 +442,14 @@ var LibrarySDL = {
}
// fall through
case 'keydown': case 'keyup': case 'keypress': case 'mousedown': case 'mouseup': case 'DOMMouseScroll': case 'mousewheel':
+ // If we preventDefault on keydown events, the subsequent keypress events
+ // won't fire. However, it's fine (and in some cases necessary) to
+ // preventDefault for keys that don't generate a character. Otherwise,
+ // preventDefault is the right thing to do in general.
+ if (event.type !== 'keydown' || (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */)) {
+ event.preventDefault();
+ }
+
if (event.type == 'DOMMouseScroll' || event.type == 'mousewheel') {
var button = (event.type == 'DOMMouseScroll' ? event.detail : -event.wheelDelta) > 0 ? 4 : 3;
var event2 = {
@@ -463,7 +471,6 @@ var LibrarySDL = {
// ignore extra ups, can happen if we leave the canvas while pressing down, then return,
// since we add a mouseup in that case
if (!SDL.DOMButtons[event.button]) {
- event.preventDefault();
return;
}
@@ -499,13 +506,6 @@ var LibrarySDL = {
SDL.savedKeydown = event;
}
- // If we preventDefault on keydown events, the subsequent keypress events
- // won't fire. However, it's fine (and in some cases necessary) to
- // preventDefault for keys that don't generate a character.
- if (event.type !== 'keydown' || (event.keyCode === 8 /* backspace */ || event.keyCode === 9 /* tab */)) {
- event.preventDefault();
- }
-
// Don't push keypress events unless SDL_StartTextInput has been called.
if (event.type !== 'keypress' || SDL.textInput) {
SDL.events.push(event);
@@ -755,7 +755,7 @@ var LibrarySDL = {
document.addEventListener("keydown", SDL.receiveEvent);
document.addEventListener("keyup", SDL.receiveEvent);
document.addEventListener("keypress", SDL.receiveEvent);
- document.addEventListener("blur", SDL.receiveEvent);
+ window.addEventListener("blur", SDL.receiveEvent);
document.addEventListener("visibilitychange", SDL.receiveEvent);
}
window.addEventListener("unload", SDL.receiveEvent);
@@ -883,6 +883,14 @@ var LibrarySDL = {
surfData.locked++;
if (surfData.locked > 1) return 0;
+ // Mark in C/C++-accessible SDL structure
+ // SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ...
+ // So we have fields all of the same size, and 5 of them before us.
+ // TODO: Use macros like in library.js
+ {{{ makeSetValue('surf', '5*Runtime.QUANTUM_SIZE', 'surfData.buffer', 'void*') }}};
+
+ if (surf == SDL.screen && Module.screenIsReadOnly && surfData.image) return 0;
+
surfData.image = surfData.ctx.getImageData(0, 0, surfData.width, surfData.height);
if (surf == SDL.screen) {
var data = surfData.image.data;
@@ -925,12 +933,6 @@ var LibrarySDL = {
}
}
- // Mark in C/C++-accessible SDL structure
- // SDL_Surface has the following fields: Uint32 flags, SDL_PixelFormat *format; int w, h; Uint16 pitch; void *pixels; ...
- // So we have fields all of the same size, and 5 of them before us.
- // TODO: Use macros like in library.js
- {{{ makeSetValue('surf', '5*Runtime.QUANTUM_SIZE', 'surfData.buffer', 'void*') }}};
-
return 0;
},
@@ -1271,6 +1273,11 @@ var LibrarySDL = {
return 1;
},
+ SDL_SetPalette__deps: ['SDL_SetColors'],
+ SDL_SetPalette: function(surf, flags, colors, firstColor, nColors) {
+ return _SDL_SetColors(surf, colors, firstColor, nColors);
+ },
+
SDL_MapRGB: function(fmt, r, g, b) {
// Canvas screens are always RGBA. We assume the machine is little-endian.
return r&0xff|(g&0xff)<<8|(b&0xff)<<16|0xff000000;
@@ -2276,7 +2283,7 @@ var LibrarySDL = {
SDL_CondWaitTimeout: function() { throw 'SDL_CondWaitTimeout: TODO' },
SDL_WM_IconifyWindow: function() { throw 'SDL_WM_IconifyWindow TODO' },
- Mix_SetPostMix: function() { throw 'Mix_SetPostMix: TODO' },
+ Mix_SetPostMix: function() { Runtime.warnOnce('Mix_SetPostMix: TODO') },
Mix_QuerySpec: function() { throw 'Mix_QuerySpec: TODO' },
Mix_FadeInChannelTimed: function() { throw 'Mix_FadeInChannelTimed' },
Mix_FadeOutChannel: function() { throw 'Mix_FadeOutChannel' },
diff --git a/src/modules.js b/src/modules.js
index 373e60d9..1a931572 100644
--- a/src/modules.js
+++ b/src/modules.js
@@ -227,6 +227,8 @@ var Types = {
needAnalysis: {}, // Types noticed during parsing, that need analysis
+ hasInlineJS: false, // whether the program has inline JS anywhere
+
// Set to true if we actually use precise i64 math: If PRECISE_I64_MATH is set, and also such math is actually
// needed (+,-,*,/,% - we do not need it for bitops), or PRECISE_I64_MATH is 2 (forced)
preciseI64MathUsed: (PRECISE_I64_MATH == 2)
@@ -467,7 +469,10 @@ var PassManager = {
}));
} else if (phase == 'funcs') {
print('\n//FORWARDED_DATA:' + JSON.stringify({
- Types: { preciseI64MathUsed: Types.preciseI64MathUsed },
+ Types: {
+ hasInlineJS: Types.hasInlineJS,
+ preciseI64MathUsed: Types.preciseI64MathUsed
+ },
Functions: {
blockAddresses: Functions.blockAddresses,
indexedFunctions: Functions.indexedFunctions,
diff --git a/src/postamble.js b/src/postamble.js
index df844121..94b60288 100644
--- a/src/postamble.js
+++ b/src/postamble.js
@@ -10,8 +10,8 @@ ExitStatus.prototype = new Error();
ExitStatus.prototype.constructor = ExitStatus;
var initialStackTop;
-
var preloadStartTime = null;
+var calledMain = false;
Module['callMain'] = Module.callMain = function callMain(args) {
assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on __ATMAIN__)');
@@ -70,6 +70,8 @@ Module['callMain'] = Module.callMain = function callMain(args) {
} else {
throw e;
}
+ } finally {
+ calledMain = true;
}
}
@@ -126,7 +128,15 @@ function exit(status) {
// exit the runtime
exitRuntime();
-
+
+ // TODO We should handle this differently based on environment.
+ // In the browser, the best we can do is throw an exception
+ // to halt execution, but in node we could process.exit and
+ // I'd imagine SM shell would have something equivalent.
+ // This would let us set a proper exit status (which
+ // would be great for checking test exit statuses).
+ // https://github.com/kripken/emscripten/issues/1371
+
// throw an exception to halt the current execution
throw new ExitStatus(status);
}
diff --git a/src/preamble.js b/src/preamble.js
index 227b3043..abcd1c67 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -569,6 +569,78 @@ function Pointer_stringify(ptr, /* optional */ length) {
}
Module['Pointer_stringify'] = Pointer_stringify;
+// Given a pointer 'ptr' to a null-terminated UTF16LE-encoded string in the emscripten HEAP, returns
+// a copy of that string as a Javascript String object.
+function UTF16ToString(ptr) {
+ var i = 0;
+
+ var str = '';
+ while (1) {
+ var codeUnit = {{{ makeGetValue('ptr', 'i*2', 'i16') }}};
+ if (codeUnit == 0)
+ return str;
+ ++i;
+ // fromCharCode constructs a character from a UTF-16 code unit, so we can pass the UTF16 string right through.
+ str += String.fromCharCode(codeUnit);
+ }
+}
+Module['UTF16ToString'] = UTF16ToString;
+
+// Copies the given Javascript String object 'str' to the emscripten HEAP at address 'outPtr',
+// null-terminated and encoded in UTF16LE form. The copy will require at most (str.length*2+1)*2 bytes of space in the HEAP.
+function stringToUTF16(str, outPtr) {
+ for(var i = 0; i < str.length; ++i) {
+ // charCodeAt returns a UTF-16 encoded code unit, so it can be directly written to the HEAP.
+ var codeUnit = str.charCodeAt(i); // possibly a lead surrogate
+ {{{ makeSetValue('outPtr', 'i*2', 'codeUnit', 'i16') }}}
+ }
+ // Null-terminate the pointer to the HEAP.
+ {{{ makeSetValue('outPtr', 'str.length*2', 0, 'i16') }}}
+}
+Module['stringToUTF16'] = stringToUTF16;
+
+// Given a pointer 'ptr' to a null-terminated UTF32LE-encoded string in the emscripten HEAP, returns
+// a copy of that string as a Javascript String object.
+function UTF32ToString(ptr) {
+ var i = 0;
+
+ var str = '';
+ while (1) {
+ var utf32 = {{{ makeGetValue('ptr', 'i*4', 'i32') }}};
+ if (utf32 == 0)
+ return str;
+ ++i;
+ // Gotcha: fromCharCode constructs a character from a UTF-16 encoded code (pair), not from a Unicode code point! So encode the code point to UTF-16 for constructing.
+ if (utf32 >= 0x10000) {
+ var ch = utf32 - 0x10000;
+ str += String.fromCharCode(0xD800 | (ch >> 10), 0xDC00 | (ch & 0x3FF));
+ } else {
+ str += String.fromCharCode(utf32);
+ }
+ }
+}
+Module['UTF32ToString'] = UTF32ToString;
+
+// Copies the given Javascript String object 'str' to the emscripten HEAP at address 'outPtr',
+// null-terminated and encoded in UTF32LE form. The copy will require at most (str.length+1)*4 bytes of space in the HEAP,
+// but can use less, since str.length does not return the number of characters in the string, but the number of UTF-16 code units in the string.
+function stringToUTF32(str, outPtr) {
+ var iChar = 0;
+ for(var iCodeUnit = 0; iCodeUnit < str.length; ++iCodeUnit) {
+ // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code unit, not a Unicode code point of the character! We must decode the string to UTF-32 to the heap.
+ var codeUnit = str.charCodeAt(iCodeUnit); // possibly a lead surrogate
+ if (codeUnit >= 0xD800 && codeUnit <= 0xDFFF) {
+ var trailSurrogate = str.charCodeAt(++iCodeUnit);
+ codeUnit = 0x10000 + ((codeUnit & 0x3FF) << 10) | (trailSurrogate & 0x3FF);
+ }
+ {{{ makeSetValue('outPtr', 'iChar*4', 'codeUnit', 'i32') }}}
+ ++iChar;
+ }
+ // Null-terminate the pointer to the HEAP.
+ {{{ makeSetValue('outPtr', 'iChar*4', 0, 'i32') }}}
+}
+Module['stringToUTF32'] = stringToUTF32;
+
// Memory management
var PAGE_SIZE = 4096;
diff --git a/src/proxyClient.js b/src/proxyClient.js
new file mode 100644
index 00000000..04f7ed11
--- /dev/null
+++ b/src/proxyClient.js
@@ -0,0 +1,74 @@
+
+// proxy to/from worker
+
+Module.ctx = Module.canvas.getContext('2d');
+
+var worker = new Worker('{{{ filename }}}.js');
+
+worker.onmessage = function(event) {
+ var data = event.data;
+ switch (data.target) {
+ case 'stdout': {
+ Module.print(data.content);
+ break;
+ }
+ case 'stderr': {
+ Module.printErr(data.content);
+ break;
+ }
+ case 'window': {
+ window[data.method]();
+ break;
+ }
+ case 'canvas': {
+ switch (data.op) {
+ case 'resize': {
+ Module.canvas.width = data.width;
+ Module.canvas.height = data.height;
+ Module.canvasData = Module.ctx.getImageData(0, 0, data.width, data.height);
+ postMessage({ target: 'canvas', boundingClientRect: Module.canvas.getBoundingClientRect() });
+ break;
+ }
+ case 'render': {
+ Module.canvasData.data.set(data.image.data);
+ Module.ctx.putImageData(Module.canvasData, 0, 0);
+ break;
+ }
+ default: throw 'eh?';
+ }
+ break;
+ }
+ default: throw 'what?';
+ }
+};
+
+function cloneEvent(event) {
+ var ret = {};
+ for (var x in event) {
+ if (x == x.toUpperCase()) continue;
+ var prop = event[x];
+ if (typeof prop === 'number' || typeof prop === 'string') ret[x] = prop;
+ }
+ return ret;
+};
+
+['keydown', 'keyup', 'keypress', 'blur', 'visibilitychange'].forEach(function(event) {
+ document.addEventListener(event, function(event) {
+ worker.postMessage({ target: 'document', event: cloneEvent(event) });
+ event.preventDefault();
+ });
+});
+
+['unload'].forEach(function(event) {
+ window.addEventListener(event, function(event) {
+ worker.postMessage({ target: 'window', event: cloneEvent(event) });
+ });
+});
+
+['mousedown', 'mouseup', 'mousemove', 'DOMMouseScroll', 'mousewheel', 'mouseout'].forEach(function(event) {
+ Module.canvas.addEventListener(event, function(event) {
+ worker.postMessage({ target: 'canvas', event: cloneEvent(event) });
+ event.preventDefault();
+ }, true);
+});
+
diff --git a/src/proxyWorker.js b/src/proxyWorker.js
new file mode 100644
index 00000000..29b2528d
--- /dev/null
+++ b/src/proxyWorker.js
@@ -0,0 +1,143 @@
+
+function EventListener() {
+ this.listeners = {};
+
+ this.addEventListener = function(event, func) {
+ if (!this.listeners[event]) this.listeners[event] = [];
+ this.listeners[event].push(func);
+ };
+
+ this.fireEvent = function(event) {
+ event.preventDefault = function(){};
+
+ if (event.type in this.listeners) {
+ this.listeners[event.type].forEach(function(listener) {
+ listener(event);
+ });
+ }
+ };
+};
+
+var window = this;
+var windowExtra = new EventListener();
+for (var x in windowExtra) window[x] = windowExtra[x];
+
+window.close = function() {
+ postMessage({ target: 'window', method: 'close' });
+};
+
+var document = new EventListener();
+
+document.createElement = function(what) {
+ switch(what) {
+ case 'canvas': {
+ var canvas = new EventListener();
+ canvas.ensureData = function() {
+ if (!canvas.data || canvas.data.width !== canvas.width || canvas.data.height !== canvas.height) {
+ canvas.data = {
+ width: canvas.width,
+ height: canvas.height,
+ data: new Uint8Array(canvas.width*canvas.height*4)
+ };
+ postMessage({ target: 'canvas', op: 'resize', width: canvas.width, height: canvas.height });
+ }
+ };
+ canvas.getContext = function(type) {
+ assert(type == '2d');
+ return {
+ getImageData: function(x, y, w, h) {
+ assert(x == 0 && y == 0 && w == canvas.width && h == canvas.height);
+ canvas.ensureData();
+ return {
+ width: canvas.data.width,
+ height: canvas.data.height,
+ data: new Uint8Array(canvas.data.data) // TODO: can we avoid this copy?
+ };
+ },
+ putImageData: function(image, x, y) {
+ canvas.ensureData();
+ assert(x == 0 && y == 0 && image.width == canvas.width && image.height == canvas.height);
+ canvas.data.data.set(image.data); // TODO: can we avoid this copy?
+ postMessage({ target: 'canvas', op: 'render', image: canvas.data });
+ }
+ };
+ };
+ canvas.boundingClientRect = {};
+ canvas.getBoundingClientRect = function() {
+ return {
+ width: canvas.boundingClientRect.width,
+ height: canvas.boundingClientRect.height,
+ top: canvas.boundingClientRect.top,
+ left: canvas.boundingClientRect.left,
+ bottom: canvas.boundingClientRect.bottom,
+ right: canvas.boundingClientRect.right
+ };
+ };
+ return canvas;
+ }
+ default: throw 'document.createElement ' + what;
+ }
+};
+
+var console = {
+ log: function(x) {
+ Module.printErr(x);
+ }
+};
+
+Module.canvas = document.createElement('canvas');
+
+Module.setStatus = function(){};
+
+Module.print = function(x) {
+ postMessage({ target: 'stdout', content: x });
+};
+Module.printErr = function(x) {
+ postMessage({ target: 'stderr', content: x });
+};
+
+// buffer messages until the program starts to run
+
+var messageBuffer = null;
+
+function messageResender() {
+ if (calledMain) {
+ assert(messageBuffer && messageBuffer.length > 0);
+ messageBuffer.forEach(function(message) {
+ onmessage(message);
+ });
+ messageBuffer = null;
+ } else {
+ setTimeout(messageResender, 100);
+ }
+}
+
+onmessage = function(message) {
+ if (!calledMain) {
+ if (!messageBuffer) {
+ messageBuffer = [];
+ setTimeout(messageResender, 100);
+ }
+ messageBuffer.push(message);
+ }
+ switch (message.data.target) {
+ case 'document': {
+ document.fireEvent(message.data.event);
+ break;
+ }
+ case 'window': {
+ window.fireEvent(message.data.event);
+ break;
+ }
+ case 'canvas': {
+ if (message.data.event) {
+ Module.canvas.fireEvent(message.data.event);
+ } else if (message.data.boundingClientRect) {
+ Module.canvas.boundingClientRect = message.data.boundingClientRect;
+ } else throw 'ey?';
+ break;
+ }
+ default: throw 'wha? ' + message.data.target;
+ }
+};
+
diff --git a/src/runtime.js b/src/runtime.js
index 33088ad9..6b1afd80 100644
--- a/src/runtime.js
+++ b/src/runtime.js
@@ -214,7 +214,11 @@ var Runtime = {
// and it adds no size
// XXX this happens in java-nbody for example... assert(index === type.fields.length, 'zero-length in the middle!');
size = 0;
- alignSize = type.alignSize || QUANTUM_SIZE;
+ if (Types.types[field]) {
+ alignSize = Runtime.getAlignSize(null, Types.types[field].alignSize);
+ } else {
+ alignSize = type.alignSize || QUANTUM_SIZE;
+ }
} else {
size = Types.types[field].flatSize;
alignSize = Runtime.getAlignSize(null, Types.types[field].alignSize);
diff --git a/src/settings.js b/src/settings.js
index 79c54c2b..1ef0cd58 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -343,6 +343,9 @@ var RUNTIME_LINKED_LIBS = []; // If this is a main file (BUILD_AS_SHARED_LIB ==
// linked libraries can break things.
var BUILD_AS_WORKER = 0; // If set to 1, this is a worker library, a special kind of library
// that is run in a worker. See emscripten.h
+var PROXY_TO_WORKER = 0; // If set to 1, we build the project into a js file that will run
+ // in a worker, and generate an html file that proxies input and
+ // output to/from it.
var LINKABLE = 0; // If set to 1, this file can be linked with others, either as a shared
// library or as the main file that calls a shared library. To enable that,
// we will not internalize all symbols and cull the unused ones, in other