diff options
-rw-r--r-- | src/analyzer.js | 648 | ||||
-rw-r--r-- | src/compiler.js | 1 | ||||
-rw-r--r-- | src/intertyper.js | 3 | ||||
-rw-r--r-- | src/jsifier.js | 39 | ||||
-rw-r--r-- | src/library.js | 8 | ||||
-rw-r--r-- | src/modules.js | 2 | ||||
-rw-r--r-- | src/parseTools.js | 157 | ||||
-rw-r--r-- | src/runtime.js | 27 | ||||
-rw-r--r-- | tests/cases/i64toi8star.ll | 2 | ||||
-rw-r--r-- | tests/cases/inttoptr.ll | 2 | ||||
-rw-r--r-- | tests/cases/lifetime.ll | 42 | ||||
-rw-r--r-- | tests/cases/lifetime.py | 6 | ||||
-rw-r--r-- | tests/lifetime.ll | 37 | ||||
-rwxr-xr-x | tests/runner.py | 68 | ||||
-rwxr-xr-x | tools/bindings_generator.py | 4 | ||||
-rw-r--r-- | tools/shared.py | 1 |
16 files changed, 582 insertions, 465 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index 729d4607..1c643303 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -22,6 +22,7 @@ function cleanFunc(func) { var BRANCH_INVOKE = set('branch', 'invoke'); var SIDE_EFFECT_CAUSERS = set('call', 'invoke', 'atomic'); +var UNUNFOLDABLE = set('value', 'type', 'phiparam'); // Analyzer @@ -117,23 +118,16 @@ function analyzer(data, sidePass) { // Currently we just legalize completely unrealistic types into bundles of i32s, and just // the most common instructions that can be involved with such types: load, store, shifts, // trunc and zext. - // - // TODO: Expand this also into legalization of i64 into i32,i32, which can then - // replace our i64 mode 1 implementation. Legalizing i64s is harder though - // as they can appear in function arguments and we would also need to implement - // an unfolder (to uninline inline LLVM function calls, so that each LLVM line - // has a single LLVM instruction). substrate.addActor('Legalizer', { processItem: function(data) { // Legalization if (USE_TYPED_ARRAYS == 2) { function isIllegalType(type) { - return getBits(type) > 64; + var bits = getBits(type); + return bits > 0 && (bits >= 64 || !isPowerOfTwo(bits)); } function getLegalVars(base, bits) { - if (isNumber(base)) { - return getLegalLiterals(base, bits); - } + assert(!isNumber(base)); var ret = new Array(Math.ceil(bits/32)); var i = 0; while (bits > 0) { @@ -154,48 +148,95 @@ function analyzer(data, sidePass) { } return ret; } - // Unfolds internal inline llvmfunc calls, for example x = load (bitcast y) - // will become temp = y \n x = load temp - // @return The index of the original line, after the unfolding. In the example - // above, the index returned will be the new index of the line with `load', - // that is, i+1. - function unfold(lines, i, item, slot) { - if (item[slot].intertype == 'value') return i; - // TODO: unfold multiple slots at once - var tempIdent = '$$emscripten$temp$' + i; - item[slot].assignTo = tempIdent; - item[slot].lineNum = lines[i].lineNum - 0.5; - lines.splice(i, 0, item[slot]); - item[slot] = { intertype: 'value', ident: tempIdent, type: item[slot].type }; - return i+1; + // Uses the right factor to multiply line numbers by so that they fit in between + // the line[i] and the line after it + function interpLines(lines, i, toAdd) { + var prev = i >= 0 ? lines[i].lineNum : -1; + var next = (i < lines.length-1) ? lines[i+1].lineNum : (lines[i].lineNum + 0.5); + var factor = (next - prev)/(4*toAdd.length+3); + for (var k = 0; k < toAdd.length; k++) { + toAdd[k].lineNum = prev + ((k+1)*factor); + } } function removeAndAdd(lines, i, toAdd) { var item = lines[i]; - var interp = getInterp(lines, i, toAdd.length); - for (var k = 0; k < toAdd.length; k++) { - toAdd[k].lineNum = item.lineNum + (k*interp); - } + interpLines(lines, i, toAdd); Array.prototype.splice.apply(lines, [i, 1].concat(toAdd)); return toAdd.length; } - // Assuming we will replace the item at line i, with num items, returns - // the right factor to multiply line numbers by so that they fit in between - // the removed line and the line after it - function getInterp(lines, i, num) { - var next = (i < lines.length-1) ? lines[i+1].lineNum : (lines[i].lineNum + 0.5); - return (next - lines[i].lineNum)/(3*num+2); + function legalizeFunctionParameters(params) { + var i = 0; + while (i < params.length) { + var param = params[i]; + if (param.intertype == 'value' && isIllegalType(param.type)) { + var toAdd = getLegalVars(param.ident, getBits(param.type)).map(function(element) { + return { + intertype: 'value', + type: 'i' + element.bits, + ident: element.ident, + byval: 0 + }; + }); + Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); + i += toAdd.length; + continue; + } + i++; + } } data.functions.forEach(function(func) { + // Legalize function params + legalizeFunctionParameters(func.params); + // Legalize lines in labels + var tempId = 0; func.labels.forEach(function(label) { var i = 0, bits; while (i < label.lines.length) { var item = label.lines[i]; - if (item.intertype == 'store') { - if (isIllegalType(item.valueType)) { - dprint('legalizer', 'Legalizing store at line ' + item.lineNum); + var value = item; + // Check if we need to legalize here, and do some trivial legalization along the way + var isIllegal = false; + walkInterdata(item, function(item) { + if (item.intertype == 'getelementptr' || (item.intertype == 'call' && item.ident in LLVM.INTRINSICS_32)) { + // Turn i64 args into i32 + for (var i = 0; i < item.params.length; i++) { + if (item.params[i].type == 'i64') item.params[i].type = 'i32'; + } + } + if (isIllegalType(item.valueType) || isIllegalType(item.type)) { + isIllegal = true; + } + }); + if (!isIllegal) { + i++; + continue; + } + // Unfold this line. If we unfolded, we need to return and process the lines we just + // generated - they may need legalization too + var unfolded = []; + walkAndModifyInterdata(item, function(subItem) { + // Unfold all non-value interitems that we can, and also unfold all numbers (doing the latter + // makes it easier later since we can then assume illegal expressions are always variables + // accessible through ident$x, and not constants we need to parse then and there) + if (subItem != item && (!(subItem.intertype in UNUNFOLDABLE) || + (subItem.intertype == 'value' && isNumber(subItem.ident) && isIllegalType(subItem.type)))) { + var tempIdent = '$$emscripten$temp$' + (tempId++); + subItem.assignTo = tempIdent; + unfolded.unshift(subItem); + return { intertype: 'value', ident: tempIdent, type: subItem.type }; + } + }); + if (unfolded.length > 0) { + interpLines(label.lines, i-1, unfolded); + Array.prototype.splice.apply(label.lines, [i, 0].concat(unfolded)); + continue; // remain at this index, to unfold newly generated lines + } + // This is an illegal-containing line, and it is unfolded. Legalize it now + dprint('legalizer', 'Legalizing ' + item.intertype + ' at line ' + item.lineNum); + switch (item.intertype) { + case 'store': { var toAdd = []; bits = getBits(item.valueType); - i = unfold(label.lines, i, item, 'value'); var elements; elements = getLegalVars(item.value.ident, bits); var j = 0; @@ -228,238 +269,299 @@ function analyzer(data, sidePass) { i += removeAndAdd(label.lines, i, toAdd); continue; } - } else if (item.assignTo) { - var value = item; - switch (value.intertype) { - case 'load': { - if (isIllegalType(value.valueType)) { - dprint('legalizer', 'Legalizing load at line ' + item.lineNum); - bits = getBits(value.valueType); - i = unfold(label.lines, i, value, 'pointer'); - var interp = getInterp(label.lines, i, Math.ceil(bits/32)); - label.lines.splice(i, 1); - var elements = getLegalVars(item.assignTo, bits); - var j = 0; - elements.forEach(function(element) { - var tempVar = '$st$' + i + '$' + j; - label.lines.splice(i+j*2, 0, { - intertype: 'getelementptr', - assignTo: tempVar, - ident: value.pointer.ident, - type: '[0 x i32]*', - params: [ - { intertype: 'value', ident: value.pointer.ident, type: '[0 x i32]*' }, // technically bitcast is needed in llvm, but not for us - { intertype: 'value', ident: '0', type: 'i32' }, - { intertype: 'value', ident: j.toString(), type: 'i32' } - ], - lineNum: item.lineNum + (j*interp) - }); - var actualSizeType = 'i' + element.bits; // The last one may be smaller than 32 bits - label.lines.splice(i+j*2+1, 0, { - intertype: 'load', - assignTo: element.ident, - pointerType: actualSizeType + '*', - valueType: actualSizeType, - type: actualSizeType, // XXX why is this missing from intertyper? - pointer: { intertype: 'value', ident: tempVar, type: actualSizeType + '*' }, - ident: tempVar, - pointerType: actualSizeType + '*', - align: value.align, - lineNum: item.lineNum + ((j+0.5)*interp) - }); - j++; - }); - Types.needAnalysis['[0 x i32]'] = 0; - i += j*2; - continue; - } - break; + // call, return: Return value is in an unlegalized array literal. Not fully optimal. + case 'call': case 'invoke': { + bits = getBits(value.type); + var elements = getLegalVars(item.assignTo, bits); + var toAdd = [value]; + // legalize parameters + legalizeFunctionParameters(value.params); + if (value.assignTo) { + // legalize return value + var j = 0; + toAdd = toAdd.concat(elements.map(function(element) { + return { + intertype: 'value', + assignTo: element.ident, + type: 'i' + bits, + ident: value.assignTo + '[' + (j++) + ']' + }; + })); } - case 'phi': { - if (isIllegalType(value.type)) { - dprint('legalizer', 'Legalizing phi at line ' + item.lineNum); - bits = getBits(value.type); - var toAdd = []; - var elements = getLegalVars(item.assignTo, bits); - var j = 0; - elements.forEach(function(element) { - toAdd.push({ - intertype: 'phi', - assignTo: element.ident, - type: 'i' + element.bits, - params: value.params.map(function(param) { - return { - intertype: 'phiparam', - label: param.label, - value: { // TODO: unfolding - intertype: 'value', - ident: param.value.ident + '$' + j, - type: 'i' + element.bits, - } - }; - }) - }); - j++; - }); - i += removeAndAdd(label.lines, i, toAdd); + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'return': { + bits = getBits(item.type); + var elements = getLegalVars(item.value.ident, bits); + item.value.ident = '[' + elements.map(function(element) { return element.ident }).join(',') + ']'; + i++; + continue; + } + case 'value': { + bits = getBits(value.type); + var elements = getLegalVars(item.assignTo, bits); + var values = getLegalLiterals(item.ident, bits); + var j = 0; + var toAdd = elements.map(function(element) { + return { + intertype: 'value', + assignTo: element.ident, + type: 'i' + bits, + ident: values[j++].ident + }; + }); + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'load': { + bits = getBits(value.valueType); + var elements = getLegalVars(item.assignTo, bits); + var j = 0; + var toAdd = []; + elements.forEach(function(element) { + var tempVar = '$st$' + i + '$' + j; + toAdd.push({ + intertype: 'getelementptr', + assignTo: tempVar, + ident: value.pointer.ident, + type: '[0 x i32]*', + params: [ + { intertype: 'value', ident: value.pointer.ident, type: '[0 x i32]*' }, // technically bitcast is needed in llvm, but not for us + { intertype: 'value', ident: '0', type: 'i32' }, + { intertype: 'value', ident: j.toString(), type: 'i32' } + ] + }); + var actualSizeType = 'i' + element.bits; // The last one may be smaller than 32 bits + toAdd.push({ + intertype: 'load', + assignTo: element.ident, + pointerType: actualSizeType + '*', + valueType: actualSizeType, + type: actualSizeType, // XXX why is this missing from intertyper? + pointer: { intertype: 'value', ident: tempVar, type: actualSizeType + '*' }, + ident: tempVar, + pointerType: actualSizeType + '*', + align: value.align + }); + j++; + }); + Types.needAnalysis['[0 x i32]'] = 0; + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'phi': { + bits = getBits(value.type); + var toAdd = []; + var elements = getLegalVars(item.assignTo, bits); + var j = 0; + elements.forEach(function(element) { + toAdd.push({ + intertype: 'phi', + assignTo: element.ident, + type: 'i' + element.bits, + params: value.params.map(function(param) { + return { + intertype: 'phiparam', + label: param.label, + value: { + intertype: 'value', + ident: param.value.ident + '$' + j, + type: 'i' + element.bits, + } + }; + }) + }); + j++; + }); + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'bitcast': { + var inType = item.type2; + var outType = item.type; + if ((inType in Runtime.INT_TYPES && outType in Runtime.FLOAT_TYPES) || + (inType in Runtime.FLOAT_TYPES && outType in Runtime.INT_TYPES)) { + i++; + continue; // special case, handled in processMathop + } + // fall through + } + case 'inttoptr': case 'ptrtoint': { + value = { + op: item.intertype, + param1: item.params[0] + }; + // fall through + } + case 'mathop': { + var toAdd = []; + var sourceBits = getBits(value.param1.type); + // All mathops can be parametrized by how many shifts we do, and how big the source is + var shifts = 0; + var targetBits = sourceBits; + var processor = null; + var signed = false; + switch (value.op) { + case 'ashr': { + signed = true; + // fall through + } + case 'lshr': { + shifts = parseInt(value.param2.ident); + break; + } + case 'shl': { + shifts = -parseInt(value.param2.ident); + break; + } + case 'sext': { + signed = true; + // fall through + } + case 'trunc': case 'zext': case 'ptrtoint': { + targetBits = getBits(value.param2.ident); + break; + } + case 'inttoptr': { + targetBits = 32; + break; + } + case 'bitcast': { + break; + } + case 'select': { + sourceBits = targetBits = getBits(value.param2.type); + var otherElementsA = getLegalVars(value.param2.ident, sourceBits); + var otherElementsB = getLegalVars(value.param3.ident, sourceBits); + processor = function(result, j) { + return { + intertype: 'mathop', + op: 'select', + type: 'i' + otherElementsA[j].bits, + param1: value.param1, + param2: { intertype: 'value', ident: otherElementsA[j].ident, type: 'i' + otherElementsA[j].bits }, + param3: { intertype: 'value', ident: otherElementsB[j].ident, type: 'i' + otherElementsB[j].bits } + }; + }; + break; + } + case 'or': case 'and': case 'xor': { + var otherElements = getLegalVars(value.param2.ident, sourceBits); + processor = function(result, j) { + return { + intertype: 'mathop', + op: value.op, + type: 'i' + otherElements[j].bits, + param1: result, + param2: { intertype: 'value', ident: otherElements[j].ident, type: 'i' + otherElements[j].bits } + }; + }; + break; + } + case 'add': case 'sub': case 'sdiv': case 'udiv': case 'mul': case 'urem': case 'srem': + case 'icmp':case 'uitofp': case 'sitofp': { + // We cannot do these in parallel chunks of 32-bit operations. We will handle these in processMathop + i++; continue; } - break; + default: throw 'Invalid mathop for legalization: ' + [value.op, item.lineNum, dump(item)]; } - case 'mathop': { - if (isIllegalType(value.type)) { - dprint('legalizer', 'Legalizing mathop at line ' + item.lineNum); - var toAdd = []; - assert(value.param1.intertype == 'value', 'TODO: unfolding'); - var sourceBits = getBits(value.param1.type); - var sourceElements; - if (sourceBits <= 64) { - // The input is a legal type - if (sourceBits <= 32) { - sourceElements = [{ ident: value.param1.ident, bits: sourceBits }]; - } else if (sourceBits == 64 && I64_MODE == 1) { - sourceElements = [{ ident: value.param1.ident + '[0]', bits: 32 }, - { ident: value.param1.ident + '[1]', bits: 32 }]; - // Add the source element as a param so that it is not eliminated as unneeded (the idents are not a simple ident here) - toAdd.push({ - intertype: 'value', ident: ';', type: 'rawJS', - params: [{ intertype: 'value', ident: value.param1.ident, type: 'i32' }] - }); - } else { - throw 'Invalid legal type as source of legalization ' + sourceBits; - } - } else { - sourceElements = getLegalVars(value.param1.ident, sourceBits); - } - // All mathops can be parametrized by how many shifts we do, and how big the source is - var shifts = 0; - var targetBits; - var processor = null; - switch (value.op) { - case 'lshr': { - assert(value.param2.intertype == 'value', 'TODO: unfolding'); - shifts = parseInt(value.param2.ident); - targetBits = sourceBits; - break; - } - case 'shl': { - assert(value.param2.intertype == 'value', 'TODO: unfolding'); - shifts = -parseInt(value.param2.ident); - targetBits = sourceBits; - break; - } - case 'trunc': case 'zext': { - assert(value.param2.intertype == 'type' || value.param2.intertype == 'value', 'TODO: unfolding'); - targetBits = getBits(value.param2.ident); - break; - } - case 'or': case 'and': case 'xor': { - targetBits = sourceBits; - var otherElements = getLegalVars(value.param2.ident, sourceBits); - processor = function(result, j) { - return { - intertype: 'mathop', - op: value.op, - type: 'i' + otherElements[j].bits, - param1: result, - param2: { intertype: 'value', ident: otherElements[j].ident, type: 'i' + otherElements[j].bits } - }; - }; - break; - } - default: throw 'Invalid mathop for legalization: ' + [value.op, item.lineNum, dump(item)]; - } - // Do the legalization - assert(isNumber(shifts), 'TODO: handle nonconstant shifts'); - var targetElements = getLegalVars(item.assignTo, targetBits); - var sign = shifts >= 0 ? 1 : -1; - var shiftOp = shifts >= 0 ? 'shl' : 'lshr'; - var shiftOpReverse = shifts >= 0 ? 'lshr' : 'shl'; - var whole = shifts >= 0 ? Math.floor(shifts/32) : Math.ceil(shifts/32); - var fraction = Math.abs(shifts % 32); - for (var j = 0; j < targetElements.length; j++) { - var result = { - intertype: 'value', - ident: (j + whole >= 0 && j + whole < sourceElements.length) ? sourceElements[j + whole].ident : '0', - type: 'i32', - }; - if (fraction != 0) { - var other = { - intertype: 'value', - ident: (j + sign + whole >= 0 && j + sign + whole < sourceElements.length) ? sourceElements[j + sign + whole].ident : '0', - type: 'i32', - }; - other = { - intertype: 'mathop', - op: shiftOp, - type: 'i32', - param1: other, - param2: { intertype: 'value', ident: (32 - fraction).toString(), type: 'i32' } - }; - result = { - intertype: 'mathop', - op: shiftOpReverse, - type: 'i32', - param1: result, - param2: { intertype: 'value', ident: fraction.toString(), type: 'i32' } - }; - result = { - intertype: 'mathop', - op: 'or', - type: 'i32', - param1: result, - param2: other - } - } - if (targetElements[j].bits < 32 && shifts < 0) { - // truncate bits that fall off the end. This is not needed in most cases, can probably be optimized out - result = { - intertype: 'mathop', - op: 'and', - type: 'i32', - param1: result, - param2: { intertype: 'value', ident: (Math.pow(2, targetElements[j].bits)-1).toString(), type: 'i32' } - } - } - if (processor) { - result = processor(result, j); - } - result.assignTo = targetElements[j].ident; - toAdd.push(result); + // Do the legalization + var sourceElements; + if (sourceBits <= 32) { + // The input is a legal type + sourceElements = [{ ident: value.param1.ident, bits: sourceBits }]; + } else { + sourceElements = getLegalVars(value.param1.ident, sourceBits); + } + 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'); + value.intertype = 'value'; + value.ident = 'Runtime.bitshift64(' + sourceElements[0].ident + ', ' + + sourceElements[1].ident + ',"' + value.op + '",' + value.param2.ident + '$0);' + + 'var ' + value.assignTo + '$0 = ' + value.assignTo + '[0], ' + value.assignTo + '$1 = ' + value.assignTo + '[1];'; + i++; + continue; + } + var targetElements = getLegalVars(item.assignTo, targetBits); + var sign = shifts >= 0 ? 1 : -1; + var shiftOp = shifts >= 0 ? 'shl' : 'lshr'; + var shiftOpReverse = shifts >= 0 ? 'lshr' : 'shl'; + var whole = shifts >= 0 ? Math.floor(shifts/32) : Math.ceil(shifts/32); + var fraction = Math.abs(shifts % 32); + if (signed) { + var signedFill = '((' + sourceElements[sourceElements.length-1].ident + '|0) < 0 ? -1 : 0)'; + var signedKeepAlive = { intertype: 'value', ident: sourceElements[sourceElements.length-1].ident, type: 'i32' }; + } + for (var j = 0; j < targetElements.length; j++) { + var result = { + intertype: 'value', + ident: (j + whole >= 0 && j + whole < sourceElements.length) ? sourceElements[j + whole].ident : (signed ? signedFill : '0'), + param1: (signed && j + whole > sourceElements.length) ? signedKeepAlive : null, + type: 'i32', + }; + if (fraction != 0) { + var other = { + intertype: 'value', + ident: (j + sign + whole >= 0 && j + sign + whole < sourceElements.length) ? sourceElements[j + sign + whole].ident : (signed ? signedFill : '0'), + param1: (signed && j + sign + whole > sourceElements.length) ? signedKeepAlive : null, + type: 'i32', + }; + other = { + intertype: 'mathop', + op: shiftOp, + type: 'i32', + param1: other, + param2: { intertype: 'value', ident: (32 - fraction).toString(), type: 'i32' } + }; + result = { + intertype: 'mathop', + // shifting in 1s from the top is a special case + op: (signed && shifts >= 0 && j + sign + whole >= sourceElements.length) ? 'ashr' : shiftOpReverse, + type: 'i32', + param1: result, + param2: { intertype: 'value', ident: fraction.toString(), type: 'i32' } + }; + result = { + intertype: 'mathop', + op: 'or', + type: 'i32', + param1: result, + param2: other } - if (targetBits <= 64) { - // We are generating a normal legal type here - var legalValue; - if (targetBits == 64 && I64_MODE == 1) { - // Generate an i64-1 [low,high]. This will be unnecessary when we legalize i64s - legalValue = { - intertype: 'value', - ident: '[' + targetElements[0].ident + ',' + targetElements[1].ident + ']', - type: 'rawJS', - // Add the target elements as params so that they are not eliminated as unneeded (the ident is not a simple ident here) - params: targetElements.map(function(element) { - return { intertype: 'value', ident: element.ident, type: 'i32' }; - }) - }; - } else if (targetBits <= 32) { - legalValue = { intertype: 'value', ident: targetElements[0].ident, type: 'rawJS' }; - // truncation to smaller than 32 bits has already been done, if necessary - } else { - throw 'Invalid legal type as target of legalization ' + targetBits; - } - legalValue.assignTo = item.assignTo; - toAdd.push(legalValue); + } + if (targetElements[j].bits < 32 && shifts < 0) { + // truncate bits that fall off the end. This is not needed in most cases, can probably be optimized out + result = { + intertype: 'mathop', + op: 'and', + type: 'i32', + param1: result, + param2: { intertype: 'value', ident: (Math.pow(2, targetElements[j].bits)-1).toString(), type: 'i32' } } - i += removeAndAdd(label.lines, i, toAdd); - continue; } - break; + if (processor) { + result = processor(result, j); + } + result.assignTo = targetElements[j].ident; + toAdd.push(result); + } + if (targetBits <= 32) { + // We are generating a normal legal type here + legalValue = { intertype: 'value', ident: targetElements[0].ident, type: 'rawJS' }; + // truncation to smaller than 32 bits has already been done, if necessary + legalValue.assignTo = item.assignTo; + toAdd.push(legalValue); } + i += removeAndAdd(label.lines, i, toAdd); + continue; } } - i++; - continue; + assert(0, 'Could not legalize illegal line: ' + [item.lineNum, dump(item)]); } + if (dcheck('legalizer')) dprint('zz legalized: \n' + dump(label.lines)); }); }); } @@ -741,33 +843,7 @@ function analyzer(data, sidePass) { //if (dcheck('vars')) dprint('analyzed variables: ' + dump(func.variables)); } - // Filter out no longer used variables, collapsing more as we go - while (true) { - analyzeVariableUses(); - - var recalc = false; - - keys(func.variables).forEach(function(vname) { - var variable = func.variables[vname]; - if (variable.uses == 0 && variable.origin != 'funcparam') { - // Eliminate this variable if we can - var sideEffects = false; - walkInterdata(func.lines[variable.rawLinesIndex], function(item) { - if (item.intertype in SIDE_EFFECT_CAUSERS) sideEffects = true; - }); - if (!sideEffects) { - dprint('vars', 'Eliminating ' + vname); - func.lines[variable.rawLinesIndex].intertype = 'noop'; - func.lines[variable.rawLinesIndex].assignTo = null; - // in theory we can also null out some fields here to save memory - delete func.variables[vname]; - recalc = true; - } - } - }); - - if (!recalc) break; - } + analyzeVariableUses(); // Decision time diff --git a/src/compiler.js b/src/compiler.js index bf9d9c54..d37bc68b 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -138,6 +138,7 @@ EXPORTED_GLOBALS = set(EXPORTED_GLOBALS); // Settings sanity checks assert(!(USE_TYPED_ARRAYS === 2 && QUANTUM_SIZE !== 4), 'For USE_TYPED_ARRAYS == 2, must have normal QUANTUM_SIZE of 4'); +assert(!(USE_TYPED_ARRAYS !== 2 && I64_MODE === 1), 'i64 mode 1 is only supported with typed arrays mode 2'); // Output some info and warnings based on settings diff --git a/src/intertyper.js b/src/intertyper.js index 3dab220b..91ad15eb 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -539,9 +539,6 @@ function intertyper(data, sidePass, baseLineNums) { params: params, hasVarArgs: hasVarArgs(params), lineNum: item.lineNum, - paramIdents: params.map(function(param) { - return (param.intertype == 'varargs') ? null : toNiceIdent(param.ident); - }).filter(function(param) { return param != null; }) }]; } }); diff --git a/src/jsifier.js b/src/jsifier.js index 71533309..b830fc7c 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -497,16 +497,20 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS = '\n'; + var paramIdents = func.params.map(function(param) { + return (param.intertype == 'varargs') ? null : toNiceIdent(param.ident); + }).filter(function(param) { return param != null; }) + if (CLOSURE_ANNOTATIONS) { func.JS += '/**\n'; - func.paramIdents.forEach(function(param) { + paramIdents.forEach(function(param) { func.JS += ' * @param {number} ' + param + '\n'; }); func.JS += ' * @return {number}\n' func.JS += ' */\n'; } - func.JS += 'function ' + func.ident + '(' + func.paramIdents.join(', ') + ') {\n'; + func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n'; if (PROFILE) { func.JS += ' if (PROFILING) { ' @@ -1046,37 +1050,16 @@ function JSify(data, functionsOnly, givenFunctions) { var hasVarArgs = isVarArgsFunctionType(type); var normalArgs = (hasVarArgs && !useJSArgs) ? countNormalArgs(type) : -1; - if (I64_MODE == 1 && ident in LLVM.INTRINSICS_32) { - // Some LLVM intrinsics use i64 where it is not needed, and would cause much overhead - params.forEach(function(param) { if (param.type == 'i64') param.type = 'i32' }); - } - params.forEach(function(param, i) { var val = finalizeParam(param); if (!hasVarArgs || useJSArgs || i < normalArgs) { - if (param.type == 'i64' && I64_MODE == 1) { - val = makeCopyI64(val); // Must copy [low, high] i64s, so they don't end up modified in the caller - } args.push(val); argsTypes.push(param.type); } else { - if (!(param.type == 'i64' && I64_MODE == 1)) { - varargs.push(val); - varargs = varargs.concat(zeros(Runtime.getNativeFieldSize(param.type)-1)); - varargsTypes.push(param.type); - varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize(param.type)-1)); - } else { - // i64 mode 1. Write one i32 with type i64, and one i32 with type i32 - varargs.push(val + '[0]'); - varargs = varargs.concat(zeros(Runtime.getNativeFieldSize('i32')-1)); - ignoreFunctionIndexizing.push(varargs.length); // We will have a value there, but no type (the type is i64, but we write two i32s) - varargs.push(val + '[1]'); - varargs = varargs.concat(zeros(Runtime.getNativeFieldSize('i32')-1)); - varargsTypes.push('i64'); - varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize('i32')-1)); - varargsTypes.push('i32'); - varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize('i32')-1)); - } + varargs.push(val); + varargs = varargs.concat(zeros(Runtime.getNativeFieldSize(param.type)-1)); + varargsTypes.push(param.type); + varargsTypes = varargsTypes.concat(zeros(Runtime.getNativeFieldSize(param.type)-1)); } }); @@ -1096,7 +1079,6 @@ function JSify(data, functionsOnly, givenFunctions) { varargs.map(function(arg, i) { var type = varargsTypes[i]; if (type == 0) return null; - if (I64_MODE == 1 && type == 'i64') type = 'i32'; // We have [i64, 0, 0, 0, i32, 0, 0, 0] in the layout at this point var ret = makeSetValue(getFastValue('tempInt', '+', offset), 0, arg, type, null, null, QUANTUM_SIZE, null, ','); offset += Runtime.getNativeFieldSize(type); return ret; @@ -1190,6 +1172,7 @@ function JSify(data, functionsOnly, givenFunctions) { print(postParts[0]); // Print out global variables and postsets TODO: batching + legalizedI64s = false; JSify(analyzer(intertyper(data.unparsedGlobalss[0].lines, true), true), true, Functions); data.unparsedGlobalss = null; diff --git a/src/library.js b/src/library.js index cf28a73c..e0a38817 100644 --- a/src/library.js +++ b/src/library.js @@ -3481,11 +3481,17 @@ LibraryManager.library = { ___setErrNo(ERRNO_CODES.ERANGE); } +#if USE_TYPED_ARRAYS == 2 + if (bits == 64) { + ret = [{{{ splitI64('ret') }}}]; + } +#else #if I64_MODE == 1 if (bits == 64) { ret = {{{ splitI64('ret') }}}; } #endif +#endif return ret; }, @@ -4377,7 +4383,7 @@ LibraryManager.library = { ptrTV -= {{{ Runtime.QUANTUM_SIZE }}}; var TI = {{{ makeGetValue('ptrTV', '0', '*') }}}; do { - if (TI == attemptedTI) return 1; + if (TI == attemptedTI) return ptr; // Go to parent class var type_infoAddr = {{{ makeGetValue('TI', '0', '*') }}} - {{{ Runtime.QUANTUM_SIZE*2 }}}; var type_info = {{{ makeGetValue('type_infoAddr', '0', '*') }}}; diff --git a/src/modules.js b/src/modules.js index 52f14c2f..2896d632 100644 --- a/src/modules.js +++ b/src/modules.js @@ -161,7 +161,7 @@ var PreProcessor = { // parameter to it will prevent nativization of the variable being cast (!) for (var i = 0; i < lines.length; i++) { var line = lines[i]; - if (/call void @llvm.lifetime.(start|end)\(i\d+ -1, i8\* %(\d+)\).*/.exec(line)) { + if (/call void @llvm.lifetime.(start|end)\(i\d+ -1,.*/.exec(line)) { lines[i] = ';'; } } diff --git a/src/parseTools.js b/src/parseTools.js index 3cc17cc4..1b48737e 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -129,9 +129,12 @@ function isIntImplemented(type) { return type[0] == 'i' || isPointerType(type); } +// Note: works for iX types, not pointers (even though they are implemented as ints) function getBits(type) { if (!type || type[0] != 'i') return 0; - return parseInt(type.substr(1)); + var left = type.substr(1); + if (!isNumber(left)) return 0; + return parseInt(left); } function isVoidType(type) { @@ -530,18 +533,27 @@ function makeI64(low, high) { } } +// XXX Make all i64 parts signed + // Splits a number (an integer in a double, possibly > 32 bits) into an I64_MODE 1 i64 value. -// Will suffer from rounding. margeI64 does the opposite. -// TODO: optimize I64 calcs. For example, saving their parts as signed 32 as opposed to unsigned would help +// Will suffer from rounding. mergeI64 does the opposite. function splitI64(value) { // We need to min here, since our input might be a double, and large values are rounded, so they can // be slightly higher than expected. And if we get 4294967296, that will turn into a 0 if put into a // HEAP32 or |0'd, etc. - return makeInlineCalculation(makeI64('VALUE>>>0', 'Math.min(Math.floor(VALUE/4294967296), 4294967295)'), value, 'tempBigIntP'); + if (legalizedI64s) { + return [value + '>>>0', 'Math.min(Math.floor(' + value + '/4294967296), 4294967295)']; + } else { + return makeInlineCalculation(makeI64('VALUE>>>0', 'Math.min(Math.floor(VALUE/4294967296), 4294967295)'), value, 'tempBigIntP'); + } } function mergeI64(value) { assert(I64_MODE == 1); - return makeInlineCalculation(RuntimeGenerator.makeBigInt('VALUE[0]', 'VALUE[1]'), value, 'tempI64'); + if (legalizedI64s) { + return RuntimeGenerator.makeBigInt(value + '$0', value + '$1'); + } else { + return makeInlineCalculation(RuntimeGenerator.makeBigInt('VALUE[0]', 'VALUE[1]'), value, 'tempI64'); + } } // Takes an i64 value and changes it into the [low, high] form used in i64 mode 1. In that @@ -646,10 +658,11 @@ function parseI64Constant(str) { if (!isNumber(str)) { // This is a variable. Copy it, so we do not modify the original - return makeCopyI64(str); + return legalizedI64s ? str : makeCopyI64(str); } var parsed = parseArbitraryInt(str, 64); + if (legalizedI64s) return parsed; return '[' + parsed[0] + ',' + parsed[1] + ']'; } @@ -888,11 +901,6 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa if (i < bytes-1) ret += '|'; } ret = '(' + makeSignOp(ret, type, unsigned ? 'un' : 're', true); - } else { - assert(bytes == 8); - ret += 'tempBigInt=' + makeGetValue(ptr, pos, 'i32', noNeedFirst, true, ignore, align) + ','; - ret += 'tempBigInt2=' + makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, true, ignore, align) + ','; - ret += makeI64('tempBigInt', 'tempBigInt2'); } } else { if (type == 'float') { @@ -906,11 +914,6 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa } } - if (type == 'i64' && I64_MODE == 1) { - return '[' + makeGetValue(ptr, pos, 'i32', noNeedFirst, 1, ignore) + ',' - + makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, 1, ignore) + ']'; - } - var offset = calcFastOffset(ptr, pos, noNeedFirst); if (SAFE_HEAP && !noSafe) { if (type !== 'null' && type[0] !== '#') type = '"' + safeQuote(type) + '"'; @@ -966,7 +969,8 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'tempDoubleI32[1]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; } - var needSplitting = isIntImplemented(type) && !isPowerOfTwo(getBits(type)); // an unnatural type like i24 + var bits = getBits(type); + var needSplitting = bits > 0 && !isPowerOfTwo(bits); // an unnatural type like i24 if (USE_TYPED_ARRAYS == 2 && (align || needSplitting)) { // Alignment is important here, or we need to split this up for other reasons. var bytes = Runtime.getNativeTypeSize(type); @@ -978,16 +982,12 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, ret += 'tempBigInt=' + value + sep; ret += makeSetValue(ptr, pos, 'tempBigInt&0xffff', 'i16', noNeedFirst, ignore, 2) + sep; ret += makeSetValue(ptr, getFastValue(pos, '+', 2), 'tempBigInt>>16', 'i16', noNeedFirst, ignore, 2); - } else if (bytes != 8) { + } else { ret += 'tempBigInt=' + value + sep; for (var i = 0; i < bytes; i++) { ret += makeSetValue(ptr, getFastValue(pos, '+', i), 'tempBigInt&0xff', 'i8', noNeedFirst, ignore, 1); if (i < bytes-1) ret += sep + 'tempBigInt>>=8' + sep; } - } else { // bytes == 8, specific optimization - ret += 'tempPair=' + ensureI64_1(value) + sep; - ret += makeSetValue(ptr, pos, 'tempPair[0]', 'i32', noNeedFirst, ignore, align) + sep; - ret += makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'tempPair[1]', 'i32', noNeedFirst, ignore, align); } } else { ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8) + sep; @@ -997,11 +997,6 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, } } - if (type == 'i64' && I64_MODE == 1) { - return '(' + makeSetValue(ptr, pos, value + '[0]', 'i32', noNeedFirst, ignore) + ',' - + makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), value + '[1]', 'i32', noNeedFirst, ignore) + ')'; - } - value = indexizeFunctions(value, type); var offset = calcFastOffset(ptr, pos, noNeedFirst); if (SAFE_HEAP && !noSafe) { @@ -1530,6 +1525,8 @@ function isSignedOp(op, variant) { return op in SIGNED_OP || (variant && variant[0] == 's'); } +var legalizedI64s = USE_TYPED_ARRAYS == 2; // We do not legalize globals, but do legalize function lines. This will be true in the latter case + function processMathop(item) { var op = item.op; var variant = item.variant; @@ -1539,7 +1536,7 @@ function processMathop(item) { if (item['param'+i]) { paramTypes[i-1] = item['param'+i].type || type; item['ident'+i] = finalizeLLVMParameter(item['param'+i]); - if (!isNumber(item['ident'+i])) { + if (!isNumber(item['ident'+i]) && !isNiceIdent(item['ident'+i])) { item['ident'+i] = '(' + item['ident'+i] + ')'; // we may have nested expressions. So enforce the order of operations we want } } else { @@ -1570,8 +1567,24 @@ function processMathop(item) { if ((type == 'i64' || paramTypes[0] == 'i64' || paramTypes[1] == 'i64' || ident2 == '(i64)') && I64_MODE == 1) { var warnI64_1 = function() { - warnOnce('Arithmetic on 64-bit integers in mode 1 is rounded and flaky, like mode 0, but much slower!'); + warnOnce('Arithmetic on 64-bit integers in mode 1 is rounded and flaky, like mode 0!'); }; + // In ops that can be either legalized or not, we need to differentiate how we access low and high parts + var low1 = ident1 + (legalizedI64s ? '$0' : '[0]'); + var high1 = ident1 + (legalizedI64s ? '$1' : '[1]'); + var low2 = ident2 + (legalizedI64s ? '$0' : '[0]'); + var high2 = ident2 + (legalizedI64s ? '$1' : '[1]'); + function finish(result) { + // If this is in legalization mode, steal the assign and assign into two vars + if (legalizedI64s) { + assert(item.assignTo); + var ret = 'var ' + item.assignTo + '$0 = ' + result[0] + '; var ' + item.assignTo + '$1 = ' + result[1] + ';'; + item.assignTo = null; + return ret; + } else { + return result; + } + } switch (op) { // basic integer ops case 'or': { @@ -1587,7 +1600,7 @@ function processMathop(item) { case 'ashr': case 'lshr': { if (!isNumber(ident2)) { - return 'Runtime.bitshift64(' + ident1 + ',"' + op + '",' + stripCorrections(ident2) + '[0]|0)'; + return 'Runtime.bitshift64(' + ident1 + '[0], ' + ident1 + '[1],"' + op + '",' + stripCorrections(ident2) + '[0]|0)'; } bits = parseInt(ident2); var ander = Math.pow(2, bits)-1; @@ -1623,38 +1636,28 @@ function processMathop(item) { } } } - case 'uitofp': case 'sitofp': return ident1 + '[0] + ' + ident1 + '[1]*4294967296'; - case 'fptoui': case 'fptosi': return splitI64(ident1); + case 'uitofp': case 'sitofp': return low1 + ' + ' + high1 + '*4294967296'; + case 'fptoui': case 'fptosi': return finish(splitI64(ident1)); case 'icmp': { switch (variant) { - case 'uge': return ident1 + '[1] >= ' + ident2 + '[1] && (' + ident1 + '[1] > ' + ident2 + '[1] || ' + - ident1 + '[0] >= ' + ident2 + '[0])'; - case 'sge': return '(' + ident1 + '[1]|0) >= (' + ident2 + '[1]|0) && ((' + ident1 + '[1]|0) > (' + ident2 + '[1]|0) || ' + - '(' + ident1 + '[0]|0) >= (' + ident2 + '[0]|0))'; - case 'ule': return ident1 + '[1] <= ' + ident2 + '[1] && (' + ident1 + '[1] < ' + ident2 + '[1] || ' + - ident1 + '[0] <= ' + ident2 + '[0])'; - case 'sle': return '(' + ident1 + '[1]|0) <= (' + ident2 + '[1]|0) && ((' + ident1 + '[1]|0) < (' + ident2 + '[1]|0) || ' + - '(' + ident1 + '[0]|0) <= (' + ident2 + '[0]|0))'; - case 'ugt': return ident1 + '[1] > ' + ident2 + '[1] || (' + ident1 + '[1] == ' + ident2 + '[1] && ' + - ident1 + '[0] > ' + ident2 + '[0])'; - case 'sgt': return '(' + ident1 + '[1]|0) > (' + ident2 + '[1]|0) || ((' + ident1 + '[1]|0) == (' + ident2 + '[1]|0) && ' + - '(' + ident1 + '[0]|0) > (' + ident2 + '[0]|0))'; - case 'ult': return ident1 + '[1] < ' + ident2 + '[1] || (' + ident1 + '[1] == ' + ident2 + '[1] && ' + - ident1 + '[0] < ' + ident2 + '[0])'; - case 'slt': return '(' + ident1 + '[1]|0) < (' + ident2 + '[1]|0) || ((' + ident1 + '[1]|0) == (' + ident2 + '[1]|0) && ' + - '(' + ident1 + '[0]|0) < (' + ident2 + '[0]|0))'; - case 'ne': case 'eq': { - // We must sign them, so we do not compare -1 to 255 (could have unsigned them both too) - // since LLVM tells us if <=, >= etc. comparisons are signed, but not == and !=. - assert(paramTypes[0] == paramTypes[1]); - ident1 = makeSignOp(ident1, paramTypes[0], 're'); - ident2 = makeSignOp(ident2, paramTypes[1], 're'); - if (variant === 'eq') { - return ident1 + '[0] == ' + ident2 + '[0] && ' + ident1 + '[1] == ' + ident2 + '[1]'; - } else { - return ident1 + '[0] != ' + ident2 + '[0] || ' + ident1 + '[1] != ' + ident2 + '[1]'; - } - } + case 'uge': return high1 + ' >= ' + high2 + ' && (' + high1 + ' > ' + high + ' || ' + + low1 + ' >= ' + low2 + ')'; + case 'sge': return '(' + high1 + '|0) >= (' + high2 + '|0) && ((' + high1 + '|0) > (' + high2 + '|0) || ' + + '(' + low1 + '|0) >= (' + low2 + '|0))'; + case 'ule': return high1 + ' <= ' + high2 + ' && (' + high1 + ' < ' + high2 + ' || ' + + low1 + ' <= ' + low2 + ')'; + case 'sle': return '(' + high1 + '|0) <= (' + high2 + '|0) && ((' + high1 + '|0) < (' + high2 + '|0) || ' + + '(' + low1 + '|0) <= (' + low2 + '|0))'; + case 'ugt': return high1 + ' > ' + high2 + ' || (' + high1 + ' == ' + high2 + ' && ' + + low1 + ' > ' + low2 + ')'; + case 'sgt': return '(' + high1 + '|0) > (' + high2 + '|0) || ((' + high1 + '|0) == (' + high2 + '|0) && ' + + '(' + low1 + '|0) > (' + low2 + '|0))'; + case 'ult': return high1 + ' < ' + high2 + ' || (' + high1 + ' == ' + high2 + ' && ' + + low1 + ' < ' + low2 + ')'; + case 'slt': return '(' + high1 + '|0) < (' + high2 + '|0) || ((' + high1 + '|0) == (' + high2 + '|0) && ' + + '(' + low1 + '|0) < (' + low2 + '|0))'; + case 'ne': return low1 + ' != ' + low2 + ' || ' + high1 + ' != ' + high2 + ''; + case 'eq': return low1 + ' == ' + low2 + ' && ' + high1 + ' == ' + high2 + ''; default: throw 'Unknown icmp variant: ' + variant; } } @@ -1667,11 +1670,11 @@ function processMathop(item) { case 'ptrtoint': return makeI64(ident1, 0); case 'inttoptr': return '(' + ident1 + '[0])'; // just directly truncate the i64 to a 'pointer', which is an i32 // Dangerous, rounded operations. TODO: Fully emulate - case 'add': warnI64_1(); return handleOverflow(splitI64(mergeI64(ident1) + '+' + mergeI64(ident2)), bits); - case 'sub': warnI64_1(); return handleOverflow(splitI64(mergeI64(ident1) + '-' + mergeI64(ident2)), bits); - case 'sdiv': case 'udiv': warnI64_1(); return splitI64(makeRounding(mergeI64(ident1) + '/' + mergeI64(ident2), bits, op[0] === 's')); - case 'mul': warnI64_1(); return handleOverflow(splitI64(mergeI64(ident1) + '*' + mergeI64(ident2)), bits); - case 'urem': case 'srem': warnI64_1(); return splitI64(mergeI64(ident1) + '%' + mergeI64(ident2)); + case 'add': warnI64_1(); return finish(splitI64(mergeI64(ident1) + '+' + mergeI64(ident2))); + case 'sub': warnI64_1(); return finish(splitI64(mergeI64(ident1) + '-' + mergeI64(ident2))); + case 'sdiv': case 'udiv': warnI64_1(); return finish(splitI64(makeRounding(mergeI64(ident1) + '/' + mergeI64(ident2), bits, op[0] === 's'))); + case 'mul': warnI64_1(); return finish(splitI64(mergeI64(ident1) + '*' + mergeI64(ident2))); + case 'urem': case 'srem': warnI64_1(); return finish(splitI64(mergeI64(ident1) + '%' + mergeI64(ident2))); case 'bitcast': { // Pointers are not 64-bit, so there is really only one possible type of bitcast here, int to float or vice versa assert(USE_TYPED_ARRAYS == 2, 'Can only bitcast ints <-> floats with typed arrays mode 2'); @@ -1831,6 +1834,7 @@ function walkInterdata(item, pre, post, obj) { var originalObj = obj; if (obj && obj.replaceWith) obj = obj.replaceWith; // allow pre to replace the object we pass to all its children if (item.value && walkInterdata(item.value, pre, post, obj)) return true; + // TODO if (item.pointer && walkInterdata(item.pointer, pre, post, obj)) return true; if (item.dependent && walkInterdata(item.dependent, pre, post, obj)) return true; var i; for (i = 1; i <= 4; i++) { @@ -1851,6 +1855,29 @@ function walkInterdata(item, pre, post, obj) { return post && post(item, originalObj, obj); } +// Separate from walkInterdata so that the former is as fast as possible +// If the callback returns a value, we replace the current item with that +// value, and do *not* walk the children. +function walkAndModifyInterdata(item, pre) { + if (!item || !item.intertype) return false; + var ret = pre(item); + if (ret) return ret; + var repl; + if (item.value && (repl = walkAndModifyInterdata(item.value, pre))) item.value = repl; + if (item.pointer && (repl = walkAndModifyInterdata(item.pointer, pre))) item.pointer = repl; + if (item.dependent && (repl = walkAndModifyInterdata(item.dependent, pre))) item.dependent = repl; + var i; + for (i = 1; i <= 4; i++) { + if (item['param'+i] && (repl = walkAndModifyInterdata(item['param'+i], pre))) item['param'+i] = repl; + } + if (item.params) { + for (i = 0; i <= item.params.length; i++) { + if (repl = walkAndModifyInterdata(item.params[i], pre)) item.params[i] = repl; + } + } + // Ignore possibleVars because we can't replace them anyhow +} + function parseBlockAddress(segment) { return { intertype: 'blockaddress', func: toNiceIdent(segment[2].item.tokens[0].text), label: toNiceIdent(segment[2].item.tokens[2].text), type: 'i32' }; } diff --git a/src/runtime.js b/src/runtime.js index b5663045..852d08d8 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -79,9 +79,10 @@ var RuntimeGenerator = { // Rounding is inevitable if the number is large. This is a particular problem for small negative numbers // (-1 will be rounded!), so handle negatives separately and carefully makeBigInt: function(low, high, unsigned) { - return '(' + unsigned + - ' ? (' + makeSignOp(low, 'i32', 'un', 1, 1) + '+(' + makeSignOp(high, 'i32', 'un', 1, 1) + '*4294967296))' + - ' : (' + makeSignOp(low, 'i32', 'un', 1, 1) + '+(' + makeSignOp(high, 'i32', 're', 1, 1) + '*4294967296)))'; + var unsignedRet = '(' + makeSignOp(low, 'i32', 'un', 1, 1) + '+(' + makeSignOp(high, 'i32', 'un', 1, 1) + '*4294967296))'; + var signedRet = '(' + makeSignOp(low, 'i32', 'un', 1, 1) + '+(' + makeSignOp(high, 'i32', 're', 1, 1) + '*4294967296))'; + if (typeof unsigned === 'string') return '(' + unsigned + ' ? ' + unsignedRet + ' : ' + signedRet + ')'; + return unsigned ? unsignedRet : signedRet; } }; @@ -122,34 +123,34 @@ var Runtime = { FLOAT_TYPES: set('float', 'double'), // Mirrors processMathop's treatment of constants (which we optimize directly) - bitshift64: function(value, op, bits) { + bitshift64: function(low, high, op, bits) { var ander = Math.pow(2, bits)-1; if (bits < 32) { switch (op) { case 'shl': - return [value[0] << bits, (value[1] << bits) | ((value[0]&(ander << (32 - bits))) >>> (32 - bits))]; + return [low << bits, (high << bits) | ((low&(ander << (32 - bits))) >>> (32 - bits))]; case 'ashr': - return [(((value[0] >>> bits ) | ((value[1]&ander) << (32 - bits))) >> 0) >>> 0, (value[1] >> bits) >>> 0]; + return [(((low >>> bits ) | ((high&ander) << (32 - bits))) >> 0) >>> 0, (high >> bits) >>> 0]; case 'lshr': - return [((value[0] >>> bits) | ((value[1]&ander) << (32 - bits))) >>> 0, value[1] >>> bits]; + return [((low >>> bits) | ((high&ander) << (32 - bits))) >>> 0, high >>> bits]; } } else if (bits == 32) { switch (op) { case 'shl': - return [0, value[0]]; + return [0, low]; case 'ashr': - return [value[1], (value[1]|0) < 0 ? ander : 0]; + return [high, (high|0) < 0 ? ander : 0]; case 'lshr': - return [value[1], 0]; + return [high, 0]; } } else { // bits > 32 switch (op) { case 'shl': - return [0, value[0] << (bits - 32)]; + return [0, low << (bits - 32)]; case 'ashr': - return [(value[1] >> (bits - 32)) >>> 0, (value[1]|0) < 0 ? ander : 0]; + return [(high >> (bits - 32)) >>> 0, (high|0) < 0 ? ander : 0]; case 'lshr': - return [value[1] >>> (bits - 32) , 0]; + return [high >>> (bits - 32) , 0]; } } abort('unknown bitshift64 op: ' + [value, op, bits]); diff --git a/tests/cases/i64toi8star.ll b/tests/cases/i64toi8star.ll index 53a31d02..d4a39340 100644 --- a/tests/cases/i64toi8star.ll +++ b/tests/cases/i64toi8star.ll @@ -27,6 +27,6 @@ entry: %"alloca point" = bitcast i32 0 to i32 ; [#uses=0] %5 = call i32 @PyLong_FromVoidPtr(i8* null) nounwind ; [#uses=0] %13 = call i32 @PyLong_FromVoidPtr(i8* inttoptr (i64 1 to i8*)) nounwind ; [#uses=0] - %0 = call i32 bitcast (i32 (i8*)* @puts to i32 (i32*)*)(i8* getelementptr inbounds ([14 x i8]* @.str, i32 0, i32 0)) ; [#uses=0] + %1 = call i32 bitcast (i32 (i8*)* @puts to i32 (i32*)*)(i8* getelementptr inbounds ([14 x i8]* @.str, i32 0, i32 0)) ; [#uses=0] ret i32 0 } diff --git a/tests/cases/inttoptr.ll b/tests/cases/inttoptr.ll index c70904c8..b0711672 100644 --- a/tests/cases/inttoptr.ll +++ b/tests/cases/inttoptr.ll @@ -15,6 +15,6 @@ entry: %"alloca point" = bitcast i32 0 to i32 ; [#uses=0] %sz.i7 = inttoptr i32 64 to i32* ; [#uses=1 type=i32*] store i32 184, i32* %sz.i7, align 8, !tbaa !1610 - %0 = call i32 bitcast (i32 (i8*)* @puts to i32 (i32*)*)(i8* getelementptr inbounds ([14 x i8]* @.str, i32 0, i32 0)) ; [#uses=0] + %1 = call i32 bitcast (i32 (i8*)* @puts to i32 (i32*)*)(i8* getelementptr inbounds ([14 x i8]* @.str, i32 0, i32 0)) ; [#uses=0] ret i32 0 } diff --git a/tests/cases/lifetime.ll b/tests/cases/lifetime.ll deleted file mode 100644 index dc6d471d..00000000 --- a/tests/cases/lifetime.ll +++ /dev/null @@ -1,42 +0,0 @@ -; ModuleID = '/tmp/emscripten/tmp/src.cpp.o' -target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32" -target triple = "i386-pc-linux-gnu" - -%struct.vec2 = type { float, float } - -@.str = private unnamed_addr constant [15 x i8] c"hello, world!\0A\00" ; [#uses=1] - -; [#uses=1] -declare i32 @printf(i8* noalias, ...) - -define linkonce_odr float @vec2Length(%struct.vec2* %this) nounwind align 2 { -entry: - %__first.addr.i = alloca %struct.b2Pair.5*, align 4 ; [#uses=3 type=%struct.b2Pair.5**] - %__last.addr.i = alloca %struct.b2Pair.5*, align 4 ; [#uses=3 type=%struct.b2Pair.5**] - %__comp.addr.i = alloca %struct.b2Pair.5*, align 4 ; [#uses=2 type=%struct.b2Pair.5**] - %13 = bitcast %struct.vec2** %__first.addr.i to i8* ; [#uses=1 type=i8*] - call void @llvm.lifetime.start(i64 -1, i8* %13) - %14 = bitcast %struct.vec2** %__last.addr.i to i8* ; [#uses=1 type=i8*] - call void @llvm.lifetime.start(i64 -1, i8* %14) - %15 = bitcast i1 (%struct.vec2*, %struct.vec2*)** %__comp.addr.i to i8* ; [#uses=1 type=i8*] - call void @llvm.lifetime.start(i64 -1, i8* %15) - store %struct.vec2* %10, %struct.vec2** %__first.addr.i, align 4 - store %struct.vec2* %add.ptr, %struct.vec2** %__last.addr.i, align 4 - %18 = bitcast %struct.vec2** %__first.addr.i to i8* ; [#uses=1 type=i8*] - call void @llvm.lifetime.end(i64 -1, i8* %18) - %19 = bitcast %struct.vec2** %__last.addr.i to i8* ; [#uses=1 type=i8*] - call void @llvm.lifetime.end(i64 -1, i8* %19) - %20 = bitcast i1 (%struct.vec2*, %struct.vec2*)** %__comp.addr.i to i8* ; [#uses=1 type=i8*] - call void @llvm.lifetime.end(i64 -1, i8* %20) -} - -define i32 @main() { -entry: - %retval = alloca i32, align 4 ; [#uses=1] - store i32 0, i32* %retval - %b = getelementptr inbounds i32* %retval, i32 0, i32 1 ; [#uses=1] ; force __stackBase__ to appear! - %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0)) ; [#uses=0] - call i32 (i32)* @nonexistant(i32 %b) ; keep %b alive - ret i32 0 -} - diff --git a/tests/cases/lifetime.py b/tests/cases/lifetime.py deleted file mode 100644 index 3bb9cbac..00000000 --- a/tests/cases/lifetime.py +++ /dev/null @@ -1,6 +0,0 @@ -if Settings.MICRO_OPTS: - assert '__stackBase__' in generated, 'There should be some stack activity (without which, we cannot do the next checks)' - assert '__stackBase__+4' not in generated, 'All variables should have been nativized' - assert '__stackBase__+8' not in generated, 'All variables should have been nativized' - assert 'comp_addr' not in generated, 'This variable should have been eliminated during analysis' - diff --git a/tests/lifetime.ll b/tests/lifetime.ll new file mode 100644 index 00000000..058d6b4a --- /dev/null +++ b/tests/lifetime.ll @@ -0,0 +1,37 @@ +; ModuleID = '/tmp/emscripten/tmp/src.cpp.o' +target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32" +target triple = "i386-pc-linux-gnu" + +%struct.vec2 = type { float, float } + +@.str = private unnamed_addr constant [15 x i8] c"hello, world!\0A\00" ; [#uses=1] + +; [#uses=1] +declare i32 @printf(i8* noalias, ...) + +define linkonce_odr i32 @vec2Length(%struct.vec2* %this) nounwind align 2 { +entry: + %__first.addr.i = alloca %struct.vec2*, align 4 ; [#uses=3 type=%struct.vec2**] + %__last.addr.i = alloca %struct.vec2*, align 4 ; [#uses=3 type=%struct.vec2**] + %__comp.addr.i = alloca %struct.vec2*, align 4 ; [#uses=2 type=%struct.vec2**] + %a13 = bitcast %struct.vec2** %__first.addr.i to i8* ; [#uses=1 type=i8*] + call void @llvm.lifetime.start(i64 -1, i8* %a13) + %a14 = bitcast %struct.vec2** %__last.addr.i to i8* ; [#uses=1 type=i8*] + call void @llvm.lifetime.start(i64 -1, i8* %a14) + %a18 = bitcast %struct.vec2** %__first.addr.i to i8* ; [#uses=1 type=i8*] + call void @llvm.lifetime.end(i64 -1, i8* %a18) + %a19 = bitcast %struct.vec2** %__last.addr.i to i8* ; [#uses=1 type=i8*] + call void @llvm.lifetime.end(i64 -1, i8* %a19) + ret i32 0 +} + +define i32 @main() { +entry: + %retval = alloca i32, align 4 ; [#uses=1] + %call = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([15 x i8]* @.str, i32 0, i32 0)) ; [#uses=0] + ret i32 0 +} + +declare void @llvm.lifetime.start(i64, i8*) +declare void @llvm.lifetime.end(i64, i8*) + diff --git a/tests/runner.py b/tests/runner.py index 1cd94111..ac422e9e 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -428,6 +428,8 @@ if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv): self.do_run(src, output, force_c=True) def test_i64(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('i64 mode 1 requires ta2') + for i64_mode in [0,1]: if i64_mode == 0 and Settings.USE_TYPED_ARRAYS != 0: continue # Typed arrays truncate i64 if i64_mode == 1 and Settings.QUANTUM_SIZE == 1: continue # TODO: i64 mode 1 for q1 @@ -1547,6 +1549,25 @@ if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv): self.do_run(src, '*11,74,32,1012*\n*11*\n*22*') def test_dynamic_cast(self): + src = r''' + #include <stdio.h> + + struct Support { + virtual void f() { + printf("f()\n"); + } + }; + + struct Derived : Support { + }; + + int main() { + Support * p = new Derived; + dynamic_cast<Derived*>(p)->f(); + } + ''' + self.do_run(src, 'f()\n') + src = ''' #include <stdio.h> @@ -3290,6 +3311,7 @@ at function.:blag self.do_run(src, expected) def test_parseInt(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('i64 mode 1 requires ta2') if Settings.QUANTUM_SIZE == 1: return self.skip('Q1 and I64_1 do not mix well yet') Settings.I64_MODE = 1 # Necessary to prevent i64s being truncated into i32s, but we do still get doubling # FIXME: The output here is wrong, due to double rounding of i64s! @@ -3298,7 +3320,7 @@ at function.:blag self.do_run(src, expected) def test_printf(self): - if Settings.QUANTUM_SIZE == 1: return self.skip('Q1 and I64_1 do not mix well yet') + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('i64 mode 1 requires ta2') Settings.I64_MODE = 1 self.banned_js_engines = [NODE_JS, V8_ENGINE] # SpiderMonkey and V8 do different things to float64 typed arrays, un-NaNing, etc. src = open(path_from_root('tests', 'printf', 'test.c'), 'r').read() @@ -3869,15 +3891,14 @@ def process(filename): self.do_run(src, "1 2 3") def test_readdir(self): - add_pre_run = ''' def process(filename): src = open(filename, 'r').read().replace( - '// {{PRE_RUN_ADDITIONS}}', - "FS.createFolder('', 'test', true, true);\\nFS.createLazyFile( 'test', 'some_file', 'http://localhost/some_file', true, false);\\nFS.createFolder('test', 'some_directory', true, true);" + '// {{PRE_RUN_ADDITIONS}}', + "FS.createFolder('', 'test', true, true);\\nFS.createLazyFile( 'test', 'some_file', 'http://localhost/some_file', true, false);\\nFS.createFolder('test', 'some_directory', true, true);" ) open(filename, 'w').write(src) - ''' +''' src = ''' #include <dirent.h> @@ -4674,17 +4695,31 @@ def process(filename): 'hello python world!\n[0, 2, 4, 6]\n5\n22\n5.470000', args=['-S', '-c' '''print "hello python world!"; print [x*2 for x in range(4)]; t=2; print 10-3-t; print (lambda x: x*2)(11); print '%f' % 5.47''']) + def test_lifetime(self): + if self.emcc_args is None: return self.skip('test relies on emcc opts') + + try: + os.environ['EMCC_LEAVE_INPUTS_RAW'] = '1' + + self.do_ll_run(path_from_root('tests', 'lifetime.ll'), 'hello, world!\n') + if '-O1' in self.emcc_args or '-O2' in self.emcc_args: + assert 'a18' not in open(os.path.join(self.get_dir(), 'src.cpp.o.js')).read(), 'lifetime stuff and their vars must be culled' + else: + assert 'a18' in open(os.path.join(self.get_dir(), 'src.cpp.o.js')).read(), "without opts, it's there" + + finally: + del os.environ['EMCC_LEAVE_INPUTS_RAW'] + # Test cases in separate files. Note that these files may contain invalid .ll! # They are only valid enough for us to read for test purposes, not for llvm-as # to process. def test_cases(self): - try: - self.banned_js_engines = [NODE_JS] # node issue 1669, exception causes stdout not to be flushed + if Building.LLVM_OPTS: return self.skip("Our code is not exactly 'normal' llvm assembly") + try: os.environ['EMCC_LEAVE_INPUTS_RAW'] = '1' - + self.banned_js_engines = [NODE_JS] # node issue 1669, exception causes stdout not to be flushed Settings.CHECK_OVERFLOWS = 0 - if Building.LLVM_OPTS: return self.skip("Our code is not exactly 'normal' llvm assembly") for name in glob.glob(path_from_root('tests', 'cases', '*.ll')): shortname = name.replace('.ll', '') @@ -5563,6 +5598,9 @@ TT = %s del T # T is just a shape for the specific subclasses, we don't test it itself class other(RunnerCore): + def test_reminder(self): + assert 0, 'find appearances of i64 in src/, most are now unneeded' + def test_emcc(self): emcc_debug = os.environ.get('EMCC_DEBUG') @@ -5725,12 +5763,12 @@ Options that are modified or new in %s include: for params, test, text in [ (['-s', 'INLINING_LIMIT=0'], lambda generated: 'function _dump' in generated, 'no inlining without opts'), (['-O1', '-s', 'INLINING_LIMIT=0'], lambda generated: 'function _dump' not in generated, 'inlining'), - (['-s', 'USE_TYPED_ARRAYS=0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), - (['-s', 'USE_TYPED_ARRAYS=1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), + (['-s', 'USE_TYPED_ARRAYS=0', '-s', 'I64_MODE=0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), + (['-s', 'USE_TYPED_ARRAYS=1', '-s', 'I64_MODE=0'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), ([], lambda generated: 'Module["_dump"]' not in generated, 'dump is not exported by default'), (['-s', 'EXPORTED_FUNCTIONS=["_main", "_dump"]'], lambda generated: 'Module["_dump"]' in generated, 'dump is now exported'), - (['--typed-arrays', '0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), - (['--typed-arrays', '1'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), + (['--typed-arrays', '0', '-s', 'I64_MODE=0'], lambda generated: 'new Int32Array' not in generated, 'disable typed arrays'), + (['--typed-arrays', '1', '-s', 'I64_MODE=0'], lambda generated: 'IHEAPU = ' in generated, 'typed arrays 1 selected'), (['--typed-arrays', '2'], lambda generated: 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 selected'), (['--llvm-opts', '1'], lambda generated: '_puts(' in generated, 'llvm opts requested'), ]: @@ -5867,7 +5905,7 @@ f.close() clear() output = Popen([EMCC, path_from_root('tests', 'hello_world_gles.c'), '-o', 'something.html', '-DHAVE_BUILTIN_SINCOS', - '-s', 'USE_TYPED_ARRAYS=0', + '-s', 'USE_TYPED_ARRAYS=0', '-s', 'I64_MODE=0', '--shell-file', path_from_root('tests', 'hello_world_gles_shell.html')], stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] @@ -6079,7 +6117,7 @@ elif 'benchmark' in str(sys.argv): ''' self.do_benchmark(src, [], 'final: 720.') - def test_files(self): + def zzztest_files(self): src = r''' #include<stdio.h> #include<stdlib.h> diff --git a/tools/bindings_generator.py b/tools/bindings_generator.py index bdb3e755..e1c1ab4b 100755 --- a/tools/bindings_generator.py +++ b/tools/bindings_generator.py @@ -180,8 +180,8 @@ for classname, clazz in classes.iteritems(): method['name'] = 'op_comp' method['operator'] = ' return arg0 == arg1;' if len(method['parameters'][0]) == 2 else ' return *self == arg0;' else: - print 'zz unknown operator:', method['name'] - 1/0. + print 'zz unknown operator:', method['name'], ', ignoring' + method['ignore'] = True # Fill in some missing stuff method['returns_text'] = method['returns_text'].replace('&', '').replace('*', '') diff --git a/tools/shared.py b/tools/shared.py index d830627c..b5ae1ae1 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -333,7 +333,6 @@ class Settings: Settings.CORRECT_SIGNS = 0 Settings.CORRECT_OVERFLOWS = 0 Settings.CORRECT_ROUNDINGS = 0 - Settings.I64_MODE = 0 Settings.DOUBLE_MODE = 0 if noisy: print >> sys.stderr, 'Warning: Applying some potentially unsafe optimizations! (Use -O2 if this fails.)' |