on 33166ee help sign in
360 lines 13 kB history raw
1******************************
2New Foreign Function Interface
3******************************
5.. sectionauthor:: Edwin Brady
7Ever since Idris has had multiple backends compiling to different
8target languages on potentially different platforms, we have had the
9problem that the foreign function interface (FFI) was written under
10the assumption of compiling to C. As a result, it has been hard to
11write generic code for multiple targets, or even to be sure that if
12code compiles that it will run on the expected target.
14As of 0.9.17, Idris will have a new foreign function interface (FFI)
15which is aware of multiple targets. Users who are working with the
16default code generator can happily continue writing programs as before
17with no changes, but if you are writing bindings for an external
18library, writing a back end, or working with a non-C back end, there
19are some things you will need to be aware of, which this page
20describes.
22The ``IO'`` monad, and ``main``
23===============================
25The ``IO`` monad exists as before, but is now specific to the C
26backend (or, more precisely, any backend whose foreign function calls
27are compatible with C.) Additionally, there is now an ``IO'`` monad,
28which is parameterised over a FFI descriptor:
30.. code-block:: idris
32 data IO' : (lang : FFI) -> Type -> Type
34The Prelude defines two FFI descriptors which are imported
35automatically, for C and JavaScript/Node, and defines ``IO`` to use
36the C FFI and ``JS_IO`` to use the JavaScript FFI:
38.. code-block:: idris
40 FFI_C : FFI
41 FFI_JS : FFI
43 IO : Type -> Type
44 IO a = IO' FFI_C a
46 JS_IO : Type -> Type
47 JS_IO a = IO' FFI_JS a
49As before, the entry point to an Idris program is ``main``, but the
50type of ``main`` can now be any instance of ``IO'``, e.g. the
51following are both valid:
53.. code-block:: idris
55 main : IO ()
56 main : JS_IO ()
58The FFI descriptor includes details about which types can be
59marshalled between the foreign language and Idris, and the "target" of
60a foreign function call (typically just a String representation of the
61function's name, but potentially something more complicated such as an
62external library file or even a URL).
64FFI descriptors
65===============
67An FFI descriptor is a record containing a predicate which holds when
68a type can be marshalled, and the type of the target of a foreign
69call:
71.. code-block:: idris
73 record FFI where
74 constructor MkFFI
75 ffi_types : Type -> Type
76 ffi_fn : Type
78For C, this is:
80.. code-block:: idris
82 ||| Supported C integer types
83 public export
84 data C_IntTypes : Type -> Type where
85 C_IntChar : C_IntTypes Char
86 C_IntNative : C_IntTypes Int
87 C_IntBits8 : C_IntTypes Bits8
88 C_IntBits16 : C_IntTypes Bits16
89 C_IntBits32 : C_IntTypes Bits32
90 C_IntBits64 : C_IntTypes Bits64
92 ||| Supported C function types
93 public export
94 data C_FnTypes : Type -> Type where
95 C_Fn : C_Types s -> C_FnTypes t -> C_FnTypes (s -> t)
96 C_FnIO : C_Types t -> C_FnTypes (IO' FFI_C t)
97 C_FnBase : C_Types t -> C_FnTypes t
99 ||| Supported C foreign types
100 public export
101 data C_Types : Type -> Type where
102 C_Str : C_Types String
103 C_Float : C_Types Double
104 C_Ptr : C_Types Ptr
105 C_MPtr : C_Types ManagedPtr
106 C_Unit : C_Types ()
107 C_Any : C_Types (Raw a)
108 C_FnT : C_FnTypes t -> C_Types (CFnPtr t)
109 C_IntT : C_IntTypes i -> C_Types i
111 ||| A descriptor for the C FFI. See the constructors of `C_Types`
112 ||| and `C_IntTypes` for the concrete types that are available.
113 %error_reverse
114 public export
115 FFI_C : FFI
116 FFI_C = MkFFI C_Types String String
118Foreign calls
119=============
121To call a foreign function, the ``foreign`` function is used. For
122example:
124.. code-block:: idris
126 do_fopen : String -> String -> IO Ptr
127 do_fopen f m
128 = foreign FFI_C "fileOpen" (String -> String -> IO Ptr) f m
130The ``foreign`` function takes an FFI description, a function name (the
131type is given by the ``ffi_fn`` field of ``FFI_C`` here), and a function
132type, which gives the expected types of the remaining arguments. Here,
133we're calling an external function ``fileOpen`` which takes, in the C, a
134``char*`` file name, a ``char*`` mode, and returns a file pointer. It is
135the job of the C back end to convert Idris ``String`` to C ``char*``
136and vice versa.
138The argument types and return type given here must be present in the
139``fn_types`` predicate of the ``FFI_C`` description for the foreign
140call to be valid.
142**Note** The arguments to ``foreign`` *must* be known at compile time,
143because the foreign calls are generated statically. The ``%inline``
144directive on a function can be used to give hints to help this, for
145example a shorthand for calling external JavaScript functions:
147.. code-block:: idris
149 %inline
150 jscall : (fname : String) -> (ty : Type) ->
151 {auto fty : FTy FFI_JS [] ty} -> ty
152 jscall fname ty = foreign FFI_JS fname ty
154C callbacks
155-----------
156It is possible to pass an Idris function to a C function taking a function
157pointer by using ``CFnPtr`` in the function type. The Idris function is passed
158to ``MkCFnPtr`` in the arguments. The example below shows declaring the C standard
159library function ``qsort`` which takes a pointer to a comparison function.
161.. code-block:: idris
163 myComparer : Ptr -> Ptr -> Int
164 myComparer = ...
166 qsort : Ptr -> Int -> Int -> IO ()
167 test2 data elems elsize = foreign FFI_C "qsort"
168 (Ptr -> Int -> Int -> CFnPtr (Ptr -> Ptr -> Int) -> IO ())
169 data elems elsize (MkCFnPtr myComparer)
171There are a few limitations to callbacks in the C FFI. The foreign function can't
172take the function to make a callback of as an argument. This will give a
173compilation error:
175.. code-block:: idris
177 -- This does not work
178 example : (Int -> ()) -> IO ()
179 example f = foreign FFI_C "callbacker" (CFnPtr (Int -> ()) -> IO ()) f
181The other big limitation is that it doesn't support IO functions. Use
182``unsafePerformIO`` to wrap them.
184There are two special function names:
185``%wrapper`` returns the function pointer that wraps an Idris function. This
186is useful if the function pointer isn't taken by a C function directly but
187should be inserted into a data structure. A foreign declaration using
188``%wrapper`` must return ``IO Ptr``.
190.. code-block:: idris
192 -- this returns the C function pointer to a qsort comparer
193 example_wrapper : IO Ptr
194 example_wrapper = foreign FFI_C "%wrapper" (CFnPtr (Ptr -> Ptr -> Int) -> IO Ptr)
195 (MkCFnPtr myComparer)
197``%dynamic`` calls a C function pointer with some arguments. This is useful if
198a C function returns or data structure contains a C function pointer, for example
199structs of function pointers are common in object-oriented C such as in COM or the
200Linux kernel. The function type contains an extra ``Ptr`` at the start for the
201function pointer. ``%dynamic`` can be seen as a pseudo-function that calls the
202function in the first argument, passing the remaining arguments to it.
204.. code-block:: idris
206 -- we have a pointer to a function with the signature int f(int), call it
207 example_dynamic : Ptr -> Int -> IO Int
208 example_dynamic fn x = foreign FFI_C "%dynamic" (Ptr -> Int -> IO Int) fn x
210If the foreign name is prefixed by a ``&``, it is treated as a pointer to the
211global variable with the following name. The type must be just ``IO Ptr``.
213.. code-block:: idris
215 -- access the global variable errno
216 errno : IO Ptr
217 errno = foreign FFI_C "&errno" (IO Ptr)
219FFI implementation
220------------------
222In order to write bindings to external libraries, the details of how
223``foreign`` works are unnecessary --- you simply need to know that
224``foreign`` takes an FFI descriptor, the function name, and its
225type. It is instructive to look a little deeper, however:
227The type of ``foreign`` is as follows:
229.. code-block:: idris
231 foreign : (ffi : FFI)
232 -> (fname : ffi_fn f)
233 -> (ty : Type)
234 -> {auto fty : FTy ffi [] ty}
235 -> ty
237The important argument here is the implicit ``fty``, which contains a
238proof (``FTy``) that the given type is valid according to the FFI
239description ``ffi``:
241.. code-block:: idris
243 data FTy : FFI -> List Type -> Type -> Type where
244 FRet : ffi_types f t -> FTy f xs (IO' f t)
245 FFun : ffi_types f s -> FTy f (s :: xs) t -> FTy f xs (s -> t)
247Notice that this uses the ``ffi_types`` field of the FFI descriptor
248--- these arguments to ``FRet`` and ``FFun`` give explicit proofs that
249the type is valid in this FFI. For example, the above ``do_fopen``
250builds the following implicit proof as the ``fty`` argument to
251``foreign``:
253.. code-block:: idris
255 FFun C_Str (FFun C_Str (FRet C_Ptr))
257Compiling foreign calls
258=======================
260(This section assumes some knowledge of the Idris internals.)
262When writing a back end, we now need to know how to compile
263``foreign``. We'll skip the details here of how a ``foreign`` call
264reaches the intermediate representation (the IR), though you can look
265in ``IO.idr`` in the ``prelude`` package to see a bit more detail ---
266a ``foreign`` call is implemented by the primitive function
267``mkForeignPrim``. The important part of the IR as defined in
268``Lang.hs`` is the following constructor:
270.. code-block:: idris
272 data LExp = ...
273 | LForeign FDesc -- Function descriptor
274 FDesc -- Return type descriptor
275 [(FDesc, LExp)]
277So, a ``foreign`` call appears in the IR as the ``LForeign``
278constructor, which takes a function descriptor (of a type given by the
279``ffi_fn`` field in the FFI descriptor), a return type descriptor
280(given by an application of ``FTy``), and a list of arguments with
281type descriptors (also given by an application of ``FTy``).
283An ``FDesc`` describes an application of a name to some arguments, and
284is really just a simplified subset of an ``LExp``:
286.. code-block:: idris
288 data FDesc = FCon Name
289 | FStr String
290 | FUnknown
291 | FApp Name [FDesc]
293There are corresponding structures in the lower level IRs, such as the
294defunctionalised, simplified and bytecode forms.
296Our ``do_fopen`` example above arrives in the ``LExp`` form as:
298.. code-block:: idris
300 LForeign (FStr "fileOpen") (FCon (sUN "C_Ptr"))
301 [(FCon (sUN "C_Str"), f), (FCon (sUN "C_Str"), m)]
303(Assuming that ``f`` and ``m`` stand for the ``LExp`` representations
304of the arguments.) This information should be enough for any back end
305to marshal the arguments and return value appropriately.
307.. note::
309 When processing ``FDesc``, be aware that there may be implicit
310 arguments, which have not been erased. For example, ``C_IntT`` has
311 an implicit argument ``i``, so will appear in an ``FDesc`` as
312 something of the form ``FApp (sUN "C_IntT") [i, t]`` where ``i`` is
313 the implicit argument (which can be ignored) and ``t`` is the
314 descriptor of the integer type. See ``CodegenC.hs``, specifically
315 the function ``toFType``, to see how this works in practice.
317JavaScript FFI descriptor
318=========================
320The JavaScript FFI descriptor is a little more complex, because the
321JavaScript FFI supports marshalling functions. It is defined as
322follows:
324.. code-block:: idris
326 mutual
327 data JsFn t = MkJsFn t
329 data JS_IntTypes : Type -> Type where
330 JS_IntChar : JS_IntTypes Char
331 JS_IntNative : JS_IntTypes Int
333 data JS_FnTypes : Type -> Type where
334 JS_Fn : JS_Types s -> JS_FnTypes t -> JS_FnTypes (s -> t)
335 JS_FnIO : JS_Types t -> JS_FnTypes (IO' l t)
336 JS_FnBase : JS_Types t -> JS_FnTypes t
338 data JS_Types : Type -> Type where
339 JS_Str : JS_Types String
340 JS_Float : JS_Types Float
341 JS_Ptr : JS_Types Ptr
342 JS_Unit : JS_Types ()
343 JS_FnT : JS_FnTypes a -> JS_Types (JsFn a)
344 JS_IntT : JS_IntTypes i -> JS_Types i
346The reason for wrapping function types in a ``JsFn`` is to help the
347proof search when building ``FTy``. We hope to improve proof search
348eventually, but for the moment it works much more reliably if the
349indices are disjoint! An example of using this appears in `IdrisScript
350<https://github.com/idris-hackers/IdrisScript>`__ when setting
351timeouts:
353.. code-block:: idris
355 setTimeout : (() -> JS_IO ()) -> (millis : Int) -> JS_IO Timeout
356 setTimeout f millis = do
357 timeout <- jscall "setTimeout(%0, %1)"
358 (JsFn (() -> JS_IO ()) -> Int -> JS_IO Ptr)
359 (MkJsFn f) millis
360 return $ MkTimeout timeout