Next Previous Contents

3. Code Generation

In the broadest sense SLIRP consumes an interface definition and produces one or more wrapper products. The input interface is specified via one or more source files (C or C++ headers, and/or Fortran source), supplemented with an optional SLIRP interface file. The generated output may take the form of:

By default SLIRP generates S-Lang wrappers; the other output forms are generated by specifying the -stubs, -cfront, -make, and -print options, respectively, at invocation.

3.1 Wrapper Functions

SLIRP is capable of wrapping most routines defined in most C and Fortran 77 codes. Its C++ support is less comprehensive, but broad enough to generate useful bindings for a variety of C++ codes. Although the code generated by SLIRP has been verified compliant with more modern Fortran compilers (such as gfortran or f95), there is no explicit support for extensions to the language (e.g. objects or modules) which appear in newer versions of the Fortran standard.

There are three conditions under which SLIRP will not wrap a given function. The first two occur when the -noautotype switch has been specified on the command line and there is no

For example, since SLIRP does not contain typemaps for function pointers any function whose signature contains one will not be wrapped. SLIRP will also ignore a function

This can be useful when you would prefer to code a wrapper manually rather than accept what SLIRP would generate, or if you'd rather not wrap the function at all. The exact mechanisms for ignoring symbols are discussed in the next section.

Wrapper Function Structure

Each S-Lang wrapper function has the general form

                Function_Declaration
                {
                   Variable_Declaration_Block
                   Argument_Marshal_Block
                   Function_Call_Block
                   Return_Block
                }

Wrappers are declared with static linkage, to avoid clashes with other modules that might define similar symbols. Arguments are passed from S-Lang scope in one of two ways: via the function parameter list (for simple scalar types) or within the argument marshalling block. In the latter case the wrapper is declared with a void argument list and each argument is explicitly popped from the S-Lang stack and verified to match in type and number the arguments required by the wrapped function. The function call block invokes the underlying routine, while the return block is responsible for finalization tasks such as deallocating temporary resources and pushing the return value onto the S-Lang stack.

Through the use of annotations it's possible to automatically construct S-Lang wrappers whose signatures do not match that of the underlying function, by either accepting fewer arguments or returning more values.

Usage Statements

By default the argument marshalling block also contains code to emit Usage: statements when the function has been invoked with the incorrect number or type of arguments. These serve as useful reminders of the purpose of the function, for both new and veteran users of a library, and can often save a trip to the documentation. See the -nopop runtime switch for more details.

3.2 Interface Files

Although many command line options are provided to steer the code generation process, interface files are by far the more powerful means of customizing and extending SLIRP, and their description occupies the bulk of this manual. Interface files are S-Lang scripts, and so may contain any legal collection of S-Lang statements. They are also optional, in the sense that SLIRP does not require one for nominal execution; most bindings projects, however, would likely benefit from their use.

The SLgtk interface file, for example, contains hundreds of type mappings and ignore directives, among other content. At the other end of the scale, the kitchensink module could in principle be generated from an interface file containing only

        slirp_define_opaque("KDatum",NULL,"ksink_datum_destroy");
While most SLIRP interface files won't be this brief, generating one for a first cut module for the typical library will usually take only a few minutes. Additional interface files are bundled in the examples subdirectory of the SLIRP distribution.

Loading

By default SLIRP looks for an interface file named slirprc in the current working directory; failing that, SLIRP then attempts to load the file specified by the environment variable $SLIRPRC. Specifying the -rc switch at invocation time overrides both of these, provided the given file is readable.

Nesting

An interface file may reference other interface files by calling

            slirp_include_rc(filename)
This permits modules with obvious dependencies to share common type definitions and variables, in fashion similar to that of the ANSI C #include mechanism. Note that this will likely introduce a link time dependency between the modules, which will have to be reflected in your distribution (e.g. within a Makefile or project file).

The function has a void return type, and will signal an error if problems occur while loading the external file. To avoid redundancy and save space, code will not be generated for types loaded through external interfaces files.

3.3 Preprocessing

As noted earlier, SLIRP does not contain a full C preprocessor, with the chief omissions being that it does not #include headers at runtime, nor will it perform substitution upon parameterized (function) macros. This should go unnoticed by most bindings projects, especially those whose interface is defined within a single file. Libraries which define their public interface within multiple header files, such as Gtk, may be wrapped with SLIRP by specifying each public header on the command line at invocation time.

Conditional Compilation

SLIRP evaluates the conditional compilation directives #ifdef, #ifndef, #if, #elif, #else, and #endif, and will avoid wrapping any content within an exclusion block. The ?, :, and , operators are not supported during conditional evaluation, and will induce the truth value of the entire expression in which they appear to 0 (false).

Simple #define Macros

SLIRP wraps integral-valued and real-valued #define macros as Integer_Type and Double_Type constants, respectively. Likewise, string-valued macros (i.e. text enclosed within quotes) are wrapped as variables of String_Type. SLIRP adopts a conservative stance for these wrappings, preferring macros of form

        #define         identifier      value
or combinations thereof which, after substitution, are equivalent to such. Source file macros can be redefined in an interface file by using either of
        slirp_define_macro(name [, value]);
        #define    name      [value]
Both parameters to the functional form are strings. To define a string constant using the functional form the value must include appropriately delimited quotes. Following C, when value is omitted the macro is defined to be the empty string. Source macros may be ignored by specifying an #undef directive
        #undef     name
within an interface file, or by using the methods described in section Ignoring. Both the #define and #undef directives are described further within the chapters on annotations.

Parameterized #define Macros

Macros whose LHS contains parentheses, and possibly commas, are referred to as parameterized, or function, macros. Although the preprocessor will not substitute such macros when parsing source files, it is possible to wrap them as functions callable from S-Lang scope with

        slirp_map_macro(macro_name , "return_type ( argument_list )");
This indicates to SLIRP that the named macro should be interpreted as if it were a function prototyped as
        return_type   strlow(macro_name)  (argument_list);

3.4 Enumerations

While processing enumerations SLIRP will wrap each enumerant as a S-Lang Integer_Type constant. This allows scripts to utilize the enumeration in the same manner as would C code, and in many instances the two will be indistinguishable from one another. For example, the following S-Lang snippet

                variable value = KSINK_BAD;
                ksink_print_error(value);
is equivalent to the C code
                int value = KSINK_BAD;
                ksink_print_error(value);
and in fact both could be reduced to the single line
                ksink_print_error(KSINK_BAD);
and used interchangeably.

3.5 Ignoring Symbols

Sometimes it's desirable to have SLIRP avoid wrapping one or more functions, macros, or variables. Prior to the introduction of annotations this was done by adding the name of each individual symbol to an ignore array, as described in the following subsections. Although this is still supported, the preferred method is to use an annotation such as #ignore or #undef, as they are cleaner and more general. In either case, recall that because C symbol names are case-sensitive the contents of ignore lists are as well. Groups of functions may also be tagged en masse for ignoring, by matching their argument signatures to an #argmap(ignore) annotation as described in section IgnoreMap.

Functions

Prior to the introduction of the #ignore annotation a function could be explicitly ignored by adding its name to the list of ignored functions within your interface file. This list is declared by SLIRP as a global array of String_Type

        variable ignored_functions = String_Type[0];
that is empty by default. The idiom for extending the list in this manner is
        ignored_functions = [
        "name_of_first_function_to_ignore",
        "name_of_second_function_to_ignore",
        ...
        "name_of_nth_function_to_ignore",
        ignored_functions ];
This approach is deprecated, though, in favor of the #ignore or #argmap(ignore) annotations.

Macros and Variables

If you'd like SLIRP to ignore a particular #define macro, add its name to the ignored_macros list. This is another String_Type array, initially declared as

        variable ignored_macros = String_Type[0];

Rather than explicitly adding new elements to this array, though, your interface file should utilize either the #ignore or #undef directives. Finally, the array

        variable ignored_variables = String_Type[0];
records the names of those variables defined in the input interface which SLIRP should not make visible within S-Lang scope. Again, instead of explicitly manipulating this variable your interface file should employ the #ignore directive.

3.6 NULL and Omitted Arguments

Unlike in other languages, where null is viewed as a value that may be assigned to pointers of any type, a null in S-Lang is both a value (NULL) and a type (Null_Type). The Null_Type is useful, but can require additional care on the part of language binders. To see why, consider the prototype

        void SomeFunc(char *name, double value);
All C compilers should permit this function to be invoked as follows:
        SomeFunc(NULL, 5);
(whether the function behaves well in such cases is a different question). A straightforward mapping of this function to S-Lang would be registered with a function table entry like
        MAKE_INTRINSIC_2("SomeFunc", SomeFunc, SLANG_VOID_TYPE,
                                        SLANG_STRING_TYPE, SLANG_DOUBLE_TYPE)
But this would prohibit the function being called from S-Lang scope as
        SomeFunc(NULL, 5);
because the interpreter would complain that the Null_Type of the first argument cannot be cast to a String_Type. Fortunately there is a way around this, namely to register the wrapper as though it accepts zero arguments
        MAKE_INTRINSIC_0("SomeFunc", SomeFunc, SLANG_VOID_TYPE)
and then pop the char* and double values explicitly within the body of the wrapper, at which point their types may be checked and the NULL values handled accordingly.

While this technique works, it would seem to require additional -- and more important, manual -- coding on the part of the language binder. To automate the generation of such code SLIRP provides the associative array

        variable accepts_null_args = Assoc_Type[Any_Type,NULL];
The elements of this array are themselves 1D arrays, whose elements represent those arguments in the function signature for which NULL is a valid value. In our case a statement like
        accepts_null_args["SomeFunc"] = [1];
would indicate to SLIRP that when it's asked to generate a wrapper for SomeFunc it should consider NULL a legal value for the first argument, whatever its type may be. This feature also allows functions to be called with arguments omitted, since the two statements
        SomeFunc( , 5);
        SomeFunc(NULL, 5);
are equivalent in S-Lang.

3.7 C++

When wrapping a C++ library SLIRP will attempt to wrap all functions, class methods, constants, enumerated types, and simple #define macros specified in its public interface. Get/set methods are automatically generated to wrap simple public fields of a class. Overloaded functions and methods will be wrapped, and class inheritance relationships will be honored. Default values are also supported, although you should be mindful that the only sensible default value which may be assigned to pointer arguments is NULL.

While this enables the module developer to wrap a good fraction of mainstream C++ code, by no means should it be considered a full treatment of the language. For example, get/set wrappers for public class fields are generated only when those fields are of basic/primitive type (in the parlance of Kernighan and Ritchie, 1988), and a number of more semantically deep features of C++ (such as templates, operators, and friend classes) are completely unsupported.

The -cfront option may be specified on the commandline to generate standalone C wrappers for a C++ library. The code generated in this case will make no reference to the S-Lang C api, and is thus suitable for calling the C++ library directly from C or Fortran. Examples of both usages are given in the examples/cpp directory.

3.8 Fortran

If a Fortran compiler is detected on the system during configuration SLIRP will also be able to generate bindings for many Fortran codes. Simply specify the Fortran source (where files are assumed to end with either a .f or .F suffix) to SLIRP in the same manner one would normally specify C/C++ headers. SLIRP allows Fortran and C/C++ to be processed during the same invocation, which simplifies the creation of mixed-language modules. Several sample modules containing wrapped Fortran code are given in the examples/fortran and examples/vec directories.

Implicit typing is supported, as are complex-valued functions and subroutines with complex arguments. In most common cases necessary conversions like transposition and string array unrolling are performed transparently, allowing multidimensional and CHARACTER arrays to be passed back and forth between S-Lang & Fortran without regard to issues like string length or whether arrays are internally laid out in row-major or column-major formats. COMMON blocks will also be wrapped, and can be accessed from S-Lang scope using the functions

 get_commblock_list()                      gives names of all common blocks in module
 get_commblock(blkname)                    retrieves a CommBlock handle to a given block
 get_commblock_value(block,varname)        retrieves value of named variable in block
 set_commblock_value(block,varname,value)  assigns value to named variable in block
as well as using struct.field notation. Here are some examples:
        linux%  cd examples/fortran
        linux%  slsh

        slsh>   slsh_append_semicolon(1);
        slsh>
        slsh>   import("fsubs")
        This module interfaces to 6 Fortran common blocks.
        You may need to invoke the Fortran routine(s) which
        define(s) them prior to using their values in S-Lang.
        You may use struct.field notation to access or modify
        the values of individual common block variables.
        Type "help commblock" for more information.

        slsh>   initcomm("blah")

        slsh>   com1 = get_commblock("com1")

        slsh>   com1
        Fortran common block with 2 variables:
                icom1 Integer_Type
                rcom1 Float_Type

        slsh>   com1.icom1
        2

        slsh>   com1.icom1 = 99

        slsh>   com1.icom1
        99

        slsh>   print(get_commblock_list)
        "com_block2_"
        "com4"
        "_BLNK_"
        "com3"
        "com5"
        "com1"

        slsh>   com4 = get_commblock("com4")
        slsh>   com4
        Fortran common block with 4 variables:
                comp1 Complex_Type
                comp2 Complex_Type[2]
                comp3 Complex_Type
                comp4 Complex_Type[2]

        slsh>   print(com4.comp4)
        (40 + 50i)
        (60 + 70i)

        slsh>   com4.comp4[0] = -9 - 9i
        slsh>   print(com4.comp4)
        (-9 - 9i)
        (60 + 70i)
The main limitation of the COMMON block support is that individual elements of string arrays & single-precision complex arrays cannot be modified:
        slsh>   com4.comp2[0] = -6i
        Complex_Type Array is read-only
This is because S-Lang does not provide, as of version 2.1.3, a single precision complex type, so COMMON block variables of this type must be converted to/from double precision. Similarly,
        slsh>   com5 = get_commblock("com5")
        slsh>   com5
        Fortran common block with 2 variables:
                str String_Type (length 13)
                strarr String_Type[3] (length 13)

        slsh>   com5.strarr[0] = "foo"
        String_Type Array is read-only
string arrays are implemented differently in Fortran and S-Lang/C, so passing them back and forth between the languages also requires conversion. These conversions are, however, transparently performed for scalar instances of string and single-precision complex types:
        slsh>   com4.comp1
        (3 + 2i)
        slsh>   com4.comp1 = 99 - 99i
        slsh>   com4.comp1
        (99 - 99i)
Recall that Fortran call semantics differ from C, in that Fortran passes all variables by reference (i.e., as pointers), even scalars, while C passes only by value. The implications of this are that to pass a scalar variable from C to Fortran one normally needs to use the address operator, as in:
        int c_variable = 5;
        some_fortran_function( ..., &c_variable, ...);
As a convenience SLIRP hides this detail from the S-Lang programmer, enabling Fortran entry points to be invoked from S-Lang with the same syntax used to invoke C functions. However, as demonstrated in the sample Fortran module, when writing your own annotations your prototypes will need to use pointer syntax. Another convenience afforded by SLIRP is that Fortran functions and subroutines are invoked from S-Lang scope using exactly the same name as would be employed in Fortran scope. That is, the S-Lang programmer needs no knowledge of how the Fortran compiler mangles entry point names (e.g. upper case, or underscore suffix, etc) in order to call a function or subroutine from S-Lang scope.

In order to maximize portability SLIRP wraps all Fortran function calls within a subroutine whose first argument corresponds to the return value of the function. In general this tactic will not be visible at the S-Lang layer, although the generated wrappers will themselves need to be reflected as a build dependency within the module Makefile. For example, the SLIRP Makefile generation facility would automatically add sfwrap_foo.f as a dependency for a module generated from foo.f.

Finally, routines and functions whose signatures contain function pointer arguments (actual arguments which are names, e.g., of external procedures) will not be automatically wrapped.

3.9 Makefiles

As an additional convenience SLIRP can also generate "starter" make files to automate compilation and linking of your module. For example, given a header file cos.h containing

        double cos(double x);
one might generate the module, compile it, and invocation-test the result in just two simple steps:
        unix% slirp -lm cos.h
        Starter make file generated to 'Makefile'

        unix% make test

        cc  -fPIC -I. -c cos_glue.c
        gcc -shared -o cos-module.so cos_glue.o  -lm -lslang -ldl -lm

        slsh cos-test.sl
        Success!
(see the examples/makef directory for a demonstration). This process can be initiated or tuned by the -make, -ldflags, -I, -L, and -l flags, which are described in section Command_Line_Options and should be reasonably familiar to anyone conversant with compilation.

3.10 Template Modules

SLIRP can be used to generate empty (template) modules, by either specifying an empty header file, such as

        unix%  touch foo.h
        unix%  slirp foo.h
        unix%  ls -l foo_glue.c
        -rw-rw-r--  1 mnoble asc 3393 Nov 10 17:39 foo_glue.c
or by specifying /dev/null as input:
        unix%  slirp -m template_example /dev/null
        unix%  ls -l template_example_glue.c
        -rw-rw-r--  1 mnoble asc  3470 Nov 10 17:41 template_example_glue.c

3.11 Stubs

When the -stubs option is specified SLIRP will generate one callable function with an empty body, or stub, for each entry point in the input interface. This can be a surprisingly useful testing feature, since it allows a module to be generated and nominally exercised without the need to compile or link in the underlying library or any of its (potentially numerous) dependencies.

The SLIRP distribution bundles two stub modules, in the examples/stubs and examples/opengl subdirectories. In the OpenGL module, for example, stubs for the functions prototyped as

    void glVertex2d( GLdouble x, GLdouble y );
    GLboolean glAreTexturesResident( GLsizei n, const GLuint *textures, GLboolean *residences);
resemble
    void  glVertex2d (GLdouble arg1, GLdouble arg2) {  } 

    GLboolean  glAreTexturesResident (GLsizei arg1, const GLuint* arg2, GLboolean* arg3)
    {
        return (GLboolean) 0;
    }
Since stub functions are pure C they exhibit no dependency upon S-Lang, and may thus be employed in wider contexts.

3.12 Debugging With slirp_debug_pause()

Debugging modules can be difficult, e.g. because breakpoints cannot be set within a module until after it's been loaded. Further, knowing exactly where (in the source code) and when (during process execution) this occurs requires in-depth knowledge of S-Lang library internals, and is virtually impossible to isolate when the S-Lang library has not been compiled for debugging. To address these issues SLIRP offers the -d option, which will embed within your module the slirp_debug_pause() stub.

To activate the stub set the SLIRP_DEBUG_PAUSE environment variable before importing your module. This will cause the parent process to wait before exiting module initialization, during which time you may set breakpoints within the codebase of the module (since its symbol table will have been loaded at that point) or its dependencies. This removes the need for module developers to learn the internals of dynamic loading in S-Lang, and permits the module to be debugged even when the S-Lang library itself has not been compiled for such.

The debugging stub will do nothing if SLIRP_DEBUG_PAUSE is unset in the environment. When the variable is set, however, if the result of atoi(getenv(SLIRP_DEBUG_PAUSE)) evaluates to a negative integer N slirp_debug_pause() will sleep for abs(N) seconds, otherwise the stub will pause indefinitely, awaiting a keypress in the terminal from which the parent process was launched.


Next Previous Contents