Hey @pnkfelix. If you wanted to chat about #65672, specifically why GenKill is implemented for BitSet, I'd be up for a quick sync discussion sometime in the next few days. Migrating the borrow-check analyses resulted in a ~0.5% reduction in instruction count for clean builds of many non-trivial benchmarks, so this has moved up my list of priorities somewhat. Assuming that I haven't broken anything, my guess is that the reductions stem from overhead around FlowsAtLocation, which is no longer used.

or wait, maybe I confused myself and was thinking that trait GenKillis the transfer function, rather than an abstraction over the state vector

let me double-check the PR again. If trait GenKill is just meant to abstract over the choice of how to represent the state vector, then all we have is a small documentation issue, not a design problem.

(but then again, I can't blame myself for thinking this, since you did impl GenKill for GenKillSet, where the latter does indeed represent the transfer function.)

I still think you should kill off trait GenKill, following a principle of YAGNI. Or in my own parlance: wait until you actually have one (and preferably more than one) implementers of a trait before you introduce it.

and I think the only place where you instantiate it as a GenKillSet (rather than as a BitSet) is here, in new_gen_kill. Is that true, or am I missing something?

So you are using the trait GenKill for abstract over both the representation of an accumulated transfer function and over the represent the state vector). I'm still musing about the right way to document this.

(because I can at least comprehend the motivation, of 1. wanting to ensure that people only write the definition of their transfer functions once, but also 2. not wanting to have to construct a GenKillSet representing the transfer function, only to then immediately apply it a single time to a BitSet before discarding it. Better to just operate on the BitSet directly in that case.

Yes, this is true. It will be used in more places in the future, but I can worry about that later. This is the place where we compute the cumulative block transfer function and save it for later.

So you are using the trait GenKill for abstract over both the representation of an accumulated transfer function and over the represent the state vector). I'm still musing about the right way to document this.

Correct. This is the core idea.

So I'm just thinking that the documentation needs to be tweaked to make this design more immediately clear, both in terms of motivation and also in terms of what the relevant abstractions are.

Would listing the two use-cases help? Sometimes you want to build up a cumulative transfer function and sometimes you don't care about about the transfer function itself and just need the dataflow state at a certain point.

Basically your second-to-last message above.

@pnkfelix I added some docs to #65672 to address your concerns. Also, the latest perf numbers for the new framework are good, with an ~1.0% reduction in instruction count during check builds for many real-world crates.