diff options
Diffstat (limited to 'src/parseTools.js')
-rw-r--r-- | src/parseTools.js | 815 |
1 files changed, 519 insertions, 296 deletions
diff --git a/src/parseTools.js b/src/parseTools.js index 89267d10..3949491e 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -6,11 +6,10 @@ // Does simple 'macro' substitution, using Django-like syntax, // {{{ code }}} will be replaced with |eval(code)|. function processMacros(text) { - return text.replace(/{{{[^}]+}}}/g, function(str) { + return text.replace(/{{{([^}]|}(?!}))+}}}/g, function(str) { str = str.substr(3, str.length-6); var ret = eval(str); - if (ret !== undefined) ret = ret.toString(); - return ret; + return ret ? ret.toString() : ''; }); } @@ -66,7 +65,7 @@ function pointingLevels(type) { var ret = 0; var len1 = type.length - 1; while (type[len1-ret] && type[len1-ret] === '*') { - ret ++; + ret++; } return ret; } @@ -79,6 +78,7 @@ function toNiceIdent(ident) { assert(ident); if (parseFloat(ident) == ident) return ident; if (ident == 'null') return '0'; // see parseNumerical + if (ident == 'undef') return '0'; return ident.replace('%', '$').replace(/["&\\ \.@:<>,\*\[\]\(\)-]/g, '_'); } @@ -104,6 +104,15 @@ function isNiceIdent(ident, loose) { } } +function isJSVar(ident) { + return /^\(?[$_]?[\w$_\d ]*\)+$/.test(ident); + +} + +function isLocalVar(ident) { + return ident[0] == '$'; +} + function isStructPointerType(type) { // This test is necessary for clang - in llvm-gcc, we // could check for %struct. The downside is that %1 can @@ -112,7 +121,7 @@ function isStructPointerType(type) { // |%5()| as a function call (like |i32 (i8*)| etc.). So // we must check later on, in call(), where we have more // context, to differentiate such cases. - // A similar thing happns in isStructType() + // A similar thing happens in isStructType() return !Runtime.isNumberType(type) && type[0] == '%'; } @@ -120,9 +129,13 @@ function isPointerType(type) { return type[type.length-1] == '*'; } +function isArrayType(type) { + return /^\[\d+\ x\ (.*)\]/.test(type); +} + function isStructType(type) { if (isPointerType(type)) return false; - if (/^\[\d+\ x\ (.*)\]/.test(type)) return true; // [15 x ?] blocks. Like structs + if (isArrayType(type)) return true; if (/<?{ ?[^}]* ?}>?/.test(type)) return true; // { i32, i8 } etc. - anonymous struct types // See comment in isStructPointerType() return type[0] == '%'; @@ -155,7 +168,8 @@ function isIntImplemented(type) { } // Note: works for iX types and structure types, not pointers (even though they are implemented as ints) -function getBits(type) { +function getBits(type, allowPointers) { + if (allowPointers && isPointerType(type)) return 32; if (!type) return 0; if (type[0] == 'i') { var left = type.substr(1); @@ -167,11 +181,23 @@ function getBits(type) { } if (isStructType(type)) { var typeData = Types.types[type]; + if (typeData === undefined) return 0; return typeData.flatSize*8; } return 0; } +function getNumIntChunks(type) { + return Math.ceil(getBits(type, true)/32); +} + +function isIdenticallyImplemented(type1, type2) { + var floats = +(type1 in Runtime.FLOAT_TYPES) + +(type2 in Runtime.FLOAT_TYPES); + if (floats == 2) return true; + if (floats == 1) return false; + return getNumIntChunks(type1) == getNumIntChunks(type2); +} + function isIllegalType(type) { var bits = getBits(type); return bits > 0 && (bits >= 64 || !isPowerOfTwo(bits)); @@ -210,21 +236,44 @@ function isPossiblyFunctionType(type) { function isFunctionType(type, out) { if (!isPossiblyFunctionType(type)) return false; + type = type.substr(0, type.length-1); // remove final '*' + var firstOpen = type.indexOf('('); + if (firstOpen <= 0) return false; type = type.replace(/"[^"]+"/g, '".."'); - var parts; - // hackish, but quick splitting of function def parts. this must be fast as it happens a lot - if (type[0] != '[') { - parts = type.split(' '); + var lastOpen = type.lastIndexOf('('); + var returnType; + if (firstOpen == lastOpen) { + returnType = getReturnType(type); + if (!isType(returnType)) return false; } else { - var index = type.search(']'); - index += type.substr(index).search(' '); - parts = [type.substr(0, index), type.substr(index+1)]; + returnType = 'i8*'; // some pointer type, no point in analyzing further + } + if (out) out.returnType = returnType; + // find ( that starts the arguments + var depth = 0, i = type.length-1, argText = null; + while (i >= 0) { + var curr = type[i]; + if (curr == ')') depth++; + else if (curr == '(') { + depth--; + if (depth == 0) { + argText = type.substr(i); + break; + } + } + i--; } - if (pointingLevels(type) !== 1) return false; - var text = removeAllPointing(parts.slice(1).join(' ')); - if (!text) return false; - if (out) out.returnType = parts[0]; - return isType(parts[0]) && isFunctionDef({ text: text, item: tokenize(text.substr(1, text.length-2), true) }, out); + assert(argText); + return isFunctionDef({ text: argText, item: tokenize(argText.substr(1, argText.length-2), true) }, out); +} + +function getReturnType(type) { + if (pointingLevels(type) > 1) return '*'; // the type of a call can be either the return value, or the entire function. ** or more means it is a return value + var lastOpen = type.lastIndexOf('('); + if (lastOpen > 0) { + return type.substr(0, lastOpen-1); + } + return type; } var isTypeCache = {}; // quite hot, optimize as much as possible @@ -241,11 +290,22 @@ function isVarArgsFunctionType(type) { return type.substr(-varArgsSuffix.length) == varArgsSuffix; } -function countNormalArgs(type) { - var out = {}; +function getNumLegalizedVars(type) { // how many legalized variables are needed to represent this type + if (type in Runtime.FLOAT_TYPES) return 1; + return Math.max(getNumIntChunks(type), 1); +} + +function countNormalArgs(type, out, legalized) { + out = out || {}; if (!isFunctionType(type, out)) return -1; - if (isVarArgsFunctionType(type)) out.numArgs--; - return out.numArgs; + var ret = 0; + if (out.segments) { + for (var i = 0; i < out.segments.length; i++) { + ret += legalized ? getNumLegalizedVars(out.segments[i][0].text) : 1; + } + } + if (isVarArgsFunctionType(type)) ret--; + return ret; } function addIdent(token) { @@ -404,8 +464,7 @@ function isIndexableGlobal(ident) { return false; } var data = Variables.globals[ident]; - // in asm.js, externals are just globals - return !data.alias && (ASM_JS || !data.external); + return !data.alias && !data.external; } function makeGlobalDef(ident) { @@ -421,8 +480,7 @@ function makeGlobalUse(ident) { UNINDEXABLE_GLOBALS[ident] = 1; return ident; } - // We know and assert on TOTAL_STACK being equal to GLOBAL_BASE - return (TOTAL_STACK + index).toString(); + return (Runtime.GLOBAL_BASE + index).toString(); } return ident; } @@ -493,7 +551,7 @@ function parseLLVMSegment(segment) { return { intertype: 'value', ident: toNiceIdent(segment[1].text), - type: segment[0].text + type: type }; } } @@ -557,7 +615,9 @@ function parseLLVMFunctionCall(segment) { function eatLLVMIdent(tokens) { var ret; if (tokens[0].text in PARSABLE_LLVM_FUNCTIONS) { - ret = parseLLVMFunctionCall([{text: 'i0'}].concat(tokens.slice(0,2))).ident; // TODO: Handle more cases, return a full object, process it later + var item = parseLLVMFunctionCall([{text: '?'}].concat(tokens.slice(0,2))); // TODO: Handle more cases, return a full object, process it later + if (item.intertype == 'bitcast') checkBitcast(item); + ret = item.ident; tokens.shift(); tokens.shift(); } else { @@ -571,7 +631,7 @@ function cleanOutTokens(filterOut, tokens, indexes) { if (typeof indexes !== 'object') indexes = [indexes]; for (var i = indexes.length-1; i >=0; i--) { var index = indexes[i]; - while (tokens[index].text in filterOut) { + while (index < tokens.length && tokens[index].text in filterOut) { tokens.splice(index, 1); } } @@ -592,6 +652,7 @@ function IEEEUnHex(stringy) { while (stringy.length < 16) stringy = '0' + stringy; if (FAKE_X86_FP80 && stringy.length > 16) { stringy = stringy.substr(stringy.length-16, 16); + assert(TARGET_X86, 'must only see >64 bit floats in x86, as fp80s'); warnOnce('.ll contains floating-point values with more than 64 bits. Faking values for them. If they are used, this will almost certainly break horribly!'); } assert(stringy.length === 16, 'Can only unhex 16-digit double numbers, nothing platform-specific'); // |long double| can cause x86_fp80 which causes this @@ -642,14 +703,16 @@ function makeI64(low, high) { // Splits a number (an integer in a double, possibly > 32 bits) into an USE_TYPED_ARRAYS == 2 i64 value. // Will suffer from rounding. mergeI64 does the opposite. -function splitI64(value) { +function splitI64(value, floatConversion) { // 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. + var lowInput = legalizedI64s ? value : 'VALUE'; + if (floatConversion && ASM_JS) lowInput = asmFloatToInt(lowInput); if (legalizedI64s) { - return [value + '>>>0', 'Math.min(Math.floor((' + value + ')/4294967296), 4294967295)']; + return [lowInput + '>>>0', 'Math.min(Math.floor((' + value + ')/' + asmEnsureFloat(4294967296, 'float') + '), ' + asmEnsureFloat(4294967295, 'float') + ')>>>0']; } else { - return makeInlineCalculation(makeI64('VALUE>>>0', 'Math.min(Math.floor(VALUE/4294967296), 4294967295)'), value, 'tempBigIntP'); + return makeInlineCalculation(makeI64(lowInput + '>>>0', 'Math.min(Math.floor(VALUE/' + asmEnsureFloat(4294967296, 'float') + '), ' + asmEnsureFloat(4294967295, 'float') + ')>>>0'), value, 'tempBigIntP'); } } function mergeI64(value, unsigned) { @@ -678,7 +741,7 @@ function makeCopyI64(value) { function parseArbitraryInt(str, bits) { // We parse the string into a vector of digits, base 10. This is convenient to work on. - assert(bits % 32 == 0 || ('i' + (bits % 32)) in Runtime.INT_TYPES, 'Arbitrary-sized ints must tails that are of legal size'); + assert(bits > 0); // NB: we don't check that the value in str can fit in this amount of bits function str2vec(s) { // index 0 is the highest value var ret = []; @@ -803,7 +866,9 @@ function parseNumerical(value, type) { return '0'; } if (isNumber(value)) { - return parseFloat(value).toString(); // will change e.g. 5.000000e+01 to 50 + var ret = parseFloat(value); // will change e.g. 5.000000e+01 to 50 + if (type in Runtime.FLOAT_TYPES && value[0] == '-' && ret === 0) return '-0'; // fix negative 0, toString makes it 0 + return ret.toString(); } else { return value; } @@ -968,27 +1033,29 @@ function checkSafeHeap() { return SAFE_HEAP === 1 || checkSpecificSafeHeap(); } -if (ASM_JS) { - var hexMemoryMask = '0x' + (TOTAL_MEMORY-1).toString(16); - var decMemoryMask = (TOTAL_MEMORY-1).toString(); - var memoryMask = hexMemoryMask.length <= decMemoryMask.length ? hexMemoryMask : decMemoryMask; -} - -function getHeapOffset(offset, type) { +function getHeapOffset(offset, type, forceAsm) { if (USE_TYPED_ARRAYS !== 2) { return offset; - } else { - if (Runtime.getNativeFieldSize(type) > 4) { - type = 'i32'; // XXX we emulate 64-bit values as 32 + } + + if (Runtime.getNativeFieldSize(type) > 4) { + if (type == 'i64' || TARGET_X86) { + type = 'i32'; // XXX we emulate 64-bit values as 32 in x86, and also in le32 but only i64, not double } - var shifts = Math.log(Runtime.getNativeTypeSize(type))/Math.LN2; - offset = '(' + offset + ')'; - if (ASM_JS && phase == 'funcs') offset = '(' + offset + '&' + memoryMask + ')'; - if (shifts != 0) { - return '(' + offset + '>>' + shifts + ')'; + } + + var sz = Runtime.getNativeTypeSize(type); + var shifts = Math.log(sz)/Math.LN2; + offset = '(' + offset + ')'; + if (shifts != 0) { + if (CHECK_HEAP_ALIGN) { + return '(CHECK_ALIGN_' + sz + '(' + offset + '|0)>>' + shifts + ')'; } else { - return offset; + return '(' + offset + '>>' + shifts + ')'; } + } else { + // we need to guard against overflows here, HEAP[U]8 expects a guaranteed int + return isJSVar(offset) ? offset : '(' + offset + '|0)'; } } @@ -999,7 +1066,8 @@ function makeVarDef(js) { function asmEnsureFloat(value, type) { // ensures that a float type has either 5.5 (clearly a float) or +5 (float due to asm coercion) if (!ASM_JS) return value; - if (!isIntImplemented(type) && isNumber(value) && value.toString().indexOf('.') < 0) { + // coerce if missing a '.', or if smaller than 1, so could be 1e-5 which has no . + if (type in Runtime.FLOAT_TYPES && isNumber(value) && (value.toString().indexOf('.') < 0 || Math.abs(value) < 1)) { return '(+(' + value + '))'; } else { return value; @@ -1007,31 +1075,66 @@ function asmEnsureFloat(value, type) { // ensures that a float type has either 5 } function asmInitializer(type, impl) { - if (isIntImplemented(type)) {// || (impl && impl == 'VAR_EMULATED')) { - return '0'; - } else { + if (type in Runtime.FLOAT_TYPES) { return '+0'; + } else { + return '0'; } } -function asmCoercion(value, type) { +function asmCoercion(value, type, signedness) { if (!ASM_JS) return value; if (type == 'void') { return value; - } else if (isIntImplemented(type)) { + } else if (type in Runtime.FLOAT_TYPES) { + if (isNumber(value)) { + return asmEnsureFloat(value, type); + } else { + if (signedness) { + if (signedness == 'u') { + value = '(' + value + ')>>>0'; + } else { + value = '(' + value + ')|0'; + } + } + return '(+(' + value + '))'; + } + } else { return '((' + value + ')|0)'; + } +} + +function asmFloatToInt(x) { + return '(~~(' + x + '))'; +} + +function makeGetTempDouble(i, type, forSet) { // get an aliased part of the tempDouble temporary storage + // Cannot use makeGetValue because it uses us + // this is a unique case where we *can* use HEAPF64 + var slab = type == 'double' ? 'HEAPF64' : makeGetSlabs(null, type)[0]; + var ptr = getFastValue('tempDoublePtr', '+', Runtime.getNativeTypeSize(type)*i); + var offset; + if (type == 'double') { + offset = '(' + ptr + ')>>3'; } else { - return '(+(' + value + '))'; + offset = getHeapOffset(ptr, type); } + var ret = slab + '[' + offset + ']'; + if (!forSet) ret = asmCoercion(ret, type); + return ret; } -function makeGetTempDouble(i) { // TODO: Support other than i32 - return makeGetValue('tempDoublePtr', Runtime.getNativeTypeSize('i32')*i, 'i32'); +function makeSetTempDouble(i, type, value) { + return makeGetTempDouble(i, type, true) + '=' + asmEnsureFloat(value, type); } +var asmPrintCounter = 0; + // See makeSetValue -function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSafe) { +function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSafe, forceAsm) { if (UNALIGNED_MEMORY) align = 1; + else if (FORCE_ALIGNED_MEMORY && !isIllegalType(type)) align = 8; + if (isStructType(type)) { var typeData = Types.types[type]; var ret = []; @@ -1041,23 +1144,26 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa return '{ ' + ret.join(', ') + ' }'; } - if (DOUBLE_MODE == 1 && USE_TYPED_ARRAYS == 2 && type == 'double') { - return '(HEAP32[tempDoublePtr>>2]=' + makeGetValue(ptr, pos, 'i32', noNeedFirst, unsigned, ignore, align) + ',' + - 'HEAP32[tempDoublePtr+4>>2]=' + makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, unsigned, ignore, align) + ',' + - 'HEAPF64[tempDoublePtr>>3])'; + // In double mode 1, in x86 we always assume unaligned because we can't trust that; otherwise in le32 + // we need this code path if we are not fully aligned. + if (DOUBLE_MODE == 1 && USE_TYPED_ARRAYS == 2 && type == 'double' && (TARGET_X86 || align < 8)) { + return '(' + makeSetTempDouble(0, 'i32', makeGetValue(ptr, pos, 'i32', noNeedFirst, unsigned, ignore, align)) + ',' + + makeSetTempDouble(1, 'i32', makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, unsigned, ignore, align)) + ',' + + makeGetTempDouble(0, 'double') + ')'; } if (USE_TYPED_ARRAYS == 2 && align) { // Alignment is important here. May need to split this up var bytes = Runtime.getNativeTypeSize(type); + if (DOUBLE_MODE == 0 && type == 'double') bytes = 4; // we will really only read 4 bytes here if (bytes > align) { var ret = '('; if (isIntImplemented(type)) { if (bytes == 4 && align == 2) { // Special case that we can optimize - ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore) + '+' + + ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore) + '|' + '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore) + '<<16)'; - } else { // XXX we cannot truly handle > 4... + } else { // XXX we cannot truly handle > 4... (in x86) ret = ''; for (var i = 0; i < bytes; i++) { ret += '(' + makeGetValue(ptr, getFastValue(pos, '+', i), 'i8', noNeedFirst, 1, ignore) + (i > 0 ? '<<' + (8*i) : '') + ')'; @@ -1067,9 +1173,9 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa } } else { if (type == 'float') { - ret += 'copyTempFloat(' + getFastValue(ptr, '+', pos) + '),HEAPF32[tempDoublePtr>>2]'; + ret += 'copyTempFloat(' + asmCoercion(getFastValue(ptr, '+', pos), 'i32') + '),' + makeGetTempDouble(0, 'float'); } else { - ret += 'copyTempDouble(' + getFastValue(ptr, '+', pos) + '),HEAP64[tempDoublePtr>>3]'; + ret += 'copyTempDouble(' + asmCoercion(getFastValue(ptr, '+', pos), 'i32') + '),' + makeGetTempDouble(0, 'double'); } } ret += ')'; @@ -1079,18 +1185,27 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa var offset = calcFastOffset(ptr, pos, noNeedFirst); if (SAFE_HEAP && !noSafe) { - if (type !== 'null' && type[0] !== '#') type = '"' + safeQuote(type) + '"'; - if (type[0] === '#') type = type.substr(1); - return 'SAFE_HEAP_LOAD(' + offset + ', ' + type + ', ' + (!!unsigned+0) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; + var printType = type; + if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; + if (printType[0] === '#') printType = printType.substr(1); + return asmCoercion('SAFE_HEAP_LOAD(' + asmCoercion(offset, 'i32') + ', ' + (ASM_JS ? 0 : printType) + ', ' + (!!unsigned+0) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')', type); } else { - var ret = makeGetSlabs(ptr, type, false, unsigned)[0] + '[' + getHeapOffset(offset, type) + ']'; - if (ASM_JS && phase == 'funcs') { + var ret = makeGetSlabs(ptr, type, false, unsigned)[0] + '[' + getHeapOffset(offset, type, forceAsm) + ']'; + if (ASM_JS && (phase == 'funcs' || forceAsm)) { ret = asmCoercion(ret, type); } + if (ASM_HEAP_LOG) { + ret = makeInlineCalculation('(asmPrint' + (type in Runtime.FLOAT_TYPES ? 'Float' : 'Int') + '(' + (asmPrintCounter++) + ',' + asmCoercion('VALUE', type) + '), VALUE)', ret, + 'temp' + (type in Runtime.FLOAT_TYPES ? 'Double' : 'Int')); + } return ret; } } +function makeGetValueAsm(ptr, pos, type, unsigned) { + return makeGetValue(ptr, pos, type, null, unsigned, null, null, null, true); +} + function indexizeFunctions(value, type) { assert((type && type !== '?') || (typeof value === 'string' && value.substr(0, 6) === 'CHECK_'), 'No type given for function indexizing'); assert(value !== type, 'Type set to value'); @@ -1120,8 +1235,10 @@ function indexizeFunctions(value, type) { //! 'null' means, in the context of SAFE_HEAP, that we should accept all types; //! which means we should write to all slabs, ignore type differences if any on reads, etc. //! @param noNeedFirst Whether to ignore the offset in the pointer itself. -function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign) { +function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign, forceAsm) { if (UNALIGNED_MEMORY && !forcedAlign) align = 1; + else if (FORCE_ALIGNED_MEMORY && !isIllegalType(type)) align = 8; + sep = sep || ';'; if (isStructType(type)) { var typeData = Types.types[type]; @@ -1137,10 +1254,10 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, return ret.join('; '); } - if (DOUBLE_MODE == 1 && USE_TYPED_ARRAYS == 2 && type == 'double') { - return '(HEAPF64[tempDoublePtr>>3]=' + value + ',' + - makeSetValue(ptr, pos, 'HEAP32[tempDoublePtr>>2]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + - makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'HEAP32[tempDoublePtr+4>>2]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; + if (DOUBLE_MODE == 1 && USE_TYPED_ARRAYS == 2 && type == 'double' && (TARGET_X86 || align < 8)) { + return '(' + makeSetTempDouble(0, 'double', value) + ',' + + makeSetValue(ptr, pos, makeGetTempDouble(0, 'i32'), 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + + makeSetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), makeGetTempDouble(1, 'i32'), 'i32', noNeedFirst, ignore, align, noSafe, ',') + ')'; } else if (USE_TYPED_ARRAYS == 2 && type == 'i64') { return '(tempI64 = [' + splitI64(value) + '],' + makeSetValue(ptr, pos, 'tempI64[0]', 'i32', noNeedFirst, ignore, align, noSafe, ',') + ',' + @@ -1152,6 +1269,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, 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); + if (DOUBLE_MODE == 0 && type == 'double') bytes = 4; // we will really only read 4 bytes here if (bytes > align || needSplitting) { var ret = ''; if (isIntImplemented(type)) { @@ -1164,7 +1282,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, 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; + if (i < bytes-1) ret += sep + 'tempBigInt = tempBigInt>>8' + sep; } } } else { @@ -1178,17 +1296,19 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, value = indexizeFunctions(value, type); var offset = calcFastOffset(ptr, pos, noNeedFirst); if (SAFE_HEAP && !noSafe) { - if (type !== 'null' && type[0] !== '#') type = '"' + safeQuote(type) + '"'; - if (type[0] === '#') type = type.substr(1); - return 'SAFE_HEAP_STORE(' + offset + ', ' + value + ', ' + type + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; + var printType = type; + if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; + if (printType[0] === '#') printType = printType.substr(1); + return 'SAFE_HEAP_STORE(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + (ASM_JS ? 0 : printType) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; } else { - return makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type) + ']=' + value }).join(sep); - //return '(print("set:"+(' + value + ')+":"+(' + getHeapOffset(offset, type) + ')),' + - // makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type) + ']=' + value }).join('; ') + ')'; + return makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type, forceAsm) + ']=' + value }).join(sep); } } -var SEEK_OPTIMAL_ALIGN_MIN = 20; +function makeSetValueAsm(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign) { + return makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign, true); +} + var UNROLL_LOOP_MAX = 8; function makeSetValues(ptr, pos, value, type, num, align) { @@ -1208,8 +1328,8 @@ function makeSetValues(ptr, pos, value, type, num, align) { } else { // USE_TYPED_ARRAYS == 2 // If we don't know how to handle this at compile-time, or handling it is best done in a large amount of code, call memset // TODO: optimize the case of numeric num but non-numeric value - if (!isNumber(num) || !isNumber(value) || (align < 4 && parseInt(num) >= SEEK_OPTIMAL_ALIGN_MIN)) { - return '_memset(' + getFastValue(ptr, '+', pos) + ', ' + value + ', ' + num + ', ' + align + ')'; + if (!isNumber(num) || !isNumber(value) || (parseInt(num)/align >= UNROLL_LOOP_MAX)) { + return '_memset(' + asmCoercion(getFastValue(ptr, '+', pos), 'i32') + ', ' + asmCoercion(value, 'i32') + ', ' + asmCoercion(num, 'i32') + ')'; } num = parseInt(num); value = parseInt(value); @@ -1223,13 +1343,7 @@ function makeSetValues(ptr, pos, value, type, num, align) { [4, 2, 1].forEach(function(possibleAlign) { if (num == 0) return; if (align >= possibleAlign) { - if (num <= UNROLL_LOOP_MAX*possibleAlign || ASM_JS) { // XXX test asm performance - ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign, values[possibleAlign])); - } else { - ret.push('for (var $$dest = ' + getFastValue(ptr, '+', pos) + (possibleAlign > 1 ? '>>' + log2(possibleAlign) : '') + ', ' + - '$$stop = $$dest + ' + Math.floor(num/possibleAlign) + '; $$dest < $$stop; $$dest++) {\n' + - ' HEAP' + (possibleAlign*8) + '[$$dest] = ' + values[possibleAlign] + '\n}'); - } + ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign, values[possibleAlign])); pos = getFastValue(pos, '+', Math.floor(num/possibleAlign)*possibleAlign); num %= possibleAlign; } @@ -1266,24 +1380,21 @@ function makeCopyValues(dest, src, num, type, modifier, align, sep) { unroll(type, 1) + ' }'; } else { // USE_TYPED_ARRAYS == 2 // If we don't know how to handle this at compile-time, or handling it is best done in a large amount of code, call memset - if (!isNumber(num) || (align < 4 && parseInt(num) >= SEEK_OPTIMAL_ALIGN_MIN)) { - return '_memcpy(' + dest + ', ' + src + ', ' + num + ', ' + align + ')'; + if (!isNumber(num)) num = stripCorrections(num); + if (!isNumber(align)) align = stripCorrections(align); + if (!isNumber(num) || (parseInt(num)/align >= UNROLL_LOOP_MAX)) { + return '_memcpy(' + dest + ', ' + src + ', ' + num + ')'; } num = parseInt(num); + if (ASM_JS) { + dest = stripCorrections(dest); // remove corrections, since we will be correcting after we add anyhow, + src = stripCorrections(src); // and in the heap assignment expression + } var ret = []; [4, 2, 1].forEach(function(possibleAlign) { if (num == 0) return; if (align >= possibleAlign) { - // If we can unroll the loop, do so. Also do so if we must unroll it (we do not create real loops when inlined) - if (num <= UNROLL_LOOP_MAX*possibleAlign || sep == ',' || ASM_JS) { // XXX test asm performance - ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign)); - } else { - assert(sep == ';'); - ret.push('for (var $$src = ' + src + (possibleAlign > 1 ? '>>' + log2(possibleAlign) : '') + ', ' + - '$$dest = ' + dest + (possibleAlign > 1 ? '>>' + log2(possibleAlign) : '') + ', ' + - '$$stop = $$src + ' + Math.floor(num/possibleAlign) + '; $$src < $$stop; $$src++, $$dest++) {\n' + - ' HEAP' + (possibleAlign*8) + '[$$dest] = HEAP' + (possibleAlign*8) + '[$$src]\n}'); - } + ret.push(unroll('i' + (possibleAlign*8), Math.floor(num/possibleAlign), possibleAlign)); src = getFastValue(src, '+', Math.floor(num/possibleAlign)*possibleAlign); dest = getFastValue(dest, '+', Math.floor(num/possibleAlign)*possibleAlign); num %= possibleAlign; @@ -1303,17 +1414,23 @@ function makeHEAPView(which, start, end) { var PLUS_MUL = set('+', '*'); var MUL_DIV = set('*', '/'); var PLUS_MINUS = set('+', '-'); +var TWO_TWENTY = Math.pow(2, 20); // Given two values and an operation, returns the result of that operation. // Tries to do as much as possible at compile time. +// Leaves overflows etc. unhandled, *except* for integer multiply, in order to be efficient with Math.imul function getFastValue(a, op, b, type) { a = a.toString(); b = b.toString(); + a = a == 'true' ? '1' : (a == 'false' ? '0' : a); + b = b == 'true' ? '1' : (b == 'false' ? '0' : b); if (isNumber(a) && isNumber(b)) { if (op == 'pow') { return Math.pow(a, b).toString(); } else { - return eval(a + op + '(' + b + ')').toString(); // parens protect us from "5 - -12" being seen as "5--12" which is "(5--)12" + var value = eval(a + op + '(' + b + ')'); // parens protect us from "5 - -12" being seen as "5--12" which is "(5--)12" + if (op == '/' && type in Runtime.INT_TYPES) value = value|0; // avoid emitting floats + return value.toString(); } } if (op == 'pow') { @@ -1341,6 +1458,15 @@ function getFastValue(a, op, b, type) { return '(' + a + '<<' + shifts + ')'; } } + if (!(type in Runtime.FLOAT_TYPES)) { + // if guaranteed small enough to not overflow into a double, do a normal multiply + var bits = getBits(type) || 32; // default is 32-bit multiply for things like getelementptr indexes + // Note that we can emit simple multiple in non-asm.js mode, but asm.js will not parse "16-bit" multiple, so must do imul there + if ((isNumber(a) && Math.abs(a) < TWO_TWENTY) || (isNumber(b) && Math.abs(b) < TWO_TWENTY) || (bits < 32 && !ASM_JS)) { + return '(((' + a + ')*(' + b + '))&' + ((Math.pow(2, bits)-1)|0) + ')'; // keep a non-eliminatable coercion directly on this + } + return 'Math.imul(' + a + ',' + b + ')'; + } } else { if (a == '0') { return '0'; @@ -1394,56 +1520,132 @@ function makeGetPos(ptr) { var IHEAP_FHEAP = set('IHEAP', 'IHEAPU', 'FHEAP'); -function makePointer(slab, pos, allocator, type, ptr) { +var temp64f = new Float64Array(1); +var temp32f = new Float32Array(temp64f.buffer); +var temp32 = new Uint32Array(temp64f.buffer); +var temp16 = new Uint16Array(temp64f.buffer); +var temp8 = new Uint8Array(temp64f.buffer); +var memoryInitialization = []; + +function writeInt8s(slab, i, value, type) { + var currSize; + switch (type) { + case 'i1': + case 'i8': temp8[0] = value; currSize = 1; break; + case 'i16': temp16[0] = value; currSize = 2; break; + case 'float': temp32f[0] = value; currSize = 4; break; + case 'double': temp64f[0] = value; currSize = 8; break; + case 'i64': // fall through, i64 is two i32 chunks + case 'i32': // fall through, i32 can be a pointer + default: { + if (type == 'i32' || type == 'i64' || type[type.length-1] == '*') { + if (!isNumber(value)) { // function table stuff, etc. + slab[i] = value; + slab[i+1] = slab[i+2] = slab[i+3] = 0; + return 4; + } + temp32[0] = value; + currSize = 4; + } else { + throw 'what? ' + types[i]; + } + } + } + for (var j = 0; j < currSize; j++) { + slab[i+j] = temp8[j]; + } + return currSize; +} + +function makePointer(slab, pos, allocator, type, ptr, finalMemoryInitialization) { assert(type, 'makePointer requires type info'); - if (slab.substr(0, 4) === 'HEAP' || (USE_TYPED_ARRAYS == 1 && slab in IHEAP_FHEAP)) return pos; + if (typeof slab == 'string' && (slab.substr(0, 4) === 'HEAP' || (USE_TYPED_ARRAYS == 1 && slab in IHEAP_FHEAP))) return pos; var types = generateStructTypes(type); - // compress type info and data if possible - var de; - try { - // compress all-zeros into a number (which will become zeros(..)). - // note that we cannot always eval the slab, e.g., if it contains ident,0,0 etc. In that case, no compression TODO: ensure we get arrays here, not str - var evaled = typeof slab === 'string' ? eval(slab) : slab; - de = dedup(evaled); - if (de.length === 1 && de[0] === 0) { - slab = types.length; - if (USE_TYPED_ARRAYS == 2) { - types = ['i8']; // if data is zeros, we don't need type info + if (typeof slab == 'object') { + for (var i = 0; i < slab.length; i++) { + var curr = slab[i]; + if (isNumber(curr)) { + slab[i] = parseFloat(curr); // fix "5" to 5 etc. + } else if (curr == 'undef') { + slab[i] = 0; } } - // TODO: if not all zeros, at least filter out items with type === 0. requires cleverness to know how to skip at runtime though. also - // be careful of structure padding - } catch(e){} - de = dedup(types); - if (de.length === 1) { - types = de[0]; - } else if (de.length === 2 && typeof slab === 'number') { - // If slab is all zeros, we can compress types even if we have i32,0,0,0,i32,0,0,0 etc. - we do not need the zeros - de = de.filter(function(x) { return x !== 0 }); + } + // compress type info and data if possible + if (USE_TYPED_ARRAYS != 2) { + var de; + try { + // compress all-zeros into a number (which will become zeros(..)). + // note that we cannot always eval the slab, e.g., if it contains ident,0,0 etc. In that case, no compression TODO: ensure we get arrays here, not str + var evaled = typeof slab === 'string' ? eval(slab) : slab; + de = dedup(evaled); + if (de.length === 1 && de[0] == 0) { + slab = types.length; + } + // TODO: if not all zeros, at least filter out items with type === 0. requires cleverness to know how to skip at runtime though. also + // be careful of structure padding + } catch(e){} + de = dedup(types); if (de.length === 1) { types = de[0]; + } else if (de.length === 2 && typeof slab === 'number') { + // If slab is all zeros, we can compress types even if we have i32,0,0,0,i32,0,0,0 etc. - we do not need the zeros + de = de.filter(function(x) { return x !== 0 }); + if (de.length === 1) { + types = de[0]; + } } + } else { // USE_TYPED_ARRAYS == 2 + if (!finalMemoryInitialization) { + // XXX This heavily assumes the target endianness is the same as our current endianness! XXX + var i = 0; + while (i < slab.length) { + var currType = types[i]; + if (!currType) { i++; continue } + i += writeInt8s(slab, i, slab[i], currType); + } + types = 'i8'; + } + } + if (allocator == 'ALLOC_NONE' && USE_TYPED_ARRAYS == 2) { + if (!finalMemoryInitialization) { + // writing out into memory, without a normal allocation. We put all of these into a single big chunk. + assert(typeof slab == 'object'); + assert(slab.length % QUANTUM_SIZE == 0, slab.length); // must be aligned already + var offset = ptr - Runtime.GLOBAL_BASE; + for (var i = 0; i < slab.length; i++) { + memoryInitialization[offset + i] = slab[i]; + } + return ''; + } + // This is the final memory initialization + types = 'i8'; } + // JS engines sometimes say array initializers are too large. Work around that by chunking and calling concat to combine at runtime - var chunkSize = 10240; + var chunkSize = JS_CHUNK_SIZE; function chunkify(array) { // break very large slabs into parts var ret = ''; var index = 0; while (index < array.length) { - ret = (ret ? ret + '.concat(' : '') + '[' + array.slice(index, index + chunkSize).map(JSON.stringify) + ']' + (ret ? ')' : ''); + ret = (ret ? ret + '.concat(' : '') + '[' + array.slice(index, index + chunkSize).map(JSON.stringify) + ']' + (ret ? ')\n' : ''); index += chunkSize; } return ret; } - if (typeof slab == 'string' && evaled && evaled.length > chunkSize) { - slab = chunkify(evaled); + if (typeof slab == 'object' && slab.length > chunkSize) { + slab = chunkify(slab); + } + if (typeof types == 'object') { + while (types.length < slab.length) types.push(0); } if (typeof types != 'string' && types.length > chunkSize) { types = chunkify(types); } else { types = JSON.stringify(types); } + if (typeof slab == 'object') slab = '[' + slab.join(',') + ']'; return 'allocate(' + slab + ', ' + types + (allocator ? ', ' + allocator : '') + (allocator == 'ALLOC_NONE' ? ', ' + ptr : '') + ')'; } @@ -1470,7 +1672,11 @@ function makeGetSlabs(ptr, type, allowMultiple, unsigned) { case 'i1': case 'i8': return [unsigned ? 'HEAPU8' : 'HEAP8']; break; case 'i16': return [unsigned ? 'HEAPU16' : 'HEAP16']; break; case 'i32': case 'i64': return [unsigned ? 'HEAPU32' : 'HEAP32']; break; - case 'float': case 'double': return ['HEAPF32']; break; + case 'double': { + if (TARGET_LE32) return ['HEAPF64']; // in le32, we do have the ability to assume 64-bit alignment + // otherwise, fall through to float + } + case 'float': return ['HEAPF32']; default: { throw 'what, exactly, can we do for unknown types in TA2?! ' + new Error().stack; } @@ -1479,45 +1685,48 @@ function makeGetSlabs(ptr, type, allowMultiple, unsigned) { return []; } -function finalizeLLVMFunctionCall(item, noIndexizeFunctions) { - if (item.intertype == 'getelementptr') { // TODO finalizeLLVMParameter on the ident and the indexes? - return makePointer(makeGetSlabs(item.ident, item.type)[0], getGetElementPtrIndexes(item), null, item.type); - } - if (item.intertype == 'bitcast') { - // Warn about some types of casts, then fall through to the handling code below - var oldType = item.params[0].type; - var newType = item.type; - if (isPossiblyFunctionType(oldType) && isPossiblyFunctionType(newType)) { - var oldCount = countNormalArgs(oldType); - var newCount = countNormalArgs(newType); - if (oldCount != newCount && oldCount && newCount) { - warnOnce('Casting a function pointer type to another with a different number of arguments. See more info in the compiler source'); - if (VERBOSE) { - warnOnce('Casting a function pointer type to another with a different number of arguments: ' + oldType + ' vs. ' + newType + ', on ' + item.params[0].ident); +function checkBitc |