Browse Source

Merge pull request #52 from Akeit0/optimize

Large Optimization
Annulus Games 11 months ago
parent
commit
0b07415a30

+ 7 - 0
src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs

@@ -65,6 +65,11 @@ public class FunctionCompilationContext : IDisposable
     FastListCore<BreakDescription> breakQueue;
     FastListCore<GotoDescription> gotoQueue;
 
+    /// <summary>
+    /// Maximum local stack size
+    /// </summary>
+    public byte MaxStackPosition { get; set; }
+
     /// <summary>
     /// Chunk name (for debug)
     /// </summary>
@@ -415,6 +420,7 @@ public class FunctionCompilationContext : IDisposable
             UpValues = upvalues.AsSpan().ToArray(),
             Functions = functions.AsSpan().ToArray(),
             ParameterCount = ParameterCount,
+            MaxStackPosition = MaxStackPosition,
         };
 
         foreach (var function in functions.AsSpan())
@@ -444,6 +450,7 @@ public class FunctionCompilationContext : IDisposable
         LoopLevel = 0;
         ParameterCount = 0;
         HasVariableArguments = false;
+        MaxStackPosition = 0;
     }
 
     /// <summary>

+ 10 - 2
src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs

@@ -619,8 +619,16 @@ public sealed class LuaCompiler : ISyntaxNodeVisitor<ScopeCompilationContext, bo
         // push closure instruction
         context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true);
 
-        // assign global variable
-        context.PushInstruction(Instruction.SetTabUp(0, (ushort)(index + 256), (ushort)(context.StackPosition - 1)), node.Position);
+        if (context.TryGetLocalVariableInThisScope(node.Name, out var variable))
+        {
+            // assign local variable
+            context.PushInstruction(Instruction.Move(variable.RegisterIndex, (ushort)(context.StackPosition - 1)), node.Position, true);
+        }
+        else
+        {
+            // assign global variable
+            context.PushInstruction(Instruction.SetTabUp(0, (ushort)(index + 256), (ushort)(context.StackPosition - 1)), node.Position);
+        }
 
         return true;
     }

+ 1 - 0
src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs

@@ -79,6 +79,7 @@ public class ScopeCompilationContext : IDisposable
         {
             StackPosition++;
         }
+        Function.MaxStackPosition = Math.Max(Function.MaxStackPosition, StackPosition);
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]

+ 30 - 3
src/Lua/Exceptions.cs

@@ -4,7 +4,16 @@ using Lua.Runtime;
 
 namespace Lua;
 
-public class LuaException(string message) : Exception(message);
+public class LuaException : Exception
+{
+    protected LuaException(Exception innerException) : base(innerException.Message, innerException)
+    {
+    }
+
+    public LuaException(string message) : base(message)
+    {
+    }
+}
 
 public class LuaParseException(string? chunkName, SourcePosition position, string message) : LuaException(message)
 {
@@ -44,9 +53,27 @@ public class LuaParseException(string? chunkName, SourcePosition position, strin
     public override string Message => $"{ChunkName}:{(Position == null ? "" : $"{Position.Value}:")} {base.Message}";
 }
 
-public class LuaRuntimeException(Traceback traceback, string message) : LuaException(message)
+public class LuaRuntimeException : LuaException
 {
-    public Traceback LuaTraceback { get; } = traceback;
+    public LuaRuntimeException(Traceback traceback, Exception innerException) : base(innerException)
+    {
+        LuaTraceback = traceback;
+    }
+
+    public LuaRuntimeException(Traceback traceback, string message) : base(message)
+    {
+        LuaTraceback = traceback;
+    }
+
+    public LuaRuntimeException(Traceback traceback, LuaValue errorObject): base(errorObject.ToString())
+    {
+        LuaTraceback = traceback;
+        ErrorObject = errorObject;
+    }
+
+    public Traceback LuaTraceback { get; }
+
+    public LuaValue? ErrorObject { get; }
 
     public static void AttemptInvalidOperation(Traceback traceback, string op, LuaValue a, LuaValue b)
     {

+ 23 - 7
src/Lua/Internal/FastStackCore.cs

@@ -16,7 +16,7 @@ public struct FastStackCore<T>
     public readonly ReadOnlySpan<T> AsSpan()
     {
         if (array == null) return [];
-        return array.AsSpan(0, tail);
+        return array.AsSpan(0, tail)!;
     }
 
     public readonly Span<T?> GetBuffer()
@@ -33,15 +33,14 @@ public struct FastStackCore<T>
         }
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public void Push(in T item)
     {
         array ??= new T[InitialCapacity];
 
         if (tail == array.Length)
         {
-            var newArray = new T[tail * 2];
-            Array.Copy(array, newArray, tail);
-            array = newArray;
+            Array.Resize(ref array, tail * 2);
         }
 
         array[tail] = item;
@@ -64,9 +63,21 @@ public struct FastStackCore<T>
         return true;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal bool TryPop()
+    {
+        if (tail == 0)
+        {
+            return false;
+        }
+        array[--tail] = default;
+
+        return true;
+    }
+
     public T Pop()
     {
-        if (!TryPop(out var result)) throw new InvalidOperationException("Empty stack");
+        if (!TryPop(out var result)) ThrowForEmptyStack();
         return result;
     }
 
@@ -85,7 +96,7 @@ public struct FastStackCore<T>
 
     public T Peek()
     {
-        if (!TryPeek(out var result)) throw new InvalidOperationException();
+        if (!TryPeek(out var result)) ThrowForEmptyStack();
         return result;
     }
 
@@ -119,4 +130,9 @@ public struct FastStackCore<T>
         array.AsSpan(0, tail).Clear();
         tail = 0;
     }
-}
+    
+    void ThrowForEmptyStack()
+    {
+        throw new InvalidOperationException("Empty stack");
+    }
+}

+ 73 - 0
src/Lua/Internal/LuaValueArrayPool.cs

@@ -0,0 +1,73 @@
+namespace Lua.Internal;
+
+internal static class LuaValueArrayPool
+{
+    static FastStackCore<LuaValue[]> poolOf1024;
+    static FastStackCore<LuaValue[]> poolOf1;
+
+    static readonly object lockObject = new();
+
+
+    public static LuaValue[] Rent1024()
+    {
+        lock (lockObject)
+        {
+            if (poolOf1024.Count > 0)
+            {
+                return poolOf1024.Pop();
+            }
+
+            return new LuaValue[1024];
+        }
+    }
+
+    public static LuaValue[] Rent1()
+    {
+        lock (lockObject)
+        {
+            if (poolOf1.Count > 0)
+            {
+                return poolOf1.Pop();
+            }
+
+            return new LuaValue[1];
+        }
+    }
+
+    public static void Return1024(LuaValue[] array, bool clear = false)
+    {
+        if (array.Length != 1024)
+        {
+            ThrowInvalidArraySize(array.Length, 1024);
+        }
+
+        if (clear)
+        {
+            array.AsSpan().Clear();
+        }
+        lock (lockObject)
+        {
+            poolOf1024.Push(array);
+        }
+    }
+
+
+    public static void Return1(LuaValue[] array)
+    {
+        if (array.Length != 1)
+        {
+            ThrowInvalidArraySize(array.Length, 1);
+        }
+
+        array[0] = LuaValue.Nil;
+        lock (lockObject)
+        {
+            poolOf1.Push(array);
+        }
+    }
+
+    static void ThrowInvalidArraySize(int size, int expectedSize)
+    {
+        throw new InvalidOperationException($"Invalid array size: {size}, expected: {expectedSize}");
+    }
+}

+ 449 - 0
src/Lua/Internal/LuaValueDictionary.cs

@@ -0,0 +1,449 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace Lua.Internal
+{
+    /// <summary>
+    /// A minimal dictionary that uses LuaValue as key and value.
+    /// Nil value counting is included.
+    /// </summary>
+    internal sealed class LuaValueDictionary
+    {
+        private int[]? _buckets;
+        private Entry[]? _entries;
+        private int _count;
+        private int _freeList;
+        private int _freeCount;
+        private int _version;
+        private const int StartOfFreeList = -3;
+
+        private int _nilCount;
+
+        public LuaValueDictionary(int capacity)
+        {
+            if (capacity < 0)
+            {
+                ThrowHelper.ThrowArgumentOutOfRangeException(nameof(capacity));
+            }
+
+            if (capacity > 0)
+            {
+                Initialize(capacity);
+            }
+        }
+
+        public int Count => _count - _freeCount;
+
+        public int NilCount => _nilCount;
+
+
+        public LuaValue this[LuaValue key]
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            get
+            {
+                ref LuaValue value = ref FindValue(key);
+                if (!Unsafe.IsNullRef(ref value))
+                {
+                    return value;
+                }
+
+                return default;
+            }
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            set => Insert(key, value);
+        }
+
+
+        public void Clear()
+        {
+            int count = _count;
+            if (count > 0)
+            {
+                Debug.Assert(_buckets != null, "_buckets should be non-null");
+                Debug.Assert(_entries != null, "_entries should be non-null");
+
+                Array.Clear(_buckets, 0, _buckets.Length);
+
+                _count = 0;
+                _freeList = -1;
+                _freeCount = 0;
+                Array.Clear(_entries, 0, count);
+                _nilCount = 0;
+            }
+        }
+
+        public bool ContainsKey(LuaValue key) =>
+            !Unsafe.IsNullRef(ref FindValue(key));
+
+        public bool ContainsValue(LuaValue value)
+        {
+            Entry[]? entries = _entries;
+
+            for (int i = 0; i < _count; i++)
+            {
+                if (entries![i].next >= -1 && entries[i].value.Equals(value))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+
+        public Enumerator GetEnumerator() => new Enumerator(this);
+
+        internal ref LuaValue FindValue(LuaValue key)
+        {
+            ref Entry entry = ref Unsafe.NullRef<Entry>();
+            if (_buckets != null)
+            {
+                Debug.Assert(_entries != null, "expected entries to be != null");
+                {
+                    uint hashCode = (uint)key.GetHashCode();
+                    int i = GetBucket(hashCode);
+                    Entry[]? entries = _entries;
+                    uint collisionCount = 0;
+
+                    // ValueType: Devirtualize with EqualityComparer<LuaValue>.Default intrinsic
+                    i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
+                    do
+                    {
+                        // Should be a while loop https://github.com/dotnet/runtime/issues/9422
+                        // Test in if to drop range check for following array access
+                        if ((uint)i >= (uint)entries.Length)
+                        {
+                            goto ReturnNotFound;
+                        }
+
+                        entry = ref entries[i];
+                        if (entry.hashCode == hashCode && entry.key.Equals(key))
+                        {
+                            goto ReturnFound;
+                        }
+
+                        i = entry.next;
+
+                        collisionCount++;
+                    } while (collisionCount <= (uint)entries.Length);
+
+                    // The chain of entries forms a loop; which means a concurrent update has happened.
+                    // Break out of the loop and throw, rather than looping forever.
+                    goto ConcurrentOperation;
+                }
+            }
+
+            goto ReturnNotFound;
+
+        ConcurrentOperation:
+            ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+        ReturnFound:
+            ref LuaValue value = ref entry.value;
+        Return:
+            return ref value;
+        ReturnNotFound:
+            value = ref Unsafe.NullRef<LuaValue>();
+            goto Return;
+        }
+
+        private void Initialize(int capacity)
+        {
+            var newSize = 8;
+            while (newSize < capacity)
+            {
+                newSize *= 2;
+            }
+
+            int size = newSize;
+            int[] buckets = new int[size];
+            Entry[] entries = new Entry[size];
+
+            // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails
+            _freeList = -1;
+            _buckets = buckets;
+            _entries = entries;
+        }
+
+        private void Insert(LuaValue key, LuaValue value)
+        {
+            if (value.Type is LuaValueType.Nil)
+            {
+                _nilCount++;
+            }
+
+            if (_buckets == null)
+            {
+                Initialize(0);
+            }
+
+            Debug.Assert(_buckets != null);
+
+            Entry[]? entries = _entries;
+            Debug.Assert(entries != null, "expected entries to be non-null");
+
+
+            uint hashCode = (uint)key.GetHashCode();
+
+            uint collisionCount = 0;
+            ref int bucket = ref GetBucket(hashCode);
+            int i = bucket - 1; // Value in _buckets is 1-based
+
+            {
+                ref Entry entry = ref Unsafe.NullRef<Entry>();
+                while ((uint)i < (uint)entries.Length)
+                {
+                    entry = ref entries[i];
+                    if (entry.hashCode == hashCode && entry.key.Equals(key))
+                    {
+                        if (entry.value.Type is LuaValueType.Nil)
+                        {
+                            _nilCount--;
+                        }
+
+                        entry.value = value;
+                        return;
+                    }
+
+                    i = entry.next;
+
+                    collisionCount++;
+                    if (collisionCount > (uint)entries.Length)
+                    {
+                        // The chain of entries forms a loop; which means a concurrent update has happened.
+                        // Break out of the loop and throw, rather than looping forever.
+                        ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+                    }
+                }
+            }
+
+            int index;
+            if (_freeCount > 0)
+            {
+                index = _freeList;
+                Debug.Assert((StartOfFreeList - entries[_freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow");
+                _freeList = StartOfFreeList - entries[_freeList].next;
+                _freeCount--;
+            }
+            else
+            {
+                int count = _count;
+                if (count == entries.Length)
+                {
+                    Resize();
+                    bucket = ref GetBucket(hashCode);
+                }
+
+                index = count;
+                _count = count + 1;
+                entries = _entries;
+            }
+
+            {
+                ref Entry entry = ref entries![index];
+                entry.hashCode = hashCode;
+                entry.next = bucket - 1; // Value in _buckets is 1-based
+                entry.key = key;
+                entry.value = value;
+                bucket = index + 1; // Value in _buckets is 1-based
+                _version++;
+            }
+        }
+
+
+        private void Resize() => Resize(_entries!.Length * 2);
+
+        private void Resize(int newSize)
+        {
+            // Value types never rehash
+            Debug.Assert(_entries != null, "_entries should be non-null");
+            Debug.Assert(newSize >= _entries.Length);
+
+            Entry[] entries = new Entry[newSize];
+
+            int count = _count;
+            Array.Copy(_entries, entries, count);
+
+
+            // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails
+            _buckets = new int[newSize];
+
+            for (int i = 0; i < count; i++)
+            {
+                if (entries[i].next >= -1)
+                {
+                    ref int bucket = ref GetBucket(entries[i].hashCode);
+                    entries[i].next = bucket - 1; // Value in _buckets is 1-based
+                    bucket = i + 1;
+                }
+            }
+
+            _entries = entries;
+        }
+
+        public bool Remove(LuaValue key)
+        {
+            // The overload Remove(LuaValue key, out LuaValue value) is a copy of this method with one additional
+            // statement to copy the value for entry being removed into the output parameter.
+            // Code has been intentionally duplicated for performance reasons.
+
+            if (_buckets != null)
+            {
+                Debug.Assert(_entries != null, "entries should be non-null");
+                uint collisionCount = 0;
+
+
+                uint hashCode = (uint)key.GetHashCode();
+
+                ref int bucket = ref GetBucket(hashCode);
+                Entry[]? entries = _entries;
+                int last = -1;
+                int i = bucket - 1; // Value in buckets is 1-based
+                while (i >= 0)
+                {
+                    ref Entry entry = ref entries[i];
+
+                    if (entry.hashCode == hashCode && entry.key.Equals(key))
+                    {
+                        if (last < 0)
+                        {
+                            bucket = entry.next + 1; // Value in buckets is 1-based
+                        }
+                        else
+                        {
+                            entries[last].next = entry.next;
+                        }
+
+                        Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646");
+                        entry.next = StartOfFreeList - _freeList;
+
+                        if (entry.value.Type is LuaValueType.Nil)
+                        {
+                            _nilCount--;
+                        }
+
+                        entry.key = default;
+                        entry.value = default;
+
+                        _freeList = i;
+                        _freeCount++;
+                        return true;
+                    }
+
+                    last = i;
+                    i = entry.next;
+
+                    collisionCount++;
+                    if (collisionCount > (uint)entries.Length)
+                    {
+                        // The chain of entries forms a loop; which means a concurrent update has happened.
+                        // Break out of the loop and throw, rather than looping forever.
+                        ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryGetValue(LuaValue key, out LuaValue value)
+        {
+            ref LuaValue valRef = ref FindValue(key);
+            if (!Unsafe.IsNullRef(ref valRef))
+            {
+                value = valRef;
+                return true;
+            }
+
+            value = default;
+            return false;
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private ref int GetBucket(uint hashCode)
+        {
+            int[] buckets = _buckets!;
+            return ref buckets[hashCode & (buckets.Length - 1)];
+        }
+
+        private struct Entry
+        {
+            public uint hashCode;
+
+            /// <summary>
+            /// 0-based index of next entry in chain: -1 means end of chain
+            /// also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3,
+            /// so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc.
+            /// </summary>
+            public int next;
+
+            public LuaValue key; // Key of entry
+            public LuaValue value; // Value of entry
+        }
+
+        public struct Enumerator
+        {
+            private readonly LuaValueDictionary dictionary;
+            private readonly int _version;
+            private int _index;
+            private KeyValuePair<LuaValue, LuaValue> _current;
+
+            internal Enumerator(LuaValueDictionary dictionary)
+            {
+                this.dictionary = dictionary;
+                _version = dictionary._version;
+                _index = 0;
+                _current = default;
+            }
+
+            public bool MoveNext()
+            {
+                if (_version != dictionary._version)
+                {
+                    ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+                }
+
+                // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
+                // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
+                while ((uint)_index < (uint)dictionary._count)
+                {
+                    ref Entry entry = ref dictionary._entries![_index++];
+
+                    if (entry.next >= -1)
+                    {
+                        _current = new KeyValuePair<LuaValue, LuaValue>(entry.key, entry.value);
+                        return true;
+                    }
+                }
+
+                _index = dictionary._count + 1;
+                _current = default;
+                return false;
+            }
+
+            public KeyValuePair<LuaValue, LuaValue> Current => _current;
+        }
+        static class ThrowHelper
+        {
+            public static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported()
+            {
+                throw new InvalidOperationException("Concurrent operations are not supported");
+            }
+
+            public static void ThrowArgumentOutOfRangeException(string paramName)
+            {
+                throw new ArgumentOutOfRangeException(paramName);
+            }
+
+            public static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
+            {
+                throw new InvalidOperationException("Collection was modified after the enumerator was instantiated.");
+            }
+        }
+    }
+}

+ 33 - 1
src/Lua/Internal/PooledList.cs

@@ -36,6 +36,32 @@ internal ref struct PooledList<T>
         tail++;
     }
 
+    public void AddRange(scoped ReadOnlySpan<T> items)
+    {
+        ThrowIfDisposed();
+
+        if (buffer == null)
+        {
+            buffer = ArrayPool<T>.Shared.Rent(items.Length);
+        }
+        else if (buffer.Length < tail + items.Length)
+        {
+            var newSize = buffer.Length * 2;
+            while (newSize < tail + items.Length)
+            {
+                newSize *= 2;
+            }
+            
+            var newArray = ArrayPool<T>.Shared.Rent(newSize);
+            buffer.AsSpan().CopyTo(newArray);
+            ArrayPool<T>.Shared.Return(buffer);
+            buffer = newArray;
+        }
+
+        items.CopyTo(buffer.AsSpan()[tail..]);
+        tail += items.Length;
+    }
+    
     public void Clear()
     {
         ThrowIfDisposed();
@@ -78,6 +104,12 @@ internal ref struct PooledList<T>
 
     void ThrowIfDisposed()
     {
-        if (tail == -1) throw new ObjectDisposedException(nameof(PooledList<T>));
+        if (tail == -1) ThrowDisposedException();
+    }
+    
+    void ThrowDisposedException()
+    {
+        throw new ObjectDisposedException(nameof(PooledList<T>));
     }
+    
 }

+ 1 - 1
src/Lua/Lua.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <TargetFrameworks>net8.0; net6.0; netstandard2.1</TargetFrameworks>
-    <LangVersion>12</LangVersion>
+    <LangVersion>13</LangVersion>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

+ 8 - 9
src/Lua/LuaCoroutine.cs

@@ -63,11 +63,6 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                         Stack.EnsureCapacity(baseThread.Stack.Count);
                         baseThread.Stack.AsSpan().CopyTo(Stack.GetBuffer());
                         Stack.NotifyTop(baseThread.Stack.Count);
-
-                        // copy callstack value
-                        CallStack.EnsureCapacity(baseThread.CallStack.Count);
-                        baseThread.CallStack.AsSpan().CopyTo(CallStack.GetBuffer());
-                        CallStack.NotifyTop(baseThread.CallStack.Count);
                     }
                     else
                     {
@@ -78,6 +73,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                                 : context.Arguments[1..].ToArray()
                         });
                     }
+
                     break;
                 case LuaThreadStatus.Normal:
                 case LuaThreadStatus.Running:
@@ -154,9 +150,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
                         State = context.State,
                         Thread = this,
                         ArgumentCount = context.ArgumentCount - 1,
-                        FrameBase = frameBase,
-                        ChunkName = Function.Name,
-                        RootChunkName = context.RootChunkName,
+                        FrameBase = frameBase
                     }, this.buffer, cancellationToken).Preserve();
 
                     Volatile.Write(ref isFirstCall, false);
@@ -197,7 +191,7 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
 
                     Volatile.Write(ref status, (byte)LuaThreadStatus.Dead);
                     buffer.Span[0] = false;
-                    buffer.Span[1] = ex.Message;
+                    buffer.Span[1] = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value: ex.Message;
                     return 2;
                 }
                 else
@@ -225,6 +219,11 @@ public sealed class LuaCoroutine : LuaThread, IValueTaskSource<LuaCoroutine.Yiel
             throw new LuaRuntimeException(context.State.GetTraceback(), "cannot call yield on a coroutine that is not currently running");
         }
 
+        if (context.Thread.GetCallStackFrames()[^2].Function is not Closure)
+        {
+            throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield across a C#-call boundary");
+        }
+
         resume.SetResult(new()
         {
             Results = context.Arguments.ToArray(),

+ 1 - 3
src/Lua/LuaFunction.cs

@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
 using Lua.Runtime;
 
 namespace Lua;
@@ -16,9 +17,6 @@ public class LuaFunction(string name, Func<LuaFunctionExecutionContext, Memory<L
         var frame = new CallStackFrame
         {
             Base = context.FrameBase,
-            CallPosition = context.SourcePosition,
-            ChunkName = context.ChunkName ?? LuaState.DefaultChunkName,
-            RootChunkName = context.RootChunkName ?? LuaState.DefaultChunkName,
             VariableArgumentCount = this is Closure closure ? Math.Max(context.ArgumentCount - closure.Proto.ParameterCount, 0) : 0,
             Function = this,
         };

+ 1 - 0
src/Lua/LuaFunctionExecutionContext.cs

@@ -14,6 +14,7 @@ public readonly record struct LuaFunctionExecutionContext
     public SourcePosition? SourcePosition { get; init; }
     public string? RootChunkName { get; init; }
     public string? ChunkName { get; init; }
+    public int? CallerInstructionIndex { get; init; }
     public object? AdditionalContext { get; init; }
 
     public ReadOnlySpan<LuaValue> Arguments

+ 0 - 1
src/Lua/LuaFunctionExtensions.cs

@@ -4,7 +4,6 @@ namespace Lua;
 
 public static class LuaFunctionExtensions
 {
-
     public static async ValueTask<LuaValue[]> InvokeAsync(this LuaFunction function, LuaState state, LuaValue[] arguments, CancellationToken cancellationToken = default)
     {
         using var buffer = new PooledArray<LuaValue>(1024);

+ 25 - 5
src/Lua/LuaState.cs

@@ -88,13 +88,33 @@ public sealed class LuaState
 
     public Traceback GetTraceback()
     {
-        // TODO: optimize
+        if (threadStack.Count == 0)
+        {
+            return new()
+            {
+                RootFunc = (Closure)MainThread.GetCallStackFrames()[0].Function,
+                StackFrames = MainThread.GetCallStackFrames()[1..]
+                    .ToArray()
+            };
+        }
+
+        using var list = new PooledList<CallStackFrame>(8);
+        foreach (var frame in MainThread.GetCallStackFrames()[1..])
+        {
+            list.Add(frame);
+        }
+        foreach (var thread in threadStack.AsSpan())
+        {
+            if (thread.CallStack.Count == 0) continue;
+            foreach (var frame in thread.GetCallStackFrames()[1..])
+            {
+                list.Add(frame);
+            }
+        }
         return new()
         {
-            StackFrames = threadStack.AsSpan().ToArray()
-                .Append(MainThread)
-                .SelectMany(x => x.GetCallStackFrames()[1..].ToArray())
-                .ToArray()
+            RootFunc = (Closure)MainThread.GetCallStackFrames()[0].Function,
+            StackFrames = list.AsSpan().ToArray()
         };
     }
 

+ 25 - 23
src/Lua/LuaTable.cs

@@ -16,7 +16,7 @@ public sealed class LuaTable
     }
 
     LuaValue[] array;
-    Dictionary<LuaValue, LuaValue> dictionary;
+    readonly LuaValueDictionary dictionary;
     LuaTable? metatable;
 
     public LuaValue this[LuaValue key]
@@ -31,7 +31,7 @@ public sealed class LuaTable
                 if (index > 0 && index <= array.Length)
                 {
                     // Arrays in Lua are 1-origin...
-                    return MemoryMarshalEx.UnsafeElementAt(array, index - 1);
+                    return array[index - 1];
                 }
             }
 
@@ -41,18 +41,23 @@ public sealed class LuaTable
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set
         {
-            if (key.Type is LuaValueType.Number && double.IsNaN(key.UnsafeRead<double>()))
+            if (key.TryReadNumber(out var d))
             {
-                ThrowIndexIsNaN();
-            }
+                if (double.IsNaN(d))
+                {
+                    ThrowIndexIsNaN();
+                }
 
-            if (TryGetInteger(key, out var index))
-            {
-                if (0 < index && index <= Math.Max(array.Length * 2, 8))
+                if (MathEx.IsInteger(d))
                 {
-                    EnsureArrayCapacity(index);
-                    MemoryMarshalEx.UnsafeElementAt(array, index - 1) = value;
-                    return;
+                    var index = (int)d;
+                    if (0 < index && index <= Math.Max(array.Length * 2, 8))
+                    {
+                        if (array.Length < index)
+                            EnsureArrayCapacity(index);
+                        array[index - 1] = value;
+                        return;
+                    }
                 }
             }
 
@@ -62,7 +67,7 @@ public sealed class LuaTable
 
     public int HashMapCount
     {
-        get => dictionary.Count(x => x.Value.Type is not LuaValueType.Nil);
+        get => dictionary.Count - dictionary.NilCount;
     }
 
     public int ArrayLength
@@ -73,6 +78,7 @@ public sealed class LuaTable
             {
                 if (array[i].Type is LuaValueType.Nil) return i;
             }
+
             return array.Length;
         }
     }
@@ -83,6 +89,7 @@ public sealed class LuaTable
         set => metatable = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public bool TryGetValue(LuaValue key, out LuaValue value)
     {
         if (key.Type is LuaValueType.Nil)
@@ -95,7 +102,7 @@ public sealed class LuaTable
         {
             if (index > 0 && index <= array.Length)
             {
-                value = MemoryMarshalEx.UnsafeElementAt(array, index - 1);
+                value = array[index - 1];
                 return value.Type is not LuaValueType.Nil;
             }
         }
@@ -113,7 +120,7 @@ public sealed class LuaTable
         if (TryGetInteger(key, out var index))
         {
             return index > 0 && index <= array.Length &&
-                MemoryMarshalEx.UnsafeElementAt(array, index - 1).Type != LuaValueType.Nil;
+                   array[index - 1].Type != LuaValueType.Nil;
         }
 
         return dictionary.TryGetValue(key, out var value) && value.Type is not LuaValueType.Nil;
@@ -121,20 +128,15 @@ public sealed class LuaTable
 
     public LuaValue RemoveAt(int index)
     {
-        if (index <= 0 || index > array.Length)
-        {
-            throw new IndexOutOfRangeException();
-        }
-
         var arrayIndex = index - 1;
-        var value = MemoryMarshalEx.UnsafeElementAt(array, arrayIndex);
+        var value = array[arrayIndex];
 
         if (arrayIndex < array.Length - 1)
         {
             array.AsSpan(arrayIndex + 1).CopyTo(array.AsSpan(arrayIndex));
         }
-        
-        MemoryMarshalEx.UnsafeElementAt(array, array.Length - 1) = default;
+
+        array[^1] = default;
 
         return value;
     }
@@ -153,7 +155,7 @@ public sealed class LuaTable
         {
             array.AsSpan(arrayIndex, array.Length - arrayIndex - 1).CopyTo(array.AsSpan(arrayIndex + 1));
         }
-        
+
         array[arrayIndex] = value;
     }
 

+ 36 - 3
src/Lua/LuaThread.cs

@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
 using Lua.Internal;
 using Lua.Runtime;
 
@@ -31,15 +32,45 @@ public abstract class LuaThread
         return callStack.AsSpan();
     }
 
-    internal void PushCallStackFrame(CallStackFrame frame)
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PushCallStackFrame(in CallStackFrame frame)
     {
         callStack.Push(frame);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal void PopCallStackFrame()
     {
-        var frame = callStack.Pop();
-        stack.PopUntil(frame.Base);
+        if (callStack.TryPop(out var frame))
+        {
+            stack.PopUntil(frame.Base);
+        }
+        else
+        {
+            ThrowForEmptyStack();
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PopCallStackFrameUnsafe(int frameBase)
+    {
+        if (callStack.TryPop())
+        {
+            stack.PopUntil(frameBase);
+        }
+        else
+        {
+            ThrowForEmptyStack();
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal void PopCallStackFrameUnsafe()
+    {
+        if (!callStack.TryPop())
+        {
+            ThrowForEmptyStack();
+        }
     }
 
     internal void DumpStackValues()
@@ -50,4 +81,6 @@ public abstract class LuaThread
             Console.WriteLine($"LuaStack [{i}]\t{span[i]}");
         }
     }
+    
+    static void ThrowForEmptyStack() => throw new InvalidOperationException("Empty stack");
 }

+ 192 - 68
src/Lua/LuaValue.cs

@@ -23,17 +23,15 @@ public readonly struct LuaValue : IEquatable<LuaValue>
 {
     public static readonly LuaValue Nil = default;
 
-    readonly LuaValueType type;
+    public readonly LuaValueType Type;
     readonly double value;
     readonly object? referenceValue;
 
-    public LuaValueType Type => type;
-
     public bool TryRead<T>(out T result)
     {
         var t = typeof(T);
 
-        switch (type)
+        switch (Type)
         {
             case LuaValueType.Number:
                 if (t == typeof(float))
@@ -60,7 +58,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
             case LuaValueType.Boolean:
                 if (t == typeof(bool))
                 {
-                    var v = value == 1;
+                    var v = value != 0;
                     result = Unsafe.As<bool, T>(ref v);
                     return true;
                 }
@@ -82,49 +80,8 @@ public readonly struct LuaValue : IEquatable<LuaValue>
                 }
                 else if (t == typeof(double))
                 {
-                    var str = (string)referenceValue!;
-                    var span = str.AsSpan().Trim();
-
-                    if (span.Length == 0)
-                    {
-                        result = default!;
-                        return false;
-                    }
-
-                    var sign = 1;
-                    var first = span[0];
-                    if (first is '+')
-                    {
-                        sign = 1;
-                        span = span[1..];
-                    }
-                    else if (first is '-')
-                    {
-                        sign = -1;
-                        span = span[1..];
-                    }
-
-                    if (span.Length > 2 && span[0] is '0' && span[1] is 'x' or 'X')
-                    {
-                        // TODO: optimize
-                        try
-                        {
-                            var d = HexConverter.ToDouble(span) * sign;
-                            result = Unsafe.As<double, T>(ref d);
-                            return true;
-                        }
-                        catch (FormatException)
-                        {
-                            result = default!;
-                            return false;
-                        }
-                    }
-                    else
-                    {
-                        var tryResult = double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out var d);
-                        result = tryResult ? Unsafe.As<double, T>(ref d) : default!;
-                        return tryResult;
-                    }
+                    result = default!;
+                    return TryParseToDouble(out Unsafe.As<T, double>(ref result));
                 }
                 else if (t == typeof(object))
                 {
@@ -204,19 +161,162 @@ public readonly struct LuaValue : IEquatable<LuaValue>
         result = default!;
         return false;
     }
-    
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal bool TryReadBool(out bool result)
+    {
+        if (Type == LuaValueType.Boolean)
+        {
+            result = value != 0;
+            return true;
+        }
+
+        result = default!;
+        return false;
+    }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal bool TryReadNumber(out double result)
     {
-        if (type == LuaValueType.Number)
+        if (Type == LuaValueType.Number)
         {
             result = value;
             return true;
         }
+
         result = default!;
         return false;
     }
-   
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal bool TryReadTable(out LuaTable result)
+    {
+        if (Type == LuaValueType.Table)
+        {
+            var v = referenceValue!;
+            result = Unsafe.As<object, LuaTable>(ref v);
+            return true;
+        }
+
+        result = default!;
+        return false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal bool TryReadFunction(out LuaFunction result)
+    {
+        if (Type == LuaValueType.Function)
+        {
+            var v = referenceValue!;
+            result = Unsafe.As<object, LuaFunction>(ref v);
+            return true;
+        }
+
+        result = default!;
+        return false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal bool TryReadString(out string result)
+    {
+        if (Type == LuaValueType.String)
+        {
+            var v = referenceValue!;
+            result = Unsafe.As<object, string>(ref v);
+            return true;
+        }
+
+        result = default!;
+        return false;
+    }
+
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal bool TryReadDouble(out double result)
+    {
+        if (Type == LuaValueType.Number)
+        {
+            result = value;
+            return true;
+        }
+
+        return TryParseToDouble(out result);
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static bool TryReadOrSetDouble(ref LuaValue luaValue, out double result)
+    {
+        if (luaValue.Type == LuaValueType.Number)
+        {
+            result = luaValue.value;
+            return true;
+        }
+
+        if (luaValue.TryParseToDouble(out result))
+        {
+            luaValue = result;
+            return true;
+        }
+
+        return false;
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal double UnsafeReadDouble()
+    {
+        return value;
+    }
+
+    bool TryParseToDouble(out double result)
+    {
+        if (Type != LuaValueType.String)
+        {
+            result = default!;
+            return false;
+        }
+
+        var str = Unsafe.As<string>(referenceValue!);
+        var span = str.AsSpan().Trim();
+        if (span.Length == 0)
+        {
+            result = default!;
+            return false;
+        }
+
+        var sign = 1;
+        var first = span[0];
+        if (first is '+')
+        {
+            sign = 1;
+            span = span[1..];
+        }
+        else if (first is '-')
+        {
+            sign = -1;
+            span = span[1..];
+        }
+
+        if (span.Length > 2 && span[0] is '0' && span[1] is 'x' or 'X')
+        {
+            // TODO: optimize
+            try
+            {
+                var d = HexConverter.ToDouble(span) * sign;
+                result = d;
+                return true;
+            }
+            catch (FormatException)
+            {
+                result = default!;
+                return false;
+            }
+        }
+        else
+        {
+            return double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
+        }
+    }
+
     public T Read<T>()
     {
         if (!TryRead<T>(out var result)) throw new InvalidOperationException($"Cannot convert LuaValueType.{Type} to {typeof(T).FullName}.");
@@ -226,11 +326,11 @@ public readonly struct LuaValue : IEquatable<LuaValue>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     internal T UnsafeRead<T>()
     {
-        switch (type)
+        switch (Type)
         {
             case LuaValueType.Boolean:
                 {
-                    var v = value == 1;
+                    var v = value != 0;
                     return Unsafe.As<bool, T>(ref v);
                 }
             case LuaValueType.Number:
@@ -255,96 +355,118 @@ public readonly struct LuaValue : IEquatable<LuaValue>
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public bool ToBoolean()
     {
+        if (Type == LuaValueType.Boolean) return value != 0;
         if (Type is LuaValueType.Nil) return false;
-        if (TryRead<bool>(out var result)) return result;
         return true;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(bool value)
     {
-        type = LuaValueType.Boolean;
+        Type = LuaValueType.Boolean;
         this.value = value ? 1 : 0;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(double value)
     {
-        type = LuaValueType.Number;
+        Type = LuaValueType.Number;
         this.value = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(string value)
     {
-        type = LuaValueType.String;
+        Type = LuaValueType.String;
         referenceValue = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(LuaFunction value)
     {
-        type = LuaValueType.Function;
+        Type = LuaValueType.Function;
         referenceValue = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(LuaTable value)
     {
-        type = LuaValueType.Table;
+        Type = LuaValueType.Table;
         referenceValue = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(LuaThread value)
     {
-        type = LuaValueType.Thread;
+        Type = LuaValueType.Thread;
         referenceValue = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public LuaValue(ILuaUserData value)
     {
-        type = LuaValueType.UserData;
+        Type = LuaValueType.UserData;
         referenceValue = value;
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static implicit operator LuaValue(bool value)
     {
         return new(value);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static implicit operator LuaValue(double value)
     {
         return new(value);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static implicit operator LuaValue(string value)
     {
         return new(value);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static implicit operator LuaValue(LuaTable value)
     {
         return new(value);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static implicit operator LuaValue(LuaFunction value)
     {
         return new(value);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static implicit operator LuaValue(LuaThread value)
     {
         return new(value);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public override int GetHashCode()
     {
-        return HashCode.Combine(type, value, referenceValue);
+        return Type switch
+        {
+            LuaValueType.Nil => 0,
+            LuaValueType.Boolean or LuaValueType.Number => value.GetHashCode(),
+            LuaValueType.String => Unsafe.As<string>(referenceValue)!.GetHashCode(),
+            _ => referenceValue!.GetHashCode()
+        };
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public bool Equals(LuaValue other)
     {
         if (other.Type != Type) return false;
 
-        return type switch
+        return Type switch
         {
             LuaValueType.Nil => true,
             LuaValueType.Boolean or LuaValueType.Number => other.value == value,
+            LuaValueType.String => Unsafe.As<string>(other.referenceValue) == Unsafe.As<string>(referenceValue),
             _ => other.referenceValue!.Equals(referenceValue)
         };
     }
@@ -354,24 +476,26 @@ public readonly struct LuaValue : IEquatable<LuaValue>
         return obj is LuaValue value1 && Equals(value1);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static bool operator ==(LuaValue a, LuaValue b)
     {
         return a.Equals(b);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public static bool operator !=(LuaValue a, LuaValue b)
     {
         return !a.Equals(b);
     }
 
-    public override string? ToString()
+    public override string ToString()
     {
-        return type switch
+        return Type switch
         {
             LuaValueType.Nil => "nil",
             LuaValueType.Boolean => Read<bool>() ? "true" : "false",
             LuaValueType.String => Read<string>(),
-            LuaValueType.Number => Read<double>().ToString(),
+            LuaValueType.Number => Read<double>().ToString(CultureInfo.InvariantCulture),
             LuaValueType.Function => $"function: {referenceValue!.GetHashCode()}",
             LuaValueType.Thread => $"thread: {referenceValue!.GetHashCode()}",
             LuaValueType.Table => $"table: {referenceValue!.GetHashCode()}",
@@ -421,7 +545,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
     {
         if (this.TryGetMetamethod(context.State, Metamethods.ToString, out var metamethod))
         {
-            if (!metamethod.TryRead<LuaFunction>(out var func))
+            if (!metamethod.TryReadFunction(out var func))
             {
                 LuaRuntimeException.AttemptInvalidOperation(context.State.GetTraceback(), "call", metamethod);
             }
@@ -436,7 +560,7 @@ public readonly struct LuaValue : IEquatable<LuaValue>
         }
         else
         {
-            buffer.Span[0] = ToString()!;
+            buffer.Span[0] = ToString();
             return new(1);
         }
     }

+ 8 - 4
src/Lua/Runtime/CallStackFrame.cs

@@ -1,5 +1,4 @@
 using System.Runtime.InteropServices;
-using Lua.CodeAnalysis;
 
 namespace Lua.Runtime;
 
@@ -7,9 +6,14 @@ namespace Lua.Runtime;
 public record struct CallStackFrame
 {
     public required int Base;
-    public required string ChunkName;
-    public required string RootChunkName;
     public required LuaFunction Function;
-    public required SourcePosition? CallPosition;
     public required int VariableArgumentCount;
+    public int CallerInstructionIndex;
+    internal CallStackFrameFlags Flags;
+}
+
+[Flags]
+public enum CallStackFrameFlags
+{
+    ReversedLe = 1,
 }

+ 7 - 2
src/Lua/Runtime/Chunk.cs

@@ -13,10 +13,15 @@ public sealed class Chunk
     public required UpValueInfo[] UpValues { get; init; }
     public required Chunk[] Functions { get; init; }
     public required int ParameterCount { get; init; }
+    
+    public required byte MaxStackPosition { get; init; }
+
+    Chunk? rootCache;
 
     internal Chunk GetRoot()
     {
-        if (Parent == null) return this;
-        return Parent.GetRoot();
+        if (rootCache != null) return rootCache;
+        if (Parent == null) return rootCache = this;
+        return rootCache = Parent.GetRoot();
     }
 }

+ 5 - 3
src/Lua/Runtime/Closure.cs

@@ -9,7 +9,7 @@ public sealed class Closure : LuaFunction
     FastListCore<UpValue> upValues;
 
     public Closure(LuaState state, Chunk proto, LuaTable? environment = null)
-        : base(proto.Name, (context, buffer, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, context.Thread.GetCurrentFrame(), buffer, ct))
+        : base(proto.Name, (context, buffer, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, buffer, ct))
     {
         this.proto = proto;
 
@@ -43,15 +43,17 @@ public sealed class Closure : LuaFunction
         {
             return state.GetOrAddUpValue(thread, thread.GetCallStackFrames()[^1].Base + description.Index);
         }
+        
         if (description.Index == -1) // -1 is global environment
         {
             return envUpValue;
         }
+        
         if (thread.GetCallStackFrames()[^1].Function is Closure parentClosure)
         {
-            return parentClosure.UpValues[description.Index];
+             return parentClosure.UpValues[description.Index];
         }
-
+        
         throw new Exception();
     }
 }

+ 29 - 1
src/Lua/Runtime/Instruction.cs

@@ -1,3 +1,5 @@
+using System.Runtime.CompilerServices;
+
 namespace Lua.Runtime;
 
 public struct Instruction : IEquatable<Instruction>
@@ -12,43 +14,69 @@ public struct Instruction : IEquatable<Instruction>
 
     public OpCode OpCode
     {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         get => (OpCode)(byte)(_value & 0x3F); // 6 bits
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => _value = (_value & 0xFFFFFFC0) | ((uint)value & 0x3F);
     }
 
     public byte A
     {
-        get => (byte)((_value >> 6) & 0xFF); // 8 bits
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        get => (byte)((_value >> 6)); // 8 bits
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => _value = (_value & 0xFFFFC03F) | (((uint)value & 0xFF) << 6);
     }
 
     public ushort B
     {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         get => (ushort)((_value >> 23) & 0x1FF); // 9 bits
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => _value = (_value & 0xC07FFFFF) | (((uint)value & 0x1FF) << 23);
     }
 
+    internal uint UIntB
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        get => (_value >> 23) & 0x1FF; // 9 bits
+    }
+
     public ushort C
     {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         get => (ushort)((_value >> 14) & 0x1FF); // 9 bits
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => _value = (_value & 0xFF803FFF) | (((uint)value & 0x1FF) << 14);
     }
 
+    internal uint UIntC
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        get => (_value >> 14) & 0x1FF; // 9 bits
+    }
+
     public uint Bx
     {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         get => (_value >> 14) & 0x3FFFF; // 18 bits (14-31)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => _value = (_value & 0x00003FFF) | ((value & 0x3FFFF) << 14);
     }
 
     public int SBx
     {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         get => (int)(Bx - 131071); // signed 18 bits
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => Bx = (uint)(value + 131071);
     }
 
     public uint Ax
     {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         get => (_value >> 6) & 0x3FFFFFF; // 26 bits (6-31)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         set => _value = (_value & 0x0000003F) | ((value & 0x3FFFFFF) << 6);
     }
 

+ 46 - 18
src/Lua/Runtime/LuaStack.cs

@@ -1,9 +1,10 @@
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using Lua.Internal;
 
 namespace Lua.Runtime;
 
-public class LuaStack(int initialSize = 256)
+public sealed class LuaStack(int initialSize = 256)
 {
     LuaValue[] array = new LuaValue[initialSize];
     int top;
@@ -13,15 +14,21 @@ public class LuaStack(int initialSize = 256)
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     public void EnsureCapacity(int newSize)
     {
-        var size = array.Length;
-        if (size >= newSize) return;
+        if (array.Length >= newSize) return;
 
-        while (size < newSize)
+        Resize(ref array, newSize);
+        return;
+
+        static void Resize(ref LuaValue[] array, int newSize)
         {
-            size *= 2;
-        }
+            var size = array.Length;
+            while (size < newSize)
+            {
+                size *= 2;
+            }
 
-        Array.Resize(ref array, size);
+            Array.Resize(ref array, size);
+        }
     }
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -34,7 +41,7 @@ public class LuaStack(int initialSize = 256)
     public void Push(LuaValue value)
     {
         EnsureCapacity(top + 1);
-        UnsafeGet(top) = value;
+        array[top] = value;
         top++;
     }
 
@@ -51,8 +58,8 @@ public class LuaStack(int initialSize = 256)
     {
         if (top == 0) ThrowEmptyStack();
         top--;
-        var item = UnsafeGet(top);
-        UnsafeGet(top) = default;
+        var item = array[top];
+        array[top] = default;
         return item;
     }
 
@@ -60,14 +67,8 @@ public class LuaStack(int initialSize = 256)
     public void PopUntil(int newSize)
     {
         if (newSize >= top) return;
-        if (newSize == 0)
-        {
-            array.AsSpan().Clear();
-        }
-        else
-        {
-            array.AsSpan(newSize).Clear();
-        }
+
+        array.AsSpan(newSize, top - newSize).Clear();
         top = newSize;
     }
 
@@ -102,6 +103,33 @@ public class LuaStack(int initialSize = 256)
         return ref MemoryMarshalEx.UnsafeElementAt(array, index);
     }
 
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal ref LuaValue Get(int index)
+    {
+        return ref array[index];
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal ref LuaValue FastGet(int index)
+    {
+#if NET6_0_OR_GREATER
+        return ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
+#else
+        return ref array[index];
+#endif
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal ref LuaValue GetWithNotifyTop(int index)
+    {
+        if (this.top <= index) this.top = index + 1;
+#if NET6_0_OR_GREATER
+        return ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), index);
+#else
+        return ref array[index];
+#endif
+    }
+
     static void ThrowEmptyStack()
     {
         throw new InvalidOperationException("Empty stack");

File diff suppressed because it is too large
+ 1466 - 867
src/Lua/Runtime/LuaVirtualMachine.cs


+ 54 - 9
src/Lua/Runtime/Tracebacks.cs

@@ -1,24 +1,69 @@
+using System.Globalization;
+using System.Runtime.CompilerServices;
 using Lua.CodeAnalysis;
+using Lua.Internal;
 
 namespace Lua.Runtime;
 
 public class Traceback
 {
+    public required Closure RootFunc { get; init; }
     public required CallStackFrame[] StackFrames { get; init; }
 
-    internal string RootChunkName => StackFrames.Length == 0 ? "" : StackFrames[^1].RootChunkName;
-    internal SourcePosition LastPosition => StackFrames.Length == 0 ? default : StackFrames[^1].CallPosition!.Value;
+    internal string RootChunkName => RootFunc.Proto.Name; //StackFrames.Length == 0 ? "" : StackFrames[^1].Function is Closure closure ? closure.Proto.GetRoot().Name : StackFrames[^2].Function.Name;
+
+    internal SourcePosition LastPosition
+    {
+        get
+        {
+            var stackFrames = StackFrames.AsSpan();
+            for (var index = stackFrames.Length - 1; index >= 0; index--)
+            {
+                LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
+                var frame = stackFrames[index];
+                if (lastFunc is Closure closure)
+                {
+                    var p = closure.Proto;
+                    return p.SourcePositions[frame.CallerInstructionIndex];
+                }
+            }
+
+            return default;
+        }
+    }
+
 
     public override string ToString()
     {
-        var str = string.Join("\n   ", StackFrames
-            .Where(x => x.CallPosition != null)
-            .Select(x =>
+        using var list = new PooledList<char>(64);
+        list.AddRange("stack traceback:\n");
+        var stackFrames = StackFrames.AsSpan();
+        var intFormatBuffer = (stackalloc char[15]);
+        for (var index = stackFrames.Length - 1; index >= 0; index--)
+        {
+            LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc;
+            var frame = stackFrames[index];
+            if (lastFunc is not null and not Closure)
+            {
+                list.AddRange("\t[C#]: in function '");
+                list.AddRange(lastFunc.Name);
+                list.AddRange("'\n");
+            }
+            else if (lastFunc is Closure closure)
             {
-                return $"{x.RootChunkName}:{x.CallPosition!.Value.Line}: {(string.IsNullOrEmpty(x.ChunkName) ? "" : $"in '{x.ChunkName}'")}";
-            })
-            .Reverse());
+                var p = closure.Proto;
+                var root = p.GetRoot();
+                list.AddRange("\t");
+                list.AddRange(root.Name);
+                list.AddRange(":");
+                p.SourcePositions[frame.CallerInstructionIndex].Line.TryFormat(intFormatBuffer, out var charsWritten,provider:CultureInfo.InvariantCulture);
+                list.AddRange(intFormatBuffer[..charsWritten]);
+                list.AddRange(root == p ? ": in '" : ": in function '");
+                list.AddRange(p.Name);
+                list.AddRange("'\n");
+            }
+        }
 
-        return $"stack traceback:\n   {str}";
+        return list.AsSpan().ToString();
     }
 }

+ 3 - 3
src/Lua/Runtime/UpValue.cs

@@ -42,7 +42,7 @@ public sealed class UpValue
         }
         else
         {
-            return Thread!.Stack.UnsafeGet(RegisterIndex);
+            return Thread!.Stack.Get(RegisterIndex);
         }
     }
 
@@ -55,7 +55,7 @@ public sealed class UpValue
         }
         else
         {
-            Thread!.Stack.UnsafeGet(RegisterIndex) = value;
+            Thread!.Stack.Get(RegisterIndex) = value;
         }
     }
 
@@ -64,7 +64,7 @@ public sealed class UpValue
     {
         if (!IsClosed)
         {
-            value = Thread!.Stack.UnsafeGet(RegisterIndex);
+            value = Thread!.Stack.Get(RegisterIndex);
         }
 
         IsClosed = true;

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

@@ -102,11 +102,11 @@ public sealed class BasicLibrary
 
     public ValueTask<int> Error(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
-        var obj = context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil
+        var value = context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil
             ? "(error object is a nil value)"
-            : context.Arguments[0].ToString();
+            : context.Arguments[0];
 
-        throw new LuaRuntimeException(context.State.GetTraceback(), obj!);
+        throw new LuaRuntimeException(context.State.GetTraceback(), value);
     }
 
     public ValueTask<int> GetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
@@ -156,7 +156,7 @@ public sealed class BasicLibrary
         buffer.Span[2] = 0;
         return new(3);
     }
-    
+
     public async ValueTask<int> LoadFile(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
         // Lua-CSharp does not support binary chunks, the mode argument is ignored.
@@ -240,7 +240,7 @@ public sealed class BasicLibrary
             return new(1);
         }
     }
-    
+
     public ValueTask<int> Pairs(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument<LuaTable>(0);
@@ -285,7 +285,15 @@ public sealed class BasicLibrary
         catch (Exception ex)
         {
             buffer.Span[0] = false;
-            buffer.Span[1] = ex.Message;
+            if (ex is LuaRuntimeException { ErrorObject: not null } luaEx)
+            {
+                buffer.Span[1] = luaEx.ErrorObject.Value;
+            }
+            else
+            {
+                buffer.Span[1] = ex.Message;
+            }
+
             return 2;
         }
     }
@@ -352,7 +360,7 @@ public sealed class BasicLibrary
         arg0[arg1] = arg2;
         return new(0);
     }
-    
+
     public ValueTask<int> Select(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument(0);
@@ -390,7 +398,7 @@ public sealed class BasicLibrary
             return default;
         }
     }
-    
+
     public ValueTask<int> SetMetatable(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument<LuaTable>(0);
@@ -559,7 +567,7 @@ public sealed class BasicLibrary
         var arg0 = context.GetArgument(0);
         return arg0.CallToStringAsync(context, buffer, cancellationToken);
     }
-    
+
     public ValueTask<int> Type(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument(0);
@@ -605,19 +613,21 @@ public sealed class BasicLibrary
         catch (Exception ex)
         {
             methodBuffer.AsSpan().Clear();
+            var error = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message;
 
-            context.State.Push(ex.Message);
+            context.State.Push(error);
 
             // invoke error handler
             await arg1.InvokeAsync(context with
             {
                 State = context.State,
                 ArgumentCount = 1,
-                FrameBase = context.Thread.Stack.Count - context.ArgumentCount,
+                FrameBase = context.Thread.Stack.Count - 1,
             }, methodBuffer.AsMemory(), cancellationToken);
 
             buffer.Span[0] = false;
-            buffer.Span[1] = ex.Message;
+            buffer.Span[1] = methodBuffer[0];
+
 
             return 2;
         }

+ 20 - 6
src/Lua/Standard/CoroutineLibrary.cs

@@ -64,15 +64,29 @@ public sealed class CoroutineLibrary
 
             stack.Push(thread);
             stack.PushRange(context.Arguments);
+            context.Thread.PushCallStackFrame(new()
+            {
+                Base = frameBase,
+                VariableArgumentCount = 0,
+                Function = arg0,
+            });
+            try
+            {
+                var resultCount = await thread.ResumeAsync(context with
+                {
+                    ArgumentCount = context.ArgumentCount + 1,
+                    FrameBase = frameBase,
+                }, buffer, cancellationToken);
 
-            var resultCount = await thread.ResumeAsync(context with
+                buffer.Span[1..].CopyTo(buffer.Span[0..]);
+                return resultCount - 1;
+            }
+            finally
             {
-                ArgumentCount = context.ArgumentCount + 1,
-                FrameBase = frameBase,
-            }, buffer, cancellationToken);
+                context.Thread.PopCallStackFrame();
+            }
 
-            buffer.Span[1..].CopyTo(buffer.Span[0..]);
-            return resultCount - 1;
+           
         });
 
         return new(1);

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

@@ -8,6 +8,7 @@ namespace Lua.Standard;
 public sealed class TableLibrary
 {
     public static readonly TableLibrary Instance = new();
+    
 
     public TableLibrary()
     {
@@ -40,6 +41,7 @@ public sealed class TableLibrary
         ],
         ParameterCount = 2,
         UpValues = [],
+        MaxStackPosition = 2,
     };
 
     public ValueTask<int> Concat(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
@@ -158,9 +160,22 @@ public sealed class TableLibrary
         var arg1 = context.HasArgument(1)
             ? context.GetArgument<LuaFunction>(1)
             : new Closure(context.State, defaultComparer);
-
-        await QuickSortAsync(context, arg0.GetArrayMemory(), 0, arg0.ArrayLength - 1, arg1, cancellationToken);
-        return 0;
+        
+        context.Thread.PushCallStackFrame(new ()
+        {
+            Base = context.FrameBase,
+            VariableArgumentCount = 0,
+            Function = arg1
+        });
+        try
+        {
+            await QuickSortAsync(context, arg0.GetArrayMemory(), 0, arg0.ArrayLength - 1, arg1, cancellationToken);
+            return 0;
+        }
+        finally
+        {
+            context.Thread.PopCallStackFrameUnsafe(context.FrameBase);
+        }
     }
 
     async ValueTask QuickSortAsync(LuaFunctionExecutionContext context, Memory<LuaValue> memory, int low, int high, LuaFunction comparer, CancellationToken cancellationToken)

+ 10 - 5
tests/Lua.Tests/tests-lua/coroutine.lua

@@ -1,7 +1,7 @@
 print "testing coroutines"
 
 -- local debug = require'debug'
-
+local weakTableSupported = false
 local f
 
 local main, ismain = coroutine.running()
@@ -205,8 +205,10 @@ C[1] = x;
 local f = x()
 assert(f() == 21 and x()() == 32 and x() == f)
 x = nil
-collectgarbage()
-assert(C[1] == nil)
+if weakTableSupported then
+  collectgarbage()
+  assert(C[1] == nil)
+end
 assert(f() == 43 and f() == 53)
 
 
@@ -239,8 +241,11 @@ assert(a and b == 3)
 assert(coroutine.status(co1) == 'dead')
 
 -- infinite recursion of coroutines
-a = function(a) coroutine.wrap(a)(a) end
-assert(not pcall(a, a))
+-- C# cannot catch stack overflow exception 
+if false then
+  local a = coroutine.wrap(function () a() end)
+  assert(not pcall(a))
+end
 
 
 -- access to locals of erroneous coroutines

Some files were not shown because too many files changed in this diff