Class StreamDemand


final class StreamDemand extends AbstractStreamStageGenerator
THE demand rule of a stream pipeline: head N bounds the PULL (decision 10, rule B) - use this, never re-derive which heads gate a loop or how the gate is folded into a loop condition.

Every head in a segment stops the pull once it has its N items. A segment runs from the nearest upstream stage that must see everything (sort, tail, join, and group/split/async, which are lowered as drains) - or the source - down to the next such stage. A must-drain stage always consumes its whole input; a head after it limits only what it emits, so it gates the drain's EMISSION loop, never the drain.

The gate is ordinary IR: each loop condition becomes c1 < l1 AND (c2 < l2 AND ... hasNext()) as nested short-circuit LOGICAL_AND blocks, the counters FIRST, so hasNext()/next() are never called once any head is satisfied. That ordering is backend Contract 12: no back end may hoist, prefetch or speculatively call hasNext() - on an unbounded or infinite source it blocks or never ends.

  • Constructor Details

  • Method Details

    • endsDemandSegment

      static boolean endsDemandSegment(int opType)
      True for a stage that is lowered as a drain (or emits only after its loop): a head downstream of it cannot bound what flows into it, so the demand segment ends there. THE rule is StreamStages.endsDemandSegment(int), shared with the front end's boundedness checks.
    • segmentEnd

      static int segmentEnd(List<EK9Parser.StreamPartContext> stages)
      The index of the first stage that ends the demand segment, or -1 when every stage streams. The stages before it are the ones a head among them can stop pulling for - which is also how far a multi-source cat a, b pulls each source in turn before it must gather (decision 15 b).
    • heads

      static List<StageState> heads(List<EK9Parser.StreamPartContext> stages, List<StageState> stageStates)
      THE heads that bound the pull of a loop over stages: every head before the first stage that ends the demand segment, in pipeline order. Empty when there is none (the loop is a plain hasNext()).
    • headsCappingAsync

      static List<StageState> headsCappingAsync(List<EK9Parser.StreamPartContext> postAsyncStages, List<StageState> postAsyncStates)
      THE heads that cap an async (decision 15 d): those reached from the async through stages that pass EVERY item on - map, call, tee and another head - in pipeline order. Every task started then yields an item such a head counts, so a head N needs only N tasks and the async starts no more than that: the caller bounds what is SUBMITTED by these limits. A stage that can drop an item (filter, reject, uniq, skip, flatten - an element may flatten to nothing) or must see everything stops the scan; past it, which tasks yield usable items is unknown, so the window stays. postAsyncStages/postAsyncStates start at the stage straight after the async.
    • appendDemandCondition

      String appendDemandCondition(List<IRInstr> conditionEvaluation, List<StageState> bounds, List<IRInstr> rightInstructions, String rightResult, String scopeId, DebugInfo debugInfo)
      Append the demand-bounded condition c1 < l1 AND (... AND right) to conditionEvaluation and return its Boolean result. With no bounds, right is appended unchanged (byte-identical to an ungated loop). bounds are counter/limit pairs: the heads, or the async window's in-flight bound.
    • continueCondition

      FreshBooleanOperand continueCondition(List<StageState> heads, String scopeId, DebugInfo debugInfo)
      The demand condition of a loop whose own continuation is not a hasNext() - a for range, whose range test the back end evaluates itself. Returns the heads alone (c1 < l1 AND ...) in their own scope-free instruction list, or null when no head bounds the loop.
    • appendDemandOverPrimitive

      String appendDemandOverPrimitive(List<IRInstr> condition, List<StageState> heads, List<IRInstr> primitiveInstructions, String primitiveResult, String scopeId, DebugInfo debugInfo)
      Gate a loop whose continuation is a runtime helper's PRIMITIVE hasNext-style call (Ek9Group.hasNextGroup, Ek9Async.hasNext): with no heads the condition is unchanged; otherwise the primitive is lifted to a Boolean (Boolean._of) so it can be the right operand of the demand AND. Returns the {primitive} to branch on, appending everything to condition.
    • appendBoundedCondition

      String appendBoundedCondition(List<IRInstr> conditionEvaluation, List<IRInstr> leftInstructions, String leftResult, String leftPrimitive, List<IRInstr> rightInstructions, String rightResult, String scopeId, DebugInfo debugInfo)
      Append a short-circuit LEFT AND RIGHT Boolean condition to conditionEvaluation and return the combined Boolean result variable. RIGHT is evaluated ONLY when LEFT's primitive is true (LOGICAL_AND_BLOCK short-circuit). This is the generic two-operand AND-block; callers build LEFT (the short-circuit decision: head's counter < limit, the window's inFlight < W, etc.) and pass its result+primitive, plus the RIGHT operand's instructions+result (e.g. the managed hasNext()). Mirrors GuardedConditionEvaluator's guard-into-condition fold.