{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} module GHC.StgToJS.Utils ( assignToTypedExprs , assignCoerce1 , assignToExprCtx -- * Core Utils , isUnboxableCon , isUnboxable , SlotCount(..) , slotCount , varSize , varSlotCount , typeSize , isVoid , isPtr , isSingleVar , isMultiVar , isMatchable , tyConVt , idVt , typeVt , uTypeVt , primRepVt , typePrimRep' , tyConPrimRep' , kindPrimRep' , primTypeVt , argVt , dataConType , isBoolDataCon , fixedLayout , stackSlotType , idPrimReps , typePrimReps , primRepSize , assocPrimReps , assocIdPrimReps , assocIdExprs , mkArityTag , toTypeList -- * Stg Utils , bindingRefs , rhsRefs , exprRefs , altRefs , argRefs , hasExport , collectTopIds , collectIds , removeTick , LiveVars , liveStatic , liveVars , stgTopBindLive , stgBindLive , stgBindRhsLive , stgRhsLive , stgArgLive , stgExprLive , stgAltLive , stgLetNoEscapeLive , bindees , isUpdatableRhs , stgLneLive , stgLneLive' , stgLneLiveExpr , isInlineExpr , inspectInlineBinding , inspectInlineRhs , isInlineForeignCall , isInlineApp ) where import GHC.Prelude import GHC.StgToJS.Types import GHC.StgToJS.ExprCtx import GHC.JS.Unsat.Syntax import GHC.JS.Make import GHC.JS.Transform import GHC.Core.DataCon import GHC.Core.TyCo.Rep hiding (typeSize) import GHC.Core.TyCon import GHC.Core.Type hiding (typeSize) import GHC.Stg.Syntax import GHC.Tc.Utils.TcType import GHC.Builtin.Types import GHC.Builtin.Types.Prim import GHC.Builtin.Names import GHC.Builtin.PrimOps (PrimOp(SeqOp), primOpIsReallyInline) import GHC.Types.RepType import GHC.Types.Var import GHC.Types.Var.Set import GHC.Types.Id import GHC.Types.Id.Info import GHC.Types.Unique.FM import GHC.Types.Unique.Set import GHC.Types.ForeignCall import GHC.Types.TyThing import GHC.Types.Name import GHC.Utils.Misc import GHC.Utils.Outputable hiding ((<>)) import GHC.Utils.Panic import qualified Data.Bits as Bits import qualified Data.Foldable as F import qualified Data.Set as S import qualified Data.List as L import Data.Set (Set) import Data.Monoid assignToTypedExprs :: [TypedExpr] -> [JExpr] -> JStat assignToTypedExprs tes es = assignAllEqual (concatMap typex_expr tes) es assignTypedExprs :: [TypedExpr] -> [TypedExpr] -> JStat assignTypedExprs tes es = -- TODO: check primRep (typex_typ) here? assignToTypedExprs tes (concatMap typex_expr es) assignToExprCtx :: ExprCtx -> [JExpr] -> JStat assignToExprCtx ctx es = assignToTypedExprs (ctxTarget ctx) es -- | Assign first expr only (if it exists), performing coercions between some -- PrimReps (e.g. StablePtr# and Addr#). assignCoerce1 :: [TypedExpr] -> [TypedExpr] -> JStat assignCoerce1 [x] [y] = assignCoerce x y assignCoerce1 [] [] = mempty assignCoerce1 _x _y = pprPanic "assignCoerce1" (vcat [ text "lengths do not match" -- FIXME: Outputable instance removed until JStg replaces JStat -- , ppr x -- , ppr y ]) -- | Assign p2 to p1 with optional coercion assignCoerce :: TypedExpr -> TypedExpr -> JStat -- Coercion between StablePtr# and Addr# assignCoerce (TypedExpr AddrRep [a_val, a_off]) (TypedExpr (BoxedRep (Just Unlifted)) [sptr]) = mconcat [ a_val |= var "h$stablePtrBuf" , a_off |= sptr ] assignCoerce (TypedExpr (BoxedRep (Just Unlifted)) [sptr]) (TypedExpr AddrRep [_a_val, a_off]) = sptr |= a_off assignCoerce p1 p2 = assignTypedExprs [p1] [p2] -------------------------------------------------------------------------------- -- Core Utils -------------------------------------------------------------------------------- -- | can we unbox C x to x, only if x is represented as a Number isUnboxableCon :: DataCon -> Bool isUnboxableCon dc | [t] <- dataConRepArgTys dc , [t1] <- typeVt (scaledThing t) = isUnboxable t1 && dataConTag dc == 1 && length (tyConDataCons $ dataConTyCon dc) == 1 | otherwise = False -- | one-constructor types with one primitive field represented as a JS Number -- can be unboxed isUnboxable :: VarType -> Bool isUnboxable DoubleV = True isUnboxable IntV = True -- includes Char# isUnboxable _ = False -- | Number of slots occupied by a PrimRep data SlotCount = NoSlot | OneSlot | TwoSlots deriving (Show,Eq,Ord) instance Outputable SlotCount where ppr = text . show -- | Return SlotCount as an Int slotCount :: SlotCount -> Int slotCount = \case NoSlot -> 0 OneSlot -> 1 TwoSlots -> 2 -- | Number of slots occupied by a value with the given VarType varSize :: VarType -> Int varSize = slotCount . varSlotCount varSlotCount :: VarType -> SlotCount varSlotCount VoidV = NoSlot varSlotCount LongV = TwoSlots -- hi, low varSlotCount AddrV = TwoSlots -- obj/array, offset varSlotCount _ = OneSlot typeSize :: Type -> Int typeSize t = sum . map varSize . typeVt $ t isVoid :: VarType -> Bool isVoid VoidV = True isVoid _ = False isPtr :: VarType -> Bool isPtr PtrV = True isPtr _ = False isSingleVar :: VarType -> Bool isSingleVar v = varSlotCount v == OneSlot isMultiVar :: VarType -> Bool isMultiVar v = case varSlotCount v of NoSlot -> False OneSlot -> False TwoSlots -> True -- | can we pattern match on these values in a case? isMatchable :: [VarType] -> Bool isMatchable [DoubleV] = True isMatchable [IntV] = True isMatchable _ = False tyConVt :: HasDebugCallStack => TyCon -> [VarType] tyConVt = typeVt . mkTyConTy idVt :: HasDebugCallStack => Id -> [VarType] idVt = typeVt . idType typeVt :: HasDebugCallStack => Type -> [VarType] typeVt t | isRuntimeRepKindedTy t = [] typeVt t = map primRepVt (typePrimRep t)-- map uTypeVt (repTypeArgs t) -- only use if you know it's not an unboxed tuple uTypeVt :: HasDebugCallStack => UnaryType -> VarType uTypeVt ut | isRuntimeRepKindedTy ut = VoidV -- | isRuntimeRepTy ut = VoidV -- GHC panics on this otherwise | Just (tc, ty_args) <- splitTyConApp_maybe ut , length ty_args /= tyConArity tc = PtrV | isPrimitiveType ut = (primTypeVt ut) | otherwise = case typePrimRep' ut of [] -> VoidV [pt] -> primRepVt pt _ -> pprPanic "uTypeVt: not unary" (ppr ut) primRepVt :: HasDebugCallStack => PrimRep -> VarType primRepVt VoidRep = VoidV primRepVt (BoxedRep _) = PtrV -- fixme does ByteArray# ever map to this? primRepVt IntRep = IntV primRepVt Int8Rep = IntV primRepVt Int16Rep = IntV primRepVt Int32Rep = IntV primRepVt WordRep = IntV primRepVt Word8Rep = IntV primRepVt Word16Rep = IntV primRepVt Word32Rep = IntV primRepVt Int64Rep = LongV primRepVt Word64Rep = LongV primRepVt AddrRep = AddrV primRepVt FloatRep = DoubleV primRepVt DoubleRep = DoubleV primRepVt (VecRep{}) = error "uTypeVt: vector types are unsupported" typePrimRep' :: HasDebugCallStack => UnaryType -> [PrimRep] typePrimRep' ty = kindPrimRep' empty (typeKind ty) -- | Find the primitive representation of a 'TyCon'. Defined here to -- avoid module loops. Call this only on unlifted tycons. tyConPrimRep' :: HasDebugCallStack => TyCon -> [PrimRep] tyConPrimRep' tc = kindPrimRep' empty res_kind where res_kind = tyConResKind tc -- | Take a kind (of shape @TYPE rr@) and produce the 'PrimRep's -- of values of types of this kind. kindPrimRep' :: HasDebugCallStack => SDoc -> Kind -> [PrimRep] kindPrimRep' doc ki | Just ki' <- coreView ki = kindPrimRep' doc ki' kindPrimRep' doc (TyConApp _typ [runtime_rep]) = -- ASSERT( typ `hasKey` tYPETyConKey ) runtimeRepPrimRep doc runtime_rep kindPrimRep' doc ki = pprPanic "kindPrimRep'" (ppr ki $$ doc) primTypeVt :: HasDebugCallStack => Type -> VarType primTypeVt t = case tyConAppTyCon_maybe (unwrapType t) of Nothing -> error "primTypeVt: not a TyCon" Just tc | tc == charPrimTyCon -> IntV | tc == intPrimTyCon -> IntV | tc == wordPrimTyCon -> IntV | tc == floatPrimTyCon -> DoubleV | tc == doublePrimTyCon -> DoubleV | tc == int8PrimTyCon -> IntV | tc == word8PrimTyCon -> IntV | tc == int16PrimTyCon -> IntV | tc == word16PrimTyCon -> IntV | tc == int32PrimTyCon -> IntV | tc == word32PrimTyCon -> IntV | tc == int64PrimTyCon -> LongV | tc == word64PrimTyCon -> LongV | tc == addrPrimTyCon -> AddrV | tc == stablePtrPrimTyCon -> AddrV | tc == stableNamePrimTyCon -> PtrV | tc == statePrimTyCon -> VoidV | tc == proxyPrimTyCon -> VoidV | tc == realWorldTyCon -> VoidV | tc == threadIdPrimTyCon -> PtrV | tc == weakPrimTyCon -> PtrV | tc == arrayPrimTyCon -> ArrV | tc == smallArrayPrimTyCon -> ArrV | tc == byteArrayPrimTyCon -> ObjV -- can contain any JS reference, used for JSVal | tc == mutableArrayPrimTyCon -> ArrV | tc == smallMutableArrayPrimTyCon -> ArrV | tc == mutableByteArrayPrimTyCon -> ObjV -- can contain any JS reference, used for JSVal | tc == mutVarPrimTyCon -> PtrV | tc == mVarPrimTyCon -> PtrV | tc == tVarPrimTyCon -> PtrV | tc == bcoPrimTyCon -> PtrV -- unsupported? | tc == stackSnapshotPrimTyCon -> PtrV | tc == ioPortPrimTyCon -> PtrV -- unsupported? | tc == anyTyCon -> PtrV | tc == compactPrimTyCon -> PtrV -- unsupported? | tc == eqPrimTyCon -> VoidV -- coercion token? | tc == eqReprPrimTyCon -> VoidV -- role | tc == unboxedUnitTyCon -> VoidV -- Void# | otherwise -> PtrV -- anything else must be some boxed thing argVt :: StgArg -> VarType argVt a = uTypeVt . stgArgType $ a dataConType :: DataCon -> Type dataConType dc = idType (dataConWrapId dc) isBoolDataCon :: DataCon -> Bool isBoolDataCon dc = isBoolTy (dataConType dc) -- standard fixed layout: payload types -- payload starts at .d1 for heap objects, entry closest to Sp for stack frames fixedLayout :: [VarType] -> CILayout fixedLayout vts = CILayoutFixed (sum (map varSize vts)) vts -- 2-var values might have been moved around separately, use DoubleV as substitute -- ObjV is 1 var, so this is no problem for implicit metadata stackSlotType :: Id -> VarType stackSlotType i | OneSlot <- varSlotCount otype = otype | otherwise = DoubleV where otype = uTypeVt (idType i) idPrimReps :: Id -> [PrimRep] idPrimReps = typePrimReps . idType typePrimReps :: Type -> [PrimRep] typePrimReps = typePrimRep . unwrapType primRepSize :: PrimRep -> SlotCount primRepSize p = varSlotCount (primRepVt p) -- | Associate the given values to each RrimRep in the given order, taking into -- account the number of slots per PrimRep assocPrimReps :: [PrimRep] -> [JExpr] -> [(PrimRep, [JExpr])] assocPrimReps [] _ = [] assocPrimReps (r:rs) vs = case (primRepSize r,vs) of (NoSlot, xs) -> (r,[]) : assocPrimReps rs xs (OneSlot, x:xs) -> (r,[x]) : assocPrimReps rs xs (TwoSlots, x:y:xs) -> (r,[x,y]) : assocPrimReps rs xs err -> pprPanic "assocPrimReps" (ppr $ map (satJExpr Nothing) <$> err) -- | Associate the given values to the Id's PrimReps, taking into account the -- number of slots per PrimRep assocIdPrimReps :: Id -> [JExpr] -> [(PrimRep, [JExpr])] assocIdPrimReps i = assocPrimReps (idPrimReps i) -- | Associate the given JExpr to the Id's PrimReps, taking into account the -- number of slots per PrimRep assocIdExprs :: Id -> [JExpr] -> [TypedExpr] assocIdExprs i es = fmap (uncurry TypedExpr) (assocIdPrimReps i es) mkArityTag :: Int -> Int -> Int mkArityTag arity registers = arity Bits..|. (registers `Bits.shiftL` 8) toTypeList :: [VarType] -> [Int] toTypeList = concatMap (\x -> replicate (varSize x) (fromEnum x)) -------------------------------------------------------------------------------- -- Stg Utils -------------------------------------------------------------------------------- s :: a -> Set a s = S.singleton l :: (a -> Set Id) -> [a] -> Set Id l = F.foldMap -- | collect Ids that this binding refers to -- (does not include the bindees themselves) -- first argument is Id -> StgExpr map for unfloated arguments bindingRefs :: UniqFM Id CgStgExpr -> CgStgBinding -> Set Id bindingRefs u = \case StgNonRec _ rhs -> rhsRefs u rhs StgRec bs -> l (rhsRefs u . snd) bs rhsRefs :: UniqFM Id CgStgExpr -> CgStgRhs -> Set Id rhsRefs u = \case StgRhsClosure _ _ _ _ body _ -> exprRefs u body StgRhsCon _ccs d _mu _ticks args _ -> l s [ i | AnId i <- dataConImplicitTyThings d] <> l (argRefs u) args exprRefs :: UniqFM Id CgStgExpr -> CgStgExpr -> Set Id exprRefs u = \case StgApp f args -> s f <> l (argRefs u) args StgConApp d _n args _ -> l s [ i | AnId i <- dataConImplicitTyThings d] <> l (argRefs u) args StgOpApp _ args _ -> l (argRefs u) args StgLit {} -> mempty StgCase expr _ _ alts -> exprRefs u expr <> mconcat (fmap (altRefs u) alts) StgLet _ bnd expr -> bindingRefs u bnd <> exprRefs u expr StgLetNoEscape _ bnd expr -> bindingRefs u bnd <> exprRefs u expr StgTick _ expr -> exprRefs u expr altRefs :: UniqFM Id CgStgExpr -> CgStgAlt -> Set Id altRefs u alt = exprRefs u (alt_rhs alt) argRefs :: UniqFM Id CgStgExpr -> StgArg -> Set Id argRefs u = \case StgVarArg id | Just e <- lookupUFM u id -> exprRefs u e | otherwise -> s id _ -> mempty hasExport :: CgStgBinding -> Bool hasExport bnd = case bnd of StgNonRec b e -> isExportedBind b e StgRec bs -> any (uncurry isExportedBind) bs where isExportedBind _i (StgRhsCon _cc con _ _ _ _) = getUnique con == staticPtrDataConKey isExportedBind _ _ = False collectTopIds :: CgStgBinding -> [Id] collectTopIds (StgNonRec b _) = [b] collectTopIds (StgRec bs) = let xs = map (zapFragileIdInfo . fst) bs in seqList xs `seq` xs collectIds :: UniqFM Id CgStgExpr -> CgStgBinding -> [Id] collectIds unfloated b = let xs = map zapFragileIdInfo . filter acceptId $ S.toList (bindingRefs unfloated b) in seqList xs `seq` xs where acceptId i = all ($ i) [not . isForbidden] -- fixme test this: [isExported[isGlobalId, not.isForbidden] -- the GHC.Prim module has no js source file isForbidden i | Just m <- nameModule_maybe (getName i) = m == gHC_PRIM | otherwise = False removeTick :: CgStgExpr -> CgStgExpr removeTick (StgTick _ e) = e removeTick e = e ----------------------------------------------------- -- Live vars -- -- TODO: should probably be moved into GHC.Stg.LiveVars type LiveVars = DVarSet liveStatic :: LiveVars -> LiveVars liveStatic = filterDVarSet isGlobalId liveVars :: LiveVars -> LiveVars liveVars = filterDVarSet (not . isGlobalId) stgTopBindLive :: CgStgTopBinding -> [(Id, LiveVars)] stgTopBindLive = \case StgTopLifted b -> stgBindLive b StgTopStringLit {} -> [] stgBindLive :: CgStgBinding -> [(Id, LiveVars)] stgBindLive = \case StgNonRec b rhs -> [(b, stgRhsLive rhs)] StgRec bs -> map (\(b,rhs) -> (b, stgRhsLive rhs)) bs stgBindRhsLive :: CgStgBinding -> LiveVars stgBindRhsLive b = let (bs, ls) = unzip (stgBindLive b) in delDVarSetList (unionDVarSets ls) bs stgRhsLive :: CgStgRhs -> LiveVars stgRhsLive = \case StgRhsClosure _ _ _ args e _ -> delDVarSetList (stgExprLive True e) args StgRhsCon _ _ _ _ args _ -> unionDVarSets (map stgArgLive args) stgArgLive :: StgArg -> LiveVars stgArgLive = \case StgVarArg occ -> unitDVarSet occ StgLitArg {} -> emptyDVarSet stgExprLive :: Bool -> CgStgExpr -> LiveVars stgExprLive includeLHS = \case StgApp occ args -> unionDVarSets (unitDVarSet occ : map stgArgLive args) StgLit {} -> emptyDVarSet StgConApp _dc _n args _tys -> unionDVarSets (map stgArgLive args) StgOpApp _op args _ty -> unionDVarSets (map stgArgLive args) StgCase e b _at alts | includeLHS -> el `unionDVarSet` delDVarSet al b | otherwise -> delDVarSet al b where al = unionDVarSets (map stgAltLive alts) el = stgExprLive True e StgLet _ b e -> delDVarSetList (stgBindRhsLive b `unionDVarSet` stgExprLive True e) (bindees b) StgLetNoEscape _ b e -> delDVarSetList (stgBindRhsLive b `unionDVarSet` stgExprLive True e) (bindees b) StgTick _ti e -> stgExprLive True e stgAltLive :: CgStgAlt -> LiveVars stgAltLive alt = delDVarSetList (stgExprLive True (alt_rhs alt)) (alt_bndrs alt) stgLetNoEscapeLive :: Bool -> StgBinding -> StgExpr -> LiveVars stgLetNoEscapeLive _someBool _b _e = panic "stgLetNoEscapeLive" bindees :: CgStgBinding -> [Id] bindees = \case StgNonRec b _e -> [b] StgRec bs -> map fst bs isUpdatableRhs :: CgStgRhs -> Bool isUpdatableRhs (StgRhsClosure _ _ u _ _ _) = isUpdatable u isUpdatableRhs _ = False stgLneLive' :: CgStgBinding -> [Id] stgLneLive' b = filter (`notElem` bindees b) (stgLneLive b) stgLneLive :: CgStgBinding -> [Id] stgLneLive (StgNonRec _b e) = stgLneLiveExpr e stgLneLive (StgRec bs) = L.nub $ concatMap (stgLneLiveExpr . snd) bs stgLneLiveExpr :: CgStgRhs -> [Id] stgLneLiveExpr rhs = dVarSetElems (liveVars $ stgRhsLive rhs) -- stgLneLiveExpr (StgRhsClosure _ _ _ _ e) = dVarSetElems (liveVars (stgExprLive e)) -- stgLneLiveExpr StgRhsCon {} = [] -- | returns True if the expression is definitely inline isInlineExpr :: UniqSet Id -> CgStgExpr -> (UniqSet Id, Bool) isInlineExpr v = \case StgApp i args -> (emptyUniqSet, isInlineApp v i args) StgLit{} -> (emptyUniqSet, True) StgConApp{} -> (emptyUniqSet, True) StgOpApp (StgFCallOp f _) _ _ -> (emptyUniqSet, isInlineForeignCall f) StgOpApp (StgPrimOp SeqOp) [StgVarArg e] t -> (emptyUniqSet, e `elementOfUniqSet` v || isStrictType t) StgOpApp (StgPrimOp op) _ _ -> (emptyUniqSet, primOpIsReallyInline op) StgOpApp (StgPrimCallOp _c) _ _ -> (emptyUniqSet, True) StgCase e b _ alts ->let (_ve, ie) = isInlineExpr v e v' = addOneToUniqSet v b (vas, ias) = unzip $ map (isInlineExpr v') (fmap alt_rhs alts) vr = L.foldl1' intersectUniqSets vas in (vr, (ie || b `elementOfUniqSet` v) && and ias) StgLet _ b e -> isInlineExpr (inspectInlineBinding v b) e StgLetNoEscape _ _b e -> isInlineExpr v e StgTick _ e -> isInlineExpr v e inspectInlineBinding :: UniqSet Id -> CgStgBinding -> UniqSet Id inspectInlineBinding v = \case StgNonRec i r -> inspectInlineRhs v i r StgRec bs -> foldl' (\v' (i,r) -> inspectInlineRhs v' i r) v bs inspectInlineRhs :: UniqSet Id -> Id -> CgStgRhs -> UniqSet Id inspectInlineRhs v i = \case StgRhsCon{} -> addOneToUniqSet v i StgRhsClosure _ _ ReEntrant _ _ _ -> addOneToUniqSet v i _ -> v isInlineForeignCall :: ForeignCall -> Bool isInlineForeignCall (CCall (CCallSpec _ cconv safety)) = not (playInterruptible safety) && not (cconv /= JavaScriptCallConv && playSafe safety) isInlineApp :: UniqSet Id -> Id -> [StgArg] -> Bool isInlineApp v i = \case _ | isJoinId i -> False [] -> isUnboxedTupleType (idType i) || isStrictType (idType i) || i `elementOfUniqSet` v [StgVarArg a] | DataConWrapId dc <- idDetails i , isNewTyCon (dataConTyCon dc) , isStrictType (idType a) || a `elementOfUniqSet` v || isStrictId a -> True _ -> False