\chapter{Foundations}
\label{chap:foundations}

The aim of flow analysis is to safely approximate an answer the
question:\footnote{See for example the transparencies accompanying
  Chapter 3 ``Control Flow Analysis'' of
  \citet{nielson-nielson-hankin}:
  \url{http://www2.imm.dtu.dk/~riis/PPA/ppa.html}}
\begin{center}\it
For each function application, which functions may be applied?
\end{center}

% \begin{itemize}
% \item To get the most precise answer, simply run the program and find out.

% \item To get the least precise answer, don't do anything: the answer
% is {\em all of them}.  

% \item To get an answer somewhere in between, do something in between.
% \end{itemize}

% This is an intentionally oversimplified view of program analysis, but
% it demonstrates that 
Analysis can easily be understood as the safe approximation to program
evaluation.  It makes sense, then, to first consider evaluation in
detail.  In the following sections, an evaluation function
($\mathcal{E}$) is defined, from which an instrumented variation
($\mathcal{I}$) is derived and abstracted to obtain the abstract
evaluator ($\mathcal{A}$).  Finally, we review basic concepts from
complexity theory and sketch our approach to proving lower bounds.

\section{Structure and Interpretation}

The meaningful phrases of a program are called {\em expressions}, the
process of executing or interpreting these expressions is called {\em
evaluation}, and the result of evaluating an expression is called a
{\em value} \cite{reynolds-acm72}.

We will consider a higher-order applicative programming language based
on the $\lambda$-calculus, in which evaluation is environment based
and functional values are represented using closures.
The syntax of the language is given by the following grammar:
\begin{displaymath}
\begin{array}{l@{\quad}l@{\quad}l}
\Exp & e ::= x\ |\ e\;e\ |\ \lambda x.e & \mbox{expressions}
\end{array}
\end{displaymath}

\begin{quotation}%
% \begin{singlespace}
{\bf Note to the reader:} This may seem a rather minimal programming
language and you may wonder what the broader applicability of these
results are in the face of other language features.  But as noted
earlier, this dissertation is concerned with {\em lower bounds} on
static analysis.  By examining a minimal, core language, all results
will immediately apply to any language which includes this core.  In
other words, the more restricted the subject language, the broader the
applicability.

It may be that the lower bound can be improved in the presence of some
language features, that is, the given set of features may make
analysis provably harder, but it certainly can not make it any
easier.\footnote{This is to be contrasted with, for example, a type
  soundness theorem, where it is just the opposite: adding new language
  feature may revoke soundness.

  For similar remarks, see the discussion in section 2 of
  \citet{reps-ai96} concerning the benefits of formulating an analysis
  problem in ``trimmed-down form,'' which not only leads to a wider
  applicability of the lower bounds, but also ``allows one to gain
  greater insight into exactly what aspects of an
  interprocedural-analysis problem introduce what computational
  limitations on algorithms for these problems.''

  In other words, lower bounds should be derived not by piling feature
  on top of feature, but by removing the weaknesses and restrictions
  that make additional features appear necessary.}
% \end{singlespace}
\end{quotation}

Following \citet{landin-64}, substitution is modelled using {\em
environments}.  Procedures will be represented as {\em closures}, a
$\lambda$-term together with its lexical environment, which closes
over the free variables in the term, binding variables to values.

We use $\rho$ to range over {\em environments} (mappings from
variables to closures), and $v$ to range over {\em closures} (pairs
consisting of a term and an environment that closes the term).  The
empty environment (undefined on all variables) is denoted $\emptyenv$,
and we occasionally abuse syntax and write the closed term $e$ in
place of the closure $\langle e,\emptyenv\rangle$.  Environment extension
is written $\rho[x\mapsto v]$ and we write $\emptyenv[x\mapsto v]$ as
$[x\mapsto v]$ and $[x_1\mapsto v_1]\dots[x_n\mapsto v_n]$ as
$[x_1\mapsto v_1,\dots,x_n\mapsto v_n]$.

\begin{displaymath}
\begin{array}{l@{\quad}l@{\quad}l}
\Env & \rho \in \Var \rightharpoonup \Val & \mbox{environments}\\
\Val & v \in \langle\Exp,\Env\rangle & \mbox{closures}
\end{array}
\end{displaymath}

The meaning of an expression is given in terms of an evaluation
function, or interpreter.  Following \citet[Chapter 4,
``Metalinguistic Abstraction'']{sicp}, an interpreter is defined as
follows:
\begin{quotation}%
An {\em evaluator} (or {\em interpreter}) for a programming language
is a procedure that, when applied to an expression of the language,
performs the actions required to evaluate that expression.
\end{quotation}

\begin{figure}
\begin{displaymath}
\begin{array}{lcl}
\evalf & : & \Exp \times \Env \rightharpoonup \Val\\
\\
\eval{x}\rho & = & \rho(x)\\
\eval{\lambda x.e}\rho & = & \langle\lambda x.e,\rho'\rangle\\
\ &\ & \quad\mbox{where } \rho' = \rho \restrict \fv{\lambda x.e}\\
\eval{e_1 e_2}\rho & = &
       \mbox{\bf let }\langle\lambda x.e_0,\rho'\rangle = 
                      \eval{e_1}\rho \mbox{\bf\ in }\\   %% \rho\restrict\fv{e_1}
\ &\ & \mbox{\bf let }v = 
                      \eval{e_2}\rho \mbox{\bf\ in }\\   %% \rho\restrict\fv{e_2}
\ &\ &   \quad   \eval{e_0}{\rho'[x \mapsto v]}
\end{array}
\end{displaymath}
\caption{Evaluator $\evalf$.}
\label{figure-eval}
\end{figure}


The evaluation function for the language is given in
\autoref{figure-eval}.  We say $e$ evaluates to $v$ under environment
$\rho$ iff $\eval{e}\rho = v$ and computing the evaluation function
defines our notion of the ``running of a program.''  Some examples of
evaluation:
\begin{eqnarray*}
\eval{\lambda x.x}{\emptyenv} & = & \langle \lambda x.x, \emptyenv\rangle\\
\eval{(\lambda x.\lambda z. x) (\lambda y.y)}\emptyenv & = & \langle \lambda z.x,[x\mapsto \langle \lambda y.y,\emptyenv\rangle]\rangle\\
\eval{(\lambda f.ff(\lambda y.y))(\lambda x.x)}\emptyenv & = & \langle\lambda y.y,\emptyenv\rangle
\end{eqnarray*}


This gives us a mostly {\em extensional} view of program
behaviour---evaluation maps programs to values, but offers little
information regarding {\em how} this value was computed.  For the sake
of program optimization it is much more useful to know about
operational (or {\em intensional}) properties of programs.  These
properties are formulated by appealing to an ``instrumented
interpreter,'' which is the subject of the next section.  Intuitively,
the instrumented evaluator works just like the un-instrumented
evaluator, but additionally maintains a complete history of the
operations carried out during the running of the program.

%% We choose an evaluation strategy of call-by-value.\footnote{These
%% choices may seem arbitrary, but the evaluator is in fact they an
%% artifact derived from the program analyses we chose to investigate.}

%% \dvhnote{Discuss {\em extensional} aspect and the extensional property
%% of evaluation.  See chapter 6 of \cite{sampath-phd} for a good
%% discussion.  Compare this later to intensional aspects: the
%% intrumented interpreter, exact cfa, and finally, its approximation.}

\section{Instrumented Interpretation}
\label{section-instrumented}

Instrumented (or concrete) interpretation carries out the running of
program while maintaining a trace of the operations performed, thus
providing an operational history of evaluation.  A suitable
reformulation of the original definition of an evaluator to
incorporate instrumentation is then:

\begin{quotation}%
An {\em instrumented evaluator} (or {\em instrumented interpreter})
for a programming language is a procedure that, when applied to an
expression of the language, performs {\em and records} the actions
required to evaluate that expression.
\end{quotation}

Exactly which actions should be record will vary the domain of any
given static analysis and there is no universal notion of a program
trace, but for flow analysis, the interesting actions are:

\begin{itemize}
\item Every time the value of a subexpression is computed, record its
value and the context in which it was evaluated.

\item Every time a variable is bound, record the value and context in
which it was bound.
\end{itemize}

These actions are recorded in a {\em cache}, and there is one for each
kind of action:
\begin{eqnarray*}
\ecache & : & \Lab \times \Delta \rightharpoonup \Val \\
\eenv   & : & \Var \times \Delta \rightharpoonup \Val\\
\Cache & = & (\Lab \times \Delta \rightharpoonup \Val) \times
             (\Var \times \Delta \rightharpoonup \Val)\\
\end{eqnarray*}
The $\ecache$ cache records the result, or returned value, of each
subcomputation, and the $\eenv$ cache records each binding of a
variable to a value.  Given the label of a subexpression ($\Lab$) and
a description of the context ($\Delta$), $\ecache$ returns the value
produced by that subexpression evaluated in that context.  Given the
name of a variable ($\Var$) and a description of the context
($\Delta$), $\eenv$ returns the value bound to that variable in that
context.  The $\ecache$ cache is a partial function since a
subexpression 1) may not produce a value, it may diverge, or 2) may
not be evaluated in the given context.  The $\eenv$ cache is partial
for analogous reasons.

The set $\Lab$ is used to index subexpressions.  It can easily be
thought of as the implicit source location of the expression, but our
formalism will use an explicit labelling scheme.  We use $\ell$ to
range over labels.  The syntax of the source language is given by the
following grammar, and programs are assumed to be uniquely labelled:
\begin{displaymath}
\begin{array}{l@{\quad}l@{\quad}l}
\Exp & e ::= t^\ell & \mbox{expressions (or labeled terms)}\\
\Term & t ::= x\ |\ (e\;e)\ |\ (\lambda x.e) & \mbox{terms (or unlabeled expressions)}
\end{array}
\end{displaymath}

Irrelevant labels are frequently omitted for presentation purposes.

The set $\Delta$ consists of {\em contours}, which are strings of
labels from application nodes in the abstract syntax of the program.
A string of application labels describes the context under which the
term evaluated.

A variable may be bound to any number of values during the course of
evaluation.  Likewise, a subexpression that occurs once syntactically
may evaluate to any number of values during evaluation.  So asking
about the flows of a subexpression is ambiguous without further
information.  Consider the following example, where \True\ and \False\
are closed, syntactic values:
\begin{displaymath}
(\lambda f.f(f\; \True)) (\lambda y. \False)
\end{displaymath}
During evaluation, $y$ gets bound to both \True\ and \False---asking
``what was $y$ bound to?'' is ambiguous.  But let us label the
applications in our term:
\begin{displaymath}
((\lambda f.(f(f\; \True)^1)^2) (\lambda y. \False))^3
\end{displaymath}
Notice that $y$ is bound to different values within different
contexts.  That is, $y$ is bound to \True\ when evaluating the
application labeled 1, and $y$ is bound to \False\ when evaluating the
application labeled 2.  Both of these occur while evaluating the
outermost application, labeled 3.  A string of these application
labels, called a {\em contour}, uniquely describes the {\em context}
under which a subexpression evaluates.  Adopting the common convention
of writing a context as an expression with a hole ``$[\;]$'' in it
\cite{pllc}, the following contours describe the given contexts:
\begin{eqnarray*}
3 2 1 & \mbox{describes} & ((\lambda f.(f [\;]^1)^2) (\lambda y.\False))^3\\
3 2 & \mbox{describes} & ((\lambda f.[\;]^2) (\lambda y.\False))^3
\end{eqnarray*}
So a question about what a subexpression evaluates to {\em within a
  given context} has an unambiguous answer.  The interpreter,
therefore, maintains an environment that maps each variable to a
description of the context in which it was bound.  Similarly, flow
questions about a particular subexpression or variable binding must be
accompanied by a description of a context.  Returning to the example,
the binding cache would give $\eenv(y,321) = \True$ and $\eenv(y,32) =
\False$.

The set $\Val$ consists of closures, however the environment component
of the closures are non-standard.  Rather than mapping variables to
values, these environments map variables to contours; the contour
describes the context in which the variable was bound, so the value
may be retrieved from the $\eenv$ cache.  In other words, these
environments include an added layer of indirection through the cache:
variables are mapped not to their values but the location of their
definition site, where the value can be found.

So we have the following data definitions:
\begin{displaymath}
\begin{array}{cclcll}
\delta & \in & \Delta & = & \Lab^{\star}  & \mbox{contours}\\
     v & \in & \Val & = & \Term \times \Env & \mbox{(contour) values}\\
  \rho & \in & \Env & = & \Var \rightharpoonup \Delta & \mbox{(contour) environments}
\end{array}
\end{displaymath}
Note that this notation overloads the meaning of $\Val$, $\Exp$, and
$\Env$ with that given in the previous section.  It should be clear
from setting which is meant, and when both meanings need to be used in
the same context, the latter will be refered to as {\em contour
values} and {\em contour environments}.

The cache is computed by the instrumented interpreter, $\mathcal{I}$,
the instrumented, intentional analog of $\mathcal{E}$.  It can be
concisely and intuitively written as an imperative program that
mutates an initially empty cache, as given in
\autoref{fig-i-imperative}.

\begin{figure}[h]
\begin{displaymath}
\begin{array}{lcl}
\ivf & : & \Exp \times \Env \times \Delta \rightharpoonup \Unit\\
\\
\iv{x^\ell}^\rho_\delta & = & \ecache(\ell,\delta) \leftarrow \eenv(x,\rho(x))\\
\iv{(\lambda x.e)^\ell}^\rho_\delta & = & \ecache(\ell,\delta) \leftarrow \langle\lambda x.e,\rho'\rangle\\
\ &\ & \quad\mbox{where } \rho' = \rho \restrict \fv{\lambda x.e}\\
\iv{(t^{\ell_1}t^{\ell_2})^\ell}^\rho_\delta & = & \iv{t^{\ell_1}}^{\rho}_\delta;\ \iv{t^{\ell_2}}^{\rho}_\delta;\\
\ &\ & \mbox{let }\langle\lambda x.t^{\ell_0},\ce'\rangle = \ecache(\ell_1,\delta)\mbox{ in}\\
\ & \ & \quad \eenv(x,\delta\ell) \leftarrow \ecache(\ell_2,\delta);\\
\ & \ & \quad \iv{t^{\ell_0}}_{\delta\ell}^{\rho'[x\mapsto \delta\ell]};\\
\ & \ & \quad \ecache(\ell,\delta) \leftarrow \ecache(\ell_0,\delta\ell)
\end{array}
\end{displaymath}
\caption{Instrumented evaluator $\mathcal{I}$, imperative style.}
\label{fig-i-imperative}
\end{figure}

$\iv{t^\ell}^\ce_\delta$ evaluates $t$ and writes the result of
evaluation into the $\ecache$ cache at location $(\ell,\delta)$.  The
notation $\ecache(\ell,\delta) \leftarrow v$ means that the cache is
mutated so that $\ecache(\ell,\delta) = v$, and similarly for
$\eenv(x,\delta) \leftarrow v$.  The type $\Unit$ is used here to
emphasize the imperative nature of the instrumented evaluator; no
meaningful return value is produced, the evaluator is run only for
effect on the caches.  The notation $\delta\ell$ denotes the
concatenation of contour $\delta$ and label $\ell$.  The symbol
$\epsilon$ denotes the empty contour.

We interpret $\ecache(\ell,\delta) = v$ as saying, ``the expression
labeled $\ell$ {\em evaluates to} $v$ in the context described by
$\delta$,'' and $\eenv(x,\delta) = v$ as ``the variable $x$ is {\em
  bound to} $v$ in the context described by $\delta$.''  
%
Conversely, we say ``$v$ {\em flows to} the expression labelled $\ell$
into the context described by $\delta$,'' and ``$v$ {\em flows to} the
binding of $x$ in the context described by $\delta$,'' respectively.
%
We refer to a fact such as, $\ecache(\ell,\delta) = v$ or
$\eenv(x,\delta) = v $, as a {\em flow}.
%
The instrumented interpreter works by accumulating a set of flows as
they occur during evaluation.

Notice that this evaluator does not return values---it writes them
into the cache: if the expression $t^\ell$ evaluates in the contour
$\delta$ to $v$, then $\ecache(\ell,\delta)$ is assigned $v$.  When
the value of a subexpression is needed, as in the application case,
the subexpression is first interpreted (causing its value to be
written in the cache) and subsequently retrieved from the $\ecache$
cache.  When the value of a variable is needed, it is retrieved from
the $\eenv$ cache, using the contour environment to get the
appropriate binding.  

In other words, $\ecache$ is playing the role of a global return
mechanism, while $\eenv$ is playing the role of a global environment.

Although the evaluator is mutating the cache, each location is written
into just once.  A straight-forward induction proof shows that the
current label together with the current contour---which constitute the
cache address that will be written into---forms a unique string.

Returning to the earlier example, the cache constructed by
\begin{displaymath}
\iv{((\lambda f.(f(f\; \True)^1)^2) (\lambda y. \False))^3}_\epsilon^\emptyenv
\end{displaymath}
includes the following entries:
\begin{eqnarray*}
\eenv(f,3) & = & \lambda y.\False\\
\eenv(y,321) & = & \True\\
\eenv(y,32) & = & \False\\
\ecache(1,32) & = & \lambda y.\False\\
%% \ecache(2,3) & = & \False\\
\ecache(3,\epsilon) & = & \False
\end{eqnarray*}


The evaluator can be written in a functional style by threading the
cache through the computation as seen in \autoref{fig-i-functional}.
\begin{figure}[h]
\begin{displaymath}
\begin{array}{lcl}
\ivf & : & \Exp \times \Env \times \Delta \times \Cache \rightharpoonup \Cache\\
\\
%%%%%%%%%%%%%%%%%%
%% Var
\iv{x^\ell}^\rho_\delta\;\ecache,\eenv & = & 
\ecache[(\ell,\delta) \mapsto \eenv(x,\ce(x))], \eenv\\
%%%%%%%%%%%%%%%%%%
%% Lam
\iv{(\lambda x.e)^\ell}^\rho_\delta\;\ecache,\eenv & = & 
\ecache[(\ell,\delta) \mapsto \langle\lambda x.e,\ce'\rangle], \eenv\\
\ &\ & \quad\mbox{where } \rho' = \rho \restrict \fv{\lambda x.e}\\
%%%%%%%%%%%%%%%%%%
%% App
\iv{(t^{\ell_1}t^{\ell_2})^\ell}^\rho_\delta\;\ecache,\eenv & = & 
\mbox{let }\ecache_1,\eenv_1 = \iv{t^{\ell_1}}_{\ce}^\delta\;\ecache,\eenv\mbox{ in}\\
\ & \ & \mbox{let }\ecache_2,\eenv_2 = \iv{t^{\ell_2}}_{\ce}^\delta\;\ecache_1,\eenv_1\mbox{ in}\\
\ &\ & \mbox{let }\langle\lambda x.t^{\ell_0},\ce'\rangle = \ecache_2(\ell_1,\delta)\mbox{ in}\\
\ &\ & \mbox{let }\eenv_3 = \eenv_2[(x,\delta\ell) \mapsto \ecache_3(\ell_2,\delta)]\mbox{ in}\\
\ &\ & \mbox{let }\ecache_3,\eenv_4 = \iv{t^{\ell_0}}_{\ce'[x\mapsto \delta\ell]}^{\delta\ell}\;\ecache_2,\eenv_3\mbox{ in}\\
\ &\ & \mbox{let }\ecache_4 = \ecache_3[(\ell,\delta) \mapsto \ecache_3(\ell_0,\delta\ell)]\mbox{ in}\\
\ &\ & \quad \ecache_4,\eenv_4
\end{array}
\end{displaymath}
\caption{Instrumented evaluator $\mathcal{I}$, functional style.}
\label{fig-i-functional}
\end{figure}

In a more declarative style, we can write a specification of {\em
  acceptable caches}; a cache is acceptable iff it records at least
all of the flows which occur during instrumented evaluation.  The
smallest cache satisfying this acceptability relation is the one that
is computed by the above interpreter, clearly.  The acceptability
relation is given in \autoref{fig-i-declarative}.  It is same cache
acceptability relation can be obtained from that given by \citet[Table
3.10, page 192]{nielson-nielson-hankin} for $k$CFA by letting $k$ be
arbitrarily large.  (Looking ahead, the idea of $k$CFA is that the
evaluator will begin to lose information and approximate evaluation
after a contour has reached a length of $k$.  If $k$ is sufficiently
large, approximation never occurs.  So the acceptability relation of
\autoref{fig-i-declarative} can also be seen as the specification of
``$\infty$CFA''.  For any program that terminates, there is a $k$ such
that performing $k$CFA results in a cache meeting the specification of
\autoref{fig-i-declarative}.  In other words, for any program that
halts, there is a $k$ such that $k$CFA runs it.)
\begin{figure}[h]
\begin{displaymath}
\begin{array}{lcl}
%%%%%%%%%%%%%%%%%%
%% Var
\ecache,\eenv \kmodels{\ce}{\delta} x^\ell & 
\mbox{ iff } & 
\ecache(\ell,\delta) = \eenv(x,\ce(x)) \\
%%%%%%%%%%%%%%%%%%
%% Lam
\ecache,\eenv \kmodels{\ce}{\delta}  (\lambda x .e)^\ell & 
\mbox{ iff } & 
\ecache(\ell,\delta) = \langle\lambda x .e,\ce'\rangle\\
\ & \ & \quad  \mbox{where } \ce' = ce \restrict \fv{\lambda x.e}\\
%%%%%%%%%%%%%%%%%%
%% App
\ecache,\eenv \kmodels{\ce}{\delta} (t^{\ell_1}\ t^{\ell_2})^\ell & 
\mbox{ iff } & 
\ecache\kmodels{\ce}{\delta} t^{\ell_1} \wedge
\ecache\kmodels{\ce}{\delta} t^{\ell_2} \;\wedge \\
\ & \ & \mbox{let }\langle\lambda x.t^{\ell_0},\ce'\rangle = \ecache(\ell_1,\delta) \mbox{ in}\\
\ & \ & \quad\eenv(x,\delta\ell) = \ecache(\ell_2,\delta) \;\wedge \\
\ & \ & \quad\ecache,\eenv\kmodels{\ce'[x\mapsto\delta\ell]}{\delta\ell} t^{\ell_0}\; \wedge  \\
\ & \ & \quad\ecache(\ell,\delta) = \ecache(\ell_0,\delta\ell)
\end{array}
\end{displaymath}
\caption{Exact cache acceptability, or Instrumented evaluator $\mathcal{I}$, declarative style.}
\label{fig-i-declarative}
\end{figure}

There may be a multitude of acceptable analyses for a given program,
so caches are partially ordered by:

\begin{displaymath}
\begin{array}{lllcl}
\ecache & \sqsubseteq & \ecache' & 
\mbox{ iff } & 
\forall \ell,\delta : \ecache(\ell,\delta) = v\Rightarrow \ecache'(\ell,\delta) = v\\
%%
\eenv & \sqsubseteq & \eenv' & 
\mbox{ iff } & 
\forall x,\delta : \eenv(x,\delta) = v\Rightarrow \eenv'(x,\delta) = v
\end{array}
\end{displaymath}

Generally, we are concerned only with the {\em least} such caches with
respect to the domain of variables and labels found in the given
program of interest.


Clearly, because constructing such a cache is equivalent to evaluating
a program, it is not effectively computable.

All of the flow analyses considered in this dissertation can be
thought of as an abstraction (in the sense of being a {\em computable
  approximation}) to this instrumented interpreter, which not only
evaluates a program, but records a history of {\em flows}.

\section{Abstracted Interpretation}
\label{sec:ai}

Computing a complete program trace can produce an arbitrarily large
cache.  One way to regain decidability is to bound the size of the
cache.  This is achieved in $k$CFA by bounding the length of contours
to $k$ labels.

If during the course of evaluation, or more appropriately {\em
  analysis}, the contour is extended to exceed $k$ labels, the
analyzer will truncate the string, keeping the $k$ most recent labels.

But now that the cache size is bounded, a sufficiently large
computation will exhaust the cache.  Due to truncation, the uniqueness
of cache locations no longer holds and there will come a point when a
result needs to be written into a location that is already occupied
with a different value.  If the analyzer were to simply overwrite the
value already in that location, the analysis would be unsound.
Instead the analyzer must consider {\em both} values as flowing out of
this point.

This in turn can lead to further approximation.  Suppose a function
application has two values given for flow analysis of the operator
subterm and another two values given for the operand.  The analyzer
must consider the application of each function to each argument.





$k$CFA is a safe, computable approximation to this instrumented
interpreter; it is a kind of abstract interpretation
\cite{cousot-cousot-popl77,jones-nielson,nielson-nielson-hankin}.
Rather than constructing an {\em exact} cache $\ecache,\eenv$, it
constructs an {\em abstract} cache $\cache,\aenv$:
\begin{displaymath}
\begin{array}{cclcll}
\cache & : & \Lab \times \Delta \rightarrow \widehat{\Val} \\
\aenv & : & \Var \times \Delta \rightarrow \widehat{\Val}\\
\\
\ACache & = & (\Lab \times \Delta \rightarrow \widehat{\Val}) \times
              (\Var \times \Delta \rightarrow \widehat{\Val})
\end{array}
\end{displaymath}
which maps labels and variables, not to values, but to sets of
values---{\em abstract values}:
\begin{displaymath}
\begin{array}{cclcll}
\hat{v}      & \in & \widehat{\Val}   & = & \mathcal{P}(\Term \times \Env) & \mbox{abstract values}.
\end{array}
\end{displaymath}

Approximation arises from contours being bounded at length $k$.  If
during the course of instrumented evaluation, the length of the
contour would exceed length $k$, then the $k$CFA abstract interpreter
will truncate it to length $k$.  In other words, only a partial
description of the context can be given, which results in ambiguity.
A subexpression may evaluate to two distinct values, but within
contexts which are only distinguished by $k+1$ labels.  Questions
about which value the subexpression evaluates to can only supply $k$
labels, so the answer must be {\em both}, according to a sound
approximation.

When applying a function, there is now a set of possible closures that
flow into the operator position.  Likewise, there can be a
multiplicity of arguments.  What is the interpreter to do?  The
abstract interpreter must apply all possible closures to all possible
arguments.

The abstract interpreter $\mathcal{A}$, the imprecise analog of
$\mathcal{I}$, is given in \autoref{fig-a-imperative} using the
concise imperative style.
\begin{figure}[h]
\begin{displaymath}
\begin{array}{lcl}
\avf_k & : & \Exp \times \Env \times \Delta \rightarrow \Unit\\ 
\\
\avk{x^\ell}{\ce}{\delta} & = & \cache(\ell,\delta) \leftarrow \aenv(x,\ce(x))\\
\avk{(\lambda x.e)^\ell}{\ce}{\delta} & = & \cache(\ell,\delta) \leftarrow \{\langle\lambda x.e,\ce'\rangle\}\\
\ &\ & \quad\mbox{where } \ce' = \ce \restrict \fv{\lambda x.e}\\
\avk{(t^{\ell_1} t^{\ell_2})^\ell}{\ce}{\delta} & = & \avk{t^{\ell_1}}{\ce}{\delta}; \avk{t^{\ell_2}}{\ce}{\delta};\\
\ &\ &\mbox{\bf for each }\langle\lambda x.t^{\ell_0},\ce'\rangle \mbox{\bf\ in\ } \cache(\ell_1,\delta) \mbox{\bf\ do}\\
\ &\ & \quad\aenv(x,\lceil\delta\ell\rceil_k) \leftarrow \cache(\ell_2,\delta);\\
\ &\ & \quad\avk{t^{\ell_0}}{\ce'[x\mapsto\lceil\delta\ell\rceil_k]}{\lceil\delta\ell\rceil_k};\\
\ &\ & \quad\cache(\ell,\delta)\leftarrow\cache(\ell_0,\lceil\delta\ell\rceil_k)
\end{array}
\end{displaymath}
\caption{Abstract evaluator $\avf$, imperative style.}
\label{fig-a-imperative}
\end{figure}
We write $\cache(\ell,\delta) \leftarrow \hat{v}$ (or $\aenv(x,\delta)
\leftarrow \hat{v}$) to indicate an updated cache where $\ell,\delta$
(resp., $x,\delta$) maps to $\cache(\ell,\delta) \cup \hat{v}$ (resp.,
$\aenv(\ell,\delta) \cup \hat{v}$).  The notation
$\lceil\delta\rceil_k$ denotes $\delta$ truncated to the rightmost
(i.e., most recent) $k$ labels.

There are many ways the concise imperative abstract evaluator can be
written in a more verbose functional style, and this style will be
useful for proofs in the following sections. 
% Here we write \dots to mean \dots

\begin{figure}[h]
\begin{displaymath}
\begin{array}{lcl}
\avf_k & : & \Exp \times \Env \times \Delta \times \ACache\rightarrow \ACache\\ 
\\
%%%%%%%%%%%%%%%%%%
%% Var
\avk{x^\ell}{\ce}{\delta}\;\cache,\aenv & = & 
\cache[(\ell,\delta) \mapsto \aenv(x,\ce(x))], \aenv\\
%%%%%%%%%%%%%%%%%%
%% Lam
\avk{(\lambda x.e)^\ell}{\ce}{\delta}\;\cache,\aenv & = & 
\cache[(\ell,\delta) \mapsto \{\langle\lambda x.e,\ce'\rangle\}], \aenv\\
\ &\ & \quad\mbox{where } \ce' = \ce \restrict \fv{\lambda x.e}\\
%%%%%%%%%%%%%%%%%%
%% App
\avk{(t^{\ell_1}\ t^{\ell_2})^\ell}{\ce}{\delta}\;\cache,\aenv & = &
\cache_3[(\ell,\delta)\mapsto \cache_3(\ell_0,\delta')],\aenv_3,\mbox{ where}\\
\ &\ &\begin{array}{lcl}
  \delta' & = & \lceil\delta\ell\rceil_k\\
  \cache_1,\aenv_1 & = & \avk{t^{\ell_1}}\ce\delta\;\cache,\aenv\\
  \cache_2,\aenv_2 & = & \avk{t^{\ell_2}}\ce\delta\;\cache_1,\aenv_1\\
  \cache_3,\aenv_3 & = & \\
\multicolumn{3}{c}{
\bigsqcup\nolimits_{\langle\lambda x.t^{\ell_0},\ce'\rangle}^{\cache_2(\ell, \delta)}
\left(\avk{t^{\ell_0}}{\ce'[x\mapsto\delta']}{\delta'}\;\cache_2,\aenv_2[(x,\delta')\mapsto \cache_2(\ell_2,\delta)]\right)}
\end{array}
\end{array}
\end{displaymath}
\caption{Abstract evaluator $\avf$, functional style.}
\label{fig-a-functional}
\end{figure}

Compared to the exact evaluator, contours similarly distinguish
evaluation within contexts described by as many as $k$ application
sites: beyond this, the distinction is blurred.  The imprecision of
the analysis requires that $\avf$ be iterated until the cache reaches
a fixed point, but care must taken to avoid looping in an iteration
since a single iteration of $\avk{e}{\ce}{\delta}$ may in turn make a
recursive call to $\avk{e}{\ce}{\delta}$ under the same contour and
environment.  This care is the algorithmic analog of appealing to the
co-inductive hypothesis in judging an analysis acceptable (described
below).


We interpret $\cache(\ell,\delta) = \{v,\dots\}$ as saying, ``the
expression labeled $\ell$ {\em may evaluate to} $v$ in the context
described by $\delta$,'' and $\eenv(x,\delta) = v$ as ``the variable
$x$ is {\em may be bound to} $v$ in the context described by
$\delta$.''
%
Conversely, we say ``$v$ {\em flows to} the expression labelled $\ell$
into the context described by $\delta$'' and each $\{v,\dots\}$ ``{\em
  flows out} of the expression labelled $\ell$ in the context
described by $\delta$,'' and ``$v$ {\em flows to} the binding of $x$
in the context described by $\delta$,'' respectively.
%
We refer to a fact such as, $\cache(\ell,\delta) \ni v$ or
$\aenv(x,\delta) \ni v $, as a {\em flow}.
%
The abstract interpreter works by accumulating a set of flows as they
occur during abstract interpretation until reaching a fixed point.
%
Although this overloads the terminology used in describing the
instrumented interpreter, the notions are compatible and the setting
should make it clear which sense is intended.


An acceptable $k$-level control flow analysis for an expression $e$ is
written $\cache,\aenv\kmodels{\ce}{\delta} e$, which states that
$\cache,\aenv$ is an acceptable analysis of $e$ in the context of the
current environment $\ce$ and current contour $\delta$ (for the top
level analysis of a program, these will both be empty).

Just as done in the previous section, we can write a specification of
acceptable caches rather than an algorithm that computes.  The
resulting specification given in \autoref{fig-kcfa-declarative} is
what is found, for example, in \citet{nielson-nielson-hankin}.

\begin{figure}[h]
\begin{displaymath}
\begin{array}{lcl}
\cache,\aenv \kmodels{\ce}{\delta} x^\ell 
& \mbox{ iff } 
& \aenv(x,\ce(x)) \subseteq \cache(\ell,\delta)\\
%%
\cache,\aenv \kmodels{\ce}{\delta} (\lambda x .e)^\ell 
& \mbox{ iff } 
& \langle\lambda x .e,\ce'\rangle \in \cache(\ell,\delta)\\
\ & \ & \quad  \mbox{where } \ce' = ce \restrict \fv{\lambda x.e}\\
%%
\cache,\aenv \kmodels{\ce}{\delta} (t^{\ell_1}\ t^{\ell_2})^\ell &
\mbox{ iff } &
\cache,\aenv\kmodels{\ce}{\delta} t^{\ell_1} \wedge
\cache,\aenv\kmodels{\ce}{\delta} t^{\ell_2} \wedge \\
\ &\ & \quad\forall \langle\lambda x.t^{\ell_0},\ce'\rangle \in \cache(\ell_1,\delta) : \\
\ &\ & \qquad\cache(\ell_2,\delta) \subseteq \aenv(x,\lceil\delta\ell\rceil_k) \wedge\\
\ &\ & \qquad\cache,\aenv\kmodels{ce'[x \mapsto \lceil\delta\ell\rceil_k]}{\lceil\delta\ell\rceil_k} t^{\ell_0} \wedge  \\
\ &\ & \qquad\cache(\ell_0,\lceil\delta\ell\rceil_k) \subseteq \cache(\ell,\delta)
\end{array}
\end{displaymath}
\caption{Abstract cache acceptability, or Abstract evaluator $\mathcal{A}$, declarative style.}
\label{fig-kcfa-declarative}
\end{figure}

There may be a multitude of acceptable analyses for a given program,
so caches are partially ordered by:

\begin{displaymath}
\begin{array}{lllcl}
\cache & \sqsubseteq & \cache' & 
\mbox{ iff } & 
\forall \ell,\delta : \cache(\ell,\delta) = \hat{v}\Rightarrow \hat{v} \subseteq \cache'(\ell,\delta) \\
%%
\aenv & \sqsubseteq & \aenv' & 
\mbox{ iff } & 
\forall x,\delta : \aenv(x,\delta) = \hat{v}\Rightarrow \hat{v}\subseteq \aenv'(x,\delta)
\end{array}
\end{displaymath}

Generally, we are concerned only with the {\em least} such caches with
respect to the domain of variables and labels found in the given
program of interest.



By bounding the contour length, the inductive proof that
$(\ell,\delta)$ was unique for any write into the cache was
invalidated.  Similarly, induction can no longer be relied upon for
verification of acceptability.  It may be the case that proving
$\cache,\aenv\models_\delta^\rho t^\ell$ obligates proofs of other
propositions, which in turn rely upon verification of
$\cache,\aenv\models_\delta^\rho t^\ell$.  Thus the acceptability
relation is defined {\em co-inductively}, given by the greatest fixed
point of the functional defined according to the following clauses of
\autoref{fig-kcfa-declarative}.  Proofs by co-induction would allow
this later obligation to be dismissed by the {\em co-inductive
  hypothesis}.



\paragraph{Fine print:} 
To be precise, we take as our starting point {\em uniform} $k$CFA
rather than a $k$CFA in which,
\begin{eqnarray*}
\ACache & = & (\Lab \times \Env \rightarrow \widehat{\Val}) \times
              (\Var \times \Env \rightarrow \widehat{\Val})
\end{eqnarray*}
The differences are immaterial for our purposes.  See
\citet{nielson-nielson-hankin} for details and a discussion on the use
of coinduction in specifying static analyses.

Having established the foundations of evaluation and analysis, we now
turn to the foundations of our tools and techniques employed in the
investigation of program analysis.

\section{Computational Complexity}


\subsection{Why a Complexity Investigation?}

Static analysis can be understood as a ``technique for computing
conservative approximations of solution for undecidable
problems.''\footnote{Quoted from Michael Schwartzbach's 2009 Static
  Analysis course description from University of Aarhus
  (\url{http://www.brics.dk/~mis/static.html/}, accessed June 3,
  2009).  The same sentiment is expressed in Patrick Cousot's 2005 MIT
  Abstract Interpretation lecture notes on undecidability, complexity,
  automatic abstract termination proofs by semidefinite programming
  (\url{http://web.mit.edu/16.399/www/}).}  Complexity
characterizations therefore offer a natural refinement of that
understanding.

A fundamental question we need to be able to answer is this: what can
be deduced about a long-running program with a time-bounded analyzer?
When we statically analyze exponential-time programs with a
polynomial-time method, there should be a analytic bound on what we
can learn at compile-time: a theorem delineating how exponential time
is being viewed through the compressed, myopic lens of polynomial time
computation.

We are motivated as well by yardsticks such as Shannon's theorem from
information theory \cite{shannon}: specify a bandwidth for
communication and an error rate, and Shannon's results give bounds on
the channel capacity.  We too have essential measures: the time
complexity of our analysis, the asymptotic differential between that
bound and the time bound of the program we are analyzing.  There ought
to be a fundamental result about what information can be yielded as a
function of that differential.  At one end, if the program and
analyzer take the same time, the analyzer can just run the program to
find out everything.  At the other end, if the analyzer does no work
(or a constant amount of work), nothing can be learned.  Analytically
speaking, what is in between?

In the closely related area of pointer analysis, computational
complexity has played a prominent role in the development and
evaluation of analyses.\footnote{See \autoref{sec-pointer-analysis}
  for details.}  It was the starting point for the widely influential
\citet{landi-ryder-pldi92}, according to the authors' retrospective
\citeyearpar{landi-ryder-pldi92retro}.

The theory of computational complexity has proved to be a fruitful
tool in relating seemingly disparate computational problems.  Through
notions of logspace reductions\footnote{Logspace reductions are
  essentially memory efficient translations between instances of
  problems.  The PL-theorist may be most comfortable thinking of these
  reductions as space-efficient compilers.} between problems, what may
have appeared to be two totally unrelated problems can be shown to be,
in fact, so closely related that a method for efficiently computing
solutions to one can be used as a method for efficiently computing the
other, and {\em vice versa}.  For example, at first glance, 0CFA and
circuit evaluation have little to do with each other.  Yet, as shown
in this dissertation, the two problems are intimately related; they
are both complete for \ptime.

There are two fundamental relations a problem can have to a complexity
class.  The problem can be {\em included} in the complexity class,
meaning the problem is no harder than the hardest problems in the
class.  Or, the problem can be a {\em hard} problem within the class,
meaning that no other problem in the class is harder than this one.
When a problem is both included and hard for a given class, it said to
be {\em complete} for that class; it is as hard as the hardest
problems in the class, but no harder.

Inclusion results establish feasibility of analysis---it tells us
analysis can be performed within some upper-bound on resources.  These
results are proven by constructing efficient and correct program
analyzers, either by solving the analysis problem directly or reducing
it to another problem with a known inclusion.

Hardness results, on the other hand, establish the minimum resource
requirements to perform analysis in general.  They can be viewed as
lower-bounds on the difficulty of analysis, telling us, for example,
when no amount of cleverness in algorithm design will improve on the
efficiency of performing analysis.  So while inclusion results have an
existential form: ``there exists an algorithm such that it operates
within these bounded resources,'' hardness results have a universal
form: ``for all algorithms computing the analysis, at least these
resources can be consumed.''

Whereas inclusion results require clever algorithmic insights applied
to a program analyzer, hardness results require clever exploitation of
the analysis to perform computational work.  

Lower bounds are proved by giving reductions---efficient
compilers---for transforming instances of some problem that is known
to be hard for a class, (e.g. circuit evaluation and \ptime) into
instances of a flow analysis problem.
%
Such a reduction to a flow analysis problem establishes a {\em lower
  bound} on the complexity of flow analysis: solving the flow problem
must be {\em at least as hard} as solving the original problem, which
is known to be of the hardest in the class.

The aim, then, is to solve hard problems by make principled use of the
analysis.  From a programming language perspective, the analyzer can
be regarded as an evaluator of a language, albeit a language with
implicit resource bounds and a (sometimes) non-standard computational
model.  Lower bounds can be proved by writing computationally
intensive programs in this language.  That is, proving lower bounds is
an exercise in clever hacking in the language of analysis.

For flow analysis, inclusion results are largely known.  Simple
algorithmic analysis applied to the program analyzer is often
sufficient to establish an upper bound.

Much work in the literature on flow analysis has been concerned with
finding more and more efficient ways to compute various program
analyses.  But while great effort has been expended in this direction,
there is little work addressing the fundamental limits of this
endeavour.  Lower-bounds tell us to what extent this is possible.

This investigation also provides insight into a more general subject:
the complexity of computing via abstract interpretation.  It stands to
reason that as the computational domain becomes more refined, so too
should computational complexity.  In this instance, the domain is the
size of the abstract cache $\cache$ and the values (namely, {\em
  closures}) that can be stored in the cache.  As the cache size and
number of closures increase\footnote{Observe that since closure
  environments map free variables to contours, the number of closures
  increases when the contour length $k$ is increased.}, so too should
the complexity of computation.  From a theoretical perspective, we
would like to understand better the trade-offs between these various
parameters.

Viewed from another perspective, hardness results can be seen as a
characterization of the {\em expressiveness} of an analysis; it is a
measure of the work an analysis is capable of doing.  The complexity
and expressivity of an analysis are two sides of the same coin and
analyses can be compared and characterized by the class of
computations each captures.  In their definitive study,
\citet{nielson-nielson-hankin} remark, ``Program analysis is still far
from being able to precisely relate ingredients of different
approaches to one another,'' but we can use computational complexity
theory as an effective tool in relating analyses.  Moreover, insights
gleaned from this understanding of analysis can inform future analysis
design.  To develop rich analyses, we should expect larger and larger
classes to be captured.  In short: computational complexity is a means
to both organize and extend the universe of static analyses.

Other research has shown a correspondence between 0CFA and certain
type systems \cite{palsberg-okeefe-toplas95,heintze-sas95} and a
further connection has been made between intersection typing and
$k$CFA \cite{Mossin:97:FlowAnalysis, palsberg-pavlopoulou-jfp01}.
Work has also been done on relating the various flavors of control
flow analysis, such as 0CFA, $k$CFA, polymorphic splitting, and
uniform $k$CFA \cite{nielson-nielson-popl97}.
%% We can enhance these connections by relating of these systems through
%% minimal complete developments.
Moreover, control flow analysis can be computed under a number of
different guises such as set-based analysis \cite{heintze-lfp94},
closure analysis \cite{sestoft-ms,sestoft-fpca89}, abstract
interpretation
\cite{shivers-phd,tang-jouvelot-tacs94,might-shivers-popl06,might-phd,midtgaard-jensen-sas-08,midtgaard-jensen-icfp09},
and type and effect systems
\cite{faxen-sas95,heintze-sas95,faxen-lomaps97,banerjee-icfp97}.

We believe a useful taxonomy of these and other static analyses can be
derived by investigating their computational complexity.  Results on
the complexity of static analyses are way of understanding when two
seemingly different program analyses are in fact computing the same
thing.


% Leading researchers in this field have made evident that this
% knowledge has been both needed and lacking. For example, in a

% definitive survey on principles of program analysis,
% \citet{nielson-nielson-hankin} underline the similarities among
% different program analysis techniques.  In the preface, they make an
% analogy between their approach and that of computational complexity,
% where seemingly unrelated problems can be related through notions of
% {\em reduction} (which is just a resource-bounded compiler).

% While what is known about inclusions between complexity classes ``does
% not amount to much'' \cite[p. 139]{Papadimitriou94}, some\dots some of
% these relations between classes are known.

% \begin{quotation}%
% There is an important analogy to complexity theory here. [\dots] The
% notions of logspace reduction between problems and of \np-complete
% problems lead to realising that problems may appear unrelated at first
% sight and nonetheless be so closely related that a good algorithm or
% heuristics for one will give rise to a good algorithm or heuristics of
% the other.
% \end{quotation}


\subsection{Complexity Classes}

In this section, we review some basic definitions about complexity
classes and define the flow analysis problem.

A complexity class is specified by a model of computation, a mode of
computation (e.g.~deterministic, non-deterministic), the designation
of a unit of {\em work}---a resource used up by computation
(e.g.~time, space), and finally, a function $f$ that bounds the use of
this resource.  The complexity class is defined as the set of all
languages decided by some Turing machine $M$ that for any input $x$
operates in the given mode within the bounds on available resources,
at most $f(|x|)$ units of work \cite[page 139]{Papadimitriou94}.

Turing machines are used as the model of computation, in both
deterministic and non-deterministic mode.  Recall the formal
definition of a Turing machine: a 7-tuple
\[
\langle Q,\Sigma,\Gamma,\delta,q_0,q_a,q_r\rangle
\]
where $Q$, $\Sigma$, and $\Gamma$ are finite sets, $Q$ is the set of
machine states (and $\{q_0,q_a,q_r\}\subseteq Q$), $\Sigma$ is the
input alphabet, and $\Gamma$ is the tape alphabet, where
$\Sigma\subseteq\Gamma$.  For a deterministic machine, the transition
function, $\delta$, is a partial function that maps the current
machine state and tape contents under the head position to the next
machine state, a symbol to write on the tape under the head, and left
or right shift operation for moving the head.  For a non-deterministic
machine, the transition function is actually a relation and may map
each machine configuration to multiple successor configurations.  The
states $q_0$, $q_a$, and $q_r$ are the machine's initial, accept, and
reject states, respectively.  Transitions consume one unit of time and
space consumption is measured by the amount of tape used.

\begin{definition}
Let $f:\mathcal{N} \rightarrow \mathcal{N}$.  We say that machine {\em
  $M$ operates within time $f(n)$} if, for any input string $x$, the
time required by $M$ on $x$ is at most $f(|x|)$ where $|x|$ denotes
the length of string $x$.  Function $f(n)$ is a {\em time bound} for
$M$.  

Let $g:\mathcal{N} \rightarrow \mathcal{N}$. We say that machine {\em
  $M$ operates within space $g(n)$} if, for any input string $x$, the
space required for the work tape of $M$ on $x$ is at most $g(|x|)$.
Function $g(n)$ is a {\em space bound} for $M$.
\end{definition}

Space bounds do not take into consideration the amount of space needed
to represent the input or output of a machine, but only its working
memory.  To this end, one can consider Turing machines with three
tapes: an input, output and work tape.  (Such machines can be
simulated by single tape Turing machines with an inconsequential loss
of efficiency). The input tape is read-only and contains the input
string.  The work tape is initially empty and can be read from and
written to.  The output tape, where the result of computation is
written, is initially empty and is write only.  A space bound
characterizes the size of the work only.  See \citet[Sections
2.3--5]{Papadimitriou94} or \citet[Section 7.5]{garey-johnson} for
further details.

A complexity class is a set of languages representing decision
problems that can be decided within some specified bound on the
resources used, typically time and space.  Suppose the decision
problem can be decided by a deterministic Turing machine operating in
time (space) $f(n)$, we say the problem is in \dtime$(f(n))$
(\dspace$(f(n))$); likewise, if a problem can be decided by a
non-deterministic Turing machine operating in time $f(n)$, we say the
problem is in \ntime$(f(n))$.

We make use of the following standard complexity classes:
\begin{displaymath}
\begin{array}{lrclcl}
 & \mbox{\logspace} & = & \bigcup_{j>0} \mbox{\dspace}(j \log n) \\
\subseteq & \mbox{\ptime} &  = & \bigcup_{j>0} \mbox{\dtime}(n^j)\\
\subseteq & \mbox{\np} & = & \bigcup_{j>0} \mbox{\ntime}(n^j)\\
\subseteq & \mbox{\exptime} & = & \bigcup_{j>0} \mbox{\dtime}(2^{n^j})
\end{array}
\end{displaymath}
In addition to the above inequalities, it is known that \ptime\
$\subset$ \exptime.


% \begin{definition}
% Let the {\em Kalmar elmentary functions} be 
% %\begin{displaymath}
% \begin{eqnarray*}
% \mathbf{K}(0,n) & = & n\\
% \mathbf{K}(t+1,n) & = & 2^{\mathbf{K}(t,n)}
% \end{eqnarray*}
% %\end{displaymath}
% \end{definition}

% \dvhnote{This should be related to the ICC community where there is a
% strong interest in capturing class of computations with implicit
% syntactic discipline.}

What is the difficulty of computing within this hierarchy? What are
the sources of approximation that render such analysis tractable?  We
examine these questions in relation to {\em flow analysis problems},
which are {\em decision problems}, computational problems that require
either a ``yes'' or ``no'' answer, and therefore are insensitive to
output size (it is just one bit).

The flow analysis decision problem we examine is the following:
\begin{description}
\item[Flow analysis decision problem:] Given an expression $e$, an
abstract value $v$, and a pair $(\ell,\delta)$, does $v$ flow into
$(\ell,\delta)$ by this flow analysis?
\end{description}


\section{Proving Lower Bounds: The Art of Exploitation}
\label{sec:approach}

Program exploitation---a staple of hacking---is simply a clever way of
making a program do what you want it to do, even if it was designed to
prevent that action \cite[page 115]{erickson-hacking}. 
%
This is precisely the idea in proving lower bounds, too.

The program to be exploited, in this setting, is the static analyzer.
%
What we would like to do varies, but generally we want to exploit
analysis to solve various computationally difficult classes of
problems.
%
In some cases, what we want to do is {\em evaluate}, rather than
approximate, the program being analyzed.
%
In this sense, we truly subvert the analyzer by using it to carry out
that which it was designed to avoid (to discover {\em without actually
  running} \cite[page xv]{muchnick-jones-81}).

The approach of exploiting analysis for computation manifests itself
in two ways in this dissertation:

\newpage
\begin{enumerate}
\item Subverting abstraction

The first stems from a observation that perhaps the languages of
abstract and concrete interpretation intersect.  That is, abstract
interpretation makes approximations compared to concrete
interpretation, but perhaps there is a subset of programs on which no
approximation is made by analysis.  For this class of programs,
abstract and concrete interpretation are synonymous.  Such a language
certainly exists for all of the flow analyses examined in this
dissertation.  We conjecture that in general, for every useful
abstract interpretation, there is a subset of the analyzed language
for which abstract and concrete interpretation coincide.  By
identifying this class of programs, lower bounds can be proved by
programming within the subset.

One of the fundamental ideas of computer science is that ``we can
regard almost any program as the evaluator for some language''
\cite[page 360]{sicp}.  So it is natural to ask of any algorithm, {\em
  what is the language being evaluated?}  The question is particularly
relevant when asked of an abstract evaluator.  We can gain insight
into an analysis by comparing the language of the abstract evaluator
to the language of the concrete evaluator.

So a program analysis itself can be viewed as a kind of programming
language, and an analyzer as a kind of evaluator.
%
Because of the requisite decidability of analysis, these languages
will come with implicit bounds on computational resources---if the
analysis is decidable, these languages cannot be Turing-complete.
%
But lower bounds can be proved by clever hacking within the
unconventional language of analysis.

This approach is the subject of \autoref{chapter-0cfa}.

\item Harnessing re-evaluation

  The second way analysis can be exploited is to identify the sources
  of approximation in the analysis and instead of avoiding them,
  (turning the abstract into the concrete as above), harness them for
  combinatorial power.  In this approach, lower bounds can be proved
  by programming the language of the analysis in a way that has little
  to do with programming in the language of concrete interpretation.

  Researchers have made empirical observations that computing a more
  precise analysis is often cheaper than performing a less precise
  one.  The less precise analysis ``yields coarser approximations, and
  thus induces more merging. More merging leads to more propagation,
  which in turn leads to more reevaluation''
  \cite{wright-jagannathan-toplas98}.  \citet{might-shivers-icfp06}
  make a similar observation: ``imprecision reinforces itself during a
  flow analysis through an ever-worsening feedback loop.''
  % 
  For the purposes of proving lower bounds, we are able to harness
  this re-evaluation as a computation engine.
  

This approach is the subject of \autoref{chapter-kcfa}.

\end{enumerate}