One could argue that this should go in the function and not be an
on-demand analysis pass but we can do that later. For now, we just
break it out into a separate data structure.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
Instead of tracking pinned things in the register allocator, we split
the register allocator into RegAllocator and PinnedRegAllocator. The
RegAllocator struct only allows for very simple single-SSA allocations
and frees. It tracks locations of everything, what's used, etc. but
otherwise knows nothing about pinning or vectors.
The new PinnedRegAllocator struct wraps a RegAllocator by taking a
mutable reference to it. It provides support for pinning and all the
vector stuff. To destroy a PinnedRegAllocator, finish() is called which
re-places any evicted SSA values and populates an OpParCopy with any
needed copies. Because PinnedRegAllocator owns a mutable reference to
the RegAllocator, it's impossible to mix uses of PinnedRegAllocator and
RegAllocator. This ensures that, for as long as the pinned version
exists, nothing can be allocated which migh escape the pinning.
This fixes a bunch of corner cases when register pressure gets tight.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
Since the top value is always the default register, we don't need to
worry about overflowing a u8. What we do need to worry about is
register files with zero registers which is a thing pre-Turing for
uniform register files. Use num instead of max so we don't end up
subtracting 1 from 0.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
While coping the entire RA struct from the predecessor is probably
faster, it may contain values which are not live in the current block.
We could purge those but the iteration gets tricky with Rust. It's far
clearer and more rust-friendly to deal with it as an initialization
problem.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
Sometimes it's useful to have something that's per-register-file like we
do for register allocation. Since this is generally useful, add a
struct for it. In future, it might be neat to pull in the enum_map
crate which basically does this generically.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
If we walk the blocks forwards, we end up having a minimum O(n^2)
algorithm because everything has to propagate bottom to top. Looping
over the blocks bacwards ensures that all the liveness information is
propgated in the first pass in the absence of back edges.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
All passes allocate a Vec per Instr during map(). This is wasteful,
because most instances of map() produce a single instruction (by
mapping one instruction to another instruction) or no instructions at
all.
In such cases, they return an empty Vec, or a Vec with a single entry.
Rework the signatures so that a Vec is only when mapping one instruction
into many.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
Heap-allocate Instrs to avoid copying them around whenever they are
mutated by a pass. This lowers the amount of copies in detriment of
cache-locality.
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>
Instead of I/D/FMov instructions, just use [DF]Add instead. For ineg, we
add a new INeg instruction which we can lower to IADD3 later. The
reason for this is that IAdd3 is complicated and makes detecting an ineg
rather annoying. Also, if we ever bring NAK up on older hardware, not
all hardware has IAdd3 and INeg will be lowerable everywhere.
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/24998>