aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xemcc4
-rw-r--r--emscripten-version.txt2
-rw-r--r--src/library.js13
-rw-r--r--src/library_fs.js5
-rw-r--r--src/library_idbfs.js3
-rw-r--r--src/library_memfs.js169
-rw-r--r--src/library_sdl.js2
-rw-r--r--src/modules.js25
-rw-r--r--src/settings.js4
-rw-r--r--system/local/include/README.txt2
-rw-r--r--tests/test_browser.py7
-rw-r--r--tests/test_core.py32
-rwxr-xr-xtools/ffdb.py501
-rw-r--r--tools/js-optimizer.js5
-rw-r--r--tools/shared.py2
15 files changed, 560 insertions, 216 deletions
diff --git a/emcc b/emcc
index 5e57bc37..20f04dd1 100755
--- a/emcc
+++ b/emcc
@@ -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'