Skip orphan LoopOutputSelect when its LoopOutput is missing

Companion defensive fix to 16de9638. `output_body_producer` is keyed
by stream_id and populated from `outputs` (LoopOutput nodes). The
post-loop wiring then indexed `output_body_producer[&stream_id]` for
every LoopOutputSelect, which panics with "no entry found for key" if
extraction lands a LoopOutputSelect whose corresponding LoopOutput
isn't in the LLIR (e.g. a genome that picked a non-LoopOutput
representative for that stream's eclass).

Skip the orphan select rather than panicking. The select node stays
un-substituted, so the post-loop consumer's edge falls through to the
select itself; the select gets removed with the other markers at the
end of unroll. The consumer's edge will dangle, but that's a separate
concern from the unroll-mechanism panic this prevents.

Together with 16de9638, this closes the two `[&key]` index sites in
`unroll_loops_in_llir` that can land on a missing key when egglog
extraction produces a structurally unusual LLIR. Both sites now
gracefully fall through with a defensible semantic (use the body
producer / select node directly), so the unroll mechanism never
panics on extraction-shape variation.

cuda_lite + python CUDA suites still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joe Fioti
2026-04-26 15:28:13 +00:00
parent 16de9638fc
commit 93fb02c495

View File

@@ -1969,9 +1969,16 @@ pub fn unroll_loops_in_llir(llir: &mut LLIRGraph) {
marker_post_sub.insert(end_node, sub);
}
// Each LoopOutputSelect(stream, iter) routes to iter's clone of that
// stream's body producer.
// stream's body producer. Skip if extraction produced an orphan select
// whose stream has no LoopOutput — leaving it un-substituted lets the
// post-loop edge fall through to the select node itself, which gets
// removed with the rest of the loop markers (the consumer's edge then
// points to a removed node, but that's a separate concern from the
// unroll mechanism's correctness invariants here).
for (&select_node, &(stream_id, iter)) in &output_selects {
let body_producer = output_body_producer[&stream_id];
let Some(&body_producer) = output_body_producer.get(&stream_id) else {
continue;
};
let sub = clone_map[iter]
.get(&body_producer)
.copied()