diff options
51 files changed, 632 insertions, 143 deletions
@@ -94,4 +94,5 @@ a license to everyone to use it as detailed in LICENSE.) * Pin Zhang <zhangpin04@gmail.com> * Nick Bray <ncbray@chromium.org> (copyright owned by Google, Inc.) * Aidan Hobson Sayers <aidanhs@cantab.net> +* Charlie Birks <admin@daftgames.net> @@ -1577,6 +1577,35 @@ try: js_transform_tempfiles = [final] + if memory_init_file: + if shared.Settings.USE_TYPED_ARRAYS != 2: + if type(memory_init_file) == int: logging.warning('memory init file requires typed arrays mode 2') + else: + memfile = target + '.mem' + shared.try_delete(memfile) + def repl(m): + # handle chunking of the memory initializer + s = re.sub('[\[\]\n\(\)\. ]', '', m.groups(0)[0]) + s = s.replace('concat', ',') + if s[-1] == ',': s = s[:-1] + open(memfile, 'wb').write(''.join(map(lambda x: chr(int(x or '0')), s.split(',')))) + if DEBUG: + # Copy into temp dir as well, so can be run there too + temp_memfile = os.path.join(shared.EMSCRIPTEN_TEMP_DIR, os.path.basename(memfile)) + if os.path.abspath(memfile) != os.path.abspath(memfile): + shutil.copyfile(memfile, temp_memfile) + return 'loadMemoryInitializer("%s");' % os.path.basename(memfile) + src = re.sub(shared.JS.memory_initializer_pattern, repl, open(final).read(), count=1) + open(final + '.mem.js', 'w').write(src) + final += '.mem.js' + js_transform_tempfiles[-1] = final # simple text substitution preserves comment line number mappings + if DEBUG: + if os.path.exists(memfile): + save_intermediate('meminit') + logging.debug('wrote memory initialization to %s' % memfile) + else: + logging.debug('did not see memory initialization') + # It is useful to run several js optimizer passes together, to save on unneeded unparsing/reparsing js_optimizer_queue = [] js_optimizer_extra_info = {} @@ -1654,35 +1683,6 @@ try: src = re.sub(r'\n+[ \n]*\n+', '\n', src) open(final, 'w').write(src) - if memory_init_file: - if shared.Settings.USE_TYPED_ARRAYS != 2: - if type(memory_init_file) == int: logging.warning('memory init file requires typed arrays mode 2') - else: - memfile = target + '.mem' - shared.try_delete(memfile) - def repl(m): - # handle chunking of the memory initializer - s = re.sub('[\[\]\n\(\)\. ]', '', m.groups(0)[0]) - s = s.replace('concat', ',') - if s[-1] == ',': s = s[:-1] - open(memfile, 'wb').write(''.join(map(lambda x: chr(int(x or '0')), s.split(',')))) - if DEBUG: - # Copy into temp dir as well, so can be run there too - temp_memfile = os.path.join(shared.EMSCRIPTEN_TEMP_DIR, os.path.basename(memfile)) - if os.path.abspath(memfile) != os.path.abspath(memfile): - shutil.copyfile(memfile, temp_memfile) - return 'loadMemoryInitializer("%s");' % os.path.basename(memfile) - src = re.sub(shared.JS.memory_initializer_pattern, repl, src, count=1) - open(final + '.mem.js', 'w').write(src) - final += '.mem.js' - js_transform_tempfiles[-1] = final # simple text substitution preserves comment line number mappings - if DEBUG: - if os.path.exists(memfile): - save_intermediate('meminit') - logging.debug('wrote memory initialization to %s' % memfile) - else: - logging.debug('did not see memory initialization') - def generate_source_map(map_file_base_name, offset=0): jsrun.run_js(shared.path_from_root('tools', 'source-maps', 'sourcemapper.js'), shared.NODE_JS, js_transform_tempfiles + diff --git a/src/analyzer.js b/src/analyzer.js index 2a7d64f5..b20dedff 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -281,6 +281,14 @@ function analyzer(data, sidePass) { Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); i += toAdd.length; continue; + } else if (param.intertype == 'structvalue') { + // 'flatten' out the struct into scalars + var toAdd = param.params; + toAdd.forEach(function(param) { + param.byval = 0; + }); + Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); + continue; // do not increment i; proceed to process the new params } i++; } diff --git a/src/jsifier.js b/src/jsifier.js index 179a910a..7273f54c 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -23,7 +23,7 @@ function JSify(data, functionsOnly, givenFunctions) { var mainPass = !functionsOnly; if (mainPass) { - var shellFile = SHELL_FILE ? SHELL_FILE : (BUILD_AS_SHARED_LIB ? 'shell_sharedlib.js' : 'shell.js'); + var shellFile = SHELL_FILE ? SHELL_FILE : (BUILD_AS_SHARED_LIB || SIDE_MODULE ? 'shell_sharedlib.js' : 'shell.js'); if (phase == 'pre') { // We will start to print out the data, but must do so carefully - we are @@ -47,10 +47,11 @@ function JSify(data, functionsOnly, givenFunctions) { var shellParts = read(shellFile).split('{{BODY}}'); print(processMacros(preprocess(shellParts[0]))); - var preFile = BUILD_AS_SHARED_LIB ? 'preamble_sharedlib.js' : 'preamble.js'; + var preFile = BUILD_AS_SHARED_LIB || SIDE_MODULE ? 'preamble_sharedlib.js' : 'preamble.js'; var pre = processMacros(preprocess(read(preFile).replace('{{RUNTIME}}', getRuntime()))); print(pre); + // Populate implementedFunctions. Note that this is before types, and will be updated later. data.unparsedFunctions.forEach(function(func) { Functions.implementedFunctions[func.ident] = Functions.getSignature(func.returnType, func.params.map(function(param) { return param.type })); }); @@ -80,7 +81,7 @@ function JSify(data, functionsOnly, givenFunctions) { if (phase == 'pre') { var libFuncsToInclude; if (INCLUDE_FULL_LIBRARY) { - assert(!BUILD_AS_SHARED_LIB, 'Cannot have both INCLUDE_FULL_LIBRARY and BUILD_AS_SHARED_LIB set.') + assert(!(BUILD_AS_SHARED_LIB || SIDE_MODULE), 'Cannot have both INCLUDE_FULL_LIBRARY and BUILD_AS_SHARED_LIB/SIDE_MODULE set.') libFuncsToInclude = []; for (var key in LibraryManager.library) { if (!key.match(/__(deps|postset|inline|asm|sig)$/)) { @@ -454,7 +455,7 @@ function JSify(data, functionsOnly, givenFunctions) { var postsetId = ident + '__postset'; var postset = LibraryManager.library[postsetId]; - if (postset && !addedLibraryItems[postsetId]) { + if (postset && !addedLibraryItems[postsetId] && !SIDE_MODULE) { addedLibraryItems[postsetId] = true; ret.push({ intertype: 'GlobalVariablePostSet', @@ -497,6 +498,7 @@ function JSify(data, functionsOnly, givenFunctions) { Functions.libraryFunctions[ident.substr(1)] = 2; } } + if (SIDE_MODULE) return ';'; // we import into the side module js library stuff from the outside parent if ((!ASM_JS || phase == 'pre') && (EXPORT_ALL || (ident in EXPORTED_FUNCTIONS))) { contentText += '\nModule["' + ident + '"] = ' + ident + ';'; @@ -1835,7 +1837,7 @@ function JSify(data, functionsOnly, givenFunctions) { print('Runtime.typeInfo = ' + JSON.stringify(Types.types)); print('Runtime.structMetadata = ' + JSON.stringify(Types.structMetadata)); } - var postFile = BUILD_AS_SHARED_LIB ? 'postamble_sharedlib.js' : 'postamble.js'; + var postFile = BUILD_AS_SHARED_LIB || SIDE_MODULE ? 'postamble_sharedlib.js' : 'postamble.js'; var postParts = processMacros(preprocess(read(postFile))).split('{{GLOBAL_VARS}}'); print(postParts[0]); @@ -1871,6 +1873,12 @@ function JSify(data, functionsOnly, givenFunctions) { // Data if (mainPass) { + if (phase == 'pre') { + // types have been parsed, so we can figure out function signatures (which can use types) + data.unparsedFunctions.forEach(function(func) { + Functions.implementedFunctions[func.ident] = Functions.getSignature(func.returnType, func.params.map(function(param) { return param.type })); + }); + } substrate.addItems(data.functionStubs, 'FunctionStub'); assert(data.functions.length == 0); } else { diff --git a/src/library.js b/src/library.js index 9e78db13..3ba2f56b 100644 --- a/src/library.js +++ b/src/library.js @@ -6808,24 +6808,38 @@ LibraryManager.library = { _pthread_once.seen[ptr] = 1; }, + $PTHREAD_SPECIFIC: {}, + $PTHREAD_SPECIFIC_NEXT_KEY: 1, + pthread_key_create__deps: ['$PTHREAD_SPECIFIC', '$PTHREAD_SPECIFIC_NEXT_KEY', '$ERRNO_CODES'], pthread_key_create: function(key, destructor) { - if (!_pthread_key_create.keys) _pthread_key_create.keys = {}; + if (key == 0) { + return ERRNO_CODES.EINVAL; + } + {{{ makeSetValue('key', '0', 'PTHREAD_SPECIFIC_NEXT_KEY', 'i32*') }}} // values start at 0 - _pthread_key_create.keys[key] = 0; + PTHREAD_SPECIFIC[PTHREAD_SPECIFIC_NEXT_KEY] = 0; + PTHREAD_SPECIFIC_NEXT_KEY++; + return 0; }, + pthread_getspecific__deps: ['$PTHREAD_SPECIFIC'], pthread_getspecific: function(key) { - return _pthread_key_create.keys[key] || 0; + return PTHREAD_SPECIFIC[key] || 0; }, + pthread_setspecific__deps: ['$PTHREAD_SPECIFIC', '$ERRNO_CODES'], pthread_setspecific: function(key, value) { - _pthread_key_create.keys[key] = value; + if (value == 0) { + return ERRNO_CODES.EINVAL; + } + PTHREAD_SPECIFIC[key] = value; + return 0; }, - pthread_key_delete: ['$ERRNO_CODES'], + pthread_key_delete__deps: ['$PTHREAD_SPECIFIC', '$ERRNO_CODES'], pthread_key_delete: function(key) { - if (_pthread_key_create.keys[key]) { - delete _pthread_key_create.keys[key]; + if (key in PTHREAD_SPECIFIC) { + delete PTHREAD_SPECIFIC[key]; return 0; } return ERRNO_CODES.EINVAL; diff --git a/src/library_browser.js b/src/library_browser.js index 558c9a59..591a3c11 100644 --- a/src/library_browser.js +++ b/src/library_browser.js @@ -821,6 +821,13 @@ mergeInto(LibraryManager.library, { emscripten_set_canvas_size: function(width, height) { Browser.setCanvasSize(width, height); }, + + emscripten_get_canvas_size: function(width, height, isFullscreen) { + var canvas = Module['canvas']; + {{{ makeSetValue('width', '0', 'canvas.width', 'i32') }}}; + {{{ makeSetValue('height', '0', 'canvas.height', 'i32') }}}; + {{{ makeSetValue('isFullscreen', '0', 'Browser.isFullScreen ? 1 : 0', 'i32') }}}; + }, emscripten_get_now: function() { if (ENVIRONMENT_IS_NODE) { diff --git a/src/library_fs.js b/src/library_fs.js index e1397356..9c8223ab 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -802,6 +802,90 @@ mergeInto(LibraryManager.library, { } }, + indexedDB: function() { + return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + }, + + DB_NAME: function() { + return 'EM_FS_' + window.location.pathname; + }, + DB_VERSION: 20, + DB_STORE_NAME: 'FILE_DATA', + + // asynchronously saves a list of files to an IndexedDB. The DB will be created if not already existing. + saveFilesToDB: function(paths, onload, onerror) { + onload = onload || function(){}; + onerror = onerror || function(){}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = function() { + console.log('creating db'); + var db = openRequest.result; + db.createObjectStore(FS.DB_STORE_NAME); + }; + openRequest.onsuccess = function() { + var db = openRequest.result; + var transaction = db.transaction([FS.DB_STORE_NAME], 'readwrite'); + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, fail = 0, total = paths.length; + function finish() { + if (fail == 0) onload(); else onerror(); + } + paths.forEach(function(path) { + var putRequest = files.put(FS.analyzePath(path).object.contents, path); + putRequest.onsuccess = function() { ok++; if (ok + fail == total) finish() }; + putRequest.onerror = function() { fail++; if (ok + fail == total) finish() }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, + + // asychronously loads a file from IndexedDB. + loadFilesFromDB: function(paths, onload, onerror) { + onload = onload || function(){}; + onerror = onerror || function(){}; + var indexedDB = FS.indexedDB(); + try { + var openRequest = indexedDB.open(FS.DB_NAME(), FS.DB_VERSION); + } catch (e) { + return onerror(e); + } + openRequest.onupgradeneeded = onerror; // no database to load from + openRequest.onsuccess = function() { + var db = openRequest.result; + try { + var transaction = db.transaction([FS.DB_STORE_NAME], 'readonly'); + } catch(e) { + onerror(e); + return; + } + var files = transaction.objectStore(FS.DB_STORE_NAME); + var ok = 0, fail = 0, total = paths.length; + function finish() { + if (fail == 0) onload(); else onerror(); + } + paths.forEach(function(path) { + var getRequest = files.get(path); + getRequest.onsuccess = function() { + if (FS.analyzePath(path).exists) { + FS.unlink(path); + } + FS.createDataFile(PATH.dirname(path), PATH.basename(path), getRequest.result, true, true, true); + ok++; + if (ok + fail == total) finish(); + }; + getRequest.onerror = function() { fail++; if (ok + fail == total) finish() }; + }); + transaction.onerror = onerror; + }; + openRequest.onerror = onerror; + }, + // // general // diff --git a/src/library_memfs.js b/src/library_memfs.js index 63326c42..354f5e95 100644 --- a/src/library_memfs.js +++ b/src/library_memfs.js @@ -2,17 +2,13 @@ mergeInto(LibraryManager.library, { $MEMFS__deps: ['$FS'], $MEMFS: { // content modes - CONTENT_OWNING: 1, // contains a subarray into the heap, and we own it - need to free() when no longer needed + CONTENT_OWNING: 1, // contains a subarray into the heap, and we own it, without copying (note: someone else needs to free() it, if that is necessary) CONTENT_FLEXIBLE: 2, // has been modified or never set to anything, and is a flexible js array that can grow/shrink CONTENT_FIXED: 3, // contains some fixed-size content written into it, in a typed array ensureFlexible: function(node) { if (node.contentMode !== MEMFS.CONTENT_FLEXIBLE) { var contents = node.contents; node.contents = Array.prototype.slice.call(contents); - if (node.contentMode === MEMFS.CONTENT_OWNING) { - assert(contents.byteOffset); - Module['_free'](contents.byteOffset); - } node.contentMode = MEMFS.CONTENT_FLEXIBLE; } }, diff --git a/src/library_openal.js b/src/library_openal.js index 7f5d5df6..dea986f3 100644 --- a/src/library_openal.js +++ b/src/library_openal.js @@ -244,7 +244,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alDeleteSources called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } for (var i = 0; i < count; ++i) { @@ -258,7 +257,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alGenSources called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } for (var i = 0; i < count; ++i) { @@ -318,7 +316,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourcei called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -387,7 +384,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourcef called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -440,7 +436,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSource3f called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -481,7 +476,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourceQueueBuffers called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -518,7 +512,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourceUnqueueBuffers called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -557,7 +550,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alDeleteBuffers called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } if (count > AL.currentContext.buf.length) { @@ -601,7 +593,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alGenBuffers called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } for (var i = 0; i < count; ++i) { @@ -615,7 +606,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alBufferData called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } if (buffer > AL.currentContext.buf.length) { @@ -680,7 +670,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourcePlay called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -700,7 +689,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourceStop called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -720,7 +708,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alSourcePause called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -740,7 +727,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alGetSourcei called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -804,7 +790,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alGetSourcef called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } var src = AL.currentContext.src[source - 1]; @@ -865,7 +850,6 @@ var LibraryOpenAL = { #if OPENAL_DEBUG console.error("alListenerfv called without a valid context"); #endif - AL.currentContext.err = 0xA004 /* AL_INVALID_OPERATION */; return; } switch (param) { diff --git a/src/library_sdl.js b/src/library_sdl.js index 7b1837e8..d6cb6d18 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -567,7 +567,12 @@ var LibrarySDL = { switch (event.type) { case 'keydown': case 'keyup': { var down = event.type === 'keydown'; - var code = SDL.keyCodes[event.keyCode] || event.keyCode; + var code = event.keyCode; + if (code >= 65 && code <= 90) { + code += 32; // make lowercase for SDL + } else { + code = SDL.keyCodes[event.keyCode] || event.keyCode; + } {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}}; // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED diff --git a/src/parseTools.js b/src/parseTools.js index 046dac1b..66354dca 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -430,6 +430,8 @@ function parseParamTokens(params) { ret.push(parseLLVMFunctionCall(segment)); } else if (segment[1].text === 'blockaddress') { ret.push(parseBlockAddress(segment)); + } else if (segment[1].type && segment[1].type == '{') { + ret.push(parseLLVMSegment(segment)); } else { if (segment[2] && segment[2].text == 'to') { // part of bitcast params segment = segment.slice(0, 2); diff --git a/src/settings.js b/src/settings.js index cb64bfd9..03b4ed64 100644 --- a/src/settings.js +++ b/src/settings.js @@ -6,6 +6,11 @@ // emcc -s OPTION1=VALUE1 -s OPTION2=VALUE2 [..other stuff..] // // See https://github.com/kripken/emscripten/wiki/Code-Generation-Modes/ +// +// Note that the values here are the defaults in -O0, that is, unoptimized +// mode. See apply_opt_level in tools/shared.py for how -O1,2,3 affect these +// flags. +// // Tuning var QUANTUM_SIZE = 4; // This is the size of an individual field in a structure. 1 would @@ -131,6 +136,16 @@ var OUTLINING_LIMIT = 0; // A function size above which we try to automatically // large functions (JS engines often compile them very slowly, // compile them with lower optimizations, or do not optimize them // at all). If 0 |