Esempio 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 ();
        }
Esempio n. 2
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 ();
        }