1 //------------------------------------------------------------------------------
2 // <copyright file="XmlIlVisitor.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
9 using System.Xml.XPath;
10 using System.Xml.Schema;
11 using System.Globalization;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.Diagnostics;
15 using System.Reflection;
16 using System.Reflection.Emit;
18 using System.Xml.Xsl.Qil;
19 using System.Xml.Xsl.Runtime;
21 namespace System.Xml.Xsl.IlGen {
22 using TypeFactory = System.Xml.Xsl.XmlQueryTypeFactory;
23 using Res = System.Xml.Utils.Res;
26 /// Creates Msil code for an entire QilExpression graph. Code is generated in one of two modes: push or
27 /// pull. In push mode, code is generated to push the values in an iterator to the XmlWriter
28 /// interface. In pull mode, the values in an iterator are stored in a physical location such as
29 /// the stack or a local variable by an iterator. The iterator is passive, and will just wait for
30 /// a caller to pull the data and/or instruct the iterator to enumerate the next value.
32 internal class XmlILVisitor : QilVisitor {
33 private QilExpression qil;
34 private GenerateHelper helper;
35 private IteratorDescriptor iterCurr, iterNested;
39 //-----------------------------------------------
41 //-----------------------------------------------
44 /// Visits the specified QilExpression graph and generates MSIL code.
46 public void Visit(QilExpression qil, GenerateHelper helper, MethodInfo methRoot) {
49 this.iterNested = null;
52 // Prepare each global parameter and global variable to be visited
53 PrepareGlobalValues(qil.GlobalParameterList);
54 PrepareGlobalValues(qil.GlobalVariableList);
56 // Visit each global parameter and global variable
57 VisitGlobalValues(qil.GlobalParameterList);
58 VisitGlobalValues(qil.GlobalVariableList);
60 // Build each function
61 foreach (QilFunction ndFunc in qil.FunctionList) {
62 // Visit each parameter and the function body
66 // Build the root expression
67 this.helper.MethodBegin(methRoot, null, true);
68 StartNestedIterator(qil.Root);
70 Debug.Assert(this.iterCurr.Storage.Location == ItemLocation.None, "Root expression should have been pushed to the writer.");
71 EndNestedIterator(qil.Root);
72 this.helper.MethodEnd();
76 /// Create IteratorDescriptor for each global value. This pre-visit is necessary because a global early
77 /// in the list may reference a global later in the list and therefore expect its IteratorDescriptor to already
80 private void PrepareGlobalValues(QilList globalIterators) {
81 MethodInfo methGlobal;
82 IteratorDescriptor iterInfo;
84 foreach (QilIterator iter in globalIterators) {
85 Debug.Assert(iter.NodeType == QilNodeType.Let || iter.NodeType == QilNodeType.Parameter);
87 // Get metadata for method which computes this global's value
88 methGlobal = XmlILAnnotation.Write(iter).FunctionBinding;
89 Debug.Assert(methGlobal != null, "Metadata for global value should have already been computed");
91 // Create an IteratorDescriptor for this global value
92 iterInfo = new IteratorDescriptor(this.helper);
94 // Iterator items will be stored in a global location
95 iterInfo.Storage = StorageDescriptor.Global(methGlobal, GetItemStorageType(iter), !iter.XmlType.IsSingleton);
97 // Associate IteratorDescriptor with parameter
98 XmlILAnnotation.Write(iter).CachedIteratorDescriptor = iterInfo;
103 /// Visit each global variable or parameter. Create a IteratorDescriptor for each global value. Generate code for
106 private void VisitGlobalValues(QilList globalIterators) {
107 MethodInfo methGlobal;
108 Label lblGetGlobal, lblComputeGlobal;
112 foreach (QilIterator iter in globalIterators) {
113 QilParameter param = iter as QilParameter;
115 // Get MethodInfo for method that computes the value of this global
116 methGlobal = XmlILAnnotation.Write(iter).CachedIteratorDescriptor.Storage.GlobalLocation;
117 isCached = !iter.XmlType.IsSingleton;
119 // Notify the StaticDataManager of the new global value
120 idxValue = this.helper.StaticData.DeclareGlobalValue(iter.DebugName);
122 // Generate code for this method
123 this.helper.MethodBegin(methGlobal, iter.SourceLine, false);
125 lblGetGlobal = this.helper.DefineLabel();
126 lblComputeGlobal = this.helper.DefineLabel();
128 // if (runtime.IsGlobalComputed(idx)) goto LabelGetGlobal;
129 this.helper.LoadQueryRuntime();
130 this.helper.LoadInteger(idxValue);
131 this.helper.Call(XmlILMethods.GlobalComputed);
132 this.helper.Emit(OpCodes.Brtrue, lblGetGlobal);
134 // Compute value of global value
135 StartNestedIterator(iter);
138 Debug.Assert(iter.XmlType == TypeFactory.ItemS, "IlGen currently only supports parameters of type item*.");
140 // param = runtime.ExternalContext.GetParameter(localName, namespaceUri);
141 // if (param == null) goto LabelComputeGlobal;
142 LocalBuilder locParam = this.helper.DeclareLocal("$$$param", typeof(object));
143 this.helper.CallGetParameter(param.Name.LocalName, param.Name.NamespaceUri);
144 this.helper.Emit(OpCodes.Stloc, locParam);
145 this.helper.Emit(OpCodes.Ldloc, locParam);
146 this.helper.Emit(OpCodes.Brfalse, lblComputeGlobal);
148 // runtime.SetGlobalValue(idxValue, runtime.ChangeTypeXsltResult(idxType, value));
149 // Ensure that the storage type of the parameter corresponds to static type
150 this.helper.LoadQueryRuntime();
151 this.helper.LoadInteger(idxValue);
153 this.helper.LoadQueryRuntime();
154 this.helper.LoadInteger(this.helper.StaticData.DeclareXmlType(XmlQueryTypeFactory.ItemS));
155 this.helper.Emit(OpCodes.Ldloc, locParam);
156 this.helper.Call(XmlILMethods.ChangeTypeXsltResult);
158 this.helper.CallSetGlobalValue(typeof(object));
160 // goto LabelGetGlobal;
161 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblGetGlobal);
164 // LabelComputeGlobal:
165 this.helper.MarkLabel(lblComputeGlobal);
167 if (iter.Binding != null) {
168 // runtime.SetGlobalValue(idxValue, (object) value);
169 this.helper.LoadQueryRuntime();
170 this.helper.LoadInteger(idxValue);
172 // Compute value of global value
173 NestedVisitEnsureStack(iter.Binding, GetItemStorageType(iter), isCached);
175 this.helper.CallSetGlobalValue(GetStorageType(iter));
178 // Throw exception, as there is no default value for this parameter
179 // XmlQueryRuntime.ThrowException("...");
180 Debug.Assert(iter.NodeType == QilNodeType.Parameter, "Only parameters may not have a default value");
181 this.helper.LoadQueryRuntime();
182 this.helper.Emit(OpCodes.Ldstr, Res.GetString(Res.XmlIl_UnknownParam, new string[] {param.Name.LocalName, param.Name.NamespaceUri}));
183 this.helper.Call(XmlILMethods.ThrowException);
186 EndNestedIterator(iter);
189 // return (T) runtime.GetGlobalValue(idxValue);
190 this.helper.MarkLabel(lblGetGlobal);
191 this.helper.CallGetGlobalValue(idxValue, GetStorageType(iter));
193 this.helper.MethodEnd();
198 /// Generate code for the specified function.
200 private void Function(QilFunction ndFunc) {
203 IteratorDescriptor iterInfo;
206 // Annotate each function parameter with a IteratorDescriptor
207 foreach (QilIterator iter in ndFunc.Arguments) {
208 Debug.Assert(iter.NodeType == QilNodeType.Parameter);
210 // Create an IteratorDescriptor for this parameter
211 iterInfo = new IteratorDescriptor(this.helper);
213 // Add one to parameter index, as 0th parameter is always "this"
214 paramId = XmlILAnnotation.Write(iter).ArgumentPosition + 1;
216 // The ParameterInfo for each argument should be set as its location
217 iterInfo.Storage = StorageDescriptor.Parameter(paramId, GetItemStorageType(iter), !iter.XmlType.IsSingleton);
219 // Associate IteratorDescriptor with Let iterator
220 XmlILAnnotation.Write(iter).CachedIteratorDescriptor = iterInfo;
223 methFunc = XmlILAnnotation.Write(ndFunc).FunctionBinding;
224 useWriter = (XmlILConstructInfo.Read(ndFunc).ConstructMethod == XmlILConstructMethod.Writer);
226 // Generate query code from QilExpression tree
227 this.helper.MethodBegin(methFunc, ndFunc.SourceLine, useWriter);
229 foreach (QilIterator iter in ndFunc.Arguments) {
230 // DebugInfo: Sequence point just before generating code for the bound expression
231 if (this.qil.IsDebug && iter.SourceLine != null)
232 this.helper.DebugSequencePoint(iter.SourceLine);
234 // Calculate default value of this parameter
235 if (iter.Binding != null) {
236 Debug.Assert(iter.XmlType == TypeFactory.ItemS, "IlGen currently only supports default values in parameters of type item*.");
237 paramId = (iter.Annotation as XmlILAnnotation).ArgumentPosition + 1;
239 // runtime.MatchesXmlType(param, XmlTypeCode.QName);
240 Label lblLocalComputed = this.helper.DefineLabel();
241 this.helper.LoadQueryRuntime();
242 this.helper.LoadParameter(paramId);
243 this.helper.LoadInteger((int)XmlTypeCode.QName);
244 this.helper.Call(XmlILMethods.SeqMatchesCode);
246 this.helper.Emit(OpCodes.Brfalse, lblLocalComputed);
248 // Compute default value of this parameter
249 StartNestedIterator(iter);
250 NestedVisitEnsureStack(iter.Binding, GetItemStorageType(iter), /*isCached:*/!iter.XmlType.IsSingleton);
251 EndNestedIterator(iter);
253 this.helper.SetParameter(paramId);
254 this.helper.MarkLabel(lblLocalComputed);
258 StartNestedIterator(ndFunc);
260 // If function did not push results to writer, then function will return value(s) (rather than void)
262 NestedVisit(ndFunc.Definition);
264 NestedVisitEnsureStack(ndFunc.Definition, GetItemStorageType(ndFunc), !ndFunc.XmlType.IsSingleton);
266 EndNestedIterator(ndFunc);
268 this.helper.MethodEnd();
271 //-----------------------------------------------
273 //-----------------------------------------------
276 /// Generate a query plan for the QilExpression subgraph.
278 protected override QilNode Visit(QilNode nd) {
282 // DebugInfo: Sequence point just before generating code for this expression
283 if (this.qil.IsDebug && nd.SourceLine != null && !(nd is QilIterator))
284 this.helper.DebugSequencePoint(nd.SourceLine);
286 // Expressions are constructed using one of several possible methods
287 switch (XmlILConstructInfo.Read(nd).ConstructMethod) {
288 case XmlILConstructMethod.WriterThenIterator:
289 // Push results of expression to cached writer; then iterate over cached results
290 NestedConstruction(nd);
293 case XmlILConstructMethod.IteratorThenWriter:
294 // Iterate over items in the sequence; send items to writer
298 case XmlILConstructMethod.Iterator:
299 Debug.Assert(nd.XmlType.IsSingleton || CachesResult(nd) || this.iterCurr.HasLabelNext,
300 "When generating code for a non-singleton expression, LabelNext must be defined.");
304 // Allow base internal class to dispatch to correct Visit method
313 /// VisitChildren should never be called.
315 protected override QilNode VisitChildren(QilNode parent) {
316 Debug.Fail("Visit" + parent.NodeType + " should never be called");
321 /// Generate code to cache a sequence of items that are pushed to output.
323 private void NestedConstruction(QilNode nd) {
324 // Start nested construction of a sequence of items
325 this.helper.CallStartSequenceConstruction();
327 // Allow base internal class to dispatch to correct Visit method
330 // Get the result sequence
331 this.helper.CallEndSequenceConstruction();
332 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), true);
336 /// Iterate over items produced by the "nd" expression and copy each item to output.
338 private void CopySequence(QilNode nd) {
339 XmlQueryType typ = nd.XmlType;
343 StartWriterLoop(nd, out hasOnEnd, out lblOnEnd);
345 if (typ.IsSingleton) {
346 // Always write atomic values via XmlQueryOutput
347 this.helper.LoadQueryOutput();
349 // Allow base internal class to dispatch to correct Visit method
351 this.iterCurr.EnsureItemStorageType(nd.XmlType, typeof(XPathItem));
354 // Allow base internal class to dispatch to correct Visit method
356 this.iterCurr.EnsureItemStorageType(nd.XmlType, typeof(XPathItem));
358 // Save any stack values in a temporary local
359 this.iterCurr.EnsureNoStackNoCache("$$$copyTemp");
361 this.helper.LoadQueryOutput();
364 // Write value to output
365 this.iterCurr.EnsureStackNoCache();
366 this.helper.Call(XmlILMethods.WriteItem);
368 EndWriterLoop(nd, hasOnEnd, lblOnEnd);
372 /// Generate code for QilNodeType.DataSource.
375 /// Generates code to retrieve a document using the XmlResolver.
377 protected override QilNode VisitDataSource(QilDataSource ndSrc) {
380 // XPathNavigator navDoc = runtime.ExternalContext.GetEntity(uri)
381 this.helper.LoadQueryContext();
382 NestedVisitEnsureStack(ndSrc.Name);
383 NestedVisitEnsureStack(ndSrc.BaseUri);
384 this.helper.Call(XmlILMethods.GetDataSource);
386 locNav = this.helper.DeclareLocal("$$$navDoc", typeof(XPathNavigator));
387 this.helper.Emit(OpCodes.Stloc, locNav);
389 // if (navDoc == null) goto LabelNextCtxt;
390 this.helper.Emit(OpCodes.Ldloc, locNav);
391 this.helper.Emit(OpCodes.Brfalse, this.iterCurr.GetLabelNext());
393 this.iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
399 /// Generate code for QilNodeType.Nop.
401 protected override QilNode VisitNop(QilUnary ndNop) {
402 return Visit(ndNop.Child);
406 /// Generate code for QilNodeType.OptimizeBarrier.
408 protected override QilNode VisitOptimizeBarrier(QilUnary ndBarrier) {
409 return Visit(ndBarrier.Child);
413 /// Generate code for QilNodeType.Error.
415 protected override QilNode VisitError(QilUnary ndErr) {
416 // XmlQueryRuntime.ThrowException(strErr);
417 this.helper.LoadQueryRuntime();
418 NestedVisitEnsureStack(ndErr.Child);
419 this.helper.Call(XmlILMethods.ThrowException);
421 if (XmlILConstructInfo.Read(ndErr).ConstructMethod == XmlILConstructMethod.Writer) {
422 this.iterCurr.Storage = StorageDescriptor.None();
425 // Push dummy value so that Location is not None and IL rules are met
426 this.helper.Emit(OpCodes.Ldnull);
427 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), false);
434 /// Generate code for QilNodeType.Warning.
436 protected override QilNode VisitWarning(QilUnary ndWarning) {
437 // runtime.SendMessage(strWarning);
438 this.helper.LoadQueryRuntime();
439 NestedVisitEnsureStack(ndWarning.Child);
440 this.helper.Call(XmlILMethods.SendMessage);
442 if (XmlILConstructInfo.Read(ndWarning).ConstructMethod == XmlILConstructMethod.Writer)
443 this.iterCurr.Storage = StorageDescriptor.None();
445 VisitEmpty(ndWarning);
451 /// Generate code for QilNodeType.True.
454 /// BranchingContext.OnFalse context: [nothing]
455 /// BranchingContext.OnTrue context: goto LabelParent;
456 /// BranchingContext.None context: push true();
458 protected override QilNode VisitTrue(QilNode ndTrue) {
459 if (this.iterCurr.CurrentBranchingContext != BranchingContext.None) {
460 // Make sure there's an IL code path to both the true and false branches in order to avoid dead
461 // code which can cause IL verification errors.
462 this.helper.EmitUnconditionalBranch(this.iterCurr.CurrentBranchingContext == BranchingContext.OnTrue ?
463 OpCodes.Brtrue : OpCodes.Brfalse, this.iterCurr.LabelBranch);
465 this.iterCurr.Storage = StorageDescriptor.None();
468 // Push boolean result onto the stack
469 this.helper.LoadBoolean(true);
470 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
477 /// Generate code for QilNodeType.False.
480 /// BranchingContext.OnFalse context: goto LabelParent;
481 /// BranchingContext.OnTrue context: [nothing]
482 /// BranchingContext.None context: push false();
484 protected override QilNode VisitFalse(QilNode ndFalse) {
485 if (this.iterCurr.CurrentBranchingContext != BranchingContext.None) {
486 // Make sure there's an IL code path to both the true and false branches in order to avoid dead
487 // code which can cause IL verification errors.
488 this.helper.EmitUnconditionalBranch(this.iterCurr.CurrentBranchingContext == BranchingContext.OnFalse ?
489 OpCodes.Brtrue : OpCodes.Brfalse, this.iterCurr.LabelBranch);
491 this.iterCurr.Storage = StorageDescriptor.None();
494 // Push boolean result onto the stack
495 this.helper.LoadBoolean(false);
496 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
503 /// Generate code for QilNodeType.LiteralString.
505 protected override QilNode VisitLiteralString(QilLiteral ndStr) {
506 this.helper.Emit(OpCodes.Ldstr, (string) ndStr);
507 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
512 /// Generate code for QilNodeType.LiteralInt32.
514 protected override QilNode VisitLiteralInt32(QilLiteral ndInt) {
515 this.helper.LoadInteger((int) ndInt);
516 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(int), false);
521 /// Generate code for QilNodeType.LiteralInt64.
523 protected override QilNode VisitLiteralInt64(QilLiteral ndLong) {
524 this.helper.Emit(OpCodes.Ldc_I8, (long) ndLong);
525 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(long), false);
530 /// Generate code for QilNodeType.LiteralDouble.
532 protected override QilNode VisitLiteralDouble(QilLiteral ndDbl) {
533 this.helper.Emit(OpCodes.Ldc_R8, (double) ndDbl);
534 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(double), false);
539 /// Generate code for QilNodeType.LiteralDecimal.
541 protected override QilNode VisitLiteralDecimal(QilLiteral ndDec) {
542 this.helper.ConstructLiteralDecimal((decimal) ndDec);
543 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(decimal), false);
548 /// Generate code for QilNodeType.LiteralQName.
550 protected override QilNode VisitLiteralQName(QilName ndQName) {
551 this.helper.ConstructLiteralQName(ndQName.LocalName, ndQName.NamespaceUri);
552 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XmlQualifiedName), false);
557 /// Generate code for QilNodeType.And.
560 /// BranchingContext.OnFalse context: (expr1) and (expr2)
561 /// ==> if (!expr1) goto LabelParent;
562 /// if (!expr2) goto LabelParent;
564 /// BranchingContext.OnTrue context: (expr1) and (expr2)
565 /// ==> if (!expr1) goto LabelTemp;
566 /// if (expr1) goto LabelParent;
569 /// BranchingContext.None context: (expr1) and (expr2)
570 /// ==> if (!expr1) goto LabelTemp;
571 /// if (!expr1) goto LabelTemp;
579 protected override QilNode VisitAnd(QilBinary ndAnd) {
580 IteratorDescriptor iterParent = this.iterCurr;
584 StartNestedIterator(ndAnd.Left);
585 lblOnFalse = StartConjunctiveTests(iterParent.CurrentBranchingContext, iterParent.LabelBranch);
587 EndNestedIterator(ndAnd.Left);
589 // Visit right branch
590 StartNestedIterator(ndAnd.Right);
591 StartLastConjunctiveTest(iterParent.CurrentBranchingContext, iterParent.LabelBranch, lblOnFalse);
593 EndNestedIterator(ndAnd.Right);
595 // End And expression
596 EndConjunctiveTests(iterParent.CurrentBranchingContext, iterParent.LabelBranch, lblOnFalse);
602 /// Fixup branching context for all but the last test in a conjunctive (Logical And) expression.
603 /// Return a temporary label which will be passed to StartLastAndBranch() and EndAndBranch().
605 private Label StartConjunctiveTests(BranchingContext brctxt, Label lblBranch) {
609 case BranchingContext.OnFalse:
610 // If condition evaluates to false, branch to false label
611 this.iterCurr.SetBranching(BranchingContext.OnFalse, lblBranch);
615 // If condition evaluates to false:
616 // 1. Jump to new false label that will be fixed just beyond the second condition
617 // 2. Or, jump to code that pushes "false"
618 lblOnFalse = this.helper.DefineLabel();
619 this.iterCurr.SetBranching(BranchingContext.OnFalse, lblOnFalse);
625 /// Fixup branching context for the last test in a conjunctive (Logical And) expression.
627 private void StartLastConjunctiveTest(BranchingContext brctxt, Label lblBranch, Label lblOnFalse) {
629 case BranchingContext.OnTrue:
630 // If last condition evaluates to true, branch to true label
631 this.iterCurr.SetBranching(BranchingContext.OnTrue, lblBranch);
635 // If last condition evalutes to false, branch to false label
636 // Else fall through to true code path
637 this.iterCurr.SetBranching(BranchingContext.OnFalse, lblOnFalse);
643 /// Anchor any remaining labels.
645 private void EndConjunctiveTests(BranchingContext brctxt, Label lblBranch, Label lblOnFalse) {
647 case BranchingContext.OnTrue:
648 // Anchor false label
649 this.helper.MarkLabel(lblOnFalse);
650 goto case BranchingContext.OnFalse;
652 case BranchingContext.OnFalse:
653 this.iterCurr.Storage = StorageDescriptor.None();
656 case BranchingContext.None:
657 // Convert branch targets into push of true/false
658 this.helper.ConvBranchToBool(lblOnFalse, false);
659 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
665 /// Generate code for QilNodeType.Or.
668 /// BranchingContext.OnFalse context: (expr1) or (expr2)
669 /// ==> if (expr1) goto LabelTemp;
670 /// if (!expr2) goto LabelParent;
673 /// BranchingContext.OnTrue context: (expr1) or (expr2)
674 /// ==> if (expr1) goto LabelParent;
675 /// if (expr1) goto LabelParent;
677 /// BranchingContext.None context: (expr1) or (expr2)
678 /// ==> if (expr1) goto LabelTemp;
679 /// if (expr1) goto LabelTemp;
687 protected override QilNode VisitOr(QilBinary ndOr) {
688 Label lblTemp = new Label();
691 switch (this.iterCurr.CurrentBranchingContext) {
692 case BranchingContext.OnFalse:
693 // If left condition evaluates to true, jump to new label that will be fixed
694 // just beyond the second condition
695 lblTemp = this.helper.DefineLabel();
696 NestedVisitWithBranch(ndOr.Left, BranchingContext.OnTrue, lblTemp);
699 case BranchingContext.OnTrue:
700 // If left condition evaluates to true, branch to true label
701 NestedVisitWithBranch(ndOr.Left, BranchingContext.OnTrue, this.iterCurr.LabelBranch);
705 // If left condition evalutes to true, jump to code that pushes "true"
706 Debug.Assert(this.iterCurr.CurrentBranchingContext == BranchingContext.None);
707 lblTemp = this.helper.DefineLabel();
708 NestedVisitWithBranch(ndOr.Left, BranchingContext.OnTrue, lblTemp);
712 // Visit right branch
713 switch (this.iterCurr.CurrentBranchingContext) {
714 case BranchingContext.OnFalse:
715 // If right condition evaluates to false, branch to false label
716 NestedVisitWithBranch(ndOr.Right, BranchingContext.OnFalse, this.iterCurr.LabelBranch);
719 case BranchingContext.OnTrue:
720 // If right condition evaluates to true, branch to true label
721 NestedVisitWithBranch(ndOr.Right, BranchingContext.OnTrue, this.iterCurr.LabelBranch);
725 // If right condition evalutes to true, jump to code that pushes "true".
726 // Otherwise, if both conditions evaluate to false, fall through code path
727 // will push "false".
728 NestedVisitWithBranch(ndOr.Right, BranchingContext.OnTrue, lblTemp);
732 switch (this.iterCurr.CurrentBranchingContext) {
733 case BranchingContext.OnFalse:
735 this.helper.MarkLabel(lblTemp);
736 goto case BranchingContext.OnTrue;
738 case BranchingContext.OnTrue:
739 this.iterCurr.Storage = StorageDescriptor.None();
742 case BranchingContext.None:
743 // Convert branch targets into push of true/false
744 this.helper.ConvBranchToBool(lblTemp, true);
745 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
753 /// Generate code for QilNodeType.Not.
756 /// BranchingContext.OnFalse context: not(expr1)
757 /// ==> if (expr1) goto LabelParent;
759 /// BranchingContext.OnTrue context: not(expr1)
760 /// ==> if (!expr1) goto LabelParent;
762 /// BranchingContext.None context: not(expr1)
763 /// ==> if (expr1) goto LabelTemp;
771 protected override QilNode VisitNot(QilUnary ndNot) {
772 Label lblTemp = new Label();
775 // Reverse branch types
776 switch (this.iterCurr.CurrentBranchingContext) {
777 case BranchingContext.OnFalse:
778 NestedVisitWithBranch(ndNot.Child, BranchingContext.OnTrue, this.iterCurr.LabelBranch);
781 case BranchingContext.OnTrue:
782 NestedVisitWithBranch(ndNot.Child, BranchingContext.OnFalse, this.iterCurr.LabelBranch);
786 // Replace boolean argument on top of stack with its inverse
787 Debug.Assert(this.iterCurr.CurrentBranchingContext == BranchingContext.None);
788 lblTemp = this.helper.DefineLabel();
789 NestedVisitWithBranch(ndNot.Child, BranchingContext.OnTrue, lblTemp);
793 if (this.iterCurr.CurrentBranchingContext == BranchingContext.None) {
794 // If condition evaluates to true, then jump to code that pushes false
795 this.helper.ConvBranchToBool(lblTemp, false);
796 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
799 this.iterCurr.Storage = StorageDescriptor.None();
806 /// Generate code for QilNodeType.Conditional.
808 protected override QilNode VisitConditional(QilTernary ndCond) {
809 XmlILConstructInfo info = XmlILConstructInfo.Read(ndCond);
811 if (info.ConstructMethod == XmlILConstructMethod.Writer) {
812 Label lblFalse, lblDone;
815 lblFalse = this.helper.DefineLabel();
816 NestedVisitWithBranch(ndCond.Left, BranchingContext.OnFalse, lblFalse);
818 // Generate true branch code
819 NestedVisit(ndCond.Center);
821 // Generate false branch code. If false branch is the empty list,
822 if (ndCond.Right.NodeType == QilNodeType.Sequence && ndCond.Right.Count == 0) {
823 // Then generate simplified code that doesn't contain a false branch
824 this.helper.MarkLabel(lblFalse);
825 NestedVisit(ndCond.Right);
828 // Jump past false branch
829 lblDone = this.helper.DefineLabel();
830 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
832 // Generate false branch code
833 this.helper.MarkLabel(lblFalse);
834 NestedVisit(ndCond.Right);
836 this.helper.MarkLabel(lblDone);
839 this.iterCurr.Storage = StorageDescriptor.None();
842 IteratorDescriptor iterInfoTrue;
843 LocalBuilder locBool = null, locCond = null;
844 Label lblFalse, lblDone, lblNext;
845 Type itemStorageType = GetItemStorageType(ndCond);
846 Debug.Assert(info.ConstructMethod == XmlILConstructMethod.Iterator);
848 // Evaluate conditional test -- save boolean result in boolResult
849 Debug.Assert(ndCond.Left.XmlType.TypeCode == XmlTypeCode.Boolean);
850 lblFalse = this.helper.DefineLabel();
852 if (ndCond.XmlType.IsSingleton) {
853 // if (!bool-expr) goto LabelFalse;
854 NestedVisitWithBranch(ndCond.Left, BranchingContext.OnFalse, lblFalse);
857 // CondType itemCond;
858 // int boolResult = bool-expr;
859 locCond = this.helper.DeclareLocal("$$$cond", itemStorageType);
860 locBool = this.helper.DeclareLocal("$$$boolResult", typeof(bool));
861 NestedVisitEnsureLocal(ndCond.Left, locBool);
863 // if (!boolResult) goto LabelFalse;
864 this.helper.Emit(OpCodes.Ldloc, locBool);
865 this.helper.Emit(OpCodes.Brfalse, lblFalse);
868 // Generate code for true branch
869 ConditionalBranch(ndCond.Center, itemStorageType, locCond);
870 iterInfoTrue = this.iterNested;
873 lblDone = this.helper.DefineLabel();
874 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
876 // Generate code for false branch
878 this.helper.MarkLabel(lblFalse);
879 ConditionalBranch(ndCond.Right, itemStorageType, locCond);
881 // If conditional is not cardinality one, then need to iterate through all values
882 if (!ndCond.XmlType.IsSingleton) {
883 Debug.Assert(!ndCond.Center.XmlType.IsSingleton || !ndCond.Right.XmlType.IsSingleton);
885 // IL's rules do not allow OpCodes.Br here
887 this.helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblDone);
890 lblNext = this.helper.DefineLabel();
891 this.helper.MarkLabel(lblNext);
893 // if (boolResult) goto LabelNextTrue else goto LabelNextFalse;
894 this.helper.Emit(OpCodes.Ldloc, locBool);
895 this.helper.Emit(OpCodes.Brtrue, iterInfoTrue.GetLabelNext());
896 this.helper.EmitUnconditionalBranch(OpCodes.Br, this.iterNested.GetLabelNext());
898 this.iterCurr.SetIterator(lblNext, StorageDescriptor.Local(locCond, itemStorageType, false));
902 this.helper.MarkLabel(lblDone);
909 /// Generate code for one of the branches of QilNodeType.Conditional.
911 private void ConditionalBranch(QilNode ndBranch, Type itemStorageType, LocalBuilder locResult) {
912 if (locResult == null) {
913 Debug.Assert(ndBranch.XmlType.IsSingleton, "Conditional must produce a singleton");
915 // If in a branching context, then inherit branch target from parent context
916 if (this.iterCurr.IsBranching) {
917 Debug.Assert(itemStorageType == typeof(bool));
918 NestedVisitWithBranch(ndBranch, this.iterCurr.CurrentBranchingContext, this.iterCurr.LabelBranch);
921 NestedVisitEnsureStack(ndBranch, itemStorageType, false);
925 // Link nested iterator to parent conditional's iterator
926 NestedVisit(ndBranch, this.iterCurr.GetLabelNext());
927 this.iterCurr.EnsureItemStorageType(ndBranch.XmlType, itemStorageType);
928 this.iterCurr.EnsureLocalNoCache(locResult);
933 /// Generate code for QilNodeType.Choice.
935 protected override QilNode VisitChoice(QilChoice ndChoice) {
937 Label[] switchLabels;
938 Label lblOtherwise, lblDone;
939 int regBranches, idx;
940 Debug.Assert(XmlILConstructInfo.Read(ndChoice).PushToWriterFirst);
942 // Evaluate the expression
943 NestedVisit(ndChoice.Expression);
945 // Generate switching code
946 ndBranches = ndChoice.Branches;
947 regBranches = ndBranches.Count - 1;
948 switchLabels = new Label[regBranches];
949 for (idx = 0; idx < regBranches; idx++)
950 switchLabels[idx] = this.helper.DefineLabel();
952 lblOtherwise = this.helper.DefineLabel();
953 lblDone = this.helper.DefineLabel();
956 // case 0: goto Label[0];
958 // case N-1: goto Label[N-1];
959 // default: goto LabelOtherwise;
960 this.helper.Emit(OpCodes.Switch, switchLabels);
961 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblOtherwise);
963 for (idx = 0; idx < regBranches; idx++) {
965 this.helper.MarkLabel(switchLabels[idx]);
967 // Generate regular branch code
968 NestedVisit(ndBranches[idx]);
971 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
975 this.helper.MarkLabel(lblOtherwise);
977 // Generate otherwise branch code
978 NestedVisit(ndBranches[idx]);
981 this.helper.MarkLabel(lblDone);
983 this.iterCurr.Storage = StorageDescriptor.None();
989 /// Generate code for QilNodeType.Length.
993 /// foreach (item in expr)
996 protected override QilNode VisitLength(QilUnary ndSetLen) {
997 Label lblOnEnd = this.helper.DefineLabel();
998 OptimizerPatterns patt = OptimizerPatterns.Read(ndSetLen);
1000 if (CachesResult(ndSetLen.Child)) {
1001 NestedVisitEnsureStack(ndSetLen.Child);
1002 this.helper.CallCacheCount(this.iterNested.Storage.ItemStorageType);
1006 this.helper.Emit(OpCodes.Ldc_I4_0);
1008 StartNestedIterator(ndSetLen.Child, lblOnEnd);
1010 // foreach (item in expr) {
1011 Visit(ndSetLen.Child);
1013 // Pop values of SetLength expression from the stack if necessary
1014 this.iterCurr.EnsureNoCache();
1015 this.iterCurr.DiscardStack();
1018 this.helper.Emit(OpCodes.Ldc_I4_1);
1019 this.helper.Emit(OpCodes.Add);
1021 if (patt.MatchesPattern(OptimizerPatternName.MaxPosition)) {
1022 // Short-circuit rest of loop if max position has been exceeded
1023 this.helper.Emit(OpCodes.Dup);
1024 this.helper.LoadInteger((int) patt.GetArgument(OptimizerPatternArgument.MaxPosition));
1025 this.helper.Emit(OpCodes.Bgt, lblOnEnd);
1029 this.iterCurr.LoopToEnd(lblOnEnd);
1031 EndNestedIterator(ndSetLen.Child);
1034 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(int), false);
1040 /// Find physical query plan for QilNodeType.Sequence.
1042 protected override QilNode VisitSequence(QilList ndSeq) {
1043 if (XmlILConstructInfo.Read(ndSeq).ConstructMethod == XmlILConstructMethod.Writer) {
1044 // Push each item in the list to output
1045 foreach (QilNode nd in ndSeq)
1049 // Empty sequence is special case
1050 if (ndSeq.Count == 0)
1060 /// Generate code for the empty sequence.
1062 private void VisitEmpty(QilNode nd) {
1063 Debug.Assert(XmlILConstructInfo.Read(nd).PullFromIteratorFirst, "VisitEmpty should only be called if items are iterated");
1065 // IL's rules prevent OpCodes.Br here
1067 this.helper.EmitUnconditionalBranch(OpCodes.Brtrue, this.iterCurr.GetLabelNext());
1069 // Push dummy value so that Location is not None and IL rules are met
1070 this.helper.Emit(OpCodes.Ldnull);
1071 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), false);
1075 /// Generate code for QilNodeType.Sequence, when sort-merging to retain document order is not necessary.
1077 private void Sequence(QilList ndSeq) {
1078 LocalBuilder locIdx, locList;
1079 Label lblStart, lblNext, lblOnEnd = new Label();
1080 Label[] arrSwitchLabels;
1082 Type itemStorageType = GetItemStorageType(ndSeq);
1083 Debug.Assert(XmlILConstructInfo.Read(ndSeq).ConstructMethod == XmlILConstructMethod.Iterator, "This method should only be called if items in list are pulled from a code iterator.");
1085 // Singleton list is a special case if in addition to the singleton there are warnings or errors which should be executed
1086 if (ndSeq.XmlType.IsSingleton) {
1087 foreach (QilNode nd in ndSeq) {
1088 // Generate nested iterator's code
1089 if (nd.XmlType.IsSingleton) {
1090 NestedVisitEnsureStack(nd);
1093 lblOnEnd = this.helper.DefineLabel();
1094 NestedVisit(nd, lblOnEnd);
1095 this.iterCurr.DiscardStack();
1096 this.helper.MarkLabel(lblOnEnd);
1099 this.iterCurr.Storage = StorageDescriptor.Stack(itemStorageType, false);
1104 locList = this.helper.DeclareLocal("$$$itemList", itemStorageType);
1105 locIdx = this.helper.DeclareLocal("$$$idxList", typeof(int));
1107 arrSwitchLabels = new Label[ndSeq.Count];
1108 lblStart = this.helper.DefineLabel();
1110 for (i = 0; i < ndSeq.Count; i++) {
1111 // LabelOnEnd[i - 1]:
1112 // When previous nested iterator is exhausted, it should jump to this (the next) iterator
1114 this.helper.MarkLabel(lblOnEnd);
1116 // Create new LabelOnEnd for all but the last iterator, which jumps back to parent iterator when exhausted
1117 if (i == ndSeq.Count - 1)
1118 lblOnEnd = this.iterCurr.GetLabelNext();
1120 lblOnEnd = this.helper.DefineLabel();
1123 this.helper.LoadInteger(i);
1124 this.helper.Emit(OpCodes.Stloc, locIdx);
1126 // Generate nested iterator's code
1127 NestedVisit(ndSeq[i], lblOnEnd);
1129 // Result of list should be saved to a common type and location
1130 this.iterCurr.EnsureItemStorageType(ndSeq[i].XmlType, itemStorageType);
1131 this.iterCurr.EnsureLocalNoCache(locList);
1133 // Switch statement will jump to nested iterator's LabelNext
1134 arrSwitchLabels[i] = this.iterNested.GetLabelNext();
1136 // IL's rules prevent OpCodes.Br here
1138 this.helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblStart);
1142 lblNext = this.helper.DefineLabel();
1143 this.helper.MarkLabel(lblNext);
1146 // case 0: goto LabelNext1;
1148 // case N-1: goto LabelNext[N];
1149 this.helper.Emit(OpCodes.Ldloc, locIdx);
1150 this.helper.Emit(OpCodes.Switch, arrSwitchLabels);
1153 this.helper.MarkLabel(lblStart);
1155 this.iterCurr.SetIterator(lblNext, StorageDescriptor.Local(locList, itemStorageType, false));
1160 /// Generate code for QilNodeType.Union.
1162 protected override QilNode VisitUnion(QilBinary ndUnion) {
1163 return CreateSetIterator(ndUnion, "$$$iterUnion", typeof(UnionIterator), XmlILMethods.UnionCreate, XmlILMethods.UnionNext);
1167 /// Generate code for QilNodeType.Intersection.
1169 protected override QilNode VisitIntersection(QilBinary ndInter) {
1170 return CreateSetIterator(ndInter, "$$$iterInter", typeof(IntersectIterator), XmlILMethods.InterCreate, XmlILMethods.InterNext);
1174 /// Generate code for QilNodeType.Difference.
1176 protected override QilNode VisitDifference(QilBinary ndDiff) {
1177 return CreateSetIterator(ndDiff, "$$$iterDiff", typeof(DifferenceIterator), XmlILMethods.DiffCreate, XmlILMethods.DiffNext);
1181 /// Generate code to combine nodes from two nested iterators using Union, Intersection, or Difference semantics.
1183 private QilNode CreateSetIterator(QilBinary ndSet, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext) {
1184 LocalBuilder locIter, locNav;
1185 Label lblNext, lblCall, lblNextLeft, lblNextRight, lblInitRight;
1187 // SetIterator iterSet;
1188 // XPathNavigator navSet;
1189 locIter = this.helper.DeclareLocal(iterName, iterType);
1190 locNav = this.helper.DeclareLocal("$$$navSet", typeof(XPathNavigator));
1192 // iterSet.Create(runtime);
1193 this.helper.Emit(OpCodes.Ldloca, locIter);
1194 this.helper.LoadQueryRuntime();
1195 this.helper.Call(methCreate);
1197 // Define labels that will be used
1198 lblNext = this.helper.DefineLabel();
1199 lblCall = this.helper.DefineLabel();
1200 lblInitRight = this.helper.DefineLabel();
1202 // Generate left nested iterator. When it is empty, it will branch to lblNext.
1204 NestedVisit(ndSet.Left, lblNext);
1205 lblNextLeft = this.iterNested.GetLabelNext();
1206 this.iterCurr.EnsureLocal(locNav);
1207 this.helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblCall);
1209 // Generate right nested iterator. When it is empty, it will branch to lblNext.
1212 this.helper.MarkLabel(lblInitRight);
1213 NestedVisit(ndSet.Right, lblNext);
1214 lblNextRight = this.iterNested.GetLabelNext();
1215 this.iterCurr.EnsureLocal(locNav);
1216 this.helper.EmitUnconditionalBranch(OpCodes.Brtrue, lblCall);
1219 this.helper.MarkLabel(lblNext);
1220 this.helper.Emit(OpCodes.Ldnull);
1221 this.helper.Emit(OpCodes.Stloc, locNav);
1224 // switch (iterSet.MoveNext(nestedNested)) {
1225 // case SetIteratorResult.NoMoreNodes: goto LabelNextCtxt;
1226 // case SetIteratorResult.InitRightIterator: goto LabelInitRight;
1227 // case SetIteratorResult.NeedLeftNode: goto LabelNextLeft;
1228 // case SetIteratorResult.NeedRightNode: goto LabelNextRight;
1230 this.helper.MarkLabel(lblCall);
1231 this.helper.Emit(OpCodes.Ldloca, locIter);
1232 this.helper.Emit(OpCodes.Ldloc, locNav);
1233 this.helper.Call(methNext);
1235 // If this iterator always returns a single node, then NoMoreNodes will never be returned
1236 // Don't expose Next label if this iterator always returns a single node
1237 if (ndSet.XmlType.IsSingleton) {
1238 this.helper.Emit(OpCodes.Switch, new Label[] {lblInitRight, lblNextLeft, lblNextRight});
1239 this.iterCurr.Storage = StorageDescriptor.Current(locIter, typeof(XPathNavigator));
1242 this.helper.Emit(OpCodes.Switch, new Label[] {this.iterCurr.GetLabelNext(), lblInitRight, lblNextLeft, lblNextRight});
1243 this.iterCurr.SetIterator(lblNext, StorageDescriptor.Current(locIter, typeof(XPathNavigator)));
1250 /// Generate code for QilNodeType.Average.
1252 protected override QilNode VisitAverage(QilUnary ndAvg) {
1253 XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndAvg)];
1254 return CreateAggregator(ndAvg, "$$$aggAvg", meths, meths.AggAvg, meths.AggAvgResult);
1258 /// Generate code for QilNodeType.Sum.
1260 protected override QilNode VisitSum(QilUnary ndSum) {
1261 XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndSum)];
1262 return CreateAggregator(ndSum, "$$$aggSum", meths, meths.AggSum, meths.AggSumResult);
1266 /// Generate code for QilNodeType.Minimum.
1268 protected override QilNode VisitMinimum(QilUnary ndMin) {
1269 XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndMin)];
1270 return CreateAggregator(ndMin, "$$$aggMin", meths, meths.AggMin, meths.AggMinResult);
1274 /// Generate code for QilNodeType.Maximum.
1276 protected override QilNode VisitMaximum(QilUnary ndMax) {
1277 XmlILStorageMethods meths = XmlILMethods.StorageMethods[GetItemStorageType(ndMax)];
1278 return CreateAggregator(ndMax, "$$$aggMax", meths, meths.AggMax, meths.AggMaxResult);
1282 /// Generate code for QilNodeType.Sum, QilNodeType.Average, QilNodeType.Minimum, and QilNodeType.Maximum.
1284 private QilNode CreateAggregator(QilUnary ndAgg, string aggName, XmlILStorageMethods methods, MethodInfo methAgg, MethodInfo methResult) {
1285 Label lblOnEnd = this.helper.DefineLabel();
1286 Type typAgg = methAgg.DeclaringType;
1287 LocalBuilder locAgg;
1291 locAgg = this.helper.DeclareLocal(aggName, typAgg);
1292 this.helper.Emit(OpCodes.Ldloca, locAgg);
1293 this.helper.Call(methods.AggCreate);
1295 // foreach (num in expr) {
1296 StartNestedIterator(ndAgg.Child, lblOnEnd);
1297 this.helper.Emit(OpCodes.Ldloca, locAgg);
1300 // agg.Aggregate(num);
1301 this.iterCurr.EnsureStackNoCache();
1302 this.iterCurr.EnsureItemStorageType(ndAgg.XmlType, GetItemStorageType(ndAgg));
1303 this.helper.Call(methAgg);
1304 this.helper.Emit(OpCodes.Ldloca, locAgg);
1307 this.iterCurr.LoopToEnd(lblOnEnd);
1309 // End nested iterator
1310 EndNestedIterator(ndAgg.Child);
1312 // If aggregate might be empty sequence, then generate code to handle this possibility
1313 if (ndAgg.XmlType.MaybeEmpty) {
1314 // if (agg.IsEmpty) goto LabelNextCtxt;
1315 this.helper.Call(methods.AggIsEmpty);
1316 this.helper.Emit(OpCodes.Brtrue, this.iterCurr.GetLabelNext());
1317 this.helper.Emit(OpCodes.Ldloca, locAgg);
1320 // result = agg.Result;
1321 this.helper.Call(methResult);
1322 this.iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndAgg), false);
1328 /// Generate code for QilNodeType.Negate.
1330 protected override QilNode VisitNegate(QilUnary ndNeg) {
1331 NestedVisitEnsureStack(ndNeg.Child);
1332 this.helper.CallArithmeticOp(QilNodeType.Negate, ndNeg.XmlType.TypeCode);
1333 this.iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndNeg), false);
1338 /// Generate code for QilNodeType.Add.
1340 protected override QilNode VisitAdd(QilBinary ndPlus) {
1341 return ArithmeticOp(ndPlus);
1345 /// Generate code for QilNodeType.Subtract.
1347 protected override QilNode VisitSubtract(QilBinary ndMinus) {
1348 return ArithmeticOp(ndMinus);
1352 /// Generate code for QilNodeType.Multiply.
1354 protected override QilNode VisitMultiply(QilBinary ndMul) {
1355 return ArithmeticOp(ndMul);
1359 /// Generate code for QilNodeType.Divide.
1361 protected override QilNode VisitDivide(QilBinary ndDiv) {
1362 return ArithmeticOp(ndDiv);
1366 /// Generate code for QilNodeType.Modulo.
1368 protected override QilNode VisitModulo(QilBinary ndMod) {
1369 return ArithmeticOp(ndMod);
1373 /// Generate code for two-argument arithmetic operations.
1375 private QilNode ArithmeticOp(QilBinary ndOp) {
1376 NestedVisitEnsureStack(ndOp.Left, ndOp.Right);
1377 this.helper.CallArithmeticOp(ndOp.NodeType, ndOp.XmlType.TypeCode);
1378 this.iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndOp), false);
1383 /// Generate code for QilNodeType.StrLength.
1385 protected override QilNode VisitStrLength(QilUnary ndLen) {
1386 NestedVisitEnsureStack(ndLen.Child);
1387 this.helper.Call(XmlILMethods.StrLen);
1388 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(int), false);
1393 /// Generate code for QilNodeType.StrConcat.
1395 protected override QilNode VisitStrConcat(QilStrConcat ndStrConcat) {
1396 LocalBuilder locStringConcat;
1399 QilNode listStrings;
1400 Debug.Assert(!ndStrConcat.Values.XmlType.IsSingleton, "Optimizer should have folded StrConcat of a singleton value");
1402 // Get delimiter (assuming it's not the empty string)
1403 delimiter = ndStrConcat.Delimiter;
1404 if (delimiter.NodeType == QilNodeType.LiteralString && ((string) (QilLiteral) delimiter).Length == 0) {
1408 listStrings = ndStrConcat.Values;
1409 if (listStrings.NodeType == QilNodeType.Sequence && listStrings.Count < 5) {
1410 // Faster concat possible only if cardinality can be guaranteed at compile-time and there's no delimiter
1411 fasterConcat = true;
1412 foreach (QilNode ndStr in listStrings) {
1413 if (!ndStr.XmlType.IsSingleton)
1414 fasterConcat = false;
1418 // If more than 4 strings, array will need to be built
1419 fasterConcat = false;
1423 foreach (QilNode ndStr in listStrings)
1424 NestedVisitEnsureStack(ndStr);
1426 this.helper.CallConcatStrings(listStrings.Count);
1429 // Create StringConcat helper internal class
1430 locStringConcat = this.helper.DeclareLocal("$$$strcat", typeof(StringConcat));
1431 this.helper.Emit(OpCodes.Ldloca, locStringConcat);
1432 this.helper.Call(XmlILMethods.StrCatClear);
1434 // Set delimiter, if it's not empty string
1435 if (delimiter != null) {
1436 this.helper.Emit(OpCodes.Ldloca, locStringConcat);
1437 NestedVisitEnsureStack(delimiter);
1438 this.helper.Call(XmlILMethods.StrCatDelim);
1441 this.helper.Emit(OpCodes.Ldloca, locStringConcat);
1443 if (listStrings.NodeType == QilNodeType.Sequence) {
1444 foreach (QilNode ndStr in listStrings)
1445 GenerateConcat(ndStr, locStringConcat);
1448 GenerateConcat(listStrings, locStringConcat);
1451 // Push resulting string onto stack
1452 this.helper.Call(XmlILMethods.StrCatResult);
1455 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
1461 /// Generate code to concatenate string values returned by expression "ndStr" using the StringConcat helper class.
1463 private void GenerateConcat(QilNode ndStr, LocalBuilder locStringConcat) {
1466 // str = each string;
1467 lblOnEnd = this.helper.DefineLabel();
1468 StartNestedIterator(ndStr, lblOnEnd);
1471 // strcat.Concat(str);
1472 this.iterCurr.EnsureStackNoCache();
1473 this.iterCurr.EnsureItemStorageType(ndStr.XmlType, typeof(string));
1474 this.helper.Call(XmlILMethods.StrCatCat);
1475 this.helper.Emit(OpCodes.Ldloca, locStringConcat);
1480 this.iterCurr.LoopToEnd(lblOnEnd);
1482 // End nested iterator
1483 EndNestedIterator(ndStr);
1487 /// Generate code for QilNodeType.StrParseQName.
1489 protected override QilNode VisitStrParseQName(QilBinary ndParsedTagName) {
1490 VisitStrParseQName(ndParsedTagName, false);
1491 return ndParsedTagName;
1495 /// Generate code for QilNodeType.StrParseQName.
1497 private void VisitStrParseQName(QilBinary ndParsedTagName, bool preservePrefix) {
1498 // If QName prefix should be preserved, then don't create an XmlQualifiedName, which discards the prefix
1499 if (!preservePrefix)
1500 this.helper.LoadQueryRuntime();
1502 // Push (possibly computed) tag name onto the stack
1503 NestedVisitEnsureStack(ndParsedTagName.Left);
1505 // If type of second parameter is string,
1506 if (ndParsedTagName.Right.XmlType.TypeCode == XmlTypeCode.String) {
1507 // Then push (possibly computed) namespace onto the stack
1508 Debug.Assert(ndParsedTagName.Right.XmlType.IsSingleton);
1509 NestedVisitEnsureStack(ndParsedTagName.Right);
1511 if (!preservePrefix)
1512 this.helper.CallParseTagName(GenerateNameType.TagNameAndNamespace);
1515 // Else push index of set of prefix mappings to use in resolving the prefix
1516 if (ndParsedTagName.Right.NodeType == QilNodeType.Sequence)
1517 this.helper.LoadInteger(this.helper.StaticData.DeclarePrefixMappings(ndParsedTagName.Right));
1519 this.helper.LoadInteger(this.helper.StaticData.DeclarePrefixMappings(new QilNode[] {ndParsedTagName.Right}));
1521 // If QName prefix should be preserved, then don't create an XmlQualifiedName, which discards the prefix
1522 if (!preservePrefix)
1523 this.helper.CallParseTagName(GenerateNameType.TagNameAndMappings);
1526 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XmlQualifiedName), false);
1530 /// Generate code for QilNodeType.Ne.
1532 protected override QilNode VisitNe(QilBinary ndNe) {
1538 /// Generate code for QilNodeType.Eq.
1540 protected override QilNode VisitEq(QilBinary ndEq) {
1546 /// Generate code for QilNodeType.Gt.
1548 protected override QilNode VisitGt(QilBinary ndGt) {
1554 /// Generate code for QilNodeType.Ne.
1556 protected override QilNode VisitGe(QilBinary ndGe) {
1562 /// Generate code for QilNodeType.Lt.
1564 protected override QilNode VisitLt(QilBinary ndLt) {
1570 /// Generate code for QilNodeType.Le.
1572 protected override QilNode VisitLe(QilBinary ndLe) {
1578 /// Generate code for comparison operations.
1580 private void Compare(QilBinary ndComp) {
1581 QilNodeType relOp = ndComp.NodeType;
1583 Debug.Assert(ndComp.Left.XmlType.IsAtomicValue && ndComp.Right.XmlType.IsAtomicValue, "Operands to compare must be atomic values.");
1584 Debug.Assert(ndComp.Left.XmlType.IsSingleton && ndComp.Right.XmlType.IsSingleton, "Operands to compare must be cardinality one.");
1585 Debug.Assert(ndComp.Left.XmlType == ndComp.Right.XmlType, "Operands to compare may not be heterogenous.");
1587 if (relOp == QilNodeType.Eq || relOp == QilNodeType.Ne) {
1588 // Generate better code for certain special cases
1589 if (TryZeroCompare(relOp, ndComp.Left, ndComp.Right))
1592 if (TryZeroCompare(relOp, ndComp.Right, ndComp.Left))
1595 if (TryNameCompare(relOp, ndComp.Left, ndComp.Right))
1598 if (TryNameCompare(relOp, ndComp.Right, ndComp.Left))
1602 // Push two operands onto the stack
1603 NestedVisitEnsureStack(ndComp.Left, ndComp.Right);
1605 // Perform comparison
1606 code = ndComp.Left.XmlType.TypeCode;
1608 case XmlTypeCode.String:
1609 case XmlTypeCode.Decimal:
1610 case XmlTypeCode.QName:
1611 if (relOp == QilNodeType.Eq || relOp == QilNodeType.Ne) {
1612 this.helper.CallCompareEquals(code);
1614 // If relOp is Eq, then branch to true label or push "true" if Equals function returns true (non-zero)
1615 // If relOp is Ne, then branch to true label or push "true" if Equals function returns false (zero)
1616 ZeroCompare((relOp == QilNodeType.Eq) ? QilNodeType.Ne : QilNodeType.Eq, true);
1619 Debug.Assert(code != XmlTypeCode.QName, "QName values do not support the " + relOp + " operation");
1621 // Push -1, 0, or 1 onto the stack depending upon the result of the comparison
1622 this.helper.CallCompare(code);
1624 // Compare result to 0 (e.g. Ge is >= 0)
1625 this.helper.Emit(OpCodes.Ldc_I4_0);
1626 ClrCompare(relOp, code);
1630 case XmlTypeCode.Integer:
1631 case XmlTypeCode.Int:
1632 case XmlTypeCode.Boolean:
1633 case XmlTypeCode.Double:
1634 ClrCompare(relOp, code);
1638 Debug.Fail("Comparisons for datatype " + code + " are invalid.");
1644 /// Generate code for QilNodeType.VisitIs.
1646 protected override QilNode VisitIs(QilBinary ndIs) {
1647 // Generate code to push arguments onto stack
1648 NestedVisitEnsureStack(ndIs.Left, ndIs.Right);
1649 this.helper.Call(XmlILMethods.NavSamePos);
1651 // navThis.IsSamePosition(navThat);
1652 ZeroCompare(QilNodeType.Ne, true);
1657 /// Generate code for QilNodeType.VisitBefore.
1659 protected override QilNode VisitBefore(QilBinary ndBefore) {
1660 ComparePosition(ndBefore);
1665 /// Generate code for QilNodeType.VisitAfter.
1667 protected override QilNode VisitAfter(QilBinary ndAfter) {
1668 ComparePosition(ndAfter);
1673 /// Generate code for QilNodeType.VisitBefore and QilNodeType.VisitAfter.
1675 private void ComparePosition(QilBinary ndComp) {
1676 // Generate code to push arguments onto stack
1677 this.helper.LoadQueryRuntime();
1678 NestedVisitEnsureStack(ndComp.Left, ndComp.Right);
1679 this.helper.Call(XmlILMethods.CompPos);
1681 // XmlQueryRuntime.ComparePosition(navThis, navThat) < 0;
1682 this.helper.LoadInteger(0);
1683 ClrCompare(ndComp.NodeType == QilNodeType.Before ? QilNodeType.Lt : QilNodeType.Gt, XmlTypeCode.String);
1687 /// Generate code for a QilNodeType.For.
1689 protected override QilNode VisitFor(QilIterator ndFor) {
1690 IteratorDescriptor iterInfo;
1692 // Reference saved location
1693 iterInfo = XmlILAnnotation.Write(ndFor).CachedIteratorDescriptor;
1694 this.iterCurr.Storage = iterInfo.Storage;
1696 // If the iterator is a reference to a global variable or parameter,
1697 if (this.iterCurr.Storage.Location == ItemLocation.Global) {
1698 // Then compute global value and push it onto the stack
1699 this.iterCurr.EnsureStack();
1706 /// Generate code for a QilNodeType.Let.
1708 protected override QilNode VisitLet(QilIterator ndLet) {
1710 return VisitFor(ndLet);
1714 /// Generate code for a QilNodeType.Parameter.
1716 protected override QilNode VisitParameter(QilParameter ndParameter) {
1718 return VisitFor(ndParameter);
1722 /// Generate code for a QilNodeType.Loop.
1724 protected override QilNode VisitLoop(QilLoop ndLoop) {
1728 StartWriterLoop(ndLoop, out hasOnEnd, out lblOnEnd);
1730 StartBinding(ndLoop.Variable);
1732 // Unnest loop body as part of the current iterator
1735 EndBinding(ndLoop.Variable);
1737 EndWriterLoop(ndLoop, hasOnEnd, lblOnEnd);
1743 /// Generate code for a QilNodeType.Filter.
1745 protected override QilNode VisitFilter(QilLoop ndFilter) {
1746 // Handle any special-case patterns that are rooted at Filter
1747 if (HandleFilterPatterns(ndFilter))
1750 StartBinding(ndFilter.Variable);
1752 // Result of filter is the sequence bound to the iterator
1753 this.iterCurr.SetIterator(this.iterNested);
1755 // If filter is false, skip the current item
1756 StartNestedIterator(ndFilter.Body);
1757 this.iterCurr.SetBranching(BranchingContext.OnFalse, this.iterCurr.ParentIterator.GetLabelNext());
1758 Visit(ndFilter.Body);
1759 EndNestedIterator(ndFilter.Body);
1761 EndBinding(ndFilter.Variable);
1767 /// There are a number of path patterns that can be rooted at Filter nodes. Determine whether one of these patterns
1768 /// has been previously matched on "ndFilter". If so, generate code for the pattern and return true. Otherwise, just
1771 private bool HandleFilterPatterns(QilLoop ndFilter) {
1772 OptimizerPatterns patt = OptimizerPatterns.Read(ndFilter);
1773 LocalBuilder locIter;
1774 XmlNodeKindFlags kinds;
1776 QilNode input, step;
1777 bool isFilterElements;
1779 // Handle FilterElements and FilterContentKind patterns
1780 isFilterElements = patt.MatchesPattern(OptimizerPatternName.FilterElements);
1781 if (isFilterElements || patt.MatchesPattern(OptimizerPatternName.FilterContentKind)) {
1782 if (isFilterElements) {
1783 // FilterElements pattern, so Kind = Element and Name = Argument
1784 kinds = XmlNodeKindFlags.Element;
1785 name = (QilName) patt.GetArgument(OptimizerPatternArgument.ElementQName);
1788 // FilterKindTest pattern, so Kind = Argument and Name = null
1789 kinds = ((XmlQueryType) patt.GetArgument(OptimizerPatternArgument.KindTestType)).NodeKinds;
1793 step = (QilNode) patt.GetArgument(OptimizerPatternArgument.StepNode);
1794 input = (QilNode) patt.GetArgument(OptimizerPatternArgument.StepInput);
1795 switch (step.NodeType) {
1796 case QilNodeType.Content:
1797 if (isFilterElements) {
1799 locIter = this.helper.DeclareLocal("$$$iterElemContent", typeof(ElementContentIterator));
1801 // iter.Create(navCtxt, locName, ns);
1802 this.helper.Emit(OpCodes.Ldloca, locIter);
1803 NestedVisitEnsureStack(input);
1804 this.helper.CallGetAtomizedName(this.helper.StaticData.DeclareName(name.LocalName));
1805 this.helper.CallGetAtomizedName(this.helper.StaticData.DeclareName(name.NamespaceUri));
1806 this.helper.Call(XmlILMethods.ElemContentCreate);
1808 GenerateSimpleIterator(typeof(XPathNavigator), locIter, XmlILMethods.ElemContentNext);
1811 if (kinds == XmlNodeKindFlags.Content) {
1812 CreateSimpleIterator(input, "$$$iterContent", typeof(ContentIterator), XmlILMethods.ContentCreate, XmlILMethods.ContentNext);
1816 locIter = this.helper.DeclareLocal("$$$iterContent", typeof(NodeKindContentIterator));
1818 // iter.Create(navCtxt, nodeType);
1819 this.helper.Emit(OpCodes.Ldloca, locIter);
1820 NestedVisitEnsureStack(input);
1821 this.helper.LoadInteger((int) QilXmlToXPathNodeType(kinds));
1822 this.helper.Call(XmlILMethods.KindContentCreate);
1824 GenerateSimpleIterator(typeof(XPathNavigator), locIter, XmlILMethods.KindContentNext);
1829 case QilNodeType.Parent:
1830 CreateFilteredIterator(input, "$$$iterPar", typeof(ParentIterator), XmlILMethods.ParentCreate, XmlILMethods.ParentNext,
1831 kinds, name, TriState.Unknown, null);
1834 case QilNodeType.Ancestor:
1835 case QilNodeType.AncestorOrSelf:
1836 CreateFilteredIterator(input, "$$$iterAnc", typeof(AncestorIterator), XmlILMethods.AncCreate, XmlILMethods.AncNext,
1837 kinds, name, (step.NodeType == QilNodeType.Ancestor) ? TriState.False : TriState.True, null);
1840 case QilNodeType.Descendant:
1841 case QilNodeType.DescendantOrSelf:
1842 CreateFilteredIterator(input, "$$$iterDesc", typeof(DescendantIterator), XmlILMethods.DescCreate, XmlILMethods.DescNext,
1843 kinds, name, (step.NodeType == QilNodeType.Descendant) ? TriState.False : TriState.True, null);
1846 case QilNodeType.Preceding:
1847 CreateFilteredIterator(input, "$$$iterPrec", typeof(PrecedingIterator), XmlILMethods.PrecCreate, XmlILMethods.PrecNext,
1848 kinds, name, TriState.Unknown, null);
1851 case QilNodeType.FollowingSibling:
1852 CreateFilteredIterator(input, "$$$iterFollSib", typeof(FollowingSiblingIterator), XmlILMethods.FollSibCreate, XmlILMethods.FollSibNext,
1853 kinds, name, TriState.Unknown, null);
1856 case QilNodeType.PrecedingSibling:
1857 CreateFilteredIterator(input, "$$$iterPreSib", typeof(PrecedingSiblingIterator), XmlILMethods.PreSibCreate, XmlILMethods.PreSibNext,
1858 kinds, name, TriState.Unknown, null);
1861 case QilNodeType.NodeRange:
1862 CreateFilteredIterator(input, "$$$iterRange", typeof(NodeRangeIterator), XmlILMethods.NodeRangeCreate, XmlILMethods.NodeRangeNext,
1863 kinds, name, TriState.Unknown, ((QilBinary) step).Right);
1866 case QilNodeType.XPathFollowing:
1867 CreateFilteredIterator(input, "$$$iterFoll", typeof(XPathFollowingIterator), XmlILMethods.XPFollCreate, XmlILMethods.XPFollNext,
1868 kinds, name, TriState.Unknown, null);
1871 case QilNodeType.XPathPreceding:
1872 CreateFilteredIterator(input, "$$$iterPrec", typeof(XPathPrecedingIterator), XmlILMethods.XPPrecCreate, XmlILMethods.XPPrecNext,
1873 kinds, name, TriState.Unknown, null);
1877 Debug.Assert(false, "Pattern " + step.NodeType + " should have been handled.");
1881 else if (patt.MatchesPattern(OptimizerPatternName.FilterAttributeKind)) {
1882 // Handle FilterAttributeKind pattern
1883 input = (QilNode) patt.GetArgument(OptimizerPatternArgument.StepInput);
1884 CreateSimpleIterator(input, "$$$iterAttr", typeof(AttributeIterator), XmlILMethods.AttrCreate, XmlILMethods.AttrNext);
1887 else if (patt.MatchesPattern(OptimizerPatternName.EqualityIndex)) {
1888 // Handle EqualityIndex pattern
1889 Label lblOnEnd = this.helper.DefineLabel();
1890 Label lblLookup = this.helper.DefineLabel();
1891 QilIterator nodes = (QilIterator) patt.GetArgument(OptimizerPatternArgument.IndexedNodes);
1892 QilNode keys = (QilNode) patt.GetArgument(OptimizerPatternArgument.KeyExpression);
1894 // XmlILIndex index;
1895 // if (runtime.FindIndex(navCtxt, indexId, out index)) goto LabelLookup;
1896 LocalBuilder locIndex = this.helper.DeclareLocal("$$$index", typeof(XmlILIndex));
1897 this.helper.LoadQueryRuntime();
1898 this.helper.Emit(OpCodes.Ldarg_1);
1899 this.helper.LoadInteger(this.indexId);
1900 this.helper.Emit(OpCodes.Ldloca, locIndex);
1901 this.helper.Call(XmlILMethods.FindIndex);
1902 this.helper.Emit(OpCodes.Brtrue, lblLookup);
1904 // runtime.AddNewIndex(navCtxt, indexId, [build index]);
1905 this.helper.LoadQueryRuntime();
1906 this.helper.Emit(OpCodes.Ldarg_1);
1907 this.helper.LoadInteger(this.indexId);
1908 this.helper.Emit(OpCodes.Ldloc, locIndex);
1910 // Generate code to iterate over the the nodes which are being indexed ($iterNodes in the pattern)
1911 StartNestedIterator(nodes, lblOnEnd);
1912 StartBinding(nodes);
1914 // Generate code to iterate over the keys for each node ($bindingKeys in the pattern)
1917 // index.Add(key, value);
1918 this.iterCurr.EnsureStackNoCache();
1920 this.iterCurr.EnsureStackNoCache();
1921 this.iterCurr.EnsureItemStorageType(nodes.XmlType, typeof(XPathNavigator));
1922 this.helper.Call(XmlILMethods.IndexAdd);
1923 this.helper.Emit(OpCodes.Ldloc, locIndex);
1926 this.iterCurr.LoopToEnd(lblOnEnd);
1928 EndNestedIterator(nodes);
1930 // runtime.AddNewIndex(navCtxt, indexId, [build index]);
1931 this.helper.Call(XmlILMethods.AddNewIndex);
1934 // results = index.Lookup(keyValue);
1935 this.helper.MarkLabel(lblLookup);
1936 this.helper.Emit(OpCodes.Ldloc, locIndex);
1937 this.helper.Emit(OpCodes.Ldarg_2);
1938 this.helper.Call(XmlILMethods.IndexLookup);
1939 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), true);
1950 /// Generate code for a Let, For, or Parameter iterator. Bind iterated value to a variable.
1952 private void StartBinding(QilIterator ndIter) {
1953 OptimizerPatterns patt = OptimizerPatterns.Read(ndIter);
1954 Debug.Assert(ndIter != null);
1956 // DebugInfo: Sequence point just before generating code for the bound expression
1957 if (this.qil.IsDebug && ndIter.SourceLine != null)
1958 this.helper.DebugSequencePoint(ndIter.SourceLine);
1960 // Treat cardinality one Let iterators as if they were For iterators (no nesting necessary)
1961 if (ndIter.NodeType == QilNodeType.For || ndIter.XmlType.IsSingleton) {
1962 StartForBinding(ndIter, patt);
1965 Debug.Assert(ndIter.NodeType == QilNodeType.Let || ndIter.NodeType == QilNodeType.Parameter);
1966 Debug.Assert(!patt.MatchesPattern(OptimizerPatternName.IsPositional));
1968 // Bind Let values (nested iterator) to variable
1969 StartLetBinding(ndIter);
1972 // Attach IteratorDescriptor to the iterator
1973 XmlILAnnotation.Write(ndIter).CachedIteratorDescriptor = this.iterNested;
1977 /// Bind values produced by the "ndFor" expression to a non-stack location that can later
1980 private void StartForBinding(QilIterator ndFor, OptimizerPatterns patt) {
1981 LocalBuilder locPos = null;
1982 Debug.Assert(ndFor.XmlType.IsSingleton);
1984 // For expression iterator will be unnested as part of parent iterator
1985 if (this.iterCurr.HasLabelNext)
1986 StartNestedIterator(ndFor.Binding, this.iterCurr.GetLabelNext());
1988 StartNestedIterator(ndFor.Binding);
1990 if (patt.MatchesPattern(OptimizerPatternName.IsPositional)) {
1991 // Need to track loop index so initialize it to 0 before starting loop
1992 locPos = this.helper.DeclareLocal("$$$pos", typeof(int));
1993 this.helper.Emit(OpCodes.Ldc_I4_0);
1994 this.helper.Emit(OpCodes.Stloc, locPos);
1997 // Allow base internal class to dispatch based on QilExpression node type
1998 Visit(ndFor.Binding);
2000 // DebugInfo: Open variable scope
2001 // DebugInfo: Ensure that for variable is stored in a local and tag it with the user-defined name
2002 if (this.qil.IsDebug && ndFor.DebugName != null) {
2003 this.helper.DebugStartScope();
2005 // Ensure that values are stored in a local variable with a user-defined name
2006 this.iterCurr.EnsureLocalNoCache("$$$for");
2007 this.iterCurr.Storage.LocalLocation.SetLocalSymInfo(ndFor.DebugName);
2010 // Ensure that values are not stored on the stack
2011 this.iterCurr.EnsureNoStackNoCache("$$$for");
2014 if (patt.MatchesPattern(OptimizerPatternName.IsPositional)) {
2015 // Increment position
2016 this.helper.Emit(OpCodes.Ldloc, locPos);
2017 this.helper.Emit(OpCodes.Ldc_I4_1);
2018 this.helper.Emit(OpCodes.Add);
2019 this.helper.Emit(OpCodes.Stloc, locPos);
2021 if (patt.MatchesPattern(OptimizerPatternName.MaxPosition)) {
2022 // Short-circuit rest of loop if max position has already been reached
2023 this.helper.Emit(OpCodes.Ldloc, locPos);
2024 this.helper.LoadInteger((int) patt.GetArgument(OptimizerPatternArgument.MaxPosition));
2025 this.helper.Emit(OpCodes.Bgt, this.iterCurr.ParentIterator.GetLabelNext());
2028 this.iterCurr.LocalPosition = locPos;
2031 EndNestedIterator(ndFor.Binding);
2032 this.iterCurr.SetIterator(this.iterNested);
2036 /// Bind values in the "ndLet" expression to a non-stack location that can later be referenced.
2038 public void StartLetBinding(QilIterator ndLet) {
2039 Debug.Assert(!ndLet.XmlType.IsSingleton);
2041 // Construct nested iterator
2042 StartNestedIterator(ndLet);
2044 // Allow base internal class to dispatch based on QilExpression node type
2045 NestedVisit(ndLet.Binding, GetItemStorageType(ndLet), !ndLet.XmlType.IsSingleton);
2047 // DebugInfo: Open variable scope
2048 // DebugInfo: Ensure that for variable is stored in a local and tag it with the user-defined name
2049 if (this.qil.IsDebug && ndLet.DebugName != null) {
2050 this.helper.DebugStartScope();
2052 // Ensure that cache is stored in a local variable with a user-defined name
2053 this.iterCurr.EnsureLocal("$$$cache");
2054 this.iterCurr.Storage.LocalLocation.SetLocalSymInfo(ndLet.DebugName);
2057 // Ensure that cache is not stored on the stack
2058 this.iterCurr.EnsureNoStack("$$$cache");
2061 EndNestedIterator(ndLet);
2065 /// Mark iterator variables as out-of-scope.
2067 private void EndBinding(QilIterator ndIter) {
2068 Debug.Assert(ndIter != null);
2070 // Variables go out of scope here
2071 if (this.qil.IsDebug && ndIter.DebugName != null)
2072 this.helper.DebugEndScope();
2076 /// Generate code for QilNodeType.PositionOf.
2078 protected override QilNode VisitPositionOf(QilUnary ndPos) {
2079 QilIterator ndIter = ndPos.Child as QilIterator;
2080 LocalBuilder locPos;
2081 Debug.Assert(ndIter.NodeType == QilNodeType.For);
2083 locPos = XmlILAnnotation.Write(ndIter).CachedIteratorDescriptor.LocalPosition;
2084 Debug.Assert(locPos != null);
2085 this.iterCurr.Storage = StorageDescriptor.Local(locPos, typeof(int), false);
2091 /// Generate code for QilNodeType.Sort.
2093 protected override QilNode VisitSort(QilLoop ndSort) {
2094 Type itemStorageType = GetItemStorageType(ndSort);
2095 LocalBuilder locCache, locKeys;
2096 Label lblOnEndSort = this.helper.DefineLabel();
2097 Debug.Assert(ndSort.Variable.NodeType == QilNodeType.For);
2099 // XmlQuerySequence<T> cache;
2100 // cache = XmlQuerySequence.CreateOrReuse(cache);
2101 XmlILStorageMethods methods = XmlILMethods.StorageMethods[itemStorageType];
2102 locCache = this.helper.DeclareLocal("$$$cache", methods.SeqType);
2103 this.helper.Emit(OpCodes.Ldloc, locCache);
2104 this.helper.CallToken(methods.SeqReuse);
2105 this.helper.Emit(OpCodes.Stloc, locCache);
2106 this.helper.Emit(OpCodes.Ldloc, locCache);
2108 // XmlSortKeyAccumulator keys;
2109 // keys.Create(runtime);
2110 locKeys = this.helper.DeclareLocal("$$$keys", typeof(XmlSortKeyAccumulator));
2111 this.helper.Emit(OpCodes.Ldloca, locKeys);
2112 this.helper.Call(XmlILMethods.SortKeyCreate);
2114 // Construct nested iterator
2115 // foreach (item in sort-expr) {
2116 StartNestedIterator(ndSort.Variable, lblOnEndSort);
2117 StartBinding(ndSort.Variable);
2118 Debug.Assert(!this.iterNested.Storage.IsCached);
2121 this.iterCurr.EnsureStackNoCache();
2122 this.iterCurr.EnsureItemStorageType(ndSort.Variable.XmlType, GetItemStorageType(ndSort.Variable));
2123 this.helper.Call(methods.SeqAdd);
2125 this.helper.Emit(OpCodes.Ldloca, locKeys);
2127 // Add keys to accumulator (there may be several keys)
2128 foreach (QilSortKey ndKey in ndSort.Body)
2129 VisitSortKey(ndKey, locKeys);
2131 // keys.FinishSortKeys();
2132 this.helper.Call(XmlILMethods.SortKeyFinish);
2135 this.helper.Emit(OpCodes.Ldloc, locCache);
2136 this.iterCurr.LoopToEnd(lblOnEndSort);
2138 // Remove cache reference from stack
2139 this.helper.Emit(OpCodes.Pop);
2141 // cache.SortByKeys(keys.Keys);
2142 this.helper.Emit(OpCodes.Ldloc, locCache);
2143 this.helper.Emit(OpCodes.Ldloca, locKeys);
2144 this.helper.Call(XmlILMethods.SortKeyKeys);
2145 this.helper.Call(methods.SeqSortByKeys);
2147 // End nested iterator
2148 this.iterCurr.Storage = StorageDescriptor.Local(locCache, itemStorageType, true);
2149 EndBinding(ndSort.Variable);
2150 EndNestedIterator(ndSort.Variable);
2151 this.iterCurr.SetIterator(this.iterNested);
2157 /// Generate code to add a (value, collation) sort key to the XmlSortKeyAccumulator.
2159 private void VisitSortKey(QilSortKey ndKey, LocalBuilder locKeys) {
2161 Debug.Assert(ndKey.Key.XmlType.IsAtomicValue, "Sort key must be an atomic value.");
2163 // Push collation onto the stack
2164 this.helper.Emit(OpCodes.Ldloca, locKeys);
2165 if (ndKey.Collation.NodeType == QilNodeType.LiteralString) {
2166 // collation = runtime.GetCollation(idx);
2167 this.helper.CallGetCollation(this.helper.StaticData.DeclareCollation((string) (QilLiteral) ndKey.Collation));
2170 // collation = runtime.CreateCollation(str);
2171 this.helper.LoadQueryRuntime();
2172 NestedVisitEnsureStack(ndKey.Collation);
2173 this.helper.Call(XmlILMethods.CreateCollation);
2176 if (ndKey.XmlType.IsSingleton) {
2177 NestedVisitEnsureStack(ndKey.Key);
2179 // keys.AddSortKey(collation, value);
2180 this.helper.AddSortKey(ndKey.Key.XmlType);
2183 lblOnEndKey = this.helper.DefineLabel();
2184 StartNestedIterator(ndKey.Key, lblOnEndKey);
2186 this.iterCurr.EnsureStackNoCache();
2187 this.iterCurr.EnsureItemStorageType(ndKey.Key.XmlType, GetItemStorageType(ndKey.Key));
2189 // Non-empty sort key
2190 // keys.AddSortKey(collation, value);
2191 this.helper.AddSortKey(ndKey.Key.XmlType);
2195 Label lblDone = this.helper.DefineLabel();
2196 this.helper.EmitUnconditionalBranch(OpCodes.Br_S, lblDone);
2197 this.helper.MarkLabel(lblOnEndKey);
2199 // Empty sequence key
2200 // keys.AddSortKey(collation);
2201 this.helper.AddSortKey(null);
2203 this.helper.MarkLabel(lblDone);
2205 EndNestedIterator(ndKey.Key);
2210 /// Generate code for for QilNodeType.DocOrderDistinct.
2212 protected override QilNode VisitDocOrderDistinct(QilUnary ndDod) {
2213 // DocOrderDistinct applied to a singleton is a no-op
2214 if (ndDod.XmlType.IsSingleton)
2215 return Visit(ndDod.Child);
2217 // Handle any special-case patterns that are rooted at DocOrderDistinct
2218 if (HandleDodPatterns(ndDod))
2221 // Sort results of child expression by document order and remove duplicate nodes
2222 // cache = runtime.DocOrderDistinct(cache);
2223 this.helper.LoadQueryRuntime();
2224 NestedVisitEnsureCache(ndDod.Child, typeof(XPathNavigator));
2225 this.iterCurr.EnsureStack();
2226 this.helper.Call(XmlILMethods.DocOrder);
2231 /// There are a number of path patterns that can be rooted at DocOrderDistinct nodes. Determine whether one of these
2232 /// patterns has been previously matched on "ndDod". If so, generate code for the pattern and return true. Otherwise,
2233 /// just return false.
2235 private bool HandleDodPatterns(QilUnary ndDod) {
2236 OptimizerPatterns pattDod = OptimizerPatterns.Read(ndDod);
2237 XmlNodeKindFlags kinds;
2239 QilNode input, step;
2242 // Handle JoinAndDod and DodReverse patterns
2243 isJoinAndDod = pattDod.MatchesPattern(OptimizerPatternName.JoinAndDod);
2244 if (isJoinAndDod || pattDod.MatchesPattern(OptimizerPatternName.DodReverse)) {
2245 OptimizerPatterns pattStep = OptimizerPatterns.Read((QilNode) pattDod.GetArgument(OptimizerPatternArgument.DodStep));
2247 if (pattStep.MatchesPattern(OptimizerPatternName.FilterElements)) {
2248 // FilterElements pattern, so Kind = Element and Name = Argument
2249 kinds = XmlNodeKindFlags.Element;
2250 name = (QilName) pattStep.GetArgument(OptimizerPatternArgument.ElementQName);
2252 else if (pattStep.MatchesPattern(OptimizerPatternName.FilterContentKind)) {
2253 // FilterKindTest pattern, so Kind = Argument and Name = null
2254 kinds = ((XmlQueryType) pattStep.GetArgument(OptimizerPatternArgument.KindTestType)).NodeKinds;
2258 Debug.Assert(pattStep.MatchesPattern(OptimizerPatternName.Axis), "Dod patterns should only match if step is FilterElements or FilterKindTest or Axis");
2259 kinds = ((ndDod.XmlType.NodeKinds & XmlNodeKindFlags.Attribute) != 0) ? XmlNodeKindFlags.Any : XmlNodeKindFlags.Content;
2263 step = (QilNode) pattStep.GetArgument(OptimizerPatternArgument.StepNode);
2265 switch (step.NodeType) {
2266 case QilNodeType.Content:
2267 CreateContainerIterator(ndDod, "$$$iterContent", typeof(ContentMergeIterator), XmlILMethods.ContentMergeCreate, XmlILMethods.ContentMergeNext,
2268 kinds, name, TriState.Unknown);
2271 case QilNodeType.Descendant:
2272 case QilNodeType.DescendantOrSelf:
2273 CreateContainerIterator(ndDod, "$$$iterDesc", typeof(DescendantMergeIterator), XmlILMethods.DescMergeCreate, XmlILMethods.DescMergeNext,
2274 kinds, name, (step.NodeType == QilNodeType.Descendant) ? TriState.False : TriState.True);
2277 case QilNodeType.XPathFollowing:
2278 CreateContainerIterator(ndDod, "$$$iterFoll", typeof(XPathFollowingMergeIterator), XmlILMethods.XPFollMergeCreate, XmlILMethods.XPFollMergeNext,
2279 kinds, name, TriState.Unknown);
2282 case QilNodeType.FollowingSibling:
2283 CreateContainerIterator(ndDod, "$$$iterFollSib", typeof(FollowingSiblingMergeIterator), XmlILMethods.FollSibMergeCreate, XmlILMethods.FollSibMergeNext,
2284 kinds, name, TriState.Unknown);
2287 case QilNodeType.XPathPreceding:
2288 CreateContainerIterator(ndDod, "$$$iterPrec", typeof(XPathPrecedingMergeIterator), XmlILMethods.XPPrecMergeCreate, XmlILMethods.XPPrecMergeNext,
2289 kinds, name, TriState.Unknown);
2293 Debug.Assert(false, "Pattern " + step.NodeType + " should have been handled.");
2298 input = (QilNode) pattStep.GetArgument(OptimizerPatternArgument.StepInput);
2299 switch (step.NodeType) {
2300 case QilNodeType.Ancestor:
2301 case QilNodeType.AncestorOrSelf:
2302 CreateFilteredIterator(input, "$$$iterAnc", typeof(AncestorDocOrderIterator), XmlILMethods.AncDOCreate, XmlILMethods.AncDONext,
2303 kinds, name, (step.NodeType == QilNodeType.Ancestor) ? TriState.False : TriState.True, null);
2306 case QilNodeType.PrecedingSibling:
2307 CreateFilteredIterator(input, "$$$iterPreSib", typeof(PrecedingSiblingDocOrderIterator), XmlILMethods.PreSibDOCreate, XmlILMethods.PreSibDONext,
2308 kinds, name, TriState.Unknown, null);
2311 case QilNodeType.XPathPreceding:
2312 CreateFilteredIterator(input, "$$$iterPrec", typeof(XPathPrecedingDocOrderIterator), XmlILMethods.XPPrecDOCreate, XmlILMethods.XPPrecDONext,
2313 kinds, name, TriState.Unknown, null);
2317 Debug.Assert(false, "Pattern " + step.NodeType + " should have been handled.");
2322 else if (pattDod.MatchesPattern(OptimizerPatternName.DodMerge)) {
2323 // DodSequenceMerge dodMerge;
2324 LocalBuilder locMerge = this.helper.DeclareLocal("$$$dodMerge", typeof(DodSequenceMerge));
2325 Label lblOnEnd = this.helper.DefineLabel();
2327 // dodMerge.Create(runtime);
2328 this.helper.Emit(OpCodes.Ldloca, locMerge);
2329 this.helper.LoadQueryRuntime();
2330 this.helper.Call(XmlILMethods.DodMergeCreate);
2331 this.helper.Emit(OpCodes.Ldloca, locMerge);
2333 StartNestedIterator(ndDod.Child, lblOnEnd);
2335 // foreach (seq in expr) {
2338 // dodMerge.AddSequence(seq);
2339 Debug.Assert(this.iterCurr.Storage.IsCached, "DodMerge pattern should only be matched when cached sequences are returned from loop");
2340 this.iterCurr.EnsureStack();
2341 this.helper.Call(XmlILMethods.DodMergeAdd);
2342 this.helper.Emit(OpCodes.Ldloca, locMerge);
2345 this.iterCurr.LoopToEnd(lblOnEnd);
2347 EndNestedIterator(ndDod.Child);
2349 // mergedSequence = dodMerge.MergeSequences();
2350 this.helper.Call(XmlILMethods.DodMergeSeq);
2352 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), true);
2361 /// Generate code for for QilNodeType.Invoke.
2363 protected override QilNode VisitInvoke(QilInvoke ndInvoke) {
2364 QilFunction ndFunc = ndInvoke.Function;
2365 MethodInfo methInfo = XmlILAnnotation.Write(ndFunc).FunctionBinding;
2366 bool useWriter = (XmlILConstructInfo.Read(ndFunc).ConstructMethod == XmlILConstructMethod.Writer);
2367 Debug.Assert(!XmlILConstructInfo.Read(ndInvoke).PushToWriterFirst || useWriter);
2369 // Push XmlQueryRuntime onto the stack as the first parameter
2370 this.helper.LoadQueryRuntime();
2372 // Generate code to push each Invoke argument onto the stack
2373 for (int iArg = 0; iArg < ndInvoke.Arguments.Count; iArg++) {
2374 QilNode ndActualArg = ndInvoke.Arguments[iArg];
2375 QilNode ndFormalArg = ndInvoke.Function.Arguments[iArg];
2376 NestedVisitEnsureStack(ndActualArg, GetItemStorageType(ndFormalArg), !ndFormalArg.XmlType.IsSingleton);
2379 // Check whether this call should compiled using the .tailcall instruction
2380 if (OptimizerPatterns.Read(ndInvoke).MatchesPattern(OptimizerPatternName.TailCall))
2381 this.helper.TailCall(methInfo);
2383 this.helper.Call(methInfo);
2385 // If function's results are not pushed to Writer,
2387 // Return value is on the stack; ensure it has the correct storage type
2388 this.iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndInvoke), !ndInvoke.XmlType.IsSingleton);
2391 this.iterCurr.Storage = StorageDescriptor.None();
2398 /// Generate code for for QilNodeType.Content.
2400 protected override QilNode VisitContent(QilUnary ndContent) {
2401 CreateSimpleIterator(ndContent.Child, "$$$iterAttrContent", typeof(AttributeContentIterator), XmlILMethods.AttrContentCreate, XmlILMethods.AttrContentNext);
2406 /// Generate code for for QilNodeType.Attribute.
2408 protected override QilNode VisitAttribute(QilBinary ndAttr) {
2409 QilName ndName = ndAttr.Right as QilName;
2410 Debug.Assert(ndName != null, "Attribute node must have a literal QName as its second argument");
2412 // XPathNavigator navAttr;
2413 LocalBuilder locNav = this.helper.DeclareLocal("$$$navAttr", typeof(XPathNavigator));
2415 // navAttr = SyncToNavigator(navAttr, navCtxt);
2416 SyncToNavigator(locNav, ndAttr.Left);
2418 // if (!navAttr.MoveToAttribute(localName, namespaceUri)) goto LabelNextCtxt;
2419 this.helper.Emit(OpCodes.Ldloc, locNav);
2420 this.helper.CallGetAtomizedName(this.helper.StaticData.DeclareName(ndName.LocalName));
2421 this.helper.CallGetAtomizedName(this.helper.StaticData.DeclareName(ndName.NamespaceUri));
2422 this.helper.Call(XmlILMethods.NavMoveAttr);
2423 this.helper.Emit(OpCodes.Brfalse, this.iterCurr.GetLabelNext());
2425 this.iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
2430 /// Generate code for for QilNodeType.Parent.
2432 protected override QilNode VisitParent(QilUnary ndParent) {
2433 // XPathNavigator navParent;
2434 LocalBuilder locNav = this.helper.DeclareLocal("$$$navParent", typeof(XPathNavigator));
2436 // navParent = SyncToNavigator(navParent, navCtxt);
2437 SyncToNavigator(locNav, ndParent.Child);
2439 // if (!navParent.MoveToParent()) goto LabelNextCtxt;
2440 this.helper.Emit(OpCodes.Ldloc, locNav);
2441 this.helper.Call(XmlILMethods.NavMoveParent);
2442 this.helper.Emit(OpCodes.Brfalse, this.iterCurr.GetLabelNext());
2444 this.iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
2449 /// Generate code for for QilNodeType.Root.
2451 protected override QilNode VisitRoot(QilUnary ndRoot) {
2452 // XPathNavigator navRoot;
2453 LocalBuilder locNav = this.helper.DeclareLocal("$$$navRoot", typeof(XPathNavigator));
2455 // navRoot = SyncToNavigator(navRoot, navCtxt);
2456 SyncToNavigator(locNav, ndRoot.Child);
2458 // navRoot.MoveToRoot();
2459 this.helper.Emit(OpCodes.Ldloc, locNav);
2460 this.helper.Call(XmlILMethods.NavMoveRoot);
2462 this.iterCurr.Storage = StorageDescriptor.Local(locNav, typeof(XPathNavigator), false);
2467 /// Generate code for QilNodeType.XmlContext.
2470 /// Generates code to retrieve the default document using the XmlResolver.
2472 protected override QilNode VisitXmlContext(QilNode ndCtxt) {
2473 // runtime.ExternalContext.DefaultDataSource
2474 this.helper.LoadQueryContext();
2475 this.helper.Call(XmlILMethods.GetDefaultDataSource);
2476 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), false);
2481 /// Find physical query plan for QilNodeType.Descendant.
2483 protected override QilNode VisitDescendant(QilUnary ndDesc) {
2484 CreateFilteredIterator(ndDesc.Child, "$$$iterDesc", typeof(DescendantIterator), XmlILMethods.DescCreate, XmlILMethods.DescNext,
2485 XmlNodeKindFlags.Any, null, TriState.False, null);
2490 /// Generate code for for QilNodeType.DescendantOrSelf.
2492 protected override QilNode VisitDescendantOrSelf(QilUnary ndDesc) {
2493 CreateFilteredIterator(ndDesc.Child, "$$$iterDesc", typeof(DescendantIterator), XmlILMethods.DescCreate, XmlILMethods.DescNext,
2494 XmlNodeKindFlags.Any, null, TriState.True, null);
2499 /// Find physical query plan for QilNodeType.Ancestor.
2501 protected override QilNode VisitAncestor(QilUnary ndAnc) {
2502 CreateFilteredIterator(ndAnc.Child, "$$$iterAnc", typeof(AncestorIterator), XmlILMethods.AncCreate, XmlILMethods.AncNext,
2503 XmlNodeKindFlags.Any, null, TriState.False, null);
2508 /// Find physical query plan for QilNodeType.AncestorOrSelf.
2510 protected override QilNode VisitAncestorOrSelf(QilUnary ndAnc) {
2511 CreateFilteredIterator(ndAnc.Child, "$$$iterAnc", typeof(AncestorIterator), XmlILMethods.AncCreate, XmlILMethods.AncNext,
2512 XmlNodeKindFlags.Any, null, TriState.True, null);
2517 /// Find physical query plan for QilNodeType.Preceding.
2519 protected override QilNode VisitPreceding(QilUnary ndPrec) {
2520 CreateFilteredIterator(ndPrec.Child, "$$$iterPrec", typeof(PrecedingIterator), XmlILMethods.PrecCreate, XmlILMethods.PrecNext,
2521 XmlNodeKindFlags.Any, null, TriState.Unknown, null);
2526 /// Find physical query plan for QilNodeType.FollowingSibling.
2528 protected override QilNode VisitFollowingSibling(QilUnary ndFollSib) {
2529 CreateFilteredIterator(ndFollSib.Child, "$$$iterFollSib", typeof(FollowingSiblingIterator), XmlILMethods.FollSibCreate, XmlILMethods.FollSibNext,
2530 XmlNodeKindFlags.Any, null, TriState.Unknown, null);
2535 /// Find physical query plan for QilNodeType.PrecedingSibling.
2537 protected override QilNode VisitPrecedingSibling(QilUnary ndPreSib) {
2538 CreateFilteredIterator(ndPreSib.Child, "$$$iterPreSib", typeof(PrecedingSiblingIterator), XmlILMethods.PreSibCreate, XmlILMethods.PreSibNext,
2539 XmlNodeKindFlags.Any, null, TriState.Unknown, null);
2544 /// Find physical query plan for QilNodeType.NodeRange.
2546 protected override QilNode VisitNodeRange(QilBinary ndRange) {
2547 CreateFilteredIterator(ndRange.Left, "$$$iterRange", typeof(NodeRangeIterator), XmlILMethods.NodeRangeCreate, XmlILMethods.NodeRangeNext,
2548 XmlNodeKindFlags.Any, null, TriState.Unknown, ndRange.Right);
2553 /// Generate code for for QilNodeType.Deref.
2555 protected override QilNode VisitDeref(QilBinary ndDeref) {
2556 // IdIterator iterId;
2557 LocalBuilder locIter = this.helper.DeclareLocal("$$$iterId", typeof(IdIterator));
2559 // iterId.Create(navCtxt, value);
2560 this.helper.Emit(OpCodes.Ldloca, locIter);
2561 NestedVisitEnsureStack(ndDeref.Left);
2562 NestedVisitEnsureStack(ndDeref.Right);
2563 this.helper.Call(XmlILMethods.IdCreate);
2565 GenerateSimpleIterator(typeof(XPathNavigator), locIter, XmlILMethods.IdNext);
2571 /// Generate code for QilNodeType.ElementCtor.
2573 protected override QilNode VisitElementCtor(QilBinary ndElem) {
2574 XmlILConstructInfo info = XmlILConstructInfo.Read(ndElem);
2576 GenerateNameType nameType;
2577 Debug.Assert(XmlILConstructInfo.Read(ndElem).PushToWriterFirst, "Element contruction should always be pushed to writer.");
2579 // Runtime checks must be made in the following cases:
2580 // 1. Xml state is not known at compile-time, or is illegal
2581 // 2. Element's namespace must be declared
2582 // 3. Element's attributes might be duplicates of one another, or namespaces might follow attributes
2583 callChk = CheckWithinContent(info) || !info.IsNamespaceInScope || ElementCachesAttributes(info);
2585 // If it is not known whether element content was output, then make this check at run-time
2586 if (XmlILConstructInfo.Read(ndElem.Right).FinalStates == PossibleXmlStates.Any)
2589 // If runtime state after EndElement is called is not known, then call XmlQueryOutput.WriteEndElementChk
2590 if (info.FinalStates == PossibleXmlStates.Any)
2593 // If WriteStartElementChk will *not* be called, then code must be generated to ensure valid state transitions
2595 BeforeStartChecks(ndElem);
2597 // Generate call to WriteStartElement
2598 nameType = LoadNameAndType(XPathNodeType.Element, ndElem.Left, true, callChk);
2599 this.helper.CallWriteStartElement(nameType, callChk);
2601 // Recursively construct content
2602 NestedVisit(ndElem.Right);
2604 // If runtime state is guaranteed to be EnumAttrs, and an element is being constructed, call XmlQueryOutput.StartElementContent
2605 if (XmlILConstructInfo.Read(ndElem.Right).FinalStates == PossibleXmlStates.EnumAttrs && !callChk)
2606 this.helper.CallStartElementContent();
2608 // Generate call to WriteEndElement
2609 nameType = LoadNameAndType(XPathNodeType.Element, ndElem.Left, false, callChk);
2610 this.helper.CallWriteEndElement(nameType, callChk);
2613 AfterEndChecks(ndElem);
2615 this.iterCurr.Storage = StorageDescriptor.None();
2620 /// Generate code for QilNodeType.AttributeCtor.
2622 protected override QilNode VisitAttributeCtor(QilBinary ndAttr) {
2623 XmlILConstructInfo info = XmlILConstructInfo.Read(ndAttr);
2625 GenerateNameType nameType;
2626 Debug.Assert(XmlILConstructInfo.Read(ndAttr).PushToWriterFirst, "Attribute construction should always be pushed to writer.");
2628 // Runtime checks must be made in the following cases:
2629 // 1. Xml state is not known at compile-time, or is illegal
2630 // 2. Attribute's namespace must be declared
2631 callChk = CheckEnumAttrs(info) || !info.IsNamespaceInScope;
2633 // If WriteStartAttributeChk will *not* be called, then code must be generated to ensure well-formedness
2634 // and track namespace scope.
2636 BeforeStartChecks(ndAttr);
2638 // Generate call to WriteStartAttribute
2639 nameType = LoadNameAndType(XPathNodeType.Attribute, ndAttr.Left, true, callChk);
2640 this.helper.CallWriteStartAttribute(nameType, callChk);
2642 // Recursively construct content
2643 NestedVisit(ndAttr.Right);
2645 // Generate call to WriteEndAttribute
2646 this.helper.CallWriteEndAttribute(callChk);
2649 AfterEndChecks(ndAttr);
2651 this.iterCurr.Storage = StorageDescriptor.None();
2656 /// Generate code for QilNodeType.CommentCtor.
2658 protected override QilNode VisitCommentCtor(QilUnary ndComment) {
2659 Debug.Assert(XmlILConstructInfo.Read(ndComment).PushToWriterFirst, "Comment construction should always be pushed to writer.");
2661 // Always call XmlQueryOutput.WriteStartComment
2662 this.helper.CallWriteStartComment();
2664 // Recursively construct content
2665 NestedVisit(ndComment.Child);
2667 // Always call XmlQueryOutput.WriteEndComment
2668 this.helper.CallWriteEndComment();
2670 this.iterCurr.Storage = StorageDescriptor.None();
2675 /// Generate code for QilNodeType.PICtor.
2677 protected override QilNode VisitPICtor(QilBinary ndPI) {
2678 Debug.Assert(XmlILConstructInfo.Read(ndPI).PushToWriterFirst, "PI construction should always be pushed to writer.");
2680 // Always call XmlQueryOutput.WriteStartPI
2681 this.helper.LoadQueryOutput();
2682 NestedVisitEnsureStack(ndPI.Left);
2683 this.helper.CallWriteStartPI();
2685 // Recursively construct content
2686 NestedVisit(ndPI.Right);
2688 // Always call XmlQueryOutput.WriteEndPI
2689 this.helper.CallWriteEndPI();
2691 this.iterCurr.Storage = StorageDescriptor.None();
2696 /// Generate code for QilNodeType.TextCtor.
2698 protected override QilNode VisitTextCtor(QilUnary ndText) {
2699 return VisitTextCtor(ndText, false);
2703 /// Generate code for QilNodeType.RawTextCtor.
2705 protected override QilNode VisitRawTextCtor(QilUnary ndText) {
2706 return VisitTextCtor(ndText, true);
2710 /// Generate code for QilNodeType.TextCtor and QilNodeType.RawTextCtor.
2712 private QilNode VisitTextCtor(QilUnary ndText, bool disableOutputEscaping) {
2713 XmlILConstructInfo info = XmlILConstructInfo.Read(ndText);
2715 Debug.Assert(info.PushToWriterFirst, "Text construction should always be pushed to writer.");
2717 // Write out text in different contexts (within attribute, within element, within comment, etc.)
2718 switch (info.InitialStates) {
2719 case PossibleXmlStates.WithinAttr:
2720 case PossibleXmlStates.WithinComment:
2721 case PossibleXmlStates.WithinPI:
2726 callChk = CheckWithinContent(info);
2731 BeforeStartChecks(ndText);
2733 this.helper.LoadQueryOutput();
2735 // Push string value of text onto IL stack
2736 NestedVisitEnsureStack(ndText.Child);
2738 // Write out text in different contexts (within attribute, within element, within comment, etc.)
2739 switch (info.InitialStates) {
2740 case PossibleXmlStates.WithinAttr:
2741 // Ignore hints when writing out attribute text
2742 this.helper.CallWriteString(false, callChk);
2745 case PossibleXmlStates.WithinComment:
2746 // Call XmlQueryOutput.WriteCommentString
2747 this.helper.Call(XmlILMethods.CommentText);
2750 case PossibleXmlStates.WithinPI:
2751 // Call XmlQueryOutput.WriteProcessingInstructionString
2752 this.helper.Call(XmlILMethods.PIText);
2756 // Call XmlQueryOutput.WriteTextBlockChk, XmlQueryOutput.WriteTextBlockNoEntities, or XmlQueryOutput.WriteTextBlock
2757 this.helper.CallWriteString(disableOutputEscaping, callChk);
2762 AfterEndChecks(ndText);
2764 this.iterCurr.Storage = StorageDescriptor.None();
2769 /// Generate code for QilNodeType.DocumentCtor.
2771 protected override QilNode VisitDocumentCtor(QilUnary ndDoc) {
2772 Debug.Assert(XmlILConstructInfo.Read(ndDoc).PushToWriterFirst, "Document root construction should always be pushed to writer.");
2774 // Generate call to XmlQueryOutput.WriteStartRootChk
2775 this.helper.CallWriteStartRoot();
2777 // Recursively construct content
2778 NestedVisit(ndDoc.Child);
2780 // Generate call to XmlQueryOutput.WriteEndRootChk
2781 this.helper.CallWriteEndRoot();
2783 this.iterCurr.Storage = StorageDescriptor.None();
2789 /// Generate code for QilNodeType.NamespaceDecl.
2791 protected override QilNode VisitNamespaceDecl(QilBinary ndNmsp) {
2792 XmlILConstructInfo info = XmlILConstructInfo.Read(ndNmsp);
2794 Debug.Assert(info.PushToWriterFirst, "Namespace construction should always be pushed to writer.");
2796 // Runtime checks must be made in the following cases:
2797 // 1. Xml state is not known at compile-time, or is illegal
2798 // 2. Namespaces might be added to element after attributes have already been added
2799 callChk = CheckEnumAttrs(info) || MightHaveNamespacesAfterAttributes(info);
2801 // If WriteNamespaceDeclarationChk will *not* be called, then code must be generated to ensure well-formedness
2802 // and track namespace scope.
2804 BeforeStartChecks(ndNmsp);
2806 this.helper.LoadQueryOutput();
2808 // Recursively construct prefix and ns
2809 NestedVisitEnsureStack(ndNmsp.Left);
2810 NestedVisitEnsureStack(ndNmsp.Right);
2812 // Generate call to WriteNamespaceDecl
2813 this.helper.CallWriteNamespaceDecl(callChk);
2816 AfterEndChecks(ndNmsp);
2818 this.iterCurr.Storage = StorageDescriptor.None();
2823 /// Generate code for for QilNodeType.RtfCtor.
2825 protected override QilNode VisitRtfCtor(QilBinary ndRtf) {
2826 OptimizerPatterns patt = OptimizerPatterns.Read(ndRtf);
2827 string baseUri = (string) (QilLiteral) ndRtf.Right;
2829 if (patt.MatchesPattern(OptimizerPatternName.SingleTextRtf)) {
2830 // Special-case Rtf containing a root node and a single text node child
2831 this.helper.LoadQueryRuntime();
2832 NestedVisitEnsureStack((QilNode) patt.GetArgument(OptimizerPatternArgument.RtfText));
2833 this.helper.Emit(OpCodes.Ldstr, baseUri);
2834 this.helper.Call(XmlILMethods.RtfConstr);
2837 // Start nested construction of an Rtf
2838 this.helper.CallStartRtfConstruction(baseUri);
2840 // Write content of Rtf to writer
2841 NestedVisit(ndRtf.Left);
2843 // Get the result Rtf
2844 this.helper.CallEndRtfConstruction();
2847 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathNavigator), false);
2852 /// Generate code for QilNodeType.NameOf.
2854 protected override QilNode VisitNameOf(QilUnary ndName) {
2855 return VisitNodeProperty(ndName);
2859 /// Generate code for QilNodeType.LocalNameOf.
2861 protected override QilNode VisitLocalNameOf(QilUnary ndName) {
2862 return VisitNodeProperty(ndName);
2866 /// Generate code for QilNodeType.NamespaceUriOf.
2868 protected override QilNode VisitNamespaceUriOf(QilUnary ndName) {
2869 return VisitNodeProperty(ndName);
2873 /// Generate code for QilNodeType.PrefixOf.
2875 protected override QilNode VisitPrefixOf(QilUnary ndName) {
2876 return VisitNodeProperty(ndName);
2880 /// Generate code to push the local name, namespace uri, or qname of the context navigator.
2882 private QilNode VisitNodeProperty(QilUnary ndProp) {
2883 // Generate code to push argument onto stack
2884 NestedVisitEnsureStack(ndProp.Child);
2886 switch (ndProp.NodeType) {
2887 case QilNodeType.NameOf:
2888 // push new XmlQualifiedName(navigator.LocalName, navigator.NamespaceURI);
2889 this.helper.Emit(OpCodes.Dup);
2890 this.helper.Call(XmlILMethods.NavLocalName);
2891 this.helper.Call(XmlILMethods.NavNmsp);
2892 this.helper.Construct(XmlILConstructors.QName);
2893 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XmlQualifiedName), false);
2896 case QilNodeType.LocalNameOf:
2897 // push navigator.Name;
2898 this.helper.Call(XmlILMethods.NavLocalName);
2899 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
2902 case QilNodeType.NamespaceUriOf:
2903 // push navigator.NamespaceURI;
2904 this.helper.Call(XmlILMethods.NavNmsp);
2905 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
2908 case QilNodeType.PrefixOf:
2909 // push navigator.Prefix;
2910 this.helper.Call(XmlILMethods.NavPrefix);
2911 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
2915 Debug.Assert(false);
2923 /// Find physical query plan for QilNodeType.TypeAssert.
2925 protected override QilNode VisitTypeAssert(QilTargetType ndTypeAssert) {
2926 if (!ndTypeAssert.Source.XmlType.IsSingleton && ndTypeAssert.XmlType.IsSingleton && !this.iterCurr.HasLabelNext) {
2927 // This case occurs when a non-singleton expression is treated as cardinality One.
2928 // The trouble is that the expression will branch to an end label when it's done iterating, so
2929 // an end label must be provided. But there is no next label in the current iteration context,
2930 // so we've got to create a dummy label instead (IL requires it). This creates an infinite loop,
2931 // but since it's known statically that the expression is cardinality One, this branch will never
2933 Label lblDummy = this.helper.DefineLabel();
2934 this.helper.MarkLabel(lblDummy);
2935 NestedVisit(ndTypeAssert.Source, lblDummy);
2938 // Generate code for child expression
2939 Visit(ndTypeAssert.Source);
2942 this.iterCurr.EnsureItemStorageType(ndTypeAssert.Source.XmlType, GetItemStorageType(ndTypeAssert));
2943 return ndTypeAssert;
2947 /// Generate code for QilNodeType.IsType.
2949 protected override QilNode VisitIsType(QilTargetType ndIsType) {
2950 XmlQueryType typDerived, typBase;
2951 XmlTypeCode codeBase;
2953 typDerived = ndIsType.Source.XmlType;
2954 typBase = ndIsType.TargetType;
2955 Debug.Assert(!typDerived.NeverSubtypeOf(typBase), "Normalizer should have eliminated IsType where source can never be a subtype of destination type.");
2957 // Special Case: Test whether singleton item is a Node
2958 if (typDerived.IsSingleton && (object) typBase == (object) TypeFactory.Node) {
2959 NestedVisitEnsureStack(ndIsType.Source);
2960 Debug.Assert(this.iterCurr.Storage.ItemStorageType == typeof(XPathItem), "If !IsNode, then storage type should be Item");
2962 // if (item.IsNode op true) goto LabelBranch;
2963 this.helper.Call(XmlILMethods.ItemIsNode);
2964 ZeroCompare(QilNodeType.Ne, true);
2969 // Special Case: Source value is a singleton Node, and we're testing whether it is an Element, Attribute, PI, etc.
2970 if (MatchesNodeKinds(ndIsType, typDerived, typBase))
2973 // Special Case: XmlTypeCode is sufficient to describe destination type
2974 if ((object) typBase == (object) TypeFactory.Double) codeBase = XmlTypeCode.Double;
2975 else if ((object) typBase == (object) TypeFactory.String) codeBase = XmlTypeCode.String;
2976 else if ((object) typBase == (object) TypeFactory.Boolean) codeBase = XmlTypeCode.Boolean;
2977 else if ((object) typBase == (object) TypeFactory.Node) codeBase = XmlTypeCode.Node;
2978 else codeBase = XmlTypeCode.None;
2980 if (codeBase != XmlTypeCode.None) {
2981 // if (runtime.MatchesXmlType(value, code) op true) goto LabelBranch;
2982 this.helper.LoadQueryRuntime();
2983 NestedVisitEnsureStack(ndIsType.Source, typeof(XPathItem), !typDerived.IsSingleton);
2984 this.helper.LoadInteger((int) codeBase);
2985 this.helper.Call(typDerived.IsSingleton ? XmlILMethods.ItemMatchesCode : XmlILMethods.SeqMatchesCode);
2986 ZeroCompare(QilNodeType.Ne, true);
2991 // if (runtime.MatchesXmlType(value, idxType) op true) goto LabelBranch;
2992 this.helper.LoadQueryRuntime();
2993 NestedVisitEnsureStack(ndIsType.Source, typeof(XPathItem), !typDerived.IsSingleton);
2994 this.helper.LoadInteger(this.helper.StaticData.DeclareXmlType(typBase));
2995 this.helper.Call(typDerived.IsSingleton ? XmlILMethods.ItemMatchesType : XmlILMethods.SeqMatchesType);
2996 ZeroCompare(QilNodeType.Ne, true);
3002 /// 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.
3003 /// Otherwise, return false, and a call to MatchesXmlType will be generated instead.
3005 private bool MatchesNodeKinds(QilTargetType ndIsType, XmlQueryType typDerived, XmlQueryType typBase) {
3006 XmlNodeKindFlags kinds;
3007 bool allowKinds = true;
3008 XPathNodeType kindsRuntime;
3011 // If not checking whether typDerived is some kind of singleton node, then fallback to MatchesXmlType
3012 if (!typBase.IsNode || !typBase.IsSingleton)
3015 // If typDerived is not statically guaranteed to be a singleton node (and not an rtf), then fallback to MatchesXmlType
3016 if (!typDerived.IsNode || !typDerived.IsSingleton || !typDerived.IsNotRtf)
3019 // Now we are guaranteed that typDerived is a node, and typBase is a node, so check node kinds
3020 // Ensure that typBase is only composed of kind-test prime types (no name-test, no schema-test, etc.)
3021 kinds = XmlNodeKindFlags.None;
3022 foreach (XmlQueryType typItem in typBase) {
3023 if ((object) typItem == (object) TypeFactory.Element) kinds |= XmlNodeKindFlags.Element;
3024 else if ((object) typItem == (object) TypeFactory.Attribute) kinds |= XmlNodeKindFlags.Attribute;
3025 else if ((object) typItem == (object) TypeFactory.Text) kinds |= XmlNodeKindFlags.Text;
3026 else if ((object) typItem == (object) TypeFactory.Document) kinds |= XmlNodeKindFlags.Document;
3027 else if ((object) typItem == (object) TypeFactory.Comment) kinds |= XmlNodeKindFlags.Comment;
3028 else if ((object) typItem == (object) TypeFactory.PI) kinds |= XmlNodeKindFlags.PI;
3029 else if ((object) typItem == (object) TypeFactory.Namespace) kinds |= XmlNodeKindFlags.Namespace;
3033 Debug.Assert((typDerived.NodeKinds & kinds) != XmlNodeKindFlags.None, "Normalizer should have taken care of case where node kinds are disjoint.");
3035 kinds = typDerived.NodeKinds & kinds;
3037 // Attempt to allow or disallow exactly one kind
3038 if (!Bits.ExactlyOne((uint) kinds)) {
3039 // Not possible to allow one kind, so try to disallow one kind
3040 kinds = ~kinds & XmlNodeKindFlags.Any;
3041 allowKinds = !allowKinds;
3045 case XmlNodeKindFlags.Element: kindsRuntime = XPathNodeType.Element; break;
3046 case XmlNodeKindFlags.Attribute: kindsRuntime = XPathNodeType.Attribute; break;
3047 case XmlNodeKindFlags.Namespace: kindsRuntime = XPathNodeType.Namespace; break;
3048 case XmlNodeKindFlags.PI: kindsRuntime = XPathNodeType.ProcessingInstruction; break;
3049 case XmlNodeKindFlags.Comment: kindsRuntime = XPathNodeType.Comment; break;
3050 case XmlNodeKindFlags.Document: kindsRuntime = XPathNodeType.Root; break;
3053 // Union of several types (when testing for Text, we need to test for Whitespace as well)
3055 // if (((1 << navigator.NodeType) & nodesDisallow) op 0) goto LabelBranch;
3056 this.helper.Emit(OpCodes.Ldc_I4_1);
3057 kindsRuntime = XPathNodeType.All;
3061 // Push navigator.NodeType onto the stack
3062 NestedVisitEnsureStack(ndIsType.Source);
3063 this.helper.Call(XmlILMethods.NavType);
3065 if (kindsRuntime == XPathNodeType.All) {
3066 // if (((1 << navigator.NodeType) & kindsUnion) op 0) goto LabelBranch;
3067 this.helper.Emit(OpCodes.Shl);
3070 if ((kinds & XmlNodeKindFlags.Document) != 0) kindsUnion |= (1 << (int) XPathNodeType.Root);
3071 if ((kinds & XmlNodeKindFlags.Element) != 0) kindsUnion |= (1 << (int) XPathNodeType.Element);
3072 if ((kinds & XmlNodeKindFlags.Attribute) != 0) kindsUnion |= (1 << (int) XPathNodeType.Attribute);
3073 if ((kinds & XmlNodeKindFlags.Text) != 0) kindsUnion |= (1 << (int) (int) XPathNodeType.Text) |
3074 (1 << (int) (int) XPathNodeType.SignificantWhitespace) |
3075 (1 << (int) (int) XPathNodeType.Whitespace);
3076 if ((kinds & XmlNodeKindFlags.Comment) != 0) kindsUnion |= (1 << (int) XPathNodeType.Comment);
3077 if ((kinds & XmlNodeKindFlags.PI) != 0) kindsUnion |= (1 << (int) XPathNodeType.ProcessingInstruction);
3078 if ((kinds & XmlNodeKindFlags.Namespace) != 0) kindsUnion |= (1 << (int) XPathNodeType.Namespace);
3080 this.helper.LoadInteger(kindsUnion);
3081 this.helper.Emit(OpCodes.And);
3082 ZeroCompare(allowKinds ? QilNodeType.Ne : QilNodeType.Eq, false);
3085 // if (navigator.NodeType op runtimeItem) goto LabelBranch;
3086 this.helper.LoadInteger((int) kindsRuntime);
3087 ClrCompare(allowKinds ? QilNodeType.Eq : QilNodeType.Ne, XmlTypeCode.Int);
3094 /// Generate code for QilNodeType.IsEmpty.
3097 /// BranchingContext.OnFalse context: is-empty(expr)
3098 /// ==> foreach (item in expr)
3099 /// goto LabelBranch;
3101 /// BranchingContext.OnTrue context: is-empty(expr)
3102 /// ==> foreach (item in expr)
3105 /// LabelOnEnd: (called if foreach is empty)
3106 /// goto LabelBranch;
3108 /// BranchingContext.None context: is-empty(expr)
3109 /// ==> foreach (item in expr)
3113 /// LabelOnEnd: (called if foreach is empty)
3116 protected override QilNode VisitIsEmpty(QilUnary ndIsEmpty) {
3119 // If the child expression returns a cached result,
3120 if (CachesResult(ndIsEmpty.Child)) {
3121 // Then get the count directly from the cache
3122 NestedVisitEnsureStack(ndIsEmpty.Child);
3123 this.helper.CallCacheCount(this.iterNested.Storage.ItemStorageType);
3125 switch (this.iterCurr.CurrentBranchingContext) {
3126 case BranchingContext.OnFalse:
3127 // Take false path if count != 0
3128 this.helper.TestAndBranch(0, this.iterCurr.LabelBranch, OpCodes.Bne_Un);
3131 case BranchingContext.OnTrue:
3132 // Take true path if count == 0
3133 this.helper.TestAndBranch(0, this.iterCurr.LabelBranch, OpCodes.Beq);
3137 Debug.Assert(this.iterCurr.CurrentBranchingContext == BranchingContext.None);
3139 // if (count == 0) goto LabelTrue;
3140 lblTrue = this.helper.DefineLabel();
3141 this.helper.Emit(OpCodes.Brfalse_S, lblTrue);
3143 // Convert branch targets into push of true/false
3144 this.helper.ConvBranchToBool(lblTrue, true);
3149 Label lblOnEnd = this.helper.DefineLabel();
3150 IteratorDescriptor iterParent = this.iterCurr;
3152 // Forward any LabelOnEnd jumps to LabelBranch if BranchingContext.OnTrue
3153 if (iterParent.CurrentBranchingContext == BranchingContext.OnTrue)
3154 StartNestedIterator(ndIsEmpty.Child, this.iterCurr.LabelBranch);
3156 StartNestedIterator(ndIsEmpty.Child, lblOnEnd);
3158 Visit(ndIsEmpty.Child);
3160 // Pop value of IsEmpty expression from the stack if necessary
3161 this.iterCurr.EnsureNoCache();
3162 this.iterCurr.DiscardStack();
3164 switch (iterParent.CurrentBranchingContext) {
3165 case BranchingContext.OnFalse:
3166 // Reverse polarity of iterator
3167 this.helper.EmitUnconditionalBranch(OpCodes.Br, iterParent.LabelBranch);
3168 this.helper.MarkLabel(lblOnEnd);
3171 case BranchingContext.OnTrue:
3175 case BranchingContext.None:
3176 // Convert branch targets into push of true/false
3177 this.helper.ConvBranchToBool(lblOnEnd, true);
3181 // End nested iterator
3182 EndNestedIterator(ndIsEmpty.Child);
3185 if (this.iterCurr.IsBranching)
3186 this.iterCurr.Storage = StorageDescriptor.None();
3188 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
3194 /// Generate code for QilNodeType.XPathNodeValue.
3196 protected override QilNode VisitXPathNodeValue(QilUnary ndVal) {
3197 Label lblOnEnd, lblDone;
3198 Debug.Assert(ndVal.Child.XmlType.IsNode, "XPathNodeValue node may only be applied to a sequence of Nodes.");
3200 // If the expression is a singleton,
3201 if (ndVal.Child.XmlType.IsSingleton) {
3202 // Then generate code to push expresion result onto the stack
3203 NestedVisitEnsureStack(ndVal.Child, typeof(XPathNavigator), false);
3206 this.helper.Call(XmlILMethods.Value);
3209 lblOnEnd = this.helper.DefineLabel();
3211 // Construct nested iterator and iterate over results
3212 StartNestedIterator(ndVal.Child, lblOnEnd);
3214 this.iterCurr.EnsureStackNoCache();
3217 this.helper.Call(XmlILMethods.Value);
3219 // Handle empty sequence by pushing empty string onto the stack
3220 lblDone = this.helper.DefineLabel();
3221 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
3222 this.helper.MarkLabel(lblOnEnd);
3223 this.helper.Emit(OpCodes.Ldstr, "");
3224 this.helper.MarkLabel(lblDone);
3226 // End nested iterator
3227 EndNestedIterator(ndVal.Child);
3230 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
3236 /// Find physical query plan for QilNodeType.XPathFollowing.
3238 protected override QilNode VisitXPathFollowing(QilUnary ndFoll) {
3239 CreateFilteredIterator(ndFoll.Child, "$$$iterFoll", typeof(XPathFollowingIterator), XmlILMethods.XPFollCreate, XmlILMethods.XPFollNext,
3240 XmlNodeKindFlags.Any, null, TriState.Unknown, null);
3245 /// Find physical query plan for QilNodeType.XPathPreceding.
3247 protected override QilNode VisitXPathPreceding(QilUnary ndPrec) {
3248 CreateFilteredIterator(ndPrec.Child, "$$$iterPrec", typeof(XPathPrecedingIterator), XmlILMethods.XPPrecCreate, XmlILMethods.XPPrecNext,
3249 XmlNodeKindFlags.Any, null, TriState.Unknown, null);
3254 /// Find physical query plan for QilNodeType.XPathNamespace.
3256 protected override QilNode VisitXPathNamespace(QilUnary ndNmsp) {
3257 CreateSimpleIterator(ndNmsp.Child, "$$$iterNmsp", typeof(NamespaceIterator), XmlILMethods.NmspCreate, XmlILMethods.NmspNext);
3262 /// Generate code for QilNodeType.XsltGenerateId.
3264 protected override QilNode VisitXsltGenerateId(QilUnary ndGenId) {
3265 Label lblOnEnd, lblDone;
3267 this.helper.LoadQueryRuntime();
3269 // If the expression is a singleton,
3270 if (ndGenId.Child.XmlType.IsSingleton) {
3271 // Then generate code to push expresion result onto the stack
3272 NestedVisitEnsureStack(ndGenId.Child, typeof(XPathNavigator), false);
3274 // runtime.GenerateId(value);
3275 this.helper.Call(XmlILMethods.GenId);
3278 lblOnEnd = this.helper.DefineLabel();
3280 // Construct nested iterator and iterate over results
3281 StartNestedIterator(ndGenId.Child, lblOnEnd);
3282 Visit(ndGenId.Child);
3283 this.iterCurr.EnsureStackNoCache();
3284 this.iterCurr.EnsureItemStorageType(ndGenId.Child.XmlType, typeof(XPathNavigator));
3286 // runtime.GenerateId(value);
3287 this.helper.Call(XmlILMethods.GenId);
3289 // Handle empty sequence by pushing empty string onto the stack
3290 lblDone = this.helper.DefineLabel();
3291 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblDone);
3292 this.helper.MarkLabel(lblOnEnd);
3293 this.helper.Emit(OpCodes.Pop);
3294 this.helper.Emit(OpCodes.Ldstr, "");
3295 this.helper.MarkLabel(lblDone);
3297 // End nested iterator
3298 EndNestedIterator(ndGenId.Child);
3301 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(string), false);
3307 /// Generate code for for QilNodeType.XsltInvokeLateBound.
3309 protected override QilNode VisitXsltInvokeLateBound(QilInvokeLateBound ndInvoke) {
3310 LocalBuilder locArgs = this.helper.DeclareLocal("$$$args", typeof(IList<XPathItem>[]));
3311 QilName ndName = (QilName) ndInvoke.Name;
3312 Debug.Assert(XmlILConstructInfo.Read(ndInvoke).ConstructMethod != XmlILConstructMethod.Writer);
3314 // runtime.ExternalContext.InvokeXsltLateBoundFunction(name, ns, args);
3315 this.helper.LoadQueryContext();
3316 this.helper.Emit(OpCodes.Ldstr, ndName.LocalName);
3317 this.helper.Emit(OpCodes.Ldstr, ndName.NamespaceUri);
3319 // args = new IList<XPathItem>[argCount];
3320 this.helper.LoadInteger(ndInvoke.Arguments.Count);
3321 this.helper.Emit(OpCodes.Newarr, typeof(IList<XPathItem>));
3322 this.helper.Emit(OpCodes.Stloc, locArgs);
3324 for (int iArg = 0; iArg < ndInvoke.Arguments.Count; iArg++) {
3325 QilNode ndArg = ndInvoke.Arguments[iArg];
3330 this.helper.Emit(OpCodes.Ldloc, locArgs);
3331 this.helper.LoadInteger(iArg);
3332 this.helper.Emit(OpCodes.Ldelema, typeof(IList<XPathItem>));
3334 NestedVisitEnsureCache(ndArg, typeof(XPathItem));
3335 this.iterCurr.EnsureStack();
3337 this.helper.Emit(OpCodes.Stobj, typeof(IList<XPathItem>));
3340 this.helper.Emit(OpCodes.Ldloc, locArgs);
3342 this.helper.Call(XmlILMethods.InvokeXsltLate);
3344 // Returned item sequence is on the stack
3345 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(XPathItem), true);
3351 /// Generate code for for QilNodeType.XsltInvokeEarlyBound.
3353 protected override QilNode VisitXsltInvokeEarlyBound(QilInvokeEarlyBound ndInvoke) {
3354 QilName ndName = ndInvoke.Name;
3355 XmlExtensionFunction extFunc;
3356 Type clrTypeRetSrc, clrTypeRetDst;
3358 // Retrieve metadata from the extension function
3359 extFunc = new XmlExtensionFunction(ndName.LocalName, ndName.NamespaceUri, ndInvoke.ClrMethod);
3360 clrTypeRetSrc = extFunc.ClrReturnType;
3361 clrTypeRetDst = GetStorageType(ndInvoke);
3363 // Prepare to call runtime.ChangeTypeXsltResult
3364 if (clrTypeRetSrc != clrTypeRetDst && !ndInvoke.XmlType.IsEmpty) {
3365 this.helper.LoadQueryRuntime();
3366 this.helper.LoadInteger(this.helper.StaticData.DeclareXmlType(ndInvoke.XmlType));
3369 // If this is not a static method, then get the instance object
3370 if (!extFunc.Method.IsStatic) {
3371 // Special-case the XsltLibrary object
3372 if (ndName.NamespaceUri.Length == 0)
3373 this.helper.LoadXsltLibrary();
3375 this.helper.CallGetEarlyBoundObject(this.helper.StaticData.DeclareEarlyBound(ndName.NamespaceUri, extFunc.Method.DeclaringType), extFunc.Method.DeclaringType);
3378 // Generate code to push each Invoke argument onto the stack
3379 for (int iArg = 0; iArg < ndInvoke.Arguments.Count; iArg++) {
3380 QilNode ndActualArg;
3381 XmlQueryType xmlTypeFormalArg;
3382 Type clrTypeActualArg, clrTypeFormalArg;
3384 ndActualArg = ndInvoke.Arguments[iArg];
3386 // Infer Xml type and Clr type of formal argument
3387 xmlTypeFormalArg = extFunc.GetXmlArgumentType(iArg);
3388 clrTypeFormalArg = extFunc.GetClrArgumentType(iArg);
3390 Debug.Assert(ndActualArg.XmlType.IsSubtypeOf(xmlTypeFormalArg), "Xml type of actual arg must be a subtype of the Xml type of the formal arg");
3392 // Use different conversion rules for internal Xslt libraries. If the actual argument is
3393 // stored using Clr type T, then library must use type T, XPathItem, IList<T>, or IList<XPathItem>.
3394 // If the actual argument is stored using Clr type IList<T>, then library must use type
3395 // IList<T> or IList<XPathItem>. This is to ensure that there will not be unnecessary
3396 // conversions that take place when calling into an internal library.
3397 if (ndName.NamespaceUri.Length == 0) {
3398 Type itemType = GetItemStorageType(ndActualArg);
3400 if (clrTypeFormalArg == XmlILMethods.StorageMethods[itemType].IListType) {
3401 // Formal type is IList<T>
3402 NestedVisitEnsureStack(ndActualArg, itemType, true);
3404 else if (clrTypeFormalArg == XmlILMethods.StorageMethods[typeof(XPathItem)].IListType) {
3405 // Formal type is IList<XPathItem>
3406 NestedVisitEnsureStack(ndActualArg, typeof(XPathItem), true);
3408 else if ((ndActualArg.XmlType.IsSingleton && clrTypeFormalArg == itemType) || ndActualArg.XmlType.TypeCode == XmlTypeCode.None) {
3410 NestedVisitEnsureStack(ndActualArg, clrTypeFormalArg, false);
3412 else if (ndActualArg.XmlType.IsSingleton && clrTypeFormalArg == typeof(XPathItem)) {
3413 // Formal type is XPathItem
3414 NestedVisitEnsureStack(ndActualArg, typeof(XPathItem), false);
3417 Debug.Fail("Internal Xslt library may not use parameters of type " + clrTypeFormalArg);
3420 // There is an implicit upcast to the Xml type of the formal argument. This can change the Clr storage type.
3421 clrTypeActualArg = GetStorageType(xmlTypeFormalArg);
3423 // If the formal Clr type is typeof(object) or if it is not a supertype of the actual Clr type, then call ChangeTypeXsltArgument
3424 if (xmlTypeFormalArg.TypeCode == XmlTypeCode.Item || !clrTypeFormalArg.IsAssignableFrom(clrTypeActualArg)) {
3425 // (clrTypeFormalArg) runtime.ChangeTypeXsltArgument(xmlTypeFormalArg, (object) value, clrTypeFormalArg);
3426 this.helper.LoadQueryRuntime();
3427 this.helper.LoadInteger(this.helper.StaticData.DeclareXmlType(xmlTypeFormalArg));
3428 NestedVisitEnsureStack(ndActualArg, GetItemStorageType(xmlTypeFormalArg), !xmlTypeFormalArg.IsSingleton);
3429 this.helper.TreatAs(clrTypeActualArg, typeof(object));
3430 this.helper.LoadType(clrTypeFormalArg);
3431 this.helper.Call(XmlILMethods.ChangeTypeXsltArg);
3432 this.helper.TreatAs(typeof(object), clrTypeFormalArg);
3435 NestedVisitEnsureStack(ndActualArg, GetItemStorageType(xmlTypeFormalArg), !xmlTypeFormalArg.IsSingleton);
3440 // Invoke the target method
3441 this.helper.Call(extFunc.Method);
3443 // Return value is on the stack; convert it to canonical ILGen storage type
3444 if (ndInvoke.XmlType.IsEmpty) {
3445 this.helper.Emit(OpCodes.Ldsfld, XmlILMethods.StorageMethods[typeof(XPathItem)].SeqEmpty);
3447 else if (clrTypeRetSrc != clrTypeRetDst) {
3448 // (T) runtime.ChangeTypeXsltResult(idxType, (object) value);
3449 this.helper.TreatAs(clrTypeRetSrc, typeof(object));
3450 this.helper.Call(XmlILMethods.ChangeTypeXsltResult);
3451 this.helper.TreatAs(typeof(object), clrTypeRetDst);
3453 else if (ndName.NamespaceUri.Length != 0 && !clrTypeRetSrc.IsValueType){
3454 // Check for null if a user-defined extension function returns a reference type
3455 Label lblSkip = this.helper.DefineLabel();
3456 this.helper.Emit(OpCodes.Dup);
3457 this.helper.Emit(OpCodes.Brtrue, lblSkip);
3458 this.helper.LoadQueryRuntime();
3459 this.helper.Emit(OpCodes.Ldstr, Res.GetString(Res.Xslt_ItemNull));
3460 this.helper.Call(XmlILMethods.ThrowException);
3461 this.helper.MarkLabel(lblSkip);
3464 this.iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(ndInvoke), !ndInvoke.XmlType.IsSingleton);
3470 /// Generate code for QilNodeType.XsltCopy.
3472 protected override QilNode VisitXsltCopy(QilBinary ndCopy) {
3473 Label lblSkipContent = this.helper.DefineLabel();
3474 Debug.Assert(XmlILConstructInfo.Read(ndCopy).PushToWriterFirst);
3476 // if (!xwrtChk.StartCopyChk(navCopy)) goto LabelSkipContent;
3477 this.helper.LoadQueryOutput();
3479 NestedVisitEnsureStack(ndCopy.Left);
3480 Debug.Assert(ndCopy.Left.XmlType.IsNode);
3482 this.helper.Call(XmlILMethods.StartCopy);
3483 this.helper.Emit(OpCodes.Brfalse, lblSkipContent);
3485 // Recursively construct content
3486 NestedVisit(ndCopy.Right);
3488 // xwrtChk.EndCopyChk(navCopy);
3489 this.helper.LoadQueryOutput();
3491 NestedVisitEnsureStack(ndCopy.Left);
3492 Debug.Assert(ndCopy.Left.XmlType.IsNode);
3494 this.helper.Call(XmlILMethods.EndCopy);
3496 // LabelSkipContent:
3497 this.helper.MarkLabel(lblSkipContent);
3499 this.iterCurr.Storage = StorageDescriptor.None();
3504 /// Generate code for QilNodeType.XsltCopyOf.
3506 protected override QilNode VisitXsltCopyOf(QilUnary ndCopyOf) {
3507 Debug.Assert(XmlILConstructInfo.Read(ndCopyOf).PushToWriterFirst, "XsltCopyOf should always be pushed to writer.");
3509 this.helper.LoadQueryOutput();
3511 // XmlQueryOutput.XsltCopyOf(navigator);
3512 NestedVisitEnsureStack(ndCopyOf.Child);
3513 this.helper.Call(XmlILMethods.CopyOf);
3515 this.iterCurr.Storage = StorageDescriptor.None();
3520 /// Generate code for QilNodeType.XsltConvert.
3522 protected override QilNode VisitXsltConvert(QilTargetType ndConv) {
3523 XmlQueryType typSrc, typDst;
3526 typSrc = ndConv.Source.XmlType;
3527 typDst = ndConv.TargetType;
3529 if (GetXsltConvertMethod(typSrc, typDst, out meth)) {
3530 NestedVisitEnsureStack(ndConv.Source);
3533 // If a conversion could not be found, then convert the source expression to item or item* and try again
3534 NestedVisitEnsureStack(ndConv.Source, typeof(XPathItem), !typSrc.IsSingleton);
3535 if (!GetXsltConvertMethod(typSrc.IsSingleton ? TypeFactory.Item : TypeFactory.ItemS, typDst, out meth))
3536 Debug.Fail("Conversion from " + ndConv.Source.XmlType + " to " + ndConv.TargetType + " is not supported.");
3539 // XsltConvert.XXXToYYY(value);
3541 this.helper.Call(meth);
3543 this.iterCurr.Storage = StorageDescriptor.Stack(GetItemStorageType(typDst), !typDst.IsSingleton);
3548 /// Get the XsltConvert method that converts from "typSrc" to "typDst". Return false if no
3549 /// such method exists. This conversion matrix should match the one in XsltConvert.ExternalValueToExternalValue.
3551 private bool GetXsltConvertMethod(XmlQueryType typSrc, XmlQueryType typDst, out MethodInfo meth) {
3554 // Note, Ref.Equals is OK to use here, since we will always fall back to Item or Item* in the
3555 // case where the source or destination types do not match the static types exposed on the
3556 // XmlQueryTypeFactory. This is bad for perf if it accidentally occurs, but the results
3557 // should still be correct.
3560 if ((object) typDst == (object) TypeFactory.BooleanX) {
3561 if ((object) typSrc == (object) TypeFactory.Item) meth = XmlILMethods.ItemToBool;
3562 else if ((object) typSrc == (object) TypeFactory.ItemS) meth = XmlILMethods.ItemsToBool;
3565 else if ((object) typDst == (object) TypeFactory.DateTimeX) {
3566 if ((object) typSrc == (object) TypeFactory.StringX) meth = XmlILMethods.StrToDT;
3569 else if ((object) typDst == (object) TypeFactory.DecimalX) {
3570 if ((object) typSrc == (object) TypeFactory.DoubleX) meth = XmlILMethods.DblToDec;
3573 else if ((object) typDst == (object) TypeFactory.DoubleX) {
3574 if ((object) typSrc == (object) TypeFactory.DecimalX) meth = XmlILMethods.DecToDbl;
3575 else if ((object) typSrc == (object) TypeFactory.IntX) meth = XmlILMethods.IntToDbl;
3576 else if ((object) typSrc == (object) TypeFactory.Item) meth = XmlILMethods.ItemToDbl;
3577 else if ((object) typSrc == (object) TypeFactory.ItemS) meth = XmlILMethods.ItemsToDbl;
3578 else if ((object) typSrc == (object) TypeFactory.LongX) meth = XmlILMethods.LngToDbl;
3579 else if ((object) typSrc == (object) TypeFactory.StringX) meth = XmlILMethods.StrToDbl;
3582 else if ((object) typDst == (object) TypeFactory.IntX) {
3583 if ((object) typSrc == (object) TypeFactory.DoubleX) meth = XmlILMethods.DblToInt;
3586 else if ((object) typDst == (object) TypeFactory.LongX) {
3587 if ((object) typSrc == (object) TypeFactory.DoubleX) meth = XmlILMethods.DblToLng;
3590 else if ((object) typDst == (object) TypeFactory.NodeNotRtf) {
3591 if ((object) typSrc == (object) TypeFactory.Item) meth = XmlILMethods.ItemToNode;
3592 else if ((object) typSrc == (object) TypeFactory.ItemS) meth = XmlILMethods.ItemsToNode;
3595 else if ((object) typDst == (object) TypeFactory.NodeSDod ||
3596 (object) typDst == (object) TypeFactory.NodeNotRtfS) {
3597 if ((object) typSrc == (object) TypeFactory.Item) meth = XmlILMethods.ItemToNodes;
3598 else if ((object) typSrc == (object) TypeFactory.ItemS) meth = XmlILMethods.ItemsToNodes;
3601 else if ((object) typDst == (object) TypeFactory.StringX) {
3602 if ((object) typSrc == (object) TypeFactory.DateTimeX) meth = XmlILMethods.DTToStr;
3603 else if ((object) typSrc == (object) TypeFactory.DoubleX) meth = XmlILMethods.DblToStr;
3604 else if ((object) typSrc == (object) TypeFactory.Item) meth = XmlILMethods.ItemToStr;
3605 else if ((object) typSrc == (object) TypeFactory.ItemS) meth = XmlILMethods.ItemsToStr;
3608 return meth != null;
3612 //-----------------------------------------------
3614 //-----------------------------------------------
3617 /// Ensure that the "locNav" navigator is positioned to the context node "ndCtxt".
3619 private void SyncToNavigator(LocalBuilder locNav, QilNode ndCtxt) {
3620 this.helper.Emit(OpCodes.Ldloc, locNav);
3621 NestedVisitEnsureStack(ndCtxt);
3622 this.helper.CallSyncToNavigator();
3623 this.helper.Emit(OpCodes.Stloc, locNav);
3627 /// Generate boiler-plate code to create a simple Xml iterator.
3631 /// iter.Create(navCtxt);
3633 /// if (!iter.MoveNext())
3634 /// goto LabelNextCtxt;
3636 private void CreateSimpleIterator(QilNode ndCtxt, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext) {
3638 LocalBuilder locIter = this.helper.DeclareLocal(iterName, iterType);
3640 // iter.Create(navCtxt);
3641 this.helper.Emit(OpCodes.Ldloca, locIter);
3642 NestedVisitEnsureStack(ndCtxt);
3643 this.helper.Call(methCreate);
3645 GenerateSimpleIterator(typeof(XPathNavigator), locIter, methNext);
3649 /// Generate boiler-plate code to create an Xml iterator that uses an XmlNavigatorFilter to filter items.
3653 /// iter.Create(navCtxt, filter [, orSelf] [, navEnd]);
3655 /// if (!iter.MoveNext())
3656 /// goto LabelNextCtxt;
3658 private void CreateFilteredIterator(QilNode ndCtxt, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext,
3659 XmlNodeKindFlags kinds, QilName ndName, TriState orSelf, QilNode ndEnd) {
3661 LocalBuilder locIter = this.helper.DeclareLocal(iterName, iterType);
3663 // iter.Create(navCtxt, filter [, orSelf], [, navEnd]);
3664 this.helper.Emit(OpCodes.Ldloca, locIter);
3665 NestedVisitEnsureStack(ndCtxt);
3666 LoadSelectFilter(kinds, ndName);
3667 if (orSelf != TriState.Unknown)
3668 this.helper.LoadBoolean(orSelf == TriState.True);
3670 NestedVisitEnsureStack(ndEnd);
3671 this.helper.Call(methCreate);
3673 GenerateSimpleIterator(typeof(XPathNavigator), locIter, methNext);
3677 /// Generate boiler-plate code to create an Xml iterator that controls a nested iterator.
3681 /// iter.Create(filter [, orSelf]);
3682 /// ...nested iterator...
3683 /// navInput = nestedNested;
3686 /// navInput = null;
3688 /// switch (iter.MoveNext(navInput)) {
3689 /// case IteratorState.NoMoreNodes: goto LabelNextCtxt;
3690 /// case IteratorState.NextInputNode: goto LabelNextNested;
3693 private void CreateContainerIterator(QilUnary ndDod, string iterName, Type iterType, MethodInfo methCreate, MethodInfo methNext,
3694 XmlNodeKindFlags kinds, QilName ndName, TriState orSelf) {
3696 LocalBuilder locIter = this.helper.DeclareLocal(iterName, iterType);
3697 Label lblOnEndNested;
3698 QilLoop ndLoop = (QilLoop) ndDod.Child;
3699 Debug.Assert(ndDod.NodeType == QilNodeType.DocOrderDistinct && ndLoop != null);
3701 // iter.Create(filter [, orSelf]);
3702 this.helper.Emit(OpCodes.Ldloca, locIter);
3703 LoadSelectFilter(kinds, ndName);
3704 if (orSelf != TriState.Unknown)
3705 this.helper.LoadBoolean(orSelf == TriState.True);
3706 this.helper.Call(methCreate);
3708 // Generate nested iterator (branch to lblOnEndNested when iteration is complete)
3709 lblOnEndNested = this.helper.DefineLabel();
3710 StartNestedIterator(ndLoop, lblOnEndNested);
3711 StartBinding(ndLoop.Variable);
3712 EndBinding(ndLoop.Variable);
3713 EndNestedIterator(ndLoop.Variable);
3714 this.iterCurr.Storage = this.iterNested.Storage;
3716 GenerateContainerIterator(ndDod, locIter, lblOnEndNested, methNext, typeof(XPathNavigator));
3720 /// Generate boiler-plate code that calls MoveNext on a simple Xml iterator. Iterator should have already been
3721 /// created by calling code.
3726 /// if (!iter.MoveNext())
3727 /// goto LabelNextCtxt;
3729 private void GenerateSimpleIterator(Type itemStorageType, LocalBuilder locIter, MethodInfo methNext) {
3733 lblNext = this.helper.DefineLabel();
3734 this.helper.MarkLabel(lblNext);
3736 // if (!iter.MoveNext()) goto LabelNextCtxt;
3737 this.helper.Emit(OpCodes.Ldloca, locIter);
3738 this.helper.Call(methNext);
3739 this.helper.Emit(OpCodes.Brfalse, this.iterCurr.GetLabelNext());
3741 this.iterCurr.SetIterator(lblNext, StorageDescriptor.Current(locIter, itemStorageType));
3745 /// Generate boiler-plate code that calls MoveNext on an Xml iterator that controls a nested iterator. Iterator should
3746 /// have already been created by calling code.
3754 /// switch (iter.MoveNext(navCtxt)) {
3755 /// case IteratorState.NoMoreNodes: goto LabelNextCtxt;
3756 /// case IteratorState.NextInputNode: goto LabelNextNested;
3759 private void GenerateContainerIterator(QilNode nd, LocalBuilder locIter, Label lblOnEndNested,
3760 MethodInfo methNext, Type itemStorageType) {
3763 // Define labels that will be used
3764 lblCall = this.helper.DefineLabel();
3766 // iter.MoveNext(input);
3768 this.iterCurr.EnsureNoStackNoCache(nd.XmlType.IsNode ? "$$$navInput" : "$$$itemInput");
3769 this.helper.Emit(OpCodes.Ldloca, locIter);
3770 this.iterCurr.PushValue();
3771 this.helper.EmitUnconditionalBranch(OpCodes.Br, lblCall);
3774 // iterSet.MoveNext(null);
3775 this.helper.MarkLabel(lblOnEndNested);
3776 this.helper.Emit(OpCodes.Ldloca, locIter);
3777 this.helper.Emit(OpCodes.Ldnull);
3780 // result = iter.MoveNext(input);
3781 this.helper.MarkLabel(lblCall);
3782 this.helper.Call(methNext);
3784 // If this iterator always returns a single node, then NoMoreNodes will never be returned
3785 if (nd.XmlType.IsSingleton) {
3786 // if (result == IteratorResult.NeedInputNode) goto LabelNextInput;
3787 this.helper.LoadInteger((int) IteratorResult.NeedInputNode);
3788 this.helper.Emit(OpCodes.Beq, this.iterNested.GetLabelNext());
3790 this.iterCurr.Storage = StorageDescriptor.Current(locIter, itemStorageType);
3793 // switch (iter.MoveNext(input)) {
3794 // case IteratorResult.NoMoreNodes: goto LabelNextCtxt;
3795 // case IteratorResult.NeedInputNode: goto LabelNextInput;
3797 this.helper.Emit(OpCodes.Switch, new Label[] {this.iterCurr.GetLabelNext(), this.iterNested.GetLabelNext()});
3799 this.iterCurr.SetIterator(lblOnEndNested, StorageDescriptor.Current(locIter, itemStorageType));
3804 /// Load XmlQueryOutput, load a name (computed or literal) and load an index to an Xml schema type.
3805 /// Return an enumeration that specifies what kind of name was loaded.
3807 private GenerateNameType LoadNameAndType(XPathNodeType nodeType, QilNode ndName, bool isStart, bool callChk) {
3808 QilName ndLiteralName;
3809 string prefix, localName, ns;
3810 GenerateNameType nameType;
3811 Debug.Assert(ndName.XmlType.TypeCode == XmlTypeCode.QName, "Element or attribute name must have QName type.");
3813 this.helper.LoadQueryOutput();
3815 // 0. Default is to pop names off stack
3816 nameType = GenerateNameType.StackName;
3819 if (ndName.NodeType == QilNodeType.LiteralQName) {
3820 // If checks need to be made on End construction, then always pop names from stack
3821 if (isStart || !callChk) {
3822 ndLiteralName = ndName as QilName;
3823 prefix = ndLiteralName.Prefix;
3824 localName = ndLiteralName.LocalName;
3825 ns = ndLiteralName.NamespaceUri;
3827 // Check local name, namespace parts in debug code
3828 Debug.Assert(ValidateNames.ValidateName(prefix, localName, ns, nodeType, ValidateNames.Flags.AllExceptPrefixMapping));
3830 // If the namespace is empty,
3831 if (ndLiteralName.NamespaceUri.Length == 0) {
3832 // Then always call method on XmlQueryOutput
3833 this.helper.Emit(OpCodes.Ldstr, ndLiteralName.LocalName);
3834 return GenerateNameType.LiteralLocalName;
3837 // If prefix is not valid for the node type,
3838 if (!ValidateNames.ValidateName(prefix, localName, ns, nodeType, ValidateNames.Flags.CheckPrefixMapping)) {
3839 // Then construct a new prefix at run-time
3841 this.helper.Emit(OpCodes.Ldstr, localName);
3842 this.helper.Emit(OpCodes.Ldstr, ns);
3843 this.helper.Construct(XmlILConstructors.QName);
3845 nameType = GenerateNameType.QName;
3849 // Push string parts
3850 this.helper.Emit(OpCodes.Ldstr, prefix);
3851 this.helper.Emit(OpCodes.Ldstr, localName);
3852 this.helper.Emit(OpCodes.Ldstr, ns);
3854 nameType = GenerateNameType.LiteralName;
3861 if (ndName.NodeType == QilNodeType.NameOf) {
3862 // Preserve prefix of source node, so just push navigator onto stack
3863 NestedVisitEnsureStack((ndName as QilUnary).Child);
3864 nameType = GenerateNameType.CopiedName;
3866 // 3. Parsed tag names (foo:bar)
3867 else if (ndName.NodeType == QilNodeType.StrParseQName) {
3868 // Preserve prefix from parsed tag name
3869 VisitStrParseQName(ndName as QilBinary, true);
3871 // Type of name depends upon data-type of name argument
3872 if ((ndName as QilBinary).Right.XmlType.TypeCode == XmlTypeCode.String)
3873 nameType = GenerateNameType.TagNameAndNamespace;
3875 nameType = GenerateNameType.TagNameAndMappings;
3877 // 4. Other computed qnames
3879 // Push XmlQualifiedName onto the stack
3880 NestedVisitEnsureStack(ndName);
3881 nameType = GenerateNameType.QName;
3890 /// If the first argument is a constant value that evaluates to zero, then a more optimal instruction sequence
3891 /// can be generated that does not have to push the zero onto the stack. Instead, a Brfalse or Brtrue instruction
3894 private bool TryZeroCompare(QilNodeType relOp, QilNode ndFirst, QilNode ndSecond) {
3895 Debug.Assert(relOp == QilNodeType.Eq || relOp == QilNodeType.Ne);
3897 switch (ndFirst.NodeType) {
3898 case QilNodeType.LiteralInt64:
3899 if ((int) (QilLiteral) ndFirst != 0) return false;
3902 case QilNodeType.LiteralInt32:
3903 if ((int) (QilLiteral) ndFirst != 0) return false;
3906 case QilNodeType.False:
3909 case QilNodeType.True:
3910 // Inverse of QilNodeType.False
3911 relOp = (relOp == QilNodeType.Eq) ? QilNodeType.Ne : QilNodeType.Eq;
3918 // Generate code to push second argument on stack
3919 NestedVisitEnsureStack(ndSecond);
3921 // Generate comparison code -- op == 0 or op != 0
3922 ZeroCompare(relOp, ndSecond.XmlType.TypeCode == XmlTypeCode.Boolean);
3928 /// If the comparison involves a qname, then perform comparison using atoms and return true.
3929 /// Otherwise, return false (caller will perform comparison).
3931 private bool TryNameCompare(QilNodeType relOp, QilNode ndFirst, QilNode ndSecond) {
3932 Debug.Assert(relOp == QilNodeType.Eq || relOp == QilNodeType.Ne);
3934 if (ndFirst.NodeType == QilNodeType.NameOf) {
3935 switch (ndSecond.NodeType) {
3936 case QilNodeType.NameOf:
3937 case QilNodeType.LiteralQName: {
3938 this.helper.LoadQueryRuntime();
3940 // Push left navigator onto the stack
3941 NestedVisitEnsureStack((ndFirst as QilUnary).Child);
3943 // Push the local name and namespace uri of the right argument onto the stack
3944 if (ndSecond.NodeType == QilNodeType.LiteralQName) {
3945 QilName ndName = ndSecond as QilName;
3946 this.helper.LoadInteger(this.helper.StaticData.DeclareName(ndName.LocalName));
3947 this.helper.LoadInteger(this.helper.StaticData.DeclareName(ndName.NamespaceUri));
3949 // push runtime.IsQNameEqual(navigator, localName, namespaceUri)
3950 this.helper.Call(XmlILMethods.QNameEqualLit);
3953 // Generate code to locate the navigator argument of NameOf operator
3954 Debug.Assert(ndSecond.NodeType == QilNodeType.NameOf);
3955 NestedVisitEnsureStack(ndSecond);
3957 // push runtime.IsQNameEqual(nav1, nav2)
3958 this.helper.Call(XmlILMethods.QNameEqualNav);
3961 // Branch based on boolean result or push boolean value
3962 ZeroCompare((relOp == QilNodeType.Eq) ? QilNodeType.Ne : QilNodeType.Eq, true);
3968 // Caller must perform comparison
3973 /// For QilExpression types that map directly to CLR primitive types, the built-in CLR comparison operators can
3974 /// be used to perform the specified relational operation.
3976 private void ClrCompare(QilNodeType relOp, XmlTypeCode code) {
3980 switch (this.iterCurr.CurrentBranchingContext) {
3981 case BranchingContext.OnFalse:
3982 // Reverse the comparison operator
3983 // Use Bxx_Un OpCodes to handle NaN case for double and single types
3984 if (code == XmlTypeCode.Double || code == XmlTypeCode.Float) {
3986 case QilNodeType.Gt: opcode = OpCodes.Ble_Un; break;
3987 case QilNodeType.Ge: opcode = OpCodes.Blt_Un; break;
3988 case QilNodeType.Lt: opcode = OpCodes.Bge_Un; break;
3989 case QilNodeType.Le: opcode = OpCodes.Bgt_Un; break;
3990 case QilNodeType.Eq: opcode = OpCodes.Bne_Un; break;
3991 case QilNodeType.Ne: opcode = OpCodes.Beq; break;
3992 default: Debug.Assert(false); opcode = OpCodes.Nop; break;
3998 case QilNodeType.Gt: opcode = OpCodes.Ble; break;
3999 case QilNodeType.Ge: opcode = OpCodes.Blt; break;
4000 case QilNodeType.Lt: opcode = OpCodes.Bge; break;
4001 case QilNodeType.Le: opcode = OpCodes.Bgt; break;
4002 case QilNodeType.Eq: opcode = OpCodes.Bne_Un; break;
4003 case QilNodeType.Ne: opcode = OpCodes.Beq; break;
4004 default: Debug.Assert(false); opcode = OpCodes.Nop; break;
4007 this.helper.Emit(opcode, this.iterCurr.LabelBranch);
4008 this.iterCurr.Storage = StorageDescriptor.None();
4011 case BranchingContext.OnTrue:
4013 case QilNodeType.Gt: opcode = OpCodes.Bgt; break;
4014 case QilNodeType.Ge: opcode = OpCodes.Bge; break;
4015 case QilNodeType.Lt: opcode = OpCodes.Blt; break;
4016 case QilNodeType.Le: opcode = OpCodes.Ble; break;
4017 case QilNodeType.Eq: opcode = OpCodes.Beq; break;
4018 case QilNodeType.Ne: opcode = OpCodes.Bne_Un; break;
4019 default: Debug.Assert(false); opcode = OpCodes.Nop; break;
4021 this.helper.Emit(opcode, this.iterCurr.LabelBranch);
4022 this.iterCurr.Storage = StorageDescriptor.None();
4026 Debug.Assert(this.iterCurr.CurrentBranchingContext == BranchingContext.None);
4028 case QilNodeType.Gt: this.helper.Emit(OpCodes.Cgt); break;
4029 case QilNodeType.Lt: this.helper.Emit(OpCodes.Clt); break;
4030 case QilNodeType.Eq: this.helper.Emit(OpCodes.Ceq); break;
4033 case QilNodeType.Ge: opcode = OpCodes.Bge_S; break;
4034 case QilNodeType.Le: opcode = OpCodes.Ble_S; break;
4035 case QilNodeType.Ne: opcode = OpCodes.Bne_Un_S; break;
4036 default: Debug.Assert(false); opcode = OpCodes.Nop; break;
4039 // Push "true" if comparison succeeds, "false" otherwise
4040 lblTrue = this.helper.DefineLabel();
4041 this.helper.Emit(opcode, lblTrue);
4042 this.helper.ConvBranchToBool(lblTrue, true);
4045 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
4051 /// Generate code to compare the top stack value to 0 by using the Brfalse or Brtrue instructions,
4052 /// which avoid pushing zero onto the stack. Both of these instructions test for null/zero/false.
4054 private void ZeroCompare(QilNodeType relOp, bool isBoolVal) {
4056 Debug.Assert(relOp == QilNodeType.Eq || relOp == QilNodeType.Ne);
4058 // Test to determine if top stack value is zero (if relOp is Eq) or is not zero (if relOp is Ne)
4059 switch (this.iterCurr.CurrentBranchingContext) {
4060 case BranchingContext.OnTrue:
4061 // If relOp is Eq, jump to true label if top value is zero (Brfalse)
4062 // If relOp is Ne, jump to true label if top value is non-zero (Brtrue)
4063 this.helper.Emit((relOp == QilNodeType.Eq) ? OpCodes.Brfalse : OpCodes.Brtrue, this.iterCurr.LabelBranch);
4064 this.iterCurr.Storage = StorageDescriptor.None();
4067 case BranchingContext.OnFalse:
4068 // If relOp is Eq, jump to false label if top value is non-zero (Brtrue)
4069 // If relOp is Ne, jump to false label if top value is zero (Brfalse)
4070 this.helper.Emit((relOp == QilNodeType.Eq) ? OpCodes.Brtrue : OpCodes.Brfalse, this.iterCurr.LabelBranch);
4071 this.iterCurr.Storage = StorageDescriptor.None();
4075 Debug.Assert(this.iterCurr.CurrentBranchingContext == BranchingContext.None);
4077 // Since (boolval != 0) = boolval, value on top of the stack is already correct
4078 if (!isBoolVal || relOp == QilNodeType.Eq) {
4079 // If relOp is Eq, push "true" if top value is zero, "false" otherwise
4080 // If relOp is Ne, push "true" if top value is non-zero, "false" otherwise
4081 lblTrue = this.helper.DefineLabel();
4082 this.helper.Emit((relOp == QilNodeType.Eq) ? OpCodes.Brfalse : OpCodes.Brtrue, lblTrue);
4083 this.helper.ConvBranchToBool(lblTrue, true);
4086 this.iterCurr.Storage = StorageDescriptor.Stack(typeof(bool), false);
4092 /// Construction within a loop is starting. If transition from non-Any to Any state occurs, then ensure
4093 /// that runtime state will be set.
4095 private void StartWriterLoop(QilNode nd, out bool hasOnEnd, out Label lblOnEnd) {
4096 XmlILConstructInfo info = XmlILConstructInfo.Read(nd);
4098 // By default, do not create a new iteration label
4100 lblOnEnd = new Label();
4102 // If loop is not involved in Xml construction, or if loop returns exactly one value, then do nothing
4103 if (!info.PushToWriterLast || nd.XmlType.IsSingleton)
4106 if (!this.iterCurr.HasLabelNext) {
4107 // Iterate until all items are constructed
4109 lblOnEnd = this.helper.DefineLabel();
4110 this.iterCurr.SetIterator(lblOnEnd, StorageDescriptor.None());
4115 /// Construction within a loop is ending. If transition from non-Any to Any state occurs, then ensure that
4116 /// runtime state will be set.
4118 private void EndWriterLoop(QilNode nd, bool hasOnEnd, Label lblOnEnd) {
4119 XmlILConstructInfo info = XmlILConstructInfo.Read(nd);
4121 // If loop is not involved in Xml construction, then do nothing
4122 if (!info.PushToWriterLast)
4125 // Since results of construction were pushed to writer, there are no values to return
4126 this.iterCurr.Storage = StorageDescriptor.None();
4128 // If loop returns exactly one value, then do nothing further
4129 if (nd.XmlType.IsSingleton)
4133 // Loop over all items in the list, sending each to the output writer
4134 this.iterCurr.LoopToEnd(lblOnEnd);
4139 /// Returns true if the specified node's owner element might have local namespaces added to it
4140 /// after attributes have already been added.
4142 private bool MightHaveNamespacesAfterAttributes(XmlILConstructInfo info) {
4143 // Get parent element
4145 info = info.ParentElementInfo;
4147 // If a parent element has not been statically identified, then assume that the runtime
4148 // element will have namespaces added after attributes.
4152 return info.MightHaveNamespacesAfterAttributes;
4156 /// Returns true if the specified element should cache attributes.
4158 private bool ElementCachesAttributes(XmlILConstructInfo info) {
4159 // Attributes will be cached if namespaces might be constructed after the attributes
4160 return info.MightHaveDuplicateAttributes || info.MightHaveNamespacesAfterAttributes;
4164 /// This method is called before calling any WriteEnd??? method. It generates code to perform runtime
4165 /// construction checks separately. This should only be called if the XmlQueryOutput::StartElementChk
4166 /// method will *not* be called.
4168 private void BeforeStartChecks(QilNode ndCtor) {
4169 switch (XmlILConstructInfo.Read(ndCtor).InitialStates) {
4170 case PossibleXmlStates.WithinSequence:
4171 // If runtime state is guaranteed to be WithinSequence, then call XmlQueryOutput.StartTree
4172 this.helper.CallStartTree(QilConstructorToNodeType(ndCtor.NodeType));
4175 case PossibleXmlStates.EnumAttrs:
4176 switch (ndCtor.NodeType) {
4177 case QilNodeType.ElementCtor:
4178 case QilNodeType.TextCtor:
4179 case QilNodeType.RawTextCtor:
4180 case QilNodeType.PICtor:
4181 case QilNodeType.CommentCtor:
4182 // If runtime state is guaranteed to be EnumAttrs, and content is being constructed, call
4183 // XmlQueryOutput.StartElementContent
4184 this.helper.CallStartElementContent();
4192 /// This method is called after calling any WriteEnd??? method. It generates code to perform runtime
4193 /// construction checks separately. This should only be called if the XmlQueryOutput::EndElementChk
4194 /// method will *not* be called.
4196 private void AfterEndChecks(QilNode ndCtor) {
4197 if (XmlILConstructInfo.Read(ndCtor).FinalStates == PossibleXmlStates.WithinSequence) {
4198 // If final runtime state is guaranteed to be WithinSequence, then call XmlQueryOutput.StartTree
4199 this.helper.CallEndTree();
4204 /// Return true if a runtime check needs to be made in order to transition into the WithinContent state.
4206 private bool CheckWithinContent(XmlILConstructInfo info) {
4207 switch (info.InitialStates) {
4208 case PossibleXmlStates.WithinSequence:
4209 case PossibleXmlStates.EnumAttrs:
4210 case PossibleXmlStates.WithinContent:
4211 // Transition to WithinContent can be ensured at compile-time
4219 /// Return true if a runtime check needs to be made in order to transition into the EnumAttrs state.
4221 private bool CheckEnumAttrs(XmlILConstructInfo info) {
4222 switch (info.InitialStates) {
4223 case PossibleXmlStates.WithinSequence:
4224 case PossibleXmlStates.EnumAttrs:
4225 // Transition to EnumAttrs can be ensured at compile-time
4233 /// Map the XmlNodeKindFlags enumeration into the XPathNodeType enumeration.
4235 private XPathNodeType QilXmlToXPathNodeType(XmlNodeKindFlags xmlTypes) {
4237 case XmlNodeKindFlags.Element: return XPathNodeType.Element;
4238 case XmlNodeKindFlags.Attribute: return XPathNodeType.Attribute;
4239 case XmlNodeKindFlags.Text: return XPathNodeType.Text;
4240 case XmlNodeKindFlags.Comment: return XPathNodeType.Comment;
4242 Debug.Assert(xmlTypes == XmlNodeKindFlags.PI);
4243 return XPathNodeType.ProcessingInstruction;
4247 /// Map a QilExpression constructor type into the XPathNodeType enumeration.
4249 private XPathNodeType QilConstructorToNodeType(QilNodeType typ) {
4251 case QilNodeType.DocumentCtor: return XPathNodeType.Root;
4252 case QilNodeType.ElementCtor: return XPathNodeType.Element;
4253 case QilNodeType.TextCtor: return XPathNodeType.Text;
4254 case QilNodeType.RawTextCtor: return XPathNodeType.Text;
4255 case QilNodeType.PICtor: return XPathNodeType.ProcessingInstruction;
4256 case QilNodeType.CommentCtor: return XPathNodeType.Comment;
4257 case QilNodeType.AttributeCtor: return XPathNodeType.Attribute;
4258 case QilNodeType.NamespaceDecl: return XPathNodeType.Namespace;
4261 Debug.Assert(false, "Cannot map QilNodeType " + typ + " to an XPathNodeType");
4262 return XPathNodeType.All;
4266 /// Load an XmlNavigatorFilter that matches only the specified name and types onto the stack.
4268 private void LoadSelectFilter(XmlNodeKindFlags xmlTypes, QilName ndName) {
4269 if (ndName != null) {
4271 Debug.Assert(xmlTypes == XmlNodeKindFlags.Element);
4272 this.helper.CallGetNameFilter(this.helper.StaticData.DeclareNameFilter(ndName.LocalName, ndName.NamespaceUri));
4275 // Either type cannot be a union, or else it must be >= union of all Content types
4276 bool isXmlTypeUnion = IsNodeTypeUnion(xmlTypes);
4277 Debug.Assert(!isXmlTypeUnion || (xmlTypes & XmlNodeKindFlags.Content) == XmlNodeKindFlags.Content);
4279 if (isXmlTypeUnion) {
4280 if ((xmlTypes & XmlNodeKindFlags.Attribute) != 0) {
4281 // Union includes attributes, so allow all node kinds
4282 this.helper.CallGetTypeFilter(XPathNodeType.All);
4285 // Filter attributes
4286 this.helper.CallGetTypeFilter(XPathNodeType.Attribute);
4290 // Filter nodes of all but one type
4291 this.helper.CallGetTypeFilter(QilXmlToXPathNodeType(xmlTypes));
4297 /// Return true if more than one node type is set.
4299 private static bool IsNodeTypeUnion(XmlNodeKindFlags xmlTypes) {
4300 return ((int) xmlTypes & ((int) xmlTypes - 1)) != 0;
4304 /// Start construction of a new nested iterator. If this.iterCurr == null, then the new iterator
4305 /// is a top-level, or root iterator. Otherwise, the new iterator will be nested within the
4306 /// current iterator.
4308 private void StartNestedIterator(QilNode nd) {
4309 IteratorDescriptor iterParent = this.iterCurr;
4311 // Create a new, nested iterator
4312 if (iterParent == null) {
4313 // Create a "root" iterator info that has no parernt
4314 this.iterCurr = new IteratorDescriptor(this.helper);
4317 // Create a nested iterator
4318 this.iterCurr = new IteratorDescriptor(iterParent);
4321 this.iterNested = null;
4325 /// Calls StartNestedIterator(nd) and also sets up the nested iterator to branch to "lblOnEnd" when iteration
4328 private void StartNestedIterator(QilNode nd, Label lblOnEnd) {
4329 StartNestedIterator(nd);
4330 this.iterCurr.SetIterator(lblOnEnd, StorageDescriptor.None());
4334 /// End construction of the current iterator.
4336 private void EndNestedIterator(QilNode nd) {
4337 Debug.Assert(this.iterCurr.Storage.Location == ItemLocation.None ||
4338 this.iterCurr.Storage.ItemStorageType == GetItemStorageType(nd) ||
4339 this.iterCurr.Storage.ItemStorageType == typeof(XPathItem) ||
4340 nd.XmlType.TypeCode == XmlTypeCode.None,
4341 "QilNodeType " + nd.NodeType + " cannot be stored using type " + this.iterCurr.Storage.ItemStorageType + ".");
4343 // If the nested iterator was constructed in branching mode,
4344 if (this.iterCurr.IsBranching) {
4345 // Then if branching hasn't already taken place, do so now
4346 if (this.iterCurr.Storage.Location != ItemLocation.None) {
4347 this.iterCurr.EnsureItemStorageType(nd.XmlType, typeof(bool));
4348 this.iterCurr.EnsureStackNoCache();
4350 if (this.iterCurr.CurrentBranchingContext == BranchingContext.OnTrue)
4351 this.helper.Emit(OpCodes.Brtrue, this.iterCurr.LabelBranch);
4353 this.helper.Emit(OpCodes.Brfalse, this.iterCurr.LabelBranch);
4355 this.iterCurr.Storage = StorageDescriptor.None();
4359 // Save current iterator as nested iterator
4360 this.iterNested = this.iterCurr;
4362 // Update current iterator to be parent iterator
4363 this.iterCurr = this.iterCurr.ParentIterator;
4367 /// Recursively generate code to iterate over the results of the "nd" expression. If "nd" is pushed
4368 /// to the writer, then there are no results. If "nd" is a singleton expression and isCached is false,
4369 /// then generate code to construct the singleton. Otherwise, cache the sequence in an XmlQuerySequence
4370 /// object. Ensure that all items are converted to the specified "itemStorageType".
4372 private void NestedVisit(QilNode nd, Type itemStorageType, bool isCached) {
4373 if (XmlILConstructInfo.Read(nd).PushToWriterLast) {
4374 // Push results to output, so nothing is left to store
4375 StartNestedIterator(nd);
4377 EndNestedIterator(nd);
4378 this.iterCurr.Storage = StorageDescriptor.None();
4380 else if (!isCached && nd.XmlType.IsSingleton) {
4381 // Storage of result will be a non-cached singleton
4382 StartNestedIterator(nd);
4384 this.iterCurr.EnsureNoCache();
4385 this.iterCurr.EnsureItemStorageType(nd.XmlType, itemStorageType);
4386 EndNestedIterator(nd);
4387 this.iterCurr.Storage = this.iterNested.Storage;
4390 NestedVisitEnsureCache(nd, itemStorageType);
4395 /// Calls NestedVisit(QilNode, Type, bool), storing result in the default storage type for "nd".
4397 private void NestedVisit(QilNode nd) {
4398 NestedVisit(nd, GetItemStorageType(nd), !nd.XmlType.IsSingleton);
4402 /// Recursively generate code to iterate over the results of the "nd" expression. When the expression
4403 /// has been fully iterated, it will jump to "lblOnEnd".
4405 private void NestedVisit(QilNode nd, Label lblOnEnd) {
4406 Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
4407 StartNestedIterator(nd, lblOnEnd);
4409 this.iterCurr.EnsureNoCache();
4410 this.iterCurr.EnsureItemStorageType(nd.XmlType, GetItemStorageType(nd));
4411 EndNestedIterator(nd);
4412 this.iterCurr.Storage = this.iterNested.Storage;
4416 /// Call NestedVisit(QilNode) and ensure that result is pushed onto the IL stack.
4418 private void NestedVisitEnsureStack(QilNode nd) {
4419 Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
4421 this.iterCurr.EnsureStack();
4425 /// Generate code for both QilExpression nodes and ensure that each result is pushed onto the IL stack.
4427 private void NestedVisitEnsureStack(QilNode ndLeft, QilNode ndRight) {
4428 NestedVisitEnsureStack(ndLeft);
4429 NestedVisitEnsureStack(ndRight);
4433 /// Call NestedVisit(QilNode, Type, bool) and ensure that result is pushed onto the IL stack.
4435 private void NestedVisitEnsureStack(QilNode nd, Type itemStorageType, bool isCached) {
4436 Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
4437 NestedVisit(nd, itemStorageType, isCached);
4438 this.iterCurr.EnsureStack();
4442 /// Call NestedVisit(QilNode) and ensure that result is stored in local variable "loc".
4444 private void NestedVisitEnsureLocal(QilNode nd, LocalBuilder loc) {
4445 Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
4447 this.iterCurr.EnsureLocal(loc);
4451 /// Start a nested iterator in a branching context and recursively generate code for the specified QilExpression node.
4453 private void NestedVisitWithBranch(QilNode nd, BranchingContext brctxt, Label lblBranch) {
4454 Debug.Assert(nd.XmlType.IsSingleton && !XmlILConstructInfo.Read(nd).PushToWriterLast);
4455 StartNestedIterator(nd);
4456 this.iterCurr.SetBranching(brctxt, lblBranch);
4458 EndNestedIterator(nd);
4459 this.iterCurr.Storage = StorageDescriptor.None();
4463 /// Generate code for the QilExpression node and ensure that results are fully cached as an XmlQuerySequence. All results
4464 /// should be converted to "itemStorageType" before being added to the cache.
4466 private void NestedVisitEnsureCache(QilNode nd, Type itemStorageType) {
4467 Debug.Assert(!XmlILConstructInfo.Read(nd).PushToWriterLast);
4468 bool cachesResult = CachesResult(nd);
4469 LocalBuilder locCache;
4470 Label lblOnEnd = this.helper.DefineLabel();
4472 XmlILStorageMethods methods;
4474 // If bound expression will already be cached correctly, then don't create an XmlQuerySequence
4476 StartNestedIterator(nd);
4478 EndNestedIterator(nd);
4479 this.iterCurr.Storage = this.iterNested.Storage;
4480 Debug.Assert(this.iterCurr.Storage.IsCached, "Expression result should be cached. CachesResult() might have a bug in it.");
4482 // If type of items in the cache matches "itemStorageType", then done
4483 if (this.iterCurr.Storage.ItemStorageType == itemStorageType)
4486 // If the cache has navigators in it, or if converting to a cache of navigators, then EnsureItemStorageType
4487 // can directly convert without needing to create a new cache.
4488 if (this.iterCurr.Storage.ItemStorageType == typeof(XPathNavigator) || itemStorageType == typeof(XPathNavigator)) {
4489 this.iterCurr.EnsureItemStorageType(nd.XmlType, itemStorageType);
4493 this.iterCurr.EnsureNoStack("$$$cacheResult");
4496 // Always store navigators in XmlQueryNodeSequence (which implements IList<XPathItem>)
4497 cacheType = (GetItemStorageType(nd) == typeof(XPathNavigator)) ? typeof(XPathNavigator) : itemStorageType;
4499 // XmlQuerySequence<T> cache;
4500 methods = XmlILMethods.StorageMethods[cacheType];
4501 locCache = this.helper.DeclareLocal("$$$cache", methods.SeqType);
4502 this.helper.Emit(OpCodes.Ldloc, locCache);
4504 // Special case non-navigator singletons to use overload of CreateOrReuse
4505 if (nd.XmlType.IsSingleton) {
4506 // cache = XmlQuerySequence.CreateOrReuse(cache, item);
4507 NestedVisitEnsureStack(nd, cacheType, false);
4508 this.helper.CallToken(methods.SeqReuseSgl);
4509 this.helper.Emit(OpCodes.Stloc, locCache);
4512 // XmlQuerySequence<T> cache;
4513 // cache = XmlQuerySequence.CreateOrReuse(cache);
4514 this.helper.CallToken(methods.SeqReuse);
4515 this.helper.Emit(OpCodes.Stloc, locCache);
4516 this.helper.Emit(OpCodes.Ldloc, locCache);
4518 StartNestedIterator(nd, lblOnEnd);
4521 this.iterCurr.Storage = this.iterCurr.ParentIterator.Storage;
4526 this.iterCurr.EnsureItemStorageType(nd.XmlType, cacheType);
4527 this.iterCurr.EnsureStackNoCache();
4528 this.helper.Call(methods.SeqAdd);
4529 this.helper.Emit(OpCodes.Ldloc, locCache);
4532 this.iterCurr.LoopToEnd(lblOnEnd);
4534 EndNestedIterator(nd);
4536 // Remove cache reference from stack
4537 this.helper.Emit(OpCodes.Pop);
4540 this.iterCurr.Storage = StorageDescriptor.Local(locCache, itemStorageType, true);
4544 /// Returns true if the specified QilExpression node type is *guaranteed* to cache its results in an XmlQuerySequence,
4545 /// where items in the cache are stored using the default storage type.
4547 private bool CachesResult(QilNode nd) {
4548 OptimizerPatterns patt;
4550 switch (nd.NodeType) {
4551 case QilNodeType.Let:
4552 case QilNodeType.Parameter:
4553 case QilNodeType.Invoke:
4554 case QilNodeType.XsltInvokeLateBound:
4555 case QilNodeType.XsltInvokeEarlyBound:
4556 return !nd.XmlType.IsSingleton;
4558 case QilNodeType.Filter:
4559 // EqualityIndex pattern caches results
4560 patt = OptimizerPatterns.Read(nd);
4561 return patt.MatchesPattern(OptimizerPatternName.EqualityIndex);
4563 case QilNodeType.DocOrderDistinct:
4564 if (nd.XmlType.IsSingleton)
4567 // JoinAndDod and DodReverse patterns don't cache results
4568 patt = OptimizerPatterns.Read(nd);
4569 return !patt.MatchesPattern(OptimizerPatternName.JoinAndDod) && !patt.MatchesPattern(OptimizerPatternName.DodReverse);
4571 case QilNodeType.TypeAssert:
4572 QilTargetType ndTypeAssert = (QilTargetType) nd;
4573 // Check if TypeAssert would be no-op
4574 return CachesResult(ndTypeAssert.Source) && GetItemStorageType(ndTypeAssert.Source) == GetItemStorageType(ndTypeAssert);
4581 /// Shortcut call to XmlILTypeHelper.GetStorageType.
4583 private Type GetStorageType(QilNode nd) {
4584 return XmlILTypeHelper.GetStorageType(nd.XmlType);
4588 /// Shortcut call to XmlILTypeHelper.GetStorageType.
4590 private Type GetStorageType(XmlQueryType typ) {
4591 return XmlILTypeHelper.GetStorageType(typ);
4595 /// Shortcut call to XmlILTypeHelper.GetStorageType, using an expression's prime type.
4597 private Type GetItemStorageType(QilNode nd) {
4598 return XmlILTypeHelper.GetStorageType(nd.XmlType.Prime);
4602 /// Shortcut call to XmlILTypeHelper.GetStorageType, using the prime type.
4604 private Type GetItemStorageType(XmlQueryType typ) {
4605 return XmlILTypeHelper.GetStorageType(typ.Prime);