/// <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)); }
public abstract LocalDefinition GetPreviousLocal( Cci.ITypeReference type, ILocalSymbolInternal symbol, string nameOpt, SynthesizedLocalKind kind, LocalDebugId id, LocalVariableAttributes pdbAttributes, LocalSlotConstraints constraints, bool isDynamic, ImmutableArray<TypedConstant> dynamicTransformFlags);
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)); }
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)); }
/// <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; }
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)); }
/// <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; }
/// <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); }
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); }
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); }
public override bool TryGetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId, DiagnosticBag diagnostics, out int slotIndex) { slotIndex = -1; return false; }
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; }
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); }
public override bool TryGetPreviousHoistedLocalSlotIndex(SyntaxNode currentDeclarator, Cci.ITypeReference currentType, SynthesizedLocalKind synthesizedKind, LocalDebugId currentId, out int slotIndex) { slotIndex = -1; return(false); }
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); }
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; } }
/// <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); }
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); }
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); } }
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]; }