aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/analyzer.js68
-rw-r--r--src/jsifier.js28
-rw-r--r--src/library.js27
-rw-r--r--src/parseTools.js38
-rw-r--r--src/preamble.js62
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 = [];