/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/CodeGen/CodeGenerator.cs
560 строк
23 KB
Fred Silberberg
Fix sequence point and instrumentation issues with runtime async (#82236)
10 фев 2026, 20:32
Не верифицирован
10 фев 2026, 20:32
3b5698d
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Reflection.Metadata; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.CodeGen { internal sealed partial class CodeGenerator { private readonly MethodSymbol _method; // Syntax of the method body (block or an expression) being emitted, // or null if the method being emitted isn't a source method. // If we are emitting a lambda this is its body. private readonly SyntaxNode _methodBodySyntaxOpt; private readonly BoundStatement _boundBody; private readonly ILBuilder _builder; private readonly PEModuleBuilder _module; private readonly BindingDiagnosticBag _diagnostics; private readonly ILEmitStyle _ilEmitStyle; private readonly bool _emitPdbSequencePoints; private readonly HashSet<LocalSymbol> _stackLocals; // There are scenarios where rvalues need to be passed to ref/in parameters // in such cases the values must be spilled into temps and retained for the entirety of // the most encompassing expression. private ArrayBuilder<LocalDefinition> _expressionTemps; // not 0 when in a protected region with a handler. private int _tryNestingLevel; // not 0 when emitting code in a catch filter private int _inCatchFilterLevel; private readonly SynthesizedLocalOrdinalsDispenser _synthesizedLocalOrdinals = new SynthesizedLocalOrdinalsDispenser(); private int _uniqueNameId; // label used when return is emitted in a form of store/goto private static readonly object s_returnLabel = new object(); private int _asyncCatchHandlerOffset = -1; private ArrayBuilder<int> _asyncYieldPoints; private ArrayBuilder<int> _asyncResumePoints; /// <summary> /// In some cases returns are handled as gotos to return epilogue. /// This is used to track the state of the epilogue. /// </summary> private IndirectReturnState _indirectReturnState; /// <summary> /// Used to implement <see cref="BoundSavePreviousSequencePoint"/> and <see cref="BoundRestorePreviousSequencePoint"/>. /// </summary> private PooledDictionary<object, TextSpan> _savedSequencePoints; private enum IndirectReturnState : byte { NotNeeded = 0, // did not see indirect returns Needed = 1, // saw indirect return and need to emit return sequence Emitted = 2, // return sequence has been emitted } private LocalDefinition _returnTemp; /// <summary> /// True if there was a <see cref="ILOpCode.Localloc"/> anywhere in the method. This will /// affect whether or not we require the locals init flag to be marked, since locals init /// affects <see cref="ILOpCode.Localloc"/>. /// </summary> private bool _sawStackalloc; public CodeGenerator( MethodSymbol method, BoundStatement boundBody, ILBuilder builder, PEModuleBuilder moduleBuilder, BindingDiagnosticBag diagnostics, OptimizationLevel optimizations, bool emittingPdb) { Debug.Assert((object)method != null); Debug.Assert(boundBody != null); Debug.Assert(builder != null); Debug.Assert(moduleBuilder != null); Debug.Assert(diagnostics != null); Debug.Assert(diagnostics.DiagnosticBag != null); _method = method; _boundBody = boundBody; _builder = builder; _module = moduleBuilder; _diagnostics = diagnostics; if (!method.GenerateDebugInfo) { // Always optimize synthesized methods that don't contain user code. // // Specifically, always optimize synthesized explicit interface implementation methods // (aka bridge methods) with by-ref returns because peverify produces errors if we // return a ref local (which the return local will be in such cases). _ilEmitStyle = ILEmitStyle.Release; } else { if (optimizations == OptimizationLevel.Debug) { _ilEmitStyle = ILEmitStyle.Debug; } else { _ilEmitStyle = IsDebugPlus() ? ILEmitStyle.DebugFriendlyRelease : ILEmitStyle.Release; } } // Emit sequence points unless // - the PDBs are not being generated // - debug information for the method is not generated since the method does not contain // user code that can be stepped through, or changed during EnC. // // This setting only affects generating PDB sequence points, it shall not affect generated IL in any way. _emitPdbSequencePoints = emittingPdb && method.GenerateDebugInfo; try { _boundBody = Optimizer.Optimize( boundBody, debugFriendly: _ilEmitStyle != ILEmitStyle.Release, stackLocals: out _stackLocals); } catch (BoundTreeVisitor.CancelledByStackGuardException ex) { ex.AddAnError(diagnostics); _boundBody = boundBody; } var sourceMethod = method as SourceMemberMethodSymbol; (BlockSyntax blockBody, ArrowExpressionClauseSyntax expressionBody) = sourceMethod?.Bodies ?? default; _methodBodySyntaxOpt = (SyntaxNode)blockBody ?? expressionBody ?? sourceMethod?.SyntaxNode; } private bool IsDebugPlus() { return _module.Compilation.Options.DebugPlusMode; } private bool IsPeVerifyCompatEnabled() => _module.Compilation.IsPeVerifyCompatEnabled; private LocalDefinition LazyReturnTemp { get { var result = _returnTemp; if (result == null) { Debug.Assert(!_method.ReturnsVoid, "returning something from void method?"); var slotConstraints = _method.RefKind == RefKind.None ? LocalSlotConstraints.None : LocalSlotConstraints.ByRef; var returnTypeWithAnnotations = _method.ReturnTypeWithAnnotations; if (_method.IsAsync && _module.Compilation.IsRuntimeAsyncEnabledIn(_method)) { // The return type of the method is either Task<T> or ValueTask<T>. The il of the method is // actually going to appear to return a T, not the wrapper task type. So we need to // translate the return type to the actual type that will be returned. var returnType = returnTypeWithAnnotations.Type; Debug.Assert(((InternalSpecialType)returnType.OriginalDefinition.ExtendedSpecialType) is InternalSpecialType.System_Threading_Tasks_ValueTask_T or InternalSpecialType.System_Threading_Tasks_Task_T); returnTypeWithAnnotations = ((NamedTypeSymbol)returnType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; } var bodySyntax = _methodBodySyntaxOpt; if (_ilEmitStyle == ILEmitStyle.Debug && bodySyntax != null) { int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(bodySyntax), bodySyntax.SyntaxTree); var localSymbol = new SynthesizedLocal(_method, returnTypeWithAnnotations, SynthesizedLocalKind.FunctionReturnValue, bodySyntax); result = _builder.LocalSlotManager.DeclareLocal( type: _module.Translate(localSymbol.Type, bodySyntax, _diagnostics.DiagnosticBag), symbol: localSymbol, name: null, kind: localSymbol.SynthesizedKind, id: new LocalDebugId(syntaxOffset, ordinal: 0), pdbAttributes: localSymbol.SynthesizedKind.PdbAttributes(), constraints: slotConstraints, dynamicTransformFlags: ImmutableArray<bool>.Empty, tupleElementNames: ImmutableArray<string>.Empty, isSlotReusable: false); } else { result = AllocateTemp(returnTypeWithAnnotations.Type, _boundBody.Syntax, slotConstraints); } _returnTemp = result; } return result; } } internal static bool IsStackLocal(LocalSymbol local, HashSet<LocalSymbol> stackLocalsOpt) => stackLocalsOpt?.Contains(local) ?? false; private bool IsStackLocal(LocalSymbol local) => IsStackLocal(local, _stackLocals); public void Generate(out bool hasStackalloc) { this.GenerateImpl(); hasStackalloc = _sawStackalloc; Debug.Assert(_asyncCatchHandlerOffset < 0); Debug.Assert(_asyncYieldPoints == null); Debug.Assert(_asyncResumePoints == null); } public void Generate( out int asyncCatchHandlerOffset, out ImmutableArray<int> asyncYieldPoints, out ImmutableArray<int> asyncResumePoints, out bool hasStackAlloc) { this.GenerateImpl(); hasStackAlloc = _sawStackalloc; Debug.Assert(_asyncCatchHandlerOffset >= 0); asyncCatchHandlerOffset = _diagnostics.HasAnyErrors() ? -1 : _builder.GetILOffsetFromMarker(_asyncCatchHandlerOffset); ArrayBuilder<int> yieldPoints = _asyncYieldPoints; ArrayBuilder<int> resumePoints = _asyncResumePoints; Debug.Assert((yieldPoints == null) == (resumePoints == null)); if (yieldPoints == null || _diagnostics.HasAnyErrors()) { asyncYieldPoints = ImmutableArray<int>.Empty; asyncResumePoints = ImmutableArray<int>.Empty; } else { var yieldPointBuilder = ArrayBuilder<int>.GetInstance(); var resumePointBuilder = ArrayBuilder<int>.GetInstance(); int n = yieldPoints.Count; for (int i = 0; i < n; i++) { int yieldOffset = _builder.GetILOffsetFromMarker(yieldPoints[i]); int resumeOffset = _builder.GetILOffsetFromMarker(resumePoints[i]); Debug.Assert(resumeOffset >= 0); // resume marker should always be reachable from dispatch // yield point may not be reachable if the whole // await is not reachable; we just ignore such awaits if (yieldOffset > 0) { yieldPointBuilder.Add(yieldOffset); resumePointBuilder.Add(resumeOffset); } } asyncYieldPoints = yieldPointBuilder.ToImmutableAndFree(); asyncResumePoints = resumePointBuilder.ToImmutableAndFree(); yieldPoints.Free(); resumePoints.Free(); } } private void GenerateImpl() { SetInitialDebugDocument(); // Synthesized methods should have a sequence point // at offset 0 to ensure correct stepping behavior. if (_emitPdbSequencePoints && _method.IsImplicitlyDeclared) { _builder.DefineInitialHiddenSequencePoint(); } try { EmitStatement(_boundBody); if (_indirectReturnState == IndirectReturnState.Needed) { // it is unfortunate that return was not handled while we were in scope of the method // it can happen in rare cases involving exception handling (for example all returns were from a try) // in such case we can still handle return here. HandleReturn(); } if (!_diagnostics.HasAnyErrors()) { _builder.Realize(); } } catch (EmitCancelledException) { Debug.Assert(_diagnostics.HasAnyErrors()); } _synthesizedLocalOrdinals.Free(); Debug.Assert(!(_expressionTemps?.Count > 0), "leaking expression temps?"); _expressionTemps?.Free(); _savedSequencePoints?.Free(); } private void HandleReturn() { _builder.MarkLabel(s_returnLabel); Debug.Assert(_method.ReturnsVoid == (_returnTemp == null) || (_method.IsAsync && _module.Compilation.IsRuntimeAsyncEnabledIn(_method) && ((InternalSpecialType)_method.ReturnType.ExtendedSpecialType) is InternalSpecialType.System_Threading_Tasks_Task or InternalSpecialType.System_Threading_Tasks_ValueTask)); if (_emitPdbSequencePoints && !_method.IsIterator && (!_method.IsAsync || _module.Compilation.IsRuntimeAsyncEnabledIn(_method))) { // In debug mode user could set a breakpoint on the last "}" of the method and // expect to hit it before exiting the method. // We do it by rewriting all returns into a jump to an Exit label // and mark the Exit sequence with sequence point for the span of the last "}". BlockSyntax blockSyntax = _methodBodySyntaxOpt as BlockSyntax; if (blockSyntax != null) { EmitSequencePoint(blockSyntax.SyntaxTree, blockSyntax.CloseBraceToken.Span); } } if (_returnTemp != null) { _builder.EmitLocalLoad(LazyReturnTemp); _builder.EmitRet(false); } else { _builder.EmitRet(true); } _indirectReturnState = IndirectReturnState.Emitted; } private void EmitTypeReferenceToken(Cci.ITypeReference symbol, SyntaxNode syntaxNode) { _builder.EmitToken(symbol, syntaxNode); } private void EmitSymbolToken(TypeSymbol symbol, SyntaxNode syntaxNode) { EmitTypeReferenceToken(_module.Translate(symbol, syntaxNode, _diagnostics.DiagnosticBag), syntaxNode); } private void EmitSymbolToken(MethodSymbol method, SyntaxNode syntaxNode, BoundArgListOperator optArgList, bool encodeAsRawDefinitionToken = false) { var methodRef = _module.Translate(method, syntaxNode, _diagnostics.DiagnosticBag, optArgList, needDeclaration: encodeAsRawDefinitionToken); _builder.EmitToken(methodRef, syntaxNode, encodeAsRawDefinitionToken ? Cci.MetadataWriter.RawTokenEncoding.RowId : 0); } private void EmitSymbolToken(FieldSymbol symbol, SyntaxNode syntaxNode) { var fieldRef = _module.Translate(symbol, syntaxNode, _diagnostics.DiagnosticBag); _builder.EmitToken(fieldRef, syntaxNode); } private void EmitSignatureToken(FunctionPointerTypeSymbol symbol, SyntaxNode syntaxNode) { _builder.EmitToken(_module.Translate(symbol).Signature, syntaxNode); } private void EmitSequencePointStatement(BoundSequencePoint node) { SyntaxNode syntax = node.Syntax; if (_emitPdbSequencePoints) { if (syntax == null) //Null syntax indicates hidden sequence point (not equivalent to WasCompilerGenerated) { EmitHiddenSequencePoint(); } else { EmitSequencePoint(syntax); } } BoundStatement statement = node.StatementOpt; int instructionsEmitted = 0; if (statement != null) { instructionsEmitted = this.EmitStatementAndCountInstructions(statement); } if (instructionsEmitted == 0 && syntax != null && _ilEmitStyle == ILEmitStyle.Debug) { // if there was no code emitted, then emit nop // otherwise this point could get associated with some random statement, possibly in a wrong scope _builder.EmitOpCode(ILOpCode.Nop); } } private void EmitSequencePointStatement(BoundSequencePointWithSpan node) { TextSpan span = node.Span; if (span != default(TextSpan) && _emitPdbSequencePoints) { this.EmitSequencePoint(node.SyntaxTree, span); } BoundStatement statement = node.StatementOpt; int instructionsEmitted = 0; if (statement != null) { instructionsEmitted = this.EmitStatementAndCountInstructions(statement); } if (instructionsEmitted == 0 && span != default(TextSpan) && _ilEmitStyle == ILEmitStyle.Debug) { // if there was no code emitted, then emit nop // otherwise this point could get associated with some random statement, possibly in a wrong scope _builder.EmitOpCode(ILOpCode.Nop); } } private void EmitSavePreviousSequencePoint(BoundSavePreviousSequencePoint statement) { if (!_emitPdbSequencePoints) return; ArrayBuilder<RawSequencePoint> sequencePoints = _builder.SeqPointsOpt; if (sequencePoints is null) return; for (int i = sequencePoints.Count - 1; i >= 0; i--) { var span = sequencePoints[i].Span; if (span == RawSequencePoint.HiddenSequencePointSpan) continue; // Found the previous non-hidden sequence point. Save it. _savedSequencePoints ??= PooledDictionary<object, TextSpan>.GetInstance(); _savedSequencePoints.Add(statement.Identifier, span); return; } } private void EmitRestorePreviousSequencePoint(BoundRestorePreviousSequencePoint node) { Debug.Assert(node.Syntax is { }); if (_savedSequencePoints is null || !_savedSequencePoints.TryGetValue(node.Identifier, out var span)) return; EmitStepThroughSequencePoint(node.Syntax, span); } private void EmitStepThroughSequencePoint(BoundStepThroughSequencePoint node) { EmitStepThroughSequencePoint(node.Syntax, node.Span); } private void EmitStepThroughSequencePoint(SyntaxNode syntaxNode, TextSpan span) { if (!_emitPdbSequencePoints) return; var label = new object(); // The IL builder is eager to discard unreachable code, so // we fool it by branching on a condition that is always true at runtime. _builder.EmitConstantValue(ConstantValue.Create(true), syntaxNode); _builder.EmitBranch(ILOpCode.Brtrue, label); EmitSequencePoint(syntaxNode.SyntaxTree, span); _builder.EmitOpCode(ILOpCode.Nop); _builder.MarkLabel(label); EmitHiddenSequencePoint(); } private void SetInitialDebugDocument() { if (_emitPdbSequencePoints && _methodBodySyntaxOpt != null) { // If methodBlockSyntax is available (i.e. we're in a SourceMethodSymbol), then // provide the IL builder with our best guess at the appropriate debug document. // If we don't and this is hidden sequence point precedes all non-hidden sequence // points, then the IL Builder will drop the sequence point for lack of a document. // This negatively impacts the scenario where we insert hidden sequence points at // the beginnings of methods so that step-into (F11) will handle them correctly. _builder.SetInitialDebugDocument(_methodBodySyntaxOpt.SyntaxTree); } } private void EmitHiddenSequencePoint() { Debug.Assert(_emitPdbSequencePoints); _builder.DefineHiddenSequencePoint(); } private void EmitSequencePoint(SyntaxNode syntax) { EmitSequencePoint(syntax.SyntaxTree, syntax.Span); } private TextSpan EmitSequencePoint(SyntaxTree syntaxTree, TextSpan span) { Debug.Assert(syntaxTree != null); Debug.Assert(_emitPdbSequencePoints); _builder.DefineSequencePoint(syntaxTree, span); return span; } private void AddExpressionTemp(LocalDefinition temp) { // in some cases like stack locals, there is no slot allocated. if (temp == null) { return; } ArrayBuilder<LocalDefinition> exprTemps = _expressionTemps; if (exprTemps == null) { exprTemps = ArrayBuilder<LocalDefinition>.GetInstance(); _expressionTemps = exprTemps; } Debug.Assert(!exprTemps.Contains(temp)); exprTemps.Add(temp); } private void ReleaseExpressionTemps() { if (_expressionTemps?.Count > 0) { // release in reverse order to keep same temps on top of the temp stack if possible for (int i = _expressionTemps.Count - 1; i >= 0; i--) { var temp = _expressionTemps[i]; FreeTemp(temp); } _expressionTemps.Clear(); } } } }