File: CodeGen\CodeGenerator.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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;
 
        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 bodySyntax = _methodBodySyntaxOpt;
                    if (_ilEmitStyle == ILEmitStyle.Debug && bodySyntax != null)
                    {
                        int syntaxOffset = _method.CalculateLocalSyntaxOffset(LambdaUtilities.GetDeclaratorPosition(bodySyntax), bodySyntax.SyntaxTree);
                        var localSymbol = new SynthesizedLocal(_method, _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(_method.ReturnType, _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));
 
            if (_emitPdbSequencePoints && !_method.IsIterator && !_method.IsAsync)
            {
                // 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, _diagnostics.DiagnosticBag);
        }
 
        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, _diagnostics.DiagnosticBag, 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, _diagnostics.DiagnosticBag);
        }
 
        private void EmitSignatureToken(FunctionPointerTypeSymbol symbol, SyntaxNode syntaxNode)
        {
            _builder.EmitToken(_module.Translate(symbol).Signature, syntaxNode, _diagnostics.DiagnosticBag);
        }
 
        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.SyntaxTree, span);
        }
 
        private void EmitStepThroughSequencePoint(BoundStepThroughSequencePoint node)
        {
            EmitStepThroughSequencePoint(node.Syntax.SyntaxTree, node.Span);
        }
 
        private void EmitStepThroughSequencePoint(SyntaxTree syntaxTree, 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));
            _builder.EmitBranch(ILOpCode.Brtrue, label);
            EmitSequencePoint(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();
            }
        }
    }
}