context: /*
      Copyright (C) 2014-2019 de4dot@gmail.com
  
      This file is part of dnSpy
  
      dnSpy is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      dnSpy is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.ComponentModel.Composition;
  using System.Diagnostics;
  using System.Linq;
  using dnlib.DotNet;
  using dnlib.DotNet.Emit;
  using dnSpy.Contracts.Decompiler;
  using dnSpy.Contracts.Extension;
  using dnSpy.Contracts.Text;
  
  namespace dnSpy.AsmEditor.MethodBody {
  	[Flags]
  	enum WriteObjectFlags : uint {
  		None						= 0,
  		ShortInstruction			= 0x00000001,
  	}
  
  	static class BodyUtils {
  		public static readonly Parameter NullParameter = new Parameter(int.MinValue);
  		static ISimpleILPrinter simpleILPrinter;
  
  		[ExportAutoLoaded]
  		sealed class BodyUtilsInit : IAutoLoaded {
  			[ImportingConstructor]
  			BodyUtilsInit([ImportMany] IEnumerable<ISimpleILPrinter> simpleILPrinters) => BodyUtils.simpleILPrinter = simpleILPrinters.OrderBy(a => a.Order).FirstOrDefault() ?? new DummyPrinter();
  
  			sealed class DummyPrinter : ISimpleILPrinter {
  				public double Order => 0;
  
  				public bool Write(IDecompilerOutput output, IMemberRef? member) {
  					if (member is null || member is GenericParam)
  						return false;
  					Write(output, member);
  					return true;
  				}
  
  				public void Write(IDecompilerOutput output, TypeSig? type) => Write(output, (object?)type);
  
  				public void Write(IDecompilerOutput output, MethodSig? sig) => Write(output, (object?)sig);
  
  				void Write(IDecompilerOutput output, object? value) => output.Write($"Missing ISimpleILPrinter: {value}", BoxedTextColor.Text);
  			}
  		}
  
  		public static bool IsNull(object? op) =>
  			op is null ||
  			op == NullParameter ||
  			op == InstructionVM.Null ||
  			op == LocalVM.Null;
  
  		public static object? TryGetVM(Dictionary<object, object> ops, object? objModel) {
  			if (objModel is null)
  				return null;
  			if (!ops.TryGetValue(objModel, out var objVm))
  				return objModel;
  			return objVm;
  		}
  
  		public static object? TryGetModel(Dictionary<object, object> ops, object? objVm) {
  			if (IsNull(objVm))
  				return null;
  			Debug.Assert(!(objVm is null));
  			if (!ops.TryGetValue(objVm, out var objModel))
  				return objVm;
  			return objModel;
  		}
  
  		public static object? ToOperandVM(Dictionary<object, object> ops, object? operand) {
  			if (operand is IList<Instruction> targets) {
  				var newTargets = new InstructionVM?[targets.Count];
  				for (int i = 0; i < newTargets.Length; i++)
  					newTargets[i] = (InstructionVM?)TryGetVM(ops, (object)targets[i] ?? InstructionVM.Null);
  				return newTargets;
  			}
  
  			return TryGetVM(ops, operand);
  		}
  
  		public static object? ToOperandModel(Dictionary<object, object> ops, object? operand) {
  			if (operand is IList<InstructionVM> targets) {
  				var newTargets = new Instruction?[targets.Count];
  				for (int i = 0; i < newTargets.Length; i++)
  					newTargets[i] = TryGetModel(ops, targets[i]) as Instruction;
  				return newTargets;
  			}
  
  			return TryGetModel(ops, operand);
  		}
  
  		public static void UpdateIndexesOffsets(this IList<InstructionVM> instrs, int index) {
  			uint offset = index > 0 ? instrs[index - 1].Offset + (uint)instrs[index - 1].GetSize() : 0;
  			for (; index < instrs.Count; index++) {
  				var instr = instrs[index];
  				instr.Index = index;
  				instr.Offset = offset;
  				offset += (uint)instr.GetSize();
  			}
  		}
  
  		public static uint UpdateInstructionOffsets(this IList<InstructionVM> instrs, int index = 0) {
  			uint offset = index > 0 ? instrs[index - 1].Offset + (uint)instrs[index - 1].GetSize() : 0;
  			for (; index < instrs.Count; index++) {
  				var instr = instrs[index];
  				instr.Offset = offset;
  				offset += (uint)instr.GetSize();
  			}
  			return offset;
  		}
  
  		public static void SimplifyMacros(this IList<InstructionVM> instrs, IList<LocalVM> locals, IList<Parameter> parameters) {
  			foreach (var instr in instrs) {
  				switch (instr.Code) {
  				case Code.Beq_S:
  					instr.Code = Code.Beq;
  					break;
  
  				case Code.Bge_S:
  					instr.Code = Code.Bge;
  					break;
  
  				case Code.Bge_Un_S:
  					instr.Code = Code.Bge_Un;
  					break;
  
  				case Code.Bgt_S:
  					instr.Code = Code.Bgt;
  					break;
  
  				case Code.Bgt_Un_S:
  					instr.Code = Code.Bgt_Un;
  					break;
  
  				case Code.Ble_S:
  					instr.Code = Code.Ble;
  					break;
  
  				case Code.Ble_Un_S:
  					instr.Code = Code.Ble_Un;
  					break;
  
  				case Code.Blt_S:
  					instr.Code = Code.Blt;
  					break;
  
  				case Code.Blt_Un_S:
  					instr.Code = Code.Blt_Un;
  					break;
  
  				case Code.Bne_Un_S:
  					instr.Code = Code.Bne_Un;
  					break;
  
  				case Code.Br_S:
  					instr.Code = Code.Br;
  					break;
  
  				case Code.Brfalse_S:
  					instr.Code = Code.Brfalse;
  					break;
  
  				case Code.Brtrue_S:
  					instr.Code = Code.Brtrue;
  					break;
  
  				case Code.Ldarg_0:
  					instr.Code = Code.Ldarg;
  					instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 0) ?? BodyUtils.NullParameter;
  					break;
  
  				case Code.Ldarg_1:
  					instr.Code = Code.Ldarg;
  					instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 1) ?? BodyUtils.NullParameter;
  					break;
  
  				case Code.Ldarg_2:
  					instr.Code = Code.Ldarg;
  					instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 2) ?? BodyUtils.NullParameter;
  					break;
  
  				case Code.Ldarg_3:
  					instr.Code = Code.Ldarg;
  					instr.InstructionOperandVM.OperandListItem = ReadList(parameters, 3) ?? BodyUtils.NullParameter;
  					break;
  
  				case Code.Ldarg_S:
  					instr.Code = Code.Ldarg;
  					break;
  
  				case Code.Ldarga_S:
  					instr.Code = Code.Ldarga;
  					break;
  
  				case Code.Ldc_I4_0:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 0;
  					break;
  
  				case Code.Ldc_I4_1:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 1;
  					break;
  
  				case Code.Ldc_I4_2:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 2;
  					break;
  
  				case Code.Ldc_I4_3:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 3;
  					break;
  
  				case Code.Ldc_I4_4:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 4;
  					break;
  
  				case Code.Ldc_I4_5:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 5;
  					break;
  
  				case Code.Ldc_I4_6:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 6;
  					break;
  
  				case Code.Ldc_I4_7:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 7;
  					break;
  
  				case Code.Ldc_I4_8:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = 8;
  					break;
  
  				case Code.Ldc_I4_M1:
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = -1;
  					break;
  
  				case Code.Ldc_I4_S:
  					if (instr.InstructionOperandVM.SByte.HasError)
  						break;
  					instr.Code = Code.Ldc_I4;
  					instr.InstructionOperandVM.Int32.Value = instr.InstructionOperandVM.SByte.Value;
  					break;
  
  				case Code.Ldloc_0:
  					instr.Code = Code.Ldloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 0) ?? LocalVM.Null;
  					break;
  
  				case Code.Ldloc_1:
  					instr.Code = Code.Ldloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 1) ?? LocalVM.Null;
  					break;
  
  				case Code.Ldloc_2:
  					instr.Code = Code.Ldloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 2) ?? LocalVM.Null;
  					break;
  
  				case Code.Ldloc_3:
  					instr.Code = Code.Ldloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 3) ?? LocalVM.Null;
  					break;
  
  				case Code.Ldloc_S:
  					instr.Code = Code.Ldloc;
  					break;
  
  				case Code.Ldloca_S:
  					instr.Code = Code.Ldloca;
  					break;
  
  				case Code.Leave_S:
  					instr.Code = Code.Leave;
  					break;
  
  				case Code.Starg_S:
  					instr.Code = Code.Starg;
  					break;
  
  				case Code.Stloc_0:
  					instr.Code = Code.Stloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 0) ?? LocalVM.Null;
  					break;
  
  				case Code.Stloc_1:
  					instr.Code = Code.Stloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 1) ?? LocalVM.Null;
  					break;
  
  				case Code.Stloc_2:
  					instr.Code = Code.Stloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 2) ?? LocalVM.Null;
  					break;
  
  				case Code.Stloc_3:
  					instr.Code = Code.Stloc;
  					instr.InstructionOperandVM.OperandListItem = ReadList(locals, 3) ?? LocalVM.Null;
  					break;
  
  				case Code.Stloc_S:
  					instr.Code = Code.Stloc;
  					break;
  				}
  			}
  		}
  
  		static T? ReadList<T>(IList<T>? list, int index) where T : class {
  			if (list is null || index < 0 || index >= list.Count)
  				return null;
  			return list[index];
  		}
  
  		public static void OptimizeMacros(this IList<InstructionVM> instrs) {
  			foreach (var instr in instrs) {
  				Parameter? arg;
  				LocalVM? local;
  				switch (instr.Code) {
  				case Code.Ldarg:
  				case Code.Ldarg_S:
  					arg = instr.InstructionOperandVM.Value as Parameter;
  					if (arg is null)
  						break;
  					if (arg.Index == 0)
  						instr.Code = Code.Ldarg_0;
  					else if (arg.Index == 1)
  						instr.Code = Code.Ldarg_1;
  					else if (arg.Index == 2)
  						instr.Code = Code.Ldarg_2;
  					else if (arg.Index == 3)
  						instr.Code = Code.Ldarg_3;
  					else if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue)
  						instr.Code = Code.Ldarg_S;
  					break;
  
  				case Code.Ldarga:
  					arg = instr.InstructionOperandVM.Value as Parameter;
  					if (arg is null)
  						break;
  					if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue)
  						instr.Code = Code.Ldarga_S;
  					break;
  
  				case Code.Ldc_I4:
  				case Code.Ldc_I4_S:
  					int i4;
  					if (instr.Code == Code.Ldc_I4) {
  						if (instr.InstructionOperandVM.Int32.HasError)
  							break;
  						i4 = instr.InstructionOperandVM.Int32.Value;
  					}
  					else {
  						if (instr.InstructionOperandVM.SByte.HasError)
  							break;
  						i4 = instr.InstructionOperandVM.SByte.Value;
  					}
  					switch (i4) {
  					case 0: instr.Code = Code.Ldc_I4_0; break;
  					case 1: instr.Code = Code.Ldc_I4_1; break;
  					case 2: instr.Code = Code.Ldc_I4_2; break;
  					case 3: instr.Code = Code.Ldc_I4_3; break;
  					case 4: instr.Code = Code.Ldc_I4_4; break;
  					case 5: instr.Code = Code.Ldc_I4_5; break;
  					case 6: instr.Code = Code.Ldc_I4_6; break;
  					case 7: instr.Code = Code.Ldc_I4_7; break;
  					case 8: instr.Code = Code.Ldc_I4_8; break;
  					case-1: instr.Code = Code.Ldc_I4_M1; break;
  
  					default:
  						if (sbyte.MinValue <= i4 && i4 <= sbyte.MaxValue) {
  							instr.Code = Code.Ldc_I4_S;
  							instr.InstructionOperandVM.SByte.Value = (sbyte)i4;
  						}
  						break;
  					}
  					break;
  
  				case Code.Ldloc:
  				case Code.Ldloc_S:
  					local = instr.InstructionOperandVM.Value as LocalVM;
  					if (local is null)
  						break;
  					if (local.Index == 0)
  						instr.Code = Code.Ldloc_0;
  					else if (local.Index == 1)
  						instr.Code = Code.Ldloc_1;
  					else if (local.Index == 2)
  						instr.Code = Code.Ldloc_2;
  					else if (local.Index == 3)
  						instr.Code = Code.Ldloc_3;
  					else if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue)
  						instr.Code = Code.Ldloc_S;
  					break;
  
  				case Code.Ldloca:
  					local = instr.InstructionOperandVM.Value as LocalVM;
  					if (local is null)
  						break;
  					if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue)
  						instr.Code = Code.Ldloca_S;
  					break;
  
  				case Code.Starg:
  					arg = instr.InstructionOperandVM.Value as Parameter;
  					if (arg is null)
  						break;
  					if (byte.MinValue <= arg.Index && arg.Index <= byte.MaxValue)
  						instr.Code = Code.Starg_S;
  					break;
  
  				case Code.Stloc:
  				case Code.Stloc_S:
  					local = instr.InstructionOperandVM.Value as LocalVM;
  					if (local is null)
  						break;
  					if (local.Index == 0)
  						instr.Code = Code.Stloc_0;
  					else if (local.Index == 1)
  						instr.Code = Code.Stloc_1;
  					else if (local.Index == 2)
  						instr.Code = Code.Stloc_2;
  					else if (local.Index == 3)
  						instr.Code = Code.Stloc_3;
  					else if (byte.MinValue <= local.Index && local.Index <= byte.MaxValue)
  						instr.Code = Code.Stloc_S;
  					break;
  				}
  			}
  
  			instrs.OptimizeBranches();
  		}
  
  		public static void SimplifyBranches(this IList<InstructionVM> instrs) {
  			foreach (var instr in instrs) {
  				switch (instr.Code) {
  				case Code.Beq_S:	instr.Code = Code.Beq; break;
  				case Code.Bge_S:	instr.Code = Code.Bge; break;
  				case Code.Bgt_S:	instr.Code = Code.Bgt; break;
  				case Code.Ble_S:	instr.Code = Code.Ble; break;
  				case Code.Blt_S:	instr.Code = Code.Blt; break;
  				case Code.Bne_Un_S:	instr.Code = Code.Bne_Un; break;
  				case Code.Bge_Un_S:	instr.Code = Code.Bge_Un; break;
  				case Code.Bgt_Un_S:	instr.Code = Code.Bgt_Un; break;
  				case Code.Ble_Un_S:	instr.Code = Code.Ble_Un; break;
  				case Code.Blt_Un_S:	instr.Code = Code.Blt_Un; break;
  				case Code.Br_S:		instr.Code = Code.Br; break;
  				case Code.Brfalse_S:instr.Code = Code.Brfalse; break;
  				case Code.Brtrue_S:	instr.Code = Code.Brtrue; break;
  				case Code.Leave_S:	instr.Code = Code.Leave; break;
  				}
  			}
  		}
  
  		public static void OptimizeBranches(this IList<InstructionVM> instrs) {
  			while (true) {
  				instrs.UpdateInstructionOffsets();
  
  				bool modified = false;
  				foreach (var instr in instrs) {
  					OpCode shortOpCode;
  					switch (instr.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;
  
gt: 					case Code.Br:		shortOpCode = OpCodes.Br_S; break;