summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/bindings_generator.py55
-rw-r--r--tools/bisect_pair_lines.py63
-rw-r--r--tools/eliminator/asm-eliminator-test-output.js129
-rw-r--r--tools/eliminator/asm-eliminator-test.js94
-rwxr-xr-xtools/ffdb.py453
-rw-r--r--tools/js-optimizer.js396
-rw-r--r--tools/js_optimizer.py28
-rw-r--r--tools/jsrun.py9
-rw-r--r--tools/shared.py189
-rw-r--r--tools/system_libs.py65
-rw-r--r--tools/test-js-optimizer-asm-last-output.js27
-rw-r--r--tools/test-js-optimizer-asm-last.js33
-rw-r--r--tools/test-js-optimizer-asm-pre-f32.js8
-rw-r--r--tools/test-js-optimizer-asm-pre-output-f32.js8
-rw-r--r--tools/test-js-optimizer-asm-pre-output.js78
-rw-r--r--tools/test-js-optimizer-asm-pre.js79
-rw-r--r--tools/test-js-optimizer-asm-regs-harder-output.js5
-rw-r--r--tools/test-js-optimizer-asm-regs-harder.js8
-rw-r--r--tools/webidl_binder.py433
19 files changed, 1945 insertions, 215 deletions
diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py
index 5bf0996e..9bc8b929 100755
--- a/tools/bindings_generator.py
+++ b/tools/bindings_generator.py
@@ -1,6 +1,61 @@
#!/usr/bin/env python2
'''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+XXX THIS IS DEPRECATED, see webidl_binder.py XXX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Use CppHeaderParser to parse some C++ headers, and generate binding code for them.
Usage:
diff --git a/tools/bisect_pair_lines.py b/tools/bisect_pair_lines.py
new file mode 100644
index 00000000..f698ef2a
--- /dev/null
+++ b/tools/bisect_pair_lines.py
@@ -0,0 +1,63 @@
+'''
+Given two similar files, for example one with an additional optimization pass,
+and with different results, will bisect between them to find the smallest
+diff that makes the outputs different.
+Unlike bisect_pairs, this uses lines instead of diffs. We replace line by line. This assumes
+the programs differ on each line but lines have not been added or removed
+'''
+
+import os, sys, shutil
+from subprocess import Popen, PIPE, STDOUT
+
+__rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+def path_from_root(*pathelems):
+ return os.path.join(__rootpath__, *pathelems)
+exec(open(path_from_root('tools', 'shared.py'), 'r').read())
+
+file1 = open(sys.argv[1]).read()
+file2 = open(sys.argv[2]).read()
+
+leftf = open('left', 'w')
+leftf.write(file1)
+leftf.close()
+
+rightf = open('right', 'w')
+rightf.write(file2)
+rightf.close()
+
+def run_code(name):
+ ret = run_js(name, stderr=PIPE, full_output=True)
+ # fix stack traces
+ ret = filter(lambda line: not line.startswith(' at ') and not name in line, ret.split('\n'))
+ return '\n'.join(ret)
+
+print 'running files'
+left_result = run_code('left')
+right_result = run_code('right') # right as in left-right, not as in correct
+assert left_result != right_result
+
+low = 0
+high = file1.count('\n')
+
+print 'beginning bisection, %d lines' % high
+
+left_lines = file1.split('\n')
+right_lines = file2.split('\n')
+
+while True:
+ mid = int((low + high)/2)
+ print low, high, ' current: %d' % mid,
+ open('middle', 'w').write('\n'.join(left_lines[:mid] + right_lines[mid:]))
+ shutil.copyfile('middle', 'middle' + str(mid))
+ result = run_code('middle')
+ print result == left_result, result == right_result#, 'XXX', left_result, 'YYY', result, 'ZZZ', right_result
+ if mid == low or mid == high: break
+ if result == right_result:
+ low = mid
+ elif result == left_result:
+ high = mid
+ else:
+ raise Exception('new result!?!?')
+
+print 'middle%d is like left, middle%d is like right' % (mid+1, mid)
+
diff --git a/tools/eliminator/asm-eliminator-test-output.js b/tools/eliminator/asm-eliminator-test-output.js
index 9caf99d0..ab4c13cc 100644
--- a/tools/eliminator/asm-eliminator-test-output.js
+++ b/tools/eliminator/asm-eliminator-test-output.js
@@ -24,7 +24,7 @@ function __Z11printResultPiS_j($needle, $haystack, $len) {
}
function _segment_holding($addr) {
$addr = $addr | 0;
- var $sp_0 = 0, $3 = 0, $12 = 0, $_0 = 0, label = 0;
+ var $sp_0 = 0, $3 = 0, $_0 = 0, label = 0;
$sp_0 = __gm_ + 444 | 0;
while (1) {
$3 = HEAP32[(($sp_0 | 0) & 16777215) >> 2] | 0;
@@ -35,13 +35,11 @@ function _segment_holding($addr) {
break;
}
}
- $12 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0;
- if (($12 | 0) == 0) {
+ $sp_0 = HEAP32[(($sp_0 + 8 | 0) & 16777215) >> 2] | 0;
+ if (($sp_0 | 0) == 0) {
$_0 = 0;
label = 1659;
break;
- } else {
- $sp_0 = $12;
}
}
if (label == 1659) {
@@ -147,27 +145,25 @@ function looop3() {
}
}
function looop4() {
- var i = 0, helper = 0;
+ var i = 0, i$looptemp = 0;
while (1) {
do_it();
- helper = i + 1 | 0;
- f(i, helper);
- if (condition()) {
- i = helper;
- } else {
+ i$looptemp = i;
+ i = i + 1 | 0;
+ f(i$looptemp, i);
+ if (!condition()) {
break;
}
}
}
function looop4b() {
- var i = 0, helper = 0;
+ var i = 0, i$looptemp = 0;
while (1) {
do_it();
- helper = i + 1 | 0;
- g(helper);
- if (condition(i)) {
- i = helper;
- } else {
+ i$looptemp = i;
+ i = i + 1 | 0;
+ g(i);
+ if (!condition(i$looptemp)) {
break;
}
}
@@ -251,24 +247,22 @@ function multiloop($n_0, $35) {
function multiloop2($n_0, $35) {
$n_0 = $n_0 | 0;
$35 = $35 | 0;
- var $p_0 = 0, $39 = 0, $41 = 0, $46 = 0;
+ var $p_0 = 0, $41 = 0, $p_0$looptemp = 0;
$n_0 = $35;
$p_0 = (HEAP32[$15 >> 2] | 0) + ($35 << 1) | 0;
while (1) {
- $39 = $p_0 - 2 | 0;
- $41 = HEAPU16[$39 >> 1] | 0;
+ $p_0$looptemp = $p_0;
+ $p_0 = $p_0 - 2 | 0;
+ $41 = HEAPU16[$p_0 >> 1] | 0;
if ($41 >>> 0 < $2 >>> 0) {
$_off0 = 0;
} else {
$_off0 = $41 - $2 & 65535;
}
- HEAP16[$39 >> 1] = $p_0;
- $46 = $n_0 - 1 | 0;
- if (($46 | 0) == 0) {
+ HEAP16[$p_0 >> 1] = $p_0$looptemp;
+ $n_0 = $n_0 - 1 | 0;
+ if (($n_0 | 0) == 0) {
break;
- } else {
- $n_0 = $46;
- $p_0 = $39;
}
}
}
@@ -822,7 +816,7 @@ function selfAssign() {
function elimOneLoopVar($argc, $argv) {
$argc = $argc | 0;
$argv = $argv | 0;
- var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $inc = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0;
+ var $arg$0 = 0, $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $ok$0 = 0, $primes$011 = 0, $retval$0 = 0, $vararg_buffer1 = 0;
$curri$012 = 2;
$primes$011 = 0;
while (1) {
@@ -831,14 +825,12 @@ function elimOneLoopVar($argc, $argv) {
if ($call10 > Math_fround(+2)) {
$j$010 = 2;
while (1) {
- $inc = $j$010 + 1 | 0;
if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) {
$ok$0 = 0;
break L15;
}
- if (Math_fround($inc | 0) < $call10) {
- $j$010 = $inc;
- } else {
+ $j$010 = $j$010 + 1 | 0;
+ if (!(Math_fround($j$010 | 0) < $call10)) {
$ok$0 = 1;
break;
}
@@ -901,4 +893,79 @@ function elimOneLoopVar4() {
}
}
}
+function elimOneLoopVarStillUsed() {
+ var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0;
+ while (1) {
+ if ((($curri$012 | 0) % ($j$010 | 0) & -1 | 0) == 0) {
+ break;
+ }
+ $j$010 = $j$010 + 1 | 0;
+ if (!(Math_fround($j$010 | 0) < $call10)) {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVarStillUsedSE() {
+ var $call10 = Math_fround(0), $curri$012 = 0, $j$010 = 0, $retval$0 = 0, $j$010$looptemp = 0;
+ while (1) {
+ $j$010$looptemp = $j$010;
+ $j$010 = $j$010 + sideeffect() | 0;
+ if ((($curri$012 | 0) % ($j$010$looptemp | 0) & -1 | 0) == 0) {
+ break;
+ }
+ if (!(Math_fround($j$010 | 0) < $call10)) {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVar5() {
+ var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0;
+ $storemerge3$neg9 = -1;
+ while (1) {
+ $25 = $jp + ($26 << 2) | 0;
+ HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0;
+ $30 = $26 + 1 | 0;
+ if (($30 | 0) == 63) {
+ f($30);
+ break;
+ } else {
+ $storemerge3$neg9 = $18 ^ -1;
+ $26 = $30;
+ }
+ }
+}
+function loopVarWithContinue() {
+ var i = 0, i$looptemp = 0;
+ i = 0;
+ while (1) {
+ i$looptemp = i;
+ i = i + 1;
+ if (check()) {
+ i = i$looptemp + 1;
+ continue;
+ }
+ work(i);
+ work(i$looptemp);
+ work(i);
+ if (check()) {
+ break;
+ }
+ }
+}
+function helperExtraUse() {
+ var i = 0, i$looptemp = 0;
+ i = 0;
+ while (1) {
+ i$looptemp = i;
+ i = i + 1;
+ work(i$looptemp);
+ work(i);
+ if (check()) {
+ break;
+ }
+ }
+ return i;
+}
diff --git a/tools/eliminator/asm-eliminator-test.js b/tools/eliminator/asm-eliminator-test.js
index a3de3d9d..7b949c44 100644
--- a/tools/eliminator/asm-eliminator-test.js
+++ b/tools/eliminator/asm-eliminator-test.js
@@ -1131,5 +1131,97 @@ function elimOneLoopVar4() {
}
}
}
-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4"]
+function elimOneLoopVarStillUsed() {
+ var $0 = 0, $1 = 0, $arg$0 = 0, $arrayidx = 0, $call10 = Math_fround(0), $cmp = 0, $cmp11 = 0, $cmp119 = 0, $cmp12 = 0, $cmp7 = 0, $conv = 0, $conv8 = Math_fround(0), $conv9 = Math_fround(0), $curri$012 = 0, $inc = 0, $inc14$primes$0 = 0, $inc16 = 0, $j$010 = 0, $ok$0 = 0;
+ var $primes$011 = 0, $rem = 0, $retval$0 = 0, $sub = 0, $vararg_buffer1 = 0, label = 0, sp = 0;
+ while (1) {
+ $rem = ($curri$012 | 0) % ($j$010 | 0) & -1;
+ $cmp12 = ($rem | 0) == 0;
+ $inc = $j$010 + 1 | 0;
+ if ($cmp12) {
+ $ok$0 = 0;
+ break;
+ }
+ $conv8 = Math_fround($inc | 0);
+ $cmp11 = $conv8 < $call10;
+ if ($cmp11) {
+ $j$010 = $inc;
+ } else {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVarStillUsedSE() {
+ var $0 = 0, $1 = 0, $arg$0 = 0, $arrayidx = 0, $call10 = Math_fround(0), $cmp = 0, $cmp11 = 0, $cmp119 = 0, $cmp12 = 0, $cmp7 = 0, $conv = 0, $conv8 = Math_fround(0), $conv9 = Math_fround(0), $curri$012 = 0, $inc = 0, $inc14$primes$0 = 0, $inc16 = 0, $j$010 = 0, $ok$0 = 0;
+ var $primes$011 = 0, $rem = 0, $retval$0 = 0, $sub = 0, $vararg_buffer1 = 0, label = 0, sp = 0;
+ while (1) {
+ $rem = ($curri$012 | 0) % ($j$010 | 0) & -1;
+ $cmp12 = ($rem | 0) == 0;
+ $inc = $j$010 + sideeffect() | 0; // side effect!
+ if ($cmp12) {
+ $ok$0 = 0;
+ break;
+ }
+ $conv8 = Math_fround($inc | 0);
+ $cmp11 = $conv8 < $call10;
+ if ($cmp11) {
+ $j$010 = $inc;
+ } else {
+ break;
+ }
+ }
+ return $retval$0 | 0;
+}
+function elimOneLoopVar5() {
+ var $storemerge3$neg9 = 0, $18 = 0, $25 = 0, $26 = 0, $30 = 0, $jp = 0;
+ $storemerge3$neg9 = -1;
+ while (1) {
+ $25 = $jp + ($26 << 2) | 0;
+ HEAP32[$25 >> 2] = ($18 + $storemerge3$neg9 | 0) + (HEAP32[$25 >> 2] | 0) | 0;
+ $30 = $26 + 1 | 0;
+ if (($30 | 0) == 63) {
+ f($30); // loop var used here, so cannot be easily optimized
+ break;
+ } else {
+ $storemerge3$neg9 = $18 ^ -1;
+ $26 = $30;
+ }
+ }
+}
+function loopVarWithContinue() {
+ var i = 0, inc = 0;
+ i = 0;
+ while (1) {
+ inc = i + 1;
+ if (check()) {
+ i = i + 1;
+ continue;
+ }
+ work(inc);
+ work(i);
+ work(inc);
+ if (check()) {
+ break;
+ } else {
+ i = inc;
+ }
+ }
+}
+function helperExtraUse() {
+ var i = 0, inc = 0;
+ i = 0;
+ while (1) {
+ inc = i + 1;
+ work(i);
+ work(inc);
+ if (check()) {
+ break;
+ } else {
+ i = inc;
+ }
+ }
+ return inc;
+}
+// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "__Z11printResultPiS_j", "_segment_holding", "__ZN5identC2EiPKcPci", "_vec2Length", "exc", "label", "confuusion", "tempDouble", "_org_apache_harmony_luni_util_NumberConverter_freeFormat__", "__ZN23b2EdgeAndPolygonContact8EvaluateEP10b2ManifoldRK11b2TransformS4_", "_java_nio_charset_Charset_forNameInternal___java_lang_String", "looop2", "looop3", "looop4", "looop5", "looop6", "looop7", "looop8", "multiloop", "multiloop2", "tempDouble2", "watIf", "select2", "binary", "cute", "selfAssign", "elimOneLoopVar", "elimOneLoopVar2", "elimOneLoopVar3", "elimOneLoopVar4", "elimOneLoopVarStillUsed", "elimOneLoopVarStillUsedSE", "elimOneLoopVar5", "helperExtraUse"]
diff --git a/tools/ffdb.py b/tools/ffdb.py
new file mode 100755
index 00000000..2171cb0e
--- /dev/null
+++ b/tools/ffdb.py
@@ -0,0 +1,453 @@
+#!/usr/bin/env python
+
+import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess, re, base64, struct, imghdr
+
+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
+b2g_socket = None # Python socket object for the active connection to the B2G device
+read_queue = '' # Inbound queue of partial data read so far from the device
+
+webappsActorName = None
+
+def sizeof_fmt(num):
+ for x in ['bytes','KB','MB','GB']:
+ if num < 1024.0:
+ return "%3.1f%s" % (num, x)
+ num /= 1024.0
+ return "%3.1f%s" % (num, 'TB')
+
+def zipdir(path, zipfilename):
+ try:
+ import zlib
+ zip_mode = zipfile.ZIP_DEFLATED
+ except:
+ zip_mode = zipfile.ZIP_STORED
+
+ zipf = zipfile.ZipFile(zipfilename, 'w', zip_mode)
+ files_to_compress = []
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ files_to_compress += [(root, file)]
+
+ n = 1
+ for tuple in files_to_compress:
+ (root, file) = tuple
+ filename = os.path.join(root, file)
+ filesize = os.path.getsize(filename)
+ path_in_archive = os.path.relpath(filename, path)
+ print 'Compressing ' + str(n) + '/' + str(len(files_to_compress)) + ': "' + path_in_archive + '" (' + sizeof_fmt(filesize) + ')...'
+ n += 1
+ zipf.write(os.path.join(root, file), path_in_archive)
+ zipf.close()
+ print 'Done. '
+
+# Returns given log message formatted to be outputted on a HTML page.
+def format_html(msg):
+ if not msg.endswith('\n'):
+ msg += '\n'
+ msg = cgi.escape(msg)
+ msg = msg.replace('\r\n', '<br />').replace('\n', '<br />')
+ return msg
+
+# Prints a verbose log message to stdout channel. Only shown if run with --verbose.
+def logv(msg):
+ if LOG_VERBOSE:
+ sys.stdout.write(format_html(msg) + '\n')
+ sys.stdout.flush()
+
+# Reads data from the socket, and tries to parse what we have got so far as a JSON message.
+# The messages are of form "bytelength:{jsondict}", where bytelength tells how many bytes
+# there are in the data that comes after the colon.
+# 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)
+ payload = ''
+ while ':' in read_queue:
+ semicolon = read_queue.index(':')
+ payload_len = int(read_queue[:semicolon])
+ if semicolon+1+payload_len > len(read_queue):
+ read_queue += b2g_socket.recv(65536*2)
+ continue
+ payload = read_queue[semicolon+1:semicolon+1+payload_len]
+ read_queue = read_queue[semicolon+1+payload_len:]
+ logv('Read a message of size ' + str(payload_len) + 'b from socket.')
+ payload = json.loads(payload)
+ # 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']
+ return payload
+
+# Sends a command to the B2G device and waits for the response and returns it as a JSON dict.
+def send_b2g_cmd(to, cmd, data = {}, print_errors_to_console = True):
+ global b2g_socket
+ msg = { 'to': to, 'type': cmd}
+ msg = dict(msg.items() + data.items())
+ msg = json.dumps(msg, encoding='latin-1')
+ msg = msg.replace('\\\\', '\\')
+ msg = str(len(msg))+':'+msg
+ logv('Sending cmd:' + cmd + ' to:' + to)
+ b2g_socket.sendall(msg)
+ return read_b2g_response(print_errors_to_console)
+
+def escape_bytes(b):
+ return str(b)
+
+# Sends a data fragment of a packaged app upload. This is a special-case version of the send_b2g_cmd
+# command optimized for performance.
+def send_b2g_data_chunk(to, data_blob):
+ byte_str = []
+ e = '\u0000'
+ # '"' == 34
+ # '\' == 92
+ i = 0
+ while i < len(data_blob):
+ o = ord(data_blob[i])
+# if o == 34 or o == 92 or o >= 128 or o <= 32:#o <= 32 or o >= 36:# or o == ord('\\'):
+ if o <= 34 or o >= 128 or o == 92:
+ c = hex(o)[2:]
+ byte_str += e[:-len(c)] + c
+ else:
+ byte_str += data_blob[i]
+ i += 1
+ message = '{"to":"'+to+'","type":"chunk","chunk":"' + ''.join(byte_str) + '"}'
+ message = str(len(message)) + ':' + message
+ logv('{"to":"'+to+'","type":"chunk","chunk":"<data>"}')
+ b2g_socket.sendall(message)
+ return read_b2g_response()
+
+def send_b2g_bulk_data(to, data_blob):
+ message = 'bulk ' + to + ' stream ' + str(len(data_blob)) + ':'
+ logv(message)
+ b2g_socket.sendall(message)
+ b2g_socket.sendall(data_blob)
+ # It seems that B2G doesn't send any response JSON back after a bulk transfer is finished, so no read_b2g_response() here.
+
+# Queries the device for a list of all installed apps.
+def b2g_get_appslist():
+ global webappsActorName
+ apps = send_b2g_cmd(webappsActorName, 'getAll')
+ return apps['apps']
+
+# Queries the device for a list of all currently running apps.
+def b2g_get_runningapps():
+ global webappsActorName
+ apps = send_b2g_cmd(webappsActorName, 'listRunningApps')
+ return apps['apps'] # Returns manifestURLs of all running apps
+
+def print_applist(applist, running_app_manifests, print_removable):
+ num_printed = 0
+ for app in applist:
+ if print_removable or app['removable']: # Print only removable apps unless --all is specified, skip the built-in apps that can't be uninstalled.
+ if 'manifest' in app and 'version' in app['manifest']:
+ version = " version '" + app['manifest']['version'] + "'"
+ else:
+ version = ''
+ if app['manifestURL'] in running_app_manifests:
+ version += ' RUNNING'
+ print ' ' + str(app['localId']) + ': "' + app['name'] + '"' + version
+ num_printed += 1
+ return num_printed
+
+def main():
+ global b2g_socket, webappsActorName, HOST, PORT, VERBOSE
+ if len(sys.argv) < 2 or '--help' in sys.argv or 'help' in sys.argv or '-v' in sys.argv:
+ print '''Firefox OS Debug Bridge, a tool for automating FFOS device tasks from the command line.
+
+ Usage: ffdb.py <command>, where command is one of:
+
+ list [--running] [--all]: Prints out the user applications installed on the device.
+ If --running is passed, only the currently opened apps are shown.
+ If --all is specified, then also uninstallable system applications are listed.
+ launch <app>: Starts the given application. If already running, brings to front.
+ close <app>: Terminates the execution of the given application.
+ uninstall <app>: Removes the given application from the device.
+ install <path>: Uploads and installs a packaged app that resides in the given local directory.
+ <path> may either refer to a directory containing a packaged app, or to a prepackaged zip file.
+ log <app> [--clear]: Starts a persistent log listener that reads web console messages from the given application.
+ If --clear is passed, the message log for that application is cleared instead.
+ navigate <url>: Opens the given web page in the B2G browser.
+ screenshot [filename.png]: Takes a screenshot of the current contents displayed on the device. If an optional
+ filename is specified, the screenshot is saved to that file. Otherwise the filename
+ will be autogenerated.
+
+ Options: Additionally, the following options may be passed to control FFDB execution:
+
+ --host <hostname>: Specifies the target network address to connect to. Default: 'localhost'.
+ --port <number>: Specifies the network port to connect to. Default: 6000.
+ --verbose: Enables verbose printing, mostly useful for debugging.
+ --simulator: Signal that we will be connecting to a FFOS simulator and not a real device.
+
+ In the above, whenever a command requires an <app> to be specified, either the human-readable name,
+ localId or manifestURL of the application can be used.'''
+
+ sys.exit(0)
+
+ connect_to_simulator = False
+
+ options_with_value = ['--host', '--port']
+ options = options_with_value + ['--verbose', '--simulator']
+ # Process options
+ for i in range(0, len(sys.argv)):
+ if sys.argv[i] in options_with_value:
+ if i+1 >= sys.argv or sys.argv[i+1].startswith('-'):
+ print >> sys.stderr, "Missing value for option " + sys.argv[i] +'!'
+ sys.exit(1)
+ if sys.argv[i] == '--host':
+ HOST = sys.argv[i+1]
+ elif sys.argv[i] == '--port':
+ PORT = int(sys.argv[i+1])
+ elif sys.argv[i] == '--verbose':
+ VERBOSE = True
+ elif sys.argv[i] == '--simulator':
+ connect_to_simulator = True
+
+ # Clear the processed options so that parsing the commands below won't trip up on these.
+ if sys.argv[i] in options: sys.argv[i] = ''
+ if sys.argv[i] in options_with_value: sys.argv[i+1] = ''
+
+ sys.argv = filter(lambda x: len(x) > 0, sys.argv)
+
+ b2g_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ b2g_socket.connect((HOST, PORT))
+ except Exception, e:
+ if e[0] == 61: # Connection refused
+ if (HOST == 'localhost' or HOST == '127.0.0.1') and not connect_to_simulator:
+ cmd = ['adb', 'forward', 'tcp:'+str(PORT), 'localfilesystem:/data/local/debugger-socket']
+ print 'Connection to ' + HOST + ':' + str(PORT) + ' refused, attempting to forward device debugger-socket to local address by calling ' + str(cmd) + ':'
+ else:
+ print 'Error! Failed to connect to B2G ' + ('simulator' if connect_to_simulator else 'device') + ' debugger socket at address ' + HOST + ':' + str(PORT) + '!'
+ sys.exit(1)
+ try:
+ retcode = subprocess.check_call(cmd)
+ except Exception, e:
+ print 'Error! Failed to execute adb: ' + str(e)
+ print "Check that the device is connected properly, call 'adb devices' to list the detected devices."
+ sys.exit(1)
+ if retcode is not 0:
+ print 'Error! Failed to connect to B2G device and executing adb failed with return code ' + retcode + '!'
+ sys.exit(1)
+ time.sleep(3)
+ # Try again:
+ try:
+ b2g_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ b2g_socket.connect((HOST, PORT))
+ except Exception, e:
+ print 'Error! Failed to connect to B2G device debugger socket at address ' + HOST + ':' + str(PORT) + '!'
+ sys.exit(1)
+
+ handshake = read_b2g_response()
+ logv('Connected. Handshake: ' + str(handshake))
+
+ data = send_b2g_cmd('root', 'listTabs')
+ deviceActorName = data['deviceActor']
+ logv('deviceActor: ' + deviceActorName)
+ webappsActorName = data['webappsActor']
+ logv('webappsActor: ' + webappsActorName)
+
+ send_b2g_cmd(deviceActorName, 'getDescription')
+ send_b2g_cmd(deviceActorName, 'getRawPermissionsTable')
+
+ apps = b2g_get_appslist()
+
+ if sys.argv[1] == 'list':
+ running_app_manifests = b2g_get_runningapps()
+ printed_apps = apps
+ print_only_running = '--running' in sys.argv and not '--all' in sys.argv
+ if print_only_running: # Print running apps only?
+ print 'Running applications by id:'
+ printed_apps = filter(lambda x: x['manifestURL'] in running_app_manifests, apps)
+ else:
+ print 'Installed applications by id:'
+ num_printed = print_applist(printed_apps, running_app_manifests, '--all' in sys.argv or print_only_running)
+ if num_printed == 0:
+ if print_only_running:
+ print ' No applications running.'
+ else:
+ print ' No applications installed.'
+ if not '--all' in sys.argv and not print_only_running:
+ print 'Not showing built-in apps that cannot be uninstalled. Pass --all to include those in the listing.'
+ elif sys.argv[1] == 'launch' or sys.argv[1] == 'close' or sys.argv[1] == 'uninstall' or sys.argv[1] == 'getAppActor':
+ if len(sys.argv) < 3:
+ print 'Error! No application name given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <app>'
+ return 1
+ for app in apps:
+ if str(app['localId']) == sys.argv[2] or app['name'] == sys.argv[2] or app['manifestURL'] == sys.argv[2]:
+ send_b2g_cmd(webappsActorName, sys.argv[1], { 'manifestURL': app['manifestURL'] })
+ return 0
+ print 'Error! Application "' + sys.argv[2] + '" was not found! Use the \'list\' command to find installed applications.'
+ return 1
+ elif sys.argv[1] == 'install':
+ if len(sys.argv) < 3:
+ print 'Error! No application path given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <path>'
+ return 1
+ target_app_path = sys.argv[2]
+ 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)
+
+ send_b2g_cmd(webappsActorName, 'install', { 'appId': str(uuid.uuid4()), '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)
+ elif sys.argv[1] == 'navigate':
+ if len(sys.argv) < 3:
+ print 'Error! No URL given! Usage: ' + sys.argv[0] + ' ' + sys.argv[1] + ' <url>'
+ return 1
+ browserActor = ''
+ for app in apps:
+ if app['name'] == 'Browser':
+ browserActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] })
+ break
+ if 'actor' in browserActor:
+ browserActor = browserActor['actor']['actor']
+ send_b2g_cmd(browserActor, 'navigateTo', { 'url': sys.argv[2]})
+ else:
+ print 'Web browser is not running!'
+ elif sys.argv[1] == 'log':
+ appActor = ''
+ for app in apps:
+ if str(app['localId']) == sys.argv[2] or app['name'] == sys.argv[2] or app['manifestURL'] == sys.argv[2]:
+ appActor = send_b2g_cmd(webappsActorName, 'getAppActor', { 'manifestURL': app['manifestURL'] })
+ break
+ if 'actor' in appActor:
+ consoleActor = appActor['actor']['consoleActor']
+
+ if '-c' in sys.argv or '-clear' in sys.argv or '--clear' in sys.argv:
+ send_b2g_cmd(consoleActor, 'clearMessagesCache')
+ print 'Cleared message log.'
+ sys.exit(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'] == 'error':
+ color = FAIL + 'E/'
+ else:
+ color = m['level'] + '/'
+
+ print color + str(m['functionName']) + '@' + str(m['filename']) + ':' + str(m['lineNumber']) + ': ' + str(arg) + ENDC
+
+ msgs = send_b2g_cmd(consoleActor, 'getCachedMessages', { 'messageTypes': ['PageError', 'ConsoleAPI'] })
+ log_b2g_message(msgs)
+
+ while True:
+ msg = read_b2g_response()
+ log_b2g_message(msg)
+ else:
+ print 'Application "' + sys.argv[2] + '" is not running!'
+ elif sys.argv[1] == 'screenshot':
+ if len(sys.argv) >= 3:
+ filename = sys.argv[2]
+ if not filename.endswith('.png'):
+ print >> sys.stderr, "Writing screenshots only to .png files are supported!"
+ sys.exit(1)
+ else:
+ filename = time.strftime("screen_%Y%m%d_%H%M%S.png", time.gmtime())
+
+ data_reply = send_b2g_cmd(deviceActorName, 'screenshotToDataURL')
+ data = data_reply['value']
+ data_get_actor = data['actor']
+ data_len = int(data['length'])
+ data_str = data['initial']
+ delim = re.search(",", data_str).start()
+ data_format = data_str[:delim]
+ if data_format != "data:image/png;base64":
+ print >> sys.stderr, "Error: Received screenshot from device in an unexpected format '" + data_format + "'!"
+ sys.exit(1)
+ data = data_str[delim+1:]
+ chunk_size = 65000
+ pos = len(data_str)
+ while pos < data_len:
+ bytes_to_read = min(data_len - pos, chunk_size)
+ data_reply = send_b2g_cmd(data_get_actor, 'substring', { 'start': str(pos), 'end': str(pos + bytes_to_read) })
+ if len(data_reply['substring']) != bytes_to_read:
+ print >> sys.stderr, 'Error! Expected to receive ' + str(bytes_to_read) + ' bytes of image data, but got ' + str(len(data_reply['substring'])) + ' bytes instead!'
+ sys.exit(1)
+ data += data_reply['substring']
+ pos += bytes_to_read
+ send_b2g_cmd(data_get_actor, 'release') # We need to explicitly free the screenshot image string from the device, or the Devtools connection leaks resources!
+ binary_data = base64.b64decode(data)
+ open(filename, 'wb').write(binary_data)
+
+ def get_png_image_size(filename):
+ fhandle = open(filename, 'rb')
+ head = fhandle.read(24)
+ if len(head) != 24:
+ return (-1, -1)
+ check = struct.unpack('>i', head[4:8])[0]
+ if check != 0x0d0a1a0a:
+ return (-1, -1)
+ return struct.unpack('>ii', head[16:24])
+
+ width, height = get_png_image_size(filename)
+ if width <= 0 or height <= 0:
+ print >> sys.stderr, "Wrote " + sizeof_fmt(len(binary_data)) + " to file '" + filename + "', but the contents may be corrupted!"
+ else:
+ print "Wrote " + sizeof_fmt(len(binary_data)) + " to file '" + filename + "' (" + str(width) + 'x' + str(height) + ' pixels).'
+ else:
+ print "Unknown command '" + sys.argv[1] + "'! Pass --help for instructions."
+
+ b2g_socket.close()
+ return 0
+
+if __name__ == '__main__':
+ returncode = main()
+ logv('ffdb.py quitting with process exit code ' + str(returncode))
+ sys.exit(returncode)
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js
index c4585b84..9b8387be 100644
--- a/tools/js-optimizer.js
+++ b/tools/js-optimizer.js
@@ -136,6 +136,7 @@ var CONTROL_FLOW = set('do', 'while', 'for', 'if', 'switch');
var NAME_OR_NUM = set('name', 'num');
var ASSOCIATIVE_BINARIES = set('+', '*', '|', '&', '^');
var ALTER_FLOW = set('break', 'continue', 'return');
+var BITWISE = set('|', '&', '^');
var BREAK_CAPTURERS = set('do', 'while', 'for', 'switch');
var CONTINUE_CAPTURERS = LOOP;
@@ -387,14 +388,18 @@ function simplifyExpressions(ast) {
return innerNode;
}
}
- } else if (type === 'binary' && node[1] === '&' && node[3][0] === 'num') {
- // Rewrite (X < Y) & 1 to (X < Y)|0. (Subsequent passes will eliminate
- // the |0 if possible.)
- var input = node[2];
- var amount = node[3][1];
- if (input[0] === 'binary' && (input[1] in COMPARE_OPS) && amount == 1) {
- node[1] = '|';
- node[3][1] = 0;
+ } else if (type === 'binary' && (node[1] in BITWISE)) {
+ for (var i = 2; i <= 3; i++) {
+ var subNode = node[i];
+ if (subNode[0] === 'binary' && subNode[1] === '&' && subNode[3][0] === 'num' && subNode[3][1] == 1) {
+ // Rewrite (X < Y) & 1 to X < Y , when it is going into a bitwise operator. We could
+ // remove even more (just replace &1 with |0, then subsequent passes could remove the |0)
+ // but v8 issue #2513 means the code would then run very slowly in chrome.
+ var input = subNode[2];
+ if (input[0] === 'binary' && (input[1] in COMPARE_OPS)) {
+ node[i] = input;
+ }
+ }
}
}
});
@@ -749,11 +754,74 @@ function simplifyExpressions(ast) {
});
}
+ function emitsBoolean(node) {
+ if (node[0] === 'num') {
+ return node[1] === 0 || node[1] === 1;
+ }
+ if (node[0] === 'binary') return node[1] in COMPARE_OPS;
+ if (node[0] === 'unary-prefix') return node[1] === '!';
+ if (node[0] === 'conditional') return emitsBoolean(node[2]) && emitsBoolean(node[3]);
+ return false;
+ }
+
+ // expensive | expensive can be turned into expensive ? 1 : expensive, and
+ // expensive | cheap can be turned into cheap ? 1 : expensive,
+ // so that we can avoid the expensive computation, if it has no side effects.
+ function conditionalize(ast) {
+ var MIN_COST = 7;
+ traverse(ast, function(node, type) {
+ if (node[0] === 'binary' && (node[1] === '|' || node[1] === '&') && node[3][0] !== 'num' && node[2][0] !== 'num') {
+ // logical operator on two non-numerical values
+ var left = node[2];
+ var right = node[3];
+ if (!emitsBoolean(left) || !emitsBoolean(right)) return;
+ var leftEffects = hasSideEffects(left);
+ var rightEffects = hasSideEffects(right);
+ if (leftEffects && rightEffects) return; // both must execute
+ // canonicalize with side effects, if any, happening on the left
+ if (rightEffects) {
+ if (measureCost(left) < MIN_COST) return; // avoidable code is too cheap
+ var temp = left;
+ left = right;
+ right = temp;
+ } else if (leftEffects) {
+ if (measureCost(right) < MIN_COST) return; // avoidable code is too cheap
+ } else {
+ // no side effects, reorder based on cost estimation
+ var leftCost = measureCost(left);
+ var rightCost = measureCost(right);
+ if (Math.max(leftCost, rightCost) < MIN_COST) return; // avoidable code is too cheap
+ // canonicalize with expensive code on the right
+ if (leftCost > rightCost) {
+ var temp = left;
+ left = right;
+ right = temp;
+ }
+ }
+ // worth it, perform conditionalization
+ var ret;
+ if (node[1] === '|') {
+ ret = ['conditional', left, ['num', 1], right];
+ } else { // &
+ ret = ['conditional', left, right, ['num', 0]];
+ }
+ if (left[0] === 'unary-prefix' && left[1] === '!') {
+ ret[1] = flipCondition(left);
+ var temp = ret[2];
+ ret[2] = ret[3];
+ ret[3] = temp;
+ }
+ return ret;
+ }
+ });
+ }
+
traverseGeneratedFunctions(ast, function(func) {
simplifyIntegerConversions(func);
simplifyBitops(func);
joinAdditions(func);
simplifyNotComps(func);
+ conditionalize(func);
// simplifyZeroComp(func); TODO: investigate performance
});
}
@@ -970,10 +1038,32 @@ function hasSideEffects(node) { // this is 99% incomplete!
}
return false;
}
+ case 'conditional': return hasSideEffects(node[1]) || hasSideEffects(node[2]) || hasSideEffects(node[3]);
default: return true;
}
}
+// checks if a node has just basic operations, nothing with side effects nor that can notice side effects, which
+// implies we can move it around in the code
+function triviallySafeToMove(node, asmData) {
+ var ok = true;
+ traverse(node, function(node, type) {
+ switch (type) {
+ case 'stat': case 'binary': case 'unary-prefix': case 'assign': case 'num':
+ break;
+ case 'name':
+ if (!(node[1] in asmData.vars) && !(node[1] in asmData.params)) ok = false;
+ break;
+ case 'call':
+ if (callHasSideEffects(node)) ok = false;
+ break;
+ default:
+ ok = false;
+ }
+ });
+ return ok;
+}
+
// Clear out empty ifs and blocks, and redundant blocks/stats and so forth
// Operates on generated functions only
function vacuum(ast) {
@@ -1342,13 +1432,21 @@ var ASM_DOUBLE = 1;
var ASM_FLOAT = 2;
var ASM_NONE = 3;
-function detectAsmCoercion(node, asmInfo) {
+var ASM_FLOAT_ZERO = null; // TODO: share the entire node?
+
+function detectAsmCoercion(node, asmInfo, inVarDef) {
// for params, +x vs x|0, for vars, 0.0 vs 0
if (node[0] === 'num' && node[1].toString().indexOf('.') >= 0) return ASM_DOUBLE;
if (node[0] === 'unary-prefix') return ASM_DOUBLE;
if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround') return ASM_FLOAT;
if (asmInfo && node[0] == 'name') return getAsmType(node[1], asmInfo);
- if (node[0] === 'name') return ASM_NONE;
+ if (node[0] === 'name') {
+ if (!inVarDef) return ASM_NONE;
+ // We are in a variable definition, where Math_fround(0) optimized into a global constant becomes f0 = Math_fround(0)
+ if (!ASM_FLOAT_ZERO) ASM_FLOAT_ZERO = node[1];
+ else assert(ASM_FLOAT_ZERO === node[1]);
+ return ASM_FLOAT;
+ }
return ASM_INT;
}
@@ -1366,7 +1464,13 @@ function makeAsmVarDef(v, type) {
switch (type) {
case ASM_INT: return [v, ['num', 0]];
case ASM_DOUBLE: return [v, ['unary-prefix', '+', ['num', 0]]];
- case ASM_FLOAT: return [v, ['call', ['name', 'Math_fround'], [['num', 0]]]];
+ case ASM_FLOAT: {
+ if (ASM_FLOAT_ZERO) {
+ return [v, ['name', ASM_FLOAT_ZERO]];
+ } else {
+ return [v, ['call', ['name', 'Math_fround'], [['num', 0]]]];
+ }
+ }
default: throw 'wha? ' + JSON.stringify([node, type]) + new Error().stack;
}
}
@@ -1409,9 +1513,7 @@ function normalizeAsm(func) {
var name = v[0];
var value = v[1];
if (!(name in data.vars)) {
- assert(value[0] === 'num' || (value[0] === 'unary-prefix' && value[2][0] === 'num') // must be valid coercion no-op
- || (value[0] === 'call' && value[1][0] === 'name' && value[1][1] === 'Math_fround'));
- data.vars[name] = detectAsmCoercion(value);
+ data.vars[name] = detectAsmCoercion(value, null, true);
v.length = 1; // make an un-assigning var
} else {
assert(j === 0, 'cannot break in the middle');
@@ -1425,22 +1527,6 @@ function normalizeAsm(func) {
traverse(stats[i], function(node, type) {
if (type === 'var') {
assert(0, 'should be no vars to fix! ' + func[1] + ' : ' + JSON.stringify(node));
- /*
- for (var j = 0; j < node[1].length; j++) {
- var v = node[1][j];
- var name = v[0];
- var value = v[1];
- if (!(name in data.vars)) {
- if (value[0] != 'name') {
- data.vars[name] = detectAsmCoercion(value); // detect by coercion
- } else {
- var origin = value[1];
- data.vars[name] = data.vars[origin] || ASM_INT; // detect by origin variable, or assume int for non-locals
- }
- }
- }
- unVarify(node[1], node);
- */
} else if (type === 'call' && node[1][0] === 'function') {
assert(!node[1][1]); // anonymous functions only
data.inlines.push(node[1]);
@@ -2001,12 +2087,18 @@ function registerizeHarder(ast) {
function pushActiveLabels(onContinue, onBreak) {
// Push the target junctions for continuing/breaking a loop.
// This should be called before traversing into a loop.
- var newLabels = copy(activeLabels[activeLabels.length-1]);
+ var prevLabels = activeLabels[activeLabels.length-1];
+ var newLabels = copy(prevLabels);
newLabels[null] = [onContinue, onBreak];
if (nextLoopLabel) {
newLabels[nextLoopLabel] = [onContinue, onBreak];
nextLoopLabel = null;
}
+ // An unlabelled 'continue' should jump to innermost loop,
+ // ignoring any nested 'switch' statements.
+ if (onContinue === null && prevLabels[null]) {
+ newLabels[null][0] = prevLabels[null][0];
+ }
activeLabels.push(newLabels);
}
@@ -2156,18 +2248,30 @@ function registerizeHarder(ast) {
break;
case 'conditional':
isInExpr++;
- buildFlowGraph(node[1]);
- var jEnter = markJunction();
- var jExit = addJunction();
- if (node[2]) {
- buildFlowGraph(node[2]);
- }
- joinJunction(jExit);
- setJunction(jEnter);
- if (node[3]) {
- buildFlowGraph(node[3]);
+ // If the conditional has no side-effects, we can treat it as a single
+ // block, which might open up opportunities to remove it entirely.
+ if (!hasSideEffects(node)) {
+ buildFlowGraph(node[1]);
+ if (node[2]) {
+ buildFlowGraph(node[2]);
+ }
+ if (node[3]) {
+ buildFlowGraph(node[3]);
+ }
+ } else {
+ buildFlowGraph(node[1]);
+ var jEnter = markJunction();
+ var jExit = addJunction();
+ if (node[2]) {
+ buildFlowGraph(node[2]);
+ }
+ joinJunction(jExit);
+ setJunction(jEnter);
+ if (node[3]) {
+ buildFlowGraph(node[3]);
+ }
+ joinJunction(jExit);
}
- joinJunction(jExit);
isInExpr--;
break;
case 'while':
@@ -3519,20 +3623,23 @@ function eliminate(ast, memSafe) {
seenUses[name]++;
}
} else if (type === 'while') {
+ if (!asm) return;
// try to remove loop helper variables specifically
var stats = node[2][1];
var last = stats[stats.length-1];
if (last && last[0] === 'if' && last[2][0] === 'block' && last[3] && last[3][0] === 'block') {
var ifTrue = last[2];
var ifFalse = last[3];
+ clearEmptyNodes(ifTrue[1]);
+ clearEmptyNodes(ifFalse[1]);
var flip = false;
- if (ifFalse[1][0] && ifFalse[1][0][0] === 'break') { // canonicalize break in the if
+ if (ifFalse[1][0] && ifFalse[1][ifFalse[1].length-1][0] === 'break') { // canonicalize break in the if-true
var temp = ifFalse;
ifFalse = ifTrue;
ifTrue = temp;
flip = true;
}
- if (ifTrue[1][0] && ifTrue[1][0][0] === 'break') {
+ if (ifTrue[1][0] && ifTrue[1][ifTrue[1].length-1][0] === 'break') {
var assigns = ifFalse[1];
clearEmptyNodes(assigns);
var loopers = [], helpers = [];
@@ -3569,6 +3676,17 @@ function eliminate(ast, memSafe) {
}
}
}
+ // remove loop vars that are used in the if
+ traverse(ifTrue, function(node, type) {
+ if (type === 'name') {
+ var index = loopers.indexOf(node[1]);
+ if (index < 0) index = helpers.indexOf(node[1]);
+ if (index >= 0) {
+ loopers.splice(index, 1);
+ helpers.splice(index, 1);
+ }
+ }
+ });
if (loopers.length === 0) return;
for (var l = 0; l < loopers.length; l++) {
var looper = loopers[l];
@@ -3589,17 +3707,61 @@ function eliminate(ast, memSafe) {
}
}
if (found < 0) return;
- var looperUsed = false;
- for (var i = found+1; i < stats.length && !looperUsed; i++) {
+ // if a loop variable is used after we assigned to the helper, we must save its value and use that.
+ // (note that this can happen due to elimination, if we eliminate an expression containing the
+ // loop var far down, past the assignment!)
+ // first, see if the looper and helper overlap
+ var firstLooperUsage = -1;
+ var lastLooperUsage = -1;
+ var firstHelperUsage = -1;
+ var lastHelperUsage = -1;
+ for (var i = found+1; i < stats.length; i++) {
var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition
traverse(curr, function(node, type) {
- if (type === 'name' && node[1] === looper) {
- looperUsed = true;
- return true;
+ if (type === 'name') {
+ if (node[1] === looper) {
+ if (firstLooperUsage < 0) firstLooperUsage = i;
+ lastLooperUsage = i;
+ } else if (node[1] === helper) {
+ if (firstHelperUsage < 0) firstHelperUsage = i;
+ lastHelperUsage = i;
+ }
}
});
}
- if (looperUsed) return;
+ if (firstLooperUsage >= 0) {
+ // the looper is used, we cannot simply merge the two variables
+ if ((firstHelperUsage < 0 || firstHelperUsage > lastLooperUsage) && lastLooperUsage+1 < stats.length && triviallySafeToMove(stats[found], asmData) &&
+ seenUses[helper] === namings[helper]) {
+ // the helper is not used, or it is used after the last use of the looper, so they do not overlap,
+ // and the last looper usage is not on the last line (where we could not append after it), and the
+ // helper is not used outside of the loop.
+ // just move the looper definition to after the looper's last use
+ stats.splice(lastLooperUsage+1, 0, stats[found]);
+ stats.splice(found, 1);
+ } else {
+ // they overlap, we can still proceed with the loop optimization, but we must introduce a
+ // loop temp helper variable
+ var temp = looper + '$looptemp';
+ assert(!(temp in asmData.vars));
+ for (var i = firstLooperUsage; i <= lastLooperUsage; i++) {
+ var curr = i < stats.length-1 ? stats[i] : last[1]; // on the last line, just look in the condition
+ traverse(curr, function looperToLooptemp(node, type) {
+ if (type === 'name') {
+ if (node[1] === looper) {
+ node[1] = temp;
+ }
+ } else if (type === 'assign' && node[2][0] === 'name') {
+ // do not traverse the assignment target, phi assignments to the loop variable must remain
+ traverse(node[3], looperToLooptemp);
+ return null;
+ }
+ });
+ }
+ asmData.vars[temp] = asmData.vars[looper];
+ stats.splice(found, 0, ['stat', ['assign', true, ['name', temp], ['name', looper]]]);
+ }
+ }
}
for (var l = 0; l < helpers.length; l++) {
for (var k = 0; k < helpers.length; k++) {
@@ -3710,7 +3872,7 @@ function minifyGlobals(ast) {
var first = true; // do not minify initial 'var asm ='
// find the globals
traverse(ast, function(node, type) {
- if (type === 'var') {
+ if (type === 'var' || type === 'const') {
if (first) {
first = false;
return;
@@ -3914,6 +4076,21 @@ function measureSize(ast) {
return size;
}
+function measureCost(ast) {
+ var size = 0;
+ traverse(ast, function(node, type) {
+ if (type === 'num' || type === 'unary-prefix') size--;
+ else if (type === 'binary') {
+ if (node[3][0] === 'num' && node[3][1] === 0) size--;
+ else if (node[1] === '/' || node[1] === '%') size += 2;
+ }
+ else if (type === 'call' && !callHasSideEffects(node)) size -= 2;
+ else if (type === 'sub') size++;
+ size++;
+ });
+ return size;
+}
+
function aggressiveVariableEliminationInternal(func, asmData) {
// This removes as many variables as possible. This is often not the best thing because it increases
// code size, but it is far preferable to the risk of split functions needing to do more spilling, so
@@ -4915,36 +5092,44 @@ function safeHeap(ast) {
}
}
} else if (type === 'sub') {
- var heap = node[1][1];
- if (heap[0] !== 'H') return;
- var ptr = fixPtr(node[2], heap);
- // SAFE_HEAP_LOAD(ptr, bytes, isFloat)
- switch (heap) {
- case 'HEAP8': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 1], ['num', '0'], ['num', '0']]], ASM_INT);
- }
- case 'HEAPU8': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 1], ['num', '0'], ['num', '1']]], ASM_INT);
- }
- case 'HEAP16': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 2], ['num', '0'], ['num', '0']]], ASM_INT);
- }
- case 'HEAPU16': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 2], ['num', '0'], ['num', '1']]], ASM_INT);
- }
- case 'HEAP32': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '0'], ['num', '0']]], ASM_INT);
- }
- case 'HEAPU32': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '0'], ['num', '1']]], ASM_INT);
- }
- case 'HEAPF32': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '1'], ['num', '0']]], ASM_DOUBLE);
- }
- case 'HEAPF64': {
- return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 8], ['num', '1'], ['num', '0']]], ASM_DOUBLE);
+ var target = node[1][1];
+ if (target[0] === 'H') {
+ // heap access
+ var heap = target;
+ var ptr = fixPtr(node[2], heap);
+ // SAFE_HEAP_LOAD(ptr, bytes, isFloat)
+ switch (heap) {
+ case 'HEAP8': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 1], ['num', '0'], ['num', '0']]], ASM_INT);
+ }
+ case 'HEAPU8': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 1], ['num', '0'], ['num', '1']]], ASM_INT);
+ }
+ case 'HEAP16': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 2], ['num', '0'], ['num', '0']]], ASM_INT);
+ }
+ case 'HEAPU16': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 2], ['num', '0'], ['num', '1']]], ASM_INT);
+ }
+ case 'HEAP32': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '0'], ['num', '0']]], ASM_INT);
+ }
+ case 'HEAPU32': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '0'], ['num', '1']]], ASM_INT);
+ }
+ case 'HEAPF32': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 4], ['num', '1'], ['num', '0']]], ASM_DOUBLE);
+ }
+ case 'HEAPF64': {
+ return makeAsmCoercion(['call', ['name', 'SAFE_HEAP_LOAD'], [ptr, ['num', 8], ['num', '1'], ['num', '0']]], ASM_DOUBLE);
+ }
+ default: throw 'bad heap ' + heap;
}
- default: throw 'bad heap ' + heap;
+ } else {
+ assert(target[0] == 'F');
+ // function table indexing mask
+ assert(node[2][0] === 'binary' && node[2][1] === '&');
+ node[2][2] = makeAsmCoercion(['call', ['name', 'SAFE_FT_MASK'], [makeAsmCoercion(node[2][2], ASM_INT), makeAsmCoercion(node[2][3], ASM_INT)]], ASM_INT);
}
}
});
@@ -4952,10 +5137,19 @@ function safeHeap(ast) {
function optimizeFrounds(ast) {
// collapse fround(fround(..)), which can happen due to elimination
+ // also emit f0 instead of fround(0) (except in returns)
+ var inReturn = false;
function fix(node) {
+ if (node[0] === 'return') inReturn = true;
traverseChildren(node, fix);
- if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround' && node[2][0][0] === 'call' && node[2][0][1][0] === 'name' && node[2][0][1][1] === 'Math_fround') {
- return node[2][0];
+ if (node[0] === 'return') inReturn = false;
+ if (node[0] === 'call' && node[1][0] === 'name' && node[1][1] === 'Math_fround') {
+ var arg = node[2][0];
+ if (arg[0] === 'num') {
+ if (!inReturn && arg[1] === 0) return ['name', 'f0'];
+ } else if (arg[0] === 'call' && arg[1][0] === 'name' && arg[1][1] === 'Math_fround') {
+ return arg;
+ }
}
}
traverseChildren(ast, fix);
@@ -4977,7 +5171,9 @@ function prepDotZero(ast) {
function fixDotZero(js) {
return js.replace(/DOT\$ZERO\(([-+]?(0x)?[0-9a-f]*\.?[0-9]+([eE][-+]?[0-9]+)?)\)/g, function(m, num) {
if (num.substr(0, 2) === '0x' || num.substr(0, 3) === '-0x') {
- return eval(num) + '.0';
+ var ret = eval(num).toString();
+ if (ret.indexOf('.') < 0) return ret + '.0';
+ return ret;
}
if (num.indexOf('.') >= 0) return num;
var e = num.indexOf('e');
@@ -4987,8 +5183,11 @@ function fixDotZero(js) {
}
function asmLastOpts(ast) {
+ var statsStack = [];
traverseGeneratedFunctions(ast, function(fun) {
traverse(fun, function(node, type) {
+ var stats = getStatements(node);
+ if (stats) statsStack.push(stats);
if (type === 'while' && node[1][0] === 'num' && node[1][1] === 1 && node[2][0] === 'block' && node[2].length == 2) {
// This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops
// into shapes that might confuse other passes
@@ -4996,15 +5195,28 @@ function asmLastOpts(ast) {
// while (1) { .. if (..) { break } } ==> do { .. } while(..)
var stats = node[2][1];
var last = stats[stats.length-1];
- if (last && last[0] === 'if' && !last[3] && last[2][0] === 'block' && last[2][1][0] && last[2][1][0][0] === 'break' && !last[2][1][0][1]) {
+ if (last && last[0] === 'if' && !last[3] && last[2][0] === 'block' && last[2][1][0]) {
+ var lastStats = last[2][1];
+ var lastNum = lastStats.length;
+ var lastLast = lastStats[lastNum-1];
+ if (!(lastLast[0] === 'break' && !lastLast[1])) return;// if not a simple break, dangerous
+ for (var i = 0; i < lastNum; i++) {
+ if (lastStats[i][0] !== 'stat' && lastStats[i][0] !== 'break') return; // something dangerous
+ }
+ // ok, a bunch of statements ending in a break
var abort = false;
var stack = 0;
+ var breaks = 0;
traverse(stats, function(node, type) {
- if (type == 'continue') {
- if (stack == 0 || node[1]) { // abort if labeled (we do not analyze labels here yet), or a continue directly on us
+ if (type === 'continue') {
+ if (stack === 0 || node[1]) { // abort if labeled (we do not analyze labels here yet), or a continue directly on us
abort = true;
return true;
}
+ } else if (type === 'break') {
+ if (stack === 0 || node[1]) { // relevant if labeled (we do not analyze labels here yet), or a break directly on us
+ breaks++;
+ }
} else if (type in LOOP) {
stack++;
}
@@ -5014,6 +5226,15 @@ function asmLastOpts(ast) {
}
});
if (abort) return;
+ assert(breaks > 0);
+ if (lastStats.length > 1 && breaks !== 1) return; // if we have code aside from the break, we can only move it out if there is just one break
+ // start to optimize
+ if (lastStats.length > 1) {
+ var parent = statsStack[statsStack.length-1];
+ var me = parent.indexOf(node);
+ if (me < 0) return; // not always directly on a stats, could be in a label for example
+ parent.splice.apply(parent, [me+1, 0].concat(lastStats.slice(0, lastStats.length-1)));
+ }
var conditionToBreak = last[1];
stats.pop();
node[0] = 'do';
@@ -5042,6 +5263,9 @@ function asmLastOpts(ast) {
}
}
}
+ }, function(node, type) {
+ var stats = getStatements(node);
+ if (stats) statsStack.pop();
});
});
}
diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py
index d0284528..e06c2d2f 100644
--- a/tools/js_optimizer.py
+++ b/tools/js_optimizer.py
@@ -137,6 +137,10 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False, extra_info=
if closure:
passes = filter(lambda p: p != 'closure', passes) # we will do it manually
+ cleanup = 'cleanup' in passes
+ if cleanup:
+ passes = filter(lambda p: p != 'cleanup', passes) # we will do it manually
+
if not know_generated and jcache:
# JCache cannot be used without metadata, since it might reorder stuff, and that's dangerous since only generated can be reordered
# This means jcache does not work after closure compiler runs, for example. But you won't get much benefit from jcache with closure
@@ -291,23 +295,29 @@ EMSCRIPTEN_FUNCS();
for filename in filenames: temp_files.note(filename)
- if closure:
- # run closure on the shell code, everything but what we js-optimize
+ if closure or cleanup:
+ # run on the shell code, everything but what we js-optimize
start_asm = '// EMSCRIPTEN_START_ASM\n'
end_asm = '// EMSCRIPTEN_END_ASM\n'
- closure_sep = 'wakaUnknownBefore(); var asm=wakaUnknownAfter(global,env,buffer)\n'
+ cl_sep = 'wakaUnknownBefore(); var asm=wakaUnknownAfter(global,env,buffer)\n'
- closuree = temp_files.get('.closure.js').name
- c = open(closuree, 'w')
+ cle = temp_files.get('.cl.js').name
+ c = open(cle, 'w')
pre_1, pre_2 = pre.split(start_asm)
post_1, post_2 = post.split(end_asm)
c.write(pre_1)
- c.write(closure_sep)
+ c.write(cl_sep)
c.write(post_2)
c.close()
- closured = shared.Building.closure_compiler(closuree, pretty='minifyWhitespace' not in passes)
- temp_files.note(closured)
- coutput = open(closured).read()
+ if closure:
+ if DEBUG: print >> sys.stderr, 'running closure on shell code'
+ cld = shared.Building.closure_compiler(cle, pretty='minifyWhitespace' not in passes)
+ else:
+ if DEBUG: print >> sys.stderr, 'running cleanup on shell code'
+ cld = cle + '.js'
+ subprocess.Popen(js_engine + [JS_OPTIMIZER, cle, 'noPrintMetadata'] + (['minifyWhitespace'] if 'minifyWhitespace' in passes else []), stdout=open(cld, 'w')).communicate()
+ temp_files.note(cld)
+ coutput = open(cld).read()
coutput = coutput.replace('wakaUnknownBefore();', '')
after = 'wakaUnknownAfter'
start = coutput.find(after)
diff --git a/tools/jsrun.py b/tools/jsrun.py
index f74a1492..d63451db 100644
--- a/tools/jsrun.py
+++ b/tools/jsrun.py
@@ -14,10 +14,10 @@ def timeout_run(proc, timeout=None, note='unnamed process', full_output=False):
out = proc.communicate()
out = map(lambda o: '' if o is None else o, out)
if TRACK_PROCESS_SPAWNS:
- logging.info('Process ' + str(proc.pid) + ' finished after ' + str(time.time() - start) + ' seconds.')
+ logging.info('Process ' + str(proc.pid) + ' finished after ' + str(time.time() - start) + ' seconds. Exit code: ' + str(proc.returncode))
return '\n'.join(out) if full_output else out[0]
-def run_js(filename, engine=None, args=[], check_timeout=False, stdin=None, stdout=PIPE, stderr=None, cwd=None, full_output=False):
+def run_js(filename, engine=None, args=[], check_timeout=False, stdin=None, stdout=PIPE, stderr=None, cwd=None, full_output=False, assert_returncode=None):
if type(engine) is not list:
engine = [engine]
command = engine + [filename] + (['--'] if 'd8' in engine[0] or 'jsc' in engine[0] else []) + args
@@ -30,8 +30,11 @@ def run_js(filename, engine=None, args=[], check_timeout=False, stdin=None, stdo
timeout = 15*60 if check_timeout else None
if TRACK_PROCESS_SPAWNS:
logging.info('Blocking on process ' + str(proc.pid) + ': ' + str(command) + (' for ' + str(timeout) + ' seconds' if timeout else ' until it finishes.'))
- return timeout_run(
+ ret = timeout_run(
proc,
timeout,
'Execution',
full_output=full_output)
+ if assert_returncode is not None and proc.returncode is not assert_returncode:
+ raise Exception('Expected the command ' + str(command) + ' to finish with return code ' + str(assert_returncode) + ', but it returned with code ' + str(proc.returncode) + ' instead! Output: ' + str(ret))
+ return ret
diff --git a/tools/shared.py b/tools/shared.py
index 82bdd98b..7aaa4136 100644
--- a/tools/shared.py
+++ b/tools/shared.py
@@ -326,7 +326,7 @@ def check_fastcomp():
break
d = os.path.dirname(d)
if not seen:
- logging.warning('did not see a source tree above LLVM_DIR, could not verify version numbers match')
+ logging.warning('did not see a source tree above the LLVM root directory (guessing based on directory of %s), could not verify version numbers match' % LLVM_COMPILER)
return True
except Exception, e:
logging.warning('could not check fastcomp: %s' % str(e))
@@ -1144,63 +1144,131 @@ class Building:
unresolved_symbols = set([func[1:] for func in Settings.EXPORTED_FUNCTIONS])
resolved_symbols = set()
temp_dirs = []
- files = map(os.path.abspath, files)
+ def make_paths_absolute(f):
+ if f.startswith('-'): # skip flags
+ return f
+ else:
+ return os.path.abspath(f)
+ files = map(make_paths_absolute, files)
+ # Paths of already included object files from archives.
+ added_contents = set()
+ # Map of archive name to list of extracted object file paths.
+ ar_contents = {}
has_ar = False
for f in files:
- has_ar = has_ar or Building.is_ar(f)
+ if not f.startswith('-'):
+ has_ar = has_ar or Building.is_ar(f)
+
+ # If we have only one archive or the force_archive_contents flag is set,
+ # then we will add every object file we see, regardless of whether it
+ # resolves any undefined symbols.
+ force_add_all = len(files) == 1 or force_archive_contents
+
+ # Considers an object file for inclusion in the link. The object is included
+ # if force_add=True or if the object provides a currently undefined symbol.
+ # If the object is included, the symbol tables are updated and the function
+ # returns True.
+ def consider_object(f, force_add=False):
+ new_symbols = Building.llvm_nm(f)
+ do_add = force_add or not unresolved_symbols.isdisjoint(new_symbols.defs)
+ if do_add:
+ logging.debug('adding object %s to link' % (f))
+ # Update resolved_symbols table with newly resolved symbols
+ resolved_symbols.update(new_symbols.defs)
+ # Update unresolved_symbols table by adding newly unresolved symbols and
+ # removing newly resolved symbols.
+ unresolved_symbols.update(new_symbols.undefs.difference(resolved_symbols))
+ unresolved_symbols.difference_update(new_symbols.defs)
+ actual_files.append(f)
+ return do_add
+
+ def get_archive_contents(f):
+ if f in ar_contents:
+ return ar_contents[f]
+
+ cwd = os.getcwd()
+ try:
+ temp_dir = os.path.join(EMSCRIPTEN_TEMP_DIR, 'ar_output_' + str(os.getpid()) + '_' + str(len(temp_dirs)))
+ temp_dirs.append(temp_dir)
+ safe_ensure_dirs(temp_dir)
+ os.chdir(temp_dir)
+ contents = filter(lambda x: len(x) > 0, Popen([LLVM_AR, 't', f], stdout=PIPE).communicate()[0].split('\n'))
+ if len(contents) == 0:
+ logging.debug('Archive %s appears to be empty (recommendation: link an .so instead of .a)' % f)
+ else:
+ for content in contents: # ar will silently fail if the directory for the file does not exist, so make all the necessary directories
+ dirname = os.path.dirname(content)
+ if dirname:
+ safe_ensure_dirs(dirname)
+ Popen([LLVM_AR, 'xo', f], stdout=PIPE).communicate() # if absolute paths, files will appear there. otherwise, in this directory
+ contents = map(lambda content: os.path.join(temp_dir, content), contents)
+ contents = filter(os.path.exists, map(os.path.abspath, contents))
+ contents = filter(Building.is_bitcode, contents)
+ ar_contents[f] = contents
+ finally:
+ os.chdir(cwd)
+
+ return contents
+
+ # Traverse a single archive. The object files are repeatedly scanned for
+ # newly satisfied symbols until no new symbols are found. Returns true if
+ # any object files were added to the link.
+ def consider_archive(f):
+ added_any_objects = False
+ loop_again = True
+ logging.debug('considering archive %s' % (f))
+ contents = get_archive_contents(f)
+ while loop_again: # repeatedly traverse until we have everything we need
+ loop_again = False
+ for content in contents:
+ if content in added_contents: continue
+ # Link in the .o if it provides symbols, *or* this is a singleton archive (which is apparently an exception in gcc ld)
+ if consider_object(content, force_add=force_add_all):
+ added_contents.add(content)
+ loop_again = True
+ added_any_objects = True
+ logging.debug('done running loop of archive %s' % (f))
+ return added_any_objects
+
+ current_archive_group = None
for f in files:
- if not Building.is_ar(f):
+ if f.startswith('-'):
+ if f in ['--start-group', '-(']:
+ assert current_archive_group is None, 'Nested --start-group, missing --end-group?'
+ current_archive_group = []
+ elif f in ['--end-group', '-)']:
+ assert current_archive_group is not None, '--end-group without --start-group'
+ # rescan the archives in the group until we don't find any more
+ # objects to link.
+ loop_again = True
+ logging.debug('starting archive group loop');
+ while loop_again:
+ loop_again = False
+ for archive in current_archive_group:
+ if consider_archive(archive):
+ loop_again = True
+ logging.debug('done with archive group loop');
+ current_archive_group = None
+ else:
+ logging.debug('Ignoring unsupported link flag: %s' % f)
+ elif not Building.is_ar(f):
if Building.is_bitcode(f):
if has_ar:
- new_symbols = Building.llvm_nm(f)
- resolved_symbols = resolved_symbols.union(new_symbols.defs)
- unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs)
- actual_files.append(f)
+ consider_object(f, force_add=True)
+ else:
+ # If there are no archives then we can simply link all valid bitcode
+ # files and skip the symbol table stuff.
+ actual_files.append(f)
else:
# Extract object files from ar archives, and link according to gnu ld semantics
# (link in an entire .o from the archive if it supplies symbols still unresolved)
- cwd = os.getcwd()
- try:
- temp_dir = os.path.join(EMSCRIPTEN_TEMP_DIR, 'ar_output_' + str(os.getpid()) + '_' + str(len(temp_dirs)))
- temp_dirs.append(temp_dir)
- safe_ensure_dirs(temp_dir)
- os.chdir(temp_dir)
- contents = filter(lambda x: len(x) > 0, Popen([LLVM_AR, 't', f], stdout=PIPE).communicate()[0].split('\n'))
- #print >> sys.stderr, ' considering archive', f, ':', contents
- if len(contents) == 0:
- logging.debug('Archive %s appears to be empty (recommendation: link an .so instead of .a)' % f)
- else:
- for content in contents: # ar will silently fail if the directory for the file does not exist, so make all the necessary directories
- dirname = os.path.dirname(content)
- if dirname:
- safe_ensure_dirs(dirname)
- Popen([LLVM_AR, 'xo', f], stdout=PIPE).communicate() # if absolute paths, files will appear there. otherwise, in this directory
- contents = map(lambda content: os.path.join(temp_dir, content), contents)
- contents = filter(os.path.exists, map(os.path.abspath, contents))
- added_contents = set()
- added = True
- #print >> sys.stderr, ' initial undef are now ', unresolved_symbols, '\n'
- while added: # recursively traverse until we have everything we need
- #print >> sys.stderr, ' running loop of archive including for', f
- added = False
- for content in contents:
- if content in added_contents: continue
- new_symbols = Building.llvm_nm(content)
- # Link in the .o if it provides symbols, *or* this is a singleton archive (which is apparently an exception in gcc ld)
- #print >> sys.stderr, 'need', content, '?', unresolved_symbols, 'and we can supply', new_symbols.defs
- #print >> sys.stderr, content, 'DEF', new_symbols.defs, '\n'
- if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1 or force_archive_contents:
- if Building.is_bitcode(content):
- #print >> sys.stderr, ' adding object', content, '\n'
- resolved_symbols = resolved_symbols.union(new_symbols.defs)
- unresolved_symbols = unresolved_symbols.union(new_symbols.undefs.difference(resolved_symbols)).difference(new_symbols.defs)
- #print >> sys.stderr, ' undef are now ', unresolved_symbols, '\n'
- actual_files.append(content)
- added_contents.add(content)
- added = True
- #print >> sys.stderr, ' done running loop of archive including for', f
- finally:
- os.chdir(cwd)
+ consider_archive(f)
+ # If we're inside a --start-group/--end-group section, add to the list
+ # so we can loop back around later.
+ if current_archive_group is not None:
+ current_archive_group.append(f)
+ assert current_archive_group is None, '--start-group without matching --end-group'
+
try_delete(target)
# Finish link
@@ -1362,12 +1430,10 @@ class Building:
cmdline = [PYTHON, EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + args
if jsrun.TRACK_PROCESS_SPAWNS:
logging.info('Executing emscripten.py compiler with cmdline "' + ' '.join(cmdline) + '"')
- compiler_output = jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling')
- #print compiler_output
+ jsrun.timeout_run(Popen(cmdline, stdout=PIPE), None, 'Compiling')
# Detect compilation crashes and errors
- if compiler_output is not None and 'Traceback' in compiler_output and 'in test_' in compiler_output: print compiler_output; assert 0
- assert os.path.exists(filename + '.o.js') and len(open(filename + '.o.js', 'r').read()) > 0, 'Emscripten failed to generate .js: ' + str(compiler_output)
+ assert os.path.exists(filename + '.o.js'), 'Emscripten failed to generate .js'
return filename + '.o.js'
@@ -1650,12 +1716,17 @@ class JS:
return '+0'
@staticmethod
- def make_coercion(value, sig, settings=None):
+ def make_coercion(value, sig, settings=None, ffi_arg=False, ffi_result=False):
settings = settings or Settings
if sig == 'i':
return value + '|0'
elif sig == 'f' and settings.get('PRECISE_F32'):
- return 'Math_fround(' + value + ')'
+ if ffi_arg:
+ return '+Math_fround(' + value + ')'
+ elif ffi_result:
+ return 'Math_fround(+(' + value + '))'
+ else:
+ return 'Math_fround(' + value + ')'
elif sig == 'd' or sig == 'f':
return '+' + value
else:
@@ -1831,7 +1902,11 @@ def unsuffixed_basename(name):
return os.path.basename(unsuffixed(name))
def safe_move(src, dst):
- if os.path.abspath(src) == os.path.abspath(dst):
+ src = os.path.abspath(src)
+ dst = os.path.abspath(dst)
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ if src == dst:
return
shutil.move(src, dst)
diff --git a/tools/system_libs.py b/tools/system_libs.py
index 50910a8a..78bf2d48 100644
--- a/tools/system_libs.py
+++ b/tools/system_libs.py
@@ -55,27 +55,49 @@ def calculate(temp_files, in_temp, stdout, stderr):
os.path.join('libcxx', 'new.cpp'),
]
musl_files = [
+ ['ctype', [
+ 'isdigit.c',
+ 'isspace.c',
+ 'isupper.c',
+ 'tolower.c',
+ ]],
['internal', [
+ 'intscan.c',
'floatscan.c',
'shgetc.c',
]],
['math', [
+ 'frexp.c',
+ 'frexpf.c',
+ 'frexpl.c',
'scalbn.c',
'scalbnl.c',
]],
+ ['multibyte', [
+ 'wctomb.c',
+ 'wcrtomb.c',
+ ]],
['stdio', [
'__overflow.c',
'__toread.c',
'__towrite.c',
'__uflow.c',
+ 'fwrite.c',
+ 'snprintf.c',
+ 'sprintf.c',
+ 'vfprintf.c',
+ 'vsnprintf.c',
+ 'vsprintf.c',
]],
['stdlib', [
'atof.c',
'atoi.c',
'atol.c',
'strtod.c',
+ 'strtol.c',
]],
['string', [
+ 'memchr.c',
'memcmp.c',
'strcasecmp.c',
'strcmp.c',
@@ -103,7 +125,22 @@ def calculate(temp_files, in_temp, stdout, stderr):
def create_libcextra():
logging.debug('building libcextra for cache')
musl_files = [
+ ['compat', [
+ 'strlwr.c',
+ 'strtol_l.c',
+ 'strupr.c'
+ ]],
['ctype', [
+ 'isalnum.c',
+ 'isalpha.c',
+ 'isascii.c',
+ 'isblank.c',
+ 'iscntrl.c',
+ 'isgraph.c',
+ 'islower.c',
+ 'isprint.c',
+ 'ispunct.c',
+ 'isxdigit.c',
'iswalnum.c',
'iswalpha.c',
'iswblank.c',
@@ -117,19 +154,30 @@ def calculate(temp_files, in_temp, stdout, stderr):
'iswspace.c',
'iswupper.c',
'iswxdigit.c',
+ 'toascii.c',
+ 'toupper.c',
'towctrans.c',
'wcswidth.c',
'wctrans.c',
'wcwidth.c',
]],
- ['internal', [
- 'intscan.c',
- ]],
['legacy', [
'err.c',
]],
['locale', [
'iconv.c',
+ 'isalnum_l.c',
+ 'isalpha_l.c',
+ 'isblank_l.c',
+ 'iscntrl_l.c',
+ 'isdigit_l.c',
+ 'isgraph_l.c',
+ 'islower_l.c',
+ 'isprint_l.c',
+ 'ispunct_l.c',
+ 'isspace_l.c',
+ 'isupper_l.c',
+ 'isxdigit_l.c',
'iswalnum_l.c',
'iswalpha_l.c',
'iswblank_l.c',
@@ -148,6 +196,8 @@ def calculate(temp_files, in_temp, stdout, stderr):
'strfmon.c',
'strncasecmp_l.c',
'strxfrm.c',
+ 'tolower_l.c',
+ 'toupper_l.c',
'towctrans_l.c',
'towlower_l.c',
'towupper_l.c',
@@ -198,12 +248,10 @@ def calculate(temp_files, in_temp, stdout, stderr):
'mbsrtowcs.c',
'mbstowcs.c',
'mbtowc.c',
- 'wcrtomb.c',
'wcsnrtombs.c',
'wcsrtombs.c',
'wcstombs.c',
'wctob.c',
- 'wctomb.c',
]],
['regex', [
'fnmatch.c',
@@ -213,6 +261,8 @@ def calculate(temp_files, in_temp, stdout, stderr):
'tre-mem.c',
]],
['stdio', [
+ '__string_read.c',
+ 'asprintf.c',
'fwprintf.c',
'swprintf.c',
'vfwprintf.c',
@@ -221,6 +271,10 @@ def calculate(temp_files, in_temp, stdout, stderr):
'wprintf.c',
'fputwc.c',
'fputws.c',
+ 'sscanf.c',
+ 'vasprintf.c',
+ 'vfscanf.c',
+ 'vsscanf.c',
]],
['stdlib', [
'atoll.c',
@@ -240,7 +294,6 @@ def calculate(temp_files, in_temp, stdout, stderr):
'memccpy.c',
'memmem.c',
'mempcpy.c',
- 'memchr.c',
'memrchr.c',
'rindex.c',
'stpcpy.c',
diff --git a/tools/test-js-optimizer-asm-last-output.js b/tools/test-js-optimizer-asm-last-output.js
index 1b9ac585..95afaeb7 100644
--- a/tools/test-js-optimizer-asm-last-output.js
+++ b/tools/test-js-optimizer-asm-last-output.js
@@ -30,6 +30,7 @@ function finall(x) {
a = -999999984306749400.0;
a = -999999984306749400.0;
a = -0xde0b6b000000000;
+ a = 1.1234567890123457e+21;
f(g() | 0);
return 12.0e10;
}
@@ -71,5 +72,31 @@ function looop() {
break;
}
}
+ do {
+ blah();
+ } while (!shah());
+ a = b;
+ LABELED : while (1) {
+ blah();
+ if (shah()) {
+ c = d;
+ break;
+ }
+ }
+ while (1) {
+ blah();
+ if (check) break;
+ if (shah()) {
+ e = f;
+ break;
+ }
+ }
+ do {
+ blah();
+ while (1) {
+ if (check) break;
+ }
+ } while (!shah());
+ g = h;
}
diff --git a/tools/test-js-optimizer-asm-last.js b/tools/test-js-optimizer-asm-last.js
index 1d39b1a6..6261e915 100644
--- a/tools/test-js-optimizer-asm-last.js
+++ b/tools/test-js-optimizer-asm-last.js
@@ -30,6 +30,7 @@ function finall(x) {
a = +-0xde0b6b000000000;
a = -+0xde0b6b000000000;
a = -0xde0b6b000000000;
+ a = +0x3ce7184d470dd60000;
f(g() & -1);
return +12e10;
}
@@ -86,6 +87,38 @@ function looop() {
break;
}
}
+ while (1) {
+ blah();
+ if (shah()) {
+ a = b;
+ break;
+ }
+ }
+ LABELED: while (1) {
+ blah();
+ if (shah()) {
+ c = d;
+ break;
+ }
+ }
+ while (1) {
+ blah();
+ if (check) break; // prevents optimization
+ if (shah()) {
+ e = f;
+ break;
+ }
+ }
+ while (1) {
+ blah();
+ while (1) {
+ if (check) break; // safe to optimize
+ }
+ if (shah()) {
+ g = h;
+ break;
+ }
+ }
}
// EMSCRIPTEN_GENERATED_FUNCTIONS: ["finall", "looop"]
diff --git a/tools/test-js-optimizer-asm-pre-f32.js b/tools/test-js-optimizer-asm-pre-f32.js
index 5471deeb..be515b36 100644
--- a/tools/test-js-optimizer-asm-pre-f32.js
+++ b/tools/test-js-optimizer-asm-pre-f32.js
@@ -14,4 +14,10 @@ function dupe() {
x = Math_fround(Math_fround(Math_fround(x)));
x = Math_fround(Math_fround(Math_fround(Math_fround(x))));
}
-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["badf", "badf2", "dupe"]
+function zeros(x) {
+ x = Math_fround(x);
+ var y = Math_fround(0);
+ print(Math_fround(y) + Math_fround(0));
+ return Math_fround(0); // return needs to stay as is
+}
+// EMSCRIPTEN_GENERATED_FUNCTIONS: ["badf", "badf2", "dupe", "zeros"]
diff --git a/tools/test-js-optimizer-asm-pre-output-f32.js b/tools/test-js-optimizer-asm-pre-output-f32.js
index 19059619..f0f2d0da 100644
--- a/tools/test-js-optimizer-asm-pre-output-f32.js
+++ b/tools/test-js-optimizer-asm-pre-output-f32.js
@@ -4,7 +4,7 @@ function badf() {
HEAP32[$gep23_asptr >> 2] = $9;
}
function badf2() {
- var $9 = Math_fround(0);
+ var $9 = f0;
$9 = Math_fround($8);
HEAPF32[$gep23_asptr >> 2] = $9;
}
@@ -14,4 +14,10 @@ function dupe() {
x = Math_fround(x);
x = Math_fround(x);
}
+function zeros(x) {
+ x = Math_fround(x);
+ var y = f0;
+ print(Math_fround(y) + f0);
+ return Math_fround(0);
+}
diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js
index c9746e78..da3ebb4f 100644
--- a/tools/test-js-optimizer-asm-pre-output.js
+++ b/tools/test-js-optimizer-asm-pre-output.js
@@ -106,15 +106,11 @@ function sign_extension_simplification() {
}
}
function compare_result_simplification() {
- HEAP32[$4] = HEAP32[$5] < HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] > HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] <= HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] <= HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] == HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] === HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] != HEAP32[$6];
- HEAP32[$4] = HEAP32[$5] !== HEAP32[$6];
- var x = HEAP32[$5] != HEAP32[$6] | 0;
+ f((a > b & 1) + 1 | 0);
+ f(a > b | z);
+ f(a > b | c > d);
+ HEAP32[$4] = HEAP32[$5] < HEAP32[$6] & 1;
+ var x = HEAP32[$5] != HEAP32[$6] & 1;
}
function tempDoublePtr($45, $14, $28, $42) {
$45 = $45 | 0;
@@ -538,4 +534,68 @@ function fcomp() {
if (5 >= ($b | 0)) return 5;
if (5 >= 5) return 5;
}
+function conditionalizeMe() {
+ if (x > 1 ? x + y + z + w > 12 : 0) {
+ b();
+ }
+ if (a() > 1 ? x + y + z + w > 12 : 0) {
+ b();
+ }
+ if (x > 1 & x + y + z + k() > 12) {
+ b();
+ }
+ if (a() > 1 & x + y + z + k() > 12) {
+ b();
+ }
+ if (x > 1 ? 1 : x + y + z + w > 12) {
+ b();
+ }
+ if (a() > 1 ? 1 : x + y + z + w > 12) {
+ b();
+ }
+ if (x > 1 | x + y + z + k() > 12) {
+ b();
+ }
+ if (a() > 1 | x + y + z + k() > 12) {
+ b();
+ }
+ if (x > 1 ? 1 : x + y + z + w > 12) {
+ b();
+ }
+ if (a() > 1 ? 1 : x + y + z + w > 12) {
+ b();
+ }
+ if (x + y + z + k() > 12 | x > 1) {
+ b();
+ }
+ if (x + y + z + k() > 12 | a() > 1) {
+ b();
+ }
+ while (x > 1 ? x + y + z + w > 12 : 0) {
+ b();
+ }
+ while (a() > 1 ? x + y + z + w > 12 : 0) {
+ b();
+ }
+ while (x > 1 & x + y + z + k() > 12) {
+ b();
+ }
+ while (a() > 1 & x + y + z + k() > 12) {
+ b();
+ }
+ if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(+0))) {
+ b();
+ }
+ if ($sub$i480 >= Math_fround(+0) ? !($sub4$i483 >= Math_fround(HEAPF32[x + y | 0])) : 1) {
+ b();
+ }
+ if (x > 10 | HEAP[20] + 2 > 5) {
+ b();
+ }
+ print(((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? 1 : $el) | $cheap > 0);
+ print(((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? 1 : -1) | $cheap > 0);
+ print($cheap > 0 ? 1 : (HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? 1 : 0);
+ print(((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > a % b % c % d ? -1 : 1) | $cheap > 0);
+ return (((((Math_imul(i6 + 1, i7) | 0) + 17 | 0) % 5 | 0) == 0 ? 1 : ((((Math_imul(i7 + 1, i7) | 0) + 11 | 0) >>> 0) % 3 | 0) == 0) | 0) == 0;
+}
diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js
index 00ebd7d7..08d8c2d4 100644
--- a/tools/test-js-optimizer-asm-pre.js
+++ b/tools/test-js-optimizer-asm-pre.js
@@ -113,16 +113,11 @@ function sign_extension_simplification() {
}
}
function compare_result_simplification() {
- // Eliminate these '&1's.
+ f(((a > b)&1) + 1 | 0);
+ f(((a > b)&1) | z);
+ f(((a > b)&1) | (c > d & 1));
+ // Don't eliminate these '&1's.
HEAP32[$4] = (HEAP32[$5] < HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] > HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] == HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] === HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] != HEAP32[$6]) & 1;
- HEAP32[$4] = (HEAP32[$5] !== HEAP32[$6]) & 1;
- // Convert the &1 to |0 here, since we don't get an implicit coersion.
var x = (HEAP32[$5] != HEAP32[$6]) & 1;
}
function tempDoublePtr($45, $14, $28, $42) {
@@ -550,4 +545,68 @@ function fcomp() {
if (!(5 < ($b|0))) return 5;
if (!(5 < 5)) return 5;
}
-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp"]
+function conditionalizeMe() {
+ if ((x > 1) & (x+y+z+w > 12)) {
+ b();
+ }
+ if ((a() > 1) & (x+y+z+w > 12)) {
+ b();
+ }
+ if ((x > 1) & (x+y+z+k() > 12)) {
+ b();
+ }
+ if ((a() > 1) & (x+y+z+k() > 12)) {
+ b();
+ }
+ if ((x > 1) | (x+y+z+w > 12)) {
+ b();
+ }
+ if ((a() > 1) | (x+y+z+w > 12)) {
+ b();
+ }
+ if ((x > 1) | (x+y+z+k() > 12)) {
+ b();
+ }
+ if ((a() > 1) | (x+y+z+k() > 12)) {
+ b();
+ }
+ if ((x+y+z+w > 12) | (x > 1)) {
+ b();
+ }
+ if ((x+y+z+w > 12) | (a() > 1)) {
+ b();
+ }
+ if ((x+y+z+k() > 12) | (x > 1)) {
+ b();
+ }
+ if ((x+y+z+k() > 12) | (a() > 1)) {
+ b();
+ }
+ while ((x > 1) & (x+y+z+w > 12)) {
+ b();
+ }
+ while ((a() > 1) & (x+y+z+w > 12)) {
+ b();
+ }
+ while ((x > 1) & (x+y+z+k() > 12)) {
+ b();
+ }
+ while ((a() > 1) & (x+y+z+k() > 12)) {
+ b();
+ }
+ if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(+0))) {
+ b();
+ }
+ if (!($sub$i480 >= Math_fround(+0)) | !($sub4$i483 >= Math_fround(HEAPF32[x+y|0]))) {
+ b();
+ }
+ if (x > 10 | (HEAP[20] + 2) > 5) {
+ b();
+ }
+ print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? 1 : $el) | ($cheap > 0) ); // conditional does not always emit boolean
+ print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? 1 : -1) | ($cheap > 0) );
+ print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? 1 : 0) | ($cheap > 0) ); // this one is safe!
+ print( (((HEAP8[a] + HEAP8[b] + HEAP8[c] + HEAP8[d] + HEAP8[e] + HEAP8[f] | 0) > (a % b % c % d)) ? -1 : 1) | ($cheap > 0) );
+ return ((((Math_imul(i6+1, i7) | 0) + 17 | 0) % 5 | 0 | 0) == 0 | ((((Math_imul(i7+1, i7) | 0) + 11 | 0) >>> 0) % 3 | 0 | 0) == 0 | 0) == 0;
+}
+// EMSCRIPTEN_GENERATED_FUNCTIONS: ["a", "b", "rett", "ret2t", "retf", "i32_8", "tempDoublePtr", "boxx", "_main", "badf", "badf2", "fcomp", "conditionalizeMe"]
diff --git a/tools/test-js-optimizer-asm-regs-harder-output.js b/tools/test-js-optimizer-asm-regs-harder-output.js
index e1df42cb..c3b326f6 100644
--- a/tools/test-js-optimizer-asm-regs-harder-output.js
+++ b/tools/test-js-optimizer-asm-regs-harder-output.js
@@ -129,4 +129,9 @@ function linkedVars() {
}
return i2 + i1;
}
+function deadCondExpr(i2) {
+ i2 = i2 | 0;
+ var i1 = 0;
+ return i1 | 0;
+}
diff --git a/tools/test-js-optimizer-asm-regs-harder.js b/tools/test-js-optimizer-asm-regs-harder.js
index 0231a215..fa72aab8 100644
--- a/tools/test-js-optimizer-asm-regs-harder.js
+++ b/tools/test-js-optimizer-asm-regs-harder.js
@@ -149,5 +149,11 @@ function linkedVars() {
}
return outer1 + outer2;
}
-// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "stackRestore", "switchey", "switchey2", "iffey", "labelledJump", "linkedVars']
+function deadCondExpr(input) {
+ input = input|0;
+ var dead = 0, temp = 0;
+ dead = (!input ? -1 : input)|0;
+ return temp|0;
+}
+// EMSCRIPTEN_GENERATED_FUNCTIONS: ["asm", "_doit", "stackRestore", "switchey", "switchey2", "iffey", "labelledJump", "linkedVars", "deadCondExpr"]
diff --git a/tools/webidl_binder.py b/tools/webidl_binder.py
new file mode 100644
index 00000000..168460fe
--- /dev/null
+++ b/tools/webidl_binder.py
@@ -0,0 +1,433 @@
+
+'''
+WebIDL binder
+
+https://github.com/kripken/emscripten/wiki/WebIDL-Binder
+'''
+
+import os, sys
+
+import shared
+
+sys.path.append(shared.path_from_root('third_party'))
+sys.path.append(shared.path_from_root('third_party', 'ply'))
+
+import WebIDL
+
+class Dummy:
+ def __init__(self, init):
+ for k, v in init.iteritems():
+ self.__dict__[k] = v
+
+ def getExtendedAttribute(self, name):
+ return None
+
+input_file = sys.argv[1]
+output_base = sys.argv[2]
+
+shared.try_delete(output_base + '.cpp')
+shared.try_delete(output_base + '.js')
+
+p = WebIDL.Parser()
+p.parse(open(input_file).read())
+data = p.finish()
+
+interfaces = {}
+implements = {}
+
+for thing in data:
+ if isinstance(thing, WebIDL.IDLInterface):
+ interfaces[thing.identifier.name] = thing
+ elif isinstance(thing, WebIDL.IDLImplementsStatement):
+ implements.setdefault(thing.implementor.identifier.name, []).append(thing.implementee.identifier.name)
+
+#print interfaces
+#print implements
+
+pre_c = []
+mid_c = []
+mid_js = []
+
+pre_c += [r'''
+#include <emscripten.h>
+''']
+
+mid_c += [r'''
+extern "C" {
+''']
+
+def emit_constructor(name):
+ global mid_js
+ mid_js += [r'''%s.prototype = %s;
+%s.prototype.constructor = %s;
+%s.prototype.__class__ = %s;
+%s.__cache__ = {};
+Module['%s'] = %s;
+''' % (name, 'Object.create(%s.prototype)' % (implements[name][0] if implements.get(name) else 'WrapperObject'), name, name, name, name, name, name, name)]
+
+
+mid_js += ['''
+// Bindings utilities
+
+function WrapperObject() {
+}
+''']
+
+emit_constructor('WrapperObject')
+
+mid_js += ['''
+function getCache(__class__) {
+ return (__class__ || WrapperObject).__cache__;
+}
+Module['getCache'] = getCache;
+
+function wrapPointer(ptr, __class__) {
+ var cache = getCache(__class__);
+ var ret = cache[ptr];
+ if (ret) return ret;
+ ret = Object.create((__class__ || WrapperObject).prototype);
+ ret.ptr = ptr;
+ return cache[ptr] = ret;
+}
+Module['wrapPointer'] = wrapPointer;
+
+function castObject(obj, __class__) {
+ return wrapPointer(obj.ptr, __class__);
+}
+Module['castObject'] = castObject;
+
+Module['NULL'] = wrapPointer(0);
+
+function destroy(obj) {
+ if (!obj['__destroy__']) throw 'Error: Cannot destroy object. (Did you create it yourself?)';
+ obj['__destroy__']();
+ // Remove from cache, so the object can be GC'd and refs added onto it released
+ delete getCache(obj.__class__)[obj.ptr];
+}
+Module['destroy'] = destroy;
+
+function compare(obj1, obj2) {
+ return obj1.ptr === obj2.ptr;
+}
+Module['compare'] = compare;
+
+function getPointer(obj) {
+ return obj.ptr;
+}
+Module['getPointer'] = getPointer;
+
+function getClass(obj) {
+ return obj.__class__;
+}
+Module['getClass'] = getClass;
+
+// Converts a value into a C-style string.
+function ensureString(value) {
+ if (typeof value == 'string') return allocate(intArrayFromString(value), 'i8', ALLOC_STACK);
+ return value;
+}
+
+''']
+
+C_FLOATS = ['float', 'double']
+
+def type_to_c(t, non_pointing=False):
+ #print 'to c ', t
+ t = t.replace(' (Wrapper)', '')
+ if t == 'Long':
+ return 'int'
+ elif t == 'Short':
+ return 'short'
+ elif t == 'Void':
+ return 'void'
+ elif t == 'String':
+ return 'char*'
+ elif t == 'Float':
+ return 'float'
+ elif t == 'Double':
+ return 'double'
+ elif t == 'Boolean':
+ return 'bool'
+ elif t in interfaces:
+ return (interfaces[t].getExtendedAttribute('Prefix') or [''])[0] + t + ('' if non_pointing else '*')
+ else:
+ return t
+
+def take_addr_if_nonpointer(m):
+ if m.getExtendedAttribute('Ref') or m.getExtendedAttribute('Value'):
+ return '&'
+ return ''
+
+def deref_if_nonpointer(m):
+ if m.getExtendedAttribute('Ref') or m.getExtendedAttribute('Value'):
+ return '*'
+ return ''
+
+def type_to_cdec(raw):
+ name = ret = type_to_c(raw.type.name, non_pointing=True)
+ if raw.getExtendedAttribute('Const'): ret = 'const ' + ret
+ if name not in interfaces: return ret
+ if raw.getExtendedAttribute('Ref'):
+ return ret + '&'
+ if raw.getExtendedAttribute('Value'):
+ return ret
+ return ret + '*'
+
+def render_function(class_name, func_name, sigs, return_type, non_pointer, copy, operator, constructor, func_scope, call_content=None, const=False):
+ global mid_c, mid_js, js_impl_methods
+
+ #print 'renderfunc', class_name, func_name, sigs, return_type, constructor
+
+ bindings_name = class_name + '_' + func_name
+ min_args = min(sigs.keys())
+ max_args = max(sigs.keys())
+
+ c_names = {}
+
+ # JS
+
+ cache = ('getCache(%s)[this.ptr] = this;' % class_name) if constructor else ''
+ call_prefix = '' if not constructor else 'this.ptr = '
+ call_postfix = ''
+ if return_type != 'Void' and not constructor: call_prefix = 'return '
+ if not constructor:
+ if return_type in interfaces:
+ call_prefix += 'wrapPointer('
+ call_postfix += ', ' + return_type + ')'
+
+ args = ['arg%d' % i for i in range(max_args)]
+ if not constructor:
+ body = ' var self = this.ptr;\n'
+ pre_arg = ['self']
+ else:
+ body = ''
+ pre_arg = []
+
+ for i in range(max_args):
+ # note: null has typeof object, but is ok to leave as is, since we are calling into asm code where null|0 = 0
+ body += " if (arg%d && typeof arg%d === 'object') arg%d = arg%d.ptr;\n" % (i, i, i, i)
+ body += " else arg%d = ensureString(arg%d);\n" % (i, i)
+
+ for i in range(min_args, max_args):
+ c_names[i] = 'emscripten_bind_%s_%d' % (bindings_name, i)
+ body += ' if (arg%d === undefined) { %s%s(%s)%s%s }\n' % (i, call_prefix, '_' + c_names[i], ', '.join(pre_arg + args[:i]), call_postfix, '' if 'return ' in call_prefix else '; ' + (cache or ' ') + 'return')
+ c_names[max_args] = 'emscripten_bind_%s_%d' % (bindings_name, max_args)
+ body += ' %s%s(%s)%s;\n' % (call_prefix, '_' + c_names[max_args], ', '.join(pre_arg + args), call_postfix)
+ if cache:
+ body += ' ' + cache + '\n'
+ mid_js += [r'''function%s(%s) {
+%s
+}''' % ((' ' + func_name) if constructor else '', ', '.join(args), body[:-1])]
+
+ # C
+
+ for i in range(min_args, max_args+1):
+ raw = sigs.get(i)
+ if raw is None: continue
+ sig = [arg.type.name for arg in raw]
+
+ c_arg_types = map(type_to_c, sig)
+
+ normal_args = ', '.join(['%s arg%d' % (c_arg_types[j], j) for j in range(i)])
+ if constructor:
+ full_args = normal_args
+ else:
+ full_args = type_to_c(class_name, non_pointing=True) + '* self' + ('' if not normal_args else ', ' + normal_args)
+ call_args = ', '.join(['%sarg%d' % ('*' if raw[j].getExtendedAttribute('Ref') else '', j) for j in range(i)])
+ if constructor:
+ call = 'new ' + type_to_c(class_name, non_pointing=True)
+ call += '(' + call_args + ')'
+ elif call_content is not None:
+ call = call_content
+ else:
+ call = 'self->' + func_name
+ call += '(' + call_args + ')'
+
+ if operator:
+ assert '=' in operator, 'can only do += *= etc. for now, all with "="'
+ cast_self = 'self'
+ if class_name != func_scope:
+ # this function comes from an ancestor class; for operators, we must cast it
+ cast_self = 'dynamic_cast<' + type_to_c(func_scope) + '>(' + cast_self + ')'
+ call = '(*%s %s %sarg0)' % (cast_self, operator, '*' if sig[0] in interfaces else '')
+
+ pre = ''
+
+ basic_return = 'return ' if constructor or return_type is not 'Void' else ''
+ return_prefix = basic_return
+ return_postfix = ''
+ if non_pointer:
+ return_prefix += '&';
+ if copy:
+ pre += ' static %s temp;\n' % type_to_c(return_type, non_pointing=True)
+ return_prefix += '(temp = '
+ return_postfix += ', &temp)'
+
+ c_return_type = type_to_c(return_type)
+ mid_c += [r'''
+%s%s EMSCRIPTEN_KEEPALIVE %s(%s) {
+%s %s%s%s;
+}
+''' % ('const ' if const else '', type_to_c(class_name) if constructor else c_return_type, c_names[i], full_args, pre, return_prefix, call, return_postfix)]
+
+ if not constructor:
+ if i == max_args:
+ dec_args = ', '.join(map(lambda j: type_to_cdec(raw[j]) + ' arg' + str(j), range(i)))
+ js_call_args = ', '.join(['%sarg%d' % (('(int)' if sig[j] in interfaces else '') + ('&' if raw[j].getExtendedAttribute('Ref') or raw[j].getExtendedAttribute('Value') else ''), j) for j in range(i)])
+
+ js_impl_methods += [r''' %s %s(%s) {
+ %sEM_ASM_%s({
+ var self = Module['getCache'](Module['%s'])[$0];
+ if (!self.hasOwnProperty('%s')) throw 'a JSImplementation must implement all functions, you forgot %s::%s.';
+ %sself.%s(%s)%s;
+ }, (int)this%s);
+ }''' % (c_return_type, func_name, dec_args,
+ basic_return, 'INT' if c_return_type not in C_FLOATS else 'DOUBLE',
+ class_name,
+ func_name, class_name, func_name,
+ return_prefix,
+ func_name,
+ ','.join(['$%d' % i for i in range(1, max_args)]),
+ return_postfix,
+ (', ' if js_call_args else '') + js_call_args)]
+
+
+for name, interface in interfaces.iteritems():
+ js_impl = interface.getExtendedAttribute('JSImplementation')
+ if not js_impl: continue
+ implements[name] = [js_impl[0]]
+
+names = interfaces.keys()
+names.sort(lambda x, y: 1 if implements.get(x) and implements[x][0] == y else (-1 if implements.get(y) and implements[y][0] == x else 0))
+
+for name in names:
+ interface = interfaces[name]
+
+ mid_js += ['\n// ' + name + '\n']
+ mid_c += ['\n// ' + name + '\n']
+
+ global js_impl_methods
+ js_impl_methods = []
+
+ cons = interface.getExtendedAttribute('Constructor')
+ if type(cons) == list: raise Exception('do not use "Constructor", instead create methods with the name of the interface')
+
+ js_impl = interface.getExtendedAttribute('JSImplementation')
+ if js_impl:
+ js_impl = js_impl[0]
+
+ # Methods
+
+ seen_constructor = False # ensure a constructor, even for abstract base classes
+ for m in interface.members:
+ if m.identifier.name == name:
+ seen_constructor = True
+ break
+ if not seen_constructor:
+ mid_js += ['function %s() { throw "cannot construct a %s, no constructor in IDL" }\n' % (name, name)]
+ emit_constructor(name)
+
+ for m in interface.members:
+ if not m.isMethod(): continue
+ constructor = m.identifier.name == name
+ if not constructor:
+ parent_constructor = False
+ temp = m.parentScope
+ while temp.parentScope:
+ if temp.identifier.name == m.identifier.name:
+ parent_constructor = True
+ temp = temp.parentScope
+ if parent_constructor:
+ continue
+ if not constructor:
+ mid_js += [r'''
+%s.prototype.%s = ''' % (name, m.identifier.name)]
+ sigs = {}
+ return_type = None
+ for ret, args in m.signatures():
+ if return_type is None:
+ return_type = ret.name
+ else:
+ assert return_type == ret.name, 'overloads must have the same return type'
+ for i in range(len(args)+1):
+ if i == len(args) or args[i].optional:
+ assert i not in sigs, 'overloading must differentiate by # of arguments (cannot have two signatures that differ by types but not by length)'
+ sigs[i] = args[:i]
+ render_function(name,
+ m.identifier.name, sigs, return_type,
+ m.getExtendedAttribute('Ref'),
+ m.getExtendedAttribute('Value'),
+ (m.getExtendedAttribute('Operator') or [None])[0],
+ constructor,
+ func_scope=m.parentScope.identifier.name,
+ const=m.getExtendedAttribute('Const'))
+ mid_js += [';\n']
+ if constructor:
+ emit_constructor(name)
+
+ for m in interface.members:
+ if not m.isAttr(): continue
+ attr = m.identifier.name
+
+ get_name = 'get_' + attr
+ mid_js += [r'''
+ %s.prototype.%s= ''' % (name, get_name)]
+ render_function(name,
+ get_name, { 0: [] }, m.type.name,
+ None,
+ None,
+ None,
+ False,
+ func_scope=interface,
+ call_content=take_addr_if_nonpointer(m) + 'self->' + attr,
+ const=m.getExtendedAttribute('Const'))
+
+ set_name = 'set_' + attr
+ mid_js += [r'''
+ %s.prototype.%s= ''' % (name, set_name)]
+ render_function(name,
+ set_name, { 1: [Dummy({ 'type': m.type })] }, 'Void',
+ None,
+ None,
+ None,
+ False,
+ func_scope=interface,
+ call_content='self->' + attr + ' = ' + deref_if_nonpointer(m) + 'arg0',
+ const=m.getExtendedAttribute('Const'))
+
+ if not interface.getExtendedAttribute('NoDelete'):
+ mid_js += [r'''
+ %s.prototype.__destroy__ = ''' % name]
+ render_function(name,
+ '__destroy__', { 0: [] }, 'Void',
+ None,
+ None,
+ None,
+ False,
+ func_scope=interface,
+ call_content='delete self')
+
+ # Emit C++ class implementation that calls into JS implementation
+
+ if js_impl:
+ pre_c += [r'''
+class %s : public %s {
+public:
+%s
+};
+''' % (name, type_to_c(js_impl, non_pointing=True), '\n'.join(js_impl_methods))]
+
+mid_c += ['\n}\n\n']
+mid_js += ['\n']
+
+# Write
+
+c = open(output_base + '.cpp', 'w')
+for x in pre_c: c.write(x)
+for x in mid_c: c.write(x)
+c.close()
+
+js = open(output_base + '.js', 'w')
+for x in mid_js: js.write(x)
+js.close()
+