diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/preamble.js | 188 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 9 | ||||
-rw-r--r-- | src/relooper/fuzzer.py | 6 | ||||
-rw-r--r-- | src/relooper/test.cpp | 31 | ||||
-rw-r--r-- | src/relooper/test.txt | 153 |
5 files changed, 319 insertions, 68 deletions
diff --git a/src/preamble.js b/src/preamble.js index 2aec94c6..f46119c7 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -310,28 +310,6 @@ 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. Closure can also do so. To avoid that, add your function to -// the exports using something like -// -// -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]' -// -// @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', 'string' or 'array' (use 'number' for any C pointer, and -// 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit). -// @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, -// except that 'array' is not possible (there is no way for us to know the length of the array) -// @param args An array of the arguments to the function, as native JS values (as in returnType) -// 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 (as in returnType) -function ccall(ident, returnType, argTypes, args) { - return ccallFunc(getCFunc(ident), returnType, argTypes, args); -} -Module["ccall"] = ccall; - // Returns the C function with a specified identifier (for C++, you need to do manual name mangling) function getCFunc(ident) { try { @@ -343,53 +321,141 @@ function getCFunc(ident) { return func; } -// Internal function that does a C call using a function, not an identifier -function ccallFunc(func, returnType, argTypes, args) { +var cwrap, ccall; +(function(){ var stack = 0; - function toC(value, type) { - if (type == 'string') { - if (value === null || value === undefined || value === 0) return 0; // null string - value = intArrayFromString(value); - type = 'array'; - } - if (type == 'array') { - if (!stack) stack = Runtime.stackSave(); - var ret = Runtime.stackAlloc(value.length); - writeArrayToMemory(value, ret); + var JSfuncs = { + 'stackSave' : function() { + stack = Runtime.stackSave(); + }, + 'stackRestore' : function() { + Runtime.stackRestore(stack); + }, + // type conversion from js to c + 'arrayToC' : function(arr) { + var ret = Runtime.stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + 'stringToC' : function(str) { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { // null string + ret = Runtime.stackAlloc(str.length + 1); // +1 for the trailing '\0' + writeStringToMemory(str, ret); + } return ret; } - return value; - } - function fromC(value, type) { - if (type == 'string') { - return Pointer_stringify(value); + }; + // For fast lookup of conversion functions + var toC = {'string' : JSfuncs['stringToC'], 'array' : JSfuncs['arrayToC']}; + + // 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. Closure can also do so. To avoid that, add your function to + // the exports using something like + // + // -s EXPORTED_FUNCTIONS='["_main", "_myfunc"]' + // + // @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', 'string' or 'array' (use 'number' for any C pointer, and + // 'array' for JavaScript arrays and typed arrays; note that arrays are 8-bit). + // @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, + // except that 'array' is not possible (there is no way for us to know the length of the array) + // @param args An array of the arguments to the function, as native JS values (as in returnType) + // 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 (as in returnType) + ccall = function ccallFunc(ident, returnType, argTypes, args) { + var func = getCFunc(ident); + var cArgs = []; +#if ASSERTIONS + assert(returnType !== 'array', 'Return type should not be "array".'); +#endif + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = Runtime.stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } } - assert(type != 'array'); - return value; + var ret = func.apply(null, cArgs); + if (returnType === 'string') ret = Pointer_stringify(ret); + if (stack !== 0) JSfuncs['stackRestore'](); + return ret; } - var i = 0; - var cArgs = args ? args.map(function(arg) { - return toC(arg, argTypes[i++]); - }) : []; - var ret = fromC(func.apply(null, cArgs), returnType); - if (stack) Runtime.stackRestore(stack); - return ret; -} -// Returns a native JS wrapper for a C function. This is similar to ccall, but -// returns a function you can call repeatedly in a normal way. For example: -// -// var my_function = cwrap('my_c_function', 'number', ['number', 'number']); -// alert(my_function(5, 22)); -// alert(my_function(99, 12)); -// -function cwrap(ident, returnType, argTypes) { - var func = getCFunc(ident); - return function() { - return ccallFunc(func, returnType, argTypes, Array.prototype.slice.call(arguments)); + var sourceRegex = /^function \((.*)\)\s*{\s*([^]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/; + function parseJSFunc(jsfunc) { + // Match the body and the return value of a javascript function source + var parsed = jsfunc.toString().match(sourceRegex).slice(1); + return {arguments : parsed[0], body : parsed[1], returnValue: parsed[2]} } -} + var JSsource = {}; + for (var fun in JSfuncs) { + if (JSfuncs.hasOwnProperty(fun)) { + // Elements of toCsource are arrays of three items: + // the code, and the return value + JSsource[fun] = parseJSFunc(JSfuncs[fun]); + } + } + // Returns a native JS wrapper for a C function. This is similar to ccall, but + // returns a function you can call repeatedly in a normal way. For example: + // + // var my_function = cwrap('my_c_function', 'number', ['number', 'number']); + // alert(my_function(5, 22)); + // alert(my_function(99, 12)); + // + cwrap = function cwrap(ident, returnType, argTypes) { + var cfunc = getCFunc(ident); + // When the function takes numbers and returns a number, we can just return + // the original function + var numericArgs = argTypes.every(function(type){ return type === 'number'}); + var numericRet = (returnType !== 'string'); + if ( numericRet && numericArgs) { + return cfunc; + } + // Creation of the arguments list (["$1","$2",...,"$nargs"]) + var argNames = argTypes.map(function(x,i){return '$'+i}); + var funcstr = "(function(" + argNames.join(',') + ") {"; + var nargs = argTypes.length; + if (!numericArgs) { + // Generate the code needed to convert the arguments from javascript + // values to pointers + funcstr += JSsource['stackSave'].body + ';'; + for (var i = 0; i < nargs; i++) { + var arg = argNames[i], type = argTypes[i]; + if (type === 'number') continue; + var convertCode = JSsource[type + 'ToC']; // [code, return] + funcstr += 'var ' + convertCode.arguments + ' = ' + arg + ';'; + funcstr += convertCode.body + ';'; + funcstr += arg + '=' + convertCode.returnValue + ';'; + } + } + + // When the code is compressed, the name of cfunc is not literally 'cfunc' anymore + var cfuncname = parseJSFunc(function(){return cfunc}).returnValue; + // Call the function + funcstr += 'var ret = ' + cfuncname + '(' + argNames.join(',') + ');'; + if (!numericRet) { // Return type can only by 'string' or 'number' + // Convert the result to a string + var strgfy = parseJSFunc(function(){return Pointer_stringify}).returnValue; + funcstr += 'ret = ' + strgfy + '(ret);'; + } + if (!numericArgs) { + // If we had a stack, restore it + funcstr += JSsource['stackRestore'].body + ';'; + } + funcstr += 'return ret})'; + return eval(funcstr); + }; +})(); Module["cwrap"] = cwrap; +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 diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index 568dd381..9e469ec4 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -1097,7 +1097,7 @@ void Relooper::Calculate(Block *Entry) { // Remove unneeded breaks and continues. // A flow operation is trivially unneeded if the shape we naturally get to by normal code // execution is the same as the flow forces us to. - void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL, LoopShape *LastLoop=NULL) { + void RemoveUnneededFlows(Shape *Root, Shape *Natural=NULL, LoopShape *LastLoop=NULL, unsigned Depth=0) { BlockSet NaturalBlocks; FollowNaturalFlow(Natural, NaturalBlocks); Shape *Next = Root; @@ -1108,7 +1108,7 @@ void Relooper::Calculate(Block *Entry) { if (Simple->Inner->BranchVar) LastLoop = NULL; // a switch clears out the loop (TODO: only for breaks, not continue) if (Simple->Next) { - if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2) { + if (!Simple->Inner->BranchVar && Simple->Inner->ProcessedBranchesOut.size() == 2 && Depth < 20) { // If there is a next block, we already know at Simple creation time to make direct branches, // and we can do nothing more in general. But, we try to optimize the case of a break and // a direct: This would normally be if (break?) { break; } .. but if we @@ -1144,6 +1144,7 @@ void Relooper::Calculate(Block *Entry) { } } } + Depth++; // this optimization increases depth, for us and all our next chain (i.e., until this call returns) } Next = Simple->Next; } else { @@ -1168,11 +1169,11 @@ void Relooper::Calculate(Block *Entry) { } }, { for (IdShapeMap::iterator iter = Multiple->InnerMap.begin(); iter != Multiple->InnerMap.end(); iter++) { - RemoveUnneededFlows(iter->second, Multiple->Next, Multiple->Breaks ? NULL : LastLoop); + RemoveUnneededFlows(iter->second, Multiple->Next, Multiple->Breaks ? NULL : LastLoop, Depth+1); } Next = Multiple->Next; }, { - RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop); + RemoveUnneededFlows(Loop->Inner, Loop->Inner, Loop, Depth+1); Next = Loop->Next; }); } diff --git a/src/relooper/fuzzer.py b/src/relooper/fuzzer.py index 18db997e..313f4d5e 100644 --- a/src/relooper/fuzzer.py +++ b/src/relooper/fuzzer.py @@ -3,7 +3,7 @@ import random, subprocess, difflib while True: # Random decisions - num = random.randint(2, 250) + num = random.randint(2, 500) density = random.random() * random.random() decisions = [random.randint(1, num*20) for x in range(num*3)] branches = [0]*num @@ -123,14 +123,14 @@ int main() { open('fuzz.slow.js', 'w').write(slow) open('fuzz.cpp', 'w').write(fast) print '_' - slow_out = subprocess.Popen(['mozjs', '-m', '-n', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] + slow_out = subprocess.Popen(['mozjs', 'fuzz.slow.js'], stdout=subprocess.PIPE).communicate()[0] print '.' subprocess.call(['g++', 'fuzz.cpp', 'Relooper.o', '-o', 'fuzz', '-g']) print '*' subprocess.call(['./fuzz'], stdout=open('fuzz.fast.js', 'w')) print '-' - fast_out = subprocess.Popen(['mozjs', '-m', '-n', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] + fast_out = subprocess.Popen(['mozjs', 'fuzz.fast.js'], stdout=subprocess.PIPE).communicate()[0] print if slow_out != fast_out: diff --git a/src/relooper/test.cpp b/src/relooper/test.cpp index 9f3ddceb..f319757b 100644 --- a/src/relooper/test.cpp +++ b/src/relooper/test.cpp @@ -1,4 +1,6 @@ +#include <vector> + #include "Relooper.h" int main() { @@ -435,5 +437,34 @@ int main() { puts(r.GetOutputBuffer()); } + + if (1) { + Relooper::MakeOutputBuffer(10); + + printf("\n\n-- lots of exits to an unwind block, possible nesting --\n\n"); + + const int DEPTH = 40; + + std::vector<Block*> blocks; + for (int i = 0; i < DEPTH; i++) blocks.push_back(new Block("// block\n", NULL)); + Block *last = new Block("// last\nreturn;\n", NULL); + Block *UW = new Block("// UW\nresumeException();\n\n", NULL); + + for (int i = 0; i < DEPTH; i++) { + Block *b = blocks[i]; + b->AddBranchTo(i+1 < DEPTH ? blocks[i+1] : last, "check()", NULL); + b->AddBranchTo(UW, NULL, NULL); + } + + Relooper r; + for (int i = 0; i < DEPTH; i++) r.AddBlock(blocks[i]); + r.AddBlock(last); + r.AddBlock(UW); + + r.Calculate(blocks[0]); + r.Render(); + + puts(r.GetOutputBuffer()); + } } diff --git a/src/relooper/test.txt b/src/relooper/test.txt index d53aeeb1..2ae331c0 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -417,3 +417,156 @@ } // block D + + +-- lots of exits to an unwind block, possible nesting -- + + // block + do { + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (check()) { + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // block + if (!(check())) { + break; + } + // last + return; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } while(0); + // UW + resumeException(); + + |