diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/jsifier.js | 34 | ||||
-rw-r--r-- | src/library.js | 87 | ||||
-rw-r--r-- | src/library_sdl.js | 20 | ||||
-rw-r--r-- | src/modules.js | 6 | ||||
-rw-r--r-- | src/parseTools.js | 36 | ||||
-rw-r--r-- | src/preamble.js | 30 | ||||
-rw-r--r-- | src/settings.js | 9 |
7 files changed, 167 insertions, 55 deletions
diff --git a/src/jsifier.js b/src/jsifier.js index 9c34ddf1..bd432001 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -42,6 +42,7 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { substrate = new Substrate('JSifyer'); var GLOBAL_VARIABLES = !mainPass ? givenGlobalVariables : data.globalVariables; + Variables.globals = GLOBAL_VARIABLES; Functions.currFunctions = !mainPass ? givenFunctions.currFunctions : {}; Functions.currExternalFunctions = !mainPass ? givenFunctions.currExternalFunctions : {}; @@ -299,6 +300,7 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { substrate.addActor('FunctionStub', { processItem: function(item) { var ret = [item]; + if (IGNORED_FUNCTIONS.indexOf(item.ident) >= 0) return null; var shortident = item.ident.substr(1); if (BUILD_AS_SHARED_LIB) { // Shared libraries reuse the runtime of their parents. @@ -409,6 +411,7 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { funcs: {}, seen: {}, processItem: function(item) { + if (IGNORED_FUNCTIONS.indexOf(item.ident) >= 0) return null; if (this.seen[item.__uid__]) return null; if (item.intertype == 'function') { this.funcs[item.ident] = item; @@ -831,28 +834,13 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { // We cannot compile assembly. See comment in intertyper.js:'Call' assert(ident != 'asm', 'Inline assembly cannot be compiled to JavaScript!'); - // Special cases - if (ident == '_llvm_va_start') { - // varargs - we received a pointer to the varargs as a final 'extra' parameter - var data = 'arguments[' + Framework.currItem.funcData.ident + '.length]'; - return makeSetValue(params[0].ident, 0, data, 'void*'); - } else if (ident == '_llvm_va_end') { - return ';'; - } else if (ident == '_EMSCRIPTEN_COMMENT') { - var param = finalizeParam(params[0]); - if (param.indexOf('CHECK_OVERFLOW') >= 0) { - param = param.split('(')[1].split(',')[0]; - } - return '// ' + GLOBAL_VARIABLES[param].value.text.replace('\\00', '') + ' '; - } - + var shortident = LibraryManager.getRootIdent(ident.slice(1)) || ident.slice(1); // ident may not be in library, if all there is is ident__inline var func = Functions.currFunctions[ident] || Functions.currExternalFunctions[ident]; - var args = []; var argsTypes = []; var varargs = []; var varargsTypes = []; - var useJSArgs = (ident.slice(1) + '__jsargs') in LibraryManager.library; + var useJSArgs = (shortident + '__jsargs') in LibraryManager.library; params.forEach(function(param, i) { var val = finalizeParam(param); @@ -878,11 +866,21 @@ function JSify(data, functionsOnly, givenFunctions, givenGlobalVariables) { varargs = makePointer('[' + varargs + ']', 0, 'ALLOC_STACK', varargsTypes); } + args = args.concat(varargs); + var argsText = args.join(', '); + + // Inline if either we inline whenever we can (and we can), or if there is no noninlined version + var inline = LibraryManager.library[shortident + '__inline']; + var nonInlined = shortident in LibraryManager.library; + if (inline && (INLINE_LIBRARY_FUNCS || !nonInlined)) { + return inline.apply(null, args); // Warning: inlining does not prevent recalculation of the arguments. They should be simple identifiers + } + if (getVarData(funcData, ident)) { ident = 'FUNCTION_TABLE[' + ident + ']'; } - return ident + '(' + args.concat(varargs).join(', ') + ')'; + return ident + '(' + args.join(', ') + ')'; } makeFuncLineActor('getelementptr', function(item) { return finalizeLLVMFunctionCall(item) }); makeFuncLineActor('call', function(item) { diff --git a/src/library.js b/src/library.js index c753422d..faa86c68 100644 --- a/src/library.js +++ b/src/library.js @@ -24,7 +24,7 @@ LibraryManager.library = { _impure_ptr: 0, $FS__deps: ['$ERRNO_CODES', '__setErrNo', 'stdin', 'stdout', 'stderr', '_impure_ptr'], - $FS__postset: 'FS.init();', + $FS__postset: 'FS.init(); __ATEXIT__.push({ func: function() { FS.quit() } });', $FS: { // The path to the current folder. currentPath: '/', @@ -246,8 +246,7 @@ LibraryManager.library = { // Makes sure a file's contents are loaded. Returns whether the file has // been loaded successfully. No-op for files that have been loaded already. forceLoadFile: function(obj) { - if (obj.isDevice || obj.isFolder || obj.link || - 'contents' in obj) return true; + if (obj.isDevice || obj.isFolder || obj.link || obj.contents) return true; var success = true; if (typeof XMLHttpRequest !== 'undefined') { // Browser. @@ -323,19 +322,6 @@ LibraryManager.library = { return input.cache.shift(); }; if (!output) output = function(val) { - if (!output.printer) { - if (typeof print == 'function') { - // Either console or custom print function defined. - output.printer = print; - } else if (console && typeof console.log == 'function') { - // Browser-like environment with a console. - output.printer = console.log; - } else { - // Fallback to a harmless no-op. - output.printer = function() {}; - } - } - if (!output.buffer) output.buffer = []; if (val === null || val === '\n'.charCodeAt(0)) { output.printer(output.buffer.join('')); output.buffer = []; @@ -343,6 +329,8 @@ LibraryManager.library = { output.buffer.push(String.fromCharCode(val)); } }; + if (!output.printer) output.printer = print; + if (!output.buffer) output.buffer = []; if (!error) error = output; // Create the temporary folder. @@ -403,6 +391,12 @@ LibraryManager.library = { // Once initialized, permissions start having effect. FS.ignorePermissions = false; + }, + + quit: function() { + // Flush any partially-printed lines in stdout and stderr + if (FS.streams[2].object.output.buffer.length > 0) FS.streams[2].object.output('\n'.charCodeAt(0)); + if (FS.streams[3].object.output.buffer.length > 0) FS.streams[3].object.output('\n'.charCodeAt(0)); } }, @@ -3353,6 +3347,7 @@ LibraryManager.library = { qsort__deps: ['memcpy'], qsort: function(base, num, size, comparator) { + if (num == 0 || size == 0) return; // forward calls to the JavaScript sort method // first, sort the items logically comparator = FUNCTION_TABLE[comparator]; @@ -3572,13 +3567,21 @@ LibraryManager.library = { // string.h // ========================================================================== + memcpy__inline: function (dest, src, num, idunno) { + var ret = ''; +#if ASSERTIONS + ret += "assert(" + num + " % 1 === 0, 'memcpy given ' + " + num + " + ' bytes to copy. Problem with QUANTUM_SIZE=1 corrections perhaps?');"; +#endif + ret += makeCopyValues(dest, src, num, 'null'); + return ret; + }, memcpy: function (dest, src, num, idunno) { #if ASSERTIONS assert(num % 1 === 0, 'memcpy given ' + num + ' bytes to copy. Problem with QUANTUM_SIZE=1 corrections perhaps?'); #endif - // || 0, since memcpy sometimes copies uninitialized areas XXX: Investigate why initializing alloc'ed memory does not fix that too - {{{ makeCopyValues('dest', 'src', 'num', 'null', ' || 0') }}}; + {{{ makeCopyValues('dest', 'src', 'num', 'null') }}}; }, + llvm_memcpy_i32: 'memcpy', llvm_memcpy_i64: 'memcpy', llvm_memcpy_p0i8_p0i8_i32: 'memcpy', @@ -3598,6 +3601,9 @@ LibraryManager.library = { llvm_memmove_p0i8_p0i8_i32: 'memmove', llvm_memmove_p0i8_p0i8_i64: 'memmove', + memset__inline: function(ptr, value, num) { + return makeSetValues(ptr, 0, value, 'null', num); + }, memset: function(ptr, value, num) { {{{ makeSetValues('ptr', '0', 'value', 'null', 'num') }}} }, @@ -3978,6 +3984,14 @@ LibraryManager.library = { // LLVM specifics // ========================================================================== + llvm_va_start__inline: function(ptr) { + // varargs - we received a pointer to the varargs as a final 'extra' parameter + var data = 'arguments[' + Framework.currItem.funcData.ident + '.length]'; + return makeSetValue(ptr, 0, data, 'void*'); + }, + + llvm_va_end: function() {}, + llvm_va_copy: function(ppdest, ppsrc) { {{{ makeCopyValues('ppdest', 'ppsrc', QUANTUM_SIZE, 'null') }}} /* Alternate implementation that copies the actual DATA; it assumes the va_list is prefixed by its size @@ -4003,6 +4017,15 @@ LibraryManager.library = { } return ret; }, + + llvm_ctlz_i32: function(x) { + for (var i=0; i<32; i++) { + if ( (x & (1 << (31-i))) != 0 ) { + return i; + } + } + return 32; + }, __assert_fail: function(condition, file, line) { ABORT = true; @@ -4158,8 +4181,8 @@ LibraryManager.library = { return ret; }, - llvm_expect_i32: function(x, y) { - return x == y; // TODO: inline this + llvm_expect_i32__inline: function(x, y) { + return '((' + x + ')==(' + y + '))'; }, llvm_lifetime_start: function() {}, @@ -4169,10 +4192,20 @@ LibraryManager.library = { // iostream.h // ========================================================================== - // TODO: Document; compile from real implementation. + // libc++ + + $libcxx__postset: 'try { __ZNSt3__14coutE = 1 } catch(e){}; try { __ZNSt3__14cerrE = 2 } catch(e){};', + $libcxx: {}, + + _ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPKv__deps: ['fputs', '$libcxx'], + _ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEPKv: function(stream, str) { + _fputs(str, _stdout); // XXX stderr etc. + }, + + // glibc _ZNSt8ios_base4InitC1Ev: function() { - // need valid 'file descriptors' + // need valid 'file descriptors' for glibc //__ZSt4cout = 1; //__ZSt4cerr = 2; }, @@ -4245,6 +4278,11 @@ LibraryManager.library = { scalbnf: 'ldexp', scalbln: 'ldexp', scalblnf: 'ldexp', + cbrt: function(x) { + return Math.pow(x, 1/3); + }, + cbrtf: 'cbrt', + cbrtl: 'cbrt', modf: function(x, intpart) { {{{ makeSetValue('intpart', 0, 'Math.floor(x)', 'double') }}} @@ -5374,6 +5412,11 @@ LibraryManager.library = { _Z21emscripten_run_scriptPKc: function(ptr) { eval(Pointer_stringify(ptr)); + }, + + EMSCRIPTEN_COMMENT__inline: function(param) { + param = stripCorrections(param); + return '// ' + Variables.globals[param].value.text.replace('\\00', '') + ' '; } }; diff --git a/src/library_sdl.js b/src/library_sdl.js index af94301a..5ca049fe 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -255,6 +255,8 @@ mergeInto(LibraryManager.library, { }, SDL_SetVideoMode: function(width, height, depth, flags) { + Module['canvas'].width = width; + Module['canvas'].height = height; return SDL.screen = SDL.makeSurface(width, height, flags); }, @@ -268,7 +270,7 @@ mergeInto(LibraryManager.library, { } surfData.ctx.putImageData(surfData.image, 0, 0); } - _SDL_CloseAudio(); // make sure we don't leave our audio timer running + if (SDL.audio) _SDL_CloseAudio(); // make sure we don't leave our audio timer running __shutdownRuntime__(); throw 'SDL_Quit!'; }, @@ -304,11 +306,27 @@ mergeInto(LibraryManager.library, { if (!surfData.colors) { var data = surfData.image.data; var buffer = surfData.buffer; +#if USE_TYPED_ARRAYS == 2 + assert(buffer % 4 == 0, 'Invalid buffer offset: ' + buffer); + var src = buffer >> 2; + var dst = 0; + while (dst < num) { + var val = HEAP32[src]; // This is optimized. Instead, we could do {{{ makeGetValue('buffer', 'dst', 'i32') }}}; + data[dst] = val & 0xff; + data[dst+1] = (val >> 8) & 0xff; + data[dst+2] = (val >> 16) & 0xff; + data[dst+3] = 0xff; + src++; + dst += 4; + } +#else for (var i = 0; i < num; i++) { // We may need to correct signs here. Potentially you can hardcode a write of 255 to alpha, say, and // the compiler may decide to write -1 in the llvm bitcode... data[i] = {{{ makeGetValue('buffer', 'i', 'i8') + (CORRECT_SIGNS ? '&0xff' : '') }}}; + if (i % 4 == 3) data[i] = 0xff; } +#endif } else { var width = Module['canvas'].width; var height = Module['canvas'].height; diff --git a/src/modules.js b/src/modules.js index 2341b575..3b370878 100644 --- a/src/modules.js +++ b/src/modules.js @@ -186,6 +186,10 @@ var Debugging = { } }; +var Variables = { + globals: null +}; + var Types = { types: {}, fatTypes: {}, // With QUANTUM_SIZE=1, we store the full-size type data here @@ -255,7 +259,7 @@ var LibraryManager = { load: function() { assert(!this.library); - for (var suffix in set('', '_sdl', '_gl', '_browser')) { + for (var suffix in set('', '_sdl', '_browser')) { eval(processMacros(preprocess(read('library' + suffix + '.js'), CONSTANTS))); } }, diff --git a/src/parseTools.js b/src/parseTools.js index f9ef419a..2e7e1970 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -793,14 +793,23 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore) { } } +var UNROLL_LOOP_LIMIT = 5; + function makeSetValues(ptr, pos, value, type, num) { function safety(where) { where = where || getFastValue(ptr, '+', pos) + '+$mspi$'; return ';' + (SAFE_HEAP ? 'SAFE_HEAP_ACCESS(' + where + ', ' + type + ', 1)' : ''); } if (USE_TYPED_ARRAYS in set(0, 1)) { + if (isNumber(num)) { + if (num < UNROLL_LOOP_LIMIT) { + return range(num).map(function(i) { + return makeSetValue(ptr, getFastValue(pos, '+', i), value, type); + }).join('; '); + } + } return 'for (var $mspi$ = 0; $mspi$ < ' + num + '; $mspi$++) {\n' + - makeSetValue(ptr, getFastValue(pos, '+', '$mspi$'), value, type) + ';\n}'; + makeSetValue(ptr, getFastValue(pos, '+', '$mspi$'), value, type) + '\n}'; } else { // USE_TYPED_ARRAYS == 2 /* return 'for (var $mspi$ = 0; $mspi$ < ' + num + '; $mspi$++) {\n' + @@ -837,6 +846,17 @@ function makeCopyValues(dest, src, num, type, modifier) { return (SAFE_HEAP ? 'SAFE_HEAP_COPY_HISTORY(' + to + ', ' + from + ')' : ''); } if (USE_TYPED_ARRAYS in set(0, 1)) { + if (isNumber(num)) { + if (num < UNROLL_LOOP_LIMIT) { + return range(num).map(function(i) { + return type !== 'null' ? makeSetValue(dest, i, makeGetValue(src, i, type) + (modifier || ''), type) + : // Null is special-cased: We copy over all heaps + makeGetSlabs(dest, 'null', true).map(function(slab) { + return slab + '[' + dest + '+' + i + ']=' + slab + '[' + src + '+' + i + ']'; + }).join('; ') + '; ' + safety(dest + '+' + i, src + '+' + i) + }).join('; '); + } + } return 'for (var $mcpi$ = 0; $mcpi$ < ' + num + '; $mcpi$++) {\n' + (type !== 'null' ? makeSetValue(dest, '$mcpi$', makeGetValue(src, '$mcpi$', type) + (modifier || ''), type) : // Null is special-cased: We copy over all heaps @@ -1362,3 +1382,17 @@ function finalizeBlockAddress(param) { return Functions.currFunctions[param.func].labelIds[param.label]; // XXX We rely on currFunctions here...? } +function stripCorrections(param) { + var m; + if (m = /^\((.*)\)$/.exec(param)) { + param = m[1]; + } + if (m = /^\((\w+)\)&\d+$/.exec(param)) { + param = m[1]; + } + if (m = /CHECK_OVERFLOW\(([^,)]*),.*/.exec(param)) { + param = m[1]; + } + return param; +} + diff --git a/src/preamble.js b/src/preamble.js index c8f93b56..faf7aa72 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -23,6 +23,9 @@ var ACCEPTABLE_SAFE_HEAP_ERRORS = 0; function SAFE_HEAP_ACCESS(dest, type, store, ignore) { //if (dest === A_NUMBER) print ([dest, type, store] + ' ' + new Error().stack); // Something like this may be useful, in debugging +#if USE_TYPED_ARRAYS == 2 + return; // It is legitimate to violate the load-store assumption in this case +#endif if (type && type[type.length-1] == '*') type = 'i32'; // pointers are ints, for our purposes here // Note that this will pass even with unions: You can store X, load X, then store Y and load Y. // You cannot, however, do the nonportable act of store X and load Y! @@ -30,7 +33,7 @@ function SAFE_HEAP_ACCESS(dest, type, store, ignore) { HEAP_HISTORY[dest] = ignore ? null : type; } else { #if USE_TYPED_ARRAYS == 0 - if (!HEAP[dest] && HEAP[dest] !== 0 && HEAP[dest] !== false) { // false can be the result of a mathop comparator + if (!HEAP[dest] && HEAP[dest] !== 0 && HEAP[dest] !== false && !ignore) { // false can be the result of a mathop comparator var error = true; try { if (HEAP[dest].toString() === 'NaN') error = false; // NaN is acceptable, as a double value @@ -98,11 +101,11 @@ function SAFE_HEAP_STORE(dest, value, type, ignore) { if (type[type.length-1] === '*') type = 'i32'; // hardcoded pointers as 32-bit switch(type) { case 'i1': case 'i8': HEAP8[dest] = value; break; - case 'i16': assert(dest % 2 === 0, type + ' stores must be aligned'); HEAP16[dest>>1] = value; break; - case 'i32': assert(dest % 4 === 0, type + ' stores must be aligned'); HEAP32[dest>>2] = value; break; - case 'i64': assert(dest % 4 === 0, type + ' stores must be aligned'); warn64(); HEAP32[dest>>2] = value; break; // XXX store int64 as int32 - case 'float': assert(dest % 4 === 0, type + ' stores must be aligned'); HEAPF32[dest>>2] = value; break; - case 'double': assert(dest % 4 === 0, type + ' stores must be aligned'); warn64(); HEAPF32[dest>>2] = value; break; // XXX store doubles as floats + case 'i16': assert(dest % 2 === 0, type + ' stores must be aligned: ' + dest); HEAP16[dest>>1] = value; break; + case 'i32': assert(dest % 4 === 0, type + ' stores must be aligned: ' + dest); HEAP32[dest>>2] = value; break; + case 'i64': assert(dest % 4 === 0, type + ' stores must be aligned: ' + dest); warn64(); HEAP32[dest>>2] = value; break; // XXX store int64 as int32 + case 'float': assert(dest % 4 === 0, type + ' stores must be aligned: ' + dest); HEAPF32[dest>>2] = value; break; + case 'double': assert(dest % 4 === 0, type + ' stores must be aligned: ' + dest); warn64(); HEAPF32[dest>>2] = value; break; // XXX store doubles as floats default: throw 'weird type for typed array II: ' + type + new Error().stack; } #else @@ -139,18 +142,18 @@ function SAFE_HEAP_LOAD(dest, type, unsigned, ignore) { break; } case 'i16': { - assert(dest % 2 === 0, type + ' loads must be aligned'); + assert(dest % 2 === 0, type + ' loads must be aligned: ' + dest); ret = (unsigned ? HEAPU16 : HEAP16)[dest>>1]; break; } case 'i32': case 'i64': { // XXX store int64 as int32 - assert(dest % 4 === 0, type + ' loads must be aligned'); + assert(dest % 4 === 0, type + ' loads must be aligned: ' + dest); if (type === 'i64') warn64(); ret = (unsigned ? HEAPU32 : HEAP32)[dest>>2]; break; } case 'float': case 'double': { // XXX store doubles as floats - assert(dest % 4 === 0, type + ' loads must be aligned'); + assert(dest % 4 === 0, type + ' loads must be aligned: ' + dest); if (type === 'double') warn64(); ret = HEAPF32[dest>>2]; break; @@ -503,7 +506,8 @@ Module['Array_stringify'] = Array_stringify; // Memory management -var FUNCTION_TABLE; +var FUNCTION_TABLE; // XXX: In theory the indexes here can be equal to pointers to stacked or malloced memory. Such comparisons should + // be false, but can turn out true. We should probably set the top bit to prevent such issues. var PAGE_SIZE = 4096; function alignMemoryPage(x) { @@ -655,8 +659,10 @@ Module['String_copy'] = String_copy; // Tools -if (typeof print === 'undefined') { - this['print'] = console.log; // we are on the web +if (typeof console === 'object' && typeof console.log === 'function') { + this['print'] = function(x) { console.log(x) }; // web console +} else if (typeof print === 'undefined') { + this['print'] = function(){}; // harmless no-op } // This processes a JS string into a C-line array of numbers, 0-terminated. diff --git a/src/settings.js b/src/settings.js index 0cbe989b..0e70316f 100644 --- a/src/settings.js +++ b/src/settings.js @@ -53,6 +53,8 @@ SKIP_STACK_IN_SMALL = 1; // When enabled, does not push/pop the stack at all in // may allocate stack later, and in a loop, this can be // very bad. In particular, when debugging, printf()ing // a lot can exhaust the stack very fast, with this option. + // In particular, be careful with the autodebugger! +INLINE_LIBRARY_FUNCS = 1; // Will inline library functions that have __inline defined // Generated code debugging options SAFE_HEAP = 0; // Check each write to the heap against a list of blocked addresses @@ -114,6 +116,13 @@ PROFILE = 0; // Enables runtime profiling. See test_profiling for a usage exampl EXPORTED_FUNCTIONS = ['_main']; // Functions that are explicitly exported, so they are guaranteed to // be accessible outside of the generated code. +IGNORED_FUNCTIONS = []; // Functions that we should not generate, neither a stub nor a complete function. + // This is useful if your project code includes a function, and you want to replace + // that in the compiled code with your own handwritten JS. (Of course even without + // this option, you could just override the generated function at runtime. However, + // JS engines might optimize better if the function is defined once in a single + // place in your code.) + EXPORTED_GLOBALS = []; // Global non-function variables that are explicitly // exported, so they are guaranteed to be // accessible outside of the generated code. |