internal void Emit(OpCode opcode) { instructions.Add(CurrentPos(), new CodeInstruction(opcode)); LogIL(opcode); il.Emit(opcode); }
/// <summary> /// Processes and writes IL to the provided method body. /// Note that this cleans the existing method body (removes insturctions and exception handlers). /// </summary> /// <param name="body">Method body to write to.</param> /// <param name="original">Original method that transpiler can optionally call into</param> /// <exception cref="NotSupportedException"> /// One of IL opcodes contains a CallSide (e.g. calli), which is currently not /// fully supported. /// </exception> /// <exception cref="ArgumentNullException">One of IL opcodes with an operand contains a null operand.</exception> public void WriteTo(MethodBody body, MethodBase original = null) { // Clean up the body of the target method body.Instructions.Clear(); body.ExceptionHandlers.Clear(); var il = new CecilILGenerator(body.GetILProcessor()); var cil = il.GetProxy(); // Define an "empty" label // In Harmony, the first label can point to the end of the method // Apparently, some transpilers naively call new Label() to define a label and thus end up // using the first label without knowing it // By defining the first label we'll ensure label count is correct il.DefineLabel(); // Step 1: Prepare labels for instructions Prepare(vDef => il.GetLocal(vDef), il.DefineLabel); // Step 2: Run the code instruction through transpilers var newInstructions = ApplyTranspilers(cil, original); // We don't remove trailing `ret`s because we need to do so only if prefixes/postfixes are present // Step 3: Emit code foreach (var ins in newInstructions) { ins.labels.ForEach(l => il.MarkLabel(l)); ins.blocks.ForEach(b => il.MarkBlockBefore(b)); // We don't replace `ret`s yet because we might not need to // We do that only if we add prefixes/postfixes // We also don't need to care for long/short forms thanks to Cecil/MonoMod // Temporary fix: CecilILGenerator doesn't properly handle ldarg switch (ins.opcode.OperandType) { case SRE.OperandType.InlineNone: il.Emit(ins.opcode); break; case SRE.OperandType.InlineSig: throw new NotSupportedException( "Emitting opcodes with CallSites is currently not fully implemented"); default: if (ins.operand == null) { throw new ArgumentNullException(nameof(ins.operand), $"Invalid argument for {ins}"); } il.Emit(ins.opcode, ins.operand); break; } ins.blocks.ForEach(b => il.MarkBlockAfter(b)); } // Note: We lose all unassigned labels here along with any way to log them // On the contrary, we gain better logging anyway down the line by using Cecil // Step 4: Run the code through raw IL manipulators (if any) // TODO: IL Manipulators }