Yorick: Embedding

Home Manual Packages Global Index Keywords Quick Reference |
# 4. Embedding Compiled Routines Inside Yorick You can create a custom version of Yorick containing your own C or Fortran compiled code. If you are careful, your custom version will be easily portable to any site where Yorick has been installed. You will considerably ease portability problems (read "future hassles for yourself") by writing in ANSI C, which is a portable language, as opposed to Fortran, which is not. My experience with C++ is that its portability is intermediate between ANSI C and Fortran. You should be able to write C++ packages for Yorick by using the `extern "C"` statement for the interface routines called by the interpreter. I don't encourage this, however, since the interpreted language removes many of the motives for programming in C++ in the first place. I won't say any more about C++ packages for Yorick; the idea is to use the ``Make-cxx'` template instead of the standard ``Maketmpl'` to build versions of Yorick which include C++ packages. If you do choose Fortran, stick to a strict subset of ANSI Fortran 77. Do not attempt to pass character variables into or out of interface routines, nor put them in common blocks. Also, try not to use common blocks to pass inputs to or receive outputs from your interface routines. (This is possible; if you enjoy Fortran programming, presumably you'll enjoy figuring out a portable way to do this.) Whether you write in Fortran or C, do not attempt to do I/O of any sort in your compiled code. The whole idea of embedding routines inside Yorick is to let Yorick handle all I/O -- text and graphics. In the following discussion, I refer to three directories: ``Y_SITE'` is the directory where the architecture independent parts of Yorick reside; everything Yorick needs at runtime is here. ``Y_HOME'` is the directory where the libraries and executables you need to build custom versions are stored. ``Y_LAUNCH'` is the directory containing the executable for your version of Yorick. When you run Yorick, the names of all three directories are available as variables; for example, start yorick and type `Y_HOME` to print the name of that directory. Also, I will only discuss the UNIX program development environment; at the present time, Yorick is not easy to extend on any other platform. The first step is to create a directory in which to build your custom version of Yorick, and put your C and/or Fortran source and header files there. Next, you need to write a Yorick include file ``my_start.i'` which will be loaded whenever your custom Yorick starts. This file is read not only by Yorick as a startup file, but also by the Codger automatic code generator when you make the custom version. Codger generates a table of all of the compiled objects (functions, global variables, or Fortran common blocks) which can be referenced from the interpreter. It will also generate wrapper code to call your compiled functions, if you decide not to do this yourself. In this include file:
    extern my_func;
    /* DOCUMENT my_func(input)
         returns the frobnostication of the array INPUT.
     */
connects `my_func` to a compiled function `Y_my_func` of type BuiltIn, which you are responsible for writing. See ``Y_HOME/ydata.h'` for the definition of the BuiltIn function type. This function must pop its arguments off of Yorick's interpreter stack using routines declared in ``Y_HOME/ydata.h'`, and push its result back onto the stack. The functions `YGet*` grab things off the stack; `sp` is the stack pointer. The `Globalize` function gives you access to variables by the names visible to the interpreter; they are in `globTab`. You can use `Push*` functions to push values onto the stack; use `CheckStack` to be sure there is enough stack space to do so. Usually, you want to avoid all these details; codger can generate ``Y_my_func'` for you automatically. To do this, put PROTOTYPE comments in your startup include file:
    func my_func(input)
    /* DOCUMENT my_func(input)
         returns the frobnostication of the array INPUT.
     */
    {
      return my_func_raw(input, numberof(input));
    }
    extern my_func_raw;
    /* PROTOTYPE
       double my_func_C_name(double array input, long length)
     */
This generates a wrapper for a C function which takes a single array as input and returns a scalar result. If the function had been Fortran, it would have looked like this (Fortran passes all arguments by reference -- that is, as if they were arrays):
    func my_func(input)
    /* DOCUMENT my_func(input)
         returns the frobnostication of the array INPUT.
     */
    {
      return my_func_raw(input, numberof(input));
    }
    extern my_func_raw;
    /* PROTOTYPE FORTRAN
       double my_func_Fortran_name(double array input, long array length)
     */
Legal data types for the function return result in the `PROTOTYPE` comment are: void (i.e.- a subroutine), char, short, int, long, float, or double. Legal data types for the function parameters in the `PROTOTYPE` comment are: void (only if there are no other parameters), char, short, int, long, float, double, string (char *, guaranteed 0-terminated), or pointer (void *). These may be followed by the word "array", which becomes "*" in the C source code, to indicate an array of that type. The parameter name is optional. The `DOCUMENT` comment should start with `/* DOCUMENT`. They will be returned by the interpreted command `help, my_func`, and be included in the poor- man's document produced by Yorick's "mkdoc" command (see ``Y_HOME/include/mkdoc.i'`).
    extern my_global;
    reshape, my_global, datatype;
attaches the interpreted variable `my_global` to a C-compiled global of the same name, which has the data type `datatype` (this must have been declared in a previous struct or be one of the primitive types). If you want `my_global` to be attached to a global variable of a different name, use:
    extern my_global;
    /* EXTERNAL my_global_C_name */
    reshape, my_global, datatype;
To attach to a Fortran common block, say
    	double var1, var2, var3
    	common /my_common/ var1, var2, var3
    	save /my_common/
(note that this doesn't make sense unless the common block is saved outside the scope of the functions in which it is used) use:
    struct my_common_type { double var1, var2, var3; }
    extern my_common;
    /* EXTERNAL FORTRAN my_common */
    reshape, my_common, my_common_type;
If you mix double, integer, and real data in a single common block, you can ensure that you won't have any alignment difficulties by putting all the doubles first, followed by integers and reals. If you don't do this, you're relying on the existence of a Fortran compiler switch which forces proper data alignment -- some machine someday won't have this. Near the beginning of your startup include file (or files -- you can have several if you like), you should place a MAKE-INSTRUCTIONS comment:
    /* MAKE-INSTRUCTIONS
    SRCS = src1.c src2.c src3.c \
           src4.c src5.f src6.c src7.f
    LIB = frob
    #DEPLIBS =
    #NO-WRAPPERS
    */
In this comment, the four keywords `SRCS`, `LIB`, `DEPLIBS`, and `NO-WRAPPERS` are recognized. You can use `\` at the end of a line to continue it on the next line. Only the `SRCS` keyword is mandatory; you can either omit the others, or precede them by a `#` to comment them out, as with `NO-WRAPPERS` in the example. The `SRCS` is a space delimited list of the source files required to build the compiled functions (and any functions they may call) declared in this startup include file. This list is used in two ways: first, it determines the names of the corresponding object files, and second, any `.f` or `.F` suffixes alert Yorick to load with any special libraries necessary for Fortran. The `LIB` is the name of the library to be built for your package. In the example, the library will be `libfrob.a`. If you do not supply a library name, no library will be built. This doesn't affect your custom Yorick, but if you later want to add more compiled functions, you won't have an easy way to tell Yorick to pick up the functions in this package. The `DEPLIBS` is a space delimited list of system libraries that your package needs. You should not list `m` (the libm math library), `X11`, or any other library which Yorick routinely loads with on your platform. Hopefully your package will not need any dependent libraries, because if it does, it will be much less portable. Not only will those libraries be unavailable on some platforms, but they will almost certainly be in different locations. For now, you will need to edit the Makefile by hand to insert the proper `-L` options to allow the compiler to find these libraries, and you will need to do that separately on every platform where you build your package. I would like to automate this to some extent -- the obvious thing is to put a list of library locations in Y_HOME/lib which can be maintained by a guru at each site -- but for now, you're on your own. The `NO-WRAPPERS` keyword must be present if this startup include file contains no PROTOTYPE comments (yes, I could check for this automatically, but it slows things down needlessly). To summarize, in addition to the DOCUMENT comments, your startup include file should contain PROTOTYPE comments for each compiled function for which you want codger to automatically generate a wrapper, and a single MAKE-INSTRUCTIONS comment. Yorick can build a Makefile automatically. If you don't know what a Makefile is, you can read the UNIX make manpage, but there is a good chance you won't need to know -- read on. To do this, remove any old Makefiles from your directory and type (to the shell):
    yorick -batch make.i my_prog my_start.i
If your package requires several startup include files, list the others after ``my_start.i'`. The name `my_prog` is what your custom version of Yorick will be called. You can load packages you created previously into your new Yorick by giving their names after a `+` on this command line:
    yorick -batch make.i my_prog my_start.i + old_pkg1.i old_pkg2.i
These additional startup include files must be in either ``Y_SITE/startup'` or ``Y_SITE/contrib'`. (Or you can make softlinks to them from the current directory.) The `LIB` libraries mentioned in those startup include files must be in ``Y_HOME/lib'` or ``Y_HOME/lib/contrib'` (or again have softlinks from your current directory). By default, the packages ``matrix.i'` and ``fft.i'` will be included. To omit them, place ``-matrix'` or ``-fft'` The ``make.i'` script produces a Makefile which you can use to build your custom Yorick. You should regard the automatically produced file as a first cut; feel free to edit it by hand to get it right. To do that, you will need to read and understand Yorick's Makefile template, ``Y_HOME/Maketmpl'`. (You can switch to another Makefile template if you need to; the ``Make-cxx'` and ``Make-mpy'` templates are lurking in other parts of the Yorick distribution.) After you've got Makefile the way you want it, type:
    make
To compile and load your new version of Yorick. After you build it, you can move your startup include files into ``Y_SITE/contrib'` or ``Y_SITE/startup'` and your library into ``Y_HOME/lib'` or ``Y_HOME/lib/contrib'` to make them "visible" for other package builders. You don't have to move your startup include files, but if you don't, you will need to keep your new executable in the same directory as they reside -- Yorick looks in ``Y_LAUNCH'` for them as well as in the standard places. When you move your package to a different platform, take the Makefile you created along as a part of it. In your source directory on the new platform, type:
    yorick -batch make.i
    make
This will change the path to the Makefile template to the appropriate location on the new platform. Then make builds your custom version on the new platform. (Yorick versions older than 1.3 used a different system of Makefile templates; this command should convert your old Makefile to the new form. The original will be renamed ``Makefile.old'`. The goal of this system is portability. The basic idea is that all the platform specific problems can be solved once in the Makefile template (or in the several templates), so that you can easily move your packages from one platform to another.