Ticket #2979 (closed feature request: fixed)

Opened 4 years ago

Last modified 11 months ago

better support for FFI C wrappers for macros in system headers

Reported by: duncan Owned by:
Priority: normal Milestone: 7.6.1
Component: Compiler (FFI) Version: 7.3
Keywords: Cc: iku.iwasa@…, pho@…, v.dijk.bas@…, mauricio.antunes@…, redneb@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Building GHC failed Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description

Often C functions get defined via macros on some systems. For example iconv gets munged and renamed by some silly macros on OSX. This messes up the iconv binding package (and haskeline which currently directly ffi imports iconv rather than using the Haskell iconv package).

Actually making wrapper functions is harder than it should be. For one thing one has to pick a single name for the wrapper function and this then prevents loading multiple versions of a Haskell package because their C symbols will clash. We have similar problems for C snippets in other packages like bytestring, so it's not constraint to FFI wrappers.

We should think about how to make this easier. We should think about where the best place to put improvements eg ghc, cabal, c2hs or hsc2hs.

Perhaps just a way to compile versioned C function names using a CPP define for the current package name and version. Eg in the .c file:

int HS_VERSIONED_SYMBOL(foo_wrapper) (int blah) {
  ...
}

And similar in the .hs file where we ffi import it.

Change History

  Changed 4 years ago by simonmar

  • difficulty set to Unknown

see also #2471

  Changed 4 years ago by igloo

  • milestone set to 6.12 branch

  Changed 4 years ago by igloo

  • milestone changed from 6.12 branch to 6.12.1

  Changed 4 years ago by iquiw

  • cc iku.iwasa@… added

  Changed 4 years ago by igloo

  • failure set to None/Unknown
  • milestone changed from 6.12.1 to 6.14.1

I would like to be able to just say

foreign import wrapped "iconv" iconv :: Foo -> IO Bar

and have GHC automatically generate the trivial wrapper.

  Changed 3 years ago by simonmar

See the thread here for some of the discussion regarding where the best place to put support for this is:

  Changed 2 years ago by igloo

  • milestone changed from 7.0.1 to 7.0.2

  Changed 2 years ago by igloo

  • milestone changed from 7.0.2 to 7.2.1

  Changed 2 years ago by simonmar

  • component changed from Compiler to Compiler (FFI)

  Changed 20 months ago by igloo

  • milestone changed from 7.2.1 to 7.4.1

  Changed 18 months ago by igloo@…

commit 36f8cabecd5a8320ee174abb56e73841a5cbc9c7

Author: Ian Lynagh <igloo@earth.li>
Date:   Sat Nov 26 14:54:47 2011 +0000

    Implement a capi calling convention; fixes #2979
    
    In GHC, this provides an easy way to call a C function via a C wrapper.
    This is important when the function is really defined by CPP.
    
    Requires the new CApiFFI extension.
    
    Not documented yet, as it's still an experimental feature at this stage.

 compiler/cmm/PprC.hs                    |    1 +
 compiler/deSugar/DsForeign.lhs          |   62 +++++++++++++++++++++++++-----
 compiler/llvmGen/LlvmCodeGen/CodeGen.hs |    1 +
 compiler/main/DynFlags.hs               |    2 +
 compiler/parser/Lexer.x                 |    5 ++
 compiler/parser/Parser.y.pp             |    3 +
 compiler/prelude/ForeignCall.lhs        |   11 ++++-
 compiler/typecheck/TcForeign.lhs        |    1 +
 8 files changed, 73 insertions(+), 13 deletions(-)

  Changed 18 months ago by igloo

  • status changed from new to closed
  • resolution set to fixed

Fixed.

  Changed 18 months ago by PHO

  • cc pho@… added

  Changed 18 months ago by PHO

  • status changed from closed to new
  • resolution fixed deleted

Unfortunately, the brand-new capi calling convention doesn't work well with macros that take pointers as their arguments.

From libraries/base/System/Posix/Internals.hs:

foreign import capi unsafe "HsBase.h sigaddset"
   c_sigaddset :: Ptr CSigset -> CInt -> IO CInt

This foreign import generates the following C wrapper:

HsInt32 ghc_wrapper_d2kM_sigaddset(HsPtr a1, HsInt32 a2) {return sigaddset(a1, a2);}

And yes, sigaddset(3) is actually defined as a macro (at least on Darwin 9.8.0):

#define sigaddset(set, signo)   (*(set) |= __sigbits(signo), 0)

Since HsPtr is a type synonym of void* defined by includes/HsFFI.h, the C wrapper in question end up dereferencing void*:

/var/folders/rN/rNbpGijHEzGPi-Ux2JwMgU+++TI/-Tmp-/ghc27760_0/ghc27760_0.c: In fu
nction ‘ghc_wrapper_d2kM_sigaddset’:

/var/folders/rN/rNbpGijHEzGPi-Ux2JwMgU+++TI/-Tmp-/ghc27760_0/ghc27760_0.c:13:0:
     warning: dereferencing ‘void *’ pointer

/var/folders/rN/rNbpGijHEzGPi-Ux2JwMgU+++TI/-Tmp-/ghc27760_0/ghc27760_0.c:13:0:
     error: invalid use of void expression

  Changed 18 months ago by PHO

  • failure changed from None/Unknown to Building GHC failed
  • version changed from 6.10.1 to 7.3
  • type changed from feature request to bug

  Changed 18 months ago by PHO

  • blocking 5408 added

  Changed 18 months ago by igloo

  • type changed from bug to feature request
  • milestone changed from 7.4.1 to 7.6.1

I considered something like (in deSugar/DsForeign.lhs):

showStgType :: Type -> SDoc
showStgType t = case showStgPtrType t of
                Just sdoc -> sdoc
                Nothing   -> text "Hs" <> text (showFFIType t)

showStgPtrType :: Type -> Maybe SDoc
showStgPtrType t = case tcSplitTyConApp_maybe (repType t) of
                   Just (tc, [arg])
                    | tyConName tc == ptrTyConName ->
                       case showStgPtrType arg of
                       Just sdoc ->
                           Just (sdoc <> char '*')
                       Nothing -> Nothing
                   Just (tc, [])
                    | tyConName tc `elem` [intTyConName, int8TyConName, int16TyConName, int32TyConName, int64TyConName, wordTyConName, word8TyConName, word16TyConName, word32TyConName, word64TyConName] ->
                       Just (text "Hs" <> text (showFFIType t))
                   _ -> Nothing

but (a) this doesn't fix this particular instance of the problem by itself, as CSigset = () (we could fix that by using hsc2hs, if it's guaranteed to be a numeric type), and (b) it's a little unsatisfactory in general as it doesn't handle pointers to structs at all.

Perhaps we should have something like

foreign import capi unsafe "HsBase.h sigaddset"
   c_sigaddset :: {-# CType "sigset_t *" #-} Ptr CSigset -> CInt -> IO CInt

For now I've reverted the sigset changes, so the build's going through again.

  Changed 18 months ago by PHO

  • blocking 5408 removed

(In #5408) I rebased my previous patch and added a new one:  https://github.com/phonohawk/ghc/compare/master...patch-5408

To pull my branch for this issue:

git fetch git://github.com/phonohawk/ghc.git patch-5408

With these patches, the HEAD now produces fully working GHC even on powerpc-apple-darwin.

  Changed 18 months ago by PHO

Perhaps we should have something like

foreign import capi unsafe "HsBase.h sigaddset"
   c_sigaddset :: {-# CType "sigset_t *" #-} Ptr CSigset -> CInt -> IO CInt

Or perhaps something like this?

-- Defined in somewhere around Foreign.C
class CType a where
    cType :: a -> String

data CSigset
instance CType CSigset where cType = "sigset_t"
instance CType CInt    where cType = "int" -- Defined in Foreign.C.Types
instance CType a => CType (Ptr a) where    -- Defined in Foreign.C.Types
    cType = (++ "*") . cType

-- Good: every types are instances of CType.
foreign import capi unsafe "HsBase.h sigaddset"
   c_sigaddset :: Ptr CSigset -> CInt -> IO CInt

-- Bad: Foo isn't an instance of CType, resulting in a compilation error.
data Foo
foreign import capi unsafe "HsBase.h sigaddset"
   c_sigaddset :: Ptr Foo -> CInt -> IO CInt

-- OK: ccall doesn't care about CType.
foreign import ccall unsafe "HsBase.h sigaddset"
   c_sigaddset :: Ptr CSigset -> CInt -> IO CInt

  Changed 18 months ago by PHO

Oops, I meant:

instance CType CSigset where cType _ = "sigset_t"
instance CType CInt    where cType _ = "int"
instance CType a => CType (Ptr a) where
    cType _ = cType (undefined :: a) ++ "*"

follow-up: ↓ 22   Changed 18 months ago by simonmar

That would involve compile-time execution (like TH), which seems like a big dependency to add. It wouldn't work in the stage 1 compiler, which would mean we couldn't use it in the boot libraries.

in reply to: ↑ 21   Changed 18 months ago by PHO

Replying to simonmar:

That would involve compile-time execution (like TH), which seems like a big dependency to add. It wouldn't work in the stage 1 compiler, which would mean we couldn't use it in the boot libraries.

Ah, stage-1 compiler. It completely slipped my mind...

  Changed 15 months ago by basvandijk

  • cc v.dijk.bas@… added

  Changed 15 months ago by MauricioAntunes

  • cc mauricio.antunes@… added

  Changed 11 months ago by redneb

  • cc redneb@… added

  Changed 11 months ago by igloo

  • status changed from new to closed
  • resolution set to fixed

This was fixed by:

commit 7b24c3fffecbf9fc219c10f24d1472d0d03da6a1
Author: Ian Lynagh <igloo@earth.li>
Date:   Fri Feb 17 15:50:59 2012 +0000

    Allow a header to be specified in a CTYPE pragma
    
    You can now say
        data {-# CTYPE "some_header.h" "the C type" #-} Foo = ...
Note: See TracTickets for help on using tickets.