diff options
-rwxr-xr-x | emcc | 4 | ||||
-rw-r--r-- | emscripten-version.txt | 2 | ||||
-rw-r--r-- | src/library.js | 13 | ||||
-rw-r--r-- | src/library_fs.js | 5 | ||||
-rw-r--r-- | src/library_idbfs.js | 3 | ||||
-rw-r--r-- | src/library_memfs.js | 169 | ||||
-rw-r--r-- | src/library_sdl.js | 2 | ||||
-rw-r--r-- | src/modules.js | 25 | ||||
-rw-r--r-- | src/settings.js | 4 | ||||
-rw-r--r-- | system/local/include/README.txt | 2 | ||||
-rw-r--r-- | tests/test_browser.py | 7 | ||||
-rw-r--r-- | tests/test_core.py | 32 | ||||
-rwxr-xr-x | tools/ffdb.py | 501 | ||||
-rw-r--r-- | tools/js-optimizer.js | 5 | ||||
-rw-r--r-- | tools/shared.py | 2 |
15 files changed, 560 insertions, 216 deletions
@@ -1376,6 +1376,8 @@ try: if shared.Settings.ASM_JS and shared.Settings.DLOPEN_SUPPORT: assert shared.Settings.DISABLE_EXCEPTION_CATCHING, 'no exceptions support with dlopen in asm yet' + assert not (bind and shared.Settings.NO_DYNAMIC_EXECUTION), 'NO_DYNAMIC_EXECUTION disallows embind' + if proxy_to_worker: shared.Settings.PROXY_TO_WORKER = 1 @@ -1692,7 +1694,7 @@ try: logging.debug('wrote memory initialization to %s', memfile) else: logging.debug('did not see memory initialization') - elif shared.Settings.USE_TYPED_ARRAYS == 2 and not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE: + elif shared.Settings.USE_TYPED_ARRAYS == 2 and not shared.Settings.MAIN_MODULE and not shared.Settings.SIDE_MODULE and debug_level < 4: # not writing a binary init, but we can at least optimize them by splitting them up src = open(final).read() src = shared.JS.optimize_initializer(src) diff --git a/emscripten-version.txt b/emscripten-version.txt index de59d0c6..5f079d01 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.19.1 +1.19.2 diff --git a/src/library.js b/src/library.js index c17952b3..1f7fd37d 100644 --- a/src/library.js +++ b/src/library.js @@ -3166,7 +3166,7 @@ LibraryManager.library = { #endif environ: 'allocate(1, "i32*", ALLOC_STATIC)', __environ__deps: ['environ'], - __environ: '_environ', + __environ: 'environ', __buildEnvironment__deps: ['__environ'], __buildEnvironment: function(env) { // WARNING: Arbitrary limit! @@ -3388,9 +3388,20 @@ LibraryManager.library = { memcpy__sig: 'iiii', memcpy__deps: ['emscripten_memcpy_big'], memcpy: function(dest, src, num) { +#if USE_TYPED_ARRAYS == 0 + {{{ makeCopyValues('dest', 'src', 'num', 'null') }}}; + return num; +#endif +#if USE_TYPED_ARRAYS == 1 + {{{ makeCopyValues('dest', 'src', 'num', 'null') }}}; + return num; +#endif + dest = dest|0; src = src|0; num = num|0; var ret = 0; +#if USE_TYPED_ARRAYS if ((num|0) >= 4096) return _emscripten_memcpy_big(dest|0, src|0, num|0)|0; +#endif ret = dest|0; if ((dest&3) == (src&3)) { while (dest & 3) { diff --git a/src/library_fs.js b/src/library_fs.js index 5f7f1dea..1fff6348 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -1480,6 +1480,7 @@ mergeInto(LibraryManager.library, { // WARNING: Can't read binary files in V8's d8 or tracemonkey's js, as // read() will try to parse UTF8. obj.contents = intArrayFromString(Module['read'](obj.url), true); + obj.usedBytes = obj.contents.length; } catch (e) { success = false; } @@ -1601,6 +1602,10 @@ mergeInto(LibraryManager.library, { node.contents = null; node.url = properties.url; } + // Add a function that defers querying the file size until it is asked the first time. + Object.defineProperty(node, "usedBytes", { + get: function() { return this.contents.length; } + }); // override each stream op with one that tries to force load the lazy file first var stream_ops = {}; var keys = Object.keys(node.stream_ops); diff --git a/src/library_idbfs.js b/src/library_idbfs.js index 91015e77..8082c196 100644 --- a/src/library_idbfs.js +++ b/src/library_idbfs.js @@ -135,6 +135,9 @@ mergeInto(LibraryManager.library, { if (FS.isDir(stat.mode)) { return callback(null, { timestamp: stat.mtime, mode: stat.mode }); } else if (FS.isFile(stat.mode)) { + // Performance consideration: storing a normal JavaScript array to a IndexedDB is much slower than storing a typed array. + // Therefore always convert the file contents to a typed array first before writing the data to IndexedDB. + node.contents = MEMFS.getFileDataAsTypedArray(node); return callback(null, { timestamp: stat.mtime, mode: stat.mode, contents: node.contents }); } else { return callback(new Error('node type not supported')); diff --git a/src/library_memfs.js b/src/library_memfs.js index 95c3ae65..24d6cc22 100644 --- a/src/library_memfs.js +++ b/src/library_memfs.js @@ -2,11 +2,6 @@ mergeInto(LibraryManager.library, { $MEMFS__deps: ['$FS'], $MEMFS: { ops_table: null, - - // content modes - 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 mount: function(mount) { return MEMFS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 511 /* 0777 */, 0); }, @@ -71,8 +66,11 @@ mergeInto(LibraryManager.library, { } else if (FS.isFile(node.mode)) { node.node_ops = MEMFS.ops_table.file.node; node.stream_ops = MEMFS.ops_table.file.stream; - node.contents = []; - node.contentMode = MEMFS.CONTENT_FLEXIBLE; + node.usedBytes = 0; // The actual number of bytes used in the typed array, as opposed to contents.buffer.byteLength which gives the whole capacity. + // When the byte data of the file is populated, this will point to either a typed array, or a normal JS array. Typed arrays are preferred + // for performance, and used by default. However, typed arrays are not resizable like normal JS arrays are, so there is a small disk size + // penalty involved for appending file writes that continuously grow a file similar to std::vector capacity vs used -scheme. + node.contents = null; } else if (FS.isLink(node.mode)) { node.node_ops = MEMFS.ops_table.link.node; node.stream_ops = MEMFS.ops_table.link.stream; @@ -87,13 +85,88 @@ mergeInto(LibraryManager.library, { } return node; }, - ensureFlexible: function(node) { - if (node.contentMode !== MEMFS.CONTENT_FLEXIBLE) { - var contents = node.contents; - node.contents = Array.prototype.slice.call(contents); - node.contentMode = MEMFS.CONTENT_FLEXIBLE; + + // Given a file node, returns its file data converted to a regular JS array. You should treat this as read-only. + getFileDataAsRegularArray: function(node) { +#if USE_TYPED_ARRAYS == 2 + if (node.contents && node.contents.subarray) { + var arr = []; + for (var i = 0; i < node.usedBytes; ++i) arr.push(node.contents[i]); + return arr; // Returns a copy of the original data. + } +#endif + return node.contents; // No-op, the file contents are already in a JS array. Return as-is. + }, + +#if USE_TYPED_ARRAYS == 2 + // Given a file node, returns its file data converted to a typed array. + getFileDataAsTypedArray: function(node) { + if (node.contents && node.contents.subarray) return node.contents.subarray(0, node.usedBytes); // Make sure to not return excess unused bytes. + return new Uint8Array(node.contents); + }, +#endif + + // Allocates a new backing store for the given node so that it can fit at least newSize amount of bytes. + // May allocate more, to provide automatic geometric increase and amortized linear performance appending writes. + // Never shrinks the storage. + expandFileStorage: function(node, newCapacity) { +#if USE_TYPED_ARRAYS == 2 + +#if !MEMFS_APPEND_TO_TYPED_ARRAYS + // If we are asked to expand the size of a file that already exists, revert to using a standard JS array to store the file + // instead of a typed array. This makes resizing the array more flexible because we can just .push() elements at the back to + // increase the size. + if (node.contents && node.contents.subarray && newCapacity > node.contents.length) { + node.contents = MEMFS.getFileDataAsRegularArray(node); + node.usedBytes = node.contents.length; // We might be writing to a lazy-loaded file which had overridden this property, so force-reset it. } +#endif + + if (!node.contents || node.contents.subarray) { // Keep using a typed array if creating a new storage, or if old one was a typed array as well. + var prevCapacity = node.contents ? node.contents.buffer.byteLength : 0; + if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough. + // Don't expand strictly to the given requested limit if it's only a very small increase, but instead geometrically grow capacity. + // For small filesizes (<1MB), perform size*2 geometric increase, but for large sizes, do a much more conservative size*1.125 increase to + // avoid overshooting the allocation cap by a very large margin. + var CAPACITY_DOUBLING_MAX = 1024 * 1024; + newCapacity = Math.max(newCapacity, (prevCapacity * (prevCapacity < CAPACITY_DOUBLING_MAX ? 2.0 : 1.125)) | 0); + if (prevCapacity != 0) newCapacity = Math.max(newCapacity, 256); // At minimum allocate 256b for each file when expanding. + var oldContents = node.contents; + node.contents = new Uint8Array(newCapacity); // Allocate new storage. + if (node.usedBytes > 0) node.contents.set(oldContents.subarray(0, node.usedBytes), 0); // Copy old data over to the new storage. + return; + } +#endif + // Not using a typed array to back the file storage. Use a standard JS array instead. + if (!node.contents && newCapacity > 0) node.contents = []; + while (node.contents.length < newCapacity) node.contents.push(0); }, + + // Performs an exact resize of the backing file storage to the given size, if the size is not exactly this, the storage is fully reallocated. + resizeFileStorage: function(node, newSize) { + if (node.usedBytes == newSize) return; + if (newSize == 0) { + node.contents = null; // Fully decommit when requesting a resize to zero. + node.usedBytes = 0; + return; + } + +#if USE_TYPED_ARRAYS == 2 + if (!node.contents || node.contents.subarray) { // Resize a typed array if that is being used as the backing store. + var oldContents = node.contents; + node.contents = new Uint8Array(new ArrayBuffer(newSize)); // Allocate new storage. + node.contents.set(oldContents.subarray(0, Math.min(newSize, node.usedBytes))); // Copy old data over to the new storage. + node.usedBytes = newSize; + return; + } +#endif + // Backing with a JS array. + if (!node.contents) node.contents = []; + if (node.contents.length > newSize) node.contents.length = newSize; + else while (node.contents.length < newSize) node.contents.push(0); + node.usedBytes = newSize; + }, + node_ops: { getattr: function(node) { var attr = {}; @@ -108,7 +181,7 @@ mergeInto(LibraryManager.library, { if (FS.isDir(node.mode)) { attr.size = 4096; } else if (FS.isFile(node.mode)) { - attr.size = node.contents.length; + attr.size = node.usedBytes; } else if (FS.isLink(node.mode)) { attr.size = node.link.length; } else { @@ -131,10 +204,7 @@ mergeInto(LibraryManager.library, { node.timestamp = attr.timestamp; } if (attr.size !== undefined) { - MEMFS.ensureFlexible(node); - var contents = node.contents; - if (attr.size < contents.length) contents.length = attr.size; - else while (attr.size > contents.length) contents.push(0); + MEMFS.resizeFileStorage(node, attr.size); } }, lookup: function(parent, name) { @@ -198,9 +268,8 @@ mergeInto(LibraryManager.library, { stream_ops: { read: function(stream, buffer, offset, length, position) { var contents = stream.node.contents; - if (position >= contents.length) - return 0; - var size = Math.min(contents.length - position, length); + if (position >= stream.node.usedBytes) return 0; + var size = Math.min(stream.node.usedBytes - position, length); assert(size >= 0); #if USE_TYPED_ARRAYS == 2 if (size > 8 && contents.subarray) { // non-trivial, and typed array @@ -208,47 +277,53 @@ mergeInto(LibraryManager.library, { } else #endif { - for (var i = 0; i < size; i++) { - buffer[offset + i] = contents[position + i]; - } + for (var i = 0; i < size; i++) buffer[offset + i] = contents[position + i]; } return size; }, + + // Writes the byte range (buffer[offset], buffer[offset+length]) to offset 'position' into the file pointed by 'stream' write: function(stream, buffer, offset, length, position, canOwn) { + if (!length) return 0; var node = stream.node; node.timestamp = Date.now(); - var contents = node.contents; + #if USE_TYPED_ARRAYS == 2 - if (length && contents.length === 0 && position === 0 && buffer.subarray) { - // just replace it with the new data -#if ASSERTIONS - assert(buffer.length); -#endif - if (canOwn && offset === 0) { - node.contents = buffer; // this could be a subarray of Emscripten HEAP, or allocated from some other source. - node.contentMode = (buffer.buffer === HEAP8.buffer) ? MEMFS.CONTENT_OWNING : MEMFS.CONTENT_FIXED; - } else { - node.contents = new Uint8Array(buffer.subarray(offset, offset+length)); - node.contentMode = MEMFS.CONTENT_FIXED; + if (buffer.subarray && (!node.contents || node.contents.subarray)) { // This write is from a typed array to a typed array? + if (canOwn) { // Can we just reuse the buffer we are given? + node.contents = buffer.subarray(offset, offset + length); + node.usedBytes = length; + return length; + } else if (node.usedBytes === 0) { // If this first write to an empty file, do a fast set since we don't need to care about old data. + node.contents = new Uint8Array(buffer.subarray(offset, offset + length)); + node.usedBytes = length; + return length; + } else if (position + length <= node.usedBytes) { // Writing to an already allocated and used subrange of the file? + node.contents.set(buffer.subarray(offset, offset + length), position); + return length; } - return length; } #endif - MEMFS.ensureFlexible(node); - var contents = node.contents; - while (contents.length < position) contents.push(0); - for (var i = 0; i < length; i++) { - contents[position + i] = buffer[offset + i]; - } + // Appending to an existing file and we need to reallocate, or source data did not come as a typed array. + MEMFS.expandFileStorage(node, position+length); +#if USE_TYPED_ARRAYS == 2 + if (node.contents.subarray && buffer.subarray) node.contents.set(buffer.subarray(offset, offset + length), position); // Use typed array write if available. + else +#endif + for (var i = 0; i < length; i++) { + node.contents[position + i] = buffer[offset + i]; // Or fall back to manual write if not. + } + node.usedBytes = Math.max(node.usedBytes, position+length); return length; }, + llseek: function(stream, offset, whence) { var position = offset; if (whence === 1) { // SEEK_CUR. position += stream.position; } else if (whence === 2) { // SEEK_END. if (FS.isFile(stream.node.mode)) { - position += stream.node.contents.length; + position += stream.node.usedBytes; } } if (position < 0) { @@ -259,10 +334,8 @@ mergeInto(LibraryManager.library, { return position; }, allocate: function(stream, offset, length) { - MEMFS.ensureFlexible(stream.node); - var contents = stream.node.contents; - var limit = offset + length; - while (limit > contents.length) contents.push(0); + MEMFS.expandFileStorage(stream.node, offset + length); + stream.node.usedBytes = Math.max(stream.node.usedBytes, offset + length); }, mmap: function(stream, buffer, offset, length, position, prot, flags) { if (!FS.isFile(stream.node.mode)) { @@ -280,7 +353,7 @@ mergeInto(LibraryManager.library, { ptr = contents.byteOffset; } else { // Try to avoid unnecessary slices. - if (position > 0 || position + length < contents.length) { + if (position > 0 || position + length < stream.node.usedBytes) { if (contents.subarray) { contents = contents.subarray(position, position + length); } else { diff --git a/src/library_sdl.js b/src/library_sdl.js index a01b3c6c..7145a7ba 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -2601,7 +2601,7 @@ var LibrarySDL = { if (info && info.audio) { info.audio.pause(); } else { - Module.printErr('Mix_Pause: no sound found for channel: ' + channel); + //Module.printErr('Mix_Pause: no sound found for channel: ' + channel); } }, diff --git a/src/modules.js b/src/modules.js index fd5c23cd..56f4c827 100644 --- a/src/modules.js +++ b/src/modules.js @@ -443,6 +443,31 @@ var LibraryManager = { } } + // apply synonyms. these are typically not speed-sensitive, and doing it this way makes it possible to not include hacks in the compiler + // (and makes it simpler to switch between SDL verisons, fastcomp and non-fastcomp, etc.). + var lib = LibraryManager.library; + libloop: for (var x in lib) { + if (x.lastIndexOf('__') > 0) continue; // ignore __deps, __* + if (lib[x + '__asm']) continue; // ignore asm library functions, those need to be fully optimized + if (typeof lib[x] === 'string') { + var target = x; + while (typeof lib[target] === 'string') { + if (lib[target].indexOf('(') >= 0) continue libloop; + target = lib[target]; + } + if (typeof lib[target] === 'undefined' || typeof lib[target] === 'function') { + if (target.indexOf('Math_') < 0) { + lib[x] = new Function('return _' + target + '.apply(null, arguments)'); + if (!lib[x + '__deps']) lib[x + '__deps'] = []; + lib[x + '__deps'].push(target); + } else { + lib[x] = new Function('return ' + target + '.apply(null, arguments)'); + } + continue; + } + } + } + /* // export code for CallHandlers.h printErr('============================'); diff --git a/src/settings.js b/src/settings.js index bdb149e3..a8761d90 100644 --- a/src/settings.js +++ b/src/settings.js @@ -323,6 +323,10 @@ var FS_LOG = 0; // Log all FS operations. This is especially helpful when you'r // so that you can create a virtual file system with all of the required files. var CASE_INSENSITIVE_FS = 0; // If set to nonzero, the provided virtual filesystem if treated case-insensitive, like // Windows and OSX do. If set to 0, the VFS is case-sensitive, like on Linux. +var MEMFS_APPEND_TO_TYPED_ARRAYS = 0; // If set to nonzero, MEMFS will always utilize typed arrays as the backing store + // for appending data to files. The default behavior is to use typed arrays for files + // when the file size doesn't change after initial creation, and for files that do + // change size, use normal JS arrays instead. var USE_BSS = 1; // https://en.wikipedia.org/wiki/.bss // When enabled, 0-initialized globals are sorted to the end of the globals list, diff --git a/system/local/include/README.txt b/system/local/include/README.txt new file mode 100644 index 00000000..09bd03e0 --- /dev/null +++ b/system/local/include/README.txt @@ -0,0 +1,2 @@ +You may place local includes that you require here and they will +be found by the default compiler search path. diff --git a/tests/test_browser.py b/tests/test_browser.py index c8e07b25..0771cb1b 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -1096,9 +1096,10 @@ keydown(100);keyup(100); // trigger the end shutil.move('test.html', 'third.html') def test_fs_idbfs_sync(self): - secret = str(time.time()) - self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=['-DFIRST', '-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) - self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=['-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + secret = str(time.time()) + self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=mode + ['-DFIRST', '-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) + self.btest(path_from_root('tests', 'fs', 'test_idbfs_sync.c'), '1', force_c=True, args=mode + ['-DSECRET=\'' + secret + '\'', '-s', '''EXPORTED_FUNCTIONS=['_main', '_success']''']) def test_sdl_pumpevents(self): # key events should be detected using SDL_PumpEvents diff --git a/tests/test_core.py b/tests/test_core.py index 76a87066..13b6c0f3 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4030,11 +4030,14 @@ def process(filename): src = open(path_from_root('tests', 'files.cpp'), 'r').read() mem_file = 'src.cpp.o.js.mem' - try_delete(mem_file) - self.do_run(src, ('size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\ntexte\n', 'size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\ntexte\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\n'), - post_build=post, extra_emscripten_args=['-H', 'libc/fcntl.h']) - if self.emcc_args and '--memory-init-file' in self.emcc_args: - assert os.path.exists(mem_file) + orig_args = self.emcc_args + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + self.emcc_args = orig_args + mode + try_delete(mem_file) + self.do_run(src, ('size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\ntexte\n', 'size: 7\ndata: 100,-56,50,25,10,77,123\nloop: 100 -56 50 25 10 77 123 \ninput:hi there!\ntexto\ntexte\n$\n5 : 10,30,20,11,88\nother=some data.\nseeked=me da.\nseeked=ata.\nseeked=ta.\nfscanfed: 10 - hello\nok.\n'), + post_build=post, extra_emscripten_args=['-H', 'libc/fcntl.h']) + if self.emcc_args and '--memory-init-file' in self.emcc_args: + assert os.path.exists(mem_file) def test_files_m(self): # Test for Module.stdin etc. @@ -4077,7 +4080,10 @@ def process(filename): test_path = path_from_root('tests', 'core', 'test_fwrite_0') src, output = (test_path + s for s in ('.in', '.out')) - self.do_run_from_file(src, output) + orig_args = self.emcc_args if self.emcc_args else [] + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + self.emcc_args = orig_args + mode + self.do_run_from_file(src, output) def test_fgetc_ungetc(self): src = open(path_from_root('tests', 'stdio', 'test_fgetc_ungetc.c'), 'r').read() @@ -4275,7 +4281,10 @@ def process(filename): if self.emcc_args is None: return self.skip('requires libcxx') test_path = path_from_root('tests', 'core', 'test_wprintf') src, output = (test_path + s for s in ('.c', '.out')) - self.do_run_from_file(src, output) + orig_args = self.emcc_args + for mode in [[], ['-s', 'MEMFS_APPEND_TO_TYPED_ARRAYS=1']]: + self.emcc_args = orig_args + mode + self.do_run_from_file(src, output) def test_direct_string_constant_usage(self): if self.emcc_args is None: return self.skip('requires libcxx') @@ -5150,7 +5159,7 @@ def process(filename): \'\'\' FS.createDataFile('/', 'paper.pdf', eval(Module.read('paper.pdf.js')), true, false); Module.callMain(Module.arguments); - Module.print("Data: " + JSON.stringify(FS.root.contents['filename-1.ppm'].contents.map(function(x) { return unSign(x, 8) }))); + Module.print("Data: " + JSON.stringify(MEMFS.getFileDataAsRegularArray(FS.root.contents['filename-1.ppm']).map(function(x) { return unSign(x, 8) }))); \'\'\' ) src.close() @@ -5200,7 +5209,7 @@ def process(filename): )) ).replace( '// {{POST_RUN_ADDITIONS}}', - "Module.print('Data: ' + JSON.stringify(FS.analyzePath('image.raw').object.contents));" + "Module.print('Data: ' + JSON.stringify(MEMFS.getFileDataAsRegularArray(FS.analyzePath('image.raw').object)));" ) open(filename, 'w').write(src) ''' @@ -6419,10 +6428,15 @@ def process(filename): if (i < 10) throw i; // line 5 } + #include <iostream> + #include <string> + int main() { + std::string x = "ok"; // add libc++ stuff to make this big, test for #2410 int i; scanf("%d", &i); foo(i); + std::cout << x << std::endl; return 0; } ''' diff --git a/tools/ffdb.py b/tools/ffdb.py index 2171cb0e..9734014e 100755 --- a/tools/ffdb.py +++ b/tools/ffdb.py @@ -2,6 +2,7 @@ import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess, re, base64, struct, imghdr +ADB = 'adb' # Path to the adb executable LOG_VERBOSE = False # Verbose printing enabled with --verbose HOST = 'localhost' # The remote host to connect to the B2G device PORT = 6000 # The port on the host on which the B2G device listens on @@ -62,7 +63,18 @@ def logv(msg): # Returns a JSON dictionary of the received message. def read_b2g_response(print_errors_to_console = True): global read_queue, b2g_socket - read_queue += b2g_socket.recv(65536*2) + try: + if len(read_queue) == 0: + read_queue += b2g_socket.recv(65536*2) + except KeyboardInterrupt: + print ' Aborted by user' + sys.exit(1) + except Exception, e: + if e[0] == 57: # Socket is not connected + print 'Error! Failed to receive data from the device: socket is not connected!' + sys.exit(1) + else: + raise payload = '' while ':' in read_queue: semicolon = read_queue.index(':') @@ -77,6 +89,8 @@ def read_b2g_response(print_errors_to_console = True): # Log received errors immediately to console if print_errors_to_console and 'error' in payload: print >> sys.stderr, 'Received error "' + payload['error'] + '"! Reason: ' + payload['message'] + else: + break return payload # Sends a command to the B2G device and waits for the response and returns it as a JSON dict. @@ -150,8 +164,269 @@ def print_applist(applist, running_app_manifests, print_removable): num_printed += 1 return num_printed +def adb_devices(): + try: + devices = subprocess.check_output([ADB, 'devices']) + devices = devices.strip().split('\n')[1:] + devices = map(lambda x: x.strip().split('\t'), devices) + return devices + except Exception, e: + return [] + +def b2g_get_prefs_filename(): + return subprocess.check_output([ADB, 'shell', 'echo', '-n', '/data/b2g/mozilla/*.default/prefs.js']) + +def b2g_get_prefs_data(): + return subprocess.check_output([ADB, 'shell', 'cat', '/data/b2g/mozilla/*.default/prefs.js']) + +def b2g_get_pref(sub): + prefs_data = b2g_get_prefs_data().split('\n') + # Filter to find all prefs that have the substring 'sub' in them. + r = re.compile('user_pref\w*\(\w*"([^"]*)"\w*,\w*([^\)]*)') + for line in prefs_data: + m = r.match(line) + if m and (sub is None or sub in m.group(1)): + print m.group(1) + ': ' + m.group(2).strip() + +def b2g_set_pref(pref, value): + prefs_data = b2g_get_prefs_data().split('\n') + # Remove any old value of this pref. + r = re.compile('user_pref\w*\(\w*"([^"]*)"\w*,\w*([^\)]*)') + new_prefs_data = [] + for line in prefs_data: + m = r.match(line) + if not m or m.group(1) != pref: + new_prefs_data += [line] + + if value != None: + print 'Setting pref "' + pref + '" = ' + value + new_prefs_data += ['user_pref("' + pref + '", ' + value + ');'] + else: + print 'Unsetting pref "' + pref + '"' + (oshandle, tempfilename) = tempfile.mkstemp(suffix='.js', prefix='ffdb_temp_') + os.write(oshandle, '\n'.join(new_prefs_data)); + + # Write the new pref + subprocess.check_output([ADB, 'shell', 'stop', 'b2g']) + subprocess.check_output([ADB, 'push', tempfilename, b2g_get_prefs_filename()]) + subprocess.check_output([ADB, 'shell', 'start', 'b2g']) + print 'Rebooting phone...' + + def delete_temp_file(): + os.remove(tempfilename) + atexit.register(delete_temp_file) + +def get_packaged_app_manifest(target_app_path): + if os.path.isdir(target_app_path): + manifest_file = os.path.join(target_app_path, 'manifest.webapp') + if not os.path.isfile(manifest_file): + print "Error: Failed to find FFOS packaged app manifest file '" + manifest_file + "'! That directory does not contain a packaged app?" + sys.exit(1) + return json.loads(open(manifest_file, 'r').read()) + elif target_app_path.endswith('.zip') and os.path.isfile(target_app_path): + try: + z = zipfile.ZipFile(target_app_path, "r") + bytes = z.read('manifest.webapp') + except Exception, e: + print "Error: Failed to read FFOS packaged app manifest file 'manifest.webapp' in zip file '" + target_app_path + "'! Error: " + str(e) + sys.exit(1) + return None + return json.loads(str(bytes)) + else: + print "Error: Path '" + target_app_path + "' is neither a directory or a .zip file to represent the location of a FFOS packaged app!" + sys.exit(1) + return None + +def b2g_install(target_app_path): + if os.path.isdir(target_app_path): + print 'Zipping up the contents of directory "' + target_app_path + '"...' + (oshandle, tempzip) = tempfile.mkstemp(suffix='.zip', prefix='ffdb_temp_') + zipdir(target_app_path, tempzip) + target_app_path = tempzip + # Remember to delete the temporary package after we quit. + def delete_temp_file(): + os.remove(tempzip) + atexit.register(delete_temp_file) + + print 'Uploading application package "' + target_app_path + '"...' + print 'Size of compressed package: ' + sizeof_fmt(os.path.getsize(target_app_path)) + '.' + app_file = open(target_app_path, 'rb') + data = app_file.read() + file_size = len(data) + + uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage', { 'bulk': 'true'}, print_errors_to_console = False) # This may fail if on old device. + start_time = time.time() + if 'actor' in uploadResponse and 'BulkActor' in uploadResponse['actor']: # New B2G 2.0 hotness: binary data transfer + packageUploadActor = uploadResponse['actor'] + send_b2g_bulk_data(packageUploadActor, data) + else: # Old B2G 1.4 and older, serialize binary data in JSON text strings (SLOW!) + print 'Bulk upload is not supported, uploading binary data with old slow format. Consider flashing your device to FFOS 2.0 or newer to enjoy faster upload speeds.' + uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage') + packageUploadActor = uploadResponse['actor'] + chunk_size = 4*1024*1024 + i = 0 + while i < file_size: + chunk = data[i:i+chunk_size] + + send_b2g_data_chunk(packageUploadActor, chunk) + i += chunk_size + bytes_uploaded = min(i, file_size) + cur_time = time.time() + secs_elapsed = cur_time - start_time + percentage_done = bytes_uploaded * 1.0 / file_size + total_time = secs_elapsed / percentage_done + time_left = total_time - secs_elapsed + print sizeof_fmt(bytes_uploaded) + " uploaded, {:5.1f} % done.".format(percentage_done*100.0) + ' Elapsed: ' + str(int(secs_elapsed)) + ' seconds. Time left: ' + str(datetime.timedelta(seconds=int(time_left))) + '. Data rate: {:5.2f} KB/second.'.format(bytes_uploaded / 1024.0 / secs_elapsed) + + app_local_id = str(uuid.uuid4()) + reply = send_b2g_cmd(webappsActorName, 'install', { 'appId': app_local_id, 'upload': packageUploadActor }) + cur_time = time.time() + secs_elapsed = cur_time - start_time + print 'Upload of ' + sizeof_fmt(file_size) + ' finished. Total time elapsed: ' + str(int(secs_elapsed)) + ' seconds. Data rate: {:5.2f} KB/second.'.format(file_size / 1024.0 / secs_elapsed) + if not 'appId' in reply: + print 'Error: Application install failed! ' + str(reply) + sys.exit() + return reply['appId'] + +def b2g_app_command(app_command, app_name): + apps = b2g_get_appslist() + for app in apps: + if str(app['localId']) == app_name or app['name'] == app_name or app['manifestURL'] == app_name or app['id'] == app_name: + send_b2g_cmd(webappsActorName, app_command, { 'manifestURL': app['manifestURL'] }) + return 0 + print 'Error! Application "' + app_name + '" was not found! Use the \'list\' command to find installed applications.' + return 1 + +def b2g_memory(app_name): + apps = b2g_get_appslist() + appActor = '' + for app in apps: + if str(app['localId']) == app_name or app['name'] == app_name or app['manifestURL'] == app_name or app['id'] == app_name: + appActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] }) + break + if 'actor' in appActor: + memoryActor = appActor['actor']['memoryActor'] + measure = send_b2g_cmd(memoryActor, 'measure') + for k,v in measure.items(): + if k != 'from': + if k in ['otherSize', 'jsStringsSize', 'jsObjectsSize', 'styleSize', 'jsOtherSize', 'domSize', 'total']: # These are formatted in bytes + print k + ': ' + sizeof_fmt(v) + else: + print k + ': ' + str(v) + +def b2g_log(app_name, clear=False): + apps = b2g_get_appslist() + appActor = '' + for app in apps: + if str(app['localId']) == app_name or app['name'] == app_name or app['manifestURL'] == app_name or app['id'] == app_name: + appActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] }) + break + if 'actor' in appActor: + consoleActor = appActor['actor']['consoleActor'] + + if clear: + send_b2g_cmd(consoleActor, 'clearMessagesCache') + print 'Cleared message log.' + return 0 + + msgs = send_b2g_cmd(consoleActor, 'startListeners', { 'listeners': ['PageError','ConsoleAPI','NetworkActivity','FileActivity'] }) + + def log_b2g_message(msg): + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = "\033[1m" + msgs = [] + if 'type' in msg and msg['type'] == 'consoleAPICall': + msgs = [msg['message']] + elif 'messages' in msg: + msgs = msg['messages'] + + for m in msgs: + args = m['arguments'] + + for arg in args: + if m['level'] == 'log': + color = 'I/' + elif m['level'] == 'warn': + color = WARNING + 'W/' + elif m['level' |