Browse Source

improve: improve call stack handling and exception management

Akeit0 7 months ago
parent
commit
0bbaa0141a

+ 54 - 26
src/Lua/Exceptions.cs

@@ -2,6 +2,7 @@ using Lua.CodeAnalysis;
 using Lua.CodeAnalysis.Syntax;
 using Lua.CodeAnalysis.Syntax;
 using Lua.Internal;
 using Lua.Internal;
 using Lua.Runtime;
 using Lua.Runtime;
+using System.Runtime.CompilerServices;
 
 
 namespace Lua;
 namespace Lua;
 
 
@@ -94,42 +95,62 @@ public class LuaRuntimeException : LuaException
 
 
     public LuaRuntimeException(LuaThread? thread, LuaValue errorObject)
     public LuaRuntimeException(LuaThread? thread, LuaValue errorObject)
     {
     {
+        if (thread != null)
+        {
+            thread.CurrentException?.Build();
+            thread.ExceptionTrace.Clear();
+            thread.CurrentException = this;
+        }
         Thread = thread;
         Thread = thread;
+        
         ErrorObject = errorObject;
         ErrorObject = errorObject;
     }
     }
 
 
-    public Traceback? LuaTraceback { get; private set; }
+    Traceback? luaTraceback;
+
+    public Traceback? LuaTraceback
+    {
+
+        get
+        {
+            if (luaTraceback == null)
+            {
+                Build();
+            }
+            return luaTraceback;
+        }
+    }
     internal LuaThread? Thread { get; private set; } = default!;
     internal LuaThread? Thread { get; private set; } = default!;
     public LuaValue ErrorObject { get; }
     public LuaValue ErrorObject { get; }
 
 
-    public static void AttemptInvalidOperation(LuaThread? traceback, string op, LuaValue a, LuaValue b)
+    public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a, LuaValue b)
     {
     {
-        throw new LuaRuntimeException(traceback, $"attempt to {op} a '{a.Type}' with a '{b.Type}'");
+        throw new LuaRuntimeException(thread, $"attempt to {op} a '{a.Type}' with a '{b.Type}'");
     }
     }
 
 
-    public static void AttemptInvalidOperation(LuaThread? traceback, string op, LuaValue a)
+    public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a)
     {
     {
-        throw new LuaRuntimeException(traceback, $"attempt to {op} a '{a.Type}' value");
+        throw new LuaRuntimeException(thread, $"attempt to {op} a '{a.Type}' value");
     }
     }
 
 
-    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName)
+    public static void BadArgument(LuaThread? thread, int argumentId, string functionName)
     {
     {
-        throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (value expected)");
+        throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' (value expected)");
     }
     }
 
 
-    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName, LuaValueType[] expected)
+    public static void BadArgument(LuaThread? thread, int argumentId, string functionName, LuaValueType[] expected)
     {
     {
-        throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({string.Join(" or ", expected)} expected)");
+        throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' ({string.Join(" or ", expected)} expected)");
     }
     }
 
 
-    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName, string expected, string actual)
+    public static void BadArgument(LuaThread? thread, int argumentId, string functionName, string expected, string actual)
     {
     {
-        throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({expected} expected, got {actual})");
+        throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' ({expected} expected, got {actual})");
     }
     }
 
 
-    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName, string message)
+    public static void BadArgument(LuaThread? thread, int argumentId, string functionName, string message)
     {
     {
-        throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({message})");
+        throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' ({message})");
     }
     }
 
 
     public static void BadArgumentNumberIsNotInteger(LuaThread? thread, int argumentId, string functionName)
     public static void BadArgumentNumberIsNotInteger(LuaThread? thread, int argumentId, string functionName)
@@ -163,15 +184,27 @@ public class LuaRuntimeException : LuaException
         }
         }
     }
     }
 
 
-    internal void BuildWithPop(int top)
+
+    [MethodImpl(MethodImplOptions.NoInlining)]
+    internal Traceback? Build()
     {
     {
+        if (luaTraceback != null) return luaTraceback;
         if (Thread != null)
         if (Thread != null)
         {
         {
-            var callStack = Thread.CallStack.AsSpan()[top..];
-            if (callStack.IsEmpty) return;
-            LuaTraceback = new Traceback(Thread.State) { RootFunc = callStack[0].Function, StackFrames = callStack[1..].ToArray(), };
-            Thread.CallStack.PopUntil(top);
+            var callStack = Thread.ExceptionTrace.AsSpan();
+            if (callStack.IsEmpty) return null;
+            luaTraceback = new Traceback(Thread.State,callStack);
+            Thread.ExceptionTrace.Clear();
+            Thread = null;
         }
         }
+
+        return luaTraceback;
+    }
+
+    internal void Forget()
+    {
+        Thread?.ExceptionTrace.Clear();
+        Thread = null;
     }
     }
 
 
     public override string Message
     public override string Message
@@ -191,7 +224,9 @@ public class LuaRuntimeException : LuaException
     public override string ToString()
     public override string ToString()
     {
     {
         if (LuaTraceback == null)
         if (LuaTraceback == null)
+        {
             return base.ToString();
             return base.ToString();
+        }
         var pooledList = new PooledList<char>(64);
         var pooledList = new PooledList<char>(64);
         pooledList.Clear();
         pooledList.Clear();
         try
         try
@@ -207,16 +242,9 @@ public class LuaRuntimeException : LuaException
         {
         {
             pooledList.Dispose();
             pooledList.Dispose();
         }
         }
-        //return $"{Message} {StackTrace}";
     }
     }
 }
 }
 
 
-public class LuaAssertionException(LuaThread? traceback, string message) : LuaRuntimeException(traceback, message)
-{
-    // public override string ToString()
-    // {
-    //     return $"{Message}\n{StackTrace}";
-    // }
-}
+public class LuaAssertionException(LuaThread? traceback, string message) : LuaRuntimeException(traceback, message);
 
 
 public class LuaModuleNotFoundException(string moduleName) : LuaException($"module '{moduleName}' not found");
 public class LuaModuleNotFoundException(string moduleName) : LuaException($"module '{moduleName}' not found");

+ 1 - 1
src/Lua/Internal/FastStackCore.cs

@@ -85,8 +85,8 @@ public struct FastStackCore<T>
 
 
     public void PopUntil(int top)
     public void PopUntil(int top)
     {
     {
+        array.AsSpan(top, tail - top).Clear();
         tail = top;
         tail = top;
-        array.AsSpan(top, tail).Clear();
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 100 - 0
src/Lua/Internal/ReversedStack.cs

@@ -0,0 +1,100 @@
+namespace Lua.Internal;
+
+internal sealed class ReversedStack<T>(int capacity = 16)
+{
+    T[] buffer = new T[capacity];
+    int tail = capacity;
+
+    public int Length => buffer.Length - tail;
+
+    public Span<T> AsSpan()
+    {
+        return buffer.AsSpan(tail, buffer.Length - tail);
+    }
+
+    public void Push(T element)
+    {
+        EnsureAdditionalCapacity(1);
+        buffer[--tail] = element;
+    }
+
+    public void Push(ReadOnlySpan<T> elements)
+    {
+        if (elements.Length == 0) return;
+        EnsureAdditionalCapacity(elements.Length);
+        tail -= elements.Length;
+        elements.CopyTo(buffer.AsSpan(tail));
+    }
+
+    public T Pop()
+    {
+        if (IsEmpty)
+        {
+            ThrowEmpty();
+        }
+
+        static void ThrowEmpty() => throw new InvalidOperationException("Cannot pop an empty stack");
+        return buffer[tail++];
+    }
+
+    public void Pop(int count)
+    {
+        if (count > buffer.Length - tail)
+        {
+            ThrowEmpty();
+        }
+
+        static void ThrowEmpty() => throw new InvalidOperationException("Cannot pop more elements than exist in the stack");
+        tail += count;
+    }
+
+    public void TryPop(out T? element)
+    {
+        if (IsEmpty)
+        {
+            element = default!;
+            return;
+        }
+
+        element = buffer[tail++];
+    }
+
+    public void Clear()
+    {
+        tail = buffer.Length;
+    }
+
+    public bool IsEmpty => tail == buffer.Length;
+
+    public T Peek()
+    {
+        if (IsEmpty)
+        {
+            ThrowEmpty();
+        }
+
+        static void ThrowEmpty() => throw new InvalidOperationException("Cannot peek an empty stack");
+        return buffer[tail];
+    }
+
+
+    void EnsureAdditionalCapacity(int required)
+    {
+        if (tail >= required) return;
+        Resize(required - tail + buffer.Length);
+
+        void Resize(int requiredCapacity)
+        {
+            int newCapacity = buffer.Length * 2;
+            while (newCapacity < requiredCapacity)
+            {
+                newCapacity *= 2;
+            }
+
+            T[] newBuffer = new T[newCapacity];
+            AsSpan().CopyTo(newBuffer.AsSpan(newCapacity - buffer.Length));
+            tail = newCapacity - (buffer.Length - tail);
+            buffer = newBuffer;
+        }
+    }
+}

+ 1 - 2
src/Lua/LuaCoroutine.cs

@@ -189,8 +189,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                 {
                 {
                     if (ex is LuaRuntimeException luaRuntimeException)
                     if (ex is LuaRuntimeException luaRuntimeException)
                     {
                     {
-                        luaRuntimeException.BuildWithPop(0);
-                        traceback = luaRuntimeException.LuaTraceback;
+                        traceback = luaRuntimeException.Build();
                     }
                     }
 
 
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);

+ 15 - 8
src/Lua/LuaFunction.cs

@@ -20,16 +20,23 @@ public class LuaFunction(string name, Func<LuaFunctionExecutionContext, Cancella
             context = context with { ArgumentCount = context.ArgumentCount - varArgumentCount };
             context = context with { ArgumentCount = context.ArgumentCount - varArgumentCount };
         }
         }
 
 
-        var frame = new CallStackFrame { Base = context.FrameBase, VariableArgumentCount = varArgumentCount, Function = this, ReturnBase = context.ReturnFrameBase };
-        context.Thread.PushCallStackFrame(frame);
+        var callStackFrameCount = context.Thread.CallStackFrameCount;
+        try
+        {
+            var frame = new CallStackFrame { Base = context.FrameBase, VariableArgumentCount = varArgumentCount, Function = this, ReturnBase = context.ReturnFrameBase };
+            context.Thread.PushCallStackFrame(frame);
+
+            if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            {
+                return await LuaVirtualMachine.ExecuteCallHook(context, cancellationToken);
+            }
 
 
-        if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            var r = await Func(context, cancellationToken);
+            return r;
+        }
+        finally
         {
         {
-            return await LuaVirtualMachine.ExecuteCallHook(context, cancellationToken);
+            context.Thread.PopCallStackFrameUntil(callStackFrameCount);
         }
         }
-
-        var r = await Func(context, cancellationToken);
-        context.Thread.PopCallStackFrame();
-        return r;
     }
     }
 }
 }

+ 87 - 16
src/Lua/LuaThread.cs

@@ -25,7 +25,7 @@ public abstract class LuaThread
         throw new LuaRuntimeException(context.Thread, "attempt to yield from outside a coroutine");
         throw new LuaRuntimeException(context.Thread, "attempt to yield from outside a coroutine");
     }
     }
 
 
-    internal class ThreadCoreData : IPoolNode<ThreadCoreData>
+    protected class ThreadCoreData : IPoolNode<ThreadCoreData>
     {
     {
         //internal  LuaCoroutineData? coroutineData;
         //internal  LuaCoroutineData? coroutineData;
         internal LuaStack Stack = new();
         internal LuaStack Stack = new();
@@ -59,17 +59,20 @@ public abstract class LuaThread
     }
     }
 
 
     public LuaState State { get; protected set; } = null!;
     public LuaState State { get; protected set; } = null!;
-    internal ThreadCoreData? CoreData = new();
+    protected ThreadCoreData? CoreData = new();
     internal BitFlags2 LineAndCountHookMask;
     internal BitFlags2 LineAndCountHookMask;
     internal BitFlags2 CallOrReturnHookMask;
     internal BitFlags2 CallOrReturnHookMask;
     internal bool IsInHook;
     internal bool IsInHook;
     internal int HookCount;
     internal int HookCount;
     internal int BaseHookCount;
     internal int BaseHookCount;
     internal int LastPc;
     internal int LastPc;
+
+    internal LuaRuntimeException? CurrentException;
+    internal readonly ReversedStack<CallStackFrame> ExceptionTrace = new();
+
     public bool IsRunning { get; protected set; }
     public bool IsRunning { get; protected set; }
     internal LuaFunction? Hook { get; set; }
     internal LuaFunction? Hook { get; set; }
     public LuaStack Stack => CoreData!.Stack;
     public LuaStack Stack => CoreData!.Stack;
-    internal ref FastStackCore<CallStackFrame> CallStack => ref CoreData!.CallStack;
 
 
     internal bool IsLineHookEnabled
     internal bool IsLineHookEnabled
     {
     {
@@ -103,28 +106,98 @@ public abstract class LuaThread
         IsRunning = true;
         IsRunning = true;
         try
         try
         {
         {
-            await closure.InvokeAsync(new()
-            {
-                Thread = this, ArgumentCount = Stack.Count, ReturnFrameBase = 0,
-            }, cancellationToken);
+            await closure.InvokeAsync(new() { Thread = this, ArgumentCount = Stack.Count, ReturnFrameBase = 0, }, cancellationToken);
 
 
             return Stack.Count;
             return Stack.Count;
         }
         }
-        catch (LuaRuntimeException e)
-        {
-            e.BuildWithPop(0);
-            throw;
-        }
         finally
         finally
         {
         {
+            PopCallStackFrameUntil(0);
             IsRunning = false;
             IsRunning = false;
         }
         }
     }
     }
 
 
+    public int CallStackFrameCount => CoreData == null ? 0 : CoreData!.CallStack.Count;
+
+    public ref readonly CallStackFrame GetCurrentFrame()
+    {
+        return ref CoreData!.CallStack.PeekRef();
+    }
+
+    public ReadOnlySpan<LuaValue> GetStackValues()
+    {
+        return CoreData == null ? default : CoreData!.Stack.AsSpan();
+    }
+
+    public ReadOnlySpan<CallStackFrame> GetCallStackFrames()
+    {
+        return CoreData == null ? default : CoreData!.CallStack.AsSpan();
+    }
+
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PushCallStackFrame(in CallStackFrame frame)
+    {
+        CurrentException?.Build();
+        CurrentException = null;
+        CoreData!.CallStack.Push(frame);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PopCallStackFrameWithStackPop()
+    {
+        var coreData = CoreData!;
+        var popFrame = coreData.CallStack.Pop();
+        if (CurrentException != null)
+        {
+            ExceptionTrace.Push(popFrame);
+        }
+
+        coreData.Stack.PopUntil(popFrame.Base);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PopCallStackFrameWithStackPop(int frameBase)
+    {
+        var coreData = CoreData!;
+        var popFrame = coreData.CallStack.Pop();
+        if (CurrentException != null)
+        {
+            ExceptionTrace.Push(popFrame);
+        }
+
+        {
+            coreData.Stack.PopUntil(frameBase);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PopCallStackFrame()
+    {
+        var coreData = CoreData!;
+        var popFrame = coreData.CallStack.Pop();
+        if (CurrentException != null)
+        {
+            ExceptionTrace.Push(popFrame);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PopCallStackFrameUntil(int top)
+    {
+        var coreData = CoreData!;
+        ref var callStack = ref coreData.CallStack;
+        if (CurrentException != null)
+        {
+            ExceptionTrace.Push(callStack.AsSpan()[top..]);
+        }
+
+        callStack.PopUntil(top);
+    }
 
 
     internal void DumpStackValues()
     internal void DumpStackValues()
     {
     {
-        var span = this.GetStackValues();
+        var span = GetStackValues();
         for (int i = 0; i < span.Length; i++)
         for (int i = 0; i < span.Length; i++)
         {
         {
             Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
             Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
@@ -133,9 +206,7 @@ public abstract class LuaThread
 
 
     public Traceback GetTraceback()
     public Traceback GetTraceback()
     {
     {
-        var frames = this.GetCallStackFrames();
-
-        return new(State) { RootFunc = frames[0].Function, StackFrames = this.GetCallStackFrames()[1..].ToArray() };
+        return new(State, GetCallStackFrames());
     }
     }
 
 
     protected void ThrowIfRunning()
     protected void ThrowIfRunning()

+ 39 - 84
src/Lua/LuaThreadExtensions.cs

@@ -55,79 +55,32 @@ public static class LuaThreadExtensions
 
 
     public static void Push(this LuaThread thread, LuaValue value)
     public static void Push(this LuaThread thread, LuaValue value)
     {
     {
-        thread.CoreData!.Stack.Push(value);
+        thread.Stack.Push(value);
     }
     }
 
 
     public static void Push(this LuaThread thread, params ReadOnlySpan<LuaValue> span)
     public static void Push(this LuaThread thread, params ReadOnlySpan<LuaValue> span)
     {
     {
-        thread.CoreData!.Stack.PushRange(span);
+        thread.Stack.PushRange(span);
     }
     }
 
 
     public static void Pop(this LuaThread thread, int count)
     public static void Pop(this LuaThread thread, int count)
     {
     {
-        thread.CoreData!.Stack.Pop(count);
+        thread.Stack.Pop(count);
     }
     }
 
 
     public static LuaValue Pop(this LuaThread thread)
     public static LuaValue Pop(this LuaThread thread)
     {
     {
-        return thread.CoreData!.Stack.Pop();
+        return thread.Stack.Pop();
     }
     }
 
 
     public static LuaReturnValuesReader ReadReturnValues(this LuaThread thread, int argumentCount)
     public static LuaReturnValuesReader ReadReturnValues(this LuaThread thread, int argumentCount)
     {
     {
-        var stack = thread.CoreData!.Stack;
+        var stack = thread.Stack;
         return new LuaReturnValuesReader(stack, stack.Count - argumentCount);
         return new LuaReturnValuesReader(stack, stack.Count - argumentCount);
     }
     }
 
 
-    public static ref readonly CallStackFrame GetCurrentFrame(this LuaThread thread)
-    {
-        return ref thread.CoreData!.CallStack.PeekRef();
-    }
 
 
-    public static ReadOnlySpan<LuaValue> GetStackValues(this LuaThread thread)
-    {
-        if (thread.CoreData == null) return default;
-        return thread.CoreData!.Stack.AsSpan();
-    }
-
-    public static ReadOnlySpan<CallStackFrame> GetCallStackFrames(this LuaThread thread)
-    {
-        if (thread.CoreData == null) return default;
-        return thread.CoreData!.CallStack.AsSpan();
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static void PushCallStackFrame(this LuaThread thread, in CallStackFrame frame)
-    {
-        thread.CoreData!.CallStack.Push(frame);
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static void PopCallStackFrameWithStackPop(this LuaThread thread)
-    {
-        var coreData = thread.CoreData!;
-
-        coreData.Stack.PopUntil(coreData!.CallStack.Pop().Base);
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static void PopCallStackFrameWithStackPop(this LuaThread thread, int frameBase)
-    {
-        var coreData = thread.CoreData!;
-        coreData!.CallStack.Pop();
-        {
-            coreData.Stack.PopUntil(frameBase);
-        }
-    }
-
-    [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    internal static void PopCallStackFrame(this LuaThread thread)
-    {
-        var coreData = thread.CoreData!;
-        coreData!.CallStack.Pop();
-    }
-
-    public static async ValueTask<LuaValue> OpArithmetic(this LuaThread thread, LuaValue left, LuaValue right, OpCode opCode, CancellationToken ct = default)
+    public static async ValueTask<LuaValue> Arithmetic(this LuaThread thread, LuaValue x, LuaValue y, OpCode opCode, CancellationToken cancellationToken = default)
     {
     {
         [MethodImpl(MethodImplOptions.NoInlining)]
         [MethodImpl(MethodImplOptions.NoInlining)]
         static double Mod(double a, double b)
         static double Mod(double a, double b)
@@ -157,31 +110,32 @@ public static class LuaThreadExtensions
         }
         }
 
 
 
 
-        if (left.TryReadDouble(out var numB) && right.TryReadDouble(out var numC))
+        if (x.TryReadDouble(out var numX) && y.TryReadDouble(out var numY))
         {
         {
-            return ArithmeticOperation(opCode, numB, numC);
+            return ArithmeticOperation(opCode, numX, numY);
         }
         }
 
 
-        return await LuaVirtualMachine.ExecuteBinaryOperationMetaMethod(thread, left, right, opCode, ct);
+
+        return await LuaVirtualMachine.ExecuteBinaryOperationMetaMethod(thread, x, y, opCode, cancellationToken);
     }
     }
 
 
-    public static async ValueTask<LuaValue> OpUnary(this LuaThread thread, LuaValue left, OpCode opCode, CancellationToken ct = default)
+    public static async ValueTask<LuaValue> Unary(this LuaThread thread, LuaValue value, OpCode opCode, CancellationToken cancellationToken = default)
     {
     {
         if (opCode == OpCode.Unm)
         if (opCode == OpCode.Unm)
         {
         {
-            if (left.TryReadDouble(out var numB))
+            if (value.TryReadDouble(out var numB))
             {
             {
                 return -numB;
                 return -numB;
             }
             }
         }
         }
         else if (opCode == OpCode.Len)
         else if (opCode == OpCode.Len)
         {
         {
-            if (left.TryReadString(out var str))
+            if (value.TryReadString(out var str))
             {
             {
                 return str.Length;
                 return str.Length;
             }
             }
 
 
-            if (left.TryReadTable(out var table))
+            if (value.TryReadTable(out var table))
             {
             {
                 return table.ArrayLength;
                 return table.ArrayLength;
             }
             }
@@ -192,11 +146,11 @@ public static class LuaThreadExtensions
         }
         }
 
 
 
 
-        return await LuaVirtualMachine.ExecuteUnaryOperationMetaMethod(thread, left, opCode, ct);
+        return await LuaVirtualMachine.ExecuteUnaryOperationMetaMethod(thread, value, opCode, cancellationToken);
     }
     }
 
 
 
 
-    public static async ValueTask<bool> OpCompare(this LuaThread thread, LuaValue vb, LuaValue vc, OpCode opCode, CancellationToken ct = default)
+    public static async ValueTask<bool> Compare(this LuaThread thread, LuaValue x, LuaValue y, OpCode opCode, CancellationToken cancellationToken = default)
     {
     {
         if (opCode is not (OpCode.Eq or OpCode.Lt or OpCode.Le))
         if (opCode is not (OpCode.Eq or OpCode.Lt or OpCode.Le))
         {
         {
@@ -205,30 +159,30 @@ public static class LuaThreadExtensions
 
 
         if (opCode == OpCode.Eq)
         if (opCode == OpCode.Eq)
         {
         {
-            if (vb == vc)
+            if (x == y)
             {
             {
                 return true;
                 return true;
             }
             }
         }
         }
         else
         else
         {
         {
-            if (vb.TryReadNumber(out var numB) && vc.TryReadNumber(out var numC))
+            if (x.TryReadNumber(out var numX) && y.TryReadNumber(out var numY))
             {
             {
-                return opCode == OpCode.Lt ? numB < numC : numB <= numC;
+                return opCode == OpCode.Lt ? numX < numY : numX <= numY;
             }
             }
 
 
-            if (vb.TryReadString(out var strB) && vc.TryReadString(out var strC))
+            if (x.TryReadString(out var strX) && y.TryReadString(out var strY))
             {
             {
-                var c = StringComparer.Ordinal.Compare(strB, strC);
+                var c = StringComparer.Ordinal.Compare(strX, strY);
                 return opCode == OpCode.Lt ? c < 0 : c <= 0;
                 return opCode == OpCode.Lt ? c < 0 : c <= 0;
             }
             }
         }
         }
 
 
 
 
-        return await LuaVirtualMachine.ExecuteCompareOperationMetaMethod(thread, vb, vc, opCode, ct);
+        return await LuaVirtualMachine.ExecuteCompareOperationMetaMethod(thread, x, y, opCode, cancellationToken);
     }
     }
 
 
-    public static ValueTask<LuaValue> OpGetTable(this LuaThread thread, LuaValue table, LuaValue key, CancellationToken ct = default)
+    public static async ValueTask<LuaValue> GetTable(this LuaThread thread, LuaValue table, LuaValue key, CancellationToken cancellationToken = default)
     {
     {
         if (table.TryReadTable(out var luaTable))
         if (table.TryReadTable(out var luaTable))
         {
         {
@@ -238,10 +192,11 @@ public static class LuaThreadExtensions
             }
             }
         }
         }
 
 
-        return LuaVirtualMachine.ExecuteGetTableSlowPath(thread, table, key, ct);
+
+        return await LuaVirtualMachine.ExecuteGetTableSlowPath(thread, table, key, cancellationToken);
     }
     }
 
 
-    public static ValueTask OpSetTable(this LuaThread thread, LuaValue table, LuaValue key, LuaValue value, CancellationToken ct = default)
+    public static async ValueTask SetTable(this LuaThread thread, LuaValue table, LuaValue key, LuaValue value, CancellationToken cancellationToken = default)
     {
     {
         if (key.TryReadNumber(out var numB))
         if (key.TryReadNumber(out var numB))
         {
         {
@@ -258,39 +213,39 @@ public static class LuaThreadExtensions
             if (!Unsafe.IsNullRef(ref valueRef) && valueRef.Type != LuaValueType.Nil)
             if (!Unsafe.IsNullRef(ref valueRef) && valueRef.Type != LuaValueType.Nil)
             {
             {
                 valueRef = value;
                 valueRef = value;
-                return default;
+                return;
             }
             }
         }
         }
 
 
-        return LuaVirtualMachine.ExecuteSetTableSlowPath(thread, table, key, value, ct);
+        await LuaVirtualMachine.ExecuteSetTableSlowPath(thread, table, key, value, cancellationToken);
     }
     }
 
 
-    public static ValueTask<LuaValue> OpConcat(this LuaThread thread, ReadOnlySpan<LuaValue> values, CancellationToken ct = default)
+    public static ValueTask<LuaValue> Concat(this LuaThread thread, ReadOnlySpan<LuaValue> values, CancellationToken cancellationToken = default)
     {
     {
         thread.Stack.PushRange(values);
         thread.Stack.PushRange(values);
-        return OpConcat(thread, values.Length, ct);
+        return Concat(thread, values.Length, cancellationToken);
     }
     }
 
 
-    public static ValueTask<LuaValue> OpConcat(this LuaThread thread, int concatCount, CancellationToken ct = default)
+    public static async ValueTask<LuaValue> Concat(this LuaThread thread, int concatCount, CancellationToken cancellationToken = default)
     {
     {
-        return LuaVirtualMachine.Concat(thread, concatCount, ct);
+        return await LuaVirtualMachine.Concat(thread, concatCount, cancellationToken);
     }
     }
 
 
-    public static ValueTask<int> OpCall(this LuaThread thread, int funcIndex, CancellationToken ct = default)
+    public static async ValueTask<int> Call(this LuaThread thread, int funcIndex, CancellationToken cancellationToken = default)
     {
     {
-        return LuaVirtualMachine.Call(thread, funcIndex, ct);
+        return await LuaVirtualMachine.Call(thread, funcIndex, cancellationToken);
     }
     }
 
 
-    public static ValueTask<LuaValue[]> OpCall(this LuaThread thread, LuaValue function, ReadOnlySpan<LuaValue> args, CancellationToken ct = default)
+    public static ValueTask<LuaValue[]> Call(this LuaThread thread, LuaValue function, ReadOnlySpan<LuaValue> arguments, CancellationToken cancellationToken = default)
     {
     {
         var funcIndex = thread.Stack.Count;
         var funcIndex = thread.Stack.Count;
         thread.Stack.Push(function);
         thread.Stack.Push(function);
-        thread.Stack.PushRange(args);
-        return Impl(thread, funcIndex, ct);
+        thread.Stack.PushRange(arguments);
+        return Impl(thread, funcIndex, cancellationToken);
 
 
-        static async ValueTask<LuaValue[]> Impl(LuaThread thread, int funcIndex, CancellationToken ct)
+        static async ValueTask<LuaValue[]> Impl(LuaThread thread, int funcIndex, CancellationToken cancellationToken)
         {
         {
-            await LuaVirtualMachine.Call(thread, funcIndex, ct);
+            await LuaVirtualMachine.Call(thread, funcIndex, cancellationToken);
             var count = thread.Stack.Count - funcIndex;
             var count = thread.Stack.Count - funcIndex;
             using var results = thread.ReadReturnValues(count);
             using var results = thread.ReadReturnValues(count);
             return results.AsSpan().ToArray();
             return results.AsSpan().ToArray();

+ 2 - 2
src/Lua/Runtime/LuaClosure.cs

@@ -18,13 +18,13 @@ public sealed class LuaClosure : LuaFunction
             return;
             return;
         }
         }
 
 
-        if (thread.CallStack.Count == 0)
+        if (thread.CallStackFrameCount == 0)
         {
         {
             upValues.Add(thread.State.EnvUpValue);
             upValues.Add(thread.State.EnvUpValue);
             return;
             return;
         }
         }
 
 
-        var baseIndex = thread.CallStack.Peek().Base;
+        var baseIndex = thread.GetCallStackFrames()[^1].Base;
 
 
         // add upvalues
         // add upvalues
         for (int i = 0; i < proto.UpValues.Length; i++)
         for (int i = 0; i < proto.UpValues.Length; i++)

+ 83 - 60
src/Lua/Runtime/LuaVirtualMachine.cs

@@ -42,7 +42,7 @@ public static partial class LuaVirtualMachine
             Pc = -1;
             Pc = -1;
             Instruction = default;
             Instruction = default;
             PostOperation = PostOperationType.None;
             PostOperation = PostOperationType.None;
-            BaseCallStackCount = thread.CallStack.Count;
+            BaseCallStackCount = thread.CallStackFrameCount;
             LastHookPc = -1;
             LastHookPc = -1;
             Task = default;
             Task = default;
         }
         }
@@ -60,7 +60,7 @@ public static partial class LuaVirtualMachine
         public int CurrentReturnFrameBase;
         public int CurrentReturnFrameBase;
         public ValueTask<int> Task;
         public ValueTask<int> Task;
         public int LastHookPc;
         public int LastHookPc;
-        public bool IsTopLevel => BaseCallStackCount == Thread.CallStack.Count;
+        public bool IsTopLevel => BaseCallStackCount == Thread.CallStackFrameCount;
 
 
         public int BaseCallStackCount;
         public int BaseCallStackCount;
 
 
@@ -84,9 +84,8 @@ public static partial class LuaVirtualMachine
         public bool PopFromBuffer(int src, int srcCount)
         public bool PopFromBuffer(int src, int srcCount)
         {
         {
             var result = Stack.GetBuffer().Slice(src, srcCount);
             var result = Stack.GetBuffer().Slice(src, srcCount);
-            ref var callStack = ref Thread.CallStack;
         Re:
         Re:
-            var frames = callStack.AsSpan();
+            var frames = Thread.GetCallStackFrames();
             if (frames.Length == BaseCallStackCount)
             if (frames.Length == BaseCallStackCount)
             {
             {
                 var returnBase = frames[^1].ReturnBase;
                 var returnBase = frames[^1].ReturnBase;
@@ -196,15 +195,9 @@ public static partial class LuaVirtualMachine
 
 
         public void PopOnTopCallStackFrames()
         public void PopOnTopCallStackFrames()
         {
         {
-            ref var callStack = ref Thread.CallStack;
-            var count = callStack.Count;
+            var count = Thread.CallStackFrameCount;
             if (count == BaseCallStackCount) return;
             if (count == BaseCallStackCount) return;
-            while (callStack.Count > BaseCallStackCount + 1)
-            {
-                callStack.TryPop();
-            }
-
-            Thread.PopCallStackFrame();
+            Thread.PopCallStackFrameUntil(BaseCallStackCount);
         }
         }
 
 
         bool ExecutePostOperation(PostOperationType postOperation)
         bool ExecutePostOperation(PostOperationType postOperation)
@@ -809,9 +802,11 @@ public static partial class LuaVirtualMachine
             if (e is not LuaRuntimeException)
             if (e is not LuaRuntimeException)
             {
             {
                 var newException = new LuaRuntimeException(context.Thread, e);
                 var newException = new LuaRuntimeException(context.Thread, e);
+                context.PopOnTopCallStackFrames();
                 throw newException;
                 throw newException;
             }
             }
 
 
+            context.PopOnTopCallStackFrames();
             throw;
             throw;
         }
         }
     }
     }
@@ -950,7 +945,7 @@ public static partial class LuaVirtualMachine
         var stack = thread.Stack;
         var stack = thread.Stack;
         do
         do
         {
         {
-            var top = thread.Stack.Count;
+            var top = stack.Count;
             var n = 2;
             var n = 2;
             ref var lhs = ref stack.Get(top - 2);
             ref var lhs = ref stack.Get(top - 2);
             ref var rhs = ref stack.Get(top - 1);
             ref var rhs = ref stack.Get(top - 1);
@@ -1035,17 +1030,23 @@ public static partial class LuaVirtualMachine
             var newFrame = func.CreateNewFrame(context, stack.Count - argCount + varArgCount, target, varArgCount);
             var newFrame = func.CreateNewFrame(context, stack.Count - argCount + varArgCount, target, varArgCount);
 
 
             context.Thread.PushCallStackFrame(newFrame);
             context.Thread.PushCallStackFrame(newFrame);
-            if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+            try
             {
             {
-                await ExecuteCallHook(context, newFrame, argCount);
-            }
+                if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook)
+                {
+                    await ExecuteCallHook(context, newFrame, argCount);
+                }
 
 
 
 
-            await func.Invoke(context, newFrame, argCount);
-            stack.PopUntil(target + 1);
-            context.Thread.PopCallStackFrame();
-            context.PostOperation = PostOperationType.DontPop;
-            return;
+                await func.Invoke(context, newFrame, argCount);
+                stack.PopUntil(target + 1);
+                context.PostOperation = PostOperationType.DontPop;
+                return;
+            }
+            finally
+            {
+                context.Thread.PopCallStackFrame();
+            }
         }
         }
 
 
         LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), description, vb, vc);
         LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), description, vb, vc);
@@ -1154,16 +1155,21 @@ public static partial class LuaVirtualMachine
         var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = funcIndex };
         var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = funcIndex };
 
 
         thread.PushCallStackFrame(newFrame);
         thread.PushCallStackFrame(newFrame);
-        var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = funcIndex };
-        if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+        try
         {
         {
-            await ExecuteCallHook(functionContext, ct);
-        }
-
+            var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = funcIndex };
+            if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+            {
+                await ExecuteCallHook(functionContext, ct);
+            }
 
 
-        await func.Func(functionContext, ct);
-        thread.PopCallStackFrame();
-        return thread.Stack.Count - funcIndex;
+            await func.Func(functionContext, ct);
+            return thread.Stack.Count - funcIndex;
+        }
+        finally
+        {
+            thread.PopCallStackFrame();
+        }
     }
     }
 
 
     static void CallPostOperation(VirtualMachineExecutionContext context)
     static void CallPostOperation(VirtualMachineExecutionContext context)
@@ -1823,19 +1829,25 @@ public static partial class LuaVirtualMachine
             var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = newBase };
             var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = newBase };
 
 
             thread.PushCallStackFrame(newFrame);
             thread.PushCallStackFrame(newFrame);
-            var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = newBase };
-            if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+            try
             {
             {
-                await ExecuteCallHook(functionContext, ct);
-            }
+                var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = newBase };
+                if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+                {
+                    await ExecuteCallHook(functionContext, ct);
+                }
 
 
 
 
-            await func.Func(functionContext, ct);
-            var results = stack.GetBuffer()[newFrame.ReturnBase..];
-            var result = results.Length == 0 ? default : results[0];
-            results.Clear();
-            thread.PopCallStackFrameWithStackPop();
-            return result;
+                await func.Func(functionContext, ct);
+                var results = stack.GetBuffer()[newFrame.ReturnBase..];
+                var result = results.Length == 0 ? default : results[0];
+                results.Clear();
+                return result;
+            }
+            finally
+            {
+                thread.PopCallStackFrameWithStackPop();
+            }
         }
         }
 
 
         LuaRuntimeException.AttemptInvalidOperation(thread, description, vb, vc);
         LuaRuntimeException.AttemptInvalidOperation(thread, description, vb, vc);
@@ -1949,19 +1961,25 @@ public static partial class LuaVirtualMachine
             var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = newBase };
             var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = newBase };
 
 
             thread.PushCallStackFrame(newFrame);
             thread.PushCallStackFrame(newFrame);
-            var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = newBase };
-            if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+            try
             {
             {
-                await ExecuteCallHook(functionContext, ct);
-            }
+                var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = newBase };
+                if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+                {
+                    await ExecuteCallHook(functionContext, ct);
+                }
 
 
 
 
-            await func.Func(functionContext, ct);
-            var results = stack.GetBuffer()[newFrame.ReturnBase..];
-            var result = results.Length == 0 ? default : results[0];
-            results.Clear();
-            thread.PopCallStackFrameWithStackPop();
-            return result;
+                await func.Func(functionContext, ct);
+                var results = stack.GetBuffer()[newFrame.ReturnBase..];
+                var result = results.Length == 0 ? default : results[0];
+                results.Clear();
+                return result;
+            }
+            finally
+            {
+                thread.PopCallStackFrameWithStackPop();
+            }
         }
         }
 
 
         LuaRuntimeException.AttemptInvalidOperation(thread, description, vb);
         LuaRuntimeException.AttemptInvalidOperation(thread, description, vb);
@@ -2100,19 +2118,25 @@ public static partial class LuaVirtualMachine
             var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = newBase };
             var newFrame = new CallStackFrame() { Base = newBase, VariableArgumentCount = variableArgumentCount, Function = func, ReturnBase = newBase };
 
 
             thread.PushCallStackFrame(newFrame);
             thread.PushCallStackFrame(newFrame);
-            var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = newBase };
-            if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+            try
             {
             {
-                await ExecuteCallHook(functionContext, ct);
-            }
+                var functionContext = new LuaFunctionExecutionContext() { Thread = thread, ArgumentCount = argCount, ReturnFrameBase = newBase };
+                if (thread.CallOrReturnHookMask.Value != 0 && !thread.IsInHook)
+                {
+                    await ExecuteCallHook(functionContext, ct);
+                }
 
 
 
 
-            await func.Func(functionContext, ct);
-            var results = stack.GetBuffer()[newFrame.ReturnBase..];
-            var result = results.Length == 0 ? default : results[0];
-            results.Clear();
-            thread.PopCallStackFrameWithStackPop();
-            return result.ToBoolean();
+                await func.Func(functionContext, ct);
+                var results = stack.GetBuffer()[newFrame.ReturnBase..];
+                var result = results.Length == 0 ? default : results[0];
+                results.Clear();
+                return result.ToBoolean();
+            }
+            finally
+            {
+                thread.PopCallStackFrameWithStackPop();
+            }
         }
         }
 
 
         if (opCode == OpCode.Le)
         if (opCode == OpCode.Le)
@@ -2151,7 +2175,6 @@ public static partial class LuaVirtualMachine
         var stackBuffer = stack.GetBuffer()[temp..];
         var stackBuffer = stack.GetBuffer()[temp..];
         stackBuffer[..argumentCount].CopyTo(stackBuffer[variableArgumentCount..]);
         stackBuffer[..argumentCount].CopyTo(stackBuffer[variableArgumentCount..]);
         stackBuffer.Slice(argumentCount, variableArgumentCount).CopyTo(stackBuffer);
         stackBuffer.Slice(argumentCount, variableArgumentCount).CopyTo(stackBuffer);
-        ;
         stack.PopUntil(top);
         stack.PopUntil(top);
     }
     }
 
 

+ 17 - 16
src/Lua/Runtime/Tracebacks.cs

@@ -3,20 +3,21 @@ using Lua.Internal;
 
 
 namespace Lua.Runtime;
 namespace Lua.Runtime;
 
 
-public class Traceback(LuaState state)
+public class Traceback(LuaState state, ReadOnlySpan<CallStackFrame> stackFrames)
 {
 {
     public LuaState State => state;
     public LuaState State => state;
-    public required LuaFunction RootFunc { get; init; }
-    public required CallStackFrame[] StackFrames { get; init; }
+    public LuaFunction RootFunc => StackFrames[0].Function;
+    readonly CallStackFrame[] stackFramesArray = stackFrames.ToArray();
+    public ReadOnlySpan<CallStackFrame> StackFrames => stackFramesArray;
 
 
     internal void WriteLastLuaTrace(ref PooledList<char> list)
     internal void WriteLastLuaTrace(ref PooledList<char> list)
     {
     {
         var intFormatBuffer = (stackalloc char[15]);
         var intFormatBuffer = (stackalloc char[15]);
         var shortSourceBuffer = (stackalloc char[59]);
         var shortSourceBuffer = (stackalloc char[59]);
-        var stackFrames = StackFrames.AsSpan();
-        for (var index = stackFrames.Length - 1; index >= 0; index--)
+        var stackFrames = StackFrames;
+        for (var index = stackFrames.Length - 1; index >= 1; index--)
         {
         {
-            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
+            LuaFunction lastFunc = stackFrames[index - 1].Function;
             var frame = stackFrames[index];
             var frame = stackFrames[index];
             if (!frame.IsTailCall && lastFunc is LuaClosure closure)
             if (!frame.IsTailCall && lastFunc is LuaClosure closure)
             {
             {
@@ -43,10 +44,10 @@ public class Traceback(LuaState state)
     {
     {
         get
         get
         {
         {
-            var stackFrames = StackFrames.AsSpan();
-            for (var index = stackFrames.Length - 1; index >= 0; index--)
+            var stackFrames = StackFrames;
+            for (var index = stackFrames.Length - 1; index >= 1; index--)
             {
             {
-                LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
+                LuaFunction lastFunc = stackFrames[index - 1].Function;
                 var frame = stackFrames[index];
                 var frame = stackFrames[index];
                 if (!frame.IsTailCall && lastFunc is LuaClosure closure)
                 if (!frame.IsTailCall && lastFunc is LuaClosure closure)
                 {
                 {
@@ -68,7 +69,7 @@ public class Traceback(LuaState state)
 
 
     public override string ToString()
     public override string ToString()
     {
     {
-        return GetTracebackString(State, RootFunc, StackFrames, LuaValue.Nil);
+        return GetTracebackString(State, StackFrames, LuaValue.Nil);
     }
     }
 
 
     public string ToString(int skipFrames)
     public string ToString(int skipFrames)
@@ -78,10 +79,10 @@ public class Traceback(LuaState state)
             return "stack traceback:\n";
             return "stack traceback:\n";
         }
         }
 
 
-        return GetTracebackString(State, RootFunc, StackFrames[..^skipFrames], LuaValue.Nil);
+        return GetTracebackString(State, StackFrames[..^skipFrames], LuaValue.Nil);
     }
     }
 
 
-    internal static string GetTracebackString(LuaState state, LuaFunction rootFunc, ReadOnlySpan<CallStackFrame> stackFrames, LuaValue message, bool skipFirstCsharpCall = false)
+    internal static string GetTracebackString(LuaState state, ReadOnlySpan<CallStackFrame> stackFrames, LuaValue message, bool skipFirstCsharpCall = false)
     {
     {
         using var list = new PooledList<char>(64);
         using var list = new PooledList<char>(64);
         if (message.Type is not LuaValueType.Nil)
         if (message.Type is not LuaValueType.Nil)
@@ -102,9 +103,9 @@ public class Traceback(LuaState state)
             }
             }
         }
         }
 
 
-        for (var index = stackFrames.Length - 1; index >= 0; index--)
+        for (var index = stackFrames.Length - 1; index >= 1; index--)
         {
         {
-            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : rootFunc;
+            LuaFunction lastFunc = stackFrames[index - 1].Function;
             if (lastFunc is not null and not LuaClosure)
             if (lastFunc is not null and not LuaClosure)
             {
             {
                 list.AddRange("\t[C#]: in function '");
                 list.AddRange("\t[C#]: in function '");
@@ -166,8 +167,8 @@ public class Traceback(LuaState state)
                     }
                     }
                 }
                 }
 
 
-                var caller = index > 1 ? stackFrames[index - 2].Function : rootFunc;
-                if (index > 0 && caller is LuaClosure callerClosure)
+                var caller = index > 1 ? stackFrames[index - 2].Function : stackFrames[0].Function;
+                if (index > 1 && caller is LuaClosure callerClosure)
                 {
                 {
                     var t = LuaDebug.GetFuncName(callerClosure.Proto, stackFrames[index - 1].CallerInstructionIndex, out var name);
                     var t = LuaDebug.GetFuncName(callerClosure.Proto, stackFrames[index - 1].CallerInstructionIndex, out var name);
                     if (t is not null)
                     if (t is not null)

+ 14 - 6
src/Lua/Standard/BasicLibrary.cs

@@ -243,7 +243,7 @@ public sealed class BasicLibrary
 
 
     public async ValueTask<int> PCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public async ValueTask<int> PCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
-        var frameCount = context.Thread.CallStack.Count;
+        var frameCount = context.Thread.CallStackFrameCount;
         var arg0 = context.GetArgument<LuaFunction>(0);
         var arg0 = context.GetArgument<LuaFunction>(0);
         try
         try
         {
         {
@@ -254,9 +254,10 @@ public sealed class BasicLibrary
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
-            context.Thread.CallStack.PopUntil(frameCount);
+            context.Thread.PopCallStackFrameUntil(frameCount);
             if (ex is LuaRuntimeException luaEx)
             if (ex is LuaRuntimeException luaEx)
             {
             {
+                luaEx.Forget();
                 return context.Return(false, luaEx.ErrorObject);
                 return context.Return(false, luaEx.ErrorObject);
             }
             }
             else
             else
@@ -543,7 +544,7 @@ public sealed class BasicLibrary
 
 
     public async ValueTask<int> XPCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public async ValueTask<int> XPCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
-        var frameCount = context.Thread.CallStack.Count;
+        var frameCount = context.Thread.CallStackFrameCount;
         var arg0 = context.GetArgument<LuaFunction>(0);
         var arg0 = context.GetArgument<LuaFunction>(0);
         var arg1 = context.GetArgument<LuaFunction>(1);
         var arg1 = context.GetArgument<LuaFunction>(1);
 
 
@@ -556,10 +557,17 @@ public sealed class BasicLibrary
         }
         }
         catch (Exception ex)
         catch (Exception ex)
         {
         {
-            context.Thread.CallStack.PopUntil(frameCount);
-            var error = ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message;
+            context.Thread.PopCallStackFrameUntil(frameCount);
+            if (ex is LuaRuntimeException luaEx)
+            {
+                luaEx.Forget();
+                context.Thread.Push(luaEx.ErrorObject);
+            }
+            else
+            {
+                context.Thread.Push(ex.Message);
+            }
 
 
-            context.Thread.Push(error);
 
 
             // invoke error handler
             // invoke error handler
             var count = await arg1.InvokeAsync(context with { ArgumentCount = 1, ReturnFrameBase = context.ReturnFrameBase + 1 }, cancellationToken);
             var count = await arg1.InvokeAsync(context with { ArgumentCount = 1, ReturnFrameBase = context.ReturnFrameBase + 1 }, cancellationToken);

+ 3 - 3
src/Lua/Standard/DebugLibrary.cs

@@ -353,8 +353,8 @@ public class DebugLibrary
         }
         }
 
 
         var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1);
         var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1);
-        var frames = callStack[1..^skipCount];
-        return new(context.Return(Runtime.Traceback.GetTracebackString(context.State, callStack[0].Function, frames, message, level == 1)));
+        var frames = callStack[..^skipCount];
+        return new(context.Return(Runtime.Traceback.GetTracebackString(context.State, frames, message, level == 1)));
     }
     }
 
 
     public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> GetRegistry(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -449,7 +449,7 @@ public class DebugLibrary
 
 
         if (thread.IsLineHookEnabled)
         if (thread.IsLineHookEnabled)
         {
         {
-            thread.LastPc = thread.CallStack.Count > 0 ? thread.GetCurrentFrame().CallerInstructionIndex : -1;
+            thread.LastPc = thread.CallStackFrameCount > 0 ? thread.GetCurrentFrame().CallerInstructionIndex : -1;
         }
         }
 
 
         thread.Hook = hook;
         thread.Hook = hook;

+ 17 - 16
tests/Lua.Tests/LuaApiTests.cs

@@ -38,7 +38,7 @@ public class LuaApiTests
         var a = result[0].Read<LuaTable>();
         var a = result[0].Read<LuaTable>();
         var b = result[1].Read<LuaTable>();
         var b = result[1].Read<LuaTable>();
 
 
-        var c = await state.MainThread.OpArithmetic(a, b, OpCode.Add);
+        var c = await state.MainThread.Arithmetic(a, b, OpCode.Add);
         var table = c.Read<LuaTable>();
         var table = c.Read<LuaTable>();
         Assert.Multiple(() =>
         Assert.Multiple(() =>
         {
         {
@@ -70,7 +70,7 @@ public class LuaApiTests
         var result = await state.DoStringAsync(source);
         var result = await state.DoStringAsync(source);
         var a = result[0].Read<LuaTable>();
         var a = result[0].Read<LuaTable>();
 
 
-        var c = await state.MainThread.OpUnary(a, OpCode.Unm);
+        var c = await state.MainThread.Unary(a, OpCode.Unm);
         var table = c.Read<LuaTable>();
         var table = c.Read<LuaTable>();
         Assert.Multiple(() =>
         Assert.Multiple(() =>
         {
         {
@@ -108,9 +108,9 @@ public class LuaApiTests
         var a = result[0].Read<LuaTable>();
         var a = result[0].Read<LuaTable>();
         var b = result[1].Read<LuaTable>();
         var b = result[1].Read<LuaTable>();
         var c = result[2].Read<LuaTable>();
         var c = result[2].Read<LuaTable>();
-        var ab = await state.MainThread.OpCompare(a, b, OpCode.Eq);
+        var ab = await state.MainThread.Compare(a, b, OpCode.Eq);
         Assert.False(ab);
         Assert.False(ab);
-        var ac = await state.MainThread.OpCompare(a, c, OpCode.Eq);
+        var ac = await state.MainThread.Compare(a, c, OpCode.Eq);
         Assert.True(ac);
         Assert.True(ac);
     }
     }
 
 
@@ -128,9 +128,9 @@ public class LuaApiTests
                      """;
                      """;
         var result = await state.DoStringAsync(source);
         var result = await state.DoStringAsync(source);
         var a = result[0].Read<LuaTable>();
         var a = result[0].Read<LuaTable>();
-        Assert.That(await state.MainThread.OpGetTable(a, "x"), Is.EqualTo(new LuaValue(1)));
+        Assert.That(await state.MainThread.GetTable(a, "x"), Is.EqualTo(new LuaValue(1)));
         a.Metatable!["__index"] = state.DoStringAsync("return function(a,b) return b end").Result[0];
         a.Metatable!["__index"] = state.DoStringAsync("return function(a,b) return b end").Result[0];
-        Assert.That(await state.MainThread.OpGetTable(a, "x"), Is.EqualTo(new LuaValue("x")));
+        Assert.That(await state.MainThread.GetTable(a, "x"), Is.EqualTo(new LuaValue("x")));
     }
     }
 
 
     [Test]
     [Test]
@@ -148,7 +148,7 @@ public class LuaApiTests
                      """;
                      """;
         var result = await state.DoStringAsync(source);
         var result = await state.DoStringAsync(source);
         var a = result[0].Read<LuaTable>();
         var a = result[0].Read<LuaTable>();
-        await state.MainThread.OpSetTable(a, "a", "b");
+        await state.MainThread.SetTable(a, "a", "b");
         var b = a.Metatable!["__newindex"].Read<LuaTable>()["a"];
         var b = a.Metatable!["__newindex"].Read<LuaTable>()["a"];
         Assert.True(b.Read<string>() == "b");
         Assert.True(b.Read<string>() == "b");
     }
     }
@@ -185,7 +185,7 @@ return a,b,c
         var a = result[0];
         var a = result[0];
         var b = result[1];
         var b = result[1];
         var c = result[2];
         var c = result[2];
-        var d = await state.MainThread.OpConcat([a, b, c]);
+        var d = await state.MainThread.Concat([a, b, c]);
 
 
         var table = d.Read<LuaTable>();
         var table = d.Read<LuaTable>();
         Assert.That(table.ArrayLength, Is.EqualTo(9));
         Assert.That(table.ArrayLength, Is.EqualTo(9));
@@ -228,20 +228,21 @@ return a,b,c
         var a = result[0];
         var a = result[0];
         var b = result[1];
         var b = result[1];
         var c = result[2];
         var c = result[2];
-        var d = await state.MainThread.OpArithmetic(b, c, OpCode.Add);
+        var d = await state.MainThread.Arithmetic(b, c, OpCode.Add);
         Assert.True(d.TryRead(out string s));
         Assert.True(d.TryRead(out string s));
         Assert.That(s, Is.EqualTo("abc"));
         Assert.That(s, Is.EqualTo("abc"));
-        d = await state.MainThread.OpUnary(b, OpCode.Unm);
+        d = await state.MainThread.Unary(b, OpCode.Unm);
         Assert.True(d.TryRead(out s));
         Assert.True(d.TryRead(out s));
         Assert.That(s, Is.EqualTo("abb"));
         Assert.That(s, Is.EqualTo("abb"));
-        d = await state.MainThread.OpConcat([c, b]);
+        d = await state.MainThread.Concat([c, b]);
         Assert.True(d.TryRead(out s));
         Assert.True(d.TryRead(out s));
         Assert.That(s, Is.EqualTo("acb"));
         Assert.That(s, Is.EqualTo("acb"));
 
 
-        var aResult = await state.MainThread.OpCall(a, [b, c]);
+        var aResult = await state.MainThread.Call(a, [b, c]);
         Assert.That(aResult, Has.Length.EqualTo(1));
         Assert.That(aResult, Has.Length.EqualTo(1));
         Assert.That(aResult[0].Read<string>(), Is.EqualTo("abc"));
         Assert.That(aResult[0].Read<string>(), Is.EqualTo("abc"));
     }
     }
+
     [Test]
     [Test]
     public async Task Test_Metamethod_MetaCallViaMeta_VarArg()
     public async Task Test_Metamethod_MetaCallViaMeta_VarArg()
     {
     {
@@ -269,17 +270,17 @@ return a,b,c
         var a = result[0];
         var a = result[0];
         var b = result[1];
         var b = result[1];
         var c = result[2];
         var c = result[2];
-        var d = await state.MainThread.OpArithmetic(b, c, OpCode.Add);
+        var d = await state.MainThread.Arithmetic(b, c, OpCode.Add);
         Assert.True(d.TryRead(out string s));
         Assert.True(d.TryRead(out string s));
         Assert.That(s, Is.EqualTo("abc"));
         Assert.That(s, Is.EqualTo("abc"));
-        d = await state.MainThread.OpUnary(b, OpCode.Unm);
+        d = await state.MainThread.Unary(b, OpCode.Unm);
         Assert.True(d.TryRead(out s));
         Assert.True(d.TryRead(out s));
         Assert.That(s, Is.EqualTo("abb"));
         Assert.That(s, Is.EqualTo("abb"));
-        d = await state.MainThread.OpConcat([c, b]);
+        d = await state.MainThread.Concat([c, b]);
         Assert.True(d.TryRead(out s));
         Assert.True(d.TryRead(out s));
         Assert.That(s, Is.EqualTo("acb"));
         Assert.That(s, Is.EqualTo("acb"));
 
 
-        var aResult = await state.MainThread.OpCall(a, [b, c]);
+        var aResult = await state.MainThread.Call(a, [b, c]);
         Assert.That(aResult, Has.Length.EqualTo(1));
         Assert.That(aResult, Has.Length.EqualTo(1));
         Assert.That(aResult[0].Read<string>(), Is.EqualTo("abc"));
         Assert.That(aResult[0].Read<string>(), Is.EqualTo("abc"));
     }
     }

+ 8 - 1
tests/Lua.Tests/LuaTests.cs

@@ -43,8 +43,15 @@ public class LuaTests
         }
         }
         catch (LuaRuntimeException e)
         catch (LuaRuntimeException e)
         {
         {
-            var line = e.LuaTraceback!.LastLine;
+            var luaTraceback = e.LuaTraceback;
+            if (luaTraceback == null)
+            {
+                throw;
+            }
+
+            var line = luaTraceback.LastLine;
             throw new Exception($"{path}:line {line}\n{e.InnerException}\n {e}");
             throw new Exception($"{path}:line {line}\n{e.InnerException}\n {e}");
+
         }
         }
     }
     }
 }
 }