private static string GetCapturedVariableFieldName(Symbol variable, ref int uniqueId)
        {
            if (IsThis(variable))
            {
                return(GeneratedNames.ThisProxyName());
            }

            var local = variable as LocalSymbol;

            if ((object)local != null)
            {
                if (local.SynthesizedLocalKind == SynthesizedLocalKind.LambdaDisplayClass)
                {
                    return(GeneratedNames.MakeLambdaDisplayClassStorageName(uniqueId++));
                }

                if (local.SynthesizedLocalKind == SynthesizedLocalKind.ExceptionFilterAwaitHoistedExceptionLocal)
                {
                    return(GeneratedNames.MakeHoistedLocalFieldName(string.Empty, uniqueId++));
                }
            }

            Debug.Assert(variable.Name != null);
            return(variable.Name);
        }
        private static string GetCapturedVariableFieldName(Symbol variable, ref int uniqueId)
        {
            if (IsThis(variable))
            {
                return(GeneratedNames.ThisProxyFieldName());
            }

            var local = variable as LocalSymbol;

            if ((object)local != null)
            {
                if (local.SynthesizedKind == SynthesizedLocalKind.LambdaDisplayClass)
                {
                    return(GeneratedNames.MakeLambdaDisplayLocalName(uniqueId++));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.ExceptionFilterAwaitHoistedExceptionLocal)
                {
                    return(GeneratedNames.MakeHoistedLocalFieldName(local.SynthesizedKind, uniqueId++));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.InstrumentationPayload)
                {
                    return(GeneratedNames.MakeSynthesizedInstrumentationPayloadLocalFieldName(uniqueId++));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.UserDefined && local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchSection)
                {
                    return(GeneratedNames.MakeHoistedLocalFieldName(local.SynthesizedKind, uniqueId++, local.Name));
                }
            }

            Debug.Assert(variable.Name != null);
            return(variable.Name);
        }
Beispiel #3
0
        private StateMachineFieldSymbol GetOrAllocateReusableHoistedField(TypeSymbol type, out bool reused, LocalSymbol local = null)
        {
            // In debug builds we don't reuse any hoisted variable.
            Debug.Assert(F.Compilation.Options.OptimizationLevel == OptimizationLevel.Release);

            ArrayBuilder <StateMachineFieldSymbol> fields;

            if (_lazyAvailableReusableHoistedFields != null && _lazyAvailableReusableHoistedFields.TryGetValue(type, out fields) && fields.Count > 0)
            {
                var field = fields.Last();
                fields.RemoveLast();
                reused = true;
                return(field);
            }

            reused = false;
            var slotIndex = _nextHoistedFieldId++;

            if (local?.SynthesizedKind == SynthesizedLocalKind.UserDefined)
            {
                string fieldName = GeneratedNames.MakeHoistedLocalFieldName(SynthesizedLocalKind.UserDefined, slotIndex, local.Name);
                return(F.StateMachineField(type, fieldName, SynthesizedLocalKind.UserDefined, slotIndex));
            }

            return(F.StateMachineField(type, GeneratedNames.ReusableHoistedLocalFieldName(slotIndex)));
        }
Beispiel #4
0
        private static string GetCapturedVariableFieldName(Symbol variable, ref int uniqueId)
        {
            if (IsThis(variable))
            {
                return(GeneratedNames.ThisProxyFieldName());
            }

            var local = variable as LocalSymbol;

            if ((object)local != null)
            {
                if (local.SynthesizedKind == SynthesizedLocalKind.LambdaDisplayClass)
                {
                    return(GeneratedNames.MakeLambdaDisplayLocalName(uniqueId++));
                }

                if (
                    local.SynthesizedKind
                    == SynthesizedLocalKind.ExceptionFilterAwaitHoistedExceptionLocal
                    )
                {
                    return(GeneratedNames.MakeHoistedLocalFieldName(
                               local.SynthesizedKind,
                               uniqueId++
                               ));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.InstrumentationPayload)
                {
                    return(GeneratedNames.MakeSynthesizedInstrumentationPayloadLocalFieldName(
                               uniqueId++
                               ));
                }

                if (
                    local.SynthesizedKind == SynthesizedLocalKind.UserDefined &&
                    (
                        local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchSection ||
                        local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchExpressionArm
                    )
                    )
                {
                    // The programmer can use the same identifier for pattern variables in different
                    // sections of a switch statement, but they are all hoisted into
                    // the same frame for the enclosing switch statement and must be given
                    // unique field names.
                    return(GeneratedNames.MakeHoistedLocalFieldName(
                               local.SynthesizedKind,
                               uniqueId++,
                               local.Name
                               ));
                }
            }

            Debug.Assert(variable.Name != null);
            return(variable.Name);
        }
Beispiel #5
0
        private static string GetCapturedVariableFieldName(Symbol variable, ref int uniqueId)
        {
            if (IsThis(variable))
            {
                return(GeneratedNames.ThisProxyFieldName());
            }

            var local = variable as LocalSymbol;

            if ((object)local != null)
            {
                if (local.SynthesizedKind == SynthesizedLocalKind.LambdaDisplayClass)
                {
                    return(GeneratedNames.MakeLambdaDisplayLocalName(uniqueId++));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.ExceptionFilterAwaitHoistedExceptionLocal)
                {
                    return(GeneratedNames.MakeHoistedLocalFieldName(local.SynthesizedKind, uniqueId++));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.InstrumentationPayload)
                {
                    return(GeneratedNames.MakeSynthesizedInstrumentationPayloadLocalFieldName(uniqueId++));
                }

                if (local.SynthesizedKind == SynthesizedLocalKind.UserDefined && local.ScopeDesignatorOpt?.Kind() == SyntaxKind.SwitchSection)
                {
                    return(GeneratedNames.MakeHoistedLocalFieldName(local.SynthesizedKind, uniqueId++, local.Name));
                }

                //
                // @MattWindsor91 (Concept-C# 2017)
                //
                // We added a new type of synthesised variable to represent
                // concept witness dictionaries, so we must generate names for
                // them.

                if (local.SynthesizedKind == SynthesizedLocalKind.ConceptDictionary)
                {
                    return(GeneratedNames.MakeConceptDictionaryLocalFieldName(uniqueId++));
                }
            }

            Debug.Assert(variable.Name != null);
            return(variable.Name);
        }
Beispiel #6
0
        private SynthesizedFieldSymbolBase MakeHoistedLocalField(TypeMap TypeMap, LocalSymbol local, TypeSymbol type)
        {
            Debug.Assert(local.SynthesizedLocalKind == SynthesizedLocalKind.None ||
                         local.SynthesizedLocalKind == SynthesizedLocalKind.LambdaDisplayClass);

            int index = nextLocalNumber++;

            // Special Case: There's logic in the EE to recognize locals that have been captured by a lambda
            // and would have been hoisted for the state machine.  Basically, we just hoist the local containing
            // the instance of the lambda display class and retain its original name (rather than using an
            // iterator local name).  See FUNCBRECEE::ImportIteratorMethodInheritedLocals.
            string fieldName = (local.SynthesizedLocalKind == SynthesizedLocalKind.LambdaDisplayClass)
                ? GeneratedNames.MakeLambdaDisplayClassStorageName(index)
                : GeneratedNames.MakeHoistedLocalFieldName(local.Name, index);

            return(F.StateMachineField(TypeMap.SubstituteType(type), fieldName, index));
        }
Beispiel #7
0
        private BoundExpression HoistExpression(
            BoundExpression expr,
            AwaitExpressionSyntax awaitSyntaxOpt,
            int syntaxOffset,
            RefKind refKind,
            ArrayBuilder <BoundExpression> sideEffects,
            ArrayBuilder <StateMachineFieldSymbol> hoistedFields,
            ref bool needsSacrificialEvaluation)
        {
            switch (expr.Kind)
            {
            case BoundKind.ArrayAccess:
            {
                var             array      = (BoundArrayAccess)expr;
                BoundExpression expression = HoistExpression(array.Expression, awaitSyntaxOpt, syntaxOffset, RefKind.None, sideEffects, hoistedFields, ref needsSacrificialEvaluation);
                var             indices    = ArrayBuilder <BoundExpression> .GetInstance();

                foreach (var index in array.Indices)
                {
                    indices.Add(HoistExpression(index, awaitSyntaxOpt, syntaxOffset, RefKind.None, sideEffects, hoistedFields, ref needsSacrificialEvaluation));
                }

                needsSacrificialEvaluation = true;         // need to force array index out of bounds exceptions
                return(array.Update(expression, indices.ToImmutableAndFree(), array.Type));
            }

            case BoundKind.FieldAccess:
            {
                var field = (BoundFieldAccess)expr;
                if (field.FieldSymbol.IsStatic)
                {
                    // the address of a static field, and the value of a readonly static field, is stable
                    if (refKind != RefKind.None || field.FieldSymbol.IsReadOnly)
                    {
                        return(expr);
                    }
                    goto default;
                }

                if (refKind == RefKind.None)
                {
                    goto default;
                }

                var isFieldOfStruct = !field.FieldSymbol.ContainingType.IsReferenceType;

                var receiver = HoistExpression(field.ReceiverOpt, awaitSyntaxOpt, syntaxOffset,
                                               isFieldOfStruct ? refKind : RefKind.None, sideEffects, hoistedFields, ref needsSacrificialEvaluation);
                if (receiver.Kind != BoundKind.ThisReference && !isFieldOfStruct)
                {
                    needsSacrificialEvaluation = true;         // need the null check in field receiver
                }

                return(F.Field(receiver, field.FieldSymbol));
            }

            case BoundKind.ThisReference:
            case BoundKind.BaseReference:
            case BoundKind.DefaultExpression:
                return(expr);

            case BoundKind.Call:
                var call = (BoundCall)expr;
                // NOTE: There are two kinds of 'In' arguments that we may see at this point:
                //       - `RefKindExtensions.StrictIn`     (originally specified with 'In' modifier)
                //       - `RefKind.In`                     (specified with no modifiers and matched an 'In' parameter)
                //
                //       It is allowed to spill ordinary `In` arguments by value if reference-preserving spilling is not possible.
                //       The "strict" ones do not permit implicit copying, so the same situation should result in an error.
                if (refKind != RefKind.None && refKind != RefKind.In)
                {
                    Debug.Assert(call.Method.RefKind != RefKind.None);
                    F.Diagnostics.Add(ErrorCode.ERR_RefReturningCallAndAwait, F.Syntax.Location, call.Method);
                }
                // method call is not referentially transparent, we can only spill the result value.
                refKind = RefKind.None;
                goto default;

            case BoundKind.ConditionalOperator:
                var conditional = (BoundConditionalOperator)expr;
                // NOTE: There are two kinds of 'In' arguments that we may see at this point:
                //       - `RefKindExtensions.StrictIn`     (originally specified with 'In' modifier)
                //       - `RefKind.In`                     (specified with no modifiers and matched an 'In' parameter)
                //
                //       It is allowed to spill ordinary `In` arguments by value if reference-preserving spilling is not possible.
                //       The "strict" ones do not permit implicit copying, so the same situation should result in an error.
                if (refKind != RefKind.None && refKind != RefKind.RefReadOnly)
                {
                    Debug.Assert(conditional.IsRef);
                    F.Diagnostics.Add(ErrorCode.ERR_RefConditionalAndAwait, F.Syntax.Location);
                }
                // conditional expr is not referentially transparent, we can only spill the result value.
                refKind = RefKind.None;
                goto default;

            default:
                if (expr.ConstantValue != null)
                {
                    return(expr);
                }

                if (refKind != RefKind.None)
                {
                    throw ExceptionUtilities.UnexpectedValue(expr.Kind);
                }

                TypeSymbol fieldType = expr.Type;
                StateMachineFieldSymbol hoistedField;
                if (F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug)
                {
                    const SynthesizedLocalKind kind = SynthesizedLocalKind.AwaitByRefSpill;

                    Debug.Assert(awaitSyntaxOpt != null);

                    int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(kind, syntaxOffset);
                    var id      = new LocalDebugId(syntaxOffset, ordinal);

                    // Editing await expression is not allowed. Thus all spilled fields will be present in the previous state machine.
                    // However, it may happen that the type changes, in which case we need to allocate a new slot.
                    int slotIndex;
                    if (slotAllocatorOpt == null ||
                        !slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(
                            awaitSyntaxOpt,
                            F.ModuleBuilderOpt.Translate(fieldType, awaitSyntaxOpt, Diagnostics),
                            kind,
                            id,
                            Diagnostics,
                            out slotIndex))
                    {
                        slotIndex = _nextFreeHoistedLocalSlot++;
                    }

                    string fieldName = GeneratedNames.MakeHoistedLocalFieldName(kind, slotIndex);
                    hoistedField = F.StateMachineField(expr.Type, fieldName, new LocalSlotDebugInfo(kind, id), slotIndex);
                }
                else
                {
                    hoistedField = GetOrAllocateReusableHoistedField(fieldType, reused: out _);
                }

                hoistedFields.Add(hoistedField);

                var replacement = F.Field(F.This(), hoistedField);
                sideEffects.Add(F.AssignmentExpression(replacement, expr));
                return(replacement);
            }
        }
        private void CreateNonReusableLocalProxies(
            IEnumerable <Symbol> variablesToHoist,
            out IReadOnlyDictionary <Symbol, CapturedSymbolReplacement> proxies,
            out int nextFreeHoistedLocalSlot)
        {
            var proxiesBuilder = new Dictionary <Symbol, CapturedSymbolReplacement>();

            var  typeMap             = stateMachineType.TypeMap;
            bool isDebugBuild        = F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug;
            bool mapToPreviousFields = isDebugBuild && slotAllocatorOpt != null;

            nextFreeHoistedLocalSlot = mapToPreviousFields ? slotAllocatorOpt.PreviousHoistedLocalSlotCount : 0;

            foreach (var variable in variablesToHoist)
            {
                Debug.Assert(variable.Kind == SymbolKind.Local || variable.Kind == SymbolKind.Parameter);

                if (variable.Kind == SymbolKind.Local)
                {
                    var local           = (LocalSymbol)variable;
                    var synthesizedKind = local.SynthesizedKind;

                    if (!synthesizedKind.MustSurviveStateMachineSuspension())
                    {
                        continue;
                    }

                    // no need to hoist constants
                    if (local.IsConst)
                    {
                        continue;
                    }

                    if (local.RefKind != RefKind.None)
                    {
                        // we'll create proxies for these variables later:
                        Debug.Assert(synthesizedKind == SynthesizedLocalKind.Spill);
                        continue;
                    }

                    Debug.Assert(local.RefKind == RefKind.None);
                    StateMachineFieldSymbol field = null;

                    if (ShouldPreallocateNonReusableProxy(local))
                    {
                        // variable needs to be hoisted
                        var fieldType = typeMap.SubstituteType(local.Type.TypeSymbol).TypeSymbol;

                        LocalDebugId id;
                        int          slotIndex = -1;

                        if (isDebugBuild)
                        {
                            // Calculate local debug id.
                            //
                            // EnC: When emitting the baseline (gen 0) the id is stored in a custom debug information attached to the kickoff method.
                            //      When emitting a delta the id is only used to map to the existing field in the previous generation.
                            SyntaxNode declaratorSyntax = local.GetDeclaratorSyntax();
                            int        syntaxOffset     = this.method.CalculateLocalSyntaxOffset(declaratorSyntax.SpanStart, declaratorSyntax.SyntaxTree);
                            int        ordinal          = synthesizedLocalOrdinals.AssignLocalOrdinal(synthesizedKind, syntaxOffset);
                            id = new LocalDebugId(syntaxOffset, ordinal);

                            // map local id to the previous id, if available:
                            int previousSlotIndex;
                            if (mapToPreviousFields && slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(
                                    declaratorSyntax,
                                    F.ModuleBuilderOpt.Translate(fieldType, declaratorSyntax, diagnostics),
                                    synthesizedKind,
                                    id,
                                    diagnostics,
                                    out previousSlotIndex))
                            {
                                slotIndex = previousSlotIndex;
                            }
                        }
                        else
                        {
                            id = LocalDebugId.None;
                        }

                        if (slotIndex == -1)
                        {
                            slotIndex = nextFreeHoistedLocalSlot++;
                        }

                        string fieldName = GeneratedNames.MakeHoistedLocalFieldName(synthesizedKind, slotIndex, local.Name);
                        field = F.StateMachineField(fieldType, fieldName, new LocalSlotDebugInfo(synthesizedKind, id), slotIndex);
                    }

                    if (field != null)
                    {
                        proxiesBuilder.Add(local, new CapturedToStateMachineFieldReplacement(field, isReusable: false));
                    }
                }
                else
                {
                    var parameter = (ParameterSymbol)variable;
                    if (parameter.IsThis)
                    {
                        var containingType = method.ContainingType;
                        var proxyField     = F.StateMachineField(containingType, GeneratedNames.ThisProxyFieldName(), isPublic: true, isThis: true);
                        proxiesBuilder.Add(parameter, new CapturedToStateMachineFieldReplacement(proxyField, isReusable: false));

                        if (PreserveInitialParameterValuesAndThreadId)
                        {
                            var initialThis = containingType.IsStructType() ?
                                              F.StateMachineField(containingType, GeneratedNames.StateMachineThisParameterProxyName(), isPublic: true, isThis: true) : proxyField;

                            initialParameters.Add(parameter, new CapturedToStateMachineFieldReplacement(initialThis, isReusable: false));
                        }
                    }
                    else
                    {
                        // The field needs to be public iff it is initialized directly from the kickoff method
                        // (i.e. not for IEnumerable which loads the values from parameter proxies).
                        var proxyField = F.StateMachineField(typeMap.SubstituteType(parameter.Type.TypeSymbol).TypeSymbol, parameter.Name, isPublic: !PreserveInitialParameterValuesAndThreadId);
                        proxiesBuilder.Add(parameter, new CapturedToStateMachineFieldReplacement(proxyField, isReusable: false));

                        if (PreserveInitialParameterValuesAndThreadId)
                        {
                            var field = F.StateMachineField(typeMap.SubstituteType(parameter.Type.TypeSymbol).TypeSymbol, GeneratedNames.StateMachineParameterProxyFieldName(parameter.Name), isPublic: true);
                            initialParameters.Add(parameter, new CapturedToStateMachineFieldReplacement(field, isReusable: false));
                        }
                    }
                }
            }

            proxies = proxiesBuilder;
        }
        private BoundExpression HoistExpression(
            BoundExpression expr,
            AwaitExpressionSyntax awaitSyntaxOpt,
            int syntaxOffset,
            bool isRef,
            ArrayBuilder <BoundExpression> sideEffects,
            ArrayBuilder <StateMachineFieldSymbol> hoistedFields,
            ref bool needsSacrificialEvaluation)
        {
            switch (expr.Kind)
            {
            case BoundKind.ArrayAccess:
            {
                var             array      = (BoundArrayAccess)expr;
                BoundExpression expression = HoistExpression(array.Expression, awaitSyntaxOpt, syntaxOffset, false, sideEffects, hoistedFields, ref needsSacrificialEvaluation);
                var             indices    = ArrayBuilder <BoundExpression> .GetInstance();

                foreach (var index in array.Indices)
                {
                    indices.Add(HoistExpression(index, awaitSyntaxOpt, syntaxOffset, false, sideEffects, hoistedFields, ref needsSacrificialEvaluation));
                }

                needsSacrificialEvaluation = true;         // need to force array index out of bounds exceptions
                return(array.Update(expression, indices.ToImmutableAndFree(), array.Type));
            }

            case BoundKind.FieldAccess:
            {
                var field = (BoundFieldAccess)expr;
                if (field.FieldSymbol.IsStatic)
                {
                    // the address of a static field, and the value of a readonly static field, is stable
                    if (isRef || field.FieldSymbol.IsReadOnly)
                    {
                        return(expr);
                    }
                    goto default;
                }

                if (!isRef)
                {
                    goto default;
                }

                var isFieldOfStruct = !field.FieldSymbol.ContainingType.IsReferenceType;

                var receiver = HoistExpression(field.ReceiverOpt, awaitSyntaxOpt, syntaxOffset, isFieldOfStruct, sideEffects, hoistedFields, ref needsSacrificialEvaluation);
                if (receiver.Kind != BoundKind.ThisReference && !isFieldOfStruct)
                {
                    needsSacrificialEvaluation = true;         // need the null check in field receiver
                }

                return(F.Field(receiver, field.FieldSymbol));
            }

            case BoundKind.ThisReference:
            case BoundKind.BaseReference:
            case BoundKind.DefaultExpression:
                return(expr);

            case BoundKind.Call:
                var call = (BoundCall)expr;
                if (isRef)
                {
                    Debug.Assert(call.Method.RefKind != RefKind.None);
                    F.Diagnostics.Add(ErrorCode.ERR_RefReturningCallAndAwait, F.Syntax.Location, call.Method);
                    isRef = false;     // Switch to ByVal to avoid asserting later in the pipeline
                }
                goto default;

            case BoundKind.ConditionalOperator:
                var conditional = (BoundConditionalOperator)expr;
                if (isRef)
                {
                    Debug.Assert(conditional.IsRef);
                    F.Diagnostics.Add(ErrorCode.ERR_RefConditionalAndAwait, F.Syntax.Location);
                    isRef = false;     // Switch to ByVal to avoid asserting later in the pipeline
                }
                goto default;

            default:
                if (expr.ConstantValue != null)
                {
                    return(expr);
                }

                if (isRef)
                {
                    throw ExceptionUtilities.UnexpectedValue(expr.Kind);
                }

                TypeSymbol fieldType = expr.Type;
                StateMachineFieldSymbol hoistedField;
                if (F.Compilation.Options.OptimizationLevel == OptimizationLevel.Debug)
                {
                    const SynthesizedLocalKind kind = SynthesizedLocalKind.AwaitByRefSpill;

                    Debug.Assert(awaitSyntaxOpt != null);

                    int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(kind, syntaxOffset);
                    var id      = new LocalDebugId(syntaxOffset, ordinal);

                    // Editing await expression is not allowed. Thus all spilled fields will be present in the previous state machine.
                    // However, it may happen that the type changes, in which case we need to allocate a new slot.
                    int slotIndex;
                    if (slotAllocatorOpt == null ||
                        !slotAllocatorOpt.TryGetPreviousHoistedLocalSlotIndex(
                            awaitSyntaxOpt,
                            F.ModuleBuilderOpt.Translate(fieldType, awaitSyntaxOpt, Diagnostics),
                            kind,
                            id,
                            Diagnostics,
                            out slotIndex))
                    {
                        slotIndex = _nextFreeHoistedLocalSlot++;
                    }

                    string fieldName = GeneratedNames.MakeHoistedLocalFieldName(kind, slotIndex);
                    hoistedField = F.StateMachineField(expr.Type, fieldName, new LocalSlotDebugInfo(kind, id), slotIndex);
                }
                else
                {
                    hoistedField = GetOrAllocateReusableHoistedField(fieldType, reused: out _);
                }

                hoistedFields.Add(hoistedField);

                var replacement = F.Field(F.This(), hoistedField);
                sideEffects.Add(F.AssignmentExpression(replacement, expr));
                return(replacement);
            }
        }