aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rwxr-xr-xtools/bindings_generator.py55
-rwxr-xr-xtools/ffdb.py342
-rw-r--r--tools/js-optimizer.js121
-rw-r--r--tools/jsrun.py9
-rw-r--r--tools/shared.py9
-rw-r--r--tools/test-js-optimizer-asm-pre-f32.js8
-rw-r--r--tools/test-js-optimizer-asm-pre-output-f32.js8
-rw-r--r--tools/webidl_binder.py432
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