ddc-core-simpl-0.4.1.2: DDC/Core/Transform/Boxing.hs
-- | Manage representation of numeric values in a module.
--
-- We use three seprate versions of each numeric type.
-- Nat# Numeric index type.
-- B# Nat# Boxed representation type.
-- U# Nat# Unboxed representation type.
--
-- A numeric index type is the type of pure values like 23#, where "pure value"
-- means the mathematical value, free from any considerations about how that
-- might be represented at runtime in the physical machine.
--
-- The Boxed and Unboxed representation types commit to a specific runtime
-- representation, and have implications for runtime performance and space
-- usage of the compiled program.
--
-- The boxing transform takes an input program using just pure values and
-- numeric index types, and refines it to a program that commits to particular
-- representations of those values. In particular, we commit to a particular
-- representation for function arguments and results, which makes the program
-- adhere to a function calling convention that follow-on transformations
-- to lower level languages (like Core Salt) can deal with.
--
-- This Boxing transform should do just enough to make the code well-formed
-- with respect to runtime representation. Demand-driven optimisations like
-- local unboxing should be done in follow-on transformations.
--
-- We make the following representation commitments, so that the default
-- representation is boxed.
--
-- Literal values are wrapped into their boxed representation:
-- 23#
-- => convert# [B# Nat#] [Nat#] 23#
--
-- Use unboxed versions of primitive operators:
-- add# [Nat#] x y
-- => convert# [B# Nat#] [U# Nat#]
-- (add# [U# Nat#] (convert# [U# Nat#] [B# Nat#] x)
-- (convert# [U# Nat#] [B# Nat#] y))
--
-- Case scrutinees are unwrapped when matching against literal patterns:
-- case x of { 0# -> ... }
-- => case convert [B# Nat#] [Nat#] x of { 0# -> ... }
--
-- After performing this transformation the program is said
-- to "use representational types", or be in "representational form".
--
-- [Note: Boxing and Partial Application]
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- Unlike in Haskell, we do not allow explictly unboxed types in the source
-- program because we don't want to deal with partial applications of
-- functions to unboxed values. With our current setup we always have a version
-- of each function that accepts boxed values, so we never need to do generic
-- application involving unboxed values. Fast-path function specialisations
-- that take unboxed parameters should be created separately, and not replace
-- the existing slow-path, fully boxed version. Taking this approach is possible
-- in a strict language because the boxed and unboxed values have the same
-- semantic meaning. Boxing of values does not imply "lifting" of the associated
-- semantic domain.
--
module DDC.Core.Transform.Boxing
( Rep (..)
, Config (..)
, Boxing (..))
where
import DDC.Core.Compounds
import DDC.Core.Module
import DDC.Core.Exp
import DDC.Type.Transform.Instantiate
import DDC.Type.DataDef
import Control.Monad
---------------------------------------------------------------------------------------------------
-- | Representation of the values of some type.
data Rep
-- | Values of this type cannot be directly represented in the target
-- language. We need to use a boxed or unboxed representation instead.
= RepNone
-- | Type is represented in boxed form,
-- and thus can instantiate polymorphic types.
| RepBoxed
-- | Type is represented in unboxed form,
-- and thus cannot instantiate polymorphic types.
| RepUnboxed
deriving (Eq, Ord, Show)
data Config a n
= Config
{ -- | Values of this type needs boxing to make the program
-- representational. This will only be passed types of kind Data.
configIsValueIndexType :: Type n -> Bool
-- | Check if this is a boxed representation type.
, configIsBoxedType :: Type n -> Bool
-- | Check if this is an unboxed representation type.
, configIsUnboxedType :: Type n -> Bool
-- | Get the boxed version of some data type, if any.
-- This will only be passed types where typeNeedsBoxing returns True.
, configBoxedOfIndexType :: Type n -> Maybe (Type n)
-- | Get the unboxed version of some data type, if any.
-- This will only be passed types where typeNeedsBoxing returns True.
, configUnboxedOfIndexType :: Type n -> Maybe (Type n)
-- | Take the index type from a boxed type, if it is one.
, configIndexTypeOfBoxed :: Type n -> Maybe (Type n)
-- | Take the index type from an unboxed type, if it is one.
, configIndexTypeOfUnboxed :: Type n -> Maybe (Type n)
-- | Take the type of a literal name, if there is one.
, configValueTypeOfLitName :: n -> Maybe (Type n)
-- | Take the type of a primitive operator name, if it is one.
-- The primops can be polytypic, but must have prenex rank-1 types.
, configValueTypeOfPrimOpName :: n -> Maybe (Type n)
-- | Take the type of a foreign function name, if it is one.
-- The function can be polymorphic, but must have a prenex rank-1 type.
, configValueTypeOfForeignName :: n -> Maybe (Type n)
-- | Check if the primop with this name works on unboxed values
-- directly. Operators where this function returns False are assumed
-- to take boxed values for every argument.
, configNameIsUnboxedOp :: n -> Bool
-- | Wrap a value of the given index type.
-- This will only be passed types where typeNeedsBoxing returns True.
, configBoxedOfValue :: a -> Exp a n -> Type n -> Maybe (Exp a n)
-- | Unwrap a boxed value of the given index type.
-- This will only be passed types where typeNeedsBoxing returns True.
, configValueOfBoxed :: a -> Exp a n -> Type n -> Maybe (Exp a n)
-- | Box an unboxed value of the given index type.
-- This will only be passed types where typeNeedsBoxing returns True.
, configBoxedOfUnboxed :: a -> Exp a n -> Type n -> Maybe (Exp a n)
-- | Unbox a boxed value of the given index type.
-- This will only be passed types where typeNeedsBoxing returns True.
, configUnboxedOfBoxed :: a -> Exp a n -> Type n -> Maybe (Exp a n) }
---------------------------------------------------------------------------------------------------
class Boxing (c :: * -> * -> *) where
-- | Rewrite a module to use explitit boxed and unboxed types.
boxing :: (Show n, Show a, Ord n)
=> Config a n
-> c a n
-> c a n
-- Module -----------------------------------------------------------------------------------------
instance Boxing Module where
boxing config mm
= let
-- Handle boxing in the types of exported values.
exportValues'
= map (boxingExportValue config) $ moduleExportValues mm
-- Handle boxing in the types of imported values.
importValues'
= map (boxingImportValue config) $ moduleImportValues mm
-- Add locally imported foreign functions to the foreign function detector.
-- We want the original type here, before it has been passed through
-- the boxing transform.
typeOfForeignName n
-- The provided config already says this is foreign.
| Just t <- configValueTypeOfForeignName config n
= Just t
-- This is a locally imported C function.
| Just (ImportSourceSea _ t)
<- lookup n (moduleImportValues mm)
= Just t
| otherwise
= Nothing
-- Use our new foreign function detector in the config.
config'
= config
{ configValueTypeOfForeignName = typeOfForeignName }
-- Do the boxing transform.
in mm { moduleBody = boxing config' (moduleBody mm)
, moduleExportValues = exportValues'
, moduleImportValues = importValues'
, moduleDataDefsLocal = map (boxingDataDef config') (moduleDataDefsLocal mm) }
-- | Manage boxing in the type of an exported value.
boxingExportValue
:: Config a n
-> (n, ExportSource n)
-> (n, ExportSource n)
boxingExportValue config (n, esrc)
= case esrc of
ExportSourceLocal n' t
-> (n, ExportSourceLocal n' (boxingT config t))
ExportSourceLocalNoType{}
-> (n, esrc)
-- | Manage boxing in the type of an imported value.
boxingImportValue
:: Config a n
-> (n, ImportSource n)
-> (n, ImportSource n)
boxingImportValue config (n, isrc)
= case isrc of
-- This shouldn't happen for values, but just pass it through.
ImportSourceAbstract _
-> (n, isrc)
-- Function imported from a DDC compiled module.
ImportSourceModule mn n' t
-> (n, ImportSourceModule mn n' (boxingT config t))
-- Value imported using the standard C calling convention.
ImportSourceSea str t
-> (n, ImportSourceSea str (boxingSeaT config t))
-- Exp --------------------------------------------------------------------------------------------
instance Boxing Exp where
boxing config xx
= let down = boxing config
in case xx of
-- Convert literals to their boxed representations.
XCon a dc
| Just dcn <- takeNameOfDaCon dc
, Just tLit <- configValueTypeOfLitName config dcn
, configIsValueIndexType config tLit
, Just xx' <- configBoxedOfValue config a xx tLit
-> xx'
-- When applying a primop that works on unboxed values,
-- unbox its arguments and then rebox the result.
XApp a x1 x2
-- Split the application of a primop into its name and arguments.
-- The arguments here include type arguments as well.
| Just (xFn, tPrim, xsArgsAll)
<- splitUnboxedOpApp config xx
-> let
-- Split off the type arguments.
(asArgs, tsArgs) = unzip $ [(a', t) | XType a' t <- xsArgsAll]
-- For each type argument, if we know how to create the unboxed version
-- then do so. If this is wrong then the type checker will catch it later.
getTypeUnboxed t
| Just t' <- configUnboxedOfIndexType config t
= t'
| otherwise = t
tsArgsUnboxed = map getTypeUnboxed tsArgs
-- Instantiate the type to work out which arguments need to be unboxed,
-- and which we can leave as-is.
Just tPrimInstUnboxed = instantiateTs tPrim tsArgsUnboxed
(tsArgsInstUnboxed, tResultInstUnboxed)
= takeTFunArgResult tPrimInstUnboxed
-- Unboxing arguments to the function.
xsArgs = drop (length tsArgs) xsArgsAll
in if -- We must end up with a type of each argument.
-- If not then the primop is partially applied or something else is wrong.
-- The Tetra to Salt conversion will give a proper error message
-- if the primop is indeed partially applied.
not (length xsArgs == length tsArgsInstUnboxed)
then XApp a (down x1) (down x2)
-- We got a type for each argument, so the primop is fully applied
-- and we can do the boxing/unboxing transform.
else let xsArgs'
= [ unboxExp config a tArgInst (down xArg)
| xArg <- xsArgs
| tArgInst <- tsArgsInstUnboxed ]
in boxExp config a tResultInstUnboxed
$ xApps a xFn ( [XType a' t | t <- tsArgsUnboxed
| a' <- asArgs]
++ xsArgs')
-- Unrap scrutinees when matching against literal patterns.
XCase a xScrut alts
| p : _ <- [ p | AAlt (PData p@DaConPrim{} []) _ <- alts]
, Just tLit <- configValueTypeOfLitName config (daConName p)
, configIsValueIndexType config tLit
, Just xScrut' <- configValueOfBoxed config a (down xScrut) tLit
-> XCase a xScrut' (map down alts)
-- Boilerplate
XVar{} -> xx
XCon{} -> xx
XLAM a b x -> XLAM a b (down x)
XLam a b x -> XLam a (boxingB config b) (down x)
XApp a x1 x2 -> XApp a (down x1) (down x2)
XLet a lts x -> XLet a (down lts) (down x)
XCase a x alts -> XCase a (down x) (map down alts)
XCast a c x -> XCast a c (down x)
XType a t -> XType a (boxingT config t)
XWitness{} -> xx
-- | Box an expression that produces a value.
boxExp :: Config a n -> a -> Type n -> Exp a n -> Exp a n
boxExp config a t xx
| configIsValueIndexType config t
, Just x' <- configBoxedOfUnboxed config a xx t
= x'
| configIsUnboxedType config t
, Just tIdx <- configIndexTypeOfUnboxed config t
, Just x' <- configBoxedOfUnboxed config a xx tIdx
= x'
| otherwise
= xx
-- | Unbox an expression that produces a boxed value.
unboxExp :: Config a n -> a -> Type n -> Exp a n -> Exp a n
unboxExp config a t xx
| configIsValueIndexType config t
, Just x' <- configUnboxedOfBoxed config a xx t
= x'
| configIsUnboxedType config t
, Just tIdx <- configIndexTypeOfUnboxed config t
, Just x' <- configUnboxedOfBoxed config a xx tIdx
= x'
| otherwise
= xx
-- | If this is an application of some primitive operator or foreign function that
-- works on unboxed values then split it into the function and arguments.
--
-- The arguments returned include type arguments as well.
splitUnboxedOpApp
:: Config a n
-> Exp a n
-> Maybe (Exp a n, Type n, [Exp a n])
splitUnboxedOpApp config xx
= case xx of
XApp{}
| Just (n, xsArgsAll) <- takeXPrimApps xx
, Just (xFn, _) <- takeXApps xx
, configNameIsUnboxedOp config n
, Just tPrim <- configValueTypeOfPrimOpName config n
-> Just (xFn, tPrim, xsArgsAll)
XApp{}
| Just (xFn@(XVar _ (UName n)), xsArgsAll)
<- takeXApps xx
, Just tForeign <- configValueTypeOfForeignName config n
-> Just (xFn, tForeign, xsArgsAll)
_ -> Nothing
-- Lets -------------------------------------------------------------------------------------------
instance Boxing Lets where
boxing config lts
= let down = boxing config
in case lts of
LLet b x
-> let b' = boxingB config b
x' = down x
in LLet b' x'
LRec bxs
-> let bxs' = [(boxingB config b, down x)
| (b, x) <- bxs]
in LRec bxs'
LPrivate{} -> lts
LWithRegion{} -> lts
-- Alt --------------------------------------------------------------------------------------------
instance Boxing Alt where
boxing config alt
= case alt of
AAlt PDefault x
-> AAlt PDefault (boxing config x)
AAlt (PData dc bs) x
-> AAlt (PData dc (map (boxingB config) bs)) (boxing config x)
---------------------------------------------------------------------------------------------------
-- | Manage boxing in a Bind.
boxingB :: Config a n -> Bind n -> Bind n
boxingB config bb
= case bb of
BAnon t -> BAnon (boxingT config t)
BName n t -> BName n (boxingT config t)
BNone t -> BNone (boxingT config t)
-- | Manage boxing in a Type.
boxingT :: Config a n -> Type n -> Type n
boxingT config tt
| configIsValueIndexType config tt
, Just tResult <- configBoxedOfIndexType config tt
= tResult
| otherwise
= let down = boxingT config
in case tt of
TVar{} -> tt
TCon{} -> tt
TForall b t -> TForall b (down t)
TApp t1 t2 -> TApp (down t1) (down t2)
TSum{} -> tt
-- | Manage boxing in the type of a C value.
boxingSeaT :: Config a n -> Type n -> Type n
boxingSeaT config tt
| configIsValueIndexType config tt
, Just tResult <- configUnboxedOfIndexType config tt
= tResult
| otherwise
= let down = boxingSeaT config
in case tt of
TVar{} -> tt
TCon{} -> tt
TForall b t -> TForall b (down t)
TApp t1 t2 -> TApp (down t1) (down t2)
TSum{} -> tt
-- | Manage boxing in a data type definition.
boxingDataDef :: Config a n -> DataDef n -> DataDef n
boxingDataDef config def@DataDef{}
= def { dataDefCtors = liftM (map (boxingDataCtor config)) (dataDefCtors def) }
-- | Manage boxing in a data constructor definition.
boxingDataCtor :: Config a n -> DataCtor n -> DataCtor n
boxingDataCtor config ctor@DataCtor{}
= ctor
{ dataCtorFieldTypes = map (boxingT config) (dataCtorFieldTypes ctor)
, dataCtorResultType = boxingT config (dataCtorResultType ctor) }