Thursday, June 06, 2013

Building a Lisp Interpreter from Scratch -- Part 10: Foreign Functions

(This is Part 10 of a series of posts on pLisp)

The foreign functions interface in pLisp is the gateway to access and use external code. It is defined by two special forms, LOAD-FOREIGN-LIBRARY and CALL-FOREIGN-FUNCTION. The first form takes a string (literal or otherwise) that represents the name of the shared library to load, and does a dlopen() on it. The successfully created handle to the library is stored internally by pLisp, and is used to service requests through the second form. The second form is the actual invocation of the library's exported function.

We use libffi for implementing these special forms.

The data types supported are integer, float, character and string, and pointers to these data types.

To illustrate with an example, if we have a shared library called libtest.so that exposes a function called fn_ret_float:


float fn_ret_float(int i, float f, char c, char *s)

{
  printf("%d\n",i);
  printf("%f\n",f);
  printf("%c\n",c);
  printf("%s\n",s);
  printf("%f\n", f + f);
  printf("exiting fn_ret_float\n");
  return f+f;
}

we invoke the function as:

$ ./plisp
Welcome to pLISP. Type 'quit' to exit.
USER> (load-foreign-library "libtest.so")
NIL
USER> (call-foreign-function "fn_ret_float" 'float
                             '((10 integer)
                             (6.5 float)
                             (#\a character)
                             ("abc" character-pointer)))
10
6.500000
a
abc
13.000000
exiting fn_ret_float
13.000000
USER>

The first parameter to CALL-FOREIGN-FUNCTION is the name of the to-be-invoked function, expressed as a string; the next parameter is the return type of the function; this is followed by a list containing the actual parameters and their respective data types. Passing the data types too may seem redundant, since pLisp can figure out the types on its own, but we still need to differentiate between pointers and non-pointers, so we have to live with this redundancy.

Another example:

int fn_arg_int_ptr(int *i)
{
  printf("passed value is %d\n", *i);
  *i = 100;
  return 0;
}

USER> (define i 10)
I
USER> (call-foreign-function "fn_arg_int_ptr" 'integer
                             '((i integer-pointer)))
passed value is 10
0
USER> i
100
USER>

This illustrates the above point about the redundancy -- if we do not mention the type of the argument (integer-pointer), pLisp will not know whether we need to pass the value of i or the address of i to the function.

A couple of points with respect to libffi:
  1. There seems to be some issues with using the ffi_arg struct for returning floats; I had to explicitly use a float variable for this.
  2. libffi is also used to implement the FORMAT special form. It handles the variable arguments requirement perfectly. More on FORMAT later.