\documentclass{llncs}

% DAMP'08 parameters
%   page limit: 15 pages
%   deadline  : 26 October

\usepackage{amsmath}
\usepackage{version}
\usepackage{alltt}
\usepackage{url}
\usepackage{graphicx}
\usepackage{epsfig}
\usepackage{pifont}
\usepackage{pgf,pgfarrows,pgfnodes}
% version control
%
\includeversion{DRAFT}
%\excludeversion{DRAFT}

\def\Version{\relax}
\begin{DRAFT}%
  {
    \gdef\Version{%
      \\
      \textbf{--- DRAFT ---}\\[1ex]
      \ignorespaces}
  }
\end{DRAFT}


%
%  pgf stuff
%  ---------
\newcommand{\mpgfrect}[4]{\pgfrect[stroke]{\pgfpoint{#1}{#2}}{\pgfpoint{#3}{#4}}}
\newcommand{\mpgfline}[4]{\pgfline{\pgfpoint{#1}{#2}}{\pgfpoint{#3}{#4}}}

\newcommand{\mpgfbox}[2]{\mpgfrect{#1}{#2}{0.2cm}{0.2cm}}    

% end pgf

\renewcommand{\textfraction}{0.2}
\renewcommand{\topfraction}{0.9}
\renewcommand{\dbltopfraction}{0.9}
\renewcommand{\floatpagefraction}{0.9}
\renewcommand{\dblfloatpagefraction}{0.9}

% Misc macros
%
\newcommand{\code}[1]{\texttt{#1}}
\newcommand{\comm}[1]{\textrm{--- {#1}}}

% TODO's
%
\newcommand{\todo}[2]{\emph{\textbf{Review #1}: #2}}
\newcommand{\TODO}[1]{\emph{\textbf{TODO:} #1}}

\begin{document}

\title{Vectorisation of Functional Languages, for Real!
  \Version}
\author{%
  Manuel M. T. Chakravarty\inst{1}\and
  Roman Leshchinskiy\inst{1}\and
  Simon Peyton Jones\inst{2}\and
  Gabriele Keller\inst{1}
}
\institute{%
  Programming Languages and Systems,
  School of Computer Science and Engineering,
  University of New South Wales,
  \email{\{chak,rl,keller\}@cse.unsw.edu.au}
  \and
  Microsoft Research Ltd,
  Cambridge, England,
  \email{simonpj@microsoft.com}
}  
\maketitle

\begin{abstract}
  The vectorisation of functional programs, also known as the \emph{flattening
    transformation,} has been around for over 17 years, and been extended in
  many ways since its inception.  Nevertheless, we are the first to attempt
  its implementation in a mature, industrial-strength compiler.  In this
  paper, we report on the conceptual challenges of this undertaking and on the
  methods that we devised to meet these challenges.
\end{abstract}

\section{Introduction}
\label{sec:intro}

% Should cite Prins somewhere, too.

The idea of implementing \emph{nested data parallelism}~\cite{blelloch:ppa} in
functional programs by a vectorising program transformation is at least as old
as Blelloch \& Sabot's seminal
work~\cite{blelloch-sabot:compiling-collection,blelloch:diss} on the
\emph{flattening transformation.}  The basic idea has since been generalised
to cover the central features of modern functional languages, such as
algebraic data types, parametric polymorphism, and higher-order
functions~\cite{some-of-our-stuff}.  However, apart from a prototype that
compiled a subset of Paralation Lisp, the only complete implementation of that
transformation was, to the best of our knowledge, the experimental NESL
system~\cite{blelloch-etal:impl-nested-parallel}.  NESL, being an experimental
language, was a severely restricted functional language; for example, it
did not admit user-defined algebraic data types, did not support higher-order
functions, and did not have a module system, neither did it implement separate
compilation.  In other words, an implementation of vectorisation in a mature
compiler for a fully-fledged, general-purpose functional language is still
outstanding. 

We have stepped up to the challenge and are adding vectorisation to the
Glasgow Haskell Compiler (GHC), a mature, industrial-strength compiler, as
part of the \emph{Data Parallel Haskell project}~\cite{chak-etal:DPH}.  As
expected, we met conceptual problems beyond the basic program transformation.
Some of these were of a general nature---i.e., the combination of vectorised
with unvectorised code in the presence of separate compilation---and will
likely be met by any implementation of vectorisation in a fully-fledged
compiler.  In this paper, we describe these problems and our solutions.

In summary, the paper makes the following technical contributions:
%
\begin{itemize}
\item Given separate compilation and higher-order functions, we found that we
  need to be able to freely mix modules compiled with and without
  vectorisation.  Moreover, there is some code that cannot be vectorised
  (e.g., code performing I/O or other side effects), and we wanted to continue
  to use the underlying operating system's standard linker.  We solved this by
  maintaining vectorised and unvectorised code side by side and vectorising
  some expressions only partially (Section~\ref{sec:mixing}).
\item To admit an efficient, unboxed representation of arrays containing
  user-defined data structures, we potentially need to generate a large number
  of array operations for each user-defined data structure.  This would lead
  to code bloat in both generated code and the compiler.  We avoided this by
  devising a generic representation of arrays and array operations using some
  recent additions to the Haskell type system~\cite{at-tf-papers}
  (Section~\ref{sec:generics}).
\end{itemize}
\TODO{Do we want to say anything about closure conversion?  Or anything else?}
%
The implementation of NESL, while surely a challenge in its own right, did not
solve these problems as NESL only provided rudimentary I/O and did neither
support separate compilation, higher-order functions, nor user-defined data
types.

\TODO{Mention that the second contribution generalises to a form of Generic
  Haskell's type-indexed data types for Haskell with type families?}

% Mention the Manticore project



\section{What's hard about implementing vectorisation?}
\label{sec:overview}

\subsection{Vectorisation in a nutshell}

\begin{figure}[tbp]
  \centering
  \begin{minipage}[t]{0.5\linewidth}
\begin{alltt}
module ConvexHull (
  Point(..), within, quickhull
) where

data Point = P Float Float
           deriving (Show, Read)

quickhull :: [:Point:] -> [:Point:]
quickhull [::] = [::]
quickhull ps = 
  let leftmost  = fold1P left  ps
      rightmost = fold1P right ps
  in 
  hsplit (leftmost, rightmost) ps +:+ 
  hsplit (rightmost, leftmost) ps
\end{alltt}
  \end{minipage}%
  \begin{minipage}[t]{0.5\linewidth}
\begin{alltt}
module Main (main)
where
import ConvexHull

area = (P -100 -100, P 100 100)

main :: IO ()
main = 
  do
    contents <- readFile "data"
    let pnts = toP (read contents)
    print $ 
      quickhull [:p | p <- pnts
                , p `within` area:]
\end{alltt}
  \end{minipage}%
  \par\medskip
  NB: definition of \code{left}, \code{right}, \code{hsplit},
  and \code{within} omitted
  \caption{Convex hull example program}
  \label{fig:vect-example}
\end{figure}

\begin{verbatim}
Whatever we have to explain about vectorisation, so that
the rest of the paper makes sense.  That certainly includes
the changed representation of closures and array closures.
At the same time introduce the DPH-specific types and
operations.  Use Figure 1 as an example.
\end{verbatim}

\subsection{What's new?}

The NESL system featured the to date most comprehensive implementation of
vectorisation.  Nevertheless, it was a fairly experimental system due to the
restricted feature set of the language, its lack of support for separate
compilation, and its limited ability to interact with existing libraries and
the underlying operating system.  In contrast, in our implementation in GHC,
we aim to support vectorisation in a mature and already widely used compiler.
So, what is really new in our implementation?
%
\begin{itemize}
\item User-defined algebraic data structures: The only data structures in NESL
  are arrays and tuples.  We layed the conceptual groundwork to use
  vectorisation with user-defined algebraic data
  types~\cite{keller-chak:tflat,chak-keller:more-types}.
\item Higher-order functions: The NESL is a first-order language.  We extended
  vectorisation to apply in the presence of higher-order
  functions~\cite{leshchinskiy-etal:ho-flattening}.
\item Separate compilation: NESL was implemented as a whole program compiler.
  We addressed some of the conceptual issues of supporting vectorisation under
  separate compilation in earlier
  work~\cite{chak-keller:array-fusion,chak-etal:DPH}.  
\item Gang parallelism: We deloved a mapping of vectorised code to
  gang-parallel threads in a manner that supports array
  fusion~\cite{keller-chak:dtypes,chak-etal:DPH}.
\item Partial vectorisation: In the presense of separate compilation,
  side-effecting computations, and interaction with foreign code,
  vectorisation can only be partial.  We report on our approach to partial
  vectorisation in the present paper.
\end{itemize}
%
Our implementation of vectorisation in GHC includes all of the above.  During
the implementation, we found that four issues require special attention: (1)
partial vectorisation, (2) the generation of custom operations on arrays of
user-defined data types, (3) the implementation of higher-order functions by
closure conversion, and (4) the treatment of polymorphism under partial
vectorisation.  We cover the first two in this paper.  The latter two are
beyond the scope of this paper and we plan to tackle them in more detail in
future work.


\section{Mixing vectorised with unvectorised code}
\label{sec:mixing}

In a realistic setting, we will never be able to vectorise an entire program.
For example, we cannot vectorise code that includes side effects, as
vectorisation changes the execution order drastically.  This affects all I/O,
but also computations in Haskell's other state-transformer monads, such as
\code{ST}, and foreign function calls to external C code and OS functionality.
In addition to code that due to its semantics cannot be vectorised, GHC also
support various language extensions, such as rank-$n$ types, which we
currently do not know how to vectorise.  Finally, mixing of vectorised and
unvectorised code is desirable for pragmatic reasons; i.e., a partially
vectorised program may use a library distributed in binary form.

Within a single program, mixing vectorised and unvectorised code happens on
two levels. Firstly, since vectorisation is enabled on a module by module
basis, we must be able to use unvectorised (possibly third-party) modules in
vectorised ones and to link the resulting program using a standard linker.
Secondly, even in a module compiled with vectorisation enabled some
subexpressions might be impossible to vectorise. As an example, consider the
\code{main} function in Figure~\ref{fig:vect-example}.  The standard function
\code{print} performs I/O and cannot be
vectorised.  However, the rest of that statement---i.e., the call to
\code{quickhull} and the array comprehension---are the performance critical
parts of this little application that need to be vectorised.

%That Haskell is a higher-order language complicates the situation further.
%Consider the function \code{mapP :: (a -> b) -> [:a:] -> [:b:]}, which is part
%of our parallel array library.  It maps a function, in parallel, over an
%array.  Any parallel function accepting a functional argument needs to be able
%to deal with both vectorised and unvectorised arguments.  In general, we
%cannot statically determine which kind of argument will be passed.
%\TODO{This needs to be explained better.}

\TODO{Give an overview of the approach}

\subsection{Bindings}

\TODO{Do we want to talk about data type definitions at all?}

A Haskell module is essentially a collection of bindings of the form
%
\begin{verbatim}
f :: t
f = e
\end{verbatim}
%
During vectorisation, we try to vectorise the right-hand side of each such
binding as much as possible, producing
%
\begin{verbatim}
f :: t
f = e'
\end{verbatim}
%
where \verb|e'| is \verb|e| vectorised as much as possible. Ideally, \verb|e|
can be vectorised completely. In this case, we also generate a fully
vectorised version of the original binding:
%
\begin{verbatim}
f_v :: t_v
f_v = e_v
\end{verbatim}
%
Note that in general, \verb|e'| and \verb|e_v| will be different and in
particular, will have different types (\verb|t| and \verb|t_v|, respectively).
We really need both versions even if \verb|e| is fully vectorisable: \verb|f|
and \verb|f_v| will be used by unvectorised and vectorised code,
respectively.

\TODO{Do we want to mention interface files at all?}

For every exported variable \verb|f|, the module interface records whether
it has a vectorised version \verb|f_v|. This information is used when
compiling the module's clients to determine if expressions referencing these
variables can be vectorised.  This mechanism allows us to
transparently mix modules compiled with and without vectorisation. If a
vectorised module imports functions defined in an unvectorised one, the
transformation simply notes that these functions have no vectorised versions
and proceeds accordingly. On the other hand, when compiling a module without
vectorisation the compiler simply ignores the vectorised versions of functions
imported from a vectorised module.

\subsection{Partial vectorisation of expressions}

In order to be able to deal with expressions which can only be partially
vectorised or cannot be vectorised at all, the vectorisation transformation
must be extended. When vectorising an expression $e$ of type $t$, it can now
produces two results:
\begin{itemize}
\item an expression $e'$ of type $t$ which is obtained by vectorising $e$ as
much as possible while retaining its original type and
\item either the fully vectorised expression $e_v$ of type $t_v$ 
\end{itemize}

\TODO{Tricky are lambda bound variables.  If they are polymorphic, we don't
  have conversions for them.  For the moment, we don't try to be clever with
  them.}

\subsection{Conversions}

Whenever we mix vectorised and unvectorised code, we have to convert between
the vectorised and unvectorised representation of terms. In general, the
conversion has to be performed in both directions. Obviously, not all terms
can be converted in this manner; for instance, \code{IO} action do not have a
vectorised representation. In general, whether a term can be converted depends
on its type.
\begin{itemize}
\item For simple types such as \code{Int} the conversion is trivial
(the identity).
\item For more complex types such as functions over simple types it is more
involved but still possible.
\item For types involving \code{IO}, higher-rank types etc., no conversion is
available.
\end{itemize}


\section{Vectorising user-defined, parametrised data structures}
\label{sec:generics}

A central aspect of vectorisation is to transform arrays of lists, trees, and
other algebraic data structures into lists and trees of arrays of unboxed
elementary types (such as machine integers, floats, and so on).  \TODO{A
  figure of an original and transformed tree would be helpful here.}  This
\emph{representation transformation} of arrays is crucial to achieve high
performance, and hence, also needs to be performed for arrays containing
user-defined data types.  In general, we cannot statically predict whether a
user-defined data type will eventually be used in a parallel array---the use
could be in a not-yet compiled module.  Consequently, when a module is
compiled with vectorisation, we need to generate, for every used-defined data
type \code{T}, a new data type \code{T$_{\mathit{arr}}$} that determines the
representation of parallel arrays of \code{T} and replaces all uses of
\code{[:T:]} in vectorised code.

The transformation from \code{T} to \code{T$_{\mathit{arr}}$} itself is fairly
straight-forward and oriented at the product-sum structure of \code{T}.
However, matters are complicated by the folowing: Firstly, all standard array
operations (e.g., \code{mapP}, \code{filterP}, \code{fold1P}, \code{permute},
and so on) need to work on \code{T$_{\mathit{arr}}$}---after all, user code
may apply these functions to values of type \code{[:T:]}---and secondly, the
implementation of these functions is \emph{not parametric}; i.e.,
\code{filterP$_{\mathtt{T}}$ :: (T -> Bool) -> T$_{\mathit{arr}}$ ->
  T$_{\mathit{arr}}$} and \code{filterP$_{\mathtt{S}}$ :: (S -> Bool) ->
  S$_{\mathit{arr}}$ -> S$_{\mathit{arr}}$} cannot use the same code.  Hence,
we have to use a type class to overload the various implementations of the
array operations.  More details on the importance of non-parametric arrays in
vectorisation are
in~\cite{keller-chak:tflat,chak-keller:more-types,chak-etal:DPH}.

Unfortunately, this type class contains a rather large number of functions and,
for complicated user-defined data types, each of these functions gets quite
involved.  Hence, generating an type class instance for every
\code{T$_{\mathit{arr}}$} would significantly complicate the compiler and lead
to serious code bloat in programs that contain many user-defined data types.
Alhtough, the array operations after the representation transformation are no
longer parametric, they still are structurally similar as the generation
of the \code{T$_{\mathit{arr}}$} is guided by the product-sum structure of
original \code{T}.  Hence, we need a facility similar to the
\emph{type-indexed data types} of Generic Haskell~\cite{hinze-etal:tidata}.

GHC has recently acquired support for so-called \emph{data type families} and
\emph{type synonym
  families}~\cite{chakravarty:assoc-data,chakravarty:assoc-syn,schrijvers-etal:type-functions}.
In the following, we will demonstrate that we can implement parallel arrays
generically by using a combination of data families and synonym families.
Although our focus here is on arrays, the approach that we present here goes
beyond arrays---it is applicable to a wide range of type-indexed data types.
The basic idea is that we, firstly, use a type family to map arbitrary
algebraic data types to \emph{representation types} that make the product-sum
structure explicit.  Secondly, we define generic arrays as a data family over
the product-sum representation.  In the following, we start with the latter by
defining generic arrays for explicit product-sum types, and then, generalise
to algebraic data types by introducing a mapping from algebraic types to
explicit product-sum types.

\subsection{Arrays of products, sums, and arrays}

\begin{figure}[tbp]
  \centering
  \begin{minipage}[t]{0.55\linewidth}
\begin{alltt}
class Representable e => PA e where
  data PArray e
  lengthPA    :: PArray e -> Int
  emptyPA     :: PArray e
  replicatePA :: Int -> e -> PArray e
  (!:!)       :: PArray e -> Int -> e
  \comm{and many more}

instance PA Unit where
  data PArray Unit = PA_Unit !Int Unit
  replicatePA n x = PA_Unit n x
  PA_Unit n x !:! i 
    = if i < n then x else error "..."

instance PA Int where
  data PArray Int 
    = PA_Int !Int (UnboxedArray Int)
  replicatePA n x 
    = PA_Int n (replicateUA n x)
  PA_Int _ a !:! i = a!i

instance PA Float where
  \comm{As for \code{Int}s}
\end{alltt}
  \end{minipage}%
  \begin{minipage}[t]{0.45\linewidth}
\begin{alltt}
instance (PA e1, PA e2) =>
    PA (e1 :*: e2) where
  data PArray (e1 :*: e2) 
    = PA_Prod !Int (PArray e1) 
                   (PArray e2)
  replicatePA n (x1 :*: x2) 
    = PA_Prod n (replicatePA n x1) 
                (replicatePA n x2)
  PA_Prod _ a1 a2 !:! i 
    = (a1!:!i) :*: (a2!:!i)

instance (PA e1, PA e2) => 
    PA (e1 :+: e2) where
  data PArray (e1 :+: e2) =
  \comm{Details omitted}

instance PA e => PA (PArray e) where
  data PArray (PArray e) =
  \comm{Details omitted}
\end{alltt}
  \end{minipage}%
  \par\medskip
  NB: We omitted \code{lengthP} and \code{emptyP} in instances and assume
  unboxed arrays, \code{UnboxedArray e}, for primitive types \code{e} with
  operations \code{replicateUA} etc.
  \caption{A non-parametric array representation}
  \label{fig:PA}
\end{figure}
%
We previously described the reasons for using non-parametric arrays as well as
the precise representation for arrays of products, of sums, and of arrays
themselves in much detail
\cite{keller-chak:tflat,chak-keller:more-types,chak-etal:DPH}.
Figure~\ref{fig:PA} sketches the structure of this array representation.  The
type class \code{PA e} has an instance for each type \code{e} that may be
stored in an array.  All basic array operations are methods of \code{PA} and
vary in dependence on the element type.  Moreover, the \emph{array
  representation type} \code{PArray e}, defined as an associated type of the
class, also varies in dependence on the element type \code{e}.  We will
discuss the superclass constraint \code{Representation e} in the next
subsection.

Assuming a unit type, product type, and sum type defined as
%
\begin{quote}
\begin{verbatim}
data Unit = Unit
data (:+:) a b = Inl a | Inr b
data (:*:) a b = a :*: b
\end{verbatim}
\end{quote}
%
we define instances of \code{PA} for these elementary structures.
\code{PArray Unit} only stores the length of the array, \code{PArray
  (e1:*:e2)} is decomposed into a pair of an \code{PArray e1} and an
\code{PArray e2}.  Sums and arrays of arrays, \code{PArray (PArray e)} are
more involved, but can again be defined in terms of simpler arrays.

\subsection{Representation types}

\begin{figure}[tbp]
  \centering
  \begin{minipage}[t]{0.45\linewidth}
\begin{alltt}
class Representable a where
  type Repr a
  toRepr   :: a -> Repr a
  fromRepr :: Repr a -> a
  \comm{default definition:}
  type Repr a = a
  toRepr   = id
  fromRepr = id

\comm{instances using the default}
instance Representable Unit
instance Representable Int
instance Representable Float
instance (Representable a, 
          Representable b) => 
    Representable (a :*: b)
instance (Representable a, 
          Representable b) => 
    Representable (a :+: b)
instance Representable e => 
    Representable (PArray e)
\end{alltt}
  \end{minipage}%
  \begin{minipage}[t]{0.55\linewidth}
\begin{alltt}
instance Representable e => 
    Representable [:e:] where
  type Repr [:e:] = PArray e
  \comm{conversions omitted}

\comm{Representing parallel rose trees}
data RTree e = RTree e [:RTree e:]

instance Representable (RTree e) where
  type Repr (RTree e) = e :*: PArray (RTree e)
  toRepr (RTree x ts) = x :*: toRepr ts
  fromRepr (x :*: ts) = RTree x (fromRepr ts)

instance PA e => PA (RTree e) where
  newtype PArray (RTree e) 
    = PA_RTree (PArray (Repr (RTree e)))
  replicatePA n e 
    = PA_RTree $ replicatePA n (toRepr e)
  (PA_RTree a) !:! i = fromRepr (a!:!i)
\end{alltt}
  \end{minipage}%
  \par\medskip
  NB: We omitted \code{lengthP} and \code{emptyP} in instances and assume
  unboxed arrays, \code{UnboxedArray e}, for primitive types \code{e} with
  operations \code{replicateUA} etc.
  \caption{Generic representation types}
  \label{fig:Repr}
\end{figure}
%
To utilize the non-parametric definition of \code{PArray} for arbitrary
algebraic data types, we introduce a mapping from arbitrary types to
product-sum representation types by a type class \code{Representable a} with
an associated type synonym \code{Repr a}.  Given a type \code{a}, the synonym
\code{Repr a} denotes its product-sum representation type.  The code is given
in Figure~\ref{fig:Repr}.

\code{Unit}, \code{(:*:)}, \code{(:+:)} and basic types, such as \code{Int}
and \code{Float} represent themselves.  Source-level parallel arrays
\code{[:e:]} are represented by non-parametric arrays \code{PArray e}.  

On this basis, we can define representations for algebraic data types.  For
example, the type of points \code{Point}, from Figure~\ref{fig:vect-example}
is represented by a simple product:
%
\begin{quote}
\begin{alltt}
instance (Representable a, Representable b) => 
    Representable (P a b) where
  type Repr (P a b) = a :*: b
  toRepr (P x y)     = x :*: y
  fromRepr (x :*: y) = P x y) 
\end{alltt}
\end{quote}
%
Taking the representation type mapping and the non-parametric array
representation as a data family together, we can use points in parallel arrays:
%
\begin{quote}
\begin{alltt}
instance (PA e1, PA e2) => PA (e1, e2) where
  newtype PArray (e1, e2) = PA_Pair (PArray (Repr (e1, e2)))
  replicatePA n e   = PA_Pair $ replicatePA n (toRepr e)
  (PA_Pair a) !:! i = fromRepr (a!:!i)
\end{alltt}
\end{quote}

A more sophisticated example is that of \emph{parallel rose trees}
%
\begin{quote}
\begin{alltt}
data RTree e = RTree e [:RTree e:]
\end{alltt}
\end{quote}
%
as they are used for a data parallel version of the Barnes-Hut algorithm for
solving the $n$-body problem~\cite{barnes-hut:nbody,keller-chak:tflat}.  This
is a recursive type, where the recursion goes through a parallel array.  It's
representation and array instances are given in Figure~\ref{fig:Repr}.

\TODO{We might want to have a diagram somewhere showing how the two classes
  work together, if there is space.}


\subsection{Deriving instances for algebraic data types}

\begin{verbatim}
* In GHC, we actually perform the transformation on Core.
* Explain what's automated.
* We have hardcoded this for parallel arrays.  We might look
  at providing a general facility for type-indexed adat types
  instead.
\end{verbatim}


\section{Related work}
\label{sec:related}

\begin{verbatim}
* State that the NESL implementation didn't address any of the
  issues that we discussed in this paper, because it did not
  support modules and separate compilation, had only rudimentary
  I/O.  It also did not support higher-order functions.  It required
  that all code gets vectorised and used whole-program compilation
  so that all code could be monomorphised.  It also did not have
  used-defined algebraic data types.
* Do we have any information about similar issues in Manticore
  and/or how much they use vectorisation?
* Does Intel's Ct share any of the problems?
* Generic Haskell and friends
* Derivable Type Classes: only covers type-indexed functions, not types
* Didn't L\"oh and Hinze invent Open Data Types to be able to implement
  TIDT in Haskell?
\end{verbatim}


\section{Conclusion}
\label{sec:conclusion}


\bibliographystyle{splncs}
\bibliography{slpj-author,simon,vect-for-real}

\end{document}

% Local 

%%% Local Variables: 
%%% mode: latex
%%% TeX-master: t
%%% IspellDict: british
%%% End: 
