X-Git-Url: http://git.megacz.com/?p=ghc-hetmet.git;a=blobdiff_plain;f=docs%2Fcomm%2Fthe-beast%2Ffexport.html;fp=docs%2Fcomm%2Fthe-beast%2Ffexport.html;h=956043bafb6aa4f490977026a5765a6e6990cc5c;hp=0000000000000000000000000000000000000000;hb=0065d5ab628975892cea1ec7303f968c3338cbe1;hpb=28a464a75e14cece5db40f2765a29348273ff2d2 diff --git a/docs/comm/the-beast/fexport.html b/docs/comm/the-beast/fexport.html new file mode 100644 index 0000000..956043b --- /dev/null +++ b/docs/comm/the-beast/fexport.html @@ -0,0 +1,231 @@ + + +
+ +
+ (1) static export of an IO-typed function from some module MMM
+
+ foreign export foo :: Int -> Int -> IO Int
+
+ For this we generate no Haskell code. However, a C stub is + generated, and it looks like this: +
+
+extern StgClosure* MMM_foo_closure; + +HsInt foo (HsInt a1, HsInt a2) +{ + SchedulerStatus rc; + HaskellObj ret; + rc = rts_evalIO( + rts_apply(rts_apply(MMM_foo_closure,rts_mkInt(a1)), + rts_mkInt(a2) + ), + &ret + ); + rts_checkSchedStatus("foo",rc); + return(rts_getInt(ret)); +} ++
+ This does the obvious thing: builds in the heap the expression
+ (foo a1 a2)
, calls rts_evalIO
to run it,
+ and uses rts_getInt
to fish out the result.
+
+
+ (2) static export of a non-IO-typed function from some module MMM
+
+ foreign export foo :: Int -> Int -> Int
+
+ This is identical to case (1), with the sole difference that the
+ stub calls rts_eval
rather than
+ rts_evalIO
.
+
+
+ (3) dynamic export of an IO-typed function from some module MMM
+
+ foreign export mkCallback :: (Int -> Int -> IO Int) -> IO (FunPtr a)
+
+ Dynamic exports are a whole lot more complicated than their static + counterparts. +
+ First of all, we get some Haskell code, which, when given a
+ function callMe :: (Int -> Int -> IO Int)
to be made
+ C-callable, IO-returns a FunPtr a
, which is the
+ address of the resulting C-callable code. This address can now be
+ handed out to the C-world, and callers to it will get routed
+ through to callMe
.
+
+ The generated Haskell function looks like this: +
+
+mkCallback f + = do sp <- mkStablePtr f + r <- ccall "createAdjustorThunk" sp (&"run_mkCallback") + return r ++
+ createAdjustorThunk
is a gruesome,
+ architecture-specific function in the RTS. It takes a stable
+ pointer to the Haskell function to be run, and the address of the
+ associated C wrapper, and returns a piece of machine code,
+ which, when called from the outside (C) world, eventually calls
+ through to f
.
+
+ This machine code fragment is called the "Adjustor Thunk" (don't
+ ask me why). What it does is simply to call onwards to the C
+ helper
+ function run_mkCallback
, passing all the args given
+ to it but also conveying sp
, which is a stable
+ pointer
+ to the Haskell function to run. So:
+
+
+createAdjustorThunk ( StablePtr sp, CCodeAddress addr_of_helper_C_fn ) +{ + create malloc'd piece of machine code "mc", behaving thusly: + + mc ( args_to_mc ) + { + jump to addr_of_helper_C_fn, passing sp as an additional + argument + } ++
+ This is a horrible hack, because there is no portable way, even at
+ the machine code level, to function which adds one argument and
+ then transfers onwards to another C function. On x86s args are
+ pushed R to L onto the stack, so we can just push sp
,
+ fiddle around with return addresses, and jump onwards to the
+ helper C function. However, on architectures which use register
+ windows and/or pass args extensively in registers (Sparc, Alpha,
+ MIPS, IA64), this scheme borders on the unviable. GHC has a
+ limited createAdjustorThunk
implementation for Sparc
+ and Alpha, which handles only the cases where all args, including
+ the extra one, fit in registers.
+
+ Anyway: the other lump of code generated as a result of a + f-x-dynamic declaration is the C helper stub. This is basically + the same as in the static case, except that it only ever gets + called from the adjustor thunk, and therefore must accept + as an extra argument, a stable pointer to the Haskell function + to run, naturally enough, as this is not known until run-time. + It then dereferences the stable pointer and does the call in + the same way as the f-x-static case: +
+HsInt Main_d1kv ( StgStablePtr the_stableptr, + void* original_return_addr, + HsInt a1, HsInt a2 ) +{ + SchedulerStatus rc; + HaskellObj ret; + rc = rts_evalIO( + rts_apply(rts_apply((StgClosure*)deRefStablePtr(the_stableptr), + rts_mkInt(a1) + ), + rts_mkInt(a2) + ), + &ret + ); + rts_checkSchedStatus("Main_d1kv",rc); + return(rts_getInt(ret)); +} ++
+ Note how this function has a purely made-up name
+ Main_d1kv
, since unlike the f-x-static case, this
+ function is never called from user code, only from the adjustor
+ thunk.
+
+ Note also how the function takes a bogus parameter
+ original_return_addr
, which is part of this extra-arg
+ hack. The usual scheme is to leave the original caller's return
+ address in place and merely push the stable pointer above that,
+ hence the spare parameter.
+
+ Finally, there is some extra trickery, detailed in
+ ghc/rts/Adjustor.c
, to get round the following
+ problem: the adjustor thunk lives in mallocville. It is
+ quite possible that the Haskell code will actually
+ call free()
on the adjustor thunk used to get to it
+ -- because otherwise there is no way to reclaim the space used
+ by the adjustor thunk. That's all very well, but it means that
+ the C helper cannot return to the adjustor thunk in the obvious
+ way, since we've already given it back using free()
.
+ So we leave, on the C stack, the address of whoever called the
+ adjustor thunk, and before calling the helper, mess with the stack
+ such that when the helper returns, it returns directly to the
+ adjustor thunk's caller.
+
+ That's how the stdcall
convention works. If the
+ adjustor thunk has been called using the ccall
+ convention, we return indirectly, via a statically-allocated
+ yet-another-magic-piece-of-code, which takes care of removing the
+ extra argument that the adjustor thunk pushed onto the stack.
+ This is needed because in ccall
-world, it is the
+ caller who removes args after the call, and the original caller of
+ the adjustor thunk has no way to know about the extra arg pushed
+ by the adjustor thunk.
+
+ You didn't really want to know all this stuff, did you? +
+
+
+
+ (4) dynamic export of an non-IO-typed function from some module MMM
+
+ foreign export mkCallback :: (Int -> Int -> Int) -> IO (FunPtr a)
+
+ (4) relates to (3) as (2) relates to (1), that is, it's identical,
+ except the C stub uses rts_eval
instead of
+ rts_evalIO
.
+
+ + +
+ Unfortunately there is no obvious candidate for the invisible + side-channel. We've chosen to pass it on the stack, with the + bad consequences detailed above. Another possibility would be to + park it in a global variable, but this is non-reentrant and + non-(OS-)thread-safe. A third idea is to put it into a callee-saves + register, but that has problems too: the C helper may not use that + register and therefore we will have trashed any value placed there + by the caller; and there is no C-level portable way to read from + the register inside the C helper. +
+ In short, we can't think of a really satisfactory solution. I'd + vote for introducing some kind of OS-thread-local-state and passing + it in there, but that introduces complications of its own. +
+ OS-thread-safety is of concern in the C stubs, whilst
+ building up the expressions to run. These need to have exclusive
+ access to the heap whilst allocating in it. Also, there needs to
+ be some guarantee that no GC will happen in between the
+ deRefStablePtr
call and when rts_eval[IO]
+ starts running. At the moment there are no guarantees for
+ either property. This needs to be sorted out before the
+ implementation can be regarded as fully safe to use.
+
+
+ + +Last modified: Weds 27 Feb 02 + + + +