Generating Function Prototypes
Alastair Reid
reid at reid-consulting-uk.ltd.uk
Thu Jul 4 06:55:43 EDT 2002
Alastair:
>> (I'm not sure that the ffi should allow compilers to vary in this
>> way but it does.)
Manuel:
> The FFI doesn't really allow compilers to emit prototypes (at least
> not in general) as this leads to semantic differences eg in argument
> promotion, for which Section 4.1.5 precisely specifies the
> behaviour.
I just reread that section and can't see anything that contradicts the
idea that the FFI implementation ignores any header files provided.
For example, the phrase:
"implements calls to C functions ... as if a function prototype for
the called functions is in scope"
can be interpreted as meaning:
1) Will generate code (by generating machine code directly, by
generating C code which is then compiled, or by some other means)
which assumes the existence of a prototype _even if the user does
not supply one_.
[I see this as the most obvious interpretation of the phrase.]
2) Will generate C code which #includes any header files the user has
provided.
[I see this as a less likely interpretation but it seems to be the
one you intend?]
My reading of the whole section is that every mention of users
providing header files and function prototypes refers to compiling the
C code being called not the Haskell code doing the calling. The
examples of using foreign import don't show the use of header files
though the second example needs a header file to compile correctly
with GHC.
If the intent is that any user-provided header files must be obeyed, then
I think the spec should explicitly say:
When a header file containing a function prototype is not provided,
the function calling convention employed is undefined. It may vary
between different operating systems, between different Haskell compilers,
and between different functions.
The 'between different functions' part is because GHC #includes a lot
of header files into the code it compiles - it may well have a
prototype for the function you're calling even though you don't
provide one. Trickier yet, gcc likes to #include some files that
aren't mentioned in any of the header files that you #include. These
define functions like 'memmove' which gcc treates magically.
Also, I'm using 'function calling convention' instead of 'argument
passing convention' because some C compilers pass _and return_ small
structs (such as 2 ints) specially and I think you need prototypes at
both definition and call site to make the code work correctly.
I also think the wording could be changed to clearly separate the
notion of the user invoking a C compiler on a C library that they wish
to call from Haskell and the Haskell compiler invoking a C compiler as
part of its compilation process.
I attach my rewording of the section. Note that I am trying to make
it quite clear that an ffi declaration is not portable unless you
provide function prototypes except in the special case that your C
compiler generates the same code with or without a prototype.
\subsubsection{C Argument Promotion}
The argument passing conventions of C are dependant on whether a
function prototype for the function is in scope at the site where the
function is defined and at the sites where the function is called..
%
In particular, if no function prototype is in scope, \emph{default
argument promotion} is applied to integral and floating types.
For example, consider the following C
function definition, which lacks a prototype:
%
\begin{quote}
\begin{verbatim}
void foo (a) {
float a;
...
}
\end{verbatim}
\end{quote}
%
The lack of a prototype implies that a C compiler will apply default argument
promotion to the parameter \code{a}, and thus, \code{foo} will expect to
receive a value of type \code{double}, \emph{not} \code{float}. Hence,
any callers should call \code{foo} as though it had been defined
with type:
%
\begin{quote}
\begin{verbatim}
void foo (double a);
\end{verbatim}
\end{quote}
%
To ensure portability across different operating systems and compilers,
the user should write a header file (called \code{"header.h"}, say)
containing this prototype and should use this \code{foreign import}
declaration.
%
\begin{quote}
\begin{verbatim}
foreign import ccall "header.h" foo :: Double -> IO ()
\end{verbatim}
\end{quote}
%
(Of course, it is more reliable to use the same header file when
compiling the function being called --- but that is not always
possible.)
Just as one must be consistent in providing function prototypes
when compiling C code, one must be consistent in providing function
prototypes when compiling Haskell code.
%
That is, when a header file containing a function prototype is not
provided and the function calling convention is affected by default
argument promotion, the function calling convention employed by
foreign function declarations is undefined.
%
The calling convention employed may vary between different operating systems,
between different Haskell compilers, and between different functions.
A similar situation arises in the case of \code{foreign export} declarations
that use types that would be altered under the C default argument promotion
rules. When calling such Haskell functions from C, a function prototype
matching the signature provided in the \code{foreign export} declaration must
be in scope; otherwise, the C compiler will erroneously apply the promotion
rules to all function arguments.
Note that for a C function defined to a accept a variable number of arguments,
all arguments beyond the explicitly typed arguments suffer argument promotion.
However, because C permits the calling convention to be different for such
functions; a Haskell system will, in general, not be able to make use of
variable argument functions. Hence, their use is deprecated in portable code.
-- Alastair
ps I still think we're better off removing header files completely and
having the Haskell type completely determine the calling convention
employed. But since I'm not getting any takers on that, I'll settle for
pinning down the spec as tightly as possible.
More information about the FFI
mailing list