diff --git a/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs b/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs index dfa2d73f..5522e0ed 100644 --- a/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs +++ b/src/Lua/CodeAnalysis/Compilation/FunctionCompilationContext.cs @@ -130,7 +130,7 @@ public void PushInstruction(in Instruction instruction, in SourcePosition positi /// Push or merge the new instruction. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in SourcePosition position, ref bool incrementStackPosition) + public void PushOrMergeInstruction(in Instruction instruction, in SourcePosition position, ref bool incrementStackPosition) { if (instructions.Length == 0) { @@ -139,27 +139,40 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in return; } + var activeLocals = Scope.ActiveLocalVariables; + ref var lastInstruction = ref instructions.AsSpan()[^1]; var opcode = instruction.OpCode; switch (opcode) { case OpCode.Move: - // last A is not local variable - if (lastInstruction.A != lastLocal && - // available to merge - lastInstruction.A == instruction.B && - // not already merged - lastInstruction.A != lastInstruction.B) + + if ( + // available to merge and last A is not local variable + lastInstruction.A == instruction.B && !activeLocals[lastInstruction.A]) { switch (lastInstruction.OpCode) { - case OpCode.GetTable: + case OpCode.LoadK: + case OpCode.LoadBool when lastInstruction.C == 0: + case OpCode.LoadNil when lastInstruction.B == 0: + case OpCode.GetUpVal: + case OpCode.GetTabUp: + case OpCode.GetTable when !activeLocals[lastInstruction.B]: + case OpCode.SetTabUp: + case OpCode.SetUpVal: + case OpCode.SetTable: + case OpCode.NewTable: + case OpCode.Self: case OpCode.Add: case OpCode.Sub: case OpCode.Mul: case OpCode.Div: case OpCode.Mod: case OpCode.Pow: + case OpCode.Unm: + case OpCode.Not: + case OpCode.Len: case OpCode.Concat: { lastInstruction.A = instruction.A; @@ -173,7 +186,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in case OpCode.GetTable: { // Merge MOVE GetTable - if (lastInstruction.OpCode == OpCode.Move && lastLocal != lastInstruction.A) + if (lastInstruction.OpCode == OpCode.Move && !activeLocals[lastInstruction.A]) { if (lastInstruction.A == instruction.B) { @@ -189,7 +202,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in case OpCode.SetTable: { // Merge MOVE SETTABLE - if (lastInstruction.OpCode == OpCode.Move && lastLocal != lastInstruction.A) + if (lastInstruction.OpCode == OpCode.Move && !activeLocals[lastInstruction.A]) { var lastB = lastInstruction.B; var lastA = lastInstruction.A; @@ -200,7 +213,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in { ref var last2Instruction = ref instructions.AsSpan()[^2]; var last2A = last2Instruction.A; - if (last2Instruction.OpCode == OpCode.Move && lastLocal != last2A && instruction.C == last2A) + if (last2Instruction.OpCode == OpCode.Move && !activeLocals[last2A] && instruction.C == last2A) { last2Instruction = Instruction.SetTable((byte)(lastB), instruction.B, last2Instruction.B); instructions.RemoveAtSwapback(instructions.Length - 1); @@ -232,7 +245,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in if (last2OpCode is OpCode.LoadK or OpCode.Move) { var last2A = last2Instruction.A; - if (last2A != lastLocal && instruction.C == last2A) + if (!activeLocals[last2A] && instruction.C == last2A) { var c = last2OpCode == OpCode.LoadK ? last2Instruction.Bx + 256 : last2Instruction.B; last2Instruction = lastInstruction; @@ -250,10 +263,9 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in case OpCode.Unm: case OpCode.Not: case OpCode.Len: - if (lastInstruction.OpCode == OpCode.Move && lastLocal != lastInstruction.A && lastInstruction.A == instruction.B) + if (lastInstruction.OpCode == OpCode.Move && !activeLocals[lastInstruction.A] && lastInstruction.A == instruction.B) { lastInstruction = instruction with { B = lastInstruction.B }; - ; instructionPositions[^1] = position; incrementStackPosition = false; return; @@ -264,6 +276,7 @@ public void PushOrMergeInstruction(int lastLocal, in Instruction instruction, in if (lastInstruction.OpCode == OpCode.Move && instruction.B == 2 && lastInstruction.B < 256) { lastInstruction = instruction with { A = (byte)lastInstruction.B }; + instructionPositions[^1] = position; incrementStackPosition = false; return; diff --git a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs b/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs index f6696f9e..e9118086 100644 --- a/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs +++ b/src/Lua/CodeAnalysis/Compilation/LuaCompiler.cs @@ -146,7 +146,13 @@ public bool VisitBinaryExpressionNode(BinaryExpressionNode node, ScopeCompilatio a = context.StackTopPosition; } - context.PushInstruction(Instruction.Test(a, (byte)(node.OperatorType is BinaryOperator.And ? 0 : 1)), node.Position); + context.PushInstruction(Instruction.Test(a, 0), node.Position); + if (node.OperatorType is BinaryOperator.Or) + { + context.PushInstruction(Instruction.Jmp(0, 2), node.Position); + context.PushInstruction(Instruction.Move(r, a), node.Position); + } + var testJmpIndex = context.Function.Instructions.Length; context.PushInstruction(Instruction.Jmp(0, 0), node.Position); diff --git a/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs b/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs index 3be2b072..3133f0bf 100644 --- a/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs +++ b/src/Lua/CodeAnalysis/Compilation/ScopeCompilationContext.cs @@ -33,7 +33,7 @@ public static void Return(ScopeCompilationContext context) readonly Dictionary, LocalVariableDescription> localVariables = new(256, Utf16StringMemoryComparer.Default); readonly Dictionary, LabelDescription> labels = new(32, Utf16StringMemoryComparer.Default); - byte lastLocalVariableIndex; + internal BitFlags256 ActiveLocalVariables = default; public byte StackStartPosition { get; private set; } public byte StackPosition { get; set; } @@ -74,7 +74,7 @@ public FunctionCompilationContext CreateChildFunction() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void PushInstruction(in Instruction instruction, SourcePosition position, bool incrementStackPosition = false) { - Function.PushOrMergeInstruction(lastLocalVariableIndex, instruction, position, ref incrementStackPosition); + Function.PushOrMergeInstruction(instruction, position, ref incrementStackPosition); if (incrementStackPosition) { StackPosition++; @@ -98,7 +98,7 @@ public void TryPushCloseUpValue(byte top, SourcePosition position) public void AddLocalVariable(ReadOnlyMemory name, LocalVariableDescription description, bool markAsLastLocalVariable = true) { localVariables[name] = description; - lastLocalVariableIndex = description.RegisterIndex; + ActiveLocalVariables.Set(description.RegisterIndex); } @@ -173,7 +173,7 @@ public void Reset() HasCapturedLocalVariables = false; localVariables.Clear(); labels.Clear(); - lastLocalVariableIndex = 0; + ActiveLocalVariables = default; } /// diff --git a/src/Lua/Internal/BitFlags256.cs b/src/Lua/Internal/BitFlags256.cs new file mode 100644 index 00000000..0e3a3176 --- /dev/null +++ b/src/Lua/Internal/BitFlags256.cs @@ -0,0 +1,23 @@ +namespace Lua.Internal; + +internal unsafe struct BitFlags256 +{ + internal fixed long Data[4]; + + public bool this[int index] + { + get => (Data[index >> 6] & (1L << (index & 63))) != 0; + set + { + if (value) + { + Data[index >> 6] |= 1L << (index & 63); + } + else + { + Data[index >> 6] &= ~(1L << (index & 63)); + } + } + } + public void Set(int index) => Data[index >> 6] |= 1L << (index & 63); +} \ No newline at end of file diff --git a/tests/Lua.Tests/ConditionalsTests.cs b/tests/Lua.Tests/ConditionalsTests.cs new file mode 100644 index 00000000..3bec16da --- /dev/null +++ b/tests/Lua.Tests/ConditionalsTests.cs @@ -0,0 +1,22 @@ +namespace Lua.Tests; + +public class ConditionalsTests +{ + [Test] + public async Task Test_Clamp() + { + var source = @" +function clamp(x, min, max) + return x < min and min or (x > max and max or x) +end + +return clamp(0, 1, 25), clamp(10, 1, 25), clamp(30, 1, 25) +"; + var result = await LuaState.Create().DoStringAsync(source); + + Assert.That(result, Has.Length.EqualTo(3)); + Assert.That(result[0], Is.EqualTo(new LuaValue(1))); + Assert.That(result[1], Is.EqualTo(new LuaValue(10))); + Assert.That(result[2], Is.EqualTo(new LuaValue(25))); + } +} \ No newline at end of file