diff options
Diffstat (limited to 'src/analyzer.js')
-rw-r--r-- | src/analyzer.js | 212 |
1 files changed, 162 insertions, 50 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index 0ad3e017..2cc46ab6 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -9,7 +9,6 @@ var VAR_NATIVIZED = 'nativized'; var VAR_EMULATED = 'emulated'; var ENTRY_IDENT = toNiceIdent('%0'); -var ENTRY_IDENT_IDS = set(0, 1); // XXX function recomputeLines(func) { func.lines = func.labels.map(function(label) { return label.lines }).reduce(concatenator, []); @@ -18,8 +17,10 @@ function recomputeLines(func) { // Handy sets var BRANCH_INVOKE = set('branch', 'invoke'); +var LABEL_ENDERS = set('branch', 'return', 'switch'); var SIDE_EFFECT_CAUSERS = set('call', 'invoke', 'atomic'); var UNUNFOLDABLE = set('value', 'structvalue', 'type', 'phiparam'); +var I64_DOUBLE_FLIP = { i64: 'double', double: 'i64' }; // Analyzer @@ -88,7 +89,7 @@ function analyzer(data, sidePass) { // Internal line if (!currLabelFinished) { item.functions.slice(-1)[0].labels.slice(-1)[0].lines.push(subItem); // If this line fails, perhaps missing a label? - if (subItem.intertype === 'branch') { + if (subItem.intertype in LABEL_ENDERS) { currLabelFinished = true; } } else { @@ -99,7 +100,86 @@ function analyzer(data, sidePass) { } } delete item.items; + this.forwardItem(item, 'CastAway'); + } + }); + + // CastAway - try to remove bitcasts of double<-->i64, which LLVM sometimes generates unnecessarily + // (load a double, convert to i64, use as i64). + // We optimize this by checking if there are such bitcasts. If so we create a shadow + // variable that is of the other type, and use that in the relevant places. (As SSA, this is valid, and + // variable elimination later will remove the double load if it is no longer needed.) + // + // Note that aside from being an optimization, this is needed for correctness in some cases: If code + // assumes it can bitcast a double to an i64 and back and forth without loss, that may be violated + // due to NaN canonicalization. + substrate.addActor('CastAway', { + processItem: function(item) { this.forwardItem(item, 'Legalizer'); + if (USE_TYPED_ARRAYS != 2) return; + + item.functions.forEach(function(func) { + var has = false; + func.labels.forEach(function(label) { + var lines = label.lines; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.intertype == 'bitcast' && line.type in I64_DOUBLE_FLIP) { + has = true; + } + } + }); + if (!has) return; + // there are i64<-->double bitcasts, create shadows for everything + var shadowed = {}; + func.labels.forEach(function(label) { + var lines = label.lines; + var i = 0; + while (i < lines.length) { + var lines = label.lines; + var line = lines[i]; + if (line.intertype == 'load' && line.type in I64_DOUBLE_FLIP) { + if (line.pointer.intertype != 'value') { i++; continue } // TODO + shadowed[line.assignTo] = 1; + var shadow = line.assignTo + '$$SHADOW'; + var flip = I64_DOUBLE_FLIP[line.type]; + lines.splice(i + 1, 0, { // if necessary this element will be legalized in the next phase + tokens: null, + indent: 2, + lineNum: line.lineNum + 0.5, + assignTo: shadow, + intertype: 'load', + pointerType: flip + '*', + type: flip, + valueType: flip, + pointer: { + intertype: 'value', + ident: line.pointer.ident, + type: flip + '*' + }, + align: line.align, + ident: line.ident + }); + // note: no need to update func.lines, it is generated in a later pass + i++; + } + i++; + } + }); + // use shadows where possible + func.labels.forEach(function(label) { + var lines = label.lines; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.intertype == 'bitcast' && line.type in I64_DOUBLE_FLIP && line.ident in shadowed) { + var shadow = line.ident + '$$SHADOW'; + line.params[0].ident = shadow; + line.params[0].type = line.type; + line.type2 = line.type; + } + } + }); + }); } }); @@ -121,8 +201,13 @@ function analyzer(data, sidePass) { // Legalization if (USE_TYPED_ARRAYS == 2) { function getLegalVars(base, bits, allowLegal) { - if (allowLegal && bits <= 32) return [{ ident: base, bits: bits }]; + bits = bits || 32; // things like pointers are all i32, but show up as 0 bits from getBits + if (allowLegal && bits <= 32) return [{ ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits }]; if (isNumber(base)) return getLegalLiterals(base, bits); + if (base[0] == '{') { + warnOnce('seeing source of illegal data ' + base + ', likely an inline struct - assuming zeroinit'); + return getLegalLiterals('0', bits); + } var ret = new Array(Math.ceil(bits/32)); var i = 0; if (base == 'zeroinitializer' || base == 'undef') base = 0; @@ -169,12 +254,15 @@ function analyzer(data, sidePass) { var factor = (next - prev)/(4*toAdd.length+3); for (var k = 0; k < toAdd.length; k++) { toAdd[k].lineNum = prev + ((k+1)*factor); + assert(k == 0 || toAdd[k].lineNum > toAdd[k-1].lineNum); } } function removeAndAdd(lines, i, toAdd) { var item = lines[i]; interpLines(lines, i, toAdd); Array.prototype.splice.apply(lines, [i, 1].concat(toAdd)); + if (i > 0) assert(lines[i].lineNum > lines[i-1].lineNum); + if (i + toAdd.length < lines.length) assert(lines[i + toAdd.length - 1].lineNum < lines[i + toAdd.length].lineNum); return toAdd.length; } function legalizeFunctionParameters(params) { @@ -223,12 +311,16 @@ function analyzer(data, sidePass) { for (var i = 0; i < item.params.length; i++) { if (item.params[i].type == 'i64') item.params[i].type = 'i32'; } + } else if (item.intertype == 'inttoptr') { + var input = item.params[0]; + if (input.type == 'i64') input.type = 'i32'; // inttoptr can only care about 32 bits anyhow since pointers are 32-bit } if (isIllegalType(item.valueType) || isIllegalType(item.type)) { isIllegal = true; - } - if ((item.intertype == 'load' || item.intertype == 'store') && isStructType(item.valueType)) { + } else if ((item.intertype == 'load' || item.intertype == 'store') && isStructType(item.valueType)) { isIllegal = true; // storing an entire structure is illegal + } else if (item.intertype == 'mathop' && item.op == 'trunc' && isIllegalType(item.params[1].ident)) { // trunc stores target value in second ident + isIllegal = true; } }); if (!isIllegal) { @@ -285,7 +377,7 @@ function analyzer(data, sidePass) { var elements = getLegalParams([item.value], bits)[0]; var j = 0; elements.forEach(function(element) { - var tempVar = '$st$' + i + '$' + j; + var tempVar = '$st$' + (tempId++) + '$' + j; toAdd.push({ intertype: 'getelementptr', assignTo: tempVar, @@ -315,12 +407,14 @@ function analyzer(data, sidePass) { } // call, return: Return the first 32 bits, the rest are in temp case 'call': { - bits = getBits(value.type); - var elements = getLegalVars(item.assignTo, bits); var toAdd = [value]; // legalize parameters legalizeFunctionParameters(value.params); - if (value.assignTo && isIllegalType(item.type)) { + // legalize return value, if any + var returnType = getReturnType(item.type); + if (value.assignTo && isIllegalType(returnType)) { + bits = getBits(returnType); + var elements = getLegalVars(item.assignTo, bits); // legalize return value value.assignTo = elements[0].ident; for (var j = 1; j < elements.length; j++) { @@ -396,7 +490,7 @@ function analyzer(data, sidePass) { var j = 0; var toAdd = []; elements.forEach(function(element) { - var tempVar = '$st$' + i + '$' + j; + var tempVar = '$ld$' + (tempId++) + '$' + j; toAdd.push({ intertype: 'getelementptr', assignTo: tempVar, @@ -459,6 +553,23 @@ function analyzer(data, sidePass) { i++; continue; // special case, handled in makeComparison } + case 'va_arg': { + assert(value.type == 'i64'); + assert(value.value.type == 'i32*', value.value.type); + i += removeAndAdd(label.lines, i, range(2).map(function(x) { + return { + intertype: 'va_arg', + assignTo: value.assignTo + '$' + x, + type: 'i32', + value: { + intertype: 'value', + ident: value.value.ident, // We read twice from the same i32* var, incrementing // + '$' + x, + type: 'i32*' + } + }; + })); + continue; + } case 'extractvalue': { // XXX we assume 32-bit alignment in extractvalue/insertvalue, // but in theory they can run on packed structs too (see use getStructuralTypePartBits) // potentially legalize the actual extracted value too if it is >32 bits, not just the extraction in general @@ -617,8 +728,8 @@ function analyzer(data, sidePass) { for (var i = 0; i < targetElements.length; i++) { if (i > 0) { switch(value.variant) { - case 'eq': ident += '&&'; break; - case 'ne': ident += '||'; break; + case 'eq': ident += '&'; break; + case 'ne': ident += '|'; break; default: throw 'unhandleable illegal icmp: ' + value.variant; } } @@ -635,7 +746,7 @@ function analyzer(data, sidePass) { break; } case 'add': case 'sub': case 'sdiv': case 'udiv': case 'mul': case 'urem': case 'srem': - case 'uitofp': case 'sitofp': { + case 'uitofp': case 'sitofp': case 'fptosi': case 'fptoui': { // We cannot do these in parallel chunks of 32-bit operations. We will handle these in processMathop i++; continue; @@ -643,20 +754,20 @@ function analyzer(data, sidePass) { default: throw 'Invalid mathop for legalization: ' + [value.op, item.lineNum, dump(item)]; } // Do the legalization - var sourceElements; - if (sourceBits <= 32) { - // The input is a legal type - sourceElements = [{ ident: value.params[0].ident, bits: sourceBits }]; - } else { - sourceElements = getLegalVars(value.params[0].ident, sourceBits); - } + var sourceElements = getLegalVars(value.params[0].ident, sourceBits, true); if (!isNumber(shifts)) { // We can't statically legalize this, do the operation at runtime TODO: optimize assert(sourceBits == 64, 'TODO: handle nonconstant shifts on != 64 bits'); + assert(PRECISE_I64_MATH, 'Must have precise i64 math for non-constant 64-bit shifts'); + Types.preciseI64MathUsed = 1; value.intertype = 'value'; - value.ident = 'Runtime' + (ASM_JS ? '_' : '.') + 'bitshift64(' + sourceElements[0].ident + ', ' + - sourceElements[1].ident + ',"' + value.op + '",' + value.params[1].ident + '$0);' + - 'var ' + value.assignTo + '$0 = ' + makeGetTempDouble(0) + ', ' + value.assignTo + '$1 = ' + makeGetTempDouble(1) + ';'; + value.ident = 'var ' + value.assignTo + '$0 = ' + + asmCoercion('_bitshift64' + value.op[0].toUpperCase() + value.op.substr(1) + '(' + + asmCoercion(sourceElements[0].ident, 'i32') + ',' + + asmCoercion(sourceElements[1].ident, 'i32') + ',' + + asmCoercion(value.params[1].ident + '$0', 'i32') + ')', 'i32' + ) + ';' + + 'var ' + value.assignTo + '$1 = tempRet0;'; value.assignTo = null; i++; continue; @@ -678,9 +789,9 @@ function analyzer(data, sidePass) { params: [(signed && j + whole > sourceElements.length) ? signedKeepAlive : null], type: 'i32', }; - if (j == 0 && isUnsignedOp(value.op) && sourceBits < 32) { + if (j == 0 && sourceBits < 32) { // zext sign correction - result.ident = makeSignOp(result.ident, 'i' + sourceBits, 'un', 1, 1); + result.ident = makeSignOp(result.ident, 'i' + sourceBits, isUnsignedOp(value.op) ? 'un' : 're', 1, 1); } if (fraction != 0) { var other = { @@ -950,6 +1061,7 @@ function analyzer(data, sidePass) { // Function parameters func.params.forEach(function(param) { if (param.intertype !== 'varargs') { + if (func.variables[param.ident]) warn('cannot have duplicate variable names: ' + param.ident); // toNiceIdent collisions? func.variables[param.ident] = { ident: param.ident, type: param.type, @@ -963,6 +1075,7 @@ function analyzer(data, sidePass) { // Normal variables func.lines.forEach(function(item, i) { if (item.assignTo) { + if (func.variables[item.assignTo]) warn('cannot have duplicate variable names: ' + item.assignTo); // toNiceIdent collisions? var variable = func.variables[item.assignTo] = { ident: item.assignTo, type: item.type, @@ -973,6 +1086,9 @@ function analyzer(data, sidePass) { if (variable.origin === 'alloca') { variable.allocatedNum = item.allocatedNum; } + if (variable.origin === 'call') { + variable.type = getReturnType(variable.type); + } } }); @@ -1317,14 +1433,14 @@ function analyzer(data, sidePass) { func.labelsDict = {}; func.labelIds = {}; func.labelIdsInverse = {}; - func.labelIds[toNiceIdent('%0')] = 0; + func.labelIds[toNiceIdent('%0')] = 1; func.labelIdsInverse[0] = toNiceIdent('%0'); - func.labelIds[toNiceIdent('%1')] = 1; - func.labelIdsInverse[1] = toNiceIdent('%1'); func.labelIdCounter = 2; func.labels.forEach(function(label) { - func.labelIds[label.ident] = func.labelIdCounter++; - func.labelIdsInverse[func.labelIdCounter-1] = label.ident; + if (!(label.ident in func.labelIds)) { + func.labelIds[label.ident] = func.labelIdCounter++; + func.labelIdsInverse[func.labelIdCounter-1] = label.ident; + } }); // Minify label ids to numeric ids. @@ -1359,16 +1475,12 @@ function analyzer(data, sidePass) { } }); - // The entry might not have an explicit label, and there is no consistent naming convention for it - it can be %0 or %1 - // So we need to handle that in a special way here. function getActualLabelId(labelId) { if (func.labelsDict[labelId]) return labelId; - if (labelId in ENTRY_IDENT_IDS) { - labelId = func.labelIds[ENTRY_IDENT]; - assert(func.labelsDict[labelId]); - return labelId; - } - return null; + // If not present, it must be a surprisingly-named entry (or undefined behavior, in which case, still ok to use the entry) + labelId = func.labelIds[ENTRY_IDENT]; + assert(func.labelsDict[labelId]); + return labelId; } // Basic longjmp support, see library.js setjmp/longjmp @@ -1378,22 +1490,23 @@ function analyzer(data, sidePass) { var label = func.labels[i]; for (var j = 0; j < label.lines.length; j++) { var line = label.lines[j]; - if (line.intertype == 'call' && line.ident == setjmp) { + if ((line.intertype == 'call' || line.intertype == 'invoke') && line.ident == setjmp) { // Add a new label - var oldIdent = label.ident; - var newIdent = func.labelIdCounter++; + var oldLabel = label.ident; + var newLabel = func.labelIdCounter++; if (!func.setjmpTable) func.setjmpTable = []; - func.setjmpTable.push([oldIdent, newIdent, line.assignTo]); + func.setjmpTable.push({ oldLabel: oldLabel, newLabel: newLabel, assignTo: line.assignTo }); func.labels.splice(i+1, 0, { intertype: 'label', - ident: newIdent, + ident: newLabel, lineNum: label.lineNum + 0.5, lines: label.lines.slice(j+1) }); + func.labelsDict[newLabel] = func.labels[i+1]; label.lines = label.lines.slice(0, j+1); label.lines.push({ intertype: 'branch', - label: toNiceIdent(newIdent), + label: toNiceIdent(newLabel), lineNum: line.lineNum + 0.01, // XXX legalizing might confuse this }); // Correct phis @@ -1402,8 +1515,8 @@ function analyzer(data, sidePass) { if (phi.intertype == 'phi') { for (var i = 0; i < phi.params.length; i++) { var sourceLabelId = getActualLabelId(phi.params[i].label); - if (sourceLabelId == oldIdent) { - phi.params[i].label = newIdent; + if (sourceLabelId == oldLabel) { + phi.params[i].label = newLabel; } } } @@ -1480,7 +1593,7 @@ function analyzer(data, sidePass) { calcAllocatedSize(item.allocatedType)*item.allocatedNum: 0; if (USE_TYPED_ARRAYS === 2) { // We need to keep the stack aligned - item.allocatedSize = Runtime.forceAlign(item.allocatedSize, QUANTUM_SIZE); + item.allocatedSize = Runtime.forceAlign(item.allocatedSize, Runtime.STACK_ALIGN); } } var index = 0; @@ -1514,9 +1627,8 @@ function analyzer(data, sidePass) { for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) { + if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum))) { finishedInitial = true; - continue; } if (item.intertype == 'alloca' && finishedInitial) { func.otherStackAllocations = true; @@ -1565,7 +1677,7 @@ function analyzer(data, sidePass) { } item.functions.forEach(function(func) { dprint('relooping', "// relooping function: " + func.ident); - func.block = makeBlock(func.labels, [toNiceIdent(func.labels[0].ident)], func.labelsDict, func.forceEmulated); + func.block = makeBlock(func.labels, [func.labels[0].ident], func.labelsDict, func.forceEmulated); }); return finish(); |