diff options
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/bindings_generator.py | 55 | ||||
-rw-r--r-- | tools/bisect_pair_lines.py | 63 | ||||
-rw-r--r-- | tools/eliminator/asm-eliminator-test-output.js | 129 | ||||
-rw-r--r-- | tools/eliminator/asm-eliminator-test.js | 94 | ||||
-rwxr-xr-x | tools/ffdb.py | 453 | ||||
-rw-r--r-- | tools/js-optimizer.js | 396 | ||||
-rw-r--r-- | tools/js_optimizer.py | 28 | ||||
-rw-r--r-- | tools/jsrun.py | 9 | ||||
-rw-r--r-- | tools/shared.py | 189 | ||||
-rw-r--r-- | tools/system_libs.py | 65 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-last-output.js | 27 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-last.js | 33 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre-f32.js | 8 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre-output-f32.js | 8 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre-output.js | 78 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre.js | 79 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-regs-harder-output.js | 5 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-regs-harder.js | 8 | ||||
-rw-r--r-- | tools/webidl_binder.py | 433 |
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() + |