From: eaker@ukulele.crd.ge.com (Chuck Eaker)
Newsgroups: comp.lang.forth
Subject: C assembler
Date: 13 Oct 92 19:15:23 GMT
Organization: GE Corporate Research & Development

In article <1992Oct9.153438.11711@email.tuwien.ac.at>, anton@mips.complang.tuwien.ac.at (Anton Martin Ertl) writes:
|> In article <H.f5CtajwYbto@harvee.billerica.ma.us>, esj@harvee.billerica.ma.us (Eric S Johansson) writes:
|> |> You can bypass the access and maintenance issues by using the time 
|> |> honored techinque of generating C as your high-level assembler code.
|> |> Or if you are feeling more hyper, you could rip open GCC and feed RTL 
|> |> into the code generator.
|> 
|> Right, but the system would not be interactive.

Not necessarily. I recently looked at Mikael R.K.  Patel's
TILE Forth (to see how it handled OO extensions) which is
implemented in C. While browsing through the code I started
to wonder about writing CODE definitions in C. When the
smoke cleared I had added an ASSEMBLER vocabulary which
allowed me to add primitives interactively in two ways:

1. code hello ( -- )
          with Samples   use _f__hello   end-code

2. code hello ( -- )
          " fprintf(\"Hello from C.\\n\");" cc end-code

The first way searches for the "Samples" library and links it
into the executing Forth program's memory space, then finds
the address of the _f__hello symbol in that library and sets
the word being defined, hello, so that the code at that
address is executed when hello is executed.

The second way passes the string to the C compiler which
creates an executable which is then merged into the
executing Forth program's memory space. The executable
contains a symbol whose address is executed when hello
is executed. All of this happens as the text between code
and end-code is interpreted.

I implemented both ways using Sun's shareable library
routines dlopen() and dlsym(). I don't know whether the same
thing can be done in other environments. The dlopen() and
dlsym() functions provide an enormous amount of magic.

In the Sun Unix environment the libraries can be written in
any number of languages, so the cc word could be augmented
with f77 and, if you like, as. Using f77, for example,
complex numbers could be added to Forth quite easily.

I also modified " so that strings may be arbitrarily long,
contain multiple lines, and the " and \ characters can both
be escaped with \.

In the first way, the library is prepared in the
conventional manner: writing and compiling a C source file
which defines a number of parameterless functions returning
VOID. These functions get and return parameters through the
Forth stack which is defined in a header file included in
all compilations of Forth symbols. (Part of the magic of
dlopen() is that it performs run-time linking of the library
to unresolved symbols in the Forth executable such as the
stack data structures.) The first way, of course, is very
fast while interpreting new code definitions.

The second way insulates the user from whatever is outside
the Forth environment, but it has to start up another
process which does all of the dirty work required to compile
C source and produce an executable that is then mapped into
the Forth process's memory space. My hack simply puts the
string of C code into a file then passes the file name to a
shell script which invokes cc and ld. Next, dlopen() opens
the "shared library" that was just produced, and dlsym()
gets the address of the compiled code. This address is
linked to the word being defined. The second way consumes
about 4 seconds per code definition on a SPARCstation IPC.
I'm sure it can be speeded up.  Even so, it is a small price
to pay for this previously unavailable functionality.

It might even be possible to standardize the C "assembler"
by agreeing on a set of C primitives along the lines of
Allen Holub's "C-code" (see Chapter 6 of his
_Compiler_Design_in_C_) and using macros to hide
implementation details about the stacks and dictionary.

As I urged a month or so ago, here is a way to "swallow
whole" the work of others. I'm currently building,
interactively, an interface to Motif. Then, using Forth, I
can put a Forth wrapper around a lot of different libraries
produced by my colleagues that heretofore have been
difficult to experiment with or difficult to combine.

A further refinement that comes to mind is to add the word 
SWALLOW which will look at a C header file and create a
vocabulary which accesses the functions defined in the header
file through words automatically added to that vocabulary.

The advantages of this approach are many including the
maintenance and portability features mentioned by the
original poster. Another is the fact that vendors typically
spend a great deal of effort on supplying C compilers which
generate efficient, optimized code for their platforms.

With the with/use approach, vendors could have a fairly
standard way of distributing Forth libraries in non-source
formats. If we could agree on some data stucture details,
you would only have to link to vocabularies defined in the
libraries which would automatically make the words defined
in them available for use. Actually, dlopen() executes an
initialization routine supplied by a library the first time
dlopen() is called on a library. The library could link to
symbols in the Forth process which tell the library all it
needs to know to organize itself in a way that the Forth it
is linking to can use it. Or the library could provide
information to a function in the Forth process which creates
the organization it needs. The possibilities for expanding
Forth's C horizons are rather interesting.

--
Chuck Eaker / P.O. Box 8, K-1 3C12 / Schenectady, NY 12301 USA
eaker@crd.ge.com        eaker@crdgw1.UUCP       (518) 387-5964