\documentclass[11pt]{article}
\usepackage{ifthen}
\usepackage{alltt}
% \usepackage{indentfirst}
\usepackage{latexsym}
\usepackage[pdftex]{graphicx}
\usepackage{gdefs}
% \usepackage{logdef}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{setspace}
\usepackage{rotating}
\usepackage{longtable}
\usepackage{hhline}
\usepackage{refs}
\usepackage{graph}
\usepackage{changebar}
\usepackage{array}
\usepackage{wrapfig}
\usepackage{color}
\usepackage[small,compact]{titlesec}

\usepackage{tweaklist}
% this sets the spacings in the itemize environment
\renewcommand{\itemhook}{\setlength{\topsep}{0pt}%
  \setlength{\itemsep}{0pt}%
  \setlength{\parsep}{0ex}%
}

% this sets the spacings in the enumerate environment
\renewcommand{\enumhook}{\setlength{\topsep}{0pt}%
  \setlength{\itemsep}{0pt}%
  \setlength{\parsep}{0ex}%
}

% this sets the spacings in the description environment
\renewcommand{\descripthook}{\setlength{\topsep}{0pt}%
  \setlength{\itemsep}{0pt}%
  \setlength{\parsep}{0ex}%
}

\setlength{\topmargin}{0.0in}
\setlength{\headheight}{0.0in}
\setlength{\headsep}{0.0in}
\setlength{\textwidth}{6.5in}
\setlength{\oddsidemargin}{0.0in}
\setlength{\evensidemargin}{0.0in}
\setlength{\textheight}{9.0in}

\setcounter{topnumber}{5}
\def\topfraction{0.99}
\def\textfraction{0.05}
\def\floatpagefraction{0.9}
\setcounter{bottomnumber}{5}
\def\bottomfraction{0.99}
\setcounter{totalnumber}{10}
\def\dbltopfraction{0.99}
\def\dblfloatpagefraction{0.8}
\setcounter{dbltopnumber}{5}

\setlength{\marginparwidth}{.95in}
\let\oldmarginpar\marginpar
\renewcommand\marginpar[1]{\-\oldmarginpar[\raggedleft\footnotesize #1]%
{\raggedright\footnotesize #1}}

\let\oldthebibliography=\thebibliography
  \let\endoldthebibliography=\endthebibliography
  \renewenvironment{thebibliography}[1]{%
    \begin{oldthebibliography}{#1}%
      \setlength{\parskip}{0ex}%
      \setlength{\itemsep}{0ex}%
  }%
  {%
    \end{oldthebibliography}%
  }

\newcommand{\Func}{\textit{Func}}
\newcommand{\Succ}{\textit{Succ}}
\newcommand{\Merge}{\textit{Merge}}
\newcommand{\Id}{\textit{Id}}
\newcommand{\push}{\textit{push}}
\newcommand{\pop}{\textit{pop}}
\newcommand{\cs}{\textit{cs}}
\newcommand{\First}{\textit{First}}
\newcommand{\Follow}{\textit{Follow}}
\newcommand{\CS}{\textit{CS}}
\newcommand{\Productions}{\textit{Productions}}


\title{Probabilistic Calling Context \\
       and Efficient Call-Trace Collection\\
       CS701 \\
}

\author{Thomas Reps \\ \small[Based on notes taken by Ashkan Forouhi on November 17, 2015]}
\date{}

\begin{document}           % End of preamble and beginning of text.

\maketitle
\begin{abstract}
This lecture concerns the material in two papers:
``Probabilistic Calling Context'' by Bond and McKinley \cite{OOPSLA:BM07},
and ``Efficient Call-Trace Collection'' by Wu et al.\ \cite{POPL:WXCZZ16}.
\end{abstract}

\section{Introduction}

In this lecture, we switch our focus from the previous profiling
topics covered (path profiling, whole program paths, and statistics
recovered from whole program paths) to focus on issues related to
calling contexts.
There are two kinds of things we can imagine a tool making use of.
First, it might see (and process) the whole sequence of matching calls
and returns with a nesting relationship, as shown below:

\begin{center}
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[width=2.1in]{figures/string-with-nesting-relationship.eps}
\end{tabular}
\end{center}

\noindent
Alternatively, it might not gather the whole trace right away,
but for efficiency gather enough of the trace so that the whole
trace can be recovered after execution finishes, via post-processing.
The trick here is that there is some redundancy, so we will
have the tool put in instrumentation in a certain number of places,
and after execution finishes perform an inference process to
recover the information about the full trace.

The first situation is addressed in the paper by Bond and
McKinley \cite{OOPSLA:BM07}.
The second situation is addressed in the paper by Wu et al.\ \cite{POPL:WXCZZ16}.
We present results from both papers in this lecture.

\section{Probabilistic Calling Context}

When logging or profiling, the goal is to gather some kind of
information, and you may be interested in having
each of the pieces of information tagged with the calling context
that holds at the time the information was generated.
Thus, every time the program or profiler emits a piece of information,
it needs to have a calling-context tag available.

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[height=2.1in]{figures/example-stack.eps}
\end{tabular}
\caption{A stack representing the calling context.}
\label{Fi:ExampleStack}
\end{figure}

Consider the stack shown in \figref{ExampleStack}.
Suppose that something happens after calling $R$.
We want to emit that information tagged with the calling context
$\B{\cdot, \textrm{Info}}$, where $\cdot$ denotes some kind of
calling-context tag.
The problem is that the obvious technique, which is brute force,
is very expensive and causes your program to slow down a lot.
Thus, the question is ``What kind of tracking can we
do to make this information available whenever we want it?''
We obviously have to do something when calls and returns occur,
because the stack keeps changing;
however, we want to do as little work as possible.

\subsection{Representing and Maintaining Calling Context in Static Analysis}
\label{Se:RepresentingAndMaintainingCallingContextInStaticAnalysis}

Let's start by talking about so-called ``call-strings.''
They were introduced 34 years ago in the second part of the
paper on interprocedural dataflow analysis
by Sharir and Pnueli \cite{PFA:SP81}.
The actual principle is pretty simple (and what I'll actually describe
is a method of manipulating call-strings that improves on what
was given by Sharir and Pnueli).

The ``call-strings approach'' to dataflow analysis is appropriate for
static-analysis domains for which it is hard to create transfer
functions that meet the conditions needed to apply the so-called
``functional approach'' to dataflow analysis that is described in the
first part of the Sharir-Pnueli paper---i.e., dataflow analysis based
on $\Phi$-functions.
Recall that for the functional approach, when you have the
transfer function associated with a path and the transfer
function associated with an edge that extends the path,
you have to be able to mimic the composition of the two functions.

\begin{center}
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[width=3.1in]{figures/abstract-composition.eps}
\end{tabular}
\end{center}

\noindent
That means that you have to have an abstract domain in which
an abstract-domain element represents a transition relation
(of possible transitions) between two sets of states:

For some abstract domains, you may have abstract values that
represent sets of states, but for which there is no convenient way to
represent possible transitions between sets of states.
That is, the abstract domain lets you over-approximate the set of
states that can arise at a given program point, but not a way to
represent the possible state transitions that can occur in going
from program point $p_1$ to program point $p_2$ (such as the
procedure's entry node and exit node, respectively).
However, you may still want to do some kind of interprocedural
analysis.
The most difficult issue to deal with is procedure calls.
In particular, when the analyzer considers a called procedure,
it may have gotten to that procedure from multiple call-sites.
Each call-site may cause different abstract values to be passed into
the entry node of the callee.
Unfortunately, you do not have a summary function for the callee---and
it is that function that allows us to re-use the results of
the analysis of the callee in the functional approach to interprocedural
dataflow analysis.
Consequently, the analyzer will have to analyze the callee multiple
times, with different information each time.
Moreover, when the analyzer has no way to track what initial information
came from what call-site, it will pass irrelevant or imprecise answers
back up to different call sites along invalid paths like the one shown below:

\begin{center}
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[width=3.1in]{figures/invalid-path.eps}
\end{tabular}
\end{center}

The mechanism that Sharir and Pnueli came up with to address this
problem is essentially to equip each dataflow fact with a
finite-state machine (FSM), where each FSM state is an
abstraction of the contents of the call stack on entry to the procedure.
You first pick a length that you are going to allow the call stack to
grow to (for our example, we've picked 2).

Consider the call stack and activation record for the current procedure shown
in \figref{ExampleStack}(a).
The call string is an abstraction of caller information, not including
the current procedure (if you want the current procedure, you can always
add it to the end of the current call-string).
Thus, if the length is 2, the call-string would be the one shown in
\figref{ExampleStack}(b).

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{3.0ex}}c@{\hspace{0ex}}}
  \includegraphics[height=2.1in]{figures/stack-suffix.eps}
  &
  \includegraphics[height=1.0in]{figures/abstracted-stack.eps}
  \\
  (a) & (b)
\end{tabular}
\caption{(a) A stack suffix; (b) the abstraction of the stack suffix with a call-string of length 2.}
\label{Fi:CallStringsApproach}
\end{figure}

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{3.0ex}}c@{\hspace{0ex}}}
  \includegraphics[width=.41\textwidth]{figures/propagation-step1.eps}
  &
  \includegraphics[width=.45\textwidth]{figures/propagation-step2.eps}
  \\
  (a) & (b)
  \\
  \\
  \includegraphics[width=.45\textwidth]{figures/propagation-step3.eps}
  &
  \includegraphics[width=.52\textwidth]{figures/propagation-step4.eps}
  \\
  (c) & (d)
  \\
  \\
  \includegraphics[width=.52\textwidth]{figures/propagation-step5.eps}
  &
  \\
  (e) &
\end{tabular}
\caption{Propagation---over call-edges and return-edges---of dataflow facts tagged with call-strings.}
\label{Fi:CallStringsApproachPropagation}
\end{figure}

The question is now ``What does the analyzer do after it analyzes a
callee and needs to propagate information back to a calling procedure?''
The idea is that at each point in the program,
you have a set of dataflow facts, and each fact
will be tagged with some call-string.
Suppose that you are at $C_1$, the call-string length is 2,
and you have two call-string/value pairs:
$<\begin{array}{|c|}\hline M \\ \hline R \\ \hline \end{array}$ $, v_1>$
and
$<\begin{array}{|c|}\hline M \\ \hline \end{array}$ $, v_2>$.
When you enter procedure Q,
$\begin{array}{|c|}\hline M \\ \hline R \\ \hline \end{array}$
temporarily becomes
$\begin{array}{|c|}\hline M \\ \hline R \\ \hline C_1 \\ \hline \end{array}$,
but because this call-string is of length 3, it is turned into the
saturated call-string
$\begin{matrix}*\\\begin{array}{|c|}\hline R \\ \hline C_1 \\ \hline \end{array}\end{matrix}$.
Consequently, we obtain the tagged dataflow items
shown in \figref{CallStringsApproachPropagation}(a).
When the analyzer reaches the end of $Q$, the call-strings remain unchanged.
(They may have changed as the analyzer propagates information into
procedures called by $Q$, but they will be restored when the called procedure
returns to $Q$.) Thus, by the time the analyzer gets to the exit node
of $Q$, we have the situation shown in \figref{CallStringsApproachPropagation}(b).

Meanwhile, there may have been dataflow information propagated into $Q$ from
call-site $C_2$.
Suppose that there is only one dataflow value there, namely,
$<\begin{array}{|c|}\hline M \\ \hline R \\ \hline \end{array}$ $, v_2>$.
(See \figref{CallStringsApproachPropagation}(c).)
When that dataflow fact is propagated to the entry node of $Q$, the same
process is applied.
(We call this processes of modifying the call-string $\push^\#$ because the
call-strings are an abstraction of the stack, and---in the concrete semantics---the
call on $Q$ involves pushing a new activation record onto the stack.)

After the information from call-site $C_2$ propagates to the exit node of $Q$,
we have the situation depicted in \figref{CallStringsApproachPropagation}(d).
Now the question is, ``At the exit node, how do we propagate the
tagged dataflow facts back up to callers?''
Part of the solution is easy:
just look at the ``top'' of the stack.
(Note that in the figures, the stack-top is shown at the bottom.)
In the example, the analyzer only transfers back the first two pairs
to the return-site that corresponds to call-site $C_1$, and the last
pair is transferred to the return-site that corresponds to call-site
$C_2$.
Just as we had a $\push^\#$ before, we need to define a $\pop^\#$.
What does a $\pop^\#$ do?
For the case of the unsaturated pair
$<\begin{array}{|c|}\hline M \\ \hline C_1 \\ \hline \end{array}$ $, v'_2>$
the answer is clear: we just remove $C_1$ from the call-string, and
go back to having $<\begin{array}{|c|}\hline M \\ \hline \end{array}$ $, v'_2>$.
But what about the other pairs?

Here is where we cheat a little bit.
Normally, when you are doing path problems, you are only allowed to
``follow your nose'' by proceeding along edges of the graph.
However, for the return from a procedure, we let the return-node
in the caller have \emph{two} predecessors, and use a two-argument
$\Merge$ function to obtain the value that is passed into the
return-node.
The $\Merge$ function attempts to match each one of the call-strings
at the call-node with each one of the call-strings at the exit node
to check if it is possible that a $\push^\#$ of the caller fact's
call-string creates the call-string of the callee fact's call-string.
If there is a match, then the call-string used at the return-node
is the one obtained from the call-node (whereas the second component
of the pair is obtained from the exit-node's pair---modulo any adjustments
needed to handle the change of scopes from the callee to the caller).

For example, when checking against
$\begin{array}{|c|}\hline M \\ \hline R \\ \hline \end{array}$,
$\begin{matrix}*\\\begin{array}{|c|}\hline R \\ \hline C_1 \\ \hline \end{array}\end{matrix}$
matches, but
$\begin{matrix}*\\\begin{array}{|c|}\hline R \\ \hline C_2 \\ \hline \end{array}\end{matrix}$
does not:
$\push^\#\left(\:\begin{array}{|c|}\hline M \\ \hline R \\ \hline \end{array}\:,C_1\right)$
$= \begin{matrix}*\\\begin{array}{|c|}\hline R \\ \hline C_1 \\ \hline \end{array}\end{matrix}$,
but
$\push^\#\left(\:\begin{array}{|c|}\hline M \\ \hline R \\ \hline \end{array}\:,C_1\right)$
$\neq \begin{matrix}*\\\begin{array}{|c|}\hline R \\ \hline C_2 \\ \hline \end{array}\end{matrix}$.
The result of this process is depicted in \figref{CallStringsApproachPropagation}(e).

The manipulations of the call-string are reminiscent of state
changes in a finite-state machine.
However, they differ from what goes on in a finite-state machine because
the state change at a return-node involves a \emph{two-argument}
transition function.
In fact, the kind of mechanism pioneered by $\Merge$ functions
has been formalized as a purely automata-theoretic device
called a Visibly Pushdown Automaton (VPA) \cite{STOC:AM04}.
A VPA accepts words whose alphabet symbols are labeled to distinguish
them as coming from three disjoint sets of symbols:
\emph{internal symbols}, \emph{call symbols},
and \emph{return symbols}.\footnote{Technically, an interprocedural
  dataflow analyzer that uses $\Merge$ functions acts
  like a mixture of VPAs and a related formalism called
  Nested Word Automata (NWA) \cite{DLT:AM2006}.
  In particular, the use of three kinds of alphabet symbols
  is similar to VPAs;
  the use of a two-argument state-transition function for returns
  is similar to NWAs.
}
In our case, the transitions for call-strings have a particularly simple structure:
\begin{itemize}
  \item
    Internal Edges: $\delta_i = \{ (\cs, e, \cs) \}$ (i.e., $\delta_i$
    does not change the call-string state)
  \item
    Call Edges: $\delta_c = \{ (\cs_c, c, \push^\#(\cs_c,c)) \}$ (i.e., $\delta_c$ performs a $\push^\#$)
  \item
    Return Edges: $\delta_r = \{ (\cs_x, \cs_c, r, \cs_c) \mid \push^\#(\cs_c,c_r) = \cs_x \}$,
    where $c_r$ denotes the call-site that corresponds to the target of $r$.
    (In other words, $\delta_r$ restores $\cs_c$ whenever $\cs_x$ is
    one $\push^\#(\cdot,c_r)$ away from $\cs_c$.)
\end{itemize}

\subsection{Representing and Maintaining Calling Context in Dynamic Analysis}

What we discussed in \sectref{RepresentingAndMaintainingCallingContextInStaticAnalysis}
was one way in which calling context can be represented and maintained
by a static analyzer.
Let's now return to the problem of representing and maintaining calling
context during dynamic analysis.
As discussed at the beginning of the lecture, we want to set things up
in the run-time environment so that, at any moment, we can tag information
with a label that represents the current calling context
(see \figref{ExampleStack}).

Obviously, the call stack is part of the run-time environment,
so one approach would be to walk the stack and emit a list of
the pending calls.
Typically, a stack frame has a stored frame pointer, which points
into the caller's activation record;
moreover, one would typically have the ability to move from
that address to the stored frame pointer in the caller's
activation record, and so on.
By this means you can traverse the entire stack---in essence,
the collection of activation records on the stack is just a
singly linked list.
You also have to have some way of identifying the call-site
to which a given activation record corresponds.
However, in each activation record, the return address
in the caller is typically stored at a given offset from the frame pointer,
which provides us with the information that we need for
emitting the list of pending calls.

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{3.0ex}}c@{\hspace{3.0ex}}c@{\hspace{0ex}}}
  \includegraphics[height=3.0in]{figures/calling-context-tree-1.eps}
  &
  \includegraphics[height=3.0in]{figures/calling-context-tree-2.eps}
  &
  \includegraphics[height=3.0in]{figures/calling-context-tree-3.eps}
  \\
  (a) & (b) & (c)
\end{tabular}
\caption{Positions in a calling-context tree.}
\label{Fi:CallingContextTree}
\end{figure}

Another thing you could do is to have the program build a
calling-context tree.
Suppose that at a given moment, the program's calling context
is represented by the node indicated in \figref{CallingContextTree}(a).
If the program now returns from the current called procedure,
performing a pop, you would leave that node, but would not de-allocate it---thus
moving to the situation depicted in \figref{CallingContextTree}(b).
If the program does another pop and then a push that happens to go to
something already contained in the calling-context tree, you would not
have to build anything, but just move to an already-existing node,
as shown in \figref{CallingContextTree}(c).
Thus, at any moment that you want to write out the current calling-context
information, you can just write the address of the current node in the
calling-context tree.

The overhead that the authors quote is a 2--4$\times$ slow-down of the
program if you use a calling-context tree.
In contrast, with their method, the overheads are very low;
they give three numbers, corresponding to three hypothetical clients that
require calling-context information in different circumstances:
\begin{center}
  \begin{tabular}{ | l | r | }
    \hline
    A query at every system call & 0\% overhead \\ \hline
    A query at every \texttt{java.util} call & 3\% overhead \\ \hline
    A query at every \texttt{java.API} call & 9\% overhead \\ \hline
  \end{tabular}
\end{center} 
It must be said that with their method, you will not have a firm
guarantee that you are getting completely accurate information, but
their numbers are such that with either 64-bit or 32-bit values, there
is a good chance that no imprecision arises.

Before getting to what they do, let's think a little bit about what
the requirements are:
\begin{itemize}
  \item
    You need to make it efficiently computable.
  \item
    You need to make it deterministic. (This requirement is not actually
    a firm requirement.
    You need to make it re-playable, meaning that if you are given whatever
    the calling-context descriptor turns out to be, you can figure out
    unambiguously what set of call stacks it corresponds to.)
  \item
    You need to avoid ``stupid'' (i.e., avoidable) collisions. For
    example, you do not want the calling context for ``Main calls A calls B calls C''
    to have the same descriptor as ``Main calls B calls C calls A.''
    This example suggests that whatever operation is performed,
    it needs to be non-commutative.
\end{itemize}
What Bond and McKinley do is simple and cute:
You keep a numeric value;
perform a very simple calculation on it at each call;
and perform its inverse at each return.
For an initial value $v$, a push is going to take you to
$3*v + \textrm{hash}(\textrm{call-site})$.
The hash function they use is a hash of the signature,
plus the line number.
On a pop, they do the reverse: subtract the hash, and
divide it by 3.
Alternatively, you can store a copy of the value in a
local variable, so that when you return, you can
just pick it up from the local variable.
In the paper, they say that they use this technique
to help ``... correctly [maintain $v$] in the face of exception control flow;''
however, the paper does not explain what they do for exceptions,
nor whether exceptions cause the technique to lose temporarily
the ability to provide a usable calling-context tag.

In each Java method, they instrument the code using
the scheme shown below, where the boxed code indicates
the instrumentation that is introduced, and $f$ is the function
$\lambda v . \lambda \CS . 3 * v + \textrm{hash}(\CS)$:

\verb|method () {|\\
\indent \indent \fbox{\texttt{int temp = v;}}\\
\indent \indent ...\\
\indent \indent \fbox{\texttt{v = f(temp, CS\_1);}}\\
\color{red}
\verb|CS_1:| 
\color{black}
\verb|A();|\\
\indent \indent ...\\
\indent \indent \fbox{\texttt{v = f(temp, CS\_2);}}\\
\color{red}
\verb|CS_2:| 
\color{black}
\verb|B();|\\
\indent \indent ...\\
\verb|}|\\

One good feature of the approach is that one adds the instrumentation
code and then compiles.
This order allows the method to work even if inline-expansion
optimizations are performed.
For instance, suppose that the optimizer performs an in-line
expansion of \texttt{method\_2} in \texttt{method\_1} at
call-site \texttt{CS\_i}:

\verb|method_1 () {|\\
\indent \indent \fbox{\texttt{int temp = v;}}\\
\indent \indent ...\\
\indent \indent \fbox{\texttt{v = f(temp, CS\_i);}}\\
\color{red}
\verb|CS_i:| 
\color{black}
\verb|method_2();|\\
\indent \indent ...\\
\verb|}|\\
\\
\verb|method_2 () {|\\
\indent \indent \fbox{\texttt{int temp = v;}}\\
\indent \indent ...\\
\indent \indent \fbox{\texttt{v = f(temp, CS\_j);}}\\
\color{red}
\verb|CS_j:| 
\color{black}
\verb|method_3();|\\
\indent \indent ...\\
\verb|}|\\

\noindent
Then at the in-lined instance of \texttt{CS\_j}, we will have
\texttt{v = f(f(temp, CS\_i), CS\_j);},
which equals $9*v + 3*\textrm{hash}(\texttt{CS\_i}) + \textrm{hash}(\texttt{CS\_j})$.
Note that $3*\textrm{hash}(\texttt{CS\_i}) + \textrm{hash}(\texttt{CS\_j})$ is a
compile-time constant.

Like the call-strings used by Sharir and Pnueli, $v$ does not include the
contribution from the current procedure.
However, the appropriate calling-context tag can be computed by
applying $f$ with the current program point:

\verb|method () {|\\
\indent \indent \fbox{\texttt{int temp = v;}}\\
\indent \indent ...\\
\indent \indent \fbox{\texttt{v = f(temp, CS\_1);}}\\
\color{red}
\verb|pp:| 
\color{black}
\verb|query(f(temp,pp));  // query is the user-specified monitoring function|\\
\indent \indent ...\\
\verb|}|\\

Bond and McKinley had 19 benchmark programs, and reported measurements
when the benchmarks were run with medium and large input sets.
What they found was that when they went from medium to large inputs,
they encountered as many as $6\times$ as many calling contexts.
However, in 10--12 of the 19 benchmarks---depending on which of the
hypothetical clients (mentioned earlier) they assumed was in use---there
were \emph{no} new calling contexts.
In other words, for a significant number of the examples, the calling
context is pretty stable, but for some it can go up substantially.

One issue that is not addressed in the paper is that, if you are
given some number, you would like to know what stack configuration(s)
it corresponds to.
To find this out, you will need to carry out some kind of search of
the space of calling-context tags; however, the algorithm to carry this
out is not discussed in the paper.

Another interesting matter is the numbers provided about the expected
number of collisions, for both 32-bit and 64-bit calling-context tags.
They just considered a random model, with $n$ values selected randomly.
The resulting table, which shows the number of collisions for $10^k$ numbers
drawn at random, for varying values of $k$, is as follows:

\begin{center}
  \begin{tabular}{|| l | c | c ||}
    \hline\hline
             & 32 bit & 64 bit \\
    \hline
    $10^3$ & 0 & 0 \\
    $10^4$ & 0 & 0 \\
    $10^5$ & 1 & 0 \\
    $10^6$ & 116 & 0 \\
    $10^7$ & 11K & 0 \\
    $10^8$ & 1.1M & 0 \\
    $10^9$ & 107M (11\%) & 0 \\
    $10^{10}$ & 6B (61\%) & 3 \\
    \hline\hline
  \end{tabular}
\end{center}

One drawback of the method is that for certain kinds of applications,
you can imagine wanting to compare two runs of slightly different
programs.
For this situation, you would like to have a fingerprint that is
resilient to whatever small changes were made to the program.
By using the line number of the call-site in the hash function,
their implementation technique will often destroy the ability to compare
calling-context tags across runs of slightly different programs.
For instance, if there is a one-line change in a file, all call-sites
later in the file will have different line numbers, and thus any
calling context that involves any of those functions will likely not
have the same value of $v$.
Even worse, if you add a one-line comment to \texttt{main}, the value of
$v$ for \emph{all} of the calling contexts in the program are likely to change.
Thus, for applications in which one desires to be able to compare runs
of slightly different programs, one would want to use a hash function
that is more resilient to such changes, such as a hash of the text
of the whole function, minus all comments and whitespace.


\section{Efficient Call-Trace Collection}

We now turn to the paper by Wu et al.\ \cite{POPL:WXCZZ16} about Casper,
a tool for call-trace collection.
In this work, the goal is to recover an entire trace of calls
and returns.
The baseline method is to log information at every call and return
transition, which on average has a runtime overhead of 213\%, and
produces about 50 GB of data per hour.
A simple optimization is to log only the first call in each branch,
because in a piece of non-branching code, once you are on a path, you
will continue on that path (at least if the program does not crash).
This one technique alone reduces the average overhead to 128\%.
The authors claim that Casper has only a 68\% overhead.

\subsection{Overview}

The technique of Wu et al.\ involves a context-free language
that is an over-approximation of the traces of calls and returns
in executions of the program.
(The context-free language is the same as what you have seen
before in interprocedural dataflow analysis, where returns
must match with preceding calls.)
Note that in an execution of the program, the actual calls and
returns that are made are controlled by the concrete execution
state---and thus the branches taken by the program can be correlated;
that is, it might be the case that whenever there is a call to $P$,
the program also calls $Q$, or alternatively, whenever there is a call to
$P$ the program never calls $R$.
Consequently, there can be strings in the context-free language that
could never be traces generated by an execution of the program.

For the moment, just accept that there is an appropriate context-free
language $L$ that we want to use.
We will show that there is an easy way to construct a grammar $G$,
such that:
\begin{enumerate}
  \item
    $L = L(G)$
  \item
    $G$ is LL(1).
\end{enumerate}
In other words, there is way of constructing a description of $L$,
such that all strings of $L$ can be parsed by a predictive parser.

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[height=3.5in]{figures/parse-tree.eps}
\end{tabular}
\caption{A parse tree for an execution trace.
}
\label{Fi:ParseTree}
\end{figure}

One approach, which corresponds to the baseline method, would be
to log calls and returns and parse the resulting string to obtain
a parse tree for the string (with respect to grammar $G$).
\figref{ParseTree} shows this in outline form:
one has an execution trace with a set of symbols (calls and returns);
above that, there is the parse tree for this execution trace.

What Wu et al.\ do is to log only a subset of the calls and returns.
The string that is obtained is thus just a portion of the execution trace.
They instrument a subset of the call and return points, called
the Instrumentation-Points set $I$, and only the calls and returns
in $I$ are logged.
In other words, only elements of $I$ in the input string are ``visible,''
and so instead of string $\sigma$ we have $\sigma$ projected on
$I$ (which we will denote by $\sigma|_I$).

\bigskip
\begin{center}
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[width=6.1in]{figures/execution-trace.eps}
\end{tabular}
\end{center}

\noindent
The question is ``Which symbols should be dropped?''
Wu et al.$~$aim to use a predictive parser on $\sigma|_I$, and
want to arrange things so that the predictive parser builds a
tree that is essentially isomorphic to the parse tree of the full string.
The difference is that at places where there are missing symbols,
the parse tree will not have a child.
However, because this is a predictive parser, and because there
is a known correspondence between the two productions, we can use the
correspondence to restore the parse tree, and therefore,
restore the full string!
This goal is depicted in rather sketchy form in \figref{OverlaidParseTree}.

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[height=3.5in]{figures/overlaid-parse-tree.eps}
\end{tabular}
\caption{A parse tree for string $\sigma$ and the string $\sigma|_I$.
  $\sigma|_I$ is missing the input symbols indicated by dashes, and
  its parse tree is missing the associated subtrees.
  (One such subtree is indicated by the dashed sloping line in the tree.)
}
\label{Fi:OverlaidParseTree}
\end{figure}

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{0ex}}}
  \includegraphics[width=.7\textwidth]{figures/program-example.eps}
\end{tabular}
\caption{An example program \cite{POPL:WXCZZ16}.}
\label{Fi:ProgramExample}
\end{figure}

Let's now take a look at the example they provide in the paper,
which is shown in \figref{ProgramExample}.
Suppose that the actual string is:
\[
  c_1r_2c_2c_5r_4r_3c_2c_5r_4r_3c_3c_7r_4r_6c_4r_7r_1,
\]
but all you are given is:
\[
  c_5c_5c_7
\]
Basically, we can see that from the first $c_5$,
we can infer the first part of the string:
\[
  \color{red}[\color{black}c_1r_2c_2\color{red}\overleftarrow{c_5}]\color{black}r_4r_3c_2c_5r_4r_3c_3c_7r_4r_6c_4r_7r_1
\]
How do we do that?
Starting from \verb|main()|, the only action that one can do is call
\verb|A()|, which generates $c_1$.
\verb|A()| does nothing but return, which generates $r_2$.
Now the program has a choice of either going to $c_2$ or $c_3$.
The authors note that we can recover that the program must have
gone down the then-branch at line 5 to call \verb|B()|.
The call to \verb|B()| would generate $c_2$, and \verb|B()| would make
a call to \verb|C()| at $c_5$.
If the program had called \verb|E()| from \verb|A()| instead,
a $c_3$ would have been generated, and the program would have performed
a call on \verb|E()|, at which point the program would have had to perform
either a return or another call before we got back to \verb|B()|,
where our call site $c_5$ was.
We can continue to apply the same reasoning method, and recover the entire string:
\[
  \color{red}[\color{black}c_1r_2c_2\color{red}\overleftarrow{c_5]}[\color{black}r_4r_3c_2\color{red}\overleftarrow{c_5]}[\color{black}r_4r_3c_3\color{red}\overleftarrow{c_7]}[\color{black}r_4r_6c_4r_7r_1\color{red}\overleftarrow{]}\color{black}
\]
The final segment, $[\color{black}r_4r_6c_4r_7r_1\color{red}\overleftarrow{]}\color{black}$
is recovered using the fact that there was nothing else in string $\sigma|_I$
after $c_7$, plus the information that the program did not crash.

\subsection{Problem Formalization}

In a nutshell, the problem is addressed as follows:
\begin{enumerate}
  \item
    The set of possible traces is modeled (over-approximated) by a context-free language.
  \item
    There is an easy way to give a grammar for the language that is LL(1), and
    hence parsable by a predictive parser.
  \item
    It is possible to devise a way to pick instrumentation sites $I$ such that
    the parse of any ``trimmed-down'' string (i.e., which only has alphabet
    symbols in $I$) is 1-for-1 with the parse of the full string.
    One uses the former to recover the latter, which fills in the symbols
    missing in the trimmed-down string.
\end{enumerate}

The starting point is the LL(1) description of the full language of the
program's call-trace strings.
Wu et al.\ express the grammar as productions of a special form:
each right-hand side consists of a terminal symbol
for some call/return site, followed by other symbols.

At this point we need to introduce some terminology and notation.
From now on, I will use the term ``transfer site'' when I want to
refer to call nodes and exit nodes, but do not need to distinguish
between the two.
Wu et al.\ work with what they call the \emph{call-site control-flow graph}
(CSCFG), which is a version of the control-flow graph that is
projected down to just procedure entry nodes, procedure exit nodes,
and all call sites.
The CSCFG has an edge $m \rightarrow n$ if there is a path in a
procedure's CFG from $m$ to $n$ that does not pass through another call-site.

The next step is to define the appropriate LL(1) context-free grammar.
The grammar consists of the following kinds of elements
\begin{enumerate}
  \item
    $t_i$ : $t_i$ is a terminal symbol that represents a transfer-site
    (call or return) in some function $f$.
  \item
    $\Func_{f_{c_i}}$ : $\Func_{f_{c_i}}$ is a nonterminal whose language is the
    set of call-trace fragments generated for the function $f_{c_i}$
    at call-site $c_i$.
  \item
    $\Succ_{t_i}$ : $\Succ_{t_i}$ is a nonterminal whose language is the
    set of call-trace fragments generated for the path from (i) the successor of
    transfer-site $t_i$ in function $f$ to (ii) the exit of $f$.
\end{enumerate} 

\begin{figure}
\centering
\begin{tabular}{@{\hspace{0ex}}c@{\hspace{3.0ex}}c@{\hspace{0ex}}}
  \includegraphics[height=2.4in]{figures/LL1-production-path.eps}
  & 
  \includegraphics[height=2.8in]{figures/example-grammar.eps}
  \\
  (a) & (b)
\end{tabular}
\caption{(a) A path described by the production $\Func_f \rightarrow c_i ~ \Func_{f_{c_i}} ~ \Succ_{c_i}$.
         (b) The grammar created for the program in \figref{ProgramExample} \cite{POPL:WXCZZ16}.
        }
\label{Fi:LL1ProductionPath}
\end{figure}

It is now possible to define our grammar $G = (V, \Sigma, S, \Productions)$:
\begin{itemize}
  \item $S$ : $\Func_{\textit{main}}$
  \item $\Sigma$ : The program's calls and returns
  \item $V = \{Func_f \mid f~\textrm{is a function in the program} \} \cup \{\Succ_{c_i} \mid c_i~\textrm {is a call site} \} $
\end{itemize}
The grammar has productions for the call-sites that are
upwards-exposed in each procedure, as well as for cases
where there are no calls.\footnote{
  A transfer-site $t$ is \emph{upwards-exposed} in a procedure $P$ when there
  is a path from the entry node of $P$ to $t$ that does not pass through
  another transfer-site.
}
These productions have the following form:
\[
  \begin{array}{@{\hspace{0ex}}r@{\hspace{2.0ex}}c@{\hspace{2.0ex}}l@{\hspace{5.0ex}}l@{\hspace{0ex}}}
    \Func_f & \rightarrow & c_i ~~ \Func_{f_{c_i}} ~~ \Succ_{c_i} & \textrm{($c_i$ is an upwards-exposed call site in $f$)} \\
    \Func_f & \rightarrow & r_j                                & \textrm{($r_j$ is an upwards-exposed return site in $f$)} \\
  \end{array}
\]
To combine both kinds of productions into ones of a single form, we have
\[
  \Func_f ~ \rightarrow ~ t_i ~~ \gamma_{t_i},
\]
where $\gamma_{t_i}$ is $\varepsilon$ for an upwards-exposed return-site.

The grammar has similar rules for the $\Succ_{c_i}$ nonterminals,
which represent the set of call-trace fragments in the procedure
after the call on $c_i$ returns:
\[
  \begin{array}{@{\hspace{0ex}}r@{\hspace{2.0ex}}c@{\hspace{2.0ex}}l@{\hspace{5.0ex}}l@{\hspace{0ex}}}
    \Succ_{c_i} & \rightarrow & c_j ~~ \Func_{f_{c_j}} ~~ \Succ_{c_j} & \textrm{(there is an edge $c_i \rightarrow c_j$ in the CSCFG)} \\
    \Succ_{c_i} & \rightarrow & r_j                                & \textrm{(there is an edge $c_i \rightarrow r_j$ in the CSCFG)}
  \end{array}
\]
or
\[
  \Succ_{c_i} ~ \rightarrow ~ t_i ~~ \gamma_{t_i}.
\]

Our first observation is that this grammar is LL(1).
The two properties of an LL(1) grammar are:
\begin{enumerate}
  \item
    The grammar does not have any left-recursion:
    In our case, all the rules start with a terminal $t_i$, so this
    requirement is fulfilled.
  \item
    Whenever we have two productions $A \rightarrow \alpha$ and $A \rightarrow \beta $:
    \begin{enumerate}
      \item
        $\First(\alpha) \cap \First(\beta) = \emptyset$.
        In our case, for each pair of productions of the form
        $N \rightarrow t_i~\gamma_{t_i}$ and $N \rightarrow t_j~\gamma_{t_j}$,
        $t_i$ and $t_j$ must be different.
      \item
        At most one of $\alpha$ and $\beta$ can derive
        $\varepsilon$.
        In our case, this requirement is vacuously satisfied because
        none of the right-hand sides of the productions
        can derive $\varepsilon$.
      \item
        If $\beta \xrightarrow{*} \varepsilon $, then
        $\First(\alpha) \cap \Follow(A) = \emptyset$.
        This requirement is vacuously satisfied because
        none of the right-hand sides of the productions
        can derive $\varepsilon$.
      \item
        If $\alpha \xrightarrow{*} \varepsilon $, then
        $\First(\beta) \cap \Follow(A) = \emptyset$.
        Again, this requirement is vacuously satisfied.
    \end{enumerate}
\end{enumerate}

We restrict the strings to alphabet symbols in $I$, which
is a subset of the set of transfer-sites.
The way to do that is via a grammar transformation:
each production in $G$ of the form $N \rightarrow t_i \gamma_{t_i}$,
it is transformed as follows:
\[
  \begin{array}{@{\hspace{0ex}}r@{\hspace{1.0ex}}c@{\hspace{1.0ex}}l@{\hspace{5.0ex}}l@{\hspace{0ex}}}
    N' & \rightarrow & t_i \gamma'_{t_i}, & \textrm{if}~t_i \in I \\

    N' & \rightarrow & \gamma'_{t_i},     & \textrm{if}~t_i \notin I
  \end{array}
\]
This transformation maps $G$ to $G'$, where the set of nonterminals
is mapped one-to-one from $N$ to $N'$.
The problem is that $\Productions$ can be mapped many-one to
$\Productions'$, which can cause $G'$ to fail to be LL(1), and hence
one could not use a predictive parser.

However, the only situations in which the mapping is many-one
is when there are two productions of the form
$P_i : N \rightarrow t_i $ and
$P_j : N \rightarrow t_j $, where $t_i, t_j \notin I$.
Thus, the first requirement on us is that we must find a way
to avoid such cases.
The second requirement on us is that we need the grammar $G'$
that one obtains to be parsable by a predictive parser.\footnote{
  I think the paper makes a claim that is not correct.
  They show that the problem of minimizing the size of $I$
  is NP-hard, if you restrict the tool to use an LL(1) grammar.
  They then make the argument that it would be NP-hard if
  the tool were allowed to use an LL(k) grammar. However,
  a parser for an LL(k) grammar has more freedom to look ahead
  than a parser for an LL(1) grammar, and thus a minimal solution
  to the LL(1) version of the problem does not, in general,
  give you a minimal solution to the LL(k) version of the problem.
}

The actual algorithm they propose for finding $G'$ in the
face of the NP-hardness result uses the following idea:
take your program, and for the purposes of finding an
instrumentation scheme, take out all the back-edges created
by recursive calls;
you are then left with something that is an (artificial)
non-recursive program.
They show that even for this restricted situation, the
optimal-instrumentation problem is a hard problem.
The reason is that if you just look at each individual procedure,
you have to solve a vertex-cover problem, which is NP-hard.
However, there is an approximation algorithm for the vertex-cover
problem, and Wu et al.\ use that algorithm as a subroutine in the
construction of an appropriate $G'$.

There are several other issues that they deal with in the paper.
One is dealing with crashes.
If the program crashes, one will not necessarily have an
input string that generates a whole parse tree.
Thus, they lift the definition of a feasible call trace to be a
\emph{prefix} of a string that parses.
In the case of traces created using the reduced instrumentation set $I$,
there is a problem, because even if you can complete the parse tree
up to the places where you have symbols, you won't necessarily be
able to reconstruct the entire tail of the trace beyond the point
of the crash.
To handle this problem, they assume that they have the stack at
the crash point, and they use the stack information to help fill in
the missing portion of the parse tree.

They also need to deal with callbacks; virtual calls; finalize
and static initializers; and exceptions.
There are short paragraphs in the paper describing what they do
for for each of these constructs.

{\small
  \bibliography{cs701}
  \bibliographystyle{plain}
}

\end{document}
