diff options
author | Alon Zakai <alonzakai@gmail.com> | 2015-11-13 22:12:58 -0800 |
---|---|---|
committer | Alon Zakai <alonzakai@gmail.com> | 2015-11-13 22:12:58 -0800 |
commit | 749cd728ee33223423055aa02baeaeae61eb90a4 (patch) | |
tree | 2929628b53f47c8af7e935ee9f1c57b8f25e80b5 /src/emscripten-optimizer/optimizer.cpp | |
parent | f08d6fbd7c24dc9dc07081c72869095dab3647ea (diff) | |
download | binaryen-749cd728ee33223423055aa02baeaeae61eb90a4.tar.gz binaryen-749cd728ee33223423055aa02baeaeae61eb90a4.tar.bz2 binaryen-749cd728ee33223423055aa02baeaeae61eb90a4.zip |
remove unneeded emscripten optimizer.cpp file
Diffstat (limited to 'src/emscripten-optimizer/optimizer.cpp')
-rw-r--r-- | src/emscripten-optimizer/optimizer.cpp | 3851 |
1 files changed, 0 insertions, 3851 deletions
diff --git a/src/emscripten-optimizer/optimizer.cpp b/src/emscripten-optimizer/optimizer.cpp deleted file mode 100644 index d569fb928..000000000 --- a/src/emscripten-optimizer/optimizer.cpp +++ /dev/null @@ -1,3851 +0,0 @@ -#include <cstdint> -#include <cstdio> -#include <cmath> -#include <string> -#include <algorithm> -#include <map> - -#include "simple_ast.h" -#include "optimizer.h" - -#include "optimizer-shared.cpp" - -typedef std::vector<IString> StringVec; - -//================== -// Globals -//================== - -Ref extraInfo; - -//================== -// Infrastructure -//================== - -template<class T, class V> -int indexOf(T list, V value) { - for (size_t i = 0; i < list.size(); i++) { - if (list[i] == value) return i; - } - return -1; -} - -int jsD2I(double x) { - return (int)((int64_t)x); -} - -char *strdupe(const char *str) { - char *ret = (char *)malloc(strlen(str)+1); // leaked! - strcpy(ret, str); - return ret; -} - -IString getHeapStr(int x, bool unsign) { - switch (x) { - case 8: return unsign ? HEAPU8 : HEAP8; - case 16: return unsign ? HEAPU16 : HEAP16; - case 32: return unsign ? HEAPU32 : HEAP32; - } - assert(0); - return ":("; -} - -Ref deStat(Ref node) { - if (node[0] == STAT) return node[1]; - return node; -} - -Ref getStatements(Ref node) { - if (node[0] == DEFUN) { - return node[3]; - } else if (node[0] == BLOCK) { - return node->size() > 1 ? node[1] : nullptr; - } else { - return arena.alloc(); - } -} - -// Types - -AsmType intToAsmType(int type) { - if (type >= 0 && type <= ASM_NONE) return (AsmType)type; - else { - assert(0); - return ASM_NONE; - } -} - -// forward decls -Ref makeEmpty(); -bool isEmpty(Ref node); -Ref makeAsmVarDef(const IString& v, AsmType type); -Ref makeArray(int size_hint); -Ref makeBool(bool b); -Ref makeNum(double x); -Ref makeName(IString str); -Ref makeAsmCoercion(Ref node, AsmType type); -Ref make1(IString type, Ref a); -Ref make3(IString type, Ref a, Ref b, Ref c); - -AsmData::AsmData(Ref f) { - func = f; - - // process initial params - Ref stats = func[3]; - size_t i = 0; - while (i < stats->size()) { - Ref node = stats[i]; - if (node[0] != STAT || node[1][0] != ASSIGN || node[1][2][0] != NAME) break; - node = node[1]; - Ref name = node[2][1]; - int index = func[2]->indexOf(name); - if (index < 0) break; // not an assign into a parameter, but a global - IString& str = name->getIString(); - if (locals.count(str) > 0) break; // already done that param, must be starting function body - locals[str] = Local(detectType(node[3]), true); - params.push_back(str); - stats[i] = makeEmpty(); - i++; - } - // process initial variable definitions and remove '= 0' etc parts - these - // are not actually assignments in asm.js - while (i < stats->size()) { - Ref node = stats[i]; - if (node[0] != VAR) break; - for (size_t j = 0; j < node[1]->size(); j++) { - Ref v = node[1][j]; - IString& name = v[0]->getIString(); - Ref value = v[1]; - if (locals.count(name) == 0) { - locals[name] = Local(detectType(value, nullptr, true), false); - vars.push_back(name); - v->setSize(1); // make an un-assigning var - } else { - assert(j == 0); // cannot break in the middle - goto outside; - } - } - i++; - } - outside: - // look for other var definitions and collect them - while (i < stats->size()) { - traversePre(stats[i], [&](Ref node) { - Ref type = node[0]; - if (type == VAR) { - dump("bad, seeing a var in need of fixing", func); - abort(); //, 'should be no vars to fix! ' + func[1] + ' : ' + JSON.stringify(node)); - } - }); - i++; - } - // look for final RETURN statement to get return type. - Ref retStmt = stats->back(); - if (!!retStmt && retStmt[0] == RETURN && !!retStmt[1]) { - ret = detectType(retStmt[1]); - } else { - ret = ASM_NONE; - } -} - -void AsmData::denormalize() { - Ref stats = func[3]; - // Remove var definitions, if any - for (size_t i = 0; i < stats->size(); i++) { - if (stats[i][0] == VAR) { - stats[i] = makeEmpty(); - } else { - if (!isEmpty(stats[i])) break; - } - } - // calculate variable definitions - Ref varDefs = makeArray(vars.size()); - for (auto v : vars) { - varDefs->push_back(makeAsmVarDef(v, locals[v].type)); - } - // each param needs a line; reuse emptyNodes as much as we can - size_t numParams = params.size(); - size_t emptyNodes = 0; - while (emptyNodes < stats->size()) { - if (!isEmpty(stats[emptyNodes])) break; - emptyNodes++; - } - size_t neededEmptyNodes = numParams + (varDefs->size() ? 1 : 0); // params plus one big var if there are vars - if (neededEmptyNodes > emptyNodes) { - stats->insert(0, neededEmptyNodes - emptyNodes); - } else if (neededEmptyNodes < emptyNodes) { - stats->splice(0, emptyNodes - neededEmptyNodes); - } - // add param coercions - int next = 0; - for (auto param : func[2]->getArray()) { - IString str = param->getIString(); - assert(locals.count(str) > 0); - stats[next++] = make1(STAT, make3(ASSIGN, makeBool(true), makeName(str.c_str()), makeAsmCoercion(makeName(str.c_str()), locals[str].type))); - } - if (varDefs->size()) { - stats[next] = make1(VAR, varDefs); - } - /* - if (inlines->size() > 0) { - var i = 0; - traverse(func, function(node, type) { - if (type == CALL && node[1][0] == NAME && node[1][1] == 'inlinejs') { - node[1] = inlines[i++]; // swap back in the body - } - }); - } - */ - // ensure that there's a final RETURN statement if needed. - if (ret != ASM_NONE) { - Ref retStmt = stats->back(); - if (!retStmt || retStmt[0] != RETURN) { - Ref retVal = makeNum(0); - if (ret != ASM_INT) { - retVal = makeAsmCoercion(retVal, ret); - } - stats->push_back(make1(RETURN, retVal)); - } - } - //printErr('denormalized \n\n' + astToSrc(func) + '\n\n'); -} - -// Constructions TODO: share common constructions, and assert they remain frozen - -Ref makeArray(int size_hint=0) { - return &arena.alloc()->setArray(size_hint); -} - -Ref makeBool(bool b) { - return &arena.alloc()->setBool(b); -} - -Ref makeString(const IString& s) { - return &arena.alloc()->setString(s); -} - -Ref makeEmpty() { - return ValueBuilder::makeToplevel(); -} - -Ref makeNum(double x) { - return ValueBuilder::makeDouble(x); -} - -Ref makeName(IString str) { - return ValueBuilder::makeName(str); -} - -Ref makeBlock() { - return ValueBuilder::makeBlock(); -} - -Ref make1(IString s1, Ref a) { - Ref ret(makeArray(2)); - ret->push_back(makeString(s1)); - ret->push_back(a); - return ret; -} - -Ref make2(IString s1, IString s2, Ref a) { - Ref ret(makeArray(2)); - ret->push_back(makeString(s1)); - ret->push_back(makeString(s2)); - ret->push_back(a); - return ret; -} - -Ref make2(IString s1, Ref a, Ref b) { - Ref ret(makeArray(3)); - ret->push_back(makeString(s1)); - ret->push_back(a); - ret->push_back(b); - return ret; -} - -Ref make3(IString type, IString a, Ref b, Ref c) { - Ref ret(makeArray(4)); - ret->push_back(makeString(type)); - ret->push_back(makeString(a)); - ret->push_back(b); - ret->push_back(c); - return ret; -} - -Ref make3(IString type, Ref a, Ref b, Ref c) { - Ref ret(makeArray(4)); - ret->push_back(makeString(type)); - ret->push_back(a); - ret->push_back(b); - ret->push_back(c); - return ret; -} - -Ref makeAsmVarDef(const IString& v, AsmType type) { - Ref val; - switch (type) { - case ASM_INT: val = makeNum(0); break; - case ASM_DOUBLE: val = make2(UNARY_PREFIX, PLUS, makeNum(0)); break; - case ASM_FLOAT: { - if (!ASM_FLOAT_ZERO.isNull()) { - val = makeName(ASM_FLOAT_ZERO); - } else { - val = make2(CALL, makeName(MATH_FROUND), &(makeArray(1))->push_back(makeNum(0))); - } - break; - } - case ASM_FLOAT32X4: { - val = make2(CALL, makeName(SIMD_FLOAT32X4), &(makeArray(4))->push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0))); - break; - } - case ASM_FLOAT64X2: { - val = make2(CALL, makeName(SIMD_FLOAT64X2), &(makeArray(2))->push_back(makeNum(0)).push_back(makeNum(0))); - break; - } - case ASM_INT8X16: { - val = make2(CALL, makeName(SIMD_INT8X16), &(makeArray(16))->push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0))); - break; - } - case ASM_INT16X8: { - val = make2(CALL, makeName(SIMD_INT16X8), &(makeArray(8))->push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0))); - break; - } - case ASM_INT32X4: { - val = make2(CALL, makeName(SIMD_INT32X4), &(makeArray(4))->push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0)).push_back(makeNum(0))); - break; - } - default: assert(0); - } - return make1(v, val); -} - -Ref makeAsmCoercion(Ref node, AsmType type) { - switch (type) { - case ASM_INT: return make3(BINARY, OR, node, makeNum(0)); - case ASM_DOUBLE: return make2(UNARY_PREFIX, PLUS, node); - case ASM_FLOAT: return make2(CALL, makeName(MATH_FROUND), &(makeArray(1))->push_back(node)); - case ASM_FLOAT32X4: return make2(CALL, makeName(SIMD_FLOAT32X4_CHECK), &(makeArray(1))->push_back(node)); - case ASM_FLOAT64X2: return make2(CALL, makeName(SIMD_FLOAT64X2_CHECK), &(makeArray(1))->push_back(node)); - case ASM_INT8X16: return make2(CALL, makeName(SIMD_INT8X16_CHECK), &(makeArray(1))->push_back(node)); - case ASM_INT16X8: return make2(CALL, makeName(SIMD_INT16X8_CHECK), &(makeArray(1))->push_back(node)); - case ASM_INT32X4: return make2(CALL, makeName(SIMD_INT32X4_CHECK), &(makeArray(1))->push_back(node)); - case ASM_NONE: - default: return node; // non-validating code, emit nothing XXX this is dangerous, we should only allow this when we know we are not validating - } -} - -// Checks - -bool isEmpty(Ref node) { - return (node->size() == 2 && node[0] == TOPLEVEL && node[1]->size() == 0) || - (node->size() > 0 && node[0] == BLOCK && (!node[1] || node[1]->size() == 0)); -} - -bool commable(Ref node) { // TODO: hashing - IString type = node[0]->getIString(); - if (type == ASSIGN || type == BINARY || type == UNARY_PREFIX || type == NAME || type == NUM || type == CALL || type == SEQ || type == CONDITIONAL || type == SUB) return true; - return false; -} - -bool isMathFunc(const char *name) { - static const char *Math_ = "Math_"; - static unsigned size = strlen(Math_); - return strncmp(name, Math_, size) == 0; -} - -bool isMathFunc(Ref value) { - return value->isString() && isMathFunc(value->getCString()); -} - -bool callHasSideEffects(Ref node) { // checks if the call itself (not the args) has side effects (or is not statically known) - return !(node[1][0] == NAME && isMathFunc(node[1][1])); -} - -bool hasSideEffects(Ref node) { // this is 99% incomplete! - IString type = node[0]->getIString(); - switch (type[0]) { - case 'n': - if (type == NUM || type == NAME) return false; - break; - case 's': - if (type == STRING) return false; - if (type == SUB) return hasSideEffects(node[1]) || hasSideEffects(node[2]); - break; - case 'u': - if (type == UNARY_PREFIX) return hasSideEffects(node[2]); - break; - case 'b': - if (type == BINARY) return hasSideEffects(node[2]) || hasSideEffects(node[3]); - break; - case 'c': - if (type == CALL) { - if (callHasSideEffects(node)) return true; - // This is a statically known call, with no side effects. only args can side effect us - for (auto arg : node[2]->getArray()) { - if (hasSideEffects(arg)) return true; - } - return false; - } else if (type == CONDITIONAL) return hasSideEffects(node[1]) || hasSideEffects(node[2]) || hasSideEffects(node[3]); - break; - } - return true; -} - -// checks if a node has just basic operations, nothing with side effects nor that can notice side effects, which -// implies we can move it around in the code -bool triviallySafeToMove(Ref node, AsmData& asmData) { - bool ok = true; - traversePre(node, [&](Ref node) { - Ref type = node[0]; - if (type == STAT || type == BINARY || type == UNARY_PREFIX || type == ASSIGN || type == NUM) return; - else if (type == NAME) { - if (!asmData.isLocal(node[1]->getIString())) ok = false; - } else if (type == CALL) { - if (callHasSideEffects(node)) ok = false; - } else { - ok = false; - } - }); - return ok; -} - -// Transforms - -// We often have branchings that are simplified so one end vanishes, and -// we then get -// if (!(x < 5)) -// or such. Simplifying these saves space and time. -Ref simplifyNotCompsDirect(Ref node) { - if (node[0] == UNARY_PREFIX && node[1] == L_NOT) { - // de-morgan's laws do not work on floats, due to nans >:( - if (node[2][0] == BINARY && (detectType(node[2][2]) == ASM_INT && detectType(node[2][3]) == ASM_INT)) { - Ref op = node[2][1]; - switch(op->getCString()[0]) { - case '<': { - if (op == LT) { op->setString(GE); break; } - if (op == LE) { op->setString(GT); break; } - return node; - } - case '>': { - if (op == GT) { op->setString(LE); break; } - if (op == GE) { op->setString(LT); break; } - return node; - } - case '=': { - if (op == EQ) { op->setString(NE); break; } - return node; - } - case '!': { - if (op == NE) { op->setString(EQ); break; } - return node; - } - default: return node; - } - return make3(BINARY, op, node[2][2], node[2][3]); - } else if (node[2][0] == UNARY_PREFIX && node[2][1] == L_NOT) { - return node[2][2]; - } - } - return node; -} - -Ref flipCondition(Ref cond) { - return simplifyNotCompsDirect(make2(UNARY_PREFIX, L_NOT, cond)); -} - -void safeCopy(Ref target, Ref source) { // safely copy source onto target, even if source is a subnode of target - Ref temp = source; // hold on to source - *target = *temp; -} - -void clearEmptyNodes(Ref arr) { - int skip = 0; - for (size_t i = 0; i < arr->size(); i++) { - if (skip) { - arr[i-skip] = arr[i]; - } - if (isEmpty(deStat(arr[i]))) { - skip++; - } - } - if (skip) arr->setSize(arr->size() - skip); -} -void clearUselessNodes(Ref arr) { - int skip = 0; - for (size_t i = 0; i < arr->size(); i++) { - Ref curr = arr[i]; - if (skip) { - arr[i-skip] = curr; - } - if (isEmpty(deStat(curr)) || (curr[0] == STAT && !hasSideEffects(curr[1]))) { - skip++; - } - } - if (skip) arr->setSize(arr->size() - skip); -} - -void removeAllEmptySubNodes(Ref ast) { - traversePre(ast, [](Ref node) { - if (node[0] == DEFUN) { - clearEmptyNodes(node[3]); - } else if (node[0] == BLOCK && node->size() > 1 && !!node[1]) { - clearEmptyNodes(node[1]); - } else if (node[0] == SEQ && isEmpty(node[1])) { - safeCopy(node, node[2]); - } - }); -} -void removeAllUselessSubNodes(Ref ast) { - traversePrePost(ast, [](Ref node) { - Ref type = node[0]; - if (type == DEFUN) { - clearUselessNodes(node[3]); - } else if (type == BLOCK && node->size() > 1 && !!node[1]) { - clearUselessNodes(node[1]); - } else if (type == SEQ && isEmpty(node[1])) { - safeCopy(node, node[2]); - } - }, [](Ref node) { - Ref type = node[0]; - if (type == IF) { - bool empty2 = isEmpty(node[2]), has3 = node->size() == 4 && !!node[3], empty3 = !has3 || isEmpty(node[3]); - if (!empty2 && empty3 && has3) { // empty else clauses - node->setSize(3); - } else if (empty2 && !empty3) { // empty if blocks - safeCopy(node, make2(IF, make2(UNARY_PREFIX, L_NOT, node[1]), node[3])); - } else if (empty2 && empty3) { - if (hasSideEffects(node[1])) { - safeCopy(node, make1(STAT, node[1])); - } else { - safeCopy(node, makeEmpty()); - } - } - } - }); -} - -Ref unVarify(Ref vars) { // transform var x=1, y=2 etc. into (x=1, y=2), i.e., the same assigns, but without a var definition - Ref ret = makeArray(1); - ret->push_back(makeString(STAT)); - if (vars->size() == 1) { - ret->push_back(make3(ASSIGN, makeBool(true), makeName(vars[0][0]->getIString()), vars[0][1])); - } else { - ret->push_back(makeArray(vars->size()-1)); - Ref curr = ret[1]; - for (size_t i = 0; i+1 < vars->size(); i++) { - curr->push_back(makeString(SEQ)); - curr->push_back(make3(ASSIGN, makeBool(true), makeName(vars[i][0]->getIString()), vars[i][1])); - if (i != vars->size()-2) { - curr->push_back(makeArray()); - curr = curr[2]; - } - } - curr->push_back(make3(ASSIGN, makeBool(true), makeName(vars->back()[0]->getIString()), vars->back()[1])); - } - return ret; -} - -// Calculations - -int measureCost(Ref ast) { - int size = 0; - traversePre(ast, [&size](Ref node) { - Ref type = node[0]; - if (type == NUM || type == UNARY_PREFIX) size--; - else if (type == BINARY) { - if (node[3][0] == NUM && node[3][1]->getNumber() == 0) size--; - else if (node[1] == DIV || node[1] == MOD) size += 2; - } - else if (type == CALL && !callHasSideEffects(node)) size -= 2; - else if (type == SUB) size++; - size++; - }); - return size; -} - -//================== -// Params -//================== - -bool preciseF32 = false, - receiveJSON = false, - emitJSON = false, - minifyWhitespace = false, - last = false; - -//===================== -// Optimization passes -//===================== - -#define HASES \ - bool has(const IString& str) { \ - return count(str) > 0; \ - } \ - bool has(Ref node) { \ - return node->isString() && count(node->getIString()) > 0; \ - } - -class StringSet : public cashew::IStringSet { -public: - StringSet() {} - StringSet(const char *str) : IStringSet(str) {} - - HASES - - void dump() { - err("==="); - for (auto str : *this) { - errv("%s", str.c_str()); - } - err("==="); - } -}; - -StringSet USEFUL_BINARY_OPS("<< >> | & ^"), - COMPARE_OPS("< <= > >= == == != !=="), - BITWISE("| & ^"), - SAFE_BINARY_OPS("+ -"), // division is unsafe as it creates non-ints in JS; mod is unsafe as signs matter so we can't remove |0's; mul does not nest with +,- in asm - COERCION_REQUIRING_OPS("sub unary-prefix"), // ops that in asm must be coerced right away - COERCION_REQUIRING_BINARIES("* / %"); // binary ops that in asm must be coerced - -StringSet ASSOCIATIVE_BINARIES("+ * | & ^"), - CONTROL_FLOW("do while for if switch"), - LOOP("do while for"), - NAME_OR_NUM("name num"), - CONDITION_CHECKERS("if do while switch"), - SAFE_TO_DROP_COERCION("unary-prefix name num"); - -StringSet BREAK_CAPTURERS("do while for switch"), - CONTINUE_CAPTURERS("do while for"), - FUNCTIONS_THAT_ALWAYS_THROW("abort ___resumeException ___cxa_throw ___cxa_rethrow"); - -bool isFunctionTable(const char *name) { - static const char *functionTable = "FUNCTION_TABLE"; - static unsigned size = strlen(functionTable); - return strncmp(name, functionTable, size) == 0; -} - -bool isFunctionTable(Ref value) { - return value->isString() && isFunctionTable(value->getCString()); -} - -// Internal utilities - -bool canDropCoercion(Ref node) { - if (SAFE_TO_DROP_COERCION.has(node[0])) return true; - if (node[0] == BINARY) { - switch (node[1]->getCString()[0]) { - case '>': return node[1] == RSHIFT || node[1] == TRSHIFT; - case '<': return node[1] == LSHIFT; - case '|': case '^': case '&': return true; - } - } - return false; -} - -Ref simplifyCondition(Ref node) { - node = simplifyNotCompsDirect(node); - // on integers, if (x == 0) is the same as if (x), and if (x != 0) as if (!x) - if (node[0] == BINARY && (node[1] == EQ || node[1] == NE)) { - Ref target; - if (detectType(node[2]) == ASM_INT && node[3][0] == NUM && node[3][1]->getNumber() == 0) { - target = node[2]; - } else if (detectType(node[3]) == ASM_INT && node[2][0] == NUM && node[2][1]->getNumber() == 0) { - target = node[3]; - } - if (!!target) { - if (target[0] == BINARY && (target[1] == OR || target[1] == TRSHIFT) && target[3][0] == NUM && target[3][1]->getNumber() == 0 && - canDropCoercion(target[2])) { - target = target[2]; // drop the coercion, in a condition it is ok to do if (x) - } - if (node[1] == EQ) { - return make2(UNARY_PREFIX, L_NOT, target); - } else { - return target; - } - } - } - return node; -} - -// Passes - -// Eliminator aka Expressionizer -// -// The goal of this pass is to eliminate unneeded variables (which represent one of the infinite registers in the LLVM -// model) and thus to generate complex expressions where possible, for example -// -// var x = a(10); -// var y = HEAP[20]; -// print(x+y); -// -// can be transformed into -// -// print(a(10)+HEAP[20]); -// -// The basic principle is to scan along the code in the order of parsing/execution, and keep a list of tracked -// variables that are current contenders for elimination. We must untrack when we see something that we cannot -// cross, for example, a write to memory means we must invalidate variables that depend on reading from -// memory, since if we change the order then we do not preserve the computation. -// -// We rely on some assumptions about emscripten-generated code here, which means we can do a lot more than -// a general JS optimization can. For example, we assume that SUB nodes (indexing like HEAP[..]) are -// memory accesses or FUNCTION_TABLE accesses, and in both cases that the symbol cannot be replaced although -// the contents can. So we assume FUNCTION_TABLE might have its contents changed but not be pointed to -// a different object, which allows -// -// var x = f(); -// FUNCTION_TABLE[x](); -// -// to be optimized (f could replace FUNCTION_TABLE, so in general JS eliminating x is not valid). -// -// In memSafe mode, we are more careful and assume functions can replace HEAP and FUNCTION_TABLE, which -// can happen in ALLOW_MEMORY_GROWTH mode - -StringSet ELIMINATION_SAFE_NODES("assign call if toplevel do return label switch binary unary-prefix"); // do is checked carefully, however -StringSet IGNORABLE_ELIMINATOR_SCAN_NODES("num toplevel string break continue dot"); // dot can only be STRING_TABLE.* -StringSet ABORTING_ELIMINATOR_SCAN_NODES("new object function defun for while array throw"); // we could handle some of these, TODO, but nontrivial (e.g. for while, the condition is hit multiple times after the body) -StringSet HEAP_NAMES("HEAP8 HEAP16 HEAP32 HEAPU8 HEAPU16 HEAPU32 HEAPF32 HEAPF64"); - -bool isTempDoublePtrAccess(Ref node) { // these are used in bitcasts; they are not really affecting memory, and should cause no invalidation - assert(node[0] == SUB); - return (node[2][0] == NAME && node[2][1] == TEMP_DOUBLE_PTR) || - (node[2][0] == BINARY && ((node[2][2][0] == NAME && node[2][2][1] == TEMP_DOUBLE_PTR) || - (node[2][3][0] == NAME && node[2][3][1] == TEMP_DOUBLE_PTR))); -} - -class StringIntMap : public std::unordered_map<IString, int> { -public: - HASES -}; - -class StringStringMap : public std::unordered_map<IString, IString> { -public: - HASES -}; - -class StringRefMap : public std::unordered_map<IString, Ref> { -public: - HASES -}; - -class StringTypeMap : public std::unordered_map<IString, AsmType> { -public: - HASES -}; - -void eliminate(Ref ast, bool memSafe) { -#ifdef PROFILING - clock_t tasmdata = 0; - clock_t tfnexamine = 0; - clock_t tvarcheck = 0; - clock_t tstmtelim = 0; - clock_t tstmtscan = 0; - clock_t tcleanvars = 0; - clock_t treconstruct = 0; -#endif - - // Find variables that have a single use, and if they can be eliminated, do so - traverseFunctions(ast, [&](Ref func) { - -#ifdef PROFILING - clock_t start = clock(); -#endif - - AsmData asmData(func); - -#ifdef PROFILING - tasmdata += clock() - start; - start = clock(); -#endif - - // First, find the potentially eliminatable functions: that have one definition and one use - - StringIntMap definitions; - StringIntMap uses; - StringIntMap namings; - StringRefMap values; - StringIntMap varsToRemove; // variables being removed, that we can eliminate all 'var x;' of (this refers to VAR nodes we should remove) - // 1 means we should remove it, 2 means we successfully removed it - StringSet varsToTryToRemove; // variables that have 0 uses, but have side effects - when we scan we can try to remove them - - // examine body and note locals - traversePre(func, [&](Ref node) { - Ref type = node[0]; - if (type == NAME) { - IString& name = node[1]->getIString(); - uses[name]++; - namings[name]++; - } else if (type == ASSIGN) { - Ref target = node[2]; - if (target[0] == NAME) { - IString& name = target[1]->getIString(); - // values is only used if definitions is 1 - if (definitions[name]++ == 0) { - values[name] = node[3]; - } - assert(node[1]->isBool(true)); // not +=, -= etc., just = - uses[name]--; // because the name node will show up by itself in the previous case - } - } - }); - -#ifdef PROFILING - tfnexamine += clock() - start; - start = clock(); -#endif - - StringSet potentials; // local variables with 1 definition and 1 use - StringSet sideEffectFree; // whether a local variable has no side effects in its definition. Only relevant when there are no uses - - auto unprocessVariable = [&](IString name) { - potentials.erase(name); - varsToRemove.erase(name); - sideEffectFree.erase(name); - varsToTryToRemove.erase(name); - }; - std::function<void (IString)> processVariable = [&](IString name) { - if (definitions[name] == 1 && uses[name] == 1) { - potentials.insert(name); - } else if (uses[name] == 0 && definitions[name] <= 1) { // no uses, no def or 1 def (cannot operate on phis, and the llvm optimizer will remove unneeded phis anyhow) (no definition means it is a function parameter, or a local with just |var x;| but no defining assignment) - bool sideEffects = false; - auto val = values.find(name); - Ref value; - if (val != values.end()) { - value = val->second; - // TODO: merge with other side effect code - // First, pattern-match - // (HEAP32[((tempDoublePtr)>>2)]=((HEAP32[(($_sroa_0_0__idx1)>>2)])|0),HEAP32[(((tempDoublePtr)+(4))>>2)]=((HEAP32[((($_sroa_0_0__idx1)+(4))>>2)])|0),(+(HEAPF64[(tempDoublePtr)>>3]))) - // which has no side effects and is the special form of converting double to i64. - if (!(value[0] == SEQ && value[1][0] == ASSIGN && value[1][2][0] == SUB && value[1][2][2][0] == BINARY && value[1][2][2][1] == RSHIFT && - value[1][2][2][2][0] == NAME && value[1][2][2][2][1] == TEMP_DOUBLE_PTR)) { - // If not that, then traverse and scan normally. - sideEffects = hasSideEffects(value); - } - } - if (!sideEffects) { - varsToRemove[name] = !definitions[name] ? 2 : 1; // remove it normally - sideEffectFree.insert(name); - // Each time we remove a variable with 0 uses, if its value has no - // side effects and vanishes too, then we can remove a use from variables - // appearing in it, and possibly eliminate again - if (!!value) { - traversePre(value, [&](Ref node) { - if (node[0] == NAME) { - IString name = node[1]->getIString(); - node[1]->setString(EMPTY); // we can remove this - it will never be shown, and should not be left to confuse us as we traverse - if (asmData.isLocal(name)) { - uses[name]--; // cannot be infinite recursion since we descend an energy function - assert(uses[name] >= 0); - unprocessVariable(name); - processVariable(name); - } - } else if (node[0] == CALL) { - // no side effects, so this must be a Math.* call or such. We can just ignore it and all children - node[0]->setString(NAME); - node[1]->setString(EMPTY); - } - }); - } - } else { - varsToTryToRemove.insert(name); // try to remove it later during scanning - } - } - }; - for (auto name : asmData.locals) { - processVariable(name.first); - } - -#ifdef PROFILING - tvarcheck += clock() - start; - start = clock(); -#endif - - //printErr('defs: ' + JSON.stringify(definitions)); - //printErr('uses: ' + JSON.stringify(uses)); - //printErr('values: ' + JSON.stringify(values)); - //printErr('locals: ' + JSON.stringify(locals)); - //printErr('varsToRemove: ' + JSON.stringify(varsToRemove)); - //printErr('varsToTryToRemove: ' + JSON.stringify(varsToTryToRemove)); - values.clear(); - //printErr('potentials: ' + JSON.stringify(potentials)); - // We can now proceed through the function. In each list of statements, we try to eliminate - struct Tracking { - bool usesGlobals, usesMemory, hasDeps; - Ref defNode; - bool doesCall; - }; - class Tracked : public std::unordered_map<IString, Tracking> { - public: - HASES - }; - Tracked tracked; - #define dumpTracked() { errv("tracking %d", tracked.size()); for (auto t : tracked) errv("... %s", t.first.c_str()); } - // Although a set would be more appropriate, it would also be slower - std::unordered_map<IString, StringVec> depMap; - - bool globalsInvalidated = false; // do not repeat invalidations, until we track something new - bool memoryInvalidated = false; - bool callsInvalidated = false; - auto track = [&](IString name, Ref value, Ref defNode) { // add a potential that has just been defined to the tracked list, we hope to eliminate it - Tracking& track = tracked[name]; - track.usesGlobals = false; - track.usesMemory = false; - track.hasDeps = false; - track.defNode = defNode; - track.doesCall = false; - bool ignoreName = false; // one-time ignorings of names, as first op in sub and call - traversePre(value, [&](Ref node) { - Ref type = node[0]; - if (type == NAME) { - if (!ignoreName) { - IString depName = node[1]->getIString(); - if (!asmData.isLocal(depName)) { - track.usesGlobals = true; - } - if (!potentials.has(depName)) { // deps do not matter for potentials - they are defined once, so no complexity - depMap[depName].push_back(name); - track.hasDeps = true; - } - } else { - ignoreName = false; - } - } else if (type == SUB) { - track.usesMemory = true; - ignoreName = true; - } else if (type == CALL) { - track.usesGlobals = true; - track.usesMemory = true; - track.doesCall = true; - ignoreName = true; - } else { - ignoreName = false; - } - }); - if (track.usesGlobals) globalsInvalidated = false; - if (track.usesMemory) memoryInvalidated = false; - if (track.doesCall) callsInvalidated = false; - }; - - // TODO: invalidate using a sequence number for each type (if you were tracked before the last invalidation, you are cancelled). remove for.in loops - #define INVALIDATE(what, check) \ - auto invalidate##what = [&]() { \ - std::vector<IString> temp; \ - for (auto t : tracked) { \ - IString name = t.first; \ - Tracking& info = tracked[name]; \ - if (check) { \ - temp.push_back(name); \ - } \ - } \ - for (size_t i = 0; i < temp.size(); i++) { \ - tracked.erase(temp[i]); \ - } \ - }; - INVALIDATE(Globals, info.usesGlobals); - INVALIDATE(Memory, info.usesMemory); - INVALIDATE(Calls, info.doesCall); - - auto invalidateByDep = [&](IString dep) { - for (auto name : depMap[dep]) { - tracked.erase(name); - } - depMap.erase(dep); - }; - - std::function<void (IString name, Ref node)> doEliminate; - - // Generate the sequence of execution. This determines what is executed before what, so we know what can be reordered. Using - // that, performs invalidations and eliminations - auto scan = [&](Ref node) { - bool abort = false; - bool allowTracking = true; // false inside an if; also prevents recursing in an if - std::function<void (Ref, bool)> traverseInOrder = [&](Ref node, bool ignoreSub) { - if (abort) return; - Ref type = node[0]; - if (type == ASSIGN) { - Ref target = node[2]; - Ref value = node[3]; - bool nameTarget = target[0] == NAME; - // If this is an assign to a name, handle it below rather than - // traversing and treating as a read - if (!nameTarget) { - traverseInOrder(target, true); // evaluate left - } - traverseInOrder(value, false); // evaluate right - // do the actual assignment - if (nameTarget) { - IString name = target[1]->getIString(); - if (potentials.has(name) && allowTracking) { - track(name, node[3], node); - } else if (varsToTryToRemove.has(name)) { - // replace it in-place - safeCopy(node, value); - varsToRemove[name] = 2; - } else { - // expensive check for invalidating specific tracked vars. This list is generally quite short though, because of - // how we just eliminate in short spans and abort when control flow happens TODO: history numbers instead - invalidateByDep(name); // can happen more than once per dep.. - if (!asmData.isLocal(name) && !globalsInvalidated) { - invalidateGlobals(); - globalsInvalidated = true; - } - // if we can track this name (that we assign into), and it has 0 uses and we want to remove its VAR - // definition - then remove it right now, there is no later chance - if (allowTracking && varsToRemove.has(name) && uses[name] == 0) { - track(name, node[3], node); - doEliminate(name, node); - } - } - } else if (target[0] == SUB) { - if (isTempDoublePtrAccess(target)) { - if (!globalsInvalidated) { - invalidateGlobals(); - globalsInvalidated = true; - } - } else if (!memoryInvalidated) { - invalidateMemory(); - memoryInvalidated = true; - } - } - } else if (type == SUB) { - // Only keep track of the global array names in memsafe mode i.e. - // when they may change underneath us due to resizing - if (node[1][0] != NAME || memSafe) { - traverseInOrder(node[1], false); // evaluate inner - } - traverseInOrder(node[2], false); // evaluate outer - // ignoreSub means we are a write (happening later), not a read - if (!ignoreSub && !isTempDoublePtrAccess(node)) { - // do the memory access - if (!callsInvalidated) { - invalidateCalls(); - callsInvalidated = true; - } - } - } else if (type == BINARY) { - bool flipped = false; - if (ASSOCIATIVE_BINARIES.has(node[1]) && !NAME_OR_NUM.has(node[2][0]) && NAME_OR_NUM.has(node[3][0])) { // TODO recurse here? - // associatives like + and * can be reordered in the simple case of one of the sides being a name, since we assume they are all just numbers - Ref temp = node[2]; - node[2] = node[3]; - node[3] = temp; - flipped = true; - } - traverseInOrder(node[2], false); - traverseInOrder(node[3], false); - if (flipped && NAME_OR_NUM.has(node[2][0])) { // dunno if we optimized, but safe to flip back - and keeps the code closer to the original and more readable - Ref temp = node[2]; - node[2] = node[3]; - node[3] = temp; - } - } else if (type == NAME) { - IString name = node[1]->getIString(); - if (tracked.has(name)) { - doEliminate(name, node); - } else if (!asmData.isLocal(name) && !callsInvalidated && (memSafe || !HEAP_NAMES.has(name))) { // ignore HEAP8 etc when not memory safe, these are ok to - // access, e.g. SIMD_Int32x4_load(HEAP8, ...) - invalidateCalls(); - callsInvalidated = true; - } - } else if (type == UNARY_PREFIX || type == UNARY_POSTFIX) { - traverseInOrder(node[2], false); - } else if (IGNORABLE_ELIMINATOR_SCAN_NODES.has(type)) { - } else if (type == CALL) { - // Named functions never change and are therefore safe to not track - if (node[1][0] != NAME) { - traverseInOrder(node[1], false); - } - Ref args = node[2]; - for (size_t i = 0; i < args->size(); i++) { - traverseInOrder(args[i], false); - } - if (callHasSideEffects(node)) { - // these two invalidations will also invalidate calls - if (!globalsInvalidated) { - invalidateGlobals(); - globalsInvalidated = true; - } - if (!memoryInvalidated) { - invalidateMemory(); - memoryInvalidated = true; - } - } - } else if (type == IF) { - if (allowTracking) { - traverseInOrder(node[1], false); // can eliminate into condition, but nowhere else - if (!callsInvalidated) { // invalidate calls, since we cannot eliminate them into an if that may not execute! - invalidateCalls(); - callsInvalidated = true; - } - allowTracking = false; - traverseInOrder(node[2], false); // 2 and 3 could be 'parallel', really.. - if (!!node[3]) traverseInOrder(node[3], false); - allowTracking = true; - } else { - tracked.clear(); - } - } else if (type == BLOCK) { - Ref stats = getStatements(node); - if (!!stats) { - for (size_t i = 0; i < stats->size(); i++) { - traverseInOrder(stats[i], false); - } - } - } else if (type == STAT) { - traverseInOrder(node[1], false); - } else if (type == LABEL) { - traverseInOrder(node[2], false); - } else if (type == SEQ) { - traverseInOrder(node[1], false); - traverseInOrder(node[2], false); - } else if (type == DO) { - if (node[1][0] == NUM && node[1][1]->getNumber() == 0) { // one-time loop - traverseInOrder(node[2], false); - } else { - tracked.clear(); - } - } else if (type == RETURN) { - if (!!node[1]) traverseInOrder(node[1], false); - } else if (type == CONDITIONAL) { - if (!callsInvalidated) { // invalidate calls, since we cannot eliminate them into a branch of an LLVM select/JS conditional that does not execute - invalidateCalls(); - callsInvalidated = true; - } - traverseInOrder(node[1], false); - traverseInOrder(node[2], false); - traverseInOrder(node[3], false); - } else if (type == SWITCH) { - traverseInOrder(node[1], false); - Tracked originalTracked = tracked; - Ref cases = node[2]; - for (size_t i = 0; i < cases->size(); i++) { - Ref c = cases[i]; - assert(c[0]->isNull() || c[0][0] == NUM || (c[0][0] == UNARY_PREFIX && c[0][2][0] == NUM)); - Ref stats = c[1]; - for (size_t j = 0; j < stats->size(); j++) { - traverseInOrder(stats[j], false); - } - // We cannot track from one switch case into another if there are external dependencies, undo all new trackings - // Otherwise we can track, e.g. a var used in a case before assignment in another case is UB in asm.js, so no need for the assignment - // TODO: general framework here, use in if-else as well - std::vector<IString> toDelete; - for (auto t : tracked) { - if (!originalTracked.has(t.first)) { - Tracking& info = tracked[t.first]; - if (info.usesGlobals || info.usesMemory || info.hasDeps) { - toDelete.push_back(t.first); - } - } - } - for (auto t : toDelete) { - tracked.erase(t); - } - } - tracked.clear(); // do not track from inside the switch to outside - } else { - assert(ABORTING_ELIMINATOR_SCAN_NODES.has(type)); - tracked.clear(); - abort = true; - } - }; - traverseInOrder(node, false); - }; - //var eliminationLimit = 0; // used to debugging purposes - doEliminate = [&](IString name, Ref node) { - //if (eliminationLimit == 0) return; - //eliminationLimit--; - //printErr('elim!!!!! ' + name); - // yes, eliminate! - varsToRemove[name] = 2; // both assign and var definitions can have other vars we must clean up - assert(tracked.has(name)); - Tracking& info = tracked[name]; - Ref defNode = info.defNode; - assert(!!defNode); - if (!sideEffectFree.has(name)) { - assert(defNode[0] != VAR); - // assign - Ref value = defNode[3]; - // wipe out the assign - safeCopy(defNode, makeEmpty()); - // replace this node in-place - safeCopy(node, value); - } else { - // This has no side effects and no uses, empty it out in-place - safeCopy(node, makeEmpty()); - } - tracked.erase(name); - }; - traversePre(func, [&](Ref block) { - // Look for statements, including while-switch pattern - Ref stats = getStatements(block); - if (!stats && (block[0] == WHILE && block[2][0] == SWITCH)) { - stats = &(makeArray(1)->push_back(block[2])); - } - if (!stats) return; - tracked.clear(); - for (size_t i = 0; i < stats->size(); i++) { - Ref node = deStat(stats[i]); - Ref type = node[0]; - if (type == RETURN && i+1 < stats->size()) { - stats->setSize(i+1); // remove any code after a return - } - // Check for things that affect elimination - if (ELIMINATION_SAFE_NODES.has(type)) { -#ifdef PROFILING - tstmtelim += clock() - start; - start = clock(); -#endif - scan(node); -#ifdef PROFILING - tstmtscan += clock() - start; - start = clock(); -#endif - } else if (type == VAR) { - continue; // asm normalisation has reduced 'var' to just the names - } else { - tracked.clear(); // not a var or assign, break all potential elimination so far - } - } - }); - -#ifdef PROFILING - tstmtelim += clock() - start; - start = clock(); -#endif - - StringIntMap seenUses; - StringStringMap helperReplacements; // for looper-helper optimization - - // clean up vars, and loop variable elimination - traversePrePost(func, [&](Ref node) { - // pre - Ref type = node[0]; - /*if (type == VAR) { - 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[1] = []; - } - } else */ - if (type == ASSIGN && node[1]->isBool(true) && node[2][0] == NAME && node[3][0] == NAME && node[2][1] == node[3][1]) { - // elimination led to X = X, which we can just remove - safeCopy(node, makeEmpty()); - } - }, [&](Ref node) { - // post - Ref type = node[0]; - if (type == NAME) { - IString name = node[1]->getIString(); - if (helperReplacements.has(name)) { - node[1]->setString(helperReplacements[name]); - return; // no need to track this anymore, we can't loop-optimize more than once - } - // track how many uses we saw. we need to know when a variable is no longer used (hence we run this in the post) - seenUses[name]++; - } else if (type == WHILE) { - // try to remove loop helper variables specifically - Ref stats = node[2][1]; - Ref last = stats->back(); - if (!!last && last[0] == IF && last[2][0] == BLOCK && !!last[3] && last[3][0] == BLOCK) { - Ref ifTrue = last[2]; - Ref ifFalse = last[3]; - clearEmptyNodes(ifTrue[1]); - clearEmptyNodes(ifFalse[1]); - bool flip = false; - if (ifFalse[1]->size() > 0 && !!ifFalse[1][0] && !!ifFalse[1]->back() && ifFalse[1]->back()[0] == BREAK) { // canonicalize break in the if-true - Ref temp = ifFalse; - ifFalse = ifTrue; - ifTrue = temp; - flip = true; - } - if (ifTrue[1]->size() > 0 && !!ifTrue[1][0] && !!ifTrue[1]->back() && ifTrue[1]->back()[0] == BREAK) { - Ref assigns = ifFalse[1]; - clearEmptyNodes(assigns); - std::vector<IString> loopers, helpers; - for (size_t i = 0; i < assigns->size(); i++) { - if (assigns[i][0] == STAT && assigns[i][1][0] == ASSIGN) { - Ref assign = assigns[i][1]; - if (assign[1]->isBool(true) && assign[2][0] == NAME && assign[3][0] == NAME) { - IString looper = assign[2][1]->getIString(); - IString helper = assign[3][1]->getIString(); - if (definitions[helper] == 1 && seenUses[looper] == namings[looper] && - !helperReplacements.has(helper) && !helperReplacements.has(looper)) { - loopers.push_back(looper); - helpers.push_back(helper); - } - } - } - } - // remove loop vars that are used in the rest of the else - for (size_t i = 0; i < assigns->size(); i++) { - if (assigns[i][0] == STAT && assigns[i][1][0] == ASSIGN) { - Ref assign = assigns[i][1]; - if (!(assign[1]->isBool(true) && assign[2][0] == NAME && assign[3][0] == NAME) || indexOf(loopers, assign[2][1]->getIString()) < 0) { - // this is not one of the loop assigns - traversePre(assign, [&](Ref node) { - if (node[0] == NAME) { - int index = indexOf(loopers, node[1]->getIString()); - if (index < 0) index = indexOf(helpers, node[1]->getIString()); - if (index >= 0) { - loopers.erase(loopers.begin() + index); - helpers.erase(helpers.begin() + index); - } - } - }); - } - } - } - // remove loop vars that are used in the if - traversePre(ifTrue, [&](Ref node) { - if (node[0] == NAME) { - int index = indexOf(loopers, node[1]->getIString()); - if (index < 0) index = indexOf(helpers, node[1]->getIString()); - if (index >= 0) { - loopers.erase(loopers.begin() + index); - helpers.erase(helpers.begin() + index); - } - } - }); - if (loopers.size() == 0) return; - for (size_t l = 0; l < loopers.size(); l++) { - IString looper = loopers[l]; - IString helper = helpers[l]; - // the remaining issue is whether loopers are used after the assignment to helper and before the last line (where we assign to it) - int found = -1; - for (int i = (int)stats->size()-2; i >= 0; i--) { - Ref curr = stats[i]; - if (curr[0] == STAT && curr[1][0] == ASSIGN) { - Ref currAssign = curr[1]; - if (currAssign[1]->isBool(true) && currAssign[2][0] == NAME) { - IString to = currAssign[2][1]->getIString(); - if (to == helper) { - found = i; - break; - } - } - } - } - if (found < 0) return; - // if a loop variable is used after we assigned to the helper, we must save its value and use that. - // (note that this can happen due to elimination, if we eliminate an expression containing the - // loop var far down, past the assignment!) - // first, see if the looper and helpers overlap. Note that we check for this looper, compared to - // *ALL* the helpers. Helpers will be replaced by loopers as we eliminate them, potentially - // causing conflicts, so any helper is a concern. - int firstLooperUsage = -1; - int lastLooperUsage = -1; - int firstHelperUsage = -1; - for (int i = found+1; i < (int)stats->size(); i++) { - Ref curr = i < (int)stats->size()-1 ? stats[i] : last[1]; // on the last line, just look in the condition - traversePre(curr, [&](Ref node) { - if (node[0] == NAME) { - if (node[1] == looper) { - if (firstLooperUsage < 0) firstLooperUsage = i; - lastLooperUsage = i; - } else if (indexOf(helpers, node[1]->getIString()) >= 0) { - if (firstHelperUsage < 0) firstHelperUsage = i; - } - } - }); - } - if (firstLooperUsage >= 0) { - // the looper is used, we cannot simply merge the two variables - if ((firstHelperUsage < 0 || firstHelperUsage > lastLooperUsage) && lastLooperUsage+1 < (int)stats->size() && triviallySafeToMove(stats[found], asmData) && - seenUses[helper] == namings[helper]) { - // the helper is not used, or it is used after the last use of the looper, so they do not overlap, - // and the last looper usage is not on the last line (where we could not append after it), and the - // helper is not used outside of the loop. - // just move the looper definition to after the looper's last use - stats->insert(lastLooperUsage+1, stats[found]); - stats->splice(found, 1); - } else { - // they overlap, we can still proceed with the loop optimization, but we must introduce a - // loop temp helper variable - IString temp(strdupe((std::string(looper.c_str()) + "$looptemp").c_str())); - assert(!asmData.isLocal(temp)); - for (int i = firstLooperUsage; i <= lastLooperUsage; i++) { - Ref curr = i < (int)stats->size()-1 ? stats[i] : last[1]; // on the last line, just look in the condition - - std::function<bool (Ref)> looperToLooptemp = [&](Ref node) { - if (node[0] == NAME) { - if (node[1] == looper) { - node[1]->setString(temp); - } - } else if (node[0] == ASSIGN && node[2][0] == NAME) { - // do not traverse the assignment target, phi assignments to the loop variable must remain - traversePrePostConditional(node[3], looperToLooptemp, [](Ref node){}); - return false; - } - return true; - }; - traversePrePostConditional(curr, looperToLooptemp, [](Ref node){}); - } - asmData.addVar(temp, asmData.getType(looper)); - stats->insert(found, make1(STAT, make3(ASSIGN, makeBool(true), makeName(temp), makeName(looper)))); - } - } - } - for (size_t l = 0; l < helpers.size(); l++) { - for (size_t k = 0; k < helpers.size(); k++) { - if (l != k && helpers[l] == helpers[k]) return; // it is complicated to handle a shared helper, abort - } - } - // hurrah! this is safe to do - for (size_t l = 0; l < loopers.size(); l++) { - IString looper = loopers[l]; - IString helper = helpers[l]; - varsToRemove[helper] = 2; - traversePre(node, [&](Ref node) { // replace all appearances of helper with looper - if (node[0] == NAME && node[1] == helper) node[1]->setString(looper); - }); - helperReplacements[helper] = looper; // replace all future appearances of helper with looper - helperReplacements[looper] = looper; // avoid any further attempts to optimize looper in this manner (seenUses is wrong anyhow, too) - } - // simplify the if. we remove the if branch, leaving only the else - if (flip) { - last[1] = simplifyNotCompsDirect(make2(UNARY_PREFIX, L_NOT, last[1])); - Ref temp = last[2]; - last[2] = last[3]; - last[3] = temp; - } - if (loopers.size() == assigns->size()) { - last->pop_back(); - } else { - Ref elseStats = getStatements(last[3]); - for (size_t i = 0; i < elseStats->size(); i++) { - Ref stat = deStat(elseStats[i]); - if (stat[0] == ASSIGN && stat[2][0] == NAME) { - if (indexOf(loopers, stat[2][1]->getIString()) >= 0) { - elseStats[i] = makeEmpty(); - } - } - } - } - } - } - } - }); - -#ifdef PROFILING - tcleanvars += clock() - start; - start = clock(); -#endif - - for (auto v : varsToRemove) { - if (v.second == 2 && asmData.isVar(v.first)) asmData.deleteVar(v.first); - } - - asmData.denormalize(); - -#ifdef PROFILING - treconstruct += clock() - start; - start = clock(); -#endif - - }); - - removeAllEmptySubNodes(ast); - -#ifdef PROFILING - errv(" EL stages: a:%li fe:%li vc:%li se:%li (ss:%li) cv:%li r:%li", - tasmdata, tfnexamine, tvarcheck, tstmtelim, tstmtscan, tcleanvars, treconstruct); -#endif -} - -void eliminateMemSafe(Ref ast) { - eliminate(ast, true); -} - -void simplifyExpressions(Ref ast) { - // Simplify common expressions used to perform integer conversion operations - // in cases where no conversion is needed. - auto simplifyIntegerConversions = [](Ref ast) { - traversePre(ast, [](Ref node) { - Ref type = node[0]; - if (type == BINARY && node[1] == RSHIFT && node[3][0] == NUM && - node[2][0] == BINARY && node[2][1] == LSHIFT && node[2][3][0] == NUM && node[3][1]->getNumber() == node[2][3][1]->getNumber()) { - // Transform (x&A)<<B>>B to X&A. - Ref innerNode = node[2][2]; - double shifts = node[3][1]->getNumber(); - if (innerNode[0] == BINARY && innerNode[1] == AND && innerNode[3][0] == NUM) { - double mask = innerNode[3][1]->getNumber(); - if (isInteger32(mask) && isInteger32(shifts) && ((jsD2I(mask) << jsD2I(shifts)) >> jsD2I(shifts)) == jsD2I(mask)) { - safeCopy(node, innerNode); - return; - } - } - } else if (type == BINARY && BITWISE.has(node[1])) { - for (int i = 2; i <= 3; i++) { - Ref subNode = node[i]; - if (subNode[0] == BINARY && subNode[1] == AND && subNode[3][0] == NUM && subNode[3][1]->getNumber() == 1) { - // Rewrite (X < Y) & 1 to X < Y , when it is going into a bitwise operator. We could - // remove even more (just replace &1 with |0, then subsequent passes could remove the |0) - // but v8 issue #2513 means the code would then run very slowly in chrome. - Ref input = subNode[2]; - if (input[0] == BINARY && COMPARE_OPS.has(input[1])) { - safeCopy(node[i], input); - } - } - } - } - }); - }; - - // When there is a bunch of math like (((8+5)|0)+12)|0, only the external |0 is needed, one correction is enough. - // At each node, ((X|0)+Y)|0 can be transformed into (X+Y): The inner corrections are not needed - // TODO: Is the same is true for 0xff, 0xffff? - // Likewise, if we have |0 inside a block that will be >>'d, then the |0 is unnecessary because some - // 'useful' mathops already |0 anyhow. - - auto simplifyOps = [](Ref ast) { - auto removeMultipleOrZero = [&ast] { - bool rerun = true; - while (rerun) { - rerun = false; - std::vector<int> stack; - std::function<void (Ref)> process = [&stack, &rerun, &process, &ast](Ref node) { - Ref type = node[0]; - if (type == BINARY && node[1] == OR) { - if (node[2][0] == NUM && node[3][0] == NUM) { - node[2][1]->setNumber(jsD2I(node[2][1]->getNumber()) | jsD2I(node[3][1]->getNumber())); - stack.push_back(0); - safeCopy(node, node[2]); - return; - } - bool go = false; - if (node[2][0] == NUM && node[2][1]->getNumber() == 0) { - // canonicalize order - Ref temp = node[3]; - node[3] = node[2]; - node[2] = temp; - go = true; - } else if (node[3][0] == NUM && node[3][1]->getNumber() == 0) { - go = true; - } - if (!go) { - stack.push_back(1); - return; - } - // We might be able to remove this correction - for (int i = stack.size()-1; i >= 0; i--) { - if (stack[i] >= 1) { - if (stack.back() < 2 && node[2][0] == CALL) break; // we can only remove multiple |0s on these - if (stack.back() < 1 && (COERCION_REQUIRING_OPS.has(node[2][0]) || - (node[2][0] == BINARY && COERCION_REQUIRING_BINARIES.has(node[2][1])))) break; // we can remove |0 or >>2 - // we will replace ourselves with the non-zero side. Recursively process that node. - Ref result = node[2][0] == NUM && node[2][1]->getNumber() == 0 ? node[3] : node[2], other; - // replace node in-place - safeCopy(node, result); - rerun = true; - process(result); - return; - } else if (stack[i] == -1) { - break; // Too bad, we can't - } - } - stack.push_back(2); // From here on up, no need for this kind of correction, it's done at the top - // (Add this at the end, so it is only added if we did not remove it) - } else if (type == BINARY && USEFUL_BINARY_OPS.has(node[1])) { - stack.push_back(1); - } else if ((type == BINARY && SAFE_BINARY_OPS.has(node[1])) || type == NUM || type == NAME) { - stack.push_back(0); // This node is safe in that it does not interfere with this optimization - } else if (type == UNARY_PREFIX && node[1] == B_NOT) { - stack.push_back(1); - } else { - stack.push_back(-1); // This node is dangerous! Give up if you see this before you see '1' - } - }; - - traversePrePost(ast, process, [&stack](Ref node) { - assert(!stack.empty()); - stack.pop_back(); - }); - } - }; - - removeMultipleOrZero(); - - // & and heap-related optimizations - - bool hasTempDoublePtr = false, rerunOrZeroPass = false; - - traversePrePostConditional(ast, [](Ref node) { - // Detect trees which should not - // be simplified. - if (node[0] == SUB && node[1][0] == NAME && isFunctionTable(node[1][1])) { - return false; // do not traverse subchildren here, we should not collapse 55 & 126. - } - return true; - }, [&hasTempDoublePtr, &rerunOrZeroPass](Ref node) { - // Simplifications are done now so - // that we simplify a node's operands before the node itself. This allows - // optimizations to cascade. - Ref type = node[0]; - if (type == NAME) { - if (node[1] == TEMP_DOUBLE_PTR) hasTempDoublePtr = true; - } else if (type == BINARY && node[1] == AND && node[3][0] == NUM) { - if (node[2][0] == NUM) { - safeCopy(node, makeNum(jsD2I(node[2][1]->getNumber()) & jsD2I(node[3][1]->getNumber()))); - return; - } - Ref input = node[2]; - double amount = node[3][1]->getNumber(); - if (input[0] == BINARY && input[1] == AND && input[3][0] == NUM) { - // Collapse X & 255 & 1 - node[3][1]->setNumber(jsD2I(amount) & jsD2I(input[3][1]->getNumber())); - node[2] = input[2]; - } else if (input[0] == SUB && input[1][0] == NAME) { - // HEAP8[..] & 255 => HEAPU8[..] - HeapInfo hi = parseHeap(input[1][1]->getCString()); - if (hi.valid) { - if (isInteger32(amount) && amount == powl(2, hi.bits)-1) { - if (!hi.unsign) { - input[1][1]->setString(getHeapStr(hi.bits, true)); // make unsigned - } - // we cannot return HEAPU8 without a coercion, but at least we do HEAP8 & 255 => HEAPU8 | 0 - node[1]->setString(OR); - node[3][1]->setNumber(0); - return; - } - } - } else if (input[0] == BINARY && input[1] == RSHIFT && - input[2][0] == BINARY && input[2][1] == LSHIFT && - input[2][3][0] == NUM && input[3][0] == NUM && - input[2][3][1]->getInteger() == input[3][1]->getInteger() && - (~(0xFFFFFFFFu >> input[3][1]->getInteger()) & jsD2I(amount)) == 0) { - // x << 24 >> 24 & 255 => x & 255 - return safeCopy(node, make3(BINARY, AND, input[2][2], node[3])); - } - } else if (type == BINARY && node[1] == XOR) { - // LLVM represents bitwise not as xor with -1. Translate it back to an actual bitwise not. - if (node[3][0] == UNARY_PREFIX && node[3][1] == MINUS && node[3][2][0] == NUM && - node[3][2][1]->getNumber() == 1 && - !(node[2][0] == UNARY_PREFIX && node[2][1] == B_NOT)) { // avoid creating ~~~ which is confusing for asm given the role of ~~ - safeCopy(node, make2(UNARY_PREFIX, B_NOT, node[2])); - return; - } - } else if (type == BINARY && node[1] == RSHIFT && node[3][0] == NUM && - node[2][0] == BINARY && node[2][1] == LSHIFT && node[2][3][0] == NUM && - node[2][2][0] == SUB && node[2][2][1][0] == NAME) { - // collapse HEAPU?8[..] << 24 >> 24 etc. into HEAP8[..] | 0 - double amount = node[3][1]->getNumber(); - if (amount == node[2][3][1]->getNumber()) { - HeapInfo hi = parseHeap(node[2][2][1][1]->getCString()); - if (hi.valid && hi.bits == 32 - amount) { - node[2][2][1][1]->setString(getHeapStr(hi.bits, false)); - node[1]->setString(OR); - node[2] = node[2][2]; - node[3][1]->setNumber(0); - rerunOrZeroPass = true; - return; - } - } - } else if (type == ASSIGN) { - // optimizations for assigning into HEAP32 specifically - if (node[1]->isBool(true) && node[2][0] == SUB && node[2][1][0] == NAME) { - if (node[2][1][1] == HEAP32) { - // HEAP32[..] = x | 0 does not need the | 0 (unless it is a mandatory |0 of a call) - if (node[3][0] == BINARY && node[3][1] == OR) { - if (node[3][2][0] == NUM && node[3][2][1]->getNumber() == 0 && node[3][3][0] != CALL) { - node[3] = node[3][3]; - } else if (node[3][3][0] == NUM && node[3][3][1]->getNumber() == 0 && node[3][2][0] != CALL) { - node[3] = node[3][2]; - } - } - } else if (node[2][1][1] == HEAP8) { - // HEAP8[..] = x & 0xff does not need the & 0xff - if (node[3][0] == BINARY && node[3][1] == AND && node[3][3][0] == NUM && node[3][3][1]->getNumber() == 0xff) { - node[3] = node[3][2]; - } - } else if (node[2][1][1] == HEAP16) { - // HEAP16[..] = x & 0xffff does not need the & 0xffff - if (node[3][0] == BINARY && node[3][1] == AND && node[3][3][0] == NUM && node[3][3][1]->getNumber() == 0xffff) { - node[3] = node[3][2]; - } - } - } - Ref value = node[3]; - if (value[0] == BINARY && value[1] == OR) { - // canonicalize order of |0 to end - if (value[2][0] == NUM && value[2][1]->getNumber() == 0) { - Ref temp = value[2]; - value[2] = value[3]; - value[3] = temp; - } - // if a seq ends in an |0, remove an external |0 - // note that it is only safe to do this in assigns, like we are doing here (return (x, y|0); is not valid) - if (value[2][0] == SEQ && value[2][2][0] == BINARY && USEFUL_BINARY_OPS.has(value[2][2][1])) { - node[3] = value[2]; - } - } - } else if (type == BINARY && node[1] == RSHIFT && node[2][0] == NUM && node[3][0] == NUM) { - // optimize num >> num, in asm we need this since we do not optimize shifts in asm.js - node[0]->setString(NUM); - node[1]->setNumber(jsD2I(node[2][1]->getNumber()) >> jsD2I(node[3][1]->getNumber())); - node->setSize(2); - return; - } else if (type == BINARY && node[1] == PLUS) { - // The most common mathop is addition, e.g. in getelementptr done repeatedly. We can join all of those, - // by doing (num+num) ==> newnum. - if (node[2][0] == NUM && node[3][0] == NUM) { - node[2][1]->setNumber(jsD2I(node[2][1]->getNumber()) + jsD2I(node[3][1]->getNumber())); - safeCopy(node, node[2]); - return; - } - } - }); - - if (rerunOrZeroPass) removeMultipleOrZero(); - - if (hasTempDoublePtr) { - AsmData asmData(ast); - traversePre(ast, [](Ref node) { - Ref type = node[0]; - if (type == ASSIGN) { - if (node[1]->isBool(true) && node[2][0] == SUB && node[2][1][0] == NAME && node[2][1][1] == HEAP32) { - // remove bitcasts that are now obviously pointless, e.g. - // HEAP32[$45 >> 2] = HEAPF32[tempDoublePtr >> 2] = ($14 < $28 ? $14 : $28) - $42, HEAP32[tempDoublePtr >> 2] | 0; - Ref value = node[3]; - if (value[0] == SEQ && value[1][0] == ASSIGN && value[1][2][0] == SUB && value[1][2][1][0] == NAME && value[1][2][1][1] == HEAPF32 && - value[1][2][2][0] == BINARY && value[1][2][2][2][0] == NAME && value[1][2][2][2][1] == TEMP_DOUBLE_PTR) { - // transform to HEAPF32[$45 >> 2] = ($14 < $28 ? $14 : $28) - $42; - node[2][1][1]->setString(HEAPF32); - node[3] = value[1][3]; - } - } - } else if (type == SEQ) { - // (HEAP32[tempDoublePtr >> 2] = HEAP32[$37 >> 2], +HEAPF32[tempDoublePtr >> 2]) - // ==> - // +HEAPF32[$37 >> 2] - if (node[0] == SEQ && node[1][0] == ASSIGN && node[1][2][0] == SUB && node[1][2][1][0] == NAME && - (node[1][2][1][1] == HEAP32 || node[1][2][1][1] == HEAPF32) && - node[1][2][2][0] == BINARY && node[1][2][2][2][0] == NAME && node[1][2][2][2][1] == TEMP_DOUBLE_PTR && - node[1][3][0] == SUB && node[1][3][1][0] == NAME && (node[1][3][1][1] == HEAP32 || node[1][3][1][1] == HEAPF32) && - node[2][0] != SEQ) { // avoid (x, y, z) which can be used for tempDoublePtr on doubles for alignment fixes - if (node[1][2][1][1] == HEAP32) { - node[1][3][1][1]->setString(HEAPF32); - safeCopy(node, makeAsmCoercion(node[1][3], detectType(node[2]))); - return; - } else { - node[1][3][1][1]->setString(HEAP32); - safeCopy(node, make3(BINARY, OR, node[1][3], makeNum(0))); - return; - } - } - } - }); - - // finally, wipe out remaining ones by finding cases where all assignments to X are bitcasts, and all uses are writes to - // the other heap type, then eliminate the bitcast - struct BitcastData { - int define_HEAP32, define_HEAPF32, use_HEAP32, use_HEAPF32, namings; - bool ok; - std::vector<Ref> defines, uses; - - BitcastData() : define_HEAP32(0), define_HEAPF32(0), use_HEAP32(0), use_HEAPF32(0), namings(0), ok(false) {} - }; - std::unordered_map<IString, BitcastData> bitcastVars; - traversePre(ast, [&bitcastVars](Ref node) { - if (node[0] == ASSIGN && node[1]->isBool(true) && node[2][0] == NAME) { - Ref value = node[3]; - if (value[0] == SEQ && value[1][0] == ASSIGN && value[1][2][0] == SUB && value[1][2][1][0] == NAME && - (value[1][2][1][1] == HEAP32 || value[1][2][1][1] == HEAPF32) && - value[1][2][2][0] == BINARY && value[1][2][2][2][0] == NAME && value[1][2][2][2][1] == TEMP_DOUBLE_PTR) { - IString name = node[2][1]->getIString(); - IString heap = value[1][2][1][1]->getIString(); - if (heap == HEAP32) { - bitcastVars[name].define_HEAP32++; - } else { - assert(heap == HEAPF32); - bitcastVars[name].define_HEAPF32++; - } - bitcastVars[name].defines.push_back(node); - bitcastVars[name].ok = true; - } - } - }); - traversePre(ast, [&bitcastVars](Ref node) { - Ref type = node[0]; - if (type == NAME && bitcastVars[node[1]->getCString()].ok) { - bitcastVars[node[1]->getCString()].namings++; - } else if (type == ASSIGN && node[1]->isBool(true)) { - Ref value = node[3]; - if (value[0] == NAME) { - IString name = value[1]->getIString(); - if (bitcastVars[name].ok) { - Ref target = node[2]; - if (target[0] == SUB && target[1][0] == NAME && (target[1][1] == HEAP32 || target[1][1] == HEAPF32)) { - if (target[1][1] == HEAP32) { - bitcastVars[name].use_HEAP32++; - } else { - bitcastVars[name].use_HEAPF32++; - } - bitcastVars[name].uses.push_back(node); - } - } - } - } - }); - for (auto iter : bitcastVars) { - const IString& v = iter.first; - BitcastData& info = iter.second; - // good variables define only one type, use only one type, have definitions and uses, and define as a different type than they use - if (info.define_HEAP32*info.define_HEAPF32 == 0 && info.use_HEAP32*info.use_HEAPF32 == 0 && - info.define_HEAP32+info.define_HEAPF32 > 0 && info.use_HEAP32+info.use_HEAPF32 > 0 && - info.define_HEAP32*info.use_HEAP32 == 0 && info.define_HEAPF32*info.use_HEAPF32 == 0 && - asmData.isLocal(v.c_str()) && info.namings == info.define_HEAP32+info.define_HEAPF32+info.use_HEAP32+info.use_HEAPF32) { - IString& correct = info.use_HEAP32 ? HEAPF32 : HEAP32; - for (auto define : info.defines) { - define[3] = define[3][1][3]; - if (correct == HEAP32) { - define[3] = make3(BINARY, OR, define[3], makeNum(0)); - } else { - assert(correct == HEAPF32); - define[3] = makeAsmCoercion(define[3], preciseF32 ? ASM_FLOAT : ASM_DOUBLE); - } - // do we want a simplifybitops on the new values here? - } - for (auto use : info.uses) { - use[2][1][1]->setString(correct.c_str()); - } - AsmType correctType; - switch(asmData.getType(v.c_str())) { - case ASM_INT: correctType = preciseF32 ? ASM_FLOAT : ASM_DOUBLE; break; - case ASM_FLOAT: case ASM_DOUBLE: correctType = ASM_INT; break; - default: assert(0); - } - asmData.setType(v.c_str(), correctType); - } - } - asmData.denormalize(); - } - }; - - std::function<bool (Ref)> emitsBoolean = [&emitsBoolean](Ref node) { - Ref type = node[0]; - if (type == NUM) { - return node[1]->getNumber() == 0 || node[1]->getNumber() == 1; - } - if (type == BINARY) return COMPARE_OPS.has(node[1]); - if (type == UNARY_PREFIX) return node[1] == L_NOT; - if (type == CONDITIONAL) return emitsBoolean(node[2]) && emitsBoolean(node[3]); - return false; - }; - - // expensive | expensive can be turned into expensive ? 1 : expensive, and - // expensive | cheap can be turned into cheap ? 1 : expensive, - // so that we can avoid the expensive computation, if it has no side effects. - auto conditionalize = [&emitsBoolean](Ref ast) { - traversePre(ast, [&emitsBoolean](Ref node) { - const int MIN_COST = 7; - if (node[0] == BINARY && (node[1] == OR || node[1] == AND) && node[3][0] != NUM && node[2][0] != NUM) { - // logical operator on two non-numerical values - Ref left = node[2]; - Ref right = node[3]; - if (!emitsBoolean(left) || !emitsBoolean(right)) return; - bool leftEffects = hasSideEffects(left); - bool rightEffects = hasSideEffects(right); - if (leftEffects && rightEffects) return; // both must execute - // canonicalize with side effects, if any, happening on the left - if (rightEffects) { - if (measureCost(left) < MIN_COST) return; // avoidable code is too cheap - Ref temp = left; - left = right; - right = temp; - } else if (leftEffects) { - if (measureCost(right) < MIN_COST) return; // avoidable code is too cheap - } else { - // no side effects, reorder based on cost estimation - int leftCost = measureCost(left); - int rightCost = measureCost(right); - if (std::max(leftCost, rightCost) < MIN_COST) return; // avoidable code is too cheap - // canonicalize with expensive code on the right - if (leftCost > rightCost) { - Ref temp = left; - left = right; - right = temp; - } - } - // worth it, perform conditionalization - Ref ret; - if (node[1] == OR) { - ret = make3(CONDITIONAL, left, makeNum(1), right); - } else { // & - ret = make3(CONDITIONAL, left, right, makeNum(0)); - } - if (left[0] == UNARY_PREFIX && left[1] == L_NOT) { - ret[1] = flipCondition(left); - Ref temp = ret[2]; - ret[2] = ret[3]; - ret[3] = temp; - } - safeCopy(node, ret); - return; - } - }); - }; - - traverseFunctions(ast, [&](Ref func) { - simplifyIntegerConversions(func); - simplifyOps(func); - traversePre(func, [](Ref node) { - Ref ret = simplifyNotCompsDirect(node); - if (ret.get() != node.get()) { // if we received a different pointer in return, then we need to copy the new value - safeCopy(node, ret); - } - }); - conditionalize(func); - }); -} - -void simplifyIfs(Ref ast) { - traverseFunctions(ast, [](Ref func) { - bool simplifiedAnElse = false; - - traversePre(func, [&simplifiedAnElse](Ref node) { - // simplify if (x) { if (y) { .. } } to if (x ? y : 0) { .. } - if (node[0] == IF) { - Ref body = node[2]; - // recurse to handle chains - while (body[0] == BLOCK) { - Ref stats = body[1]; - if (stats->size() == 0) break; - Ref other = stats->back(); - if (other[0] != IF) { - // our if block does not end with an if. perhaps if have an else we can flip - if (node->size() > 3 && !!node[3] && node[3][0] == BLOCK) { - stats = node[3][1]; - if (stats->size() == 0) break; - other = stats->back(); - if (other[0] == IF) { - // flip node - node[1] = flipCondition(node[1]); - node[2] = node[3]; - node[3] = body; - body = node[2]; - } else break; - } else break; - } - // we can handle elses, but must be fully identical - if (!!node[3] || !!other[3]) { - if (!node[3]) break; - if (!node[3]->deepCompare(other[3])) { - // the elses are different, but perhaps if we flipped a condition we can do better - if (node[3]->deepCompare(other[2])) { - // flip other. note that other may not have had an else! add one if so; we will eliminate such things later - if (!other[3]) other[3] = makeBlock(); - other[1] = flipCondition(other[1]); - Ref temp = other[2]; - other[2] = other[3]; - other[3] = temp; - } else break; - } - } - if (stats->size() > 1) { - // try to commaify - turn everything between the ifs into a comma operator inside the second if - bool ok = true; - for (size_t i = 0; i+1 < stats->size(); i++) { - Ref curr = deStat(stats[i]); - if (!commable(curr)) ok = false; - } - if (!ok) break; - for (int i = stats->size()-2; i >= 0; i--) { - Ref curr = deStat(stats[i]); - other[1] = make2(SEQ, curr, other[1]); - } - Ref temp = makeArray(1); - temp->push_back(other); - stats = body[1] = temp; - } - if (stats->size() != 1) break; - if (!!node[3]) simplifiedAnElse = true; - node[1] = make3(CONDITIONAL, node[1], other[1], makeNum(0)); - body = node[2] = other[2]; - } - } - }); - - if (simplifiedAnElse) { - // there may be fusing opportunities - - // we can only fuse if we remove all uses of the label. if there are - // other ones - if the label check can be reached from elsewhere - - // we must leave it - bool abort = false; - - std::unordered_map<int, int> labelAssigns; - - traversePre(func, [&labelAssigns, &abort](Ref node) { - if (node[0] == ASSIGN && node[2][0] == NAME && node[2][1] == LABEL) { - if (node[3][0] == NUM) { - int value = node[3][1]->getInteger(); - labelAssigns[value] = labelAssigns[value] + 1; - } else { - // label is assigned a dynamic value (like from indirectbr), we cannot do anything - abort = true; - } - } - }); - if (abort) return; - - std::unordered_map<int, int> labelChecks; - - traversePre(func, [&labelChecks, &abort](Ref node) { - if (node[0] == BINARY && node[1] == EQ && node[2][0] == BINARY && node[2][1] == OR && - node[2][2][0] == NAME && node[2][2][1] == LABEL) { - if (node[3][0] == NUM) { - int value = node[3][1]->getInteger(); - labelChecks[value] = labelChecks[value] + 1; - } else { - // label is checked vs a dynamic value (like from indirectbr), we cannot do anything - abort = true; - } - } - }); - if (abort) return; - - int inLoop = 0; // when in a loop, we do not emit label = 0; in the relooper as there is no need - traversePrePost(func, [&inLoop, &labelAssigns, &labelChecks](Ref node) { - if (node[0] == WHILE) inLoop++; - Ref stats = getStatements(node); - if (!!stats && stats->size() > 0) { - for (int i = 0; i < (int)stats->size()-1; i++) { - Ref pre = stats[i]; - Ref post = stats[i+1]; - if (pre[0] == IF && pre->size() > 3 && !!pre[3] && post[0] == IF && (post->size() <= 3 || !post[3])) { - Ref postCond = post[1]; - if (postCond[0] == BINARY && postCond[1] == EQ && - postCond[2][0] == BINARY && postCond[2][1] == OR && - postCond[2][2][0] == NAME && postCond[2][2][1] == LABEL && - postCond[2][3][0] == NUM && postCond[2][3][1]->getNumber() == 0 && - postCond[3][0] == NUM) { - int postValue = postCond[3][1]->getInteger(); - Ref preElse = pre[3]; - if (labelAssigns[postValue] == 1 && labelChecks[postValue] == 1 && preElse[0] == BLOCK && preElse->size() >= 2 && preElse[1]->size() == 1) { - Ref preStat = preElse[1][0]; - if (preStat[0] == STAT && preStat[1][0] == ASSIGN && - preStat[1][1]->isBool(true) && preStat[1][2][0] == NAME && preStat[1][2][1] == LABEL && - preStat[1][3][0] == NUM && preStat[1][3][1]->getNumber() == postValue) { - // Conditions match, just need to make sure the post clears label - if (post[2][0] == BLOCK && post[2]->size() >= 2 && post[2][1]->size() > 0) { - Ref postStat = post[2][1][0]; - bool haveClear = - postStat[0] == STAT && postStat[1][0] == ASSIGN && - postStat[1][1]->isBool(true) && postStat[1][2][0] == NAME && postStat[1][2][1] == LABEL && - postStat[1][3][0] == NUM && postStat[1][3][1]->getNumber() == 0; - if (!inLoop || haveClear) { - // Everything lines up, do it - pre[3] = post[2]; - if (haveClear) pre[3][1]->splice(0, 1); // remove the label clearing - stats->splice(i+1, 1); // remove the post entirely - } - } - } - } - } - } - } - } - }, [&inLoop](Ref node) { - if (node[0] == WHILE) inLoop--; - }); - assert(inLoop == 0); - } - }); -} - -void optimizeFrounds(Ref ast) { - // collapse fround(fround(..)), which can happen due to elimination - // also emit f0 instead of fround(0) (except in returns) - int inReturn = 0; - traversePrePost(ast, [&](Ref node) { - if (node[0] == RETURN) { - inReturn++; - } - }, [&](Ref node) { - if (node[0] == RETURN) { - inReturn--; - } - if (node[0] == CALL && node[1][0] == NAME && node[1][1] == MATH_FROUND) { - Ref arg = node[2][0]; - if (arg[0] == NUM) { - if (!inReturn && arg[1]->getInteger() == 0) { - safeCopy(node, makeName(F0)); - } - } else if (arg[0] == CALL && arg[1][0] == NAME && arg[1][1] == MATH_FROUND) { - safeCopy(node, arg); - } - } - }); -} - -// 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_FLOAT64X2: return "F2"; break; - case ASM_INT8X16: return "I16"; break; - case ASM_INT16X8: return "I8"; 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); - const int size = 256; - 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); - // Add parameters as a first (fake) var (with assignment), so they get taken into consideration - // note: params are special, they can never share a register between them (see later) - Ref fake; - if (!!fun[2] && fun[2]->size()) { - Ref assign = makeNum(0); - // TODO: will be an isEmpty here, can reuse it. - fun[3]->insert(0, make1(VAR, fun[2]->map([&assign](Ref param) { - return &(makeArray(2)->push_back(param).push_back(assign)); - }))); - } - // Replace all var definitions with assignments; we will add var definitions at the top after we registerize - StringSet allVars; - traversePre(fun, [&](Ref node) { - Ref type = node[0]; - if (type == VAR) { - Ref vars = node[1]->filter([](Ref varr) { return varr->size() > 1; }); - if (vars->size() >= 1) { - safeCopy(node, unVarify(vars)); - } else { - safeCopy(node, makeEmpty()); - } - } else if (type == NAME) { - allVars.insert(node[1]->getIString()); - } - }); - removeAllUselessSubNodes(fun); // vacuum? - StringTypeMap regTypes; // reg name -> type - auto getNewRegName = [&](int num, IString name) { - AsmType type = asmData.getType(name); - 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. - // While doing so, check if all a variable's uses are dominated in a simple - // way by a simple assign, if so, then we can assign its register to it - // just for its definition to its last use, and not to the entire toplevel loop, - // we call such variables "optimizable" - StringIntMap varUses; - int level = 1; - std::unordered_map<int, StringSet> levelDominations; // level => set of dominated variables XXX vector? - StringIntMap varLevels; - StringSet possibles; - StringSet unoptimizables; - auto purgeLevel = [&]() { - // Invalidate all dominating on this level, further users make it unoptimizable - for (auto name : levelDominations[level]) { - varLevels[name] = 0; - } - levelDominations[level].clear(); - level--; - }; - std::function<bool (Ref node)> possibilifier = [&](Ref node) { - Ref type = node[0]; - if (type == NAME) { - IString name = node[1]->getIString(); - if (asmData.isLocal(name)) { - varUses[name]++; - if (possibles.has(name) && !varLevels[name]) unoptimizables.insert(name); // used outside of simple domination - } - } else if (type == ASSIGN && node[1]->isBool(true)) { - if (!!node[2] && node[2][0] == NAME) { - IString name = node[2][1]->getIString(); - // if local and not yet used, this might be optimizable if we dominate - // all other uses - if (asmData.isLocal(name) && !varUses[name] && !varLevels[name]) { - possibles.insert(name); - varLevels[name] = level; - levelDominations[level].insert(name); - } - } - } else if (CONTROL_FLOW.has(type)) { - // recurse children, in the context of a loop - if (type == WHILE || type == DO) { - traversePrePostConditional(node[1], possibilifier, [](Ref node){}); - level++; - traversePrePostConditional(node[2], possibilifier, [](Ref node){}); - purgeLevel(); - } else if (type == FOR) { - traversePrePostConditional(node[1], possibilifier, [](Ref node){}); - for (int i = 2; i <= 4; i++) { - level++; - traversePrePostConditional(node[i], possibilifier, [](Ref node){}); - purgeLevel(); - } - } else if (type == IF) { - traversePrePostConditional(node[1], possibilifier, [](Ref node){}); - level++; - traversePrePostConditional(node[2], possibilifier, [](Ref node){}); - purgeLevel(); - if (node->size() > 3 && !!node[3]) { - level++; - traversePrePostConditional(node[3], possibilifier, [](Ref node){}); - purgeLevel(); - } - } else if (type == SWITCH) { - traversePrePostConditional(node[1], possibilifier, [](Ref node){}); - Ref cases = node[2]; - for (size_t i = 0; i < cases->size(); i++) { - level++; - traversePrePostConditional(cases[i][1], possibilifier, [](Ref node){}); - purgeLevel(); - } - } else assert(0);; - return false; // prevent recursion into children, which we already did - } - return true; - }; - traversePrePostConditional(fun, possibilifier, [](Ref node){}); - - StringSet optimizables; - for (auto possible : possibles) { - if (!unoptimizables.has(possible)) optimizables.insert(possible); - } - - // Go through the function's code, assigning 'registers'. - // The only tricky bit is to keep variables locked on a register through loops, - // since they can potentially be returned to. Optimizable variables lock onto - // loops that they enter, unoptimizable variables lock in a conservative way - // into the topmost loop. - // Note that we cannot lock onto a variable in a loop if it was used and free'd - // before! (then they could overwrite us in the early part of the loop). For now - // we just use a fresh register to make sure we avoid this, but it could be - // optimized to check for safe registers (free, and not used in this loop level). - StringStringMap varRegs; // maps variables to the register they will use all their life - std::vector<StringVec> freeRegsClasses; - freeRegsClasses.resize(ASM_NONE); - int nextReg = 1; - StringVec fullNames; - fullNames.push_back(EMPTY); // names start at 1 - std::vector<StringVec> loopRegs; // for each loop nesting level, the list of bound variables - int loops = 0; // 0 is toplevel, 1 is first loop, etc - StringSet activeOptimizables; - StringIntMap optimizableLoops; - StringSet paramRegs; // true if the register is used by a parameter (and so needs no def at start of function; also cannot - // be shared with another param, each needs its own) - auto decUse = [&](IString name) { - if (!varUses[name]) return false; // no uses left, or not a relevant variable - if (optimizables.has(name)) activeOptimizables.insert(name); - IString reg = varRegs[name]; - assert(asmData.isLocal(name)); - StringVec& freeRegs = freeRegsClasses[asmData.getType(name)]; - if (!reg) { - // acquire register - if (optimizables.has(name) && freeRegs.size() > 0 && - !(asmData.isParam(name) && paramRegs.has(freeRegs.back()))) { // do not share registers between parameters - reg = freeRegs.back(); - freeRegs.pop_back(); - } else { - assert(fullNames.size() == nextReg); - reg = getNewRegName(nextReg++, name); - fullNames.push_back(reg); - if (asmData.isParam(name)) paramRegs.insert(reg); - } - varRegs[name] = reg; - } - varUses[name]--; - assert(varUses[name] >= 0); - if (varUses[name] == 0) { - if (optimizables.has(name)) activeOptimizables.erase(name); - // If we are not in a loop, or we are optimizable and not bound to a loop - // (we might have been in one but left it), we can free the register now. - if (loops == 0 || (optimizables.has(name) && !optimizableLoops.has(name))) { - // free register - freeRegs.push_back(reg); - } else { - // when the relevant loop is exited, we will free the register - int relevantLoop = optimizables.has(name) ? (optimizableLoops[name] ? optimizableLoops[name] : 1) : 1; - if ((int)loopRegs.size() <= relevantLoop+1) loopRegs.resize(relevantLoop+1); - loopRegs[relevantLoop].push_back(reg); - } - } - return true; - }; - traversePrePost(fun, [&](Ref node) { // XXX we rely on traversal order being the same as execution order here - Ref type = node[0]; - if (type == NAME) { - IString name = node[1]->getIString(); - if (decUse(name)) { - node[1]->setString(varRegs[name]); - } - } else if (LOOP.has(type)) { - loops++; - // Active optimizables lock onto this loop, if not locked onto one that encloses this one - for (auto name : activeOptimizables) { - if (!optimizableLoops[name]) { - optimizableLoops[name] = loops; - } - } - } - }, [&](Ref node) { - Ref type = node[0]; - if (LOOP.has(type)) { - // Free registers that were locked to this loop - if ((int)loopRegs.size() > loops && loopRegs[loops].size() > 0) { - for (auto loopReg : loopRegs[loops]) { - freeRegsClasses[regTypes[loopReg]].push_back(loopReg); - } - loopRegs[loops].clear(); - } - loops--; - } - }); - if (!!fun[2] && fun[2]->size()) { - fun[2]->setSize(0); // clear params, we will fill with registers - fun[3]->splice(0, 1); // remove fake initial var - } - - asmData.locals.clear(); - asmData.params.clear(); - asmData.vars.clear(); - for (int i = 1; i < nextReg; i++) { - IString reg = fullNames[i]; - AsmType type = regTypes[reg]; - if (!paramRegs.has(reg)) { - asmData.addVar(reg, type); - } else { - asmData.addParam(reg, type); - fun[2]->push_back(makeString(reg)); - } - } - asmData.denormalize(); - }); -} - -// 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) { -#ifdef PROFILING - clock_t tasmdata = 0; - clock_t tflowgraph = 0; - clock_t tlabelfix = 0; - clock_t tbackflow = 0; - clock_t tjuncvaruniqassign = 0; - clock_t tjuncvarsort = 0; - clock_t tregassign = 0; - clock_t tblockproc = 0; - clock_t treconstruct = 0; -#endif - - traverseFunctions(ast, [&](Ref fun) { - -#ifdef PROFILING - clock_t start = clock(); -#endif - - // Do not try to process non-validating methods, like the heap replacer - bool abort = false; - traversePre(fun, [&abort](Ref node) { - if (node[0] == NEW) abort = true; - }); - if (abort) return; - - AsmData asmData(fun); - -#ifdef PROFILING - tasmdata += clock() - start; - start = clock(); -#endif - - // Utilities for allocating register variables. - // We need distinct register pools for each type of variable. - - typedef std::map<int, IString> IntStringMap; - std::vector<IntStringMap> 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); - IntStringMap& allRegs = allRegsByType[type]; - int 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 - - struct Junction { - int id; - std::set<int> inblocks, outblocks; - IOrderedStringSet live; - Junction(int id_) : id(id_) {} - }; - struct Node { - }; - struct Block { - int id, entry, exit; - std::set<int> labels; - std::vector<Ref> nodes; - std::vector<bool> isexpr; - 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; - ContinueBreak() : co(-1), br(-1) {} - ContinueBreak(int co_, int br_) : co(co_), br(br_) {} - }; - typedef std::unordered_map<IString, ContinueBreak> LabelState; - - std::vector<Junction> junctions; - std::vector<Block*> blocks; - int currEntryJunction = -1; - Block* nextBasicBlock = nullptr; - int isInExpr = 0; - std::vector<LabelState> activeLabels; - activeLabels.resize(1); - IString nextLoopLabel; - - 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. - int id = junctions.size(); - junctions.push_back(Junction(id)); - return id; - }; - - std::function<int (int, bool)> joinJunction; - - auto markJunction = [&](int id) { - // Mark current traversal location as a junction. - // This makes a new basic block exiting at this position. - if (id < 0) { - id = addJunction(); - } - joinJunction(id, true); - return id; - }; - - 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.size() == 0); // refusing to abandon an in-progress basic block - if (force || junctions[id].inblocks.size() > 0) { - currEntryJunction = id; - } else { - currEntryJunction = -1; - } - }; - - 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) { - 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 = new Block(); - setJunction(id, force); - return id; - }; - - auto pushActiveLabels = [&](int onContinue, int onBreak) { - // Push the target junctions for continuing/breaking a loop. - // This should be called before traversing into a loop. - assert(activeLabels.size() > 0); - LabelState& prevLabels = activeLabels.back(); - LabelState newLabels = prevLabels; - newLabels[EMPTY] = ContinueBreak(onContinue, onBreak); - if (!!nextLoopLabel) { - newLabels[nextLoopLabel] = ContinueBreak(onContinue, onBreak); - nextLoopLabel = EMPTY; - } - // An unlabelled CONTINUE should jump to innermost loop, - // ignoring any nested SWITCH statements. - if (onContinue < 0 && prevLabels.count(EMPTY) > 0) { - newLabels[EMPTY].co = prevLabels[EMPTY].co; - } - activeLabels.push_back(newLabels); - }; - - auto popActiveLabels = [&]() { - // Pop the target junctions for continuing/breaking a loop. - // This should be called after traversing into a loop. - activeLabels.pop_back(); - }; - - 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 to an existing junction is dead code. - if (type == RETURN) { - joinJunction(EXIT_JUNCTION, false); - } else { - assert(activeLabels.size() > 0); - assert(activeLabels.back().count(label) > 0); // 'jump to unknown label'); - auto targets = activeLabels.back()[label]; - if (type == CONTINUE) { - joinJunction(targets.co, false); - } else if (type == BREAK) { - joinJunction(targets.br, false); - } else { - assert(0); // 'unknown jump node type'); - } - } - currEntryJunction = -1; - }; - - 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'); - IString name = node[1]->getIString(); - if (asmData.isLocal(name)) { - nextBasicBlock->nodes.push_back(node); - nextBasicBlock->isexpr.push_back(isInExpr != 0); - if (nextBasicBlock->kill.count(name) == 0) { - nextBasicBlock->use[name] = 1; - } - } - }; - - 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]->isBool(true)); // 'not a kill node'); - assert(node[2][0] == NAME); //, 'not a kill node'); - IString name = node[2][1]->getIString(); - if (asmData.isLocal(name)) { - nextBasicBlock->nodes.push_back(node); - nextBasicBlock->isexpr.push_back(isInExpr != 0); - nextBasicBlock->kill.insert(name); - } - }; - - std::function<Ref (Ref)> 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]->getNumber() == 0) { - return lookThroughCasts(node[2]); - } - } - return node; - }; - - 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]->getInteger()); - } - }; - - auto isTrueNode = [&](Ref node) { - // Check if the given node is statically truthy. - return (node[0] == NUM && node[1]->getNumber() != 0); - }; - - auto isFalseNode = [&](Ref node) { - // Check if the given node is statically falsy. - return (node[0] == NUM && node[1]->getNumber() == 0); - }; - - std::function<void (Ref)> 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. - 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 < 0 && junctions.size() > 0) { - safeCopy(node, makeEmpty()); - return; - } - - // Traverse each node type according to its particular control-flow semantics. - // TODO: switchify this - if (type == DEFUN) { - int jEntry = markJunction(-1); - assert(jEntry == ENTRY_JUNCTION); - int jExit = addJunction(); - assert(jExit == EXIT_JUNCTION); - for (size_t i = 0; i < node[3]->size(); i++) { - buildFlowGraph(node[3][i]); - } - joinJunction(jExit, false); - } else if (type == IF) { - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - int jEnter = markJunction(-1); - int jExit = addJunction(); - if (!!node[2]) { - // Detect and mark "if (label == N) { <labelled block> }". - 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])); - } - } - buildFlowGraph(node[2]); - } - joinJunction(jExit, false); - setJunction(jEnter, false); - if (node->size() > 3 && !!node[3]) { - buildFlowGraph(node[3]); - } - joinJunction(jExit, false); - } 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]); - if (!!node[2]) { - buildFlowGraph(node[2]); - } - if (!!node[3]) { - buildFlowGraph(node[3]); - } - } else { - buildFlowGraph(node[1]); - int jEnter = markJunction(-1); - int jExit = addJunction(); - if (!!node[2]) { - buildFlowGraph(node[2]); - } - joinJunction(jExit, false); - setJunction(jEnter, false); - if (!!node[3]) { - buildFlowGraph(node[3]); - } - joinJunction(jExit, false); - } - 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(-1); - int jExit = addJunction(); - pushActiveLabels(jLoop, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jLoop, false); - setJunction(jExit, false); - } else { - int jCond = markJunction(-1); - int jLoop = addJunction(); - int jExit = addJunction(); - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - joinJunction(jLoop, false); - pushActiveLabels(jCond, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jCond, false); - // An empty basic-block linking condition exit to loop exit. - setJunction(jLoop, false); - joinJunction(jExit, false); - } - } 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]); - popActiveLabels(); - joinJunction(jExit, false); - } else if (isTrueNode(node[1])) { - int jLoop = markJunction(-1); - int jExit = addJunction(); - pushActiveLabels(jLoop, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jLoop, false); - setJunction(jExit, false); - } else { - int jLoop = markJunction(-1); - int jCond = addJunction(); - int jCondExit = addJunction(); - int jExit = addJunction(); - pushActiveLabels(jCond, jExit); - buildFlowGraph(node[2]); - popActiveLabels(); - joinJunction(jCond, false); - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - joinJunction(jCondExit, false); - joinJunction(jLoop, false); - setJunction(jCondExit, false); - joinJunction(jExit, false); - } - } else if (type == FOR) { - int jTest = addJunction(); - int jBody = addJunction(); - int jStep = addJunction(); - int jExit = addJunction(); - buildFlowGraph(node[1]); - joinJunction(jTest, false); - isInExpr++; - buildFlowGraph(node[2]); - isInExpr--; - joinJunction(jBody, false); - pushActiveLabels(jStep, jExit); - buildFlowGraph(node[4]); - popActiveLabels(); - joinJunction(jStep, false); - buildFlowGraph(node[3]); - 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]->getIString(); - 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(-1); - int jExit = addJunction(); - pushActiveLabels(-1, jExit); - bool hasDefault = false; - for (size_t i = 0; i < node[2]->size(); i++) { - setJunction(jCheckExit, false); - // 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])); - } - } - for (size_t 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, EMPTY); - } else { - 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, false); - } - joinJunction(jExit, false); - popActiveLabels(); - } else if (type == RETURN) { - if (!!node[1]) { - isInExpr++; - buildFlowGraph(node[1]); - isInExpr--; - } - markNonLocalJump(type->getIString(), EMPTY); - } else if (type == BREAK || type == CONTINUE) { - markNonLocalJump(type->getIString(), !!node[1] ? node[1]->getIString() : EMPTY); - } 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 (size_t 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 (size_t 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, EMPTY); - } - } - } 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); - -#ifdef PROFILING - tflowgraph += clock() - start; - start = clock(); -#endif - - 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 - // that the former jumps straight to the later. - - std::map<int, Block*> labelledBlocks; - typedef std::pair<Ref, Block*> Jump; - std::vector<Jump> labelledJumps; - - for (size_t i = 0; i < blocks.size(); i++) { - Block* block = blocks[i]; - // Does it have any labels as preconditions to its entry? - 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 (labelledBlocks.count(labelVal) > 0) { - labelledBlocks.clear(); - labelledJumps.clear(); - goto AFTER_FINDLABELLEDBLOCKS; - } - labelledBlocks[labelVal] = block; - } - // Does it assign a specific label value at exit? - 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. - // This can happen due to indirect branching in llvm output. - if (finalNode[3][0] != NUM) { - labelledBlocks.clear(); - labelledJumps.clear(); - goto AFTER_FINDLABELLEDBLOCKS; - } - 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 (size_t 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) { - labelledBlocks.clear(); - labelledJumps.clear(); - 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); - // Add a fake use of LABEL to keep it alive in predecessor. - block->use[LABEL] = 1; - block->nodes.insert(block->nodes.begin(), makeName(LABEL)); - block->isexpr.insert(block->isexpr.begin(), 1); - } - for (size_t i = 0; i < labelledJumps.size(); i++) { - auto labelVal = labelledJumps[i].first; - auto block = labelledJumps[i].second; - Block* targetBlock = labelledBlocks[labelVal->getInteger()]; - 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.insert(block->id); - } - } - -#ifdef PROFILING - tlabelfix += clock() - start; - start = clock(); -#endif - - // 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. - - auto analyzeJunction = [&](Junction& junc) { - // Update the live set for this junction. - IOrderedStringSet live; - for (auto b : junc.outblocks) { - Block* block = blocks[b]; - IOrderedStringSet& liveSucc = junctions[block->exit].live; - for (auto name : liveSucc) { - if (!block->kill.has(name)) { - live.insert(name); - } - } - for (auto name : block->use) { - live.insert(name.first); - } - } - junc.live = live; - }; - - 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. - auto live = junctions[block->exit].live; - StringIntMap use; - StringSet kill; - StringStringMap link; - StringIntMap lastUseLoc; - StringIntMap firstDeadLoc; - StringIntMap firstKillLoc; - StringIntMap lastKillLoc; - for (auto name : live) { - link[name] = name; - lastUseLoc[name] = block->nodes.size(); - firstDeadLoc[name] = block->nodes.size(); - } - for (int j = block->nodes.size() - 1; j >= 0 ; j--) { - Ref node = block->nodes[j]; - if (node[0] == NAME) { - IString name = node[1]->getIString(); - live.insert(name); - use[name] = j; - if (lastUseLoc.count(name) == 0) { - lastUseLoc[name] = j; - firstDeadLoc[name] = j; - } - } else { - IString name = node[2][1]->getIString(); - // We only keep assignments if they will be subsequently used. - if (live.has(name)) { - kill.insert(name); - use.erase(name); - live.erase(name); - firstDeadLoc[name] = j; - firstKillLoc[name] = j; - if (lastUseLoc.count(name) == 0) { - lastUseLoc[name] = j; - } - 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". - if (link.has(name)) { - IString oldLink = link[name]; - link.erase(name); - if (node[3][0] == NAME) { - if (asmData.isLocal(node[3][1]->getIString())) { - link[node[3][1]->getIString()] = 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. - auto removeUnusedNodes = [&](int j, int n) { - for (auto pair : lastUseLoc) { - pair.second -= n; - } - for (auto pair : firstKillLoc) { - pair.second -= n; - } - for (auto pair : lastKillLoc) { - pair.second -= 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])) { - safeCopy(node, node[3]); - removeUnusedNodes(j, 1); - } else { - int numUsesInExpr = 0; - traversePre(node[3], [&](Ref node) { - if (node[0] == NAME && asmData.isLocal(node[1]->getIString())) { - numUsesInExpr++; - } - }); - safeCopy(node, makeEmpty()); - j = j - numUsesInExpr; - removeUnusedNodes(j, 1 + numUsesInExpr); - } - } - } - } - // XXX efficiency - block->use = use; - block->kill = kill; - block->link = link; - block->lastUseLoc = lastUseLoc; - block->firstDeadLoc = firstDeadLoc; - block->firstKillLoc = firstKillLoc; - block->lastKillLoc = lastKillLoc; - }; - - // Ordered map to work in approximate reverse order of junction appearance - std::set<int> jWorkSet; - std::set<int> bWorkSet; - - // Be sure to visit every junction at least once. - // This avoids missing some vars because we disconnected them - // when processing the labelled jumps. - for (size_t i = EXIT_JUNCTION; i < junctions.size(); i++) { - jWorkSet.insert(i); - for (auto b : junctions[i].inblocks) { - bWorkSet.insert(b); - } - } - // Exit junction never has any live variable changes to propagate - jWorkSet.erase(EXIT_JUNCTION); - - do { - // 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 (jWorkSet.size() > 0) { - auto last = jWorkSet.end(); - --last; - Junction& junc = junctions[*last]; - jWorkSet.erase(last); - IOrderedStringSet oldLive = junc.live; // copy it here, to check for changes later - analyzeJunction(junc); - if (oldLive != junc.live) { - // Live set changed, updated predecessor blocks and junctions. - for (auto b : junc.inblocks) { - bWorkSet.insert(b); - jWorkSet.insert(blocks[b]->entry); - } - } - } - // Now update the blocks based on the calculated live sets. - while (bWorkSet.size() > 0) { - auto last = bWorkSet.end(); - --last; - Block* block = blocks[*last]; - bWorkSet.erase(last); - auto oldUse = block->use; - analyzeBlock(block); - if (oldUse != block->use) { - // The use set changed, re-process the entry junction. - jWorkSet.insert(block->entry); - } - } - } while (jWorkSet.size() > 0); - -#ifdef PROFILING - tbackflow += clock() - start; - start = clock(); -#endif - - // 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 (auto name : asmData.params) { - junctions[ENTRY_JUNCTION].live.insert(name); - } - - // 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"). - - struct JuncVar { - std::vector<bool> conf; - IOrderedStringSet link; - std::unordered_set<int> excl; - int reg; - bool used; - JuncVar() : reg(-1), used(false) {} - }; - size_t numLocals = asmData.locals.size(); - std::unordered_map<IString, size_t> nameToNum; - std::vector<IString> numToName; - nameToNum.reserve(numLocals); - numToName.reserve(numLocals); - for (auto kv : asmData.locals) { - nameToNum[kv.first] = numToName.size(); - numToName.push_back(kv.first); - } - - std::vector<JuncVar> juncVars(numLocals); - for (Junction& junc : junctions) { - for (IString name : junc.live) { - JuncVar& jVar = juncVars[nameToNum[name]]; - jVar.used = true; - jVar.conf.assign(numLocals, false); - } - } - std::map<IString, std::vector<Block*>> possibleBlockConflictsMap; - std::vector<std::pair<size_t, std::vector<Block*>>> possibleBlockConflicts; - std::unordered_map<IString, std::vector<Block*>> possibleBlockLinks; - possibleBlockConflicts.reserve(numLocals); - possibleBlockLinks.reserve(numLocals); - - for (Junction& junc : junctions) { - // Pre-compute the possible conflicts and links for each block rather - // than checking potentially impossible options for each var - possibleBlockConflictsMap.clear(); - possibleBlockConflicts.clear(); - possibleBlockLinks.clear(); - for (auto b : junc.outblocks) { - Block* block = blocks[b]; - Junction& jSucc = junctions[block->exit]; - for (auto name : jSucc.live) { - possibleBlockConflictsMap[name].push_back(block); - } - for (auto name_linkname : block->link) { - if (name_linkname.first != name_linkname.second) { - possibleBlockLinks[name_linkname.first].push_back(block); - } - } - } - // Find the live variables in this block, mark them as unnecessary to - // check for conflicts (we mark all live vars as conflicting later) - std::vector<size_t> liveJVarNums; - liveJVarNums.reserve(junc.live.size()); - for (auto name : junc.live) { - size_t jVarNum = nameToNum[name]; - liveJVarNums.push_back(jVarNum); - possibleBlockConflictsMap.erase(name); - } - // Extract just the variables we might want to check for conflicts - for (auto kv : possibleBlockConflictsMap) { - possibleBlockConflicts.push_back(std::make_pair(nameToNum[kv.first], kv.second)); - } - - for (size_t jVarNum : liveJVarNums) { - JuncVar& jvar = juncVars[jVarNum]; - IString name = numToName[jVarNum]; - // It conflicts with all other names live at this junction. - for (size_t liveJVarNum : liveJVarNums) { - jvar.conf[liveJVarNum] = true; - } - jvar.conf[jVarNum] = false; // except for itself, of course - - // It conflicts with any output vars of successor blocks, - // if they're assigned before it goes dead in that block. - for (auto jvarnum_blocks : possibleBlockConflicts) { - size_t otherJVarNum = jvarnum_blocks.first; - IString otherName = numToName[otherJVarNum]; - for (auto block : jvarnum_blocks.second) { - if (block->lastKillLoc[otherName] < block->firstDeadLoc[name]) { - jvar.conf[otherJVarNum] = true; - juncVars[otherJVarNum].conf[jVarNum] = true; - break; - } - } - } - - // It links with any linkages in the outgoing blocks. - for (auto block: possibleBlockLinks[name]) { - IString linkName = block->link[name]; - jvar.link.insert(linkName); - juncVars[nameToNum[linkName]].link.insert(name); - } - } - } - -#ifdef PROFILING - tjuncvaruniqassign += clock() - start; - start = clock(); -#endif - - // 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. - - std::vector<size_t> sortedJVarNums; - sortedJVarNums.reserve(juncVars.size()); - std::vector<size_t> jVarConfCounts(numLocals); - for (size_t jVarNum = 0; jVarNum < juncVars.size(); jVarNum++) { - JuncVar& jVar = juncVars[jVarNum]; - if (!jVar.used) continue; - jVarConfCounts[jVarNum] = std::count(jVar.conf.begin(), jVar.conf.end(), true); - sortedJVarNums.push_back(jVarNum); - } - std::sort(sortedJVarNums.begin(), sortedJVarNums.end(), [&](const size_t vi1, const size_t vi2) { - // sort by # of conflicts - if (jVarConfCounts[vi1] < jVarConfCounts[vi2]) return true; - if (jVarConfCounts[vi1] == jVarConfCounts[vi2]) return numToName[vi1] < numToName[vi2]; - return false; - }); - -#ifdef PROFILING - tjuncvarsort += clock() - start; - start = clock(); -#endif - - // 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. - - std::function<bool (IString, int)> 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 = juncVars[nameToNum[name]]; - if (jv.reg > 0) { - return jv.reg == reg; - } - if (jv.excl.count(reg) > 0) { - return false; - } - jv.reg = reg; - // Exclude use of this register at all conflicting variables. - for (size_t confNameNum = 0; confNameNum < jv.conf.size(); confNameNum++) { - if (jv.conf[confNameNum]) { - juncVars[confNameNum].excl.insert(reg); - } - } - // Try to propagate it into linked variables. - // It's not an error if we can't. - for (auto linkName : jv.link) { - tryAssignRegister(linkName, reg); - } - return true; - }; - for (size_t jVarNum : sortedJVarNums) { - // It may already be assigned due to linked-variable propagation. - if (juncVars[jVarNum].reg > 0) { - continue; - } - IString name = numToName[jVarNum]; - // Try to use existing registers first. - auto& allRegs = allRegsByType[asmData.getType(name)]; - bool moar = false; - for (auto reg : allRegs) { - if (tryAssignRegister(name, reg.first)) { - moar = true; - break; - } - } - if (moar) continue; - // They're all taken, create a new one. - tryAssignRegister(name, createReg(name)); - } - -#ifdef PROFILING - tregassign += clock() - start; - start = clock(); -#endif - - // 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 (size_t i = 0; i < blocks.size(); i++) { - Block* block = blocks[i]; - if (block->nodes.size() == 0) continue; - 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_map<int, int> inputDeadLoc; - std::unordered_map<int, IString> inputVarsByReg; - for (auto name : jExit.live) { - if (!block->kill.has(name)) { - inputVars.insert(name); - int reg = juncVars[nameToNum[name]].reg; - assert(reg > 0); // 'input variable doesnt have a register'); - inputDeadLoc[reg] = block->firstDeadLoc[name]; - inputVarsByReg[reg] = name; - } - } - for (auto pair : block->use) { - IString name = pair.first; - if (!inputVars.has(name)) { - inputVars.insert(name); - int reg = juncVars[nameToNum[name]].reg; - assert(reg > 0); // 'input variable doesnt have a register'); - inputDeadLoc[reg] = block->firstDeadLoc[name]; - inputVarsByReg[reg] = name; - } - } - // 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". - StringIntMap assignedRegs; - auto freeRegsByTypePre = allRegsByType; // XXX copy - // Begin with all live vars assigned per the exit junction. - for (auto name : jExit.live) { - int reg = juncVars[nameToNum[name]].reg; - assert(reg > 0); // 'output variable doesnt have a register'); - assignedRegs[name] = reg; - freeRegsByTypePre[asmData.getType(name)].erase(reg); // XXX assert? - } - std::vector<std::vector<int>> freeRegsByType; - freeRegsByType.resize(freeRegsByTypePre.size()); - for (size_t 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<std::pair<int, Ref>> 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(); - IntStringMap& allRegs = allRegsByType[asmData.getType(name)]; - std::vector<int>& 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 <= 0) { - if (inputVars.has(name) && j <= block->firstDeadLoc[name]) { - // Assignment to an input variable, must use pre-assigned reg. - reg = juncVars[nameToNum[name]].reg; - assignedRegs[name] = reg; - for (int k = freeRegs.size() - 1; k >= 0; k--) { - if (freeRegs[k] == reg) { - 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 (int k = freeRegs.size() - 1; k >= 0; k--) { - reg = freeRegs[k]; - // Check for conflict with input registers. - if (inputDeadLoc.count(reg) > 0) { - if (block->firstKillLoc[name] <= inputDeadLoc[reg]) { - if (name != inputVarsByReg[reg]) { - continue; - } - } - } - // Found one! - assignedRegs[name] = reg; - assert(reg > 0); - freeRegs.erase(freeRegs.begin() + k); - break; - } - // If we didn't find a suitable register, create a new one. - if (assignedRegs[name] <= 0) { - reg = createReg(name); - assignedRegs[name] = reg; - } - } - } - node[1]->setString(allRegs[reg]); - } else { - // A kill. This frees the assigned register. - assert(reg > 0); //, 'live variable doesnt have a reg?') - node[2][1]->setString(allRegs[reg]); - freeRegs.push_back(reg); - assignedRegs.erase(name); - if (node[3][0] == NAME && asmData.isLocal(node[3][1]->getIString())) { - maybeRemoveNodes.push_back(std::pair<int, Ref>(j, node)); - } - } - } - // If we managed to create any "x=x" assignments, remove them. - for (size_t j = 0; j < maybeRemoveNodes.size(); j++) { - Ref node = maybeRemoveNodes[j].second; - if (node[2][1] == node[3][1]) { - if (block->isexpr[maybeRemoveNodes[j].first]) { - safeCopy(node, node[2]); - } else { - safeCopy(node, makeEmpty()); - } - } - } - } - -#ifdef PROFILING - tblockproc += clock() - start; - start = clock(); -#endif - - // Assign registers to function params based on entry junction - - StringSet paramRegs; - if (!!fun[2]) { - for (size_t i = 0; i < fun[2]->size(); i++) { - auto& allRegs = allRegsByType[asmData.getType(fun[2][i]->getIString())]; - fun[2][i]->setString(allRegs[juncVars[nameToNum[fun[2][i]->getIString()]].reg]); - paramRegs.insert(fun[2][i]->getIString()); - } - } - - // That's it! - // Re-construct the function with appropriate variable definitions. - - asmData.locals.clear(); - asmData.params.clear(); - asmData.vars.clear(); - for (int i = 1; i < nextReg; i++) { - for (size_t type = 0; type < allRegsByType.size(); type++) { - if (allRegsByType[type].count(i) > 0) { - IString reg = allRegsByType[type][i]; - if (!paramRegs.has(reg)) { - asmData.addVar(reg, intToAsmType(type)); - } else { - asmData.addParam(reg, intToAsmType(type)); - } - break; - } - } - } - asmData.denormalize(); - - removeAllUselessSubNodes(fun); // XXX vacuum? vacuum(fun); - -#ifdef PROFILING - treconstruct += clock() - start; - start = clock(); -#endif - - }); -#ifdef PROFILING - errv(" RH stages: a:%li fl:%li lf:%li bf:%li jvua:%li jvs:%li jra:%li bp:%li r:%li", - tasmdata, tflowgraph, tlabelfix, tbackflow, tjuncvaruniqassign, tjuncvarsort, tregassign, tblockproc, treconstruct); -#endif -} -// end registerizeHarder - -// minified names generation -StringSet RESERVED("do if in for new try var env let"); -const char *VALID_MIN_INITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$"; -const char *VALID_MIN_LATERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$0123456789"; - -StringVec minifiedNames; -std::vector<int> minifiedState; - -void ensureMinifiedNames(int n) { // make sure the nth index in minifiedNames exists. done 100% deterministically - static int VALID_MIN_INITS_LEN = strlen(VALID_MIN_INITS); - static int VALID_MIN_LATERS_LEN = strlen(VALID_MIN_LATERS); - - while ((int)minifiedNames.size() < n+1) { - // generate the current name - std::string name; - name += VALID_MIN_INITS[minifiedState[0]]; - for (size_t i = 1; i < minifiedState.size(); i++) { - name += VALID_MIN_LATERS[minifiedState[i]]; - } - IString str(strdupe(name.c_str())); // leaked! - if (!RESERVED.has(str)) minifiedNames.push_back(str); - // increment the state - size_t i = 0; - while (1) { - minifiedState[i]++; - if (minifiedState[i] < (i == 0 ? VALID_MIN_INITS_LEN : VALID_MIN_LATERS_LEN)) break; - // overflow - minifiedState[i] = 0; - i++; - if (i == minifiedState.size()) minifiedState.push_back(-1); // will become 0 after increment in next loop head - } - } -} - -void minifyLocals(Ref ast) { - assert(!!extraInfo); - IString GLOBALS("globals"); - assert(extraInfo->has(GLOBALS)); - Ref globals = extraInfo[GLOBALS]; - - if (minifiedState.size() == 0) minifiedState.push_back(0); - - traverseFunctions(ast, [&globals](Ref fun) { - // Analyse the asmjs to figure out local variable names, - // but operate on the original source tree so that we don't - // miss any global names in e.g. variable initializers. - AsmData asmData(fun); - asmData.denormalize(); // TODO: we can avoid modifying at all here - we just need a list of local vars+params - - StringStringMap newNames; - StringSet usedNames; - - // Find all the globals that we need to minify using - // pre-assigned names. Don't actually minify them yet - // as that might interfere with local variable names. - traversePre(fun, [&](Ref node) { - if (node[0] == NAME) { - IString name = node[1]->getIString(); - if (!asmData.isLocal(name)) { - if (globals->has(name)) { - IString minified = globals[name]->getIString(); - assert(!!minified); - newNames[name] = minified; - usedNames.insert(minified); - } - } - } - }); - - // The first time we encounter a local name, we assign it a - // minified name that's not currently in use. Allocating on - // demand means they're processed in a predictable order, - // which is very handy for testing/debugging purposes. - int nextMinifiedName = 0; - auto getNextMinifiedName = [&]() { - IString minified; - while (1) { - ensureMinifiedNames(nextMinifiedName); - minified = minifiedNames[nextMinifiedName++]; - // TODO: we can probably remove !isLocalName here - if (!usedNames.has(minified) && !asmData.isLocal(minified)) { - return minified; - } - } - }; - - // We can also minify loop labels, using a separate namespace - // to the variable declarations. - StringStringMap newLabels; - int nextMinifiedLabel = 0; - auto getNextMinifiedLabel = [&]() { - ensureMinifiedNames(nextMinifiedLabel); - return minifiedNames[nextMinifiedLabel++]; - }; - - // Traverse and minify all names. - if (globals->has(fun[1]->getIString())) { - fun[1]->setString(globals[fun[1]->getIString()]->getIString()); - assert(!!fun[1]); - } - if (!!fun[2]) { - for (size_t i = 0; i < fun[2]->size(); i++) { - IString minified = getNextMinifiedName(); - newNames[fun[2][i]->getIString()] = minified; - fun[2][i]->setString(minified); - } - } - traversePre(fun[3], [&](Ref node) { - Ref type = node[0]; - if (type == NAME) { - IString name = node[1]->getIString(); - IString minified = newNames[name]; - if (!!minified) { - node[1]->setString(minified); - } else if (asmData.isLocal(name)) { - minified = getNextMinifiedName(); - newNames[name] = minified; - node[1]->setString(minified); - } - } else if (type == VAR) { - for (size_t i = 0; i < node[1]->size(); i++) { - Ref defn = node[1][i]; - IString name = defn[0]->getIString(); - if (!(newNames.has(name))) { - newNames[name] = getNextMinifiedName(); - } - defn[0]->setString(newNames[name]); - } - } else if (type == LABEL) { - IString name = node[1]->getIString(); - if (!newLabels.has(name)) { - newLabels[name] = getNextMinifiedLabel(); - } - node[1]->setString(newLabels[name]); - } else if (type == BREAK || type == CONTINUE) { - if (node->size() > 1 && !!node[1]) { - node[1]->setString(newLabels[node[1]->getIString()]); - } - } - }); - }); -} - -void asmLastOpts(Ref ast) { - std::vector<Ref> statsStack; - traverseFunctions(ast, [&](Ref fun) { - traversePrePost(fun, [&](Ref node) { - Ref type = node[0]; - Ref stats = getStatements(node); - if (!!stats) statsStack.push_back(stats); - if (CONDITION_CHECKERS.has(type)) { - node[1] = simplifyCondition(node[1]); - } - if (type == WHILE && node[1][0] == NUM && node[1][1]->getNumber() == 1 && node[2][0] == BLOCK && node[2]->size() == 2) { - // This is at the end of the pipeline, we can assume all other optimizations are done, and we modify loops - // into shapes that might confuse other passes - - // while (1) { .. if (..) { break } } ==> do { .. } while(..) - Ref stats = node[2][1]; - Ref last = stats->back(); - if (!!last && last[0] == IF && (last->size() < 4 || !last[3]) && last[2][0] == BLOCK && !!last[2][1][0]) { - Ref lastStats = last[2][1]; - int lastNum = lastStats->size(); - Ref lastLast = lastStats[lastNum-1]; - if (!(lastLast[0] == BREAK && !lastLast[1])) return;// if not a simple break, dangerous - for (int i = 0; i < lastNum; i++) { - if (lastStats[i][0] != STAT && lastStats[i][0] != BREAK) return; // something dangerous - } - // ok, a bunch of statements ending in a break - bool abort = false; - int stack = 0; - int breaks = 0; - traversePrePost(stats, [&](Ref node) { - Ref type = node[0]; - if (type == CONTINUE) { - if (stack == 0 || !!node[1]) { // abort if labeled (we do not analyze labels here yet), or a continue directly on us - abort = true; - } - } else if (type == BREAK) { - if (stack == 0 || !!node[1]) { // relevant if labeled (we do not analyze labels here yet), or a break directly on us - breaks++; - } - } else if (LOOP.has(type)) { - stack++; - } - }, [&](Ref node) { - if (LOOP.has(node[0])) { - stack--; - } - }); - if (abort) return; - assert(breaks > 0); - if (lastStats->size() > 1 && breaks != 1) return; // if we have code aside from the break, we can only move it out if there is just one break - if (statsStack.size() < 1) return; // no chance we have this stats on hand - // start to optimize - if (lastStats->size() > 1) { - Ref parent = statsStack.back(); - int me = parent->indexOf(node); - if (me < 0) return; // not always directly on a stats, could be in a label for example - parent->insert(me+1, lastStats->size()-1); - for (size_t i = 0; i+1 < lastStats->size(); i++) { - parent[me+1+i] = lastStats[i]; - } - } - Ref conditionToBreak = last[1]; - stats->pop_back(); - node[0]->setString(DO); - node[1] = simplifyNotCompsDirect(make2(UNARY_PREFIX, L_NOT, conditionToBreak)); - } - } else if (type == BINARY) { - if (node[1] == AND) { - if (node[3][0] == UNARY_PREFIX && node[3][1] == MINUS && node[3][2][0] == NUM && node[3][2][1]->getNumber() == 1) { - // Change &-1 into |0, at this point the hint is no longer needed - node[1]->setString(OR); - node[3] = node[3][2]; - node[3][1]->setNumber(0); - } - } else if (node[1] == MINUS && node[3][0] == UNARY_PREFIX) { - // avoid X - (-Y) because some minifiers buggily emit X--Y which is invalid as -- can be a unary. Transform to - // X + Y - if (node[3][1] == MINUS) { // integer - node[1]->setString(PLUS); - node[3] = node[3][2]; - } else if (node[3][1] == PLUS) { // float - if (node[3][2][0] == UNARY_PREFIX && node[3][2][1] == MINUS) { - node[1]->setString(PLUS); - node[3][2] = node[3][2][2]; - } - } - } - } - }, [&](Ref node) { - if (statsStack.size() > 0) { - Ref stats = getStatements(node); - if (!!stats) statsStack.pop_back(); - } - }); - // convert { singleton } into singleton - traversePre(fun, [](Ref node) { - if (node[0] == BLOCK && !!getStatements(node) && node[1]->size() == 1) { - safeCopy(node, node[1][0]); - } - }); - // convert L: do { .. } while(0) into L: { .. } - traversePre(fun, [](Ref node) { - if (node[0] == LABEL && node[1]->isString() /* careful of var label = 5 */ && - node[2][0] == DO && node[2][1][0] == NUM && node[2][1][1]->getNumber() == 0 && node[2][2][0] == BLOCK) { - // there shouldn't be any continues on this, not direct break or continue - IString label = node[1]->getIString(); - bool abort = false; - int breakCaptured = 0, continueCaptured = 0; - traversePrePost(node[2][2], [&](Ref node) { - if (node[0] == CONTINUE) { - if (!node[1] && !continueCaptured) { - abort = true; - } else if (node[1]->isString() && node[1]->getIString() == label) { - abort = true; - } - } - if (node[0] == BREAK && !node[1] && !breakCaptured) { - abort = true; - } - if (BREAK_CAPTURERS.has(node[0])) { - breakCaptured++; - } - if (CONTINUE_CAPTURERS.has(node[0])) { - continueCaptured++; - } - }, [&](Ref node) { - if (BREAK_CAPTURERS.has(node[0])) { - breakCaptured--; - } - if (CONTINUE_CAPTURERS.has(node[0])) { - continueCaptured--; - } - }); - if (abort) return; - safeCopy(node[2], node[2][2]); - } - }); - }); -} - -// Contrary to the name this does not eliminate actual dead functions, only -// those marked as such with DEAD_FUNCTIONS -void eliminateDeadFuncs(Ref ast) { - assert(!!extraInfo); - IString DEAD_FUNCTIONS("dead_functions"); - IString ABORT("abort"); - assert(extraInfo->has(DEAD_FUNCTIONS)); - StringSet deadFunctions; - for (size_t i = 0; i < extraInfo[DEAD_FUNCTIONS]->size(); i++) { - deadFunctions.insert(extraInfo[DEAD_FUNCTIONS][i]->getIString()); - } - traverseFunctions(ast, [&](Ref fun) { - if (!deadFunctions.has(fun[1].get()->getIString())) { - return; - } - AsmData asmData(fun); - fun[3]->setSize(1); - fun[3][0] = make1(STAT, make2(CALL, makeName(ABORT), &(makeArray(1))->push_back(makeNum(-1)))); - asmData.vars.clear(); - asmData.denormalize(); - }); -} - |