// dnlib: See LICENSE.txt for more info using System.Collections.Generic; namespace dnlib.DotNet.Emit { /// /// Instruction utility methods /// public static class MethodUtils { /// /// Shorter instructions are converted to the longer form, eg. Ldc_I4_1 is /// converted to Ldc_I4 with a 1 as the operand. /// /// All instructions /// All locals /// All method parameters, including the hidden 'this' parameter /// if it's an instance method. Use . public static void SimplifyMacros(this IList instructions, IList locals, IList parameters) { int count = instructions.Count; for (int i = 0; i < count; i++) { var instr = instructions[i]; switch (instr.OpCode.Code) { case Code.Beq_S: instr.OpCode = OpCodes.Beq; break; case Code.Bge_S: instr.OpCode = OpCodes.Bge; break; case Code.Bge_Un_S: instr.OpCode = OpCodes.Bge_Un; break; case Code.Bgt_S: instr.OpCode = OpCodes.Bgt; break; case Code.Bgt_Un_S: instr.OpCode = OpCodes.Bgt_Un; break; case Code.Ble_S: instr.OpCode = OpCodes.Ble; break; case Code.Ble_Un_S: instr.OpCode = OpCodes.Ble_Un; break; case Code.Blt_S: instr.OpCode = OpCodes.Blt; break; case Code.Blt_Un_S: instr.OpCode = OpCodes.Blt_Un; break; case Code.Bne_Un_S: instr.OpCode = OpCodes.Bne_Un; break; case Code.Br_S: instr.OpCode = OpCodes.Br; break; case Code.Brfalse_S: instr.OpCode = OpCodes.Brfalse; break; case Code.Brtrue_S: instr.OpCode = OpCodes.Brtrue; break; case Code.Ldarg_0: instr.OpCode = OpCodes.Ldarg; instr.Operand = ReadList(parameters, 0); break; case Code.Ldarg_1: instr.OpCode = OpCodes.Ldarg; instr.Operand = ReadList(parameters, 1); break; case Code.Ldarg_2: instr.OpCode = OpCodes.Ldarg; instr.Operand = ReadList(parameters, 2); break; case Code.Ldarg_3: instr.OpCode = OpCodes.Ldarg; instr.Operand = ReadList(parameters, 3); break; case Code.Ldarg_S: instr.OpCode = OpCodes.Ldarg; break; case Code.Ldarga_S: instr.OpCode = OpCodes.Ldarga; break; case Code.Ldc_I4_0: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 0; break; case Code.Ldc_I4_1: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 1; break; case Code.Ldc_I4_2: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 2; break; case Code.Ldc_I4_3: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 3; break; case Code.Ldc_I4_4: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 4; break; case Code.Ldc_I4_5: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 5; break; case Code.Ldc_I4_6: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 6; break; case Code.Ldc_I4_7: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 7; break; case Code.Ldc_I4_8: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = 8; break; case Code.Ldc_I4_M1: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = -1; break; case Code.Ldc_I4_S: instr.OpCode = OpCodes.Ldc_I4; instr.Operand = (int)(sbyte)instr.Operand; break; case Code.Ldloc_0: instr.OpCode = OpCodes.Ldloc; instr.Operand = ReadList(locals, 0); break; case Code.Ldloc_1: instr.OpCode = OpCodes.Ldloc; instr.Operand = ReadList(locals, 1); break; case Code.Ldloc_2: instr.OpCode = OpCodes.Ldloc; instr.Operand = ReadList(locals, 2); break; case Code.Ldloc_3: instr.OpCode = OpCodes.Ldloc; instr.Operand = ReadList(locals, 3); break; case Code.Ldloc_S: instr.OpCode = OpCodes.Ldloc; break; case Code.Ldloca_S: instr.OpCode = OpCodes.Ldloca; break; case Code.Leave_S: instr.OpCode = OpCodes.Leave; break; case Code.Starg_S: instr.OpCode = OpCodes.Starg; break; case Code.Stloc_0: instr.OpCode = OpCodes.Stloc; instr.Operand = ReadList(locals, 0); break; case Code.Stloc_1: instr.OpCode = OpCodes.Stloc; instr.Operand = ReadList(locals, 1); break; case Code.Stloc_2: instr.OpCode = OpCodes.Stloc; instr.Operand = ReadList(locals, 2); break; case Code.Stloc_3: instr.OpCode = OpCodes.Stloc; instr.Operand = ReadList(locals, 3); break; case Code.Stloc_S: instr.OpCode = OpCodes.Stloc; break; } } } static T ReadList(IList list, int index) { if (list is null) return default; if ((uint)index < (uint)list.Count) return list[index]; return default; } /// /// Optimizes instructions by using the shorter form if possible. Eg. Ldc_I4 1 /// will be replaced with Ldc_I4_1. /// /// All instructions public static void OptimizeMacros(this IList instructions) { int count = instructions.Count; for (int i = 0; i < count; i++) { var instr = instructions[i]; Parameter arg; Local local; switch (instr.OpCode.Code) { case Code.Ldarg: case Code.Ldarg_S: arg = instr.Operand as Parameter; if (arg is null) break; if (arg.Index == 0) { instr.OpCode = OpCodes.Ldarg_0; instr.Operand = null; } else if (arg.Index == 1) { instr.OpCode = OpCodes.Ldarg_1; instr.Operand = null; } else if (arg.Index == 2) { instr.OpCode = OpCodes.Ldarg_2; instr.Operand = null; } else if (arg.Index == 3) { instr.OpCode = OpCodes.Ldarg_3; instr.Operand = null; } else if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue) instr.OpCode = OpCodes.Ldarg_S; break; case Code.Ldarga: arg = instr.Operand as Parameter; if (arg is null) break; if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue) instr.OpCode = OpCodes.Ldarga_S; break; case Code.Ldc_I4: case Code.Ldc_I4_S: int i4; if (instr.Operand is int) i4 = (int)instr.Operand; else if (instr.Operand is sbyte) i4 = (sbyte)instr.Operand; else break; switch (i4) { case 0: instr.OpCode = OpCodes.Ldc_I4_0; instr.Operand = null; break; case 1: instr.OpCode = OpCodes.Ldc_I4_1; instr.Operand = null; break; case 2: instr.OpCode = OpCodes.Ldc_I4_2; instr.Operand = null; break; case 3: instr.OpCode = OpCodes.Ldc_I4_3; instr.Operand = null; break; case 4: instr.OpCode = OpCodes.Ldc_I4_4; instr.Operand = null; break; case 5: instr.OpCode = OpCodes.Ldc_I4_5; instr.Operand = null; break; case 6: instr.OpCode = OpCodes.Ldc_I4_6; instr.Operand = null; break; case 7: instr.OpCode = OpCodes.Ldc_I4_7; instr.Operand = null; break; case 8: instr.OpCode = OpCodes.Ldc_I4_8; instr.Operand = null; break; case -1: instr.OpCode = OpCodes.Ldc_I4_M1; instr.Operand = null; break; default: if (sbyte.MinValue <= i4 && i4 <= sbyte.MaxValue) { instr.OpCode = OpCodes.Ldc_I4_S; instr.Operand = (sbyte)i4; } break; } break; case Code.Ldloc: case Code.Ldloc_S: local = instr.Operand as Local; if (local is null) break; if (local.Index == 0) { instr.OpCode = OpCodes.Ldloc_0; instr.Operand = null; } else if (local.Index == 1) { instr.OpCode = OpCodes.Ldloc_1; instr.Operand = null; } else if (local.Index == 2) { instr.OpCode = OpCodes.Ldloc_2; instr.Operand = null; } else if (local.Index == 3) { instr.OpCode = OpCodes.Ldloc_3; instr.Operand = null; } else if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue) instr.OpCode = OpCodes.Ldloc_S; break; case Code.Ldloca: local = instr.Operand as Local; if (local is null) break; if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue) instr.OpCode = OpCodes.Ldloca_S; break; case Code.Starg: arg = instr.Operand as Parameter; if (arg is null) break; if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue) instr.OpCode = OpCodes.Starg_S; break; case Code.Stloc: case Code.Stloc_S: local = instr.Operand as Local; if (local is null) break; if (local.Index == 0) { instr.OpCode = OpCodes.Stloc_0; instr.Operand = null; } else if (local.Index == 1) { instr.OpCode = OpCodes.Stloc_1; instr.Operand = null; } else if (local.Index == 2) { instr.OpCode = OpCodes.Stloc_2; instr.Operand = null; } else if (local.Index == 3) { instr.OpCode = OpCodes.Stloc_3; instr.Operand = null; } else if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue) instr.OpCode = OpCodes.Stloc_S; break; } } OptimizeBranches(instructions); } /// /// Short branch instructions are converted to the long form, eg. Beq_S is /// converted to Beq. /// /// All instructions public static void SimplifyBranches(this IList instructions) { int count = instructions.Count; for (int i = 0; i < count; i++) { var instr = instructions[i]; switch (instr.OpCode.Code) { case Code.Beq_S: instr.OpCode = OpCodes.Beq; break; case Code.Bge_S: instr.OpCode = OpCodes.Bge; break; case Code.Bgt_S: instr.OpCode = OpCodes.Bgt; break; case Code.Ble_S: instr.OpCode = OpCodes.Ble; break; case Code.Blt_S: instr.OpCode = OpCodes.Blt; break; case Code.Bne_Un_S: instr.OpCode = OpCodes.Bne_Un; break; case Code.Bge_Un_S: instr.OpCode = OpCodes.Bge_Un; break; case Code.Bgt_Un_S: instr.OpCode = OpCodes.Bgt_Un; break; case Code.Ble_Un_S: instr.OpCode = OpCodes.Ble_Un; break; case Code.Blt_Un_S: instr.OpCode = OpCodes.Blt_Un; break; case Code.Br_S: instr.OpCode = OpCodes.Br; break; case Code.Brfalse_S:instr.OpCode = OpCodes.Brfalse; break; case Code.Brtrue_S: instr.OpCode = OpCodes.Brtrue; break; case Code.Leave_S: instr.OpCode = OpCodes.Leave; break; } } } /// /// Optimizes branches by using the smallest possible branch /// /// All instructions public static void OptimizeBranches(this IList instructions) { while (true) { UpdateInstructionOffsets(instructions); bool modified = false; int count = instructions.Count; for (int i = 0; i < count; i++) { var instr = instructions[i]; OpCode shortOpCode; switch (instr.OpCode.Code) { case Code.Beq: shortOpCode = OpCodes.Beq_S; break; case Code.Bge: shortOpCode = OpCodes.Bge_S; break; case Code.Bge_Un: shortOpCode = OpCodes.Bge_Un_S; break; case Code.Bgt: shortOpCode = OpCodes.Bgt_S; break; case Code.Bgt_Un: shortOpCode = OpCodes.Bgt_Un_S; break; case Code.Ble: shortOpCode = OpCodes.Ble_S; break; case Code.Ble_Un: shortOpCode = OpCodes.Ble_Un_S; break; case Code.Blt: shortOpCode = OpCodes.Blt_S; break; case Code.Blt_Un: shortOpCode = OpCodes.Blt_Un_S; break; case Code.Bne_Un: shortOpCode = OpCodes.Bne_Un_S; break; case Code.Br: shortOpCode = OpCodes.Br_S; break; case Code.Brfalse: shortOpCode = OpCodes.Brfalse_S; break; case Code.Brtrue: shortOpCode = OpCodes.Brtrue_S; break; case Code.Leave: shortOpCode = OpCodes.Leave_S; break; default: continue; } var targetInstr = instr.Operand as Instruction; if (targetInstr is null) continue; int afterShortInstr; if (targetInstr.Offset >= instr.Offset) { // Target is >= this instruction so use the offset after // current instruction afterShortInstr = (int)instr.Offset + instr.GetSize(); } else { // Target is < this instruction so use the offset after // the short instruction const int operandSize = 1; afterShortInstr = (int)instr.Offset + shortOpCode.Size + operandSize; } int displ = (int)targetInstr.Offset - afterShortInstr; if (sbyte.MinValue <= displ && displ <= sbyte.MaxValue) { instr.OpCode = shortOpCode; modified = true; } } if (!modified) break; } } /// /// Updates each instruction's offset /// /// All instructions /// Total size in bytes of all instructions public static uint UpdateInstructionOffsets(this IList instructions) { uint offset = 0; int count = instructions.Count; for (int i = 0; i < count; i++) { var instr = instructions[i]; instr.Offset = offset; offset += (uint)instr.GetSize(); } return offset; } } }