64 lines
3.2 KiB
TeX
64 lines
3.2 KiB
TeX
\section{The \staticdeps{} heuristic}
|
|
|
|
The static analyzer we present, \staticdeps{}, only aims to tackle the
|
|
difficulty~\ref{memcarried_difficulty_arith} mentioned above: tracking
|
|
dependencies across arbitrarily complex pointer arithmetic.
|
|
|
|
To do so, \staticdeps{} works at the basic-block level, unrolled enough times
|
|
to fill the reorder buffer as detailed above; this way, arbitrarily
|
|
long-reaching relevant loop-carried dependencies can be detected.
|
|
|
|
This problem could be solved using symbolic calculus algorithms. However, those
|
|
algorithms are not straightforward to implement, and the equality test between
|
|
two arbitrary expressions can be costly.
|
|
|
|
\medskip{}
|
|
Instead, we use an heuristic based on random values. We consider the set $\calR
|
|
= \left\{0, 1, \ldots, 2^{64}-1\right\}$ of values representable by a 64-bits
|
|
unsigned integer; we extend this set to $\bar\calR = \calR \cup \{\bot\}$,
|
|
where $\bot$ denotes an invalid value. We then proceed as previously for
|
|
register-carried dependencies, applying the following principles.
|
|
|
|
\smallskip{}
|
|
\begin{itemize}
|
|
\item{} Whenever an unknown value is read, either from a register or from
|
|
memory, generate a fresh value from $\calR$, uniformly sampled at
|
|
random. This value is saved to a shadow register file or memory, and
|
|
will be used again the next time this same data is accessed.
|
|
|
|
\item{} Whenever an integer arithmetic operation is encountered, compute
|
|
the result of the operation and save the result to the shadow register
|
|
file or memory.
|
|
|
|
\item{} Whenever another kind of operation, or an operation that is
|
|
unsupported, is encountered, save the destination operand as $\bot$;
|
|
this operation is assumed to not be valid pointer arithmetic.
|
|
Operations on $\bot$ always yield $\bot$ as a result.
|
|
|
|
\item{} Whenever writing to a memory location, compute the written address
|
|
using the above principles, and proceed as with a dynamic analysis,
|
|
keeping track of the instruction that last wrote to a memory address.
|
|
|
|
\item{} Whenever reading from a memory location, compute the read address
|
|
using the above principles, and generate a dependency from the current
|
|
instruction to the instruction that last wrote to this address (if
|
|
known).
|
|
\end{itemize}
|
|
|
|
The semantics needed to compute encountered operations are obtained by lifting
|
|
the kernel's assembly to \valgrind{}'s \vex{} intermediary representation.
|
|
|
|
\medskip{}
|
|
|
|
This first analysis provides us with a raw list of dependencies across
|
|
iterations of the considered basic block. We then ``re-roll'' the unrolled
|
|
kernel by transcribing each dependency to a triplet $(\texttt{source\_insn},
|
|
\texttt{dest\_insn}, \Delta{}k)$, where the first two elements are the source
|
|
and destination instruction of the dependency \emph{in the original,
|
|
non-unrolled kernel}, and $\Delta{}k$ is the number of iterations of the kernel
|
|
between the source and destination instruction of the dependency.
|
|
|
|
Finally, we filter out spurious dependencies: each dependency found should
|
|
occur for each kernel iteration $i$ at which $i + \Delta{}k$ is within bounds.
|
|
If the dependency is found for less than $80\,\%$ of those iterations, the
|
|
dependency is declared spurious and is dropped.
|