File: System\Xml\Xsl\IlGen\XmlIlVisitor.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Numerics;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Xml.Xsl.Qil;
using System.Xml.Xsl.Runtime;
using TypeFactory = System.Xml.Xsl.XmlQueryTypeFactory;
 
namespace System.Xml.Xsl.IlGen
{
    /// <summary>
    /// Creates Msil code for an entire QilExpression graph.  Code is generated in one of two modes: push or
    /// pull.  In push mode, code is generated to push the values in an iterator to the XmlWriter
    /// interface.  In pull mode, the values in an iterator are stored in a physical location such as
    /// the stack or a local variable by an iterator.  The iterator is passive, and will just wait for
    /// a caller to pull the data and/or instruct the iterator to enumerate the next value.
    /// </summary>
    [RequiresDynamicCode("Creates DynamicMethods")]
    internal sealed class XmlILVisitor : QilVisitor
    {
        private QilExpression _qil = null!;
        private GenerateHelper _helper = null!;
        private IteratorDescriptor _iterCurr = null!;
        private IteratorDescriptor? _iterNested;
        private int _indexId;
 
        [RequiresUnreferencedCode("Method VisitXsltInvokeEarlyBound will require code that cannot be statically analyzed.")]
        public XmlILVisitor()
        { }
 
        //-----------------------------------------------
        // Entry
        //-----------------------------------------------
 
        /// <summary>
        /// Visits the specified QilExpression graph and generates MSIL code.
        /// </summary>
        public void Visit(QilExpression qil, GenerateHelper helper, MethodInfo methRoot)
        {
            _qil = qil;
            _helper = helper;
            _iterNested = null;
            _indexId = 0;
 
            // Prepare each global parameter and global variable to be visited
            PrepareGlobalValues(qil.GlobalParameterList);
            PrepareGlobalValues(qil.GlobalVariableList);
 
            // Visit each global parameter and global variable
            VisitGlobalValues(qil.GlobalParameterList);
            VisitGlobalValues(qil.GlobalVariableList);
 
            // Build each function
            foreach (QilFunction ndFunc in qil.FunctionList)
            {
                // Visit each parameter and the function body
                Function(ndFunc);
            }
 
            // Build the root expression
            _helper.MethodBegin(methRoot, null, true);
            StartNestedIterator();
            Visit(qil.Root);
            Debug.Assert(_iterCurr.Storage.Location == ItemLocation.None, "Root expression should have been pushed to the writer.");
            EndNestedIterator(qil.Root);
            _helper.MethodEnd();
        }
 
        /// <summary>
        /// Create IteratorDescriptor for each global value.  This pre-visit is necessary because a global early
        /// in the list may reference a global later in the list and therefore expect its IteratorDescriptor to already
        /// be initialized.
        /// </summary>
        private void PrepareGlobalValues(QilList globalIterators)
        {
            MethodInfo? methGlobal;
            IteratorDescriptor iterInfo;
 
            foreach (QilIterator iter in globalIterators)
            {
                Debug.Assert(iter.NodeType == QilNodeType.Let || iter.NodeType == QilNodeType.Parameter);
 
                // Get metadata for method which computes this global's value
                methGlobal = XmlILAnnotation.Write(iter).FunctionBinding;
                Debug.Assert(methGlobal != null, "Metadata for global value should have already been computed");
 
                // Create an IteratorDescriptor for this global value
                iterInfo = new IteratorDescriptor(_helper);
 
                // Iterator items will be stored in a global location
                iterInfo.Storage = StorageDescriptor.Global(methGlobal, GetItemStorageType(iter), !iter.XmlType!.IsSingleton);
 
                // Associate IteratorDescriptor with parameter
                XmlILAnnotation.Write(iter).CachedIteratorDescriptor = iterInfo;
            }
        }
 
        /// <summary>
        /// Visit each global variable or parameter.  Create a IteratorDescriptor for each global value.  Generate code for
        /// default values.
        /// </summary>
        private void VisitGlobalValues(QilList globalIterators)
        {
            MethodInfo methGlobal;
            Label lblGetGlobal, lblComputeGlobal;
            bool isCached;
            int idxValue;
 
            foreach (QilIterator iter in globalIterators)
            {
                QilParameter? param = iter as QilParameter;
 
                // Get MethodInfo for method that computes the value of this global
                methGlobal = XmlILAnnotation.Write(iter).CachedIteratorDescriptor!.Storage.GlobalLocation!;
                isCached = !iter.XmlType!.IsSingleton;
 
                // Notify the StaticDataManager of the new global value
                idxValue = _helper.StaticData.DeclareGlobalValue(iter.DebugName!);
 
                // Generate code for this method
                _helper.MethodBegin(methGlobal, iter.SourceLine, false);
 
                lblGetGlobal = _helper.DefineLabel();
                lblComputeGlobal = _helper.DefineLabel();
 
                // if (runtime.IsGlobalComputed(idx)) goto LabelGetGlobal;
                _helper.LoadQueryRuntime();
                _helper.LoadInteger(idxValue);
                _helper.Call(XmlILMethods.GlobalComputed);
                _helper.Emit(OpCodes.Brtrue, lblGetGlobal);
 
                // Compute value of global value
                StartNestedIterator();
 
                if (param != null)
                {
                    Debug.Assert(iter.XmlType == TypeFactory.ItemS, "IlGen currently only supports parameters of type item*.");
 
                    // param = runtime.ExternalContext.GetParameter(localName, namespaceUri);
                    // if (param == null) goto LabelComputeGlobal;
                    LocalBuilder locParam = _helper.DeclareLocal("$$$param", typeof(object));
                    _helper.CallGetParameter(param.Name!.LocalName, param.Name.NamespaceUri);
                    _helper.Emit(OpCodes.Stloc, locParam);
                    _helper.Emit(OpCodes.Ldloc, locParam);
                    _helper.Emit(OpCodes.Brfalse, lblComputeGlobal);
 
                    // runtime.SetGlobalValue(idxValue, runtime.ChangeTypeXsltResult(idxType, value));
                    // Ensure that the storage type of the parameter corresponds to static type
                    _helper.LoadQueryRuntime();
                    _helper.LoadInteger(idxValue);
 
                    _helper.LoadQueryRuntime();
                    _helper.LoadInteger(_helper.StaticData.DeclareXmlType(XmlQueryTypeFactory.ItemS));
                    _helper.Emit(OpCodes.Ldloc, locParam);
                    _helper.Call(XmlILMethods.ChangeTypeXsltResult);
 
                    _helper.CallSetGlobalValue(typeof(object));
 
                    // goto LabelGetGlobal;
                    _helper.EmitUnconditionalBranch(OpCodes.Br, lblGetGlobal);
                }
 
                // LabelComputeGlobal:
                _helper.MarkLabel(lblComputeGlobal);
 
                if (iter.Binding != null)
                {
                    // runtime.SetGlobalValue(idxValue, (object) value);
                    _helper.LoadQueryRuntime();
                    _helper.LoadInteger(idxValue);
 
                    // Compute value of global value
                    NestedVisitEnsureStack(iter.Binding, GetItemStorageType(iter), isCached);
 
                    _helper.CallSetGlobalValue(GetStorageType(iter));
                }
                else
                {
                    // Throw exception, as there is no default value for this parameter
                    // XmlQueryRuntime.ThrowException("...");
                    Debug.Assert(iter.NodeType == QilNodeType.Parameter, "Only parameters may not have a default value");
                    _helper.LoadQueryRuntime();
                    _helper.Emit(OpCodes.Ldstr, SR.Format(SR.XmlIl_UnknownParam, new string?[] { param!.Name!.LocalName, param.Name.NamespaceUri }));
                    _helper.Call(XmlILMethods.ThrowException);
                }
 
                EndNestedIterator(iter);
 
                // LabelGetGlobal:
                // return (T) runtime.GetGlobalValue(idxValue);
                _helper.MarkLabel(lblGetGlobal);
                _helper.CallGetGlobalValue(idxValue, GetStorageType(iter));
 
                _helper.MethodEnd();
            }
        }
 
        /// <summary>
        /// Generate code for the specified function.
        /// </summary>
        private void Function(QilFunction ndFunc)
        {
            MethodInfo methFunc;
            int paramId;
            IteratorDescriptor iterInfo;
            bool useWriter;
 
            // Annotate each function parameter with a IteratorDescriptor
            foreach (QilIterator iter in ndFunc.Arguments)
            {
                Debug.Assert(iter.NodeType == QilNodeType.Parameter);
 
                // Create an IteratorDescriptor for this parameter
                iterInfo = new IteratorDescriptor(_helper);
 
                // Add one to parameter index, as 0th parameter is always "this"
                paramId = XmlILAnnotation.Write(iter).ArgumentPosition + 1;
 
                // The ParameterInfo for each argument should be set as its location
                iterInfo.Storage = StorageDescriptor.Parameter(paramId, GetItemStorageType(iter), !iter.XmlType!.IsSingleton);
 
                // Associate IteratorDescriptor with Let iterator
                XmlILAnnotation.Write(iter).CachedIteratorDescriptor = iterInfo;
            }
 
            methFunc = XmlILAnnotation.Write(ndFunc).FunctionBinding!;
            useWriter = (XmlILConstructInfo.Read(ndFunc).ConstructMethod == XmlILConstructMethod.Writer);
 
            // Generate query code from QilExpression tree
            _helper.MethodBegin(methFunc, ndFunc.SourceLine, useWriter);
 
            foreach (QilIterator iter in ndFunc.Arguments)
            {
                // DebugInfo: Sequence point just before generating code for the bound expression
                if (_qil.IsDebug && iter.SourceLine != null)
                    _helper.DebugSequencePoint(iter.SourceLine);
 
                // Calculate default value of this parameter
                if (iter.Binding != null)
                {
                    Debug.Assert(iter.XmlType == TypeFactory.ItemS, "IlGen currently only supports default values in parameters of type item*.");
                    paramId = (iter.Annotation as XmlILAnnotation)!.ArgumentPosition + 1;
 
                    // runtime.MatchesXmlType(param, XmlTypeCode.QName);
                    Label lblLocalComputed = _helper.DefineLabel();
                    _helper.LoadQueryRuntime();
                    _helper.LoadParameter(paramId);
                    _helper.LoadInteger((int)XmlTypeCode.QName);
                    _helper.Call(XmlILMethods.SeqMatchesCode);
 
                    _helper.Emit(OpCodes.Brfalse, lblLocalComputed);
 
                    // Compute default value of this parameter
                    StartNestedIterator();
                    NestedVisitEnsureStack(iter.Binding, GetItemStorageType(iter), /*isCached:*/!iter.XmlType.IsSingleton);
                    EndNestedIterator(iter);
 
                    _helper.SetParameter(paramId);
                    _helper.MarkLabel(lblLocalComputed);
                }
            }
 
            StartNestedIterator();
 
            // If function did not push results to writer, then function will return value(s) (rather than void)
            if (useWriter)
                NestedVisit(ndFunc.Definition);
            else
                NestedVisitEnsureStack(ndFunc.Definition, GetItemStorageType(ndFunc), !ndFunc.XmlType!.IsSingleton);
 
            EndNestedIterator(ndFunc);
 
            _helper.MethodEnd();
        }
 
        //-----------------------------------------------
        // QilVisitor
        //-----------------------------------------------
 
        /// <summary>
        /// Generate a query plan for the QilExpression subgraph.
        /// </summary>
        protected override QilNode Visit(QilNode nd)
        {
            if (nd == null)
                return null!;
 
            // DebugInfo: Sequence point just before generating code for this expression
            if (_qil.IsDebug && nd.SourceLine != null && !(nd is QilIterator))
                _helper.DebugSequencePoint(nd.SourceLine);
 
            // Expressions are constructed using one of several possible methods
            switch (XmlILConstructInfo.Read(nd).ConstructMethod)
            {
                case XmlILConstructMethod.WriterThenIterator:
                    // Push results of expression to cached writer; then iterate over cached results
                    NestedConstruction(nd);
                    break;
 
                case XmlILConstructMethod.IteratorThenWriter:
                    // Iterate over items in the sequence; send items to writer
                    CopySequence(nd);
                    break;
 
                case XmlILConstructMethod.Iterator:
                    Debug.Assert(nd.XmlType!.IsSingleton || CachesResult(nd) || _iterCurr.HasLabelNext,
                                 "When generating code for a non-singleton expression, LabelNext must be defined.");
                    goto default;
 
                default:
                    // Allow base internal class to dispatch to correct Visit method
                    base.Visit(nd);
                    break;
            }
 
            return nd;
        }
 
        /// <summary>
        /// VisitChildren should never be called.
        /// </summary>
        protected override QilNode VisitChildren(QilNode parent)
        {
            Debug.Fail($"Visit{parent.NodeType} should never be called");
            return parent;
        }
 
        /// <summary>
        /// Generate code to cache a sequence of items that are pushed to output.
        /// </summary>
        private void NestedConstruction(QilNode nd)
        {
            // Start nested construction of a sequence of items
            _helper.CallStartSequenceConstruction();
 
            // Allow base internal class to dispatch to correct Visit method
            base.Visit(nd);
 
            // Get the result sequence
            _helper.CallEndSequenceConstruction();
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), true);
        }
 
        /// <summary>
        /// Iterate over items produced by the "nd" expression and copy each item to output.
        /// </summary>
        private void CopySequence(QilNode nd)
        {
            XmlQueryType typ = nd.XmlType!;
            bool hasOnEnd;
            Label lblOnEnd;
 
            StartWriterLoop(nd, out hasOnEnd, out lblOnEnd);
 
            if (typ.IsSingleton)
            {
                // Always write atomic values via XmlQueryOutput
                _helper.LoadQueryOutput();
 
                // Allow base internal class to dispatch to correct Visit method
                base.Visit(nd);
                _iterCurr.EnsureItemStorageType(nd.XmlType!, typeof(XPathItem));
            }
            else
            {
                // Allow base internal class to dispatch to correct Visit method
                base.Visit(nd);
                _iterCurr.EnsureItemStorageType(nd.XmlType!, typeof(XPathItem));
 
                // Save any stack values in a temporary local
                _iterCurr.EnsureNoStackNoCache("$$$copyTemp");
 
                _helper.LoadQueryOutput();
            }
 
            // Write value to output
            _iterCurr.EnsureStackNoCache();
            _helper.Call(XmlILMethods.WriteItem);
 
            EndWriterLoop(nd, hasOnEnd, lblOnEnd);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.DataSource.
        /// </summary>
        /// <remarks>
        /// Generates code to retrieve a document using the XmlResolver.
        /// </remarks>
        protected override QilNode VisitDataSource(QilDataSource ndSrc)
        {
            LocalBuilder locNav;
 
            // XPathNavigator navDoc = runtime.ExternalContext.GetEntity(uri)
            _helper.LoadQueryContext();
            NestedVisitEnsureStack(ndSrc.Name);
            NestedVisitEnsureStack(ndSrc.BaseUri);
            _helper.Call(XmlILMethods.GetDataSource);
 
            locNav = _helper.DeclareLocal("$$$navDoc", typeof(XPathNavigator));
            _helper.Emit(OpCodes.Stloc, locNav);
 
            // if (navDoc == null) goto LabelNextCtxt;
            _helper.Emit(OpCodes.Ldloc, locNav);
            _helper.Emit(OpCodes.Brfalse, _iterCurr.GetLabelNext());
 
            _iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
 
            return ndSrc;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Nop.
        /// </summary>
        protected override QilNode VisitNop(QilUnary ndNop)
        {
            return Visit(ndNop.Child);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.OptimizeBarrier.
        /// </summary>
        protected override QilNode VisitOptimizeBarrier(QilUnary ndBarrier)
        {
            return Visit(ndBarrier.Child);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Error.
        /// </summary>
        protected override QilNode VisitError(QilUnary ndErr)
        {
            // XmlQueryRuntime.ThrowException(strErr);
            _helper.LoadQueryRuntime();
            NestedVisitEnsureStack(ndErr.Child);
            _helper.Call(XmlILMethods.ThrowException);
 
            if (XmlILConstructInfo.Read(ndErr).ConstructMethod == XmlILConstructMethod.Writer)
            {
                _iterCurr.Storage = StorageDescriptor.None();
            }
            else
            {
                // Push dummy value so that Location is not None and IL rules are met
                _helper.Emit(OpCodes.Ldnull);
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), false);
            }
 
            return ndErr;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Warning.
        /// </summary>
        protected override QilNode VisitWarning(QilUnary ndWarning)
        {
            // runtime.SendMessage(strWarning);
            _helper.LoadQueryRuntime();
            NestedVisitEnsureStack(ndWarning.Child);
            _helper.Call(XmlILMethods.SendMessage);
 
            if (XmlILConstructInfo.Read(ndWarning).ConstructMethod == XmlILConstructMethod.Writer)
                _iterCurr.Storage = StorageDescriptor.None();
            else
                VisitEmpty(ndWarning);
 
            return ndWarning;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.True.
        /// </summary>
        /// <remarks>
        /// BranchingContext.OnFalse context: [nothing]
        /// BranchingContext.OnTrue context:  goto LabelParent;
        /// BranchingContext.None context:  push true();
        /// </remarks>
        protected override QilNode VisitTrue(QilNode ndTrue)
        {
            if (_iterCurr.CurrentBranchingContext != BranchingContext.None)
            {
                // Make sure there's an IL code path to both the true and false branches in order to avoid dead
                // code which can cause IL verification errors.
                _helper.EmitUnconditionalBranch(_iterCurr.CurrentBranchingContext == BranchingContext.OnTrue ?
                        OpCodes.Brtrue : OpCodes.Brfalse, _iterCurr.LabelBranch);
 
                _iterCurr.Storage = StorageDescriptor.None();
            }
            else
            {
                // Push boolean result onto the stack
                _helper.LoadBoolean(true);
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
            }
 
            return ndTrue;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.False.
        /// </summary>
        /// <remarks>
        /// BranchingContext.OnFalse context: goto LabelParent;
        /// BranchingContext.OnTrue context:  [nothing]
        /// BranchingContext.None context:  push false();
        /// </remarks>
        protected override QilNode VisitFalse(QilNode ndFalse)
        {
            if (_iterCurr.CurrentBranchingContext != BranchingContext.None)
            {
                // Make sure there's an IL code path to both the true and false branches in order to avoid dead
                // code which can cause IL verification errors.
                _helper.EmitUnconditionalBranch(_iterCurr.CurrentBranchingContext == BranchingContext.OnFalse ?
                        OpCodes.Brtrue : OpCodes.Brfalse, _iterCurr.LabelBranch);
 
                _iterCurr.Storage = StorageDescriptor.None();
            }
            else
            {
                // Push boolean result onto the stack
                _helper.LoadBoolean(false);
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
            }
 
            return ndFalse;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LiteralString.
        /// </summary>
        protected override QilNode VisitLiteralString(QilLiteral ndStr)
        {
            _helper.Emit(OpCodes.Ldstr, (string)ndStr);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
            return ndStr;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LiteralInt32.
        /// </summary>
        protected override QilNode VisitLiteralInt32(QilLiteral ndInt)
        {
            _helper.LoadInteger((int)ndInt);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(int), false);
            return ndInt;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LiteralInt64.
        /// </summary>
        protected override QilNode VisitLiteralInt64(QilLiteral ndLong)
        {
            _helper.Emit(OpCodes.Ldc_I8, (long)ndLong);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(long), false);
            return ndLong;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LiteralDouble.
        /// </summary>
        protected override QilNode VisitLiteralDouble(QilLiteral ndDbl)
        {
            _helper.Emit(OpCodes.Ldc_R8, (double)ndDbl);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(double), false);
            return ndDbl;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LiteralDecimal.
        /// </summary>
        protected override QilNode VisitLiteralDecimal(QilLiteral ndDec)
        {
            _helper.ConstructLiteralDecimal((decimal)ndDec);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(decimal), false);
            return ndDec;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LiteralQName.
        /// </summary>
        protected override QilNode VisitLiteralQName(QilName ndQName)
        {
            _helper.ConstructLiteralQName(ndQName.LocalName, ndQName.NamespaceUri);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XmlQualifiedName), false);
            return ndQName;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.And.
        /// </summary>
        /// <remarks>
        /// BranchingContext.OnFalse context: (expr1) and (expr2)
        /// ==> if (!expr1) goto LabelParent;
        ///     if (!expr2) goto LabelParent;
        ///
        /// BranchingContext.OnTrue context: (expr1) and (expr2)
        /// ==> if (!expr1) goto LabelTemp;
        ///     if (expr1) goto LabelParent;
        ///     LabelTemp:
        ///
        /// BranchingContext.None context: (expr1) and (expr2)
        /// ==> if (!expr1) goto LabelTemp;
        ///     if (!expr1) goto LabelTemp;
        ///     push true();
        ///     goto LabelSkip;
        ///     LabelTemp:
        ///     push false();
        ///     LabelSkip:
        ///
        /// </remarks>
        protected override QilNode VisitAnd(QilBinary ndAnd)
        {
            IteratorDescriptor iterParent = _iterCurr;
            Label lblOnFalse;
 
            // Visit left branch
            StartNestedIterator();
            lblOnFalse = StartConjunctiveTests(iterParent.CurrentBranchingContext, iterParent.LabelBranch);
            Visit(ndAnd.Left);
            EndNestedIterator(ndAnd.Left);
 
            // Visit right branch
            StartNestedIterator();
            StartLastConjunctiveTest(iterParent.CurrentBranchingContext, iterParent.LabelBranch, lblOnFalse);
            Visit(ndAnd.Right);
            EndNestedIterator(ndAnd.Right);
 
            // End And expression
            EndConjunctiveTests(iterParent.CurrentBranchingContext, lblOnFalse);
 
            return ndAnd;
        }
 
        /// <summary>
        /// Fixup branching context for all but the last test in a conjunctive (Logical And) expression.
        /// Return a temporary label which will be passed to StartLastAndBranch() and EndAndBranch().
        /// </summary>
        private Label StartConjunctiveTests(BranchingContext brctxt, Label lblBranch)
        {
            Label lblOnFalse;
 
            switch (brctxt)
            {
                case BranchingContext.OnFalse:
                    // If condition evaluates to false, branch to false label
                    _iterCurr.SetBranching(BranchingContext.OnFalse, lblBranch);
                    return lblBranch;
 
                default:
                    // If condition evaluates to false:
                    //   1. Jump to new false label that will be fixed just beyond the second condition
                    //   2. Or, jump to code that pushes "false"
                    lblOnFalse = _helper.DefineLabel();
                    _iterCurr.SetBranching(BranchingContext.OnFalse, lblOnFalse);
                    return lblOnFalse;
            }
        }
 
        /// <summary>
        /// Fixup branching context for the last test in a conjunctive (Logical And) expression.
        /// </summary>
        private void StartLastConjunctiveTest(BranchingContext brctxt, Label lblBranch, Label lblOnFalse)
        {
            switch (brctxt)
            {
                case BranchingContext.OnTrue:
                    // If last condition evaluates to true, branch to true label
                    _iterCurr.SetBranching(BranchingContext.OnTrue, lblBranch);
                    break;
 
                default:
                    // If last condition evaluates to false, branch to false label
                    // Else fall through to true code path
                    _iterCurr.SetBranching(BranchingContext.OnFalse, lblOnFalse);
                    break;
            }
        }
 
        /// <summary>
        /// Anchor any remaining labels.
        /// </summary>
        private void EndConjunctiveTests(BranchingContext brctxt, Label lblOnFalse)
        {
            switch (brctxt)
            {
                case BranchingContext.OnTrue:
                    // Anchor false label
                    _helper.MarkLabel(lblOnFalse);
                    goto case BranchingContext.OnFalse;
 
                case BranchingContext.OnFalse:
                    _iterCurr.Storage = StorageDescriptor.None();
                    break;
 
                case BranchingContext.None:
                    // Convert branch targets into push of true/false
                    _helper.ConvBranchToBool(lblOnFalse, false);
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
                    break;
            }
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Or.
        /// </summary>
        /// <remarks>
        /// BranchingContext.OnFalse context: (expr1) or (expr2)
        /// ==> if (expr1) goto LabelTemp;
        ///     if (!expr2) goto LabelParent;
        ///     LabelTemp:
        ///
        /// BranchingContext.OnTrue context: (expr1) or (expr2)
        /// ==> if (expr1) goto LabelParent;
        ///     if (expr1) goto LabelParent;
        ///
        /// BranchingContext.None context: (expr1) or (expr2)
        /// ==> if (expr1) goto LabelTemp;
        ///     if (expr1) goto LabelTemp;
        ///     push false();
        ///     goto LabelSkip;
        ///     LabelTemp:
        ///     push true();
        ///     LabelSkip:
        ///
        /// </remarks>
        protected override QilNode VisitOr(QilBinary ndOr)
        {
            Label lblTemp = default;
 
            // Visit left branch
            switch (_iterCurr.CurrentBranchingContext)
            {
                case BranchingContext.OnFalse:
                    // If left condition evaluates to true, jump to new label that will be fixed
                    // just beyond the second condition
                    lblTemp = _helper.DefineLabel();
                    NestedVisitWithBranch(ndOr.Left, BranchingContext.OnTrue, lblTemp);
                    break;
 
                case BranchingContext.OnTrue:
                    // If left condition evaluates to true, branch to true label
                    NestedVisitWithBranch(ndOr.Left, BranchingContext.OnTrue, _iterCurr.LabelBranch);
                    break;
 
                default:
                    // If left condition evaluates to true, jump to code that pushes "true"
                    Debug.Assert(_iterCurr.CurrentBranchingContext == BranchingContext.None);
                    lblTemp = _helper.DefineLabel();
                    NestedVisitWithBranch(ndOr.Left, BranchingContext.OnTrue, lblTemp);
                    break;
            }
 
            // Visit right branch
            switch (_iterCurr.CurrentBranchingContext)
            {
                case BranchingContext.OnFalse:
                    // If right condition evaluates to false, branch to false label
                    NestedVisitWithBranch(ndOr.Right, BranchingContext.OnFalse, _iterCurr.LabelBranch);
                    break;
 
                case BranchingContext.OnTrue:
                    // If right condition evaluates to true, branch to true label
                    NestedVisitWithBranch(ndOr.Right, BranchingContext.OnTrue, _iterCurr.LabelBranch);
                    break;
 
                default:
                    // If right condition evaluates to true, jump to code that pushes "true".
                    // Otherwise, if both conditions evaluate to false, fall through code path
                    // will push "false".
                    NestedVisitWithBranch(ndOr.Right, BranchingContext.OnTrue, lblTemp);
                    break;
            }
 
            switch (_iterCurr.CurrentBranchingContext)
            {
                case BranchingContext.OnFalse:
                    // Anchor true label
                    _helper.MarkLabel(lblTemp);
                    goto case BranchingContext.OnTrue;
 
                case BranchingContext.OnTrue:
                    _iterCurr.Storage = StorageDescriptor.None();
                    break;
 
                case BranchingContext.None:
                    // Convert branch targets into push of true/false
                    _helper.ConvBranchToBool(lblTemp, true);
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
                    break;
            }
 
            return ndOr;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Not.
        /// </summary>
        /// <remarks>
        /// BranchingContext.OnFalse context: not(expr1)
        /// ==> if (expr1) goto LabelParent;
        ///
        /// BranchingContext.OnTrue context: not(expr1)
        /// ==> if (!expr1) goto LabelParent;
        ///
        /// BranchingContext.None context: not(expr1)
        /// ==> if (expr1) goto LabelTemp;
        ///     push false();
        ///     goto LabelSkip;
        ///     LabelTemp:
        ///     push true();
        ///     LabelSkip:
        ///
        /// </remarks>
        protected override QilNode VisitNot(QilUnary ndNot)
        {
            Label lblTemp = default;
 
            // Visit operand
            // Reverse branch types
            switch (_iterCurr.CurrentBranchingContext)
            {
                case BranchingContext.OnFalse:
                    NestedVisitWithBranch(ndNot.Child, BranchingContext.OnTrue, _iterCurr.LabelBranch);
                    break;
 
                case BranchingContext.OnTrue:
                    NestedVisitWithBranch(ndNot.Child, BranchingContext.OnFalse, _iterCurr.LabelBranch);
                    break;
 
                default:
                    // Replace boolean argument on top of stack with its inverse
                    Debug.Assert(_iterCurr.CurrentBranchingContext == BranchingContext.None);
                    lblTemp = _helper.DefineLabel();
                    NestedVisitWithBranch(ndNot.Child, BranchingContext.OnTrue, lblTemp);
                    break;
            }
 
            if (_iterCurr.CurrentBranchingContext == BranchingContext.None)
            {
                // If condition evaluates to true, then jump to code that pushes false
                _helper.ConvBranchToBool(lblTemp, false);
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
            }
            else
            {
                _iterCurr.Storage = StorageDescriptor.None();
            }
 
            return ndNot;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Conditional.
        /// </summary>
        protected override QilNode VisitConditional(QilTernary ndCond)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(ndCond);
 
            if (info.ConstructMethod == XmlILConstructMethod.Writer)
            {
                Label lblFalse, lblDone;
 
                // Evaluate if test
                lblFalse = _helper.DefineLabel();
                NestedVisitWithBranch(ndCond.Left, BranchingContext.OnFalse, lblFalse);
 
                // Generate true branch code
                NestedVisit(ndCond.Center);
 
                // Generate false branch code.  If false branch is the empty list,
                if (ndCond.Right.NodeType == QilNodeType.Sequence && ndCond.Right.Count == 0)
                {
                    // Then generate simplified code that doesn't contain a false branch
                    _helper.MarkLabel(lblFalse);
                    NestedVisit(ndCond.Right);
                }
                else
                {
                    // Jump past false branch
                    lblDone = _helper.DefineLabel();
                    _helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
 
                    // Generate false branch code
                    _helper.MarkLabel(lblFalse);
                    NestedVisit(ndCond.Right);
 
                    _helper.MarkLabel(lblDone);
                }
 
                _iterCurr.Storage = StorageDescriptor.None();
            }
            else
            {
                IteratorDescriptor? iterInfoTrue;
                LocalBuilder? locBool = null, locCond = null;
                Label lblFalse, lblDone, lblNext;
                Type itemStorageType = GetItemStorageType(ndCond);
                Debug.Assert(info.ConstructMethod == XmlILConstructMethod.Iterator);
 
                // Evaluate conditional test -- save boolean result in boolResult
                Debug.Assert(ndCond.Left.XmlType!.TypeCode == XmlTypeCode.Boolean);
                lblFalse = _helper.DefineLabel();
 
                if (ndCond.XmlType!.IsSingleton)
                {
                    // if (!bool-expr) goto LabelFalse;
                    NestedVisitWithBranch(ndCond.Left, BranchingContext.OnFalse, lblFalse);
                }
                else
                {
                    // CondType itemCond;
                    // int boolResult = bool-expr;
                    locCond = _helper.DeclareLocal("$$$cond", itemStorageType);
                    locBool = _helper.DeclareLocal("$$$boolResult", typeof(bool));
                    NestedVisitEnsureLocal(ndCond.Left, locBool);
 
                    // if (!boolResult) goto LabelFalse;
                    _helper.Emit(OpCodes.Ldloc, locBool);
                    _helper.Emit(OpCodes.Brfalse, lblFalse);
                }
 
                // Generate code for true branch
                ConditionalBranch(ndCond.Center, itemStorageType, locCond);
                iterInfoTrue = _iterNested;
 
                // goto LabelDone;
                lblDone = _helper.DefineLabel();
                _helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
 
                // Generate code for false branch
                // LabelFalse:
                _helper.MarkLabel(lblFalse);
                ConditionalBranch(ndCond.Right, itemStorageType, locCond);
 
                // If conditional is not cardinality one, then need to iterate through all values
                if (!ndCond.XmlType.IsSingleton)
                {
                    Debug.Assert(!ndCond.Center.XmlType!.IsSingleton || !ndCond.Right.XmlType!.IsSingleton);
 
                    // IL's rules do not allow OpCodes.Br here
                    // goto LabelDone;
                    _helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblDone);
 
                    // LabelNext:
                    lblNext = _helper.DefineLabel();
                    _helper.MarkLabel(lblNext);
 
                    // if (boolResult) goto LabelNextTrue else goto LabelNextFalse;
                    _helper.Emit(OpCodes.Ldloc, locBool!);
                    _helper.Emit(OpCodes.Brtrue, iterInfoTrue!.GetLabelNext());
                    _helper.EmitUnconditionalBranch(OpCodes.Br, _iterNested!.GetLabelNext());
 
                    _iterCurr.SetIterator(lblNext, StorageDescriptor.Local(locCond!, itemStorageType, false));
                }
 
                // LabelDone:
                _helper.MarkLabel(lblDone);
            }
 
            return ndCond;
        }
 
        /// <summary>
        /// Generate code for one of the branches of QilNodeType.Conditional.
        /// </summary>
        private void ConditionalBranch(QilNode ndBranch, Type itemStorageType, LocalBuilder? locResult)
        {
            if (locResult == null)
            {
                Debug.Assert(ndBranch.XmlType!.IsSingleton, "Conditional must produce a singleton");
 
                // If in a branching context, then inherit branch target from parent context
                if (_iterCurr.IsBranching)
                {
                    Debug.Assert(itemStorageType == typeof(bool));
                    NestedVisitWithBranch(ndBranch, _iterCurr.CurrentBranchingContext, _iterCurr.LabelBranch);
                }
                else
                {
                    NestedVisitEnsureStack(ndBranch, itemStorageType, false);
                }
            }
            else
            {
                // Link nested iterator to parent conditional's iterator
                NestedVisit(ndBranch, _iterCurr.GetLabelNext());
                _iterCurr.EnsureItemStorageType(ndBranch.XmlType!, itemStorageType);
                _iterCurr.EnsureLocalNoCache(locResult);
            }
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Choice.
        /// </summary>
        protected override QilNode VisitChoice(QilChoice ndChoice)
        {
            QilNode ndBranches;
            Label[] switchLabels;
            Label lblOtherwise, lblDone;
            int regBranches, idx;
            Debug.Assert(XmlILConstructInfo.Read(ndChoice).PushToWriterFirst);
 
            // Evaluate the expression
            NestedVisit(ndChoice.Expression);
 
            // Generate switching code
            ndBranches = ndChoice.Branches;
            regBranches = ndBranches.Count - 1;
            switchLabels = new Label[regBranches];
            for (idx = 0; idx < regBranches; idx++)
                switchLabels[idx] = _helper.DefineLabel();
 
            lblOtherwise = _helper.DefineLabel();
            lblDone = _helper.DefineLabel();
 
            // switch (value)
            //   case 0: goto Label[0];
            //   ...
            //   case N-1: goto Label[N-1];
            //   default: goto LabelOtherwise;
            _helper.Emit(OpCodes.Switch, switchLabels);
            _helper.EmitUnconditionalBranch(OpCodes.Br, lblOtherwise);
 
            for (idx = 0; idx < regBranches; idx++)
            {
                // Label[i]:
                _helper.MarkLabel(switchLabels[idx]);
 
                // Generate regular branch code
                NestedVisit(ndBranches[idx]);
 
                // goto LabelDone
                _helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
            }
 
            // LabelOtherwise:
            _helper.MarkLabel(lblOtherwise);
 
            // Generate otherwise branch code
            NestedVisit(ndBranches[idx]);
 
            // LabelDone:
            _helper.MarkLabel(lblDone);
 
            _iterCurr.Storage = StorageDescriptor.None();
 
            return ndChoice;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Length.
        /// </summary>
        /// <remarks>
        /// int length = 0;
        /// foreach (item in expr)
        ///   length++;
        /// </remarks>
        protected override QilNode VisitLength(QilUnary ndSetLen)
        {
            Label lblOnEnd = _helper.DefineLabel();
            OptimizerPatterns patt = OptimizerPatterns.Read(ndSetLen);
 
            if (CachesResult(ndSetLen.Child))
            {
                NestedVisitEnsureStack(ndSetLen.Child);
                _helper.CallCacheCount(_iterNested!.Storage.ItemStorageType);
            }
            else
            {
                // length = 0;
                _helper.Emit(OpCodes.Ldc_I4_0);
 
                StartNestedIterator(lblOnEnd);
 
                // foreach (item in expr) {
                Visit(ndSetLen.Child);
 
                // Pop values of SetLength expression from the stack if necessary
                _iterCurr.EnsureNoCache();
                _iterCurr.DiscardStack();
 
                // length++;
                _helper.Emit(OpCodes.Ldc_I4_1);
                _helper.Emit(OpCodes.Add);
 
                if (patt.MatchesPattern(OptimizerPatternName.MaxPosition))
                {
                    // Short-circuit rest of loop if max position has been exceeded
                    _helper.Emit(OpCodes.Dup);
                    _helper.LoadInteger((int)patt.GetArgument(OptimizerPatternArgument.MaxPosition));
                    _helper.Emit(OpCodes.Bgt, lblOnEnd);
                }
 
                // }
                _iterCurr.LoopToEnd(lblOnEnd);
 
                EndNestedIterator(ndSetLen.Child);
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(int), false);
 
            return ndSetLen;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.Sequence.
        /// </summary>
        protected override QilNode VisitSequence(QilList ndSeq)
        {
            if (XmlILConstructInfo.Read(ndSeq).ConstructMethod == XmlILConstructMethod.Writer)
            {
                // Push each item in the list to output
                foreach (QilNode nd in ndSeq)
                    NestedVisit(nd);
            }
            else
            {
                // Empty sequence is special case
                if (ndSeq.Count == 0)
                    VisitEmpty(ndSeq);
                else
                    Sequence(ndSeq);
            }
 
            return ndSeq;
        }
 
        /// <summary>
        /// Generate code for the empty sequence.
        /// </summary>
        private void VisitEmpty(QilNode nd)
        {
            Debug.Assert(XmlILConstructInfo.Read(nd).PullFromIteratorFirst, "VisitEmpty should only be called if items are iterated");
 
            // IL's rules prevent OpCodes.Br here
            // Empty sequence
            _helper.EmitUnconditionalBranch(OpCodes.Brtrue, _iterCurr.GetLabelNext());
 
            // Push dummy value so that Location is not None and IL rules are met
            _helper.Emit(OpCodes.Ldnull);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), false);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Sequence, when sort-merging to retain document order is not necessary.
        /// </summary>
        private void Sequence(QilList ndSeq)
        {
            LocalBuilder locIdx, locList;
            Label lblStart, lblNext, lblOnEnd = default;
            Label[] arrSwitchLabels;
            int i;
            Type itemStorageType = GetItemStorageType(ndSeq);
            Debug.Assert(XmlILConstructInfo.Read(ndSeq).ConstructMethod == XmlILConstructMethod.Iterator, "This method should only be called if items in list are pulled from a code iterator.");
 
            // Singleton list is a special case if in addition to the singleton there are warnings or errors which should be executed
            if (ndSeq.XmlType.IsSingleton)
            {
                foreach (QilNode nd in ndSeq)
                {
                    // Generate nested iterator's code
                    if (nd.XmlType!.IsSingleton)
                    {
                        NestedVisitEnsureStack(nd);
                    }
                    else
                    {
                        lblOnEnd = _helper.DefineLabel();
                        NestedVisit(nd, lblOnEnd);
                        _iterCurr.DiscardStack();
                        _helper.MarkLabel(lblOnEnd);
                    }
                }
                _iterCurr.Storage = StorageDescriptor.Stack(itemStorageType, false);
            }
            else
            {
                // Type itemList;
                // int idxList;
                locList = _helper.DeclareLocal("$$$itemList", itemStorageType);
                locIdx = _helper.DeclareLocal("$$$idxList", typeof(int));
 
                arrSwitchLabels = new Label[ndSeq.Count];
                lblStart = _helper.DefineLabel();
 
                for (i = 0; i < ndSeq.Count; i++)
                {
                    // LabelOnEnd[i - 1]:
                    // When previous nested iterator is exhausted, it should jump to this (the next) iterator
                    if (i != 0)
                        _helper.MarkLabel(lblOnEnd);
 
                    // Create new LabelOnEnd for all but the last iterator, which jumps back to parent iterator when exhausted
                    if (i == ndSeq.Count - 1)
                        lblOnEnd = _iterCurr.GetLabelNext();
                    else
                        lblOnEnd = _helper.DefineLabel();
 
                    // idxList = [i];
                    _helper.LoadInteger(i);
                    _helper.Emit(OpCodes.Stloc, locIdx);
 
                    // Generate nested iterator's code
                    NestedVisit(ndSeq[i], lblOnEnd);
 
                    // Result of list should be saved to a common type and location
                    _iterCurr.EnsureItemStorageType(ndSeq[i].XmlType!, itemStorageType);
                    _iterCurr.EnsureLocalNoCache(locList);
 
                    // Switch statement will jump to nested iterator's LabelNext
                    arrSwitchLabels[i] = _iterNested!.GetLabelNext();
 
                    // IL's rules prevent OpCodes.Br here
                    // goto LabelStart;
                    _helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblStart);
                }
 
                // LabelNext:
                lblNext = _helper.DefineLabel();
                _helper.MarkLabel(lblNext);
 
                // switch (idxList)
                //   case 0: goto LabelNext1;
                //   ...
                //   case N-1: goto LabelNext[N];
                _helper.Emit(OpCodes.Ldloc, locIdx);
                _helper.Emit(OpCodes.Switch, arrSwitchLabels);
 
                // LabelStart:
                _helper.MarkLabel(lblStart);
 
                _iterCurr.SetIterator(lblNext, StorageDescriptor.Local(locList, itemStorageType, false));
            }
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Union.
        /// </summary>
        protected override QilNode VisitUnion(QilBinary ndUnion)
        {
            return CreateSetIterator(ndUnion, "$$$iterUnion", typeof(UnionIterator), XmlILMethods.UnionCreate, XmlILMethods.UnionNext, XmlILMethods.UnionCurrent);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Intersection.
        /// </summary>
        protected override QilNode VisitIntersection(QilBinary ndInter)
        {
            return CreateSetIterator(ndInter, "$$$iterInter", typeof(IntersectIterator), XmlILMethods.InterCreate, XmlILMethods.InterNext, XmlILMethods.InterCurrent);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Difference.
        /// </summary>
        protected override QilNode VisitDifference(QilBinary ndDiff)
        {
            return CreateSetIterator(ndDiff, "$$$iterDiff", typeof(DifferenceIterator), XmlILMethods.DiffCreate, XmlILMethods.DiffNext, XmlILMethods.DiffCurrent);
        }
 
        /// <summary>
        /// Generate code to combine nodes from two nested iterators using Union, Intersection, or Difference semantics.
        /// </summary>
        private QilBinary CreateSetIterator(QilBinary ndSet, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext, MethodInfo methCurrent)
        {
            LocalBuilder locIter, locNav;
            Label lblNext, lblCall, lblNextLeft, lblNextRight, lblInitRight;
 
            // SetIterator iterSet;
            // XPathNavigator navSet;
            locIter = _helper.DeclareLocal(iterName, iterType);
            locNav = _helper.DeclareLocal("$$$navSet", typeof(XPathNavigator));
 
            // iterSet.Create(runtime);
            _helper.Emit(OpCodes.Ldloca, locIter);
            _helper.LoadQueryRuntime();
            _helper.Call(methCreate);
 
            // Define labels that will be used
            lblNext = _helper.DefineLabel();
            lblCall = _helper.DefineLabel();
            lblInitRight = _helper.DefineLabel();
 
            // Generate left nested iterator.  When it is empty, it will branch to lblNext.
            // goto LabelCall;
            NestedVisit(ndSet.Left, lblNext);
            lblNextLeft = _iterNested!.GetLabelNext();
            _iterCurr.EnsureLocal(locNav);
            _helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblCall);
 
            // Generate right nested iterator.  When it is empty, it will branch to lblNext.
            // LabelInitRight:
            // goto LabelCall;
            _helper.MarkLabel(lblInitRight);
            NestedVisit(ndSet.Right, lblNext);
            lblNextRight = _iterNested.GetLabelNext();
            _iterCurr.EnsureLocal(locNav);
            _helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblCall);
 
            // LabelNext:
            _helper.MarkLabel(lblNext);
            _helper.Emit(OpCodes.Ldnull);
            _helper.Emit(OpCodes.Stloc, locNav);
 
            // LabelCall:
            // switch (iterSet.MoveNext(nestedNested)) {
            //      case SetIteratorResult.NoMoreNodes: goto LabelNextCtxt;
            //      case SetIteratorResult.InitRightIterator: goto LabelInitRight;
            //      case SetIteratorResult.NeedLeftNode: goto LabelNextLeft;
            //      case SetIteratorResult.NeedRightNode: goto LabelNextRight;
            // }
            _helper.MarkLabel(lblCall);
            _helper.Emit(OpCodes.Ldloca, locIter);
            _helper.Emit(OpCodes.Ldloc, locNav);
            _helper.Call(methNext);
 
            // If this iterator always returns a single node, then NoMoreNodes will never be returned
            // Don't expose Next label if this iterator always returns a single node
            if (ndSet.XmlType!.IsSingleton)
            {
                _helper.Emit(OpCodes.Switch, new Label[] { lblInitRight, lblNextLeft, lblNextRight });
                _iterCurr.Storage = StorageDescriptor.Current(locIter, methCurrent, typeof(XPathNavigator));
            }
            else
            {
                _helper.Emit(OpCodes.Switch, new Label[] { _iterCurr.GetLabelNext(), lblInitRight, lblNextLeft, lblNextRight });
                _iterCurr.SetIterator(lblNext, StorageDescriptor.Current(locIter, methCurrent, typeof(XPathNavigator)));
            }
 
            return ndSet;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Average.
        /// </summary>
        protected override QilNode VisitAverage(QilUnary ndAvg)
        {
            XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndAvg)];
            return CreateAggregator(ndAvg, "$$$aggAvg", meths, meths.AggAvg!, meths.AggAvgResult!);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Sum.
        /// </summary>
        protected override QilNode VisitSum(QilUnary ndSum)
        {
            XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndSum)];
            return CreateAggregator(ndSum, "$$$aggSum", meths, meths.AggSum!, meths.AggSumResult!);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Minimum.
        /// </summary>
        protected override QilNode VisitMinimum(QilUnary ndMin)
        {
            XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndMin)];
            return CreateAggregator(ndMin, "$$$aggMin", meths, meths.AggMin!, meths.AggMinResult!);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Maximum.
        /// </summary>
        protected override QilNode VisitMaximum(QilUnary ndMax)
        {
            XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndMax)];
            return CreateAggregator(ndMax, "$$$aggMax", meths, meths.AggMax!, meths.AggMaxResult!);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Sum, QilNodeType.Average, QilNodeType.Minimum, and QilNodeType.Maximum.
        /// </summary>
        private QilUnary CreateAggregator(QilUnary ndAgg, string aggName, XmlILStorageMethods methods, MethodInfo methAgg, MethodInfo methResult)
        {
            Label lblOnEnd = _helper.DefineLabel();
            Type typAgg = methAgg.DeclaringType!;
            LocalBuilder locAgg;
 
            // Aggregate agg;
            // agg.Create();
            locAgg = _helper.DeclareLocal(aggName, typAgg);
            _helper.Emit(OpCodes.Ldloca, locAgg);
            _helper.Call(methods.AggCreate!);
 
            // foreach (num in expr) {
            StartNestedIterator(lblOnEnd);
            _helper.Emit(OpCodes.Ldloca, locAgg);
            Visit(ndAgg.Child);
 
            //   agg.Aggregate(num);
            _iterCurr.EnsureStackNoCache();
            _iterCurr.EnsureItemStorageType(ndAgg.XmlType!, GetItemStorageType(ndAgg));
            _helper.Call(methAgg);
            _helper.Emit(OpCodes.Ldloca, locAgg);
 
            // }
            _iterCurr.LoopToEnd(lblOnEnd);
 
            // End nested iterator
            EndNestedIterator(ndAgg.Child);
 
            // If aggregate might be empty sequence, then generate code to handle this possibility
            if (ndAgg.XmlType!.MaybeEmpty)
            {
                // if (agg.IsEmpty) goto LabelNextCtxt;
                _helper.Call(methods.AggIsEmpty!);
                _helper.Emit(OpCodes.Brtrue, _iterCurr.GetLabelNext());
                _helper.Emit(OpCodes.Ldloca, locAgg);
            }
 
            // result = agg.Result;
            _helper.Call(methResult);
            _iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndAgg), false);
 
            return ndAgg;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Negate.
        /// </summary>
        protected override QilNode VisitNegate(QilUnary ndNeg)
        {
            NestedVisitEnsureStack(ndNeg.Child);
            _helper.CallArithmeticOp(QilNodeType.Negate, ndNeg.XmlType!.TypeCode);
            _iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndNeg), false);
            return ndNeg;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Add.
        /// </summary>
        protected override QilNode VisitAdd(QilBinary ndPlus)
        {
            return ArithmeticOp(ndPlus);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Subtract.
        /// </summary>
        protected override QilNode VisitSubtract(QilBinary ndMinus)
        {
            return ArithmeticOp(ndMinus);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Multiply.
        /// </summary>
        protected override QilNode VisitMultiply(QilBinary ndMul)
        {
            return ArithmeticOp(ndMul);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Divide.
        /// </summary>
        protected override QilNode VisitDivide(QilBinary ndDiv)
        {
            return ArithmeticOp(ndDiv);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Modulo.
        /// </summary>
        protected override QilNode VisitModulo(QilBinary ndMod)
        {
            return ArithmeticOp(ndMod);
        }
 
        /// <summary>
        /// Generate code for two-argument arithmetic operations.
        /// </summary>
        private QilBinary ArithmeticOp(QilBinary ndOp)
        {
            NestedVisitEnsureStack(ndOp.Left, ndOp.Right);
            _helper.CallArithmeticOp(ndOp.NodeType, ndOp.XmlType!.TypeCode);
            _iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndOp), false);
            return ndOp;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.StrLength.
        /// </summary>
        protected override QilNode VisitStrLength(QilUnary ndLen)
        {
            NestedVisitEnsureStack(ndLen.Child);
            _helper.Call(XmlILMethods.StrLen);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(int), false);
            return ndLen;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.StrConcat.
        /// </summary>
        protected override QilNode VisitStrConcat(QilStrConcat ndStrConcat)
        {
            LocalBuilder locStringConcat;
            bool fasterConcat;
            QilNode? delimiter;
            QilNode listStrings;
            Debug.Assert(!ndStrConcat.Values.XmlType!.IsSingleton, "Optimizer should have folded StrConcat of a singleton value");
 
            // Get delimiter (assuming it's not the empty string)
            delimiter = ndStrConcat.Delimiter;
            if (delimiter.NodeType == QilNodeType.LiteralString && ((string)(QilLiteral)delimiter).Length == 0)
            {
                delimiter = null;
            }
 
            listStrings = ndStrConcat.Values;
            if (listStrings.NodeType == QilNodeType.Sequence && listStrings.Count < 5)
            {
                // Faster concat possible only if cardinality can be guaranteed at compile-time and there's no delimiter
                fasterConcat = true;
                foreach (QilNode ndStr in listStrings)
                {
                    if (!ndStr.XmlType!.IsSingleton)
                        fasterConcat = false;
                }
            }
            else
            {
                // If more than 4 strings, array will need to be built
                fasterConcat = false;
            }
 
            if (fasterConcat)
            {
                foreach (QilNode ndStr in listStrings)
                    NestedVisitEnsureStack(ndStr);
 
                _helper.CallConcatStrings(listStrings.Count);
            }
            else
            {
                // Create StringConcat helper internal class
                locStringConcat = _helper.DeclareLocal("$$$strcat", typeof(StringConcat));
                _helper.Emit(OpCodes.Ldloca, locStringConcat);
                _helper.Call(XmlILMethods.StrCatClear);
 
                // Set delimiter, if it's not empty string
                if (delimiter != null)
                {
                    _helper.Emit(OpCodes.Ldloca, locStringConcat);
                    NestedVisitEnsureStack(delimiter);
                    _helper.Call(XmlILMethods.StrCatDelim);
                }
 
                _helper.Emit(OpCodes.Ldloca, locStringConcat);
 
                if (listStrings.NodeType == QilNodeType.Sequence)
                {
                    foreach (QilNode ndStr in listStrings)
                        GenerateConcat(ndStr, locStringConcat);
                }
                else
                {
                    GenerateConcat(listStrings, locStringConcat);
                }
 
                // Push resulting string onto stack
                _helper.Call(XmlILMethods.StrCatResult);
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
 
            return ndStrConcat;
        }
 
        /// <summary>
        /// Generate code to concatenate string values returned by expression "ndStr" using the StringConcat helper class.
        /// </summary>
        private void GenerateConcat(QilNode ndStr, LocalBuilder locStringConcat)
        {
            Label lblOnEnd;
 
            // str = each string;
            lblOnEnd = _helper.DefineLabel();
            StartNestedIterator(lblOnEnd);
            Visit(ndStr);
 
            // strcat.Concat(str);
            _iterCurr.EnsureStackNoCache();
            _iterCurr.EnsureItemStorageType(ndStr.XmlType!, typeof(string));
            _helper.Call(XmlILMethods.StrCatCat);
            _helper.Emit(OpCodes.Ldloca, locStringConcat);
 
            // Get next string
            // goto LabelNext;
            // LabelOnEnd:
            _iterCurr.LoopToEnd(lblOnEnd);
 
            // End nested iterator
            EndNestedIterator(ndStr);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.StrParseQName.
        /// </summary>
        protected override QilNode VisitStrParseQName(QilBinary ndParsedTagName)
        {
            VisitStrParseQName(ndParsedTagName, false);
            return ndParsedTagName;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.StrParseQName.
        /// </summary>
        private void VisitStrParseQName(QilBinary ndParsedTagName, bool preservePrefix)
        {
            // If QName prefix should be preserved, then don't create an XmlQualifiedName, which discards the prefix
            if (!preservePrefix)
                _helper.LoadQueryRuntime();
 
            // Push (possibly computed) tag name onto the stack
            NestedVisitEnsureStack(ndParsedTagName.Left);
 
            // If type of second parameter is string,
            if (ndParsedTagName.Right.XmlType!.TypeCode == XmlTypeCode.String)
            {
                // Then push (possibly computed) namespace onto the stack
                Debug.Assert(ndParsedTagName.Right.XmlType.IsSingleton);
                NestedVisitEnsureStack(ndParsedTagName.Right);
 
                if (!preservePrefix)
                    _helper.CallParseTagName(GenerateNameType.TagNameAndNamespace);
            }
            else
            {
                // Else push index of set of prefix mappings to use in resolving the prefix
                if (ndParsedTagName.Right.NodeType == QilNodeType.Sequence)
                    _helper.LoadInteger(_helper.StaticData.DeclarePrefixMappings(ndParsedTagName.Right));
                else
                    _helper.LoadInteger(_helper.StaticData.DeclarePrefixMappings(new QilNode[] { ndParsedTagName.Right }));
 
                // If QName prefix should be preserved, then don't create an XmlQualifiedName, which discards the prefix
                if (!preservePrefix)
                    _helper.CallParseTagName(GenerateNameType.TagNameAndMappings);
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XmlQualifiedName), false);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Ne.
        /// </summary>
        protected override QilNode VisitNe(QilBinary ndNe)
        {
            Compare(ndNe);
            return ndNe;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Eq.
        /// </summary>
        protected override QilNode VisitEq(QilBinary ndEq)
        {
            Compare(ndEq);
            return ndEq;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Gt.
        /// </summary>
        protected override QilNode VisitGt(QilBinary ndGt)
        {
            Compare(ndGt);
            return ndGt;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Ne.
        /// </summary>
        protected override QilNode VisitGe(QilBinary ndGe)
        {
            Compare(ndGe);
            return ndGe;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Lt.
        /// </summary>
        protected override QilNode VisitLt(QilBinary ndLt)
        {
            Compare(ndLt);
            return ndLt;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Le.
        /// </summary>
        protected override QilNode VisitLe(QilBinary ndLe)
        {
            Compare(ndLe);
            return ndLe;
        }
 
        /// <summary>
        /// Generate code for comparison operations.
        /// </summary>
        private void Compare(QilBinary ndComp)
        {
            QilNodeType relOp = ndComp.NodeType;
            XmlTypeCode code;
            Debug.Assert(ndComp.Left.XmlType!.IsAtomicValue && ndComp.Right.XmlType!.IsAtomicValue, "Operands to compare must be atomic values.");
            Debug.Assert(ndComp.Left.XmlType.IsSingleton && ndComp.Right.XmlType.IsSingleton, "Operands to compare must be cardinality one.");
            Debug.Assert(ndComp.Left.XmlType == ndComp.Right.XmlType, "Operands to compare may not be heterogenous.");
 
            if (relOp == QilNodeType.Eq || relOp == QilNodeType.Ne)
            {
                // Generate better code for certain special cases
                if (TryZeroCompare(relOp, ndComp.Left, ndComp.Right))
                    return;
 
                if (TryZeroCompare(relOp, ndComp.Right, ndComp.Left))
                    return;
 
                if (TryNameCompare(relOp, ndComp.Left, ndComp.Right))
                    return;
 
                if (TryNameCompare(relOp, ndComp.Right, ndComp.Left))
                    return;
            }
 
            // Push two operands onto the stack
            NestedVisitEnsureStack(ndComp.Left, ndComp.Right);
 
            // Perform comparison
            code = ndComp.Left.XmlType.TypeCode;
            switch (code)
            {
                case XmlTypeCode.String:
                case XmlTypeCode.Decimal:
                case XmlTypeCode.QName:
                    if (relOp == QilNodeType.Eq || relOp == QilNodeType.Ne)
                    {
                        _helper.CallCompareEquals(code);
 
                        // If relOp is Eq, then branch to true label or push "true" if Equals function returns true (non-zero)
                        // If relOp is Ne, then branch to true label or push "true" if Equals function returns false (zero)
                        ZeroCompare((relOp == QilNodeType.Eq) ? QilNodeType.Ne : QilNodeType.Eq, true);
                    }
                    else
                    {
                        Debug.Assert(code != XmlTypeCode.QName, $"QName values do not support the {relOp} operation");
 
                        // Push -1, 0, or 1 onto the stack depending upon the result of the comparison
                        _helper.CallCompare(code);
 
                        // Compare result to 0 (e.g. Ge is >= 0)
                        _helper.Emit(OpCodes.Ldc_I4_0);
                        ClrCompare(relOp, code);
                    }
                    break;
 
                case XmlTypeCode.Integer:
                case XmlTypeCode.Int:
                case XmlTypeCode.Boolean:
                case XmlTypeCode.Double:
                    ClrCompare(relOp, code);
                    break;
 
                default:
                    Debug.Fail($"Comparisons for datatype {code} are invalid.");
                    break;
            }
        }
 
        /// <summary>
        /// Generate code for QilNodeType.VisitIs.
        /// </summary>
        protected override QilNode VisitIs(QilBinary ndIs)
        {
            // Generate code to push arguments onto stack
            NestedVisitEnsureStack(ndIs.Left, ndIs.Right);
            _helper.Call(XmlILMethods.NavSamePos);
 
            // navThis.IsSamePosition(navThat);
            ZeroCompare(QilNodeType.Ne, true);
            return ndIs;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.VisitBefore.
        /// </summary>
        protected override QilNode VisitBefore(QilBinary ndBefore)
        {
            ComparePosition(ndBefore);
            return ndBefore;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.VisitAfter.
        /// </summary>
        protected override QilNode VisitAfter(QilBinary ndAfter)
        {
            ComparePosition(ndAfter);
            return ndAfter;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.VisitBefore and QilNodeType.VisitAfter.
        /// </summary>
        private void ComparePosition(QilBinary ndComp)
        {
            // Generate code to push arguments onto stack
            _helper.LoadQueryRuntime();
            NestedVisitEnsureStack(ndComp.Left, ndComp.Right);
            _helper.Call(XmlILMethods.CompPos);
 
            // XmlQueryRuntime.ComparePosition(navThis, navThat) < 0;
            _helper.LoadInteger(0);
            ClrCompare(ndComp.NodeType == QilNodeType.Before ? QilNodeType.Lt : QilNodeType.Gt, XmlTypeCode.String);
        }
 
        /// <summary>
        /// Generate code for a QilNodeType.For.
        /// </summary>
        protected override QilNode VisitFor(QilIterator ndFor)
        {
            IteratorDescriptor iterInfo;
 
            // Reference saved location
            iterInfo = XmlILAnnotation.Write(ndFor).CachedIteratorDescriptor!;
            _iterCurr.Storage = iterInfo.Storage;
 
            // If the iterator is a reference to a global variable or parameter,
            if (_iterCurr.Storage.Location == ItemLocation.Global)
            {
                // Then compute global value and push it onto the stack
                _iterCurr.EnsureStack();
            }
 
            return ndFor;
        }
 
        /// <summary>
        /// Generate code for a QilNodeType.Let.
        /// </summary>
        protected override QilNode VisitLet(QilIterator ndLet)
        {
            // Same as For
            return VisitFor(ndLet);
        }
 
        /// <summary>
        /// Generate code for a QilNodeType.Parameter.
        /// </summary>
        protected override QilNode VisitParameter(QilParameter ndParameter)
        {
            // Same as For
            return VisitFor(ndParameter);
        }
 
        /// <summary>
        /// Generate code for a QilNodeType.Loop.
        /// </summary>
        protected override QilNode VisitLoop(QilLoop ndLoop)
        {
            bool hasOnEnd;
            Label lblOnEnd;
 
            StartWriterLoop(ndLoop, out hasOnEnd, out lblOnEnd);
 
            StartBinding(ndLoop.Variable);
 
            // Unnest loop body as part of the current iterator
            Visit(ndLoop.Body);
 
            EndBinding(ndLoop.Variable);
 
            EndWriterLoop(ndLoop, hasOnEnd, lblOnEnd);
 
            return ndLoop;
        }
 
        /// <summary>
        /// Generate code for a QilNodeType.Filter.
        /// </summary>
        protected override QilNode VisitFilter(QilLoop ndFilter)
        {
            // Handle any special-case patterns that are rooted at Filter
            if (HandleFilterPatterns(ndFilter))
                return ndFilter;
 
            StartBinding(ndFilter.Variable);
 
            // Result of filter is the sequence bound to the iterator
            _iterCurr.SetIterator(_iterNested!);
 
            // If filter is false, skip the current item
            StartNestedIterator();
            _iterCurr.SetBranching(BranchingContext.OnFalse, _iterCurr.ParentIterator!.GetLabelNext());
            Visit(ndFilter.Body);
            EndNestedIterator(ndFilter.Body);
 
            EndBinding(ndFilter.Variable);
 
            return ndFilter;
        }
 
        /// <summary>
        /// There are a number of path patterns that can be rooted at Filter nodes.  Determine whether one of these patterns
        /// has been previously matched on "ndFilter".  If so, generate code for the pattern and return true.  Otherwise, just
        /// return false.
        /// </summary>
        private bool HandleFilterPatterns(QilLoop ndFilter)
        {
            OptimizerPatterns patt = OptimizerPatterns.Read(ndFilter);
            LocalBuilder locIter;
            XmlNodeKindFlags kinds;
            QilName? name;
            QilNode input, step;
            bool isFilterElements;
 
            // Handle FilterElements and FilterContentKind patterns
            isFilterElements = patt.MatchesPattern(OptimizerPatternName.FilterElements);
            if (isFilterElements || patt.MatchesPattern(OptimizerPatternName.FilterContentKind))
            {
                if (isFilterElements)
                {
                    // FilterElements pattern, so Kind = Element and Name = Argument
                    kinds = XmlNodeKindFlags.Element;
                    name = (QilName)patt.GetArgument(OptimizerPatternArgument.ElementQName);
                }
                else
                {
                    // FilterKindTest pattern, so Kind = Argument and Name = null
                    kinds = ((XmlQueryType)patt.GetArgument(OptimizerPatternArgument.KindTestType)).NodeKinds;
                    name = null;
                }
 
                step = (QilNode)patt.GetArgument(OptimizerPatternArgument.StepNode);
                input = (QilNode)patt.GetArgument(OptimizerPatternArgument.StepInput);
                switch (step.NodeType)
                {
                    case QilNodeType.Content:
                        if (isFilterElements)
                        {
                            // Iterator iter;
                            locIter = _helper.DeclareLocal("$$$iterElemContent", typeof(ElementContentIterator));
 
                            // iter.Create(navCtxt, locName, ns);
                            _helper.Emit(OpCodes.Ldloca, locIter);
                            NestedVisitEnsureStack(input);
                            _helper.CallGetAtomizedName(_helper.StaticData.DeclareName(name!.LocalName));
                            _helper.CallGetAtomizedName(_helper.StaticData.DeclareName(name.NamespaceUri));
                            _helper.Call(XmlILMethods.ElemContentCreate);
 
                            GenerateSimpleIterator(typeof(XPathNavigator), locIter, XmlILMethods.ElemContentNext, XmlILMethods.ElemContentCurrent);
                        }
                        else
                        {
                            if (kinds == XmlNodeKindFlags.Content)
                            {
                                CreateSimpleIterator(input, "$$$iterContent", typeof(ContentIterator), XmlILMethods.ContentCreate, XmlILMethods.ContentNext, XmlILMethods.ContentCurrent);
                            }
                            else
                            {
                                // Iterator iter;
                                locIter = _helper.DeclareLocal("$$$iterContent", typeof(NodeKindContentIterator));
 
                                // iter.Create(navCtxt, nodeType);
                                _helper.Emit(OpCodes.Ldloca, locIter);
                                NestedVisitEnsureStack(input);
                                _helper.LoadInteger((int)QilXmlToXPathNodeType(kinds));
                                _helper.Call(XmlILMethods.KindContentCreate);
 
                                GenerateSimpleIterator(typeof(XPathNavigator), locIter, XmlILMethods.KindContentNext, XmlILMethods.KindContentCurrent);
                            }
                        }
                        return true;
 
                    case QilNodeType.Parent:
                        CreateFilteredIterator(input, "$$$iterPar", typeof(ParentIterator), XmlILMethods.ParentCreate, XmlILMethods.ParentNext, XmlILMethods.ParentCurrent,
                                               kinds, name, TriState.Unknown, null);
                        return true;
 
                    case QilNodeType.Ancestor:
                    case QilNodeType.AncestorOrSelf:
                        CreateFilteredIterator(input, "$$$iterAnc", typeof(AncestorIterator), XmlILMethods.AncCreate, XmlILMethods.AncNext, XmlILMethods.AncCurrent,
                                               kinds, name, (step.NodeType == QilNodeType.Ancestor) ? TriState.False : TriState.True, null);
                        return true;
 
                    case QilNodeType.Descendant:
                    case QilNodeType.DescendantOrSelf:
                        CreateFilteredIterator(input, "$$$iterDesc", typeof(DescendantIterator), XmlILMethods.DescCreate, XmlILMethods.DescNext, XmlILMethods.DescCurrent,
                                               kinds, name, (step.NodeType == QilNodeType.Descendant) ? TriState.False : TriState.True, null);
                        return true;
 
                    case QilNodeType.Preceding:
                        CreateFilteredIterator(input, "$$$iterPrec", typeof(PrecedingIterator), XmlILMethods.PrecCreate, XmlILMethods.PrecNext, XmlILMethods.PrecCurrent,
                                               kinds, name, TriState.Unknown, null);
                        return true;
 
                    case QilNodeType.FollowingSibling:
                        CreateFilteredIterator(input, "$$$iterFollSib", typeof(FollowingSiblingIterator), XmlILMethods.FollSibCreate, XmlILMethods.FollSibNext, XmlILMethods.FollSibCurrent,
                                               kinds, name, TriState.Unknown, null);
                        return true;
 
                    case QilNodeType.PrecedingSibling:
                        CreateFilteredIterator(input, "$$$iterPreSib", typeof(PrecedingSiblingIterator), XmlILMethods.PreSibCreate, XmlILMethods.PreSibNext, XmlILMethods.PreSibCurrent,
                                               kinds, name, TriState.Unknown, null);
                        return true;
 
                    case QilNodeType.NodeRange:
                        CreateFilteredIterator(input, "$$$iterRange", typeof(NodeRangeIterator), XmlILMethods.NodeRangeCreate, XmlILMethods.NodeRangeNext, XmlILMethods.NodeRangeCurrent,
                                               kinds, name, TriState.Unknown, ((QilBinary)step).Right);
                        return true;
 
                    case QilNodeType.XPathFollowing:
                        CreateFilteredIterator(input, "$$$iterFoll", typeof(XPathFollowingIterator), XmlILMethods.XPFollCreate, XmlILMethods.XPFollNext, XmlILMethods.XPFollCurrent,
                                               kinds, name, TriState.Unknown, null);
                        return true;
 
                    case QilNodeType.XPathPreceding:
                        CreateFilteredIterator(input, "$$$iterPrec", typeof(XPathPrecedingIterator), XmlILMethods.XPPrecCreate, XmlILMethods.XPPrecNext, XmlILMethods.XPPrecCurrent,
                                               kinds, name, TriState.Unknown, null);
                        return true;
 
                    default:
                        Debug.Fail($"Pattern {step.NodeType} should have been handled.");
                        break;
                }
            }
            else if (patt.MatchesPattern(OptimizerPatternName.FilterAttributeKind))
            {
                // Handle FilterAttributeKind pattern
                input = (QilNode)patt.GetArgument(OptimizerPatternArgument.StepInput);
                CreateSimpleIterator(input, "$$$iterAttr", typeof(AttributeIterator), XmlILMethods.AttrCreate, XmlILMethods.AttrNext, XmlILMethods.AttrCurrent);
                return true;
            }
            else if (patt.MatchesPattern(OptimizerPatternName.EqualityIndex))
            {
                // Handle EqualityIndex pattern
                Label lblOnEnd = _helper.DefineLabel();
                Label lblLookup = _helper.DefineLabel();
                QilIterator nodes = (QilIterator)patt.GetArgument(OptimizerPatternArgument.IndexedNodes);
                QilNode keys = (QilNode)patt.GetArgument(OptimizerPatternArgument.KeyExpression);
 
                // XmlILIndex index;
                // if (runtime.FindIndex(navCtxt, indexId, out index)) goto LabelLookup;
                LocalBuilder locIndex = _helper.DeclareLocal("$$$index", typeof(XmlILIndex));
                _helper.LoadQueryRuntime();
                _helper.Emit(OpCodes.Ldarg_1);
                _helper.LoadInteger(_indexId);
                _helper.Emit(OpCodes.Ldloca, locIndex);
                _helper.Call(XmlILMethods.FindIndex);
                _helper.Emit(OpCodes.Brtrue, lblLookup);
 
                // runtime.AddNewIndex(navCtxt, indexId, [build index]);
                _helper.LoadQueryRuntime();
                _helper.Emit(OpCodes.Ldarg_1);
                _helper.LoadInteger(_indexId);
                _helper.Emit(OpCodes.Ldloc, locIndex);
 
                // Generate code to iterate over the nodes which are being indexed ($iterNodes in the pattern)
                StartNestedIterator(lblOnEnd);
                StartBinding(nodes);
 
                // Generate code to iterate over the keys for each node ($bindingKeys in the pattern)
                Visit(keys);
 
                // index.Add(key, value);
                _iterCurr.EnsureStackNoCache();
                VisitFor(nodes);
                _iterCurr.EnsureStackNoCache();
                _iterCurr.EnsureItemStorageType(nodes.XmlType!, typeof(XPathNavigator));
                _helper.Call(XmlILMethods.IndexAdd);
                _helper.Emit(OpCodes.Ldloc, locIndex);
 
                // LabelOnEnd:
                _iterCurr.LoopToEnd(lblOnEnd);
                EndBinding(nodes);
                EndNestedIterator(nodes);
 
                // runtime.AddNewIndex(navCtxt, indexId, [build index]);
                _helper.Call(XmlILMethods.AddNewIndex);
 
                // LabelLookup:
                // results = index.Lookup(keyValue);
                _helper.MarkLabel(lblLookup);
                _helper.Emit(OpCodes.Ldloc, locIndex);
                _helper.Emit(OpCodes.Ldarg_2);
                _helper.Call(XmlILMethods.IndexLookup);
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), true);
 
                _indexId++;
 
                return true;
            }
 
            return false;
        }
 
        /// <summary>
        /// Generate code for a Let, For, or Parameter iterator.  Bind iterated value to a variable.
        /// </summary>
        private void StartBinding(QilIterator ndIter)
        {
            OptimizerPatterns patt = OptimizerPatterns.Read(ndIter);
            Debug.Assert(ndIter != null);
 
            // DebugInfo: Sequence point just before generating code for the bound expression
            if (_qil.IsDebug && ndIter.SourceLine != null)
                _helper.DebugSequencePoint(ndIter.SourceLine);
 
            // Treat cardinality one Let iterators as if they were For iterators (no nesting necessary)
            if (ndIter.NodeType == QilNodeType.For || ndIter.XmlType!.IsSingleton)
            {
                StartForBinding(ndIter, patt);
            }
            else
            {
                Debug.Assert(ndIter.NodeType == QilNodeType.Let || ndIter.NodeType == QilNodeType.Parameter);
                Debug.Assert(!patt.MatchesPattern(OptimizerPatternName.IsPositional));
 
                // Bind Let values (nested iterator) to variable
                StartLetBinding(ndIter);
            }
 
            // Attach IteratorDescriptor to the iterator
            XmlILAnnotation.Write(ndIter).CachedIteratorDescriptor = _iterNested;
        }
 
        /// <summary>
        /// Bind values produced by the "ndFor" expression to a non-stack location that can later
        /// be referenced.
        /// </summary>
        private void StartForBinding(QilIterator ndFor, OptimizerPatterns patt)
        {
            LocalBuilder? locPos = null;
            Debug.Assert(ndFor.XmlType!.IsSingleton);
 
            // For expression iterator will be unnested as part of parent iterator
            if (_iterCurr.HasLabelNext)
                StartNestedIterator(_iterCurr.GetLabelNext());
            else
                StartNestedIterator();
 
            if (patt.MatchesPattern(OptimizerPatternName.IsPositional))
            {
                // Need to track loop index so initialize it to 0 before starting loop
                locPos = _helper.DeclareLocal("$$$pos", typeof(int));
                _helper.Emit(OpCodes.Ldc_I4_0);
                _helper.Emit(OpCodes.Stloc, locPos);
            }
 
            // Allow base internal class to dispatch based on QilExpression node type
            Visit(ndFor.Binding!);
 
            // DebugInfo: Open variable scope
            // DebugInfo: Ensure that for variable is stored in a local and tag it with the user-defined name
            if (_qil.IsDebug && ndFor.DebugName != null)
            {
                _helper.DebugStartScope();
 
                // Ensure that values are stored in a local variable with a user-defined name
                _iterCurr.EnsureLocalNoCache("$$$for");
            }
            else
            {
                // Ensure that values are not stored on the stack
                _iterCurr.EnsureNoStackNoCache("$$$for");
            }
 
            if (patt.MatchesPattern(OptimizerPatternName.IsPositional))
            {
                // Increment position
                _helper.Emit(OpCodes.Ldloc, locPos!);
                _helper.Emit(OpCodes.Ldc_I4_1);
                _helper.Emit(OpCodes.Add);
                _helper.Emit(OpCodes.Stloc, locPos!);
 
                if (patt.MatchesPattern(OptimizerPatternName.MaxPosition))
                {
                    // Short-circuit rest of loop if max position has already been reached
                    _helper.Emit(OpCodes.Ldloc, locPos!);
                    _helper.LoadInteger((int)patt.GetArgument(OptimizerPatternArgument.MaxPosition));
                    _helper.Emit(OpCodes.Bgt, _iterCurr.ParentIterator!.GetLabelNext());
                }
 
                _iterCurr.LocalPosition = locPos;
            }
 
            EndNestedIterator(ndFor.Binding!);
            _iterCurr.SetIterator(_iterNested!);
        }
 
        /// <summary>
        /// Bind values in the "ndLet" expression to a non-stack location that can later be referenced.
        /// </summary>
        public void StartLetBinding(QilIterator ndLet)
        {
            Debug.Assert(!ndLet.XmlType!.IsSingleton);
 
            // Construct nested iterator
            StartNestedIterator();
 
            // Allow base internal class to dispatch based on QilExpression node type
            NestedVisit(ndLet.Binding!, GetItemStorageType(ndLet), !ndLet.XmlType.IsSingleton);
 
            // DebugInfo: Open variable scope
            // DebugInfo: Ensure that for variable is stored in a local and tag it with the user-defined name
            if (_qil.IsDebug && ndLet.DebugName != null)
            {
                _helper.DebugStartScope();
 
                // Ensure that cache is stored in a local variable with a user-defined name
                _iterCurr.EnsureLocal("$$$cache");
            }
            else
            {
                // Ensure that cache is not stored on the stack
                _iterCurr.EnsureNoStack("$$$cache");
            }
 
            EndNestedIterator(ndLet);
        }
 
        /// <summary>
        /// Mark iterator variables as out-of-scope.
        /// </summary>
        private void EndBinding(QilIterator ndIter)
        {
            Debug.Assert(ndIter != null);
 
            // Variables go out of scope here
            if (_qil.IsDebug && ndIter.DebugName != null)
                _helper.DebugEndScope();
        }
 
        /// <summary>
        /// Generate code for QilNodeType.PositionOf.
        /// </summary>
        protected override QilNode VisitPositionOf(QilUnary ndPos)
        {
            QilIterator ndIter = (ndPos.Child as QilIterator)!;
            LocalBuilder locPos;
            Debug.Assert(ndIter.NodeType == QilNodeType.For);
 
            locPos = XmlILAnnotation.Write(ndIter).CachedIteratorDescriptor!.LocalPosition!;
            Debug.Assert(locPos != null);
            _iterCurr.Storage = StorageDescriptor.Local(locPos, typeof(int), false);
 
            return ndPos;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Sort.
        /// </summary>
        protected override QilNode VisitSort(QilLoop ndSort)
        {
            Type itemStorageType = GetItemStorageType(ndSort);
            LocalBuilder locCache, locKeys;
            Label lblOnEndSort = _helper.DefineLabel();
            Debug.Assert(ndSort.Variable.NodeType == QilNodeType.For);
 
            // XmlQuerySequence<T> cache;
            // cache = XmlQuerySequence.CreateOrReuse(cache);
            XmlILStorageMethods methods = XmlILMethods.StorageMethods[itemStorageType];
            locCache = _helper.DeclareLocal("$$$cache", methods.SeqType);
            _helper.Emit(OpCodes.Ldloc, locCache);
            _helper.Call(methods.SeqReuse);
            _helper.Emit(OpCodes.Stloc, locCache);
            _helper.Emit(OpCodes.Ldloc, locCache);
 
            // XmlSortKeyAccumulator keys;
            // keys.Create(runtime);
            locKeys = _helper.DeclareLocal("$$$keys", typeof(XmlSortKeyAccumulator));
            _helper.Emit(OpCodes.Ldloca, locKeys);
            _helper.Call(XmlILMethods.SortKeyCreate);
 
            // Construct nested iterator
            // foreach (item in sort-expr) {
            StartNestedIterator(lblOnEndSort);
            StartBinding(ndSort.Variable);
            Debug.Assert(!_iterNested!.Storage.IsCached);
 
            // cache.Add(item);
            _iterCurr.EnsureStackNoCache();
            _iterCurr.EnsureItemStorageType(ndSort.Variable.XmlType!, GetItemStorageType(ndSort.Variable));
            _helper.Call(methods.SeqAdd);
 
            _helper.Emit(OpCodes.Ldloca, locKeys);
 
            // Add keys to accumulator (there may be several keys)
            foreach (QilSortKey ndKey in ndSort.Body)
                VisitSortKey(ndKey, locKeys);
 
            // keys.FinishSortKeys();
            _helper.Call(XmlILMethods.SortKeyFinish);
 
            // }
            _helper.Emit(OpCodes.Ldloc, locCache);
            _iterCurr.LoopToEnd(lblOnEndSort);
 
            // Remove cache reference from stack
            _helper.Emit(OpCodes.Pop);
 
            // cache.SortByKeys(keys.Keys);
            _helper.Emit(OpCodes.Ldloc, locCache);
            _helper.Emit(OpCodes.Ldloca, locKeys);
            _helper.Call(XmlILMethods.SortKeyKeys);
            _helper.Call(methods.SeqSortByKeys);
 
            // End nested iterator
            _iterCurr.Storage = StorageDescriptor.Local(locCache, itemStorageType, true);
            EndBinding(ndSort.Variable);
            EndNestedIterator(ndSort.Variable);
            _iterCurr.SetIterator(_iterNested);
 
            return ndSort;
        }
 
        /// <summary>
        /// Generate code to add a (value, collation) sort key to the XmlSortKeyAccumulator.
        /// </summary>
        private void VisitSortKey(QilSortKey ndKey, LocalBuilder locKeys)
        {
            Label lblOnEndKey;
            Debug.Assert(ndKey.Key.XmlType!.IsAtomicValue, "Sort key must be an atomic value.");
 
            // Push collation onto the stack
            _helper.Emit(OpCodes.Ldloca, locKeys);
            if (ndKey.Collation.NodeType == QilNodeType.LiteralString)
            {
                // collation = runtime.GetCollation(idx);
                _helper.CallGetCollation(_helper.StaticData.DeclareCollation((string)(QilLiteral)ndKey.Collation));
            }
            else
            {
                // collation = runtime.CreateCollation(str);
                _helper.LoadQueryRuntime();
                NestedVisitEnsureStack(ndKey.Collation);
                _helper.Call(XmlILMethods.CreateCollation);
            }
 
            if (ndKey.XmlType!.IsSingleton)
            {
                NestedVisitEnsureStack(ndKey.Key);
 
                // keys.AddSortKey(collation, value);
                _helper.AddSortKey(ndKey.Key.XmlType);
            }
            else
            {
                lblOnEndKey = _helper.DefineLabel();
                StartNestedIterator(lblOnEndKey);
                Visit(ndKey.Key);
                _iterCurr.EnsureStackNoCache();
                _iterCurr.EnsureItemStorageType(ndKey.Key.XmlType, GetItemStorageType(ndKey.Key));
 
                // Non-empty sort key
                // keys.AddSortKey(collation, value);
                _helper.AddSortKey(ndKey.Key.XmlType);
 
                // goto LabelDone;
                // LabelOnEnd:
                Label lblDone = _helper.DefineLabel();
                _helper.EmitUnconditionalBranch(OpCodes.Br_S, lblDone);
                _helper.MarkLabel(lblOnEndKey);
 
                // Empty sequence key
                // keys.AddSortKey(collation);
                _helper.AddSortKey(null);
 
                _helper.MarkLabel(lblDone);
 
                EndNestedIterator(ndKey.Key);
            }
        }
 
        /// <summary>
        /// Generate code for QilNodeType.DocOrderDistinct.
        /// </summary>
        protected override QilNode VisitDocOrderDistinct(QilUnary ndDod)
        {
            // DocOrderDistinct applied to a singleton is a no-op
            if (ndDod.XmlType!.IsSingleton)
                return Visit(ndDod.Child);
 
            // Handle any special-case patterns that are rooted at DocOrderDistinct
            if (HandleDodPatterns(ndDod))
                return ndDod;
 
            // Sort results of child expression by document order and remove duplicate nodes
            // cache = runtime.DocOrderDistinct(cache);
            _helper.LoadQueryRuntime();
            NestedVisitEnsureCache(ndDod.Child, typeof(XPathNavigator));
            _iterCurr.EnsureStack();
            _helper.Call(XmlILMethods.DocOrder);
            return ndDod;
        }
 
        /// <summary>
        /// There are a number of path patterns that can be rooted at DocOrderDistinct nodes.  Determine whether one of these
        /// patterns has been previously matched on "ndDod".  If so, generate code for the pattern and return true.  Otherwise,
        /// just return false.
        /// </summary>
        private bool HandleDodPatterns(QilUnary ndDod)
        {
            OptimizerPatterns pattDod = OptimizerPatterns.Read(ndDod);
            XmlNodeKindFlags kinds;
            QilName? name;
            QilNode input, step;
            bool isJoinAndDod;
 
            // Handle JoinAndDod and DodReverse patterns
            isJoinAndDod = pattDod.MatchesPattern(OptimizerPatternName.JoinAndDod);
            if (isJoinAndDod || pattDod.MatchesPattern(OptimizerPatternName.DodReverse))
            {
                OptimizerPatterns pattStep = OptimizerPatterns.Read((QilNode)pattDod.GetArgument(OptimizerPatternArgument.DodStep));
 
                if (pattStep.MatchesPattern(OptimizerPatternName.FilterElements))
                {
                    // FilterElements pattern, so Kind = Element and Name = Argument
                    kinds = XmlNodeKindFlags.Element;
                    name = (QilName)pattStep.GetArgument(OptimizerPatternArgument.ElementQName);
                }
                else if (pattStep.MatchesPattern(OptimizerPatternName.FilterContentKind))
                {
                    // FilterKindTest pattern, so Kind = Argument and Name = null
                    kinds = ((XmlQueryType)pattStep.GetArgument(OptimizerPatternArgument.KindTestType)).NodeKinds;
                    name = null;
                }
                else
                {
                    Debug.Assert(pattStep.MatchesPattern(OptimizerPatternName.Axis), "Dod patterns should only match if step is FilterElements or FilterKindTest or Axis");
                    kinds = ((ndDod.XmlType!.NodeKinds & XmlNodeKindFlags.Attribute) != 0) ? XmlNodeKindFlags.Any : XmlNodeKindFlags.Content;
                    name = null;
                }
 
                step = (QilNode)pattStep.GetArgument(OptimizerPatternArgument.StepNode);
                if (isJoinAndDod)
                {
                    switch (step.NodeType)
                    {
                        case QilNodeType.Content:
                            CreateContainerIterator(ndDod, "$$$iterContent", typeof(ContentMergeIterator), XmlILMethods.ContentMergeCreate, XmlILMethods.ContentMergeNext, XmlILMethods.ContentMergeCurrent,
                                                    kinds, name, TriState.Unknown);
                            return true;
 
                        case QilNodeType.Descendant:
                        case QilNodeType.DescendantOrSelf:
                            CreateContainerIterator(ndDod, "$$$iterDesc", typeof(DescendantMergeIterator), XmlILMethods.DescMergeCreate, XmlILMethods.DescMergeNext, XmlILMethods.DescMergeCurrent,
                                                    kinds, name, (step.NodeType == QilNodeType.Descendant) ? TriState.False : TriState.True);
                            return true;
 
                        case QilNodeType.XPathFollowing:
                            CreateContainerIterator(ndDod, "$$$iterFoll", typeof(XPathFollowingMergeIterator), XmlILMethods.XPFollMergeCreate, XmlILMethods.XPFollMergeNext, XmlILMethods.XPFollMergeCurrent,
                                                    kinds, name, TriState.Unknown);
                            return true;
 
                        case QilNodeType.FollowingSibling:
                            CreateContainerIterator(ndDod, "$$$iterFollSib", typeof(FollowingSiblingMergeIterator), XmlILMethods.FollSibMergeCreate, XmlILMethods.FollSibMergeNext, XmlILMethods.FollSibMergeCurrent,
                                                    kinds, name, TriState.Unknown);
                            return true;
 
                        case QilNodeType.XPathPreceding:
                            CreateContainerIterator(ndDod, "$$$iterPrec", typeof(XPathPrecedingMergeIterator), XmlILMethods.XPPrecMergeCreate, XmlILMethods.XPPrecMergeNext, XmlILMethods.XPPrecMergeCurrent,
                                                    kinds, name, TriState.Unknown);
                            return true;
 
                        default:
                            Debug.Fail($"Pattern {step.NodeType} should have been handled.");
                            break;
                    }
                }
                else
                {
                    input = (QilNode)pattStep.GetArgument(OptimizerPatternArgument.StepInput);
                    switch (step.NodeType)
                    {
                        case QilNodeType.Ancestor:
                        case QilNodeType.AncestorOrSelf:
                            CreateFilteredIterator(input, "$$$iterAnc", typeof(AncestorDocOrderIterator), XmlILMethods.AncDOCreate, XmlILMethods.AncDONext, XmlILMethods.AncDOCurrent,
                                                   kinds, name, (step.NodeType == QilNodeType.Ancestor) ? TriState.False : TriState.True, null);
                            return true;
 
                        case QilNodeType.PrecedingSibling:
                            CreateFilteredIterator(input, "$$$iterPreSib", typeof(PrecedingSiblingDocOrderIterator), XmlILMethods.PreSibDOCreate, XmlILMethods.PreSibDONext, XmlILMethods.PreSibDOCurrent,
                                                   kinds, name, TriState.Unknown, null);
                            return true;
 
                        case QilNodeType.XPathPreceding:
                            CreateFilteredIterator(input, "$$$iterPrec", typeof(XPathPrecedingDocOrderIterator), XmlILMethods.XPPrecDOCreate, XmlILMethods.XPPrecDONext, XmlILMethods.XPPrecDOCurrent,
                                                   kinds, name, TriState.Unknown, null);
                            return true;
 
                        default:
                            Debug.Fail($"Pattern {step.NodeType} should have been handled.");
                            break;
                    }
                }
            }
            else if (pattDod.MatchesPattern(OptimizerPatternName.DodMerge))
            {
                // DodSequenceMerge dodMerge;
                LocalBuilder locMerge = _helper.DeclareLocal("$$$dodMerge", typeof(DodSequenceMerge));
                Label lblOnEnd = _helper.DefineLabel();
 
                // dodMerge.Create(runtime);
                _helper.Emit(OpCodes.Ldloca, locMerge);
                _helper.LoadQueryRuntime();
                _helper.Call(XmlILMethods.DodMergeCreate);
                _helper.Emit(OpCodes.Ldloca, locMerge);
 
                StartNestedIterator(lblOnEnd);
 
                // foreach (seq in expr) {
                Visit(ndDod.Child);
 
                // dodMerge.AddSequence(seq);
                Debug.Assert(_iterCurr.Storage.IsCached, "DodMerge pattern should only be matched when cached sequences are returned from loop");
                _iterCurr.EnsureStack();
                _helper.Call(XmlILMethods.DodMergeAdd);
                _helper.Emit(OpCodes.Ldloca, locMerge);
 
                // }
                _iterCurr.LoopToEnd(lblOnEnd);
 
                EndNestedIterator(ndDod.Child);
 
                // mergedSequence = dodMerge.MergeSequences();
                _helper.Call(XmlILMethods.DodMergeSeq);
 
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), true);
 
                return true;
            }
 
            return false;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Invoke.
        /// </summary>
        protected override QilNode VisitInvoke(QilInvoke ndInvoke)
        {
            QilFunction ndFunc = ndInvoke.Function;
            MethodInfo methInfo = XmlILAnnotation.Write(ndFunc).FunctionBinding!;
            bool useWriter = (XmlILConstructInfo.Read(ndFunc).ConstructMethod == XmlILConstructMethod.Writer);
            Debug.Assert(!XmlILConstructInfo.Read(ndInvoke).PushToWriterFirst || useWriter);
 
            // Push XmlQueryRuntime onto the stack as the first parameter
            _helper.LoadQueryRuntime();
 
            // Generate code to push each Invoke argument onto the stack
            for (int iArg = 0; iArg < ndInvoke.Arguments.Count; iArg++)
            {
                QilNode ndActualArg = ndInvoke.Arguments[iArg];
                QilNode ndFormalArg = ndInvoke.Function.Arguments[iArg];
                NestedVisitEnsureStack(ndActualArg, GetItemStorageType(ndFormalArg), !ndFormalArg.XmlType!.IsSingleton);
            }
 
            // Check whether this call should compiled using the .tailcall instruction
            if (OptimizerPatterns.Read(ndInvoke).MatchesPattern(OptimizerPatternName.TailCall))
                _helper.TailCall(methInfo);
            else
                _helper.Call(methInfo);
 
            // If function's results are not pushed to Writer,
            if (!useWriter)
            {
                // Return value is on the stack; ensure it has the correct storage type
                _iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndInvoke), !ndInvoke.XmlType!.IsSingleton);
            }
            else
            {
                _iterCurr.Storage = StorageDescriptor.None();
            }
 
            return ndInvoke;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Content.
        /// </summary>
        protected override QilNode VisitContent(QilUnary ndContent)
        {
            CreateSimpleIterator(ndContent.Child, "$$$iterAttrContent", typeof(AttributeContentIterator), XmlILMethods.AttrContentCreate, XmlILMethods.AttrContentNext, XmlILMethods.AttrContentCurrent);
            return ndContent;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Attribute.
        /// </summary>
        protected override QilNode VisitAttribute(QilBinary ndAttr)
        {
            QilName? ndName = ndAttr.Right as QilName;
            Debug.Assert(ndName != null, "Attribute node must have a literal QName as its second argument");
 
            // XPathNavigator navAttr;
            LocalBuilder locNav = _helper.DeclareLocal("$$$navAttr", typeof(XPathNavigator));
 
            // navAttr = SyncToNavigator(navAttr, navCtxt);
            SyncToNavigator(locNav, ndAttr.Left);
 
            // if (!navAttr.MoveToAttribute(localName, namespaceUri)) goto LabelNextCtxt;
            _helper.Emit(OpCodes.Ldloc, locNav);
            _helper.CallGetAtomizedName(_helper.StaticData.DeclareName(ndName.LocalName));
            _helper.CallGetAtomizedName(_helper.StaticData.DeclareName(ndName.NamespaceUri));
            _helper.Call(XmlILMethods.NavMoveAttr);
            _helper.Emit(OpCodes.Brfalse, _iterCurr.GetLabelNext());
 
            _iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
            return ndAttr;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Parent.
        /// </summary>
        protected override QilNode VisitParent(QilUnary ndParent)
        {
            // XPathNavigator navParent;
            LocalBuilder locNav = _helper.DeclareLocal("$$$navParent", typeof(XPathNavigator));
 
            // navParent = SyncToNavigator(navParent, navCtxt);
            SyncToNavigator(locNav, ndParent.Child);
 
            // if (!navParent.MoveToParent()) goto LabelNextCtxt;
            _helper.Emit(OpCodes.Ldloc, locNav);
            _helper.Call(XmlILMethods.NavMoveParent);
            _helper.Emit(OpCodes.Brfalse, _iterCurr.GetLabelNext());
 
            _iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
            return ndParent;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Root.
        /// </summary>
        protected override QilNode VisitRoot(QilUnary ndRoot)
        {
            // XPathNavigator navRoot;
            LocalBuilder locNav = _helper.DeclareLocal("$$$navRoot", typeof(XPathNavigator));
 
            // navRoot = SyncToNavigator(navRoot, navCtxt);
            SyncToNavigator(locNav, ndRoot.Child);
 
            // navRoot.MoveToRoot();
            _helper.Emit(OpCodes.Ldloc, locNav);
            _helper.Call(XmlILMethods.NavMoveRoot);
 
            _iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
            return ndRoot;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XmlContext.
        /// </summary>
        /// <remarks>
        /// Generates code to retrieve the default document using the XmlResolver.
        /// </remarks>
        protected override QilNode VisitXmlContext(QilNode ndCtxt)
        {
            // runtime.ExternalContext.DefaultDataSource
            _helper.LoadQueryContext();
            _helper.Call(XmlILMethods.GetDefaultDataSource);
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), false);
            return ndCtxt;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.Descendant.
        /// </summary>
        protected override QilNode VisitDescendant(QilUnary ndDesc)
        {
            CreateFilteredIterator(ndDesc.Child, "$$$iterDesc", typeof(DescendantIterator), XmlILMethods.DescCreate, XmlILMethods.DescNext, XmlILMethods.DescCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.False, null);
            return ndDesc;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.DescendantOrSelf.
        /// </summary>
        protected override QilNode VisitDescendantOrSelf(QilUnary ndDesc)
        {
            CreateFilteredIterator(ndDesc.Child, "$$$iterDesc", typeof(DescendantIterator), XmlILMethods.DescCreate, XmlILMethods.DescNext, XmlILMethods.DescCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.True, null);
            return ndDesc;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.Ancestor.
        /// </summary>
        protected override QilNode VisitAncestor(QilUnary ndAnc)
        {
            CreateFilteredIterator(ndAnc.Child, "$$$iterAnc", typeof(AncestorIterator), XmlILMethods.AncCreate, XmlILMethods.AncNext, XmlILMethods.AncCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.False, null);
            return ndAnc;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.AncestorOrSelf.
        /// </summary>
        protected override QilNode VisitAncestorOrSelf(QilUnary ndAnc)
        {
            CreateFilteredIterator(ndAnc.Child, "$$$iterAnc", typeof(AncestorIterator), XmlILMethods.AncCreate, XmlILMethods.AncNext, XmlILMethods.AncCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.True, null);
            return ndAnc;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.Preceding.
        /// </summary>
        protected override QilNode VisitPreceding(QilUnary ndPrec)
        {
            CreateFilteredIterator(ndPrec.Child, "$$$iterPrec", typeof(PrecedingIterator), XmlILMethods.PrecCreate, XmlILMethods.PrecNext, XmlILMethods.PrecCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.Unknown, null);
            return ndPrec;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.FollowingSibling.
        /// </summary>
        protected override QilNode VisitFollowingSibling(QilUnary ndFollSib)
        {
            CreateFilteredIterator(ndFollSib.Child, "$$$iterFollSib", typeof(FollowingSiblingIterator), XmlILMethods.FollSibCreate, XmlILMethods.FollSibNext, XmlILMethods.FollSibCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.Unknown, null);
            return ndFollSib;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.PrecedingSibling.
        /// </summary>
        protected override QilNode VisitPrecedingSibling(QilUnary ndPreSib)
        {
            CreateFilteredIterator(ndPreSib.Child, "$$$iterPreSib", typeof(PrecedingSiblingIterator), XmlILMethods.PreSibCreate, XmlILMethods.PreSibNext, XmlILMethods.PreSibCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.Unknown, null);
            return ndPreSib;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.NodeRange.
        /// </summary>
        protected override QilNode VisitNodeRange(QilBinary ndRange)
        {
            CreateFilteredIterator(ndRange.Left, "$$$iterRange", typeof(NodeRangeIterator), XmlILMethods.NodeRangeCreate, XmlILMethods.NodeRangeNext, XmlILMethods.NodeRangeCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.Unknown, ndRange.Right);
            return ndRange;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.Deref.
        /// </summary>
        protected override QilNode VisitDeref(QilBinary ndDeref)
        {
            // IdIterator iterId;
            LocalBuilder locIter = _helper.DeclareLocal("$$$iterId", typeof(IdIterator));
 
            // iterId.Create(navCtxt, value);
            _helper.Emit(OpCodes.Ldloca, locIter);
            NestedVisitEnsureStack(ndDeref.Left);
            NestedVisitEnsureStack(ndDeref.Right);
            _helper.Call(XmlILMethods.IdCreate);
 
            GenerateSimpleIterator(typeof(XPathNavigator), locIter, XmlILMethods.IdNext, XmlILMethods.IdCurrent);
 
            return ndDeref;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.ElementCtor.
        /// </summary>
        protected override QilNode VisitElementCtor(QilBinary ndElem)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(ndElem);
            bool callChk;
            GenerateNameType nameType;
            Debug.Assert(XmlILConstructInfo.Read(ndElem).PushToWriterFirst, "Element construction should always be pushed to writer.");
 
            // Runtime checks must be made in the following cases:
            //   1. Xml state is not known at compile-time, or is illegal
            //   2. Element's namespace must be declared
            //   3. Element's attributes might be duplicates of one another, or namespaces might follow attributes
            callChk = CheckWithinContent(info) || !info.IsNamespaceInScope || ElementCachesAttributes(info);
 
            // If it is not known whether element content was output, then make this check at run-time
            if (XmlILConstructInfo.Read(ndElem.Right).FinalStates == PossibleXmlStates.Any)
                callChk = true;
 
            // If runtime state after EndElement is called is not known, then call XmlQueryOutput.WriteEndElementChk
            if (info.FinalStates == PossibleXmlStates.Any)
                callChk = true;
 
            // If WriteStartElementChk will *not* be called, then code must be generated to ensure valid state transitions
            if (!callChk)
                BeforeStartChecks(ndElem);
 
            // Generate call to WriteStartElement
            nameType = LoadNameAndType(XPathNodeType.Element, ndElem.Left, true, callChk);
            _helper.CallWriteStartElement(nameType, callChk);
 
            // Recursively construct content
            NestedVisit(ndElem.Right);
 
            // If runtime state is guaranteed to be EnumAttrs, and an element is being constructed, call XmlQueryOutput.StartElementContent
            if (XmlILConstructInfo.Read(ndElem.Right).FinalStates == PossibleXmlStates.EnumAttrs && !callChk)
                _helper.CallStartElementContent();
 
            // Generate call to WriteEndElement
            nameType = LoadNameAndType(XPathNodeType.Element, ndElem.Left, false, callChk);
            _helper.CallWriteEndElement(nameType, callChk);
 
            if (!callChk)
                AfterEndChecks(ndElem);
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndElem;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.AttributeCtor.
        /// </summary>
        protected override QilNode VisitAttributeCtor(QilBinary ndAttr)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(ndAttr);
            bool callChk;
            GenerateNameType nameType;
            Debug.Assert(XmlILConstructInfo.Read(ndAttr).PushToWriterFirst, "Attribute construction should always be pushed to writer.");
 
            // Runtime checks must be made in the following cases:
            //   1. Xml state is not known at compile-time, or is illegal
            //   2. Attribute's namespace must be declared
            callChk = CheckEnumAttrs(info) || !info.IsNamespaceInScope;
 
            // If WriteStartAttributeChk will *not* be called, then code must be generated to ensure well-formedness
            // and track namespace scope.
            if (!callChk)
                BeforeStartChecks(ndAttr);
 
            // Generate call to WriteStartAttribute
            nameType = LoadNameAndType(XPathNodeType.Attribute, ndAttr.Left, true, callChk);
            _helper.CallWriteStartAttribute(nameType, callChk);
 
            // Recursively construct content
            NestedVisit(ndAttr.Right);
 
            // Generate call to WriteEndAttribute
            _helper.CallWriteEndAttribute(callChk);
 
            if (!callChk)
                AfterEndChecks(ndAttr);
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndAttr;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.CommentCtor.
        /// </summary>
        protected override QilNode VisitCommentCtor(QilUnary ndComment)
        {
            Debug.Assert(XmlILConstructInfo.Read(ndComment).PushToWriterFirst, "Comment construction should always be pushed to writer.");
 
            // Always call XmlQueryOutput.WriteStartComment
            _helper.CallWriteStartComment();
 
            // Recursively construct content
            NestedVisit(ndComment.Child);
 
            // Always call XmlQueryOutput.WriteEndComment
            _helper.CallWriteEndComment();
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndComment;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.PICtor.
        /// </summary>
        protected override QilNode VisitPICtor(QilBinary ndPI)
        {
            Debug.Assert(XmlILConstructInfo.Read(ndPI).PushToWriterFirst, "PI construction should always be pushed to writer.");
 
            // Always call XmlQueryOutput.WriteStartPI
            _helper.LoadQueryOutput();
            NestedVisitEnsureStack(ndPI.Left);
            _helper.CallWriteStartPI();
 
            // Recursively construct content
            NestedVisit(ndPI.Right);
 
            // Always call XmlQueryOutput.WriteEndPI
            _helper.CallWriteEndPI();
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndPI;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.TextCtor.
        /// </summary>
        protected override QilNode VisitTextCtor(QilUnary ndText)
        {
            return VisitTextCtor(ndText, false);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.RawTextCtor.
        /// </summary>
        protected override QilNode VisitRawTextCtor(QilUnary ndText)
        {
            return VisitTextCtor(ndText, true);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.TextCtor and QilNodeType.RawTextCtor.
        /// </summary>
        private QilUnary VisitTextCtor(QilUnary ndText, bool disableOutputEscaping)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(ndText);
            bool callChk;
            Debug.Assert(info.PushToWriterFirst, "Text construction should always be pushed to writer.");
 
            // Write out text in different contexts (within attribute, within element, within comment, etc.)
            switch (info.InitialStates)
            {
                case PossibleXmlStates.WithinAttr:
                case PossibleXmlStates.WithinComment:
                case PossibleXmlStates.WithinPI:
                    callChk = false;
                    break;
 
                default:
                    callChk = CheckWithinContent(info);
                    break;
            }
 
            if (!callChk)
                BeforeStartChecks(ndText);
 
            _helper.LoadQueryOutput();
 
            // Push string value of text onto IL stack
            NestedVisitEnsureStack(ndText.Child);
 
            // Write out text in different contexts (within attribute, within element, within comment, etc.)
            switch (info.InitialStates)
            {
                case PossibleXmlStates.WithinAttr:
                    // Ignore hints when writing out attribute text
                    _helper.CallWriteString(false, callChk);
                    break;
 
                case PossibleXmlStates.WithinComment:
                    // Call XmlQueryOutput.WriteCommentString
                    _helper.Call(XmlILMethods.CommentText);
                    break;
 
                case PossibleXmlStates.WithinPI:
                    // Call XmlQueryOutput.WriteProcessingInstructionString
                    _helper.Call(XmlILMethods.PIText);
                    break;
 
                default:
                    // Call XmlQueryOutput.WriteTextBlockChk, XmlQueryOutput.WriteTextBlockNoEntities, or XmlQueryOutput.WriteTextBlock
                    _helper.CallWriteString(disableOutputEscaping, callChk);
                    break;
            }
 
            if (!callChk)
                AfterEndChecks(ndText);
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndText;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.DocumentCtor.
        /// </summary>
        protected override QilNode VisitDocumentCtor(QilUnary ndDoc)
        {
            Debug.Assert(XmlILConstructInfo.Read(ndDoc).PushToWriterFirst, "Document root construction should always be pushed to writer.");
 
            // Generate call to XmlQueryOutput.WriteStartRootChk
            _helper.CallWriteStartRoot();
 
            // Recursively construct content
            NestedVisit(ndDoc.Child);
 
            // Generate call to XmlQueryOutput.WriteEndRootChk
            _helper.CallWriteEndRoot();
 
            _iterCurr.Storage = StorageDescriptor.None();
 
            return ndDoc;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.NamespaceDecl.
        /// </summary>
        protected override QilNode VisitNamespaceDecl(QilBinary ndNmsp)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(ndNmsp);
            bool callChk;
            Debug.Assert(info.PushToWriterFirst, "Namespace construction should always be pushed to writer.");
 
            // Runtime checks must be made in the following cases:
            //   1. Xml state is not known at compile-time, or is illegal
            //   2. Namespaces might be added to element after attributes have already been added
            callChk = CheckEnumAttrs(info) || MightHaveNamespacesAfterAttributes(info);
 
            // If WriteNamespaceDeclarationChk will *not* be called, then code must be generated to ensure well-formedness
            // and track namespace scope.
            if (!callChk)
                BeforeStartChecks(ndNmsp);
 
            _helper.LoadQueryOutput();
 
            // Recursively construct prefix and ns
            NestedVisitEnsureStack(ndNmsp.Left);
            NestedVisitEnsureStack(ndNmsp.Right);
 
            // Generate call to WriteNamespaceDecl
            _helper.CallWriteNamespaceDecl(callChk);
 
            if (!callChk)
                AfterEndChecks(ndNmsp);
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndNmsp;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.RtfCtor.
        /// </summary>
        protected override QilNode VisitRtfCtor(QilBinary ndRtf)
        {
            OptimizerPatterns patt = OptimizerPatterns.Read(ndRtf);
            string baseUri = (string)(QilLiteral)ndRtf.Right;
 
            if (patt.MatchesPattern(OptimizerPatternName.SingleTextRtf))
            {
                // Special-case Rtf containing a root node and a single text node child
                _helper.LoadQueryRuntime();
                NestedVisitEnsureStack((QilNode)patt.GetArgument(OptimizerPatternArgument.RtfText));
                _helper.Emit(OpCodes.Ldstr, baseUri);
                _helper.Call(XmlILMethods.RtfConstr);
            }
            else
            {
                // Start nested construction of an Rtf
                _helper.CallStartRtfConstruction(baseUri);
 
                // Write content of Rtf to writer
                NestedVisit(ndRtf.Left);
 
                // Get the result Rtf
                _helper.CallEndRtfConstruction();
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), false);
            return ndRtf;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.NameOf.
        /// </summary>
        protected override QilNode VisitNameOf(QilUnary ndName)
        {
            return VisitNodeProperty(ndName);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.LocalNameOf.
        /// </summary>
        protected override QilNode VisitLocalNameOf(QilUnary ndName)
        {
            return VisitNodeProperty(ndName);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.NamespaceUriOf.
        /// </summary>
        protected override QilNode VisitNamespaceUriOf(QilUnary ndName)
        {
            return VisitNodeProperty(ndName);
        }
 
        /// <summary>
        /// Generate code for QilNodeType.PrefixOf.
        /// </summary>
        protected override QilNode VisitPrefixOf(QilUnary ndName)
        {
            return VisitNodeProperty(ndName);
        }
 
        /// <summary>
        /// Generate code to push the local name, namespace uri, or qname of the context navigator.
        /// </summary>
        private QilUnary VisitNodeProperty(QilUnary ndProp)
        {
            // Generate code to push argument onto stack
            NestedVisitEnsureStack(ndProp.Child);
 
            switch (ndProp.NodeType)
            {
                case QilNodeType.NameOf:
                    // push new XmlQualifiedName(navigator.LocalName, navigator.NamespaceURI);
                    _helper.Emit(OpCodes.Dup);
                    _helper.Call(XmlILMethods.NavLocalName);
                    _helper.Call(XmlILMethods.NavNmsp);
                    _helper.Construct(XmlILConstructors.QName);
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(XmlQualifiedName), false);
                    break;
 
                case QilNodeType.LocalNameOf:
                    // push navigator.Name;
                    _helper.Call(XmlILMethods.NavLocalName);
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
                    break;
 
                case QilNodeType.NamespaceUriOf:
                    // push navigator.NamespaceURI;
                    _helper.Call(XmlILMethods.NavNmsp);
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
                    break;
 
                case QilNodeType.PrefixOf:
                    // push navigator.Prefix;
                    _helper.Call(XmlILMethods.NavPrefix);
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
                    break;
 
                default:
                    Debug.Fail($"Unexpected node type {ndProp.NodeType}");
                    break;
            }
 
            return ndProp;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.TypeAssert.
        /// </summary>
        protected override QilNode VisitTypeAssert(QilTargetType ndTypeAssert)
        {
            if (!ndTypeAssert.Source.XmlType!.IsSingleton && ndTypeAssert.XmlType!.IsSingleton && !_iterCurr.HasLabelNext)
            {
                // This case occurs when a non-singleton expression is treated as cardinality One.
                // The trouble is that the expression will branch to an end label when it's done iterating, so
                // an end label must be provided.  But there is no next label in the current iteration context,
                // so we've got to create a dummy label instead (IL requires it).  This creates an infinite loop,
                // but since it's known statically that the expression is cardinality One, this branch will never
                // be taken.
                Label lblDummy = _helper.DefineLabel();
                _helper.MarkLabel(lblDummy);
                NestedVisit(ndTypeAssert.Source, lblDummy);
            }
            else
            {
                // Generate code for child expression
                Visit(ndTypeAssert.Source);
            }
 
            _iterCurr.EnsureItemStorageType(ndTypeAssert.Source.XmlType, GetItemStorageType(ndTypeAssert));
            return ndTypeAssert;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.IsType.
        /// </summary>
        protected override QilNode VisitIsType(QilTargetType ndIsType)
        {
            XmlQueryType typDerived, typBase;
            XmlTypeCode codeBase;
 
            typDerived = ndIsType.Source.XmlType!;
            typBase = ndIsType.TargetType;
            Debug.Assert(!typDerived!.NeverSubtypeOf(typBase), "Normalizer should have eliminated IsType where source can never be a subtype of destination type.");
 
            // Special Case: Test whether singleton item is a Node
            if (typDerived.IsSingleton && (object)typBase == (object)TypeFactory.Node)
            {
                NestedVisitEnsureStack(ndIsType.Source);
                Debug.Assert(_iterCurr.Storage.ItemStorageType == typeof(XPathItem), "If !IsNode, then storage type should be Item");
 
                // if (item.IsNode op true) goto LabelBranch;
                _helper.Call(XmlILMethods.ItemIsNode);
                ZeroCompare(QilNodeType.Ne, true);
 
                return ndIsType;
            }
 
            // Special Case: Source value is a singleton Node, and we're testing whether it is an Element, Attribute, PI, etc.
            if (MatchesNodeKinds(ndIsType, typDerived, typBase))
                return ndIsType;
 
            // Special Case: XmlTypeCode is sufficient to describe destination type
            if ((object)typBase == (object)TypeFactory.Double) codeBase = XmlTypeCode.Double;
            else if ((object)typBase == (object)TypeFactory.String) codeBase = XmlTypeCode.String;
            else if ((object)typBase == (object)TypeFactory.Boolean) codeBase = XmlTypeCode.Boolean;
            else if ((object)typBase == (object)TypeFactory.Node) codeBase = XmlTypeCode.Node;
            else codeBase = XmlTypeCode.None;
 
            if (codeBase != XmlTypeCode.None)
            {
                // if (runtime.MatchesXmlType(value, code) op true) goto LabelBranch;
                _helper.LoadQueryRuntime();
                NestedVisitEnsureStack(ndIsType.Source, typeof(XPathItem), !typDerived.IsSingleton);
                _helper.LoadInteger((int)codeBase);
                _helper.Call(typDerived.IsSingleton ? XmlILMethods.ItemMatchesCode : XmlILMethods.SeqMatchesCode);
                ZeroCompare(QilNodeType.Ne, true);
 
                return ndIsType;
            }
 
            // if (runtime.MatchesXmlType(value, idxType) op true) goto LabelBranch;
            _helper.LoadQueryRuntime();
            NestedVisitEnsureStack(ndIsType.Source, typeof(XPathItem), !typDerived.IsSingleton);
            _helper.LoadInteger(_helper.StaticData.DeclareXmlType(typBase));
            _helper.Call(typDerived.IsSingleton ? XmlILMethods.ItemMatchesType : XmlILMethods.SeqMatchesType);
            ZeroCompare(QilNodeType.Ne, true);
 
            return ndIsType;
        }
 
        /// <summary>
        /// Faster code can be generated if type test is just a node kind test.  If this special case is detected, then generate code and return true.
        /// Otherwise, return false, and a call to MatchesXmlType will be generated instead.
        /// </summary>
        private bool MatchesNodeKinds(QilTargetType ndIsType, XmlQueryType typDerived, XmlQueryType typBase)
        {
            XmlNodeKindFlags kinds;
            bool allowKinds = true;
            XPathNodeType kindsRuntime;
            int kindsUnion;
 
            // If not checking whether typDerived is some kind of singleton node, then fallback to MatchesXmlType
            if (!typBase.IsNode || !typBase.IsSingleton)
                return false;
 
            // If typDerived is not statically guaranteed to be a singleton node (and not an rtf), then fallback to MatchesXmlType
            if (!typDerived.IsNode || !typDerived.IsSingleton || !typDerived.IsNotRtf)
                return false;
 
            // Now we are guaranteed that typDerived is a node, and typBase is a node, so check node kinds
            // Ensure that typBase is only composed of kind-test prime types (no name-test, no schema-test, etc.)
            kinds = XmlNodeKindFlags.None;
            foreach (XmlQueryType typItem in typBase)
            {
                if ((object)typItem == (object)TypeFactory.Element) kinds |= XmlNodeKindFlags.Element;
                else if ((object)typItem == (object)TypeFactory.Attribute) kinds |= XmlNodeKindFlags.Attribute;
                else if ((object)typItem == (object)TypeFactory.Text) kinds |= XmlNodeKindFlags.Text;
                else if ((object)typItem == (object)TypeFactory.Document) kinds |= XmlNodeKindFlags.Document;
                else if ((object)typItem == (object)TypeFactory.Comment) kinds |= XmlNodeKindFlags.Comment;
                else if ((object)typItem == (object)TypeFactory.PI) kinds |= XmlNodeKindFlags.PI;
                else if ((object)typItem == (object)TypeFactory.Namespace) kinds |= XmlNodeKindFlags.Namespace;
                else return false;
            }
 
            Debug.Assert((typDerived.NodeKinds & kinds) != XmlNodeKindFlags.None, "Normalizer should have taken care of case where node kinds are disjoint.");
 
            kinds = typDerived.NodeKinds & kinds;
 
            // Attempt to allow or disallow exactly one kind
            if (!BitOperations.IsPow2((uint)kinds))
            {
                // Not possible to allow one kind, so try to disallow one kind
                kinds = ~kinds & XmlNodeKindFlags.Any;
                allowKinds = !allowKinds;
            }
 
            switch (kinds)
            {
                case XmlNodeKindFlags.Element: kindsRuntime = XPathNodeType.Element; break;
                case XmlNodeKindFlags.Attribute: kindsRuntime = XPathNodeType.Attribute; break;
                case XmlNodeKindFlags.Namespace: kindsRuntime = XPathNodeType.Namespace; break;
                case XmlNodeKindFlags.PI: kindsRuntime = XPathNodeType.ProcessingInstruction; break;
                case XmlNodeKindFlags.Comment: kindsRuntime = XPathNodeType.Comment; break;
                case XmlNodeKindFlags.Document: kindsRuntime = XPathNodeType.Root; break;
 
                default:
                    // Union of several types (when testing for Text, we need to test for Whitespace as well)
 
                    // if (((1 << navigator.NodeType) & nodesDisallow) op 0) goto LabelBranch;
                    _helper.Emit(OpCodes.Ldc_I4_1);
                    kindsRuntime = XPathNodeType.All;
                    break;
            }
 
            // Push navigator.NodeType onto the stack
            NestedVisitEnsureStack(ndIsType.Source);
            _helper.Call(XmlILMethods.NavType);
 
            if (kindsRuntime == XPathNodeType.All)
            {
                // if (((1 << navigator.NodeType) & kindsUnion) op 0) goto LabelBranch;
                _helper.Emit(OpCodes.Shl);
 
                kindsUnion = 0;
                if ((kinds & XmlNodeKindFlags.Document) != 0) kindsUnion |= (1 << (int)XPathNodeType.Root);
                if ((kinds & XmlNodeKindFlags.Element) != 0) kindsUnion |= (1 << (int)XPathNodeType.Element);
                if ((kinds & XmlNodeKindFlags.Attribute) != 0) kindsUnion |= (1 << (int)XPathNodeType.Attribute);
                if ((kinds & XmlNodeKindFlags.Text) != 0)
                    kindsUnion |= (1 << (int)(int)XPathNodeType.Text) |
                                (1 << (int)(int)XPathNodeType.SignificantWhitespace) |
                                (1 << (int)(int)XPathNodeType.Whitespace);
                if ((kinds & XmlNodeKindFlags.Comment) != 0) kindsUnion |= (1 << (int)XPathNodeType.Comment);
                if ((kinds & XmlNodeKindFlags.PI) != 0) kindsUnion |= (1 << (int)XPathNodeType.ProcessingInstruction);
                if ((kinds & XmlNodeKindFlags.Namespace) != 0) kindsUnion |= (1 << (int)XPathNodeType.Namespace);
 
                _helper.LoadInteger(kindsUnion);
                _helper.Emit(OpCodes.And);
                ZeroCompare(allowKinds ? QilNodeType.Ne : QilNodeType.Eq, false);
            }
            else
            {
                // if (navigator.NodeType op runtimeItem) goto LabelBranch;
                _helper.LoadInteger((int)kindsRuntime);
                ClrCompare(allowKinds ? QilNodeType.Eq : QilNodeType.Ne, XmlTypeCode.Int);
            }
 
            return true;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.IsEmpty.
        /// </summary>
        /// <remarks>
        /// BranchingContext.OnFalse context: is-empty(expr)
        /// ==> foreach (item in expr)
        ///         goto LabelBranch;
        ///
        /// BranchingContext.OnTrue context: is-empty(expr)
        /// ==> foreach (item in expr)
        ///         break;
        ///     ...
        ///     LabelOnEnd: (called if foreach is empty)
        ///     goto LabelBranch;
        ///
        /// BranchingContext.None context: is-empty(expr)
        /// ==> foreach (item in expr)
        ///         break;
        ///     push true();
        ///     ...
        ///     LabelOnEnd: (called if foreach is empty)
        ///     push false();
        /// </remarks>
        protected override QilNode VisitIsEmpty(QilUnary ndIsEmpty)
        {
            Label lblTrue;
 
            // If the child expression returns a cached result,
            if (CachesResult(ndIsEmpty.Child))
            {
                // Then get the count directly from the cache
                NestedVisitEnsureStack(ndIsEmpty.Child);
                _helper.CallCacheCount(_iterNested!.Storage.ItemStorageType);
 
                switch (_iterCurr.CurrentBranchingContext)
                {
                    case BranchingContext.OnFalse:
                        // Take false path if count != 0
                        _helper.TestAndBranch(0, _iterCurr.LabelBranch, OpCodes.Bne_Un);
                        break;
 
                    case BranchingContext.OnTrue:
                        // Take true path if count == 0
                        _helper.TestAndBranch(0, _iterCurr.LabelBranch, OpCodes.Beq);
                        break;
 
                    default:
                        Debug.Assert(_iterCurr.CurrentBranchingContext == BranchingContext.None);
 
                        // if (count == 0) goto LabelTrue;
                        lblTrue = _helper.DefineLabel();
                        _helper.Emit(OpCodes.Brfalse_S, lblTrue);
 
                        // Convert branch targets into push of true/false
                        _helper.ConvBranchToBool(lblTrue, true);
                        break;
                }
            }
            else
            {
                Label lblOnEnd = _helper.DefineLabel();
                IteratorDescriptor iterParent = _iterCurr;
 
                // Forward any LabelOnEnd jumps to LabelBranch if BranchingContext.OnTrue
                if (iterParent.CurrentBranchingContext == BranchingContext.OnTrue)
                    StartNestedIterator(_iterCurr.LabelBranch);
                else
                    StartNestedIterator(lblOnEnd);
 
                Visit(ndIsEmpty.Child);
 
                // Pop value of IsEmpty expression from the stack if necessary
                _iterCurr.EnsureNoCache();
                _iterCurr.DiscardStack();
 
                switch (iterParent.CurrentBranchingContext)
                {
                    case BranchingContext.OnFalse:
                        // Reverse polarity of iterator
                        _helper.EmitUnconditionalBranch(OpCodes.Br, iterParent.LabelBranch);
                        _helper.MarkLabel(lblOnEnd);
                        break;
 
                    case BranchingContext.OnTrue:
                        // Nothing to do
                        break;
 
                    case BranchingContext.None:
                        // Convert branch targets into push of true/false
                        _helper.ConvBranchToBool(lblOnEnd, true);
                        break;
                }
 
                // End nested iterator
                EndNestedIterator(ndIsEmpty.Child);
            }
 
            if (_iterCurr.IsBranching)
                _iterCurr.Storage = StorageDescriptor.None();
            else
                _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
 
            return ndIsEmpty;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XPathNodeValue.
        /// </summary>
        protected override QilNode VisitXPathNodeValue(QilUnary ndVal)
        {
            Label lblOnEnd, lblDone;
            Debug.Assert(ndVal.Child.XmlType!.IsNode, "XPathNodeValue node may only be applied to a sequence of Nodes.");
 
            // If the expression is a singleton,
            if (ndVal.Child.XmlType.IsSingleton)
            {
                // Then generate code to push expression result onto the stack
                NestedVisitEnsureStack(ndVal.Child, typeof(XPathNavigator), false);
 
                // navigator.Value;
                _helper.Call(XmlILMethods.Value);
            }
            else
            {
                lblOnEnd = _helper.DefineLabel();
 
                // Construct nested iterator and iterate over results
                StartNestedIterator(lblOnEnd);
                Visit(ndVal.Child);
                _iterCurr.EnsureStackNoCache();
 
                // navigator.Value;
                _helper.Call(XmlILMethods.Value);
 
                // Handle empty sequence by pushing empty string onto the stack
                lblDone = _helper.DefineLabel();
                _helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
                _helper.MarkLabel(lblOnEnd);
                _helper.Emit(OpCodes.Ldstr, "");
                _helper.MarkLabel(lblDone);
 
                // End nested iterator
                EndNestedIterator(ndVal.Child);
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
 
            return ndVal;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.XPathFollowing.
        /// </summary>
        protected override QilNode VisitXPathFollowing(QilUnary ndFoll)
        {
            CreateFilteredIterator(ndFoll.Child, "$$$iterFoll", typeof(XPathFollowingIterator), XmlILMethods.XPFollCreate, XmlILMethods.XPFollNext, XmlILMethods.XPFollCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.Unknown, null);
            return ndFoll;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.XPathPreceding.
        /// </summary>
        protected override QilNode VisitXPathPreceding(QilUnary ndPrec)
        {
            CreateFilteredIterator(ndPrec.Child, "$$$iterPrec", typeof(XPathPrecedingIterator), XmlILMethods.XPPrecCreate, XmlILMethods.XPPrecNext, XmlILMethods.XPPrecCurrent,
                                   XmlNodeKindFlags.Any, null, TriState.Unknown, null);
            return ndPrec;
        }
 
        /// <summary>
        /// Find physical query plan for QilNodeType.XPathNamespace.
        /// </summary>
        protected override QilNode VisitXPathNamespace(QilUnary ndNmsp)
        {
            CreateSimpleIterator(ndNmsp.Child, "$$$iterNmsp", typeof(NamespaceIterator), XmlILMethods.NmspCreate, XmlILMethods.NmspNext, XmlILMethods.NmspCurrent);
            return ndNmsp;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XsltGenerateId.
        /// </summary>
        protected override QilNode VisitXsltGenerateId(QilUnary ndGenId)
        {
            Label lblOnEnd, lblDone;
 
            _helper.LoadQueryRuntime();
 
            // If the expression is a singleton,
            if (ndGenId.Child.XmlType!.IsSingleton)
            {
                // Then generate code to push expression result onto the stack
                NestedVisitEnsureStack(ndGenId.Child, typeof(XPathNavigator), false);
 
                // runtime.GenerateId(value);
                _helper.Call(XmlILMethods.GenId);
            }
            else
            {
                lblOnEnd = _helper.DefineLabel();
 
                // Construct nested iterator and iterate over results
                StartNestedIterator(lblOnEnd);
                Visit(ndGenId.Child);
                _iterCurr.EnsureStackNoCache();
                _iterCurr.EnsureItemStorageType(ndGenId.Child.XmlType, typeof(XPathNavigator));
 
                // runtime.GenerateId(value);
                _helper.Call(XmlILMethods.GenId);
 
                // Handle empty sequence by pushing empty string onto the stack
                lblDone = _helper.DefineLabel();
                _helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
                _helper.MarkLabel(lblOnEnd);
                _helper.Emit(OpCodes.Pop);
                _helper.Emit(OpCodes.Ldstr, "");
                _helper.MarkLabel(lblDone);
 
                // End nested iterator
                EndNestedIterator(ndGenId.Child);
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
 
            return ndGenId;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XsltInvokeLateBound.
        /// </summary>
        protected override QilNode VisitXsltInvokeLateBound(QilInvokeLateBound ndInvoke)
        {
            LocalBuilder locArgs = _helper.DeclareLocal("$$$args", typeof(IList<XPathItem>[]));
            QilName ndName = (QilName)ndInvoke.Name;
            Debug.Assert(XmlILConstructInfo.Read(ndInvoke).ConstructMethod != XmlILConstructMethod.Writer);
 
            // runtime.ExternalContext.InvokeXsltLateBoundFunction(name, ns, args);
            _helper.LoadQueryContext();
            _helper.Emit(OpCodes.Ldstr, ndName.LocalName);
            _helper.Emit(OpCodes.Ldstr, ndName.NamespaceUri);
 
            // args = new IList<XPathItem>[argCount];
            _helper.LoadInteger(ndInvoke.Arguments.Count);
            _helper.Emit(OpCodes.Newarr, typeof(IList<XPathItem>));
            _helper.Emit(OpCodes.Stloc, locArgs);
 
            for (int iArg = 0; iArg < ndInvoke.Arguments.Count; iArg++)
            {
                QilNode ndArg = ndInvoke.Arguments[iArg];
 
                // args[0] = arg0;
                // ...
                // args[N] = argN;
                _helper.Emit(OpCodes.Ldloc, locArgs);
                _helper.LoadInteger(iArg);
                _helper.Emit(OpCodes.Ldelema, typeof(IList<XPathItem>));
 
                NestedVisitEnsureCache(ndArg, typeof(XPathItem));
                _iterCurr.EnsureStack();
 
                _helper.Emit(OpCodes.Stobj, typeof(IList<XPathItem>));
            }
 
            _helper.Emit(OpCodes.Ldloc, locArgs);
 
            _helper.Call(XmlILMethods.InvokeXsltLate);
 
            // Returned item sequence is on the stack
            _iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), true);
 
            return ndInvoke;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XsltInvokeEarlyBound.
        /// </summary>
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:RequiresUnreferencedCode",
            Justification = "Suppressing warning about not having the RequiresUnreferencedCode attribute since we added " +
            "the attribute to this subclass' constructor. This allows us to not have to annotate the whole QilNode hirerarchy.")]
        protected override QilNode VisitXsltInvokeEarlyBound(QilInvokeEarlyBound ndInvoke)
        {
            QilName ndName = ndInvoke.Name;
            XmlExtensionFunction extFunc;
            Type? clrTypeRetSrc;
            Type clrTypeRetDst;
 
            // Retrieve metadata from the extension function
            extFunc = new XmlExtensionFunction(ndName.LocalName, ndName.NamespaceUri, ndInvoke.ClrMethod);
            clrTypeRetSrc = extFunc.ClrReturnType;
            clrTypeRetDst = GetStorageType(ndInvoke);
 
            // Prepare to call runtime.ChangeTypeXsltResult
            if (clrTypeRetSrc != clrTypeRetDst && !ndInvoke.XmlType!.IsEmpty)
            {
                _helper.LoadQueryRuntime();
                _helper.LoadInteger(_helper.StaticData.DeclareXmlType(ndInvoke.XmlType));
            }
 
            // If this is not a static method, then get the instance object
            if (!extFunc.Method!.IsStatic)
            {
                // Special-case the XsltLibrary object
                if (ndName.NamespaceUri.Length == 0)
                    _helper.LoadXsltLibrary();
                else
                    _helper.CallGetEarlyBoundObject(_helper.StaticData.DeclareEarlyBound(ndName.NamespaceUri, extFunc.Method.DeclaringType!), extFunc.Method.DeclaringType!);
            }
 
            // Generate code to push each Invoke argument onto the stack
            for (int iArg = 0; iArg < ndInvoke.Arguments.Count; iArg++)
            {
                QilNode ndActualArg;
                XmlQueryType xmlTypeFormalArg;
                Type clrTypeActualArg, clrTypeFormalArg;
 
                ndActualArg = ndInvoke.Arguments[iArg];
 
                // Infer Xml type and Clr type of formal argument
                xmlTypeFormalArg = extFunc.GetXmlArgumentType(iArg);
                clrTypeFormalArg = extFunc.GetClrArgumentType(iArg);
 
                Debug.Assert(ndActualArg.XmlType!.IsSubtypeOf(xmlTypeFormalArg), "Xml type of actual arg must be a subtype of the Xml type of the formal arg");
 
                // Use different conversion rules for internal Xslt libraries.  If the actual argument is
                // stored using Clr type T, then library must use type T, XPathItem, IList<T>, or IList<XPathItem>.
                // If the actual argument is stored using Clr type IList<T>, then library must use type
                // IList<T> or IList<XPathItem>.  This is to ensure that there will not be unnecessary
                // conversions that take place when calling into an internal library.
                if (ndName.NamespaceUri.Length == 0)
                {
                    Type itemType = GetItemStorageType(ndActualArg);
 
                    if (clrTypeFormalArg == XmlILMethods.StorageMethods[itemType].IListType)
                    {
                        // Formal type is IList<T>
                        NestedVisitEnsureStack(ndActualArg, itemType, true);
                    }
                    else if (clrTypeFormalArg == XmlILMethods.StorageMethods[typeof(XPathItem)].IListType)
                    {
                        // Formal type is IList<XPathItem>
                        NestedVisitEnsureStack(ndActualArg, typeof(XPathItem), true);
                    }
                    else if ((ndActualArg.XmlType.IsSingleton && clrTypeFormalArg == itemType) || ndActualArg.XmlType.TypeCode == XmlTypeCode.None)
                    {
                        // Formal type is T
                        NestedVisitEnsureStack(ndActualArg, clrTypeFormalArg, false);
                    }
                    else if (ndActualArg.XmlType.IsSingleton && clrTypeFormalArg == typeof(XPathItem))
                    {
                        // Formal type is XPathItem
                        NestedVisitEnsureStack(ndActualArg, typeof(XPathItem), false);
                    }
                    else
                        Debug.Fail($"Internal Xslt library may not use parameters of type {clrTypeFormalArg}");
                }
                else
                {
                    // There is an implicit upcast to the Xml type of the formal argument.  This can change the Clr storage type.
                    clrTypeActualArg = GetStorageType(xmlTypeFormalArg);
 
                    // If the formal Clr type is typeof(object) or if it is not a supertype of the actual Clr type, then call ChangeTypeXsltArgument
                    if (xmlTypeFormalArg.TypeCode == XmlTypeCode.Item || !clrTypeFormalArg.IsAssignableFrom(clrTypeActualArg))
                    {
                        // (clrTypeFormalArg) runtime.ChangeTypeXsltArgument(xmlTypeFormalArg, (object) value, clrTypeFormalArg);
                        _helper.LoadQueryRuntime();
                        _helper.LoadInteger(_helper.StaticData.DeclareXmlType(xmlTypeFormalArg));
                        NestedVisitEnsureStack(ndActualArg, GetItemStorageType(xmlTypeFormalArg), !xmlTypeFormalArg.IsSingleton);
                        _helper.TreatAs(clrTypeActualArg, typeof(object));
                        _helper.LoadType(clrTypeFormalArg);
                        _helper.Call(XmlILMethods.ChangeTypeXsltArg);
                        _helper.TreatAs(typeof(object), clrTypeFormalArg);
                    }
                    else
                    {
                        NestedVisitEnsureStack(ndActualArg, GetItemStorageType(xmlTypeFormalArg), !xmlTypeFormalArg.IsSingleton);
                    }
                }
            }
 
            // Invoke the target method
            _helper.Call(extFunc.Method);
 
            // Return value is on the stack; convert it to canonical ILGen storage type
            if (ndInvoke.XmlType!.IsEmpty)
            {
                _helper.Emit(OpCodes.Ldsfld, XmlILMethods.StorageMethods[typeof(XPathItem)].SeqEmpty);
            }
            else if (clrTypeRetSrc != clrTypeRetDst)
            {
                // (T) runtime.ChangeTypeXsltResult(idxType, (object) value);
                _helper.TreatAs(clrTypeRetSrc!, typeof(object));
                _helper.Call(XmlILMethods.ChangeTypeXsltResult);
                _helper.TreatAs(typeof(object), clrTypeRetDst);
            }
            else if (ndName.NamespaceUri.Length != 0 && !clrTypeRetSrc.IsValueType)
            {
                // Check for null if a user-defined extension function returns a reference type
                Label lblSkip = _helper.DefineLabel();
                _helper.Emit(OpCodes.Dup);
                _helper.Emit(OpCodes.Brtrue, lblSkip);
                _helper.LoadQueryRuntime();
                _helper.Emit(OpCodes.Ldstr, SR.Xslt_ItemNull);
                _helper.Call(XmlILMethods.ThrowException);
                _helper.MarkLabel(lblSkip);
            }
 
            _iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndInvoke), !ndInvoke.XmlType.IsSingleton);
 
            return ndInvoke;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XsltCopy.
        /// </summary>
        protected override QilNode VisitXsltCopy(QilBinary ndCopy)
        {
            Label lblSkipContent = _helper.DefineLabel();
            Debug.Assert(XmlILConstructInfo.Read(ndCopy).PushToWriterFirst);
 
            // if (!xwrtChk.StartCopyChk(navCopy)) goto LabelSkipContent;
            _helper.LoadQueryOutput();
 
            NestedVisitEnsureStack(ndCopy.Left);
            Debug.Assert(ndCopy.Left.XmlType!.IsNode);
 
            _helper.Call(XmlILMethods.StartCopy);
            _helper.Emit(OpCodes.Brfalse, lblSkipContent);
 
            // Recursively construct content
            NestedVisit(ndCopy.Right);
 
            // xwrtChk.EndCopyChk(navCopy);
            _helper.LoadQueryOutput();
 
            NestedVisitEnsureStack(ndCopy.Left);
            Debug.Assert(ndCopy.Left.XmlType.IsNode);
 
            _helper.Call(XmlILMethods.EndCopy);
 
            // LabelSkipContent:
            _helper.MarkLabel(lblSkipContent);
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndCopy;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XsltCopyOf.
        /// </summary>
        protected override QilNode VisitXsltCopyOf(QilUnary ndCopyOf)
        {
            Debug.Assert(XmlILConstructInfo.Read(ndCopyOf).PushToWriterFirst, "XsltCopyOf should always be pushed to writer.");
 
            _helper.LoadQueryOutput();
 
            // XmlQueryOutput.XsltCopyOf(navigator);
            NestedVisitEnsureStack(ndCopyOf.Child);
            _helper.Call(XmlILMethods.CopyOf);
 
            _iterCurr.Storage = StorageDescriptor.None();
            return ndCopyOf;
        }
 
        /// <summary>
        /// Generate code for QilNodeType.XsltConvert.
        /// </summary>
        protected override QilNode VisitXsltConvert(QilTargetType ndConv)
        {
            XmlQueryType typSrc, typDst;
            MethodInfo? meth;
 
            typSrc = ndConv.Source.XmlType!;
            typDst = ndConv.TargetType;
 
            if (GetXsltConvertMethod(typSrc, typDst, out meth))
            {
                NestedVisitEnsureStack(ndConv.Source);
            }
            else
            {
                // If a conversion could not be found, then convert the source expression to item or item* and try again
                NestedVisitEnsureStack(ndConv.Source, typeof(XPathItem), !typSrc.IsSingleton);
                if (!GetXsltConvertMethod(typSrc.IsSingleton ? TypeFactory.Item : TypeFactory.ItemS, typDst, out meth))
                    Debug.Fail($"Conversion from {ndConv.Source.XmlType} to {ndConv.TargetType} is not supported.");
            }
 
            // XsltConvert.XXXToYYY(value);
            if (meth != null)
                _helper.Call(meth);
 
            _iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(typDst), !typDst.IsSingleton);
            return ndConv;
        }
 
        /// <summary>
        /// Get the XsltConvert method that converts from "typSrc" to "typDst".  Return false if no
        /// such method exists.  This conversion matrix should match the one in XsltConvert.ExternalValueToExternalValue.
        /// </summary>
        private static bool GetXsltConvertMethod(XmlQueryType typSrc, XmlQueryType typDst, out MethodInfo? meth)
        {
            meth = null;
 
            // Note, Ref.Equals is OK to use here, since we will always fall back to Item or Item* in the
            // case where the source or destination types do not match the static types exposed on the
            // XmlQueryTypeFactory.  This is bad for perf if it accidentally occurs, but the results
            // should still be correct.
 
            // => xs:boolean
            if ((object)typDst == (object)TypeFactory.BooleanX)
            {
                if ((object)typSrc == (object)TypeFactory.Item) meth = XmlILMethods.ItemToBool;
                else if ((object)typSrc == (object)TypeFactory.ItemS) meth = XmlILMethods.ItemsToBool;
            }
            // => xs:dateTime
            else if ((object)typDst == (object)TypeFactory.DateTimeX)
            {
                if ((object)typSrc == (object)TypeFactory.StringX) meth = XmlILMethods.StrToDT;
            }
            // => xs:decimal
            else if ((object)typDst == (object)TypeFactory.DecimalX)
            {
                if ((object)typSrc == (object)TypeFactory.DoubleX) meth = XmlILMethods.DblToDec;
            }
            // => xs:double
            else if ((object)typDst == (object)TypeFactory.DoubleX)
            {
                if ((object)typSrc == (object)TypeFactory.DecimalX) meth = XmlILMethods.DecToDbl;
                else if ((object)typSrc == (object)TypeFactory.IntX) meth = XmlILMethods.IntToDbl;
                else if ((object)typSrc == (object)TypeFactory.Item) meth = XmlILMethods.ItemToDbl;
                else if ((object)typSrc == (object)TypeFactory.ItemS) meth = XmlILMethods.ItemsToDbl;
                else if ((object)typSrc == (object)TypeFactory.LongX) meth = XmlILMethods.LngToDbl;
                else if ((object)typSrc == (object)TypeFactory.StringX) meth = XmlILMethods.StrToDbl;
            }
            // => xs:int
            else if ((object)typDst == (object)TypeFactory.IntX)
            {
                if ((object)typSrc == (object)TypeFactory.DoubleX) meth = XmlILMethods.DblToInt;
            }
            // => xs:long
            else if ((object)typDst == (object)TypeFactory.LongX)
            {
                if ((object)typSrc == (object)TypeFactory.DoubleX) meth = XmlILMethods.DblToLng;
            }
            // => node
            else if ((object)typDst == (object)TypeFactory.NodeNotRtf)
            {
                if ((object)typSrc == (object)TypeFactory.Item) meth = XmlILMethods.ItemToNode;
                else if ((object)typSrc == (object)TypeFactory.ItemS) meth = XmlILMethods.ItemsToNode;
            }
            // => node*
            else if ((object)typDst == (object)TypeFactory.NodeSDod ||
                     (object)typDst == (object)TypeFactory.NodeNotRtfS)
            {
                if ((object)typSrc == (object)TypeFactory.Item) meth = XmlILMethods.ItemToNodes;
                else if ((object)typSrc == (object)TypeFactory.ItemS) meth = XmlILMethods.ItemsToNodes;
            }
            // => xs:string
            else if ((object)typDst == (object)TypeFactory.StringX)
            {
                if ((object)typSrc == (object)TypeFactory.DateTimeX) meth = XmlILMethods.DTToStr;
                else if ((object)typSrc == (object)TypeFactory.DoubleX) meth = XmlILMethods.DblToStr;
                else if ((object)typSrc == (object)TypeFactory.Item) meth = XmlILMethods.ItemToStr;
                else if ((object)typSrc == (object)TypeFactory.ItemS) meth = XmlILMethods.ItemsToStr;
            }
 
            return meth != null;
        }
 
 
        //-----------------------------------------------
        // Helper methods
        //-----------------------------------------------
 
        /// <summary>
        /// Ensure that the "locNav" navigator is positioned to the context node "ndCtxt".
        /// </summary>
        private void SyncToNavigator(LocalBuilder locNav, QilNode ndCtxt)
        {
            _helper.Emit(OpCodes.Ldloc, locNav);
            NestedVisitEnsureStack(ndCtxt);
            _helper.CallSyncToNavigator();
            _helper.Emit(OpCodes.Stloc, locNav);
        }
 
        /// <summary>
        /// Generate boiler-plate code to create a simple Xml iterator.
        /// </summary>
        /// <remarks>
        ///     Iterator iter;
        ///     iter.Create(navCtxt);
        /// LabelNext:
        ///     if (!iter.MoveNext())
        ///         goto LabelNextCtxt;
        /// </remarks>
        private void CreateSimpleIterator(QilNode ndCtxt, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext, MethodInfo methCurrent)
        {
            // Iterator iter;
            LocalBuilder locIter = _helper.DeclareLocal(iterName, iterType);
 
            // iter.Create(navCtxt);
            _helper.Emit(OpCodes.Ldloca, locIter);
            NestedVisitEnsureStack(ndCtxt);
            _helper.Call(methCreate);
 
            GenerateSimpleIterator(typeof(XPathNavigator), locIter, methNext, methCurrent);
        }
 
        /// <summary>
        /// Generate boiler-plate code to create an Xml iterator that uses an XmlNavigatorFilter to filter items.
        /// </summary>
        /// <remarks>
        ///     Iterator iter;
        ///     iter.Create(navCtxt, filter [, orSelf] [, navEnd]);
        /// LabelNext:
        ///     if (!iter.MoveNext())
        ///         goto LabelNextCtxt;
        /// </remarks>
        private void CreateFilteredIterator(QilNode ndCtxt, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext, MethodInfo methCurrent,
                                                XmlNodeKindFlags kinds, QilName? ndName, TriState orSelf, QilNode? ndEnd)
        {
            // Iterator iter;
            LocalBuilder locIter = _helper.DeclareLocal(iterName, iterType);
 
            // iter.Create(navCtxt, filter [, orSelf], [, navEnd]);
            _helper.Emit(OpCodes.Ldloca, locIter);
            NestedVisitEnsureStack(ndCtxt);
            LoadSelectFilter(kinds, ndName);
            if (orSelf != TriState.Unknown)
                _helper.LoadBoolean(orSelf == TriState.True);
            if (ndEnd != null)
                NestedVisitEnsureStack(ndEnd);
            _helper.Call(methCreate);
 
            GenerateSimpleIterator(typeof(XPathNavigator), locIter, methNext, methCurrent);
        }
 
        /// <summary>
        /// Generate boiler-plate code to create an Xml iterator that controls a nested iterator.
        /// </summary>
        /// <remarks>
        ///     Iterator iter;
        ///     iter.Create(filter [, orSelf]);
        ///         ...nested iterator...
        ///     navInput = nestedNested;
        ///     goto LabelCall;
        /// LabelNext:
        ///     navInput = null;
        /// LabelCall:
        ///     switch (iter.MoveNext(navInput)) {
        ///         case IteratorState.NoMoreNodes: goto LabelNextCtxt;
        ///         case IteratorState.NextInputNode: goto LabelNextNested;
        ///     }
        /// </remarks>
        private void CreateContainerIterator(QilUnary ndDod, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext, MethodInfo methCurrent,
                                                   XmlNodeKindFlags kinds, QilName? ndName, TriState orSelf)
        {
            // Iterator iter;
            LocalBuilder locIter = _helper.DeclareLocal(iterName, iterType);
            Label lblOnEndNested;
            QilLoop ndLoop = (QilLoop)ndDod.Child;
            Debug.Assert(ndDod.NodeType == QilNodeType.DocOrderDistinct && ndLoop != null);
 
            // iter.Create(filter [, orSelf]);
            _helper.Emit(OpCodes.Ldloca, locIter);
            LoadSelectFilter(kinds, ndName);
            if (orSelf != TriState.Unknown)
                _helper.LoadBoolean(orSelf == TriState.True);
            _helper.Call(methCreate);
 
            // Generate nested iterator (branch to lblOnEndNested when iteration is complete)
            lblOnEndNested = _helper.DefineLabel();
            StartNestedIterator(lblOnEndNested);
            StartBinding(ndLoop.Variable);
            EndBinding(ndLoop.Variable);
            EndNestedIterator(ndLoop.Variable);
            _iterCurr.Storage = _iterNested!.Storage;
 
            GenerateContainerIterator(ndDod, locIter, lblOnEndNested, methNext, methCurrent, typeof(XPathNavigator));
        }
 
        /// <summary>
        /// Generate boiler-plate code that calls MoveNext on a simple Xml iterator.  Iterator should have already been
        /// created by calling code.
        /// </summary>
        /// <remarks>
        ///     ...
        /// LabelNext:
        ///     if (!iter.MoveNext())
        ///         goto LabelNextCtxt;
        /// </remarks>
        private void GenerateSimpleIterator(Type itemStorageType, LocalBuilder locIter, MethodInfo methNext, MethodInfo methCurrent)
        {
            Label lblNext;
 
            // LabelNext:
            lblNext = _helper.DefineLabel();
            _helper.MarkLabel(lblNext);
 
            // if (!iter.MoveNext()) goto LabelNextCtxt;
            _helper.Emit(OpCodes.Ldloca, locIter);
            _helper.Call(methNext);
            _helper.Emit(OpCodes.Brfalse, _iterCurr.GetLabelNext());
 
            _iterCurr.SetIterator(lblNext, StorageDescriptor.Current(locIter, methCurrent, itemStorageType));
        }
 
        /// <summary>
        /// Generate boiler-plate code that calls MoveNext on an Xml iterator that controls a nested iterator.  Iterator should
        /// have already been created by calling code.
        /// </summary>
        /// <remarks>
        ///     ...
        ///     goto LabelCall;
        /// LabelNext:
        ///     navCtxt = null;
        /// LabelCall:
        ///     switch (iter.MoveNext(navCtxt)) {
        ///         case IteratorState.NoMoreNodes: goto LabelNextCtxt;
        ///         case IteratorState.NextInputNode: goto LabelNextNested;
        ///     }
        /// </remarks>
        private void GenerateContainerIterator(QilNode nd, LocalBuilder locIter, Label lblOnEndNested,
                                                       MethodInfo methNext, MethodInfo methCurrent, Type itemStorageType)
        {
            Label lblCall;
 
            // Define labels that will be used
            lblCall = _helper.DefineLabel();
 
            // iter.MoveNext(input);
            // goto LabelCall;
            _iterCurr.EnsureNoStackNoCache(nd.XmlType!.IsNode ? "$$$navInput" : "$$$itemInput");
            _helper.Emit(OpCodes.Ldloca, locIter);
            _iterCurr.PushValue();
            _helper.EmitUnconditionalBranch(OpCodes.Br, lblCall);
 
            // LabelNext:
            // iterSet.MoveNext(null);
            _helper.MarkLabel(lblOnEndNested);
            _helper.Emit(OpCodes.Ldloca, locIter);
            _helper.Emit(OpCodes.Ldnull);
 
            // LabelCall:
            // result = iter.MoveNext(input);
            _helper.MarkLabel(lblCall);
            _helper.Call(methNext);
 
            // If this iterator always returns a single node, then NoMoreNodes will never be returned
            if (nd.XmlType.IsSingleton)
            {
                // if (result == IteratorResult.NeedInputNode) goto LabelNextInput;
                _helper.LoadInteger((int)IteratorResult.NeedInputNode);
                _helper.Emit(OpCodes.Beq, _iterNested!.GetLabelNext());
 
                _iterCurr.Storage = StorageDescriptor.Current(locIter, methCurrent, itemStorageType);
            }
            else
            {
                // switch (iter.MoveNext(input)) {
                //      case IteratorResult.NoMoreNodes: goto LabelNextCtxt;
                //      case IteratorResult.NeedInputNode: goto LabelNextInput;
                // }
                _helper.Emit(OpCodes.Switch, new Label[] { _iterCurr.GetLabelNext(), _iterNested!.GetLabelNext() });
 
                _iterCurr.SetIterator(lblOnEndNested, StorageDescriptor.Current(locIter, methCurrent, itemStorageType));
            }
        }
 
        /// <summary>
        /// Load XmlQueryOutput, load a name (computed or literal) and load an index to an Xml schema type.
        /// Return an enumeration that specifies what kind of name was loaded.
        /// </summary>
        private GenerateNameType LoadNameAndType(XPathNodeType nodeType, QilNode ndName, bool isStart, bool callChk)
        {
            QilName ndLiteralName;
            string prefix, localName, ns;
            GenerateNameType nameType;
            Debug.Assert(ndName.XmlType!.TypeCode == XmlTypeCode.QName, "Element or attribute name must have QName type.");
 
            _helper.LoadQueryOutput();
 
            // 0. Default is to pop names off stack
            nameType = GenerateNameType.StackName;
 
            // 1. Literal names
            if (ndName.NodeType == QilNodeType.LiteralQName)
            {
                // If checks need to be made on End construction, then always pop names from stack
                if (isStart || !callChk)
                {
                    ndLiteralName = (ndName as QilName)!;
                    prefix = ndLiteralName.Prefix;
                    localName = ndLiteralName.LocalName;
                    ns = ndLiteralName.NamespaceUri;
 
                    // Check local name, namespace parts in debug code
                    Debug.Assert(ValidateNames.ValidateName(prefix, localName, ns, nodeType, ValidateNames.Flags.AllExceptPrefixMapping));
 
                    // If the namespace is empty,
                    if (ndLiteralName.NamespaceUri.Length == 0)
                    {
                        // Then always call method on XmlQueryOutput
                        _helper.Emit(OpCodes.Ldstr, ndLiteralName.LocalName);
                        return GenerateNameType.LiteralLocalName;
                    }
 
                    // If prefix is not valid for the node type,
                    if (!ValidateNames.ValidateName(prefix, localName, ns, nodeType, ValidateNames.Flags.CheckPrefixMapping))
                    {
                        // Then construct a new prefix at run-time
                        if (isStart)
                        {
                            _helper.Emit(OpCodes.Ldstr, localName);
                            _helper.Emit(OpCodes.Ldstr, ns);
                            _helper.Construct(XmlILConstructors.QName);
 
                            nameType = GenerateNameType.QName;
                        }
                    }
                    else
                    {
                        // Push string parts
                        _helper.Emit(OpCodes.Ldstr, prefix);
                        _helper.Emit(OpCodes.Ldstr, localName);
                        _helper.Emit(OpCodes.Ldstr, ns);
 
                        nameType = GenerateNameType.LiteralName;
                    }
                }
            }
            else
            {
                if (isStart)
                {
                    // 2. Copied names
                    if (ndName.NodeType == QilNodeType.NameOf)
                    {
                        // Preserve prefix of source node, so just push navigator onto stack
                        NestedVisitEnsureStack((ndName as QilUnary)!.Child);
                        nameType = GenerateNameType.CopiedName;
                    }
                    // 3. Parsed tag names (foo:bar)
                    else if (ndName.NodeType == QilNodeType.StrParseQName)
                    {
                        // Preserve prefix from parsed tag name
                        VisitStrParseQName((ndName as QilBinary)!, true);
 
                        // Type of name depends upon data-type of name argument
                        if ((ndName as QilBinary)!.Right.XmlType!.TypeCode == XmlTypeCode.String)
                            nameType = GenerateNameType.TagNameAndNamespace;
                        else
                            nameType = GenerateNameType.TagNameAndMappings;
                    }
                    // 4. Other computed qnames
                    else
                    {
                        // Push XmlQualifiedName onto the stack
                        NestedVisitEnsureStack(ndName);
                        nameType = GenerateNameType.QName;
                    }
                }
            }
 
            return nameType;
        }
 
        /// <summary>
        /// If the first argument is a constant value that evaluates to zero, then a more optimal instruction sequence
        /// can be generated that does not have to push the zero onto the stack.  Instead, a Brfalse or Brtrue instruction
        /// can be used.
        /// </summary>
        private bool TryZeroCompare(QilNodeType relOp, QilNode ndFirst, QilNode ndSecond)
        {
            Debug.Assert(relOp == QilNodeType.Eq || relOp == QilNodeType.Ne);
 
            switch (ndFirst.NodeType)
            {
                case QilNodeType.LiteralInt64:
                    if ((int)(QilLiteral)ndFirst != 0) return false;
                    break;
 
                case QilNodeType.LiteralInt32:
                    if ((int)(QilLiteral)ndFirst != 0) return false;
                    break;
 
                case QilNodeType.False:
                    break;
 
                case QilNodeType.True:
                    // Inverse of QilNodeType.False
                    relOp = (relOp == QilNodeType.Eq) ? QilNodeType.Ne : QilNodeType.Eq;
                    break;
 
                default:
                    return false;
            }
 
            // Generate code to push second argument on stack
            NestedVisitEnsureStack(ndSecond);
 
            // Generate comparison code -- op == 0 or op != 0
            ZeroCompare(relOp, ndSecond.XmlType!.TypeCode == XmlTypeCode.Boolean);
 
            return true;
        }
 
        /// <summary>
        /// If the comparison involves a qname, then perform comparison using atoms and return true.
        /// Otherwise, return false (caller will perform comparison).
        /// </summary>
        private bool TryNameCompare(QilNodeType relOp, QilNode ndFirst, QilNode ndSecond)
        {
            Debug.Assert(relOp == QilNodeType.Eq || relOp == QilNodeType.Ne);
 
            if (ndFirst.NodeType == QilNodeType.NameOf)
            {
                switch (ndSecond.NodeType)
                {
                    case QilNodeType.NameOf:
                    case QilNodeType.LiteralQName:
                        {
                            _helper.LoadQueryRuntime();
 
                            // Push left navigator onto the stack
                            NestedVisitEnsureStack((ndFirst as QilUnary)!.Child);
 
                            // Push the local name and namespace uri of the right argument onto the stack
                            if (ndSecond.NodeType == QilNodeType.LiteralQName)
                            {
                                QilName ndName = (ndSecond as QilName)!;
                                _helper.LoadInteger(_helper.StaticData.DeclareName(ndName.LocalName));
                                _helper.LoadInteger(_helper.StaticData.DeclareName(ndName.NamespaceUri));
 
                                // push runtime.IsQNameEqual(navigator, localName, namespaceUri)
                                _helper.Call(XmlILMethods.QNameEqualLit);
                            }
                            else
                            {
                                // Generate code to locate the navigator argument of NameOf operator
                                Debug.Assert(ndSecond.NodeType == QilNodeType.NameOf);
                                NestedVisitEnsureStack(ndSecond);
 
                                // push runtime.IsQNameEqual(nav1, nav2)
                                _helper.Call(XmlILMethods.QNameEqualNav);
                            }
 
                            // Branch based on boolean result or push boolean value
                            ZeroCompare((relOp == QilNodeType.Eq) ? QilNodeType.Ne : QilNodeType.Eq, true);
                            return true;
                        }
                }
            }
 
            // Caller must perform comparison
            return false;
        }
 
        /// <summary>
        /// For QilExpression types that map directly to CLR primitive types, the built-in CLR comparison operators can
        /// be used to perform the specified relational operation.
        /// </summary>
        private void ClrCompare(QilNodeType relOp, XmlTypeCode code)
        {
            OpCode opcode;
            Label lblTrue;
 
            switch (_iterCurr.CurrentBranchingContext)
            {
                case BranchingContext.OnFalse:
                    // Reverse the comparison operator
                    // Use Bxx_Un OpCodes to handle NaN case for double and single types
                    if (code == XmlTypeCode.Double || code == XmlTypeCode.Float)
                    {
                        switch (relOp)
                        {
                            case QilNodeType.Gt: opcode = OpCodes.Ble_Un; break;
                            case QilNodeType.Ge: opcode = OpCodes.Blt_Un; break;
                            case QilNodeType.Lt: opcode = OpCodes.Bge_Un; break;
                            case QilNodeType.Le: opcode = OpCodes.Bgt_Un; break;
                            case QilNodeType.Eq: opcode = OpCodes.Bne_Un; break;
                            case QilNodeType.Ne: opcode = OpCodes.Beq; break;
                            default: Debug.Fail($"Unexpected rel op {relOp}"); opcode = OpCodes.Nop; break;
                        }
                    }
                    else
                    {
                        switch (relOp)
                        {
                            case QilNodeType.Gt: opcode = OpCodes.Ble; break;
                            case QilNodeType.Ge: opcode = OpCodes.Blt; break;
                            case QilNodeType.Lt: opcode = OpCodes.Bge; break;
                            case QilNodeType.Le: opcode = OpCodes.Bgt; break;
                            case QilNodeType.Eq: opcode = OpCodes.Bne_Un; break;
                            case QilNodeType.Ne: opcode = OpCodes.Beq; break;
                            default: Debug.Fail($"Unexpected rel op {relOp}"); opcode = OpCodes.Nop; break;
                        }
                    }
                    _helper.Emit(opcode, _iterCurr.LabelBranch);
                    _iterCurr.Storage = StorageDescriptor.None();
                    break;
 
                case BranchingContext.OnTrue:
                    switch (relOp)
                    {
                        case QilNodeType.Gt: opcode = OpCodes.Bgt; break;
                        case QilNodeType.Ge: opcode = OpCodes.Bge; break;
                        case QilNodeType.Lt: opcode = OpCodes.Blt; break;
                        case QilNodeType.Le: opcode = OpCodes.Ble; break;
                        case QilNodeType.Eq: opcode = OpCodes.Beq; break;
                        case QilNodeType.Ne: opcode = OpCodes.Bne_Un; break;
                        default: Debug.Fail($"Unexpected rel op {relOp}"); opcode = OpCodes.Nop; break;
                    }
                    _helper.Emit(opcode, _iterCurr.LabelBranch);
                    _iterCurr.Storage = StorageDescriptor.None();
                    break;
 
                default:
                    Debug.Assert(_iterCurr.CurrentBranchingContext == BranchingContext.None);
                    switch (relOp)
                    {
                        case QilNodeType.Gt: _helper.Emit(OpCodes.Cgt); break;
                        case QilNodeType.Lt: _helper.Emit(OpCodes.Clt); break;
                        case QilNodeType.Eq: _helper.Emit(OpCodes.Ceq); break;
                        default:
                            switch (relOp)
                            {
                                case QilNodeType.Ge: opcode = OpCodes.Bge_S; break;
                                case QilNodeType.Le: opcode = OpCodes.Ble_S; break;
                                case QilNodeType.Ne: opcode = OpCodes.Bne_Un_S; break;
                                default: Debug.Fail($"Unexpected rel op {relOp}"); opcode = OpCodes.Nop; break;
                            }
 
                            // Push "true" if comparison succeeds, "false" otherwise
                            lblTrue = _helper.DefineLabel();
                            _helper.Emit(opcode, lblTrue);
                            _helper.ConvBranchToBool(lblTrue, true);
                            break;
                    }
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
                    break;
            }
        }
 
        /// <summary>
        /// Generate code to compare the top stack value to 0 by using the Brfalse or Brtrue instructions,
        /// which avoid pushing zero onto the stack.  Both of these instructions test for null/zero/false.
        /// </summary>
        private void ZeroCompare(QilNodeType relOp, bool isBoolVal)
        {
            Label lblTrue;
            Debug.Assert(relOp == QilNodeType.Eq || relOp == QilNodeType.Ne);
 
            // Test to determine if top stack value is zero (if relOp is Eq) or is not zero (if relOp is Ne)
            switch (_iterCurr.CurrentBranchingContext)
            {
                case BranchingContext.OnTrue:
                    // If relOp is Eq, jump to true label if top value is zero (Brfalse)
                    // If relOp is Ne, jump to true label if top value is non-zero (Brtrue)
                    _helper.Emit((relOp == QilNodeType.Eq) ? OpCodes.Brfalse : OpCodes.Brtrue, _iterCurr.LabelBranch);
                    _iterCurr.Storage = StorageDescriptor.None();
                    break;
 
                case BranchingContext.OnFalse:
                    // If relOp is Eq, jump to false label if top value is non-zero (Brtrue)
                    // If relOp is Ne, jump to false label if top value is zero (Brfalse)
                    _helper.Emit((relOp == QilNodeType.Eq) ? OpCodes.Brtrue : OpCodes.Brfalse, _iterCurr.LabelBranch);
                    _iterCurr.Storage = StorageDescriptor.None();
                    break;
 
                default:
                    Debug.Assert(_iterCurr.CurrentBranchingContext == BranchingContext.None);
 
                    // Since (boolval != 0) = boolval, value on top of the stack is already correct
                    if (!isBoolVal || relOp == QilNodeType.Eq)
                    {
                        // If relOp is Eq, push "true" if top value is zero, "false" otherwise
                        // If relOp is Ne, push "true" if top value is non-zero, "false" otherwise
                        lblTrue = _helper.DefineLabel();
                        _helper.Emit((relOp == QilNodeType.Eq) ? OpCodes.Brfalse : OpCodes.Brtrue, lblTrue);
                        _helper.ConvBranchToBool(lblTrue, true);
                    }
 
                    _iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
                    break;
            }
        }
 
        /// <summary>
        /// Construction within a loop is starting.  If transition from non-Any to Any state occurs, then ensure
        /// that runtime state will be set.
        /// </summary>
        private void StartWriterLoop(QilNode nd, out bool hasOnEnd, out Label lblOnEnd)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(nd);
 
            // By default, do not create a new iteration label
            hasOnEnd = false;
            lblOnEnd = default;
 
            // If loop is not involved in Xml construction, or if loop returns exactly one value, then do nothing
            if (!info.PushToWriterLast || nd.XmlType!.IsSingleton)
                return;
 
            if (!_iterCurr.HasLabelNext)
            {
                // Iterate until all items are constructed
                hasOnEnd = true;
                lblOnEnd = _helper.DefineLabel();
                _iterCurr.SetIterator(lblOnEnd, StorageDescriptor.None());
            }
        }
 
        /// <summary>
        /// Construction within a loop is ending.  If transition from non-Any to Any state occurs, then ensure that
        /// runtime state will be set.
        /// </summary>
        private void EndWriterLoop(QilNode nd, bool hasOnEnd, Label lblOnEnd)
        {
            XmlILConstructInfo info = XmlILConstructInfo.Read(nd);
 
            // If loop is not involved in Xml construction, then do nothing
            if (!info.PushToWriterLast)
                return;
 
            // Since results of construction were pushed to writer, there are no values to return
            _iterCurr.Storage = StorageDescriptor.None();
 
            // If loop returns exactly one value, then do nothing further
            if (nd.XmlType!.IsSingleton)
                return;
 
            if (hasOnEnd)
            {
                // Loop over all items in the list, sending each to the output writer
                _iterCurr.LoopToEnd(lblOnEnd);
            }
        }
 
        /// <summary>
        /// Returns true if the specified node's owner element might have local namespaces added to it
        /// after attributes have already been added.
        /// </summary>
        private static bool MightHaveNamespacesAfterAttributes(XmlILConstructInfo? info)
        {
            // Get parent element
            if (info != null)
                info = info.ParentElementInfo;
 
            // If a parent element has not been statically identified, then assume that the runtime
            // element will have namespaces added after attributes.
            if (info == null)
                return true;
 
            return info.MightHaveNamespacesAfterAttributes;
        }
 
        /// <summary>
        /// Returns true if the specified element should cache attributes.
        /// </summary>
        private static bool ElementCachesAttributes(XmlILConstructInfo info)
        {
            // Attributes will be cached if namespaces might be constructed after the attributes
            return info.MightHaveDuplicateAttributes || info.MightHaveNamespacesAfterAttributes;
        }
 
        /// <summary>
        /// This method is called before calling any WriteEnd??? method.  It generates code to perform runtime
        /// construction checks separately.  This should only be called if the XmlQueryOutput::StartElementChk
        /// method will *not* be called.
        /// </summary>
        private void BeforeStartChecks(QilNode ndCtor)
        {
            switch (XmlILConstructInfo.Read(ndCtor).InitialStates)
            {
                case PossibleXmlStates.WithinSequence:
                    // If runtime state is guaranteed to be WithinSequence, then call XmlQueryOutput.StartTree
                    _helper.CallStartTree(QilConstructorToNodeType(ndCtor.NodeType));
                    break;
 
                case PossibleXmlStates.EnumAttrs:
                    switch (ndCtor.NodeType)
                    {
                        case QilNodeType.ElementCtor:
                        case QilNodeType.TextCtor:
                        case QilNodeType.RawTextCtor:
                        case QilNodeType.PICtor:
                        case QilNodeType.CommentCtor:
                            // If runtime state is guaranteed to be EnumAttrs, and content is being constructed, call
                            // XmlQueryOutput.StartElementContent
                            _helper.CallStartElementContent();
                            break;
                    }
                    break;
            }
        }
 
        /// <summary>
        /// This method is called after calling any WriteEnd??? method.  It generates code to perform runtime
        /// construction checks separately.  This should only be called if the XmlQueryOutput::EndElementChk
        /// method will *not* be called.
        /// </summary>
        private void AfterEndChecks(QilNode ndCtor)
        {
            if (XmlILConstructInfo.Read(ndCtor).FinalStates == PossibleXmlStates.WithinSequence)
            {
                // If final runtime state is guaranteed to be WithinSequence, then call XmlQueryOutput.StartTree
                _helper.CallEndTree();
            }
        }
 
        /// <summary>
        /// Return true if a runtime check needs to be made in order to transition into the WithinContent state.
        /// </summary>
        private static bool CheckWithinContent(XmlILConstructInfo info)
        {
            switch (info.InitialStates)
            {
                case PossibleXmlStates.WithinSequence:
                case PossibleXmlStates.EnumAttrs:
                case PossibleXmlStates.WithinContent:
                    // Transition to WithinContent can be ensured at compile-time
                    return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Return true if a runtime check needs to be made in order to transition into the EnumAttrs state.
        /// </summary>
        private static bool CheckEnumAttrs(XmlILConstructInfo info)
        {
            switch (info.InitialStates)
            {
                case PossibleXmlStates.WithinSequence:
                case PossibleXmlStates.EnumAttrs:
                    // Transition to EnumAttrs can be ensured at compile-time
                    return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Map the XmlNodeKindFlags enumeration into the XPathNodeType enumeration.
        /// </summary>
        private static XPathNodeType QilXmlToXPathNodeType(XmlNodeKindFlags xmlTypes)
        {
            switch (xmlTypes)
            {
                case XmlNodeKindFlags.Element: return XPathNodeType.Element;
                case XmlNodeKindFlags.Attribute: return XPathNodeType.Attribute;
                case XmlNodeKindFlags.Text: return XPathNodeType.Text;
                case XmlNodeKindFlags.Comment: return XPathNodeType.Comment;
            }
            Debug.Assert(xmlTypes == XmlNodeKindFlags.PI);
            return XPathNodeType.ProcessingInstruction;
        }
 
        /// <summary>
        /// Map a QilExpression constructor type into the XPathNodeType enumeration.
        /// </summary>
        private static XPathNodeType QilConstructorToNodeType(QilNodeType typ)
        {
            switch (typ)
            {
                case QilNodeType.DocumentCtor: return XPathNodeType.Root;
                case QilNodeType.ElementCtor: return XPathNodeType.Element;
                case QilNodeType.TextCtor: return XPathNodeType.Text;
                case QilNodeType.RawTextCtor: return XPathNodeType.Text;
                case QilNodeType.PICtor: return XPathNodeType.ProcessingInstruction;
                case QilNodeType.CommentCtor: return XPathNodeType.Comment;
                case QilNodeType.AttributeCtor: return XPathNodeType.Attribute;
                case QilNodeType.NamespaceDecl: return XPathNodeType.Namespace;
            }
 
            Debug.Fail($"Cannot map QilNodeType {typ} to an XPathNodeType");
            return XPathNodeType.All;
        }
 
        /// <summary>
        /// Load an XmlNavigatorFilter that matches only the specified name and types onto the stack.
        /// </summary>
        private void LoadSelectFilter(XmlNodeKindFlags xmlTypes, QilName? ndName)
        {
            if (ndName != null)
            {
                // Push NameFilter
                Debug.Assert(xmlTypes == XmlNodeKindFlags.Element);
                _helper.CallGetNameFilter(_helper.StaticData.DeclareNameFilter(ndName.LocalName, ndName.NamespaceUri));
            }
            else
            {
                // Either type cannot be a union, or else it must be >= union of all Content types
                bool isXmlTypeUnion = IsNodeTypeUnion(xmlTypes);
                Debug.Assert(!isXmlTypeUnion || (xmlTypes & XmlNodeKindFlags.Content) == XmlNodeKindFlags.Content);
 
                if (isXmlTypeUnion)
                {
                    if ((xmlTypes & XmlNodeKindFlags.Attribute) != 0)
                    {
                        // Union includes attributes, so allow all node kinds
                        _helper.CallGetTypeFilter(XPathNodeType.All);
                    }
                    else
                    {
                        // Filter attributes
                        _helper.CallGetTypeFilter(XPathNodeType.Attribute);
                    }
                }
                else
                {
                    // Filter nodes of all but one type
                    _helper.CallGetTypeFilter(QilXmlToXPathNodeType(xmlTypes));
                }
            }
        }
 
        /// <summary>
        /// Return true if more than one node type is set.
        /// </summary>
        private static bool IsNodeTypeUnion(XmlNodeKindFlags xmlTypes)
        {
            return ((int)xmlTypes & ((int)xmlTypes - 1)) != 0;
        }
 
        /// <summary>
        /// Start construction of a new nested iterator.  If this.iterCurr == null, then the new iterator
        /// is a top-level, or root iterator.  Otherwise, the new iterator will be nested within the
        /// current iterator.
        /// </summary>
        [MemberNotNull(nameof(_iterCurr))]
        private void StartNestedIterator()
        {
            IteratorDescriptor? iterParent = _iterCurr;
 
            // Create a new, nested iterator
            if (iterParent == null)
            {
                // Create a "root" iterator info that has no parernt
                _iterCurr = new IteratorDescriptor(_helper);
            }
            else
            {
                // Create a nested iterator
                _iterCurr = new IteratorDescriptor(iterParent);
            }
 
            _iterNested = null;
        }
 
        /// <summary>
        /// Calls StartNestedIterator(nd) and also sets up the nested iterator to branch to "lblOnEnd" when iteration
        /// is complete.
        /// </summary>
        private void StartNestedIterator(Label lblOnEnd)
        {
            StartNestedIterator();
            _iterCurr.SetIterator(lblOnEnd, StorageDescriptor.None());
        }
 
        /// <summary>
        /// End construction of the current iterator.
        /// </summary>
        private void EndNestedIterator(QilNode nd)
        {
            Debug.Assert(_iterCurr.Storage.Location == ItemLocation.None ||
                         _iterCurr.Storage.ItemStorageType == GetItemStorageType(nd) ||
                         _iterCurr.Storage.ItemStorageType == typeof(XPathItem) ||
                         nd.XmlType!.TypeCode == XmlTypeCode.None,
                         $"QilNodeType {nd.NodeType} cannot be stored using type {_iterCurr.Storage.ItemStorageType}."
            );
 
            // If the nested iterator was constructed in branching mode,
            if (_iterCurr.IsBranching)
            {
                // Then if branching hasn't already taken place, do so now
                if (_iterCurr.Storage.Location != ItemLocation.None)
                {
                    _iterCurr.EnsureItemStorageType(nd.XmlType!, typeof(bool));
                    _iterCurr.EnsureStackNoCache();
 
                    if (_iterCurr.CurrentBranchingContext == BranchingContext.OnTrue)
                        _helper.Emit(OpCodes.Brtrue, _iterCurr.LabelBranch);
                    else
                        _helper.Emit(OpCodes.Brfalse, _iterCurr.LabelBranch);
 
                    _iterCurr.Storage = StorageDescriptor.None();
                }
            }
 
            // Save current iterator as nested iterator
            _iterNested = _iterCurr;
 
            // Update current iterator to be parent iterator
            _iterCurr = _iterCurr.ParentIterator!;
        }
 
        /// <summary>
        /// Recursively generate code to iterate over the results of the "nd" expression.  If "nd" is pushed
        /// to the writer, then there are no results.  If "nd" is a singleton expression and isCached is false,
        /// then generate code to construct the singleton.  Otherwise, cache the sequence in an XmlQuerySequence
        /// object.  Ensure that all items are converted to the specified "itemStorageType".
        /// </summary>
        private void NestedVisit(QilNode nd, Type itemStorageType, bool isCached)
        {
            if (XmlILConstructInfo.Read(nd).PushToWriterLast)
            {
                // Push results to output, so nothing is left to store
                StartNestedIterator();
                Visit(nd);
                EndNestedIterator(nd);
                _iterCurr.Storage = StorageDescriptor.None();
            }
            else if (!isCached && nd.XmlType!.IsSingleton)
            {
                // Storage of result will be a non-cached singleton
                StartNestedIterator();
                Visit(nd);
                _iterCurr.EnsureNoCache();
                _iterCurr.EnsureItemStorageType(nd.XmlType, itemStorageType);
                EndNestedIterator(nd);
                _iterCurr.Storage = _iterNested!.Storage;
            }
            else
            {
                NestedVisitEnsureCache(nd, itemStorageType);
            }
        }
 
        /// <summary>
        /// Calls NestedVisit(QilNode, Type, bool), storing result in the default storage type for "nd".
        /// </summary>
        private void NestedVisit(QilNode nd)
        {
            NestedVisit(nd, GetItemStorageType(nd), !nd.XmlType!.IsSingleton);
        }
 
        /// <summary>
        /// Recursively generate code to iterate over the results of the "nd" expression.  When the expression
        /// has been fully iterated, it will jump to "lblOnEnd".
        /// </summary>
        private void NestedVisit(QilNode nd, Label lblOnEnd)
        {
            Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
            StartNestedIterator(lblOnEnd);
            Visit(nd);
            _iterCurr.EnsureNoCache();
            _iterCurr.EnsureItemStorageType(nd.XmlType!, GetItemStorageType(nd));
            EndNestedIterator(nd);
            _iterCurr.Storage = _iterNested!.Storage;
        }
 
        /// <summary>
        /// Call NestedVisit(QilNode) and ensure that result is pushed onto the IL stack.
        /// </summary>
        private void NestedVisitEnsureStack(QilNode nd)
        {
            Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
            NestedVisit(nd);
            _iterCurr.EnsureStack();
        }
 
        /// <summary>
        /// Generate code for both QilExpression nodes and ensure that each result is pushed onto the IL stack.
        /// </summary>
        private void NestedVisitEnsureStack(QilNode ndLeft, QilNode ndRight)
        {
            NestedVisitEnsureStack(ndLeft);
            NestedVisitEnsureStack(ndRight);
        }
 
        /// <summary>
        /// Call NestedVisit(QilNode, Type, bool) and ensure that result is pushed onto the IL stack.
        /// </summary>
        private void NestedVisitEnsureStack(QilNode nd, Type itemStorageType, bool isCached)
        {
            Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
            NestedVisit(nd, itemStorageType, isCached);
            _iterCurr.EnsureStack();
        }
 
        /// <summary>
        /// Call NestedVisit(QilNode) and ensure that result is stored in local variable "loc".
        /// </summary>
        private void NestedVisitEnsureLocal(QilNode nd, LocalBuilder loc)
        {
            Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
            NestedVisit(nd);
            _iterCurr.EnsureLocal(loc);
        }
 
        /// <summary>
        /// Start a nested iterator in a branching context and recursively generate code for the specified QilExpression node.
        /// </summary>
        private void NestedVisitWithBranch(QilNode nd, BranchingContext brctxt, Label lblBranch)
        {
            Debug.Assert(nd.XmlType!.IsSingleton && !XmlILConstructInfo.Read(nd).PushToWriterLast);
            StartNestedIterator();
            _iterCurr.SetBranching(brctxt, lblBranch);
            Visit(nd);
            EndNestedIterator(nd);
            _iterCurr.Storage = StorageDescriptor.None();
        }
 
        /// <summary>
        /// Generate code for the QilExpression node and ensure that results are fully cached as an XmlQuerySequence.  All results
        /// should be converted to "itemStorageType" before being added to the cache.
        /// </summary>
        private void NestedVisitEnsureCache(QilNode nd, Type itemStorageType)
        {
            Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
            bool cachesResult = CachesResult(nd);
            LocalBuilder locCache;
            Label lblOnEnd = _helper.DefineLabel();
            Type cacheType;
            XmlILStorageMethods methods;
 
            // If bound expression will already be cached correctly, then don't create an XmlQuerySequence
            if (cachesResult)
            {
                StartNestedIterator();
                Visit(nd);
                EndNestedIterator(nd);
                _iterCurr.Storage = _iterNested!.Storage;
                Debug.Assert(_iterCurr.Storage.IsCached, "Expression result should be cached.  CachesResult() might have a bug in it.");
 
                // If type of items in the cache matches "itemStorageType", then done
                if (_iterCurr.Storage.ItemStorageType == itemStorageType)
                    return;
 
                // If the cache has navigators in it, or if converting to a cache of navigators, then EnsureItemStorageType
                // can directly convert without needing to create a new cache.
                if (_iterCurr.Storage.ItemStorageType == typeof(XPathNavigator) || itemStorageType == typeof(XPathNavigator))
                {
                    _iterCurr.EnsureItemStorageType(nd.XmlType!, itemStorageType);
                    return;
                }
 
                _iterCurr.EnsureNoStack("$$$cacheResult");
            }
 
            // Always store navigators in XmlQueryNodeSequence (which implements IList<XPathItem>)
            cacheType = (GetItemStorageType(nd) == typeof(XPathNavigator)) ? typeof(XPathNavigator) : itemStorageType;
 
            // XmlQuerySequence<T> cache;
            methods = XmlILMethods.StorageMethods[cacheType];
            locCache = _helper.DeclareLocal("$$$cache", methods.SeqType);
            _helper.Emit(OpCodes.Ldloc, locCache);
 
            // Special case non-navigator singletons to use overload of CreateOrReuse
            if (nd.XmlType!.IsSingleton)
            {
                // cache = XmlQuerySequence.CreateOrReuse(cache, item);
                NestedVisitEnsureStack(nd, cacheType, false);
                _helper.Call(methods.SeqReuseSgl);
                _helper.Emit(OpCodes.Stloc, locCache);
            }
            else
            {
                // XmlQuerySequence<T> cache;
                // cache = XmlQuerySequence.CreateOrReuse(cache);
                _helper.Call(methods.SeqReuse);
                _helper.Emit(OpCodes.Stloc, locCache);
                _helper.Emit(OpCodes.Ldloc, locCache);
 
                StartNestedIterator(lblOnEnd);
 
                if (cachesResult)
                    _iterCurr.Storage = _iterCurr.ParentIterator!.Storage;
                else
                    Visit(nd);
 
                // cache.Add(item);
                _iterCurr.EnsureItemStorageType(nd.XmlType, cacheType);
                _iterCurr.EnsureStackNoCache();
                _helper.Call(methods.SeqAdd);
                _helper.Emit(OpCodes.Ldloc, locCache);
 
                // }
                _iterCurr.LoopToEnd(lblOnEnd);
 
                EndNestedIterator(nd);
 
                // Remove cache reference from stack
                _helper.Emit(OpCodes.Pop);
            }
 
            _iterCurr.Storage = StorageDescriptor.Local(locCache, itemStorageType, true);
        }
 
        /// <summary>
        /// Returns true if the specified QilExpression node type is *guaranteed* to cache its results in an XmlQuerySequence,
        /// where items in the cache are stored using the default storage type.
        /// </summary>
        private static bool CachesResult(QilNode nd)
        {
            OptimizerPatterns patt;
 
            switch (nd.NodeType)
            {
                case QilNodeType.Let:
                case QilNodeType.Parameter:
                case QilNodeType.Invoke:
                case QilNodeType.XsltInvokeLateBound:
                case QilNodeType.XsltInvokeEarlyBound:
                    return !nd.XmlType!.IsSingleton;
 
                case QilNodeType.Filter:
                    // EqualityIndex pattern caches results
                    patt = OptimizerPatterns.Read(nd);
                    return patt.MatchesPattern(OptimizerPatternName.EqualityIndex);
 
                case QilNodeType.DocOrderDistinct:
                    if (nd.XmlType!.IsSingleton)
                        return false;
 
                    // JoinAndDod and DodReverse patterns don't cache results
                    patt = OptimizerPatterns.Read(nd);
                    return !patt.MatchesPattern(OptimizerPatternName.JoinAndDod) && !patt.MatchesPattern(OptimizerPatternName.DodReverse);
 
                case QilNodeType.TypeAssert:
                    QilTargetType ndTypeAssert = (QilTargetType)nd;
                    // Check if TypeAssert would be no-op
                    return CachesResult(ndTypeAssert.Source) && GetItemStorageType(ndTypeAssert.Source) == GetItemStorageType(ndTypeAssert);
            }
 
            return false;
        }
 
        /// <summary>
        /// Shortcut call to XmlILTypeHelper.GetStorageType.
        /// </summary>
        private static Type GetStorageType(QilNode nd)
        {
            return XmlILTypeHelper.GetStorageType(nd.XmlType!);
        }
 
        /// <summary>
        /// Shortcut call to XmlILTypeHelper.GetStorageType.
        /// </summary>
        private static Type GetStorageType(XmlQueryType typ)
        {
            return XmlILTypeHelper.GetStorageType(typ);
        }
 
        /// <summary>
        /// Shortcut call to XmlILTypeHelper.GetStorageType, using an expression's prime type.
        /// </summary>
        private static Type GetItemStorageType(QilNode nd)
        {
            return XmlILTypeHelper.GetStorageType(nd.XmlType!.Prime);
        }
 
        /// <summary>
        /// Shortcut call to XmlILTypeHelper.GetStorageType, using the prime type.
        /// </summary>
        private static Type GetItemStorageType(XmlQueryType typ)
        {
            return XmlILTypeHelper.GetStorageType(typ.Prime);
        }
    }
}