diff --git a/sandbox/Benchmark/AddBenchmark.cs b/sandbox/Benchmark/AddBenchmark.cs index 0c0a9221..99af7b39 100644 --- a/sandbox/Benchmark/AddBenchmark.cs +++ b/sandbox/Benchmark/AddBenchmark.cs @@ -22,10 +22,11 @@ public void Setup() core.Setup("add.lua"); core.LuaCSharpState.OpenStandardLibraries(); - core.LuaCSharpState.Environment["add"] = new LuaFunction("add", (context, buffer, ct) => + core.LuaCSharpState.Environment["add"] = new LuaFunction("add", (context, ct) => { - buffer.Span[0] = context.GetArgument(0) + context.GetArgument(1); - return new(1); + var a = context.GetArgument(0); + var b = context.GetArgument(1); + return new(context.Return(a + b)); }); core.MoonSharpState.Globals["add"] = (Func)Add; core.NLuaState.RegisterFunction("add", typeof(AddBenchmark).GetMethod(nameof(Add), BindingFlags.Static | BindingFlags.Public)); diff --git a/sandbox/Benchmark/InterpreterSteps.cs b/sandbox/Benchmark/InterpreterSteps.cs index 59ed0bb4..70f2de8e 100644 --- a/sandbox/Benchmark/InterpreterSteps.cs +++ b/sandbox/Benchmark/InterpreterSteps.cs @@ -92,6 +92,8 @@ public Chunk Compile() [Benchmark] public async ValueTask RunAsync() { - await state.RunAsync(chunk, results); + using (await state.RunAsync(chunk)) + { + } } } \ No newline at end of file diff --git a/sandbox/ConsoleApp1/Program.cs b/sandbox/ConsoleApp1/Program.cs index 1e986c96..fc81e61f 100644 --- a/sandbox/ConsoleApp1/Program.cs +++ b/sandbox/ConsoleApp1/Program.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using Lua.CodeAnalysis.Syntax; using Lua.CodeAnalysis.Compilation; using Lua.Runtime; @@ -11,7 +12,7 @@ try { - var source = File.ReadAllText("test.lua"); + var source = File.ReadAllText(GetAbsolutePath("test.lua")); var syntaxTree = LuaSyntaxTree.Parse(source, "test.lua"); @@ -26,12 +27,11 @@ Console.WriteLine("Output " + new string('-', 50)); - var results = new LuaValue[64]; - var resultCount = await state.RunAsync(chunk, results); + using var results = await state.RunAsync(chunk); Console.WriteLine("Result " + new string('-', 50)); - for (int i = 0; i < resultCount; i++) + for (int i = 0; i < results.Count; i++) { Console.WriteLine(results[i]); } @@ -41,6 +41,19 @@ catch (Exception ex) { Console.WriteLine(ex); + if (ex is LuaRuntimeException luaRuntimeException) + { + Console.WriteLine(luaRuntimeException.LuaTraceback); + if (ex is { InnerException: not null } luaEx) + { + Console.WriteLine(luaEx.InnerException); + } + } +} + +static string GetAbsolutePath(string relativePath, [CallerFilePath] string callerFilePath = "") +{ + return Path.Combine(Path.GetDirectoryName(callerFilePath)!, relativePath); } static void DebugChunk(Chunk chunk, int id) @@ -56,14 +69,24 @@ static void DebugChunk(Chunk chunk, int id) index++; } - Console.WriteLine("Constants " + new string('-', 50)); index = 0; + Console.WriteLine("Locals " + new string('-', 50)); + index = 0; + foreach (var local in chunk.Locals.ToArray()) + { + Console.WriteLine($"[{index}]\t{local.Index}\t{local.Name}\t{local.StartPc}\t{local.EndPc}"); + index++; + } + + Console.WriteLine("Constants " + new string('-', 50)); + index = 0; foreach (var constant in chunk.Constants.ToArray()) { Console.WriteLine($"[{index}]\t{constant}"); index++; } - Console.WriteLine("UpValues " + new string('-', 50)); index = 0; + Console.WriteLine("UpValues " + new string('-', 50)); + index = 0; foreach (var upValue in chunk.UpValues.ToArray()) { Console.WriteLine($"[{index}]\t{upValue.Name}\t{(upValue.IsInRegister ? 1 : 0)}\t{upValue.Index}"); diff --git a/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs b/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs index 4772b3ef..4d78eb92 100644 --- a/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs +++ b/src/Lua.SourceGenerator/LuaObjectGenerator.Emit.cs @@ -201,7 +201,7 @@ static bool ValidateMembers(TypeMetadata typeMetadata, Compilation compilation, static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, in SourceProductionContext context) { - builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction(""index"", (context, buffer, ct) =>"); + builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_index = new global::Lua.LuaFunction(""index"", (context, ct) =>"); using (builder.BeginBlockScope()) { @@ -233,8 +233,7 @@ static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builde } builder.AppendLine(";"); - builder.AppendLine("buffer.Span[0] = result;"); - builder.AppendLine("return new(1);"); + builder.AppendLine( "return new global::System.Threading.Tasks.ValueTask(context.Return(result));"); } builder.AppendLine(");"); @@ -244,7 +243,7 @@ static bool TryEmitIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builde static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder builder, in SourceProductionContext context) { - builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction(""newindex"", (context, buffer, ct) =>"); + builder.AppendLine(@"static readonly global::Lua.LuaFunction __metamethod_newindex = new global::Lua.LuaFunction(""newindex"", (context, ct) =>"); using (builder.BeginBlockScope()) { @@ -295,8 +294,7 @@ static bool TryEmitNewIndexMetamethod(TypeMetadata typeMetadata, CodeBuilder bui builder.AppendLine(@$"throw new global::Lua.LuaRuntimeException(context.State.GetTraceback(), $""'{{key}}' not found."");"); } } - - builder.AppendLine("return new(0);"); + builder.AppendLine( "return new global::System.Threading.Tasks.ValueTask(context.Return());"); } builder.AppendLine(");"); @@ -348,7 +346,7 @@ static bool TryEmitMethods(TypeMetadata typeMetadata, CodeBuilder builder, Symbo static void EmitMethodFunction(string functionName, string chunkName, TypeMetadata typeMetadata, MethodMetadata methodMetadata, CodeBuilder builder, SymbolReferences references) { - builder.AppendLine($@"static readonly global::Lua.LuaFunction {functionName} = new global::Lua.LuaFunction(""{chunkName}"", {(methodMetadata.IsAsync ? "async" : "")} (context, buffer, ct) =>"); + builder.AppendLine($@"static readonly global::Lua.LuaFunction {functionName} = new global::Lua.LuaFunction(""{chunkName}"", {(methodMetadata.IsAsync ? "async" : "")} (context, ct) =>"); using (builder.BeginBlockScope()) { @@ -413,23 +411,28 @@ static void EmitMethodFunction(string functionName, string chunkName, TypeMetada builder.Append(string.Join(",", Enumerable.Range(1, index - 1).Select(x => $"arg{x}")), false); builder.AppendLine(");", false); } - + + builder.Append("return "); if (methodMetadata.HasReturnValue) { if (SymbolEqualityComparer.Default.Equals(methodMetadata.Symbol.ReturnType, references.LuaValue)) { - builder.AppendLine("buffer.Span[0] = result;"); + + builder.AppendLine(methodMetadata.IsAsync ? + "context.Return(result));" : + "new global::System.Threading.Tasks.ValueTask(context.Return(result));"); } else { - builder.AppendLine("buffer.Span[0] = new global::Lua.LuaValue(result);"); + builder.AppendLine(methodMetadata.IsAsync ? + "context.Return(new global::Lua.LuaValue(result))));" : + "new global::System.Threading.Tasks.ValueTask(context.Return(new global::Lua.LuaValue(result)));"); } - builder.AppendLine($"return {(methodMetadata.IsAsync ? "1" : "new(1)")};"); } else { - builder.AppendLine($"return {(methodMetadata.IsAsync ? "0" : "new(0)")};"); + builder.AppendLine(methodMetadata.IsAsync ? "context.Return();" : "new global::System.Threading.Tasks.ValueTask(context.Return());"); } } builder.AppendLine(");"); diff --git a/src/Lua/CodeAnalysis/Compilation/Descriptions.cs b/src/Lua/CodeAnalysis/Compilation/Descriptions.cs index 5001cbf4..e3313e45 100644 --- a/src/Lua/CodeAnalysis/Compilation/Descriptions.cs +++ b/src/Lua/CodeAnalysis/Compilation/Descriptions.cs @@ -6,6 +6,7 @@ namespace Lua.CodeAnalysis.Compilation public readonly record struct LocalVariableDescription { public required byte RegisterIndex { get; init; } + public required int StartPc { get; init; } } public readonly record struct FunctionDescription diff --git a/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs b/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs index e564f660..dfa2d73f 100644 --- a/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs +++ b/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs @@ -60,6 +60,7 @@ internal static FunctionCompilationContext Create(ScopeCompilationContext? paren // upvalues FastListCore upvalues; + FastListCore localVariables; // loop FastListCore breakQueue; @@ -90,6 +91,16 @@ internal static FunctionCompilationContext Create(ScopeCompilationContext? paren /// public bool HasVariableArguments { get; set; } + /// + /// Line number where the function is defined + /// + public int LineDefined { get; set; } + + /// + /// Last line number where the function is defined + /// + public int LastLineDefined { get; set; } + /// /// Parent scope context /// @@ -127,6 +138,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in instructionPositions.Add(position); return; } + ref var lastInstruction = ref instructions.AsSpan()[^1]; var opcode = instruction.OpCode; switch (opcode) @@ -156,6 +168,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in } } } + break; case OpCode.GetTable: { @@ -169,8 +182,8 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in incrementStackPosition = false; return; } - } + break; } case OpCode.SetTable: @@ -197,6 +210,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in return; } } + lastInstruction = Instruction.SetTable((byte)(lastB), instruction.B, instruction.C); instructionPositions[^1] = position; incrementStackPosition = false; @@ -217,7 +231,6 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in var last2OpCode = last2Instruction.OpCode; if (last2OpCode is OpCode.LoadK or OpCode.Move) { - var last2A = last2Instruction.A; if (last2A != lastLocal && instruction.C == last2A) { @@ -231,6 +244,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in } } } + break; } case OpCode.Unm: @@ -238,11 +252,13 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in case OpCode.Len: if (lastInstruction.OpCode == OpCode.Move && lastLocal != lastInstruction.A && lastInstruction.A == instruction.B) { - lastInstruction = instruction with { B = lastInstruction.B }; ; + lastInstruction = instruction with { B = lastInstruction.B }; + ; instructionPositions[^1] = position; incrementStackPosition = false; return; } + break; case OpCode.Return: if (lastInstruction.OpCode == OpCode.Move && instruction.B == 2 && lastInstruction.B < 256) @@ -252,6 +268,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in incrementStackPosition = false; return; } + break; } @@ -302,6 +319,17 @@ public bool TryGetFunctionProto(ReadOnlyMemory name, [NotNullWhen(true)] o } } + public void AddLocalVariable(ReadOnlyMemory name, LocalVariableDescription description) + { + localVariables.Add(new LocalValueInfo() + { + Name = name, + Index = description.RegisterIndex, + StartPc = description.StartPc, + EndPc = Instructions.Length, + }); + } + public void AddUpValue(UpValueInfo upValue) { upvalues.Add(upValue); @@ -378,6 +406,7 @@ public void ResolveAllBreaks(byte startPosition, int endPosition, ScopeCompilati { instruction.A = startPosition; } + instruction.SBx = endPosition - description.Index; } @@ -409,8 +438,10 @@ public Chunk ToChunk() { // add return instructions.Add(Instruction.Return(0, 1)); - instructionPositions.Add(instructionPositions.Length == 0 ? default : instructionPositions[^1]); - + instructionPositions.Add( new (LastLineDefined, 0)); + Scope.RegisterLocalsToFunction(); + var locals = localVariables.AsSpan().ToArray(); + Array.Sort(locals, (x, y) => x.Index.CompareTo(y.Index)); var chunk = new Chunk() { Name = ChunkName ?? "chunk", @@ -418,9 +449,13 @@ public Chunk ToChunk() SourcePositions = instructionPositions.AsSpan().ToArray(), Constants = constants.AsSpan().ToArray(), UpValues = upvalues.AsSpan().ToArray(), + Locals = locals, Functions = functions.AsSpan().ToArray(), ParameterCount = ParameterCount, + HasVariableArguments = HasVariableArguments, MaxStackPosition = MaxStackPosition, + LineDefined = LineDefined, + LastLineDefined = LastLineDefined, }; foreach (var function in functions.AsSpan()) @@ -442,6 +477,7 @@ public void Reset() constantIndexMap.Clear(); constants.Clear(); upvalues.Clear(); + localVariables.Clear(); functionMap.Clear(); functions.Clear(); breakQueue.Clear(); diff --git a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs b/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs index 3cd17910..f6696f9e 100644 --- a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs +++ b/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs @@ -20,7 +20,9 @@ public Chunk Compile(string source, string? chunkName = null) public Chunk Compile(LuaSyntaxTree syntaxTree, string? chunkName = null) { using var context = FunctionCompilationContext.Create(null); - + context.HasVariableArguments = true; + context.LineDefined = syntaxTree.Position.Line; + context.LastLineDefined = syntaxTree.Position.Line; // set global enviroment upvalue context.AddUpValue(new() { @@ -466,9 +468,11 @@ public bool VisitLocalAssignmentStatementNode(LocalAssignmentStatementNode node, context.AddLocalVariable(identifier.Name, new() { RegisterIndex = (byte)(context.StackPosition - 1), + StartPc = context.Function.Instructions.Length, }); } } + return true; } @@ -584,10 +588,10 @@ void CompileCallFunctionExpression(CallFunctionExpressionNode node, ScopeCompila // function declaration public bool VisitFunctionDeclarationExpressionNode(FunctionDeclarationExpressionNode node, ScopeCompilationContext context) { - var funcIndex = CompileFunctionProto(ReadOnlyMemory.Empty, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false); + var funcIndex = CompileFunctionProto(ReadOnlyMemory.Empty, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line); // push closure instruction - context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true); + context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true); return true; } @@ -598,26 +602,27 @@ public bool VisitLocalFunctionDeclarationStatementNode(LocalFunctionDeclarationS context.AddLocalVariable(node.Name, new() { RegisterIndex = context.StackPosition, + StartPc = context.Function.Instructions.Length, }); // compile function - var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false); + var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line); // push closure instruction - context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true); + context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true); return true; } public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNode node, ScopeCompilationContext context) { - var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false); + var funcIndex = CompileFunctionProto(node.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length, node.HasVariableArguments, false, node.LineDefined, node.EndPosition.Line); // add closure var index = context.Function.GetConstantIndex(node.Name.ToString()); // push closure instruction - context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.Position, true); + context.PushInstruction(Instruction.Closure(context.StackPosition, funcIndex), node.EndPosition, true); if (context.TryGetLocalVariableInThisScope(node.Name, out var variable)) { @@ -636,7 +641,7 @@ public bool VisitFunctionDeclarationStatementNode(FunctionDeclarationStatementNo public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationStatementNode node, ScopeCompilationContext context) { var funcIdentifier = node.MemberPath[^1]; - var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length + 1, node.HasVariableArguments, node.HasSelfParameter); + var funcIndex = CompileFunctionProto(funcIdentifier.Name, context, node.ParameterNodes, node.Nodes, node.ParameterNodes.Length + 1, node.HasVariableArguments, node.HasSelfParameter, node.LineDefined, node.EndPosition.Line); // add closure var index = context.Function.GetConstantIndex(funcIdentifier.Name.ToString()); @@ -657,7 +662,7 @@ public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationState // push closure instruction var closureIndex = context.StackPosition; - context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.Position, true); + context.PushInstruction(Instruction.Closure(closureIndex, funcIndex), node.EndPosition, true); // set table context.PushInstruction(Instruction.SetTable(tableIndex, (ushort)(index + 256), closureIndex), funcIdentifier.Position); @@ -666,18 +671,21 @@ public bool VisitTableMethodDeclarationStatementNode(TableMethodDeclarationState return true; } - int CompileFunctionProto(ReadOnlyMemory functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, int parameterCount, bool hasVarArg, bool hasSelfParameter) + int CompileFunctionProto(ReadOnlyMemory functionName, ScopeCompilationContext context, IdentifierNode[] parameters, SyntaxNode[] statements, int parameterCount, bool hasVarArg, bool hasSelfParameter, int lineDefined, int lastLineDefined) { using var funcContext = context.CreateChildFunction(); funcContext.ChunkName = functionName.ToString(); funcContext.ParameterCount = parameterCount; funcContext.HasVariableArguments = hasVarArg; + funcContext.LineDefined = lineDefined; + funcContext.LastLineDefined = lastLineDefined; if (hasSelfParameter) { funcContext.Scope.AddLocalVariable("self".AsMemory(), new() { RegisterIndex = 0, + StartPc = 0, }); funcContext.Scope.StackPosition++; @@ -690,6 +698,7 @@ int CompileFunctionProto(ReadOnlyMemory functionName, ScopeCompilationCont funcContext.Scope.AddLocalVariable(parameter.Name, new() { RegisterIndex = (byte)(i + (hasSelfParameter ? 1 : 0)), + StartPc = 0, }); funcContext.Scope.StackPosition++; @@ -751,10 +760,10 @@ public bool VisitIfStatementNode(IfStatementNode node, ScopeCompilationContext c // if using (var scopeContext = context.CreateChildScope()) { - CompileConditionNode(node.IfNode.ConditionNode, scopeContext, true); + CompileConditionNode(node.IfNode.ConditionNode, scopeContext, true, node.IfNode.Position); var ifPosition = scopeContext.Function.Instructions.Length; - scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.Position); + scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.IfNode.Position); foreach (var childNode in node.IfNode.ThenNodes) { @@ -765,7 +774,7 @@ public bool VisitIfStatementNode(IfStatementNode node, ScopeCompilationContext c if (hasElse) { endJumpIndexList.Add(scopeContext.Function.Instructions.Length); - scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.Position, true); + scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.IfNode.ThenNodes[^1].Position, true); } else { @@ -783,7 +792,7 @@ public bool VisitIfStatementNode(IfStatementNode node, ScopeCompilationContext c CompileConditionNode(elseIf.ConditionNode, scopeContext, true); var elseifPosition = scopeContext.Function.Instructions.Length; - scopeContext.PushInstruction(Instruction.Jmp(0, 0), node.Position); + scopeContext.PushInstruction(Instruction.Jmp(0, 0), elseIf.Position); foreach (var childNode in elseIf.ThenNodes) { @@ -795,11 +804,11 @@ public bool VisitIfStatementNode(IfStatementNode node, ScopeCompilationContext c if (hasElse) { endJumpIndexList.Add(scopeContext.Function.Instructions.Length); - scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), node.Position); + scopeContext.PushInstruction(Instruction.Jmp(stackPositionToClose, 0), elseIf.Position); } else { - scopeContext.TryPushCloseUpValue(stackPositionToClose, node.Position); + scopeContext.TryPushCloseUpValue(stackPositionToClose, elseIf.Position); } scopeContext.Function.Instructions[elseifPosition].SBx = scopeContext.Function.Instructions.Length - 1 - elseifPosition; @@ -840,8 +849,9 @@ public bool VisitRepeatStatementNode(RepeatStatementNode node, ScopeCompilationC CompileConditionNode(node.ConditionNode, scopeContext, true); var a = scopeContext.HasCapturedLocalVariables ? (byte)(stackPosition + 1) : (byte)0; - scopeContext.PushInstruction(Instruction.Jmp(a, startIndex - scopeContext.Function.Instructions.Length - 1), node.Position); - scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, node.Position); + var untilPosition = node.ConditionNode.Position; + scopeContext.PushInstruction(Instruction.Jmp(a, startIndex - scopeContext.Function.Instructions.Length - 1), untilPosition); + scopeContext.TryPushCloseUpValue(scopeContext.StackPosition, untilPosition); context.Function.LoopLevel--; @@ -895,20 +905,39 @@ public bool VisitNumericForStatementNode(NumericForStatementNode node, ScopeComp else { var index = context.Function.GetConstantIndex(1); - context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.Position, true); + context.PushInstruction(Instruction.LoadK(context.StackPosition, index), node.DoPosition, true); } var prepIndex = context.Function.Instructions.Length; - context.PushInstruction(Instruction.ForPrep(startPosition, 0), node.Position, true); + context.PushInstruction(Instruction.ForPrep(startPosition, 0), node.DoPosition, true); // compile statements context.Function.LoopLevel++; using var scopeContext = context.CreateChildScope(); { + scopeContext.AddLocalVariable("(for index)".AsMemory(), new() + { + RegisterIndex = startPosition, + StartPc = context.Function.Instructions.Length, + }); + + scopeContext.AddLocalVariable("(for limit)".AsMemory(), new() + { + RegisterIndex = (byte)(startPosition + 1), + StartPc = context.Function.Instructions.Length, + }); + + scopeContext.AddLocalVariable("(for step)".AsMemory(), new() + { + RegisterIndex = (byte)(startPosition + 2), + StartPc = context.Function.Instructions.Length, + }); + // add local variable scopeContext.AddLocalVariable(node.VariableName, new() { - RegisterIndex = startPosition + RegisterIndex = (byte)(startPosition + 3), + StartPc = context.Function.Instructions.Length, }); foreach (var childNode in node.StatementNodes) @@ -941,7 +970,7 @@ public bool VisitGenericForStatementNode(GenericForStatementNode node, ScopeComp // jump to TFORCALL var startJumpIndex = context.Function.Instructions.Length; - context.PushInstruction(Instruction.Jmp(0, 0), node.Position); + context.PushInstruction(Instruction.Jmp(0, 0), node.DoPosition); // compile statements context.Function.LoopLevel++; @@ -949,13 +978,32 @@ public bool VisitGenericForStatementNode(GenericForStatementNode node, ScopeComp { scopeContext.StackPosition = (byte)(startPosition + 3 + node.Names.Length); + scopeContext.AddLocalVariable("(for generator)".AsMemory(), new() + { + RegisterIndex = (byte)(startPosition), + StartPc = context.Function.Instructions.Length, + }); + + scopeContext.AddLocalVariable("(for state)".AsMemory(), new() + { + RegisterIndex = (byte)(startPosition + 1), + StartPc = context.Function.Instructions.Length, + }); + + scopeContext.AddLocalVariable("(for control)".AsMemory(), new() + { + RegisterIndex = (byte)(startPosition + 2), + StartPc = context.Function.Instructions.Length, + }); + // add local variables for (int i = 0; i < node.Names.Length; i++) { var name = node.Names[i]; scopeContext.AddLocalVariable(name.Name, new() { - RegisterIndex = (byte)(startPosition + 3 + i) + RegisterIndex = (byte)(startPosition + 3 + i), + StartPc = context.Function.Instructions.Length, }); } @@ -1102,6 +1150,7 @@ static bool TryGetConstant(ExpressionNode node, ScopeCompilationContext context, { value = stringLiteral.Text.ToString(); } + return true; case UnaryExpressionNode unaryExpression: if (TryGetConstant(unaryExpression.Node, context, out var unaryNodeValue)) @@ -1114,6 +1163,7 @@ static bool TryGetConstant(ExpressionNode node, ScopeCompilationContext context, value = -d1; return true; } + break; case UnaryOperator.Not: if (unaryNodeValue.TryRead(out var b)) @@ -1121,9 +1171,11 @@ static bool TryGetConstant(ExpressionNode node, ScopeCompilationContext context, value = !b; return true; } + break; } } + break; case BinaryExpressionNode binaryExpression: if (TryGetConstant(binaryExpression.LeftNode, context, out var leftValue) && @@ -1169,6 +1221,7 @@ static bool TryGetConstant(ExpressionNode node, ScopeCompilationContext context, break; } } + break; } @@ -1187,7 +1240,8 @@ static bool IsFixedNumberOfReturnValues(ExpressionNode node) /// Condition node /// Context /// If true, generates an instruction sequence that skips the next instruction if the condition is false. - void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, bool falseIsSkip) + /// Position of the test instruction + void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, bool falseIsSkip, SourcePosition? testPosition = null) { if (node is BinaryExpressionNode binaryExpression) { @@ -1239,7 +1293,7 @@ void CompileConditionNode(ExpressionNode node, ScopeCompilationContext context, } node.Accept(this, context); - context.PushInstruction(Instruction.Test((byte)(context.StackPosition - 1), falseIsSkip ? (byte)0 : (byte)1), node.Position); + context.PushInstruction(Instruction.Test((byte)(context.StackPosition - 1), falseIsSkip ? (byte)0 : (byte)1), testPosition ?? node.Position); } void CompileExpressionList(SyntaxNode rootNode, ExpressionNode[] expressions, int minimumCount, ScopeCompilationContext context) diff --git a/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs b/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs index 51c67984..3be2b072 100644 --- a/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs +++ b/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs @@ -153,6 +153,14 @@ public bool TryGetLabel(ReadOnlyMemory name, out LabelDescription descript return false; } + + public void RegisterLocalsToFunction() + { + foreach (var localVariable in localVariables) + { + Function.AddLocalVariable(localVariable.Key, localVariable.Value); + } + } /// /// Resets the values ​​held in the context. @@ -173,6 +181,7 @@ public void Reset() /// public void Dispose() { + RegisterLocalsToFunction(); Function = null!; Pool.Return(this); } diff --git a/src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs b/src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs index 5404489a..f0f18172 100644 --- a/src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs +++ b/src/Lua/CodeAnalysis/Syntax/LuaSyntaxTree.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax; -public record LuaSyntaxTree(SyntaxNode[] Nodes) : SyntaxNode(new SourcePosition(0, 0)) +public record LuaSyntaxTree(SyntaxNode[] Nodes,SourcePosition Position) : SyntaxNode(Position) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs index e604af75..c1ce41d1 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationExpressionNode.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax.Nodes; -public record FunctionDeclarationExpressionNode(IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position) : ExpressionNode(Position) +public record FunctionDeclarationExpressionNode(IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position, int LineDefined,SourcePosition EndPosition) : ExpressionNode(Position) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs index 181ee0ff..08858c47 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/FunctionDeclarationStatementNode.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax.Nodes; -public record FunctionDeclarationStatementNode(ReadOnlyMemory Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position) : StatementNode(Position) +public record FunctionDeclarationStatementNode(ReadOnlyMemory Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position,int LineDefined,SourcePosition EndPosition) : StatementNode(Position) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs index 74b62479..d4c34afd 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/GenericForStatementNode.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax.Nodes; -public record GenericForStatementNode(IdentifierNode[] Names, ExpressionNode[] ExpressionNodes, StatementNode[] StatementNodes, SourcePosition Position) : StatementNode(Position) +public record GenericForStatementNode(IdentifierNode[] Names, ExpressionNode[] ExpressionNodes, StatementNode[] StatementNodes, SourcePosition Position, SourcePosition DoPosition, SourcePosition EndPosition) : StatementNode(Position) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs index 79aa885a..37853641 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/IfStatementNode.cs @@ -4,6 +4,7 @@ public record IfStatementNode(IfStatementNode.ConditionAndThenNodes IfNode, IfSt { public record ConditionAndThenNodes { + public SourcePosition Position; public required ExpressionNode ConditionNode; public required StatementNode[] ThenNodes; } diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs index dd31836a..34396555 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/LocalFunctionDeclarationNode.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax.Nodes; -public record LocalFunctionDeclarationStatementNode(ReadOnlyMemory Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position) : FunctionDeclarationStatementNode(Name, ParameterNodes, Nodes, HasVariableArguments, Position) +public record LocalFunctionDeclarationStatementNode(ReadOnlyMemory Name, IdentifierNode[] ParameterNodes, SyntaxNode[] Nodes, bool HasVariableArguments, SourcePosition Position, int LineDefined,SourcePosition EndPosition) : FunctionDeclarationStatementNode(Name, ParameterNodes, Nodes, HasVariableArguments, Position, LineDefined, EndPosition) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs index 43167744..b64fa57b 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/NumericForStatementNode.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax.Nodes; -public record NumericForStatementNode(ReadOnlyMemory VariableName, ExpressionNode InitNode, ExpressionNode LimitNode, ExpressionNode? StepNode, StatementNode[] StatementNodes, SourcePosition Position) : StatementNode(Position) +public record NumericForStatementNode(ReadOnlyMemory VariableName, ExpressionNode InitNode, ExpressionNode LimitNode, ExpressionNode? StepNode, StatementNode[] StatementNodes, SourcePosition Position,SourcePosition DoPosition) : StatementNode(Position) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs b/src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs index dc4b87d1..cb159633 100644 --- a/src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs +++ b/src/Lua/CodeAnalysis/Syntax/Nodes/TableMethodDeclarationStatementNode.cs @@ -1,6 +1,6 @@ namespace Lua.CodeAnalysis.Syntax.Nodes; -public record TableMethodDeclarationStatementNode(IdentifierNode[] MemberPath, IdentifierNode[] ParameterNodes, StatementNode[] Nodes, bool HasVariableArguments, bool HasSelfParameter, SourcePosition Position) : StatementNode(Position) +public record TableMethodDeclarationStatementNode(IdentifierNode[] MemberPath, IdentifierNode[] ParameterNodes, StatementNode[] Nodes, bool HasVariableArguments, bool HasSelfParameter, SourcePosition Position, int LineDefined,SourcePosition EndPosition) : StatementNode(Position) { public override TResult Accept(ISyntaxNodeVisitor visitor, TContext context) { diff --git a/src/Lua/CodeAnalysis/Syntax/Parser.cs b/src/Lua/CodeAnalysis/Syntax/Parser.cs index 99984512..4ac87574 100644 --- a/src/Lua/CodeAnalysis/Syntax/Parser.cs +++ b/src/Lua/CodeAnalysis/Syntax/Parser.cs @@ -33,8 +33,19 @@ public LuaSyntaxTree Parse() var node = ParseStatement(ref enumerator); root.Add(node); } + var tokensSpan = tokens.AsSpan(); + var lastToken = tokensSpan[0]; + for (int i = tokensSpan.Length-1; 0(64); using var elseIfBuilder = new PooledList(64); @@ -280,6 +295,7 @@ IfStatementNode ParseIfStatement(ref SyntaxTokenEnumerator enumerator) case 0: ifNodes = new() { + Position = thenToken.Position, ConditionNode = condition, ThenNodes = builder.AsSpan().ToArray(), }; @@ -288,6 +304,7 @@ IfStatementNode ParseIfStatement(ref SyntaxTokenEnumerator enumerator) case 1: elseIfBuilder.Add(new() { + Position = thenToken.Position, ConditionNode = condition, ThenNodes = builder.AsSpan().ToArray(), }); @@ -311,7 +328,7 @@ IfStatementNode ParseIfStatement(ref SyntaxTokenEnumerator enumerator) // check 'then' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Then); - + thenToken = enumerator.Current; // set elseif state state = 1; @@ -353,7 +370,7 @@ WhileStatementNode ParseWhileStatement(ref SyntaxTokenEnumerator enumerator) CheckCurrent(ref enumerator, SyntaxTokenType.Do); // parse statements - var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); + var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _); return new WhileStatementNode(condition, statements, whileToken.Position); } @@ -365,7 +382,7 @@ RepeatStatementNode ParseRepeatStatement(ref SyntaxTokenEnumerator enumerator) var repeatToken = enumerator.Current; // parse statements - var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until); + var statements = ParseStatementList(ref enumerator, SyntaxTokenType.Until, out _); // skip 'until keyword' CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Until, out _); @@ -418,11 +435,12 @@ NumericForStatementNode ParseNumericForStatement(ref SyntaxTokenEnumerator enume // skip 'do' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Do); + var doToken = enumerator.Current; // parse statements - var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); + var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out _); - return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position); + return new NumericForStatementNode(varName, initialValueNode, limitNode, stepNode, statements, forToken.Position, doToken.Position); } GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken forToken) @@ -433,33 +451,33 @@ GenericForStatementNode ParseGenericForStatement(ref SyntaxTokenEnumerator enume // skip 'in' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.In, out _); enumerator.SkipEoL(); - + var iteratorToken = enumerator.Current; var expressions = ParseExpressionList(ref enumerator); MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); // skip 'do' keyword CheckCurrent(ref enumerator, SyntaxTokenType.Do); - + var doToken = enumerator.Current; // parse statements - var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); + var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken); - return new GenericForStatementNode(identifiers, expressions, statements, forToken.Position); + return new GenericForStatementNode(identifiers, expressions, statements, iteratorToken.Position, doToken.Position, endToken.Position); } FunctionDeclarationStatementNode ParseFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken) { - var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false); - return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position); + var (Name, Identifiers, Statements, HasVariableArgments, LineDefined, EndPosition) = ParseFunctionDeclarationCore(ref enumerator, false); + return new FunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, EndPosition); } LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken) { - var (Name, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, false); - return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position); + var (Name, Identifiers, Statements, HasVariableArgments, LineDefined, EndPosition) = ParseFunctionDeclarationCore(ref enumerator, false); + return new LocalFunctionDeclarationStatementNode(Name, Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, EndPosition); } - (ReadOnlyMemory Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous) + (ReadOnlyMemory Name, IdentifierNode[] Identifiers, StatementNode[] Statements, bool HasVariableArgments, int LineDefined, SourcePosition EndPosition) ParseFunctionDeclarationCore(ref SyntaxTokenEnumerator enumerator, bool isAnonymous) { ReadOnlyMemory name; @@ -478,7 +496,7 @@ LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref } // skip '(' - CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out _); + CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.LParen, out var leftParenToken); enumerator.SkipEoL(); // parse parameters @@ -494,16 +512,16 @@ LocalFunctionDeclarationStatementNode ParseLocalFunctionDeclarationStatement(ref CheckCurrent(ref enumerator, SyntaxTokenType.RParen); // parse statements - var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); + var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken); - return (name, identifiers, statements, hasVarArg); + return (name, identifiers, statements, hasVarArg, leftParenToken.Position.Line, endToken.Position); } TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref SyntaxTokenEnumerator enumerator, SyntaxToken functionToken) { using var names = new PooledList(32); var hasSelfParameter = false; - + SyntaxToken leftParenToken; while (true) { CheckCurrent(ref enumerator, SyntaxTokenType.Identifier); @@ -517,6 +535,7 @@ TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref Syn { LuaParseException.UnexpectedToken(ChunkName, enumerator.Current.Position, enumerator.Current); } + hasSelfParameter = enumerator.Current.Type is SyntaxTokenType.Colon; MoveNextWithValidation(ref enumerator); @@ -524,6 +543,7 @@ TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref Syn } else if (enumerator.Current.Type is SyntaxTokenType.LParen) { + leftParenToken = enumerator.Current; // skip '(' MoveNextWithValidation(ref enumerator); enumerator.SkipEoL(); @@ -544,9 +564,9 @@ TableMethodDeclarationStatementNode ParseTableMethodDeclarationStatement(ref Syn CheckCurrent(ref enumerator, SyntaxTokenType.RParen); // parse statements - var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End); + var statements = ParseStatementList(ref enumerator, SyntaxTokenType.End, out var endToken); - return new TableMethodDeclarationStatementNode(names.AsSpan().ToArray(), identifiers, statements, hasVarArg, hasSelfParameter, functionToken.Position); + return new TableMethodDeclarationStatementNode(names.AsSpan().ToArray(), identifiers, statements, hasVarArg, hasSelfParameter, functionToken.Position, leftParenToken.Position.Line, endToken.Position); } bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence precedence, [NotNullWhen(true)] out ExpressionNode? result) @@ -579,7 +599,7 @@ bool TryParseExpression(ref SyntaxTokenEnumerator enumerator, OperatorPrecedence // nested table access & function call RECURSIVE: enumerator.SkipEoL(); - + var nextType = enumerator.GetNext().Type; if (nextType is SyntaxTokenType.LSquare or SyntaxTokenType.Dot or SyntaxTokenType.Colon) { @@ -851,9 +871,9 @@ FunctionDeclarationExpressionNode ParseFunctionDeclarationExpression(ref SyntaxT // skip 'function' keyword CheckCurrentAndSkip(ref enumerator, SyntaxTokenType.Function, out var functionToken); enumerator.SkipEoL(); - - var (_, Identifiers, Statements, HasVariableArgments) = ParseFunctionDeclarationCore(ref enumerator, true); - return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, functionToken.Position); + + var (_, Identifiers, Statements, HasVariableArgments, LineDefined, LastLineDefined) = ParseFunctionDeclarationCore(ref enumerator, true); + return new FunctionDeclarationExpressionNode(Identifiers, Statements, HasVariableArgments, functionToken.Position, LineDefined, LastLineDefined); } ExpressionNode[] ParseCallFunctionArguments(ref SyntaxTokenEnumerator enumerator) @@ -946,20 +966,22 @@ IdentifierNode[] ParseIdentifierList(ref SyntaxTokenEnumerator enumerator) return buffer.AsSpan().ToArray(); } - StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endToken) + StatementNode[] ParseStatementList(ref SyntaxTokenEnumerator enumerator, SyntaxTokenType endTokenType, out SyntaxToken endToken) { using var statements = new PooledList(64); // parse statements while (enumerator.MoveNext()) { - if (enumerator.Current.Type == endToken) break; + if (enumerator.Current.Type == endTokenType) break; if (enumerator.Current.Type is SyntaxTokenType.EndOfLine or SyntaxTokenType.SemiColon) continue; var node = ParseStatement(ref enumerator); statements.Add(node); } + endToken = enumerator.Current; + return statements.AsSpan().ToArray(); } diff --git a/src/Lua/Exceptions.cs b/src/Lua/Exceptions.cs index 8774ff51..0301c18e 100644 --- a/src/Lua/Exceptions.cs +++ b/src/Lua/Exceptions.cs @@ -100,6 +100,11 @@ public static void BadArgument(Traceback traceback, int argumentId, string funct 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) + { + throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' ({message})"); + } + public static void BadArgumentNumberIsNotInteger(Traceback traceback, int argumentId, string functionName) { throw new LuaRuntimeException(traceback, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)"); diff --git a/src/Lua/Internal/BitFlags.cs b/src/Lua/Internal/BitFlags.cs new file mode 100644 index 00000000..2f5a2649 --- /dev/null +++ b/src/Lua/Internal/BitFlags.cs @@ -0,0 +1,38 @@ +namespace Lua.Internal; + +internal struct BitFlags2 +{ + public byte Value; + + public bool Flag0 + { + get => (Value & 1) == 1; + set + { + if (value) + { + Value |= 1; + } + else + { + Value = (byte)(Value & ~1); + } + } + } + + public bool Flag1 + { + get => (Value & 2) == 2; + set + { + if (value) + { + Value |= 2; + } + else + { + Value = (byte)(Value & ~2); + } + } + } +} \ No newline at end of file diff --git a/src/Lua/Internal/FastStackCore.cs b/src/Lua/Internal/FastStackCore.cs index 2879dbf1..485daae9 100644 --- a/src/Lua/Internal/FastStackCore.cs +++ b/src/Lua/Internal/FastStackCore.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -100,6 +101,17 @@ public T Peek() return result; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal ref T PeekRef() + { + if (tail == 0) + { + ThrowForEmptyStack(); + } + + return ref array[tail - 1]!; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void EnsureCapacity(int capacity) @@ -130,7 +142,8 @@ public void Clear() array.AsSpan(0, tail).Clear(); tail = 0; } - + + [DoesNotReturn] void ThrowForEmptyStack() { throw new InvalidOperationException("Empty stack"); diff --git a/src/Lua/Internal/LuaDebug.cs b/src/Lua/Internal/LuaDebug.cs new file mode 100644 index 00000000..b15098f1 --- /dev/null +++ b/src/Lua/Internal/LuaDebug.cs @@ -0,0 +1,696 @@ +using Lua.Runtime; +using static Lua.Internal.OpMode; +using static Lua.Internal.OpArgMask; + +namespace Lua.Internal; + +internal readonly struct LuaDebug : IDisposable +{ + readonly LuaDebugBuffer buffer; + readonly uint version; + + LuaDebug(LuaDebugBuffer buffer, uint version) + { + this.buffer = buffer; + this.version = version; + } + + public string? Name + { + get + { + CheckVersion(); + return buffer.Name; + } + } + + public string? NameWhat + { + get + { + CheckVersion(); + return buffer.NameWhat; + } + } + + public string? What + { + get + { + CheckVersion(); + return buffer.What; + } + } + + public string? Source + { + get + { + CheckVersion(); + return buffer.Source; + } + } + + public int CurrentLine + { + get + { + CheckVersion(); + return buffer.CurrentLine; + } + } + + public int LineDefined + { + get + { + CheckVersion(); + return buffer.LineDefined; + } + } + + public int LastLineDefined + { + get + { + CheckVersion(); + return buffer.LastLineDefined; + } + } + + public int UpValueCount + { + get + { + CheckVersion(); + return buffer.UpValueCount; + } + } + + public int ParameterCount + { + get + { + CheckVersion(); + return buffer.ParameterCount; + } + } + + public bool IsVarArg + { + get + { + CheckVersion(); + return buffer.IsVarArg; + } + } + + public bool IsTailCall + { + get + { + CheckVersion(); + return buffer.IsTailCall; + } + } + + public ReadOnlySpan ShortSource + { + get + { + CheckVersion(); + return buffer.ShortSource.AsSpan(0, buffer.ShortSourceLength); + } + } + + + public static LuaDebug Create(LuaState state, CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan what, out bool isValid) + { + if (!state.DebugBufferPool.TryPop(out var buffer)) + { + buffer = new(state); + } + + isValid = buffer.GetInfo(prevFrame, frame, function, pc, what); + + return new(buffer, buffer.version); + } + + public void CheckVersion() + { + if (buffer.version != version) ThrowObjectDisposedException(); + } + + + public void Dispose() + { + if (buffer.version != version) ThrowObjectDisposedException(); + buffer.Return(version); + } + + void ThrowObjectDisposedException() + { + throw new ObjectDisposedException("This has been disposed"); + } + + + internal class LuaDebugBuffer(LuaState state) + { + internal uint version; + LuaState state = state; + public string? Name; + public string? NameWhat; + public string? What; + public string? Source; + public int CurrentLine; + public int LineDefined; + public int LastLineDefined; + public int UpValueCount; + public int ParameterCount; + public bool IsVarArg; + public bool IsTailCall; + public readonly char[] ShortSource = new char[59]; + public int ShortSourceLength; + + internal void Return(uint version) + { + if (this.version != version) throw new ObjectDisposedException("Buffer has been modified"); + + Name = null; + NameWhat = null; + What = null; + Source = null; + CurrentLine = 0; + LineDefined = 0; + LastLineDefined = 0; + UpValueCount = 0; + ParameterCount = 0; + IsVarArg = false; + IsTailCall = false; + + if (version < uint.MaxValue) + { + this.version++; + state.DebugBufferPool.Push(this); + } + } + + + internal bool GetInfo(CallStackFrame? prevFrame, CallStackFrame? frame, LuaFunction function, int pc, ReadOnlySpan what) + { + LuaClosure? closure = function as LuaClosure; + int status = 1; + foreach (var c in what) + { + switch (c) + { + case 'S': + { + GetFuncInfo(function); + break; + } + case 'l': + { + CurrentLine = (pc >= 0 && closure is not null) ? closure.Proto.SourcePositions[pc].Line : -1; + break; + } + case 'u': + { + UpValueCount = (closure is null) ? 0 : closure.UpValues.Length; + if (closure is null) + { + IsVarArg = true; + ParameterCount = 0; + } + else + { + IsVarArg = closure.Proto.HasVariableArguments; + ParameterCount = closure.Proto.ParameterCount; + } + + break; + } + case 't': + { + IsTailCall = frame.HasValue && (frame.Value.Flags | CallStackFrameFlags.TailCall) == frame.Value.Flags; + break; + } + case 'n': + { + /* calling function is a known Lua function? */ + if (prevFrame is { Function: LuaClosure prevFrameClosure }) + NameWhat = GetFuncName(prevFrameClosure.Proto, frame?.CallerInstructionIndex ?? 0, out Name); + else + NameWhat = null; + if (NameWhat is null) + { + NameWhat = ""; /* not found */ + Name = null; + } + else if (NameWhat != null && Name is "?") + { + Name = function.Name; + } + + break; + } + case 'L': + case 'f': /* handled by lua_getinfo */ + break; + default: + status = 0; /* invalid option */ + break; + } + } + + return status == 1; + } + + void GetFuncInfo(LuaFunction f) + { + if (f is not LuaClosure cl) + { + Source = "=[C#]"; + LineDefined = -1; + LastLineDefined = -1; + What = "C#"; + } + else + { + var p = cl.Proto; + Source = p.GetRoot().Name; + LineDefined = p.LineDefined; + LastLineDefined = p.LastLineDefined; + What = (p.GetRoot() == p) ? "main" : "Lua"; + } + + ShortSourceLength = WriteShortSource(Source, ShortSource); + } + } + + + internal static string? GetLocalName(Chunk chunk, int register, int pc) + { + var locals = chunk.Locals; + foreach (var local in locals) + { + if (local.Index == register && pc >= local.StartPc && pc < local.EndPc) + { + return local.Name.ToString(); + } + + if (local.Index > register) + { + break; + } + } + + return null; + } + + static int FilterPc(int pc, int jmpTarget) + { + if (pc < jmpTarget) /* is code conditional (inside a jump)? */ + return -1; /* cannot know who sets that register */ + else return pc; /* current position sets that register */ + } + + internal static int FindSetRegister(Chunk chunk, int lastPc, int reg) + { + int pc; + int setReg = -1; /* keep last instruction that changed 'reg' */ + int jmpTarget = 0; /* any code before this address is conditional */ + var instructions = chunk.Instructions; + for (pc = 0; pc < lastPc; pc++) + { + Instruction i = instructions[pc]; + OpCode op = i.OpCode; + int a = i.A; + switch (op) + { + case OpCode.LoadNil: + { + int b = i.B; + if (a <= reg && reg <= a + b) /* set registers from 'a' to 'a+b' */ + setReg = FilterPc(pc, jmpTarget); + break; + } + case OpCode.TForCall: + { + if (reg >= a + 2) /* affect all regs above its base */ + setReg = FilterPc(pc, jmpTarget); + break; + } + case OpCode.Call: + case OpCode.TailCall: + { + if (reg >= a) /* affect all registers above base */ + setReg = FilterPc(pc, jmpTarget); + break; + } + case OpCode.Jmp: + { + int b = i.SBx; + int dest = pc + 1 + b; + /* jump is forward and do not skip `lastpc'? */ + if (pc < dest && dest <= lastPc) + { + if (dest > jmpTarget) + jmpTarget = dest; /* update 'jmptarget' */ + } + + break; + } + case OpCode.Test: + { + if (reg == a) /* jumped code can change 'a' */ + setReg = FilterPc(pc, jmpTarget); + break; + } + default: + if (TestAMode(op) && reg == a) /* any instruction that set A */ + setReg = FilterPc(pc, jmpTarget); + break; + } + } + + return setReg; + } + + static void GetConstantName(Chunk p, int pc, int c, out string name) + { + if (c >= 256) + { + /* is 'c' a constant? */ + ref var kvalue = ref p.Constants[c - 256]; + if (kvalue.TryReadString(out name)) + { + /* literal constant? */ + /* it is its own name */ + return; + } + /* else no reasonable name found */ + } + else + { + /* 'c' is a register */ + var what = GetName(p, pc, c, out name!); /* search for 'c' */ + if (what != null && what[0] == 'c') + { + /* found a constant name? */ + return; /* 'name' already filled */ + } + /* else no reasonable name found */ + } + + name = "?"; /* no reasonable name found */ + } + + + internal static string? GetName(Chunk chunk, int lastPc, int reg, out string? name) + { + name = GetLocalName(chunk, reg, lastPc); + if (name != null) + { + return "local"; + } + + var pc = FindSetRegister(chunk, lastPc, reg); + if (pc != -1) + { + /* could find instruction? */ + Instruction i = chunk.Instructions[pc]; + OpCode op = i.OpCode; + switch (op) + { + case OpCode.Move: + { + int b = i.B; /* move from 'b' to 'a' */ + if (b < i.A) + return GetName(chunk, pc, b, out name); /* get name for 'b' */ + break; + } + case OpCode.GetTabUp: + case OpCode.GetTable: + { + int k = i.C; /* key index */ + int t = i.B; /* table index */ + + var vn = (op == OpCode.GetTable) /* name of indexed variable */ + ? GetLocalName(chunk, t + 1, pc) + : chunk.UpValues[t].Name.ToString(); + GetConstantName(chunk, pc, k, out name); + return vn is "_ENV" ? "global" : "field"; + } + case OpCode.GetUpVal: + { + name = chunk.UpValues[i.B].Name.ToString(); + return "upvalue"; + } + case OpCode.LoadK: + case OpCode.LoadKX: + { + uint b = (op == OpCode.LoadKX) + ? i.Bx + : (chunk.Instructions[pc + 1].Ax); + if (chunk.Constants[b].TryReadString(out name)) + { + return "constant"; + } + + break; + } + case OpCode.Self: + { + int k = i.C; /* key index */ + GetConstantName(chunk, pc, k, out name); + return "method"; + } + default: break; /* go through to return NULL */ + } + } + + return null; /* could not find reasonable name */ + } + + internal static string? GetFuncName(Chunk chunk, int pc, out string? name) + { + Instruction i = chunk.Instructions[pc]; /* calling instruction */ + switch (i.OpCode) + { + case OpCode.Call: + case OpCode.TailCall: /* get function name */ + return GetName(chunk, pc, i.A, out name); + case OpCode.TForCall: + { + /* for iterator */ + name = "for iterator"; + return "for iterator"; + } + case OpCode.Self: + case OpCode.GetTabUp: + case OpCode.GetTable: + name = "index"; + break; + case OpCode.SetTabUp: + case OpCode.SetTable: + name = "newindex"; + break; + case OpCode.Add: + name = "add"; + break; + case OpCode.Sub: + name = "sub"; + break; + case OpCode.Mul: + name = "mul"; + break; + case OpCode.Div: + name = "div"; + break; + case OpCode.Mod: + name = "mod"; + break; + case OpCode.Pow: + name = "pow"; + break; + case OpCode.Unm: + name = "unm"; + break; + case OpCode.Len: + name = "len"; + break; + case OpCode.Concat: + name = "concat"; + break; + case OpCode.Eq: + name = "eq"; + break; + case OpCode.Lt: + name = "lt"; + break; + case OpCode.Le: + name = "le"; + break; + default: + name = null; + return null; + } + + return "metamethod"; + } + + internal static int WriteShortSource(ReadOnlySpan source, Span dest) + { + const string PRE = "[string \""; + const int PRE_LEN = 9; + const string POS = "\"]"; + const int POS_LEN = 2; + const string RETS = "..."; + const int RETS_LEN = 3; + const string PREPOS = "[string \"\"]"; + + const int BUFFER_LEN = 59; + if (dest.Length != BUFFER_LEN) throw new ArgumentException("dest must be 60 chars long"); + + if (source.Length == 0) + { + PREPOS.AsSpan().CopyTo(dest); + return PREPOS.Length; + } + + if (source[0] == '=') + { + source = source[1..]; /* skip the '=' */ + /* 'literal' source */ + if (source.Length < BUFFER_LEN) /* small enough? */ + { + source.CopyTo(dest); + return source.Length; + } + else + { + /* truncate it */ + source[..BUFFER_LEN].CopyTo(dest); + return BUFFER_LEN; + } + } + else if (source[0] == '@') + { + /* file name */ + source = source[1..]; /* skip the '@' */ + if (source.Length <= BUFFER_LEN) /* small enough? */ + { + source.CopyTo(dest); + return source.Length; + } + else + { + /* add '...' before rest of name */ + RETS.AsSpan().CopyTo(dest); + source[^(BUFFER_LEN - RETS_LEN)..].CopyTo(dest[RETS_LEN..]); + + return BUFFER_LEN; + } + } + else + { + /* string; format as [string "source"] */ + + + PRE.AsSpan().CopyTo(dest); + int newLine = source.IndexOf('\n'); + if (newLine == -1 && source.Length < BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN)) + { + source.CopyTo(dest[PRE_LEN..]); + POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]); + return PRE_LEN + source.Length + POS_LEN; + } + + if (newLine != -1) + { + source = source[..newLine]; /* stop at first newline */ + } + + if (BUFFER_LEN - (PRE_LEN + RETS_LEN + POS_LEN) < source.Length) + { + source = source[..(BUFFER_LEN - PRE_LEN - RETS_LEN - POS_LEN)]; + } + + /* add '...' before rest of name */ + source.CopyTo(dest[PRE_LEN..]); + RETS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length)..]); + POS.AsSpan().CopyTo(dest[(PRE_LEN + source.Length + RETS_LEN)..]); + return PRE_LEN + source.Length + RETS_LEN + POS_LEN; + } + } + + static int GetOpMode(byte t, byte a, OpArgMask b, OpArgMask c, OpMode m) => (((t) << 7) | ((a) << 6) | (((byte)b) << 4) | (((byte)c) << 2) | ((byte)m)); + + + static readonly int[] OpModes = + [ + GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_MOVE */ + GetOpMode(0, 1, OpArgK, OpArgN, iABx), /* OP_LOADK */ + GetOpMode(0, 1, OpArgN, OpArgN, iABx), /* OP_LOADKX */ + GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_LOADBOOL */ + GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_LOADNIL */ + GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_GETUPVAL */ + GetOpMode(0, 1, OpArgU, OpArgK, iABC), /* OP_GETTABUP */ + GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_GETTABLE */ + GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABUP */ + GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_SETUPVAL */ + GetOpMode(0, 0, OpArgK, OpArgK, iABC), /* OP_SETTABLE */ + GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_NEWTABLE */ + GetOpMode(0, 1, OpArgR, OpArgK, iABC), /* OP_SELF */ + GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_ADD */ + GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_SUB */ + GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MUL */ + GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_DIV */ + GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_MOD */ + GetOpMode(0, 1, OpArgK, OpArgK, iABC), /* OP_POW */ + GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_UNM */ + GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_NOT */ + GetOpMode(0, 1, OpArgR, OpArgN, iABC), /* OP_LEN */ + GetOpMode(0, 1, OpArgR, OpArgR, iABC), /* OP_CONCAT */ + GetOpMode(0, 0, OpArgR, OpArgN, iAsBx), /* OP_JMP */ + GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_EQ */ + GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LT */ + GetOpMode(1, 0, OpArgK, OpArgK, iABC), /* OP_LE */ + GetOpMode(1, 0, OpArgN, OpArgU, iABC), /* OP_TEST */ + GetOpMode(1, 1, OpArgR, OpArgU, iABC), /* OP_TESTSET */ + GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_CALL */ + GetOpMode(0, 1, OpArgU, OpArgU, iABC), /* OP_TAILCALL */ + GetOpMode(0, 0, OpArgU, OpArgN, iABC), /* OP_RETURN */ + GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORLOOP */ + GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_FORPREP */ + GetOpMode(0, 0, OpArgN, OpArgU, iABC), /* OP_TFORCALL */ + GetOpMode(0, 1, OpArgR, OpArgN, iAsBx), /* OP_TFORLOOP */ + GetOpMode(0, 0, OpArgU, OpArgU, iABC), /* OP_SETLIST */ + GetOpMode(0, 1, OpArgU, OpArgN, iABx), /* OP_CLOSURE */ + GetOpMode(0, 1, OpArgU, OpArgN, iABC), /* OP_VARARG */ + GetOpMode(0, 0, OpArgU, OpArgU, iAx), /* OP_EXTRAARG */ + ]; + + internal static OpMode GetOpMode(OpCode m) => (OpMode)(OpModes[(int)m] & 3); + internal static OpArgMask GetBMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 4) & 3); + internal static OpArgMask GetCMode(OpCode m) => (OpArgMask)((OpModes[(int)m] >> 2) & 3); + internal static bool TestAMode(OpCode m) => (OpModes[(int)m] & (1 << 6)) != 0; + internal static bool TestTMode(OpCode m) => (OpModes[(int)m] & (1 << 7)) != 0; +} + +internal enum OpMode : byte +{ + iABC, + iABx, + iAsBx, + iAx +} + +internal enum OpArgMask : byte +{ + OpArgN, /* argument is not used */ + OpArgU, /* argument is used */ + OpArgR, /* argument is a register or a jump offset */ + OpArgK /* argument is a constant or register/constant */ +} \ No newline at end of file diff --git a/src/Lua/Internal/LuaValueArrayPool.cs b/src/Lua/Internal/LuaValueArrayPool.cs deleted file mode 100644 index 8c35deba..00000000 --- a/src/Lua/Internal/LuaValueArrayPool.cs +++ /dev/null @@ -1,73 +0,0 @@ -namespace Lua.Internal; - -internal static class LuaValueArrayPool -{ - static FastStackCore poolOf1024; - static FastStackCore 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}"); - } -} \ No newline at end of file diff --git a/src/Lua/LuaCoroutine.cs b/src/Lua/LuaCoroutine.cs index 3e39e303..12680381 100644 --- a/src/Lua/LuaCoroutine.cs +++ b/src/Lua/LuaCoroutine.cs @@ -20,18 +20,17 @@ struct ResumeContext byte status; bool isFirstCall = true; ValueTask functionTask; - LuaValue[] buffer; + int returnFrameBase; ManualResetValueTaskSourceCore resume; ManualResetValueTaskSourceCore yield; + Traceback? traceback; + internal int ReturnFrameBase => returnFrameBase; public LuaCoroutine(LuaFunction function, bool isProtectedMode) { IsProtectedMode = isProtectedMode; Function = function; - - buffer = ArrayPool.Shared.Rent(1024); - buffer.AsSpan().Clear(); } public override LuaThreadStatus GetStatus() => (LuaThreadStatus)status; @@ -43,8 +42,9 @@ public override void UnsafeSetStatus(LuaThreadStatus status) public bool IsProtectedMode { get; } public LuaFunction Function { get; } + internal Traceback? LuaTraceback => traceback; - public override async ValueTask ResumeAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken = default) + public override async ValueTask ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default) { var baseThread = context.Thread; baseThread.UnsafeSetStatus(LuaThreadStatus.Normal); @@ -80,9 +80,7 @@ public override async ValueTask ResumeAsync(LuaFunctionExecutionContext con case LuaThreadStatus.Running: if (IsProtectedMode) { - buffer.Span[0] = false; - buffer.Span[1] = "cannot resume non-suspended coroutine"; - return 2; + return context.Return(false, "cannot resume non-suspended coroutine"); } else { @@ -91,9 +89,7 @@ public override async ValueTask ResumeAsync(LuaFunctionExecutionContext con case LuaThreadStatus.Dead: if (IsProtectedMode) { - buffer.Span[0] = false; - buffer.Span[1] = "cannot resume dead coroutine"; - return 2; + return context.Return(false, "cannot resume dead coroutine"); } else { @@ -117,6 +113,7 @@ public override async ValueTask ResumeAsync(LuaFunctionExecutionContext con { if (isFirstCall) { + returnFrameBase = Stack.Count; int frameBase; var variableArgumentCount = Function.GetVariableArgumentCount(context.ArgumentCount - 1); @@ -124,7 +121,6 @@ public override async ValueTask ResumeAsync(LuaFunctionExecutionContext con { var fixedArgumentCount = context.ArgumentCount - 1 - variableArgumentCount; var args = context.Arguments; - Stack.PushRange(args.Slice(1 + fixedArgumentCount, variableArgumentCount)); frameBase = Stack.Count; @@ -143,47 +139,35 @@ public override async ValueTask ResumeAsync(LuaFunctionExecutionContext con State = context.State, Thread = this, ArgumentCount = context.ArgumentCount - 1, - FrameBase = frameBase - }, this.buffer, cancellationToken).Preserve(); + FrameBase = frameBase, + ReturnFrameBase = returnFrameBase + }, cancellationToken).Preserve(); Volatile.Write(ref isFirstCall, false); } var (index, result0, result1) = await ValueTaskEx.WhenAny(resumeTask, functionTask!); - var bufferSpan = buffer.Span; if (index == 0) { var results = result0.Results; - - bufferSpan[0] = true; - results.CopyTo(bufferSpan[1..]); - - return results.Length + 1; + return context.Return(true, results.AsSpan()); } else { - var resultCount = functionTask!.Result; - Volatile.Write(ref status, (byte)LuaThreadStatus.Dead); - bufferSpan[0] = true; - this.buffer.AsSpan()[..resultCount].CopyTo(bufferSpan[1..]); - - ArrayPool.Shared.Return(this.buffer); - - return 1 + resultCount; + var count = context.Return(true, Stack.AsSpan()[returnFrameBase..]); + Stack.PopUntil(returnFrameBase); + return count; } } catch (Exception ex) when (ex is not OperationCanceledException) { if (IsProtectedMode) { - ArrayPool.Shared.Return(this.buffer); - + traceback = (ex as LuaRuntimeException)?.LuaTraceback; Volatile.Write(ref status, (byte)LuaThreadStatus.Dead); - buffer.Span[0] = false; - buffer.Span[1] = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message; - return 2; + return context.Return(false, ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message); } else { @@ -203,14 +187,14 @@ public override async ValueTask ResumeAsync(LuaFunctionExecutionContext con } } - public override async ValueTask YieldAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken = default) + public override async ValueTask YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default) { if (Volatile.Read(ref status) != (byte)LuaThreadStatus.Running) { 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) + if (context.Thread.GetCallStackFrames()[^2].Function is not LuaClosure) { throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield across a C#-call boundary"); } @@ -236,12 +220,7 @@ public override async ValueTask YieldAsync(LuaFunctionExecutionContext cont try { var result = await new ValueTask(this, yield.Version); - for (int i = 0; i < result.Results.Length; i++) - { - buffer.Span[i] = result.Results[i]; - } - - return result.Results.Length; + return (context.Return(result.Results)); } catch (Exception ex) when (ex is not OperationCanceledException) { diff --git a/src/Lua/LuaFunction.cs b/src/Lua/LuaFunction.cs index 2c1fae5a..855ca7ea 100644 --- a/src/Lua/LuaFunction.cs +++ b/src/Lua/LuaFunction.cs @@ -1,30 +1,34 @@ -using System.Runtime.CompilerServices; using Lua.Runtime; namespace Lua; -public class LuaFunction(string name, Func, CancellationToken, ValueTask> func) +public class LuaFunction(string name, Func> func) { public string Name { get; } = name; - internal Func, CancellationToken, ValueTask> Func { get; } = func; + internal Func> Func { get; } = func; - public LuaFunction(Func, CancellationToken, ValueTask> func) : this("anonymous", func) + public LuaFunction(Func> func) : this("anonymous", func) { } - public async ValueTask InvokeAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask InvokeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var frame = new CallStackFrame { Base = context.FrameBase, - VariableArgumentCount = this is Closure closure ? Math.Max(context.ArgumentCount - closure.Proto.ParameterCount, 0) : 0, + VariableArgumentCount = this.GetVariableArgumentCount(context.ArgumentCount), Function = this, + ReturnBase = context.ReturnFrameBase }; - context.Thread.PushCallStackFrame(frame); try { - return await Func(context, buffer, cancellationToken); + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + return await LuaVirtualMachine.ExecuteCallHook(context, cancellationToken); + } + + return await Func(context, cancellationToken); } finally { diff --git a/src/Lua/LuaFunctionExecutionContext.cs b/src/Lua/LuaFunctionExecutionContext.cs index d9a483ad..4299c339 100644 --- a/src/Lua/LuaFunctionExecutionContext.cs +++ b/src/Lua/LuaFunctionExecutionContext.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Lua.CodeAnalysis; +using Lua.Runtime; namespace Lua; @@ -11,6 +12,7 @@ public readonly record struct LuaFunctionExecutionContext public required LuaThread Thread { get; init; } public required int ArgumentCount { get; init; } public required int FrameBase { get; init; } + public required int ReturnFrameBase { get; init; } public SourcePosition? SourcePosition { get; init; } public string? RootChunkName { get; init; } public string? ChunkName { get; init; } @@ -19,10 +21,7 @@ public readonly record struct LuaFunctionExecutionContext public ReadOnlySpan Arguments { - get - { - return Thread.GetStackValues().Slice(FrameBase, ArgumentCount); - } + get { return Thread.GetStackValues().Slice(FrameBase, ArgumentCount); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -38,6 +37,17 @@ public LuaValue GetArgument(int index) return Arguments[index]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal LuaValue GetArgumentOrDefault(int index, LuaValue defaultValue = default) + { + if (ArgumentCount <= index) + { + return defaultValue; + } + + return Arguments[index]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public T GetArgument(int index) { @@ -55,6 +65,10 @@ public T GetArgument(int index) { LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString()); } + else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData) + { + LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead()?.GetType().ToString() ?? "userdata: 0"); + } else { LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString()); @@ -64,6 +78,116 @@ public T GetArgument(int index) return argValue; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal T GetArgumentOrDefault(int index, T defaultValue = default!) + { + if (ArgumentCount <= index) + { + return defaultValue; + } + + var arg = Arguments[index]; + + if (arg.Type is LuaValueType.Nil) + { + return defaultValue; + } + + if (!arg.TryRead(out var argValue)) + { + var t = typeof(T); + if ((t == typeof(int) || t == typeof(long)) && arg.TryReadNumber(out _)) + { + LuaRuntimeException.BadArgumentNumberIsNotInteger(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name); + } + else if (LuaValue.TryGetLuaValueType(t, out var type)) + { + LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, type.ToString(), arg.Type.ToString()); + } + else if (arg.Type is LuaValueType.UserData or LuaValueType.LightUserData) + { + LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.UnsafeRead()?.GetType().ToString() ?? "userdata: 0"); + } + else + { + LuaRuntimeException.BadArgument(State.GetTraceback(), index + 1, Thread.GetCurrentFrame().Function.Name, t.Name, arg.Type.ToString()); + } + } + + return argValue; + } + + public int Return() + { + Thread.Stack.PopUntil(ReturnFrameBase); + return 0; + } + + public int Return(LuaValue result) + { + var stack = Thread.Stack; + stack.SetTop(ReturnFrameBase + 1); + stack.FastGet(ReturnFrameBase) = result; + return 1; + } + + public int Return(LuaValue result0, LuaValue result1) + { + var stack = Thread.Stack; + stack.SetTop(ReturnFrameBase + 2); + stack.FastGet(ReturnFrameBase) = result0; + stack.FastGet(ReturnFrameBase + 1) = result1; + return 2; + } + + public int Return(LuaValue result0, LuaValue result1, LuaValue result2) + { + var stack = Thread.Stack; + stack.SetTop(ReturnFrameBase + 3); + stack.FastGet(ReturnFrameBase) = result0; + stack.FastGet(ReturnFrameBase + 1) = result1; + stack.FastGet(ReturnFrameBase + 2) = result2; + return 3; + } + + public int Return(ReadOnlySpan results) + { + var stack = Thread.Stack; + stack.EnsureCapacity(ReturnFrameBase + results.Length); + results.CopyTo(stack.GetBuffer()[ReturnFrameBase..(ReturnFrameBase + results.Length)]); + stack.SetTop(ReturnFrameBase + results.Length); + return results.Length; + } + + internal int Return(LuaValue result0, ReadOnlySpan results) + { + var stack = Thread.Stack; + stack.EnsureCapacity(ReturnFrameBase + results.Length); + stack.SetTop(ReturnFrameBase + results.Length + 1); + var buffer = stack.GetBuffer(); + buffer[ReturnFrameBase] = result0; + results.CopyTo(buffer[(ReturnFrameBase + 1)..(ReturnFrameBase + results.Length + 1)]); + return results.Length + 1; + } + + public Span GetReturnBuffer(int count) + { + var stack = Thread.Stack; + stack.SetTop(ReturnFrameBase + count); + var buffer = stack.GetBuffer()[ReturnFrameBase..(ReturnFrameBase + count)]; + return buffer; + } + + public CSharpClosure? GetCsClosure() + { + return Thread.GetCurrentFrame().Function as CSharpClosure; + } + + internal void ThrowBadArgument(int index, string message) + { + LuaRuntimeException.BadArgument(State.GetTraceback(), index, Thread.GetCurrentFrame().Function.Name, message); + } + void ThrowIfArgumentNotExists(int index) { if (ArgumentCount <= index) diff --git a/src/Lua/LuaFunctionExtensions.cs b/src/Lua/LuaFunctionExtensions.cs index 7027164f..dc043d7e 100644 --- a/src/Lua/LuaFunctionExtensions.cs +++ b/src/Lua/LuaFunctionExtensions.cs @@ -6,7 +6,6 @@ public static class LuaFunctionExtensions { public static async ValueTask InvokeAsync(this LuaFunction function, LuaState state, LuaValue[] arguments, CancellationToken cancellationToken = default) { - using var buffer = new PooledArray(1024); var thread = state.CurrentThread; var frameBase = thread.Stack.Count; @@ -22,8 +21,10 @@ public static async ValueTask InvokeAsync(this LuaFunction function, Thread = thread, ArgumentCount = arguments.Length, FrameBase = frameBase, - }, buffer.AsMemory(), cancellationToken); - - return buffer.AsSpan()[0..resultCount].ToArray(); + ReturnFrameBase = frameBase, + }, cancellationToken); + var r =thread.Stack.GetBuffer()[frameBase..(frameBase + resultCount)].ToArray(); + thread.Stack.PopUntil(frameBase); + return r; } } \ No newline at end of file diff --git a/src/Lua/LuaMainThread.cs b/src/Lua/LuaMainThread.cs index 12e1070a..fabe6d94 100644 --- a/src/Lua/LuaMainThread.cs +++ b/src/Lua/LuaMainThread.cs @@ -12,14 +12,12 @@ public override void UnsafeSetStatus(LuaThreadStatus status) // Do nothing } - public override ValueTask ResumeAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken = default) + public override ValueTask ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default) { - buffer.Span[0] = false; - buffer.Span[1] = "cannot resume non-suspended coroutine"; - return new(2); + return new(context.Return(false,"cannot resume non-suspended coroutine")); } - public override ValueTask YieldAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken = default) + public override ValueTask YieldAsync(LuaFunctionExecutionContext context,CancellationToken cancellationToken = default) { throw new LuaRuntimeException(context.State.GetTraceback(), "attempt to yield from outside a coroutine"); } diff --git a/src/Lua/LuaResult.cs b/src/Lua/LuaResult.cs new file mode 100644 index 00000000..6f37f591 --- /dev/null +++ b/src/Lua/LuaResult.cs @@ -0,0 +1,26 @@ +using Lua.Runtime; + +namespace Lua; + +public readonly struct LuaResult : IDisposable +{ + readonly LuaStack stack; + readonly int returnBase; + + internal LuaResult(LuaStack stack, int returnBase) + { + this.stack = stack; + this.returnBase =returnBase; + } + + public int Count => stack.Count - returnBase; + public int Length => stack.Count - returnBase; + public ReadOnlySpan AsSpan() => stack.AsSpan()[returnBase..]; + + public LuaValue this[int index] => AsSpan()[index]; + + public void Dispose() + { + stack.PopUntil(returnBase); + } +} \ No newline at end of file diff --git a/src/Lua/LuaState.cs b/src/Lua/LuaState.cs index e11a22d8..d2f341f5 100644 --- a/src/Lua/LuaState.cs +++ b/src/Lua/LuaState.cs @@ -16,16 +16,22 @@ public sealed class LuaState FastStackCore threadStack; readonly LuaTable packages = new(); readonly LuaTable environment; + readonly LuaTable registry = new(); readonly UpValue envUpValue; bool isRunning; + FastStackCore debugBufferPool; + internal UpValue EnvUpValue => envUpValue; internal ref FastStackCore ThreadStack => ref threadStack; internal ref FastListCore OpenUpValues => ref openUpValues; + internal ref FastStackCore DebugBufferPool => ref debugBufferPool; public LuaTable Environment => environment; + public LuaTable Registry => registry; public LuaTable LoadedModules => packages; public LuaMainThread MainThread => mainThread; + public LuaThread CurrentThread { get @@ -56,24 +62,28 @@ public static LuaState Create() envUpValue = UpValue.Closed(environment); } - public async ValueTask RunAsync(Chunk chunk, Memory buffer, CancellationToken cancellationToken = default) + public async ValueTask RunAsync(Chunk chunk, CancellationToken cancellationToken = default) { + ThrowIfResultNotDisposed(); ThrowIfRunning(); Volatile.Write(ref isRunning, true); try { - var closure = new Closure(this, chunk); - return await closure.InvokeAsync(new() + var closure = new LuaClosure(this, chunk); + await closure.InvokeAsync(new() { State = this, Thread = CurrentThread, ArgumentCount = 0, FrameBase = 0, + ReturnFrameBase = 0, SourcePosition = null, RootChunkName = chunk.Name, ChunkName = chunk.Name, - }, buffer, cancellationToken); + }, cancellationToken); + + return new LuaResult(CurrentThread.Stack, 0); } finally { @@ -88,32 +98,30 @@ public void Push(LuaValue value) public Traceback GetTraceback() { - if (threadStack.Count == 0) - { - return new() - { - RootFunc = (Closure)MainThread.GetCallStackFrames()[0].Function, - StackFrames = MainThread.GetCallStackFrames()[1..] - .ToArray() - }; - } + return GetTraceback(CurrentThread); + } + internal Traceback GetTraceback(LuaThread thread) + { using var list = new PooledList(8); - foreach (var frame in MainThread.GetCallStackFrames()[1..]) + foreach (var frame in thread.GetCallStackFrames()[1..]) { list.Add(frame); } - foreach (var thread in threadStack.AsSpan()) + + LuaClosure rootFunc; + if (thread.GetCallStackFrames()[0].Function is LuaClosure closure) { - if (thread.CallStack.Count == 0) continue; - foreach (var frame in thread.GetCallStackFrames()[1..]) - { - list.Add(frame); - } + rootFunc = closure; } - return new() + else { - RootFunc = (Closure)MainThread.GetCallStackFrames()[0].Function, + rootFunc = (LuaClosure)MainThread.GetCallStackFrames()[0].Function; + } + + return new(this) + { + RootFunc = rootFunc, StackFrames = list.AsSpan().ToArray() }; } @@ -200,6 +208,14 @@ internal void CloseUpValues(LuaThread thread, int frameBase) } } + void ThrowIfResultNotDisposed() + { + if (MainThread.Stack.Count != 0) + { + throw new InvalidOperationException("LuaResult is not disposed"); + } + } + void ThrowIfRunning() { if (Volatile.Read(ref isRunning)) @@ -207,4 +223,4 @@ void ThrowIfRunning() throw new InvalidOperationException("the lua state is currently running"); } } -} +} \ No newline at end of file diff --git a/src/Lua/LuaStateExtensions.cs b/src/Lua/LuaStateExtensions.cs index 01e74962..b40e67ce 100644 --- a/src/Lua/LuaStateExtensions.cs +++ b/src/Lua/LuaStateExtensions.cs @@ -1,4 +1,3 @@ -using System.Buffers; using Lua.CodeAnalysis.Compilation; using Lua.CodeAnalysis.Syntax; @@ -6,47 +5,41 @@ namespace Lua; public static class LuaStateExtensions { - public static ValueTask DoStringAsync(this LuaState state, string source, Memory buffer, string? chunkName = null, CancellationToken cancellationToken = default) + public static async ValueTask DoStringAsync(this LuaState state, string source, Memory buffer, string? chunkName = null, CancellationToken cancellationToken = default) { var syntaxTree = LuaSyntaxTree.Parse(source, chunkName); var chunk = LuaCompiler.Default.Compile(syntaxTree, chunkName); - return state.RunAsync(chunk, buffer, cancellationToken); + using var result = await state.RunAsync(chunk, cancellationToken); + result.AsSpan().CopyTo(buffer.Span); + return result.Count; } public static async ValueTask DoStringAsync(this LuaState state, string source, string? chunkName = null, CancellationToken cancellationToken = default) { - var buffer = ArrayPool.Shared.Rent(1024); - try - { - var resultCount = await DoStringAsync(state, source, buffer, chunkName, cancellationToken); - return buffer.AsSpan(0, resultCount).ToArray(); - } - finally - { - ArrayPool.Shared.Return(buffer); - } + var syntaxTree = LuaSyntaxTree.Parse(source, chunkName); + var chunk = LuaCompiler.Default.Compile(syntaxTree, chunkName); + using var result = await state.RunAsync(chunk, cancellationToken); + return result.AsSpan().ToArray(); } public static async ValueTask DoFileAsync(this LuaState state, string path, Memory buffer, CancellationToken cancellationToken = default) { var text = await File.ReadAllTextAsync(path, cancellationToken); - var fileName = Path.GetFileName(path); + var fileName = "@" + Path.GetFileName(path); var syntaxTree = LuaSyntaxTree.Parse(text, fileName); var chunk = LuaCompiler.Default.Compile(syntaxTree, fileName); - return await state.RunAsync(chunk, buffer, cancellationToken); + using var result = await state.RunAsync(chunk, cancellationToken); + result.AsSpan().CopyTo(buffer.Span); + return result.Count; } public static async ValueTask DoFileAsync(this LuaState state, string path, CancellationToken cancellationToken = default) { - var buffer = ArrayPool.Shared.Rent(1024); - try - { - var resultCount = await DoFileAsync(state, path, buffer, cancellationToken); - return buffer.AsSpan(0, resultCount).ToArray(); - } - finally - { - ArrayPool.Shared.Return(buffer); - } + var text = await File.ReadAllTextAsync(path, cancellationToken); + var fileName = "@" + Path.GetFileName(path); + var syntaxTree = LuaSyntaxTree.Parse(text, fileName); + var chunk = LuaCompiler.Default.Compile(syntaxTree, fileName); + using var result = await state.RunAsync(chunk, cancellationToken); + return result.AsSpan().ToArray(); } } \ No newline at end of file diff --git a/src/Lua/LuaTable.cs b/src/Lua/LuaTable.cs index dd4fcc41..6fc1f70a 100644 --- a/src/Lua/LuaTable.cs +++ b/src/Lua/LuaTable.cs @@ -19,6 +19,8 @@ public LuaTable(int arrayCapacity, int dictionaryCapacity) readonly LuaValueDictionary dictionary; LuaTable? metatable; + internal LuaValueDictionary Dictionary => dictionary; + public LuaValue this[LuaValue key] { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Lua/LuaThread.cs b/src/Lua/LuaThread.cs index 4171c6c9..0fb47f2d 100644 --- a/src/Lua/LuaThread.cs +++ b/src/Lua/LuaThread.cs @@ -8,8 +8,8 @@ public abstract class LuaThread { public abstract LuaThreadStatus GetStatus(); public abstract void UnsafeSetStatus(LuaThreadStatus status); - public abstract ValueTask ResumeAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken = default); - public abstract ValueTask YieldAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken = default); + public abstract ValueTask ResumeAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default); + public abstract ValueTask YieldAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken = default); LuaStack stack = new(); FastStackCore callStack; @@ -17,9 +17,42 @@ public abstract class LuaThread internal LuaStack Stack => stack; internal ref FastStackCore CallStack => ref callStack; - public CallStackFrame GetCurrentFrame() + internal bool IsLineHookEnabled { - return callStack.Peek(); + get => LineAndCountHookMask.Flag0; + set => LineAndCountHookMask.Flag0 = value; + } + + internal bool IsCountHookEnabled + { + get => LineAndCountHookMask.Flag1; + set => LineAndCountHookMask.Flag1 = value; + } + + internal BitFlags2 LineAndCountHookMask; + + internal bool IsCallHookEnabled + { + get => CallOrReturnHookMask.Flag0; + set => CallOrReturnHookMask.Flag0 = value; + } + + internal bool IsReturnHookEnabled + { + get => CallOrReturnHookMask.Flag1; + set => CallOrReturnHookMask.Flag1 = value; + } + + internal BitFlags2 CallOrReturnHookMask; + internal bool IsInHook; + internal int HookCount; + internal int BaseHookCount; + internal int LastPc; + internal LuaFunction? Hook { get; set; } + + public ref readonly CallStackFrame GetCurrentFrame() + { + return ref callStack.PeekRef(); } public ReadOnlySpan GetStackValues() @@ -39,7 +72,7 @@ internal void PushCallStackFrame(in CallStackFrame frame) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PopCallStackFrame() + internal void PopCallStackFrameWithStackPop() { if (callStack.TryPop(out var frame)) { @@ -52,7 +85,7 @@ internal void PopCallStackFrame() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PopCallStackFrameUnsafe(int frameBase) + internal void PopCallStackFrameWithStackPop(int frameBase) { if (callStack.TryPop()) { @@ -65,7 +98,7 @@ internal void PopCallStackFrameUnsafe(int frameBase) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void PopCallStackFrameUnsafe() + internal void PopCallStackFrame() { if (!callStack.TryPop()) { @@ -81,6 +114,6 @@ internal void DumpStackValues() Console.WriteLine($"LuaStack [{i}]\t{span[i]}"); } } - + static void ThrowForEmptyStack() => throw new InvalidOperationException("Empty stack"); } \ No newline at end of file diff --git a/src/Lua/LuaThreadExtensions.cs b/src/Lua/LuaThreadExtensions.cs index ad7e2666..7a3405f2 100644 --- a/src/Lua/LuaThreadExtensions.cs +++ b/src/Lua/LuaThreadExtensions.cs @@ -6,26 +6,25 @@ public static class LuaThreadExtensions { public static async ValueTask ResumeAsync(this LuaThread thread, LuaState state, CancellationToken cancellationToken = default) { - using var buffer = new PooledArray(1024); - var frameBase = thread.Stack.Count; thread.Stack.Push(thread); - var resultCount = await thread.ResumeAsync(new() + await thread.ResumeAsync(new() { State = state, Thread = state.CurrentThread, ArgumentCount = 1, FrameBase = frameBase, - }, buffer.AsMemory(), cancellationToken); - - return buffer.AsSpan()[0..resultCount].ToArray(); + ReturnFrameBase = frameBase, + }, cancellationToken); + var returnBase = ((LuaCoroutine)thread).ReturnFrameBase; + var results = thread.Stack.AsSpan()[returnBase..].ToArray(); + thread.Stack.PopUntil(returnBase); + return results; } public static async ValueTask ResumeAsync(this LuaThread thread, LuaState state, LuaValue[] arguments, CancellationToken cancellationToken = default) { - using var buffer = new PooledArray(1024); - var frameBase = thread.Stack.Count; thread.Stack.Push(thread); for (int i = 0; i < arguments.Length; i++) @@ -33,14 +32,17 @@ public static async ValueTask ResumeAsync(this LuaThread thread, Lua thread.Stack.Push(arguments[i]); } - var resultCount = await thread.ResumeAsync(new() + await thread.ResumeAsync(new() { State = state, Thread = state.CurrentThread, ArgumentCount = 1 + arguments.Length, FrameBase = frameBase, - }, buffer.AsMemory(), cancellationToken); - - return buffer.AsSpan()[0..resultCount].ToArray(); + ReturnFrameBase = frameBase, + }, cancellationToken); + var returnBase = ((LuaCoroutine)thread).ReturnFrameBase; + var results = thread.Stack.AsSpan()[returnBase..].ToArray(); + thread.Stack.PopUntil(returnBase); + return results; } } \ No newline at end of file diff --git a/src/Lua/LuaUserData.cs b/src/Lua/LuaUserData.cs index 98b9a3cf..19671373 100644 --- a/src/Lua/LuaUserData.cs +++ b/src/Lua/LuaUserData.cs @@ -1,6 +1,22 @@ namespace Lua; +internal sealed class LuaUserData : ILuaUserData +{ + public LuaTable? Metatable { get; set; } + readonly LuaValue[] userValues = new LuaValue[1]; + public Span UserValues => userValues; + + public LuaUserData(LuaValue value, LuaTable? metatable) + { + userValues[0] = value; + Metatable = metatable; + } +} + public interface ILuaUserData { LuaTable? Metatable { get; set; } + + //We use span for compatibility with lua5.4. + Span UserValues => default; } \ No newline at end of file diff --git a/src/Lua/LuaValue.cs b/src/Lua/LuaValue.cs index 924e309f..267c4402 100644 --- a/src/Lua/LuaValue.cs +++ b/src/Lua/LuaValue.cs @@ -14,6 +14,7 @@ public enum LuaValueType : byte Number, Function, Thread, + LightUserData, UserData, Table, } @@ -136,6 +137,16 @@ public bool TryRead(out T result) } else { + break; + } + case LuaValueType.LightUserData: + { + if (referenceValue is T tValue) + { + result = tValue; + return true; + } + break; } case LuaValueType.UserData: @@ -360,6 +371,7 @@ internal T UnsafeRead() case LuaValueType.Thread: case LuaValueType.Function: case LuaValueType.Table: + case LuaValueType.LightUserData: case LuaValueType.UserData: { var v = referenceValue!; @@ -378,6 +390,13 @@ public bool ToBoolean() return true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public LuaValue(object obj) + { + Type = LuaValueType.LightUserData; + referenceValue = obj; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public LuaValue(bool value) { @@ -517,6 +536,7 @@ public override string ToString() LuaValueType.Function => $"function: {referenceValue!.GetHashCode()}", LuaValueType.Thread => $"thread: {referenceValue!.GetHashCode()}", LuaValueType.Table => $"table: {referenceValue!.GetHashCode()}", + LuaValueType.LightUserData => $"userdata: {referenceValue!.GetHashCode()}", LuaValueType.UserData => $"userdata: {referenceValue!.GetHashCode()}", _ => "", }; @@ -554,12 +574,17 @@ public static bool TryGetLuaValueType(Type type, out LuaValueType result) result = LuaValueType.Thread; return true; } + else if (type == typeof(ILuaUserData) || type.IsAssignableFrom(typeof(ILuaUserData))) + { + result = LuaValueType.UserData; + return true; + } result = default; return false; } - internal ValueTask CallToStringAsync(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + internal ValueTask CallToStringAsync(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (this.TryGetMetamethod(context.State, Metamethods.ToString, out var metamethod)) { @@ -568,18 +593,20 @@ internal ValueTask CallToStringAsync(LuaFunctionExecutionContext context, M LuaRuntimeException.AttemptInvalidOperation(context.State.GetTraceback(), "call", metamethod); } - context.State.Push(this); + var stack = context.Thread.Stack; + stack.Push(this); return func.InvokeAsync(context with { ArgumentCount = 1, - FrameBase = context.Thread.Stack.Count - 1, - }, buffer, cancellationToken); + FrameBase = stack.Count - 1, + ReturnFrameBase = stack.Count-1, + },cancellationToken); } else { - buffer.Span[0] = ToString(); - return new(1); + context.Thread.Stack.Push(ToString()); + return default; } } } \ No newline at end of file diff --git a/src/Lua/Runtime/CSharpClosure.cs b/src/Lua/Runtime/CSharpClosure.cs new file mode 100644 index 00000000..2ed85fdf --- /dev/null +++ b/src/Lua/Runtime/CSharpClosure.cs @@ -0,0 +1,6 @@ +namespace Lua.Runtime; + +public sealed class CSharpClosure(string name,LuaValue[] upValues,Func> func) : LuaFunction(name, func) +{ + public readonly LuaValue[] UpValues = upValues; +} \ No newline at end of file diff --git a/src/Lua/Runtime/CallStackFrame.cs b/src/Lua/Runtime/CallStackFrame.cs index 7b00bc22..6a134c02 100644 --- a/src/Lua/Runtime/CallStackFrame.cs +++ b/src/Lua/Runtime/CallStackFrame.cs @@ -6,14 +6,19 @@ namespace Lua.Runtime; public record struct CallStackFrame { public required int Base; + public required int ReturnBase; public required LuaFunction Function; public required int VariableArgumentCount; public int CallerInstructionIndex; internal CallStackFrameFlags Flags; + internal bool IsTailCall => (Flags & CallStackFrameFlags.TailCall) == CallStackFrameFlags.TailCall; } [Flags] public enum CallStackFrameFlags { + //None = 0, ReversedLe = 1, + TailCall = 2, + InHook = 4, } \ No newline at end of file diff --git a/src/Lua/Runtime/Chunk.cs b/src/Lua/Runtime/Chunk.cs index cfd8c53b..350ff7b8 100644 --- a/src/Lua/Runtime/Chunk.cs +++ b/src/Lua/Runtime/Chunk.cs @@ -11,10 +11,13 @@ public sealed class Chunk public required SourcePosition[] SourcePositions { get; init; } public required LuaValue[] Constants { get; init; } public required UpValueInfo[] UpValues { get; init; } + public required LocalValueInfo[] Locals { get; init; } public required Chunk[] Functions { get; init; } public required int ParameterCount { get; init; } - + public required bool HasVariableArguments { get; init; } public required byte MaxStackPosition { get; init; } + public required int LineDefined { get; init; } + public required int LastLineDefined { get; init; } Chunk? rootCache; diff --git a/src/Lua/Runtime/LocalValueInfo.cs b/src/Lua/Runtime/LocalValueInfo.cs new file mode 100644 index 00000000..a138e6ed --- /dev/null +++ b/src/Lua/Runtime/LocalValueInfo.cs @@ -0,0 +1,9 @@ +namespace Lua.Runtime; + +public readonly record struct LocalValueInfo +{ + public required ReadOnlyMemory Name { get; init; } + public required byte Index { get; init; } + public required int StartPc { get; init; } + public required int EndPc { get; init; } +} \ No newline at end of file diff --git a/src/Lua/Runtime/Closure.cs b/src/Lua/Runtime/LuaClosure.cs similarity index 76% rename from src/Lua/Runtime/Closure.cs rename to src/Lua/Runtime/LuaClosure.cs index 8cb4a5d0..20f48226 100644 --- a/src/Lua/Runtime/Closure.cs +++ b/src/Lua/Runtime/LuaClosure.cs @@ -3,13 +3,13 @@ namespace Lua.Runtime; -public sealed class Closure : LuaFunction +public sealed class LuaClosure : LuaFunction { Chunk proto; FastListCore upValues; - public Closure(LuaState state, Chunk proto, LuaTable? environment = null) - : base(proto.Name, (context, buffer, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, buffer, ct)) + public LuaClosure(LuaState state, Chunk proto, LuaTable? environment = null) + : base(proto.Name, (context, ct) => LuaVirtualMachine.ExecuteClosureAsync(context.State, ct)) { this.proto = proto; @@ -24,6 +24,7 @@ public Closure(LuaState state, Chunk proto, LuaTable? environment = null) public Chunk Proto => proto; public ReadOnlySpan UpValues => upValues.AsSpan(); + internal Span GetUpValuesSpan() => upValues.AsSpan(); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal LuaValue GetUpValue(int index) @@ -47,7 +48,7 @@ static UpValue GetUpValueFromDescription(LuaState state, LuaThread thread, UpVal { if (description.IsInRegister) { - return state.GetOrAddUpValue(thread, thread.GetCallStackFrames()[^1].Base + description.Index); + return state.GetOrAddUpValue(thread, thread.GetCurrentFrame().Base + description.Index); } if (description.Index == -1) // -1 is global environment @@ -55,7 +56,7 @@ static UpValue GetUpValueFromDescription(LuaState state, LuaThread thread, UpVal return envUpValue; } - if (thread.GetCallStackFrames()[^1].Function is Closure parentClosure) + if (thread.GetCurrentFrame().Function is LuaClosure parentClosure) { return parentClosure.UpValues[description.Index]; } diff --git a/src/Lua/Runtime/LuaStack.cs b/src/Lua/Runtime/LuaStack.cs index 8ac48ae7..6d0900b7 100644 --- a/src/Lua/Runtime/LuaStack.cs +++ b/src/Lua/Runtime/LuaStack.cs @@ -27,6 +27,11 @@ static void Resize(ref LuaValue[] array, int newSize) size *= 2; } + if (1000000 < size) + { + throw new LuaException("Lua Stack overflow"); + } + Array.Resize(ref array, size); } } @@ -134,4 +139,11 @@ static void ThrowEmptyStack() { throw new InvalidOperationException("Empty stack"); } + + internal void SetTop( int top) + { + EnsureCapacity(top); + NotifyTop(top); + PopUntil(top); + } } \ No newline at end of file diff --git a/src/Lua/Runtime/LuaValueRuntimeExtensions.cs b/src/Lua/Runtime/LuaValueRuntimeExtensions.cs index e65c69ba..542c85f8 100644 --- a/src/Lua/Runtime/LuaValueRuntimeExtensions.cs +++ b/src/Lua/Runtime/LuaValueRuntimeExtensions.cs @@ -15,8 +15,8 @@ public static bool TryGetMetamethod(this LuaValue value, LuaState state, string [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetVariableArgumentCount(this LuaFunction function, int argumentCount) { - return function is Closure luaClosure - ? argumentCount - luaClosure.Proto.ParameterCount + return function is LuaClosure { Proto.HasVariableArguments: true } luaClosure + ?argumentCount - luaClosure.Proto.ParameterCount : 0; } } \ No newline at end of file diff --git a/src/Lua/Runtime/LuaVirtualMachine.Debug.cs b/src/Lua/Runtime/LuaVirtualMachine.Debug.cs new file mode 100644 index 00000000..937d5ee0 --- /dev/null +++ b/src/Lua/Runtime/LuaVirtualMachine.Debug.cs @@ -0,0 +1,226 @@ +using System.Runtime.CompilerServices; + +namespace Lua.Runtime; + +public static partial class LuaVirtualMachine +{ + [MethodImpl(MethodImplOptions.NoInlining)] + static bool ExecutePerInstructionHook(ref VirtualMachineExecutionContext context) + { + var r = Impl(context); + if (r.IsCompleted) + { + if (r.Result == 0) + { + context.Thread.PopCallStackFrameWithStackPop(); + } + + return false; + } + + context.Task = r; + context.Pc--; + return true; + + static async ValueTask Impl(VirtualMachineExecutionContext context) + { + bool countHookIsDone = false; + if (context.Thread.IsCountHookEnabled && --context.Thread.HookCount == 0) + { + context.Thread.HookCount = context.Thread.BaseHookCount; + + var hook = context.Thread.Hook!; + var stack = context.Thread.Stack; + stack.Push("count"); + stack.Push(LuaValue.Nil); + var funcContext = new LuaFunctionExecutionContext + { + State = context.State, + Thread = context.Thread, + ArgumentCount = 2, + FrameBase = context.Thread.Stack.Count - 2, + ReturnFrameBase = context.Thread.Stack.Count - 2, + }; + var frame = new CallStackFrame + { + Base = funcContext.FrameBase, + ReturnBase = funcContext.ReturnFrameBase, + VariableArgumentCount = hook.GetVariableArgumentCount(funcContext.ArgumentCount), + Function = hook, + CallerInstructionIndex = context.Pc, + }; + frame.Flags |= CallStackFrameFlags.InHook; + context.Thread.IsInHook = true; + context.Thread.PushCallStackFrame(frame); + await hook.Func(funcContext, context.CancellationToken); + context.Thread.IsInHook = false; + + + countHookIsDone = true; + } + + + if (context.Thread.IsLineHookEnabled) + { + var pc = context.Pc; + var sourcePositions = context.Chunk.SourcePositions; + var line = sourcePositions[pc].Line; + + if (countHookIsDone || pc == 0 || context.Thread.LastPc < 0 || pc <= context.Thread.LastPc || sourcePositions[context.Thread.LastPc].Line != line) + { + if (countHookIsDone) + { + context.Thread.PopCallStackFrameWithStackPop(); + } + + + var hook = context.Thread.Hook!; + var stack = context.Thread.Stack; + stack.Push("line"); + stack.Push(line); + var funcContext = new LuaFunctionExecutionContext + { + State = context.State, + Thread = context.Thread, + ArgumentCount = 2, + FrameBase = context.Thread.Stack.Count - 2, + ReturnFrameBase = context.Thread.Stack.Count - 2, + }; + var frame = new CallStackFrame + { + Base = funcContext.FrameBase, + ReturnBase = funcContext.ReturnFrameBase, + VariableArgumentCount = hook.GetVariableArgumentCount(funcContext.ArgumentCount), + Function = hook, + CallerInstructionIndex = pc, + }; + frame.Flags |= CallStackFrameFlags.InHook; + context.Thread.IsInHook = true; + context.Thread.PushCallStackFrame(frame); + await hook.Func(funcContext, context.CancellationToken); + context.Thread.IsInHook = false; + context.Pc--; + context.Thread.LastPc = pc; + return 0; + } + + context.Thread.LastPc = pc; + } + + if (countHookIsDone) + { + context.Pc--; + return 0; + } + + return -1; + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static ValueTask ExecuteCallHook(ref VirtualMachineExecutionContext context, in CallStackFrame frame, int arguments, bool isTailCall = false) + { + return ExecuteCallHook(new() + { + State = context.State, + Thread = context.Thread, + ArgumentCount = arguments, + FrameBase = frame.Base, + ReturnFrameBase = frame.ReturnBase, + CallerInstructionIndex = frame.CallerInstructionIndex, + }, context.CancellationToken, isTailCall); + } + + internal static async ValueTask ExecuteCallHook(LuaFunctionExecutionContext context, CancellationToken cancellationToken, bool isTailCall = false) + { + var argCount = context.ArgumentCount; + var hook = context.Thread.Hook!; + var stack = context.Thread.Stack; + if (context.Thread.IsCallHookEnabled) + { + stack.Push((isTailCall ? "tail call" : "call")); + + stack.Push(LuaValue.Nil); + var funcContext = new LuaFunctionExecutionContext + { + State = context.State, + Thread = context.Thread, + ArgumentCount = 2, + FrameBase = context.Thread.Stack.Count - 2, + ReturnFrameBase = context.Thread.Stack.Count - 2, + }; + CallStackFrame frame = new() + { + Base = funcContext.FrameBase, + ReturnBase = funcContext.ReturnFrameBase, + VariableArgumentCount = hook.GetVariableArgumentCount(2), + Function = hook, + CallerInstructionIndex = 0, + Flags = CallStackFrameFlags.InHook + }; + + context.Thread.PushCallStackFrame(frame); + try + { + context.Thread.IsInHook = true; + await hook.Func(funcContext, cancellationToken); + } + finally + { + context.Thread.IsInHook = false; + context.Thread.PopCallStackFrameWithStackPop(); + } + } + + { + ref readonly var frame = ref context.Thread.GetCurrentFrame(); + var task = frame.Function.Func(new() + { + State = context.State, + Thread = context.Thread, + ArgumentCount = argCount, + FrameBase = frame.Base, + ReturnFrameBase = frame.ReturnBase, + }, cancellationToken); + var r = await task; + if (isTailCall || !context.Thread.IsReturnHookEnabled) + { + return r; + } + + stack.Push("return"); + stack.Push(LuaValue.Nil); + var funcContext = new LuaFunctionExecutionContext + { + State = context.State, + Thread = context.Thread, + ArgumentCount = 2, + FrameBase = context.Thread.Stack.Count - 2, + ReturnFrameBase = context.Thread.Stack.Count - 2, + }; + + + context.Thread.PushCallStackFrame(new() + { + Base = funcContext.FrameBase, + ReturnBase = funcContext.ReturnFrameBase, + VariableArgumentCount = hook.GetVariableArgumentCount(2), + Function = hook, + CallerInstructionIndex = 0, + Flags = CallStackFrameFlags.InHook + }); + try + { + context.Thread.IsInHook = true; + await hook.Func(funcContext, cancellationToken); + } + finally + { + context.Thread.IsInHook = false; + } + + context.Thread.PopCallStackFrameWithStackPop(); + return r; + } + } +} \ No newline at end of file diff --git a/src/Lua/Runtime/LuaVirtualMachine.cs b/src/Lua/Runtime/LuaVirtualMachine.cs index d0ff6ae3..ee45d145 100644 --- a/src/Lua/Runtime/LuaVirtualMachine.cs +++ b/src/Lua/Runtime/LuaVirtualMachine.cs @@ -14,65 +14,78 @@ public static partial class LuaVirtualMachine struct VirtualMachineExecutionContext( LuaState state, LuaStack stack, - LuaValue[] resultsBuffer, - Memory buffer, LuaThread thread, in CallStackFrame frame, CancellationToken cancellationToken) { public readonly LuaState State = state; public readonly LuaStack Stack = stack; - public Closure Closure = (Closure)frame.Function; - public readonly LuaValue[] ResultsBuffer = resultsBuffer; - public readonly Memory Buffer = buffer; + public LuaClosure LuaClosure = (LuaClosure)frame.Function; public readonly LuaThread Thread = thread; - public Chunk Chunk => Closure.Proto; + public Chunk Chunk => LuaClosure.Proto; public int FrameBase = frame.Base; public int VariableArgumentCount = frame.VariableArgumentCount; public readonly CancellationToken CancellationToken = cancellationToken; public int Pc = -1; public Instruction Instruction; - public int ResultCount; - public int TaskResult; + public int CurrentReturnFrameBase = frame.ReturnBase; public ValueTask Task; + public int LastHookPc = -1; public bool IsTopLevel => BaseCallStackCount == Thread.CallStack.Count; - readonly int BaseCallStackCount = thread.CallStack.Count; + public readonly int BaseCallStackCount = thread.CallStack.Count; + + public PostOperationType PostOperation; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Pop(Instruction instruction, int frameBase) { - if (BaseCallStackCount == Thread.CallStack.Count) return false; var count = instruction.B - 1; var src = instruction.A + frameBase; if (count == -1) count = Stack.Count - src; - return PopFromBuffer(Stack.GetBuffer().Slice(src, count)); + return PopFromBuffer(src, count); } [MethodImpl(MethodImplOptions.NoInlining)] - public bool PopFromBuffer(Span result) + public bool PopFromBuffer(int src, int srcCount) { + var result = Stack.GetBuffer().Slice(src, srcCount); ref var callStack = ref Thread.CallStack; Re: var frames = callStack.AsSpan(); - if (frames.Length == BaseCallStackCount) return false; + if (frames.Length == BaseCallStackCount) + { + var returnBase = frames[^1].ReturnBase; + if (src != returnBase) + { + result.CopyTo(Stack.GetBuffer()[returnBase..]); + } + + Stack.PopUntil(returnBase + srcCount); + return false; + } + ref readonly var frame = ref frames[^1]; Pc = frame.CallerInstructionIndex; + Thread.LastPc = Pc; ref readonly var lastFrame = ref frames[^2]; - Closure = Unsafe.As(lastFrame.Function); + LuaClosure = Unsafe.As(lastFrame.Function); + CurrentReturnFrameBase = frame.ReturnBase; var callInstruction = Chunk.Instructions[Pc]; - FrameBase = lastFrame.Base; - VariableArgumentCount = lastFrame.VariableArgumentCount; if (callInstruction.OpCode == OpCode.TailCall) { - Thread.PopCallStackFrameUnsafe(); + Thread.PopCallStackFrame(); goto Re; } + + FrameBase = lastFrame.Base; + VariableArgumentCount = lastFrame.VariableArgumentCount; + var opCode = callInstruction.OpCode; if (opCode is OpCode.Eq or OpCode.Lt or OpCode.Le) { - var compareResult = result.Length > 0 && result[0].ToBoolean(); + var compareResult = srcCount > 0 && result[0].ToBoolean(); if ((frame.Flags & CallStackFrameFlags.ReversedLe) != 0) { compareResult = !compareResult; @@ -83,7 +96,7 @@ public bool PopFromBuffer(Span result) Pc++; } - Thread.PopCallStackFrameUnsafe(frame.Base); + Thread.PopCallStackFrameWithStackPop(); return true; } @@ -107,34 +120,32 @@ public bool PopFromBuffer(Span result) break; case OpCode.Self: Stack.Get(target) = result.Length == 0 ? LuaValue.Nil : result[0]; - Thread.PopCallStackFrameUnsafe(target + 2); + Thread.PopCallStackFrameWithStackPop(target + 2); return true; case OpCode.SetTable or OpCode.SetTabUp: targetCount = 0; break; // Other opcodes has one result default: - targetCount = 1; - break; + Stack.Get(target) = result.Length == 0 ? LuaValue.Nil : result[0]; + Thread.PopCallStackFrameWithStackPop(target + 1); + return true; } - var count = Math.Min(result.Length, targetCount); Stack.EnsureCapacity(target + targetCount); - - - var stackBuffer = Stack.GetBuffer(); - if (count > 0) + if (0 < targetCount && src != target) { - result[..count].CopyTo(stackBuffer.Slice(target, count)); - } + if (targetCount < result.Length) + { + result = result.Slice(0, targetCount); + } - if (targetCount > count) - { - stackBuffer.Slice(target + count, targetCount - count).Clear(); + result.CopyTo(Stack.GetBuffer().Slice(target, targetCount)); } + Stack.PopUntil(target + Math.Min(targetCount, srcCount)); Stack.NotifyTop(target + targetCount); - Thread.PopCallStackFrameUnsafe(target + targetCount); + Thread.PopCallStackFrame(); return true; } @@ -142,8 +153,9 @@ public bool PopFromBuffer(Span result) public void Push(in CallStackFrame frame) { Pc = -1; - Closure = (frame.Function as Closure)!; + LuaClosure = (LuaClosure)(frame.Function); FrameBase = frame.Base; + CurrentReturnFrameBase = frame.ReturnBase; VariableArgumentCount = frame.VariableArgumentCount; } @@ -160,78 +172,63 @@ public void PopOnTopCallStackFrames() Thread.PopCallStackFrame(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearResultsBuffer() + bool ExecutePostOperation(PostOperationType postOperation) { - if (TaskResult == 0) return; - if (TaskResult == 1) + var stackCount = Stack.Count; + var resultsSpan = Stack.GetBuffer()[CurrentReturnFrameBase..]; + switch (postOperation) { - ResultsBuffer[0] = default; - return; - } - - ResultsBuffer.AsSpan(0, TaskResult).Clear(); - } + case PostOperationType.Nop: break; + case PostOperationType.SetResult: + var RA = Instruction.A + FrameBase; + Stack.Get(RA) = stackCount > CurrentReturnFrameBase ? Stack.Get(CurrentReturnFrameBase) : LuaValue.Nil; + Stack.NotifyTop(RA + 1); + Stack.PopUntil(RA + 1); + break; + case PostOperationType.TForCall: + TForCallPostOperation(ref this); + break; + case PostOperationType.Call: + CallPostOperation(ref this); + break; + case PostOperationType.TailCall: + if (!PopFromBuffer(CurrentReturnFrameBase, Stack.Count - CurrentReturnFrameBase)) + { + return false; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ClearResultsBuffer(int count) - { - if (count == 0) return; - if (count == 1) - { - ResultsBuffer[0] = default; - return; + break; + case PostOperationType.Self: + SelfPostOperation(ref this, resultsSpan); + break; + case PostOperationType.Compare: + ComparePostOperation(ref this, resultsSpan); + break; } - ResultsBuffer.AsSpan(0, count).Clear(); + return true; } public async ValueTask ExecuteClosureAsyncImpl() { - while (MoveNext(ref this, out var postOperation)) + var returnFrameBase = CurrentReturnFrameBase; + + while (MoveNext(ref this)) { - TaskResult = await Task; + await Task; Task = default; - - Thread.PopCallStackFrame(); - switch (postOperation) + if (PostOperation != PostOperationType.TailCall) { - case PostOperationType.Nop: break; - case PostOperationType.SetResult: - var RA = Instruction.A + FrameBase; - Stack.Get(RA) = TaskResult == 0 ? LuaValue.Nil : ResultsBuffer[0]; - Stack.NotifyTop(RA + 1); - ClearResultsBuffer(); - break; - case PostOperationType.TForCall: - TForCallPostOperation(ref this); - break; - case PostOperationType.Call: - CallPostOperation(ref this); - break; - case PostOperationType.TailCall: - var resultsSpan = ResultsBuffer.AsSpan(0, TaskResult); - if (!PopFromBuffer(resultsSpan)) - { - ResultCount = TaskResult; - resultsSpan.CopyTo(Buffer.Span); - resultsSpan.Clear(); - LuaValueArrayPool.Return1024(ResultsBuffer); - return TaskResult; - } + Thread.PopCallStackFrame(); + } - resultsSpan.Clear(); - break; - case PostOperationType.Self: - SelfPostOperation(ref this); - break; - case PostOperationType.Compare: - ComparePostOperation(ref this); - break; + if (!ExecutePostOperation(PostOperation)) + { + break; } } - return ResultCount; + return Thread.Stack.Count - returnFrameBase; } } @@ -247,71 +244,86 @@ enum PostOperationType Compare, } - internal static ValueTask ExecuteClosureAsync(LuaState luaState, Memory buffer, CancellationToken cancellationToken) + internal static ValueTask ExecuteClosureAsync(LuaState luaState, CancellationToken cancellationToken) { var thread = luaState.CurrentThread; - ref readonly var frame = ref thread.GetCallStackFrames()[^1]; - var resultBuffer = LuaValueArrayPool.Rent1024(); + ref readonly var frame = ref thread.GetCurrentFrame(); - var context = new VirtualMachineExecutionContext(luaState, thread.Stack, resultBuffer, buffer, thread, in frame, + var context = new VirtualMachineExecutionContext(luaState, thread.Stack, thread, in frame, cancellationToken); return context.ExecuteClosureAsyncImpl(); } - static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperationType postOperation) + static bool MoveNext(ref VirtualMachineExecutionContext context) { - postOperation = PostOperationType.None; - try { - // This is a label to restart the execution when new function is called or restarted + // This is a label to restart the execution when new function is called or restarted Restart: ref var instructionsHead = ref context.Chunk.Instructions[0]; var frameBase = context.FrameBase; var stack = context.Stack; stack.EnsureCapacity(frameBase + context.Chunk.MaxStackPosition); ref var constHead = ref MemoryMarshalEx.UnsafeElementAt(context.Chunk.Constants, 0); + ref var lineAndCountHookMask = ref context.Thread.LineAndCountHookMask; + goto Loop; + LineHook: + { + context.LastHookPc = context.Pc; + if (!context.Thread.IsInHook && ExecutePerInstructionHook(ref context)) + { + { + context.PostOperation = PostOperationType.Nop; + return true; + } + } + + --context.Pc; + } + + Loop: while (true) { - var instructionRef = Unsafe.Add(ref instructionsHead, ++context.Pc); - context.Instruction = instructionRef; - switch (instructionRef.OpCode) + var instruction = Unsafe.Add(ref instructionsHead, ++context.Pc); + context.Instruction = instruction; + if (lineAndCountHookMask.Value != 0 && (context.Pc != context.LastHookPc)) + { + goto LineHook; + } + + context.LastHookPc = -1; + var iA = instruction.A; + var opCode = instruction.OpCode; + switch (opCode) { case OpCode.Move: - var instruction = instructionRef; ref var stackHead = ref stack.FastGet(frameBase); - var iA = instruction.A; Unsafe.Add(ref stackHead, iA) = Unsafe.Add(ref stackHead, instruction.UIntB); stack.NotifyTop(iA + frameBase + 1); continue; case OpCode.LoadK: - instruction = instructionRef; - stack.GetWithNotifyTop(instruction.A + frameBase) = Unsafe.Add(ref constHead, instruction.Bx); + stack.GetWithNotifyTop(iA + frameBase) = Unsafe.Add(ref constHead, instruction.Bx); continue; case OpCode.LoadBool: - instruction = instructionRef; - stack.GetWithNotifyTop(instruction.A + frameBase) = instruction.B != 0; + stack.GetWithNotifyTop(iA + frameBase) = instruction.B != 0; if (instruction.C != 0) context.Pc++; continue; case OpCode.LoadNil: - instruction = instructionRef; - var ra1 = instruction.A + frameBase + 1; + var ra1 = iA + frameBase + 1; var iB = instruction.B; stack.GetBuffer().Slice(ra1 - 1, iB + 1).Clear(); stack.NotifyTop(ra1 + iB); continue; case OpCode.GetUpVal: - instruction = instructionRef; - stack.GetWithNotifyTop(instruction.A + frameBase) = context.Closure.GetUpValue(instruction.B); + stack.GetWithNotifyTop(iA + frameBase) = context.LuaClosure.GetUpValue(instruction.B); continue; case OpCode.GetTabUp: case OpCode.GetTable: - instruction = instructionRef; stackHead = ref stack.FastGet(frameBase); ref readonly var vc = ref RKC(ref stackHead, ref constHead, instruction); - ref readonly var vb = ref (instruction.OpCode == OpCode.GetTable ? ref Unsafe.Add(ref stackHead, instruction.UIntB) : ref context.Closure.GetUpValueRef(instruction.B)); + ref readonly var vb = ref (instruction.OpCode == OpCode.GetTable ? ref Unsafe.Add(ref stackHead, instruction.UIntB) : ref context.LuaClosure.GetUpValueRef(instruction.B)); var doRestart = false; if (vb.TryReadTable(out var luaTable) && luaTable.TryGetValue(vc, out var resultValue) || GetTableValueSlowPath(vb, vc, ref context, out resultValue, out doRestart)) { @@ -320,10 +332,9 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - postOperation = PostOperationType.SetResult; return true; case OpCode.SetTabUp: - instruction = instructionRef; + case OpCode.SetTable: stackHead = ref stack.FastGet(frameBase); vb = ref RKB(ref stackHead, ref constHead, instruction); if (vb.TryReadNumber(out var numB)) @@ -335,7 +346,7 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati } } - var table = context.Closure.GetUpValue(instruction.A); + var table = opCode == OpCode.SetTabUp ? context.LuaClosure.GetUpValue(iA) : Unsafe.Add(ref stackHead, iA); if (table.TryReadTable(out luaTable)) { @@ -354,55 +365,15 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - postOperation = PostOperationType.Nop; return true; - case OpCode.SetUpVal: - instruction = instructionRef; - context.Closure.SetUpValue(instruction.B, stack.FastGet(instruction.A + frameBase)); + context.LuaClosure.SetUpValue(instruction.B, stack.FastGet(iA + frameBase)); continue; - case OpCode.SetTable: - instruction = instructionRef; - stackHead = ref stack.FastGet(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - if (vb.TryReadNumber(out numB)) - { - if (double.IsNaN(numB)) - { - ThrowLuaRuntimeException(ref context, " table index is NaN"); - - return true; - } - } - - table = Unsafe.Add(ref stackHead, instruction.A); - - if (table.TryReadTable(out luaTable)) - { - ref var valueRef = ref luaTable.FindValue(vb); - if (!Unsafe.IsNullRef(ref valueRef) && valueRef.Type != LuaValueType.Nil) - { - valueRef = RKC(ref stackHead, ref constHead, instruction); - continue; - } - } - - vc = ref RKC(ref stackHead, ref constHead, instruction); - if (SetTableValueSlowPath(table, vb, vc, ref context, out doRestart)) - { - if (doRestart) goto Restart; - continue; - } - - postOperation = PostOperationType.Nop; - return true; case OpCode.NewTable: - instruction = instructionRef; - stack.GetWithNotifyTop(instruction.A + frameBase) = new LuaTable(instruction.B, instruction.C); + + stack.GetWithNotifyTop(iA + frameBase) = new LuaTable(instruction.B, instruction.C); continue; case OpCode.Self: - instruction = instructionRef; - iA = instruction.A; stackHead = ref stack.FastGet(frameBase); vc = ref RKC(ref stackHead, ref constHead, instruction); table = Unsafe.Add(ref stackHead, instruction.UIntB); @@ -417,182 +388,66 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - postOperation = PostOperationType.Self; return true; case OpCode.Add: - instruction = instructionRef; - iA = instruction.A; - stackHead = ref stack.FastGet(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - vc = ref RKC(ref stackHead, ref constHead, instruction); - if (vb.Type == LuaValueType.Number && vc.Type == LuaValueType.Number) - { - Unsafe.Add(ref stackHead, iA) = vb.UnsafeReadDouble() + vc.UnsafeReadDouble(); - stack.NotifyTop(iA + frameBase + 1); - continue; - } - - if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out var numC)) - { - Unsafe.Add(ref stackHead, iA) = numB + numC; - stack.NotifyTop(iA + frameBase + 1); - continue; - } - - if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Add, "add", out doRestart)) - { - if (doRestart) goto Restart; - continue; - } - - postOperation = PostOperationType.SetResult; - return true; case OpCode.Sub: - instruction = instructionRef; - iA = instruction.A; - stackHead = ref stack.FastGet(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - vc = ref RKC(ref stackHead, ref constHead, instruction); - - if (vb.Type == LuaValueType.Number && vc.Type == LuaValueType.Number) - { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = vb.UnsafeReadDouble() - vc.UnsafeReadDouble(); - stack.NotifyTop(ra1); - continue; - } - - if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out numC)) - { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = numB - numC; - stack.NotifyTop(ra1); - continue; - } - - if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Sub, "sub", out doRestart)) - { - if (doRestart) goto Restart; - continue; - } - - postOperation = PostOperationType.SetResult; - return true; - case OpCode.Mul: - instruction = instructionRef; - iA = instruction.A; - stackHead = ref stack.FastGet(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - vc = ref RKC(ref stackHead, ref constHead, instruction); - - if (vb.Type == LuaValueType.Number && vc.Type == LuaValueType.Number) - { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = vb.UnsafeReadDouble() * vc.UnsafeReadDouble(); - stack.NotifyTop(ra1); - continue; - } - - if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out numC)) - { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = numB * numC; - stack.NotifyTop(ra1); - continue; - } - - if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Mul, "mul", out doRestart)) - { - if (doRestart) goto Restart; - continue; - } - - postOperation = PostOperationType.SetResult; - return true; - case OpCode.Div: - instruction = instructionRef; - iA = instruction.A; + case OpCode.Mod: + case OpCode.Pow: stackHead = ref stack.FastGet(frameBase); vb = ref RKB(ref stackHead, ref constHead, instruction); vc = ref RKC(ref stackHead, ref constHead, instruction); - if (vb.Type == LuaValueType.Number && vc.Type == LuaValueType.Number) + [MethodImpl(MethodImplOptions.NoInlining)] + static double Mod(double a, double b) { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = vb.UnsafeReadDouble() / vc.UnsafeReadDouble(); - stack.NotifyTop(ra1); - continue; - } - - if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out numC)) - { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = numB / numC; - stack.NotifyTop(ra1); - continue; - } + var mod = a % b; + if ((b > 0 && mod < 0) || (b < 0 && mod > 0)) + { + mod += b; + } - if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Div, "div", out doRestart)) - { - if (doRestart) goto Restart; - continue; + return mod; } - postOperation = PostOperationType.SetResult; - return true; - case OpCode.Mod: - instruction = instructionRef; - iA = instruction.A; - stackHead = ref stack.FastGet(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - vc = ref RKC(ref stackHead, ref constHead, instruction); - if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out numC)) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static double ArithmeticOperation(OpCode code, double a, double b) { - var mod = numB % numC; - if ((numC > 0 && mod < 0) || (numC < 0 && mod > 0)) + return code switch { - mod += numC; - } - - Unsafe.Add(ref stackHead, iA) = mod; - continue; + OpCode.Add => a + b, + OpCode.Sub => a - b, + OpCode.Mul => a * b, + OpCode.Div => a / b, + OpCode.Mod => Mod(a, b), + OpCode.Pow => Math.Pow(a, b), + _ => 0 + }; } - if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Mod, "mod", out doRestart)) + if (vb.Type == LuaValueType.Number && vc.Type == LuaValueType.Number) { - if (doRestart) goto Restart; + Unsafe.Add(ref stackHead, iA) = ArithmeticOperation(opCode, vb.UnsafeReadDouble(), vc.UnsafeReadDouble()); + stack.NotifyTop(iA + frameBase + 1); continue; } - postOperation = PostOperationType.SetResult; - return true; - case OpCode.Pow: - instruction = instructionRef; - iA = instruction.A; - stackHead = ref stack.FastGet(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - vc = ref RKC(ref stackHead, ref constHead, instruction); - if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out numC)) + if (vb.TryReadDouble(out numB) && vc.TryReadDouble(out var numC)) { - ra1 = iA + frameBase + 1; - Unsafe.Add(ref stackHead, iA) = Math.Pow(numB, numC); - stack.NotifyTop(ra1); + Unsafe.Add(ref stackHead, iA) = ArithmeticOperation(opCode, numB, numC); + stack.NotifyTop(iA + frameBase + 1); continue; } - if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Pow, "pow", out doRestart)) + if (ExecuteBinaryOperationMetaMethod(vb, vc, ref context, opCode, out doRestart)) { if (doRestart) goto Restart; continue; } - postOperation = PostOperationType.SetResult; return true; case OpCode.Unm: - instruction = instructionRef; - iA = instruction.A; stackHead = ref stack.FastGet(frameBase); vb = ref Unsafe.Add(ref stackHead, instruction.UIntB); @@ -604,45 +459,37 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - if (ExecuteUnaryOperationMetaMethod(vb, ref context, Metamethods.Unm, "unm", false, out doRestart)) + if (ExecuteUnaryOperationMetaMethod(vb, ref context, OpCode.Unm, out doRestart)) { if (doRestart) goto Restart; continue; } - postOperation = PostOperationType.SetResult; return true; case OpCode.Not: - instruction = instructionRef; - iA = instruction.A; - ra1 = iA + frameBase + 1; stackHead = ref stack.FastGet(frameBase); Unsafe.Add(ref stackHead, iA) = !Unsafe.Add(ref stackHead, instruction.UIntB).ToBoolean(); - stack.NotifyTop(ra1); + stack.NotifyTop(iA + frameBase + 1); continue; case OpCode.Len: - instruction = instructionRef; stackHead = ref stack.FastGet(frameBase); - vb = ref Unsafe.Add(ref stackHead, instruction.UIntB); if (vb.TryReadString(out var str)) { - iA = instruction.A; ra1 = iA + frameBase + 1; Unsafe.Add(ref stackHead, iA) = str.Length; stack.NotifyTop(ra1); continue; } - if (ExecuteUnaryOperationMetaMethod(vb, ref context, Metamethods.Len, "get length of", true, out doRestart)) + if (ExecuteUnaryOperationMetaMethod(vb, ref context, OpCode.Len, out doRestart)) { if (doRestart) goto Restart; continue; } - postOperation = PostOperationType.SetResult; return true; case OpCode.Concat: if (Concat(ref context, out doRestart)) @@ -651,12 +498,10 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - postOperation = PostOperationType.SetResult; return true; case OpCode.Jmp: - instruction = instructionRef; context.Pc += instruction.SBx; - iA = instruction.A; + if (iA != 0) { context.State.CloseUpValues(context.Thread, frameBase + iA - 1); @@ -664,8 +509,6 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; case OpCode.Eq: - instruction = instructionRef; - iA = instruction.A; stackHead = ref stack.Get(frameBase); vb = ref RKB(ref stackHead, ref constHead, instruction); vc = ref RKC(ref stackHead, ref constHead, instruction); @@ -679,62 +522,22 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - if (ExecuteCompareOperationMetaMethod(vb, vc, ref context, Metamethods.Eq, null, out doRestart)) + if (ExecuteCompareOperationMetaMethod(vb, vc, ref context, OpCode.Eq, out doRestart)) { if (doRestart) goto Restart; continue; } - postOperation = PostOperationType.Compare; return true; case OpCode.Lt: - instruction = instructionRef; - iA = instruction.A; - stackHead = ref stack.Get(frameBase); - vb = ref RKB(ref stackHead, ref constHead, instruction); - vc = ref RKC(ref stackHead, ref constHead, instruction); - - if (vb.TryReadNumber(out numB) && vc.TryReadNumber(out numC)) - { - var compareResult = numB < numC; - if (compareResult != (iA == 1)) - { - context.Pc++; - } - - continue; - } - - - if (vb.TryReadString(out var strB) && vc.TryReadString(out var strC)) - { - var compareResult = StringComparer.Ordinal.Compare(strB, strC) < 0; - if (compareResult != (iA == 1)) - { - context.Pc++; - } - - continue; - } - - if (ExecuteCompareOperationMetaMethod(vb, vc, ref context, Metamethods.Lt, "less than", out doRestart)) - { - if (doRestart) goto Restart; - continue; - } - - postOperation = PostOperationType.Compare; - return true; case OpCode.Le: - instruction = instructionRef; - iA = instruction.A; stackHead = ref stack.Get(frameBase); vb = ref RKB(ref stackHead, ref constHead, instruction); vc = ref RKC(ref stackHead, ref constHead, instruction); if (vb.TryReadNumber(out numB) && vc.TryReadNumber(out numC)) { - var compareResult = numB <= numC; + var compareResult = opCode == OpCode.Lt ? numB < numC : numB <= numC; if (compareResult != (iA == 1)) { context.Pc++; @@ -743,9 +546,10 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - if (vb.TryReadString(out strB) && vc.TryReadString(out strC)) + if (vb.TryReadString(out var strB) && vc.TryReadString(out var strC)) { - var compareResult = StringComparer.Ordinal.Compare(strB, strC) <= 0; + var c = StringComparer.Ordinal.Compare(strB, strC); + var compareResult = opCode == OpCode.Lt ? c < 0 : c <= 0; if (compareResult != (iA == 1)) { context.Pc++; @@ -754,24 +558,21 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - if (ExecuteCompareOperationMetaMethod(vb, vc, ref context, Metamethods.Le, "less than or equals", out doRestart)) + if (ExecuteCompareOperationMetaMethod(vb, vc, ref context, opCode, out doRestart)) { if (doRestart) goto Restart; continue; } - postOperation = PostOperationType.Compare; return true; case OpCode.Test: - instruction = instructionRef; - if (stack.Get(instruction.A + frameBase).ToBoolean() != (instruction.C == 1)) + if (stack.Get(iA + frameBase).ToBoolean() != (instruction.C == 1)) { context.Pc++; } continue; case OpCode.TestSet: - instruction = instructionRef; vb = ref stack.Get(instruction.B + frameBase); if (vb.ToBoolean() != (instruction.C == 1)) { @@ -779,7 +580,7 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati } else { - stack.GetWithNotifyTop(instruction.A + frameBase) = vb; + stack.GetWithNotifyTop(iA + frameBase) = vb; } continue; @@ -787,11 +588,14 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati case OpCode.Call: if (Call(ref context, out doRestart)) { - if (doRestart) goto Restart; + if (doRestart) + { + goto Restart; + } + continue; } - postOperation = PostOperationType.Call; return true; case OpCode.TailCall: if (TailCall(ref context, out doRestart)) @@ -801,47 +605,34 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - postOperation = PostOperationType.TailCall; return true; case OpCode.Return: - instruction = instructionRef; - iA = instruction.A; - ra1 = iA + frameBase + 1; context.State.CloseUpValues(context.Thread, frameBase); - if (context.Pop(instruction, frameBase)) goto Restart; - var retCount = instruction.B - 1; - - if (retCount == -1) + if (context.Pop(instruction, frameBase)) { - retCount = stack.Count - (ra1 - 1); + goto Restart; } - if (0 < retCount) - { - stack.GetBuffer().Slice(ra1 - 1, retCount).CopyTo(context.Buffer.Span); - } - - context.ResultCount = retCount; goto End; case OpCode.ForLoop: - ref var indexRef = ref stack.Get(instructionRef.A + frameBase); + ref var indexRef = ref stack.Get(iA + frameBase); var limit = Unsafe.Add(ref indexRef, 1).UnsafeReadDouble(); var step = Unsafe.Add(ref indexRef, 2).UnsafeReadDouble(); var index = indexRef.UnsafeReadDouble() + step; if (step >= 0 ? index <= limit : limit <= index) { - context.Pc += instructionRef.SBx; + context.Pc += instruction.SBx; indexRef = index; Unsafe.Add(ref indexRef, 3) = index; - stack.NotifyTop(instructionRef.A + frameBase + 4); + stack.NotifyTop(iA + frameBase + 4); continue; } - stack.NotifyTop(instructionRef.A + frameBase + 1); + stack.NotifyTop(iA + frameBase + 1); continue; case OpCode.ForPrep: - indexRef = ref stack.Get(instructionRef.A + frameBase); + indexRef = ref stack.Get(iA + frameBase); if (!indexRef.TryReadDouble(out var init)) { @@ -862,8 +653,8 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati } indexRef = init - step; - stack.NotifyTop(instructionRef.A + frameBase + 1); - context.Pc += instructionRef.SBx; + stack.NotifyTop(iA + frameBase + 1); + context.Pc += instruction.SBx; continue; case OpCode.TForCall: if (TForCall(ref context, out doRestart)) @@ -872,13 +663,9 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati continue; } - postOperation = PostOperationType.TForCall; return true; case OpCode.TForLoop: - instruction = instructionRef; - iA = instruction.A; - ra1 = iA + frameBase + 1; - ref var forState = ref stack.Get(ra1); + ref var forState = ref stack.Get(iA + frameBase + 1); if (forState.Type is not LuaValueType.Nil) { @@ -891,32 +678,37 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati SetList(ref context); continue; case OpCode.Closure: - instruction = instructionRef; - iA = instruction.A; ra1 = iA + frameBase + 1; stack.EnsureCapacity(ra1); - stack.Get(ra1 - 1) = new Closure(context.State, context.Chunk.Functions[instruction.SBx]); + stack.Get(ra1 - 1) = new LuaClosure(context.State, context.Chunk.Functions[instruction.SBx]); stack.NotifyTop(ra1); continue; case OpCode.VarArg: - instruction = instructionRef; - iA = instruction.A; - ra1 = iA + frameBase + 1; - var frameVariableArgumentCount = context.VariableArgumentCount; - var count = instruction.B == 0 - ? frameVariableArgumentCount - : instruction.B - 1; - var ra = ra1 - 1; - stack.EnsureCapacity(ra + count); - stackHead = ref stack.Get(0); - for (int i = 0; i < count; i++) + VarArg(ref context); + + static void VarArg(ref VirtualMachineExecutionContext context) { - Unsafe.Add(ref stackHead, ra + i) = frameVariableArgumentCount > i - ? Unsafe.Add(ref stackHead, frameBase - (frameVariableArgumentCount - i)) - : default; + var instruction = context.Instruction; + var iA = instruction.A; + var frameBase = context.FrameBase; + var frameVariableArgumentCount = context.VariableArgumentCount; + var count = instruction.B == 0 + ? frameVariableArgumentCount + : instruction.B - 1; + var ra = iA + frameBase; + var stack = context.Stack; + stack.EnsureCapacity(ra + count); + ref var stackHead = ref stack.Get(0); + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref stackHead, ra + i) = frameVariableArgumentCount > i + ? Unsafe.Add(ref stackHead, frameBase - (frameVariableArgumentCount - i)) + : default; + } + + stack.NotifyTop(ra + count); } - stack.NotifyTop(ra + count); continue; case OpCode.ExtraArg: default: @@ -926,22 +718,21 @@ static bool MoveNext(ref VirtualMachineExecutionContext context, out PostOperati } End: - postOperation = PostOperationType.None; - LuaValueArrayPool.Return1024(context.ResultsBuffer); + context.PostOperation = PostOperationType.None; return false; } catch (Exception e) { - context.PopOnTopCallStackFrames(); context.State.CloseUpValues(context.Thread, context.FrameBase); - LuaValueArrayPool.Return1024(context.ResultsBuffer, true); if (e is not LuaRuntimeException) { - var newException = new LuaRuntimeException(GetTracebacks(ref context), e); + var newException = new LuaRuntimeException(context.State.GetTraceback(), e); + context.PopOnTopCallStackFrames(); context = default; throw newException; } + context.PopOnTopCallStackFrames(); throw; } } @@ -958,7 +749,7 @@ static void ThrowLuaNotImplementedException(ref VirtualMachineExecutionContext c } - static void SelfPostOperation(ref VirtualMachineExecutionContext context) + static void SelfPostOperation(ref VirtualMachineExecutionContext context, Span results) { var stack = context.Stack; var instruction = context.Instruction; @@ -967,9 +758,8 @@ static void SelfPostOperation(ref VirtualMachineExecutionContext context) ref var stackHead = ref stack.Get(0); var table = Unsafe.Add(ref stackHead, RB); Unsafe.Add(ref stackHead, RA + 1) = table; - Unsafe.Add(ref stackHead, RA) = context.TaskResult == 0 ? LuaValue.Nil : context.ResultsBuffer[0]; + Unsafe.Add(ref stackHead, RA) = results.Length == 0 ? LuaValue.Nil : results[0]; stack.NotifyTop(RA + 2); - context.ClearResultsBuffer(); } static bool Concat(ref VirtualMachineExecutionContext context, out bool doRestart) @@ -1006,7 +796,7 @@ static bool Concat(ref VirtualMachineExecutionContext context, out bool doRestar return true; } - return ExecuteBinaryOperationMetaMethod(vb, vc, ref context, Metamethods.Concat, "concat", out doRestart); + return ExecuteBinaryOperationMetaMethod(vb, vc, ref context, OpCode.Concat, out doRestart); } static bool Call(ref VirtualMachineExecutionContext context, out bool doRestart) @@ -1029,10 +819,18 @@ static bool Call(ref VirtualMachineExecutionContext context, out bool doRestart) var thread = context.Thread; var (newBase, argumentCount, variableArgumentCount) = PrepareForFunctionCall(thread, func, instruction, RA); - var newFrame = func.CreateNewFrame(ref context, newBase, variableArgumentCount); + var newFrame = func.CreateNewFrame(ref context, newBase, RA, variableArgumentCount); thread.PushCallStackFrame(newFrame); - if (func is Closure) + if (thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.Call; + context.Task = ExecuteCallHook(ref context, newFrame, argumentCount); + doRestart = false; + return false; + } + + if (func is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1053,40 +851,22 @@ static bool FuncCall(ref VirtualMachineExecutionContext context, in CallStackFra } var awaiter = task.GetAwaiter(); - context.Thread.PopCallStackFrameUnsafe(newBase); - context.TaskResult = awaiter.GetResult(); + + awaiter.GetResult(); var instruction = context.Instruction; - var rawResultCount = context.TaskResult; - var resultCount = rawResultCount; var ic = instruction.C; if (ic != 0) { - resultCount = ic - 1; - } - - if (resultCount == 0) - { - context.Stack.Pop(); - } - else - { + var resultCount = ic - 1; var stack = context.Stack; - var RA = instruction.A + context.FrameBase; - stack.EnsureCapacity(RA + resultCount); - ref var stackHead = ref stack.Get(RA); - var results = context.ResultsBuffer.AsSpan(0, rawResultCount); - for (int i = 0; i < resultCount; i++) - { - Unsafe.Add(ref stackHead, i) = i >= rawResultCount - ? default - : results[i]; - } - - stack.NotifyTop(RA + resultCount); - results.Clear(); + var top = instruction.A + context.FrameBase + resultCount; + stack.EnsureCapacity(top); + stack.PopUntil(top); + stack.NotifyTop(top); } + context.Thread.PopCallStackFrame(); return true; } } @@ -1094,35 +874,16 @@ static bool FuncCall(ref VirtualMachineExecutionContext context, in CallStackFra static void CallPostOperation(ref VirtualMachineExecutionContext context) { var instruction = context.Instruction; - var rawResultCount = context.TaskResult; - var resultCount = rawResultCount; var ic = instruction.C; if (ic != 0) { - resultCount = ic - 1; - } - - if (resultCount == 0) - { - context.Stack.Pop(); - } - else - { + var resultCount = ic - 1; var stack = context.Stack; - var RA = instruction.A + context.FrameBase; - stack.EnsureCapacity(RA + resultCount); - ref var stackHead = ref stack.Get(RA); - var results = context.ResultsBuffer.AsSpan(0, rawResultCount); - for (int i = 0; i < resultCount; i++) - { - Unsafe.Add(ref stackHead, i) = i >= rawResultCount - ? default - : results[i]; - } - - stack.NotifyTop(RA + resultCount); - results.Clear(); + var top = instruction.A + context.FrameBase + resultCount; + stack.EnsureCapacity(top); + stack.PopUntil(top); + stack.NotifyTop(top); } } @@ -1148,12 +909,26 @@ static bool TailCall(ref VirtualMachineExecutionContext context, out bool doRest var (newBase, argumentCount, variableArgumentCount) = PrepareForFunctionTailCall(thread, func, instruction, RA); - var newFrame = func.CreateNewFrame(ref context, newBase, variableArgumentCount); + + var lastPc = thread.GetCurrentFrame().CallerInstructionIndex; + context.Thread.PopCallStackFrame(); + var newFrame = func.CreateNewTailCallFrame(ref context, newBase, context.CurrentReturnFrameBase, variableArgumentCount); + + newFrame.CallerInstructionIndex = lastPc; thread.PushCallStackFrame(newFrame); - context.Push(newFrame); - if (func is Closure) + if (thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.TailCall; + context.Task = ExecuteCallHook(ref context, newFrame, argumentCount, true); + doRestart = false; + return false; + } + + + if (func is LuaClosure) { + context.Push(newFrame); doRestart = true; return true; } @@ -1163,24 +938,19 @@ static bool TailCall(ref VirtualMachineExecutionContext context, out bool doRest if (!task.IsCompleted) { + context.PostOperation = PostOperationType.TailCall; context.Task = task; return false; } - context.Thread.PopCallStackFrame(); - doRestart = true; - var awaiter = task.GetAwaiter(); - var resultCount = awaiter.GetResult(); - var resultsSpan = context.ResultsBuffer.AsSpan(0, resultCount); - if (!context.PopFromBuffer(resultsSpan)) + task.GetAwaiter().GetResult(); + if (!context.PopFromBuffer(context.CurrentReturnFrameBase, context.Stack.Count - context.CurrentReturnFrameBase)) { - doRestart = false; - context.ResultCount = resultCount; - resultsSpan.CopyTo(context.Buffer.Span); + return true; } - resultsSpan.Clear(); + doRestart = true; return true; } @@ -1201,9 +971,17 @@ static bool TForCall(ref VirtualMachineExecutionContext context, out bool doRest stack.Get(newBase) = stack.Get(RA + 1); stack.Get(newBase + 1) = stack.Get(RA + 2); stack.NotifyTop(newBase + 2); - var newFrame = iterator.CreateNewFrame(ref context, newBase); + var newFrame = iterator.CreateNewFrame(ref context, newBase, RA + 3, iterator.GetVariableArgumentCount(2)); context.Thread.PushCallStackFrame(newFrame); - if (iterator is Closure) + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.TForCall; + context.Task = ExecuteCallHook(ref context, newFrame, 2); + doRestart = false; + return false; + } + + if (iterator is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1211,16 +989,16 @@ static bool TForCall(ref VirtualMachineExecutionContext context, out bool doRest } var task = iterator.Invoke(ref context, newFrame, 2); - if (!task.IsCompleted) { + context.PostOperation = PostOperationType.TForCall; context.Task = task; return false; } var awaiter = task.GetAwaiter(); - context.TaskResult = awaiter.GetResult(); + awaiter.GetResult(); context.Thread.PopCallStackFrame(); TForCallPostOperation(ref context); return true; @@ -1231,19 +1009,7 @@ static void TForCallPostOperation(ref VirtualMachineExecutionContext context) var stack = context.Stack; var instruction = context.Instruction; var RA = instruction.A + context.FrameBase; - var resultBuffer = context.ResultsBuffer; - var resultCount = context.TaskResult; - stack.EnsureCapacity(RA + instruction.C + 3); - for (int i = 1; i <= instruction.C; i++) - { - var index = i - 1; - stack.Get(RA + 2 + i) = index >= resultCount - ? LuaValue.Nil - : resultBuffer[i - 1]; - } - - stack.NotifyTop(RA + instruction.C + 3); - context.ClearResultsBuffer(resultCount); + stack.SetTop(RA + instruction.C + 3); } static void SetList(ref VirtualMachineExecutionContext context) @@ -1264,17 +1030,18 @@ static void SetList(ref VirtualMachineExecutionContext context) table.EnsureArrayCapacity((instruction.C - 1) * 50 + count); stack.GetBuffer().Slice(RA + 1, count) .CopyTo(table.GetArraySpan()[((instruction.C - 1) * 50)..]); + stack.PopUntil(RA + 1); } - static void ComparePostOperation(ref VirtualMachineExecutionContext context) + static void ComparePostOperation(ref VirtualMachineExecutionContext context, Span results) { - var compareResult = context.TaskResult != 0 && context.ResultsBuffer[0].ToBoolean(); + var compareResult = results.Length != 0 && results[0].ToBoolean(); if (compareResult != (context.Instruction.A == 1)) { context.Pc++; } - context.ClearResultsBuffer(); + results.Clear(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1345,8 +1112,16 @@ static bool CallGetTableFunc(LuaValue table, LuaFunction indexTable, LuaValue ke var newFrame = indexTable.CreateNewFrame(ref context, stack.Count - 2); context.Thread.PushCallStackFrame(newFrame); + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = context.Instruction.OpCode == OpCode.GetTable ? PostOperationType.SetResult : PostOperationType.Self; + context.Task = ExecuteCallHook(ref context, newFrame, 2); + doRestart = false; + result = default; + return false; + } - if (indexTable is Closure) + if (indexTable is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1358,16 +1133,17 @@ static bool CallGetTableFunc(LuaValue table, LuaFunction indexTable, LuaValue ke if (!task.IsCompleted) { + context.PostOperation = context.Instruction.OpCode == OpCode.GetTable ? PostOperationType.SetResult : PostOperationType.Self; context.Task = task; result = default; return false; } var awaiter = task.GetAwaiter(); - context.Thread.PopCallStackFrame(); - var resultCount = awaiter.GetResult(); - result = resultCount == 0 ? default : context.ResultsBuffer[0]; - context.ClearResultsBuffer(resultCount); + awaiter.GetResult(); + var results = stack.GetBuffer()[newFrame.Base..]; + result = results.Length == 0 ? default : results[0]; + context.Thread.PopCallStackFrameWithStackPop(); return true; } @@ -1417,6 +1193,7 @@ static bool SetTableValueSlowPath(LuaValue table, LuaValue key, LuaValue value, Function: if (table.TryReadFunction(out var function)) { + context.PostOperation = PostOperationType.Nop; return CallSetTableFunc(targetTable, function, key, value, ref context, out doRestart); } } @@ -1436,8 +1213,15 @@ static bool CallSetTableFunc(LuaValue table, LuaFunction newIndexFunction, LuaVa var newFrame = newIndexFunction.CreateNewFrame(ref context, stack.Count - 3); context.Thread.PushCallStackFrame(newFrame); + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.Nop; + context.Task = ExecuteCallHook(ref context, newFrame, 3); + doRestart = false; + return false; + } - if (newIndexFunction is Closure) + if (newIndexFunction is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1447,25 +1231,22 @@ static bool CallSetTableFunc(LuaValue table, LuaFunction newIndexFunction, LuaVa var task = newIndexFunction.Invoke(ref context, newFrame, 3); if (!task.IsCompleted) { + context.PostOperation = PostOperationType.Nop; context.Task = task; return false; } - var resultCount = task.GetAwaiter().GetResult(); - if (0 < resultCount) - { - context.ClearResultsBuffer(resultCount); - } - - thread.PopCallStackFrame(); + task.GetAwaiter().GetResult(); + thread.PopCallStackFrameWithStackPop(); return true; } [MethodImpl(MethodImplOptions.NoInlining)] static bool ExecuteBinaryOperationMetaMethod(LuaValue vb, LuaValue vc, - ref VirtualMachineExecutionContext context, string name, string description, out bool doRestart) + ref VirtualMachineExecutionContext context, OpCode opCode, out bool doRestart) { + var (name, description) = opCode.GetNameAndDescription(); doRestart = false; if (vb.TryGetMetamethod(context.State, name, out var metamethod) || vc.TryGetMetamethod(context.State, name, out metamethod)) @@ -1479,11 +1260,18 @@ static bool ExecuteBinaryOperationMetaMethod(LuaValue vb, LuaValue vc, stack.Push(vb); stack.Push(vc); - var newFrame = func.CreateNewFrame(ref context, stack.Count - 2); + var newFrame = func.CreateNewFrame(ref context, stack.Count - 2, context.FrameBase + context.Instruction.A, func.GetVariableArgumentCount(2)); context.Thread.PushCallStackFrame(newFrame); + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.SetResult; + context.Task = ExecuteCallHook(ref context, newFrame, 2); + doRestart = false; + return false; + } - if (func is Closure) + if (func is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1495,15 +1283,19 @@ static bool ExecuteBinaryOperationMetaMethod(LuaValue vb, LuaValue vc, if (!task.IsCompleted) { + context.PostOperation = PostOperationType.SetResult; context.Task = task; return false; } - var resultCount = task.GetAwaiter().GetResult(); - context.Thread.PopCallStackFrame(); + task.GetAwaiter().GetResult(); + var RA = context.Instruction.A + context.FrameBase; - stack.Get(RA) = resultCount == 0 ? LuaValue.Nil : context.ResultsBuffer[0]; - context.ClearResultsBuffer(resultCount); + + var results = stack.GetBuffer()[newFrame.Base..]; + stack.Get(RA) = results.Length == 0 ? default : results[0]; + results.Clear(); + context.Thread.PopCallStackFrameWithStackPop(); return true; } @@ -1513,8 +1305,9 @@ static bool ExecuteBinaryOperationMetaMethod(LuaValue vb, LuaValue vc, [MethodImpl(MethodImplOptions.NoInlining)] static bool ExecuteUnaryOperationMetaMethod(LuaValue vb, ref VirtualMachineExecutionContext context, - string name, string description, bool isLen, out bool doRestart) + OpCode opCode, out bool doRestart) { + var (name, description) = opCode.GetNameAndDescription(); doRestart = false; var stack = context.Stack; if (vb.TryGetMetamethod(context.State, name, out var metamethod)) @@ -1528,8 +1321,15 @@ static bool ExecuteUnaryOperationMetaMethod(LuaValue vb, ref VirtualMachineExecu var newFrame = func.CreateNewFrame(ref context, stack.Count - 1); context.Thread.PushCallStackFrame(newFrame); + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.SetResult; + context.Task = ExecuteCallHook(ref context, newFrame, 1); + doRestart = false; + return false; + } - if (func is Closure) + if (func is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1541,19 +1341,20 @@ static bool ExecuteUnaryOperationMetaMethod(LuaValue vb, ref VirtualMachineExecu if (!task.IsCompleted) { + context.PostOperation = PostOperationType.SetResult; context.Task = task; return false; } - context.Thread.PopCallStackFrame(); var RA = context.Instruction.A + context.FrameBase; - var resultCount = task.GetAwaiter().GetResult(); - stack.Get(RA) = resultCount == 0 ? LuaValue.Nil : context.ResultsBuffer[0]; - context.ClearResultsBuffer(resultCount); + var results = stack.GetBuffer()[newFrame.Base..]; + stack.Get(RA) = results.Length == 0 ? default : results[0]; + results.Clear(); + context.Thread.PopCallStackFrameWithStackPop(); return true; } - if (isLen && vb.TryReadTable(out var table)) + if (opCode == OpCode.Len && vb.TryReadTable(out var table)) { var RA = context.Instruction.A + context.FrameBase; stack.Get(RA) = table.ArrayLength; @@ -1566,8 +1367,9 @@ static bool ExecuteUnaryOperationMetaMethod(LuaValue vb, ref VirtualMachineExecu [MethodImpl(MethodImplOptions.NoInlining)] static bool ExecuteCompareOperationMetaMethod(LuaValue vb, LuaValue vc, - ref VirtualMachineExecutionContext context, string name, string? description, out bool doRestart) + ref VirtualMachineExecutionContext context, OpCode opCode, out bool doRestart) { + var (name, description) = opCode.GetNameAndDescription(); doRestart = false; bool reverseLe = false; ReCheck: @@ -1585,8 +1387,15 @@ static bool ExecuteCompareOperationMetaMethod(LuaValue vb, LuaValue vc, var newFrame = func.CreateNewFrame(ref context, stack.Count - 2); if (reverseLe) newFrame.Flags |= CallStackFrameFlags.ReversedLe; context.Thread.PushCallStackFrame(newFrame); + if (context.Thread.CallOrReturnHookMask.Value != 0 && !context.Thread.IsInHook) + { + context.PostOperation = PostOperationType.Compare; + context.Task = ExecuteCallHook(ref context, newFrame, 2); + doRestart = false; + return false; + } - if (func is Closure) + if (func is LuaClosure) { context.Push(newFrame); doRestart = true; @@ -1597,25 +1406,26 @@ static bool ExecuteCompareOperationMetaMethod(LuaValue vb, LuaValue vc, if (!task.IsCompleted) { + context.PostOperation = PostOperationType.Compare; context.Task = task; return false; } - context.Thread.PopCallStackFrame(); - var resultCount = task.GetAwaiter().GetResult(); - var compareResult = resultCount != 0 && context.ResultsBuffer[0].ToBoolean(); + var results = stack.GetBuffer()[newFrame.Base..]; + var compareResult = results.Length == 0 && results[0].ToBoolean(); compareResult = reverseLe ? !compareResult : compareResult; if (compareResult != (context.Instruction.A == 1)) { context.Pc++; } - context.ClearResultsBuffer(resultCount); + results.Clear(); + context.Thread.PopCallStackFrameWithStackPop(); return true; } - if (name == Metamethods.Le) + if (opCode == OpCode.Le) { reverseLe = true; name = Metamethods.Lt; @@ -1623,7 +1433,7 @@ static bool ExecuteCompareOperationMetaMethod(LuaValue vb, LuaValue vc, goto ReCheck; } - if (description != null) + if (opCode != OpCode.Eq) { if (reverseLe) { @@ -1737,22 +1547,51 @@ static Traceback GetTracebacks(LuaState state, int pc) CallerInstructionIndex = pc }); var tracebacks = state.GetTraceback(); - state.CurrentThread.PopCallStackFrame(); + state.CurrentThread.PopCallStackFrameWithStackPop(); return tracebacks; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static CallStackFrame CreateNewFrame(this LuaFunction function, ref VirtualMachineExecutionContext context, int newBase, int variableArgumentCount = 0) + static CallStackFrame CreateNewFrame(this LuaFunction function, ref VirtualMachineExecutionContext context, int newBase) + { + return new() + { + Base = newBase, + ReturnBase = newBase, + Function = function, + VariableArgumentCount = 0, + CallerInstructionIndex = context.Pc, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static CallStackFrame CreateNewFrame(this LuaFunction function, ref VirtualMachineExecutionContext context, int newBase, int returnBase, int variableArgumentCount) { return new() { Base = newBase, + ReturnBase = returnBase, Function = function, VariableArgumentCount = variableArgumentCount, CallerInstructionIndex = context.Pc, }; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static CallStackFrame CreateNewTailCallFrame(this LuaFunction function, ref VirtualMachineExecutionContext context, int newBase, int returnBase, int variableArgumentCount) + { + return new() + { + Base = newBase, + ReturnBase = returnBase, + Function = function, + VariableArgumentCount = variableArgumentCount, + CallerInstructionIndex = context.Pc, + Flags = CallStackFrameFlags.TailCall + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] static ValueTask Invoke(this LuaFunction function, ref VirtualMachineExecutionContext context, in CallStackFrame frame, int arguments) { @@ -1762,7 +1601,8 @@ static ValueTask Invoke(this LuaFunction function, ref VirtualMachineExecut Thread = context.Thread, ArgumentCount = arguments, FrameBase = frame.Base, + ReturnFrameBase = frame.ReturnBase, CallerInstructionIndex = frame.CallerInstructionIndex, - }, context.ResultsBuffer, context.CancellationToken); + }, context.CancellationToken); } } \ No newline at end of file diff --git a/src/Lua/Runtime/Metamethods.cs b/src/Lua/Runtime/Metamethods.cs index 16957d04..b766efb6 100644 --- a/src/Lua/Runtime/Metamethods.cs +++ b/src/Lua/Runtime/Metamethods.cs @@ -21,4 +21,45 @@ public static class Metamethods public const string Pairs = "__pairs"; public const string IPairs = "__ipairs"; public new const string ToString = "__tostring"; + + internal static (string Name, string Description) GetNameAndDescription(this OpCode opCode) + { + switch (opCode) + { + case OpCode.GetTabUp: + case OpCode.GetTable: + case OpCode.Self: + return (Index, "index"); + case OpCode.SetTabUp: + case OpCode.SetTable: + return (NewIndex, "new index"); + case OpCode.Add: + return (Add, "add"); + case OpCode.Sub: + return (Sub, "sub"); + case OpCode.Mul: + return (Mul, "mul"); + case OpCode.Div: + return (Div, "div"); + case OpCode.Mod: + return (Mod, "mod"); + case OpCode.Pow: + return (Pow, "pow"); + case OpCode.Unm: + return (Unm, "unm"); + case OpCode.Len: + return (Len, "get length of"); + case OpCode.Eq: + return (Eq, "eq"); + case OpCode.Lt: + return (Lt, "lt"); + case OpCode.Le: + return (Le, "le"); + case OpCode.Call: + return (Call, "call"); + case OpCode.Concat: + return (Concat, "concat"); + default: return (opCode.ToString(), opCode.ToString()); + } + } } \ No newline at end of file diff --git a/src/Lua/Runtime/Tracebacks.cs b/src/Lua/Runtime/Tracebacks.cs index 2a97ea19..93df737b 100644 --- a/src/Lua/Runtime/Tracebacks.cs +++ b/src/Lua/Runtime/Tracebacks.cs @@ -5,12 +5,13 @@ namespace Lua.Runtime; -public class Traceback +public class Traceback(LuaState state) { - public required Closure RootFunc { get; init; } + public LuaState State => state; + public required LuaClosure RootFunc { get; init; } public required CallStackFrame[] StackFrames { get; init; } - internal string RootChunkName => RootFunc.Proto.Name; //StackFrames.Length == 0 ? "" : StackFrames[^1].Function is Closure closure ? closure.Proto.GetRoot().Name : StackFrames[^2].Function.Name; + internal string RootChunkName => RootFunc.Proto.Name; internal SourcePosition LastPosition { @@ -21,49 +22,162 @@ internal SourcePosition LastPosition { LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : RootFunc; var frame = stackFrames[index]; - if (lastFunc is Closure closure) + if (!frame.IsTailCall && lastFunc is LuaClosure closure) { var p = closure.Proto; + if (frame.CallerInstructionIndex < 0 || p.SourcePositions.Length <= frame.CallerInstructionIndex) + { + Console.WriteLine($"Trace back error"); + return default; + } + return p.SourcePositions[frame.CallerInstructionIndex]; } } + return default; } } - public override string ToString() + { + return GetTracebackString(State, RootFunc, StackFrames, LuaValue.Nil); + } + + public string ToString(int skipFrames) + { + if(skipFrames < 0 || skipFrames >= StackFrames.Length) + { + return "stack traceback:\n"; + } + return GetTracebackString(State, RootFunc, StackFrames[..^skipFrames], LuaValue.Nil); + } + + internal static string GetTracebackString(LuaState state, LuaClosure rootFunc, ReadOnlySpan stackFrames, LuaValue message, bool skipFirstCsharpCall = false) { using var list = new PooledList(64); + if (message.Type is not LuaValueType.Nil) + { + list.AddRange(message.ToString()); + list.AddRange("\n"); + } + list.AddRange("stack traceback:\n"); - var stackFrames = StackFrames.AsSpan(); var intFormatBuffer = (stackalloc char[15]); + var shortSourceBuffer = (stackalloc char[59]); + { + if (0 < stackFrames.Length && !skipFirstCsharpCall && stackFrames[^1].Function is { } f and not LuaClosure) + { + list.AddRange("\t[C#]: in function '"); + list.AddRange(f.Name); + list.AddRange("'\n"); + } + } + 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) + LuaFunction lastFunc = index > 0 ? stackFrames[index - 1].Function : rootFunc; + if (lastFunc is not null and not LuaClosure) { list.AddRange("\t[C#]: in function '"); list.AddRange(lastFunc.Name); list.AddRange("'\n"); } - else if (lastFunc is Closure closure) + else if (lastFunc is LuaClosure closure) { + var frame = stackFrames[index]; + + if (frame.IsTailCall) + { + list.AddRange("\t(...tail calls...)\n"); + } + var p = closure.Proto; var root = p.GetRoot(); list.AddRange("\t"); - list.AddRange(root.Name); + var len = LuaDebug.WriteShortSource(root.Name, shortSourceBuffer); + list.AddRange(shortSourceBuffer[..len]); 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"); + if (p.SourcePositions.Length <= frame.CallerInstructionIndex) + { + list.AddRange("Trace back error"); + } + else + { + p.SourcePositions[frame.CallerInstructionIndex].Line.TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture); + list.AddRange(intFormatBuffer[..charsWritten]); + } + + + list.AddRange(": in "); + if (root == p) + { + list.AddRange("main chunk"); + list.AddRange("\n"); + goto Next; + } + + if (0 < index && stackFrames[index - 1].Flags.HasFlag(CallStackFrameFlags.InHook)) + { + list.AddRange("hook"); + list.AddRange(" '"); + list.AddRange("?"); + list.AddRange("'\n"); + goto Next; + } + + foreach (var pair in state.Environment.Dictionary) + { + if (pair.Key.TryReadString(out var name) + && pair.Value.TryReadFunction(out var result) && + result == closure) + { + list.AddRange("function '"); + list.AddRange(name); + list.AddRange("'\n"); + goto Next; + } + } + + var caller = index > 1 ? stackFrames[index - 2].Function : rootFunc; + if (index > 0 && caller is LuaClosure callerClosure) + { + var t = LuaDebug.GetFuncName(callerClosure.Proto, stackFrames[index - 1].CallerInstructionIndex, out var name); + if (t is not null) + { + if (t is "global") + { + list.AddRange("function '"); + list.AddRange(name); + list.AddRange("'\n"); + } + else + { + list.AddRange(t); + list.AddRange(" '"); + list.AddRange(name); + list.AddRange("'\n"); + } + + goto Next; + } + } + + + list.AddRange("function <"); + list.AddRange(shortSourceBuffer[..len]); + list.AddRange(":"); + { + p.LineDefined.TryFormat(intFormatBuffer, out var charsWritten, provider: CultureInfo.InvariantCulture); + list.AddRange(intFormatBuffer[..charsWritten]); + list.AddRange(">\n"); + } + + Next: ; } } - return list.AsSpan().ToString(); + return list.AsSpan()[..^1].ToString(); } } \ No newline at end of file diff --git a/src/Lua/Standard/BasicLibrary.cs b/src/Lua/Standard/BasicLibrary.cs index cc72b997..3577de5b 100644 --- a/src/Lua/Standard/BasicLibrary.cs +++ b/src/Lua/Standard/BasicLibrary.cs @@ -11,7 +11,8 @@ public sealed class BasicLibrary public BasicLibrary() { - Functions = [ + Functions = + [ new("assert", Assert), new("collectgarbage", CollectGarbage), new("dofile", DoFile), @@ -36,7 +37,7 @@ public BasicLibrary() new("xpcall", XPCall), ]; - IPairsIterator = new("iterator", (context, buffer, cancellationToken) => + IPairsIterator = new("iterator", (context, cancellationToken) => { var table = context.GetArgument(0); var i = context.GetArgument(1); @@ -44,16 +45,12 @@ public BasicLibrary() i++; if (table.TryGetValue(i, out var value)) { - buffer.Span[0] = i; - buffer.Span[1] = value; + return new(context.Return(i, value)); } else { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = LuaValue.Nil; + return new(context.Return(LuaValue.Nil, LuaValue.Nil)); } - - return new(2); }); PairsIterator = new("iterator", Next); @@ -63,7 +60,7 @@ public BasicLibrary() readonly LuaFunction IPairsIterator; readonly LuaFunction PairsIterator; - public ValueTask Assert(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Assert(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); @@ -78,38 +75,43 @@ public ValueTask Assert(LuaFunctionExecutionContext context, Memory CollectGarbage(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask CollectGarbage(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { GC.Collect(); - return new(0); + return new(context.Return()); } - public async ValueTask DoFile(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask DoFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); // do not use LuaState.DoFileAsync as it uses the newExecutionContext var text = await File.ReadAllTextAsync(arg0, cancellationToken); - var fileName = Path.GetFileName(arg0); + var fileName = "@" + Path.GetFileName(arg0); var chunk = LuaCompiler.Default.Compile(text, fileName); - return await new Closure(context.State, chunk).InvokeAsync(context, buffer, cancellationToken); + return await new LuaClosure(context.State, chunk).InvokeAsync(context, cancellationToken); } - public ValueTask Error(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Error(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - var value = context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil - ? "(error object is a nil value)" + var value = context.ArgumentCount == 0 + ? LuaValue.Nil : context.Arguments[0]; - throw new LuaRuntimeException(context.State.GetTraceback(), value); + var traceback = context.State.GetTraceback(); + if (value.TryReadString(out var str)) + { + value = $"{traceback.RootChunkName}:{traceback.LastPosition.Line}: {str}"; + } + + throw new LuaRuntimeException(traceback, value); } - public ValueTask GetMetatable(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask GetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); @@ -117,26 +119,26 @@ public ValueTask GetMetatable(LuaFunctionExecutionContext context, Memory IPairs(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask IPairs(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); @@ -148,16 +150,13 @@ public ValueTask IPairs(LuaFunctionExecutionContext context, Memory LoadFile(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask LoadFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { // Lua-CSharp does not support binary chunks, the mode argument is ignored. var arg0 = context.GetArgument(0); @@ -169,20 +168,17 @@ public async ValueTask LoadFile(LuaFunctionExecutionContext context, Memory try { var text = await File.ReadAllTextAsync(arg0, cancellationToken); - var fileName = Path.GetFileName(arg0); + var fileName = "@" + Path.GetFileName(arg0); var chunk = LuaCompiler.Default.Compile(text, fileName); - buffer.Span[0] = new Closure(context.State, chunk, arg2); - return 1; + return context.Return(new LuaClosure(context.State, chunk, arg2)); } catch (Exception ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - return 2; + return context.Return(LuaValue.Nil, ex.Message); } } - public ValueTask Load(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Load(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { // Lua-CSharp does not support binary chunks, the mode argument is ignored. var arg0 = context.GetArgument(0); @@ -200,9 +196,8 @@ public ValueTask Load(LuaFunctionExecutionContext context, Memory { if (arg0.TryRead(out var str)) { - var chunk = LuaCompiler.Default.Compile(str, arg1 ?? "chunk"); - buffer.Span[0] = new Closure(context.State, chunk, arg3); - return new(1); + var chunk = LuaCompiler.Default.Compile(str, arg1 ?? str); + return new(context.Return(new LuaClosure(context.State, chunk, arg3))); } else if (arg0.TryRead(out var function)) { @@ -217,31 +212,26 @@ public ValueTask Load(LuaFunctionExecutionContext context, Memory } catch (Exception ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - return new(2); + return new(context.Return(LuaValue.Nil, ex.Message)); } } - public ValueTask Next(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Next(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.HasArgument(1) ? context.Arguments[1] : LuaValue.Nil; if (arg0.TryGetNext(arg1, out var kv)) { - buffer.Span[0] = kv.Key; - buffer.Span[1] = kv.Value; - return new(2); + return new(context.Return(kv.Key, kv.Value)); } else { - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new(context.Return(LuaValue.Nil)); } } - public ValueTask Pairs(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Pairs(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); @@ -253,115 +243,100 @@ public ValueTask Pairs(LuaFunctionExecutionContext context, Memory PCall(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask PCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - try { - using var methodBuffer = new PooledArray(1024); - - var resultCount = await arg0.InvokeAsync(context with + var count = await arg0.InvokeAsync(context with { State = context.State, ArgumentCount = context.ArgumentCount - 1, FrameBase = context.FrameBase + 1, - }, methodBuffer.AsMemory(), cancellationToken); + ReturnFrameBase = context.ReturnFrameBase + 1 + }, cancellationToken); - buffer.Span[0] = true; - methodBuffer.AsSpan()[..resultCount].CopyTo(buffer.Span[1..]); - - return resultCount + 1; + context.Thread.Stack.Get(context.ReturnFrameBase) = true; + return count + 1; } catch (Exception ex) { - buffer.Span[0] = false; if (ex is LuaRuntimeException { ErrorObject: not null } luaEx) { - buffer.Span[1] = luaEx.ErrorObject.Value; + return context.Return(false, luaEx.ErrorObject.Value); } else { - buffer.Span[1] = ex.Message; + return context.Return(false, ex.Message); } - - return 2; } } - public async ValueTask Print(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask Print(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - using var methodBuffer = new PooledArray(1); - for (int i = 0; i < context.ArgumentCount; i++) { - await context.Arguments[i].CallToStringAsync(context, methodBuffer.AsMemory(), cancellationToken); - Console.Write(methodBuffer[0]); + var top = context.Thread.Stack.Count; + await context.Arguments[i].CallToStringAsync(context, cancellationToken); + Console.Write(context.Thread.Stack.Get(top).ToString()); Console.Write('\t'); } Console.WriteLine(); - return 0; + return context.Return(); } - public ValueTask RawEqual(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RawEqual(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - buffer.Span[0] = arg0 == arg1; - return new(1); + return new(context.Return(arg0 == arg1)); } - public ValueTask RawGet(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RawGet(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - - buffer.Span[0] = arg0[arg1]; - return new(1); + return new(context.Return(arg0[arg1])); } - public ValueTask RawLen(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RawLen(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); if (arg0.TryRead(out var table)) { - buffer.Span[0] = table.ArrayLength; + return new(context.Return(table.ArrayLength)); } else if (arg0.TryRead(out var str)) { - buffer.Span[0] = str.Length; + return new(context.Return(str.Length)); } else { LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "rawlen", [LuaValueType.String, LuaValueType.Table]); + return default; } - - return new(1); } - public ValueTask RawSet(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RawSet(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); var arg2 = context.GetArgument(2); arg0[arg1] = arg2; - return new(0); + return new(context.Return()); } - public ValueTask Select(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Select(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); @@ -376,14 +351,11 @@ public ValueTask Select(LuaFunctionExecutionContext context, Memory(out var str) && str == "#") { - buffer.Span[0] = context.ArgumentCount - 1; - return new(1); + return new(context.Return(context.ArgumentCount - 1)); } else { @@ -392,7 +364,7 @@ public ValueTask Select(LuaFunctionExecutionContext context, Memory SetMetatable(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask SetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); @@ -415,11 +387,11 @@ public ValueTask SetMetatable(LuaFunctionExecutionContext context, Memory(); } - buffer.Span[0] = arg0; - return new(1); + + return new(context.Return(arg0)); } - public ValueTask ToNumber(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask ToNumber(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var e = context.GetArgument(0); int? toBase = context.HasArgument(1) @@ -466,6 +438,7 @@ public ValueTask ToNumber(LuaFunctionExecutionContext context, Memory 2 && span[0] is '0' && span[1] is 'x' or 'X') @@ -489,13 +462,13 @@ public ValueTask ToNumber(LuaFunctionExecutionContext context, Memory text, int toBase) @@ -555,17 +528,18 @@ static double StringToDouble(ReadOnlySpan text, int toBase) return value; } - public ValueTask ToString(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask ToString(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - return arg0.CallToStringAsync(context, buffer, cancellationToken); + context.Return(); + return arg0.CallToStringAsync(context, cancellationToken); } - public ValueTask Type(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Type(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = arg0.Type switch + return new(context.Return(arg0.Type switch { LuaValueType.Nil => "nil", LuaValueType.Boolean => "boolean", @@ -573,56 +547,47 @@ public ValueTask Type(LuaFunctionExecutionContext context, Memory LuaValueType.Number => "number", LuaValueType.Function => "function", LuaValueType.Thread => "thread", + LuaValueType.LightUserData => "userdata", LuaValueType.UserData => "userdata", LuaValueType.Table => "table", _ => throw new NotImplementedException(), - }; - - return new(1); + })); } - public async ValueTask XPCall(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask XPCall(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - using var methodBuffer = new PooledArray(1024); - methodBuffer.AsSpan().Clear(); - try { - var resultCount = await arg0.InvokeAsync(context with + var count = await arg0.InvokeAsync(context with { State = context.State, ArgumentCount = context.ArgumentCount - 2, FrameBase = context.FrameBase + 2, - }, methodBuffer.AsMemory(), cancellationToken); - - buffer.Span[0] = true; - methodBuffer.AsSpan()[..resultCount].CopyTo(buffer.Span[1..]); + ReturnFrameBase = context.ReturnFrameBase + 1 + }, cancellationToken); - return resultCount + 1; + context.Thread.Stack.Get(context.ReturnFrameBase) = true; + return count + 1; } catch (Exception ex) { - methodBuffer.AsSpan().Clear(); var error = ex is LuaRuntimeException { ErrorObject: not null } luaEx ? luaEx.ErrorObject.Value : ex.Message; context.State.Push(error); // invoke error handler - await arg1.InvokeAsync(context with + var count = await arg1.InvokeAsync(context with { State = context.State, ArgumentCount = 1, FrameBase = context.Thread.Stack.Count - 1, - }, methodBuffer.AsMemory(), cancellationToken); - - buffer.Span[0] = false; - buffer.Span[1] = methodBuffer[0]; - - - return 2; + ReturnFrameBase = context.ReturnFrameBase + 1 + }, cancellationToken); + context.Thread.Stack.Get(context.ReturnFrameBase) = false; + return count + 1; } } } \ No newline at end of file diff --git a/src/Lua/Standard/BitwiseLibrary.cs b/src/Lua/Standard/BitwiseLibrary.cs index d4ad2d08..e508f6c3 100644 --- a/src/Lua/Standard/BitwiseLibrary.cs +++ b/src/Lua/Standard/BitwiseLibrary.cs @@ -8,7 +8,8 @@ public sealed class BitwiseLibrary public BitwiseLibrary() { - Functions = [ + Functions = + [ new("arshift", ArShift), new("band", BAnd), new("bnot", BNot), @@ -26,7 +27,7 @@ public BitwiseLibrary() public readonly LuaFunction[] Functions; - public ValueTask ArShift(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask ArShift(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); var disp = context.GetArgument(1); @@ -46,16 +47,15 @@ public ValueTask ArShift(LuaFunctionExecutionContext context, Memory>= a; } - buffer.Span[0] = (uint)v; - return new(1); + return new(context.Return((uint)v)); } - public ValueTask BAnd(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask BAnd(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.ArgumentCount == 0) { - buffer.Span[0] = uint.MaxValue; - return new(1); + context.Return(uint.MaxValue); + return default; } var arg0 = context.GetArgument(0); @@ -72,26 +72,24 @@ public ValueTask BAnd(LuaFunctionExecutionContext context, Memory value &= v; } - buffer.Span[0] = value; - return new(1); + + return new(context.Return(value)); } - public ValueTask BNot(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask BNot(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "bnot", 1, arg0); var value = Bit32Helper.ToUInt32(arg0); - buffer.Span[0] = ~value; - return new(1); + return new(context.Return(~value)); } - public ValueTask BOr(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask BOr(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.ArgumentCount == 0) { - buffer.Span[0] = 0; - return new(1); + return new(context.Return(0)); } var arg0 = context.GetArgument(0); @@ -108,16 +106,15 @@ public ValueTask BOr(LuaFunctionExecutionContext context, Memory value |= v; } - buffer.Span[0] = value; - return new(1); + return new(context.Return(value)); } - public ValueTask BTest(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask BTest(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.ArgumentCount == 0) { - buffer.Span[0] = true; - return new(1); + ; + return new(context.Return(true)); } var arg0 = context.GetArgument(0); @@ -134,16 +131,14 @@ public ValueTask BTest(LuaFunctionExecutionContext context, Memory BXor(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask BXor(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.ArgumentCount == 0) { - buffer.Span[0] = 0; - return new(1); + return new(context.Return(0)); } var arg0 = context.GetArgument(0); @@ -160,11 +155,10 @@ public ValueTask BXor(LuaFunctionExecutionContext context, Memory value ^= v; } - buffer.Span[0] = value; - return new(1); + return new(context.Return(value)); } - public ValueTask Extract(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Extract(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); @@ -184,18 +178,16 @@ public ValueTask Extract(LuaFunctionExecutionContext context, Memory> field) & mask; + return new(context.Return((n >> field) & mask)); } - - return new(1); } - public ValueTask LRotate(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask LRotate(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); var disp = context.GetArgument(1); @@ -215,11 +207,11 @@ public ValueTask LRotate(LuaFunctionExecutionContext context, Memory> (32 - a)); } - buffer.Span[0] = v; - return new(1); + ; + return new(context.Return(v)); } - public ValueTask LShift(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask LShift(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); var disp = context.GetArgument(1); @@ -243,11 +235,10 @@ public ValueTask LShift(LuaFunctionExecutionContext context, Memory Replace(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Replace(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); @@ -279,11 +270,10 @@ public ValueTask Replace(LuaFunctionExecutionContext context, Memory RRotate(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RRotate(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); var disp = context.GetArgument(1); @@ -303,11 +293,10 @@ public ValueTask RRotate(LuaFunctionExecutionContext context, Memory> a) | (v << (32 - a)); } - buffer.Span[0] = v; - return new(1); + return new(context.Return(v)); } - public ValueTask RShift(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RShift(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); var disp = context.GetArgument(1); @@ -331,7 +320,6 @@ public ValueTask RShift(LuaFunctionExecutionContext context, Memory>= a; } - buffer.Span[0] = v; - return new(1); + return new(context.Return(v)); } } \ No newline at end of file diff --git a/src/Lua/Standard/CoroutineLibrary.cs b/src/Lua/Standard/CoroutineLibrary.cs index 93b96a7f..098578c7 100644 --- a/src/Lua/Standard/CoroutineLibrary.cs +++ b/src/Lua/Standard/CoroutineLibrary.cs @@ -1,3 +1,5 @@ +using Lua.Runtime; + namespace Lua.Standard; public sealed class CoroutineLibrary @@ -6,7 +8,8 @@ public sealed class CoroutineLibrary public CoroutineLibrary() { - Functions = [ + Functions = + [ new("create", Create), new("resume", Resume), new("running", Running), @@ -18,82 +21,84 @@ public CoroutineLibrary() public readonly LuaFunction[] Functions; - public ValueTask Create(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Create(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = new LuaCoroutine(arg0, true); - return new(1); + return new(context.Return(new LuaCoroutine(arg0, true))); } - public ValueTask Resume(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Resume(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var thread = context.GetArgument(0); - return thread.ResumeAsync(context, buffer, cancellationToken); + return thread.ResumeAsync(context, cancellationToken); } - public ValueTask Running(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Running(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - buffer.Span[0] = context.Thread; - buffer.Span[1] = context.Thread == context.State.MainThread; - return new(2); + return new(context.Return(context.Thread, context.Thread == context.State.MainThread)); } - public ValueTask Status(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Status(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var thread = context.GetArgument(0); - buffer.Span[0] = thread.GetStatus() switch + return new(context.Return(thread.GetStatus() switch { LuaThreadStatus.Normal => "normal", LuaThreadStatus.Suspended => "suspended", LuaThreadStatus.Running => "running", LuaThreadStatus.Dead => "dead", _ => "", - }; - return new(1); + })); } - public ValueTask Wrap(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + + public ValueTask Wrap(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var thread = new LuaCoroutine(arg0, false); - - buffer.Span[0] = new LuaFunction("wrap", async (context, buffer, cancellationToken) => - { - var stack = context.Thread.Stack; - var frameBase = stack.Count; - - stack.Push(thread); - stack.PushRange(context.Arguments); - context.Thread.PushCallStackFrame(new() - { - Base = frameBase, - VariableArgumentCount = 0, - Function = arg0, - }); - try + return new(context.Return(new CSharpClosure("wrap", [thread], + static async (context, cancellationToken) => { - var resultCount = await thread.ResumeAsync(context with + var thread = context.GetCsClosure()!.UpValues[0].Read(); + if (thread is not LuaCoroutine coroutine) { - ArgumentCount = context.ArgumentCount + 1, - FrameBase = frameBase, - }, buffer, cancellationToken); + return await thread.ResumeAsync(context, cancellationToken); + } - buffer.Span[1..].CopyTo(buffer.Span[0..]); - return resultCount - 1; - } - finally - { - context.Thread.PopCallStackFrame(); - } - - - }); + var stack = context.Thread.Stack; + var frameBase = stack.Count; - return new(1); + stack.Push(thread); + stack.PushRange(context.Arguments); + context.Thread.PushCallStackFrame(new() + { + Base = frameBase, + ReturnBase = context.ReturnFrameBase, + VariableArgumentCount = 0, + Function = coroutine.Function + }); + try + { + await thread.ResumeAsync(context with + { + ArgumentCount = context.ArgumentCount + 1, + FrameBase = frameBase, + ReturnFrameBase = context.ReturnFrameBase, + }, cancellationToken); + var result = context.GetReturnBuffer(context.Thread.Stack.Count - context.ReturnFrameBase); + result[1..].CopyTo(result); + context.Thread.Stack.Pop(); + return result.Length - 1; + } + finally + { + context.Thread.PopCallStackFrame(); + } + }))); } - public ValueTask Yield(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Yield(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - return context.Thread.YieldAsync(context, buffer, cancellationToken); + return context.Thread.YieldAsync(context, cancellationToken); } } \ No newline at end of file diff --git a/src/Lua/Standard/DebugLibrary.cs b/src/Lua/Standard/DebugLibrary.cs new file mode 100644 index 00000000..77cda6d4 --- /dev/null +++ b/src/Lua/Standard/DebugLibrary.cs @@ -0,0 +1,614 @@ +using System.Runtime.CompilerServices; +using Lua.Runtime; +using Lua.Internal; + +namespace Lua.Standard; + +public class DebugLibrary +{ + public static readonly DebugLibrary Instance = new(); + + public DebugLibrary() + { + Functions = + [ + new("getlocal", GetLocal), + new("setlocal", SetLocal), + new("getupvalue", GetUpValue), + new("setupvalue", SetUpValue), + new("getmetatable", GetMetatable), + new("setmetatable", SetMetatable), + new("getuservalue", GetUserValue), + new("setuservalue", SetUserValue), + new("traceback", Traceback), + new("getregistry", GetRegistry), + new("upvalueid", UpValueId), + new("upvaluejoin", UpValueJoin), + new("gethook", GetHook), + new("sethook", SetHook), + new("getinfo", GetInfo), + ]; + } + + public readonly LuaFunction[] Functions; + + + static LuaThread GetLuaThread(in LuaFunctionExecutionContext context, out int argOffset) + { + if (context.ArgumentCount < 1) + { + argOffset = 0; + return context.Thread; + } + + if (context.GetArgument(0).TryRead(out var thread)) + { + argOffset = 1; + return thread; + } + + argOffset = 0; + return context.Thread; + } + + + static ref LuaValue FindLocal(LuaThread thread, int level, int index, out string? name) + { + if (index == 0) + { + name = null; + return ref Unsafe.NullRef(); + } + + var callStack = thread.GetCallStackFrames(); + var frame = callStack[^(level + 1)]; + if (index < 0) + { + index = -index - 1; + var frameVariableArgumentCount = frame.VariableArgumentCount; + if (frameVariableArgumentCount > 0 && index < frameVariableArgumentCount) + { + name = "(*vararg)"; + return ref thread.Stack.Get(frame.Base - frameVariableArgumentCount + index); + } + + name = null; + return ref Unsafe.NullRef(); + } + + index -= 1; + + + var frameBase = frame.Base; + + + if (frame.Function is LuaClosure closure) + { + var locals = closure.Proto.Locals; + var nextFrame = callStack[^level]; + var currentPc = nextFrame.CallerInstructionIndex; + { + int nextFrameBase = (closure.Proto.Instructions[currentPc].OpCode is OpCode.Call or OpCode.TailCall) ? nextFrame.Base - 1 : nextFrame.Base; + if (nextFrameBase - 1 < frameBase + index) + { + name = null; + return ref Unsafe.NullRef(); + } + } + foreach (var local in locals) + { + if (local.Index == index && currentPc >= local.StartPc && currentPc < local.EndPc) + { + name = local.Name.ToString(); + return ref thread.Stack.Get(frameBase + local.Index); + } + + if (local.Index > index) + { + break; + } + } + } + else + { + int nextFrameBase = level != 0 ? callStack[^level].Base : thread.Stack.Count; + + if (nextFrameBase - 1 < frameBase + index) + { + name = null; + return ref Unsafe.NullRef(); + } + } + + name = "(*temporary)"; + return ref thread.Stack.Get(frameBase + index); + } + + public ValueTask GetLocal(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + static LuaValue GetParam(LuaFunction function, int index) + { + if (function is LuaClosure closure) + { + var paramCount = closure.Proto.ParameterCount; + if (0 <= index && index < paramCount) + { + return closure.Proto.Locals[index].Name.ToString(); + } + } + + return LuaValue.Nil; + } + + var thread = GetLuaThread(context, out var argOffset); + + var index = context.GetArgument(argOffset + 1); + if (context.GetArgument(argOffset).TryReadFunction(out var f)) + { + return new(context.Return(GetParam(f, index - 1))); + } + + var level = context.GetArgument(argOffset); + + + if (level < 0 || level >= thread.GetCallStackFrames().Length) + { + context.ThrowBadArgument(1, "level out of range"); + } + + ref var local = ref FindLocal(thread, level, index, out var name); + if (name is null) + { + return new(context.Return(LuaValue.Nil)); + } + + return new(context.Return(name, local)); + } + + public ValueTask SetLocal(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var thread = GetLuaThread(context, out var argOffset); + + var value = context.GetArgument(argOffset + 2); + var index = context.GetArgument(argOffset + 1); + var level = context.GetArgument(argOffset); + + + if (level < 0 || level >= thread.GetCallStackFrames().Length) + { + context.ThrowBadArgument(1, "level out of range"); + } + + ref var local = ref FindLocal(thread, level, index, out var name); + if (name is null) + { + return new(context.Return(LuaValue.Nil)); + } + + local = value; + return new(context.Return(name)); + } + + public ValueTask GetUpValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var func = context.GetArgument(0); + var index = context.GetArgument(1) - 1; + if (func is not LuaClosure closure) + { + if (func is CSharpClosure csClosure) + { + var upValues = csClosure.UpValues; + if (index < 0 || index >= upValues.Length) + { + return new(context.Return()); + } + + return new(context.Return("", upValues[index])); + } + + return new(context.Return()); + } + + { + var upValues = closure.UpValues; + var descriptions = closure.Proto.UpValues; + if (index < 0 || index >= descriptions.Length) + { + return new(context.Return()); + } + + var description = descriptions[index]; + return new(context.Return(description.Name.ToString(), upValues[index].GetValue())); + } + } + + public ValueTask SetUpValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var func = context.GetArgument(0); + var index = context.GetArgument(1) - 1; + var value = context.GetArgument(2); + if (func is not LuaClosure closure) + { + if (func is CSharpClosure csClosure) + { + var upValues = csClosure.UpValues; + if (index >= 0 && index < upValues.Length) + { + upValues[index] = value; + return new(context.Return("")); + } + + } + + return new(context.Return()); + } + + { + var upValues = closure.UpValues; + var descriptions = closure.Proto.UpValues; + if (index < 0 || index >= descriptions.Length) + { + return new(context.Return()); + } + + var description = descriptions[index]; + upValues[index].SetValue(value); + return new(context.Return(description.Name.ToString())); + } + } + + public ValueTask GetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var arg0 = context.GetArgument(0); + + if (context.State.TryGetMetatable(arg0, out var table)) + { + return new(context.Return(table)); + } + else + { + return new(context.Return(LuaValue.Nil)); + } + } + + public ValueTask SetMetatable(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var arg0 = context.GetArgument(0); + var arg1 = context.GetArgument(1); + + if (arg1.Type is not (LuaValueType.Nil or LuaValueType.Table)) + { + LuaRuntimeException.BadArgument(context.State.GetTraceback(), 2, "setmetatable", [LuaValueType.Nil, LuaValueType.Table]); + } + + context.State.SetMetatable(arg0, arg1.UnsafeRead()); + + return new(context.Return(arg0)); + } + + public ValueTask GetUserValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + if (!context.GetArgumentOrDefault(0).TryRead(out var iUserData)) + { + return new(context.Return(LuaValue.Nil)); + } + + var index = 1; // context.GetArgument(1); //for lua 5.4 + var userValues = iUserData.UserValues; + if (index > userValues.Length + //index < 1 || // for lua 5.4 + ) + { + return new(context.Return(LuaValue.Nil)); + } + + return new(context.Return(userValues[index - 1])); + } + + public ValueTask SetUserValue(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var iUserData = context.GetArgument(0); + var value = context.GetArgument(1); + var index = 1; // context.GetArgument(2);// for lua 5.4 + var userValues = iUserData.UserValues; + if (index > userValues.Length + //|| index < 1 // for lua 5.4 + ) + { + return new(context.Return(LuaValue.Nil)); + } + + userValues[index - 1] = value; + return new(context.Return(new LuaValue(iUserData))); + } + + public ValueTask Traceback(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var thread = GetLuaThread(context, out var argOffset); + + var message = context.GetArgumentOrDefault(argOffset); + var level = context.GetArgumentOrDefault(argOffset + 1, argOffset == 0 ? 1 : 0); + + if (message.Type is not (LuaValueType.Nil or LuaValueType.String or LuaValueType.Number)) + { + return new(context.Return(message)); + } + + if (level < 0) + { + return new(context.Return(LuaValue.Nil)); + } + + if (thread is LuaCoroutine coroutine) + { + if (coroutine.LuaTraceback is not null) + { + return new(context.Return(coroutine.LuaTraceback.ToString(level))); + } + } + + var callStack = thread.GetCallStackFrames(); + if (callStack.Length == 0) + { + return new(context.Return("stack traceback:")); + } + + var skipCount = Math.Min(Math.Max(level - 1, 0), callStack.Length - 1); + var frames = callStack[1..^skipCount]; + return new(context.Return(Runtime.Traceback.GetTracebackString(context.State, (LuaClosure)callStack[0].Function, frames, message, level == 1))); + } + + public ValueTask GetRegistry(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + return new(context.Return(context.State.Registry)); + } + + public ValueTask UpValueId(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var n1 = context.GetArgument(1); + var f1 = context.GetArgument(0); + + if (f1 is not LuaClosure closure) + { + return new(context.Return(LuaValue.Nil)); + } + + var upValues = closure.GetUpValuesSpan(); + if (n1 <= 0 || n1 > upValues.Length) + { + return new(context.Return(LuaValue.Nil)); + } + + return new(context.Return(new LuaValue(upValues[n1 - 1]))); + } + + public ValueTask UpValueJoin(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var n2 = context.GetArgument(3); + var f2 = context.GetArgument(2); + var n1 = context.GetArgument(1); + var f1 = context.GetArgument(0); + + if (f1 is not LuaClosure closure1 || f2 is not LuaClosure closure2) + { + return new(context.Return(LuaValue.Nil)); + } + + var upValues1 = closure1.GetUpValuesSpan(); + var upValues2 = closure2.GetUpValuesSpan(); + if (n1 <= 0 || n1 > upValues1.Length) + { + context.ThrowBadArgument(1, "invalid upvalue index"); + } + + if (n2 < 0 || n2 > upValues2.Length) + { + context.ThrowBadArgument(3, "invalid upvalue index"); + } + + upValues1[n1 - 1] = upValues2[n2 - 1]; + return new(0); + } + + public async ValueTask SetHook(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var thread = GetLuaThread(context, out var argOffset); + LuaFunction? hook = context.GetArgumentOrDefault(argOffset); + if (hook is null) + { + thread.HookCount = -1; + thread.BaseHookCount = 0; + thread.IsCountHookEnabled = false; + thread.Hook = null; + thread.IsLineHookEnabled = false; + thread.IsCallHookEnabled = false; + thread.IsReturnHookEnabled = false; + return 0; + } + + var mask = context.GetArgument(argOffset + 1); + if (context.HasArgument(argOffset + 2)) + { + var count = context.GetArgument(argOffset + 2); + thread.BaseHookCount = count; + thread.HookCount = count; + if (count > 0) + { + thread.IsCountHookEnabled = true; + } + } + else + { + thread.HookCount = 0; + thread.BaseHookCount = 0; + thread.IsCountHookEnabled = false; + } + + thread.IsLineHookEnabled = (mask.Contains('l')); + thread.IsCallHookEnabled = (mask.Contains('c')); + thread.IsReturnHookEnabled = (mask.Contains('r')); + + if (thread.IsLineHookEnabled) + { + thread.LastPc = thread.CallStack.Count > 0 ? thread.GetCurrentFrame().CallerInstructionIndex : -1; + } + + thread.Hook = hook; + if (thread.IsReturnHookEnabled && context.Thread == thread) + { + var stack = thread.Stack; + stack.Push("return"); + stack.Push(LuaValue.Nil); + var funcContext = new LuaFunctionExecutionContext + { + State = context.State, + Thread = context.Thread, + ArgumentCount = 2, + FrameBase = stack.Count - 2, + ReturnFrameBase = stack.Count - 2, + }; + var frame = new CallStackFrame + { + Base = funcContext.FrameBase, + ReturnBase = funcContext.ReturnFrameBase, + VariableArgumentCount = hook.GetVariableArgumentCount(2), + Function = hook, + }; + frame.Flags |= CallStackFrameFlags.InHook; + thread.PushCallStackFrame(frame); + try + { + thread.IsInHook = true; + await hook.Func(funcContext, cancellationToken); + } + finally + { + thread.IsInHook = false; + } + + thread.PopCallStackFrameWithStackPop(); + } + + return 0; + } + + + public ValueTask GetHook(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var thread = GetLuaThread(context, out var argOffset); + if (thread.Hook is null) + { + return new(context.Return(LuaValue.Nil, LuaValue.Nil, LuaValue.Nil)); + } + + return new(context.Return(thread.Hook, + ( + (thread.IsCallHookEnabled ? "c" : "") + + (thread.IsReturnHookEnabled ? "r" : "") + + (thread.IsLineHookEnabled ? "l" : "") + ) + , thread.BaseHookCount)); + } + + public ValueTask GetInfo(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + //return new(0); + var thread = GetLuaThread(context, out var argOffset); + string what = context.GetArgumentOrDefault(argOffset + 1, "flnStu"); + CallStackFrame? previousFrame = null; + CallStackFrame? currentFrame = null; + int pc = 0; + var arg1 = context.GetArgument(argOffset); + + if (arg1.TryReadFunction(out var functionToInspect)) + { + //what = ">" + what; + } + else if (arg1.TryReadNumber(out _)) + { + var level = context.GetArgument(argOffset) + 1; + + var callStack = thread.GetCallStackFrames(); + + if (level <= 0 || level > callStack.Length) + { + return new(context.Return(LuaValue.Nil)); + } + + + currentFrame = thread.GetCallStackFrames()[^(level)]; + previousFrame = level + 1 <= callStack.Length ? callStack[^(level + 1)] : null; + if (level != 1) + { + pc = thread.GetCallStackFrames()[^(level - 1)].CallerInstructionIndex; + } + + functionToInspect = currentFrame.Value.Function; + } + else + { + context.ThrowBadArgument(argOffset, "function or level expected"); + } + + using var debug = LuaDebug.Create(context.State, previousFrame, currentFrame, functionToInspect, pc, what, out var isValid); + if (!isValid) + { + context.ThrowBadArgument(argOffset + 1, "invalid option"); + } + + var table = new LuaTable(0, 1); + if (what.Contains('S')) + { + table["source"] = debug.Source ?? LuaValue.Nil; + table["short_src"] = debug.ShortSource.ToString(); + table["linedefined"] = debug.LineDefined; + table["lastlinedefined"] = debug.LastLineDefined; + table["what"] = debug.What ?? LuaValue.Nil; + ; + } + + if (what.Contains('l')) + { + table["currentline"] = debug.CurrentLine; + } + + if (what.Contains('u')) + { + table["nups"] = debug.UpValueCount; + table["nparams"] = debug.ParameterCount; + table["isvararg"] = debug.IsVarArg; + } + + if (what.Contains('n')) + { + table["name"] = debug.Name ?? LuaValue.Nil; + table["namewhat"] = debug.NameWhat ?? LuaValue.Nil; + } + + if (what.Contains('t')) + { + table["istailcall"] = debug.IsTailCall; + } + + if (what.Contains('f')) + { + table["func"] = functionToInspect; + } + + if (what.Contains('L')) + { + if (functionToInspect is LuaClosure closure) + { + var activeLines = new LuaTable(0, 8); + foreach (var pos in closure.Proto.SourcePositions) + { + activeLines[pos.Line] = true; + } + + table["activelines"] = activeLines; + } + } + + return new(context.Return(table)); + } +} \ No newline at end of file diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index ba1a5dc6..72ddeade 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -7,14 +7,14 @@ namespace Lua.Standard; public class FileHandle : ILuaUserData { - public static readonly LuaFunction IndexMetamethod = new("index", (context, buffer, ct) => + public static readonly LuaFunction IndexMetamethod = new("index", (context, ct) => { context.GetArgument(0); var key = context.GetArgument(1); if (key.TryRead(out var name)) { - buffer.Span[0] = name switch + return new(context.Return(name switch { "close" => CloseFunction!, "flush" => FlushFunction!, @@ -24,14 +24,13 @@ public class FileHandle : ILuaUserData "setvbuf" => SetVBufFunction!, "write" => WriteFunction!, _ => LuaValue.Nil, - }; + })); } else { - buffer.Span[0] = LuaValue.Nil; + return new(context.Return( LuaValue.Nil)); } - return new(1); }); Stream stream; @@ -131,70 +130,63 @@ public void Close() } } - static readonly LuaFunction CloseFunction = new("close", (context, buffer, cancellationToken) => + static readonly LuaFunction CloseFunction = new("close", (context, cancellationToken) => { var file = context.GetArgument(0); try { file.Close(); - buffer.Span[0] = true; - return new(1); + return new (context.Return(true)); } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; - return new(3); + return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } }); - static readonly LuaFunction FlushFunction = new("flush", (context, buffer, cancellationToken) => + static readonly LuaFunction FlushFunction = new("flush", (context, cancellationToken) => { var file = context.GetArgument(0); try { file.Flush(); - buffer.Span[0] = true; - return new(1); + return new(context.Return(true)); } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; - return new(3); + return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } }); - static readonly LuaFunction LinesFunction = new("lines", (context, buffer, cancellationToken) => + static readonly LuaFunction LinesFunction = new("lines", (context, cancellationToken) => { var file = context.GetArgument(0); var format = context.HasArgument(1) ? context.Arguments[1] : "*l"; - LuaValue[] formats = [format]; - buffer.Span[0] = new LuaFunction("iterator", (context, buffer, cancellationToken) => + return new (context.Return(new CSharpClosure("iterator", [new (file),format],static (context, cancellationToken) => { - var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, buffer, true); - return new(resultCount); - }); - - return new(1); + var upValues = context.GetCsClosure()!.UpValues.AsSpan(); + var file = upValues[0].Read(); + context.Return(); + var resultCount = IOHelper.Read(context.State, file, "lines", 0, upValues[1..], context.Thread.Stack, true); + return new (resultCount); + }))); }); - static readonly LuaFunction ReadFunction = new("read", (context, buffer, cancellationToken) => + static readonly LuaFunction ReadFunction = new("read", (context, cancellationToken) => { var file = context.GetArgument(0); - var resultCount = IOHelper.Read(context.State, file, "read", 1, context.Arguments[1..], buffer, false); + context.Return(); + var resultCount = IOHelper.Read(context.State, file, "read", 1, context.Arguments[1..], context.Thread.Stack, false); return new(resultCount); }); - static readonly LuaFunction SeekFunction = new("seek", (context, buffer, cancellationToken) => + static readonly LuaFunction SeekFunction = new("seek", (context, cancellationToken) => { var file = context.GetArgument(0); var whence = context.HasArgument(1) @@ -211,19 +203,15 @@ public void Close() try { - buffer.Span[0] = file.Seek(whence, (long)offset); - return new(1); + return new(context.Return(file.Seek(whence, (long)offset))); } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; - return new(3); + return new (context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } }); - static readonly LuaFunction SetVBufFunction = new("setvbuf", (context, buffer, cancellationToken) => + static readonly LuaFunction SetVBufFunction = new("setvbuf", (context, cancellationToken) => { var file = context.GetArgument(0); var mode = context.GetArgument(1); @@ -232,15 +220,15 @@ public void Close() : -1; file.SetVBuf(mode, size); - - buffer.Span[0] = true; - return new(1); + + return new(context.Return(true)); }); - static readonly LuaFunction WriteFunction = new("write", (context, buffer, cancellationToken) => + static readonly LuaFunction WriteFunction = new("write", (context, cancellationToken) => { var file = context.GetArgument(0); - var resultCount = IOHelper.Write(file, "write", context, buffer); + context.Return(); + var resultCount = IOHelper.Write(file, "write", context); return new(resultCount); }); } \ No newline at end of file diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 4a95d220..77a6629b 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -1,4 +1,5 @@ using Lua.Internal; +using Lua.Runtime; using Lua.Standard.Internal; namespace Lua.Standard; @@ -9,7 +10,8 @@ public sealed class IOLibrary public IOLibrary() { - Functions = [ + Functions = + [ new("close", Close), new("flush", Flush), new("input", Input), @@ -24,7 +26,7 @@ public IOLibrary() public readonly LuaFunction[] Functions; - public ValueTask Close(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Close(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.HasArgument(0) ? context.GetArgument(0) @@ -33,169 +35,166 @@ public ValueTask Close(LuaFunctionExecutionContext context, Memory Flush(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Flush(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Environment["io"].Read()["stdout"].Read(); try { file.Flush(); - buffer.Span[0] = true; - return new(1); + return new(context.Return(true)); } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; - return new(3); + return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } } - public ValueTask Input(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Input(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var io = context.State.Environment["io"].Read(); if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil) { - buffer.Span[0] = io["stdio"]; - return new(1); + return new(context.Return(io["stdio"])); } var arg = context.Arguments[0]; if (arg.TryRead(out var file)) { io["stdio"] = new(file); - buffer.Span[0] = new(file); - return new(1); + return new(context.Return(new LuaValue(file))); } else { var stream = File.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); var handle = new FileHandle(stream); io["stdio"] = new(handle); - buffer.Span[0] = new(handle); - return new(1); + return new(context.Return(new LuaValue(handle))); } } - public ValueTask Lines(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Lines(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.ArgumentCount == 0) { var file = context.State.Environment["io"].Read()["stdio"].Read(); - buffer.Span[0] = new LuaFunction("iterator", (context, buffer, ct) => + return new(context.Return(new CSharpClosure("iterator", [new(file)], static (context, ct) => { - var resultCount = IOHelper.Read(context.State, file, "lines", 0, [], buffer, true); - if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil) + var file = context.GetCsClosure()!.UpValues[0].Read(); + context.Return(); + var resultCount = IOHelper.Read(context.State, file, "lines", 0, [], context.Thread.Stack, true); + if (resultCount > 0 && context.Thread.Stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil) { file.Close(); } + return new(resultCount); - }); - return new(1); + }))); } else { var fileName = context.GetArgument(0); + var stack = context.Thread.Stack; + context.Return(); - using var methodBuffer = new PooledArray(32); - IOHelper.Open(context.State, fileName, "r", methodBuffer.AsMemory(), true); + IOHelper.Open(context.State, fileName, "r", stack, true); - var file = methodBuffer[0].Read(); - var formats = context.Arguments[1..].ToArray(); + var file = stack.Get(context.ReturnFrameBase).Read(); + var upValues = new LuaValue[context.Arguments.Length]; + upValues[0] = new(file); + context.Arguments[1..].CopyTo(upValues[1..]); - buffer.Span[0] = new LuaFunction("iterator", (context, buffer, ct) => + return new(context.Return(new CSharpClosure("iterator", upValues, static (context, ct) => { - var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, buffer, true); - if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil) + var upValues = context.GetCsClosure()!.UpValues; + var file = upValues[0].Read(); + var formats = upValues.AsSpan(1); + var stack = context.Thread.Stack; + context.Return(); + var resultCount = IOHelper.Read(context.State, file, "lines", 0, formats, stack, true); + if (resultCount > 0 && stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil) { file.Close(); } - return new(resultCount); - }); - return new(1); + return new(resultCount); + }))); } } - public ValueTask Open(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Open(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var fileName = context.GetArgument(0); var mode = context.HasArgument(1) ? context.GetArgument(1) : "r"; - - var resultCount = IOHelper.Open(context.State, fileName, mode, buffer, false); + context.Return(); + var resultCount = IOHelper.Open(context.State, fileName, mode, context.Thread.Stack, false); return new(resultCount); } - public ValueTask Output(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Output(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var io = context.State.Environment["io"].Read(); if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil) { - buffer.Span[0] = io["stdout"]; - return new(1); + return new(context.Return(io["stdout"])); } var arg = context.Arguments[0]; if (arg.TryRead(out var file)) { io["stdout"] = new(file); - buffer.Span[0] = new(file); - return new(1); + return new(context.Return(new LuaValue(file))); } else { var stream = File.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); var handle = new FileHandle(stream); io["stdout"] = new(handle); - buffer.Span[0] = new(handle); - return new(1); + return new(context.Return(new LuaValue(handle))); } } - public ValueTask Read(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Environment["io"].Read()["stdio"].Read(); - var resultCount = IOHelper.Read(context.State, file, "read", 0, context.Arguments, buffer, false); + context.Return(); + var stack = context.Thread.Stack; + + var resultCount = IOHelper.Read(context.State, file, "read", 0, context.Arguments, stack, false); return new(resultCount); } - public ValueTask Type(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Type(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); if (arg0.TryRead(out var file)) { - buffer.Span[0] = file.IsClosed ? "closed file" : "file"; + return new(context.Return(file.IsClosed ? "closed file" : "file")); } else { - buffer.Span[0] = LuaValue.Nil; + return new(context.Return(LuaValue.Nil)); } - - return new(1); } - public ValueTask Write(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Write(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Environment["io"].Read()["stdout"].Read(); - var resultCount = IOHelper.Write(file, "write", context, buffer); + context.Return(); + var resultCount = IOHelper.Write(file, "write", context); return new(resultCount); } } \ No newline at end of file diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index c1ee59db..4016c27f 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -1,11 +1,12 @@ using System.Text; using Lua.Internal; +using Lua.Runtime; namespace Lua.Standard.Internal; internal static class IOHelper { - public static int Open(LuaState state, string fileName, string mode, Memory buffer, bool throwError) + public static int Open(LuaState state, string fileName, string mode, LuaStack stack, bool throwError) { var fileMode = mode switch { @@ -25,7 +26,7 @@ public static int Open(LuaState state, string fileName, string mode, Memory, async) - public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context, Memory buffer) + public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context) { try { @@ -70,25 +71,28 @@ public static int Write(FileHandle file, string name, LuaFunctionExecutionContex } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; + var stack = context.Thread.Stack; + stack.Push(LuaValue.Nil); + stack.Push(ex.Message); + stack.Push(ex.HResult); return 3; } - buffer.Span[0] = new(file); + context.Thread.Stack.Push(new(file)); return 1; } static readonly LuaValue[] defaultReadFormat = ["*l"]; - public static int Read(LuaState state, FileHandle file, string name, int startArgumentIndex, ReadOnlySpan formats, Memory buffer, bool throwError) + public static int Read(LuaState state, FileHandle file, string name, int startArgumentIndex, ReadOnlySpan formats, LuaStack stack, bool throwError) { if (formats.Length == 0) { formats = defaultReadFormat; } + var top = stack.Count; + try { for (int i = 0; i < formats.Length; i++) @@ -104,16 +108,16 @@ public static int Read(LuaState state, FileHandle file, string name, int startAr throw new NotImplementedException(); case "*a": case "*all": - buffer.Span[i] = file.ReadToEnd(); + stack.Push(file.ReadToEnd()); break; case "*l": case "*line": - buffer.Span[i] = file.ReadLine() ?? LuaValue.Nil; + stack.Push(file.ReadLine() ?? LuaValue.Nil); break; case "L": case "*L": var text = file.ReadLine(); - buffer.Span[i] = text == null ? LuaValue.Nil : text + Environment.NewLine; + stack.Push(text == null ? LuaValue.Nil : text + Environment.NewLine); break; } } @@ -126,14 +130,15 @@ public static int Read(LuaState state, FileHandle file, string name, int startAr var b = file.ReadByte(); if (b == -1) { - buffer.Span[0] = LuaValue.Nil; + stack.PopUntil(top); + stack.Push(LuaValue.Nil); return 1; } byteBuffer[j] = (byte)b; } - buffer.Span[i] = Encoding.UTF8.GetString(byteBuffer.AsSpan()); + stack.Push(Encoding.UTF8.GetString(byteBuffer.AsSpan())); } else { @@ -150,9 +155,10 @@ public static int Read(LuaState state, FileHandle file, string name, int startAr throw; } - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; + stack.PopUntil(top); + stack.Push(LuaValue.Nil); + stack.Push(ex.Message); + stack.Push(ex.HResult); return 3; } } diff --git a/src/Lua/Standard/MathematicsLibrary.cs b/src/Lua/Standard/MathematicsLibrary.cs index d72e453a..3d303c01 100644 --- a/src/Lua/Standard/MathematicsLibrary.cs +++ b/src/Lua/Standard/MathematicsLibrary.cs @@ -7,7 +7,8 @@ public sealed class MathematicsLibrary public MathematicsLibrary() { - Functions = [ + Functions = + [ new("abs", Abs), new("acos", Acos), new("asin", Asin), @@ -43,134 +44,123 @@ public MathematicsLibrary() public sealed class RandomUserData(Random random) : ILuaUserData { LuaTable? SharedMetatable; - public LuaTable? Metatable { get => SharedMetatable; set => SharedMetatable = value; } + + public LuaTable? Metatable + { + get => SharedMetatable; + set => SharedMetatable = value; + } + public Random Random { get; } = random; } - public ValueTask Abs(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Abs(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Abs(arg0); - return new(1); + return new (context.Return(Math.Abs(arg0))); } - public ValueTask Acos(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Acos(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Acos(arg0); - return new(1); + return new (context.Return(Math.Acos(arg0))); } - public ValueTask Asin(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Asin(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Asin(arg0); - return new(1); + return new (context.Return(Math.Asin(arg0))); } - public ValueTask Atan2(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Atan2(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - buffer.Span[0] = Math.Atan2(arg0, arg1); - return new(1); + return new (context.Return(Math.Atan2(arg0, arg1))); } - public ValueTask Atan(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Atan(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Atan(arg0); - return new(1); + return new (context.Return(Math.Atan(arg0))); } - public ValueTask Ceil(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Ceil(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Ceiling(arg0); - return new(1); + return new (context.Return(Math.Ceiling(arg0))); } - public ValueTask Cos(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Cos(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Cos(arg0); - return new(1); + return new (context.Return(Math.Cos(arg0))); } - public ValueTask Cosh(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Cosh(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Cosh(arg0); - return new(1); + return new (context.Return(Math.Cosh(arg0))); } - public ValueTask Deg(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Deg(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = arg0 * (180.0 / Math.PI); - return new(1); + return new (context.Return(arg0 * (180.0 / Math.PI))); } - public ValueTask Exp(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Exp(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Exp(arg0); - return new(1); + return new (context.Return(Math.Exp(arg0))); } - public ValueTask Floor(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Floor(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Floor(arg0); - return new(1); + return new (context.Return(Math.Floor(arg0))); } - public ValueTask Fmod(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Fmod(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - buffer.Span[0] = arg0 % arg1; - return new(1); + return new (context.Return(arg0 % arg1)); } - public ValueTask Frexp(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Frexp(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var (m, e) = MathEx.Frexp(arg0); - buffer.Span[0] = m; - buffer.Span[1] = e; - return new(2); + return new (context.Return(m,e)); } - public ValueTask Ldexp(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Ldexp(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - buffer.Span[0] = arg0 * Math.Pow(2, arg1); - return new(1); + return new (context.Return(arg0 * Math.Pow(2, arg1))); } - public ValueTask Log(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Log(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); if (context.ArgumentCount == 1) { - buffer.Span[0] = Math.Log(arg0); + return new (context.Return(Math.Log(arg0))); } else { var arg1 = context.GetArgument(1); - buffer.Span[0] = Math.Log(arg0, arg1); + return new (context.Return(Math.Log(arg0, arg1))); } - - return new(1); } - public ValueTask Max(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Max(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); for (int i = 1; i < context.ArgumentCount; i++) @@ -178,12 +168,10 @@ public ValueTask Max(LuaFunctionExecutionContext context, Memory x = Math.Max(x, context.GetArgument(i)); } - buffer.Span[0] = x; - - return new(1); + return new (context.Return(x)); } - public ValueTask Min(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Min(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var x = context.GetArgument(0); for (int i = 1; i < context.ArgumentCount; i++) @@ -191,98 +179,86 @@ public ValueTask Min(LuaFunctionExecutionContext context, Memory x = Math.Min(x, context.GetArgument(i)); } - buffer.Span[0] = x; - - return new(1); + return new (context.Return(x)); } - public ValueTask Modf(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Modf(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var (i, f) = MathEx.Modf(arg0); - buffer.Span[0] = i; - buffer.Span[1] = f; - return new(2); + return new (context.Return(i,f)); } - public ValueTask Pow(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Pow(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - buffer.Span[0] = Math.Pow(arg0, arg1); - return new(1); + return new (context.Return(Math.Pow(arg0, arg1))); } - public ValueTask Rad(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Rad(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = arg0 * (Math.PI / 180.0); - return new(1); + return new (context.Return(arg0 * (Math.PI / 180.0))); } - public ValueTask Random(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Random(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var rand = context.State.Environment[RandomInstanceKey].Read().Random; if (context.ArgumentCount == 0) { - buffer.Span[0] = rand.NextDouble(); + return new (context.Return(rand.NextDouble())); } else if (context.ArgumentCount == 1) { var arg0 = context.GetArgument(0); - buffer.Span[0] = rand.NextDouble() * (arg0 - 1) + 1; + return new (context.Return(rand.NextDouble() * (arg0 - 1) + 1)); } else { var arg0 = context.GetArgument(0); var arg1 = context.GetArgument(1); - buffer.Span[0] = rand.NextDouble() * (arg1 - arg0) + arg0; + return new (context.Return(rand.NextDouble() * (arg1 - arg0) + arg0)); } - return new(1); } - public ValueTask RandomSeed(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask RandomSeed(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); context.State.Environment[RandomInstanceKey] = new(new RandomUserData(new Random((int)BitConverter.DoubleToInt64Bits(arg0)))); - return new(0); + return new (context.Return()); } - public ValueTask Sin(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Sin(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Sin(arg0); - return new(1); + return new (context.Return(Math.Sin(arg0))); } - public ValueTask Sinh(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Sinh(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Sinh(arg0); - return new(1); + return new (context.Return(Math.Sinh(arg0))); } - public ValueTask Sqrt(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Sqrt(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Sqrt(arg0); - return new(1); + return new (context.Return(Math.Sqrt(arg0))); } - public ValueTask Tan(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Tan(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Tan(arg0); - return new(1); + return new (context.Return(Math.Tan(arg0))); } - public ValueTask Tanh(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Tanh(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); - buffer.Span[0] = Math.Tanh(arg0); - return new(1); + return new (context.Return(Math.Tanh(arg0))); } } \ No newline at end of file diff --git a/src/Lua/Standard/ModuleLibrary.cs b/src/Lua/Standard/ModuleLibrary.cs index 562a7114..2d186a68 100644 --- a/src/Lua/Standard/ModuleLibrary.cs +++ b/src/Lua/Standard/ModuleLibrary.cs @@ -15,7 +15,7 @@ public ModuleLibrary() public readonly LuaFunction RequireFunction; - public async ValueTask Require(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask Require(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var loaded = context.State.LoadedModules; @@ -24,15 +24,12 @@ public async ValueTask Require(LuaFunctionExecutionContext context, Memory< { var module = await context.State.ModuleLoader.LoadAsync(arg0, cancellationToken); var chunk = LuaCompiler.Default.Compile(module.ReadText(), module.Name); + await new LuaClosure(context.State, chunk).InvokeAsync(context, cancellationToken); - using var methodBuffer = new PooledArray(1); - await new Closure(context.State, chunk).InvokeAsync(context, methodBuffer.AsMemory(), cancellationToken); - - loadedTable = methodBuffer[0]; + loadedTable = context.Thread.Stack.Get(context.ReturnFrameBase); loaded[arg0] = loadedTable; } - buffer.Span[0] = loadedTable; - return 1; + return context.Return(loadedTable); } } \ No newline at end of file diff --git a/src/Lua/Standard/OpenLibsExtensions.cs b/src/Lua/Standard/OpenLibsExtensions.cs index ed2fb763..e9ec6a25 100644 --- a/src/Lua/Standard/OpenLibsExtensions.cs +++ b/src/Lua/Standard/OpenLibsExtensions.cs @@ -109,13 +109,11 @@ public static void OpenStringLibrary(this LuaState state) state.SetMetatable(key, metatable); } - metatable[Metamethods.Index] = new LuaFunction("index", (context, buffer, cancellationToken) => + metatable[Metamethods.Index] = new LuaFunction("index", (context, cancellationToken) => { context.GetArgument(0); var key = context.GetArgument(1); - - buffer.Span[0] = @string[key]; - return new(1); + return new(context.Return(@string[key])); }); } @@ -130,6 +128,18 @@ public static void OpenTableLibrary(this LuaState state) state.Environment["table"] = table; state.LoadedModules["table"] = table; } + + public static void OpenDebugLibrary(this LuaState state) + { + var debug = new LuaTable(0, DebugLibrary.Instance.Functions.Length); + foreach (var func in DebugLibrary.Instance.Functions) + { + debug[func.Name] = func; + } + + state.Environment["debug"] = debug; + state.LoadedModules["debug"] = debug; + } public static void OpenStandardLibraries(this LuaState state) { @@ -142,5 +152,6 @@ public static void OpenStandardLibraries(this LuaState state) state.OpenOperatingSystemLibrary(); state.OpenStringLibrary(); state.OpenTableLibrary(); + state.OpenDebugLibrary(); } } \ No newline at end of file diff --git a/src/Lua/Standard/OperatingSystemLibrary.cs b/src/Lua/Standard/OperatingSystemLibrary.cs index 11dcb8dc..7d55148e 100644 --- a/src/Lua/Standard/OperatingSystemLibrary.cs +++ b/src/Lua/Standard/OperatingSystemLibrary.cs @@ -26,13 +26,12 @@ public OperatingSystemLibrary() public readonly LuaFunction[] Functions; - public ValueTask Clock(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Clock(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - buffer.Span[0] = DateTimeHelper.GetUnixTime(DateTime.UtcNow, Process.GetCurrentProcess().StartTime); - return new(1); + return new(context.Return(DateTimeHelper.GetUnixTime(DateTime.UtcNow, Process.GetCurrentProcess().StartTime))); } - public ValueTask Date(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Date(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var format = context.HasArgument(0) ? context.GetArgument(0).AsSpan() @@ -74,25 +73,23 @@ public ValueTask Date(LuaFunctionExecutionContext context, Memory table["yday"] = now.DayOfYear; table["isdst"] = isDst; - buffer.Span[0] = table; + return new (context.Return(table)); } else { - buffer.Span[0] = DateTimeHelper.StrFTime(context.State, format, now); + return new (context.Return(DateTimeHelper.StrFTime(context.State, format, now))); } - return new(1); } - public ValueTask DiffTime(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask DiffTime(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var t2 = context.GetArgument(0); var t1 = context.GetArgument(1); - buffer.Span[0] = t2 - t1; - return new(1); + return new (context.Return(t2 - t1)); } - public ValueTask Execute(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Execute(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { // os.execute(command) is not supported @@ -102,12 +99,11 @@ public ValueTask Execute(LuaFunctionExecutionContext context, Memory Exit(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Exit(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { // Ignore 'close' parameter @@ -133,80 +129,67 @@ public ValueTask Exit(LuaFunctionExecutionContext context, Memory Environment.Exit(0); } - return new(0); + return new(context.Return()); } - public ValueTask GetEnv(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask GetEnv(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var variable = context.GetArgument(0); - buffer.Span[0] = Environment.GetEnvironmentVariable(variable) ?? LuaValue.Nil; - return new(1); + return new (context.Return(Environment.GetEnvironmentVariable(variable) ?? LuaValue.Nil)); } - public ValueTask Remove(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Remove(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var fileName = context.GetArgument(0); try { File.Delete(fileName); - buffer.Span[0] = true; - return new(1); + return new (context.Return(true)); } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; - return new(3); + return new (context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } } - public ValueTask Rename(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Rename(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var oldName = context.GetArgument(0); var newName = context.GetArgument(1); try { File.Move(oldName, newName); - buffer.Span[0] = true; - return new(1); + return new (context.Return(true)); } catch (IOException ex) { - buffer.Span[0] = LuaValue.Nil; - buffer.Span[1] = ex.Message; - buffer.Span[2] = ex.HResult; - return new(3); + return new (context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } } - public ValueTask SetLocale(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask SetLocale(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { // os.setlocale is not supported (always return nil) - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new (context.Return(LuaValue.Nil)); } - public ValueTask Time(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Time(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.HasArgument(0)) { var table = context.GetArgument(0); var date = DateTimeHelper.ParseTimeTable(context.State, table); - buffer.Span[0] = DateTimeHelper.GetUnixTime(date); - return new(1); + return new (context.Return(DateTimeHelper.GetUnixTime(date))); } else { - buffer.Span[0] = DateTimeHelper.GetUnixTime(DateTime.UtcNow); - return new(1); + return new (context.Return(DateTimeHelper.GetUnixTime(DateTime.UtcNow))); } } - public ValueTask TmpName(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask TmpName(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - buffer.Span[0] = Path.GetTempFileName(); - return new(1); + return new (context.Return(Path.GetTempFileName())); } } \ No newline at end of file diff --git a/src/Lua/Standard/StringLibrary.cs b/src/Lua/Standard/StringLibrary.cs index 9eeb1bb1..124f674d 100644 --- a/src/Lua/Standard/StringLibrary.cs +++ b/src/Lua/Standard/StringLibrary.cs @@ -1,5 +1,7 @@ using System.Text; +using System.Text.RegularExpressions; using Lua.Internal; +using Lua.Runtime; namespace Lua.Standard; @@ -9,7 +11,8 @@ public sealed class StringLibrary public StringLibrary() { - Functions = [ + Functions = + [ new("byte", Byte), new("char", Char), new("dump", Dump), @@ -28,7 +31,7 @@ public StringLibrary() public readonly LuaFunction[] Functions; - public ValueTask Byte(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Byte(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); var i = context.HasArgument(1) @@ -42,20 +45,20 @@ public ValueTask Byte(LuaFunctionExecutionContext context, Memory LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "byte", 3, j); var span = StringHelper.Slice(s, (int)i, (int)j); + var buffer = context.GetReturnBuffer(span.Length); for (int k = 0; k < span.Length; k++) { - buffer.Span[k] = span[k]; + buffer[k] = span[k]; } return new(span.Length); } - public ValueTask Char(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Char(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { if (context.ArgumentCount == 0) { - buffer.Span[0] = ""; - return new(1); + return new(context.Return("")); } var builder = new ValueStringBuilder(context.ArgumentCount); @@ -66,17 +69,16 @@ public ValueTask Char(LuaFunctionExecutionContext context, Memory builder.Append((char)arg); } - buffer.Span[0] = builder.ToString(); - return new(1); + return new(context.Return(builder.ToString())); } - public ValueTask Dump(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Dump(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { // stirng.dump is not supported (throw exception) throw new NotSupportedException("stirng.dump is not supported"); } - public ValueTask Find(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Find(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); var pattern = context.GetArgument(1); @@ -98,16 +100,13 @@ public ValueTask Find(LuaFunctionExecutionContext context, Memory // out of range if (init != 1 && (init < 1 || init > s.Length)) { - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new(context.Return(LuaValue.Nil)); } // empty pattern if (pattern.Length == 0) { - buffer.Span[0] = 1; - buffer.Span[1] = 1; - return new(2); + return new(context.Return(1, 1)); } var source = s.AsSpan()[(int)(init - 1)..]; @@ -117,14 +116,11 @@ public ValueTask Find(LuaFunctionExecutionContext context, Memory var start = source.IndexOf(pattern); if (start == -1) { - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new(context.Return(LuaValue.Nil)); } // 1-based - buffer.Span[0] = start + 1; - buffer.Span[1] = start + pattern.Length; - return new(2); + return new(context.Return(start + 1, start + pattern.Length)); } else { @@ -134,22 +130,19 @@ public ValueTask Find(LuaFunctionExecutionContext context, Memory if (match.Success) { // 1-based - buffer.Span[0] = init + match.Index; - buffer.Span[1] = init + match.Index + match.Length - 1; - return new(2); + return new(context.Return(init + match.Index, init + match.Index + match.Length - 1)); } else { - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new(context.Return(LuaValue.Nil)); } } } - public async ValueTask Format(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask Format(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var format = context.GetArgument(0); - + var stack = context.Thread.Stack; // TODO: pooling StringBuilder var builder = new StringBuilder(format.Length * 2); var parameterIndex = 1; @@ -238,6 +231,7 @@ public async ValueTask Format(LuaFunctionExecutionContext context, Memory Format(LuaFunctionExecutionContext context, Memory(1)) { - await parameter.CallToStringAsync(context, strBuffer.AsMemory(), cancellationToken); - formattedValue = strBuffer[0].Read(); + var top = stack.Count; + stack.Push(default); + await parameter.CallToStringAsync(context with { ReturnFrameBase = top }, cancellationToken); + formattedValue = stack.Pop().Read(); } if (specifier is 's' && precision > 0 && precision <= formattedValue.Length) { formattedValue = formattedValue[..precision]; } + break; case 'q': switch (parameter.Type) @@ -311,13 +308,16 @@ public async ValueTask Format(LuaFunctionExecutionContext context, Memory().ToString(); break; default: - using (var strBuffer = new PooledArray(1)) + { - await parameter.CallToStringAsync(context, strBuffer.AsMemory(), cancellationToken); - formattedValue = strBuffer[0].Read(); + var top = stack.Count; + stack.Push(default); + await parameter.CallToStringAsync(context with { ReturnFrameBase = top }, cancellationToken); + formattedValue = stack.Pop().Read(); } break; } + break; case 'i': case 'd': @@ -382,6 +382,7 @@ public async ValueTask Format(LuaFunctionExecutionContext context, Memory Format(LuaFunctionExecutionContext context, Memory GMatch(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask GMatch(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); var pattern = context.GetArgument(1); var regex = StringHelper.ToRegex(pattern); var matches = regex.Matches(s); - var i = 0; - buffer.Span[0] = new LuaFunction("iterator", (context, buffer, cancellationToken) => + return new (context.Return(new CSharpClosure("iterator", [new LuaValue(matches), 0], static (context, cancellationToken) => { + var upValues = context.GetCsClosure()!.UpValues; + var matches = upValues[0].Read(); + var i = upValues[1].Read(); if (matches.Count > i) { var match = matches[i]; var groups = match.Groups; i++; - + upValues[1] = i; if (groups.Count == 1) { - buffer.Span[0] = match.Value; + return new (context.Return(match.Value)); } else { + var buffer = context.GetReturnBuffer(groups.Count); for (int j = 0; j < groups.Count; j++) { - buffer.Span[j] = groups[j + 1].Value; + buffer[j] = groups[j + 1].Value; } + return new(buffer.Length); } - return new(groups.Count); } else { - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new (context.Return(LuaValue.Nil)); } - }); + }))); - return new(1); } - public async ValueTask GSub(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask GSub(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); var pattern = context.GetArgument(1); @@ -516,14 +516,14 @@ public async ValueTask GSub(LuaFunctionExecutionContext context, Memory(1024); + await func.InvokeAsync(context with { ArgumentCount = match.Groups.Count, FrameBase = context.Thread.Stack.Count - context.ArgumentCount, - }, methodBuffer.AsMemory(), cancellationToken); + }, cancellationToken); - result = methodBuffer[0]; + result = context.Thread.Stack.Get(context.ReturnFrameBase); } else { @@ -553,25 +553,22 @@ await func.InvokeAsync(context with builder.Append(s.AsSpan()[lastIndex..s.Length]); - buffer.Span[0] = builder.ToString(); - return 1; + return context.Return(builder.ToString()); } - public ValueTask Len(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Len(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); - buffer.Span[0] = s.Length; - return new(1); + return new (context.Return(s.Length)); } - public ValueTask Lower(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Lower(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); - buffer.Span[0] = s.ToLower(); - return new(1); + return new (context.Return(s.ToLower())); } - public ValueTask Rep(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Rep(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); var n_arg = context.GetArgument(1); @@ -593,22 +590,20 @@ public ValueTask Rep(LuaFunctionExecutionContext context, Memory } } - buffer.Span[0] = builder.ToString(); - return new(1); + return new (context.Return(builder.ToString())); } - public ValueTask Reverse(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Reverse(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); using var strBuffer = new PooledArray(s.Length); var span = strBuffer.AsSpan()[..s.Length]; s.AsSpan().CopyTo(span); span.Reverse(); - buffer.Span[0] = span.ToString(); - return new(1); + return new (context.Return(span.ToString())); } - public ValueTask Sub(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Sub(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); var i = context.GetArgument(1); @@ -619,14 +614,12 @@ public ValueTask Sub(LuaFunctionExecutionContext context, Memory LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "sub", 2, i); LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "sub", 3, j); - buffer.Span[0] = StringHelper.Slice(s, (int)i, (int)j).ToString(); - return new(1); + return new (context.Return(StringHelper.Slice(s, (int)i, (int)j).ToString())); } - public ValueTask Upper(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Upper(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var s = context.GetArgument(0); - buffer.Span[0] = s.ToUpper(); - return new(1); + return new (context.Return(s.ToUpper())); } } \ No newline at end of file diff --git a/src/Lua/Standard/TableLibrary.cs b/src/Lua/Standard/TableLibrary.cs index c9e1773c..f4385a19 100644 --- a/src/Lua/Standard/TableLibrary.cs +++ b/src/Lua/Standard/TableLibrary.cs @@ -41,10 +41,14 @@ public TableLibrary() ], ParameterCount = 2, UpValues = [], + Locals = [new LocalValueInfo(){Name = "a".AsMemory(),StartPc = 0,Index = 0,EndPc = 4}, new LocalValueInfo(){Name = "b".AsMemory(),StartPc = 0,Index = 1,EndPc = 4}], MaxStackPosition = 2, + HasVariableArguments = false, + LineDefined = 0, + LastLineDefined = 0, }; - public ValueTask Concat(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Concat(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.HasArgument(1) @@ -79,11 +83,10 @@ public ValueTask Concat(LuaFunctionExecutionContext context, Memory Insert(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Insert(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var table = context.GetArgument(0); @@ -105,10 +108,10 @@ public ValueTask Insert(LuaFunctionExecutionContext context, Memory Pack(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Pack(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var table = new LuaTable(context.ArgumentCount, 1); @@ -119,11 +122,10 @@ public ValueTask Pack(LuaFunctionExecutionContext context, Memory } table["n"] = span.Length; - buffer.Span[0] = table; - return new(1); + return new (context.Return(table)); } - public ValueTask Remove(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Remove(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var table = context.GetArgument(0); var n_arg = context.HasArgument(1) @@ -138,43 +140,41 @@ public ValueTask Remove(LuaFunctionExecutionContext context, Memory table.ArrayLength) { - buffer.Span[0] = LuaValue.Nil; - return new(1); + return new (context.Return(LuaValue.Nil)); } - buffer.Span[0] = table.RemoveAt(n); - return new(1); + return new (context.Return(table.RemoveAt(n))); } - public async ValueTask Sort(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public async ValueTask Sort(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.HasArgument(1) ? context.GetArgument(1) - : new Closure(context.State, defaultComparer); + : new LuaClosure(context.State, defaultComparer); context.Thread.PushCallStackFrame(new () { Base = context.FrameBase, + ReturnBase = context.ReturnFrameBase, VariableArgumentCount = 0, Function = arg1 }); try { await QuickSortAsync(context, arg0.GetArrayMemory(), 0, arg0.ArrayLength - 1, arg1, cancellationToken); - return 0; + return context.Return(); } finally { - context.Thread.PopCallStackFrameUnsafe(context.FrameBase); + context.Thread.PopCallStackFrameWithStackPop(); } } @@ -190,26 +190,30 @@ async ValueTask QuickSortAsync(LuaFunctionExecutionContext context, Memory PartitionAsync(LuaFunctionExecutionContext context, Memory memory, int low, int high, LuaFunction comparer, CancellationToken cancellationToken) { - using var methodBuffer = new PooledArray(1); + var pivot = memory.Span[high]; int i = low - 1; for (int j = low; j < high; j++) { - context.State.Push(memory.Span[j]); - context.State.Push(pivot); + var stack = context.Thread.Stack; + var top = stack.Count; + stack.Push(memory.Span[j]); + stack.Push(pivot); await comparer.InvokeAsync(context with { ArgumentCount = 2, - FrameBase = context.Thread.Stack.Count - context.ArgumentCount, - }, methodBuffer.AsMemory(), cancellationToken); + FrameBase = stack.Count - context.ArgumentCount, + ReturnFrameBase = top + }, cancellationToken); - if (methodBuffer[0].ToBoolean()) + if (context.Thread.Stack.Get(top).ToBoolean()) { i++; Swap(memory.Span, i, j); } + context.Thread.Stack.PopUntil(top); } Swap(memory.Span, i + 1, high); @@ -222,7 +226,7 @@ void Swap(Span span, int i, int j) (span[i], span[j]) = (span[j], span[i]); } - public ValueTask Unpack(LuaFunctionExecutionContext context, Memory buffer, CancellationToken cancellationToken) + public ValueTask Unpack(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var arg1 = context.HasArgument(1) @@ -233,9 +237,11 @@ public ValueTask Unpack(LuaFunctionExecutionContext context, Memory()[1].Read(), Does.Contain("stack traceback:")); + } } \ No newline at end of file diff --git a/tests/Lua.Tests/ParserTests.cs b/tests/Lua.Tests/ParserTests.cs index 1dcda7eb..d132eb05 100644 --- a/tests/Lua.Tests/ParserTests.cs +++ b/tests/Lua.Tests/ParserTests.cs @@ -17,8 +17,8 @@ elseif true then end"; var actual = LuaSyntaxTree.Parse(source).Nodes[0]; var expected = new IfStatementNode( - new() { ConditionNode = new BooleanLiteralNode(true, new(1, 3)), ThenNodes = [] }, - [new() { ConditionNode = new BooleanLiteralNode(true, new(2, 7)), ThenNodes = [] }], + new() { Position = new(1,8),ConditionNode = new BooleanLiteralNode(true, new(1, 3)), ThenNodes = [] }, + [new() {Position = new(2,13), ConditionNode = new BooleanLiteralNode(true, new(2, 7)), ThenNodes = [] }], [], new(1, 0)); diff --git a/tests/Lua.Tests/tests-lua/coroutine.lua b/tests/Lua.Tests/tests-lua/coroutine.lua index 34d19774..d177c97a 100644 --- a/tests/Lua.Tests/tests-lua/coroutine.lua +++ b/tests/Lua.Tests/tests-lua/coroutine.lua @@ -54,15 +54,15 @@ assert(not s and string.find(a, "dead") and coroutine.status(f) == "dead") -- yields in tail calls --- local function foo (i) return coroutine.yield(i) end --- f = coroutine.wrap(function () --- for i=1,10 do --- assert(foo(i) == _G.x) --- end --- return 'a' --- end) --- for i=1,10 do _G.x = i; assert(f(i) == i) end --- _G.x = 'xuxu'; assert(f('xuxu') == 'a') +local function foo (i) return coroutine.yield(i) end +f = coroutine.wrap(function () + for i=1,10 do + assert(foo(i) == _G.x) + end + return 'a' +end) +for i=1,10 do _G.x = i; assert(f(i) == i) end +_G.x = 'xuxu'; assert(f('xuxu') == 'a') -- recursive function pf (n, i) @@ -78,33 +78,33 @@ for i=1,10 do end -- sieve --- function gen (n) --- return coroutine.wrap(function () --- for i=2,n do coroutine.yield(i) end --- end) --- end +function gen (n) + return coroutine.wrap(function () + for i=2,n do coroutine.yield(i) end + end) +end --- function filter (p, g) --- return coroutine.wrap(function () --- while 1 do --- local n = g() --- if n == nil then return end --- if math.fmod(n, p) ~= 0 then coroutine.yield(n) end --- end --- end) --- end +function filter (p, g) + return coroutine.wrap(function () + while 1 do + local n = g() + if n == nil then return end + if math.fmod(n, p) ~= 0 then coroutine.yield(n) end + end + end) +end --- local x = gen(100) --- local a = {} --- while 1 do --- local n = x() --- if n == nil then break end --- table.insert(a, n) --- x = filter(n, x) --- end +local x = gen(100) +local a = {} +while 1 do + local n = x() + if n == nil then break end + table.insert(a, n) + x = filter(n, x) +end --- assert(#a == 25 and a[#a] == 97) +assert(#a == 25 and a[#a] == 97) -- yielding across C boundaries diff --git a/tests/Lua.Tests/tests-lua/db.lua b/tests/Lua.Tests/tests-lua/db.lua index d3d8c25b..b381452c 100644 --- a/tests/Lua.Tests/tests-lua/db.lua +++ b/tests/Lua.Tests/tests-lua/db.lua @@ -28,7 +28,7 @@ do assert(debug.getinfo(1000) == nil) -- out of range level assert(debug.getinfo(-1) == nil) -- out of range level local a = debug.getinfo(print) - assert(a.what == "C" and a.short_src == "[C]") + assert(a.what == "C#" and a.short_src == "[C#]") -- changed C to C# a = debug.getinfo(print, "L") assert(a.activelines == nil) local b = debug.getinfo(test, "SfL") @@ -82,7 +82,10 @@ repeat assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name) function f (x, name) -- local! - name = name or 'f' + if not name then -- todo fix compiler bug Lua-CSharp + name = 'f' + end + local a = debug.getinfo(1) assert(a.name == name and a.namewhat == 'local') return x @@ -238,7 +241,7 @@ function f(a,b) local _, y = debug.getlocal(1, 2) assert(x == a and y == b) assert(debug.setlocal(2, 3, "pera") == "AA".."AA") - assert(debug.setlocal(2, 4, "maçã") == "B") + assert(debug.setlocal(2, 4, "ma��") == "B") x = debug.getinfo(2) assert(x.func == g and x.what == "Lua" and x.name == 'g' and x.nups == 1 and string.find(x.source, "^@.*db%.lua$")) @@ -253,10 +256,10 @@ function foo() end; foo() -- set L -- check line counting inside strings and empty lines -_ = 'alo\ -alo' .. [[ - -]] +--_ = 'alo\ -- todo fix compiler bug Lua-CSharp +--alo' .. [[ +-- +--]] --[[ ]] assert(debug.getinfo(1, "l").currentline == L+11) -- check count of lines @@ -266,9 +269,9 @@ function g(...) local arg = {...} do local a,b,c; a=math.sin(40); end local feijao - local AAAA,B = "xuxu", "mamão" + local AAAA,B = "xuxu", "mam�o" f(AAAA,B) - assert(AAAA == "pera" and B == "maçã") + assert(AAAA == "pera" and B == "ma��") do local B = 13 local x,y = debug.getlocal(1,5) @@ -463,8 +466,8 @@ co = load[[ local a = 0 -- 'A' should be visible to debugger only after its complete definition debug.sethook(function (e, l) - if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == "(*temporary)") - elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A") + if l == 3 then a = a + 1; assert(debug.getlocal(2, 1) == nil)-- assert(debug.getlocal(2, 1) == "(*temporary)") --changed behavior Lua-CSharp + elseif l == 4 then a = a + 1; assert(debug.getlocal(2, 1) == "A") end end, "l") co() -- run local function definition @@ -620,12 +623,11 @@ setmetatable(a, { local b = setmetatable({}, getmetatable(a)) -assert(a[3] == "__index" and a^3 == "__pow" and a..a == "__concat") -assert(a/3 == "__div" and 3%a == "__mod") -assert (a==b and a.op == "__eq") -assert (a>=b and a.op == "__le") -assert (a>b and a.op == "__lt") +assert(a[3] == "index" and a^3 == "pow" and a..a == "concat") +assert(a/3 == "div" and 3%a == "mod") +assert (a==b and a.op == "eq") +assert (a>=b and a.op == "le") +assert (a>b and a.op == "lt") print"OK" - diff --git a/tests/Lua.Tests/tests-lua/db_mini.lua b/tests/Lua.Tests/tests-lua/db_mini.lua new file mode 100644 index 00000000..0040b817 --- /dev/null +++ b/tests/Lua.Tests/tests-lua/db_mini.lua @@ -0,0 +1,227 @@ +-- testing debug library + + + +local a = 1 + +local function multi_assert(expected, ...) + local arg = { ... } + for i = 1, #arg do + assert(arg[i] == expected[i]) + end +end +local function test_locals(x, ...) + local b = "local b" + assert(debug.getlocal(test_locals, 1) == "x") + multi_assert({ "x", 1 }, debug.getlocal(1, 1)) + multi_assert({ "b", "local b" }, debug.getlocal(1, 2)) + multi_assert({ "(vararg)", 2 }, debug.getlocal(1, -1)) + multi_assert({ "(vararg)", 3 }, debug.getlocal(1, -2)) + multi_assert({ "a", 1 }, debug.getlocal(2, 1)) + assert(debug.setlocal(2, 1, "new a") == "a") + +end + +test_locals(1, 2, 3) +assert(a == "new a") + +-- test file and string names truncation +a = "function f () end" +local function dostring (s, x) + return load(s, x)() +end +dostring(a) +assert(debug.getinfo(f).short_src == string.format('[string "%s"]', a)) +dostring(a .. string.format("; %s\n=1", string.rep('p', 400))) +assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$')) +dostring(a .. string.format("; %s=1", string.rep('p', 400))) +assert(string.find(debug.getinfo(f).short_src, '^%[string [^\n]*%.%.%."%]$')) +dostring("\n" .. a) +assert(debug.getinfo(f).short_src == '[string "..."]') +dostring(a, "") +assert(debug.getinfo(f).short_src == '[string ""]') +dostring(a, "@xuxu") +assert(debug.getinfo(f).short_src == "xuxu") +dostring(a, "@" .. string.rep('p', 1000) .. 't') +assert(string.find(debug.getinfo(f).short_src, "^%.%.%.p*t$")) +dostring(a, "=xuxu") +assert(debug.getinfo(f).short_src == "xuxu") +dostring(a, string.format("=%s", string.rep('x', 500))) +assert(string.find(debug.getinfo(f).short_src, "^x*$")) +dostring(a, "=") +assert(debug.getinfo(f).short_src == "") +a = nil; +f = nil; + +repeat + local g = { x = function() + local a = debug.getinfo(2) + assert(a.name == 'f' and a.namewhat == 'local') + a = debug.getinfo(1) + assert(a.name == 'x' and a.namewhat == 'field') + return 'xixi' + end } + local f = function() + return 1 + 1 and (not 1 or g.x()) + end + assert(f() == 'xixi') + g = debug.getinfo(f) + assert(g.what == "Lua" and g.func == f and g.namewhat == "" and not g.name) + + function f (x, name) + -- local! + if not name then + name = 'f' + end + local a = debug.getinfo(1) + print(a.name, a.namewhat, name) + assert(a.name == name and a.namewhat == 'local') + return x + end + + -- breaks in different conditions + if 3 > 4 then + break + end ; + f() + if 3 < 4 then + a = 1 + else + break + end ; + f() + while 1 do + local x = 10; + break + end ; + f() + local b = 1 + if 3 > 4 then + return math.sin(1) + end ; + f() + a = 3 < 4; + f() + a = 3 < 4 or 1; + f() + repeat local x = 20; + if 4 > 3 then + f() + else + break + end ; + f() until 1 + g = {} + f(g).x = f(2) and f(10) + f(9) + assert(g.x == f(19)) + function g(x) + if not x then + return 3 + end + return (x('a', 'x')) + end + assert(g(f) == 'a') +until 1 + +local function test_upvalues() + local a = 3 + local function f(x) + local b = a + x + local function g(y) + local c = b + y + local function h() + return a + b + c + end + multi_assert({ "a", 3 }, debug.getupvalue(h, 1)) + multi_assert({ "b", 4 }, debug.getupvalue(h, 2)) + multi_assert({ "c", 6 }, debug.getupvalue(h, 3)) + multi_assert({ "b", 4 }, debug.getupvalue(g, 1)) + multi_assert({ "a", 3 }, debug.getupvalue(g, 2)) + multi_assert({ "a", 3 }, debug.getupvalue(f, 1)) + debug.setupvalue(h, 1, 10) + debug.setupvalue(h, 2, 20) + debug.setupvalue(h, 3, 30) + assert(h() == 60) + end + g(2) + end + f(1) +end +test_upvalues() +local mt = { + __metatable = "my own metatable", + __index = function(o, k) + return o + k + end +} + +local a = 1 +local b = 2 +local function f() + return a +end +local function g() + return b +end + +debug.upvaluejoin(f, 1, g, 1) + +assert(f() == 2) +b = 3 +assert(f() == 3) + +debug.setmetatable(10, mt) +assert(debug.getmetatable(10) == mt) +a = 10 +assert(a[3] == 13) + +assert(debug.traceback(print) == print) +assert(debug.traceback(print) == print) + +assert(type(debug.getregistry()) == "table") + +-- testing nparams, nups e isvararg +local t = debug.getinfo(print, "u") +assert(t.isvararg == true and t.nparams == 0 and t.nups == 0) + +t = debug.getinfo(function(a, b, c) +end, "u") +assert(t.isvararg == false and t.nparams == 3 and t.nups == 0) + +t = debug.getinfo(function(a, b, ...) + return t[a] +end, "u") +assert(t.isvararg == true and t.nparams == 2 and t.nups == 1) + +t = debug.getinfo(1) -- main +assert(t.isvararg == true and t.nparams == 0 and t.nups == 1 and + debug.getupvalue(t.func, 1) == "_ENV") + + +-- testing debugging of coroutines + +local function checktraceback (co, p, level) + local tb = debug.traceback(co, nil, level) + local i = 0 + for l in string.gmatch(tb, "[^\n]+\n?") do + assert(i == 0 or string.find(l, p[i])) + i = i + 1 + end + assert(p[i] == nil) +end + +local function f (n) + if n > 0 then + f(n - 1) + else + coroutine.yield() + end +end + +local co = coroutine.create(f) +coroutine.resume(co, 3) +checktraceback(co, { "yield", "db_mini.lua", "db_mini.lua", "db_mini.lua", "db_mini.lua" }) +checktraceback(co, { "db_mini.lua", "db_mini.lua", "db_mini.lua", "db_mini.lua" }, 1) +checktraceback(co, { "db_mini.lua", "db_mini.lua", "db_mini.lua" }, 2) +checktraceback(co, { "db_mini.lua" }, 4) +checktraceback(co, {}, 40) \ No newline at end of file