From 83000a665a289c6de2f027b52baac71233b48f82 Mon Sep 17 00:00:00 2001 From: Brenton Farmer Date: Mon, 2 Dec 2024 08:13:35 -0800 Subject: [PATCH 1/7] WIP benchmarks and tests --- .../Visitors/StructuralReductionVisitor.cs | 13 +- .../Visitors/SubexpressionCachingVisitor.cs | 215 +++++++---- .../Transformation/NodeExpression.cs | 23 +- .../Transformation/StateMachineBuilder.cs | 18 +- .../Transitions/AwaitResultTransition.cs | 29 +- .../Transitions/AwaitTransition.cs | 31 +- .../Transitions/ConditionalTransition.cs | 23 +- .../Transitions/FinalTransition.cs | 11 +- .../Transitions/GotoTransition.cs | 4 +- .../Transitions/LoopTransition.cs | 2 +- .../Transitions/SwitchTransition.cs | 28 +- .../Transformation/Transitions/Transition.cs | 38 +- .../Transitions/TryCatchTransition.cs | 36 +- .../OptimizerBenchmarks.cs | 358 ++++++++++++++++++ .../Hyperbee.Expressions.Benchmark/Program.cs | 34 ++ .../Optimizers/InliningOptimizerTests.cs | 42 ++ .../OperatorReductionOptimizerTests.cs | 48 +++ .../StructuralReductionOptimizerTests.cs | 52 +++ .../SubexpressionCachingOptimizerTests.cs | 49 +++ 19 files changed, 786 insertions(+), 268 deletions(-) create mode 100644 test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs index f970b47..56b4d9f 100644 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs +++ b/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs @@ -14,7 +14,7 @@ public Expression Transform( Expression expression ) // Visit BlockExpression, handling block flattening and reduction protected override Expression VisitBlock( BlockExpression node ) { - var variables = new List(); + var variables = new List( node.Variables ); var expressions = new List(); foreach ( var expr in node.Expressions ) @@ -37,10 +37,17 @@ protected override Expression VisitBlock( BlockExpression node ) } } - // Remove single-use blocks - return expressions.Count == 1 ? expressions[0] : Expression.Block( variables, expressions ); + // Return the single expression directly if there are no variables + if ( variables.Count == 0 && expressions.Count == 1 ) + { + return expressions[0]; + } + + // Construct a new block only if there are variables or multiple expressions + return Expression.Block( variables, expressions ); } + // Visit GotoExpression, eliminating redundant Goto expressions protected override Expression VisitGoto( GotoExpression node ) { diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs index 74b1a49..26a95e0 100644 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs +++ b/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs @@ -1,50 +1,18 @@ using System.Linq.Expressions; +using System.Reflection; using Hyperbee.Expressions.Visitors; namespace Hyperbee.Expressions.Optimizers.Visitors; -// SubexpressionCachingVisitor: Subexpression Caching -// -// This optimizer performs subexpression caching to reduce repeated computation in complex expressions. -// By identifying and reusing common subexpressions, it improves execution efficiency, especially in cases -// where identical subexpressions are evaluated multiple times within an expression tree. -// -// This optimizer works in multiple phases: -// * Fingerprinting: Each expression is traversed to create a unique "fingerprint" for subexpressions. -// * Deferred Caching: Subexpressions deemed "complex enough" are queued for caching. This ensures -// that only the necessary subexpressions are cached and that simple expressions remain unchanged. -// * Block Wrapping: Cached expressions are assigned to variables in a `BlockExpression`, which groups -// these assignments and ensures variables are reused wherever possible. -// -// Example: -// -// Before Optimization: -// -// .Lambda #Lambda1 { -// 5 * (3 + 2) + 5 * (3 + 2) -// } -// -// After Optimization: -// -// .Lambda #Lambda1 { -// .Block(System.Int32 $cacheVar) { -// $cacheVar = 5 * (3 + 2); -// $cacheVar + $cacheVar -// } -// } -// -// In this example, the optimizer identifies the subexpression `5 * (3 + 2)` as a repeated, cacheable part. -// It creates a variable `$cacheVar` to hold the computed value of `5 * (3 + 2)`, and replaces occurrences -// of this subexpression with `$cacheVar` in the resulting `BlockExpression`. This optimization reduces -// redundant calculations, resulting in a more efficient expression execution. - public class SubexpressionCachingVisitor : ExpressionVisitor, IExpressionTransformer { - private readonly Dictionary _fingerprintCache = new( new ByteArrayComparer() ); - private readonly Dictionary _cacheVariables = new( new ByteArrayComparer() ); + private readonly Dictionary _fingerprintCache = new(new ByteArrayComparer()); + private readonly Dictionary _cacheVariables = new(new ByteArrayComparer()); private readonly Queue<(Expression Original, ParameterExpression Variable)> _deferredReplacements = new(); - private readonly ExpressionFingerprinter _fingerprinter = new(); + private readonly CacheableHelper _cacheHelper = new(); + + private int _cacheVariableCounter; public int Priority => PriorityGroup.ExpressionReductionAndCaching + 70; @@ -61,27 +29,28 @@ public Expression Transform( Expression expression ) var blockExpressions = new List(); var variables = new List(); + if ( visitedExpression is BlockExpression originalBlock ) + { + variables.AddRange( originalBlock.Variables ); + blockExpressions.AddRange( originalBlock.Expressions ); + } + else + { + blockExpressions.Add( visitedExpression ); + } + while ( _deferredReplacements.Count > 0 ) { var (original, cacheVariable) = _deferredReplacements.Dequeue(); - if ( variables.Contains( cacheVariable ) ) + if ( !variables.Contains( cacheVariable ) ) { - continue; + variables.Add( cacheVariable ); } - variables.Add( cacheVariable ); - blockExpressions.Add( Expression.Assign( cacheVariable, original ) ); + blockExpressions.Insert( 0, Expression.Assign( cacheVariable, original ) ); } - if ( visitedExpression is LambdaExpression lambda ) - { - blockExpressions.Add( lambda.Body ); - var lambdaResult = Expression.Lambda( lambda.Type, Expression.Block( variables, blockExpressions ), lambda.Parameters ); - return lambdaResult; - } - - blockExpressions.Add( visitedExpression ); return Expression.Block( variables, blockExpressions ); } @@ -93,7 +62,7 @@ public override Expression Visit( Expression node ) private Expression ResolveExpression( Expression original, Expression visitedNode ) { - if ( !ReferenceEquals( original, visitedNode ) || !IsComplexEnoughToCache( visitedNode ) ) + if ( !_cacheHelper.IsCacheable( original ) ) { return visitedNode; } @@ -111,39 +80,13 @@ private Expression ResolveExpression( Expression original, Expression visitedNod return cacheVariable; } - cacheVariable = Expression.Variable( cachedNode.Type, "cacheVar" ); + cacheVariable = Expression.Variable( cachedNode.Type, $"cacheVar{_cacheVariableCounter++}" ); _cacheVariables[fingerprint] = cacheVariable; _deferredReplacements.Enqueue( (visitedNode, cacheVariable) ); return cacheVariable; } - private static bool IsComplexEnoughToCache( Expression node ) - { - return node switch - { - BinaryExpression binary => - (binary.Left is not ConstantExpression || binary.Right is not ConstantExpression) && - (IsComplexEnoughToCache( binary.Left ) || - IsComplexEnoughToCache( binary.Right ) || - binary.NodeType is - ExpressionType.Add or - ExpressionType.Multiply or - ExpressionType.Divide or - ExpressionType.Subtract - ), - - MethodCallExpression or MemberExpression or InvocationExpression => true, - UnaryExpression unary => unary.Operand is not ConstantExpression && IsComplexEnoughToCache( unary.Operand ), - BlockExpression block => block.Expressions.Count > 1, - ConditionalExpression or NewExpression or NewArrayExpression => true, - LambdaExpression lambda => lambda.Parameters.Count > 0 || IsComplexEnoughToCache( lambda.Body ), - MemberInitExpression or ListInitExpression or IndexExpression => true, - ConstantExpression => false, - _ => false - }; - } - private class ByteArrayComparer : IEqualityComparer { public bool Equals( byte[] x, byte[] y ) @@ -151,10 +94,7 @@ public bool Equals( byte[] x, byte[] y ) if ( ReferenceEquals( x, y ) ) return true; - if ( x == null || y == null ) - return false; - - if ( x.Length != y.Length ) + if ( x == null || y == null || x.Length != y.Length ) return false; for ( var i = 0; i < x.Length; i++ ) @@ -170,12 +110,119 @@ public int GetHashCode( byte[] bytes ) { var hash = 17; - foreach ( var x in bytes ) + foreach ( var b in bytes ) { - hash = hash * 31 + x; + hash = hash * 31 + b; } return hash; } } + + protected class CacheableHelper + { + private readonly ImmutabilityVisitor _visitor = new(); + + public bool IsCacheable( Expression expression ) + { + return IsImmutable( expression ) && IsComplexEnoughToCache( expression ); + } + + private bool IsImmutable( Expression expression ) + { + return _visitor.IsImmutable( expression ); + } + + public static bool IsComplexEnoughToCache( Expression node ) + { + return node switch + { + BinaryExpression binary => + (binary.Left is not ConstantExpression || binary.Right is not ConstantExpression) && + (IsComplexEnoughToCache( binary.Left ) || + IsComplexEnoughToCache( binary.Right ) || + binary.NodeType is + ExpressionType.Add or + ExpressionType.Multiply or + ExpressionType.Divide or + ExpressionType.Subtract), + + MethodCallExpression or MemberExpression or InvocationExpression => true, + UnaryExpression unary => IsComplexEnoughToCache( unary.Operand ), + ConditionalExpression or NewExpression or NewArrayExpression => true, + LambdaExpression lambda => lambda.Parameters.Count > 0 || IsComplexEnoughToCache( lambda.Body ), + MemberInitExpression or ListInitExpression or IndexExpression => true, + ConstantExpression => false, + _ => false + }; + } + } + + private class ImmutabilityVisitor : ExpressionVisitor + { + private static readonly Dictionary ImmutableTypeCache = new(); + + private bool _isImmutable; + + public bool IsImmutable( Expression node ) + { + _isImmutable = true; + Visit( node ); + return _isImmutable; + } + + protected override Expression VisitMember( MemberExpression node ) + { + if ( !IsImmutableType( node.Type ) ) + { + _isImmutable = false; + } + + return base.VisitMember( node ); + } + + protected override Expression VisitParameter( ParameterExpression node ) + { + if ( !IsImmutableType( node.Type ) ) + { + _isImmutable = false; + } + + return base.VisitParameter( node ); + } + + private static bool IsImmutableType( Type type ) + { + if ( ImmutableTypeCache.TryGetValue( type, out var isImmutable ) ) + { + return isImmutable; + } + + if ( type == typeof(Guid) || typeof(IConvertible).IsAssignableFrom( type ) ) + { + return ImmutableTypeCache[type] = true; + } + + if ( type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) ) + { + return ImmutableTypeCache[type] = IsImmutableType( type.GetGenericArguments()[0] ); + } + + if ( type.IsValueType ) + { + return ImmutableTypeCache[type] = type + .GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ) + .All( field => IsImmutableType( field.FieldType ) ); + } + + if ( type.IsClass ) + { + return ImmutableTypeCache[type] = type + .GetProperties() + .All( property => property.CanRead && !property.CanWrite && IsImmutableType( property.PropertyType ) ); + } + + return ImmutableTypeCache[type] = false; + } + } } diff --git a/src/Hyperbee.Expressions/Transformation/NodeExpression.cs b/src/Hyperbee.Expressions/Transformation/NodeExpression.cs index d5b38b3..4222608 100644 --- a/src/Hyperbee.Expressions/Transformation/NodeExpression.cs +++ b/src/Hyperbee.Expressions/Transformation/NodeExpression.cs @@ -39,33 +39,12 @@ public NodeExpression( int stateId, int scopeId, int groupId ) public bool IsNoOp => Expressions.Count == 0 && ResultVariable == null; - protected override Expression VisitChildren( ExpressionVisitor visitor ) - { - return Update( - Expressions.Select( visitor.Visit ).ToList(), - visitor.Visit( ResultValue ), - visitor.Visit( ResultVariable ), - (Transition) visitor.Visit( Transition ) - ); - } - - private Expression Update( List expressions, Expression resultValue, Expression resultVariable, Transition transition ) - { - Expressions = expressions; - ResultValue = resultValue; - ResultVariable = resultVariable; - Transition = transition; - - return this; - } - public override Expression Reduce() { if ( StateMachineSource == null ) throw new InvalidOperationException( $"Reduce requires an {nameof( Transformation.StateMachineSource )} instance." ); - Transition.Parent = this; - return Transition.Reduce(); + return Transition.Reduce( this ); } internal static List Merge( List nodes ) //BF ME - not sure if this is the right place or not diff --git a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs index edc9068..07bac2d 100644 --- a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs +++ b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs @@ -337,7 +337,7 @@ FieldInfo[] fields // Variable Hoisting - HoistVariables( source, fields, stateMachine ); + //HoistVariables( source, fields, stateMachine ); // Assign state-machine source to nodes @@ -359,6 +359,22 @@ FieldInfo[] fields var bodyExpressions = CreateBody( stateField, source ); + // + var fieldMembers = fields + .Select( field => Field( stateMachine, field ) ) + .ToDictionary( x => x.Member.Name ); + + var hoistingVisitor = new HoistingVisitor( fieldMembers ); + + var rbe = new List(); + + foreach ( var node in bodyExpressions ) + { + rbe.Add( hoistingVisitor.Visit( node ) ); + } + + bodyExpressions = rbe; + // Add the final builder result assignment bodyExpressions.AddRange( diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs index 7753ddb..e4709d6 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace Hyperbee.Expressions.Transformation.Transitions; @@ -11,29 +12,7 @@ internal class AwaitResultTransition : Transition internal override NodeExpression FallThroughNode => TargetNode; - protected override Expression VisitChildren( ExpressionVisitor visitor ) - { - return Update( - visitor.Visit( AwaiterVariable ), - visitor.Visit( ResultVariable ) - ); - } - - internal AwaitResultTransition Update( Expression awaiterVariable, Expression resultVariable ) - { - if ( awaiterVariable == AwaiterVariable && resultVariable == ResultVariable ) - return this; - - return new AwaitResultTransition - { - AwaiterVariable = awaiterVariable, - ResultVariable = resultVariable, - TargetNode = TargetNode, - AwaitBinder = AwaitBinder - }; - } - - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return GetExpressions(); @@ -47,7 +26,7 @@ List GetExpressions() if ( ResultVariable == null ) { - var transition = GotoOrFallThrough( Parent.StateOrder, TargetNode ); + var transition = GotoOrFallThrough( parent.StateOrder, TargetNode ); return transition == Empty() ? [getResultCall] @@ -56,7 +35,7 @@ List GetExpressions() return [ Assign( ResultVariable, getResultCall ), - GotoOrFallThrough( Parent.StateOrder, TargetNode ) + GotoOrFallThrough( parent.StateOrder, TargetNode ) ]; } } diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs index 6a0b2bf..d75f0aa 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace Hyperbee.Expressions.Transformation.Transitions; @@ -14,37 +15,13 @@ internal class AwaitTransition : Transition internal override NodeExpression FallThroughNode => CompletionNode; - protected override Expression VisitChildren( ExpressionVisitor visitor ) - { - return Update( - visitor.Visit( Target ), - visitor.Visit( AwaiterVariable ) - ); - } - - internal AwaitTransition Update( Expression target, Expression awaiterVariable ) - { - if ( target == Target && awaiterVariable == AwaiterVariable ) - return this; - - return new AwaitTransition - { - StateId = StateId, - Target = target, - AwaiterVariable = awaiterVariable, - CompletionNode = CompletionNode, - AwaitBinder = AwaitBinder, - ConfigureAwait = ConfigureAwait - }; - } - - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return GetExpressions(); List GetExpressions() { - var resolverSource = Parent.StateMachineSource; + var resolverSource = parent.StateMachineSource; var getAwaiterMethod = AwaitBinder.GetAwaiterMethod; var getAwaiterCall = getAwaiterMethod.IsStatic @@ -78,7 +55,7 @@ List GetExpressions() ) }; - var fallThrough = GotoOrFallThrough( Parent.StateOrder, CompletionNode, true ); + var fallThrough = GotoOrFallThrough( parent.StateOrder, CompletionNode, true ); if ( fallThrough != null ) body.Add( fallThrough ); diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs index 954c14e..513a74b 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace Hyperbee.Expressions.Transformation.Transitions; @@ -10,31 +11,13 @@ internal class ConditionalTransition : Transition internal override NodeExpression FallThroughNode => IfFalse; - protected override Expression VisitChildren( ExpressionVisitor visitor ) - { - return Update( visitor.Visit( Test ) ); - } - - internal ConditionalTransition Update( Expression test ) - { - if ( test == Test ) - return this; - - return new ConditionalTransition - { - Test = test, - IfTrue = IfTrue, - IfFalse = IfFalse - }; - } - - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return [GetExpression()]; Expression GetExpression() { - var fallThrough = GotoOrFallThrough( Parent.StateOrder, IfFalse, true ); + var fallThrough = GotoOrFallThrough( parent.StateOrder, IfFalse, true ); if ( fallThrough == null ) return IfThen( Test, Goto( IfTrue.NodeLabel ) ); diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs index 202a98d..e187fc0 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace Hyperbee.Expressions.Transformation.Transitions; @@ -10,15 +11,15 @@ internal override void Optimize( HashSet references ) { } - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return EmptyBody; } - protected override void AssignResult( List expressions ) + protected override void AssignResult( NodeExpression parent, List expressions ) { - var resultField = Parent.StateMachineSource.ResultField; - var returnValue = Parent.StateMachineSource.ReturnValue; + var resultField = parent.StateMachineSource.ResultField; + var returnValue = parent.StateMachineSource.ReturnValue; if ( returnValue != null ) { @@ -46,7 +47,7 @@ protected override void AssignResult( List expressions ) } expressions.Add( - Assign( resultField, Parent.ResultValue ?? Constant( null, typeof( IVoidResult ) ) ) + Assign( resultField, parent.ResultValue ?? Constant( null, typeof( IVoidResult ) ) ) ); } } diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs index f7351f1..a97da0f 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs @@ -7,9 +7,9 @@ internal class GotoTransition : Transition public NodeExpression TargetNode { get; set; } internal override NodeExpression FallThroughNode => TargetNode; - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { - return [GotoOrFallThrough( Parent.StateOrder, TargetNode )]; + return [GotoOrFallThrough( parent.StateOrder, TargetNode )]; } internal override void Optimize( HashSet references ) diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs index 00388b4..9eabb07 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs @@ -10,7 +10,7 @@ internal class LoopTransition : Transition internal override NodeExpression FallThroughNode => BodyNode; - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return EmptyBody; } diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs index 1df3ff7..2da0842 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace Hyperbee.Expressions.Transformation.Transitions; @@ -10,28 +11,7 @@ internal class SwitchTransition : Transition internal override NodeExpression FallThroughNode => DefaultNode; - protected override Expression VisitChildren( ExpressionVisitor visitor ) - { - return Update( - visitor.Visit( SwitchValue ), - CaseNodes.Select( x => x.Update( x.TestValues.Select( visitor.Visit ).ToList() ) ).ToList() - ); - } - - internal SwitchTransition Update( Expression switchValue, List caseNodes ) - { - if ( switchValue == SwitchValue ) - return this; - - return new SwitchTransition - { - DefaultNode = DefaultNode, - SwitchValue = switchValue, - CaseNodes = caseNodes - }; - } - - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return [GetExpression()]; @@ -42,7 +22,7 @@ Expression GetExpression() if ( DefaultNode != null ) { defaultBody = GotoOrFallThrough( - Parent.StateOrder, + parent.StateOrder, DefaultNode, allowNull: true ); @@ -53,7 +33,7 @@ Expression GetExpression() } var cases = CaseNodes - .Select( switchCase => switchCase.Reduce( Parent.StateOrder ) ) + .Select( switchCase => switchCase.Reduce( parent.StateOrder ) ) .ToArray(); return Switch( SwitchValue, defaultBody, cases ); diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs index d256b46..e9351cf 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs @@ -1,63 +1,57 @@ using System.Diagnostics; using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; + namespace Hyperbee.Expressions.Transformation.Transitions; [DebuggerDisplay( "Transition = {GetType().Name,nq}" )] -internal abstract class Transition : Expression +internal abstract class Transition { protected static readonly List EmptyBody = [Empty()]; - public override ExpressionType NodeType => ExpressionType.Extension; - public override Type Type => typeof( void ); - public override bool CanReduce => true; - - protected override Expression VisitChildren( ExpressionVisitor visitor ) => this; - internal abstract NodeExpression FallThroughNode { get; } internal abstract void Optimize( HashSet references ); - internal NodeExpression Parent { get; set; } - - public override Expression Reduce() + public Expression Reduce( NodeExpression parent ) { - if ( Parent == null ) - throw new InvalidOperationException( $"Transition Reduce requires a {nameof( Parent )} instance." ); + if ( parent == null ) + throw new InvalidOperationException( $"Transition Reduce requires a {nameof( parent )} instance." ); - var reduced = Reduce( Parent ); + var reduced = ReduceInternal( parent ); return (reduced.Count == 1) ? reduced[0] : Block( reduced ); } - private List Reduce( NodeExpression node ) + private List ReduceInternal( NodeExpression parent ) { var expressions = new List( 8 ) // Label, Expressions, AssignResult, Transition { - Label( node.NodeLabel ) + Label( parent.NodeLabel ) }; - expressions.AddRange( node.Expressions ); + expressions.AddRange( parent.Expressions ); // add result assignment - AssignResult( expressions ); + AssignResult( parent, expressions ); // add transition body - expressions.AddRange( GetBody() ); + expressions.AddRange( GetBody( parent ) ); return expressions; } - protected abstract List GetBody(); + protected abstract List GetBody( NodeExpression parent ); - protected virtual void AssignResult( List expressions ) + protected virtual void AssignResult( NodeExpression parent, List expressions ) { - var resultValue = Parent.ResultValue; - var resultVariable = Parent.ResultVariable; + var resultValue = parent.ResultValue; + var resultVariable = parent.ResultVariable; if ( resultValue != null && resultVariable != null && resultValue.Type == resultVariable.Type ) { diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs index 1e3e308..3ed770a 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs @@ -1,4 +1,5 @@ using System.Linq.Expressions; +using static System.Linq.Expressions.Expression; namespace Hyperbee.Expressions.Transformation.Transitions; @@ -16,36 +17,7 @@ internal class TryCatchTransition : Transition internal override NodeExpression FallThroughNode => TryNode; - protected override Transition VisitChildren( ExpressionVisitor visitor ) - { - return Update( - CatchBlocks.Select( c => new CatchBlockDefinition( c.Handler, visitor.Visit( c.UpdateBody ), c.CatchState ) ).ToList(), - visitor.Visit( TryStateVariable ), - visitor.Visit( ExceptionVariable ) - ); - } - - internal TryCatchTransition Update( - List catchBlocks, - Expression tryStateVariable, - Expression exceptionVariable ) - { - if ( catchBlocks == CatchBlocks && tryStateVariable == TryStateVariable && exceptionVariable == ExceptionVariable ) - return this; - - return new TryCatchTransition - { - TryNode = TryNode, - FinallyNode = FinallyNode, - CatchBlocks = catchBlocks, - TryStateVariable = tryStateVariable, - ExceptionVariable = exceptionVariable, - StateScope = StateScope, - Scopes = Scopes - }; - } - - protected override List GetBody() + protected override List GetBody(NodeExpression parent ) { return GetExpressions(); @@ -56,14 +28,14 @@ List GetExpressions() JumpTableBuilder.Build( StateScope, Scopes, - Parent.StateMachineSource.StateIdField + parent.StateMachineSource.StateIdField ) }; //body.AddRange( StateScope.Nodes ); //BF ME - merge nodes here body.AddRange( NodeExpression.Merge( StateScope.Nodes ) ); - MapCatchBlock( Parent.StateOrder, out var catches, out var switchCases ); + MapCatchBlock( parent.StateOrder, out var catches, out var switchCases ); return [ TryCatch( diff --git a/test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs b/test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs new file mode 100644 index 0000000..c6418bd --- /dev/null +++ b/test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs @@ -0,0 +1,358 @@ +using System.Linq.Expressions; +using BenchmarkDotNet.Attributes; +using Hyperbee.Expressions.Optimizers; + +namespace Hyperbee.Expressions.Benchmark; + +[MemoryDiagnoser] +public class OptimizerBenchmarks +{ + private ExpressionOptimizer _optimizer; + private Expression _unoptimizedTree; + + /* + + // Inlining Optimizer + [GlobalSetup( Targets = + [ + nameof(BenchmarkInlining), + nameof(ExecuteUnoptimizedInlining), + nameof(ExecuteOptimizedInlining) + ] )] + public void SetupInlining() + { + const int Iterations = 1000; + _optimizer = new InliningOptimizer(); + + var labelTarget = Expression.Label( typeof( int ) ); + var paramX = Expression.Parameter( typeof( int ), "x" ); + var counter = Expression.Parameter( typeof( int ), "counter" ); + + var lambda = Expression.Lambda( + Expression.Add( paramX, Expression.Constant( 5 ) ), + paramX + ); + + var targetExpr = Expression.Invoke( lambda, paramX ); + + _unoptimizedTree = Expression.Block( + [paramX, counter], + Expression.Assign( paramX, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.Block( + targetExpr, + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( Iterations ) ), + Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + Expression.Break( labelTarget, targetExpr ) + ) + ), + labelTarget + ) + ); + } + + [Benchmark] + public Expression BenchmarkInlining() + { + return _optimizer.Optimize( _unoptimizedTree ); + } + + [Benchmark] + public int ExecuteUnoptimizedInlining() + { + var lambda = Expression.Lambda>( _unoptimizedTree ); + return lambda.Compile()(); + } + + [Benchmark] + public int ExecuteOptimizedInlining() + { + var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); + var lambda = Expression.Lambda>( optimizedTree ); + return lambda.Compile()(); + } + + // Operator Reduction Optimizer + [GlobalSetup( Targets = + [ + nameof(BenchmarkOperatorReduction), + nameof(ExecuteUnoptimizedOperatorReduction), + nameof(ExecuteOptimizedOperatorReduction) + ] )] + public void SetupOperatorReduction() + { + const int Iterations = 1000; + _optimizer = new OperatorReductionOptimizer(); + + var labelTarget = Expression.Label( typeof( int ) ); + var paramX = Expression.Parameter( typeof( int ), "x" ); + var counter = Expression.Parameter( typeof( int ), "counter" ); + + var nestedExpr = Expression.Multiply( + Expression.Add( paramX, Expression.Constant( 0 ) ), + Expression.Add( Expression.Constant( 1 ), paramX ) + ); + + var targetExpr = Expression.Add( + nestedExpr, + Expression.Multiply( + Expression.Add( Expression.Constant( 2 ), Expression.Constant( 3 ) ), + Expression.Constant( 1 ) + ) + ); + + _unoptimizedTree = Expression.Block( + [paramX, counter], + Expression.Assign( paramX, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.Block( + targetExpr, + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( Iterations ) ), + Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + Expression.Break( labelTarget, targetExpr ) + ) + ), + labelTarget + ) + ); + } + + [Benchmark] + public Expression BenchmarkOperatorReduction() + { + return _optimizer.Optimize( _unoptimizedTree ); + } + + [Benchmark] + public int ExecuteUnoptimizedOperatorReduction() + { + var lambda = Expression.Lambda>( _unoptimizedTree ); + return lambda.Compile()(); + } + + [Benchmark] + public int ExecuteOptimizedOperatorReduction() + { + var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); + var lambda = Expression.Lambda>( optimizedTree ); + return lambda.Compile()(); + } + + //Structural Reduction Optimizer + [GlobalSetup( Targets = [ + nameof(BenchmarkStructuralReduction), + nameof(ExecuteUnoptimizedStructuralReduction), + nameof(ExecuteOptimizedStructuralReduction) + ] )] + public void SetupStructuralReduction() + { + const int Iterations = 1000; + _optimizer = new StructuralReductionOptimizer(); + + var labelTarget = Expression.Label( typeof( int ) ); + var paramX = Expression.Parameter( typeof( int ), "x" ); + var counter = Expression.Parameter( typeof( int ), "counter" ); + + var targetExpr = Expression.Block( + [paramX, counter], + Expression.Block( + Expression.Block( + Expression.IfThen( // Constant conditional + Expression.Constant( true ), + Expression.Assign( + counter, + Expression.Add( counter, Expression.Constant( 30 + 12 ) ) + ) + ), + Expression.Goto( labelTarget, Expression.Constant( 0 ) ) // Redundant Goto + ) + ), + Expression.TryCatch( // Empty TryCatch + Expression.Empty(), + Expression.Catch( Expression.Parameter( typeof( Exception ), "ex" ), Expression.Empty() ) + ) + ); + + _unoptimizedTree = Expression.Block( + [paramX, counter], + Expression.Assign( paramX, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( Iterations ) ), + targetExpr, + Expression.Break( labelTarget, Expression.Constant( 0 ) ) + ), + labelTarget + ) + ); + } + + [Benchmark] + public Expression BenchmarkStructuralReduction() + { + return _optimizer.Optimize( _unoptimizedTree ); + } + + [Benchmark] + public int ExecuteUnoptimizedStructuralReduction() + { + var lambda = Expression.Lambda>( _unoptimizedTree ); + return lambda.Compile()(); + } + + [Benchmark] + public int ExecuteOptimizedStructuralReduction() + { + var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); + var lambda = Expression.Lambda>( optimizedTree ); + return lambda.Compile()(); + } + + */ + + // Subexpression Caching Optimizer + + [GlobalSetup( Targets = [ + nameof(BenchmarkSubexpressionCaching), + nameof(ExecuteUnoptimizedSubexpressionCaching), + nameof(ExecuteOptimizedSubexpressionCaching) + ] )] + public void SetupSubexpressionCaching() + { + const int Iterations = 1000; + _optimizer = new SubexpressionCachingOptimizer(); + + var labelTarget = Expression.Label( typeof( int ) ); + var counter = Expression.Parameter( typeof( int ), "counter" ); + + var repeatedExpr = Expression.Add( + Expression.Multiply( + Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ), + Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) + ), + Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) + ); + + var targetBlock = Expression.Block( + repeatedExpr, + Expression.TryCatch( + Expression.Empty(), + Expression.Catch( Expression.Parameter( typeof( Exception ), "ex" ), Expression.Empty() ) + ) + ); + + _unoptimizedTree = Expression.Block( + [counter], + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( Iterations ) ), + Expression.Block( + Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + targetBlock + ), + Expression.Break( labelTarget, Expression.Constant( 0 ) ) + ), + labelTarget + ) + ); + } + + [Benchmark] + public Expression BenchmarkSubexpressionCaching() + { + return _optimizer.Optimize( _unoptimizedTree ); + } + + [Benchmark] + public int ExecuteUnoptimizedSubexpressionCaching() + { + var lambda = Expression.Lambda>( _unoptimizedTree ); + return lambda.Compile()(); + } + + //[Benchmark] + public int ExecuteOptimizedSubexpressionCaching() + { + var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); + var lambda = Expression.Lambda>( optimizedTree ); + return lambda.Compile()(); + } + + //// Value Binding Optimizer + //[GlobalSetup( Targets = [ + // nameof(BenchmarkValueBinding), + // nameof(ExecuteUnoptimizedValueBinding), + // nameof(ExecuteOptimizedValueBinding) + //] )] + //public void SetupValueBinding() + //{ + // _optimizer = new ValueBindingOptimizer(); + + // var labelTarget = Expression.Label( typeof(int) ); // Change to int + // var tempVar = Expression.Variable( typeof(string), "temp" ); + // var counter = Expression.Variable( typeof(int), "counter" ); + + // var containerAccess = Expression.Constant( new Container() ); + // var nestedProperty = Expression.Property( containerAccess, nameof(Container.Nested) ); + // var nestedAccess = Expression.Property( nestedProperty, nameof(Container.Nested.Value) ); + + // _unoptimizedTree = Expression.Block( + // [tempVar, counter], + // Expression.Assign( tempVar, Expression.Constant( string.Empty ) ), + // Expression.Assign( counter, Expression.Constant( 0 ) ), + // Expression.Loop( + // Expression.Block( + // Expression.Assign( tempVar, nestedAccess ), + // Expression.IfThenElse( + // Expression.LessThan( counter, Expression.Constant( 10 ) ), + // Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + // Expression.Break( labelTarget, counter ) + // ) + // ), + // labelTarget + // ) + // ); + //} + + //[Benchmark] + //public Expression BenchmarkValueBinding() + //{ + // return _optimizer.Optimize(_unoptimizedTree); + //} + + //[Benchmark] + //public int ExecuteUnoptimizedValueBinding() + //{ + // var lambda = Expression.Lambda>(_unoptimizedTree); + // return lambda.Compile()(); + //} + + //[Benchmark] + //public int ExecuteOptimizedValueBinding() + //{ + // var optimizedTree = _optimizer.Optimize(_unoptimizedTree); + // var lambda = Expression.Lambda>(optimizedTree); + // return lambda.Compile()(); + //} + + private class TestClass + { + public string Method() => "Result"; + } + + private class Container + { + public NestedClass Nested { get; } = new (); + } + + private class NestedClass + { + public string Value => "ExpectedValue"; + } +} diff --git a/test/Hyperbee.Expressions.Benchmark/Program.cs b/test/Hyperbee.Expressions.Benchmark/Program.cs index 695272a..33961b8 100644 --- a/test/Hyperbee.Expressions.Benchmark/Program.cs +++ b/test/Hyperbee.Expressions.Benchmark/Program.cs @@ -6,6 +6,40 @@ internal class Program { static void Main( string[] args ) { + //try + //{ + // var bm = new OptimizerBenchmarks(); + + //bm.SetupInlining(); + //bm.BenchmarkInlining(); + //bm.ExecuteUnoptimizedInlining(); + //bm.ExecuteOptimizedInlining(); + + //bm.SetupOperatorReduction(); + //bm.BenchmarkOperatorReduction(); + //bm.ExecuteUnoptimizedOperatorReduction(); + //bm.ExecuteOptimizedOperatorReduction(); + + //bm.SetupStructuralReduction(); + //bm.BenchmarkStructuralReduction(); + //bm.ExecuteUnoptimizedStructuralReduction(); + //bm.ExecuteOptimizedStructuralReduction(); + + //bm.SetupSubexpressionCaching(); + //bm.BenchmarkSubexpressionCaching(); + //bm.ExecuteUnoptimizedSubexpressionCaching(); + //bm.ExecuteOptimizedSubexpressionCaching(); // execution of optimized throws + + //bm.SetupValueBinding(); + //bm.BenchmarkValueBinding(); // optimizer throws + //bm.ExecuteUnoptimizedValueBinding(); + //bm.ExecuteOptimizedValueBinding(); + //} + //catch ( Exception ex ) + //{ + // Console.WriteLine( ex ); + //} + BenchmarkSwitcher.FromAssembly( typeof( Program ).Assembly ).Run( args, new BenchmarkConfig.Config() ); } } diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs index ac78248..34ed3eb 100644 --- a/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs +++ b/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs @@ -6,6 +6,48 @@ namespace Hyperbee.Expressions.Tests.Optimizers; [TestClass] public class InliningOptimizerTests { + [TestMethod] + public void Inlining_ShouldCompile() + { + // Arrange + + var labelTarget = Expression.Label( typeof( int ) ); + var paramX = Expression.Parameter( typeof( int ), "x" ); + var counter = Expression.Parameter( typeof( int ), "counter" ); + + var lambda = Expression.Lambda( + Expression.Add( paramX, Expression.Constant( 5 ) ), + paramX + ); + + var targetExpr = Expression.Invoke( lambda, paramX ); + + var block = Expression.Block( + [paramX, counter], + Expression.Assign( paramX, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.Block( + targetExpr, + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + Expression.Break( labelTarget, targetExpr ) + ) + ), + labelTarget + ) + ); + + var optimizer = new InliningOptimizer(); + + // Act + var optimized = optimizer.Optimize( block ); + + // Assert + var compiled = Expression.Lambda>( optimized ).Compile(); + } + [TestMethod] public void Inlining_ShouldInlineSimpleConstant() { diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs index 87c7f1d..119fe02 100644 --- a/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs +++ b/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs @@ -6,6 +6,54 @@ namespace Hyperbee.Expressions.Tests.Optimizers; [TestClass] public class OperatorReductionOptimizerTests { + [TestMethod] + public void OperatorReduction_ShouldCompile() + { + // Arrange + + var labelTarget = Expression.Label( typeof(int) ); + var paramX = Expression.Parameter( typeof(int), "x" ); + var counter = Expression.Parameter( typeof(int), "counter" ); + + var nestedExpr = Expression.Multiply( + Expression.Add( paramX, Expression.Constant( 0 ) ), + Expression.Add( Expression.Constant( 1 ), paramX ) + ); + + var targetExpr = Expression.Add( + nestedExpr, + Expression.Multiply( + Expression.Add( Expression.Constant( 2 ), Expression.Constant( 3 ) ), + Expression.Constant( 1 ) + ) + ); + + var block = Expression.Block( + [paramX, counter], + Expression.Assign( paramX, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.Block( + targetExpr, + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + Expression.Break( labelTarget, targetExpr ) + ) + ), + labelTarget + ) + ); + + var optimizer = new OperatorReductionOptimizer(); + + // Act + var optimized = optimizer.Optimize( block ); + + // Assert + var compiled = Expression.Lambda>( optimized ).Compile(); + } + [TestMethod] public void OperatorReduction_ShouldRemoveAddZero() { diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs index 1e180ce..56495f4 100644 --- a/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs +++ b/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs @@ -6,6 +6,58 @@ namespace Hyperbee.Expressions.Tests.Optimizers; [TestClass] public class StructuralReductionOptimizerTests { + [TestMethod] + public void StructuralReduction_ShouldCompile() + { + // Arrange + + var labelTarget = Expression.Label( typeof(int) ); + var paramX = Expression.Parameter( typeof(int), "x" ); + var counter = Expression.Parameter( typeof(int), "counter" ); + + var targetBlock = Expression.Block( + [paramX, counter], // Declare 'counter' here to ensure it is scoped correctly + Expression.Block( + Expression.Block( + Expression.IfThen( + Expression.Constant( true ), + Expression.Assign( + counter, + Expression.Add( counter, Expression.Constant( 30 + 12 ) ) + ) + ), + Expression.Goto( labelTarget, Expression.Constant( 0 ) ) // Redundant Goto + ) + ), + Expression.TryCatch( + Expression.Empty(), + Expression.Catch( Expression.Parameter( typeof(Exception), "ex" ), Expression.Empty() ) + ) + ); + + var block = Expression.Block( + [paramX, counter], // Declare variables in the outer block + Expression.Assign( paramX, Expression.Constant( 10 ) ), + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( 1000 ) ), + targetBlock, + Expression.Break( labelTarget, Expression.Constant( 0 ) ) + ), + labelTarget + ) + ); + + var optimizer = new StructuralReductionOptimizer(); + + // Act + var optimized = optimizer.Optimize( block ); + + // Assert + var compiled = Expression.Lambda>( optimized ).Compile(); + } + [TestMethod] public void StructuralReduction_ShouldRemoveUnreachableCode() { diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs index f9ebb63..e7213bd 100644 --- a/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs +++ b/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs @@ -12,6 +12,55 @@ public class TestClass public string Method() => "Result"; } + [TestMethod] + public void SubexpressionCaching_ShouldCompile() + { + var labelTarget = Expression.Label( typeof(int) ); + var counter = Expression.Parameter( typeof(int), "counter" ); + + var repeatedExpr = Expression.Add( + Expression.Multiply( + Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ), + Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) + ), + Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) + ); + + var targetBlock = Expression.Block( + repeatedExpr, + Expression.TryCatch( + Expression.Empty(), + Expression.Catch( Expression.Parameter( typeof(Exception), "ex" ), Expression.Empty() ) + ) + ); + + var block = Expression.Block( + [counter], + Expression.Assign( counter, Expression.Constant( 0 ) ), + Expression.Loop( + Expression.IfThenElse( + Expression.LessThan( counter, Expression.Constant( 10 ) ), + Expression.Block( + Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), + targetBlock + ), + Expression.Break( labelTarget, Expression.Constant( 0 ) ) // Provide default value + ), + labelTarget + ) + ); + + // Arrange + var optimizer = new SubexpressionCachingOptimizer(); + + // Act + var optimized = optimizer.Optimize( block ); + + // Assert + var compiled = Expression.Lambda>( optimized ).Compile(); + compiled(); + } + [TestMethod] public void SubexpressionCaching_ShouldNotCacheSimpleExpressions() { From de04047cee4378e31f26f1dddb3cbb089bfc3b46 Mon Sep 17 00:00:00 2001 From: Brenton Farmer Date: Mon, 2 Dec 2024 09:14:50 -0800 Subject: [PATCH 2/7] refactors to remove optimizers and simplify transitions --- README.md | 8 - docs/docs.projitems | 8 - docs/index.md | 8 - docs/optimizers/constant-folding-optimizer.md | 28 -- .../operator-reduction-optimizer.md | 28 -- docs/optimizers/optimizer-builder.md | 40 -- docs/optimizers/optimizers.md | 11 - docs/optimizers/overview.md | 43 --- .../structural-reduction-optimizer.md | 32 -- .../subexpression-caching-optimizer.md | 31 -- docs/optimizers/value-binding-optimizer.md | 31 -- .../Optimizers/ConstantFoldingOptimizer.cs | 15 - .../Optimizers/ExpressionOptimizer.cs | 28 -- .../Optimizers/IExpressionTransformer.cs | 18 - .../Optimizers/InliningOptimizer.cs | 33 -- .../Optimizers/OperatorReductionOptimizer.cs | 32 -- .../Optimizers/OptimizerBuilder.cs | 180 --------- .../StructuralReductionOptimizer.cs | 17 - .../SubexpressionCachingOptimizer.cs | 40 -- .../Optimizers/ValueBindingOptimizer.cs | 18 - .../Visitors/ConstantFoldingVisitor.cs | 329 ---------------- .../Optimizers/Visitors/InliningVisitor.cs | 117 ------ .../Visitors/MemberAccessVisitor.cs | 73 ---- .../Visitors/OperatorReductionVisitor.cs | 176 --------- .../Visitors/StructuralReductionVisitor.cs | 134 ------- .../Visitors/SubexpressionCachingVisitor.cs | 228 ----------- .../Visitors/VariableReducerVisitor.cs | 126 ------ .../Transformation/NodeExpression.cs | 9 +- .../Transitions/AwaitResultTransition.cs | 38 +- .../Transitions/AwaitTransition.cs | 7 +- .../Transitions/ConditionalTransition.cs | 7 +- .../Transitions/FinalTransition.cs | 5 +- .../Transitions/GotoTransition.cs | 4 +- .../Transitions/LoopTransition.cs | 3 +- .../Transitions/SwitchTransition.cs | 7 +- .../Transformation/Transitions/Transition.cs | 37 +- .../Transitions/TryCatchTransition.cs | 7 +- .../OptimizerBenchmarks.cs | 358 ------------------ .../Hyperbee.Expressions.Benchmark/Program.cs | 34 -- .../Optimizers/InliningOptimizerTests.cs | 152 -------- .../OperatorReductionOptimizerTests.cs | 166 -------- .../StructuralReductionOptimizerTests.cs | 165 -------- .../SubexpressionCachingOptimizerTests.cs | 195 ---------- .../Optimizers/ValueBindingOptimizerTests.cs | 174 --------- 44 files changed, 70 insertions(+), 3130 deletions(-) delete mode 100644 docs/optimizers/constant-folding-optimizer.md delete mode 100644 docs/optimizers/operator-reduction-optimizer.md delete mode 100644 docs/optimizers/optimizer-builder.md delete mode 100644 docs/optimizers/optimizers.md delete mode 100644 docs/optimizers/overview.md delete mode 100644 docs/optimizers/structural-reduction-optimizer.md delete mode 100644 docs/optimizers/subexpression-caching-optimizer.md delete mode 100644 docs/optimizers/value-binding-optimizer.md delete mode 100644 src/Hyperbee.Expressions/Optimizers/ConstantFoldingOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/ExpressionOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/IExpressionTransformer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/InliningOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/OperatorReductionOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/OptimizerBuilder.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/StructuralReductionOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/SubexpressionCachingOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/ValueBindingOptimizer.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/ConstantFoldingVisitor.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/InliningVisitor.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/MemberAccessVisitor.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/OperatorReductionVisitor.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs delete mode 100644 src/Hyperbee.Expressions/Optimizers/Visitors/VariableReducerVisitor.cs delete mode 100644 test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs delete mode 100644 test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs delete mode 100644 test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs delete mode 100644 test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs delete mode 100644 test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs delete mode 100644 test/Hyperbee.Expressions.Tests/Optimizers/ValueBindingOptimizerTests.cs diff --git a/README.md b/README.md index 80fb122..0abd16b 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,6 @@ workflows and other language constructs. * `StringFormatExpression`: An expression that creates a string using a supplied format string and parameters. * `DebugExpression`: An expression that helps when debugging expression trees. -* **Expression Optimizers** - * `OptimizerBuilder`: An composite optimizer builder. - * `ConstantFoldingOptimizer`: An optimizer that precomputes constant expressions. - * `OperatorReductionOptimizer`: An optimizer that simplifies arithmetic and logical expressions. - * `StructuralReductionOptimizer`: An optimizer that removes unreachable code and consolidates nested or redundant constructs. - * `ValueBindingOptimizer`: An optimizer that simplifies and inlines, variable, constant, and member access operations. - * `SubExpressionCachingOptimizer`: An optimizer that identifies and caches repeated subexpressions within a tree. - * Supports Fast Expression Compiler (FEC) for improved performance. ## Examples diff --git a/docs/docs.projitems b/docs/docs.projitems index c377965..fa849f7 100644 --- a/docs/docs.projitems +++ b/docs/docs.projitems @@ -9,14 +9,6 @@ docs - - - - - - - - diff --git a/docs/index.md b/docs/index.md index 5645aa4..38ce917 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,14 +26,6 @@ trees to handle asynchronous workflows and other language constructs. * `StringFormatExpression`: An expression that creates a string using a supplied format string and parameters. * `DebugExpression`: An expression that helps when debugging expression trees. -* **Expression Optimizers** - * `OptimizerBuilder`: An composite optimizer builder. - * `ConstantFoldingOptimizer`: An optimizer that precomputes constant expressions. - * `OperatorReductionOptimizer`: An optimizer that simplifies arithmetic and logical expressions. - * `StructuralReductionOptimizer`: An optimizer that removes unreachable code and consolidates nested or redundant constructs. - * `ValueBindingOptimizer`: An optimizer that simplifies and inlines, variable, constant, and member access operations. - * `SubExpressionCachingOptimizer`: An optimizer that identifies and caches repeated subexpressions within a tree. - * Supports Fast Expression Compiler (FEC) for improved performance. ## Examples diff --git a/docs/optimizers/constant-folding-optimizer.md b/docs/optimizers/constant-folding-optimizer.md deleted file mode 100644 index 78344cb..0000000 --- a/docs/optimizers/constant-folding-optimizer.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: default -title: Constant Folding Optimizer -parent: optimizers -nav_order: 3 ---- -# Constant Folding Optimizer -The **Constant Folding Optimizer** precomputes constant expressions. - -## **Purpose** -- **Issue**: Expressions involving only constants can unnecessarily consume runtime resources for evaluation. -- **Solution**: Replaces constant expressions with their precomputed values. -- **Result**: Simplifies trees and improves runtime performance by removing redundant evaluations. - -## **Usage Example** -### **Before Optimization** -```csharp -.Lambda #Lambda1> { - .Add(.Constant(3), .Constant(5)) -} -``` - -### **After Optimization** -```csharp -.Lambda #Lambda1> { - .Constant(8) -} -``` \ No newline at end of file diff --git a/docs/optimizers/operator-reduction-optimizer.md b/docs/optimizers/operator-reduction-optimizer.md deleted file mode 100644 index d25f048..0000000 --- a/docs/optimizers/operator-reduction-optimizer.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: default -title: Operator Reduction Optimizer -parent: optimizers -nav_order: 4 ---- -# Operator Reduction Optimizer -The **Operator Reduction Optimizer** simplifies arithmetic and logical expressions. - -## **Purpose** -- **Issue**: Expression trees often contain redundant operations, such as adding zero or multiplying by one. -- **Solution**: By removing or simplifying these expressions, the optimizer reduces tree size and computation complexity. -- **Result**: Improved runtime performance through reduced instruction execution and simplified interpretation. - -## **Usage Example** -### **Before Optimization** -```csharp -.Lambda #Lambda1> { - .Add(.Parameter(x), .Constant(0)) -} -``` - -### **After Optimization** -```csharp -.Lambda #Lambda1> { - .Parameter(x) -} -``` \ No newline at end of file diff --git a/docs/optimizers/optimizer-builder.md b/docs/optimizers/optimizer-builder.md deleted file mode 100644 index 6816d6d..0000000 --- a/docs/optimizers/optimizer-builder.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: default -title: Optimizer Builder -parent: optimizers -nav_order: 2 ---- -# Optimizer Builder -The `OptimizerBuilder` constructs a composite optimizer, and orders optimizers based on their dependencies and priorities, ensuring proper execution. - -## **Dependency Ordering** -The builder: -1. Constructs a dependency graph based on declared dependencies. -2. Topologically sorts the graph to resolve dependency order. -3. Weaves standalone optimizers into the sequence based on priority. - -## **Workflow** -- **Input Optimizers**: - - `OperatorReductionOptimizer`: Depends on `ConstantFoldingVisitor` and `OperatorReductionVisitor`. - - `StructuralReductionOptimizer`: Depends on `StructuralReductionVisitor`. - -- **Dependency Graph**: - - Nodes: `{ConstantFoldingVisitor, OperatorReductionVisitor, StructuralReductionVisitor}` - - Edges: `{ConstantFoldingVisitor -> OperatorReductionVisitor}` - -- **Sorted Execution Order**: - 1. `ConstantFoldingVisitor` - 2. `OperatorReductionVisitor` - 3. `StructuralReductionVisitor` - -## **Usage Example** -### **Builder Setup** -```csharp -var optimizer = new OptimizerBuilder() - .With() - .With() - .With() - .Build(); - -var optimizedExpression = optimizer.Optimize(expression); -``` diff --git a/docs/optimizers/optimizers.md b/docs/optimizers/optimizers.md deleted file mode 100644 index 512b9cd..0000000 --- a/docs/optimizers/optimizers.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -layout: default -title: Optimizers -has_children: true -nav_order: 1 ---- -# Optimizers - -Expression optimization enhances the execution of expression trees by reducing redundancies, simplifying constructs, -and eliminating unnecessary computations. By restructuring and minimizing expression complexity, optimizations -improve runtime performance and reduce memory usage. diff --git a/docs/optimizers/overview.md b/docs/optimizers/overview.md deleted file mode 100644 index e0360fe..0000000 --- a/docs/optimizers/overview.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -layout: default -title: Overview -parent: optimizers -nav_order: 1 ---- -# Overview of Expression Optimization - -Expression optimization enhances the execution of expression trees by reducing redundancies, simplifying constructs, -and eliminating unnecessary computations. By restructuring and minimizing expression complexity, optimizations -improve runtime performance and reduce memory usage. - -## Optimization Categories - -Optimizations generally address one fo four categories of expression tree elements: - -### Structural Optimization - -Simplifies control flow structures like loops, conditionals, and blocks. - - - **How**: Removes unreachable code and consolidates nested blocks. - - **Why**: Flattening nested blocks into a single sequence reduces interpreter overhead. - -### Expression-Level Optimization - -Optimizes arithmetic, logical, and constant expressions. - - - **How**: Eliminates trivial operations, such as adding zero or multiplying by one, and precomputes constant results. - - **Why**: Folding constants reduces evaluation costs at runtime. - -### Inlining and Binding - -Inlines variables, constants, and member accesses to reduce overhead and memory usage. - - - **How**: Eliminates temporary variables and simplifies bindings. - - **Why**: Replacing a single-use variable with its value directly reduces allocation and access overhead. - -### Subexpression Caching - -Identifies and caches reusable subexpressions to minimize repeated computations. - - - **How**: Prevents redundant evaluation of identical subexpressions in complex trees. - - **Why**: Storing the result of a reused subexpression in a temporary variable minimizes redundant computation. diff --git a/docs/optimizers/structural-reduction-optimizer.md b/docs/optimizers/structural-reduction-optimizer.md deleted file mode 100644 index 450ad05..0000000 --- a/docs/optimizers/structural-reduction-optimizer.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: default -title: Structural Reduction Optimizer -parent: optimizers -nav_order: 5 ---- -# Structural Reduction Optimizer -The **Structural Reduction Optimizer** removes unreachable code and consolidates nested or redundant constructs. - -## **Purpose** -- **Issue**: Control flow structures can introduce unnecessary branching and redundant constructs, increasing overhead. -- **Solution**: Simplifies control flow by removing dead branches, collapsing nested blocks, and optimizing loops. -- **Result**: Streamlined execution paths with reduced branching and improved readability. - -## **Usage Example** -### **Before Optimization** -```csharp -.Lambda #Lambda1> { - .IfThenElse( - .Constant(true), - .Constant(42), - .Constant(0) - ) -} -``` - -### **After Optimization** -```csharp -.Lambda #Lambda1> { - .Constant(42) -} -``` \ No newline at end of file diff --git a/docs/optimizers/subexpression-caching-optimizer.md b/docs/optimizers/subexpression-caching-optimizer.md deleted file mode 100644 index 7010398..0000000 --- a/docs/optimizers/subexpression-caching-optimizer.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: default -title: Subexpression Caching Optimizer -parent: optimizers -nav_order: 7 ---- -# Subexpression Caching Optimizer -The **Subexpression Caching Optimizer** identifies and caches repeated subexpressions within a tree. - -## **Purpose** -- **Issue**: Identical subexpressions evaluated multiple times can lead to redundant computations. -- **Solution**: Creates temporary variables for reusable subexpressions and updates the tree to use these variables. -- **Result**: Reduced computation time by minimizing repeated evaluation. - -## **Usage Example** -### **Before Optimization** -```csharp -.Lambda #Lambda1> { - .Add(.Multiply(5, (3 + 2)), .Multiply(5, (3 + 2))) -} -``` - -### **After Optimization** -```csharp -.Lambda #Lambda1> { - .Block(System.Int32 $cacheVar) { - $cacheVar = .Multiply(5, (3 + 2)); - .Add($cacheVar, $cacheVar) - } -} -``` diff --git a/docs/optimizers/value-binding-optimizer.md b/docs/optimizers/value-binding-optimizer.md deleted file mode 100644 index f6cf764..0000000 --- a/docs/optimizers/value-binding-optimizer.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: default -title: Value Binding Optimizer -parent: optimizers -nav_order: 6 ---- -# Value Binding Optimizer -The **Value Binding Optimizer** simplifies and inlines, variable, constant, and member access operations. - -## **Purpose** -- **Issue**: Expression trees often include temporary variables or member accesses that can be resolved to constants or direct values. -- **Solution**: Inlines single-use variables, evaluates constant member accesses, and simplifies bindings. -- **Result**: Reduced memory usage and improved evaluation speed. - -## **Usage Example** -### **Before Optimization** -```csharp -.Lambda #Lambda1> { - .Block( - .Assign(.Parameter(x), .Constant(5)), - .Parameter(x) - ) -} -``` - -### **After Optimization** -```csharp -.Lambda #Lambda1> { - .Constant(5) -} -``` \ No newline at end of file diff --git a/src/Hyperbee.Expressions/Optimizers/ConstantFoldingOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/ConstantFoldingOptimizer.cs deleted file mode 100644 index 952e5a4..0000000 --- a/src/Hyperbee.Expressions/Optimizers/ConstantFoldingOptimizer.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Hyperbee.Expressions.Optimizers.Visitors; - -namespace Hyperbee.Expressions.Optimizers; - -// ConstantFoldingOptimizer: Constant Evaluation and Simplification -// -// This optimizer evaluates constant expressions and simplifies them to their simplest form. - -public class ConstantFoldingOptimizer : ExpressionOptimizer -{ - public override IExpressionTransformer[] Dependencies => - [ - new ConstantFoldingVisitor() - ]; -} diff --git a/src/Hyperbee.Expressions/Optimizers/ExpressionOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/ExpressionOptimizer.cs deleted file mode 100644 index 91fa66e..0000000 --- a/src/Hyperbee.Expressions/Optimizers/ExpressionOptimizer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Expressions.Optimizers; - -public abstract class ExpressionOptimizer //: IExpressionOptimizer -{ - public abstract IExpressionTransformer[] Dependencies { get; } - - public virtual Expression Optimize( Expression expression ) - { - foreach ( var dependency in Dependencies ) - { - expression = dependency.Transform( expression ); - } - - return expression; - } - - public TExpr Optimize( TExpr expression ) where TExpr : LambdaExpression - { - foreach ( var dependency in Dependencies ) - { - expression = (TExpr) dependency.Transform( expression ); - } - - return expression; - } -} diff --git a/src/Hyperbee.Expressions/Optimizers/IExpressionTransformer.cs b/src/Hyperbee.Expressions/Optimizers/IExpressionTransformer.cs deleted file mode 100644 index 7106d48..0000000 --- a/src/Hyperbee.Expressions/Optimizers/IExpressionTransformer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Expressions.Optimizers; - -public interface IExpressionTransformer -{ - int Priority { get; } - - Expression Transform( Expression expression ); -} - -public static class PriorityGroup -{ - public const int ConstantEvaluationAndSimplification = 0x0100; - public const int ControlFlowAndVariableSimplification = 0x0200; - public const int StructuralReductionAndConsolidation = 0x0400; - public const int ExpressionReductionAndCaching = 0x0800; -} diff --git a/src/Hyperbee.Expressions/Optimizers/InliningOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/InliningOptimizer.cs deleted file mode 100644 index f2fc366..0000000 --- a/src/Hyperbee.Expressions/Optimizers/InliningOptimizer.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Hyperbee.Expressions.Optimizers.Visitors; - -namespace Hyperbee.Expressions.Optimizers; - -// InliningOptimizer: Inlining Functions, Constants, and Conditional Values -// -// This optimizer inlines constants and simplifies expressions by propagating known values. -// It focuses on inlining lambda expressions, boolean short-circuiting, and simplifying conditional -// expressions based on constant values. -// -// Before: -// .Invoke(lambda, .Constant(5)) -// -// After: -// .LambdaExpression( -// .Parameter(x), -// .Add(.Constant(5), .Constant(5)) -// ) -// -// Before: -// .IfThenElse(.Constant(true), .Constant("True Branch"), .Constant("False Branch")) -// -// After: -// .Constant("True Branch") -// -public class InliningOptimizer : ExpressionOptimizer -{ - public override IExpressionTransformer[] Dependencies => - [ - new InliningVisitor() - ]; -} - diff --git a/src/Hyperbee.Expressions/Optimizers/OperatorReductionOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/OperatorReductionOptimizer.cs deleted file mode 100644 index b965b3d..0000000 --- a/src/Hyperbee.Expressions/Optimizers/OperatorReductionOptimizer.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Hyperbee.Expressions.Optimizers.Visitors; - -namespace Hyperbee.Expressions.Optimizers; - -// ExpressionReduction: Arithmetic and Logical Reduction -// -// This optimizer removes trivial arithmetic and logical expressions that -// have no effect, simplifies nested expressions, and combines sequential -// expressions where possible. -// -// Examples: -// Before: -// .Add(.Parameter(x), .Constant(0)) -// -// After: -// .Parameter(x) -// -// Before: -// .Multiply(.Parameter(x), .Constant(1)) -// -// After: -// .Parameter(x) -// -public class OperatorReductionOptimizer : ExpressionOptimizer -{ - public override IExpressionTransformer[] Dependencies => - [ - new ConstantFoldingVisitor(), - new OperatorReductionVisitor() - ]; -} - diff --git a/src/Hyperbee.Expressions/Optimizers/OptimizerBuilder.cs b/src/Hyperbee.Expressions/Optimizers/OptimizerBuilder.cs deleted file mode 100644 index 7d0c03f..0000000 --- a/src/Hyperbee.Expressions/Optimizers/OptimizerBuilder.cs +++ /dev/null @@ -1,180 +0,0 @@ -namespace Hyperbee.Expressions.Optimizers; - -public class OptimizerBuilder -{ - private readonly List _optimizers = []; - - public OptimizerBuilder With() where T : ExpressionOptimizer, new() - { - _optimizers.Add( new T() ); - return this; - } - - public ExpressionOptimizer Build() - { - // Separate optimizers based on dependency count - var multiDependencyOptimizers = _optimizers.Where( opt => opt.Dependencies.Length > 1 ).ToList(); - var standaloneTransformers = _optimizers - .Where( opt => opt.Dependencies.Length == 1 ) - .SelectMany( opt => opt.Dependencies ) - .OrderBy( tran => tran.Priority ) - .ToList(); - - // Build and sort the dependency graph - var dependencyGraph = BuildDependencyGraph( multiDependencyOptimizers ); - var sortedDependencies = TopologicalSort( dependencyGraph ); - - // Weave standalone transformers into sorted dependencies to achieve a unified execution order - var wovenTransformers = WeaveStandaloneTransformers( sortedDependencies, standaloneTransformers ); - - return new CompositeOptimizer( wovenTransformers.ToArray() ); - } - - // Construct a dependency graph based on relative order within each optimizer's dependencies. - // Each visitor type becomes a node, and edges are added to maintain the internal order of each optimizer’s dependencies. - // This process ensures that dependencies are combined into a consistent order across optimizers without duplication. - // - // Example: - // Optimizer 1: [A, B, C, D] - // Optimizer 2: [S, C, E, F, D] - // - // Resulting Graph: - // Nodes: [A, B, C, D, S, E, F] - // Edges: [A → B, B → C, C → D, S → C, C → E, E → F, F → D] - // - // This ensures all relative dependencies are respected, creating a coherent dependency graph. - - private static Dictionary> BuildDependencyGraph( IEnumerable optimizers ) - { - var graph = new Dictionary>(); - - foreach ( var optimizer in optimizers ) - { - var dependencies = optimizer.Dependencies; - for ( var i = 0; i < dependencies.Length - 1; i++ ) - { - var current = dependencies[i].GetType(); - var next = dependencies[i + 1].GetType(); - - if ( !graph.ContainsKey( current ) ) - graph[current] = []; - - if ( !graph[current].Contains( next ) ) - graph[current].Add( next ); - } - - // Ensure each dependency type is represented as a node in the graph - foreach ( var dep in dependencies.Select( d => d.GetType() ) ) - { - if ( !graph.ContainsKey( dep ) ) - graph[dep] = []; - } - } - - return graph; - } - - // Perform a topological sort on the dependency graph to ensure a globally consistent order that satisfies - // all relative constraints from each optimizer's dependency list. - // - // Example: - // Given a graph with edges: [A → B, B → C, C → D, S → C, C → E, E → F, F → D] - // - // Calling TopologicalSort on this graph would yield the sorted order: [A, S, B, C, E, F, D]. - // This order preserves dependencies across optimizers and avoids duplication. - // - // Before Sort: - // Graph Edges: [A → B, B → C, C → D, S → C, C → E, E → F, F → D] - // - // After Sort: - // Sorted Order: [A, S, B, C, E, F, D] - - private List TopologicalSort( Dictionary> graph ) - { - var sorted = new List(); - var visited = new HashSet(); - var visiting = new HashSet(); - - foreach ( var node in graph.Keys ) - { - if ( !Visit( node ) ) - throw new InvalidOperationException( $"Cycle detected in dependencies involving {node}." ); - } - - return sorted.Select( type => - _optimizers.SelectMany( opt => opt.Dependencies ) - .First( dep => dep.GetType() == type ) - ).ToList(); - - bool Visit( Type node ) - { - if ( visited.Contains( node ) ) - return true; - - if ( !visiting.Add( node ) ) - return false; // Cycle detected - - foreach ( var neighbor in graph[node] ) - { - if ( !Visit( neighbor ) ) - return false; - } - - visiting.Remove( node ); - visited.Add( node ); - sorted.Add( node ); - - return true; - } - } - - // Weave standalone transformers into the sorted list of multi-dependency transformers, based on priority. - // This ensures that standalone transformers ordered according to priority, while respecting the sorted - // execution order of multi-dependency transformers. - // - // Example: - // Sorted Transformers: [A, B, C] - // Standalone Transformers: [X (Priority: 1), Y (Priority: 2)] - // - // Resulting Order: - // [X, A, B, Y, C] - // - // This interleaving ensures that the final list maintains dependency order while honoring standalone transformer priorities. - - private static List WeaveStandaloneTransformers( List sortedTransformers, List standaloneTransformers ) - { - var finalTransformers = new List(); - int standaloneIndex = 0; - - for ( var index = 0; index < sortedTransformers.Count; index++ ) - { - var transformer = sortedTransformers[index]; - while ( standaloneIndex < standaloneTransformers.Count && standaloneTransformers[standaloneIndex].Priority <= transformer.Priority ) - { - finalTransformers.Add( standaloneTransformers[standaloneIndex] ); - standaloneIndex++; - } - - finalTransformers.Add( transformer ); - } - - // Append any remaining standalone transformers - while ( standaloneIndex < standaloneTransformers.Count ) - { - finalTransformers.Add( standaloneTransformers[standaloneIndex] ); - standaloneIndex++; - } - - return finalTransformers; - } - - public class CompositeOptimizer : ExpressionOptimizer - { - public override IExpressionTransformer[] Dependencies { get; } - - internal CompositeOptimizer( IExpressionTransformer[] dependencies ) - { - Dependencies = dependencies; - } - } -} diff --git a/src/Hyperbee.Expressions/Optimizers/StructuralReductionOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/StructuralReductionOptimizer.cs deleted file mode 100644 index 2257e2e..0000000 --- a/src/Hyperbee.Expressions/Optimizers/StructuralReductionOptimizer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Hyperbee.Expressions.Optimizers.Visitors; - -namespace Hyperbee.Expressions.Optimizers; - -// StructuralReductionOptimizer: Control Flow Simplification -// -// This optimizer manages blocks, labels, loops, and conditionals, removing unreachable -// code and dead branches based on control flow and constant conditions. - -public class StructuralReductionOptimizer : ExpressionOptimizer -{ - public override IExpressionTransformer[] Dependencies => - [ - new StructuralReductionVisitor() - ]; -} - diff --git a/src/Hyperbee.Expressions/Optimizers/SubexpressionCachingOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/SubexpressionCachingOptimizer.cs deleted file mode 100644 index 8d1a28c..0000000 --- a/src/Hyperbee.Expressions/Optimizers/SubexpressionCachingOptimizer.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Hyperbee.Expressions.Optimizers.Visitors; - -namespace Hyperbee.Expressions.Optimizers; - -// ExpressionCachingOptimizer: Expression Subexpression Caching -// -// This optimizer performs subexpression caching to reduce repeated computation in complex expressions. -// By identifying and reusing common subexpressions, it improves execution efficiency, especially in cases -// where identical subexpressions are evaluated multiple times within an expression tree. -// -// Example: -// -// Before Optimization: -// -// .Lambda #Lambda1 { -// 5 * (3 + 2) + 5 * (3 + 2) -// } -// -// After Optimization: -// -// .Lambda #Lambda1 { -// .Block(System.Int32 $cacheVar) { -// $cacheVar = 5 * (3 + 2); -// $cacheVar + $cacheVar -// } -// } -// -// In this example, the optimizer identifies the subexpression `5 * (3 + 2)` as a repeated, cacheable part. -// It creates a variable `$cacheVar` to hold the computed value of `5 * (3 + 2)`, and replaces occurrences -// of this subexpression with `$cacheVar` in the resulting `BlockExpression`. This optimization reduces -// redundant calculations, resulting in a more efficient expression execution. - -public class SubexpressionCachingOptimizer : ExpressionOptimizer -{ - public override IExpressionTransformer[] Dependencies => - [ - new SubexpressionCachingVisitor() - ]; -} - diff --git a/src/Hyperbee.Expressions/Optimizers/ValueBindingOptimizer.cs b/src/Hyperbee.Expressions/Optimizers/ValueBindingOptimizer.cs deleted file mode 100644 index 74a1e63..0000000 --- a/src/Hyperbee.Expressions/Optimizers/ValueBindingOptimizer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Hyperbee.Expressions.Optimizers.Visitors; - -namespace Hyperbee.Expressions.Optimizers; - -// VariableBindingOptimizer: Variable, Constant, and Member Access Optimization -// -// This optimizer handles optimizations related to variables, constants, and member accesses, -// performing constant folding, variable inlining, and simplifying member access. - -public class ValueBindingOptimizer : ExpressionOptimizer -{ - public override IExpressionTransformer[] Dependencies => - [ - new VariableReducerVisitor(), - new ConstantFoldingVisitor(), - new MemberAccessVisitor() - ]; -} diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/ConstantFoldingVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/ConstantFoldingVisitor.cs deleted file mode 100644 index 311f5f5..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/ConstantFoldingVisitor.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System.Linq.Expressions; -using System.Numerics; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -// ConstantFoldingVisitor: Arithmetic, Logical, and Conditional Constant Folding -// -// This visitor evaluates constant expressions at compile-time, replacing them with their computed result. -// It simplifies arithmetic, boolean, string, and conditional expressions when all operands are constants. -// -// Examples: -// -// Before: -// .Add(.Constant(3), .Constant(5)) -// -// After: -// .Constant(8) -// -// Before: -// .IfThenElse(.Constant(true), .Constant("True Branch"), .Constant("False Branch")) -// -// After: -// .Constant("True Branch") -// -// Before: -// .Negate(.Constant(5)) -// -// After: -// .Constant(-5) -// -public class ConstantFoldingVisitor : ExpressionVisitor, IExpressionTransformer -{ - public int Priority => PriorityGroup.ConstantEvaluationAndSimplification + 10; - - public Expression Transform( Expression expression ) - { - return Visit( expression ); - } - - protected override Expression VisitBinary( BinaryExpression node ) - { - if ( node.Left is not ConstantExpression leftConst || node.Right is not ConstantExpression rightConst ) - { - return base.VisitBinary( node ); - } - - var folded = FoldConstants( node.NodeType, leftConst, rightConst ); - return folded ?? base.VisitBinary( node ); - } - - protected override Expression VisitUnary( UnaryExpression node ) - { - if ( node.Operand is ConstantExpression constOperand ) - { - return FoldUnaryOperation( node.NodeType, constOperand ) ?? base.VisitUnary( node ); - } - - return base.VisitUnary( node ); - } - - protected override Expression VisitConditional( ConditionalExpression node ) - { - var test = Visit( node.Test ); - - if ( test is not ConstantExpression testConst ) - { - return base.VisitConditional( node ); - } - - var condition = (bool) testConst.Value!; - var result = condition ? node.IfTrue : node.IfFalse; - return Visit( result ); - - } - - private static ConstantExpression FoldUnaryOperation( ExpressionType nodeType, ConstantExpression operand ) - { - // Unary folding: Supports negation, logical NOT, and bitwise NOT for constants. - // - // Examples: - // - // Before: - // .Negate(.Constant(5)) - // - // After: - // .Constant(-5) - // - return nodeType switch - { - ExpressionType.Negate when operand.Value is int intValue => Expression.Constant( -intValue ), - ExpressionType.Not when operand.Value is bool boolValue => Expression.Constant( !boolValue ), - ExpressionType.OnesComplement when operand.Value is int intValue => Expression.Constant( ~intValue ), - _ => null - }; - } - - private static ConstantExpression FoldConstants( ExpressionType nodeType, ConstantExpression leftConst, ConstantExpression rightConst ) - { - if ( !typeof( IConvertible ).IsAssignableFrom( leftConst.Type ) || !typeof( IConvertible ).IsAssignableFrom( rightConst.Type ) ) - return null; - - // Arithmetic and logical operations on constants - if ( IsNumericType( leftConst.Type ) && IsNumericType( rightConst.Type ) ) - { - return FoldNumericOperation( nodeType, leftConst, rightConst ); - } - - if ( leftConst.Type != rightConst.Type ) - { - return null; - } - - var type = leftConst.Type; - - return type switch - { - not null when type == typeof( bool ) => FoldBooleanOperation( nodeType, (bool) leftConst.Value!, (bool) rightConst.Value! ), - not null when type == typeof( string ) => FoldStringOperation( nodeType, (string) leftConst.Value!, (string) rightConst.Value! ), - not null when type == typeof( char ) => FoldCharOperation( nodeType, (char) leftConst.Value!, (char) rightConst.Value! ), - not null when type == typeof( DateTime ) => FoldDateTimeOperation( nodeType, (DateTime) leftConst.Value!, rightConst.Value ), - _ => null - }; - } - - private static ConstantExpression FoldBooleanOperation( ExpressionType nodeType, bool left, bool right ) - { - // Boolean folding: Simplifies operations like AND, OR, and comparisons. - // - // Examples: - // - // Before: - // .And(.Constant(true), .Constant(false)) - // - // After: - // .Constant(false) - // - return nodeType switch - { - ExpressionType.And => Expression.Constant( left && right ), - ExpressionType.Or => Expression.Constant( left || right ), - ExpressionType.Equal => Expression.Constant( left == right ), - ExpressionType.NotEqual => Expression.Constant( left != right ), - _ => null - }; - } - - private static ConstantExpression FoldCharOperation( ExpressionType nodeType, char left, char right ) - { - // Char folding: Supports addition and subtraction on char constants. - // - // Examples: - // - // Before: - // .Add(.Constant('A'), .Constant(1)) - // - // After: - // .Constant('B') - // - int leftInt = left; - int rightInt = right; - - return nodeType switch - { - ExpressionType.Add => Expression.Constant( (char) (leftInt + rightInt) ), - ExpressionType.Subtract => Expression.Constant( (char) (leftInt - rightInt) ), - _ => null - }; - } - - private static ConstantExpression FoldDateTimeOperation( ExpressionType nodeType, DateTime left, object rightValue ) - { - // DateTime folding: Supports addition and subtraction of TimeSpan to DateTime. - // - // Examples: - // - // Before: - // .Add(.Constant(new DateTime(2024, 1, 1)), .Constant(TimeSpan.FromDays(1))) - // - // After: - // .Constant(new DateTime(2024, 1, 2)) - // - if ( rightValue is not TimeSpan rightTimeSpan ) - return null; - - return nodeType switch - { - ExpressionType.Add => Expression.Constant( left + rightTimeSpan ), - ExpressionType.Subtract => Expression.Constant( left - rightTimeSpan ), - _ => null - }; - } - - private static ConstantExpression FoldStringOperation( ExpressionType nodeType, string left, string right ) - { - // String folding: Concatenates strings for `Add` operation. - // - // Examples: - // - // Before: - // .Add(.Constant("Hello, "), .Constant("World!")) - // - // After: - // .Constant("Hello, World!") - // - return nodeType != ExpressionType.Add ? null : Expression.Constant( left + right ); - } - - private static ConstantExpression FoldNumericOperation( ExpressionType nodeType, ConstantExpression leftConst, ConstantExpression rightConst ) - { - Type commonType; - object leftValue; - object rightValue; - - if ( leftConst.Type == rightConst.Type ) - { - commonType = leftConst.Type; - leftValue = leftConst.Value!; - rightValue = rightConst.Value!; - } - else - { - commonType = GetNumericType( leftConst.Type, rightConst.Type ); - - if ( commonType == null ) - throw new InvalidOperationException( "Incompatible numeric types." ); - - leftValue = Convert.ChangeType( leftConst.Value, commonType )!; - rightValue = Convert.ChangeType( rightConst.Value, commonType )!; - } - - return commonType switch - { - not null when commonType == typeof( byte ) => ApplyOperation( nodeType, (byte) leftValue, (byte) rightValue ), - not null when commonType == typeof( sbyte ) => ApplyOperation( nodeType, (sbyte) leftValue, (sbyte) rightValue ), - not null when commonType == typeof( short ) => ApplyOperation( nodeType, (short) leftValue, (short) rightValue ), - not null when commonType == typeof( ushort ) => ApplyOperation( nodeType, (ushort) leftValue, (ushort) rightValue ), - not null when commonType == typeof( int ) => ApplyOperation( nodeType, (int) leftValue, (int) rightValue ), - not null when commonType == typeof( uint ) => ApplyOperation( nodeType, (uint) leftValue, (uint) rightValue ), - not null when commonType == typeof( long ) => ApplyOperation( nodeType, (long) leftValue, (long) rightValue ), - not null when commonType == typeof( ulong ) => ApplyOperation( nodeType, (ulong) leftValue, (ulong) rightValue ), - not null when commonType == typeof( float ) => ApplyOperation( nodeType, (float) leftValue, (float) rightValue ), - not null when commonType == typeof( double ) => ApplyOperation( nodeType, (double) leftValue, (double) rightValue ), - not null when commonType == typeof( decimal ) => ApplyOperation( nodeType, (decimal) leftValue, (decimal) rightValue ), - _ => throw new InvalidOperationException( "Unsupported type for promoted operation." ) - }; - - static ConstantExpression ApplyOperation( ExpressionType nodeType, T left, T right ) where T : INumber - { - var constant = nodeType switch - { - ExpressionType.Add => left + right, - ExpressionType.Subtract => left - right, - ExpressionType.Multiply => left * right, - ExpressionType.Divide => Divide( left, right ), - _ => throw new InvalidOperationException( $"Unsupported operation {nodeType} for type {typeof( T )}." ) - }; - - return Expression.Constant( constant ); - - static T Divide( T left, T right ) - { - if ( right == T.Zero ) - throw new DivideByZeroException(); - - return left / right; - } - } - } - - private static bool IsNumericType( Type type ) - { - if ( type == null ) - return false; - - return Type.GetTypeCode( type ) switch - { - TypeCode.Byte or - TypeCode.SByte or - TypeCode.Int16 or - TypeCode.UInt16 or - TypeCode.Int32 or - TypeCode.UInt32 or - TypeCode.Int64 or - TypeCode.UInt64 or - TypeCode.Single or - TypeCode.Double or - TypeCode.Decimal => true, - _ => false - }; - } - - private static readonly TypeCode[] TypeCodePromotionOrder = - [ - TypeCode.Byte, - TypeCode.SByte, - TypeCode.Int16, - TypeCode.UInt16, - TypeCode.Int32, - TypeCode.UInt32, - TypeCode.Int64, - TypeCode.UInt64, - TypeCode.Single, - TypeCode.Double, - TypeCode.Decimal - ]; - - private static Type GetNumericType( Type leftType, Type rightType ) - { - var leftCode = Type.GetTypeCode( leftType ); - var rightCode = Type.GetTypeCode( rightType ); - - if ( leftCode == rightCode ) - return leftType; - - var leftIndex = Array.IndexOf( TypeCodePromotionOrder, leftCode ); - var rightIndex = Array.IndexOf( TypeCodePromotionOrder, rightCode ); - - if ( leftIndex < 0 || rightIndex < 0 ) - return null; - - var commonTypeCode = TypeCodePromotionOrder[Math.Max( leftIndex, rightIndex )]; - - return Type.GetType( "System." + commonTypeCode ); - } -} - - - - diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/InliningVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/InliningVisitor.cs deleted file mode 100644 index 3f26f56..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/InliningVisitor.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -// InliningOptimizer: Inlining Functions, Constants, and Conditional Values -// -// This optimizer inlines constants and simplifies expressions by propagating known values. -// It focuses on inlining lambda expressions, boolean short-circuiting, and simplifying conditional -// expressions based on constant values. -// -// Before: -// .Invoke(lambda, .Constant(5)) -// -// After: -// .LambdaExpression( -// .Parameter(x), -// .Add(.Constant(5), .Constant(5)) -// ) -// -// Before: -// .IfThenElse(.Constant(true), .Constant("True Branch"), .Constant("False Branch")) -// -// After: -// .Constant("True Branch") -// -public class InliningVisitor : ExpressionVisitor, IExpressionTransformer -{ - private ConstantFoldingVisitor ConstantFoldingVisitor { get; } = new(); - - public int Priority => PriorityGroup.ControlFlowAndVariableSimplification + 30; - - public Expression Transform( Expression expression ) - { - return Visit( expression ); - } - - protected override Expression VisitBinary( BinaryExpression node ) - { - // Visit left and right nodes to apply inlining or other optimizations - var left = Visit( node.Left ); - var right = Visit( node.Right ); - - // Handle short-circuiting for `AndAlso` and `OrElse` - if ( node.NodeType == ExpressionType.AndAlso || node.NodeType == ExpressionType.OrElse ) - { - if ( left is ConstantExpression leftConst && leftConst.Value is bool leftBool ) - { - // Short-circuit based on left value - return (node.NodeType, leftBool) switch - { - (ExpressionType.AndAlso, true ) => ConstantFoldingVisitor.Visit( right ), - (ExpressionType.AndAlso, false ) => Expression.Constant( false ), - (ExpressionType.OrElse, true ) => Expression.Constant( true ), - (ExpressionType.OrElse, false ) => ConstantFoldingVisitor.Visit( right ), - _ => node.Update( left, node.Conversion, right ) - }; - } - } - - // If both sides are constants, and it's not a logical short-circuit case, try folding directly - if ( left is ConstantExpression && right is ConstantExpression ) - { - return ConstantFoldingVisitor.Visit( Expression.MakeBinary( node.NodeType, left, right ) ); - } - - return node.Update( left, node.Conversion, right ); - } - - protected override Expression VisitInvocation( InvocationExpression node ) - { - if ( node.Expression is LambdaExpression lambda ) - { - // Inline lambda expressions by replacing parameters with arguments - var argumentMap = lambda.Parameters - .Zip( node.Arguments, ( parameter, argument ) => (parameter, argument) ) - .ToDictionary( pair => pair.parameter, pair => pair.argument ); - - var inlinedBody = new ParameterReplacer( argumentMap ).Visit( lambda.Body ); - return ConstantFoldingVisitor.Visit( inlinedBody ); // Apply folding after inlining - } - - return base.VisitInvocation( node ); - } - - protected override Expression VisitConditional( ConditionalExpression node ) - { - // Evaluate conditional expressions with constant test conditions - var test = Visit( node.Test ); - var ifTrue = Visit( node.IfTrue ); - var ifFalse = Visit( node.IfFalse ); - - if ( test is ConstantExpression constTest && constTest.Value is bool condition ) - { - var result = condition ? ifTrue : ifFalse; - return ConstantFoldingVisitor.Visit( result ); // Apply folding after resolving the condition - } - - return node.Update( test, ifTrue, ifFalse ); - } - - // Helper class for inlining: replaces parameters with arguments - private class ParameterReplacer : ExpressionVisitor - { - private readonly Dictionary _replacements; - - public ParameterReplacer( Dictionary replacements ) - { - _replacements = replacements; - } - - protected override Expression VisitParameter( ParameterExpression node ) - { - return _replacements.TryGetValue( node, out var replacement ) ? replacement : base.VisitParameter( node ); - } - } -} - diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/MemberAccessVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/MemberAccessVisitor.cs deleted file mode 100644 index 12cf05b..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/MemberAccessVisitor.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -// MemberAccessVisitor: Constant Field and Property Access Simplification -// -// This visitor simplifies member access by precomputing values for constant fields or -// properties. When a field or property is accessed through a constant object, it evaluates -// the expression and replaces it with the value. -// -// Before: -// .MemberAccess(.Constant(obj), Property) -// -// After: -// .Constant(value) -// -public class MemberAccessVisitor : ExpressionVisitor, IExpressionTransformer -{ - public int Priority => PriorityGroup.ConstantEvaluationAndSimplification + 20; - - public Expression Transform( Expression expression ) - { - return Visit( expression ); - } - - protected override Expression VisitMember( MemberExpression node ) - { - node = (MemberExpression) base.VisitMember( node ); - - if ( node.Expression is not ConstantExpression constExpr ) - { - return node; - } - - switch ( node.Member ) - { - case FieldInfo fieldInfo: - var fieldValue = fieldInfo.GetValue( constExpr.Value ); - return Expression.Constant( fieldValue, node.Type ); - - case PropertyInfo propertyInfo when propertyInfo.CanRead: - var propertyValue = propertyInfo.GetMethod!.IsStatic - ? propertyInfo.GetValue( null ) - : propertyInfo.GetValue( constExpr.Value ); - return Expression.Constant( propertyValue, node.Type ); - - default: - return node; - } - } - - protected override Expression VisitBinary( BinaryExpression node ) - { - if ( node.NodeType != ExpressionType.Coalesce || node.Left is not ConstantExpression leftConst || leftConst.Value != null ) - { - return base.VisitBinary( node ); - } - - return Visit( node.Right ); - } - - protected override Expression VisitIndex( IndexExpression node ) - { - if ( node.Object is not ConstantExpression constantArray || constantArray.Value is not Array array || node.Arguments[0] is not ConstantExpression indexExpr ) - { - return base.VisitIndex( node ); - } - - var index = (int) indexExpr.Value!; - return Expression.Constant( array.GetValue( index ), node.Type ); - } -} diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/OperatorReductionVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/OperatorReductionVisitor.cs deleted file mode 100644 index 5b57370..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/OperatorReductionVisitor.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -// OperatorReductionVisitor: Arithmetic and Logical Operator Reduction -// -// This visitor eliminates trivial arithmetic expressions (like adding zero, multiplying by one), -// simplifies logical identities (like x && true or x || false), and flattens nested expressions -// where applicable. -// -// Examples: -// -// Before: -// .Add(.Parameter(x), .Constant(0)) -// -// After: -// .Parameter(x) -// -// Before: -// .Multiply(.Parameter(x), .Constant(1)) -// -// After: -// .Parameter(x) -// -// Before: -// .Add(.Add(.Parameter(x), .Parameter(y)), .Parameter(z)) -// -// After: -// .Add(.Parameter(x), .Parameter(y), .Parameter(z)) -// -public class OperatorReductionVisitor : ExpressionVisitor, IExpressionTransformer -{ - public int Priority => PriorityGroup.ExpressionReductionAndCaching + 60; - - public Expression Transform( Expression expression ) - { - return Visit( expression ); - } - - protected override Expression VisitBinary( BinaryExpression node ) - { - // Recursively visit left and right nodes to ensure optimizations - // are applied in subexpressions first - - var left = Visit( node.Left ); - var right = Visit( node.Right ); - - // Apply trivial arithmetic and logical simplifications - - switch ( node.NodeType ) - { - // Arithmetic simplifications - case ExpressionType.Add: - case ExpressionType.Subtract: - { - if ( right is ConstantExpression rightConst && rightConst.Value is int rightVal ) - { - // x + 0 or x - 0 - if ( rightVal == 0 ) - return left; - } - - if ( node.NodeType == ExpressionType.Add && left is ConstantExpression leftConst && leftConst.Value is int leftValue ) - { - // 0 + x - if ( leftValue == 0 ) - return right; - } - - break; - } - - case ExpressionType.Multiply: - { - if ( right is ConstantExpression rightConst && rightConst.Value is int rightValue ) - { - // x * 1 - if ( rightValue == 1 ) - return left; - - // x * 0 - if ( rightValue == 0 ) - return Expression.Constant( 0 ); - } - - if ( left is ConstantExpression leftConst && leftConst.Value is int leftValue ) - { - // 1 * x - if ( leftValue == 1 ) - return right; - - // 0 * x - if ( leftValue == 0 ) - return Expression.Constant( 0 ); - } - - break; - } - - // Logical short-circuiting for `&& true`, `|| false`, etc. - case ExpressionType.AndAlso: - { - // x && true or true && x - if ( right is ConstantExpression rightConst && rightConst.Value is bool rightValue && rightValue ) - return left; - - if ( left is ConstantExpression leftConst && leftConst.Value is bool leftValue && leftValue ) - return right; - - // false && x or x && false - if ( left is ConstantExpression lConstAndFalse && lConstAndFalse.Value is bool leftValAndFalse && !leftValAndFalse || - right is ConstantExpression rConstAndFalse && rConstAndFalse.Value is bool rightValAndFalse && !rightValAndFalse ) - return Expression.Constant( false ); - - break; - } - - case ExpressionType.OrElse: - { - // x || false or false || x - if ( right is ConstantExpression rightConst && rightConst.Value is bool rightValue && !rightValue ) - return left; - - if ( left is ConstantExpression leftConst && leftConst.Value is bool leftValue && !leftValue ) - return right; - - // true || x or x || true - if ( left is ConstantExpression lConstOrTrue && lConstOrTrue.Value is bool leftValOrTrue && leftValOrTrue || - right is ConstantExpression rConstOrTrue && rConstOrTrue.Value is bool rightValOrTrue && rightValOrTrue ) - return Expression.Constant( true ); - - break; - } - } - - if ( node.NodeType != ExpressionType.Add && node.NodeType != ExpressionType.Multiply ) - { - return node.Update( left, node.Conversion, right ); - } - - // Handle nested expression flattening specifically for `Add` and `Multiply` - - // Collect terms for the node and recursively flatten if necessary - var allTerms = FlattenBinaryExpression( node, node.NodeType ).ToList(); - - // Aggregate constants in the list (e.g., sum all constants for `Add`) - var constants = allTerms.OfType().ToList(); - var otherTerms = allTerms.Except( constants ).ToList(); - - if ( constants.Count <= 0 ) - { - return otherTerms.Aggregate( ( acc, term ) => Expression.MakeBinary( node.NodeType, acc, term ) ); - } - - // Sum or multiply the constant values based on node type - var constantResult = node.NodeType switch - { - ExpressionType.Add => constants.Sum( c => (int) c.Value! ), - _ => constants.Aggregate( 1, ( acc, c ) => acc * (int) c.Value! ) - }; - - otherTerms.Insert( 0, Expression.Constant( constantResult ) ); - - return otherTerms.Aggregate( ( acc, term ) => Expression.MakeBinary( node.NodeType, acc, term ) ); - } - - private static IEnumerable FlattenBinaryExpression( Expression expr, ExpressionType type ) - { - if ( expr is BinaryExpression binary && binary.NodeType == type ) - { - return FlattenBinaryExpression( binary.Left, type ).Concat( FlattenBinaryExpression( binary.Right, type ) ); - } - - return [expr]; - } -} diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs deleted file mode 100644 index 56b4d9f..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/StructuralReductionVisitor.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -public class StructuralReductionVisitor : ExpressionVisitor, IExpressionTransformer -{ - public int Priority => PriorityGroup.StructuralReductionAndConsolidation + 50; - - public Expression Transform( Expression expression ) - { - return Visit( expression ); - } - - // Visit BlockExpression, handling block flattening and reduction - protected override Expression VisitBlock( BlockExpression node ) - { - var variables = new List( node.Variables ); - var expressions = new List(); - - foreach ( var expr in node.Expressions ) - { - var visitedExpr = Visit( expr ); - - // Skip over no-op expressions and empty blocks - if ( IsNoOpExpression( visitedExpr ) ) - continue; - - // Flatten nested blocks - if ( visitedExpr is BlockExpression nestedBlock ) - { - variables.AddRange( nestedBlock.Variables ); - expressions.AddRange( nestedBlock.Expressions ); - } - else - { - expressions.Add( visitedExpr ); - } - } - - // Return the single expression directly if there are no variables - if ( variables.Count == 0 && expressions.Count == 1 ) - { - return expressions[0]; - } - - // Construct a new block only if there are variables or multiple expressions - return Expression.Block( variables, expressions ); - } - - - // Visit GotoExpression, eliminating redundant Goto expressions - protected override Expression VisitGoto( GotoExpression node ) - { - if ( IsNoOpExpression( node.Value ) ) - { - return Expression.Empty(); // Skip no-op Gotos - } - - return base.VisitGoto( node ); - } - - // Visit LoopExpression, optimizing based on loop content - protected override Expression VisitLoop( LoopExpression node ) - { - var visitedBody = Visit( node.Body ); - - if ( IsFalsyConstant( visitedBody ) || IsNoOpExpression( visitedBody ) || IsEffectivelyInfiniteNoOpLoop( visitedBody ) ) - { - return node.Type == typeof( void ) ? Expression.Empty() : Expression.Default( node.Type ); - } - - return ReferenceEquals( node.Body, visitedBody ) - ? node - : Expression.Loop( visitedBody, node.BreakLabel, node.ContinueLabel ); - } - - // Visit ConditionalExpression, simplifying based on constant conditions - protected override Expression VisitConditional( ConditionalExpression node ) - { - var test = Visit( node.Test ); - - if ( test is ConstantExpression constantTest && constantTest.Value is bool boolTest ) - { - // Replace based on constant true or false - return boolTest ? Visit( node.IfTrue ) : Visit( node.IfFalse ); - } - - return node.Update( test, Visit( node.IfTrue ), Visit( node.IfFalse ) ); - } - - // Visit TryCatchExpression, simplifying empty or no-op TryCatch blocks - protected override Expression VisitTry( TryExpression node ) - { - var body = Visit( node.Body ); - - // Remove Try if catches are no-ops or finally has no effect - if ( node.Handlers.All( handler => IsNoOpExpression( handler.Body ) ) && (node.Finally == null || IsNoOpExpression( node.Finally )) ) - { - return body; - } - - return node.Update( body, node.Handlers, node.Finally, node.Fault ); - } - - // Helper methods for loop reduction and simplification - private static bool IsFalsyConstant( Expression expression ) - { - return expression is ConstantExpression constant && constant.Value switch - { - null => true, - bool boolValue => !boolValue, - int intValue => intValue == 0, - double doubleValue => doubleValue == 0.0, - float floatValue => floatValue == 0.0f, - _ => false - }; - } - - private static bool IsNoOpExpression( Expression expression ) - { - return expression is DefaultExpression || - (expression is BlockExpression block && block.Expressions.All( IsNoOpExpression )); - } - - private static bool IsEffectivelyInfiniteNoOpLoop( Expression expression ) - { - return expression is ConstantExpression && !HasControlFlowOrSideEffects( expression ); - } - - private static bool HasControlFlowOrSideEffects( Expression expression ) - { - return expression is not ConstantExpression; - } -} diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs deleted file mode 100644 index 26a95e0..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/SubexpressionCachingVisitor.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System.Linq.Expressions; -using System.Reflection; -using Hyperbee.Expressions.Visitors; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -public class SubexpressionCachingVisitor : ExpressionVisitor, IExpressionTransformer -{ - private readonly Dictionary _fingerprintCache = new(new ByteArrayComparer()); - private readonly Dictionary _cacheVariables = new(new ByteArrayComparer()); - private readonly Queue<(Expression Original, ParameterExpression Variable)> _deferredReplacements = new(); - private readonly ExpressionFingerprinter _fingerprinter = new(); - private readonly CacheableHelper _cacheHelper = new(); - - private int _cacheVariableCounter; - - public int Priority => PriorityGroup.ExpressionReductionAndCaching + 70; - - public Expression Transform( Expression expression ) - { - _deferredReplacements.Clear(); - var visitedExpression = Visit( expression ); - - if ( _deferredReplacements.Count == 0 ) - { - return visitedExpression; - } - - var blockExpressions = new List(); - var variables = new List(); - - if ( visitedExpression is BlockExpression originalBlock ) - { - variables.AddRange( originalBlock.Variables ); - blockExpressions.AddRange( originalBlock.Expressions ); - } - else - { - blockExpressions.Add( visitedExpression ); - } - - while ( _deferredReplacements.Count > 0 ) - { - var (original, cacheVariable) = _deferredReplacements.Dequeue(); - - if ( !variables.Contains( cacheVariable ) ) - { - variables.Add( cacheVariable ); - } - - blockExpressions.Insert( 0, Expression.Assign( cacheVariable, original ) ); - } - - return Expression.Block( variables, blockExpressions ); - } - - public override Expression Visit( Expression node ) - { - var visited = base.Visit( node ); - return ResolveExpression( node, visited ); - } - - private Expression ResolveExpression( Expression original, Expression visitedNode ) - { - if ( !_cacheHelper.IsCacheable( original ) ) - { - return visitedNode; - } - - var fingerprint = _fingerprinter.ComputeFingerprint( visitedNode ); - - if ( !_fingerprintCache.TryGetValue( fingerprint, out var cachedNode ) ) - { - _fingerprintCache[fingerprint] = visitedNode; - cachedNode = visitedNode; - } - - if ( _cacheVariables.TryGetValue( fingerprint, out var cacheVariable ) ) - { - return cacheVariable; - } - - cacheVariable = Expression.Variable( cachedNode.Type, $"cacheVar{_cacheVariableCounter++}" ); - _cacheVariables[fingerprint] = cacheVariable; - _deferredReplacements.Enqueue( (visitedNode, cacheVariable) ); - - return cacheVariable; - } - - private class ByteArrayComparer : IEqualityComparer - { - public bool Equals( byte[] x, byte[] y ) - { - if ( ReferenceEquals( x, y ) ) - return true; - - if ( x == null || y == null || x.Length != y.Length ) - return false; - - for ( var i = 0; i < x.Length; i++ ) - { - if ( x[i] != y[i] ) - return false; - } - - return true; - } - - public int GetHashCode( byte[] bytes ) - { - var hash = 17; - - foreach ( var b in bytes ) - { - hash = hash * 31 + b; - } - - return hash; - } - } - - protected class CacheableHelper - { - private readonly ImmutabilityVisitor _visitor = new(); - - public bool IsCacheable( Expression expression ) - { - return IsImmutable( expression ) && IsComplexEnoughToCache( expression ); - } - - private bool IsImmutable( Expression expression ) - { - return _visitor.IsImmutable( expression ); - } - - public static bool IsComplexEnoughToCache( Expression node ) - { - return node switch - { - BinaryExpression binary => - (binary.Left is not ConstantExpression || binary.Right is not ConstantExpression) && - (IsComplexEnoughToCache( binary.Left ) || - IsComplexEnoughToCache( binary.Right ) || - binary.NodeType is - ExpressionType.Add or - ExpressionType.Multiply or - ExpressionType.Divide or - ExpressionType.Subtract), - - MethodCallExpression or MemberExpression or InvocationExpression => true, - UnaryExpression unary => IsComplexEnoughToCache( unary.Operand ), - ConditionalExpression or NewExpression or NewArrayExpression => true, - LambdaExpression lambda => lambda.Parameters.Count > 0 || IsComplexEnoughToCache( lambda.Body ), - MemberInitExpression or ListInitExpression or IndexExpression => true, - ConstantExpression => false, - _ => false - }; - } - } - - private class ImmutabilityVisitor : ExpressionVisitor - { - private static readonly Dictionary ImmutableTypeCache = new(); - - private bool _isImmutable; - - public bool IsImmutable( Expression node ) - { - _isImmutable = true; - Visit( node ); - return _isImmutable; - } - - protected override Expression VisitMember( MemberExpression node ) - { - if ( !IsImmutableType( node.Type ) ) - { - _isImmutable = false; - } - - return base.VisitMember( node ); - } - - protected override Expression VisitParameter( ParameterExpression node ) - { - if ( !IsImmutableType( node.Type ) ) - { - _isImmutable = false; - } - - return base.VisitParameter( node ); - } - - private static bool IsImmutableType( Type type ) - { - if ( ImmutableTypeCache.TryGetValue( type, out var isImmutable ) ) - { - return isImmutable; - } - - if ( type == typeof(Guid) || typeof(IConvertible).IsAssignableFrom( type ) ) - { - return ImmutableTypeCache[type] = true; - } - - if ( type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) ) - { - return ImmutableTypeCache[type] = IsImmutableType( type.GetGenericArguments()[0] ); - } - - if ( type.IsValueType ) - { - return ImmutableTypeCache[type] = type - .GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic ) - .All( field => IsImmutableType( field.FieldType ) ); - } - - if ( type.IsClass ) - { - return ImmutableTypeCache[type] = type - .GetProperties() - .All( property => property.CanRead && !property.CanWrite && IsImmutableType( property.PropertyType ) ); - } - - return ImmutableTypeCache[type] = false; - } - } -} diff --git a/src/Hyperbee.Expressions/Optimizers/Visitors/VariableReducerVisitor.cs b/src/Hyperbee.Expressions/Optimizers/Visitors/VariableReducerVisitor.cs deleted file mode 100644 index cf3727a..0000000 --- a/src/Hyperbee.Expressions/Optimizers/Visitors/VariableReducerVisitor.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Linq.Expressions; - -namespace Hyperbee.Expressions.Optimizers.Visitors; - -// VariableReducerVisitor: Inlining and Memory Optimization -// -// This visitor combines inlining and memory optimization by inlining single-use variables, removing unused -// variables, and reusing parameters where possible. This helps in reducing both memory usage and -// unnecessary assignments, making the code more efficient. -// -// Example Transformations: -// Before: -// .Block( -// .Assign(.Parameter(x), .Constant(5)), -// .Add(.Parameter(x), .Parameter(x)) -// ) -// -// After: -// .Add(.Constant(5), .Constant(5)) -// -// Before: -// .Block( -// .Assign(.Parameter(temp), .Constant(42)), -// .Parameter(temp) -// ) -// -// After: -// .Constant(42) - -public class VariableReducerVisitor : ExpressionVisitor, IExpressionTransformer -{ - private readonly Dictionary _replacements = new(); - private readonly Dictionary _reusedParameters = new(); - private readonly HashSet _uniqueVariables = []; - - public int Priority => PriorityGroup.ControlFlowAndVariableSimplification + 40; - - public Expression Transform( Expression expression ) - { - _replacements.Clear(); - _reusedParameters.Clear(); - _uniqueVariables.Clear(); - - return Visit( expression ); - } - - protected override Expression VisitBlock( BlockExpression node ) - { - //var variables = new List(); - var expressions = new List(); - var variableUsageCount = new Dictionary(); - - // Count usages and track replacements for inlining - foreach ( var expr in node.Expressions ) - { - if ( expr is BinaryExpression binaryExpr && binaryExpr.NodeType == ExpressionType.Assign && - binaryExpr.Left is ParameterExpression variable ) - { - variableUsageCount.TryAdd( variable, 0 ); - variableUsageCount[variable]++; - _replacements[variable] = binaryExpr.Right; - } - } - - // Inline or retain variables based on usage - foreach ( var expr in node.Expressions ) - { - switch ( expr ) - { - case BinaryExpression binaryExpr when binaryExpr.NodeType == ExpressionType.Assign && - binaryExpr.Left is ParameterExpression variable: - if ( variableUsageCount[variable] == 1 && _replacements[variable] is ConstantExpression ) - { - // Inline single-use constant assignments - expressions.Add( Visit( _replacements[variable] ) ); - } - else - { - _uniqueVariables.Add( variable ); - //variables.Add(variable); - expressions.Add( Visit( expr ) ); - } - break; - default: - expressions.Add( Visit( expr ) ); - break; - } - } - - // Remove unused variables and consolidate variable declarations - var finalVariables = new List( _uniqueVariables ); - - return finalVariables.Count == 0 ? expressions.Last() : Expression.Block( finalVariables, expressions ); - } - - protected override Expression VisitParameter( ParameterExpression node ) - { - // Reuse parameters if already processed - if ( _reusedParameters.TryGetValue( node, out var reusedParam ) ) - { - return reusedParam; - } - - if ( _replacements.TryGetValue( node, out var replacement ) ) - { - return Visit( replacement ); - } - - _reusedParameters[node] = node; - return base.VisitParameter( node ); - } - - protected override Expression VisitLoop( LoopExpression node ) - { - var visitedBody = Visit( node.Body ); - - if ( ReferenceEquals( visitedBody, node.Body ) ) - return node; - - if ( visitedBody is ConstantExpression constantBody ) - return constantBody; - - return visitedBody; - } -} - diff --git a/src/Hyperbee.Expressions/Transformation/NodeExpression.cs b/src/Hyperbee.Expressions/Transformation/NodeExpression.cs index 4222608..1e2d80f 100644 --- a/src/Hyperbee.Expressions/Transformation/NodeExpression.cs +++ b/src/Hyperbee.Expressions/Transformation/NodeExpression.cs @@ -44,7 +44,14 @@ public override Expression Reduce() if ( StateMachineSource == null ) throw new InvalidOperationException( $"Reduce requires an {nameof( Transformation.StateMachineSource )} instance." ); - return Transition.Reduce( this ); + var expressions = new List( 8 ) { Label( NodeLabel ) }; + expressions.AddRange( Expressions ); + + Transition.GetExpressions( this, expressions ); + + return expressions.Count == 1 + ? expressions[0] + : Block( expressions ); } internal static List Merge( List nodes ) //BF ME - not sure if this is the right place or not diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs index e4709d6..d5b4f7f 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitResultTransition.cs @@ -12,11 +12,12 @@ internal class AwaitResultTransition : Transition internal override NodeExpression FallThroughNode => TargetNode; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return GetExpressions(); + expressions.AddRange( Expressions() ); + return; - List GetExpressions() + List Expressions() { var getResultMethod = AwaitBinder.GetResultMethod; @@ -33,13 +34,42 @@ List GetExpressions() : [getResultCall, transition]; } - return [ + return + [ Assign( ResultVariable, getResultCall ), GotoOrFallThrough( parent.StateOrder, TargetNode ) ]; } } + //protected override List GetBody(NodeExpression parent ) + //{ + // return GetExpressions(); + + // List GetExpressions() + // { + // var getResultMethod = AwaitBinder.GetResultMethod; + + // var getResultCall = getResultMethod.IsStatic + // ? Call( getResultMethod, AwaiterVariable ) + // : Call( Constant( AwaitBinder ), getResultMethod, AwaiterVariable ); + + // if ( ResultVariable == null ) + // { + // var transition = GotoOrFallThrough( parent.StateOrder, TargetNode ); + + // return transition == Empty() + // ? [getResultCall] + // : [getResultCall, transition]; + // } + + // return [ + // Assign( ResultVariable, getResultCall ), + // GotoOrFallThrough( parent.StateOrder, TargetNode ) + // ]; + // } + //} + internal override void Optimize( HashSet references ) { TargetNode = OptimizeGotos( TargetNode ); diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs index d75f0aa..6e88943 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/AwaitTransition.cs @@ -15,11 +15,12 @@ internal class AwaitTransition : Transition internal override NodeExpression FallThroughNode => CompletionNode; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return GetExpressions(); + expressions.AddRange( Expressions() ); + return; - List GetExpressions() + List Expressions() { var resolverSource = parent.StateMachineSource; var getAwaiterMethod = AwaitBinder.GetAwaiterMethod; diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs index 513a74b..fd92a64 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/ConditionalTransition.cs @@ -11,11 +11,12 @@ internal class ConditionalTransition : Transition internal override NodeExpression FallThroughNode => IfFalse; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return [GetExpression()]; + expressions.Add( Expression() ); + return; - Expression GetExpression() + Expression Expression() { var fallThrough = GotoOrFallThrough( parent.StateOrder, IfFalse, true ); diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs index e187fc0..0edce5a 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/FinalTransition.cs @@ -11,12 +11,11 @@ internal override void Optimize( HashSet references ) { } - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return EmptyBody; } - protected override void AssignResult( NodeExpression parent, List expressions ) + protected override void SetResult( List expressions, NodeExpression parent ) { var resultField = parent.StateMachineSource.ResultField; var returnValue = parent.StateMachineSource.ReturnValue; diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs index a97da0f..f978ee3 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/GotoTransition.cs @@ -7,9 +7,9 @@ internal class GotoTransition : Transition public NodeExpression TargetNode { get; set; } internal override NodeExpression FallThroughNode => TargetNode; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return [GotoOrFallThrough( parent.StateOrder, TargetNode )]; + expressions.Add( GotoOrFallThrough( parent.StateOrder, TargetNode ) ); } internal override void Optimize( HashSet references ) diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs index 9eabb07..14d44e6 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/LoopTransition.cs @@ -10,9 +10,8 @@ internal class LoopTransition : Transition internal override NodeExpression FallThroughNode => BodyNode; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return EmptyBody; } internal override void Optimize( HashSet references ) diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs index 2da0842..a9badcc 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/SwitchTransition.cs @@ -11,11 +11,12 @@ internal class SwitchTransition : Transition internal override NodeExpression FallThroughNode => DefaultNode; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return [GetExpression()]; + expressions.Add( Expression() ); + return; - Expression GetExpression() + Expression Expression() { Expression defaultBody; diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs index e9351cf..bd4a589 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs @@ -14,41 +14,16 @@ internal abstract class Transition internal abstract void Optimize( HashSet references ); - public Expression Reduce( NodeExpression parent ) + public void GetExpressions( NodeExpression parent, List expressions ) { if ( parent == null ) - throw new InvalidOperationException( $"Transition Reduce requires a {nameof( parent )} instance." ); + throw new InvalidOperationException( $"Transition {nameof(GetExpressions)} requires a {nameof(parent)} instance." ); - var reduced = ReduceInternal( parent ); - - return (reduced.Count == 1) - ? reduced[0] - : Block( reduced ); + SetResult( expressions, parent ); + SetBody( expressions, parent ); } - private List ReduceInternal( NodeExpression parent ) - { - var expressions = new List( 8 ) // Label, Expressions, AssignResult, Transition - { - Label( parent.NodeLabel ) - }; - - expressions.AddRange( parent.Expressions ); - - // add result assignment - - AssignResult( parent, expressions ); - - // add transition body - - expressions.AddRange( GetBody( parent ) ); - - return expressions; - } - - protected abstract List GetBody( NodeExpression parent ); - - protected virtual void AssignResult( NodeExpression parent, List expressions ) + protected virtual void SetResult( List expressions, NodeExpression parent ) { var resultValue = parent.ResultValue; var resultVariable = parent.ResultVariable; @@ -63,6 +38,8 @@ protected virtual void AssignResult( NodeExpression parent, List exp } } + protected abstract void SetBody( List expressions, NodeExpression parent ); + protected static Expression GotoOrFallThrough( int order, NodeExpression node, bool allowNull = false ) { return order + 1 == node.StateOrder diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs index 3ed770a..3cce2e6 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/TryCatchTransition.cs @@ -17,11 +17,12 @@ internal class TryCatchTransition : Transition internal override NodeExpression FallThroughNode => TryNode; - protected override List GetBody(NodeExpression parent ) + protected override void SetBody( List expressions, NodeExpression parent ) { - return GetExpressions(); + expressions.AddRange( Expressions() ); + return; - List GetExpressions() + List Expressions() { var body = new List { diff --git a/test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs b/test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs deleted file mode 100644 index c6418bd..0000000 --- a/test/Hyperbee.Expressions.Benchmark/OptimizerBenchmarks.cs +++ /dev/null @@ -1,358 +0,0 @@ -using System.Linq.Expressions; -using BenchmarkDotNet.Attributes; -using Hyperbee.Expressions.Optimizers; - -namespace Hyperbee.Expressions.Benchmark; - -[MemoryDiagnoser] -public class OptimizerBenchmarks -{ - private ExpressionOptimizer _optimizer; - private Expression _unoptimizedTree; - - /* - - // Inlining Optimizer - [GlobalSetup( Targets = - [ - nameof(BenchmarkInlining), - nameof(ExecuteUnoptimizedInlining), - nameof(ExecuteOptimizedInlining) - ] )] - public void SetupInlining() - { - const int Iterations = 1000; - _optimizer = new InliningOptimizer(); - - var labelTarget = Expression.Label( typeof( int ) ); - var paramX = Expression.Parameter( typeof( int ), "x" ); - var counter = Expression.Parameter( typeof( int ), "counter" ); - - var lambda = Expression.Lambda( - Expression.Add( paramX, Expression.Constant( 5 ) ), - paramX - ); - - var targetExpr = Expression.Invoke( lambda, paramX ); - - _unoptimizedTree = Expression.Block( - [paramX, counter], - Expression.Assign( paramX, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.Block( - targetExpr, - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( Iterations ) ), - Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - Expression.Break( labelTarget, targetExpr ) - ) - ), - labelTarget - ) - ); - } - - [Benchmark] - public Expression BenchmarkInlining() - { - return _optimizer.Optimize( _unoptimizedTree ); - } - - [Benchmark] - public int ExecuteUnoptimizedInlining() - { - var lambda = Expression.Lambda>( _unoptimizedTree ); - return lambda.Compile()(); - } - - [Benchmark] - public int ExecuteOptimizedInlining() - { - var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); - var lambda = Expression.Lambda>( optimizedTree ); - return lambda.Compile()(); - } - - // Operator Reduction Optimizer - [GlobalSetup( Targets = - [ - nameof(BenchmarkOperatorReduction), - nameof(ExecuteUnoptimizedOperatorReduction), - nameof(ExecuteOptimizedOperatorReduction) - ] )] - public void SetupOperatorReduction() - { - const int Iterations = 1000; - _optimizer = new OperatorReductionOptimizer(); - - var labelTarget = Expression.Label( typeof( int ) ); - var paramX = Expression.Parameter( typeof( int ), "x" ); - var counter = Expression.Parameter( typeof( int ), "counter" ); - - var nestedExpr = Expression.Multiply( - Expression.Add( paramX, Expression.Constant( 0 ) ), - Expression.Add( Expression.Constant( 1 ), paramX ) - ); - - var targetExpr = Expression.Add( - nestedExpr, - Expression.Multiply( - Expression.Add( Expression.Constant( 2 ), Expression.Constant( 3 ) ), - Expression.Constant( 1 ) - ) - ); - - _unoptimizedTree = Expression.Block( - [paramX, counter], - Expression.Assign( paramX, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.Block( - targetExpr, - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( Iterations ) ), - Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - Expression.Break( labelTarget, targetExpr ) - ) - ), - labelTarget - ) - ); - } - - [Benchmark] - public Expression BenchmarkOperatorReduction() - { - return _optimizer.Optimize( _unoptimizedTree ); - } - - [Benchmark] - public int ExecuteUnoptimizedOperatorReduction() - { - var lambda = Expression.Lambda>( _unoptimizedTree ); - return lambda.Compile()(); - } - - [Benchmark] - public int ExecuteOptimizedOperatorReduction() - { - var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); - var lambda = Expression.Lambda>( optimizedTree ); - return lambda.Compile()(); - } - - //Structural Reduction Optimizer - [GlobalSetup( Targets = [ - nameof(BenchmarkStructuralReduction), - nameof(ExecuteUnoptimizedStructuralReduction), - nameof(ExecuteOptimizedStructuralReduction) - ] )] - public void SetupStructuralReduction() - { - const int Iterations = 1000; - _optimizer = new StructuralReductionOptimizer(); - - var labelTarget = Expression.Label( typeof( int ) ); - var paramX = Expression.Parameter( typeof( int ), "x" ); - var counter = Expression.Parameter( typeof( int ), "counter" ); - - var targetExpr = Expression.Block( - [paramX, counter], - Expression.Block( - Expression.Block( - Expression.IfThen( // Constant conditional - Expression.Constant( true ), - Expression.Assign( - counter, - Expression.Add( counter, Expression.Constant( 30 + 12 ) ) - ) - ), - Expression.Goto( labelTarget, Expression.Constant( 0 ) ) // Redundant Goto - ) - ), - Expression.TryCatch( // Empty TryCatch - Expression.Empty(), - Expression.Catch( Expression.Parameter( typeof( Exception ), "ex" ), Expression.Empty() ) - ) - ); - - _unoptimizedTree = Expression.Block( - [paramX, counter], - Expression.Assign( paramX, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( Iterations ) ), - targetExpr, - Expression.Break( labelTarget, Expression.Constant( 0 ) ) - ), - labelTarget - ) - ); - } - - [Benchmark] - public Expression BenchmarkStructuralReduction() - { - return _optimizer.Optimize( _unoptimizedTree ); - } - - [Benchmark] - public int ExecuteUnoptimizedStructuralReduction() - { - var lambda = Expression.Lambda>( _unoptimizedTree ); - return lambda.Compile()(); - } - - [Benchmark] - public int ExecuteOptimizedStructuralReduction() - { - var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); - var lambda = Expression.Lambda>( optimizedTree ); - return lambda.Compile()(); - } - - */ - - // Subexpression Caching Optimizer - - [GlobalSetup( Targets = [ - nameof(BenchmarkSubexpressionCaching), - nameof(ExecuteUnoptimizedSubexpressionCaching), - nameof(ExecuteOptimizedSubexpressionCaching) - ] )] - public void SetupSubexpressionCaching() - { - const int Iterations = 1000; - _optimizer = new SubexpressionCachingOptimizer(); - - var labelTarget = Expression.Label( typeof( int ) ); - var counter = Expression.Parameter( typeof( int ), "counter" ); - - var repeatedExpr = Expression.Add( - Expression.Multiply( - Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ), - Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) - ), - Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) - ); - - var targetBlock = Expression.Block( - repeatedExpr, - Expression.TryCatch( - Expression.Empty(), - Expression.Catch( Expression.Parameter( typeof( Exception ), "ex" ), Expression.Empty() ) - ) - ); - - _unoptimizedTree = Expression.Block( - [counter], - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( Iterations ) ), - Expression.Block( - Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - targetBlock - ), - Expression.Break( labelTarget, Expression.Constant( 0 ) ) - ), - labelTarget - ) - ); - } - - [Benchmark] - public Expression BenchmarkSubexpressionCaching() - { - return _optimizer.Optimize( _unoptimizedTree ); - } - - [Benchmark] - public int ExecuteUnoptimizedSubexpressionCaching() - { - var lambda = Expression.Lambda>( _unoptimizedTree ); - return lambda.Compile()(); - } - - //[Benchmark] - public int ExecuteOptimizedSubexpressionCaching() - { - var optimizedTree = _optimizer.Optimize( _unoptimizedTree ); - var lambda = Expression.Lambda>( optimizedTree ); - return lambda.Compile()(); - } - - //// Value Binding Optimizer - //[GlobalSetup( Targets = [ - // nameof(BenchmarkValueBinding), - // nameof(ExecuteUnoptimizedValueBinding), - // nameof(ExecuteOptimizedValueBinding) - //] )] - //public void SetupValueBinding() - //{ - // _optimizer = new ValueBindingOptimizer(); - - // var labelTarget = Expression.Label( typeof(int) ); // Change to int - // var tempVar = Expression.Variable( typeof(string), "temp" ); - // var counter = Expression.Variable( typeof(int), "counter" ); - - // var containerAccess = Expression.Constant( new Container() ); - // var nestedProperty = Expression.Property( containerAccess, nameof(Container.Nested) ); - // var nestedAccess = Expression.Property( nestedProperty, nameof(Container.Nested.Value) ); - - // _unoptimizedTree = Expression.Block( - // [tempVar, counter], - // Expression.Assign( tempVar, Expression.Constant( string.Empty ) ), - // Expression.Assign( counter, Expression.Constant( 0 ) ), - // Expression.Loop( - // Expression.Block( - // Expression.Assign( tempVar, nestedAccess ), - // Expression.IfThenElse( - // Expression.LessThan( counter, Expression.Constant( 10 ) ), - // Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - // Expression.Break( labelTarget, counter ) - // ) - // ), - // labelTarget - // ) - // ); - //} - - //[Benchmark] - //public Expression BenchmarkValueBinding() - //{ - // return _optimizer.Optimize(_unoptimizedTree); - //} - - //[Benchmark] - //public int ExecuteUnoptimizedValueBinding() - //{ - // var lambda = Expression.Lambda>(_unoptimizedTree); - // return lambda.Compile()(); - //} - - //[Benchmark] - //public int ExecuteOptimizedValueBinding() - //{ - // var optimizedTree = _optimizer.Optimize(_unoptimizedTree); - // var lambda = Expression.Lambda>(optimizedTree); - // return lambda.Compile()(); - //} - - private class TestClass - { - public string Method() => "Result"; - } - - private class Container - { - public NestedClass Nested { get; } = new (); - } - - private class NestedClass - { - public string Value => "ExpectedValue"; - } -} diff --git a/test/Hyperbee.Expressions.Benchmark/Program.cs b/test/Hyperbee.Expressions.Benchmark/Program.cs index 33961b8..695272a 100644 --- a/test/Hyperbee.Expressions.Benchmark/Program.cs +++ b/test/Hyperbee.Expressions.Benchmark/Program.cs @@ -6,40 +6,6 @@ internal class Program { static void Main( string[] args ) { - //try - //{ - // var bm = new OptimizerBenchmarks(); - - //bm.SetupInlining(); - //bm.BenchmarkInlining(); - //bm.ExecuteUnoptimizedInlining(); - //bm.ExecuteOptimizedInlining(); - - //bm.SetupOperatorReduction(); - //bm.BenchmarkOperatorReduction(); - //bm.ExecuteUnoptimizedOperatorReduction(); - //bm.ExecuteOptimizedOperatorReduction(); - - //bm.SetupStructuralReduction(); - //bm.BenchmarkStructuralReduction(); - //bm.ExecuteUnoptimizedStructuralReduction(); - //bm.ExecuteOptimizedStructuralReduction(); - - //bm.SetupSubexpressionCaching(); - //bm.BenchmarkSubexpressionCaching(); - //bm.ExecuteUnoptimizedSubexpressionCaching(); - //bm.ExecuteOptimizedSubexpressionCaching(); // execution of optimized throws - - //bm.SetupValueBinding(); - //bm.BenchmarkValueBinding(); // optimizer throws - //bm.ExecuteUnoptimizedValueBinding(); - //bm.ExecuteOptimizedValueBinding(); - //} - //catch ( Exception ex ) - //{ - // Console.WriteLine( ex ); - //} - BenchmarkSwitcher.FromAssembly( typeof( Program ).Assembly ).Run( args, new BenchmarkConfig.Config() ); } } diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs deleted file mode 100644 index 34ed3eb..0000000 --- a/test/Hyperbee.Expressions.Tests/Optimizers/InliningOptimizerTests.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Linq.Expressions; -using Hyperbee.Expressions.Optimizers; - -namespace Hyperbee.Expressions.Tests.Optimizers; - -[TestClass] -public class InliningOptimizerTests -{ - [TestMethod] - public void Inlining_ShouldCompile() - { - // Arrange - - var labelTarget = Expression.Label( typeof( int ) ); - var paramX = Expression.Parameter( typeof( int ), "x" ); - var counter = Expression.Parameter( typeof( int ), "counter" ); - - var lambda = Expression.Lambda( - Expression.Add( paramX, Expression.Constant( 5 ) ), - paramX - ); - - var targetExpr = Expression.Invoke( lambda, paramX ); - - var block = Expression.Block( - [paramX, counter], - Expression.Assign( paramX, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.Block( - targetExpr, - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - Expression.Break( labelTarget, targetExpr ) - ) - ), - labelTarget - ) - ); - - var optimizer = new InliningOptimizer(); - - // Act - var optimized = optimizer.Optimize( block ); - - // Assert - var compiled = Expression.Lambda>( optimized ).Compile(); - } - - [TestMethod] - public void Inlining_ShouldInlineSimpleConstant() - { - // Before: .Add(.Constant(10), .Constant(5)) - // After: .Constant(15) - - // Arrange - var expression = Expression.Add( Expression.Constant( 10 ), Expression.Constant( 5 ) ); - var optimizer = new InliningOptimizer(); - - // Act - var result = optimizer.Optimize( expression ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( 15, value ); - } - - [TestMethod] - public void Inlining_ShouldInlineLambdaExpression() - { - // Before: .Invoke((x) => x + 5, .Constant(3)) - // After: .Constant(8) - - // Arrange - var parameter = Expression.Parameter( typeof( int ), "x" ); - var lambda = Expression.Lambda( Expression.Add( parameter, Expression.Constant( 5 ) ), parameter ); - var invocation = Expression.Invoke( lambda, Expression.Constant( 3 ) ); - var optimizer = new InliningOptimizer(); - - // Act - var result = optimizer.Optimize( invocation ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( 8, value ); - } - - [TestMethod] - public void Inlining_ShouldShortCircuitBoolean() - { - // Before: .AndAlso(.Constant(true), .Constant(false)) - // After: .Constant(false) - - // Arrange - var expression = Expression.AndAlso( Expression.Constant( true ), Expression.Constant( false ) ); - var optimizer = new InliningOptimizer(); - - // Act - var result = optimizer.Optimize( expression ); - var value = (bool) ((ConstantExpression) result).Value!; - - // Assert - Assert.IsFalse( value ); - } - - [TestMethod] - public void Inlining_ShouldInlineConditionalExpression() - { - // Before: .Conditional(.Constant(true), .Constant("True"), .Constant("False")) - // After: .Constant("True") - - // Arrange - var condition = Expression.Constant( true ); - var conditional = Expression.Condition( condition, Expression.Constant( "True" ), Expression.Constant( "False" ) ); - var optimizer = new InliningOptimizer(); - - // Act - var result = optimizer.Optimize( conditional ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( "True", value ); - } - - [TestMethod] - public void Inlining_ShouldSimplifyNestedConditionalExpression() - { - // Before: .Conditional(.Constant(true), .Conditional(.Constant(false), .Constant("A"), .Constant("B")), .Constant("C")) - // After: .Constant("B") - - // Arrange - var innerCondition = Expression.Condition( - Expression.Constant( false ), - Expression.Constant( "A" ), - Expression.Constant( "B" ) - ); - var outerCondition = Expression.Condition( - Expression.Constant( true ), - innerCondition, - Expression.Constant( "C" ) - ); - var optimizer = new InliningOptimizer(); - - // Act - var result = optimizer.Optimize( outerCondition ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( "B", value ); - } -} diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs deleted file mode 100644 index 119fe02..0000000 --- a/test/Hyperbee.Expressions.Tests/Optimizers/OperatorReductionOptimizerTests.cs +++ /dev/null @@ -1,166 +0,0 @@ -using System.Linq.Expressions; -using Hyperbee.Expressions.Optimizers; - -namespace Hyperbee.Expressions.Tests.Optimizers; - -[TestClass] -public class OperatorReductionOptimizerTests -{ - [TestMethod] - public void OperatorReduction_ShouldCompile() - { - // Arrange - - var labelTarget = Expression.Label( typeof(int) ); - var paramX = Expression.Parameter( typeof(int), "x" ); - var counter = Expression.Parameter( typeof(int), "counter" ); - - var nestedExpr = Expression.Multiply( - Expression.Add( paramX, Expression.Constant( 0 ) ), - Expression.Add( Expression.Constant( 1 ), paramX ) - ); - - var targetExpr = Expression.Add( - nestedExpr, - Expression.Multiply( - Expression.Add( Expression.Constant( 2 ), Expression.Constant( 3 ) ), - Expression.Constant( 1 ) - ) - ); - - var block = Expression.Block( - [paramX, counter], - Expression.Assign( paramX, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.Block( - targetExpr, - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - Expression.Break( labelTarget, targetExpr ) - ) - ), - labelTarget - ) - ); - - var optimizer = new OperatorReductionOptimizer(); - - // Act - var optimized = optimizer.Optimize( block ); - - // Assert - var compiled = Expression.Lambda>( optimized ).Compile(); - } - - [TestMethod] - public void OperatorReduction_ShouldRemoveAddZero() - { - // Before: .Add(.Parameter(x), .Constant(0)) - // After: .Parameter(x) - - // Arrange - var expression = Expression.Add( Expression.Parameter( typeof( int ), "x" ), Expression.Constant( 0 ) ); - var optimizer = new OperatorReductionOptimizer(); - - // Act - var result = optimizer.Optimize( expression ); - - // Assert - Assert.IsInstanceOfType( result, typeof( ParameterExpression ) ); - } - - [TestMethod] - public void OperatorReduction_ShouldRemoveMultiplyByOne() - { - // Before: .Multiply(.Parameter(x), .Constant(1)) - // After: .Parameter(x) - - // Arrange - var expression = Expression.Multiply( Expression.Parameter( typeof( int ), "x" ), Expression.Constant( 1 ) ); - var optimizer = new OperatorReductionOptimizer(); - - // Act - var result = optimizer.Optimize( expression ); - - // Assert - Assert.IsInstanceOfType( result, typeof( ParameterExpression ) ); - } - - [TestMethod] - public void OperatorReduction_ShouldSimplifyLogicalIdentity() - { - // Before: .AndAlso(.Constant(true), .Parameter(x)) - // After: .Parameter(x) - - // Arrange - var andExpression = Expression.AndAlso( Expression.Constant( true ), Expression.Parameter( typeof( bool ), "x" ) ); - var optimizer = new OperatorReductionOptimizer(); - - // Act - var result = optimizer.Optimize( andExpression ); - - // Assert - Assert.IsInstanceOfType( result, typeof( ParameterExpression ) ); - } - - [TestMethod] - public void OperatorReduction_ShouldFlattenNestedExpressions() - { - // Before: .Add(.Add(.Constant(1), .Constant(2)), .Constant(3)) - // After: .Constant(6) - - // Arrange - var nestedExpression = Expression.Add( Expression.Add( Expression.Constant( 1 ), Expression.Constant( 2 ) ), Expression.Constant( 3 ) ); - var optimizer = new OperatorReductionOptimizer(); - - // Act - var result = optimizer.Optimize( nestedExpression ); - var constant = result as ConstantExpression; - var value = constant?.Value; - - // Assert - Assert.IsInstanceOfType( result, typeof( ConstantExpression ), "Expected a ConstantExpression after optimization." ); - Assert.IsNotNull( constant, "Expected a non-null ConstantExpression." ); - Assert.AreEqual( 6, value ); - } - - [TestMethod] - public void OperatorReduction_ShouldSimplifyMultiOperationReduction() - { - // Before: .Multiply(.Add(.Parameter(x), .Constant(0)), .Constant(1)) - // After: .Parameter(x) - - // Arrange - var parameter = Expression.Parameter( typeof( int ), "x" ); - var addZero = Expression.Add( parameter, Expression.Constant( 0 ) ); - var multiplyByOne = Expression.Multiply( addZero, Expression.Constant( 1 ) ); - var optimizer = new OperatorReductionOptimizer(); - - // Act - var result = optimizer.Optimize( multiplyByOne ); - - // Assert - Assert.IsInstanceOfType( result, typeof( ParameterExpression ) ); - } - - [TestMethod] - public void OperatorReduction_ShouldSimplifyComplexBooleanReduction() - { - // Before: .AndAlso(.OrElse(.Constant(true), .Constant(false)), .Constant(true)) - // After: .Constant(true) - - // Arrange - var orExpression = Expression.OrElse( Expression.Constant( true ), Expression.Constant( false ) ); - var andExpression = Expression.AndAlso( orExpression, Expression.Constant( true ) ); - var optimizer = new OperatorReductionOptimizer(); - - // Act - var result = optimizer.Optimize( andExpression ); - var value = (bool) ((ConstantExpression) result).Value!; - - // Assert - Assert.IsTrue( value ); - } -} diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs deleted file mode 100644 index 56495f4..0000000 --- a/test/Hyperbee.Expressions.Tests/Optimizers/StructuralReductionOptimizerTests.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Linq.Expressions; -using Hyperbee.Expressions.Optimizers; - -namespace Hyperbee.Expressions.Tests.Optimizers; - -[TestClass] -public class StructuralReductionOptimizerTests -{ - [TestMethod] - public void StructuralReduction_ShouldCompile() - { - // Arrange - - var labelTarget = Expression.Label( typeof(int) ); - var paramX = Expression.Parameter( typeof(int), "x" ); - var counter = Expression.Parameter( typeof(int), "counter" ); - - var targetBlock = Expression.Block( - [paramX, counter], // Declare 'counter' here to ensure it is scoped correctly - Expression.Block( - Expression.Block( - Expression.IfThen( - Expression.Constant( true ), - Expression.Assign( - counter, - Expression.Add( counter, Expression.Constant( 30 + 12 ) ) - ) - ), - Expression.Goto( labelTarget, Expression.Constant( 0 ) ) // Redundant Goto - ) - ), - Expression.TryCatch( - Expression.Empty(), - Expression.Catch( Expression.Parameter( typeof(Exception), "ex" ), Expression.Empty() ) - ) - ); - - var block = Expression.Block( - [paramX, counter], // Declare variables in the outer block - Expression.Assign( paramX, Expression.Constant( 10 ) ), - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( 1000 ) ), - targetBlock, - Expression.Break( labelTarget, Expression.Constant( 0 ) ) - ), - labelTarget - ) - ); - - var optimizer = new StructuralReductionOptimizer(); - - // Act - var optimized = optimizer.Optimize( block ); - - // Assert - var compiled = Expression.Lambda>( optimized ).Compile(); - } - - [TestMethod] - public void StructuralReduction_ShouldRemoveUnreachableCode() - { - // Before: .Block(.Constant(1), .Constant(2)) - // After: .Constant(1) - - // Arrange - var block = Expression.Block( Expression.Constant( 1 ), Expression.Constant( 2 ) ); - var optimizer = new StructuralReductionOptimizer(); - - // Act - var result = optimizer.Optimize( block ); - var value = ((ConstantExpression) ((BlockExpression) result).Expressions[0]).Value; - - // Assert - Assert.AreEqual( 1, value ); - } - - [TestMethod] - public void StructuralReduction_ShouldSimplifyEmptyTryCatch() - { - // Before: .TryCatch(.Empty(), .Catch(...)) - // After: .Empty() - - // Arrange - var tryCatch = Expression.TryCatch( - Expression.Empty(), - Expression.Catch( Expression.Parameter( typeof( Exception ) ), Expression.Empty() ) - ); - var optimizer = new StructuralReductionOptimizer(); - - // Act - var result = optimizer.Optimize( tryCatch ); - - // Assert - Assert.IsInstanceOfType( result, typeof( DefaultExpression ) ); - } - - [TestMethod] - public void StructuralReduction_ShouldRemoveInfiniteLoop() - { - // Before: .Loop(.Constant(1)) - // After: .Empty() - - // Arrange - var loop = Expression.Loop( Expression.Constant( 1 ) ); - var optimizer = new StructuralReductionOptimizer(); - - // Act - var result = optimizer.Optimize( loop ); - - // Assert - Assert.IsInstanceOfType( result, typeof( DefaultExpression ) ); - } - - [TestMethod] - public void StructuralReduction_ShouldSimplifyNestedConditionalExpression() - { - // Before: .Block(.IfThenElse(.Constant(true), .IfThenElse(.Constant(false), .Break(), .Constant("B")))) - // After: .Constant("B") - - // Arrange - var innerCondition = Expression.IfThenElse( - Expression.Constant( false ), - Expression.Break( Expression.Label() ), - Expression.Constant( "B" ) - ); - var outerCondition = Expression.IfThenElse( - Expression.Constant( true ), - innerCondition, - Expression.Constant( "C" ) - ); - var block = Expression.Block( outerCondition ); - var optimizer = new StructuralReductionOptimizer(); - - // Act - var result = optimizer.Optimize( block ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( "B", value ); - } - - [TestMethod] - public void StructuralReduction_ShouldSimplifyLoopWithComplexCondition() - { - // Before: .Loop(.IfThenElse(.Constant(false), .Break(), .Constant(1))) - // After: .Empty() - - // Arrange - var loopCondition = Expression.IfThenElse( - Expression.Constant( false ), - Expression.Break( Expression.Label() ), - Expression.Constant( 1 ) - ); - var loop = Expression.Loop( loopCondition ); - var optimizer = new StructuralReductionOptimizer(); - - // Act - var result = optimizer.Optimize( loop ); - - // Assert - Assert.IsInstanceOfType( result, typeof( DefaultExpression ) ); - } -} diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs deleted file mode 100644 index e7213bd..0000000 --- a/test/Hyperbee.Expressions.Tests/Optimizers/SubexpressionCachingOptimizerTests.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System.Linq.Expressions; -using Hyperbee.Expressions.Optimizers; -using Hyperbee.Expressions.Tests.TestSupport; - -namespace Hyperbee.Expressions.Tests.Optimizers; - -[TestClass] -public class SubexpressionCachingOptimizerTests -{ - public class TestClass - { - public string Method() => "Result"; - } - - [TestMethod] - public void SubexpressionCaching_ShouldCompile() - { - var labelTarget = Expression.Label( typeof(int) ); - var counter = Expression.Parameter( typeof(int), "counter" ); - - var repeatedExpr = Expression.Add( - Expression.Multiply( - Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ), - Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) - ), - Expression.Add( Expression.Constant( 3 ), Expression.Constant( 5 ) ) - ); - - var targetBlock = Expression.Block( - repeatedExpr, - Expression.TryCatch( - Expression.Empty(), - Expression.Catch( Expression.Parameter( typeof(Exception), "ex" ), Expression.Empty() ) - ) - ); - - var block = Expression.Block( - [counter], - Expression.Assign( counter, Expression.Constant( 0 ) ), - Expression.Loop( - Expression.IfThenElse( - Expression.LessThan( counter, Expression.Constant( 10 ) ), - Expression.Block( - Expression.Assign( counter, Expression.Add( counter, Expression.Constant( 1 ) ) ), - targetBlock - ), - Expression.Break( labelTarget, Expression.Constant( 0 ) ) // Provide default value - ), - labelTarget - ) - ); - - // Arrange - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var optimized = optimizer.Optimize( block ); - - // Assert - var compiled = Expression.Lambda>( optimized ).Compile(); - compiled(); - } - - [TestMethod] - public void SubexpressionCaching_ShouldNotCacheSimpleExpressions() - { - // Before: .Add(.Constant(2), .Constant(3)) + .Add(.Constant(2), .Constant(3)) - // After: .Add(.Constant(2), .Constant(3)) + .Add(.Constant(2), .Constant(3)) - - // Arrange - var simpleExpr = Expression.Add( Expression.Constant( 2 ), Expression.Constant( 3 ) ); - var referenceExpr = Expression.Lambda>( - Expression.Add( simpleExpr, simpleExpr ) - ); - - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var optimizedExpr = optimizer.Optimize( referenceExpr ); - var reference = referenceExpr.Compile(); - var result = reference(); - var optimized = optimizedExpr.Compile(); - var comparand = optimized(); - - // Assert - Assert.AreEqual( result, comparand ); - Assert.IsTrue( optimizedExpr.GetDebugView().Contains( "2 + 3 + 2 + 3" ), "Simple expression should not be cached." ); - } - - [TestMethod] - public void SubexpressionCaching_ShouldCacheComplexExpressions() - { - // Before: .Add(.Multiply(.Constant(5), .Add(.Constant(3), .Constant(2))), .Multiply(.Constant(5), .Add(.Constant(3), .Constant(2)))) - // After: .Block(.Assign(variable, .Multiply(.Constant(5), .Add(.Constant(3), .Constant(2)))), .Add(variable, variable)) - - // Arrange - var complexExpr = Expression.Multiply( Expression.Constant( 5 ), Expression.Add( Expression.Constant( 3 ), Expression.Constant( 2 ) ) ); - var referenceExpr = Expression.Lambda>( - Expression.Add( complexExpr, complexExpr ) - ); - - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var optimizedExpr = optimizer.Optimize( referenceExpr ); - var reference = referenceExpr.Compile(); - var result = reference(); - var optimized = optimizedExpr.Compile(); - var comparand = optimized(); - - // Assert - Assert.AreEqual( result, comparand ); - Assert.IsTrue( optimizedExpr.GetDebugView().Contains( "Block" ), "Complex expression should be cached with a block." ); - } - - [TestMethod] - public void SubexpressionCaching_ShouldCacheRepeatedMethodCalls() - { - // Before: .Add(.Call(Method), .Call(Method)) - // After: .Block(.Assign(variable, .Call(Method)), .Add(variable, variable)) - - // Arrange - Expression> referenceExpr = () => Math.Abs( -5 ) + Math.Abs( -5 ); - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var optimizedExpr = optimizer.Optimize( referenceExpr ); - var reference = referenceExpr.Compile(); - var result = reference(); - var optimized = optimizedExpr.Compile(); - var comparand = optimized(); - - // Assert - Assert.AreEqual( result, comparand ); - Assert.IsTrue( optimizedExpr.GetDebugView().Contains( "Block" ), "Method call should be cached with a block." ); - } - - [TestMethod] - public void SubexpressionCaching_ShouldCacheRepeatedExpressions() - { - // Before: .Add(.Add(x, .Constant(5)), .Add(x, .Constant(5))) - // After: .Block(.Assign(variable, .Add(x, .Constant(5))), .Add(variable, variable)) - - // Arrange - var x = Expression.Parameter( typeof( int ), "x" ); - var repeatedExpr = Expression.Add( x, Expression.Constant( 5 ) ); - var complexExpression = Expression.Add( repeatedExpr, repeatedExpr ); - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var result = optimizer.Optimize( complexExpression ); - - // Assert - Assert.IsInstanceOfType( result, typeof( BlockExpression ) ); - Assert.AreEqual( 2, ((BlockExpression) result).Expressions.Count ); - } - - [TestMethod] - public void SubexpressionCaching_ShouldCacheCrossScopeExpression() - { - // Before: .Block(.Call(obj.Method), .Block(.Call(obj.Method))) - // After: .Block(.Assign(variable, .Call(obj.Method)), .Variable(variable), .Block(.Variable(variable))) - - // Arrange - var methodCall = Expression.Call( Expression.Constant( new TestClass() ), "Method", null ); - var innerBlock = Expression.Block( methodCall ); - var outerBlock = Expression.Block( methodCall, innerBlock ); - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var result = optimizer.Optimize( outerBlock ); - - // Assert - Assert.IsInstanceOfType( result, typeof( BlockExpression ) ); - } - - [TestMethod] - public void SubexpressionCaching_ShouldCacheParameterizedSubexpression() - { - // Before: .Block(.Add(.Parameter(x), .Constant(5)), .Add(.Parameter(x), .Constant(5))) - // After: .Block(.Assign(variable, .Add(.Parameter(x), .Constant(5))), .Variable(variable), .Variable(variable)) - - // Arrange - var parameter = Expression.Parameter( typeof( int ), "x" ); - var addExpression = Expression.Add( parameter, Expression.Constant( 5 ) ); - var block = Expression.Block( addExpression, addExpression ); - var optimizer = new SubexpressionCachingOptimizer(); - - // Act - var result = optimizer.Optimize( block ); - - // Assert - Assert.IsInstanceOfType( result, typeof( BlockExpression ) ); - } -} diff --git a/test/Hyperbee.Expressions.Tests/Optimizers/ValueBindingOptimizerTests.cs b/test/Hyperbee.Expressions.Tests/Optimizers/ValueBindingOptimizerTests.cs deleted file mode 100644 index 7ff0ae5..0000000 --- a/test/Hyperbee.Expressions.Tests/Optimizers/ValueBindingOptimizerTests.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.Linq.Expressions; -using Hyperbee.Expressions.Optimizers; - -namespace Hyperbee.Expressions.Tests.Optimizers; - -[TestClass] -public class ValueBindingOptimizerTests -{ - public class Container - { - public NestedClass Nested { get; } = new(); - } - - public class NestedClass - { - public string Value => "ExpectedValue"; - } - - [TestMethod] - public void ValueBinding_ShouldFoldConstants() - { - // Before: .Add(.Constant(2), .Constant(3)) - // After: .Constant(5) - - // Arrange - var expression = Expression.Add( Expression.Constant( 2 ), Expression.Constant( 3 ) ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( expression ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( 5, value ); - } - - [TestMethod] - public void ValueBinding_ShouldInlineSingleUseVariable() - { - // Before: .Block(.Assign(.Parameter(x), .Constant(10)), .Parameter(x)) - // After: .Constant(10) - - // Arrange - var parameter = Expression.Parameter( typeof( int ), "x" ); - var block = Expression.Block( [parameter], Expression.Assign( parameter, Expression.Constant( 10 ) ), parameter ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( block ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( 10, value ); - } - - [TestMethod] - public void ValueBinding_ShouldSimplifyConstantMemberAccess() - { - // Before: .Property(.Constant(new DateTime(2024, 1, 1)), "Year") - // After: .Constant(2024) - - // Arrange - var date = Expression.Constant( new DateTime( 2024, 1, 1 ) ); - var memberAccess = Expression.Property( date, "Year" ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( memberAccess ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( 2024, value ); - } - - [TestMethod] - public void ValueBinding_ShouldInlineVariableInNestedScope() - { - // Before: .Block(.Assign(.Parameter(x), .Constant(10)), .Block(.Parameter(x))) - // After: .Constant(10) - - // Arrange - var parameter = Expression.Parameter( typeof( int ), "x" ); - var nestedBlock = Expression.Block( parameter ); - var outerBlock = Expression.Block( [parameter], Expression.Assign( parameter, Expression.Constant( 10 ) ), nestedBlock ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( outerBlock ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( 10, value ); - } - - [TestMethod] - public void ValueBinding_ShouldSimplifyComplexMemberAccess() - { - // Before: .Property(.Property(.Constant(new Container()), "Nested"), "Value") - // After: .Constant("ExpectedValue") - - // Arrange - var container = Expression.Constant( new Container() ); - var nestedMember = Expression.Property( container, nameof( Container.Nested ) ); - var memberAccess = Expression.Property( nestedMember, nameof( Container.Nested.Value ) ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( memberAccess ); - var value = ((ConstantExpression) result).Value; - - // Assert - Assert.AreEqual( "ExpectedValue", value ); - } - - [TestMethod] - public void ValueBinding_ShouldReduceRedundantAllocations() - { - // Before: .Block(.Assign(.Parameter(x), .Constant(1)), .Parameter(x)) - // After: .Constant(1) - - // Arrange - var param = Expression.Parameter( typeof( int ), "x" ); - var redundantAlloc = Expression.Block( [param], Expression.Assign( param, Expression.Constant( 1 ) ), param ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( redundantAlloc ); - var constant = (ConstantExpression) result; - var value = constant.Value; - - // Assert - Assert.AreEqual( 1, value ); - } - - [TestMethod] - public void ValueBinding_ShouldReuseTemporaryVariableInLoop() - { - // Before: .Loop(.Block(.Assign(.Parameter(temp), .Constant(42)), .Parameter(temp))) - // After: .Constant(42) - - // Arrange - var tempVar = Expression.Parameter( typeof( int ), "temp" ); - var loop = Expression.Loop( Expression.Block( [tempVar], Expression.Assign( tempVar, Expression.Constant( 42 ) ), tempVar ) ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( loop ); - - // Assert - var resultValue = (result as ConstantExpression)?.Value; - - Assert.IsInstanceOfType( result, typeof( ConstantExpression ) ); - Assert.AreEqual( 42, resultValue, "Expected the loop body constant to have value 42." ); - } - - [TestMethod] - public void ValueBinding_ShouldReduceAllocationsInNestedBlocks() - { - // Before: .Block(.Block(.Assign(.Parameter(temp), .Constant(10)), .Parameter(temp)), .Parameter(temp)) - // After: .Constant(10) - - // Arrange - var tempVar = Expression.Parameter( typeof( int ), "temp" ); - var innerBlock = Expression.Block( [tempVar], Expression.Assign( tempVar, Expression.Constant( 10 ) ), tempVar ); - var outerBlock = Expression.Block( innerBlock, tempVar ); - var optimizer = new ValueBindingOptimizer(); - - // Act - var result = optimizer.Optimize( outerBlock ); - - // Assert - Assert.IsInstanceOfType( result, typeof( ConstantExpression ) ); - } -} From 3ef92697d12fb2195900465046fd0847f4ef0d52 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 2 Dec 2024 17:15:58 +0000 Subject: [PATCH 3/7] Updated code formatting to match rules in .editorconfig --- .../Transformation/Transitions/Transition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs index bd4a589..fc897f3 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs @@ -17,7 +17,7 @@ internal abstract class Transition public void GetExpressions( NodeExpression parent, List expressions ) { if ( parent == null ) - throw new InvalidOperationException( $"Transition {nameof(GetExpressions)} requires a {nameof(parent)} instance." ); + throw new InvalidOperationException( $"Transition {nameof( GetExpressions )} requires a {nameof( parent )} instance." ); SetResult( expressions, parent ); SetBody( expressions, parent ); From 54b1a86ba5461286e4363b86697c42e0f370fadb Mon Sep 17 00:00:00 2001 From: Brenton Farmer Date: Mon, 2 Dec 2024 09:21:11 -0800 Subject: [PATCH 4/7] AddExpressions --- src/Hyperbee.Expressions/Transformation/NodeExpression.cs | 2 +- .../Transformation/Transitions/Transition.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Hyperbee.Expressions/Transformation/NodeExpression.cs b/src/Hyperbee.Expressions/Transformation/NodeExpression.cs index 1e2d80f..dfd62ec 100644 --- a/src/Hyperbee.Expressions/Transformation/NodeExpression.cs +++ b/src/Hyperbee.Expressions/Transformation/NodeExpression.cs @@ -47,7 +47,7 @@ public override Expression Reduce() var expressions = new List( 8 ) { Label( NodeLabel ) }; expressions.AddRange( Expressions ); - Transition.GetExpressions( this, expressions ); + Transition.AddExpressions( this, expressions ); return expressions.Count == 1 ? expressions[0] diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs index fc897f3..0266bf6 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs @@ -14,10 +14,10 @@ internal abstract class Transition internal abstract void Optimize( HashSet references ); - public void GetExpressions( NodeExpression parent, List expressions ) + public void AddExpressions( NodeExpression parent, List expressions ) { if ( parent == null ) - throw new InvalidOperationException( $"Transition {nameof( GetExpressions )} requires a {nameof( parent )} instance." ); + throw new InvalidOperationException( $"Transition {nameof( AddExpressions )} requires a {nameof( parent )} instance." ); SetResult( expressions, parent ); SetBody( expressions, parent ); From adc40d8450f3855512e98b42ef3979ac4570f43e Mon Sep 17 00:00:00 2001 From: Brenton Farmer Date: Mon, 2 Dec 2024 11:12:05 -0800 Subject: [PATCH 5/7] more state-machine clean up --- .../Transformation/StateMachineBuilder.cs | 91 +++++++++++-------- .../Transformation/Transitions/Transition.cs | 21 +++-- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs index 07bac2d..7853acf 100644 --- a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs +++ b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs @@ -331,13 +331,9 @@ FieldInfo[] fields var exitLabel = Label( "ST_EXIT" ); - // Optimize source nodes - - StateMachineOptimizer.Optimize( source ); + //// Optimize source nodes - // Variable Hoisting - - //HoistVariables( source, fields, stateMachine ); + //StateMachineOptimizer.Optimize( source ); // Assign state-machine source to nodes @@ -350,30 +346,20 @@ FieldInfo[] fields source.ReturnValue ); - foreach ( var node in source.Nodes ) - { - node.StateMachineSource = stateMachineSource; // required for node reducers - } + var bodyExpressions = CreateBody( fields, source, stateMachineSource ); - // Add the state-nodes + //foreach ( var node in source.Nodes ) + //{ + // node.StateMachineSource = stateMachineSource; // required for node reducers + //} - var bodyExpressions = CreateBody( stateField, source ); + //// Add the state-nodes and hoist variables - // - var fieldMembers = fields - .Select( field => Field( stateMachine, field ) ) - .ToDictionary( x => x.Member.Name ); - - var hoistingVisitor = new HoistingVisitor( fieldMembers ); - - var rbe = new List(); - - foreach ( var node in bodyExpressions ) - { - rbe.Add( hoistingVisitor.Visit( node ) ); - } - - bodyExpressions = rbe; + //var bodyExpressions = HoistVariables( + // CreateBody( stateField, source ), + // fields, + // stateMachine + //); // Add the final builder result assignment @@ -428,32 +414,65 @@ FieldInfo[] fields ); } - private static List CreateBody( MemberExpression stateField, LoweringResult source ) + private static List CreateBody( FieldInfo[] fields, LoweringResult source, StateMachineSource stateMachineSource ) { + // Optimize source nodes + + StateMachineOptimizer.Optimize( source ); + + // Assign state-machine source to nodes + + foreach ( var node in source.Nodes ) + { + node.StateMachineSource = stateMachineSource; // required for node reducers + } + + // Create the body expressions + var firstScope = source.Scopes.First(); var jumpTable = JumpTableBuilder.Build( firstScope, source.Scopes, - stateField + stateMachineSource.StateIdField ); - return [jumpTable, Block( NodeExpression.Merge( firstScope.Nodes ) )]; //BF ME + List bodyExpressions = [jumpTable, Block( NodeExpression.Merge( firstScope.Nodes ) )]; //BF ME + + // Add the state-nodes and hoist variables + + bodyExpressions = HoistVariables( + bodyExpressions, + fields, + stateMachineSource.StateMachine + ); + + return bodyExpressions; } - [MethodImpl( MethodImplOptions.AggressiveInlining )] - private static void HoistVariables( LoweringResult source, FieldInfo[] fields, ParameterExpression stateMachine ) + //private static List CreateBody( MemberExpression stateField, LoweringResult source ) + //{ + // var firstScope = source.Scopes.First(); + + // var jumpTable = JumpTableBuilder.Build( + // firstScope, + // source.Scopes, + // stateField + // ); + + // return [jumpTable, Block( NodeExpression.Merge( firstScope.Nodes ) )]; //BF ME + //} + + private static List HoistVariables( List expressions, FieldInfo[] fields, ParameterExpression stateMachine ) { var fieldMembers = fields .Select( field => Field( stateMachine, field ) ) .ToDictionary( x => x.Member.Name ); var hoistingVisitor = new HoistingVisitor( fieldMembers ); + var hoisted = expressions.Select( hoistingVisitor.Visit ).ToList(); - foreach ( var node in source.Nodes ) - { - hoistingVisitor.Visit( node ); - } + return hoisted; } private sealed class HoistingVisitor( IDictionary memberExpressions ) : ExpressionVisitor diff --git a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs index 0266bf6..c47c52a 100644 --- a/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs +++ b/src/Hyperbee.Expressions/Transformation/Transitions/Transition.cs @@ -8,12 +8,8 @@ namespace Hyperbee.Expressions.Transformation.Transitions; internal abstract class Transition { - protected static readonly List EmptyBody = [Empty()]; - internal abstract NodeExpression FallThroughNode { get; } - internal abstract void Optimize( HashSet references ); - public void AddExpressions( NodeExpression parent, List expressions ) { if ( parent == null ) @@ -40,12 +36,7 @@ protected virtual void SetResult( List expressions, NodeExpression p protected abstract void SetBody( List expressions, NodeExpression parent ); - protected static Expression GotoOrFallThrough( int order, NodeExpression node, bool allowNull = false ) - { - return order + 1 == node.StateOrder - ? allowNull ? null : Empty() - : Goto( node.NodeLabel ); - } + internal abstract void Optimize( HashSet references ); protected static NodeExpression OptimizeGotos( NodeExpression node ) { @@ -56,5 +47,15 @@ protected static NodeExpression OptimizeGotos( NodeExpression node ) return node; } + + protected static Expression GotoOrFallThrough( int order, NodeExpression node, bool allowNull = false ) + { + if ( order + 1 == node.StateOrder ) + { + return allowNull ? null : Empty(); + } + + return Goto( node.NodeLabel ); + } } From d41b4defe824dd3db83c19076a3f140f7dd195f5 Mon Sep 17 00:00:00 2001 From: Brenton Farmer Date: Mon, 2 Dec 2024 11:14:58 -0800 Subject: [PATCH 6/7] it is helpful to save before commit --- .../Transformation/StateMachineBuilder.cs | 94 ++++++------------- 1 file changed, 30 insertions(+), 64 deletions(-) diff --git a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs index 7853acf..880b64b 100644 --- a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs +++ b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs @@ -331,12 +331,6 @@ FieldInfo[] fields var exitLabel = Label( "ST_EXIT" ); - //// Optimize source nodes - - //StateMachineOptimizer.Optimize( source ); - - // Assign state-machine source to nodes - var stateMachineSource = new StateMachineSource( stateMachine, exitLabel, @@ -346,20 +340,9 @@ FieldInfo[] fields source.ReturnValue ); - var bodyExpressions = CreateBody( fields, source, stateMachineSource ); - - //foreach ( var node in source.Nodes ) - //{ - // node.StateMachineSource = stateMachineSource; // required for node reducers - //} - - //// Add the state-nodes and hoist variables + // Create the body expressions - //var bodyExpressions = HoistVariables( - // CreateBody( stateField, source ), - // fields, - // stateMachine - //); + var bodyExpressions = CreateBody( fields, source, stateMachineSource ); // Add the final builder result assignment @@ -376,38 +359,34 @@ FieldInfo[] fields ) ] ); - // Create a try-catch block to handle exceptions + // Create final lambda with try-catch block var exceptionParam = Parameter( typeof( Exception ), "ex" ); - var tryCatchBlock = TryCatch( - Block( - typeof( void ), - source.ReturnValue != null - ? [source.ReturnValue] - : [], - bodyExpressions - ), - Catch( - exceptionParam, - Block( - Assign( stateField, Constant( -2 ) ), - Call( - builderField, - nameof( AsyncTaskMethodBuilder.SetException ), - null, - exceptionParam - ) - ) - ) - ); - - // Create the final lambda expression - return Lambda( typeof( MoveNextDelegate<> ).MakeGenericType( stateMachineType ), Block( - tryCatchBlock, + TryCatch( + Block( + typeof(void), + source.ReturnValue != null + ? [source.ReturnValue] + : [], + bodyExpressions + ), + Catch( + exceptionParam, + Block( + Assign( stateField, Constant( -2 ) ), + Call( + builderField, + nameof(AsyncTaskMethodBuilder.SetException), + null, + exceptionParam + ) + ) + ) + ), Label( exitLabel ) ), stateMachine @@ -420,11 +399,11 @@ private static List CreateBody( FieldInfo[] fields, LoweringResult s StateMachineOptimizer.Optimize( source ); - // Assign state-machine source to nodes + // Assign state-machine source to nodes (required for node reducers) foreach ( var node in source.Nodes ) { - node.StateMachineSource = stateMachineSource; // required for node reducers + node.StateMachineSource = stateMachineSource; } // Create the body expressions @@ -437,12 +416,12 @@ private static List CreateBody( FieldInfo[] fields, LoweringResult s stateMachineSource.StateIdField ); - List bodyExpressions = [jumpTable, Block( NodeExpression.Merge( firstScope.Nodes ) )]; //BF ME + var bodyBlock = Block( NodeExpression.Merge( firstScope.Nodes ) ); - // Add the state-nodes and hoist variables + // hoist variables - bodyExpressions = HoistVariables( - bodyExpressions, + var bodyExpressions = HoistVariables( + [jumpTable, bodyBlock], fields, stateMachineSource.StateMachine ); @@ -450,19 +429,6 @@ private static List CreateBody( FieldInfo[] fields, LoweringResult s return bodyExpressions; } - //private static List CreateBody( MemberExpression stateField, LoweringResult source ) - //{ - // var firstScope = source.Scopes.First(); - - // var jumpTable = JumpTableBuilder.Build( - // firstScope, - // source.Scopes, - // stateField - // ); - - // return [jumpTable, Block( NodeExpression.Merge( firstScope.Nodes ) )]; //BF ME - //} - private static List HoistVariables( List expressions, FieldInfo[] fields, ParameterExpression stateMachine ) { var fieldMembers = fields From 38ff74e9fb4249daba90ca298badc803a9b74e8d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 2 Dec 2024 19:15:45 +0000 Subject: [PATCH 7/7] Updated code formatting to match rules in .editorconfig --- .../Transformation/StateMachineBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs index 880b64b..29b9b8e 100644 --- a/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs +++ b/src/Hyperbee.Expressions/Transformation/StateMachineBuilder.cs @@ -368,7 +368,7 @@ FieldInfo[] fields Block( TryCatch( Block( - typeof(void), + typeof( void ), source.ReturnValue != null ? [source.ReturnValue] : [], @@ -380,7 +380,7 @@ FieldInfo[] fields Assign( stateField, Constant( -2 ) ), Call( builderField, - nameof(AsyncTaskMethodBuilder.SetException), + nameof( AsyncTaskMethodBuilder.SetException ), null, exceptionParam ) @@ -403,7 +403,7 @@ private static List CreateBody( FieldInfo[] fields, LoweringResult s foreach ( var node in source.Nodes ) { - node.StateMachineSource = stateMachineSource; + node.StateMachineSource = stateMachineSource; } // Create the body expressions