Bug #3945


Poor optimization of `$doc1/descendant-or-self::node()/child::node()`

Added by Michael Kay over 3 years ago. Updated over 3 years ago.

Start date:
Due date:
% Done:


Estimated time:
Legacy ID:
Applies to branch:
Fix Committed on Branch:
Fixed in Maintenance Release:


The expression $doc1/descendant-or-self::node()/child::node() arises in the generated XSLT code produced by conversion of the QT3 test fn-innermost-053.

Here $doc1 has unknown type.

In Saxon 9.8 the composite path expression is translated to

conditionalSort(xxx, docOrder(($doc1 treat as node())/descendant::node())

Saxon 9.9 does not succeed in reducing the path expression to a single axis step in this way.

The advantage of rewriting descendant-or-self::node()/child::node() as descendant::node() is that the latter naturally delivers nodes in document order; it is also a faster traversal of the TinyTree.

Actions #1

Updated by Michael Kay over 3 years ago

Compiling the query

declare variable $doc external;  $doc/descendant-or-self::node()/child::node()

with the -explain option, both 9.8 and 9.9 fail to deliver this optimization.

Actions #2

Updated by Michael Kay over 3 years ago

Looking at how the original stylesheet is optimized in 9.8.

SlashExpression docOrder(($doc1) treat as node()/descendant-or-self::node())/child::node()

The LHS turns into ConditionalSorter(exists(tail(($doc1) treat as node())), docOrder(($doc1) treat as node()/descendant-or-self::node()))

DocumentSorter line 108 turns the SlashExpression (of which the above is the LHS) into ConditionalSorter(exists(tail(($doc1) treat as node())), docOrder((($doc1) treat as node()/descendant-or-self::node())/child::node()))

that is, it has moved the final /child::node step inside the ConditionalSorter, which paves the way for the descendant-or-self and child steps to be combined (which happens in SlashExpression.simplifyDescendantPath(), line 287)

The 9.9 optimization path also succeeds in folding the final child::node() step inside the ConditionalSorter, but it seems that the final simplifyDescendantPath() never happens.

Comparing the 9.8 and 9.9 optimization paths step-by-step, 9.8 having optimized the whole template body, goes into global variable extraction (which succeeds) and then does another optimization pass on the result. 9.9 has optimizer options set which inhibit global variable extraction, and the second optimization pass therefore doesn't happen. It's the second pass that recognizes the opportunity to simplify the descendant axis.

Actions #3

Updated by Michael Kay over 3 years ago

The Configuration is initializing a defaultXsltCompilerInfo during its init sequence, at a point where the variable optimizerOptions holds the optimizer options for Saxon-HE; the value is subsequently changed to the set of options appropriate to Saxon-EE, but this is too late to affect the default XSLT compiler info object.

This causes 9.9 to generate essentially the same code as 9.8 for this example stylesheet.

However, this leaves open the question why two optimization passes should be needed to optimize this particular construct, and why it doesn't get optimized in the simple case of the direct query given in comment #1.

Actions #4

Updated by Michael Kay over 3 years ago

The issue here seems to be that SlashExpression.simplifyDescendantPath is called during the type-checking phase, and is not called again during the optimization phase. So if optimization generates an expression that is amenable to this rewrite, it won't normally happen, unless we go back to repeat the type-checking, which is normally not encouraged.

Putting another call on simplifyDescendantPath() into the optimize phase solves the problem.

I won't do this for 9.8, however, in the interests of stability.

Actions #5

Updated by Michael Kay over 3 years ago

  • Status changed from New to Resolved
  • Fix Committed on Branch 9.9 added

Resolved with three patches to 9.9:

(a) ensure that the defaultXsltCompilerInfo in an EnterpriseConfiguration uses all Saxon-EE optimization options

(b) put a call on simplifyDescendantPath() into SlashExpression.optimize()

(c) after rewriting sort(X)/Y as sort(X/Y) in DocumentSorter.optimize(), run an optimization pass over the new slash expression X/Y.

Actions #6

Updated by O'Neil Delpratt over 3 years ago

  • Status changed from Resolved to Closed
  • % Done changed from 0 to 100
  • Fixed in Maintenance Release added

Bug fix applied to the Saxon maintenance release.

Please register to edit this issue

Also available in: Atom PDF