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 (); }
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; }
// 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 (); }