Пример #1
0
 /// <summary>
 /// Returns an index of a slot that stores specified hoisted local variable in the previous generation.
 /// </summary>
 public abstract bool TryGetPreviousHoistedLocalSlotIndex(
     SyntaxNode currentDeclarator,
     Cci.ITypeReference currentType,
     SynthesizedLocalKind synthesizedKind,
     LocalDebugId currentId,
     DiagnosticBag diagnostics,
     out int slotIndex);
        private bool TryGetPreviousLocalId(SyntaxNode currentDeclarator, LocalDebugId currentId, out LocalDebugId previousId)
        {
            if (_syntaxMap == null)
            {
                // no syntax map
                // => the source of the current method is the same as the source of the previous method
                // => relative positions are the same
                // => synthesized ids are the same
                previousId = currentId;
                return(true);
            }

            SyntaxNode?previousDeclarator = _syntaxMap(currentDeclarator);

            if (previousDeclarator == null)
            {
                previousId = default;
                return(false);
            }

            int syntaxOffset = CalculateSyntaxOffsetInPreviousMethod(previousDeclarator);

            previousId = new LocalDebugId(syntaxOffset, currentId.Ordinal);
            return(true);
        }
        public override bool TryGetPreviousHoistedLocalSlotIndex(
            SyntaxNode currentDeclarator,
            Cci.ITypeReference currentType,
            SynthesizedLocalKind synthesizedKind,
            LocalDebugId currentId,
            DiagnosticBag diagnostics,
            out int slotIndex)
        {
            // The previous method was not a state machine (it is allowed to change non-state machine to a state machine):
            if (_hoistedLocalSlots == null)
            {
                slotIndex = -1;
                return(false);
            }

            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out LocalDebugId previousId))
            {
                slotIndex = -1;
                return(false);
            }

            var previousType = _symbolMap.MapReference(currentType);

            if (previousType == null)
            {
                slotIndex = -1;
                return(false);
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return(_hoistedLocalSlots.TryGetValue(localKey, out slotIndex));
        }
Пример #4
0
 public abstract LocalDefinition GetPreviousLocal(
     Cci.ITypeReference type,
     ILocalSymbolInternal symbol,
     string nameOpt,
     SynthesizedLocalKind kind,
     LocalDebugId id,
     LocalVariableAttributes pdbAttributes,
     LocalSlotConstraints constraints,
     bool isDynamic,
     ImmutableArray<TypedConstant> dynamicTransformFlags);
Пример #5
0
        public override LocalDefinition GetPreviousLocal(
            Cci.ITypeReference currentType,
            ILocalSymbolInternal currentLocalSymbol,
            string nameOpt,
            SynthesizedLocalKind kind,
            LocalDebugId id,
            uint pdbAttributes,
            LocalSlotConstraints constraints,
            bool isDynamic,
            ImmutableArray <TypedConstant> dynamicTransformFlags)
        {
            if (id.IsNone)
            {
                return(null);
            }

            LocalDebugId previousId;

            if (!TryGetPreviousLocalId(currentLocalSymbol.GetDeclaratorSyntax(), id, out previousId))
            {
                return(null);
            }

            var previousType = _symbolMap.MapReference(currentType);

            if (previousType == null)
            {
                return(null);
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncLocalInfo(new LocalSlotDebugInfo(kind, previousId), previousType, constraints, signature: null);

            int slot;

            if (!_previousLocalSlots.TryGetValue(localKey, out slot))
            {
                return(null);
            }

            return(new LocalDefinition(
                       currentLocalSymbol,
                       nameOpt,
                       currentType,
                       slot,
                       kind,
                       id,
                       pdbAttributes,
                       constraints,
                       isDynamic,
                       dynamicTransformFlags));
        }
Пример #6
0
            public override LocalDefinition GetPreviousLocal(
                Cci.ITypeReference type,
                ILocalSymbolInternal symbol,
                string nameOpt,
                SynthesizedLocalKind synthesizedKind,
                LocalDebugId id,
                uint pdbAttributes,
                LocalSlotConstraints constraints,
                bool isDynamic,
                ImmutableArray <TypedConstant> dynamicTransformFlags)
            {
                var local = symbol as EELocalSymbol;

                if ((object)local == null)
                {
                    return(null);
                }

                return(_locals[local.Ordinal]);
            }
        public override bool TryGetPreviousHoistedLocalSlotIndex(
            SyntaxNode currentDeclarator,
            Cci.ITypeReference currentType,
            SynthesizedLocalKind synthesizedKind,
            LocalDebugId currentId,
            DiagnosticBag diagnostics,
            out int slotIndex)
        {
            // Well-formed state machine attribute wasn't found in the baseline (the type is missing or bad).
            // Should rarely happen since the IDE reports a rude edit if the attribute type doesn't exist.
            if (_hoistedLocalSlotsOpt == null)
            {
                // TODO: better error message https://github.com/dotnet/roslyn/issues/9196
                diagnostics.Add(_messageProvider.CreateDiagnostic(_messageProvider.ERR_ModuleEmitFailure, NoLocation.Singleton, _previousTopLevelMethod.ContainingModule.Name));
                slotIndex = -1;
                return(false);
            }

            LocalDebugId previousId;

            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                slotIndex = -1;
                return(false);
            }

            var previousType = _symbolMap.MapReference(currentType);

            if (previousType == null)
            {
                slotIndex = -1;
                return(false);
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return(_hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex));
        }
Пример #8
0
 /// <summary>
 /// Creates a new LocalDefinition.
 /// </summary>
 /// <param name="symbolOpt">Local symbol, used by edit and continue only, null otherwise.</param>
 /// <param name="nameOpt">Name associated with the slot.</param>
 /// <param name="type">Type associated with the slot.</param>
 /// <param name="slot">Slot position in the signature.</param>
 /// <param name="dynamicTransformFlags">Contains the synthesized dynamic attributes of the local</param>
 /// <param name="synthesizedKind">Local kind.</param>
 /// <param name="id">Local id.</param>
 /// <param name="pdbAttributes">Value to emit in the attributes field in the PDB.</param>
 /// <param name="constraints">Specifies whether slot type should have pinned modifier and whether slot should have byref constraint.</param>
 /// <param name="isDynamic">Specifies if the type is Dynamic.</param>
 public LocalDefinition(
     ILocalSymbol symbolOpt,
     string nameOpt,
     Cci.ITypeReference type,
     int slot,
     SynthesizedLocalKind synthesizedKind,
     LocalDebugId id,
     uint pdbAttributes,
     LocalSlotConstraints constraints,
     bool isDynamic,
     ImmutableArray<TypedConstant> dynamicTransformFlags)
 {
     _symbolOpt = symbolOpt;
     _nameOpt = nameOpt;
     _type = type;
     _slot = slot;
     _slotInfo = new LocalSlotDebugInfo(synthesizedKind, id);
     _pdbAttributes = pdbAttributes;
     _dynamicTransformFlags = dynamicTransformFlags;
     _constraints = constraints;
     _isDynamic = isDynamic;
 }
Пример #9
0
        public override bool TryGetPreviousHoistedLocalSlotIndex(
            SyntaxNode currentDeclarator,
            Cci.ITypeReference currentType,
            SynthesizedLocalKind synthesizedKind,
            LocalDebugId currentId,
            DiagnosticBag diagnostics,
            out int slotIndex)
        {
            // Well-formed state machine attribute wasn't found in the baseline (the type is missing or bad).
            // Should rarely happen since the IDE reports a rude edit if the attribute type doesn't exist.
            if (_hoistedLocalSlotsOpt == null)
            {
                ReportMissingStateMachineAttribute(diagnostics);
                slotIndex = -1;
                return(false);
            }

            LocalDebugId previousId;

            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                slotIndex = -1;
                return(false);
            }

            var previousType = _symbolMap.MapReference(currentType);

            if (previousType == null)
            {
                slotIndex = -1;
                return(false);
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return(_hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex));
        }
Пример #10
0
 /// <summary>
 /// Creates a new LocalDefinition.
 /// </summary>
 /// <param name="symbolOpt">Local symbol, used by edit and continue only, null otherwise.</param>
 /// <param name="nameOpt">Name associated with the slot.</param>
 /// <param name="type">Type associated with the slot.</param>
 /// <param name="slot">Slot position in the signature.</param>
 /// <param name="synthesizedKind">Local kind.</param>
 /// <param name="id">Local id.</param>
 /// <param name="pdbAttributes">Value to emit in the attributes field in the PDB.</param>
 /// <param name="constraints">Specifies whether slot type should have pinned modifier and whether slot should have byref constraint.</param>
 /// <param name="dynamicTransformFlags">The synthesized dynamic attributes of the local.</param>
 /// <param name="tupleElementNames">Tuple element names of the local.</param>
 public LocalDefinition(
     ILocalSymbol symbolOpt,
     string nameOpt,
     Cci.ITypeReference type,
     int slot,
     SynthesizedLocalKind synthesizedKind,
     LocalDebugId id,
     LocalVariableAttributes pdbAttributes,
     LocalSlotConstraints constraints,
     ImmutableArray<TypedConstant> dynamicTransformFlags,
     ImmutableArray<TypedConstant> tupleElementNames)
 {
     _symbolOpt = symbolOpt;
     _nameOpt = nameOpt;
     _type = type;
     _slot = slot;
     _slotInfo = new LocalSlotDebugInfo(synthesizedKind, id);
     _pdbAttributes = pdbAttributes;
     _dynamicTransformFlags = dynamicTransformFlags.NullToEmpty();
     _tupleElementNames = tupleElementNames.NullToEmpty();
     _constraints = constraints;
 }
Пример #11
0
        /// <summary>
        /// Gets the name and id of the local that are going to be generated into the debug metadata.
        /// </summary>
        private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId localId)
        {
            localId = LocalDebugId.None;

            if (local.IsImportedFromMetadata)
            {
                return local.Name;
            }

            var localKind = local.SynthesizedKind;

            // only user-defined locals should be named during lowering:
            Debug.Assert((local.Name == null) == (localKind != SynthesizedLocalKind.UserDefined));

            if (!localKind.IsLongLived())
            {
                return null;
            }

            if (_optimizations == OptimizationLevel.Debug)
            {
                var syntax = local.GetDeclaratorSyntax();
                int syntaxOffset = _method.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree);

                int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset);

                // user-defined locals should have 0 ordinal:
                Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined);

                localId = new LocalDebugId(syntaxOffset, ordinal);
            }

            return local.Name ?? GeneratedNames.MakeSynthesizedLocalName(localKind, ref _uniqueNameId);
        }
Пример #12
0
        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;
        }
        public override bool TryGetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId, out int slotIndex)
        {
            Debug.Assert(_hoistedLocalSlotsOpt != null);

            LocalDebugId previousId;
            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                slotIndex = -1;
                return false;
            }

            var previousType = _symbolMap.MapReference(currentType);
            if (previousType == null)
            {
                slotIndex = -1;
                return false;
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return _hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex);
        }
        private bool TryGetPreviousLocalId(SyntaxNode currentDeclarator, LocalDebugId currentId, out LocalDebugId previousId)
        {
            if (_syntaxMapOpt == null)
            {
                // no syntax map 
                // => the source of the current method is the same as the source of the previous method 
                // => relative positions are the same 
                // => synthesized ids are the same
                previousId = currentId;
                return true;
            }

            SyntaxNode previousDeclarator = _syntaxMapOpt(currentDeclarator);
            if (previousDeclarator == null)
            {
                previousId = default(LocalDebugId);
                return false;
            }

            int syntaxOffset = _previousMethod.CalculateLocalSyntaxOffset(previousDeclarator.SpanStart, previousDeclarator.SyntaxTree);
            previousId = new LocalDebugId(syntaxOffset, currentId.Ordinal);
            return true;
        }
        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);
            }
        }
        public override LocalDefinition GetPreviousLocal(
            Cci.ITypeReference currentType,
            ILocalSymbolInternal currentLocalSymbol,
            string nameOpt,
            SynthesizedLocalKind kind,
            LocalDebugId id,
            uint pdbAttributes,
            LocalSlotConstraints constraints,
            bool isDynamic,
            ImmutableArray<TypedConstant> dynamicTransformFlags)
        {
            if (id.IsNone)
            {
                return null;
            }

            LocalDebugId previousId;
            if (!TryGetPreviousLocalId(currentLocalSymbol.GetDeclaratorSyntax(), id, out previousId))
            {
                return null;
            }

            var previousType = _symbolMap.MapReference(currentType);
            if (previousType == null)
            {
                return null;
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncLocalInfo(new LocalSlotDebugInfo(kind, previousId), previousType, constraints, signature: null);

            int slot;
            if (!_previousLocalSlots.TryGetValue(localKey, out slot))
            {
                return null;
            }

            return new LocalDefinition(
                currentLocalSymbol,
                nameOpt,
                currentType,
                slot,
                kind,
                id,
                pdbAttributes,
                constraints,
                isDynamic,
                dynamicTransformFlags);
        }
Пример #17
0
        public override bool TryGetPreviousHoistedLocalSlotIndex(
            SyntaxNode currentDeclarator, 
            Cci.ITypeReference currentType,
            SynthesizedLocalKind synthesizedKind,
            LocalDebugId currentId,
            DiagnosticBag diagnostics, 
            out int slotIndex)
        {
            // The previous method was not a state machine (it is allowed to change non-state machine to a state machine):
            if (_hoistedLocalSlotsOpt == null)
            {
                slotIndex = -1;
                return false;
            }

            LocalDebugId previousId;
            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                slotIndex = -1;
                return false;
            }

            var previousType = _symbolMap.MapReference(currentType);
            if (previousType == null)
            {
                slotIndex = -1;
                return false;
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return _hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex);
        }
Пример #18
0
 public override bool TryGetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId, DiagnosticBag diagnostics, out int slotIndex)
 {
     slotIndex = -1;
     return false;
 }
Пример #19
0
        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.AwaitSpill);
                        continue;
                    }

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

                    if (!local.SynthesizedKind.IsSlotReusable(F.Compilation.Options.OptimizationLevel))
                    {
                        // variable needs to be hoisted
                        var fieldType = typeMap.SubstituteType(local.Type).Type;

                        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, 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);
                        proxiesBuilder.Add(parameter, new CapturedToStateMachineFieldReplacement(proxyField, isReusable: false));

                        if (PreserveInitialParameterValues)
                        {
                            var initialThis = containingType.IsStructType() ?
                                F.StateMachineField(containingType, GeneratedNames.StateMachineThisParameterProxyName(), isPublic: 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).Type, parameter.Name, isPublic: !PreserveInitialParameterValues);
                        proxiesBuilder.Add(parameter, new CapturedToStateMachineFieldReplacement(proxyField, isReusable: false));

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

            proxies = proxiesBuilder;
        }
Пример #20
0
        public override int GetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId)
        {
            Debug.Assert(_hoistedLocalSlotsOpt != null);

            LocalDebugId previousId;

            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                return(-1);
            }

            var previousType = _symbolMap.MapReference(currentType);

            if (previousType == null)
            {
                return(-1);
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            int slotIndex;

            if (!_hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex))
            {
                return(-1);
            }

            return(slotIndex);
        }
Пример #21
0
 public override bool TryGetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId, out int slotIndex)
 {
     slotIndex = -1;
     return(false);
 }
Пример #22
0
        public override bool TryGetPreviousHoistedLocalSlotIndex(
            SyntaxNode currentDeclarator, 
            Cci.ITypeReference currentType,
            SynthesizedLocalKind synthesizedKind,
            LocalDebugId currentId,
            DiagnosticBag diagnostics, 
            out int slotIndex)
        {
            // Well-formed state machine attribute wasn't found in the baseline (the type is missing or bad).
            // Should rarely happen since the IDE reports a rude edit if the attribute type doesn't exist.
            if (_hoistedLocalSlotsOpt == null)
            {
                ReportMissingStateMachineAttribute(diagnostics);
                slotIndex = -1;
                return false;
            }

            LocalDebugId previousId;
            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                slotIndex = -1;
                return false;
            }

            var previousType = _symbolMap.MapReference(currentType);
            if (previousType == null)
            {
                slotIndex = -1;
                return false;
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return _hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex);
        }
Пример #23
0
        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.DefaultOperator:
                    return expr;

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

                    hoistedFields.Add(hoistedField);

                    var replacement = F.Field(F.This(), hoistedField);
                    sideEffects.Add(F.AssignmentExpression(replacement, expr));
                    return replacement;
            }
        }
Пример #24
0
        /// <summary>
        /// Gets the name and id of the local that are going to be generated into the debug metadata.
        /// </summary>
        private string GetLocalDebugName(ILocalSymbolInternal local, out LocalDebugId localId)
        {
            localId = LocalDebugId.None;

            if (local.IsImportedFromMetadata)
            {
                return local.Name;
            }

            var localKind = local.SynthesizedKind;

            // only user-defined locals should be named during lowering:
            Debug.Assert((local.Name == null) == (localKind != SynthesizedLocalKind.UserDefined));

            // Generating debug names for instrumentation payloads should be allowed, as described in https://github.com/dotnet/roslyn/issues/11024.
            // For now, skip naming locals generated by instrumentation as they might not have a local syntax offset.
            // Locals generated by instrumentation might exist in methods which do not contain a body (auto property initializers).
            if (!localKind.IsLongLived() || localKind == SynthesizedLocalKind.InstrumentationPayload)
            {
                return null;
            }

            if (_ilEmitStyle == ILEmitStyle.Debug)
            {
                var syntax = local.GetDeclaratorSyntax();
                int syntaxOffset = _method.CalculateLocalSyntaxOffset(syntax.SpanStart, syntax.SyntaxTree);

                int ordinal = _synthesizedLocalOrdinals.AssignLocalOrdinal(localKind, syntaxOffset);

                // user-defined locals should have 0 ordinal:
                Debug.Assert(ordinal == 0 || localKind != SynthesizedLocalKind.UserDefined);

                localId = new LocalDebugId(syntaxOffset, ordinal);
            }

            return local.Name ?? GeneratedNames.MakeSynthesizedLocalName(localKind, ref _uniqueNameId);
        }
Пример #25
0
        public override bool TryGetPreviousHoistedLocalSlotIndex(
            SyntaxNode currentDeclarator, 
            Cci.ITypeReference currentType,
            SynthesizedLocalKind synthesizedKind,
            LocalDebugId currentId,
            DiagnosticBag diagnostics, 
            out int slotIndex)
        {
            // Well-formed state machine attribute wasn't found in the baseline (the type is missing or bad).
            // Should rarely happen since the IDE reports a rude edit if the attribute type doesn't exist.
            if (_hoistedLocalSlotsOpt == null)
            {
                // TODO: better error message https://github.com/dotnet/roslyn/issues/9196
                diagnostics.Add(_messageProvider.CreateDiagnostic(_messageProvider.ERR_ModuleEmitFailure, NoLocation.Singleton, _previousTopLevelMethod.ContainingModule.Name));
                slotIndex = -1;
                return false;
            }

            LocalDebugId previousId;
            if (!TryGetPreviousLocalId(currentDeclarator, currentId, out previousId))
            {
                slotIndex = -1;
                return false;
            }

            var previousType = _symbolMap.MapReference(currentType);
            if (previousType == null)
            {
                slotIndex = -1;
                return false;
            }

            // TODO (bug #781309): Should report a warning if the type of the local has changed
            // and the previous value will be dropped.
            var localKey = new EncHoistedLocalInfo(new LocalSlotDebugInfo(synthesizedKind, previousId), previousType);

            return _hoistedLocalSlotsOpt.TryGetValue(localKey, out slotIndex);
        }
Пример #26
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);
            }
        }
Пример #27
0
            public override LocalDefinition GetPreviousLocal(
                Cci.ITypeReference type,
                ILocalSymbolInternal symbol,
                string nameOpt,
                SynthesizedLocalKind synthesizedKind,
                LocalDebugId id,
                uint pdbAttributes,
                LocalSlotConstraints constraints,
                bool isDynamic,
                ImmutableArray<TypedConstant> dynamicTransformFlags)
            {
                var local = symbol as EELocalSymbol;
                if ((object)local == null)
                {
                    return null;
                }

                return _locals[local.Ordinal];
            }