Replace the LSRA stack with a hash table.
authorPat Gavlin <pagavlin@microsoft.com>
Tue, 26 Jul 2016 13:27:06 +0000 (06:27 -0700)
committerPat Gavlin <pagavlin@microsoft.com>
Mon, 1 Aug 2016 22:14:01 +0000 (15:14 -0700)
commitbe52cf34bcf76a56b46a6ff8ecd607614b327d2f
tree963d25e8eb26ff3de62bcc7d4c86764181d0af7e
parent78957b1db0a15ccf89ebca5da7c910a45001b95b
Replace the LSRA stack with a hash table.

LSRA currently uses a stack to find the `LocationInfo` for each register consumed
by a node. The changes in this stack's contents given a particular node are
governed by three factors:
- The number of registers the node consumes (`gtLsraInfo.srcCount`)
- The number of registers the node produces (`gtLstaInfo.dstCount`)
- Whether or not the node produces an unused value (`gtLsraInfo.isLocalDefUse`)

In all cases, `gtLsraInfo.srcCount` values are popped off of the stack in the
order in which they were pushed (i.e. in FIFO rather than LIFO order). If the
node produces a value that will be used, `gtLsraInfo.dstCount` values are
then pushed onto the stack. If the node produces an unused value, nothing is
pushed onto the stack.

Naively, it would appear that the number of registers a node consumes would be
at least the count of the node's non-leaf operands (to put it differently, one
might assume that any non-leaf operator that produces a value would define at
least one register). However, contained nodes complicate the situation: because
a contained node's execution is subsumed by its user, the contained node's
sources become sources for its user and the contained node does not define any
registers. As a result, both the number of registers consumed and the number of
registers produced by a contained node are 0. Thus, contained nodes do not
update the stack, and the node's parent (if it is not also contained) will
pop the values produced by the contained node's operands. Logically speaking,
it is as if a contained node defines the transitive closure of the registers
defined by its own non-contained operands.

The use of the stack relies on the property that even in linear order the
JIT's IR is still tree ordered. That is to say, given an operator and its
operands, any nodes that execute between any two operands do not produce
SDSU temps that are consumed after the second operand. IR with such a
shape would unbalance the stack.

The planned move to the LIR design given in dotnet/coreclr#6366 removes the tree order
constraint in order to simplify understanding and manipulating the IR in the
backend. Because LIR may not be tree ordered, LSRA may no longer use a stack
to find the `LocationInfo` for a node's operands. This change replaces the
stack with a map from nodes to lists of `LocationInfo` values, each of
which describes a register that is logically defined (if not physically
defined) by that node. Only contained nodes logically define registers that
they do not physically define: contained nodes map to the list of
`LocationInfo` values logically defined by their operands. All non-contained
nodes map to the list of `LocationInfo` values that they physically define.
Non-contained nodes that do not define any registers are not inserted into
the map.

Commit migrated from https://github.com/dotnet/coreclr/commit/41a1d0a422c8c444479809ab9acd3854d8bf66cc
src/coreclr/src/jit/gentree.cpp
src/coreclr/src/jit/gentree.h
src/coreclr/src/jit/lower.cpp
src/coreclr/src/jit/lsra.cpp
src/coreclr/src/jit/lsra.h
src/coreclr/src/jit/nodeinfo.h
src/coreclr/src/jit/smallhash.h [new file with mode: 0644]
src/coreclr/src/jit/utils.h