protected virtual IEnumerable<Instruction> ImplementWait(Instruction instruction, TypeReference waitFutureType) { //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 (); IList<Instruction> precontinuationStack = null; if (continuationInst != lastStack) { precontinuationStack = ExtractInstructionRange (continuationInst, lastStack); // FIXME: Currently, we can't handle any jumps into this critical area if (precontinuationStack.Any (i => method.Body.IsJumpTarget (i))) throw new NotSupportedException ("Support for jumps into continuation critical area not implemented"); } // Insert ldarg.0 for CoroutineFuture.Chain // FIXME: Right now, we're depending on the C# compiler's behavior re. not separating arg0 from call InsertBefore (lastStack, Instruction.Create (OpCodes.Ldarg_0)); var chainInstructions = new List<Instruction> (); chainInstructions.Add (Instruction.Create (OpCodes.Call, chain)); var continuation = Instruction.Create (OpCodes.Ret); continuations.Add (continuation); var afterContinuation = Instruction.Create (OpCodes.Ldarg_0); // CoroutineFuture.Chain returns false if we don't need continuation chainInstructions.Add (Instruction.Create (OpCodes.Brfalse_S, afterContinuation)); chainInstructions.Add (continuation); chainInstructions.Add (afterContinuation); // this is the instruction that will follow if the future is not faulted var notFaulted = Instruction.Create (OpCodes.Nop); // check for exceptions chainInstructions.Add (Instruction.Create (OpCodes.Call, checkException)); chainInstructions.Add (Instruction.Create (OpCodes.Ldarg_0)); chainInstructions.Add (Instruction.Create (OpCodes.Ldfld, chainedFld)); chainInstructions.Add (Instruction.Create (OpCodes.Callvirt, module.Import (getException))); chainInstructions.Add (Instruction.Create (OpCodes.Brfalse, notFaulted)); // mark exception as handled and throw it again right there chainInstructions.Add (Instruction.Create (OpCodes.Ldarg_0)); chainInstructions.Add (Instruction.Create (OpCodes.Ldfld, chainedFld)); chainInstructions.Add (Instruction.Create (OpCodes.Dup)); chainInstructions.Add (Instruction.Create (OpCodes.Ldc_I4, -1)); chainInstructions.Add (Instruction.Create (OpCodes.Call, module.Import (setStatus))); chainInstructions.Add (Instruction.Create (OpCodes.Callvirt, module.Import (getException))); chainInstructions.Add (Instruction.Create (OpCodes.Throw)); // redirect jumps from our phony Wait call to the chain call. method.Body.Instructions.RedirectJumps (instruction, chainInstructions [0]); // -- add continuation to InstructionMap InstructionMap.Add (chainInstructions [0], chainInstructions); yield return notFaulted; // move anything on the stack before the Wait to after the continuation if (precontinuationStack != null) { foreach (var inst in precontinuationStack) yield return inst; } // load the continuation result if there is one // FIXME: Eventually, we prolly don't want to do this if we're just going to pop the result.. // but we'd have to get rid of the pop instruction too... if (waitFutureType != null /* && instruction.Next.OpCode != OpCodes.Pop */) { var genWaitFuture = waitFutureType.CopyGeneric (coroutineType, skews); yield return Instruction.Create (OpCodes.Ldarg_0); yield return Instruction.Create (OpCodes.Ldfld, chainedFld); yield return Instruction.Create (OpCodes.Isinst, genWaitFuture); yield return Instruction.Create (OpCodes.Call, module.ImportFrom (getValue, genWaitFuture)); } // reset chained to null yield return Instruction.Create (OpCodes.Ldarg_0); yield return Instruction.Create (OpCodes.Ldnull); yield return Instruction.Create (OpCodes.Stfld, chainedFld); }
// ----- // We are taking a method implementation and extracting it into its own class. // If the method is generic, we need to make that class generic and update any // type refs to the old method's generic parameters to refer to the new class's // generic parameters instead. protected TypeReference FixTypeToken(TypeReference token) { return module.Import (token.CopyGeneric (coroutineType, skews)); }