% -*- TeX-master: t; TeX-PDF-mode: t -*-

\documentclass[11pt]{article}
\usepackage{ifthen}
\usepackage{alltt}
% \usepackage{indentfirst}
\usepackage{latexsym}
\usepackage{graphicx}
\usepackage{gdefs}
% \usepackage{logdef}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{setspace}
\usepackage{rotating}
\usepackage{longtable}
\usepackage{hhline}
% \usepackage{refs}
\usepackage{changebar}
\usepackage{array}
\usepackage{wrapfig}
\usepackage[small,compact]{titlesec}
\usepackage{color} % For the \warn command.
\newcommand{\warn}[1]{\textbf{\color{red} [#1] }}

\usepackage{enumitem}
\setlist{nosep,leftmargin=\parindent}

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

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

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

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

\DeclareMathOperator{\combine}{\oplus}
\DeclareMathOperator*{\Combine}{\bigoplus}
\newcommand{\pe}{{\textrm pe}}
\newcommand{\pgen}{{\textrm pgen}}
\newcommand{\cogen}{{\textrm cogen}}

\newcommand{\Paths}{\textit{Paths}}
\newcommand{\Id}{\textit{Id}}
\newcommand{\Var}{\textit{Var}}
\newcommand{\NULL}{\textit{NULL}}
\newcommand{\nullVar}{\textit{null}}
\newcommand{\head}{\textit{head}}
\newcommand{\temp}{\textit{temp}}
\newcommand{\malloc}{\textit{malloc}}
\newcommand{\mallocThree}{\textit{malloc\$3}}

\title{Problems: Dataflow Analysis}
\date{}

\sloppy

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

\maketitle

\begin{enumerate}

\item
This question is about points-to analysis for a language like C
that allows pointers, pointers-to-pointers, etc.
The ultimate goal of the analysis is to discover,
for each node $n$ in the program's control-flow graph and
for each variable \texttt{x}, what variables \texttt{x}
might point to at $n$.
Thus, the final answer at node $n$ will be a set of pairs, such as
\[
  \{ \B{\texttt{x},\texttt{y}}, \B{\texttt{x},\texttt{z}}, \B{\texttt{y},\texttt{a}} \},
\]
meaning that, at $n$,
(i) \texttt{x} might point to \texttt{y},
(ii) \texttt{x} might point to \texttt{z}, and
(iii) \texttt{y} might point to \texttt{a}.

\hspace*{1.5ex}Assume that the programming language includes assignment statements, if-then-else
statements, and while loops (but no function calls, no pointer arithmetic,
and no heap-allocated storage).
Also assume that the assignment statements have been normalized so that there
is at most one pointer dereference per statement (e.g., the following
statements are okay:
\texttt{x = \&y; x = y; x = *y; *x = y;}, but
\texttt{*x = *y;} and \texttt{**x = y} are not).

\begin{enumerate}
  \item
    \label{It:PathProblemConstituents}
    Define, as a path problem, a \emph{flow-sensitive} points-to analysis for the language
    by providing the following:

    \begin{enumerate}
      \item The domain of dataflow answers (see above), as well as the domain of dataflow functions.
      \item The extend operation
      \item The combine operation used to combine dataflow facts at convergence points in
            the control-flow graph
      \item The dataflow functions for each kind of edge in the control-flow
            graph (i.e., the four different kinds of assignments, and the outgoing true-edges
            and false-edges at a branch node), as well as the initial value to use
            at the program's start node.
    \end{enumerate}

  \item
    The following program fragment constructs a linked list of unspecified size:
    \[\begin{array}{l}
      1: \quad \head = NULL;  \qquad\qquad //~\head = \&null \\
      2: \quad \textrm{while} (*) \{ \\
      3: \quad \quad \temp = \malloc(); \qquad //~\temp = \&\mallocThree \\
      4: \quad \quad *\temp = \head; \\
      5: \quad \quad \head = \temp; \\
      6: \quad \} \\
      7: \quad 
    \end{array}\]
    State the
    \begin{enumerate}
      \item
        global (combine-over-all-paths) view
      \item
        \label{It:EquationalView}
        local (equational) view, by giving the set of equations for your program
    \end{enumerate}

  \item
    Solve the equations that you gave in Question \ref{It:EquationalView}.

  \item
    Why is it interesting to know whether the dataflow functions for a
    flow-sensitive analysis are distributive?

  \item
    Are the dataflow functions that you wrote for Question \ref{It:PathProblemConstituents} all
    distributive?  (Justify your answer.)
\end{enumerate}

\newpage
\item
  This question concerns interprocedural reaching-definitions analysis
  and the Sharir-Pnueli call-strings approach (augmented with the
  technique for propagating information from an exit node to a corresponding
  return node that involves ``peeking-back at the dataflow information
  available at the call-node'').

  \begin{enumerate}
    \item
      Give an example program for which tracking call-strings of length up to 2
      gives more precise results than tracking call-strings of length up to 1.
      In particular, show the dataflow information obtained at each call-node,
      entry-node, exit-node, and return-node.

    \item
      For your example with call-strings 1, explain the point at which a
      call-string-saturation step takes place that is the root cause
      of the loss of precision.
      Then explain how that causes less precise information to be propagated
      from one or more callees to one or more callers.
  \end{enumerate}

\newpage
\item
  In this question, assume that we have a very simple programming
  language whose control-flow graphs have just read statements,
  assignment statements, conditions, and print statements; and there are
  no pointers, arrays, structs, etc.

  \hspace*{1.5ex}
  A path that includes no definition of variable $x$ is called
  an \emph{$x$-definition-free} path (in our simple language,
  only read and assignment statements define variables).
  The classic problem of live variables is defined as follows:
  \begin{equation*}
    \begin{array}{@{\hspace{0ex}}|@{\hspace{0.5ex}}p{.8\textwidth}@{\hspace{0.5ex}}|@{\hspace{0ex}}}
      \hline
      Variable $x$ is \emph{live} after node $n$ iff there is an
      $x$-definition-free path from a successor of $n$ to a node
      $m$ that uses $x$. \\
      \hline
    \end{array}
  \end{equation*}

  \hspace*{1.5ex}
  An assignment statement is \emph{useless} if it assigns to a variable
  that is \emph{not live}.
  Thus, live-variable analysis can be used to optimize a program by
  (i) performing live-variable analysis, and then (ii) removing useless assignment
  statements.
  A drawback of this approach is that after statements are removed by step (ii),
  there may be more opportunities to optimize the code by
  (iii) performing live-variable analysis again, and then (iv) removing
  the assignment statements that are now useless.
  This two-step process can be continued until there is a round when
  no assignment statement is removed.

  \hspace*{1.5ex}
  To avoid the need to iterate---i.e., perform live-variable analysis,
  find and remove useless assignment statements, perform live-variable analysis,
  find and remove useless assignment statements, etc.---we can perform
  a different analysis: {\em truly-live} analysis, which
  can be defined as follows:
  \begin{equation*}
    \begin{array}{@{\hspace{0ex}}|@{\hspace{0.5ex}}p{.8\textwidth}@{\hspace{0.5ex}}|@{\hspace{0ex}}}
      \hline
      Variable $x$ is \emph{truly live} after node $n$ iff there is an
      $x$-definition-free
      path from a successor of $n$ to a node $m$ that uses $x$, and:
      \begin{itemize}
        \item
          $m$ is a condition, or
        \item
          $m$ is a print statement, or
        \item
          $m$ is an assignment statement that assigns to a variable $y$
          that is truly live after $m$.
      \end{itemize} \\
      \hline
    \end{array}
  \end{equation*}
  Now an assignment statement is \emph{useless} if it assigns to a variable
  that is \emph{not truly live}, and we would optimize the code by (i) performing
  truly-live-variable analysis, and (ii) removing useless assignment statements.
  A little thought will convince you that no additional assignment statements
  can be removed if steps (i) and (ii) were to be repeated.

  \begin{enumerate}
    \item
      Like live-variable analysis, truly-live analysis is a backward
      problem.
      Define truly-live analysis by specifying each of the following:
      \begin{itemize}
        \item
          What is the type of a dataflow value associated with a node?
        \item
          What is the combine operation?
        \item
          What is the ``initial'' dataflow value (i.e., the value that holds at the
          \emph{exit} of the main procedure)?
      \end{itemize}

    \item
      For each of the four different node-kinds $k$ that we have in a
      program, we also need to specify the dataflow function for an edge
      whose target is of node-kind $k$.
      To do so, let's think about doing live-variable analysis and
      truly-live-variable analysis using the \emph{exploded-supergraph}
      model used in the IFDS framework.
      Assume that we have a program with three variables: $x$, $y$, and $z$.
      Fill in the edges of the exploded supergraph in each of the
      pictures on the next page so that they correctly represent the dataflow function
      for an edge into the respective CFG node shown.
      Fill in the graph for live analysis on the left and the graph
      for truly-live analysis on the right.

      \begin{center}
        \includegraphics[width=14.0cm]{Q3.eps}
      \end{center}
  
\newpage
    \item
      Is truly-live-variable analysis a gen/kill problem?
      Justify your answer.
  
    \item
      Recall that when using the exploded supergraph to perform dataflow analysis,
      \emph{summary edges} are added to the graph to summarize the effects
      of each procedure call.
      Assume that we have a program that includes the procedure $P$ shown below,
      in which $g1$, $g2$, and $g3$ are all global variables.
      \begin{verbatim}
       void P() {
          g3 = g2;
          read g2;
          while (g2 > 0) {
              g3 = g2;
              g2 = g2 / 2;
          }
          if (g2 == 10) {
              g1 = g3;
          }
      }
      \end{verbatim}
      Assume that procedure $main$ has one local variable $x$, and contains
      a single call to $P$.
      Complete the two pictures below by filling in the summary edges of the
      exploded supergraph that would be associated with the call to $P$
      in $main$---once for live analysis and once for truly-live analysis.

      \begin{center}
        \includegraphics[width=14.0cm]{Q3b.eps}
      \end{center}

    \item
      Give an example for which truly-live analysis is better than repeated
      live-variable analysis;
      i.e., an example with an assignment statement
      ``$x = \ldots$''
      such that truly-live analysis will find that
      $x$ is \emph{not} truly live afterwards (and so the assignment could
      be removed), but that fact will not be discovered by repeated live-variable
      analysis, because $x$ is always live afterwards, no matter how often
      you repeat the steps of performing live-variable analysis and removing
      useless assignment statements.
      Hint: your example can involve just a single procedure; you'll need an example
      with a loop.

  \end{enumerate}

\newpage
\item
  Recall that we can use the standard lattice framework for dataflow
  analysis to define an instance of a (forward, intraprocedural)
  dataflow problem for a given CFG as follows:
  \begin{itemize}
    \item
      We define a complete lattice of dataflow values, where the lattice has no
      infinite ascending chains.
    \item
      We assign a distributive (and hence monotonic) dataflow function to each CFG edge.
    \item
      We specify a special ``initial'' dataflow value that holds at the start node
      of the CFG.
  \end{itemize}

  \hspace*{1.5ex}
  For such problem instances, an algorithm that produces the solution
  to the dataflow problem (using the CFG) is the following:
  \begin{quote}
  for the entry node $e$, initialize $\textrm{val}[e]$ to the special initial value \\
  for all other nodes $n$, initialize $\textrm{val}[n]$ to BOTTOM \\
  repeat \{ \\
  \hspace*{3ex} for each node $n$, compute $\textrm{val}[n] = {\displaystyle \Combine_{m \rightarrow n \in \textrm{Edges}}} f_{m,n}(\textrm{val}[m])$ \\
  \} until no change
  \end{quote}

  This algorithm finds the least fixed point of the set of equations
  that define the dataflow values for each of the CFG nodes.
  You can think of one iteration of the loop as an application of a
  function {\tt f}.
  Using ``BOT'' to denote the vector of values in which all entries
  are BOTTOM except for the one for the entry node, whose value is the
  special initial value, the loop above computes
  
  \begin{verbatim}
  f(BOT)
  f(f(BOT))
  f(f(f(BOT)))
   ...
  \end{verbatim}

  until a fixed point is found.
  That fixed point is a value $\mbox{\tt v\_0} = \mbox{\tt f}^k(\mbox{\tt BOT})$;
  i.e., $k$ applications of $\mbox{\tt f}$ to  $\mbox{\tt BOT}$.

  \hspace*{1.5ex}
  This question is about doing incremental dataflow analysis: i.e.,
  assume that you have used the above algorithm to solve a dataflow
  problem.  Now someone edits the program, and you want the solution for
  the updated CFG, but you don't want to start over and compute the new
  solution from scratch.

  \hspace*{1.5ex}
  To simplify the problem, we will assume that the shape of the CFG is not
  changed (it has the same nodes and edges as before).
  However, the code in some of the nodes---and therefore the
  dataflow functions associated with those nodes' outgoing edges---has changed.
  This situation means that the function {\tt f} discussed above has
  changed to a new function {\tt f\_new} (which is still monotonic).

  \begin{enumerate}
    \item
      Here is one way to compute the new solution (remember that {\tt v\_0}
      is the solution to the original problem):

      \begin{verbatim}
       v_1 = f_new(v_0) combine v_0
       v_2 = f_new(v_1) combine v_1
       v_3 = f_new(v_2) combine v_2
       ...
       until no change
      \end{verbatim}
    
      Prove (i) that this process is guaranteed to terminate, and
      (ii) that it will produce a safe solution; i.e., one that is
      greater than or equal to the least fixed point of function {\tt f\_new}.
    
    \medskip 
    \item
      While the loop above is guaranteed to terminate and to find a safe solution,
      it is not guaranteed to find the least fixed point of {\tt f\_new}.
      Show that this is true by supplying the following:
      \begin{itemize}
        \item a dataflow problem
        \item a simple CFG
        \item the solution to the dataflow problem (dataflow values for
            each CFG node)
        \item a change to the code at one CFG node
        \item the least solution to the new dataflow problem (the
            solution that would be found by solving the new problem
            from scratch)
        \item the (different) solution that would be found using the incremental
            approach described above in part (a).
      \end{itemize}
  \end{enumerate}

\end{enumerate}


\end{document}
