Ejemplo n.º 1
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();
            }
        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)
            {
                InternalCompilerError.DCICE002(methodToAnalyze, delegatePushingInstruction).Throw();
            }

            return(result);
        }
Ejemplo n.º 3
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();
        }
        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);
            }
        }
Ejemplo n.º 5
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.º 6
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);
        }
Ejemplo n.º 7
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.º 8
0
        public static (MethodDefinition[], Dictionary <FieldReference, CapturedVariableDescription> capturedVariables) CloneClosureExecuteMethodAndItsLocalFunctions(
            IEnumerable <MethodDefinition> methodsToClone, TypeDefinition targetType, string newMethodName)
        {
            Dictionary <FieldReference, CapturedVariableDescription> capturedVariables = new Dictionary <FieldReference, CapturedVariableDescription>();

            // Construct list of all DisplayClasses under our declaring type (we need to do this as other delegates might use generate their own DisplayClasses)
            var lambdaDisplayClasses = DisplayClassDescendants(methodsToClone.First().DeclaringType);

            // Find all instructions that load or store a variable in one of our DisplayClasses
            // We use these instructions to discover variables that are captured by our lambda.
            // Note: we have to look for Stfld instructions as well for the case where a captured variable is only stored into.
            var instructionsThatLoadsOrStoresVariableInDisplayClass =
                methodsToClone.SelectMany(method => method.Body.Instructions)
                .Where(i => (i.IsLoadFieldOrLoadFieldAddress() || i.IsStoreField()) && i.Operand is FieldReference &&
                       !(i.Operand as FieldReference).FieldType.IsDisplayClass() && lambdaDisplayClasses.Contains((i.Operand as FieldReference).DeclaringType))
                .ToArray();

            // Walk through instructions that use a captured variable and try to construct list of CapturedVariableDescriptions (old and new fields)
            foreach (var instructionThatLoadsOrStoresVariableInDisplayClass in instructionsThatLoadsOrStoresVariableInDisplayClass)
            {
                var oldField = instructionThatLoadsOrStoresVariableInDisplayClass.Operand as FieldReference;
                if (capturedVariables.ContainsKey(oldField))
                {
                    continue;
                }

                var oldFields = new List <FieldReference>();
                oldFields.Add(oldField);

                var containingMethod        = methodsToClone.Single(m => m.Body.Instructions.Contains(instructionThatLoadsOrStoresVariableInDisplayClass));
                var initialLdFldInstruction = CecilHelpers.FindInstructionThatPushedArg(containingMethod, 0, instructionThatLoadsOrStoresVariableInDisplayClass);
                foreach (var instruction in WalkBackLdFldInstructionsToLdarg0(containingMethod, initialLdFldInstruction))
                {
                    var field = instruction.Operand as FieldReference;
                    oldFields.Insert(0, field);
                }

                var oldFieldDefinition = oldField.Resolve();
                var newField           = new FieldDefinition(oldFieldDefinition.Name, oldFieldDefinition.Attributes, oldFieldDefinition.FieldType);
                capturedVariables[oldField] = new CapturedVariableDescription()
                {
                    ChainOfFieldsToOldField = oldFields.ToArray(),
                    NewField = newField
                };
                targetType.Fields.Add(newField);
            }

            // Walk through all instructions that load or store are captured variables and nop out nested DisplayClasses
            var instructionsThatLoadOrStoreCapturedVariable =
                methodsToClone.SelectMany(method => method.Body.Instructions)
                .Where(i => ((i.IsLoadFieldOrLoadFieldAddress() || i.IsStoreField()) &&
                             capturedVariables.Keys.Any(fr => fr == (i.Operand as FieldReference)))).ToArray();

            foreach (var instructionThatLoadOrStoreCapturedVariable in instructionsThatLoadOrStoreCapturedVariable)
            {
                var containingMethod        = methodsToClone.Single(m => m.Body.Instructions.Contains(instructionThatLoadOrStoreCapturedVariable));
                var initialLdFldInstruction = CecilHelpers.FindInstructionThatPushedArg(containingMethod, 0, instructionThatLoadOrStoreCapturedVariable);
                foreach (var instruction in WalkBackLdFldInstructionsToLdarg0(containingMethod, initialLdFldInstruction))
                {
                    instruction.MakeNOP();
                }
            }

            var executeMethod = methodsToClone.First();

            if (executeMethod.HasGenericParameters)
            {
                throw new ArgumentException();
            }

            var clonedMethods = methodsToClone.ToDictionary(m => m, m =>
            {
                var clonedMethod = new MethodDefinition(m == executeMethod ? newMethodName : m.Name, MethodAttributes.Public, m.ReturnType)
                {
                    HasThis = m.HasThis, DeclaringType = targetType
                };
                clonedMethod.DebugInformation.Scope = m.DebugInformation.Scope;

                targetType.Methods.Add(clonedMethod);
                return(clonedMethod);
            }
                                                            );

            foreach (var methodToClone in methodsToClone)
            {
                var methodDefinition = clonedMethods[methodToClone];

                foreach (var lambdaParameter in methodToClone.Parameters)
                {
                    var executeParameter = new ParameterDefinition(lambdaParameter.Name, lambdaParameter.Attributes,
                                                                   lambdaParameter.ParameterType);
                    foreach (var ca in lambdaParameter.CustomAttributes)
                    {
                        executeParameter.CustomAttributes.Add(ca);
                    }

                    methodDefinition.Parameters.Add(executeParameter);
                }

                var ilProcessor = methodDefinition.Body.GetILProcessor();

                var oldVarToNewVar = new Dictionary <VariableDefinition, VariableDefinition>();
                foreach (var vd in methodToClone.Body.Variables)
                {
                    var newVd = new VariableDefinition(vd.VariableType);
                    methodDefinition.Body.Variables.Add(newVd);

                    var sourceVariable = methodToClone.DebugInformation?.Scope?.Variables?.FirstOrDefault(v => v.Index == vd.Index);
                    if (sourceVariable != null)
                    {
                        methodDefinition.DebugInformation.Scope.Variables.Add(new VariableDebugInformation(newVd, sourceVariable.Name));
                    }

                    oldVarToNewVar.Add(vd, newVd);
                }

                var         oldToNewInstructions = new Dictionary <Instruction, Instruction>();
                Instruction previous             = null;
                foreach (var instruction in methodToClone.Body.Instructions)
                {
                    instruction.Previous = previous;
                    if (previous != null)
                    {
                        previous.Next = instruction;
                    }

                    var clonedOperand = instruction.Operand;
                    if (clonedOperand is FieldReference fr)
                    {
                        if (capturedVariables.TryGetValue(clonedOperand as FieldReference, out var capturedVariableForField))
                        {
                            clonedOperand = capturedVariableForField.NewField;
                        }
                    }

                    if (clonedOperand is VariableDefinition vd)
                    {
                        if (oldVarToNewVar.TryGetValue(vd, out var replacement))
                        {
                            clonedOperand = replacement;
                        }
                    }

                    if (clonedOperand is MethodReference mr)
                    {
                        var targetThatWeAreCloning = methodsToClone.FirstOrDefault(m => m.FullName == mr.FullName);
                        if (targetThatWeAreCloning != null)
                        {
                            var replacement = clonedMethods[targetThatWeAreCloning];
                            clonedOperand = replacement;
                        }
                    }

                    var newInstruction = MakeInstruction(instruction.OpCode, clonedOperand);
                    oldToNewInstructions.Add(instruction, newInstruction);
                    ilProcessor.Append(newInstruction);

                    previous = instruction;
                }

                var oldDebugInfo = methodToClone.DebugInformation;
                var newDebugInfo = methodDefinition.DebugInformation;
                foreach (var seq in oldDebugInfo.SequencePoints)
                {
                    newDebugInfo.SequencePoints.Add(seq);
                }

                // For all instructions that point to another instruction (like branches), make sure we patch those instructions to the new ones too.
                foreach (var newInstruction in oldToNewInstructions.Values)
                {
                    if (newInstruction.Operand is Instruction oldInstruction)
                    {
                        newInstruction.Operand = oldToNewInstructions[oldInstruction];
                    }
                    else if (newInstruction.Operand is Instruction[] instructions)
                    {
                        newInstruction.Operand = instructions.Select(i => oldToNewInstructions[i]).ToArray();
                    }
                }
            }

            return(clonedMethods.Values.ToArray(), capturedVariables);
        }
        private void VerifyDisplayClassFieldsAreValid()
        {
            if (LambdaJobDescriptionConstruction.AllowReferenceTypes)
            {
                return;
            }

            foreach (var field in ClonedFields.Values)
            {
                var typeDefinition = field.FieldType.Resolve();
                if (typeDefinition.TypeReferenceEquals(LambdaJobDescriptionConstruction.ContainingMethod.DeclaringType))
                {
                    foreach (var method in ClonedMethods)
                    {
                        var thisLoadingInstructions = method.Body.Instructions.Where(i => i.Operand is FieldReference fr && fr.FieldType.TypeReferenceEquals(typeDefinition));

                        foreach (var thisLoadingInstruction in thisLoadingInstructions)
                        {
                            var next = thisLoadingInstruction.Next;
                            if (next.Operand is FieldReference fr)
                            {
                                UserError.DC0001(method, next, fr).Throw();
                            }
                        }
                    }
                }

                if (typeDefinition.IsDelegate())
                {
                    continue;
                }

                if (!typeDefinition.IsValueType)
                {
                    foreach (var clonedMethod in ClonedMethods)
                    {
                        var methodInvocations = clonedMethod.Body.Instructions.Where(i => i.Operand is MethodReference mr && mr.HasThis);
                        foreach (var methodInvocation in methodInvocations)
                        {
                            var pushThisInstruction = CecilHelpers.FindInstructionThatPushedArg(clonedMethod, 0, methodInvocation);
                            if (pushThisInstruction == null)
                            {
                                continue;
                            }
                            if (pushThisInstruction.Operand is FieldReference fr && fr.FieldType.TypeReferenceEquals(typeDefinition))
                            {
                                UserError.DC0002(clonedMethod, methodInvocation, (MethodReference)methodInvocation.Operand).Throw();
                            }
                        }
                    }

                    //todo: we need a better way to detect this, and a much better error message, but let's start with this stopgap version
                    //since it's already much better than the generic DC0004 we would otherwise have thrown below.
                    if (field.Name.Contains("_locals"))
                    {
                        UserError.DC0022(LambdaJobDescriptionConstruction.ContainingMethod, LambdaJobDescriptionConstruction.WithCodeInvocationInstruction).Throw();
                    }

                    UserError.DC0004(LambdaJobDescriptionConstruction.ContainingMethod, LambdaJobDescriptionConstruction.WithCodeInvocationInstruction, field).Throw();
                }
            }
        }
        public static JobStructForLambdaJob Rewrite(MethodDefinition methodContainingLambdaJob, LambdaJobDescriptionConstruction lambdaJobDescriptionConstruction, List <DiagnosticMessage> warnings)
        {
            if (methodContainingLambdaJob.DeclaringType.CustomAttributes.Any(c => (c.AttributeType.Name == "ExecuteAlways" && c.AttributeType.Namespace == "UnityEngine")))
            {
                warnings.Add(UserError.DC0032(methodContainingLambdaJob.DeclaringType, methodContainingLambdaJob, lambdaJobDescriptionConstruction.ScheduleOrRunInvocationInstruction));
            }

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

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

            var methodLambdaWasEmittedAs = lambdaJobDescriptionConstruction.MethodLambdaWasEmittedAs;

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

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

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

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

            var moduleDefinition = methodContainingLambdaJob.Module;

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

            VariableDefinition displayClassVariable = null;

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


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

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

                if (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);

                VariableDefinition tempStorageForJobHandle = null;

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

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

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

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

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

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

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

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

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

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

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

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                }

                MethodReference runOrScheduleMethod;

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

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

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

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

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

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

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

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

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

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

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

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

                        yield return(targetInstruction);

                        yield return(finalBranchDestination);
                    }
                }

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

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

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

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

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

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

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

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

            var scheduleInstructions = InstructionsToReplaceScheduleInvocationWith().ToList();

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

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

            return(generatedJobStruct);
        }
Ejemplo n.º 11
0
        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))
                {
                    // todo: Make this a expression switch statement once 8.0 is allowed in DOTS (2020.2)
                    LambdaJobDescriptionKind jobDescriptionKind;
                    switch (((MethodReference)getEntitiesOrJobInstruction.Operand).Name)
                    {
                    case EntitiesGetterName:   jobDescriptionKind = LambdaJobDescriptionKind.Entities; break;

                    case JobGetterName:        jobDescriptionKind = LambdaJobDescriptionKind.Job;      break;

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

                    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.DC0043(method, givenName, getEntitiesOrJobInstruction).Throw();
                    }

                    var hasWithStructuralChangesModifier
                        = modifiers.Any(m => m.MethodName == nameof(LambdaJobDescriptionConstructionMethods.WithStructuralChanges));
                    if (hasWithStructuralChangesModifier)
                    {
                        if (jobDescriptionKind == LambdaJobDescriptionKind.Job)
                        {
                            UserError.DC0057(method, getEntitiesOrJobInstruction).Throw();
                        }
                        if (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();
                            }
                        }
                    }

                    if (modifiers.All(m => m.MethodName != nameof(LambdaForEachDescriptionConstructionMethods.ForEach) &&
                                      m.MethodName != nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode)))
                    {
                        DiagnosticMessage MakeDiagnosticMessage()
                        {
                            switch (jobDescriptionKind)
                            {
                            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.DC0053(method.DeclaringType, method, getEntitiesOrJobInstruction).Throw();
                    }

                    var withCodeInvocationInstruction = modifiers
                                                        .Single(m => m.MethodName == nameof(LambdaForEachDescriptionConstructionMethods.ForEach) || m.MethodName == nameof(LambdaSingleJobDescriptionConstructionMethods.WithCode))
                                                        .InstructionInvokingMethod;
                    return(new LambdaJobDescriptionConstruction()
                    {
                        Kind = jobDescriptionKind,
                        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 arguments = mr.Parameters.Skip(1).Select(p =>
                {
                    var instructionThatPushedArg = CecilHelpers.FindInstructionThatPushedArg(method, p.Index, cursor);
                    if (instructionThatPushedArg.IsLoadConstantInt(out var intValue))
                    {
                        return(intValue);
                    }
                    return(instructionThatPushedArg.Operand);
                }).ToArray();
                var invokedConstructionMethod = new InvokedConstructionMethod(mr.Name,
                                                                              (mr as GenericInstanceMethod)?.GenericArguments.ToArray() ?? Array.Empty <TypeReference>(),
                                                                              arguments, cursor);

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

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

                    bool allowDynamicValue = methodDefinitionParameter.CustomAttributes.Any(c => c.AttributeType.TypeReferenceEquals(allowDynamicValueAttribute));

                    //all arguments to ForEach are implicit allowdynamicvalue, to allow for users bring-your-own-delegate setups, without us having to make the AllowDynamicAttribute public.
                    if (invokedConstructionMethod.MethodName == nameof(LambdaForEachDescriptionConstructionMethods.ForEach))
                    {
                        allowDynamicValue = true;
                    }

                    if (!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);
            }
        }
        public static List <DiagnosticMessage> VerifyMethod(MethodDefinition method, HashSet <TypeReference> _nonRestrictedTypes)
        {
            var diagnosticMessages = new List <DiagnosticMessage>();

            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 RequiredModifierType || tr is GenericInstanceType)
                {
                    tr = tr.GetElementType();
                    return(IsTypeRestrictedToBlobAssetStorage(tr));
                }
                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);
                    }
                }

                if (!TryResolve(tr, method, diagnosticMessages, out var td))
                {
                    _nonRestrictedTypes.Add(tr);
                    return(false);
                }

                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.IsInvocation(out var targetMethod) &&
                    targetMethod.DeclaringType.TypeReferenceEquals(typeof(BlobBuilder)) &&
                    targetMethod.Name == nameof(BlobBuilder.ConstructRoot) &&
                    targetMethod is GenericInstanceMethod genericTargetMethod)
                {
                    foreach (var arg in genericTargetMethod.GenericArguments)
                    {
                        var validatedTypes = new HashSet <TypeReference>();
                        if (IsOrHasReferenceTypeField(arg, validatedTypes, method, diagnosticMessages, out var fieldDescription))
                        {
                            string errorFieldPath = fieldDescription == null ? arg.Name : arg.Name + fieldDescription;
                            var    message        = $"You may not build a type {arg.Name} with {nameof(BlobBuilder.Construct)} as {errorFieldPath} is a reference or pointer.  Only non-reference types are allowed in Blobs.";
                            diagnosticMessages.Add(UserError.MakeError("ConstructBlobWithRefTypeViolation", message, method, instruction));
                        }
                    }
                }
                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}";

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

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

                        diagnosticMessages.Add(
                            UserError.MakeError("MayOnlyLiveInBlobStorageViolation",
                                                $"{tr.Name} may only live in blob storage. Access it by (non-readonly) ref instead: `{error}`", method,
                                                instruction));
                    }
                }
            }

            return(diagnosticMessages);
        }
Ejemplo n.º 13
0
        public static List <DiagnosticMessage> VerifyMethod(MethodDefinition method, HashSet <TypeReference> _nonRestrictedTypes)
        {
            var diagnosticMessages = new List <DiagnosticMessage>();

            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 RequiredModifierType || tr is GenericInstanceType)
                {
                    tr = tr.GetElementType();
                    return(IsTypeRestrictedToBlobAssetStorage(tr));
                }
                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);
                    }
                }

                // Don't do a CheckedResolve here. If we somehow fail we don't want to block the user.
                var td = tr.Resolve();

                if (td == null)
                {
                    diagnosticMessages.Add(
                        UserError.MakeWarning("ResolveFailureWarning",
                                              $"Unable to resolve type {tr.FullName} for verification.",
                                              method, method.Body.Instructions.FirstOrDefault()));
                    _nonRestrictedTypes.Add(tr);

                    return(false);
                }

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

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

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

                        diagnosticMessages.Add(
                            UserError.MakeError("MayOnlyLiveInBlobStorageViolation",
                                                $"{tr.Name} may only live in blob storage. Access it by (non-readonly) ref instead: `{error}`", method,
                                                instruction));
                    }
                }
            }

            return(diagnosticMessages);
        }