on 239c8ca help sign in
279 lines 9.6 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 data C_IntTypes : Type -> Type where
84 C_IntChar : C_IntTypes Char
85 C_IntNative : C_IntTypes Int
86 ... -- more integer types
88 ||| Supported C foreign types
89 data C_Types : Type -> Type where
90 C_Str : C_Types String
91 C_Float : C_Types Float
92 C_Ptr : C_Types Ptr
93 C_MPtr : C_Types ManagedPtr
94 C_Unit : C_Types ()
95 C_Any : C_Types (Raw a)
96 C_IntT : C_IntTypes i -> C_Types i
98 FFI_C : FFI
99 FFI_C = MkFFI C_Types
100 String -- the name of the C function
102Foreign calls
103=============
105To call a foreign function, the ``foreign`` function is used. For
106example:
108.. code-block:: idris
110 do_fopen : String -> String -> IO Ptr
111 do_fopen f m
112 = foreign FFI_C "fileOpen" (String -> String -> IO Ptr) f m
114The ``foreign`` function takes an FFI description, a function name (the
115type is given by the ``ffi_fn`` field of ``FFI_C`` here), and a function
116type, which gives the expected types of the remaining arguments. Here,
117we're calling an external function ``fileOpen`` which takes, in the C, a
118``char*`` file name, a ``char*`` mode, and returns a file pointer. It is
119the job of the C back end to convert Idris ``String`` to C ``char*``
120and vice versa.
122The argument types and return type given here must be present in the
123``fn_types`` predicate of the ``FFI_C`` description for the foreign
124call to be valid.
126**Note** The arguments to ``foreign`` *must* be known at compile time,
127because the foreign calls are generated statically. The ``%inline``
128directive on a function can be used to give hints to help this, for
129example a shorthand for calling external JavaScript functions:
131.. code-block:: idris
133 %inline
134 jscall : (fname : String) -> (ty : Type) ->
135 {auto fty : FTy FFI_JS [] ty} -> ty
136 jscall fname ty = foreign FFI_JS fname ty
138FFI implementation
139------------------
141In order to write bindings to external libraries, the details of how
142``foreign`` works are unnecessary --- you simply need to know that
143``foreign`` takes an FFI descriptor, the function name, and its
144type. It is instructive to look a little deeper, however:
146The type of ``foreign`` is as follows:
148.. code-block:: idris
150 foreign : (ffi : FFI)
151 -> (fname : ffi_fn f)
152 -> (ty : Type)
153 -> {auto fty : FTy ffi [] ty}
154 -> ty
156The important argument here is the implicit ``fty``, which contains a
157proof (``FTy``) that the given type is valid according to the FFI
158description ``ffi``:
160.. code-block:: idris
162 data FTy : FFI -> List Type -> Type -> Type where
163 FRet : ffi_types f t -> FTy f xs (IO' f t)
164 FFun : ffi_types f s -> FTy f (s :: xs) t -> FTy f xs (s -> t)
166Notice that this uses the ``ffi_types`` field of the FFI descriptor
167--- these arguments to ``FRet`` and ``FFun`` give explicit proofs that
168the type is valid in this FFI. For example, the above ``do_fopen``
169builds the following implicit proof as the ``fty`` argument to
170``foreign``:
172.. code-block:: idris
174 FFun C_Str (FFun C_Str (FRet C_Ptr))
176Compiling foreign calls
177=======================
179(This section assumes some knowledge of the Idris internals.)
181When writing a back end, we now need to know how to compile
182``foreign``. We'll skip the details here of how a ``foreign`` call
183reaches the intermediate representation (the IR), though you can look
184in ``IO.idr`` in the ``prelude`` package to see a bit more detail ---
185a ``foreign`` call is implemented by the primitive function
186``mkForeignPrim``. The important part of the IR as defined in
187``Lang.hs`` is the following constructor:
189.. code-block:: idris
191 data LExp = ...
192 | LForeign FDesc -- Function descriptor
193 FDesc -- Return type descriptor
194 [(FDesc, LExp)]
196So, a ``foreign`` call appears in the IR as the ``LForeign``
197constructor, which takes a function descriptor (of a type given by the
198``ffi_fn`` field in the FFI descriptor), a return type descriptor
199(given by an application of ``FTy``), and a list of arguments with
200type descriptors (also given by an application of ``FTy``).
202An ``FDesc`` describes an application of a name to some arguments, and
203is really just a simplified subset of an ``LExp``:
205.. code-block:: idris
207 data FDesc = FCon Name
208 | FStr String
209 | FUnknown
210 | FApp Name [FDesc]
212There are corresponding structures in the lower level IRs, such as the
213defunctionalised, simplified and bytecode forms.
215Our ``do_fopen`` example above arrives in the ``LExp`` form as:
217.. code-block:: idris
219 LForeign (FStr "fileOpen") (FCon (sUN "C_Ptr"))
220 [(FCon (sUN "C_Str"), f), (FCon (sUN "C_Str"), m)]
222(Assuming that ``f`` and ``m`` stand for the ``LExp`` representations
223of the arguments.) This information should be enough for any back end
224to marshal the arguments and return value appropriately.
226.. note::
228 When processing ``FDesc``, be aware that there may be implicit
229 arguments, which have not been erased. For example, ``C_IntT`` has
230 an implicit argument ``i``, so will appear in an ``FDesc`` as
231 something of the form ``FApp (sUN "C_IntT") [i, t]`` where ``i`` is
232 the implicit argument (which can be ignored) and ``t`` is the
233 descriptor of the integer type. See ``CodegenC.hs``, specifically
234 the function ``toFType``, to see how this works in practice.
236JavaScript FFI descriptor
237=========================
239The JavaScript FFI descriptor is a little more complex, because the
240JavaScript FFI supports marshalling functions. It is defined as
241follows:
243.. code-block:: idris
245 mutual
246 data JsFn t = MkJsFn t
248 data JS_IntTypes : Type -> Type where
249 JS_IntChar : JS_IntTypes Char
250 JS_IntNative : JS_IntTypes Int
252 data JS_FnTypes : Type -> Type where
253 JS_Fn : JS_Types s -> JS_FnTypes t -> JS_FnTypes (s -> t)
254 JS_FnIO : JS_Types t -> JS_FnTypes (IO' l t)
255 JS_FnBase : JS_Types t -> JS_FnTypes t
257 data JS_Types : Type -> Type where
258 JS_Str : JS_Types String
259 JS_Float : JS_Types Float
260 JS_Ptr : JS_Types Ptr
261 JS_Unit : JS_Types ()
262 JS_FnT : JS_FnTypes a -> JS_Types (JsFn a)
263 JS_IntT : JS_IntTypes i -> JS_Types i
265The reason for wrapping function types in a ``JsFn`` is to help the
266proof search when building ``FTy``. We hope to improve proof search
267eventually, but for the moment it works much more reliably if the
268indices are disjoint! An example of using this appears in `IdrisScript
269<https://github.com/idris-hackers/IdrisScript>`__ when setting
270timeouts:
272.. code-block:: idris
274 setTimeout : (() -> JS_IO ()) -> (millis : Int) -> JS_IO Timeout
275 setTimeout f millis = do
276 timeout <- jscall "setTimeout(%0, %1)"
277 (JsFn (() -> JS_IO ()) -> Int -> JS_IO Ptr)
278 (MkJsFn f) millis
279 return $ MkTimeout timeout