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