public static void EraseMethodInvocationFromInstructions(ILProcessor ilProcessor, Instruction callInstruction)
        {
            var argumentPushingInstructions = new List <Instruction>();
            int succesfullyEraseArguments   = 0;

            if (!(callInstruction.Operand is MethodReference methodReference))
            {
                return;
            }

            bool isMethodThatReturnsItsFirstArgument = !methodReference.HasThis && (methodReference.Parameters.FirstOrDefault()?.ParameterType.TypeReferenceEquals(methodReference.ReturnType) ?? false);

            var parametersCount = methodReference.Parameters.Count + (methodReference.HasThis ? 1 : 0);

            for (int i = 0; i != parametersCount; i++)
            {
                if (isMethodThatReturnsItsFirstArgument && i == 0)
                {
                    continue;
                }

                var instructionThatPushedArg = FindInstructionThatPushedArg(ilProcessor.Body.Method, i, callInstruction);
                if (instructionThatPushedArg == null)
                {
                    continue;
                }

                if (InstructionExtensions.IsInvocation(instructionThatPushedArg))
                {
                    continue;
                }

                var pushDelta = InstructionExtensions.GetPushDelta(instructionThatPushedArg);
                var popDelta  = InstructionExtensions.GetPopDelta(instructionThatPushedArg);

                if (pushDelta == 1 && popDelta == 0)
                {
                    argumentPushingInstructions.Add(instructionThatPushedArg);
                    succesfullyEraseArguments++;
                    continue;
                }

                if (pushDelta == 1 && popDelta == 1)
                {
                    var whoPushedThat = FindInstructionThatPushedArg(ilProcessor.Body.Method, 0, instructionThatPushedArg);
                    if (InstructionExtensions.GetPopDelta(whoPushedThat) == 0 && InstructionExtensions.GetPushDelta(whoPushedThat) == 1)
                    {
                        argumentPushingInstructions.Add(instructionThatPushedArg);
                        argumentPushingInstructions.Add(whoPushedThat);
                        succesfullyEraseArguments++;
                        continue;
                    }
                }
            }


            foreach (var i in argumentPushingInstructions)
            {
                i.MakeNOP();
            }

            //we're going to remove the invocation. While we do this we want to remain stack neutral. The stackbehaviour going in is that the jobdescription itself will be on the stack,
            //plus any arguments the method might have.  After the function, it will put the same jobdescription on the stack as the return value.
            //we're going to pop all the arguments, but leave the jobdescription itself on the stack, since that the behaviour of the original method.
            var parametersToErase = parametersCount - (isMethodThatReturnsItsFirstArgument ? 1 : 0);
            var popInstructions   = Enumerable.Repeat(Instruction.Create(OpCodes.Pop), parametersToErase - succesfullyEraseArguments);

            ilProcessor.InsertBefore(callInstruction, popInstructions);

            //instead of removing the call instruction, we'll replace it with a NOP opcode. This is safer as this instruction
            //might be the target of a branch instruction that we don't want to become invalid.
            callInstruction.MakeNOP();

            if (!methodReference.ReturnType.TypeReferenceEquals(methodReference.Module.TypeSystem.Void) && !isMethodThatReturnsItsFirstArgument)
            {
                callInstruction.OpCode = OpCodes.Ldnull;
            }
        }
        public static Instruction FindInstructionThatPushedArg(MethodDefinition containingMethod, int argNumber,
                                                               Instruction callInstructionsWhoseArgumentsWeWantToFind)
        {
            containingMethod.Body.EnsurePreviousAndNextAreSet();

            var cursor = callInstructionsWhoseArgumentsWeWantToFind.Previous;

            int stackSlotWhoseWriteWeAreLookingFor     = argNumber;
            int stackSlotWhereNextPushWouldBeWrittenTo = InstructionExtensions.GetPopDelta(callInstructionsWhoseArgumentsWeWantToFind);

            var seenInstructions = new HashSet <Instruction>()
            {
                callInstructionsWhoseArgumentsWeWantToFind, cursor
            };

            while (cursor != null)
            {
                var pushAmount = cursor.GetPushDelta();
                var popAmount  = cursor.GetPopDelta();

                var result = CecilHelpers.MatchesDelegateProducingPattern(containingMethod, cursor, CecilHelpers.DelegateProducingPattern.MatchSide.End);
                if (result != null)
                {
                    //so we are crawling backwards through isntructions.  if we find a "this is roslyn caching a delegate" sequence,
                    //we're going to pretend it is a single instruction, that pushes the delegate on the stack, and pops nothing.
                    cursor     = result.Instructions.First();
                    pushAmount = 1;
                    popAmount  = 0;
                }
                else if (cursor.IsBranch())
                {
                    var target = (Instruction)cursor.Operand;
                    if (!seenInstructions.Contains(target))
                    {
                        if (IsUnsupportedBranch(cursor))
                        {
                            UserError.DC0010(containingMethod, cursor).Throw();
                        }
                    }
                }


                for (int i = 0; i != pushAmount; i++)
                {
                    stackSlotWhereNextPushWouldBeWrittenTo--;
                    if (stackSlotWhereNextPushWouldBeWrittenTo == stackSlotWhoseWriteWeAreLookingFor)
                    {
                        return(cursor);
                    }
                }

                for (int i = 0; i != popAmount; i++)
                {
                    stackSlotWhereNextPushWouldBeWrittenTo++;
                }

                cursor = cursor.Previous;
                seenInstructions.Add(cursor);
            }

            return(null);
        }
        private void ApplyPostProcessingOnJobCode(MethodDefinition[] methodUsedByLambdaJobs,
                                                  LambdaParameterValueInformations lambdaParameterValueInformations)
        {
            var forEachInvocations = new List <(MethodDefinition, Instruction)>();
            var methodDefinition   = methodUsedByLambdaJobs.First();

            forEachInvocations.AddRange(methodDefinition.Body.Instructions.Where(IsChunkEntitiesForEachInvocation).Select(i => (methodDefinition, i)));

            foreach (var methodUsedByLambdaJob in methodUsedByLambdaJobs)
            {
                var methodBody = methodUsedByLambdaJob.Body;

                var displayClassVariable = methodBody.Variables.SingleOrDefault(v => v.VariableType.Name.Contains("DisplayClass"));
                if (displayClassVariable != null)
                {
                    TypeDefinition displayClass = displayClassVariable.VariableType.Resolve();
                    bool           allDelegatesAreGuaranteedNotToOutliveMethod =
                        displayClass.IsValueType ||
                        CecilHelpers.AllDelegatesAreGuaranteedNotToOutliveMethodFor(methodUsedByLambdaJob);

                    if (!displayClass.IsValueType && allDelegatesAreGuaranteedNotToOutliveMethod)
                    {
                        CecilHelpers.PatchMethodThatUsedDisplayClassToTreatItAsAStruct(methodBody,
                                                                                       displayClassVariable, displayClass);
                        CecilHelpers.PatchDisplayClassToBeAStruct(displayClass);
                    }
                }
            }

            int counter = 1;

            foreach (var(methodUsedByLambdaJob, instruction) in forEachInvocations)
            {
                var methodBody = methodUsedByLambdaJob.Body;
                var(ldFtn, newObj) = FindClosureCreatingInstructions(methodBody, instruction);

                var newType = new TypeDefinition("", "InlineEntitiesForEachInvocation" + counter++, TypeAttributes.NestedPublic | TypeAttributes.SequentialLayout, methodUsedByLambdaJob.Module.ImportReference(typeof(ValueType)))
                {
                    DeclaringType = methodUsedByLambdaJob.DeclaringType
                };
                methodUsedByLambdaJob.DeclaringType.NestedTypes.Add(newType);

                CloneLambdaMethodAndItsLocalMethods();

                var iterateEntitiesMethod = CreateIterateEntitiesMethod(lambdaParameterValueInformations);

                var variable = new VariableDefinition(newType);
                methodBody.Variables.Add(variable);

                InstructionExtensions.MakeNOP(ldFtn.Previous);
                InstructionExtensions.MakeNOP(ldFtn);
                newObj.OpCode  = OpCodes.Ldnull;
                newObj.Operand = null;

                var displayClassVariable = methodBody.Variables.SingleOrDefault(v => v.VariableType.Name.Contains("DisplayClass"));
                if (displayClassVariable == null)
                {
                    continue;
                }
                var ilProcessor = methodBody.GetILProcessor();

                ilProcessor.InsertAfter(instruction, new List <Instruction>
                {
                    //no need to drop the delegate from the stack, because we just removed the function that placed it on the stack in the first place.
                    //do not drop the description from the stack, as the original method returns it, and we want to maintain stack behaviour.

                    //call our new method
                    Instruction.Create(OpCodes.Ldloca, variable),
                    Instruction.Create(OpCodes.Initobj, newType),

                    Instruction.Create(OpCodes.Ldloca, variable),
                    Instruction.Create(OpCodes.Ldloca, displayClassVariable),
                    Instruction.Create(OpCodes.Call, ReadFromDisplayClassMethod),


                    Instruction.Create(OpCodes.Ldloca, variable),
                    Instruction.Create(OpCodes.Ldarga, methodBody.Method.Parameters.First(p => p.ParameterType.Name == nameof(ArchetypeChunk))),
                    Instruction.Create(OpCodes.Ldarg_0),
                    Instruction.Create(OpCodes.Ldfld, lambdaParameterValueInformations._runtimesField),
                    Instruction.Create(OpCodes.Call, (MethodReference)iterateEntitiesMethod),
                });

#if ENABLE_DOTS_COMPILER_CHUNKS
                var chunkEntitiesInvocation = LambdaJobDescriptionConstruction.FindInstructionThatPushedArg(methodBody.Method, 0, instruction);
                if (chunkEntitiesInvocation.Operand is MethodReference mr && mr.Name == "get_" + nameof(ArchetypeChunk.Entities) && mr.DeclaringType.Name == nameof(ArchetypeChunk))
                {
                    CecilHelpers.EraseMethodInvocationFromInstructions(ilProcessor, chunkEntitiesInvocation);
                }
#endif

                CecilHelpers.EraseMethodInvocationFromInstructions(ilProcessor, instruction);
            }
        }