diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/analyzer.js | 2766 | ||||
-rw-r--r-- | src/compiler.js | 15 | ||||
-rw-r--r-- | src/compiler_phase.html | 33 | ||||
-rw-r--r-- | src/framework.js | 257 | ||||
-rw-r--r-- | src/intertyper.js | 1850 | ||||
-rw-r--r-- | src/jsifier.js | 1222 | ||||
-rw-r--r-- | src/library.js | 158 | ||||
-rw-r--r-- | src/library_fs.js | 72 | ||||
-rw-r--r-- | src/library_gl.js | 168 | ||||
-rw-r--r-- | src/library_idbfs.js | 216 | ||||
-rw-r--r-- | src/library_memfs.js | 26 | ||||
-rw-r--r-- | src/library_nodefs.js | 234 | ||||
-rw-r--r-- | src/library_sdl.js | 280 | ||||
-rw-r--r-- | src/library_sockfs.js | 12 | ||||
-rw-r--r-- | src/modules.js | 6 | ||||
-rw-r--r-- | src/parseTools.js | 211 | ||||
-rw-r--r-- | src/postamble.js | 3 | ||||
-rw-r--r-- | src/preamble.js | 25 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 2 | ||||
-rw-r--r-- | src/relooper/emscripten/glue.js | 11 | ||||
-rw-r--r-- | src/runtime.js | 41 | ||||
-rw-r--r-- | src/settings.js | 3 | ||||
-rw-r--r-- | src/utility.js | 11 |
23 files changed, 4181 insertions, 3441 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index b20dedff..3fb20253 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -27,82 +27,72 @@ var SHADOW_FLIP = { i64: 'double', double: 'i64' }; //, i32: 'float', float: 'i3 function analyzer(data, sidePass) { var mainPass = !sidePass; - // Substrate - var substrate = new Substrate('Analyzer'); - - // Sorter - substrate.addActor('Sorter', { - processItem: function(item) { - item.items.sort(function (a, b) { return a.lineNum - b.lineNum }); - this.forwardItem(item, 'Gatherer'); + var item = { items: data }; + var data = item; + + var newTypes = {}; + + // Gather + // Single-liners + ['globalVariable', 'functionStub', 'unparsedFunction', 'unparsedGlobals', 'unparsedTypes', 'alias'].forEach(function(intertype) { + var temp = splitter(item.items, function(item) { return item.intertype == intertype }); + item.items = temp.leftIn; + item[intertype + 's'] = temp.splitOut; + }); + var temp = splitter(item.items, function(item) { return item.intertype == 'type' }); + item.items = temp.leftIn; + temp.splitOut.forEach(function(type) { + //dprint('types', 'adding defined type: ' + type.name_); + Types.types[type.name_] = type; + newTypes[type.name_] = 1; + if (QUANTUM_SIZE === 1) { + Types.fatTypes[type.name_] = copy(type); } }); - // Gatherer - substrate.addActor('Gatherer', { - processItem: function(item) { - // Single-liners - ['globalVariable', 'functionStub', 'unparsedFunction', 'unparsedGlobals', 'unparsedTypes', 'alias'].forEach(function(intertype) { - var temp = splitter(item.items, function(item) { return item.intertype == intertype }); - item.items = temp.leftIn; - item[intertype + 's'] = temp.splitOut; - }); - var temp = splitter(item.items, function(item) { return item.intertype == 'type' }); - item.items = temp.leftIn; - temp.splitOut.forEach(function(type) { - //dprint('types', 'adding defined type: ' + type.name_); - Types.types[type.name_] = type; - if (QUANTUM_SIZE === 1) { - Types.fatTypes[type.name_] = copy(type); - } - }); - - // Functions & labels - item.functions = []; - var currLabelFinished; // Sometimes LLVM puts a branch in the middle of a label. We need to ignore all lines after that. - item.items.sort(function(a, b) { return a.lineNum - b.lineNum }); - for (var i = 0; i < item.items.length; i++) { - var subItem = item.items[i]; - assert(subItem.lineNum); - if (subItem.intertype == 'function') { - item.functions.push(subItem); - subItem.endLineNum = null; - subItem.lines = []; // We will fill in the function lines after the legalizer, since it can modify them - subItem.labels = []; - subItem.forceEmulated = false; - - // no explicit 'entry' label in clang on LLVM 2.8 - most of the time, but not all the time! - so we add one if necessary - if (item.items[i+1].intertype !== 'label') { - item.items.splice(i+1, 0, { - intertype: 'label', - ident: ENTRY_IDENT, - lineNum: subItem.lineNum + '.5' - }); - } - } else if (subItem.intertype == 'functionEnd') { - item.functions.slice(-1)[0].endLineNum = subItem.lineNum; - } else if (subItem.intertype == 'label') { - item.functions.slice(-1)[0].labels.push(subItem); - subItem.lines = []; - currLabelFinished = false; - } else if (item.functions.length > 0 && item.functions.slice(-1)[0].endLineNum === null) { - // Internal line - if (!currLabelFinished) { - item.functions.slice(-1)[0].labels.slice(-1)[0].lines.push(subItem); // If this line fails, perhaps missing a label? - if (subItem.intertype in LABEL_ENDERS) { - currLabelFinished = true; - } - } else { - print('// WARNING: content after a branch in a label, line: ' + subItem.lineNum); - } - } else { - throw 'ERROR: what is this? ' + dump(subItem); + // Functions & labels + item.functions = []; + var currLabelFinished = false; // Sometimes LLVM puts a branch in the middle of a label. We need to ignore all lines after that. + item.items.sort(function(a, b) { return a.lineNum - b.lineNum }); + for (var i = 0; i < item.items.length; i++) { + var subItem = item.items[i]; + assert(subItem.lineNum); + if (subItem.intertype == 'function') { + item.functions.push(subItem); + subItem.endLineNum = null; + subItem.lines = []; // We will fill in the function lines after the legalizer, since it can modify them + subItem.labels = []; + subItem.forceEmulated = false; + + // no explicit 'entry' label in clang on LLVM 2.8 - most of the time, but not all the time! - so we add one if necessary + if (item.items[i+1].intertype !== 'label') { + item.items.splice(i+1, 0, { + intertype: 'label', + ident: ENTRY_IDENT, + lineNum: subItem.lineNum + '.5' + }); + } + } else if (subItem.intertype == 'functionEnd') { + item.functions.slice(-1)[0].endLineNum = subItem.lineNum; + } else if (subItem.intertype == 'label') { + item.functions.slice(-1)[0].labels.push(subItem); + subItem.lines = []; + currLabelFinished = false; + } else if (item.functions.length > 0 && item.functions.slice(-1)[0].endLineNum === null) { + // Internal line + if (!currLabelFinished) { + item.functions.slice(-1)[0].labels.slice(-1)[0].lines.push(subItem); // If this line fails, perhaps missing a label? + if (subItem.intertype in LABEL_ENDERS) { + currLabelFinished = true; } + } else { + print('// WARNING: content after a branch in a label, line: ' + subItem.lineNum); } - delete item.items; - this.forwardItem(item, 'CastAway'); + } else { + throw 'ERROR: what is this? ' + dump(subItem); } - }); + } + delete item.items; // CastAway - try to remove bitcasts of double<-->i64, which LLVM sometimes generates unnecessarily // (load a double, convert to i64, use as i64). @@ -113,75 +103,72 @@ function analyzer(data, sidePass) { // Note that aside from being an optimization, this is needed for correctness in some cases: If code // assumes it can bitcast a double to an i64 and back and forth without loss, that may be violated // due to NaN canonicalization. - substrate.addActor('CastAway', { - processItem: function(item) { - this.forwardItem(item, 'Legalizer'); - if (USE_TYPED_ARRAYS != 2) return; + function castAway() { + if (USE_TYPED_ARRAYS != 2) return; - item.functions.forEach(function(func) { - var has = false; - func.labels.forEach(function(label) { - var lines = label.lines; - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP) { - has = true; - } + item.functions.forEach(function(func) { + var has = false; + func.labels.forEach(function(label) { + var lines = label.lines; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP) { + has = true; } - }); - if (!has) return; - // there are integer<->floating-point bitcasts, create shadows for everything - var shadowed = {}; - func.labels.forEach(function(label) { - var lines = label.lines; - var i = 0; - while (i < lines.length) { - var lines = label.lines; - var line = lines[i]; - if (line.intertype == 'load' && line.type in SHADOW_FLIP) { - if (line.pointer.intertype != 'value') { i++; continue } // TODO - shadowed[line.assignTo] = 1; - var shadow = line.assignTo + '$$SHADOW'; - var flip = SHADOW_FLIP[line.type]; - lines.splice(i + 1, 0, { // if necessary this element will be legalized in the next phase - tokens: null, - indent: 2, - lineNum: line.lineNum + 0.5, - assignTo: shadow, - intertype: 'load', - pointerType: flip + '*', - type: flip, - valueType: flip, - pointer: { - intertype: 'value', - ident: line.pointer.ident, - type: flip + '*' - }, - align: line.align, - ident: line.ident - }); - // note: no need to update func.lines, it is generated in a later pass - i++; - } + } + }); + if (!has) return; + // there are integer<->floating-point bitcasts, create shadows for everything + var shadowed = {}; + func.labels.forEach(function(label) { + var lines = label.lines; + var i = 0; + while (i < lines.length) { + var lines = label.lines; + var line = lines[i]; + if (line.intertype == 'load' && line.type in SHADOW_FLIP) { + if (line.pointer.intertype != 'value') { i++; continue } // TODO + shadowed[line.assignTo] = 1; + var shadow = line.assignTo + '$$SHADOW'; + var flip = SHADOW_FLIP[line.type]; + lines.splice(i + 1, 0, { // if necessary this element will be legalized in the next phase + tokens: null, + indent: 2, + lineNum: line.lineNum + 0.5, + assignTo: shadow, + intertype: 'load', + pointerType: flip + '*', + type: flip, + valueType: flip, + pointer: { + intertype: 'value', + ident: line.pointer.ident, + type: flip + '*' + }, + align: line.align, + ident: line.ident + }); + // note: no need to update func.lines, it is generated in a later pass i++; } - }); - // use shadows where possible - func.labels.forEach(function(label) { - var lines = label.lines; - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP && line.ident in shadowed) { - var shadow = line.ident + '$$SHADOW'; - line.params[0].ident = shadow; - line.params[0].type = line.type; - line.type2 = line.type; - } + i++; + } + }); + // use shadows where possible + func.labels.forEach(function(label) { + var lines = label.lines; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (line.intertype == 'bitcast' && line.type in SHADOW_FLIP && line.ident in shadowed) { + var shadow = line.ident + '$$SHADOW'; + line.params[0].ident = shadow; + line.params[0].type = line.type; + line.type2 = line.type; } - }); + } }); - } - }); + }); + } // Legalize LLVM unrealistic types into realistic types. // @@ -196,727 +183,724 @@ 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. - substrate.addActor('Legalizer', { - processItem: function(data) { - // Legalization - if (USE_TYPED_ARRAYS == 2) { - function getLegalVars(base, bits, allowLegal) { - bits = bits || 32; // things like pointers are all i32, but show up as 0 bits from getBits - if (allowLegal && bits <= 32) return [{ ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits }]; - if (isNumber(base)) return getLegalLiterals(base, bits); - if (base[0] == '{') { - warnOnce('seeing source of illegal data ' + base + ', likely an inline struct - assuming zeroinit'); - return getLegalLiterals('0', bits); - } - var ret = new Array(Math.ceil(bits/32)); - var i = 0; - if (base == 'zeroinitializer' || base == 'undef') base = 0; - while (bits > 0) { - ret[i] = { ident: base ? base + '$' + i : '0', bits: Math.min(32, bits) }; - bits -= 32; - i++; - } - return ret; - } - function getLegalLiterals(text, bits) { - var parsed = parseArbitraryInt(text, bits); - var ret = new Array(Math.ceil(bits/32)); - var i = 0; - while (bits > 0) { - ret[i] = { ident: (parsed[i]|0).toString(), bits: Math.min(32, bits) }; // resign all values - bits -= 32; - i++; - } - return ret; + function legalizer() { + // Legalization + if (USE_TYPED_ARRAYS == 2) { + function getLegalVars(base, bits, allowLegal) { + bits = bits || 32; // things like pointers are all i32, but show up as 0 bits from getBits + if (allowLegal && bits <= 32) return [{ ident: base + ('i' + bits in Runtime.INT_TYPES ? '' : '$0'), bits: bits }]; + if (isNumber(base)) return getLegalLiterals(base, bits); + if (base[0] == '{') { + warnOnce('seeing source of illegal data ' + base + ', likely an inline struct - assuming zeroinit'); + return getLegalLiterals('0', bits); } - function getLegalStructuralParts(value) { - return value.params.slice(0); + var ret = new Array(Math.ceil(bits/32)); + var i = 0; + if (base == 'zeroinitializer' || base == 'undef') base = 0; + while (bits > 0) { + ret[i] = { ident: base ? base + '$' + i : '0', bits: Math.min(32, bits) }; + bits -= 32; + i++; } - function getLegalParams(params, bits) { - return params.map(function(param) { - var value = param.value || param; - if (isNumber(value.ident)) { - return getLegalLiterals(value.ident, bits); - } else if (value.intertype == 'structvalue') { - return getLegalStructuralParts(value).map(function(part) { - return { ident: part.ident, bits: part.type.substr(1) }; - }); - } else { - return getLegalVars(value.ident, bits); - } - }); + return ret; + } + function getLegalLiterals(text, bits) { + var parsed = parseArbitraryInt(text, bits); + var ret = new Array(Math.ceil(bits/32)); + var i = 0; + while (bits > 0) { + ret[i] = { ident: (parsed[i]|0).toString(), bits: Math.min(32, bits) }; // resign all values + bits -= 32; + i++; } - // 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); - assert(k == 0 || toAdd[k].lineNum > toAdd[k-1].lineNum); + return ret; + } + function getLegalStructuralParts(value) { + return value.params.slice(0); + } + function getLegalParams(params, bits) { + return params.map(function(param) { + var value = param.value || param; + if (isNumber(value.ident)) { + return getLegalLiterals(value.ident, bits); + } else if (value.intertype == 'structvalue') { + return getLegalStructuralParts(value).map(function(part) { + return { ident: part.ident, bits: part.type.substr(1) }; + }); + } else { + return getLegalVars(value.ident, bits); } + }); + } + // 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); + assert(k == 0 || toAdd[k].lineNum > toAdd[k-1].lineNum); } - function removeAndAdd(lines, i, toAdd) { - var item = lines[i]; - interpLines(lines, i, toAdd); - Array.prototype.splice.apply(lines, [i, 1].concat(toAdd)); - if (i > 0) assert(lines[i].lineNum > lines[i-1].lineNum); - if (i + toAdd.length < lines.length) assert(lines[i + toAdd.length - 1].lineNum < lines[i + toAdd.length].lineNum); - return toAdd.length; - } - function legalizeFunctionParameters(params) { - 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; - } else if (param.intertype == 'structvalue') { - // 'flatten' out the struct into scalars - var toAdd = param.params; - toAdd.forEach(function(param) { - param.byval = 0; - }); - Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); - continue; // do not increment i; proceed to process the new params - } - i++; + } + function removeAndAdd(lines, i, toAdd) { + var item = lines[i]; + interpLines(lines, i, toAdd); + Array.prototype.splice.apply(lines, [i, 1].concat(toAdd)); + if (i > 0) assert(lines[i].lineNum > lines[i-1].lineNum); + if (i + toAdd.length < lines.length) assert(lines[i + toAdd.length - 1].lineNum < lines[i + toAdd.length].lineNum); + return toAdd.length; + } + function legalizeFunctionParameters(params) { + 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; + } else if (param.intertype == 'structvalue') { + // 'flatten' out the struct into scalars + var toAdd = param.params; + toAdd.forEach(function(param) { + param.byval = 0; + }); + Array.prototype.splice.apply(params, [i, 1].concat(toAdd)); + continue; // do not increment i; proceed to process the new params } + i++; } - function fixUnfolded(item) { - // Unfolded items may need some correction to work properly in the global scope - if (item.intertype in MATHOPS) { - item.op = item.intertype; - item.intertype = 'mathop'; - } + } + function fixUnfolded(item) { + // Unfolded items may need some correction to work properly in the global scope + if (item.intertype in MATHOPS) { + item.op = item.intertype; + item.intertype = 'mathop'; } - data.functions.forEach(function(func) { - // Legalize function params - legalizeFunctionParameters(func.params); - // Legalize lines in labels - var tempId = 0; - func.labels.forEach(function(label) { - if (dcheck('legalizer')) dprint('zz legalizing: \n' + dump(label.lines)); - var i = 0, bits; - while (i < label.lines.length) { - var item = label.lines[i]; - 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'; - } - } else if (item.intertype == 'inttoptr') { - var input = item.params[0]; - if (input.type == 'i64') input.type = 'i32'; // inttoptr can only care about 32 bits anyhow since pointers are 32-bit - } - if (isIllegalType(item.valueType) || isIllegalType(item.type)) { - isIllegal = true; - } else if ((item.intertype == 'load' || item.intertype == 'store') && isStructType(item.valueType)) { - isIllegal = true; // storing an entire structure is illegal - } else if (item.intertype == 'mathop' && item.op == 'trunc' && isIllegalType(item.params[1].ident)) { // trunc stores target value in second ident - isIllegal = true; + } + data.functions.forEach(function(func) { + // Legalize function params + legalizeFunctionParameters(func.params); + // Legalize lines in labels + var tempId = 0; + func.labels.forEach(function(label) { + if (dcheck('legalizer')) dprint('zz legalizing: \n' + dump(label.lines)); + var i = 0, bits; + while (i < label.lines.length) { + var item = label.lines[i]; + 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 (!isIllegal) { - //if (dcheck('legalizer')) dprint('no need to legalize \n' + dump(item)); - i++; - continue; + } else if (item.intertype == 'inttoptr') { + var input = item.params[0]; + if (input.type == 'i64') input.type = 'i32'; // inttoptr can only care about 32 bits anyhow since pointers are 32-bit } - // 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)))) { - if (item.intertype == 'phi') { - assert(subItem.intertype == 'value' || subItem.intertype == 'structvalue' || subItem.intertype in PARSABLE_LLVM_FUNCTIONS, 'We can only unfold some expressions in phis'); - // we must handle this in the phi itself, if we unfold normally it will not be pushed back with the phi - } else { + if (isIllegalType(item.valueType) || isIllegalType(item.type)) { + isIllegal = true; + } else if ((item.intertype == 'load' || item.intertype == 'store') && isStructType(item.valueType)) { + isIllegal = true; // storing an entire structure is illegal + } else if (item.intertype == 'mathop' && item.op == 'trunc' && isIllegalType(item.params[1].ident)) { // trunc stores target value in second ident + isIllegal = true; + } + }); + if (!isIllegal) { + //if (dcheck('legalizer')) dprint('no need to legalize \n' + dump(item)); + 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)))) { + if (item.intertype == 'phi') { + assert(subItem.intertype == 'value' || subItem.intertype == 'structvalue' || subItem.intertype in PARSABLE_LLVM_FUNCTIONS, 'We can only unfold some expressions in phis'); + // we must handle this in the phi itself, if we unfold normally it will not be pushed back with the phi + } else { + var tempIdent = '$$etemp$' + (tempId++); + subItem.assignTo = tempIdent; + unfolded.unshift(subItem); + fixUnfolded(subItem); + return { intertype: 'value', ident: tempIdent, type: subItem.type }; + } + } else if (subItem.intertype == 'switch' && isIllegalType(subItem.type)) { + subItem.switchLabels.forEach(function(switchLabel) { + if (switchLabel.value[0] != '$') { var tempIdent = '$$etemp$' + (tempId++); - subItem.assignTo = tempIdent; - unfolded.unshift(subItem); - fixUnfolded(subItem); - return { intertype: 'value', ident: tempIdent, type: subItem.type }; + unfolded.unshift({ + assignTo: tempIdent, + intertype: 'value', + ident: switchLabel.value, + type: subItem.type + }); + switchLabel.value = tempIdent; } - } else if (subItem.intertype == 'switch' && isIllegalType(subItem.type)) { - subItem.switchLabels.forEach(function(switchLabel) { - if (switchLabel.value[0] != '$') { - var tempIdent = '$$etemp$' + (tempId++); - unfolded.unshift({ - assignTo: tempIdent, - intertype: 'value', - ident: switchLabel.value, - type: subItem.type - }); - switchLabel.value = tempIdent; - } + }); + } + }); + 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); + var finalizer = null; + switch (item.intertype) { + case 'store': { + var toAdd = []; + bits = getBits(item.valueType); + var elements = getLegalParams([item.value], bits)[0]; + var j = 0; + elements.forEach(function(element) { + var tempVar = '$st$' + (tempId++) + '$' + j; + toAdd.push({ + intertype: 'getelementptr', + assignTo: tempVar, + ident: item.pointer.ident, + type: '[0 x i32]*', + params: [ + { intertype: 'value', ident: item.pointer.ident, type: '[0 x i32]*' }, // technically a bitcase is needed in llvm, but not for us + { intertype: 'value', ident: '0', type: 'i32' }, + { intertype: 'value', ident: j.toString(), type: 'i32' } + ], }); - } - }); - 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 + var actualSizeType = 'i' + element.bits; // The last one may be smaller than 32 bits + toAdd.push({ + intertype: 'store', + valueType: actualSizeType, + value: { intertype: 'value', ident: element.ident, type: actualSizeType }, + pointer: { intertype: 'value', ident: tempVar, type: actualSizeType + '*' }, + ident: tempVar, + pointerType: actualSizeType + '*', + align: item.align, + }); + j++; + }); + Types.needAnalysis['[0 x i32]'] = 0; + i += removeAndAdd(label.lines, i, toAdd); + continue; } - // This is an illegal-containing line, and it is unfolded. Legalize it now - dprint('legalizer', 'Legalizing ' + item.intertype + ' at line ' + item.lineNum); - var finalizer = null; - switch (item.intertype) { - case 'store': { - var toAdd = []; - bits = getBits(item.valueType); - var elements = getLegalParams([item.value], bits)[0]; - var j = 0; - elements.forEach(function(element) { - var tempVar = '$st$' + (tempId++) + '$' + j; - toAdd.push({ - intertype: 'getelementptr', - assignTo: tempVar, - ident: item.pointer.ident, - type: '[0 x i32]*', - params: [ - { intertype: 'value', ident: item.pointer.ident, type: '[0 x i32]*' }, // technically a bitcase 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 + // call, return: Return the first 32 bits, the rest are in temp + case 'call': { + var toAdd = [value]; + // legalize parameters + legalizeFunctionParameters(value.params); + // legalize return value, if any + var returnType = getReturnType(item.type); + if (value.assignTo && isIllegalType(returnType)) { + bits = getBits(returnType); + var elements = getLegalVars(item.assignTo, bits); + // legalize return value + value.assignTo = elements[0].ident; + for (var j = 1; j < elements.length; j++) { + var element = elements[j]; toAdd.push({ - intertype: 'store', - valueType: actualSizeType, - value: { intertype: 'value', ident: element.ident, type: actualSizeType }, - pointer: { intertype: 'value', ident: tempVar, type: actualSizeType + '*' }, - ident: tempVar, - pointerType: actualSizeType + '*', - align: item.align, + intertype: 'value', + assignTo: element.ident, + type: element.bits, + ident: 'tempRet' + (j - 1) }); - j++; - }); - Types.needAnalysis['[0 x i32]'] = 0; - i += removeAndAdd(label.lines, i, toAdd); - continue; - } - // call, return: Return the first 32 bits, the rest are in temp - case 'call': { - var toAdd = [value]; - // legalize parameters - legalizeFunctionParameters(value.params); - // legalize return value, if any - var returnType = getReturnType(item.type); - if (value.assignTo && isIllegalType(returnType)) { - bits = getBits(returnType); - var elements = getLegalVars(item.assignTo, bits); - // legalize return value - value.assignTo = elements[0].ident; - for (var j = 1; j < elements.length; j++) { - var element = elements[j]; - toAdd.push({ - intertype: 'value', - assignTo: element.ident, - type: element.bits, - ident: 'tempRet' + (j - 1) - }); - assert(j<10); // TODO: dynamically create more than 10 tempRet-s - } + assert(j<10); // TODO: dynamically create more than 10 tempRet-s } - i += removeAndAdd(label.lines, i, toAdd); - continue; } - case 'landingpad': { - // not much to legalize - i++; - continue; - } - case 'return': { - bits = getBits(item.type); - var elements = getLegalVars(item.value.ident, bits); - item.value.ident = '('; - for (var j = 1; j < elements.length; j++) { - item.value.ident += 'tempRet' + (j-1) + '=' + elements[j].ident + ','; - } - item.value.ident += elements[0].ident + ')'; - i++; - continue; + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'landingpad': { + // not much to legalize + i++; + continue; + } + case 'return': { + bits = getBits(item.type); + var elements = getLegalVars(item.value.ident, bits); + item.value.ident = '('; + for (var j = 1; j < elements.length; j++) { + item.value.ident += 'tempRet' + (j-1) + '=' + elements[j].ident + ','; } - case 'invoke': { - legalizeFunctionParameters(value.params); - // We can't add lines after this, since invoke already modifies control flow. So we handle the return in invoke - i++; - continue; + item.value.ident += elements[0].ident + ')'; + i++; + continue; + } + case 'invoke': { + legalizeFunctionParameters(value.params); + // We can't add lines after this, since invoke already modifies control flow. So we handle the return in invoke + 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 'structvalue': { + bits = getBits(value.type); + var elements = getLegalVars(item.assignTo, bits); + var toAdd = []; + for (var j = 0; j < item.params.length; j++) { + toAdd[j] = { + intertype: 'value', + assignTo: elements[j].ident, + type: 'i32', + ident: item.params[j].ident + }; } - 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 = '$ld$' + (tempId++) + '$' + 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' } + ] }); - i += removeAndAdd(label.lines, i, toAdd); - continue; - } - case 'structvalue': { - bits = getBits(value.type); - var elements = getLegalVars(item.assignTo, bits); - var toAdd = []; - for (var j = 0; j < item.params.length; j++) { - toAdd[j] = { - intertype: 'value', - assignTo: elements[j].ident, - type: 'i32', - ident: item.params[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 = '$ld$' + (tempId++) + '$' + 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 newItem = { - intertype: 'load', + var newItem = { + intertype: 'load', + assignTo: element.ident, + pointerType: 'i32*', + valueType: 'i32', + type: 'i32', + pointer: { intertype: 'value', ident: tempVar, type: 'i32*' }, + ident: tempVar, + align: value.align + }; + var newItem2 = null; + // The last one may be smaller than 32 bits + if (element.bits < 32) { + newItem.assignTo += '$preadd$'; + newItem2 = { + intertype: 'mathop', + op: 'and', assignTo: element.ident, - pointerType: 'i32*', - valueType: 'i32', type: 'i32', - pointer: { intertype: 'value', ident: tempVar, type: 'i32*' }, - ident: tempVar, - align: value.align - }; - var newItem2 = null; - // The last one may be smaller than 32 bits - if (element.bits < 32) { - newItem.assignTo += '$preadd$'; - newItem2 = { - intertype: 'mathop', - op: 'and', - assignTo: element.ident, + params: [{ + intertype: 'value', type: 'i32', - params: [{ - intertype: 'value', - type: 'i32', - ident: newItem.assignTo - }, { - intertype: 'value', - type: 'i32', - ident: (0xffffffff >>> (32 - element.bits)).toString() - }], - }; - } - toAdd.push(newItem); - if (newItem2) toAdd.push(newItem2); - 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; - var values = getLegalParams(value.params, bits); - elements.forEach(function(element) { - var k = 0; - 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: values[k++][j].ident, - type: 'i' + element.bits, - } - }; - }) - }); - j++; - }); - i += removeAndAdd(label.lines, i, toAdd); - continue; - } - case 'switch': { - i++; - continue; // special case, handled in makeComparison - } - case 'va_arg': { - assert(value.type == 'i64'); - assert(value.value.type == 'i32*', value.value.type); - i += removeAndAdd(label.lines, i, range(2).map(function(x) { - return { - intertype: 'va_arg', - assignTo: value.assignTo + '$' + x, - type: 'i32', - value: { + ident: newItem.assignTo + }, { intertype: 'value', - ident: value.value.ident, // We read twice from the same i32* var, incrementing // + '$' + x, - type: 'i32*' - } + type: 'i32', + ident: (0xffffffff >>> (32 - element.bits)).toString() + }], }; - })); - continue; - } - case 'extractvalue': { // XXX we assume 32-bit alignment in extractvalue/insertvalue, - // but in theory they can run on packed structs too (see use getStructuralTypePartBits) - // potentially legalize the actual extracted value too if it is >32 bits, not just the extraction in general - var index = item.indexes[0][0].text; - var parts = getStructureTypeParts(item.type); - var indexedType = parts[index]; - var targetBits = getBits(indexedType); - var sourceBits = getBits(item.type); - var elements = getLegalVars(item.assignTo, targetBits, true); // possibly illegal - var sourceElements = getLegalVars(item.ident, sourceBits); // definitely illegal - var toAdd = []; - var sourceIndex = 0; - for (var partIndex = 0; partIndex < parts.length; partIndex++) { - if (partIndex == index) { - for (var j = 0; j < elements.length; j++) { - toAdd.push({ - intertype: 'value', - assignTo: elements[j].ident, - type: 'i' + elements[j].bits, - ident: sourceElements[sourceIndex+j].ident - }); - } - break; - } - sourceIndex += getStructuralTypePartBits(parts[partIndex])/32; } - i += removeAndAdd(label.lines, i, toAdd); - continue; - } - case 'insertvalue': { - var index = item.indexes[0][0].text; // the modified index - var parts = getStructureTypeParts(item.type); - var indexedType = parts[index]; - var indexBits = getBits(indexedType); - var bits = getBits(item.type); // source and target - bits = getBits(value.type); - var toAdd = []; - var elements = getLegalVars(item.assignTo, bits); - var sourceElements = getLegalVars(item.ident, bits); - var indexElements = getLegalVars(item.value.ident, indexBits, true); // possibly legal - var sourceIndex = 0; - for (var partIndex = 0; partIndex < parts.length; partIndex++) { - var currNum = getStructuralTypePartBits(parts[partIndex])/32; - for (var j = 0; j < currNum; j++) { + toAdd.push(newItem); + if (newItem2) toAdd.push(newItem2); + 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; + var values = getLegalParams(value.params, bits); + elements.forEach(function(element) { + var k = 0; + 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: values[k++][j].ident, + type: 'i' + element.bits, + } + }; + }) + }); + j++; + }); + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'switch': { + i++; + continue; // special case, handled in makeComparison + } + case 'va_arg': { + assert(value.type == 'i64'); + assert(value.value.type == 'i32*', value.value.type); + i += removeAndAdd(label.lines, i, range(2).map(function(x) { + return { + intertype: 'va_arg', + assignTo: value.assignTo + '$' + x, + type: 'i32', + value: { + intertype: 'value', + ident: value.value.ident, // We read twice from the same i32* var, incrementing // + '$' + x, + type: 'i32*' + } + }; + })); + continue; + } + case 'extractvalue': { // XXX we assume 32-bit alignment in extractvalue/insertvalue, + // but in theory they can run on packed structs too (see use getStructuralTypePartBits) + // potentially legalize the actual extracted value too if it is >32 bits, not just the extraction in general + var index = item.indexes[0][0].text; + var parts = getStructureTypeParts(item.type); + var indexedType = parts[index]; + var targetBits = getBits(indexedType); + var sourceBits = getBits(item.type); + var elements = getLegalVars(item.assignTo, targetBits, true); // possibly illegal + var sourceElements = getLegalVars(item.ident, sourceBits); // definitely illegal + var toAdd = []; + var sourceIndex = 0; + for (var partIndex = 0; partIndex < parts.length; partIndex++) { + if (partIndex == index) { + for (var j = 0; j < elements.length; j++) { toAdd.push({ intertype: 'value', - assignTo: elements[sourceIndex+j].ident, - type: 'i' + elements[sourceIndex+j].bits, - ident: partIndex == index ? indexElements[j].ident : sourceElements[sourceIndex+j].ident + assignTo: elements[j].ident, + type: 'i' + elements[j].bits, + ident: sourceElements[sourceIndex+j].ident }); } - sourceIndex += currNum; + break; } - i += removeAndAdd(label.lines, i, toAdd); - continue; + sourceIndex += getStructuralTypePartBits(parts[partIndex])/32; } - 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 + i += removeAndAdd(label.lines, i, toAdd); + continue; + } + case 'insertvalue': { + var index = item.indexes[0][0].text; // the modified index + var parts = getStructureTypeParts(item.type); + var indexedType = parts[index]; + var indexBits = getBits(indexedType); + var bits = getBits(item.type); // source and target + bits = getBits(value.type); + var toAdd = []; + var elements = getLegalVars(item.assignTo, bits); + var sourceElements = getLegalVars(item.ident, bits); + var indexElements = getLegalVars(item.value.ident, indexBits, true); // possibly legal + var sourceIndex = 0; + for (var partIndex = 0; partIndex < parts.length; partIndex++) { + var currNum = getStructuralTypePartBits(parts[partIndex])/32; + for (var j = 0; j < currNum; j++) { + toAdd.push({ + intertype: 'value', + assignTo: elements[sourceIndex+j].ident, + type: 'i' + elements[sourceIndex+j].bits, + ident: partIndex == index ? indexElements[j].ident : sourceElements[sourceIndex+j].ident + }); } - // fall through + sourceIndex += currNum; } - case 'inttoptr': case 'ptrtoint': case 'zext': case 'sext': case 'trunc': case 'ashr': case 'lshr': case 'shl': case 'or': case 'and': case 'xor': { - value = { - op: item.intertype, - variant: item.variant, - type: item.type, - params: item.params - }; - // fall through + 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 } - case 'mathop': { - var toAdd = []; - var sourceBits = getBits(value.params[0].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.params[1].ident); - break; - } - case 'shl': { - shifts = -parseInt(value.params[1].ident); - break; - } - case 'sext': { - signed = true; - // fall through - } - case 'trunc': case 'zext': case 'ptrtoint': { - targetBits = getBits(value.params[1] ? value.params[1].ident : value.type); - break; - } - case 'inttoptr': { - targetBits = 32; - break; - } - case 'bitcast': { - if (!sourceBits) { - // we can be asked to bitcast doubles or such to integers, handle that as best we can (if it's a double that - // was an x86_fp80, this code will likely break when called) - sourceBits = targetBits = Runtime.getNativeTypeSize(value.params[0].type); - warn('legalizing non-integer bitcast on ll #' + item.lineNum); - } - break; - } - case 'select': { - sourceBits = targetBits = getBits(value.params[1].type); - var params = getLegalParams(value.params.slice(1), sourceBits); - processor = function(result, j) { - return { - intertype: 'mathop', - op: 'select', - type: 'i' + params[0][j].bits, - params: [ - value.params[0], - { intertype: 'value', ident: params[0][j].ident, type: 'i' + params[0][j].bits }, - { intertype: 'value', ident: params[1][j].ident, type: 'i' + params[1][j].bits } - ] - }; - }; - break; - } - case 'or': case 'and': case 'xor': case 'icmp': { - var otherElements = getLegalVars(value.params[1].ident, sourceBits); - processor = function(result, j) { - return { - intertype: 'mathop', - op: value.op, - variant: value.variant, - type: 'i' + otherElements[j].bits, - params: [ - result, - { intertype: 'value', ident: otherElements[j].ident, type: 'i' + otherElements[j].bits } - ] - }; - }; - if (value.op == 'icmp') { - if (sourceBits == 64) { // handle the i64 case in processMathOp, where we handle full i64 math - i++; - continue; - } - finalizer = function() { - var ident = ''; - for (var i = 0; i < targetElements.length; i++) { - if (i > 0) { - switch(value.variant) { - case 'eq': ident += '&'; break; - case 'ne': ident += '|'; break; - default: throw 'unhandleable illegal icmp: ' + value.variant; - } - } - ident += targetElements[i].ident; - } - return { - intertype: 'value', - ident: ident, - type: 'rawJS', - assignTo: item.assignTo - }; - } - } - break; - } - case 'add': case 'sub': case 'sdiv': case 'udiv': case 'mul': case 'urem': case 'srem': { - if (sourceBits < 32) { - // when we add illegal types like i24, we must work on the singleton chunks - item.assignTo += '$0'; - item.params[0].ident += '$0'; - item.params[1].ident += '$0'; - } - // fall through - } - case 'uitofp': case 'sitofp': case 'fptosi': case 'fptoui': { - // We cannot do these in parallel chunks of 32-bit operations. We will handle these in processMathop - i++; - continue; - } - default: throw 'Invalid mathop for legalization: ' + [value.op, item.lineNum, dump(item)]; + // fall through + } + case 'inttoptr': case 'ptrtoint': case 'zext': case 'sext': case 'trunc': case 'ashr': case 'lshr': case 'shl': case 'or': case 'and': case 'xor': { + value = { + op: item.intertype, + variant: item.variant, + type: item.type, + params: item.params + }; + // fall through + } + case 'mathop': { + var toAdd = []; + var sourceBits = getBits(value.params[0].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 } - // Do the legalization - var sourceElements = getLegalVars(value.params[0].ident, sourceBits, true); - if (!isNumber(shifts)) { - // We can't statically legalize this, do the operation at runtime TODO: optimize - assert(sourceBits == 64, 'TODO: handle nonconstant shifts on != 64 bits'); - assert(PRECISE_I64_MATH, 'Must have precise i64 math for non-constant 64-bit shifts'); - Types.preciseI64MathUsed = 1; - value.intertype = 'value'; - value.ident = 'var ' + value.assignTo + '$0 = ' + - asmCoercion('_bitshift64' + value.op[0].toUpperCase() + value.op.substr(1) + '(' + - asmCoercion(sourceElements[0].ident, 'i32') + ',' + - asmCoercion(sourceElements[1].ident, 'i32') + ',' + - asmCoercion(value.params[1].ident + '$0', 'i32') + ')', 'i32' - ) + ';' + - 'var ' + value.assignTo + '$1 = tempRet0;'; - value.assignTo = null; - i++; - continue; + case 'lshr': { + shifts = parseInt(value.params[1].ident); + break; } - 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 = '(' + makeSignOp(sourceElements[sourceElements.length-1].ident, 'i' + sourceElements[sourceElements.length-1].bits, 're', 1, 1) + ' < 0 ? -1 : 0)'; - var signedKeepAlive = { intertype: 'value', ident: sourceElements[sourceElements.length-1].ident, type: 'i32' }; + case 'shl': { + shifts = -parseInt(value.params[1].ident); + break; } - 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'), - params: [(signed && j + whole > sourceElements.length) ? signedKeepAlive : null], - type: 'i32', - }; - if (j == 0 && sourceBits < 32) { - // zext sign correction - result.ident = makeSignOp(result.ident, 'i' + sourceBits, isUnsignedOp(value.op) ? 'un' : 're', 1, 1); + case 'sext': { + signed = true; + // fall through + } + case 'trunc': case 'zext': case 'ptrtoint': { + targetBits = getBits(value.params[1] ? value.params[1].ident : value.type); + break; + } + case 'inttoptr': { + targetBits = 32; + break; + } + case 'bitcast': { + if (!sourceBits) { + // we can be asked to bitcast doubles or such to integers, handle that as best we can (if it's a double that + // was an x86_fp80, this code will likely break when called) + sourceBits = targetBits = Runtime.getNativeTypeSize(value.params[0].type); + warn('legalizing non-integer bitcast on ll #' + item.lineNum); } - 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'), - params: [(signed && j + sign + whole > sourceElements.length) ? signedKeepAlive : null], - type: 'i32', - }; - other = { + break; + } + case 'select': { + sourceBits = targetBits = getBits(value.params[1].type); + var params = getLegalParams(value.params.slice(1), sourceBits); + processor = function(result, j) { + return { intertype: 'mathop', - op: shiftOp, - type: 'i32', + op: 'select', + type: 'i' + params[0][j].bits, params: [ - other, - { intertype: 'value', ident: (32 - fraction).toString(), type: 'i32' } + value.params[0], + { intertype: 'value', ident: params[0][j].ident, type: 'i' + params[0][j].bits }, + { intertype: 'value', ident: params[1][j].ident, type: 'i' + params[1][j].bits } ] }; - result = { + }; + break; + } + case 'or': case 'and': case 'xor': case 'icmp': { + var otherElements = getLegalVars(value.params[1].ident, sourceBits); + processor = function(result, j) { + return { 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', + op: value.op, + variant: value.variant, + type: 'i' + otherElements[j].bits, params: [ result, - { intertype: 'value', ident: fraction.toString(), type: 'i32' } + { intertype: 'value', ident: otherElements[j].ident, type: 'i' + otherElements[j].bits } ] }; - result = { - intertype: 'mathop', - op: 'or', - type: 'i32', - params: [ - result, - other - ] + }; + if (value.op == 'icmp') { + if (sourceBits == 64) { // handle the i64 case in processMathOp, where we handle full i64 math + i++; + continue; } - } - 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', - params: [ - result, - { intertype: 'value', ident: (Math.pow(2, targetElements[j].bits)-1).toString(), type: 'i32' } - ] + finalizer = function() { + var ident = ''; + for (var i = 0; i < targetElements.length; i++) { + if (i > 0) { + switch(value.variant) { + case 'eq': ident += '&'; break; + case 'ne': ident += '|'; break; + default: throw 'unhandleable illegal icmp: ' + value.variant; + } + } + ident += targetElements[i].ident; + } + return { + intertype: 'value', + ident: ident, + type: 'rawJS', + assignTo: item.assignTo + }; } } - if (processor) { - result = processor(result, j); + break; + } + case 'add': case 'sub': case 'sdiv': case 'udiv': case 'mul': case 'urem': case 'srem': { + if (sourceBits < 32) { + // when we add illegal types like i24, we must work on the singleton chunks + item.assignTo += '$0'; + item.params[0].ident += '$0'; + item.params[1].ident += '$0'; } - result.assignTo = targetElements[j].ident; - toAdd.push(result); + // fall through } - if (targetBits <= 32) { - // We are generating a normal legal type here - legalValue = { + case 'uitofp': case 'sitofp': case 'fptosi': case 'fptoui': { + // We cannot do these in parallel chunks of 32-bit operations. We will handle these in processMathop + i++; + continue; + } + default: throw 'Invalid mathop for legalization: ' + [value.op, item.lineNum, dump(item)]; + } + // Do the legalization + var sourceElements = getLegalVars(value.params[0].ident, sourceBits, true); + if (!isNumber(shifts)) { + // We can't statically legalize this, do the operation at runtime TODO: optimize + assert(sourceBits == 64, 'TODO: handle nonconstant shifts on != 64 bits'); + assert(PRECISE_I64_MATH, 'Must have precise i64 math for non-constant 64-bit shifts'); + Types.preciseI64MathUsed = 1; + value.intertype = 'value'; + value.ident = 'var ' + value.assignTo + '$0 = ' + + asmCoercion('_bitshift64' + value.op[0].toUpperCase() + value.op.substr(1) + '(' + + asmCoercion(sourceElements[0].ident, 'i32') + ',' + + asmCoercion(sourceElements[1].ident, 'i32') + ',' + + asmCoercion(value.params[1].ident + '$0', 'i32') + ')', 'i32' + ) + ';' + + 'var ' + value.assignTo + '$1 = tempRet0;'; + value.assignTo = null; + i++; + continue; + } + 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 = '(' + makeSignOp(sourceElements[sourceElements.length-1].ident, 'i' + sourceElements[sourceElements.length-1].bits, 're', 1, 1) + ' < 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'), + params: [(signed && j + whole > sourceElements.length) ? signedKeepAlive : null], + type: 'i32', + }; + if (j == 0 && sourceBits < 32) { + // zext sign correction + result.ident = makeSignOp(result.ident, 'i' + sourceBits, isUnsignedOp(value.op) ? 'un' : 're', 1, 1); + } + if (fraction != 0) { + var other = { intertype: 'value', - ident: targetElements[0].ident + (targetBits < 32 ? '&' + (Math.pow(2, targetBits)-1) : ''), - type: 'rawJS' + ident: (j + sign + whole >= 0 && j + sign + whole < sourceElements.length) ? sourceElements[j + sign + whole].ident : (signed ? signedFill : '0'), + params: [(signed && j + sign + whole > sourceElements.length) ? signedKeepAlive : null], + type: 'i32', + }; + other = { + intertype: 'mathop', + op: shiftOp, + type: 'i32', + params: [ + other, + { intertype: 'value', ident: (32 - fraction).toString(), type: 'i32' } + ] }; - legalValue.assignTo = item.assignTo; - toAdd.push(legalValue); - } else if (finalizer) { - toAdd.push(finalizer()); + 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', + params: [ + result, + { intertype: 'value', ident: fraction.toString(), type: 'i32' } + ] + }; + result = { + intertype: 'mathop', + op: 'or', + type: 'i32', + params: [ + result, + other + ] + } } - i += removeAndAdd(label.lines, i, toAdd); - continue; + 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', + params: [ + result, + { 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); } + if (targetBits <= 32) { + // We are generating a normal legal type here + legalValue = { + intertype: 'value', + ident: targetElements[0].ident + (targetBits < 32 ? '&' + (Math.pow(2, targetBits)-1) : ''), + type: 'rawJS' + }; + legalValue.assignTo = item.assignTo; + toAdd.push(legalValue); + } else if (finalizer) { + toAdd.push(finalizer()); + } + i += removeAndAdd(label.lines, i, toAdd); + continue; } - assert(0, 'Could not legalize illegal line: ' + [item.lineNum, dump(item)]); } - if (dcheck('legalizer')) dprint('zz legalized: \n' + dump(label.lines)); - }); - }); - } - - // Add function lines to func.lines, after our modifications to the label lines - data.functions.forEach(function(func) { - func.labels.forEach(function(label) { - func.lines = func.lines.concat(label.lines); + assert(0, 'Could not legalize illegal line: ' + [item.lineNum, dump(item)]); + } + if (dcheck('legalizer')) dprint('zz legalized: \n' + dump(label.lines)); }); }); - this.forwardItem(data, 'Typevestigator'); } - }); - function addTypeInternal(type, data) { + // Add function lines to func.lines, after our modifications to the label lines + data.functions.forEach(function(func) { + func.labels.forEach(function(label) { + func.lines = func.lines.concat(label.lines); + }); + }); + } + + function addTypeInternal(type) { if (type.length == 1) return; if (Types.types[type]) return; if (['internal', 'hidden', 'inbounds', 'void'].indexOf(type) != -1) return; @@ -927,8 +911,9 @@ function analyzer(data, sidePass) { // to look at the underlying type - it was not defined explicitly // anywhere else. var nonPointing = removeAllPointing(type); + if (Types.types[nonPointing]) return; var check = /^\[(\d+)\ x\ (.*)\]$/.exec(nonPointing); - if (check && !Types.types[nonPointing]) { + if (check) { var num = parseInt(check[1]); num = Math.max(num, 1); // [0 x something] is used not for allocations and such of course, but // for indexing - for an |array of unknown length|, basically. So we @@ -936,7 +921,7 @@ function analyzer(data, sidePass) { // check that we never allocate with this (either as a child structure // in the analyzer, or in calcSize in alloca). var subType = check[2]; - addTypeInternal(subType, data); // needed for anonymous structure definitions (see below) + addTypeInternal(subType); // needed for anonymous structure definitions (see below) // Huge structural types are represented very inefficiently, both here and in generated JS. Best to avoid them - for example static char x[10*1024*1024]; is bad, while static char *x = malloc(10*1024*1024) is fine. if (num >= 10*1024*1024) warnOnce('warning: very large fixed-size structural type: ' + type + ' - can you reduce it? (compilation may be slow)'); @@ -945,6 +930,7 @@ function analyzer(data, sidePass) { fields: range(num).map(function() { return subType }), lineNum: '?' }; + newTypes[nonPointing] = 1; // Also add a |[0 x type]| type var zerod = '[0 x ' + subType + ']'; if (!Types.types[zerod]) { @@ -953,6 +939,7 @@ function analyzer(data, sidePass) { fields: [subType, subType], // Two, so we get the flatFactor right. We care about the flatFactor, not the size here lineNum: '?' }; + newTypes[zerod] = 1; } return; } @@ -983,6 +970,7 @@ function analyzer(data, sidePass) { packed: packed, lineNum: '?' }; + newTypes[type] = 1; return; } @@ -994,251 +982,243 @@ function analyzer(data, sidePass) { flatSize: 1, lineNum: '?' }; + newTypes[type] = 1; } - function addType(type, data) { - addTypeInternal(type, data); + function addType(type) { + addTypeInternal(type); if (QUANTUM_SIZE === 1) { Types.flipTypes(); - addTypeInternal(type, data); + addTypeInternal(type); Types.flipTypes(); } } // Typevestigator - substrate.addActor('Typevestigator', { - processItem: function(data) { - if (sidePass) { // Do not investigate in the main pass - it is only valid to start to do so in the first side pass, - // which handles type definitions, and later. Doing so before the first side pass will result in - // making bad guesses about types which are actually defined - for (var type in Types.needAnalysis) { - if (type) addType(type, data); - } - Types.needAnalysis = {}; + function typevestigator() { + if (sidePass) { // Do not investigate in the main pass - it is only valid to start to do so in the first side pass, + // which handles type definitions, and later. Doing so before the first side pass will result in + // making bad guesses about types which are actually defined + for (var type in Types.needAnalysis) { + if (type) addType(type); } - this.forwardItem(data, 'Typeanalyzer'); + Types.needAnalysis = {}; } - }); + } // Type analyzer - substrate.addActor('Typeanalyzer', { - processItem: function analyzeTypes(item, fatTypes) { - var types = Types.types; - - // 'fields' is the raw list of LLVM fields. However, we embed - // child structures into parent structures, basically like C. - // So { int, { int, int }, int } would be represented as - // an Array of 4 ints. getelementptr on the parent would take - // values 0, 1, 2, where 2 is the entire middle structure. - // We also need to be careful with getelementptr to child - // structures - we return a pointer to the same slab, just - // a different offset. Likewise, need to be careful for - // getelementptr of 2 (the last int) - it's real index is 4. - // The benefit of this approach is inheritance - - // { { ancestor } , etc. } = descendant - // In this case it is easy to bitcast ancestor to descendant - // pointers - nothing needs to be done. If the ancestor were - // a new slab, it would need some pointer to the outer one - // for casting in that direction. - // TODO: bitcasts of non-inheritance cases of embedding (not at start) - var more = true; - while (more) { - more = false; - for (var typeName in types) { - var type = types[typeName]; - if (type.flatIndexes) continue; - var ready = true; - type.fields.forEach(function(field) { - if (isStructType(field)) { - if (!types[field]) { - addType(field, item); + function analyzeTypes(fatTypes) { + var types = Types.types; + + // 'fields' is the raw list of LLVM fields. However, we embed + // child structures into parent structures, basically like C. + // So { int, { int, int }, int } would be represented as + // an Array of 4 ints. getelementptr on the parent would take + // values 0, 1, 2, where 2 is the entire middle structure. + // We also need to be careful with getelementptr to child + // structures - we return a pointer to the same slab, just + // a different offset. Likewise, need to be careful for + // getelementptr of 2 (the last int) - it's real index is 4. + // The benefit of this approach is inheritance - + // { { ancestor } , etc. } = descendant + // In this case it is easy to bitcast ancestor to descendant + // pointers - nothing needs to be done. If the ancestor were + // a new slab, it would need some pointer to the outer one + // for casting in that direction. + // TODO: bitcasts of non-inheritance cases of embedding (not at start) + var more = true; + while (more) { + more = false; + for (var typeName in newTypes) { + var type = types[typeName]; + if (type.flatIndexes) continue; + var ready = true; + type.fields.forEach(function(field) { + if (isStructType(field)) { + if (!types[field]) { + addType(field); + ready = false; + } else { + if (!types[field].flatIndexes) { + newTypes[field] = 1; ready = false; - } else { - if (!types[field].flatIndexes) { - ready = false; - } } } - }); - if (!ready) { - more = true; - continue; } - - Runtime.calculateStructAlignment(type); - - if (dcheck('types')) dprint('type (fat=' + !!fatTypes + '): ' + type.name_ + ' : ' + JSON.stringify(type.fields)); - if (dcheck('types')) dprint(' has final size of ' + type.flatSize + ', flatting: ' + type.needsFlattening + ' ? ' + (type.flatFactor ? type.flatFactor : JSON.stringify(type.flatIndexes))); + }); + if (!ready) { + more = true; + continue; } - } - if (QUANTUM_SIZE === 1 && !fatTypes) { - Types.flipTypes(); - // Fake a quantum size of 4 for fat types. TODO: Might want non-4 for some reason? - var trueQuantumSize = QUANTUM_SIZE; - Runtime.QUANTUM_SIZE = 4; - analyzeTypes(item, true); - Runtime.QUANTUM_SIZE = trueQuantumSize; - Types.flipTypes(); - } + Runtime.calculateStructAlignment(type); - if (!fatTypes) { - this.forwardItem(item, 'VariableAnalyzer'); + if (dcheck('types')) dprint('type (fat=' + !!fatTypes + '): ' + type.name_ + ' : ' + JSON.stringify(type.fields)); + if (dcheck('types')) dprint(' has final size of ' + type.flatSize + ', flatting: ' + type.needsFlattening + ' ? ' + (type.flatFactor ? type.flatFactor : JSON.stringify(type.flatIndexes))); } } - }); + + if (QUANTUM_SIZE === 1 && !fatTypes) { + Types.flipTypes(); + // Fake a quantum size of 4 for fat types. TODO: Might want non-4 for some reason? + var trueQuantumSize = QUANTUM_SIZE; + Runtime.QUANTUM_SIZE = 4; + analyzeTypes(true); + Runtime.QUANTUM_SIZE = trueQuantumSize; + Types.flipTypes(); + } + + newTypes = null; + } // Variable analyzer - substrate.addActor('VariableAnalyzer', { - processItem: function(item) { - // Globals - - var old = item.globalVariables; - item.globalVariables = {}; - old.forEach(function(variable) { - variable.impl = 'emulated'; // All global variables are emulated, for now. Consider optimizing later if useful - item.globalVariables[variable.ident] = variable; + function variableAnalyzer() { + // Globals + + var old = item.globalVariables; + item.globalVariables = {}; + old.forEach(function(variable) { + variable.impl = 'emulated'; // All global variables are emulated, for now. Consider optimizing later if useful + item.globalVariables[variable.ident] = variable; + }); + + // Function locals + + item.functions.forEach(function(func) { + func.variables = {}; + + // LLVM is SSA, so we always have a single assignment/write. We care about + // the reads/other uses. + + // Function parameters + func.params.forEach(function(param) { + if (param.intertype !== 'varargs') { + if (func.variables[param.ident]) warn('cannot have duplicate variable names: ' + param.ident); // toNiceIdent collisions? + func.variables[param.ident] = { + ident: param.ident, + type: param.type, + origin: 'funcparam', + lineNum: func.lineNum, + rawLinesIndex: -1 + }; + } }); - // Function locals - - item.functions.forEach(function(func) { - func.variables = {}; - - // LLVM is SSA, so we always have a single assignment/write. We care about - // the reads/other uses. - - // Function parameters - func.params.forEach(function(param) { - if (param.intertype !== 'varargs') { - if (func.variables[param.ident]) warn('cannot have duplicate variable names: ' + param.ident); // toNiceIdent collisions? - func.variables[param.ident] = { - ident: param.ident, - type: param.type, - origin: 'funcparam', - lineNum: func.lineNum, - rawLinesIndex: -1 - }; + // Normal variables + func.lines.forEach(function(item, i) { + if (item.assignTo) { + if (func.variables[item.assignTo]) warn('cannot have duplicate variable names: ' + item.assignTo); // toNiceIdent collisions? + var variable = func.variables[item.assignTo] = { + ident: item.assignTo, + type: item.type, + origin: item.intertype, + lineNum: item.lineNum, + rawLinesIndex: i + }; + if (variable.origin === 'alloca') { + variable.allocatedNum = item.allocatedNum; } - }); - - // Normal variables - func.lines.forEach(function(item, i) { - if (item.assignTo) { - if (func.variables[item.assignTo]) warn('cannot have duplicate variable names: ' + item.assignTo); // toNiceIdent collisions? - var variable = func.variables[item.assignTo] = { - ident: item.assignTo, - type: item.type, - origin: item.intertype, - lineNum: item.lineNum, - rawLinesIndex: i - }; - if (variable.origin === 'alloca') { - variable.allocatedNum = item.allocatedNum; - } - if (variable.origin === 'call') { - variable.type = getReturnType(variable.type); - } + if (variable.origin === 'call') { + variable.type = getReturnType(variable.type); } - }); + } + }); - if (QUANTUM_SIZE === 1) { - // Second pass over variables - notice when types are crossed by bitcast - - func.lines.forEach(function(item) { - if (item.assignTo && item.intertype === 'bitcast') { - // bitcasts are unique in that they convert one pointer to another. We - // sometimes need to know the original type of a pointer, so we save that. - // - // originalType is the type this variable is created from - // derivedTypes are the types that this variable is cast into - func.variables[item.assignTo].originalType = item.type2; - - if (!isNumber(item.assignTo)) { - if (!func.variables[item.assignTo].derivedTypes) { - func.variables[item.assignTo].derivedTypes = []; - } - func.variables[item.assignTo].derivedTypes.push(item.type); + if (QUANTUM_SIZE === 1) { + // Second pass over variables - notice when types are crossed by bitcast + + func.lines.forEach(function(item) { + if (item.assignTo && item.intertype === 'bitcast') { + // bitcasts are unique in that they convert one pointer to another. We + // sometimes need to know the original type of a pointer, so we save that. + // + // originalType is the type this variable is created from + // derivedTypes are the types that this variable is cast into + func.variables[item.assignTo].originalType = item.type2; + + if (!isNumber(item.assignTo)) { + if (!func.variables[item.assignTo].derivedTypes) { + func.variables[item.assignTo].derivedTypes = []; } + func.variables[item.assignTo].derivedTypes.push(item.type); } - }); - } + } + }); + } - // Analyze variable uses + // Analyze variable uses - function analyzeVariableUses() { - dprint('vars', 'Analyzing variables for ' + func.ident + '\n'); + function analyzeVariableUses() { + dprint('vars', 'Analyzing variables for ' + func.ident + '\n'); - for (vname in func.variables) { - var variable = func.variables[vname]; + for (vname in func.variables) { + var variable = func.variables[vname]; - // Whether the value itself is used. For an int, always yes. For a pointer, - // we might never use the pointer's value - we might always just store to it / - // read from it. If so, then we can optimize away the pointer. - variable.hasValueTaken = false; + // Whether the value itself is used. For an int, always yes. For a pointer, + // we might never use the pointer's value - we might always just store to it / + // read from it. If so, then we can optimize away the pointer. + variable.hasValueTaken = false; - variable.pointingLevels = pointingLevels(variable.type); + variable.pointingLevels = pointingLevels(variable.type); - variable.uses = 0; - } + variable.uses = 0; + } - // TODO: improve the analysis precision. bitcast, for example, means we take the value, but perhaps we only use it to load/store - var inNoop = 0; - func.lines.forEach(function(line) { - walkInterdata(line, function(item) { - if (item.intertype == 'noop') inNoop++; - if (!inNoop) { - if (item.ident in func.variables) { - func.variables[item.ident].uses++; - - if (item.intertype != 'load' && item.intertype != 'store') { - func.variables[item.ident].hasValueTaken = true; - } + // TODO: improve the analysis precision. bitcast, for example, means we take the value, but perhaps we only use it to load/store + var inNoop = 0; + func.lines.forEach(function(line) { + walkInterdata(line, function(item) { + if (item.intertype == 'noop') inNoop++; + if (!inNoop) { + if (item.ident in func.variables) { + func.variables[item.ident].uses++; + + if (item.intertype != 'load' && item.intertype != 'store') { + func.variables[item.ident].hasValueTaken = true; } } - }, function(item) { - if (item.intertype == 'noop') inNoop--; - }); + } + }, function(item) { + if (item.intertype == 'noop') inNoop--; }); + }); - //if (dcheck('vars')) dprint('analyzed variables: ' + dump(func.variables)); - } - - analyzeVariableUses(); - - // Decision time + //if (dcheck('vars')) dprint('analyzed variables: ' + dump(func.variables)); + } - for (vname in func.variables) { - var variable = func.variables[vname]; - var pointedType = pointingLevels(variable.type) > 0 ? removePointing(variable.type) : null; - if (variable.origin == 'getelementptr') { - // Use our implementation that emulates pointers etc. - // TODO Can we perhaps nativize some of these? However to do so, we need to discover their - // true types; we have '?' for them now, as they cannot be discovered in the intertyper. - variable.impl = VAR_EMULATED; - } else if (variable.origin == 'funcparam') { - variable.impl = VAR_EMULATED; - } else if (variable.type == 'i64*' && USE_TYPED_ARRAYS == 2) { - variable.impl = VAR_EMULATED; - } else if (MICRO_OPTS && variable.pointingLevels === 0) { - // A simple int value, can be implemented as a native variable - variable.impl = VAR_NATIVE; - } else if (MICRO_OPTS && variable.origin === 'alloca' && !variable.hasValueTaken && - variable.allocatedNum === 1 && - (Runtime.isNumberType(pointedType) || Runtime.isPointerType(pointedType))) { - // A pointer to a value which is only accessible through this pointer. Basically - // a local value on the stack, which nothing fancy is done on. So we can - // optimize away the pointing altogether, and just have a native variable - variable.impl = VAR_NATIVIZED; - } else { - variable.impl = VAR_EMULATED; - } - if (dcheck('vars')) dprint('// var ' + vname + ': ' + JSON.stringify(variable)); + analyzeVariableUses(); + + // Decision time + + for (vname in func.variables) { + var variable = func.variables[vname]; + var pointedType = pointingLevels(variable.type) > 0 ? removePointing(variable.type) : null; + if (variable.origin == 'getelementptr') { + // Use our implementation that emulates pointers etc. + // TODO Can we perhaps nativize some of these? However to do so, we need to discover their + // true types; we have '?' for them now, as they cannot be discovered in the intertyper. + variable.impl = VAR_EMULATED; + } else if (variable.origin == 'funcparam') { + variable.impl = VAR_EMULATED; + } else if (variable.type == 'i64*' && USE_TYPED_ARRAYS == 2) { + variable.impl = VAR_EMULATED; + } else if (MICRO_OPTS && variable.pointingLevels === 0) { + // A simple int value, can be implemented as a native variable + variable.impl = VAR_NATIVE; + } else if (MICRO_OPTS && variable.origin === 'alloca' && !variable.hasValueTaken && + variable.allocatedNum === 1 && + (Runtime.isNumberType(pointedType) || Runtime.isPointerType(pointedType))) { + // A pointer to a value which is only accessible through this pointer. Basically + // a local value on the stack, which nothing fancy is done on. So we can + // optimize away the pointing altogether, and just have a native variable + variable.impl = VAR_NATIVIZED; + } else { + variable.impl = VAR_EMULATED; } - }); - this.forwardItem(item, 'Signalyzer'); - } - }); + if (dcheck('vars')) dprint('// var ' + vname + ': ' + JSON.stringify(variable)); + } + }); + } // Sign analyzer // @@ -1251,214 +1231,208 @@ function analyzer(data, sidePass) { // to see where it is used. We only care about mathops, since only they // need signs. // - substrate.addActor('Signalyzer', { - processItem: function(item) { - this.forwardItem(item, 'QuantumFixer'); - if (USE_TYPED_ARRAYS != 2 || CORRECT_SIGNS == 1) return; - - function seekIdent(item, obj) { - if (item.ident === obj.ident) { - obj.found++; - } + function signalyzer() { + if (USE_TYPED_ARRAYS != 2 || CORRECT_SIGNS == 1) return; + + function seekIdent(item, obj) { + if (item.ident === obj.ident) { + obj.found++; } + } - function seekMathop(item, obj) { - if (item.intertype === 'mathop' && obj.found && !obj.decided) { - if (isUnsignedOp(item.op, item.variant)) { - obj.unsigned++; - } else { - obj.signed++; - } + function seekMathop(item, obj) { + if (item.intertype === 'mathop' && obj.found && !obj.decided) { + if (isUnsignedOp(item.op, item.variant)) { + obj.unsigned++; + } else { + obj.signed++; } } + } - item.functions.forEach(function(func) { - func.lines.forEach(function(line, i) { - if (line.intertype === 'load') { - // Floats have no concept of signedness. Mark them as 'signed', which is the default, for which we do nothing - if (line.type in Runtime.FLOAT_TYPES) { - line.unsigned = false; - return; - } - // Booleans are always unsigned - var data = func.variables[line.assignTo]; - if (data.type === 'i1') { - line.unsigned = true; - return; - } - - var total = data.uses; - if (total === 0) return; - var obj = { ident: line.assignTo, found: 0, unsigned: 0, signed: 0, total: total }; - // in loops with phis, we can also be used *before* we are defined - var j = i-1, k = i+1; - while(1) { - assert(j >= 0 || k < func.lines.length, 'Signalyzer ran out of space to look for sign indications for line ' + line.lineNum); - if (j >= 0 && walkInterdata(func.lines[j], seekIdent, seekMathop, obj)) break; - if (k < func.lines.length && walkInterdata(func.lines[k], seekIdent, seekMathop, obj)) break; - if (obj.total && obj.found >= obj.total) break; // see comment below - j -= 1; - k += 1; - } + item.functions.forEach(function(func) { + func.lines.forEach(function(line, i) { + if (line.intertype === 'load') { + // Floats have no concept of signedness. Mark them as 'signed', which is the default, for which we do nothing + if (line.type in Runtime.FLOAT_TYPES) { + line.unsigned = false; + return; + } + // Booleans are always unsigned + var data = func.variables[line.assignTo]; + if (data.type === 'i1') { + line.unsigned = true; + return; + } - // unsigned+signed might be < total, since the same ident can appear multiple times in the same mathop. - // found can actually be > total, since we currently have the same ident in a GEP (see cubescript test) - // in the GEP item, and a child item (we have the ident copied onto the GEP item as a convenience). - // probably not a bug-causer, but FIXME. see also a reference to this above - // we also leave the loop above potentially early due to this. otherwise, though, we end up scanning the - // entire function in some cases which is very slow - assert(obj.found >= obj.total, 'Could not Signalyze line ' + line.lineNum); - line.unsigned = obj.unsigned > 0; - dprint('vars', 'Signalyzer: ' + line.assignTo + ' has unsigned == ' + line.unsigned + ' (line ' + line.lineNum + ')'); + var total = data.uses; + if (total === 0) return; + var obj = { ident: line.assignTo, found: 0, unsigned: 0, signed: 0, total: total }; + // in loops with phis, we can also be used *before* we are defined + var j = i-1, k = i+1; + while(1) { + assert(j >= 0 || k < func.lines.length, 'Signalyzer ran out of space to look for sign indications for line ' + line.lineNum); + if (j >= 0 && walkInterdata(func.lines[j], seekIdent, seekMathop, obj)) break; + if (k < func.lines.length && walkInterdata(func.lines[k], seekIdent, seekMathop, obj)) break; + if (obj.total && obj.found >= obj.total) break; // see comment below + j -= 1; + k += 1; } - }); + + // unsigned+signed might be < total, since the same ident can appear multiple times in the same mathop. + // found can actually be > total, since we currently have the same ident in a GEP (see cubescript test) + // in the GEP item, and a child item (we have the ident copied onto the GEP item as a convenience). + // probably not a bug-causer, but FIXME. see also a reference to this above + // we also leave the loop above potentially early due to this. otherwise, though, we end up scanning the + // entire function in some cases which is very slow + assert(obj.found >= obj.total, 'Could not Signalyze line ' + line.lineNum); + line.unsigned = obj.unsigned > 0; + dprint('vars', 'Signalyzer: ' + line.assignTo + ' has unsigned == ' + line.unsigned + ' (line ' + line.lineNum + ')'); + } }); - } - }); + }); + } // Quantum fixer // // See settings.js for the meaning of QUANTUM_SIZE. The issue we fix here is, // to correct the .ll assembly code so that things work with QUANTUM_SIZE=1. // - substrate.addActor('QuantumFixer', { - processItem: function(item) { - this.forwardItem(item, 'LabelAnalyzer'); - if (QUANTUM_SIZE !== 1) return; - - // ptrs: the indexes of parameters that are pointers, whose originalType is what we want - // bytes: the index of the 'bytes' parameter - // TODO: malloc, realloc? - var FIXABLE_CALLS = { - 'memcpy': { ptrs: [0,1], bytes: 2 }, - 'memmove': { ptrs: [0,1], bytes: 2 }, - 'memset': { ptrs: [0], bytes: 2 }, - 'qsort': { ptrs: [0], bytes: 2 } - }; + function quantumFixer() { + if (QUANTUM_SIZE !== 1) return; + + // ptrs: the indexes of parameters that are pointers, whose originalType is what we want + // bytes: the index of the 'bytes' parameter + // TODO: malloc, realloc? + var FIXABLE_CALLS = { + 'memcpy': { ptrs: [0,1], bytes: 2 }, + 'memmove': { ptrs: [0,1], bytes: 2 }, + 'memset': { ptrs: [0], bytes: 2 }, + 'qsort': { ptrs: [0], bytes: 2 } + }; - function getSize(types, type, fat) { - if (types[type]) return types[type].flatSize; - if (fat) { - Runtime.QUANTUM_SIZE = 4; - } - var ret = Runtime.getNativeTypeSize(type); - if (fat) { - Runtime.QUANTUM_SIZE = 1; - } - return ret; + function getSize(types, type, fat) { + if (types[type]) return types[type].flatSize; + if (fat) { + Runtime.QUANTUM_SIZE = 4; } - - function getFlatIndexes(types, type) { - if (types[type]) return types[type].flatIndexes; - return [0]; + var ret = Runtime.getNativeTypeSize(type); + if (fat) { + Runtime.QUANTUM_SIZE = 1; } + return ret; + } - item.functions.forEach(function(func) { - function getOriginalType(param) { - function get() { - if (param.intertype === 'value' && !isNumber(param.ident)) { - if (func.variables[param.ident]) { - return func.variables[param.ident].originalType || null; - } else { - return item.globalVariables[param.ident].originalType; - } - } else if (param.intertype === 'bitcast') { - return param.params[0].type; - } else if (param.intertype === 'getelementptr') { - if (param.params[0].type[0] === '[') return param.params[0].type; - } - return null; - } - var ret = get(); - if (ret && ret[0] === '[') { - var check = /^\[(\d+)\ x\ (.*)\]\*$/.exec(ret); - assert(check); - ret = check[2] + '*'; - } - return ret; - } + function getFlatIndexes(types, type) { + if (types[type]) return types[type].flatIndexes; + return [0]; + } - func.lines.forEach(function(line) { - // Call - if (line.intertype === 'call') { - var funcIdent = LibraryManager.getRootIdent(line.ident.substr(1)); - var fixData = FIXABLE_CALLS[funcIdent]; - if (!fixData) return; - var ptrs = fixData.ptrs.map(function(ptr) { return line.params[ptr] }); - var bytes = line.params[fixData.bytes].ident; - - // Only consider original types. This assumes memcpy always has pointers bitcast to i8* - var originalTypes = ptrs.map(getOriginalType); - for (var i = 0; i < originalTypes.length; i++) { - if (!originalTypes[i]) return; - } - originalTypes = originalTypes.map(function(type) { return removePointing(type) }); - var sizes = originalTypes.map(function(type) { return getSize(Types.types, type) }); - var fatSizes = originalTypes.map(function(type) { return getSize(Types.fatTypes, type, true) }); - // The sizes may not be identical, if we copy a descendant class into a parent class. We use - // the smaller size in that case. However, this may also be a bug, it is hard to tell, hence a warning - warn(dedup(sizes).length === 1, 'All sizes should probably be identical here: ' + dump(originalTypes) + ':' + dump(sizes) + ':' + - line.lineNum); - warn(dedup(fatSizes).length === 1, 'All fat sizes should probably be identical here: ' + dump(originalTypes) + ':' + dump(sizes) + ':' + - line.lineNum); - var size = Math.min.apply(null, sizes); - var fatSize = Math.min.apply(null, fatSizes); - if (isNumber(bytes)) { - // Figure out how much to copy. - var fixedBytes; - if (bytes % fatSize === 0) { - fixedBytes = size*(bytes/fatSize); - } else if (fatSize % bytes === 0 && size % (fatSize/bytes) === 0) { - // Assume this is a simple array. XXX We can be wrong though! See next TODO - fixedBytes = size/(fatSize/bytes); - } else { - // Just part of a structure. Align them to see how many fields. Err on copying more. - // TODO: properly generate a complete structure, including nesteds, and calculate on that - var flatIndexes = getFlatIndexes(Types.types, originalTypes[0]).concat(size); - var fatFlatIndexes = getFlatIndexes(Types.fatTypes, originalTypes[0]).concat(fatSize); - var index = 0; - var left = bytes; - fixedBytes = 0; - while (left > 0) { - left -= fatFlatIndexes[index+1] - fatFlatIndexes[index]; // note: we copy the alignment bytes too, which is unneeded - fixedBytes += flatIndexes[index+1] - flatIndexes[index]; - } - } - line.params[fixData.bytes].ident = fixedBytes; + item.functions.forEach(function(func) { + function getOriginalType(param) { + function get() { + if (param.intertype === 'value' && !isNumber(param.ident)) { + if (func.variables[param.ident]) { + return func.variables[param.ident].originalType || null; } else { - line.params[fixData.bytes].intertype = 'jsvalue'; - // We have an assertion in library::memcpy() that this is round - line.params[fixData.bytes].ident = size + '*(' + bytes + '/' + fatSize + ')'; + return item.globalVariables[param.ident].originalType; } + } else if (param.intertype === 'bitcast') { + return param.params[0].type; + } else if (param.intertype === 'getelementptr') { + if (param.params[0].type[0] === '[') return param.params[0].type; } - }); - }); + return null; + } + var ret = get(); + if (ret && ret[0] === '[') { + var check = /^\[(\d+)\ x\ (.*)\]\*$/.exec(ret); + assert(check); + ret = check[2] + '*'; + } + return ret; + } - // 2nd part - fix hardcoded constant offsets in global constants - values(item.globalVariables).forEach(function(variable) { - function recurse(item) { - if (item.contents) { - item.contents.forEach(recurse); - } else if (item.intertype === 'getelementptr' && item.params[0].intertype === 'bitcast' && item.params[0].type === 'i8*') { - var originalType = removePointing(item.params[0].params[0].type); - var fatSize = getSize(Types.fatTypes, originalType, true); - var slimSize = getSize(Types.types, originalType, false); - assert(fatSize % slimSize === 0); - item.params.slice(1).forEach(function(param) { - if (param.intertype === 'value' && isNumber(param.ident)) { - var corrected = parseInt(param.ident)/(fatSize/slimSize); - assert(corrected % 1 === 0); - param.ident = corrected.toString(); + func.lines.forEach(function(line) { + // Call + if (line.intertype === 'call') { + var funcIdent = LibraryManager.getRootIdent(line.ident.substr(1)); + var fixData = FIXABLE_CALLS[funcIdent]; + if (!fixData) return; + var ptrs = fixData.ptrs.map(function(ptr) { return line.params[ptr] }); + var bytes = line.params[fixData.bytes].ident; + + // Only consider original types. This assumes memcpy always has pointers bitcast to i8* + var originalTypes = ptrs.map(getOriginalType); + for (var i = 0; i < originalTypes.length; i++) { + if (!originalTypes[i]) return; + } + originalTypes = originalTypes.map(function(type) { return removePointing(type) }); + var sizes = originalTypes.map(function(type) { return getSize(Types.types, type) }); + var fatSizes = originalTypes.map(function(type) { return getSize(Types.fatTypes, type, true) }); + // The sizes may not be identical, if we copy a descendant class into a parent class. We use + // the smaller size in that case. However, this may also be a bug, it is hard to tell, hence a warning + warn(dedup(sizes).length === 1, 'All sizes should probably be identical here: ' + dump(originalTypes) + ':' + dump(sizes) + ':' + + line.lineNum); + warn(dedup(fatSizes).length === 1, 'All fat sizes should probably be identical here: ' + dump(originalTypes) + ':' + dump(sizes) + ':' + + line.lineNum); + var size = Math.min.apply(null, sizes); + var fatSize = Math.min.apply(null, fatSizes); + if (isNumber(bytes)) { + // Figure out how much to copy. + var fixedBytes; + if (bytes % fatSize === 0) { + fixedBytes = size*(bytes/fatSize); + } else if (fatSize % bytes === 0 && size % (fatSize/bytes) === 0) { + // Assume this is a simple array. XXX We can be wrong though! See next TODO + fixedBytes = size/(fatSize/bytes); + } else { + // Just part of a structure. Align them to see how many fields. Err on copying more. + // TODO: properly generate a complete structure, including nesteds, and calculate on that + var flatIndexes = getFlatIndexes(Types.types, originalTypes[0]).concat(size); + var fatFlatIndexes = getFlatIndexes(Types.fatTypes, originalTypes[0]).concat(fatSize); + var index = 0; + var left = bytes; + fixedBytes = 0; + while (left > 0) { + left -= fatFlatIndexes[index+1] - fatFlatIndexes[index]; // note: we copy the alignment bytes too, which is unneeded + fixedBytes += flatIndexes[index+1] - flatIndexes[index]; } - }); - } else if (item.params) { - item.params.forEach(recurse); + } + line.params[fixData.bytes].ident = fixedBytes; + } else { + line.params[fixData.bytes].intertype = 'jsvalue'; + // We have an assertion in library::memcpy() that this is round + line.params[fixData.bytes].ident = size + '*(' + bytes + '/' + fatSize + ')'; } } - if (!variable.external && variable.value) recurse(variable.value); }); - } - }); + }); + + // 2nd part - fix hardcoded constant offsets in global constants + values(item.globalVariables).forEach(function(variable) { + function recurse(item) { + if (item.contents) { + item.contents.forEach(recurse); + } else if (item.intertype === 'getelementptr' && item.params[0].intertype === 'bitcast' && item.params[0].type === 'i8*') { + var originalType = removePointing(item.params[0].params[0].type); + var fatSize = getSize(Types.fatTypes, originalType, true); + var slimSize = getSize(Types.types, originalType, false); + assert(fatSize % slimSize === 0); + item.params.slice(1).forEach(function(param) { + if (param.intertype === 'value' && isNumber(param.ident)) { + var corrected = parseInt(param.ident)/(fatSize/slimSize); + assert(corrected % 1 === 0); + param.ident = corrected.toString(); + } + }); + } else if (item.params) { + item.params.forEach(recurse); + } + } + if (!variable.external && variable.value) recurse(variable.value); + }); + } function operateOnLabels(line, func) { function process(item, id) { @@ -1477,268 +1451,260 @@ function analyzer(data, sidePass) { } // Label analyzer - substrate.addActor('LabelAnalyzer', { - processItem: function(item) { - item.functions.forEach(function(func) { - func.labelsDict = {}; - func.labelIds = {}; - func.labelIdsInverse = {}; - func.labelIdCounter = 1; - func.labels.forEach(function(label) { - if (!(label.ident in func.labelIds)) { - func.labelIds[label.ident] = func.labelIdCounter++; - func.labelIdsInverse[func.labelIdCounter-1] = label.ident; - } - }); - var entryIdent = func.labels[0].ident; - - // Minify label ids to numeric ids. - func.labels.forEach(function(label) { - label.ident = func.labelIds[label.ident]; - label.lines.forEach(function(line) { - operateOnLabels(line, function(item, id) { - item[id] = func.labelIds[item[id]].toString(); // strings, because we will append as we process - }); + function labelAnalyzer() { + item.functions.forEach(function(func) { + func.labelsDict = {}; + func.labelIds = {}; + func.labelIdsInverse = {}; + func.labelIdCounter = 1; + func.labels.forEach(function(label) { + if (!(label.ident in func.labelIds)) { + func.labelIds[label.ident] = func.labelIdCounter++; + func.labelIdsInverse[func.labelIdCounter-1] = label.ident; + } + }); + var entryIdent = func.labels[0].ident; + + // Minify label ids to numeric ids. + func.labels.forEach(function(label) { + label.ident = func.labelIds[label.ident]; + label.lines.forEach(function(line) { + operateOnLabels(line, function(item, id) { + item[id] = func.labelIds[item[id]].toString(); // strings, because we will append as we process }); }); + }); - func.labels.forEach(function(label) { - func.labelsDict[label.ident] = label; - }); + func.labels.forEach(function(label) { + func.labelsDict[label.ident] = label; + }); - // Correct phis - func.labels.forEach(function(label) { - label.lines.forEach(function(phi) { - if (phi.intertype == 'phi') { - for (var i = 0; i < phi.params.length; i++) { - phi.params[i].label = func.labelIds[phi.params[i].label]; - if (VERBOSE && !phi.params[i].label) warn('phi refers to nonexistent label on line ' + phi.lineNum); - } + // Correct phis + func.labels.forEach(function(label) { + label.lines.forEach(function(phi) { + if (phi.intertype == 'phi') { + for (var i = 0; i < phi.params.length; i++) { + phi.params[i].label = func.labelIds[phi.params[i].label]; + if (VERBOSE && !phi.params[i].label) warn('phi refers to nonexistent label on line ' + phi.lineNum); } - }); - }); - - func.lines.forEach(function(line) { - if (line.intertype == 'indirectbr') { - func.forceEmulated = true; } }); + }); - function getActualLabelId(labelId) { - if (func.labelsDict[labelId]) return labelId; - // If not present, it must be a surprisingly-named entry (or undefined behavior, in which case, still ok to use the entry) - labelId = func.labelIds[entryIdent]; - assert(func.labelsDict[labelId]); - return labelId; + func.lines.forEach(function(line) { + if (line.intertype == 'indirectbr') { + func.forceEmulated = true; } + }); - // Basic longjmp support, see library.js setjmp/longjmp - var setjmp = toNiceIdent('@setjmp'); - func.setjmpTable = null; - for (var i = 0; i < func.labels.length; i++) { - var label = func.labels[i]; - for (var j = 0; j < label.lines.length; j++) { - var line = label.lines[j]; - if ((line.intertype == 'call' || line.intertype == 'invoke') && line.ident == setjmp) { - // Add a new label - var oldLabel = label.ident; - var newLabel = func.labelIdCounter++; - if (!func.setjmpTable) func.setjmpTable = []; - func.setjmpTable.push({ oldLabel: oldLabel, newLabel: newLabel, assignTo: line.assignTo }); - func.labels.splice(i+1, 0, { - intertype: 'label', - ident: newLabel, - lineNum: label.lineNum + 0.5, - lines: label.lines.slice(j+1) - }); - func.labelsDict[newLabel] = func.labels[i+1]; - label.lines = label.lines.slice(0, j+1); - label.lines.push({ - intertype: 'branch', - label: toNiceIdent(newLabel), - lineNum: line.lineNum + 0.01, // XXX legalizing might confuse this - }); - // Correct phis - func.labels.forEach(function(label) { - label.lines.forEach(function(phi) { - if (phi.intertype == 'phi') { - for (var i = 0; i < phi.params.length; i++) { - var sourceLabelId = getActualLabelId(phi.params[i].label); - if (sourceLabelId == oldLabel) { - phi.params[i].label = newLabel; - } + function getActualLabelId(labelId) { + if (func.labelsDict[labelId]) return labelId; + // If not present, it must be a surprisingly-named entry (or undefined behavior, in which case, still ok to use the entry) + labelId = func.labelIds[entryIdent]; + assert(func.labelsDict[labelId]); + return labelId; + } + + // Basic longjmp support, see library.js setjmp/longjmp + var setjmp = toNiceIdent('@setjmp'); + func.setjmpTable = null; + for (var i = 0; i < func.labels.length; i++) { + var label = func.labels[i]; + for (var j = 0; j < label.lines.length; j++) { + var line = label.lines[j]; + if ((line.intertype == 'call' || line.intertype == 'invoke') && line.ident == setjmp) { + // Add a new label + var oldLabel = label.ident; + var newLabel = func.labelIdCounter++; + if (!func.setjmpTable) func.setjmpTable = []; + func.setjmpTable.push({ oldLabel: oldLabel, newLabel: newLabel, assignTo: line.assignTo }); + func.labels.splice(i+1, 0, { + intertype: 'label', + ident: newLabel, + lineNum: label.lineNum + 0.5, + lines: label.lines.slice(j+1) + }); + func.labelsDict[newLabel] = func.labels[i+1]; + label.lines = label.lines.slice(0, j+1); + label.lines.push({ + intertype: 'branch', + label: toNiceIdent(newLabel), + lineNum: line.lineNum + 0.01, // XXX legalizing might confuse this + }); + // Correct phis + func.labels.forEach(function(label) { + label.lines.forEach(function(phi) { + if (phi.intertype == 'phi') { + for (var i = 0; i < phi.params.length; i++) { + var sourceLabelId = getActualLabelId(phi.params[i].label); + if (sourceLabelId == oldLabel) { + phi.params[i].label = newLabel; } } - }); + } }); - } + }); } } - if (func.setjmpTable) { - func.forceEmulated = true; - recomputeLines(func); - } - - // Properly implement phis, by pushing them back into the branch - // that leads to here. We will only have the |var| definition in this location. + } + if (func.setjmpTable) { + func.forceEmulated = true; + recomputeLines(func); + } - // First, push phis back - func.labels.forEach(function(label) { - label.lines.forEach(function(phi) { - if (phi.intertype == 'phi') { - for (var i = 0; i < phi.params.length; i++) { - var param = phi.params[i]; - if (VERBOSE && !param.label) warn('phi refers to nonexistent label on line ' + phi.lineNum); - var sourceLabelId = getActualLabelId(param.label); - if (sourceLabelId) { - var sourceLabel = func.labelsDict[sourceLabelId]; - var lastLine = sourceLabel.lines.slice(-1)[0]; - assert(lastLine.intertype in LLVM.PHI_REACHERS, 'Only some can lead to labels with phis:' + [func.ident, label.ident, lastLine.intertype]); - if (!lastLine.phi) { - lastLine.phi = true; - assert(!lastLine.dependent); - lastLine.dependent = { - intertype: 'phiassigns', - params: [] - }; + // Properly implement phis, by pushing them back into the branch + // that leads to here. We will only have the |var| definition in this location. + + // First, push phis back + func.labels.forEach(function(label) { + label.lines.forEach(function(phi) { + if (phi.intertype == 'phi') { + for (var i = 0; i < phi.params.length; i++) { + var param = phi.params[i]; + if (VERBOSE && !param.label) warn('phi refers to nonexistent label on line ' + phi.lineNum); + var sourceLabelId = getActualLabelId(param.label); + if (sourceLabelId) { + var sourceLabel = func.labelsDict[sourceLabelId]; + var lastLine = sourceLabel.lines.slice(-1)[0]; + assert(lastLine.intertype in LLVM.PHI_REACHERS, 'Only some can lead to labels with phis:' + [func.ident, label.ident, lastLine.intertype]); + if (!lastLine.phi) { + lastLine.phi = true; + assert(!lastLine.dependent); + lastLine.dependent = { + intertype: 'phiassigns', + params: [] }; - lastLine.dependent.params.push({ - intertype: 'phiassign', - ident: phi.assignTo, - value: param.value, - targetLabel: label.ident - }); - } + }; + lastLine.dependent.params.push({ + intertype: 'phiassign', + ident: phi.assignTo, + value: param.value, + targetLabel: label.ident + }); } - // The assign to phi is now just a var - phi.intertype = 'var'; - phi.ident = phi.assignTo; - phi.assignTo = null; } - }); + // The assign to phi is now just a var + phi.intertype = 'var'; + phi.ident = phi.assignTo; + phi.assignTo = null; + } }); + }); - if (func.ident in NECESSARY_BLOCKADDRS) { - Functions.blockAddresses[func.ident] = {}; - for (var needed in NECESSARY_BLOCKADDRS[func.ident]) { - assert(needed in func.labelIds); - Functions.blockAddresses[func.ident][needed] = func.labelIds[needed]; - } + if (func.ident in NECESSARY_BLOCKADDRS) { + Functions.blockAddresses[func.ident] = {}; + for (var needed in NECESSARY_BLOCKADDRS[func.ident]) { + assert(needed in func.labelIds); + Functions.blockAddresses[func.ident][needed] = func.labelIds[needed]; } - }); - this.forwardItem(item, 'StackAnalyzer'); - } - }); + } + }); + } // Stack analyzer - calculate the base stack usage - substrate.addActor('StackAnalyzer', { - processItem: function(data) { - data.functions.forEach(function(func) { - var lines = func.labels[0].lines; - for (var i = 0; i < lines.length; i++) { - var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; - item.allocatedSize = func.variables[item.assignTo].impl === VAR_EMULATED ? - calcAllocatedSize(item.allocatedType)*item.allocatedNum: 0; - if (USE_TYPED_ARRAYS === 2) { - // We need to keep the stack aligned - item.allocatedSize = Runtime.forceAlign(item.allocatedSize, Runtime.STACK_ALIGN); - } + function stackAnalyzer() { + data.functions.forEach(function(func) { + var lines = func.labels[0].lines; + for (var i = 0; i < lines.length; i++) { + var item = lines[i]; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; + item.allocatedSize = func.variables[item.assignTo].impl === VAR_EMULATED ? + calcAllocatedSize(item.allocatedType)*item.allocatedNum: 0; + if (USE_TYPED_ARRAYS === 2) { + // We need to keep the stack aligned + item.allocatedSize = Runtime.forceAlign(item.allocatedSize, Runtime.STACK_ALIGN); } - var index = 0; - for (var i = 0; i < lines.length; i++) { - var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; - item.allocatedIndex = index; - index += item.allocatedSize; - delete item.allocatedSize; - } - func.initialStack = index; - func.otherStackAllocations = false; - while (func.initialStack == 0) { // one-time loop with possible abort in the middle - // If there is no obvious need for stack management, perhaps we don't need it - // (we try to optimize that way with SKIP_STACK_IN_SMALL). However, - // we need to note if stack allocations other than initial allocs can happen here - // If so, we need to rewind the stack when we leave. - - // By-value params are causes of additional allocas (although we could in theory make them normal allocas too) - func.params.forEach(function(param) { - if (param.byVal) { - func.otherStackAllocations = true; - } - }); - if (func.otherStackAllocations) break; + } + var index = 0; + for (var i = 0; i < lines.length; i++) { + var item = lines[i]; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; + item.allocatedIndex = index; + index += item.allocatedSize; + delete item.allocatedSize; + } + func.initialStack = index; + func.otherStackAllocations = false; + while (func.initialStack == 0) { // one-time loop with possible abort in the middle + // If there is no obvious need for stack management, perhaps we don't need it + // (we try to optimize that way with SKIP_STACK_IN_SMALL). However, + // we need to note if stack allocations other than initial allocs can happen here + // If so, we need to rewind the stack when we leave. + + // By-value params are causes of additional allocas (although we could in theory make them normal allocas too) + func.params.forEach(function(param) { + if (param.byVal) { + func.otherStackAllocations = true; + } + }); + if (func.otherStackAllocations) break; - // Allocas - var finishedInitial = false; + // Allocas + var finishedInitial = false; - lines = func.lines; // We need to consider all the function lines now, not just the first label + lines = func.lines; // We need to consider all the function lines now, not just the first label - for (var i = 0; i < lines.length; i++) { - var item = lines[i]; - if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum))) { - finishedInitial = true; - } - if (item.intertype == 'alloca' && finishedInitial) { - func.otherStackAllocations = true; - break; - } + for (var i = 0; i < lines.length; i++) { + var item = lines[i]; + if (!finishedInitial && (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum))) { + finishedInitial = true; } - if (func.otherStackAllocations) break; - - // Varargs - for (var i = 0; i < lines.length; i++) { - var item = lines[i]; - if (item.intertype == 'call' && isVarArgsFunctionType(item.type)) { - func.otherStackAllocations = true; - break; - } + if (item.intertype == 'alloca' && finishedInitial) { + func.otherStackAllocations = true; + break; } - if (func.otherStackAllocations) break; + } + if (func.otherStackAllocations) break; - break; + // Varargs + for (var i = 0; i < lines.length; i++) { + var item = lines[i]; + if (item.intertype == 'call' && isVarArgsFunctionType(item.type)) { + func.otherStackAllocations = true; + break; + } } - }); - this.forwardItem(data, 'Relooper'); - } - }); + if (func.otherStackAllocations) break; - // ReLooper - reconstruct nice loops, as much as possible - // This is now done in the jsify stage, using compiled relooper2 - substrate.addActor('Relooper', { - processItem: function(item) { - function finish() { - item.__finalResult__ = true; - return [item]; - } - function makeBlock(labels, entries, labelsDict, forceEmulated) { - if (labels.length == 0) return null; - dprint('relooping', 'prelooping: ' + entries + ',' + labels.length + ' labels'); - assert(entries && entries[0]); // need at least 1 entry - - var emulated = { - type: 'emulated', - id: 'B', - labels: labels, - entries: entries.slice(0) - }; - return emulated; + break; } - item.functions.forEach(function(func) { - dprint('relooping', "// relooping function: " + func.ident); - func.block = makeBlock(func.labels, [func.labels[0].ident], func.labelsDict, func.forceEmulated); - }); + }); + } - return finish(); + // ReLooper - reconstruct nice loops, as much as possible + // This is now done in the jsify stage, using compiled relooper2 + function relooper() { + function makeBlock(labels, entries, labelsDict, forceEmulated) { + if (labels.length == 0) return null; + dprint('relooping', 'prelooping: ' + entries + ',' + labels.length + ' labels'); + assert(entries && entries[0]); // need at least 1 entry + + var emulated = { + type: 'emulated', + id: 'B', + labels: labels, + entries: entries.slice(0) + }; + return emulated; } - }); - - // Data - substrate.addItem({ - items: data - }, 'Sorter'); + item.functions.forEach(function(func) { + dprint('relooping', "// relooping function: " + func.ident); + func.block = makeBlock(func.labels, [func.labels[0].ident], func.labelsDict, func.forceEmulated); + }); + } - // Solve it - return substrate.solve(); + // main + castAway(); + legalizer(); + typevestigator(); + analyzeTypes(); + variableAnalyzer(); + signalyzer(); + quantumFixer(); + labelAnalyzer(); + stackAnalyzer(); + relooper(); + + return item; } diff --git a/src/compiler.js b/src/compiler.js index f7c6dd59..90060837 100644 --- a/src/compiler.js +++ b/src/compiler.js @@ -121,6 +121,8 @@ if (typeof print === 'undefined') { // *** Environment setup code *** +DEBUG_MEMORY = false; + // Basic utilities load('utility.js'); @@ -211,15 +213,19 @@ C_DEFINES = temp.defines; // Load compiler code -load('framework.js'); load('modules.js'); load('parseTools.js'); load('intertyper.js'); load('analyzer.js'); load('jsifier.js'); -if (RELOOP) { +if (phase == 'funcs' && RELOOP) { // XXX handle !singlePhase RelooperModule = { TOTAL_MEMORY: ceilPowerOfTwo(2*RELOOPER_BUFFER_SIZE) }; - load(RELOOPER); + try { + load(RELOOPER); + } catch(e) { + printErr('cannot load relooper at ' + RELOOPER + ' : ' + e + ', trying in current dir'); + load('relooper.js'); + } assert(typeof Relooper != 'undefined'); } globalEval(processMacros(preprocess(read('runtime.js')))); @@ -273,6 +279,9 @@ function compile(raw) { intertyped = null; JSify(analyzed); + //dumpInterProf(); + //printErr(phase + ' paths (fast, slow): ' + [fastPaths, slowPaths]); + phase = null; if (DEBUG_MEMORY) { diff --git a/src/compiler_phase.html b/src/compiler_phase.html new file mode 100644 index 00000000..8ca631e8 --- /dev/null +++ b/src/compiler_phase.html @@ -0,0 +1,33 @@ +<html> +<body> +<h2>Run the emscripten compiler in a web page, just for laughs</h2> +Open the web console to see stderr output +<hr> +<pre id="output"></pre> +<script> + arguments = ['tmp/emscripten_temp/tmpbTF9CI.txt', 'tmp/emscripten_temp/tmpz8Yvie.pre.ll', 'pre']; // copy from emscripten.py output + + var outputElement = document.getElementById('output'); + print = function(x) { + outputElement.innerHTML += 'output hidden, profiling mode'; + print = function(){}; + //outputElement.innerHTML += x; + }; + + // For generated code + var Module = { + print: function(x) { + throw 'what?' + } + }; + + var startTime = Date.now(); +</script> +<script src="compiler.js"> +</script> +<script> + outputElement.innerHTML += '<br>total time: ' + (Date.now() - startTime); +</script> +</body> +</html> + diff --git a/src/framework.js b/src/framework.js deleted file mode 100644 index 1a98ca16..00000000 --- a/src/framework.js +++ /dev/null @@ -1,257 +0,0 @@ -//"use strict"; - -// -// A framework to make building Emscripten easier. Lets you write modular -// code to handle specific issues. -// -// Actor - Some code that processes items. -// Item - Some data that is processed by actors. -// Substrate - A 'world' with some actors and some items. -// -// The idea is to create a substrate, fill it with the proper items -// and actors, and then run it to completion. Actors will process -// the items they are given, and forward the results to other actors, -// until we are finished. Some of the results are then the final -// output of the entire calculation done in the substrate. -// - -var DEBUG = 0; -var DEBUG_MEMORY = 0; - -var MemoryDebugger = { - clear: function() { - MemoryDebugger.time = Date.now(); - MemoryDebugger.datas = {}; - var info = MemoryDebugger.doGC(); - MemoryDebugger.last = info[2]; - MemoryDebugger.max = 0; - MemoryDebugger.tick('--clear--'); - }, - - doGC: function() { - var raw = gc().replace('\n', ''); - print('zz raw gc info: ' + raw); - return /before (\d+), after (\d+),.*/.exec(raw); - }, - - tick: function(name) { - var now = Date.now(); - if (now - this.time > 1000) { - // .. - this.time = now; - } - - // assume |name| exists from now on - - var raw = gc().replace('\n', ''); - var info = MemoryDebugger.doGC(); - var before = info[1]; - var after = info[2]; - MemoryDebugger.max = Math.max(MemoryDebugger.max, before, after); - // A GC not called by us may have done some work 'silently' - var garbage = before - after; - var real = after - MemoryDebugger.last; - print('zz gc stats changes: ' + [name, real, garbage]); - MemoryDebugger.last = after; - - if (Math.abs(garbage) + Math.abs(real) > 0) { - var data = MemoryDebugger.datas[name]; - if (!data) { - data = MemoryDebugger.datas[name] = { - name: name, - count: 0, - garbage: 0, - real: 0 - }; - } - data.garbage += garbage; - data.real += real; - data.count++; - } - - MemoryDebugger.dump(); - }, - - dump: function() { - var vals = values(MemoryDebugger.datas); - print('zz max: ' + (MemoryDebugger.max/(1024*1024)).toFixed(3)); - print('zz real:'); - vals.sort(function(x, y) { return y.real - x.real }); - vals.forEach(function(v) { if (Math.abs(v.real) > 1024*1024) print('zz ' + v.name + ' real = ' + (v.real/(1024*1024)).toFixed(3) + ' mb'); }); - print('zz garbage:'); - vals.sort(function(x, y) { return y.garbage - x.garbage }); - vals.forEach(function(v) { if (Math.abs(v.garbage) > 1024*1024) print('zz ' + v.name + ' garbage = ' + (v.garbage/(1024*1024)).toFixed(3) + ' mb'); }); - } -} - -if (DEBUG_MEMORY) MemoryDebugger.clear(); - -function Substrate(name_) { - this.name_ = name_; - this.actors = {}; - this.currUid = 1; -}; - -Substrate.prototype = { - addItem: function(item, targetActor) { - if (targetActor == '/dev/null') return; - if (targetActor == '/dev/stdout') { - this.results.push(item); - return; - } - this.actors[targetActor].inbox.push(item); - }, - - addItems: function(items, targetActor) { - if (targetActor == '/dev/null') return; - if (targetActor == '/dev/stdout') { - this.results = this.results.concat(items); - return; - } - this.actors[targetActor].inbox = this.actors[targetActor].inbox.concat(items); - }, - - checkInbox: function(actor) { - var items = actor.inbox; - for (var i = 0; i < items.length; i++) { - var item = items[i]; - if (!item.__uid__) { - item.__uid__ = this.currUid; - this.currUid ++; - } - } - var MAX_INCOMING = Infinity; - if (MAX_INCOMING == Infinity) { - actor.inbox = []; - actor.items = actor.items.concat(items); - } else { - throw 'Warning: Enter this code at your own risk. It can save memory, but often regresses speed.'; - actor.inbox = items.slice(MAX_INCOMING); - actor.items = actor.items.concat(items.slice(0, MAX_INCOMING)); - } - }, - - addActor: function(name_, actor) { - assert(name_ && actor); - actor.name_ = name_; - actor.items = []; - actor.inbox = []; - actor.forwardItem = bind(this, this.addItem); - actor.forwardItems = bind(this, this.addItems); - this.actors[name_] = actor; - if (!actor.process) actor.process = Actor.prototype.process; - return actor; - }, - - solve: function() { - dprint('framework', "// Solving " + this.name_ + "..."); - - if (DEBUG_MEMORY) MemoryDebugger.tick('pre-run substrate ' + this.name_ + ' (priors may be cleaned)'); - - var startTime = Date.now(); - var midTime = startTime; - var that = this; - function midComment() { - var curr = Date.now(); - if (curr - midTime > 500) { - dprint('framework', '// Working on ' + that.name_ + ', so far ' + ((curr-startTime)/1000).toString().substr(0,10) + ' seconds.'); - midTime = curr; - } - } - function finalComment() { - dprint('framework', '// Completed ' + that.name_ + ' in ' + ((Date.now() - startTime)/1000).toString().substr(0,10) + ' seconds.'); - } - - var ret = null; - var finalResult = null; - this.results = []; - var finished = false; - var onResult = this.onResult; - var actors = values(this.actors); // Assumes actors are not constantly added - while (!finished) { - dprint('framework', "Cycle start, items: ");// + values(this.actors).map(function(actor) actor.items).reduce(function(x,y) x+y, 0)); - var hadProcessing = false; - for (var i = 0; i < actors.length; i++) { - var actor = actors[i]; - if (actor.inbox.length == 0 && actor.items.length == 0) continue; - - midComment(); - - this.checkInbox(actor); - var outputs; - var currResultCount = this.results.length; - dprint('framework', 'Processing using ' + actor.name_ + ': ' + actor.items.length); - outputs = actor.process(actor.items); - actor.items = []; - if (DEBUG_MEMORY) MemoryDebugger.tick('actor ' + actor.name_); - dprint('framework', 'New results: ' + (outputs.length + this.results.length - currResultCount) + ' out of ' + (this.results.length + outputs.length)); - hadProcessing = true; - - if (outputs && outputs.length > 0) { - if (outputs.length === 1 && outputs[0].__finalResult__) { - if (DEBUG) print("Solving complete: __finalResult__"); - delete outputs[0].__finalResult__; // Might recycle this - delete outputs[0].__uid__; - finalComment(); - finished = true; - finalResult = outputs[0]; - } else { - this.results = this.results.concat(outputs); - } - } - } - if (!hadProcessing) { - if (DEBUG) print("Solving complete: no remaining items"); - finalComment(); - this.results.forEach(function(result) { - delete result.__uid__; // Might recycle these - if (onResult) onResult(result); - }); - ret = this.results; - this.results = null; // No need to hold on to them any more - break; - } - if (finalResult) { - ret = finalResult; - break; - } - midComment(); - } - - // Clear the actors. Do not forget about the actors, though, to make this substrate reusable. - actors.forEach(function(actor) { - actor.items = null; - actor.inbox = null; - }); - - if (DEBUG_MEMORY) MemoryDebugger.tick('finished ' + this.name_); - - return ret; - } -}; - -// Global access to the currently-being processed item. -// Note that if you overload process in Actor, this will need to be set if you rely on it. -var Framework = { - currItem: null -}; - -function Actor() { }; -Actor.prototype = { - process: function(items) { - var ret = []; - for (var i = 0; i < items.length; i++) { - var item = items[i]; - items[i] = null; // items may be very very large. Allow GCing to occur in the loop by releasing refs here - dprint('frameworkLines', 'Processing item for llvm line ' + item.lineNum); - Framework.currItem = item; - var outputs = this.processItem(item); - Framework.currItem = null; - if (outputs) { - ret = ret.concat(outputs); - } - } - return ret; - } -}; - diff --git a/src/intertyper.js b/src/intertyper.js index 082fd993..07f2020c 100644 --- a/src/intertyper.js +++ b/src/intertyper.js @@ -3,10 +3,151 @@ // LLVM assembly => internal intermediate representation, which is ready // to be processed by the later stages. -var tokenizer; // TODO: Clean this up/out - // XXX In particular, this closes over the substrate, which can keep stuff in memory, which is bad +var fastPaths = 0, slowPaths = 0; + +// Line tokenizer +function tokenizer(item, inner) { + //assert(item.lineNum != 40000); + //if (item.lineNum) print(item.lineNum); + var tokens = []; + var quotes = 0; + var lastToken = null; + var CHUNKSIZE = 64; // How much forward to peek forward. Too much means too many string segments copied + // Note: '{' is not an encloser, as its use in functions is split over many lines + var enclosers = { + '[': 0, + ']': '[', + '(': 0, + ')': '(', + '<': 0, + '>': '<' + }; + var totalEnclosing = 0; + function makeToken(text) { + if (text.length == 0) return; + // merge certain tokens + if (lastToken && ( (lastToken.text == '%' && text[0] == '"') || /^\**$/.test(text) ) ) { + lastToken.text += text; + return; + } + + var token = { + text: text + }; + if (text[0] in enclosers) { + token.item = tokenizer({ + lineText: text.substr(1, text.length-2) + }, true); + token.type = text[0]; + } + // merge certain tokens + if (lastToken && isType(lastToken.text) && isFunctionDef(token)) { + lastToken.text += ' ' + text; + } else if (lastToken && text[0] == '}') { // }, }*, etc. + var openBrace = tokens.length-1; + while (tokens[openBrace].text.substr(-1) != '{') openBrace --; + token = combineTokens(tokens.slice(openBrace+1)); + tokens.splice(openBrace, tokens.length-openBrace+1); + tokens.push(token); + token.type = '{'; + token.text = '{ ' + token.text + ' }'; + var pointingLevelsToAdd = pointingLevels(text) - pointingLevels(token.text); + while (pointingLevelsToAdd > 0) { + token.text += '*'; + pointingLevelsToAdd--; + } + lastToken = token; + } else { + tokens.push(token); + lastToken = token; + } + } + // Split using meaningful characters + var lineText = item.lineText + ' '; + var re = /[\[\]\(\)<>, "]/g; + var segments = lineText.split(re); + segments.pop(); + var len = segments.length; + var i = -1; + var curr = ''; + var segment, letter; + for (var s = 0; s < len; s++) { + segment = segments[s]; + i += segment.length + 1; + letter = lineText[i]; + curr += segment; + switch (letter) { + case ' ': + if (totalEnclosing == 0 && quotes == 0) { + makeToken(curr); + curr = ''; + } else { + curr += ' '; + } + break; + case '"': + if (totalEnclosing == 0) { + if (quotes == 0) { + if (curr == '@' || curr == '%') { + curr += '"'; + } else { + makeToken(curr); + curr = '"'; + } + } else { + makeToken(curr + '"'); + curr = ''; + } + } else { + curr += '"'; + } + quotes = 1-quotes; + break; + case ',': + if (totalEnclosing == 0 && quotes == 0) { + makeToken(curr); + curr = ''; + tokens.push({ text: ',' }); + } else { + curr += ','; + } + break; + default: + assert(letter in enclosers); + if (quotes) { + curr += letter; + break; + } + if (letter in ENCLOSER_STARTERS) { + if (totalEnclosing == 0) { + makeToken(curr); + curr = ''; + } + curr += letter; + enclosers[letter]++; + totalEnclosing++; + } else { + enclosers[enclosers[letter]]--; + totalEnclosing--; + if (totalEnclosing == 0) { + makeToken(curr + letter); + curr = ''; + } else { + curr += letter; + } + } + } + } + var newItem = { + tokens: tokens, + indent: lineText.search(/[^ ]/), + lineNum: item.lineNum + }; + return newItem; +} + function tokenize(text) { - return tokenizer.processItem({ lineText: text }, true); + return tokenizer({ lineText: text }, true); } // Handy sets @@ -22,672 +163,490 @@ var NSW_NUW = set('nsw', 'nuw'); // Intertyper -function intertyper(data, sidePass, baseLineNums) { +function intertyper(lines, sidePass, baseLineNums) { var mainPass = !sidePass; baseLineNums = baseLineNums || [[0,0]]; // each pair [#0,#1] means "starting from line #0, the base line num is #1" dprint('framework', 'Big picture: Starting intertyper, main pass=' + mainPass); - // Substrate - - var substrate = new Substrate('Intertyper'); + var finalResults = []; - // Line splitter. We break off some bunches of lines into unparsedBundles, which are + // Line splitter. We break off some bunches of lines into unparsed bundles, which are // parsed in separate passes later. This helps to keep memory usage low - we can start // from raw lines and end up with final JS for each function individually that way, instead // of intertyping them all, then analyzing them all, etc. - substrate.addActor('LineSplitter', { - processItem: function _lineSplitter(item) { - var lines = item.llvmLines; - var ret = []; - var inContinual = false; - var inFunction = false; - var currFunctionLines; - var currFunctionLineNum; - var unparsedBundles = []; - var unparsedTypes, unparsedGlobals; - if (mainPass) { - unparsedTypes = { - intertype: 'unparsedTypes', - lines: [] - }; - unparsedBundles.push(unparsedTypes); - unparsedGlobals = { - intertype: 'unparsedGlobals', - lines: [] - }; - unparsedBundles.push(unparsedGlobals); - } - var baseLineNumPosition = 0; - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - if (singlePhase) lines[i] = null; // lines may be very very large. Allow GCing to occur in the loop by releasing refs here + function lineSplitter() { + var ret = []; + var inContinual = false; + var inFunction = false; + var currFunctionLines; + var currFunctionLineNum; + var unparsedTypes, unparsedGlobals; + if (mainPass) { + unparsedTypes = { + intertype: 'unparsedTypes', + lines: [] + }; + finalResults.push(unparsedTypes); + unparsedGlobals = { + intertype: 'unparsedGlobals', + lines: [] + }; + finalResults.push(unparsedGlobals); + } + var baseLineNumPosition = 0; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (singlePhase) lines[i] = null; // lines may be very very large. Allow GCing to occur in the loop by releasing refs here - while (baseLineNumPosition < baseLineNums.length-1 && i >= baseLineNums[baseLineNumPosition+1][0]) { - baseLineNumPosition++; - } + while (baseLineNumPosition < baseLineNums.length-1 && i >= baseLineNums[baseLineNumPosition+1][0]) { + baseLineNumPosition++; + } - if (mainPass && (line[0] == '%' || line[0] == '@')) { - // If this isn't a type, it's a global variable, make a note of the information now, we will need it later - var parts = line.split(' = '); - assert(parts.length >= 2); - var left = parts[0], right = parts.slice(1).join(' = '); - var testType = /^type .*/.exec(right); - if (!testType) { - var globalIdent = toNiceIdent(left); - var testAlias = /^(hidden )?alias .*/.exec(right); - Variables.globals[globalIdent] = { - name: globalIdent, - alias: !!testAlias, - impl: VAR_EMULATED - }; - unparsedGlobals.lines.push(line); - } else { - unparsedTypes.lines.push(line); - } - continue; - } - if (mainPass && /^define .*/.test(line)) { - inFunction = true; - currFunctionLines = []; - currFunctionLineNum = i + 1; + if (mainPass && (line[0] == '%' || line[0] == '@')) { + // If this isn't a type, it's a global variable, make a note of the information now, we will need it later + var parts = line.split(' = '); + assert(parts.length >= 2); + var left = parts[0], right = parts.slice(1).join(' = '); + var testType = /^type .*/.exec(right); + if (!testType) { + var globalIdent = toNiceIdent(left); + var testAlias = /^(hidden )?alias .*/.exec(right); + Variables.globals[globalIdent] = { + name: globalIdent, + alias: !!testAlias, + impl: VAR_EMULATED + }; + unparsedGlobals.lines.push(line); + } else { + unparsedTypes.lines.push(line); } - if (!inFunction || !mainPass) { - if (inContinual || /^\ +(to|catch |filter |cleanup).*/.test(line)) { - // to after invoke or landingpad second line - ret.slice(-1)[0].lineText += line; - if (/^\ +\]/.test(line)) { // end of llvm switch - inContinual = false; - } - } else { - ret.push({ - lineText: line, - lineNum: i + 1 + baseLineNums[baseLineNumPosition][1] - baseLineNums[baseLineNumPosition][0] - }); - if (/^\ +switch\ .*/.test(line)) { - // beginning of llvm switch - inContinual = true; - } + continue; + } + if (mainPass && /^define .*/.test(line)) { + inFunction = true; + currFunctionLines = []; + currFunctionLineNum = i + 1; + } + if (!inFunction || !mainPass) { + if (inContinual || /^\ +(to|catch |filter |cleanup).*/.test(line)) { + // to after invoke or landingpad second line + ret.slice(-1)[0].lineText += line; + if (/^\ +\]/.test(line)) { // end of llvm switch + inContinual = false; } } else { - currFunctionLines.push(line); - } - if (mainPass && /^}.*/.test(line)) { - inFunction = false; - if (mainPass) { - var func = funcHeader.processItem(tokenizer.processItem({ lineText: currFunctionLines[0], lineNum: currFunctionLineNum }, true))[0]; - - if (SKIP_STACK_IN_SMALL && /emscripten_autodebug/.exec(func.ident)) { - warnOnce('Disabling SKIP_STACK_IN_SMALL because we are apparently processing autodebugger data'); - SKIP_STACK_IN_SMALL = 0; - } - - var ident = toNiceIdent(func.ident); - if (!(ident in DEAD_FUNCTIONS)) { - unparsedBundles.push({ - intertype: 'unparsedFunction', - // We need this early, to know basic function info - ident, params, varargs - ident: ident, - params: func.params, - returnType: func.returnType, - hasVarArgs: func.hasVarArgs, - lineNum: currFunctionLineNum, - lines: currFunctionLines - }); - } - currFunctionLines = []; + ret.push({ + lineText: line, + lineNum: i + 1 + baseLineNums[baseLineNumPosition][1] - baseLineNums[baseLineNumPosition][0] + }); + if (/^\ +switch\ .*/.test(line)) { + // beginning of llvm switch + inContinual = true; } } + } else { + currFunctionLines.push(line); } - // We need lines beginning with ';' inside functions, because older LLVM versions generated labels that way. But when not - // parsing functions, we can ignore all such lines and save some time that way. - this.forwardItems(ret.filter(function(item) { return item.lineText && (item.lineText[0] != ';' || !mainPass); }), 'Tokenizer'); - return unparsedBundles; - } - }); + if (mainPass && /^}.*/.test(line)) { + inFunction = false; + if (mainPass) { + var func = funcHeaderHandler(tokenizer({ lineText: currFunctionLines[0], lineNum: currFunctionLineNum }, true)); - // Line tokenizer - tokenizer = substrate.addActor('Tokenizer', { - processItem: function _tokenizer(item, inner) { - //assert(item.lineNum != 40000); - //if (item.lineNum) print(item.lineNum); - var tokens = []; - var quotes = 0; - var lastToken = null; - var CHUNKSIZE = 64; // How much forward to peek forward. Too much means too many string segments copied - // Note: '{' is not an encloser, as its use in functions is split over many lines - var enclosers = { - '[': 0, - ']': '[', - '(': 0, - ')': '(', - '<': 0, - '>': '<' - }; - var totalEnclosing = 0; - var that = this; - function makeToken(text) { - if (text.length == 0) return; - // merge certain tokens - if (lastToken && ( (lastToken.text == '%' && text[0] == '"') || /^\**$/.test(text) ) ) { - lastToken.text += text; - return; - } + if (SKIP_STACK_IN_SMALL && /emscripten_autodebug/.exec(func.ident)) { + warnOnce('Disabling SKIP_STACK_IN_SMALL because we are apparently processing autodebugger data'); + SKIP_STACK_IN_SMALL = 0; + } - var token = { - text: text - }; - if (text[0] in enclosers) { - token.item = that.processItem({ - lineText: text.substr(1, text.length-2) - }, true); - token.type = text[0]; - } - // merge certain tokens - if (lastToken && isType(lastToken.text) && isFunctionDef(token)) { - lastToken.text += ' ' + text; - } else if (lastToken && text[0] == '}') { // }, }*, etc. - var openBrace = tokens.length-1; - while (tokens[openBrace].text.substr(-1) != '{') openBrace --; - token = combineTokens(tokens.slice(openBrace+1)); - tokens.splice(openBrace, tokens.length-openBrace+1); - tokens.push(token); - token.type = '{'; - token.text = '{ ' + token.text + ' }'; - var pointingLevelsToAdd = pointingLevels(text) - pointingLevels(token.text); - while (pointingLevelsToAdd > 0) { - token.text += '*'; - pointingLevelsToAdd--; + var ident = toNiceIdent(func.ident); + if (!(ident in DEAD_FUNCTIONS)) { + finalResults.push({ + intertype: 'unparsedFunction', + // We need this early, to know basic function info - ident, params, varargs + ident: ident, + params: func.params, + returnType: func.returnType, + hasVarArgs: func.hasVarArgs, + lineNum: currFunctionLineNum, + lines: currFunctionLines + }); } - lastToken = token; - } else { - tokens.push(token); - lastToken = token; - } - } - // Split using meaningful characters - var lineText = item.lineText + ' '; - var re = /[\[\]\(\)<>, "]/g; - var segments = lineText.split(re); - segments.pop(); - var len = segments.length; - var i = -1; - var curr = ''; - var segment, letter; - for (var s = 0; s < len; s++) { - segment = segments[s]; - i += segment.length + 1; - letter = lineText[i]; - curr += segment; - switch (letter) { - case ' ': - if (totalEnclosing == 0 && quotes == 0) { - makeToken(curr); - curr = ''; - } else { - curr += ' '; - } - break; - case '"': - if (totalEnclosing == 0) { - if (quotes == 0) { - if (curr == '@' || curr == '%') { - curr += '"'; - } else { - makeToken(curr); - curr = '"'; - } - } else { - makeToken(curr + '"'); - curr = ''; - } - } else { - curr += '"'; - } - quotes = 1-quotes; - break; - case ',': - if (totalEnclosing == 0 && quotes == 0) { - makeToken(curr); - curr = ''; - tokens.push({ text: ',' }); - } else { - curr += ','; - } - break; - default: - assert(letter in enclosers); - if (quotes) { - curr += letter; - break; - } - if (letter in ENCLOSER_STARTERS) { - if (totalEnclosing == 0) { - makeToken(curr); - curr = ''; - } - curr += letter; - enclosers[letter]++; - totalEnclosing++; - } else { - enclosers[enclosers[letter]]--; - totalEnclosing--; - if (totalEnclosing == 0) { - makeToken(curr + letter); - curr = ''; - } else { - curr += letter; - } - } + currFunctionLines = []; } } - var newItem = { - tokens: tokens, - indent: lineText.search(/[^ ]/), - lineNum: item.lineNum - }; - if (inner) { - return newItem; - } else { - this.forwardItem(newItem, 'Triager'); - } - return null; } - }); + // We need lines beginning with ';' inside functions, because older LLVM versions generated labels that way. But when not + // parsing functions, we can ignore all such lines and save some time that way. + return ret.filter(function(item) { return item.lineText && (item.lineText[0] != ';' || !mainPass); }); + } - substrate.addActor('Triager', { - processItem: function _triager(item) { - function triage() { - assert(!item.intertype); - var token0Text = item.tokens[0].text; - var token1Text = item.tokens[1] ? item.tokens[1].text : null; - var tokensLength = item.tokens.length; - if (item.indent === 2) { - if (tokensLength >= 5 && - (token0Text == 'store' || token1Text == 'store')) - return 'Store'; - if (tokensLength >= 3 && token0Text == 'br') - return 'Branch'; - if (tokensLength >= 2 && token0Text == 'ret') - return 'Return'; - if (tokensLength >= 2 && token0Text == 'switch') - return 'Switch'; - if (token0Text == 'unreachable') - return 'Unreachable'; - if (tokensLength >= 3 && token0Text == 'indirectbr') - return 'IndirectBr'; - if (tokensLength >= 2 && token0Text == 'resume') - return 'Resume'; - if (tokensLength >= 3 && - (token0Text == 'load' || token1Text == 'load')) - return 'Load'; - if (tokensLength >= 3 && - token0Text in MATHOPS) - return 'Mathops'; - if (tokensLength >= 3 && token0Text == 'bitcast') - return 'Bitcast'; - if (tokensLength >= 3 && token0Text == 'getelementptr') - return 'GEP'; - if (tokensLength >= 2 && token0Text == 'alloca') - return 'Alloca'; - if (tokensLength >= 3 && token0Text == 'extractvalue') - return 'ExtractValue'; - if (tokensLength >= 3 && token0Text == 'insertvalue') - return 'InsertValue'; - if (tokensLength >= 3 && token0Text == 'phi') - return 'Phi'; - if (tokensLength >= 3 && token0Text == 'va_arg') - return 'va_arg'; - if (tokensLength >= 3 && token0Text == 'landingpad') - return 'Landingpad'; - if (token0Text == 'fence') - return '/dev/null'; - } else if (item.indent === 0) { - if ((tokensLength >= 1 && token0Text.substr(-1) == ':') || - (tokensLength >= 3 && token1Text == '<label>') || - (tokensLength >= 2 && token1Text == ':')) - return 'Label'; - if (tokensLength >= 4 && token0Text == 'declare') - return 'External'; - if (tokensLength >= 3 && token1Text == '=') - return 'Global'; - if (tokensLength >= 4 && token0Text == 'define' && - item.tokens.slice(-1)[0].text == '{') - return 'FuncHeader'; - if (tokensLength >= 1 && token0Text == '}') - return 'FuncEnd'; - if (token0Text == 'module' && token1Text == 'asm') { - warn('Ignoring module asm: ' + item.tokens[2].text); - return '/dev/null'; - } - if (token0Text == 'attributes') - return '/dev/null'; - } - if (tokensLength >= 3 && (token0Text == 'call' || token1Text == 'call')) - return 'Call'; - if (token0Text == 'target') { - if (token1Text == 'triple') { - var triple = item.tokens[3].text; - triple = triple.substr(1, triple.length-2); - var expected = TARGET_LE32 ? 'le32-unknown-nacl' : 'i386-pc-linux-gnu'; - if (triple !== expected) { - warn('using an unexpected LLVM triple: ' + [triple, ' !== ', expected] + ' (are you using emcc for everything and not clang?)'); - } - } - return '/dev/null'; - } - if (token0Text == ';') - return '/dev/null'; - if (tokensLength >= 3 && token0Text == 'invoke') - return 'Invoke'; - if (tokensLength >= 3 && token0Text == 'atomicrmw' || token0Text == 'cmpxchg') - return 'Atomic'; - throw 'Invalid token, cannot triage: ' + dump(item); + function triager(item) { + assert(!item.intertype); + if (item.indent == 2 && (eq = findTokenText(item, '=')) >= 0) { + item.assignTo = toNiceIdent(combineTokens(item.tokens.slice(0, eq)).text); + item.tokens = item.tokens.slice(eq+1); + } + var token0Text = item.tokens[0].text; + var token1Text = item.tokens[1] ? item.tokens[1].text : null; + var tokensLength = item.tokens.length; + if (item.indent === 2) { + if (tokensLength >= 5 && + (token0Text == 'store' || token1Text == 'store')) + return storeHandler(item); + if (tokensLength >= 3 && token0Text == 'br') + return branchHandler(item); + if (tokensLength >= 2 && token0Text == 'ret') + return returnHandler(item); + if (tokensLength >= 2 && token0Text == 'switch') + return switchHandler(item); + if (token0Text == 'unreachable') + return unreachableHandler(item); + if (tokensLength >= 3 && token0Text == 'indirectbr') + return indirectBrHandler(item); + if (tokensLength >= 2 && token0Text == 'resume') + return resumeHandler(item); + if (tokensLength >= 3 && + (token0Text == 'load' || token1Text == 'load')) + return loadHandler(item); + if (tokensLength >= 3 && + token0Text in MATHOPS) + return mathopsHandler(item); + if (tokensLength >= 3 && token0Text == 'bitcast') + return bitcastHandler(item); + if (tokensLength >= 3 && token0Text == 'getelementptr') + return GEPHandler(item); + if (tokensLength >= 2 && token0Text == 'alloca') + return allocaHandler(item); + if (tokensLength >= 3 && token0Text == 'extractvalue') + return extractValueHandler(item); + if (tokensLength >= 3 && token0Text == 'insertvalue') + return insertValueHandler(item); + if (tokensLength >= 3 && token0Text == 'phi') + return phiHandler(item); + if (tokensLength >= 3 && token0Text == 'va_arg') + return va_argHandler(item); + if (tokensLength >= 3 && token0Text == 'landingpad') + return landingpadHandler(item); + if (token0Text == 'fence') + return null; + } else if (item.indent === 0) { + if ((tokensLength >= 1 && token0Text.substr(-1) == ':') || + (tokensLength >= 3 && token1Text == '<label>') || + (tokensLength >= 2 && token1Text == ':')) + return labelHandler(item); + if (tokensLength >= 4 && token0Text == 'declare') + return externalHandler(item); + if (tokensLength >= 3 && token1Text == '=') + return globalHandler(item); + if (tokensLength >= 4 && token0Text == 'define' && + item.tokens.slice(-1)[0].text == '{') + return funcHeaderHandler(item); + if (tokensLength >= 1 && token0Text == '}') + return funcEndHandler(item); + if (token0Text == 'module' && token1Text == 'asm') { + warn('Ignoring module asm: ' + item.tokens[2].text); + return null; } - var eq; - if (item.indent == 2 && (eq = findTokenText(item, '=')) >= 0) { - item.assignTo = toNiceIdent(combineTokens(item.tokens.slice(0, eq)).text); - item.tokens = item.tokens.slice(eq+1); + if (token0Text == 'attributes') + return null; + } + if (tokensLength >= 3 && (token0Text == 'call' || token1Text == 'call')) + return callHandler(item); + if (token0Text == 'target') { + if (token1Text == 'triple') { + var triple = item.tokens[3].text; + triple = triple.substr(1, triple.length-2); + var expected = TARGET_LE32 ? 'le32-unknown-nacl' : 'i386-pc-linux-gnu'; + if (triple !== expected) { + warn('using an unexpected LLVM triple: ' + [triple, ' !== ', expected] + ' (are you using emcc for everything and not clang?)'); + } } - this.forwardItem(item, triage()); + return null; } - }); + if (token0Text == ';') + return null; + if (tokensLength >= 3 && token0Text == 'invoke') + return invokeHandler(item); + if (tokensLength >= 3 && token0Text == 'atomicrmw' || token0Text == 'cmpxchg') + return atomicHandler(item); + throw 'Invalid token, cannot triage: ' + dump(item); + } // Line parsers to intermediate form // globals: type or variable - substrate.addActor('Global', { - processItem: function _global(item) { - function scanConst(value, type) { - // Gets an array of constant items, separated by ',' tokens - function handleSegments(tokens) { - // Handle a single segment (after comma separation) - function handleSegment(segment) { - if (segment[1].text == 'null') { - return { intertype: 'value', ident: '0', type: 'i32' }; - } else if (segment[1].text == 'zeroinitializer') { - Types.needAnalysis[segment[0].text] = 0; - return { intertype: 'emptystruct', type: segment[0].text }; - } else if (segment[1].text in PARSABLE_LLVM_FUNCTIONS) { - return parseLLVMFunctionCall(segment); - } else if (segment[1].type && segment[1].type == '{') { - Types.needAnalysis[segment[0].text] = 0; - return { intertype: 'struct', type: segment[0].text, contents: handleSegments(segment[1].tokens) }; - } else if (segment[1].type && segment[1].type == '<') { - Types.needAnalysis[segment[0].text] = 0; - return { intertype: 'struct', type: segment[0].text, contents: handleSegments(segment[1].item.tokens[0].tokens) }; - } else if (segment[1].type && segment[1].type == '[') { - Types.needAnalysis[segment[0].text] = 0; - return { intertype: 'list', type: segment[0].text, contents: handleSegments(segment[1].item.tokens) }; - } else if (segment.length == 2) { - Types.needAnalysis[segment[0].text] = 0; - return { intertype: 'value', type: segment[0].text, ident: toNiceIdent(segment[1].text) }; - } else if (segment[1].text === 'c') { - // string - var text = segment[2].text; - text = text.substr(1, text.length-2); - return { intertype: 'string', text: text, type: 'i8*' }; - } else if (segment[1].text === 'blockaddress') { - return parseBlockAddress(segment); - } else { - throw 'Invalid segment: ' + dump(segment); - } - }; - return splitTokenList(tokens).map(handleSegment); - } + function noteGlobalVariable(ret) { + if (!NAMED_GLOBALS) { + Variables.globals[ret.ident].type = ret.type; + Variables.globals[ret.ident].external = ret.external; + } + Types.needAnalysis[ret.type] = 0; + } - Types.needAnalysis[type] = 0; - if (Runtime.isNumberType(type) || pointingLevels(type) >= 1) { - return { value: toNiceIdent(value.text), type: type }; - } else if (value.text in ZEROINIT_UNDEF) { // undef doesn't really need initting, but why not - return { intertype: 'emptystruct', type: type }; - } else if (value.text && value.text[0] == '"') { - return { intertype: 'string', text: value.text.substr(1, value.text.length-2) }; - } else { - if (value.type == '<') { // <{ i8 }> etc. - value = value.item.tokens; - } - var contents; - if (value.item) { - // list of items - contents = value.item.tokens; - } else if (value.type == '{') { - // struct - contents = value.tokens; - } else if (value[0]) { - contents = value[0]; + function globalHandler(item) { + function scanConst(value, type) { + // Gets an array of constant items, separated by ',' tokens + function handleSegments(tokens) { + // Handle a single segment (after comma separation) + function handleSegment(segment) { + if (segment[1].text == 'null') { + return { intertype: 'value', ident: '0', type: 'i32' }; + } else if (segment[1].text == 'zeroinitializer') { + Types.needAnalysis[segment[0].text] = 0; + return { intertype: 'emptystruct', type: segment[0].text }; + } else if (segment[1].text in PARSABLE_LLVM_FUNCTIONS) { + return parseLLVMFunctionCall(segment); + } else if (segment[1].type && segment[1].type == '{') { + Types.needAnalysis[segment[0].text] = 0; + return { intertype: 'struct', type: segment[0].text, contents: handleSegments(segment[1].tokens) }; + } else if (segment[1].type && segment[1].type == '<') { + Types.needAnalysis[segment[0].text] = 0; + return { intertype: 'struct', type: segment[0].text, contents: handleSegments(segment[1].item.tokens[0].tokens) }; + } else if (segment[1].type && segment[1].type == '[') { + Types.needAnalysis[segment[0].text] = 0; + return { intertype: 'list', type: segment[0].text, contents: handleSegments(segment[1].item.tokens) }; + } else if (segment.length == 2) { + Types.needAnalysis[segment[0].text] = 0; + return { intertype: 'value', type: segment[0].text, ident: toNiceIdent(segment[1].text) }; + } else if (segment[1].text === 'c') { + // string + var text = segment[2].text; + text = text.substr(1, text.length-2); + return { intertype: 'string', text: text, type: 'i8*' }; + } else if (segment[1].text === 'blockaddress') { + return parseBlockAddress(segment); } else { - throw '// interfailzzzzzzzzzzzzzz ' + dump(value.item) + ' ::: ' + dump(value); + throw 'Invalid segment: ' + dump(segment); } - return { intertype: 'segments', contents: handleSegments(contents) }; - } + }; + return splitTokenList(tokens).map(handleSegment); } - cleanOutTokens(LLVM.VISIBILITIES, item.tokens, 2); - if (item.tokens[2].text == 'alias') { - cleanOutTokens(LLVM.LINKAGES, item.tokens, 3); - cleanOutTokens(LLVM.VISIBILITIES, item.tokens, 3); - var last = getTokenIndexByText(item.tokens, ';'); - var ret = { - intertype: 'alias', - ident: toNiceIdent(item.tokens[0].text), - value: parseLLVMSegment(item.tokens.slice(3, last)), - lineNum: item.lineNum - }; - ret.type = ret.value.type; - Types.needAnalysis[ret.type] = 0; - if (!NAMED_GLOBALS) { - Variables.globals[ret.ident].type = ret.type; + Types.needAnalysis[type] = 0; + if (Runtime.isNumberType(type) || pointingLevels(type) >= 1) { + return { value: toNiceIdent(value.text), type: type }; + } else if (value.text in ZEROINIT_UNDEF) { // undef doesn't really need initting, but why not + return { intertype: 'emptystruct', type: type }; + } else if (value.text && value.text[0] == '"') { + return { intertype: 'string', text: value.text.substr(1, value.text.length-2) }; + } else { + if (value.type == '<') { // <{ i8 }> etc. + value = value.item.tokens; + } + var contents; + if (value.item) { + // list of items + contents = value.item.tokens; + } else if (value.type == '{') { + // struct + contents = value.tokens; + } else if (value[0]) { + contents = value[0]; + } else { + throw '// interfailzzzzzzzzzzzzzz ' + dump(value.item) + ' ::: ' + dump(value); } - return [ret]; + return { intertype: 'segments', contents: handleSegments(contents) }; } - if (item.tokens[2].text == 'type') { - var fields = []; - var packed = false; - if (Runtime.isNumberType(item.tokens[3].text)) { - // Clang sometimes has |= i32| instead of |= { i32 }| - fields = [item.tokens[3].text]; - } else if (item.tokens[3].text != 'opaque') { - if (item.tokens[3].type == '<') { - packed = true; - item.tokens[3] = item.tokens[3].item.tokens[0]; - } - var subTokens = item.tokens[3].tokens; - if (subTokens) { - subTokens.push({text:','}); - while (subTokens[0]) { - var stop = 1; - while ([','].indexOf(subTokens[stop].text) == -1) stop ++; - fields.push(combineTokens(subTokens.slice(0, stop)).text); - subTokens.splice(0, stop+1); - } - } + } + + cleanOutTokens(LLVM.VISIBILITIES, item.tokens, 2); + if (item.tokens[2].text == 'alias') { + cleanOutTokens(LLVM.LINKAGES, item.tokens, 3); + cleanOutTokens(LLVM.VISIBILITIES, item.tokens, 3); + var last = getTokenIndexByText(item.tokens, ';'); + var ret = { + intertype: 'alias', + ident: toNiceIdent(item.tokens[0].text), + value: parseLLVMSegment(item.tokens.slice(3, last)), + lineNum: item.lineNum + }; + ret.type = ret.value.type; + Types.needAnalysis[ret.type] = 0; + if (!NAMED_GLOBALS) { + Variables.globals[ret.ident].type = ret.type; + } + return ret; + } + if (item.tokens[2].text == 'type') { + var fields = []; + var packed = false; + if (Runtime.isNumberType(item.tokens[3].text)) { + // Clang sometimes has |= i32| instead of |= { i32 }| + fields = [item.tokens[3].text]; + } else if (item.tokens[3].text != 'opaque') { + if (item.tokens[3].type == '<') { + packed = true; + item.tokens[3] = item.tokens[3].item.tokens[0]; } - return [{ - intertype: 'type', - name_: item.tokens[0].text, - fields: fields, - packed: packed, - lineNum: item.lineNum - }]; - } else { - // variable - var ident = item.tokens[0].text; - var private_ = findTokenText(item, 'private') >= 0 || findTokenText(item, 'internal') >= 0; - var named = findTokenText(item, 'unnamed_addr') < 0; - cleanOutTokens(LLVM.GLOBAL_MODIFIERS, item.tokens, [2, 3]); - var external = false; - if (item.tokens[2].text === 'external') { - external = true; - item.tokens.splice(2, 1); + var subTokens = item.tokens[3].tokens; + if (subTokens) { + subTokens.push({text:','}); + while (subTokens[0]) { + var stop = 1; + while ([','].indexOf(subTokens[stop].text) == -1) stop ++; + fields.push(combineTokens(subTokens.slice(0, stop)).text); + subTokens.splice(0, stop+1); + } } - Types.needAnalysis[item.tokens[2].text] = 0; - var ret = { - intertype: 'globalVariable', - ident: toNiceIdent(ident), - type: item.tokens[2].text, - external: external, - private_: private_, - named: named, - lineNum: item.lineNum - }; - if (!NAMED_GLOBALS) { - Variables.globals[ret.ident].type = ret.type; - Variables.globals[ret.ident].external = external; + } + return { + intertype: 'type', + name_: item.tokens[0].text, + fields: fields, + packed: packed, + lineNum: item.lineNum + }; + } else { + // variable + var ident = item.tokens[0].text; + var private_ = findTokenText(item, 'private') >= 0 || findTokenText(item, 'internal') >= 0; + var named = findTokenText(item, 'unnamed_addr') < 0; + cleanOutTokens(LLVM.GLOBAL_MODIFIERS, item.tokens, [2, 3]); + var external = false; + if (item.tokens[2].text === 'external') { + external = true; + item.tokens.splice(2, 1); + } + var ret = { + intertype: 'globalVariable', + ident: toNiceIdent(ident), + type: item.tokens[2].text, + external: external, + private_: private_, + named: named, + lineNum: item.lineNum + }; + noteGlobalVariable(ret); + if (ident == '@llvm.global_ctors') { + ret.ctors = []; + if (item.tokens[3].item) { + var subTokens = item.tokens[3].item.tokens; + splitTokenList(subTokens).forEach(function(segment) { + var ctor = toNiceIdent(segment[1].tokens.slice(-1)[0].text); + ret.ctors.push(ctor); + if (ASM_JS) { // must export the global constructors from asm.js module, so mark as implemented and exported + Functions.implementedFunctions[ctor] = 'v'; + EXPORTED_FUNCTIONS[ctor] = 1; + } + }); } - Types.needAnalysis[ret.type] = 0; - if (ident == '@llvm.global_ctors') { - ret.ctors = []; - if (item.tokens[3].item) { - var subTokens = item.tokens[3].item.tokens; - splitTokenList(subTokens).forEach(function(segment) { - var ctor = toNiceIdent(segment[1].tokens.slice(-1)[0].text); - ret.ctors.push(ctor); - if (ASM_JS) { // must export the global constructors from asm.js module, so mark as implemented and exported - Functions.implementedFunctions[ctor] = 'v'; - EXPORTED_FUNCTIONS[ctor] = 1; - } - }); + } else if (!external) { + if (item.tokens[3] && item.tokens[3].text != ';') { + if (item.tokens[3].text == 'c') { + item.tokens.splice(3, 1); } - } else if (!external) { - if (item.tokens[3] && item.tokens[3].text != ';') { - if (item.tokens[3].text == 'c') { - item.tokens.splice(3, 1); - } - if (item.tokens[3].text in PARSABLE_LLVM_FUNCTIONS) { - ret.value = parseLLVMFunctionCall(item.tokens.slice(2)); - } else { - ret.value = scanConst(item.tokens[3], ret.type); - } + if (item.tokens[3].text in PARSABLE_LLVM_FUNCTIONS) { + ret.value = parseLLVMFunctionCall(item.tokens.slice(2)); } else { - ret.value = { intertype: 'value', ident: '0', value: '0', type: ret.type }; + ret.value = scanConst(item.tokens[3], ret.type); } + } else { + ret.value = { intertype: 'value', ident: '0', value: '0', type: ret.type }; } - return [ret]; } + return ret; } - }); + } // function header - var funcHeader = substrate.addActor('FuncHeader', { - processItem: function(item) { - item.tokens = item.tokens.filter(function(token) { - return !(token.text in LLVM.LINKAGES || token.text in LLVM.PARAM_ATTR || token.text in LLVM.FUNC_ATTR || token.text in LLVM.CALLING_CONVENTIONS); - }); - var params = parseParamTokens(item.tokens[2].item.tokens); - if (sidePass) dprint('unparsedFunctions', 'Processing function: ' + item.tokens[1].text); - return [{ - intertype: 'function', - ident: toNiceIdent(item.tokens[1].text), - returnType: item.tokens[0].text, - params: params, - hasVarArgs: hasVarArgs(params), - lineNum: item.lineNum, - }]; - } - }); + function funcHeaderHandler(item) { + item.tokens = item.tokens.filter(function(token) { + return !(token.text in LLVM.LINKAGES || token.text in LLVM.PARAM_ATTR || token.text in LLVM.FUNC_ATTR || token.text in LLVM.CALLING_CONVENTIONS); + }); + var params = parseParamTokens(item.tokens[2].item.tokens); + if (sidePass) dprint('unparsedFunctions', 'Processing function: ' + item.tokens[1].text); + return { + intertype: 'function', + ident: toNiceIdent(item.tokens[1].text), + returnType: item.tokens[0].text, + params: params, + hasVarArgs: hasVarArgs(params), + lineNum: item.lineNum, + }; + } // label - substrate.addActor('Label', { - processItem: function(item) { - var rawLabel = item.tokens[0].text.substr(-1) == ':' ? - '%' + item.tokens[0].text.substr(0, item.tokens[0].text.length-1) : - (item.tokens[1].text == '<label>' ? - '%' + item.tokens[2].text.substr(1) : - '%' + item.tokens[0].text) - var niceLabel = toNiceIdent(rawLabel); - return [{ - intertype: 'label', - ident: niceLabel, - lineNum: item.lineNum - }]; - } - }); - - // TODO: remove dis - substrate.addActor('Reintegrator', { - processItem: function(item) { - this.forwardItem(item, '/dev/stdout'); - } - }); + function labelHandler(item) { + var rawLabel = item.tokens[0].text.substr(-1) == ':' ? + '%' + item.tokens[0].text.substr(0, item.tokens[0].text.length-1) : + (item.tokens[1].text == '<label>' ? + '%' + item.tokens[2].text.substr(1) : + '%' + item.tokens[0].text) + var niceLabel = toNiceIdent(rawLabel); + return { + intertype: 'label', + ident: niceLabel, + lineNum: item.lineNum + }; + } // 'load' - substrate.addActor('Load', { - processItem: function(item) { - item.intertype = 'load'; - cleanOutTokens(LLVM.ACCESS_OPTIONS, item.tokens, [0, 1]); - item.pointerType = item.tokens[1].text; - item.valueType = item.type = removePointing(item.pointerType); - Types.needAnalysis[item.type] = 0; - var last = getTokenIndexByText(item.tokens, ';'); - var segments = splitTokenList(item.tokens.slice(1, last)); - item.pointer = parseLLVMSegment(segments[0]); - if (segments.length > 1) { - assert(segments[1][0].text == 'align'); - item.align = parseInt(segments[1][1].text) || QUANTUM_SIZE; // 0 means preferred arch align - } else { - item.align = QUANTUM_SIZE; - } - item.ident = item.pointer.ident || null; - this.forwardItem(item, 'Reintegrator'); + function loadHandler(item) { + item.intertype = 'load'; + cleanOutTokens(LLVM.ACCESS_OPTIONS, item.tokens, [0, 1]); + item.pointerType = item.tokens[1].text; + item.valueType = item.type = removePointing(item.pointerType); + Types.needAnalysis[item.type] = 0; + var last = getTokenIndexByText(item.tokens, ';'); + var segments = splitTokenList(item.tokens.slice(1, last)); + item.pointer = parseLLVMSegment(segments[0]); + if (segments.length > 1) { + assert(segments[1][0].text == 'align'); + item.align = parseInt(segments[1][1].text) || QUANTUM_SIZE; // 0 means preferred arch align + } else { + item.align = QUANTUM_SIZE; } - }); + item.ident = item.pointer.ident || null; + return item; + } // 'extractvalue' - substrate.addActor('ExtractValue', { - processItem: function(item) { - var last = getTokenIndexByText(item.tokens, ';'); - item.intertype = 'extractvalue'; - item.type = item.tokens[1].text; // Of the origin aggregate - not what we extract from it. For that, can only infer it later - Types.needAnalysis[item.type] = 0; - item.ident = toNiceIdent(item.tokens[2].text); - item.indexes = splitTokenList(item.tokens.slice(4, last)); - this.forwardItem(item, 'Reintegrator'); - } - }); + function extractValueHandler(item) { + var last = getTokenIndexByText(item.tokens, ';'); + item.intertype = 'extractvalue'; + item.type = item.tokens[1].text; // Of the origin aggregate - not what we extract from it. For that, can only infer it later + Types.needAnalysis[item.type] = 0; + item.ident = toNiceIdent(item.tokens[2].text); + item.indexes = splitTokenList(item.tokens.slice(4, last)); + return item; + } // 'insertvalue' - substrate.addActor('InsertValue', { - processItem: function(item) { - var last = getTokenIndexByText(item.tokens, ';'); - item.intertype = 'insertvalue'; - item.type = item.tokens[1].text; // Of the origin aggregate, as well as the result - Types.needAnalysis[item.type] = 0; - item.ident = toNiceIdent(item.tokens[2].text); - var segments = splitTokenList(item.tokens.slice(4, last)); - item.value = parseLLVMSegment(segments[0]); - item.indexes = segments.slice(1); - this.forwardItem(item, 'Reintegrator'); - } - }); + function insertValueHandler(item) { + var last = getTokenIndexByText(item.tokens, ';'); + item.intertype = 'insertvalue'; + item.type = item.tokens[1].text; // Of the origin aggregate, as well as the result + Types.needAnalysis[item.type] = 0; + item.ident = toNiceIdent(item.tokens[2].text); + var segments = splitTokenList(item.tokens.slice(4, last)); + item.value = parseLLVMSegment(segments[0]); + item.indexes = segments.slice(1); + return item; + } // 'bitcast' - substrate.addActor('Bitcast', { - processItem: function(item) { - item.intertype = 'bitcast'; - item.type = item.tokens[4].text; // The final type - Types.needAnalysis[item.type] = 0; - var to = getTokenIndexByText(item.tokens, 'to'); - item.params = [parseLLVMSegment(item.tokens.slice(1, to))]; - item.ident = item.params[0].ident; - item.type2 = item.tokens[1].text; // The original type - Types.needAnalysis[item.type2] = 0; - this.forwardItem(item, 'Reintegrator'); - } - }); + function bitcastHandler(item) { + item.intertype = 'bitcast'; + item.type = item.tokens[4].text; // The final type + Types.needAnalysis[item.type] = 0; + var to = getTokenIndexByText(item.tokens, 'to'); + item.params = [parseLLVMSegment(item.tokens.slice(1, to))]; + item.ident = item.params[0].ident; + item.type2 = item.tokens[1].text; // The original type + Types.needAnalysis[item.type2] = 0; + return item; + } // 'getelementptr' - substrate.addActor('GEP', { - processItem: function(item) { - var first = 0; - while (!isType(item.tokens[first].text)) first++; - Types.needAnalysis[item.tokens[first].text] = 0; - var last = getTokenIndexByText(item.tokens, ';'); - var segment = [ item.tokens[first], { text: 'getelementptr' }, null, { item: { - tokens: item.tokens.slice(first, last) - } } ]; - var data = parseLLVMFunctionCall(segment); - item.intertype = 'getelementptr'; - item.type = '*'; // We need type info to determine this - all we know is it's a pointer - item.params = data.params; - item.ident = data.ident; - this.forwardItem(item, 'Reintegrator'); - } - }); + function GEPHandler(item) { + var first = 0; + while (!isType(item.tokens[first].text)) first++; + Types.needAnalysis[item.tokens[first].text] = 0; + var last = getTokenIndexByText(item.tokens, ';'); + var segment = [ item.tokens[first], { text: 'getelementptr' }, null, { item: { + tokens: item.tokens.slice(first, last) + } } ]; + var data = parseLLVMFunctionCall(segment); + item.intertype = 'getelementptr'; + item.type = '*'; // We need type info to determine this - all we know is it's a pointer + item.params = data.params; + item.ident = data.ident; + return item; + } // 'call', 'invoke' function makeCall(item, type) { item.intertype = type; @@ -727,7 +686,7 @@ function intertyper(data, sidePass, baseLineNums) { }); if (item.assignTo) item.ident = 'return ' + item.ident; item.ident = '(function(' + params + ') { ' + item.ident + ' })(' + args + ');'; - return { forward: null, ret: [item], item: item }; + return { forward: null, ret: item, item: item }; } if (item.ident.substr(-2) == '()') { // See comment in isStructType() @@ -750,330 +709,417 @@ function intertyper(data, sidePass, baseLineNums) { if (item.indent == 2) { // standalone call - not in assign item.standalone = true; - return { forward: null, ret: [item], item: item }; + return { forward: null, ret: item, item: item }; } - return { forward: item, ret: [], item: item }; + return { forward: item, ret: null, item: item }; } - substrate.addActor('Call', { - processItem: function(item) { - var result = makeCall.call(this, item, 'call'); - if (result.forward) this.forwardItem(result.forward, 'Reintegrator'); - return result.ret; - } - }); - substrate.addActor('Invoke', { - processItem: function(item) { - var result = makeCall.call(this, item, 'invoke'); - if (DISABLE_EXCEPTION_CATCHING == 1) { - result.item.intertype = 'call'; - result.ret.push({ - intertype: 'branch', - label: result.item.toLabel, - lineNum: (result.forward ? item.parentLineNum : item.lineNum) + 0.5 - }); - } - if (result.forward) this.forwardItem(result.forward, 'Reintegrator'); - return result.ret; + function callHandler(item) { + var result = makeCall.call(this, item, 'call'); + if (result.forward) this.forwardItem(result.forward, 'Reintegrator'); + return result.ret; + } + function invokeHandler(item) { + var result = makeCall.call(this, item, 'invoke'); + if (DISABLE_EXCEPTION_CATCHING == 1) { + result.item.intertype = 'call'; + finalResults.push({ + intertype: 'branch', + label: result.item.toLabel, + lineNum: (result.forward ? item.parentLineNum : item.lineNum) + 0.5 + }); } - }); - substrate.addActor('Atomic', { - processItem: function(item) { - item.intertype = 'atomic'; - if (item.tokens[0].text == 'atomicrmw') { - if (item.tokens[1].text == 'volatile') item.tokens.splice(1, 1); - item.op = item.tokens[1].text; - item.tokens.splice(1, 1); - } else { - assert(item.tokens[0].text == 'cmpxchg') - if (item.tokens[1].text == 'volatile') item.tokens.splice(1, 1); - item.op = 'cmpxchg'; - } - var last = getTokenIndexByText(item.tokens, ';'); - item.params = splitTokenList(item.tokens.slice(1, last)).map(parseLLVMSegment); - item.type = item.params[1].type; - this.forwardItem(item, 'Reintegrator'); + if (result.forward) this.forwardItem(result.forward, 'Reintegrator'); + return result.ret; + } + function atomicHandler(item) { + item.intertype = 'atomic'; + if (item.tokens[0].text == 'atomicrmw') { + if (item.tokens[1].text == 'volatile') item.tokens.splice(1, 1); + item.op = item.tokens[1].text; + item.tokens.splice(1, 1); + } else { + assert(item.tokens[0].text == 'cmpxchg') + if (item.tokens[1].text == 'volatile') item.tokens.splice(1, 1); + item.op = 'cmpxchg'; } - }); + var last = getTokenIndexByText(item.tokens, ';'); + item.params = splitTokenList(item.tokens.slice(1, last)).map(parseLLVMSegment); + item.type = item.params[1].type; + return item; + } // 'landingpad' - substrate.addActor('Landingpad', { - processItem: function(item) { - item.intertype = 'landingpad'; - item.type = item.tokens[1].text; - item.catchables = []; - var catchIdx = findTokenText(item, "catch"); - if (catchIdx != -1) { - do { - var nextCatchIdx = findTokenTextAfter(item, "catch", catchIdx+1); - if (nextCatchIdx == -1) - nextCatchIdx = item.tokens.length; - item.catchables.push(parseLLVMSegment(item.tokens.slice(catchIdx+2, nextCatchIdx))); - catchIdx = nextCatchIdx; - } while (catchIdx != item.tokens.length); - } - Types.needAnalysis[item.type] = 0; - this.forwardItem(item, 'Reintegrator'); + function landingpadHandler(item) { + item.intertype = 'landingpad'; + item.type = item.tokens[1].text; + item.catchables = []; + var catchIdx = findTokenText(item, "catch"); + if (catchIdx != -1) { + do { + var nextCatchIdx = findTokenTextAfter(item, "catch", catchIdx+1); + if (nextCatchIdx == -1) + nextCatchIdx = item.tokens.length; + item.catchables.push(parseLLVMSegment(item.tokens.slice(catchIdx+2, nextCatchIdx))); + catchIdx = nextCatchIdx; + } while (catchIdx != item.tokens.length); } - }); + Types.needAnalysis[item.type] = 0; + return item; + } // 'alloca' var allocaPossibleVars = ['allocatedNum']; - substrate.addActor('Alloca', { - processItem: function(item) { - item.intertype = 'alloca'; - item.allocatedType = item.tokens[1].text; - if (item.tokens.length > 3 && Runtime.isNumberType(item.tokens[3].text)) { - item.allocatedNum = toNiceIdent(item.tokens[4].text); - item.possibleVars = allocaPossibleVars; - } else { - item.allocatedNum = 1; - } - item.type = addPointing(item.tokens[1].text); // type of pointer we will get - Types.needAnalysis[item.type] = 0; - item.type2 = item.tokens[1].text; // value we will create, and get a pointer to - Types.needAnalysis[item.type2] = 0; - this.forwardItem(item, 'Reintegrator'); + function allocaHandler(item) { + item.intertype = 'alloca'; + item.allocatedType = item.tokens[1].text; + if (item.tokens.length > 3 && Runtime.isNumberType(item.tokens[3].text)) { + item.allocatedNum = toNiceIdent(item.tokens[4].text); + item.possibleVars = allocaPossibleVars; + } else { + item.allocatedNum = 1; } - }); + item.type = addPointing(item.tokens[1].text); // type of pointer we will get + Types.needAnalysis[item.type] = 0; + item.type2 = item.tokens[1].text; // value we will create, and get a pointer to + Types.needAnalysis[item.type2] = 0; + return item; + } // 'phi' - substrate.addActor('Phi', { - processItem: function(item) { - item.intertype = 'phi'; - item.type = item.tokens[1].text; - var typeToken = [item.tokens[1]]; - Types.needAnalysis[item.type] = 0; - var last = getTokenIndexByText(item.tokens, ';'); - item.params = splitTokenList(item.tokens.slice(2, last)).map(function(segment) { - var subSegments = splitTokenList(segment[0].item.tokens); - var ret = { - intertype: 'phiparam', - label: toNiceIdent(subSegments[1][0].text), - value: parseLLVMSegment(typeToken.concat(subSegments[0])) - }; - return ret; - }).filter(function(param) { return param.value && param.value.ident != 'undef' }); - this.forwardItem(item, 'Reintegrator'); - } - }); + function phiHandler(item) { + item.intertype = 'phi'; + item.type = item.tokens[1].text; + var typeToken = [item.tokens[1]]; + Types.needAnalysis[item.type] = 0; + var last = getTokenIndexByText(item.tokens, ';'); + item.params = splitTokenList(item.tokens.slice(2, last)).map(function(segment) { + var subSegments = splitTokenList(segment[0].item.tokens); + var ret = { + intertype: 'phiparam', + label: toNiceIdent(subSegments[1][0].text), + value: parseLLVMSegment(typeToken.concat(subSegments[0])) + }; + return ret; + }).filter(function(param) { return param.value && param.value.ident != 'undef' }); + return item; + } // 'phi' - substrate.addActor('va_arg', { - processItem: function(item) { - item.intertype = 'va_arg'; - var segments = splitTokenList(item.tokens.slice(1)); - item.type = segments[1][0].text; - item.value = parseLLVMSegment(segments[0]); - this.forwardItem(item, 'Reintegrator'); - } - }); + function va_argHandler(item) { + item.intertype = 'va_arg'; + var segments = splitTokenList(item.tokens.slice(1)); + item.type = segments[1][0].text; + item.value = parseLLVMSegment(segments[0]); + return item; + } // mathops - substrate.addActor('Mathops', { - processItem: function(item) { - item.intertype = 'mathop'; - item.op = item.tokens[0].text; - item.variant = null; - while (item.tokens[1].text in NSW_NUW) item.tokens.splice(1, 1); - if (['icmp', 'fcmp'].indexOf(item.op) != -1) { - item.variant = item.tokens[1].text; - item.tokens.splice(1, 1); - } - if (item.tokens[1].text == 'exact') item.tokens.splice(1, 1); // TODO: Implement trap values - var segments = splitTokenList(item.tokens.slice(1)); - item.params = []; - for (var i = 1; i <= 4; i++) { - if (segments[i-1]) { - if (i > 1 && segments[i-1].length == 1 && segments[0].length > 1 && !isType(segments[i-1][0].text)) { - segments[i-1].unshift(segments[0][0]); // Add the type from the first segment, they are all alike - } - item.params[i-1] = parseLLVMSegment(segments[i-1]); - } - } - var setParamTypes = true; - if (item.op === 'select') { - assert(item.params[1].type === item.params[2].type); - item.type = item.params[1].type; - } else if (item.op in LLVM.CONVERSIONS) { - item.type = item.params[1].type; - setParamTypes = false; - } else { - item.type = item.params[0].type; - } - if (setParamTypes) { - for (var i = 0; i < 4; i++) { - if (item.params[i]) item.params[i].type = item.type; // All params have the same type, normally + function mathopsHandler(item) { + item.intertype = 'mathop'; + item.op = item.tokens[0].text; + item.variant = null; + while (item.tokens[1].text in NSW_NUW) item.tokens.splice(1, 1); + if (['icmp', 'fcmp'].indexOf(item.op) != -1) { + item.variant = item.tokens[1].text; + item.tokens.splice(1, 1); + } + if (item.tokens[1].text == 'exact') item.tokens.splice(1, 1); // TODO: Implement trap values + var segments = splitTokenList(item.tokens.slice(1)); + item.params = []; + for (var i = 1; i <= 4; i++) { + if (segments[i-1]) { + if (i > 1 && segments[i-1].length == 1 && segments[0].length > 1 && !isType(segments[i-1][0].text)) { + segments[i-1].unshift(segments[0][0]); // Add the type from the first segment, they are all alike } + item.params[i-1] = parseLLVMSegment(segments[i-1]); } - if (item.op in LLVM.EXTENDS) { - item.type = item.params[1].ident; - item.params[0].type = item.params[1].type; - // TODO: also remove 2nd param? - } else if (item.op in LLVM.COMPS) { - item.type = 'i1'; + } + var setParamTypes = true; + if (item.op === 'select') { + assert(item.params[1].type === item.params[2].type); + item.type = item.params[1].type; + } else if (item.op in LLVM.CONVERSIONS) { + item.type = item.params[1].type; + setParamTypes = false; + } else { + item.type = item.params[0].type; + } + if (setParamTypes) { + for (var i = 0; i < 4; i++) { + if (item.params[i]) item.params[i].type = item.type; // All params have the same type, normally } - if (USE_TYPED_ARRAYS == 2) { - // Some specific corrections, since 'i64' is special - if (item.op in LLVM.SHIFTS) { - item.params[1].type = 'i32'; - } else if (item.op == 'select') { - item.params[0].type = 'i1'; - } + } + if (item.op in LLVM.EXTENDS) { + item.type = item.params[1].ident; + item.params[0].type = item.params[1].type; + // TODO: also remove 2nd param? + } else if (item.op in LLVM.COMPS) { + item.type = 'i1'; + } + if (USE_TYPED_ARRAYS == 2) { + // Some specific corrections, since 'i64' is special + if (item.op in LLVM.SHIFTS) { + item.params[1].type = 'i32'; + } else if (item.op == 'select') { + item.params[0].type = 'i1'; } - Types.needAnalysis[item.type] = 0; - this.forwardItem(item, 'Reintegrator'); } - }); + Types.needAnalysis[item.type] = 0; + return item; + } // 'store' - substrate.addActor('Store', { - processItem: function(item) { - cleanOutTokens(LLVM.ACCESS_OPTIONS, item.tokens, [0, 1]); - var segments = splitTokenList(item.tokens.slice(1)); - var ret = { - intertype: 'store', - valueType: item.tokens[1].text, - value: parseLLVMSegment(segments[0]), - pointer: parseLLVMSegment(segments[1]), - lineNum: item.lineNum - }; - Types.needAnalysis[ret.valueType] = 0; - ret.ident = toNiceIdent(ret.pointer.ident); - ret.pointerType = ret.pointer.type; - Types.needAnalysis[ret.pointerType] = 0; - if (segments.length > 2) { - assert(segments[2][0].text == 'align'); - ret.align = parseInt(segments[2][1].text) || QUANTUM_SIZE; // 0 means preferred arch align - } else { - ret.align = QUANTUM_SIZE; - } - return [ret]; + function storeHandler(item) { + cleanOutTokens(LLVM.ACCESS_OPTIONS, item.tokens, [0, 1]); + var segments = splitTokenList(item.tokens.slice(1)); + var ret = { + intertype: 'store', + valueType: item.tokens[1].text, + value: parseLLVMSegment(segments[0]), + pointer: parseLLVMSegment(segments[1]), + lineNum: item.lineNum + }; + Types.needAnalysis[ret.valueType] = 0; + ret.ident = toNiceIdent(ret.pointer.ident); + ret.pointerType = ret.pointer.type; + Types.needAnalysis[ret.pointerType] = 0; + if (segments.length > 2) { + assert(segments[2][0].text == 'align'); + ret.align = parseInt(segments[2][1].text) || QUANTUM_SIZE; // 0 means preferred arch align + } else { + ret.align = QUANTUM_SIZE; } - }); + return ret; + } // 'br' - substrate.addActor('Branch', { - processItem: function(item) { - if (item.tokens[1].text == 'label') { - return [{ - intertype: 'branch', - label: toNiceIdent(item.tokens[2].text), - lineNum: item.lineNum - }]; - } else { - var commaIndex = findTokenText(item, ','); - return [{ - intertype: 'branch', - value: parseLLVMSegment(item.tokens.slice(1, commaIndex)), - labelTrue: toNiceIdent(item.tokens[commaIndex+2].text), - labelFalse: toNiceIdent(item.tokens[commaIndex+5].text), - lineNum: item.lineNum - }]; - } - } - }); - // 'ret' - substrate.addActor('Return', { - processItem: function(item) { - var type = item.tokens[1].text; - Types.needAnalysis[type] = 0; - return [{ - intertype: 'return', - type: type, - value: (item.tokens[2] && type !== 'void') ? parseLLVMSegment(item.tokens.slice(1)) : null, + function branchHandler(item) { + if (item.tokens[1].text == 'label') { + return { + intertype: 'branch', + label: toNiceIdent(item.tokens[2].text), lineNum: item.lineNum - }]; - } - }); - // 'resume' - partial implementation - substrate.addActor('Resume', { - processItem: function(item) { - return [{ - intertype: 'resume', - ident: toNiceIdent(item.tokens[2].text), + }; + } else { + var commaIndex = findTokenText(item, ','); + return { + intertype: 'branch', + value: parseLLVMSegment(item.tokens.slice(1, commaIndex)), + labelTrue: toNiceIdent(item.tokens[commaIndex+2].text), + labelFalse: toNiceIdent(item.tokens[commaIndex+5].text), lineNum: item.lineNum - }]; + }; } - }); + } + // 'ret' + function returnHandler(item) { + var type = item.tokens[1].text; + Types.needAnalysis[type] = 0; + return { + intertype: 'return', + type: type, + value: (item.tokens[2] && type !== 'void') ? parseLLVMSegment(item.tokens.slice(1)) : null, + lineNum: item.lineNum + }; + } + // 'resume' - partial implementation + function resumeHandler(item) { + return { + intertype: 'resume', + ident: toNiceIdent(item.tokens[2].text), + lineNum: item.lineNum + }; + } // 'switch' - substrate.addActor('Switch', { - processItem: function(item) { - function parseSwitchLabels(item) { - var ret = []; - var tokens = item.item.tokens; - while (tokens.length > 0) { - ret.push({ - value: tokens[1].text, - label: toNiceIdent(tokens[4].text) - }); - tokens = tokens.slice(5); - } - return ret; + function switchHandler(item) { + function parseSwitchLabels(item) { + var ret = []; + var tokens = item.item.tokens; + while (tokens.length > 0) { + ret.push({ + value: tokens[1].text, + label: toNiceIdent(tokens[4].text) + }); + tokens = tokens.slice(5); } - var type = item.tokens[1].text; - Types.needAnalysis[type] = 0; - return [{ - intertype: 'switch', - type: type, - ident: toNiceIdent(item.tokens[2].text), - defaultLabel: toNiceIdent(item.tokens[5].text), - switchLabels: parseSwitchLabels(item.tokens[6]), - lineNum: item.lineNum - }]; + return ret; } - }); + var type = item.tokens[1].text; + Types.needAnalysis[type] = 0; + return { + intertype: 'switch', + type: type, + ident: toNiceIdent(item.tokens[2].text), + defaultLabel: toNiceIdent(item.tokens[5].text), + switchLabels: parseSwitchLabels(item.tokens[6]), + lineNum: item.lineNum + }; + } // function end - substrate.addActor('FuncEnd', { - processItem: function(item) { - return [{ - intertype: 'functionEnd', - lineNum: item.lineNum - }]; - } - }); + function funcEndHandler(item) { + return { + intertype: 'functionEnd', + lineNum: item.lineNum + }; + } // external function stub - substrate.addActor('External', { - processItem: function(item) { - while (item.tokens[1].text in LLVM.LINKAGES || item.tokens[1].text in LLVM.PARAM_ATTR || item.tokens[1].text in LLVM.VISIBILITIES || item.tokens[1].text in LLVM.CALLING_CONVENTIONS) { - item.tokens.splice(1, 1); - } - var params = parseParamTokens(item.tokens[3].item.tokens); - return [{ - intertype: 'functionStub', - ident: toNiceIdent(item.tokens[2].text), - returnType: item.tokens[1], - params: params, - hasVarArgs: hasVarArgs(params), - lineNum: item.lineNum - }]; + function externalHandler(item) { + while (item.tokens[1].text in LLVM.LINKAGES || item.tokens[1].text in LLVM.PARAM_ATTR || item.tokens[1].text in LLVM.VISIBILITIES || item.tokens[1].text in LLVM.CALLING_CONVENTIONS) { + item.tokens.splice(1, 1); } - }); + var params = parseParamTokens(item.tokens[3].item.tokens); + return { + intertype: 'functionStub', + ident: toNiceIdent(item.tokens[2].text), + returnType: item.tokens[1], + params: params, + hasVarArgs: hasVarArgs(params), + lineNum: item.lineNum + }; + } // 'unreachable' - substrate.addActor('Unreachable', { - processItem: function(item) { - return [{ - intertype: 'unreachable', - lineNum: item.lineNum - }]; - } - }); + function unreachableHandler(item) { + return { + intertype: 'unreachable', + lineNum: item.lineNum + }; + } // 'indirectbr' - substrate.addActor('IndirectBr', { - processItem: function(item) { - var ret = { - intertype: 'indirectbr', - value: parseLLVMSegment(splitTokenList(item.tokens.slice(1))[0]), - type: item.tokens[1].text, - lineNum: item.lineNum - }; - Types.needAnalysis[ret.type] = 0; - return [ret]; + function indirectBrHandler(item) { + var ret = { + intertype: 'indirectbr', + value: parseLLVMSegment(splitTokenList(item.tokens.slice(1))[0]), + type: item.tokens[1].text, + lineNum: item.lineNum + }; + Types.needAnalysis[ret.type] = 0; + return ret; + } + + // Fast paths - quick parses of common patterns, avoid tokenizing entirely + + function tryFastPaths(line) { + var m, ret; + if (phase === 'pre') { + // string constant + if (0) { // works, but not worth it m = /([@\.\w\d_]+) = (private )?(unnamed_addr )?(constant )?(\[\d+ x i8\]) c"([^"]+)".*/.exec(line.lineText)) { + if (m[1] === '@llvm.global_ctors') return ret; + ret = { + intertype: 'globalVariable', + ident: toNiceIdent(m[1]), + type: m[5], + external: false, + private_: m[2] !== null, + named: m[3] === null, + lineNum: line.lineNum, + value: { + intertype: 'string', + text: m[6] + } + }; + noteGlobalVariable(ret); + } + } else if (phase === 'funcs') { + if (m = /^ (%[\w\d\._]+) = (getelementptr|load) ([%\w\d\._ ,\*\-@]+)$/.exec(line.lineText)) { + var assignTo = m[1]; + var intertype = m[2]; + var args = m[3]; + switch (intertype) { + case 'getelementptr': { + if (args[0] === 'i' && args.indexOf('inbounds ') === 0) { + args = args.substr(9); + } + var params = args.split(', ').map(function(param) { + var parts = param.split(' '); + assert(parts.length === 2); + Types.needAnalysis[parts[0]] = 0; + return { + intertype: 'value', + type: parts[0], + ident: toNiceIdent(parts[1]), + byVal: 0 + } + }); + ret = { + intertype: 'getelementptr', + lineNum: line.lineNum, + assignTo: toNiceIdent(assignTo), + ident: params[0].ident, + type: '*', + params: params + }; + break; + } + case 'load': { + if (m = /(^[%\w\d\._\-@\*]+) ([%\w\d\._\-@]+)(, align \d+)?$/.exec(args)) { + var ident = toNiceIdent(m[2]); + var type = m[1]; + assert(type[type.length-1] === '*', type); + var valueType = type.substr(0, type.length-1); + ret = { + intertype: 'load', + lineNum: line.lineNum, + assignTo: toNiceIdent(assignTo), + ident: ident, + type: valueType, + valueType: valueType, + pointerType: type, + pointer: { + intertype: 'value', + ident: ident, + type: type, + }, + align: parseAlign(m[3]) + }; + } + break; + } + default: throw 'unexpected fast path type ' + intertype; + } + //else if (line.lineText.indexOf(' = load ') > 0) printErr('close: ' + JSON.stringify(line.lineText)); + } } - }); + if (ret) { + if (COMPILER_ASSERTIONS) { + //printErr(['\n', JSON.stringify(ret), '\n', JSON.stringify(triager(tokenizer(line)))]); + var normal = triager(tokenizer(line)); + delete normal.tokens; + delete normal.indent; + assert(sortedJsonCompare(normal, ret), 'fast path: ' + dump(normal) + '\n vs \n' + dump(ret)); + } + } + return ret; + } // Input - substrate.addItem({ - llvmLines: data - }, 'LineSplitter'); + lineSplitter().forEach(function(line) { + var item = tryFastPaths(line); + if (item) { + finalResults.push(item); + fastPaths++; + return; + } + slowPaths++; - substrate.onResult = function(result) { - if (result.tokens) result.tokens = null; // We do not need tokens, past the intertyper. Clean them up as soon as possible here. - }; + //var time = Date.now(); + + var t = tokenizer(line); + item = triager(t); + + /* + var type = item ? item.intertype + (item.op ? ':' + item.op : ''): 'none'; + if (!interProf[type]) interProf[type] = { ms: 0, n: 0 }; + interProf[type].ms += Date.now() - time; + interProf[type].n++; + */ + + if (!item) return; + finalResults.push(item); + if (item.tokens) item.tokens = null; // We do not need tokens, past the intertyper. Clean them up as soon as possible here. + }); + return finalResults; +} + +// intertyper profiler - return substrate.solve(); +/* +var interProf = {}; +function dumpInterProf() { + printErr('\nintertyper/' + phase + ' (ms | n): ' + JSON.stringify(keys(interProf).sort(function(x, y) { return interProf[y].ms - interProf[x].ms }).map(function(x) { return x + ' : ' + interProf[x].ms + ' | ' + interProf[x].n }), null, ' ') + '\n'); } +*/ diff --git a/src/jsifier.js b/src/jsifier.js index 1f53b1a2..96cb8d9a 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -22,6 +22,8 @@ var functionStubSigs = {}; function JSify(data, functionsOnly, givenFunctions) { var mainPass = !functionsOnly; + var itemsDict = { type: [], GlobalVariableStub: [], functionStub: [], function: [], GlobalVariable: [], GlobalVariablePostSet: [] }; + if (mainPass) { var shellFile = SHELL_FILE ? SHELL_FILE : (BUILD_AS_SHARED_LIB || SIDE_MODULE ? 'shell_sharedlib.js' : 'shell.js'); @@ -58,17 +60,6 @@ function JSify(data, functionsOnly, givenFunctions) { } } - // Does simple 'macro' substitution, using Django-like syntax, - // {{{ code }}} will be replaced with |eval(code)|. - function processMacros(text) { - return text.replace(/{{{[^}]+}}}/g, function(str) { - str = str.substr(3, str.length-6); - return eval(str).toString(); - }); - } - - var substrate = new Substrate('JSifyer'); - if (mainPass) { // Handle unparsed types TODO: Batch them analyzer(intertyper(data.unparsedTypess[0].lines, true), true); @@ -138,21 +129,6 @@ function JSify(data, functionsOnly, givenFunctions) { // Actors - // type - // FIXME: This is no longer used, we do not actually need to JSify on types. TODO: Remove this and related code - substrate.addActor('Type', { - processItem: function(item) { - var type = Types.types[item.name_]; - var niceName = toNiceIdent(item.name_); - // We might export all of Types.types, cleaner that way, but do not want slowdowns in accessing flatteners - item.JS = 'var ' + niceName + '___SIZE = ' + Types.types[item.name_].flatSize + '; // ' + item.name_ + '\n'; - if (type.needsFlattening && !type.flatFactor) { - item.JS += 'var ' + niceName + '___FLATTENER = ' + JSON.stringify(Types.types[item.name_].flatIndexes) + ';'; - } - return [item]; - } - }); - function makeEmptyStruct(type) { var ret = []; var typeData = Types.types[type]; @@ -255,146 +231,144 @@ function JSify(data, functionsOnly, givenFunctions) { } // globalVariable - substrate.addActor('GlobalVariable', { - processItem: function(item) { - function needsPostSet(value) { - if (typeof value !== 'string') return false; - return value[0] in UNDERSCORE_OPENPARENS || value.substr(0, 14) === 'CHECK_OVERFLOW' - || value.substr(0, 6) === 'GLOBAL'; - } - - item.intertype = 'GlobalVariableStub'; - assert(!item.lines); // FIXME remove this, after we are sure it isn't needed - var ret = [item]; - if (item.ident == '_llvm_global_ctors') { - item.JS = '\n/* global initializers */ __ATINIT__.push(' + - item.ctors.map(function(ctor) { return '{ func: function() { ' + ctor + '() } }' }).join(',') + - ');\n'; - return ret; - } + function globalVariableHandler(item) { + function needsPostSet(value) { + if (typeof value !== 'string') return false; + return value[0] in UNDERSCORE_OPENPARENS || value.substr(0, 14) === 'CHECK_OVERFLOW' + || value.substr(0, 6) === 'GLOBAL'; + } + + item.intertype = 'GlobalVariableStub'; + itemsDict.GlobalVariableStub.push(item); + assert(!item.lines); // FIXME remove this, after we are sure it isn't needed + if (item.ident == '_llvm_global_ctors') { + item.JS = '\n/* global initializers */ __ATINIT__.push(' + + item.ctors.map(function(ctor) { return '{ func: function() { ' + ctor + '() } }' }).join(',') + + ');\n'; + return; + } - var constant = null; - var allocator = (BUILD_AS_SHARED_LIB && !item.external) ? 'ALLOC_NORMAL' : 'ALLOC_STATIC'; - var index = null; - if (item.external && BUILD_AS_SHARED_LIB) { - // External variables in shared libraries should not be declared as - // they would shadow similarly-named globals in the parent. - item.JS = ''; - } else { - item.JS = makeGlobalDef(item.ident); - } + var constant = null; + var allocator = (BUILD_AS_SHARED_LIB && !item.external) ? 'ALLOC_NORMAL' : 'ALLOC_STATIC'; + var index = null; + if (item.external && BUILD_AS_SHARED_LIB) { + // External variables in shared libraries should not be declared as + // they would shadow similarly-named globals in the parent. + item.JS = ''; + } else { + item.JS = makeGlobalDef(item.ident); + } - if (!NAMED_GLOBALS && isIndexableGlobal(item.ident)) { - index = makeGlobalUse(item.ident); // index !== null indicates we are indexing this - allocator = 'ALLOC_NONE'; - } + if (!NAMED_GLOBALS && isIndexableGlobal(item.ident)) { + index = makeGlobalUse(item.ident); // index !== null indicates we are indexing this + allocator = 'ALLOC_NONE'; + } - Variables.globals[item.ident].named = item.named; + Variables.globals[item.ident].named = item.named; - if (ASM_JS && (MAIN_MODULE || SIDE_MODULE) && !item.private_ && !NAMED_GLOBALS && isIndexableGlobal(item.ident)) { - // We need this to be named (and it normally would not be), so that it can be linked to and used from other modules - Variables.globals[item.ident].linkable = 1; - } + if (ASM_JS && (MAIN_MODULE || SIDE_MODULE) && !item.private_ && !NAMED_GLOBALS && isIndexableGlobal(item.ident)) { + // We need this to be named (and it normally would not be), so that it can be linked to and used from other modules + Variables.globals[item.ident].linkable = 1; + } - if (isBSS(item)) { - var length = calcAllocatedSize(item.type); - length = Runtime.alignMemory(length); + if (isBSS(item)) { + var length = calcAllocatedSize(item.type); + length = Runtime.alignMemory(length); - // If using indexed globals, go ahead and early out (no need to explicitly - // initialize). - if (!NAMED_GLOBALS) { - return ret; - } - // If using named globals, we can at least shorten the call to allocate by - // passing an integer representing the size of memory to alloc instead of - // an array of 0s of size length. - else { - constant = length; + // If using indexed globals, go ahead and early out (no need to explicitly + // initialize). + if (!NAMED_GLOBALS) { + return; + } + // If using named globals, we can at least shorten the call to allocate by + // passing an integer representing the size of memory to alloc instead of + // an array of 0s of size length. + else { + constant = length; + } + } else { + if (item.external) { + if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { + constant = zeros(Runtime.getNativeFieldSize(item.type)); + } else { + constant = makeEmptyStruct(item.type); } } else { - if (item.external) { - if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { - constant = zeros(Runtime.getNativeFieldSize(item.type)); - } else { - constant = makeEmptyStruct(item.type); - } - } else { - constant = parseConst(item.value, item.type, item.ident); + constant = parseConst(item.value, item.type, item.ident); + } + assert(typeof constant === 'object');//, [typeof constant, JSON.stringify(constant), item.external]); + + // This is a flattened object. We need to find its idents, so they can be assigned to later + var structTypes = null; + constant.forEach(function(value, i) { + if (needsPostSet(value)) { // ident, or expression containing an ident + if (!structTypes) structTypes = generateStructTypes(item.type); + itemsDict.GlobalVariablePostSet.push({ + intertype: 'GlobalVariablePostSet', + JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors + }); + constant[i] = '0'; } - assert(typeof constant === 'object');//, [typeof constant, JSON.stringify(constant), item.external]); - - // This is a flattened object. We need to find its idents, so they can be assigned to later - var structTypes = null; - constant.forEach(function(value, i) { - if (needsPostSet(value)) { // ident, or expression containing an ident - if (!structTypes) structTypes = generateStructTypes(item.type); - ret.push({ - intertype: 'GlobalVariablePostSet', - JS: makeSetValue(makeGlobalUse(item.ident), i, value, structTypes[i], false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors - }); - constant[i] = '0'; - } - }); + }); - if (item.external) { - // External variables in shared libraries should not be declared as - // they would shadow similarly-named globals in the parent, so do nothing here. - if (BUILD_AS_SHARED_LIB) return ret; - if (SIDE_MODULE) return []; - // Library items need us to emit something, but everything else requires nothing. - if (!LibraryManager.library[item.ident.slice(1)]) return ret; + if (item.external) { + // External variables in shared libraries should not be declared as + // they would shadow similarly-named globals in the parent, so do nothing here. + if (BUILD_AS_SHARED_LIB) return; + if (SIDE_MODULE) { + itemsDict.GlobalVariableStub.pop(); // remove this item + return; } + // Library items need us to emit something, but everything else requires nothing. + if (!LibraryManager.library[item.ident.slice(1)]) return; + } - // ensure alignment - constant = constant.concat(zeros(Runtime.alignMemory(constant.length) - constant.length)); + // ensure alignment + constant = constant.concat(zeros(Runtime.alignMemory(constant.length) - constant.length)); - // Special case: class vtables. We make sure they are null-terminated, to allow easy runtime operations - if (item.ident.substr(0, 5) == '__ZTV') { - constant = constant.concat(zeros(Runtime.alignMemory(QUANTUM_SIZE))); - } + // Special case: class vtables. We make sure they are null-terminated, to allow easy runtime operations + if (item.ident.substr(0, 5) == '__ZTV') { + constant = constant.concat(zeros(Runtime.alignMemory(QUANTUM_SIZE))); } + } - // NOTE: This is the only place that could potentially create static - // allocations in a shared library. - constant = makePointer(constant, null, allocator, item.type, index); + // NOTE: This is the only place that could potentially create static + // allocations in a shared library. + constant = makePointer(constant, null, allocator, item.type, index); - var js = (index !== null ? '' : item.ident + '=') + constant; - if (js) js += ';'; + var js = (index !== null ? '' : item.ident + '=') + constant; + if (js) js += ';'; - if (!ASM_JS && NAMED_GLOBALS && (EXPORT_ALL || (item.ident in EXPORTED_GLOBALS))) { - js += '\nModule["' + item.ident + '"] = ' + item.ident + ';'; - } - if (BUILD_AS_SHARED_LIB == 2 && !item.private_) { - // TODO: make the assert conditional on ASSERTIONS - js += 'if (globalScope) { assert(!globalScope["' + item.ident + '"]); globalScope["' + item.ident + '"] = ' + item.ident + ' }'; - } - if (item.external && !NAMED_GLOBALS) { - js = 'var ' + item.ident + ' = ' + js; // force an explicit naming, even if unnamed globals, for asm forwarding - } - return ret.concat({ - intertype: 'GlobalVariable', - JS: js, - }); + if (!ASM_JS && NAMED_GLOBALS && (EXPORT_ALL || (item.ident in EXPORTED_GLOBALS))) { + js += '\nModule["' + item.ident + '"] = ' + item.ident + ';'; } - }); + if (BUILD_AS_SHARED_LIB == 2 && !item.private_) { + // TODO: make the assert conditional on ASSERTIONS + js += 'if (globalScope) { assert(!globalScope["' + item.ident + '"]); globalScope["' + item.ident + '"] = ' + item.ident + ' }'; + } + if (item.external && !NAMED_GLOBALS) { + js = 'var ' + item.ident + ' = ' + js; // force an explicit naming, even if unnamed globals, for asm forwarding + } + itemsDict.GlobalVariableStub.push({ + intertype: 'GlobalVariable', + JS: js, + }); + } // alias - substrate.addActor('Alias', { - processItem: function(item) { - item.intertype = 'GlobalVariableStub'; - var ret = [item]; - item.JS = 'var ' + item.ident + ';'; - // Set the actual value in a postset, since it may be a global variable. We also order by dependencies there - Variables.globals[item.ident].targetIdent = item.value.ident; - var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value); - if ((MAIN_MODULE || SIDE_MODULE) && isFunctionType(item.type)) { - var target = item.value.ident; - if (!Functions.aliases[target]) Functions.aliases[target] = []; - Functions.aliases[target].push(item.ident); - } - return ret; + function aliasHandler(item) { + item.intertype = 'GlobalVariableStub'; + itemsDict.GlobalVariableStub.push(item); + item.JS = 'var ' + item.ident + ';'; + // Set the actual value in a postset, since it may be a global variable. We also order by dependencies there + Variables.globals[item.ident].targetIdent = item.value.ident; + var value = Variables.globals[item.ident].resolvedAlias = finalizeLLVMParameter(item.value); + if ((MAIN_MODULE || SIDE_MODULE) && isFunctionType(item.type)) { + var target = item.value.ident; + if (!Functions.aliases[target]) Functions.aliases[target] = []; + Functions.aliases[target].push(item.ident); } - }); + } function processLibraryFunction(snippet, ident) { snippet = snippet.toString(); @@ -411,147 +385,163 @@ function JSify(data, functionsOnly, givenFunctions) { } // functionStub - substrate.addActor('FunctionStub', { - processItem: function(item) { - // note the signature - if (item.returnType && item.params) { - functionStubSigs[item.ident] = Functions.getSignature(item.returnType.text, item.params.map(function(arg) { return arg.type }), false); - } - - function addFromLibrary(ident) { - if (ident in addedLibraryItems) return ''; - addedLibraryItems[ident] = true; - - // dependencies can be JS functions, which we just run - if (typeof ident == 'function') return ident(); - - // Don't replace implemented functions with library ones (which can happen when we add dependencies). - // Note: We don't return the dependencies here. Be careful not to end up where this matters - if (('_' + ident) in Functions.implementedFunctions) return ''; - - var snippet = LibraryManager.library[ident]; - var redirectedIdent = null; - var deps = LibraryManager.library[ident + '__deps'] || []; - var isFunction = false; - - if (typeof snippet === 'string') { - var target = LibraryManager.library[snippet]; - if (target) { - // Redirection for aliases. We include the parent, and at runtime make ourselves equal to it. - // This avoid having duplicate functions with identical content. - redirectedIdent = snippet; - deps.push(snippet); - snippet = '_' + snippet; - } - // In asm, we need to know about library functions. If there is a target, though, then no - // need to consider this a library function - we will call directly to it anyhow - if (ASM_JS && !redirectedIdent && (typeof target == 'function' || /Math\.\w+/.exec(snippet))) { - Functions.libraryFunctions[ident] = 1; - } - } else if (typeof snippet === 'object') { - snippet = stringifyWithFunctions(snippet); - } else if (typeof snippet === 'function') { - isFunction = true; - snippet = processLibraryFunction(snippet, ident); - if (ASM_JS) Functions.libraryFunctions[ident] = 1; + function functionStubHandler(item) { + // note the signature + if (item.returnType && item.params) { + functionStubSigs[item.ident] = Functions.getSignature(item.returnType.text, item.params.map(function(arg) { return arg.type }), false); + } + + function addFromLibrary(ident) { + if (ident in addedLibraryItems) return ''; + addedLibraryItems[ident] = true; + + // dependencies can be JS functions, which we just run + if (typeof ident == 'function') return ident(); + + // Don't replace implemented functions with library ones (which can happen when we add dependencies). + // Note: We don't return the dependencies here. Be careful not to end up where this matters + if (('_' + ident) in Functions.implementedFunctions) return ''; + + var snippet = LibraryManager.library[ident]; + var redirectedIdent = null; + var deps = LibraryManager.library[ident + '__deps'] || []; + var isFunction = false; + + if (typeof snippet === 'string') { + var target = LibraryManager.library[snippet]; + if (target) { + // Redirection for aliases. We include the parent, and at runtime make ourselves equal to it. + // This avoid having duplicate functions with identical content. + redirectedIdent = snippet; + deps.push(snippet); + snippet = '_' + snippet; } - - var postsetId = ident + '__postset'; - var postset = LibraryManager.library[postsetId]; - if (postset && !addedLibraryItems[postsetId] && !SIDE_MODULE) { - addedLibraryItems[postsetId] = true; - ret.push({ - intertype: 'GlobalVariablePostSet', - JS: postset - }); + // In asm, we need to know about library functions. If there is a target, though, then no + // need to consider this a library function - we will call directly to it anyhow + if (ASM_JS && !redirectedIdent && (typeof target == 'function' || /Math\.\w+/.exec(snippet))) { + Functions.libraryFunctions[ident] = 1; } + } else if (typeof snippet === 'object') { + snippet = stringifyWithFunctions(snippet); + } else if (typeof snippet === 'function') { + isFunction = true; + snippet = processLibraryFunction(snippet, ident); + if (ASM_JS) Functions.libraryFunctions[ident] = 1; + } + + var postsetId = ident + '__postset'; + var postset = LibraryManager.library[postsetId]; + if (postset && !addedLibraryItems[postsetId] && !SIDE_MODULE) { + addedLibraryItems[postsetId] = true; + itemsDict.GlobalVariablePostSet.push({ + intertype: 'GlobalVariablePostSet', + JS: postset + }); + } - if (redirectedIdent) { - deps = deps.concat(LibraryManager.library[redirectedIdent + '__deps'] || []); - } - if (ASM_JS) { - // In asm, dependencies implemented in C might be needed by JS library functions. - // We don't know yet if they are implemented in C or not. To be safe, export such - // special cases. - [LIBRARY_DEPS_TO_AUTOEXPORT].forEach(function(special) { - deps.forEach(function(dep) { - if (dep == special && !EXPORTED_FUNCTIONS[dep]) { - EXPORTED_FUNCTIONS[dep] = 1; - } - }); + if (redirectedIdent) { + deps = deps.concat(LibraryManager.library[redirectedIdent + '__deps'] || []); + } + if (ASM_JS) { + // In asm, dependencies implemented in C might be needed by JS library functions. + // We don't know yet if they are implemented in C or not. To be safe, export such + // special cases. + [LIBRARY_DEPS_TO_AUTOEXPORT].forEach(function(special) { + deps.forEach(function(dep) { + if (dep == special && !EXPORTED_FUNCTIONS[dep]) { + EXPORTED_FUNCTIONS[dep] = 1; + } }); + }); + } + // $ident's are special, we do not prefix them with a '_'. + if (ident[0] === '$') { + ident = ident.substr(1); + } else { + ident = '_' + ident; + } + if (VERBOSE) printErr('adding ' + ident + ' and deps ' + deps); + var depsText = (deps ? '\n' + deps.map(addFromLibrary).filter(function(x) { return x != '' }).join('\n') : ''); + var contentText = isFunction ? snippet : ('var ' + ident + '=' + snippet + ';'); + if (ASM_JS) { + var sig = LibraryManager.library[ident.substr(1) + '__sig']; + if (isFunction && sig && LibraryManager.library[ident.substr(1) + '__asm']) { + // asm library function, add it as generated code alongside the generated code + Functions.implementedFunctions[ident] = sig; + asmLibraryFunctions.push(contentText); + contentText = ' '; + EXPORTED_FUNCTIONS[ident] = 1; + Functions.libraryFunctions[ident.substr(1)] = 2; } - // $ident's are special, we do not prefix them with a '_'. - if (ident[0] === '$') { - ident = ident.substr(1); - } else { - ident = '_' + ident; - } - if (VERBOSE) printErr('adding ' + ident + ' and deps ' + deps); - var depsText = (deps ? '\n' + deps.map(addFromLibrary).filter(function(x) { return x != '' }).join('\n') : ''); - var contentText = isFunction ? snippet : ('var ' + ident + '=' + snippet + ';'); - if (ASM_JS) { - var sig = LibraryManager.library[ident.substr(1) + '__sig']; - if (isFunction && sig && LibraryManager.library[ident.substr(1) + '__asm']) { - // asm library function, add it as generated code alongside the generated code - Functions.implementedFunctions[ident] = sig; - asmLibraryFunctions.push(contentText); - contentText = ' '; - EXPORTED_FUNCTIONS[ident] = 1; - Functions.libraryFunctions[ident.substr(1)] = 2; - } - } - if (SIDE_MODULE) return ';'; // we import into the side module js library stuff from the outside parent - if ((!ASM_JS || phase == 'pre') && - (EXPORT_ALL || (ident in EXPORTED_FUNCTIONS))) { - contentText += '\nModule["' + ident + '"] = ' + ident + ';'; - } - return depsText + contentText; } + if (SIDE_MODULE) return ';'; // we import into the side module js library stuff from the outside parent + if ((!ASM_JS || phase == 'pre') && + (EXPORT_ALL || (ident in EXPORTED_FUNCTIONS))) { + contentText += '\nModule["' + ident + '"] = ' + ident + ';'; + } + return depsText + contentText; + } - var ret = [item]; - if (IGNORED_FUNCTIONS.indexOf(item.ident) >= 0) return null; - var shortident = item.ident.substr(1); - if (BUILD_AS_SHARED_LIB) { - // Shared libraries reuse the runtime of their parents. - item.JS = ''; - } else { - // If this is not linkable, anything not in the library is definitely missing - var cancel = false; - if (!LINKABLE && !LibraryManager.library.hasOwnProperty(shortident) && !LibraryManager.library.hasOwnProperty(shortident + '__inline')) { - if (ERROR_ON_UNDEFINED_SYMBOLS) error('unresolved symbol: ' + shortident); - if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) printErr('warning: unresolved symbol: ' + shortident); - if (ASM_JS || item.ident in DEAD_FUNCTIONS) { - // emit a stub that will fail during runtime. this allows asm validation to succeed. - LibraryManager.library[shortident] = new Function("Module['printErr']('missing function: " + shortident + "'); abort(-1);"); - } else { - cancel = true; // emit nothing, not even var X = undefined; - } + itemsDict.functionStub.push(item); + if (IGNORED_FUNCTIONS.indexOf(item.ident) >= 0) return; + var shortident = item.ident.substr(1); + if (BUILD_AS_SHARED_LIB) { + // Shared libraries reuse the runtime of their parents. + item.JS = ''; + } else { + // If this is not linkable, anything not in the library is definitely missing + var cancel = false; + if (!LINKABLE && !LibraryManager.library.hasOwnProperty(shortident) && !LibraryManager.library.hasOwnProperty(shortident + '__inline')) { + if (ERROR_ON_UNDEFINED_SYMBOLS) error('unresolved symbol: ' + shortident); + if (VERBOSE || WARN_ON_UNDEFINED_SYMBOLS) printErr('warning: unresolved symbol: ' + shortident); + if (ASM_JS || item.ident in DEAD_FUNCTIONS) { + // emit a stub that will fail during runtime. this allows asm validation to succeed. + LibraryManager.library[shortident] = new Function("Module['printErr']('missing function: " + shortident + "'); abort(-1);"); + } else { + cancel = true; // emit nothing, not even var X = undefined; } - item.JS = cancel ? ';' : addFromLibrary(shortident); } - return ret; + item.JS = cancel ? ';' : addFromLibrary(shortident); } - }); + } // function splitter - substrate.addActor('FunctionSplitter', { - processItem: function(item) { - var ret = [item]; - item.splitItems = 0; - item.labels.forEach(function(label) { - label.lines.forEach(function(line) { - line.func = item.ident; - line.funcData = item; // TODO: remove all these, access it globally - line.parentLabel = label.ident; - ret.push(line); - item.splitItems ++; - }); - }); - - this.forwardItems(ret, 'FuncLineTriager'); - } - }); + function functionSplitter(item) { + item.lines.forEach(function(line) { + Framework.currItem = line; + line.funcData = item; // TODO: remove all these, access it globally + switch (line.intertype) { + case 'value': line.JS = valueHandler(line); break; + case 'noop': line.JS = noopHandler(line); break; + case 'var': line.JS = varHandler(line); break; + case 'store': line.JS = storeHandler(line); break; + case 'deleted': line.JS = deletedHandler(line); break; + case 'branch': line.JS = branchHandler(line); break; + case 'switch': line.JS = switchHandler(line); break; + case 'return': line.JS = returnHandler(line); break; + case 'resume': line.JS = resumeHandler(line); break; + case 'invoke': line.JS = invokeHandler(line); break; + case 'atomic': line.JS = atomicHandler(line); break; + case 'landingpad': line.JS = landingpadHandler(line); break; + case 'load': line.JS = loadHandler(line); break; + case 'extractvalue': line.JS = extractvalueHandler(line); break; + case 'insertvalue': line.JS = insertvalueHandler(line); break; + case 'indirectbr': line.JS = indirectbrHandler(line); break; + case 'alloca': line.JS = allocaHandler(line); break; + case 'va_arg': line.JS = va_argHandler(line); break; + case 'mathop': line.JS = mathopHandler(line); break; + case 'bitcast': line.JS = bitcastHandler(line); break; + case 'getelementptr': line.JS = getelementptrHandler(line); break; + case 'call': line.JS = callHandler(line); break; + case 'unreachable': line.JS = unreachableHandler(line); break; + default: throw 'what is this line? ' + dump(line); + } + assert(line.JS); + if (line.assignTo) makeAssign(line); + Framework.currItem = null; + }); + functionReconstructor(item); + } // function for filtering functions for label debugging if (LABEL_FUNCTION_FILTERS.length > 0) { @@ -567,322 +557,297 @@ function JSify(data, functionsOnly, givenFunctions) { } // function reconstructor & post-JS optimizer - substrate.addActor('FunctionReconstructor', { - funcs: {}, - seen: {}, - processItem: function(item) { - if (this.seen[item.__uid__]) return null; - if (item.intertype == 'function') { - this.funcs[item.ident] = item; - item.relines = {}; - this.seen[item.__uid__] = true; - return null; - } - var line = item; - var func = this.funcs[line.func]; - if (!func) return null; - - // Re-insert our line - this.seen[item.__uid__] = true; - var label = func.labels.filter(function(label) { return label.ident == line.parentLabel })[0]; - label.lines = label.lines.map(function(line2) { - return (line2.lineNum !== line.lineNum) ? line2 : line; - }); - func.splitItems --; - // OLD delete line.funcData; // clean up - if (func.splitItems > 0) return null; + function functionReconstructor(func) { + // We have this function all reconstructed, go and finalize it's JS! - // We have this function all reconstructed, go and finalize it's JS! + if (IGNORED_FUNCTIONS.indexOf(func.ident) >= 0) return null; - if (IGNORED_FUNCTIONS.indexOf(func.ident) >= 0) return null; + func.JS = '\n'; - func.JS = '\n'; + var paramIdents = func.params.map(function(param) { + return toNiceIdent(param.ident); + }); - var paramIdents = func.params.map(function(param) { - return toNiceIdent(param.ident); + if (CLOSURE_ANNOTATIONS) { + func.JS += '/**\n'; + paramIdents.forEach(function(param) { + func.JS += ' * @param {number} ' + param + '\n'; }); + func.JS += ' * @return {number}\n' + func.JS += ' */\n'; + } - if (CLOSURE_ANNOTATIONS) { - func.JS += '/**\n'; - paramIdents.forEach(function(param) { - func.JS += ' * @param {number} ' + param + '\n'; - }); - func.JS += ' * @return {number}\n' - func.JS += ' */\n'; - } - - if (PRINT_SPLIT_FILE_MARKER) { - func.JS += '\n//FUNCTION_BEGIN_MARKER\n' - var associatedSourceFile = "NO_SOURCE"; - } - - if (DLOPEN_SUPPORT) Functions.getIndex(func.ident); + if (PRINT_SPLIT_FILE_MARKER) { + func.JS += '\n//FUNCTION_BEGIN_MARKER\n' + var associatedSourceFile = "NO_SOURCE"; + } + + if (DLOPEN_SUPPORT) Functions.getIndex(func.ident); - func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n'; + func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n'; - if (PGO) { - func.JS += INDENTATION + 'PGOMonitor.called["' + func.ident + '"] = 1;\n'; - } + if (PGO) { + func.JS += INDENTATION + 'PGOMonitor.called["' + func.ident + '"] = 1;\n'; + } - if (ASM_JS) { - // spell out argument types - func.params.forEach(function(param) { - func.JS += INDENTATION + param.ident + ' = ' + asmCoercion(param.ident, param.type) + ';\n'; - }); + if (ASM_JS) { + // spell out argument types + func.params.forEach(function(param) { + func.JS += INDENTATION + param.ident + ' = ' + asmCoercion(param.ident, param.type) + ';\n'; + }); - // spell out local variables - var vars = values(func.variables).filter(function(v) { return v.origin != 'funcparam' }); - if (vars.length > 0) { - var chunkSize = 8; - var chunks = []; - var i = 0; - while (i < vars.length) { - chunks.push(vars.slice(i, i+chunkSize)); - i += chunkSize; - } - for (i = 0; i < chunks.length; i++) { - func.JS += INDENTATION + 'var ' + chunks[i].map(function(v) { - var type = getImplementationType(v); - if (!isIllegalType(type) || v.ident.indexOf('$', 1) > 0) { // not illegal, or a broken up illegal - return v.ident + ' = ' + asmInitializer(type); //, func.variables[v.ident].impl); - } else { - return range(Math.ceil(getBits(type)/32)).map(function(i) { - return v.ident + '$' + i + '= 0'; - }).join(','); - } - }).join(', ') + ';\n'; - } + // spell out local variables + var vars = values(func.variables).filter(function(v) { return v.origin != 'funcparam' }); + if (vars.length > 0) { + var chunkSize = 8; + var chunks = []; + var i = 0; + while (i < vars.length) { + chunks.push(vars.slice(i, i+chunkSize)); + i += chunkSize; + } + for (i = 0; i < chunks.length; i++) { + func.JS += INDENTATION + 'var ' + chunks[i].map(function(v) { + var type = getImplementationType(v); + if (!isIllegalType(type) || v.ident.indexOf('$', 1) > 0) { // not illegal, or a broken up illegal + return v.ident + ' = ' + asmInitializer(type); //, func.variables[v.ident].impl); + } else { + return range(Math.ceil(getBits(type)/32)).map(function(i) { + return v.ident + '$' + i + '= 0'; + }).join(','); + } + }).join(', ') + ';\n'; } } + } - if (true) { // TODO: optimize away when not needed - if (CLOSURE_ANNOTATIONS) func.JS += '/** @type {number} */'; - func.JS += INDENTATION + 'var label = 0;\n'; - } + if (true) { // TODO: optimize away when not needed + if (CLOSURE_ANNOTATIONS) func.JS += '/** @type {number} */'; + func.JS += INDENTATION + 'var label = 0;\n'; + } - if (ASM_JS) { - var hasByVal = false; - func.params.forEach(function(param) { - hasByVal = hasByVal || param.byVal; - }); - if (hasByVal) { - func.JS += INDENTATION + 'var tempParam = 0;\n'; - } + if (ASM_JS) { + var hasByVal = false; + func.params.forEach(function(param) { + hasByVal = hasByVal || param.byVal; + }); + if (hasByVal) { + func.JS += INDENTATION + 'var tempParam = 0;\n'; } + } + + if (func.hasVarArgsCall) { + func.JS += INDENTATION + 'var tempVarArgs = 0;\n'; + } + + // Prepare the stack, if we need one. If we have other stack allocations, force the stack to be set up. + func.JS += INDENTATION + RuntimeGenerator.stackEnter(func.initialStack, func.otherStackAllocations) + ';\n'; - if (func.hasVarArgsCall) { - func.JS += INDENTATION + 'var tempVarArgs = 0;\n'; + // Make copies of by-value params + // XXX It is not clear we actually need this. While without this we fail, it does look like + // Clang normally does the copy itself, in the calling function. We only need this code + // when Clang optimizes the code and passes the original, not the copy, to the other + // function. But Clang still copies, the copy is just unused! Need to figure out if that + // is caused by our running just some optimizations (the safe ones), or if its a bug + // in Clang, or a bug in our understanding of the IR. + func.params.forEach(function(param) { + if (param.byVal) { + var type = removePointing(param.type); + var typeInfo = Types.types[type]; + func.JS += INDENTATION + (ASM_JS ? '' : 'var ') + 'tempParam = ' + param.ident + '; ' + param.ident + ' = ' + RuntimeGenerator.stackAlloc(typeInfo.flatSize) + ';' + + makeCopyValues(param.ident, 'tempParam', typeInfo.flatSize, 'null', null, param.byVal) + ';\n'; } + }); - // Prepare the stack, if we need one. If we have other stack allocations, force the stack to be set up. - func.JS += INDENTATION + RuntimeGenerator.stackEnter(func.initialStack, func.otherStackAllocations) + ';\n'; + if (LABEL_DEBUG && functionNameFilterTest(func.ident)) func.JS += " Module.print(INDENT + ' Entering: " + func.ident + ": ' + Array.prototype.slice.call(arguments)); INDENT += ' ';\n"; - // Make copies of by-value params - // XXX It is not clear we actually need this. While without this we fail, it does look like - // Clang normally does the copy itself, in the calling function. We only need this code - // when Clang optimizes the code and passes the original, not the copy, to the other - // function. But Clang still copies, the copy is just unused! Need to figure out if that - // is caused by our running just some optimizations (the safe ones), or if its a bug - // in Clang, or a bug in our understanding of the IR. - func.params.forEach(function(param) { - if (param.byVal) { - var type = removePointing(param.type); - var typeInfo = Types.types[type]; - func.JS += INDENTATION + (ASM_JS ? '' : 'var ') + 'tempParam = ' + param.ident + '; ' + param.ident + ' = ' + RuntimeGenerator.stackAlloc(typeInfo.flatSize) + ';' + - makeCopyValues(param.ident, 'tempParam', typeInfo.flatSize, 'null', null, param.byVal) + ';\n'; + // Walk function blocks and generate JS + function walkBlock(block, indent) { + if (!block) return ''; + dprint('relooping', 'walking block: ' + block.type + ',' + block.entries + ' : ' + block.labels.length); + function getLabelLines(label, indent, relooping) { + if (!label) return ''; + var ret = ''; + if ((LABEL_DEBUG >= 2) && functionNameFilterTest(func.ident)) { + ret += indent + "Module.print(INDENT + '" + func.ident + ":" + label.ident + "');\n"; } - }); - - if (LABEL_DEBUG && functionNameFilterTest(func.ident)) func.JS += " Module.print(INDENT + ' Entering: " + func.ident + ": ' + Array.prototype.slice.call(arguments)); INDENT += ' ';\n"; - - // Walk function blocks and generate JS - function walkBlock(block, indent) { - if (!block) return ''; - dprint('relooping', 'walking block: ' + block.type + ',' + block.entries + ' : ' + block.labels.length); - function getLabelLines(label, indent, relooping) { - if (!label) return ''; - var ret = ''; - if ((LABEL_DEBUG >= 2) && functionNameFilterTest(func.ident)) { - ret += indent + "Module.print(INDENT + '" + func.ident + ":" + label.ident + "');\n"; - } - if (EXECUTION_TIMEOUT > 0) { - ret += indent + 'if (Date.now() - START_TIME >= ' + (EXECUTION_TIMEOUT*1000) + ') throw "Timed out!" + (new Error().stack);\n'; - } - - if (PRINT_SPLIT_FILE_MARKER && Debugging.on && Debugging.getAssociatedSourceFile(line.lineNum)) { - // Overwrite the associated source file for every line. The last line should contain the source file associated to - // the return value/address of outer most block (the marked function). - associatedSourceFile = Debugging.getAssociatedSourceFile(line.lineNum); - } - - // for special labels we care about (for phi), mark that we visited them - var i = 0; - return ret + label.lines.map(function(line) { - var JS = line.JS; - if (relooping && i == label.lines.length-1) { - if (line.intertype == 'branch' || line.intertype == 'switch') { - JS = ''; // just branching operations - done in the relooper, so nothing need be done here - } else if (line.intertype == 'invoke') { - JS = line.reloopingJS; // invokes have code that is not rendered in the relooper (the call inside a try-catch) - } - } - i++; - // invoke instructions span two lines, and the debug info is located - // on the second line, hence the +1 - return JS + (Debugging.on ? Debugging.getComment(line.lineNum + (line.intertype === 'invoke' ? 1 : 0)) : ''); - }) - .join('\n') - .split('\n') // some lines include line breaks - .map(function(line) { return indent + line }) - .join('\n'); + if (EXECUTION_TIMEOUT > 0) { + ret += indent + 'if (Date.now() - START_TIME >= ' + (EXECUTION_TIMEOUT*1000) + ') throw "Timed out!" + (new Error().stack);\n'; } - var ret = ''; - if (!RELOOP || func.forceEmulated) { // TODO: also if just 1 label? - if (block.labels.length > 1) { - if (block.entries.length == 1) { - ret += indent + 'label = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + getOriginalLabelId(block.entries[0]) + ' */' : '') + '\n'; - } // otherwise, should have been set before! - if (func.setjmpTable) { - if (!ASM_JS) { - var setjmpTable = {}; - ret += indent + 'var mySetjmpIds = {};\n'; - ret += indent + 'var setjmpTable = {'; - func.setjmpTable.forEach(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into - ret += '"' + getLabelId(triple.oldLabel) + '": ' + 'function(value) { label = ' + getLabelId(triple.newLabel) + '; ' + triple.assignTo + ' = value },'; - }); - ret += 'dummy: 0'; - ret += '};\n'; - } else { - ret += 'var setjmpLabel = 0;\n'; - ret += 'var setjmpTable = ' + RuntimeGenerator.stackAlloc(4 * (MAX_SETJMPS + 1) * 2) + ';\n'; - ret += makeSetValue('setjmpTable', '0', '0', 'i32') + ';'; // initialize first entry to 0 - } - } - ret += indent + 'while(1) '; - if (func.setjmpTable && !ASM_JS) { - ret += 'try { '; - } - ret += 'switch(' + asmCoercion('label', 'i32') + ') {\n'; - ret += block.labels.map(function(label) { - return indent + INDENTATION + 'case ' + getLabelId(label.ident) + ': ' + (SHOW_LABELS ? '// ' + getOriginalLabelId(label.ident) : '') + '\n' - + getLabelLines(label, indent + INDENTATION + INDENTATION); - }).join('\n') + '\n'; - if (func.setjmpTable && ASM_JS) { - // emit a label in which we write to the proper local variable, before jumping to the actual label - ret += INDENTATION + 'case ' + SETJMP_LABEL + ': '; - ret += func.setjmpTable.map(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into - return 'if ((setjmpLabel|0) == ' + getLabelId(triple.oldLabel) + ') { ' + triple.assignTo + ' = threwValue; label = ' + triple.newLabel + ' }\n'; - }).join(' else '); - if (ASSERTIONS) ret += 'else abort(-3);\n'; - ret += '__THREW__ = threwValue = 0;\n'; - ret += 'break;\n'; - } - if (ASSERTIONS) ret += indent + INDENTATION + 'default: assert(0' + (ASM_JS ? '' : ', "bad label: " + label') + ');\n'; - ret += indent + '}\n'; - if (func.setjmpTable && !ASM_JS) { - ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; + + if (PRINT_SPLIT_FILE_MARKER && Debugging.on && Debugging.getAssociatedSourceFile(label.lines[label.lines.length-1].lineNum)) { + // Overwrite the associated source file for every line. The last line should contain the source file associated to + // the return value/address of outer most block (the marked function). + associatedSourceFile = Debugging.getAssociatedSourceFile(label.lines[label.lines.length-1].lineNum); + } + + // for special labels we care about (for phi), mark that we visited them + var i = 0; + return ret + label.lines.map(function(line) { + var JS = line.JS; + if (relooping && i == label.lines.length-1) { + if (line.intertype == 'branch' || line.intertype == 'switch') { + JS = ''; // just branching operations - done in the relooper, so nothing need be done here + } else if (line.intertype == 'invoke') { + JS = line.reloopingJS; // invokes have code that is not rendered in the relooper (the call inside a try-catch) } - } else { - ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0], indent); } - ret += '\n'; - } else { - // Reloop multiple blocks using the compiled relooper - - //Relooper.setDebug(1); - Relooper.init(); - - if (ASM_JS) Relooper.setAsmJSMode(1); - - var blockMap = {}; - // add blocks - for (var i = 0; i < block.labels.length; i++) { - var label = block.labels[i]; - var content = getLabelLines(label, '', true); - //printErr(func.ident + ' : ' + label.ident + ' : ' + content + '\n'); - var last = label.lines[label.lines.length-1]; - if (!last.signedIdent) { - blockMap[label.ident] = Relooper.addBlock(content); + i++; + // invoke instructions span two lines, and the debug info is located + // on the second line, hence the +1 + return JS + (Debugging.on ? Debugging.getComment(line.lineNum + (line.intertype === 'invoke' ? 1 : 0)) : ''); + }) + .join('\n') + .split('\n') // some lines include line breaks + .map(function(line) { return indent + line }) + .join('\n'); + } + var ret = ''; + if (!RELOOP || func.forceEmulated) { // TODO: also if just 1 label? + if (block.labels.length > 1) { + if (block.entries.length == 1) { + ret += indent + 'label = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + getOriginalLabelId(block.entries[0]) + ' */' : '') + '\n'; + } // otherwise, should have been set before! + if (func.setjmpTable) { + if (!ASM_JS) { + var setjmpTable = {}; + ret += indent + 'var mySetjmpIds = {};\n'; + ret += indent + 'var setjmpTable = {'; + func.setjmpTable.forEach(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into + ret += '"' + getLabelId(triple.oldLabel) + '": ' + 'function(value) { label = ' + getLabelId(triple.newLabel) + '; ' + triple.assignTo + ' = value },'; + }); + ret += 'dummy: 0'; + ret += '};\n'; } else { - assert(last.intertype == 'switch'); - blockMap[label.ident] = Relooper.addBlock(content, last.signedIdent); + ret += 'var setjmpLabel = 0;\n'; + ret += 'var setjmpTable = ' + RuntimeGenerator.stackAlloc(4 * (MAX_SETJMPS + 1) * 2) + ';\n'; + ret += makeSetValue('setjmpTable', '0', '0', 'i32') + ';'; // initialize first entry to 0 } } - // add branchings - function relevant(x) { return x && x.length > 2 ? x : 0 } // ignores ';' which valueJS and label*JS can be if empty - for (var i = 0; i < block.labels.length; i++) { - var label = block.labels[i]; - var ident = label.ident; - var last = label.lines[label.lines.length-1]; - //printErr('zz last ' + dump(last)); - if (last.intertype == 'branch') { - if (last.label) { // 1 target - Relooper.addBranch(blockMap[ident], blockMap[last.label], 0, relevant(last.labelJS)); - } else { // 2 targets - Relooper.addBranch(blockMap[ident], blockMap[last.labelTrue], last.valueJS, relevant(last.labelTrueJS)); - Relooper.addBranch(blockMap[ident], blockMap[last.labelFalse], 0, relevant(last.labelFalseJS)); - } - } else if (last.intertype == 'switch') { - last.groupedLabels.forEach(function(switchLabel) { - Relooper.addBranch(blockMap[ident], blockMap[switchLabel.label], switchLabel.value, relevant(switchLabel.labelJS)); - }); - Relooper.addBranch(blockMap[ident], blockMap[last.defaultLabel], 0, relevant(last.defaultLabelJS)); - } else if (last.intertype == 'invoke') { - Relooper.addBranch(blockMap[ident], blockMap[last.toLabel], '!__THREW__', relevant(last.toLabelJS)); - Relooper.addBranch(blockMap[ident], blockMap[last.unwindLabel], 0, relevant(last.unwindLabelJS)); - } else if (last.intertype in RELOOP_IGNORED_LASTS) { - } else { - throw 'unknown reloop last line: ' + last.intertype; + ret += indent + 'while(1) '; + if (func.setjmpTable && !ASM_JS) { + ret += 'try { '; + } + ret += 'switch(' + asmCoercion('label', 'i32') + ') {\n'; + ret += block.labels.map(function(label) { + return indent + INDENTATION + 'case ' + getLabelId(label.ident) + ': ' + (SHOW_LABELS ? '// ' + getOriginalLabelId(label.ident) : '') + '\n' + + getLabelLines(label, indent + INDENTATION + INDENTATION); + }).join('\n') + '\n'; + if (func.setjmpTable && ASM_JS) { + // emit a label in which we write to the proper local variable, before jumping to the actual label + ret += INDENTATION + 'case ' + SETJMP_LABEL + ': '; + ret += func.setjmpTable.map(function(triple) { // original label, label we created for right after the setjmp, variable setjmp result goes into + return 'if ((setjmpLabel|0) == ' + getLabelId(triple.oldLabel) + ') { ' + triple.assignTo + ' = threwValue; label = ' + triple.newLabel + ' }\n'; + }).join(' else '); + if (ASSERTIONS) ret += 'else abort(-3);\n'; + ret += '__THREW__ = threwValue = 0;\n'; + ret += 'break;\n'; + } + if (ASSERTIONS) ret += indent + INDENTATION + 'default: assert(0' + (ASM_JS ? '' : ', "bad label: " + label') + ');\n'; + ret += indent + '}\n'; + if (func.setjmpTable && !ASM_JS) { + ret += ' } catch(e) { if (!e.longjmp || !(e.id in mySetjmpIds)) throw(e); setjmpTable[setjmpLabels[e.id]](e.value) }'; + } + } else { + ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0], indent); + } + ret += '\n'; + } else { + // Reloop multiple blocks using the compiled relooper + + //Relooper.setDebug(1); + Relooper.init(); + + if (ASM_JS) Relooper.setAsmJSMode(1); + + var blockMap = {}; + // add blocks + for (var i = 0; i < block.labels.length; i++) { + var label = block.labels[i]; + var content = getLabelLines(label, '', true); + //printErr(func.ident + ' : ' + label.ident + ' : ' + content + '\n'); + var last = label.lines[label.lines.length-1]; + if (!last.signedIdent) { + blockMap[label.ident] = Relooper.addBlock(content); + } else { + assert(last.intertype == 'switch'); + blockMap[label.ident] = Relooper.addBlock(content, last.signedIdent); + } + } + // add branchings + function relevant(x) { return x && x.length > 2 ? x : 0 } // ignores ';' which valueJS and label*JS can be if empty + for (var i = 0; i < block.labels.length; i++) { + var label = block.labels[i]; + var ident = label.ident; + var last = label.lines[label.lines.length-1]; + //printErr('zz last ' + dump(last)); + if (last.intertype == 'branch') { + if (last.label) { // 1 target + Relooper.addBranch(blockMap[ident], blockMap[last.label], 0, relevant(last.labelJS)); + } else { // 2 targets + Relooper.addBranch(blockMap[ident], blockMap[last.labelTrue], last.valueJS, relevant(last.labelTrueJS)); + Relooper.addBranch(blockMap[ident], blockMap[last.labelFalse], 0, relevant(last.labelFalseJS)); } + } else if (last.intertype == 'switch') { + last.groupedLabels.forEach(function(switchLabel) { + Relooper.addBranch(blockMap[ident], blockMap[switchLabel.label], switchLabel.value, relevant(switchLabel.labelJS)); + }); + Relooper.addBranch(blockMap[ident], blockMap[last.defaultLabel], 0, relevant(last.defaultLabelJS)); + } else if (last.intertype == 'invoke') { + Relooper.addBranch(blockMap[ident], blockMap[last.toLabel], '!__THREW__', relevant(last.toLabelJS)); + Relooper.addBranch(blockMap[ident], blockMap[last.unwindLabel], 0, relevant(last.unwindLabelJS)); + } else if (last.intertype in RELOOP_IGNORED_LASTS) { + } else { + throw 'unknown reloop last line: ' + last.intertype; } - ret += Relooper.render(blockMap[block.entries[0]]); } - return ret; - } - func.JS += walkBlock(func.block, INDENTATION); - // Finalize function - if (LABEL_DEBUG && functionNameFilterTest(func.ident)) func.JS += " INDENT = INDENT.substr(0, INDENT.length-2);\n"; - // Ensure a return in a function with a type that returns, even if it lacks a return (e.g., if it aborts()) - if (RELOOP && func.lines.length > 0 && func.returnType != 'void') { - var returns = func.labels.filter(function(label) { return label.lines[label.lines.length-1].intertype == 'return' }).length; - if (returns == 0) func.JS += INDENTATION + 'return ' + asmCoercion('0', func.returnType); - } - func.JS += '}\n'; - - if (PRINT_SPLIT_FILE_MARKER) { - func.JS += '\n//FUNCTION_END_MARKER_OF_SOURCE_FILE_' + associatedSourceFile + '\n'; + ret += Relooper.render(blockMap[block.entries[0]]); + Relooper.cleanup(); } + return ret; + } + func.JS += walkBlock(func.block, INDENTATION); + // Finalize function + if (LABEL_DEBUG && functionNameFilterTest(func.ident)) func.JS += " INDENT = INDENT.substr(0, INDENT.length-2);\n"; + // Ensure a return in a function with a type that returns, even if it lacks a return (e.g., if it aborts()) + if (RELOOP && func.lines.length > 0 && func.returnType != 'void') { + var returns = func.labels.filter(function(label) { return label.lines[label.lines.length-1].intertype == 'return' }).length; + if (returns == 0) func.JS += INDENTATION + 'return ' + asmCoercion('0', func.returnType); + } + func.JS += '}\n'; + + if (PRINT_SPLIT_FILE_MARKER) { + func.JS += '\n//FUNCTION_END_MARKER_OF_SOURCE_FILE_' + associatedSourceFile + '\n'; + } - if (!ASM_JS && (EXPORT_ALL || (func.ident in EXPORTED_FUNCTIONS))) { - func.JS += 'Module["' + func.ident + '"] = ' + func.ident + ';'; - } + if (!ASM_JS && (EXPORT_ALL || (func.ident in EXPORTED_FUNCTIONS))) { + func.JS += 'Module["' + func.ident + '"] = ' + func.ident + ';'; + } - if (!ASM_JS && INLINING_LIMIT && func.lines.length >= INLINING_LIMIT) { - func.JS += func.ident + '["X"]=1;'; - } + if (!ASM_JS && INLINING_LIMIT && func.lines.length >= INLINING_LIMIT) { + func.JS += func.ident + '["X"]=1;'; + } - if (BUILD_AS_SHARED_LIB == 2) { - // TODO: make the assert conditional on ASSERTIONS - func.JS += 'if (globalScope) { assert(!globalScope["' + func.ident + '"]); globalScope["' + func.ident + '"] = ' + func.ident + ' }'; - } + if (BUILD_AS_SHARED_LIB == 2) { + // TODO: make the assert conditional on ASSERTIONS + func.JS += 'if (globalScope) { assert(!globalScope["' + func.ident + '"]); globalScope["' + func.ident + '"] = ' + func.ident + ' }'; + } - func.JS = func.JS.replace(/\n *;/g, '\n'); // remove unneeded lines + func.JS = func.JS.replace(/\n *;/g, '\n'); // remove unneeded lines - if (MAIN_MODULE || SIDE_MODULE) { - // Clone the function for each of its aliases. We do not know which name it will be used by in another module, - // and we do not have a heavyweight metadata system to resolve aliases during linking - var aliases = Functions.aliases[func.ident]; - if (aliases) { - var body = func.JS.substr(func.JS.indexOf('(')); - aliases.forEach(function(alias) { - func.JS += '\n' + 'function ' + alias + body; - }); - } + if (MAIN_MODULE || SIDE_MODULE) { + // Clone the function for each of its aliases. We do not know which name it will be used by in another module, + // and we do not have a heavyweight metadata system to resolve aliases during linking + var aliases = Functions.aliases[func.ident]; + if (aliases) { + var body = func.JS.substr(func.JS.indexOf('(')); + aliases.forEach(function(alias) { + func.JS += '\n' + 'function ' + alias + body; + }); } - - return func; } - }); + itemsDict.function.push(func); + } function getVarData(funcData, ident) { var local = funcData.variables[ident]; @@ -898,18 +863,6 @@ function JSify(data, functionsOnly, givenFunctions) { return data.impl; } - substrate.addActor('FuncLineTriager', { - processItem: function(item) { - if (item.intertype == 'function') { - this.forwardItem(item, 'FunctionReconstructor'); // XXX not really needed - } else if (item.JS) { - this.forwardItem(item, 'FunctionReconstructor'); // XXX not really needed - } else { - this.forwardItem(item, 'Intertype:' + item.intertype); - } - } - }); - // An interitem that has |assignTo| is an assign to that item. They call this function which // generates the actual assignment. function makeAssign(item) { @@ -944,29 +897,16 @@ function JSify(data, functionsOnly, givenFunctions) { } // Function lines - function makeFuncLineActor(intertype, func) { - return substrate.addActor('Intertype:' + intertype, { - processItem: function(item) { - item.JS = func(item); - if (!item.JS) throw "No JS generated for " + dump((item.funcData=null,item)); - if (item.assignTo) { - makeAssign(item); - if (!item.JS) throw "No assign JS generated for " + dump(item); - } - this.forwardItem(item, 'FunctionReconstructor'); - } - }); - } - makeFuncLineActor('value', function(item) { + function valueHandler(item) { return item.ident; - }); - makeFuncLineActor('noop', function(item) { + } + function noopHandler(item) { return ';'; - }); - makeFuncLineActor('var', function(item) { // assigns into phis become simple vars + } + function varHandler(item) { // assigns into phis become simple vars return ASM_JS ? ';' : ('var ' + item.ident + ';'); - }); - makeFuncLineActor('store', function(item) { + } + function storeHandler(item) { var value = finalizeLLVMParameter(item.value); if (pointingLevels(item.pointerType) == 1) { value = parseNumerical(value, item.valueType); @@ -998,9 +938,9 @@ function JSify(data, functionsOnly, givenFunctions) { throw 'unknown [store] impl: ' + impl; } return null; - }); + } - makeFuncLineActor('deleted', function(item) { return ';' }); + function deletedHandler(item) { return ';' } function getOriginalLabelId(label) { var funcData = Framework.currItem.funcData; @@ -1113,7 +1053,7 @@ function JSify(data, functionsOnly, givenFunctions) { */ } - makeFuncLineActor('branch', function(item) { + function branchHandler(item) { var phiSets = calcPhiSets(item); if (!item.value) { return (item.labelJS = getPhiSetsForLabel(phiSets, item.label)) + makeBranch(item.label, item.currLabelId); @@ -1134,8 +1074,8 @@ function JSify(data, functionsOnly, givenFunctions) { return head + labelTrue + else_ + labelFalse + tail; } } - }); - makeFuncLineActor('switch', function(item) { + } + function switchHandler(item) { // use a switch if the range is not too big or sparse var minn = Infinity, maxx = -Infinity; item.switchLabels.forEach(function(switchLabel) { @@ -1174,7 +1114,8 @@ function JSify(data, functionsOnly, givenFunctions) { if (!useIfs) { ret += 'switch(' + signedIdent + ') {\n'; } - for (var targetLabel in targetLabels) { + // process target labels, sorting them so output is consistently ordered + keys(targetLabels).sort().forEach(function(targetLabel) { if (!first && useIfs) { ret += 'else '; } else { @@ -1202,7 +1143,7 @@ function JSify(data, functionsOnly, givenFunctions) { labelJS: phiSet }); } - } + }); var phiSet = item.defaultLabelJS = getPhiSetsForLabel(phiSets, item.defaultLabel); if (useIfs) { if (item.switchLabels.length > 0) ret += 'else {\n'; @@ -1219,8 +1160,8 @@ function JSify(data, functionsOnly, givenFunctions) { ret += ' ' + toNiceIdent(item.value); } return ret; - }); - makeFuncLineActor('return', function(item) { + } + function returnHandler(item) { var ret = RuntimeGenerator.stackExit(item.funcData.initialStack, item.funcData.otherStackAllocations) + ';\n'; if (LABEL_DEBUG && functionNameFilterTest(item.funcData.ident)) { ret += "Module.print(INDENT + 'Exiting: " + item.funcData.ident + "');\n" @@ -1233,9 +1174,9 @@ function JSify(data, functionsOnly, givenFunctions) { ret += ' ' + asmCoercion(value, item.type); } return ret + ';'; - }); - makeFuncLineActor('resume', function(item) { - if (DISABLE_EXCEPTION_CATCHING) return 'abort()'; + } + function resumeHandler(item) { + if (DISABLE_EXCEPTION_CATCHING && !(item.funcData.ident in EXCEPTION_CATCHING_WHITELIST)) return 'abort()'; if (item.ident == 0) { // No exception to resume, so we can just bail. // This is related to issue #917 and http://llvm.org/PR15518 @@ -1244,8 +1185,8 @@ function JSify(data, functionsOnly, givenFunctions) { // If there is no current exception, set this one as it (during a resume, the current exception can be wiped out) var ptr = makeStructuralAccess(item.ident, 0); return '___resumeException(' + asmCoercion(ptr, 'i32') + ')'; - }); - makeFuncLineActor('invoke', function(item) { + } + function invokeHandler(item) { // Wrapping in a function lets us easily return values if we are // in an assignment var disabled = DISABLE_EXCEPTION_CATCHING == 2 && !(item.funcData.ident in EXCEPTION_CATCHING_WHITELIST); @@ -1291,14 +1232,14 @@ function JSify(data, functionsOnly, givenFunctions) { ret += 'if (!__THREW__) { ' + item.toLabelJS + makeBranch(item.toLabel, item.currLabelId) + ' } else { ' + item.unwindLabelJS + makeBranch(item.unwindLabel, item.currLabelId) + ' }'; return ret; - }); - makeFuncLineActor('atomic', function(item) { + } + function atomicHandler(item) { var type = item.params[0].type; var param1 = finalizeLLVMParameter(item.params[0]); var param2 = finalizeLLVMParameter(item.params[1]); switch (item.op) { - case 'add': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue+' + param2, type, null, null, null, null, ',') + ',tempValue)'; - case 'sub': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue-' + param2, type, null, null, null, null, ',') + ',tempValue)'; + case 'add': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, asmCoercion('tempValue+' + param2, type), type, null, null, null, null, ',') + ',tempValue)'; + case 'sub': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, asmCoercion('tempValue-' + param2, type), type, null, null, null, null, ',') + ',tempValue)'; case 'or': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue|' + param2, type, null, null, null, null, ',') + ',tempValue)'; case 'and': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue&' + param2, type, null, null, null, null, ',') + ',tempValue)'; case 'xor': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, 'tempValue^' + param2, type, null, null, null, null, ',') + ',tempValue)'; @@ -1309,9 +1250,9 @@ function JSify(data, functionsOnly, givenFunctions) { } default: throw 'unhandled atomic op: ' + item.op; } - }); - makeFuncLineActor('landingpad', function(item) { - if (DISABLE_EXCEPTION_CATCHING && USE_TYPED_ARRAYS == 2) { + } + function landingpadHandler(item) { + if (DISABLE_EXCEPTION_CATCHING && !(item.funcData.ident in EXCEPTION_CATCHING_WHITELIST) && USE_TYPED_ARRAYS == 2) { ret = makeVarDef(item.assignTo) + '$0 = 0; ' + item.assignTo + '$1 = 0;'; item.assignTo = null; if (VERBOSE) warnOnce('landingpad, but exceptions are disabled!'); @@ -1324,18 +1265,18 @@ function JSify(data, functionsOnly, givenFunctions) { item.assignTo = null; } return ret; - }); - makeFuncLineActor('load', function(item) { + } + function loadHandler(item) { var value = finalizeLLVMParameter(item.pointer); var impl = item.ident ? getVarImpl(item.funcData, item.ident) : VAR_EMULATED; switch (impl) { case VAR_NATIVIZED: { if (isNumber(item.ident)) { - item.assignTo = null; // Direct read from a memory address; this may be an intentional segfault, if not, it is a bug in the source if (ASM_JS) { - return 'abort(' + item.ident + ')'; + return asmCoercion('abort(' + item.ident + ')', item.type); } else { + item.assignTo = null; return 'throw "fault on read from ' + item.ident + '";'; } } @@ -1344,8 +1285,8 @@ function JSify(data, functionsOnly, givenFunctions) { case VAR_EMULATED: return makeGetValue(value, 0, item.type, 0, item.unsigned, 0, item.align); default: throw "unknown [load] impl: " + impl; } - }); - makeFuncLineActor('extractvalue', function(item) { + } + function extractvalueHandler(item) { assert(item.indexes.length == 1); // TODO: use getelementptr parsing stuff, for depth. For now, we assume that LLVM aggregates are flat, // and we emulate them using simple JS objects { f1: , f2: , } etc., for speed var index = item.indexes[0][0].text; @@ -1358,8 +1299,8 @@ function JSify(data, functionsOnly, givenFunctions) { return 'var ' + assignTo + '$0 = ' + item.ident + '.f' + index + '[0];' + 'var ' + assignTo + '$1 = ' + item.ident + '.f' + index + '[1];'; } - }); - makeFuncLineActor('insertvalue', function(item) { + } + function insertvalueHandler(item) { assert(item.indexes.length == 1); // TODO: see extractvalue var ret = '(', ident; if (item.ident === '0') { @@ -1367,24 +1308,24 @@ function JSify(data, functionsOnly, givenFunctions) { ret += item.ident + ' = [' + makeEmptyStruct(item.type) + '], '; } return ret + item.ident + '.f' + item.indexes[0][0].text + ' = ' + finalizeLLVMParameter(item.value) + ', ' + item.ident + ')'; - }); - makeFuncLineActor('indirectbr', function(item) { + } + function indirectbrHandler(item) { var phiSets = calcPhiSets(item); var js = 'var ibr = ' + finalizeLLVMParameter(item.value) + ';\n'; for (var targetLabel in phiSets) { js += 'if (' + makeComparison('ibr', '==', targetLabel, 'i32') + ') { ' + getPhiSetsForLabel(phiSets, targetLabel) + ' }\n'; } return js + makeBranch('ibr', item.currLabelId, true); - }); - makeFuncLineActor('alloca', function(item) { + } + function allocaHandler(item) { if (typeof item.allocatedIndex === 'number') { if (item.allocatedSize === 0) return ''; // This will not actually be shown - it's nativized return asmCoercion(getFastValue('sp', '+', item.allocatedIndex.toString()), 'i32'); } else { return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.allocatedNum)); } - }); - makeFuncLineActor('va_arg', function(item) { + } + function va_argHandler(item) { assert(TARGET_LE32); var ident = item.value.ident; var move = Runtime.STACK_ALIGN; @@ -1393,11 +1334,11 @@ function JSify(data, functionsOnly, givenFunctions) { return '(tempInt=' + makeGetValue(ident, Runtime.QUANTUM_SIZE, '*') + ',' + makeSetValue(ident, Runtime.QUANTUM_SIZE, 'tempInt + ' + move, '*') + ',' + makeGetValue(makeGetValue(ident, 0, '*'), 'tempInt', item.type) + ')'; - }); + } - makeFuncLineActor('mathop', processMathop); + var mathopHandler = processMathop; - makeFuncLineActor('bitcast', function(item) { + function bitcastHandler(item) { var temp = { op: 'bitcast', variant: null, type: item.type, assignTo: item.assignTo, @@ -1406,7 +1347,7 @@ function JSify(data, functionsOnly, givenFunctions) { var ret = processMathop(temp); if (!temp.assignTo) item.assignTo = null; // If the assign was stolen, propagate that return ret; - }); + } function makeFunctionCall(ident, params, funcData, type, forceByPointer, hasReturn, invoke) { // We cannot compile assembly. See comment in intertyper.js:'Call' @@ -1614,33 +1555,26 @@ function JSify(data, functionsOnly, givenFunctions) { return js; } - makeFuncLineActor('getelementptr', function(item) { return finalizeLLVMFunctionCall(item) }); - makeFuncLineActor('call', function(item) { + function getelementptrHandler(item) { return finalizeLLVMFunctionCall(item) } + function callHandler(item) { if (item.standalone && LibraryManager.isStubFunction(item.ident)) return ';'; var ret = makeFunctionCall(item.ident, item.params, item.funcData, item.type, false, !!item.assignTo || !item.standalone) + (item.standalone ? ';' : ''); return makeVarArgsCleanup(ret); - }); + } - makeFuncLineActor('unreachable', function(item) { + function unreachableHandler(item) { var ret = ''; if (ASM_JS && item.funcData.returnType != 'void') ret = 'return ' + asmCoercion('0', item.funcData.returnType) + ';'; if (ASSERTIONS) { ret = (ASM_JS ? 'abort()' : 'throw "Reached an unreachable!"') + ';' + ret; } return ret || ';'; - }); + } // Final combiner - function finalCombiner(items) { + function finalCombiner() { dprint('unparsedFunctions', 'Starting finalCombiner'); - var itemsDict = { type: [], GlobalVariableStub: [], functionStub: [], function: [], GlobalVariable: [], GlobalVariablePostSet: [] }; - items.forEach(function(item) { - item.lines = null; - var small = { intertype: item.intertype, JS: item.JS, ident: item.ident, dependencies: item.dependencies }; // Release memory - itemsDict[small.intertype].push(small); - }); - items = null; var splitPostSets = splitter(itemsDict.GlobalVariablePostSet, function(x) { return x.ident && x.dependencies }); itemsDict.GlobalVariablePostSet = splitPostSets.leftIn; @@ -1897,7 +1831,7 @@ function JSify(data, functionsOnly, givenFunctions) { Functions.implementedFunctions[func.ident] = Functions.getSignature(func.returnType, func.params.map(function(param) { return param.type })); }); } - substrate.addItems(data.functionStubs, 'FunctionStub'); + data.functionStubs.forEach(functionStubHandler); assert(data.functions.length == 0); } else { if (phase == 'pre') { @@ -1906,21 +1840,21 @@ function JSify(data, functionsOnly, givenFunctions) { data.globalVariables._llvm_global_ctors.ctors.unshift('runPostSets'); // run postsets right before global initializers hasCtors = true; } else { - substrate.addItems([{ + globalVariableHandler({ intertype: 'GlobalVariableStub', ident: '_llvm_global_ctors', type: '[1 x { i32, void ()* }]', ctors: ["runPostSets"], - }], 'GlobalVariable'); + }); } } - substrate.addItems(sortGlobals(data.globalVariables), 'GlobalVariable'); - substrate.addItems(data.aliass, 'Alias'); - substrate.addItems(data.functions, 'FunctionSplitter'); + sortGlobals(data.globalVariables).forEach(globalVariableHandler); + data.aliass.forEach(aliasHandler); + data.functions.forEach(functionSplitter); } - finalCombiner(substrate.solve()); + finalCombiner(); dprint('framework', 'Big picture: Finishing JSifier, main pass=' + mainPass); } diff --git a/src/library.js b/src/library.js index 7a144951..abee70c4 100644 --- a/src/library.js +++ b/src/library.js @@ -628,24 +628,13 @@ LibraryManager.library = { // http://pubs.opengroup.org/onlinepubs/000095399/functions/chdir.html // NOTE: The path argument may be a string, to simplify fchdir(). if (typeof path !== 'string') path = Pointer_stringify(path); - var lookup; try { - lookup = FS.lookupPath(path, { follow: true }); + FS.chdir(path); + return 0; } catch (e) { FS.handleFSError(e); return -1; } - if (!FS.isDir(lookup.node.mode)) { - ___setErrNo(ERRNO_CODES.ENOTDIR); - return -1; - } - var err = FS.nodePermissions(lookup.node, 'x'); - if (err) { - ___setErrNo(err); - return -1; - } - FS.currentPath = lookup.path; - return 0; }, chown__deps: ['$FS', '__setErrNo', '$ERRNO_CODES'], chown: function(path, owner, group, dontResolveLastLink) { @@ -849,12 +838,14 @@ LibraryManager.library = { if (size == 0) { ___setErrNo(ERRNO_CODES.EINVAL); return 0; - } else if (size < FS.currentPath.length + 1) { + } + var cwd = FS.cwd(); + if (size < cwd.length + 1) { ___setErrNo(ERRNO_CODES.ERANGE); return 0; } else { - for (var i = 0; i < FS.currentPath.length; i++) { - {{{ makeSetValue('buf', 'i', 'FS.currentPath.charCodeAt(i)', 'i8') }}} + for (var i = 0; i < cwd.length; i++) { + {{{ makeSetValue('buf', 'i', 'cwd.charCodeAt(i)', 'i8') }}} } {{{ makeSetValue('buf', 'i', '0', 'i8') }}} return buf; @@ -1600,12 +1591,6 @@ LibraryManager.library = { __scanString.whiteSpace[{{{ charCode('\v') }}}] = 1; __scanString.whiteSpace[{{{ charCode('\f') }}}] = 1; __scanString.whiteSpace[{{{ charCode('\r') }}}] = 1; - __scanString.whiteSpace[' '] = 1; - __scanString.whiteSpace['\t'] = 1; - __scanString.whiteSpace['\n'] = 1; - __scanString.whiteSpace['\v'] = 1; - __scanString.whiteSpace['\f'] = 1; - __scanString.whiteSpace['\r'] = 1; } // Supports %x, %4x, %d.%d, %lld, %s, %f, %lf. // TODO: Support all format specifiers. @@ -1645,7 +1630,7 @@ LibraryManager.library = { if (nextC > 0) { var maxx = 1; if (nextC > formatIndex+1) { - var sub = format.substring(formatIndex+1, nextC) + var sub = format.substring(formatIndex+1, nextC); maxx = parseInt(sub); if (maxx != sub) maxx = 0; } @@ -1663,6 +1648,53 @@ LibraryManager.library = { } } + // handle %[...] + if (format[formatIndex] === '%' && format.indexOf('[', formatIndex+1) > 0) { + var match = /\%([0-9]*)\[(\^)?(\]?[^\]]*)\]/.exec(format.substring(formatIndex)); + if (match) { + var maxNumCharacters = parseInt(match[1]) || Infinity; + var negateScanList = (match[2] === '^'); + var scanList = match[3]; + + // expand "middle" dashs into character sets + var middleDashMatch; + while ((middleDashMatch = /([^\-])\-([^\-])/.exec(scanList))) { + var rangeStartCharCode = middleDashMatch[1].charCodeAt(0); + var rangeEndCharCode = middleDashMatch[2].charCodeAt(0); + for (var expanded = ''; rangeStartCharCode <= rangeEndCharCode; expanded += String.fromCharCode(rangeStartCharCode++)); + scanList = scanList.replace(middleDashMatch[1] + '-' + middleDashMatch[2], expanded); + } + + var argPtr = {{{ makeGetValue('varargs', 'argIndex', 'void*') }}}; + argIndex += Runtime.getAlignSize('void*', null, true); + fields++; + + for (var i = 0; i < maxNumCharacters; i++) { + next = get(); + if (negateScanList) { + if (scanList.indexOf(String.fromCharCode(next)) < 0) { + {{{ makeSetValue('argPtr++', 0, 'next', 'i8') }}}; + } else { + unget(); + break; + } + } else { + if (scanList.indexOf(String.fromCharCode(next)) >= 0) { + {{{ makeSetValue('argPtr++', 0, 'next', 'i8') }}}; + } else { + unget(); + break; + } + } + } + + // write out null-terminating character + {{{ makeSetValue('argPtr++', 0, '0', 'i8') }}}; + formatIndex += match[0].length; + + continue; + } + } // remove whitespace while (1) { next = get(); @@ -1724,6 +1756,17 @@ LibraryManager.library = { } else { next = get(); var first = true; + + // Strip the optional 0x prefix for %x. + if ((type == 'x' || type == 'X') && (next == {{{ charCode('0') }}})) { + var peek = get(); + if (peek == {{{ charCode('x') }}} || peek == {{{ charCode('X') }}}) { + next = get(); + } else { + unget(); + } + } + while ((curr < max_ || isNaN(max_)) && next > 0) { if (!(next in __scanString.whiteSpace) && // stop on whitespace (type == 's' || @@ -1785,7 +1828,7 @@ LibraryManager.library = { break; } fields++; - } else if (format[formatIndex] in __scanString.whiteSpace) { + } else if (format[formatIndex].charCodeAt(0) in __scanString.whiteSpace) { next = get(); while (next in __scanString.whiteSpace) { if (next <= 0) break mainLoop; // End of input. @@ -1856,6 +1899,7 @@ LibraryManager.library = { var flagLeftAlign = false; var flagAlternative = false; var flagZeroPad = false; + var flagPadSign = false; flagsLoop: while (1) { switch (next) { case {{{ charCode('+') }}}: @@ -1874,6 +1918,9 @@ LibraryManager.library = { flagZeroPad = true; break; } + case {{{ charCode(' ') }}}: + flagPadSign = true; + break; default: break flagsLoop; } @@ -2040,14 +2087,20 @@ LibraryManager.library = { } // Add sign if needed - if (flagAlwaysSigned) { - if (currArg < 0) { - prefix = '-' + prefix; - } else { + if (currArg >= 0) { + if (flagAlwaysSigned) { prefix = '+' + prefix; + } else if (flagPadSign) { + prefix = ' ' + prefix; } } + // Move sign to prefix so we zero-pad after the sign + if (argText.charAt(0) == '-') { + prefix = '-' + prefix; + argText = argText.substr(1); + } + // Add padding. while (prefix.length + argText.length < width) { if (flagLeftAlign) { @@ -2130,8 +2183,12 @@ LibraryManager.library = { if (next == {{{ charCode('E') }}}) argText = argText.toUpperCase(); // Add sign. - if (flagAlwaysSigned && currArg >= 0) { - argText = '+' + argText; + if (currArg >= 0) { + if (flagAlwaysSigned) { + argText = '+' + argText; + } else if (flagPadSign) { + argText = ' ' + argText; + } } } @@ -2769,6 +2826,13 @@ LibraryManager.library = { asprintf: function(s, format, varargs) { return _sprintf(-s, format, varargs); }, + dprintf__deps: ['_formatString', 'write'], + dprintf: function(fd, format, varargs) { + var result = __formatString(format, varargs); + var stack = Runtime.stackSave(); + var ret = _write(fd, allocate(result, 'i8', ALLOC_STACK), result.length); + Runtime.stackRestore(stack); + }, #if TARGET_X86 // va_arg is just like our varargs @@ -2777,6 +2841,7 @@ LibraryManager.library = { vprintf: 'printf', vsprintf: 'sprintf', vasprintf: 'asprintf', + vdprintf: 'dprintf', vscanf: 'scanf', vfscanf: 'fscanf', vsscanf: 'sscanf', @@ -2804,6 +2869,10 @@ LibraryManager.library = { vasprintf: function(s, format, va_arg) { return _asprintf(s, format, {{{ makeGetValue('va_arg', 0, '*') }}}); }, + vdprintf__deps: ['dprintf'], + vdprintf: function (fd, format, va_arg) { + return _dprintf(fd, format, {{{ makeGetValue('va_arg', 0, '*') }}}); + }, vscanf__deps: ['scanf'], vscanf: function(format, va_arg) { return _scanf(format, {{{ makeGetValue('va_arg', 0, '*') }}}); @@ -3674,6 +3743,7 @@ LibraryManager.library = { }, // We always assume ASCII locale. strcoll: 'strcmp', + strcoll_l: 'strcmp', strcasecmp__asm: true, strcasecmp__sig: 'iii', @@ -3940,6 +4010,7 @@ LibraryManager.library = { } }, _toupper: 'toupper', + toupper_l: 'toupper', tolower__asm: true, tolower__sig: 'ii', @@ -3950,54 +4021,65 @@ LibraryManager.library = { return (chr - {{{ charCode('A') }}} + {{{ charCode('a') }}})|0; }, _tolower: 'tolower', + tolower_l: 'tolower', // The following functions are defined as macros in glibc. islower: function(chr) { return chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}; }, + islower_l: 'islower', isupper: function(chr) { return chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}; }, + isupper_l: 'isupper', isalpha: function(chr) { return (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); }, + isalpha_l: 'isalpha', isdigit: function(chr) { return chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}; }, - isdigit_l: 'isdigit', // no locale support yet + isdigit_l: 'isdigit', isxdigit: function(chr) { return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('f') }}}) || (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('F') }}}); }, - isxdigit_l: 'isxdigit', // no locale support yet + isxdigit_l: 'isxdigit', isalnum: function(chr) { return (chr >= {{{ charCode('0') }}} && chr <= {{{ charCode('9') }}}) || (chr >= {{{ charCode('a') }}} && chr <= {{{ charCode('z') }}}) || (chr >= {{{ charCode('A') }}} && chr <= {{{ charCode('Z') }}}); }, + isalnum_l: 'isalnum', ispunct: function(chr) { return (chr >= {{{ charCode('!') }}} && chr <= {{{ charCode('/') }}}) || (chr >= {{{ charCode(':') }}} && chr <= {{{ charCode('@') }}}) || (chr >= {{{ charCode('[') }}} && chr <= {{{ charCode('`') }}}) || (chr >= {{{ charCode('{') }}} && chr <= {{{ charCode('~') }}}); }, + ispunct_l: 'ispunct', isspace: function(chr) { return (chr == 32) || (chr >= 9 && chr <= 13); }, + isspace_l: 'isspace', isblank: function(chr) { return chr == {{{ charCode(' ') }}} || chr == {{{ charCode('\t') }}}; }, + isblank_l: 'isblank', iscntrl: function(chr) { return (0 <= chr && chr <= 0x1F) || chr === 0x7F; }, + iscntrl_l: 'iscntrl', isprint: function(chr) { return 0x1F < chr && chr < 0x7F; }, + isprint_l: 'isprint', isgraph: function(chr) { return 0x20 < chr && chr < 0x7F; }, + isgraph_l: 'isgraph', // Lookup tables for glibc ctype implementation. __ctype_b_loc: function() { // http://refspecs.freestandards.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/baselib---ctype-b-loc.html @@ -4098,7 +4180,7 @@ LibraryManager.library = { llvm_va_end: function() {}, llvm_va_copy: function(ppdest, ppsrc) { - // copy the list start + // copy the list start {{{ makeCopyValues('ppdest', 'ppsrc', Runtime.QUANTUM_SIZE, 'null', null, 1) }}}; // copy the list's current offset (will be advanced with each call to va_arg) @@ -6270,11 +6352,15 @@ LibraryManager.library = { // locale.h // ========================================================================== + newlocale__deps: ['malloc'], newlocale: function(mask, locale, base) { - return 0; + return _malloc({{{ QUANTUM_SIZE}}}); }, - freelocale: function(locale) {}, + freelocale__deps: ['free'], + freelocale: function(locale) { + _free(locale); + }, uselocale: function(locale) { return 0; @@ -8619,7 +8705,7 @@ function autoAddDeps(object, name) { } // Add aborting stubs for various libc stuff needed by libc++ -['pthread_cond_signal', 'pthread_equal', 'wcstol', 'wcstoll', 'wcstoul', 'wcstoull', 'wcstof', 'wcstod', 'wcstold', 'swprintf', 'pthread_join', 'pthread_detach', 'strcoll_l', 'strxfrm_l', 'wcscoll_l', 'toupper_l', 'tolower_l', 'iswspace_l', 'iswprint_l', 'iswcntrl_l', 'iswupper_l', 'iswlower_l', 'iswalpha_l', 'iswdigit_l', 'iswpunct_l', 'iswxdigit_l', 'iswblank_l', 'wcsxfrm_l', 'towupper_l', 'towlower_l', 'catgets', 'catopen', 'catclose'].forEach(function(aborter) { +['pthread_cond_signal', 'pthread_equal', 'wcstol', 'wcstoll', 'wcstoul', 'wcstoull', 'wcstof', 'wcstod', 'wcstold', 'pthread_join', 'pthread_detach', 'catgets', 'catopen', 'catclose'].forEach(function(aborter) { LibraryManager.library[aborter] = function() { throw 'TODO: ' + aborter }; }); diff --git a/src/library_fs.js b/src/library_fs.js index 8bf0a83a..c4b29227 100644 --- a/src/library_fs.js +++ b/src/library_fs.js @@ -1,5 +1,5 @@ mergeInto(LibraryManager.library, { - $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$VFS', '$PATH', '$TTY', '$MEMFS', 'stdin', 'stdout', 'stderr', 'fflush'], + $FS__deps: ['$ERRNO_CODES', '$ERRNO_MESSAGES', '__setErrNo', '$VFS', '$PATH', '$TTY', '$MEMFS', '$IDBFS', '$NODEFS', 'stdin', 'stdout', 'stderr', 'fflush'], $FS__postset: 'FS.staticInit();' + '__ATINIT__.unshift({ func: function() { if (!Module["noFSInit"] && !FS.init.initialized) FS.init() } });' + '__ATMAIN__.push({ func: function() { FS.ignorePermissions = false } });' + @@ -14,6 +14,7 @@ mergeInto(LibraryManager.library, { 'Module["FS_createDevice"] = FS.createDevice;', $FS: { root: null, + mounts: [], devices: [null], streams: [null], nextInode: 1, @@ -50,11 +51,8 @@ mergeInto(LibraryManager.library, { // // paths // - cwd: function() { - return FS.currentPath; - }, lookupPath: function(path, opts) { - path = PATH.resolve(FS.currentPath, path); + path = PATH.resolve(FS.cwd(), path); opts = opts || { recurse_count: 0 }; if (opts.recurse_count > 8) { // max recursive lookup of 8 @@ -309,7 +307,7 @@ mergeInto(LibraryManager.library, { if (!FS.isDir(node.mode)) { return ERRNO_CODES.ENOTDIR; } - if (FS.isRoot(node) || FS.getPath(node) === FS.currentPath) { + if (FS.isRoot(node) || FS.getPath(node) === FS.cwd()) { return ERRNO_CODES.EBUSY; } } else { @@ -422,17 +420,45 @@ mergeInto(LibraryManager.library, { // // core // + syncfs: function(populate, callback) { + if (typeof(populate) === 'function') { + callback = populate; + populate = false; + } + + var completed = 0; + var total = FS.mounts.length; + var done = function(err) { + if (err) { + return callback(err); + } + if (++completed >= total) { + callback(null); + } + }; + + // sync all mounts + for (var i = 0; i < FS.mounts.length; i++) { + var mount = FS.mounts[i]; + if (!mount.type.syncfs) { + done(null); + continue; + } + mount.type.syncfs(mount, populate, done); + } + }, mount: function(type, opts, mountpoint) { + var lookup; + if (mountpoint) { + lookup = FS.lookupPath(mountpoint, { follow: false }); + mountpoint = lookup.path; // use the absolute path + } var mount = { type: type, opts: opts, mountpoint: mountpoint, root: null }; - var lookup; - if (mountpoint) { - lookup = FS.lookupPath(mountpoint, { follow: false }); - } // create a root node for the fs var root = type.mount(mount); root.mount = mount; @@ -446,6 +472,8 @@ mergeInto(LibraryManager.library, { FS.root = mount.root; } } + // add to our cached list of mounts + FS.mounts.push(mount); return root; }, lookup: function(parent, name) { @@ -759,7 +787,6 @@ mergeInto(LibraryManager.library, { follow: !(flags & {{{ cDefine('O_NOFOLLOW') }}}) }); node = lookup.node; - path = lookup.path; } catch (e) { // ignore } @@ -791,10 +818,13 @@ mergeInto(LibraryManager.library, { if ((flags & {{{ cDefine('O_TRUNC')}}})) { FS.truncate(node, 0); } + // we've already handled these, don't pass down to the underlying vfs + flags &= ~({{{ cDefine('O_EXCL') }}} | {{{ cDefine('O_TRUNC') }}}); + // register the stream with the filesystem var stream = FS.createStream({ - path: path, node: node, + path: FS.getPath(node), // we want the absolute path to the node flags: flags, seekable: true, position: 0, @@ -959,8 +989,21 @@ mergeInto(LibraryManager.library, { // // module-level FS code - // TODO move to pre/postamble // + cwd: function() { + return FS.currentPath; + }, + chdir: function(path) { + var lookup = FS.lookupPath(path, { follow: true }); + if (!FS.isDir(lookup.node.mode)) { + throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); + } + var err = FS.nodePermissions(lookup.node, 'x'); + if (err) { + throw new FS.ErrnoError(err); + } + FS.currentPath = lookup.path; + }, createDefaultDirectories: function() { FS.mkdir('/tmp'); }, @@ -1367,7 +1410,10 @@ mergeInto(LibraryManager.library, { throw new FS.ErrnoError(ERRNO_CODES.EIO); } var contents = stream.node.contents; + if (position >= contents.length) + return 0; var size = Math.min(contents.length - position, length); + assert(size >= 0); if (contents.slice) { // normal array for (var i = 0; i < size; i++) { buffer[offset + i] = contents[position + i]; diff --git a/src/library_gl.js b/src/library_gl.js index 16ea5531..83e68777 100644 --- a/src/library_gl.js +++ b/src/library_gl.js @@ -217,6 +217,23 @@ var LibraryGL = { throw 'Invalid format (' + format + ')'; } break; + case 0x1403 /* GL_UNSIGNED_SHORT */: + if (format == 0x1902 /* GL_DEPTH_COMPONENT */) { + sizePerPixel = 2; + } else { + throw 'Invalid format (' + format + ')'; + } + break; + case 0x1405 /* GL_UNSIGNED_INT */: + if (format == 0x1902 /* GL_DEPTH_COMPONENT */) { + sizePerPixel = 4; + } else { + throw 'Invalid format (' + format + ')'; + } + break; + case 0x84FA /* UNSIGNED_INT_24_8_WEBGL */: + sizePerPixel = 4; + break; case 0x8363 /* GL_UNSIGNED_SHORT_5_6_5 */: case 0x8033 /* GL_UNSIGNED_SHORT_4_4_4_4 */: case 0x8034 /* GL_UNSIGNED_SHORT_5_5_5_1 */: @@ -244,6 +261,8 @@ var LibraryGL = { pixels = {{{ makeHEAPView('U8', 'pixels', 'pixels+bytes') }}}; } else if (type == 0x1406 /* GL_FLOAT */) { pixels = {{{ makeHEAPView('F32', 'pixels', 'pixels+bytes') }}}; + } else if (type == 0x1405 /* GL_UNSIGNED_INT */ || type == 0x84FA /* UNSIGNED_INT_24_8_WEBGL */) { + pixels = {{{ makeHEAPView('U32', 'pixels', 'pixels+bytes') }}}; } else { pixels = {{{ makeHEAPView('U16', 'pixels', 'pixels+bytes') }}}; } @@ -291,6 +310,9 @@ var LibraryGL = { Module.ctx.bufferSubData(Module.ctx.ARRAY_BUFFER, 0, HEAPU8.subarray(cb.ptr, cb.ptr + size)); +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(cb.size, cb.type, cb.stride, 0); +#endif Module.ctx.vertexAttribPointer(i, cb.size, cb.type, cb.normalized, cb.stride, 0); } }, @@ -302,6 +324,56 @@ var LibraryGL = { }, #endif +#if GL_ASSERTIONS + validateGLObjectID: function(objectHandleArray, objectID, callerFunctionName, objectReadableType) { + if (objectID != 0) { + if (objectHandleArray[objectID] === null) { + console.error(callerFunctionName + ' called with an already deleted ' + objectReadableType + ' ID ' + objectID + '!'); + } else if (!objectHandleArray[objectID]) { + console.error(callerFunctionName + ' called with an invalid ' + objectReadableType + ' ID ' + objectID + '!'); + } + } + }, + // Validates that user obeys GL spec #6.4: http://www.khronos.org/registry/webgl/specs/latest/1.0/#6.4 + validateVertexAttribPointer: function(dimension, dataType, stride, offset) { + var sizeBytes = 1; + switch(dataType) { + case 0x1400 /* GL_BYTE */: + case 0x1401 /* GL_UNSIGNED_BYTE */: + sizeBytes = 1; + break; + case 0x1402 /* GL_SHORT */: + case 0x1403 /* GL_UNSIGNED_SHORT */: + sizeBytes = 2; + break; + case 0x1404 /* GL_INT */: + case 0x1405 /* GL_UNSIGNED_INT */: + case 0x1406 /* GL_FLOAT */: + sizeBytes = 4; + break; + case 0x140A /* GL_DOUBLE */: + sizeBytes = 8; + break; + default: + console.error('Invalid vertex attribute data type GLenum ' + dataType + ' passed to GL function!'); + } + if (dimension == 0x80E1 /* GL_BGRA */) { + console.error('WebGL does not support size=GL_BGRA in a call to glVertexAttribPointer! Please use size=4 and type=GL_UNSIGNED_BYTE instead!'); + } else if (dimension < 1 || dimension > 4) { + console.error('Invalid dimension='+dimension+' in call to glVertexAttribPointer, must be 1,2,3 or 4.'); + } + if (stride < 0 || stride > 255) { + console.error('Invalid stride='+stride+' in call to glVertexAttribPointer. Note that maximum supported stride in WebGL is 255!'); + } + if (offset % sizeBytes != 0) { + console.error('GL spec section 6.4 error: vertex attribute data offset of ' + offset + ' bytes should have been a multiple of the data type size that was used: GLenum ' + dataType + ' has size of ' + sizeBytes + ' bytes!'); + } + if (stride % sizeBytes != 0) { + console.error('GL spec section 6.4 error: vertex attribute data stride of ' + stride + ' bytes should have been a multiple of the data type size that was used: GLenum ' + dataType + ' has size of ' + sizeBytes + ' bytes!'); + } + }, +#endif + initExtensions: function() { if (GL.initExtensions.done) return; GL.initExtensions.done = true; @@ -334,6 +406,10 @@ var LibraryGL = { GL.elementIndexUintExt = Module.ctx.getExtension('OES_element_index_uint'); GL.standardDerivativesExt = Module.ctx.getExtension('OES_standard_derivatives'); + + GL.depthTextureExt = Module.ctx.getExtension("WEBGL_depth_texture") || + Module.ctx.getExtension("MOZ_WEBGL_depth_texture") || + Module.ctx.getExtension("WEBKIT_WEBGL_depth_texture"); } }, @@ -588,6 +664,9 @@ var LibraryGL = { glBindTexture__sig: 'vii', glBindTexture: function(target, texture) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.textures, texture, 'glBindTexture', 'texture'); +#endif Module.ctx.bindTexture(target, texture ? GL.textures[texture] : null); }, @@ -710,6 +789,9 @@ var LibraryGL = { glBindRenderbuffer__sig: 'vii', glBindRenderbuffer: function(target, renderbuffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.renderbuffers, renderbuffer, 'glBindRenderbuffer', 'renderbuffer'); +#endif Module.ctx.bindRenderbuffer(target, renderbuffer ? GL.renderbuffers[renderbuffer] : null); }, @@ -727,6 +809,9 @@ var LibraryGL = { glGetUniformfv__sig: 'viii', glGetUniformfv: function(program, location, params) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetUniformfv', 'program'); +#endif var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number') { {{{ makeSetValue('params', '0', 'data', 'float') }}}; @@ -739,6 +824,9 @@ var LibraryGL = { glGetUniformiv__sig: 'viii', glGetUniformiv: function(program, location, params) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetUniformiv', 'program'); +#endif var data = Module.ctx.getUniform(GL.programs[program], GL.uniforms[location]); if (typeof data == 'number' || typeof data == 'boolean') { {{{ makeSetValue('params', '0', 'data', 'i32') }}}; @@ -751,6 +839,9 @@ var LibraryGL = { glGetUniformLocation__sig: 'iii', glGetUniformLocation: function(program, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetUniformLocation', 'program'); +#endif name = Pointer_stringify(name); var ptable = GL.uniformTable[program]; if (!ptable) ptable = GL.uniformTable[program] = {}; @@ -810,6 +901,9 @@ var LibraryGL = { glGetActiveUniform__sig: 'viiiiiii', glGetActiveUniform: function(program, index, bufSize, length, size, type, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetActiveUniform', 'program'); +#endif program = GL.programs[program]; var info = Module.ctx.getActiveUniform(program, index); @@ -1018,6 +1112,9 @@ var LibraryGL = { glBindBuffer__sig: 'vii', glBindBuffer: function(target, buffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.buffers, buffer, 'glBindBuffer', 'buffer'); +#endif var bufferObj = buffer ? GL.buffers[buffer] : null; if (target == Module.ctx.ARRAY_BUFFER) { @@ -1062,6 +1159,9 @@ var LibraryGL = { glGetActiveAttrib__sig: 'viiiiiii', glGetActiveAttrib: function(program, index, bufSize, length, size, type, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetActiveAttrib', 'program'); +#endif program = GL.programs[program]; var info = Module.ctx.getActiveAttrib(program, index); @@ -1094,6 +1194,9 @@ var LibraryGL = { glGetAttachedShaders__sig: 'viiii', glGetAttachedShaders: function(program, maxCount, count, shaders) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetAttachedShaders', 'program'); +#endif var result = Module.ctx.getAttachedShaders(GL.programs[program]); var len = result.length; if (len > maxCount) { @@ -1109,12 +1212,18 @@ var LibraryGL = { glShaderSource__sig: 'viiii', glShaderSource: function(shader, count, string, length) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glShaderSource', 'shader'); +#endif var source = GL.getSource(shader, count, string, length); Module.ctx.shaderSource(GL.shaders[shader], source); }, glGetShaderSource__sig: 'viiii', glGetShaderSource: function(shader, bufSize, length, source) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderSource', 'shader'); +#endif var result = Module.ctx.getShaderSource(GL.shaders[shader]); result = result.slice(0, Math.max(0, bufSize - 1)); writeStringToMemory(result, source); @@ -1125,11 +1234,17 @@ var LibraryGL = { glCompileShader__sig: 'vi', glCompileShader: function(shader) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glCompileShader', 'shader'); +#endif Module.ctx.compileShader(GL.shaders[shader]); }, glGetShaderInfoLog__sig: 'viiii', glGetShaderInfoLog: function(shader, maxLength, length, infoLog) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderInfoLog', 'shader'); +#endif var log = Module.ctx.getShaderInfoLog(GL.shaders[shader]); // Work around a bug in Chromium which causes getShaderInfoLog to return null if (!log) { @@ -1144,6 +1259,9 @@ var LibraryGL = { glGetShaderiv__sig: 'viii', glGetShaderiv : function(shader, pname, p) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.shaders, shader, 'glGetShaderiv', 'shader'); +#endif if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getShaderInfoLog(GL.shaders[shader]).length + 1', 'i32') }}}; } else { @@ -1153,6 +1271,9 @@ var LibraryGL = { glGetProgramiv__sig: 'viii', glGetProgramiv : function(program, pname, p) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetProgramiv', 'program'); +#endif if (pname == 0x8B84) { // GL_INFO_LOG_LENGTH {{{ makeSetValue('p', '0', 'Module.ctx.getProgramInfoLog(GL.programs[program]).length + 1', 'i32') }}}; } else { @@ -1187,12 +1308,20 @@ var LibraryGL = { glAttachShader__sig: 'vii', glAttachShader: function(program, shader) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glAttachShader', 'program'); + GL.validateGLObjectID(GL.shaders, shader, 'glAttachShader', 'shader'); +#endif Module.ctx.attachShader(GL.programs[program], GL.shaders[shader]); }, glDetachShader__sig: 'vii', glDetachShader: function(program, shader) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glDetachShader', 'program'); + GL.validateGLObjectID(GL.shaders, shader, 'glDetachShader', 'shader'); +#endif Module.ctx.detachShader(GL.programs[program], GL.shaders[shader]); }, @@ -1206,12 +1335,18 @@ var LibraryGL = { glLinkProgram__sig: 'vi', glLinkProgram: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glLinkProgram', 'program'); +#endif Module.ctx.linkProgram(GL.programs[program]); GL.uniformTable[program] = {}; // uniforms no longer keep the same names after linking }, glGetProgramInfoLog__sig: 'viiii', glGetProgramInfoLog: function(program, maxLength, length, infoLog) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glGetProgramInfoLog', 'program'); +#endif var log = Module.ctx.getProgramInfoLog(GL.programs[program]); // Work around a bug in Chromium which causes getProgramInfoLog to return null if (!log) { @@ -1226,11 +1361,17 @@ var LibraryGL = { glUseProgram__sig: 'vi', glUseProgram: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glUseProgram', 'program'); +#endif Module.ctx.useProgram(program ? GL.programs[program] : null); }, glValidateProgram__sig: 'vi', glValidateProgram: function(program) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glValidateProgram', 'program'); +#endif Module.ctx.validateProgram(GL.programs[program]); }, @@ -1243,12 +1384,18 @@ var LibraryGL = { glBindAttribLocation__sig: 'viii', glBindAttribLocation: function(program, index, name) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.programs, program, 'glBindAttribLocation', 'program'); +#endif name = Pointer_stringify(name); Module.ctx.bindAttribLocation(GL.programs[program], index, name); }, glBindFramebuffer__sig: 'vii', glBindFramebuffer: function(target, framebuffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.framebuffers, framebuffer, 'glBindFramebuffer', 'framebuffer'); +#endif Module.ctx.bindFramebuffer(target, framebuffer ? GL.framebuffers[framebuffer] : null); }, @@ -1276,12 +1423,18 @@ var LibraryGL = { glFramebufferRenderbuffer__sig: 'viiii', glFramebufferRenderbuffer: function(target, attachment, renderbuffertarget, renderbuffer) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.renderbuffers, renderbuffer, 'glFramebufferRenderbuffer', 'renderbuffer'); +#endif Module.ctx.framebufferRenderbuffer(target, attachment, renderbuffertarget, GL.renderbuffers[renderbuffer]); }, glFramebufferTexture2D__sig: 'viiiii', glFramebufferTexture2D: function(target, attachment, textarget, texture, level) { +#if GL_ASSERTIONS + GL.validateGLObjectID(GL.textures, texture, 'glFramebufferTexture2D', 'texture'); +#endif Module.ctx.framebufferTexture2D(target, attachment, textarget, GL.textures[texture], level); }, @@ -3161,6 +3314,9 @@ var LibraryGL = { var clientAttributes = GL.immediate.clientAttributes; +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(positionSize, positionType, GL.immediate.stride, clientAttributes[GL.immediate.VERTEX].offset); +#endif Module.ctx.vertexAttribPointer(this.positionLocation, positionSize, positionType, false, GL.immediate.stride, clientAttributes[GL.immediate.VERTEX].offset); Module.ctx.enableVertexAttribArray(this.positionLocation); @@ -3173,6 +3329,9 @@ var LibraryGL = { if (attribLoc === undefined || attribLoc < 0) continue; if (texUnitID < textureSizes.length && textureSizes[texUnitID]) { +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(textureSizes[texUnitID], textureTypes[texUnitID], GL.immediate.stride, GL.immediate.clientAttributes[GL.immediate.TEXTURE0 + texUnitID].offset); +#endif Module.ctx.vertexAttribPointer(attribLoc, textureSizes[texUnitID], textureTypes[texUnitID], false, GL.immediate.stride, GL.immediate.clientAttributes[GL.immediate.TEXTURE0 + texUnitID].offset); Module.ctx.enableVertexAttribArray(attribLoc); @@ -3189,6 +3348,9 @@ var LibraryGL = { } } if (colorSize) { +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(colorSize, colorType, GL.immediate.stride, clientAttributes[GL.immediate.COLOR].offset); +#endif Module.ctx.vertexAttribPointer(this.colorLocation, colorSize, colorType, true, GL.immediate.stride, clientAttributes[GL.immediate.COLOR].offset); Module.ctx.enableVertexAttribArray(this.colorLocation); @@ -3197,6 +3359,9 @@ var LibraryGL = { Module.ctx.vertexAttrib4fv(this.colorLocation, GL.immediate.clientColor); } if (this.hasNormal) { +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(normalSize, normalType, GL.immediate.stride, clientAttributes[GL.immediate.NORMAL].offset); +#endif Module.ctx.vertexAttribPointer(this.normalLocation, normalSize, normalType, true, GL.immediate.stride, clientAttributes[GL.immediate.NORMAL].offset); Module.ctx.enableVertexAttribArray(this.normalLocation); @@ -4176,6 +4341,9 @@ var LibraryGL = { } cb.clientside = false; #endif +#if GL_ASSERTIONS + GL.validateVertexAttribPointer(size, type, stride, ptr); +#endif Module.ctx.vertexAttribPointer(index, size, type, normalized, stride, ptr); }, diff --git a/src/library_idbfs.js b/src/library_idbfs.js new file mode 100644 index 00000000..9031bad8 --- /dev/null +++ b/src/library_idbfs.js @@ -0,0 +1,216 @@ +mergeInto(LibraryManager.library, { + $IDBFS__deps: ['$FS', '$MEMFS', '$PATH'], + $IDBFS: { + dbs: {}, + indexedDB: function() { + return window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; + }, + DB_VERSION: 20, + DB_STORE_NAME: 'FILE_DATA', + // reuse all of the core MEMFS functionality + mount: function(mount) { + return MEMFS.mount.apply(null, arguments); + }, + // the only custom function IDBFS implements is to handle + // synchronizing the wrapped MEMFS with a backing IDB instance + syncfs: function(mount, populate, callback) { + IDBFS.getLocalSet(mount, function(err, local) { + if (err) return callback(err); + + IDBFS.getRemoteSet(mount, function(err, remote) { + if (err) return callback(err); + + var src = populate ? remote : local; + var dst = populate ? local : remote; + + IDBFS.reconcile(src, dst, callback); + }); + }); + }, + reconcile: function(src, dst, callback) { + var total = 0; + + var create = {}; + for (var key in src.files) { + if (!src.files.hasOwnProperty(key)) continue; + var e = src.files[key]; + var e2 = dst.files[key]; + if (!e2 || e.timestamp > e2.timestamp) { + create[key] = e; + total++; + } + } + + var remove = {}; + for (var key in dst.files) { + if (!dst.files.hasOwnProperty(key)) continue; + var e = dst.files[key]; + var e2 = src.files[key]; + if (!e2) { + remove[key] = e; + total++; + } + } + + if (!total) { + // early out + return callback(null); + } + + var completed = 0; + var done = function(err) { + if (err) return callback(err); + if (++completed >= total) { + return callback(null); + } + }; + + // create a single transaction to handle and IDB reads / writes we'll need to do + var db = src.type === 'remote' ? src.db : dst.db; + var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readwrite'); + transaction.onerror = function() { callback(this.error); }; + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); + + for (var path in create) { + if (!create.hasOwnProperty(path)) continue; + var entry = create[path]; + + if (dst.type === 'local') { + // save file to local + try { + if (FS.isDir(entry.mode)) { + FS.mkdir(path, entry.mode); + } else if (FS.isFile(entry.mode)) { + var stream = FS.open(path, 'w+', 0666); + FS.write(stream, entry.contents, 0, entry.contents.length, 0, true /* canOwn */); + FS.close(stream); + } + done(null); + } catch (e) { + return done(e); + } + } else { + // save file to IDB + var req = store.put(entry, path); + req.onsuccess = function() { done(null); }; + req.onerror = function() { done(this.error); }; + } + } + + for (var path in remove) { + if (!remove.hasOwnProperty(path)) continue; + var entry = remove[path]; + + if (dst.type === 'local') { + // delete file from local + try { + if (FS.isDir(entry.mode)) { + // TODO recursive delete? + FS.rmdir(path); + } else if (FS.isFile(entry.mode)) { + FS.unlink(path); + } + done(null); + } catch (e) { + return done(e); + } + } else { + // delete file from IDB + var req = store.delete(path); + req.onsuccess = function() { done(null); }; + req.onerror = function() { done(this.error); }; + } + } + }, + getLocalSet: function(mount, callback) { + var files = {}; + + var isRealDir = function(p) { + return p !== '.' && p !== '..'; + }; + var toAbsolute = function(root) { + return function(p) { + return PATH.join(root, p); + } + }; + + var check = FS.readdir(mount.mountpoint) + .filter(isRealDir) + .map(toAbsolute(mount.mountpoint)); + + while (check.length) { + var path = check.pop(); + var stat, node; + + try { + var lookup = FS.lookupPath(path); + node = lookup.node; + stat = FS.stat(path); + } catch (e) { + return callback(e); + } + + if (FS.isDir(stat.mode)) { + check.push.apply(check, FS.readdir(path) + .filter(isRealDir) + .map(toAbsolute(path))); + + files[path] = { mode: stat.mode, timestamp: stat.mtime }; + } else if (FS.isFile(stat.mode)) { + files[path] = { contents: node.contents, mode: stat.mode, timestamp: stat.mtime }; + } else { + return callback(new Error('node type not supported')); + } + } + + return callback(null, { type: 'local', files: files }); + }, + getDB: function(name, callback) { + // look it up in the cache + var db = IDBFS.dbs[name]; + if (db) { + return callback(null, db); + } + var req; + try { + req = IDBFS.indexedDB().open(name, IDBFS.DB_VERSION); + } catch (e) { + return onerror(e); + } + req.onupgradeneeded = function() { + db = req.result; + db.createObjectStore(IDBFS.DB_STORE_NAME); + }; + req.onsuccess = function() { + db = req.result; + // add to the cache + IDBFS.dbs[name] = db; + callback(null, db); + }; + req.onerror = function() { + callback(this.error); + }; + }, + getRemoteSet: function(mount, callback) { + var files = {}; + + IDBFS.getDB(mount.mountpoint, function(err, db) { + if (err) return callback(err); + + var transaction = db.transaction([IDBFS.DB_STORE_NAME], 'readonly'); + transaction.onerror = function() { callback(this.error); }; + + var store = transaction.objectStore(IDBFS.DB_STORE_NAME); + store.openCursor().onsuccess = function(event) { + var cursor = event.target.result; + if (!cursor) { + return callback(null, { type: 'remote', db: db, files: files }); + } + + files[cursor.key] = cursor.value; + cursor.continue(); + }; + }); + } + } +}); diff --git a/src/library_memfs.js b/src/library_memfs.js index 354f5e95..4e56d996 100644 --- a/src/library_memfs.js +++ b/src/library_memfs.js @@ -5,18 +5,10 @@ mergeInto(LibraryManager.library, { CONTENT_OWNING: 1, // contains a subarray into the heap, and we own it, without copying (note: someone else needs to free() it, if that is necessary) CONTENT_FLEXIBLE: 2, // has been modified or never set to anything, and is a flexible js array that can grow/shrink CONTENT_FIXED: 3, // contains some fixed-size content written into it, in a typed array - ensureFlexible: function(node) { - if (node.contentMode !== MEMFS.CONTENT_FLEXIBLE) { - var contents = node.contents; - node.contents = Array.prototype.slice.call(contents); - node.contentMode = MEMFS.CONTENT_FLEXIBLE; - } - }, - mount: function(mount) { - return MEMFS.create_node(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); + return MEMFS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); }, - create_node: function(parent, name, mode, dev) { + createNode: function(parent, name, mode, dev) { if (FS.isBlkdev(mode) || FS.isFIFO(mode)) { // no supported throw new FS.ErrnoError(ERRNO_CODES.EPERM); @@ -74,6 +66,13 @@ mergeInto(LibraryManager.library, { } return node; }, + ensureFlexible: function(node) { + if (node.contentMode !== MEMFS.CONTENT_FLEXIBLE) { + var contents = node.contents; + node.contents = Array.prototype.slice.call(contents); + node.contentMode = MEMFS.CONTENT_FLEXIBLE; + } + }, node_ops: { getattr: function(node) { var attr = {}; @@ -121,7 +120,7 @@ mergeInto(LibraryManager.library, { throw new FS.ErrnoError(ERRNO_CODES.ENOENT); }, mknod: function(parent, name, mode, dev) { - return MEMFS.create_node(parent, name, mode, dev); + return MEMFS.createNode(parent, name, mode, dev); }, rename: function(old_node, new_dir, new_name) { // if we're overwriting a directory at new_name, make sure it's empty. @@ -163,7 +162,7 @@ mergeInto(LibraryManager.library, { return entries; }, symlink: function(parent, newname, oldpath) { - var node = MEMFS.create_node(parent, newname, 0777 | {{{ cDefine('S_IFLNK') }}}, 0); + var node = MEMFS.createNode(parent, newname, 0777 | {{{ cDefine('S_IFLNK') }}}, 0); node.link = oldpath; return node; }, @@ -177,7 +176,10 @@ mergeInto(LibraryManager.library, { stream_ops: { read: function(stream, buffer, offset, length, position) { var contents = stream.node.contents; + if (position >= contents.length) + return 0; var size = Math.min(contents.length - position, length); + assert(size >= 0); #if USE_TYPED_ARRAYS == 2 if (size > 8 && contents.subarray) { // non-trivial, and typed array buffer.set(contents.subarray(position, position + size), offset); diff --git a/src/library_nodefs.js b/src/library_nodefs.js new file mode 100644 index 00000000..d8df1689 --- /dev/null +++ b/src/library_nodefs.js @@ -0,0 +1,234 @@ +mergeInto(LibraryManager.library, { + $NODEFS__deps: ['$FS', '$PATH'], + $NODEFS__postset: 'if (ENVIRONMENT_IS_NODE) { var fs = require("fs"); }', + $NODEFS: { + mount: function (mount) { + assert(ENVIRONMENT_IS_NODE); + return NODEFS.createNode(null, '/', NODEFS.getMode(mount.opts.root), 0); + }, + createNode: function (parent, name, mode, dev) { + if (!FS.isDir(mode) && !FS.isFile(mode) && !FS.isLink(mode)) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + var node = FS.createNode(parent, name, mode); + node.node_ops = NODEFS.node_ops; + node.stream_ops = NODEFS.stream_ops; + return node; + }, + getMode: function (path) { + var stat; + try { + stat = fs.lstatSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return stat.mode; + }, + realPath: function (node) { + var parts = []; + while (node.parent !== node) { + parts.push(node.name); + node = node.parent; + } + parts.push(node.mount.opts.root); + parts.reverse(); + return PATH.join.apply(null, parts); + }, + node_ops: { + getattr: function(node) { + var path = NODEFS.realPath(node); + var stat; + try { + stat = fs.lstatSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return { + dev: stat.dev, + ino: stat.ino, + mode: stat.mode, + nlink: stat.nlink, + uid: stat.uid, + gid: stat.gid, + rdev: stat.rdev, + size: stat.size, + atime: stat.atime, + mtime: stat.mtime, + ctime: stat.ctime, + blksize: stat.blksize, + blocks: stat.blocks + }; + }, + setattr: function(node, attr) { + var path = NODEFS.realPath(node); + try { + if (attr.mode !== undefined) { + fs.chmodSync(path, attr.mode); + // update the common node structure mode as well + node.mode = attr.mode; + } + if (attr.timestamp !== undefined) { + var date = new Date(attr.timestamp); + fs.utimesSync(path, date, date); + } + if (attr.size !== undefined) { + fs.truncateSync(path, attr.size); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + lookup: function (parent, name) { + var path = PATH.join(NODEFS.realPath(parent), name); + var mode = NODEFS.getMode(path); + return NODEFS.createNode(parent, name, mode); + }, + mknod: function (parent, name, mode, dev) { + var node = NODEFS.createNode(parent, name, mode, dev); + // create the backing node for this in the fs root as well + var path = NODEFS.realPath(node); + try { + if (FS.isDir(node.mode)) { + fs.mkdirSync(path, node.mode); + } else { + fs.writeFileSync(path, '', { mode: node.mode }); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return node; + }, + rename: function (oldNode, newDir, newName) { + var oldPath = NODEFS.realPath(oldNode); + var newPath = PATH.join(NODEFS.realPath(newDir), newName); + try { + fs.renameSync(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + unlink: function(parent, name) { + var path = PATH.join(NODEFS.realPath(parent), name); + try { + fs.unlinkSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + rmdir: function(parent, name) { + var path = PATH.join(NODEFS.realPath(parent), name); + try { + fs.rmdirSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readdir: function(node) { + var path = NODEFS.realPath(node); + try { + return fs.readdirSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + symlink: function(parent, newName, oldPath) { + var newPath = PATH.join(NODEFS.realPath(parent), newName); + try { + fs.symlinkSync(oldPath, newPath); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + readlink: function(node) { + var path = NODEFS.realPath(node); + try { + return fs.readlinkSync(path); + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + }, + stream_ops: { + open: function (stream) { + var path = NODEFS.realPath(stream.node); + try { + if (FS.isFile(stream.node.mode)) { + stream.nfd = fs.openSync(path, stream.flags); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + close: function (stream) { + try { + if (FS.isFile(stream.node.mode)) { + fs.closeSync(stream.nfd); + } + } catch (e) { + if (!e.code) throw e; + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + }, + read: function (stream, buffer, offset, length, position) { + // FIXME this is terrible. + var nbuffer = new Buffer(length); + var res; + try { + res = fs.readSync(stream.nfd, nbuffer, 0, length, position); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + if (res > 0) { + for (var i = 0; i < res; i++) { + buffer[offset + i] = nbuffer[i]; + } + } + return res; + }, + write: function (stream, buffer, offset, length, position) { + // FIXME this is terrible. + var nbuffer = new Buffer(buffer.subarray(offset, offset + length)); + var res; + try { + res = fs.writeSync(stream.nfd, nbuffer, 0, length, position); + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + return res; + }, + llseek: function (stream, offset, whence) { + var position = offset; + if (whence === 1) { // SEEK_CUR. + position += stream.position; + } else if (whence === 2) { // SEEK_END. + if (FS.isFile(stream.node.mode)) { + try { + var stat = fs.fstatSync(stream.nfd); + position += stat.size; + } catch (e) { + throw new FS.ErrnoError(ERRNO_CODES[e.code]); + } + } + } + + if (position < 0) { + throw new FS.ErrnoError(ERRNO_CODES.EINVAL); + } + + stream.position = position; + return position; + } + } + } +});
\ No newline at end of file diff --git a/src/library_sdl.js b/src/library_sdl.js index 656b5a02..1d6a00b6 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -627,7 +627,7 @@ var LibrarySDL = { // since the browser engine handles that for us. Therefore, in JS we just // maintain a list of channels and return IDs for them to the SDL consumer. allocateChannels: function(num) { // called from Mix_AllocateChannels and init - if (SDL.numChannels && SDL.numChannels >= num) return; + if (SDL.numChannels && SDL.numChannels >= num && num != 0) return; SDL.numChannels = num; SDL.channels = []; for (var i = 0; i < num; i++) { @@ -1054,7 +1054,10 @@ var LibrarySDL = { } else { dr = { x: 0, y: 0, w: -1, h: -1 }; } + var oldAlpha = dstData.ctx.globalAlpha; + dstData.ctx.globalAlpha = srcData.alpha/255; dstData.ctx.drawImage(srcData.canvas, sr.x, sr.y, sr.w, sr.h, dr.x, dr.y, sr.w, sr.h); + dstData.ctx.globalAlpha = oldAlpha; if (dst != SDL.screen) { // XXX As in IMG_Load, for compatibility we write out |pixels| console.log('WARNING: copying canvas data to memory for compatibility'); @@ -1377,60 +1380,240 @@ var LibrarySDL = { // SDL_Audio - // TODO fix SDL_OpenAudio, and add some tests for it. It's currently broken. SDL_OpenAudio: function(desired, obtained) { - SDL.allocateChannels(32); - - SDL.audio = { - freq: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.freq, 'i32', 0, 1) }}}, - format: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.format, 'i16', 0, 1) }}}, - channels: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.channels, 'i8', 0, 1) }}}, - samples: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.samples, 'i16', 0, 1) }}}, - callback: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.callback, 'void*', 0, 1) }}}, - userdata: {{{ makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.userdata, 'void*', 0, 1) }}}, - paused: true, - timer: null - }; - - if (obtained) { - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.freq, 'SDL.audio.freq', 'i32') }}}; // no good way for us to know if the browser can really handle this - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.format, 33040, 'i16') }}}; // float, signed, 16-bit - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.channels, 'SDL.audio.channels', 'i8') }}}; - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.silence, makeGetValue('desired', C_STRUCTS.SDL_AudioSpec.silence, 'i8', 0, 1), 'i8') }}}; // unclear if browsers can provide this - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.samples, 'SDL.audio.samples', 'i16') }}}; - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.callback, 'SDL.audio.callback', '*') }}}; - {{{ makeSetValue('obtained', C_STRUCTS.SDL_AudioSpec.userdata, 'SDL.audio.userdata', '*') }}}; - } - - var totalSamples = SDL.audio.samples*SDL.audio.channels; - SDL.audio.bufferSize = totalSamples*2; // hardcoded 16-bit audio - SDL.audio.buffer = _malloc(SDL.audio.bufferSize); - SDL.audio.caller = function() { - Runtime.dynCall('viii', SDL.audio.callback, [SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize]); - SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize); - }; - // Mozilla Audio API. TODO: Other audio APIs try { - SDL.audio.mozOutput = new Audio(); - SDL.audio.mozOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler - SDL.audio.mozBuffer = new Float32Array(totalSamples); - SDL.audio.pushAudio = function(ptr, size) { - var mozBuffer = SDL.audio.mozBuffer; - for (var i = 0; i < totalSamples; i++) { - mozBuffer[i] = ({{{ makeGetValue('ptr', 'i*2', 'i16', 0, 0) }}}) / 0x8000; // hardcoded 16-bit audio, signed (TODO: reSign if not ta2?) + SDL.audio = { + freq: {{{ makeGetValue('desired', 'C_STRUCTS.SDL_AudioSpec.freq', 'i32', 0, 1) }}}, + format: {{{ makeGetValue('desired', 'C_STRUCTS.SDL_AudioSpec.format', 'i16', 0, 1) }}}, + channels: {{{ makeGetValue('desired', 'C_STRUCTS.SDL_AudioSpec.channels', 'i8', 0, 1) }}}, + samples: {{{ makeGetValue('desired', 'C_STRUCTS.SDL_AudioSpec.samples', 'i16', 0, 1) }}}, // Samples in the CB buffer per single sound channel. + callback: {{{ makeGetValue('desired', 'C_STRUCTS.SDL_AudioSpec.callback', 'void*', 0, 1) }}}, + userdata: {{{ makeGetValue('desired', 'C_STRUCTS.SDL_AudioSpec.userdata', 'void*', 0, 1) }}}, + paused: true, + timer: null + }; + // The .silence field tells the constant sample value that corresponds to the safe un-skewed silence value for the wave data. + if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) { + SDL.audio.silence = 128; // Audio ranges in [0, 255], so silence is half-way in between. + } else if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) { + SDL.audio.silence = 0; // Signed data in range [-32768, 32767], silence is 0. + } else { + throw 'Invalid SDL audio format ' + SDL.audio.format + '!'; + } + // Round the desired audio frequency up to the next 'common' frequency value. + // Web Audio API spec states 'An implementation must support sample-rates in at least the range 22050 to 96000.' + if (SDL.audio.freq <= 0) { + throw 'Unsupported sound frequency ' + SDL.audio.freq + '!'; + } else if (SDL.audio.freq <= 22050) { + SDL.audio.freq = 22050; // Take it safe and clamp everything lower than 22kHz to that. + } else if (SDL.audio.freq <= 32000) { + SDL.audio.freq = 32000; + } else if (SDL.audio.freq <= 44100) { + SDL.audio.freq = 44100; + } else if (SDL.audio.freq <= 48000) { + SDL.audio.freq = 48000; + } else if (SDL.audio.freq <= 96000) { + SDL.audio.freq = 96000; + } else { + throw 'Unsupported sound frequency ' + SDL.audio.freq + '!'; + } + if (SDL.audio.channels == 0) { + SDL.audio.channels = 1; // In SDL both 0 and 1 mean mono. + } else if (SDL.audio.channels < 0 || SDL.audio.channels > 32) { + throw 'Unsupported number of audio channels for SDL audio: ' + SDL.audio.channels + '!'; + } else if (SDL.audio.channels != 1 && SDL.audio.channels != 2) { // Unsure what SDL audio spec supports. Web Audio spec supports up to 32 channels. + console.log('Warning: Using untested number of audio channels ' + SDL.audio.channels); + } + if (SDL.audio.samples < 1024 || SDL.audio.samples > 524288 /* arbitrary cap */) { + throw 'Unsupported audio callback buffer size ' + SDL.audio.samples + '!'; + } else if ((SDL.audio.samples & (SDL.audio.samples-1)) != 0) { + throw 'Audio callback buffer size ' + SDL.audio.samples + ' must be a power-of-two!'; + } + + var totalSamples = SDL.audio.samples*SDL.audio.channels; + SDL.audio.bytesPerSample = (SDL.audio.format == 0x0008 /*AUDIO_U8*/ || SDL.audio.format == 0x8008 /*AUDIO_S8*/) ? 1 : 2; + SDL.audio.bufferSize = totalSamples*SDL.audio.bytesPerSample; + SDL.audio.buffer = _malloc(SDL.audio.bufferSize); + + // Create a callback function that will be routinely called to ask more audio data from the user application. + SDL.audio.caller = function() { + if (!SDL.audio) { + return; + } + Runtime.dynCall('viii', SDL.audio.callback, [SDL.audio.userdata, SDL.audio.buffer, SDL.audio.bufferSize]); + SDL.audio.pushAudio(SDL.audio.buffer, SDL.audio.bufferSize); + }; + + SDL.audio.audioOutput = new Audio(); + // As a workaround use Mozilla Audio Data API on Firefox until it ships with Web Audio and sound quality issues are fixed. + if (typeof(SDL.audio.audioOutput['mozSetup'])==='function') { + SDL.audio.audioOutput['mozSetup'](SDL.audio.channels, SDL.audio.freq); // use string attributes on mozOutput for closure compiler + SDL.audio.mozBuffer = new Float32Array(totalSamples); + SDL.audio.nextPlayTime = 0; + SDL.audio.pushAudio = function(ptr, size) { + var mozBuffer = SDL.audio.mozBuffer; + // The input audio data for SDL audio is either 8-bit or 16-bit interleaved across channels, output for Mozilla Audio Data API + // needs to be Float32 interleaved, so perform a sample conversion. + if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) { + for (var i = 0; i < totalSamples; i++) { + mozBuffer[i] = ({{{ makeGetValue('ptr', 'i*2', 'i16', 0, 0) }}}) / 0x8000; + } + } else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) { + for (var i = 0; i < totalSamples; i++) { + var v = ({{{ makeGetValue('ptr', 'i', 'i8', 0, 0) }}}); + mozBuffer[i] = ((v >= 0) ? v-128 : v+128) /128; + } + } + // Submit the audio data to audio device. + SDL.audio.audioOutput['mozWriteAudio'](mozBuffer); + + // Compute when the next audio callback should be called. + var curtime = Date.now() / 1000.0 - SDL.audio.startTime; + if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) { + console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.'); + } + var playtime = Math.max(curtime, SDL.audio.nextPlayTime); + var buffer_duration = SDL.audio.samples / SDL.audio.freq; + SDL.audio.nextPlayTime = playtime + buffer_duration; + // Schedule the next audio callback call. + SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1000.0 * (playtime-curtime)); + } + } else { + // Initialize Web Audio API if we haven't done so yet. Note: Only initialize Web Audio context ever once on the web page, + // since initializing multiple times fails on Chrome saying 'audio resources have been exhausted'. + if (!SDL.audioContext) { + if (typeof(AudioContext) === 'function') { + SDL.audioContext = new AudioContext(); + } else if (typeof(webkitAudioContext) === 'function') { + SDL.audioContext = new webkitAudioContext(); + } else { + throw 'Web Audio API is not available!'; + } + } + SDL.audio.soundSource = new Array(); // Use an array of sound sources as a ring buffer to queue blocks of synthesized audio to Web Audio API. + SDL.audio.nextSoundSource = 0; // Index of the next sound buffer in the ring buffer queue to play. + SDL.audio.nextPlayTime = 0; // Time in seconds when the next audio block is due to start. + + // The pushAudio function with a new audio buffer whenever there is new audio data to schedule to be played back on the device. + SDL.audio.pushAudio=function(ptr,sizeBytes) { + try { + --SDL.audio.numAudioTimersPending; + + var sizeSamples = sizeBytes / SDL.audio.bytesPerSample; // How many samples fit in the callback buffer? + var sizeSamplesPerChannel = sizeSamples / SDL.audio.channels; // How many samples per a single channel fit in the cb buffer? + if (sizeSamplesPerChannel != SDL.audio.samples) { + throw 'Received mismatching audio buffer size!'; + } + // Allocate new sound buffer to be played. + var source = SDL.audioContext['createBufferSource'](); + if (SDL.audio.soundSource[SDL.audio.nextSoundSource]) { + SDL.audio.soundSource[SDL.audio.nextSoundSource]['disconnect'](); // Explicitly disconnect old source, since we know it shouldn't be running anymore. + } + SDL.audio.soundSource[SDL.audio.nextSoundSource] = source; + var soundBuffer = SDL.audioContext['createBuffer'](SDL.audio.channels,sizeSamplesPerChannel,SDL.audio.freq); + SDL.audio.soundSource[SDL.audio.nextSoundSource]['connect'](SDL.audioContext['destination']); + + // The input audio data is interleaved across the channels, i.e. [L, R, L, R, L, R, ...] and is either 8-bit or 16-bit as + // supported by the SDL API. The output audio wave data for Web Audio API must be in planar buffers of [-1,1]-normalized Float32 data, + // so perform a buffer conversion for the data. + var numChannels = SDL.audio.channels; + for(var i = 0; i < numChannels; ++i) { + var channelData = soundBuffer['getChannelData'](i); + if (channelData.length != sizeSamplesPerChannel) { + throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + sizeSamplesPerChannel + ' samples!'; + } + if (SDL.audio.format == 0x8010 /*AUDIO_S16LSB*/) { + for(var j = 0; j < sizeSamplesPerChannel; ++j) { + channelData[j] = ({{{ makeGetValue('ptr', '(j*numChannels + i)*2', 'i16', 0, 0) }}}) / 0x8000; + } + } else if (SDL.audio.format == 0x0008 /*AUDIO_U8*/) { + for(var j = 0; j < sizeSamplesPerChannel; ++j) { + var v = ({{{ makeGetValue('ptr', 'j*numChannels + i', 'i8', 0, 0) }}}); + channelData[j] = ((v >= 0) ? v-128 : v+128) /128; + } + } + } + // Workaround https://bugzilla.mozilla.org/show_bug.cgi?id=883675 by setting the buffer only after filling. The order is important here! + source['buffer'] = soundBuffer; + + // Schedule the generated sample buffer to be played out at the correct time right after the previously scheduled + // sample buffer has finished. + var curtime = SDL.audioContext['currentTime']; +// if (curtime > SDL.audio.nextPlayTime && SDL.audio.nextPlayTime != 0) { +// console.log('warning: Audio callback had starved sending audio by ' + (curtime - SDL.audio.nextPlayTime) + ' seconds.'); +// } + var playtime = Math.max(curtime, SDL.audio.nextPlayTime); + SDL.audio.soundSource[SDL.audio.nextSoundSource]['start'](playtime); + var buffer_duration = sizeSamplesPerChannel / SDL.audio.freq; + SDL.audio.nextPlayTime = playtime + buffer_duration; + SDL.audio.nextSoundSource = (SDL.audio.nextSoundSource + 1) % 4; + var secsUntilNextCall = playtime-curtime; + + // Queue the next audio frame push to be performed when the previously queued buffer has finished playing. + if (SDL.audio.numAudioTimersPending == 0) { + var preemptBufferFeedMSecs = buffer_duration/2.0; + SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, Math.max(0.0, 1000.0*secsUntilNextCall-preemptBufferFeedMSecs)); + ++SDL.audio.numAudioTimersPending; + } + + // If we are risking starving, immediately queue an extra second buffer. + if (secsUntilNextCall <= buffer_duration && SDL.audio.numAudioTimersPending <= 1) { + ++SDL.audio.numAudioTimersPending; + Browser.safeSetTimeout(SDL.audio.caller, 1.0); + } + } catch(e) { + console.log('Web Audio API error playing back audio: ' + e.toString()); + } } - SDL.audio.mozOutput['mozWriteAudio'](mozBuffer); } + + if (obtained) { + // Report back the initialized audio parameters. + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.freq', 'SDL.audio.freq', 'i32') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.format', 'SDL.audio.format', 'i16') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.channels', 'SDL.audio.channels', 'i8') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.silence', 'SDL.audio.silence', 'i8') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.samples', 'SDL.audio.samples', 'i16') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.callback', 'SDL.audio.callback', '*') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.userdata', 'SDL.audio.userdata', '*') }}}; + } + SDL.allocateChannels(32); + } catch(e) { + console.log('Initializing SDL audio threw an exception: "' + e.toString() + '"! Continuing without audio.'); SDL.audio = null; + SDL.allocateChannels(0); + if (obtained) { + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.freq', 0, 'i32') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.format', 0, 'i16') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.channels', 0, 'i8') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.silence', 0, 'i8') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.samples', 0, 'i16') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.callback', 0, '*') }}}; + {{{ makeSetValue('obtained', 'C_STRUCTS.SDL_AudioSpec.userdata', 0, '*') }}}; + } + } + if (!SDL.audio) { + return -1; } - if (!SDL.audio) return -1; return 0; }, SDL_PauseAudio: function(pauseOn) { - if (SDL.audio.paused !== pauseOn) { - SDL.audio.timer = pauseOn ? SDL.audio.timer && clearInterval(SDL.audio.timer) : Browser.safeSetInterval(SDL.audio.caller, 1/35); + if (!SDL.audio) { + return; + } + if (pauseOn) { + if (SDL.audio.timer !== undefined) { + clearTimeout(SDL.audio.timer); + SDL.audio.numAudioTimersPending = 0; + SDL.audio.timer = undefined; + } + } else if (!SDL.audio.timer) { + // Start the audio playback timer callback loop. + SDL.audio.numAudioTimersPending = 1; + SDL.audio.timer = Browser.safeSetTimeout(SDL.audio.caller, 1); + SDL.audio.startTime = Date.now() / 1000.0; // Only used for Mozilla Audio Data API. Not needed for Web Audio API. } SDL.audio.paused = pauseOn; }, @@ -1438,9 +1621,18 @@ var LibrarySDL = { SDL_CloseAudio__deps: ['SDL_PauseAudio', 'free'], SDL_CloseAudio: function() { if (SDL.audio) { + try{ + for(var i = 0; i < SDL.audio.soundSource.length; ++i) { + if (!(typeof(SDL.audio.soundSource[i]==='undefined'))) { + SDL.audio.soundSource[i].stop(0); + } + } + } catch(e) {} + SDL.audio.soundSource = null; _SDL_PauseAudio(1); _free(SDL.audio.buffer); SDL.audio = null; + SDL.allocateChannels(0); } }, diff --git a/src/library_sockfs.js b/src/library_sockfs.js index b11c6495..af29d11b 100644 --- a/src/library_sockfs.js +++ b/src/library_sockfs.js @@ -5,12 +5,6 @@ mergeInto(LibraryManager.library, { mount: function(mount) { return FS.createNode(null, '/', {{{ cDefine('S_IFDIR') }}} | 0777, 0); }, - nextname: function() { - if (!SOCKFS.nextname.current) { - SOCKFS.nextname.current = 0; - } - return 'socket[' + (SOCKFS.nextname.current++) + ']'; - }, createSocket: function(family, type, protocol) { var streaming = type == {{{ cDefine('SOCK_STREAM') }}}; if (protocol) { @@ -95,6 +89,12 @@ mergeInto(LibraryManager.library, { sock.sock_ops.close(sock); } }, + nextname: function() { + if (!SOCKFS.nextname.current) { + SOCKFS.nextname.current = 0; + } + return 'socket[' + (SOCKFS.nextname.current++) + ']'; + }, // backend-specific stream ops websocket_sock_ops: { // diff --git a/src/modules.js b/src/modules.js index 1a931572..2757c2cb 100644 --- a/src/modules.js +++ b/src/modules.js @@ -422,7 +422,7 @@ var LibraryManager = { load: function() { if (this.library) return; - var libraries = ['library.js', 'library_path.js', 'library_fs.js', 'library_memfs.js', 'library_sockfs.js', 'library_tty.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries); + var libraries = ['library.js', 'library_path.js', 'library_fs.js', 'library_idbfs.js', 'library_memfs.js', 'library_nodefs.js', 'library_sockfs.js', 'library_tty.js', 'library_browser.js', 'library_sdl.js', 'library_gl.js', 'library_glut.js', 'library_xlib.js', 'library_egl.js', 'library_gc.js', 'library_jansson.js', 'library_openal.js', 'library_glfw.js'].concat(additionalLibraries); for (var i = 0; i < libraries.length; i++) { eval(processMacros(preprocess(read(libraries[i])))); } @@ -508,3 +508,7 @@ var PassManager = { } }; +var Framework = { + currItem: null +}; + diff --git a/src/parseTools.js b/src/parseTools.js index 2ccf0179..7ebc0de2 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -111,12 +111,22 @@ function isNiceIdent(ident, loose) { } function isJSVar(ident) { - return /^\(?[$_]?[\w$_\d ]*\)+$/.test(ident); - + if (ident[0] === '(') { + if (ident[ident.length-1] !== ')') return false; + ident = ident.substr(1, ident.length-2); + } + return /^[$_]?[\w$_\d]* *$/.test(ident); } function isLocalVar(ident) { - return ident[0] == '$'; + return ident[0] === '$'; +} + +// Simple variables or numbers, or things already quoted, do not need to be quoted +function needsQuoting(ident) { + if (/^[-+]?[$_]?[\w$_\d]*$/.test(ident)) return false; // number or variable + if (ident[0] === '(' && ident[ident.length-1] === ')' && ident.indexOf('(', 1) < 0) return false; // already fully quoted + return true; } function isStructPointerType(type) { @@ -933,12 +943,12 @@ function parseLLVMString(str) { var ret = []; var i = 0; while (i < str.length) { - var chr = str[i]; - if (chr != '\\') { - ret.push(chr.charCodeAt(0)); + var chr = str.charCodeAt(i); + if (chr !== 92) { // 92 === '//'.charCodeAt(0) + ret.push(chr); i++; } else { - ret.push(eval('0x' + str[i+1]+str[i+2])); + ret.push(parseInt(str[i+1]+str[i+2], '16')); i += 3; } } @@ -1197,7 +1207,7 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa var typeData = Types.types[type]; var ret = []; for (var i = 0; i < typeData.fields.length; i++) { - ret.push('f' + i + ': ' + makeGetValue(ptr, pos + typeData.flatIndexes[i], typeData.fields[i], noNeedFirst, unsigned)); + ret.push('f' + i + ': ' + makeGetValue(ptr, pos + typeData.flatIndexes[i], typeData.fields[i], noNeedFirst, unsigned, 0, 0, noSafe)); } return '{ ' + ret.join(', ') + ' }'; } @@ -1205,8 +1215,8 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa // 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)) + ',' + + return '(' + makeSetTempDouble(0, 'i32', makeGetValue(ptr, pos, 'i32', noNeedFirst, unsigned, ignore, align, noSafe)) + ',' + + makeSetTempDouble(1, 'i32', makeGetValue(ptr, getFastValue(pos, '+', Runtime.getNativeTypeSize('i32')), 'i32', noNeedFirst, unsigned, ignore, align, noSafe)) + ',' + makeGetTempDouble(0, 'double') + ')'; } @@ -1219,12 +1229,12 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa if (isIntImplemented(type)) { if (bytes == 4 && align == 2) { // Special case that we can optimize - ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore) + '|' + - '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore) + '<<16)'; + ret += makeGetValue(ptr, pos, 'i16', noNeedFirst, 2, ignore, 2, noSafe) + '|' + + '(' + makeGetValue(ptr, getFastValue(pos, '+', 2), 'i16', noNeedFirst, 2, ignore, 2, noSafe) + '<<16)'; } 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) : '') + ')'; + ret += '(' + makeGetValue(ptr, getFastValue(pos, '+', i), 'i8', noNeedFirst, 1, ignore, 1, noSafe) + (i > 0 ? '<<' + (8*i) : '') + ')'; if (i < bytes-1) ret += '|'; } ret = '(' + makeSignOp(ret, type, unsigned ? 'un' : 're', true); @@ -1303,7 +1313,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, value = range(typeData.fields.length).map(function(i) { return value + '.f' + i }); } for (var i = 0; i < typeData.fields.length; i++) { - ret.push(makeSetValue(ptr, getFastValue(pos, '+', typeData.flatIndexes[i]), value[i], typeData.fields[i], noNeedFirst)); + ret.push(makeSetValue(ptr, getFastValue(pos, '+', typeData.flatIndexes[i]), value[i], typeData.fields[i], noNeedFirst, 0, 0, noSafe)); } return ret.join('; '); } @@ -1330,17 +1340,17 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, if (bytes == 4 && align == 2) { // Special case that we can optimize 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); + ret += makeSetValue(ptr, pos, 'tempBigInt&0xffff', 'i16', noNeedFirst, ignore, 2, noSafe) + sep; + ret += makeSetValue(ptr, getFastValue(pos, '+', 2), 'tempBigInt>>16', 'i16', noNeedFirst, ignore, 2, noSafe); } else { ret += 'tempBigInt=' + value + sep; for (var i = 0; i < bytes; i++) { - ret += makeSetValue(ptr, getFastValue(pos, '+', i), 'tempBigInt&0xff', 'i8', noNeedFirst, ignore, 1); + ret += makeSetValue(ptr, getFastValue(pos, '+', i), 'tempBigInt&0xff', 'i8', noNeedFirst, ignore, 1, noSafe); if (i < bytes-1) ret += sep + 'tempBigInt = tempBigInt>>8' + sep; } } } else { - ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8, null, null, true) + sep; + ret += makeSetValue('tempDoublePtr', 0, value, type, noNeedFirst, ignore, 8, noSafe, null, true) + sep; ret += makeCopyValues(getFastValue(ptr, '+', pos), 'tempDoublePtr', Runtime.getNativeTypeSize(type), type, null, align, sep); } return ret; @@ -1349,6 +1359,7 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, value = indexizeFunctions(value, type); var offset = calcFastOffset(ptr, pos, noNeedFirst); + if (phase === 'pre' && isNumber(offset)) offset += ' '; // avoid pure numeric strings, seem to be perf issues with overly-aggressive interning or slt in pre processing of heap inits if (SAFE_HEAP && !noSafe) { var printType = type; if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; @@ -1465,77 +1476,97 @@ function makeHEAPView(which, start, end) { return 'HEAP' + which + '.subarray((' + start + ')' + mod + ',(' + end + ')' + mod + ')'; } -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 { - 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(); + a = a === 'true' ? '1' : (a === 'false' ? '0' : a); + b = b === 'true' ? '1' : (b === 'false' ? '0' : b); + + var aNumber = null, bNumber = null; + if (typeof a === 'number') { + aNumber = a; + a = a.toString(); + } else if (isNumber(a)) aNumber = parseFloat(a); + if (typeof b === 'number') { + bNumber = b; + b = b.toString(); + } else if (isNumber(b)) bNumber = parseFloat(b); + + if (aNumber !== null && bNumber !== null) { + switch (op) { + case '+': return (aNumber + bNumber).toString(); + case '-': return (aNumber - bNumber).toString(); + case '*': return (aNumber * bNumber).toString(); + case '/': { + if (type[0] === 'i') { + return ((aNumber / bNumber)|0).toString(); + } else { + return (aNumber / bNumber).toString(); + } + } + case '%': return (aNumber % bNumber).toString(); + case '|': return (aNumber | bNumber).toString(); + case '>>>': return (aNumber >>> bNumber).toString(); + case '&': return (aNumber & bNumber).toString(); + case 'pow': return Math.pow(aNumber, bNumber).toString(); + default: throw 'need to implement getFastValue pn ' + op; } } - if (op == 'pow') { - if (a == '2' && isIntImplemented(type)) { + if (op === 'pow') { + if (a === '2' && isIntImplemented(type)) { return '(1 << (' + b + '))'; } return 'Math.pow(' + a + ', ' + b + ')'; } - if (op in PLUS_MUL && isNumber(a)) { // if one of them is a number, keep it last + if ((op === '+' || op === '*') && aNumber !== null) { // if one of them is a number, keep it last var c = b; b = a; a = c; - } - if (op in MUL_DIV) { - if (op == '*') { - if (a == 0 || b == 0) { - return '0'; - } else if (a == 1) { - return b; - } else if (b == 1) { - return a; - } else if (isNumber(b) && type && isIntImplemented(type) && Runtime.getNativeTypeSize(type) <= 32) { - var shifts = Math.log(parseFloat(b))/Math.LN2; - if (shifts % 1 == 0) { - return '(' + a + '<<' + shifts + ')'; - } + var cNumber = bNumber; + bNumber = aNumber; + aNumber = cNumber; + } + if (op === '*') { + // We can't eliminate where a or b are 0 as that would break things for creating + // a negative 0. + if ((aNumber === 0 || bNumber === 0) && !(type in Runtime.FLOAT_TYPES)) { + return '0'; + } else if (aNumber === 1) { + return b; + } else if (bNumber === 1) { + return a; + } else if (bNumber !== null && type && isIntImplemented(type) && Runtime.getNativeTypeSize(type) <= 32) { + var shifts = Math.log(bNumber)/Math.LN2; + if (shifts % 1 === 0) { + 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 + ')|0)'; + } + 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 ((aNumber !== null && Math.abs(a) < TWO_TWENTY) || (bNumber !== null && 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 } - } else { - if (a == '0') { - return '0'; - } else if (b == 1) { - return a; - } // Doing shifts for division is problematic, as getting the rounding right on negatives is tricky - } - } else if (op in PLUS_MINUS) { - if (b[0] == '-') { - op = op == '+' ? '-' : '+'; + return '(Math.imul(' + a + ',' + b + ')|0)'; + } + } else if (op === '/') { + if (a === '0' && !(type in Runtime.FLOAT_TYPES)) { // careful on floats, since 0*NaN is not 0 + return '0'; + } else if (b === 1) { + return a; + } // Doing shifts for division is problematic, as getting the rounding right on negatives is tricky + } else if (op === '+' || op === '-') { + if (b[0] === '-') { + op = op === '+' ? '-' : '+'; b = b.substr(1); } - if (a == 0) { - return op == '+' ? b : '(-' + b + ')'; - } else if (b == 0) { + if (aNumber === 0) { + return op === '+' ? b : '(-' + b + ')'; + } else if (bNumber === 0) { return a; } } @@ -1564,12 +1595,8 @@ function getFastValues(list, op, type) { } function calcFastOffset(ptr, pos, noNeedFirst) { - var offset = noNeedFirst ? '0' : makeGetPos(ptr); - return getFastValue(offset, '+', pos, 'i32'); -} - -function makeGetPos(ptr) { - return ptr; + assert(!noNeedFirst); + return getFastValue(ptr, '+', pos, 'i32'); } var IHEAP_FHEAP = set('IHEAP', 'IHEAPU', 'FHEAP'); @@ -1759,7 +1786,7 @@ function checkBitcast(item) { } else { warnOnce('Casting a function pointer type to a potentially incompatible one (use -s VERBOSE=1 to see more)'); } - warnOnce('See https://github.com/kripken/emscripten/wiki/CodeGuidlinesAndLimitations#function-pointer-issues for more information on dangerous function pointer casts'); + warnOnce('See https://github.com/kripken/emscripten/wiki/CodeGuidelinesAndLimitations#function-pointer-issues for more information on dangerous function pointer casts'); if (ASM_JS) warnOnce('Incompatible function pointer casts are very dangerous with ASM_JS=1, you should investigate and correct these'); } if (oldCount != newCount && oldCount && newCount) showWarning(); @@ -1806,7 +1833,7 @@ function getGetElementPtrIndexes(item) { // struct, and possibly further substructures, all embedded // can also be to 'blocks': [8 x i32]*, not just structs type = removePointing(type); - var indexes = [makeGetPos(ident)]; + var indexes = [ident]; var offset = item.params[1]; if (offset != 0) { if (isStructType(type)) { @@ -1961,13 +1988,12 @@ function makeSignOp(value, type, op, force, ignore) { if (USE_TYPED_ARRAYS == 2 && type == 'i64') { return value; // these are always assumed to be two 32-bit unsigneds. } - if (isPointerType(type)) type = 'i32'; // Pointers are treated as 32-bit ints if (!value) return value; var bits, full; if (type in Runtime.INT_TYPES) { bits = parseInt(type.substr(1)); - full = op + 'Sign(' + value + ', ' + bits + ', ' + Math.floor(ignore || (correctSpecificSign())) + ')'; + full = op + 'Sign(' + value + ', ' + bits + ', ' + Math.floor(ignore || correctSpecificSign()) + ')'; // Always sign/unsign constants at compile time, regardless of CHECK/CORRECT if (isNumber(value)) { return eval(full).toString(); @@ -1975,23 +2001,25 @@ function makeSignOp(value, type, op, force, ignore) { } if ((ignore || !correctSigns()) && !CHECK_SIGNS && !force) return value; if (type in Runtime.INT_TYPES) { + // this is an integer, but not a number (or we would have already handled it) // shortcuts if (!CHECK_SIGNS || ignore) { + if (value === 'true') { + value = '1'; + } else if (value === 'false') { + value = '0'; + } else if (needsQuoting(value)) value = '(' + value + ')'; if (bits === 32) { if (op === 're') { - return '(' + getFastValue(value, '|', '0') + ')'; + return '(' + value + '|0)'; } else { - - return '(' + getFastValue(value, '>>>', '0') + ')'; - // Alternatively, we can consider the lengthier - // return makeInlineCalculation('VALUE >= 0 ? VALUE : ' + Math.pow(2, bits) + ' + VALUE', value, 'tempBigInt'); - // which does not always turn us into a 32-bit *un*signed value + return '(' + value + '>>>0)'; } } else if (bits < 32) { if (op === 're') { - return makeInlineCalculation('(VALUE << ' + (32-bits) + ') >> ' + (32-bits), value, 'tempInt'); + return '((' + value + '<<' + (32-bits) + ')>>' + (32-bits) + ')'; } else { - return '(' + getFastValue(value, '&', Math.pow(2, bits)-1) + ')'; + return '(' + value + '&' + (Math.pow(2, bits)-1) + ')'; } } else { // bits > 32 if (op === 're') { @@ -2081,7 +2109,7 @@ function processMathop(item) { if (item.params[i]) { paramTypes[i] = item.params[i].type || type; idents[i] = finalizeLLVMParameter(item.params[i]); - if (!isNumber(idents[i]) && !isNiceIdent(idents[i])) { + if (needsQuoting(idents[i])) { idents[i] = '(' + idents[i] + ')'; // we may have nested expressions. So enforce the order of operations we want } } else { @@ -2520,3 +2548,8 @@ function makePrintChars(s, sep) { return ret; } +function parseAlign(text) { // parse ", align \d+" + if (!text) return QUANTUM_SIZE; + return parseInt(text.substr(8)); +} + diff --git a/src/postamble.js b/src/postamble.js index 8f585b86..cd892733 100644 --- a/src/postamble.js +++ b/src/postamble.js @@ -35,9 +35,10 @@ var preloadStartTime = null; var calledMain = false; var calledRun = false; -dependenciesFulfilled = function() { +dependenciesFulfilled = function runCaller() { // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false) if (!calledRun && shouldRunNow) run(); + if (!calledRun) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled } Module['callMain'] = Module.callMain = function callMain(args) { diff --git a/src/preamble.js b/src/preamble.js index 02935f8f..183fd0c8 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -299,11 +299,10 @@ function ccallFunc(func, returnType, argTypes, args) { function toC(value, type) { if (type == 'string') { if (value === null || value === undefined || value === 0) return 0; // null string - if (!stack) stack = Runtime.stackSave(); - var ret = Runtime.stackAlloc(value.length+1); - writeStringToMemory(value, ret); - return ret; - } else if (type == 'array') { + value = intArrayFromString(value); + type = 'array'; + } + if (type == 'array') { if (!stack) stack = Runtime.stackSave(); var ret = Runtime.stackAlloc(value.length); writeArrayToMemory(value, ret); @@ -717,7 +716,7 @@ var FAST_MEMORY = Module['FAST_MEMORY'] || {{{ FAST_MEMORY }}}; // Initialize the runtime's memory #if USE_TYPED_ARRAYS // check for full engine support (use string 'subarray' to avoid closure compiler confusion) -assert(!!Int32Array && !!Float64Array && !!(new Int32Array(1)['subarray']) && !!(new Int32Array(1)['set']), +assert(typeof Int32Array !== 'undefined' && typeof Float64Array !== 'undefined' && !!(new Int32Array(1)['subarray']) && !!(new Int32Array(1)['set']), 'Cannot fallback to non-typed array case: Code is too specialized'); #if USE_TYPED_ARRAYS == 1 @@ -908,6 +907,17 @@ function writeArrayToMemory(array, buffer) { } Module['writeArrayToMemory'] = writeArrayToMemory; +function writeAsciiToMemory(str, buffer, dontAddNull) { + for (var i = 0; i < str.length; i++) { +#if ASSERTIONS + assert(str.charCodeAt(i) === str.charCodeAt(i)&0xff); +#endif + {{{ makeSetValue('buffer', 'i', 'str.charCodeAt(i)', 'i8') }}} + } + if (!dontAddNull) {{{ makeSetValue('buffer', 'str.length', 0, 'i8') }}} +} +Module['writeAsciiToMemory'] = writeAsciiToMemory; + {{{ unSign }}} {{{ reSign }}} @@ -993,8 +1003,9 @@ function removeRunDependency(id) { runDependencyWatcher = null; } if (dependenciesFulfilled) { - dependenciesFulfilled(); + var callback = dependenciesFulfilled; dependenciesFulfilled = null; + callback(); // can add another dependenciesFulfilled } } } diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index afb6ecc8..d79dca5a 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -8,8 +8,6 @@ #include "ministring.h" -// TODO: move all set to unorderedset - template <class T, class U> bool contains(const T& container, const U& contained) { return container.find(contained) != container.end(); } diff --git a/src/relooper/emscripten/glue.js b/src/relooper/emscripten/glue.js index 92c50500..587cf529 100644 --- a/src/relooper/emscripten/glue.js +++ b/src/relooper/emscripten/glue.js @@ -13,13 +13,16 @@ RelooperGlue['init'] = function() { this.r = _rl_new_relooper(); }, + RelooperGlue['cleanup'] = function() { + _rl_delete_relooper(this.r); + }, RelooperGlue['addBlock'] = function(text, branchVar) { assert(this.r); assert(text.length+1 < TBUFFER_SIZE, 'buffer too small, increase RELOOPER_BUFFER_SIZE'); - writeStringToMemory(text, tbuffer); + writeAsciiToMemory(text, tbuffer); if (branchVar) { assert(branchVar.length+1 < VBUFFER_SIZE, 'buffer too small, increase RELOOPER_BUFFER_SIZE'); - writeStringToMemory(branchVar, vbuffer); + writeAsciiToMemory(branchVar, vbuffer); } var b = _rl_new_block(tbuffer, branchVar ? vbuffer : 0); _rl_relooper_add_block(this.r, b); @@ -29,14 +32,14 @@ assert(this.r); if (condition) { assert(condition.length+1 < TBUFFER_SIZE/2, 'buffer too small, increase RELOOPER_BUFFER_SIZE'); - writeStringToMemory(condition, tbuffer); + writeAsciiToMemory(condition, tbuffer); condition = tbuffer; } else { condition = 0; // allow undefined, null, etc. as inputs } if (code) { assert(code.length+1 < TBUFFER_SIZE/2, 'buffer too small, increase RELOOPER_BUFFER_SIZE'); - writeStringToMemory(code, tbuffer + TBUFFER_SIZE/2); + writeAsciiToMemory(code, tbuffer + TBUFFER_SIZE/2); code = tbuffer + TBUFFER_SIZE/2; } else { code = 0; // allow undefined, null, etc. as inputs diff --git a/src/runtime.js b/src/runtime.js index 6b1afd80..00031fed 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -145,29 +145,30 @@ var Runtime = { return l + h; }, - //! Returns the size of a type, as C/C++ would have it (in 32-bit, for now), in bytes. + //! Returns the size of a type, as C/C++ would have it (in 32-bit), in bytes. //! @param type The type, by name. - getNativeTypeSize: function(type, quantumSize) { - if (Runtime.QUANTUM_SIZE == 1) return 1; - var size = { - '%i1': 1, - '%i8': 1, - '%i16': 2, - '%i32': 4, - '%i64': 8, - "%float": 4, - "%double": 8 - }['%'+type]; // add '%' since float and double confuse Closure compiler as keys, and also spidermonkey as a compiler will remove 's from '_i8' etc - if (!size) { - if (type.charAt(type.length-1) == '*') { - size = Runtime.QUANTUM_SIZE; // A pointer - } else if (type[0] == 'i') { - var bits = parseInt(type.substr(1)); - assert(bits % 8 == 0); - size = bits/8; + getNativeTypeSize: function(type) { +#if QUANTUM_SIZE == 1 + return 1; +#else + switch (type) { + case 'i1': case 'i8': return 1; + case 'i16': return 2; + case 'i32': return 4; + case 'i64': return 8; + case 'float': return 4; + case 'double': return 8; + default: { + if (type[type.length-1] === '*') { + return Runtime.QUANTUM_SIZE; // A pointer + } else if (type[0] === 'i') { + var bits = parseInt(type.substr(1)); + assert(bits % 8 === 0); + return bits/8; + } } } - return size; +#endif }, //! Returns the size of a structure field, as C/C++ would have it (in 32-bit, diff --git a/src/settings.js b/src/settings.js index 8cdf420c..6e2b9e6c 100644 --- a/src/settings.js +++ b/src/settings.js @@ -202,6 +202,7 @@ var SOCKET_WEBRTC = 0; // Select socket backend, either webrtc or websockets. var OPENAL_DEBUG = 0; // Print out debugging information from our OpenAL implementation. +var GL_ASSERTIONS = 0; // Adds extra checks for error situations in the GL library. Can impact performance. var GL_DEBUG = 0; // Print out all calls into WebGL. As with LIBRARY_DEBUG, you can set a runtime // option, in this case GL.debug. var GL_TESTING = 0; // When enabled, sets preserveDrawingBuffer in the context, to allow tests to work (but adds overhead) @@ -425,6 +426,8 @@ var JS_CHUNK_SIZE = 10240; // Used as a maximum size before breaking up expressi var EXPORT_NAME = 'Module'; // Global variable to export the module as for environments without a standardized module // loading system (e.g. the browser and SM shell). +var COMPILER_ASSERTIONS = 0; // costly (slow) compile-time assertions + // Compiler debugging options var DEBUG_TAGS_SHOWING = []; // Some useful items: diff --git a/src/utility.js b/src/utility.js index 7d122cef..b793106c 100644 --- a/src/utility.js +++ b/src/utility.js @@ -334,6 +334,17 @@ function jsonCompare(x, y) { return JSON.stringify(x) == JSON.stringify(y); } +function sortedJsonCompare(x, y) { + if (x === null || typeof x !== 'object') return x === y; + for (var i in x) { + if (!sortedJsonCompare(x[i], y[i])) return false; + } + for (var i in y) { + if (!sortedJsonCompare(x[i], y[i])) return false; + } + return true; +} + function stringifyWithFunctions(obj) { if (typeof obj === 'function') return obj.toString(); if (obj === null || typeof obj !== 'object') return JSON.stringify(obj); |