summaryrefslogtreecommitdiff
path: root/ghc/rts/ForeignCall.c
diff options
context:
space:
mode:
Diffstat (limited to 'ghc/rts/ForeignCall.c')
-rw-r--r--ghc/rts/ForeignCall.c363
1 files changed, 313 insertions, 50 deletions
diff --git a/ghc/rts/ForeignCall.c b/ghc/rts/ForeignCall.c
index 637cd1b447..5b1e64ff46 100644
--- a/ghc/rts/ForeignCall.c
+++ b/ghc/rts/ForeignCall.c
@@ -1,25 +1,75 @@
/* -----------------------------------------------------------------------------
- * $Id: ForeignCall.c,v 1.8 1999/10/22 09:59:34 sewardj Exp $
+ * $Id: ForeignCall.c,v 1.9 1999/10/22 15:58:21 sewardj Exp $
*
* (c) The GHC Team 1994-1999.
*
- * Foreign Function calls
- *
+ * Implementation of foreign import and foreign export.
* ---------------------------------------------------------------------------*/
#include "Rts.h"
#ifdef INTERPRETER
-#include "Assembler.h" /* for CFun stuff */
+#include "RtsUtils.h" /* barf :-) */
+#include "Assembler.h" /* for CFun stuff */
#include "Evaluator.h"
#include "ForeignCall.h"
+/* Exports of this file:
+ mkDescriptor
+ ccall
+ createAdjThunk
+ Everything else is local, I think.
+*/
+
+/* ----------------------------------------------------------------------
+ * Some misc-ery to begin with.
+ * --------------------------------------------------------------------*/
+
+CFunDescriptor* mkDescriptor( char* as, char* rs )
+{
+ /* ToDo: don't use malloc */
+ CFunDescriptor *d = malloc(sizeof(CFunDescriptor));
+ if (d == NULL) return d;
+ d->arg_tys = as;
+ d->result_tys = rs;
+ d->num_args = strlen(as);
+ d->num_results = strlen(rs);
+ return d;
+}
+
+
+/* ----------------------------------------------------------------------
+ * Part the first: CALLING OUT -- foreign import
+ * --------------------------------------------------------------------*/
+
+/* SOME NOTES ABOUT PARAMETERISATION.
+
+ These pertain equally to foreign import and foreign export.
+
+ Implementations for calling in and out are very architecture
+ dependent. After some consideration, it appears that the two
+ important factors are the instruction set, and the calling
+ convention used. Factors like the OS and compiler are not
+ directly relevant.
+
+ So: routines which are architecture dependent are have
+ _instructionsetname_callingconventionname attached to the
+ the base name. For example, code specific to the ccall
+ convention on x86 would be suffixed _x86_ccall.
+
+ A third possible dimension of parameterisation relates to the
+ split between callee and caller saves registers. For example,
+ x86_ccall code needs to assume a split, and different splits
+ using ccall on x86 need different code. However, that does not
+ yet seem an issue, so it is ignored here.
+*/
+
-/* --------------------------------------------------------------------------
+/* ------------------------------------------------------------------
* Calling out to C: a simple, universal calling API
- * ------------------------------------------------------------------------*/
+ * ----------------------------------------------------------------*/
/* The universal call-C API supplies a single function:
@@ -93,41 +143,29 @@
have to be handwritten assembly. The above design is intended
to make that assembly as simple as possible, at the expense
of a small amount of complication for the API's user.
-*/
-/* ToDo: move these to the Right Place */
-extern StgInt PopTaggedInt ( void ) ;
-extern StgWord PopTaggedWord ( void ) ;
-extern StgAddr PopTaggedAddr ( void ) ;
-extern StgStablePtr PopTaggedStablePtr ( void ) ;
-extern StgChar PopTaggedChar ( void ) ;
-extern StgFloat PopTaggedFloat ( void ) ;
-extern StgDouble PopTaggedDouble ( void ) ;
+ These architecture-dependent assembly routines are in
+ rts/universal_call_c.S.
+*/
-extern void PushTaggedInt ( StgInt );
-extern void PushTaggedWord ( StgWord );
-extern void PushTaggedAddr ( StgAddr );
-extern void PushTaggedStablePtr ( StgStablePtr );
-extern void PushTaggedChar ( StgChar );
-extern void PushTaggedFloat ( StgFloat );
-extern void PushTaggedDouble ( StgDouble );
-extern void PushPtr ( StgPtr );
-extern StgPtr PopPtr ( void );
+/* ----------------------------------------------------------------*
+ * External refs for the assembly routines.
+ * ----------------------------------------------------------------*/
+extern void universal_call_c_x86_ccall ( int, void*, char*, void* );
+static void universal_call_c_generic ( int, void*, char*, void* );
-extern void universal_call_c_x86_linux ( int, void*, char*, void* );
- void universal_call_c_generic ( int, void*, char*, void* );
-/* --------------------------------------------------------------------------
+/* ----------------------------------------------------------------*
* This is a generic version of universal call that
* only works for specific argument patterns.
*
- * It allows ports to work on the Hugs Prelude immeduately,
- * even if univeral_call_c_<os/specific> is not ported.
- * ------------------------------------------------------------------------*/
+ * It allows ports to work on the Hugs Prelude immediately,
+ * even if univeral_call_c_arch_callingconvention is not available.
+ * ----------------------------------------------------------------*/
-void universal_call_c_generic
+static void universal_call_c_generic
( int n_args,
void* args,
char* argstr,
@@ -165,10 +203,13 @@ void universal_call_c_generic
printf("' [%d arg(s)]\n",n_args);
assert(0);
}
+#undef CALL
#undef CMP
+#undef ARG
}
-/* --------------------------------------------------------------------------
+
+/* ----------------------------------------------------------------*
* Move args/results between STG stack and the above API's arg block
* Returns 0 on success
* 1 if too many args/results or non-handled type
@@ -177,7 +218,8 @@ void universal_call_c_generic
* Assumes an LP64 programming model for 64 bit:
* sizeof(long)==sizeof(void*)==64 on a 64 bit platform
* sizeof(int)==32 on a 64 bit platform
- * ------------------------------------------------------------------------*/
+ * This code attempts to be architecture neutral (viz, generic).
+ * ----------------------------------------------------------------*/
int ccall ( CFunDescriptor* d, void (*fun)(void), StgBCO** bco )
{
@@ -195,9 +237,6 @@ int ccall ( CFunDescriptor* d, void (*fun)(void), StgBCO** bco )
if (d->num_args > 30 || d->num_results > 1)
return 1; /* unlikely, but ... */
- //fprintf ( stderr, "ccall: `%s' %d -> `%s' %d\n",
- // d-> arg_tys, d->num_args, d->result_tys, d->num_results );
-
p = (unsigned int*) &arg_vec[1];
for (i = 0; i < d->num_args; i++) {
switch (d->arg_tys[i]) {
@@ -267,11 +306,8 @@ int ccall ( CFunDescriptor* d, void (*fun)(void), StgBCO** bco )
PushPtr((StgPtr)(*bco));
SaveThreadState();
- //fprintf(stderr, " argc=%d arg_vec=%p argd_vec=%p `%s' fun=%p\n",
- // d->num_args, arg_vec, argd_vec, argd_vec, fun );
-
#if 1
- universal_call_c_x86_linux (
+ universal_call_c_x86_ccall (
d->num_args, (void*)arg_vec, argd_vec, fun );
#else
universal_call_c_generic (
@@ -320,17 +356,244 @@ int ccall ( CFunDescriptor* d, void (*fun)(void), StgBCO** bco )
-CFunDescriptor* mkDescriptor( char* as, char* rs )
-{
- /* ToDo: don't use malloc */
- CFunDescriptor *d = malloc(sizeof(CFunDescriptor));
- if (d == NULL) return d;
- d->arg_tys = as;
- d->result_tys = rs;
- d->num_args = strlen(as);
- d->num_results = strlen(rs);
- return d;
+/* ----------------------------------------------------------------------
+ * Part the second: CALLING IN -- foreign export {dynamic}
+ * --------------------------------------------------------------------*/
+
+/* Make it possible for the evaluator to get hold of bytecode
+ for a given function by name. Useful but a hack. Sigh.
+ */
+extern void* getHugs_AsmObject_for ( char* s );
+
+
+/* ----------------------------------------------------------------*
+ * The implementation for x86_ccall.
+ * ----------------------------------------------------------------*/
+
+static
+HaskellObj
+unpackArgsAndCallHaskell_x86_ccall_wrk ( StgStablePtr stableptr,
+ char* tydesc, char* args)
+{
+ /* Copy args out of the C stack frame in an architecture
+ dependent fashion, under the direction of the type description
+ string tydesc. Dereference the stable pointer, giving the
+ Haskell function to call. Build an application of this to
+ the arguments, and finally wrap primRunST round the whole
+ thing, since we know it returns an IO type. Then evaluate
+ the whole, which leaves nodeOut as the evaluated 'a', where
+ the type of the function called is .... -> IO a.
+
+ We can't immediately unpack the results and return, since
+ int results need to return in a different register (%eax and
+ possibly %edx) from float things (%st(0)). So return nodeOut
+ to the relevant wrapper function, which knows enough about
+ the return type to do the Right Thing.
+
+ There's no getting round it: this is most heinous hack.
+ */
+
+ HaskellObj node;
+ HaskellObj nodeOut;
+ SchedulerStatus sstat;
+
+ char* resp = tydesc;
+ char* argp = tydesc;
+
+ node = (HaskellObj)deRefStablePtr(stableptr);
+
+ if (*argp != ':') argp++;
+ ASSERT( *argp == ':' );
+ argp++;
+ while (*argp) {
+ switch (*argp) {
+ case CHAR_REP:
+ node = rts_apply ( node, rts_mkChar ( *(char*)args ) );
+ args += 4;
+ break;
+ case INT_REP:
+ node = rts_apply ( node, rts_mkInt ( *(int*)args ) );
+ args += 4;
+ break;
+ case WORD_REP:
+ node = rts_apply ( node, rts_mkWord ( *(unsigned int*)args ) );
+ args += 4;
+ break;
+ case ADDR_REP:
+ node = rts_apply ( node, rts_mkAddr ( *(void**)args ) );
+ args += 4;
+ break;
+ case STABLE_REP:
+ node = rts_apply ( node, rts_mkStablePtr ( *(int*)args ) );
+ args += 4;
+ break;
+ case FLOAT_REP:
+ node = rts_apply ( node, rts_mkFloat ( *(float*)args ) );
+ args += 4;
+ break;
+ case DOUBLE_REP:
+ node = rts_apply ( node, rts_mkDouble ( *(double*)args ) );
+ args += 8;
+ break;
+ default:
+ barf(
+ "unpackArgsAndCallHaskell_x86_ccall: unexpected arg type rep");
+ }
+ argp++;
+ }
+
+ node = rts_apply (
+ asmClosureOfObject(getHugs_AsmObject_for("primRunST")),
+ node );
+
+ sstat = rts_eval ( node, &nodeOut );
+ if (sstat != Success)
+ barf ("unpackArgsAndCallHaskell_x86_ccall: eval failed");
+
+ return nodeOut;
+}
+
+
+static
+double
+unpackArgsAndCallHaskell_x86_ccall_DOUBLE ( StgStablePtr stableptr,
+ char* tydesc, char* args)
+{
+ HaskellObj nodeOut
+ = unpackArgsAndCallHaskell_x86_ccall_wrk ( stableptr, tydesc, args );
+ /* Return a double. This return will go into %st(0), which
+ is unmodified by the adjustor thunk.
+ */
+ ASSERT(tydesc[0] == DOUBLE_REP);
+ return rts_getDouble(nodeOut);
+}
+
+
+static
+float
+unpackArgsAndCallHaskell_x86_ccall_FLOAT ( StgStablePtr stableptr,
+ char* tydesc, char* args)
+{
+ HaskellObj nodeOut
+ = unpackArgsAndCallHaskell_x86_ccall_wrk ( stableptr, tydesc, args );
+ /* Probably could be merged with the double case, since %st(0) is
+ still the return register.
+ */
+ ASSERT(tydesc[0] == FLOAT_REP);
+ return rts_getFloat(nodeOut);
+}
+
+
+static
+unsigned long
+unpackArgsAndCallHaskell_x86_ccall_INTISH ( StgStablePtr stableptr,
+ char* tydesc, char* args)
+{
+ HaskellObj nodeOut
+ = unpackArgsAndCallHaskell_x86_ccall_wrk ( stableptr, tydesc, args );
+ /* A complete hack. We know that all these returns will be
+ put into %eax (and %edx, if it is a 64-bit return), and
+ the adjustor thunk will then itself return to the original
+ (C-world) caller without modifying %eax or %edx, so the
+ original caller will be a Happy Bunny.
+ */
+ switch (*tydesc) {
+ case ':': return 0;
+ case CHAR_REP: return (unsigned long)rts_getChar(nodeOut);
+ case INT_REP: return (unsigned long)rts_getInt(nodeOut);
+ case WORD_REP: return (unsigned long)rts_getWord(nodeOut);
+ case ADDR_REP: return (unsigned long)rts_getAddr(nodeOut);
+ case STABLE_REP: return (unsigned long)rts_getStablePtr(nodeOut);
+ default:
+ barf(
+ "unpackArgsAndCallHaskell_x86_ccall: unexpected res type rep");
+ }
+}
+
+
+static
+StgAddr createAdjThunk_x86_ccall ( StgStablePtr stableptr,
+ StgAddr typestr )
+{
+ unsigned char* codeblock;
+ unsigned char* cp;
+ unsigned int ts = (unsigned int)typestr;
+ unsigned int sp = (unsigned int)stableptr;
+ unsigned int ch;
+
+ if (((char*)typestr)[0] == DOUBLE_REP)
+ ch = (unsigned int)&unpackArgsAndCallHaskell_x86_ccall_DOUBLE;
+ else if (((char*)typestr)[0] == FLOAT_REP)
+ ch = (unsigned int)&unpackArgsAndCallHaskell_x86_ccall_FLOAT;
+ else
+ ch = (unsigned int)&unpackArgsAndCallHaskell_x86_ccall_INTISH;
+
+ codeblock = malloc ( 1 + 0x22 );
+ if (!codeblock) {
+ fprintf ( stderr,
+ "createAdjThunk_x86_ccall (foreign export dynamic):\n"
+ "\tfatal: can't alloc mem\n" );
+ exit(1);
+ }
+ cp = codeblock;
+ /* Generate the following:
+ 0000 53 pushl %ebx
+ 0001 51 pushl %ecx
+ 0002 56 pushl %esi
+ 0003 57 pushl %edi
+ 0004 55 pushl %ebp
+ 0005 89E0 movl %esp,%eax # sp -> eax
+ 0007 83C018 addl $24,%eax # move eax back over 5 saved regs + retaddr
+ 000a 50 pushl %eax # push arg-block addr
+ 000b 6844332211 pushl $0x11223344 # push addr of type descr string
+ 0010 6877665544 pushl $0x44556677 # push stableptr to closure
+ 0015 E8BBAA9988 call 0x8899aabb # SEE COMMENT BELOW
+ 001a 83C40C addl $12,%esp # pop 3 args
+ 001d 5D popl %ebp
+ 001e 5F popl %edi
+ 001f 5E popl %esi
+ 0020 59 popl %ecx
+ 0021 5B popl %ebx
+ 0022 C3 ret
+ */
+ *cp++ = 0x53;
+ *cp++ = 0x51;
+ *cp++ = 0x56;
+ *cp++ = 0x57;
+ *cp++ = 0x55;
+ *cp++ = 0x89; *cp++ = 0xE0;
+ *cp++ = 0x83; *cp++ = 0xC0; *cp++ = 0x18;
+ *cp++ = 0x50;
+ *cp++ = 0x68; *cp++=ts;ts>>=8; *cp++=ts;ts>>=8; *cp++=ts;ts>>=8; *cp++=ts;
+ *cp++ = 0x68; *cp++=sp;sp>>=8; *cp++=sp;sp>>=8; *cp++=sp;sp>>=8; *cp++=sp;
+
+ /* call address needs to be: displacement relative to next insn */
+ ch = ch - ( ((unsigned int)cp) + 5);
+ *cp++ = 0xE8; *cp++=ch;ch>>=8; *cp++=ch;ch>>=8; *cp++=ch;ch>>=8; *cp++=ch;
+
+ *cp++ = 0x83; *cp++ = 0xC4; *cp++ = 0x0C;
+ *cp++ = 0x5D;
+ *cp++ = 0x5F;
+ *cp++ = 0x5E;
+ *cp++ = 0x59;
+ *cp++ = 0x5B;
+ *cp++ = 0xC3;
+
+ return codeblock;
+}
+
+
+/* ----------------------------------------------------------------*
+ * The only function involved in foreign-export that needs to be
+ * visible outside this file.
+ * ----------------------------------------------------------------*/
+
+StgAddr createAdjThunk ( StgStablePtr stableptr,
+ StgAddr typestr )
+{
+ return createAdjThunk_x86_ccall ( stableptr, typestr );
}
#endif /* INTERPRETER */
+