ghc-9.12.1: GHC/StgToJS/FFI.hs
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
module GHC.StgToJS.FFI
( genPrimCall
, genForeignCall
)
where
import GHC.Prelude
import GHC.JS.JStg.Syntax
import GHC.JS.Make
import GHC.StgToJS.Arg
import GHC.StgToJS.ExprCtx
import GHC.StgToJS.Ids
import GHC.StgToJS.Literal
import GHC.StgToJS.Monad
import GHC.StgToJS.Regs
import GHC.StgToJS.Symbols
import GHC.StgToJS.Types
import GHC.StgToJS.Utils
import GHC.Types.RepType
import GHC.Types.ForeignCall
import GHC.Types.Unique.Map
import GHC.Stg.Syntax
import GHC.Builtin.PrimOps
import GHC.Builtin.Types.Prim
import GHC.Core.Type hiding (typeSize)
import GHC.Utils.Misc
import GHC.Utils.Outputable (renderWithContext, defaultSDocContext, ppr)
import GHC.Data.FastString
import Data.Char
import Data.Monoid
import qualified Data.List as L
genPrimCall :: ExprCtx -> PrimCall -> [StgArg] -> Type -> G (JStgStat, ExprResult)
genPrimCall ctx (PrimCall lbl _) args t = do
j <- parseFFIPattern False False False (unpackFS hdStr ++ unpackFS lbl) t (concatMap typex_expr $ ctxTarget ctx) args
return (j, ExprInline)
-- | generate the actual call
{-
parse FFI patterns:
"&value -> value
1. "function" -> ret = function(...)
2. "$r = $1.f($2) -> r1 = a1.f(a2)
arguments, $1, $2, $3 unary arguments
$1_1, $1_2, for a binary argument
return type examples
1. $r unary return
2. $r1, $r2 binary return
3. $r1, $r2, $r3_1, $r3_2 unboxed tuple return
-}
parseFFIPattern :: Bool -- ^ catch exception and convert them to haskell exceptions
-> Bool -- ^ async (only valid with javascript calling conv)
-> Bool -- ^ using javascript calling convention
-> String
-> Type
-> [JStgExpr]
-> [StgArg]
-> G JStgStat
parseFFIPattern catchExcep async jscc pat t es as
| catchExcep = do
c <- parseFFIPatternA async jscc pat t es as
-- Generate:
-- try {
-- `c`;
-- } catch(except) {
-- return h$throwJSException(except);
-- }
return (TryStat c exceptStr (ReturnStat (ApplExpr hdThrowJSException [except])) mempty)
| otherwise = parseFFIPatternA async jscc pat t es as
parseFFIPatternA :: Bool -- ^ async
-> Bool -- ^ using JavaScript calling conv
-> String
-> Type
-> [JStgExpr]
-> [StgArg]
-> G JStgStat
-- async calls get an extra callback argument
-- call it with the result
parseFFIPatternA True True pat t es as = do
cb <- freshIdent
x <- freshIdent
d <- freshIdent
stat <- parseFFIPattern' (Just (toJExpr cb)) True pat t es as
return $ mconcat
[ x ||= (toJExpr (jhFromList [(mv, null_)]))
, cb ||= ApplExpr hdMkForeignCallback [toJExpr x]
, stat
, IfStat (InfixExpr StrictEqOp (toJExpr x .^ mv) null_)
(mconcat
[ toJExpr x .^ mv |= UOpExpr NewOp (ApplExpr hdMVar [])
, sp |= Add sp one_
, (IdxExpr stack sp) |= hdUnboxFFIResult
, ReturnStat $ ApplExpr hdTakeMVar [toJExpr x .^ mv]
])
(mconcat
[ d ||= toJExpr x .^ mv
, copyResult (toJExpr d)
])
]
where nrst = typeSize t
copyResult d = assignAllEqual es (map (IdxExpr d . toJExpr) [0..nrst-1])
parseFFIPatternA _async javascriptCc pat t es as =
parseFFIPattern' Nothing javascriptCc pat t es as
-- parseFFIPatternA _ _ _ _ _ _ = error "parseFFIPattern: non-JavaScript pattern must be synchronous"
parseFFIPattern' :: Maybe JStgExpr -- ^ Nothing for sync, Just callback for async
-> Bool -- ^ javascript calling convention used
-> String -- ^ pattern called
-> Type -- ^ return type
-> [JStgExpr] -- ^ expressions to return in (may be more than necessary)
-> [StgArg] -- ^ arguments
-> G JStgStat
parseFFIPattern' callback javascriptCc pat t ret args
| not javascriptCc = mkApply pat
| otherwise = mkApply pat
where
tgt = take (typeSize t) ret
-- automatic apply, build call and result copy
mkApply f
| Just cb <- callback = do
(stats, as) <- unzip <$> mapM (genFFIArg javascriptCc) args
cs <- getSettings
return $ traceCall cs as <> mconcat stats <> ApplStat f' (concat as++[cb])
| {-ts@-}
(t:ts') <- tgt = do
(stats, as) <- unzip <$> mapM (genFFIArg javascriptCc) args
cs <- getSettings
return $ traceCall cs as
<> mconcat stats
<> (t |= ApplExpr f' (concat as) )
<> copyResult ts'
-- _ -> error "mkApply: empty list"
| otherwise = do
(stats, as) <- unzip <$> mapM (genFFIArg javascriptCc) args
cs <- getSettings
return $ traceCall cs as <> mconcat stats <> ApplStat f' (concat as)
where f' = toJExpr (global $ mkFastString f)
copyResult rs = mconcat $ zipWith (\t r -> toJExpr r |= toJExpr t) (enumFrom Ret1) rs
traceCall cs as
| csTraceForeign cs = ApplStat hdTraceForeign [toJExpr pat, toJExpr as]
| otherwise = mempty
-- generate arg to be passed to FFI call, with marshalling JStgStat to be run
-- before the call
genFFIArg :: Bool -> StgArg -> G (JStgStat, [JStgExpr])
genFFIArg _isJavaScriptCc (StgLitArg l) = (mempty,) <$> genLit l
genFFIArg isJavaScriptCc a@(StgVarArg i)
| not isJavaScriptCc &&
(tycon == byteArrayPrimTyCon || tycon == mutableByteArrayPrimTyCon) =
(\x -> (mempty,[x, zero_])) <$> varForId i
| isVoid r = return (mempty, [])
-- | Just x <- marshalFFIArg a = x
| isMultiVar r = (mempty,) <$> mapM (varForIdN i) [1..varSize r]
| otherwise = (\x -> (mempty,[x])) <$> varForId i
where
tycon = tyConAppTyCon (unwrapType arg_ty)
arg_ty = stgArgType a
r = unaryTypeJSRep arg_ty
genForeignCall :: HasDebugCallStack
=> ExprCtx
-> ForeignCall
-> Type
-> [JStgExpr]
-> [StgArg]
-> G (JStgStat, ExprResult)
genForeignCall _ctx
(CCall (CCallSpec (StaticTarget _ tgt Nothing True)
JavaScriptCallConv
PlayRisky))
_t
[obj]
args
| tgt == hdBuildObjectStr
, Just pairs <- getObjectKeyValuePairs args = do
pairs' <- mapM (\(k,v) -> genArg v >>= \vs -> return (k, head vs)) pairs
return ( (|=) obj (ValExpr (JHash $ listToUniqMap pairs'))
, ExprInline
)
genForeignCall ctx (CCall (CCallSpec ccTarget cconv safety)) t tgt args = do
emitForeign (ctxSrcSpan ctx) lbl safety cconv (map showArgType args) (showType t)
(,exprResult) <$> parseFFIPattern catchExcep async isJsCc (unpackFS lbl) t tgt' args
where
isJsCc = cconv == JavaScriptCallConv
lbl | (StaticTarget _ clbl _mpkg _isFunPtr) <- ccTarget
= let clbl' = unpackFS clbl
hDollarS = unpackFS hdStr
in if | isJsCc -> clbl
| wrapperPrefix `L.isPrefixOf` clbl' ->
mkFastString (hDollarS ++ (drop 2 $ dropWhile isDigit $ drop (length wrapperPrefix) clbl'))
| otherwise -> mkFastString $ hDollarS ++ clbl'
| otherwise = hdCallDynamicStr
exprResult | async = ExprCont
| otherwise = ExprInline
catchExcep = (cconv == JavaScriptCallConv) &&
playSafe safety || playInterruptible safety
async | isJsCc = playInterruptible safety
| otherwise = playInterruptible safety || playSafe safety
tgt' | async = take (length tgt) jsRegsFromR1
| otherwise = tgt
wrapperPrefix = unpackFS wrapperColonStr
getObjectKeyValuePairs :: [StgArg] -> Maybe [(FastString, StgArg)]
getObjectKeyValuePairs [] = Just []
getObjectKeyValuePairs (k:v:xs)
| Just t <- argJSStringLitUnfolding k =
fmap ((t,v):) (getObjectKeyValuePairs xs)
getObjectKeyValuePairs _ = Nothing
argJSStringLitUnfolding :: StgArg -> Maybe FastString
argJSStringLitUnfolding (StgVarArg _v) = Nothing -- fixme
argJSStringLitUnfolding _ = Nothing
showArgType :: StgArg -> FastString
showArgType a = showType (stgArgType a)
showType :: Type -> FastString
showType t
| Just tc <- tyConAppTyCon_maybe (unwrapType t) =
mkFastString (renderWithContext defaultSDocContext (ppr tc))
| otherwise = unknown