diff options
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/bindings_generator.py | 55 | ||||
-rwxr-xr-x | tools/ffdb.py | 342 | ||||
-rw-r--r-- | tools/js-optimizer.js | 121 | ||||
-rw-r--r-- | tools/jsrun.py | 9 | ||||
-rw-r--r-- | tools/shared.py | 9 | ||||
-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/webidl_binder.py | 432 |
8 files changed, 923 insertions, 61 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/ffdb.py b/tools/ffdb.py new file mode 100755 index 00000000..c22fd9db --- /dev/null +++ b/tools/ffdb.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python + +import socket, json, sys, uuid, datetime, time, logging, cgi, zipfile, os, tempfile, atexit, subprocess + +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): + zipf = zipfile.ZipFile(zipfilename, 'w') + 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) + print 'Compressing ' + str(n) + '/' + str(len(files_to_compress)) + ': "' + os.path.relpath(filename, path) + '" (' + sizeof_fmt(filesize) + ')...' + n += 1 + zipf.write(os.path.join(root, file)) + 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)) + 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(): + global read_queue, b2g_socket + read_queue += b2g_socket.recv(65536*2) + 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) + 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 = {}): + 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() + +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 + b2g_socket.sendall(message) + +# 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 + 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. + + 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) + + 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': + 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 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)) + '.' + uploadResponse = send_b2g_cmd(webappsActorName, 'uploadPackage') + packageUploadActor = uploadResponse['actor'] + app_file = open(target_app_path, 'rb') + data = app_file.read() + file_size = len(data) + chunk_size = 4*1024*1024 + i = 0 + start_time = time.time() + 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!' + 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 32c26c51..2914b6e8 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1342,13 +1342,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 +1374,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 +1423,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 +1437,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]); @@ -3721,7 +3717,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; @@ -4926,36 +4922,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); } } }); @@ -4963,10 +4967,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); 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..826baa83 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1650,12 +1650,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: 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/webidl_binder.py b/tools/webidl_binder.py new file mode 100644 index 00000000..0507cc78 --- /dev/null +++ b/tools/webidl_binder.py @@ -0,0 +1,432 @@ + +''' +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 |