Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / System.Core / System.Linq.Expressions / EmitContext.cs
index d952c0c3af2c5b5675c1e95d5874c3938d22bfba..a43b87f728bf0f2ad672b30c2e2b2edea1a2b808 100644 (file)
@@ -27,6 +27,7 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
+#if !FULL_AOT_RUNTIME
 using System;
 using System.Collections.ObjectModel;
 using System.Collections.Generic;
@@ -38,25 +39,221 @@ using System.Runtime.CompilerServices;
 
 namespace System.Linq.Expressions {
 
-       abstract class EmitContext {
+       class CompilationContext {
 
-               protected LambdaExpression owner;
-               protected Type [] param_types;
-               protected Type return_type;
+               class ParameterReplacer : ExpressionTransformer {
 
-               protected Dictionary<object, int> indexes;
+                       CompilationContext context;
+                       ExecutionScope scope;
+                       object [] locals;
 
-               public ILGenerator ig;
+                       public ParameterReplacer (CompilationContext context, ExecutionScope scope, object [] locals)
+                       {
+                               this.context = context;
+                               this.scope = scope;
+                               this.locals = locals;
+                       }
+
+                       protected override Expression VisitParameter (ParameterExpression parameter)
+                       {
+                               var scope = this.scope;
+                               var locals = this.locals;
+
+                               while (scope != null) {
+                                       int position = IndexOfHoistedLocal (scope, parameter);
+                                       if (position != -1)
+                                               return ReadHoistedLocalFromArray (locals, position);
+
+                                       locals = scope.Locals;
+                                       scope = scope.Parent;
+                               }
+
+                               return parameter;
+                       }
+
+                       Expression ReadHoistedLocalFromArray (object [] locals, int position)
+                       {
+                               return Expression.Field (
+                                       Expression.Convert (
+                                               Expression.ArrayIndex (
+                                                       Expression.Constant (locals),
+                                                       Expression.Constant (position)),
+                                               locals [position].GetType ()),
+                                       "Value");
+                       }
+
+                       int IndexOfHoistedLocal (ExecutionScope scope, ParameterExpression parameter)
+                       {
+                               return context.units [scope.compilation_unit].IndexOfHoistedLocal (parameter);
+                       }
+               }
+
+               class HoistedVariableDetector : ExpressionVisitor {
+
+                       Dictionary<ParameterExpression, LambdaExpression> parameter_to_lambda =
+                               new Dictionary<ParameterExpression, LambdaExpression> ();
+
+                       Dictionary<LambdaExpression, List<ParameterExpression>> hoisted_map;
+
+                       LambdaExpression lambda;
+
+                       public Dictionary<LambdaExpression, List<ParameterExpression>> Process (LambdaExpression lambda)
+                       {
+                               Visit (lambda);
+                               return hoisted_map;
+                       }
+
+                       protected override void VisitLambda (LambdaExpression lambda)
+                       {
+                               this.lambda = lambda;
+                               foreach (var parameter in lambda.Parameters)
+                                       parameter_to_lambda [parameter] = lambda;
+                               base.VisitLambda (lambda);
+                       }
+
+                       protected override void VisitParameter (ParameterExpression parameter)
+                       {
+                               if (lambda.Parameters.Contains (parameter))
+                                       return;
+
+                               Hoist (parameter);
+                       }
+
+                       void Hoist (ParameterExpression parameter)
+                       {
+                               LambdaExpression lambda;
+                               if (!parameter_to_lambda.TryGetValue (parameter, out lambda))
+                                       return;
+
+                               if (hoisted_map == null)
+                                       hoisted_map = new Dictionary<LambdaExpression, List<ParameterExpression>> ();
+
+                               List<ParameterExpression> hoisted;
+                               if (!hoisted_map.TryGetValue (lambda, out hoisted)) {
+                                       hoisted = new List<ParameterExpression> ();
+                                       hoisted_map [lambda] = hoisted;
+                               }
+
+                               hoisted.Add (parameter);
+                       }
+               }
+
+               List<object> globals = new List<object> ();
+               List<EmitContext> units = new List<EmitContext> ();
+               Dictionary<LambdaExpression, List<ParameterExpression>> hoisted_map;
+
+               public int AddGlobal (object global)
+               {
+                       return AddItemToList (global, globals);
+               }
+
+               public object [] GetGlobals ()
+               {
+                       return globals.ToArray ();
+               }
+
+               static int AddItemToList<T> (T item, IList<T> list)
+               {
+                       list.Add (item);
+                       return list.Count - 1;
+               }
+
+               public int AddCompilationUnit (LambdaExpression lambda)
+               {
+                       DetectHoistedVariables (lambda);
+                       return AddCompilationUnit (null, lambda);
+               }
+
+               public int AddCompilationUnit (EmitContext parent, LambdaExpression lambda)
+               {
+                       var context = new EmitContext (this, parent, lambda);
+                       var unit = AddItemToList (context, units);
+                       context.Emit ();
+                       return unit;
+               }
+
+               void DetectHoistedVariables (LambdaExpression lambda)
+               {
+                       hoisted_map = new HoistedVariableDetector ().Process (lambda);
+               }
+
+               public List<ParameterExpression> GetHoistedLocals (LambdaExpression lambda)
+               {
+                       if (hoisted_map == null)
+                               return null;
+
+                       List<ParameterExpression> hoisted;
+                       hoisted_map.TryGetValue (lambda, out hoisted);
+                       return hoisted;
+               }
+
+               public object [] CreateHoistedLocals (int unit)
+               {
+                       var hoisted = GetHoistedLocals (units [unit].Lambda);
+                       return new object [hoisted == null ? 0 : hoisted.Count];
+               }
+
+               public Expression IsolateExpression (ExecutionScope scope, object [] locals, Expression expression)
+               {
+                       return new ParameterReplacer (this, scope, locals).Transform (expression);
+               }
+
+               public Delegate CreateDelegate ()
+               {
+                       return CreateDelegate (0, new ExecutionScope (this));
+               }
+
+               public Delegate CreateDelegate (int unit, ExecutionScope scope)
+               {
+                       return units [unit].CreateDelegate (scope);
+               }
+       }
+
+       class EmitContext {
+
+               CompilationContext context;
+               EmitContext parent;
+               LambdaExpression lambda;
+               DynamicMethod method;
+               LocalBuilder hoisted_store;
+               List<ParameterExpression> hoisted;
+
+               public readonly ILGenerator ig;
+
+               public bool HasHoistedLocals {
+                       get { return hoisted != null && hoisted.Count > 0; }
+               }
 
-               protected EmitContext (LambdaExpression lambda)
+               public LambdaExpression Lambda {
+                       get { return lambda; }
+               }
+
+               public EmitContext (CompilationContext context, EmitContext parent, LambdaExpression lambda)
                {
-                       this.owner = lambda;
+                       this.context = context;
+                       this.parent = parent;
+                       this.lambda = lambda;
+                       this.hoisted = context.GetHoistedLocals (lambda);
+
+                       method = new DynamicMethod (
+                               "lambda_method",
+                               lambda.GetReturnType (),
+                               CreateParameterTypes (lambda.Parameters),
+                               typeof (ExecutionScope),
+                               true);
 
-                       param_types = CreateParameterTypes (owner.Parameters);
-                       return_type = owner.GetReturnType ();
+                       ig = method.GetILGenerator ();
                }
 
-               static Type [] CreateParameterTypes (ReadOnlyCollection<ParameterExpression> parameters)
+               public void Emit ()
+               {
+                       if (HasHoistedLocals)
+                               EmitStoreHoistedLocals ();
+
+                       lambda.EmitBody (this);
+               }
+
+               static Type [] CreateParameterTypes (IList<ParameterExpression> parameters)
                {
                        var types = new Type [parameters.Count + 1];
                        types [0] = typeof (ExecutionScope);
@@ -67,26 +264,22 @@ namespace System.Linq.Expressions {
                        return types;
                }
 
-               public static EmitContext Create (LambdaExpression lambda)
+               public bool IsLocalParameter (ParameterExpression parameter, ref int position)
                {
-#if !NET_2_1
-                       if (Environment.GetEnvironmentVariable ("LINQ_DBG") != null)
-                               return new DebugEmitContext (lambda);
-#endif
-                       return new DynamicEmitContext (lambda);
+                       position = lambda.Parameters.IndexOf (parameter);
+                       if (position > -1) {
+                               position++;
+                               return true;
+                       }
+
+                       return false;
                }
 
-               public int GetParameterPosition (ParameterExpression p)
+               public Delegate CreateDelegate (ExecutionScope scope)
                {
-                       int position = owner.Parameters.IndexOf (p);
-                       if (position == -1)
-                               throw new InvalidOperationException ("Parameter not in scope");
-
-                       return position + 1; // + 1 because 0 is the ExecutionScope
+                       return method.CreateDelegate (lambda.Type, scope);
                }
 
-               public abstract Delegate CreateDelegate ();
-
                public void Emit (Expression expression)
                {
                        expression.Emit (this);
@@ -101,13 +294,56 @@ namespace System.Linq.Expressions {
                        return local;
                }
 
-               public void EmitLoad (Expression expression)
+               public void EmitLoadAddress (Expression expression)
+               {
+                       ig.Emit (OpCodes.Ldloca, EmitStored (expression));
+               }
+
+               public void EmitLoadEnum (Expression expression)
+               {
+                       expression.Emit (this);
+                       ig.Emit (OpCodes.Box, expression.Type);
+               }
+
+               public void EmitLoadEnum (LocalBuilder local)
                {
+                       ig.Emit (OpCodes.Ldloc, local);
+                       ig.Emit (OpCodes.Box, local.LocalType);
+               }
+
+               public void EmitLoadSubject (Expression expression)
+               {
+                       if (expression.Type.IsEnum) {
+                               EmitLoadEnum (expression);
+                               return;
+                       }
+
                        if (expression.Type.IsValueType) {
-                               var local = EmitStored (expression);
-                               ig.Emit (OpCodes.Ldloca, local);
-                       } else
-                               expression.Emit (this);
+                               EmitLoadAddress (expression);
+                               return;
+                       }
+
+                       Emit (expression);
+               }
+
+               public void EmitLoadSubject (LocalBuilder local)
+               {
+                       if (local.LocalType.IsEnum) {
+                               EmitLoadEnum (local);
+                               return;
+                       }
+
+                       if (local.LocalType.IsValueType) {
+                               EmitLoadAddress (local);
+                               return;
+                       }
+
+                       EmitLoad (local);
+               }
+
+               public void EmitLoadAddress (LocalBuilder local)
+               {
+                       ig.Emit (OpCodes.Ldloca, local);
                }
 
                public void EmitLoad (LocalBuilder local)
@@ -115,22 +351,53 @@ namespace System.Linq.Expressions {
                        ig.Emit (OpCodes.Ldloc, local);
                }
 
-               public void EmitCall (LocalBuilder local, IEnumerable<Expression> arguments, MethodInfo method)
+               public void EmitCall (LocalBuilder local, IList<Expression> arguments, MethodInfo method)
                {
-                       EmitLoad (local);
-                       EmitCollection (arguments);
+                       EmitLoadSubject (local);
+                       EmitArguments (method, arguments);
                        EmitCall (method);
                }
 
-               public void EmitCall (Expression expression, IEnumerable<Expression> arguments, MethodInfo method)
+               public void EmitCall (LocalBuilder local, MethodInfo method)
+               {
+                       EmitLoadSubject (local);
+                       EmitCall (method);
+               }
+
+               public void EmitCall (Expression expression, MethodInfo method)
                {
                        if (!method.IsStatic)
-                               EmitLoad (expression);
+                               EmitLoadSubject (expression);
 
-                       EmitCollection (arguments);
                        EmitCall (method);
                }
 
+               public void EmitCall (Expression expression, IList<Expression> arguments, MethodInfo method)
+               {
+                       if (!method.IsStatic)
+                               EmitLoadSubject (expression);
+
+                       EmitArguments (method, arguments);
+                       EmitCall (method);
+               }
+
+               void EmitArguments (MethodInfo method, IList<Expression> arguments)
+               {
+                       var parameters = method.GetParameters ();
+
+                       for (int i = 0; i < parameters.Length; i++) {
+                               var parameter = parameters [i];
+                               var argument = arguments [i];
+
+                               if (parameter.ParameterType.IsByRef) {
+                                       ig.Emit (OpCodes.Ldloca, EmitStored (argument));
+                                       continue;
+                               }
+
+                               Emit (arguments [i]);
+                       }
+               }
+
                public void EmitCall (MethodInfo method)
                {
                        ig.Emit (
@@ -138,6 +405,38 @@ namespace System.Linq.Expressions {
                                method);
                }
 
+               public void EmitNullableHasValue (LocalBuilder local)
+               {
+                       EmitCall (local, "get_HasValue");
+               }
+
+               public void EmitNullableInitialize (LocalBuilder local)
+               {
+                       ig.Emit (OpCodes.Ldloca, local);
+                       ig.Emit (OpCodes.Initobj, local.LocalType);
+                       ig.Emit (OpCodes.Ldloc, local);
+               }
+
+               public void EmitNullableGetValue (LocalBuilder local)
+               {
+                       EmitCall (local, "get_Value");
+               }
+
+               public void EmitNullableGetValueOrDefault (LocalBuilder local)
+               {
+                       EmitCall (local, "GetValueOrDefault");
+               }
+
+               void EmitCall (LocalBuilder local, string method_name)
+               {
+                       EmitCall (local, local.LocalType.GetMethod (method_name, Type.EmptyTypes));
+               }
+
+               public void EmitNullableNew (Type of)
+               {
+                       ig.Emit (OpCodes.Newobj, of.GetConstructor (new [] { of.GetFirstGenericArgument () }));
+               }
+
                public void EmitCollection<T> (IEnumerable<T> collection) where T : Expression
                {
                        foreach (var expression in collection)
@@ -160,8 +459,10 @@ namespace System.Linq.Expressions {
                {
                        expression.Emit (this);
 
-                       if (expression.Type.IsValueType)
-                               ig.Emit (OpCodes.Box, expression.Type);
+                       var type = expression.Type;
+
+                       if (type.IsValueType)
+                               ig.Emit (OpCodes.Box, type);
 
                        ig.Emit (OpCodes.Isinst, candidate);
                }
@@ -176,143 +477,134 @@ namespace System.Linq.Expressions {
                        EmitReadGlobal (global, global.GetType ());
                }
 
-               public void EmitReadGlobal (object global, Type type)
+               public void EmitLoadGlobals ()
                {
                        EmitScope ();
 
                        ig.Emit (OpCodes.Ldfld, typeof (ExecutionScope).GetField ("Globals"));
+               }
 
-                       int index;
-                       if (!indexes.TryGetValue (global, out index))
-                               throw new InvalidOperationException ();
+               public void EmitReadGlobal (object global, Type type)
+               {
+                       EmitLoadGlobals ();
 
-                       ig.Emit (OpCodes.Ldc_I4, index);
+                       ig.Emit (OpCodes.Ldc_I4, AddGlobal (global, type));
                        ig.Emit (OpCodes.Ldelem, typeof (object));
 
+                       EmitLoadStrongBoxValue (type);
+               }
+
+               public void EmitLoadStrongBoxValue (Type type)
+               {
                        var strongbox = type.MakeStrongBoxType ();
 
                        ig.Emit (OpCodes.Isinst, strongbox);
                        ig.Emit (OpCodes.Ldfld, strongbox.GetField ("Value"));
                }
-       }
 
-       class GlobalCollector : ExpressionVisitor {
+               int AddGlobal (object value, Type type)
+               {
+                       return context.AddGlobal (CreateStrongBox (value, type));
+               }
 
-               List<object> globals = new List<object> ();
-               Dictionary<object, int> indexes = new Dictionary<object,int> ();
+               public void EmitCreateDelegate (LambdaExpression lambda)
+               {
+                       EmitScope ();
 
-               public List<object> Globals {
-                       get { return globals; }
-               }
+                       ig.Emit (OpCodes.Ldc_I4, AddChildContext (lambda));
+                       if (hoisted_store != null)
+                               ig.Emit (OpCodes.Ldloc, hoisted_store);
+                       else
+                               ig.Emit (OpCodes.Ldnull);
 
-               public Dictionary<object, int> Indexes {
-                       get { return indexes; }
+                       ig.Emit (OpCodes.Callvirt, typeof (ExecutionScope).GetMethod ("CreateDelegate"));
+
+                       ig.Emit (OpCodes.Castclass, lambda.Type);
                }
 
-               public GlobalCollector (Expression expression)
+               void EmitStoreHoistedLocals ()
                {
-                       Visit (expression);
+                       EmitHoistedLocalsStore ();
+                       for (int i = 0; i < hoisted.Count; i++)
+                               EmitStoreHoistedLocal (i, hoisted [i]);
                }
 
-               protected override void VisitUnary (UnaryExpression unary)
+               void EmitStoreHoistedLocal (int position, ParameterExpression parameter)
                {
-                       if (unary.NodeType != ExpressionType.Quote) {
-                               base.VisitUnary (unary);
-                               return;
-                       }
-
-                       AddGlobal (unary.Operand, typeof (Expression));
+                       ig.Emit (OpCodes.Ldloc, hoisted_store);
+                       ig.Emit (OpCodes.Ldc_I4, position);
+                       parameter.Emit (this);
+                       EmitCreateStrongBox (parameter.Type);
+                       ig.Emit (OpCodes.Stelem, typeof (object));
                }
 
-               protected override void VisitConstant (ConstantExpression constant)
+               public void EmitLoadHoistedLocalsStore ()
                {
-                       var value = constant.Value;
-
-                       if (value == null)
-                               return;
-
-                       AddGlobal (value, value.GetType ());
+                       ig.Emit (OpCodes.Ldloc, hoisted_store);
                }
 
-               void AddGlobal (object value, Type type)
+               void EmitCreateStrongBox (Type type)
                {
-                       if (Type.GetTypeCode (type) != TypeCode.Object)
-                               return;
-
-                       indexes.Add (value, globals.Count);
-                       globals.Add (CreateStrongBox (value, type));
+                       ig.Emit (OpCodes.Newobj, type.MakeStrongBoxType ().GetConstructor (new [] { type }));
                }
 
-               static object CreateStrongBox (object value, Type type)
+               void EmitHoistedLocalsStore ()
                {
-                       return Activator.CreateInstance (
-                               type.MakeStrongBoxType (), value);
+                       EmitScope ();
+                       hoisted_store = ig.DeclareLocal (typeof (object []));
+                       ig.Emit (OpCodes.Callvirt, typeof (ExecutionScope).GetMethod ("CreateHoistedLocals"));
+                       ig.Emit (OpCodes.Stloc, hoisted_store);
                }
-       }
-
-       class DynamicEmitContext : EmitContext {
-
-               DynamicMethod method;
-               List<object> globals;
 
-               static object mlock = new object ();
-               static int method_count;
-
-               public DynamicMethod Method {
-                       get { return method; }
+               public void EmitLoadLocals ()
+               {
+                       ig.Emit (OpCodes.Ldfld, typeof (ExecutionScope).GetField ("Locals"));
                }
 
-               public DynamicEmitContext (LambdaExpression lambda)
-                       : base (lambda)
+               public void EmitParentScope ()
                {
-                       // FIXME: Need to force this to be verifiable, see:
-                       // https://bugzilla.novell.com/show_bug.cgi?id=355005
-                       method = new DynamicMethod (GenerateName (), return_type, param_types, typeof (ExecutionScope), true);
-                       ig = method.GetILGenerator ();
-
-                       var collector = new GlobalCollector (lambda);
-
-                       globals = collector.Globals;
-                       indexes = collector.Indexes;
-
-                       owner.Emit (this);
+                       ig.Emit (OpCodes.Ldfld, typeof (ExecutionScope).GetField ("Parent"));
                }
 
-               public override Delegate CreateDelegate ()
+               public void EmitIsolateExpression ()
                {
-                       return method.CreateDelegate (owner.Type, new ExecutionScope (globals.ToArray ()));
+                       ig.Emit (OpCodes.Callvirt, typeof (ExecutionScope).GetMethod ("IsolateExpression"));
                }
 
-               static string GenerateName ()
+               public int IndexOfHoistedLocal (ParameterExpression parameter)
                {
-                       lock (mlock) {
-                               return "lambda_method-" + (method_count++);
-                       }
-               }
-       }
+                       if (!HasHoistedLocals)
+                               return -1;
 
-#if !NET_2_1
-       class DebugEmitContext : DynamicEmitContext {
+                       return hoisted.IndexOf (parameter);
+               }
 
-               public DebugEmitContext (LambdaExpression lambda)
-                       : base (lambda)
+               public bool IsHoistedLocal (ParameterExpression parameter, ref int level, ref int position)
                {
-                       var name = Method.Name;
-                       var file_name = name + ".dll";
+                       if (parent == null)
+                               return false;
 
-                       var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly (
-                               new AssemblyName (name), AssemblyBuilderAccess.RunAndSave, Path.GetTempPath ());
+                       if (parent.hoisted != null) {
+                               position = parent.hoisted.IndexOf (parameter);
+                               if (position > -1)
+                                       return true;
+                       }
 
-                       var type = assembly.DefineDynamicModule (file_name, file_name).DefineType ("Linq", TypeAttributes.Public);
+                       level++;
 
-                       var method = type.DefineMethod (name, MethodAttributes.Public | MethodAttributes.Static, return_type, param_types);
-                       ig = method.GetILGenerator ();
+                       return parent.IsHoistedLocal (parameter, ref level, ref position);
+               }
 
-                       owner.Emit (this);
+               int AddChildContext (LambdaExpression lambda)
+               {
+                       return context.AddCompilationUnit (this, lambda);
+               }
 
-                       type.CreateType ();
-                       assembly.Save (file_name);
+               static object CreateStrongBox (object value, Type type)
+               {
+                       return Activator.CreateInstance (
+                               type.MakeStrongBoxType (), value);
                }
        }
-#endif
 }
+#endif