Plan 9 from Bell Labs’s /usr/web/sources/contrib/fernan/nhc98/src/compiler98/ForeignCode.hs

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


-- | Needs 'IdSupply'

module ForeignCode
  ( Foreign(..), toForeign, strForeign
  , ForeignMemo, foreignMemo
  , ImpExp(..)
  , Style(..)
  , Arg(..), Res
  , cTypename
  ) where

import Maybe (fromJust,isNothing)
import List (isPrefixOf,isSuffixOf,intersperse)
import SysDeps (unpackPS)
import Syntax
import Id
import Info
import NT
import TokenId
import qualified Data.Map as Map
import Util.Extra (mix,warning)
import GcodeLow (fun,foreignfun,fixStr)


data ImpExp  = Imported | Exported
data Style   = Ordinary | CCast | Address | FunAddress | Dynamic | Wrapper
             deriving Eq
data Foreign = Foreign  ImpExp  -- import or export?
                        Bool    -- generate C prototype?
                        Style   -- cast / address / dynamic etc
                        (Maybe FilePath) -- C header file (for proto checking)
                        String  -- foreign function name
                        TokenId -- Haskell function name
                        Int     -- arity
                        [Arg]   -- argument types
                        Res     -- result type

instance Show ImpExp where
  showsPrec _ Imported = showString "import"
  showsPrec _ Exported = showString "export"

instance Show Style where
  showsPrec _ Ordinary = showString "ccall"
  showsPrec _ CCast    = showString "cast"
  showsPrec _ Address  = showString "ccall &"
  showsPrec _ FunAddress  = showString "ccall &"
  showsPrec _ Dynamic  = showString "ccall dynamic"
  showsPrec _ Wrapper  = showString "ccall wrapper"

instance Show Foreign where
  showsPrec _ (Foreign ie _proto style incl cname hname arity args res) =
    word "foreign" . space . shows ie . space . shows style . space .
    showChar '"' . maybe id (\i-> word i . space) incl . word cname .
    showChar '"' . space .
    shows hname . space . shows arity . showString " :: " .
    showString (mix " -> " (map show args)) .
    showString " -> " . shows res

data Arg = Int8  | Int16  | Int32  | Int64
         | Word8 | Word16 | Word32 | Word64
         | Float | Double | Char   | Bool | Int
         | Ptr   | FunPtr [Arg] | StablePtr | ForeignPtr
         | Addr  | ForeignObj | Integer | PackedString
         | Unknown String | Unit | HaskellFun [Arg]

instance Show Arg where
  showsPrec _ Int8         = showString "FFI.Int8"
  showsPrec _ Int16        = showString "FFI.Int16"
  showsPrec _ Int32        = showString "FFI.Int32"
  showsPrec _ Int64        = showString "FFI.Int64"
  showsPrec _ Word8        = showString "FFI.Word8"
  showsPrec _ Word16       = showString "FFI.Word16"
  showsPrec _ Word32       = showString "FFI.Word32"
  showsPrec _ Word64       = showString "FFI.Word64"
  showsPrec _ Int          = showString "Prelude.Int"
  showsPrec _ Float        = showString "Prelude.Float"
  showsPrec _ Double       = showString "Prelude.Double"
  showsPrec _ Char         = showString "Prelude.Char"
  showsPrec _ Bool         = showString "Prelude.Bool"
  showsPrec _ Ptr          = showString "FFI.Ptr"
  showsPrec _ (FunPtr t)   = showString "FFI.FunPtr"
      . parens (showString (concat (intersperse " -> " (map show t))))
  showsPrec _ StablePtr    = showString "FFI.StablePtr"
  showsPrec _ ForeignPtr   = showString "FFI.ForeignPtr"
  showsPrec _ Addr         = showString "FFI.Addr"              -- deprecated
  showsPrec _ ForeignObj   = showString "FFI.ForeignObj"        -- deprecated
  showsPrec _ Integer      = showString "Prelude.Integer"       -- non-standard
  showsPrec _ PackedString = showString "PackedString.PackedString" -- non-std
  showsPrec _ Unit         = showString "Prelude.()"
  showsPrec _ (HaskellFun as)  =
      parens (showString (concat (intersperse " -> " (map show as))))
  showsPrec _ (Unknown s)  = showString s

-- Note: as of 2000-10-18, the result can never have an IO type - pure only.
-- (IO results are created by wrapping auxiliary _mkIOokN around a pure call.)
type Res = Arg

foreignname, localname, profname :: TokenId -> ShowS
foreignname hname = showString foreignfun . fixStr (show hname)
localname   hname = showString fun        . fixStr (show hname)
profname    hname = showString "pf_"      . fixStr (show hname)
----

toForeign :: Map.Map Id Info -> ForeignMemo
              -> CallConv -> ImpExp -> String -> Int -> Id -> Foreign
-- toForeign _symboltable _memo (Other callconv) _ie _cname _arity _var
  -- callconv `notElem` ["primitive","fastccall","faststdcall","builtin"] =
  --  error ("Foreign calling convention \""++callconv++"\" not supported.")
toForeign symboltable memo callconv ie cname arity var =
    Foreign ie proto style include cfunc hname arity' args res
  where
    info = fromJust (Map.lookup var symboltable)
    hname = tidI info
    hnameStr = (reverse . (\w->if head w=='#' then tail w else w)
               . unpackPS . extractV) hname
    (args,res) = searchType style symboltable memo info
    (cfunc,style,include) = case callconv of Cast -> (hnameStr,CCast,Nothing)
                                             _    -> parseEntity cname hnameStr
    proto = (callconv/=Noproto) && (isNothing include)
    arity' = if arity==length args then arity
             else error ("foreign function: arity does not match: "
                         ++hnameStr++" has "++show arity
                         ++" args, expected "++show (length args)++"\n")

parseEntity :: String -> String -> (String,Style,Maybe FilePath)
parseEntity entity hname =
    case words entity of
      ("dynamic":_) -> (hname, Dynamic, Nothing)
      ("wrapper":_) -> (hname, Wrapper, Nothing)
      ("static":ws) -> filename ws
      ws            -> filename ws
  where
    filename []     = (hname, Ordinary, Nothing)
    filename (w:ws) | ".h" `isSuffixOf` w = address (Just w) ws
                    | otherwise           = address Nothing  (w:ws)
    address file ("&":ws) = lib file Address  ws
    address file (w:ws)   | "&" `isPrefixOf` w = lib file Address (tail w:ws)
    address file    ws    = lib file Ordinary ws
    lib file style []     = (hname, style, file)
    lib file style (w:ws) | "[" `isPrefixOf` w && "]" `isSuffixOf` w
                                      = cfunc file style ws
                          | otherwise = cfunc file style (w:ws)
    cfunc file style []      = (hname, style, file)
    cfunc file style [cname] = (cname, style, file)
    cfunc _file _style _ws      =
        error ("Couldn't parse entity string in foreign import: "++entity)


searchType :: Style -> Map.Map Id Info -> ForeignMemo -> Info -> ([Arg],Res)
searchType style st (arrow,io) info =
  let
    toList (NTcons c _ nts) | c==arrow  = let [a,b] = nts in a: toList b
    toList (NTcons c _ nts) | c==io     = let [a]   = nts in [a] -- within FunPtr
    toList (NTstrict nt)  = toList nt
    toList nt             = [nt]

    toTid (NTcons c _ nts)  =
      case Map.lookup c st of
        Just i | isRealData i ->
                   case toArg (tidI i) of
                     FunPtr _ -> FunPtr (map toTid (toList (head nts)))
                     HaskellFun _ -> HaskellFun (map toTid (toList (head nts)))
                     t        -> t
               | otherwise -> toTid (getNT (isRenamingFor st i))
    toTid (NTapp t1 _t2)  = toTid t1
    toTid (NTstrict t)    = toTid t
    toTid t = Unknown (show t)  -- error ("Unrecognised NT: "++show t)
                -- 'Unknown' lets polymorphic heap-values across unmolested

    toArg t | t==tInt           = Int
         --   t==tWord          = Word32
            | t==tBool          = Bool
            | t==tChar          = Char
            | t==tFloat         = Float
            | t==tDouble        = Double
            | t==tPtr           = Ptr
            | t==tPtrBC         = Ptr
            | t==tFunPtr        = FunPtr []
            | t==tFunPtrBC      = FunPtr []
            | t==tStablePtr     = StablePtr
            | t==tStablePtrBC   = StablePtr
            | t==tForeignPtr    = ForeignPtr
            | t==tForeignPtrBC  = ForeignPtr
            | t==tAddr          = Addr          -- deprecated
            | t==tAddrBC        = Addr          -- deprecated
            | t==tForeignObj    = ForeignObj    -- deprecated
            | t==tForeignObjBC  = ForeignObj    -- deprecated
            | t==(t_Tuple 0)    = Unit  -- no void args, but need void results
            | t==tInt8          = Int8
            | t==tInt8BC        = Int8
            | t==tInt16         = Int16
            | t==tInt16BC       = Int8
            | t==tInt32         = Int32
            | t==tInt32BC       = Int32
            | t==tInt64         = Int64
            | t==tInt64BC       = Int64
            | t==tWord8         = Word8
            | t==tWord8BC       = Word8
            | t==tWord16        = Word16
            | t==tWord16BC      = Word16
            | t==tWord32        = Word32
            | t==tWord32BC      = Word32
            | t==tWord64        = Word64
            | t==tWord64BC      = Word64
            | t==tPackedString  = PackedString  -- non-standard
            | t==tInteger       = Integer               -- non-standard
            | style==Wrapper
              && t==t_Arrow     = HaskellFun [] -- foreign export "wrapper"
            | otherwise         =
                    warning ("foreign import/export has non-primitive type: "
                             ++show t++"\n") (Unknown (show t))

    getNT (NewType _ _ _ [nt]) = nt
    getNT (NewType _ _ _ (nt:_)) = nt
    getNT _                    = error ("Unable to retrieve newtype info.")

    splitRes args = (init args, last args)

  in
    (splitRes . map toTid . toList . getNT . ntI) info

----
type ForeignMemo = (Id,Id)

foreignMemo :: Map.Map Id Info -> ForeignMemo
foreignMemo st =
--    (findFirst (check t_Arrow . flip Map.lookup st) [1..]
--    ,findFirst (check tIO     . flip Map.lookup st) [1..])
    (findFirst (check t_Arrow) minfos
    ,findFirst (check tIO    ) minfos)
  where
    minfos = map (Just . snd) (Map.toList st) ++ [Nothing]
    check tid (Just info) | cmpTid tid info  = Just (uniqueI info)
                          | otherwise        = Nothing
        -- If the ident doesn't exist after typecheck, it won't be used!
    check _tid Nothing                       = error "ForeignCode.foreignMemo: used unused identifier?"

findFirst :: (a->Maybe b) -> [a] -> b
-- specification:   findFirst = head . catMaybes . map
findFirst _ [] = error "findFirst failed"
findFirst f (x:xs) =
    case f x of
      Just b  -> b
      Nothing -> findFirst f xs

----

strForeign :: Foreign -> ShowS
strForeign f@(Foreign Imported proto style incl cname hname arity args res) =
    nl . comment (shows f) . nl .
    maybe id
          (\i-> word "#include " . showChar '"' . word i . showChar '"' . nl)
          incl .
    (if proto then genProto style else id) .
    word "#ifdef PROFILE" . nl .
    word "static SInfo" . space . profinfo . space . equals . space .
      opencurly . strquote (word modname) . comma .
                  strquote (shows hname) . comma .
                  strquote (shows res) .
      closecurly . semi .
    word "#endif" . nl .
    word "C_HEADER" . parens (foreignname hname) . space .
    opencurly . nl .
      indent . word "NodePtr nodeptr" . semi .
      cResDecl res .
      listsep semi (zipWith cArg args [1..]) . semi .
      foldr (.) id (zipWith cArgDefn args [1..]) . nl .
      (case style of
         CCast -> cCast arity res
         Address -> cAddr cname res
         FunAddress -> cFunAddr cname
         Dynamic -> cDynamic arity res
         _ -> if length args == 1 && noarg (head args)
              then cCall cname 0 res
              else cCall cname arity res) . nl .
      cFooter profinfo res .
    closecurly . nl
  where
    genProto :: Style -> ShowS
    genProto Ordinary =
      case res of
        FunPtr t ->
          word "extern" . space . cResType (last t)
          . parens (star . word cname
                   . parens (listsep comma (map cTypename args)))
          . parens (listsep comma (map cTypename (init t))) . semi
        _ ->
          word "extern" . space . cResType res . space . word cname
          . parens (listsep comma (map cTypename args)) . semi
    genProto FunAddress =
          word "extern" . space . cResType res . space . word cname
          . parens id . semi
    genProto CCast   = id
    genProto Address = id
    genProto Dynamic = id
    genProto Wrapper = error "foreign import wrapper not yet supported."

    cArg a n = indent . cArgDecl a n
    modname  = (reverse . unpackPS . extractM) hname
    profinfo = profname hname
    noarg Unit = True
    noarg _    = False

strForeign f@(Foreign Exported _ _ _ cname hname arity args res) =
    nl . comment (shows f) . nl .
    cCodeDecl cname args res . space .
    opencurly . nl .
      --cResDecl res .
      hCall arity hname args .
      hResult res .
    closecurly . nl


---- foreign import ----

cArgDecl :: Arg -> Int -> ShowS
cArgDecl (FunPtr t) n = cResType (last t) . parens (star . narg n)
                        . parens (listsep comma (map cTypename (init t)))
cArgDecl Unit       n = comment (cTypename Unit . space . narg n)
cArgDecl arg        n = cTypename arg . space . narg n

cArgDefn :: Arg -> Int -> ShowS
cArgDefn Unit _n =
    id
cArgDefn arg n =
    indent . word "nodeptr = C_GETARG1" . parens (shows n) . semi .
    indent . word "IND_REMOVE(nodeptr)" . semi .
    indent . narg n . showString " = " .
    parens (cTypename arg) . cConvert arg . semi

cResDecl :: Res -> ShowS
cResDecl (FunPtr t) = indent . cResType (last t) . parens (star . word "result")
                      . parens (listsep comma (map cTypename (init t))) . semi
cResDecl Unit = id
cResDecl arg  = indent . cTypename arg . space . word "result" . semi

cCall :: String -> Int -> Res -> ShowS
cCall cname arity res =
      indent . (case res of
                    Unit -> id
                    _    -> word "result = ") .
      word cname .  parens (listsep comma (map narg [1..arity])) . semi

cCast :: Int -> Res -> ShowS
cCast arity res =
    if arity /= 1 then
      error ("\"foreign import cast\" has wrong arity.")
    else
      indent . word "result = " . parens (cResType res) . parens (narg 1) . semi

cAddr :: String -> Res -> ShowS
cAddr cname res =
    indent . word "result = " . parens (cResType res) . word "&" .
    word cname . semi

cFunAddr :: String -> ShowS
cFunAddr cname =
    indent . word "result = " . word cname . semi

cDynamic :: Int -> Res -> ShowS
cDynamic arity res =
      indent . (case res of
                    Unit -> id
                    _    -> word "result = ") .
      word "(*arg1)" .  parens (listsep comma (map narg [2..arity])) . semi

cFooter :: ShowS -> Arg -> ShowS
cFooter profinfo arg =
    indent . word "nodeptr = " . hConvert arg (word "result") . semi .
    indent . word "INIT_PROFINFO(nodeptr,&" . profinfo . word ")" . semi .
    indent . word "C_RETURN(nodeptr)" . semi


---- foreign export ----

cCodeDecl :: String -> [Arg] -> Res -> ShowS
cCodeDecl cname args res =
    cResType res . space . word cname . space .
      parens (listsep comma (zipWith cArgDecl args [1..]))

cResType :: Res -> ShowS
cResType res = cTypename res

hCall :: Int -> TokenId -> [Arg] -> ShowS
hCall arity hname args =
    indent . word "NodePtr nodeptr, vap, args" . squares (shows arity) . semi .
    indent . word "C_CHECK" .
        parens (parens (shows (arity+1) . word "+EXTRA") . word "*2") . semi .
    foldr (.) id (zipWith hArg1 args [1..]) .
    indent . word "vap = Hp" . semi .
    indent . word "*Hp = " . parens (word "Node") .
        word "C_VAPTAG" . parens (localname hname) . semi .
    indent . word "Hp += 1+EXTRA" . semi .
    foldr (.) id (zipWith hArg2 args [1 :: Int ..]) .
    indent . word "nodeptr = evalExport(vap)" . semi .
    indent . word "IND_REMOVE(nodeptr)" . semi
  where
    hArg1 arg n = indent . word "args" . squares (shows (n-1)) . space .
                  equals . space . hConvert arg (narg n) . semi
    hArg2 _   n = indent . word "*Hp++ = (Node)args" . squares (shows (n-1)) . semi

hResult :: Res -> ShowS
hResult Unit = id       -- allow for IO ()
hResult res  = indent . word "return" . space . cConvert res . semi


---- shared between foreign import/export ----

cTypename :: Arg -> ShowS
cTypename Int8         = word "HsInt8"
cTypename Int16        = word "HsInt16"
cTypename Int32        = word "HsInt32"
cTypename Int64        = word "HsInt64"
cTypename Word8        = word "HsWord8"
cTypename Word16       = word "HsWord16"
cTypename Word32       = word "HsWord32"
cTypename Word64       = word "HsWord64"
cTypename Int          = word "HsInt"
cTypename Bool         = word "HsBool"
cTypename Char         = word "char"
cTypename Float        = word "float"
cTypename Double       = word "double"
cTypename Ptr          = word "void*"
cTypename (FunPtr t)   = cResType (last t) . parens star
                         . parens (listsep comma (map cTypename (init t)))
cTypename StablePtr    = word "StablePtr"
cTypename ForeignPtr   = word "void*"
cTypename Unit         = word "void"
cTypename Addr         = word "void*"   -- deprecated
cTypename ForeignObj   = word "void*"   -- deprecated
cTypename PackedString = word "char*"   -- non-standard
cTypename Integer      = word "Node*" -- non-standard
cTypename (Unknown _)  = word "Node*" -- for passing Haskell heap values
-- cTypename x = error ("*** cTypename [" ++ show x ++ "]")

cConvert :: Arg -> ShowS
cConvert Int          = word "GET_INT_VALUE(nodeptr)"
cConvert Bool         = word "GET_BOOL_VALUE(nodeptr)"
cConvert Char         = word "GET_CHAR_VALUE(nodeptr)"
cConvert Float        = word "get_float_value(nodeptr)"
cConvert Double       = word "get_double_value(nodeptr)"
cConvert Int8         = word "GET_8BIT_VALUE(nodeptr)"
cConvert Int16        = word "GET_16BIT_VALUE(nodeptr)"
cConvert Int32        = word "GET_32BIT_VALUE(nodeptr)"
cConvert Int64        = word "nhc_get_64bit_value(nodeptr)"
cConvert Word8        = word "GET_8BIT_VALUE(nodeptr)"
cConvert Word16       = word "GET_16BIT_VALUE(nodeptr)"
cConvert Word32       = word "GET_32BIT_VALUE(nodeptr)"
cConvert Word64       = word "nhc_get_64bit_value(nodeptr)"
cConvert Ptr          = word "GET_INT_VALUE(nodeptr)"
cConvert (FunPtr _)   = word "GET_INT_VALUE(nodeptr)"
cConvert StablePtr    = word "GET_INT_VALUE(nodeptr)"
cConvert ForeignPtr   = word "derefForeignObj((ForeignObj*)GET_INT_VALUE(nodeptr))"
cConvert Addr         = word "GET_INT_VALUE(nodeptr)"
cConvert ForeignObj   = word "derefForeignObj((ForeignObj*)GET_INT_VALUE(nodeptr))"
cConvert PackedString = word "nhc_getPackedString(nodeptr)"
cConvert Integer      = word "nodeptr"
cConvert Unit         = word "0"
cConvert (Unknown _)  = word "nodeptr"

hConvert :: Arg -> ShowS -> ShowS
hConvert Int          s = word "nhc_mkInt" . parens s
hConvert Bool         s = word "nhc_mkBool" . parens s
hConvert Char         s = word "nhc_mkChar" . parens s
hConvert Float        s = word "nhc_mkFloat" . parens s
hConvert Double       s = word "nhc_mkDouble" . parens s
hConvert Int8         s = word "nhc_mkInt8" . parens s
hConvert Int16        s = word "nhc_mkInt16" . parens s
hConvert Int32        s = word "nhc_mkInt32" . parens s
hConvert Int64        s = word "nhc_mkInt64" . parens s
hConvert Word8        s = word "nhc_mkWord8" . parens s
hConvert Word16       s = word "nhc_mkWord16" . parens s
hConvert Word32       s = word "nhc_mkWord32" . parens s
hConvert Word64       s = word "nhc_mkWord64" . parens s
hConvert Ptr          s = word "nhc_mkInt" . parens (word "(int)" . s)
hConvert (FunPtr _)   s = word "nhc_mkInt" . parens (word "(int)" . s)
hConvert StablePtr    s = word "nhc_mkInt" . parens (word "(int)" . s)
{- Returning ForeignPtr's to Haskell is usually illegal: -}
hConvert ForeignPtr   s =
  warning ("foreign import/export should not return ForeignPtr type.\n") s
hConvert Addr         s = word "nhc_mkInt" . parens (word "(int)" . s)
{- Returning ForeignObj's to Haskell is usually illegal: -}
hConvert ForeignObj   s =
  warning ("foreign import/export should not return ForeignObj type.\n") s
hConvert PackedString s = word "nhc_mkString" . parens (word "(char*)" . s)
hConvert Integer      s = s
hConvert Unit         _ = word "nhc_mkUnit()"
hConvert (Unknown _)  s = s	-- for passing Haskell heap values untouched

openparen, closeparen, opencurly, closecurly, semi,
   nl, space, equals, comma, star, indent :: ShowS
openparen  = showChar '('
closeparen = showChar ')'
opencurly  = showChar '{'
closecurly = showChar '}'
semi       = showChar ';' . nl
nl         = showChar '\n'
space      = showChar ' '
equals     = showChar '='
comma      = showChar ','
star       = showChar '*'
indent     = space . space

parens, squares, strquote, comment :: ShowS -> ShowS
parens   s = openparen . s . closeparen
squares  s = showChar '[' . s . showChar ']'
strquote s = showChar '"' . s . showChar '"'
comment  s = word "/*" . space . s . space . word "*/"

word :: String -> ShowS
word = showString

narg :: Int -> ShowS
narg n = word "arg" . shows n

listsep :: ShowS -> [ShowS] -> ShowS
listsep s x = if length x > 0 then foldr1 (\l r -> l . s . r) x else id

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].