|
|
|
# The VECTORISE pragma
|
|
|
|
|
|
|
|
|
|
|
|
The vectoriser needs to know about all types and functions whose vectorised variants are directly implemented by the DPH library (instead of generated by the vectoriser), and it needs to know what the vectorised versions are. That is the purpose of the `VECTORISE` pragma (which comes in in number of flavours).
|
|
|
|
|
|
|
|
## The basic VECTORISE pragma for values
|
|
|
|
|
|
|
|
|
|
|
|
Given a function `f`, the vectoriser generates a vectorised version `f_v`, which comprises the original, scalar version of the function and a second version lifted into array space. The lifted version operates on arrays of inputs and produces arrays of results in one parallel computation. The original function name is, then, rebound to use the scalar version referred to by `f_v`. This differs from the original in that it uses vectorised versions for any embedded parallel array computations.
|
|
|
|
|
|
|
|
|
|
|
|
However, if a variable `f` is accompanied by a pragma of the form
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
{-# VECTORISE f = e #-}
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
then the vectoriser defines `f_v = e` and refrains from rebinding `f`. This implies that for `f :: t`, `e`'s type is the `t` vectorised (in particular), `e`'s type uses the array closure type `(:->)` instead of the vanilla function space `(->)`. The vectoriser checks that `e` has the appropriate type.
|
|
|
|
|
|
|
|
**Caveat:** Currently, the type is indeed checked by the vectoriser by comparing Core types. It would be better to perform that check in the type checker to instantiate the type of `f_v` appropriately if it is overly general. At the moment, the vectorised version of `t` and the inferred type of `e` need to exactly match up (including all dictionaries *and their order*).
|
|
|
|
|
|
|
|
## The NOVECTORISE pragma for values
|
|
|
|
|
|
|
|
|
|
|
|
If a variable `f` is accompanied by a pragma
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
{-# NOVECTORISE f #-}
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
then it is ignored by the vectoriser — i.e., no function `f_v` is generated and `f` is left untouched.
|
|
|
|
|
|
|
|
**Caveat:** If `f`'s definition contains bindings that are being floated to the toplevel, those bindings will still be vectorised.
|
|
|
|
|
|
|
|
## The VECTORISE SCALAR pragma for functions
|
|
|
|
|
|
|
|
|
|
|
|
Functions that contain no array computations, especially if they are cheap (such as `(+)`), should not be vectorised, but applied by simply mapping them over an array. This could be achieved by using the `VECTORISE` pragma with an appropriate right-hand side, but leads to repetitive code that we rather like the compiler to generate.
|
|
|
|
|
|
|
|
|
|
|
|
If a unary function `f` is accompanied by a pragma
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
{-# VECTORISE SCALAR f #-}
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
then the vectoriser generates
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
f_v = closure1 f (scalar_map f)
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
and keeps `f` unchanged.
|
|
|
|
|
|
|
|
|
|
|
|
For a binary function, it generates
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
f_v = closure2 f (scalar_zipWith f)
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
for a tertiary function, it generates
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
f_v = closure3 f (scalar_zipWith3 f)
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
and so on. (The variable `f` must have a proper function type.)
|
|
|
|
|
|
|
|
## The basic VECTORISE pragma for type constructors
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
{-# VECTORISE type T = ty #-}
|
|
|
|
```
|
|
|
|
|
|
|
|
## The VECTORISE SCALAR pragma for type constructors
|
|
|
|
|
|
|
|
```wiki
|
|
|
|
{-# VECTORISE SCALAR T #-}
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
The type constructor `T` must be in scope, but may be imported. |