Browse Source

Lazy stack trace

Akeit0 7 months ago
parent
commit
5dda75ce23

+ 3 - 3
src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs

@@ -262,7 +262,7 @@ partial class LuaObjectGenerator
                     {
                         if (propertyMetadata.IsReadOnly)
                         {
-                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.Thread.GetTraceback(), $""'{{key}}' cannot overwrite."");");
+                            builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.Thread, $""'{{key}}' cannot overwrite."");");
                         }
                         else if (propertyMetadata.IsStatic)
                         {
@@ -284,7 +284,7 @@ partial class LuaObjectGenerator
 
                     using (builder.BeginIndentScope())
                     {
-                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.Thread.GetTraceback(), $""'{{key}}' cannot overwrite."");");
+                        builder.AppendLine($@"throw new global::Lua.LuaRuntimeException(context.Thread, $""'{{key}}' cannot overwrite."");");
                     }
                 }
 
@@ -292,7 +292,7 @@ partial class LuaObjectGenerator
 
                 using (builder.BeginIndentScope())
                 {
-                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.Thread.GetTraceback(), $""'{{key}}' not found."");");
+                    builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.Thread, $""'{{key}}' not found."");");
                 }
             }
 

+ 44 - 17
src/Lua/Exceptions.cs

@@ -64,60 +64,61 @@ public class LuaUnDumpException(string message) : LuaException(message);
 
 public class LuaRuntimeException : LuaException
 {
-    public LuaRuntimeException(Traceback traceback, Exception innerException) : base(innerException)
+    public LuaRuntimeException(LuaThread? thread, Exception innerException) : base(innerException)
     {
-        LuaTraceback = traceback;
+        Thread = thread;
     }
 
-    public LuaRuntimeException(Traceback traceback, LuaValue errorObject) : base(CreateMessage(traceback, errorObject))
+    public LuaRuntimeException(LuaThread? thread, LuaValue errorObject)
     {
-        LuaTraceback = traceback;
+        Thread = thread;
         ErrorObject = errorObject;
     }
 
-    public Traceback LuaTraceback { get; }
+    public Traceback? LuaTraceback { get; private set; }
+    internal LuaThread? Thread { get; private set; } = default!;
     public LuaValue ErrorObject { get; }
 
-    public static void AttemptInvalidOperation(Traceback traceback, string op, LuaValue a, LuaValue b)
+    public static void AttemptInvalidOperation(LuaThread? traceback, string op, LuaValue a, LuaValue b)
     {
         throw new LuaRuntimeException(traceback, $"attempt to {op} a '{a.Type}' with a '{b.Type}'");
     }
 
-    public static void AttemptInvalidOperation(Traceback traceback, string op, LuaValue a)
+    public static void AttemptInvalidOperation(LuaThread? traceback, string op, LuaValue a)
     {
         throw new LuaRuntimeException(traceback, $"attempt to {op} a '{a.Type}' value");
     }
 
-    public static void BadArgument(Traceback traceback, int argumentId, string functionName)
+    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName)
     {
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (value expected)");
     }
 
-    public static void BadArgument(Traceback traceback, int argumentId, string functionName, LuaValueType[] expected)
+    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName, LuaValueType[] expected)
     {
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({string.Join(" or ", expected)} expected)");
     }
 
-    public static void BadArgument(Traceback traceback, int argumentId, string functionName, string expected, string actual)
+    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName, string expected, string actual)
     {
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({expected} expected, got {actual})");
     }
 
-    public static void BadArgument(Traceback traceback, int argumentId, string functionName, string message)
+    public static void BadArgument(LuaThread? traceback, int argumentId, string functionName, string message)
     {
         throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({message})");
     }
 
-    public static void BadArgumentNumberIsNotInteger(Traceback traceback, int argumentId, string functionName)
+    public static void BadArgumentNumberIsNotInteger(LuaThread? thread, int argumentId, string functionName)
     {
-        throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");
+        throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");
     }
 
-    public static void ThrowBadArgumentIfNumberIsNotInteger(LuaThread thread, string functionName, int argumentId, double value)
+    public static void ThrowBadArgumentIfNumberIsNotInteger(LuaThread? thread, string functionName, int argumentId, double value)
     {
         if (!MathEx.IsInteger(value))
         {
-            BadArgumentNumberIsNotInteger(thread.GetTraceback(), argumentId, functionName);
+            BadArgumentNumberIsNotInteger(thread, argumentId, functionName);
         }
     }
 
@@ -139,14 +140,40 @@ public class LuaRuntimeException : LuaException
         }
     }
 
+    internal void BuildWithPop(int top)
+    {
+        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);
+        }
+    }
+
+    public override string Message
+    {
+        get
+        {
+            if (InnerException != null) return InnerException.Message;
+            if (LuaTraceback == null)
+            {
+                return ErrorObject.ToString();
+            }
+
+            return CreateMessage(LuaTraceback, ErrorObject);
+        }
+    }
 
     public override string ToString()
     {
+        if (LuaTraceback == null)
+            return base.ToString();
         var pooledList = new PooledList<char>(64);
         pooledList.Clear();
         try
         {
-            pooledList.AddRange(base.Message);
+            pooledList.AddRange(Message);
             pooledList.Add('\n');
             pooledList.AddRange(LuaTraceback.ToString());
             pooledList.Add('\n');
@@ -161,7 +188,7 @@ public class LuaRuntimeException : LuaException
     }
 }
 
-public class LuaAssertionException(Traceback traceback, string message) : LuaRuntimeException(traceback, message)
+public class LuaAssertionException(LuaThread? traceback, string message) : LuaRuntimeException(traceback, message)
 {
     // public override string ToString()
     // {

+ 6 - 0
src/Lua/Internal/FastStackCore.cs

@@ -83,6 +83,12 @@ public struct FastStackCore<T>
         return result;
     }
 
+    public void PopUntil(int top)
+    {
+        tail = top;
+        array.AsSpan(top, tail).Clear();
+    }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public bool TryPeek(out T value)
     {

+ 10 - 12
src/Lua/LuaCoroutine.cs

@@ -120,8 +120,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                     }
                     else
                     {
-                        if (baseThread != null) throw new LuaRuntimeException(baseThread.GetTraceback(), "cannot resume non-suspended coroutine");
-                        else throw new LuaException("cannot resume non-suspended coroutine");
+                        throw new LuaRuntimeException(baseThread, "cannot resume non-suspended coroutine");
                     }
                 case LuaThreadStatus.Dead:
                     if (IsProtectedMode)
@@ -133,8 +132,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                     }
                     else
                     {
-                        if (baseThread != null) throw new LuaRuntimeException(baseThread.GetTraceback(), "cannot resume dead coroutine");
-                        else throw new LuaException("cannot resume dead coroutine");
+                        throw new LuaRuntimeException(baseThread, "cannot resume dead coroutine");
                     }
             }
 
@@ -189,7 +187,12 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
             {
                 if (IsProtectedMode)
                 {
-                    traceback = (ex as LuaRuntimeException)?.LuaTraceback;
+                    if (ex is LuaRuntimeException luaRuntimeException)
+                    {
+                        luaRuntimeException.BuildWithPop(0);
+                        traceback = luaRuntimeException.LuaTraceback;
+                    }
+
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
                     ReleaseCore();
                     stack.PopUntil(returnBase);
@@ -233,19 +236,14 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
     {
         if (Volatile.Read(ref status) != (byte)LuaThreadStatus.Running)
         {
-            if (baseThread != null)
-            {
-                throw new LuaRuntimeException(baseThread.GetTraceback(), "cannot yield from a non-running coroutine");
-            }
-
-            throw new LuaException("cannot call yield on a coroutine that is not currently running");
+            throw new LuaRuntimeException(baseThread, "cannot yield from a non-running coroutine");
         }
 
         if (baseThread != null)
         {
             if (baseThread.GetCallStackFrames()[^2].Function is not LuaClosure)
             {
-                throw new LuaRuntimeException(baseThread.GetTraceback(), "attempt to yield across a C#-call boundary");
+                throw new LuaRuntimeException(baseThread, "attempt to yield across a C#-call boundary");
             }
         }
 

+ 6 - 10
src/Lua/LuaFunction.cs

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

+ 10 - 10
src/Lua/LuaFunctionExecutionContext.cs

@@ -60,19 +60,19 @@ public readonly record struct LuaFunctionExecutionContext
             var t = typeof(T);
             if ((t == typeof(int) || t == typeof(long)) && arg.TryReadNumber(out _))
             {
-                LuaRuntimeException.BadArgumentNumberIsNotInteger(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+                LuaRuntimeException.BadArgumentNumberIsNotInteger(Thread, index + 1, Thread.GetCurrentFrame().Function.Name);
             }
             else if (LuaValue.TryGetLuaValueType(t, out var type))
             {
-                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData)
             {
-                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
+                LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
             }
             else
             {
-                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
             }
         }
 
@@ -99,19 +99,19 @@ public readonly record struct LuaFunctionExecutionContext
             var t = typeof(T);
             if ((t == typeof(int) || t == typeof(long)) && arg.TryReadNumber(out _))
             {
-                LuaRuntimeException.BadArgumentNumberIsNotInteger(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+                LuaRuntimeException.BadArgumentNumberIsNotInteger(Thread, index + 1, Thread.GetCurrentFrame().Function.Name);
             }
             else if (LuaValue.TryGetLuaValueType(t, out var type))
             {
-                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString());
             }
             else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData)
             {
-                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
+                LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead<object>()?.GetType().ToString() ?? "userdata: 0");
             }
             else
             {
-                LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
+                LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString());
             }
         }
 
@@ -186,14 +186,14 @@ public readonly record struct LuaFunctionExecutionContext
 
     internal void ThrowBadArgument(int index, string message)
     {
-        LuaRuntimeException.BadArgument(Thread.GetTraceback(), index, Thread.GetCurrentFrame().Function.Name, message);
+        LuaRuntimeException.BadArgument(Thread, index, Thread.GetCurrentFrame().Function.Name, message);
     }
 
     void ThrowIfArgumentNotExists(int index)
     {
         if (ArgumentCount <= index)
         {
-            LuaRuntimeException.BadArgument(Thread.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name);
+            LuaRuntimeException.BadArgument(Thread, index + 1, Thread.GetCurrentFrame().Function.Name);
         }
     }
 }

+ 6 - 1
src/Lua/LuaThread.cs

@@ -22,7 +22,7 @@ public abstract class LuaThread
 
     public virtual ValueTask<int> YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default)
     {
-        throw new LuaRuntimeException(context.Thread.GetTraceback(), "attempt to yield from outside a coroutine");
+        throw new LuaRuntimeException(context.Thread, "attempt to yield from outside a coroutine");
     }
 
     internal class ThreadCoreData : IPoolNode<ThreadCoreData>
@@ -110,6 +110,11 @@ public abstract class LuaThread
 
             return Stack.Count;
         }
+        catch (LuaRuntimeException e)
+        {
+            e.BuildWithPop(0);
+            throw;
+        }
         finally
         {
             IsRunning = false;

+ 0 - 1
src/Lua/LuaThreadExtensions.cs

@@ -129,5 +129,4 @@ public static class LuaThreadExtensions
         var coreData = thread.CoreData!;
         coreData!.CallStack.Pop();
     }
-    
 }

+ 1 - 1
src/Lua/LuaValue.cs

@@ -596,7 +596,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
         {
             if (!metamethod.TryReadFunction(out var func))
             {
-                LuaRuntimeException.AttemptInvalidOperation(context.Thread.GetTraceback(), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(context.Thread, "call", metamethod);
             }
 
             var stack = context.Thread.Stack;

+ 1 - 1
src/Lua/Runtime/LuaVirtualMachine.Debug.cs

@@ -137,11 +137,11 @@ public static partial class LuaVirtualMachine
             {
                 context.Thread.IsInHook = true;
                 await hook.Func(funcContext, cancellationToken);
+                context.Thread.PopCallStackFrameWithStackPop();
             }
             finally
             {
                 context.Thread.IsInHook = false;
-                context.Thread.PopCallStackFrameWithStackPop();
             }
         }
 

+ 24 - 28
src/Lua/Runtime/LuaVirtualMachine.cs

@@ -767,12 +767,10 @@ public static partial class LuaVirtualMachine
             context.State.CloseUpValues(context.Thread, context.FrameBase);
             if (e is not LuaRuntimeException)
             {
-                var newException = new LuaRuntimeException(context.Thread.GetTraceback(), e);
-                context.PopOnTopCallStackFrames();
+                var newException = new LuaRuntimeException(context.Thread, e);
                 throw newException;
             }
 
-            context.PopOnTopCallStackFrames();
             throw;
         }
     }
@@ -780,12 +778,12 @@ public static partial class LuaVirtualMachine
 
     static void ThrowLuaRuntimeException(VirtualMachineExecutionContext context, string message)
     {
-        throw new LuaRuntimeException(context.Thread.GetTraceback(), message);
+        throw new LuaRuntimeException(context.Thread, message);
     }
 
     static void ThrowLuaNotImplementedException(VirtualMachineExecutionContext context, OpCode opcode)
     {
-        throw new LuaRuntimeException(context.Thread.GetTraceback(), $"OpCode {opcode} is not implemented");
+        throw new LuaRuntimeException(context.Thread, $"OpCode {opcode} is not implemented");
     }
 
 
@@ -807,7 +805,6 @@ public static partial class LuaVirtualMachine
     {
         var instruction = context.Instruction;
         var stack = context.Stack;
-        var top = stack.Count - 1;
         var b = instruction.B;
         var c = instruction.C;
         stack.NotifyTop(context.FrameBase + c + 1);
@@ -904,7 +901,7 @@ public static partial class LuaVirtualMachine
         {
             if (!metamethod.TryReadFunction(out var func))
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", metamethod);
             }
 
             var stack = context.Stack;
@@ -928,7 +925,7 @@ public static partial class LuaVirtualMachine
             return;
         }
 
-        LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), description, vb, vc);
+        LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), description, vb, vc);
         return;
     }
 
@@ -950,7 +947,7 @@ public static partial class LuaVirtualMachine
             }
             else
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", va);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", va);
             }
         }
 
@@ -1050,7 +1047,7 @@ public static partial class LuaVirtualMachine
             }
             else
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", metamethod);
             }
         }
 
@@ -1118,7 +1115,7 @@ public static partial class LuaVirtualMachine
             }
             else
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", metamethod);
             }
         }
 
@@ -1266,7 +1263,7 @@ public static partial class LuaVirtualMachine
 
             if (!table.TryGetMetamethod(context.State, Metamethods.Index, out var metatableValue))
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "index", table);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "index", table);
             }
 
             table = metatableValue;
@@ -1277,7 +1274,7 @@ public static partial class LuaVirtualMachine
             }
         }
 
-        throw new LuaRuntimeException(GetTracebacks(context), "loop in gettable");
+        throw new LuaRuntimeException(GetThreadWithCurrentPc(context), "loop in gettable");
     }
 
     [MethodImpl(MethodImplOptions.NoInlining)]
@@ -1364,7 +1361,7 @@ public static partial class LuaVirtualMachine
 
             if (!table.TryGetMetamethod(context.State, Metamethods.NewIndex, out var metatableValue))
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "index", table);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "index", table);
             }
 
             table = metatableValue;
@@ -1377,7 +1374,7 @@ public static partial class LuaVirtualMachine
             }
         }
 
-        throw new LuaRuntimeException(GetTracebacks(context), "loop in settable");
+        throw new LuaRuntimeException(GetThreadWithCurrentPc(context), "loop in settable");
     }
 
     [MethodImpl(MethodImplOptions.NoInlining)]
@@ -1432,7 +1429,7 @@ public static partial class LuaVirtualMachine
         {
             if (!metamethod.TryReadFunction(out var func))
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", metamethod);
             }
 
             var stack = context.Stack;
@@ -1479,7 +1476,7 @@ public static partial class LuaVirtualMachine
             return true;
         }
 
-        LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), description, vb, vc);
+        LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), description, vb, vc);
         return false;
     }
 
@@ -1494,7 +1491,7 @@ public static partial class LuaVirtualMachine
         {
             if (!metamethod.TryReadFunction(out var func))
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", metamethod);
             }
 
             stack.Push(vb);
@@ -1543,7 +1540,7 @@ public static partial class LuaVirtualMachine
             return true;
         }
 
-        LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), description, vb);
+        LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), description, vb);
         return true;
     }
 
@@ -1560,7 +1557,7 @@ public static partial class LuaVirtualMachine
         {
             if (!metamethod.TryReadFunction(out var func))
             {
-                LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), "call", metamethod);
             }
 
             var stack = context.Stack;
@@ -1622,7 +1619,7 @@ public static partial class LuaVirtualMachine
                 (vb, vc) = (vc, vb);
             }
 
-            LuaRuntimeException.AttemptInvalidOperation(GetTracebacks(context), description, vb, vc);
+            LuaRuntimeException.AttemptInvalidOperation(GetThreadWithCurrentPc(context), description, vb, vc);
         }
         else
         {
@@ -1699,7 +1696,7 @@ public static partial class LuaVirtualMachine
             argumentCount -= variableArgumentCount;
             variableArgumentCount = 0;
         }
-        
+
         if (variableArgumentCount == 0)
         {
             return (argumentCount, 0);
@@ -1750,18 +1747,17 @@ public static partial class LuaVirtualMachine
         return PrepareVariableArgument(thread.Stack, newBase, argumentCount, variableArgumentCount);
     }
 
-    static Traceback GetTracebacks(VirtualMachineExecutionContext context)
+    static LuaThread GetThreadWithCurrentPc(VirtualMachineExecutionContext context)
     {
-        return GetTracebacks(context.Thread, context.Pc);
+        GetThreadWithCurrentPc(context.Thread, context.Pc);
+        return context.Thread;
     }
 
-    static Traceback GetTracebacks(LuaThread thread, int pc)
+
+    static void GetThreadWithCurrentPc(LuaThread thread, int pc)
     {
         var frame = thread.GetCurrentFrame();
         thread.PushCallStackFrame(frame with { CallerInstructionIndex = pc });
-        var tracebacks = thread.GetTraceback();
-        thread.PopCallStackFrameWithStackPop();
-        return tracebacks;
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 15 - 12
src/Lua/Standard/BasicLibrary.cs

@@ -71,7 +71,7 @@ public sealed class BasicLibrary
                 message = context.GetArgument<string>(1);
             }
 
-            throw new LuaAssertionException(context.Thread.GetTraceback(), message);
+            throw new LuaAssertionException(context.Thread, message);
         }
 
         return new(context.Return(context.Arguments));
@@ -100,8 +100,7 @@ public sealed class BasicLibrary
             ? LuaValue.Nil
             : context.Arguments[0];
 
-        var traceback = context.Thread.GetTraceback();
-        throw new LuaRuntimeException(traceback, value);
+        throw new LuaRuntimeException(context.Thread, value);
     }
 
     public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -136,7 +135,7 @@ public sealed class BasicLibrary
         {
             if (!metamethod.TryRead<LuaFunction>(out var function))
             {
-                LuaRuntimeException.AttemptInvalidOperation(context.Thread.GetTraceback(), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(context.Thread, "call", metamethod);
             }
 
             return function.InvokeAsync(context, cancellationToken);
@@ -200,7 +199,7 @@ public sealed class BasicLibrary
             }
             else
             {
-                LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 1, "load");
+                LuaRuntimeException.BadArgument(context.Thread, 1, "load");
                 return default; // dummy
             }
         }
@@ -234,7 +233,7 @@ public sealed class BasicLibrary
         {
             if (!metamethod.TryRead<LuaFunction>(out var function))
             {
-                LuaRuntimeException.AttemptInvalidOperation(context.Thread.GetTraceback(), "call", metamethod);
+                LuaRuntimeException.AttemptInvalidOperation(context.Thread, "call", metamethod);
             }
 
             return function.InvokeAsync(context, cancellationToken);
@@ -245,6 +244,7 @@ public sealed class BasicLibrary
 
     public async ValueTask<int> PCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
+        var frameCount = context.Thread.CallStack.Count;
         var arg0 = context.GetArgument<LuaFunction>(0);
         try
         {
@@ -255,6 +255,7 @@ public sealed class BasicLibrary
         }
         catch (Exception ex)
         {
+            context.Thread.CallStack.PopUntil(frameCount);
             if (ex is LuaRuntimeException luaEx)
             {
                 return context.Return(false, luaEx.ErrorObject);
@@ -308,7 +309,7 @@ public sealed class BasicLibrary
         }
         else
         {
-            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "rawlen", [LuaValueType.String, LuaValueType.Table]);
+            LuaRuntimeException.BadArgument(context.Thread, 2, "rawlen", [LuaValueType.String, LuaValueType.Table]);
             return default;
         }
     }
@@ -331,7 +332,7 @@ public sealed class BasicLibrary
         {
             if (Math.Abs(index) > context.ArgumentCount)
             {
-                throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #1 to 'select' (index out of range)");
+                throw new LuaRuntimeException(context.Thread, "bad argument #1 to 'select' (index out of range)");
             }
 
             var span = index >= 0
@@ -346,7 +347,7 @@ public sealed class BasicLibrary
         }
         else
         {
-            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 1, "select", "number", arg0.Type.ToString());
+            LuaRuntimeException.BadArgument(context.Thread, 1, "select", "number", arg0.Type.ToString());
             return default;
         }
     }
@@ -358,12 +359,12 @@ public sealed class BasicLibrary
 
         if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
         {
-            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
+            LuaRuntimeException.BadArgument(context.Thread, 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
         }
 
         if (arg0.Metatable != null && arg0.Metatable.TryGetValue(Metamethods.Metatable, out _))
         {
-            throw new LuaRuntimeException(context.Thread.GetTraceback(), "cannot change a protected metatable");
+            throw new LuaRuntimeException(context.Thread, "cannot change a protected metatable");
         }
         else if (arg1.Type is LuaValueType.Nil)
         {
@@ -387,7 +388,7 @@ public sealed class BasicLibrary
 
         if (toBase != null && (toBase < 2 || toBase > 36))
         {
-            throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #2 to 'tonumber' (base out of range)");
+            throw new LuaRuntimeException(context.Thread, "bad argument #2 to 'tonumber' (base out of range)");
         }
 
         double? value = null;
@@ -543,6 +544,7 @@ public sealed class BasicLibrary
 
     public async ValueTask<int> XPCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
+        var frameCount = context.Thread.CallStack.Count;
         var arg0 = context.GetArgument<LuaFunction>(0);
         var arg1 = context.GetArgument<LuaFunction>(1);
 
@@ -555,6 +557,7 @@ public sealed class BasicLibrary
         }
         catch (Exception ex)
         {
+            context.Thread.CallStack.PopUntil(frameCount);
             var error = ex is LuaRuntimeException luaEx ? luaEx.ErrorObject : ex.Message;
 
             context.Thread.Push(error);

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

@@ -277,7 +277,7 @@ public class DebugLibrary
 
         if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table))
         {
-            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
+            LuaRuntimeException.BadArgument(context.Thread, 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]);
         }
 
         context.State.SetMetatable(arg0, arg1.UnsafeRead<LuaTable>());

+ 1 - 1
src/Lua/Standard/FileHandle.cs

@@ -197,7 +197,7 @@ public class FileHandle : ILuaUserData
 
         if (whence is not ("set" or "cur" or "end"))
         {
-            throw new LuaRuntimeException(context.Thread.GetTraceback(), $"bad argument #2 to 'seek' (invalid option '{whence}')");
+            throw new LuaRuntimeException(context.Thread, $"bad argument #2 to 'seek' (invalid option '{whence}')");
         }
 
         try

+ 3 - 3
src/Lua/Standard/Internal/Bit32Helper.cs

@@ -21,12 +21,12 @@ internal static class Bit32Helper
     public static void ValidateFieldAndWidth(LuaThread thread, string functionName, int argumentId, int field, int width)
     {
         if (field > 31 || (field + width) > 32)
-            throw new LuaRuntimeException(thread.GetTraceback(), "trying to access non-existent bits");
+            throw new LuaRuntimeException(thread, "trying to access non-existent bits");
 
         if (field < 0)
-            throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #{argumentId} to '{functionName}' (field cannot be negative)");
+            throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' (field cannot be negative)");
 
         if (width <= 0)
-            throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #{argumentId} to '{functionName}' (width must be positive)");
+            throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' (width must be positive)");
     }
 }

+ 3 - 3
src/Lua/Standard/Internal/DateTimeHelper.cs

@@ -34,7 +34,7 @@ internal static class DateTimeHelper
             {
                 if (required)
                 {
-                    throw new LuaRuntimeException(thread.GetTraceback(), $"field '{key}' missing in date table");
+                    throw new LuaRuntimeException(thread, $"field '{key}' missing in date table");
                 }
                 else
                 {
@@ -47,7 +47,7 @@ internal static class DateTimeHelper
                 return (int)d;
             }
 
-            throw new LuaRuntimeException(thread.GetTraceback(), $"field '{key}' is not an integer");
+            throw new LuaRuntimeException(thread, $"field '{key}' is not an integer");
         }
 
         var day = GetTimeField(thread, table, "day");
@@ -186,7 +186,7 @@ internal static class DateTimeHelper
             }
             else
             {
-                throw new LuaRuntimeException(thread.GetTraceback(), $"bad argument #1 to 'date' (invalid conversion specifier '{format.ToString()}')");
+                throw new LuaRuntimeException(thread, $"bad argument #1 to 'date' (invalid conversion specifier '{format.ToString()}')");
             }
         }
 

+ 3 - 3
src/Lua/Standard/Internal/IOHelper.cs

@@ -12,7 +12,7 @@ internal static class IOHelper
             "r" or "rb" or "r+" or "r+b" => FileMode.Open,
             "w" or "wb" or "w+" or "w+b" => FileMode.Create,
             "a" or "ab" or "a+" or "a+b" => FileMode.Append,
-            _ => throw new LuaRuntimeException(thread.GetTraceback(), "bad argument #2 to 'open' (invalid mode)"),
+            _ => throw new LuaRuntimeException(thread, "bad argument #2 to 'open' (invalid mode)"),
         };
 
         var fileAccess = mode switch
@@ -64,7 +64,7 @@ internal static class IOHelper
                 }
                 else
                 {
-                    LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), i + 1, name);
+                    LuaRuntimeException.BadArgument(context.Thread, i + 1, name);
                 }
             }
         }
@@ -142,7 +142,7 @@ internal static class IOHelper
                 }
                 else
                 {
-                    LuaRuntimeException.BadArgument(thread.GetTraceback(), i + 1, name);
+                    LuaRuntimeException.BadArgument(thread, i + 1, name);
                 }
             }
 

+ 1 - 1
src/Lua/Standard/OperatingSystemLibrary.cs

@@ -121,7 +121,7 @@ public sealed class OperatingSystemLibrary
             }
             else
             {
-                LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), 1, "exit", LuaValueType.Nil.ToString(), code.Type.ToString());
+                LuaRuntimeException.BadArgument(context.Thread, 1, "exit", LuaValueType.Nil.ToString(), code.Type.ToString());
             }
         }
         else

+ 13 - 13
src/Lua/Standard/StringLibrary.cs

@@ -174,23 +174,23 @@ public sealed class StringLibrary
                     switch (c)
                     {
                         case '-':
-                            if (leftJustify) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
+                            if (leftJustify) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
                             leftJustify = true;
                             break;
                         case '+':
-                            if (plusSign) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
+                            if (plusSign) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
                             plusSign = true;
                             break;
                         case '0':
-                            if (zeroPadding) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
+                            if (zeroPadding) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
                             zeroPadding = true;
                             break;
                         case '#':
-                            if (alternateForm) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
+                            if (alternateForm) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
                             alternateForm = true;
                             break;
                         case ' ':
-                            if (blank) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (repeated flags)");
+                            if (blank) throw new LuaRuntimeException(context.Thread, "invalid format (repeated flags)");
                             blank = true;
                             break;
                         default:
@@ -208,7 +208,7 @@ public sealed class StringLibrary
                 {
                     i++;
                     if (char.IsDigit(format[i])) i++;
-                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (width or precision too long)");
+                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread, "invalid format (width or precision too long)");
                     width = int.Parse(format.AsSpan()[start..i]);
                 }
 
@@ -219,7 +219,7 @@ public sealed class StringLibrary
                     start = i;
                     if (char.IsDigit(format[i])) i++;
                     if (char.IsDigit(format[i])) i++;
-                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread.GetTraceback(), "invalid format (width or precision too long)");
+                    if (char.IsDigit(format[i])) throw new LuaRuntimeException(context.Thread, "invalid format (width or precision too long)");
                     precision = int.Parse(format.AsSpan()[start..i]);
                 }
 
@@ -228,7 +228,7 @@ public sealed class StringLibrary
 
                 if (context.ArgumentCount <= parameterIndex)
                 {
-                    throw new LuaRuntimeException(context.Thread.GetTraceback(), $"bad argument #{parameterIndex + 1} to 'format' (no value)");
+                    throw new LuaRuntimeException(context.Thread, $"bad argument #{parameterIndex + 1} to 'format' (no value)");
                 }
 
                 var parameter = context.GetArgument(parameterIndex++);
@@ -243,7 +243,7 @@ public sealed class StringLibrary
                     case 'G':
                         if (!parameter.TryRead<double>(out var f))
                         {
-                            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
+                            LuaRuntimeException.BadArgument(context.Thread, parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
                         }
 
                         switch (specifier)
@@ -324,7 +324,7 @@ public sealed class StringLibrary
                     case 'X':
                         if (!parameter.TryRead<double>(out var x))
                         {
-                            LuaRuntimeException.BadArgument(context.Thread.GetTraceback(), parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
+                            LuaRuntimeException.BadArgument(context.Thread, parameterIndex + 1, "format", LuaValueType.Number.ToString(), parameter.Type.ToString());
                         }
 
                         LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.Thread, "format", parameterIndex + 1, x);
@@ -382,7 +382,7 @@ public sealed class StringLibrary
 
                         break;
                     default:
-                        throw new LuaRuntimeException(context.Thread.GetTraceback(), $"invalid option '%{specifier}' to 'format'");
+                        throw new LuaRuntimeException(context.Thread, $"invalid option '%{specifier}' to 'format'");
                 }
 
                 // Apply blank (' ') flag for positive numbers
@@ -519,7 +519,7 @@ public sealed class StringLibrary
             }
             else
             {
-                throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #3 to 'gsub' (string/function/table expected)");
+                throw new LuaRuntimeException(context.Thread, "bad argument #3 to 'gsub' (string/function/table expected)");
             }
 
             if (result.TryRead<string>(out var rs))
@@ -537,7 +537,7 @@ public sealed class StringLibrary
             }
             else
             {
-                throw new LuaRuntimeException(context.Thread.GetTraceback(), $"invalid replacement value (a {result.Type})");
+                throw new LuaRuntimeException(context.Thread, $"invalid replacement value (a {result.Type})");
             }
 
             lastIndex = match.Index + match.Length;

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

@@ -68,7 +68,7 @@ public sealed class TableLibrary
             }
             else
             {
-                throw new LuaRuntimeException(context.Thread.GetTraceback(), $"invalid value ({value.Type}) at index {i} in table for 'concat'");
+                throw new LuaRuntimeException(context.Thread, $"invalid value ({value.Type}) at index {i} in table for 'concat'");
             }
 
             if (i != arg3) builder.Append(arg1);
@@ -95,7 +95,7 @@ public sealed class TableLibrary
 
         if (pos <= 0 || pos > table.ArrayLength + 1)
         {
-            throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #2 to 'insert' (position out of bounds)");
+            throw new LuaRuntimeException(context.Thread, "bad argument #2 to 'insert' (position out of bounds)");
         }
 
         table.Insert(pos, value);
@@ -135,7 +135,7 @@ public sealed class TableLibrary
                 return new(context.Return(LuaValue.Nil));
             }
 
-            throw new LuaRuntimeException(context.Thread.GetTraceback(), "bad argument #2 to 'remove' (position out of bounds)");
+            throw new LuaRuntimeException(context.Thread, "bad argument #2 to 'remove' (position out of bounds)");
         }
         else if (n > table.ArrayLength)
         {

+ 3 - 3
tests/Lua.Tests/AsyncTests.cs

@@ -11,7 +11,7 @@ public class AsyncTests
     {
         state = LuaState.Create();
         state.OpenStandardLibraries();
-        var assert = state.Environment["assert"].Read<LuaFunction>() ;
+        var assert = state.Environment["assert"].Read<LuaFunction>();
         state.Environment["assert"] = new LuaFunction("wait",
             async (c, ct) =>
             {
@@ -19,9 +19,9 @@ public class AsyncTests
                 return await assert.InvokeAsync(c, ct);
             });
     }
-    
+
     [Test]
-    public  async Task Test_Async()
+    public async Task Test_Async()
     {
         var path = FileHelper.GetAbsolutePath("tests-lua/coroutine.lua");
         try

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

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