From ba36482db83001b3ede203a92e56d31a30356b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20R=C3=A4tzel?= Date: Mon, 6 Jan 2025 21:36:23 +0000 Subject: [PATCH] Optimize memory block sizes allocated for virtual stack frames Add stack depth analysis to compute a more precise upper bound for stack depth for a given stack frame. --- implement/pine/Pine/PineVM/PineVM.cs | 44 +++++++- implement/pine/PineVM/PineIRCompiler.cs | 20 ++-- implement/pine/PineVM/StackInstruction.cs | 119 ++++++++++++++++++++-- implement/test-elm-time/PineVMTests.cs | 2 +- 4 files changed, 159 insertions(+), 26 deletions(-) diff --git a/implement/pine/Pine/PineVM/PineVM.cs b/implement/pine/Pine/PineVM/PineVM.cs index 20d6eb78..447c737a 100644 --- a/implement/pine/Pine/PineVM/PineVM.cs +++ b/implement/pine/Pine/PineVM/PineVM.cs @@ -88,6 +88,40 @@ public record StackFrameInstructions( .DefaultIfEmpty(-1) .Max(); + public int MaxStackUsage { init; get; } = ComputeMaxStackUsage(Instructions); + + public static int ComputeMaxStackUsage( + IReadOnlyList Instructions) + { + int currentDepth = 0; + int maxDepth = 0; + + for (int i = 0; i < Instructions.Count; i++) + { + var instr = Instructions[i]; + + var (popCount, pushCount) = + StackInstruction.GetPopCountAndPushCount(instr); + + currentDepth = currentDepth - popCount + pushCount; + + if (currentDepth < 0) + { + throw new InvalidOperationException( + "Instruction sequence pops more items than available."); + } + + maxDepth = + maxDepth < currentDepth + ? + currentDepth + : + maxDepth; + } + + return maxDepth; + } + public virtual bool Equals(StackFrameInstructions? other) { if (other is not { } notNull) @@ -299,7 +333,7 @@ StackFrame StackFrameFromExpression( expression, instructions, EnvironmentValue: environment, - StackValues: new PineValue[instructions.Instructions.Count], + StackValues: new PineValue[instructions.MaxStackUsage], LocalsValues: new PineValue[instructions.MaxLocalIndex + 1], BeginInstructionCount: beginInstructionCount, BeginParseAndEvalCount: beginParseAndEvalCount, @@ -1516,7 +1550,7 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) continue; } - case StackInstructionKind.Concat_List: + case StackInstructionKind.Concat_Generic: { var listValue = currentFrame.PopTopmostFromStack(); @@ -1797,7 +1831,7 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) continue; } - case StackInstructionKind.Int_Is_Sorted_Asc_List: + case StackInstructionKind.Int_Is_Sorted_Asc_Generic: { var listValue = currentFrame.PopTopmostFromStack(); @@ -1981,7 +2015,7 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) continue; } - case StackInstructionKind.Int_Add_List: + case StackInstructionKind.Int_Add_Generic: { var listValue = currentFrame.PopTopmostFromStack(); @@ -1992,7 +2026,7 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) continue; } - case StackInstructionKind.Int_Mul_List: + case StackInstructionKind.Int_Mul_Generic: { var listValue = currentFrame.PopTopmostFromStack(); diff --git a/implement/pine/PineVM/PineIRCompiler.cs b/implement/pine/PineVM/PineIRCompiler.cs index 1ced5198..b4f05857 100644 --- a/implement/pine/PineVM/PineIRCompiler.cs +++ b/implement/pine/PineVM/PineIRCompiler.cs @@ -530,7 +530,7 @@ public static NodeCompilationResult CompileKernelApplication_Equal( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Equal_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Equal_Generic)); } public static NodeCompilationResult CompileKernelApplication_Head( @@ -743,7 +743,7 @@ public static NodeCompilationResult CompileKernelApplication_Concat( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Concat_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Concat_Generic)); } } @@ -956,7 +956,7 @@ public static NodeCompilationResult CompileKernelApplication_Int_Add( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Int_Add_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Int_Add_Generic)); } } @@ -1045,7 +1045,7 @@ public static NodeCompilationResult CompileKernelApplication_Int_Mul( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Int_Mul_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Int_Mul_Generic)); } } @@ -1091,7 +1091,7 @@ public static NodeCompilationResult CompileKernelApplication_Int_Is_Sorted_Asc( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Int_Is_Sorted_Asc_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Int_Is_Sorted_Asc_Generic)); } public static NodeCompilationResult CompileKernelApplication_Bit_And( @@ -1142,7 +1142,7 @@ public static NodeCompilationResult CompileKernelApplication_Bit_And( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_And_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_And_Generic)); } } @@ -1194,7 +1194,7 @@ public static NodeCompilationResult CompileKernelApplication_Bit_Or( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Or_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Or_Generic)); } } @@ -1246,7 +1246,7 @@ public static NodeCompilationResult CompileKernelApplication_Bit_Xor( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Xor_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Xor_Generic)); } } @@ -1291,7 +1291,7 @@ public static NodeCompilationResult CompileKernelApplication_Bit_Shift_Left( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Shift_Left_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Shift_Left_Generic)); } public static NodeCompilationResult CompileKernelApplication_Bit_Shift_Right( @@ -1322,7 +1322,7 @@ public static NodeCompilationResult CompileKernelApplication_Bit_Shift_Right( input, copyToLocal, prior) - .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Shift_Right_List)); + .AppendInstruction(new StackInstruction(StackInstructionKind.Bit_Shift_Right_Generic)); } public static Expression? TryParseExprAsIntNegation(Expression expression) diff --git a/implement/pine/PineVM/StackInstruction.cs b/implement/pine/PineVM/StackInstruction.cs index 9a81ff1c..24b3c3a4 100644 --- a/implement/pine/PineVM/StackInstruction.cs +++ b/implement/pine/PineVM/StackInstruction.cs @@ -26,6 +26,7 @@ public enum StackInstructionKind /// /// Copy the top value from the stack into the local at index . + /// The value is not popped from the stack. /// Local_Set, @@ -47,7 +48,7 @@ public enum StackInstructionKind /// /// Concatenates the items in the list from the top value on the stack. /// - Concat_List, + Concat_Generic, /// /// Concatenates the top value from the stack and the second value from the stack. @@ -112,7 +113,7 @@ public enum StackInstructionKind /// /// Check if all items in the list from the top value on the stack are equal. /// - Equal_List, + Equal_Generic, /// /// Negate the top value on the stack, works for both integers and booleans. @@ -123,12 +124,16 @@ public enum StackInstructionKind /// Corresponding to the kernel function 'int_is_sorted_asc' of the Pine language, /// where we cannot proof a more specific representation is possible at compile time. /// - Int_Is_Sorted_Asc_List, + Int_Is_Sorted_Asc_Generic, Int_Less_Than_Binary, Int_Less_Than_Or_Equal_Binary, + Int_Less_Than_Const, + + Int_Less_Than_Or_Equal_Const, + /// /// Jump to the offset from if the top value on the stack is true. /// @@ -163,7 +168,7 @@ public enum StackInstructionKind /// /// Add all items in the list from the top value from the stack. /// - Int_Add_List, + Int_Add_Generic, /// /// Subtract the top value from the second value on the stack. @@ -183,17 +188,21 @@ public enum StackInstructionKind /// /// Multiply all items in the list from the top value from the stack. /// - Int_Mul_List, + Int_Mul_Generic, - Bit_And_List, + Bit_And_Generic, Bit_And_Binary, - Bit_Or_List, + Bit_And_Const, + + Bit_Or_Generic, Bit_Or_Binary, - Bit_Xor_List, + Bit_Or_Const, + + Bit_Xor_Generic, Bit_Xor_Binary, @@ -203,13 +212,13 @@ public enum StackInstructionKind Bit_Shift_Left_Const, - Bit_Shift_Left_List, + Bit_Shift_Left_Generic, Bit_Shift_Right_Var, Bit_Shift_Right_Const, - Bit_Shift_Right_List, + Bit_Shift_Right_Generic, } /// @@ -258,6 +267,96 @@ public static StackInstruction Jump_Unconditional(int offset) => /// public static StackInstruction Jump_If_True(int offset) => new(StackInstructionKind.Jump_If_True_Const, JumpOffset: offset); + + public static (int popCount, int pushCount) GetPopCountAndPushCount(StackInstruction instruction) => + instruction.Kind switch + { + StackInstructionKind.Push_Literal => (0, 1), + StackInstructionKind.Push_Environment => (0, 1), + + StackInstructionKind.Local_Set => (0, 0), + StackInstructionKind.Local_Get => (0, 1), + + StackInstructionKind.Pop => (1, 0), + + StackInstructionKind.Length => (1, 1), + + StackInstructionKind.Concat_Generic => (1, 1), + StackInstructionKind.Concat_Binary => (2, 1), + + StackInstructionKind.Slice_Skip_Var_Take_Var => (3, 1), + StackInstructionKind.Slice_Skip_Var_Take_Const => (2, 1), + + StackInstructionKind.Skip_Generic => (1, 1), + StackInstructionKind.Skip_Binary => (2, 1), + + StackInstructionKind.Take_Generic => (1, 1), + StackInstructionKind.Take_Binary => (2, 1), + + StackInstructionKind.Skip_Head_Var => (2, 1), + StackInstructionKind.Skip_Head_Const => (1, 1), + + StackInstructionKind.Head_Generic => (1, 1), + + StackInstructionKind.Reverse => (1, 1), + + StackInstructionKind.BuildList => + (instruction.TakeCount + ?? throw new System.Exception( + "Missing TakeCount for BuildList instruction"), + 1), + + StackInstructionKind.Equal_Binary_Var => (2, 1), + StackInstructionKind.Not_Equal_Binary_Var => (2, 1), + StackInstructionKind.Equal_Binary_Const => (1, 1), + StackInstructionKind.Not_Equal_Binary_Const => (1, 1), + StackInstructionKind.Equal_Generic => (1, 1), + StackInstructionKind.Negate => (1, 1), + + StackInstructionKind.Int_Is_Sorted_Asc_Generic => (1, 1), + StackInstructionKind.Int_Less_Than_Binary => (2, 1), + StackInstructionKind.Int_Less_Than_Or_Equal_Binary => (2, 1), + StackInstructionKind.Int_Less_Than_Const => (1, 1), + StackInstructionKind.Int_Less_Than_Or_Equal_Const => (1, 1), + + StackInstructionKind.Jump_If_True_Const => (1, 0), + StackInstructionKind.Jump_Const => (0, 0), + StackInstructionKind.Return => (1, 0), + StackInstructionKind.Parse_And_Eval => (2, 1), + + StackInstructionKind.Int_Add_Binary => (2, 1), + StackInstructionKind.Int_Add_Const => (1, 1), + StackInstructionKind.Int_Add_Generic => (1, 1), + StackInstructionKind.Int_Sub_Binary => (2, 1), + StackInstructionKind.Int_Mul_Binary => (2, 1), + StackInstructionKind.Int_Mul_Const => (1, 1), + StackInstructionKind.Int_Mul_Generic => (1, 1), + + StackInstructionKind.Bit_And_Generic => (1, 1), + StackInstructionKind.Bit_And_Binary => (2, 1), + StackInstructionKind.Bit_And_Const => (1, 1), + + StackInstructionKind.Bit_Or_Generic => (1, 1), + StackInstructionKind.Bit_Or_Binary => (2, 1), + StackInstructionKind.Bit_Or_Const => (1, 1), + + StackInstructionKind.Bit_Xor_Generic => (1, 1), + StackInstructionKind.Bit_Xor_Binary => (2, 1), + + StackInstructionKind.Bit_Not => (1, 1), + + StackInstructionKind.Bit_Shift_Left_Var => (2, 1), + StackInstructionKind.Bit_Shift_Left_Const => (1, 1), + StackInstructionKind.Bit_Shift_Left_Generic => (1, 1), + + StackInstructionKind.Bit_Shift_Right_Var => (2, 1), + StackInstructionKind.Bit_Shift_Right_Const => (1, 1), + StackInstructionKind.Bit_Shift_Right_Generic => (1, 1), + + var otherKind => + throw new System.NotImplementedException( + "Unknown StackInstructionKind: " + otherKind) + }; } diff --git a/implement/test-elm-time/PineVMTests.cs b/implement/test-elm-time/PineVMTests.cs index a96051b0..b5390bdf 100644 --- a/implement/test-elm-time/PineVMTests.cs +++ b/implement/test-elm-time/PineVMTests.cs @@ -401,7 +401,7 @@ public void Compile_stack_frame_instructions() new StackInstruction(StackInstructionKind.Skip_Binary), - new StackInstruction(StackInstructionKind.Concat_List), + new StackInstruction(StackInstructionKind.Concat_Generic), new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 0),