51 | | Vectorised functions use an explicit closure representation: |
52 | | {{{ |
53 | | data a :-> b |
54 | | = forall e. |
55 | | Cls { clsFun :: !(e -> a -> b) |
56 | | , clsAFun :: !(PArr e -> PArr a -> PArr b) |
57 | | , clsEnv :: e |
58 | | } |
59 | | }}} |
60 | | with basic closure construction and application as |
61 | | {{{ |
62 | | lam :: (a -> b) -> (a :-> b) |
63 | | lam f = Cls (const f) (const (mapP f)) () |
| 51 | For the moment, I am assuming that we only need to closure-convert lifted code, but not scalar code. The rationale is that the closure representation is only needed to pack, merge, etc. array closures, which represent arrays of functions. |
65 | | ($:) :: (a :-> b) -> a -> b |
66 | | (Cls f _ e) $: x = f e x |
67 | | }}} |
68 | | |
69 | | ---- |
70 | | '''TODO:''' Can't we actually omit the explicit closure representation for ''scalar'' closures? We need it for array closures, so that we can, e.g., pack them, but I think we don't really need it for scalar closures given that we combine closure-conversion and vectorisation into one pass. If that is correct, we can represent vectorised functions as |
71 | | {{{ |
72 | | data a ->> b = Fun { fun :: !( a -> b) |
73 | | , afun :: !(PArr a -> PArr b) |
74 | | } |
75 | | |
76 | | vect :: (a -> b) -> (a ->> b) |
77 | | vect f = Fun f (mapP f) |
78 | | }}} |
79 | | |
80 | | Hmm, thinking about this, a data type will get us into trouble with unboxed types, but we also can't use a type synonym, as we can have partial applications of function arrows. So, we have to split this up. |
81 | | |
82 | | A data type to combine the scalar and lifted version of a function: |
| 53 | A data type to combine the scalar and lifted version of a function (i.e., a glorified strict pair): |
95 | | ---- |
| 66 | |
| 67 | Three questions may arise at this point: |
| 68 | * Why don't we combine `(:||)` and `(:->)` into one data type? Answer: This won't work for functions involving unboxed types; in particular, we cannot write `Int# :-> Int#` (this would lead to a kind error). |
| 69 | * Do we really define the `PA` instances over the unvectorised types? Answer: As `(->)` may be partially applied in the original program, we can have partial applications of `(->_v)`, and hence, of `(:->)`. These may then be used to instantiate type constructor variables in converted types, where the type arguments will be vectorised. |
| 70 | * Why don't we closure convert scalar code (to keeps things more orthogonal)? Answers: (1) I think the transformations are simpler like this, as only lifting has to perform on-the-fly closure conversion; (2) the generated Core code is smaller; (3) flattening and the ndp library place considerable stress on the simplifier, so reducing this a bit seems a good strategy; and (4) I am far from sure that the current approach to handling functions manipulating unboxed types would work if we closure-converted scalar code. |
| 106 | |
| 107 | |
| 108 | ---- |
| 109 | |
| 110 | === Old Material === |
| 111 | |
| 112 | ==== Vectorised functions ==== |
| 113 | |
| 114 | Vectorised functions use an explicit closure representation: |
| 115 | {{{ |
| 116 | data a :-> b |
| 117 | = forall e. |
| 118 | Cls { clsFun :: !(e -> a -> b) |
| 119 | , clsAFun :: !(PArr e -> PArr a -> PArr b) |
| 120 | , clsEnv :: e |
| 121 | } |
| 122 | }}} |
| 123 | with basic closure construction and application as |
| 124 | {{{ |
| 125 | lam :: (a -> b) -> (a :-> b) |
| 126 | lam f = Cls (const f) (const (mapP f)) () |
| 127 | |
| 128 | ($:) :: (a :-> b) -> a -> b |
| 129 | (Cls f _ e) $: x = f e x |
| 130 | }}} |