From 8e34f8bf273b42e03c8a8ad2d1d8d2534812ab62 Mon Sep 17 00:00:00 2001 From: Ian Farmer Date: Tue, 6 Apr 2010 04:03:06 +0000 Subject: [PATCH] Implement tail call optimization to avoid stack overflow. --- src/core/org/luaj/vm2/LuaClosure.java | 25 ++------ src/core/org/luaj/vm2/TailcallVarargs.java | 71 ++++++++++++++++++++++ test/lua/tailcalls.lua | 43 +++++++++++++ 3 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 src/core/org/luaj/vm2/TailcallVarargs.java diff --git a/src/core/org/luaj/vm2/LuaClosure.java b/src/core/org/luaj/vm2/LuaClosure.java index d96e77ee..347b44ce 100644 --- a/src/core/org/luaj/vm2/LuaClosure.java +++ b/src/core/org/luaj/vm2/LuaClosure.java @@ -302,28 +302,17 @@ public class LuaClosure extends LuaFunction { } case Lua.OP_TAILCALL: /* A B C return R(A)(R(A+1), ... ,R(A+B-1)) */ - switch ( i & (Lua.MASK_B | Lua.MASK_C) ) { - case (1<>>23; - c = (i>>14)&0x1ff; v = b>0? varargsOf(stack,a+1,b-1): // exact arg count varargsOf(stack, a+1, top-v.narg()-(a+1), v); // from prev top - switch ( c ) { - case 1: stack[a].invoke(v); return NONE; - case 2: return stack[a].invoke(v).arg1(); - default: return stack[a].invoke(v); - } + return new TailcallVarargs( stack[a], v ); } case Lua.OP_RETURN: /* A B return R(A), ... ,R(A+B-2) (see note) */ @@ -461,6 +450,4 @@ public class LuaClosure extends LuaFunction { protected void setUpvalue(int i, LuaValue v) { upValues[i].setValue(v); } - - } diff --git a/src/core/org/luaj/vm2/TailcallVarargs.java b/src/core/org/luaj/vm2/TailcallVarargs.java new file mode 100644 index 00000000..d313916a --- /dev/null +++ b/src/core/org/luaj/vm2/TailcallVarargs.java @@ -0,0 +1,71 @@ +/******************************************************************************* +* Copyright (c) 2010 Luaj.org. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ +package org.luaj.vm2; + +public class TailcallVarargs extends Varargs { + + private LuaValue func; + private Varargs args; + private Varargs result; + + public TailcallVarargs(LuaValue f, Varargs args) { + this.func = f; + this.args = args; + } + + private void eval() { + TailcallVarargs nextcall = this; + do { + LuaValue func = nextcall.func; + Varargs args = nextcall.args; + nextcall = null; + Varargs r = func.invoke(args); + + if (r instanceof TailcallVarargs) + nextcall = (TailcallVarargs)r; + else + this.result = r; + + } while (nextcall != null); + } + + @Override + public LuaValue arg( int i ) { + if ( result == null ) + eval(); + return result.arg(i); + } + + @Override + public LuaValue arg1() { + if (result == null) + eval(); + return result.arg1(); + } + + @Override + public int narg() { + if (result == null) + eval(); + return result.narg(); + } +} \ No newline at end of file diff --git a/test/lua/tailcalls.lua b/test/lua/tailcalls.lua index 7a4cecd6..d5185288 100644 --- a/test/lua/tailcalls.lua +++ b/test/lua/tailcalls.lua @@ -98,3 +98,46 @@ print(factorial(5)) local result2 = f(-1234) print( result2 ) + +local function fib_bad(n) + local function helper(i, a, b) + if i >= n then + return a + else + -- not recognized by luac as a tailcall! + local result = helper(i + 1, b, a + b) + return result + end + end + return helper(1, 1, 1) +end + +local function fib_good(n) + local function helper(i, a, b) + if i >= n then + return a + else + -- must be a tail call! + return helper(i + 1, b, a + b) + end + end + return helper(1, 1, 1) +end + +print(pcall(fib_bad, 30)) +print(pcall(fib_bad, 25000)) +print(pcall(fib_good, 30)) +print(pcall(fib_good, 25000)) + +local function fib_all(n, i, a, b) + i = i or 1 + a = a or 1 + b = b or 1 + if i >= n then + return + else + return a, fib_all(n, i+1, b, a+b) + end +end + +print(fib_all(10))