phd-thesis/manuscrit/60_staticdeps/40_staticdeps.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.