The ARM/Cortex Forth cross-compiler supports calling functions in C or any language that can provide functions that use the AAPCS calling convention. This is an ARM convention documented in IHI0042F_aapcs.pdf.
This chapter documents the Forth side of the Forth to C interface. A separate chapter discusses the C side of the interface.
In order to provide the greatest isolation between sections of code written in other languages, the functions foreign to Forth are accessed by SVC calls and/or jump tables. The simple solution just uses SVC calls for all foreign functions.
All foreign function calls are made using the AAPCS calling convention.
Calls with a variable number of parameters (varargs) are not supported.
The interface is defined for Cortex-M CPUs only. If you need an ARM32 interface, contact MPE.
This work is directly inspired by Robert Sexton's Sockpuppet interface:
https://github.com/rbsexton/sockpuppet
His contribution and permission are gratefully acknowledged.
Access to functions in a library is provided by the SVC( n ) syntax which is similar to a C style function prototype, e.g.
|
where n is the SVC call number. Everything on the source line after the closing ')' is discarded. The example is for the function open() from a file API, and produces a Forth word open.
open \ pathname flags mode -- int
The parser used to separate the tokens is not ideal. If you have problems with a definition, make sure that * tokens are white-space separated. Formal parameter names, e.g. argv above are ignored. Array indicators, [] above, are also ignored when part of the names.
A small number of SVC calls are defined by the system. Regard entries 0..15 as being reserved by the Sockpuppet API.
|
Calling through SVC calls is simple, but has overhead. In addition, lower priority interrupts are disabled by an SVC call and so interrupt latency suffers badly. You cannot do ticker-based timing inside an SVC-based call without messing about with the ARM/Cortex priority mechanisms. The solution is is to call the functions through a jump table as often used by an SVC call mechanism. This requires a little bit more code than the pure SVC approach, but has much better interrupt latency and gives more flexibility.
The example of open() is used again. Access to functions in a library is provided by the JTI( n ) syntax which is similar to a C style function prototype, e.g.
|
where n is the index (0, 1, 2, ...) into the jump table. The parsing rules are as for SVC calls. The definition results in a Forth word open.
open \ pathname flags mode -- int
Before use, you have to tell the cross compiler where the address of the jump table is held, and then use an SVC call find the address of the jump table.
|
Before use, you have to declare the base address of the primary ROM table used for calling ROM functions. For Luminary/TI CPUs, this will probably be:
$0100:0010 setPriTable
Now you can define a set of ROM calls, for example, again for a TI CPU.
|
where
Parameters:
Description:
Writes the corresponding bit values to the output pin(s)
specified by ui8Pins. Writing to a pin configured as an
input pin has no effect. The pin(s) are specified using a
bit-packed byte, where each bit that is set identifies the
pin to be accessed, and where bit 0 of the byte represents
GPIO port pin 0, bit 1 represents GPIO port pin 1, and so
on.
Returns:
None.
To call this function, use the Forth form:
port pins val ROM_GPIOPinWrite
Where the address of the routine is known at the Forth compile time, you can use a direct call.
DIR( addr ) int foo( int a, char *b, char c );
The Forth word marshalls the parameters and calls the subroutine at target address addr.
Very rudimentary support for C comments in declarations is provided, but it is good enough for the vast majority of declarations.
The example below is taken from a SQLite interface.
|
|
The Forth <name> is case-insensitive.
As a standard Forth's string length for dictionary names is only guaranteed up to 31 characters for portable source code, long API names can cause problems.
In the discussion caller refers to the Forth system (below the application layer and callee refers to a a foreign function. The SVC() only supports the AAPCS standard as used by C.
When using calls to floating point libraries, there are three possible calling conventions, noFPAPI, softFPABI) and *\fo{hardFPABI.
For now, the only supported combination by the cross compiler uses FPsystem=2 and FPABI=2, i.e 32 bit VFP floats, a separate Forth float stack, and a hardfloat ABI.
The system generates code to either promote or demote non-CELL sized arguments and return results which can be either signed or unsigned. Although Forth is an un-typed language it must deal with libraries which do have typed calling conventions. In general the use of non-CELL arguments should be avoided but return results should be declared in Forth with the same size as the C or PASCAL convention documented.
The default calling convention for AAPCS is used. The right-most argument/parameter in the C-style prototype is on the top the Forth data stack. When calling an external function the parameters are reordered as required by the AAPCS; this is to enable the argument list to read left to right in Forth source as well as in the C-style operating system documentation.
When using calls to floating point libraries, there are three possible values, 0 for noFPAPI, 1 for\fo{softFPABI) and 2 for hardFPABI. At present, only the hardFPABI is supported. Defines which floating point pack is installed and active, 0 for none, 1 for software floating point, 2 for VFP2 and 3 for VFP3. At present, only the VFP2 option is supported, which is binary compatible with VFP3.
: SVC( \ "text" -- ; )
Declare an external API reference of the form:
SVC( n ) int foo( int a, char *b, char c );
The Forth word marshalls the parameters and calls SVC/SWI n. The Forth word has the same name as the function in the library, but the Forth word name is not case-sensitive. The length of the function's name may not be longer than a Forth word name.
: +ForceTbits \ --
Force function addresses to have the T bit (bit 0) set
by the code that performs the call.
This is the default. If you know that addresses in jump
tables already have this bit set, you can use -ForceTbits
below.
: -ForceTbits \ --
Do not compile the code that forces function addresses to have
the T bit (bit 0) set by the code that performs the call.
You will have to dump a table to find this out for your system.
: holdsJumpTable \ addr(t) --
The base address of the jump table is stored at the given
target address. HoldsJumpTable must be used before
any JTI( definition is made.
|
: JumpTable \ -- addr
Returns the target address that holds the jump table
base address
: JTI( \ "text" -- ; )
Declare an external API reference of the form:
JTI( n ) int foo( int a, char *b, char c );
The Forth word marshalls the parameters and calls entry n (0, 1, 2, ...) in the jump table The Forth word can have the same name as the function in the library, but the Forth word name is not case-sensitive. The length of the function's name may not be longer than a Forth word name.
: DIR( \ "text" -- ; )
Declare an external API reference of the form:
DIR( addr ) int foo( int a, char *b, char c );
The Forth word marshalls the paramters and calls the subroutine at target address addr. The Forth word can have the same name as the function in the library, but the Forth word name is not case-sensitive. The length of the function's name may not be longer than a Forth word name.
: setPriTable \ addr(t) --
Set the base address of the Primary ROM table used for
calling ROM functions. For Luminary/TI CPUs, this will
probably be:
$0100:0010 setPriTable
: DIC( \ "text" -- ; )
Declare an external API reference of the form:
DIC( pri, sec ) int foo( int a, char *b, char c );
The comma betwee pri and sec is optional. This form is used when calling ROM routines in devices from TI, NXP and others. In these the primary table entries hold the address of another table. The secondary index is then used to fetch the address of the actual routine to be called. The Forth word marshalls the parameters and calls the routine. The Forth word can have the same name as the function in the library, but the Forth word name is not case-sensitive. The length of the function's name may not be longer than a Forth word name.
: +DebugExterns \ --
Turn on display of debug information.
: -DebugExterns \ --
Turn off display of debug information.
The types known by the system are all found in the vocabulary TYPES. You can add new ones at will. Each TYPE definition modifies one or more of the following VALUEs. )
argSIZE |
Size in bytes of data type. |
argDEFSIGN |
Default sign of data type if no override is supplied. |
argREQSIGN |
Sign OverRide. This and the previous use 0 = unsigned and 1 = signed. |
argISPOINTER |
1 if type is a pointer, 0 otherwise |
Each TYPES definition can either set these flags directly or can be made up of existing types.
: "C" \ --
Set Calling convention to "C" standard. Arguments are
reversed, and the caller cleans up the stack. This is
the default.
: "PASCAL" \ --
Set the calling convention to the "PASCAL" standard as used
by Pascal compilers. Arguments are not reversed, and the
called routine cleans up the stack.
: L>R \ --
By default, arguments are assumed to be on the Forth stack
with the top item matching the rightmost argument in the
declaration so that the Forth parameter order matches that
in the C-style declaration.
L>R confirms this and is the default.
: R>L \ --
By default, arguments are assumed to be on the Forth stack
with the top item matching the rightmost argument in the
declaration so that the Forth parameter order matches that
in the C-style declaration.
R>L reverses this.
: unsigned \ --
Request current parameter as being unsigned.
: signed \ --
Request current parameter as being signed.
: int \ --
Declare parameter as integer. This is a signed 32 bit quantity
unless preceeded by unsigned.
: char \ --
Declare parameter as character. This is a signed 8 bit quantity
unless preceeded by unsigned.
: void \ --
Declare parameter as void. A VOID parameter has no
size. It is used to declare an empty parameter list, a null
return type or is combined with * to indicate a generic
pointer.
: * \ --
Mark current parameter as a pointer.
: ** \ --
Mark current parameter as a pointer.
: *** \ --
Mark current parameter as a pointer.
: const ; \ --
Marks next item as constant in C terminology. Ignored
by VFX Forth.
: int32 \ --
A 32bit signed quantity.
: int16 \ --
A 16 bit signed quantity.
: int8 \ --
An 8 bit signed quantity.
: uint32 \ --
32bit unsigned quantity.
: uint16 \ --
16bit unsigned quantity.
: uint8 \ --
8bit unsigned quantity.
: LongLong \ --
A 64 bit signed or unsigned integer. At run-time, the argument
is taken from the Forth data stack as a normal Forth double
with the top item on the top of the data stack.
: LONG int ;
A 32 bit signed quantity.
: SHORT \ --
For most compilers a short is a 16 bit signed item,
unless preceded by unsigned.
: BYTE \ --
An 8 bit unsigned quantity.
: float \ --
32 bit float.
: double \ --
64 bit float.
: bool1 \ --
One byte boolean.
: bool4 \ --
Four byte boolean.
: ... \ --
The parameter list is of unknown size. This is an indicator
for a C varargs call. Run-time support for this varies between
CPUs and operating system implementations of VFX Forth. Test, test,
test.