| "use strict"; |
| |
| // factorial calls aux that tail-calls back into factorial. |
| // It is not OK to turn that tail call into a jump back to the top of the function, because the call to aux is not a tail call. |
| function factorial(n) { |
| function aux(n) { |
| if (n == 1) |
| return 1; |
| return factorial(n - 1); |
| } |
| |
| return n * aux(n); |
| } |
| |
| // Same, but this time with an irrelevant tail call in factorial. |
| // This exercises a different code path because the recursive tail call optimization aborts early if the op_enter block is not split, and it is split by the detection of a tail call. |
| function factorial2(n) { |
| function aux2(n) { |
| if (n == 1) |
| return 1; |
| return factorial2(n - 1); |
| } |
| function id(n) { |
| return n; |
| } |
| |
| return id(n * aux2(n)); |
| } |
| |
| // This time, aux is tail-calling itself: the optimization is valid but must jump to the start of aux3, not of factorial3. |
| function factorial3(n) { |
| function aux3(n, acc) { |
| if (n == 1) |
| return acc; |
| return aux3 (n-1, n*acc); |
| } |
| |
| return n * aux3(n-1, 1); |
| } |
| |
| // In this case, it is valid to jump all the way to the top of factorial4, because all calls are tail calls. |
| function factorial4(n, acc) { |
| function aux4(n, acc) { |
| if (n == 1) |
| return acc; |
| return factorial4(n-1, n*acc); |
| } |
| |
| if (acc) |
| return aux4(n, acc); |
| return aux4(n, 1); |
| } |
| |
| // This function is used to test that recursing with a different number of arguments doesn't mess up with the stack. |
| // The first two tail calls should be optimized, but not the last one (no place on the stack for the third argument). |
| function foo(a, b) { |
| if (a == 0) |
| return 42; |
| if (a == 1) |
| return foo(a - 1); |
| if (a == 2) |
| return foo(b - 1, a); |
| return foo (b - 1, a, 43); |
| } |
| |
| // Same deal as foo, just with an inlining thrown into the mix. |
| // Even the first tail call should not be optimized in this case, because some code in the compiler may constant-fold the number of arguments in that case. |
| function bar(x, y) { |
| function auxBar(a, b) { |
| if (a == 0) |
| return 42; |
| if (a == 1) |
| return auxBar(a - 1); |
| if (a == 2) |
| return auxBar(b - 1, a); |
| return auxBar(b - 1, a, 43); |
| } |
| |
| return auxBar(x, y); |
| } |
| |
| function test(result, expected, name) { |
| if (result != expected) |
| throw "Wrong result for " + name + ": " + result + " instead of " + expected; |
| } |
| |
| for (var i = 0; i < 10000; ++i) { |
| test(factorial(20), 2432902008176640000, "factorial"); |
| test(factorial2(20), 2432902008176640000, "factorial2"); |
| test(factorial3(20), 2432902008176640000, "factorial3"); |
| test(factorial4(20), 2432902008176640000, "factorial4"); |
| test(foo(10, 10), 42, "foo"); |
| test(bar(10, 10), 42, "bar"); |
| } |