From 93bdf60cd6eeb7a494b01bb045fcc8c463f9ff90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20R=C3=A4tzel?= Date: Fri, 3 Jan 2025 19:01:13 +0000 Subject: [PATCH] Introduce a new execution engine and IR using atomic instructions This commit changes the internal intermediate representation used to run Pine programs, introducing a new compiler for that IR and a corresponding execution engine. One immediate benefit was an improvement in runtime efficiency: In workloads parsing Elm module syntax, I saw a more than 30 percent reduction in response times for larger files. Another benefit is improved performance profiling. The count of instructions performed is now a better proxy for runtime expenses than the previous interpreter, where each instruction could contain a subexpression tree of arbitrary size. This switch to an IR with flat/atomic instructions makes it easier to further compile to IRs like WebAssembly or .NET CIL. --- implement/CHANGELOG.md | 15 +- implement/Pine.Core/KernelFunction.cs | 14 + implement/pine/Pine/PineVM/PineVM.cs | 1056 ++++++++++++----- .../pine/Pine/PineVM/StackInstruction.cs | 67 -- implement/pine/PineVM/PineIRCompiler.cs | 813 +++++++++++++ implement/pine/PineVM/StackInstruction.cs | 244 ++++ implement/test-elm-time/PGOTests.cs | 60 +- implement/test-elm-time/PineVMTests.cs | 626 +++++----- 8 files changed, 2222 insertions(+), 673 deletions(-) delete mode 100644 implement/pine/Pine/PineVM/StackInstruction.cs create mode 100644 implement/pine/PineVM/PineIRCompiler.cs create mode 100644 implement/pine/PineVM/StackInstruction.cs diff --git a/implement/CHANGELOG.md b/implement/CHANGELOG.md index 17da5b00..20b08545 100644 --- a/implement/CHANGELOG.md +++ b/implement/CHANGELOG.md @@ -38,24 +38,27 @@ Expand the Elm compiler and core libraries to support `Float` literals and `Stri + Added a framework to integrate the various interfaces we want to use in command-line interface applications (`Platform.CommandLineApp`), including stdIn/stdOut/stdErr streams and environment variables. + Introduced the `pine run` command as a common way to run an app. -## 2024-10-26 - Added VSCode Extension and Language Server +## 2024-10-26 - Added VS Code Extension and Language Server -Published the first version of the Elm developer tools VSCode extension, with a command to format Elm modules. +Published the first version of the Elm developer tools VS Code extension, with a command to format Elm modules. -## 2024-12-08 - Expanded VSCode Extension and Language Server +## 2024-12-08 - Expanded VS Code Extension and Language Server + Added feature: Completions: Shows completion suggestions matching the current context + Added feature: Hover tips: Shows type annotations and documentation for a type alias, module, custom type or function -## 2024-12-15 - Expanded VSCode Extension and Language Server +## 2024-12-15 - Expanded VS Code Extension and Language Server + Added feature: 'Go to Definition' -## 2024-12-19 - Expanded VSCode Extension and Language Server +## 2024-12-19 - Expanded VS Code Extension and Language Server + Added feature: 'Find All References' -## 2024-12-22 - Expanded VSCode Extension and Language Server +## 2024-12-22 - Expanded VS Code Extension and Language Server + Added feature: 'Rename Symbol' +## 2025-01-03 - Introduced New Execution Engine And IR + ++ Changed the internal intermediate representation used to run Pine programs, introducing a new compiler for that IR and a corresponding execution engine. diff --git a/implement/Pine.Core/KernelFunction.cs b/implement/Pine.Core/KernelFunction.cs index 708f1c31..70300517 100644 --- a/implement/Pine.Core/KernelFunction.cs +++ b/implement/Pine.Core/KernelFunction.cs @@ -624,6 +624,13 @@ public static PineValue bit_shift_left(PineValue value) return PineValue.EmptyList; } + return bit_shift_left(shiftCount, blobValue); + } + + public static PineValue bit_shift_left( + BigInteger shiftCount, + PineValue.BlobValue blobValue) + { var offsetBytes = (int)(shiftCount / 8); var offsetBits = (int)(shiftCount % 8); @@ -669,6 +676,13 @@ public static PineValue bit_shift_right(PineValue value) return PineValue.EmptyList; } + return bit_shift_right(shiftCount, blobValue); + } + + public static PineValue bit_shift_right( + BigInteger shiftCount, + PineValue.BlobValue blobValue) + { var offsetBytes = (int)(shiftCount / 8); var offsetBits = (int)(shiftCount % 8); diff --git a/implement/pine/Pine/PineVM/PineVM.cs b/implement/pine/Pine/PineVM/PineVM.cs index f306e53e..06833127 100644 --- a/implement/pine/Pine/PineVM/PineVM.cs +++ b/implement/pine/Pine/PineVM/PineVM.cs @@ -21,7 +21,7 @@ public class PineVM : IPineVM private IDictionary? EvalCache { init; get; } - private EvaluationConfig? evaluationConfigDefault; + private readonly EvaluationConfig? evaluationConfigDefault; private readonly Action? reportFunctionApplication; @@ -111,7 +111,8 @@ record StackFrame( Expression Expression, StackFrameInstructions Instructions, PineValue EnvironmentValue, - Memory InstructionsResultValues, + Memory StackValues, + Memory LocalsValues, long BeginInstructionCount, long BeginParseAndEvalCount, long BeginStackFrameCount, @@ -119,7 +120,9 @@ record StackFrame( { public int InstructionPointer { get; set; } = 0; - public int LastEvalResultIndex { get; set; } = -1; + public int StackPointer { get; set; } = 0; + + public long InstructionCount { get; set; } = 0; public void ReturnFromChildFrame(PineValue frameReturnValue) { @@ -134,17 +137,48 @@ public void ReturnFromChildFrame(PineValue frameReturnValue) public void PushInstructionResult(PineValue value) { - InstructionsResultValues.Span[InstructionPointer] = value; - LastEvalResultIndex = InstructionPointer; + if (value is null) + { + throw new InvalidOperationException( + "PushInstructionResult called with null value"); + } + + StackValues.Span[StackPointer] = value; + StackPointer++; ++InstructionPointer; } - public PineValue LastEvalResult() + public void LocalSet(int localIndex, PineValue value) + { + if (value is null) + { + throw new InvalidOperationException( + "LocalSet called with null value"); + } + + LocalsValues.Span[localIndex] = value; + } + + public PineValue LocalGet(int localIndex) + { + var value = LocalsValues.Span[localIndex]; + + if (value is null) + { + throw new InvalidOperationException( + "LocalGet called with null value"); + } + + return value; + } + + public PineValue PopTopmostFromStack() { - if (LastEvalResultIndex < 0) - throw new InvalidOperationException("Reference to last eval result before first eval"); + if (StackPointer <= 0) + throw new InvalidOperationException("ConsumeSingleFromStack called with empty stack"); - return InstructionsResultValues.Span[LastEvalResultIndex]; + --StackPointer; + return StackValues.Span[StackPointer]; } } @@ -251,7 +285,8 @@ StackFrame StackFrameFromExpression( expression, instructions, EnvironmentValue: environment, - new PineValue[instructions.Instructions.Count], + StackValues: new PineValue[instructions.Instructions.Count], + LocalsValues: new PineValue[instructions.Instructions.Count], BeginInstructionCount: beginInstructionCount, BeginParseAndEvalCount: beginParseAndEvalCount, BeginStackFrameCount: beginStackFrameCount, @@ -396,7 +431,9 @@ envConstraintId is null ? skipInlining: skipInlining); IReadOnlyList allInstructions = - [.. InstructionsFromExpressionTransitive(reducedExpression).Append(StackInstruction.Return)]; + [.. InstructionsFromExpressionTransitive(reducedExpression), + StackInstruction.Return + ]; return allInstructions; } @@ -740,14 +777,17 @@ public static Expression SubstituteSubexpressionsForEnvironmentConstraint( public static IReadOnlyList InstructionsFromExpressionTransitive( Expression rootExpression) { - var node = NodeFromExpressionTransitive( - rootExpression, - conditionalsToSkip: []); + var node = + NodeFromExpressionTransitive( + rootExpression, + conditionalsToSkip: []); - return InstructionsFromNode( - node, - reusableExpressionResultOffset: _ => null, - shouldSeparate: _ => false); + return + InstructionsFromNode( + node, + instructionIndex: 0, + reusableExpressionResultOffset: _ => null, + makeReusable: _ => false).instructions; } public static ImperativeNode NodeFromExpressionTransitive( @@ -794,25 +834,58 @@ public static ImperativeNode NodeFromExpressionTransitive( return new ImperativeNode.LeafNode(rootExpression); } - public static IReadOnlyList InstructionsFromNode( + public static (IReadOnlyList instructions, IReadOnlyDictionary localsSet) + InstructionsFromNode( ImperativeNode imperativeNode, + int instructionIndex, Func reusableExpressionResultOffset, - Func shouldSeparate) + Func makeReusable) { if (imperativeNode is ImperativeNode.LeafNode leaf) { - var instructionExprsFiltered = - ExpressionsToSeparateSkippingConditional( + var allSubexpressions = new HashSet(); + + var subexpressionsToReuse = new HashSet(); + + foreach ( + var subexpression in + Expression.EnumerateSelfAndDescendants( leaf.Expression, - expressionAlreadyCovered: expr => reusableExpressionResultOffset(expr).HasValue, - shouldSeparate: shouldSeparate) - .Append(leaf.Expression) - .Distinct() - .ToImmutableArray(); + skipDescendants: + subexpression => + { + if (reusableExpressionResultOffset(subexpression).HasValue) + { + return true; + } + + if (!ExpressionLargeEnoughForCSE(subexpression)) + { + return true; + } + + if (makeReusable(subexpression)) + { + subexpressionsToReuse.Add(subexpression); + } + + if (allSubexpressions.Contains(subexpression)) + { + subexpressionsToReuse.Add(subexpression); + + return true; + } + + allSubexpressions.Add(subexpression); + + return false; + })) + { + } var localInstructionIndexFromExpr = new Dictionary(); - int? reusableEvalResult(Expression expr) + int? reusableLocalIndex(Expression expr) { { if (reusableExpressionResultOffset.Invoke(expr) is { } reusableIndex) @@ -831,27 +904,47 @@ public static IReadOnlyList InstructionsFromNode( return null; } - var instructionsOptimized = - instructionExprsFiltered - .Select((expression, instructionIndex) => - { - if (reusableEvalResult(expression) is { } reusableIndex) - { - var offset = reusableIndex - instructionIndex; + var subexpressionsToReuseInstructions = new List(); - return new Expression.StackReferenceExpression(offset); - } + var subexpressionsToReuseOrdered = + subexpressionsToReuse + .OrderBy(se => se.SubexpressionCount) + .ToImmutableArray(); - localInstructionIndexFromExpr.Add(expression, instructionIndex); + foreach (var subexpression in subexpressionsToReuseOrdered) + { + var subexpressionInstructions = + PineIRCompiler.CompileExpressionTransitive( + subexpression, + localIndexFromExpr: reusableLocalIndex); + + var localIndex = + instructionIndex + + subexpressionsToReuseInstructions.Count + + subexpressionInstructions.Count; + + subexpressionsToReuseInstructions.AddRange( + [ + ..subexpressionInstructions, + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: localIndex) + ]); + + localInstructionIndexFromExpr[subexpression] = localIndex; + } - return expression; - }) - .Select((instruction, instructionIndex) => - OptimizeExpressionTransitive(instruction, instructionIndex, reusableEvalResult)) - .Select(StackInstruction.Eval) + var rootInstructions = + PineIRCompiler.CompileExpressionTransitive( + leaf.Expression, + reusableLocalIndex) .ToImmutableArray(); - return instructionsOptimized; + IReadOnlyList instructionsOptimized = + [ + ..subexpressionsToReuseInstructions, + ..rootInstructions + ]; + + return (instructionsOptimized, localInstructionIndexFromExpr); } if (imperativeNode is ImperativeNode.ConditionalNode conditional) @@ -864,6 +957,12 @@ public static IReadOnlyList InstructionsFromNode( var unconditionalExprUnderCondition = new HashSet(); + + /* + * TODO: Change unconditionalExprUnderCondition + * General 'unconditional' is either in condition or in continuation. + * */ + foreach (var nodeUnderCondition in unconditionalNodesUnderCondition) { if (nodeUnderCondition is not ImperativeNode.LeafNode leafNode) @@ -896,7 +995,10 @@ public static IReadOnlyList InstructionsFromNode( foreach (var otherExpr in Expression.EnumerateSelfAndDescendants( otherLeafNode.Expression, - skipDescendants: candidatesForCSE.Contains)) + skipDescendants: + descendant => + candidatesForCSE.Contains(descendant) || + descendant == conditional.Origin)) { if (!ExpressionLargeEnoughForCSE(otherExpr)) continue; @@ -911,40 +1013,28 @@ public static IReadOnlyList InstructionsFromNode( } } - var conditionInstructions = + var conditionResult = InstructionsFromNode( conditional.Condition, + instructionIndex: instructionIndex, reusableExpressionResultOffset, - shouldSeparate: candidatesForCSE.Contains); + makeReusable: candidatesForCSE.Contains); - var reusableFromCondition = new Dictionary(); - - for (int i = 0; i < conditionInstructions.Count; i++) - { - var instruction = conditionInstructions[i]; - - if (instruction is not StackInstruction.EvalInstruction evalInstruction) - { - // Only include the range of instructions that will be executed unconditionally. - break; - } - - reusableFromCondition[evalInstruction.Expression] = i; - } + var conditionInstructions = conditionResult.instructions; var instructionsBeforeBranchFalseCount = conditionInstructions.Count + 1; - int? reusableResultOffsetForBranchFalse(Expression expression) + int? reusableResultOffsetForBranch(Expression expression) { if (reusableExpressionResultOffset(expression) is { } earlierOffset) { - return earlierOffset - instructionsBeforeBranchFalseCount; + return earlierOffset; } - if (reusableFromCondition.TryGetValue(expression, out var offsetFromCondition)) + if (conditionResult.localsSet.TryGetValue(expression, out var localIndex)) { - return offsetFromCondition - instructionsBeforeBranchFalseCount; + return localIndex; } return null; @@ -953,50 +1043,60 @@ public static IReadOnlyList InstructionsFromNode( IReadOnlyList falseBranchInstructions = [.. InstructionsFromNode( conditional.FalseBranch, - reusableResultOffsetForBranchFalse, - shouldSeparate: _ => false)]; + instructionIndex: instructionIndex + instructionsBeforeBranchFalseCount, + reusableResultOffsetForBranch, + makeReusable: _ => false) + .instructions]; IReadOnlyList trueBranchInstructions = [.. InstructionsFromNode( conditional.TrueBranch, + instructionIndex: instructionIndex + instructionsBeforeBranchFalseCount + falseBranchInstructions.Count + 1, reusableExpressionResultOffset: - expr => reusableResultOffsetForBranchFalse(expr) - (falseBranchInstructions.Count + 1), - shouldSeparate: _ => false)]; + reusableResultOffsetForBranch, + makeReusable: _ => false) + .instructions]; IReadOnlyList falseBranchInstructionsAndJump = [.. falseBranchInstructions, - StackInstruction.Jump(trueBranchInstructions.Count + 1) + StackInstruction.Jump_Unconditional(trueBranchInstructions.Count + 1) ]; var branchInstruction = - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: falseBranchInstructionsAndJump.Count); + StackInstruction.Jump_If_True( + offset: falseBranchInstructionsAndJump.Count); + + var conditionalResultLocalIndex = + instructionIndex + instructionsBeforeBranchFalseCount + falseBranchInstructions.Count; IReadOnlyList instructionsBeforeContinuation = [..conditionInstructions, branchInstruction, ..falseBranchInstructionsAndJump, ..trueBranchInstructions, - new StackInstruction.CopyLastAssignedInstruction() + new StackInstruction( + StackInstructionKind.Local_Set, + LocalIndex: conditionalResultLocalIndex) ]; int? reusableResultOffsetForContinuation(Expression expression) { if (expression == conditional.Origin) { - return -1; + return conditionalResultLocalIndex; } - return - reusableResultOffsetForBranchFalse(expression) - - (instructionsBeforeContinuation.Count - instructionsBeforeBranchFalseCount); + return reusableResultOffsetForBranch(expression); } IReadOnlyList continuationInstructions = - [.. InstructionsFromNode( + [ + .. InstructionsFromNode( conditional.Continuation, + instructionIndex: instructionIndex + instructionsBeforeContinuation.Count + 1, reusableResultOffsetForContinuation, - shouldSeparate: _ => false) + makeReusable: _ => false) + .instructions ]; IReadOnlyList mergedInstructions = @@ -1004,78 +1104,13 @@ [.. InstructionsFromNode( ..continuationInstructions ]; - return mergedInstructions; + return (mergedInstructions, conditionResult.localsSet); } throw new NotImplementedException( "Unexpected node type: " + imperativeNode.GetType().FullName); } - static Expression OptimizeExpressionTransitive( - Expression expression, - int instructionIndex, - Func reusableEvalResultOffset) - { - return - CompilePineToDotNet.ReducePineExpression.TransformPineExpressionWithOptionalReplacement( - findReplacement: - expr => OptimizeExpressionStep( - expr, - instructionIndex, - reusableEvalResultOffset), - expression).expr; - } - - static Expression? OptimizeExpressionStep( - Expression expression, - int instructionIndex, - Func reusableEvalResultOffset) - { - if (expression is Expression.Environment) - return null; - - if (reusableEvalResultOffset(expression) is { } reusableOffset) - { - var offset = reusableOffset - instructionIndex; - - if (offset != 0) - { - if (offset >= 0) - { - throw new Exception( - "Found non-negative offset for stack ref expr: " + offset + - " (selfIndex is " + instructionIndex + ")"); - } - - return new Expression.StackReferenceExpression(offset: offset); - } - } - - { - /* - * Skip over all expressions that we do not descend into when enumerating the components. - * (EnumerateComponentsOrderedForCompilation) - * */ - - if (expression is Expression.Conditional) - { - // Return non-null value to stop descend. - return expression; - } - } - - if (TryFuseStep(expression) is { } fused) - { - return - OptimizeExpressionTransitive( - fused, - instructionIndex, - reusableEvalResultOffset: reusableEvalResultOffset); - } - - return null; - } - static IReadOnlyList ExpressionsToSeparateSkippingConditional( Expression rootExpression, Func expressionAlreadyCovered, @@ -1463,7 +1498,8 @@ public Result EvaluateExpressionOnCustomStack( Expression: expression, Instructions: null, EnvironmentValue: environmentValue, - InstructionsResultValues: null, + StackValues: null, + LocalsValues: null, BeginInstructionCount: instructionCount, BeginParseAndEvalCount: parseAndEvalCount, BeginStackFrameCount: stackFrameCount, @@ -1522,11 +1558,13 @@ void pushStackFrame(StackFrame newFrame) if (currentFrame.ExpressionValue is { } currentFrameExprValue) { - var frameInstructionCount = instructionCount - currentFrame.BeginInstructionCount; + var frameTotalInstructionCount = + instructionCount - currentFrame.BeginInstructionCount; + var frameParseAndEvalCount = parseAndEvalCount - currentFrame.BeginParseAndEvalCount; var frameStackFrameCount = stackFrameCount - currentFrame.BeginStackFrameCount; - if (frameInstructionCount + frameStackFrameCount * 100 > 700 && EvalCache is { } evalCache) + if (frameTotalInstructionCount + frameStackFrameCount * 100 > 700 && EvalCache is { } evalCache) { evalCache.TryAdd( new EvalCacheEntryKey(currentFrameExprValue, currentFrame.EnvironmentValue), @@ -1538,7 +1576,7 @@ void pushStackFrame(StackFrame newFrame) ExpressionValue: currentFrameExprValue, currentFrame.Expression, currentFrame.EnvironmentValue, - InstructionCount: frameInstructionCount, + InstructionCount: frameTotalInstructionCount, ParseAndEvalCount: frameParseAndEvalCount, ReturnValue: frameReturnValue, StackTrace: CompileStackTrace(10))); @@ -1603,6 +1641,8 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) ++instructionCount; + ++currentFrame.InstructionCount; + try { if (currentFrame.Specialization is { } specializedFrame) @@ -1625,8 +1665,8 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) if (stepResult is ApplyStepwise.StepResult.Continue cont) { if (invokePrecompiledOrBuildStackFrame( - expressionValue: null, - expression: cont.Expression, + expressionValue: null, + expression: cont.Expression, environmentValue: cont.EnvironmentValue) is { } error) { return error; @@ -1646,199 +1686,639 @@ static ExecutionErrorReport buildErrorReport(StackFrame stackFrame) "Instruction pointer out of bounds. Missing explicit return instruction."; } - ReadOnlyMemory stackPrevValues = - currentFrame.InstructionsResultValues[..currentFrame.InstructionPointer]; + var currentInstruction = + currentFrame.Instructions.Instructions[currentFrame.InstructionPointer] + ?? + throw new InvalidOperationException("currentInstruction is null"); - var currentInstruction = currentFrame.Instructions.Instructions[currentFrame.InstructionPointer]; + var instructionKind = currentInstruction.Kind; - if (currentInstruction is StackInstruction.ReturnInstruction) + switch (instructionKind) { - var lastAssignedIndex = currentFrame.LastEvalResultIndex; + case StackInstructionKind.Push_Literal: + { + currentFrame.PushInstructionResult( + currentInstruction.Literal + ?? + throw new Exception("Invalid operation form: Missing literal value")); - if (lastAssignedIndex < 0) - { - return "Return instruction before assignment"; - } + continue; + } - var frameReturnValue = - currentFrame.InstructionsResultValues.Span[lastAssignedIndex]; + case StackInstructionKind.Push_Environment: + { + currentFrame.PushInstructionResult(currentFrame.EnvironmentValue); - var returnOverall = - returnFromStackFrame(frameReturnValue); + continue; + } - if (returnOverall is not null) - { - return returnOverall; - } + case StackInstructionKind.Equal_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); - continue; - } + var areEqual = left == right; - if (currentInstruction is StackInstruction.CopyLastAssignedInstruction) - { - var lastAssignedIndex = currentFrame.LastEvalResultIndex; + currentFrame.PushInstructionResult( + areEqual ? PineVMValues.TrueValue : PineVMValues.FalseValue); - if (lastAssignedIndex < 0) - { - return "CopyLastAssignedInstruction before assignment"; - } + continue; + } - var lastAssignedValue = - currentFrame.InstructionsResultValues.Span[lastAssignedIndex]; + case StackInstructionKind.Not_Equal_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); - if (lastAssignedValue is null) - { - throw new InvalidIntermediateCodeException( - "CopyLastAssignedInstruction referenced null", - innerException: null, - buildErrorReport(currentFrame)); - } + var areEqual = left == right; - currentFrame.PushInstructionResult(lastAssignedValue); + currentFrame.PushInstructionResult( + areEqual ? PineVMValues.FalseValue : PineVMValues.TrueValue); - continue; - } + continue; + } - if (currentInstruction is StackInstruction.EvalInstruction evalInstr) - { - if (evalInstr.Expression is Expression.ParseAndEval parseAndEval) - { + case StackInstructionKind.Length: + { + var listValue = currentFrame.PopTopmostFromStack(); + + var lengthValue = KernelFunction.length(listValue); + + currentFrame.PushInstructionResult(lengthValue); + + continue; + } + + case StackInstructionKind.Skip_Head_Const: + { + var index = + currentInstruction.SkipCount + ?? + throw new Exception("Invalid operation form: Missing index value"); + + var indexClamped = + index < 0 ? 0 : index; + + var prevValue = currentFrame.PopTopmostFromStack(); + + var fromIndexValue = + prevValue switch + { + PineValue.ListValue listValue => + listValue.Elements.Count <= indexClamped + ? + PineValue.EmptyList + : + listValue.Elements[indexClamped], + + _ => + PineValue.EmptyList + }; + + currentFrame.PushInstructionResult(fromIndexValue); + + continue; + } + + case StackInstructionKind.Skip_Head_Var: { - ++parseAndEvalCount; + var indexValue = currentFrame.PopTopmostFromStack(); + + var prevValue = currentFrame.PopTopmostFromStack(); - if (config.ParseAndEvalCountLimit is { } limit && parseAndEvalCount > limit) + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(indexValue) is { } skipCount) { - return - "Parse and eval count limit exceeded: " + - CommandLineInterface.FormatIntegerForDisplay(limit); + var skipCountInt = (int)skipCount; + + var skipCountClamped = + skipCountInt < 0 ? 0 : skipCountInt; + + resultValue = + prevValue switch + { + PineValue.ListValue listValue => + listValue.Elements.Count <= skipCountClamped + ? + PineValue.EmptyList + : + listValue.Elements[skipCountClamped], + + _ => + PineValue.EmptyList + }; } + + currentFrame.PushInstructionResult(resultValue); + + continue; } - var expressionValue = - EvaluateExpressionDefaultLessStack( - parseAndEval.Encoded, - currentFrame.EnvironmentValue, - stackPrevValues: stackPrevValues); + case StackInstructionKind.Head_Generic: + { + var prevValue = currentFrame.PopTopmostFromStack(); + + var headValue = KernelFunction.head(prevValue); - var environmentValue = - EvaluateExpressionDefaultLessStack( - parseAndEval.Environment, - currentFrame.EnvironmentValue, - stackPrevValues: stackPrevValues); + currentFrame.PushInstructionResult(headValue); + + continue; + } + case StackInstructionKind.Skip_Binary: { - if (InvocationCachedResultOrOverride( - expressionValue: expressionValue, - environmentValue: environmentValue) is { } fromCacheOrDelegate) + var skipCountValue = currentFrame.PopTopmostFromStack(); + + var prevValue = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(skipCountValue) is { } skipCount) { - currentFrame.PushInstructionResult(fromCacheOrDelegate); + resultValue = KernelFunction.skip(skipCount, prevValue); + } - continue; + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Take_Binary: + { + var takeCountValue = currentFrame.PopTopmostFromStack(); + + var prevValue = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(takeCountValue) is { } takeCount) + { + resultValue = KernelFunction.take(takeCount, prevValue); } + + currentFrame.PushInstructionResult(resultValue); + + continue; } - var parseResult = parseCache.ParseExpression(expressionValue); + case StackInstructionKind.BuildList: + { + var itemsCount = + currentInstruction.TakeCount + ?? + throw new Exception("Invalid operation form: Missing take count"); + + var items = new PineValue[itemsCount]; - if (parseResult.IsErrOrNull() is { } parseErr) + for (int i = 0; i < itemsCount; ++i) + { + items[itemsCount - i - 1] = currentFrame.PopTopmostFromStack(); + } + + currentFrame.PushInstructionResult(PineValue.List(items)); + + continue; + } + + case StackInstructionKind.Concat_Binary: { - return - "Failed to parse expression from value: " + parseErr + - " - expressionValue is " + DescribeValueForErrorMessage(expressionValue) + - " - environmentValue is " + DescribeValueForErrorMessage(environmentValue); + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + currentFrame.PushInstructionResult(KernelFunction.concat([left, right])); + + continue; } - if (parseResult.IsOkOrNull() is not { } parseOk) + case StackInstructionKind.Concat_List: { - throw new NotImplementedException( - "Unexpected result type: " + parseResult.GetType().FullName); + var listValue = currentFrame.PopTopmostFromStack(); + + var concatenated = KernelFunction.concat(listValue); + + currentFrame.PushInstructionResult(concatenated); + + continue; } + case StackInstructionKind.Slice_Skip_Var_Take_Var: { - if (invokePrecompiledOrBuildStackFrame( - expressionValue: expressionValue, - parseOk, - environmentValue) is { } error) + var takeCountValue = currentFrame.PopTopmostFromStack(); + var skipCountValue = currentFrame.PopTopmostFromStack(); + + var prevValue = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(takeCountValue) is { } takeCount && + KernelFunction.SignedIntegerFromValueRelaxed(skipCountValue) is { } skipCount) { - return error; + resultValue = + FusedSkipAndTake( + prevValue, + skipCount: (int)skipCount, + takeCount: (int)takeCount); } + currentFrame.PushInstructionResult(resultValue); + continue; } - } - if (evalInstr.Expression is Expression.Conditional conditionalExpr) - { - var conditionValue = - EvaluateExpressionDefaultLessStack( - conditionalExpr.Condition, - currentFrame.EnvironmentValue, - stackPrevValues: stackPrevValues); - - var expressionToContinueWith = - conditionValue == PineVMValues.TrueValue - ? - conditionalExpr.TrueBranch - : - conditionalExpr.FalseBranch; - - if (ExpressionShouldGetNewStackFrame(expressionToContinueWith)) + case StackInstructionKind.Slice_Skip_Var_Take_Const: { - pushStackFrame( - StackFrameFromExpression( - expressionValue: null, - expressionToContinueWith, - currentFrame.EnvironmentValue, - beginInstructionCount: instructionCount, - beginParseAndEvalCount: parseAndEvalCount, - beginStackFrameCount: stackFrameCount)); + var takeCount = + currentInstruction.TakeCount + ?? + throw new Exception("Invalid operation form: Missing take count"); + + var skipCountValue = currentFrame.PopTopmostFromStack(); + + var prevValue = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(skipCountValue) is { } skipCount) + { + resultValue = + FusedSkipAndTake( + prevValue, + skipCount: (int)skipCount, + takeCount: takeCount); + } + + currentFrame.PushInstructionResult(resultValue); continue; } - var evalBranchResult = - EvaluateExpressionDefaultLessStack( - expressionToContinueWith, - currentFrame.EnvironmentValue, - stackPrevValues: stackPrevValues); + case StackInstructionKind.Reverse: + { + var listValue = currentFrame.PopTopmostFromStack(); - currentFrame.PushInstructionResult(evalBranchResult); ; + var reversed = KernelFunction.reverse(listValue); - continue; - } + currentFrame.PushInstructionResult(reversed); - var evalResult = - EvaluateExpressionDefaultLessStack( - evalInstr.Expression, - currentFrame.EnvironmentValue, - stackPrevValues: stackPrevValues); + continue; + } - currentFrame.PushInstructionResult(evalResult); + case StackInstructionKind.Local_Set: + { + var fromStack = currentFrame.PopTopmostFromStack(); - continue; - } + currentFrame.LocalSet( + currentInstruction.LocalIndex + ?? + throw new Exception("Invalid operation form: Missing local index"), + fromStack); - if (currentInstruction is StackInstruction.JumpInstruction jumpInstruction) - { - currentFrame.InstructionPointer += jumpInstruction.Offset; - continue; - } + currentFrame.InstructionPointer++; - if (currentInstruction is StackInstruction.ConditionalJumpInstruction conditionalStatement) - { - var conditionValue = currentFrame.LastEvalResult(); + continue; + } - if (conditionValue == PineVMValues.TrueValue) - { - currentFrame.InstructionPointer += 1 + conditionalStatement.TrueBranchOffset; - continue; - } + case StackInstructionKind.Local_Get: + { + var value = + currentFrame.LocalGet( + currentInstruction.LocalIndex + ?? + throw new Exception("Invalid operation form: Missing local index")); - currentFrame.InstructionPointer++; - continue; - } + currentFrame.PushInstructionResult(value); + + continue; + } + + case StackInstructionKind.Int_Add_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + var resultValue = KernelFunction.int_add(left, right); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Int_Sub_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(left) is { } leftInt && + KernelFunction.SignedIntegerFromValueRelaxed(right) is { } rightInt) + { + resultValue = PineValueAsInteger.ValueFromSignedInteger(leftInt - rightInt); + } + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Int_Mul_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(left) is { } leftInt && + KernelFunction.SignedIntegerFromValueRelaxed(right) is { } rightInt) + { + resultValue = PineValueAsInteger.ValueFromSignedInteger(leftInt * rightInt); + } + + currentFrame.PushInstructionResult(resultValue); - throw new NotImplementedException( - "Unexpected instruction type: " + currentInstruction.GetType().FullName); + continue; + } + + case StackInstructionKind.Int_Less_Than_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(left) is { } leftInt && + KernelFunction.SignedIntegerFromValueRelaxed(right) is { } rightInt) + { + resultValue = + leftInt < rightInt ? + PineVMValues.TrueValue : + PineVMValues.FalseValue; + } + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Int_Less_Than_Or_Equal_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (KernelFunction.SignedIntegerFromValueRelaxed(left) is { } leftInt && + KernelFunction.SignedIntegerFromValueRelaxed(right) is { } rightInt) + { + resultValue = + leftInt <= rightInt ? + PineVMValues.TrueValue : + PineVMValues.FalseValue; + } + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Negate: + { + var value = currentFrame.PopTopmostFromStack(); + + var resultValue = KernelFunction.negate(value); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Return: + { + var frameReturnValue = + currentFrame.PopTopmostFromStack(); + + var returnOverall = + returnFromStackFrame(frameReturnValue); + + if (returnOverall is not null) + { + return returnOverall; + } + + continue; + } + + case StackInstructionKind.Skip_Generic: + { + var genericValue = currentFrame.PopTopmostFromStack(); + + var resultValue = KernelFunction.skip(genericValue); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Take_Generic: + { + var genericValue = currentFrame.PopTopmostFromStack(); + + var resultValue = KernelFunction.take(genericValue); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Int_Is_Sorted_Asc_List: + { + var listValue = currentFrame.PopTopmostFromStack(); + + var isSorted = KernelFunction.int_is_sorted_asc(listValue); + + currentFrame.PushInstructionResult(isSorted); + + continue; + } + + case StackInstructionKind.Parse_And_Eval: + { + { + ++parseAndEvalCount; + + if (config.ParseAndEvalCountLimit is { } limit && parseAndEvalCount > limit) + { + var stackTraceHashes = + CompileStackTrace(100) + .Select(expr => mutableCache.ComputeHash(ExpressionEncoding.EncodeExpressionAsValue(expr))) + .ToArray(); + + return + "Parse and eval count limit exceeded: " + + CommandLineInterface.FormatIntegerForDisplay(limit) + + "\nLast stack frames expressions:\n" + + string.Join("\n", stackTraceHashes.Select(hash => CommonConversion.StringBase16(hash)[..8])); + } + } + + var expressionValue = currentFrame.PopTopmostFromStack(); + + var environmentValue = currentFrame.PopTopmostFromStack(); + + { + if (InvocationCachedResultOrOverride( + expressionValue: expressionValue, + environmentValue: environmentValue) is { } fromCacheOrDelegate) + { + currentFrame.PushInstructionResult(fromCacheOrDelegate); + + continue; + } + } + + var parseResult = parseCache.ParseExpression(expressionValue); + + if (parseResult.IsErrOrNull() is { } parseErr) + { + return + "Failed to parse expression from value: " + parseErr + + " - expressionValue is " + DescribeValueForErrorMessage(expressionValue) + + " - environmentValue is " + DescribeValueForErrorMessage(environmentValue); + } + + if (parseResult.IsOkOrNull() is not { } parseOk) + { + throw new NotImplementedException( + "Unexpected result type: " + parseResult.GetType().FullName); + } + + { + if (invokePrecompiledOrBuildStackFrame( + expressionValue: expressionValue, + parseOk, + environmentValue) is { } error) + { + return error; + } + + continue; + } + } + + case StackInstructionKind.Jump_Const: + { + currentFrame.InstructionPointer += currentInstruction.JumpOffset!.Value; + + continue; + } + + case StackInstructionKind.Jump_If_True_Const: + { + var conditionValue = currentFrame.PopTopmostFromStack(); + + if (conditionValue == PineVMValues.TrueValue) + { + currentFrame.InstructionPointer += 1 + currentInstruction.JumpOffset!.Value; + continue; + } + + currentFrame.InstructionPointer++; + + continue; + } + + case StackInstructionKind.Bit_And_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = KernelFunction.bit_and(PineValue.List([left, right])); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Bit_Or_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = KernelFunction.bit_or(PineValue.List([left, right])); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Bit_Xor_Binary: + { + var right = currentFrame.PopTopmostFromStack(); + var left = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = KernelFunction.bit_xor(PineValue.List([left, right])); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Bit_Not: + { + var value = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = KernelFunction.bit_not(value); + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Bit_Shift_Left_Var: + { + var shiftValue = currentFrame.PopTopmostFromStack(); + var value = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (value is PineValue.BlobValue blobValue) + { + if (KernelFunction.SignedIntegerFromValueRelaxed(shiftValue) is { } shiftCount) + { + resultValue = KernelFunction.bit_shift_left(shiftCount, blobValue); + } + } + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + case StackInstructionKind.Bit_Shift_Right_Var: + { + var shiftValue = currentFrame.PopTopmostFromStack(); + var value = currentFrame.PopTopmostFromStack(); + + PineValue resultValue = PineValue.EmptyList; + + if (value is PineValue.BlobValue blobValue) + { + if (KernelFunction.SignedIntegerFromValueRelaxed(shiftValue) is { } shiftCount) + { + resultValue = KernelFunction.bit_shift_right(shiftCount, blobValue); + } + } + + currentFrame.PushInstructionResult(resultValue); + + continue; + } + + default: + throw new NotImplementedException( + "Unexpected instruction kind: " + instructionKind); + } } catch (Exception e) { @@ -2441,8 +2921,8 @@ public PineValue EvaluateConditionalExpression( } return EvaluateExpressionDefaultLessStack( - conditional.FalseBranch, - environment, + conditional.FalseBranch, + environment, stackPrevValues: stackPrevValues); } } diff --git a/implement/pine/Pine/PineVM/StackInstruction.cs b/implement/pine/Pine/PineVM/StackInstruction.cs deleted file mode 100644 index 49896e57..00000000 --- a/implement/pine/Pine/PineVM/StackInstruction.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Pine.Core; -using Pine.Json; -using System; -using System.Text.Json.Serialization; - -namespace Pine.PineVM; - - -[JsonConverter(typeof(JsonConverterForChoiceType))] -public abstract record StackInstruction -{ - public static StackInstruction Eval(Expression expression) => - new EvalInstruction(expression); - - public static StackInstruction Jump(int offset) => - new JumpInstruction(offset); - - public static readonly StackInstruction Return = new ReturnInstruction(); - - public record EvalInstruction( - Expression Expression) - : StackInstruction; - - public record JumpInstruction( - int Offset) - : StackInstruction; - - public record ConditionalJumpInstruction( - int TrueBranchOffset) - : StackInstruction; - - public record ReturnInstruction - : StackInstruction; - - public record CopyLastAssignedInstruction - : StackInstruction; - - public static StackInstruction TransformExpressionWithOptionalReplacement( - Func transformExpression, - StackInstruction instruction) - { - switch (instruction) - { - case EvalInstruction evalInstruction: - - var newExpression = transformExpression(evalInstruction.Expression); - - return new EvalInstruction(newExpression); - - case JumpInstruction: - return instruction; - - case ConditionalJumpInstruction: - return instruction; - - case ReturnInstruction: - return Return; - - case CopyLastAssignedInstruction: - return instruction; - - default: - throw new NotImplementedException( - "Unexpected instruction type: " + instruction.GetType().FullName); - } - } -} diff --git a/implement/pine/PineVM/PineIRCompiler.cs b/implement/pine/PineVM/PineIRCompiler.cs new file mode 100644 index 00000000..3aec1aee --- /dev/null +++ b/implement/pine/PineVM/PineIRCompiler.cs @@ -0,0 +1,813 @@ +using Pine.Core; +using System.Collections.Generic; +using System; + +namespace Pine.PineVM; + +public class PineIRCompiler +{ + + /// + /// Recursively compile an expression into a flat list of PineIROp instructions. + /// + public static IReadOnlyList CompileExpressionTransitive( + Expression expr, + Func localIndexFromExpr) + { + if (localIndexFromExpr(expr) is { } localIndex) + { + return + [new StackInstruction( + Kind: StackInstructionKind.Local_Get, + LocalIndex: localIndex) + ]; + } + + switch (expr) + { + case Expression.Literal literalExpr: + return + [new StackInstruction( + Kind: StackInstructionKind.Push_Literal, + Literal: literalExpr.Value) + ]; + + case Expression.Environment: + return + [ + StackInstruction.PushEnvironment + ]; + + case Expression.List listExpr: + { + /* + * Assume that the subsequence for each item only leaves one value on the stack. + * + * (When we need to reuse a value from a subexpression multiple times, + * we don't use a non-consuming instruction but use local_get instead to copy it) + * */ + + var itemsInstructions = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var itemExpr = listExpr.items[i]; + + var itemInstructions = + CompileExpressionTransitive( + itemExpr, + localIndexFromExpr); + + itemsInstructions.AddRange(itemInstructions); + } + + return + [ + ..itemsInstructions, + new StackInstruction( + StackInstructionKind.BuildList, + TakeCount: listExpr.items.Count) + ]; + } + + case Expression.Conditional: + + throw new InvalidOperationException( + "Conditional should be filtered out earlier"); + + case Expression.ParseAndEval pae: + return + CompileParseAndEval( + pae, + localIndexFromExpr); + + case Expression.KernelApplication kernelApp: + return + CompileKernelApplication( + kernelApp, + localIndexFromExpr); + + default: + throw new NotImplementedException( + "Unexpected expression type: " + expr.GetType().Name); + } + } + + public static IReadOnlyList CompileParseAndEval( + Expression.ParseAndEval parseAndEvalExpr, + Func localIndexFromExpr) + { + var environmentOps = + CompileExpressionTransitive( + parseAndEvalExpr.Environment, + localIndexFromExpr); + + var encodedOps = + CompileExpressionTransitive( + parseAndEvalExpr.Encoded, + localIndexFromExpr); + + return + [ + .. environmentOps, + .. encodedOps, + new StackInstruction(StackInstructionKind.Parse_And_Eval) + ]; + } + + public static IReadOnlyList CompileKernelApplication( + Expression.KernelApplication kernelApplication, + Func localIndexFromExpr) + { + return + kernelApplication.Function switch + { + nameof(KernelFunction.length) => + [ + .. CompileExpressionTransitive( + kernelApplication.Input, + localIndexFromExpr), + new StackInstruction(StackInstructionKind.Length) + ], + + nameof(KernelFunction.negate) => + [ + .. CompileExpressionTransitive( + kernelApplication.Input, + localIndexFromExpr), + new StackInstruction(StackInstructionKind.Negate) + ], + + nameof(KernelFunction.equal) => + CompileKernelApplication_Equal( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.head) => + CompileKernelApplication_Head( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.skip) => + CompileKernelApplication_Skip( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.take) => + CompileKernelApplication_Take( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.concat) => + CompileKernelApplication_Concat( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.reverse) => + CompileKernelApplication_Reverse( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.int_add) => + CompileKernelApplication_Int_Add( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.int_mul) => + CompileKernelApplication_Int_Mul( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.int_is_sorted_asc) => + CompileKernelApplication_Int_Is_Sorted_Asc( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.bit_and) => + CompileKernelApplication_Bit_And( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.bit_or) => + CompileKernelApplication_Bit_Or( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.bit_xor) => + CompileKernelApplication_Bit_Xor( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.bit_not) => + CompileKernelApplication_Bit_Not( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.bit_shift_left) => + CompileKernelApplication_Bit_Shift_Left( + kernelApplication.Input, + localIndexFromExpr), + + nameof(KernelFunction.bit_shift_right) => + CompileKernelApplication_Bit_Shift_Right( + kernelApplication.Input, + localIndexFromExpr), + + _ => + throw new NotImplementedException( + "Unknown kernel function: " + kernelApplication.Function), + }; + } + + public static IReadOnlyList CompileKernelApplication_Equal( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 2) + { + var leftOps = + CompileExpressionTransitive( + listExpr.items[0], + localIndexFromExpr); + + var rightOps = + CompileExpressionTransitive( + listExpr.items[1], + localIndexFromExpr); + + return + [ + .. rightOps, + .. leftOps, + new StackInstruction(StackInstructionKind.Equal_Binary) + ]; + } + } + + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Equal_List) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Head( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.KernelApplication innerKernelApp && innerKernelApp.Function is "skip") + { + if (innerKernelApp.Input is Expression.List skipList && skipList.items.Count is 2) + { + var sourceOps = + CompileExpressionTransitive( + skipList.items[1], + localIndexFromExpr); + + if (skipList.items[0] is Expression.Literal literalExpr) + { + if (PineValueAsInteger.SignedIntegerFromValueRelaxed(literalExpr.Value).IsOkOrNullable() is { } skipCount) + { + return + [ + .. sourceOps, + new StackInstruction(StackInstructionKind.Skip_Head_Const, SkipCount: (int)skipCount) + ]; + } + } + + var skipCountOps = + CompileExpressionTransitive( + skipList.items[0], + localIndexFromExpr); + + return + [ + .. sourceOps, + .. skipCountOps, + new StackInstruction(StackInstructionKind.Skip_Head_Var) + ]; + } + } + + var inputOps = + CompileExpressionTransitive( + input, + localIndexFromExpr); + + return + [.. inputOps, + new StackInstruction(StackInstructionKind.Head_Generic) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Skip( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr && listExpr.items.Count is 2) + { + var skipCountOps = + CompileExpressionTransitive( + listExpr.items[0], + localIndexFromExpr); + + var sourceOps = + CompileExpressionTransitive( + listExpr.items[1], + localIndexFromExpr); + + return + [ + .. sourceOps, + .. skipCountOps, + new StackInstruction(StackInstructionKind.Skip_Binary) + ]; + } + + var inputOps = + CompileExpressionTransitive( + input, + localIndexFromExpr); + + return + [.. inputOps, + new StackInstruction(StackInstructionKind.Skip_Generic) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Take( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr && listExpr.items.Count is 2) + { + var takeCountValueExpr = listExpr.items[0]; + + var takeCountOps = + CompileExpressionTransitive( + takeCountValueExpr, + localIndexFromExpr); + + if (listExpr.items[1] is Expression.KernelApplication sourceKernelApp && + sourceKernelApp.Function is "skip") + { + if (sourceKernelApp.Input is Expression.List skipList && skipList.items.Count is 2) + { + var skipCountOps = + CompileExpressionTransitive( + skipList.items[0], + localIndexFromExpr); + + var sourceOps = + CompileExpressionTransitive( + skipList.items[1], + localIndexFromExpr); + + /* + * Earlier reduction pass should already have reduced the contents in take to + * literal at this point, if at all possible, so don't reach for more general eval here. + */ + if (takeCountValueExpr is Expression.Literal takeCountLiteral) + { + if (KernelFunction.SignedIntegerFromValueRelaxed(takeCountLiteral.Value) is { } takeCount) + { + return + [ + .. sourceOps, + .. skipCountOps, + new StackInstruction( + StackInstructionKind.Slice_Skip_Var_Take_Const, + TakeCount: (int)takeCount) + ]; + } + } + + return + [ + .. sourceOps, + .. skipCountOps, + .. takeCountOps, + new StackInstruction(StackInstructionKind.Slice_Skip_Var_Take_Var) + ]; + } + } + + { + var sourceOps = + CompileExpressionTransitive( + listExpr.items[1], + localIndexFromExpr); + + return + [ + .. sourceOps, + .. takeCountOps, + new StackInstruction(StackInstructionKind.Take_Binary) + ]; + } + } + + var inputOps = + CompileExpressionTransitive( + input, + localIndexFromExpr); + + return + [ + .. inputOps, + new StackInstruction(StackInstructionKind.Take_Generic) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Concat( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + var concatOps = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var item = listExpr.items[i]; + + var itemOps = + CompileExpressionTransitive( + item, + localIndexFromExpr); + + concatOps.AddRange(itemOps); + + if (0 < i) + { + concatOps.Add(new StackInstruction(StackInstructionKind.Concat_Binary)); + } + } + + return concatOps; + } + else + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Concat_List) + ]; + } + } + + public static IReadOnlyList CompileKernelApplication_Reverse( + Expression input, + Func localIndexFromExpr) + { + + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Reverse) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Int_Add( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 0) + { + return + [ + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValueAsInteger.ValueFromSignedInteger(0)) + ]; + } + + var addOps = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var item = listExpr.items[i]; + + var itemOps = + CompileExpressionTransitive( + item, + localIndexFromExpr); + + addOps.AddRange(itemOps); + + if (0 < i) + { + addOps.Add(new StackInstruction(StackInstructionKind.Int_Add_Binary)); + } + } + + return addOps; + } + else + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Int_Add_List) + ]; + } + } + + public static IReadOnlyList CompileKernelApplication_Int_Mul( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 0) + { + return + [ + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValueAsInteger.ValueFromSignedInteger(1)) + ]; + } + + var mulOps = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var item = listExpr.items[i]; + + var itemOps = + CompileExpressionTransitive( + item, + localIndexFromExpr); + + mulOps.AddRange(itemOps); + + if (0 < i) + { + mulOps.Add(new StackInstruction(StackInstructionKind.Int_Mul_Binary)); + } + } + + return mulOps; + } + else + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Int_Mul_List) + ]; + } + } + + public static IReadOnlyList CompileKernelApplication_Int_Is_Sorted_Asc( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 0 || listExpr.items.Count is 1) + { + return + [ + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineVMValues.TrueValue) + ]; + } + + if (listExpr.items.Count is 2) + { + var leftOps = + CompileExpressionTransitive( + listExpr.items[0], + localIndexFromExpr); + + var rightOps = + CompileExpressionTransitive( + listExpr.items[1], + localIndexFromExpr); + + return + [ + .. leftOps, + .. rightOps, + new StackInstruction(StackInstructionKind.Int_Less_Than_Or_Equal_Binary) + ]; + } + } + + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Int_Is_Sorted_Asc_List) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Bit_And( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 0) + { + return + [ + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyBlob) + ]; + } + + var andOps = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var item = listExpr.items[i]; + + var itemOps = + CompileExpressionTransitive( + item, + localIndexFromExpr); + + andOps.AddRange(itemOps); + + if (0 < i) + { + andOps.Add(new StackInstruction(StackInstructionKind.Bit_And_Binary)); + } + } + + return andOps; + } + else + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Bit_And_List) + ]; + } + } + + public static IReadOnlyList CompileKernelApplication_Bit_Or( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 0) + { + return + [ + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyBlob) + ]; + } + + var orOps = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var item = listExpr.items[i]; + + var itemOps = + CompileExpressionTransitive( + item, + localIndexFromExpr); + + orOps.AddRange(itemOps); + + if (0 < i) + { + orOps.Add(new StackInstruction(StackInstructionKind.Bit_Or_Binary)); + } + } + + return orOps; + } + else + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Bit_Or_List) + ]; + } + } + + public static IReadOnlyList CompileKernelApplication_Bit_Xor( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr) + { + if (listExpr.items.Count is 0) + { + return + [ + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyBlob) + ]; + } + + var xorOps = new List(); + + for (var i = 0; i < listExpr.items.Count; ++i) + { + var item = listExpr.items[i]; + + var itemOps = + CompileExpressionTransitive( + item, + localIndexFromExpr); + + xorOps.AddRange(itemOps); + + if (0 < i) + { + xorOps.Add(new StackInstruction(StackInstructionKind.Bit_Xor_Binary)); + } + } + + return xorOps; + } + else + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Bit_Xor_List) + ]; + } + } + + public static IReadOnlyList CompileKernelApplication_Bit_Not( + Expression input, + Func localIndexFromExpr) + { + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Bit_Not) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Bit_Shift_Left( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr && listExpr.items.Count is 2) + { + var shiftCountOps = + CompileExpressionTransitive( + listExpr.items[0], + localIndexFromExpr); + + var sourceOps = + CompileExpressionTransitive( + listExpr.items[1], + localIndexFromExpr); + + return + [ + .. sourceOps, + .. shiftCountOps, + new StackInstruction(StackInstructionKind.Bit_Shift_Left_Var) + ]; + } + + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Bit_Shift_Left_List) + ]; + } + + public static IReadOnlyList CompileKernelApplication_Bit_Shift_Right( + Expression input, + Func localIndexFromExpr) + { + if (input is Expression.List listExpr && listExpr.items.Count is 2) + { + var shiftCountOps = + CompileExpressionTransitive( + listExpr.items[0], + localIndexFromExpr); + + var sourceOps = + CompileExpressionTransitive( + listExpr.items[1], + localIndexFromExpr); + + return + [ + .. sourceOps, + .. shiftCountOps, + new StackInstruction(StackInstructionKind.Bit_Shift_Right_Var) + ]; + } + + return + [ + .. CompileExpressionTransitive(input, localIndexFromExpr), + new StackInstruction(StackInstructionKind.Bit_Shift_Right_List) + ]; + } +} diff --git a/implement/pine/PineVM/StackInstruction.cs b/implement/pine/PineVM/StackInstruction.cs new file mode 100644 index 00000000..0d9dd23c --- /dev/null +++ b/implement/pine/PineVM/StackInstruction.cs @@ -0,0 +1,244 @@ +using Pine.Core; + +namespace Pine.PineVM; + +/* + * As in the Pine language, the value representing True for conditional jumps is (Blob [4]). + * + * As in the Pine language, operations expecting integers will return an empty list if one + * of the operand values is not a valid encoding of an integer. + * */ + +/// +/// On execution of an instruction, the arguments are popped from the stack and the result is pushed to the stack. +/// +/// For more information on how these instructions are interpreted, see +/// +public enum StackInstructionKind +{ + /// + /// Push the literal from to the stack. + /// + Push_Literal, + + Push_Environment, + + /// + /// Pop the top value from the stack and store it in the local at index . + /// + Local_Set, + + /// + /// Load the local at index and push it to the stack. + /// + Local_Get, + + /// + /// Drop the top value from the stack. + /// + Pop, + + /// + /// Gets the length of the top value from the stack as integer. + /// + Length, + + /// + /// Concatenates the items in the list from the top value on the stack. + /// + Concat_List, + + /// + /// Concatenates the top value from the stack and the second value from the stack. + /// + /// If the two arguments are not both lists or both blobs, returns only the top value. + /// + Concat_Binary, + + /// + /// Slice the third value from the stack, + /// using the second value as the start index and the top value as the length. + /// + Slice_Skip_Var_Take_Var, + + /// + /// Slice the second value from the stack, + /// using the top value as the start index and the as the length. + /// + Slice_Skip_Var_Take_Const, + + Skip_Generic, + + Skip_Binary, + + Take_Generic, + + Take_Binary, + + /// + /// From the second value on the stack, get the element at the index of the top value on the stack. + /// + Skip_Head_Var, + + /// + /// From the top value on the stack, get the element at the index of . + /// + Skip_Head_Const, + + Head_Generic, + + Reverse, + + /// + /// Builds a list from the top values on the stack. + /// + BuildList, + + /// + /// Check if the top two values on the stack are equal. + /// + Equal_Binary, + + Not_Equal_Binary, + + /// + /// Check if all items in the list from the top value on the stack are equal. + /// + Equal_List, + + /// + /// Negate the top value on the stack, works for both integers and booleans. + /// + Negate, + + /// + /// 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_Less_Than_Binary, + + Int_Less_Than_Or_Equal_Binary, + + /// + /// Jump to the offset from if the top value on the stack is true. + /// + Jump_If_True_Const, + + /// + /// Unconditional jump to the offset from . + /// + Jump_Const, + + /// + /// The top value on the stack becomes the result of the function. + /// + Return, + + /// + /// Tries to parse the top value from the stack as a Pine expression and evaluates it using + /// the second value on the stack as the environment. + /// + Parse_And_Eval, + + /// + /// Add the top two values on the stack. + /// + Int_Add_Binary, + + /// + /// Add all items in the list from the top value from the stack. + /// + Int_Add_List, + + /// + /// Subtract the top value from the second value on the stack. + /// + Int_Sub_Binary, + + /// + /// Multiply the top two values on the stack. + /// + Int_Mul_Binary, + + /// + /// Multiply all items in the list from the top value from the stack. + /// + Int_Mul_List, + + Bit_And_List, + + Bit_And_Binary, + + Bit_Or_List, + + Bit_Or_Binary, + + Bit_Xor_List, + + Bit_Xor_Binary, + + Bit_Not, + + Bit_Shift_Left_Var, + + Bit_Shift_Left_Const, + + Bit_Shift_Left_List, + + Bit_Shift_Right_Var, + + Bit_Shift_Right_Const, + + Bit_Shift_Right_List, +} + +/// +/// Represents a single instruction for the Pine stack-based virtual machine. +/// Depending on the , this instruction may consume zero or more values +/// from the evaluation stack, optionally produce a new value to push onto the stack, +/// or manipulate local variables and control flow. +/// +/// +/// The fields , , , +/// , , and may or may not +/// be relevant depending on the specific . For instance, +/// uses , +/// uses , +/// and jump instructions use . +/// Refer to the documentation on each value for more details. +/// +/// The enum value specifying which operation to perform. +/// An optional literal value (used by push or parse instructions). +/// An optional index for reading/writing to a local variable. +/// An optional skip count used in slice/skip operations. +/// An optional take count used in slice/build operations. +/// An optional offset for conditional or unconditional jumps. +/// An optional amount to shift bits, used by bitwise shift instructions. +public record StackInstruction( + StackInstructionKind Kind, + PineValue? Literal = null, + int? LocalIndex = null, + int? SkipCount = null, + int? TakeCount = null, + int? JumpOffset = null, + int? ShiftCount = null) +{ + public static readonly StackInstruction PushEnvironment = + new(StackInstructionKind.Push_Environment); + + public static readonly StackInstruction Return = + new(StackInstructionKind.Return); + + public static StackInstruction Jump_Unconditional(int offset) => + new(StackInstructionKind.Jump_Const, JumpOffset: offset); + + /// + /// Creates a new instruction to jump to the specified offset if the top value on the stack is true. + /// + public static StackInstruction Jump_If_True(int offset) => + new(StackInstructionKind.Jump_If_True_Const, JumpOffset: offset); +} + + diff --git a/implement/test-elm-time/PGOTests.cs b/implement/test-elm-time/PGOTests.cs index ba0b9f39..6ce5fffb 100644 --- a/implement/test-elm-time/PGOTests.cs +++ b/implement/test-elm-time/PGOTests.cs @@ -27,14 +27,14 @@ module Test exposing (..) usingRecordAccess record fieldId = case fieldId of - 0 -> - record.alfa + 0 -> + record.alfa - 1 -> - record.delta + 1 -> + record.delta - _ -> - record.other + _ -> + record.other """; @@ -401,7 +401,7 @@ static long ReportsAverageInstructionCount(IReadOnlyList - { record | alfa = fieldValue } + 0 -> + { record | alfa = fieldValue } - 1 -> - { record | delta = fieldValue } + 1 -> + { record | delta = fieldValue } - _ -> - { record | other = fieldValue } + _ -> + { record | other = fieldValue } """; @@ -1001,7 +1001,7 @@ static long ReportsAverageInstructionCount(IReadOnlyList - Tuple.first + 0 -> + Tuple.first - 1 -> - Tuple.first >> String.repeat 3 + 1 -> + Tuple.first >> String.repeat 3 - _ -> - Tuple.first >> String.reverse + _ -> + Tuple.first >> String.reverse in listMap function list @@ -1445,17 +1445,17 @@ import Dict usingDictFold dict functionId = case functionId of - 0 -> - Dict.foldl - (\key value list -> ( value, key ) :: list) - [] - dict + 0 -> + Dict.foldl + (\key value list -> ( value, key ) :: list) + [] + dict - _ -> - Dict.foldl - (\key value list -> ( value, key ) :: list) - [] - dict + _ -> + Dict.foldl + (\key value list -> ( value, key ) :: list) + [] + dict """; diff --git a/implement/test-elm-time/PineVMTests.cs b/implement/test-elm-time/PineVMTests.cs index 0be2a3f9..ba732aeb 100644 --- a/implement/test-elm-time/PineVMTests.cs +++ b/implement/test-elm-time/PineVMTests.cs @@ -246,8 +246,11 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval(new Expression.Literal(PineValue.EmptyBlob)), - StackInstruction.Return + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValue.EmptyBlob), + + StackInstruction.Return, ]) }, @@ -263,12 +266,18 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - Expression.ListInstance( - [ - new Expression.Literal(PineValue.EmptyList), - new Expression.Literal(PineValue.EmptyBlob), - ])), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValue.EmptyList), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValue.EmptyBlob), + + new StackInstruction( + StackInstructionKind.BuildList, + TakeCount: 2), + StackInstruction.Return ]) }, @@ -306,24 +315,24 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.KernelApplication - ( - function: "skip", - input: Expression.ListInstance( - [ - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(1)), - Expression.EnvironmentInstance, - ]) - )), - StackInstruction.Eval( - Expression.ListInstance( - [ - new Expression.Literal(PineValue.EmptyList), - new Expression.StackReferenceExpression(offset: -1), - new Expression.Literal(PineValue.EmptyBlob), - new Expression.StackReferenceExpression(offset: -1), - ])), + StackInstruction.PushEnvironment, + + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValueAsInteger.ValueFromSignedInteger(1)), + + new StackInstruction(StackInstructionKind.Skip_Binary), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyList), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyBlob), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 4), + StackInstruction.Return ]) }, @@ -376,30 +385,28 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.KernelApplication - ( - function: "concat", - input: - new Expression.KernelApplication - ( - function: "skip", - input: - Expression.ListInstance( - [ - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13)), - Expression.EnvironmentInstance, - ]) - ) - )), - StackInstruction.Eval( - Expression.ListInstance( - [ - new Expression.Literal(PineValue.EmptyList), - new Expression.StackReferenceExpression(offset: -1), - new Expression.Literal(PineValue.EmptyBlob), - new Expression.StackReferenceExpression(offset: -1), - ])), + StackInstruction.PushEnvironment, + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), + + new StackInstruction(StackInstructionKind.Skip_Binary), + + new StackInstruction(StackInstructionKind.Concat_List), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 4), + + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyList), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 4), + + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyBlob), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 4), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 4), + StackInstruction.Return ]) }, @@ -415,10 +422,12 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: Expression.EnvironmentInstance, - environment: Expression.EnvironmentInstance)), + StackInstruction.PushEnvironment, + + StackInstruction.PushEnvironment, + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + StackInstruction.Return ]) }, @@ -438,16 +447,16 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: Expression.EnvironmentInstance, - environment: Expression.EnvironmentInstance)), - StackInstruction.Eval( - Expression.ListInstance( - [ - new Expression.StackReferenceExpression(offset: -1), - new Expression.Literal(PineValue.EmptyBlob), - ])), + StackInstruction.PushEnvironment, + + StackInstruction.PushEnvironment, + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + + new StackInstruction(StackInstructionKind.Push_Literal, Literal: PineValue.EmptyBlob), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 2), + StackInstruction.Return ]) }, @@ -475,27 +484,32 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11)), - environment: - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13)))), - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: Expression.EnvironmentInstance, - environment: - Expression.ListInstance( - [ - new Expression.StackReferenceExpression(offset: -1), - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17)), - ]))), - StackInstruction.Eval( - Expression.ListInstance( - [ - new Expression.StackReferenceExpression(offset: -1), - new Expression.Literal(PineValue.EmptyBlob), - ])), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(17)), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 2), + + StackInstruction.PushEnvironment, + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValue.EmptyBlob), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 2), + StackInstruction.Return ]) }, @@ -515,23 +529,27 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + StackInstruction.Jump_If_True(offset : 2), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17))), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(17)), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13))), - new StackInstruction.CopyLastAssignedInstruction(), + StackInstruction.Jump_Unconditional(offset: 2), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), - StackInstruction.Return, + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 3), + + StackInstruction.Return ]) }, @@ -554,6 +572,7 @@ public void Compile_stack_frame_instructions() Expression.EnvironmentInstance, ]) ), + environment: Expression.EnvironmentInstance), falseBranch: new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17))), @@ -561,37 +580,38 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval(new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset:2), + StackInstruction.Jump_If_True(offset : 2), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17))), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(17)), - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: - new Expression.KernelApplication - ( - function: "skip", - input: Expression.ListInstance( - [ - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13)), - Expression.EnvironmentInstance, - ]) - ), - Expression.EnvironmentInstance)), + StackInstruction.Jump_Unconditional(6), - new StackInstruction.CopyLastAssignedInstruction(), + StackInstruction.PushEnvironment, - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), - StackInstruction.Return, + StackInstruction.PushEnvironment, + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), + + new StackInstruction(StackInstructionKind.Skip_Binary), + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 3), + + StackInstruction.Return ]) }, - new { expression = @@ -602,6 +622,7 @@ public void Compile_stack_frame_instructions() Expression.ConditionalInstance( condition: new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11)), + trueBranch: new Expression.ParseAndEval( encoded: @@ -614,7 +635,10 @@ public void Compile_stack_frame_instructions() Expression.EnvironmentInstance, ]) ), + + environment: Expression.EnvironmentInstance), + falseBranch: new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17))) ]), @@ -622,36 +646,41 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval(new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + StackInstruction.Jump_If_True(offset : 2), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17))), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(17)), - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: - new Expression.KernelApplication - ( - function: "skip", - input: Expression.ListInstance( - [ - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13)), - Expression.EnvironmentInstance, - ]) - ), - Expression.EnvironmentInstance)), + StackInstruction.Jump_Unconditional(6), - new StackInstruction.CopyLastAssignedInstruction(), - StackInstruction.Eval(Expression.ListInstance( - [ - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(47)), - new Expression.StackReferenceExpression(offset: -1), - ])), - StackInstruction.Return, + StackInstruction.PushEnvironment, + + StackInstruction.PushEnvironment, + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), + + new StackInstruction(StackInstructionKind.Skip_Binary), + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 3), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(47)), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 2), + + StackInstruction.Return ]) }, @@ -678,45 +707,45 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval(new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 8), + StackInstruction.Jump_If_True(offset : 10), - // Outer if-false: - StackInstruction.Eval(new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + StackInstruction.Jump_If_True(offset : 4), - // Inner if-false: - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: Expression.EnvironmentInstance, - environment:Expression.EnvironmentInstance)), - StackInstruction.Jump(offset: 2), + StackInstruction.PushEnvironment, - // Inner if-true - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(21))), + StackInstruction.PushEnvironment, - // Copy from inner result to outer conditional - new StackInstruction.CopyLastAssignedInstruction(), + new StackInstruction(StackInstructionKind.Parse_And_Eval), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + StackInstruction.Jump_Unconditional(2), - // End outer if-false - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(21)), - // Outer if-true - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(23))), + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 7), - new StackInstruction.CopyLastAssignedInstruction(), + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 7), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + StackInstruction.Jump_Unconditional(2), - StackInstruction.Return, + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(23)), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 11), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 11), + + StackInstruction.Return ]) }, @@ -754,47 +783,57 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval(new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + StackInstruction.Jump_If_True(offset : 2), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(17))), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(17)), - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(41)), - Expression.EnvironmentInstance)), + StackInstruction.Jump_Unconditional(4), - new StackInstruction.CopyLastAssignedInstruction(), + StackInstruction.PushEnvironment, - StackInstruction.Eval(new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(41)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + new StackInstruction(StackInstructionKind.Parse_And_Eval), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(19))), - StackInstruction.Jump(offset: 2), + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 3), - StackInstruction.Eval( - new Expression.ParseAndEval( - encoded: - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(43)), - Expression.EnvironmentInstance)), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), - new StackInstruction.CopyLastAssignedInstruction(), + StackInstruction.Jump_If_True(offset: 2), - StackInstruction.Eval(Expression.ListInstance( - [ - new Expression.StackReferenceExpression(offset: -7), - new Expression.StackReferenceExpression(offset: -1), - ])), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(19)), - StackInstruction.Return, + StackInstruction.Jump_Unconditional(4), + + StackInstruction.PushEnvironment, + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(43)), + + new StackInstruction(StackInstructionKind.Parse_And_Eval), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 12), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 3), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 12), + + new StackInstruction(StackInstructionKind.BuildList, TakeCount: 2), + + StackInstruction.Return ]) }, @@ -819,37 +858,43 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(11))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(11)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 8), + StackInstruction.Jump_If_True(offset : 8), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(21))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(21)), - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + StackInstruction.Jump_If_True(offset : 2), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(27))), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(27)), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(23))), + StackInstruction.Jump_Unconditional(2), - new StackInstruction.CopyLastAssignedInstruction(), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(23)), - StackInstruction.Jump(offset: 2), + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 5), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13))), - new StackInstruction.CopyLastAssignedInstruction(), + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 5), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + StackInstruction.Jump_Unconditional(2), - StackInstruction.Return, + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 9), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 9), + + StackInstruction.Return ]) }, @@ -878,10 +923,12 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.KernelApplications_Skip_Head_Path( - SkipCounts: (int[]) [17], - Argument:Expression.EnvironmentInstance)), + StackInstruction.PushEnvironment, + + new StackInstruction( + StackInstructionKind.Skip_Head_Const, + SkipCount: 17), + StackInstruction.Return ]) }, @@ -929,17 +976,24 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.KernelApplications_Skip_Head_Path( - SkipCounts: (int[])[17], - Argument: - Expression.ListInstance( - [ - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(21)), - new Expression.KernelApplications_Skip_Head_Path( - SkipCounts: (int[]) [23], - Argument: Expression.EnvironmentInstance) - ]))), + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(21)), + + StackInstruction.PushEnvironment, + + new StackInstruction( + StackInstructionKind.Skip_Head_Const, + SkipCount: 23), + + new StackInstruction( + StackInstructionKind.BuildList, + TakeCount: 2), + + new StackInstruction( + StackInstructionKind.Skip_Head_Const, + SkipCount: 17), + StackInstruction.Return ]) }, @@ -999,54 +1053,61 @@ public void Compile_stack_frame_instructions() expected = new PineVM.StackFrameInstructions( [ - StackInstruction.Eval( - new Expression.KernelApplication - ( - function: nameof(KernelFunction.int_mul), - input: - Expression.ListInstance( - [ - Expression.EnvironmentInstance, - Expression.EnvironmentInstance, - Expression.EnvironmentInstance, - ]) - )), + StackInstruction.PushEnvironment, - StackInstruction.Eval( - Expression.ListInstance( - [ - new Expression.StackReferenceExpression(offset: -1), - new Expression.StackReferenceExpression(offset: -1), - ])), + StackInstruction.PushEnvironment, - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 8), + new StackInstruction( + StackInstructionKind.Int_Mul_Binary), - StackInstruction.Eval( - new Expression.StackReferenceExpression(offset: -3)), + StackInstruction.PushEnvironment, - new StackInstruction.ConditionalJumpInstruction( - TrueBranchOffset: 2), + new StackInstruction( + StackInstructionKind.Int_Mul_Binary), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(27))), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.Local_Set, + LocalIndex: 5), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(23))), + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 5), - new StackInstruction.CopyLastAssignedInstruction(), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 5), - StackInstruction.Jump(offset: 2), + new StackInstruction( + StackInstructionKind.BuildList, + TakeCount: 2), - StackInstruction.Eval( - new Expression.Literal(PineValueAsInteger.ValueFromSignedInteger(13))), - new StackInstruction.CopyLastAssignedInstruction(), + StackInstruction.Jump_If_True(offset: 8), - StackInstruction.Eval(new Expression.StackReferenceExpression(offset: -1)), + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 5), - StackInstruction.Return, + StackInstruction.Jump_If_True(offset: 2), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(27)), + + StackInstruction.Jump_Unconditional(2), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(23)), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 13), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 13), + + StackInstruction.Jump_Unconditional(offset: 2), + + new StackInstruction( + StackInstructionKind.Push_Literal, + Literal: PineValueAsInteger.ValueFromSignedInteger(13)), + + new StackInstruction(StackInstructionKind.Local_Set, LocalIndex: 17), + + new StackInstruction(StackInstructionKind.Local_Get, LocalIndex: 17), + + StackInstruction.Return ]) }, }; @@ -1055,12 +1116,13 @@ public void Compile_stack_frame_instructions() foreach (var testCase in testCases) { - var compiled = PineVM.CompileExpression( - testCase.expression, - specializations: [], - parseCache, - disableReduction: true, - skipInlining: (_, _) => false); + var compiled = + PineVM.CompileExpression( + testCase.expression, + specializations: [], + parseCache, + disableReduction: true, + skipInlining: (_, _) => false); Assert.AreEqual( testCase.expected.Instructions.Count,