An Incremental Slicing Method for Functional Programs

Abstract: Several applications of slicing require a program to be sliced with respect to more than one slicing criterion. Program specialization, parallelization and cohesion measurement are examples of such applications. These applications can benefit from an incremental static slicing method in which a significant extent of the computations for slicing with respect to one criterion could be reused for another. In this paper, we consider the problem of incremental slicing of functional programs. We first present a non-incremental version of the slicing algorithm which does a polyvariant analysis 1 of functions. Since polyvariant analyses tend to be costly, we compute a compact context-independent summary of each function and then use this summary at the call sites of the function. The construction of the function summary is non-trivial and helps in the development of the incremental version. The incremental method, on the other hand, consists of a one-time pre-computation step that uses the non-incremental version to slice the program with respect to a fixed default slicing criterion and processes the results further to a canonical form. Presented with an actual slicing criterion, the incremental step involves a low-cost computation that uses the results of the pre-computation to obtain the slice. We have implemented a prototype of the slicer for a pure subset of Scheme, with pairs and lists as the only algebraic data types. Our experiments show that the incremental step of the slicer runs orders of magnitude faster than the non-incremental version. We have also proved the correctness of our incremental algorithm with respect to the non-incremental version.

\section{Experiments and results}\label{sec:exp-result}
In this  section, we present the  results from our experiments  on the
implementations of  both versions of  slicing.  In the absence  of the
details of implementations of other  slicing methods, we have compared
the  incremental   step  of   our  method  with   the  non-incremental
version.
Our experiments show that the incremental slicing algorithm gives
benefits  even  when  the  overhead of  creating  the  completing
automata is amortized  over  even a
few slicing criteria.

Our benchmarks consists of first order programs derived from the nofib
suite~\cite{nofib}. The higher order programs have been handcrafted to
bring out the issues related  to higher order slicing.  The program named
{\bf parser} includes most of the higher  order parser combinators
required for  parsing. {\bf fold} corresponds  to the
example    in     Figure~\ref{fig:higher-order-mot-example}.
Table~\ref{tab:exp-results} shows  the time required for  slicing with
different slicing  criteria.  For each  benchmark, we first  show, the
pre-computation time,  i.e.  the  time required to construct the
completing  automata.  We  then   consider  three  different  slicing
criteria, and  for each  slicing criterion, present the  times for
non-incremental slicing and the incremental step.
The  results   in  Table~\ref{tab:exp-results}   show  that   for  all
benchmarks, the  time required to  compute the completing  automata is
comparable   to    the   time   taken   for    computing   the   slice
non-incrementally.  Since computing completing  automata is a one time
activity, incremental slicing is very efficient even when a program is
sliced only  twice.  As seen in  Table~\ref{tab:exp-results}, the time
taken  for the  incremental step  is orders  of magnitude  faster than
non-incremental slicing,  thus confirming the benefits  of reusing the
completing automata.

We also show the number of  expressions in the original program and in
the slice  produced to  demonstrate the  effectiveness of  the slicing
process itself.  Here are some  of the  interesting cases.  It  can be
seen  that the  slice  for  {\bf nqueens}  for  any slicing  criterion
includes the  entire program.  This  is because finding out  whether a
solution exists  for {\bf nqueens}  requires the entire program  to be
executed.   On  the  other  hand,   the  program  {\bf  lambda}  is  a
$\lambda$-expression evaluator  that returns a tuple  consisting of an
atomic value  and a  list.  The  criterion $\{\epsilon,  0\}$ requires
majority of the expressions in the  program to be present in the slice
to  compute  the atomic  value.   On  the  other hand,  the  criterion
$\{\epsilon\}$ or  $\{\epsilon, 1\}$  do not require  any value  to be
computed and expressions  which compute the constructor  only are kept
in  the slice,  hence our  algorithm is  able to  discard most  of the
expressions.  This  behavior can be  clearly seen in  the higher-order
example  {\bf fold}  where a  slicing criterion  $\{\epsilon, \ensuremath{\mathbf{0}}\}$
selects an expression which only uses the first element of {\tt lst1},
thus allowing our slicing algorithm to discard most of the expressions
that construct  {\tt lst1}.
After  examining the  nature of  the benchmark  programs, the  slicing
criteria and  the slices, we  conclude that slicing is  most effective
when the slicing criterion selects  parts of a bounded structure, such
as a tuple, and  the components of the tuple are  produced by parts of
the program that are largely disjoint.

\section{Introduction}
\label{sec:intro}
Program slicing refers to the class of techniques that delete parts of
a  given  program while  preserving  certain  desired behaviors,  for
example, memory, state or parts of output. These behaviors are called
{\em  slicing criteria}.   Applications  of slicing  include
debugging     (root-cause    analysis),     program    specialization,
parallelization  and cohesion  measurement.  However,  in some  of the
above applications,  a program has to  be sliced more than  once, each
time  with a  different  slicing criterion.  In  such situations,  the
existing
techniques~\cite{weiser84, horwitz88, reps96, Liu:2003,
  Rodrigues_jucs_12_7, Silva_System_Dependence_Graph}   are
inefficient as they typically analyze the program multiple times.
Each round of analysis involves  a fixed point computation on the
program text or some intermediate  form of the program, typically
SDG  in the  case of  imperative languages.   We thus  require an
incremental  approach to  slicing which can avoid  repeated  fixpoint
computation  by reusing  some of  the information  obtained while
slicing the same program earlier with a different criterion.

\input{example-program}

The        example       from~\cite{reps96}        shown       in
Figure~\ref{fig:mot-example}b motivates the  need for incremental
slicing. It shows a simple  program in a Scheme-like language. It
takes a  string as  input and  returns a  pair consisting  of the
number    of    characters    and   lines    in    the    string.
Figure~\ref{fig:mot-example}b shows the program when it is sliced
with respect  to the first  component of the output  pair, namely
the number of lines in the  string ({\tt lc}).  All references to
the  count   of  characters   ({\tt  cc})  and   the  expressions
responsible for  computing {\tt  cc} only  have been  sliced away
(denoted $\ensuremath{\square}$).   The same  program can  also be  sliced to
produce only the char count and the resulting program is shown in
Figure~\ref{fig:mot-example}c.  \mbox{}

The  example illustrates  several important  aspects for  an effective
slicing  procedure.  We  need the  ability to  specify a  rich set  of
slicing  criteria to  select  different parts  of  a possibly  complex
output structure (first and second component of the output pair in the
example, or say,  every even element in an output  list).  Also notice
that to compute some part of  an output structure, all prefixes of the
structure have to be computed.  Thus, slicing criteria have to be {\em
  prefix-closed}.   Finally, it  seems likely  from the  example, that
certain  parts  of   the  program  will  be  present   in  any  slice,
irrespective  of the  specific slicing  criterion\footnote{the trivial
  {\em null} slicing  criteria where the whole program  is sliced away
  is  an  exception, but  can  be  treated separately.}.   Thus,  when
multiple slices of the same  program are required, a slicing procedure
should strive for efficiency  by minimizing re-computations related to
the common parts.

In  this paper,  we consider  the problem  of incremental  slicing for
functional programs. We restrict ourselves  to tuples and lists as the
only  algebraic data  types.   We represent  our  slicing criteria  as
regular grammars that  represent sets of prefix-closed  strings of the
selectors \CAR\/ and \CDR.  The  slicing criterion represents the part
of the output of  the program in which we are  interested, and we view
it  as being  a  \emph{demand} on  the program.   We  first present  a
non-incremental  slicing  method, which propagates the demand represented
by  the slicing  criterion  into  the program.
In   this    our   method   resembles the   projection    function   based
methods of~\cite{reps96, Liu:2003}.  However, unlike these methods, we do
a context-sensitive analysis of functions calls. This makes our method
precise by  avoiding analysis  over infeasible  interprocedural paths.
To  avoid the  inefficiency  of  analyzing a  function  once for  each
calling context,  we create a compact  context-independent summary for
each function. This summary is then  used to step over function calls.
As we shall see, it is this context independent summary that also makes the
incremental version possible in our approach.

The incremental version, has a  one-time pre-computation step in which
the program is sliced with  respect to a \emph{default criterion} that
is same for all  programs.  The result of this step  is converted to a
set  of  automata, one  for  each  expression  in the  program.   This
completes the pre-computation step.  To decide whether a expression is
in the  slice for a given  slicing criterion, we simply  intersect the
slicing criterion with the  automaton corresponding to the expression.
If the result  is the empty set, the expression can be  removed from the
slice.

The main contributions of this paper are as follows:
\begin{enumerate}
\item We propose a view of the  slicing criterion in terms of a notion
  called {\em  demand} (Section~\ref{sec:demand})  and  formulate the
  problem of  slicing as  one of  propagating the  demand on  the main
  expression to all the sub-expressions  of the program.  The analysis
  for this is precise because it  keeps the information at the calling
  context  separate.   However it  attempts  to  reduce the  attendant
  inefficiency through  the use of function  summaries. The difficulty
  of creating function summaries in a polyvariant analysis, especially
  when the  domain of analysis  is unbounded,  has been pointed out
  in \cite{reps96}.
\item  Our   formulation  (Section~\ref{sec:grammar-formulation})
  allows us to derive an incremental version of slicing algorithm
  that factors  out computations  common to all  slicing criteria
  (Section~\ref{sec:incr-analysis})     and      re-uses     these
  computations.  To  the best  of our knowledge,  the incremental
  version of slicing in this form has not been attempted before.
\item  We  have proven  the  correctness  of  the  incremental  slicing
  algorithm   with  respect   to   the  non-incremental   version
  (Section~\ref{sec:incremental-proofs}).
\item We  have implemented a  prototype slicer for  a first-order
  version of Scheme (Section~\ref{sec:exp-result}).  We have also
  extended   the   implementation    to   higher-order   programs
  (Section~\ref{sec:higher-order}) by converting such programs to
  first-order             using             \emph{firstification}
  techniques~\cite{Mitchell:2009},    slicing   the    firstified
  programs using our slicer, and  then mapping the sliced program
  back   to  the   higher-order   version.   The   implementation
  demonstrates the expected benefits  of incremental slicing: the
  incremental step  is one to four orders  of magnitude faster  than the
  non-incremental version.
\end{enumerate}

\input{syntax-of-our-language}

\section{The target language---syntax and semantics}
\label{sec:defs}
Figure~\ref{fig:lang-syntax}  shows the  syntax of  our language.
For   ease  of   presentation,  we   restrict  the   language  to
Administrative Normal Form (ANF)~\cite{chakravarty03perspective}.
In this form,  the arguments to functions can  only be variables.
To  avoid  dealing  with  scope-shadowing,  we  assume  that  all
variables  in  a program  are  distinct.   Neither of  these  two
restrictions affect the expressibility of our language.  In fact,
it is a simple matter to transform the pure subset of first order
Scheme  to our  language,  and  map the  sliced  program back  to
Scheme.  To refer to an expression $e$, we may annotate it with a
label $\pi$ as $\pi\!:\!e$; however the  label is not part of the
language.  To keep  the description simple, we  shall assume that
each program has its own unique set of labels.  In other words, a
label  identifies both  the program  point and  the program  that
contains it.

A  program in  our language  is a  collection of  function definitions
followed by  a main expression denoted  as $e_\ensuremath{\mathbf{main}}$.  Applications
(denoted  by   the  syntactic  category  $\mathit{App}$)   consist  of
functions   or   operators    applied   to   variables.    Expressions
($\mathit{Expr}$) are either an $\SIF$ expression, a $\LET$ expression
that evaluates an application and binds the result to a variable, or a
$\SRETURN$ expression.  The $\SRETURN$ keyword is used to mark the end
of a function so as to initiate appropriate semantic actions during
execution.  The distinction between  expressions and applications will
become important while specifying the semantics of programs.

\input{semantics-of-our-language}

\subsection{Semantics}
We now present  the operational semantics for  our language. This
is   largely  borrowed   from~\cite{asati14,  lazyliveness} and we
include it here for  completeness.  We  start with  the domains
used by the semantics:
\[
\begin{array}{rlcl@{\hspace{2em}}l}
v: & \mathit{Val} &=& \mathbb{N} + \{\NIL\} + \mathit{Loc}& \hspace{-0.5cm}\mbox{-- Values}\\ \rho: & \mathit{Env}
&=&\mathit{Var} \rightarrow {Val} &
\hspace{-0.5cm}\mbox{-- Environment} \\ \heap: & \mathit{Heap} &
=&\mathit{Loc} \rightarrow (Val\times Val + \{empty\}) &\hspace{-0.5cm}
\mbox{-- Heap}
\end{array}
\]

A value  in our language  is either a  number, or the  empty list
denoted by  $\NIL$, or a  location in the heap.  The  heap maps
each location  to a pair of values denoting a cons cell. Heap
locations can also be empty. Finally, an environment is a mapping
from variables  to values.

The    dynamic   aspects     of    the    semantics,   shown    in
Figure~\ref{fig:syntax-sematics},   are   specified  as   a   state
transition system. The semantics of applications $s$ are given by
the judgement  form $\rho, \heap, s  \rightsquigarrow \heap', v$,
and those for expressions $e$ by the form $\rho, \stack, \heap, e
\rightarrow \rho', \stack', \heap', e'$. Here $\stack$ is a stack
consisting of  continuation frames  of the  form $(\rho,  x, e)$.
The frame $(\rho,  x, e)$ signifies that if  the current function
returns a value $v$, the next  expression to be evaluated is $e$,
and the  environment for this  evaluation is $\rho$  updated with
the   variable  $x$   bound   to  $v$.    The   start  state   is
$(\{\}_\rho,[\,]_\stack,    \{\}_\heap,    e_\ensuremath{\mathbf{main}})$,    where
$\{\}_\rho$ is the empty  environment, $[\,]_\stack$ is the empty
stack,  and   $\{\}_\heap$  is  the  empty   heap.   The  program
terminates successfully  with result value $\rho(x)$  on reaching
the halt state $(\rho,[\,]_\stack, \heap, (\SRETURN\ x))$. We use
the  notation  $\rho[x  \mapsto  v]$ to  denote  the  environment
obtained by  updating $\rho$ with  the value  for $x$ as  $v$. We
also use $[\myvec{x} \mapsto \myvec{v}]$ to denote an environment
in which each $x_i$ has the value $v_i$.

\section{Demand}
\label{sec:demand}
We     now     connect     slicing    with     a     notion     called
\emph{demand}. A demand  on an expression represents the
set of paths that the context  of the expression \emph{may} explore of
the  value  of   the  expression.   A  demand  is   represented  by  a
prefix-closed set of  strings over $\ensuremath{(\acar+\acdr)^\ast}$.  Each  string in the
demand, called an \emph{access path},  represents a traversal over the
heap.  $\ensuremath{\mathbf{0}}$  stands for  a single-step traversal  over the  heap by
dereferencing the  $\CAR$ field  of a  cons cell.   Similarly, $\ensuremath{\mathbf{1}}$
denotes the dereferencing of the $\CDR$ field of a cons cell.

As an  example, a demand  of $\{\epsilon, \ensuremath{\mathbf{1}}, \ensuremath{\mathbf{1}}\ensuremath{\mathbf{0}}\}$  on the
expression $(\CONS~x~y)$  means its  context \emph{may} need  to visit
the  \CAR\ field  of  $y$ in  the heap  (corresponding  to the  string
\ensuremath{\mathbf{1}}\ensuremath{\mathbf{0}}\ in the demand).  The  example also illustrates why demands
are prefix-closed---the \CAR\  field of $y$ cannot  be visited without
visiting  first  the  cons  cell  resulting  from  the  evaluation  of
$(\CONS~x~y)$   (represented  by   $\epsilon$)  and   then  the   cell
corresponding to $y$ (represented by \ensuremath{\mathbf{1}}).  The absence of \ensuremath{\mathbf{0}}\ in
the demand  also indicates that  $x$ is {\em definitely  not} visited.
Notice that  to meet the  demand $\{\epsilon, \ensuremath{\mathbf{1}},  \ensuremath{\mathbf{1}}\ensuremath{\mathbf{0}}\}$ on
$(\CONS~x~y)$,  the  access  paths  $\{\epsilon, \ensuremath{\mathbf{0}}\}$  has  to  be
visited starting  from $y$.  Thus we  can think of $(\CONS~x~y)$  as a
\emph{demand transformer} transforming  the demand $\{\epsilon, \ensuremath{\mathbf{1}},
\ensuremath{\mathbf{1}}\ensuremath{\mathbf{0}}\}$ to the demand $\{\epsilon, \ensuremath{\mathbf{0}}\}$ on $y$ and the empty
demand (represented by $\emptyset$) on $x$.

The slicing  problem is now  modeled as follows.  Viewing  the slicing
criterion  (also   a  set   of  strings   over  $\ensuremath{(\acar+\acdr)^\ast}$)   as  a
demand\footnote{supplied by a context that is external to the program}
on the  main expression  $e_\ensuremath{\mathbf{main}}$, we compute  the demand  on each
expression in the program.  If the demand on a expression turns out to
be $\emptyset$,  the expression does  not contribute to the  demand on
$e_\ensuremath{\mathbf{main}}$ and can be removed from  the slice.  Thus the solution of
the slicing problem lies in computing a demand transformer that, given
a  demand on  $e_\ensuremath{\mathbf{main}}$, computes  a \emph{demand  environment}---a
mapping of each expression (represented by its program point $\pi$) to
its  demand.  We  formulate  this computation  as  an analysis  called
\emph{demand analysis}.

We use $\sigma$ to represent  demands and $\alpha$ to represent access
path.  Given  two access paths  $\alpha_1$ and $\alpha_2$, we  use the
juxtaposition  $\alpha_1\alpha_2$ to  denote their  concatenation.  We
extend this  notation to a concatenate  a pair of demands  and even to
the  concatenation  of a  symbol  with  a demand:   $\sigma_1\sigma_2$
denotes  the   demand  $\{\alpha_1\alpha_2\mid  \alpha_1   \in  \sigma
~\mbox{and}~ \alpha_2 \in \sigma_2\}$ and $\ensuremath{\mathbf{0}}\sigma$ is a shorthand
for $\{\ensuremath{\mathbf{0}}\alpha \mid \alpha \in \sigma\}$.

\input{demand-analysis}

\subsection{Demand Analysis}
Figure~\ref{fig:demand-analysis}   shows  the   analysis.   Given   an
application  $s$  and   a  demand  $\sigma$,  \A\   returns  a  demand
environment that maps expressions of  $s$ to their demands.  The third
parameter to \A, denoted \DT, represents context-independent summaries
of the functions in the program, and will be explained shortly.

Consider the rule for the selector  $\CAR$.  If the demand $\sigma$ on
$(\CAR~x)$ is $\emptyset$, then no part  of the value of $(\CAR~x)$ is
visited  and the  demand  on  $x$ is  also  $\emptyset$.  However,  if
$\sigma$  is  non-empty,  the  context  of  $(\CAR~x)$  has  to  first
dereference the value of $x$ using the $\CAR$\ field and then traverse
the paths represented by $\sigma$. In  this case, the demand on $x$ is
the  set consisting  of  $\epsilon$ (start  at the  root  of $x$)  and
$\ensuremath{\mathbf{0}}\sigma$ (dereference  using \CAR\  and then  visit the  paths in
$\sigma$).  On   the  other  hand,   the  rule  for   the  constructor
\CONS\  works   as  follows:   To  traverse  the   path  $\ensuremath{\mathbf{0}}\alpha$
(alternately $\ensuremath{\mathbf{1}}\alpha$)  starting from the root  of $(\CONS~x~y)$,
one has to traverse the path $\alpha$ starting from $x$ (or $y$).

Since  $(\mNULLQ~x)$  only visits  the  root  of  $x$ to  examine  the
constructor,  a non-null  demand  on $(\mNULLQ~x)$  translates to  the
demand $\epsilon$ on $x$.  A  similar reasoning also explains the rule
for $(\PRIM~x~y)$.  Since, both $x$ and  $y$ evaluate to integers in a
well typed program,  a non-null demand on  $(\PRIM~x~y)$ translates to
the demand $\epsilon$ on both $x$ and $y$.

The  rule for  a  function  call uses  a  third  parameter $\DT$  that
represents the summaries of all functions in the program.  $\DT$\ is a
set  of   context-independent  summaries,  one  for   each  (function,
parameter)  pair  in  the   program.   $\DT_{\mathit  f}^{\mathit  i}$
represents a transformation that describes  how any demand $\sigma$ on
a call to  $f$ is transformed into the demand  on its $i$th parameter.
$\DT$\ is specified by the  inference rule {\sc demand-summary}.  This
rule gives  a fixed-point property  to be satisfied by  $\DT$, namely,
the demand  transformation assumed  for each  function in  the program
should be  the same as  the demand transformation calculated  from the
body of the function.  Given $\DT$,  the rule for the function call is
obvious.  Notice that the demand  environment for each application $s$
also includes the demand on $s$ itself apart from its sub-expressions.
Operationally,  the  rule {\sc  demand-summary}  is  converted into  a
grammar (Section~\ref{sec:grammar-formulation})  that is parameterized
with  respect  to  a  placeholder  terminal  representing  a  symbolic
demand. The language  generated by this grammar is  the least solution
satisfying  the rule.   The  least solution  corresponds  to the  most
precise slice.

We finally discuss  the rules for expressions  given by $\mathcal{D}$.
The  rules for  $\mRETURN$\ and  $\mIF$\  are obvious.   The rule  for
$(\mLET \ x \leftarrow s \ \mIN \ e)$ first uses $\sigma$ to calculate
the demand environment DE of the  $\mLET$-body $e$.  The demand on $s$
is the union of  the demands on all occurrences of $x$  in $e$.  It is
easy  to see  by  examining the  rules that  the  analysis results  in
demands    that     are    prefix-closed.    More     formally,    let
$\mbox{DE}_{\sigma}$  be the  demand  environment  resulting from  the
analysis of a program for a  demand $\sigma$.  Then, for an expression
$\pi\!\!:\!e$   in   the  program,
$\mbox{DE}_{\sigma}(\pi)$ is prefix closed.

\section{Computing Context-Independent Function Summaries}
\label{sec:grammar-formulation}
A slicing  method used for, say,  debugging needs to be  as precise as
possible to avoid  false errors.  We therefore choose  to analyze each
function call separately  with respect to its calling  context. We now
show how  to obtain  a context-independent  summary for  each function
definition  from  the rule  {\sc  demand-summary}.   Recall that  this
summary is  a function that transforms  any demand on the  result of a
call to demands  on the arguments.  A convenient way  of doing this is
to express how a \emph{symbolic} demand  is transformed by the body of
a function.   Summarizing the function  in this way has  two benefits.
It helps us  to propagate a demand across several  calls to a function
without analyzing  its body each  time.  Even more importantly,  it is
the key to our incremental slicing method.

However, notice  that the rules of  demand analysis requires us  to do
operations that cannot be done on  a symbolic demand. The \CONS\ rule,
for example is defined in terms  of the set $\{\alpha \mid \ensuremath{\mathbf{0}}\alpha
\in  \sigma\}$.  Clearly  this  requires  us to  know  the strings  in
$\sigma$.  Similarly, the \mIF\ rule requires to know whether $\sigma$
is  $\emptyset$.   The way  out  is  to  treat these  operations  also
symbolically.  For this we introduce  three new symbols \ensuremath{\bar\acar}, \ensuremath{\bar\acdr}\/
and  \ensuremath{{\mathbf{2}}}\/,  to  capture   the  intended  operations.   If  \ensuremath{\mathbf{0}}\/
represents  selection using  \CAR, \ensuremath{\bar\acar}\/  is intended to
represent  a use as the left argument of  \CONS. Thus  \ensuremath{\bar\acar}\ensuremath{\mathbf{0}}\/
should  reduce  to the  empty  string  $\epsilon$. Similarly  \ensuremath{{\mathbf{2}}}\/
represents  the  symbolic transformation  of  any  non-null demand  to
$\epsilon$  and  null  demand  to itself.   These  transformation  are
defined  and  also  made   deterministic  through  the  simplification
function $\mathcal{S}$.
\begin{align*}
\simpl{\ensuremath{\lbrace\epsilon\rbrace}} &= \ensuremath{\lbrace\epsilon\rbrace} \\
\simpl{\ensuremath{\mathbf{0}}\sigma} &= \ensuremath{\mathbf{0}}\simpl{\sigma}\\
\simpl{\ensuremath{\mathbf{1}}\sigma} &= \ensuremath{\mathbf{1}}\simpl{\sigma}\\
\simpl{\ensuremath{\bar\acar}\sigma} &= \{\alpha \mid \ensuremath{\mathbf{0}}\alpha \in
\simpl{\sigma}\}\\
\simpl{\ensuremath{\bar\acdr}\sigma} &= \{\alpha \mid \ensuremath{\mathbf{1}}\alpha \in
\simpl{\sigma}\}\\
\simpl{\ensuremath{{\mathbf{2}}}\sigma} &=
  \left\{
  \begin{array}{ll}
    \emptyset&\mbox{if}~\simpl{\sigma} = \emptyset\\ \{\epsilon\} &
    \mbox{otherwise}
  \end{array}\right.\\
  \simpl{\sigma_1 \cup \sigma_2} &= \simpl{\sigma_1} \cup \simpl{\sigma_2}
\end{align*}
Notice that  \ensuremath{\bar\acar}\  strips the leading  \ensuremath{\mathbf{0}}\ from the string following
it, as  required by the rule for \CONS. Similarly,
\ensuremath{{\mathbf{2}}}\ examines  the string following  it and replaces it  by
$\emptyset$  or  $\{\epsilon\}$; this is  required  by  several rules.   The
$\mathcal{A}$  rules for  \CONS\ and  \CAR\ in  terms of  the new
symbols are:
\begin{align*}
&\A(\pi\!\!:\!(\CONS~\pi_1\!\!:\!x~\pi_2\!\!:\!y),\sigma,\DT)
  = \{\pi_1 \mapsto \ensuremath{\bar\acar}\sigma, \pi_2 \mapsto \ensuremath{\bar\acdr}\sigma, \pi
  \mapsto \sigma\}
  \\ &\A(\pi\!\!:\!(\CAR~\pi_1\!\!:\!x),\sigma,\DT) = \{
  \pi_1 \mapsto \ensuremath{{\mathbf{2}}}\sigma \cup \ensuremath{\mathbf{0}}\sigma, \pi \mapsto \sigma
  \}
\end{align*}
and the $\mathcal{D}$ rule for \SIF\ is:
\begin{align*}
\mathcal{D}(\pi\!\!:\!(\SIF~\pi_1\!\!:\!x~e_1~e_2),\sigma,\DT) &= \mathcal{D}(e_1,
\demand, \DT) \cup \mathcal{D}(e_2, \demand, \DT) ~\cup\\ &~~~~\{
\pi_1 \mapsto \mbox{if}~ \sigma \ne
\emptyset~\mbox{then}~\{\epsilon\}~\mbox{else}~ \emptyset,
\\ &~~~~\pi \mapsto \sigma \}
\end{align*}
The   rules  for   \CDR,  \PRIM\   and  \NULLQ\   are  also   modified
similarly. Now the demand summaries  can be obtained symbolically with
the new  symbols as markers  indicating the operations that  should be
performed string following it.  When the final demand environments are
obtained with the given slicing criterion acting a concrete demand for
the  main  expression  $e_\ensuremath{\mathbf{main}}$,  the symbols  \ensuremath{\bar\acar},  \ensuremath{\bar\acdr}\  and
\ensuremath{{\mathbf{2}}}\   are    eliminated   using   the    simplification   function
$\mathcal{S}$.

\subsection{Finding closed-forms for the summaries $\DT$}
Recall  that $\DT_{f}^i$  is a  function that  describes how  the
demand on  a call  to $f$  translates to  its $i$th  argument.  A
straightforward translation  of the {\sc demand-summary}  rule to
obtain $\DT_{f}^i$ is as follows:  For a symbolic demand $\sigma$
compute  the  the  demand  environment  in  $e_f$,  the  body  of
$f$. From this calculate the demand on the $i$th argument of $f$,
say $x$. This  is the union of demands of  all occurrences of $x$
in the body  of $f$. The demand on the  $i$th argument is equated
to $\DT_{f}^i(\sigma)$.  Since the body may  contain other calls,
the demand analysis within $e_f$ makes use of $\DT$ in turn. Thus
our equations  may be recursive. On the whole, $\DT$ corresponds
to a set of equations, one for each argument of each function.
The reader can verify that $\Lf{\linecharcount}{2}{\sigma}$ in
our running example is:
 \begin{eqnarray*}
   \Lf{\linecharcount}{2}{\sigma} &=& \ensuremath{\bar\acar}\sigma \cup \ensuremath{{\mathbf{2}}}
   \Lf{\linecharcount}{2}{\sigma}
 \end{eqnarray*}

As  noted  in~\cite{reps96},  the   main  difficulty  in  obtaining  a
convenient function  summary is to  find a closed-form  description of
$\Lf{\linecharcount}{2}{\sigma}$    instead     of    the    recursive
specification.   Our solution  to the  problem lies  in the  following
observation: Since we know that the demand rules always prefix symbols
to the argument  demand $\sigma$, we can  write $\Lf{f}{i}{\sigma}$ as
\concatenate{\Lfun{f}{i}}{\sigma},  where $\Lfun{f}{i}$  is  a set  of
strings over the  alphabet $\{\ensuremath{\mathbf{0}}, \ensuremath{\mathbf{1}}, \ensuremath{\bar\acar},  \ensuremath{\bar\acdr}, \ensuremath{{\mathbf{2}}} \}$.
The modified equations after doing this substitution will be,
 \begin{eqnarray*}
   \concatenate{\Lfun{\linecharcount}{2}}{\sigma} &=& \ensuremath{\bar\acar}\sigma \cup \ensuremath{{\mathbf{2}}} \concatenate{\Lfun{\linecharcount}{2}}{\sigma}
 \end{eqnarray*}
 Thus, we have,
 \begin{eqnarray*}
   \Lf{\linecharcount}{2}{\sigma} &=&
   \concatenate{\Lfun{\linecharcount}{2}}{\sigma}\\
   \mbox{where }\Lfun{\linecharcount}{2} &=& \{\ensuremath{\bar\acar}\}
   \cup \ensuremath{{\mathbf{2}}}\Lfun{\linecharcount}{2}
 \end{eqnarray*}

\subsection{Computing the demand environment for the function
  bodies} The demand environment for a function body $e_\mathit{f}$ is
calculated  with respect  to a  concrete  demand.  To  start with,  we
consider  the main  expression $e_\ensuremath{\mathbf{main}}$  as  being the  body of  a
function $\ensuremath{\mathbf{main}}$, The  demand on $e_\ensuremath{\mathbf{main}}$ is  the given slicing
criterion. Further,  the concrete  demand on  a function  $f$, denoted
$\sigma_f$, is the union of the demands at all call-sites of $f$.  The
demand  environment  of a  function  body  $e_f$ is  calculated  using
$\sigma_f$. If there is a call to $g$ inside $e_f$, the demand summary
$\DT_g$ is used  to propagate the demand across  the call.  Continuing
with  our example,  the union  of the  demands on  the three  calls to
\linecharcount\ is the slicing criterion.  Therefore the demand on the
expression at program point $\pi_1$ is given by
\begin{eqnarray}
  \begin{array}{rcl}
  \Demand{\pi_1} &=& \Lfun{\linecharcount}{2}{\sigma_{\linecharcount}}\\
  \Lfun{\linecharcount}{2}&=& \{\ensuremath{\bar\acar}\} \cup \ensuremath{{\mathbf{2}}}
   \Lfun{\linecharcount}{2}\\
   \sigma_{\linecharcount} &=& \mbox{slicing criterion}
  \end{array}\label{eqn:rec-dpi1}
 \end{eqnarray}
At the end of this step, we shall have (i) A set of equations
defining the demand summaries $\DT_{\mathit {f}}^{i}$ for each argument of each
function, (ii) Equations specifying the demand $\Demand{\pi}$ at
each program point $\pi$, and (iii) an equation for each
concrete demand $\sigma_f$ on the body of each function $f$.

\subsection{Converting analysis equations to grammars}

Notice that the equations  for \Lfun{\linecharcount}{2} are still
recursive.  However,  Equation~\ref{eqn:rec-dpi1} can  also be
viewed as a  grammar with $\{\ensuremath{\mathbf{0}},\ensuremath{\mathbf{1}},\ensuremath{\bar\acdr},\ensuremath{\bar\acar},\ensuremath{{\mathbf{2}}}\}$ as
terminal     symbols      and     \Lfun{\linecharcount}{2},
\Demand{\pi_1}  and  $\sigma_{\linecharcount}$ as  non-terminals.
Thus finding  the solution to  the set of equations  generated by
the demand analysis reduces to  finding the language generated by
the  corresponding grammar.   The original  equations can  now be
re-written   as    grammar   rules as shown below:
\begin{align}
  \label{eqn:grammar-fragment}
  \begin{split}
   \var{\Demand{\pi_1}} &\rightarrow \var{\Lfun{\linecharcount}{2}}\var{\sigma_{\linecharcount}}\\
      \var{\Lfun{\linecharcount}{2}} &\rightarrow \ensuremath{\bar\acar} \mid
      \ensuremath{{\mathbf{2}}}\; \var{\Lfun{\linecharcount}{2}}
\\
   \var{\sigma_{\linecharcount}} &\rightarrow \mbox{slicing
     criteria}
  \end{split}
\end{align}
 Thus  the question  whether  the expression  at  $\pi_1$ can  be
 sliced  for the  slicing criterion  $\sigma_{\linecharcount}$ is
 equivalent to asking whether the language  $\simpl{\lang{\Demand{\pi_1}}}$ is
 empty.  In fact, the simplification process $\mathcal{S}$ itself
 can  be captured  by  adding  the following  set of  five
 unrestricted  productions named $\mathit{unrestricted}$  and
 adding   the   production   $\var{\Demandp{\pi_1}}   \rightarrow
 \var{\Demand{\pi_1}}\$$ to the grammar generated earlier.
 \begin{eqnarray*}
   \ensuremath{\bar\acar}\ensuremath{\mathbf{0}} \rightarrow \epsilon &&
   \ensuremath{\bar\acdr}\ensuremath{\mathbf{1}} \rightarrow \epsilon\\
   \ensuremath{{\mathbf{2}}}\$   \rightarrow \epsilon&&
   \ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}} \rightarrow \ensuremath{{\mathbf{2}}}\\
   \ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{1}} \rightarrow \ensuremath{{\mathbf{2}}}
 \end{eqnarray*}
 The set  of five unrestricted productions  shown are independent
 of the  program being sliced  and the slicing criterion.
 The symbol \$ marks the end of a sentence  and is required to capture
 the $\ensuremath{{\mathbf{2}}}$ rule  correctly.

 We now generalize: Assume that  $\pi$ is the program point associated
 with  an expression  $e$.  Given  a slicing  criterion $\sigma$,  let
 $G_{\pi}^\sigma$  denote  the  grammar $(N,~T,~  P_{\pi}^\sigma  \cup
 \mathit{unrestricted}\cup         \{\Demandp{\pi}         \rightarrow
 \Demand{\pi}\$\},~  \var{\Demandp{\pi}})$.  Here  $T$ is  the set  of
 terminals   $\{\ensuremath{\mathbf{0}},   \ensuremath{\mathbf{1}},   \ensuremath{\bar\acar},  \ensuremath{\bar\acdr},   \ensuremath{{\mathbf{2}}},   \$   \}$,
 $P_{\pi}^\sigma$  is the  set  of  context-free productions  defining
 $\Demand{\pi}$,  the  demand  on   $e$  (as  illustrated  by  example
 \ref{eqn:grammar-fragment}).   $N$  contains the non-terminals  of
 $P_{\pi}^\sigma$ and additionally includes the special non-terminal
 $\Demandp{\pi}$.
   As mentioned earlier, given  a slicing criterion $\sigma$, the
   question of  whether the expression  $e$ can be sliced  out of
   the  containing program  is equivalent  to asking  whether the
   language $\lang{G_{\pi}^\sigma}$ is empty.   We shall now show
   that this problem is undecidable.

   \begin{theorem}
     Given a program point  $\pi$ and slicing criterion $\sigma$,
     the  problem  whether  $\lang{G_{\pi}^\sigma}$ is  empty  is
     undecidable.
\end{theorem}

   \begin{proofoutline}
     Recollect that the  set of demands on an  expression, as obtained
     by  our analysis,  is  prefix closed.  Since  the grammar  always
     includes       production      $\{\Demandp{\pi}       \rightarrow
     \Demand{\pi}\$\}$,  $\lang{G_{\pi}^\sigma}$ is  non-empty if  and
     only if it contains $\$$ (i.e.  empty string followed by the $\$$
     symbol).  We therefore  have to show that  the equivalent problem
     of  whether   $\$$   belongs  to  $\lang{G_{\pi}^\sigma}$  is
     undecidable.

     Given a Turing machine and a string $\alpha \in (0+1)^\ast$,
     the  proof   involves  construction   of  a  grammar   $G  =
     (N\cup\lbrace S, S'\rbrace,  T, P \cup \mathit{unrestricted}
     \cup \{S' \rightarrow S\$\}, S')$ with the property that the
     Turing machine halts on $\alpha$  if and only if $G$ accepts
     $\$$.   Notice  that  $P$  is a  set  of  context-free
     productions  over   the  terminal   set  $T$  and   may  not
     necessarily  be  obtainable  from   demand  analysis  of  a
     program.  However,  $G$ can be  used to construct  a program
     whose demand  analysis results  in a  grammar $G'$  that can
     used  instead  of $G$  to  replay  the earlier  proof.   The
     details   can  be   found   in  Lemmas   B.2   and  B.3   of
     \cite{lazyliveness}.
   \end{proofoutline}

\begin{figure}[t]
  \centering
  \includegraphics[width=.6\textwidth]{images/automata-1}
  \caption{(a) \& (b) show the simplification of the automaton $\automaton{M}{\pi_1}{\sigma}$ for the slicing criteria $\sigma = \{\epsilon, \ensuremath{\mathbf{0}}\}$ and $\sigma = \{\epsilon, \ensuremath{\mathbf{1}}\}$ respectively. (c) shows the canonical automaton $\grammar{A}{\pi_{1}}{}$ and the corresponding completing automaton $\compautomaton{A}{\pi_1}$}
  \label{fig:nfa}
\end{figure}

We get around  the problem of undecidability, we use  the technique of
Mohri-Nederhoff~\cite{mohri00regular}        to       over-approximate
$\grammar{P}{\pi}{\sigma}$  by a  strongly regular  grammar.  The  NFA
corresponding     to     this      automaton     is     denoted     as
$\automaton{M}{\pi}{\sigma}$.  The simplification rules can be applied
on  $\automaton{M}{\pi}{\sigma}$ without  any loss  of precision.  The
details of the simplification process are in~\cite{karkare07liveness}.

 For our running example, the grammar after demand analysis is
 already regular, and thus remains unchanged by Mohri-Nederhoff
 transformation.  The automata in Figures~\ref{fig:nfa}(a)
 and~\ref{fig:nfa}(b) correspond to the two slicing criteria
 $\sigma_{\linecharcount} = \{\epsilon, \ensuremath{\mathbf{0}}\}$ and
 $\sigma_{\linecharcount} = \{\epsilon, \ensuremath{\mathbf{1}}\}$ and illustrate the
 simplification of corresponding Mohri-Nederhoff automata $\automaton{M}{\pi_1}{\sigma_{\linecharcount}}$. It can be seen that, when the slicing
 criterion is $\{\epsilon, \ensuremath{\mathbf{1}}\}$, the language of
 $\Demand{\pi_1}$ is empty and hence $e$ can be sliced away.
 A drawback of the method outlined above  is that with a change in the
 slicing  criterion,   the  entire  process  of   grammar  generation,
 Mohri-Nederhoff   approximation   and   simplification  has   to   be
 repeated. This is likely to be inefficient for large programs.

\section{Incremental Slicing}  \label{sec:incr-analysis}
We now present an incremental algorithm which avoids the repetition of
computation when the  same program is sliced  with different criteria.
This can  be done by pre-computing  the part of the  slice computation
that is independent  of the slicing criterion.   The pre-computed part
can then be used efficiently to  slice the program for a given slicing
criterion.

In general, the pre-computation consists of three steps: (i) computing
the  demand  at  each  expression $\pi\!: e$  for  the  \emph{fixed}  slicing
criterion $\{\epsilon\}$ and applying the Mohri-Nederhoff procedure to
yield the automaton $\grammar{M}{\pi}{\{\epsilon\}}$, (ii) a step called
\emph{canonicalization}  which  applies  the simplification  rules  on
$\grammar{M}{\pi}{\{\epsilon\}}$ until the \ensuremath{\bar\acar}\  and \ensuremath{\bar\acdr}\ symbols in
the strings accepted  by the resulting automaton are only  at the end,
and,   from  this   (iii)   constructing  an   automaton  called   the
\emph{completing automaton}.  For the running example, the canonicalized and the completing
automata are shown
Figures~\ref{fig:nfa}(c).   We explain these
steps now.

  As          stated          earlier,          the          automaton
  $\grammar{M}{\pi_{1}}{\{\epsilon\}}$,  after  some  simplifications,
  gives the first automaton (the canonicalized automaton) shown in  Figure~\ref{fig:nfa}(c), which  we shall
  denote    $\grammar{A}{\pi_{1}}{}$.     It    is   clear    that    if
  $\grammar{A}{\pi_{1}}{}$  is concatenated  with a  slicing criterion
  that starts with the symbol $\ensuremath{\mathbf{0}}$, the result, after simplification,
  will be non-empty.   We call a string that starts  with $\ensuremath{\mathbf{0}}$ as a
  \emph{completing string} for $\grammar{A}{\pi_{1}}{}$. In this case,
  detecting a completing string was easy  because  all  strings  accepted  by
  $\grammar{A}{\pi_{1}}{}$ end  with $\ensuremath{\bar\acar}$.   Now consider  the second automaton in Figure~\ref{fig:nfa}(c),
  called the \emph{completing automaton}, that recognizes the language
  $\ensuremath{\mathbf{0}}(\ensuremath{\mathbf{0}}+\ensuremath{\mathbf{1}})^\ast$.  This automaton recognizes all completing
  strings  for  $\grammar{A}{\pi_{1}}{}$  and  nothing else.   Thus  for  an
  arbitrary  slicing  criterion  $\sigma$, it  suffices  to  intersect
  $\sigma$  with  the  completing  automaton  to  decide  whether  the
  expression at $\pi_1$ will  be in the  slice. In fact,  it is
  enough for the  completing automaton to recognize  just the language
  $\{\ensuremath{\mathbf{0}}\}$ instead of  $\ensuremath{\mathbf{0}}(\ensuremath{\mathbf{0}}+\ensuremath{\mathbf{1}})^\ast$.  The reason  is that
  any slicing criterion, say $\sigma$, is prefix closed, and therefore
  $\sigma  \cap  \{\ensuremath{\mathbf{0}}\}$ is  empty  if  and  only if  $\sigma  \cap
  \ensuremath{\mathbf{0}}(\ensuremath{\mathbf{0}}+\ensuremath{\mathbf{1}})^\ast$  is   empty.   Our   incremental  algorithm
  generalizes this reasoning.

\subsection{Completing Automaton and Slicing}
For constructing the completing  automaton for an expression $e$,
we  saw that  it would  be convenient  to simplify  the automaton
$\grammar{M}{e}{\{\epsilon\}}$ to an extent  that all  accepted strings, after
simplification,  have  $\ensuremath{\bar\acar}$   and $\ensuremath{\bar\acdr}$  symbols
only at the end. We now give a set of rules, denoted by $\ensuremath{\mathcal{C}}$,
that captures this simplification.
\begin{align*}
\canonical{\ensuremath{\lbrace\epsilon\rbrace}} &= \ensuremath{\lbrace\epsilon\rbrace}\\
\canonical{\ensuremath{\mathbf{0}}\sigma} &= \ensuremath{\mathbf{0}}\canonical{\sigma} \\
\canonical{\ensuremath{\mathbf{1}}\sigma} &= \ensuremath{\mathbf{1}}\canonical{\sigma} \\
\canonical{\ensuremath{\bar\acar}\sigma} &=
    \{\ensuremath{\bar\acar} \mid \ensuremath{\lbrace\epsilon\rbrace} = \canonical{\sigma}\} \cup \{\alpha \mid \ensuremath{\mathbf{0}}\alpha \in \canonical{\sigma}\} \\
  & \cup \{\ensuremath{\bar\acar}\ensuremath{\bar\acdr}\alpha \mid \ensuremath{\bar\acdr}\alpha \in \canonical{\sigma}\} \cup \{\ensuremath{\bar\acar}\bcar\alpha \mid \ensuremath{\bar\acar}\alpha \in \canonical{\sigma}\} \\
  \canonical{\ensuremath{\bar\acdr}\sigma} &=
    \{\ensuremath{\bar\acdr} \mid \ensuremath{\lbrace\epsilon\rbrace} = \canonical{\sigma}\} \cup   \{\alpha \mid \ensuremath{\mathbf{1}}\alpha \in \canonical{\sigma}\} \\
  & \cup \{\ensuremath{\bar\acdr}\bcdr\alpha \mid \ensuremath{\bar\acdr}\alpha \in \canonical{\sigma}\} \cup \{\ensuremath{\bar\acdr}\ensuremath{\bar\acar}\alpha \mid \ensuremath{\bar\acar}\alpha \in \canonical{\sigma}\} \\
    \canonical{\ensuremath{{\mathbf{2}}}\sigma} &= \ensuremath{{\mathbf{2}}}\canonical{\sigma} \\
    \canonical{\sigma_1 \cup \sigma_2} &= \canonical{\sigma_1} \cup \canonical{\sigma_2}
\end{align*}
$\ensuremath{\mathcal{C}}$ differs from $\Simpl$  in that it accumulates continuous
run of  \ensuremath{\bar\acar}\ and \ensuremath{\bar\acdr}\  at the end  of a string.   Notice that
$\ensuremath{\mathcal{C}}$,  like $\Simpl$,  simplifies its  input string  from the
right.    Here  is   an  example   of  $\ensuremath{\mathcal{C}}$   simplification:
  \[\ensuremath{\mathbf{1}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\mathbf{0}}\acar\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{\bar\acdr}\bcdr\ensuremath{\mathbf{1}}\ensuremath{\bar\acar} \ensuremath{\stackrel{\canon}{\rightarrow}}
  \ensuremath{\mathbf{1}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\mathbf{0}}\acar\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{\bar\acdr}\ensuremath{\bar\acar} \ensuremath{\stackrel{\canon}{\rightarrow}}
  \ensuremath{\mathbf{1}}\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{\bar\acdr}\ensuremath{\bar\acar} \]
In contrast the simplification of the same string using
$\Simpl$ gives:
\[\ensuremath{\mathbf{1}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\mathbf{0}}\acar\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{\bar\acdr}\bcdr\ensuremath{\mathbf{1}}\ensuremath{\bar\acar}
\ensuremath{\stackrel{\Simpl}{\rightarrow}} \ensuremath{\mathbf{1}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\mathbf{0}}\acar\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{\bar\acdr}\bcdr\ensuremath{\mathbf{1}}\emptyset
\ensuremath{\stackrel{\Simpl}{\rightarrow}}
\ensuremath{\mathbf{1}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\mathbf{0}}\acar\ensuremath{{\mathbf{2}}}\ensuremath{\mathbf{0}}\ensuremath{\bar\acdr}\ensuremath{\bar\acar}\emptyset
\ensuremath{\stackrel{\Simpl}{\rightarrow}} \ldots \ensuremath{\stackrel{\Simpl}{\rightarrow}} \emptyset
\]

$\ensuremath{\mathcal{C}}$ satisfies two important properties:
\begin{property} \label{prop:canon-1}
The result of $\ensuremath{\mathcal{C}}$ always has the form $(\ensuremath{\mathbf{0}} + \ensuremath{\mathbf{1}} +
\ensuremath{{\mathbf{2}}})^\ast(\ensuremath{\bar\acar} + \ensuremath{\bar\acdr})^\ast$.  Further,   if  $\sigma   \subseteq
  (\ensuremath{\mathbf{0}}+\ensuremath{\mathbf{1}}+\ensuremath{{\mathbf{2}}})^\ast$, then $\canonical{\sigma} = \sigma$.
\end{property}
\begin{property}\label{prop:canon-2}
  $\Simpl$      subsumes      $\ensuremath{\mathcal{C}}$, i.e.,
  $\simpl{\canonical{\sigma_1}\canonical{\sigma_2}}             =
  \simpl{\sigma_1\sigma_2}$.
\end{property}
Note that while we have defined canonicalization over a language,
the    actual    canonicalization    takes    place    over    an
automaton---specifically    the   automaton    $\grammar{M}{\pi}{}$
obtained after the Mohri-Nederhoff transformation.
The       function       {\bf      createCompletingAutomaton}       in
Algorithm~\ref{algo:comp-automata} takes  $\grammar{A}{\pi}{}$, the
  canonicalized  Mohri-Nederhoff automaton  for the  slicing criterion
  $\ensuremath{\lbrace\epsilon\rbrace}$,  as  input, and  constructs the  completing automaton,
  denoted as $\compautomaton{A}{\pi}$.

  \input{algorithms-for-incremental-slicing}

Recollect that the strings recognized by $\grammar{A}{\pi}{}$ are of the
form  $(\ensuremath{\mathbf{0}}  +  \ensuremath{\mathbf{1}}  +  \ensuremath{{\mathbf{2}}})^\ast(\ensuremath{\bar\acar}  +  \ensuremath{\bar\acdr})^\ast$.   The
algorithm first  computes the set  of states reachable from  the start
state using only  edges with labels $\{\ensuremath{\mathbf{0}},  \ensuremath{\mathbf{1}}, \ensuremath{{\mathbf{2}}}\}$.  This
set  is  called the  {\em  frontier  set}.   It then  complements  the
automaton  and  drops  all   edges  with  $\{\ensuremath{\mathbf{0}},  \ensuremath{\mathbf{1}},  \ensuremath{{\mathbf{2}}}\}$
labels. Finally,  all states in the  {\em frontier set} are  marked as
final states.
Since \automaton{A}{\pi}{} is independent
of the slicing  criteria, the {\em completing  automaton} is also
independent of the slicing criteria and needs to be computed only
once. It can be stored and  re-used whenever the program needs to
be  sliced.   To  decide  whether  $\pi\!\!:e$ can  be  sliced  out,  the
function {\bf inSlice}       described      in
Algorithm~\ref{algo:comp-automata}   just   checks   if   the
intersection      of      the     slicing      criteria      with
\lang{\compautomaton{A}{\pi}} is null.

 \input{proof.tex}

\section{Extension  to higher order functions}
\label{sec:higher-order}
\input{higher-order}

\input{extending_to_ho}

\begin{table}[t]
\caption{Statistics for incremental and non-incremental slicing}
\label{tab:exp-results}
\centering
\input{autotable}
\end{table}

\input{Experiments}

\section{Related work}
\label{sec:relatedwork}
\input{related_work}

\section{Conclusions and Future Work}\label{sec:concl}
We  have  presented  a  demand-based  algorithm  for  incremental
slicing  of  functional programs.   The  slicing  criterion is  a
prefix-closed regular language and represents parts of the output
of the  program that  may be  of interest to  a user of our slicing
method.   We view  the slicing  criterion  as a  demand, and  the
non-incremental version of  the slicer does a  demand analysis to
propagate this demand through the program.  The slice consists of
parts of the program with non-empty demands after the propagation.  A
key  idea in  this analysis  is the  use of  symbolic demands  in
demand  analysis. Apart form better handling of calling
contexts that improves the precision of the analysis,
this also helps in building the incremental version.

The incremental version builds on the non-incremental version.  A
per  program pre-computation  step  slices the  program with  the
default  criterion   $\epsilon$.   This  step  factors   out  the
computation that  is common  to slicing  with any  criterion. The
result, reduced to a canonical form,  can now be used to find the
slice for  a given criterion  with minimal computation.   We have
proven the correctness of  the incremental algorithm with respect
to the  non-incremental version.   And finally, we  have extended
our     approach     to     higher-order     programs     through
firstification. Experiments with  our implementation confirm the
benefits of incremental slicing.

There are however two areas of concern, one related to efficiency
and the  other to precision. To  be useful, the slicer  should be
able  to slice  large  programs quickly.   While our  incremental
slicer  is  fast  enough,   the  pre-computation  step  is  slow,
primarily  because  of  the canonicalization step.   In
addition, the firstification process may create a large number of
specialized first-order programs. As  an example, our experiments
with  functional  parsers  show   that  the  higher-order  parser
combinators such  as or-parser  and the  and-parser are called
often, and the arguments to these calls are in turns calls to
higher order functions, for instance the Kleene closure and the
positive closure parsers.

\begin{figure}[t!]
       \begin{uprogram}
      \UFL(\DEFINE\ (\mapsq~\pl)
      \UNL{1} (\SIF (\NULLQ\ \pl) (\SRETURN\ \pl)
      \UNL{2} (\SRETURN\ (\CONS\ (\sq\ (\CAR\ \pl))
      \UNL{7} \ (\mapsq~(\CDR~\pl)))))
       \end{uprogram}
       \caption{Example to illustrate the imprecision due to
         Mohri-Nederhoff approximation}
       \label{fig:imprecision}
 \end{figure}

The other concern is that  while our polyvariant approach through
computation  of   function  summaries  improves   precision,  the
resulting  analysis   leads  to  an  undecidable   problem.   The
workaround  involves   an  approximation   that  could   lead  to
imprecision. As an example, consider the function
$\mathbf{mapsq}$ shown in Figure~\ref{fig:imprecision}.
 The reader  can verify  that the  function summary  for $\mapsq$
 would     be     given     as:     $\Lf{\mapsq}{1}{\sigma}     =
 \concatenate{\Lfun{\mapsq}{1}}{\sigma}$, where   $\Lfun{\mapsq}{1} $  is
 the language    $\epsilon    \mid    \ensuremath{\mathbf{1}}^{n}\ensuremath{\bar\acdr}^{n}    \mid
 \ensuremath{\mathbf{1}}^{n}\ensuremath{\mathbf{0}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\bar\acdr}^{n}$, for $n\geq 0$.   Now,   given  a   slicing
 criterion    $\sigma    =   \{\epsilon,    \ensuremath{\mathbf{1}},    \ensuremath{\mathbf{1}}\acdr,
 \ensuremath{\mathbf{1}}\acdr\ensuremath{\mathbf{0}} \}$ standing for the path to the third element of a list, it
 is   easy   to   see  that   $\Lf{\mapsq}{1}{\sigma}   $   after
 simplification would  give back $\sigma$ itself,  and this is  the most
 precise  slice. However,  due  to Mohri-Nederhoff  approximation
 $\Lfun{\mapsq}{1}$  would  be  approximated  by  $\epsilon  \mid
 \ensuremath{\mathbf{1}}^{n}\ensuremath{\bar\acdr}^{m} \mid  \ensuremath{\mathbf{1}}^{k}\ensuremath{\mathbf{0}}\ensuremath{{\mathbf{2}}}\ensuremath{\bar\acar}\ensuremath{\bar\acdr}^{l}$,
 $n$, $m$, $k$, $l$ $\geq 0$. In
 this  case, $\Lfun{\mapsq}{1}$  would  be  $(\ensuremath{\mathbf{0}} +  \ensuremath{\mathbf{1}})^*$,
 keeping all the elements of the input list $\pl$ in the slice.

\balance

\subsection{Correctness of Incremental Slicing}
  \label{sec:incremental-proofs}

We now show that the incremental algorithm to compute incremental
slices  is correct.  Recall that we use the following notations: (i)  \grammar{G}{\pi}{\sigma}  is the  grammar
generated         by          demand         analysis
(Figure~\ref{fig:demand-analysis})  for an  expression  $\pi\!\!: e$ in
the   program of interest, when the slicing   criteria is
$\sigma$, (ii) \automaton{A}{\pi}{}  is   the  automaton   corresponding  to
\grammar{G}{\pi}{\ensuremath{\lbrace\epsilon\rbrace}}  after Mohri-Nederhoff  transformation and
canonicalization, and (iii) \compautomaton{A}{\pi}
is the completing automaton   for $e$.
We  first show  that the  result of  the demand  analysis for  an
arbitrary slicing  criterion $\sigma$ can be decomposed as  the
concatenation  of the  demand analysis  obtained for  the fixed  slicing
criterion      $\{\epsilon\}$      and      $\sigma$      itself.

\begin{lemma}
  \label{thm:grammar-eq}
For   all  expressions   $e$  and   slicing  criteria   $\sigma$,
\lang{\grammar{G}{\pi}{\sigma}}                                   =
\concatenate{\lang{\grammar{G}{\pi}{\ensuremath{\lbrace\epsilon\rbrace}}}}{\sigma}.
\end{lemma}

\begin{proof}
 The proof  is by  induction on the  structure of  $e$. Observe
   that    all    the    rules    of    the    demand    analysis
   (Figure~\ref{fig:demand-analysis})   add   symbols   only   as
   prefixes to the incoming  demand.  Hence, the slicing criteria
   will always appear  as a suffix of any string that is   produced by the
   grammar.  Thus, any  grammar \lang{\grammar{G}{\pi}{\sigma}} can
   be  decomposed  as  $\concatenate{\sigma'}{\sigma}$  for  some
   language $\sigma'$.  Substituting $\ensuremath{\lbrace\epsilon\rbrace}$  for $\sigma$,
   we   get   $\grammar{G}{\pi}{\ensuremath{\lbrace\epsilon\rbrace}}  =   \sigma'$.    Thus
   \lang{\grammar{G}{\pi}{\sigma}}                                =
   \concatenate{\lang{\grammar{G}{\pi}{\ensuremath{\lbrace\epsilon\rbrace}}}}{\sigma}.
\end{proof}
Given a string $s$ over $(\ensuremath{\bar\acar}+\ensuremath{\bar\acdr})^\ast$, we use the notation $\overline{s}$ to stand for the reverse of $s$ in which all occurrences of \ensuremath{\bar\acar}\ are replaced by \ensuremath{\mathbf{0}}\ and \ensuremath{\bar\acdr}\ replaced by \ensuremath{\mathbf{1}}. Clearly, $\simpl{\{s\overline{s}\}} = \ensuremath{\lbrace\epsilon\rbrace}$.

We next prove the completeness and minimality of \compautomaton{A}{\pi}.
\begin{lemma}
  \label{thm:aecomp}
$\{s\mid\Simpl(\lang{\automaton{M}{\pi}{\{s\}}}) \neq\ \emptyset\}
  = \concatenate{\lang{\compautomaton{A}{\pi}}}{(\ensuremath{\mathbf{0}} + \ensuremath{\mathbf{1}})^\ast} $
\begin{proof}
  We first prove  $LHS\ \subseteq\ RHS$.  Let  the string $s
  \in   \Simpl(\lang{\automaton{M}{\pi}{\{s\}}})$.   Then   by
  Lemma~\ref{thm:grammar-eq},             $s             \in
  \Simpl(\concatenate{\lang{\automaton{M}{\pi}{\{\epsilon\}}}}{\{s\}})$.
  By Property~\ref{prop:canon-2},  this also means  that $s
   \in
  \Simpl(\concatenate{\canonical{\lang{\automaton{M}{\pi}{\{\epsilon\}}}}}{\{s\}})$. Since
  strings                                                 in
  $\canonical{\lang{\automaton{M}{\pi}{\{\epsilon\}}}}$ are of
  the   form  $(\ensuremath{\mathbf{0}}   +  \ensuremath{\mathbf{1}}   +  \ensuremath{{\mathbf{2}}})^\ast(\ensuremath{\bar\acar}   +
  \ensuremath{\bar\acdr}))^\ast$  (Property~\ref{prop:canon-1}),  this  means
  that there is a  string $\concatenate{p_1}{p_2}$ such that
  $p_1 \in  (\ensuremath{\mathbf{0}} + \ensuremath{\mathbf{1}} + \ensuremath{{\mathbf{2}}})^\ast$  and $p_2
  \in  (\ensuremath{\bar\acar}  +  \ensuremath{\bar\acdr})^\ast$,  and  $\Simpl(\{p_2\}\{s\})\
  \subseteq  (\ensuremath{\mathbf{0}} +  \ensuremath{\mathbf{1}})^\ast$. Thus  $s$ can  be split
  into   two   strings   $s_1$    and   $s_2$,   such   that
  $\Simpl(\concatenate{\{p_2\}}{\{s_1\}})   =  \ensuremath{\lbrace\epsilon\rbrace}$.
  Therefore $s_1  = \overline{p_2}$.  From  the construction
  of  \compautomaton{A}{\pi}   we  have   $\overline{p_2}  \in
  \lang{\compautomaton{A}{\pi}}$  and  $s_2\   \in\  (\ensuremath{\mathbf{0}}  +
  \ensuremath{\mathbf{1}})^\ast$.            Thus,           $s\           \in
  \concatenate{\lang{\compautomaton{A}{\pi}}}{(\ensuremath{\mathbf{0}}         +
    \ensuremath{\mathbf{1}})^\ast}$.

  Conversely,  for the  proof  of $RHS\  \subseteq\ LHS$,  we
  assume       that       a        string       $s       \in
  \concatenate{\lang{\compautomaton{A}{\pi}}}{(\ensuremath{\mathbf{0}}         +
    \ensuremath{\mathbf{1}})^\ast}$.      From     the     construction     of
  \compautomaton{A}{\pi} we  have strings $p_1, p_2,  s'$ such
  that                      $p_1p_2                      \in
  \canonical{\lang{\automaton{M}{\pi}{\epsilon}}}$,  $p_1  \in
  (\ensuremath{\mathbf{0}}  + \ensuremath{\mathbf{1}}  +  \ensuremath{{\mathbf{2}}})^\ast$,  $p_2  \in (\ensuremath{\bar\acar}  +
  \ensuremath{\bar\acdr})^\ast$, $s$ is  $\overline{p_2}s'$ and $s'\in (\ensuremath{\mathbf{0}}
  +                   \ensuremath{\mathbf{1}})^\ast$.                    Thus,
  $\Simpl(\lang{\automaton{M}{\pi}{\{s\}}})                  =
  \Simpl(\lang{\concatenate{\automaton{M}{\pi}{\ensuremath{\lbrace\epsilon\rbrace}}}{\{s\}}})
  =\Simpl(\concatenate{\canonical{\lang{\automaton{M}{\pi}{\{\epsilon\}}}}{\{s\}}}) =
  \Simpl(\{\concatenate{p_1}{\concatenate{p_2}{\concatenate{\overline{p_2}}{s'}}}\})
  =                    \{p_1s'\}$.                     Thus,
  $\Simpl(\lang{\automaton{M}{\pi}{\{s\}}})$ is  non-empty and
  $s \in LHS$.
\end{proof}

\end{lemma}

We   now    prove   our    main   result:   Our    slicing   algorithm
represented by \KEEP~(Algorithm~\ref{algo:comp-automata}) returns true if and only
if   \Simpl(\concatenate{\lang{\automaton{A}{\pi}{\epsilon}}}{\sigma})
is non-empty.

\begin{theorem}
  $\Simpl(\lang{\automaton{M}{\pi}{\sigma}}) \neq \emptyset
  \leftrightarrow\ \KEEP(e,\sigma)$
\end{theorem}
\begin{proof}
  We    first    prove     the    forward    implication.     Let
  $s    \in    \Simpl(\lang{\automaton{M}{\pi}{\sigma}})$.     From
  Lemma~\ref{thm:grammar-eq},
  $s                                                          \in
  \Simpl(\concatenate{\lang{\automaton{M}{\pi}{\epsilon}}}{\sigma})$.
  From                               Property~\ref{prop:canon-2},
  $s                    \in                   \Simpl(\concatenate
  {\canonical{\lang{\automaton{M}{\pi}{\epsilon}}}}{\sigma})$.
  Thus,    there   are    strings    $p_1,    p_2$   such    that
  $p_1  \in  \canonical{\lang{\automaton{M}{\pi}{\epsilon}}},\  p_2
  \in        \sigma,        s       =        \Simpl(\{p_1p_2\})$.
  Further $p_1$ in  turn can be decomposed as  $p_3p_4$ such that
  $p_3    \in    (\ensuremath{\mathbf{0}}    +   \ensuremath{\mathbf{1}}    +    \ensuremath{{\mathbf{2}}})^\ast$    and
  $p_4    \in   (\ensuremath{\bar\acar}    +   \ensuremath{\bar\acdr})^\ast$.     We   also    have
  $\Simpl(\{p_4p_2\})  \subseteq  (\ensuremath{\mathbf{0}}  +  \ensuremath{\mathbf{1}})^\ast$.   Thus
  $\overline{p_4}$ is a prefix of $p_2$.

 From   the  construction   of   $\compautomaton{A}{\pi}$,  we   know
 $\overline{p_4} \in
 \lang{\compautomaton{A}{\pi}}$.   Further,   $\overline{p_4}$ is a prefix of  $p_2$ and $p_2 \in \sigma$, from
 the    prefix   closed    property   of    $\sigma$   we    have
 $\overline{p_4}  \in \sigma$.  This  implies
 $\compautomaton{A}{\pi} \cap \sigma \neq \emptyset$  and  thus
 $\KEEP(e, \sigma)$ returns true.

 Conversely, if $\KEEP(e, \sigma)$ is true, then $\exists
s:s \in \lang{\compautomaton{A}{\pi}} \cap\sigma$. In particular,
$s \in \lang{\compautomaton{A}{\pi}}$. Thus, from
Lemma~\ref{thm:aecomp} we have
$\Simpl(\lang{\automaton{M}{\pi}{\{s\}}}) \neq \emptyset$. Further, since $s
\in \sigma$ we have $\Simpl(\lang{\automaton{M}{\pi}{\sigma}}) \neq \emptyset$.\end{proof}