diff --git a/src/FastExpressionCompiler.LightExpression/Expression.cs b/src/FastExpressionCompiler.LightExpression/Expression.cs index 6be7058d..f67042ae 100644 --- a/src/FastExpressionCompiler.LightExpression/Expression.cs +++ b/src/FastExpressionCompiler.LightExpression/Expression.cs @@ -2773,11 +2773,11 @@ internal static Expression ConvertToLightExpression(SysExpr sysExpr, ref SmallLi var pe = (System.Linq.Expressions.ParameterExpression)sysExpr; return Expression.Parameter(pe.IsByRef ? exprType.MakeByRefType() : exprType, pe.Name); } - case ExpressionType.Lambda: - { - var le = (System.Linq.Expressions.LambdaExpression)sysExpr; - var body = le.Body.ToLightExpression(ref exprsConverted); - var retType = le.ReturnType; + case ExpressionType.Lambda: + { + var le = (System.Linq.Expressions.LambdaExpression)sysExpr; + var body = le.Body.ToLightExpression(ref exprsConverted); + var retType = le.ReturnType; var pars = le.Parameters; var parCount = pars.Count; switch (parCount) @@ -2819,16 +2819,40 @@ internal static Expression ConvertToLightExpression(SysExpr sysExpr, ref SmallLi (ParameterExpression)pars[5].ToLightExpression(ref exprsConverted), retType); default: var pes = new ParameterExpression[parCount]; - for (var i = 0; i < pes.Length; i++) - pes[i] = (ParameterExpression)le.Parameters[i].ToLightExpression(ref exprsConverted); - return Expression.Lambda(exprType, body, pes, retType); - } - } - default: - if (sysExpr is System.Linq.Expressions.UnaryExpression ue) - { - var operand = ue.Operand.ToLightExpression(ref exprsConverted); - return Expression.MakeUnary(nodeType, operand, exprType, ue.Method); + for (var i = 0; i < pes.Length; i++) + pes[i] = (ParameterExpression)le.Parameters[i].ToLightExpression(ref exprsConverted); + return Expression.Lambda(exprType, body, pes, retType); + } + } + case ExpressionType.Dynamic: + { + var de = (System.Linq.Expressions.DynamicExpression)sysExpr; + var sysArgs = de.Arguments; + var args = new Expression[sysArgs.Count]; + for (var i = 0; i < args.Length; ++i) + args[i] = sysArgs[i].ToLightExpression(ref exprsConverted); + return new DynamicExpression(de.DelegateType, de.Binder, args); + } + case ExpressionType.RuntimeVariables: + { + var rve = (System.Linq.Expressions.RuntimeVariablesExpression)sysExpr; + var sysVars = rve.Variables; + var vars = new ParameterExpression[sysVars.Count]; + for (var i = 0; i < vars.Length; ++i) + vars[i] = (ParameterExpression)sysVars[i].ToLightExpression(ref exprsConverted); + return new RuntimeVariablesExpression(vars); + } + case ExpressionType.DebugInfo: + { + var die = (System.Linq.Expressions.DebugInfoExpression)sysExpr; + return Expression.DebugInfo(new SymbolDocumentInfo(die.Document.FileName), + die.StartLine, die.StartColumn, die.EndLine, die.EndColumn); + } + default: + if (sysExpr is System.Linq.Expressions.UnaryExpression ue) + { + var operand = ue.Operand.ToLightExpression(ref exprsConverted); + return Expression.MakeUnary(nodeType, operand, exprType, ue.Method); } if (sysExpr is System.Linq.Expressions.BinaryExpression be) @@ -5924,4 +5948,4 @@ public interface IParameterProvider ParameterExpression GetParameter(int index); } -#nullable restore \ No newline at end of file +#nullable restore diff --git a/src/FastExpressionCompiler.LightExpression/FlatExpression.cs b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs new file mode 100644 index 00000000..52654ef8 --- /dev/null +++ b/src/FastExpressionCompiler.LightExpression/FlatExpression.cs @@ -0,0 +1,1582 @@ +namespace FastExpressionCompiler.FlatExpression; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using FastExpressionCompiler.LightExpression.ImTools; +using ChildList = FastExpressionCompiler.LightExpression.ImTools.SmallList, FastExpressionCompiler.LightExpression.ImTools.NoArrayPool>; +using LightExpression = FastExpressionCompiler.LightExpression.Expression; +using SysCatchBlock = System.Linq.Expressions.CatchBlock; +using SysElementInit = System.Linq.Expressions.ElementInit; +using SysExpr = System.Linq.Expressions.Expression; +using SysLabelTarget = System.Linq.Expressions.LabelTarget; +using SysMemberBinding = System.Linq.Expressions.MemberBinding; +using SysParameterExpression = System.Linq.Expressions.ParameterExpression; +using SysSwitchCase = System.Linq.Expressions.SwitchCase; + +/// Classifies the stored flat node payload. +public enum ExprNodeKind : byte +{ + /// Represents a regular expression node. + Expression, + /// Represents a switch case payload. + SwitchCase, + /// Represents a catch block payload. + CatchBlock, + /// Represents a label target payload. + LabelTarget, + /// Represents a member-assignment binding payload. + MemberAssignment, + /// Represents a nested member-binding payload. + MemberMemberBinding, + /// Represents a list-binding payload. + MemberListBinding, + /// Represents an element initializer payload. + ElementInit, + /// Represents an internal object-reference metadata node. + ObjectReference, + /// Represents an internal child-list metadata node. + ChildList, + /// Represents an internal pair of UInt16 values. + UInt16Pair, +} + +/// Stores one flat expression node plus its intrusive child-link metadata in 24 bytes on 64-bit runtimes. +[StructLayout(LayoutKind.Explicit, Size = 24)] +public struct ExprNode +{ + private const int NodeTypeShift = 56; + private const int TagShift = 48; + private const int NextShift = 32; + private const int CountShift = 16; + private const ulong IndexMask = 0xFFFF; + private const ulong KindMask = 0x0F; + private const ulong NextMask = IndexMask << NextShift; + private const ulong ChildCountMask = IndexMask << CountShift; + private const ulong ChildInfoMask = ChildCountMask | IndexMask; + private const ulong KeepWithoutNextMask = ~NextMask; + private const ulong KeepWithoutChildInfoMask = ~ChildInfoMask; + private const int FlagsShift = 4; + + /// Gets or sets the runtime type of the represented node. + [FieldOffset(0)] + public Type Type; + + /// Gets or sets the runtime payload associated with the node. + [FieldOffset(8)] + public object Obj; + [FieldOffset(16)] + private ulong _data; + + /// Gets the expression kind encoded for this node. + public ExpressionType NodeType => (ExpressionType)((_data >> NodeTypeShift) & 0xFF); + + /// Gets the payload classification for this node. + public ExprNodeKind Kind => (ExprNodeKind)((_data >> TagShift) & KindMask); + + internal byte Flags => (byte)(((byte)(_data >> TagShift)) >> FlagsShift); + + /// Gets the next sibling node index in the intrusive child chain. + public int NextIdx => (int)((_data >> NextShift) & IndexMask); + + /// Gets the number of direct children linked from this node. + public int ChildCount => (int)((_data >> CountShift) & IndexMask); + + /// Gets the first child index or an auxiliary payload index. + public int ChildIdx => (int)(_data & IndexMask); + + internal ExprNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0, int nextIdx = 0) + { + Type = type; + Obj = obj; + var tag = (byte)((flags << FlagsShift) | (byte)kind); + _data = ((ulong)(byte)nodeType << NodeTypeShift) + | ((ulong)tag << TagShift) + | ((ulong)(ushort)nextIdx << NextShift) + | ((ulong)(ushort)childCount << CountShift) + | (ushort)childIdx; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetNextIdx(int nextIdx) => + _data = (_data & KeepWithoutNextMask) | ((ulong)(ushort)nextIdx << NextShift); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetChildInfo(int childIdx, int childCount) => + _data = (_data & KeepWithoutChildInfoMask) + | ((ulong)(ushort)childCount << CountShift) + | (ushort)childIdx; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool Is(ExprNodeKind kind) => Kind == kind; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool IsExpression() => Kind == ExprNodeKind.Expression; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasFlag(byte flag) => (Flags & flag) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool ShouldCloneWhenLinked() => + Kind == ExprNodeKind.LabelTarget || NodeType == ExpressionType.Parameter || Kind == ExprNodeKind.ObjectReference || ChildCount == 0; +} + +/// Stores an expression tree as a flat node array plus out-of-line closure constants. +public struct ExprTree +{ + private static readonly object ClosureConstantMarker = new(); + private const byte ParameterByRefFlag = 1; + private const byte BinaryLiftedToNullFlag = 1; + private const byte LoopHasBreakFlag = 1; + private const byte LoopHasContinueFlag = 2; + private const byte CatchHasVariableFlag = 1; + private const byte CatchHasFilterFlag = 2; + private const byte TryFaultFlag = 1; + + /// Gets or sets the root node index. + public int RootIndex; + + /// Gets or sets the flat node storage. + public SmallList, NoArrayPool> Nodes; + + /// Gets or sets closure constants that are referenced from constant nodes. + public SmallList, NoArrayPool> ClosureConstants; + + /// Adds a parameter node and returns its index. + public int Parameter(Type type, string name = null) + { + var id = Nodes.Count + 1; + return AddRawLeafExpressionNode(type, name, ExpressionType.Parameter, type.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: id); + } + + /// Adds a typed parameter node and returns its index. + public int ParameterOf(string name = null) => Parameter(typeof(T), name); + + /// Adds a variable node and returns its index. + public int Variable(Type type, string name = null) => Parameter(type, name); + + /// Adds a default-value node and returns its index. + public int Default(Type type) => AddRawExpressionNode(type, null, ExpressionType.Default); + + /// Adds a constant node using the runtime type of the supplied value. + public int Constant(object value) => + Constant(value, value?.GetType() ?? typeof(object)); + + /// Adds a constant node with an explicit constant type. + public int Constant(object value, Type type) + { + if (ShouldInlineConstant(value, type)) + return AddRawExpressionNode(type, value, ExpressionType.Constant); + + var constantIndex = ClosureConstants.Add(value); + return AddRawExpressionNodeWithChildIndex(type, ClosureConstantMarker, ExpressionType.Constant, constantIndex); + } + + /// Adds a null constant node. + public int ConstantNull(Type type = null) => AddRawExpressionNode(type ?? typeof(object), null, ExpressionType.Constant); + + /// Adds an constant node. + public int ConstantInt(int value) => AddRawExpressionNode(typeof(int), value, ExpressionType.Constant); + + /// Adds a typed constant node. + public int ConstantOf(T value) => Constant(value, typeof(T)); + + /// Adds a parameterless new node for the specified type. + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public int New(Type type) + { + if (type.IsValueType) + return AddRawExpressionNode(type, null, ExpressionType.New); + + foreach (var ctor in type.GetConstructors()) + if (ctor.GetParameters().Length == 0) + return New(ctor); + + throw new ArgumentException($"The type {type} is missing the default constructor"); + } + + /// Adds a constructor call node. + public int New(System.Reflection.ConstructorInfo constructor, params int[] arguments) => + AddFactoryExpressionNode(constructor.DeclaringType, constructor, ExpressionType.New, arguments); + + /// Adds an array initialization node. + public int NewArrayInit(Type elementType, params int[] expressions) => + AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayInit, expressions); + + /// Adds an array-bounds node. + public int NewArrayBounds(Type elementType, params int[] bounds) => + AddFactoryExpressionNode(elementType.MakeArrayType(), null, ExpressionType.NewArrayBounds, bounds); + + /// Adds an invocation node. + public int Invoke(int expression, params int[] arguments) => + arguments == null || arguments.Length == 0 + ? AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, expression) + : AddFactoryExpressionNode(Nodes[expression].Type, null, ExpressionType.Invoke, PrependToChildList(expression, arguments)); + + /// Adds a static-call node. + public int Call(System.Reflection.MethodInfo method, params int[] arguments) => + AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, arguments); + + /// Adds an instance-call node. + public int Call(int instance, System.Reflection.MethodInfo method, params int[] arguments) => + arguments == null || arguments.Length == 0 + ? AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, instance) + : AddFactoryExpressionNode(method.ReturnType, method, ExpressionType.Call, PrependToChildList(instance, arguments)); + + /// Adds a field or property access node. + public int MakeMemberAccess(int? instance, System.Reflection.MemberInfo member) => + instance.HasValue + ? AddFactoryExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess, instance.Value) + : AddRawExpressionNode(GetMemberType(member), member, ExpressionType.MemberAccess); + + /// Adds a field-access node. + public int Field(int instance, System.Reflection.FieldInfo field) => MakeMemberAccess(instance, field); + + /// Adds a property-access node. + public int Property(int instance, System.Reflection.PropertyInfo property) => MakeMemberAccess(instance, property); + + /// Adds a static property-access node. + public int Property(System.Reflection.PropertyInfo property) => MakeMemberAccess(null, property); + + /// Adds an indexed property-access node. + public int Property(int instance, System.Reflection.PropertyInfo property, params int[] arguments) => + arguments == null || arguments.Length == 0 + ? Property(instance, property) + : AddFactoryExpressionNode(property.PropertyType, property, ExpressionType.Index, PrependToChildList(instance, arguments)); + + /// Adds a one-dimensional array index node. + public int ArrayIndex(int array, int index) => MakeBinary(ExpressionType.ArrayIndex, array, index); + + /// Adds an array access node. + public int ArrayAccess(int array, params int[] indexes) => + indexes != null && indexes.Length == 1 + ? ArrayIndex(array, indexes[0]) + : AddFactoryExpressionNode(GetArrayElementType(Nodes[array].Type, indexes?.Length ?? 0), null, ExpressionType.Index, PrependToChildList(array, indexes)); + + /// Adds a conversion node. + public int Convert(int operand, Type type, System.Reflection.MethodInfo method = null) => + AddFactoryExpressionNode(type, method, ExpressionType.Convert, operand); + + /// Adds a type-as node. + public int TypeAs(int operand, Type type) => + AddFactoryExpressionNode(type, null, ExpressionType.TypeAs, operand); + + /// Adds a numeric negation node. + public int Negate(int operand, System.Reflection.MethodInfo method = null) => + MakeUnary(ExpressionType.Negate, operand, method: method); + + /// Adds a logical or bitwise not node. + public int Not(int operand, System.Reflection.MethodInfo method = null) => + MakeUnary(ExpressionType.Not, operand, method: method); + + /// Adds a unary node of the specified kind. + public int MakeUnary(ExpressionType nodeType, int operand, Type type = null, System.Reflection.MethodInfo method = null) => + AddFactoryExpressionNode(type ?? GetUnaryResultType(nodeType, Nodes[operand].Type, method), method, nodeType, operand); + + /// Adds an assignment node. + public int Assign(int left, int right) => MakeBinary(ExpressionType.Assign, left, right); + + /// Adds an addition node. + public int Add(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Add, left, right, method: method); + + /// Adds an equality node. + public int Equal(int left, int right, System.Reflection.MethodInfo method = null) => MakeBinary(ExpressionType.Equal, left, right, method: method); + + /// Adds a binary node of the specified kind. + public int MakeBinary(ExpressionType nodeType, int left, int right, bool isLiftedToNull = false, + System.Reflection.MethodInfo method = null, int? conversion = null, Type type = null) + => conversion.HasValue + ? AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), + method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right, conversion.Value) + : AddFactoryExpressionNode(type ?? GetBinaryResultType(nodeType, Nodes[left].Type, Nodes[right].Type, method), + method, nodeType, isLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, left, right); + + /// Adds a conditional node. + public int Condition(int test, int ifTrue, int ifFalse, Type type = null) => + AddFactoryExpressionNode(type ?? Nodes[ifTrue].Type, null, ExpressionType.Conditional, 0, test, ifTrue, ifFalse); + + /// Adds a block node without explicit variables. + public int Block(params int[] expressions) => + Block(null, null, expressions); + + /// Adds a block node with optional explicit result type and variables. + public int Block(Type type, IEnumerable variables, params int[] expressions) + { + if (expressions == null || expressions.Length == 0) + throw new ArgumentException("Block should contain at least one expression.", nameof(expressions)); + + ChildList children = default; + if (variables != null) + { + ChildList variableChildren = default; + foreach (var variable in variables) + variableChildren.Add(variable); + if (variableChildren.Count != 0) + children.Add(AddChildListNode(in variableChildren)); + } + ChildList bodyChildren = default; + for (var i = 0; i < expressions.Length; ++i) + bodyChildren.Add(expressions[i]); + children.Add(AddChildListNode(in bodyChildren)); + return AddFactoryExpressionNode(type ?? Nodes[expressions[expressions.Length - 1]].Type, null, ExpressionType.Block, in children); + } + + /// Adds a typed lambda node. + public int Lambda(int body, params int[] parameters) where TDelegate : Delegate => + Lambda(typeof(TDelegate), body, parameters); + + /// Adds a lambda node. + public int Lambda(Type delegateType, int body, params int[] parameters) => + parameters == null || parameters.Length == 0 + ? AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, 0, body) + : AddFactoryExpressionNode(delegateType, null, ExpressionType.Lambda, PrependToChildList(body, parameters)); + + /// Adds a member-assignment binding node. + public int Bind(System.Reflection.MemberInfo member, int expression) => + AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberAssignment, expression); + + /// Adds a nested member-binding node. + public int MemberBind(System.Reflection.MemberInfo member, params int[] bindings) => + AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberMemberBinding, bindings); + + /// Adds an element-initializer node. + public int ElementInit(System.Reflection.MethodInfo addMethod, params int[] arguments) => + AddFactoryAuxNode(addMethod.DeclaringType, addMethod, ExprNodeKind.ElementInit, arguments); + + /// Adds a list-binding node. + public int ListBind(System.Reflection.MemberInfo member, params int[] initializers) => + AddFactoryAuxNode(GetMemberType(member), member, ExprNodeKind.MemberListBinding, initializers); + + /// Adds a member-init node. + public int MemberInit(int @new, params int[] bindings) => + bindings == null || bindings.Length == 0 + ? AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, @new) + : AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.MemberInit, PrependToChildList(@new, bindings)); + + /// Adds a list-init node. + public int ListInit(int @new, params int[] initializers) => + initializers == null || initializers.Length == 0 + ? AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, @new) + : AddFactoryExpressionNode(Nodes[@new].Type, null, ExpressionType.ListInit, PrependToChildList(@new, initializers)); + + /// Adds a label-target node. + public int Label(Type type = null, string name = null) + { + var id = Nodes.Count + 1; + return AddRawLeafAuxNode(type ?? typeof(void), name, ExprNodeKind.LabelTarget, childIdx: id); + } + + /// Adds a label-expression node. + public int Label(int target, int? defaultValue = null) => + defaultValue.HasValue + ? AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target, defaultValue.Value) + : AddFactoryExpressionNode(Nodes[target].Type, null, ExpressionType.Label, 0, target); + + /// Adds a goto-family node. + public int MakeGoto(GotoExpressionKind kind, int target, int? value = null, Type type = null) + { + var resultType = type ?? (value.HasValue ? Nodes[value.Value].Type : typeof(void)); + return value.HasValue + ? AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target, value.Value) + : AddFactoryExpressionNode(resultType, kind, ExpressionType.Goto, 0, target); + } + + /// Adds a goto node. + public int Goto(int target, int? value = null, Type type = null) => MakeGoto(GotoExpressionKind.Goto, target, value, type); + + /// Adds a return node. + public int Return(int target, int value) => MakeGoto(GotoExpressionKind.Return, target, value, Nodes[value].Type); + + /// Adds a loop node. + public int Loop(int body, int? @break = null, int? @continue = null) + { + ChildList children = default; + children.Add(body); + if (@break.HasValue) + children.Add(@break.Value); + if (@continue.HasValue) + children.Add(@continue.Value); + return AddFactoryExpressionNode(typeof(void), null, ExpressionType.Loop, + (byte)((@break.HasValue ? LoopHasBreakFlag : 0) | (@continue.HasValue ? LoopHasContinueFlag : 0)), in children); + } + + /// Adds a switch-case node. + public int SwitchCase(int body, params int[] testValues) + { + ChildList children = default; + if (testValues != null && testValues.Length != 0) + for (var i = 0; i < testValues.Length; ++i) + children.Add(testValues[i]); + children.Add(body); + return AddFactoryAuxNode(Nodes[body].Type, null, ExprNodeKind.SwitchCase, children); + } + + /// Adds a switch node without an explicit default case or comparer. + public int Switch(int switchValue, params int[] cases) => + Switch(Nodes[switchValue].Type, switchValue, null, null, cases); + + /// Adds a switch node. + public int Switch(Type type, int switchValue, int? defaultBody, System.Reflection.MethodInfo comparison, params int[] cases) + { + ChildList children = default; + children.Add(switchValue); + if (defaultBody.HasValue) + children.Add(defaultBody.Value); + if (cases != null && cases.Length != 0) + { + ChildList caseChildren = default; + for (var i = 0; i < cases.Length; ++i) + caseChildren.Add(cases[i]); + children.Add(AddChildListNode(in caseChildren)); + } + return AddFactoryExpressionNode(type, comparison, ExpressionType.Switch, in children); + } + + /// Adds a catch block with an exception variable. + public int Catch(int variable, int body) => + AddFactoryAuxNode(Nodes[variable].Type, null, ExprNodeKind.CatchBlock, CatchHasVariableFlag, variable, body); + + /// Adds a catch block without an exception variable. + public int Catch(Type test, int body) => + AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, 0, body); + + /// Adds a catch block with optional exception variable and filter. + public int MakeCatchBlock(Type test, int? variable, int body, int? filter) + { + ChildList children = default; + if (variable.HasValue) + children.Add(variable.Value); + children.Add(body); + if (filter.HasValue) + children.Add(filter.Value); + return AddFactoryAuxNode(test, null, ExprNodeKind.CatchBlock, + (byte)((variable.HasValue ? CatchHasVariableFlag : 0) | (filter.HasValue ? CatchHasFilterFlag : 0)), in children); + } + + /// Adds a try/catch node. + public int TryCatch(int body, params int[] handlers) + { + if (handlers == null || handlers.Length == 0) + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body); + + ChildList handlerChildren = default; + for (var i = 0; i < handlers.Length; ++i) + handlerChildren.Add(handlers[i]); + ChildList children = default; + children.Add(body); + children.Add(AddChildListNode(in handlerChildren)); + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, in children); + } + + /// Adds a try/finally node. + public int TryFinally(int body, int @finally) => + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, body, @finally); + + /// Adds a try/fault node. + public int TryFault(int body, int fault) => + AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, TryFaultFlag, body, fault); + + /// Adds a try node with optional finally block and catch handlers. + public int TryCatchFinally(int body, int? @finally, params int[] handlers) + { + ChildList children = default; + children.Add(body); + if (@finally.HasValue) + children.Add(@finally.Value); + if (handlers != null && handlers.Length != 0) + { + ChildList handlerChildren = default; + for (var i = 0; i < handlers.Length; ++i) + handlerChildren.Add(handlers[i]); + children.Add(AddChildListNode(in handlerChildren)); + } + return AddFactoryExpressionNode(Nodes[body].Type, null, ExpressionType.Try, 0, in children); + } + + /// Adds a type-test node. + public int TypeIs(int expression, Type type) => + AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeIs, expression); + + /// Adds a type-equality test node. + public int TypeEqual(int expression, Type type) => + AddFactoryExpressionNode(typeof(bool), type, ExpressionType.TypeEqual, expression); + + /// Adds a dynamic-expression node. + public int Dynamic(Type delegateType, CallSiteBinder binder, params int[] arguments) + { + ChildList children = default; + children.Add(AddObjectReferenceNode(typeof(Type), delegateType)); + if (arguments != null && arguments.Length != 0) + for (var i = 0; i < arguments.Length; ++i) + children.Add(arguments[i]); + return AddFactoryExpressionNode(typeof(object), binder, ExpressionType.Dynamic, children); + } + + /// Adds a runtime-variables node. + public int RuntimeVariables(params int[] variables) => + AddFactoryExpressionNode(typeof(IRuntimeVariables), null, ExpressionType.RuntimeVariables, variables); + + /// Adds a debug-info node. + public int DebugInfo(string fileName, int startLine, int startColumn, int endLine, int endColumn) => + AddFactoryExpressionNode(typeof(void), fileName, ExpressionType.DebugInfo, CreateDebugInfoChildren(startLine, startColumn, endLine, endColumn)); + + /// Flattens a System.Linq expression tree. + public static ExprTree FromExpression(SysExpr expression) => + new Builder().Build(expression ?? throw new ArgumentNullException(nameof(expression))); + + /// Flattens a LightExpression tree. + public static ExprTree FromLightExpression(LightExpression expression) => + FromExpression((expression ?? throw new ArgumentNullException(nameof(expression))).ToExpression()); + + /// Reconstructs the flat tree as a System.Linq expression tree. + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", + Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] + public SysExpr ToExpression() => + Nodes.Count != 0 + ? new Reader(this).ReadExpression(RootIndex) + : throw new InvalidOperationException("Flat expression tree is empty."); + + /// Reconstructs the flat tree as a LightExpression tree. + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public LightExpression ToLightExpression() => FastExpressionCompiler.LightExpression.FromSysExpressionConverter.ToLightExpression(ToExpression()); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int child) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, CloneChild(child)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int child) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(child)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4, int c5) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4), CloneChild(c5)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, int c0, int c1, int c2, int c3, int c4, int c5, int c6) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, CloneChild(c0), CloneChild(c1), CloneChild(c2), CloneChild(c3), CloneChild(c4), CloneChild(c5), CloneChild(c6)); + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) + { + if (children != null) + switch (children.Length) + { + case 1: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0]); + case 2: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1]); + case 3: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2]); + case 4: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3]); + case 5: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4]); + case 6: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5]); + case 7: return AddFactoryExpressionNode(type, obj, nodeType, 0, children[0], children[1], children[2], children[3], children[4], children[5], children[6]); + } + + var cloned = CloneChildren(children); + return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); + } + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) + { + var cloned = CloneChildren(children); + return AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in cloned); + } + + private int AddFactoryExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags, in ChildList children) + { + var cloned = CloneChildren(children); + return AddNode(type, obj, nodeType, ExprNodeKind.Expression, flags, in cloned); + } + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType) => + AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, 0, 0, 0); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, in ChildList children) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, in children); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int[] children) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, children); + + private int AddRawExpressionNode(Type type, object obj, ExpressionType nodeType, int child0, int child1, int child2) => + AddNode(type, obj, nodeType, ExprNodeKind.Expression, 0, child0, child1, child2); + + private int AddRawLeafExpressionNode(Type type, object obj, ExpressionType nodeType, byte flags = 0, int childIdx = 0, int childCount = 0) => + AddLeafNode(type, obj, nodeType, ExprNodeKind.Expression, flags, childIdx, childCount); + + private int AddRawExpressionNodeWithChildIndex(Type type, object obj, ExpressionType nodeType, int childIdx) => + AddRawLeafExpressionNode(type, obj, nodeType, childIdx: childIdx); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child) => + AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child)); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int child) => + AddFactoryAuxNode(type, obj, kind, 0, child); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, int child0, int child1) => + AddNode(type, obj, ExpressionType.Extension, kind, flags, CloneChild(child0), CloneChild(child1)); + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, int[] children) + { + var cloned = CloneChildren(children); + return AddNode(type, obj, ExpressionType.Extension, kind, 0, in cloned); + } + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, byte flags, in ChildList children) + { + var cloned = CloneChildren(children); + return AddNode(type, obj, ExpressionType.Extension, kind, flags, in cloned); + } + + private int AddFactoryAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => + AddFactoryAuxNode(type, obj, kind, 0, in children); + + private int AddRawAuxNode(Type type, object obj, ExprNodeKind kind, in ChildList children) => + AddNode(type, obj, ExpressionType.Extension, kind, 0, in children); + + private int AddRawLeafAuxNode(Type type, object obj, ExprNodeKind kind, byte flags = 0, int childIdx = 0, int childCount = 0) => + AddLeafNode(type, obj, ExpressionType.Extension, kind, flags, childIdx, childCount); + + private int AddObjectReferenceNode(Type type, object obj) => + AddRawLeafAuxNode(type, obj, ExprNodeKind.ObjectReference); + + private int AddChildListNode(in ChildList children) => + AddRawAuxNode(null, null, ExprNodeKind.ChildList, in children); + + private int AddUInt16PairNode(int first, int second) => + AddRawLeafAuxNode(null, null, ExprNodeKind.UInt16Pair, childIdx: checked((ushort)first), childCount: checked((ushort)second)); + + private ChildList CreateDebugInfoChildren(int startLine, int startColumn, int endLine, int endColumn) + { + ChildList children = default; + children.Add(AddUInt16PairNode(startLine, startColumn)); + children.Add(AddUInt16PairNode(endLine, endColumn)); + return children; + } + + private static ChildList PrependToChildList(int first, int[] rest) + { + ChildList children = default; + children.Add(first); + if (rest != null) + for (var i = 0; i < rest.Length; ++i) + children.Add(rest[i]); + return children; + } + + /// Builds the flat representation while preserving parameter and label identity with stack-friendly maps. + private struct Builder + { + private SmallMap16> _parameterIds; + private SmallMap16> _labelIds; + private ExprTree _tree; + + public ExprTree Build(SysExpr expression) + { + _tree.RootIndex = AddExpression(expression); + return _tree; + } + + private int AddExpression(SysExpr expression) + { + switch (expression.NodeType) + { + case ExpressionType.Constant: + return AddConstant((System.Linq.Expressions.ConstantExpression)expression); + case ExpressionType.Default: + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType); + case ExpressionType.Parameter: + { + var parameter = (SysParameterExpression)expression; + return _tree.AddRawLeafExpressionNode(expression.Type, parameter.Name, expression.NodeType, + parameter.IsByRef ? ParameterByRefFlag : (byte)0, childIdx: GetId(ref _parameterIds, parameter)); + } + case ExpressionType.Lambda: + { + var lambda = (System.Linq.Expressions.LambdaExpression)expression; + ChildList children = default; + children.Add(AddExpression(lambda.Body)); + for (var i = 0; i < lambda.Parameters.Count; ++i) + children.Add(AddExpression(lambda.Parameters[i])); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Block: + { + var block = (System.Linq.Expressions.BlockExpression)expression; + ChildList children = default; + if (block.Variables.Count != 0) + { + ChildList variables = default; + for (var i = 0; i < block.Variables.Count; ++i) + variables.Add(AddExpression(block.Variables[i])); + children.Add(_tree.AddChildListNode(in variables)); + } + ChildList expressions = default; + for (var i = 0; i < block.Expressions.Count; ++i) + expressions.Add(AddExpression(block.Expressions[i])); + children.Add(_tree.AddChildListNode(in expressions)); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, in children); + } + case ExpressionType.MemberAccess: + { + var member = (System.Linq.Expressions.MemberExpression)expression; + ChildList children = default; + if (member.Expression != null) + children.Add(AddExpression(member.Expression)); + return _tree.AddRawExpressionNode(expression.Type, member.Member, expression.NodeType, + children); + } + case ExpressionType.Call: + { + var call = (System.Linq.Expressions.MethodCallExpression)expression; + ChildList children = default; + if (call.Object != null) + children.Add(AddExpression(call.Object)); + for (var i = 0; i < call.Arguments.Count; ++i) + children.Add(AddExpression(call.Arguments[i])); + return _tree.AddRawExpressionNode(expression.Type, call.Method, expression.NodeType, children); + } + case ExpressionType.New: + { + var @new = (System.Linq.Expressions.NewExpression)expression; + ChildList children = default; + for (var i = 0; i < @new.Arguments.Count; ++i) + children.Add(AddExpression(@new.Arguments[i])); + return _tree.AddRawExpressionNode(expression.Type, @new.Constructor, expression.NodeType, children); + } + case ExpressionType.NewArrayInit: + case ExpressionType.NewArrayBounds: + { + var array = (System.Linq.Expressions.NewArrayExpression)expression; + ChildList children = default; + for (var i = 0; i < array.Expressions.Count; ++i) + children.Add(AddExpression(array.Expressions[i])); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Invoke: + { + var invoke = (System.Linq.Expressions.InvocationExpression)expression; + ChildList children = default; + children.Add(AddExpression(invoke.Expression)); + for (var i = 0; i < invoke.Arguments.Count; ++i) + children.Add(AddExpression(invoke.Arguments[i])); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Index: + { + var index = (System.Linq.Expressions.IndexExpression)expression; + ChildList children = default; + if (index.Object != null) + children.Add(AddExpression(index.Object)); + for (var i = 0; i < index.Arguments.Count; ++i) + children.Add(AddExpression(index.Arguments[i])); + return _tree.AddRawExpressionNode(expression.Type, index.Indexer, expression.NodeType, children); + } + case ExpressionType.Conditional: + { + var conditional = (System.Linq.Expressions.ConditionalExpression)expression; + ChildList children = default; + children.Add(AddExpression(conditional.Test)); + children.Add(AddExpression(conditional.IfTrue)); + children.Add(AddExpression(conditional.IfFalse)); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, + children[0], children[1], children[2]); + } + case ExpressionType.Loop: + { + var loop = (System.Linq.Expressions.LoopExpression)expression; + ChildList children = default; + children.Add(AddExpression(loop.Body)); + if (loop.BreakLabel != null) + children.Add(AddLabelTarget(loop.BreakLabel)); + if (loop.ContinueLabel != null) + children.Add(AddLabelTarget(loop.ContinueLabel)); + return _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, + (byte)((loop.BreakLabel != null ? LoopHasBreakFlag : 0) | (loop.ContinueLabel != null ? LoopHasContinueFlag : 0)), in children); + } + case ExpressionType.Goto: + { + var @goto = (System.Linq.Expressions.GotoExpression)expression; + ChildList children = default; + children.Add(AddLabelTarget(@goto.Target)); + if (@goto.Value != null) + children.Add(AddExpression(@goto.Value)); + return _tree.AddRawExpressionNode(expression.Type, @goto.Kind, expression.NodeType, children); + } + case ExpressionType.Label: + { + var label = (System.Linq.Expressions.LabelExpression)expression; + ChildList children = default; + children.Add(AddLabelTarget(label.Target)); + if (label.DefaultValue != null) + children.Add(AddExpression(label.DefaultValue)); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.Switch: + { + var @switch = (System.Linq.Expressions.SwitchExpression)expression; + ChildList children = default; + children.Add(AddExpression(@switch.SwitchValue)); + if (@switch.DefaultBody != null) + children.Add(AddExpression(@switch.DefaultBody)); + if (@switch.Cases.Count != 0) + { + ChildList cases = default; + for (var i = 0; i < @switch.Cases.Count; ++i) + cases.Add(AddSwitchCase(@switch.Cases[i])); + children.Add(_tree.AddChildListNode(in cases)); + } + return _tree.AddRawExpressionNode(expression.Type, @switch.Comparison, expression.NodeType, in children); + } + case ExpressionType.Try: + { + var @try = (System.Linq.Expressions.TryExpression)expression; + ChildList children = default; + children.Add(AddExpression(@try.Body)); + var flags = (byte)0; + if (@try.Fault != null) + { + flags = TryFaultFlag; + children.Add(AddExpression(@try.Fault)); + } + else if (@try.Finally != null) + children.Add(AddExpression(@try.Finally)); + if (@try.Handlers.Count != 0) + { + ChildList handlers = default; + for (var i = 0; i < @try.Handlers.Count; ++i) + handlers.Add(AddCatchBlock(@try.Handlers[i])); + children.Add(_tree.AddChildListNode(in handlers)); + } + return _tree.AddNode(expression.Type, null, expression.NodeType, ExprNodeKind.Expression, flags, in children); + } + case ExpressionType.MemberInit: + { + var memberInit = (System.Linq.Expressions.MemberInitExpression)expression; + ChildList children = default; + children.Add(AddExpression(memberInit.NewExpression)); + for (var i = 0; i < memberInit.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberInit.Bindings[i])); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.ListInit: + { + var listInit = (System.Linq.Expressions.ListInitExpression)expression; + ChildList children = default; + children.Add(AddExpression(listInit.NewExpression)); + for (var i = 0; i < listInit.Initializers.Count; ++i) + children.Add(AddElementInit(listInit.Initializers[i])); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: + { + var typeBinary = (System.Linq.Expressions.TypeBinaryExpression)expression; + ChildList children = default; + children.Add(AddExpression(typeBinary.Expression)); + return _tree.AddRawExpressionNode(expression.Type, typeBinary.TypeOperand, expression.NodeType, + children); + } + case ExpressionType.Dynamic: + { + var dynamic = (System.Linq.Expressions.DynamicExpression)expression; + ChildList children = default; + children.Add(_tree.AddObjectReferenceNode(typeof(Type), dynamic.DelegateType)); + for (var i = 0; i < dynamic.Arguments.Count; ++i) + children.Add(AddExpression(dynamic.Arguments[i])); + return _tree.AddRawExpressionNode(expression.Type, dynamic.Binder, expression.NodeType, children); + } + case ExpressionType.RuntimeVariables: + { + var runtime = (System.Linq.Expressions.RuntimeVariablesExpression)expression; + ChildList children = default; + for (var i = 0; i < runtime.Variables.Count; ++i) + children.Add(AddExpression(runtime.Variables[i])); + return _tree.AddRawExpressionNode(expression.Type, null, expression.NodeType, children); + } + case ExpressionType.DebugInfo: + { + var debug = (System.Linq.Expressions.DebugInfoExpression)expression; + return _tree.AddFactoryExpressionNode(expression.Type, debug.Document.FileName, expression.NodeType, + _tree.CreateDebugInfoChildren(debug.StartLine, debug.StartColumn, debug.EndLine, debug.EndColumn)); + } + default: + if (expression is System.Linq.Expressions.UnaryExpression unary) + { + ChildList children = default; + children.Add(AddExpression(unary.Operand)); + return _tree.AddRawExpressionNode(expression.Type, unary.Method, expression.NodeType, + children); + } + + if (expression is System.Linq.Expressions.BinaryExpression binary) + { + ChildList children = default; + children.Add(AddExpression(binary.Left)); + children.Add(AddExpression(binary.Right)); + if (binary.Conversion != null) + children.Add(AddExpression(binary.Conversion)); + return _tree.AddNode(expression.Type, binary.Method, expression.NodeType, ExprNodeKind.Expression, + binary.IsLiftedToNull ? BinaryLiftedToNullFlag : (byte)0, in children); + } + + throw new NotSupportedException($"Flattening of `ExpressionType.{expression.NodeType}` is not supported yet."); + } + } + + private int AddConstant(System.Linq.Expressions.ConstantExpression constant) + { + if (ShouldInlineConstant(constant.Value, constant.Type)) + return _tree.AddRawExpressionNode(constant.Type, constant.Value, constant.NodeType); + + var constantIndex = _tree.ClosureConstants.Add(constant.Value); + return _tree.AddRawExpressionNodeWithChildIndex(constant.Type, ClosureConstantMarker, constant.NodeType, constantIndex); + } + + private int AddSwitchCase(SysSwitchCase switchCase) + { + ChildList children = default; + for (var i = 0; i < switchCase.TestValues.Count; ++i) + children.Add(AddExpression(switchCase.TestValues[i])); + children.Add(AddExpression(switchCase.Body)); + return _tree.AddRawAuxNode(switchCase.Body.Type, null, ExprNodeKind.SwitchCase, children); + } + + private int AddCatchBlock(SysCatchBlock catchBlock) + { + ChildList children = default; + if (catchBlock.Variable != null) + children.Add(AddExpression(catchBlock.Variable)); + children.Add(AddExpression(catchBlock.Body)); + if (catchBlock.Filter != null) + children.Add(AddExpression(catchBlock.Filter)); + return _tree.AddNode(catchBlock.Test, null, ExpressionType.Extension, ExprNodeKind.CatchBlock, + (byte)((catchBlock.Variable != null ? CatchHasVariableFlag : 0) | (catchBlock.Filter != null ? CatchHasFilterFlag : 0)), in children); + } + + private int AddLabelTarget(SysLabelTarget target) => + _tree.AddRawLeafAuxNode(target.Type, target.Name, ExprNodeKind.LabelTarget, childIdx: GetId(ref _labelIds, target)); + + private int AddMemberBinding(SysMemberBinding binding) + { + switch (binding.BindingType) + { + case MemberBindingType.Assignment: + ChildList assignmentChildren = default; + assignmentChildren.Add(AddExpression(((System.Linq.Expressions.MemberAssignment)binding).Expression)); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberAssignment, + assignmentChildren); + case MemberBindingType.MemberBinding: + { + var memberBinding = (System.Linq.Expressions.MemberMemberBinding)binding; + ChildList children = default; + for (var i = 0; i < memberBinding.Bindings.Count; ++i) + children.Add(AddMemberBinding(memberBinding.Bindings[i])); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberMemberBinding, children); + } + case MemberBindingType.ListBinding: + { + var listBinding = (System.Linq.Expressions.MemberListBinding)binding; + ChildList children = default; + for (var i = 0; i < listBinding.Initializers.Count; ++i) + children.Add(AddElementInit(listBinding.Initializers[i])); + return _tree.AddRawAuxNode(GetMemberType(binding.Member), binding.Member, ExprNodeKind.MemberListBinding, children); + } + default: + throw new NotSupportedException($"Flattening of member binding `{binding.BindingType}` is not supported yet."); + } + } + + private int AddElementInit(SysElementInit init) + { + ChildList children = default; + for (var i = 0; i < init.Arguments.Count; ++i) + children.Add(AddExpression(init.Arguments[i])); + return _tree.AddRawAuxNode(init.AddMethod.DeclaringType, init.AddMethod, ExprNodeKind.ElementInit, children); + } + + private static int GetId(ref SmallMap16> ids, object item) + { + ref var id = ref ids.Map.AddOrGetValueRef(item, out var found); + if (!found) + id = ids.Map.Count; + return id; + } + + private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + { + System.Reflection.FieldInfo field => field.FieldType, + System.Reflection.PropertyInfo property => property.PropertyType, + _ => typeof(object) + }; + } + + private int AddLeafNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int childIdx, int childCount) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, childIdx, childCount); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int child0) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, child0, 1); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 2); + Nodes[c0].SetNextIdx(c1); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 3); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 4); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 5); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + Nodes[c3].SetNextIdx(c4); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4, int c5) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 6); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + Nodes[c3].SetNextIdx(c4); + Nodes[c4].SetNextIdx(c5); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int c0, int c1, int c2, int c3, int c4, int c5, int c6) + { + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, c0, 7); + Nodes[c0].SetNextIdx(c1); + Nodes[c1].SetNextIdx(c2); + Nodes[c2].SetNextIdx(c3); + Nodes[c3].SetNextIdx(c4); + Nodes[c4].SetNextIdx(c5); + Nodes[c5].SetNextIdx(c6); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, int[] children) + { + if (children == null || children.Length == 0) + return AddNode(type, obj, nodeType, kind, flags); + + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Length); + for (var i = 1; i < children.Length; ++i) + Nodes[children[i - 1]].SetNextIdx(children[i]); + return nodeIndex; + } + + private int AddNode(Type type, object obj, ExpressionType nodeType, ExprNodeKind kind, byte flags, in ChildList children) + { + if (children.Count == 0) + return AddNode(type, obj, nodeType, kind, flags); + + var nodeIndex = Nodes.Count; + ref var newNode = ref Nodes.AddDefaultAndGetRef(); + newNode = new ExprNode(type, obj, nodeType, kind, flags, children[0], children.Count); + for (var i = 1; i < children.Count; ++i) + Nodes[children[i - 1]].SetNextIdx(children[i]); + return nodeIndex; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ShouldInlineConstant(object value, Type type) => + value == null || value is string || value is Type || type.IsEnum || Type.GetTypeCode(type) != TypeCode.Object; + + private static Type GetMemberType(System.Reflection.MemberInfo member) => member switch + { + System.Reflection.FieldInfo field => field.FieldType, + System.Reflection.PropertyInfo property => property.PropertyType, + _ => typeof(object) + }; + + private static Type GetUnaryResultType(ExpressionType nodeType, Type operandType, System.Reflection.MethodInfo method) => + nodeType switch + { + ExpressionType.IsFalse or ExpressionType.IsTrue or ExpressionType.TypeIs or ExpressionType.TypeEqual => typeof(bool), + _ => method?.ReturnType ?? operandType + }; + + private static Type GetBinaryResultType(ExpressionType nodeType, Type leftType, Type rightType, System.Reflection.MethodInfo method) + { + if (method != null) + return method.ReturnType; + + return nodeType switch + { + ExpressionType.Equal or ExpressionType.NotEqual or ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual + or ExpressionType.LessThan or ExpressionType.LessThanOrEqual or ExpressionType.AndAlso or ExpressionType.OrElse => typeof(bool), + ExpressionType.ArrayIndex => leftType.GetElementType(), + ExpressionType.Assign => leftType, + _ => leftType + }; + } + + private static Type GetArrayElementType(Type arrayType, int depth) + { + var elementType = arrayType; + for (var i = 0; i < depth; ++i) + elementType = elementType.GetElementType(); + return elementType ?? typeof(object); + } + + private int CloneChild(int index) + { + ref var node = ref Nodes[index]; + return node.ShouldCloneWhenLinked() + ? AddLeafNode(node.Type, node.Obj, node.NodeType, node.Kind, node.Flags, node.ChildIdx, node.ChildCount) + : index; + } + + private ChildList CloneChildren(int[] children) + { + ChildList cloned = default; + if (children == null) + return cloned; + + for (var i = 0; i < children.Length; ++i) + cloned.Add(CloneChild(children[i])); + return cloned; + } + + private ChildList CloneChildren(in ChildList children) + { + ChildList cloned = default; + for (var i = 0; i < children.Count; ++i) + cloned.Add(CloneChild(children[i])); + return cloned; + } + + /// Reconstructs System.Linq nodes from the flat representation while reusing parameter and label identities. + private struct Reader + { + private readonly ExprTree _tree; + private SmallMap16 _parametersById; + private SmallMap16 _labelsById; + + public Reader(ExprTree tree) + { + _tree = tree; + _parametersById = default; + _labelsById = default; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + public SysExpr ReadExpression(int index) + { + ref var node = ref _tree.Nodes[index]; + if (!node.IsExpression()) + throw new InvalidOperationException($"Node at index {index} is not an expression node."); + + switch (node.NodeType) + { + case ExpressionType.Constant: + return SysExpr.Constant(ReferenceEquals(node.Obj, ClosureConstantMarker) + ? _tree.ClosureConstants[node.ChildIdx] + : node.Obj, node.Type); + case ExpressionType.Default: + return SysExpr.Default(node.Type); + case ExpressionType.Parameter: + { + ref var parameter = ref _parametersById.Map.AddOrGetValueRef(node.ChildIdx, out var found); + if (found) + return parameter; + + var parameterType = node.HasFlag(ParameterByRefFlag) && !node.Type.IsByRef ? node.Type.MakeByRefType() : node.Type; + return parameter = SysExpr.Parameter(parameterType, (string)node.Obj); + } + case ExpressionType.Lambda: + { + var children = GetChildren(index); + var body = ReadExpression(children[0]); + var parameters = new SysParameterExpression[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + parameters[i - 1] = (SysParameterExpression)ReadExpression(children[i]); + return SysExpr.Lambda(node.Type, body, parameters); + } + case ExpressionType.Block: + { + var children = GetChildren(index); + var hasVariables = children.Count == 2; + var variableIndexes = hasVariables ? GetChildren(children[0]) : default; + var expressionIndexes = GetChildren(children[children.Count - 1]); + var variables = new SysParameterExpression[variableIndexes.Count]; + for (var i = 0; i < variables.Length; ++i) + variables[i] = (SysParameterExpression)ReadExpression(variableIndexes[i]); + var expressions = new SysExpr[expressionIndexes.Count]; + for (var i = 0; i < expressions.Length; ++i) + expressions[i] = ReadExpression(expressionIndexes[i]); + return SysExpr.Block(node.Type, variables, expressions); + } + case ExpressionType.MemberAccess: + { + var children = GetChildren(index); + return SysExpr.MakeMemberAccess(children.Count != 0 ? ReadExpression(children[0]) : null, (System.Reflection.MemberInfo)node.Obj); + } + case ExpressionType.Call: + { + var method = (System.Reflection.MethodInfo)node.Obj; + var children = GetChildren(index); + var hasInstance = !method.IsStatic; + var instance = hasInstance ? ReadExpression(children[0]) : null; + var arguments = new SysExpr[children.Count - (hasInstance ? 1 : 0)]; + for (var i = hasInstance ? 1 : 0; i < children.Count; ++i) + arguments[i - (hasInstance ? 1 : 0)] = ReadExpression(children[i]); + return SysExpr.Call(instance, method, arguments); + } + case ExpressionType.New: + { + var children = GetChildren(index); + var arguments = ReadExpressions(children); + return node.Obj is System.Reflection.ConstructorInfo ctor + ? SysExpr.New(ctor, arguments) + : CreateValueTypeNewExpression(node.Type); + } + case ExpressionType.NewArrayInit: + return SysExpr.NewArrayInit(node.Type.GetElementType(), ReadExpressions(GetChildren(index))); + case ExpressionType.NewArrayBounds: + return SysExpr.NewArrayBounds(node.Type.GetElementType(), ReadExpressions(GetChildren(index))); + case ExpressionType.Invoke: + { + var children = GetChildren(index); + var arguments = new SysExpr[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + arguments[i - 1] = ReadExpression(children[i]); + return SysExpr.Invoke(ReadExpression(children[0]), arguments); + } + case ExpressionType.Index: + { + var children = GetChildren(index); + var property = (System.Reflection.PropertyInfo)node.Obj; + var hasInstance = property != null || children.Count > 1; + var instance = hasInstance ? ReadExpression(children[0]) : null; + var arguments = new SysExpr[children.Count - (hasInstance ? 1 : 0)]; + for (var i = hasInstance ? 1 : 0; i < children.Count; ++i) + arguments[i - (hasInstance ? 1 : 0)] = ReadExpression(children[i]); + return property != null + ? SysExpr.Property(instance, property, arguments) + : SysExpr.ArrayAccess(instance, arguments); + } + case ExpressionType.Conditional: + { + var children = GetChildren(index); + return SysExpr.Condition(ReadExpression(children[0]), ReadExpression(children[1]), ReadExpression(children[2]), node.Type); + } + case ExpressionType.Loop: + { + var children = GetChildren(index); + var childIndex = 1; + var breakLabel = node.HasFlag(LoopHasBreakFlag) ? ReadLabelTarget(children[childIndex++]) : null; + var continueLabel = node.HasFlag(LoopHasContinueFlag) ? ReadLabelTarget(children[childIndex]) : null; + return SysExpr.Loop(ReadExpression(children[0]), breakLabel, continueLabel); + } + case ExpressionType.Goto: + { + var children = GetChildren(index); + var value = children.Count > 1 ? ReadExpression(children[1]) : null; + return SysExpr.MakeGoto((GotoExpressionKind)node.Obj, ReadLabelTarget(children[0]), value, node.Type); + } + case ExpressionType.Label: + { + var children = GetChildren(index); + var defaultValue = children.Count > 1 ? ReadExpression(children[1]) : null; + return SysExpr.Label(ReadLabelTarget(children[0]), defaultValue); + } + case ExpressionType.Switch: + { + var children = GetChildren(index); + var defaultBody = default(SysExpr); + ChildList caseIndexes = default; + if (children.Count > 1) + { + ref var lastChild = ref _tree.Nodes[children[children.Count - 1]]; + if (lastChild.Is(ExprNodeKind.ChildList)) + { + caseIndexes = GetChildren(children[children.Count - 1]); + if (children.Count == 3) + defaultBody = ReadExpression(children[1]); + } + else + defaultBody = ReadExpression(children[1]); + } + var cases = new SysSwitchCase[caseIndexes.Count]; + for (var i = 0; i < cases.Length; ++i) + cases[i] = ReadSwitchCase(caseIndexes[i]); + return SysExpr.Switch(node.Type, ReadExpression(children[0]), defaultBody, (System.Reflection.MethodInfo)node.Obj, cases); + } + case ExpressionType.Try: + { + var children = GetChildren(index); + if (node.HasFlag(TryFaultFlag)) + return SysExpr.TryFault(ReadExpression(children[0]), ReadExpression(children[1])); + + var handlers = default(SysCatchBlock[]); + var lastChildIsHandlerList = children.Count > 1 && _tree.Nodes[children[children.Count - 1]].Is(ExprNodeKind.ChildList); + if (lastChildIsHandlerList) + { + var handlerIndexes = GetChildren(children[children.Count - 1]); + handlers = new SysCatchBlock[handlerIndexes.Count]; + for (var i = 0; i < handlers.Length; ++i) + handlers[i] = ReadCatchBlock(handlerIndexes[i]); + } + else + handlers = Array.Empty(); + + var @finally = children.Count > 1 && (!lastChildIsHandlerList || children.Count == 3) + ? ReadExpression(children[1]) + : null; + return SysExpr.TryCatchFinally(ReadExpression(children[0]), @finally, handlers); + } + case ExpressionType.MemberInit: + { + var children = GetChildren(index); + var bindings = new SysMemberBinding[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + bindings[i - 1] = ReadMemberBinding(children[i]); + return SysExpr.MemberInit((System.Linq.Expressions.NewExpression)ReadExpression(children[0]), bindings); + } + case ExpressionType.ListInit: + { + var children = GetChildren(index); + var initializers = new SysElementInit[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + initializers[i - 1] = ReadElementInit(children[i]); + return SysExpr.ListInit((System.Linq.Expressions.NewExpression)ReadExpression(children[0]), initializers); + } + case ExpressionType.TypeIs: + return SysExpr.TypeIs(ReadExpression(GetChildren(index)[0]), (Type)node.Obj); + case ExpressionType.TypeEqual: + return SysExpr.TypeEqual(ReadExpression(GetChildren(index)[0]), (Type)node.Obj); + case ExpressionType.Dynamic: + { + var children = GetChildren(index); + var delegateType = (Type)ReadObjectReference(children[0]); + var arguments = new SysExpr[children.Count - 1]; + for (var i = 1; i < children.Count; ++i) + arguments[i - 1] = ReadExpression(children[i]); + return SysExpr.MakeDynamic(delegateType, (CallSiteBinder)node.Obj, arguments); + } + case ExpressionType.RuntimeVariables: + { + var children = GetChildren(index); + var variables = new SysParameterExpression[children.Count]; + for (var i = 0; i < children.Count; ++i) + variables[i] = (SysParameterExpression)ReadExpression(children[i]); + return SysExpr.RuntimeVariables(variables); + } + case ExpressionType.DebugInfo: + { + var children = GetChildren(index); + ReadUInt16Pair(children[0], out var startLine, out var startColumn); + ReadUInt16Pair(children[1], out var endLine, out var endColumn); + return SysExpr.DebugInfo(SysExpr.SymbolDocument((string)node.Obj), startLine, startColumn, endLine, endColumn); + } + default: + if (node.ChildCount == 1) + { + var method = node.Obj as System.Reflection.MethodInfo; + return SysExpr.MakeUnary(node.NodeType, ReadExpression(GetChildren(index)[0]), node.Type, method); + } + + if (node.ChildCount >= 2) + { + var children = GetChildren(index); + var conversion = children.Count > 2 ? (System.Linq.Expressions.LambdaExpression)ReadExpression(children[2]) : null; + return SysExpr.MakeBinary(node.NodeType, ReadExpression(children[0]), ReadExpression(children[1]), + node.HasFlag(BinaryLiftedToNullFlag), (System.Reflection.MethodInfo)node.Obj, conversion); + } + + throw new NotSupportedException($"Reconstruction of `ExpressionType.{node.NodeType}` is not supported yet."); + } + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysSwitchCase ReadSwitchCase(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Is(ExprNodeKind.SwitchCase)); + var children = GetChildren(index); + var testValues = new SysExpr[children.Count - 1]; + for (var i = 0; i < testValues.Length; ++i) + testValues[i] = ReadExpression(children[i]); + return SysExpr.SwitchCase(ReadExpression(children[children.Count - 1]), testValues); + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysCatchBlock ReadCatchBlock(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Is(ExprNodeKind.CatchBlock)); + var children = GetChildren(index); + var childIndex = 0; + var variable = node.HasFlag(CatchHasVariableFlag) ? (SysParameterExpression)ReadExpression(children[childIndex++]) : null; + var body = ReadExpression(children[childIndex++]); + var filter = node.HasFlag(CatchHasFilterFlag) ? ReadExpression(children[childIndex]) : null; + return SysExpr.MakeCatchBlock(node.Type, variable, body, filter); + } + + private SysLabelTarget ReadLabelTarget(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Is(ExprNodeKind.LabelTarget)); + ref var label = ref _labelsById.Map.AddOrGetValueRef(node.ChildIdx, out var found); + if (found) + return label; + + return label = SysExpr.Label(node.Type, (string)node.Obj); + } + + private object ReadObjectReference(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Is(ExprNodeKind.ObjectReference)); + return node.Obj; + } + + private void ReadUInt16Pair(int index, out int first, out int second) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Is(ExprNodeKind.UInt16Pair)); + first = node.ChildIdx; + second = node.ChildCount; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysMemberBinding ReadMemberBinding(int index) + { + ref var node = ref _tree.Nodes[index]; + var member = (System.Reflection.MemberInfo)node.Obj; + switch (node.Kind) + { + case ExprNodeKind.MemberAssignment: + return SysExpr.Bind(member, ReadExpression(GetChildren(index)[0])); + case ExprNodeKind.MemberMemberBinding: + { + var childIndexes = GetChildren(index); + var bindings = new SysMemberBinding[childIndexes.Count]; + for (var i = 0; i < childIndexes.Count; ++i) + bindings[i] = ReadMemberBinding(childIndexes[i]); + return SysExpr.MemberBind(member, bindings); + } + case ExprNodeKind.MemberListBinding: + { + var childIndexes = GetChildren(index); + var initializers = new SysElementInit[childIndexes.Count]; + for (var i = 0; i < childIndexes.Count; ++i) + initializers[i] = ReadElementInit(childIndexes[i]); + return SysExpr.ListBind(member, initializers); + } + default: + throw new InvalidOperationException($"Node at index {index} is not a member binding node."); + } + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysElementInit ReadElementInit(int index) + { + ref var node = ref _tree.Nodes[index]; + Debug.Assert(node.Is(ExprNodeKind.ElementInit)); + return SysExpr.ElementInit((System.Reflection.MethodInfo)node.Obj, ReadExpressions(GetChildren(index))); + } + + private ChildList GetChildren(int index) + { + ref var node = ref _tree.Nodes[index]; + var count = node.ChildCount; + ChildList children = default; + var childIndex = node.ChildIdx; + for (var i = 0; i < count; ++i) + { + children.Add(childIndex); + childIndex = _tree.Nodes[childIndex].NextIdx; + } + return children; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + private SysExpr[] ReadExpressions(in ChildList childIndexes) + { + var expressions = new SysExpr[childIndexes.Count]; + for (var i = 0; i < expressions.Length; ++i) + expressions[i] = ReadExpression(childIndexes[i]); + return expressions; + } + + [RequiresUnreferencedCode(FastExpressionCompiler.LightExpression.Trimming.Message)] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2077", + Justification = "Flat expression round-trip stores the runtime type metadata explicitly for reconstruction.")] + private static System.Linq.Expressions.NewExpression CreateValueTypeNewExpression(Type type) => SysExpr.New(type); + } + +} + +/// Provides conversions from System and LightExpression trees to . +public static class FlatExpressionExtensions +{ + /// Flattens a System.Linq expression tree. + public static ExprTree ToFlatExpression(this SysExpr expression) => ExprTree.FromExpression(expression); + + /// Flattens a LightExpression tree. + public static ExprTree ToFlatExpression(this LightExpression expression) => ExprTree.FromLightExpression(expression); +} diff --git a/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs new file mode 100644 index 00000000..525ad531 --- /dev/null +++ b/test/FastExpressionCompiler.Benchmarks/LightExprVsFlatExpr_Create_ComplexExpr.cs @@ -0,0 +1,22 @@ +using BenchmarkDotNet.Attributes; +using FastExpressionCompiler.FlatExpression; +using FastExpressionCompiler.LightExpression.UnitTests; + +namespace FastExpressionCompiler.Benchmarks +{ + [MemoryDiagnoser, RankColumn, Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)] + public class LightExprVsFlatExpr_Create_ComplexExpr + { + // Keep the created values reachable so the construction work is not elided. + private FastExpressionCompiler.LightExpression.Expression> _lightExpr; + private ExprTree _flatExpr; + + [Benchmark(Baseline = true)] + public void Create_LightExpression() => + _lightExpr = LightExpressionTests.CreateComplexLightExpression(); + + [Benchmark] + public void Create_FlatExpression() => + _flatExpr = LightExpressionTests.CreateComplexFlatExpression(); + } +} diff --git a/test/FastExpressionCompiler.Benchmarks/Program.cs b/test/FastExpressionCompiler.Benchmarks/Program.cs index b00d5c60..d6b8cc09 100644 --- a/test/FastExpressionCompiler.Benchmarks/Program.cs +++ b/test/FastExpressionCompiler.Benchmarks/Program.cs @@ -21,6 +21,7 @@ public static void Main() // BenchmarkRunner.Run(); // not included in README.md, may be it needs to // BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); //-------------------------------------------- diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj index 057d6c12..42ebb322 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/FastExpressionCompiler.LightExpression.UnitTests.csproj @@ -11,8 +11,16 @@ - - - - - + + + + + + + + + + + + + diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs new file mode 100644 index 00000000..a9e24e4e --- /dev/null +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionPropertyTests.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using FastExpressionCompiler.FlatExpression; +using static FastExpressionCompiler.LightExpression.Expression; +#if NET8_0_OR_GREATER +using CsCheck; +#endif + +namespace FastExpressionCompiler.LightExpression.UnitTests; + +public partial class LightExpressionTests +{ +#if NET8_0_OR_GREATER + public void Can_property_test_generated_flat_expression_roundtrip_structurally() + { + Gen.Int[0, int.MaxValue / 2] + .Select(seed => new GeneratedCase(seed, GeneratedIntSpecFactory.Create(seed, maxDepth: 3, maxBreadth: 3))) + .Sample(testCase => + GeneratedExpressionComparer.AreEqual( + CreateGeneratedLightExpression(testCase.Spec), + CreateGeneratedFlatExpression(testCase.Spec).ToLightExpression()), + iter: 100, + threads: 1, + seed: "0N0XIzNsQ0O2", + print: testCase => $"{testCase.Seed}: {testCase.Spec}"); + } + + private static FastExpressionCompiler.LightExpression.Expression> CreateGeneratedLightExpression(IntSpec spec) + { + var parameter = ParameterOf("p"); + return Lambda>(BuildLightInt(spec, [parameter]), parameter); + } + + private static ExprTree CreateGeneratedFlatExpression(IntSpec spec) + { + var fe = default(ExprTree); + var parameter = fe.ParameterOf("p"); + fe.RootIndex = fe.Lambda>(BuildFlatInt(ref fe, spec, [parameter]), parameter); + return fe; + } + + private static FastExpressionCompiler.LightExpression.Expression BuildLightInt(IntSpec spec, FastExpressionCompiler.LightExpression.ParameterExpression[] ints) => + spec switch + { + IntSpec.ParameterRef parameter => ints[parameter.Index], + IntSpec.Constant constant => Constant(constant.Value), + IntSpec.Add add => Add(BuildLightInt(add.Left, ints), BuildLightInt(add.Right, ints)), + IntSpec.Subtract subtract => Subtract(BuildLightInt(subtract.Left, ints), BuildLightInt(subtract.Right, ints)), + IntSpec.Multiply multiply => Multiply(BuildLightInt(multiply.Left, ints), BuildLightInt(multiply.Right, ints)), + IntSpec.Conditional conditional => Condition( + BuildLightBool(conditional.Test, ints), + BuildLightInt(conditional.IfTrue, ints), + BuildLightInt(conditional.IfFalse, ints)), + IntSpec.LetMany letMany => BuildLightBlock(letMany, ints), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static int BuildFlatInt(ref ExprTree fe, IntSpec spec, int[] ints) => + spec switch + { + IntSpec.ParameterRef parameter => ints[parameter.Index], + IntSpec.Constant constant => fe.ConstantInt(constant.Value), + IntSpec.Add add => fe.Add(BuildFlatInt(ref fe, add.Left, ints), BuildFlatInt(ref fe, add.Right, ints)), + IntSpec.Subtract subtract => fe.MakeBinary(ExpressionType.Subtract, + BuildFlatInt(ref fe, subtract.Left, ints), BuildFlatInt(ref fe, subtract.Right, ints)), + IntSpec.Multiply multiply => fe.MakeBinary(ExpressionType.Multiply, + BuildFlatInt(ref fe, multiply.Left, ints), BuildFlatInt(ref fe, multiply.Right, ints)), + IntSpec.Conditional conditional => fe.Condition( + BuildFlatBool(ref fe, conditional.Test, ints), + BuildFlatInt(ref fe, conditional.IfTrue, ints), + BuildFlatInt(ref fe, conditional.IfFalse, ints)), + IntSpec.LetMany letMany => BuildFlatBlock(ref fe, letMany, ints), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static FastExpressionCompiler.LightExpression.Expression BuildLightBool(BoolSpec spec, FastExpressionCompiler.LightExpression.ParameterExpression[] ints) => + spec switch + { + BoolSpec.Constant constant => Constant(constant.Value), + BoolSpec.Not not => Not(BuildLightBool(not.Operand, ints)), + BoolSpec.Equal equal => Equal(BuildLightInt(equal.Left, ints), BuildLightInt(equal.Right, ints)), + BoolSpec.GreaterThan greaterThan => GreaterThan(BuildLightInt(greaterThan.Left, ints), BuildLightInt(greaterThan.Right, ints)), + BoolSpec.AndAlso andAlso => AndAlso(BuildLightBool(andAlso.Left, ints), BuildLightBool(andAlso.Right, ints)), + BoolSpec.OrElse orElse => OrElse(BuildLightBool(orElse.Left, ints), BuildLightBool(orElse.Right, ints)), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static int BuildFlatBool(ref ExprTree fe, BoolSpec spec, int[] ints) => + spec switch + { + BoolSpec.Constant constant => fe.ConstantOf(constant.Value), + BoolSpec.Not not => fe.Not(BuildFlatBool(ref fe, not.Operand, ints)), + BoolSpec.Equal equal => fe.Equal(BuildFlatInt(ref fe, equal.Left, ints), BuildFlatInt(ref fe, equal.Right, ints)), + BoolSpec.GreaterThan greaterThan => fe.MakeBinary(ExpressionType.GreaterThan, + BuildFlatInt(ref fe, greaterThan.Left, ints), BuildFlatInt(ref fe, greaterThan.Right, ints)), + BoolSpec.AndAlso andAlso => fe.MakeBinary(ExpressionType.AndAlso, + BuildFlatBool(ref fe, andAlso.Left, ints), BuildFlatBool(ref fe, andAlso.Right, ints)), + BoolSpec.OrElse orElse => fe.MakeBinary(ExpressionType.OrElse, + BuildFlatBool(ref fe, orElse.Left, ints), BuildFlatBool(ref fe, orElse.Right, ints)), + _ => throw new NotSupportedException(spec.GetType().Name) + }; + + private static FastExpressionCompiler.LightExpression.Expression BuildLightBlock(IntSpec.LetMany letMany, FastExpressionCompiler.LightExpression.ParameterExpression[] ints) + { + var locals = new FastExpressionCompiler.LightExpression.ParameterExpression[letMany.Values.Length]; + var expressions = new FastExpressionCompiler.LightExpression.Expression[letMany.Values.Length + 1]; + for (var i = 0; i < locals.Length; ++i) + { + locals[i] = Variable(typeof(int), $"v{i}"); + expressions[i] = Assign(locals[i], BuildLightInt(letMany.Values[i], ints)); + } + + expressions[locals.Length] = BuildLightInt(letMany.Body, Append(ints, locals)); + return Block(locals, expressions); + } + + private static int BuildFlatBlock(ref ExprTree fe, IntSpec.LetMany letMany, int[] ints) + { + var locals = new int[letMany.Values.Length]; + var expressions = new int[letMany.Values.Length + 1]; + for (var i = 0; i < locals.Length; ++i) + { + locals[i] = fe.Variable(typeof(int), $"v{i}"); + expressions[i] = fe.Assign(locals[i], BuildFlatInt(ref fe, letMany.Values[i], ints)); + } + + expressions[locals.Length] = BuildFlatInt(ref fe, letMany.Body, Append(ints, locals)); + return fe.Block(typeof(int), locals, expressions); + } + + private static T[] Append(T[] source, T[] append) + { + var result = new T[source.Length + append.Length]; + Array.Copy(source, result, source.Length); + Array.Copy(append, 0, result, source.Length, append.Length); + return result; + } + + private readonly record struct GeneratedCase(int Seed, IntSpec Spec); + + private abstract record IntSpec + { + public sealed record ParameterRef(int Index) : IntSpec; + public sealed record Constant(int Value) : IntSpec; + public sealed record Add(IntSpec Left, IntSpec Right) : IntSpec; + public sealed record Subtract(IntSpec Left, IntSpec Right) : IntSpec; + public sealed record Multiply(IntSpec Left, IntSpec Right) : IntSpec; + public sealed record Conditional(BoolSpec Test, IntSpec IfTrue, IntSpec IfFalse) : IntSpec; + public sealed record LetMany(IntSpec[] Values, IntSpec Body) : IntSpec; + } + + private abstract record BoolSpec + { + public sealed record Constant(bool Value) : BoolSpec; + public sealed record Not(BoolSpec Operand) : BoolSpec; + public sealed record Equal(IntSpec Left, IntSpec Right) : BoolSpec; + public sealed record GreaterThan(IntSpec Left, IntSpec Right) : BoolSpec; + public sealed record AndAlso(BoolSpec Left, BoolSpec Right) : BoolSpec; + public sealed record OrElse(BoolSpec Left, BoolSpec Right) : BoolSpec; + } + + private static class GeneratedIntSpecFactory + { + public static IntSpec Create(int seed, int maxDepth, int maxBreadth) => + NextInt(new Random(seed), maxDepth, envIntCount: 1, maxBreadth); + + private static IntSpec NextInt(Random random, int depth, int envIntCount, int maxBreadth) + { + if (depth <= 0) + return NextIntLeaf(random, envIntCount); + + switch (random.Next(7)) + { + case 0: return NextIntLeaf(random, envIntCount); + case 1: return new IntSpec.Add(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 2: return new IntSpec.Subtract(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 3: return new IntSpec.Multiply(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 4: return new IntSpec.Conditional( + NextBool(random, depth - 1, envIntCount, maxBreadth), + NextInt(random, depth - 1, envIntCount, maxBreadth), + NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 5: return NextLetMany(random, depth - 1, envIntCount, maxBreadth); + default: return new IntSpec.Constant(random.Next(-8, 9)); + } + } + + private static IntSpec NextIntLeaf(Random random, int envIntCount) => + random.Next(3) == 0 + ? new IntSpec.Constant(random.Next(-8, 9)) + : new IntSpec.ParameterRef(random.Next(envIntCount)); + + private static IntSpec NextLetMany(Random random, int depth, int envIntCount, int maxBreadth) + { + var count = random.Next(1, maxBreadth + 1); + var values = new IntSpec[count]; + for (var i = 0; i < count; ++i) + values[i] = NextInt(random, depth, envIntCount, maxBreadth); + return new IntSpec.LetMany(values, NextInt(random, depth, envIntCount + count, maxBreadth)); + } + + private static BoolSpec NextBool(Random random, int depth, int envIntCount, int maxBreadth) + { + if (depth <= 0) + return NextBoolLeaf(random, envIntCount); + + switch (random.Next(6)) + { + case 0: return NextBoolLeaf(random, envIntCount); + case 1: return new BoolSpec.Not(NextBool(random, depth - 1, envIntCount, maxBreadth)); + case 2: return new BoolSpec.Equal(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 3: return new BoolSpec.GreaterThan(NextInt(random, depth - 1, envIntCount, maxBreadth), NextInt(random, depth - 1, envIntCount, maxBreadth)); + case 4: return new BoolSpec.AndAlso(NextBool(random, depth - 1, envIntCount, maxBreadth), NextBool(random, depth - 1, envIntCount, maxBreadth)); + default: return new BoolSpec.OrElse(NextBool(random, depth - 1, envIntCount, maxBreadth), NextBool(random, depth - 1, envIntCount, maxBreadth)); + } + } + + private static BoolSpec NextBoolLeaf(Random random, int envIntCount) => + random.Next(2) == 0 + ? new BoolSpec.Constant(random.Next(2) == 0) + : new BoolSpec.Equal(NextIntLeaf(random, envIntCount), NextIntLeaf(random, envIntCount)); + } + + private sealed class GeneratedExpressionComparer + { + private readonly List _xs = new(); + private readonly List _ys = new(); + + public static bool AreEqual(FastExpressionCompiler.LightExpression.Expression x, FastExpressionCompiler.LightExpression.Expression y) => new GeneratedExpressionComparer().Eq(x, y); + + private bool Eq(FastExpressionCompiler.LightExpression.Expression x, FastExpressionCompiler.LightExpression.Expression y) + { + if (ReferenceEquals(x, y)) + return true; + if (x == null || y == null || x.NodeType != y.NodeType || x.Type != y.Type) + return false; + + return x.NodeType switch + { + ExpressionType.Lambda => EqLambda((FastExpressionCompiler.LightExpression.LambdaExpression)x, (FastExpressionCompiler.LightExpression.LambdaExpression)y), + ExpressionType.Parameter => EqParameter((FastExpressionCompiler.LightExpression.ParameterExpression)x, (FastExpressionCompiler.LightExpression.ParameterExpression)y), + ExpressionType.Constant => Equals(((FastExpressionCompiler.LightExpression.ConstantExpression)x).Value, ((FastExpressionCompiler.LightExpression.ConstantExpression)y).Value), + ExpressionType.Not => Eq(((FastExpressionCompiler.LightExpression.UnaryExpression)x).Operand, ((FastExpressionCompiler.LightExpression.UnaryExpression)y).Operand), + ExpressionType.Add or ExpressionType.Subtract or ExpressionType.Multiply or ExpressionType.Assign + or ExpressionType.Equal or ExpressionType.GreaterThan or ExpressionType.AndAlso or ExpressionType.OrElse + => EqBinary((FastExpressionCompiler.LightExpression.BinaryExpression)x, (FastExpressionCompiler.LightExpression.BinaryExpression)y), + ExpressionType.Conditional => EqConditional((FastExpressionCompiler.LightExpression.ConditionalExpression)x, (FastExpressionCompiler.LightExpression.ConditionalExpression)y), + ExpressionType.Block => EqBlock((FastExpressionCompiler.LightExpression.BlockExpression)x, (FastExpressionCompiler.LightExpression.BlockExpression)y), + _ => throw new NotSupportedException(x.NodeType.ToString()) + }; + } + + private bool EqLambda(FastExpressionCompiler.LightExpression.LambdaExpression x, FastExpressionCompiler.LightExpression.LambdaExpression y) + { + if (x.Parameters.Count != y.Parameters.Count) + return false; + + var start = _xs.Count; + for (var i = 0; i < x.Parameters.Count; ++i) + { + _xs.Add(x.Parameters[i]); + _ys.Add(y.Parameters[i]); + } + + var equal = Eq(x.Body, y.Body); + _xs.RemoveRange(start, _xs.Count - start); + _ys.RemoveRange(start, _ys.Count - start); + return equal; + } + + private bool EqParameter(FastExpressionCompiler.LightExpression.ParameterExpression x, FastExpressionCompiler.LightExpression.ParameterExpression y) + { + for (var i = _xs.Count - 1; i >= 0; --i) + { + var xMatches = ReferenceEquals(_xs[i], x); + var yMatches = ReferenceEquals(_ys[i], y); + if (xMatches || yMatches) + return xMatches && yMatches; + } + + return x.Name == y.Name; + } + + private bool EqBinary(FastExpressionCompiler.LightExpression.BinaryExpression x, FastExpressionCompiler.LightExpression.BinaryExpression y) => + x.Method == y.Method && Eq(x.Left, y.Left) && Eq(x.Right, y.Right); + + private bool EqConditional(FastExpressionCompiler.LightExpression.ConditionalExpression x, FastExpressionCompiler.LightExpression.ConditionalExpression y) => + Eq(x.Test, y.Test) && Eq(x.IfTrue, y.IfTrue) && Eq(x.IfFalse, y.IfFalse); + + private bool EqBlock(FastExpressionCompiler.LightExpression.BlockExpression x, FastExpressionCompiler.LightExpression.BlockExpression y) + { + if (x.Variables.Count != y.Variables.Count || x.Expressions.Count != y.Expressions.Count) + return false; + + var start = _xs.Count; + for (var i = 0; i < x.Variables.Count; ++i) + { + _xs.Add(x.Variables[i]); + _ys.Add(y.Variables[i]); + } + + var equal = true; + for (var i = 0; equal && i < x.Expressions.Count; ++i) + equal = Eq(x.Expressions[i], y.Expressions[i]); + + _xs.RemoveRange(start, _xs.Count - start); + _ys.RemoveRange(start, _ys.Count - start); + return equal; + } + } +#else + public void Can_property_test_generated_flat_expression_roundtrip_structurally() { } +#endif +} diff --git a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs index 6421f4a4..b91c23c0 100644 --- a/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs +++ b/test/FastExpressionCompiler.LightExpression.UnitTests/LightExpressionTests.cs @@ -1,7 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; +using FastExpressionCompiler.FlatExpression; using static FastExpressionCompiler.LightExpression.Expression; using System.Linq.Expressions; @@ -10,7 +12,7 @@ namespace FastExpressionCompiler.LightExpression.UnitTests { - public class LightExpressionTests : ITest + public partial class LightExpressionTests : ITest { public int Run() { @@ -26,7 +28,13 @@ public int Run() Should_output_the_System_and_LightExpression_to_the_identical_CSharp_syntax(); Expression_produced_by_ToExpressionString_should_compile(); Multiple_methods_in_block_should_be_aligned_when_output_to_csharp(); - return 11; + Can_roundtrip_light_expression_through_flat_expression(); + Flat_expression_preserves_parameter_and_label_identity_and_collects_closure_constants(); + Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression(); + Can_build_flat_expression_directly_with_light_expression_like_api(); + Can_build_flat_expression_control_flow_directly(); + Can_property_test_generated_flat_expression_roundtrip_structurally(); + return 17; } @@ -229,6 +237,28 @@ public static Expression> CreateComplexLightExpression_wi return expr; } + public static ExprTree CreateComplexFlatExpression(string parameterName = null) + { + var fe = default(ExprTree); + var stateParamExpr = fe.ParameterOf(parameterName); + var body = fe.MemberInit( + fe.New(_ctorOfA, + fe.New(_ctorOfB), + fe.Convert( + fe.ArrayIndex(stateParamExpr, fe.ConstantInt(11)), + typeof(string)), + fe.NewArrayInit(typeof(ID), + fe.New(_ctorOfD1), + fe.New(_ctorOfD2))), + fe.Bind(_propAProp, + fe.New(_ctorOfP, + fe.New(_ctorOfB))), + fe.Bind(_fieldABop, + fe.New(_ctorOfB))); + fe.RootIndex = fe.Lambda>(body, stateParamExpr); + return fe; + } + public void Can_compile_complex_expr_with_Arrays_and_Casts() { @@ -339,6 +369,123 @@ public void Multiple_methods_in_block_should_be_aligned_when_output_to_csharp() public static void SayHi(int i, int j) { } + public void Can_roundtrip_light_expression_through_flat_expression() + { + var expr = CreateComplexLightExpression("state"); + + var flat = expr.ToFlatExpression(); + + Asserts.IsTrue(flat.Nodes.Count > 0); + Asserts.AreEqual(0, flat.ClosureConstants.Count); + + var roundtrip = (LambdaExpression)flat.ToLightExpression(); + var func = roundtrip.CompileFast>(true); + var state = new object[12]; + state[11] = "flat"; + var a = (A)func(state); + + Asserts.AreEqual("flat", a.Sop); + Asserts.IsInstanceOf

(a.Prop); + Asserts.AreEqual(2, a.Dop.Count()); + } + + public void Flat_expression_preserves_parameter_and_label_identity_and_collects_closure_constants() + { + var valueHolder = new S(); + var valueField = typeof(S).GetField(nameof(S.Value)); + var constExpr = Lambda>(Field(Constant(valueHolder), valueField)); + var constFlat = constExpr.ToFlatExpression(); + + Asserts.AreEqual(1, constFlat.ClosureConstants.Count); + Asserts.AreSame(valueHolder, constFlat.ClosureConstants[0]); + Asserts.AreEqual(null, ((LambdaExpression)constFlat.ToLightExpression()).CompileFast>(true)()); + + var p = SysExpr.Parameter(typeof(int), "p"); + var target = SysExpr.Label(typeof(int), "done"); + var sysLambda = SysExpr.Lambda>( + SysExpr.Block( + SysExpr.Goto(target, p, typeof(int)), + SysExpr.Label(target, SysExpr.Constant(0))), + p); + + var sysRoundtrip = (System.Linq.Expressions.LambdaExpression)sysLambda + .ToFlatExpression() + .ToExpression(); + + var block = (System.Linq.Expressions.BlockExpression)sysRoundtrip.Body; + var @goto = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; + var label = (System.Linq.Expressions.LabelExpression)block.Expressions[1]; + + Asserts.AreSame(sysRoundtrip.Parameters[0], @goto.Value); + Asserts.AreSame(@goto.Target, label.Target); + } + + public void Can_convert_dynamic_runtime_variables_and_debug_info_to_light_expression_and_flat_expression() + { + var runtimeParameter = SysExpr.Parameter(typeof(int), "runtime"); + var runtimeVariables = SysExpr.RuntimeVariables(runtimeParameter); + var runtimeVariablesLight = runtimeVariables.ToLightExpression(); + var runtimeVariablesRoundtrip = runtimeVariablesLight.ToFlatExpression().ToLightExpression(); + + Asserts.AreEqual(ExpressionType.RuntimeVariables, runtimeVariablesLight.NodeType); + Asserts.AreEqual(ExpressionType.RuntimeVariables, runtimeVariablesRoundtrip.NodeType); + + var document = SysExpr.SymbolDocument("flat-expression.cs"); + var debugInfo = SysExpr.DebugInfo(document, 1, 1, 1, 10); + var debugInfoLight = debugInfo.ToLightExpression(); + var debugInfoRoundtrip = debugInfoLight.ToFlatExpression().ToLightExpression(); + + Asserts.AreEqual(ExpressionType.DebugInfo, debugInfoLight.NodeType); + Asserts.AreEqual(ExpressionType.DebugInfo, debugInfoRoundtrip.NodeType); + + var dynamicArgument = SysExpr.Parameter(typeof(object), "arg"); + var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags.None, "Length", typeof(LightExpressionTests), + new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags.None, null) }); + var dynamicExpression = SysExpr.MakeDynamic(typeof(Func), binder, new[] { dynamicArgument }); + + var dynamicLight = dynamicExpression.ToLightExpression(); + var dynamicRoundtrip = dynamicLight.ToFlatExpression().ToLightExpression(); + + Asserts.AreEqual(ExpressionType.Dynamic, dynamicLight.NodeType); + Asserts.AreEqual(ExpressionType.Dynamic, dynamicRoundtrip.NodeType); + Asserts.AreEqual(ExpressionType.Dynamic, dynamicLight.ToFlatExpression().ToExpression().NodeType); + } + + public void Can_build_flat_expression_directly_with_light_expression_like_api() + { + var fe = CreateComplexFlatExpression("state"); + var lambda = (LambdaExpression)fe.ToLightExpression(); + var func = lambda.CompileFast>(true); + var runtimeState = new object[12]; + runtimeState[11] = "direct"; + + var a = (A)func(runtimeState); + + Asserts.AreEqual("direct", a.Sop); + Asserts.IsInstanceOf

(a.Prop); + Asserts.AreEqual(2, a.Dop.Count()); + } + + public void Can_build_flat_expression_control_flow_directly() + { + var fe = default(ExprTree); + var p = fe.Parameter(typeof(int), "p"); + var target = fe.Label(typeof(int), "done"); + fe.RootIndex = fe.Lambda>( + fe.Block( + fe.Goto(target, p, typeof(int)), + fe.Label(target, fe.ConstantInt(0))), + p); + + var sysLambda = (System.Linq.Expressions.LambdaExpression)fe.ToExpression(); + var block = (System.Linq.Expressions.BlockExpression)sysLambda.Body; + var gotoExpr = (System.Linq.Expressions.GotoExpression)block.Expressions[0]; + var label = (System.Linq.Expressions.LabelExpression)block.Expressions[1]; + + Asserts.AreSame(sysLambda.Parameters[0], gotoExpr.Value); + Asserts.AreSame(gotoExpr.Target, label.Target); + } + public class A { public P Prop { get; set; }