summaryrefslogtreecommitdiff
path: root/compiler/GHC/StgToJS/Expr.hs
diff options
context:
space:
mode:
authorSylvain Henry <sylvain@haskus.fr>2022-02-10 08:24:24 +0000
committerSylvain Henry <sylvain@haskus.fr>2022-11-29 09:44:31 +0100
commitcc25d52e0f65d54c052908c7d91d5946342ab88a (patch)
tree0f35764ee3b9b0451ac999b64d2db9fa074fa3dd /compiler/GHC/StgToJS/Expr.hs
parentdef47dd32491311289bff26230b664c895f178cc (diff)
downloadhaskell-cc25d52e0f65d54c052908c7d91d5946342ab88a.tar.gz
Add Javascript backend
Add JS backend adapted from the GHCJS project by Luite Stegeman. Some features haven't been ported or implemented yet. Tests for these features have been disabled with an associated gitlab ticket. Bump array submodule Work funded by IOG. Co-authored-by: Jeffrey Young <jeffrey.young@iohk.io> Co-authored-by: Luite Stegeman <stegeman@gmail.com> Co-authored-by: Josh Meredith <joshmeredith2008@gmail.com>
Diffstat (limited to 'compiler/GHC/StgToJS/Expr.hs')
-rw-r--r--compiler/GHC/StgToJS/Expr.hs1045
1 files changed, 1045 insertions, 0 deletions
diff --git a/compiler/GHC/StgToJS/Expr.hs b/compiler/GHC/StgToJS/Expr.hs
new file mode 100644
index 0000000000..fd6d09585f
--- /dev/null
+++ b/compiler/GHC/StgToJS/Expr.hs
@@ -0,0 +1,1045 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE DeriveFunctor #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.StgToJS.Expr
+-- Copyright : (c) The University of Glasgow 2001
+-- License : BSD-style (see the file LICENSE)
+--
+-- Maintainer : Jeffrey Young <jeffrey.young@iohk.io>
+-- Luite Stegeman <luite.stegeman@iohk.io>
+-- Sylvain Henry <sylvain.henry@iohk.io>
+-- Josh Meredith <josh.meredith@iohk.io>
+-- Stability : experimental
+--
+-- Code generation of Expressions
+-----------------------------------------------------------------------------
+
+module GHC.StgToJS.Expr
+ ( genExpr
+ , genEntryType
+ , loadLiveFun
+ , genStaticRefsRhs
+ , genStaticRefs
+ , genBody
+ )
+where
+
+import GHC.Prelude
+
+import GHC.JS.Syntax
+import GHC.JS.Make
+
+import GHC.StgToJS.Apply
+import GHC.StgToJS.Arg
+import GHC.StgToJS.ExprCtx
+import GHC.StgToJS.FFI
+import GHC.StgToJS.Heap
+import GHC.StgToJS.Monad
+import GHC.StgToJS.DataCon
+import GHC.StgToJS.Types
+import GHC.StgToJS.Literal
+import GHC.StgToJS.Prim
+import GHC.StgToJS.Profiling
+import GHC.StgToJS.Regs
+import GHC.StgToJS.StgUtils
+import GHC.StgToJS.CoreUtils
+import GHC.StgToJS.Utils
+import GHC.StgToJS.Stack
+import GHC.StgToJS.Ids
+
+import GHC.Types.Basic
+import GHC.Types.CostCentre
+import GHC.Types.Tickish
+import GHC.Types.Var.Set
+import GHC.Types.Id
+import GHC.Types.Unique.FM
+import GHC.Types.RepType
+
+import GHC.Stg.Syntax
+import GHC.Stg.Utils
+
+import GHC.Builtin.PrimOps
+
+import GHC.Core
+import GHC.Core.TyCon
+import GHC.Core.DataCon
+import GHC.Core.Opt.Arity (isOneShotBndr)
+import GHC.Core.Type hiding (typeSize)
+
+import GHC.Utils.Misc
+import GHC.Utils.Monad
+import GHC.Utils.Panic
+import GHC.Utils.Panic.Plain
+import GHC.Utils.Outputable (ppr, renderWithContext, defaultSDocContext)
+import qualified Control.Monad.Trans.State.Strict as State
+import GHC.Data.FastString
+import qualified GHC.Data.List.SetOps as ListSetOps
+
+import Data.Monoid
+import Data.Maybe
+import Data.Function
+import Data.Either
+import qualified Data.List as L
+import qualified Data.Set as S
+import qualified Data.Map as M
+import Control.Monad
+import Control.Arrow ((&&&))
+
+-- | Evaluate an expression in the given expression context (continuation)
+genExpr :: HasDebugCallStack => ExprCtx -> CgStgExpr -> G (JStat, ExprResult)
+genExpr ctx stg = case stg of
+ StgApp f args -> genApp ctx f args
+ StgLit l -> do
+ ls <- genLit l
+ let r = assignToExprCtx ctx ls
+ pure (r,ExprInline Nothing)
+ StgConApp con _n args _ -> do
+ as <- concatMapM genArg args
+ c <- genCon ctx con as
+ return (c, ExprInline (Just as))
+ StgOpApp (StgFCallOp f _) args t
+ -> genForeignCall ctx f t (concatMap typex_expr $ ctxTarget ctx) args
+ StgOpApp (StgPrimOp op) args t
+ -> genPrimOp ctx op args t
+ StgOpApp (StgPrimCallOp c) args t
+ -> genPrimCall ctx c args t
+ StgCase e b at alts
+ -> genCase ctx b e at alts (liveVars $ stgExprLive False stg)
+ StgLet _ b e -> do
+ (b',ctx') <- genBind ctx b
+ (s,r) <- genExpr ctx' e
+ return (b' <> s, r)
+ StgLetNoEscape _ b e -> do
+ (b', ctx') <- genBindLne ctx b
+ (s, r) <- genExpr ctx' e
+ return (b' <> s, r)
+ StgTick (ProfNote cc count scope) e -> do
+ setSCCstats <- ifProfilingM $ setCC cc count scope
+ (stats, result) <- genExpr ctx e
+ return (setSCCstats <> stats, result)
+ StgTick (SourceNote span _sname) e
+ -> genExpr (ctxSetSrcSpan span ctx) e
+ StgTick _m e
+ -> genExpr ctx e
+
+-- | regular let binding: allocate heap object
+genBind :: HasDebugCallStack
+ => ExprCtx
+ -> CgStgBinding
+ -> G (JStat, ExprCtx)
+genBind ctx bndr =
+ case bndr of
+ StgNonRec b r -> do
+ j <- assign b r >>= \case
+ Just ja -> return ja
+ Nothing -> allocCls Nothing [(b,r)]
+ return (j, addEvalRhs ctx [(b,r)])
+ StgRec bs -> do
+ jas <- mapM (uncurry assign) bs -- fixme these might depend on parts initialized by allocCls
+ let m = if null jas then Nothing else Just (mconcat $ catMaybes jas)
+ j <- allocCls m . map snd . filter (isNothing . fst) $ zip jas bs
+ return (j, addEvalRhs ctx bs)
+ where
+ ctx' = ctxClearLneFrame ctx
+
+ assign :: Id -> CgStgRhs -> G (Maybe JStat)
+ assign b (StgRhsClosure _ _ccs {-[the_fv]-} _upd [] expr)
+ | let strip = snd . stripStgTicksTop (not . tickishIsCode)
+ , StgCase (StgApp scrutinee []) _ (AlgAlt _) [GenStgAlt (DataAlt _) params sel_expr] <- strip expr
+ , StgApp selectee [] <- strip sel_expr
+ , let params_w_offsets = zip params (L.scanl' (+) 1 $ map (typeSize . idType) params)
+ , let total_size = sum (map (typeSize . idType) params)
+ -- , the_fv == scrutinee -- fixme check
+ , Just the_offset <- ListSetOps.assocMaybe params_w_offsets selectee
+ , the_offset <= 16 -- fixme make this some configurable constant
+ = do
+ let the_fv = scrutinee -- error "the_fv" -- fixme
+ let sel_tag | the_offset == 2 = if total_size == 2 then "2a"
+ else "2b"
+ | otherwise = show the_offset
+ tgts <- identsForId b
+ the_fvjs <- varsForId the_fv
+ case (tgts, the_fvjs) of
+ ([tgt], [the_fvj]) -> return $ Just
+ (tgt ||= ApplExpr (var ("h$c_sel_" <> mkFastString sel_tag)) [the_fvj])
+ _ -> panic "genBind.assign: invalid size"
+ assign b (StgRhsClosure _ext _ccs _upd [] expr)
+ | snd (isInlineExpr (ctxEvaluatedIds ctx) expr) = do
+ d <- declVarsForId b
+ tgt <- varsForId b
+ let ctx' = ctx { ctxTarget = assocIdExprs b tgt }
+ (j, _) <- genExpr ctx' expr
+ return (Just (d <> j))
+ assign _b StgRhsCon{} = return Nothing
+ assign b r = genEntry ctx' b r >> return Nothing
+
+ addEvalRhs c [] = c
+ addEvalRhs c ((b,r):xs)
+ | StgRhsCon{} <- r = addEvalRhs (ctxAssertEvaluated b c) xs
+ | (StgRhsClosure _ _ ReEntrant _ _) <- r = addEvalRhs (ctxAssertEvaluated b c) xs
+ | otherwise = addEvalRhs c xs
+
+genBindLne :: HasDebugCallStack
+ => ExprCtx
+ -> CgStgBinding
+ -> G (JStat, ExprCtx)
+genBindLne ctx bndr = do
+ -- compute live variables and the offsets where they will be stored in the
+ -- stack
+ vis <- map (\(x,y,_) -> (x,y)) <$>
+ optimizeFree oldFrameSize (newLvs++map fst updBinds)
+ -- initialize updatable bindings to null_
+ declUpds <- mconcat <$> mapM (fmap (||= null_) . identForId . fst) updBinds
+ -- update expression context to include the updated LNE frame
+ let ctx' = ctxUpdateLneFrame vis bound ctx
+ mapM_ (uncurry $ genEntryLne ctx') binds
+ return (declUpds, ctx')
+ where
+ oldFrameSize = ctxLneFrameSize ctx
+ isOldLv i = ctxIsLneBinding ctx i ||
+ ctxIsLneLiveVar ctx i
+ live = liveVars $ mkDVarSet $ stgLneLive' bndr
+ newLvs = filter (not . isOldLv) (dVarSetElems live)
+ binds = case bndr of
+ StgNonRec b e -> [(b,e)]
+ StgRec bs -> bs
+ bound = map fst binds
+ (updBinds, _nonUpdBinds) = L.partition (isUpdatableRhs . snd) binds
+
+-- | Generate let-no-escape entry
+--
+-- Let-no-escape entries live on the stack. There is no heap object associated with them.
+--
+-- A let-no-escape entry is called like a normal stack frame, although as an optimization,
+-- `Stack`[`Sp`] is not set when making the call. This is done later if the
+-- thread needs to be suspended.
+--
+-- Updatable let-no-escape binders have one 'private' slot in the stack frame. This slot
+-- is initially set to null, changed to h$blackhole when the thunk is being evaluated.
+--
+genEntryLne :: HasDebugCallStack => ExprCtx -> Id -> CgStgRhs -> G ()
+genEntryLne ctx i rhs@(StgRhsClosure _ext _cc update args body) =
+ resetSlots $ do
+ let payloadSize = ctxLneFrameSize ctx
+ vars = ctxLneFrameVars ctx
+ myOffset =
+ maybe (panic "genEntryLne: updatable binder not found in let-no-escape frame")
+ ((payloadSize-) . fst)
+ (L.find ((==i) . fst . snd) (zip [0..] vars))
+ bh | isUpdatable update =
+ jVar (\x -> mconcat
+ [ x |= ApplExpr (var "h$bh_lne") [Sub sp (toJExpr myOffset), toJExpr (payloadSize+1)]
+ , IfStat x (ReturnStat x) mempty
+ ])
+ | otherwise = mempty
+ lvs <- popLneFrame True payloadSize ctx
+ body <- genBody ctx i R1 args body
+ ei@(TxtI eii) <- identForEntryId i
+ sr <- genStaticRefsRhs rhs
+ let f = JFunc [] (bh <> lvs <> body)
+ emitClosureInfo $
+ ClosureInfo ei
+ (CIRegs 0 $ concatMap idVt args)
+ (eii <> ", " <> mkFastString (renderWithContext defaultSDocContext (ppr i)))
+ (fixedLayout . reverse $
+ map (stackSlotType . fst) (ctxLneFrameVars ctx))
+ CIStackFrame
+ sr
+ emitToplevel (ei ||= toJExpr f)
+genEntryLne ctx i (StgRhsCon cc con _mu _ticks args) = resetSlots $ do
+ let payloadSize = ctxLneFrameSize ctx
+ ei@(TxtI _eii) <- identForEntryId i
+ -- di <- varForDataConWorker con
+ ii <- freshIdent
+ p <- popLneFrame True payloadSize ctx
+ args' <- concatMapM genArg args
+ ac <- allocCon ii con cc args'
+ emitToplevel (ei ||= toJExpr (JFunc []
+ (mconcat [decl ii, p, ac, r1 |= toJExpr ii, returnStack])))
+
+-- | Generate the entry function for a local closure
+genEntry :: HasDebugCallStack => ExprCtx -> Id -> CgStgRhs -> G ()
+genEntry _ _i StgRhsCon {} = return ()
+genEntry ctx i rhs@(StgRhsClosure _ext cc {-_bi live-} upd_flag args body) = resetSlots $ do
+ let live = stgLneLiveExpr rhs -- error "fixme" -- probably find live vars in body
+ ll <- loadLiveFun live
+ llv <- verifyRuntimeReps live
+ upd <- genUpdFrame upd_flag i
+ body <- genBody entryCtx i R2 args body
+ ei@(TxtI eii) <- identForEntryId i
+ et <- genEntryType args
+ setcc <- ifProfiling $
+ if et == CIThunk
+ then enterCostCentreThunk
+ else enterCostCentreFun cc
+ sr <- genStaticRefsRhs rhs
+ emitClosureInfo $ ClosureInfo ei
+ (CIRegs 0 $ PtrV : concatMap idVt args)
+ (eii <> ", " <> mkFastString (renderWithContext defaultSDocContext (ppr i)))
+ (fixedLayout $ map (uTypeVt . idType) live)
+ et
+ sr
+ emitToplevel (ei ||= toJExpr (JFunc [] (mconcat [ll, llv, upd, setcc, body])))
+ where
+ entryCtx = ctxSetTarget [] (ctxClearLneFrame ctx)
+
+-- | Generate the entry function types for identifiers. Note that this only
+-- returns either 'CIThunk' or 'CIFun'. Everything else (PAP Blackhole etc.) is
+-- filtered as not a RuntimeRepKinded type.
+genEntryType :: HasDebugCallStack => [Id] -> G CIType
+genEntryType [] = return CIThunk
+genEntryType args0 = do
+ args' <- mapM genIdArg args
+ return $ CIFun (length args) (length $ concat args')
+ where
+ args = filter (not . isRuntimeRepKindedTy . idType) args0
+
+-- | Generate the body of an object
+genBody :: HasDebugCallStack
+ => ExprCtx
+ -> Id
+ -> StgReg
+ -> [Id]
+ -> CgStgExpr
+ -> G JStat
+genBody ctx i startReg args e = do
+ -- load arguments into local variables
+ la <- do
+ args' <- concatMapM genIdArgI args
+ return (declAssignAll args' (fmap toJExpr [startReg..]))
+
+ -- assert that arguments have valid runtime reps
+ lav <- verifyRuntimeReps args
+
+ -- compute PrimReps and their number of slots required to return the result of
+ -- i applied to args.
+ let res_vars = resultSize args i
+
+ -- compute typed expressions for each slot and assign registers
+ let go_var regs = \case
+ [] -> []
+ ((rep,size):rs) ->
+ let !(regs0,regs1) = splitAt size regs
+ !ts = go_var regs1 rs
+ in TypedExpr rep regs0 : ts
+
+ let tgt = go_var jsRegsFromR1 res_vars
+ let !ctx' = ctx { ctxTarget = tgt }
+
+ -- generate code for the expression
+ (e, _r) <- genExpr ctx' e
+
+ return $ la <> lav <> e <> returnStack
+
+-- | Find the result type after applying the function to the arguments
+--
+-- It's trickier than it looks because:
+--
+-- 1. we don't have the Arity of the Id. The following functions return
+-- different values in some cases:
+-- - idArity
+-- - typeArity . idType
+-- - idFunRepArity
+-- - typeArity . unwrapType . idType
+-- Moreover the number of args may be different than all of these arities
+--
+-- 2. sometimes the type is Any, perhaps after some unwrapping. For example
+-- HappyAbsSyn is a newtype around HappyAny which is Any or (forall a. a).
+--
+-- Se we're left to use the applied arguments to peel the type (unwrapped) one
+-- arg at a time. But passed args are args after unarisation so we need to
+-- unarise every argument type that we peel (using typePrimRepArgs) to get the
+-- number of passed args consumed by each type arg.
+--
+-- In case of failure to determine the type, we default to LiftedRep as it's
+-- probably what it is.
+--
+resultSize :: HasDebugCallStack => [Id] -> Id -> [(PrimRep, Int)]
+resultSize args i = result
+ where
+ result = result_reps `zip` result_slots
+ result_slots = fmap (slotCount . primRepSize) result_reps
+ result_reps = trim_args (unwrapType (idType i)) (length args)
+
+ trim_args t 0 = typePrimRep t
+ trim_args t n
+ | Just (_af, _mult, arg, res) <- splitFunTy_maybe t
+ , nargs <- length (typePrimRepArgs arg)
+ , assert (n >= nargs) True
+ = trim_args (unwrapType res) (n - nargs)
+ | otherwise
+ = pprTrace "result_type: not a function type, assume LiftedRep" (ppr t)
+ [LiftedRep]
+
+-- | Ensure that the set of identifiers has valid 'RuntimeRep's. This function
+-- returns a no-op when 'csRuntimeAssert' in 'StgToJSConfig' is False.
+verifyRuntimeReps :: HasDebugCallStack => [Id] -> G JStat
+verifyRuntimeReps xs = do
+ runtime_assert <- csRuntimeAssert <$> getSettings
+ if not runtime_assert
+ then pure mempty
+ else mconcat <$> mapM verifyRuntimeRep xs
+ where
+ verifyRuntimeRep i = do
+ i' <- varsForId i
+ pure $ go i' (idVt i)
+ go js (VoidV:vs) = go js vs
+ go (j1:j2:js) (LongV:vs) = v "h$verify_rep_long" [j1,j2] <> go js vs
+ go (j1:j2:js) (AddrV:vs) = v "h$verify_rep_addr" [j1,j2] <> go js vs
+ go (j:js) (v:vs) = ver j v <> go js vs
+ go [] [] = mempty
+ go _ _ = pprPanic "verifyRuntimeReps: inconsistent sizes" (ppr xs)
+ ver j PtrV = v "h$verify_rep_heapobj" [j]
+ ver j IntV = v "h$verify_rep_int" [j]
+ ver j RtsObjV = v "h$verify_rep_rtsobj" [j]
+ ver j DoubleV = v "h$verify_rep_double" [j]
+ ver j ArrV = v "h$verify_rep_arr" [j]
+ ver _ _ = mempty
+ v f as = ApplStat (var f) as
+
+-- | Given a set of 'Id's, bind each 'Id' to the appropriate data fields in N
+-- registers. This assumes these data fields have already been populated in the
+-- registers. For the empty, singleton, and binary case use register 1, for any
+-- more use as many registers as necessary.
+loadLiveFun :: [Id] -> G JStat
+loadLiveFun l = do
+ l' <- concat <$> mapM identsForId l
+ case l' of
+ [] -> return mempty
+ -- set the ident to d1 field of register 1
+ [v] -> return (v ||= r1 .^ closureField1_)
+ -- set the idents to d1 and d2 fields of register 1
+ [v1,v2] -> return $ mconcat
+ [ v1 ||= r1 .^ closureField1_
+ , v2 ||= r1 .^ closureField2_
+ ]
+ -- and so on
+ (v:vs) -> do
+ d <- freshIdent
+ let l'' = mconcat . zipWith (loadLiveVar $ toJExpr d) [(1::Int)..] $ vs
+ return $ mconcat
+ [ v ||= r1 .^ closureField1_
+ , d ||= r1 .^ closureField2_
+ , l''
+ ]
+ where
+ loadLiveVar d n v = let ident = TxtI (dataFieldName n)
+ in v ||= SelExpr d ident
+
+-- | Pop a let-no-escape frame off the stack
+popLneFrame :: Bool -> Int -> ExprCtx -> G JStat
+popLneFrame inEntry size ctx = do
+ -- calculate the new stack size
+ let ctx' = ctxLneShrinkStack ctx size
+
+ let gen_id_slot (i,n) = do
+ ids <- identsForId i
+ let !id_n = ids !! (n-1)
+ pure (id_n, SlotId i n)
+
+ is <- mapM gen_id_slot (ctxLneFrameVars ctx')
+
+ let skip = if inEntry then 1 else 0 -- pop the frame header
+ popSkipI skip is
+
+-- | Generate an updated given an 'Id'
+genUpdFrame :: UpdateFlag -> Id -> G JStat
+genUpdFrame u i
+ | isReEntrant u = pure mempty
+ | isOneShotBndr i = maybeBh
+ | isUpdatable u = updateThunk
+ | otherwise = maybeBh
+ where
+ isReEntrant ReEntrant = True
+ isReEntrant _ = False
+ maybeBh = do
+ settings <- getSettings
+ assertRtsStat (return $ bhSingleEntry settings)
+
+-- | Blackhole single entry
+--
+-- Overwrite a single entry object with a special thunk that behaves like a
+-- black hole (throws a JS exception when entered) but pretends to be a thunk.
+-- Useful for making sure that the object is not accidentally entered multiple
+-- times
+--
+bhSingleEntry :: StgToJSConfig -> JStat
+bhSingleEntry _settings = mconcat
+ [ r1 .^ closureEntry_ |= var "h$blackholeTrap"
+ , r1 .^ closureField1_ |= undefined_
+ , r1 .^ closureField2_ |= undefined_
+ ]
+
+genStaticRefsRhs :: CgStgRhs -> G CIStatic
+genStaticRefsRhs lv = genStaticRefs (stgRhsLive lv)
+
+-- fixme, update to new way to compute static refs dynamically
+genStaticRefs :: LiveVars -> G CIStatic
+genStaticRefs lv
+ | isEmptyDVarSet sv = return (CIStaticRefs [])
+ | otherwise = do
+ unfloated <- State.gets gsUnfloated
+ let xs = filter (\x -> not (elemUFM x unfloated ||
+ typeLevity_maybe (idType x) == Just Unlifted))
+ (dVarSetElems sv)
+ CIStaticRefs . catMaybes <$> mapM getStaticRef xs
+ where
+ sv = liveStatic lv
+
+ getStaticRef :: Id -> G (Maybe FastString)
+ getStaticRef = fmap (fmap itxt . listToMaybe) . identsForId
+
+-- | Reorder the things we need to push to reuse existing stack values as much
+-- as possible True if already on the stack at that location
+optimizeFree
+ :: HasDebugCallStack
+ => Int
+ -> [Id]
+ -> G [(Id,Int,Bool)] -- ^ A list of stack slots.
+ -- -- Id: stored on the slot
+ -- -- Int: the part of the value that is stored
+ -- -- Bool: True when the slot already contains a value
+optimizeFree offset ids = do
+ -- this line goes wrong vvvvvvv
+ let -- ids' = concat $ map (\i -> map (i,) [1..varSize . uTypeVt . idType $ i]) ids
+ idSize :: Id -> Int
+ idSize i = sum $ map varSize (typeVt . idType $ i)
+ ids' = concatMap (\i -> map (i,) [1..idSize i]) ids
+ -- 1..varSize] . uTypeVt . idType $ i]) (typeVt ids)
+ l = length ids'
+ slots <- drop offset . take l . (++repeat SlotUnknown) <$> getSlots
+ let slm = M.fromList (zip slots [0..])
+ (remaining, fixed) = partitionEithers $
+ map (\inp@(i,n) -> maybe (Left inp) (\j -> Right (i,n,j,True))
+ (M.lookup (SlotId i n) slm)) ids'
+ takenSlots = S.fromList (fmap (\(_,_,x,_) -> x) fixed)
+ freeSlots = filter (`S.notMember` takenSlots) [0..l-1]
+ remaining' = zipWith (\(i,n) j -> (i,n,j,False)) remaining freeSlots
+ allSlots = L.sortBy (compare `on` \(_,_,x,_) -> x) (fixed ++ remaining')
+ return $ map (\(i,n,_,b) -> (i,n,b)) allSlots
+
+-- | Allocate local closures
+allocCls :: Maybe JStat -> [(Id, CgStgRhs)] -> G JStat
+allocCls dynMiddle xs = do
+ (stat, dyn) <- partitionEithers <$> mapM toCl xs
+ ac <- allocDynAll True dynMiddle dyn
+ pure (mconcat stat <> ac)
+ where
+ -- left = static, right = dynamic
+ toCl :: (Id, CgStgRhs)
+ -> G (Either JStat (Ident,JExpr,[JExpr],CostCentreStack))
+ -- statics
+ {- making zero-arg constructors static is problematic, see #646
+ proper candidates for this optimization should have been floated
+ already
+ toCl (i, StgRhsCon cc con []) = do
+ ii <- identForId i
+ Left <$> (return (decl ii) <> allocCon ii con cc []) -}
+ toCl (i, StgRhsCon cc con _mui _ticjs [a]) | isUnboxableCon con = do
+ ii <- identForId i
+ ac <- allocCon ii con cc =<< genArg a
+ pure (Left (decl ii <> ac))
+
+ -- dynamics
+ toCl (i, StgRhsCon cc con _mu _ticks ar) =
+ -- fixme do we need to handle unboxed?
+ Right <$> ((,,,) <$> identForId i
+ <*> varForDataConWorker con
+ <*> concatMapM genArg ar
+ <*> pure cc)
+ toCl (i, cl@(StgRhsClosure _ext cc _upd_flag _args _body)) =
+ let live = stgLneLiveExpr cl
+ in Right <$> ((,,,) <$> identForId i
+ <*> varForEntryId i
+ <*> concatMapM varsForId live
+ <*> pure cc)
+
+-- fixme CgCase has a reps_compatible check here
+-- | Consume Stg case statement and generate a case statement. See also
+-- 'genAlts'
+genCase :: HasDebugCallStack
+ => ExprCtx
+ -> Id
+ -> CgStgExpr
+ -> AltType
+ -> [CgStgAlt]
+ -> LiveVars
+ -> G (JStat, ExprResult)
+genCase ctx bnd e at alts l
+ | snd (isInlineExpr (ctxEvaluatedIds ctx) e) = do
+ bndi <- identsForId bnd
+ let ctx' = ctxSetTop bnd
+ $ ctxSetTarget (assocIdExprs bnd (map toJExpr bndi))
+ $ ctx
+ (ej, r) <- genExpr ctx' e
+ let d = case r of
+ ExprInline d0 -> d0
+ ExprCont -> pprPanic "genCase: expression was not inline"
+ (pprStgExpr panicStgPprOpts e)
+
+ (aj, ar) <- genAlts (ctxAssertEvaluated bnd ctx) bnd at d alts
+ (saveCCS,restoreCCS) <- ifProfilingM $ do
+ ccsVar <- freshIdent
+ pure ( ccsVar ||= toJExpr jCurrentCCS
+ , toJExpr jCurrentCCS |= toJExpr ccsVar
+ )
+ return ( mconcat
+ [ mconcat (map decl bndi)
+ , saveCCS
+ , ej
+ , restoreCCS
+ , aj
+ ]
+ , ar
+ )
+ | otherwise = do
+ rj <- genRet (ctxAssertEvaluated bnd ctx) bnd at alts l
+ let ctx' = ctxSetTop bnd
+ $ ctxSetTarget (assocIdExprs bnd (map toJExpr [R1 ..]))
+ $ ctx
+ (ej, _r) <- genExpr ctx' e
+ return (rj <> ej, ExprCont)
+
+genRet :: HasDebugCallStack
+ => ExprCtx
+ -> Id
+ -> AltType
+ -> [CgStgAlt]
+ -> LiveVars
+ -> G JStat
+genRet ctx e at as l = freshIdent >>= f
+ where
+ allRefs :: [Id]
+ allRefs = S.toList . S.unions $ fmap (exprRefs emptyUFM . alt_rhs) as
+ lneLive :: Int
+ lneLive = maximum $ 0 : catMaybes (map (ctxLneBindingStackSize ctx) allRefs)
+ ctx' = ctxLneShrinkStack ctx lneLive
+ lneVars = map fst $ ctxLneFrameVars ctx'
+ isLne i = ctxIsLneBinding ctx i || ctxIsLneLiveVar ctx' i
+ nonLne = filter (not . isLne) (dVarSetElems l)
+
+ f :: Ident -> G JStat
+ f r@(TxtI ri) = do
+ pushLne <- pushLneFrame lneLive ctx
+ saveCCS <- ifProfilingM $ push [jCurrentCCS]
+ free <- optimizeFree 0 nonLne
+ pushRet <- pushRetArgs free (toJExpr r)
+ fun' <- fun free
+ sr <- genStaticRefs l -- srt
+ prof <- profiling
+ emitClosureInfo $
+ ClosureInfo r
+ (CIRegs 0 altRegs)
+ ri
+ (fixedLayout . reverse $
+ map (stackSlotType . fst3) free
+ ++ if prof then [ObjV] else map stackSlotType lneVars)
+ CIStackFrame
+ sr
+ emitToplevel $ r ||= toJExpr (JFunc [] fun')
+ return (pushLne <> saveCCS <> pushRet)
+ fst3 ~(x,_,_) = x
+
+ altRegs :: HasDebugCallStack => [VarType]
+ altRegs = case at of
+ PrimAlt ptc -> [primRepVt ptc]
+ MultiValAlt _n -> idVt e
+ _ -> [PtrV]
+
+ -- special case for popping CCS but preserving stack size
+ pop_handle_CCS :: [(JExpr, StackSlot)] -> G JStat
+ pop_handle_CCS [] = return mempty
+ pop_handle_CCS xs = do
+ -- grab the slots from 'xs' and push
+ addSlots (map snd xs)
+ -- move the stack pointer into the stack by ''length xs + n'
+ a <- adjSpN (length xs)
+ -- now load from the top of the stack
+ return (loadSkip 0 (map fst xs) <> a)
+
+ fun free = resetSlots $ do
+ decs <- declVarsForId e
+ load <- flip assignAll (map toJExpr [R1 ..]) . map toJExpr <$> identsForId e
+ loadv <- verifyRuntimeReps [e]
+ ras <- loadRetArgs free
+ rasv <- verifyRuntimeReps (map (\(x,_,_)->x) free)
+ restoreCCS <- ifProfilingM . pop_handle_CCS $ pure (jCurrentCCS, SlotUnknown)
+ rlne <- popLneFrame False lneLive ctx'
+ rlnev <- verifyRuntimeReps lneVars
+ (alts, _altr) <- genAlts ctx' e at Nothing as
+ return $ decs <> load <> loadv <> ras <> rasv <> restoreCCS <> rlne <> rlnev <> alts <>
+ returnStack
+
+-- | Consume an Stg case alternative and generate the corresponding alternative
+-- in JS land. If one alternative is a continuation then we must normalize the
+-- other alternatives. See 'Branch' and 'normalizeBranches'.
+genAlts :: HasDebugCallStack
+ => ExprCtx -- ^ lhs to assign expression result to
+ -> Id -- ^ id being matched
+ -> AltType -- ^ type
+ -> Maybe [JExpr] -- ^ if known, fields in datacon from earlier expression
+ -> [CgStgAlt] -- ^ the alternatives
+ -> G (JStat, ExprResult)
+genAlts ctx e at me alts = do
+ (st, er) <- case at of
+
+ PolyAlt -> case alts of
+ [alt] -> (branch_stat &&& branch_result) <$> mkAlgBranch ctx e alt
+ _ -> panic "genAlts: multiple polyalt"
+
+ PrimAlt _tc
+ | [GenStgAlt _ bs expr] <- alts
+ -> do
+ ie <- varsForId e
+ dids <- mconcat <$> mapM declVarsForId bs
+ bss <- concatMapM varsForId bs
+ (ej, er) <- genExpr ctx expr
+ return (dids <> assignAll bss ie <> ej, er)
+
+ PrimAlt tc
+ -> do
+ ie <- varsForId e
+ (r, bss) <- normalizeBranches ctx <$>
+ mapM (isolateSlots . mkPrimIfBranch ctx [primRepVt tc]) alts
+ setSlots []
+ return (mkSw ie bss, r)
+
+ MultiValAlt n
+ | [GenStgAlt _ bs expr] <- alts
+ -> do
+ eids <- varsForId e
+ l <- loadUbxTup eids bs n
+ (ej, er) <- genExpr ctx expr
+ return (l <> ej, er)
+
+ AlgAlt tc
+ | [_alt] <- alts
+ , isUnboxedTupleTyCon tc
+ -> panic "genAlts: unexpected unboxed tuple"
+
+ AlgAlt _tc
+ | Just es <- me
+ , [GenStgAlt (DataAlt dc) bs expr] <- alts
+ , not (isUnboxableCon dc)
+ -> do
+ bsi <- mapM identsForId bs
+ (ej, er) <- genExpr ctx expr
+ return (declAssignAll (concat bsi) es <> ej, er)
+
+ AlgAlt _tc
+ | [alt] <- alts
+ -> do
+ Branch _ s r <- mkAlgBranch ctx e alt
+ return (s, r)
+
+ AlgAlt _tc
+ | [alt,_] <- alts
+ , DataAlt dc <- alt_con alt
+ , isBoolDataCon dc
+ -> do
+ i <- varForId e
+ nbs <- normalizeBranches ctx <$>
+ mapM (isolateSlots . mkAlgBranch ctx e) alts
+ case nbs of
+ (r, [Branch _ s1 _, Branch _ s2 _]) -> do
+ let s = if dataConTag dc == 2
+ then IfStat i s1 s2
+ else IfStat i s2 s1
+ setSlots []
+ return (s, r)
+ _ -> error "genAlts: invalid branches for Bool"
+
+ AlgAlt _tc -> do
+ ei <- varForId e
+ (r, brs) <- normalizeBranches ctx <$>
+ mapM (isolateSlots . mkAlgBranch ctx e) alts
+ setSlots []
+ return (mkSwitch (ei .^ "f" .^ "a") brs, r)
+
+ _ -> pprPanic "genAlts: unhandled case variant" (ppr (at, length alts))
+
+ ver <- verifyMatchRep e at
+ pure (ver <> st, er)
+
+-- | If 'StgToJSConfig.csRuntimeAssert' is set, then generate an assertion that
+-- asserts the pattern match is valid, e.g., the match is attempted on a
+-- Boolean, a Data Constructor, or some number.
+verifyMatchRep :: HasDebugCallStack => Id -> AltType -> G JStat
+verifyMatchRep x alt = do
+ runtime_assert <- csRuntimeAssert <$> getSettings
+ if not runtime_assert
+ then pure mempty
+ else case alt of
+ AlgAlt tc -> do
+ ix <- varsForId x
+ pure $ ApplStat (var "h$verify_match_alg") (ValExpr(JStr(mkFastString (renderWithContext defaultSDocContext (ppr tc)))):ix)
+ _ -> pure mempty
+
+-- | A 'Branch' represents a possible branching path of an Stg case statement,
+-- i.e., a possible code path from an 'StgAlt'
+data Branch a = Branch
+ { branch_expr :: a
+ , branch_stat :: JStat
+ , branch_result :: ExprResult
+ }
+ deriving (Eq,Functor)
+
+-- | If one branch ends in a continuation but another is inline, we need to
+-- adjust the inline branch to use the continuation convention
+normalizeBranches :: ExprCtx
+ -> [Branch a]
+ -> (ExprResult, [Branch a])
+normalizeBranches ctx brs
+ | all (==ExprCont) (fmap branch_result brs) =
+ (ExprCont, brs)
+ | branchResult (fmap branch_result brs) == ExprCont =
+ (ExprCont, map mkCont brs)
+ | otherwise =
+ (ExprInline Nothing, brs)
+ where
+ mkCont b = case branch_result b of
+ ExprInline{} -> b { branch_stat = branch_stat b <> assignAll jsRegsFromR1
+ (concatMap typex_expr $ ctxTarget ctx)
+ , branch_result = ExprCont
+ }
+ _ -> b
+
+-- | Load an unboxed tuple. "Loading" means getting all 'Idents' from the input
+-- ID's, declaring them as variables in JS land and binding them, in order, to
+-- 'es'.
+loadUbxTup :: [JExpr] -> [Id] -> Int -> G JStat
+loadUbxTup es bs _n = do
+ bs' <- concatMapM identsForId bs
+ return $ declAssignAll bs' es
+
+mkSw :: [JExpr] -> [Branch (Maybe [JExpr])] -> JStat
+mkSw [e] cases = mkSwitch e (fmap (fmap (fmap head)) cases)
+mkSw es cases = mkIfElse es cases
+
+-- | Switch for pattern matching on constructors or prims
+mkSwitch :: JExpr -> [Branch (Maybe JExpr)] -> JStat
+mkSwitch e cases
+ | [Branch (Just c1) s1 _] <- n
+ , [Branch _ s2 _] <- d
+ = IfStat (InfixExpr StrictEqOp e c1) s1 s2
+
+ | [Branch (Just c1) s1 _, Branch _ s2 _] <- n
+ , null d
+ = IfStat (InfixExpr StrictEqOp e c1) s1 s2
+
+ | null d
+ = SwitchStat e (map addBreak (init n)) (branch_stat (last n))
+
+ | [Branch _ d0 _] <- d
+ = SwitchStat e (map addBreak n) d0
+
+ | otherwise = panic "mkSwitch: multiple default cases"
+ where
+ addBreak (Branch (Just c) s _) = (c, mconcat [s, BreakStat Nothing])
+ addBreak _ = panic "mkSwitch: addBreak"
+ (n,d) = L.partition (isJust . branch_expr) cases
+
+-- | if/else for pattern matching on things that js cannot switch on
+-- the list of branches is expected to have the default alternative
+-- first, if it exists
+mkIfElse :: [JExpr] -> [Branch (Maybe [JExpr])] -> JStat
+mkIfElse e s = go (L.reverse s)
+ where
+ go = \case
+ [Branch _ s _] -> s -- only one 'nothing' allowed
+ (Branch (Just e0) s _ : xs) -> IfStat (mkEq e e0) s (go xs)
+ [] -> panic "mkIfElse: empty expression list"
+ _ -> panic "mkIfElse: multiple DEFAULT cases"
+
+-- | Wrapper to contruct sequences of (===), e.g.,
+--
+-- > mkEq [l0,l1,l2] [r0,r1,r2] = (l0 === r0) && (l1 === r1) && (l2 === r2)
+--
+mkEq :: [JExpr] -> [JExpr] -> JExpr
+mkEq es1 es2
+ | length es1 == length es2 = foldl1 (InfixExpr LAndOp) (zipWith (InfixExpr StrictEqOp) es1 es2)
+ | otherwise = panic "mkEq: incompatible expressions"
+
+mkAlgBranch :: ExprCtx -- ^ toplevel id for the result
+ -> Id -- ^ datacon to match
+ -> CgStgAlt -- ^ match alternative with binders
+ -> G (Branch (Maybe JExpr))
+mkAlgBranch top d alt
+ | DataAlt dc <- alt_con alt
+ , isUnboxableCon dc
+ , [b] <- alt_bndrs alt
+ = do
+ idd <- varForId d
+ fldx <- identsForId b
+ case fldx of
+ [fld] -> do
+ (ej, er) <- genExpr top (alt_rhs alt)
+ return (Branch Nothing (mconcat [fld ||= idd, ej]) er)
+ _ -> panic "mkAlgBranch: invalid size"
+
+ | otherwise
+ = do
+ cc <- caseCond (alt_con alt)
+ idd <- varForId d
+ b <- loadParams idd (alt_bndrs alt)
+ (ej, er) <- genExpr top (alt_rhs alt)
+ return (Branch cc (b <> ej) er)
+
+-- | Generate a primitive If-expression
+mkPrimIfBranch :: ExprCtx
+ -> [VarType]
+ -> CgStgAlt
+ -> G (Branch (Maybe [JExpr]))
+mkPrimIfBranch top _vt alt =
+ (\ic (ej,er) -> Branch ic ej er) <$> ifCond (alt_con alt) <*> genExpr top (alt_rhs alt)
+
+-- fixme are bool things always checked correctly here?
+ifCond :: AltCon -> G (Maybe [JExpr])
+ifCond = \case
+ DataAlt da -> return $ Just [toJExpr (dataConTag da)]
+ LitAlt l -> Just <$> genLit l
+ DEFAULT -> return Nothing
+
+caseCond :: AltCon -> G (Maybe JExpr)
+caseCond = \case
+ DEFAULT -> return Nothing
+ DataAlt da -> return $ Just (toJExpr $ dataConTag da)
+ LitAlt l -> genLit l >>= \case
+ [e] -> pure (Just e)
+ es -> pprPanic "caseCond: expected single-variable literal" (ppr es)
+
+-- fixme use single tmp var for all branches
+-- | Load parameters from constructor
+loadParams :: JExpr -> [Id] -> G JStat
+loadParams from args = do
+ as <- concat <$> zipWithM (\a u -> map (,u) <$> identsForId a) args use
+ return $ case as of
+ [] -> mempty
+ [(x,u)] -> loadIfUsed (from .^ closureField1_) x u
+ [(x1,u1),(x2,u2)] -> mconcat
+ [ loadIfUsed (from .^ closureField1_) x1 u1
+ , loadIfUsed (from .^ closureField2_) x2 u2
+ ]
+ ((x,u):xs) -> mconcat
+ [ loadIfUsed (from .^ closureField1_) x u
+ , jVar (\d -> mconcat [ d |= from .^ closureField2_
+ , loadConVarsIfUsed d xs
+ ])
+ ]
+ where
+ use = repeat True -- fixme clean up
+ loadIfUsed fr tgt True = tgt ||= fr
+ loadIfUsed _ _ _ = mempty
+
+ loadConVarsIfUsed fr cs = mconcat $ zipWith f cs [(1::Int)..]
+ where f (x,u) n = loadIfUsed (SelExpr fr (TxtI (dataFieldName n))) x u
+
+-- | Determine if a branch will end in a continuation or not. If not the inline
+-- branch must be normalized. See 'normalizeBranches'
+-- NB. not a Monoid
+branchResult :: HasDebugCallStack => [ExprResult] -> ExprResult
+branchResult = \case
+ [] -> panic "branchResult: empty list"
+ [e] -> e
+ (ExprCont:_) -> ExprCont
+ (_:es)
+ | elem ExprCont es -> ExprCont
+ | otherwise -> ExprInline Nothing
+
+-- | Push return arguments onto the stack. The 'Bool' tracks whether the value
+-- is already on the stack or not, used in 'StgToJS.Stack.pushOptimized'.
+pushRetArgs :: HasDebugCallStack => [(Id,Int,Bool)] -> JExpr -> G JStat
+pushRetArgs free fun = do
+ rs <- mapM (\(i,n,b) -> (\es->(es!!(n-1),b)) <$> genIdArg i) free
+ pushOptimized (rs++[(fun,False)])
+
+-- | Load the return arguments then pop the stack frame
+loadRetArgs :: HasDebugCallStack => [(Id,Int,Bool)] -> G JStat
+loadRetArgs free = do
+ ids <- mapM (\(i,n,_b) -> (!! (n-1)) <$> genIdStackArgI i) free
+ popSkipI 1 ids
+
+-- | allocate multiple, possibly mutually recursive, closures
+allocDynAll :: Bool -> Maybe JStat -> [(Ident,JExpr,[JExpr],CostCentreStack)] -> G JStat
+{-
+XXX remove use of template and enable in-place init again
+allocDynAll haveDecl middle [(to,entry,free,cc)]
+ | isNothing middle && to `notElem` (free ^.. template) = do
+ ccs <- ccsVarJ cc
+ return $ allocDynamic s haveDecl to entry free ccs -}
+allocDynAll haveDecl middle cls = do
+ settings <- getSettings
+ let
+ middle' = fromMaybe mempty middle
+
+ decl_maybe i e
+ | haveDecl = toJExpr i |= e
+ | otherwise = i ||= e
+
+ makeObjs :: G JStat
+ makeObjs =
+ fmap mconcat $ forM cls $ \(i,f,_,cc) -> do
+ ccs <- maybeToList <$> costCentreStackLbl cc
+ pure $ mconcat
+ [ decl_maybe i $ if csInlineAlloc settings
+ then ValExpr (jhFromList $ [ (closureEntry_ , f)
+ , (closureField1_, null_)
+ , (closureField2_, null_)
+ , (closureMeta_ , zero_)
+ ]
+ ++ fmap (\cid -> ("cc", ValExpr (JVar cid))) ccs)
+ else ApplExpr (var "h$c") (f : fmap (ValExpr . JVar) ccs)
+ ]
+
+ fillObjs = mconcat $ map fillObj cls
+ fillObj (i,_,es,_)
+ | csInlineAlloc settings || length es > 24 =
+ case es of
+ [] -> mempty
+ [ex] -> toJExpr i .^ closureField1_ |= toJExpr ex
+ [e1,e2] -> mconcat
+ [ toJExpr i .^ closureField1_ |= toJExpr e1
+ , toJExpr i .^ closureField2_ |= toJExpr e2
+ ]
+ (ex:es) -> mconcat
+ [ toJExpr i .^ closureField1_ |= toJExpr ex
+ , toJExpr i .^ closureField2_ |= toJExpr (jhFromList (zip dataFieldNames es))
+ ]
+ | otherwise = case es of
+ [] -> mempty
+ [ex] -> toJExpr i .^ closureField1_ |= ex
+ [e1,e2] -> mconcat
+ [ toJExpr i .^ closureField1_ |= e1
+ , toJExpr i .^ closureField2_ |= e2
+ ]
+ (ex:es) -> mconcat
+ [ toJExpr i .^ closureField1_ |= ex
+ , toJExpr i .^ closureField2_ |= fillFun es
+ ]
+
+ fillFun [] = null_
+ fillFun es = ApplExpr (allocData (length es)) es
+
+ checkObjs | csAssertRts settings = mconcat $
+ map (\(i,_,_,_) -> ApplStat (ValExpr (JVar (TxtI "h$checkObj"))) [toJExpr i]) cls
+ | otherwise = mempty
+
+ objs <- makeObjs
+ pure $ mconcat [objs, middle', fillObjs, checkObjs]
+
+-- | Generate a primop. This function wraps around the real generator
+-- 'GHC.StgToJS.genPrim', handling the 'ExprCtx' and all arguments before
+-- generating the primop.
+genPrimOp :: ExprCtx -> PrimOp -> [StgArg] -> Type -> G (JStat, ExprResult)
+genPrimOp ctx op args t = do
+ as <- concatMapM genArg args
+ prof <- csProf <$> getSettings
+ bound <- csBoundsCheck <$> getSettings
+ -- fixme: should we preserve/check the primreps?
+ return $ case genPrim prof bound t op (concatMap typex_expr $ ctxTarget ctx) as of
+ PrimInline s -> (s, ExprInline Nothing)
+ PRPrimCall s -> (s, ExprCont)