diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/js-optimizer.js | 9 | ||||
-rwxr-xr-x | tools/reproduceriter.py | 311 | ||||
-rw-r--r-- | tools/test-js-optimizer-t3.js | 49 |
3 files changed, 368 insertions, 1 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d58c8c6c..e1cfbe65 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -485,13 +485,20 @@ function optimizeShiftsInternal(ast, conservative) { } // vars // XXX if var has >>=, ignore it here? That means a previous pass already optimized it - traverse(fun, function(node, type) { + var hasSwitch = traverse(fun, function(node, type) { if (type == 'var') { node[1].forEach(function(arg) { newVar(arg[0], false, arg[1]); }); + } else if (type == 'switch') { + // The relooper can't always optimize functions, and we currently don't work with + // switch statements when optimizing shifts. Bail. + return true; } }); + if (hasSwitch) { + break; + } // uses and defs TODO: weight uses by being inside a loop (powers). without that, we // optimize for code size, not speed. traverse(fun, function(node, type, stack) { diff --git a/tools/reproduceriter.py b/tools/reproduceriter.py new file mode 100755 index 00000000..3402192c --- /dev/null +++ b/tools/reproduceriter.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python + +''' + +* This is a work in progress * + +Reproducer Rewriter +=================== + +Processes a project and rewrites it so as to generate deterministic, +reproducible automatic results. For example, you can run this on a +game, and then when the game is run it will record user input and +sources of nondeterminism like Math.random(). You can then run +that recording as a benchmark or as a demo, it should give nearly +identical results every time it is run to the data that was +recorded. + +Usage: + +1. Run this script as + + reproduceriter.py IN_DIR OUT_DIR FIRST_JS + + IN_DIR should be the project directory, and OUT_DIR will be + created with the instrumented code (OUT_DIR will be overwritten + if it exists). FIRST_JS should be a path (relative to IN_DIR) to + the first JavaScript file loaded by the project (this tool + will add code to that). + + You will need to call + + Recorder.start(); + + at the right time to start the relevant event loop. For + example, if your application is a game that starts receiving + events when in fullscreen, add something like + + if (typeof Recorder != 'undefined') Recorder.start(); + + in the button that launches fullscreen. start() will start + either recording when in record mode, or replaying when + in replay mode, so you need this in both modes. + +2. Run the instrumented project in OUR_DIR and interact with + the program. When you are done recording, open the web + console and run + + Recorder.finish(); + + This will write out the recorded data into the current tab. + Save it as + + repro.data + + in OUT_DIR. + +3. To re-play the recorded data, run the instrumented build + with + + &reproduce=repro.data + + Note that as mentioned above you need to call + + Recorder.start(); + + when the recorded event loop should start to replay. + +Notes: + + * When we start to replay events, the assumption is that + there is nothing asynchronous that affects execution. So + asynchronous loading of files should have already + completed. + + TODO: start running recorded events with some trigger, for example the fullscreen button in BananaBread + +Examples + + * BananaBread: Unpack into a directory called bb, then one + directory up, run + + emscripten/tools/reproduceriter.py bb bench js/game-setup.js +''' + +import os, sys, shutil, re + +assert len(sys.argv) == 4, 'Usage: reproduceriter.py IN_DIR OUT_DIR FIRST_JS' + +# Process input args + +in_dir = sys.argv[1] +out_dir = sys.argv[2] +first_js = sys.argv[3] + +if os.path.exists(out_dir): + shutil.rmtree(out_dir) +assert os.path.exists(os.path.join(in_dir, first_js)) + +# Copy project + +print 'copying tree...' + +shutil.copytree(in_dir, out_dir) + +# Add customizations in all JS files + +print 'add customizations...' + +for parent, dirs, files in os.walk(out_dir): + for filename in files: + if filename.endswith('.js'): + fullname = os.path.join(parent, filename) + print ' ', fullname + js = open(fullname).read() + js = re.sub('document\.on(\w+) ?= ?([\w.$]+)', lambda m: 'Recorder.onEvent("' + m.group(1) + '", ' + m.group(2) + ')', js) + js = re.sub('''([\w.'"\[\]]+)\.addEventListener\(([\w,. $]+)\)''', lambda m: 'Recorder.addListener(' + m.group(1) + ', ' + m.group(2) + ')', js) + open(fullname, 'w').write(js) + +# Add our boilerplate + +print 'add boilerplate...' + +open(os.path.join(out_dir, first_js), 'w').write(''' +var Recorder = (function() { + var recorder; + var init = 'reproduce='; + var initLocation = window.location.search.indexOf(init); + var replaying = initLocation >= 0; + var raf = window['requestAnimationFrame'] || + window['mozRequestAnimationFrame'] || + window['webkitRequestAnimationFrame'] || + window['msRequestAnimationFrame'] || + window['oRequestAnimationFrame']; + if (!replaying) { + // Prepare to record + recorder = {}; + // Start + recorder.frameCounter = 0; // the frame counter is used to know when to replay events + recorder.start = function() { + alert("Starting recording! Don't forget to Recorder.finish() afterwards!"); + function count() { + recorder.frameCounter++; + raf(count); + } + count(); + recorder.started = true; + }; + // Math.random + recorder.randoms = []; + var random = Math.random; + Math.random = function() { + var ret = random(); + recorder.randoms.push(ret); + return ret; + }; + // Date.now, performance.now + recorder.dnows = []; + var dnow = Date.now; + Date.now = function() { + var ret = dnow(); + recorder.dnows.push(ret); + return ret; + }; + recorder.pnows = []; + var pnow = performance.now; + performance.now = function() { + var ret = pnow(); + recorder.pnows.push(ret); + return ret; + }; + // Events + recorder.devents = []; // document events + recorder.onEvent = function(which, callback) { + document['on' + which] = function(event) { + if (!recorder.started) return true; + event.frameCounter = recorder.frameCounter; + recorder.devents.push(event); + return callback(event); // XXX do we need to record the return value? + }; + }; + recorder.tevents = []; // custom-target events. Currently we assume a single such custom target (aside from document), e.g., a canvas for the game. + recorder.addListener = function(target, which, callback, arg) { + target.addEventListener(which, function(event) { + if (!recorder.started) return true; + event.frameCounter = recorder.frameCounter; + recorder.tevents.push(event); + return callback(event); // XXX do we need to record the return value? + }, arg); + }; + // Finish + recorder.finish = function() { + // Reorder data because pop() is faster than shift() + recorder.randoms.reverse(); + recorder.dnows.reverse(); + recorder.pnows.reverse(); + recorder.devents.reverse(); + recorder.tevents.reverse(); + // Make JSON.stringify work on data from native event objects (and only store relevant ones) + var importantProperties = { + type: 1, + movementX: 1, mozMovementX: 1, webkitMovementX: 1, + movementY: 1, mozMovementY: 1, webkitMovementY: 1, + detail: 1, + wheelDelta: 1, + pageX: 1, + pageY: 1, + button: 1, + keyCode: 1, + frameCounter: 1 + }; + function importantize(event) { + var ret = {}; + for (var prop in importantProperties) { + if (prop in event) { + ret[prop] = event[prop]; + } + } + return ret; + } + recorder.devents = recorder.devents.map(importantize); + recorder.tevents = recorder.tevents.map(importantize); + // Write out + alert('Writing out data, remember to save!'); + setTimeout(function() { + document.open(); + document.write(JSON.stringify(recorder)); + document.close(); + }, 0); + return '.'; + }; + } else { + // Load recording + var dataPath = window.location.search.substring(initLocation + init.length); + var baseURL = window.location.toString().replace('://', 'cheez999').split('?')[0].split('/').slice(0, -1).join('/').replace('cheez999', '://'); + if (baseURL[baseURL.length-1] != '/') baseURL += '/'; + var path = baseURL + dataPath; + alert('Loading replay from ' + path); + var request = new XMLHttpRequest(); + request.open('GET', path, false); + request.send(); + var raw = request.responseText; + raw = raw.substring(raw.indexOf('{'), raw.lastIndexOf('}')+1); // remove <html> etc + recorder = JSON.parse(raw); + // prepare to replay + // Start + recorder.frameCounter = 0; // the frame counter is used to know when to replay events + recorder.start = function() { + function count() { + recorder.frameCounter++; + raf(count); + // replay relevant events for this frame + while (recorder.devents.length && recorder.devents[recorder.devents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.devents.pop(); + recorder['on' + event.type](event); + } + while (recorder.tevents.length && recorder.tevents[recorder.tevents.length-1].frameCounter <= recorder.frameCounter) { + var event = recorder.tevents.pop(); + recorder['event' + event.type](event); + } + } + count(); + }; + // Math.random + Math.random = function() { + if (recorder.randoms.length > 0) { + return recorder.randoms.pop(); + } else { + recorder.finish(); + throw 'consuming too many values!'; + } + }; + // Date.now, performance.now + Date.now = function() { + if (recorder.dnows.length > 0) { + return recorder.dnows.pop(); + } else { + recorder.finish(); + throw 'consuming too many values!'; + } + }; + performance.now = function() { + if (recorder.pnows.length > 0) { + return recorder.pnows.pop(); + } else { + recorder.finish(); + throw 'consuming too many values!'; + } + }; + // Events + recorder.onEvent = function(which, callback) { + recorder['on' + which] = callback; + }; + recorder.eventCallbacks = {}; + recorder.addListener = function(target, which, callback, arg) { + recorder['event' + which] = callback; + }; + // Finish + recorder.finish = function() { + if (recorder.onFinish) { + recorder.onFinish(); + recorder.onFinish = null; + } + }; + } + return recorder; +})(); +''' + open(os.path.join(in_dir, first_js)).read() +) + +print 'done!' + diff --git a/tools/test-js-optimizer-t3.js b/tools/test-js-optimizer-t3.js new file mode 100644 index 00000000..beef1f39 --- /dev/null +++ b/tools/test-js-optimizer-t3.js @@ -0,0 +1,49 @@ +function _png_create_write_struct_2($user_png_ver, $error_ptr, $error_fn, $warn_fn, $mem_ptr, $malloc_fn, $free_fn) { + var $png_ptr$s2; + var __label__; + __label__ = 2; + var setjmpTable = { + "2": (function(value) { + __label__ = 5; + $call1 = value; + }), + dummy: 0 + }; + while (1) try { + switch (__label__) { + case 2: + var $png_ptr; + var $call = _png_create_struct(1); + $png_ptr = $call; + var $call1 = (HEAP32[$png_ptr >> 2] = __label__, 0); + __label__ = 5; + break; + case 5: + var $2 = $png_ptr; + if (($call1 | 0) == 0) { + __label__ = 4; + break; + } else { + __label__ = 3; + break; + } + case 3: + var $4 = HEAP32[($png_ptr >> 2) + (148 >> 2)]; + _png_free($2, $4); + HEAP32[($png_ptr >> 2) + (148 >> 2)] = 0; + _png_destroy_struct($png_ptr); + var $retval_0 = 0; + __label__ = 4; + break; + case 4: + var $retval_0; + return $retval_0; + default: + assert(0, "bad label: " + __label__); + } + } catch (e) { + if (!e.longjmp) throw e; + setjmpTable[e.label](e.value); + } +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["_png_create_write_struct_2"] |