File: System\Linq\Expressions\Compiler\LambdaCompiler.ControlFlow.cs
Web Access
Project: src\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Dynamic.Utils;
 
namespace System.Linq.Expressions.Compiler
{
    // The part of the LambdaCompiler dealing with low level control flow
    // break, continue, return, exceptions, etc
    internal sealed partial class LambdaCompiler
    {
        private LabelInfo EnsureLabel(LabelTarget node)
        {
            if (!_labelInfo.TryGetValue(node, out LabelInfo? result))
            {
                _labelInfo.Add(node, result = new LabelInfo(_ilg, node, false));
            }
            return result;
        }
 
        private LabelInfo ReferenceLabel(LabelTarget node)
        {
            LabelInfo result = EnsureLabel(node);
            result.Reference(_labelBlock);
            return result;
        }
 
        private LabelInfo DefineLabel(LabelTarget? node)
        {
            if (node == null)
            {
                return new LabelInfo(_ilg, null, false);
            }
            LabelInfo result = EnsureLabel(node);
            result.Define(_labelBlock);
            return result;
        }
 
        private void PushLabelBlock(LabelScopeKind type)
        {
            _labelBlock = new LabelScopeInfo(_labelBlock, type);
        }
 
        private void PopLabelBlock(LabelScopeKind kind)
        {
            Debug.Assert(_labelBlock != null && _labelBlock.Kind == kind);
            _labelBlock = _labelBlock.Parent!;
        }
 
        private void EmitLabelExpression(Expression expr, CompilationFlags flags)
        {
            var node = (LabelExpression)expr;
            Debug.Assert(node.Target != null);
 
            // If we're an immediate child of a block, our label will already
            // be defined. If not, we need to define our own block so this
            // label isn't exposed except to its own child expression.
            LabelInfo? label = null;
 
            if (_labelBlock.Kind == LabelScopeKind.Block)
            {
                _labelBlock.TryGetLabelInfo(node.Target, out label);
 
                // We're in a block but didn't find our label, try switch
                if (label == null && _labelBlock.Parent!.Kind == LabelScopeKind.Switch)
                {
                    _labelBlock.Parent.TryGetLabelInfo(node.Target, out label);
                }
 
                // if we're in a switch or block, we should've found the label
                Debug.Assert(label != null);
            }
 
            label ??= DefineLabel(node.Target);
 
            if (node.DefaultValue != null)
            {
                if (node.Target.Type == typeof(void))
                {
                    EmitExpressionAsVoid(node.DefaultValue, flags);
                }
                else
                {
                    flags = UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitExpressionStart);
                    EmitExpression(node.DefaultValue, flags);
                }
            }
 
            label.Mark();
        }
 
        private void EmitGotoExpression(Expression expr, CompilationFlags flags)
        {
            var node = (GotoExpression)expr;
            LabelInfo labelInfo = ReferenceLabel(node.Target);
 
            CompilationFlags tailCall = flags & CompilationFlags.EmitAsTailCallMask;
            if (tailCall != CompilationFlags.EmitAsNoTail)
            {
                // Since tail call flags are not passed into EmitTryExpression, CanReturn
                // means the goto will be emitted as Ret. Therefore we can emit the goto's
                // default value with tail call. This can be improved by detecting if the
                // target label is equivalent to the return label.
                tailCall = labelInfo.CanReturn ? CompilationFlags.EmitAsTail : CompilationFlags.EmitAsNoTail;
                flags = UpdateEmitAsTailCallFlag(flags, tailCall);
            }
 
            if (node.Value != null)
            {
                if (node.Target.Type == typeof(void))
                {
                    EmitExpressionAsVoid(node.Value, flags);
                }
                else
                {
                    flags = UpdateEmitExpressionStartFlag(flags, CompilationFlags.EmitExpressionStart);
                    EmitExpression(node.Value, flags);
                }
            }
 
            labelInfo.EmitJump();
 
            EmitUnreachable(node, flags);
        }
 
        // We need to push default(T), unless we're emitting ourselves as
        // void. Even though the code is unreachable, we still have to
        // generate correct IL. We can get rid of this once we have better
        // reachability analysis.
        private void EmitUnreachable(Expression node, CompilationFlags flags)
        {
            if (node.Type != typeof(void) && (flags & CompilationFlags.EmitAsVoidType) == 0)
            {
                _ilg.EmitDefault(node.Type, this);
            }
        }
 
        private bool TryPushLabelBlock(Expression node)
        {
            // Anything that is "statement-like" -- e.g. has no associated
            // stack state can be jumped into, with the exception of try-blocks
            // We indicate this by a "Block"
            //
            // Otherwise, we push an "Expression" to indicate that it can't be
            // jumped into
            switch (node.NodeType)
            {
                default:
                    if (_labelBlock.Kind != LabelScopeKind.Expression)
                    {
                        PushLabelBlock(LabelScopeKind.Expression);
                        return true;
                    }
                    return false;
                case ExpressionType.Label:
                    // LabelExpression is a bit special, if it's directly in a
                    // block it becomes associate with the block's scope. Same
                    // thing if it's in a switch case body.
                    if (_labelBlock.Kind == LabelScopeKind.Block)
                    {
                        LabelTarget label = ((LabelExpression)node).Target;
                        if (_labelBlock.ContainsTarget(label))
                        {
                            return false;
                        }
                        if (_labelBlock.Parent!.Kind == LabelScopeKind.Switch &&
                            _labelBlock.Parent.ContainsTarget(label))
                        {
                            return false;
                        }
                    }
                    PushLabelBlock(LabelScopeKind.Statement);
                    return true;
                case ExpressionType.Block:
                    if (node is SpilledExpressionBlock)
                    {
                        // treat it as an expression
                        goto default;
                    }
 
                    PushLabelBlock(LabelScopeKind.Block);
                    // Labels defined immediately in the block are valid for
                    // the whole block.
                    if (_labelBlock.Parent!.Kind != LabelScopeKind.Switch)
                    {
                        DefineBlockLabels(node);
                    }
                    return true;
                case ExpressionType.Switch:
                    PushLabelBlock(LabelScopeKind.Switch);
                    // Define labels inside of the switch cases so they are in
                    // scope for the whole switch. This allows "goto case" and
                    // "goto default" to be considered as local jumps.
                    var @switch = (SwitchExpression)node;
                    foreach (SwitchCase c in @switch.Cases)
                    {
                        DefineBlockLabels(c.Body);
                    }
                    DefineBlockLabels(@switch.DefaultBody);
                    return true;
 
                // Remove this when Convert(Void) goes away.
                case ExpressionType.Convert:
                    if (node.Type != typeof(void))
                    {
                        // treat it as an expression
                        goto default;
                    }
                    PushLabelBlock(LabelScopeKind.Statement);
                    return true;
 
                case ExpressionType.Conditional:
                case ExpressionType.Loop:
                case ExpressionType.Goto:
                    PushLabelBlock(LabelScopeKind.Statement);
                    return true;
            }
        }
 
        private void DefineBlockLabels(Expression? node)
        {
            var block = node as BlockExpression;
            if (block == null || block is SpilledExpressionBlock)
            {
                return;
            }
            for (int i = 0, n = block.ExpressionCount; i < n; i++)
            {
                Expression e = block.GetExpression(i);
 
                var label = e as LabelExpression;
                if (label != null)
                {
                    DefineLabel(label.Target);
                }
            }
        }
 
        // See if this lambda has a return label
        // If so, we'll create it now and mark it as allowing the "ret" opcode
        // This allows us to generate better IL
        private void AddReturnLabel(LambdaExpression lambda)
        {
            Expression expression = lambda.Body;
 
            while (true)
            {
                switch (expression.NodeType)
                {
                    default:
                        // Didn't find return label
                        return;
                    case ExpressionType.Label:
                        // Found the label. We can directly return from this place
                        // only if the label type is reference assignable to the lambda return type.
                        LabelTarget label = ((LabelExpression)expression).Target;
                        _labelInfo.Add(label, new LabelInfo(_ilg, label, TypeUtils.AreReferenceAssignable(lambda.ReturnType, label.Type)));
                        return;
                    case ExpressionType.Block:
                        // Look in the last significant expression of a block
                        var body = (BlockExpression)expression;
                        // omit empty and debuginfo at the end of the block since they
                        // are not going to emit any IL
                        if (body.ExpressionCount == 0)
                        {
                            return;
                        }
                        for (int i = body.ExpressionCount - 1; i >= 0; i--)
                        {
                            expression = body.GetExpression(i);
                            if (Significant(expression))
                            {
                                break;
                            }
                        }
                        continue;
                }
            }
        }
    }
}