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