SLIRP, the (SL)ang (I)nte(R)face (P)ackage, is a vectorizing code
generator aimed primarily at simplifying the process of creating
modules for the S-Lang scripting language. It can dramatically
reduce the time and
effort required to make C, C++, or Fortran code callable directly
from the S-Lang interpreter, automatically vectorize functions to
take advantage of the powerful numerical and array capabilities
native to S-Lang, generate parallelizable wrappers for OpenMP-aware
compilers, and even generate Makefiles to automate the build
process.
SLIRP may also be used to generate pure C bindings for C++ code, or
empty (stub) implementations for the interface specified by its
input. These features are more general in nature and the emitted
code has no dependencies upon S-Lang whatsoever. SLIRP has also
been directly or indirectly employed in the publication
of a number of scientific papers.
SLIRP grew out of an effort by the author to develop SLgtk, the S-Lang bindings to the Gimp Toolkit (more popularly known as Gtk). To it's credit the Gtk development community has paid close attention to the problem of binding Gtk to other languages, and mechanisms which considerably aid that process are readily available. Oddly enough, many Gtk language bindings do not use SWIG, but instead rely upon so-called .defs files, which are descriptions -- in the Scheme language -- of the apis of the underlying libraries, apparently generated from the header files and then supplemented with additional semantics.
In typical hacker fashion the SLgtk bindings project began by building upon
the work of someone else, which in this case amounted to borrowing the
.defs files from PyGtk and Gtk itself. While this proved a useful
starting point, over time enough undocumentedness/inconsistencies/omissions
were encountered to undermine my confidence, so I opted to utilize the .defs
files when they provided information not readily available in header files
(e.g., to identify functions which accept NULL for one or more arguments),
and wrote a minimal generator to fabricate the bulk of the bindings directly
from the Gtk headers. After all, even though the source may not
tell the complete story it certainly doesn't forget and never lies.
Relying too heavily upon .defs files could also limit the potential scope
of the generator, making it too specialized to be used on other libraries
which do not describe their apis in such fashion.
Given that SWIG currently does not support S-Lang a logical question to ask is why I did not write a S-Lang extension for SWIG and use it to generate SLgtk? Well, part of the answer lies above, namely that if Gtk were "more SWIG-able" then presumably more developers would be using SWIG for Gtk bindings generation. Minimizing external dependencies was another significant motivation; SLIRP is 99% pure S-Lang code, and about 1% C, so if you've got S-Lang and a C compiler installed (e.g., as millions of Linux users do by default) you can use SLIRP immediately, without installing any additional packages. Other factors included having greater control over the generated code, as well as providing the ability to support Fortran, generate vectorized wrappers (optionally parallelized with OpenMP for multiprocessors), and generate Makefiles, none of which were offered by SWIG.
The remainder of this manual assumes you have installed SLIRP as
outlined in the README at the top of the distribution. A number
of sample modules are provided in the examples subdirectory.
kitchensink Module
As our first example, consider a library named kitchensink whose public
interface ksink.h contains
typedef struct { char *name; double value; } KDatum;
typedef struct { double p[3]; unsigned long id; } KParams;
typedef enum { KSINK_GOOD=0, KSINK_BAD, KSINK_UGLY, KSINK_HORRIFIC } KErrorCode;
extern long ksink_sum (long augend, long addend);
extern double ksink_mult (double op1, double op2);
extern KDatum *ksink_datum_new (char *name, double value);
extern KParams* ksink_params_new (unsigned long id, double params[3]);
extern const char* ksink_params_str (KParams *p);
extern void ksink_datum_destroy (KDatum *datum);
extern void ksink_set_ref_i (int *i);
extern void ksink_print_array_f (long nelems, float array [ ]);
extern char** ksink_make_array_s (void);
extern void ksink_print_array_s (long nelems, char **array);
extern char** ksink_make_array_nts (void);
extern void ksink_print_array_nts (char **array);
extern void ksink_print_error (KErrorCode err);
extern void ksink_swap_double (double *i, double *j);
extern void ksink_close (int fd);
extern FILE* ksink_fopen (char *name, char *mode,
unsigned long *size,
unsigned short *nlinks);
Further suppose that one day that you misplaced your wits and decided it
was absolutely necessary that you be able to call this library from
a S-Lang script. How would you go about such? This is the problem
SLIRP helps solve. Let's begin by issuing the command
unix% slirp ksink.h
which will generate, amongst others, the wrapper
static void sl_ksink_mult (void)
{
double result;
double arg1;
double arg2;
if (SLang_Num_Function_Args != 2 ||
SLang_pop_double(&arg2) == -1 ||
SLang_pop_double(&arg1) == -1 )
{Slirp_usage_err("double = ksink_mult(double,double)"); return;}
result = ksink_mult(arg1, arg2);
(void) SLang_push_double ( result);
}
SLIRP provides a number of ways of tailoring its operation and output,
but in this instance the code is emitted to a file named ksink_glue.c
in the current working directory.
Next we need to make the S-Lang interpreter aware of this wrapper by
installing it as a so-called intrinsic function. There's more than
one way to do this, but the best practice is to create an entry for each
wrapper within an intrinsic function table, and register all of them
at once with a
single call to either SLadd_intrin_fun_table() or its namespace
variant SLns_add_intrin_fun_table(). To assist the language binder
with this SLIRP also generates an intrinsic function table entry for
each each wrapper function that it generates. In our example this
entry looks like
MAKE_INTRINSIC_0("ksink_mult",sl_ksink_mult,V),
and is also emitted to ksink_glue.c. Now all that remains is to provide
a means of binding the generated code to the S-Lang interpreter at runtime.
For this we need another code fragment to initialize the module, something
along the lines of
#include "slang.h"
#include "ksink.h"
static SLang_Intrin_Fun_Type ksink_Intrin_Funcs[] =
{
MAKE_INTRINSIC_0("ksink_mult",sl_ksink_mult,V),
...
SLANG_END_INTRIN_FUN_TABLE
};
SLANG_MODULE(ksink);
int init_ksink_module_ns(char *ns_name)
{
SLang_NameSpace_Type *ns = NULL;
if (ns_name != NULL) {
ns = SLns_create_namespace (ns_name);
if (ns == NULL)
return -1;
}
if (-1 == SLns_add_intrin_fun_table (ns, ksink_Funcs, "__ksink__"))
return -1;
return 0;
}
Typically the file generated by SLIRP will be compiled via Makefile rules,
to build an importable module which may be accessed at runtime via
the S-Lang import() function. In our case this would instruct the
S-Lang runtime to dynamically load a shared object library named
ksink-module.so, invoke its init_ksink_module_ns() initializer,
and register the wrapper functions, constants, and variables defined
within.
Here's how our ksink_mult wrapper might be used within a S-Lang script:
import("ksink");
define do_mult(op1, op2)
{
print("ksink_mult(%S, %S) = %S", op1, op2, ksink_mult(op1,op2));
}
do_mult(333, 3);
do_mult(PI/2, 2);
For more details consult the code for this example, which is located in
the examples/kitchensink directory of the SLIRP distribution and
can be built and invoked by typing make followed by make demo
at the command line. The modules directory
within the S-Lang source distribution also contains a number of useful
modules, as does the MIT modules page at
http://space.mit.edu/cxc/software/slang/modules/.
It is important to realize that SLIRP is no silver bullet. In the abstract code generators are not meant to free the programmer from the responsibility of thinking at all, but rather aim at freeing them to think about only that which requires substantive thought, leaving the rest of the presumably mundane details to be mechanically churned out by a machine.
For a variety of reasons the developer may prefer to wrap portions of a library manually, instead of relying upon wrappers emitted by a generator. As such most bindings projects will likely include both generated and hand-crafted code, so it is pragmatic to view a code generator as but one element in the toolbox of the language binder.
It should also be noted that SLIRP does not fully preprocess, nor
is it a compiler for, the C language. By choosing a S-Lang-based
implementation (instead of one, say, in C, generated from a grammar
specified in Lex/Yacc) we exchange completeness for size, simplicity
of design and distribution, ease of use, and portability.
However, as discussed in section
Pointers, even if SLIRP contained
a full C preprocessor or compiler it would still be impossible for it
to mechanically map every possible construct from the universe of
syntactically valid C libraries to an equivalent construction in S-Lang.
For these reasons code generators provide mechanisms to customize
their behavior and layer additional semantics on top of the underlying
api (such as the .defs mechanisms described earlier, or the interface
files respectively employed by SWIG and SLIRP). Nevertheless,
SLIRP is known to generate useful bindings for numerous C, C++,
and Fortran codes, including SLgtk, OpenGL, MySQL, PVM,
libGlade, glibc (gettext), HDF5, NetCDF, cgilib,
ASURV, and volpack.