Plasma GitLab Archive
Projects Blog Knowledge

Programming with Objective Caml Document Tree
 
  NAVIGATION

How to...
How to load the O'Caml bytecode interpreter into a Perl program
 How to install OCAMLODBC as findlib package




Contact
   

How to load the O'Caml bytecode interpreter into a 
Perl program

Consider you have a project mainly written in Perl, but a specific problem requires a solution in a more efficient language such as O'Caml. In the following I will show how to load the O'Caml bytecode interpreter as shared library from Perl, and how to call O'Caml functions from Perl.

The following instructions apply to all Unix operating systems with support for loading dynamic libraries at runtime (i.e., libdl.so is present). They were tested on Linux and Solaris.

Step 1: Compile the bytecode interpreter as shared library

First, you need the bytecode interpreter as shared library. It is simple to build it, because it is sufficient to pass the option -fPIC to the C compiler, and to transform the archive file (suffix .a) into a shared object (suffix .so):

  • Get the source code tarball ocaml-2.04.tar.gz from the FTP server at INRIA. Unpack the tarball:

    gzip -d ocaml-2.04.tar.gz
    tar xf ocaml-2.04.tar.gz
    
  • Change into the new directory, and configure O'Caml; see the documentation coming with the O'Caml sources for details:

    cd ocaml-2.04
    ./configure <...necessary options...>
    
  • The configuration scripts write a Makefile in the config subdirectory. Edit this Makefile, and append the option -fPIC to the line "BYTECCCOMPOPTS=...". In my case, the line looks then like

    BYTECCCOMPOPTS=-fno-defer-pop -Wall -fPIC
    but this may differ.
  • Change into the "byterun" directory, and build the interpreter:

    cd byterun
    make
    
    This results in a file "libcamlrun.a".

    Important note: On my Linux system with gcc-2.7.2.3 the compiler failed to build the interpreter. It worked with egcs-2.91.66.

  • Create a shared object: How to do this depends on the operating system. On my Linux system, the following command works:

    ld -shared --whole-archive -o libcamlrun.so libcamlrun.a
    On Solaris, use:

    ld -G -z allextract -o libcamlrun.so libcamlrun.a
  • Compile additional libraries: If you need one of the libraries in the "otherlibs" directory, compile them with -fPIC, too: Simply change into the directory, invoke "make", and "ld" the shared object. For example:

    cd otherlibs/unix
    make
    ld -shared --whole-archive -o libunix.so libunix.a
    
  • Copy all the shared objects to their final location, e.g.

    cp byterun/libcamlrun.so otherlibs/unix/libunix.so /usr/local/lib
    
    If you like other names, you may rename the shared objects; it is only required that the names begin with "lib" and end with ".so".

Note: You need of course an installed O'Caml compiler and runtime system of the same version to build your application that is going to be executed by this special bytecode interpreter. An ordinary runtime system (i.e. without -fPIC) is sufficient for that purpose.

Test your shared objects: Create a "Hello world" program "t.ml":

print_endline "Hello world";;
Create the C loader "l.c":

main (int argc, char **argv) {
    caml_startup(argv);
}
Compile t.ml with

ocamlc -o t.o -output-obj -ccopt -fPIC t.ml
and l.c with

ocamlc -o l -c l.c 
Link the executable with

cc -o l l.o t.o -L/where/to/find/libcamlrun.so -lcamlrun
Now try

./l
You will normally get an error that the library libcamlrun.so is not found. There are several ways to solve this: Add the path to the directory of libcamlrun.so to the environment variable LD_LIBRARY_PATH, or add this path to the executable (cc options "-R path" on Solaris, "-Wl,-rpath,path" on Linux), or add this path to the system configuration (edit /etc/ld.so.conf on Linux and run "ldconfig").

Step 2: Write your O'Caml application to be loaded from Perl

The application must provide the functions to be called from Perl, and register the functions. Here a simple example "dup.ml":

let dup_string x = x ^ x;;
Callback.register "ocaml_dup_string" dup_string;;
Compile with

ocamlc -o dup.o -output-obj -ccopt -fPIC dup.ml
and create a shared object:

ld -shared --whole-archive -o libdup.so dup.o -L/where/to/find/libcamlrun.so -lcamlrun
Note that the application must be a program output as an object file; it is not possible to only produce an bytecode archive and load that later (though it is possible to load bytecode archives dynamically with the dynlink extension of O'Caml).

Step 3: Write the Perl module that loads everything

Perl comes with a utility h2xs which sets up a Perl module with C extension initially. Our Perl module is called Dup, and we begin with

h2xs -A -n Dup
which creates a directory Dup in the current directory containing the files Dup.pm, Dup.xs, Makefile.PL, test.pl, and some other files.

Makefile.PL is the configuration of the Makefile to generate later. Change it as follows:

use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.

if (`uname` =~ /^SunOS/) {
    $L='-lcurses -lnsl -lsocket -lm';
} elsif (`uname` =~ /^Linux/) {
    $L='-lm';
} else {
    die "Do not know CCLIBS";
}

WriteMakefile(
    'NAME'      => 'Dup',
    'VERSION_FROM' => 'Dup.pm', # finds $VERSION
    'LIBS'      => ["-L.. -ldup
                     -L/where/to/find/libcamlrun.so -lcamlrun
                     $L"],
    'DEFINE'    => '',     # e.g., '-DHAVE_SOMETHING'
    'INC'       => '-I/where/to/find/caml/headers',
#    'CCFLAGS'    => '-g',
);
The variable $D contains the system libraries necessary to run O'Caml; these libraries can be found in the config/Makefile that was used while building O'Caml in the line setting the variable CCLIBS. The above code simply knows what to do when running under Linux or Solaris; this is not the best solution but works at the moment.

The file Dup.pm needs not to be edited; you can put additional Perl code here, and add documentation.

The file Dup.xs is the input of a preprocessor that generates Dup.c later. It may contain normal C code, and so-called XSUBs. See the documentation under "perlxs" and "perlxstut" coming with Perl. Here, the following is sufficient:

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "mlvalues.h"
#include "callback.h"

MODULE = Dup            PACKAGE = Dup           

SV *
dup(expr)
    SV *expr

    PROTOTYPE: $

    CODE:
        char *s;
        int len_expr;
        value s_caml, r_caml;
        value *f;

        /* Coerce the scalar value "expr" as string into a buffer "s", and put
         * into "len_expr" the length of the string.
         * Note that "s" may contain NUL bytes.
         */

        s = SvPV(expr, len_expr);
        
        /* Convert the buffer "s" with length "len_expr" into a Caml string
         * "s_caml":
         */

        s_caml = alloc_string(len_expr);
        memcpy(String_val(s_caml), s, len_expr);

        /* Invoke the function registered under the name "ocaml_dup_string": */

        f = caml_named_value("ocaml_dup_string");
        
        if (f == NULL) {
            croak("OCaml function not found: ocaml_dup_string");
        };

        r_caml = callback_exn(*f, s_caml);

        /* Handle normal results and exceptions differently: */

        if (Is_exception_result(r_caml)) {

            croak("Uncaught OCaml exception: %s",
                  (*format_caml_exception)(Extract_exception(r_caml)));

        } else {

            /* Create a new scalar value from the string with the given
             * length:
             */

            RETVAL = newSVpv(String_val(r_caml), (*string_length)(r_caml));
        }

    OUTPUT:
        RETVAL


BOOT:
    {
        char **a;

        /* Fake the command-line arguments: */

        a = malloc(2*sizeof(char *));
        a[0] = malloc(1);
        a[0][0] = 0;
        a[1] = NULL;
        caml_startup(a);
    }
An XSUB is defined with name dup which accepts a scalar argument and returns a scalar argument. The Perl macro SvPV interprets the scalar argument expr as string and returns the string in the buffer s. The length of the buffer is stored into len_expr. This buffer is then converted into a O'Caml string using alloc_string and copying the buffer into the allocated memory. The registered function "ocaml_dup_string" is looked up and called back (note that callback_exn does not work properly in O'Caml 2.02; it is recommended to use 2.04). If an exception is raised, it is turned into a Perl exception (if the "die" mechanism can be regarded as exception mechanism at all). Otherwise, a regular result has been computed which can be passed back to Perl using newSVpv. This function creates a new scalar value from a string.

The BOOT section initializes the bytecode interpreter by calling caml_startup; the argument passed to this function must be an argv-type array (from O'Caml visible in Sys.argv).

If you do a 'use Dup;' from Perl, the necessary shared objects are loaded, and the BOOT section is executed. This means that especially the O'Caml statement Callback.register in the file dup.ml is run such that the registered functions can be later called back.

If you execute then "$result = Dup::dup($argument);" the XSUB dup is called which passes control over to the dup function defined in the O'Caml code file.

This example demonstrates only how to pass strings from Perl to O'Caml and vice versa. Of course, it is possible to pass structured values; but this is far more complicated. Usually, it is simpler to pass such values from Perl to O'Caml as string terms, and parse these strings in the O'Caml module. This is written pretty quickly using ocamllex and ocamlyacc. In the other direction, it is possible to generate in the O'Caml module strings that can be evaluated as Perl code; for example you may return "{ 'a' => 'b' }" to return a hash reference, and do

$r = eval("{ 'a' => 'b' }");
die ($@) if ($@);
to get the hash.

Compile:Do

perl Makefile.PL
make

Test: Edit the file test.pl as follows:

# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl test.pl'

######################### We start with some black magic to print on failure.

# Change 1..1 below to 1..last_test_to_print .
# (It may become useful if the test is moved to ./t subdirectory.)

BEGIN { $| = 1; print "1..2\n"; }
END {print "not ok 1\n" unless $loaded;}
use Dup;
$loaded = 1;
print "ok 1\n";

######################### End of black magic.

# Insert your test code below (better if it prints "ok 13"
# (correspondingly "not ok 13") depending on the success of chunk 13
# of the test code):

my $s = Dup::dup("abc");
if ($s eq "abcabc") {
    print "ok 2\n";
} else {
    print "not ok 2\n";
}
Run this script by

make test
It should output

1..2
ok 1
ok 2

Install: You can install the Perl part of the whole project using "make install".

Final remarks

It is only possible to load O'Caml bytecode programs from Perl because they can be compiled as position-independent code; native programs are position-dependent and because of this not suited to be loaded dynamically. You may ask the O'Caml developers to add such a feature.

 
This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml