File: Linker.Steps\UnreachableBlocksOptimizer.cs
Web Access
Project: src\src\tools\illink\src\linker\Mono.Linker.csproj (illink)
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;
 
namespace Mono.Linker.Steps
{
	//
	// Evaluates simple properties or methods for constant expressions and
	// then uses this information to remove unreachable conditional blocks and
	// inline collected constants.
	//
	public class UnreachableBlocksOptimizer
	{
		readonly LinkContext _context;
		readonly Dictionary<MethodDefinition, MethodResult?> _cache_method_results = new (2048);
		readonly Stack<MethodDefinition> _resursion_guard = new ();
 
		MethodDefinition? IntPtrSize, UIntPtrSize;
 
		public UnreachableBlocksOptimizer (LinkContext context)
		{
			_context = context;
		}
 
		/// <summary>
		/// Processes the specified method and perform all branch removal optimizations on it.
		/// When this returns it's guaranteed that the method has been optimized (if possible).
		/// </summary>
		/// <param name="method">The method to process</param>
		public void ProcessMethod (MethodDefinition method)
		{
			if (!IsMethodSupported (method))
				return;
 
			if (_context.Annotations.GetAction (method.Module.Assembly) != AssemblyAction.Link)
				return;
 
			var reducer = new BodyReducer (method.Body, _context);
 
			try {
				//
				// If no external dependency can be extracted into constant there won't be
				// anything to optimize in the method
				//
				if (!reducer.ApplyTemporaryInlining (this))
					return;
 
				//
				// This is the main step which evaluates if any expression can
				// produce folded branches. When it finds them the unreachable
				// branch is removed.
				//
				if (reducer.RewriteBody ())
					_context.LogMessage ($"Reduced '{reducer.InstructionsReplaced}' instructions in conditional branches for [{method.DeclaringType.Module.Assembly.Name}] method '{method.GetDisplayName ()}'.");
 
				//
				// Note: The inliner cannot run before reducer rewrites body as it
				// would require another recomputing offsets due to instructions replacement
				// done by inliner
				//
				var inliner = new CallInliner (method.Body, this);
				inliner.RewriteBody ();
			} catch (Exception e) {
				throw new InternalErrorException ($"Could not process the body of method '{method.GetDisplayName ()}'.", e);
			}
		}
 
		static bool IsMethodSupported (MethodDefinition method)
		{
			if (!method.HasBody)
				return false;
 
			//
			// Block methods which rewrite does not support
			//
			switch (method.ReturnType.MetadataType) {
			case MetadataType.ByReference:
			case MetadataType.FunctionPointer:
				return false;
			}
 
			return true;
		}
 
		static bool HasJumpIntoTargetRange (Collection<Instruction> instructions, int firstInstr, int lastInstr, Func<Instruction, int?>? mapping = null)
		{
			foreach (var instr in instructions) {
				switch (instr.OpCode.FlowControl) {
				case FlowControl.Branch:
				case FlowControl.Cond_Branch:
					if (instr.Operand is Instruction target) {
						if (mapping != null && mapping (target) is int index) {
							if (index >= firstInstr && index <= lastInstr) {
								return true;
							}
						}
						else {
							for (int i = firstInstr; i <= lastInstr; i++) {
								if (instructions[i] == target) {
									return true;
								}
							}
						}
					} else {
						foreach (var rtarget in (Instruction[]) instr.Operand) {
							if (mapping != null && mapping (rtarget) is int index) {
								if (index >= firstInstr && index <= lastInstr) {
									return true;
								}
							}
							else {
								for (int i = firstInstr; i <= lastInstr; i++) {
									if (instructions[i] == rtarget) {
										return true;
									}
								}
							}
						}
					}
 
					break;
				}
			}
 
			return false;
		}
 
		static bool IsSideEffectFreeLoad (Instruction instr)
		{
			switch (instr.OpCode.Code) {
			case Code.Ldarg:
			case Code.Ldloc:
			case Code.Ldloc_0:
			case Code.Ldloc_1:
			case Code.Ldloc_2:
			case Code.Ldloc_3:
			case Code.Ldloc_S:
			case Code.Ldc_I4_0:
			case Code.Ldc_I4_1:
			case Code.Ldc_I4_2:
			case Code.Ldc_I4_3:
			case Code.Ldc_I4_4:
			case Code.Ldc_I4_5:
			case Code.Ldc_I4_6:
			case Code.Ldc_I4_7:
			case Code.Ldc_I4_8:
			case Code.Ldc_I4:
			case Code.Ldc_I4_S:
			case Code.Ldc_I4_M1:
			case Code.Ldc_I8:
			case Code.Ldc_R4:
			case Code.Ldc_R8:
			case Code.Ldnull:
			case Code.Ldstr:
				return true;
			}
 
			return false;
		}
 
		static bool IsComparisonAlwaysTrue (OpCode opCode, int left, int right)
		{
			switch (opCode.Code) {
			case Code.Beq:
			case Code.Beq_S:
			case Code.Ceq:
				return left == right;
			case Code.Bne_Un:
			case Code.Bne_Un_S:
				return left != right;
			case Code.Bge:
			case Code.Bge_S:
				return left >= right;
			case Code.Bge_Un:
			case Code.Bge_Un_S:
				return (uint) left >= (uint) right;
			case Code.Bgt:
			case Code.Bgt_S:
			case Code.Cgt:
				return left > right;
			case Code.Bgt_Un:
			case Code.Bgt_Un_S:
				return (uint) left > (uint) right;
			case Code.Ble:
			case Code.Ble_S:
				return left <= right;
			case Code.Ble_Un:
			case Code.Ble_Un_S:
				return (uint) left <= (uint) right;
			case Code.Blt:
			case Code.Blt_S:
			case Code.Clt:
				return left < right;
			case Code.Blt_Un:
			case Code.Blt_Un_S:
				return (uint) left < (uint) right;
			}
 
			throw new NotImplementedException (opCode.ToString ());
		}
 
		MethodResult? AnalyzeMethodForConstantResult (in CalleePayload callee, Stack<MethodDefinition> callStack)
		{
			MethodDefinition method = callee.Method;
 
			if (method.ReturnType.MetadataType == MetadataType.Void)
				return null;
 
			if (!method.HasBody)
				return null;
 
			switch (_context.Annotations.GetAction (method)) {
			case MethodAction.ConvertToThrow:
				return null;
			case MethodAction.ConvertToStub:
				Instruction? constant = CodeRewriterStep.CreateConstantResultInstruction (_context, method);
				return constant == null ? null : new MethodResult (constant, !HasSideEffects (method));
			}
 
			if (method.IsIntrinsic () || method.NoInlining)
				return null;
 
			if (!_context.IsOptimizationEnabled (CodeOptimizations.IPConstantPropagation, method))
				return null;
 
			var analyzer = new ConstantExpressionMethodAnalyzer (this);
			if (analyzer.Analyze (callee, callStack))
				return new MethodResult (analyzer.Result, analyzer.SideEffectFreeResult);
 
			return null;
		}
 
		static bool HasSideEffects (MethodDefinition method)
		{
			return !method.DeclaringType.IsBeforeFieldInit;
		}
 
		//
		// Return expression with a value when method implementation can be
		// interpreted during trimming
		//
		MethodResult? TryGetMethodCallResult (in CalleePayload callee)
		{
			_resursion_guard.Clear ();
			return TryGetMethodCallResult (callee, _resursion_guard);
		}
 
		MethodResult? TryGetMethodCallResult (in CalleePayload callee, Stack<MethodDefinition> callStack)
		{
			MethodResult? value;
 
			MethodDefinition method = callee.Method;
			if (!method.HasMetadataParameters () || callee.HasUnknownArguments) {
				if (!_cache_method_results.TryGetValue (method, out value) && !IsDeepStack (callStack)) {
					value = AnalyzeMethodForConstantResult (callee, callStack);
					_cache_method_results.Add (method, value);
				}
 
				return value;
			}
 
			return AnalyzeMethodForConstantResult (callee, callStack);
 
			static bool IsDeepStack (Stack<MethodDefinition> callStack) => callStack.Count > 100;
		}
 
		Instruction? GetSizeOfResult (TypeReference type)
		{
			MethodDefinition? sizeOfImpl = null;
 
			//
			// sizeof (IntPtr) and sizeof (UIntPtr) are just aliases for IntPtr.Size and UIntPtr.Size
			// which are simple static properties commonly overwritten. Instead of forcing C# code style
			// we handle both via static get_Size method
			//
			if (type.MetadataType == MetadataType.UIntPtr) {
				sizeOfImpl = (UIntPtrSize ??= FindSizeMethod (_context.TryResolve (type)));
			} else if (type.MetadataType == MetadataType.IntPtr) {
				sizeOfImpl = (IntPtrSize ??= FindSizeMethod (_context.TryResolve (type)));
			}
 
			if (sizeOfImpl == null)
				return null;
 
			return TryGetMethodCallResult (new CalleePayload (sizeOfImpl, Array.Empty<Instruction> ()))?.Instruction;
		}
 
		static Instruction? EvaluateIntrinsicCall (MethodReference method, Instruction[] arguments)
		{
			//
			// In theory any pure method could be executed via reflection but
			// that would require loading all code path dependencies.
			// For now we handle only few methods that help with core framework trimming
			//
			object? left, right;
			if (method.DeclaringType.MetadataType == MetadataType.String) {
				switch (method.Name) {
				case "op_Equality":
				case "op_Inequality":
				case "Concat":
					if (arguments.Length != 2)
						return null;
 
					if (!GetConstantValue (arguments[0], out left) ||
						!GetConstantValue (arguments[1], out right))
						return null;
 
					if (left is string sleft && right is string sright) {
						if (method.Name.Length == 6) // Concat case
							return Instruction.Create (OpCodes.Ldstr, string.Concat (sleft, sright));
 
						bool result = method.Name.Length == 11 ? sleft == sright : sleft != sright;
						return Instruction.Create (OpCodes.Ldc_I4, result ? 1 : 0); // op_Equality / op_Inequality
					}
 
					break;
				}
			}
 
			return null;
		}
 
		static Instruction[]? GetArgumentsOnStack (MethodDefinition method, Collection<Instruction> instructions, int index)
		{
			if (!method.HasMetadataParameters ())
				return Array.Empty<Instruction> ();
 
			Instruction[]? result = null;
			for (int i = method.GetMetadataParametersCount (), pos = 0; i != 0; --i, ++pos) {
				Instruction instr = instructions[index - i];
				if (!IsConstantValue (instr))
					return null;
 
				result ??= new Instruction[method.GetMetadataParametersCount ()];
 
				result[pos] = instr;
			}
 
			if (result != null && HasJumpIntoTargetRange (instructions, index - method.GetMetadataParametersCount () + 1, index))
				return null;
 
			return result;
 
			static bool IsConstantValue (Instruction instr)
			{
				switch (instr.OpCode.Code) {
				case Code.Ldc_I4_0:
				case Code.Ldc_I4_1:
				case Code.Ldc_I4_2:
				case Code.Ldc_I4_3:
				case Code.Ldc_I4_4:
				case Code.Ldc_I4_5:
				case Code.Ldc_I4_6:
				case Code.Ldc_I4_7:
				case Code.Ldc_I4_8:
				case Code.Ldc_I4:
				case Code.Ldc_I4_S:
				case Code.Ldc_I4_M1:
				case Code.Ldc_I8:
				case Code.Ldc_R4:
				case Code.Ldc_R8:
				case Code.Ldnull:
				case Code.Ldstr:
					return true;
				}
 
				return false;
			}
		}
 
		static bool GetConstantValue (Instruction instruction, out object? value)
		{
			switch (instruction.OpCode.Code) {
			case Code.Ldc_I4_0:
				value = 0;
				return true;
			case Code.Ldc_I4_1:
				value = 1;
				return true;
			case Code.Ldc_I4_2:
				value = 2;
				return true;
			case Code.Ldc_I4_3:
				value = 3;
				return true;
			case Code.Ldc_I4_4:
				value = 4;
				return true;
			case Code.Ldc_I4_5:
				value = 5;
				return true;
			case Code.Ldc_I4_6:
				value = 6;
				return true;
			case Code.Ldc_I4_7:
				value = 7;
				return true;
			case Code.Ldc_I4_8:
				value = 8;
				return true;
			case Code.Ldc_I4_M1:
				value = -1;
				return true;
			case Code.Ldc_I4:
				value = (int) instruction.Operand;
				return true;
			case Code.Ldc_I4_S:
				value = (int) (sbyte) instruction.Operand;
				return true;
			case Code.Ldc_I8:
				value = (long) instruction.Operand;
				return true;
			case Code.Ldstr:
				value = (string) instruction.Operand;
				return true;
			case Code.Ldnull:
				value = null;
				return true;
			default:
				value = null;
				return false;
			}
		}
 
		static MethodDefinition? FindSizeMethod (TypeDefinition? type)
		{
			if (type == null)
				return null;
 
			return type.Methods.First (l => !l.HasMetadataParameters () && l.IsStatic && l.Name == "get_Size");
		}
 
		readonly struct CallInliner
		{
			readonly MethodBody body;
			readonly UnreachableBlocksOptimizer optimizer;
 
			public CallInliner (MethodBody body, UnreachableBlocksOptimizer optimizer)
			{
				this.body = body;
				this.optimizer = optimizer;
			}
 
			public bool RewriteBody ()
			{
				bool changed = false;
				LinkerILProcessor processor = body.GetLinkerILProcessor ();
#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
				Collection<Instruction> instrs = body.Instructions;
#pragma warning restore RS0030
 
				for (int i = 0; i < instrs.Count; ++i) {
					Instruction instr = instrs[i];
					switch (instr.OpCode.Code) {
 
					case Code.Call:
					case Code.Callvirt:
						MethodDefinition? md = optimizer._context.TryResolve ((MethodReference) instr.Operand);
						if (md == null)
							continue;
 
						if (md.IsVirtual)
							continue;
 
						if (md.CallingConvention == MethodCallingConvention.VarArg)
							break;
 
						if (md.NoInlining)
							break;
 
						var cpl = new CalleePayload (md, GetArgumentsOnStack (md, instrs, i));
						MethodResult? call_result = optimizer.TryGetMethodCallResult (cpl);
						if (call_result is not MethodResult result)
							break;
 
						if (!result.IsSideEffectFree) {
							optimizer._context.LogMessage ($"Cannot inline constant result of '{md.GetDisplayName ()}' call due to presence of side effects");
							break;
						}
 
						if (!md.IsStatic) {
							if (!md.HasMetadataParameters () && CanInlineInstanceCall (instrs, i)) {
								processor.Replace (i - 1, Instruction.Create (OpCodes.Nop));
								processor.Replace (i, result.GetPrototype ()!);
								changed = true;
							}
 
							continue;
						}
 
						if (md.HasMetadataParameters ()) {
							if (!IsCalledWithoutSideEffects (md, instrs, i))
								continue;
 
							for (int p = 1; p <= md.GetMetadataParametersCount (); ++p) {
								processor.Replace (i - p, Instruction.Create (OpCodes.Nop));
							}
						}
 
						processor.Replace (i, result.GetPrototype ());
						changed = true;
						continue;
 
					case Code.Sizeof:
						var operand = (TypeReference) instr.Operand;
						Instruction? value = optimizer.GetSizeOfResult (operand);
						if (value != null) {
							processor.Replace (i, value.GetPrototype ());
							changed = true;
						}
 
						continue;
					}
				}
 
				return changed;
			}
 
			bool CanInlineInstanceCall (Collection<Instruction> instructions, int index)
			{
				//
				// Instance methods called on `this` have no side-effects
				//
				if (instructions[index - 1].OpCode.Code == Code.Ldarg_0)
					return !body.Method.IsStatic;
 
				// More cases can be added later
				return false;
			}
 
			static bool IsCalledWithoutSideEffects (MethodDefinition method, Collection<Instruction> instructions, int index)
			{
				for (int i = 1; i <= method.GetMetadataParametersCount (); ++i) {
					if (!IsSideEffectFreeLoad (instructions[index - i]))
						return false;
				}
 
				return true;
			}
		}
 
		struct BodyReducer
		{
			readonly LinkContext context;
			Dictionary<Instruction, int>? mapping;
 
			//
			// Sorted list of body instruction indexes which were
			// replaced pass-through nop
			//
			List<int>? conditionInstrsToRemove;
 
			//
			// Sorted list of body instruction indexes which were
			// set to be replaced with different intstruction
			//
			List<(int, Instruction)>? conditionInstrsToReplace;
 
			public BodyReducer (MethodBody body, LinkContext context)
			{
				Body = body;
				this.context = context;
 
				FoldedInstructions = null;
				mapping = null;
				conditionInstrsToRemove = null;
				conditionInstrsToReplace = null;
				InstructionsReplaced = 0;
			}
 
			public MethodBody Body { get; }
 
#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
			Collection<Instruction> Instructions => Body.Instructions;
			Collection<ExceptionHandler> ExceptionHandlers => Body.ExceptionHandlers;
#pragma warning restore RS0030
 
			public int InstructionsReplaced { get; set; }
 
			Collection<Instruction>? FoldedInstructions { get; set; }
 
			[MemberNotNull (nameof(FoldedInstructions))]
			[MemberNotNull (nameof(mapping))]
			void InitializeFoldedInstruction ()
			{
				FoldedInstructions = new Collection<Instruction> (Instructions);
				mapping = new Dictionary<Instruction, int> ();
			}
 
			public void Rewrite (int index, Instruction newInstruction)
			{
				if (FoldedInstructions == null)
					InitializeFoldedInstruction ();
 
				Debug.Assert (mapping != null);
 
				// Tracks mapping for replaced instructions for easier
				// branch targets resolution later
				mapping[Instructions[index]] = index;
 
				FoldedInstructions[index] = newInstruction;
			}
 
			void RewriteCondition (int index, Instruction instr, int operand)
			{
				switch (instr.OpCode.Code) {
				case Code.Brfalse:
				case Code.Brfalse_S:
					if (operand == 0) {
						Rewrite (index, Instruction.Create (OpCodes.Br, (Instruction) instr.Operand));
					} else {
						RewriteConditionToNop (index);
					}
 
					break;
				case Code.Brtrue:
				case Code.Brtrue_S:
					if (operand != 0) {
						Rewrite (index, Instruction.Create (OpCodes.Br, (Instruction) instr.Operand));
					} else {
						RewriteConditionToNop (index);
					}
 
					break;
 
				case Code.Switch:
					var targets = (Instruction[]) instr.Operand;
					if (operand < targets.Length) {
						// It does not need to be conditional but existing logic in BodySweeper would
						// need to be updated to deal with 1->2 instruction replacement
						RewriteConditionTo (index, Instruction.Create (operand == 0 ? OpCodes.Brfalse : OpCodes.Brtrue, targets[operand]));
						Rewrite (index, Instruction.Create (OpCodes.Br, targets[operand]));
					} else {
						RewriteConditionToNop (index);
					}
 
					break;
				}
			}
 
			void RewriteConditionToNop (int index)
			{
				conditionInstrsToRemove ??= new List<int> ();
 
				conditionInstrsToRemove.Add (index);
				RewriteToNop (index);
			}
 
			void RewriteConditionTo (int index, Instruction instruction)
			{
				conditionInstrsToReplace ??= new List<(int, Instruction)> ();
 
				conditionInstrsToReplace.Add ((index, instruction));
			}
 
			public void RewriteToNop (int index, int stackDepth)
			{
				if (FoldedInstructions == null)
					InitializeFoldedInstruction ();
 
				int start_index;
				for (start_index = index; start_index >= 0 && stackDepth > 0; --start_index) {
					stackDepth -= GetStackBehaviourDelta (FoldedInstructions[start_index], out bool undefined);
					if (undefined)
						return;
				}
 
				if (stackDepth != 0) {
					Debug.Fail ("Invalid IL?");
					return;
				}
 
				while (start_index != index)
					RewriteToNop (++start_index);
			}
 
			static int GetStackBehaviourDelta (Instruction instruction, out bool unknown)
			{
				int delta = 0;
				unknown = false;
				switch (instruction.OpCode.StackBehaviourPop) {
				case StackBehaviour.Pop0:
					break;
				case StackBehaviour.Pop1:
				case StackBehaviour.Popref:
				case StackBehaviour.Popi:
					--delta;
					break;
				case StackBehaviour.Pop1_pop1:
				case StackBehaviour.Popi_pop1:
				case StackBehaviour.Popi_popi:
				case StackBehaviour.Popi_popi8:
				case StackBehaviour.Popi_popr4:
				case StackBehaviour.Popi_popr8:
				case StackBehaviour.Popref_pop1:
				case StackBehaviour.Popref_popi:
					delta -= 2;
					break;
				case StackBehaviour.Popi_popi_popi:
				case StackBehaviour.Popref_popi_popi:
				case StackBehaviour.Popref_popi_popi8:
				case StackBehaviour.Popref_popi_popr4:
				case StackBehaviour.Popref_popi_popr8:
				case StackBehaviour.Popref_popi_popref:
					delta -= 3;
					break;
				case StackBehaviour.Varpop:
					if (instruction.Operand is IMethodSignature ms) {
						if (ms.HasThis && instruction.OpCode != OpCodes.Newobj)
							--delta;
 
						delta -= ms.Parameters.Count;
						break;
					}
 
					if (instruction.OpCode == OpCodes.Ret) {
						unknown = true;
						return 0;
					}
 
					Debug.Fail (instruction.Operand?.ToString ());
					unknown = true;
					return 0;
				default:
					Debug.Fail (instruction.OpCode.StackBehaviourPop.ToString ());
					unknown = true;
					return 0;
				}
 
				switch (instruction.OpCode.StackBehaviourPush) {
				case StackBehaviour.Push0:
					break;
				case StackBehaviour.Push1:
				case StackBehaviour.Pushi:
				case StackBehaviour.Pushi8:
				case StackBehaviour.Pushr4:
				case StackBehaviour.Pushr8:
				case StackBehaviour.Pushref:
					++delta;
					break;
				case StackBehaviour.Push1_push1:
					delta += 2;
					break;
				case StackBehaviour.Varpush:
					if (instruction.Operand is IMethodSignature ms) {
						if (ms.ReturnType.MetadataType != MetadataType.Void)
							++delta;
 
						break;
					}
 
					Debug.Fail (instruction.Operand?.ToString ());
					unknown = true;
					return 0;
				default:
					Debug.Fail (instruction.OpCode.StackBehaviourPush.ToString ());
					unknown = true;
					return 0;
				}
 
				return delta;
			}
 
			void RewriteToNop (int index)
			{
				Rewrite (index, Instruction.Create (OpCodes.Nop));
			}
 
			public bool RewriteBody ()
			{
				if (FoldedInstructions == null)
					InitializeFoldedInstruction ();
 
				if (!RemoveConditions ())
					return false;
 
				BitArray reachableInstrs = GetReachableInstructionsMap (out var unreachableEH);
				if (reachableInstrs == null)
					return false;
 
				var bodySweeper = new BodySweeper (Body, reachableInstrs, unreachableEH, context);
				bodySweeper.Initialize ();
 
				bodySweeper.Process (conditionInstrsToRemove, conditionInstrsToReplace, out var nopInstructions);
				InstructionsReplaced = bodySweeper.InstructionsReplaced;
				if (InstructionsReplaced == 0)
					return false;
 
				reachableInstrs = GetReachableInstructionsMap (out _);
				if (reachableInstrs != null)
					RemoveUnreachableInstructions (reachableInstrs);
 
				if (nopInstructions != null) {
					LinkerILProcessor processor = Body.GetLinkerILProcessor ();
 
					foreach (var instr in nopInstructions)
						processor.Remove (instr);
				}
 
				return true;
			}
 
			public bool ApplyTemporaryInlining (in UnreachableBlocksOptimizer optimizer)
			{
				bool changed = false;
				var instructions = Instructions;
				Instruction? targetResult;
 
				for (int i = 0; i < instructions.Count; ++i) {
					var instr = instructions[i];
					switch (instr.OpCode.Code) {
 
					case Code.Call:
					case Code.Callvirt:
						var md = context.TryResolve ((MethodReference) instr.Operand);
						if (md == null)
							break;
 
						// Not supported
						if (md.IsVirtual || md.CallingConvention == MethodCallingConvention.VarArg)
							break;
 
						Instruction[]? args = GetArgumentsOnStack (md, FoldedInstructions ?? instructions, i);
						targetResult = args?.Length > 0 && md.IsStatic ? EvaluateIntrinsicCall (md, args) : null;
 
						targetResult ??= optimizer.TryGetMethodCallResult (new CalleePayload (md, args))?.Instruction;
 
						if (targetResult == null)
							break;
 
						//
						// Do simple arguments stack removal by replacing argument expressions with nops. For cases
						// that require full stack understanding the logic won't work and will leave more opcodes
						// on the stack and constant won't be propagated
						//
						int depth = args?.Length ?? 0;
						if (!md.IsStatic)
							++depth;
 
						if (depth != 0)
							RewriteToNop (i - 1, depth);
 
						Rewrite (i, targetResult);
						changed = true;
						break;
 
					case Code.Ldsfld:
						var ftarget = (FieldReference) instr.Operand;
						var field = context.TryResolve (ftarget);
						if (field == null)
							break;
 
						if (context.Annotations.TryGetFieldUserValue (field, out object? value)) {
							targetResult = CodeRewriterStep.CreateConstantResultInstruction (context, field.FieldType, value);
							if (targetResult == null)
								break;
							Rewrite (i, targetResult);
							changed = true;
						}
						break;
 
					case Code.Sizeof:
						var operand = (TypeReference) instr.Operand;
						targetResult = optimizer.GetSizeOfResult (operand);
						if (targetResult != null) {
							Rewrite (i, targetResult);
							changed = true;
						}
						break;
					}
				}
 
				return changed;
			}
 
			static bool IsConditionalBranch (OpCode opCode)
			=> opCode.Code is Code.Brfalse or Code.Brfalse_S or Code.Brtrue or Code.Brtrue_S;
 
			void RemoveUnreachableInstructions (BitArray reachable)
			{
				LinkerILProcessor processor = Body.GetLinkerILProcessor ();
 
				int removed = 0;
				for (int i = 0; i < reachable.Count; ++i) {
					if (reachable[i])
						continue;
 
					int index = i - removed;
					// If we intend to remove the last instruction we replaced it with "ret" above (not "nop")
					// but we can't get rid of it completely because it may happen that the last kept instruction
					// is a conditional branch - in which case to keep the IL valid, there has to be something after
					// the conditional branch instruction (the else branch). So if that's the case
					// inject "ldnull; throw;" at the end - this branch should never be reachable and it's always valid
					// (ret may need to return a value of the right type if the method has a return value which is complicated
					// to construct out of nothing).
					if (index == Instructions.Count - 1 && Instructions[index].OpCode == OpCodes.Ret &&
						index > 0 && IsConditionalBranch (Instructions[index - 1].OpCode)) {
						processor.Replace (index, Instruction.Create (OpCodes.Ldnull));
						processor.InsertAfter (Instructions[index], Instruction.Create (OpCodes.Throw));
					} else {
						processor.RemoveAt (index);
						++removed;
					}
				}
			}
 
			bool RemoveConditions ()
			{
				Debug.Assert (FoldedInstructions != null);
				bool changed = false;
				object? left, right;
 
				//
				// Finds any branchable instruction and checks if the operand or operands
				// can be evaluated as constant result.
				//
				// The logic does not remove any instructions but replaces them with nops for
				// easier processing later (makes the mapping straigh-forward).
				//
				for (int i = 0; i < FoldedInstructions.Count; ++i) {
					var instr = FoldedInstructions[i];
					var opcode = instr.OpCode;
 
					if (opcode.FlowControl == FlowControl.Cond_Branch) {
						if (opcode.StackBehaviourPop == StackBehaviour.Pop1_pop1) {
							if (!GetOperandsConstantValues (i, out left, out right))
								continue;
 
							if (left is int lint && right is int rint) {
								if (IsJumpTargetRange (i - 1, i))
									continue;
 
								RewriteToNop (i - 2);
								RewriteToNop (i - 1);
 
								if (IsComparisonAlwaysTrue (opcode, lint, rint)) {
									Rewrite (i, Instruction.Create (OpCodes.Br, (Instruction) instr.Operand));
								} else {
									RewriteConditionToNop (i);
								}
 
								changed = true;
								continue;
							}
 
							continue;
						}
 
						if (opcode.StackBehaviourPop == StackBehaviour.Popi) {
							if (i > 0 && GetConstantValue (FoldedInstructions[i - 1], out var operand)) {
								if (operand is int opint) {
									if (IsJumpTargetRange (i, i))
										continue;
 
									RewriteToNop (i - 1);
									RewriteCondition (i, instr, opint);
 
									changed = true;
									continue;
								}
 
								if (operand is null && (opcode.Code == Code.Brfalse || opcode.Code == Code.Brfalse_S)) {
									if (IsJumpTargetRange (i, i))
										continue;
 
									RewriteToNop (i - 1);
									Rewrite (i, Instruction.Create (OpCodes.Br, (Instruction) instr.Operand));
									changed = true;
									continue;
								}
							}
 
							// Common pattern generated by C# compiler in debug mode
							if (i >= 3 && GetConstantValue (FoldedInstructions[i - 3], out operand) && operand is int opint2 && IsPairedStlocLdloc (FoldedInstructions[i - 2], FoldedInstructions[i - 1])) {
								if (IsJumpTargetRange (i - 2, i))
									continue;
 
								RewriteToNop (i - 3);
								RewriteToNop (i - 2);
								RewriteToNop (i - 1);
								RewriteCondition (i, instr, opint2);
 
								changed = true;
								continue;
							}
 
							// Pattern for non-zero based switch with constant input
							if (i >= 5 && opcode == OpCodes.Switch && GetConstantValue (FoldedInstructions[i - 5], out operand) && operand is int opint3 && IsPairedStlocLdloc (FoldedInstructions[i - 4], FoldedInstructions[i - 3])) {
								if (IsJumpTargetRange (i - 4, i))
									continue;
 
								if (!GetConstantValue (FoldedInstructions[i - 2], out operand) || operand is not int offset)
									continue;
 
								if (FoldedInstructions[i - 1].OpCode != OpCodes.Sub)
									continue;
 
								RewriteToNop (i - 5);
								RewriteToNop (i - 4);
								RewriteToNop (i - 3);
								RewriteCondition (i, instr, opint3 - offset);
 
								changed = true;
								continue;
							}
 
							continue;
						}
 
						throw new NotImplementedException ();
					}
 
					// Mode special for csc in debug mode
					switch (instr.OpCode.Code) {
					case Code.Ceq:
					case Code.Clt:
					case Code.Cgt:
						if (!GetOperandsConstantValues (i, out left, out right))
							continue;
 
						if (left is int lint && right is int rint) {
							if (IsJumpTargetRange (i - 1, i))
								continue;
 
							RewriteToNop (i - 2);
							RewriteToNop (i - 1);
 
							if (IsComparisonAlwaysTrue (instr.OpCode, lint, rint)) {
								Rewrite (i, Instruction.Create (OpCodes.Ldc_I4_1));
							} else {
								Rewrite (i, Instruction.Create (OpCodes.Ldc_I4_0));
							}
 
							changed = true;
						}
 
						break;
 
					case Code.Cgt_Un:
						if (!GetOperandsConstantValues (i, out left, out right))
							continue;
 
						if (IsJumpTargetRange (i - 1, i))
							continue;
 
						if (left == null && right == null) {
							Rewrite (i, Instruction.Create (OpCodes.Ldc_I4_0));
						}
 
						changed = true;
						break;
					}
				}
 
				return changed;
			}
 
			BitArray GetReachableInstructionsMap (out List<ExceptionHandler>? unreachableHandlers)
			{
				Debug.Assert (FoldedInstructions != null);
				unreachableHandlers = null;
				var reachable = new BitArray (FoldedInstructions.Count);
 
				Stack<int>? condBranches = null;
				bool exceptionHandlersChecked = !Body.HasExceptionHandlers;
				Instruction target;
				int i = 0;
				while (true) {
					while (i < FoldedInstructions.Count) {
						if (reachable[i])
							break;
 
						reachable[i] = true;
						var instr = FoldedInstructions[i++];
 
						switch (instr.OpCode.FlowControl) {
						case FlowControl.Branch:
							target = (Instruction) instr.Operand;
							i = GetInstructionIndex (target);
							continue;
 
						case FlowControl.Cond_Branch:
							condBranches ??= new Stack<int> ();
 
							switch (instr.Operand) {
							case Instruction starget:
								condBranches.Push (GetInstructionIndex (starget));
								continue;
							case Instruction[] mtargets:
								foreach (var t in mtargets)
									condBranches.Push (GetInstructionIndex (t));
								continue;
							default:
								throw new NotImplementedException ();
							}
 
						case FlowControl.Next:
						case FlowControl.Call:
						case FlowControl.Meta:
							continue;
 
						case FlowControl.Return:
						case FlowControl.Throw:
							break;
 
						default:
							throw new NotImplementedException ();
						}
 
						break;
					}
 
					if (condBranches?.Count > 0) {
						i = condBranches.Pop ();
						continue;
					}
 
					if (!exceptionHandlersChecked) {
						exceptionHandlersChecked = true;
 
						var instrs = Instructions;
						foreach (var handler in ExceptionHandlers) {
							int start = instrs.IndexOf (handler.TryStart);
							int end = instrs.IndexOf (handler.TryEnd) - 1;
 
							if (!HasAnyBitSet (reachable, start, end)) {
								unreachableHandlers ??= new List<ExceptionHandler> ();
 
								unreachableHandlers.Add (handler);
								continue;
							}
 
							condBranches ??= new Stack<int> ();
 
							condBranches.Push (GetInstructionIndex (handler.HandlerStart));
							if (handler.FilterStart != null) {
								condBranches.Push (GetInstructionIndex (handler.FilterStart));
								int filterEnd = GetInstructionIndex (handler.HandlerStart) - 1;
								if (filterEnd >= 0 && FoldedInstructions[filterEnd].OpCode == OpCodes.Endfilter) {
									// The endfilter instruction must be at the end of each filter block, even if it's not reachable:
									//
									// ECMA 335
									// I.12.4.2.8.2.5 endfilter:
									// 1.Shall appear as the lexically last instruction in the filter.
									// [Note: The endfilter is required even if no control - flow path reaches it.This can happen if, for
									// example, the filter does a throw.end note]
									reachable[filterEnd] = true;
								}
							}
						}
 
						if (condBranches?.Count > 0) {
							i = condBranches.Pop ();
							continue;
						}
					}
 
					return reachable;
				}
			}
 
			static bool HasAnyBitSet (BitArray bitArray, int startIndex, int endIndex)
			{
				for (int i = startIndex; i <= endIndex; ++i) {
					if (bitArray[i])
						return true;
				}
 
				return false;
			}
 
			//
			// Returns index of instruction in folded instruction body
			//
			int GetInstructionIndex (Instruction instruction)
			{
				Debug.Assert (FoldedInstructions != null && mapping != null);
				if (mapping.TryGetValue (instruction, out int idx))
					return idx;
 
				idx = FoldedInstructions.IndexOf (instruction);
				Debug.Assert (idx >= 0);
				return idx;
			}
 
			int? TryGetInstructionIndex (Instruction instruction)
			{
				Debug.Assert (mapping != null);
				if (mapping.TryGetValue (instruction, out int idx))
					return idx;
 
				return null;
			}
 
			bool GetOperandsConstantValues (int index, out object? left, out object? right)
			{
				Debug.Assert (FoldedInstructions != null);
				left = default;
				right = default;
 
				if (index < 2)
					return false;
 
				return GetConstantValue (FoldedInstructions[index - 2], out left) &&
					GetConstantValue (FoldedInstructions[index - 1], out right);
			}
 
			static bool IsPairedStlocLdloc (Instruction first, Instruction second)
			{
				switch (first.OpCode.Code) {
				case Code.Stloc_0:
					return second.OpCode.Code == Code.Ldloc_0;
				case Code.Stloc_1:
					return second.OpCode.Code == Code.Ldloc_1;
				case Code.Stloc_2:
					return second.OpCode.Code == Code.Ldloc_2;
				case Code.Stloc_3:
					return second.OpCode.Code == Code.Ldloc_3;
				case Code.Stloc_S:
				case Code.Stloc:
					if (second.OpCode.Code == Code.Ldloc_S || second.OpCode.Code == Code.Ldloc)
						return ((VariableDefinition) first.Operand).Index == ((VariableDefinition) second.Operand).Index;
 
					break;
				}
 
				return false;
			}
 
			bool IsJumpTargetRange (int firstInstr, int lastInstr)
			{
				Debug.Assert (FoldedInstructions != null);
				return HasJumpIntoTargetRange (FoldedInstructions, firstInstr, lastInstr, TryGetInstructionIndex);
			}
		}
 
		struct BodySweeper
		{
			readonly MethodBody body;
#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
			Collection<Instruction> Instructions => body.Instructions;
			Collection<VariableDefinition> Variables => body.Variables;
			Collection<ExceptionHandler> ExceptionHandlers => body.ExceptionHandlers;
#pragma warning restore RS0030
			readonly BitArray reachable;
			readonly List<ExceptionHandler>? unreachableExceptionHandlers;
			readonly LinkContext context;
			LinkerILProcessor? ilprocessor;
			LinkerILProcessor ILProcessor {
				get {
					Debug.Assert (ilprocessor != null);
					return ilprocessor;
				}
			}
 
			public BodySweeper (MethodBody body, BitArray reachable, List<ExceptionHandler>? unreachableEH, LinkContext context)
			{
				this.body = body;
				this.reachable = reachable;
				this.unreachableExceptionHandlers = unreachableEH;
				this.context = context;
 
				InstructionsReplaced = 0;
				ilprocessor = null;
			}
 
			public int InstructionsReplaced { get; set; }
 
			public void Initialize ()
			{
				var instrs = Instructions;
 
				//
				// Reusing same reachable map and altering it at indexes
				// which will remain same during replacement processing
				//
				for (int i = 0; i < instrs.Count; ++i) {
					if (reachable[i])
						continue;
 
					var instr = instrs[i];
					switch (instr.OpCode.Code) {
					case Code.Nop:
						reachable[i] = true;
						continue;
 
					case Code.Ret:
						if (i == instrs.Count - 1)
							reachable[i] = true;
 
						break;
					}
				}
 
				ilprocessor = body.GetLinkerILProcessor ();
			}
 
			public void Process (List<int>? conditionInstrsToRemove, List<(int, Instruction)>? conditionInstrsToReplace, out List<Instruction>? sentinelNops)
			{
				List<VariableDefinition>? removedVariablesReferences = null;
				var instrs = Instructions;
 
				//
				// Process list of conditional instructions that were set to be replaced and not removed
				//
				if (conditionInstrsToReplace != null) {
					foreach (var pair in conditionInstrsToReplace) {
						var instr = instrs[pair.Item1];
						switch (instr.OpCode.StackBehaviourPop) {
						case StackBehaviour.Popi:
							ILProcessor.Replace (pair.Item1, pair.Item2);
							InstructionsReplaced++;
							break;
						default:
							Debug.Fail ("not supported");
							break;
						}
					}
 
				}
 
				//
				// Initial pass which replaces unreachable instructions with nops or
				// ret to keep the body verifiable
				//
				for (int i = 0; i < instrs.Count; ++i) {
					if (reachable[i])
						continue;
 
					var instr = instrs[i];
 
					Instruction newInstr;
					if (i == instrs.Count - 1) {
						newInstr = Instruction.Create (OpCodes.Ret);
					} else {
						newInstr = Instruction.Create (OpCodes.Nop);
					}
 
					ILProcessor.Replace (i, newInstr);
					InstructionsReplaced++;
 
					VariableDefinition? variable = GetVariableReference (instr);
					if (variable != null) {
						removedVariablesReferences ??= new List<VariableDefinition> ();
						if (!removedVariablesReferences.Contains (variable))
							removedVariablesReferences.Add (variable);
					}
				}
 
				CleanExceptionHandlers ();
 
				sentinelNops = null;
 
				//
				// Process list of conditional jump which should be removed. They cannot be
				// replaced with nops as they alter the stack
				//
				if (conditionInstrsToRemove != null) {
					int bodyExpansion = 0;
 
					foreach (int instrIndex in conditionInstrsToRemove) {
						var index = instrIndex + bodyExpansion;
						var instr = instrs[index];
 
						switch (instr.OpCode.StackBehaviourPop) {
						case StackBehaviour.Pop1_pop1:
 
							InstructionsReplaced += 2;
 
							//
							// One of the operands is most likely constant and could just be removed instead of additional pop
							//
							if (index > 0 && IsSideEffectFreeLoad (instrs[index - 1])) {
								var nop = Instruction.Create (OpCodes.Nop);
 
								sentinelNops ??= new List<Instruction> ();
								sentinelNops.Add (nop);
 
								ILProcessor.Replace (index - 1, Instruction.Create (OpCodes.Pop));
								ILProcessor.Replace (index, nop);
							} else {
								var pop = Instruction.Create (OpCodes.Pop);
								ILProcessor.Replace (index, pop);
								ILProcessor.InsertAfter (pop, Instruction.Create (OpCodes.Pop));
 
								//
								// conditionInstrsToRemove is always sorted and instead of
								// increasing remaining indexes we introduce index delta value
								//
								bodyExpansion++;
							}
							break;
						case StackBehaviour.Popi:
							ILProcessor.Replace (index, Instruction.Create (OpCodes.Pop));
							InstructionsReplaced++;
							break;
						}
					}
				}
 
				//
				// Replacing instructions with nops can make local variables unused. Process them
				// as the last step to reduce more type dependencies
				//
				if (removedVariablesReferences != null) {
					CleanRemovedVariables (removedVariablesReferences);
				}
			}
 
			void CleanRemovedVariables (List<VariableDefinition> variables)
			{
				foreach (var instr in Instructions) {
					VariableDefinition? variable = GetVariableReference (instr);
					if (variable == null)
						continue;
 
					if (!variables.Remove (variable))
						continue;
 
					if (variables.Count == 0)
						return;
				}
 
				variables.Sort ((a, b) => b.Index.CompareTo (a.Index));
				var body_variables = Variables;
 
				foreach (var variable in variables) {
					var index = body_variables.IndexOf (variable);
 
					//
					// Remove variable only if it's the last one. Instead of
					// re-indexing all variables change it to System.Object,
					// which is enough to drop the dependency
					//
					if (index == body_variables.Count - 1) {
						body_variables.RemoveAt (index);
					} else {
						var objectType = BCL.FindPredefinedType (WellKnownType.System_Object, context);
						body_variables[index].VariableType = objectType ?? throw new NotSupportedException ("Missing predefined 'System.Object' type");
					}
				}
			}
 
			void CleanExceptionHandlers ()
			{
				if (unreachableExceptionHandlers == null)
					return;
 
				foreach (var eh in unreachableExceptionHandlers)
					ExceptionHandlers.Remove (eh);
			}
 
			VariableDefinition? GetVariableReference (Instruction instruction)
			{
				switch (instruction.OpCode.Code) {
				case Code.Stloc_0:
				case Code.Ldloc_0:
					return Variables[0];
				case Code.Stloc_1:
				case Code.Ldloc_1:
					return Variables[1];
				case Code.Stloc_2:
				case Code.Ldloc_2:
					return Variables[2];
				case Code.Stloc_3:
				case Code.Ldloc_3:
					return Variables[3];
				}
 
				if (instruction.Operand is VariableReference vr)
					return vr.Resolve ();
 
				return null;
			}
		}
 
		struct ConstantExpressionMethodAnalyzer
		{
			readonly LinkContext context;
			readonly UnreachableBlocksOptimizer optimizer;
 
			Stack<Instruction>? stack_instr;
			Dictionary<int, Instruction>? locals;
 
			public ConstantExpressionMethodAnalyzer (UnreachableBlocksOptimizer optimizer)
			{
				this.optimizer = optimizer;
				this.context = optimizer._context;
				stack_instr = null;
				locals = null;
				Result = null;
				SideEffectFreeResult = true;
			}
 
			//
			// Single expression that is representing the evaluation result with the specific
			// callee arguments
			//
			public Instruction? Result { get; private set; }
 
			//
			// Returns true when the method evaluation with specific arguments does not cause
			// any observable side effect (e.g. possible NRE, field access, etc)
			//
			public bool SideEffectFreeResult { get; private set; }
 
			[MemberNotNullWhen (true, nameof(Result))]
			public bool Analyze (in CalleePayload callee, Stack<MethodDefinition> callStack)
			{
				MethodDefinition method = callee.Method;
				Instruction[]? arguments = callee.Arguments;
#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
				Collection<Instruction> instructions = callee.Method.Body.Instructions;
#pragma warning restore RS0030
				MethodBody body = method.Body;
 
				VariableReference vr;
				Instruction? jmpTarget = null;
				Instruction? linstr;
				object? left, right, operand;
 
				SideEffectFreeResult = !HasSideEffects (method);
 
				//
				// We could implement a full-blown interpreter here but for now, it handles
				// cases used in runtime libraries
				//
				for (int i = 0; i < instructions.Count; ++i) {
					var instr = instructions[i];
 
					if (jmpTarget != null) {
						//
						// Handles both backward and forward jumps
						//
						if (instr != jmpTarget)
							continue;
 
						jmpTarget = null;
					}
 
					switch (instr.OpCode.Code) {
					case Code.Nop:
					case Code.Volatile:
						continue;
					case Code.Pop:
						Debug.Assert (stack_instr != null, "invalid il?");
						stack_instr?.Pop ();
						continue;
 
					case Code.Br_S:
					case Code.Br:
						jmpTarget = (Instruction) instr.Operand;
						continue;
 
					case Code.Brfalse_S:
					case Code.Brfalse: {
							if (!GetOperandConstantValue (out operand))
								return false;
 
							if (operand is int oint) {
								if (oint == 0)
									jmpTarget = (Instruction) instr.Operand;
 
								continue;
							}
 
							return false;
						}
 
					case Code.Brtrue_S:
					case Code.Brtrue: {
							if (!GetOperandConstantValue (out operand))
								return false;
 
							if (operand is int oint) {
								if (oint != 0)
									jmpTarget = (Instruction) instr.Operand;
 
								continue;
							}
 
							return false;
						}
 
					case Code.Beq:
					case Code.Beq_S:
					case Code.Bne_Un:
					case Code.Bne_Un_S:
					case Code.Bge:
					case Code.Bge_S:
					case Code.Bge_Un:
					case Code.Bge_Un_S:
					case Code.Bgt:
					case Code.Bgt_S:
					case Code.Bgt_Un:
					case Code.Bgt_Un_S:
					case Code.Ble:
					case Code.Ble_S:
					case Code.Ble_Un:
					case Code.Ble_Un_S:
					case Code.Blt:
					case Code.Blt_S:
					case Code.Blt_Un:
					case Code.Blt_Un_S:
						if (EvaluateConditionalJump (instr, out jmpTarget))
							continue;
						return false;
 
					case Code.Ldc_I4:
					case Code.Ldc_I4_S:
					case Code.Ldc_I4_0:
					case Code.Ldc_I4_1:
					case Code.Ldc_I4_2:
					case Code.Ldc_I4_3:
					case Code.Ldc_I4_4:
					case Code.Ldc_I4_5:
					case Code.Ldc_I4_6:
					case Code.Ldc_I4_7:
					case Code.Ldc_I4_8:
					case Code.Ldc_I4_M1:
					case Code.Ldc_I8:
					case Code.Ldnull:
					case Code.Ldstr:
					case Code.Ldtoken:
						PushOnStack (instr);
						continue;
 
					case Code.Ldloc_0:
						linstr = GetLocalsValue (0, body);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
					case Code.Ldloc_1:
						linstr = GetLocalsValue (1, body);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
					case Code.Ldloc_2:
						linstr = GetLocalsValue (2, body);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
					case Code.Ldloc_3:
						linstr = GetLocalsValue (3, body);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
					case Code.Ldloc:
					case Code.Ldloc_S:
						vr = (VariableReference) instr.Operand;
						linstr = GetLocalsValue (vr.Index, body);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
					case Code.Stloc_0:
						StoreToLocals (0);
						continue;
					case Code.Stloc_1:
						StoreToLocals (1);
						continue;
					case Code.Stloc_2:
						StoreToLocals (2);
						continue;
					case Code.Stloc_3:
						StoreToLocals (3);
						continue;
					case Code.Stloc_S:
					case Code.Stloc:
						vr = (VariableReference) instr.Operand;
						StoreToLocals (vr.Index);
						continue;
 
					case Code.Ldarg_0:
						if (!method.IsStatic) {
							PushOnStack (instr);
							continue;
						}
 
						linstr = GetArgumentValue (arguments, 0);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
 
					case Code.Ldarg_1:
						if (!method.IsStatic)
							return false;
 
						linstr = GetArgumentValue (arguments, 1);
						if (linstr == null)
							return false;
 
						PushOnStack (linstr);
						continue;
 
					case Code.Ldsfld: {
							var ftarget = (FieldReference) instr.Operand;
							FieldDefinition? field = context.TryResolve (ftarget);
							if (field == null)
								return false;
 
							if (context.Annotations.TryGetFieldUserValue (field, out object? value)) {
								linstr = CodeRewriterStep.CreateConstantResultInstruction (context, field.FieldType, value);
								if (linstr == null)
									return false;
							} else {
								SideEffectFreeResult = false;
								linstr = instr;
							}
 
							PushOnStack (linstr);
							continue;
						}
 
					case Code.Ceq: {
							if (!GetOperandsConstantValues (out right, out left))
								return false;
 
							if (left is int lint && right is int rint) {
								PushOnStack (Instruction.Create (OpCodes.Ldc_I4, lint == rint ? 1 : 0));
								continue;
							}
 
							if (left is long llong && right is long rlong) {
								PushOnStack (Instruction.Create (OpCodes.Ldc_I4, llong == rlong ? 1 : 0));
								continue;
							}
 
							return false;
						}
 
					case Code.Conv_I8: {
							if (!GetOperandConstantValue (out operand))
								return false;
 
							if (operand is int oint) {
								PushOnStack (Instruction.Create (OpCodes.Ldc_I8, (long) oint));
								continue;
							}
 
							// TODO: Handle more types
							return false;
						}
 
					case Code.Call:
					case Code.Callvirt: {
							MethodReference mr = (MethodReference) instr.Operand;
							MethodDefinition? md = optimizer._context.TryResolve (mr);
							if (md == null || md == method)
								return false;
 
							if (md.IsVirtual)
								return false;
 
							Instruction[]? args;
							if (!md.HasMetadataParameters ()) {
								args = Array.Empty<Instruction> ();
							} else {
								//
								// Don't need to check for ref/out because ldloca like instructions are not supported
								//
								args = GetArgumentsOnStack (md);
								if (args == null)
									return false;
							}
 
							if (md.ReturnType.MetadataType == MetadataType.Void) {
								// For now consider all void methods as side-effect causing
								SideEffectFreeResult = false;
								continue;
							}
 
							if (!md.IsStatic && !CanEvaluateInstanceMethodCall (method))
								return false;
 
							//
							// Evaluate known framework methods
							//
							if (args.Length > 0) {
								linstr = EvaluateIntrinsicCall (md, args);
								if (linstr != null) {
									PushOnStack (linstr);
									continue;
								}
							}
 
							//
							// Guard against stack overflow on recursive calls. This could be turned into
							// a warning if we check arguments too
							//
							if (callStack.Contains (md))
								return false;
 
							callStack.Push (method);
							MethodResult? call_result = optimizer.TryGetMethodCallResult (new CalleePayload (md, args), callStack);
							if (!callStack.TryPop (out _))
								return false;
 
							if (call_result is MethodResult result) {
								if (!result.IsSideEffectFree)
									SideEffectFreeResult = false;
 
								PushOnStack (result.Instruction);
								continue;
							}
 
							return false;
						}
 
					case Code.Sizeof: {
							var type = (TypeReference) instr.Operand;
							linstr = optimizer.GetSizeOfResult (type);
							if (linstr != null) {
								PushOnStack (linstr);
								continue;
							}
 
							return false;
						}
 
					case Code.Ret:
						if (ConvertStackToResult ())
							return true;
 
						break;
					}
 
					return false;
				}
 
				return false;
			}
 
			bool CanEvaluateInstanceMethodCall (MethodDefinition context)
			{
				if (stack_instr == null || !stack_instr.TryPop (out Instruction? instr))
					return false;
 
				switch (instr.OpCode.Code) {
				case Code.Ldarg_0:
					if (!context.IsStatic)
						return true;
 
					goto default;
				default:
					// We are not inlining hence can evaluate anything and decide later
					// how to handle sitation when the result is not deterministic
					SideEffectFreeResult = false;
					return true;
				}
			}
 
			bool EvaluateConditionalJump (Instruction instr, out Instruction? target)
			{
				if (!GetOperandsConstantValues (out object? right, out object? left)) {
					target = null;
					return false;
				}
 
				if (left is int lint && right is int rint) {
					if (IsComparisonAlwaysTrue (instr.OpCode, lint, rint))
						target = (Instruction) instr.Operand;
					else
						target = null;
 
					return true;
				}
 
				target = null;
				return false;
			}
 
			[MemberNotNullWhen (true, nameof(Result))]
			bool ConvertStackToResult ()
			{
				if (stack_instr == null)
					return false;
 
				if (stack_instr.Count != 1)
					return false;
 
				var instr = stack_instr.Pop ();
 
				switch (instr.OpCode.Code) {
				case Code.Ldc_I4_0:
				case Code.Ldc_I4_1:
				case Code.Ldc_I4_2:
				case Code.Ldc_I4_3:
				case Code.Ldc_I4_4:
				case Code.Ldc_I4_5:
				case Code.Ldc_I4_6:
				case Code.Ldc_I4_7:
				case Code.Ldc_I4_8:
				case Code.Ldc_I4:
				case Code.Ldc_I4_S:
				case Code.Ldc_I4_M1:
				case Code.Ldc_I8:
				case Code.Ldnull:
				case Code.Ldstr:
					Result = instr;
					return true;
				}
 
				return false;
			}
 
			static Instruction? GetArgumentValue (Instruction[]? arguments, int index)
			{
				if (arguments == null)
					return null;
 
				return index < arguments.Length ? arguments[index] : null;
			}
 
			Instruction[]? GetArgumentsOnStack (MethodDefinition method)
			{
				int length = method.GetMetadataParametersCount ();
				Debug.Assert (length != 0);
				if (stack_instr?.Count < length)
					return null;
 
				var result = new Instruction[length];
				while (length != 0)
					result[--length] = stack_instr!.Pop ();
 
				return result;
			}
 
			Instruction? GetLocalsValue (int index, MethodBody body)
			{
				if (locals != null && locals.TryGetValue (index, out Instruction? instruction))
					return instruction;
 
				if (!body.InitLocals)
					return null;
 
#pragma warning disable RS0030 // This optimizer is the reason for the banned API, so it needs to use the Cecil directly
				var variables = body.Variables;
#pragma warning restore RS0030
 
				// local variables don't need to be explicitly initialized
				return CodeRewriterStep.CreateConstantResultInstruction (context, variables[index].VariableType);
			}
 
			bool GetOperandConstantValue ([NotNullWhen (true)] out object? value)
			{
				if (stack_instr == null) {
					value = null;
					return false;
				}
 
				Instruction? instr;
				if (!stack_instr.TryPop (out instr)) {
					value = null;
					return false;
				}
 
				return GetConstantValue (instr, out value);
			}
 
			bool GetOperandsConstantValues ([NotNullWhen (true)] out object? left, [NotNullWhen (true)] out object? right)
			{
				if (stack_instr == null) {
					left = right = null;
					return false;
				}
 
				Instruction? instr;
				if (!stack_instr.TryPop (out instr)) {
					left = right = null;
					return false;
				}
 
				if (instr == null) {
					left = right = null;
					return false;
				}
 
				if (!GetConstantValue (instr, out left)) {
					left = right = null;
					return false;
				}
 
				if (!stack_instr.TryPop (out instr)) {
					left = right = null;
					return false;
				}
 
				if (instr is null) {
					left = right = null;
					return false;
				}
 
				return GetConstantValue (instr, out right);
			}
 
			void PushOnStack (Instruction instruction)
			{
				stack_instr ??= new Stack<Instruction> ();
 
				stack_instr.Push (instruction);
			}
 
			void StoreToLocals (int index)
			{
				locals ??= new Dictionary<int, Instruction> ();
 
				if (stack_instr == null)
					Debug.Fail ("Invalid IL?");
				locals[index] = stack_instr.Pop ();
			}
		}
 
		readonly record struct CalleePayload (MethodDefinition Method, Instruction[]? Arguments = null)
		{
			public bool HasUnknownArguments => Arguments is null;
		}
 
		readonly record struct MethodResult (Instruction Instruction, bool IsSideEffectFree)
		{
			public Instruction GetPrototype () => Instruction.GetPrototype ();
		}
	}
}