All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.weeaboo.lua2.vm.LuaInterpreter Maven / Gradle / Ivy

package nl.weeaboo.lua2.vm;

import static nl.weeaboo.lua2.internal.LuaArgsUtil.copyArgs;
import static nl.weeaboo.lua2.vm.LuaBoolean.FALSE;
import static nl.weeaboo.lua2.vm.LuaBoolean.TRUE;
import static nl.weeaboo.lua2.vm.LuaConstants.NONE;
import static nl.weeaboo.lua2.vm.LuaNil.NIL;
import static nl.weeaboo.lua2.vm.LuaValue.varargsOf;

import javax.annotation.Nullable;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.errorprone.annotations.CheckReturnValue;

import nl.weeaboo.lua2.LuaException;
import nl.weeaboo.lua2.LuaRunState;
import nl.weeaboo.lua2.stdlib.DebugLib;
import nl.weeaboo.lua2.stdlib.DebugTrace;
import nl.weeaboo.lua2.vm.StackFrame.Status;

final class LuaInterpreter {

    private static final Logger LOG = LoggerFactory.getLogger(LuaInterpreter.class);

    private LuaInterpreter() {
    }

    /**
     * @param thread The executing thread
     * @param callstackBase Don't touch stack frames with zero-based index < callstackBase.
     */
    public static Varargs resume(LuaThread thread, int callstackBase) {
        if (callstackBase < 0) {
            throw new IllegalArgumentException("callstackBase must be >= 0");
        }

        Varargs result = NONE;
        while (thread.isRunning() && thread.callstackSize() > callstackBase) {
            StackFrame sf = thread.callstack;
            if (sf == null) {
                throw new IllegalStateException("StackFrame on callstack is unexpectedly null");
            }
            if (sf.status == Status.FRESH) {
                startCall(thread, sf);
                sf.status = Status.RUNNING;
            }

            try {
                result = resume(thread, sf);
            } finally {
                if (sf.status == Status.FINISHED) {
                    finishCall(thread, sf, result);
                }
            }
        }

        return result;
    }

    private static Varargs resume(LuaThread thread, StackFrame sf) {
        if (sf.status != Status.RUNNING) {
            throw new LuaException("StackFrame isn't running: status=" + sf.status + ", stackFrame=" + sf);
        }

        sf.status = Status.RUNNING;

        Varargs result;

        final FrameState frameState = new FrameState();
        frameState.startRunning(thread, sf);
        try {
            result = frameState.run();
        } finally {
            frameState.finishRunning();
        }

        return result;
    }

    private static void startCall(LuaThread thread, StackFrame sf) {
        thread.preCall(sf);
    }

    private static void finishCall(LuaThread thread, StackFrame sf, Varargs retval) {
        // Pushes return values on parent's stack
        StackFrame parent = sf.parent;
        if (parent != null) {
            int a = sf.returnBase;
            int c = sf.returnCount + 1;

            if (c > 0) {
                while (--c > 0) {
                    parent.stack[a + c - 1] = retval.arg(c);
                }
                parent.top = a + retval.narg();
                parent.v = retval;
            } else {
                parent.top = a + retval.narg();
                parent.v = retval;
            }
        }

        if (thread.callstack == sf) {
            thread.popStackFrame();
        } else {
            LOG.error("Callstack was corrupted, finished={}, callstack={}",
                    sf, thread.callstack);
            sf.close();
        }
    }

    static void pushReturnValues(StackFrame sf, int a, int c) {
        pushReturnValues(sf, sf.v, a, c);
    }

    static void pushReturnValues(StackFrame sf, Varargs returnValues, int a, int c) {
        // Push return values on the stack
        if (c > 0) {
            while (--c > 0) {
                sf.stack[a + c - 1] = returnValues.arg(c);
            }
            sf.v = NONE;
        } else {
            sf.top = a + returnValues.narg();
            sf.v = returnValues;
        }
    }

    private static final class FrameState {

        private LuaRunState lrs;
        private LuaThread thread;
        private StackFrame stackFrame;

        private LuaClosure closure;
        private Prototype p;
        private int[] code;
        private LuaValue[] k;
        private UpValue[] upValues;

        private LuaValue[] stack;
        private UpValue[] openups;
        private Varargs varargs;

        private int top;
        private int pc;
        private Varargs v;

        public void startRunning(LuaThread thread, StackFrame sf) {
            this.lrs = LuaRunState.getCurrent();
            this.thread = thread;
            this.stackFrame = sf;

            closure = sf.func.checkclosure();
            p = closure.getPrototype();
            code = p.code;
            k = p.k;
            upValues = closure.getUpValues();

            stack = sf.stack;
            openups = sf.openups;
            varargs = sf.varargs;

            top = sf.top;
            pc = sf.pc;
            v = sf.v;
        }

        public Varargs run() {
            // Only check this flag once, and not between every instruction
            final boolean debugEnabled = lrs.isDebugEnabled();

            while (thread.isRunning()) {
                // Pull out instruction
                int i;
                try {
                    i = code[pc];
                } catch (ArrayIndexOutOfBoundsException e) {
                    throw new LuaException("Program Counter outside code range: " + pc + " for " + closure);
                }

                if (debugEnabled) {
                    lrs.onInstruction(pc);
                    DebugLib.debugBytecode(thread, pc, varargs, top);
                }

                pc++;

                // Process the opcode
                final int opcode = i & 0x3f;
                int a = ((i >> 6) & 0xff);
                switch (opcode) {
                case Lua.OP_MOVE:/* A B R(A):= R(B) */
                    stack[a] = stack[i >>> 23];
                    continue;
                case Lua.OP_LOADK:/* A Bx R(A):= Kst(Bx) */
                    stack[a] = k[i >>> 14];
                    continue;
                case Lua.OP_LOADBOOL:/* A B C R(A):= (Bool)B: if (C) pc++ */
                    stack[a] = (i >>> 23 != 0) ? TRUE : FALSE;
                    if ((i & (0x1ff << 14)) != 0) {
                        pc++; // Skip next instruction (if C)
                    }
                    continue;
                case Lua.OP_LOADNIL: { /* A B R(A):= ...:= R(B):= nil */
                    for (int b = i >>> 23; a <= b;) {
                        stack[a++] = NIL;
                    }
                    continue;
                }
                case Lua.OP_GETUPVAL: /* A B R(A):= UpValue[B] */
                    stack[a] = upValues[i >>> 23].getValue();
                    continue;
                case Lua.OP_GETGLOBAL: /* A Bx R(A):= Gbl[Kst(Bx)] */
                    stack[a] = closure.getfenv().get(k[i >>> 14]);
                    continue;
                case Lua.OP_GETTABLE: { /* A B C R(A):= R(B)[RK(C)] */
                    int c = (i >> 14) & 0x1ff;
                    stack[a] = stack[i >>> 23].get(c > 0xff ? k[c & 0x0ff] : stack[c]);
                    continue;
                }
                case Lua.OP_SETGLOBAL: /* A Bx Gbl[Kst(Bx)]:= R(A) */
                    closure.getfenv().set(k[i >>> 14], stack[a]);
                    continue;
                case Lua.OP_SETUPVAL: /* A B UpValue[B]:= R(A) */
                    upValues[i >>> 23].setValue(stack[a]);
                    continue;
                case Lua.OP_SETTABLE: { /* A B C R(A)[RK(B)]:= RK(C) */
                    int b = i >>> 23;
                    int c = (i >> 14) & 0x1ff;
                    stack[a].set(
                            b > 0xff ? k[b & 0x0ff] : stack[b],
                            c > 0xff ? k[c & 0x0ff] : stack[c]);
                    continue;
                }
                case Lua.OP_NEWTABLE: /* A B C R(A):= {} (size = B,C) */
                    stack[a] = new LuaTable(i >>> 23, (i >> 14) & 0x1ff);
                    continue;
                case Lua.OP_SELF:
                    opSelf(i, a);
                    continue;
                case Lua.OP_ADD:
                case Lua.OP_SUB:
                case Lua.OP_MUL:
                case Lua.OP_DIV:
                case Lua.OP_MOD:
                case Lua.OP_POW:
                    stack[a] = binaryArithmeticOp(i);
                    continue;
                case Lua.OP_UNM: /* A B R(A):= -R(B) */
                    stack[a] = stack[i >>> 23].neg();
                    continue;
                case Lua.OP_NOT: /* A B R(A):= not R(B) */
                    stack[a] = stack[i >>> 23].not();
                    continue;
                case Lua.OP_LEN: /* A B R(A):= length of R(B) */
                    stack[a] = stack[i >>> 23].len();
                    continue;
                case Lua.OP_CONCAT:
                    opConcat(i, a);
                    continue;
                case Lua.OP_JMP: /* sBx pc+=sBx */
                    pc += (i >>> 14) - 0x1ffff;
                    continue;
                case Lua.OP_EQ:
                case Lua.OP_LT:
                case Lua.OP_LE:
                    opCompare(i, a);
                    continue;
                case Lua.OP_TEST: /* A C if not (R(A) <=> C) then pc++ */
                    if (stack[a].toboolean() != ((i & (0x1ff << 14)) != 0)) {
                        ++pc;
                    }
                    continue;
                case Lua.OP_TESTSET:
                    opTestSet(i, a);
                    continue;
                case Lua.OP_CALL: {
                    Varargs result = opCall(i, a);
                    if (result != null) {
                        return result;
                    }
                    continue;
                }
                case Lua.OP_TAILCALL: {
                    Varargs result = opTailCall(i, a);
                    if (result != null) {
                        return result;
                    }
                    continue;
                }
                case Lua.OP_RETURN:
                    return opReturn(i, a);
                case Lua.OP_FORLOOP:
                    opForLoop(i, a);
                    continue;
                case Lua.OP_FORPREP:
                    opForPrep(i, a);
                    continue;
                case Lua.OP_TFORLOOP:
                    opTForLoop(i, a);
                    continue;
                case Lua.OP_SETLIST:
                    opSetList(i, a);
                    continue;
                case Lua.OP_CLOSE:
                    opClose(a);
                    continue;
                case Lua.OP_CLOSURE:
                    opClosure(i, a);
                    continue;
                case Lua.OP_VARARG:
                    opVararg(i, a);
                    continue;
                default:
                    throw new LuaException("Unsupported opcode: " + opcode);
                }
            }

            return NONE;
        }

        public void finishRunning() {
            if (thread.isDead() || thread.getStatus() == LuaThreadStatus.END_CALL) {
                stackFrame.status = Status.FINISHED;
            }

            stackFrame.top = top;
            stackFrame.pc = pc;
            stackFrame.v = v;
        }

        /**
         * A B C R(A), ... ,R(A+C-2):= R(A)(R(A+1), ... ,R(A+B-1))
         *
         * @return A non-null value if execution should terminate with the given result.
         */
        private @Nullable Varargs opCall(int i, int a) {
            int b = i >>> 23;
            int c = (i >> 14) & 0x1ff;
            if (b > 0) {
                v = varargsOf(stack, a + 1, b - 1); // exact arg count
            } else {
                v = varargsOf(stack, a + 1, top - v.narg() - (a + 1), v); // from prev top
            }

            LuaValue f = stack[a];
            if (f.isclosure()) {
                thread.pushPending(f.checkclosure(), v, a, c - 1);
                return NONE;
            }

            // Call immediately
            invoke(f, v);

            if (thread.getStatus() == LuaThreadStatus.SUSPENDED) {
                return v; // Yield
            }

            pushReturnValues(stackFrame, v, a, c);
            top = stackFrame.top;
            v = stackFrame.v;
            return null;
        }

        /**
         * A B C return R(A)(R(A+1), ... ,R(A+B-1))
         *
         * @return A non-null value if execution should terminate with the given result.
         */
        private @Nullable Varargs opTailCall(int i, int a) {
            int b = i >>> 23;
            if (b > 0) {
                v = copyArgs(stack, a + 1, b - 1); // Important: copies args
            } else {
                v = copyArgs(stack, a + 1, top - v.narg() - (a + 1), v); // Important: copies args
            }

            LuaValue f = stack[a];
            if (f.isclosure()) {
                String functionName = DebugTrace.getCalledFunctionName(thread);
                thread.postReturn(stackFrame);

                stackFrame.prepareTailcall(f.checkclosure(), v, functionName);
                top = stackFrame.top;
                pc = stackFrame.pc;
                v = stackFrame.v;

                startCall(thread, stackFrame);
                return NONE;
            }

            stackFrame.top = top;
            stackFrame.pc = pc;
            stackFrame.v = v;

            // Call hooks as if we returned from the current function, then jump into the tail-called function
            thread.postReturn(stackFrame);
            startCall(thread, stackFrame);

            // Hack to make recursive calls have the correct callstack size when I remove stackFrame later
            stackFrame.parentCount--;
            v = f.invoke(v);

            if (stackFrame != thread.callstack) {
                // Remove stackFrame from callstack
                stackFrame.close();
                StackFrame cur = thread.callstack;
                while (cur != null) {
                    if (cur.parent == stackFrame) {
                        cur.parent = stackFrame.parent;
                        break;
                    }
                    cur = cur.parent;
                }
                return NONE;
            }

            // Java function didn't do anything to the callstack, recover.
            stackFrame.parentCount++;

            pc = stackFrame.pc;

            if (thread.getStatus() == LuaThreadStatus.SUSPENDED) {
                return v; // Yield
            }

            int c = (i >> 14) & 0x1ff;
            pushReturnValues(stackFrame, v, a, c);
            top = stackFrame.top;
            v = stackFrame.v;
            return null;
        }

        /**
         * OP_ADD :: A B C R(A):= RK(B) + RK(C)
         * OP_SUB :: A B C R(A):= RK(B) - RK(C)
         * OP_MUL :: A B C R(A):= RK(B) * RK(C)
         * OP_DIV :: A B C R(A):= RK(B) / RK(C)
         * OP_MOD :: A B C R(A):= RK(B) % RK(C)
         * OP_POW :: A B C R(A):= RK(B) ^ RK(C)
         */
        @CheckReturnValue
        private LuaValue binaryArithmeticOp(int i) {
            int b = i >>> 23;
            int c = (i >> 14) & 0x1ff;

            LuaValue left = (b > 0xff ? k[b & 0x0ff] : stack[b]);
            LuaValue right = (c > 0xff ? k[c & 0x0ff] : stack[c]);

            final int opcode = (i & 0x3f);
            switch (opcode) {
            case Lua.OP_ADD:
                return left.add(right);
            case Lua.OP_SUB:
                return left.sub(right);
            case Lua.OP_MUL:
                return left.mul(right);
            case Lua.OP_DIV:
                return left.div(right);
            case Lua.OP_MOD:
                return left.mod(right);
            case Lua.OP_POW:
                return left.pow(right);
            default:
                throw new LuaException("Unsupported opcode: " + opcode);
            }
        }

        /**
         * OP_EQ :: A B C if ((RK(B) == RK(C)) ~= A) then pc++
         * OP_LT :: A B C if ((RK(B) < RK(C)) ~= A) then pc++
         * OP_LE :: A B C if ((RK(B) <= RK(C)) ~= A) then pc++
         */
        private void opCompare(int i, int a) {
            boolean aBool = (a != 0);
            int b = i >>> 23;
            int c = (i >> 14) & 0x1ff;

            LuaValue left = (b > 0xff ? k[b & 0x0ff] : stack[b]);
            LuaValue right = (c > 0xff ? k[c & 0x0ff] : stack[c]);

            final int opcode = (i & 0x3f);
            switch (opcode) {
            case Lua.OP_EQ:
                if (left.eq_b(right) != aBool) {
                    pc++;
                }
                break;
            case Lua.OP_LT:
                if (left.lt_b(right) != aBool) {
                    pc++;
                }
                break;
            case Lua.OP_LE:
                if (left.lteq_b(right) != aBool) {
                    pc++;
                }
                break;
            default:
                throw new LuaException("Unsupported opcode: " + opcode);
            }
        }

        /** A Bx R(A):= closure(KPROTO[Bx], R(A), ... ,R(A+n)) */
        private void opClosure(int i, int a) {
            Prototype newp = p.p[i >>> 14];
            LuaClosure newcl = new LuaClosure(newp, closure.getfenv());
            UpValue[] newUpValues = newcl.getUpValues();
            for (int j = 0, nup = newp.nups; j < nup; ++j) {
                i = code[pc++];

                int b = i >>> 23;
                if ((i & 4) != 0) {
                    newUpValues[j] = upValues[b];
                } else {
                    if (openups[b] == null) {
                        openups[b] = new UpValue(stack, b);
                    }
                    newUpValues[j] = openups[b];
                }
            }
            stack[a] = newcl;
        }

        /** A B C R(A):= R(B).. ... ..R(C) */
        private void opConcat(int i, int a) {
            int b = i >>> 23;
            int c = (i >> 14) & 0x1ff;

            if (c > b + 1) {
                Buffer buffer = stack[c].buffer();
                while (--c >= b) {
                    buffer = stack[c].concat(buffer);
                }
                stack[a] = buffer.value();
            } else {
                stack[a] = stack[c - 1].concat(stack[c]);
            }
        }

        /** A sBx R(A)+=R(A+2): if R(A) >> 14) - 0x1ffff;
            }
        }

        /** A sBx R(A)-=R(A+2): pc+=sBx */
        private void opForPrep(int i, int a) {
            LuaValue init = stack[a].checknumber("'for' initial value must be a number");
            LuaValue limit = stack[a + 1].checknumber("'for' limit must be a number");
            LuaValue step = stack[a + 2].checknumber("'for' step must be a number");
            stack[a] = init.sub(step);
            stack[a + 1] = limit;
            stack[a + 2] = step;
            pc += (i >>> 14) - 0x1ffff;
        }

        /**
         * A C R(A+3), ... ,R(A+2+C):= R(A)(R(A+1), R(A+2)):
         * if R(A+3) ~= nil then R(A+2)=R(A+3) else pc++
         */
        private void opTForLoop(int i, int a) {
            invoke(stack[a], varargsOf(stack[a + 1], stack[a + 2]));

            LuaValue object = v.arg1();
            if (object.isnil()) {
                pc++;
            } else {
                stack[a + 2] = stack[a + 3] = object;

                for (int c = (i >> 14) & 0x1ff; c > 1; --c) {
                    stack[a + 2 + c] = v.arg(c);
                }
                v = NONE; // todo: necessary?
            }
        }

        /** A B C R(A+1):= R(B): R(A):= R(B)[RK(C)] */
        private void opSelf(int i, int a) {
            LuaValue object = stack[i >>> 23];
            stack[a + 1] = object;

            int c = (i >> 14) & 0x1ff;
            stack[a] = object.get(c > 0xff ? k[c & 0x0ff] : stack[c]);
        }

        /** A B C R(A)[(C-1)*FPF+i]:= R(A+i), 1 <= i <= B */
        private void opSetList(int i, int a) {
            int b = (i >>> 23);
            int c = (i >> 14) & 0x1ff;
            if (c == 0) {
                c = code[pc++];
            }

            LuaValue object = stack[a];
            int offset = (c - 1) * Lua.LFIELDS_PER_FLUSH;

            if (b == 0) {
                b = top - a - 1;
                int m = b - v.narg();
                int j = 1;
                for (; j <= m; j++) {
                    object.set(offset + j, stack[a + j]);
                }
                for (; j <= b; j++) {
                    object.set(offset + j, v.arg(j - m));
                }
            } else {
                object.presize(offset + b);
                for (int j = 1; j <= b; j++) {
                    object.set(offset + j, stack[a + j]);
                }
            }
        }

        /** A B return R(A), ... ,R(A+B-2) (see note) */
        private Varargs opReturn(int i, int a) {
            int b = i >>> 23;

            stackFrame.status = Status.FINISHED;

            switch (b) {
            case 0:
                return copyArgs(stack, a, top - v.narg() - a, v); // Important: copies args
            case 1:
                return NONE;
            case 2:
                return stack[a];
            default:
                return copyArgs(stack, a, b - 1); // Important: copies args
            }
        }

        /** A B R(A), R(A+1), ..., R(A+B-1) = vararg */
        private void opVararg(int i, int a) {
            int b = i >>> 23;
            if (b == 0) {
                top = a + varargs.narg();
                v = varargs;
            } else {
                for (int j = 1; j < b; ++j) {
                    stack[a + j - 1] = varargs.arg(j);
                }
            }
        }

        /** Closes all variables in the stack up to (>=) R(A) */
        private void opClose(int a) {
            for (int b = openups.length; --b >= a;) {
                if (openups[b] != null) {
                    openups[b].close();
                    openups[b] = null;
                }
            }
        }

        /** A B C if (R(B) <=> C) then R(A):= R(B) else pc++ */
        private void opTestSet(int i, int a) {
            LuaValue o;
            /* note: doc appears to be reversed */
            if ((o = stack[i >>> 23]).toboolean() != ((i & (0x1ff << 14)) != 0)) {
                ++pc;
            } else {
                stack[a] = o; // TODO: should be sBx?
            }
        }

        private void invoke(LuaValue function, Varargs args) {
            stackFrame.top = top;
            stackFrame.pc = pc;
            stackFrame.v = v;

            v = function.invoke(args);

            top = stackFrame.top;
            pc = stackFrame.pc;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy