Example #1
0
        static void ChangeAllDisplayClassesToStructs(MethodDefinition methodContainingLambdaJob)
        {
            var result = FindDisplayClassesIn(methodContainingLambdaJob).ToList();

            foreach (var(typeDefinition, variableDefinition) in result)
            {
                if (!typeDefinition.IsValueType())
                {
                    CecilHelpers.PatchMethodThatUsedDisplayClassToTreatItAsAStruct(methodContainingLambdaJob.Body, variableDefinition);
                    CecilHelpers.PatchDisplayClassToBeAStruct(typeDefinition);
                }
            }
        }
        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);
            }
        }
Example #3
0
        public static JobStructForLambdaJob Rewrite(MethodDefinition methodContainingLambdaJob, LambdaJobDescriptionConstruction lambdaJobDescriptionConstruction, List <DiagnosticMessage> warnings)
        {
            if (methodContainingLambdaJob.DeclaringType.CustomAttributes.Any(c => (c.AttributeType.Name == "ExecuteAlways" && c.AttributeType.Namespace == "UnityEngine")))
            {
                warnings.Add(UserError.DC0032(methodContainingLambdaJob.DeclaringType, methodContainingLambdaJob, lambdaJobDescriptionConstruction.ScheduleOrRunInvocationInstruction));
            }

            if (keepUnmodifiedVersionAroundForDebugging)
            {
                CecilHelpers.CloneMethodForDiagnosingProblems(methodContainingLambdaJob);
            }

            //in order to make it easier and safer to operate on IL, we're going to "Simplify" it.  This means transforming opcodes that have short variants, into the full one
            //so that when you analyze, you can assume (ldarg, 0) opcode + operand pair,  and don't have to go check for the shorthand version of ldarg_0, without an operand.
            //more importantly, it also rewrites branch.s (branch to a instruction that is closeby enough that the offset fits within a byte), to a regular branch instructions.
            //this is the only sane thing to do, because when we rewrite IL code, we add instructions, so it can happen that by adding instructions, what was possible to be a short
            //branch instruction, now is no longer a valid short branch target,  and cecil doesn't warn against that, it will just generate broken IL, and you'll spend a long time
            //figuring out what is going on.
            methodContainingLambdaJob.Body.SimplifyMacros();

            var methodLambdaWasEmittedAs = lambdaJobDescriptionConstruction.MethodLambdaWasEmittedAs;

            if (methodLambdaWasEmittedAs.DeclaringType.TypeReferenceEquals(methodContainingLambdaJob.DeclaringType) && !lambdaJobDescriptionConstruction.AllowReferenceTypes)
            {
                //sometimes roslyn emits the lambda as an instance method in the same type of the method that contains the lambda expression.
                //it does this only in the situation where the lambda captures a field _and_ does not capture any locals.  in this case
                //there's no displayclass being created.  We should figure out exactly what instruction caused this behaviour, and tell the user
                //she can't read a field like that.

                var illegalFieldRead = methodLambdaWasEmittedAs.Body.Instructions.FirstOrDefault(i => i.OpCode == OpCodes.Ldfld && i.Previous?.OpCode == OpCodes.Ldarg_0);
                if (illegalFieldRead != null)
                {
                    UserError.DC0001(methodContainingLambdaJob, illegalFieldRead, (FieldReference)illegalFieldRead.Operand).Throw();
                }

                var illegalInvocation = methodLambdaWasEmittedAs.Body.Instructions.FirstOrDefault(i => i.IsInvocation() && methodContainingLambdaJob.DeclaringType.TypeReferenceEqualsOrInheritsFrom(((MethodReference)i.Operand).DeclaringType));
                if (illegalInvocation != null)
                {
                    UserError.DC0002(methodContainingLambdaJob, illegalInvocation, (MethodReference)illegalInvocation.Operand).Throw();
                }

                //this should never hit, but is here to make sure that in case we have a bug in detecting why roslyn emitted it like this, we can at least report an error, instead of silently generating invalid code.
                InternalCompilerError.DCICE001(methodContainingLambdaJob).Throw();
            }

            var moduleDefinition = methodContainingLambdaJob.Module;

            var body        = methodContainingLambdaJob.Body;
            var ilProcessor = body.GetILProcessor();

            VariableDefinition displayClassVariable = null;

            if (lambdaJobDescriptionConstruction.DelegateProducingSequence.CapturesLocals)
            {
                bool allDelegatesAreGuaranteedNotToOutliveMethod = lambdaJobDescriptionConstruction.DisplayClass.IsValueType || CecilHelpers.AllDelegatesAreGuaranteedNotToOutliveMethodFor(methodContainingLambdaJob);


                displayClassVariable = body.Variables.Single(v => v.VariableType.TypeReferenceEquals(lambdaJobDescriptionConstruction.DisplayClass));

                //in this step we want to get rid of the heap allocation for the delegate. In order to make the rest of the code easier to reason about and write,
                //we'll make sure that while we do this, we don't change the total stackbehaviour. Because this used to push a delegate onto the evaluation stack,
                //we also have to write something to the evaluation stack.  Later in this method it will be popped, so it doesn't matter what it is really.  I use Ldc_I4_0,
                //as I found it introduced the most reasonable artifacts when the code is decompiled back into C#.
                lambdaJobDescriptionConstruction.DelegateProducingSequence.RewriteToKeepDisplayClassOnEvaluationStack();

                if (!lambdaJobDescriptionConstruction.DisplayClass.IsValueType && allDelegatesAreGuaranteedNotToOutliveMethod)
                {
                    CecilHelpers.PatchMethodThatUsedDisplayClassToTreatItAsAStruct(body, displayClassVariable, lambdaJobDescriptionConstruction.DisplayClass);
                    CecilHelpers.PatchDisplayClassToBeAStruct(lambdaJobDescriptionConstruction.DisplayClass);
                }
            }
            else
            {
                //if the lambda is not capturing, roslyn will recycle the delegate in a static field. not so great for us. let's nop out all that code.
                var instructionThatPushedDelegate = CecilHelpers.FindInstructionThatPushedArg(methodContainingLambdaJob, 1, lambdaJobDescriptionConstruction.WithCodeInvocationInstruction);

                var result = CecilHelpers.MatchesDelegateProducingPattern(methodContainingLambdaJob, instructionThatPushedDelegate, CecilHelpers.DelegateProducingPattern.MatchSide.Start);
                result?.RewriteToProduceSingleNullValue();
            }

            FieldDefinition entityQueryField = null;

            if (lambdaJobDescriptionConstruction.Kind != LambdaJobDescriptionKind.Job)
            {
                entityQueryField = InjectAndInitializeEntityQueryField.InjectAndInitialize(methodContainingLambdaJob, lambdaJobDescriptionConstruction, methodLambdaWasEmittedAs.Parameters);
            }

            var generatedJobStruct = JobStructForLambdaJob.CreateNewJobStruct(lambdaJobDescriptionConstruction);

            if (generatedJobStruct.RunWithoutJobSystemDelegateFieldNoBurst != null)
            {
                var constructorInfo = generatedJobStruct.ExecuteDelegateType.GetConstructors().First(c => c.GetParameters().Length == 2);

                var instructions = new List <Instruction>()
                {
                    Instruction.Create(OpCodes.Ldnull),
                    Instruction.Create(OpCodes.Ldftn, generatedJobStruct.RunWithoutJobSystemMethod),
                    Instruction.Create(OpCodes.Newobj, moduleDefinition.ImportReference(constructorInfo)),
                    Instruction.Create(OpCodes.Stsfld, generatedJobStruct.RunWithoutJobSystemDelegateFieldNoBurst)
                };
                if (generatedJobStruct.RunWithoutJobSystemDelegateFieldBurst != null)
                {
                    instructions.Add(Instruction.Create(OpCodes.Ldsfld, generatedJobStruct.RunWithoutJobSystemDelegateFieldNoBurst));

                    var methodInfo = typeof(InternalCompilerInterface)
                                     .GetMethods(BindingFlags.Static | BindingFlags.Public)
                                     .Where(m => m.Name == nameof(InternalCompilerInterface.BurstCompile))
                                     .Single(m => m.GetParameters().First().ParameterType == generatedJobStruct.ExecuteDelegateType);

                    instructions.Add(Instruction.Create(OpCodes.Call, moduleDefinition.ImportReference(methodInfo)));
                    instructions.Add(Instruction.Create(OpCodes.Stsfld, generatedJobStruct.RunWithoutJobSystemDelegateFieldBurst));
                }
                InjectAndInitializeEntityQueryField.InsertIntoOnCreateForCompilerMethod(methodContainingLambdaJob.DeclaringType, instructions.ToArray());
            }

            IEnumerable <Instruction> InstructionsToReplaceScheduleInvocationWith()
            {
                var newJobStructVariable = new VariableDefinition(generatedJobStruct.TypeDefinition);

                body.Variables.Add(newJobStructVariable);

                VariableDefinition tempStorageForJobHandle = null;

                if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                {
                    tempStorageForJobHandle = new VariableDefinition(moduleDefinition.ImportReference(typeof(JobHandle)));
                    body.Variables.Add(tempStorageForJobHandle);

                    //since we're replacing the .Schedule() function on the description, the lambdajobdescription and the jobhandle argument to that function will be on the stack.
                    //we're going to need the jobhandle later when we call JobChunkExtensions.Schedule(), so lets stuff it in a variable.
                    yield return(Instruction.Create(OpCodes.Stloc, tempStorageForJobHandle));
                }

                //pop the Description struct off the stack, its services are no longer required
                yield return(Instruction.Create(OpCodes.Pop));

                yield return(Instruction.Create(OpCodes.Ldloca, newJobStructVariable));

                yield return(Instruction.Create(OpCodes.Initobj, generatedJobStruct.TypeDefinition));

                // Call ScheduleTimeInitializeMethod
                yield return(Instruction.Create(OpCodes.Ldloca, newJobStructVariable));

                yield return(Instruction.Create(OpCodes.Ldarg_0));

                if (lambdaJobDescriptionConstruction.DelegateProducingSequence.CapturesLocals)
                {
                    //only when the lambda is capturing, did we emit the ScheduleTimeInitialize method to take a displayclass argument
                    var opcode = methodLambdaWasEmittedAs.DeclaringType.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
                    yield return(Instruction.Create(opcode, displayClassVariable));
                }

                yield return(Instruction.Create(OpCodes.Call, generatedJobStruct.ScheduleTimeInitializeMethod));

                MethodInfo FindRunOrScheduleMethod()
                {
                    switch (lambdaJobDescriptionConstruction.Kind)
                    {
                    case LambdaJobDescriptionKind.Entities:
                    case LambdaJobDescriptionKind.Chunk:
                        if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                        {
                            return(typeof(JobChunkExtensions).GetMethod(nameof(JobChunkExtensions.Schedule)));
                        }
                        return(typeof(InternalCompilerInterface).GetMethod(nameof(InternalCompilerInterface.RunJobChunk)));

                    case LambdaJobDescriptionKind.Job:
                        if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                        {
                            return(typeof(IJobExtensions).GetMethod(nameof(IJobExtensions.Schedule)));
                        }
                        return(typeof(InternalCompilerInterface).GetMethod(nameof(InternalCompilerInterface.RunIJob)));

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                }

                MethodReference runOrScheduleMethod;

                if (lambdaJobDescriptionConstruction.WithStructuralChanges)
                {
                    runOrScheduleMethod = generatedJobStruct.TypeDefinition.Methods.First(definition => definition.Name == "Execute");
                }
                else
                {
                    runOrScheduleMethod = moduleDefinition.ImportReference(FindRunOrScheduleMethod())
                                          .MakeGenericInstanceMethod(generatedJobStruct.TypeDefinition);
                }

                if (lambdaJobDescriptionConstruction.WithStructuralChanges)
                {
                    yield return(Instruction.Create(OpCodes.Ldloca, newJobStructVariable));
                }
                else
                {
                    yield return(Instruction.Create(runOrScheduleMethod.Parameters.First().ParameterType.IsByReference ? OpCodes.Ldloca : OpCodes.Ldloc, newJobStructVariable));
                }

                switch (lambdaJobDescriptionConstruction.Kind)
                {
                case LambdaJobDescriptionKind.Entities:
                case LambdaJobDescriptionKind.Chunk:
                    if (lambdaJobDescriptionConstruction.WithStructuralChanges)
                    {
                        yield return(Instruction.Create(OpCodes.Ldarg_0));

                        yield return(Instruction.Create(OpCodes.Ldarg_0));

                        yield return(Instruction.Create(OpCodes.Ldfld, entityQueryField));
                    }
                    else
                    {
                        yield return(Instruction.Create(OpCodes.Ldarg_0));

                        yield return(Instruction.Create(OpCodes.Ldfld, entityQueryField));
                    }
                    break;

                case LambdaJobDescriptionKind.Job:
                    //job.Schedule() takes no entityQuery...
                    break;
                }

                if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                {
                    yield return(Instruction.Create(OpCodes.Ldloc, tempStorageForJobHandle));
                }

                if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Run &&
                    !lambdaJobDescriptionConstruction.WithStructuralChanges)
                {
                    if (!lambdaJobDescriptionConstruction.UsesBurst)
                    {
                        yield return(Instruction.Create(OpCodes.Ldsfld, generatedJobStruct.RunWithoutJobSystemDelegateFieldNoBurst));
                    }
                    else
                    {
                        yield return(Instruction.Create(OpCodes.Call, moduleDefinition.ImportReference(typeof(JobsUtility).GetMethod("get_" + nameof(JobsUtility.JobCompilerEnabled)))));

                        var targetInstruction = Instruction.Create(OpCodes.Ldsfld, generatedJobStruct.RunWithoutJobSystemDelegateFieldBurst);
                        yield return(Instruction.Create(OpCodes.Brtrue, targetInstruction));

                        yield return(Instruction.Create(OpCodes.Ldsfld, generatedJobStruct.RunWithoutJobSystemDelegateFieldNoBurst));

                        var finalBranchDestination = Instruction.Create(OpCodes.Nop);
                        yield return(Instruction.Create(OpCodes.Br, finalBranchDestination));

                        yield return(targetInstruction);

                        yield return(finalBranchDestination);
                    }
                }

                yield return(Instruction.Create(OpCodes.Call, runOrScheduleMethod));

                if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Run &&
                    generatedJobStruct.WriteToDisplayClassMethod != null && lambdaJobDescriptionConstruction.DelegateProducingSequence.CapturesLocals)
                {
                    yield return(Instruction.Create(OpCodes.Ldloca, newJobStructVariable));

                    var opcode = methodLambdaWasEmittedAs.DeclaringType.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
                    yield return(Instruction.Create(opcode, displayClassVariable));

                    yield return(Instruction.Create(OpCodes.Call, generatedJobStruct.WriteToDisplayClassMethod));
                }
            }

            foreach (var invokedMethod in lambdaJobDescriptionConstruction.InvokedConstructionMethods)
            {
                bool invokedMethodServesNoPurposeAtRuntime =
                    invokedMethod.MethodName != nameof(LambdaJobQueryConstructionMethods.WithSharedComponentFilter);

                if (invokedMethodServesNoPurposeAtRuntime)
                {
                    CecilHelpers.EraseMethodInvocationFromInstructions(ilProcessor, invokedMethod.InstructionInvokingMethod);
                }
                else
                {
                    // Rewrite WithSharedComponentFilter calls as they need to modify EntityQuery dynamically
                    if (invokedMethod.MethodName ==
                        nameof(LambdaJobQueryConstructionMethods.WithSharedComponentFilter))
                    {
                        var setSharedComponentFilterOnQueryMethod
                            = moduleDefinition.ImportReference(
                                  (lambdaJobDescriptionConstruction.Kind == LambdaJobDescriptionKind.Entities ? typeof(ForEachLambdaJobDescription_SetSharedComponent) : typeof(LambdaJobChunkDescription_SetSharedComponent)).GetMethod(
                                      nameof(LambdaJobChunkDescription_SetSharedComponent
                                             .SetSharedComponentFilterOnQuery)));
                        MethodReference genericSetSharedComponentFilterOnQueryMethod
                            = setSharedComponentFilterOnQueryMethod.MakeGenericInstanceMethod(invokedMethod.TypeArguments);

                        // Change invocation to invocation of helper method and add EntityQuery parameter to be modified
                        var setSharedComponentFilterOnQueryInstructions = new List <Instruction>
                        {
                            Instruction.Create(OpCodes.Ldarg_0),
                            Instruction.Create(OpCodes.Ldfld, entityQueryField),
                            Instruction.Create(OpCodes.Call, genericSetSharedComponentFilterOnQueryMethod)
                        };

                        ilProcessor.Replace(invokedMethod.InstructionInvokingMethod,
                                            setSharedComponentFilterOnQueryInstructions);
                    }
                }
            }

            var scheduleInstructions = InstructionsToReplaceScheduleInvocationWith().ToList();

            ilProcessor.InsertAfter(lambdaJobDescriptionConstruction.ScheduleOrRunInvocationInstruction, scheduleInstructions);
            lambdaJobDescriptionConstruction.ScheduleOrRunInvocationInstruction.MakeNOP();

            var codegenInitializeMethod = GetOrMakeOnCreateForCompilerMethod(lambdaJobDescriptionConstruction.ContainingMethod.DeclaringType);

            return(generatedJobStruct);
        }