Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Calling C functions from Forth

346 views
Skip to first unread message

Helmut Giese

unread,
Jan 6, 2010, 6:00:33 AM1/6/10
to
Hello out there,
after a loooong absence from Forth (approx. 30 years) I am having an
interesting time getting used to it again.
I am using pForth (a "C Forth") - running on Windows for ease of
experimenting and porting it to a small ARM processor (the ultimately
planned target). I don't know yet whether pForth will be the final
choice - still too many unknowns.

Anyway, I feel the urge to implement some kind of "time measurement"
(the main interest being benchmarking). On the Windows version I would
call a function giving me 'number of msec since boot time' (forgot its
name right now), and on the ARM some equivalent (self made) function.

My question now: Given that this function will be integrated in the
core - that is: its name will be known at link time - what is an
idiomatic way to integrate such a C function?

Any advice will be greately appreciated.
Best regards
Helmut Giese

Anton Ertl

unread,
Jan 6, 2010, 10:41:09 AM1/6/10
to
Helmut Giese <hgi...@ratiosoft.com> writes:
>My question now: Given that this function will be integrated in the
>core - that is: its name will be known at link time - what is an
>idiomatic way to integrate such a C function?

If I understand you correctly, you seem to have solved the foreign
function call problem already. Then you just to provide a word for
this functionality and give it a name. There is no common name for
that, however. Gforth has UTIME ( -- d ) for real time (in
microseconds) and CPUTIME ( -- duser dsystem ) for CPU time (in
microseconds)
<http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Keeping-track-of-Time.html>;
Some people use the name MS@.

If you are still thinking about how to do the foreign function call,
there is no standard, either. A foreign function interface designed
for portability is Gforth's libcc interface
<http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/C-Interface.html>.
Another interface that has been implemented in several Forth systems
for IA-32 is the extern: interface
<http://soton.mpeforth.com/flag/extern/index.html>, but I am not
convinced that it will prove to support portable Forth programs
(moving between IA-32 and ARM might be relatively easy, given that the
C type sizes typically are the same in these two architectures).

- anton
--
M. Anton Ertl http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
New standard: http://www.forth200x.org/forth200x.html
EuroForth 2009: http://www.euroforth.org/ef09/

John Passaniti

unread,
Jan 6, 2010, 1:42:27 PM1/6/10
to
On Jan 6, 6:00 am, Helmut Giese <hgi...@ratiosoft.com> wrote:
> Hello out there,
> after a loooong absence from Forth (approx. 30 years) I am having an
> interesting time getting used to it again.
> I am using pForth (a "C Forth") - running on Windows for ease of
> experimenting and porting it to a small ARM processor (the ultimately
> planned target). I don't know yet whether pForth will be the final
> choice - still too many unknowns.

pForth is one of the better C-based Forths out there for bare-metal
embedded systems work because it doesn't make any calls to the C
standard library (unless you want it to). I used it a couple times
(once on an ARM, once on a ColdFire) and found it very easy to work
with.

> My question now: Given that this function will be integrated in the
> core - that is: its name will be known at link time - what is an
> idiomatic way to integrate such a C function?

Although I can't give you the code I used (it was a closed-source
project for a client), I can tell you the basic approach I took to
this problem. It's not that difficult:

In the system I worked on, Forth had two primary functions: helping to
bring-up/test the hardware, and providing the means to do interactive
testing of C functions. The goal was to expose every C function
automatically and not have to write any wrappers to bridge between
Forth and C. I didn't quite meet that goal, but I got 95% of the way
there. The first thing I did was to ignore most type information
since on the platforms I was using, the C compiler did the usual thing
of promoting things to int (this isn't always the case, as in many C
compilers for 8 and 16-bit targets). And on the platforms I used,
sizeof(int) == sizeof(long) == sizeof(void*). Also "long long" wasn't
needed or supported. So types really didn't matter.

That meant that in order to call a C function, I really only needed to
know three things:

1. The address of the function.
2. The number of parameters it took.
3. If it had a return value.

The C compiler I used was GCC. GCC can output a variety of debugging
information in different forms. The form I chose was STABS, because
it is relatively easy to parse from assembly-language output of the
compiler. Using a script, I ripped through the .stabs directives,
extracting the function names, parameter value count, and return value
count. Then, using the symbol table after the linker ran, I parsed
that to extract the address of the functions.

So for example, say I had these C functions:

long square(int x) { return x*x; }
void doit(void) { }
int strncmp (const char* str1, const char* str2, size_t num)
{ ... }

The output of my scripts would be a text file that looked like this:

base @ hex
1 1 00001234 c: square
0 0 00005678 c: doit
1 3 00001357 c: strncmp
base !

This text is Forth source. During early development, I would append
this text file to the binary where a startup function would attempt to
find and interpret it. Later on, non-development builds wouldn't
include this text file (to save space). Instead, I would blast the
text file to the Forth interpreter serially. But regardless of how it
was compiled, the result was that you would end up with a wordlist
that had the names of C functions in it. And you could then call
those functions in a natural way:

3 square .

The run-time code built by the c: word was responsible for setting up
the stack frame to call the C function. Given the number of
parameters, it would pop off that many parameters from the Forth
stack, create the frame for C, and call the function. On return, if
there was a return value, it would push it to the Forth stack.

Obviously, this approach doesn't address things like the differences
between Forth strings and normal C strings, doesn't protect the
programmer from type errors (like passing a long value to a function
that expects a char), doesn't deal with structures, would horribly
fail on functions that were passed (or returned) structures by value,
and overall doesn't try to make the bridge between Forth and C to be
seamless. But as I didn't care about any of that, I didn't waste time
coming up with the perfect bridge.

Mat

unread,
Jan 7, 2010, 8:05:19 AM1/7/10
to
hello,

sorry if this isn't direct related but have someone experiences with
hForth for the same problem ?

thanks,
Mat.

Helmut Giese

unread,
Jan 7, 2010, 9:07:33 AM1/7/10
to
Hi Anton,

>Helmut Giese <hgi...@ratiosoft.com> writes:
>>My question now: Given that this function will be integrated in the
>>core - that is: its name will be known at link time - what is an
>>idiomatic way to integrate such a C function?
>
>If I understand you correctly, you seem to have solved the foreign
>function call problem already.
uh, my bad, I conveyed the wrong impression ...

>If you are still thinking about how to do the foreign function call,
>there is no standard, either. A foreign function interface designed

... but fortunately you still carried on.

>for portability is Gforth's libcc interface
><http://www.complang.tuwien.ac.at/forth/gforth/Docs-html/C-Interface.html>.
>Another interface that has been implemented in several Forth systems
>for IA-32 is the extern: interface
><http://soton.mpeforth.com/flag/extern/index.html>, but I am not
>convinced that it will prove to support portable Forth programs
>(moving between IA-32 and ARM might be relatively easy, given that the
>C type sizes typically are the same in these two architectures).

Thanks for the links, I put them aside for further references.

Reading them helped me in that I decided that I currently don't need
the future-proof, all-emcompassing solution. For the moment something
(anything actually) which works is sufficient. So I'll cobble up
something and carry on.

Thanks again and best regards
Helmut Giese

Helmut Giese

unread,
Jan 7, 2010, 9:51:06 AM1/7/10
to
Hi John,

>pForth is one of the better C-based Forths out there for bare-metal
>embedded systems work because it doesn't make any calls to the C
>standard library (unless you want it to). I used it a couple times
>(once on an ARM, once on a ColdFire) and found it very easy to work
>with.
yes, I can confirm this: It was rather painless to get the system up
and running.

As an anecdote (maybe useful for future readers of this thread): I got
ambitious too early - I tried to make pForth work with 2 dictionaries:
- one would go into ROM, holding whatever I considered the basics
- while the second would take up whatever I would want to do
interactively or load at run-time.
The reason is of course that I have a huge (relative to my needs)
amount of ROM available, but RAM is scarce.

Well, it didn't work out - in fact, it cannot be done without a major
rewrite of pForth's internals (which I am not inclined to do - loads
of C macros which do all of the address juggling) because it uses the
respective address fields to store _offsets_ relative to the start of
the respective section.

Now offsets don't go well with multiple dictionaries: You don't know
which dictionary to use to apply them. Oh well, ...

[snip an interesting strategy to mix C and Forth]

This sounds interesting (and I tugged it away), but is more than I
currently need: The (handful) of C functions I will need are
integrated in the core - that is their addresses are known at link
time. So I will have something like
---
unsigned int msecTime;
#ifdef WIN32
msecTime = GetTickCount();
#else
// ARM version: read internal timer and convert to msec
msecTime = ...
#endif
// transfer msecTime to top of data stack

---
and the linker will resolve the addresses.

But the technique you described will come in handy if I want to add C
code later on - don't know yet.

>But as I didn't care about any of that, I didn't waste time
>coming up with the perfect bridge.

... and rightly so. I tend to sometimes strive for more than I
actually need ...

Thanks for your advice and best regards
Helmut Giese

0 new messages