From 2a3e84086e1c28589e715b5501f008f803e3bc93 Mon Sep 17 00:00:00 2001 From: Usagi Ito Date: Wed, 10 Dec 2014 13:08:05 +0900 Subject: [PATCH 01/31] fix #3066 glfwGetCursorPos parameter typing bug 1. fix `glfwGetCursorPos`(GLFW3 API): - parameter type from 'i32' to 'double'. 2. add `GLFW.getMousePos`: - `i32` version for GLFW2. 3. fix `glfwGetMousePos`(GLFW2 API): - from call `GLFW.getCursorPos` to call `GLFW.getMousePos` --- src/library_glfw.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/library_glfw.js b/src/library_glfw.js index 7998022485c9b..4a44a55b4da27 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -527,6 +527,11 @@ var LibraryGLFW = { }, getCursorPos: function(winid, x, y) { + setValue(x, Browser.mouseX, 'double'); + setValue(y, Browser.mouseY, 'double'); + }, + + getMousePos: function(winid, x, y) { setValue(x, Browser.mouseX, 'i32'); setValue(y, Browser.mouseY, 'i32'); }, @@ -1145,7 +1150,7 @@ var LibraryGLFW = { }, glfwGetMousePos: function(x, y) { - GLFW.getCursorPos(GLFW.active.id, x, y); + GLFW.getMousePos(GLFW.active.id, x, y); }, glfwSetMousePos: function(x, y) { From 84bbf2a6e2c84de9576976e40c7d56e17eb79fb1 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Wed, 10 Dec 2014 14:50:31 +0700 Subject: [PATCH 02/31] Disable warning when asm.js validation is already disabled. When ASM_JS is already 2, no need to warn about allowing memory growth as the user must have known about this and set ASM_JS to 2. --- emcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emcc b/emcc index a78fa890131c2..f412ccb8d452a 100755 --- a/emcc +++ b/emcc @@ -932,7 +932,7 @@ try: js_opts = True logging.warning('enabling js opts to allow SAFE_HEAP to work properly') - if shared.Settings.ALLOW_MEMORY_GROWTH: + if shared.Settings.ALLOW_MEMORY_GROWTH and shared.Settings.ASM_JS != 2: logging.warning('Disabling asm.js validation for memory growth (memory can grow, but you lose some amount of speed)'); shared.Settings.ASM_JS = 2 From c884a4be5fa3caa05e53eed631684288799aa44a Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Wed, 10 Dec 2014 15:03:02 +0700 Subject: [PATCH 03/31] Set ASM_JS default prior to processing command line. This lets the user set ASM_JS to 2 and avoid a warning if they also set ALLOW_MEMORY_GROWTH. --- emcc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/emcc b/emcc index f412ccb8d452a..fd4e4c75ff6cd 100755 --- a/emcc +++ b/emcc @@ -860,6 +860,11 @@ try: # Apply optimization level settings shared.Settings.apply_opt_level(opt_level, noisy=True) + fastcomp = os.environ.get('EMCC_FAST_COMPILER') != '0' + if fastcomp: + # Set ASM_JS default here so that we can override it from the command line. + shared.Settings.ASM_JS = 1 if opt_level > 0 else 2 + # Apply -s settings in newargs here (after optimization levels, so they can override them) for change in settings_changes: key, value = change.split('=') @@ -871,11 +876,9 @@ try: if key == 'EXPORTED_FUNCTIONS': shared.Settings.ORIGINAL_EXPORTED_FUNCTIONS = shared.Settings.EXPORTED_FUNCTIONS[:] # used for warnings in emscripten.py - fastcomp = os.environ.get('EMCC_FAST_COMPILER') != '0' - if fastcomp: - shared.Settings.ASM_JS = 1 if opt_level > 0 else 2 try: + assert shared.Settings.ASM_JS > 0, 'ASM_JS must be enabled in fastcomp' assert shared.Settings.UNALIGNED_MEMORY == 0, 'forced unaligned memory not supported in fastcomp' assert shared.Settings.CHECK_HEAP_ALIGN == 0, 'check heap align not supported in fastcomp - use SAFE_HEAP instead' assert shared.Settings.SAFE_DYNCALLS == 0, 'safe dyncalls not supported in fastcomp' From c59dab8d49ce4ff6615c39b7d39ae7fefd27e8b3 Mon Sep 17 00:00:00 2001 From: Gauthier Billot Date: Wed, 10 Dec 2014 14:13:14 +0100 Subject: [PATCH 04/31] Fixed wrong variable name in library_sdl.js --- src/library_sdl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library_sdl.js b/src/library_sdl.js index befa4e4b32c9d..839f7f1b2f6a4 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -491,7 +491,7 @@ var LibrarySDL = { SDL.updateRect(dstrect, dr); } } - var blitw, blitr; + var blitw, blith; if (scale) { blitw = dr.w; blith = dr.h; } else { From 5e7e1ae50e3757601c32c8aa2c52734cf0459e6e Mon Sep 17 00:00:00 2001 From: Victor Costan Date: Wed, 10 Dec 2014 13:04:51 -0500 Subject: [PATCH 05/31] Make resize in std::vector bindings not assume a default constructor. This change uses the std::vector::resize variant that takes a value argument and uses it if the vector needs to be extended. The previous attempt of adding resize used the 1 argument version, which required that the vector's element type has a default constructor. --- system/include/emscripten/bind.h | 2 +- tests/embind/embind.test.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/system/include/emscripten/bind.h b/system/include/emscripten/bind.h index 1ec344927b106..7f9de9ab8404c 100644 --- a/system/include/emscripten/bind.h +++ b/system/include/emscripten/bind.h @@ -1379,7 +1379,7 @@ namespace emscripten { typedef std::vector VecType; void (VecType::*push_back)(const T&) = &VecType::push_back; - void (VecType::*resize)(const size_t) = &VecType::resize; + void (VecType::*resize)(const size_t, const T&) = &VecType::resize; return class_>(name) .template constructor<>() .function("push_back", push_back) diff --git a/tests/embind/embind.test.js b/tests/embind/embind.test.js index 1b29c5ad79755..141f5bc747fd0 100644 --- a/tests/embind/embind.test.js +++ b/tests/embind/embind.test.js @@ -918,23 +918,23 @@ module({ vec.delete(); }); - test("resize appends the default value", function() { + test("resize appends the given value", function() { var vec = cm.emval_test_return_vector(); - vec.resize(5); + vec.resize(5, 42); assert.equal(5, vec.size()); assert.equal(10, vec.get(0)); assert.equal(20, vec.get(1)); assert.equal(30, vec.get(2)); - assert.equal(0, vec.get(3)); - assert.equal(0, vec.get(4)); + assert.equal(42, vec.get(3)); + assert.equal(42, vec.get(4)); vec.delete(); }); test("resize preserves content when shrinking", function() { var vec = cm.emval_test_return_vector(); - vec.resize(2); + vec.resize(2, 42); assert.equal(2, vec.size()); assert.equal(10, vec.get(0)); assert.equal(20, vec.get(1)); From 5e32b716b04b8fad5d451498da070b31d5c672cd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 13:34:21 -0800 Subject: [PATCH 06/31] refactoring in preparation for conversion of registerizeHarder --- tools/optimizer/optimizer.cpp | 1121 ++++++++++++++++++++++++++++++++- 1 file changed, 1105 insertions(+), 16 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index b5f7ddfff71e0..0ed2e13245f68 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -2189,6 +2189,32 @@ void optimizeFrounds(Ref ast) { } // Very simple 'registerization', coalescing of variables into a smaller number. + +const char* getRegPrefix(AsmType type) { + switch (type) { + case ASM_INT: return"i"; break; + case ASM_DOUBLE: return "d"; break; + case ASM_FLOAT: return "f"; break; + case ASM_FLOAT32X4: return "F4"; break; + case ASM_INT32X4: return "I4"; break; + case ASM_NONE: return "Z"; break; + default: assert(0); // type doesn't have a name yet + } + return nullptr; +} + +IString getRegName(AsmType type, int num) { + const char* str = getRegPrefix(type); + int size = strlen(str) + int(ceil(log10(num))) + 3; + char temp[size]; + int written = sprintf(temp, "%s%d", str, num); + assert(written < size); + temp[written] = 0; + IString ret; + ret.set(temp, false); + return ret; +} + void registerize(Ref ast) { traverseFunctions(ast, [](Ref fun) { AsmData asmData(fun); @@ -2222,23 +2248,9 @@ void registerize(Ref ast) { auto getNewRegName = [&](int num, IString name) { const char *str; AsmType type = asmData.getType(name); - switch (type) { - case ASM_INT: str = "i"; break; - case ASM_DOUBLE: str = "d"; break; - case ASM_FLOAT: str = "f"; break; - case ASM_FLOAT32X4: str = "F4"; break; - case ASM_INT32X4: str = "I4"; break; - case ASM_NONE: str = "Z"; break; - default: assert(0); // type doesn't have a name yet - } - int size = strlen(str) + int(ceil(log10(num))) + 3; - char *temp = (char*)malloc(size); - int written = sprintf(temp, "%s%d", str, num); - assert(written < size); - temp[written] = 0; - IString ret(temp); // likely interns a new string; leaks if not XXX FIXME - regTypes[ret] = type; + IString ret = getRegName(type, num); assert(!allVars.has(ret) || asmData.isLocal(ret)); // register must not shadow non-local name + regTypes[ret] = type; return ret; }; // Find the # of uses of each variable. @@ -2433,6 +2445,1082 @@ void registerize(Ref ast) { }); } +// Assign variables to 'registers', coalescing them onto a smaller number of shared +// variables. +// +// This does the same job as 'registerize' above, but burns a lot more cycles trying +// to reduce the total number of register variables. Key points about the operation: +// +// * we decompose the AST into a flow graph and perform a full liveness +// analysis, to determine which variables are live at each point. +// +// * variables that are live concurrently are assigned to different registers. +// +// * variables that are linked via 'x=y' style statements are assigned the same +// register if possible, so that the redundant assignment can be removed. +// (e.g. assignments used to pass state around through loops). +// +// * any code that cannot be reached through the flow-graph is removed. +// (e.g. redundant break statements like 'break L123; break;'). +// +// * any assignments that we can prove are not subsequently used are removed. +// (e.g. unnecessary assignments to the 'label' variable). +// +/* +void registerizeHarder(Ref ast) { + traverseFunctions(ast, [](Ref fun) { + + // Do not try to process non-validating methods, like the heap replacer + bool abort = false; + traverse(fun, function(node, type) { + if (type === NEW) abort = true; + }); + if (abort) return; + + AsmData asmData(fun); + + // Utilities for allocating register variables. + // We need distinct register pools for each type of variable. + + std::vector allRegsByType; + allRegsByType.resize(ASM_NONE+1); + int nextReg = 1; + + auto createReg = [&](IString forName) { + // Create a new register of type suitable for the given variable name. + AsmType type = asmData.getType(forName); + StringIntMap& allRegs = allRegsByType[type]; + reg = nextReg++; + allRegs[reg] = getRegName(type, reg); + return reg; + }; + + // Traverse the tree in execution order and synthesize a basic flow-graph. + // It's convenient to build a kind of "dual" graph where the nodes identify + // the junctions between blocks at which control-flow may branch, and each + // basic block is an edge connecting two such junctions. + // For each junction we store: + // * set of blocks that originate at the junction + // * set of blocks that terminate at the junction + // For each block we store: + // * a single entry junction + // * a single exit junction + // * a 'use' and 'kill' set of names for the block + // * full sequence of 'name' and 'assign' nodes in the block + // * whether each such node appears as part of a larger expression + // (and therefore cannot be safely eliminated) + // * set of labels that can be used to jump to this block + + var junctions = []; + var blocks = []; + var currEntryJunction = null; + var nextBasicBlock = null; + var isInExpr = 0; + var activeLabels = [{}]; + var nextLoopLabel = null; + + var ENTRY_JUNCTION = 0; + var EXIT_JUNCTION = 1; + var ENTRY_BLOCK = 0; + + function addJunction() { + // Create a new junction, without inserting it into the graph. + // This is useful for e.g. pre-allocating an exit node. + var id = junctions.length; + junctions[id] = {id: id, inblocks: {}, outblocks: {}}; + return id; + } + + function markJunction(id) { + // Mark current traversal location as a junction. + // This makes a new basic block exiting at this position. + if (id === undefined || id === null) { + id = addJunction(); + } + joinJunction(id, true); + return id; + } + + function setJunction(id, force) { + // Set the next entry junction to the given id. + // This can be used to enter at a previously-declared point. + // You can't return to a junction with no incoming blocks + // unless the 'force' parameter is specified. + assert(nextBasicBlock.nodes.length === 0, 'refusing to abandon an in-progress basic block') + if (force || setSize(junctions[id].inblocks) > 0) { + currEntryJunction = id; + } else { + currEntryJunction = null; + } + } + + function joinJunction(id, force) { + // Complete the pending basic block by exiting at this position. + // This can be used to exit at a previously-declared point. + if (currEntryJunction !== null) { + nextBasicBlock.id = blocks.length; + nextBasicBlock.entry = currEntryJunction; + nextBasicBlock.exit = id; + junctions[currEntryJunction].outblocks[nextBasicBlock.id] = 1; + junctions[id].inblocks[nextBasicBlock.id] = 1; + blocks.push(nextBasicBlock); + } + nextBasicBlock = { id: null, entry: null, exit: null, labels: {}, nodes: [], isexpr: [], use: {}, kill: {} }; + setJunction(id, force); + return id; + } + + function pushActiveLabels(onContinue, onBreak) { + // Push the target junctions for continuing/breaking a loop. + // This should be called before traversing into a loop. + 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); + } + + function popActiveLabels() { + // Pop the target junctions for continuing/breaking a loop. + // This should be called after traversing into a loop. + activeLabels.pop(); + } + + function markNonLocalJump(type, label) { + // Complete a block via 'return', 'break' or 'continue'. + // This joins the targetted junction and then sets the current junction to null. + // Any code traversed before we get back an existing junction is dead code. + if (type === 'return') { + joinJunction(EXIT_JUNCTION); + } else { + label = label ? label : null; + var targets = activeLabels[activeLabels.length-1][label]; + assert(targets, 'jump to unknown label'); + if (type === 'continue') { + joinJunction(targets[0]); + } else if (type === 'break') { + joinJunction(targets[1]); + } else { + assert(false, 'unknown jump node type'); + } + } + currEntryJunction = null; + } + + function addUseNode(node) { + // Mark a use of the given name node in the current basic block. + assert(node[0] === 'name', 'not a use node'); + var name = node[1]; + if (name in localVars) { + nextBasicBlock.nodes.push(node); + nextBasicBlock.isexpr.push(isInExpr); + if (!nextBasicBlock.kill[name]) { + nextBasicBlock.use[name] = 1; + } + } + } + + function addKillNode(node) { + // Mark an assignment to the given name node in the current basic block. + assert(node[0] === 'assign', 'not a kill node'); + assert(node[1] === true, 'not a kill node'); + assert(node[2][0] === 'name', 'not a kill node'); + var name = node[2][1]; + if (name in localVars) { + nextBasicBlock.nodes.push(node); + nextBasicBlock.isexpr.push(isInExpr); + nextBasicBlock.kill[name] = 1; + } + } + + function lookThroughCasts(node) { + // Look through value-preserving casts, like "x | 0" => "x" + if (node[0] === 'binary' && node[1] === '|') { + if (node[3][0] === 'num' && node[3][1] === 0) { + return lookThroughCasts(node[2]); + } + } + return node; + } + + function addBlockLabel(node) { + assert(nextBasicBlock.nodes.length === 0, 'cant add label to an in-progress basic block') + if (node[0] === 'num') { + nextBasicBlock.labels[node[1]] = 1; + } + } + + function isTrueNode(node) { + // Check if the given node is statically truthy. + return (node[0] === 'num' && node[1] != 0); + } + + function isFalseNode(node) { + // Check if the given node is statically falsy. + return (node[0] === 'num' && node[1] == 0); + } + + function morphNode(node, newNode) { + // In-place morph a node into some other type of node. + var i = 0; + while (i < node.length && i < newNode.length) { + node[i] = newNode[i]; + i++; + } + while (i < newNode.length) { + node.push(newNode[i]); + i++; + } + if (node.length > newNode.length) { + node.length = newNode.length; + } + } + + function buildFlowGraph(node) { + // Recursive function to build up the flow-graph. + // It walks the tree in execution order, calling the above state-management + // functions at appropriate points in the traversal. + var type = node[0]; + + // Any code traversed without an active entry junction must be dead, + // as the resulting block could never be entered. Let's remove it. + if (currEntryJunction === null && junctions.length > 0) { + morphNode(node, ['block', []]); + return; + } + + // Traverse each node type according to its particular control-flow semantics. + switch (type) { + case 'defun': + var jEntry = markJunction(); + assert(jEntry === ENTRY_JUNCTION); + var jExit = addJunction(); + assert(jExit === EXIT_JUNCTION); + for (var i = 0; i < node[3].length; i++) { + buildFlowGraph(node[3][i]); + } + joinJunction(jExit); + break; + case 'if': + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + var jEnter = markJunction(); + var jExit = addJunction(); + if (node[2]) { + // Detect and mark "if (label == N) { }". + if (node[1][0] === 'binary' && node[1][1] === '==') { + var lhs = lookThroughCasts(node[1][2]); + if (lhs[0] === 'name' && lhs[1] === 'label') { + addBlockLabel(lookThroughCasts(node[1][3])); + } + } + buildFlowGraph(node[2]); + } + joinJunction(jExit); + setJunction(jEnter); + if (node[3]) { + buildFlowGraph(node[3]); + } + joinJunction(jExit); + break; + case 'conditional': + isInExpr++; + // 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); + } + isInExpr--; + break; + case 'while': + // Special-case "while (1) {}" to use fewer junctions, + // since emscripten generates a lot of these. + if (isTrueNode(node[1])) { + var jLoop = markJunction(); + var jExit = addJunction(); + pushActiveLabels(jLoop, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jLoop); + setJunction(jExit); + } else { + var jCond = markJunction(); + var jLoop = addJunction(); + var jExit = addJunction(); + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + joinJunction(jLoop); + pushActiveLabels(jCond, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jCond); + // An empty basic-block linking condition exit to loop exit. + setJunction(jLoop); + joinJunction(jExit); + } + break; + case 'do': + // Special-case "do {} while (1)" and "do {} while (0)" to use + // fewer junctions, since emscripten generates a lot of these. + if (isFalseNode(node[1])) { + var jExit = addJunction(); + pushActiveLabels(jExit, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jExit); + } else if (isTrueNode(node[1])) { + var jLoop = markJunction(); + var jExit = addJunction(); + pushActiveLabels(jLoop, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jLoop); + setJunction(jExit); + } else { + var jLoop = markJunction(); + var jCond = addJunction(); + var jCondExit = addJunction(); + var jExit = addJunction(); + pushActiveLabels(jCond, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jCond); + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + joinJunction(jCondExit); + joinJunction(jLoop); + setJunction(jCondExit); + joinJunction(jExit) + } + break; + case 'for': + var jTest = addJunction(); + var jBody = addJunction(); + var jStep = addJunction(); + var jExit = addJunction(); + buildFlowGraph(node[1]); + joinJunction(jTest); + isInExpr++; + buildFlowGraph(node[2]); + isInExpr--; + joinJunction(jBody); + pushActiveLabels(jStep, jExit); + buildFlowGraph(node[4]); + popActiveLabels(); + joinJunction(jStep); + buildFlowGraph(node[3]); + joinJunction(jTest); + setJunction(jBody); + joinJunction(jExit); + break; + case 'label': + assert(node[2][0] in BREAK_CAPTURERS, 'label on non-loop, non-switch statement') + nextLoopLabel = node[1]; + buildFlowGraph(node[2]); + break; + case 'switch': + // Emscripten generates switch statements of a very limited + // form: all case clauses are numeric literals, and all + // case bodies end with a (maybe implicit) break. So it's + // basically equivalent to a multi-way 'if' statement. + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + var condition = lookThroughCasts(node[1]); + var jCheckExit = markJunction(); + var jExit = addJunction(); + pushActiveLabels(null, jExit); + var hasDefault = false; + for (var i=0; i 0) { + if (node[2][i][0]) { + markNonLocalJump('return'); + } else { + joinJunction(jExit); + } + } + } + // If there was no default case, we also need an empty block + // linking straight from the test evaluation to the exit. + if (!hasDefault) { + setJunction(jCheckExit); + } + joinJunction(jExit); + popActiveLabels() + break; + case 'return': + if (node[1]) { + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + } + markNonLocalJump(type); + break; + case 'break': + case 'continue': + markNonLocalJump(type, node[1]); + break; + case 'assign': + isInExpr++; + buildFlowGraph(node[3]); + isInExpr--; + if (node[1] === true && node[2][0] === 'name') { + addKillNode(node); + } else { + buildFlowGraph(node[2]); + } + break; + case 'name': + addUseNode(node); + break; + case 'block': + case 'toplevel': + if (node[1]) { + for (var i = 0; i < node[1].length; i++) { + buildFlowGraph(node[1][i]); + } + } + break; + case 'stat': + buildFlowGraph(node[1]); + break; + case 'unary-prefix': + case 'unary-postfix': + isInExpr++; + buildFlowGraph(node[2]); + isInExpr--; + break; + case 'binary': + isInExpr++; + buildFlowGraph(node[2]); + buildFlowGraph(node[3]); + isInExpr--; + break; + case 'call': + isInExpr++; + buildFlowGraph(node[1]); + if (node[2]) { + for (var i = 0; i < node[2].length; i++) { + buildFlowGraph(node[2][i]); + } + } + isInExpr--; + // If the call is statically known to throw, + // treat it as a jump to function exit. + if (!isInExpr && node[1][0] === 'name') { + if (node[1][1] in FUNCTIONS_THAT_ALWAYS_THROW) { + markNonLocalJump('return'); + } + } + break; + case 'seq': + case 'sub': + isInExpr++; + buildFlowGraph(node[1]); + buildFlowGraph(node[2]); + isInExpr--; + break; + case 'dot': + case 'throw': + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + break; + case 'num': + case 'string': + case 'var': + break; + default: + printErr(JSON.stringify(node)); + assert(false, 'unsupported node type: ' + type); + } + } + buildFlowGraph(fun); + + assert(setSize(junctions[ENTRY_JUNCTION].inblocks) === 0, 'function entry must have no incoming blocks'); + assert(setSize(junctions[EXIT_JUNCTION].outblocks) === 0, 'function exit must have no outgoing blocks'); + assert(blocks[ENTRY_BLOCK].entry === ENTRY_JUNCTION, 'block zero must be the initial block'); + + // Fix up implicit jumps done by assigning to the 'label' variable. + // If a block ends with an assignment to 'label' and there's another block + // with that value of 'label' as precondition, we tweak the flow graph so + // that the former jumps straight to the later. + + var labelledBlocks = {}; + var labelledJumps = []; + FINDLABELLEDBLOCKS: + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + // Does it have any labels as preconditions to its entry? + for (var labelVal in block.labels) { + // If there are multiple blocks with the same label, all bets are off. + // This seems to happen sometimes for short blocks that end with a return. + // TODO: it should be safe to merge the duplicates if they're identical. + if (labelVal in labelledBlocks) { + labelledBlocks = {}; + labelledJumps = []; + break FINDLABELLEDBLOCKS; + } + labelledBlocks[labelVal] = block; + } + // Does it assign a specific label value at exit? + if ('label' in block.kill) { + var finalNode = block.nodes[block.nodes.length - 1]; + if (finalNode[0] === 'assign' && finalNode[2][1] === 'label') { + // If labels are computed dynamically then all bets are off. + // This can happen due to indirect branching in llvm output. + if (finalNode[3][0] !== 'num') { + labelledBlocks = {}; + labelledJumps = []; + break FINDLABELLEDBLOCKS; + } + labelledJumps.push([finalNode[3][1], block]); + } else { + // If label is assigned a non-zero value elsewhere in the block + // then all bets are off. This can happen e.g. due to outlining + // saving/restoring label to the stack. + for (var j = 0; j < block.nodes.length - 1; j++) { + if (block.nodes[j][0] === 'assign' && block.nodes[j][2][1] === 'label') { + if (block.nodes[j][3][0] !== 'num' && block.nodes[j][3][1] !== 0) { + labelledBlocks = {}; + labelledJumps = []; + break FINDLABELLEDBLOCKS; + } + } + } + } + } + } + for (var labelVal in labelledBlocks) { + var block = labelledBlocks[labelVal]; + // Disconnect it from the graph, and create a + // new junction for jumps targetting this label. + delete junctions[block.entry].outblocks[block.id]; + block.entry = addJunction(); + junctions[block.entry].outblocks[block.id] = 1; + // Add a fake use of 'label' to keep it alive in predecessor. + block.use['label'] = 1; + block.nodes.unshift(['name', 'label']); + block.isexpr.unshift(1); + } + for (var i = 0; i < labelledJumps.length; i++) { + var labelVal = labelledJumps[i][0]; + var block = labelledJumps[i][1]; + var targetBlock = labelledBlocks[labelVal]; + if (targetBlock) { + // Redirect its exit to entry of the target block. + delete junctions[block.exit].inblocks[block.id]; + block.exit = targetBlock.entry; + junctions[block.exit].inblocks[block.id] = 1; + } + } + labelledBlocks = null; + labelledJumps = null; + + // Do a backwards data-flow analysis to determine the set of live + // variables at each junction, and to use this information to eliminate + // any unused assignments. + // We run two nested phases. The inner phase builds the live set for each + // junction. The outer phase uses this to try to eliminate redundant + // stores in each basic block, which might in turn affect liveness info. + + function analyzeJunction(junc) { + // Update the live set for this junction. + var live = {}; + for (var b in junc.outblocks) { + var block = blocks[b]; + var liveSucc = junctions[block.exit].live || {}; + for (var name in liveSucc) { + if (!(name in block.kill)) { + live[name] = 1; + } + } + for (var name in block.use) { + live[name] = 1; + } + } + junc.live = live; + } + + function analyzeBlock(block) { + // Update information about the behaviour of the block. + // This includes the standard 'use' and 'kill' information, + // plus a 'link' set naming values that flow through from entry + // to exit, possibly changing names via simple 'x=y' assignments. + // As we go, we eliminate assignments if the variable is not + // subsequently used. + var live = copy(junctions[block.exit].live); + var use = {}; + var kill = {}; + var link = {}; + var lastUseLoc = {}; + var firstDeadLoc = {}; + var firstKillLoc = {}; + var lastKillLoc = {}; + for (var name in live) { + link[name] = name; + lastUseLoc[name] = block.nodes.length; + firstDeadLoc[name] = block.nodes.length; + } + for (var j = block.nodes.length - 1; j >=0 ; j--) { + var node = block.nodes[j]; + if (node[0] === 'name') { + var name = node[1]; + live[name] = 1; + use[name] = j; + if (lastUseLoc[name] === undefined) { + lastUseLoc[name] = j; + firstDeadLoc[name] = j; + } + } else { + var name = node[2][1]; + // We only keep assignments if they will be subsequently used. + if (name in live) { + kill[name] = 1; + delete use[name]; + delete live[name]; + firstDeadLoc[name] = j; + firstKillLoc[name] = j; + if (lastUseLoc[name] === undefined) { + lastUseLoc[name] = j; + } + if (lastKillLoc[name] === undefined) { + lastKillLoc[name] = j; + } + // If it's an "x=y" and "y" is not live, then we can create a + // flow-through link from "y" to "x". If not then there's no + // flow-through link for "x". + var oldLink = link[name]; + if (oldLink) { + delete link[name]; + if (node[3][0] === 'name') { + if (node[3][1] in localVars) { + link[node[3][1]] = oldLink; + } + } + } + } else { + // The result of this assignment is never used, so delete it. + // We may need to keep the RHS for its value or its side-effects. + function removeUnusedNodes(j, n) { + for (var name in lastUseLoc) { + lastUseLoc[name] -= n; + } + for (var name in firstKillLoc) { + firstKillLoc[name] -= n; + } + for (var name in lastKillLoc) { + lastKillLoc[name] -= n; + } + for (var name in firstDeadLoc) { + firstDeadLoc[name] -= n; + } + block.nodes.splice(j, n); + block.isexpr.splice(j, n); + } + if (block.isexpr[j] || hasSideEffects(node[3])) { + morphNode(node, node[3]); + removeUnusedNodes(j, 1); + } else { + var numUsesInExpr = 0; + traverse(node[3], function(node, type) { + if (type === 'name' && node[1] in localVars) { + numUsesInExpr++; + } + }); + morphNode(node, ['block', []]); + j = j - numUsesInExpr; + removeUnusedNodes(j, 1 + numUsesInExpr); + } + } + } + } + block.use = use; + block.kill = kill; + block.link = link; + block.lastUseLoc = lastUseLoc; + block.firstDeadLoc = firstDeadLoc; + block.firstKillLoc = firstKillLoc; + block.lastKillLoc = lastKillLoc; + } + + var jWorklistMap = { EXIT_JUNCTION: 1 }; + var jWorklist = [EXIT_JUNCTION]; + var bWorklistMap = {}; + var bWorklist = []; + + // Be sure to visit every junction at least once. + // This avoids missing some vars because we disconnected them + // when processing the labelled jumps. + for (var i = junctions.length - 1; i >= EXIT_JUNCTION; i--) { + jWorklistMap[i] = 1; + jWorklist.push(i); + } + + while (jWorklist.length > 0) { + // Iterate on just the junctions until we get stable live sets. + // The first run of this loop will grow the live sets to their maximal size. + // Subsequent runs will shrink them based on eliminated in-block uses. + while (jWorklist.length > 0) { + var junc = junctions[jWorklist.pop()]; + delete jWorklistMap[junc.id]; + var oldLive = junc.live || null; + analyzeJunction(junc); + if (!sortedJsonCompare(oldLive, junc.live)) { + // Live set changed, updated predecessor blocks and junctions. + for (var b in junc.inblocks) { + if (!(b in bWorklistMap)) { + bWorklistMap[b] = 1; + bWorklist.push(b); + } + var jPred = blocks[b].entry; + if (!(jPred in jWorklistMap)) { + jWorklistMap[jPred] = 1; + jWorklist.push(jPred); + } + } + } + } + // Now update the blocks based on the calculated live sets. + while (bWorklist.length > 0) { + var block = blocks[bWorklist.pop()]; + delete bWorklistMap[block.id]; + var oldUse = block.use; + analyzeBlock(block); + if (!sortedJsonCompare(oldUse, block.use)) { + // The use set changed, re-process the entry junction. + if (!(block.entry in jWorklistMap)) { + jWorklistMap[block.entry] = 1; + jWorklist.push(block.entry); + } + } + } + } + + // Insist that all function parameters are alive at function entry. + // This ensures they will be assigned independent registers, even + // if they happen to be unused. + + for (var name in asmData.params) { + junctions[ENTRY_JUNCTION].live[name] = 1; + } + + // For variables that are live at one or more junctions, we assign them + // a consistent register for the entire scope of the function. Find pairs + // of variable that cannot use the same register (the "conflicts") as well + // as pairs of variables that we'd like to have share the same register + // (the "links"). + + var junctionVariables = {}; + + function initializeJunctionVariable(name) { + junctionVariables[name] = { conf: {}, link: {}, excl: {}, reg: null }; + } + + for (var i = 0; i < junctions.length; i++) { + var junc = junctions[i]; + for (var name in junc.live) { + if (!junctionVariables[name]) initializeJunctionVariable(name); + // It conflicts with all other names live at this junction. + for (var otherName in junc.live) { + if (otherName == name) continue; + junctionVariables[name].conf[otherName] = 1; + } + for (var b in junc.outblocks) { + // It conflits with any output vars of successor blocks, + // if they're assigned before it goes dead in that block. + block = blocks[b]; + var jSucc = junctions[block.exit]; + for (var otherName in jSucc.live) { + if (junc.live[otherName]) continue; + if (block.lastKillLoc[otherName] < block.firstDeadLoc[name]) { + if (!junctionVariables[otherName]) initializeJunctionVariable(otherName); + junctionVariables[name].conf[otherName] = 1; + junctionVariables[otherName].conf[name] = 1; + } + } + // It links with any linkages in the outgoing blocks. + var linkName = block.link[name]; + if (linkName && linkName !== name) { + if (!junctionVariables[linkName]) initializeJunctionVariable(linkName); + junctionVariables[name].link[linkName] = 1; + junctionVariables[linkName].link[name] = 1; + } + } + } + } + + // Attempt to sort the junction variables to heuristically reduce conflicts. + // Simple starting point: handle the most-conflicted variables first. + // This seems to work pretty well. + + var sortedJunctionVariables = keys(junctionVariables); + sortedJunctionVariables.sort(function(name1, name2) { + var jv1 = junctionVariables[name1]; + var jv2 = junctionVariables[name2]; + if (jv1.numConfs === undefined) { + jv1.numConfs = setSize(jv1.conf); + } + if (jv2.numConfs === undefined) { + jv2.numConfs = setSize(jv2.conf); + } + return jv2.numConfs - jv1.numConfs; + }); + + // We can now assign a register to each junction variable. + // Process them in order, trying available registers until we find + // one that works, and propagating the choice to linked/conflicted + // variables as we go. + + function tryAssignRegister(name, reg) { + // Try to assign the given register to the given variable, + // and propagate that choice throughout the graph. + // Returns true if successful, false if there was a conflict. + var jv = junctionVariables[name]; + if (jv.reg !== null) { + return jv.reg === reg; + } + if (jv.excl[reg]) { + return false; + } + jv.reg = reg; + // Exclude use of this register at all conflicting variables. + for (var confName in jv.conf) { + junctionVariables[confName].excl[reg] = 1; + } + // Try to propagate it into linked variables. + // It's not an error if we can't. + for (var linkName in jv.link) { + tryAssignRegister(linkName, reg); + } + return true; + } + + NEXTVARIABLE: + for (var i = 0; i < sortedJunctionVariables.length; i++) { + var name = sortedJunctionVariables[i]; + // It may already be assigned due to linked-variable propagation. + if (junctionVariables[name].reg !== null) { + continue NEXTVARIABLE; + } + // Try to use existing registers first. + var allRegs = allRegsByType[localVars[name]]; + for (var reg in allRegs) { + if (tryAssignRegister(name, reg)) { + continue NEXTVARIABLE; + } + } + // They're all taken, create a new one. + tryAssignRegister(name, createReg(name)); + } + + // Each basic block can now be processed in turn. + // There may be internal-use-only variables that still need a register + // assigned, but they can be treated just for this block. We know + // that all inter-block variables are in a good state thanks to + // junction variable consistency. + + for (var i = 0; i < blocks.length; i++) { + var block = blocks[i]; + if (block.nodes.length === 0) continue; + var jEnter = junctions[block.entry]; + var jExit = junctions[block.exit]; + // Mark the point at which each input reg becomes dead. + // Variables alive before this point must not be assigned + // to that register. + var inputVars = {} + var inputDeadLoc = {}; + var inputVarsByReg = {}; + for (var name in jExit.live) { + if (!(name in block.kill)) { + inputVars[name] = 1; + var reg = junctionVariables[name].reg; + assert(reg !== null, 'input variable doesnt have a register'); + inputDeadLoc[reg] = block.firstDeadLoc[name]; + inputVarsByReg[reg] = name; + } + } + for (var name in block.use) { + if (!(name in inputVars)) { + inputVars[name] = 1; + var reg = junctionVariables[name].reg; + assert(reg !== null, 'input variable doesnt have a register'); + inputDeadLoc[reg] = block.firstDeadLoc[name]; + inputVarsByReg[reg] = name; + } + } + assert(setSize(setSub(inputVars, jEnter.live)) == 0); + // Scan through backwards, allocating registers on demand. + // Be careful to avoid conflicts with the input registers. + // We consume free registers in last-used order, which helps to + // eliminate "x=y" assignments that are the last use of "y". + var assignedRegs = {}; + var freeRegsByType = copy(allRegsByType); + // Begin with all live vars assigned per the exit junction. + for (var name in jExit.live) { + var reg = junctionVariables[name].reg; + assert(reg !== null, 'output variable doesnt have a register'); + assignedRegs[name] = reg; + delete freeRegsByType[localVars[name]][reg]; + } + for (var j = 0; j < freeRegsByType.length; j++) { + freeRegsByType[j] = keys(freeRegsByType[j]); + } + // Scan through the nodes in sequence, modifying each node in-place + // and grabbing/freeing registers as needed. + var maybeRemoveNodes = []; + for (var j = block.nodes.length - 1; j >= 0; j--) { + var node = block.nodes[j]; + var name = node[0] === 'assign' ? node[2][1] : node[1]; + var allRegs = allRegsByType[localVars[name]]; + var freeRegs = freeRegsByType[localVars[name]]; + var reg = assignedRegs[name]; + if (node[0] === 'name') { + // A use. Grab a register if it doesn't have one. + if (!reg) { + if (name in inputVars && j <= block.firstDeadLoc[name]) { + // Assignment to an input variable, must use pre-assigned reg. + reg = junctionVariables[name].reg; + assignedRegs[name] = reg; + for (var k = freeRegs.length - 1; k >= 0; k--) { + if (freeRegs[k] === reg) { + freeRegs.splice(k, 1); + break; + } + } + } else { + // Try to use one of the existing free registers. + // It must not conflict with an input register. + for (var k = freeRegs.length - 1; k >= 0; k--) { + reg = freeRegs[k]; + // Check for conflict with input registers. + if (block.firstKillLoc[name] <= inputDeadLoc[reg]) { + if (name !== inputVarsByReg[reg]) { + continue; + } + } + // Found one! + assignedRegs[name] = reg; + freeRegs.splice(k, 1); + break; + } + // If we didn't find a suitable register, create a new one. + if (!assignedRegs[name]) { + reg = createReg(name); + assignedRegs[name] = reg; + } + } + } + node[1] = allRegs[reg]; + } else { + // A kill. This frees the assigned register. + assert(reg, 'live variable doesnt have a reg?') + node[2][1] = allRegs[reg]; + freeRegs.push(reg); + delete assignedRegs[name]; + if (node[3][0] === 'name' && node[3][1] in localVars) { + maybeRemoveNodes.push([j, node]); + } + } + } + // If we managed to create an "x=x" assignments, remove them. + for (var j = 0; j < maybeRemoveNodes.length; j++) { + var node = maybeRemoveNodes[j][1]; + if (node[2][1] === node[3][1]) { + if (block.isexpr[maybeRemoveNodes[j][0]]) { + morphNode(node, node[2]); + } else { + morphNode(node, ['block', []]); + } + } + } + } + + // Assign registers to function params based on entry junction + + var paramRegs = {} + if (fun[2]) { + for (var i = 0; i < fun[2].length; i++) { + var allRegs = allRegsByType[localVars[fun[2][i]]]; + fun[2][i] = allRegs[junctionVariables[fun[2][i]].reg]; + paramRegs[fun[2][i]] = 1; + } + } + + // That's it! + // Re-construct the function with appropriate variable definitions. + + var finalAsmData = { + params: {}, + vars: {}, + inlines: asmData.inlines, + ret: asmData.ret, + }; + for (var i = 1; i < nextReg; i++) { + var reg; + for (var type=0; type Date: Wed, 10 Dec 2014 14:22:13 -0800 Subject: [PATCH 07/31] more work on registerizeHarder --- tools/optimizer/optimizer.cpp | 274 ++++++++++++++++++---------------- 1 file changed, 149 insertions(+), 125 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index 0ed2e13245f68..8961e29097cea 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -2473,7 +2473,7 @@ void registerizeHarder(Ref ast) { // Do not try to process non-validating methods, like the heap replacer bool abort = false; traverse(fun, function(node, type) { - if (type === NEW) abort = true; + if (type == NEW) abort = true; }); if (abort) return; @@ -2506,145 +2506,169 @@ void registerizeHarder(Ref ast) { // * a single entry junction // * a single exit junction // * a 'use' and 'kill' set of names for the block - // * full sequence of 'name' and 'assign' nodes in the block + // * full sequence of NAME and ASSIGN nodes in the block // * whether each such node appears as part of a larger expression // (and therefore cannot be safely eliminated) // * set of labels that can be used to jump to this block - var junctions = []; - var blocks = []; - var currEntryJunction = null; - var nextBasicBlock = null; - var isInExpr = 0; - var activeLabels = [{}]; - var nextLoopLabel = null; + struct Junction { + int id; + std::unordered_set inblocks, outblocks; + Junction(int id_) : id(id_) {} + }; + struct Node { + }; + struct Block { + int id, entry, exit; + StringSet labels; + std::vector nodes; + std::vector isexpr; + StringSet use; + StringSet kill; + Block() : id(-1), entry(-1), exit(-1) {} + }; + struct ContinueBreak { + int co, br; + BreakContinue(int co_, int br_) : co(co_), br(br_) {} + }; + typedef std::unordered_map LabelState; - var ENTRY_JUNCTION = 0; - var EXIT_JUNCTION = 1; - var ENTRY_BLOCK = 0; + std::vector junctions; + std::vector blocks; + int currEntryJunction = -1; + Block* nextBasicBlock = nullptr; + bool isInExpr = 0; + std::vector activeLabels; + IString nextLoopLabel; - function addJunction() { + const int ENTRY_JUNCTION = 0; + const int EXIT_JUNCTION = 1; + const int ENTRY_BLOCK = 0; + + auto addJunction = [&]() { // Create a new junction, without inserting it into the graph. // This is useful for e.g. pre-allocating an exit node. - var id = junctions.length; - junctions[id] = {id: id, inblocks: {}, outblocks: {}}; + int id = junctions.size(); + junctions.push_back(Junction(id)); return id; - } + }; - function markJunction(id) { + auto markJunction = [&](int id=-1) { // Mark current traversal location as a junction. // This makes a new basic block exiting at this position. - if (id === undefined || id === null) { + if (id < 0) { id = addJunction(); } joinJunction(id, true); return id; - } + }; - function setJunction(id, force) { + auto setJunction = [&](int id, bool force) { // Set the next entry junction to the given id. // This can be used to enter at a previously-declared point. // You can't return to a junction with no incoming blocks // unless the 'force' parameter is specified. - assert(nextBasicBlock.nodes.length === 0, 'refusing to abandon an in-progress basic block') - if (force || setSize(junctions[id].inblocks) > 0) { + assert(nextBasicBlock.nodes.size() == 0); // refusing to abandon an in-progress basic block + if (force || junctions[id].inblocks.size() > 0) { currEntryJunction = id; } else { - currEntryJunction = null; + currEntryJunction = -1; } - } + }; - function joinJunction(id, force) { + auto joinJunction = [&](int id, bool force) { // Complete the pending basic block by exiting at this position. // This can be used to exit at a previously-declared point. - if (currEntryJunction !== null) { - nextBasicBlock.id = blocks.length; - nextBasicBlock.entry = currEntryJunction; - nextBasicBlock.exit = id; - junctions[currEntryJunction].outblocks[nextBasicBlock.id] = 1; - junctions[id].inblocks[nextBasicBlock.id] = 1; - blocks.push(nextBasicBlock); + if (currEntryJunction >= 0) { + assert(nextBasicBlock); + nextBasicBlock->id = blocks.size(); + nextBasicBlock->entry = currEntryJunction; + nextBasicBlock->exit = id; + junctions[currEntryJunction].outblocks.insert(nextBasicBlock.id); + junctions[id].inblocks.insert(nextBasicBlock.id); + blocks.push_back(nextBasicBlock); } - nextBasicBlock = { id: null, entry: null, exit: null, labels: {}, nodes: [], isexpr: [], use: {}, kill: {} }; + nextBasicBlock = new Block(); setJunction(id, force); return id; - } + }; - function pushActiveLabels(onContinue, onBreak) { + IString NULL_STR; + + auto pushActiveLabels = [&](int onContinue, int onBreak) { // Push the target junctions for continuing/breaking a loop. // This should be called before traversing into a loop. - 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, + LabelState& prevLabels = activeLabels.back(); + LabelState newLabels = prevLabels; + newLabels[NULL_STR] = ContinueBreak(onContinue, onBreak); + if (!!nextLoopLabel) { + newLabels[nextLoopLabel] = ContinueBreak(onContinue, onBreak); + nextLoopLabel = NULL_STR; + } + // An unlabelled CONTINUE should jump to innermost loop, // ignoring any nested 'switch' statements. - if (onContinue === null && prevLabels[null]) { - newLabels[null][0] = prevLabels[null][0]; + if (onContinue < 0 && prevLabels.count(NULL_STR) > 0) { + newLabels[NULL_STR].co = prevLabels[NULL_STR].co; } - activeLabels.push(newLabels); - } + activeLabels.push_back(newLabels); + }; - function popActiveLabels() { + auto popActiveLabels = [&]() { // Pop the target junctions for continuing/breaking a loop. // This should be called after traversing into a loop. - activeLabels.pop(); - } + activeLabels.pop_back(); + }; - function markNonLocalJump(type, label) { - // Complete a block via 'return', 'break' or 'continue'. + auto markNonLocalJump = [&](AsmType type, IString label) { + // Complete a block via RETURN, BREAK or CONTINUE. // This joins the targetted junction and then sets the current junction to null. // Any code traversed before we get back an existing junction is dead code. - if (type === 'return') { + if (type == RETURN) { joinJunction(EXIT_JUNCTION); } else { - label = label ? label : null; - var targets = activeLabels[activeLabels.length-1][label]; - assert(targets, 'jump to unknown label'); - if (type === 'continue') { - joinJunction(targets[0]); - } else if (type === 'break') { - joinJunction(targets[1]); + assert(activeLabels.back().count(label) > 0); // 'jump to unknown label'); + auto targets = activeLabels.back()[label]; + if (type == CONTINUE) { + joinJunction(targets.co); + } else if (type == BREAK) { + joinJunction(targets.br); } else { - assert(false, 'unknown jump node type'); + assert(0); // 'unknown jump node type'); } } - currEntryJunction = null; - } + currEntryJunction = nullptr; + }; - function addUseNode(node) { + auto addUseNode = [&](Ref node) { // Mark a use of the given name node in the current basic block. - assert(node[0] === 'name', 'not a use node'); - var name = node[1]; - if (name in localVars) { - nextBasicBlock.nodes.push(node); - nextBasicBlock.isexpr.push(isInExpr); - if (!nextBasicBlock.kill[name]) { - nextBasicBlock.use[name] = 1; + assert(node[0] == NAME); // 'not a use node'); + Ref name = node[1]; + if (asmData.isLocal(name)) { + nextBasicBlock->nodes.push_back(node); + nextBasicBlock->isexpr.push_back(isInExpr); + if (nextBasicBlock->kill.count(name) == 0) { + nextBasicBlock->use.insert(name); } } - } + }; - function addKillNode(node) { + auto addKillNode = [&](Ref node) { // Mark an assignment to the given name node in the current basic block. - assert(node[0] === 'assign', 'not a kill node'); - assert(node[1] === true, 'not a kill node'); - assert(node[2][0] === 'name', 'not a kill node'); - var name = node[2][1]; - if (name in localVars) { - nextBasicBlock.nodes.push(node); - nextBasicBlock.isexpr.push(isInExpr); - nextBasicBlock.kill[name] = 1; + assert(node[0] == ASSIGN); //, 'not a kill node'); + assert(node[1]->isBool(true)); // 'not a kill node'); + assert(node[2][0] == NAME); //, 'not a kill node'); + Ref name = node[2][1]; + if (asmData.isLocal(name)) { + nextBasicBlock->nodes.push_back(node); + nextBasicBlock->isexpr.push_back(isInExpr); + nextBasicBlock->kill.insert(name); } - } + }; function lookThroughCasts(node) { // Look through value-preserving casts, like "x | 0" => "x" - if (node[0] === 'binary' && node[1] === '|') { - if (node[3][0] === 'num' && node[3][1] === 0) { + if (node[0] == 'binary' && node[1] == '|') { + if (node[3][0] == 'num' && node[3][1] == 0) { return lookThroughCasts(node[2]); } } @@ -2652,20 +2676,20 @@ void registerizeHarder(Ref ast) { } function addBlockLabel(node) { - assert(nextBasicBlock.nodes.length === 0, 'cant add label to an in-progress basic block') - if (node[0] === 'num') { + assert(nextBasicBlock.nodes.length == 0, 'cant add label to an in-progress basic block') + if (node[0] == 'num') { nextBasicBlock.labels[node[1]] = 1; } } function isTrueNode(node) { // Check if the given node is statically truthy. - return (node[0] === 'num' && node[1] != 0); + return (node[0] == 'num' && node[1] != 0); } function isFalseNode(node) { // Check if the given node is statically falsy. - return (node[0] === 'num' && node[1] == 0); + return (node[0] == 'num' && node[1] == 0); } function morphNode(node, newNode) { @@ -2692,7 +2716,7 @@ void registerizeHarder(Ref ast) { // Any code traversed without an active entry junction must be dead, // as the resulting block could never be entered. Let's remove it. - if (currEntryJunction === null && junctions.length > 0) { + if (currEntryJunction == null && junctions.length > 0) { morphNode(node, ['block', []]); return; } @@ -2701,9 +2725,9 @@ void registerizeHarder(Ref ast) { switch (type) { case 'defun': var jEntry = markJunction(); - assert(jEntry === ENTRY_JUNCTION); + assert(jEntry == ENTRY_JUNCTION); var jExit = addJunction(); - assert(jExit === EXIT_JUNCTION); + assert(jExit == EXIT_JUNCTION); for (var i = 0; i < node[3].length; i++) { buildFlowGraph(node[3][i]); } @@ -2717,9 +2741,9 @@ void registerizeHarder(Ref ast) { var jExit = addJunction(); if (node[2]) { // Detect and mark "if (label == N) { }". - if (node[1][0] === 'binary' && node[1][1] === '==') { + if (node[1][0] == 'binary' && node[1][1] == '==') { var lhs = lookThroughCasts(node[1][2]); - if (lhs[0] === 'name' && lhs[1] === 'label') { + if (lhs[0] == NAME && lhs[1] == 'label') { addBlockLabel(lookThroughCasts(node[1][3])); } } @@ -2868,7 +2892,7 @@ void registerizeHarder(Ref ast) { hasDefault = true; } else { // Detect switches dispatching to labelled blocks. - if (condition[0] === 'name' && condition[1] === 'label') { + if (condition[0] == NAME && condition[1] == 'label') { addBlockLabel(lookThroughCasts(node[2][i][0])); } } @@ -2879,7 +2903,7 @@ void registerizeHarder(Ref ast) { // If there's live code here, assume it jumps to case exit. if (currEntryJunction !== null && nextBasicBlock.nodes.length > 0) { if (node[2][i][0]) { - markNonLocalJump('return'); + markNonLocalJump(RETURN); } else { joinJunction(jExit); } @@ -2893,7 +2917,7 @@ void registerizeHarder(Ref ast) { joinJunction(jExit); popActiveLabels() break; - case 'return': + case RETURN: if (node[1]) { isInExpr++; buildFlowGraph(node[1]); @@ -2901,21 +2925,21 @@ void registerizeHarder(Ref ast) { } markNonLocalJump(type); break; - case 'break': - case 'continue': + case BREAK: + case CONTINUE: markNonLocalJump(type, node[1]); break; - case 'assign': + case ASSIGN: isInExpr++; buildFlowGraph(node[3]); isInExpr--; - if (node[1] === true && node[2][0] === 'name') { + if (node[1] == true && node[2][0] == NAME) { addKillNode(node); } else { buildFlowGraph(node[2]); } break; - case 'name': + case NAME: addUseNode(node); break; case 'block': @@ -2952,9 +2976,9 @@ void registerizeHarder(Ref ast) { isInExpr--; // If the call is statically known to throw, // treat it as a jump to function exit. - if (!isInExpr && node[1][0] === 'name') { + if (!isInExpr && node[1][0] == NAME) { if (node[1][1] in FUNCTIONS_THAT_ALWAYS_THROW) { - markNonLocalJump('return'); + markNonLocalJump(RETURN); } } break; @@ -2982,9 +3006,9 @@ void registerizeHarder(Ref ast) { } buildFlowGraph(fun); - assert(setSize(junctions[ENTRY_JUNCTION].inblocks) === 0, 'function entry must have no incoming blocks'); - assert(setSize(junctions[EXIT_JUNCTION].outblocks) === 0, 'function exit must have no outgoing blocks'); - assert(blocks[ENTRY_BLOCK].entry === ENTRY_JUNCTION, 'block zero must be the initial block'); + assert(setSize(junctions[ENTRY_JUNCTION].inblocks) == 0, 'function entry must have no incoming blocks'); + assert(setSize(junctions[EXIT_JUNCTION].outblocks) == 0, 'function exit must have no outgoing blocks'); + assert(blocks[ENTRY_BLOCK].entry == ENTRY_JUNCTION, 'block zero must be the initial block'); // Fix up implicit jumps done by assigning to the 'label' variable. // If a block ends with an assignment to 'label' and there's another block @@ -3011,7 +3035,7 @@ void registerizeHarder(Ref ast) { // Does it assign a specific label value at exit? if ('label' in block.kill) { var finalNode = block.nodes[block.nodes.length - 1]; - if (finalNode[0] === 'assign' && finalNode[2][1] === 'label') { + if (finalNode[0] == ASSIGN && finalNode[2][1] == 'label') { // If labels are computed dynamically then all bets are off. // This can happen due to indirect branching in llvm output. if (finalNode[3][0] !== 'num') { @@ -3025,7 +3049,7 @@ void registerizeHarder(Ref ast) { // then all bets are off. This can happen e.g. due to outlining // saving/restoring label to the stack. for (var j = 0; j < block.nodes.length - 1; j++) { - if (block.nodes[j][0] === 'assign' && block.nodes[j][2][1] === 'label') { + if (block.nodes[j][0] == ASSIGN && block.nodes[j][2][1] == 'label') { if (block.nodes[j][3][0] !== 'num' && block.nodes[j][3][1] !== 0) { labelledBlocks = {}; labelledJumps = []; @@ -3045,7 +3069,7 @@ void registerizeHarder(Ref ast) { junctions[block.entry].outblocks[block.id] = 1; // Add a fake use of 'label' to keep it alive in predecessor. block.use['label'] = 1; - block.nodes.unshift(['name', 'label']); + block.nodes.unshift([NAME, 'label']); block.isexpr.unshift(1); } for (var i = 0; i < labelledJumps.length; i++) { @@ -3109,11 +3133,11 @@ void registerizeHarder(Ref ast) { } for (var j = block.nodes.length - 1; j >=0 ; j--) { var node = block.nodes[j]; - if (node[0] === 'name') { + if (node[0] == NAME) { var name = node[1]; live[name] = 1; use[name] = j; - if (lastUseLoc[name] === undefined) { + if (lastUseLoc[name] == undefined) { lastUseLoc[name] = j; firstDeadLoc[name] = j; } @@ -3126,10 +3150,10 @@ void registerizeHarder(Ref ast) { delete live[name]; firstDeadLoc[name] = j; firstKillLoc[name] = j; - if (lastUseLoc[name] === undefined) { + if (lastUseLoc[name] == undefined) { lastUseLoc[name] = j; } - if (lastKillLoc[name] === undefined) { + if (lastKillLoc[name] == undefined) { lastKillLoc[name] = j; } // If it's an "x=y" and "y" is not live, then we can create a @@ -3138,7 +3162,7 @@ void registerizeHarder(Ref ast) { var oldLink = link[name]; if (oldLink) { delete link[name]; - if (node[3][0] === 'name') { + if (node[3][0] == NAME) { if (node[3][1] in localVars) { link[node[3][1]] = oldLink; } @@ -3169,7 +3193,7 @@ void registerizeHarder(Ref ast) { } else { var numUsesInExpr = 0; traverse(node[3], function(node, type) { - if (type === 'name' && node[1] in localVars) { + if (type == NAME && node[1] in localVars) { numUsesInExpr++; } }); @@ -3303,10 +3327,10 @@ void registerizeHarder(Ref ast) { sortedJunctionVariables.sort(function(name1, name2) { var jv1 = junctionVariables[name1]; var jv2 = junctionVariables[name2]; - if (jv1.numConfs === undefined) { + if (jv1.numConfs == undefined) { jv1.numConfs = setSize(jv1.conf); } - if (jv2.numConfs === undefined) { + if (jv2.numConfs == undefined) { jv2.numConfs = setSize(jv2.conf); } return jv2.numConfs - jv1.numConfs; @@ -3323,7 +3347,7 @@ void registerizeHarder(Ref ast) { // Returns true if successful, false if there was a conflict. var jv = junctionVariables[name]; if (jv.reg !== null) { - return jv.reg === reg; + return jv.reg == reg; } if (jv.excl[reg]) { return false; @@ -3367,7 +3391,7 @@ void registerizeHarder(Ref ast) { for (var i = 0; i < blocks.length; i++) { var block = blocks[i]; - if (block.nodes.length === 0) continue; + if (block.nodes.length == 0) continue; var jEnter = junctions[block.entry]; var jExit = junctions[block.exit]; // Mark the point at which each input reg becomes dead. @@ -3416,11 +3440,11 @@ void registerizeHarder(Ref ast) { var maybeRemoveNodes = []; for (var j = block.nodes.length - 1; j >= 0; j--) { var node = block.nodes[j]; - var name = node[0] === 'assign' ? node[2][1] : node[1]; + var name = node[0] == ASSIGN ? node[2][1] : node[1]; var allRegs = allRegsByType[localVars[name]]; var freeRegs = freeRegsByType[localVars[name]]; var reg = assignedRegs[name]; - if (node[0] === 'name') { + if (node[0] == NAME) { // A use. Grab a register if it doesn't have one. if (!reg) { if (name in inputVars && j <= block.firstDeadLoc[name]) { @@ -3428,7 +3452,7 @@ void registerizeHarder(Ref ast) { reg = junctionVariables[name].reg; assignedRegs[name] = reg; for (var k = freeRegs.length - 1; k >= 0; k--) { - if (freeRegs[k] === reg) { + if (freeRegs[k] == reg) { freeRegs.splice(k, 1); break; } @@ -3463,7 +3487,7 @@ void registerizeHarder(Ref ast) { node[2][1] = allRegs[reg]; freeRegs.push(reg); delete assignedRegs[name]; - if (node[3][0] === 'name' && node[3][1] in localVars) { + if (node[3][0] == NAME && node[3][1] in localVars) { maybeRemoveNodes.push([j, node]); } } @@ -3471,7 +3495,7 @@ void registerizeHarder(Ref ast) { // If we managed to create an "x=x" assignments, remove them. for (var j = 0; j < maybeRemoveNodes.length; j++) { var node = maybeRemoveNodes[j][1]; - if (node[2][1] === node[3][1]) { + if (node[2][1] == node[3][1]) { if (block.isexpr[maybeRemoveNodes[j][0]]) { morphNode(node, node[2]); } else { @@ -3519,7 +3543,7 @@ void registerizeHarder(Ref ast) { }); } -*/ +*/ // end registerizeHarder // minified names generation StringSet RESERVED("do if in for new try var env let"); From 9be21543640684d1a9df99bc8ccc17ade623bf42 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 14:56:08 -0800 Subject: [PATCH 08/31] more registerizeHarder work --- tools/optimizer/optimizer.cpp | 576 ++++++++++++++++------------------ 1 file changed, 268 insertions(+), 308 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index 8961e29097cea..7669f1d4afd98 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -765,6 +765,8 @@ StringSet ASSOCIATIVE_BINARIES("+ * | & ^"), CONDITION_CHECKERS("if do while switch"), SAFE_TO_DROP_COERCION("unary-prefix name num"); +StringSet BREAK_CAPTURERS("do while for switch"), + FUNCTIONS_THAT_ALWAYS_THROW("abort ___resumeException ___cxa_throw ___cxa_rethrow"); bool isFunctionTable(const char *name) { static const char *functionTable = "FUNCTION_TABLE"; @@ -1351,7 +1353,7 @@ void eliminate(Ref ast, bool memSafe=false) { node[1] = node[1].filter(function(pair) { return !varsToRemove[pair[0]] }); if (node[1]->size() == 0) { // wipe out an empty |var;| - node[0] = 'toplevel'; + node[0] = TOPLEVEL; node[1] = []; } } else */ @@ -2606,7 +2608,7 @@ void registerizeHarder(Ref ast) { nextLoopLabel = NULL_STR; } // An unlabelled CONTINUE should jump to innermost loop, - // ignoring any nested 'switch' statements. + // ignoring any nested SWITCH statements. if (onContinue < 0 && prevLabels.count(NULL_STR) > 0) { newLabels[NULL_STR].co = prevLabels[NULL_STR].co; } @@ -2636,7 +2638,7 @@ void registerizeHarder(Ref ast) { assert(0); // 'unknown jump node type'); } } - currEntryJunction = nullptr; + currEntryJunction = -1; }; auto addUseNode = [&](Ref node) { @@ -2665,354 +2667,312 @@ void registerizeHarder(Ref ast) { } }; - function lookThroughCasts(node) { + auto lookThroughCasts = [&](Ref node) { // Look through value-preserving casts, like "x | 0" => "x" - if (node[0] == 'binary' && node[1] == '|') { - if (node[3][0] == 'num' && node[3][1] == 0) { + if (node[0] == BINARY && node[1] == OR) { + if (node[3][0] == NUM && node[3][1] == 0) { return lookThroughCasts(node[2]); } } return node; - } + }; - function addBlockLabel(node) { - assert(nextBasicBlock.nodes.length == 0, 'cant add label to an in-progress basic block') - if (node[0] == 'num') { - nextBasicBlock.labels[node[1]] = 1; + auto addBlockLabel = [&](Ref node) { + assert(nextBasicBlock->nodes.size() == 0); // 'cant add label to an in-progress basic block') + if (node[0] == NUM) { + nextBasicBlock->labels.insert(node[1]); // XXX? } - } + }; - function isTrueNode(node) { + auto isTrueNode = [&](Ref node) { // Check if the given node is statically truthy. - return (node[0] == 'num' && node[1] != 0); - } + return (node[0] == NUM && node[1]->getNumber() != 0); + }; - function isFalseNode(node) { + auto isFalseNode = [&](Ref node) { // Check if the given node is statically falsy. - return (node[0] == 'num' && node[1] == 0); - } - - function morphNode(node, newNode) { - // In-place morph a node into some other type of node. - var i = 0; - while (i < node.length && i < newNode.length) { - node[i] = newNode[i]; - i++; - } - while (i < newNode.length) { - node.push(newNode[i]); - i++; - } - if (node.length > newNode.length) { - node.length = newNode.length; - } - } + return (node[0] == NUM && node[1]->getNumber() == 0); + }; - function buildFlowGraph(node) { + std::function buildFlowGraph = [&](Ref node) { // Recursive function to build up the flow-graph. // It walks the tree in execution order, calling the above state-management // functions at appropriate points in the traversal. - var type = node[0]; + Ref type = node[0]; // Any code traversed without an active entry junction must be dead, // as the resulting block could never be entered. Let's remove it. - if (currEntryJunction == null && junctions.length > 0) { - morphNode(node, ['block', []]); + if (currEntryJunction < 0 && junctions.size() > 0) { + safeCopy(node, makeBlock()); return; } // Traverse each node type according to its particular control-flow semantics. - switch (type) { - case 'defun': - var jEntry = markJunction(); - assert(jEntry == ENTRY_JUNCTION); - var jExit = addJunction(); - assert(jExit == EXIT_JUNCTION); - for (var i = 0; i < node[3].length; i++) { - buildFlowGraph(node[3][i]); + // TODO: switchify this + if (type == DEFUN) { + var jEntry = markJunction(); + assert(jEntry == ENTRY_JUNCTION); + int jExit = addJunction(); + assert(jExit == EXIT_JUNCTION); + for (var i = 0; i < node[3].length; i++) { + buildFlowGraph(node[3][i]); + } + joinJunction(jExit); + } else if (type == IF) { + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + int jEnter = markJunction(); + int jExit = addJunction(); + if (!!node[2]) { + // Detect and mark "if (label == N) { }". + if (node[1][0] == BINARY && node[1][1] == EQ) { + Ref lhs = lookThroughCasts(node[1][2]); + if (lhs[0] == NAME && lhs[1] == LABEL) { + addBlockLabel(lookThroughCasts(node[1][3])); + } } - joinJunction(jExit); - break; - case 'if': - isInExpr++; + buildFlowGraph(node[2]); + } + joinJunction(jExit); + setJunction(jEnter); + if (!!node[3]) { + buildFlowGraph(node[3]); + } + joinJunction(jExit); + } else if (type == CONDITIONAL) { + isInExpr++; + // 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]); - isInExpr--; - var jEnter = markJunction(); - var jExit = addJunction(); - if (node[2]) { - // Detect and mark "if (label == N) { }". - if (node[1][0] == 'binary' && node[1][1] == '==') { - var lhs = lookThroughCasts(node[1][2]); - if (lhs[0] == NAME && lhs[1] == 'label') { - addBlockLabel(lookThroughCasts(node[1][3])); - } - } + if (!!node[2]) { + buildFlowGraph(node[2]); + } + if (!!node[3]) { + buildFlowGraph(node[3]); + } + } else { + buildFlowGraph(node[1]); + int jEnter = markJunction(); + int jExit = addJunction(); + if (!!node[2]) { buildFlowGraph(node[2]); } joinJunction(jExit); setJunction(jEnter); - if (node[3]) { + if (!!node[3]) { buildFlowGraph(node[3]); } joinJunction(jExit); - break; - case 'conditional': + } + isInExpr--; + } else if (type == WHILE) { + // Special-case "while (1) {}" to use fewer junctions, + // since emscripten generates a lot of these. + if (isTrueNode(node[1])) { + int jLoop = markJunction(); + int jExit = addJunction(); + pushActiveLabels(jLoop, jExit); + buildFlowGraph(node[2]); + popActiveLabels(); + joinJunction(jLoop); + setJunction(jExit); + } else { + var jCond = markJunction(); + int jLoop = addJunction(); + int jExit = addJunction(); isInExpr++; - // 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); - } - isInExpr--; - break; - case 'while': - // Special-case "while (1) {}" to use fewer junctions, - // since emscripten generates a lot of these. - if (isTrueNode(node[1])) { - var jLoop = markJunction(); - var jExit = addJunction(); - pushActiveLabels(jLoop, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jLoop); - setJunction(jExit); - } else { - var jCond = markJunction(); - var jLoop = addJunction(); - var jExit = addJunction(); - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - joinJunction(jLoop); - pushActiveLabels(jCond, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jCond); - // An empty basic-block linking condition exit to loop exit. - setJunction(jLoop); - joinJunction(jExit); - } - break; - case 'do': - // Special-case "do {} while (1)" and "do {} while (0)" to use - // fewer junctions, since emscripten generates a lot of these. - if (isFalseNode(node[1])) { - var jExit = addJunction(); - pushActiveLabels(jExit, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jExit); - } else if (isTrueNode(node[1])) { - var jLoop = markJunction(); - var jExit = addJunction(); - pushActiveLabels(jLoop, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jLoop); - setJunction(jExit); - } else { - var jLoop = markJunction(); - var jCond = addJunction(); - var jCondExit = addJunction(); - var jExit = addJunction(); - pushActiveLabels(jCond, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jCond); - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - joinJunction(jCondExit); - joinJunction(jLoop); - setJunction(jCondExit); - joinJunction(jExit) - } - break; - case 'for': - var jTest = addJunction(); - var jBody = addJunction(); - var jStep = addJunction(); - var jExit = addJunction(); buildFlowGraph(node[1]); - joinJunction(jTest); - isInExpr++; - buildFlowGraph(node[2]); isInExpr--; - joinJunction(jBody); - pushActiveLabels(jStep, jExit); - buildFlowGraph(node[4]); + joinJunction(jLoop); + pushActiveLabels(jCond, jExit); + buildFlowGraph(node[2]); popActiveLabels(); - joinJunction(jStep); - buildFlowGraph(node[3]); - joinJunction(jTest); - setJunction(jBody); + joinJunction(jCond); + // An empty basic-block linking condition exit to loop exit. + setJunction(jLoop); joinJunction(jExit); - break; - case 'label': - assert(node[2][0] in BREAK_CAPTURERS, 'label on non-loop, non-switch statement') - nextLoopLabel = node[1]; + } + } else if (type == DO) { + // Special-case "do {} while (1)" and "do {} while (0)" to use + // fewer junctions, since emscripten generates a lot of these. + if (isFalseNode(node[1])) { + int jExit = addJunction(); + pushActiveLabels(jExit, jExit); buildFlowGraph(node[2]); - break; - case 'switch': - // Emscripten generates switch statements of a very limited - // form: all case clauses are numeric literals, and all - // case bodies end with a (maybe implicit) break. So it's - // basically equivalent to a multi-way 'if' statement. - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - var condition = lookThroughCasts(node[1]); - var jCheckExit = markJunction(); - var jExit = addJunction(); - pushActiveLabels(null, jExit); - var hasDefault = false; - for (var i=0; i 0) { - if (node[2][i][0]) { - markNonLocalJump(RETURN); - } else { - joinJunction(jExit); - } - } - } - // If there was no default case, we also need an empty block - // linking straight from the test evaluation to the exit. - if (!hasDefault) { - setJunction(jCheckExit); - } + popActiveLabels(); joinJunction(jExit); - popActiveLabels() - break; - case RETURN: - if (node[1]) { - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - } - markNonLocalJump(type); - break; - case BREAK: - case CONTINUE: - markNonLocalJump(type, node[1]); - break; - case ASSIGN: - isInExpr++; - buildFlowGraph(node[3]); - isInExpr--; - if (node[1] == true && node[2][0] == NAME) { - addKillNode(node); - } else { - buildFlowGraph(node[2]); - } - break; - case NAME: - addUseNode(node); - break; - case 'block': - case 'toplevel': - if (node[1]) { - for (var i = 0; i < node[1].length; i++) { - buildFlowGraph(node[1][i]); - } - } - break; - case 'stat': - buildFlowGraph(node[1]); - break; - case 'unary-prefix': - case 'unary-postfix': - isInExpr++; + } else if (isTrueNode(node[1])) { + int jLoop = markJunction(); + int jExit = addJunction(); + pushActiveLabels(jLoop, jExit); buildFlowGraph(node[2]); - isInExpr--; - break; - case 'binary': - isInExpr++; + popActiveLabels(); + joinJunction(jLoop); + setJunction(jExit); + } else { + int jLoop = markJunction(); + int jCond = addJunction(); + int jCondExit = addJunction(); + int jExit = addJunction(); + pushActiveLabels(jCond, jExit); buildFlowGraph(node[2]); - buildFlowGraph(node[3]); - isInExpr--; - break; - case 'call': + popActiveLabels(); + joinJunction(jCond); isInExpr++; buildFlowGraph(node[1]); - if (node[2]) { - for (var i = 0; i < node[2].length; i++) { - buildFlowGraph(node[2][i]); + isInExpr--; + joinJunction(jCondExit); + joinJunction(jLoop); + setJunction(jCondExit); + joinJunction(jExit) + } + } else if (type == FOR) { + int jTest = addJunction(); + int jBody = addJunction(); + int jStep = addJunction(); + int jExit = addJunction(); + buildFlowGraph(node[1]); + joinJunction(jTest); + isInExpr++; + buildFlowGraph(node[2]); + isInExpr--; + joinJunction(jBody); + pushActiveLabels(jStep, jExit); + buildFlowGraph(node[4]); + popActiveLabels(); + joinJunction(jStep); + buildFlowGraph(node[3]); + joinJunction(jTest); + setJunction(jBody); + joinJunction(jExit); + } else if (type == LABEL) { + assert(BREAK_CAPTURERS.has(node[2][0])); // 'label on non-loop, non-switch statement') + nextLoopLabel = node[1]; + buildFlowGraph(node[2]); + } else if (type == SWITCH) { + // Emscripten generates switch statements of a very limited + // form: all case clauses are numeric literals, and all + // case bodies end with a (maybe implicit) break. So it's + // basically equivalent to a multi-way IF statement. + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + Ref condition = lookThroughCasts(node[1]); + int jCheckExit = markJunction(); + int jExit = addJunction(); + pushActiveLabels(-1, jExit); + bool hasDefault = false; + for (int i = 0; i < node[2]->size(); i++) { + setJunction(jCheckExit); + // All case clauses are either 'default' or a numeric literal. + if (!node[2][i][0]) { + hasDefault = true; + } else { + // Detect switches dispatching to labelled blocks. + if (condition[0] == NAME && condition[1] == LABEL) { + addBlockLabel(lookThroughCasts(node[2][i][0])); } } - isInExpr--; - // If the call is statically known to throw, - // treat it as a jump to function exit. - if (!isInExpr && node[1][0] == NAME) { - if (node[1][1] in FUNCTIONS_THAT_ALWAYS_THROW) { + for (int j = 0; j < node[2][i][1]->size(); j++) { + buildFlowGraph(node[2][i][1][j]); + } + // Control flow will never actually reach the end of the case body. + // If there's live code here, assume it jumps to case exit. + if (currEntryJunction >= 0 && nextBasicBlock->nodes.size() > 0) { + if (!!node[2][i][0]) { markNonLocalJump(RETURN); + } else { + joinJunction(jExit); } } - break; - case 'seq': - case 'sub': - isInExpr++; - buildFlowGraph(node[1]); - buildFlowGraph(node[2]); - isInExpr--; - break; - case 'dot': - case 'throw': + } + // If there was no default case, we also need an empty block + // linking straight from the test evaluation to the exit. + if (!hasDefault) { + setJunction(jCheckExit); + } + joinJunction(jExit); + popActiveLabels() + } else if (type == RETURN) { + if (!!node[1]) { isInExpr++; buildFlowGraph(node[1]); isInExpr--; - break; - case 'num': - case 'string': - case 'var': - break; - default: - printErr(JSON.stringify(node)); - assert(false, 'unsupported node type: ' + type); + } + markNonLocalJump(type); + } else if (type == BREAK || type == CONTINUE) { + markNonLocalJump(type, node[1]); + } else if (type == ASSIGN) { + isInExpr++; + buildFlowGraph(node[3]); + isInExpr--; + if (node[1]->isBool(true) && node[2][0] == NAME) { + addKillNode(node); + } else { + buildFlowGraph(node[2]); + } + } else if (type == NAME) { + addUseNode(node); + } else if (type == BLOCK || type == TOPLEVEL) { + if (!!node[1]) { + for (int i = 0; i < node[1]->size(); i++) { + buildFlowGraph(node[1][i]); + } + } + } else if (type == STAT) { + buildFlowGraph(node[1]); + } else if (type == UNARY_PREFIX || type == UNARY_POSTFIX) { + isInExpr++; + buildFlowGraph(node[2]); + isInExpr--; + } else if (type == BINARY) { + isInExpr++; + buildFlowGraph(node[2]); + buildFlowGraph(node[3]); + isInExpr--; + } else if (type == CALL) { + isInExpr++; + buildFlowGraph(node[1]); + if (!!node[2]) { + for (int i = 0; i < node[2]->size(); i++) { + buildFlowGraph(node[2][i]); + } + } + isInExpr--; + // If the call is statically known to throw, + // treat it as a jump to function exit. + if (!isInExpr && node[1][0] == NAME) { + if (FUNCTIONS_THAT_ALWAYS_THROW.has(node[1][1])) { + markNonLocalJump(RETURN); + } + } + } else if (type == SEQ || type == SUB) {: + isInExpr++; + buildFlowGraph(node[1]); + buildFlowGraph(node[2]); + isInExpr--; + } else if (type == DOT || type == THROW) { + isInExpr++; + buildFlowGraph(node[1]); + isInExpr--; + } else if (type == NUM || type == STRING || type == VAR) { + // nada + } else { + assert(0); // 'unsupported node type: ' + type); } - } + }; + buildFlowGraph(fun); - assert(setSize(junctions[ENTRY_JUNCTION].inblocks) == 0, 'function entry must have no incoming blocks'); - assert(setSize(junctions[EXIT_JUNCTION].outblocks) == 0, 'function exit must have no outgoing blocks'); - assert(blocks[ENTRY_BLOCK].entry == ENTRY_JUNCTION, 'block zero must be the initial block'); + assert(junctions[ENTRY_JUNCTION].inblocks.size() == 0); // 'function entry must have no incoming blocks'); + assert(junctions[EXIT_JUNCTION].outblocks.size() == 0); // 'function exit must have no outgoing blocks'); + assert(blocks[ENTRY_BLOCK].entry == ENTRY_JUNCTION); //, 'block zero must be the initial block'); - // Fix up implicit jumps done by assigning to the 'label' variable. - // If a block ends with an assignment to 'label' and there's another block - // with that value of 'label' as precondition, we tweak the flow graph so + // Fix up implicit jumps done by assigning to the LABEL variable. + // If a block ends with an assignment to LABEL and there's another block + // with that value of LABEL as precondition, we tweak the flow graph so // that the former jumps straight to the later. var labelledBlocks = {}; @@ -3033,12 +2993,12 @@ void registerizeHarder(Ref ast) { labelledBlocks[labelVal] = block; } // Does it assign a specific label value at exit? - if ('label' in block.kill) { + if (LABEL in block.kill) { var finalNode = block.nodes[block.nodes.length - 1]; - if (finalNode[0] == ASSIGN && finalNode[2][1] == 'label') { + if (finalNode[0] == ASSIGN && finalNode[2][1] == LABEL) { // If labels are computed dynamically then all bets are off. // This can happen due to indirect branching in llvm output. - if (finalNode[3][0] !== 'num') { + if (finalNode[3][0] !== NUM) { labelledBlocks = {}; labelledJumps = []; break FINDLABELLEDBLOCKS; @@ -3049,8 +3009,8 @@ void registerizeHarder(Ref ast) { // then all bets are off. This can happen e.g. due to outlining // saving/restoring label to the stack. for (var j = 0; j < block.nodes.length - 1; j++) { - if (block.nodes[j][0] == ASSIGN && block.nodes[j][2][1] == 'label') { - if (block.nodes[j][3][0] !== 'num' && block.nodes[j][3][1] !== 0) { + if (block.nodes[j][0] == ASSIGN && block.nodes[j][2][1] == LABEL) { + if (block.nodes[j][3][0] !== NUM && block.nodes[j][3][1] !== 0) { labelledBlocks = {}; labelledJumps = []; break FINDLABELLEDBLOCKS; @@ -3067,9 +3027,9 @@ void registerizeHarder(Ref ast) { delete junctions[block.entry].outblocks[block.id]; block.entry = addJunction(); junctions[block.entry].outblocks[block.id] = 1; - // Add a fake use of 'label' to keep it alive in predecessor. - block.use['label'] = 1; - block.nodes.unshift([NAME, 'label']); + // Add a fake use of LABEL to keep it alive in predecessor. + block.use[LABEL] = 1; + block.nodes.unshift([NAME, LABEL]); block.isexpr.unshift(1); } for (var i = 0; i < labelledJumps.length; i++) { @@ -3197,7 +3157,7 @@ void registerizeHarder(Ref ast) { numUsesInExpr++; } }); - morphNode(node, ['block', []]); + morphNode(node, [BLOCK, []]); j = j - numUsesInExpr; removeUnusedNodes(j, 1 + numUsesInExpr); } @@ -3393,7 +3353,7 @@ void registerizeHarder(Ref ast) { var block = blocks[i]; if (block.nodes.length == 0) continue; var jEnter = junctions[block.entry]; - var jExit = junctions[block.exit]; + int jExit = junctions[block.exit]; // Mark the point at which each input reg becomes dead. // Variables alive before this point must not be assigned // to that register. @@ -3499,7 +3459,7 @@ void registerizeHarder(Ref ast) { if (block.isexpr[maybeRemoveNodes[j][0]]) { morphNode(node, node[2]); } else { - morphNode(node, ['block', []]); + morphNode(node, [BLOCK, []]); } } } From c2226b1afc1d758ad98fd40e54339dd18e3aee77 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 15:22:28 -0800 Subject: [PATCH 09/31] fix glfwEnable|Disable, fixes #3059 --- src/library_glfw.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/library_glfw.js b/src/library_glfw.js index 7998022485c9b..408b3031b88ec 100644 --- a/src/library_glfw.js +++ b/src/library_glfw.js @@ -1188,12 +1188,12 @@ var LibraryGLFW = { _sleep(time); }, - glfwEnable: function(token) { + glfwEnable: function(target) { target = GLFW.GLFW2ParamToGLFW3Param(target); GLFW.hints[target] = false; }, - glfwDisable: function(token) { + glfwDisable: function(target) { target = GLFW.GLFW2ParamToGLFW3Param(target); GLFW.hints[target] = true; }, From d1b2e1ea4f01a29101f7e88c4fed85cdfefacea5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 15:26:15 -0800 Subject: [PATCH 10/31] add test for #3059 --- tests/glfw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/glfw.c b/tests/glfw.c index 0a7a358b28fe8..2a247603ae573 100644 --- a/tests/glfw.c +++ b/tests/glfw.c @@ -49,6 +49,8 @@ void Init() if (glfwInit() != GL_TRUE) Shut_Down(1); + glfwEnable(GLFW_KEY_REPEAT); // test for issue #3059 + int red_bits = glfwGetWindowParam(GLFW_RED_BITS); glfwOpenWindowHint(GLFW_RED_BITS, 8); assert(glfwGetWindowParam(GLFW_RED_BITS) == 8); From 041f6b67516974129af41805021803e9dc542b66 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 15:50:28 -0800 Subject: [PATCH 11/31] continue on registerizeHarder --- tools/optimizer/optimizer.cpp | 108 +++++++++++++++++----------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index 7669f1d4afd98..2b244e97b8c55 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -2516,6 +2516,7 @@ void registerizeHarder(Ref ast) { struct Junction { int id; std::unordered_set inblocks, outblocks; + StringSet live; Junction(int id_) : id(id_) {} }; struct Node { @@ -2975,44 +2976,45 @@ void registerizeHarder(Ref ast) { // with that value of LABEL as precondition, we tweak the flow graph so // that the former jumps straight to the later. - var labelledBlocks = {}; - var labelledJumps = []; + std::unordered_map labelledBlocks; + typedef std::pair(Ref, Block*) Jump; + std::vector labelledJumps; FINDLABELLEDBLOCKS: - for (var i = 0; i < blocks.length; i++) { - var block = blocks[i]; + for (int i = 0; i < blocks.size(); i++) { + Block* block = blocks[i]; // Does it have any labels as preconditions to its entry? - for (var labelVal in block.labels) { + for (auto labelVal : block->labels) { // If there are multiple blocks with the same label, all bets are off. // This seems to happen sometimes for short blocks that end with a return. // TODO: it should be safe to merge the duplicates if they're identical. - if (labelVal in labelledBlocks) { - labelledBlocks = {}; - labelledJumps = []; + if (labelledBlocks.count(labelVal) > 0) { + labelledBlocks.clear(); + labelledJumps.clear(); break FINDLABELLEDBLOCKS; } labelledBlocks[labelVal] = block; } // Does it assign a specific label value at exit? - if (LABEL in block.kill) { - var finalNode = block.nodes[block.nodes.length - 1]; + if (block.kill.has(LABEL)) { + var finalNode = block->nodes.back(); if (finalNode[0] == ASSIGN && finalNode[2][1] == LABEL) { // If labels are computed dynamically then all bets are off. // This can happen due to indirect branching in llvm output. - if (finalNode[3][0] !== NUM) { - labelledBlocks = {}; - labelledJumps = []; + if (finalNode[3][0] != NUM) { + labelledBlocks.clear(); + labelledJumps.clear(); break FINDLABELLEDBLOCKS; } - labelledJumps.push([finalNode[3][1], block]); + labelledJumps.push(Jump(finalNode[3][1], block)); } else { // If label is assigned a non-zero value elsewhere in the block // then all bets are off. This can happen e.g. due to outlining // saving/restoring label to the stack. - for (var j = 0; j < block.nodes.length - 1; j++) { + for (int j = 0; j < block->nodes.size() - 1; j++) { if (block.nodes[j][0] == ASSIGN && block.nodes[j][2][1] == LABEL) { - if (block.nodes[j][3][0] !== NUM && block.nodes[j][3][1] !== 0) { - labelledBlocks = {}; - labelledJumps = []; + if (block.nodes[j][3][0] != NUM && block.nodes[j][3][1]->getNumber() != 0) { + labelledBlocks.clear(); + labelledJumps.clear(); break FINDLABELLEDBLOCKS; } } @@ -3020,31 +3022,29 @@ void registerizeHarder(Ref ast) { } } } - for (var labelVal in labelledBlocks) { - var block = labelledBlocks[labelVal]; + for (auto labelVal : labelledBlocks) { + var block = labelVal.second; // Disconnect it from the graph, and create a // new junction for jumps targetting this label. - delete junctions[block.entry].outblocks[block.id]; + junctions[block.entry].outblocks.erase(block.id); block.entry = addJunction(); - junctions[block.entry].outblocks[block.id] = 1; + junctions[block.entry].outblocks.insert(block.id); // Add a fake use of LABEL to keep it alive in predecessor. - block.use[LABEL] = 1; - block.nodes.unshift([NAME, LABEL]); - block.isexpr.unshift(1); - } - for (var i = 0; i < labelledJumps.length; i++) { - var labelVal = labelledJumps[i][0]; - var block = labelledJumps[i][1]; - var targetBlock = labelledBlocks[labelVal]; + block.use.insert(LABEL); + block.nodes.insert(block.nodes.begin(), makeName(LABEL)); + block.isexpr.insert(block.isexpr(), 1); + } + for (int i = 0; i < labelledJumps.size(); i++) { + auto labelVal = labelledJumps[i].first; + auto block = labelledJumps[i].second; + Block* targetBlock = labelledBlocks[labelVal]; if (targetBlock) { // Redirect its exit to entry of the target block. - delete junctions[block.exit].inblocks[block.id]; - block.exit = targetBlock.entry; - junctions[block.exit].inblocks[block.id] = 1; + junctions[block->exit].inblocks.erase(block->id); + block->exit = targetBlock->entry; + junctions[block->exit].inblocks.inasert(block->id); } } - labelledBlocks = null; - labelledJumps = null; // Do a backwards data-flow analysis to determine the set of live // variables at each junction, and to use this information to eliminate @@ -3053,23 +3053,23 @@ void registerizeHarder(Ref ast) { // junction. The outer phase uses this to try to eliminate redundant // stores in each basic block, which might in turn affect liveness info. - function analyzeJunction(junc) { + auto analyzeJunction = [&](Junction& junc) { // Update the live set for this junction. - var live = {}; - for (var b in junc.outblocks) { - var block = blocks[b]; - var liveSucc = junctions[block.exit].live || {}; - for (var name in liveSucc) { - if (!(name in block.kill)) { - live[name] = 1; + StringSet& live = junc.live; + std::unordered_set live; + for (auto b : junc.outblocks) { + Block* block = blocks[b]; + StringSet& liveSucc = junctions[block->exit].live; + for (auto name : liveSucc) { + if (!block->kill.has(name)) { + live.insert(name); } } - for (var name in block.use) { - live[name] = 1; + for (auto name : block->use) { + live.insert(name); } } - junc.live = live; - } + }; function analyzeBlock(block) { // Update information about the behaviour of the block. @@ -3270,7 +3270,7 @@ void registerizeHarder(Ref ast) { } // It links with any linkages in the outgoing blocks. var linkName = block.link[name]; - if (linkName && linkName !== name) { + if (linkName && linkName != name) { if (!junctionVariables[linkName]) initializeJunctionVariable(linkName); junctionVariables[name].link[linkName] = 1; junctionVariables[linkName].link[name] = 1; @@ -3306,7 +3306,7 @@ void registerizeHarder(Ref ast) { // and propagate that choice throughout the graph. // Returns true if successful, false if there was a conflict. var jv = junctionVariables[name]; - if (jv.reg !== null) { + if (jv.reg != null) { return jv.reg == reg; } if (jv.excl[reg]) { @@ -3329,7 +3329,7 @@ void registerizeHarder(Ref ast) { for (var i = 0; i < sortedJunctionVariables.length; i++) { var name = sortedJunctionVariables[i]; // It may already be assigned due to linked-variable propagation. - if (junctionVariables[name].reg !== null) { + if (junctionVariables[name].reg != null) { continue NEXTVARIABLE; } // Try to use existing registers first. @@ -3364,7 +3364,7 @@ void registerizeHarder(Ref ast) { if (!(name in block.kill)) { inputVars[name] = 1; var reg = junctionVariables[name].reg; - assert(reg !== null, 'input variable doesnt have a register'); + assert(reg != null, 'input variable doesnt have a register'); inputDeadLoc[reg] = block.firstDeadLoc[name]; inputVarsByReg[reg] = name; } @@ -3373,7 +3373,7 @@ void registerizeHarder(Ref ast) { if (!(name in inputVars)) { inputVars[name] = 1; var reg = junctionVariables[name].reg; - assert(reg !== null, 'input variable doesnt have a register'); + assert(reg != null, 'input variable doesnt have a register'); inputDeadLoc[reg] = block.firstDeadLoc[name]; inputVarsByReg[reg] = name; } @@ -3388,7 +3388,7 @@ void registerizeHarder(Ref ast) { // Begin with all live vars assigned per the exit junction. for (var name in jExit.live) { var reg = junctionVariables[name].reg; - assert(reg !== null, 'output variable doesnt have a register'); + assert(reg != null, 'output variable doesnt have a register'); assignedRegs[name] = reg; delete freeRegsByType[localVars[name]][reg]; } @@ -3424,7 +3424,7 @@ void registerizeHarder(Ref ast) { reg = freeRegs[k]; // Check for conflict with input registers. if (block.firstKillLoc[name] <= inputDeadLoc[reg]) { - if (name !== inputVarsByReg[reg]) { + if (name != inputVarsByReg[reg]) { continue; } } From 22b10d3def1e8b63546c6057caff9830eff95452 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 17:03:21 -0800 Subject: [PATCH 12/31] handle in html5.h the fact that document, like window, does not have getBoundingClientRect; fixes #3060 --- src/library_html5.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/library_html5.js b/src/library_html5.js index 57f37ae5aca60..b2409328f8ee9 100644 --- a/src/library_html5.js +++ b/src/library_html5.js @@ -207,6 +207,10 @@ var LibraryJSEvents = { JSEvents.registerOrRemoveHandler(eventHandler); }, + getBoundingClientRectOrZeros: function(target) { + return target.getBoundingClientRect ? target.getBoundingClientRect() : { left: 0, top: 0 }; + }, + // Copies mouse event data from the given JS mouse event 'e' to the specified Emscripten mouse event structure in the HEAP. // eventStruct: the structure to populate. // e: The JS mouse event to read data from. @@ -235,7 +239,7 @@ var LibraryJSEvents = { {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.canvasY, '0', 'i32') }}}; } if (target) { - var rect = (target === window) ? { left: 0, top: 0 } : target.getBoundingClientRect(); + var rect = JSEvents.getBoundingClientRectOrZeros(target); {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.targetX, 'e.clientX - rect.left', 'i32') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenMouseEvent.targetY, 'e.clientY - rect.top', 'i32') }}}; } else { // No specific target passed, return 0. @@ -810,7 +814,7 @@ var LibraryJSEvents = { {{{ makeSetValue('ptr', C_STRUCTS.EmscriptenTouchEvent.metaKey, 'e.metaKey', 'i32') }}}; ptr += {{{ C_STRUCTS.EmscriptenTouchEvent.touches }}}; // Advance to the start of the touch array. var canvasRect = Module['canvas'] ? Module['canvas'].getBoundingClientRect() : undefined; - var targetRect = (target === window) ? { left: 0, top: 0 } : target.getBoundingClientRect(); + var targetRect = JSEvents.getBoundingClientRectOrZeros(target); var numTouches = 0; for(var i in touches) { var t = touches[i]; From f9a12e9105f22cf713ac0ba289060037f29e5c00 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 17:42:42 -0800 Subject: [PATCH 13/31] convert more of registerizeHarder --- tools/optimizer/optimizer.cpp | 149 ++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 72 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index 2b244e97b8c55..f11a67f004f41 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -3071,59 +3071,59 @@ void registerizeHarder(Ref ast) { } }; - function analyzeBlock(block) { + auto analyzeBlock = [&](Block* block) { // Update information about the behaviour of the block. // This includes the standard 'use' and 'kill' information, // plus a 'link' set naming values that flow through from entry // to exit, possibly changing names via simple 'x=y' assignments. // As we go, we eliminate assignments if the variable is not // subsequently used. - var live = copy(junctions[block.exit].live); - var use = {}; - var kill = {}; - var link = {}; - var lastUseLoc = {}; - var firstDeadLoc = {}; - var firstKillLoc = {}; - var lastKillLoc = {}; - for (var name in live) { + auto live = junctions[block.exit].live; + StringSet use; + StringSet kill; + StringStringMap link; + StringIntMap lastUseLoc; + StringIntMap firstDeadLoc; + StringIntMap firstKillLoc; + StringIntMap lastKillLoc; + for (auto name : live) { link[name] = name; - lastUseLoc[name] = block.nodes.length; - firstDeadLoc[name] = block.nodes.length; + lastUseLoc[name] = block->nodes.size(); + firstDeadLoc[name] = block->nodes.size(); } - for (var j = block.nodes.length - 1; j >=0 ; j--) { - var node = block.nodes[j]; + for (int j = block->nodes.size() - 1; j >= 0 ; j--) { + Ref node = block->nodes[j]; if (node[0] == NAME) { - var name = node[1]; - live[name] = 1; + IString name = node[1]->getIString(); + live.insert(name); use[name] = j; - if (lastUseLoc[name] == undefined) { + if (lastUseLoc.count(name) == 0) { lastUseLoc[name] = j; firstDeadLoc[name] = j; } } else { - var name = node[2][1]; + IString name = node[2][1]->getIString(); // We only keep assignments if they will be subsequently used. - if (name in live) { - kill[name] = 1; - delete use[name]; - delete live[name]; + if (live.has(name)) { + kill.insert(name); + use.erase(name); + live.erase(name); firstDeadLoc[name] = j; firstKillLoc[name] = j; - if (lastUseLoc[name] == undefined) { + if (lastUseLoc.count(name) == 0) { lastUseLoc[name] = j; } - if (lastKillLoc[name] == undefined) { + if (lastKillLoc.count(name) == 0) { lastKillLoc[name] = j; } // If it's an "x=y" and "y" is not live, then we can create a // flow-through link from "y" to "x". If not then there's no // flow-through link for "x". - var oldLink = link[name]; - if (oldLink) { - delete link[name]; + if (link.has(name)) { + IString oldLink = link[name]; + link.erase(name); if (node[3][0] == NAME) { - if (node[3][1] in localVars) { + if (asmData.isLocal(node[3][1])) { link[node[3][1]] = oldLink; } } @@ -3131,39 +3131,40 @@ void registerizeHarder(Ref ast) { } else { // The result of this assignment is never used, so delete it. // We may need to keep the RHS for its value or its side-effects. - function removeUnusedNodes(j, n) { - for (var name in lastUseLoc) { + auto removeUnusedNodes = [&](int j, int n) { + for (auto name : lastUseLoc) { lastUseLoc[name] -= n; } - for (var name in firstKillLoc) { + for (auto name : firstKillLoc) { firstKillLoc[name] -= n; } - for (var name in lastKillLoc) { + for (auto name : lastKillLoc) { lastKillLoc[name] -= n; } - for (var name in firstDeadLoc) { + for (auto name : firstDeadLoc) { firstDeadLoc[name] -= n; } - block.nodes.splice(j, n); - block.isexpr.splice(j, n); + block->nodes.erase(block->nodes.begin() + j, block->nodes.begin() + j + n); + block->isexpr.erase(block->isexpr.begin() + j, block->isexpr.begin() + j + n); } if (block.isexpr[j] || hasSideEffects(node[3])) { - morphNode(node, node[3]); + safeCopy(node, node[3]); removeUnusedNodes(j, 1); } else { - var numUsesInExpr = 0; - traverse(node[3], function(node, type) { - if (type == NAME && node[1] in localVars) { + int numUsesInExpr = 0; + traversePre(node[3], [&](Ref node) { + if (node[0] == NAME && asmData.isLocal(node[1])) { numUsesInExpr++; } }); - morphNode(node, [BLOCK, []]); + morphNode(node, makeBlock()); j = j - numUsesInExpr; removeUnusedNodes(j, 1 + numUsesInExpr); } } } } + // XXX efficiency block.use = use; block.kill = kill; block.link = link; @@ -3171,56 +3172,60 @@ void registerizeHarder(Ref ast) { block.firstDeadLoc = firstDeadLoc; block.firstKillLoc = firstKillLoc; block.lastKillLoc = lastKillLoc; - } + }; - var jWorklistMap = { EXIT_JUNCTION: 1 }; - var jWorklist = [EXIT_JUNCTION]; - var bWorklistMap = {}; - var bWorklist = []; + std::unordered_set jWorklistMap; + jWorklistMap.insert(EXIT_JUNCTION); + std::vector jWorklist; + jWorklist.push_back(EXIT_JUNCTION); + std::unordered_set bWorklistMap; + std::vector bWorklist; // Be sure to visit every junction at least once. // This avoids missing some vars because we disconnected them // when processing the labelled jumps. - for (var i = junctions.length - 1; i >= EXIT_JUNCTION; i--) { - jWorklistMap[i] = 1; - jWorklist.push(i); + for (int i = junctions.size() - 1; i >= EXIT_JUNCTION; i--) { + jWorklistMap.insert(i); + jWorklist.push_back(i); } - while (jWorklist.length > 0) { + while (jWorklist.size() > 0) { // Iterate on just the junctions until we get stable live sets. // The first run of this loop will grow the live sets to their maximal size. // Subsequent runs will shrink them based on eliminated in-block uses. - while (jWorklist.length > 0) { - var junc = junctions[jWorklist.pop()]; - delete jWorklistMap[junc.id]; - var oldLive = junc.live || null; + while (jWorklist.size() > 0) { + Junction& junc = junctions[jWorklist.back()]; + jWorklist.pop_back(); + jWorklistMap.erase(junc.id); + StringSet oldLive = junc.live; // copy it here, to check for changes later analyzeJunction(junc); - if (!sortedJsonCompare(oldLive, junc.live)) { + if (oldLive != junc.live) { // Live set changed, updated predecessor blocks and junctions. - for (var b in junc.inblocks) { - if (!(b in bWorklistMap)) { - bWorklistMap[b] = 1; - bWorklist.push(b); + for (auto b : junc.inblocks) { + if (bWorklistMap.count(b) == 0) { + bWorklistMap.insert(b) + bWorklist.push_back(b); } - var jPred = blocks[b].entry; - if (!(jPred in jWorklistMap)) { - jWorklistMap[jPred] = 1; - jWorklist.push(jPred); + int jPred = blocks[b].entry; + if (jWorklistMap.count(jPred) == 0) { + jWorklistMap.insert(jPred); + jWorklist.push_back(jPred); } } } } // Now update the blocks based on the calculated live sets. - while (bWorklist.length > 0) { - var block = blocks[bWorklist.pop()]; - delete bWorklistMap[block.id]; - var oldUse = block.use; + while (bWorklist.size() > 0) { + Block* block = blocks[bWorklist.back()]; + bWorklist.pop_back(); + bWorklistMap.erase(block->id); + StringSet oldUse = block->use; analyzeBlock(block); - if (!sortedJsonCompare(oldUse, block.use)) { + if (oldUse != block->use) { // The use set changed, re-process the entry junction. - if (!(block.entry in jWorklistMap)) { - jWorklistMap[block.entry] = 1; - jWorklist.push(block.entry); + if (jWorklistMap.count(block->entry) == 0) { + jWorklistMap.insert(block.entry); + jWorklist.push_back(block.entry); } } } @@ -3230,8 +3235,8 @@ void registerizeHarder(Ref ast) { // This ensures they will be assigned independent registers, even // if they happen to be unused. - for (var name in asmData.params) { - junctions[ENTRY_JUNCTION].live[name] = 1; + for (auto name : asmData.params) { + junctions[ENTRY_JUNCTION].live.insert(name); } // For variables that are live at one or more junctions, we assign them From c21b740250345feab44d258cf6e12146706a4c1f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Wed, 10 Dec 2014 17:57:55 -0800 Subject: [PATCH 14/31] fix default test runner mode, need to force ASM_JS to 2 now that emcc allows changing it as well --- tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 7e297b434094f..8c376f91effc8 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -7232,7 +7232,7 @@ def setUp(self): return TT # Main test modes -default = make_run("default", compiler=CLANG, emcc_args=[]) +default = make_run("default", compiler=CLANG, emcc_args=["-s", "ASM_JS=2"]) asm1 = make_run("asm1", compiler=CLANG, emcc_args=["-O1"]) asm2 = make_run("asm2", compiler=CLANG, emcc_args=["-O2"]) asm3 = make_run("asm3", compiler=CLANG, emcc_args=["-O3"]) From ff360a967472b82852d6c1e0fb44d083536e6ed9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 11:09:23 -0800 Subject: [PATCH 15/31] return successfully parsed fields if we hit the end in the middle of fscanf; fixes #3073 --- src/library.js | 2 +- tests/test_core.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/library.js b/src/library.js index 432b4880f002b..711cc7296a4a6 100644 --- a/src/library.js +++ b/src/library.js @@ -1878,7 +1878,7 @@ LibraryManager.library = { } unget(); } - if (buffer.length === 0) return 0; // Failure. + if (buffer.length === 0) return fields; // Stop here. if (suppressAssignment) continue; var text = buffer.join(''); diff --git a/tests/test_core.py b/tests/test_core.py index 8c376f91effc8..e49c6a737b10c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4347,6 +4347,35 @@ def test_fscanf(self): self.emcc_args += ['--embed-file', 'three_numbers.txt'] self.do_run(src, 'match = 3\nx = -1.0, y = 0.1, z = -0.1\n') + def test_fscanf_2(self): + if self.emcc_args is None: return self.skip('requires emcc') + + open('a.txt', 'w').write('''1/2/3 4/5/6 7/8/9 +''') + self.emcc_args += ['--embed-file', 'a.txt'] + self.do_run(r'''#include +#include + +using namespace std; + +int +main( int argv, char ** argc ) { + cout << "fscanf test" << endl; + + FILE * file; + file = fopen("a.txt", "rb"); + int vertexIndex[4]; + int normalIndex[4]; + int uvIndex[4]; + + int matches = fscanf(file, "%d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n", &vertexIndex[0], &uvIndex[0], &normalIndex[0], &vertexIndex [1], &uvIndex[1], &normalIndex[1], &vertexIndex[2], &uvIndex[2], &normalIndex[2], &vertexIndex[3], &uvIndex[3], &normalIndex[3]); + + cout << matches << endl; + + return 0; +} +''', 'fscanf test\n9\n') + def test_fileno(self): if self.emcc_args is None: return self.skip('requires emcc') open(os.path.join(self.get_dir(), 'empty.txt'), 'w').write('') From 5b3516518c2b7a526b73b3f39bda75227ba46b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 11 Dec 2014 21:57:15 +0200 Subject: [PATCH 16/31] Fix bad glUniform1i call in tests/float_tex.cpp --- tests/float_tex.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/float_tex.cpp b/tests/float_tex.cpp index c40ff78668dbb..b80457a3ba5bc 100644 --- a/tests/float_tex.cpp +++ b/tests/float_tex.cpp @@ -77,7 +77,7 @@ static void glut_draw_callback(void) { glActiveTexture(GL_TEXTURE0); updateFloatTexture(); //we change the texture each time to create the effect (it is just for the test) glBindTexture(GL_TEXTURE_2D, nodeTexture); - glUniform1i(nodeSamplerLocation, GL_TEXTURE0); + glUniform1i(nodeSamplerLocation, 0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, indicesVBO); glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, NULL); From 8c863fe86531f0d8b950ccbb108140db32d62745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 11 Dec 2014 22:01:20 +0200 Subject: [PATCH 17/31] Clean up redundant warnings in tests/float_tex.cpp --- tests/float_tex.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/float_tex.cpp b/tests/float_tex.cpp index b80457a3ba5bc..f30323894aced 100644 --- a/tests/float_tex.cpp +++ b/tests/float_tex.cpp @@ -117,9 +117,11 @@ static void gl_init(void) { /* Get the locations of the uniforms so we can access them */ nodeSamplerLocation = glGetUniformLocation(program, "nodeInfo"); glBindAttribLocation(program, 0, "indices"); +#ifndef __EMSCRIPTEN__ // GLES2 & WebGL do not have these, only pre 3.0 desktop GL and compatibility mode GL3.0+ GL do. //Enable glPoint size in shader, always enable in Open Gl ES 2. glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glEnable(GL_POINT_SPRITE); +#endif } int main(int argc, char *argv[]) { glutInit(&argc, argv); From 5c888303eb1a7099662c455b77229890969c5bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 11 Dec 2014 22:15:02 +0200 Subject: [PATCH 18/31] Fix tests/float_tex.cpp to use unsized texture internal format GL_RGBA instead of sized texture internal format GL_RGBA32F for floating point texture, since GLES2/WebGL1 does not support the sized variants. --- tests/float_tex.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/float_tex.cpp b/tests/float_tex.cpp index f30323894aced..698b058e88671 100644 --- a/tests/float_tex.cpp +++ b/tests/float_tex.cpp @@ -61,7 +61,13 @@ static void updateFloatTexture() { ++count; } glBindTexture(GL_TEXTURE_2D, nodeTexture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data); +#ifdef __EMSCRIPTEN__ // In GLES2 and WebGL1, we must use unsized texture internal formats. + const GLenum internalFormat = GL_RGBA; +#else + // In desktop GL, we can also use sized internal formats. + const GLenum internalFormat = GL_RGBA32F; +#endif + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, NULL); From 11ca950e140e8b8d13c8ff8c0c35101a6e8fd43b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 12:16:59 -0800 Subject: [PATCH 19/31] minor fixes to registerizeHarder, and progress on conversion to native optimizer --- tools/js-optimizer.js | 4 +- tools/optimizer/optimizer.cpp | 81 +++++++++++++++++------------------ 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index a7ba7e9f39fe7..462cd717687e1 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3654,9 +3654,9 @@ function registerizeHarder(ast) { junctionVariables[name].conf[otherName] = 1; } for (var b in junc.outblocks) { - // It conflits with any output vars of successor blocks, + // It conflicts with any output vars of successor blocks, // if they're assigned before it goes dead in that block. - block = blocks[b]; + var block = blocks[b]; var jSucc = junctions[block.exit]; for (var otherName in jSucc.live) { if (junc.live[otherName]) continue; diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index f11a67f004f41..e8282027c8aa1 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -2711,11 +2711,11 @@ void registerizeHarder(Ref ast) { // Traverse each node type according to its particular control-flow semantics. // TODO: switchify this if (type == DEFUN) { - var jEntry = markJunction(); + int jEntry = markJunction(); assert(jEntry == ENTRY_JUNCTION); int jExit = addJunction(); assert(jExit == EXIT_JUNCTION); - for (var i = 0; i < node[3].length; i++) { + for (int i = 0; i < node[3].length; i++) { buildFlowGraph(node[3][i]); } joinJunction(jExit); @@ -2780,7 +2780,7 @@ void registerizeHarder(Ref ast) { joinJunction(jLoop); setJunction(jExit); } else { - var jCond = markJunction(); + int jCond = markJunction(); int jLoop = addJunction(); int jExit = addJunction(); isInExpr++; @@ -2996,7 +2996,7 @@ void registerizeHarder(Ref ast) { } // Does it assign a specific label value at exit? if (block.kill.has(LABEL)) { - var finalNode = block->nodes.back(); + Ref finalNode = block->nodes.back(); if (finalNode[0] == ASSIGN && finalNode[2][1] == LABEL) { // If labels are computed dynamically then all bets are off. // This can happen due to indirect branching in llvm output. @@ -3023,7 +3023,7 @@ void registerizeHarder(Ref ast) { } } for (auto labelVal : labelledBlocks) { - var block = labelVal.second; + Block* block = labelVal.second; // Disconnect it from the graph, and create a // new junction for jumps targetting this label. junctions[block.entry].outblocks.erase(block.id); @@ -3245,40 +3245,44 @@ void registerizeHarder(Ref ast) { // as pairs of variables that we'd like to have share the same register // (the "links"). - var junctionVariables = {}; + struct JuncVar { + StringSet conf, link, excl; + IString reg; + }; + std::unordered_map junctionVariables; - function initializeJunctionVariable(name) { - junctionVariables[name] = { conf: {}, link: {}, excl: {}, reg: null }; - } + auto initializeJunctionVariable = [&](IString name) { + junctionVariables[name] = JuncVar(); // XXX + }; - for (var i = 0; i < junctions.length; i++) { - var junc = junctions[i]; - for (var name in junc.live) { - if (!junctionVariables[name]) initializeJunctionVariable(name); + for (int i = 0; i < junctions.size(); i++) { + Junction& junc = junctions[i]; + for (auto name : junc.live) { + if (junctionVariables.count(name) == 0) initializeJunctionVariable(name); // It conflicts with all other names live at this junction. - for (var otherName in junc.live) { + for (auto otherName : junc.live) { if (otherName == name) continue; - junctionVariables[name].conf[otherName] = 1; + junctionVariables[name].conf.insert(otherName); } - for (var b in junc.outblocks) { - // It conflits with any output vars of successor blocks, + for (auto b : junc.outblocks) { + // It conflicts with any output vars of successor blocks, // if they're assigned before it goes dead in that block. - block = blocks[b]; - var jSucc = junctions[block.exit]; - for (var otherName in jSucc.live) { - if (junc.live[otherName]) continue; - if (block.lastKillLoc[otherName] < block.firstDeadLoc[name]) { - if (!junctionVariables[otherName]) initializeJunctionVariable(otherName); - junctionVariables[name].conf[otherName] = 1; - junctionVariables[otherName].conf[name] = 1; + Block* block = blocks[b]; + Junction& jSucc = junctions[block->exit]; + for (jSucc.live.has(otherName)) { + if (junc.live.has(otherName)) continue; + if (block->lastKillLoc[otherName] < block->firstDeadLoc[name]) { + if (junctionVariables.count(otherName) == 0) initializeJunctionVariable(otherName); + junctionVariables[name].conf.insert(otherName); + junctionVariables[otherName].conf.insert(name); } } // It links with any linkages in the outgoing blocks. - var linkName = block.link[name]; - if (linkName && linkName != name) { - if (!junctionVariables[linkName]) initializeJunctionVariable(linkName); - junctionVariables[name].link[linkName] = 1; - junctionVariables[linkName].link[name] = 1; + IString linkName = block->link[name]; + if (!!linkName && linkName != name) { + if (junctionVariables.count(linkName) == 0) initializeJunctionVariable(linkName); + junctionVariables[name].link.insert(linkName); + junctionVariables[linkName].link.insert(name); } } } @@ -3288,17 +3292,12 @@ void registerizeHarder(Ref ast) { // Simple starting point: handle the most-conflicted variables first. // This seems to work pretty well. - var sortedJunctionVariables = keys(junctionVariables); - sortedJunctionVariables.sort(function(name1, name2) { - var jv1 = junctionVariables[name1]; - var jv2 = junctionVariables[name2]; - if (jv1.numConfs == undefined) { - jv1.numConfs = setSize(jv1.conf); - } - if (jv2.numConfs == undefined) { - jv2.numConfs = setSize(jv2.conf); - } - return jv2.numConfs - jv1.numConfs; + StringVec sortedJunctionVariables; + for (auto pair : junctionVariables) { + sortedJunctionVariables.push_back(pair->first); + } + std::sort(sortedJunctionVariables.begin(), sortedJunctionVariables.end(), [](const IString name1, const IString name2) { + return junctionVariables[name2].conf.size() < junctionVariables[name1].conf.size(); //XXX }); // We can now assign a register to each junction variable. From 2736a26860ac01a6623817d65b5ef518401e9056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 11 Dec 2014 22:18:21 +0200 Subject: [PATCH 20/31] Apply fix from tests/float_tex.cpp to tests/gl_subdata.cpp --- tests/gl_subdata.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/gl_subdata.cpp b/tests/gl_subdata.cpp index 5c6a992686f96..05ec18a4e70d4 100644 --- a/tests/gl_subdata.cpp +++ b/tests/gl_subdata.cpp @@ -62,6 +62,13 @@ static void updateFloatTexture() { } glBindTexture(GL_TEXTURE_2D, nodeTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data); +#ifdef __EMSCRIPTEN__ // In GLES2 and WebGL1, we must use unsized texture internal formats. + const GLenum internalFormat = GL_RGBA; +#else + // In desktop GL, we can also use sized internal formats. + const GLenum internalFormat = GL_RGBA32F; +#endif + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, nbNodes, 1, 0, GL_RGBA, GL_FLOAT, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, NULL); @@ -122,9 +129,11 @@ static void gl_init(void) { /* Get the locations of the uniforms so we can access them */ nodeSamplerLocation = glGetUniformLocation(program, "nodeInfo"); glBindAttribLocation(program, 0, "indices"); +#ifndef __EMSCRIPTEN__ // GLES2 & WebGL do not have these, only pre 3.0 desktop GL and compatibility mode GL3.0+ GL do. //Enable glPoint size in shader, always enable in Open Gl ES 2. glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); glEnable(GL_POINT_SPRITE); +#endif } int main(int argc, char *argv[]) { glutInit(&argc, argv); From 76e60996d1f9596573853111f618cedc76be881c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Jyl=C3=A4nki?= Date: Thu, 11 Dec 2014 22:19:42 +0200 Subject: [PATCH 21/31] Fix bad glUniform1i call in tests/gl_subdata.cpp --- tests/gl_subdata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gl_subdata.cpp b/tests/gl_subdata.cpp index 05ec18a4e70d4..52508a4a3ec35 100644 --- a/tests/gl_subdata.cpp +++ b/tests/gl_subdata.cpp @@ -84,7 +84,7 @@ static void glut_draw_callback(void) { glActiveTexture(GL_TEXTURE0); updateFloatTexture(); //we change the texture each time to create the effect (it is just for the test) glBindTexture(GL_TEXTURE_2D, nodeTexture); - glUniform1i(nodeSamplerLocation, GL_TEXTURE0); + glUniform1i(nodeSamplerLocation, 0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, indicesVBO); glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, NULL); From 71f52126725c39a1d9c31c122b243218b487c332 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 13:18:45 -0800 Subject: [PATCH 22/31] fix some typos in registerizeHarder, and continue on conversion --- tools/js-optimizer.js | 4 +- tools/optimizer/optimizer.cpp | 137 +++++++++++++++++----------------- 2 files changed, 72 insertions(+), 69 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 462cd717687e1..a00cbd4392723 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -3755,7 +3755,7 @@ function registerizeHarder(ast) { // Mark the point at which each input reg becomes dead. // Variables alive before this point must not be assigned // to that register. - var inputVars = {} + var inputVars = {}; var inputDeadLoc = {}; var inputVarsByReg = {}; for (var name in jExit.live) { @@ -3865,7 +3865,7 @@ function registerizeHarder(ast) { // Assign registers to function params based on entry junction - var paramRegs = {} + var paramRegs = {}; if (fun[2]) { for (var i = 0; i < fun[2].length; i++) { var allRegs = allRegsByType[localVars[fun[2][i]]]; diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index e8282027c8aa1..c744fb6dd9bf1 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -3305,40 +3305,40 @@ void registerizeHarder(Ref ast) { // one that works, and propagating the choice to linked/conflicted // variables as we go. - function tryAssignRegister(name, reg) { + auto tryAssignRegister = [&](IString name, IString reg) { // Try to assign the given register to the given variable, // and propagate that choice throughout the graph. // Returns true if successful, false if there was a conflict. - var jv = junctionVariables[name]; - if (jv.reg != null) { + JuncVar& jv = junctionVariables[name]; + if (!!jv.reg) { return jv.reg == reg; } - if (jv.excl[reg]) { + if (jv.excl.has(reg)) { return false; } jv.reg = reg; // Exclude use of this register at all conflicting variables. - for (var confName in jv.conf) { - junctionVariables[confName].excl[reg] = 1; + for (auto confName : jv.conf) { + junctionVariables[confName].excl.insert(reg); } // Try to propagate it into linked variables. // It's not an error if we can't. - for (var linkName in jv.link) { + for (auto linkName : jv.link) { tryAssignRegister(linkName, reg); } return true; - } + }; NEXTVARIABLE: - for (var i = 0; i < sortedJunctionVariables.length; i++) { - var name = sortedJunctionVariables[i]; + for (int i = 0; i < sortedJunctionVariables.size(); i++) { + IString name = sortedJunctionVariables[i]; // It may already be assigned due to linked-variable propagation. - if (junctionVariables[name].reg != null) { + if (!!junctionVariables[name].reg) { continue NEXTVARIABLE; } // Try to use existing registers first. - var allRegs = allRegsByType[localVars[name]]; - for (var reg in allRegs) { + auto& allRegs = allRegsByType[asmData.getType(name)]; + for (auto reg : allRegs) { if (tryAssignRegister(name, reg)) { continue NEXTVARIABLE; } @@ -3353,92 +3353,95 @@ void registerizeHarder(Ref ast) { // that all inter-block variables are in a good state thanks to // junction variable consistency. - for (var i = 0; i < blocks.length; i++) { - var block = blocks[i]; - if (block.nodes.length == 0) continue; - var jEnter = junctions[block.entry]; - int jExit = junctions[block.exit]; + for (int i = 0; i < blocks.size(); i++) { + Block* block = blocks[i]; + if (block->nodes.size() == 0) continue; + int jEnter = junctions[block->entry]; + int jExit = junctions[block->exit]; // Mark the point at which each input reg becomes dead. // Variables alive before this point must not be assigned // to that register. - var inputVars = {} - var inputDeadLoc = {}; - var inputVarsByReg = {}; - for (var name in jExit.live) { - if (!(name in block.kill)) { - inputVars[name] = 1; - var reg = junctionVariables[name].reg; - assert(reg != null, 'input variable doesnt have a register'); - inputDeadLoc[reg] = block.firstDeadLoc[name]; + StringSet inputVars; + std::unordered_set inputDeadLoc; + StringSet inputVarsByReg; + for (auto name : jExit.live) { + if (!block->kill.has(name)) { + inputVars.insert(name); + IString reg = junctionVariables[name].reg; + assert(!!reg); // 'input variable doesnt have a register'); + inputDeadLoc[reg] = block->firstDeadLoc[name]; inputVarsByReg[reg] = name; } } - for (var name in block.use) { - if (!(name in inputVars)) { - inputVars[name] = 1; - var reg = junctionVariables[name].reg; - assert(reg != null, 'input variable doesnt have a register'); - inputDeadLoc[reg] = block.firstDeadLoc[name]; + for (auto name : block->use) { + if (!inputVars.has(name)) { + inputVars.insert(name); + IString reg = junctionVariables[name].reg; + assert(!!reg); // 'input variable doesnt have a register'); + inputDeadLoc[reg] = block->firstDeadLoc[name]; inputVarsByReg[reg] = name; } } - assert(setSize(setSub(inputVars, jEnter.live)) == 0); + // TODO assert(setSize(setSub(inputVars, jEnter.live)) == 0); // Scan through backwards, allocating registers on demand. // Be careful to avoid conflicts with the input registers. // We consume free registers in last-used order, which helps to // eliminate "x=y" assignments that are the last use of "y". - var assignedRegs = {}; - var freeRegsByType = copy(allRegsByType); + StringSet assignedRegs = {}; + std::vector freeRegsByType; + freeRegsByType.resize(allRegsByType.size()); + for (int j = 0; j < freeRegsByType.size(); j++) { + for (auto pair : allRegsByType[j]) { + freeRegsByType[j].push_back(pair.first); + } + } // Begin with all live vars assigned per the exit junction. - for (var name in jExit.live) { - var reg = junctionVariables[name].reg; - assert(reg != null, 'output variable doesnt have a register'); + for (auto name : jExit.live) { + IString reg = junctionVariables[name].reg; + assert(!!reg); // 'output variable doesnt have a register'); assignedRegs[name] = reg; - delete freeRegsByType[localVars[name]][reg]; - } - for (var j = 0; j < freeRegsByType.length; j++) { - freeRegsByType[j] = keys(freeRegsByType[j]); + freeRegsByType[localVars[name]].erase(reg); // XXX assert? } // Scan through the nodes in sequence, modifying each node in-place // and grabbing/freeing registers as needed. - var maybeRemoveNodes = []; - for (var j = block.nodes.length - 1; j >= 0; j--) { - var node = block.nodes[j]; - var name = node[0] == ASSIGN ? node[2][1] : node[1]; - var allRegs = allRegsByType[localVars[name]]; - var freeRegs = freeRegsByType[localVars[name]]; - var reg = assignedRegs[name]; + std::vector> maybeRemoveNodes; + for (int j = block->nodes.size() - 1; j >= 0; j--) { + Ref node = block.nodes[j]; + IString name = (node[0] == ASSIGN ? node[2][1] : node[1])->getIString(); + StringStringMap& allRegs = allRegsByType[asmData.getType(name)]; + StringSet& freeRegs = freeRegsByType[asmData.getType(name)]; + IString reg = assignedRegs[name]; if (node[0] == NAME) { // A use. Grab a register if it doesn't have one. if (!reg) { - if (name in inputVars && j <= block.firstDeadLoc[name]) { + if (inputVars.has(name) && j <= block->firstDeadLoc[name]) { // Assignment to an input variable, must use pre-assigned reg. reg = junctionVariables[name].reg; assignedRegs[name] = reg; - for (var k = freeRegs.length - 1; k >= 0; k--) { + for (int k = freeRegs.size() - 1; k >= 0; k--) { if (freeRegs[k] == reg) { - freeRegs.splice(k, 1); + freeRegs.erase(freeRegs.begin() + k); break; } } } else { // Try to use one of the existing free registers. // It must not conflict with an input register. - for (var k = freeRegs.length - 1; k >= 0; k--) { + for (int k = freeRegs.size() - 1; k >= 0; k--) { reg = freeRegs[k]; // Check for conflict with input registers. - if (block.firstKillLoc[name] <= inputDeadLoc[reg]) { + if (block->firstKillLoc[name] <= inputDeadLoc[reg]) { if (name != inputVarsByReg[reg]) { continue; } } // Found one! assignedRegs[name] = reg; - freeRegs.splice(k, 1); + freeRegs.erase(freeRegs.begin() + k); break; } // If we didn't find a suitable register, create a new one. - if (!assignedRegs[name]) { + if (!assignedRegs.has(name)) { reg = createReg(name); assignedRegs[name] = reg; } @@ -3447,23 +3450,23 @@ void registerizeHarder(Ref ast) { node[1] = allRegs[reg]; } else { // A kill. This frees the assigned register. - assert(reg, 'live variable doesnt have a reg?') + assert(!!reg); //, 'live variable doesnt have a reg?') node[2][1] = allRegs[reg]; - freeRegs.push(reg); - delete assignedRegs[name]; - if (node[3][0] == NAME && node[3][1] in localVars) { - maybeRemoveNodes.push([j, node]); + freeRegs.push_back(reg); + assignedRegs.erase(name); + if (node[3][0] == NAME && asmData.isLocal(node[3][1])) { + maybeRemoveNodes.push(std::pair(j, node)); } } } // If we managed to create an "x=x" assignments, remove them. - for (var j = 0; j < maybeRemoveNodes.length; j++) { - var node = maybeRemoveNodes[j][1]; + for (int j = 0; j < maybeRemoveNodes.size(); j++) { + Ref node = maybeRemoveNodes[j].second; if (node[2][1] == node[3][1]) { - if (block.isexpr[maybeRemoveNodes[j][0]]) { - morphNode(node, node[2]); + if (block->isexpr[maybeRemoveNodes[j].first]) { + safeCopy(node, node[2]); } else { - morphNode(node, [BLOCK, []]); + safeCopy(node, makeBlock()); } } } From 1e59231fd3e79913dee4bf6f545b90291c14a61b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 13:39:01 -0800 Subject: [PATCH 23/31] finish first conversion pass on registerizeHarder --- tools/optimizer/optimizer.cpp | 54 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index c744fb6dd9bf1..302318e1fcdb4 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -2484,14 +2484,15 @@ void registerizeHarder(Ref ast) { // Utilities for allocating register variables. // We need distinct register pools for each type of variable. - std::vector allRegsByType; + typedef std::unordered_map IntStringMap; + std::vector allRegsByType; allRegsByType.resize(ASM_NONE+1); int nextReg = 1; auto createReg = [&](IString forName) { // Create a new register of type suitable for the given variable name. AsmType type = asmData.getType(forName); - StringIntMap& allRegs = allRegsByType[type]; + StringStringMap& allRegs = allRegsByType[type]; reg = nextReg++; allRegs[reg] = getRegName(type, reg); return reg; @@ -3474,40 +3475,37 @@ void registerizeHarder(Ref ast) { // Assign registers to function params based on entry junction - var paramRegs = {} - if (fun[2]) { - for (var i = 0; i < fun[2].length; i++) { - var allRegs = allRegsByType[localVars[fun[2][i]]]; - fun[2][i] = allRegs[junctionVariables[fun[2][i]].reg]; - paramRegs[fun[2][i]] = 1; + StringSet paramRegs; + if (!!fun[2]) { + for (int i = 0; i < fun[2]->size(); i++) { + auto& allRegs = allRegsByType[asmData.getType(fun[2][i])]; + fun[2][i]->setString(allRegs[junctionVariables[fun[2][i]].reg]); + paramRegs.insert(fun[2][i]->getIString()); } } // That's it! // Re-construct the function with appropriate variable definitions. - - var finalAsmData = { - params: {}, - vars: {}, - inlines: asmData.inlines, - ret: asmData.ret, - }; - for (var i = 1; i < nextReg; i++) { - var reg; - for (var type=0; type 0) { + IString reg = allRegsByType[type][i]; + if (!paramRegs.has(reg)) { + asmData.addVar(reg, type); + } else { + asmData.addParam(reg, type); + } + break; + } } } - denormalizeAsm(fun, finalAsmData); - - vacuum(fun); + asmData.denormalize(); + removeAllUselessSubNodes(fun); // XXX vacuum? vacuum(fun); }); } */ // end registerizeHarder From e7c01d9f8474a9e2e77e55e41120c09ee8e59c23 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 17:08:24 -0800 Subject: [PATCH 24/31] registerizeHarder cleanups --- tools/js-optimizer.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index a00cbd4392723..b94c2beaa0340 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -2872,7 +2872,7 @@ function registerizeHarder(ast) { function createReg(forName) { // Create a new register of type suitable for the given variable name. var allRegs = allRegsByType[localVars[forName]]; - reg = nextReg++; + var reg = nextReg++; allRegs[reg] = regPrefixByType[localVars[forName]] + reg; return reg; } @@ -3027,7 +3027,7 @@ function registerizeHarder(ast) { // Look through value-preserving casts, like "x | 0" => "x" if (node[0] === 'binary' && node[1] === '|') { if (node[3][0] === 'num' && node[3][1] === 0) { - return lookThroughCasts(node[2]); + return lookThroughCasts(node[2]); } } return node; @@ -3202,7 +3202,7 @@ function registerizeHarder(ast) { joinJunction(jCondExit); joinJunction(jLoop); setJunction(jCondExit); - joinJunction(jExit) + joinJunction(jExit); } break; case 'for': @@ -3273,7 +3273,7 @@ function registerizeHarder(ast) { setJunction(jCheckExit); } joinJunction(jExit); - popActiveLabels() + popActiveLabels(); break; case 'return': if (node[1]) { From ca10f1213557f413a109cc4d42b277591d0c46d3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 17:09:10 -0800 Subject: [PATCH 25/31] update cashew --- tools/optimizer/parser.cpp | 3 ++- tools/optimizer/parser.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/optimizer/parser.cpp b/tools/optimizer/parser.cpp index 4a642a7103b5b..a650f4bd7bb1e 100644 --- a/tools/optimizer/parser.cpp +++ b/tools/optimizer/parser.cpp @@ -82,6 +82,7 @@ IString TOPLEVEL("toplevel"), NEW("new"), ARRAY("array"), OBJECT("object"), + THROW("throw"), SET("="); IStringSet keywords("var function if else do while for break continue return switch case default throw try catch finally true false null new"), @@ -115,7 +116,7 @@ struct Init { precedences.resize(OperatorClass::Tertiary + 1); - for (int prec = 0; prec < operatorClasses.size(); prec++) { + for (size_t prec = 0; prec < operatorClasses.size(); prec++) { for (auto curr : operatorClasses[prec].ops) { precedences[operatorClasses[prec].type][curr] = prec; } diff --git a/tools/optimizer/parser.h b/tools/optimizer/parser.h index 1e822e908d6c4..16fc92df54e16 100644 --- a/tools/optimizer/parser.h +++ b/tools/optimizer/parser.h @@ -91,6 +91,7 @@ extern IString TOPLEVEL, NEW, ARRAY, OBJECT, + THROW, SET; extern IStringSet keywords, allOperators; @@ -203,7 +204,7 @@ class Parser { type = NUMBER; } else if (hasChar(OPERATOR_INITS, *src)) { switch (*src) { - case '!': str = src[1] == '=' ? str = NE : str = L_NOT; break; + case '!': str = src[1] == '=' ? NE : L_NOT; break; case '%': str = MOD; break; case '&': str = AND; break; case '*': str = MUL; break; From b6d37c331ad6c5bf9a0e833ef5a905a434734980 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 17:09:21 -0800 Subject: [PATCH 26/31] get registerizeHarder port to build --- tools/optimizer/optimizer.cpp | 344 ++++++++++++++++++---------------- 1 file changed, 187 insertions(+), 157 deletions(-) diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index 302318e1fcdb4..beca2b08c4475 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "simple_ast.h" @@ -80,6 +81,18 @@ enum AsmType { ASM_NONE = 5 // number of types }; +AsmType intToAsmType(int type) { + switch (type) { + case 0: return ASM_INT; + case 1: return ASM_DOUBLE; + case 2: return ASM_FLOAT; + case 3: return ASM_FLOAT32X4; + case 4: return ASM_INT32X4; + case 5: return ASM_NONE; + default: assert(0); return ASM_NONE; + } +} + // forward decls struct AsmData; AsmType detectType(Ref node, AsmData *asmData=nullptr, bool inVarDef=false); @@ -2468,14 +2481,13 @@ void registerize(Ref ast) { // * any assignments that we can prove are not subsequently used are removed. // (e.g. unnecessary assignments to the 'label' variable). // -/* void registerizeHarder(Ref ast) { traverseFunctions(ast, [](Ref fun) { // Do not try to process non-validating methods, like the heap replacer bool abort = false; - traverse(fun, function(node, type) { - if (type == NEW) abort = true; + traversePre(fun, [&abort](Ref node) { + if (node[0] == NEW) abort = true; }); if (abort) return; @@ -2492,8 +2504,8 @@ void registerizeHarder(Ref ast) { auto createReg = [&](IString forName) { // Create a new register of type suitable for the given variable name. AsmType type = asmData.getType(forName); - StringStringMap& allRegs = allRegsByType[type]; - reg = nextReg++; + IntStringMap& allRegs = allRegsByType[type]; + int reg = nextReg++; allRegs[reg] = getRegName(type, reg); return reg; }; @@ -2525,15 +2537,22 @@ void registerizeHarder(Ref ast) { struct Block { int id, entry, exit; StringSet labels; - std::vector nodes; + std::vector nodes; std::vector isexpr; - StringSet use; + StringIntMap use; StringSet kill; + StringStringMap link; + StringIntMap lastUseLoc; + StringIntMap firstDeadLoc; + StringIntMap firstKillLoc; + StringIntMap lastKillLoc; + Block() : id(-1), entry(-1), exit(-1) {} }; struct ContinueBreak { int co, br; - BreakContinue(int co_, int br_) : co(co_), br(br_) {} + ContinueBreak() : co(-1), br(-1) {} + ContinueBreak(int co_, int br_) : co(co_), br(br_) {} }; typedef std::unordered_map LabelState; @@ -2541,7 +2560,7 @@ void registerizeHarder(Ref ast) { std::vector blocks; int currEntryJunction = -1; Block* nextBasicBlock = nullptr; - bool isInExpr = 0; + int isInExpr = 0; std::vector activeLabels; IString nextLoopLabel; @@ -2557,6 +2576,8 @@ void registerizeHarder(Ref ast) { return id; }; + std::function joinJunction; + auto markJunction = [&](int id=-1) { // Mark current traversal location as a junction. // This makes a new basic block exiting at this position. @@ -2572,7 +2593,7 @@ void registerizeHarder(Ref ast) { // This can be used to enter at a previously-declared point. // You can't return to a junction with no incoming blocks // unless the 'force' parameter is specified. - assert(nextBasicBlock.nodes.size() == 0); // refusing to abandon an in-progress basic block + assert(nextBasicBlock->nodes.size() == 0); // refusing to abandon an in-progress basic block if (force || junctions[id].inblocks.size() > 0) { currEntryJunction = id; } else { @@ -2580,7 +2601,7 @@ void registerizeHarder(Ref ast) { } }; - auto joinJunction = [&](int id, bool force) { + joinJunction = [&](int id, bool force) { // Complete the pending basic block by exiting at this position. // This can be used to exit at a previously-declared point. if (currEntryJunction >= 0) { @@ -2588,8 +2609,8 @@ void registerizeHarder(Ref ast) { nextBasicBlock->id = blocks.size(); nextBasicBlock->entry = currEntryJunction; nextBasicBlock->exit = id; - junctions[currEntryJunction].outblocks.insert(nextBasicBlock.id); - junctions[id].inblocks.insert(nextBasicBlock.id); + junctions[currEntryJunction].outblocks.insert(nextBasicBlock->id); + junctions[id].inblocks.insert(nextBasicBlock->id); blocks.push_back(nextBasicBlock); } nextBasicBlock = new Block(); @@ -2623,19 +2644,19 @@ void registerizeHarder(Ref ast) { activeLabels.pop_back(); }; - auto markNonLocalJump = [&](AsmType type, IString label) { + auto markNonLocalJump = [&](IString type, IString label) { // Complete a block via RETURN, BREAK or CONTINUE. // This joins the targetted junction and then sets the current junction to null. // Any code traversed before we get back an existing junction is dead code. if (type == RETURN) { - joinJunction(EXIT_JUNCTION); + joinJunction(EXIT_JUNCTION, false); } else { assert(activeLabels.back().count(label) > 0); // 'jump to unknown label'); auto targets = activeLabels.back()[label]; if (type == CONTINUE) { - joinJunction(targets.co); + joinJunction(targets.co, false); } else if (type == BREAK) { - joinJunction(targets.br); + joinJunction(targets.br, false); } else { assert(0); // 'unknown jump node type'); } @@ -2646,12 +2667,12 @@ void registerizeHarder(Ref ast) { auto addUseNode = [&](Ref node) { // Mark a use of the given name node in the current basic block. assert(node[0] == NAME); // 'not a use node'); - Ref name = node[1]; + IString name = node[1]->getIString(); if (asmData.isLocal(name)) { nextBasicBlock->nodes.push_back(node); nextBasicBlock->isexpr.push_back(isInExpr); if (nextBasicBlock->kill.count(name) == 0) { - nextBasicBlock->use.insert(name); + nextBasicBlock->use[name] = 1; } } }; @@ -2661,7 +2682,7 @@ void registerizeHarder(Ref ast) { assert(node[0] == ASSIGN); //, 'not a kill node'); assert(node[1]->isBool(true)); // 'not a kill node'); assert(node[2][0] == NAME); //, 'not a kill node'); - Ref name = node[2][1]; + IString name = node[2][1]->getIString(); if (asmData.isLocal(name)) { nextBasicBlock->nodes.push_back(node); nextBasicBlock->isexpr.push_back(isInExpr); @@ -2669,11 +2690,11 @@ void registerizeHarder(Ref ast) { } }; - auto lookThroughCasts = [&](Ref node) { + std::function lookThroughCasts = [&](Ref node) { // Look through value-preserving casts, like "x | 0" => "x" if (node[0] == BINARY && node[1] == OR) { - if (node[3][0] == NUM && node[3][1] == 0) { - return lookThroughCasts(node[2]); + if (node[3][0] == NUM && node[3][1]->getNumber() == 0) { + return lookThroughCasts(node[2]); } } return node; @@ -2682,7 +2703,7 @@ void registerizeHarder(Ref ast) { auto addBlockLabel = [&](Ref node) { assert(nextBasicBlock->nodes.size() == 0); // 'cant add label to an in-progress basic block') if (node[0] == NUM) { - nextBasicBlock->labels.insert(node[1]); // XXX? + nextBasicBlock->labels.insert(node[1]->getIString()); // XXX? } }; @@ -2716,10 +2737,10 @@ void registerizeHarder(Ref ast) { assert(jEntry == ENTRY_JUNCTION); int jExit = addJunction(); assert(jExit == EXIT_JUNCTION); - for (int i = 0; i < node[3].length; i++) { + for (int i = 0; i < node[3]->size(); i++) { buildFlowGraph(node[3][i]); } - joinJunction(jExit); + joinJunction(jExit, false); } else if (type == IF) { isInExpr++; buildFlowGraph(node[1]); @@ -2736,12 +2757,12 @@ void registerizeHarder(Ref ast) { } buildFlowGraph(node[2]); } - joinJunction(jExit); - setJunction(jEnter); + joinJunction(jExit, false); + setJunction(jEnter, false); if (!!node[3]) { buildFlowGraph(node[3]); } - joinJunction(jExit); + joinJunction(jExit, false); } else if (type == CONDITIONAL) { isInExpr++; // If the conditional has no side-effects, we can treat it as a single @@ -2761,12 +2782,12 @@ void registerizeHarder(Ref ast) { if (!!node[2]) { buildFlowGraph(node[2]); } - joinJunction(jExit); - setJunction(jEnter); + joinJunction(jExit, false); + setJunction(jEnter, false); if (!!node[3]) { buildFlowGraph(node[3]); } - joinJunction(jExit); + joinJunction(jExit, false); } isInExpr--; } else if (type == WHILE) { @@ -2778,8 +2799,8 @@ void registerizeHarder(Ref ast) { pushActiveLabels(jLoop, jExit); buildFlowGraph(node[2]); popActiveLabels(); - joinJunction(jLoop); - setJunction(jExit); + joinJunction(jLoop, false); + setJunction(jExit, false); } else { int jCond = markJunction(); int jLoop = addJunction(); @@ -2787,14 +2808,14 @@ void registerizeHarder(Ref ast) { isInExpr++; buildFlowGraph(node[1]); isInExpr--; - joinJunction(jLoop); + joinJunction(jLoop, false); pushActiveLabels(jCond, jExit); buildFlowGraph(node[2]); popActiveLabels(); - joinJunction(jCond); + joinJunction(jCond, false); // An empty basic-block linking condition exit to loop exit. - setJunction(jLoop); - joinJunction(jExit); + setJunction(jLoop, false); + joinJunction(jExit, false); } } else if (type == DO) { // Special-case "do {} while (1)" and "do {} while (0)" to use @@ -2804,15 +2825,15 @@ void registerizeHarder(Ref ast) { pushActiveLabels(jExit, jExit); buildFlowGraph(node[2]); popActiveLabels(); - joinJunction(jExit); + joinJunction(jExit, false); } else if (isTrueNode(node[1])) { int jLoop = markJunction(); int jExit = addJunction(); pushActiveLabels(jLoop, jExit); buildFlowGraph(node[2]); popActiveLabels(); - joinJunction(jLoop); - setJunction(jExit); + joinJunction(jLoop, false); + setJunction(jExit, false); } else { int jLoop = markJunction(); int jCond = addJunction(); @@ -2821,14 +2842,14 @@ void registerizeHarder(Ref ast) { pushActiveLabels(jCond, jExit); buildFlowGraph(node[2]); popActiveLabels(); - joinJunction(jCond); + joinJunction(jCond, false); isInExpr++; buildFlowGraph(node[1]); isInExpr--; - joinJunction(jCondExit); - joinJunction(jLoop); - setJunction(jCondExit); - joinJunction(jExit) + joinJunction(jCondExit, false); + joinJunction(jLoop, false); + setJunction(jCondExit, false); + joinJunction(jExit, false); } } else if (type == FOR) { int jTest = addJunction(); @@ -2836,22 +2857,22 @@ void registerizeHarder(Ref ast) { int jStep = addJunction(); int jExit = addJunction(); buildFlowGraph(node[1]); - joinJunction(jTest); + joinJunction(jTest, false); isInExpr++; buildFlowGraph(node[2]); isInExpr--; - joinJunction(jBody); + joinJunction(jBody, false); pushActiveLabels(jStep, jExit); buildFlowGraph(node[4]); popActiveLabels(); - joinJunction(jStep); + joinJunction(jStep, false); buildFlowGraph(node[3]); - joinJunction(jTest); - setJunction(jBody); - joinJunction(jExit); + joinJunction(jTest, false); + setJunction(jBody, false); + joinJunction(jExit, false); } else if (type == LABEL) { assert(BREAK_CAPTURERS.has(node[2][0])); // 'label on non-loop, non-switch statement') - nextLoopLabel = node[1]; + nextLoopLabel = node[1]->getIString(); buildFlowGraph(node[2]); } else if (type == SWITCH) { // Emscripten generates switch statements of a very limited @@ -2867,7 +2888,7 @@ void registerizeHarder(Ref ast) { pushActiveLabels(-1, jExit); bool hasDefault = false; for (int i = 0; i < node[2]->size(); i++) { - setJunction(jCheckExit); + setJunction(jCheckExit, false); // All case clauses are either 'default' or a numeric literal. if (!node[2][i][0]) { hasDefault = true; @@ -2884,28 +2905,28 @@ void registerizeHarder(Ref ast) { // If there's live code here, assume it jumps to case exit. if (currEntryJunction >= 0 && nextBasicBlock->nodes.size() > 0) { if (!!node[2][i][0]) { - markNonLocalJump(RETURN); + markNonLocalJump(RETURN, IString()); } else { - joinJunction(jExit); + joinJunction(jExit, false); } } } // If there was no default case, we also need an empty block // linking straight from the test evaluation to the exit. if (!hasDefault) { - setJunction(jCheckExit); + setJunction(jCheckExit, false); } - joinJunction(jExit); - popActiveLabels() + joinJunction(jExit, false); + popActiveLabels(); } else if (type == RETURN) { if (!!node[1]) { isInExpr++; buildFlowGraph(node[1]); isInExpr--; } - markNonLocalJump(type); + markNonLocalJump(type->getIString(), IString()); } else if (type == BREAK || type == CONTINUE) { - markNonLocalJump(type, node[1]); + markNonLocalJump(type->getIString(), node[1]->getIString()); } else if (type == ASSIGN) { isInExpr++; buildFlowGraph(node[3]); @@ -2947,10 +2968,10 @@ void registerizeHarder(Ref ast) { // treat it as a jump to function exit. if (!isInExpr && node[1][0] == NAME) { if (FUNCTIONS_THAT_ALWAYS_THROW.has(node[1][1])) { - markNonLocalJump(RETURN); + markNonLocalJump(RETURN, IString()); } } - } else if (type == SEQ || type == SUB) {: + } else if (type == SEQ || type == SUB) { isInExpr++; buildFlowGraph(node[1]); buildFlowGraph(node[2]); @@ -2970,7 +2991,7 @@ void registerizeHarder(Ref ast) { assert(junctions[ENTRY_JUNCTION].inblocks.size() == 0); // 'function entry must have no incoming blocks'); assert(junctions[EXIT_JUNCTION].outblocks.size() == 0); // 'function exit must have no outgoing blocks'); - assert(blocks[ENTRY_BLOCK].entry == ENTRY_JUNCTION); //, 'block zero must be the initial block'); + assert(blocks[ENTRY_BLOCK]->entry == ENTRY_JUNCTION); //, 'block zero must be the initial block'); // Fix up implicit jumps done by assigning to the LABEL variable. // If a block ends with an assignment to LABEL and there's another block @@ -2978,9 +2999,9 @@ void registerizeHarder(Ref ast) { // that the former jumps straight to the later. std::unordered_map labelledBlocks; - typedef std::pair(Ref, Block*) Jump; + typedef std::pair Jump; std::vector labelledJumps; - FINDLABELLEDBLOCKS: + for (int i = 0; i < blocks.size(); i++) { Block* block = blocks[i]; // Does it have any labels as preconditions to its entry? @@ -2991,12 +3012,12 @@ void registerizeHarder(Ref ast) { if (labelledBlocks.count(labelVal) > 0) { labelledBlocks.clear(); labelledJumps.clear(); - break FINDLABELLEDBLOCKS; + goto AFTER_FINDLABELLEDBLOCKS; } labelledBlocks[labelVal] = block; } // Does it assign a specific label value at exit? - if (block.kill.has(LABEL)) { + if (block->kill.has(LABEL)) { Ref finalNode = block->nodes.back(); if (finalNode[0] == ASSIGN && finalNode[2][1] == LABEL) { // If labels are computed dynamically then all bets are off. @@ -3004,46 +3025,49 @@ void registerizeHarder(Ref ast) { if (finalNode[3][0] != NUM) { labelledBlocks.clear(); labelledJumps.clear(); - break FINDLABELLEDBLOCKS; + goto AFTER_FINDLABELLEDBLOCKS; } - labelledJumps.push(Jump(finalNode[3][1], block)); + labelledJumps.push_back(Jump(finalNode[3][1], block)); } else { // If label is assigned a non-zero value elsewhere in the block // then all bets are off. This can happen e.g. due to outlining // saving/restoring label to the stack. for (int j = 0; j < block->nodes.size() - 1; j++) { - if (block.nodes[j][0] == ASSIGN && block.nodes[j][2][1] == LABEL) { - if (block.nodes[j][3][0] != NUM && block.nodes[j][3][1]->getNumber() != 0) { + if (block->nodes[j][0] == ASSIGN && block->nodes[j][2][1] == LABEL) { + if (block->nodes[j][3][0] != NUM && block->nodes[j][3][1]->getNumber() != 0) { labelledBlocks.clear(); labelledJumps.clear(); - break FINDLABELLEDBLOCKS; + goto AFTER_FINDLABELLEDBLOCKS; } } } } } } + + AFTER_FINDLABELLEDBLOCKS: + for (auto labelVal : labelledBlocks) { Block* block = labelVal.second; // Disconnect it from the graph, and create a // new junction for jumps targetting this label. - junctions[block.entry].outblocks.erase(block.id); - block.entry = addJunction(); - junctions[block.entry].outblocks.insert(block.id); + junctions[block->entry].outblocks.erase(block->id); + block->entry = addJunction(); + junctions[block->entry].outblocks.insert(block->id); // Add a fake use of LABEL to keep it alive in predecessor. - block.use.insert(LABEL); - block.nodes.insert(block.nodes.begin(), makeName(LABEL)); - block.isexpr.insert(block.isexpr(), 1); + block->use[LABEL] = 1; + block->nodes.insert(block->nodes.begin(), makeName(LABEL)); + block->isexpr.insert(block->isexpr.begin(), 1); } for (int i = 0; i < labelledJumps.size(); i++) { auto labelVal = labelledJumps[i].first; auto block = labelledJumps[i].second; - Block* targetBlock = labelledBlocks[labelVal]; + Block* targetBlock = labelledBlocks[labelVal->getIString()]; if (targetBlock) { // Redirect its exit to entry of the target block. junctions[block->exit].inblocks.erase(block->id); block->exit = targetBlock->entry; - junctions[block->exit].inblocks.inasert(block->id); + junctions[block->exit].inblocks.insert(block->id); } } @@ -3056,8 +3080,7 @@ void registerizeHarder(Ref ast) { auto analyzeJunction = [&](Junction& junc) { // Update the live set for this junction. - StringSet& live = junc.live; - std::unordered_set live; + StringSet live; for (auto b : junc.outblocks) { Block* block = blocks[b]; StringSet& liveSucc = junctions[block->exit].live; @@ -3067,9 +3090,10 @@ void registerizeHarder(Ref ast) { } } for (auto name : block->use) { - live.insert(name); + live.insert(name.first); } } + junc.live = live; }; auto analyzeBlock = [&](Block* block) { @@ -3079,8 +3103,8 @@ void registerizeHarder(Ref ast) { // to exit, possibly changing names via simple 'x=y' assignments. // As we go, we eliminate assignments if the variable is not // subsequently used. - auto live = junctions[block.exit].live; - StringSet use; + auto live = junctions[block->exit].live; + StringIntMap use; StringSet kill; StringStringMap link; StringIntMap lastUseLoc; @@ -3124,8 +3148,8 @@ void registerizeHarder(Ref ast) { IString oldLink = link[name]; link.erase(name); if (node[3][0] == NAME) { - if (asmData.isLocal(node[3][1])) { - link[node[3][1]] = oldLink; + if (asmData.isLocal(node[3][1]->getIString())) { + link[node[3][1]->getIString()] = oldLink; } } } @@ -3133,32 +3157,32 @@ void registerizeHarder(Ref ast) { // The result of this assignment is never used, so delete it. // We may need to keep the RHS for its value or its side-effects. auto removeUnusedNodes = [&](int j, int n) { - for (auto name : lastUseLoc) { - lastUseLoc[name] -= n; + for (auto pair : lastUseLoc) { + pair.second -= n; } - for (auto name : firstKillLoc) { - firstKillLoc[name] -= n; + for (auto pair : firstKillLoc) { + pair.second -= n; } - for (auto name : lastKillLoc) { - lastKillLoc[name] -= n; + for (auto pair : lastKillLoc) { + pair.second -= n; } - for (auto name : firstDeadLoc) { - firstDeadLoc[name] -= n; + for (auto pair : firstDeadLoc) { + pair.second -= n; } block->nodes.erase(block->nodes.begin() + j, block->nodes.begin() + j + n); block->isexpr.erase(block->isexpr.begin() + j, block->isexpr.begin() + j + n); - } - if (block.isexpr[j] || hasSideEffects(node[3])) { + }; + if (block->isexpr[j] || hasSideEffects(node[3])) { safeCopy(node, node[3]); removeUnusedNodes(j, 1); } else { int numUsesInExpr = 0; traversePre(node[3], [&](Ref node) { - if (node[0] == NAME && asmData.isLocal(node[1])) { + if (node[0] == NAME && asmData.isLocal(node[1]->getIString())) { numUsesInExpr++; } }); - morphNode(node, makeBlock()); + safeCopy(node, makeBlock()); j = j - numUsesInExpr; removeUnusedNodes(j, 1 + numUsesInExpr); } @@ -3166,13 +3190,13 @@ void registerizeHarder(Ref ast) { } } // XXX efficiency - block.use = use; - block.kill = kill; - block.link = link; - block.lastUseLoc = lastUseLoc; - block.firstDeadLoc = firstDeadLoc; - block.firstKillLoc = firstKillLoc; - block.lastKillLoc = lastKillLoc; + block->use = use; + block->kill = kill; + block->link = link; + block->lastUseLoc = lastUseLoc; + block->firstDeadLoc = firstDeadLoc; + block->firstKillLoc = firstKillLoc; + block->lastKillLoc = lastKillLoc; }; std::unordered_set jWorklistMap; @@ -3204,10 +3228,10 @@ void registerizeHarder(Ref ast) { // Live set changed, updated predecessor blocks and junctions. for (auto b : junc.inblocks) { if (bWorklistMap.count(b) == 0) { - bWorklistMap.insert(b) + bWorklistMap.insert(b); bWorklist.push_back(b); } - int jPred = blocks[b].entry; + int jPred = blocks[b]->entry; if (jWorklistMap.count(jPred) == 0) { jWorklistMap.insert(jPred); jWorklist.push_back(jPred); @@ -3220,13 +3244,13 @@ void registerizeHarder(Ref ast) { Block* block = blocks[bWorklist.back()]; bWorklist.pop_back(); bWorklistMap.erase(block->id); - StringSet oldUse = block->use; + auto oldUse = block->use; analyzeBlock(block); if (oldUse != block->use) { // The use set changed, re-process the entry junction. if (jWorklistMap.count(block->entry) == 0) { - jWorklistMap.insert(block.entry); - jWorklist.push_back(block.entry); + jWorklistMap.insert(block->entry); + jWorklist.push_back(block->entry); } } } @@ -3247,8 +3271,10 @@ void registerizeHarder(Ref ast) { // (the "links"). struct JuncVar { - StringSet conf, link, excl; - IString reg; + StringSet conf, link; + std::unordered_set excl; + int reg; + JuncVar() : reg(-1) {} }; std::unordered_map junctionVariables; @@ -3270,7 +3296,7 @@ void registerizeHarder(Ref ast) { // if they're assigned before it goes dead in that block. Block* block = blocks[b]; Junction& jSucc = junctions[block->exit]; - for (jSucc.live.has(otherName)) { + for (auto otherName : jSucc.live) { if (junc.live.has(otherName)) continue; if (block->lastKillLoc[otherName] < block->firstDeadLoc[name]) { if (junctionVariables.count(otherName) == 0) initializeJunctionVariable(otherName); @@ -3295,9 +3321,9 @@ void registerizeHarder(Ref ast) { StringVec sortedJunctionVariables; for (auto pair : junctionVariables) { - sortedJunctionVariables.push_back(pair->first); + sortedJunctionVariables.push_back(pair.first); } - std::sort(sortedJunctionVariables.begin(), sortedJunctionVariables.end(), [](const IString name1, const IString name2) { + std::sort(sortedJunctionVariables.begin(), sortedJunctionVariables.end(), [&](const IString name1, const IString name2) { return junctionVariables[name2].conf.size() < junctionVariables[name1].conf.size(); //XXX }); @@ -3306,15 +3332,15 @@ void registerizeHarder(Ref ast) { // one that works, and propagating the choice to linked/conflicted // variables as we go. - auto tryAssignRegister = [&](IString name, IString reg) { + std::function tryAssignRegister = [&](IString name, int reg) { // Try to assign the given register to the given variable, // and propagate that choice throughout the graph. // Returns true if successful, false if there was a conflict. JuncVar& jv = junctionVariables[name]; - if (!!jv.reg) { + if (jv.reg > 0) { return jv.reg == reg; } - if (jv.excl.has(reg)) { + if (jv.excl.count(reg) > 0) { return false; } jv.reg = reg; @@ -3330,20 +3356,22 @@ void registerizeHarder(Ref ast) { return true; }; - NEXTVARIABLE: for (int i = 0; i < sortedJunctionVariables.size(); i++) { IString name = sortedJunctionVariables[i]; // It may already be assigned due to linked-variable propagation. if (!!junctionVariables[name].reg) { - continue NEXTVARIABLE; + continue; } // Try to use existing registers first. auto& allRegs = allRegsByType[asmData.getType(name)]; + bool moar = false; for (auto reg : allRegs) { - if (tryAssignRegister(name, reg)) { - continue NEXTVARIABLE; + if (tryAssignRegister(name, reg.first)) { + moar = true; + break; } } + if (moar) continue; // They're all taken, create a new one. tryAssignRegister(name, createReg(name)); } @@ -3357,28 +3385,29 @@ void registerizeHarder(Ref ast) { for (int i = 0; i < blocks.size(); i++) { Block* block = blocks[i]; if (block->nodes.size() == 0) continue; - int jEnter = junctions[block->entry]; - int jExit = junctions[block->exit]; + Junction& jEnter = junctions[block->entry]; + Junction& jExit = junctions[block->exit]; // Mark the point at which each input reg becomes dead. // Variables alive before this point must not be assigned // to that register. StringSet inputVars; - std::unordered_set inputDeadLoc; - StringSet inputVarsByReg; + std::unordered_map inputDeadLoc; + std::unordered_map inputVarsByReg; for (auto name : jExit.live) { if (!block->kill.has(name)) { inputVars.insert(name); - IString reg = junctionVariables[name].reg; - assert(!!reg); // 'input variable doesnt have a register'); + int reg = junctionVariables[name].reg; + assert(reg > 0); // 'input variable doesnt have a register'); inputDeadLoc[reg] = block->firstDeadLoc[name]; inputVarsByReg[reg] = name; } } - for (auto name : block->use) { + for (auto pair : block->use) { + IString name = pair.first; if (!inputVars.has(name)) { inputVars.insert(name); - IString reg = junctionVariables[name].reg; - assert(!!reg); // 'input variable doesnt have a register'); + int reg = junctionVariables[name].reg; + assert(reg > 0); // 'input variable doesnt have a register'); inputDeadLoc[reg] = block->firstDeadLoc[name]; inputVarsByReg[reg] = name; } @@ -3388,30 +3417,31 @@ void registerizeHarder(Ref ast) { // Be careful to avoid conflicts with the input registers. // We consume free registers in last-used order, which helps to // eliminate "x=y" assignments that are the last use of "y". - StringSet assignedRegs = {}; - std::vector freeRegsByType; - freeRegsByType.resize(allRegsByType.size()); - for (int j = 0; j < freeRegsByType.size(); j++) { - for (auto pair : allRegsByType[j]) { - freeRegsByType[j].push_back(pair.first); - } - } + StringIntMap assignedRegs; + auto freeRegsByTypePre = allRegsByType; // XXX copy // Begin with all live vars assigned per the exit junction. for (auto name : jExit.live) { - IString reg = junctionVariables[name].reg; - assert(!!reg); // 'output variable doesnt have a register'); + int reg = junctionVariables[name].reg; + assert(reg > 0); // 'output variable doesnt have a register'); assignedRegs[name] = reg; - freeRegsByType[localVars[name]].erase(reg); // XXX assert? + freeRegsByTypePre[asmData.getType(name)].erase(reg); // XXX assert? + } + std::vector> freeRegsByType; + freeRegsByType.resize(freeRegsByTypePre.size()); + for (int j = 0; j < freeRegsByTypePre.size(); j++) { + for (auto pair : freeRegsByTypePre[j]) { + freeRegsByType[j].push_back(pair.first); + } } // Scan through the nodes in sequence, modifying each node in-place // and grabbing/freeing registers as needed. std::vector> maybeRemoveNodes; for (int j = block->nodes.size() - 1; j >= 0; j--) { - Ref node = block.nodes[j]; + Ref node = block->nodes[j]; IString name = (node[0] == ASSIGN ? node[2][1] : node[1])->getIString(); - StringStringMap& allRegs = allRegsByType[asmData.getType(name)]; - StringSet& freeRegs = freeRegsByType[asmData.getType(name)]; - IString reg = assignedRegs[name]; + IntStringMap& allRegs = allRegsByType[asmData.getType(name)]; + std::vector& freeRegs = freeRegsByType[asmData.getType(name)]; + int reg = assignedRegs[name]; // XXX may insert a zero if (node[0] == NAME) { // A use. Grab a register if it doesn't have one. if (!reg) { @@ -3448,15 +3478,15 @@ void registerizeHarder(Ref ast) { } } } - node[1] = allRegs[reg]; + node[1]->setString(allRegs[reg]); } else { // A kill. This frees the assigned register. assert(!!reg); //, 'live variable doesnt have a reg?') - node[2][1] = allRegs[reg]; + node[2][1]->setString(allRegs[reg]); freeRegs.push_back(reg); assignedRegs.erase(name); - if (node[3][0] == NAME && asmData.isLocal(node[3][1])) { - maybeRemoveNodes.push(std::pair(j, node)); + if (node[3][0] == NAME && asmData.isLocal(node[3][1]->getIString())) { + maybeRemoveNodes.push_back(std::pair(j, node)); } } } @@ -3478,8 +3508,8 @@ void registerizeHarder(Ref ast) { StringSet paramRegs; if (!!fun[2]) { for (int i = 0; i < fun[2]->size(); i++) { - auto& allRegs = allRegsByType[asmData.getType(fun[2][i])]; - fun[2][i]->setString(allRegs[junctionVariables[fun[2][i]].reg]); + auto& allRegs = allRegsByType[asmData.getType(fun[2][i]->getIString())]; + fun[2][i]->setString(allRegs[junctionVariables[fun[2][i]->getIString()].reg]); paramRegs.insert(fun[2][i]->getIString()); } } @@ -3495,9 +3525,9 @@ void registerizeHarder(Ref ast) { if (allRegsByType[type].count(i) > 0) { IString reg = allRegsByType[type][i]; if (!paramRegs.has(reg)) { - asmData.addVar(reg, type); + asmData.addVar(reg, intToAsmType(type)); } else { - asmData.addParam(reg, type); + asmData.addParam(reg, intToAsmType(type)); } break; } @@ -3508,7 +3538,7 @@ void registerizeHarder(Ref ast) { removeAllUselessSubNodes(fun); // XXX vacuum? vacuum(fun); }); } -*/ // end registerizeHarder +// end registerizeHarder // minified names generation StringSet RESERVED("do if in for new try var env let"); From 9d50d2f267d5b88c40b01c608a1f1eae3000e7ba Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 11 Dec 2014 18:24:04 -0800 Subject: [PATCH 27/31] make registerizeHarder testcase valid asm.js --- tools/test-js-optimizer-asm-regs-harder-output.js | 2 +- tools/test-js-optimizer-asm-regs-harder.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/test-js-optimizer-asm-regs-harder-output.js b/tools/test-js-optimizer-asm-regs-harder-output.js index c3b326f61feef..25e4e2db085df 100644 --- a/tools/test-js-optimizer-asm-regs-harder-output.js +++ b/tools/test-js-optimizer-asm-regs-harder-output.js @@ -116,7 +116,7 @@ function linkedVars() { while (1) { i2 = 9; i1 = 5; - while (i2 > 0 || i1 > 0) { + while (i2 > 0 | i1 > 0) { if (i2 < i1) { i2 = i2 - 1; } else { diff --git a/tools/test-js-optimizer-asm-regs-harder.js b/tools/test-js-optimizer-asm-regs-harder.js index fa72aab8135a2..faa2845920d62 100644 --- a/tools/test-js-optimizer-asm-regs-harder.js +++ b/tools/test-js-optimizer-asm-regs-harder.js @@ -108,7 +108,7 @@ function iffey() { } function labelledJump(x) { x = x | 0; - var label = 0 + var label = 0; // y and z don't conflict, but only if you know about labelled jumps. var y = 0, z = 0; y = 2; @@ -129,7 +129,7 @@ function linkedVars() { while (1) { outer1 = 9; outer2 = 5; - while (outer1 > 0 || outer2 > 0) { + while ((outer1 > 0) | (outer2 > 0)) { // All these copy assignment should be eliminated by var sharing. inner1_0 = outer1; inner2_0 = outer2; From b6e20f6d60cb6157c29afcd86d536ec72fb7604b Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Dec 2014 10:37:57 -0800 Subject: [PATCH 28/31] fix sanity.test_closure_compiler --- tests/test_sanity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sanity.py b/tests/test_sanity.py index 24b352be3778f..02c0954bed90d 100644 --- a/tests/test_sanity.py +++ b/tests/test_sanity.py @@ -149,12 +149,12 @@ def test_closure_compiler(self): f = open(CONFIG_FILE, 'a') f.write('CLOSURE_COMPILER = "/tmp/nowhere/nothingtoseehere/kjadsfkjwelkjsdfkqgas/nonexistent.txt"\n') f.close() - output = self.check_working([EMCC, '-O2', '-s', 'ASM_JS=0', '--closure', '1', 'tests/hello_world.cpp'], CLOSURE_FATAL) + output = self.check_working([EMCC, '-O2', '-s', '--closure', '1', 'tests/hello_world.cpp'], CLOSURE_FATAL) # With a working path, all is well restore() try_delete('a.out.js') - output = self.check_working([EMCC, '-O2', '-s', 'ASM_JS=0', '--closure', '1', 'tests/hello_world.cpp'], '') + output = self.check_working([EMCC, '-O2', '-s', '--closure', '1', 'tests/hello_world.cpp'], '') assert os.path.exists('a.out.js'), output def test_llvm(self): From a89a6711ddba719925c892471734fcc2a3b480f1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Dec 2014 10:42:18 -0800 Subject: [PATCH 29/31] remove some non-asm tests from other.test_emcc --- tests/test_other.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_other.py b/tests/test_other.py index 6b5807f05ee9e..e88ff62376f10 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -118,13 +118,13 @@ def test_emcc(self): (['-o', 'something.js', '-O2'], 2, None, 0, 1), (['-o', 'something.js', '-O2', '-g'], 2, None, 0, 0), (['-o', 'something.js', '-Os'], 2, None, 0, 1), - (['-o', 'something.js', '-O3', '-s', 'ASM_JS=0'], 3, None, 0, 1), + (['-o', 'something.js', '-O3'], 3, None, 0, 1), # and, test compiling to bitcode first (['-o', 'something.bc'], 0, [], 0, 0), (['-o', 'something.bc', '-O0'], 0, [], 0, 0), (['-o', 'something.bc', '-O1'], 1, ['-O1'], 0, 0), (['-o', 'something.bc', '-O2'], 2, ['-O2'], 0, 0), - (['-o', 'something.bc', '-O3'], 3, ['-O3', '-s', 'ASM_JS=0'], 0, 0), + (['-o', 'something.bc', '-O3'], 3, ['-O3'], 0, 0), (['-O1', '-o', 'something.bc'], 1, [], 0, 0), ]: print params, opt_level, bc_params, closure, has_malloc From d84aa198c20dc6d0c5ff247a10c3fd95f38cb0ca Mon Sep 17 00:00:00 2001 From: Jukka Jylanki Date: Fri, 12 Dec 2014 22:18:26 +0200 Subject: [PATCH 30/31] Remove timing dependency on browser.test_glgears_proxy by rendering a static nonanimated version of the gears in each rAF. --- tests/hello_world_gles_proxy.c | 9 +++++++++ tests/test_browser.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/hello_world_gles_proxy.c b/tests/hello_world_gles_proxy.c index 1a8e5f61da278..7e977fed64d0b 100644 --- a/tests/hello_world_gles_proxy.c +++ b/tests/hello_world_gles_proxy.c @@ -40,6 +40,7 @@ #define _GNU_SOURCE +#include #include #include #include @@ -613,11 +614,19 @@ gears_idle(void) static double tRot0 = -1.0, tRate0 = -1.0; double dt, t = glutGet(GLUT_ELAPSED_TIME) / 1000.0; +#ifdef STATIC_GEARS + assert(t - tRot0 > 0); // Test that time does advance even though we are rendering a static image. +#endif + if (tRot0 < 0.0) tRot0 = t; dt = t - tRot0; tRot0 = t; +#ifdef STATIC_GEARS + dt = t = 0.0; // Render the gears in a static position. +#endif + /* advance rotation for next frame */ angle += 70.0 * dt; /* 70 degrees per second */ if (angle > 3600.0) diff --git a/tests/test_browser.py b/tests/test_browser.py index 51f257412a6a4..ddd4849ac6dbb 100644 --- a/tests/test_browser.py +++ b/tests/test_browser.py @@ -758,7 +758,7 @@ def test_sdl_canvas_proxy(self): self.btest('sdl_canvas_proxy.c', reference='sdl_canvas_proxy.png', args=['--proxy-to-worker', '--preload-file', 'data.txt'], manual_reference=True, post_build=self.post_manual_reftest) def test_glgears_proxy(self): - self.btest('hello_world_gles_proxy.c', reference='gears.png', args=['--proxy-to-worker', '-s', 'GL_TESTING=1'], manual_reference=True, post_build=self.post_manual_reftest) + self.btest('hello_world_gles_proxy.c', reference='gears.png', args=['--proxy-to-worker', '-s', 'GL_TESTING=1', '-DSTATIC_GEARS=1'], manual_reference=True, post_build=self.post_manual_reftest) # test noProxy option applied at runtime From ed94894b305bb09808862523417821d3b26c8656 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 12 Dec 2014 15:48:49 -0800 Subject: [PATCH 31/31] 1.28.0 --- emscripten-version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emscripten-version.txt b/emscripten-version.txt index 362c239d66da5..b1a41036cde93 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1,2 +1,2 @@ -1.27.2 +1.28.0