diff options
34 files changed, 3628 insertions, 3318 deletions
@@ -1,4 +1,4 @@ -The following authors have all licensed their contributions to Emscripten +The following authors have all licensed their contributions to Emscripten under the licensing terms detailed in LICENSE. (Authors keep copyright of their contributions, of course; they just grant @@ -97,4 +97,6 @@ a license to everyone to use it as detailed in LICENSE.) * Charlie Birks <admin@daftgames.net> * Ranger Harke <ranger.harke@autodesk.com> (copyright owned by Autodesk, Inc.) * Tobias Vrinssen <tobias@vrinssen.de> +* Patrick R. Martin <patrick.martin.r@gmail.com> +* Richard Quirk <richard.quirk@gmail.com> @@ -238,6 +238,9 @@ Options that are modified or new in %s include: (see --llvm-opts), setting this has no effect. + Note that LLVM LTO is not perfectly stable yet, + and can can cause code to behave incorrectly. + --closure <on> 0: No closure compiler (default in -O2 and below) 1: Run closure compiler. This greatly reduces code size and may in some cases increase diff --git a/emscripten.py b/emscripten.py index 4d744fdd..19e2160d 100755 --- a/emscripten.py +++ b/emscripten.py @@ -39,7 +39,7 @@ def scan(ll, settings): if len(blockaddrs) > 0: settings['NECESSARY_BLOCKADDRS'] = blockaddrs -NUM_CHUNKS_PER_CORE = 1.25 +NUM_CHUNKS_PER_CORE = 1.0 MIN_CHUNK_SIZE = 1024*1024 MAX_CHUNK_SIZE = float(os.environ.get('EMSCRIPT_MAX_CHUNK_SIZE') or 'inf') # configuring this is just for debugging purposes @@ -209,7 +209,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, if cores > 1: intended_num_chunks = int(round(cores * NUM_CHUNKS_PER_CORE)) chunk_size = max(MIN_CHUNK_SIZE, total_ll_size / intended_num_chunks) - chunk_size += 3*len(meta) + len(forwarded_data)/3 # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) and forwarded data (less expensive but potentially significant) + chunk_size += 3*len(meta) # keep ratio of lots of function code to meta (expensive to process, and done in each parallel task) chunk_size = min(MAX_CHUNK_SIZE, chunk_size) else: chunk_size = MAX_CHUNK_SIZE # if 1 core, just use the max chunk size diff --git a/src/analyzer.js b/src/analyzer.js index b20dedff..750f2a4c 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -27,82 +27,69 @@ 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; + + // 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; + 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 +100,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,725 +180,722 @@ 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 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 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 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 + } + 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; } - // 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 (!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. |