turbo-tasks-backend: store reverse dependency edges as lists, not hash sets
The reverse dependency edges — output_dependent, cell_dependents,
cell_dependents_hashed — were AutoSet (hash sets). Every reverse-edge insert is
now gated on the reader's forward edge being newly added (need_reverse), so the
same edge is never inserted twice, and nothing ever queries the reverse sets for
membership (no *_dependents_contains). The hash set's dedup and hashing are dead
weight.
Replace them with an unordered SmallVec-backed list via a new `storage = "list"`
kind in the task_storage proc macro: push (append, no dedup) / remove (linear
position + swap_remove) / iter, no contains accessor. A debug-only assertion in
the generated `push_*` continuously checks the no-duplicate invariant, so any
future ungated insert is caught in debug/test builds at zero release cost.
The reverse inserts in the read paths (try_read_task_cell / try_read_task_output)
stay under task_pair — both the target and reader are locked together, so the
forward and reverse edges are visible atomically — but the reverse push is now
gated on the forward add reporting a genuinely new edge so the list never gets a
duplicate.
Removing the hashbrown table + load-factor slack would have shrunk the boxed
TaskStorage from 128 to 120 bytes, but TaskStorage is always heap-allocated and
mimalloc rounds 120 up to its 128-byte size class — so the inline output_dependent
list is sized List<TaskId, 6> to fill the class exactly, spending the slack on
inline dependent capacity (6 before spilling to heap) instead of leaving it idle.
187 tests pass, including test_all_in_one (the lost/doubled-invalidation canary).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>