Developing on the NanoNote in Forth

From Qi-Hardware
Jump to: navigation, search

Recent versions of the Ben Nanonote firmware include a very new version of Gforth.

[edit] Calling Operating System Functions

Gforth nowadays has a facility for calling code from any of the shared libraries installed in the system. This is called a foreign function interface (FFI). The lower level parts of that facility consist of the words open-lib and lib-sym which work correctly on the NanoNote. Unfortunately the higher level function-call implementation of the FFI rely on GCC and libtool to be present on the NanoNote and are thus not (fully) functional.

The good news is that the versions of Gforth available for NanoNote include the latest ABI-CODE support for writing definitions in assembly language, using Gforth's built-in MIPS RPN assembler. Using a simple ABI-CODE assembler definition we can create a word to call into shared library functions.

abi-code oabi32-call  ( i*x x1 x2 x3 x4 a-addr -- i*x x5 x6 )
   $sp   $sp  -12  addiu,  \ push $4, $ra, $s0
   $4   0 $sp  sw,
   $ra   4 $sp  sw,
   $s0   8 $sp  sw,
   $s0   $sp   $zero  addu,  \ save C stack pointer to $s0
   $sp   $4   4  addiu,     \ make C stack pointer point to x4
   ( mips oabi32 stack frame allocates space for register args, too!)
   $t9   0 $4  lw,       \ load function pointer. Must be t9 for PIC to work!
   $7   4 $4  lw,        \ load argument registers
   $6   8 $4  lw,
   $5   12 $4  lw,
   $ra   $t9  jalr,       \ jump to a-addr
   $4   16 $4  lw,        \   (last arg pulled into delay slot)
   $sp   $s0  $zero  addu,   \ restore C stack pointer form $s0
   $4   0 $sp  lw,           \ pop $4, $ra, $s0
   $ra   4 $sp  lw,
   $s0   8 $sp  lw,
   $sp   $sp  12  addiu,  
   $v0   16 $4 sw,      \ push result registers to Forth stack
   $v1   12 $4 sw,      \   SP update delayed
   $ra jr,             \ return
   $v0 $4 12 addiu,    \   (delay slot): return Forth stack pointer

Together with open-lib and lib-sym, oabi32-call gives us a fully-functional FFI. For functions of up to 4 arguments you have to pass exactly 4 arguments which are then copied into the argument-passing registers. If you need less arguments, just provide dummy values for the unused registers. Additional arguments can be consumed from the Forth stack. However, those (and only those) are read in reverse order. The 4 argument register values are consumed, and you get exactly two words of result data independent from the actual number of results produced by the routine. Just drop what you don't require. A few examples:

s"" open-lib constant libc
s" sleep" libc lib-sym constant 'sleep
s" printf" libc lib-sym constant 'printf
\ simple low-level call test: call sleep(2)
2 0 0 0 'sleep oabi32-call  2drop

\ more complex test: call  printf("hello world %i %i %i %i %i", 11,22,33,44,55)
: test  \ test routine 
   55 44  \ last 2 arguments passed via stack
   s\" hello world %i %i %i %i %i\0" drop    \ 1st argument passed via reg
   11 22 33     \ other 3 arguments passed via reg
   'printf oabi32-call                               \ call printf(..)
   drop    \ discard high 32bits of return value
   ."   printed " . ." chars"  \ output actual 32 bit return value
   2drop   \ discard stack arguments 55, 44

For further information on open-lib, abi-code and Gforth's MIPS RPN assembler consult this recent copy of the Gforth manual.

Personal tools