|Version 6 (modified by chak, 8 years ago) (diff)|
The transformation of types includes both closure conversion and the pairing of scalar with lifted computations.
Unboxed types and functions defined in GHC.Prim need to be treated specially during vectorisation. This is as we cannot have PA instances for unboxed types and the transformation needs to know which functions from GHC.Prim can be safely parallelised (e.g., its fine to run many +# in parallel, whereas this is not really advisable for calls to side-effecting RTS functions). Indeed, we might regard unboxed types and functions from GHC.Prim as the place where we make the transition from implementing vectorisation decisions in package ndp to hard-coding them into the compiler. It is probably a good idea to eventually move as much as possible of the hardcoded information into primops.txt.pp, but for the moment, we simply hardcode everything in the modules in vectorise/.
To treat unboxed type properly, we cannot simply use the type constructor PArr wherever we need a flattened array; instead, we define a type translation
Int#^ = UArr Int Float#^ = UArr Float Double#^ = UArr Double <and so on for other unboxed types> t^ = PArr t*
We need to represent functions whose argument and/or result type are unboxed different from functions over boxed types. The reason is the non-standard kinding rule implemented in GHC for (->), which allows that the two argument type variables are instantiated to unboxed values iff the application of (->) is saturated. We can't defined a second type constructor with that property unless we extend the TypeRep.Type representation. We also can't simply use a type synonym for a vectorised type function constructor, because we must be able to partially apply it.
- Be careful that VFun (t1* -> t2*) and t1* -> t2* includes PArr t1 and PArr t2*; so, we can only use them if we have PA instances for these types.
The type transformation rules achieve two goals: (1) they replace original type constructors and variables by their vectorised variants, where those are available, and (2) they alter the representation of functions:
T* = T_V , if T_V exists = T , otherwise a* = a_v (t1 -> t2)* | isUbxFun (t1->t2) = (t1* -> t2*) :|| (t1^ -> t2^) | otherwise = t1* :-> t2* (t1 t2)* = t1* t2* (forall a.t)* = forall a_v.t*
We need to distinguish between saturated function space applications involving unboxed types and those that don't, as we need to remain to be compatible with (->_v) = (:->).
T* = T_V , if T_V exists = T , otherwise a* = a_v (t1 -> t2)* = ( t1* -> t2*, , if kindOf t1 == # [:t1* -> t2*:]) or kindOf t2 == # = ( t1* :-> t2*, , otherwise [:t1* :-> t2*:]) (t1 t2)* = t1* t2* (forall a.t)* = forall a_v.t*