diff options
Diffstat (limited to 'src/jsifier.js')
-rw-r--r-- | src/jsifier.js | 726 |
1 files changed, 500 insertions, 226 deletions
diff --git a/src/jsifier.js b/src/jsifier.js index e57facbd..156fd65d 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -10,6 +10,9 @@ var UNDERSCORE_OPENPARENS = set('_', '('); var RELOOP_IGNORED_LASTS = set('return', 'unreachable', 'resume'); var addedLibraryItems = {}; +var asmLibraryFunctions = []; + +var SETJMP_LABEL = -1; // JSifier function JSify(data, functionsOnly, givenFunctions) { @@ -76,7 +79,7 @@ function JSify(data, functionsOnly, givenFunctions) { assert(!BUILD_AS_SHARED_LIB, 'Cannot have both INCLUDE_FULL_LIBRARY and BUILD_AS_SHARED_LIB set.') libFuncsToInclude = []; for (var key in LibraryManager.library) { - if (!key.match(/__(deps|postset|inline)$/)) { + if (!key.match(/__(deps|postset|inline|asm|sig)$/)) { libFuncsToInclude.push(key); } } @@ -107,7 +110,7 @@ function JSify(data, functionsOnly, givenFunctions) { } }); - if (phase == 'funcs') { // pre has function shells, just to defined implementedFunctions + if (phase == 'funcs') { // || phase == 'pre') { // pre has function shells, just to defined implementedFunctions var MAX_BATCH_FUNC_LINES = 1000; while (data.unparsedFunctions.length > 0) { var currFuncLines = []; @@ -230,12 +233,11 @@ function JSify(data, functionsOnly, givenFunctions) { if (value.intertype in PARSABLE_LLVM_FUNCTIONS) { return [finalizeLLVMFunctionCall(value)]; } else if (Runtime.isNumberType(type) || pointingLevels(type) >= 1) { - return makeGlobalUse(indexizeFunctions(parseNumerical(value.value), type)); + return [makeGlobalUse(indexizeFunctions(parseNumerical(value.value), type))]; } else if (value.intertype === 'emptystruct') { return makeEmptyStruct(type); } else if (value.intertype === 'string') { - return JSON.stringify(parseLLVMString(value.text)) + - ' /* ' + value.text.substr(0, 20).replace(/[*<>]/g, '_') + ' */'; // make string safe for inclusion in comment + return parseLLVMString(value.text); } else { return alignStruct(handleSegments(value.contents), type); } @@ -243,9 +245,7 @@ function JSify(data, functionsOnly, givenFunctions) { function parseConst(value, type, ident) { var constant = makeConst(value, type); - if (typeof constant === 'object') { - constant = flatten(constant).map(function(x) { return parseNumerical(x) }) - } + constant = flatten(constant).map(function(x) { return parseNumerical(x) }) return constant; } @@ -253,6 +253,7 @@ function JSify(data, functionsOnly, givenFunctions) { 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'; } @@ -265,106 +266,82 @@ function JSify(data, functionsOnly, givenFunctions) { item.ctors.map(function(ctor) { return ' { func: function() { ' + ctor + '() } }' }).join(',\n') + '\n]);\n'; return ret; + } + + 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 { - 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); - } + item.JS = makeGlobalDef(item.ident); + } - if (item.external && !ASM_JS) { // ASM_JS considers externs to be globals - // Import external global variables from the library if available. - var shortident = item.ident.slice(1); - if (LibraryManager.library[shortident] && - LibraryManager.library[shortident].length && - !BUILD_AS_SHARED_LIB) { - if (addedLibraryItems[shortident]) return ret; - var val = LibraryManager.library[shortident]; - var padding; - if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { - padding = [item.type].concat(zeros(Runtime.getNativeFieldSize(item.type)-1)); - } else { - padding = makeEmptyStruct(item.type); - } - var padded = val.concat(padding.slice(val.length)); - var js = item.ident + '=' + makePointer(JSON.stringify(padded), null, allocator, item.type, index) + ';' - if (LibraryManager.library[shortident + '__postset']) { - js += '\n' + LibraryManager.library[shortident + '__postset']; - } - ret.push({ - intertype: 'GlobalVariablePostSet', - JS: js - }); - } - return ret; + if (!NAMED_GLOBALS && isIndexableGlobal(item.ident)) { + index = makeGlobalUse(item.ident); // index !== null indicates we are indexing this + allocator = 'ALLOC_NONE'; + } + if (item.external) { + if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { + constant = zeros(Runtime.getNativeFieldSize(item.type)); } else { - if (!NAMED_GLOBALS && isIndexableGlobal(item.ident)) { - index = makeGlobalUse(item.ident); // index !== null indicates we are indexing this - allocator = 'ALLOC_NONE'; - } - if (item.external) { - assert(ASM_JS); - if (Runtime.isNumberType(item.type) || isPointerType(item.type)) { - constant = zeros(Runtime.getNativeFieldSize(item.type)); - } else { - constant = makeEmptyStruct(item.type); - } - constant = JSON.stringify(constant); - } else { - constant = parseConst(item.value, item.type, item.ident); - } - if (typeof constant === 'string' && constant[0] != '[') { - constant = [constant]; // A single item. We may need a postset for it. - } - if (typeof constant === 'object') { - // This is a flattened object. We need to find its idents, so they can be assigned to later - constant.forEach(function(value, i) { - if (needsPostSet(value)) { // ident, or expression containing an ident - ret.push({ - intertype: 'GlobalVariablePostSet', - JS: makeSetValue(makeGlobalUse(item.ident), i, value, 'i32', false, true) + ';' // ignore=true, since e.g. rtti and statics cause lots of safe_heap errors - }); - constant[i] = '0'; - } - }); - constant = '[' + constant.join(', ') + ']'; - } - // 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; - - js = (index !== null ? '' : item.ident + '=') + constant + ';'; // \n Module.print("' + item.ident + ' :" + ' + makeGlobalUse(item.ident) + ');'; + constant = makeEmptyStruct(item.type); + } + } else { + constant = parseConst(item.value, item.type, item.ident); + } + assert(typeof constant === 'object');//, [typeof constant, JSON.stringify(constant), item.external]); - // Special case: class vtables. We make sure they are null-terminated, to allow easy runtime operations - if (item.ident.substr(0, 5) == '__ZTV') { - if (index !== null) { - index = getFastValue(index, '+', Runtime.alignMemory(calcAllocatedSize(Variables.globals[item.ident].type))); - } - js += '\n' + makePointer('[0]', null, allocator, ['void*'], index) + ';'; - } - if (!ASM_JS && (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) { - assert(ASM_JS); - js = 'var ' + item.ident + ' = ' + js; // force an explicit naming, even if unnamed globals, for asm forwarding - } - return ret.concat({ - intertype: 'GlobalVariable', - JS: js, + // This is a flattened object. We need to find its idents, so they can be assigned to later + constant.forEach(function(value, i) { + if (needsPostSet(value)) { // ident, or expression containing an ident + ret.push({ + intertype: 'GlobalVariablePostSet', + JS: makeSetValue(makeGlobalUse(item.ident), i, value, 'i32', 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; + // Library items need us to emit something, but everything else requires nothing. + if (!LibraryManager.library[item.ident.slice(1)]) return ret; } + + // 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))); + } + + // 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 += ';'; + + if (!ASM_JS && (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, + }); } }); @@ -375,6 +352,7 @@ function JSify(data, functionsOnly, givenFunctions) { 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); var fix = ''; if (BUILD_AS_SHARED_LIB == 2 && !item.private_) { @@ -396,6 +374,20 @@ function JSify(data, functionsOnly, givenFunctions) { } }); + function processLibraryFunction(snippet, ident) { + snippet = snippet.toString(); + assert(snippet.indexOf('XXX missing C define') == -1, + 'Trying to include a library function with missing C defines: ' + ident + ' | ' + snippet); + + // name the function; overwrite if it's already named + snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function _' + ident + '('); + if (LIBRARY_DEBUG) { + snippet = snippet.replace('{', '{ var ret = (function() { if (Runtime.debug) Module.printErr("[library call:' + ident + ': " + Array.prototype.slice.call(arguments).map(Runtime.prettyPrint) + "]"); '); + snippet = snippet.substr(0, snippet.length-1) + '}).apply(this, arguments); if (Runtime.debug && typeof ret !== "undefined") Module.printErr(" [ return:" + Runtime.prettyPrint(ret)); return ret; \n}'; + } + return snippet; + } + // functionStub substrate.addActor('FunctionStub', { processItem: function(item) { @@ -424,23 +416,16 @@ function JSify(data, functionsOnly, givenFunctions) { deps.push(snippet); snippet = '_' + snippet; } - if (ASM_JS && (typeof target == 'function' || /Math\..+/.exec(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 = snippet.toString(); - assert(snippet.indexOf('XXX missing C define') == -1, - 'Trying to include a library function with missing C defines: ' + ident + ' | ' + snippet); - - // name the function; overwrite if it's already named - snippet = snippet.replace(/function(?:\s+([^(]+))?\s*\(/, 'function _' + ident + '('); - if (LIBRARY_DEBUG) { - snippet = snippet.replace('{', '{ var ret = (function() { if (Runtime.debug) Module.print("[library call:' + ident + ': " + Array.prototype.slice.call(arguments).map(Runtime.prettyPrint) + "]"); '); - snippet = snippet.substr(0, snippet.length-1) + '}).apply(this, arguments); if (Runtime.debug && typeof ret !== "undefined") Module.print(" [ return:" + Runtime.prettyPrint(ret)); return ret; }'; - } + snippet = processLibraryFunction(snippet, ident); if (ASM_JS) Functions.libraryFunctions[ident] = 1; } @@ -457,18 +442,43 @@ function JSify(data, functionsOnly, givenFunctions) { 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; } - var text = (deps ? '\n' + deps.map(addFromLibrary).filter(function(x) { return x != '' }).join('\n') : ''); - text += isFunction ? snippet : 'var ' + ident + '=' + snippet + ';'; - if (EXPORT_ALL || (ident in EXPORTED_FUNCTIONS)) { - text += '\nModule["' + 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; + } } - return text; + if ((!ASM_JS || phase == 'pre') && + (EXPORT_ALL || (ident in EXPORTED_FUNCTIONS))) { + contentText += '\nModule["' + ident + '"] = ' + ident + ';'; + } + return depsText + contentText; } var ret = [item]; @@ -477,13 +487,20 @@ function JSify(data, functionsOnly, givenFunctions) { if (BUILD_AS_SHARED_LIB) { // Shared libraries reuse the runtime of their parents. item.JS = ''; - } else if (LibraryManager.library.hasOwnProperty(shortident)) { - item.JS = addFromLibrary(shortident); } else { - item.JS = 'var ' + item.ident + '; // stub for ' + item.ident; - if (WARN_ON_UNDEFINED_SYMBOLS) { - warn('Unresolved symbol: ' + item.ident); + // 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; } @@ -573,6 +590,10 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS += 'function ' + func.ident + '(' + paramIdents.join(', ') + ') {\n'; + if (PGO) { + func.JS += ' PGOMonitor.called["' + func.ident + '"] = 1;\n'; + } + if (ASM_JS) { // spell out argument types func.params.forEach(function(param) { @@ -591,25 +612,19 @@ function JSify(data, functionsOnly, givenFunctions) { } for (i = 0; i < chunks.length; i++) { func.JS += ' var ' + chunks[i].map(function(v) { - if (v.type != 'i64') { - return v.ident + ' = ' + asmInitializer(v.type); //, func.variables[v.ident].impl); + 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 v.ident + '$0 = 0, ' + v.ident + '$1 = 1'; + return range(Math.ceil(getBits(type)/32)).map(function(i) { + return v.ident + '$' + i + '= 0'; + }).join(','); } }).join(', ') + ';\n'; } } } - if (PROFILE) { - func.JS += ' if (PROFILING) { ' - + 'var __parentProfilingNode__ = PROFILING_NODE; PROFILING_NODE = PROFILING_NODE.children["' + func.ident + '"]; ' - + 'if (!PROFILING_NODE) __parentProfilingNode__.children["' + func.ident + '"] = PROFILING_NODE = { time: 0, children: {}, calls: 0 };' - + 'PROFILING_NODE.calls++; ' - + 'var __profilingStartTime__ = Date.now() ' - + '}\n'; - } - if (true) { // TODO: optimize away when not needed if (CLOSURE_ANNOTATIONS) func.JS += '/** @type {number} */'; func.JS += ' var label = 0;\n'; @@ -692,26 +707,43 @@ function JSify(data, functionsOnly, givenFunctions) { ret += indent + 'label = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + getOriginalLabelId(block.entries[0]) + ' */' : '') + '\n'; } // otherwise, should have been set before! if (func.setjmpTable) { - 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[0]) + '": ' + 'function(value) { label = ' + getLabelId(triple[1]) + '; ' + triple[2] + ' = value },'; - }); - ret += 'dummy: 0'; - ret += '};\n'; + 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) { + if (func.setjmpTable && !ASM_JS) { ret += 'try { '; } - ret += 'switch(label) {\n'; + ret += 'switch(' + asmCoercion('label', 'i32') + ') {\n'; ret += block.labels.map(function(label) { return indent + ' case ' + getLabelId(label.ident) + ': ' + (SHOW_LABELS ? '// ' + getOriginalLabelId(label.ident) : '') + '\n' + getLabelLines(label, indent + ' '); - }).join('\n'); - ret += '\n' + indent + ' default: assert(0, "bad label: " + label);\n' + indent + '}'; - if (func.setjmpTable) { + }).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 += ' 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 + ' 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 { @@ -768,10 +800,10 @@ function JSify(data, functionsOnly, givenFunctions) { func.JS += walkBlock(func.block, ' '); // Finalize function if (LABEL_DEBUG && functionNameFilterTest(func.ident)) func.JS += " INDENT = INDENT.substr(0, INDENT.length-2);\n"; - // Add an unneeded return, needed for strict mode to not throw warnings in some cases. - // If we are not relooping, then switches make it unimportant to have this (and, we lack hasReturn anyhow) - if (RELOOP && func.lines.length > 0 && func.labels.filter(function(label) { return label.hasReturn }).length > 0) { - func.JS += ' return' + (func.returnType !== 'void' ? ' null' : '') + ';\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 += ' return ' + asmCoercion('0', func.returnType); } func.JS += '}\n'; @@ -892,7 +924,11 @@ function JSify(data, functionsOnly, givenFunctions) { case VAR_NATIVIZED: if (isNumber(item.ident)) { // Direct write to a memory address; this may be an intentional segfault, if not, it is a bug in the source - return 'throw "fault on write to ' + item.ident + '";'; + if (ASM_JS) { + return 'abort(' + item.ident + ')'; + } else { + return 'throw "fault on write to ' + item.ident + '";'; + } } return item.ident + '=' + value + ';'; // We have the actual value here break; @@ -997,13 +1033,13 @@ function JSify(data, functionsOnly, givenFunctions) { } for (var i = 0; i < idents.length; i++) { if (keys(deps[idents[i]]).length == 0) { - pre = 'var ' + idents[i] + ' = ' + valueJSes[idents[i]] + ';' + pre; + post = 'var ' + idents[i] + ' = ' + valueJSes[idents[i]] + ';' + post; remove(idents[i]); continue mainLoop; } } // If we got here, we have circular dependencies, and must break at least one. - pre = 'var ' + idents[0] + '$phi = ' + valueJSes[idents[0]] + ';' + pre; + pre += 'var ' + idents[0] + '$phi = ' + valueJSes[idents[0]] + ';'; post += 'var ' + idents[0] + ' = ' + idents[0] + '$phi;'; remove(idents[0]); } @@ -1045,7 +1081,7 @@ function JSify(data, functionsOnly, givenFunctions) { } }); makeFuncLineActor('switch', function(item) { - // TODO: Find a case where switch is important, and benchmark that. var SWITCH_IN_SWITCH = 1; + var useIfs = RELOOP || item.switchLabels.length < 1024; // with a huge number of cases, if-else which looks nested to js parsers can cause problems var phiSets = calcPhiSets(item); // Consolidate checks that go to the same label. This is important because it makes the relooper simpler and faster. var targetLabels = {}; // for each target label, the list of values going to it @@ -1063,16 +1099,27 @@ function JSify(data, functionsOnly, givenFunctions) { if (RELOOP) { item.groupedLabels = []; } + if (!useIfs) { + ret += 'switch(' + signedIdent + ') {\n'; + } for (var targetLabel in targetLabels) { - if (!first) { + if (!first && useIfs) { ret += 'else '; } else { first = false; } - var value = targetLabels[targetLabel].map(function(value) { - return makeComparison(signedIdent, makeSignOp(value, item.type, 're'), item.type) - }).join(' || '); - ret += 'if (' + value + ') {\n'; + var value; + if (useIfs) { + value = targetLabels[targetLabel].map(function(value) { + return makeComparison(signedIdent, '==', makeSignOp(value, item.type, 're'), item.type) + }).join(' | '); + ret += 'if (' + value + ') {\n'; + } else { + value = targetLabels[targetLabel].map(function(value) { + return 'case ' + makeSignOp(value, item.type, 're') + ':'; + }).join(' '); + ret += value + '{\n'; + } var phiSet = getPhiSetsForLabel(phiSets, targetLabel); ret += ' ' + phiSet + makeBranch(targetLabel, item.currLabelId || null) + '\n'; ret += '}\n'; @@ -1084,10 +1131,18 @@ function JSify(data, functionsOnly, givenFunctions) { }); } } - if (item.switchLabels.length > 0) ret += 'else {\n'; var phiSet = item.defaultLabelJS = getPhiSetsForLabel(phiSets, item.defaultLabel); - ret += phiSet + makeBranch(item.defaultLabel, item.currLabelId) + '\n'; - if (item.switchLabels.length > 0) ret += '}\n'; + if (useIfs) { + if (item.switchLabels.length > 0) ret += 'else {\n'; + ret += phiSet + makeBranch(item.defaultLabel, item.currLabelId) + '\n'; + if (item.switchLabels.length > 0) ret += '}\n'; + } else { + ret += 'default: {\n'; + ret += phiSet + makeBranch(item.defaultLabel, item.currLabelId) + '\n'; + ret += '}\n'; + + ret += '} break; \n'; // finish switch and break, to move control flow properly (breaks from makeBranch just broke out of the switch) + } if (item.value) { ret += ' ' + toNiceIdent(item.value); } @@ -1095,41 +1150,53 @@ function JSify(data, functionsOnly, givenFunctions) { }); makeFuncLineActor('return', function(item) { var ret = RuntimeGenerator.stackExit(item.funcData.initialStack, item.funcData.otherStackAllocations) + ';\n'; - if (PROFILE) { - ret += 'if (PROFILING) { ' - + 'PROFILING_NODE.time += Date.now() - __profilingStartTime__; ' - + 'PROFILING_NODE = __parentProfilingNode__ ' - + '}\n'; - } if (LABEL_DEBUG && functionNameFilterTest(item.funcData.ident)) { ret += "Module.print(INDENT + 'Exiting: " + item.funcData.ident + "');\n" + "INDENT = INDENT.substr(0, INDENT.length-2);\n"; } ret += 'return'; - if (item.value) { - ret += ' ' + asmCoercion(finalizeLLVMParameter(item.value), item.type); + var value = item.value ? finalizeLLVMParameter(item.value) : null; + if (!value && item.funcData.returnType != 'void') value = '0'; // no-value returns must become value returns if function returns + if (value) { + ret += ' ' + asmCoercion(value, item.type); } return ret + ';'; }); makeFuncLineActor('resume', function(item) { + if (DISABLE_EXCEPTION_CATCHING) 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 + return (EXCEPTION_DEBUG ? 'Module.print("no exception to resume")' : '') + ';'; + } // 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 (EXCEPTION_DEBUG ? 'Module.print("Resuming exception");' : '') + - 'if (' + makeGetValue('_llvm_eh_exception.buf', 0, 'void*') + ' == 0) { ' + makeSetValue('_llvm_eh_exception.buf', 0, ptr, 'void*') + ' } ' + - 'throw ' + ptr + ';'; + return '___resumeException(' + asmCoercion(ptr, 'i32') + ')'; }); makeFuncLineActor('invoke', function(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); var phiSets = calcPhiSets(item); - var call_ = makeFunctionCall(item.ident, item.params, item.funcData, item.type); - var ret = '(function() { try { __THREW__ = 0; return ' - + call_ + ' ' - + '} catch(e) { ' - + 'if (typeof e != "number") throw e; ' - + 'if (ABORT) throw e; __THREW__ = 1; ' - + (EXCEPTION_DEBUG ? 'Module.print("Exception: " + e + ", currently at: " + (new Error().stack)); ' : '') - + 'return null } })();'; + var call_ = makeFunctionCall(item.ident, item.params, item.funcData, item.type, ASM_JS && !disabled); + + var ret; + + if (disabled) { + ret = call_ + ';'; + } else if (ASM_JS) { + call_ = call_.replace('; return', ''); // we auto-add returns when aborting, but do not need them here + ret = '(__THREW__ = 0,' + call_ + ');'; + } else { + ret = '(function() { try { __THREW__ = 0; return ' + + call_ + ' ' + + '} catch(e) { ' + + 'if (typeof e != "number") throw e; ' + + 'if (ABORT) throw e; __THREW__ = 1; ' + + (EXCEPTION_DEBUG ? 'Module.print("Exception: " + e + ", currently at: " + (new Error().stack)); ' : '') + + 'return null } })();'; + } + if (item.assignTo) { ret = 'var ' + item.assignTo + ' = ' + ret; if (USE_TYPED_ARRAYS == 2 && isIllegalType(item.type)) { @@ -1142,7 +1209,7 @@ function JSify(data, functionsOnly, givenFunctions) { } item.reloopingJS = ret; // everything but the actual branching (which the relooper will do for us) item.toLabelJS = getPhiSetsForLabel(phiSets, item.toLabel); - item.unwindLabelJS = getPhiSetsForLabel(phiSets, item.unwindLabel); + item.unwindLabelJS = (ASM_JS ? '__THREW__ = 0;' : '') + getPhiSetsForLabel(phiSets, item.unwindLabel); ret += 'if (!__THREW__) { ' + item.toLabelJS + makeBranch(item.toLabel, item.currLabelId) + ' } else { ' + item.unwindLabelJS + makeBranch(item.unwindLabel, item.currLabelId) + ' }'; return ret; @@ -1154,17 +1221,26 @@ function JSify(data, functionsOnly, givenFunctions) { 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 '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)'; case 'xchg': return '(tempValue=' + makeGetValue(param1, 0, type) + ',' + makeSetValue(param1, 0, param2, type, null, null, null, null, ',') + ',tempValue)'; case 'cmpxchg': { var param3 = finalizeLLVMParameter(item.params[2]); - return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==' + param2 + ' && (' + makeSetValue(param1, 0, param3, type, null, null, null, null, ',') + ')),tempValue)'; + return '(tempValue=' + makeGetValue(param1, 0, type) + ',(' + makeGetValue(param1, 0, type) + '==(' + param2 + '|0) ? ' + makeSetValue(param1, 0, param3, type, null, null, null, null, ',') + ' : 0),tempValue)'; } default: throw 'unhandled atomic op: ' + item.op; } }); makeFuncLineActor('landingpad', function(item) { - var catchTypeArray = item.catchables.map(finalizeLLVMParameter).join(','); - var ret = '___cxa_find_matching_catch('+ makeGetValue('_llvm_eh_exception.buf', '0', 'void*') +',' + makeGetValue('_llvm_eh_exception.buf', QUANTUM_SIZE, 'void*') + ',[' + catchTypeArray +'])'; + if (DISABLE_EXCEPTION_CATCHING && 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!'); + return ret; + } + var catchTypeArray = item.catchables.map(finalizeLLVMParameter).map(function(element) { return asmCoercion(element, 'i32') }).join(','); + var ret = asmCoercion('___cxa_find_matching_catch(-1, -1' + (catchTypeArray.length > 0 ? ',' + catchTypeArray : '') +')', 'i32'); if (USE_TYPED_ARRAYS == 2) { ret = makeVarDef(item.assignTo) + '$0 = ' + ret + '; ' + item.assignTo + '$1 = tempRet0;'; item.assignTo = null; @@ -1176,6 +1252,15 @@ function JSify(data, functionsOnly, givenFunctions) { 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 + ')'; + } else { + return 'throw "fault on read from ' + item.ident + '";'; + } + } return value; // We have the actual value here } case VAR_EMULATED: return makeGetValue(value, 0, item.type, 0, item.unsigned, 0, item.align); @@ -1199,14 +1284,19 @@ function JSify(data, functionsOnly, givenFunctions) { makeFuncLineActor('insertvalue', function(item) { assert(item.indexes.length == 1); // TODO: see extractvalue var ret = '(', ident; - if (item.ident === 'undef') { + if (item.ident === '0') { item.ident = 'tempValue'; 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) { - return makeBranch(finalizeLLVMParameter(item.value), item.currLabelId, true); + 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) { if (typeof item.allocatedIndex === 'number') { @@ -1216,6 +1306,14 @@ function JSify(data, functionsOnly, givenFunctions) { return RuntimeGenerator.stackAlloc(getFastValue(calcAllocatedSize(item.allocatedType), '*', item.allocatedNum)); } }); + makeFuncLineActor('va_arg', function(item) { + assert(TARGET_LE32); + var ident = item.value.ident; + var move = Runtime.STACK_ALIGN; + return '(tempInt=' + makeGetValue(ident, 4, '*') + ',' + + makeSetValue(ident, 4, 'tempInt + ' + move, '*') + ',' + + makeGetValue(makeGetValue(ident, 0, '*'), 'tempInt', item.type) + ')'; + }); makeFuncLineActor('mathop', processMathop); @@ -1230,21 +1328,46 @@ function JSify(data, functionsOnly, givenFunctions) { return ret; }); - function makeFunctionCall(ident, params, funcData, type) { + function makeFunctionCall(ident, params, funcData, type, forceByPointer) { // We cannot compile assembly. See comment in intertyper.js:'Call' assert(ident != 'asm', 'Inline assembly cannot be compiled to JavaScript!'); - var shortident = LibraryManager.getRootIdent(ident.slice(1)) || ident.slice(1); // ident may not be in library, if all there is is ident__inline + if (ASM_JS && funcData.setjmpTable) forceByPointer = true; // in asm.js mode, we must do an invoke for each call + + ident = Variables.resolveAliasToIdent(ident); + var shortident = ident.slice(1); + var simpleIdent = shortident; + if (isLocalVar(ident)) { + var callIdent = ident; + } else { + // Not a local var, check if in library + var callIdent = LibraryManager.getRootIdent(simpleIdent); + if (callIdent) { + simpleIdent = callIdent; // ident may not be in library, if all there is is ident__inline, but in this case it is + if (callIdent.indexOf('.') < 0) { + callIdent = '_' + callIdent; // Not Math.*, so add the normal prefix + } + } else { + callIdent = ident; + } + if (callIdent == '0') return 'abort(-2)'; + } + var args = []; var argsTypes = []; var varargs = []; var varargsTypes = []; var varargsByVals = {}; var ignoreFunctionIndexizing = []; - var useJSArgs = (shortident + '__jsargs') in LibraryManager.library; + var useJSArgs = (simpleIdent + '__jsargs') in LibraryManager.library; var hasVarArgs = isVarArgsFunctionType(type); - var normalArgs = (hasVarArgs && !useJSArgs) ? countNormalArgs(type) : -1; + var normalArgs = (hasVarArgs && !useJSArgs) ? countNormalArgs(type, null, true) : -1; var byPointer = getVarData(funcData, ident); + var byPointerForced = false; + + if (forceByPointer && !byPointer) { + byPointer = byPointerForced = true; + } params.forEach(function(param, i) { var val = finalizeParam(param); @@ -1259,6 +1382,7 |