diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/intertyper.js | 4 | ||||
-rw-r--r-- | src/jsifier.js | 9 | ||||
-rw-r--r-- | src/library_browser.js | 10 | ||||
-rw-r--r-- | src/library_sdl.js | 39 | ||||
-rw-r--r-- | src/modules.js | 7 | ||||
-rw-r--r-- | src/postamble.js | 14 | ||||
-rw-r--r-- | src/preamble.js | 72 | ||||
-rw-r--r-- | src/proxyClient.js | 74 | ||||
-rw-r--r-- | src/proxyWorker.js | 143 | ||||
-rw-r--r-- | src/runtime.js | 6 | ||||
-rw-r--r-- | src/settings.js | 3 |
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 |