diff options
-rw-r--r-- | src/analyzer.js | 61 | ||||
-rw-r--r-- | src/jsifier.js | 28 | ||||
-rw-r--r-- | src/library.js | 23 | ||||
-rw-r--r-- | src/parseTools.js | 12 | ||||
-rw-r--r-- | src/preamble.js | 62 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny.ll | 65 | ||||
-rw-r--r-- | tests/cases/longjmp_tiny.txt | 2 | ||||
-rwxr-xr-x | tests/runner.py | 176 | ||||
-rw-r--r-- | tools/eliminator/eliminator-test-output.js | 11 | ||||
-rw-r--r-- | tools/eliminator/eliminator-test.js | 13 | ||||
-rw-r--r-- | tools/eliminator/eliminator.coffee | 3 | ||||
-rw-r--r-- | tools/js-optimizer.js | 57 | ||||
-rw-r--r-- | tools/test-js-optimizer-output.js | 73 | ||||
-rw-r--r-- | tools/test-js-optimizer.js | 66 |
14 files changed, 582 insertions, 70 deletions
diff --git a/src/analyzer.js b/src/analyzer.js index 54e1ab43..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__. @@ -1744,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 b830fc7c..b3701112 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); } @@ -1030,10 +1046,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 e0a38817..0d756608 100644 --- a/src/library.js +++ b/src/library.js @@ -5278,19 +5278,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 5a92335e..d3cb7795 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1699,9 +1699,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 = []; diff --git a/tests/cases/longjmp_tiny.ll b/tests/cases/longjmp_tiny.ll new file mode 100644 index 00000000..0045847c --- /dev/null +++ b/tests/cases/longjmp_tiny.ll @@ -0,0 +1,65 @@ +; ModuleID = '/tmp/emscripten_temp/src.cpp.o' +target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32-n8:16:32-S128" +target triple = "i386-pc-linux-gnu" + +@_ZL3buf = internal global [20 x i16] zeroinitializer, align 2 +@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1 +@.str1 = private unnamed_addr constant [6 x i8] c"more\0A\00", align 1 + +define i32 @main() { + %retval = alloca i32, align 4 + store i32 0, i32* %retval + %call = call i32 @setjmp(i16* getelementptr inbounds ([20 x i16]* @_ZL3buf, i32 0, i32 0)) returns_twice, !dbg !20 + %tobool = icmp ne i32 %call, 0, !dbg !20 + br i1 %tobool, label %if.else, label %if.then, !dbg !20 + +if.then: ; preds = %entry + %call1 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([13 x i8]* @.str, i32 0, i32 0)), !dbg !22 + call void @longjmp(i16* getelementptr inbounds ([20 x i16]* @_ZL3buf, i32 0, i32 0), i32 10), !dbg !24 + br label %if.end, !dbg !25 + +if.else: ; preds = %entry + %call2 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([6 x i8]* @.str1, i32 0, i32 0)), !dbg !26 + br label %if.end + +if.end: ; preds = %if.else, %if.then + ret i32 0, !dbg !28 +} + +declare i32 @setjmp(i16*) returns_twice + +declare i32 @printf(i8*, ...) + +declare void @longjmp(i16*, i32) + +!llvm.dbg.cu = !{!0} + +!0 = metadata !{i32 786449, i32 0, i32 4, metadata !"/tmp/emscripten_temp/src.cpp", metadata !"/home/alon/Dev/emscripten", metadata !"clang version 3.1 (trunk 150936)", i1 true, i1 false, metadata !"", i32 0, metadata !1, metadata !1, metadata !3, metadata !12} ; [ DW_TAG_compile_unit ] +!1 = metadata !{metadata !2} +!2 = metadata !{i32 0} +!3 = metadata !{metadata !4} +!4 = metadata !{metadata !5} +!5 = metadata !{i32 786478, i32 0, metadata !6, metadata !"main", metadata !"main", metadata !"", metadata !6, i32 7, metadata !7, i1 false, i1 true, i32 0, i32 0, null, i32 256, i1 false, i32 ()* @main, null, null, metadata !10} ; [ DW_TAG_subprogram ] +!6 = metadata !{i32 786473, metadata !"/tmp/emscripten_temp/src.cpp", metadata !"/home/alon/Dev/emscripten", null} ; [ DW_TAG_file_type ] +!7 = metadata !{i32 786453, i32 0, metadata !"", i32 0, i32 0, i64 0, i64 0, i64 0, i32 0, null, metadata !8, i32 0, i32 0} ; [ DW_TAG_subroutine_type ] +!8 = metadata !{metadata !9} +!9 = metadata !{i32 786468, null, metadata !"int", null, i32 0, i64 32, i64 32, i64 0, i32 0, i32 5} ; [ DW_TAG_base_type ] +!10 = metadata !{metadata !11} +!11 = metadata !{i32 786468} ; [ DW_TAG_base_type ] +!12 = metadata !{metadata !13} +!13 = metadata !{metadata !14} +!14 = metadata !{i32 786484, i32 0, null, metadata !"buf", metadata !"buf", metadata !"_ZL3buf", metadata !6, i32 5, metadata !15, i32 1, i32 1, [20 x i16]* @_ZL3buf} ; [ DW_TAG_variable ] +!15 = metadata !{i32 786454, null, metadata !"jmp_buf", metadata !6, i32 279, i64 0, i64 0, i64 0, i32 0, metadata !16} ; [ DW_TAG_typedef ] +!16 = metadata !{i32 786433, null, metadata !"", null, i32 0, i64 320, i64 16, i32 0, i32 0, metadata !17, metadata !18, i32 0, i32 0} ; [ DW_TAG_array_type ] +!17 = metadata !{i32 786468, null, metadata !"unsigned short", null, i32 0, i64 16, i64 16, i64 0, i32 0, i32 7} ; [ DW_TAG_base_type ] +!18 = metadata !{metadata !19} +!19 = metadata !{i32 786465, i64 0, i64 19} ; [ DW_TAG_subrange_type ] +!20 = metadata !{i32 8, i32 18, metadata !21, null} +!21 = metadata !{i32 786443, metadata !5, i32 7, i32 22, metadata !6, i32 0} ; [ DW_TAG_lexical_block ] +!22 = metadata !{i32 9, i32 15, metadata !23, null} +!23 = metadata !{i32 786443, metadata !21, i32 8, i32 31, metadata !6, i32 1} ; [ DW_TAG_lexical_block ] +!24 = metadata !{i32 10, i32 15, metadata !23, null} +!25 = metadata !{i32 11, i32 13, metadata !23, null} +!26 = metadata !{i32 12, i32 15, metadata !27, null} +!27 = metadata !{i32 786443, metadata !21, i32 11, i32 20, metadata !6, i32 2} ; [ DW_TAG_lexical_block ] +!28 = metadata !{i32 14, i32 13, metadata !21, null} diff --git a/tests/cases/longjmp_tiny.txt b/tests/cases/longjmp_tiny.txt new file mode 100644 index 00000000..8a0aa386 --- /dev/null +++ b/tests/cases/longjmp_tiny.txt @@ -0,0 +1,2 @@ +hello world +more diff --git a/tests/runner.py b/tests/runner.py index 3d27d2c2..4db17431 100755 --- a/tests/runner.py +++ b/tests/runner.py @@ -678,6 +678,72 @@ if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv): self.do_run(src, '*1329409676000000,1329412005509675,3663280683,309527*\n') + def test_i64_cmp(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('full i64 stuff only in ta2') + + src = r''' + #include <stdio.h> + + typedef long long int64; + + bool compare(int64 val) { + return val == -12; + } + + bool compare2(int64 val) { + return val < -12; + } + + int main(int argc, char * argv[]) { + printf("*%d,%d,%d,%d,%d,%d*\n", argc, compare(argc-1-12), compare(1000+argc), compare2(argc-1-10), compare2(argc-1-14), compare2(argc+1000)); + return 0; + } + ''' + + self.do_run(src, '*1,1,0,0,1,0*\n') + + def test_i64_double(self): + if Settings.USE_TYPED_ARRAYS != 2: return self.skip('full i64 stuff only in ta2') + src = r''' + #include <stdio.h> + + typedef long long int64; + #define JSDOUBLE_HI32_SIGNBIT 0x80000000 + + bool JSDOUBLE_IS_NEGZERO(double d) + { + union { + struct { + unsigned int lo, hi; + } s; + double d; + } x; + if (d != 0) + return false; + x.d = d; + return (x.s.hi & JSDOUBLE_HI32_SIGNBIT) != 0; + } + + bool JSINT64_IS_NEGZERO(int64 l) + { + union { + int64 i; + double d; + } x; + if (l != 0) + return false; + x.i = l; + return x.d == -0; + } + + int main(int argc, char * argv[]) { + printf("*%d,%d,%d,%d*\n", JSDOUBLE_IS_NEGZERO(0), JSDOUBLE_IS_NEGZERO(-0), JSDOUBLE_IS_NEGZERO(-1), JSDOUBLE_IS_NEGZERO(+1)); + printf("*%d,%d,%d,%d*\n", JSINT64_IS_NEGZERO(0), JSINT64_IS_NEGZERO(-0), JSINT64_IS_NEGZERO(-1), JSINT64_IS_NEGZERO(+1)); + return 0; + } + ''' + self.do_run(src, '*0,0,0,0*\n*1,1,0,0*\n') # same as gcc + def test_unaligned(self): if Settings.QUANTUM_SIZE == 1: return self.skip('No meaning to unaligned addresses in q1') @@ -1344,6 +1410,41 @@ if 'benchmark' not in str(sys.argv) and 'sanity' not in str(sys.argv): ''' self.do_run(src, 'Assertion failed: 1 == false') + def test_longjmp(self): + src = r''' + #include <stdio.h> + #include <setjmp.h> + + static jmp_buf buf; + + void second(void) { + printf("second\n"); // prints + longjmp(buf,1); // jumps back to where setjmp was called - making setjmp now return 1 + } + + void first(void) { + second(); + printf("first\n"); // does not print + } + + int main() { + int x = 0; + if ( ! setjmp(buf) ) { + x++; + first(); // when executed, setjmp returns 0 + } else { // when longjmp jumps back, setjmp returns 1 + printf("main: %d\n", x); // prints + } + + return 0; + } + ''' + # gcc -O0 and -O2 differ in what they do with the saved state of local vars - and we match that + if self.emcc_args is None or ('-O1' not in self.emcc_args and '-O2' not in self.emcc_args): + self.do_run(src, 'second\nmain: 1\n') + else: + self.do_run(src, 'second\nmain: 0\n') + def test_exceptions(self): if Settings.QUANTUM_SIZE == 1: return self.skip("we don't support libcxx in q1") @@ -4859,6 +4960,69 @@ Block 0: ''', post_build=post1) ### Integration tests + def test_ccall(self): + if self.emcc_args is not None and '-O2' in self.emcc_args: + self.emcc_args += ['--closure', '1'] # Use closure here, to test we export things right + + src = r''' + #include <stdio.h> + + // Optimizations might wipe out our functions without this + #define KEEPALIVE __attribute__((used)) + + extern "C" { + int KEEPALIVE get_int() { return 5; } + float KEEPALIVE get_float() { return 3.14; } + char * KEEPALIVE get_string() { return "hello world"; } + void KEEPALIVE print_int(int x) { printf("%d\n", x); } + void KEEPALIVE print_float(float x) { printf("%.2f\n", x); } + void KEEPALIVE print_string(char *x) { printf("%s\n", x); } + int KEEPALIVE multi(int x, float y, int z, char *str) { puts(str); return (x+y)*z; } + int * KEEPALIVE pointer(int *in) { printf("%d\n", *in); static int ret = 21; return &ret; } + } + + int main(int argc, char **argv) { + // keep them alive + if (argc == 10) return get_int(); + if (argc == 11) return get_float(); + if (argc == 12) return get_string()[0]; + if (argc == 13) print_int(argv[0][0]); + if (argc == 14) print_float(argv[0][0]); + if (argc == 15) print_string(argv[0]); + if (argc == 16) pointer((int*)argv[0]); + if (argc % 17 == 12) return multi(argc, float(argc)/2, argc+1, argv[0]); + return 0; + } + ''' + + post = ''' +def process(filename): + src = \'\'\' + var Module = { + 'postRun': function() { + print('*'); + var ret; + ret = ccall('get_int', 'number'); print([typeof ret, ret]); + ret = ccall('get_float', 'number'); print([typeof ret, ret.toFixed(2)]); + ret = ccall('get_string', 'string'); print([typeof ret, ret]); + ret = ccall('print_int', null, ['number'], [12]); print(typeof ret); + ret = ccall('print_float', null, ['number'], [14.56]); print(typeof ret); + ret = ccall('print_string', null, ['string'], ["cheez"]); print(typeof ret); + ret = ccall('multi', 'number', ['number', 'number', 'number', 'string'], [2, 1.4, 3, 'more']); print([typeof ret, ret]); + var p = ccall('malloc', 'pointer', ['number'], [4]); + setValue(p, 650, 'i32'); + ret = ccall('pointer', 'pointer', ['pointer'], [p]); print([typeof ret, getValue(ret, 'i32')]); + print('*'); + } + }; + \'\'\' + open(filename, 'r').read() + open(filename, 'w').write(src) +''' + + Settings.EXPORTED_FUNCTIONS = ['_get_int', '_get_float', '_get_string', '_print_int', '_print_float', '_print_string', '_multi', '_pointer', '_malloc'] + + self.do_run(src, '*\nnumber,5\nnumber,3.14\nstring,hello world\n12\nundefined\n14.56\nundefined\ncheez\nundefined\nmore\nnumber,10\n650\nnumber,21\n*\n', post_build=post) + def test_scriptaclass(self): header_filename = os.path.join(self.get_dir(), 'header.h') header = ''' @@ -5524,16 +5688,21 @@ def process(filename): def test_exit_status(self): Settings.CATCH_EXIT_CODE = 1 - src = ''' + src = r''' #include <stdio.h> #include <stdlib.h> + static void cleanup() { + printf("cleanup\n"); + } + int main() { - printf("hello, world!\\n"); + atexit(cleanup); // this atexit should still be called + printf("hello, world!\n"); exit(118); // Unusual exit status to make sure it's working! } ''' - self.do_run(src, 'hello, world!\nExit Status: 118') + self.do_run(src, 'hello, world!\ncleanup\nExit Status: 118') # Generate tests for everything @@ -5992,6 +6161,7 @@ elif 'benchmark' in str(sys.argv): pass finally: os.chdir(d) + fingerprint.append('llvm: ' + LLVM_ROOT) print 'Running Emscripten benchmarks... [ %s ]' % ' | '.join(fingerprint) sys.argv = filter(lambda x: x != 'benchmark', sys.argv) diff --git a/tools/eliminator/eliminator-test-output.js b/tools/eliminator/eliminator-test-output.js index d914a5da..aac21e87 100644 --- a/tools/eliminator/eliminator-test-output.js +++ b/tools/eliminator/eliminator-test-output.js @@ -117,4 +117,13 @@ function f3($s, $tree, $k) { } HEAP32[($s + 2908 + ($storemerge_in << 2) | 0) >> 2] = $0; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["f", "g", "h", "py", "r", "t", "f2", "f3"] +function llvm3_1() { + while (check()) { + if ($curri_01 % $zj_0 == 0) { + break; + } + var $j_0 = $aj_0 + 1; + run($j_0 / 2); + } +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1"] diff --git a/tools/eliminator/eliminator-test.js b/tools/eliminator/eliminator-test.js index 35bed0bb..55f74d67 100644 --- a/tools/eliminator/eliminator-test.js +++ b/tools/eliminator/eliminator-test.js @@ -130,5 +130,14 @@ function f3($s, $tree, $k) { } HEAP32[($s + 2908 + ($storemerge_in << 2) | 0) >> 2] = $0; } -// EMSCRIPTEN_GENERATED_FUNCTIONS: ["f", "g", "h", "py", "r", "t", "f2", "f3"] - +function llvm3_1() { + while (check()) { + var $inc = $aj_0 + 1; + if ($curri_01 % $zj_0 == 0) { + break; + } + var $j_0 = $inc; + run($j_0 / 2); + } +} +// EMSCRIPTEN_GENERATED_FUNCTIONS: ["f", "g", "h", "py", "r", "t", "f2", "f3", "llvm3_1"] diff --git a/tools/eliminator/eliminator.coffee b/tools/eliminator/eliminator.coffee index b78a5a7e..ba91aa89 100644 --- a/tools/eliminator/eliminator.coffee +++ b/tools/eliminator/eliminator.coffee @@ -49,9 +49,6 @@ NODES_WITHOUT_SIDE_EFFECTS = # Nodes which may break control flow. Moving a variable beyond them may have # side effects. CONTROL_FLOW_NODES = - return: true - break: true - continue: true new: true throw: true call: true diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d84c664a..a0595b88 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -239,7 +239,7 @@ function emptyNode() { // Dump the AST. Useful for debugging. For example, // node tools/js-optimizer.js ABSOLUTE_PATH_TO_FILE dumpAst function dumpAst(ast) { - printErr(JSON.stringify(ast)); + printErr(JSON.stringify(ast, null, ' ')); } function dumpSrc(ast) { @@ -801,18 +801,24 @@ function vacuum(ast) { if (node[0] == 'block' && (!node[1] || (typeof node[1] != 'object') || node[1].length == 0 || (node[1].length == 1 && isEmpty(node[1])))) return true; return false; } - function simplifyList(node, i) { + function simplifyList(node, si) { var changed = false; - var pre = node[i].length; - node[i] = node[i].filter(function(node) { return !isEmpty(node) }); - if (node[i].length < pre) changed = true; - // Also, seek blocks with single items we can simplify - node[i] = node[i].map(function(subNode) { - if (subNode[0] == 'block' && typeof subNode[1] == 'object' && subNode[1].length == 1 && subNode[1][0][0] == 'if') { - return subNode[1][0]; + // Merge block items into this list, thus removing unneeded |{ .. }|'s + var statements = node[si]; + var i = 0; + while (i < statements.length) { + var subNode = statements[i]; + if (subNode[0] == 'block') { + statements.splice.apply(statements, [i, 1].concat(subNode[1] || [])); + changed = true; + } else { + i++; } - return subNode; - }); + } + // Remove empty items + var pre = node[si].length; + node[si] = node[si].filter(function(node) { return !isEmpty(node) }); + if (node[si].length < pre) changed = true; if (changed) { return node; } @@ -1002,6 +1008,35 @@ function hoistMultiples(ast) { }); vacuum(ast); + + // Afterpass: Reduce + // if (..) { .. break|continue } else { .. } + // to + // if (..) { .. break|continue } .. + traverseGenerated(ast, function(container, type) { + var statements = getStatements(container); + if (!statements) return; + for (var i = 0; i < statements.length; i++) { + var node = statements[i]; + if (node[0] == 'if' && node[2][0] == 'block' && node[3] && node[3][0] == 'block') { + var stat1 = node[2][1], stat2 = node[3][1]; + // If break|continue in the latter and not the former, reverse them + if (!(stat1[stat1.length-1][0] in LOOP_FLOW) && (stat2[stat2.length-1][0] in LOOP_FLOW)) { + var temp = node[3]; + node[3] = node[2]; + node[2] = temp; + node[1] = ['unary-prefix', '!', node[1]]; + simplifyNotComps(node[1]); // bake the ! into the expression + stat1 = node[2][1]; + stat2 = node[3][1]; + } + if (stat1[stat1.length-1][0] in LOOP_FLOW) { + statements.splice.apply(statements, [i+1, 0].concat(stat2)); + node[3] = null; + } + } + } + }); } // Simplifies loops diff --git a/tools/test-js-optimizer-output.js b/tools/test-js-optimizer-output.js index 17c06a95..ca76cae1 100644 --- a/tools/test-js-optimizer-output.js +++ b/tools/test-js-optimizer-output.js @@ -68,9 +68,7 @@ function loopy() { something(); } while (0); next(); - { - something(); - } + something(); } function ignoreLoopy() { b$for_cond$4 : while (1) { @@ -141,12 +139,11 @@ function hoisting() { } pause(7); while (1) { - if ($i < $N) { - somethingElse(); - } else { + if ($i >= $N) { __label__ = 3; break; } + somethingElse(); if ($i < $N) { somethingElse(); } @@ -166,15 +163,43 @@ function hoisting() { } __label__ = 39; break; - } else { - __label__ = 38; } + __label__ = 38; } while (0); if (__label__ == 38) { var $79 = $_pr6; } pause(9); var $cmp70 = ($call69 | 0) != 0; + pause(10); + while (check()) { + if ($i < $N) { + callOther(); + break; + } + somethingElse(); + if ($i1 < $N) { + callOther(); + continue; + } + somethingElse(); + if ($i2 >= $N) { + somethingElse(); + break; + } + callOther(); + if ($i3 >= $N) { + somethingElse(); + continue; + } + callOther(); + if ($i4 < $N) { + callOther(); + break; + } + somethingElse(); + continue; + } } function innerShouldAlsoBeHoisted() { function hoisting() { @@ -208,9 +233,7 @@ function sleep() { } function demangle($cmp) { do { - if ($cmp) { - __label__ = 3; - } else { + if (!$cmp) { if (something()) { __label__ = 3; break; @@ -218,6 +241,7 @@ function demangle($cmp) { more(); break; } + __label__ = 3; } while (0); if (__label__ == 3) { final(); @@ -259,25 +283,21 @@ function moreLabels() { } if ($cmp1) { break; - } else { - inc(); } + inc(); } pause(999); |