// Author:
// Miguel de Icaza (miguel@ximain.com)
//
-// (C) 2003 Ximian, Inc.
+// (C) 2003, 2004 Novell, Inc.
+//
+// TODO: Ideally, we should have the helper classes emited as a hierarchy to map
+// their nesting, and have the visibility set to private, instead of NestedAssembly
+//
+//
//
using System;
+using System.Text;
using System.Collections;
using System.Reflection;
using System.Reflection.Emit;
namespace Mono.CSharp {
public class AnonymousMethod : Expression {
+ // Used to generate unique method names.
+ static int anonymous_method_count;
+
// An array list of AnonymousMethodParameter or null
- Parameters parameters;
- Block block;
+ public Parameters Parameters;
+
+ //
+ // The block that makes up the body for the anonymous mehtod
+ //
+ public ToplevelBlock Block;
+
+ //
+ // The container block for this anonymous method.
+ //
+ public Block ContainingBlock;
+
+ //
+ // The implicit method we create
+ //
+ public Method method;
+
+ MethodInfo invoke_mb;
+
+ // The emit context for the anonymous method
+ public EmitContext aec;
+ public InternalParameters amp;
+ bool unreachable;
+
+ //
+ // The modifiers applied to the method, we aggregate them
+ //
+ int method_modifiers = Modifiers.PRIVATE;
- public AnonymousMethod (Parameters parameters, Block block, Location l)
+ //
+ // During the resolve stage of the anonymous method body,
+ // we discover the actual scope where we are hosted, or
+ // null to host the method in the same class
+ //
+ public ScopeInfo Scope;
+
+ //
+ // Points to our container anonymous method if its present
+ //
+ public AnonymousMethod ContainerAnonymousMethod;
+
+ public AnonymousMethod (Parameters parameters, ToplevelBlock container, ToplevelBlock block, Location l)
{
- this.parameters = parameters;
- this.block = block;
+ Parameters = parameters;
+ Block = block;
loc = l;
+
+ //
+ // The order is important: this setups the CaptureContext tree hierarchy.
+ //
+ container.SetHaveAnonymousMethods (l, this);
+ block.SetHaveAnonymousMethods (l, this);
}
public override Expression DoResolve (EmitContext ec)
//
eclass = ExprClass.Value;
-
+
//
// This hack means `The type is not accessible
// anywhere', we depend on special conversion
// rules.
//
- type = typeof (AnonymousMethod);
+ type = TypeManager.anonymous_method_type;
+
return this;
}
{
// nothing, as we only exist to not do anything.
}
+
+ //
+ // Creates the host for the anonymous method
+ //
+ bool CreateMethodHost (EmitContext ec, Type return_type)
+ {
+ //
+ // Crude hack follows: we replace the TypeBuilder during the
+ // definition to get the method hosted in the right class
+ //
+
+ TypeBuilder current_type = ec.TypeContainer.TypeBuilder;
+ TypeBuilder type_host = (Scope == null ) // || Scope.ScopeTypeBuilder == null)
+ ? current_type : Scope.ScopeTypeBuilder;
+
+ if (current_type == null)
+ throw new Exception ("The current_type is null");
+
+ if (type_host == null)
+ throw new Exception ("Type host is null");
+
+ if (current_type == type_host && ec.IsStatic){
+ if (ec.IsStatic){
+ method_modifiers |= Modifiers.STATIC;
+ }
+ current_type = null;
+ }
+
+ method = new Method (
+ (TypeContainer) ec.TypeContainer,
+ new TypeExpression (return_type, loc),
+ method_modifiers, false, new MemberName ("<#AnonymousMethod>" + anonymous_method_count++),
+ Parameters, null, loc);
+ method.Block = Block;
+
+ //
+ // Swap the TypeBuilder while we define the method, then restore
+ //
+ if (current_type != null)
+ ec.TypeContainer.TypeBuilder = type_host;
+ bool res = method.Define ();
+ if (current_type != null)
+ ec.TypeContainer.TypeBuilder = current_type;
+ return res;
+ }
+
+ void Error_ParameterMismatch (Type t)
+ {
+ Report.Error (1661, loc, "Anonymous method could not be converted to delegate `" +
+ "{0}' since there is a parameter mismatch", t);
+ }
+
+ //
+ // Returns true if this anonymous method can be implicitly
+ // converted to the delegate type `delegate_type'
+ //
+ public Expression Compatible (EmitContext ec, Type delegate_type, bool probe)
+ {
+ //
+ // At this point its the first time we know the return type that is
+ // needed for the anonymous method. We create the method here.
+ //
+
+ invoke_mb = (MethodInfo) Delegate.GetInvokeMethod (ec, delegate_type, loc);
+ ParameterData invoke_pd = TypeManager.GetParameterData (invoke_mb);
+
+ if (Parameters == null){
+ int i, j;
+
+ //
+ // We provide a set of inaccessible parameters
+ //
+ int params_idx = -1;
+ for (i = 0; i < invoke_pd.Count; i++){
+ if (invoke_pd.ParameterModifier (i) == Parameter.Modifier.PARAMS)
+ params_idx = i;
+ }
+ int n = invoke_pd.Count - (params_idx != -1 ? 1 : 0);
+ Parameter [] fixedpars = new Parameter [n];
+
+ for (i = j = 0; i < invoke_pd.Count; i++){
+ if (invoke_pd.ParameterModifier (i) == Parameter.Modifier.PARAMS)
+ continue;
+ fixedpars [j] = new Parameter (
+ new TypeExpression (invoke_pd.ParameterType (i), loc),
+ "+" + j, invoke_pd.ParameterModifier (i), null);
+ j++;
+ }
+
+ Parameter variable = null;
+ if (params_idx != -1){
+ variable = new Parameter (
+ new TypeExpression (invoke_pd.ParameterType (params_idx), loc),
+ "+" + params_idx, invoke_pd.ParameterModifier (params_idx), null);
+ }
+
+ Parameters = new Parameters (fixedpars, variable, loc);
+ }
+
+ //
+ // First, parameter types of `delegate_type' must be compatible
+ // with the anonymous method.
+ //
+ amp = new InternalParameters (Parameters.GetParameterInfo (ec), Parameters);
+
+ if (amp.Count != invoke_pd.Count){
+ if (!probe){
+ Report.Error (1593, loc,
+ "Anonymous method has {0} parameters, while delegate requires {1}",
+ amp.Count, invoke_pd.Count);
+ Error_ParameterMismatch (delegate_type);
+ }
+ return null;
+ }
+
+ for (int i = 0; i < amp.Count; i++){
+ Parameter.Modifier amp_mod = amp.ParameterModifier (i);
+
+ if ((amp_mod & (Parameter.Modifier.OUT | Parameter.Modifier.REF)) != 0){
+ if (!probe){
+ Error_ParameterMismatch (delegate_type);
+ Report.Error (1677, loc, "Parameter '{0}' should not be declared with the '{1}' keyword",
+ i+1, amp.ModifierDesc (i));
+ }
+ return null;
+ }
+
+ if (amp_mod != invoke_pd.ParameterModifier (i)){
+ if (!probe){
+ Report.Error (1676, loc,
+ "Signature mismatch in parameter modifier for parameter #0", i + 1);
+ Error_ParameterMismatch (delegate_type);
+ }
+ return null;
+ }
+
+ if (amp.ParameterType (i) != invoke_pd.ParameterType (i)){
+ if (!probe){
+ Report.Error (1678, loc,
+ "Signature mismatch in parameter {0}: need `{1}' got `{2}'", i + 1,
+ TypeManager.CSharpName (invoke_pd.ParameterType (i)),
+ TypeManager.CSharpName (amp.ParameterType (i)));
+ Error_ParameterMismatch (delegate_type);
+ }
+ return null;
+ }
+ }
+
+ //
+ // If we are only probing, return ourselves
+ //
+ if (probe)
+ return this;
+
+ //
+ // Second: the return type of the delegate must be compatible with
+ // the anonymous type. Instead of doing a pass to examine the block
+ // we satisfy the rule by setting the return type on the EmitContext
+ // to be the delegate type return type.
+ //
+
+ //MethodBuilder builder = method_data.MethodBuilder;
+ //ILGenerator ig = builder.GetILGenerator ();
+
+
+ aec = new EmitContext (
+ ec.TypeContainer, ec.DeclSpace, loc, null,
+ invoke_mb.ReturnType,
+ /* REVIEW */ (ec.InIterator ? Modifiers.METHOD_YIELDS : 0) |
+ (ec.InUnsafe ? Modifiers.UNSAFE : 0) |
+ (ec.IsStatic ? Modifiers.STATIC : 0),
+ /* No constructor */ false);
+
+ aec.CurrentAnonymousMethod = this;
+ ContainerAnonymousMethod = ec.CurrentAnonymousMethod;
+ ContainingBlock = ec.CurrentBlock;
+
+ if (aec.ResolveTopBlock (ec, Block, amp, loc, out unreachable))
+ return new AnonymousDelegate (this, delegate_type, loc).Resolve (ec);
+
+ return null;
+ }
+
+ public MethodBuilder GetMethodBuilder ()
+ {
+ return method.MethodData.MethodBuilder;
+ }
+
+ public bool EmitMethod (EmitContext ec)
+ {
+ if (!CreateMethodHost (ec, invoke_mb.ReturnType))
+ return false;
+
+ MethodBuilder builder = GetMethodBuilder ();
+ ILGenerator ig = builder.GetILGenerator ();
+ aec.ig = ig;
+
+ Parameters.LabelParameters (aec, builder, loc);
+
+ //
+ // Adjust based on the computed state of the
+ // method from CreateMethodHost
+
+ aec.MethodIsStatic = (method_modifiers & Modifiers.STATIC) != 0;
+
+ aec.EmitMeta (Block, amp);
+ aec.EmitResolvedTopBlock (Block, unreachable);
+ return true;
+ }
+
+ public static void Error_AddressOfCapturedVar (string name, Location loc)
+ {
+ Report.Error (1686, loc,
+ "Variable {0} is captured in an anonymous method and its address is also being taken: they are exclusive", name);
+ }
+ }
+
+ //
+ // This will emit the code for the delegate, as well delegate creation on the host
+ //
+ public class AnonymousDelegate : DelegateCreation {
+ AnonymousMethod am;
+
+ public AnonymousDelegate (AnonymousMethod am, Type target_type, Location l)
+ {
+ type = target_type;
+ loc = l;
+ this.am = am;
+ }
+
+ public override Expression DoResolve (EmitContext ec)
+ {
+ eclass = ExprClass.Value;
+
+ return this;
+ }
+
+ public override void Emit (EmitContext ec)
+ {
+ if (!am.EmitMethod (ec))
+ return;
+
+ //
+ // Now emit the delegate creation.
+ //
+ if ((am.method.ModFlags & Modifiers.STATIC) == 0)
+ delegate_instance_expression = new AnonymousInstance (am);
+
+ Expression ml = Expression.MemberLookup (ec, type, ".ctor", loc);
+ constructor_method = ((MethodGroupExpr) ml).Methods [0];
+ delegate_method = am.GetMethodBuilder ();
+ base.Emit (ec);
+ }
+
+ class AnonymousInstance : Expression {
+ AnonymousMethod am;
+
+ public AnonymousInstance (AnonymousMethod am)
+ {
+ this.am = am;
+ eclass = ExprClass.Value;
+ }
+
+ public override Expression DoResolve (EmitContext ec)
+ {
+ return this;
+ }
+
+ public override void Emit (EmitContext ec)
+ {
+ am.aec.EmitMethodHostInstance (ec, am);
+ }
+ }
+ }
+
+ class CapturedParameter {
+ public Type Type;
+ public FieldBuilder FieldBuilder;
+ public int Idx;
+
+ public CapturedParameter (Type type, int idx)
+ {
+ Type = type;
+ Idx = idx;
+ }
+ }
+
+ //
+ // Here we cluster all the variables captured on a given scope, we also
+ // keep some extra information that might be required on each scope.
+ //
+ public class ScopeInfo {
+ public CaptureContext CaptureContext;
+ public ScopeInfo ParentScope;
+ public Block ScopeBlock;
+ public bool NeedThis = false;
+ public bool HostsParameters = false;
+
+ // For tracking the number of scopes created.
+ public int id;
+ static int count;
+ bool inited = false;
+
+ ArrayList locals = new ArrayList ();
+ ArrayList children = new ArrayList ();
+
+ //
+ // The types and fields generated
+ //
+ public TypeBuilder ScopeTypeBuilder;
+ public ConstructorBuilder ScopeConstructor;
+ public FieldBuilder THIS;
+ public FieldBuilder ParentLink;
+
+ //
+ // Points to the object of type `ScopeTypeBuilder' that
+ // holds the data for the scope
+ //
+ public LocalBuilder ScopeInstance;
+
+
+ public ScopeInfo (CaptureContext cc, Block b)
+ {
+ CaptureContext = cc;
+ ScopeBlock = b;
+ id = count++;
+
+ cc.AddScope (this);
+ }
+
+ public void AddLocal (LocalInfo li)
+ {
+ if (locals.Contains (li))
+ return;
+
+ locals.Add (li);
+ }
+
+ public bool IsCaptured (LocalInfo li)
+ {
+ return locals.Contains (li);
+ }
+
+ public void AddChild (ScopeInfo si)
+ {
+ if (children.Contains (si))
+ return;
+ children.Add (si);
+ }
+
+ static int indent = 0;
+
+ void Pad ()
+ {
+ for (int i = 0; i < indent; i++)
+ Console.Write (" ");
+ }
+
+ void EmitDebug ()
+ {
+ //Console.WriteLine (Environment.StackTrace);
+ Pad ();
+ Console.WriteLine ("START");
+ indent++;
+ Pad ();
+ Console.WriteLine ("NeedThis=" + NeedThis);
+ foreach (LocalInfo li in locals){
+ Pad ();
+ Console.WriteLine ("var {0}", li.Name);
+ }
+
+ foreach (ScopeInfo si in children)
+ si.EmitDebug ();
+ indent--;
+ Pad ();
+ Console.WriteLine ("END");
+ }
+
+ public string MakeHelperName ()
+ {
+ return String.Format ("<>AnonHelp<{0}>", id);
+ }
+
+ public void EmitScopeConstructor ()
+ {
+ Type [] constructor_types = TypeManager.NoTypes;
+ Parameters constructor_parameters = Parameters.EmptyReadOnlyParameters;
+ ScopeConstructor = ScopeTypeBuilder.DefineConstructor (
+ MethodAttributes.Public | MethodAttributes.HideBySig |
+ MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
+ CallingConventions.HasThis, constructor_types);
+ InternalParameters parameter_info = new InternalParameters (constructor_types, constructor_parameters);
+ TypeManager.RegisterMethod (ScopeConstructor, parameter_info, constructor_types);
+
+ ILGenerator cig = ScopeConstructor.GetILGenerator ();
+ cig.Emit (OpCodes.Ldarg_0);
+ cig.Emit (OpCodes.Call, TypeManager.object_ctor);
+ cig.Emit (OpCodes.Ret);
+ }
+
+ public void EmitScopeType (EmitContext ec)
+ {
+ //EmitDebug ();
+
+ if (ScopeTypeBuilder != null)
+ return;
+
+ TypeBuilder container = ec.TypeContainer.TypeBuilder;
+
+ ScopeTypeBuilder = container.DefineNestedType (
+ MakeHelperName (), TypeAttributes.AutoLayout | TypeAttributes.Class | TypeAttributes.NestedAssembly,
+ TypeManager.object_type, null);
+
+ if (NeedThis)
+ THIS = ScopeTypeBuilder.DefineField ("<>THIS", container, FieldAttributes.Assembly);
+
+ if (ParentScope != null){
+ if (ParentScope.ScopeTypeBuilder == null){
+ throw new Exception (String.Format ("My parent has not been initialized {0} and {1}", ParentScope, this));
+ }
+
+ ParentLink = ScopeTypeBuilder.DefineField ("<>parent", ParentScope.ScopeTypeBuilder,
+ FieldAttributes.Assembly);
+ }
+
+ if (NeedThis && ParentScope != null)
+ throw new Exception ("I was not expecting THIS && having a parent");
+
+ foreach (LocalInfo info in locals){
+ info.FieldBuilder = ScopeTypeBuilder.DefineField (
+ info.Name, info.VariableType, FieldAttributes.Assembly);
+ }
+
+ if (HostsParameters){
+ Hashtable captured_parameters = CaptureContext.captured_parameters;
+
+ foreach (DictionaryEntry de in captured_parameters){
+ string name = (string) de.Key;
+ CapturedParameter cp = (CapturedParameter) de.Value;
+ FieldBuilder fb;
+
+ fb = ScopeTypeBuilder.DefineField ("<p:" + name + ">", cp.Type, FieldAttributes.Assembly);
+ cp.FieldBuilder = fb;
+ }
+ }
+
+ EmitScopeConstructor ();
+ foreach (ScopeInfo si in children){
+ si.EmitScopeType (ec);
+ }
+ }
+
+ public void CloseTypes ()
+ {
+ RootContext.RegisterCompilerGeneratedType (ScopeTypeBuilder);
+ foreach (ScopeInfo si in children)
+ si.CloseTypes ();
+ }
+
+ //
+ // Emits the initialization code for the scope
+ //
+ public void EmitInitScope (EmitContext ec)
+ {
+ ILGenerator ig = ec.ig;
+
+ if (inited)
+ return;
+
+ ig.Emit (OpCodes.Newobj, (ConstructorInfo) ScopeConstructor);
+ ScopeInstance = ig.DeclareLocal (ScopeTypeBuilder);
+ ig.Emit (OpCodes.Stloc, ScopeInstance);
+
+ if (THIS != null){
+ ig.Emit (OpCodes.Ldloc, ScopeInstance);
+ ig.Emit (OpCodes.Ldarg_0);
+ ig.Emit (OpCodes.Stfld, THIS);
+ }
+
+ //
+ // Copy the parameter values, if any
+ //
+ int extra = ec.IsStatic ? 0 : 1;
+ if (HostsParameters){
+ Hashtable captured_parameters = CaptureContext.captured_parameters;
+
+ foreach (DictionaryEntry de in captured_parameters){
+ CapturedParameter cp = (CapturedParameter) de.Value;
+
+ ig.Emit (OpCodes.Ldloc, ScopeInstance);
+ ParameterReference.EmitLdArg (ig, cp.Idx + extra);
+ ig.Emit (OpCodes.Stfld, cp.FieldBuilder);
+ }
+ }
+
+ if (ParentScope != null){
+ if (!ParentScope.inited)
+ ParentScope.EmitInitScope (ec);
+
+ //
+ // Only emit initialization in our capturecontext world
+ //
+ if (ParentScope.CaptureContext == CaptureContext){
+ ig.Emit (OpCodes.Ldloc, ScopeInstance);
+ ig.Emit (OpCodes.Ldloc, ParentScope.ScopeInstance);
+ ig.Emit (OpCodes.Stfld, ParentLink);
+ } else {
+ ig.Emit (OpCodes.Ldloc, ScopeInstance);
+ ig.Emit (OpCodes.Ldarg_0);
+ ig.Emit (OpCodes.Stfld, ParentLink);
+ }
+ }
+ inited = true;
+ }
+
+ static void DoPath (StringBuilder sb, ScopeInfo start)
+ {
+ if (start.ParentScope != null){
+ DoPath (sb, start.ParentScope);
+ sb.Append (", ");
+ }
+ sb.Append ((start.id).ToString ());
+ }
+
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder ();
+
+ sb.Append ("{");
+ if (CaptureContext != null){
+ sb.Append (CaptureContext.ToString ());
+ sb.Append (":");
+ }
+
+ DoPath (sb, this);
+ sb.Append ("}");
+
+ return sb.ToString ();
+ }
+ }
+
+ //
+ // CaptureContext objects are created on demand if a method has
+ // anonymous methods and kept on the ToplevelBlock.
+ //
+ // If they exist, all ToplevelBlocks in the containing block are
+ // linked together (children pointing to their parents).
+ //
+ public class CaptureContext {
+ public static int count;
+ public int cc_id;
+ public Location loc;
+
+ //
+ // Points to the toplevel block that owns this CaptureContext
+ //
+ ToplevelBlock toplevel_owner;
+ Hashtable scopes = new Hashtable ();
+ bool have_captured_vars = false;
+ bool referenced_this = false;
+ ScopeInfo topmost = null;
+
+ //
+ // Captured fields
+ //
+ Hashtable captured_fields = new Hashtable ();
+ Hashtable captured_variables = new Hashtable ();
+ public Hashtable captured_parameters = new Hashtable ();
+ public AnonymousMethod Host;
+
+ public CaptureContext (ToplevelBlock toplevel_owner, Location loc, AnonymousMethod host)
+ {
+ cc_id = count++;
+ this.toplevel_owner = toplevel_owner;
+ this.loc = loc;
+
+ if (host != null)
+ Host = host;
+ }
+
+ void DoPath (StringBuilder sb, CaptureContext cc)
+ {
+ if (cc.ParentCaptureContext != null){
+ DoPath (sb, cc.ParentCaptureContext);
+ sb.Append (".");
+ }
+ sb.Append (cc_id.ToString ());
+ }
+
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder ();
+ sb.Append ("[");
+ DoPath (sb, this);
+ sb.Append ("]");
+ return sb.ToString ();
+ }
+
+ public ToplevelBlock ParentToplevel {
+ get {
+ return toplevel_owner.Container;
+ }
+ }
+
+ public CaptureContext ParentCaptureContext {
+ get {
+ ToplevelBlock parent = ParentToplevel;
+
+ return (parent == null) ? null : parent.CaptureContext;
+ }
+ }
+
+ // Returns the deepest of two scopes
+ public ScopeInfo Deepest (ScopeInfo a, ScopeInfo b)
+ {
+ ScopeInfo p;
+
+ if (a == null)
+ return b;
+ if (b == null)
+ return a;
+ if (a == b)
+ return a;
+
+ //
+ // If they Scopes are on the same CaptureContext, we do the double
+ // checks just so if there is an invariant change in the future,
+ // we get the exception at the end
+ //
+ for (p = a; p != null; p = p.ParentScope)
+ if (p == b)
+ return a;
+
+ for (p = b; p != null; p = p.ParentScope)
+ if (p == a)
+ return b;
+
+ CaptureContext ca = a.CaptureContext;
+ CaptureContext cb = b.CaptureContext;
+
+ for (CaptureContext c = ca; c != null; c = c.ParentCaptureContext)
+ if (c == cb)
+ return a;
+
+ for (CaptureContext c = cb; c != null; c = c.ParentCaptureContext)
+ if (c == ca)
+ return b;
+ throw new Exception ("Should never be reached");
+ }
+
+ void AdjustMethodScope (AnonymousMethod am, ScopeInfo scope)
+ {
+ am.Scope = Deepest (am.Scope, scope);
+ }
+
+ void LinkScope (ScopeInfo scope, int id)
+ {
+ ScopeInfo parent = (ScopeInfo) scopes [id];
+ scope.ParentScope = parent;
+ parent.AddChild (scope);
+
+ if (scope == topmost)
+ topmost = parent;
+ }
+
+ public void AddLocal (AnonymousMethod am, LocalInfo li)
+ {
+ if (li.Block.Toplevel != toplevel_owner){
+ ParentCaptureContext.AddLocal (am, li);
+ return;
+ }
+ int block_id = li.Block.ID;
+ ScopeInfo scope;
+ if (scopes [block_id] == null){
+ scope = new ScopeInfo (this, li.Block);
+ scopes [block_id] = scope;
+ } else
+ scope = (ScopeInfo) scopes [block_id];
+
+ if (topmost == null){
+ topmost = scope;
+ } else {
+ // Link to parent
+ for (Block b = scope.ScopeBlock.Parent; b != null; b = b.Parent){
+ if (scopes [b.ID] != null){
+ LinkScope (scope, b.ID);
+ break;
+ }
+ }
+
+ if (scope.ParentScope == null && ParentCaptureContext != null){
+ CaptureContext pcc = ParentCaptureContext;
+
+ for (Block b = am.ContainingBlock; b != null; b = b.Parent){
+ if (pcc.scopes [b.ID] != null){
+ pcc.LinkScope (scope, b.ID);
+ break;
+ }
+ }
+ }
+ }
+
+ //
+ // Adjust the owner
+ //
+ if (Host != null)
+ AdjustMethodScope (Host, topmost);
+
+ //
+ // Adjust the user
+ //
+ AdjustMethodScope (am, scope);
+
+ if (captured_variables [li] != null)
+ return;
+
+ have_captured_vars = true;
+ captured_variables [li] = li;
+ scope.AddLocal (li);
+ }
+
+ //
+ // Retursn the CaptureContext for the block that defines the parameter `name'
+ //
+ static CaptureContext _ContextForParameter (ToplevelBlock current, string name)
+ {
+ ToplevelBlock container = current.Container;
+ if (container != null){
+ CaptureContext cc = _ContextForParameter (container, name);
+ if (cc != null)
+ return cc;
+ }
+ if (current.IsParameterReference (name))
+ return current.ToplevelBlockCaptureContext;
+ return null;
+ }
+
+ static CaptureContext ContextForParameter (ToplevelBlock current, string name)
+ {
+ CaptureContext cc = _ContextForParameter (current, name);
+ if (cc == null)
+ throw new Exception (String.Format ("request for parameteter {0} failed: not found", name));
+ return cc;
+ }
+
+ //
+ // Records the captured parameter at the appropriate CaptureContext
+ //
+ public void AddParameter (EmitContext ec, AnonymousMethod am, string name, Type t, int idx)
+ {
+ CaptureContext cc = ContextForParameter (ec.CurrentBlock.Toplevel, name);
+
+ cc.AddParameterToContext (am, name, t, idx);
+ }
+
+ //
+ // Records the parameters in the context
+ //
+ void AddParameterToContext (AnonymousMethod am, string name, Type t, int idx)
+ {
+ if (captured_parameters == null)
+ captured_parameters = new Hashtable ();
+ if (captured_parameters [name] != null)
+ return;
+ captured_parameters [name] = new CapturedParameter (t, idx);
+
+ if (topmost == null){
+ //
+ // Create one ScopeInfo, if there are none.
+ //
+ topmost = new ScopeInfo (this, toplevel_owner);
+ scopes [toplevel_owner.ID] = topmost;
+ } else {
+ //
+ // If the topmost ScopeInfo is not at the topblock level, insert
+ // a new ScopeInfo there.
+ //
+ if (topmost.ScopeBlock != toplevel_owner){
+ ScopeInfo par_si = new ScopeInfo (this, toplevel_owner);
+ scopes [toplevel_owner.ID] = topmost;
+ topmost.ParentScope = par_si;
+ topmost = par_si;
+ }
+ }
+
+ topmost.HostsParameters = true;
+ AdjustMethodScope (am, topmost);
+ }
+
+ //
+ // Captured fields are only recorded on the topmost CaptureContext, because that
+ // one is the one linked to the owner of instance fields
+ //
+ public void AddField (FieldExpr fe)
+ {
+ if (fe.FieldInfo.IsStatic)
+ throw new Exception ("Attempt to register a static field as a captured field");
+
+ CaptureContext parent = ParentCaptureContext;
+ if (parent != null)
+ parent.AddField (fe);
+ else
+ captured_fields [fe] = fe;
+ }
+
+ public void CaptureThis ()
+ {
+ CaptureContext parent = ParentCaptureContext;
+ if (parent != null)
+ parent.CaptureThis ();
+ referenced_this = true;
+ }
+
+ public bool HaveCapturedVariables {
+ get {
+ return have_captured_vars;
+ }
+ }
+
+ public bool HaveCapturedFields {
+ get {
+ CaptureContext parent = ParentCaptureContext;
+ if (parent != null)
+ return parent.HaveCapturedFields;
+ return captured_fields.Count > 0;
+ }
+ }
+
+ public bool IsCaptured (LocalInfo local)
+ {
+ foreach (ScopeInfo si in scopes.Values){
+ if (si.IsCaptured (local))
+ return true;
+ }
+ return false;
+ }
+
+ //
+ // Returns whether the parameter is captured
+ //
+ public bool IsParameterCaptured (string name)
+ {
+ if (ParentCaptureContext != null && ParentCaptureContext.IsParameterCaptured (name))
+ return true;
+
+ if (captured_parameters != null)
+ return captured_parameters [name] != null;
+ return false;
+ }
+
+ public void EmitAnonymousHelperClasses (EmitContext ec)
+ {
+ if (topmost != null){
+ topmost.NeedThis = HaveCapturedFields || referenced_this;
+ topmost.EmitScopeType (ec);
+ }
+ }
+
+ public void CloseAnonymousHelperClasses ()
+ {
+ if (topmost != null)
+ topmost.CloseTypes ();
+ }
+
+ ScopeInfo GetScopeFromBlock (EmitContext ec, Block b)
+ {
+ ScopeInfo si;
+
+ si = (ScopeInfo) scopes [b.ID];
+ if (si == null)
+ throw new Exception ("Si is null for block " + b.ID);
+ si.EmitInitScope (ec);
+
+ return si;
+ }
+
+ //
+ // Emits the opcodes necessary to load the instance of the captured
+ // variable in `li'
+ //
+ public void EmitCapturedVariableInstance (EmitContext ec, LocalInfo li, AnonymousMethod am)
+ {
+ ILGenerator ig = ec.ig;
+ ScopeInfo si;
+
+ if (li.Block.Toplevel == toplevel_owner){
+ si = GetScopeFromBlock (ec, li.Block);
+ ig.Emit (OpCodes.Ldloc, si.ScopeInstance);
+ return;
+ }
+
+ si = am.Scope;
+ ig.Emit (OpCodes.Ldarg_0);
+ if (si != null){
+ while (si.ScopeBlock.ID != li.Block.ID){
+ if (si.ParentLink != null)
+ ig.Emit (OpCodes.Ldfld, si.ParentLink);
+ si = si.ParentScope;
+ if (si == null)
+ throw new Exception (
+ String.Format ("Never found block {0} starting at {1} while looking up {2}",
+ li.Block.ID, am.Scope.ScopeBlock.ID, li.Name));
+ }
+ }
+ }
+
+ //
+ // Internal routine that loads the instance to reach parameter `name'
+ //
+ void EmitParameterInstance (EmitContext ec, string name)
+ {
+ CaptureContext cc = ContextForParameter (ec.CurrentBlock.Toplevel, name);
+ if (cc != this){
+ cc.EmitParameterInstance (ec, name);
+ return;
+ }
+
+ CapturedParameter par_info = (CapturedParameter) captured_parameters [name];
+ if (par_info != null){
+ //
+ // FIXME: implementing this.
+ //
+ }
+ ILGenerator ig = ec.ig;
+
+ ScopeInfo si;
+ if (ec.CurrentBlock == toplevel_owner){
+ si = GetScopeFromBlock (ec, toplevel_owner);
+ ig.Emit (OpCodes.Ldloc, si.ScopeInstance);
+ return;
+ }
+
+ si = ec.CurrentAnonymousMethod.Scope;
+ ig.Emit (OpCodes.Ldarg_0);
+ if (si != null){
+ while (si.ParentLink != null) {
+ ig.Emit (OpCodes.Ldfld, si.ParentLink);
+ si = si.ParentScope;
+ }
+ }
+ }
+
+ //
+ // Emits the code necessary to load the parameter named `name' within
+ // an anonymous method.
+ //
+ public void EmitParameter (EmitContext ec, string name)
+ {
+ CaptureContext cc = ContextForParameter (ec.CurrentBlock.Toplevel, name);
+ if (cc != this){
+ cc.EmitParameter (ec, name);
+ return;
+ }
+ EmitParameterInstance (ec, name);
+ CapturedParameter par_info = (CapturedParameter) captured_parameters [name];
+ if (par_info != null){
+ //
+ // FIXME: implementing this.
+ //
+ }
+ ec.ig.Emit (OpCodes.Ldfld, par_info.FieldBuilder);
+ }
+
+ //
+ // Implements the assignment of `source' to the paramenter named `name' within
+ // an anonymous method.
+ //
+ public void EmitAssignParameter (EmitContext ec, string name, Expression source, bool leave_copy, bool prepare_for_load)
+ {
+ CaptureContext cc = ContextForParameter (ec.CurrentBlock.Toplevel, name);
+ if (cc != this){
+ cc.EmitAssignParameter (ec, name, source, leave_copy, prepare_for_load);
+ return;
+ }
+ ILGenerator ig = ec.ig;
+ CapturedParameter par_info = (CapturedParameter) captured_parameters [name];
+
+ EmitParameterInstance (ec, name);
+ source.Emit (ec);
+ if (leave_copy)
+ ig.Emit (OpCodes.Dup);
+ ig.Emit (OpCodes.Stfld, par_info.FieldBuilder);
+ }
+
+ //
+ // Emits the address for the parameter named `name' within
+ // an anonymous method.
+ //
+ public void EmitAddressOfParameter (EmitContext ec, string name)
+ {
+ CaptureContext cc = ContextForParameter (ec.CurrentBlock.Toplevel, name);
+ if (cc != this){
+ cc.EmitAddressOfParameter (ec, name);
+ return;
+ }
+ EmitParameterInstance (ec, name);
+ CapturedParameter par_info = (CapturedParameter) captured_parameters [name];
+ ec.ig.Emit (OpCodes.Ldflda, par_info.FieldBuilder);
+ }
+
+ //
+ // The following methods are only invoked on the host for the
+ // anonymous method.
+ //
+ public void EmitMethodHostInstance (EmitContext target, AnonymousMethod am)
+ {
+ ILGenerator ig = target.ig;
+ ScopeInfo si = am.Scope;
+
+ if (si == null){
+ ig.Emit (OpCodes.Ldarg_0);
+ return;
+ }
+
+ si.EmitInitScope (target);
+ ig.Emit (OpCodes.Ldloc, si.ScopeInstance);
+ }
+
+ ArrayList all_scopes = new ArrayList ();
+
+ public void AddScope (ScopeInfo si)
+ {
+ all_scopes.Add (si);
+ toplevel_owner.RegisterCaptureContext (this);
+ }
+
+ //
+ // Links any scopes that were not linked previously
+ //
+ public void AdjustScopes ()
+ {
+ foreach (ScopeInfo scope in all_scopes){
+ if (scope.ParentScope != null)
+ continue;
+
+ for (Block b = scope.ScopeBlock.Parent; b != null; b = b.Parent){
+ if (scopes [b.ID] != null){
+ LinkScope (scope, b.ID);
+ break;
+ }
+ }
+
+ if (scope.ParentScope == null && ParentCaptureContext != null){
+ CaptureContext pcc = ParentCaptureContext;
+
+ for (Block b = Host.ContainingBlock; b != null; b = b.Parent){
+ if (pcc.scopes [b.ID] != null){
+ pcc.LinkScope (scope, b.ID);
+ break;
+ }
+ }
+ }
+ }
+ }
}
}
-