static void InjectOnCreateForCompiler(TypeDefinition typeDefinition)
        {
            // Turns out it's not trivial to inject some code that has to be run OnCreate of the system.
            // We cannot just create an OnCreate() method, because there might be a deriving class that also implements it.
            // That child method is probably not calling base.OnCreate(), but even when it is (!!) the c# compiler bakes base.OnCreate()
            // into a direct reference to whatever is the first baseclass to have OnCreate() at the time of compilation.  So if we go
            // and inject an OnCreate() in this class later on,  the child's base.OnCreate() call will actually bypass it.
            //
            // Instead what we do is add OnCreateForCompiler,  hide it from intellisense, give you an error if wanna be that guy that goes
            // and implement it anyway,  and then we inject a OnCreateForCompiler method into each and every ComponentSystem.  The reason we have to emit it in
            // each and every system, and not just the ones where we have something to inject,  is that when we emit these method, we need
            // to also emit base.OnCreateForCompiler().  However, when we are processing an user system type,  we do not know yet if its baseclass
            // also needs an OnCreateForCompiler().   So we play it safe, and assume it does.  So every OnCreateForCompiler() that we emit,
            // will assume its basetype also has an implementation and invoke that.

            if (typeDefinition.Name == nameof(ComponentSystemBase) && typeDefinition.Namespace == "Unity.Entities")
            {
                return;
            }

            var onCreateForCompilerName = EntitiesILHelpers.GetOnCreateForCompilerName();
            var preExistingMethod       = typeDefinition.Methods.FirstOrDefault(m => m.Name == onCreateForCompilerName);

            if (preExistingMethod != null)
            {
                UserError.DC0026($"It's not allowed to implement {onCreateForCompilerName}'", preExistingMethod).Throw();
            }

            EntitiesILHelpers.GetOrMakeOnCreateForCompilerMethodFor(typeDefinition);
        }
Ejemplo n.º 2
0
        public static CecilHelpers.DelegateProducingSequence AnalyzeForEachInvocationInstruction(MethodDefinition methodToAnalyze, Instruction withCodeInvocationInstruction)
        {
            var delegatePushingInstruction = CecilHelpers.FindInstructionThatPushedArg(methodToAnalyze, 1, withCodeInvocationInstruction);

            var result = CecilHelpers.MatchesDelegateProducingPattern(methodToAnalyze, delegatePushingInstruction, CecilHelpers.DelegateProducingPattern.MatchSide.Start);

            if (result == null)
            {
                // Make sure we aren't generating this lambdajob from a stored delegate
                bool LivesInUniversalDelegatesNamespace(TypeReference type) => type.Namespace == typeof(UniversalDelegates.R <>).Namespace;

                if ((delegatePushingInstruction.OpCode == OpCodes.Ldfld && LivesInUniversalDelegatesNamespace(((FieldReference)delegatePushingInstruction.Operand).FieldType) ||
                     (delegatePushingInstruction.IsLoadLocal(out var localIndex) && LivesInUniversalDelegatesNamespace(methodToAnalyze.Body.Variables[localIndex].VariableType)) ||
                     (delegatePushingInstruction.IsLoadArg(out var argIndex) &&
                      LivesInUniversalDelegatesNamespace(methodToAnalyze.Parameters[methodToAnalyze.HasThis ? (argIndex - 1) : argIndex].ParameterType))))
                {
                    UserError.DC0044(methodToAnalyze, delegatePushingInstruction).Throw();
                }
                else if (((delegatePushingInstruction.OpCode == OpCodes.Call || delegatePushingInstruction.OpCode == OpCodes.Callvirt) &&
                          delegatePushingInstruction.Operand is MethodReference callMethod))
                {
                    if (LivesInUniversalDelegatesNamespace(callMethod.ReturnType))
                    {
                        UserError.DC0044(methodToAnalyze, delegatePushingInstruction).Throw();
                    }
                }

                InternalCompilerError.DCICE002(methodToAnalyze, delegatePushingInstruction).Throw();
            }
Ejemplo n.º 3
0
 static DiagnosticMessage CheckNativeDisableContainerSafetyRestriction(
     MethodDefinition method,
     LambdaJobDescriptionConstruction.InvokedConstructionMethod constructionMethod,
     FieldDefinition field)
 {
     if (field.FieldType.HasAttributeOrFieldWithAttribute(typeof(NativeContainerAttribute)))
     {
         return(null);
     }
     return(UserError.DC0036(method, field.Name, field.FieldType, constructionMethod.InstructionInvokingMethod));
 }
 static DiagnosticMessage CheckNativeDisableParallelForRestriction(
     MethodDefinition method,
     LambdaJobDescriptionConstruction.InvokedConstructionMethod constructionMethod,
     FieldDefinition field)
 {
     if (HasAttribute(field.FieldType.Resolve(), typeof(NativeContainerAttribute)))
     {
         return(null);
     }
     return(UserError.DC0037(method, field.Name, field.FieldType, constructionMethod.InstructionInvokingMethod));
 }
 static DiagnosticMessage CheckDeallocateOnJobCompletion(
     MethodDefinition method,
     LambdaJobDescriptionConstruction.InvokedConstructionMethod constructionMethod,
     FieldDefinition field)
 {
     if (HasAttribute(field.FieldType.Resolve(), typeof(NativeContainerSupportsDeallocateOnJobCompletionAttribute)))
     {
         return(null);
     }
     return(UserError.DC0035(method, field.Name, field.FieldType, constructionMethod.InstructionInvokingMethod));
 }
        JobStructForLambdaJob(LambdaJobDescriptionConstruction lambdaJobDescriptionConstruction2)
        {
            LambdaJobDescriptionConstruction = lambdaJobDescriptionConstruction2;
            var containingMethod = LambdaJobDescriptionConstruction.ContainingMethod;

            if (containingMethod.DeclaringType.NestedTypes.Any(t => t.Name == LambdaJobDescriptionConstruction.Name))
            {
                UserError.DC0003(LambdaJobDescriptionConstruction.Name, containingMethod, LambdaJobDescriptionConstruction.ScheduleOrRunInvocationInstruction).Throw();
            }

            var moduleDefinition = containingMethod.Module;

            var typeAttributes = TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed |
                                 TypeAttributes.AnsiClass | TypeAttributes.SequentialLayout |
                                 TypeAttributes.NestedPrivate;

            TypeDefinition = new TypeDefinition(containingMethod.DeclaringType.Namespace, LambdaJobDescriptionConstruction.Name, typeAttributes, moduleDefinition.ImportReference(typeof(ValueType)))
            {
                DeclaringType = containingMethod.DeclaringType,
                Interfaces    = { new InterfaceImplementation(moduleDefinition.ImportReference(InterfaceTypeFor(LambdaJobDescriptionConstruction.Kind))) }
            };

            containingMethod.DeclaringType.NestedTypes.Add(TypeDefinition);


            if (LambdaJobDescriptionConstruction.LambdaWasEmittedAsInstanceMethodOnContainingType)
            {
                //if you capture no locals, but you do use a field/method on the componentsystem, the lambda gets emitted as an instance method on the component system
                //this is inconvenient for us. To make the rest of our code not have to deal with this case, we will emit an OriginalLambda method on our job type, that calls
                //the lambda as it is emitted as an instance method on the component system.  See EntitiesForEachNonCapturingInvokingInstanceMethod test for more details.
                //example:
                //https://sharplab.io/#v2:CYLg1APgAgTAjAWAFBQMwAJboMLoN7LpGYZQAs6AsgJ4CSAdgM4AuAhvQMYCmlXzAFgHtgACgCU+AL6FiMomkwVK4/HOJEAogA8uHAK7MuIlQF4AfFTpM2nHnyGixYgNxrpSdVDgA2Rem26BkZeMOisYnjukkA==

                MakeOriginalLambdaMethodThatRelaysToInstanceMethodOnComponentSystem();
            }
            else
            {
                CloneLambdaMethodAndItsLocalMethods();
            }

            ApplyFieldAttributes();

            var lambdaParameterValueProviderInformations = MakeLambdaParameterValueProviderInformations();

            MakeDeallocateOnCompletionMethod();

            ExecuteMethod = MakeExecuteMethod(lambdaParameterValueProviderInformations);

            ScheduleTimeInitializeMethod = AddMethod(MakeScheduleTimeInitializeMethod(lambdaParameterValueProviderInformations));

            AddRunWithoutJobSystemMembers();

            ApplyBurstAttributeIfRequired();
        }
        // Don't do a CheckedResolve here. If we somehow fail we don't want to block the user.
        static bool TryResolve(TypeReference tr, MethodDefinition method, List <DiagnosticMessage> diagnosticMessages, out TypeDefinition td)
        {
            td = tr.Resolve();
            if (td == null)
            {
                diagnosticMessages.Add(
                    UserError.MakeWarning("ResolveFailureWarning",
                                          $"Unable to resolve type {tr.FullName} for verification.",
                                          method, method.Body.Instructions.FirstOrDefault()));
                return(false);
            }

            return(true);
        }
        private static MethodDefinition InjectOnCreateForCompiler(TypeDefinition typeDefinition)
        {
            // Turns out it's not trivial to inject some code that has to be run OnCreate of the system.
            // We cannot just create an OnCreate() method, because there might be a deriving class that also implements it.
            // That child method is probably not calling base.OnCreate(), but even when it is (!!) the c# compiler bakes base.OnCreate()
            // into a direct reference to whatever is the first baseclass to have OnCreate() at the time of compilation.  So if we go
            // and inject an OnCreate() in this class later on,  the child's base.OnCreate() call will actually bypass it.
            //
            // Instead what we do is add OnCreateForCompiler,  hide it from intellisense, give you an error if wanna be that guy that goes
            // and implement it anyway,  and then we inject a OnCreateForCompiler method into each and every ComponentSystem.  The reason we have to emit it in
            // each and every system, and not just the ones where we have something to inject,  is that when we emit these method, we need
            // to also emit base.OnCreateForCompiler().  However, when we are processing an user system type,  we do not know yet if its baseclass
            // also needs an OnCreateForCompiler().   So we play it safe, and assume it does.  So every OnCreateForCompiler() that we emit,
            // will assume its basetype also has an implementation and invoke that.

            if (typeDefinition.Name == nameof(ComponentSystemBase) && typeDefinition.Namespace == "Unity.Entities")
            {
                return(null);
            }

            var name = OnCreateForCompilerName;

            var pre_existing_method = typeDefinition.Methods.FirstOrDefault(m => m.Name == name);

            if (pre_existing_method != null)
            {
                UserError.DC0026($"It's not allowed to implement {OnCreateForCompilerName}'", pre_existing_method).Throw();
            }

            var typeSystemVoid = typeDefinition.Module.TypeSystem.Void;
            var newMethod      = new MethodDefinition(name, MethodAttributes.FamORAssem | MethodAttributes.Virtual | MethodAttributes.HideBySig, typeSystemVoid);

            typeDefinition.Methods.Add(newMethod);

            var ilProcessor = newMethod.Body.GetILProcessor();

            ilProcessor.Emit(OpCodes.Ldarg_0);
            ilProcessor.Emit(OpCodes.Call, new MethodReference(name, typeSystemVoid, typeDefinition.BaseType)
            {
                HasThis = true
            });
            ilProcessor.Emit(OpCodes.Ret);
            return(newMethod);
        }
        private static Instruction FindNextConstructionMethod(MethodDefinition method, Instruction instruction)
        {
            var cursor = instruction;
            //the description object should be on the stack, and nothing on top of it.
            int stackDepth = 1;

            while (cursor.Next != null)
            {
                cursor = cursor.Next;

                var result = CecilHelpers.MatchesDelegateProducingPattern(method, cursor, CecilHelpers.DelegateProducingPattern.MatchSide.Start);
                if (result != null)
                {
                    cursor      = result.Instructions.Last();
                    stackDepth += 1;
                    continue;
                }

                if (CecilHelpers.IsUnsupportedBranch(cursor))
                {
                    UserError.DC0010(method, cursor).Throw();
                }

                if (cursor.OpCode == OpCodes.Call && cursor.Operand is MethodReference mr && IsLambdaJobDescriptionConstructionMethod(mr))
                {
                    return(cursor);
                }

                stackDepth -= cursor.GetPopDelta();
                if (stackDepth < 1)
                {
                    UserError.DC0011(method, cursor).Throw();
                }

                stackDepth += cursor.GetPushDelta();
            }

            return(null);
        }
        void ApplyFieldAttributes()
        {
            //.Run mode doesn't go through the jobsystem, so there's no point in making all these attributes to explain the job system what everything means.
            if (LambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Run)
            {
                return;
            }

            foreach (var(methodName, attributeType) in ConstructionMethodsThatCorrespondToFieldAttributes)
            {
                foreach (var constructionMethod in LambdaJobDescriptionConstruction.InvokedConstructionMethods.Where(m => m.MethodName == methodName))
                {
                    if (constructionMethod.Arguments.Single() is FieldDefinition fieldDefinition)
                    {
                        var correspondingJobField = ClonedFields.Values.Single(f => f.Name == fieldDefinition.Name);
                        correspondingJobField.CustomAttributes.Add(new CustomAttribute(TypeDefinition.Module.ImportReference(attributeType.GetConstructor(Array.Empty <Type>()))));
                        continue;
                    }

                    UserError.DC0012(LambdaJobDescriptionConstruction.ContainingMethod, constructionMethod).Throw();
                }
            }
        }
 public static DiagnosticMessage DCICE002(MethodDefinition method, Instruction instruction)
 {
     return(UserError.MakeError(nameof(DCICE001), $"Unable to find LdFtn & NewObj pair preceding call instruction in {method.Name}", method, instruction));
 }
 public static DiagnosticMessage DCICE001(MethodDefinition method)
 {
     return(UserError.MakeError(nameof(DCICE001), "Entities.ForEach Lambda expression uses something from its outer class. This is not supported.", method, null));
 }
Ejemplo n.º 13
0
 public static DiagnosticMessage DCICE007(MethodDefinition methodToAnalyze, LambdaJobDescriptionConstruction.InvokedConstructionMethod constructionMethod)
 {
     return(UserError.MakeError(nameof(DCICE007), $"Could not find field for local captured variable for argument of {constructionMethod.MethodName}.", methodToAnalyze, constructionMethod.InstructionInvokingMethod));
 }
Ejemplo n.º 14
0
 public static DiagnosticMessage DCICE006(MethodDefinition method)
 {
     return(UserError.MakeError(nameof(DCICE006), $"Next instruction used in constructing the DisplayClass needs to be a store local instruction in {method.Name}", method, null));
 }
Ejemplo n.º 15
0
 public static DiagnosticMessage DCICE005(MethodDefinition method, Instruction instruction)
 {
     return(UserError.MakeError(nameof(DCICE005), $"Previous instruction needs to be Ldfld or Ldflda while parsing IL to generate lambda for method {method.Name}.", method, instruction));
 }
Ejemplo n.º 16
0
 public static DiagnosticMessage DCICE010(MethodDefinition method, Instruction instruction)
 {
     return(UserError.MakeError(nameof(DCICE010), $"Ldftn opcode was not preceeded with Ldloc opcode while IL was being re-written to keep DisplayClass on the stack for method {method.Name}.", method, instruction));
 }
        static LambdaJobDescriptionConstruction AnalyzeLambdaJobStatement(MethodDefinition method, Instruction getEntitiesOrJobInstruction, int lambdaNumber)
        {
            List <InvokedConstructionMethod> modifiers = new List <InvokedConstructionMethod>();

            Instruction cursor = getEntitiesOrJobInstruction;
            var         expectedPreviousMethodPushingDescription = getEntitiesOrJobInstruction;

            while (true)
            {
                cursor = FindNextConstructionMethod(method, cursor);

                var mr = cursor?.Operand as MethodReference;

                if (mr.Name == nameof(LambdaJobDescriptionExecutionMethods.Schedule) ||
                    mr.Name == nameof(LambdaJobDescriptionExecutionMethods.ScheduleParallel) ||
                    mr.Name == nameof(LambdaJobDescriptionExecutionMethods.Run))
                {
                    var withNameModifier = modifiers.FirstOrDefault(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithName));
                    var givenName        = withNameModifier?.Arguments.OfType <string>().Single();
                    var lambdaJobName    = givenName ?? $"{method.Name}_LambdaJob{lambdaNumber}";
                    if (givenName != null && !VerifyLambdaName(givenName))
                    {
                        UserError.DC0039(method, givenName, getEntitiesOrJobInstruction).Throw();
                    }

                    var hasWithStructuralChangesModifier
                        = modifiers.Any(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithStructuralChanges));

                    if (hasWithStructuralChangesModifier && mr.Name != nameof(LambdaJobDescriptionExecutionMethods.Run))
                    {
                        UserError.DC0028(method, getEntitiesOrJobInstruction).Throw();
                    }

                    FieldReference storeQueryInField = null;
                    foreach (var modifier in modifiers)
                    {
                        if (modifier.MethodName == nameof(LambdaJobQueryConstructionMethods.WithStoreEntityQueryInField))
                        {
                            var instructionThatPushedField = CecilHelpers.FindInstructionThatPushedArg(method, 1, modifier.InstructionInvokingMethod);
                            storeQueryInField = instructionThatPushedField.Operand as FieldReference;
                            if (instructionThatPushedField.OpCode != OpCodes.Ldflda || storeQueryInField == null ||
                                instructionThatPushedField.Previous.OpCode != OpCodes.Ldarg_0)
                            {
                                UserError.DC0031(method, getEntitiesOrJobInstruction).Throw();
                            }
                        }
                    }

                    LambdaJobDescriptionKind FindLambdaDescriptionKind()
                    {
                        switch (((MethodReference)getEntitiesOrJobInstruction.Operand).Name)
                        {
                        case EntitiesGetterName:
                            return(LambdaJobDescriptionKind.Entities);

                        case JobGetterName:
                            return(LambdaJobDescriptionKind.Job);

#if ENABLE_DOTS_COMPILER_CHUNKS
                        case "get_" + nameof(JobComponentSystem.Chunks):
                            return(LambdaJobDescriptionKind.Chunk);
#endif
                        default:
                            throw new ArgumentOutOfRangeException();
                        }
                    }

                    if (modifiers.All(m => m.MethodName != nameof(LambdaForEachDescriptionConstructionMethods.ForEach) &&
                                      m.MethodName != nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode)))
                    {
                        DiagnosticMessage MakeDiagnosticMessage()
                        {
                            switch (FindLambdaDescriptionKind())
                            {
                            case LambdaJobDescriptionKind.Entities:
                                return(UserError.DC0006(method, getEntitiesOrJobInstruction));

                            case LambdaJobDescriptionKind.Job:
                                return(UserError.DC0017(method, getEntitiesOrJobInstruction));

                            case LambdaJobDescriptionKind.Chunk:
                                return(UserError.DC0018(method, getEntitiesOrJobInstruction));

                            default:
                                throw new ArgumentOutOfRangeException();
                            }
                        }

                        MakeDiagnosticMessage().Throw();
                    }

                    if (method.DeclaringType.HasGenericParameters)
                    {
                        UserError.DC0025($"Entities.ForEach cannot be used in system {method.DeclaringType.Name} as Entities.ForEach in generic system types are not supported.", method, getEntitiesOrJobInstruction).Throw();
                    }

                    var withCodeInvocationInstruction = modifiers
                                                        .Single(m => m.MethodName == nameof(LambdaForEachDescriptionConstructionMethods.ForEach) || m.MethodName == nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode))
                                                        .InstructionInvokingMethod;
                    return(new LambdaJobDescriptionConstruction()
                    {
                        Kind = FindLambdaDescriptionKind(),
                        InvokedConstructionMethods = modifiers,
                        WithCodeInvocationInstruction = withCodeInvocationInstruction,
                        ScheduleOrRunInvocationInstruction = cursor,
                        LambdaJobName = lambdaJobName,
                        ChainInitiatingInstruction = getEntitiesOrJobInstruction,
                        ContainingMethod = method,
                        DelegateProducingSequence = AnalyzeForEachInvocationInstruction(method, withCodeInvocationInstruction),
                        WithStructuralChanges = hasWithStructuralChangesModifier,
                        StoreQueryInField = (storeQueryInField != null) ? storeQueryInField.Resolve() : null
                    });
                }

                var instructions = mr.Parameters.Skip(1)
                                   .Select(p => OperandObjectFor(CecilHelpers.FindInstructionThatPushedArg(method, p.Index, cursor))).ToArray();

                var invokedConstructionMethod = new InvokedConstructionMethod(mr.Name,
                                                                              (mr as GenericInstanceMethod)?.GenericArguments.ToArray() ?? Array.Empty <TypeReference>(),
                                                                              instructions, cursor);

                var allowDynamicValue = method.Module.ImportReference(typeof(AllowDynamicValueAttribute));
                for (int i = 0; i != invokedConstructionMethod.Arguments.Length; i++)
                {
                    if (invokedConstructionMethod.Arguments[i] != null)
                    {
                        continue;
                    }

                    var inbovokedForEachMethod    = mr.Resolve();
                    var methodDefinitionParameter = inbovokedForEachMethod.Parameters[i + 1];

                    if (!methodDefinitionParameter.CustomAttributes.Any(c => c.AttributeType.TypeReferenceEquals(allowDynamicValue)))
                    {
                        UserError.DC0008(method, cursor, mr).Throw();
                    }
                }

                if (modifiers.Any(m => m.MethodName == mr.Name) && !HasAllowMultipleAttribute(mr.Resolve()))
                {
                    UserError.DC0009(method, cursor, mr).Throw();
                }

                var findInstructionThatPushedArg = CecilHelpers.FindInstructionThatPushedArg(method, 0, cursor);
                if (cursor == null || findInstructionThatPushedArg != expectedPreviousMethodPushingDescription)
                {
                    UserError.DC0007(method, cursor).Throw();
                }

                expectedPreviousMethodPushingDescription = cursor;
                modifiers.Add(invokedConstructionMethod);
            }
        }
        protected override bool PostProcessImpl(TypeDefinition[] componentSystemTypes)
        {
            var assemblyDefinition = AssemblyDefinition;

            var earlyInitHelpers = assemblyDefinition.MainModule.ImportReference(typeof(EarlyInitHelpers)).CheckedResolve();

            var autoClassName = $"__JobReflectionRegistrationOutput__{(uint) assemblyDefinition.FullName.GetHashCode()}";

            var classDef = new TypeDefinition("", autoClassName, TypeAttributes.Class, assemblyDefinition.MainModule.ImportReference(typeof(object)));

            classDef.IsBeforeFieldInit = false;

            classDef.CustomAttributes.Add(new CustomAttribute(AttributeConstructorReferenceFor(typeof(DOTSCompilerGeneratedAttribute), assemblyDefinition.MainModule)));

            var funcDef = new MethodDefinition("CreateJobReflectionData", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, assemblyDefinition.MainModule.ImportReference(typeof(void)));

            funcDef.Body.InitLocals = false;

            classDef.Methods.Add(funcDef);

            var body      = funcDef.Body;
            var processor = body.GetILProcessor();

            bool anythingChanged = false;

            var declaredGenerics = new HashSet <string>();
            var genericJobs      = new List <TypeReference>();

            foreach (var attr in assemblyDefinition.CustomAttributes)
            {
                if (attr.AttributeType.FullName != RegisterGenericJobTypeAttributeName)
                {
                    continue;
                }

                var openTypeRef = (TypeReference)attr.ConstructorArguments[0].Value;
                var openType    = assemblyDefinition.MainModule.ImportReference(openTypeRef).Resolve();

                if (!openTypeRef.IsGenericInstance || !openType.IsValueType)
                {
                    AddDiagnostic(UserError.DC3001(openType));
                    continue;
                }

                TypeReference result = new GenericInstanceType(assemblyDefinition.MainModule.ImportReference(new TypeReference(openType.Namespace, openType.Name, assemblyDefinition.MainModule, openTypeRef.Scope, true)));

                foreach (var ga in ((GenericInstanceType)openTypeRef).GenericArguments)
                {
                    ((GenericInstanceType)result).GenericArguments.Add(LaunderTypeRef(ga, assemblyDefinition.MainModule));
                }

                genericJobs.Add(result);


                var fn = openType.FullName;
                if (!declaredGenerics.Contains(fn))
                {
                    declaredGenerics.Add(fn);
                }
            }

            foreach (var t in assemblyDefinition.MainModule.Types)
            {
                anythingChanged |= VisitJobStructs(t, processor, body, declaredGenerics);
            }

            foreach (var t in genericJobs)
            {
                anythingChanged |= VisitJobStructs(t, processor, body, declaredGenerics);
            }

            processor.Emit(OpCodes.Ret);

            if (anythingChanged)
            {
                var ctorFuncDef = new MethodDefinition("EarlyInit", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, assemblyDefinition.MainModule.ImportReference(typeof(void)));

#if !UNITY_DOTSRUNTIME
                if (!Defines.Contains("UNITY_DOTSPLAYER") && !Defines.Contains("UNITY_EDITOR"))
                {
                    // Needs to run automatically in the player, but we need to
                    // exclude this attribute when building for the editor, or
                    // it will re-run the registration for every enter play mode.
                    var loadTypeEnumType = assemblyDefinition.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeLoadType));
                    var attributeCtor    = assemblyDefinition.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(UnityEngine.RuntimeInitializeLoadType) }));
                    var attribute        = new CustomAttribute(attributeCtor);
                    attribute.ConstructorArguments.Add(new CustomAttributeArgument(loadTypeEnumType, UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded));
                    ctorFuncDef.CustomAttributes.Add(attribute);
                }

                if (Defines.Contains("UNITY_EDITOR"))
                {
                    // Needs to run automatically in the editor.
                    var attributeCtor2 = assemblyDefinition.MainModule.ImportReference(typeof(UnityEditor.InitializeOnLoadMethodAttribute).GetConstructor(Type.EmptyTypes));
                    ctorFuncDef.CustomAttributes.Add(new CustomAttribute(attributeCtor2));
                }
#endif

                ctorFuncDef.Body.InitLocals = false;

                var p = ctorFuncDef.Body.GetILProcessor();

                p.Emit(OpCodes.Ldnull);
                p.Emit(OpCodes.Ldftn, funcDef);

                var delegateType = assemblyDefinition.MainModule.ImportReference(earlyInitHelpers.NestedTypes.First(x => x.Name == nameof(EarlyInitHelpers.EarlyInitFunction)));
                var delegateCtor = assemblyDefinition.MainModule.ImportReference(delegateType.CheckedResolve().GetConstructors().FirstOrDefault((x) => x.Parameters.Count == 2));
                p.Emit(OpCodes.Newobj, delegateCtor);

                p.Emit(OpCodes.Call, assemblyDefinition.MainModule.ImportReference(earlyInitHelpers.Methods.First(x => x.Name == nameof(EarlyInitHelpers.AddEarlyInitFunction))));

                p.Emit(OpCodes.Ret);

                classDef.Methods.Add(ctorFuncDef);

                assemblyDefinition.MainModule.Types.Add(classDef);
            }

            return(anythingChanged);
        }
        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);
        }
Ejemplo n.º 20
0
        public static LambdaParamaterValueProviderInformation ElementProviderInformationFor(
            LambdaJobDescriptionConstruction lambdaJobDescriptionConstruction, ParameterDefinition parameter, bool withStructuralChanges)
        {
            var moduleDefinition = lambdaJobDescriptionConstruction.ContainingMethod.Module;

            (TypeReference provider, TypeReference providerRuntime) ImportReferencesFor(Type providerType, Type runtimeType, TypeReference typeOfT)
            {
                var provider = moduleDefinition
                               .ImportReference(providerType)
                               .MakeGenericInstanceType(typeOfT);
                var providerRuntime = moduleDefinition.ImportReference(runtimeType).MakeGenericInstanceType(typeOfT);

                return(provider, providerRuntime);
            }

            var parameterType         = parameter.ParameterType;
            var resolvedParameterType = parameterType.Resolve();

            // IComponentData
            if (resolvedParameterType.IsIComponentDataStruct())
            {
                var readOnly = !parameter.ParameterType.IsByReference || HasCompilerServicesIsReadOnlyAttribute(parameter);

                if (resolvedParameterType.IsTagComponentDataStruct())
                {
                    var(provider, providerRuntime) =
                        ImportReferencesFor(
                            typeof(LambdaParameterValueProvider_IComponentData_Tag <>),
                            typeof(LambdaParameterValueProvider_IComponentData_Tag <> .Runtime), parameter.ParameterType.GetElementType());
                    return(new LambdaParamaterValueProviderInformation(provider, providerRuntime, readOnly, parameter.Name, null, false));
                }
                else
                {
                    var(provider, providerRuntime) =
                        ImportReferencesFor(
                            typeof(LambdaParameterValueProvider_IComponentData <>),
                            withStructuralChanges
                                ? typeof(LambdaParameterValueProvider_IComponentData <> .StructuralChangeRuntime)
                            : typeof(LambdaParameterValueProvider_IComponentData <> .Runtime), parameter.ParameterType.GetElementType());
                    return(new LambdaParamaterValueProviderInformation(provider, providerRuntime, readOnly, parameter.Name, null, withStructuralChanges));
                }
            }

            // class IComponentData / UnityEngine.Object
            if (resolvedParameterType.IsIComponentDataClass() || resolvedParameterType.IsUnityEngineObject())
            {
                if (lambdaJobDescriptionConstruction.UsesBurst || lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                {
                    UserError.DC0023(lambdaJobDescriptionConstruction.ContainingMethod, parameterType, lambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
                }

                bool readOnly = false;
                if (parameter.ParameterType.IsByReference)
                {
                    if (HasCompilerServicesIsReadOnlyAttribute(parameter))
                    {
                        readOnly = true;
                    }
                    else
                    {
                        UserError.DC0024(lambdaJobDescriptionConstruction.ContainingMethod, parameterType, lambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
                    }
                }

                var(provider, providerRuntime) = ImportReferencesFor(typeof(LambdaParameterValueProvider_ManagedComponentData <>),
                                                                     withStructuralChanges
                        ? typeof(LambdaParameterValueProvider_ManagedComponentData <> .StructuralChangeRuntime)
                        : typeof(LambdaParameterValueProvider_ManagedComponentData <> .Runtime), parameter.ParameterType.GetElementType());
                return(new LambdaParamaterValueProviderInformation(provider, providerRuntime, readOnly, parameter.Name, parameter.ParameterType.GetElementType()));
            }

            // DynamicBuffer<T>
            if (resolvedParameterType.IsDynamicBufferOfT())
            {
                TypeReference typeRef = parameterType;
                if (parameterType is ByReferenceType referenceType)
                {
                    typeRef = referenceType.ElementType;
                }

                GenericInstanceType bufferOfT         = (GenericInstanceType)typeRef;
                TypeReference       bufferElementType = bufferOfT.GenericArguments[0];
                var(provider, providerRuntime) = ImportReferencesFor(typeof(LambdaParameterValueProvider_DynamicBuffer <>),
                                                                     withStructuralChanges
                        ? typeof(LambdaParameterValueProvider_DynamicBuffer <> .StructuralChangeRuntime)
                        : typeof(LambdaParameterValueProvider_DynamicBuffer <> .Runtime), bufferElementType);
                return(new LambdaParamaterValueProviderInformation(provider, providerRuntime, false, parameter.Name));
            }

            // ISharedComponent
            if (resolvedParameterType.IsISharedComponentData())
            {
                if (lambdaJobDescriptionConstruction.ExecutionMode != ExecutionMode.Run || lambdaJobDescriptionConstruction.UsesBurst)
                {
                    UserError.DC0019(lambdaJobDescriptionConstruction.ContainingMethod, parameter.ParameterType.GetElementType(), lambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
                }

                if (!parameter.HasCompilerServicesIsReadOnlyAttribute() && parameter.ParameterType.IsByReference)
                {
                    UserError.DC0020(lambdaJobDescriptionConstruction.ContainingMethod, parameter.ParameterType.GetElementType(), lambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
                }

                var(provider, providerRuntime) = ImportReferencesFor(typeof(LambdaParameterValueProvider_ISharedComponentData <>),
                                                                     withStructuralChanges
                        ? typeof(LambdaParameterValueProvider_ISharedComponentData <> .StructuralChangeRuntime)
                        : typeof(LambdaParameterValueProvider_ISharedComponentData <> .Runtime), parameter.ParameterType.GetElementType());
                var newProvider = new LambdaParamaterValueProviderInformation(provider, providerRuntime, false, parameter.Name, parameter.ParameterType.GetElementType(), withStructuralChanges);

                return(newProvider);
            }

            if (resolvedParameterType.TypeReferenceEquals(moduleDefinition.ImportReference(typeof(Entity))))
            {
                var provider = moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_Entity));
                var runtime  = withStructuralChanges
                    ? moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_Entity.StructuralChangeRuntime))
                    : moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_Entity.Runtime));

                return(new LambdaParamaterValueProviderInformation(provider, runtime, true, parameter.Name, null, withStructuralChanges));
            }

            if (resolvedParameterType.FullName == moduleDefinition.TypeSystem.Int32.FullName)
            {
                var    allNames = new[] { "entityInQueryIndex", "nativeThreadIndex" };
                string entityInQueryIndexName = allNames[0];
                string nativeThreadIndexName  = allNames[1];

                if (parameter.Name == entityInQueryIndexName)
                {
                    var provider = moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_EntityInQueryIndex));
                    var runtime  = withStructuralChanges
                        ? moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_EntityInQueryIndex.StructuralChangeRuntime))
                        : moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_EntityInQueryIndex.Runtime));
                    return(new LambdaParamaterValueProviderInformation(provider, runtime, true, parameter.Name));
                }

                if (parameter.Name == nativeThreadIndexName)
                {
                    var provider = moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_NativeThreadIndex));
                    var runtime  = moduleDefinition.ImportReference(typeof(LambdaParameterValueProvider_NativeThreadIndex.Runtime));
#if !UNITY_DOTSPLAYER
                    var isReadonly = true;
#else
                    // Tiny's Job System currently will set the NativeThreadIndex at the beginning of the function to be Bursted.
                    // This will make Burst Compilation fail due to the NativeThreadIndex being marked as Readonly here. So for now
                    // until we workaround this issue in the Tiny Job System, we disable marking NativeThreadIndex as Readonly.
                    var isReadonly = false;
#endif
                    return(new LambdaParamaterValueProviderInformation(provider, runtime, isReadonly, parameter.Name));
                }

                UserError.DC0014(lambdaJobDescriptionConstruction.ContainingMethod, lambdaJobDescriptionConstruction.WithCodeInvocationInstruction, parameter, allNames).Throw();
            }

            if (resolvedParameterType.IsIBufferElementData())
            {
                UserError.DC0033(lambdaJobDescriptionConstruction.ContainingMethod, parameter.Name, parameter.ParameterType.GetElementType(), lambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
            }

            if (!resolvedParameterType.GetElementType().IsPrimitive&& resolvedParameterType.GetElementType().IsValueType())
            {
                UserError.DC0021(lambdaJobDescriptionConstruction.ContainingMethod, parameter.Name, parameter.ParameterType.GetElementType(), lambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
            }

            UserError.DC0005(lambdaJobDescriptionConstruction.ContainingMethod, lambdaJobDescriptionConstruction.WithCodeInvocationInstruction, parameter).Throw();
            return(null);
        }
Ejemplo n.º 21
0
 public static DiagnosticMessage DCICE008(MethodDefinition methodToAnalyze, string methodName, Instruction methodCallInstruction)
 {
     return(UserError.MakeError(nameof(DCICE008), $"Unable to patch method {methodName} in {methodToAnalyze.Name}.  Missing expected IL to load constant while parsing.", methodToAnalyze, methodCallInstruction));
 }
Ejemplo n.º 22
0
 public static DiagnosticMessage DCICE300(TypeReference producerReference, TypeReference jobStructType, Exception ex)
 {
     return(UserError.MakeError(nameof(DCICE010), $"Unexpected error while generating automatic registration for job provider {producerReference.FullName} via job struct {jobStructType.FullName}. Please report this error.\nException: {ex.Message}", method: null, instruction: null));
 }
 public static DiagnosticMessage DCICE003(MethodDefinition method, Instruction instruction)
 {
     return(UserError.MakeError(nameof(DCICE003), $"Discovered multiple assignments of DisplayClass in {method.Name}.  This indicates unexpected IL from Roslyn.", method, instruction));
 }
Ejemplo n.º 24
0
        public static (JobStructForLambdaJob, List <DiagnosticMessage>) Rewrite(MethodDefinition methodContainingLambdaJob, LambdaJobDescriptionConstruction lambdaJobDescriptionConstruction)
        {
            var diagnosticMessages = new List <DiagnosticMessage>();

            if (methodContainingLambdaJob.DeclaringType.CustomAttributes.Any(c => (c.AttributeType.Name == "ExecuteAlways" && c.AttributeType.Namespace == "UnityEngine")))
            {
                diagnosticMessages.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.
                // Here is an example: https://sharplab.io/#v2:D4AQTAjAsAUCDMACciDCiDetE8QMwBsB7AQwBd8BLAUwIBNEBeReAOgAY8BubXXnBMgAsiACoALSgGcA4tQB21AE7lqUgLLUAtgCNlIAKxlKReVIAUASiwwAkLYD0DsZKmICJXXRKIA7pQICRABzBWVVRB8tbT0lfABXeQBjY1NENLJxamQwNFYXbKVqEik06QAuWHsnHABaAvdPHW90+QIAT0QkkgAHMniitx88Gnp8gEkzMmKGIjwu3v6lSnlgxEzpRGoADx6CSiTKMg6AGirHZ1xfbO75ELCVacjEaMyiWbv0MiJEPS3t6hJeLTBgrKTTEh0fi4HAAESIIAgYHMViYAD4bDCsThUKZSgRqKwAOrLabmEa0OiWLiIaEwgC+1LpfBg2JwNQkmw8Xh8/kC90Uj2yURiygSyVSdwyWRyeQaRRKZSklVZbJqiHqEmy3OaPlMHUQRQAjvFKINEAByDZSC2ReQMHolKRqRBHdY/YaJFImeSsZlwhFIlGWdGYtm4eGc1YWa1M1XYxk8eOIelVaGCEAiTmyB6qKQAQVQHikFhDNmqzi1zvWvh+Ou8biyRQF4SePnA+S1huKpTumxK+CIgSIvmV53V9Uy2WdSVMDHrPm6fQGLp8xG6QRI9vW4nIg6USRdU66RC0PQCYu+Wy0R3Hlxw7dyV5IKXiJECnXEQ4Yx/BETmO7akQG53nUgFUEo4KNDyrQGkuSyrlQlJ2j+MqzmeF5xLW8T0Ig8RSG+H6IAAVvhZCgbg2huiKuhingXqSpEbgrOBIyQRQ3TOicvzAogUgrIegHNu+Cp0J00gUQ+sp4EQcQLkM8jtL4JDtNxbp8kE/FngaRT4dkmR7qYhLnPCiLIqijAYucti4mYQ6EiSRzUOSoxUjS/opnG4YeSsFDbEwiDsEm4amUGFlWXY9i2fiDmks5FL0NStKRTZeL2cScXmNsXlsom0Kpsm6YiKFyJmZEKRlgVMLphAABswiIJGkjRuY6BJJVsD0kAA=
                var illegalFieldRead = methodLambdaWasEmittedAs.Body.Instructions.FirstOrDefault(IsIllegalFieldRead);
                if (illegalFieldRead != null)
                {
                    UserError.DC0001(methodContainingLambdaJob, illegalFieldRead, (FieldReference)illegalFieldRead.Operand).Throw();
                }

                // Similarly, we could end up here because the lambda captured neither a field nor a local but simply
                // uses the this-reference. This could be the case if the user calls a function that takes this as a
                // parameter, either explicitly (static) or implicitly (extension or member method).
                var illegalInvocations = methodLambdaWasEmittedAs.Body.Instructions.Where(IsIllegalInvocation).ToArray();
                if (illegalInvocations.Any())
                {
                    foreach (var illegalInvocation in illegalInvocations)
                    {
                        if (!IsPermittedIllegalInvocation(illegalInvocation))
                        {
                            UserError.DC0002(methodContainingLambdaJob, illegalInvocation,
                                             (MethodReference)illegalInvocation.Operand, methodLambdaWasEmittedAs.DeclaringType).Throw();
                        }
                    }

                    // We only had illegal invocations where we were invoking an allowed method on our declaring type.
                    // This is allowed as we will rewrite these invocations.
                    // When we clone this method we need to rewrite it correctly to reflect that this is the case.
                    lambdaJobDescriptionConstruction.HasAllowedMethodInvokedWithThis = true;
                }

                // 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.
                // We do need to allow this case when we have an allowed method invoked with this that is later replaced with codegen.
                if (!lambdaJobDescriptionConstruction.HasAllowedMethodInvokedWithThis)
                {
                    InternalCompilerError.DCICE001(methodContainingLambdaJob).Throw();
                }

                bool IsIllegalFieldRead(Instruction i)
                {
                    if (i.Previous == null)
                    {
                        return(false);
                    }
                    if (i.OpCode != OpCodes.Ldfld && i.OpCode != OpCodes.Ldflda)
                    {
                        return(false);
                    }
                    return(i.Previous.OpCode == OpCodes.Ldarg_0);
                }

                bool IsIllegalInvocation(Instruction i)
                {
                    if (!i.IsInvocation(out _))
                    {
                        return(false);
                    }
                    var declaringType = methodContainingLambdaJob.DeclaringType;
                    var method        = (MethodReference)i.Operand;

                    // is it an instance method?
                    var resolvedMethod = method.Resolve();

                    if (declaringType.TypeReferenceEqualsOrInheritsFrom(method.DeclaringType) && !resolvedMethod.IsStatic)
                    {
                        return(true);
                    }

                    // is it a method that potentially takes this as a parameter?
                    foreach (var param in method.Parameters)
                    {
                        if (declaringType.TypeReferenceEqualsOrInheritsFrom(param.ParameterType) || declaringType.TypeImplements(param.ParameterType))
                        {
                            return(true);
                        }
                    }
                    return(false);
                }

                // Check for permitted illegal invocations
                // These are due to calling a method that we later stub out with codegen or also can be due to calling
                // a local method that contains a method that we stub out with codegen.
                bool IsPermittedIllegalInvocation(Instruction instruction)
                {
                    // Check to see if this method is permitted
                    if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt)
                    {
                        var methodRef = instruction.Operand as MethodReference;
                        if (JobStructForLambdaJob.IsPermittedMethodToInvokeWithThis(methodRef))
                        {
                            return(true);
                        }
                        else
                        {
                            // Recurse into methods if they are compiler generated local methods
                            var methodDef = methodRef.Resolve();
                            if (methodDef != null && methodDef.CustomAttributes.Any(c =>
                                                                                    c.AttributeType.Name == nameof(CompilerGeneratedAttribute) &&
                                                                                    c.AttributeType.Namespace == typeof(CompilerGeneratedAttribute).Namespace))
                            {
                                foreach (var methodInstruction in methodDef.Body.Instructions)
                                {
                                    if (IsIllegalInvocation(methodInstruction) && !IsPermittedIllegalInvocation(methodInstruction))
                                    {
                                        return(false);
                                    }
                                }

                                return(true);
                            }
                        }
                    }

                    return(false);
                }
            }

            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 (allDelegatesAreGuaranteedNotToOutliveMethod)
                {
                    ChangeAllDisplayClassesToStructs(methodContainingLambdaJob);
                }
            }
            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);

                bool storeJobHandleInVariable = (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule ||
                                                 lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.ScheduleParallel);
                VariableDefinition tempStorageForJobHandle = null;

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

                    // If we aren't using an implicit system dependency and 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.

                    // If we are using implicit system dependency, lets put that in our temp instead.
                    if (lambdaJobDescriptionConstruction.UseImplicitSystemDependency)
                    {
                        yield return(Instruction.Create(OpCodes.Ldarg_0));

                        yield return(Instruction.Create(OpCodes.Call, moduleDefinition.ImportReference(typeof(SystemBase).GetMethod("get_Dependency", BindingFlags.Instance | BindingFlags.NonPublic))));
                    }
                    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.IsInSystemBase)
                        {
                            switch (lambdaJobDescriptionConstruction.ExecutionMode)
                            {
                            case ExecutionMode.Run:
                                return(typeof(InternalCompilerInterface).GetMethod(nameof(InternalCompilerInterface.RunJobChunk)));

                            case ExecutionMode.Schedule:
                                return(typeof(JobChunkExtensions).GetMethod(nameof(JobChunkExtensions.ScheduleSingle)));

                            case ExecutionMode.ScheduleParallel:
                                return(typeof(JobChunkExtensions).GetMethod(nameof(JobChunkExtensions.ScheduleParallel)));

                            default:
                                throw new ArgumentOutOfRangeException();
                            }
                        }
                        else
                        {
                            // Keep legacy behaviour in JobComponentSystems intact (aka "Schedule" equals "ScheduleParallel")
                            if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                            {
                                return(typeof(JobChunkExtensions).GetMethod(nameof(JobChunkExtensions.ScheduleParallel)));
                            }
                            return(typeof(InternalCompilerInterface).GetMethod(nameof(InternalCompilerInterface.RunJobChunk)));
                        }

                    case LambdaJobDescriptionKind.Job:
                        if (lambdaJobDescriptionConstruction.IsInSystemBase)
                        {
                            switch (lambdaJobDescriptionConstruction.ExecutionMode)
                            {
                            case ExecutionMode.Run:
                                return(typeof(InternalCompilerInterface).GetMethod(nameof(InternalCompilerInterface.RunIJob)));

                            case ExecutionMode.Schedule:
                                return(typeof(IJobExtensions).GetMethod(nameof(IJobExtensions.Schedule)));

                            default:
                                throw new ArgumentOutOfRangeException();
                            }
                        }
                        else
                        {
                            if (lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Schedule)
                            {
                                return(typeof(IJobExtensions).GetMethod(nameof(IJobExtensions.Schedule)));
                            }
                            return(typeof(InternalCompilerInterface).GetMethod(nameof(InternalCompilerInterface.RunIJob)));
                        }

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                }

                // Call CompleteDependency method to complete previous dependencies if we are running in SystemBase
                if (lambdaJobDescriptionConstruction.IsInSystemBase && lambdaJobDescriptionConstruction.ExecutionMode == ExecutionMode.Run)
                {
                    yield return(Instruction.Create(OpCodes.Ldarg_0));

                    yield return(Instruction.Create(OpCodes.Call,
                                                    moduleDefinition.ImportReference(typeof(SystemBase).GetMethod("CompleteDependency", BindingFlags.Instance | BindingFlags.NonPublic))));
                }

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

                // Store returned JobHandle in temp varaible or back in SystemBase.depenedency
                if (storeJobHandleInVariable)
                {
                    yield return(Instruction.Create(OpCodes.Ldloc, tempStorageForJobHandle));
                }
                else if (lambdaJobDescriptionConstruction.UseImplicitSystemDependency)
                {
                    yield return(Instruction.Create(OpCodes.Ldarg_0));

                    yield return(Instruction.Create(OpCodes.Call,
                                                    moduleDefinition.ImportReference(typeof(SystemBase).GetMethod("get_Dependency", BindingFlags.Instance | BindingFlags.NonPublic))));
                }

                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.UseImplicitSystemDependency)
                {
                    yield return(Instruction.Create(OpCodes.Stloc, tempStorageForJobHandle));

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

                    yield return(Instruction.Create(OpCodes.Ldloc, tempStorageForJobHandle));

                    yield return(Instruction.Create(OpCodes.Call,
                                                    moduleDefinition.ImportReference(typeof(SystemBase).GetMethod("set_Dependency", BindingFlags.Instance | BindingFlags.NonPublic))));
                }

                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)));
                        var callingTypeReference = lambdaJobDescriptionConstruction.IsInSystemBase
                            ? moduleDefinition.ImportReference(typeof(ForEachLambdaJobDescription))
                            : moduleDefinition.ImportReference(typeof(ForEachLambdaJobDescriptionJCS));
                        MethodReference genericSetSharedComponentFilterOnQueryMethod
                            = setSharedComponentFilterOnQueryMethod.MakeGenericInstanceMethod(
                                  new TypeReference[] { callingTypeReference }.Concat(invokedMethod.TypeArguments).ToArray());

                        // 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, diagnosticMessages);
        }
 public static DiagnosticMessage DCICE004(MethodDefinition method, Instruction instruction)
 {
     return(UserError.MakeError(nameof(DCICE004), $"Next instruction in initialization of DisplayClass needs to be stloc in {method.Name}", method, instruction));
 }
Ejemplo n.º 26
0
        void PatchInstructionToComponentAccessMethod(MethodDefinition method, Instruction instruction, PatchableMethod unpatchedMethod)
        {
            var ilProcessor                  = method.Body.GetILProcessor();
            var componentAccessMethod        = (GenericInstanceMethod)instruction.Operand;
            var componentDataType            = componentAccessMethod.GenericArguments.First();
            var componentDataFromEntityField = GetOrCreateComponentDataFromEntityField(componentDataType, unpatchedMethod.ReadOnly);

            // Make sure our componentDataFromEntityField doesn't give write access to a lambda parameter of the same type
            // or there is a writable lambda parameter that gives access to this type (either could violate aliasing rules).
            foreach (var parameter in LambdaParameters)
            {
                if (parameter.ParameterType.GetElementType().TypeReferenceEquals(componentDataType))
                {
                    if (!unpatchedMethod.ReadOnly)
                    {
                        UserError.DC0046(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                    else if (!parameter.HasCompilerServicesIsReadOnlyAttribute())
                    {
                        UserError.DC0047(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                }
            }

            // Find where we pushed the this argument and make it nop
            // Note: we don't want to do this when our method was inserted into our declaring type (in the case where we aren't capturing).
            var instructionThatPushedThis = CecilHelpers.FindInstructionThatPushedArg(method, 0, instruction, true);

            if (instructionThatPushedThis == null)
            {
                UserError.DC0045(method, componentAccessMethod.Name, instruction).Throw();
            }

            //this instruction is responsible for pushing the systembase 'this' object, that we called GetComponent<T>(Entity e) or its friends on.
            //there are two cases where this instance can come from, depending on how roslyn emitted the code. Either the original system
            //was captured into a <>_this variable in our displayclass.  in this case the IL will look like:
            //
            //ldarg0
            //ldfld <>__this
            //IL to load entity
            //call GetComponent<T>
            //
            //or we got emitted without a displayclass, and our method is on the
            //actual system itself, and in that case the system is just ldarg0:
            //
            //ldarg0
            //IL to load entity
            //call GetComponent<T>

            //the output IL that we want looks like this:
            //ldarg0
            //ldfld componentdatafromentity
            //IL to load entity
            //call componentdatafromentity.getcomponent<t>(entity e);
            //
            //so the changes we are going to do is remove that original ldfld if it existed, and add the ldfld for our componentdatafromentity
            //and then patch the callsite target.

            if (instructionThatPushedThis.OpCode == OpCodes.Ldfld)
            {
                instructionThatPushedThis.MakeNOP();
            }

            // Insert Ldflda of componentDataFromEntityField after that point
            var componentDataFromEntityFieldInstruction = CecilHelpers.MakeInstruction(OpCodes.Ldflda, componentDataFromEntityField);

            ilProcessor.InsertAfter(instructionThatPushedThis, componentDataFromEntityFieldInstruction);

            // Replace method that we invoke from SystemBase method to ComponentDataFromEntity<T> method (HasComponent, get_Item or set_Item)
            var componentDataFromEntityTypeDef = componentDataFromEntityField.FieldType.Resolve();
            var itemAccessMethod = TypeDefinition.Module.ImportReference(
                componentDataFromEntityTypeDef.Methods.Single(m => m.Name == unpatchedMethod.PatchedMethod));
            var closedGetItemMethod = itemAccessMethod.MakeGenericHostMethod(componentDataFromEntityField.FieldType);

            instruction.Operand = TypeDefinition.Module.ImportReference(closedGetItemMethod);
        }
Ejemplo n.º 27
0
        public static void VerifyMethod(MethodDefinition method, HashSet <TypeReference> _nonRestrictedTypes)
        {
            bool IsTypeRestrictedToBlobAssetStorage(TypeReference tr)
            {
                if (tr.IsPrimitive)
                {
                    return(false);
                }
                if (tr is GenericParameter)
                {
                    return(false);
                }
                if (tr is PointerType)
                {
                    return(false);
                }
                if (tr is ArrayType)
                {
                    return(false);
                }
                if (tr is GenericInstanceType)
                {
                    tr = tr.GetElementType();
                }
                if (_nonRestrictedTypes.Contains(tr))
                {
                    return(false);
                }

                if (tr.Scope is AssemblyNameReference anr)
                {
                    if (anr.Name == "UnityEngine" || anr.Name == "UnityEditor" || anr.Name == "mscorlib" ||
                        anr.Name == "System.Private.CoreLib")
                    {
                        return(false);
                    }
                }

                var td = tr.CheckedResolve();

                if (td.IsValueType())
                {
                    if (HasMayOnlyLiveInBlobStorageAttribute(td))
                    {
                        return(true);
                    }

                    foreach (var field in td.Fields)
                    {
                        if (field.IsStatic)
                        {
                            continue;
                        }
                        if (IsTypeRestrictedToBlobAssetStorage(field.FieldType))
                        {
                            return(true);
                        }
                    }
                }

                _nonRestrictedTypes.Add(tr);
                return(false);
            }

            foreach (var instruction in method.Body.Instructions)
            {
                if (instruction.OpCode == OpCodes.Ldfld)
                {
                    var fieldReference = (FieldReference)instruction.Operand;
                    var tr             = fieldReference.FieldType;
                    if (IsTypeRestrictedToBlobAssetStorage(tr))
                    {
                        var fancyName = FancyNameFor(fieldReference.FieldType);

                        string error =
                            $"ref {fancyName} yourVariable = ref your{fieldReference.DeclaringType.Name}.{fieldReference.Name}";

                        UserError.MakeError("MayOnlyLiveInBlobStorageViolation",
                                            $"You may only access .{fieldReference.Name} by ref, as it may only live in blob storage. try `{error}`",
                                            method, instruction).Throw();
                    }
                }

                if (instruction.OpCode == OpCodes.Ldobj)
                {
                    var tr = (TypeReference)instruction.Operand;
                    if (IsTypeRestrictedToBlobAssetStorage(tr))
                    {
                        var pushingInstruction = CecilHelpers.FindInstructionThatPushedArg(method, 0, instruction);

                        string error = $"ref {tr.Name} yourVariable = ref ...";
                        if (pushingInstruction.Operand is FieldReference fr)
                        {
                            var typeName = fr.DeclaringType.Name;
                            error = $"ref {tr.Name} yourVariable = ref your{typeName}.{fr.Name}";
                        }

                        UserError.MakeError("MayOnlyLiveInBlobStorageViolation",
                                            $"{tr.Name} may only live in blob storage. Access it by ref instead: `{error}`", method,
                                            instruction).Throw();
                    }
                }
            }
        }
Ejemplo n.º 28
0
 public static DiagnosticMessage DCICE009(MethodDefinition method, Instruction instruction, FieldDefinition field)
 {
     return(UserError.MakeError(nameof(DCICE009), $"Attempting to dispose {field.Name} in {method.Name} but it's type {field.FieldType} does not have a valid Dispose method.", method, instruction));
 }
Ejemplo n.º 29
0
        // This method is responsible for transforming the IL that calls a component access method (GetComponent/SetComponent/etc)
        // into the IL that loads a previously created ComponentDataFromEntity and either calls a method (get_Item/set_Item/HasComponent)
        // or leaves it on the stack (in the case where the method being replaced is GetComponentDataFromEntity).
        //
        // For the source IL, there are three cases where the this instance can come from, depending on how roslyn emitted the code:
        //
        // 1. Either the original system was captured into a <>_this variable in our DisplayClass.  In this case the IL will look like:
        // ldarg0
        // ldfld <>__this
        // IL to load entity
        // call GetComponent<T>
        //
        // 2. Or we got emitted without a DisplayClass, and our method is on the actual system itself, and in that case the system is just ldarg0:
        // ldarg0
        // IL to load entity
        // call GetComponent<T>
        //
        // 3. OR we captured from multiple scopes, in which case <>this will live inside of another DisplayClass:
        // ldarg.0
        // ldfld valuetype '<>c__DisplayClass0_1'::'CS$<>8__locals1'
        // ldfld class '<>c__DisplayClass0_0'::'<>4__this'
        // ldarg.1
        // call GetComponent<T>

        // And the output IL that we want looks like this:
        // ldarg0
        // ldfld ComponentDataFromEntity
        // IL to load entity
        // call ComponentDataFromEntity.GetComponent<t>(entity e);
        //
        // So the changes we are going to do is remove that original ldfld if it existed, and add the ldfld for our ComponentDataFromEntity
        // and then patch the callsite target.  We also need to get nop any ldfld instructions that were used to load the nested DisplayClasses
        // in the case where we captured locals from multiple scopes.
        void PatchInstructionToComponentAccessMethod(MethodDefinition method, Instruction instruction, PatchableMethod patchableMethod)
        {
            method.Body.SimplifyMacros();
            var ilProcessor           = method.Body.GetILProcessor();
            var componentAccessMethod = (GenericInstanceMethod)instruction.Operand;
            var componentDataType     = componentAccessMethod.GenericArguments.First();

            bool        readOnlyAccess = true;
            Instruction instructionThatPushedROAccess = null;

            switch (patchableMethod.AccessRights)
            {
            case PatchableMethod.ComponentAccessRights.ReadOnly:
                readOnlyAccess = true;
                break;

            case PatchableMethod.ComponentAccessRights.ReadWrite:
                readOnlyAccess = false;
                break;

            // Get read-access from method's param (we later nop the instruction that loads)
            case PatchableMethod.ComponentAccessRights.GetFromFirstMethodParam:
                instructionThatPushedROAccess =
                    CecilHelpers.FindInstructionThatPushedArg(componentAccessMethod.ElementMethod.Resolve(), 1, instruction, true);
                if (instructionThatPushedROAccess.IsLoadConstantInt(out var intVal))
                {
                    readOnlyAccess = intVal != 0;
                }
                else
                {
                    if (instructionThatPushedROAccess.IsInvocation(out var _))
                    {
                        UserError.DC0048(method, patchableMethod.UnpatchedMethod, instruction).Throw();
                    }
                    else if (instructionThatPushedROAccess.IsLoadLocal(out _) ||
                             instructionThatPushedROAccess.IsLoadArg(out _) ||
                             instructionThatPushedROAccess.IsLoadFieldOrLoadFieldAddress())
                    {
                        UserError.DC0049(method, patchableMethod.UnpatchedMethod, instruction).Throw();
                    }
                    else
                    {
                        InternalCompilerError.DCICE008(method, patchableMethod.UnpatchedMethod, instruction).Throw();
                    }
                }
                break;
            }

            var componentDataFromEntityField = GetOrCreateComponentDataFromEntityField(componentDataType, readOnlyAccess);

            // Make sure our componentDataFromEntityField doesn't give write access to a lambda parameter of the same type
            // or there is a writable lambda parameter that gives access to this type (either could violate aliasing rules).
            foreach (var parameter in LambdaParameters)
            {
                if (parameter.ParameterType.GetElementType().TypeReferenceEquals(componentDataType))
                {
                    if (!readOnlyAccess)
                    {
                        UserError.DC0046(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                    else if (!parameter.HasCompilerServicesIsReadOnlyAttribute())
                    {
                        UserError.DC0047(method, componentAccessMethod.Name, componentDataType.Name, instruction).Throw();
                    }
                }
            }

            // Find where we pushed the this argument and make it nop
            // Note: we don't want to do this when our method was inserted into our declaring type (in the case where we aren't capturing).
            var instructionThatPushedThis = CecilHelpers.FindInstructionThatPushedArg(method, 0, instruction, true);

            if (instructionThatPushedThis == null)
            {
                UserError.DC0045(method, componentAccessMethod.Name, instruction).Throw();
            }

            // Nop the ldfld for this
            if (instructionThatPushedThis.OpCode == OpCodes.Ldfld)
            {
                instructionThatPushedThis.MakeNOP();
            }

            // Nop any ldflds of nested DisplayClasses
            var previousInstruction = instructionThatPushedThis.Previous;

            while (previousInstruction != null && previousInstruction.OpCode == OpCodes.Ldfld &&
                   ((FieldReference)previousInstruction.Operand).IsNestedDisplayClassField())
            {
                previousInstruction.MakeNOP();
                previousInstruction = previousInstruction.Previous;
            }

            // Insert Ldflda of componentDataFromEntityField after that point
            var componentDataFromEntityFieldInstruction = CecilHelpers.MakeInstruction(
                patchableMethod.AccessFieldAsRef ? OpCodes.Ldflda : OpCodes.Ldfld, componentDataFromEntityField);

            ilProcessor.InsertAfter(instructionThatPushedThis, componentDataFromEntityFieldInstruction);

            // Replace method that we invoke from SystemBase method to ComponentDataFromEntity<T> method if we have one
            // (HasComponent, get_Item or set_Item).  Otherwise nop.
            if (patchableMethod.PatchedMethod != null)
            {
                var componentDataFromEntityTypeDef = componentDataFromEntityField.FieldType.Resolve();
                var itemAccessMethod = TypeDefinition.Module.ImportReference(
                    componentDataFromEntityTypeDef.Methods.Single(m => m.Name == patchableMethod.PatchedMethod));
                var closedGetItemMethod =
                    itemAccessMethod.MakeGenericHostMethod(componentDataFromEntityField.FieldType);
                instruction.Operand = TypeDefinition.Module.ImportReference(closedGetItemMethod);
            }
            else
            {
                instruction.MakeNOP();
            }

            // Handle special case where we have an instruction that pushed an argument for Read/Write access, nop that out
            instructionThatPushedROAccess?.MakeNOP();
            method.Body.OptimizeMacros();
        }
        private bool GenerateCalls(TypeReference producerRef, TypeReference jobStructType, MethodBody body, ILProcessor processor, HashSet <string> declaredGenerics)
        {
            try
            {
                var carrierType = producerRef.CheckedResolve();
                MethodDefinition methodToCall = null;
                while (carrierType != null)
                {
                    methodToCall = carrierType.GetMethods().FirstOrDefault((x) => x.Name == "EarlyJobInit" && x.Parameters.Count == 0 && x.IsStatic && x.IsPublic);

                    if (methodToCall != null)
                    {
                        break;
                    }

                    carrierType = carrierType.DeclaringType;
                }

                // Legacy jobs lazy initialize.
                if (methodToCall == null)
                {
                    return(false);
                }

                // We need a separate solution for generic jobs
                if (jobStructType.HasGenericParameters)
                {
                    if (!declaredGenerics.Contains(jobStructType.FullName))
                    {
                        AddDiagnostic(UserError.DC3002(jobStructType));
                    }
                    return(false);
                }

                var asm = AssemblyDefinition.MainModule;

                var errorHandler      = asm.ImportReference((asm.ImportReference(typeof(EarlyInitHelpers)).Resolve().Methods.First(x => x.Name == nameof(EarlyInitHelpers.JobReflectionDataCreationFailed))));
                var typeType          = asm.ImportReference(typeof(Type)).CheckedResolve();
                var getTypeFromHandle = asm.ImportReference(typeType.Methods.FirstOrDefault((x) => x.Name == "GetTypeFromHandle"));

                var mref = asm.ImportReference(asm.ImportReference(methodToCall).MakeGenericInstanceMethod(jobStructType));

                var callInsn   = Instruction.Create(OpCodes.Call, mref);
                var handler    = Instruction.Create(OpCodes.Nop);
                var landingPad = Instruction.Create(OpCodes.Nop);

                processor.Append(callInsn);
                processor.Append(handler);

                // This craziness is equivalent to typeof(n)
                processor.Append(Instruction.Create(OpCodes.Ldtoken, jobStructType));
                processor.Append(Instruction.Create(OpCodes.Call, getTypeFromHandle));
                processor.Append(Instruction.Create(OpCodes.Call, errorHandler));
                processor.Append(landingPad);

                var leaveSuccess = Instruction.Create(OpCodes.Leave, landingPad);
                var leaveFail    = Instruction.Create(OpCodes.Leave, landingPad);
                processor.InsertAfter(callInsn, leaveSuccess);
                processor.InsertBefore(landingPad, leaveFail);

                var exc = new ExceptionHandler(ExceptionHandlerType.Catch);
                exc.TryStart     = callInsn;
                exc.TryEnd       = leaveSuccess.Next;
                exc.HandlerStart = handler;
                exc.HandlerEnd   = leaveFail.Next;
                exc.CatchType    = asm.ImportReference(typeof(Exception));
                body.ExceptionHandlers.Add(exc);
                return(true);
            }
            catch (Exception ex)
            {
                AddDiagnostic(InternalCompilerError.DCICE300(producerRef, jobStructType, ex));
            }

            return(false);
        }