Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f7dbfac
Add DAFFODIL_TDML_TUNABLES to run TDML suites under tunables
olabusayoT Oct 6, 2026
a10578d
Inline withRetryIfBlocking and the typed equality operators
olabusayoT Oct 6, 2026
6aeb108
Add a build/write-prefetch unparse path driven by a dedicated Builder…
olabusayoT Sep 29, 2026
2ee4af0
TEMP: default useBuildWritePrefetch to true
olabusayoT Sep 29, 2026
d468f13
Reduce contention and allocation on the build/write unparse path
olabusayoT Oct 2, 2026
ff30711
Rename Builder to InfosetBuilder and move it to the infoset package
olabusayoT Oct 2, 2026
c73045e
Build the InfosetBuilder tree with NadaInfosetBuilder and the unparse…
olabusayoT Oct 2, 2026
ab055ff
Test unparse prefetch under both values
olabusayoT Oct 2, 2026
b7a2092
Extract UState event and delimiter state; build state not a UState
olabusayoT Oct 2, 2026
cd876d1
Fire debugger events on the build/write-prefetch path
olabusayoT Oct 2, 2026
213b29c
Rename prefetch write pass to unparse tree pass
olabusayoT Oct 2, 2026
879fa67
Always build the infoset builder so prefetch is a runtime choice
olabusayoT Oct 3, 2026
135241b
Free infoset nodes through the tree state so build never frees
olabusayoT Oct 4, 2026
c92f78a
Simplify the build cursor and shared context
olabusayoT Oct 4, 2026
4a04050
Give unparse errors a data location without a build state location
olabusayoT Oct 4, 2026
7ca0597
Combine the build cursor unit tests into TestInfosetBuildCursor
olabusayoT Oct 4, 2026
cf5d3c0
Run the CI unit tests once more with single pass unparse
olabusayoT Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ jobs:
- name: Run Unit Tests
run: $SBT test

# Prefetch is the default, so run the suite once more with it off to
# keep the single pass unparse path tested.
- name: Run Unit Tests (single pass unparse)
if: matrix.os == 'ubuntu-22.04' && matrix.java_version == '17'
env:
DAFFODIL_TDML_TUNABLES: useBuildPrefetch=false
run: $SBT test

- name: Run Integration Tests
run: $SBT daffodil-test-integration/test

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.apache.daffodil.core.compiler.ForParser
import org.apache.daffodil.core.compiler.ForUnparser
import org.apache.daffodil.core.dsom.*
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.SeqCompInfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.AssertExpressionEvaluationParser
import org.apache.daffodil.runtime1.processors.parsers.NadaParser
import org.apache.daffodil.runtime1.processors.parsers.SeqCompParser
Expand Down Expand Up @@ -124,6 +126,19 @@ class SeqComp private (context: SchemaComponent, children: Seq[Gram])
else if (unparserChildren.length == 1) unparserChildren.head
else new SeqCompUnparser(context.runtimeData, unparserChildren.toArray)
}

// Only the (usually at most one) child(ren) that actually build infoset
// content contribute here; children that build no infoset events
// (delimiters, padding, etc.) simply have no builder to collect.
lazy val builderChildren: Array[InfosetBuilder] = {
children
.filter(x => !x.isEmpty && (x.forWhat != ForParser))
.map(_.builder)
.filterNot(_.isEmpty)
.toArray
}

final override lazy val builder: InfosetBuilder = SeqCompInfosetBuilder(builderChildren)
}

object EmptyGram extends Gram(null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import org.apache.daffodil.core.compiler.ForUnparser
import org.apache.daffodil.core.compiler.ParserOrUnparser
import org.apache.daffodil.core.dsom.SchemaComponent
import org.apache.daffodil.lib.util.Logger
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.NadaInfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.NadaParser
import org.apache.daffodil.unparsers.runtime1.NadaUnparser

Expand Down Expand Up @@ -107,4 +109,16 @@ final class Prod(
else
unp
}

final override lazy val builder: InfosetBuilder = {
if (gram.isEmpty) {
NadaInfosetBuilder
} else {
(forWhat, gram.forWhat) match {
case (ForParser, _) => NadaInfosetBuilder
case (_, ForParser) => NadaInfosetBuilder
case _ => gram.builder
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,17 @@ import org.apache.daffodil.lib.cookers.ChoiceBranchKeyCooker
import org.apache.daffodil.lib.cookers.IntRangeCooker
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.schema.annotation.props.gen.ChoiceLengthKind
import org.apache.daffodil.lib.util.Maybe
import org.apache.daffodil.lib.util.Maybe.Nope
import org.apache.daffodil.lib.util.Maybe.One
import org.apache.daffodil.lib.util.MaybeInt
import org.apache.daffodil.lib.util.ProperlySerializableMap.*
import org.apache.daffodil.runtime1.infoset.ChoiceBranchEvent
import org.apache.daffodil.runtime1.infoset.ChoiceInfosetBuilder
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.NadaInfosetBuilder
import org.apache.daffodil.runtime1.processors.RangeBound
import org.apache.daffodil.runtime1.processors.TermRuntimeData
import org.apache.daffodil.runtime1.processors.parsers.*
import org.apache.daffodil.runtime1.processors.unparsers.*
import org.apache.daffodil.unparsers.runtime1.*
Expand Down Expand Up @@ -266,8 +273,15 @@ case class ChoiceCombinator(ch: ChoiceTermBase, alternatives: Seq[Gram])
}
}

private lazy val eventUnparserMap = ch.choiceBranchMap._1.map { case (cbe, branchTerm) =>
(cbe, branchTerm.termContentBody.unparser)
}

private lazy val hasEventBranchUnparser: Boolean =
eventUnparserMap.exists { case (_, branchUnparser) => !branchUnparser.isEmpty }

override lazy val unparser: Unparser = {
val (eventRDMap, optDefaultBranch) = ch.choiceBranchMap
val optDefaultBranch = ch.choiceBranchMap._2
/*
* Since it's impossible to know the hiddenness for terms at this level (unless
* they're a hiddenGroupRef), we always attempt to find a defaultable unparser.
Expand Down Expand Up @@ -313,11 +327,7 @@ case class ChoiceCombinator(ch: ChoiceTermBase, alternatives: Seq[Gram])
optDefaultUnparser
}

val eventUnparserMap = eventRDMap.map { case (cbe, branchTerm) =>
(cbe, branchTerm.termContentBody.unparser)
}
val mapValues = eventUnparserMap.map { case (k, v) => v }.filterNot(_.isEmpty)
if (mapValues.isEmpty) {
if (!hasEventBranchUnparser) {
if (branchForUnparse.isEmpty) {
new NadaUnparser(null)
} else {
Expand All @@ -332,4 +342,25 @@ case class ChoiceCombinator(ch: ChoiceTermBase, alternatives: Seq[Gram])
new ChoiceCombinatorUnparser(ch.modelGroupRuntimeData, cbm, choiceLengthInBits)
}
}

override lazy val builder: InfosetBuilder = {
val (eventRDMap, optDefaultBranch) = ch.choiceBranchMap

def builderFor(term: Term): (TermRuntimeData, InfosetBuilder) = {
(term.termRuntimeData, term.termContentBody.builder)
}

val branchMap: Map[ChoiceBranchEvent, (TermRuntimeData, InfosetBuilder)] =
eventRDMap.map { case (cbe, branchTerm) => (cbe, builderFor(branchTerm)) }
val defaultBranch: Maybe[(TermRuntimeData, InfosetBuilder)] = optDefaultBranch match {
case Some(term) => One(builderFor(term))
case None => Nope
}

if (!hasEventBranchUnparser && defaultBranch.isEmpty) {
NadaInfosetBuilder
} else {
new ChoiceInfosetBuilder(ch.modelGroupRuntimeData, branchMap, defaultBranch)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import org.apache.daffodil.core.dsom.*
import org.apache.daffodil.core.grammar.Gram
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.util.Maybe
import org.apache.daffodil.lib.util.Maybe.*
import org.apache.daffodil.lib.util.Misc
import org.apache.daffodil.lib.xml.XMLUtils
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.{ Parser as DaffodilParser, * }
import org.apache.daffodil.runtime1.processors.unparsers.Unparser as DaffodilUnparser
import org.apache.daffodil.unparsers.runtime1.*
Expand Down Expand Up @@ -55,6 +57,10 @@ case class DelimiterStackCombinatorSequence(sq: SequenceTermBase, body: Gram)

override lazy val unparser: DaffodilUnparser =
new DelimiterStackUnparser(uInit, uSep, uTerm, sq.termRuntimeData, body.unparser)

// Delimiters build no infoset events; the builder tree skips straight to
// whatever this sequence's body itself builds, if anything.
override lazy val builder: InfosetBuilder = body.builder
}

case class DelimiterStackCombinatorChoice(ch: ChoiceTermBase, body: Gram)
Expand All @@ -77,6 +83,10 @@ case class DelimiterStackCombinatorChoice(ch: ChoiceTermBase, body: Gram)

override lazy val unparser: DaffodilUnparser =
new DelimiterStackUnparser(uInit, None, uTerm, ch.termRuntimeData, body.unparser)

// Delimiters build no infoset events; the builder tree skips straight to
// whatever this choice's body itself builds, if anything.
override lazy val builder: InfosetBuilder = body.builder
}

case class DelimiterStackCombinatorElement(e: ElementBase, body: Gram)
Expand Down Expand Up @@ -111,6 +121,10 @@ case class DelimiterStackCombinatorElement(e: ElementBase, body: Gram)
if (u.isEmpty) u
else new DelimiterStackUnparser(uInit, None, uTerm, e.termRuntimeData, u)
}

// Delimiters build no infoset events; the builder tree skips straight to
// whatever this element's body itself builds, if anything.
override lazy val builder: InfosetBuilder = body.builder
}

case class DynamicEscapeSchemeCombinatorElement(e: ElementBase, body: Gram)
Expand All @@ -135,4 +149,9 @@ case class DynamicEscapeSchemeCombinatorElement(e: ElementBase, body: Gram)
if (u.isEmpty || schemeUnparseIsConstant) u
else new DynamicEscapeSchemeUnparser(schemeUnparseOpt.get, e.termRuntimeData, u)
}

// The escape scheme only governs delimiter matching in written bytes; the
// builder tree skips straight to whatever this element's body itself
// builds, if anything.
override lazy val builder: InfosetBuilder = body.builder
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind
import org.apache.daffodil.lib.schema.annotation.props.gen.Representation
import org.apache.daffodil.lib.schema.annotation.props.gen.TestKind
import org.apache.daffodil.lib.util.Maybe
import org.apache.daffodil.runtime1.infoset.ElementInfosetBuilder
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.NadaInfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.CaptureEndOfContentLengthParser
import org.apache.daffodil.runtime1.processors.parsers.CaptureEndOfValueLengthParser
import org.apache.daffodil.runtime1.processors.parsers.CaptureStartOfContentLengthParser
Expand All @@ -44,6 +47,7 @@ import org.apache.daffodil.unparsers.runtime1.CaptureStartOfValueLengthUnparser
import org.apache.daffodil.unparsers.runtime1.ElementOVCSpecifiedLengthUnparser
import org.apache.daffodil.unparsers.runtime1.ElementOVCUnspecifiedLengthUnparser
import org.apache.daffodil.unparsers.runtime1.ElementSpecifiedLengthUnparser
import org.apache.daffodil.unparsers.runtime1.ElementUnparserBase
import org.apache.daffodil.unparsers.runtime1.ElementUnparserInputValueCalc
import org.apache.daffodil.unparsers.runtime1.ElementUnspecifiedLengthUnparser
import org.apache.daffodil.unparsers.runtime1.ElementUnusedUnparser
Expand Down Expand Up @@ -109,7 +113,13 @@ class ElementCombinator(
if (eAfterValue.isEmpty) Maybe.Nope
else Maybe(eAfterValue.unparser)

private lazy val eReptypeUnparser: Maybe[Unparser] = repTypeElementGram.maybeUnparser
private lazy val eRepTypeUnparser: Maybe[Unparser] = repTypeElementGram.maybeUnparser

private lazy val isSpecifiedLength: Boolean =
(context.lengthKind._eq_(LengthKind.Explicit)) ||
(context.isSimpleType &&
(context.lengthKind._eq_(LengthKind.Implicit)) &&
(context.impliedRepresentation._eq_(Representation.Text)))

override lazy val unparser: Unparser = {
if (context.isOutputValueCalc) {
Expand All @@ -122,12 +132,7 @@ class ElementCombinator(
eAfterUnparser,
context.ovcCompiledExpression
)
} else if (
(context.lengthKind._eq_(LengthKind.Explicit)) ||
(context.isSimpleType &&
(context.lengthKind._eq_(LengthKind.Implicit)) &&
(context.impliedRepresentation._eq_(Representation.Text)))
) {
} else if (isSpecifiedLength) {

new ElementSpecifiedLengthUnparser(
context.erd,
Expand All @@ -136,13 +141,33 @@ class ElementCombinator(
eBeforeUnparser,
eUnparser,
eAfterUnparser,
eReptypeUnparser
eRepTypeUnparser
)
} else {
subComb.unparser
}
}

private lazy val eBuilder: InfosetBuilder = {
if (eValue.isEmpty) {
NadaInfosetBuilder
} else {
eValue.builder
}
}
private lazy val eRepTypeBuilder: InfosetBuilder = repTypeElementGram.builder

// Shares the memoized unparser above for unparseBegin/unparseEnd, so
// build and unparseTree see identical node-creation behavior.
override lazy val builder: InfosetBuilder = {
if (context.isOutputValueCalc || isSpecifiedLength) {
val eu = unparser.asInstanceOf[ElementUnparserBase]
val contentBuilder = eRepTypeBuilder.orElse(eBuilder)
new ElementInfosetBuilder(context.erd, eu, contentBuilder)
} else {
subComb.builder
}
}
}

case class ElementUnused(ctxt: ElementBase)
Expand Down Expand Up @@ -374,6 +399,14 @@ class ElementParseAndUnspecifiedLength(
new ElementUnparserInputValueCalc(context.erd, uSetVar)
}
}

// Shares the memoized unparser above for unparseBegin/unparseEnd, so
// build and unparseTree see identical nilled/OVC/IVC node-creation behavior.
override lazy val builder: InfosetBuilder = {
val eu = unparser.asInstanceOf[ElementUnparserBase]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I would rather see the same logic rather than type matching. It's much easier to visually verify the conditionalsare the same rather than figuring out which Unparsers are created.

val contentBuilder = eRepTypeBuilder.orElse(eBuilder)
new ElementInfosetBuilder(context.erd, eu, contentBuilder)
}
}

abstract class ElementCombinatorBase(
Expand Down Expand Up @@ -449,4 +482,8 @@ abstract class ElementCombinatorBase(

def unparser: Unparser

lazy val eBuilder: InfosetBuilder = eGram.builder

lazy val eRepTypeBuilder: InfosetBuilder = repTypeElementGram.builder

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package org.apache.daffodil.core.grammar.primitives
import org.apache.daffodil.core.dsom.ModelGroup
import org.apache.daffodil.core.grammar.Gram
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.runtime1.infoset.HiddenGroupInfosetBuilder
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.HiddenGroupCombinatorParser
import org.apache.daffodil.runtime1.processors.parsers.Parser
import org.apache.daffodil.runtime1.processors.unparsers.Unparser
Expand All @@ -34,4 +36,5 @@ final class HiddenGroupCombinator(ctxt: ModelGroup, body: Gram)
override lazy val unparser: Unparser =
new HiddenGroupCombinatorUnparser(ctxt.modelGroupRuntimeData, body.unparser)

override lazy val builder: InfosetBuilder = HiddenGroupInfosetBuilder(body.builder)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.apache.daffodil.core.dsom.*
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.core.layers.LayerSchemaCompiler
import org.apache.daffodil.lib.util.Misc
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.SequenceInfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.LayeredSequenceParser
import org.apache.daffodil.runtime1.processors.parsers.Parser as DaffodilParser
import org.apache.daffodil.runtime1.processors.unparsers.Unparser as DaffodilUnparser
Expand All @@ -47,4 +49,11 @@ case class LayeredSequence(sq: SequenceGroupTermBase, bodyTerm: SequenceChild)

override lazy val unparser: DaffodilUnparser =
new LayeredSequenceUnparser(srd, bodyUnparser)

// The layer transform builds no infoset events, but this is still a one-child
// sequence position: it must push/pop bodyTerm's TRD and advance the
// group index like any sequence child, or next-element resolution on
// the shared InfosetInputter breaks.
override lazy val builder: InfosetBuilder =
SequenceInfosetBuilder(Array(bodyTerm.sequenceChildBuildInfo))
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.apache.daffodil.core.dsom.ElementBase
import org.apache.daffodil.core.grammar.Gram
import org.apache.daffodil.core.grammar.Terminal
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.NilOrContentInfosetBuilder
import org.apache.daffodil.runtime1.processors.parsers.ComplexNilOrContentParser
import org.apache.daffodil.runtime1.processors.parsers.SimpleNilOrValueParser
import org.apache.daffodil.unparsers.runtime1.ComplexNilOrContentUnparser
Expand Down Expand Up @@ -59,4 +61,8 @@ case class ComplexNilOrContent(ctxt: ElementBase, nilGram: Gram, contentGram: Gr
override lazy val unparser =
ComplexNilOrContentUnparser(ctxt.erd, nilUnparser, contentUnparser)

// A nilled complex element has no children to build; nilled-ness is only
// known once the node exists, so this needs a real runtime check, not a
// static pass-through.
override lazy val builder: InfosetBuilder = NilOrContentInfosetBuilder(contentGram.builder)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import org.apache.daffodil.lib.schema.annotation.props.gen.LengthKind
import org.apache.daffodil.lib.schema.annotation.props.gen.OccursCountKind
import org.apache.daffodil.lib.schema.annotation.props.gen.Representation
import org.apache.daffodil.runtime1.dpath.NodeInfo
import org.apache.daffodil.runtime1.infoset.InfosetBuilder
import org.apache.daffodil.runtime1.infoset.SequenceChildInfosetBuildInfo
import org.apache.daffodil.runtime1.processors.parsers.*
import org.apache.daffodil.unparsers.runtime1.*

Expand Down Expand Up @@ -69,6 +71,7 @@ abstract class SequenceChild(protected val sq: SequenceTermBase, child: Term, gr

protected lazy val childParser = child.termContentBody.parser
protected lazy val childUnparser = child.termContentBody.unparser
protected lazy val childBuilder: InfosetBuilder = child.termContentBody.builder

final override lazy val parser = sequenceChildParser
final override lazy val unparser = sequenceChildUnparser
Expand All @@ -82,6 +85,9 @@ abstract class SequenceChild(protected val sq: SequenceTermBase, child: Term, gr
final lazy val optSequenceChildUnparser: Option[SequenceChildUnparser] =
if (childUnparser.isEmpty) None else Some(unparser)

final lazy val sequenceChildBuildInfo: SequenceChildInfosetBuildInfo =
SequenceChildInfosetBuildInfo(unparser, childBuilder)

/**
* There's only parse result helpers here, so let's abbreviate
*/
Expand Down
Loading
Loading