File: Microsoft\Scripting\Compiler\LabelInfo.cs
Project: ndp\fx\src\Core\System.Core.csproj (System.Core)
/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Apache License, Version 2.0, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic.Utils;
using System.Reflection.Emit;
 
#if SILVERLIGHT
using System.Core;
#endif
 
#if CLR2
namespace Microsoft.Scripting.Ast.Compiler {
#else
namespace System.Linq.Expressions.Compiler {
#endif
#if CLR2 || SILVERLIGHT
    using ILGenerator = OffsetTrackingILGenerator;
#endif
 
    /// <summary>
    /// Contains compiler state corresponding to a LabelTarget
    /// See also LabelScopeInfo.
    /// </summary>
    internal sealed class LabelInfo {
        // The tree node representing this label
        private readonly LabelTarget _node;
 
        // The IL label, will be mutated if Node is redefined
        private Label _label;
        private bool _labelDefined;
 
        internal Label Label {
            get {
                EnsureLabelAndValue();
                return _label;
            }
        }
 
        // The local that carries the label's value, if any
        private LocalBuilder _value;
 
        // The blocks where this label is defined. If it has more than one item,
        // the blocks can't be jumped to except from a child block
        private readonly Set<LabelScopeInfo> _definitions = new Set<LabelScopeInfo>();
 
        // Blocks that jump to this block
        private readonly List<LabelScopeInfo> _references = new List<LabelScopeInfo>();
 
        // True if this label is the last thing in this block
        // (meaning we can emit a direct return)
        private readonly bool _canReturn;
 
        // True if at least one jump is across blocks
        // If we have any jump across blocks to this label, then the
        // LabelTarget can only be defined in one place
        private bool _acrossBlockJump;
 
        // Until we have more information, default to a leave instruction,
        // which always works. Note: leave spills the stack, so we need to
        // ensure that StackSpiller has guarenteed us an empty stack at this
        // point. Otherwise Leave and Branch are not equivalent
        private OpCode _opCode = OpCodes.Leave;
 
        private readonly ILGenerator _ilg;
 
        internal LabelInfo(ILGenerator il, LabelTarget node, bool canReturn) {
            _ilg = il;
            _node = node;
            _canReturn = canReturn;
        }
 
        internal bool CanReturn {
            get { return _canReturn; }
        }
 
        /// <summary>
        /// Indicates if it is legal to emit a "branch" instruction based on
        /// currently available information. Call the Reference method before 
        /// using this property.
        /// </summary>
        internal bool CanBranch {
            get { return _opCode != OpCodes.Leave; }
        }
 
        internal void Reference(LabelScopeInfo block) {
            _references.Add(block);
            if (_definitions.Count > 0) {
                ValidateJump(block);
            }
        }
 
        // Returns true if the label was successfully defined
        // or false if the label is now ambiguous
        internal void Define(LabelScopeInfo block) {
            // Prevent the label from being shadowed, which enforces cleaner
            // trees. Also we depend on this for simplicity (keeping only one
            // active IL Label per LabelInfo)
            for (LabelScopeInfo j = block; j != null; j = j.Parent) {
                if (j.ContainsTarget(_node)) {
                    throw Error.LabelTargetAlreadyDefined(_node.Name);
                }
            }
 
            _definitions.Add(block);
            block.AddLabelInfo(_node, this);
 
            // Once defined, validate all jumps
            if (_definitions.Count == 1) {
                foreach (var r in _references) {
                    ValidateJump(r);
                }
            } else {
                // Was just redefined, if we had any across block jumps, they're
                // now invalid
                if (_acrossBlockJump) {
                    throw Error.AmbiguousJump(_node.Name);
                }
                // For local jumps, we need a new IL label
                // This is okay because:
                //   1. no across block jumps have been made or will be made
                //   2. we don't allow the label to be shadowed
                _labelDefined = false;
            }
        }
 
        private void ValidateJump(LabelScopeInfo reference) {
            // Assume we can do a ret/branch
            _opCode = _canReturn ? OpCodes.Ret : OpCodes.Br;
 
            // look for a simple jump out
            for (LabelScopeInfo j = reference; j != null; j = j.Parent) {
                if (_definitions.Contains(j)) {
                    // found it, jump is valid!
                    return;
                }
                if (j.Kind == LabelScopeKind.Finally ||
                    j.Kind == LabelScopeKind.Filter) {
                    break;
                }
                if (j.Kind == LabelScopeKind.Try ||
                    j.Kind == LabelScopeKind.Catch) {
                    _opCode = OpCodes.Leave;
                }
            }
 
            _acrossBlockJump = true;
            if (_node != null && _node.Type != typeof(void)) {
                throw Error.NonLocalJumpWithValue(_node.Name);
            }
 
            if (_definitions.Count > 1) {
                throw Error.AmbiguousJump(_node.Name);
            }
 
            // We didn't find an outward jump. Look for a jump across blocks
            LabelScopeInfo def = _definitions.First();
            LabelScopeInfo common = Helpers.CommonNode(def, reference, b => b.Parent);
 
            // Assume we can do a ret/branch
            _opCode = _canReturn ? OpCodes.Ret : OpCodes.Br;
 
            // Validate that we aren't jumping across a finally
            for (LabelScopeInfo j = reference; j != common; j = j.Parent) {
                if (j.Kind == LabelScopeKind.Finally) {
                    throw Error.ControlCannotLeaveFinally();
                }
                if (j.Kind == LabelScopeKind.Filter) {
                    throw Error.ControlCannotLeaveFilterTest();
                }
                if (j.Kind == LabelScopeKind.Try ||
                    j.Kind == LabelScopeKind.Catch) {
                    _opCode = OpCodes.Leave;
                }
            }
 
            // Valdiate that we aren't jumping into a catch or an expression
            for (LabelScopeInfo j = def; j != common; j = j.Parent) {
                if (!j.CanJumpInto) {
                    if (j.Kind == LabelScopeKind.Expression) {
                        throw Error.ControlCannotEnterExpression();
                    } else {
                        throw Error.ControlCannotEnterTry();
                    }
                }
            }
        }
 
        internal void ValidateFinish() {
            // Make sure that if this label was jumped to, it is also defined
            if (_references.Count > 0 && _definitions.Count == 0) {
                throw Error.LabelTargetUndefined(_node.Name);
            }
        }
 
        internal void EmitJump() {
            // Return directly if we can
            if (_opCode == OpCodes.Ret) {
                _ilg.Emit(OpCodes.Ret);
            } else {
                StoreValue();
                _ilg.Emit(_opCode, Label);
            }
        }
 
        private void StoreValue() {
            EnsureLabelAndValue();
            if (_value != null) {
                _ilg.Emit(OpCodes.Stloc, _value);
            }
        }
 
        internal void Mark() {
            if (_canReturn) {
                // Don't mark return labels unless they were actually jumped to
                // (returns are last so we know for sure if anyone jumped to it)
                if (!_labelDefined) {
                    // We don't even need to emit the "ret" because
                    // LambdaCompiler does that for us.
                    return;
                }
 
                // Otherwise, emit something like:
                // ret
                // <marked label>:
                // ldloc <value>
                _ilg.Emit(OpCodes.Ret);
            } else {
 
                // For the normal case, we emit:
                // stloc <value>
                // <marked label>:
                // ldloc <value>
                StoreValue();
            }
            MarkWithEmptyStack();
        }
 
        // Like Mark, but assumes the stack is empty
        internal void MarkWithEmptyStack() {
            _ilg.MarkLabel(Label);
            if (_value != null) {
                // We always read the value from a local, because we don't know
                // if there will be a "leave" instruction targeting it ("branch"
                // preserves its stack, but "leave" empties the stack)
                _ilg.Emit(OpCodes.Ldloc, _value);
            }
        }
 
        private void EnsureLabelAndValue() {
            if (!_labelDefined) {
                _labelDefined = true;
                _label = _ilg.DefineLabel();
                if (_node != null && _node.Type != typeof(void)) {
                    _value = _ilg.DeclareLocal(_node.Type);
                }
            }
        }
    }
 
    internal enum LabelScopeKind {
        // any "statement like" node that can be jumped into
        Statement,
 
        // these correspond to the node of the same name
        Block,
        Switch,
        Lambda,
        Try,
 
        // these correspond to the part of the try block we're in
        Catch,
        Finally,
        Filter,
 
        // the catch-all value for any other expression type
        // (means we can't jump into it)
        Expression,
    }
 
    //
    // Tracks scoping information for LabelTargets. Logically corresponds to a
    // "label scope". Even though we have arbitrary goto support, we still need
    // to track what kinds of nodes that gotos are jumping through, both to
    // emit property IL ("leave" out of a try block), and for validation, and
    // to allow labels to be duplicated in the tree, as long as the jumps are
    // considered "up only" jumps.
    //
    // We create one of these for every Expression that can be jumped into, as
    // well as creating them for the first expression we can't jump into. The
    // "Kind" property indicates what kind of scope this is.
    //
    internal sealed class LabelScopeInfo {
        private Dictionary<LabelTarget, LabelInfo> Labels; // lazily allocated, we typically use this only once every 6th-7th block
        internal readonly LabelScopeKind Kind;
        internal readonly LabelScopeInfo Parent;
 
        internal LabelScopeInfo(LabelScopeInfo parent, LabelScopeKind kind) {
            Parent = parent;
            Kind = kind;
        }
 
        /// <summary>
        /// Returns true if we can jump into this node
        /// </summary>
        internal bool CanJumpInto {
            get {
                switch (Kind) {
                    case LabelScopeKind.Block:
                    case LabelScopeKind.Statement:
                    case LabelScopeKind.Switch:
                    case LabelScopeKind.Lambda:
                        return true;
                }
                return false;
            }
        }
 
 
        internal bool ContainsTarget(LabelTarget target) {
            if (Labels == null) {
                return false;
            }
 
            return Labels.ContainsKey(target);
        }
 
        internal bool TryGetLabelInfo(LabelTarget target, out LabelInfo info) {
            if (Labels == null) {
                info = null;
                return false;
            }
 
            return Labels.TryGetValue(target, out info);
        }
 
        internal void AddLabelInfo(LabelTarget target, LabelInfo info) {
            Debug.Assert(CanJumpInto);
 
            if (Labels == null) {
                Labels = new Dictionary<LabelTarget, LabelInfo>();
            }
 
            Labels.Add(target, info);
        }
    }
}