Ejemplo n.º 1
0
        protected virtual void RewriteOriginalMethodBody(TypeDefinition coroutineType, MethodDefinition coroutineCtor)
        {
            // If this is a ctor, we need to preserve the base ctor call
            // FIXME: Will this always come first? In C# yes, but other languages... maybe not.
            if (method.IsConstructor) {
                int i = -1;
                int removeIndex = -1;
                while (method.Body.Instructions.Count > removeIndex) {
                    i++;
                    if (removeIndex == -1 && method.Body.Instructions [i].OpCode.Code == Code.Call) {
                        removeIndex = i + 1;
                        continue;
                    }
                    if (removeIndex != -1)
                        method.Body.Instructions.RemoveAt (removeIndex);
                }
            } else {
                method.Body.Instructions.Clear ();
            }
            method.Body.Variables.Clear ();

            //if (debug)
            //	method.CustomAttributes.Add (new CustomAttribute (module.Import (debuggerHidden)));

            var il = method.Body.GetILProcessor ();

            var arg = 0;
            if (method.HasThis) {
                arg = 1;
                il.Emit (OpCodes.Ldarg_0);
            }

            for (var i = 0; i < method.Parameters.Count; i++)
                il.Emit (OpCodes.Ldarg, arg++);

            if (method.HasGenericParameters || method.DeclaringType.HasGenericParameters) {

                var genericCtor = coroutineCtor.MakeGeneric (module.Import (coroutineType.MakeGeneric (method.DeclaringType.GenericParameters.Concat (method.GenericParameters).ToArray ())));
                il.Emit (OpCodes.Newobj, module.Import (genericCtor));

            } else {
                il.Emit (OpCodes.Newobj, coroutineCtor);
            }

            if (method.ReturnType.IsVoid ())
                il.Emit (OpCodes.Pop);
            il.Emit (OpCodes.Ret);

            method.Body.ComputeOffsetsAndMaxStack ();
        }
Ejemplo n.º 2
0
        private MethodDefinition InterceptMethod(PropertyDefinition property, MethodDefinition renamedMethod,
            MethodInterceptionScopeType interceptionScope)
        {
            var interceptorMethod =
                Context
                    .Cloning
                    .Clone(renamedMethod);

            renamedMethod.Name =
                Context
                    .NameAlias
                    .GenerateIdentityName(renamedMethod.Name);

            // Insert interceptor code

            // Interceptor: Insert variables
            var v0 = new VariableDefinition("V_0", Context.Importer.Import(typeof (System.Type)));
            interceptorMethod.Body.Variables.Add(v0);
            var v1 = new VariableDefinition("V_1", Context.Importer.Import(typeof (MethodInfo)));
            interceptorMethod.Body.Variables.Add(v1);
            var v2 = new VariableDefinition("V_2", Context.Importer.Import(typeof (Invocation)));
            interceptorMethod.Body.Variables.Add(v2);
            var v3 = new VariableDefinition("V_3", Context.Importer.Import(typeof (Object[])));
            interceptorMethod.Body.Variables.Add(v3);
            var v4 = new VariableDefinition("V_4", Context.Importer.Import(typeof (Boolean)));
            interceptorMethod.Body.Variables.Add(v4);
            var v5 = new VariableDefinition("V_5", Context.Importer.Import(typeof (PropertyInfo)));
            interceptorMethod.Body.Variables.Add(v5);

            // Interceptor: If has return type add to local variables
            if (renamedMethod.ReturnType.Name != "Void")
            {
                interceptorMethod.ReturnType = renamedMethod.ReturnType;
                var v6 = new VariableDefinition("V_6", interceptorMethod.ReturnType);
                interceptorMethod.Body.Variables.Add(v6);
            }

            // Interceptor: Init locals?
            interceptorMethod.Body.InitLocals = renamedMethod.Body.InitLocals;

            // Interceptor: Method return instruction
            var endOfMethodInstruction = interceptorMethod.Body.GetILProcessor().Create(OpCodes.Nop);

            // Interceptor: Get IL Processor
            var il = interceptorMethod.Body.GetILProcessor();

            // Interceptor: Insert interceptor marker
            Context.Marker.CreateMarker(interceptorMethod, MethodMarker);

            // Interceptor: Resolve type from handle uses V_0
            il.Append(new[]
                          {
                              il.Create(OpCodes.Nop),
                              il.Create(OpCodes.Ldtoken, Context.Type.Definition),
                              il.Create(OpCodes.Call, Context.Importer.Import(typeof (System.Type), "GetTypeFromHandle"))
                              ,
                              il.Create(OpCodes.Stloc_0)
                          });

            // Interceptor: Get the method info
            var methodReference = Context.Importer.Import(typeof (System.Type), "GetMethod, String, BindingFlags");
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_0),
                              il.Create(OpCodes.Ldstr, interceptorMethod.Name),
                              // BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance
                              il.Create(OpCodes.Ldc_I4_S, (sbyte) 60),
                              il.Create(OpCodes.Callvirt, methodReference),
                              il.Create(OpCodes.Stloc_1)
                          });

            // Interceptor: Get the method info
            var propertyReference = Context.Importer.Import(typeof (System.Type), "GetProperty, String, BindingFlags");
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_0),
                              il.Create(OpCodes.Ldstr, property.Name),
                              // BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance
                              il.Create(OpCodes.Ldc_I4_S, (sbyte) 60),
                              il.Create(OpCodes.Callvirt, propertyReference),
                              il.Create(OpCodes.Stloc, 5)
                          });

            // Interceptor: Initialise object array for param values
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldc_I4, interceptorMethod.Parameters.Count),
                              il.Create(OpCodes.Newarr, Context.Importer.Import(typeof (Object))),
                              il.Create(OpCodes.Stloc_3)
                          });

            foreach (var parameter in interceptorMethod.Parameters)
            {
                var argIndex = interceptorMethod.Parameters.IndexOf(parameter);

                // Interceptor: Load the argument for array
                il.Append(new[]
                              {
                                  il.Create(OpCodes.Ldloc_3),
                                  il.Create(OpCodes.Ldc_I4, argIndex),
                                  il.Create(OpCodes.Ldarg, parameter),
                              });

                // Interceptor: Box up value types
                if (parameter.ParameterType.IsValueType || parameter.ParameterType.IsGenericParameter)
                    il.Append(il.Create(OpCodes.Box, parameter.ParameterType));

                // Intreceptor: Allocate to array
                il.Append(il.Create(OpCodes.Stelem_Ref));
            }

            // Inteceptor: Initialise Method Invocation
            var methodInvocationTypRef = Context.Importer.Import(typeof (Invocation));

            var methodInvocationConstructors =
                methodInvocationTypRef
                    .Resolve()
                    .Methods
                    .Where(m => m.IsConstructor)
                    .ToList();

            // Interceptor: Get instance or static based constructor
            MethodDefinition methodInvocationConstructor;
            if (renamedMethod.IsStatic)
            {
                // If static
                methodInvocationConstructor =
                    methodInvocationConstructors
                        .Where(c =>
                               c.Parameters[0].ParameterType.Name.ToLower().IndexOf("type") != -1
                               && c.Parameters[1].ParameterType.Name.ToLower().IndexOf("propertyinfo") != -1
                               && c.Parameters[2].ParameterType.Name.ToLower().IndexOf("methodinfo") != -1)
                        .First();
            }
            else
            {
                // If instance
                methodInvocationConstructor =
                    methodInvocationConstructors
                        .Where(c =>
                               c.Parameters[0].ParameterType.Name.ToLower().IndexOf("object") != -1
                               && c.Parameters[1].ParameterType.Name.ToLower().IndexOf("type") != -1
                               && c.Parameters[2].ParameterType.Name.ToLower().IndexOf("propertyinfo") != -1
                               && c.Parameters[3].ParameterType.Name.ToLower().IndexOf("methodinfo") != -1)
                        .First();

                // Load 'this'
                il.Append(il.Create(OpCodes.Ldarg_0));
            }

            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_0),
                              il.Create(OpCodes.Ldloc, 5),
                              il.Create(OpCodes.Ldloc_1),
                              il.Create(OpCodes.Ldloc_3),
                              il.Create(OpCodes.Newobj, Context.Importer.Import(methodInvocationConstructor)),
                              il.Create(OpCodes.Stloc_2)
                          });

            // Interceptor: Call interceptor method
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_2),
                              il.Create(OpCodes.Call, Context.Importer.Import(typeof (Intercept), "HandleInvocation"))
                          });

            // Interceptor: If not void push result from interception
            if (renamedMethod.ReturnType.Name != "Void")
            {
                il.Append(new[]
                              {
                                  il.Create(OpCodes.Ldloc_2),
                                  il.Create(OpCodes.Callvirt, Context.Importer.Import(typeof (Invocation), "get_Result"))
                                  ,
                                  il.Create(OpCodes.Stloc, 6)
                              });
            }

            // Interceptor: Check if invocation has been cancelled
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_2),
                              il.Create(OpCodes.Callvirt, Context.Importer.Import(typeof (Invocation), "get_CanInvoke"))
                              ,
                              il.Create(OpCodes.Ldc_I4_0),
                              il.Create(OpCodes.Ceq),
                              il.Create(OpCodes.Stloc, 4),
                              il.Create(OpCodes.Ldloc, 4),
                              il.Create(OpCodes.Brtrue, endOfMethodInstruction)
                          });

            // Interceptor: Insert IL call from clone to renamed method
            if (!renamedMethod.IsStatic)
                il.Append(il.Create(OpCodes.Ldarg_0));

            //  Interceptor: Load args for method call
            foreach (var parameter in interceptorMethod.Parameters.ToList())
                il.Append(il.Create(OpCodes.Ldarg, parameter));

            if (renamedMethod.HasGenericParameters)
                il.Append(il.Create(OpCodes.Call,
                                    renamedMethod.MakeGeneric(interceptorMethod.GenericParameters.ToArray())));
            else
                il.Append(il.Create(OpCodes.Call, renamedMethod));

            // Interceptor: Store method return value
            if (interceptorMethod.ReturnType.Name != "Void")
                il.Append(il.Create(OpCodes.Stloc, 6));

            // Interceptor: Set return type on MethodInvocation
            if (interceptorMethod.ReturnType.Name != "Void")
            {
                if (renamedMethod.ReturnType.IsValueType)
                {
                    il.Append(new[]
                                  {
                                      il.Create(OpCodes.Ldloc_2),
                                      il.Create(OpCodes.Ldloc, 6),
                                      il.Create(OpCodes.Box, renamedMethod.ReturnType),
                                      il.Create(OpCodes.Callvirt,
                                                Context.Importer.Import(typeof (Invocation), "set_Result"))
                                  });
                }
                else
                {
                    il.Append(new[]
                                  {
                                      il.Create(OpCodes.Ldloc_2),
                                      il.Create(OpCodes.Ldloc, 6),
                                      il.Create(OpCodes.Callvirt,
                                                Context.Importer.Import(typeof (Invocation), "set_Result"))
                                  });
                }
            }

            // Interceptor: Continue the invocation by changing state
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_2),
                              il.Create(OpCodes.Call, Context.Importer.Import(typeof (Invocation), "ContinueInvocation"))
                          });

            // Interceptor: Do post invocation call
            il.Append(new[]
                          {
                              il.Create(OpCodes.Ldloc_2),
                              il.Create(OpCodes.Call, Context.Importer.Import(typeof (Intercept), "HandleInvocation"))
                          });

            // Interceptor: End of method, doing this in advance for branching ?CancelInvocation?.
            il.Append(endOfMethodInstruction);

            // Interceptor: Loading the result from the invocation
            if (interceptorMethod.ReturnType.Name != "Void")
            {
                if (renamedMethod.ReturnType.IsValueType)
                {
                    il.Append(new[]
                                  {
                                      il.Create(OpCodes.Ldloc_2),
                                      il.Create(OpCodes.Callvirt,
                                                Context.Importer.Import(typeof (Invocation), "get_Result")),
                                      il.Create(OpCodes.Unbox_Any, interceptorMethod.ReturnType),
                                  });
                }
                else
                {
                    il.Append(new[]
                                  {
                                      il.Create(OpCodes.Ldloc_2),
                                      il.Create(OpCodes.Callvirt,
                                                Context.Importer.Import(typeof (Invocation), "get_Result")),
                                  });
                }
            }

            // Interceptor: Return
            il.Append(il.Create(OpCodes.Ret));

            // If deep intercept, replace internals with call to renamed method
            Context.Scope.ModifyCallScope(renamedMethod, interceptorMethod, il, interceptionScope);

            return interceptorMethod;
        }
        private ClosureInfo ProcessClosure(AssemblyProcessorContext context, TypeDefinition closureType, TypeReference[] genericParameters)
        {
            ClosureInfo closure;
            if (closures.TryGetValue(closureType, out closure))
                return closure;

            var closureTypeConstructor = closureType.Methods.FirstOrDefault(x => x.Name == ".ctor");
            var closureGenericType = closureType.MakeGenericType(genericParameters);

            // Create factory method for pool items
            var factoryMethod = new MethodDefinition("<Factory>", MethodAttributes.HideBySig | MethodAttributes.Private | MethodAttributes.Static, closureGenericType);
            closureType.Methods.Add(factoryMethod);
            factoryMethod.Body.InitLocals = true;
            factoryMethod.Body.Variables.Add(new VariableDefinition(closureGenericType));
            var factoryMethodProcessor = factoryMethod.Body.GetILProcessor();
            // Create and store closure
            factoryMethodProcessor.Emit(OpCodes.Newobj, closureTypeConstructor.MakeGeneric(genericParameters));
            factoryMethodProcessor.Emit(OpCodes.Stloc_0);
            //// Return closure
            factoryMethodProcessor.Emit(OpCodes.Ldloc_0);
            factoryMethodProcessor.Emit(OpCodes.Ret);

            // Create pool field
            var poolField = new FieldDefinition("<pool>", FieldAttributes.Public | FieldAttributes.Static, poolType.MakeGenericType(closureGenericType));
            closureType.Fields.Add(poolField);
            var poolFieldReference = poolField.MakeGeneric(genericParameters);

            // Initialize pool
            var cctor = GetOrCreateClassConstructor(closureType);
            var ilProcessor2 = cctor.Body.GetILProcessor();
            var retInstruction = cctor.Body.Instructions.FirstOrDefault(x => x.OpCode == OpCodes.Ret);
            ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Ldnull));
            ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Ldftn, factoryMethod.MakeGeneric(genericParameters)));
            ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Newobj, funcConstructor.MakeGeneric(closureGenericType)));
            ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Newobj, poolConstructor.MakeGeneric(closureGenericType)));
            ilProcessor2.InsertBefore(retInstruction, ilProcessor2.Create(OpCodes.Stsfld, poolFieldReference));

            // Implement IPooledClosure
            closureType.Interfaces.Add(pooledClosureType);

            // Create reference count field
            var countField = new FieldDefinition("<referenceCount>", FieldAttributes.Public, context.Assembly.MainModule.TypeSystem.Int32);
            closureType.Fields.Add(countField);
            var oountFieldReference = countField.MakeGeneric(genericParameters);

            // Create AddReference method
            var addReferenceMethod = new MethodDefinition("AddReference", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot, context.Assembly.MainModule.TypeSystem.Void);
            var ilProcessor4 = addReferenceMethod.Body.GetILProcessor();
            ilProcessor4.Emit(OpCodes.Ldarg_0);
            ilProcessor4.Emit(OpCodes.Ldflda, oountFieldReference);
            ilProcessor4.Emit(OpCodes.Call, interlockedIncrementMethod);
            ilProcessor4.Emit(OpCodes.Pop);
            ilProcessor4.Emit(OpCodes.Ret);
            closureType.Methods.Add(addReferenceMethod);

            // Create Release method
            var releaseMethod = new MethodDefinition("Release", MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.NewSlot, context.Assembly.MainModule.TypeSystem.Void);
            ilProcessor4 = releaseMethod.Body.GetILProcessor();
            retInstruction = ilProcessor4.Create(OpCodes.Ret);
            // Check decremented reference count
            ilProcessor4.Emit(OpCodes.Ldarg_0);
            ilProcessor4.Emit(OpCodes.Ldflda, oountFieldReference);
            ilProcessor4.Emit(OpCodes.Call, interlockedDecrementMethod);
            ilProcessor4.Emit(OpCodes.Ldc_I4_0);
            ilProcessor4.Emit(OpCodes.Ceq);
            ilProcessor4.Emit(OpCodes.Brfalse_S, retInstruction);
            // Release this to pool
            ilProcessor4.Emit(OpCodes.Ldsfld, poolFieldReference);
            ilProcessor4.Emit(OpCodes.Ldarg_0);
            ilProcessor4.Emit(OpCodes.Callvirt, poolReleaseMethod.MakeGeneric(closureGenericType));
            ilProcessor4.Append(retInstruction);
            closureType.Methods.Add(releaseMethod);

            closures.Add(closureType, closure = new ClosureInfo
            {
                FactoryMethod = factoryMethod,
                AddReferenceMethod = addReferenceMethod,
                ReleaseMethod = releaseMethod,
                PoolField = poolField
            });

            return closure;
        }
Ejemplo n.º 4
0
        // FIXME: This method should be refactored into its own class it's so hefty!
        protected virtual void TransformAsyncMethod(MethodDefinition method)
        {
            if (method.Parameters.Any (p => p.IsOut))
                throw new Error (method.Module.Name,
                                 string.Format ("method `{0}' in type `{1}' cannot be transformed into an asynchronous coroutine becuase it has a ref or out parameter",
                                                method.Name, method.DeclaringType.Name));

            // Create Future implementation...
            var module = method.Module;
            var containingType = method.DeclaringType;

            var voidType   = module.Import (typeof (void));
            var intType    = module.Import (typeof (int));

            var isScheduled = module.Import (typeof (Future).GetProperty ("IsScheduled", BindingFlags.Public | BindingFlags.Instance).GetGetMethod ());
            var schedule    = module.Import (typeof (Future).GetMethod ("Schedule", new Type [] { typeof (Thread) }));

            var getException = module.Import (typeof (Future).GetProperty ("Exception").GetGetMethod ());
            var setException = module.Import (typeof (Future).GetProperty ("Exception").GetSetMethod ());

            var getStatus = module.Import (typeof (Future).GetProperty ("Status").GetGetMethod ());
            var setStatus = module.Import (typeof (Future).GetProperty ("Status").GetSetMethod ());

            TypeReference futureValueType = null;
            TypeReference baseType = null;

            MethodReference baseCtor = null;
            MethodReference chain = null;

            FieldDefinition [] argsFields;
            FieldDefinition [] localsFields;

            FieldReference threadFld = null;
            FieldReference chainedFld = null;
            FieldReference pcFld = null;

            VariableDefinition dupLoc = null;

            // If the method or containing type is generic, we have to account for that...

            var typeGeneric = new GenericParameter [containingType.GenericParameters.Count];
            var methodGeneric = new GenericParameter [method.GenericParameters.Count];

            var skews = new Dictionary<IGenericParameterProvider,int> (2);
            skews.Add (containingType, 0);
            skews.Add (method, typeGeneric.Length);

            int i = typeGeneric.Length + methodGeneric.Length;

            var futureName = string.Format ("__cirrus{0}_{1}_{2}_impl", asyncMethodID++, method.Name.Replace ("`", "$"),
                                            string.Join ("_", method.Parameters.Select (p => p.ParameterType.Name.Replace ("`", "$")).ToArray ()));
            if (i > 0)
                futureName += string.Format ("`{0}", i);

            var future = new TypeDefinition (null, futureName, Mono.Cecil.TypeAttributes.NestedPrivate | Mono.Cecil.TypeAttributes.Sealed);

            for (i = 0; i < typeGeneric.Length; i++) {
                typeGeneric [i] = new GenericParameter (containingType.GenericParameters [i].Name, future);
                future.GenericParameters.Add (typeGeneric [i]);
            }
            for (i = 0; i < methodGeneric.Length; i++) {
                methodGeneric [i] = new GenericParameter (method.GenericParameters [i].Name, future);
                future.GenericParameters.Add (methodGeneric [i]);
            }

            var returnType = method.ReturnType.CopyGeneric (future, skews);
            if (returnType.IsGenericInstance) { // returns Future<T>
                futureValueType = ((GenericInstanceType)returnType).GenericArguments [0].CopyGeneric (future, skews);
                baseType = module.Import (typeof (Cirrus.CoroutineFuture<>)).MakeGeneric (futureValueType);
                future.BaseType = baseType;
                baseCtor = module.Import (typeof (Cirrus.CoroutineFuture<>).GetConstructor (new Type [] {}), baseType);
                baseCtor.DeclaringType = baseType;

                threadFld = module.Import (typeof (Cirrus.CoroutineFuture<>).GetField ("thread", BindingFlags.Instance | BindingFlags.NonPublic), baseType);
                threadFld.DeclaringType = baseType;
                chainedFld = module.Import (typeof (Cirrus.CoroutineFuture<>).GetField ("chained", BindingFlags.Instance | BindingFlags.NonPublic), baseType);
                chainedFld.DeclaringType = baseType;
                pcFld = module.Import (typeof (Cirrus.CoroutineFuture<>).GetField ("pc", BindingFlags.Instance | BindingFlags.NonPublic), baseType);
                pcFld.DeclaringType = baseType;

                chain = module.Import (typeof (Cirrus.CoroutineFuture<>).GetMethod ("Chain", BindingFlags.Instance | BindingFlags.NonPublic), baseType);
                chain.DeclaringType = baseType;

            } else { // returns Future or void...
                baseType = module.Import (typeof (Cirrus.CoroutineFuture));
                future.BaseType = baseType;
                baseCtor = module.Import (typeof (Cirrus.CoroutineFuture).GetConstructor (new Type [] {}));

                threadFld = module.Import (typeof (Cirrus.CoroutineFuture).GetField ("thread", BindingFlags.Instance | BindingFlags.NonPublic));
                chainedFld = module.Import (typeof (Cirrus.CoroutineFuture).GetField ("chained", BindingFlags.Instance | BindingFlags.NonPublic));
                pcFld = module.Import (typeof (Cirrus.CoroutineFuture).GetField ("pc", BindingFlags.Instance | BindingFlags.NonPublic));

                chain = module.Import (typeof (Cirrus.CoroutineFuture).GetMethod ("Chain", BindingFlags.Instance | BindingFlags.NonPublic));

            }
            containingType.NestedTypes.Add (future);

            // create ctor
            var ctor = new MethodDefinition (".ctor", Mono.Cecil.MethodAttributes.SpecialName | Mono.Cecil.MethodAttributes.RTSpecialName |
                                               Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Public, voidType);
            var ctorIL = ctor.Body.GetILProcessor ();
            future.Methods.Add (ctor);

            // first, call base ctor
            ctorIL.Emit (OpCodes.Ldarg_0);
            ctorIL.Emit (OpCodes.Call, baseCtor);

            // add "this"
            int argStart = 0;
            if (method.HasThis) {
                var thisType = containingType.MakeGeneric (typeGeneric);
                argsFields = new FieldDefinition [method.Parameters.Count+1];
                argsFields [0] = new FieldDefinition ("$this", Mono.Cecil.FieldAttributes.Private | Mono.Cecil.FieldAttributes.InitOnly, thisType);
                future.Fields.Add (argsFields [0]);
                ctor.Parameters.Add (new ParameterDefinition (thisType));

                // this.$this = <Arg1>
                ctorIL.Emit (OpCodes.Ldarg_0);
                ctorIL.Emit (OpCodes.Ldarg_1);
                ctorIL.Emit (OpCodes.Stfld, argsFields [0]);

                argStart = 1;
            } else {
                argsFields = new FieldDefinition [method.Parameters.Count];
            }

            // load all args
            i = argStart;
            foreach (var arg in method.Parameters) {
                var paramType = arg.ParameterType.CopyGeneric (future, skews);
                argsFields [i] = new FieldDefinition ("$arg" + i, Mono.Cecil.FieldAttributes.Private, paramType);
                future.Fields.Add (argsFields [i]);
                ctor.Parameters.Add (new ParameterDefinition (paramType));

                // this.$argX = <ArgX>
                ctorIL.Emit (OpCodes.Ldarg_0);
                ctorIL.Emit (OpCodes.Ldarg, i + 1);
                ctorIL.Emit (OpCodes.Stfld, argsFields [i]);

                i++;
            }

            // create a field for each local
            i = 0;
            localsFields = new FieldDefinition [method.Body.Variables.Count];
            foreach (var local in method.Body.Variables) {
                localsFields [i] = new FieldDefinition ("$loc" + i, Mono.Cecil.FieldAttributes.Private, local.VariableType.CopyGeneric (future, skews));
                future.Fields.Add (localsFields [i]);
                i++;
            }

            // create coroutine method
            var coroutine = new MethodDefinition ("Resume", Mono.Cecil.MethodAttributes.HideBySig | Mono.Cecil.MethodAttributes.Public |
                                                  Mono.Cecil.MethodAttributes.Virtual, voidType);
            future.Methods.Add (coroutine);

            var il = coroutine.Body.GetILProcessor ();
            var cfg = ControlFlowGraph.Create (method);

            var continuations = new List<Instruction> ();
            var coroutineInstructions = new OrderedDictionary<Instruction, List<Instruction>> (); // of (original Instruction) -> (List of replacement Instructions)

            // process the method we want to transform and...
            //  1) Replace all Ldarg, Starg opcodes with Ldfld, Stfld (argsFields)
            //  2) Replace all Ldloc, Stloc opcodes with Ldfld, Stfld (localsFields)
            //  3) Before Ret, add Future<T>.Value = ...; or Future.Status = FutureStatus.Fulfilled;
            //  4) Replace calls to Future.Wait or AsyncEnumerator.get_Current with continuation
            //  5) Remove calls to Future<T>.op_Implicit preceeding Ret
            //  6) Replace calls to Thread.Yield with continuation
            foreach (var block in cfg.Blocks) {
                foreach (var instruction in block) {

                    var current = new List<Instruction> ();
                    int? opr = null;
                    MethodReference callee = null;

                    switch (instruction.OpCode.Value) {
                    //1
                    case -254 /*OpCodes.Ldarg_0*/: opr = 0; goto case -503;
                    case -253 /*OpCodes.Ldarg_1*/: opr = 1; goto case -503;
                    case -252 /*OpCodes.Ldarg_2*/: opr = 2; goto case -503;
                    case -251 /*OpCodes.Ldarg_3*/: opr = 3; goto case -503;
                    case -242 /*OpCodes.Ldarg_S*/:
                    case -503 /*OpCodes.Ldarg*/:   opr = opr ?? (int)instruction.Operand;
                        current.Add (il.Create (OpCodes.Ldarg_0));
                        current.Add (il.Create (OpCodes.Ldfld, argsFields [opr.Value]));
                        break;

                    case -241 /*OpCodes.Ldarga_S*/:
                    case -502 /*OpCodes.Ldarga*/:
                        current.Add (il.Create (OpCodes.Ldarg_0));
                        current.Add (il.Create (OpCodes.Ldflda, argsFields [((ParameterReference)instruction.Operand).Index]));
                        break;

                    case -240 /*OpCodes.Starg_S*/:
                    case -501 /*OpCodes.Starg*/: opr = (int)instruction.Operand;

                        foreach (var lastStack in cfg.FindLastStackItem (block, instruction, IsBarrier))
                            coroutineInstructions [lastStack].Insert (0, il.Create (OpCodes.Ldarg_0));

                        current.Add (il.Create (OpCodes.Stfld, argsFields [opr.Value]));
                        HandleDups (coroutine, coroutineInstructions, instruction, current, argsFields [opr.Value].FieldType, ref dupLoc);
                        break;

                    //2
                    case -250 /*OpCodes.Ldloc_0*/: opr = 0; goto case -500;
                    case -249 /*OpCodes.Ldloc_1*/: opr = 1; goto case -500;
                    case -248 /*OpCodes.Ldloc_2*/: opr = 2; goto case -500;
                    case -247 /*OpCodes.Ldloc_3*/: opr = 3; goto case -500;
                    case -239 /*OpCodes.Ldloc_S*/:
                    case -500 /*OpCodes.Ldloc*/:   opr = opr ?? ((VariableDefinition)instruction.Operand).Index;
                        current.Add (il.Create (OpCodes.Ldarg_0));
                        current.Add (il.Create (OpCodes.Ldfld, localsFields [opr.Value]));
                        break;

                    case -238 /*OpCodes.Ldloca_S*/: goto case -499;
                    case -499 /*OpCodes.Ldloca*/:
                        current.Add (il.Create (OpCodes.Ldarg_0));
                        current.Add (il.Create (OpCodes.Ldflda, localsFields [((VariableDefinition)instruction.Operand).Index]));
                        break;

                    case -246 /*OpCodes.Stloc_0*/: opr = 0; goto case -498;
                    case -245 /*OpCodes.Stloc_1*/: opr = 1; goto case -498;
                    case -244 /*OpCodes.Stloc_2*/: opr = 2; goto case -498;
                    case -243 /*OpCodes.Stloc_3*/: opr = 3; goto case -498;
                    case -237 /*OpCodes.Stloc_S*/:
                    case -498 /*OpCodes.Stloc*/:   opr = opr ?? ((VariableDefinition)instruction.Operand).Index;

                        foreach (var lastStack in cfg.FindLastStackItem (block, instruction, IsBarrier))
                            coroutineInstructions [lastStack].Insert (0, il.Create (OpCodes.Ldarg_0));

                        current.Add (il.Create (OpCodes.Stfld, localsFields [opr.Value]));
                        HandleDups (coroutine, coroutineInstructions, instruction, current, localsFields [opr.Value].FieldType, ref dupLoc);
                        break;

                    //3
                    case -214 /*OpCodes.Ret*/:
                        if (returnType.IsGenericInstance) {
                            var setValueMethod = module.Import (typeof (Future<>).GetProperty ("Value").GetSetMethod (), returnType);
                            setValueMethod.DeclaringType = returnType;

                            foreach (var lastStack in cfg.FindLastStackItem (block, instruction, IsBarrier))
                                coroutineInstructions [lastStack].Insert (0, il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Call, setValueMethod));

                        } else {
                            if (!returnType.IsVoid ())
                                current.Add (il.Create (OpCodes.Pop));
                            current.Add (il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Ldc_I4_1));
                            current.Add (il.Create (OpCodes.Call, setStatus));
                        }
                        current.Add (il.Create (OpCodes.Ret));
                        break;

                    case -145 /*OpCodes.Callvirt*/:
                    case -216 /*OpCodes.Call*/:
                        callee = instruction.Operand as MethodReference;
                        //4
                        if ((callee.Name == "Wait" && callee.DeclaringType.IsFutureType ()) ||
                            (callee.Name == "get_Current" && callee.DeclaringType.IsAsyncEnumeratorType ())) {
                            //FIXME: We're reordering instructions to keep the stack balanced before/after continuation.
                            // Better to save/restore the stack instead? ->
                            //  Foo (MayThrow (), GetSomeFuture ().Wait ());
                            // Normally, if MayThrow () throws an exception, GetSomeFuture () is not called. But because we're
                            // reordering, GetSomeFuture *is* called, the future is waited, and then MayThrow throws on the continuation.

                            //FIXME: Support multiple predecessors here!
                            var lastStack = cfg.FindLastStackItem (block, instruction, IsBarrier).Single (); // this is the Future
                            // these are the items on the stack before the Future. they will be moved til after the continuation
                            var continuationInst = cfg.FindStackHeight (block, lastStack, 0, IsBarrier).Single ();

                            // FIXME: Right now, we're depending on the C# compiler's behavior re. not separating arg0 from call
                            coroutineInstructions [lastStack].Insert (0, il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Call, chain));

                            // this is the continuation
                            var inst = il.Create (OpCodes.Ret);
                            continuations.Add (inst);

                            // this is the instruction after...
                            var inst2 = il.Create (OpCodes.Ldarg_0);
                            // ...jump there if the future was already fullfilled or faulted
                            current.Add (il.Create (OpCodes.Brfalse, inst2));

                            // continuate :)
                            current.Add (inst);
                            inst = il.Create (OpCodes.Nop);

                            // check for exceptions
                            current.Add (inst2); // Ldarg.0
                            current.Add (il.Create (OpCodes.Ldfld, chainedFld));
                            current.Add (il.Create (OpCodes.Call, getException));
                            current.Add (il.Create (OpCodes.Brfalse, inst));

                            // houston, we have an exception..
                            //if (hasCatch) {
                            // OpCodes.Call, $catch1
                            // ...
                            // } else {
                            current.Add (il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Ldfld, chainedFld));
                            current.Add (il.Create (OpCodes.Call, getException));
                            current.Add (il.Create (OpCodes.Call, setException));
                            // }
                            // if (hasFinally)
                            //  OpCodes.Call, $finally1
                            // if (!hasCatch)
                            current.Add (il.Create (OpCodes.Ret));

                            current.Add (inst); // Nop
                            coroutineInstructions.Add (inst, current);

                            i = coroutineInstructions.IndexOfKey (continuationInst);
                            while (continuationInst != lastStack) {
                                // move these to after the continuation
                                // FIXME: Currently, we can't handle any jumps into this critical area
                                if (method.Body.IsJumpTarget (continuationInst))
                                    throw new NotSupportedException ("Support for jumps into continuation critical area not implemented");

                                current = coroutineInstructions [i];
                                coroutineInstructions.RemoveAt (i);
                                coroutineInstructions.Add (continuationInst, current);

                                continuationInst = coroutineInstructions.KeyForIndex (++i);
                            }

                            // load the continuation result if there is one

                            var waitFutureType = callee.DeclaringType as GenericInstanceType;
                            if (waitFutureType != null) {
                                var getValueMethod = module.Import (typeof (Future<>).GetProperty ("Value").GetGetMethod (), waitFutureType);
                                getValueMethod.DeclaringType = waitFutureType;

                                current = new List<Instruction> ();
                                current.Add (il.Create (OpCodes.Ldarg_0));
                                current.Add (il.Create (OpCodes.Ldfld, chainedFld));
                                current.Add (il.Create (OpCodes.Call, getValueMethod));
                                coroutineInstructions.Add (instruction, current);
                            }

                            continue;
                        }

                        //5
                        if (callee.Name == "op_Implicit" && callee.DeclaringType.IsBuiltInFutureType ()
                            && instruction.Next.OpCode == OpCodes.Ret)
                            continue;

                        //6
                        if (callee.Name == "Yield" && callee.DeclaringType.FullName == "Cirrus.Thread") {
                            var inst = il.Create (OpCodes.Ret);
                            continuations.Add (inst);

                            current.Add (il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Call, isScheduled));
                            current.Add (il.Create (OpCodes.Brtrue, inst));
                            current.Add (il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Ldarg_0));
                            current.Add (il.Create (OpCodes.Ldfld, threadFld));
                            current.Add (il.Create (OpCodes.Call, schedule));
                            current.Add (inst);
                        }
                        break;

                    //FIXME: Theoretically we need to do this for any op that emits a type token
                    case -491 /*OpCodes.Initobj*/:
                        current.Add (il.Create (OpCodes.Initobj, module.Import (((TypeReference)instruction.Operand).CopyGeneric (future, skews))));
                        break;
                    }

                    if (current.Count == 0)
                        current.Add (instruction);

                    coroutineInstructions.Add (instruction, current);
                }
            }

            /////////// ACTUAL COROUTINE METHOD IL EMITTING BEGINS HERE:

            // add state machine
            il.Emit (OpCodes.Ldarg_0);
            var loadPC = il.Create (OpCodes.Ldfld, pcFld);
            il.Append (loadPC);

            var jumpTable = new Mono.Cecil.Cil.Instruction [continuations.Count + 1];
            var kve = ((IEnumerable<KeyValuePair<Instruction,List<Instruction>>>)coroutineInstructions).GetEnumerator ();
            bool first = true;

            while (kve.MoveNext ()) {
                var kv = kve.Current;
                var current = kv.Value;

                if (current [0] != kv.Key)
                    method.Body.RedirectJumps (kv.Key, current [0]);

                foreach (var inst in current)
                    il.Append (inst);

                if (first) { jumpTable [0] = current.First (); first = false; }
            }

            i = 1;
            foreach (var continuation in continuations) {
                // set the next PC before the jump
                var inst = il.Create (OpCodes.Ldarg_0);

                // FIXME: Not as efficient as possible.. since continuation is a Ret we inserted, we had to redir jumps to it previously
                method.Body.RedirectJumps (continuation, inst);

                il.InsertBefore (continuation, inst);
                il.InsertBefore (continuation, il.Create (OpCodes.Ldc_I4, i));
                il.InsertBefore (continuation, il.Create (OpCodes.Stfld, pcFld));

                jumpTable [i++] = continuation.Next;
            }

            il.InsertAfter (loadPC, il.Create (OpCodes.Switch, jumpTable));

            // finalize coroutine
            coroutine.Body.ComputeOffsetsAndMaxStack ();

            // replace original method with a call to the coroutine
            method.Body.Instructions.Clear ();
            method.Body.Variables.Clear ();

            il = method.Body.GetILProcessor ();
            if (method.HasThis)
                il.Emit (OpCodes.Ldarg_0);

            i = argStart;
            foreach (var arg in method.Parameters)
                il.Emit (OpCodes.Ldarg, i++);

            if (method.HasGenericParameters || containingType.HasGenericParameters) {

                var genericCtor = ctor.MakeGeneric (module.Import (future.MakeGeneric (containingType.GenericParameters.Concat (method.GenericParameters).ToArray ())));
                il.Emit (OpCodes.Newobj, module.Import (genericCtor));

            } else {
                il.Emit (OpCodes.Newobj, ctor);
            }

            if (returnType.IsVoid ())
                il.Emit (OpCodes.Pop);
            il.Emit (OpCodes.Ret);

            method.Body.ComputeOffsetsAndMaxStack ();

            // finish constructor
            ctorIL.Emit (OpCodes.Ldarg_0);
            ctorIL.Emit (OpCodes.Call, coroutine);
            /*
            // schedule us only if we didn't complete already
            var retInst = ctorIL.Create (OpCodes.Ret);

            ctorIL.Emit (OpCodes.Ldarg_0);
            ctorIL.Emit (OpCodes.Call, getStatus);
            ctorIL.Emit (OpCodes.Brtrue, retInst);
            ctorIL.Emit (OpCodes.Ldarg_0);
            ctorIL.Emit (OpCodes.Call, reschedule);

            ctorIL.Append (retInst);
            */
            ctorIL.Emit (OpCodes.Ret);
            ctor.Body.ComputeOffsetsAndMaxStack ();
        }