1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
|
Notes on the Foreign Function Interface (ffi) - 22 June 2002
N.B. The Hugs FFI implementation has changed significantly since the
December 2001 release.
Known limitations:
o Only the ccall calling convention is supported. All others are flagged as
errors.
o foreign export is not implemented
o foreign import wrappers are only implemented for the x86, PowerPC
and Sparc architectures and has been most thoroughly tested on
Windows, Linux and using gcc.
It should be easy to port by any experienced assembly language
programmer - especially if they first look at
fptools/ghc/rts/Adjustor.c in the GHC source tree. The following
information is intended for those brave souls who try to port the
implementation to other architectures and can be safely ignored by
everyone else.
To make foreign import wrappers work for other architectures, you
have to modify the function mkThunk in hugs98/src/builtin.c to
generate a short sequence of machine code (and then send your
fix to hugs-bugs@haskell.org for inclusion in the next release).
The goal of the code is (more or less) to implement this C function
rty f(ty1 a1, ... tym am) {
return (*app)(s,a1, ... am);
}
where rty, ty1, ... tym are C types, app is a "apply" function
generated by running "ffihugs +G" and "s" is a "stable pointer" to the
Haskell being wrapped. The reason the function is written in
machine code is:
o For foreign import wrappers the function has to be generated
dynamically and neither ANSI C nor any extensions we know of let
you generate C functions at runtime. The alternative of
invoking the C compiler and loader at runtime is not attractive.
o The code has to be placed next to a data structure in memory.
The data structure has this type:
struct thunk_data {
struct thunk_data* next;
struct thunk_data* prev;
HugsStablePtr stable;
char code[16];
};
The next and prev pointers are used to implement a doubly-linked list
used by the garbage collector to keep track of all wrapped
functions.
The stable pointer stores a stable pointer to the Haskell function being
wrapped. This is used by the garbage collector.
The code field stores the machine code. It is expected that the size
will have to be changed for other architectures.
o By writing in assembly/machine code, it is possible to use the
same code sequence no matter what the function type is. This
works because the C calling convention on most machines has the
stack looking something like this (the stack grows downwards in
this picture)
| ... |
+--------+
| argm |
+--------+
...
+--------+
| arg2 |
+--------+
| arg1 |
+--------+
|ret_addr|
+--------+
This calling convention is more or less imposed by the need to
support vararg functions in C.
To implement the above function, all we need to do is adjust the
stack to look like this:
| ... |
+--------+
| argm |
+--------+
...
+--------+
| arg2 |
+--------+
| arg1 |
+--------+
| s |
+--------+
|ret_addr|
+--------+
and jump to (tailcall) the start of app.
On the x86, you can do this with the following code sequence:
pushl (%esp) ; move the return address "up"
movl s,4(%esp) ; stick the stable pointer "under" it
jmp app ; tail call app
On the Sparc, alignment restrictions require that we add a
doubleword.
On architectures with very different architectures, you can
(hopefully) get things working by passing the stable pointer in a
global variable or, perhaps, a callee-saves register and tweaking
the "app" function (which is generated by implementForeignImportWrapper
in ffi.c) to expect "s" in that variable instead of on the stack.
o It is machine code instead of assembly code because we don't want
to invoke an assembler and linker/loader at runtime.
Having determined which assembly code sequence to use, use
"as -a" (or equivalent) to view the corresponding machine code and
then write C code which will insert that code into the code field
of a thunk.
For the x86, the code looks like this.
#if defined(__i386__)
/* 3 bytes: pushl (%esp) */
*pc++ = 0xff; *pc++ = 0x34; *pc++ = 0x24;
/* 8 bytes: movl s,4(%esp) */
*pc++ = 0xc7; *pc++ = 0x44; *pc++ = 0x24; *pc++ = 0x04;
*((HugsStablePtr*)pc)++ = s;
/* 5 bytes: jmp app */
*pc++ = 0xe9;
*((int*)pc)++ = (char*)app - ((char*)&(thunk->code[16]));
#else
...
#endif
This code contains a copy of the stable pointer because it is
convenient to do this on the x86. On architectures such as the
Sparc where 32-bit immediate loads are more painful, it may be
easier to load the copy of the stable pointer stored in the
thunk - this is stored at a fixed offset from the code.
Likewise, it may be convenient to add a copy of "app" to the
thunk struct.
|