2 Commits

Author SHA1 Message Date
John J. Camilleri
3e2673de3b Use modify instead of insert: results mildly better but not significantly 2021-03-16 16:45:57 +01:00
John J. Camilleri
6c6a201d96 Introduce state with Map for caching compilation, but results are worse 2021-03-12 13:39:56 +01:00
5 changed files with 419 additions and 236 deletions

View File

@@ -15,14 +15,14 @@ import GF.Infra.UseIO (IOE)
import GF.Text.Pretty (pp, render) import GF.Text.Pretty (pp, render)
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Control.Monad (when, unless, forM, forM_) import Control.Monad (when, forM, forM_)
import qualified Control.Monad.State as CMS import qualified Control.Monad.State.Strict as CMS
import Data.Either (lefts, rights)
import qualified Data.IntMap as IntMap
import Data.List (elemIndex) import Data.List (elemIndex)
import qualified Data.List as L import qualified Data.List as L
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import Data.Maybe (fromJust, isJust) import Data.Maybe (fromJust, isJust)
import Data.Text (Text)
import qualified Data.Text as T
import System.Environment (lookupEnv) import System.Environment (lookupEnv)
import System.FilePath ((</>), (<.>)) import System.FilePath ((</>), (<.>))
import Text.Printf (printf) import Text.Printf (printf)
@@ -37,7 +37,7 @@ mkCanon2lpgf opts gr am = do
ppCanonical debugDir canon ppCanonical debugDir canon
dumpCanonical debugDir canon dumpCanonical debugDir canon
(an,abs) <- mkAbstract ab (an,abs) <- mkAbstract ab
cncs <- mapM (mkConcrete debug) cncs cncs <- mapM (mkConcrete debug ab) cncs
let lpgf = LPGF { let lpgf = LPGF {
L.absname = an, L.absname = an,
L.abstract = abs, L.abstract = abs,
@@ -48,247 +48,268 @@ mkCanon2lpgf opts gr am = do
where where
canon@(C.Grammar ab cncs) = grammar2canonical opts am gr canon@(C.Grammar ab cncs) = grammar2canonical opts am gr
mkAbstract :: (ErrorMonad err) => C.Abstract -> err (CId, L.Abstract) mkAbstract :: (ErrorMonad err) => C.Abstract -> err (CId, L.Abstract)
mkAbstract (C.Abstract modId flags cats funs) = return (mdi2i modId, L.Abstract {}) mkAbstract (C.Abstract modId flags cats funs) = return (mdi2i modId, L.Abstract {})
mkConcrete :: (ErrorMonad err) => Bool -> C.Concrete -> err (CId, L.Concrete) mkConcrete :: (ErrorMonad err) => Bool -> C.Abstract -> C.Concrete -> err (CId, L.Concrete)
mkConcrete debug (C.Concrete modId absModId flags params' lincats lindefs) = do mkConcrete debug (C.Abstract _ _ _ funs) (C.Concrete modId absModId flags params0 lincats lindefs0) = do
let let
(C.Abstract _ _ _ funs) = ab -- Some transformations on canonical grammar
params = inlineParamAliases params'
-- Builds maps for lookups params :: [C.ParamDef]
params = inlineParamAliases params0
paramValueMap :: Map.Map C.ParamId C.ParamDef -- constructor -> definition lindefs :: [C.LinDef]
paramValueMap = Map.fromList [ (v,d) | d@(C.ParamDef _ vs) <- params, (C.Param v _) <- vs ] lindefs =
[ C.LinDef funId varIds linValue'
| (C.LinDef funId varIds linValue) <- lindefs0
, let Right linType = lookupLinType funId
, let linValue' = cleanupRecordFields linValue linType
]
lincatMap :: Map.Map C.CatId C.LincatDef -- Filter out record fields from definitions which don't appear in lincat.
lincatMap = Map.fromList [ (cid,d) | d@(C.LincatDef cid _) <- lincats ] -- Workaround for https://github.com/GrammaticalFramework/gf-core/issues/101
cleanupRecordFields :: C.LinValue -> C.LinType -> C.LinValue
funMap :: Map.Map C.FunId C.FunDef cleanupRecordFields (C.RecordValue rrvs) (C.RecordType rrs) =
funMap = Map.fromList [ (fid,d) | d@(C.FunDef fid _) <- funs ] let defnFields = Map.fromList [ (lid, lt) | (C.RecordRow lid lt) <- rrs ]
in C.RecordValue
-- | Lookup paramdef, providing dummy fallback when not found [ C.RecordRow lid lv'
-- Workaround for https://github.com/GrammaticalFramework/gf-core/issues/100 | C.RecordRow lid lv <- rrvs
lookupParamDef :: C.ParamId -> Either String C.ParamDef , Map.member lid defnFields
lookupParamDef pid = case Map.lookup pid paramValueMap of , let Just lt = Map.lookup lid defnFields
Just d -> Right d , let lv' = cleanupRecordFields lv lt
Nothing ->
-- Left $ printf "Cannot find param definition: %s" (show pid)
Right $ C.ParamDef (C.ParamId (C.Unqual "DUMMY")) [C.Param pid []]
-- | Lookup lintype for a function
lookupLinType :: C.FunId -> Either String C.LinType
lookupLinType funId = do
fun <- m2e (printf "Cannot find type for: %s" (show funId)) (Map.lookup funId funMap)
let (C.FunDef _ (C.Type _ (C.TypeApp catId _))) = fun
lincat <- m2e (printf "Cannot find lincat for: %s" (show catId)) (Map.lookup catId lincatMap)
let (C.LincatDef _ lt) = lincat
return lt
-- | Lookup lintype for a function's argument
lookupLinTypeArg :: C.FunId -> Int -> Either String C.LinType
lookupLinTypeArg funId argIx = do
fun <- m2e (printf "Cannot find type for: %s" (show funId)) (Map.lookup funId funMap)
let (C.FunDef _ (C.Type args _)) = fun
let (C.TypeBinding _ (C.Type _ (C.TypeApp catId _))) = args !! argIx
lincat <- m2e (printf "Cannot find lincat for: %s" (show catId)) (Map.lookup catId lincatMap)
let (C.LincatDef _ lt) = lincat
return lt
-- Filter out record fields from definitions which don't appear in lincat.
-- Workaround for https://github.com/GrammaticalFramework/gf-core/issues/101
cleanupRecordFields :: C.LinValue -> C.LinType -> C.LinValue
cleanupRecordFields (C.RecordValue rrvs) (C.RecordType rrs) =
let defnFields = Map.fromList [ (lid, lt) | (C.RecordRow lid lt) <- rrs ]
in C.RecordValue
[ C.RecordRow lid lv'
| C.RecordRow lid lv <- rrvs
, Map.member lid defnFields
, let Just lt = Map.lookup lid defnFields
, let lv' = cleanupRecordFields lv lt
]
cleanupRecordFields lv _ = lv
lindefs' =
[ C.LinDef funId varIds linValue'
| (C.LinDef funId varIds linValue) <- lindefs
, let Right linType = lookupLinType funId
, let linValue' = cleanupRecordFields linValue linType
] ]
es = map mkLin lindefs' cleanupRecordFields lv _ = lv
lins = Map.fromList $ rights es
-- | Main code generation function -- Builds maps for lookups
mkLin :: C.LinDef -> Either String (CId, L.LinFun)
mkLin (C.LinDef funId varIds linValue) = do
-- when debug $ trace funId
(lf, _) <- val2lin linValue
return (fi2i funId, lf)
where
val2lin :: C.LinValue -> Either String (L.LinFun, Maybe C.LinType)
val2lin lv = case lv of
C.ConcatValue v1 v2 -> do paramValueMap :: Map.Map C.ParamId C.ParamDef -- constructor -> definition
(v1',t1) <- val2lin v1 paramValueMap = Map.fromList [ (v,d) | d@(C.ParamDef _ vs) <- params, (C.Param v _) <- vs ]
(v2',t2) <- val2lin v2
return (L.Concat v1' v2', t1 <|> t2) -- t1 else t2
C.LiteralValue ll -> case ll of lincatMap :: Map.Map C.CatId C.LincatDef
C.FloatConstant f -> return (L.Token $ show f, Just C.FloatType) lincatMap = Map.fromList [ (cid,d) | d@(C.LincatDef cid _) <- lincats ]
C.IntConstant i -> return (L.Token $ show i, Just C.IntType)
C.StrConstant s -> return (L.Token s, Just C.StrType)
C.ErrorValue err -> return (L.Error err, Nothing) funMap :: Map.Map C.FunId C.FunDef
funMap = Map.fromList [ (fid,d) | d@(C.FunDef fid _) <- funs ]
C.ParamConstant (C.Param pid lvs) -> do -- | Lookup paramdef, providing dummy fallback when not found
let -- Workaround for https://github.com/GrammaticalFramework/gf-core/issues/100
collectProjections :: C.LinValue -> Either String [L.LinFun] lookupParamDef :: C.ParamId -> Either String C.ParamDef
collectProjections (C.ParamConstant (C.Param pid lvs)) = do lookupParamDef pid = case Map.lookup pid paramValueMap of
def <- lookupParamDef pid Just d -> Right d
let (C.ParamDef tpid defpids) = def Nothing ->
pidIx <- eitherElemIndex pid [ p | C.Param p _ <- defpids ] -- Left $ printf "Cannot find param definition: %s" (show pid)
rest <- mapM collectProjections lvs Right $ C.ParamDef (C.ParamId (C.Unqual "DUMMY")) [C.Param pid []]
return $ L.Ix (pidIx+1) : concat rest
collectProjections lv = do
(lf,_) <- val2lin lv
return [lf]
lfs <- collectProjections lv
let term = L.Tuple lfs
def <- lookupParamDef pid
let (C.ParamDef tpid _) = def
return (term, Just $ C.ParamType (C.ParamTypeId tpid))
C.PredefValue (C.PredefId pid) -> case pid of -- | Lookup lintype for a function
"BIND" -> return (L.Bind, Nothing) lookupLinType :: C.FunId -> Either String C.LinType
"SOFT_BIND" -> return (L.Bind, Nothing) lookupLinType funId = do
"SOFT_SPACE" -> return (L.Space, Nothing) fun <- m2e (printf "Cannot find type for: %s" (show funId)) (Map.lookup funId funMap)
"CAPIT" -> return (L.Capit, Nothing) let (C.FunDef _ (C.Type _ (C.TypeApp catId _))) = fun
"ALL_CAPIT" -> return (L.AllCapit, Nothing) lincat <- m2e (printf "Cannot find lincat for: %s" (show catId)) (Map.lookup catId lincatMap)
_ -> Left $ printf "Unknown predef function: %s" pid let (C.LincatDef _ lt) = lincat
return lt
C.RecordValue rrvs -> do -- | Lookup lintype for a function's argument
let rrvs' = sortRecordRows rrvs lookupLinTypeArg :: C.FunId -> Int -> Either String C.LinType
ts <- sequence [ val2lin lv | C.RecordRow lid lv <- rrvs' ] lookupLinTypeArg funId argIx = do
return (L.Tuple (map fst ts), Just $ C.RecordType [ C.RecordRow lid lt | (C.RecordRow lid _, (_, Just lt)) <- zip rrvs' ts]) fun <- m2e (printf "Cannot find type for: %s" (show funId)) (Map.lookup funId funMap)
let (C.FunDef _ (C.Type args _)) = fun
let (C.TypeBinding _ (C.Type _ (C.TypeApp catId _))) = args !! argIx
lincat <- m2e (printf "Cannot find lincat for: %s" (show catId)) (Map.lookup catId lincatMap)
let (C.LincatDef _ lt) = lincat
return lt
C.TableValue lt trvs -> do -- Code generation
-- group the rows by "left-most" value
let
groupRow :: C.TableRowValue -> C.TableRowValue -> Bool
groupRow (C.TableRow p1 _) (C.TableRow p2 _) = groupPattern p1 p2
groupPattern :: C.LinPattern -> C.LinPattern -> Bool -- | Main code generation function
groupPattern p1 p2 = case (p1,p2) of mkLin :: C.LinDef -> CodeGen (CId, L.LinFun)
(C.ParamPattern (C.Param pid1 _), C.ParamPattern (C.Param pid2 _)) -> pid1 == pid2 -- compare only constructors mkLin (C.LinDef funId varIds linValue) = do
(C.RecordPattern (C.RecordRow lid1 patt1:_), C.RecordPattern (C.RecordRow lid2 patt2:_)) -> groupPattern patt1 patt2 -- lid1 == lid2 necessarily -- when debug $ trace funId
_ -> error $ printf "Mismatched patterns in grouping:\n%s\n%s" (show p1) (show p2) (lf, _) <- val2lin' linValue --skip memoisation at top level
return (fi2i funId, lf)
where
val2lin :: C.LinValue -> CodeGen (L.LinFun, Maybe C.LinType)
val2lin lv@(C.TableValue _ _) = do
-- val2lin lv@(C.ParamConstant _) = do
m <- CMS.get
case Map.lookup lv m of
Just r -> return r
Nothing -> do
r <- val2lin' lv
CMS.modify (Map.insert lv r)
return r
val2lin lv = val2lin' lv
grps :: [[C.TableRowValue]] val2lin' :: C.LinValue -> CodeGen (L.LinFun, Maybe C.LinType)
grps = L.groupBy groupRow trvs val2lin' lv = case lv of
-- remove one level of depth and recurse C.ConcatValue v1 v2 -> do
let (v1',t1) <- val2lin v1
handleGroup :: [C.TableRowValue] -> Either String (L.LinFun, Maybe C.LinType) (v2',t2) <- val2lin v2
handleGroup [C.TableRow patt lv] = return (L.Concat v1' v2', t1 <|> t2) -- t1 else t2
C.LiteralValue ll -> case ll of
C.FloatConstant f -> return (L.Token $ T.pack $ show f, Just C.FloatType)
C.IntConstant i -> return (L.Token $ T.pack $ show i, Just C.IntType)
C.StrConstant s -> return (L.Token $ T.pack s, Just C.StrType)
C.ErrorValue err -> return (L.Error err, Nothing)
C.ParamConstant (C.Param pid lvs) -> do
let
collectProjections :: C.LinValue -> CodeGen [L.LinFun]
collectProjections (C.ParamConstant (C.Param pid lvs)) = do
def <- CMS.lift $ lookupParamDef pid
let (C.ParamDef tpid defpids) = def
pidIx <- CMS.lift $ eitherElemIndex pid [ p | C.Param p _ <- defpids ]
rest <- mapM collectProjections lvs
return $ L.Ix (pidIx+1) : concat rest
collectProjections lv = do
(lf,_) <- val2lin lv
return [lf]
lfs <- collectProjections lv
let term = L.Tuple lfs
def <- CMS.lift $ lookupParamDef pid
let (C.ParamDef tpid _) = def
return (term, Just $ C.ParamType (C.ParamTypeId tpid))
C.PredefValue (C.PredefId pid) -> case pid of
"BIND" -> return (L.Bind, Nothing)
"SOFT_BIND" -> return (L.Bind, Nothing)
"SOFT_SPACE" -> return (L.Space, Nothing)
"CAPIT" -> return (L.Capit, Nothing)
"ALL_CAPIT" -> return (L.AllCapit, Nothing)
_ -> CMS.lift $ Left $ printf "Unknown predef function: %s" pid
C.RecordValue rrvs -> do
let rrvs' = sortRecordRows rrvs
ts <- sequence [ val2lin lv | C.RecordRow lid lv <- rrvs' ]
return (L.Tuple (map fst ts), Just $ C.RecordType [ C.RecordRow lid lt | (C.RecordRow lid _, (_, Just lt)) <- zip rrvs' ts])
C.TableValue lt trvs -> do
-- group the rows by "left-most" value
let
groupRow :: C.TableRowValue -> C.TableRowValue -> Bool
groupRow (C.TableRow p1 _) (C.TableRow p2 _) = groupPattern p1 p2
groupPattern :: C.LinPattern -> C.LinPattern -> Bool
groupPattern p1 p2 = case (p1,p2) of
(C.ParamPattern (C.Param pid1 _), C.ParamPattern (C.Param pid2 _)) -> pid1 == pid2 -- compare only constructors
(C.RecordPattern (C.RecordRow lid1 patt1:_), C.RecordPattern (C.RecordRow lid2 patt2:_)) -> groupPattern patt1 patt2 -- lid1 == lid2 necessarily
_ -> error $ printf "Mismatched patterns in grouping:\n%s\n%s" (show p1) (show p2)
grps :: [[C.TableRowValue]]
grps = L.groupBy groupRow trvs
-- remove one level of depth and recurse
let
handleGroup :: [C.TableRowValue] -> CodeGen (L.LinFun, Maybe C.LinType)
handleGroup [C.TableRow patt lv] =
case reducePattern patt of
Just patt' -> do
(lf,lt) <- handleGroup [C.TableRow patt' lv]
return (L.Tuple [lf],lt)
Nothing -> val2lin lv
handleGroup rows = do
let rows' = map reduceRow rows
val2lin (C.TableValue lt rows') -- lt is wrong here, but is unused
reducePattern :: C.LinPattern -> Maybe C.LinPattern
reducePattern patt =
case patt of
C.ParamPattern (C.Param _ []) -> Nothing
C.ParamPattern (C.Param _ patts) -> Just $ C.ParamPattern (C.Param pid' patts')
where
C.ParamPattern (C.Param pid1 patts1) = head patts
pid' = pid1
patts' = patts1 ++ tail patts
C.RecordPattern [] -> Nothing
C.RecordPattern (C.RecordRow lid patt:rrs) ->
case reducePattern patt of case reducePattern patt of
Just patt' -> do Just patt' -> Just $ C.RecordPattern (C.RecordRow lid patt':rrs)
(lf,lt) <- handleGroup [C.TableRow patt' lv] Nothing -> if null rrs then Nothing else Just $ C.RecordPattern rrs
return (L.Tuple [lf],lt)
Nothing -> val2lin lv
handleGroup rows = do
let rows' = map reduceRow rows
val2lin (C.TableValue lt rows') -- lt is wrong here, but is unused
reducePattern :: C.LinPattern -> Maybe C.LinPattern _ -> error $ printf "Unhandled pattern in reducing: %s" (show patt)
reducePattern patt =
case patt of
C.ParamPattern (C.Param _ []) -> Nothing
C.ParamPattern (C.Param _ patts) -> Just $ C.ParamPattern (C.Param pid' patts')
where
C.ParamPattern (C.Param pid1 patts1) = head patts
pid' = pid1
patts' = patts1 ++ tail patts
C.RecordPattern [] -> Nothing reduceRow :: C.TableRowValue -> C.TableRowValue
C.RecordPattern (C.RecordRow lid patt:rrs) -> reduceRow (C.TableRow patt lv) =
case reducePattern patt of let Just patt' = reducePattern patt
Just patt' -> Just $ C.RecordPattern (C.RecordRow lid patt':rrs) in C.TableRow patt' lv
Nothing -> if null rrs then Nothing else Just $ C.RecordPattern rrs
_ -> error $ printf "Unhandled pattern in reducing: %s" (show patt) -- ts :: [(L.LinFun, Maybe C.LinType)]
ts <- mapM handleGroup grps
reduceRow :: C.TableRowValue -> C.TableRowValue -- return
reduceRow (C.TableRow patt lv) = let typ = case ts of
let Just patt' = reducePattern patt (_, Just tst):_ -> Just $ C.TableType lt tst
in C.TableRow patt' lv _ -> Nothing
return (L.Tuple (map fst ts), typ)
-- ts :: [(L.LinFun, Maybe C.LinType)] -- TODO TuplePattern, WildPattern?
ts <- mapM handleGroup grps
-- return C.TupleValue lvs -> do
let typ = case ts of ts <- mapM val2lin lvs
(_, Just tst):_ -> Just $ C.TableType lt tst return (L.Tuple (map fst ts), Just $ C.TupleType (map (fromJust.snd) ts))
_ -> Nothing
return (L.Tuple (map fst ts), typ)
-- TODO TuplePattern, WildPattern? C.VariantValue [] -> return (L.Empty, Nothing) -- TODO Just C.StrType ?
C.VariantValue (vr:_) -> val2lin vr -- NOTE variants not supported, just pick first
C.TupleValue lvs -> do C.VarValue (C.VarValueId (C.Unqual v)) -> do
ts <- mapM val2lin lvs ix <- CMS.lift $ eitherElemIndex (C.VarId v) varIds
return (L.Tuple (map fst ts), Just $ C.TupleType (map (fromJust.snd) ts)) lt <- CMS.lift $ lookupLinTypeArg funId ix
return (L.Argument (ix+1), Just lt)
C.VariantValue [] -> return (L.Empty, Nothing) -- TODO Just C.StrType ? C.PreValue pts df -> do
C.VariantValue (vr:_) -> val2lin vr -- NOTE variants not supported, just pick first pts' <- forM pts $ \(pfxs, lv) -> do
(lv', _) <- val2lin lv
return (map T.pack pfxs, lv')
(df', lt) <- val2lin df
return (L.Pre pts' df', lt)
C.VarValue (C.VarValueId (C.Unqual v)) -> do C.Projection v1 lblId -> do
ix <- eitherElemIndex (C.VarId v) varIds (v1', mtyp) <- val2lin v1
lt <- lookupLinTypeArg funId ix -- find label index in argument type
return (L.Argument (ix+1), Just lt) let Just (C.RecordType rrs) = mtyp
let rrs' = [ lid | C.RecordRow lid _ <- rrs ]
-- lblIx <- eitherElemIndex lblId rrs'
let
lblIx = case eitherElemIndex lblId rrs' of
Right x -> x
Left _ -> 0 -- corresponds to Prelude.False
-- lookup lintype for record row
let C.RecordRow _ lt = rrs !! lblIx
return (L.Projection v1' (L.Ix (lblIx+1)), Just lt)
C.PreValue pts df -> do C.Selection v1 v2 -> do
pts' <- forM pts $ \(pfxs, lv) -> do (v1', t1) <- val2lin v1
(lv', _) <- val2lin lv (v2', t2) <- val2lin v2
return (pfxs, lv') let Just (C.TableType t11 t12) = t1 -- t11 == t2
(df', lt) <- val2lin df return (L.Projection v1' v2', Just t12)
return (L.Pre pts' df', lt)
C.Projection v1 lblId -> do -- C.CommentedValue cmnt lv -> val2lin lv
(v1', mtyp) <- val2lin v1 C.CommentedValue cmnt lv -> case cmnt of
-- find label index in argument type "impossible" -> val2lin lv >>= \(_, typ) -> return (L.Empty, typ)
let Just (C.RecordType rrs) = mtyp _ -> val2lin lv
let rrs' = [ lid | C.RecordRow lid _ <- rrs ]
-- lblIx <- eitherElemIndex lblId rrs'
let
lblIx = case eitherElemIndex lblId rrs' of
Right x -> x
Left _ -> 0 -- corresponds to Prelude.False
-- lookup lintype for record row
let C.RecordRow _ lt = rrs !! lblIx
return (L.Projection v1' (L.Ix (lblIx+1)), Just lt)
C.Selection v1 v2 -> do v -> CMS.lift $ Left $ printf "val2lin not implemented for: %s" (show v)
(v1', t1) <- val2lin v1
(v2', t2) <- val2lin v2
let Just (C.TableType t11 t12) = t1 -- t11 == t2
return (L.Projection v1' v2', Just t12)
-- C.CommentedValue cmnt lv -> val2lin lv -- Invoke code generation
C.CommentedValue cmnt lv -> case cmnt of
"impossible" -> val2lin lv >>= \(_, typ) -> return (L.Empty, typ)
_ -> val2lin lv
v -> Left $ printf "val2lin not implemented for: %s" (show v)
unless (null $ lefts es) (raise $ unlines (lefts es))
let e = flip CMS.evalStateT Map.empty $ mapM mkLin lindefs
case e of
Left err -> raise err
Right lins -> do
let maybeOptimise = if debug then id else extractStrings let maybeOptimise = if debug then id else extractStrings
let concr = maybeOptimise $ L.Concrete { let concr = maybeOptimise $ L.Concrete {
L.toks = IntMap.empty, L.toks = IntMapBuilder.emptyIntMap,
L.lins = lins L.lins = Map.fromList lins
} }
return (mdi2i modId, concr) return (mdi2i modId, concr)
type CodeGen a = CMS.StateT (Map.Map C.LinValue (L.LinFun, Maybe C.LinType)) (Either String) a
-- | Remove ParamAliasDefs by inlining their definitions -- | Remove ParamAliasDefs by inlining their definitions
inlineParamAliases :: [C.ParamDef] -> [C.ParamDef] inlineParamAliases :: [C.ParamDef] -> [C.ParamDef]
inlineParamAliases defs = if null aliases then defs else map rp' pdefs inlineParamAliases defs = if null aliases then defs else map rp' pdefs
@@ -343,12 +364,12 @@ extractStrings concr = L.Concrete { L.toks = toks', L.lins = lins' }
(lins',imb') = CMS.runState (go0 (L.lins concr)) imb (lins',imb') = CMS.runState (go0 (L.lins concr)) imb
toks' = IntMapBuilder.toIntMap imb' toks' = IntMapBuilder.toIntMap imb'
go0 :: Map.Map CId L.LinFun -> CMS.State (IntMapBuilder.IMB String) (Map.Map CId L.LinFun) go0 :: Map.Map CId L.LinFun -> CMS.State (IntMapBuilder.IMB Text) (Map.Map CId L.LinFun)
go0 mp = do go0 mp = do
xs <- mapM (\(cid,lin) -> go lin >>= \lin' -> return (cid,lin')) (Map.toList mp) xs <- mapM (\(cid,lin) -> go lin >>= \lin' -> return (cid,lin')) (Map.toList mp)
return $ Map.fromList xs return $ Map.fromList xs
go :: L.LinFun -> CMS.State (IntMapBuilder.IMB String) L.LinFun go :: L.LinFun -> CMS.State (IntMapBuilder.IMB Text) L.LinFun
go lf = case lf of go lf = case lf of
L.Token str -> do L.Token str -> do
imb <- CMS.get imb <- CMS.get
@@ -360,7 +381,7 @@ extractStrings concr = L.Concrete { L.toks = toks', L.lins = lins' }
-- pts' <- mapM (\(pfxs,lv) -> go lv >>= \lv' -> return (pfxs,lv')) pts -- pts' <- mapM (\(pfxs,lv) -> go lv >>= \lv' -> return (pfxs,lv')) pts
pts' <- forM pts $ \(pfxs,lv) -> do pts' <- forM pts $ \(pfxs,lv) -> do
imb <- CMS.get imb <- CMS.get
let str = show pfxs let str = T.pack $ show pfxs
let (ix,imb') = IntMapBuilder.insert' str imb let (ix,imb') = IntMapBuilder.insert' str imb
CMS.put imb' CMS.put imb'
lv' <- go lv lv' <- go lv

View File

@@ -23,6 +23,10 @@ empty = IMB {
valMap = HashMap.empty valMap = HashMap.empty
} }
-- | An empty IntMap
emptyIntMap :: IntMap a
emptyIntMap = IntMap.empty
-- | Lookup a value -- | Lookup a value
lookup :: (Eq a, Hashable a) => a -> IMB a -> Maybe Int lookup :: (Eq a, Hashable a) => a -> IMB a -> Maybe Int
lookup a IMB { valMap = vm } = HashMap.lookup a vm lookup a IMB { valMap = vm } = HashMap.lookup a vm

View File

@@ -16,12 +16,13 @@ import PGF.Tree (Tree (..), expr2tree, prTree)
import qualified Control.Exception as EX import qualified Control.Exception as EX
import Control.Monad (liftM, liftM2, forM_) import Control.Monad (liftM, liftM2, forM_)
import qualified Control.Monad.Writer as CMW import qualified Control.Monad.Writer as CMW
import Data.Char (toUpper)
import Data.Binary (Binary, put, get, putWord8, getWord8, encodeFile, decodeFile) import Data.Binary (Binary, put, get, putWord8, getWord8, encodeFile, decodeFile)
import Data.Either (isLeft) import Data.Either (isLeft)
import qualified Data.IntMap as IntMap import qualified Data.IntMap as IntMap
import Data.List (isPrefixOf)
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Text.Printf (printf) import Text.Printf (printf)
import Prelude hiding ((!!)) import Prelude hiding ((!!))
@@ -40,7 +41,7 @@ data Abstract = Abstract {
-- | Concrete syntax -- | Concrete syntax
data Concrete = Concrete { data Concrete = Concrete {
toks :: IntMap.IntMap String, -- ^ all strings are stored exactly once here toks :: IntMap.IntMap Text, -- ^ all strings are stored exactly once here
-- lincats :: Map.Map CId LinType, -- ^ a linearization type for each category -- lincats :: Map.Map CId LinType, -- ^ a linearization type for each category
lins :: Map.Map CId LinFun -- ^ a linearization function for each function lins :: Map.Map CId LinFun -- ^ a linearization function for each function
} deriving (Show) } deriving (Show)
@@ -64,12 +65,12 @@ data LinFun =
| Space -- ^ space between adjacent tokens | Space -- ^ space between adjacent tokens
| Capit -- ^ capitalise next character | Capit -- ^ capitalise next character
| AllCapit -- ^ capitalise next word | AllCapit -- ^ capitalise next word
| Pre [([String], LinFun)] LinFun | Pre [([Text], LinFun)] LinFun
| Missing CId -- ^ missing definition (inserted at runtime) | Missing CId -- ^ missing definition (inserted at runtime)
-- From original definition in paper -- From original definition in paper
| Empty | Empty
| Token String | Token Text
| Concat LinFun LinFun | Concat LinFun LinFun
| Ix Int | Ix Int
| Tuple [LinFun] | Tuple [LinFun]
@@ -157,6 +158,10 @@ instance Binary LinFun where
14 -> liftM TokenIx get 14 -> liftM TokenIx get
_ -> fail "Failed to decode LPGF binary format" _ -> fail "Failed to decode LPGF binary format"
instance Binary Text where
put = put . TE.encodeUtf8
get = liftM TE.decodeUtf8 get
abstractName :: LPGF -> CId abstractName :: LPGF -> CId
abstractName = absname abstractName = absname
@@ -168,14 +173,22 @@ readLPGF = Data.Binary.decodeFile
-- | Main linearize function, to 'String' -- | Main linearize function, to 'String'
linearize :: LPGF -> Language -> Expr -> String linearize :: LPGF -> Language -> Expr -> String
linearize lpgf lang = linearize lpgf lang expr = T.unpack $ linearizeText lpgf lang expr
-- | Main linearize function, to 'Data.Text.Text'
linearizeText :: LPGF -> Language -> Expr -> Text
linearizeText lpgf lang =
case Map.lookup lang (concretes lpgf) of case Map.lookup lang (concretes lpgf) of
Just concr -> linearizeConcrete concr Just concr -> linearizeConcreteText concr
Nothing -> error $ printf "Unknown language: %s" (showCId lang) Nothing -> error $ printf "Unknown language: %s" (showCId lang)
-- | Language-specific linearize function, to 'String' -- | Language-specific linearize function, to 'String'
linearizeConcrete :: Concrete -> Expr -> String linearizeConcrete :: Concrete -> Expr -> String
linearizeConcrete concr expr = lin2string $ lin (expr2tree expr) linearizeConcrete concr expr = T.unpack $ linearizeConcreteText concr expr
-- | Language-specific linearize function, to 'Data.Text.Text'
linearizeConcreteText :: Concrete -> Expr -> Text
linearizeConcreteText concr expr = lin2string $ lin (expr2tree expr)
where where
lin :: Tree -> LinFun lin :: Tree -> LinFun
lin tree = case tree of lin tree = case tree of
@@ -196,7 +209,7 @@ try comp = do
-- | Evaluation context -- | Evaluation context
data Context = Context { data Context = Context {
cxArgs :: [LinFun], -- ^ is a sequence of terms cxArgs :: [LinFun], -- ^ is a sequence of terms
cxToks :: IntMap.IntMap String -- ^ token map cxToks :: IntMap.IntMap Text -- ^ token map
} }
-- | Operational semantics -- | Operational semantics
@@ -225,7 +238,7 @@ eval cxt t = case t of
PreIx pts df -> Pre pts' df' PreIx pts df -> Pre pts' df'
where where
pts' = [(pfxs, eval cxt t) | (ix, t) <- pts, let pfxs = maybe [] read $ IntMap.lookup ix (cxToks cxt)] pts' = [(pfxs, eval cxt t) | (ix, t) <- pts, let pfxs = maybe [] (read . T.unpack) $ IntMap.lookup ix (cxToks cxt)]
df' = eval cxt df df' = eval cxt df
TokenIx i -> maybe Empty Token $ IntMap.lookup i (cxToks cxt) TokenIx i -> maybe Empty Token $ IntMap.lookup i (cxToks cxt)
@@ -239,32 +252,32 @@ flattenTuple = \case
-- | Turn concrete syntax terms into an actual string. -- | Turn concrete syntax terms into an actual string.
-- This is done in two passes, first to flatten concats & evaluate pre's, then to -- This is done in two passes, first to flatten concats & evaluate pre's, then to
-- apply BIND and other predefs. -- apply BIND and other predefs.
lin2string :: LinFun -> String lin2string :: LinFun -> Text
lin2string lf = unwords $ join $ flatten [lf] lin2string lf = T.unwords $ join $ flatten [lf]
where where
-- Process bind et al into final token list -- Process bind et al into final token list
join :: [Either LinFun String] -> [String] join :: [Either LinFun Text] -> [Text]
join elt = case elt of join elt = case elt of
Right tok:Left Bind:ls -> Right tok:Left Bind:ls ->
case join ls of case join ls of
next:ls' -> tok : next : ls' next:ls' -> tok `T.append` next : ls'
_ -> [] _ -> []
Right tok:ls -> tok : join ls Right tok:ls -> tok : join ls
Left Space:ls -> join ls Left Space:ls -> join ls
Left Capit:ls -> Left Capit:ls ->
case join ls of case join ls of
next:ls' -> (toUpper (head next) : tail next) : ls' next:ls' -> T.toUpper (T.take 1 next) `T.append` T.drop 1 next : ls'
_ -> [] _ -> []
Left AllCapit:ls -> Left AllCapit:ls ->
case join ls of case join ls of
next:ls' -> map toUpper next : ls' next:ls' -> T.toUpper next : ls'
_ -> [] _ -> []
Left (Missing cid):ls -> join (Right (printf "[%s]" (show cid)) : ls) Left (Missing cid):ls -> join (Right (T.pack (printf "[%s]" (show cid))) : ls)
[] -> [] [] -> []
x -> error $ printf "Unhandled term in lin2string: %s" (show x) x -> error $ printf "Unhandled term in lin2string: %s" (show x)
-- Process concats, tuples, pre into flat list -- Process concats, tuples, pre into flat list
flatten :: [LinFun] -> [Either LinFun String] flatten :: [LinFun] -> [Either LinFun Text]
flatten [] = [] flatten [] = []
flatten (l:ls) = case l of flatten (l:ls) = case l of
Empty -> flatten ls Empty -> flatten ls
@@ -278,7 +291,7 @@ lin2string lf = unwords $ join $ flatten [lf]
f = flatten ls f = flatten ls
ch = case dropWhile isLeft f of ch = case dropWhile isLeft f of
Right next:_ -> Right next:_ ->
let matches = [ l | (pfxs, l) <- pts, any (`isPrefixOf` next) pfxs ] let matches = [ l | (pfxs, l) <- pts, any (`T.isPrefixOf` next) pfxs ]
in if null matches then df else head matches in if null matches then df else head matches
_ -> df _ -> df
in flatten (ch:ls) in flatten (ch:ls)
@@ -323,7 +336,7 @@ instance PP LPGF where
instance PP Concrete where instance PP Concrete where
pp (Concrete toks lins) = do pp (Concrete toks lins) = do
forM_ (IntMap.toList toks) $ \(i,tok) -> forM_ (IntMap.toList toks) $ \(i,tok) ->
CMW.tell [show i ++ " " ++ tok] CMW.tell [show i ++ " " ++ T.unpack tok]
CMW.tell [""] CMW.tell [""]
forM_ (Map.toList lins) $ \(cid,lin) -> do forM_ (Map.toList lins) $ \(cid,lin) -> do
CMW.tell ["# " ++ showCId cid] CMW.tell ["# " ++ showCId cid]

View File

@@ -86,6 +86,150 @@ stack exec -- hp2ps -c lpgf-bench.hp && open lpgf-bench.ps
- http://book.realworldhaskell.org/read/profiling-and-optimization.html - http://book.realworldhaskell.org/read/profiling-and-optimization.html
- https://wiki.haskell.org/Performance - https://wiki.haskell.org/Performance
### Honing in
```
stack build --test --bench --no-run-tests --no-run-benchmarks &&
stack bench --benchmark-arguments "compile lpgf testsuite/lpgf/phrasebook/PhrasebookFre.gf +RTS -T -RTS"
```
**Baseline PGF**
- compile: 1.600776s
- size: 2.88 MB Phrasebook.pgf
Max memory: 328.20 MB
**Baseline LPGF = B**
- compile: 12.401099s
- size: 3.01 MB Phrasebook.lpgf
Max memory: 1.33 GB
**Baseline LPGF String**
- compile: 12.124689s
- size: 3.01 MB Phrasebook.lpgf
Max memory: 1.34 GB
**B -extractStrings**
- compile: 13.822735s
- size: 5.78 MB Phrasebook.lpgf
Max memory: 1.39 GB
**B -cleanupRecordFields**
- compile: 13.670776s
- size: 3.01 MB Phrasebook.lpgf
Max memory: 1.48 GB
**No generation at all = E**
- compile: 0.521001s
- size: 3.27 KB Phrasebook.lpgf
Max memory: 230.69 MB
**+ Concat, Literal, Error, Predef, Tuple, Variant, Commented**
- compile: 1.503594s
- size: 3.27 KB Phrasebook.lpgf
Max memory: 395.31 MB
**+ Var, Pre, Selection**
- compile: 1.260184s
- size: 3.28 KB Phrasebook.lpgf
Max memory: 392.17 MB
**+ Record**
- compile: 1.659233s
- size: 7.07 KB Phrasebook.lpgf
Max memory: 397.41 MB
**+ Projection = X**
- compile: 1.446217s
- size: 7.94 KB Phrasebook.lpgf
Max memory: 423.62 MB
**X + Param**
- compile: 2.073838s
- size: 10.82 KB Phrasebook.lpgf
Max memory: 619.71 MB
**X + Table**
- compile: 11.26558s
- size: 2.48 MB Phrasebook.lpgf
Max memory: 1.15 GB
### Repeated terms in compilation
**Param and Table**
| Concr | Total | Unique | Perc |
|:--------------|-------:|-------:|-----:|
| PhrasebookEng | 8673 | 1724 | 20% |
| PhrasebookSwe | 14802 | 2257 | 15% |
| PhrasebookFin | 526225 | 4866 | 1% |
**Param**
| Concr | Total | Unique | Perc |
|:--------------|-------:|-------:|-----:|
| PhrasebookEng | 3211 | 78 | 2% |
| PhrasebookSwe | 7567 | 69 | 1% |
| PhrasebookFin | 316355 | 310 | 0.1% |
**Table**
| Concr | Total | Unique | Perc |
|:--------------|-------:|-------:|-----:|
| PhrasebookEng | 5470 | 1654 | 30% |
| PhrasebookSwe | 7243 | 2196 | 30% |
| PhrasebookFin | 209878 | 4564 | 2% |
### After impelementing state monad for table memoisation
**worse!**
- compile: 12.55848s
- size: 3.01 MB Phrasebook.lpgf
Max memory: 2.25 GB
**Params**
| Concr | Total | Misses | Perc |
|:--------------|-------:|-------:|------:|
| PhrasebookEng | 3211 | 72 | 2% |
| PhrasebookSwe | 7526 | 61 | 1% |
| PhrasebookFin | 135268 | 333 | 0.2% |
| PhrasebookFre | 337102 | 76 | 0.02% |
_modify instead of insert_
| Concr | Total | Misses | Perc |
|:--------------|-------:|-------:|------:|
| PhrasebookEng | 3211 | 70 | 2% |
| PhrasebookSwe | 7526 | 61 | 1% |
| PhrasebookFin | 135268 | 302 | 0.2% |
| PhrasebookFre | 337102 | 72 | 0.02% |
**Tables**
| Concr | Total | Misses | Perc |
|:--------------|------:|-------:|-----:|
| PhrasebookEng | 3719 | 3170 | 85% |
| PhrasebookSwe | 4031 | 3019 | 75% |
| PhrasebookFin | 36875 | 21730 | 59% |
| PhrasebookFre | 41397 | 32967 | 80% |
_modify instead of insert_
| Concr | Total | Misses | Perc |
|:--------------|------:|-------:|-----:|
| PhrasebookEng | 2415 | 1646 | 68% |
| PhrasebookSwe | 3228 | 2188 | 68% |
| PhrasebookFin | 8793 | 4556 | 52% |
| PhrasebookFre | 12490 | 5793 | 46% |
Conclusions:
- map itself requires more memory than acual compilation
- lookup is also as slow as actual compilation
Tried HashMap (deriving Hashable for LinValue), no inprovement.
Using show on LinValue for keys is incredibly slow.
# Notes on compilation # Notes on compilation
## 1 (see unittests/Params4) ## 1 (see unittests/Params4)

View File

@@ -14,6 +14,7 @@ import Data.Either (isLeft)
import qualified Data.List as L import qualified Data.List as L
import Data.Maybe (fromJust, isJust, isNothing) import Data.Maybe (fromJust, isJust, isNothing)
import qualified Data.Map as Map import qualified Data.Map as Map
import Data.Text (Text)
import Data.Time.Clock (getCurrentTime, diffUTCTime) import Data.Time.Clock (getCurrentTime, diffUTCTime)
import System.Console.ANSI import System.Console.ANSI
import System.Directory (listDirectory, getFileSize) import System.Directory (listDirectory, getFileSize)
@@ -154,13 +155,13 @@ linPGF2 :: PGF2.PGF -> [PGF2.Expr] -> [[String]]
linPGF2 pgf trees = linPGF2 pgf trees =
[ map (PGF2.linearize concr) trees | (_, concr) <- Map.toList (PGF2.languages pgf) ] [ map (PGF2.linearize concr) trees | (_, concr) <- Map.toList (PGF2.languages pgf) ]
linLPGF :: LPGF.LPGF -> [PGF.Expr] -> [[String]] linLPGF :: LPGF.LPGF -> [PGF.Expr] -> [[Text]]
linLPGF lpgf trees = linLPGF lpgf trees =
[ map (LPGF.linearizeConcrete concr) trees | (_,concr) <- Map.toList (LPGF.concretes lpgf) ] [ map (LPGF.linearizeConcreteText concr) trees | (_,concr) <- Map.toList (LPGF.concretes lpgf) ]
linLPGF' :: LPGF.LPGF -> [PGF.Expr] -> IO [[Either String String]] linLPGF' :: LPGF.LPGF -> [PGF.Expr] -> IO [[Either String Text]]
linLPGF' lpgf trees = linLPGF' lpgf trees =
forM (Map.toList (LPGF.concretes lpgf)) $ \(_,concr) -> mapM (LPGF.try . LPGF.linearizeConcrete concr) trees forM (Map.toList (LPGF.concretes lpgf)) $ \(_,concr) -> mapM (LPGF.try . LPGF.linearizeConcreteText concr) trees
-- | Produce human readable file size -- | Produce human readable file size
-- Adapted from https://hackage.haskell.org/package/hrfsize -- Adapted from https://hackage.haskell.org/package/hrfsize