File: System\Xml\Xsl\IlGen\IteratorDescriptor.cs
Project: ndp\fx\src\XmlUtils\System.Data.SqlXml.csproj (System.Data.SqlXml)
//------------------------------------------------------------------------------
// <copyright file="IteratorDescriptor.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
using System.Globalization;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Xsl.Runtime;
 
namespace System.Xml.Xsl.IlGen {
 
    /// <summary>
    /// Type of location in which iterator items are stored.
    /// </summary>
    internal enum ItemLocation {
        None = 0,
        Stack,                              // Each value is stored as the top value on the IL stack
        Parameter,                          // Each value is stored as a parameter to the current method
        Local,                              // Each value is stored as a local variable in the current method
        Current,                            // Each value is stored as an iterator's Current property
        Global,                             // Each value is stored as a global variable
    };
 
 
    /// <summary>
    /// None--Not in a branching context
    /// True--Branch if boolean expression evaluates to true
    /// False--Branch if boolean expression evaluates to false
    /// </summary>
    internal enum BranchingContext {None, OnTrue, OnFalse};
 
    /// <summary>
    /// Describes the Clr type and location of items returned by an iterator.
    /// This struct is meant to be immutable.
    /// </summary>
    internal struct StorageDescriptor {
        private ItemLocation location;
        private object locationObject;
        private Type itemStorageType;
        private bool isCached;
 
 
        //-----------------------------------------------
        // Create Methods
        //-----------------------------------------------
 
        /// <summary>
        /// Create default, empty StorageDescriptor.
        /// </summary>
        public static StorageDescriptor None() {
            return new StorageDescriptor();
        }
 
        /// <summary>
        /// Create a StorageDescriptor for an item located on the stack.
        /// </summary>
        public static StorageDescriptor Stack(Type itemStorageType, bool isCached) {
            StorageDescriptor storage = new StorageDescriptor();
            storage.location = ItemLocation.Stack;
            storage.itemStorageType = itemStorageType;
            storage.isCached = isCached;
            return storage;
        }
 
        /// <summary>
        /// Create a StorageDescriptor for an item which is a parameter to the current method.
        /// </summary>
        public static StorageDescriptor Parameter(int paramIndex, Type itemStorageType, bool isCached) {
            StorageDescriptor storage = new StorageDescriptor();
            storage.location = ItemLocation.Parameter;
            storage.locationObject = paramIndex;
            storage.itemStorageType = itemStorageType;
            storage.isCached = isCached;
            return storage;
        }
 
        /// <summary>
        /// Create a StorageDescriptor for an item located in a local variable.
        /// </summary>
        public static StorageDescriptor Local(LocalBuilder loc, Type itemStorageType, bool isCached) {
            Debug.Assert(loc.LocalType == itemStorageType ||
                         typeof(IList<>).MakeGenericType(itemStorageType).IsAssignableFrom(loc.LocalType),
                         "Type " + itemStorageType + " does not match the local variable's type");
 
            StorageDescriptor storage = new StorageDescriptor();
            storage.location = ItemLocation.Local;
            storage.locationObject = loc;
            storage.itemStorageType = itemStorageType;
            storage.isCached = isCached;
            return storage;
        }
 
        /// <summary>
        /// Create a StorageDescriptor for an item which is the Current item in an iterator.
        /// </summary>
        public static StorageDescriptor Current(LocalBuilder locIter, Type itemStorageType) {
            Debug.Assert(locIter.LocalType.GetMethod("get_Current").ReturnType == itemStorageType,
                         "Type " + itemStorageType + " does not match type of Current property.");
 
            StorageDescriptor storage = new StorageDescriptor();
            storage.location = ItemLocation.Current;
            storage.locationObject = locIter;
            storage.itemStorageType = itemStorageType;
            return storage;
        }
 
        /// <summary>
        /// Create a StorageDescriptor for an item located in a global variable.
        /// </summary>
        public static StorageDescriptor Global(MethodInfo methGlobal, Type itemStorageType, bool isCached) {
            Debug.Assert(methGlobal.ReturnType == itemStorageType ||
                         typeof(IList<>).MakeGenericType(itemStorageType).IsAssignableFrom(methGlobal.ReturnType),
                         "Type " + itemStorageType + " does not match the global method's return type");
 
            StorageDescriptor storage = new StorageDescriptor();
            storage.location = ItemLocation.Global;
            storage.locationObject = methGlobal;
            storage.itemStorageType = itemStorageType;
            storage.isCached = isCached;
            return storage;
        }
 
 
        //-----------------------------------------------
        // Accessor Methods
        //-----------------------------------------------
 
        /// <summary>
        /// Return copy of current descriptor, but change item's location to the stack.
        /// </summary>
        public StorageDescriptor ToStack() {
            return Stack(this.itemStorageType, this.isCached);
        }
 
        /// <summary>
        /// Create a StorageDescriptor for an item located in a local variable.
        /// </summary>
        public StorageDescriptor ToLocal(LocalBuilder loc) {
            return Local(loc, this.itemStorageType, this.isCached);
        }
 
        /// <summary>
        /// Create a StorageDescriptor which is the same as this one, except for the item storage type.
        /// </summary>
        public StorageDescriptor ToStorageType(Type itemStorageType) {
            StorageDescriptor storage = this;
            storage.itemStorageType = itemStorageType;
            return storage;
        }
 
        /// <summary>
        /// Return an enumeration specifying where the value is located.
        /// </summary>
        public ItemLocation Location {
            get { return this.location; }
        }
 
        /// <summary>
        /// Return the index of the parameter that stores this iterator's values.
        /// </summary>
        public int ParameterLocation {
            get { return (int) this.locationObject; }
        }
 
        /// <summary>
        /// Return the LocalBuilder that stores this iterator's values.
        /// </summary>
        public LocalBuilder LocalLocation {
            get { return this.locationObject as LocalBuilder; }
        }
 
        /// <summary>
        /// Return the LocalBuilder that will store this iterator's helper class.  The Current property
        /// on this iterator can be accessed to get the current iteration value.
        /// </summary>
        public LocalBuilder CurrentLocation {
            get { return this.locationObject as LocalBuilder; }
        }
 
        /// <summary>
        /// Return the MethodInfo for the method that computes this global value.
        /// </summary>
        public MethodInfo GlobalLocation {
            get { return this.locationObject as MethodInfo; }
        }
 
        /// <summary>
        /// Return true if this iterator's values are cached.
        /// </summary>
        public bool IsCached {
            get { return this.isCached; }
        }
 
        /// <summary>
        /// Return the Clr type of an individual item in the storage location (never an IList<> type).
        /// </summary>
        public Type ItemStorageType {
            get { return this.itemStorageType; }
        }
    }
 
    /// <summary>
    /// Iterators are joined together, are nested within each other, and reference each other.  This internal class
    /// contains detailed information about iteration next labels, caching, iterator item location, etc.
    /// </summary>
    internal class IteratorDescriptor {
        private GenerateHelper helper;
 
        // Related iterators
        private IteratorDescriptor iterParent;
 
        // Iteration
        private Label lblNext;
        private bool hasNext;
        private LocalBuilder locPos;
 
        // Branching
        private BranchingContext brctxt;
        private Label lblBranch;
 
        // Storage
        private StorageDescriptor storage;
 
 
        //-----------------------------------------------
        // Initialize
        //-----------------------------------------------
 
        /// <summary>
        /// Create a "root" IteratorDescriptor which has no parent iterator.
        /// </summary>
        public IteratorDescriptor(GenerateHelper helper) {
            Init(null, helper);
        }
 
        /// <summary>
        /// Create an IteratorDescriptor that is nested in a parent iterator.
        /// </summary>
        public IteratorDescriptor(IteratorDescriptor iterParent) {
            Init(iterParent, iterParent.helper);
        }
 
        /// <summary>
        /// Internal helper initializor.
        /// </summary>
        private void Init(IteratorDescriptor iterParent, GenerateHelper helper) {
            this.helper = helper;
            this.iterParent = iterParent;
        }
 
 
        //-----------------------------------------------
        // Related Iterators
        //-----------------------------------------------
 
        /// <summary>
        /// Return the iterator in which this iterator is nested.
        /// </summary>
        public IteratorDescriptor ParentIterator {
            get { return this.iterParent; }
        }
 
 
        //-----------------------------------------------
        // Iteration
        //-----------------------------------------------
 
        /// <summary>
        /// Returns true if LabelNext is currently defined.  If not, then this iterator will return
        /// exactly one result (iterator is cardinality one).
        /// </summary>
        public bool HasLabelNext {
            get { return this.hasNext; }
        }
 
        /// <summary>
        /// Return the label that is anchored to this code iterator's MoveNext code.
        /// </summary>
        public Label GetLabelNext() {
            Debug.Assert(this.hasNext);
            return this.lblNext;
        }
 
        /// <summary>
        /// Set this iterator's next label and storage.  This iterator will range over a set of values located in
        /// "storage".  To get the next value, jump to "lblNext".
        /// </summary>
        public void SetIterator(Label lblNext, StorageDescriptor storage) {
            this.lblNext = lblNext;
            this.hasNext = true;
            this.storage = storage;
        }
 
        /// <summary>
        /// Set this iterator to be the same as the specified iterator.
        /// </summary>
        public void SetIterator(IteratorDescriptor iterInfo) {
            if (iterInfo.HasLabelNext) {
                this.lblNext = iterInfo.GetLabelNext();
                this.hasNext = true;
            }
 
            this.storage = iterInfo.Storage;
        }
 
        /// <summary>
        /// Continue iteration until it is complete.  Branch to "lblOnEnd" when iteration is complete.
        /// </summary>
        /// <remarks>
        /// goto LabelNextCtxt;
        /// LabelOnEnd:
        /// </remarks>
        public void LoopToEnd(Label lblOnEnd) {
            if (this.hasNext) {
                this.helper.BranchAndMark(this.lblNext, lblOnEnd);
                this.hasNext = false;
            }
 
            // After looping is finished, storage is N/A
            this.storage = StorageDescriptor.None();
        }
 
        /// <summary>
        /// Storage location containing the position of the current item as an integer.
        /// This location is only defined on iterators, and then only if they might be
        /// referenced by a PositionOf operator.
        /// </summary>
        public LocalBuilder LocalPosition {
            get { return this.locPos; }
            set { this.locPos = value; }
        }
 
 
        //-----------------------------------------------
        // Caching
        //-----------------------------------------------
 
        /// <summary>
        /// Push the count of items in the cache onto the stack.
        /// </summary>
        public void CacheCount() {
            Debug.Assert(this.storage.IsCached);
            PushValue();
            this.helper.CallCacheCount(this.storage.ItemStorageType);
        }
 
        /// <summary>
        /// If the iterator has been fully cached, then iterate the values one-by-one.
        /// </summary>
        public void EnsureNoCache() {
            if (this.storage.IsCached) {
                if (!HasLabelNext) {
                    // If no Next label, this must be a singleton cache
                    EnsureStack();
                    this.helper.LoadInteger(0);
                    this.helper.CallCacheItem(this.storage.ItemStorageType);
 
                    this.storage = StorageDescriptor.Stack(this.storage.ItemStorageType, false);
                }
                else {
                    // int idx;
                    LocalBuilder locIdx = this.helper.DeclareLocal("$$$idx", typeof(int));
                    Label lblNext;
 
                    // Make sure cache is not on the stack
                    EnsureNoStack("$$$cache");
 
                    // idx = -1;
                    this.helper.LoadInteger(-1);
                    this.helper.Emit(OpCodes.Stloc, locIdx);
 
                    // LabelNext:
                    lblNext = this.helper.DefineLabel();
                    this.helper.MarkLabel(lblNext);
 
                    // idx++;
                    this.helper.Emit(OpCodes.Ldloc, locIdx);
                    this.helper.LoadInteger(1);
                    this.helper.Emit(OpCodes.Add);
                    this.helper.Emit(OpCodes.Stloc, locIdx);
 
                    // if (idx >= cache.Count) goto LabelNextCtxt;
                    this.helper.Emit(OpCodes.Ldloc, locIdx);
                    CacheCount();
                    this.helper.Emit(OpCodes.Bge, GetLabelNext());
 
                    // item = cache[idx];
                    PushValue();
                    this.helper.Emit(OpCodes.Ldloc, locIdx);
                    this.helper.CallCacheItem(this.storage.ItemStorageType);
 
                    SetIterator(lblNext, StorageDescriptor.Stack(this.storage.ItemStorageType, false));
                }
            }
        }
 
 
        //-----------------------------------------------
        // If-then-else branching
        //-----------------------------------------------
 
        /// <summary>
        /// Setup a branching context.  All nested iterators compiled in this context must evaluate
        /// to a single boolean value.  However, these expressions must not push the result as a boolean
        /// onto the stack.  Instead, if brctxt is BranchType.True, then the expression should
        /// jump to lblBranch if it evaluates to true.  If brctxt is BranchType.False, then the
        /// branch should happen if the evaluation result is false.
        /// </summary>
        public void SetBranching(BranchingContext brctxt, Label lblBranch) {
            Debug.Assert(brctxt != BranchingContext.None);
            this.brctxt = brctxt;
            this.lblBranch = lblBranch;
        }
 
        /// <summary>
        /// True if within a branching context.
        /// </summary>
        public bool IsBranching {
            get { return this.brctxt != BranchingContext.None; }
        }
 
        /// <summary>
        /// Returns the label to which conditionals should branch.
        /// </summary>
        public Label LabelBranch {
            get { return this.lblBranch; }
        }
 
        /// <summary>
        /// If BranchingContext.OnTrue, branch on true.  Otherwise, branch on false.
        /// </summary>
        public BranchingContext CurrentBranchingContext {
            get { return this.brctxt; }
        }
 
 
        //-----------------------------------------------
        // Storage
        //-----------------------------------------------
 
        /// <summary>
        /// Returns information about how and where iterator values are stored.
        /// </summary>
        public StorageDescriptor Storage {
            get { return this.storage; }
            set { this.storage = value; }
        }
 
        /// <summary>
        /// Push current item onto the stack without affecting Location.
        /// </summary>
        public void PushValue() {
            switch (this.storage.Location) {
                case ItemLocation.Stack:
                    this.helper.Emit(OpCodes.Dup);
                    break;
 
                case ItemLocation.Parameter:
                    this.helper.LoadParameter(this.storage.ParameterLocation);
                    break;
 
                case ItemLocation.Local:
                    this.helper.Emit(OpCodes.Ldloc, this.storage.LocalLocation);
                    break;
 
                case ItemLocation.Current:
                    this.helper.Emit(OpCodes.Ldloca, this.storage.CurrentLocation);
                    this.helper.Call(this.storage.CurrentLocation.LocalType.GetMethod("get_Current"));
                    break;
 
                default:
                    Debug.Assert(false, "Invalid location: " + this.storage.Location);
                    break;
            }
        }
       
        /// <summary>
        /// Ensure that the current item is pushed onto the stack.
        /// </summary>
        public void EnsureStack() {
            switch (this.storage.Location) {
                case ItemLocation.Stack:
                    // Already on the stack
                    return;
 
                case ItemLocation.Parameter:
                case ItemLocation.Local:
                case ItemLocation.Current:
                    PushValue();
                    break;
 
                case ItemLocation.Global:
                    // Call method that computes the value of this global value
                    this.helper.LoadQueryRuntime();
                    this.helper.Call(this.storage.GlobalLocation);
                    break;
 
                default:
                    Debug.Assert(false, "Invalid location: " + this.storage.Location);
                    break;
            }
 
            this.storage = this.storage.ToStack();
        }
 
        /// <summary>
        /// If the current item is on the stack, move it to a local variable.
        /// </summary>
        public void EnsureNoStack(string locName) {
            if (this.storage.Location == ItemLocation.Stack)
                EnsureLocal(locName);
        }
 
        /// <summary>
        /// If current item is not already in a local variable, then move it to a local variable of the specified name.
        /// </summary>
        public void EnsureLocal(string locName) {
            if (this.storage.Location != ItemLocation.Local) {
                if (this.storage.IsCached)
                    EnsureLocal(this.helper.DeclareLocal(locName, typeof(IList<>).MakeGenericType(this.storage.ItemStorageType)));
                else
                    EnsureLocal(this.helper.DeclareLocal(locName, this.storage.ItemStorageType));
            }
        }
 
        /// <summary>
        /// Ensure that current item is saved to the specified local variable.
        /// </summary>
        public void EnsureLocal(LocalBuilder bldr) {
            if (this.storage.LocalLocation != bldr) {
                // Push value onto stack and then save to bldr
                EnsureStack();
                this.helper.Emit(OpCodes.Stloc, bldr);
                this.storage = this.storage.ToLocal(bldr);
            }
        }
 
        /// <summary>
        /// Discard the current item if it is pushed onto the stack.
        /// </summary>
        public void DiscardStack() {
            if (this.storage.Location == ItemLocation.Stack) {
                this.helper.Emit(OpCodes.Pop);
                this.storage = StorageDescriptor.None();
            }
        }
       
        /// <summary>
        /// Ensure that the iterator's items are not cached, and that the current item is pushed onto the stack.
        /// </summary>
        public void EnsureStackNoCache() {
            EnsureNoCache();
            EnsureStack();
        }
 
        /// <summary>
        /// Ensure that the iterator's items are not cached, and that if the current item is pushed onto the stack,
        /// that it is moved to a local variable.
        /// </summary>
        public void EnsureNoStackNoCache(string locName) {
            EnsureNoCache();
            EnsureNoStack(locName);
        }
 
        /// <summary>
        /// Ensure that the iterator's items are not cached, and that if the current item is not already in a local,
        /// variable, that it is moved to a local variable of the specified name.
        /// </summary>
        public void EnsureLocalNoCache(string locName) {
            EnsureNoCache();
            EnsureLocal(locName);
        }
 
        /// <summary>
        /// Ensure that the iterator's items are not cached and that the current item is saved to the specified local variable.
        /// </summary>
        public void EnsureLocalNoCache(LocalBuilder bldr) {
            EnsureNoCache();
            EnsureLocal(bldr);
        }
 
        /// <summary>
        /// Each XmlQueryType has multiple legal CLR representations.  Ensure that all items returned by this iterator are in
        /// the Clr representation specified by "storageTypeDest".
        /// </summary>
        public void EnsureItemStorageType(XmlQueryType xmlType, Type storageTypeDest) {
            // If source type = destination type, then done
            if (this.storage.ItemStorageType == storageTypeDest)
                goto SetStorageType;
 
            Debug.Assert(this.storage.ItemStorageType == typeof(XPathItem) || storageTypeDest == typeof(XPathItem),
                         "EnsureItemStorageType must convert to or from Item");
 
            // If items are cached,
            if (this.storage.IsCached) {
                // Check for special case of IList<XPathNavigator> -> IList<XPathItem>
                if (this.storage.ItemStorageType == typeof(XPathNavigator)) {
                    EnsureStack();
                    this.helper.Call(XmlILMethods.NavsToItems);
                    goto SetStorageType;
                }
 
                // Check for special case of IList<XPathItem> -> IList<XPathNavigator>
                if (storageTypeDest == typeof(XPathNavigator)) {
                    EnsureStack();
                    this.helper.Call(XmlILMethods.ItemsToNavs);
                    goto SetStorageType;
                }
            }
 
            // Iterate over each item, and convert each to the destination type
            EnsureStackNoCache();
 
            // If source type is Item,
            if (this.storage.ItemStorageType == typeof(XPathItem)) {
                // Then downcast to Navigator
                if (storageTypeDest == typeof(XPathNavigator)) {
                    this.helper.Emit(OpCodes.Castclass, typeof(XPathNavigator));
                }
                else {
                    // Call ValueAs methods for atomic types
                    this.helper.CallValueAs(storageTypeDest);
                }
                goto SetStorageType;
            }
            else if (this.storage.ItemStorageType == typeof(XPathNavigator)) {
                // No-op if converting from XPathNavigator to XPathItem
                Debug.Assert(storageTypeDest == typeof(XPathItem), "Must be converting from XPathNavigator to XPathItem");
                goto SetStorageType;
            }
 
            // Destination type must be item, so generate code to create an XmlAtomicValue
            this.helper.LoadInteger(this.helper.StaticData.DeclareXmlType(xmlType));
            this.helper.LoadQueryRuntime();
            this.helper.Call(XmlILMethods.StorageMethods[this.storage.ItemStorageType].ToAtomicValue);
 
        SetStorageType:
            this.storage = this.storage.ToStorageType(storageTypeDest);
        }
    }
}