diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/analyzer.js | 68 | ||||
-rw-r--r-- | src/jsifier.js | 28 | ||||
-rw-r--r-- | src/library.js | 27 | ||||
-rw-r--r-- | src/parseTools.js | 38 | ||||
-rw-r--r-- | src/preamble.js | 62 |
5 files changed, 188 insertions, 35 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index 1c643303..593b4650 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -11,11 +11,8 @@ var VAR_EMULATED = 'emulated'; var ENTRY_IDENT = toNiceIdent('%0'); var ENTRY_IDENTS = set(toNiceIdent('%0'), toNiceIdent('%1')); -function cleanFunc(func) { - func.lines = func.lines.filter(function(line) { return line.intertype !== null }); - func.labels.forEach(function(label) { - label.lines = label.lines.filter(function(line) { return line.intertype !== null }); - }); +function recomputeLines(func) { + func.lines = func.labels.map(function(label) { return label.lines }).reduce(concatenator, []); } // Handy sets @@ -71,6 +68,7 @@ function analyzer(data, sidePass) { 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') { @@ -142,7 +140,7 @@ function analyzer(data, sidePass) { var ret = new Array(Math.ceil(bits/32)); var i = 0; while (bits > 0) { - ret[i] = { ident: parsed[i].toString(), bits: Math.min(32, bits) }; + ret[i] = { ident: (parsed[i]|0).toString(), bits: Math.min(32, bits) }; // resign all values bits -= 32; i++; } @@ -1111,10 +1109,9 @@ function analyzer(data, sidePass) { }); func.labelIds[toNiceIdent('%0')] = -1; // entry is always -1 - func.hasIndirectBr = false; func.lines.forEach(function(line) { if (line.intertype == 'indirectbr') { - func.hasIndirectBr = true; + func.forceEmulated = true; } }); @@ -1129,6 +1126,52 @@ function analyzer(data, sidePass) { return null; } + // 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.ident == setjmp) { + // Add a new label + var oldIdent = label.ident; + var newIdent = oldIdent + '$$' + i; + if (!func.setjmpTable) func.setjmpTable = []; + func.setjmpTable.push([oldIdent, newIdent, line.assignTo]); + func.labels.splice(i+1, 0, { + intertype: 'label', + ident: newIdent, + lineNum: label.lineNum + 0.5, + lines: label.lines.slice(j+1) + }); + label.lines = label.lines.slice(0, j+1); + label.lines.push({ + intertype: 'branch', + label: toNiceIdent(newIdent), + 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 == oldIdent) { + phi.params[i].label = newIdent; + } + } + } + }); + }); + } + } + } + if (func.setjmpTable) { + func.forceEmulated = true; + recomputeLines(func); + } + if (!MICRO_OPTS) { // 'Emulate' phis, by doing an if where the phi appears in the .ll. For this // we need __lastLabel__. @@ -1223,8 +1266,7 @@ function analyzer(data, sidePass) { var lines = func.labels[0].lines; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca') break; - assert(isNumber(item.allocatedNum)); + 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) { @@ -1235,7 +1277,7 @@ function analyzer(data, sidePass) { var index = 0; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca') break; + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) break; item.allocatedIndex = index; index += item.allocatedSize; delete item.allocatedSize; @@ -1260,7 +1302,7 @@ function analyzer(data, sidePass) { var finishedInitial = false; for (var i = 0; i < lines.length; i++) { var item = lines[i]; - if (!item.assignTo || item.intertype != 'alloca') { + if (!item.assignTo || item.intertype != 'alloca' || !isNumber(item.allocatedNum)) { finishedInitial = true; continue; } @@ -1745,7 +1787,7 @@ function analyzer(data, sidePass) { // TODO: each of these can be run in parallel item.functions.forEach(function(func) { dprint('relooping', "// relooping function: " + func.ident); - func.block = makeBlock(func.labels, [toNiceIdent(func.labels[0].ident)], func.labelsDict, func.hasIndirectBr); + func.block = makeBlock(func.labels, [toNiceIdent(func.labels[0].ident)], func.labelsDict, func.forceEmulated); }); return finish(); diff --git a/src/jsifier.js b/src/jsifier.js index ea4dff06..bc827135 100644 --- a/src/jsifier.js +++ b/src/jsifier.js @@ -66,7 +66,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)$/)) { + if (!key.match(/__(deps|postset|inline)$/)) { libFuncsToInclude.push(key); } } @@ -576,12 +576,28 @@ function JSify(data, functionsOnly, givenFunctions) { if (block.entries.length == 1) { ret += indent + '__label__ = ' + getLabelId(block.entries[0]) + '; ' + (SHOW_LABELS ? '/* ' + block.entries[0] + ' */' : '') + '\n'; } // otherwise, should have been set before! - ret += indent + 'while(1) switch(__label__) {\n'; + if (func.setjmpTable) { + var setjmpTable = {}; + 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'; + } + ret += indent + 'while(1) '; + if (func.setjmpTable) { + ret += 'try { '; + } + ret += 'switch(__label__) {\n'; ret += block.labels.map(function(label) { return indent + ' case ' + getLabelId(label.ident) + ': // ' + label.ident + '\n' + getLabelLines(label, indent + ' '); }).join('\n'); ret += '\n' + indent + ' default: assert(0, "bad label: " + __label__);\n' + indent + '}'; + if (func.setjmpTable) { + ret += ' } catch(e) { if (!e.longjmp) throw(e); setjmpTable[e.label](e.value) }'; + } } else { ret += (SHOW_LABELS ? indent + '/* ' + block.entries[0] + ' */' : '') + '\n' + getLabelLines(block.labels[0], indent); } @@ -1029,10 +1045,14 @@ function JSify(data, functionsOnly, givenFunctions) { makeFuncLineActor('mathop', processMathop); makeFuncLineActor('bitcast', function(item) { - return processMathop({ + var temp = { op: 'bitcast', variant: null, type: item.type, + assignTo: item.assignTo, param1: item.params[0] - }); + }; + 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) { diff --git a/src/library.js b/src/library.js index 6feb37e4..cda318d7 100644 --- a/src/library.js +++ b/src/library.js @@ -3296,7 +3296,7 @@ LibraryManager.library = { }, atexit: function(func, arg) { - __ATEXIT__.push({ func: func, arg: arg }); + __ATEXIT__.unshift({ func: func, arg: arg }); }, __cxa_atexit: 'atexit', @@ -4384,7 +4384,7 @@ LibraryManager.library = { ptrTV -= {{{ Runtime.QUANTUM_SIZE }}}; var TI = {{{ makeGetValue('ptrTV', '0', '*') }}}; do { - if (TI == attemptedTI) return 1; + if (TI == attemptedTI) return ptr; // Go to parent class var type_infoAddr = {{{ makeGetValue('TI', '0', '*') }}} - {{{ Runtime.QUANTUM_SIZE*2 }}}; var type_info = {{{ makeGetValue('type_infoAddr', '0', '*') }}}; @@ -5349,19 +5349,26 @@ LibraryManager.library = { // ========================================================================== // setjmp.h + // + // Basic support for setjmp/longjmp: enough to run the wikipedia example and + // hopefully handle most normal behavior. We do not support cases where + // longjmp behavior is undefined (for example, if the setjmp function returns + // before longjmp is called). + // + // Note that we need to emulate functions that use setjmp, and also to create + // a new label we can return to. Emulation make such functions slower, this + // can be alleviated by making a new function containing just the setjmp + // related functionality so the slowdown is more limited. // ========================================================================== - setjmp: function(env) { - // XXX print('WARNING: setjmp() not really implemented, will fail if longjmp() is actually called'); - return 0; + setjmp__inline: function(env) { + // Save the label + return '(' + makeSetValue(env, '0', '__label__', 'i32') + ', 0)'; }, - _setjmp: 'setjmp', - longjmp: function(env, val) { - // not really working... - assert(0); + longjmp: function(env, value) { + throw { longjmp: true, label: {{{ makeGetValue('env', '0', 'i32') }}}, value: value || 1 }; }, - _longjmp: 'longjmp', // ========================================================================== // signal.h diff --git a/src/parseTools.js b/src/parseTools.js index 2ab43ebf..c1eff803 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -546,7 +546,7 @@ function splitI64(value) { // be slightly higher than expected. And if we get 4294967296, that will turn into a 0 if put into a // HEAP32 or |0'd, etc. if (legalizedI64s) { - return [value + '>>>0', 'Math.min(Math.floor(' + value + '/4294967296), 4294967295)']; + return [value + '>>>0', 'Math.min(Math.floor((' + value + ')/4294967296), 4294967295)']; } else { return makeInlineCalculation(makeI64('VALUE>>>0', 'Math.min(Math.floor(VALUE/4294967296), 4294967295)'), value, 'tempBigIntP'); } @@ -606,6 +606,22 @@ function parseArbitraryInt(str, bits) { } } + function mul2(v) { // v *= 2 + for (var i = v.length-1; i >= 0; i--) { + var d = v[i]*2; + r = d >= 10; + v[i] = d%10; + var j = i-1; + if (r) { + if (j < 0) { + v.unshift(1); + break; + } + v[j] += 0.5; // will be multiplied + } + } + } + function subtract(v, w) { // v -= w. we assume v >= w while (v.length > w.length) w.splice(0, 0, 0); for (var i = 0; i < v.length; i++) { @@ -635,9 +651,11 @@ function parseArbitraryInt(str, bits) { if (str[0] == '-') { // twos-complement is needed - assert(bits == 64, "we only support 64-bit two's complement so far"); str = str.substr(1); - v = str2vec('18446744073709551616'); // 2^64 + v = str2vec('1'); + for (var i = 0; i < bits; i++) { + mul2(v); + } subtract(v, str2vec(str)); } else { v = str2vec(str); @@ -1644,7 +1662,7 @@ function processMathop(item) { case 'fptoui': case 'fptosi': return finish(splitI64(ident1)); case 'icmp': { switch (variant) { - case 'uge': return high1 + ' >= ' + high2 + ' && (' + high1 + ' > ' + high + ' || ' + + case 'uge': return high1 + ' >= ' + high2 + ' && (' + high1 + ' > ' + high2 + ' || ' + low1 + ' >= ' + low2 + ')'; case 'sge': return '(' + high1 + '|0) >= (' + high2 + '|0) && ((' + high1 + '|0) > (' + high2 + '|0) || ' + '(' + low1 + '|0) >= (' + low2 + '|0))'; @@ -1685,9 +1703,17 @@ function processMathop(item) { var inType = item.param1.type; var outType = item.type; if (inType in Runtime.INT_TYPES && outType in Runtime.FLOAT_TYPES) { - return makeInlineCalculation('tempDoubleI32[0]=VALUE[0],tempDoubleI32[1]=VALUE[1],tempDoubleF64[0]', ident1, 'tempI64'); + if (legalizedI64s) { + return '(tempDoubleI32[0]=' + ident1 + '$0, tempDoubleI32[1]=' + ident1 + '$1, tempDoubleF64[0])'; + } else { + return makeInlineCalculation('tempDoubleI32[0]=VALUE[0],tempDoubleI32[1]=VALUE[1],tempDoubleF64[0]', ident1, 'tempI64'); + } } else if (inType in Runtime.FLOAT_TYPES && outType in Runtime.INT_TYPES) { - return '(tempDoubleF64[0]=' + ident1 + ',[tempDoubleI32[0],tempDoubleI32[1]])'; + if (legalizedI64s) { + return 'tempDoubleF64[0]=' + ident1 + '; ' + finish(['tempDoubleI32[0]','tempDoubleI32[1]']); + } else { + return '(tempDoubleF64[0]=' + ident1 + ',[tempDoubleI32[0],tempDoubleI32[1]])'; + } } else { throw 'Invalid I64_MODE1 bitcast: ' + dump(item) + ' : ' + item.param1.type; } diff --git a/src/preamble.js b/src/preamble.js index e957c212..54e3b04c 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -357,13 +357,71 @@ function assert(condition, text) { } } +var globalScope = this; + +// C calling interface. A convenient way to call C functions (in C files, or +// defined with extern "C"). +// +// Note: LLVM optimizations can inline and remove functions, after which you will not be +// able to call them. Adding +// +// __attribute__((used)) +// +// to the function definition will prevent that. +// +// Note: Closure optimizations will minify function names, making +// functions no longer callable. If you run closure (on by default +// in -O2 and above), you should export the functions you will call +// by calling emcc with something like +// +// -s EXPORTED_FUNCTIONS='["_func1","_func2"]' +// +// @param ident The name of the C function (note that C++ functions will be name-mangled - use extern "C") +// @param returnType The return type of the function, one of the JS types 'number' or 'string', or 'pointer' for any type of C pointer. +// @param argTypes An array of the types of arguments for the function (if there are no arguments, this can be ommitted). Types are as in returnType. +// @param args An array of the arguments to the function, as native JS values (except for 'pointer', which is a 'number'). +// Note that string arguments will be stored on the stack (the JS string will become a C string on the stack). +// @return The return value, as a native JS value (except for 'pointer', which is a 'number'). +function ccall(ident, returnType, argTypes, args) { + function toC(value, type) { + if (type == 'string') { + var ret = STACKTOP; + Runtime.stackAlloc(value.length+1); + writeStringToMemory(value, ret); + return ret; + } + return value; + } + function fromC(value, type) { + if (type == 'string') { + return Pointer_stringify(value); + } + return value; + } + try { + var func = eval('_' + ident); + } catch(e) { + try { + func = globalScope['Module']['_' + ident]; // closure exported function + } catch(e) {} + } + assert(func, 'Cannot call unknown function ' + ident + ' (perhaps LLVM optimizations or closure removed it?)'); + var i = 0; + var cArgs = args ? args.map(function(arg) { + return toC(arg, argTypes[i++]); + }) : []; + return fromC(func.apply(null, cArgs), returnType); +} +Module["ccall"] = ccall; + // Sets a value in memory in a dynamic way at run-time. Uses the // type data. This is the same as makeSetValue, except that // makeSetValue is done at compile-time and generates the needed // code then, whereas this function picks the right code at // run-time. // Note that setValue and getValue only do *aligned* writes and reads! - +// Note that ccall uses JS types as for defining types, while setValue and +// getValue need LLVM types ('i8', 'i32') - this is a lower-level operation function setValue(ptr, value, type, noSafe) { type = type || 'i8'; if (type[type.length-1] === '*') type = 'i32'; // pointers are 32-bit @@ -398,7 +456,6 @@ function setValue(ptr, value, type, noSafe) { Module['setValue'] = setValue; // Parallel to setValue. - function getValue(ptr, type, noSafe) { type = type || 'i8'; if (type[type.length-1] === '*') type = 'i32'; // pointers are 32-bit @@ -803,6 +860,7 @@ function writeStringToMemory(string, buffer, dontAddNull) { {{{ makeSetValue('buffer', 'i', '0', 'i8') }}} } } +Module['writeStringToMemory'] = writeStringToMemory; var STRING_TABLE = []; |