FFI, safe vs unsafe
wolfgang.thaller at gmx.net
Tue Apr 4 00:03:56 EDT 2006
John Meacham wrote (... but I've reordered things):
> My only real 'must-have' is that the 4 modes all can be explicitly and
> unambiguously specified. I have opinions on the syntax/hints but
> that is
> more flexable.
I basically agree (the syntax discussion will take place in the years
after the semantics discussion), but...
I want programmers to have a way of saying "this function might spend
a lot of time in foreign lands". These calls should be concurrent on
all implementations that support it (because some separately
developed/maintained piece of Haskell code might expect to run a
computation in the background), but if there are implementations that
don't support it shouldn't flag an error, because that would
encourage library writers to specify nonconcurrent when they can't
prove that it's safe, or make code needlessly nonportable.
Another way to look at it: You cannot decide whether the call
actually has to be done concurrently by just looking at the call site
- you'd need to look at the entire program, and asking people
(especially library writers) to state and guarantee global properties
of a program that might not even be finished yet is a Bad Thing.
Therefore, the concurrency annotation on the foreign import can only
be a hint on whether the foreign function is guaranteed to return
quickly or not; the actual requirement for the call to be
"concurrent" is hidden in the other code that expects to run at the
same time. Therefore, it would be wrong for an implementation that
doesn't support concurrent calls (reentrant or nonreentrant, I don't
care) to flag an error; the foreign import declaration correctly
refuses to give a guarantee that the function will return quickly.
The error is in the code that expects to run concurrently with a
foreign import on an implementation that doesn't support that (but of
course, a compiler can't detect such an error).
> Another nice minor thing would be if haskell implementations were
> required to ignore annotations starting with 'x-' for implementation
> specific hints.
Sounds good. Syntax discussion postponed again ('x-' looks so mime-
typish. Could we add a meaningless 'application/' to the front? Just
> In my survey of when 'reentrant concurrent' was needed, I looked at
> the standard libraries and didn't find anywhere it was actually
> Are there some compelling examples of when it is really needed in a
> setting that doesn't have OS threads to begin with? (I am not
> they don't exist, I just want to see some example uses of this feature
> to get a better handle on the implementation cost)
In my experience, reentrant calls are rare in non-GUI code, but they
become quite common in GUI code (OK, in some GUI libraries, there is
only one, called something like RunMainEventLoop, but then it runs
almost all of the time and is absolutely crucial). And with most GUI
libraries, the GUI's main event loop will refuse to cooperate well
with a Haskell's implementation's scheduler, so it will need to be
called as a "concurrent" foreign import if your application is to do
any background processing while waiting for events.
Other libraries that rely on callbacks would include the GLU
Tesselator that I already mentioned, as well as several packages for
solving optimisation problems. For those, concurrency would probably
only become an issue when they are used with a GUI (even if it's only
to display a progress bar).
Another reason why you don't see them in Haskell standard library
code might be that everyone prefers Data.List.sort to foreign import
> Any particular reason hugs and GHC didn't use the state-threads
> out of curiosity? did it conflict with the push-enter model? (jhc
> the eval-apply model so I am more familier with that)
It was before my time. I guess it's because GHC uses a separate heap-
allocated Haskell thread, so it made sense not to bother to allocate
a separate C stack for every one of them. Don't know about Hugs.
> It also implys that a function call will run on the same OS thread as
> the OS thread the current haskell thread is running on.
This shouldn't be put into a standard, as the bound threads proposal
already gives a different guarantee about that, and both guarantees
taken together probably guarantee too much - taken together, they
probably mean every Haskell thread has to be an OS thread. It might
be an implementation-specific guarantee, unless the bound threads
become a part of the standard in their entirety.
> 'OS thread the current haskell
> thread is running on' (GHC already doesn't when bound threads arn't
> I am led to believe?)
There should be no such thing as the 'OS thread the current haskell
thread is running on' in any standard; OS thread identity is only
observed through the FFI.
> this means that things like 'log' and 'sin' and
> every basic operation goes through the FFI mechanism so it needs to be
> _fast_ _fast_. A neat side effect is that jhcs implementation of the
> prelude is mostly portable to different compilers.
I, too, want foreign import nonconcurrent nonreentrant to compile to
a plain call without any extras. GHC achieves that goal, even in the
presence of bound threads; I'm optimistic about jhc + state threads,
==== Bound Threads / Implementation methods ====
> I am not quite sure whether you are saying something different from
> I plan for jhc or not, my current thinking for jhc is,
> An alternate mode I'd like to experiment with one day is the complete
> oposite side of the spectrum:
> one OS thread per haskell thread, no guarentees about duplicated work
> between threads.
Let me add a third one:
One OS thread per haskell thread, locks & condition variables used to
make them behave just like state threads.
Creating threads and switching from one thread to another would be
slower than with state threads by about the time it takes to do a
trip to the kernel and back. Nonconcurrent foreign imports are just
plain calls, concurrent foreign imports have to release the lock (and
maybe signal another thread) and then re-acquire it afterwards.
Foreign exports need to check whether the current OS thread owns the
lock (TLS access), and wait for the lock if it doesn't.
And a fourth one:
Your "single true OS thread" runs all *unbound* Haskell threads and
your scheduler (on separate stacks). Bound threads run in their own
OS threads, and the scheduler will use OS thread primitives (foreign
imported nonconcurrent) to run them and to wait for their time slice
to finish (if you're doing preemption) or for them to block. It's
like "if target thread is bound, use OS thread primitives, else use
State Thread primitives to pass control to it". Foreign import
nonconcurrent is a plain call. Foreign import concurrent from a bound
thread releases the lock before calling (and waits for it
afterwards). From an unbound thread you could implement it the way
you planned to.
Call-ins would need that one stack pointer range check, and wait for
a lock if it fails.
> well, the cost of bound threads is not the cost of the call itself, it
> is that they must be serialized. foreign concurrent calls can run
> concurrently to haskell code and each other. but 'bound' foreign calls
> must wait for their dedicated thread to become available. I think
> needs to be an annotation as to which functions require boundness so
> suddenly all foreign calls arn't serialized just because they are in a
> 'forkOS'ed thread.
I think you're mistaken here. For every given OS thread, *at most
one* Haskell thread will ever be bound to it. So when you make a
foreign call, you can be sure that the OS thread that is supposed to
execute it is available *right now*, because the only Haskell thread
that has the right to cause code to be executed in the bound OS
thread has obviously finished any previous foreign calls. No need to
In the bound threads proposal, the only basic method to create a
bound thread is a call to a foreign export (or to main). The
resulting thread of Haskell execution is bonund to the OS thread that
made the call. The library function forkOS is just a call to
pthread_create or it's Windows equivalent.
It would be possible to add an annotation to "foreign export" that
states that "the Haskell thread that results from a call-in to this
function does not need to be bound" (if that would improve
performance), but back when "bound threads" were born we decided
against it to keep things simple; instead, we added
"runInUnboundThread :: IO a -> IO a" to keep the people who were
concerned about performance happy, and I have yet to see it used by
>> ==== On the implementability of "concurrent reentrant" ====
>> [...] b) The runtime thread will need to periodically check
>> whether an
>> interthread message has arrived, and if there is no work, block
>> waiting for it. The fast path of checking whether something has been
>> posted to the message queue is fast indeed - you just have to check a
>> global flag.
> I'd integrate it into the EDSM loop somehow (futex maybe) as I have a
> moral adversion for periodic checking of anything.
Yes, I was thinking of checking the flag just as a way to avoid
calling epoll if a foreign call is already waiting. Basically, you'll
want to write a message to a pipe so that it gets picked up by the
EDSM loop. After all, a pipe is nothing but a traditional unix-style
message queue :-).
> the main thing is that it is a cost paid by every foreign export.
> perhaps a flag saying "this will only be called nonconcurrently on
> exports" though, perhaps that can be an x-flag if other compilers
> take advantage of it.
Yes, such an x-flag would be entirely reasonable if those few
instructions for a stack pointer range test become significant. Those
functions will, of course, be very rare.
> how prevelant is support for __thread BTW? is it required by any
> standards or an ELFism?
It is an ELFism by birth; __declspec(thread) is the equivalent
MSVCism, and I don't know of any other equivalent features (but then
my horizon doesn't extend much beyond the Mac/Linux/Windows triad). I
hope that Apple and the mingw32 team soon implement it, too, but
that's just an unfounded hope.
That's all for now,
More information about the Haskell-prime